eda/drivers/sqlite: Work around for Clang compilation
[sdk] / eda / drivers / sqlite / EDASQLite.ec
index df0ab72..4bc49d9 100644 (file)
@@ -12,6 +12,10 @@ public import "EDA"
 #include "sqlite3.h"
 #endif
 
+#define uint _uint
+#include "ffi.h"
+#undef uint
+
 static void UnusedFunction()
 {
    int a;
@@ -45,11 +49,11 @@ int CollationCompare(Class type, int count1, void * data1, int count2, void * da
       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;
@@ -67,11 +71,11 @@ int CollationCompare(Class type, int count1, void * data1, int count2, void * da
 
       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;
@@ -81,98 +85,15 @@ int CollationCompare(Class type, int count1, void * data1, int count2, void * da
       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)
    {
@@ -184,7 +105,7 @@ class SQLiteDataSource : DataSourceDriver
 
          // 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))
          {
@@ -194,11 +115,23 @@ class SQLiteDataSource : DataSourceDriver
          }
          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;
       }
@@ -248,9 +181,10 @@ class SQLiteDatabase : Database
 {
    sqlite3 * db;
    AVLTree<String> collations { };
-   
+
    ~SQLiteDatabase()
    {
+      sqlite3_exec(db, "PRAGMA locking_mode=normal", null, null, null);
       sqlite3_close(db);
    }
 
@@ -287,7 +221,7 @@ class SQLiteDatabase : Database
          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)
       {
@@ -298,13 +232,13 @@ class SQLiteDatabase : Database
          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)
       {
@@ -320,7 +254,7 @@ class SQLiteDatabase : Database
          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) };
@@ -336,7 +270,7 @@ class SQLiteDatabase : Database
                   {
                      char * sql = t[nCols * r];
                      char * bracket = strchr(sql, '(');
-                     if(bracket) 
+                     if(bracket)
                      {
                         int c = 0;
                         bracket++;
@@ -366,7 +300,7 @@ class SQLiteDatabase : Database
                            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); }
@@ -377,9 +311,9 @@ class SQLiteDatabase : Database
                            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;
@@ -389,8 +323,9 @@ class SQLiteDatabase : Database
                }
                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);
 
@@ -406,10 +341,10 @@ class SQLiteDatabase : Database
 
                      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;
@@ -429,9 +364,14 @@ class SQLiteDatabase : Database
                      }
 
                      {
-                        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);
@@ -467,19 +407,369 @@ class SQLiteDatabase : Database
 
    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
@@ -487,7 +777,7 @@ class SQLiteTable : Table
    char * name;
    bool mustCreate;
    SQLiteDatabase db;
-   LinkList<SQLiteField> fields { };
+   LinkList<SQLiteField> _fields { };
    char * specialStatement;
    SQLiteField primaryKey;
    FieldIndex * indexFields;
@@ -504,13 +794,13 @@ class SQLiteTable : Table
       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"))
       {
@@ -566,7 +856,7 @@ class SQLiteTable : Table
             PrintLn($"WARNING: Table not yet created for class ", (String)type.name);
          }
       }
-      
+
       if(mustCreate)
       {
          if(sqliteType == SQLITE_BLOB)
@@ -618,9 +908,9 @@ class SQLiteTable : Table
          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;
@@ -628,7 +918,7 @@ class SQLiteTable : Table
 
    Field FindField(const String name)
    {
-      for(f : fields; !strcmp(f.name, name))
+      for(f : _fields; !strcmp(f.name, name))
       {
          if(!primaryKey)
          {
@@ -700,12 +990,17 @@ class SQLiteTable : Table
 
    Field GetFirstField()
    {
-      return fields.first;
+      return _fields.first;
+   }
+
+   Field GetPrimaryKey()
+   {
+      return primaryKey;
    }
 
    uint GetFieldsCount()
    {
-      return fields.count;
+      return _fields.count;
    }
 
    uint GetRowsCount()
@@ -726,9 +1021,9 @@ class SQLiteTable : Table
    }
 
    // 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;
@@ -737,7 +1032,7 @@ class SQLiteTable : Table
       {
          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];
@@ -746,13 +1041,18 @@ class SQLiteTable : Table
             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];
@@ -792,7 +1092,7 @@ class SQLiteTable : Table
 
          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);
@@ -804,7 +1104,7 @@ class SQLiteTable : Table
       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 };
    }
@@ -814,7 +1114,7 @@ class SQLiteTable : Table
       delete name;
       delete specialStatement;
       delete indexFields;
-      fields.Free();
+      _fields.Free();
    }
 }
 
@@ -825,9 +1125,10 @@ class SQLiteRow : DriverRow
 
    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;
@@ -844,7 +1145,8 @@ class SQLiteRow : DriverRow
    // 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;
@@ -854,7 +1156,9 @@ class SQLiteRow : DriverRow
    {
       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);
@@ -898,24 +1202,54 @@ class SQLiteRow : DriverRow
          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:
@@ -945,37 +1279,43 @@ class SQLiteRow : DriverRow
       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:
                {
@@ -984,7 +1324,7 @@ class SQLiteRow : DriverRow
                      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:
@@ -994,7 +1334,7 @@ class SQLiteRow : DriverRow
                      value = (int)*(char *)data;
                   else
                      value = (int)*(byte *)data;
-                  sqlite3_bind_int(statement, pos, value);
+                  result = sqlite3_bind_int(statement, pos, value);
                   break;
                }
             }
@@ -1003,25 +1343,30 @@ class SQLiteRow : DriverRow
          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;
          }
       }
@@ -1029,11 +1374,12 @@ class SQLiteRow : DriverRow
          *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)
@@ -1042,7 +1388,7 @@ class SQLiteRow : DriverRow
             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];
@@ -1051,7 +1397,7 @@ class SQLiteRow : DriverRow
                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)
@@ -1059,24 +1405,28 @@ class SQLiteRow : DriverRow
                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;
@@ -1096,12 +1446,14 @@ class SQLiteRow : DriverRow
                   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)
                {
@@ -1109,10 +1461,11 @@ class SQLiteRow : DriverRow
                   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
@@ -1130,29 +1483,67 @@ class SQLiteRow : DriverRow
 
       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);
 
@@ -1169,57 +1560,95 @@ class SQLiteRow : DriverRow
 
    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;
@@ -1278,6 +1707,7 @@ class SQLiteRow : DriverRow
          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);
@@ -1306,7 +1736,7 @@ class SQLiteRow : DriverRow
 
       switch(sqlFld.sqliteType)
       {
-         case SQLITE_INTEGER: 
+         case SQLITE_INTEGER:
          {
             switch(dataType.typeSize)
             {
@@ -1377,8 +1807,8 @@ class SQLiteRow : DriverRow
             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;
@@ -1488,16 +1918,27 @@ class SQLiteRow : DriverRow
       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)