From 8ecef430085263b820da965cb10eab560d1ec360 Mon Sep 17 00:00:00 2001 From: Adrian Kierzkowski Date: Thu, 8 Jul 2021 17:57:27 +0200 Subject: [PATCH] Fixed bug in Serializer class (fields by default should not have DEFAULT flags, but UNSPECIFIED). Also WIP on CSV -> Database(SQL) converted. Should be renamed to SerializerDatabase --- Convert.mqh | 163 ++++++++++++++--------------------- Database.mqh | 87 +++++++++++++------ EA.mqh | 32 +++++-- Indicator.struct.serialize.h | 2 + MiniMatrix.h | 60 +++++++++++++ Serializer.mqh | 9 +- SerializerConverter.mqh | 18 +++- SerializerCsv.mqh | 97 ++++++++++----------- SerializerSql.mqh | 86 ++++++++++++++++++ 9 files changed, 368 insertions(+), 186 deletions(-) create mode 100644 MiniMatrix.h create mode 100644 SerializerSql.mqh diff --git a/Convert.mqh b/Convert.mqh index 7ff9bbe9f..782e99462 100644 --- a/Convert.mqh +++ b/Convert.mqh @@ -32,9 +32,7 @@ * Class to provide conversion methods. */ class Convert { - -public: - + public: /* * Returns order type as buy or sell. * @@ -72,7 +70,7 @@ public: * Returns OP_BUY when value is positive, OP_SELL when negative, otherwise -1. */ static ENUM_ORDER_TYPE ValueToOp(int value) { - return value == 0 ? (ENUM_ORDER_TYPE) -1 : (value > 0 ? ORDER_TYPE_BUY : ORDER_TYPE_SELL); + return value == 0 ? (ENUM_ORDER_TYPE)-1 : (value > 0 ? ORDER_TYPE_BUY : ORDER_TYPE_SELL); } /** @@ -86,21 +84,19 @@ public: * Returns OP_BUY when value is positive, OP_SELL when negative, otherwise -1. */ static ENUM_ORDER_TYPE ValueToOp(double value) { - return value == 0 ? (ENUM_ORDER_TYPE) -1 : (value > 0 ? ORDER_TYPE_BUY : ORDER_TYPE_SELL); + return value == 0 ? (ENUM_ORDER_TYPE)-1 : (value > 0 ? ORDER_TYPE_BUY : ORDER_TYPE_SELL); } /** * Points per pip given digits after decimal point of a symbol price. */ - static uint PointsPerPip(uint digits) { - return (uint) pow(10, digits - (digits < 4 ? 2 : 4)); - } + static uint PointsPerPip(uint digits) { return (uint)pow(10, digits - (digits < 4 ? 2 : 4)); } /** * Returns number of points per pip. */ static uint PointsPerPip(string _symbol = NULL) { - return PointsPerPip((uint) SymbolInfo::SymbolInfoInteger(_symbol, SYMBOL_DIGITS)); + return PointsPerPip((uint)SymbolInfo::SymbolInfoInteger(_symbol, SYMBOL_DIGITS)); } /** @@ -126,49 +122,43 @@ public: * Convert pips into price value. */ static double PipsToValue(double pips, string _symbol = NULL) { - return PipsToValue(pips, (uint) SymbolInfo::SymbolInfoInteger(_symbol, SYMBOL_DIGITS)); + return PipsToValue(pips, (uint)SymbolInfo::SymbolInfoInteger(_symbol, SYMBOL_DIGITS)); } /** * Convert value into pips given price value and digits. */ - static double ValueToPips(double value, uint digits) { - return value * pow(10, digits < 4 ? 2 : 4); - } + static double ValueToPips(double value, uint digits) { return value * pow(10, digits < 4 ? 2 : 4); } /** * Convert value into pips. */ static double ValueToPips(double value, string _symbol = NULL) { - return ValueToPips(value, (uint) SymbolInfo::SymbolInfoInteger(_symbol, SYMBOL_DIGITS)); + return ValueToPips(value, (uint)SymbolInfo::SymbolInfoInteger(_symbol, SYMBOL_DIGITS)); } /** * Convert pips into points. */ - static uint PipsToPoints(double pips, int digits) { - return (uint) pips * PointsPerPip(digits); - } + static uint PipsToPoints(double pips, int digits) { return (uint)pips * PointsPerPip(digits); } /** * Convert pips into points. */ static uint PipsToPoints(double pips, string _symbol = NULL) { - return PipsToPoints(pips, (uint) SymbolInfo::SymbolInfoInteger(_symbol, SYMBOL_DIGITS)); + return PipsToPoints(pips, (uint)SymbolInfo::SymbolInfoInteger(_symbol, SYMBOL_DIGITS)); } /** * Convert points into pips. */ - static double PointsToPips(long pts, int digits) { - return (double) (pts / PointsPerPip(digits)); - } + static double PointsToPips(long pts, int digits) { return (double)(pts / PointsPerPip(digits)); } /** * Convert points into pips. */ static double PointsToPips(long pts, string _symbol = NULL) { - return PointsToPips(pts, (uint) SymbolInfo::SymbolInfoInteger(_symbol, SYMBOL_DIGITS)); + return PointsToPips(pts, (uint)SymbolInfo::SymbolInfoInteger(_symbol, SYMBOL_DIGITS)); } /** @@ -177,19 +167,19 @@ public: */ static double PointsToValue(long pts, int mode, string _symbol = NULL) { switch (mode) { - case 0: // Forex. + case 0: // Forex. // In currencies a tick is a point. return pts * SymbolInfo::SymbolInfoDouble(_symbol, SYMBOL_TRADE_TICK_SIZE); - case 1: // CFD. + case 1: // CFD. // In metals a Tick is still the smallest change, but is larger than a point. // If price can change from 123.25 to 123.50, // you have a TickSize of 0.25 and a point of 0.01. Pip has no meaning. // @todo return pts * SymbolInfo::SymbolInfoDouble(_symbol, SYMBOL_TRADE_TICK_SIZE); - case 2: // Futures. + case 2: // Futures. // @todo return pts * SymbolInfo::SymbolInfoDouble(_symbol, SYMBOL_TRADE_TICK_SIZE); - case 3: // CFD for indices. + case 3: // CFD for indices. // @todo return pts * SymbolInfo::SymbolInfoDouble(_symbol, SYMBOL_TRADE_TICK_SIZE); } @@ -201,18 +191,18 @@ public: */ static double PointsToValue(long pts, int mode, int digits) { switch (mode) { - case 0: // Forex. - return PipsToValue((double) pts / PointsPerPip(digits), digits); - case 1: // CFD. + case 0: // Forex. + return PipsToValue((double)pts / PointsPerPip(digits), digits); + case 1: // CFD. // In metals a Tick is still the smallest change, but is larger than a point. // @todo - return PipsToValue((double) pts / PointsPerPip(digits), digits); - case 2: // Futures. + return PipsToValue((double)pts / PointsPerPip(digits), digits); + case 2: // Futures. // @todo - return PipsToValue((double) pts / PointsPerPip(digits), digits); - case 3: // CFD for indices. + return PipsToValue((double)pts / PointsPerPip(digits), digits); + case 3: // CFD for indices. // @todo - return PipsToValue((double) pts / PointsPerPip(digits), digits); + return PipsToValue((double)pts / PointsPerPip(digits), digits); } return false; } @@ -221,7 +211,7 @@ public: * Convert points into price value. */ static double PointsToValue(long pts, string _symbol = NULL) { - return PointsToValue(pts, (int) SymbolInfo::SymbolInfoInteger(_symbol, SYMBOL_TRADE_CALC_MODE)); + return PointsToValue(pts, (int)SymbolInfo::SymbolInfoInteger(_symbol, SYMBOL_TRADE_CALC_MODE)); } /** @@ -249,8 +239,9 @@ public: /** * Get the difference between two price values (in pips). */ - static double GetValueDiffInPips(double price1, double price2, bool abs = false, int digits = NULL, string _symbol = NULL) { - digits = digits ? digits : (int) SymbolInfo::SymbolInfoInteger(_symbol, SYMBOL_DIGITS); + static double GetValueDiffInPips(double price1, double price2, bool abs = false, int digits = NULL, + string _symbol = NULL) { + digits = digits ? digits : (int)SymbolInfo::SymbolInfoInteger(_symbol, SYMBOL_DIGITS); return ValueToPips(abs ? fabs(price1 - price2) : (price1 - price2), digits); } @@ -258,15 +249,21 @@ public: * Add currency sign to the plain value. */ static string ValueWithCurrency(double value, int digits = 2, string currency = "USD") { - unsigned char sign; bool prefix = true; + unsigned char sign; + bool prefix = true; currency = currency == "" ? AccountInfoString(ACCOUNT_CURRENCY) : currency; - if (currency == "USD") sign = (unsigned char) '$'; - else if (currency == "GBP") sign = (unsigned char) 0xA3; // ANSI code. - else if (currency == "EUR") sign = (unsigned char) 0x80; // ANSI code. - else { sign = NULL; prefix = false; } - return prefix - ? CharToString(sign) + DoubleToString(value, digits) - : DoubleToString(value, digits) + CharToString(sign); + if (currency == "USD") + sign = (unsigned char)'$'; + else if (currency == "GBP") + sign = (unsigned char)0xA3; // ANSI code. + else if (currency == "EUR") + sign = (unsigned char)0x80; // ANSI code. + else { + sign = NULL; + prefix = false; + } + return prefix ? CharToString(sign) + DoubleToString(value, digits) + : DoubleToString(value, digits) + CharToString(sign); } /** @@ -274,10 +271,10 @@ public: */ static string IntToHex(long long_number) { string result; - int integer_number = (int) long_number; - for (int i = 0; i < 4; i++){ - int byte = (integer_number >> (i*8)) & 0xff; - result += StringFormat("%02x", byte); + int integer_number = (int)long_number; + for (int i = 0; i < 4; i++) { + int byte = (integer_number >> (i * 8)) & 0xff; + result += StringFormat("%02x", byte); } return result; } @@ -285,25 +282,25 @@ public: /** * Convert character into integer. */ - static int CharToInt(int &_chars[]) { + static int CharToInt(int& _chars[]) { return ((_chars[0]) | (_chars[1] << 8) | (_chars[2] << 16) | (_chars[3] << 24)); } /** * Assume: len % 4 == 0. */ - static int String4ToIntArray(int &output[], string in) { + static int String4ToIntArray(int& output[], string in) { int len; int i, j; len = StringLen(in); if (len % 4 != 0) len = len - len % 4; int size = ArraySize(output); if (size < len / 4) { - ArrayResize(output, len/4); + ArrayResize(output, len / 4); } for (i = 0, j = 0; j < len; i++, j += 4) { - output[i] = (StringGetCharacter(in, j)) | ((StringGetCharacter(in, j + 1)) << 8) - | ((StringGetCharacter(in, j+2)) << 16) | ((StringGetCharacter(in, j + 3)) << 24); + output[i] = (StringGetCharacter(in, j)) | ((StringGetCharacter(in, j + 1)) << 8) | + ((StringGetCharacter(in, j + 2)) << 16) | ((StringGetCharacter(in, j + 3)) << 24); } return (len / 4); } @@ -312,57 +309,31 @@ public: _out = _value != "" && _value != NULL && _value != "0" && _value != "false"; } - static void StringToType(string _value, char& _out) { - _out = (char)StringToInteger(_value); - } + static void StringToType(string _value, char& _out) { _out = (char)StringToInteger(_value); } - static void StringToType(string _value, unsigned char& _out) { - _out = (unsigned char)StringToInteger(_value); - } + static void StringToType(string _value, unsigned char& _out) { _out = (unsigned char)StringToInteger(_value); } - static void StringToType(string _value, int& _out) { - _out = (int)StringToInteger(_value); - } + static void StringToType(string _value, int& _out) { _out = (int)StringToInteger(_value); } - static void StringToType(string _value, unsigned int& _out) { - _out = (unsigned int)StringToInteger(_value); - } + static void StringToType(string _value, unsigned int& _out) { _out = (unsigned int)StringToInteger(_value); } - static void StringToType(string _value, long& _out) { - _out = (long)StringToInteger(_value); - } + static void StringToType(string _value, long& _out) { _out = (long)StringToInteger(_value); } - static void StringToType(string _value, unsigned long& _out) { - _out = (unsigned long)StringToInteger(_value); - } + static void StringToType(string _value, unsigned long& _out) { _out = (unsigned long)StringToInteger(_value); } - static void StringToType(string _value, short& _out) { - _out = (short) StringToInteger(_value); - } + static void StringToType(string _value, short& _out) { _out = (short)StringToInteger(_value); } - static void StringToType(string _value, unsigned short& _out) { - _out = (unsigned short) StringToInteger(_value); - } + static void StringToType(string _value, unsigned short& _out) { _out = (unsigned short)StringToInteger(_value); } - static void StringToType(string _value, float& _out) { - _out = (float)StringToDouble(_value); - } + static void StringToType(string _value, float& _out) { _out = (float)StringToDouble(_value); } - static void StringToType(string _value, double& _out) { - _out = StringToDouble(_value); - } + static void StringToType(string _value, double& _out) { _out = StringToDouble(_value); } - static void StringToType(string _value, color& _out) { - _out = StringToColor(_value); - } + static void StringToType(string _value, color& _out) { _out = StringToColor(_value); } - static void StringToType(string _value, datetime& _out) { - _out = StringToTime(_value); - } + static void StringToType(string _value, datetime& _out) { _out = StringToTime(_value); } - static void StringToType(string _value, string& _out) { - _out = _value; - } + static void StringToType(string _value, string& _out) { _out = _value; } /** * Converts MqlParam struct to double. @@ -407,7 +378,7 @@ public: return param.integer_value; case TYPE_DOUBLE: case TYPE_FLOAT: - return (int) param.double_value; + return (int)param.double_value; case TYPE_CHAR: case TYPE_COLOR: case TYPE_STRING: @@ -416,8 +387,6 @@ public: } return INT_MIN; } - }; - -#endif // CONVERT_MQH +#endif // CONVERT_MQH diff --git a/Database.mqh b/Database.mqh index 9289e9f3d..830306f5a 100644 --- a/Database.mqh +++ b/Database.mqh @@ -34,6 +34,7 @@ // Includes. #include "DictStruct.mqh" +#include "MiniMatrix.h" // Enums. enum DATABASE_COLUMN_FLAGS { @@ -84,33 +85,23 @@ struct DatabaseTableSchema { } } // Methods. - bool AddColumn(DatabaseTableColumnEntry &column) { - return columns.Push(column); - } + bool AddColumn(DatabaseTableColumnEntry &column) { return columns.Push(column); } }; // Struct table entry for SymbolInfo. #ifdef SYMBOLINFO_MQH struct DbSymbolInfoEntry : public SymbolInfoEntry { DatabaseTableSchema schema; // Constructors. - DbSymbolInfoEntry() - { - DefineSchema(); - } - DbSymbolInfoEntry(const MqlTick &_tick, const string _symbol = NULL) - : SymbolInfoEntry(_tick, _symbol) - { + DbSymbolInfoEntry() { DefineSchema(); } + DbSymbolInfoEntry(const MqlTick &_tick, const string _symbol = NULL) : SymbolInfoEntry(_tick, _symbol) { DefineSchema(); } // Methods. void DefineSchema() { DatabaseTableColumnEntry _columns[] = { - {"bid", TYPE_DOUBLE}, - {"ask", TYPE_DOUBLE}, - {"last", TYPE_DOUBLE}, - {"spread", TYPE_DOUBLE}, - {"volume", TYPE_INT}, - }; + {"bid", TYPE_DOUBLE}, {"ask", TYPE_DOUBLE}, {"last", TYPE_DOUBLE}, + {"spread", TYPE_DOUBLE}, {"volume", TYPE_INT}, + }; for (int i = 0; i < ArraySize(_columns); i++) { schema.columns.Push(_columns[i]); } @@ -127,7 +118,7 @@ class Database { /** * Class constructor. */ - Database(string _filename, unsigned int _flags) { + Database(string _filename, unsigned int _flags = DATABASE_OPEN_CREATE) { #ifdef __MQL5__ handle = DatabaseOpen(_filename, _flags); #else @@ -147,6 +138,21 @@ class Database { /* Table methods */ + /** + * Checks if table exists. + */ + bool TableExists(string _name) { return DatabaseTableExists(handle, _name); } + + /** + * Creates table if not yet exist. + */ + bool CreateTableIfNotExist(string _name, DatabaseTableSchema &_schema) { + if (TableExists(_name)) { + return true; + } + return CreateTable(_name, _schema); + } + /** * Creates table. */ @@ -159,12 +165,11 @@ class Database { return _result; } string query = "", subquery = ""; - for (DictStructIterator iter = _schema.columns.Begin(); - iter.IsValid(); ++iter) { - subquery += StringFormat("%s %s %s,", iter.Value().GetName(), iter.Value().GetDatatype(), - iter.Value().GetFlags()); + for (DictStructIterator iter = _schema.columns.Begin(); iter.IsValid(); ++iter) { + subquery += + StringFormat("%s %s %s,", iter.Value().GetName(), iter.Value().GetDatatype(), iter.Value().GetFlags()); } - subquery = StringSubstr(subquery, 0, StringLen(subquery) - 1); // Removes extra comma. + subquery = StringSubstr(subquery, 0, StringLen(subquery) - 1); // Removes extra comma. query = StringFormat("CREATE TABLE %s(%s);", _name, subquery); if (_result = DatabaseExecute(handle, query)) { ResetLastError(); @@ -188,6 +193,36 @@ class Database { /* Import methods */ + /** + * Imports data into table. First row must contain column names. + */ + template + bool ImportData(const string _name, MiniMatrix2d &data) { + bool _result = true; + DatabaseTableSchema _schema = GetTableSchema(_name); + string _query = "", _cols = "", _vals = ""; + for (DictStructIterator iter = _schema.columns.Begin(); iter.IsValid(); ++iter) { + _cols += iter.Value().name + ","; + } + _cols = StringSubstr(_cols, 0, StringLen(_cols) - 1); // Removes extra comma. +#ifdef __MQL5__ + if (DatabaseTransactionBegin(handle)) { + for (DictStructIterator iter = _bstruct.Begin(); iter.IsValid(); ++iter) { + _query = StringFormat("INSERT INTO %s(%s) VALUES (%s)", _name, _cols, iter.Value().ToCSV()); + _result &= DatabaseExecute(handle, _query); + } + } + if (_result) { + DatabaseTransactionCommit(handle); + } else { + DatabaseTransactionRollback(handle); + } +#else + return false; +#endif + return _result; + } + #ifdef BUFFER_STRUCT_MQH /** * Imports BufferStruct records into a table. @@ -197,15 +232,13 @@ class Database { bool _result = true; DatabaseTableSchema _schema = GetTableSchema(_name); string _query = "", _cols = "", _vals = ""; - for (DictStructIterator iter = _schema.columns.Begin(); - iter.IsValid(); ++iter) { + for (DictStructIterator iter = _schema.columns.Begin(); iter.IsValid(); ++iter) { _cols += iter.Value().name + ","; } - _cols = StringSubstr(_cols, 0, StringLen(_cols) - 1); // Removes extra comma. + _cols = StringSubstr(_cols, 0, StringLen(_cols) - 1); // Removes extra comma. #ifdef __MQL5__ if (DatabaseTransactionBegin(handle)) { - for (DictStructIterator iter = _bstruct.Begin(); - iter.IsValid(); ++iter) { + for (DictStructIterator iter = _bstruct.Begin(); iter.IsValid(); ++iter) { _query = StringFormat("INSERT INTO %s(%s) VALUES (%s)", _name, _cols, iter.Value().ToCSV()); _result &= DatabaseExecute(handle, _query); } diff --git a/EA.mqh b/EA.mqh index 92830a47b..82b4c1489 100644 --- a/EA.mqh +++ b/EA.mqh @@ -43,6 +43,7 @@ #include "SerializerConverter.mqh" #include "SerializerCsv.mqh" #include "SerializerJson.mqh" +#include "SerializerSql.mqh" #include "Strategy.mqh" #include "SummaryReport.mqh" #include "Task.mqh" @@ -363,14 +364,29 @@ class EA { if ((eparams.data_store & EA_DATA_STORE_CHART) != 0) { string _key_chart = "Chart"; _key_chart += StringFormat("-%d-%d", data_chart.GetMin(), data_chart.GetMax()); - if ((_methods & EA_DATA_EXPORT_CSV) != 0) { - SerializerConverter _stub_chart = - Serializer::MakeStubObject>(SERIALIZER_FLAG_SKIP_HIDDEN); - SerializerConverter::FromObject(data_chart, SERIALIZER_FLAG_SKIP_HIDDEN) - .ToFile(_key_chart + ".csv", SERIALIZER_FLAG_SKIP_HIDDEN, &_stub_chart); - } - if ((_methods & EA_DATA_EXPORT_DB) != 0) { - // @todo: Use Database class. + if ((_methods & EA_DATA_EXPORT_CSV) != 0 || (_methods & EA_DATA_EXPORT_DB) != 0) { + int _serializer_flags = SERIALIZER_FLAG_SKIP_HIDDEN | SERIALIZER_FLAG_INCLUDE_DYNAMIC; + + SerializerConverter _stub_chart = Serializer::MakeStubObject>(_serializer_flags); + + ChartEntry entry1; + BarOHLC ohlc1(1.2f, 1.21f, 1.19f, 1.20f, D '2025-01-01 10:00:00'); + entry1.bar = BarEntry(ohlc1); + + ChartEntry entry2; + BarOHLC ohlc2(1.23f, 1.20f, 1.15f, 1.23f, D '2025-01-01 10:00:10'); + entry2.bar = BarEntry(ohlc2); + + data_chart.Add(entry1, D '2025-01-01 10:00:00'); + data_chart.Add(entry2, D '2025-01-01 10:00:10'); + + SerializerConverter csv = SerializerConverter::FromObject(data_chart, _serializer_flags); + + if ((_methods & EA_DATA_EXPORT_CSV) != 0) { + csv.ToFile(_key_chart + ".csv", _serializer_flags, &_stub_chart); + } else if ((_methods & EA_DATA_EXPORT_DB) != 0) { + SerializerSql::ConvertToFile(csv, _key_chart + ".sql", _serializer_flags, &_stub_chart); + } } if ((_methods & EA_DATA_EXPORT_JSON) != 0) { SerializerConverter _stub_chart = diff --git a/Indicator.struct.serialize.h b/Indicator.struct.serialize.h index a3b384b88..0f9049fc8 100644 --- a/Indicator.struct.serialize.h +++ b/Indicator.struct.serialize.h @@ -25,6 +25,8 @@ * Includes Indicator's struct serializers. */ +#include "Serializer.mqh" + // Forward class declaration. class Serializer; diff --git a/MiniMatrix.h b/MiniMatrix.h new file mode 100644 index 000000000..3ceaaa5ab --- /dev/null +++ b/MiniMatrix.h @@ -0,0 +1,60 @@ +//+------------------------------------------------------------------+ +//| EA31337 framework | +//| Copyright 2016-2021, EA31337 Ltd | +//| https://github.com/EA31337 | +//+------------------------------------------------------------------+ + +/* + * This file is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +// Prevents processing this includes file for the second time. +#ifndef MINIMATRIX_MQH +#define MINIMATRIX_MQH + +template +class MiniMatrix2d { + public: + T data[]; + int size_x; + int size_y; + + MiniMatrix2d() {} + MiniMatrix2d(int _size_x, int _size_y) : size_x(_size_x), size_y(_size_y) { Resize(_size_x, _size_y); } + + void Resize(int _size_x, int _size_y) { + ArrayResize(data, _size_x * _size_y); + size_x = _size_x; + size_y = _size_y; + } + + T Get(int _x, int _y) { return data[(size_x * _y) + _x]; } + + void Set(int _x, int _y, T _value) { + int index = (size_x * _y) + _x; + + if (index < 0 || index >= (size_x * size_y)) { + Alert("Array out of range!"); + } + + data[index] = _value; + } + + int SizeX() { return size_x; } + + int SizeY() { return size_y; } +}; + +#endif diff --git a/Serializer.mqh b/Serializer.mqh index c3e4dc3f8..40ae862e0 100644 --- a/Serializer.mqh +++ b/Serializer.mqh @@ -26,7 +26,6 @@ // Includes. #include "Convert.mqh" -#include "DictBase.mqh" #include "Log.mqh" #include "Serializer.define.h" #include "Serializer.enum.h" @@ -188,7 +187,7 @@ class Serializer { * Serializes or unserializes object. */ template - void PassObject(T& self, string name, V& value, unsigned int flags = SERIALIZER_FIELD_FLAG_DEFAULT) { + void PassObject(T& self, string name, V& value, unsigned int flags = SERIALIZER_FIELD_FLAG_UNSPECIFIED) { PassStruct(self, name, value, flags); } @@ -196,7 +195,7 @@ class Serializer { * Serializes or unserializes object that acts as a value. */ template - void PassValueObject(T& self, string name, V& value, unsigned int flags = SERIALIZER_FIELD_FLAG_DEFAULT) { + void PassValueObject(T& self, string name, V& value, unsigned int flags = SERIALIZER_FIELD_FLAG_UNSPECIFIED) { if (_mode == Serialize) { value.Serialize(this); fp_precision = SERIALIZER_DEFAULT_FP_PRECISION; @@ -269,7 +268,7 @@ class Serializer { * Serializes or unserializes structure. */ template - void PassStruct(T& self, string name, V& value, unsigned int flags = SERIALIZER_FIELD_FLAG_DEFAULT) { + void PassStruct(T& self, string name, V& value, unsigned int flags = SERIALIZER_FIELD_FLAG_UNSPECIFIED) { if (_mode == Serialize) { if (!IsFieldVisible(_flags, flags)) return; } @@ -385,7 +384,7 @@ class Serializer { * Serializes or unserializes simple value. */ template - SerializerNode* Pass(T& self, string name, V& value, unsigned int flags = SERIALIZER_FIELD_FLAG_DEFAULT) { + SerializerNode* Pass(T& self, string name, V& value, unsigned int flags = SERIALIZER_FIELD_FLAG_UNSPECIFIED) { SerializerNode* child = NULL; bool _skip_push = (_flags & SERIALIZER_FLAG_SKIP_PUSH) == SERIALIZER_FLAG_SKIP_PUSH; diff --git a/SerializerConverter.mqh b/SerializerConverter.mqh index 8f442454d..c1325844e 100644 --- a/SerializerConverter.mqh +++ b/SerializerConverter.mqh @@ -30,7 +30,6 @@ class SerializerNode; // Includes. #include "File.mqh" #include "Serializer.enum.h" -#include "SerializerDict.mqh" #include "SerializerNode.mqh" class SerializerConverter { @@ -128,6 +127,23 @@ class SerializerConverter { return true; } +#ifdef SERIALIZER_CSV_MQH + + /** + * Converts object into CSV and then SQL. Thus way we don't duplicate CSV serializer's code. + */ + string ToSQL(unsigned int _stringify_flags = 0, void* _stub = NULL); + + /** + * Converts object into CSV and then SQL. Thus way we don't duplicate CSV serializer's code. + */ + bool ToSQLFile(string _path, unsigned int _stringify_flags = 0, void* _stub = NULL) { + string _data = ToSQL(_stringify_flags, _stub); + return File::SaveFile(_path, _data); + } + +#endif + void Clean() { if (root_node != NULL) { delete root_node; diff --git a/SerializerCsv.mqh b/SerializerCsv.mqh index 6ee3ff446..c39e9408f 100644 --- a/SerializerCsv.mqh +++ b/SerializerCsv.mqh @@ -29,9 +29,8 @@ #include "DictObject.mqh" #include "DictStruct.mqh" #include "Matrix.mqh" +#include "MiniMatrix.h" #include "Object.mqh" -#include "Serializer.mqh" -#include "SerializerConverter.mqh" #include "SerializerNode.mqh" struct CsvTitle { @@ -41,32 +40,6 @@ struct CsvTitle { CsvTitle(int _column_index = 0, string _title = "") : column_index(_column_index), title(_title) {} }; -template -class MiniMatrix2d { - public: - T data[]; - int size_x; - int size_y; - - MiniMatrix2d(int _size_x, int _size_y) : size_x(_size_x), size_y(_size_y) { ArrayResize(data, _size_x * _size_y); } - - T Get(int _x, int _y) { return data[(size_x * _y) + _x]; } - - void Set(int _x, int _y, T _value) { - int index = (size_x * _y) + _x; - - if (index < 0 || index >= (size_x * size_y)) { - Alert("Array out of range!"); - } - - data[index] = _value; - } - - int SizeX() { return size_x; } - - int SizeY() { return size_y; } -}; - enum ENUM_SERIALIZER_CSV_FLAGS { SERIALIZER_CSV_INCLUDE_TITLES = 1, SERIALIZER_CSV_INCLUDE_TITLES_TREE = SERIALIZER_CSV_INCLUDE_TITLES + 2, @@ -75,7 +48,9 @@ enum ENUM_SERIALIZER_CSV_FLAGS { class SerializerCsv { public: - static string Stringify(SerializerNode* _root, unsigned int serializer_flags = 0, void* serializer_aux_arg = NULL) { + static string Stringify(SerializerNode* _root, unsigned int serializer_flags = 0, void* serializer_aux_arg = NULL, + MiniMatrix2d* _matrix_out = NULL, + MiniMatrix2d* _column_types_out = NULL) { SerializerConverter* _stub = (SerializerConverter*)serializer_aux_arg; if (_stub == NULL) { @@ -104,7 +79,19 @@ class SerializerCsv { ++_num_columns; } - MiniMatrix2d _cells(_num_columns, _num_rows); + MiniMatrix2d _cells; + MiniMatrix2d _column_types; + + if (_matrix_out == NULL) { + _matrix_out = &_cells; + } + + if (_column_types_out == NULL) { + _column_types_out = &_column_types; + } + + _matrix_out.Resize(_num_columns, _num_rows); + _column_types_out.Resize(_num_columns, 1); #ifdef __debug__ Print("Stub: ", _stub.Node().ToString()); @@ -112,23 +99,23 @@ class SerializerCsv { Print("Size: ", _num_columns, " x ", _num_rows); #endif - if (!SerializerCsv::FlattenNode(_root, _stub.Node(), _cells, _include_key ? 1 : 0, _include_titles ? 1 : 0, - serializer_flags)) { + if (!SerializerCsv::FlattenNode(_root, _stub.Node(), _matrix_out, _column_types_out, _include_key ? 1 : 0, + _include_titles ? 1 : 0, serializer_flags)) { Alert("SerializerCsv: Error occured during flattening!"); } string _result; - for (int y = 0; y < _cells.SizeY(); ++y) { - for (int x = 0; x < _cells.SizeX(); ++x) { - _result += _cells.Get(x, y); + for (int y = 0; y < _matrix_out.SizeY(); ++y) { + for (int x = 0; x < _matrix_out.SizeX(); ++x) { + _result += _matrix_out.Get(x, y); - if (x != _cells.SizeX() - 1) { + if (x != _matrix_out.SizeX() - 1) { _result += ","; } } - if (y != _cells.SizeY() - 1) _result += "\n"; + if (y != _matrix_out.SizeY() - 1) _result += "\n"; } _stub.Clean(); @@ -158,8 +145,8 @@ class SerializerCsv { /** * */ - static bool FlattenNode(SerializerNode* _data, SerializerNode* _stub, MiniMatrix2d& _cells, int _column, - int _row, int _flags) { + static bool FlattenNode(SerializerNode* _data, SerializerNode* _stub, MiniMatrix2d& _cells, + MiniMatrix2d* _column_types, int _column, int _row, int _flags) { unsigned int _data_entry_idx; bool _include_key = bool(_flags & SERIALIZER_CSV_INCLUDE_KEY); @@ -173,7 +160,7 @@ class SerializerCsv { _cells.Set(0, _row + _data_entry_idx, key); } - if (!SerializerCsv::FillRow(_data.GetChild(_data_entry_idx), _stub.GetChild(0), _cells, _column, + if (!SerializerCsv::FillRow(_data.GetChild(_data_entry_idx), _stub.GetChild(0), _cells, _column_types, _column, _row + _data_entry_idx, 0, 0, _flags)) { return false; } @@ -183,7 +170,8 @@ class SerializerCsv { if (_data.IsArray()) { // Stub is an object, but data is an array (should be?). for (_data_entry_idx = 0; _data_entry_idx < _data.NumChildren(); ++_data_entry_idx) { - if (!SerializerCsv::FillRow(_data.GetChild(_data_entry_idx), _stub, _cells, _column, _row, 0, 0, _flags)) { + if (!SerializerCsv::FillRow(_data.GetChild(_data_entry_idx), _stub, _cells, _column_types, _column, _row, 0, + 0, _flags)) { return false; } @@ -191,7 +179,7 @@ class SerializerCsv { } } else { // Stub and object are both arrays. - if (!SerializerCsv::FillRow(_data, _stub, _cells, _column, _row, 0, 0, _flags)) { + if (!SerializerCsv::FillRow(_data, _stub, _cells, _column_types, _column, _row, 0, 0, _flags)) { return false; } } @@ -203,18 +191,27 @@ class SerializerCsv { /** * */ - static bool FillRow(SerializerNode* _data, SerializerNode* _stub, MiniMatrix2d& _cells, int _column, int _row, - int _index, int _level, int _flags) { + static bool FillRow(SerializerNode* _data, SerializerNode* _stub, MiniMatrix2d& _cells, + MiniMatrix2d* _column_types, int _column, int _row, int _index, + int _level, int _flags) { unsigned int _data_entry_idx, _entry_size; if (_stub.IsObject()) { for (_data_entry_idx = 0; _data_entry_idx < _data.NumChildren(); ++_data_entry_idx) { + if (_stub.NumChildren() == 0) { + Print("Stub is empty for object representation of: ", _data.ToString(false, 2)); + Print( + "Note that if you're serializing a dictionary, your stub must contain a single, dummy key and maximum " + "possible object representation."); + Print("Missing key \"", _data.Key(), "\" in stub."); + DebugBreak(); + } _entry_size = MathMax(_stub.GetChild(_data_entry_idx).TotalNumChildren(), _data.GetChild(_data_entry_idx).TotalNumChildren()); if (!SerializerCsv::FillRow(_data.GetChild(_data_entry_idx), - _stub != NULL ? _stub.GetChild(_data_entry_idx) : NULL, _cells, _column, _row, - _data_entry_idx, _level + 1, _flags)) { + _stub != NULL ? _stub.GetChild(_data_entry_idx) : NULL, _cells, _column_types, + _column, _row, _data_entry_idx, _level + 1, _flags)) { return false; } @@ -225,8 +222,8 @@ class SerializerCsv { _entry_size = MathMax(_stub.GetChild(_data_entry_idx).TotalNumChildren(), _data.GetChild(_data_entry_idx).TotalNumChildren()); - if (!SerializerCsv::FillRow(_data.GetChild(_data_entry_idx), _stub.GetChild(0), _cells, _column, _row, - _data_entry_idx, _level + 1, _flags)) { + if (!SerializerCsv::FillRow(_data.GetChild(_data_entry_idx), _stub.GetChild(0), _cells, _column_types, _column, + _row, _data_entry_idx, _level + 1, _flags)) { return false; } @@ -238,6 +235,10 @@ class SerializerCsv { bool _include_titles = bool(_flags & SERIALIZER_CSV_INCLUDE_TITLES); bool _include_titles_tree = (_flags & SERIALIZER_CSV_INCLUDE_TITLES_TREE) == SERIALIZER_CSV_INCLUDE_TITLES_TREE; + if (_column_types != NULL) { + _column_types.Set(_column, 0, _data.GetValueParam().GetType()); + } + if (_include_titles && StringLen(_cells.Get(_column, _row - 1)) == 0) { if (_include_titles_tree) { // Creating fully qualified title. diff --git a/SerializerSql.mqh b/SerializerSql.mqh new file mode 100644 index 000000000..0b75fb2c1 --- /dev/null +++ b/SerializerSql.mqh @@ -0,0 +1,86 @@ +//+------------------------------------------------------------------+ +//| EA31337 framework | +//| Copyright 2016-2021, EA31337 Ltd | +//| https://github.com/EA31337 | +//+------------------------------------------------------------------+ + +/* + * This file is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +// Prevents processing this includes file for the second time. +#ifndef SERIALIZER_SQL_MQH +#define SERIALIZER_SQL_MQH + +// Includes. +#include "Database.mqh" +#include "SerializerConverter.mqh" +#include "SerializerCsv.mqh" + +class SerializerSql { + public: + static ENUM_DATATYPE CsvParamTypeToSqlType(SerializerNodeParamType _param_type) { + switch (_param_type) { + case SerializerNodeParamBool: + return TYPE_BOOL; + case SerializerNodeParamLong: + return TYPE_LONG; + case SerializerNodeParamDouble: + return TYPE_DOUBLE; + case SerializerNodeParamString: + return TYPE_STRING; + } + Print("Error: CsvParamTypeToSqlType: wrong _param_type parameter!"); + DebugBreak(); + return (ENUM_DATATYPE)-1; + } + + static string Convert(SerializerConverter& source, unsigned int _stringify_flags = 0, void* _stub = NULL) { + // We must have titles tree as + MiniMatrix2d _matrix_out; + MiniMatrix2d _column_types; + string _csv = SerializerCsv::Stringify(source.root_node, _stringify_flags | SERIALIZER_CSV_INCLUDE_TITLES, _stub, + &_matrix_out, &_column_types); + + Database _db("test.sqlite"); + DatabaseTableSchema _schema; + int i; + + for (i = 0; i < _matrix_out.SizeX(); ++i) { + DatabaseTableColumnEntry _column; + _column.name = _matrix_out.Get(i, 0); + _column.type = CsvParamTypeToSqlType(_column_types.Get(i, 0)); + _column.flags = 0; + _column.char_size = 0; + _schema.AddColumn(_column); + } + + _db.CreateTableIfNotExist("bla", _schema); + + Print("Conversion from: \n", _csv); + return "INSERT INTO ..."; + } + + /** + * Converts object into CSV and then SQL. Thus way we don't duplicate CSV serializer's code. + */ + static bool ConvertToFile(SerializerConverter& source, string _path, unsigned int _stringify_flags = 0, + void* _stub = NULL) { + string _data = Convert(source, _stringify_flags, _stub); + return File::SaveFile(_path, _data); + } +}; + +#endif