Diff of /System.Data.SQLite/SQLiteBase.cs [9dcae3] .. [8e2213] Maximize Restore

  Switch to side-by-side view

--- a/System.Data.SQLite/SQLiteBase.cs
+++ b/System.Data.SQLite/SQLiteBase.cs
@@ -16,168 +16,73 @@
   /// This internal class provides the foundation of SQLite support.  It defines all the abstract members needed to implement
   /// a SQLite data provider, and inherits from SQLiteConvert which allows for simple translations of string to and from SQLite.
   /// </summary>
-  internal abstract class SQLiteBase : SQLiteConvert, IDisposable
+  internal class SQLiteBase : SQLiteConvert, IDisposable
   {
     internal SQLiteBase(SQLiteDateFormats fmt)
       : base(fmt) { }
 
-    static internal object _lock = new object();
-
-    /// <summary>
-    /// Returns a string representing the active version of SQLite
-    /// </summary>
-    internal abstract string Version { get; }
-    /// <summary>
-    /// Returns the number of changes the last executing insert/update caused.
-    /// </summary>
-    internal abstract int Changes { get; }
-    /// <summary>
-    /// Opens a database.
-    /// </summary>
-    /// <remarks>
-    /// Implementers should call SQLiteFunction.BindFunctions() and save the array after opening a connection
-    /// to bind all attributed user-defined functions and collating sequences to the new connection.
-    /// </remarks>
-    /// <param name="strFilename">The filename of the database to open.  SQLite automatically creates it if it doesn't exist.</param>
-    /// <param name="flags">The open flags to use when creating the connection</param>
-    /// <param name="maxPoolSize">The maximum size of the pool for the given filename</param>
-    /// <param name="usePool">If true, the connection can be pulled from the connection pool</param>
-    internal abstract void Open(string strFilename, SQLiteOpenFlagsEnum flags, int maxPoolSize, bool usePool);
-    /// <summary>
-    /// Closes the currently-open database.
-    /// </summary>
-    /// <remarks>
-    /// After the database has been closed implemeters should call SQLiteFunction.UnbindFunctions() to deallocate all interop allocated
-    /// memory associated with the user-defined functions and collating sequences tied to the closed connection.
-    /// </remarks>
-    internal abstract void Close();
-    /// <summary>
-    /// Sets the busy timeout on the connection.  SQLiteCommand will call this before executing any command.
-    /// </summary>
-    /// <param name="nTimeoutMS">The number of milliseconds to wait before returning SQLITE_BUSY</param>
-    internal abstract void SetTimeout(int nTimeoutMS);
-    /// <summary>
-    /// Returns the text of the last error issued by SQLite
-    /// </summary>
-    /// <returns></returns>
-    internal abstract string SQLiteLastError();
-
-    /// <summary>
-    /// When pooling is enabled, force this connection to be disposed rather than returned to the pool
-    /// </summary>
-    internal abstract void ClearPool();
-
-    /// <summary>
-    /// Prepares a SQL statement for execution.
-    /// </summary>
-    /// <param name="cnn">The source connection preparing the command.  Can be null for any caller except LINQ</param>
-    /// <param name="strSql">The SQL command text to prepare</param>
-    /// <param name="previous">The previous statement in a multi-statement command, or null if no previous statement exists</param>
-    /// <param name="timeoutMS">The timeout to wait before aborting the prepare</param>
-    /// <param name="strRemain">The remainder of the statement that was not processed.  Each call to prepare parses the
-    /// SQL up to to either the end of the text or to the first semi-colon delimiter.  The remaining text is returned
-    /// here for a subsequent call to Prepare() until all the text has been processed.</param>
-    /// <returns>Returns an initialized SQLiteStatement.</returns>
-    internal abstract SQLiteStatement Prepare(SQLiteConnection cnn, string strSql, SQLiteStatement previous, uint timeoutMS, out string strRemain);
-    /// <summary>
-    /// Steps through a prepared statement.
-    /// </summary>
-    /// <param name="stmt">The SQLiteStatement to step through</param>
-    /// <returns>True if a row was returned, False if not.</returns>
-    internal abstract bool Step(SQLiteStatement stmt);
-    /// <summary>
-    /// Resets a prepared statement so it can be executed again.  If the error returned is SQLITE_SCHEMA, 
-    /// transparently attempt to rebuild the SQL statement and throw an error if that was not possible.
-    /// </summary>
-    /// <param name="stmt">The statement to reset</param>
-    /// <returns>Returns -1 if the schema changed while resetting, 0 if the reset was sucessful or 6 (SQLITE_LOCKED) if the reset failed due to a lock</returns>
-    internal abstract int Reset(SQLiteStatement stmt);
-    internal abstract void Cancel();
-
-    internal abstract void Bind_Double(SQLiteStatement stmt, int index, double value);
-    internal abstract void Bind_Int32(SQLiteStatement stmt, int index, Int32 value);
-    internal abstract void Bind_Int64(SQLiteStatement stmt, int index, Int64 value);
-    internal abstract void Bind_Text(SQLiteStatement stmt, int index, string value);
-    internal abstract void Bind_Blob(SQLiteStatement stmt, int index, byte[] blobData);
-    internal abstract void Bind_DateTime(SQLiteStatement stmt, int index, DateTime dt);
-    internal abstract void Bind_Null(SQLiteStatement stmt, int index);
-
-    internal abstract int Bind_ParamCount(SQLiteStatement stmt);
-    internal abstract string Bind_ParamName(SQLiteStatement stmt, int index);
-    internal abstract int Bind_ParamIndex(SQLiteStatement stmt, string paramName);
-
-    internal abstract int ColumnCount(SQLiteStatement stmt);
-    internal abstract string ColumnName(SQLiteStatement stmt, int index);
-    internal abstract TypeAffinity ColumnAffinity(SQLiteStatement stmt, int index);
-    internal abstract string ColumnType(SQLiteStatement stmt, int index, out TypeAffinity nAffinity);
-    internal abstract int ColumnIndex(SQLiteStatement stmt, string columnName);
-    internal abstract string ColumnOriginalName(SQLiteStatement stmt, int index);
-    internal abstract string ColumnDatabaseName(SQLiteStatement stmt, int index);
-    internal abstract string ColumnTableName(SQLiteStatement stmt, int index);
-    internal abstract void ColumnMetaData(string dataBase, string table, string column, out string dataType, out string collateSequence, out bool notNull, out bool primaryKey, out bool autoIncrement);
-    internal abstract void GetIndexColumnExtendedInfo(string database, string index, string column, out int sortMode, out int onError, out string collationSequence);
-
-    internal abstract double GetDouble(SQLiteStatement stmt, int index);
-    internal abstract Int32 GetInt32(SQLiteStatement stmt, int index);
-    internal abstract Int64 GetInt64(SQLiteStatement stmt, int index);
-    internal abstract string GetText(SQLiteStatement stmt, int index);
-    internal abstract long GetBytes(SQLiteStatement stmt, int index, int nDataoffset, byte[] bDest, int nStart, int nLength);
-    internal abstract long GetChars(SQLiteStatement stmt, int index, int nDataoffset, char[] bDest, int nStart, int nLength);
-    internal abstract DateTime GetDateTime(SQLiteStatement stmt, int index);
-    internal abstract bool IsNull(SQLiteStatement stmt, int index);
-
-    internal abstract void CreateCollation(string strCollation, SQLiteCollation func, SQLiteCollation func16);
-    internal abstract void CreateFunction(string strFunction, int nArgs, bool needCollSeq, SQLiteCallback func, SQLiteCallback funcstep, SQLiteFinalCallback funcfinal);
-    internal abstract CollationSequence GetCollationSequence(SQLiteFunction func, IntPtr context);
-    internal abstract int ContextCollateCompare(CollationEncodingEnum enc, IntPtr context, string s1, string s2);
-    internal abstract int ContextCollateCompare(CollationEncodingEnum enc, IntPtr context, char[] c1, char[] c2);
-
-    internal abstract int AggregateCount(IntPtr context);
-    internal abstract IntPtr AggregateContext(IntPtr context);
-
-    internal abstract long GetParamValueBytes(IntPtr ptr, int nDataOffset, byte[] bDest, int nStart, int nLength);
-    internal abstract double GetParamValueDouble(IntPtr ptr);
-    internal abstract int GetParamValueInt32(IntPtr ptr);
-    internal abstract Int64 GetParamValueInt64(IntPtr ptr);
-    internal abstract string GetParamValueText(IntPtr ptr);
-    internal abstract TypeAffinity GetParamValueType(IntPtr ptr);
-
-    internal abstract void ReturnBlob(IntPtr context, byte[] value);
-    internal abstract void ReturnDouble(IntPtr context, double value);
-    internal abstract void ReturnError(IntPtr context, string value);
-    internal abstract void ReturnInt32(IntPtr context, Int32 value);
-    internal abstract void ReturnInt64(IntPtr context, Int64 value);
-    internal abstract void ReturnNull(IntPtr context);
-    internal abstract void ReturnText(IntPtr context, string value);
-
-    internal abstract void SetPassword(byte[] passwordBytes);
-    internal abstract void ChangePassword(byte[] newPasswordBytes);
-
-    internal abstract void SetUpdateHook(SQLiteUpdateCallback func);
-    internal abstract void SetCommitHook(SQLiteCommitCallback func);
-    internal abstract void SetRollbackHook(SQLiteRollbackCallback func);
-
-    internal abstract int GetCursorForTable(SQLiteStatement stmt, int database, int rootPage);
-    internal abstract long GetRowIdForCursor(SQLiteStatement stmt, int cursor);
-
-    internal abstract object GetValue(SQLiteStatement stmt, int index, SQLiteType typ);
-
-    internal abstract bool AutoCommit
-    {
-      get;
-    }
-
-    protected virtual void Dispose(bool bDisposing)
-    {
-    }
-
-    public void Dispose()
-    {
-      Dispose(true);
-      GC.SuppressFinalize(this);
-    }
-
-    // These statics are here for lack of a better place to put them.
+    static internal object _lock = new object();
+
+      /// <summary>
+      /// The opaque pointer returned to us by the sqlite provider
+      /// </summary>
+      protected SQLiteConnectionHandle _sql;
+
+      protected string _fileName;
+      protected bool _usePool;
+      protected int _poolVersion;
+      private bool _buildingSchema;
+
+      /// <summary>
+      /// The user-defined functions registered on this connection
+      /// </summary>
+      protected SQLiteFunction[] _functionsArray;
+
+      internal virtual string Version
+      {
+          get
+          {
+              return SQLiteBase.SQLiteVersion;
+          }
+      }
+
+      internal static string SQLiteVersion
+      {
+          get
+          {
+              return UTF8ToString(UnsafeNativeMethods.sqlite3_libversion(), -1);
+          }
+      }
+
+      internal virtual bool AutoCommit
+      {
+          get
+          {
+              return IsAutocommit(_sql);
+          }
+      }
+
+      internal virtual int Changes
+      {
+          get
+          {
+              return UnsafeNativeMethods.sqlite3_changes(_sql);
+          }
+      }
+
+      protected virtual void Dispose(bool bDisposing)
+      {
+          if(bDisposing)
+              Close();
+      }
+
+      public void Dispose()
+      {
+          Dispose(true);
+          GC.SuppressFinalize(this);
+      }
+
+      // These statics are here for lack of a better place to put them.
     // They exist here because they are called during the finalization of
     // a SQLiteStatementHandle, SQLiteConnectionHandle, and SQLiteFunctionCookieHandle.
     // Therefore these functions have to be static, and have to be low-level.
@@ -249,7 +154,892 @@
     internal static bool IsAutocommit(SQLiteConnectionHandle hdl)
     {
       return (UnsafeNativeMethods.sqlite3_get_autocommit(hdl) == 1);
-    }
+    }
+
+      internal virtual void Close()
+      {
+          if (_sql != null)
+          {
+              if (_usePool)
+              {
+                  SQLiteBase.ResetConnection(_sql);
+                  SQLiteConnectionPool.Add(_fileName, _sql, _poolVersion);
+              }
+              else
+                  _sql.Dispose();
+          }
+
+          _sql = null;
+      }
+
+      internal virtual void Cancel()
+      {
+          UnsafeNativeMethods.sqlite3_interrupt(_sql);
+      }
+
+      internal virtual void Open(string strFilename, SQLiteOpenFlagsEnum flags, int maxPoolSize, bool usePool)
+      {
+          if (_sql != null) return;
+
+          _usePool = usePool;
+          if (usePool)
+          {
+              _fileName = strFilename;
+              _sql = SQLiteConnectionPool.Remove(strFilename, maxPoolSize, out _poolVersion);
+          }
+
+          if (_sql == null)
+          {
+              IntPtr db;
+
+#if !SQLITE_STANDARD
+              int n = UnsafeNativeMethods.sqlite3_open_interop(ToUTF8(strFilename), (int)flags, out db);
+#else
+              int n = UnsafeNativeMethods.sqlite3_open_v2(ToUTF8(strFilename), out db, (int)flags, IntPtr.Zero);
+#endif
+              if (n > 0) throw new SQLiteException(n, null);
+
+              _sql = db;
+          }
+          // Bind functions to this connection.  If any previous functions of the same name
+          // were already bound, then the new bindings replace the old.
+          _functionsArray = SQLiteFunction.BindFunctions(this);
+          SetTimeout(0);
+      }
+
+      internal virtual void ClearPool()
+      {
+          SQLiteConnectionPool.ClearPool(_fileName);
+      }
+
+      internal virtual void SetTimeout(int nTimeoutMS)
+      {
+          int n = UnsafeNativeMethods.sqlite3_busy_timeout(_sql, nTimeoutMS);
+          if (n > 0) throw new SQLiteException(n, SQLiteLastError());
+      }
+
+      internal virtual bool Step(SQLiteStatement stmt)
+      {
+          int n;
+          Random rnd = null;
+          uint starttick = (uint)Environment.TickCount;
+          uint timeout = (uint)(stmt._command._commandTimeout * 1000);
+
+          while (true)
+          {
+              n = UnsafeNativeMethods.sqlite3_step(stmt._sqlite_stmt);
+
+              if (n == 100) return true;
+              if (n == 101) return false;
+
+              if (n > 0)
+              {
+                  int r;
+
+                  // An error occurred, attempt to reset the statement.  If the reset worked because the
+                  // schema has changed, re-try the step again.  If it errored our because the database
+                  // is locked, then keep retrying until the command timeout occurs.
+                  r = Reset(stmt);
+
+                  if (r == 0)
+                      throw new SQLiteException(n, SQLiteLastError());
+
+                  else if ((r == 6 || r == 5) && stmt._command != null) // SQLITE_LOCKED || SQLITE_BUSY
+                  {
+                      // Keep trying
+                      if (rnd == null) // First time we've encountered the lock
+                          rnd = new Random();
+
+                      // If we've exceeded the command's timeout, give up and throw an error
+                      if ((uint)Environment.TickCount - starttick > timeout)
+                      {
+                          throw new SQLiteException(r, SQLiteLastError());
+                      }
+                      else
+                      {
+                          // Otherwise sleep for a random amount of time up to 150ms
+                          System.Threading.Thread.Sleep(rnd.Next(1, 150));
+                      }
+                  }
+              }
+          }
+      }
+
+      internal virtual int Reset(SQLiteStatement stmt)
+      {
+          int n;
+
+#if !SQLITE_STANDARD
+          n = UnsafeNativeMethods.sqlite3_reset_interop(stmt._sqlite_stmt);
+#else
+          n = UnsafeNativeMethods.sqlite3_reset(stmt._sqlite_stmt);
+#endif
+
+          // If the schema changed, try and re-prepare it
+          if (n == 17) // SQLITE_SCHEMA
+          {
+              // Recreate a dummy statement
+              string str;
+              using (SQLiteStatement tmp = Prepare(null, stmt._sqlStatement, null, (uint)(stmt._command._commandTimeout * 1000), out str))
+              {
+                  // Finalize the existing statement
+                  stmt._sqlite_stmt.Dispose();
+                  // Reassign a new statement pointer to the old statement and clear the temporary one
+                  stmt._sqlite_stmt = tmp._sqlite_stmt;
+                  tmp._sqlite_stmt = null;
+
+                  // Reapply parameters
+                  stmt.BindParameters();
+              }
+              return -1; // Reset was OK, with schema change
+          }
+          else if (n == 6 || n == 5) // SQLITE_LOCKED || SQLITE_BUSY
+              return n;
+
+          if (n > 0)
+              throw new SQLiteException(n, SQLiteLastError());
+
+          return 0; // We reset OK, no schema changes
+      }
+
+      internal virtual string SQLiteLastError()
+      {
+          return SQLiteBase.SQLiteLastError(_sql);
+      }
+
+      internal virtual SQLiteStatement Prepare(SQLiteConnection cnn, string strSql, SQLiteStatement previous, uint timeoutMS, out string strRemain)
+      {
+          IntPtr stmt = IntPtr.Zero;
+          IntPtr ptr = IntPtr.Zero;
+          int len = 0;
+          int n = 17;
+          int retries = 0;
+          byte[] b = ToUTF8(strSql);
+          string typedefs = null;
+          SQLiteStatement cmd = null;
+          Random rnd = null;
+          uint starttick = (uint)Environment.TickCount;
+
+          GCHandle handle = GCHandle.Alloc(b, GCHandleType.Pinned);
+          IntPtr psql = handle.AddrOfPinnedObject();
+          try
+          {
+              while ((n == 17 || n == 6 || n == 5) && retries < 3)
+              {
+#if !SQLITE_STANDARD
+                  n = UnsafeNativeMethods.sqlite3_prepare_interop(_sql, psql, b.Length - 1, out stmt, out ptr, out len);
+#else
+          n = UnsafeNativeMethods.sqlite3_prepare(_sql, psql, b.Length - 1, out stmt, out ptr);
+          len = -1;
+#endif
+
+                  if (n == 17)
+                      retries++;
+                  else if (n == 1)
+                  {
+                      if (String.Compare(SQLiteLastError(), "near \"TYPES\": syntax error", StringComparison.OrdinalIgnoreCase) == 0)
+                      {
+                          int pos = strSql.IndexOf(';');
+                          if (pos == -1) pos = strSql.Length - 1;
+
+                          typedefs = strSql.Substring(0, pos + 1);
+                          strSql = strSql.Substring(pos + 1);
+
+                          strRemain = "";
+
+                          while (cmd == null && strSql.Length > 0)
+                          {
+                              cmd = Prepare(cnn, strSql, previous, timeoutMS, out strRemain);
+                              strSql = strRemain;
+                          }
+
+                          if (cmd != null)
+                              cmd.SetTypes(typedefs);
+
+                          return cmd;
+                      }
+#if !PLATFORM_COMPACTFRAMEWORK
+                      else if (_buildingSchema == false && String.Compare(SQLiteLastError(), 0, "no such table: TEMP.SCHEMA", 0, 26, StringComparison.OrdinalIgnoreCase) == 0)
+                      {
+                          strRemain = "";
+                          _buildingSchema = true;
+                          try
+                          {
+                              ISQLiteSchemaExtensions ext = ((IServiceProvider)SQLiteFactory.Instance).GetService(typeof(ISQLiteSchemaExtensions)) as ISQLiteSchemaExtensions;
+
+                              if (ext != null)
+                                  ext.BuildTempSchema(cnn);
+
+                              while (cmd == null && strSql.Length > 0)
+                              {
+                                  cmd = Prepare(cnn, strSql, previous, timeoutMS, out strRemain);
+                                  strSql = strRemain;
+                              }
+
+                              return cmd;
+                          }
+                          finally
+                          {
+                              _buildingSchema = false;
+                          }
+                      }
+#endif
+                  }
+                  else if (n == 6 || n == 5) // Locked -- delay a small amount before retrying
+                  {
+                      // Keep trying
+                      if (rnd == null) // First time we've encountered the lock
+                          rnd = new Random();
+
+                      // If we've exceeded the command's timeout, give up and throw an error
+                      if ((uint)Environment.TickCount - starttick > timeoutMS)
+                      {
+                          throw new SQLiteException(n, SQLiteLastError());
+                      }
+                      else
+                      {
+                          // Otherwise sleep for a random amount of time up to 150ms
+                          System.Threading.Thread.Sleep(rnd.Next(1, 150));
+                      }
+                  }
+              }
+
+              if (n > 0) throw new SQLiteException(n, SQLiteLastError());
+
+              strRemain = UTF8ToString(ptr, len);
+
+              if (stmt != IntPtr.Zero) cmd = new SQLiteStatement(this, stmt, strSql.Substring(0, strSql.Length - strRemain.Length), previous);
+
+              return cmd;
+          }
+          finally
+          {
+              handle.Free();
+          }
+      }
+
+      internal virtual void Bind_Double(SQLiteStatement stmt, int index, double value)
+      {
+#if !PLATFORM_COMPACTFRAMEWORK
+          int n = UnsafeNativeMethods.sqlite3_bind_double(stmt._sqlite_stmt, index, value);
+#else
+      int n = UnsafeNativeMethods.sqlite3_bind_double_interop(stmt._sqlite_stmt, index, ref value);
+#endif
+          if (n > 0) throw new SQLiteException(n, SQLiteLastError());
+      }
+
+      internal virtual void Bind_Int32(SQLiteStatement stmt, int index, int value)
+      {
+          int n = UnsafeNativeMethods.sqlite3_bind_int(stmt._sqlite_stmt, index, value);
+          if (n > 0) throw new SQLiteException(n, SQLiteLastError());
+      }
+
+      internal virtual void Bind_Int64(SQLiteStatement stmt, int index, long value)
+      {
+#if !PLATFORM_COMPACTFRAMEWORK
+          int n = UnsafeNativeMethods.sqlite3_bind_int64(stmt._sqlite_stmt, index, value);
+#else
+      int n = UnsafeNativeMethods.sqlite3_bind_int64_interop(stmt._sqlite_stmt, index, ref value);
+#endif
+          if (n > 0) throw new SQLiteException(n, SQLiteLastError());
+      }
+
+      internal virtual void Bind_Text(SQLiteStatement stmt, int index, string value)
+      {
+          byte[] b = ToUTF8(value);
+          int n = UnsafeNativeMethods.sqlite3_bind_text(stmt._sqlite_stmt, index, b, b.Length - 1, (IntPtr)(-1));
+          if (n > 0) throw new SQLiteException(n, SQLiteLastError());
+      }
+
+      internal virtual void Bind_DateTime(SQLiteStatement stmt, int index, DateTime dt)
+      {
+          byte[] b = ToUTF8(dt);
+          int n = UnsafeNativeMethods.sqlite3_bind_text(stmt._sqlite_stmt, index, b, b.Length - 1, (IntPtr)(-1));
+          if (n > 0) throw new SQLiteException(n, SQLiteLastError());
+      }
+
+      internal virtual void Bind_Blob(SQLiteStatement stmt, int index, byte[] blobData)
+      {
+          int n = UnsafeNativeMethods.sqlite3_bind_blob(stmt._sqlite_stmt, index, blobData, blobData.Length, (IntPtr)(-1));
+          if (n > 0) throw new SQLiteException(n, SQLiteLastError());
+      }
+
+      internal virtual void Bind_Null(SQLiteStatement stmt, int index)
+      {
+          int n = UnsafeNativeMethods.sqlite3_bind_null(stmt._sqlite_stmt, index);
+          if (n > 0) throw new SQLiteException(n, SQLiteLastError());
+      }
+
+      internal virtual int Bind_ParamCount(SQLiteStatement stmt)
+      {
+          return UnsafeNativeMethods.sqlite3_bind_parameter_count(stmt._sqlite_stmt);
+      }
+
+      internal virtual string Bind_ParamName(SQLiteStatement stmt, int index)
+      {
+#if !SQLITE_STANDARD
+          int len;
+          return UTF8ToString(UnsafeNativeMethods.sqlite3_bind_parameter_name_interop(stmt._sqlite_stmt, index, out len), len);
+#else
+      return UTF8ToString(UnsafeNativeMethods.sqlite3_bind_parameter_name(stmt._sqlite_stmt, index), -1);
+#endif
+      }
+
+      internal virtual int Bind_ParamIndex(SQLiteStatement stmt, string paramName)
+      {
+          return UnsafeNativeMethods.sqlite3_bind_parameter_index(stmt._sqlite_stmt, ToUTF8(paramName));
+      }
+
+      internal virtual int ColumnCount(SQLiteStatement stmt)
+      {
+          return UnsafeNativeMethods.sqlite3_column_count(stmt._sqlite_stmt);
+      }
+
+      internal virtual string ColumnName(SQLiteStatement stmt, int index)
+      {
+#if !SQLITE_STANDARD
+          int len;
+          return UTF8ToString(UnsafeNativeMethods.sqlite3_column_name_interop(stmt._sqlite_stmt, index, out len), len);
+#else
+      return UTF8ToString(UnsafeNativeMethods.sqlite3_column_name(stmt._sqlite_stmt, index), -1);
+#endif
+      }
+
+      internal virtual TypeAffinity ColumnAffinity(SQLiteStatement stmt, int index)
+      {
+          return UnsafeNativeMethods.sqlite3_column_type(stmt._sqlite_stmt, index);
+      }
+
+      internal virtual string ColumnType(SQLiteStatement stmt, int index, out TypeAffinity nAffinity)
+      {
+          int len;
+#if !SQLITE_STANDARD
+          IntPtr p = UnsafeNativeMethods.sqlite3_column_decltype_interop(stmt._sqlite_stmt, index, out len);
+#else
+      len = -1;
+      IntPtr p = UnsafeNativeMethods.sqlite3_column_decltype(stmt._sqlite_stmt, index);
+#endif
+          nAffinity = ColumnAffinity(stmt, index);
+
+          if (p != IntPtr.Zero) return UTF8ToString(p, len);
+          else
+          {
+              string[] ar = stmt.TypeDefinitions;
+              if (ar != null)
+              {
+                  if (index < ar.Length && ar[index] != null)
+                      return ar[index];
+              }
+              return String.Empty;
+
+              //switch (nAffinity)
+              //{
+              //  case TypeAffinity.Int64:
+              //    return "BIGINT";
+              //  case TypeAffinity.Double:
+              //    return "DOUBLE";
+              //  case TypeAffinity.Blob:
+              //    return "BLOB";
+              //  default:
+              //    return "TEXT";
+              //}
+          }
+      }
+
+      internal virtual int ColumnIndex(SQLiteStatement stmt, string columnName)
+      {
+          int x = ColumnCount(stmt);
+
+          for (int n = 0; n < x; n++)
+          {
+              if (String.Compare(columnName, ColumnName(stmt, n), StringComparison.OrdinalIgnoreCase) == 0)
+                  return n;
+          }
+          return -1;
+      }
+
+      internal virtual string ColumnOriginalName(SQLiteStatement stmt, int index)
+      {
+#if !SQLITE_STANDARD
+          int len;
+          return UTF8ToString(UnsafeNativeMethods.sqlite3_column_origin_name_interop(stmt._sqlite_stmt, index, out len), len);
+#else
+      return UTF8ToString(UnsafeNativeMethods.sqlite3_column_origin_name(stmt._sqlite_stmt, index), -1);
+#endif
+      }
+
+      internal virtual string ColumnDatabaseName(SQLiteStatement stmt, int index)
+      {
+#if !SQLITE_STANDARD
+          int len;
+          return UTF8ToString(UnsafeNativeMethods.sqlite3_column_database_name_interop(stmt._sqlite_stmt, index, out len), len);
+#else
+      return UTF8ToString(UnsafeNativeMethods.sqlite3_column_database_name(stmt._sqlite_stmt, index), -1);
+#endif
+      }
+
+      internal virtual string ColumnTableName(SQLiteStatement stmt, int index)
+      {
+#if !SQLITE_STANDARD
+          int len;
+          return UTF8ToString(UnsafeNativeMethods.sqlite3_column_table_name_interop(stmt._sqlite_stmt, index, out len), len);
+#else
+      return UTF8ToString(UnsafeNativeMethods.sqlite3_column_table_name(stmt._sqlite_stmt, index), -1);
+#endif
+      }
+
+      internal virtual void ColumnMetaData(string dataBase, string table, string column, out string dataType, out string collateSequence, out bool notNull, out bool primaryKey, out bool autoIncrement)
+      {
+          IntPtr dataTypePtr;
+          IntPtr collSeqPtr;
+          int nnotNull;
+          int nprimaryKey;
+          int nautoInc;
+          int n;
+          int dtLen;
+          int csLen;
+
+#if !SQLITE_STANDARD
+          n = UnsafeNativeMethods.sqlite3_table_column_metadata_interop(_sql, ToUTF8(dataBase), ToUTF8(table), ToUTF8(column), out dataTypePtr, out collSeqPtr, out nnotNull, out nprimaryKey, out nautoInc, out dtLen, out csLen);
+#else
+      dtLen = -1;
+      csLen = -1;
+
+      n = UnsafeNativeMethods.sqlite3_table_column_metadata(_sql, ToUTF8(dataBase), ToUTF8(table), ToUTF8(column), out dataTypePtr, out collSeqPtr, out nnotNull, out nprimaryKey, out nautoInc);
+#endif
+          if (n > 0) throw new SQLiteException(n, SQLiteLastError());
+
+          dataType = UTF8ToString(dataTypePtr, dtLen);
+          collateSequence = UTF8ToString(collSeqPtr, csLen);
+
+          notNull = (nnotNull == 1);
+          primaryKey = (nprimaryKey == 1);
+          autoIncrement = (nautoInc == 1);
+      }
+
+      internal virtual double GetDouble(SQLiteStatement stmt, int index)
+      {
+          double value;
+#if !PLATFORM_COMPACTFRAMEWORK
+          value = UnsafeNativeMethods.sqlite3_column_double(stmt._sqlite_stmt, index);
+#else
+      UnsafeNativeMethods.sqlite3_column_double_interop(stmt._sqlite_stmt, index, out value);
+#endif
+          return value;
+      }
+
+      internal virtual int GetInt32(SQLiteStatement stmt, int index)
+      {
+          return UnsafeNativeMethods.sqlite3_column_int(stmt._sqlite_stmt, index);
+      }
+
+      internal virtual long GetInt64(SQLiteStatement stmt, int index)
+      {
+          long value;
+#if !PLATFORM_COMPACTFRAMEWORK
+          value = UnsafeNativeMethods.sqlite3_column_int64(stmt._sqlite_stmt, index);
+#else
+      UnsafeNativeMethods.sqlite3_column_int64_interop(stmt._sqlite_stmt, index, out value);
+#endif
+          return value;
+      }
+
+      internal virtual string GetText(SQLiteStatement stmt, int index)
+      {
+#if !SQLITE_STANDARD
+          int len;
+          return UTF8ToString(UnsafeNativeMethods.sqlite3_column_text_interop(stmt._sqlite_stmt, index, out len), len);
+#else
+      return UTF8ToString(UnsafeNativeMethods.sqlite3_column_text(stmt._sqlite_stmt, index), -1);
+#endif
+      }
+
+      internal virtual DateTime GetDateTime(SQLiteStatement stmt, int index)
+      {
+#if !SQLITE_STANDARD
+          int len;
+          return ToDateTime(UnsafeNativeMethods.sqlite3_column_text_interop(stmt._sqlite_stmt, index, out len), len);
+#else
+      return ToDateTime(UnsafeNativeMethods.sqlite3_column_text(stmt._sqlite_stmt, index), -1);
+#endif
+      }
+
+      internal virtual long GetBytes(SQLiteStatement stmt, int index, int nDataOffset, byte[] bDest, int nStart, int nLength)
+      {
+          IntPtr ptr;
+          int nlen;
+          int nCopied = nLength;
+
+          nlen = UnsafeNativeMethods.sqlite3_column_bytes(stmt._sqlite_stmt, index);
+          ptr = UnsafeNativeMethods.sqlite3_column_blob(stmt._sqlite_stmt, index);
+
+          if (bDest == null) return nlen;
+
+          if (nCopied + nStart > bDest.Length) nCopied = bDest.Length - nStart;
+          if (nCopied + nDataOffset > nlen) nCopied = nlen - nDataOffset;
+
+          if (nCopied > 0)
+              Marshal.Copy((IntPtr)(ptr.ToInt64() + nDataOffset), bDest, nStart, nCopied);
+          else nCopied = 0;
+
+          return nCopied;
+      }
+
+      internal virtual long GetChars(SQLiteStatement stmt, int index, int nDataOffset, char[] bDest, int nStart, int nLength)
+      {
+          int nlen;
+          int nCopied = nLength;
+
+          string str = GetText(stmt, index);
+          nlen = str.Length;
+
+          if (bDest == null) return nlen;
+
+          if (nCopied + nStart > bDest.Length) nCopied = bDest.Length - nStart;
+          if (nCopied + nDataOffset > nlen) nCopied = nlen - nDataOffset;
+
+          if (nCopied > 0)
+              str.CopyTo(nDataOffset, bDest, nStart, nCopied);
+          else nCopied = 0;
+
+          return nCopied;
+      }
+
+      internal virtual bool IsNull(SQLiteStatement stmt, int index)
+      {
+          return (ColumnAffinity(stmt, index) == TypeAffinity.Null);
+      }
+
+      internal virtual int AggregateCount(IntPtr context)
+      {
+          return UnsafeNativeMethods.sqlite3_aggregate_count(context);
+      }
+
+      internal virtual void CreateFunction(string strFunction, int nArgs, bool needCollSeq, SQLiteCallback func, SQLiteCallback funcstep, SQLiteFinalCallback funcfinal)
+      {
+          int n;
+
+#if !SQLITE_STANDARD
+          n = UnsafeNativeMethods.sqlite3_create_function_interop(_sql, ToUTF8(strFunction), nArgs, 4, IntPtr.Zero, func, funcstep, funcfinal, (needCollSeq == true) ? 1 : 0);
+          if (n == 0) n = UnsafeNativeMethods.sqlite3_create_function_interop(_sql, ToUTF8(strFunction), nArgs, 1, IntPtr.Zero, func, funcstep, funcfinal, (needCollSeq == true) ? 1 : 0);
+#else
+      n = UnsafeNativeMethods.sqlite3_create_function(_sql, ToUTF8(strFunction), nArgs, 4, IntPtr.Zero, func, funcstep, funcfinal);
+      if (n == 0) n = UnsafeNativeMethods.sqlite3_create_function(_sql, ToUTF8(strFunction), nArgs, 1, IntPtr.Zero, func, funcstep, funcfinal);
+#endif
+          if (n > 0) throw new SQLiteException(n, SQLiteLastError());
+      }
+
+      internal virtual void CreateCollation(string strCollation, SQLiteCollation func, SQLiteCollation func16)
+      {
+          int n = UnsafeNativeMethods.sqlite3_create_collation(_sql, ToUTF8(strCollation), 2, IntPtr.Zero, func16);
+          if (n == 0) n = UnsafeNativeMethods.sqlite3_create_collation(_sql, ToUTF8(strCollation), 1, IntPtr.Zero, func);
+          if (n > 0) throw new SQLiteException(n, SQLiteLastError());
+      }
+
+      internal virtual int ContextCollateCompare(CollationEncodingEnum enc, IntPtr context, string s1, string s2)
+      {
+#if !SQLITE_STANDARD
+          byte[] b1;
+          byte[] b2;
+          System.Text.Encoding converter = null;
+
+          switch (enc)
+          {
+              case CollationEncodingEnum.UTF8:
+                  converter = System.Text.Encoding.UTF8;
+                  break;
+              case CollationEncodingEnum.UTF16LE:
+                  converter = System.Text.Encoding.Unicode;
+                  break;
+              case CollationEncodingEnum.UTF16BE:
+                  converter = System.Text.Encoding.BigEndianUnicode;
+                  break;
+          }
+
+          b1 = converter.GetBytes(s1);
+          b2 = converter.GetBytes(s2);
+
+          return UnsafeNativeMethods.sqlite3_context_collcompare(context, b1, b1.Length, b2, b2.Length);
+#else
+      throw new NotImplementedException();
+#endif
+      }
+
+      internal virtual int ContextCollateCompare(CollationEncodingEnum enc, IntPtr context, char[] c1, char[] c2)
+      {
+#if !SQLITE_STANDARD
+          byte[] b1;
+          byte[] b2;
+          System.Text.Encoding converter = null;
+
+          switch (enc)
+          {
+              case CollationEncodingEnum.UTF8:
+                  converter = System.Text.Encoding.UTF8;
+                  break;
+              case CollationEncodingEnum.UTF16LE:
+                  converter = System.Text.Encoding.Unicode;
+                  break;
+              case CollationEncodingEnum.UTF16BE:
+                  converter = System.Text.Encoding.BigEndianUnicode;
+                  break;
+          }
+
+          b1 = converter.GetBytes(c1);
+          b2 = converter.GetBytes(c2);
+
+          return UnsafeNativeMethods.sqlite3_context_collcompare(context, b1, b1.Length, b2, b2.Length);
+#else
+      throw new NotImplementedException();
+#endif
+      }
+
+      internal virtual CollationSequence GetCollationSequence(SQLiteFunction func, IntPtr context)
+      {
+#if !SQLITE_STANDARD
+          CollationSequence seq = new CollationSequence();
+          int len;
+          int type;
+          int enc;
+          IntPtr p = UnsafeNativeMethods.sqlite3_context_collseq(context, out type, out enc, out len);
+
+          if (p != null) seq.Name = UTF8ToString(p, len);
+          seq.Type = (CollationTypeEnum)type;
+          seq._func = func;
+          seq.Encoding = (CollationEncodingEnum)enc;
+
+          return seq;
+#else
+      throw new NotImplementedException();
+#endif
+      }
+
+      internal virtual long GetParamValueBytes(IntPtr p, int nDataOffset, byte[] bDest, int nStart, int nLength)
+      {
+          IntPtr ptr;
+          int nlen;
+          int nCopied = nLength;
+
+          nlen = UnsafeNativeMethods.sqlite3_value_bytes(p);
+          ptr = UnsafeNativeMethods.sqlite3_value_blob(p);
+
+          if (bDest == null) return nlen;
+
+          if (nCopied + nStart > bDest.Length) nCopied = bDest.Length - nStart;
+          if (nCopied + nDataOffset > nlen) nCopied = nlen - nDataOffset;
+
+          if (nCopied > 0)
+              Marshal.Copy((IntPtr)(ptr.ToInt32() + nDataOffset), bDest, nStart, nCopied);
+          else nCopied = 0;
+
+          return nCopied;
+      }
+
+      internal virtual double GetParamValueDouble(IntPtr ptr)
+      {
+          double value;
+#if !PLATFORM_COMPACTFRAMEWORK
+          value = UnsafeNativeMethods.sqlite3_value_double(ptr);
+#else
+      UnsafeNativeMethods.sqlite3_value_double_interop(ptr, out value);
+#endif
+          return value;
+      }
+
+      internal virtual int GetParamValueInt32(IntPtr ptr)
+      {
+          return UnsafeNativeMethods.sqlite3_value_int(ptr);
+      }
+
+      internal virtual long GetParamValueInt64(IntPtr ptr)
+      {
+          Int64 value;
+#if !PLATFORM_COMPACTFRAMEWORK
+          value = UnsafeNativeMethods.sqlite3_value_int64(ptr);
+#else
+      UnsafeNativeMethods.sqlite3_value_int64_interop(ptr, out value);
+#endif
+          return value;
+      }
+
+      internal virtual string GetParamValueText(IntPtr ptr)
+      {
+#if !SQLITE_STANDARD
+          int len;
+          return UTF8ToString(UnsafeNativeMethods.sqlite3_value_text_interop(ptr, out len), len);
+#else
+      return UTF8ToString(UnsafeNativeMethods.sqlite3_value_text(ptr), -1);
+#endif
+      }
+
+      internal virtual TypeAffinity GetParamValueType(IntPtr ptr)
+      {
+          return UnsafeNativeMethods.sqlite3_value_type(ptr);
+      }
+
+      internal virtual void ReturnBlob(IntPtr context, byte[] value)
+      {
+          UnsafeNativeMethods.sqlite3_result_blob(context, value, value.Length, (IntPtr)(-1));
+      }
+
+      internal virtual void ReturnDouble(IntPtr context, double value)
+      {
+#if !PLATFORM_COMPACTFRAMEWORK
+          UnsafeNativeMethods.sqlite3_result_double(context, value);
+#else
+      UnsafeNativeMethods.sqlite3_result_double_interop(context, ref value);
+#endif
+      }
+
+      internal virtual void ReturnError(IntPtr context, string value)
+      {
+          UnsafeNativeMethods.sqlite3_result_error(context, ToUTF8(value), value.Length);
+      }
+
+      internal virtual void ReturnInt32(IntPtr context, int value)
+      {
+          UnsafeNativeMethods.sqlite3_result_int(context, value);
+      }
+
+      internal virtual void ReturnInt64(IntPtr context, long value)
+      {
+#if !PLATFORM_COMPACTFRAMEWORK
+          UnsafeNativeMethods.sqlite3_result_int64(context, value);
+#else
+      UnsafeNativeMethods.sqlite3_result_int64_interop(context, ref value);
+#endif
+      }
+
+      internal virtual void ReturnNull(IntPtr context)
+      {
+          UnsafeNativeMethods.sqlite3_result_null(context);
+      }
+
+      internal virtual void ReturnText(IntPtr context, string value)
+      {
+          byte[] b = ToUTF8(value);
+          UnsafeNativeMethods.sqlite3_result_text(context, ToUTF8(value), b.Length - 1, (IntPtr)(-1));
+      }
+
+      internal virtual IntPtr AggregateContext(IntPtr context)
+      {
+          return UnsafeNativeMethods.sqlite3_aggregate_context(context, 1);
+      }
+
+      internal virtual void SetPassword(byte[] passwordBytes)
+      {
+          int n = UnsafeNativeMethods.sqlite3_key(_sql, passwordBytes, passwordBytes.Length);
+          if (n > 0) throw new SQLiteException(n, SQLiteLastError());
+      }
+
+      internal virtual void ChangePassword(byte[] newPasswordBytes)
+      {
+          int n = UnsafeNativeMethods.sqlite3_rekey(_sql, newPasswordBytes, (newPasswordBytes == null) ? 0 : newPasswordBytes.Length);
+          if (n > 0) throw new SQLiteException(n, SQLiteLastError());
+      }
+
+      internal virtual void SetUpdateHook(SQLiteUpdateCallback func)
+      {
+          UnsafeNativeMethods.sqlite3_update_hook(_sql, func, IntPtr.Zero);
+      }
+
+      internal virtual void SetCommitHook(SQLiteCommitCallback func)
+      {
+          UnsafeNativeMethods.sqlite3_commit_hook(_sql, func, IntPtr.Zero);
+      }
+
+      internal virtual void SetRollbackHook(SQLiteRollbackCallback func)
+      {
+          UnsafeNativeMethods.sqlite3_rollback_hook(_sql, func, IntPtr.Zero);
+      }
+
+      /// <summary>
+      /// Helper function to retrieve a column of data from an active statement.
+      /// </summary>
+      /// <param name="stmt">The statement being step()'d through</param>
+      /// <param name="index">The column index to retrieve</param>
+      /// <param name="typ">The type of data contained in the column.  If Uninitialized, this function will retrieve the datatype information.</param>
+      /// <returns>Returns the data in the column</returns>
+      internal virtual object GetValue(SQLiteStatement stmt, int index, SQLiteType typ)
+      {
+          if (IsNull(stmt, index)) return DBNull.Value;
+          TypeAffinity aff = typ.Affinity;
+          Type t = null;
+
+          if (typ.Type != DbType.Object)
+          {
+              t = SQLiteConvert.SQLiteTypeToType(typ);
+              aff = TypeToAffinity(t);
+          }
+
+          switch (aff)
+          {
+              case TypeAffinity.Blob:
+                  if (typ.Type == DbType.Guid && typ.Affinity == TypeAffinity.Text)
+                      return new Guid(GetText(stmt, index));
+
+                  int n = (int)GetBytes(stmt, index, 0, null, 0, 0);
+                  byte[] b = new byte[n];
+                  GetBytes(stmt, index, 0, b, 0, n);
+
+                  if (typ.Type == DbType.Guid && n == 16)
+                      return new Guid(b);
+
+                  return b;
+              case TypeAffinity.DateTime:
+                  return GetDateTime(stmt, index);
+              case TypeAffinity.Double:
+                  if (t == null) return GetDouble(stmt, index);
+                  else
+                      return Convert.ChangeType(GetDouble(stmt, index), t, null);
+              case TypeAffinity.Int64:
+                  if (t == null) return GetInt64(stmt, index);
+                  else
+                      return Convert.ChangeType(GetInt64(stmt, index), t, null);
+              default:
+                  return GetText(stmt, index);
+          }
+      }
+
+      internal virtual int GetCursorForTable(SQLiteStatement stmt, int db, int rootPage)
+      {
+#if !SQLITE_STANDARD
+          return UnsafeNativeMethods.sqlite3_table_cursor(stmt._sqlite_stmt, db, rootPage);
+#else
+      return -1;
+#endif
+      }
+
+      internal virtual long GetRowIdForCursor(SQLiteStatement stmt, int cursor)
+      {
+#if !SQLITE_STANDARD
+          long rowid;
+          int rc = UnsafeNativeMethods.sqlite3_cursor_rowid(stmt._sqlite_stmt, cursor, out rowid);
+          if (rc == 0) return rowid;
+
+          return 0;
+#else
+      return 0;
+#endif
+      }
+
+      internal virtual void GetIndexColumnExtendedInfo(string database, string index, string column, out int sortMode, out int onError, out string collationSequence)
+      {
+#if !SQLITE_STANDARD
+          IntPtr coll;
+          int colllen;
+          int rc;
+
+          rc = UnsafeNativeMethods.sqlite3_index_column_info_interop(_sql, ToUTF8(database), ToUTF8(index), ToUTF8(column), out sortMode, out onError, out coll, out colllen);
+          if (rc != 0) throw new SQLiteException(rc, "");
+
+          collationSequence = UTF8ToString(coll, colllen);
+#else
+      sortMode = 0;
+      onError = 2;
+      collationSequence = "BINARY";
+#endif
+      }
   }
 
   internal interface ISQLiteSchemaExtensions