#include "sqlite3.h"
#endif
+#define uint _uint
+#include "ffi.h"
+#undef uint
+
static void UnusedFunction()
{
int a;
SerialBuffer buffer1 { size = count1, count = count1, buffer = data1 };
SerialBuffer buffer2 { size = count2, count = count2, buffer = data2 };
- type._vTbl[__ecereVMethodID_class_OnUnserialize](type, &inst1, buffer1);
- type._vTbl[__ecereVMethodID_class_OnUnserialize](type, &inst2, buffer2);
+ ((void (*)(void *, void *, void *))(void *)type._vTbl[__ecereVMethodID_class_OnUnserialize])(type, &inst1, buffer1);
+ ((void (*)(void *, void *, void *))(void *)type._vTbl[__ecereVMethodID_class_OnUnserialize])(type, &inst2, buffer2);
+
+ result = ((int (*)(void *, void *, void *))(void *)type._vTbl[__ecereVMethodID_class_OnCompare])(type, inst1, inst2);
- result = type._vTbl[__ecereVMethodID_class_OnCompare](type, inst1, inst2);
-
buffer1.buffer = null;
buffer2.buffer = null;
delete buffer1;
inst1 = new0 byte[type.structSize];
inst2 = new0 byte[type.structSize];
- type._vTbl[__ecereVMethodID_class_OnUnserialize](type, inst1, buffer1);
- type._vTbl[__ecereVMethodID_class_OnUnserialize](type, inst2, buffer2);
+ ((void (*)(void *, void *, void *))(void *)type._vTbl[__ecereVMethodID_class_OnUnserialize])(type, inst1, buffer1);
+ ((void (*)(void *, void *, void *))(void *)type._vTbl[__ecereVMethodID_class_OnUnserialize])(type, inst2, buffer2);
+
+ result = ((int (*)(void *, void *, void *))(void *)type._vTbl[__ecereVMethodID_class_OnCompare])(type, inst1, inst2);
- result = type._vTbl[__ecereVMethodID_class_OnCompare](type, inst1, inst2);
-
buffer1.buffer = null;
buffer2.buffer = null;
delete buffer1;
return result;
}
else
- return type._vTbl[__ecereVMethodID_class_OnCompare](type, data1, data2);
+ return ((int (*)(void *, void *, void *))(void *)type._vTbl[__ecereVMethodID_class_OnCompare])(type, data1, data2);
}
public class SQLiteStaticLink { } // Until .imp generation is fixed
-class SQLiteDataSource : DataSourceDriver
+class SQLiteDataSource : DirFilesDataSourceDriver
{
class_property(name) = "SQLite";
- String path;
- OldList listDatabases;
- uint databasesCount;
-
- String BuildLocator(DataSource ds)
- {
- return CopyString(ds.host);
- }
-
- uint GetDatabasesCount()
- {
- return databasesCount;
- }
-
- ~SQLiteDataSource()
- {
- delete path;
- }
-
- bool Connect(const String locator)
- {
- delete path;
- path = CopyString(locator);
- // TODO, use user name and password for local security?
- // TODO, open ds in read or write mode
- if(FileExists(path))
- {
- int n = 0;
- FileListing listing { path, "sqlite" };
- databasesCount = 0;
- while(listing.Find())
- databasesCount++;
- return true;
- }
- return false;
- }
-
- bool RenameDatabase(const String name, const String rename)
- {
- if(name && rename && path && FileExists(path))
- {
- String path;
- path = MakeDatabasePath(name);
- if(FileExists(path))
- {
- bool renamed;
- String repath;
- repath = MakeDatabasePath(rename);
- renamed = RenameFile(path, repath);
- delete path;
- delete repath;
- return renamed;
- }
- delete path;
- }
- return false;
- }
-
- bool DeleteDatabase(const String name)
- {
- if(path && FileExists(path))
- {
- bool deleted;
- String path = MakeDatabasePath(name);
- deleted = DeleteFile(path); // delete file seems to return true even if the file does not exist
- databasesCount--;
- delete path;
- return deleted;
- }
- return false;
- }
-
- virtual String MakeDatabasePath(const String name)
- {
- if(name)
- {
- char build[MAX_LOCATION];
- strcpy(build, path ? path : "");
- PathCat(build, name);
- ChangeExtension(build, "sqlite", build);
- return CopyString(build);
- }
- return null;
- }
+ class_property(databaseFileExtension) = "sqlite";
Database OpenDatabase(const String name, CreateOptions createOptions, DataSource ds)
{
// sqlite3_open(path, &db);
// sqlite3_open_v2(path, &db, SQLITE_OPEN_READONLY /*SQLITE_OPEN_READWRITE*/ /*SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE*/, null );
-
+
if(sqlite3_open_v2(path, &db, (createOptions == readOnly) ? SQLITE_OPEN_READONLY :
(SQLITE_OPEN_READWRITE | ((createOptions == create) ? SQLITE_OPEN_CREATE : 0)), null))
{
}
else
{
+ bool success = true;
char command[1024];
sprintf(command, "CREATE TABLE eda_table_fields(Table_Name TEXT, Name TEXT, Type TEXT, Length INT);");
sqlite3_exec(db, command, null, null, null);
- result = SQLiteDatabase { db = db };
+ if(createOptions != readOnly)
+ {
+ sqlite3_exec(db, "PRAGMA locking_mode=exclusive", null, null, null);
+ sqlite3_exec(db, "DELETE FROM eda_table_fields WHERE Name = 'lockDummy'", null, null, null);
+ if(sqlite3_exec(db, "INSERT INTO eda_table_fields (Table_Name, Name, Type, Length) VALUES ('lockDummy', 'lockDummy', 'lockDummy', 'lockDummy')", null, null, null))
+ success = false;
+ else
+ sqlite3_exec(db, "DELETE FROM eda_table_fields WHERE Name = 'lockDummy'", null, null, null);
+ }
+
+ if(success)
+ result = SQLiteDatabase { db = db };
}
delete path;
}
{
sqlite3 * db;
AVLTree<String> collations { };
-
+
~SQLiteDatabase()
{
+ sqlite3_exec(db, "PRAGMA locking_mode=normal", null, null, null);
sqlite3_close(db);
}
field = { tbl = table, name = CopyString("Name"), type = class(String), num = -1, sqliteType = SQLITE_TEXT };
LinkTable(table);
incref field;
- table.fields.Add(field);
+ table._fields.Add(field);
}
else if(options.type == fieldsList)
{
LinkTable(table);
field = { tbl = table, name = CopyString("Name"), type = class(String), num = -1, sqliteType = SQLITE_TEXT };
incref field;
- table.fields.Add(field);
+ table._fields.Add(field);
field = { tbl = table, name = CopyString("Type"), type = class(Class), num = 0, sqliteType = SQLITE_TEXT };
incref field;
- table.fields.Add(field);
+ table._fields.Add(field);
field = { tbl = table, name = CopyString("Length"), type = class(int), num = 1, sqliteType = SQLITE_INTEGER };
incref field;
- table.fields.Add(field);
+ table._fields.Add(field);
}
else if(options.type == tableRows)
{
sprintf(command, "SELECT sql FROM sqlite_master WHERE type='table' AND name='%s';", name);
nCols = 0, nRows = 0;
result = sqlite3_get_table(db, command, &t, &nRows, &nCols, null);
-
+
if((nCols || nRows) || options.create)
{
table = SQLiteTable { db = this, name = CopyString(name) };
{
char * sql = t[nCols * r];
char * bracket = strchr(sql, '(');
- if(bracket)
+ if(bracket)
{
int c = 0;
bracket++;
dataType[c - d - 2] = 0;
while(ch && bracket[c] == ' ') c++;
-
+
if(!strcmp(dataType, "REAL")) { sqliteType = SQLITE_FLOAT; type = class(double); }
else if(!strcmp(dataType, "TEXT")) { sqliteType = SQLITE_TEXT; type = class(String); }
else if(!strcmp(dataType, "INTEGER")) { sqliteType = SQLITE_INTEGER; type = class(int); }
result = sqlite3_exec(db, command, null, null, null);
{
- SQLiteField field { tbl = table, name = CopyString(fieldName), type = type, num = table.fields.count, sqliteType = sqliteType };
+ SQLiteField field { tbl = table, name = CopyString(fieldName), type = type, num = table._fields.count, sqliteType = sqliteType };
incref field;
- table.fields.Add(field);
+ table._fields.Add(field);
}
if(!ch || ch == ')') break;
}
else
{
+ Table refTable = null;
sqlite3_stmt * statement;
-
+
sprintf(command, "SELECT Name, Type, Length FROM eda_table_fields WHERE Table_Name='%s';", name);
result = sqlite3_prepare_v2(db, command, -1, &statement, null);
if(type)
{
- if(!strcmp(type.dataTypeString, "int") || !strcmp(type.dataTypeString, "unsigned int") ||
- !strcmp(type.dataTypeString, "long") || !strcmp(type.dataTypeString, "long int") ||
- !strcmp(type.dataTypeString, "uint") || !strcmp(type.dataTypeString, "uint32") ||
- !strcmp(type.dataTypeString, "int64") || !strcmp(type.dataTypeString, "unsigned int64") || !strcmp(type.dataTypeString, "uint64") ||
+ if(!strcmp(type.dataTypeString, "int") || !strcmp(type.dataTypeString, "unsigned int") ||
+ !strcmp(type.dataTypeString, "long") || !strcmp(type.dataTypeString, "long int") ||
+ !strcmp(type.dataTypeString, "uint") || !strcmp(type.dataTypeString, "uint32") ||
+ !strcmp(type.dataTypeString, "int64") || !strcmp(type.dataTypeString, "unsigned int64") || !strcmp(type.dataTypeString, "uint64") ||
!strcmp(type.dataTypeString, "short") || !strcmp(type.dataTypeString, "unsigned short") || !strcmp(type.dataTypeString, "uint16") ||
!strcmp(type.dataTypeString, "char") || !strcmp(type.dataTypeString, "unsigned char") || !strcmp(type.dataTypeString, "byte"))
sqliteType = SQLITE_INTEGER;
}
{
- SQLiteField field { tbl = table, name = CopyString(fieldName), type = type, length = length, num = table.fields.count, sqliteType = sqliteType };
+ Table * fTable = (Table *)eClass_GetProperty(type, "table");
+ SQLiteField field { tbl = table, name = CopyString(fieldName), type = type, length = length, num = table._fields.count, sqliteType = sqliteType };
incref field;
- table.fields.Add(field);
+ if(fTable) refTable = *fTable;
+ if(!table.primaryKey && refTable && !strcmp(refTable.name, table.name))
+ table.primaryKey = field;
+
+ table._fields.Add(field);
}
}
sqlite3_finalize(statement);
bool CreateCustomFunction(char * name, SQLCustomFunction customFunction)
{
- int result = sqlite3_create_function(db, name, 1, SQLITE_UTF8, customFunction, SQLiteFunctionProcessor, null, null);
- return result == SQLITE_OK;
+ bool result = false;
+ Class cfClass = customFunction._class;
+ customFunction.method = eClass_FindMethod(cfClass, "function", cfClass.module);
+ if(customFunction.method)
+ {
+ String typeString = CopyString(customFunction.method.dataTypeString);
+ char * tokens[256];
+ int count = TokenizeWith(typeString, sizeof(tokens)/sizeof(tokens[0]), tokens, "(,)", false);
+ int c;
+ bool variadic = false;
+
+ for(c = 0; c < count; c++)
+ {
+ Class type = null;
+ bool pointer = false;
+ String arg = tokens[c];
+ char * space;
+ TrimLSpaces(arg, arg);
+ if(strchr(arg, '*')) pointer = true;
+ if(pointer)
+ // Using String for generic pointer...
+ type = class(String);
+ else
+ {
+ if((space = strchr(arg, ' '))) *space = 0;
+ if(!strcmp(arg, "void"))
+ type = null;
+ else if(!strcmp(arg, "..."))
+ variadic = true;
+ else
+ {
+ if(cfClass.templateParams.count)
+ {
+ ClassTemplateParameter p;
+ int id = 0;
+ for(p = cfClass.templateParams.first; p; p = p.next, id++)
+ {
+ if(!strcmp(p.name, arg))
+ break;
+ }
+ if(p && cfClass.templateArgs)
+ arg = cfClass.templateArgs[id].dataTypeString;
+ }
+ type = eSystem_FindClass(customFunction._class.module, arg);
+ if(!type)
+ type = eSystem_FindClass(customFunction._class.module.application, arg);
+ }
+ }
+ if(c == 0)
+ customFunction.returnType = type;
+ else
+ customFunction.args.Add(type);
+ }
+ delete typeString;
+ if(variadic)
+ {
+ result = false;
+ // Variadic args don't make sense for SQL custom functions
+ // Note that different CIF must be prepared for different set of arguments
+ // ffi_prep_cif_var(&customFunction.cif, FFI_DEFAULT_ABI, args.count-1, rType, argTypes);
+ }
+ else
+ {
+ customFunction.rType = FFIGetType(customFunction.returnType, true);
+ customFunction.argTypes.Add((void *)&ffi_type_pointer); // This pointer for SQLCustomFunction object
+ for(a : customFunction.args) customFunction.argTypes.Add((void *)FFIGetType(a, false));
+ ffi_prep_cif(&customFunction.cif, FFI_DEFAULT_ABI, customFunction.argTypes.count, customFunction.rType, (ffi_type **) customFunction.argTypes.array);
+ result = sqlite3_create_function(db, name, customFunction.args.count, SQLITE_UTF8, customFunction, SQLiteFunctionProcessor, null, null) == SQLITE_OK;
+ }
+ }
+ return result;
}
}
-void SQLiteFunctionProcessor(sqlite3_context* context, int n, sqlite3_value** value)
+static class FFITypesHolder : Map<Class, String> { ~FFITypesHolder() { Free(); } }
+FFITypesHolder structFFITypes { };
+static Iterator dummy; // TOFIX: forward struct declaration issues on Clang
+
+public ffi_type * FFIGetType(Class type, bool structByValue)
+{
+ if(type)
+ switch(type.type)
+ {
+ // Pointer Types
+ case structClass:
+ if(structByValue)
+ {
+ MapIterator<Class, String> it { map = structFFITypes };
+ ffi_type * ffiType = null;
+ if(it.Index(type, false))
+ ffiType = (void *)it.data;
+ else
+ {
+ /*
+ DataMember member;
+ Array<String> memberTypes { };
+ for(member = type.membersAndProperties.first; member; member = member.next)
+ {
+ if(!member.isProperty)
+ {
+ memberTypes.Add(FFIGetType(member.dataType
+ }
+ }
+ */
+ ffiType = new0 ffi_type[1];
+ ffiType->size = type.structSize;
+ ffiType->type = FFI_TYPE_STRUCT;
+ structFFITypes[type] = (void *)ffiType;
+ }
+ return ffiType;
+ }
+ case normalClass:
+ case noHeadClass:
+ case unionClass:
+ return &ffi_type_pointer;
+ // Scalar Types
+ case bitClass:
+ case enumClass:
+ case systemClass:
+ case unitClass:
+ if(!strcmp(type.dataTypeString, "float")) return &ffi_type_float;
+ else if(!strcmp(type.dataTypeString, "double")) return &ffi_type_double;
+ else
+ switch(type.typeSize)
+ {
+ case 1: return &ffi_type_uint8;
+ case 2: return &ffi_type_uint16;
+ case 4: return &ffi_type_uint32;
+ case 8: return &ffi_type_uint64;
+ }
+ }
+ else
+ return &ffi_type_void;
+ return null;
+}
+
+static SerialBuffer staticBuffer { };
+void SQLiteFunctionProcessor(sqlite3_context* context, int n, sqlite3_value** values)
{
SQLCustomFunction sqlFunction = sqlite3_user_data(context);
- char * text = sqlite3_value_text(*value);
- sqlFunction.array.size = 1;
- sqlFunction.array[0] = 0;
- sqlFunction.Process(text);
- sqlite3_result_text(context, sqlFunction.array.array, sqlFunction.array.count ? sqlFunction.array.count - 1 : 0, SQLITE_TRANSIENT);
+
+ /* // Simple 1 pointer param returning a string
+ void * p = sqlFunction.method.function(sqlFunction, sqlite3_value_text(values[0]));
+ sqlite3_result_text(context, p, strlen(p), SQLITE_TRANSIENT);
+ */
+ int64 retData = 0;
+ void * ret = &retData;
+ Array<String> args { size = sqlFunction.args.count + 1 };
+ Iterator<String> ffiArg { sqlFunction.argTypes };
+ Iterator<String> arg { args };
+ int i = 0;
+
+ // this * for the SQLCustomFunction
+ args[0] = (void *)&sqlFunction;
+ ffiArg.Next();
+ // Get the arguments from SQLite
+ for(a : sqlFunction.args)
+ {
+ ffi_type * type = (ffi_type *)sqlFunction.argTypes[i+1];
+ if(i >= n) break;
+ switch(a.type)
+ {
+ case normalClass:
+ case noHeadClass:
+ case structClass:
+ case unionClass:
+ {
+ void ** data = new void *[1];
+ args[i+1] = (void *)data;
+ if(a == class(String))
+ {
+ int numBytes = sqlite3_value_bytes(values[i]);
+ char * text = sqlite3_value_text(values[i]);
+ *(char **)data = text ? new byte[numBytes+1] : null;
+ if(text)
+ memcpy(*(char **)data, text, numBytes+1);
+ }
+ else
+ {
+ SerialBuffer buffer = staticBuffer; //{ };
+ buffer.pos = 0;
+ buffer._size = sqlite3_value_bytes(values[i]);
+ buffer._buffer = sqlite3_value_text(values[i]);
+ //buffer._buffer = sqlite3_value_blob(curStatement);
+ buffer.count = buffer._size;
+ if(a.type == structClass)
+ *data = new byte[a.structSize];
+ ((void (*)(void *, void *, void *))(void *)a._vTbl[__ecereVMethodID_class_OnUnserialize])(a, (a.type == structClass) ? *data : data, buffer);
+ buffer._buffer = null;
+ //delete buffer;
+ }
+ break;
+ }
+ case bitClass:
+ case enumClass:
+ case systemClass:
+ case unitClass:
+ if(type == &ffi_type_double || type == &ffi_type_float)
+ {
+ double d = sqlite3_value_double(values[i]);
+ if(a.typeSize == 8)
+ {
+ double * data = new double[1];
+ args[i+1] = (void *)data;
+ *data = d;
+ }
+ else
+ {
+ float * data = new float[1];
+ args[i+1] = (void *)data;
+ *data = (float)d;
+ }
+ }
+ else
+ {
+ switch(a.typeSize)
+ {
+ case 8:
+ {
+ int64 * data = new int64[1];
+ args[i+1] = (void *)data;
+ *data = sqlite3_value_int64(values[i]);
+ break;
+ }
+ case 4:
+ {
+ int * data = new int[1];
+ args[i+1] = (void *)data;
+ *data = sqlite3_value_int(values[i]);
+ break;
+ }
+ case 2:
+ {
+ short * data = new short[1];
+ int value;
+ args[i+1] = (void *)data;
+ value = sqlite3_value_int(values[i]);
+ if(value < 0)
+ *data = (short)value;
+ else
+ *(uint16 *)data = (uint16)value;
+ break;
+ }
+ case 1:
+ {
+ char * data = new char[1];
+ int value;
+ args[i+1] = data;
+ value = sqlite3_value_int(values[i]);
+ if(value < 0)
+ *data = (char)value;
+ else
+ *(byte *)data = (byte)value;
+ break;
+ }
+ }
+ }
+ break;
+ }
+ i++;
+ ffiArg.Next();
+ }
+ if(sqlFunction.returnType && sqlFunction.returnType.type == structClass)
+ ret = new byte[sqlFunction.returnType.typeSize];
+ ffi_call(&sqlFunction.cif, (void *)sqlFunction.method.function, ret, args.array);
+ // Give SQLite the return value
+ if(sqlFunction.returnType)
+ {
+ ffi_type * type = sqlFunction.rType;
+ Class r = sqlFunction.returnType;
+ switch(r.type)
+ {
+ case normalClass:
+ case noHeadClass:
+ case structClass:
+ case unionClass:
+ {
+ void * data = ret ? *(void **)ret : null;
+ if(r.type == structClass)
+ data = ret;
+ if(r == class(String))
+ {
+ if(data)
+ sqlite3_result_text(context, (char *)data, strlen((char *)data), SQLITE_TRANSIENT);
+ else
+ sqlite3_result_text(context, null, 0, SQLITE_TRANSIENT);
+ }
+ else
+ {
+ SerialBuffer buffer { };
+ ((void (*)(void *, void *, void *))(void *)r._vTbl[__ecereVMethodID_class_OnSerialize])(r, data, buffer);
+ sqlite3_result_text(context, buffer._buffer, buffer.count, SQLITE_TRANSIENT);
+ delete buffer;
+
+ // Avoid destroying Strings for now... (Returning memory owned by the Custom Function)
+ ((void (*)(void *, void *))(void *)r._vTbl[__ecereVMethodID_class_OnFree])(r, data);
+ }
+
+ if(r.type == structClass)
+ delete ret;
+ break;
+ }
+ case bitClass:
+ case enumClass:
+ case systemClass:
+ case unitClass:
+ if(type == &ffi_type_double || type == &ffi_type_float)
+ {
+ if(r.typeSize == 8)
+ sqlite3_result_double(context, *(double *)ret);
+ else
+ sqlite3_result_double(context, (double)*(float *)ret);
+ }
+ else
+ {
+ switch(r.typeSize)
+ {
+ case 8:
+ sqlite3_result_int64(context, (sqlite3_int64)*(int64 *)ret);
+ break;
+ case 4:
+ sqlite3_result_int(context, *(int *)ret);
+ break;
+ case 2:
+ {
+ int value;
+ //if((int)data < 0)
+ value = (int)*(short *)ret;
+ //else
+ //value = (int)*(uint16 *)ret;
+ sqlite3_result_int(context, value);
+ break;
+ }
+ case 1:
+ {
+ int value;
+ //if((int)data < 0)
+ value = (int)*(char *)ret;
+ //else
+ //value = (int)*(byte *)ret;
+ sqlite3_result_int(context, value);
+ break;
+ }
+ }
+ }
+ break;
+ }
+ }
+
+ // Free Stuff up
+ arg.Next();
+ for(type : sqlFunction.args; arg.Next())
+ {
+ // Free instance
+ void * data = *(void **)arg.data;
+ ((void (*)(void *, void *))(void *)type._vTbl[__ecereVMethodID_class_OnFree])(type, data);
+ if(type.type == structClass)
+ delete data;
+ // Free arg holder
+ data = arg.data;
+ delete data;
+ }
+ delete args;
}
class SQLiteTable : Table
char * name;
bool mustCreate;
SQLiteDatabase db;
- LinkList<SQLiteField> fields { };
+ LinkList<SQLiteField> _fields { };
char * specialStatement;
SQLiteField primaryKey;
FieldIndex * indexFields;
Table refTable = null;
Field idField = null;
command[0] = 0;
-
+
if(FindField(fieldName)) return null;
- if(!strcmp(type.dataTypeString, "int") || !strcmp(type.dataTypeString, "unsigned int") ||
- !strcmp(type.dataTypeString, "long") || !strcmp(type.dataTypeString, "long int") ||
- !strcmp(type.dataTypeString, "uint") || !strcmp(type.dataTypeString, "uint32") ||
- !strcmp(type.dataTypeString, "int64") || !strcmp(type.dataTypeString, "unsigned int64") || !strcmp(type.dataTypeString, "uint64") ||
+ if(!strcmp(type.dataTypeString, "int") || !strcmp(type.dataTypeString, "unsigned int") ||
+ !strcmp(type.dataTypeString, "long") || !strcmp(type.dataTypeString, "long int") ||
+ !strcmp(type.dataTypeString, "uint") || !strcmp(type.dataTypeString, "uint32") ||
+ !strcmp(type.dataTypeString, "int64") || !strcmp(type.dataTypeString, "unsigned int64") || !strcmp(type.dataTypeString, "uint64") ||
!strcmp(type.dataTypeString, "short") || !strcmp(type.dataTypeString, "unsigned short") || !strcmp(type.dataTypeString, "uint16") ||
!strcmp(type.dataTypeString, "char") || !strcmp(type.dataTypeString, "unsigned char") || !strcmp(type.dataTypeString, "byte"))
{
PrintLn($"WARNING: Table not yet created for class ", (String)type.name);
}
}
-
+
if(mustCreate)
{
if(sqliteType == SQLITE_BLOB)
fieldName, type.name, length);
result = sqlite3_exec(db.db, command, null, null, null);
- field = { name = CopyString(fieldName), type = type, num = fields.count, sqliteType = sqliteType };
+ field = { name = CopyString(fieldName), type = type, num = _fields.count, sqliteType = sqliteType };
incref field;
- fields.Add(field);
+ _fields.Add(field);
if(!primaryKey && refTable == this)
primaryKey = field;
return (Field)field;
Field FindField(const String name)
{
- for(f : fields; !strcmp(f.name, name))
+ for(f : _fields; !strcmp(f.name, name))
{
if(!primaryKey)
{
Field GetFirstField()
{
- return fields.first;
+ return _fields.first;
+ }
+
+ Field GetPrimaryKey()
+ {
+ return primaryKey;
}
uint GetFieldsCount()
{
- return fields.count;
+ return _fields.count;
}
uint GetRowsCount()
}
// Returns true if not ordered by row ID
- bool GetIndexOrder(char * fullOrder)
+ bool GetIndexOrder(char * fullOrder, bool flip)
{
- if(!indexFields || (indexFieldsCount == 1 && indexFields[0].field == primaryKey && indexFields[0].order == ascending))
+ if(!flip && (!indexFields || (indexFieldsCount == 1 && indexFields[0].field == primaryKey && indexFields[0].order == ascending)))
{
strcpy(fullOrder, " ORDER BY ROWID");
return false;
{
int c;
strcpy(fullOrder, " ORDER BY ");
- for(c = 0; c < indexFieldsCount; c++)
+ for(c = flip ? indexFieldsCount-1 : 0; flip ? (c >= 0) : (c < indexFieldsCount); flip ? c-- : c++)
{
char order[1024];
FieldIndex * fIndex = &indexFields[c];
strcat(order, "`");
strcat(order, fIndex->field.name);
strcat(order, "`");
- if(fIndex->order == descending) strcat(order, " DESC");
+ if(fIndex->order == (flip ? ascending : descending)) strcat(order, " DESC");
strcat(fullOrder, order);
}
return true;
}
}
+ Container<Field> GetFields()
+ {
+ return (Container<Field>)_fields;
+ }
+
DriverRow CreateRow()
{
char command[1024];
sqlite3_prepare_v2(db.db, command, -1, &updateStmt, null);*/
- GetIndexOrder(order);
+ GetIndexOrder(order, false);
sprintf(command, "SELECT ROWID, * FROM `%s`%s;", name, order);
}
sqlite3_prepare_v2(db.db, command, -1, &statement, null);
sqlite3_prepare_v2(db.db, command, -1, &setRowIDStmt, null);
return SQLiteRow
- { tbl = this, defaultStatement = statement, curStatement = statement, sysIDStatement = sysIDStmt,
+ { tbl = this, defaultStatement = statement, curStatement = statement, sysIDStatement = sysIDStmt,
insertStatement = insertStmt, deleteStatement = deleteStmt, selectRowIDsStmt = selectRowIDsStmt, setRowIDStmt = setRowIDStmt,
previousStatement = prevStmt, nextStatement = nextStmt, lastStatement = lastStmt, insertIDStatement = insertIDStmt };
}
delete name;
delete specialStatement;
delete indexFields;
- fields.Free();
+ _fields.Free();
}
}
sqlite3_stmt * defaultStatement;
sqlite3_stmt * findStatement;
+ sqlite3_stmt * prevFindStatement, * lastFindStatement;
+ sqlite3_stmt * nextFindStatement;
sqlite3_stmt * sysIDStatement;
sqlite3_stmt * queryStatement;
- sqlite3_stmt * findMultipleStatement;
sqlite3_stmt * selectRowIDsStmt;
sqlite3_stmt * setRowIDStmt;
sqlite3_stmt * lastStatement;
// Because we use GoToSysID() and the sysIDStatement when searching for a primary key with Find(),
// this flag is used to distinguish between a Find() and a GoToSysID() for Select(next) purposes:
bool findSysID;
-
+ int findBindId;
+
bool Nil()
{
return done;
{
if(defaultStatement) sqlite3_finalize(defaultStatement);
if(findStatement) sqlite3_finalize(findStatement);
- if(findMultipleStatement) sqlite3_finalize(findMultipleStatement);
+ if(prevFindStatement)sqlite3_finalize(prevFindStatement);
+ if(lastFindStatement)sqlite3_finalize(lastFindStatement);
+ if(nextFindStatement)sqlite3_finalize(nextFindStatement);
if(sysIDStatement) sqlite3_finalize(sysIDStatement);
if(insertStatement) sqlite3_finalize(insertStatement);
if(deleteStatement) sqlite3_finalize(deleteStatement);
case middle:
break;
case next:
+ case previous:
+ {
// For sysID statement, for a Find() we want to go through next/previous in order, otherwise we just go to nil
- if(!stepping && (curStatement != sysIDStatement || findSysID))
+ if((move == next && curStatement != prevFindStatement && curStatement != lastFindStatement && !stepping && (curStatement != sysIDStatement || findSysID)) ||
+ (move == previous && (curStatement == prevFindStatement || curStatement == lastFindStatement)))
{
result = sqlite3_step(curStatement);
done = result == SQLITE_DONE || (result && result != SQLITE_ROW);
if(done) { rowID = 0; sqlite3_reset(curStatement); return false; }
rowID = sqlite3_column_int64(curStatement, 0);
- break;
}
- case previous:
- {
- sqlite3_reset(curStatement);
- curStatement = (move == previous) ? (rowID ? previousStatement : lastStatement) : (rowID ? nextStatement : defaultStatement);
- sqlite3_bind_int64(curStatement, 1, (sqlite3_int64)rowID);
- result = sqlite3_step(curStatement);
- done = result == SQLITE_DONE || (result && result != SQLITE_ROW);
- if(done) { rowID = 0; sqlite3_reset(curStatement); return false; }
- rowID = sqlite3_column_int64(curStatement, 0);
+ else if(curStatement == prevFindStatement || curStatement == findStatement || curStatement == nextFindStatement || curStatement == lastFindStatement)
+ {
+ if(rowID)
+ {
+ int bindId = findBindId;
+ sqlite3_reset((move == next) ? nextFindStatement : prevFindStatement);
+ BindCursorData((move == next) ? nextFindStatement : prevFindStatement, move,
+ (move == next && (!tbl.indexFields || (tbl.indexFieldsCount == 1 && tbl.indexFields[0].field == tbl.primaryKey && tbl.indexFields[0].order == ascending))) ? false : true, &bindId);
+ sqlite3_reset(curStatement);
+ curStatement = (move == next) ? nextFindStatement : prevFindStatement;
+ result = sqlite3_step(curStatement);
+ done = result == SQLITE_DONE || (result && result != SQLITE_ROW);
+ if(done) { rowID = 0; sqlite3_reset(curStatement); return false; }
+ rowID = sqlite3_column_int64(curStatement, 0);
+ }
+ else
+ {
+ int bindId = findBindId;
+ sqlite3_reset((move == next) ? findStatement : lastFindStatement);
+ sqlite3_reset(curStatement);
+ curStatement = (move == next) ? findStatement : lastFindStatement;
+ result = sqlite3_step(curStatement);
+ done = result == SQLITE_DONE || (result && result != SQLITE_ROW);
+ if(done) { rowID = 0; sqlite3_reset(curStatement); return false; }
+ rowID = sqlite3_column_int64(curStatement, 0);
+ }
+ }
+ else
+ {
+ sqlite3_reset(curStatement);
+ curStatement = (move == previous) ? (rowID ? previousStatement : lastStatement) : (rowID ? nextStatement : defaultStatement);
+ sqlite3_bind_int64(curStatement, 1, (sqlite3_int64)rowID);
+ result = sqlite3_step(curStatement);
+ done = result == SQLITE_DONE || (result && result != SQLITE_ROW);
+ if(done) { rowID = 0; sqlite3_reset(curStatement); return false; }
+ rowID = sqlite3_column_int64(curStatement, 0);
+ }
break;
}
case nil:
if(queryString)
{
result = sqlite3_prepare_v2(tbl.db.db, queryString, -1, &queryStatement, null);
- curStatement = queryStatement;
- if(!strchr(queryString, '?'))
+ if(!result)
{
- result = sqlite3_step(queryStatement);
+ curStatement = queryStatement;
+ if(!strchr(queryString, '?'))
+ {
+ result = sqlite3_step(queryStatement);
- done = result == SQLITE_DONE || (result && result != SQLITE_ROW);
- if(done) { rowID = 0; sqlite3_reset(queryStatement); return false; }
+ done = result == SQLITE_DONE || (result && result != SQLITE_ROW);
+ if(done) { rowID = 0; sqlite3_reset(queryStatement); return false; }
- rowID = sqlite3_column_int64(queryStatement, 0);
+ rowID = sqlite3_column_int64(queryStatement, 0);
+ }
}
+ else
+ status = false;
}
else
curStatement = null;
return status;
}
- void BindData(sqlite3_stmt * statement, int pos, SQLiteField fld, typed_object data, SerialBuffer * bufferOut)
+ bool BindData(sqlite3_stmt * statement, int pos, SQLiteField fld, typed_object data, SerialBuffer * bufferOut)
{
+ int result = 1;
Class dataType = fld.type;
SerialBuffer buffer = null;
switch(fld.sqliteType)
{
- case SQLITE_INTEGER:
+ case SQLITE_INTEGER:
{
switch(dataType.typeSize)
{
case 8:
- sqlite3_bind_int64(statement, pos, (sqlite3_int64)*(int64 *)data);
+ result = sqlite3_bind_int64(statement, pos, (sqlite3_int64)*(int64 *)data);
break;
case 4:
- sqlite3_bind_int(statement, pos, *(int *)data);
+ result = sqlite3_bind_int(statement, pos, *(int *)data);
break;
case 2:
{
value = (int)*(short *)data;
else
value = (int)*(uint16 *)data;
- sqlite3_bind_int(statement, pos, value);
+ result = sqlite3_bind_int(statement, pos, value);
break;
}
case 1:
value = (int)*(char *)data;
else
value = (int)*(byte *)data;
- sqlite3_bind_int(statement, pos, value);
+ result = sqlite3_bind_int(statement, pos, value);
break;
}
}
case SQLITE_FLOAT:
{
if(dataType.typeSize == 8)
- sqlite3_bind_double(statement, pos, *(double *)data);
+ result = sqlite3_bind_double(statement, pos, *(double *)data);
else
- sqlite3_bind_double(statement, pos, (double)*(float *)data);
+ result = sqlite3_bind_double(statement, pos, (double)*(float *)data);
break;
}
case SQLITE_TEXT:
{
if((char *)data)
- sqlite3_bind_text(statement, pos, (char *)data, strlen((char *)data), SQLITE_TRANSIENT);
+ result = sqlite3_bind_text(statement, pos, (char *)data, strlen((char *)data), SQLITE_TRANSIENT);
else
- sqlite3_bind_text(statement, pos, null, 0, SQLITE_TRANSIENT);
+ result = sqlite3_bind_null(statement, pos);
break;
}
case SQLITE_BLOB:
case SQLITE_NULL:
{
- buffer = SerialBuffer { };
- dataType._vTbl[__ecereVMethodID_class_OnSerialize](dataType, data, buffer);
- sqlite3_bind_text(statement, pos, buffer._buffer, buffer.count, SQLITE_TRANSIENT);
+ if((void *)data)
+ {
+ buffer = SerialBuffer { };
+ ((void (*)(void *, void *, void *))(void *)dataType._vTbl[__ecereVMethodID_class_OnSerialize])(dataType, data, buffer);
+ result = sqlite3_bind_text(statement, pos, buffer._buffer, buffer.count, SQLITE_TRANSIENT);
+ }
+ else
+ result = sqlite3_bind_null(statement, pos);
break;
}
}
*bufferOut = buffer;
else
delete buffer;
+ return !result;
}
void AddCursorWhereClauses(char * command, MoveOptions move, bool useIndex)
{
- if(move == next)
+ if(move == next || move == previous)
{
// Where clauses for index
if(useIndex)
bool gotPrimaryKey = false;
strcatf(command, " AND (");
- for(c = 0; c < tbl.indexFieldsCount; c++)
+ for(c = ((move == next) ? 0 : tbl.indexFieldsCount-1); (move == next) ? c < tbl.indexFieldsCount : c >= 0; (move == next) ? c++ : c--)
{
char where[1024];
FieldIndex * fIndex = &tbl.indexFields[c];
strcat(where, "`");
strcat(where, fIndex->field.name);
strcat(where, "` ");
- strcat(where, fIndex->order == descending ? "<" : ">");
+ strcat(where, (fIndex->order == ((move == next) ? descending : ascending)) ? "<" : ">");
strcat(where, " ? OR (");
strcat(where, fIndex->field.name);
if(fIndex->field == tbl.primaryKey)
strcat(where, " = ? AND (");
strcat(command, where);
}
- strcat(command, gotPrimaryKey ? "1)" : "ROWID > ?)");
- for(; c > 0; c--)
+ strcat(command, gotPrimaryKey ? "1)" : ((move == next) ? "ROWID > ?)" : "ROWID < ?)"));
+ for(c = 0; c < tbl.indexFieldsCount; c++)
strcat(command, "))");
}
else
- strcatf(command, " AND ROWID > ?");
+ strcatf(command, (move == next) ? " AND ROWID > ?" : " AND ROWID < ?");
}
}
void BindCursorData(sqlite3_stmt * stmt, MoveOptions move, bool useIndex, int * bindId)
{
- if(move == next)
+ if(move == next || move == previous)
{
// The binds for the Extra ordering Where clauses
if(useIndex)
{
int c;
- for(c = 0; c < tbl.indexFieldsCount; c++)
+ /* // Code to not rely on curStatement being set up
+ SQLiteRow dataRow = (SQLiteRow)tbl.CreateRow();
+ dataRow.GoToSysID((uint)rowID);
+ */
+ for(c = ((move == next) ? 0 : tbl.indexFieldsCount-1); (move == next) ? c < tbl.indexFieldsCount : c >= 0; (move == next) ? c++ : c--)
{
FieldIndex * fIndex = &tbl.indexFields[c];
int64 data;
data = (int64)new0 byte[type.structSize];
dataPtr = (void *) data;
}
+ // ((bool (*)())(void *)dataRow.GetData)(dataRow, fld, type, (type.type == structClass) ? (void *)data : &data);
((bool (*)())(void *)this.GetData)(this, fld, type, (type.type == structClass) ? (void *)data : &data);
if(type.type == normalClass || type.type == noHeadClass)
dataPtr = (void *) data;
else
dataPtr = &data;
- ((void (*)())(void *)this.BindData)(this, stmt, (*bindId)++, fld, type, dataPtr, &buffer);
+ ((bool (*)())(void *)this.BindData)(this, stmt, (*bindId)++, fld, type, dataPtr, &buffer);
+ // NOTE: The data is bound twice, for there are 2x '?' in the query from AddCursorWhereClauses
// Reuse the buffer for Blobs...
if(fld.sqliteType == SQLITE_BLOB || fld.sqliteType == SQLITE_NULL)
{
delete buffer;
}
else
- ((void (*)())(void *)this.BindData)(this, stmt, (*bindId)++, fld, type, dataPtr, null);
+ ((bool (*)())(void *)this.BindData)(this, stmt, (*bindId)++, fld, type, dataPtr, null);
- type._vTbl[__ecereVMethodID_class_OnFree](type, dataPtr);
+ ((void (*)(void *, void *))(void *)type._vTbl[__ecereVMethodID_class_OnFree])(type, dataPtr);
}
+ // delete dataRow;
}
// Bind for the rowid
if(fld == tbl.primaryKey)
{
+ if(curStatement) { sqlite3_reset(curStatement); curStatement = null; }
+ if(findStatement) { sqlite3_finalize(findStatement); findStatement = null; }
+ if(nextFindStatement) { sqlite3_finalize(nextFindStatement); nextFindStatement = null; }
+ if(prevFindStatement) { sqlite3_finalize(prevFindStatement); prevFindStatement = null; }
+ if(lastFindStatement) { sqlite3_finalize(lastFindStatement); lastFindStatement = null; }
result = GoToSysID(*(int *)data);
if(result)
findSysID = true;
return result;
}
- sprintf(command, "SELECT ROWID, * FROM `%s` WHERE ", tbl.name);
- strcatf(command, "`%s` = ?", fld.name);
- useIndex = tbl.GetIndexOrder(order);
+ useIndex = tbl.GetIndexOrder(order, false);
+ // Basic Find
+ sprintf(command, "SELECT ROWID, * FROM `%s` WHERE `%s` = ?", tbl.name, fld.name);
AddCursorWhereClauses(command, move, useIndex);
strcat(command, order);
strcat(command, ";");
-
result = sqlite3_prepare_v2(tbl.db.db, command, -1, &stmt, null);
-
BindData(stmt, bindId++, (SQLiteField)fld, data, null);
BindCursorData(stmt, move, useIndex, &bindId);
- if(curStatement)
- sqlite3_reset(curStatement);
- if(findStatement)
- sqlite3_finalize(findStatement);
+ // Currently, we can't reset curStatement until after BindCursorData, as current data is read from it
+ if(curStatement) { sqlite3_reset(curStatement); curStatement = null; }
+ if(findStatement) { sqlite3_finalize(findStatement); findStatement = null; }
+ if(nextFindStatement) { sqlite3_finalize(nextFindStatement); nextFindStatement = null; }
+ if(prevFindStatement) { sqlite3_finalize(prevFindStatement); prevFindStatement = null; }
+ if(lastFindStatement) { sqlite3_finalize(lastFindStatement); lastFindStatement = null; }
+
curStatement = findStatement = stmt;
+ findBindId = bindId;
+
+ // For going back to forward find
+ bindId = 1;
+ sprintf(command, "SELECT ROWID, * FROM `%s` WHERE `%s` = ?", tbl.name, fld.name);
+ AddCursorWhereClauses(command, next, useIndex);
+ strcat(command, order);
+ strcat(command, ";");
+ result = sqlite3_prepare_v2(tbl.db.db, command, -1, &stmt, null);
+ BindData(stmt, bindId++, (SQLiteField)fld, data, null);
+ nextFindStatement = stmt;
+
+ // Backwards
+ tbl.GetIndexOrder(order, true);
+ // For tracing back finds
+ bindId = 1;
+ sprintf(command, "SELECT ROWID, * FROM `%s` WHERE `%s` = ?", tbl.name, fld.name);
+ AddCursorWhereClauses(command, previous, true);
+ strcat(command, order);
+ strcat(command, ";");
+ result = sqlite3_prepare_v2(tbl.db.db, command, -1, &stmt, null);
+ BindData(stmt, bindId++, (SQLiteField)fld, data, null);
+ prevFindStatement = stmt;
+
+ // For tracing back from last
+ bindId = 1;
+ sprintf(command, "SELECT ROWID, * FROM `%s` WHERE `%s` = ?", tbl.name, fld.name);
+ strcat(command, order);
+ strcat(command, ";");
+ result = sqlite3_prepare_v2(tbl.db.db, command, -1, &stmt, null);
+ BindData(stmt, bindId++, (SQLiteField)fld, data, null);
+ lastFindStatement = stmt;
result = sqlite3_step(findStatement);
bool FindMultiple(FieldFindData * findData, MoveOptions move, int numFields)
{
+#define BINDDATA \
+ for(c = 0; c < numFields; c++) \
+ { \
+ FieldFindData * fieldFind = &findData[c]; \
+ SQLiteField sqlFld = (SQLiteField)findData->field; \
+ Class dataType = sqlFld.type; \
+ BindData(stmt, bindId++, sqlFld, (dataType.type == structClass || dataType.type == noHeadClass || dataType.type == normalClass) ? fieldFind->value.p : &fieldFind->value.i, null); \
+ }
+
if(numFields)
{
- char command[4096], order[1024];
+ char criterias[4096], command[4096], order[1024];
int result;
int c;
bool useIndex;
sqlite3_stmt * stmt = null;
int bindId = 1;
- sprintf(command, "SELECT ROWID, * FROM `%s` WHERE `", tbl.name);
+ // Criterias
+ sprintf(criterias, "SELECT ROWID, * FROM `%s` WHERE `", tbl.name);
for(c = 0; c < numFields; c++)
{
FieldFindData * fieldFind = &findData[c];
- if(c) strcat(command, " AND `");
- strcat(command, fieldFind->field.name);
- strcat(command, "` = ?");
+ if(c) strcat(criterias, " AND `");
+ strcat(criterias, fieldFind->field.name);
+ strcat(criterias, "` = ?");
}
- useIndex = tbl.GetIndexOrder(order);
+ useIndex = tbl.GetIndexOrder(order, false);
+ // Basic Find (multiple)
+ strcpy(command, criterias);
AddCursorWhereClauses(command, move, useIndex);
strcat(command, order);
strcat(command, ";");
-
result = sqlite3_prepare_v2(tbl.db.db, command, -1, &stmt, null);
-
- for(c = 0; c < numFields; c++)
- {
- FieldFindData * fieldFind = &findData[c];
- SQLiteField sqlFld = (SQLiteField)findData->field;
- Class dataType = sqlFld.type;
- BindData(stmt, bindId++, sqlFld, (dataType.type == structClass || dataType.type == noHeadClass || dataType.type == normalClass) ? fieldFind->value.p : &fieldFind->value.i, null);
- }
+ BINDDATA;
BindCursorData(stmt, move, useIndex, &bindId);
- if(curStatement)
- sqlite3_reset(curStatement);
- if(findMultipleStatement)
- sqlite3_finalize(findMultipleStatement);
+ // Currently, we can't reset curStatement until after BindCursorData, as current data is read from it
+ if(curStatement) { sqlite3_reset(curStatement); curStatement = null; }
+ if(findStatement) { sqlite3_finalize(findStatement); findStatement = null; }
+ if(nextFindStatement) { sqlite3_finalize(nextFindStatement); nextFindStatement = null; }
+ if(prevFindStatement) { sqlite3_finalize(prevFindStatement); prevFindStatement = null; }
+ if(lastFindStatement) { sqlite3_finalize(lastFindStatement); lastFindStatement = null; }
- curStatement = findMultipleStatement = stmt;
+ curStatement = findStatement = stmt;
+ findBindId = bindId;
- result = sqlite3_step(findMultipleStatement);
+ // For tracing back forward finds
+ bindId = 1;
+ strcpy(command, criterias);
+ AddCursorWhereClauses(command, previous, true);
+ strcat(command, order);
+ strcat(command, ";");
+ result = sqlite3_prepare_v2(tbl.db.db, command, -1, &stmt, null);
+ BINDDATA;
+ nextFindStatement = stmt;
+
+ // Backwards
+ tbl.GetIndexOrder(order, true);
+ // For tracing back finds
+ bindId = 1;
+ strcpy(command, criterias);
+ AddCursorWhereClauses(command, next, useIndex);
+ strcat(command, order);
+ strcat(command, ";");
+ result = sqlite3_prepare_v2(tbl.db.db, command, -1, &stmt, null);
+ BINDDATA;
+ prevFindStatement = stmt;
+
+ // For tracing back from last
+ bindId = 1;
+ strcpy(command, criterias);
+ strcat(command, order);
+ strcat(command, ";");
+ result = sqlite3_prepare_v2(tbl.db.db, command, -1, &stmt, null);
+ BINDDATA;
+ lastFindStatement = stmt;
+
+ result = sqlite3_step(findStatement);
done = result == SQLITE_DONE || (result && result != SQLITE_ROW);
if(done)
{
rowID = 0;
- sqlite3_reset(findMultipleStatement);
+ sqlite3_reset(findStatement);
}
else
- rowID = sqlite3_column_int64(findMultipleStatement, 0);
+ rowID = sqlite3_column_int64(findStatement, 0);
return !done;
}
return false;
sqlite3_reset(curStatement);
sqlite3_bind_int64(sysIDStatement, 1, (sqlite3_int64)rowID);
result = sqlite3_step(curStatement);
+ done = false; // Make sure 'nil' is false
return true;
}
sqlite3_reset(insertStatement);
switch(sqlFld.sqliteType)
{
- case SQLITE_INTEGER:
+ case SQLITE_INTEGER:
{
switch(dataType.typeSize)
{
buffer._buffer = sqlite3_column_text(curStatement, num);
buffer.count = buffer._size;
- dataType._vTbl[__ecereVMethodID_class_OnUnserialize](dataType, data, buffer);
-
+ ((void (*)(void *, void *, void *))(void *)dataType._vTbl[__ecereVMethodID_class_OnUnserialize])(dataType, data, buffer);
+
buffer._buffer = null;
delete buffer;
break;
sqlite3_reset(queryStatement);
{
SerialBuffer buffer { };
- type._vTbl[__ecereVMethodID_class_OnSerialize](type, data, buffer);
+ ((void (*)(void *, void *, void *))(void *)type._vTbl[__ecereVMethodID_class_OnSerialize])(type, data, buffer);
result = sqlite3_bind_text(queryStatement, paramID, buffer._buffer, buffer.count, SQLITE_TRANSIENT);
delete buffer;
}
return !result;
}
+ bool BindQueryData(int pos, SQLiteField fld, typed_object data)
+ {
+ if(curStatement != queryStatement)
+ {
+ if(curStatement) sqlite3_reset(curStatement);
+ curStatement = queryStatement;
+ }
+ sqlite3_reset(queryStatement);
+ return BindData(queryStatement, pos, fld, data, null);
+ }
+
/*char * GetExtraColumn(int paramID)
{
- SQLiteField lastFld = tbl.fields.last;
+ SQLiteField lastFld = tbl._fields.last;
return sqlite3_column_text(curStatement, lastFld.num + 1 + paramID);
}*/
char * GetColumn(int paramID)