forked from rosh/MQL5Book
824 lines
24 KiB
MQL5
824 lines
24 KiB
MQL5
//+------------------------------------------------------------------+
| |
//| DBSQLite.mqh |
| |
//| Copyright 2022, MetaQuotes Ltd. |
| |
//| https://www.mql5.com |
| |
//| Basic ORM (Object-Relational Mapping) MQL5 <-> SQLite |
| |
//+------------------------------------------------------------------+
| |
// NB: structures with simple types and strings are allowed only
| |
// for binding to DB
| |
| |
// use this in your code to disable debug logging by PRTF
| |
// #define PRTF
| |
| |
#ifndef PRTF
| |
#include "PRTF.mqh"
| |
#endif
| |
#include "Defines.mqh"
| |
#include "AutoPtr.mqh"
| |
#include "TypeName.mqh"
| |
| |
// use this to fill empty MqlParam.string_value with param name by default
| |
// #define DB_PARAM_NAMES
| |
| |
#define TYPE_NULL ((ENUM_DATATYPE)(0))
| |
#define TYPE_BLOB ((ENUM_DATATYPE)(-1))
| |
| |
#define DB_FIELD(S,T,N) struct S##_##T##_##N: DBEntity<S>::DBField<T> { S##_##T##_##N() : DBEntity<S>::DBField<T>(#N) {}}; const S##_##T##_##N _##S##_##T##_##N;
| |
#define DB_FIELD_C1(S,T,N,C1) struct S##_##T##_##N: DBEntity<S>::DBField<T> { S##_##T##_##N() : DBEntity<S>::DBField<T>(#N, C1) {}}; const S##_##T##_##N _##S##_##T##_##N;
| |
#define DB_FIELD_C2(S,T,N,C1,C2) struct S##_##T##_##N: DBEntity<S>::DBField<T> { S##_##T##_##N() : DBEntity<S>::DBField<T>(#N, C1 + " " + C2) {}}; const S##_##T##_##N _##S##_##T##_##N;
| |
| |
/*
| |
DB_FIELD expands to:
| |
| |
struct Struct_Type_Name: DBEntity<Struct>::DBField<Type>
| |
{
| |
Struct_Type_Name() : DBEntity<Struct>::DBField<Type>("Name"){}
| |
};
| |
const Struct_Type_Name _Struct_Type_Name;
| |
*/
| |
| |
//+------------------------------------------------------------------+
| |
//| String templates of DB-constraints per field (useful reference) |
| |
//+------------------------------------------------------------------+
| |
namespace DB_CONSTRAINT
| |
{
| |
const string PRIMARY_KEY = "PRIMARY KEY";
| |
const string UNIQUE = "UNIQUE";
| |
const string NOT_NULL = "NOT NULL";
| |
const string CHECK = "CHECK (%s)"; // expression required
| |
const string CURRENT_TIMESTAMP = "CURRENT_TIMESTAMP"; // "CURRENT_TIMESTAMP" doesn't work in SQLite as expected
| |
const string CURRENT_TIME = "CURRENT_TIME"; // -- // --
| |
const string CURRENT_DATE = "CURRENT_DATE"; // -- // --
| |
const string AUTOINCREMENT = "AUTOINCREMENT";
| |
const string DEFAULT = "DEFAULT (%s)"; // expression (e.g. constant, function) required
| |
}
| |
| |
//+------------------------------------------------------------------+
| |
//| General DB types |
| |
//+------------------------------------------------------------------+
| |
namespace DB_TYPE
| |
{
| |
const string INTEGER = "INTEGER";
| |
const string REAL = "REAL";
| |
const string TEXT = "TEXT";
| |
const string BLOB = "BLOB"; // "NONE" also ok, both means anything
| |
const string NONE = "NONE";
| |
const string _NULL = "NULL";
| |
}
| |
| |
//+------------------------------------------------------------------+
| |
//| Scalar value type is used to instantiate simple DBRow objects |
| |
//+------------------------------------------------------------------+
| |
struct DBValue
| |
{
| |
int fake; // not used, struct can not be empty
| |
};
| |
| |
//+------------------------------------------------------------------+
| |
//| Pseudo-type to declare blob fields in objects using DB_FIELD |
| |
//+------------------------------------------------------------------+
| |
enum blob
| |
{
| |
};
| |
| |
//+------------------------------------------------------------------+
| |
//| Description of table column, returned by pragma table_info('tbl')|
| |
//+------------------------------------------------------------------+
| |
struct DBTableColumn
| |
{
| |
int cid;
| |
string name;
| |
string type;
| |
bool not_null;
| |
string default_value;
| |
bool primary_key;
| |
};
| |
| |
//+------------------------------------------------------------------+
| |
//| DB-aware meta-type |
| |
//+------------------------------------------------------------------+
| |
template<typename S>
| |
struct DBEntity
| |
{
| |
template<typename T>
| |
struct DBField
| |
{
| |
T f;
| |
DBField(const string name, const string constraints = "")
| |
{
| |
const int n = EXPAND(prototype);
| |
prototype[n][0] = affinity(TYPENAME(T));
| |
prototype[n][1] = name;
| |
if(StringLen(constraints) > 0 // eliminates STRING_SMALL_LEN(5035)
| |
&& StringFind(constraints, "%") >= 0)
| |
{
| |
Print("Constraint requires an expression (skipped): ", constraints);
| |
}
| |
else
| |
{
| |
prototype[n][2] = constraints;
| |
}
| |
}
| |
| |
static string affinity(const string type)
| |
{
| |
const static string ints[] =
| |
{
| |
"bool", "char", "short", "int", "long",
| |
"uchar", "ushort", "uint", "ulong", "datetime",
| |
"color", "enum"
| |
};
| |
for(int i = 0; i < ArraySize(ints); ++i)
| |
{
| |
if(type == ints[i]) return DB_TYPE::INTEGER;
| |
}
| |
| |
if(type == "float" || type == "double") return DB_TYPE::REAL;
| |
| |
if(type == "string") return DB_TYPE::TEXT;
| |
| |
return DB_TYPE::BLOB;
| |
}
| |
};
| |
| |
static string prototype[][3]; // 0 - type, 1 - name, 2 - constraint
| |
};
| |
| |
template<typename S>
| |
static string DBEntity::prototype[][3];
| |
| |
//+------------------------------------------------------------------+
| |
//| Meta-info about columns of query results |
| |
//+------------------------------------------------------------------+
| |
struct DBRowColumn
| |
{
| |
string name;
| |
// NB: the next is per row information, because in SQLite
| |
// fields are dynamically typed (properties defined at table creation
| |
// are just recommendations and can be overwritten by actual data being stored)
| |
ENUM_DATABASE_FIELD_TYPE type;
| |
int size;
| |
};
| |
| |
//+------------------------------------------------------------------+
| |
//| Reading query results into general purpose array of MqlParams |
| |
//+------------------------------------------------------------------+
| |
class DBRow
| |
{
| |
protected:
| |
const int query;
| |
int columns;
| |
DBRowColumn info[];
| |
MqlParam data[];
| |
const bool cache;
| |
int cursor;
| |
| |
virtual bool DBread()
| |
{
| |
return PRTF(DatabaseRead(query));
| |
}
| |
| |
public:
| |
DBRow(const int q, const bool c = false):
| |
query(q), cache(c), columns(0), cursor(-1)
| |
{
| |
}
| |
| |
int length() const
| |
{
| |
return columns;
| |
}
| |
| |
DBRowColumn description(const int i) const
| |
{
| |
static const DBRowColumn null = {};
| |
if(i < 0 || i >= columns) return null;
| |
return info[i];
| |
}
| |
| |
virtual bool next()
| |
{
| |
if(cache && cursor >= 0)
| |
{
| |
SetUserError(1);
| |
return false; // can't get next on cached instance
| |
}
| |
| |
const bool success = DBread();
| |
if(success)
| |
{
| |
if(cursor == -1)
| |
{
| |
columns = DatabaseColumnsCount(query);
| |
ArrayResize(info, columns);
| |
if(cache) ArrayResize(data, columns);
| |
for(int i = 0; i < columns; ++i)
| |
{
| |
DatabaseColumnName(query, i, info[i].name);
| |
info[i].type = DatabaseColumnType(query, i);
| |
info[i].size = DatabaseColumnSize(query, i);
| |
if(cache) data[i] = this[i];
| |
}
| |
}
| |
else
| |
{
| |
// NB: since value type in every column can change for specific row,
| |
// it could be useful to rescan DatabaseColumnType and DatabaseColumnSize
| |
}
| |
++cursor;
| |
}
| |
return success;
| |
}
| |
| |
int name2index(const string name) const
| |
{
| |
for(int i = 0; i < columns; ++i)
| |
{
| |
if(name == info[i].name) return i;
| |
}
| |
Print("Wrong column name: ", name);
| |
SetUserError(3); // wrong column name
| |
return -1;
| |
}
| |
| |
// read column value by name
| |
MqlParam operator[](const string name) const
| |
{
| |
const int i = name2index(name);
| |
if(i != -1) return this[i];
| |
static MqlParam param = {};
| |
return param;
| |
}
| |
| |
// read column value by index
| |
virtual MqlParam operator[](const int i = 0) const
| |
{
| |
MqlParam param = {};
| |
if(i < 0 || i >= columns) return param;
| |
if(ArraySize(data) > 0 && cursor != -1) // return cache, if exists
| |
{
| |
return data[i];
| |
}
| |
#ifdef DB_PARAM_NAMES
| |
param.string_value = "[" + names[i] + "]";
| |
#endif
| |
switch(info[i].type)
| |
{
| |
case DATABASE_FIELD_TYPE_INTEGER:
| |
switch(info[i].size)
| |
{
| |
case 1:
| |
param.type = TYPE_CHAR;
| |
break;
| |
case 2:
| |
param.type = TYPE_SHORT;
| |
break;
| |
case 4:
| |
param.type = TYPE_INT;
| |
break;
| |
case 8:
| |
default:
| |
param.type = TYPE_LONG;
| |
break;
| |
}
| |
DatabaseColumnLong(query, i, param.integer_value);
| |
break;
| |
case DATABASE_FIELD_TYPE_FLOAT:
| |
param.type = info[i].size == 4 ? TYPE_FLOAT : TYPE_DOUBLE;
| |
DatabaseColumnDouble(query, i, param.double_value);
| |
break;
| |
case DATABASE_FIELD_TYPE_TEXT:
| |
param.type = TYPE_STRING;
| |
DatabaseColumnText(query, i, param.string_value);
| |
break;
| |
case DATABASE_FIELD_TYPE_BLOB: // this is just a workaround
| |
{ // there is no other means to pass blob via MqlParam
| |
uchar blob[]; // use getBlob to get exact binary data
| |
DatabaseColumnBlob(query, i, blob);
| |
uchar key[], text[];
| |
if(CryptEncode(CRYPT_BASE64, blob, key, text))
| |
{
| |
param.string_value = CharArrayToString(text);
| |
}
| |
}
| |
param.type = TYPE_BLOB;
| |
break;
| |
case DATABASE_FIELD_TYPE_NULL:
| |
param.type = TYPE_NULL;
| |
break;
| |
}
| |
return param;
| |
}
| |
| |
// read blob column value by index
| |
template<typename S>
| |
int getBlob(const int i, S &object[])
| |
{
| |
if(ArraySize(data) > 0)
| |
{
| |
Print("Cached rows do not support blobs");
| |
SetUserError(2);
| |
return false; // can't get blob from cache: raw blobs are not cachable
| |
}
| |
return DatabaseColumnBlob(query, i, object);
| |
}
| |
| |
// read blob column value by name
| |
template<typename S>
| |
int getBlob(const string name, S &object[])
| |
{
| |
const int i = name2index(name);
| |
if(i != -1)
| |
{
| |
return getBlob(i, object);
| |
}
| |
return 0;
| |
}
| |
| |
void readAll(MqlParam ¶ms[]) const
| |
{
| |
ArrayResize(params, columns);
| |
for(int i = 0; i < columns; ++i)
| |
{
| |
params[i] = this[i];
| |
}
| |
}
| |
| |
void moveAll(MqlParam ¶ms[])
| |
{
| |
if(cache) // only cached row fills 'data' array
| |
{
| |
ArraySwap(params, data);
| |
}
| |
}
| |
| |
};
| |
| |
//+------------------------------------------------------------------+
| |
//| DB row for reading DB records into objects/structs |
| |
//+------------------------------------------------------------------+
| |
template<typename S>
| |
class DBRowStruct: public DBRow
| |
{
| |
protected:
| |
S object;
| |
| |
virtual bool DBread() override
| |
{
| |
// NB: derived structures are not allowed;
| |
// number of struct fields must not exceed number of columns in table/query
| |
return PRTF(DatabaseReadBind(query, object));
| |
}
| |
| |
public:
| |
DBRowStruct(const int q, const bool c = false): DBRow(q, c)
| |
{
| |
}
| |
| |
S get() const
| |
{
| |
return object;
| |
}
| |
};
| |
| |
//+------------------------------------------------------------------+
| |
//| DB query wrapper class |
| |
//+------------------------------------------------------------------+
| |
class DBQuery
| |
{
| |
protected:
| |
const string sql;
| |
const int db;
| |
const int handle;
| |
AutoPtr<DBRow> row; // current
| |
AutoPtr<DBRow> rows[]; // cached (if requested)
| |
| |
public:
| |
DBQuery(const int owner, const string s): db(owner), sql(s),
| |
handle(PRTF(DatabasePrepare(db, sql)))
| |
{
| |
row = NULL;
| |
}
| |
| |
~DBQuery()
| |
{
| |
DatabaseFinalize(handle);
| |
}
| |
| |
bool isValid() const
| |
{
| |
return handle != INVALID_HANDLE;
| |
}
| |
| |
int getHandle() const
| |
{
| |
return handle;
| |
}
| |
| |
int getDB() const
| |
{
| |
return db;
| |
}
| |
| |
template<typename T>
| |
bool bind(const int index, const T value)
| |
{
| |
return PRTF(DatabaseBind(handle, index, value));
| |
}
| |
| |
template<typename T>
| |
bool bindBlob(const int index, const T &value[])
| |
{
| |
return PRTF(DatabaseBindArray(handle, index, value));
| |
}
| |
| |
bool bindNull(const int index)
| |
{
| |
static const uchar null[] = {};
| |
return bindBlob(index, null);
| |
}
| |
| |
template<typename S>
| |
DBRow *start() // S &
| |
{
| |
DatabaseReset(handle);
| |
row = TYPENAME(S) == "DBValue" ? new DBRow(handle) : new DBRowStruct<S>(handle);
| |
return row[];
| |
}
| |
| |
virtual bool reset()
| |
{
| |
return DatabaseReset(handle);
| |
}
| |
| |
// use 'execute' for parametric queries with inputs and NO outputs (batch DB edit)
| |
// use 'DBRow/DBRowStruct::next' to get outputs/results from DB
| |
virtual bool execute()
| |
{
| |
// NB: can return false and set error DATABASE_NO_MORE_DATA(5126),
| |
// which is NOT an error!
| |
return PRTF(DatabaseRead(handle));
| |
}
| |
| |
// get an array of cached rows
| |
bool readAll(DBRow *&result[], const bool detach = false)
| |
{
| |
DatabaseReset(handle);
| |
| |
DBRow *temp;
| |
while((temp = new DBRow(handle, true)) != NULL && temp.next()) // generate new row on each iteration
| |
{
| |
PUSH(result, temp);
| |
if(!detach) PUSH(rows, temp); // keep trace of objects to clean them up
| |
temp = NULL;
| |
}
| |
delete temp; // clean up excessive instance for ERR_DATABASE_NO_MORE_DATA
| |
return true;
| |
}
| |
| |
template<typename S>
| |
bool readAll(S &result[])
| |
{
| |
DatabaseReset(handle);
| |
| |
DBRowStruct<S> *temp = new DBRowStruct<S>(handle);
| |
while(temp.next())
| |
{
| |
PUSH(result, temp.get());
| |
}
| |
delete temp;
| |
return true;
| |
}
| |
};
| |
| |
//+------------------------------------------------------------------+
| |
//| Main database wrapper class |
| |
//+------------------------------------------------------------------+
| |
class DBSQLite
| |
{
| |
protected:
| |
const string path;
| |
const int handle;
| |
const uint flags;
| |
int transaction;
| |
AutoPtr<DBQuery> queries[];
| |
| |
public:
| |
DBSQLite(const string file, const uint opts =
| |
DATABASE_OPEN_CREATE | DATABASE_OPEN_READWRITE):
| |
path(file), flags(opts), handle(DatabaseOpen(file, opts)), transaction(0)
| |
{
| |
}
| |
| |
~DBSQLite(void)
| |
{
| |
if(handle != INVALID_HANDLE)
| |
{
| |
DatabaseClose(handle);
| |
}
| |
}
| |
| |
int getHandle() const
| |
{
| |
return handle;
| |
}
| |
| |
bool isOpen() const
| |
{
| |
return handle != INVALID_HANDLE;
| |
}
| |
| |
bool execute(const string sql) // query without bound inputs/outputs (hardcoded logic)
| |
{
| |
return PRTF(DatabaseExecute(handle, sql));
| |
}
| |
| |
DBQuery *prepare(const string sql)
| |
{
| |
DBQuery *q = new DBQuery(handle, sql);
| |
if(!q.isValid())
| |
{
| |
delete q;
| |
return NULL;
| |
}
| |
return PUSH(queries, q);
| |
}
| |
| |
template<typename S>
| |
string columns(const string table_constraints = "") const
| |
{
| |
static const string continuation = ",\n";
| |
string result = "";
| |
const int n = ArrayRange(DBEntity<S>::prototype, 0);
| |
if(!n) return NULL;
| |
for(int i = 0; i < n; ++i)
| |
{
| |
result += StringFormat("%s%s %s %s",
| |
i > 0 ? continuation : "",
| |
DBEntity<S>::prototype[i][1], DBEntity<S>::prototype[i][0], DBEntity<S>::prototype[i][2]);
| |
}
| |
if(StringLen(table_constraints))
| |
{
| |
result += continuation + table_constraints;
| |
}
| |
return result;
| |
}
| |
| |
template<typename S>
| |
bool createTable(const bool not_exist = false, const string name = NULL,
| |
const string table_constraints = "") const
| |
{
| |
const static string query = "CREATE TABLE %s %s (%s);";
| |
const string fields = columns<S>(table_constraints);
| |
if(fields == NULL)
| |
{
| |
Print("Structure '", TYPENAME(S), "' with table fields is not initialized");
| |
SetUserError(4);
| |
return false;
| |
}
| |
// attempt to create a table that already exists
| |
// without using IF NOT EXISTS will give an error.
| |
const string sql = StringFormat(query,
| |
(not_exist ? "IF NOT EXISTS" : ""), StringLen(name) ? name : TYPENAME(S), fields);
| |
return DatabaseExecute(handle, PRTF(sql));
| |
}
| |
| |
template<typename S>
| |
bool createSimpleTable() const
| |
{
| |
return createTable(true);
| |
}
| |
| |
bool hasTable(const string table) const
| |
{
| |
return DatabaseTableExists(handle, table);
| |
}
| |
| |
bool deleteTable(const string name) const
| |
{
| |
const static string query = "DROP TABLE IF EXISTS '%s';";
| |
return DatabaseExecute(handle, StringFormat(query, name));
| |
/*
| |
// equivalent:
| |
if(!DatabaseTableExists(handle, name)) return true;
| |
if(!DatabaseExecute(handle, StringFormat("DROP TABLE '%s';", name))) return false;
| |
return !DatabaseTableExists(handle, name)
| |
&& ResetLastErrorOnCondition(_LastError == ERR_DATABASE_NO_MORE_DATA);
| |
*/
| |
}
| |
| |
static bool ResetLastErrorOnCondition(const bool cond)
| |
{
| |
if(cond)
| |
{
| |
ResetLastError();
| |
return true;
| |
}
| |
return false;
| |
}
| |
| |
template<typename S>
| |
bool insert(S &objects[], const string table = NULL)
| |
{
| |
const static string query = "INSERT INTO '%s' VALUES(%s) RETURNING rowid;";
| |
const string sql = StringFormat(query, StringLen(table) ? table : TYPENAME(S), qlist<S>());
| |
DBQuery q(handle, PRTF(sql));
| |
if(!q.isValid()) return false;
| |
DBRow *r = q.start<DBValue>();
| |
for(int i = 0; i < ArraySize(objects); ++i)
| |
{
| |
if(objects[i].bindAll(q))
| |
{
| |
if(r.next()) // we expect one row with single value of new rowid
| |
{
| |
objects[i].rowid(r[0].integer_value);
| |
}
| |
}
| |
q.reset();
| |
}
| |
| |
return true;
| |
}
| |
| |
template<typename S>
| |
long insert(S &object, const string table = NULL)
| |
{
| |
const static string query = "INSERT INTO '%s' VALUES(%s) RETURNING rowid;";
| |
const string sql = StringFormat(query, StringLen(table) ? table : TYPENAME(S), qlist<S>());
| |
DBQuery q(handle, PRTF(sql));
| |
if(!q.isValid()) return 0;
| |
if(object.bindAll(q))
| |
{
| |
DBRow *r = q.start<DBValue>(); // we expect one row with single value of new rowid
| |
if(r.next())
| |
{
| |
return object.rowid(r[0].integer_value);
| |
}
| |
}
| |
| |
return 0;
| |
}
| |
| |
template<typename S>
| |
string qlist() const
| |
{
| |
const int n = ArrayRange(DBEntity<S>::prototype, 0);
| |
string result = "?1";
| |
for(int i = 1; i < n; ++i)
| |
{
| |
const string constraint = DBEntity<S>::prototype[i][2];
| |
if(StringLen(constraint) > 0 && StringFind(constraint, DB_CONSTRAINT::CURRENT_TIMESTAMP) >= 0)
| |
// workaround for SQLite bug with not setting CURRENT_TIMESTAMP (works both on inserts and updates)
| |
result += ",STRFTIME('%s')"; // TODO: replace with UNIXEPOCH() after SQLite v.3.38
| |
else
| |
result += StringFormat(",?%d", (i + 1));
| |
}
| |
return result;
| |
}
| |
| |
template<typename S>
| |
string namelist() const
| |
{
| |
const int n = ArrayRange(DBEntity<S>::prototype, 0);
| |
string result = DBEntity<S>::prototype[0][1];
| |
for(int i = 1; i < n; ++i)
| |
{
| |
result += "," + DBEntity<S>::prototype[i][1];
| |
}
| |
return result;
| |
}
| |
| |
template<typename S>
| |
bool update(S &object, const string condition = NULL, const string table = NULL)
| |
{
| |
const static string query = "UPDATE '%s' SET (%s)=(%s) %s;";
| |
const string sql = StringFormat(query, StringLen(table) ? table : TYPENAME(S),
| |
namelist<S>(), qlist<S>(),
| |
StringLen(condition) ? condition : StringFormat("WHERE rowid=%ld", object.rowid()));
| |
DBQuery q(handle, PRTF(sql));
| |
if(!q.isValid()) return false;
| |
if(object.bindAll(q))
| |
{
| |
q.execute();
| |
}
| |
return ResetLastErrorOnCondition(_LastError == ERR_DATABASE_NO_MORE_DATA);
| |
}
| |
| |
template<typename S>
| |
bool remove(S &object, const string table = NULL, const string column = "rowid")
| |
{
| |
const static string query = "DELETE FROM '%s' WHERE %s=%ld;";
| |
const string sql = StringFormat(query, StringLen(table) ? table : TYPENAME(S),
| |
column, object.rowid());
| |
DBQuery q(handle, PRTF(sql));
| |
if(!q.isValid()) return false;
| |
q.execute();
| |
return ResetLastErrorOnCondition(_LastError == ERR_DATABASE_NO_MORE_DATA);
| |
}
| |
| |
template<typename S>
| |
bool read(const long rowid, S &s, const string table = NULL, const string column = "rowid")
| |
{
| |
// NB: INTEGER PRIMARY KEY column may not be present in S explicitly,
| |
// then selecting '*' will not return 'rowid'
| |
const static string query = "SELECT * FROM '%s' WHERE %s=%ld;";
| |
const string sql = StringFormat(query,
| |
StringLen(table) ? table : TYPENAME(S), column, rowid);
| |
DBQuery q(handle, PRTF(sql));
| |
if(!q.isValid()) return false;
| |
DBRowStruct<S> *r = q.start<S>();
| |
if(r.next())
| |
{
| |
s = r.get();
| |
return true;
| |
}
| |
return false;
| |
}
| |
| |
bool read(const long rowid, DBRow *&r, const string table, const string column = "rowid")
| |
{
| |
// NB: INTEGER PRIMARY KEY column may not be present in S explicitly,
| |
// then selecting '*' will not return 'rowid'
| |
const static string query = "SELECT * FROM '%s' WHERE %s=%ld;";
| |
const string sql = StringFormat(query, table, column, rowid);
| |
DBQuery *q = new DBQuery(handle, PRTF(sql));
| |
if(q.isValid())
| |
{
| |
r = q.start<DBValue>();
| |
if(r.next())
| |
{
| |
PUSH(queries, q);
| |
return true;
| |
}
| |
}
| |
delete q;
| |
return false;
| |
}
| |
| |
bool begin()
| |
{
| |
if(transaction > 0) // already in transaction
| |
{
| |
transaction++; // track nesting level
| |
return true;
| |
}
| |
return (bool)(transaction = PRTF(DatabaseTransactionBegin(handle)));
| |
}
| |
| |
bool commit()
| |
{
| |
if(transaction > 0)
| |
{
| |
if(--transaction == 0) // outermost transaction
| |
return PRTF(DatabaseTransactionCommit(handle));
| |
}
| |
return false;
| |
}
| |
| |
bool rollback()
| |
{
| |
if(transaction > 0)
| |
{
| |
if(--transaction == 0)
| |
return PRTF(DatabaseTransactionRollback(handle));
| |
}
| |
return false;
| |
}
| |
};
| |
| |
//+------------------------------------------------------------------+
| |
//| DB transaction guard |
| |
//+------------------------------------------------------------------+
| |
class DBTransaction
| |
{
| |
DBSQLite *db;
| |
const bool autocommit;
| |
public:
| |
DBTransaction(DBSQLite &owner, const bool c = false): db(&owner), autocommit(c)
| |
{
| |
if(CheckPointer(db) != POINTER_INVALID)
| |
{
| |
db.begin();
| |
}
| |
}
| |
| |
~DBTransaction()
| |
{
| |
if(CheckPointer(db) != POINTER_INVALID)
| |
{
| |
autocommit ? db.commit() : db.rollback();
| |
}
| |
}
| |
| |
bool commit()
| |
{
| |
if(CheckPointer(db) != POINTER_INVALID)
| |
{
| |
const bool done = db.commit();
| |
db = NULL;
| |
return done;
| |
}
| |
return false;
| |
}
| |
};
| |
//+------------------------------------------------------------------+
|