[hyena/sqlite: 1/2] [Hyena.Data.Sqlite] New light-weight sqlite binding



commit 904c89346a072d15dde3326f338f07c9708bc06b
Author: Gabriel Burt <gabriel burt gmail com>
Date:   Tue Nov 9 22:25:03 2010 -0600

    [Hyena.Data.Sqlite] New light-weight sqlite binding
    
    This commit gets rid of our use of Mono.Data.Sqlite and System.Data,
    replacing them with a custom, light-weight sqlite binding.

 .../Hyena.Data.Sqlite/DatabaseColumn.cs            |    4 +-
 .../Hyena.Data.Sqlite/DatabaseColumnAttribute.cs   |    1 -
 .../HyenaSqliteArrayDataReader.cs                  |    4 +-
 .../Hyena.Data.Sqlite/HyenaSqliteCommand.cs        |   84 ++--
 .../Hyena.Data.Sqlite/HyenaSqliteConnection.cs     |   41 +-
 .../Hyena.Data.Sqlite/ICacheableDatabaseModel.cs   |    2 -
 Hyena.Data.Sqlite/Hyena.Data.Sqlite/Sqlite.cs      |  481 ++++++++++++++++++++
 .../Hyena.Data.Sqlite/SqliteFunction.cs            |  393 ++++++++++++++++
 .../Hyena.Data.Sqlite/SqliteFunctionAttribute.cs   |   81 ++++
 .../Hyena.Data.Sqlite/SqliteModelCache.cs          |    1 -
 .../Hyena.Data.Sqlite/SqliteModelProvider.cs       |    7 +-
 Hyena.Data.Sqlite/Hyena.Data.Sqlite/SqliteUtils.cs |   15 +-
 .../Tests/SqliteModelProviderTests.cs              |    5 +-
 .../Hyena.Data.Sqlite/Tests/SqliteTests.cs         |  218 +++++++++
 Hyena.Data.Sqlite/Makefile.am                      |   10 +-
 Makefile.am                                        |    1 -
 16 files changed, 1268 insertions(+), 80 deletions(-)
---
diff --git a/Hyena.Data.Sqlite/Hyena.Data.Sqlite/DatabaseColumn.cs b/Hyena.Data.Sqlite/Hyena.Data.Sqlite/DatabaseColumn.cs
index e37bbbc..e4461e9 100644
--- a/Hyena.Data.Sqlite/Hyena.Data.Sqlite/DatabaseColumn.cs
+++ b/Hyena.Data.Sqlite/Hyena.Data.Sqlite/DatabaseColumn.cs
@@ -27,7 +27,6 @@
 //
 
 using System;
-using System.Data;
 using System.Reflection;
 using System.Text;
 
@@ -86,10 +85,9 @@ namespace Hyena.Data.Sqlite
             return SqliteUtils.ToDbFormat (type, result);
         }
 
-        public void SetValue (object target, IDataReader reader, int column)
+        public void SetFromDbValue (object target, object value)
         {
             // FIXME should we insist on nullable types?
-            object value = reader.IsDBNull (column) ? null : reader.GetValue (column);
             SetValue (target, SqliteUtils.FromDbFormat(type, value));
         }
 
diff --git a/Hyena.Data.Sqlite/Hyena.Data.Sqlite/DatabaseColumnAttribute.cs b/Hyena.Data.Sqlite/Hyena.Data.Sqlite/DatabaseColumnAttribute.cs
index dc022ed..adaf854 100644
--- a/Hyena.Data.Sqlite/Hyena.Data.Sqlite/DatabaseColumnAttribute.cs
+++ b/Hyena.Data.Sqlite/Hyena.Data.Sqlite/DatabaseColumnAttribute.cs
@@ -28,7 +28,6 @@
 
 using System;
 using System.Collections.Generic;
-using System.Data;
 using System.Reflection;
 using System.Text;
 
diff --git a/Hyena.Data.Sqlite/Hyena.Data.Sqlite/HyenaSqliteArrayDataReader.cs b/Hyena.Data.Sqlite/Hyena.Data.Sqlite/HyenaSqliteArrayDataReader.cs
index 7653995..d310ef2 100644
--- a/Hyena.Data.Sqlite/Hyena.Data.Sqlite/HyenaSqliteArrayDataReader.cs
+++ b/Hyena.Data.Sqlite/Hyena.Data.Sqlite/HyenaSqliteArrayDataReader.cs
@@ -30,12 +30,12 @@
 //
 
 using System;
-using System.Data;
+
 using System.Data.Common;
 using System.Text;
 using System.Collections;
 using System.Collections.Generic;
-using Mono.Data.Sqlite;
+
 
 namespace Hyena.Data.Sqlite
 {
diff --git a/Hyena.Data.Sqlite/Hyena.Data.Sqlite/HyenaSqliteCommand.cs b/Hyena.Data.Sqlite/Hyena.Data.Sqlite/HyenaSqliteCommand.cs
index a550d0a..44ffbdb 100644
--- a/Hyena.Data.Sqlite/Hyena.Data.Sqlite/HyenaSqliteCommand.cs
+++ b/Hyena.Data.Sqlite/Hyena.Data.Sqlite/HyenaSqliteCommand.cs
@@ -29,10 +29,8 @@
 
 using System;
 using System.IO;
-using System.Data;
 using System.Text;
 using System.Threading;
-using Mono.Data.Sqlite;
 
 namespace Hyena.Data.Sqlite
 {
@@ -62,10 +60,14 @@ namespace Hyena.Data.Sqlite
         private object [] current_values;
         private int ticks;
 
+        private Thread last_thread;
+
         public string Text {
             get { return command; }
         }
 
+        public bool ReaderDisposes { get; set; }
+
         internal HyenaCommandType CommandType;
 
         public HyenaSqliteCommand (string command)
@@ -79,7 +81,7 @@ namespace Hyena.Data.Sqlite
             ApplyValues (param_values);
         }
 
-        internal void Execute (HyenaSqliteConnection hconnection, SqliteConnection connection)
+        internal void Execute (HyenaSqliteConnection hconnection, Connection connection)
         {
             if (finished) {
                 throw new Exception ("Command is already set to finished; result needs to be claimed before command can be rerun");
@@ -89,39 +91,44 @@ namespace Hyena.Data.Sqlite
             result = null;
             int execution_ms = 0;
 
-            using (SqliteCommand sql_command = new SqliteCommand (CurrentSqlText)) {
-                sql_command.Connection = connection;
-
-                hconnection.OnExecuting (sql_command);
-
-                try {
-                    ticks = System.Environment.TickCount;
-
-                    switch (CommandType) {
-                        case HyenaCommandType.Reader:
-                            using (SqliteDataReader reader = sql_command.ExecuteReader ()) {
-                                result = new HyenaSqliteArrayDataReader (reader);
-                            }
-                            break;
-
-                        case HyenaCommandType.Scalar:
-                            result = sql_command.ExecuteScalar ();
-                            break;
-
-                        case HyenaCommandType.Execute:
-                        default:
-                            sql_command.ExecuteNonQuery ();
-                            result = sql_command.LastInsertRowID ();
-                            break;
-                    }
-
-                    execution_ms = System.Environment.TickCount - ticks;
-                    if (log_all) {
-                        Log.DebugFormat ("Executed in {0}ms {1}", execution_ms, sql_command.CommandText);
-                    }
-                } catch (Exception e) {
-                    Log.DebugFormat ("Exception executing command: {0}", sql_command.CommandText);
-                    execution_exception = e;
+            bool dispose_command = true;
+            var sql_command = connection.CreateStatement (CurrentSqlText);
+            sql_command.ReaderDisposes = ReaderDisposes;
+            hconnection.OnExecuting (sql_command);
+
+            try {
+                ticks = System.Environment.TickCount;
+
+                switch (CommandType) {
+                    case HyenaCommandType.Reader:
+                        result = sql_command.Query ();
+                        dispose_command = false;
+                        /*using (SqliteDataReader reader = sql_command.ExecuteReader ()) {
+                            result = new HyenaSqliteArrayDataReader (reader);
+                        }*/
+                        break;
+
+                    case HyenaCommandType.Scalar:
+                        result = sql_command.QueryScalar ();
+                        break;
+
+                    case HyenaCommandType.Execute:
+                    default:
+                        sql_command.Execute ();
+                        result = connection.LastInsertRowId;
+                        break;
+                }
+
+                execution_ms = System.Environment.TickCount - ticks;
+                if (log_all) {
+                    Log.DebugFormat ("Executed in {0}ms {1}", execution_ms, sql_command.CommandText);
+                }
+            } catch (Exception e) {
+                Log.DebugFormat ("Exception executing command: {0}", sql_command.CommandText);
+                execution_exception = e;
+            } finally {
+                if (dispose_command) {
+                    sql_command.Dispose ();
                 }
             }
 
@@ -148,6 +155,11 @@ namespace Hyena.Data.Sqlite
 
         internal object WaitForResult (HyenaSqliteConnection conn)
         {
+            if (last_thread != null && last_thread != Thread.CurrentThread) {
+                Log.WarningFormat ("Calling HyenaSqliteCommand from different thread ({0}) than last time it was ran ({1})\n  sql: {2}", Thread.CurrentThread.Name, last_thread.Name, Text);
+            }
+            last_thread = Thread.CurrentThread;
+
             while (!finished) {
                 conn.ResultReadySignal.WaitOne ();
             }
diff --git a/Hyena.Data.Sqlite/Hyena.Data.Sqlite/HyenaSqliteConnection.cs b/Hyena.Data.Sqlite/Hyena.Data.Sqlite/HyenaSqliteConnection.cs
index eaba2d8..f366baa 100644
--- a/Hyena.Data.Sqlite/Hyena.Data.Sqlite/HyenaSqliteConnection.cs
+++ b/Hyena.Data.Sqlite/Hyena.Data.Sqlite/HyenaSqliteConnection.cs
@@ -28,10 +28,8 @@
 //
 
 using System;
-using System.Data;
 using System.Threading;
 using System.Collections.Generic;
-using Mono.Data.Sqlite;
 
 namespace Hyena.Data.Sqlite
 {
@@ -70,10 +68,10 @@ namespace Hyena.Data.Sqlite
         }
     }
 
-    public class ExecutingEventArgs : EventArgs
+    internal class ExecutingEventArgs : EventArgs
     {
-        public readonly SqliteCommand Command;
-        public ExecutingEventArgs (SqliteCommand command)
+        public readonly Statement Command;
+        public ExecutingEventArgs (Statement command)
         {
             Command = command;
         }
@@ -87,7 +85,7 @@ namespace Hyena.Data.Sqlite
 
     public class HyenaSqliteConnection : IDisposable
     {
-        private SqliteConnection connection;
+        private Hyena.Data.Sqlite.Connection connection;
         private string dbpath;
 
         protected string DbPath { get { return dbpath; } }
@@ -116,9 +114,9 @@ namespace Hyena.Data.Sqlite
             set { warn_if_called_from_thread = value; }
         }
 
-        public string ServerVersion { get { return connection.ServerVersion; } }
+        public string ServerVersion { get { return Query<string> ("SELECT sqlite_version ()"); } }
 
-        public event EventHandler<ExecutingEventArgs> Executing;
+        internal event EventHandler<ExecutingEventArgs> Executing;
 
         public HyenaSqliteConnection(string dbpath)
         {
@@ -138,24 +136,24 @@ namespace Hyena.Data.Sqlite
         {
             command.CommandType = HyenaCommandType.Reader;
             QueueCommand (command);
-            return command.WaitForResult (this) as IDataReader;
+            return (IDataReader) command.WaitForResult (this);
         }
 
         public IDataReader Query (HyenaSqliteCommand command, params object [] param_values)
         {
             command.CommandType = HyenaCommandType.Reader;
             QueueCommand (command, param_values);
-            return command.WaitForResult (this) as IDataReader;
+            return (IDataReader) command.WaitForResult (this);
         }
 
         public IDataReader Query (string command_str, params object [] param_values)
         {
-            return Query (new HyenaSqliteCommand (command_str, param_values));
+            return Query (new HyenaSqliteCommand (command_str, param_values) { ReaderDisposes = true });
         }
 
         public IDataReader Query (object command)
         {
-            return Query (new HyenaSqliteCommand (command.ToString ()));
+            return Query (new HyenaSqliteCommand (command.ToString ()) { ReaderDisposes = true });
         }
 
         // SELECT single column, multiple rows queries
@@ -181,12 +179,12 @@ namespace Hyena.Data.Sqlite
 
         public IEnumerable<T> QueryEnumerable<T> (string command_str, params object [] param_values)
         {
-            return QueryEnumerable<T> (new HyenaSqliteCommand (command_str, param_values));
+            return QueryEnumerable<T> (new HyenaSqliteCommand (command_str, param_values) { ReaderDisposes = true });
         }
 
         public IEnumerable<T> QueryEnumerable<T> (object command)
         {
-            return QueryEnumerable<T> (new HyenaSqliteCommand (command.ToString ()));
+            return QueryEnumerable<T> (new HyenaSqliteCommand (command.ToString ()) { ReaderDisposes = true });
         }
 
         // SELECT single column, single row queries
@@ -208,12 +206,12 @@ namespace Hyena.Data.Sqlite
 
         public T Query<T> (string command_str, params object [] param_values)
         {
-            return Query<T> (new HyenaSqliteCommand (command_str, param_values));
+            return Query<T> (new HyenaSqliteCommand (command_str, param_values) { ReaderDisposes = true });
         }
 
         public T Query<T> (object command)
         {
-            return Query<T> (new HyenaSqliteCommand (command.ToString ()));
+            return Query<T> (new HyenaSqliteCommand (command.ToString ()) { ReaderDisposes = true });
         }
 
         // INSERT, UPDATE, DELETE queries
@@ -233,12 +231,12 @@ namespace Hyena.Data.Sqlite
 
         public int Execute (string command_str, params object [] param_values)
         {
-            return Execute (new HyenaSqliteCommand (command_str, param_values));
+            return Execute (new HyenaSqliteCommand (command_str, param_values) { ReaderDisposes = true });
         }
 
         public int Execute (object command)
         {
-            return Execute (new HyenaSqliteCommand (command.ToString ()));
+            return Execute (new HyenaSqliteCommand (command.ToString ()) { ReaderDisposes = true });
         }
 
 #endregion
@@ -419,8 +417,7 @@ namespace Hyena.Data.Sqlite
         private void ProcessQueue()
         {
             if (connection == null) {
-                connection = new SqliteConnection (String.Format ("Version=3,URI=file:{0}", dbpath));
-                connection.Open ();
+                connection = new Hyena.Data.Sqlite.Connection (dbpath);
             }
 
             // Keep handling queries
@@ -458,10 +455,10 @@ namespace Hyena.Data.Sqlite
             }
 
             // Finish
-            connection.Close ();
+            connection.Dispose ();
         }
 
-        internal void OnExecuting (SqliteCommand command)
+        internal void OnExecuting (Statement command)
         {
             EventHandler<ExecutingEventArgs> handler = Executing;
             if (handler != null) {
diff --git a/Hyena.Data.Sqlite/Hyena.Data.Sqlite/ICacheableDatabaseModel.cs b/Hyena.Data.Sqlite/Hyena.Data.Sqlite/ICacheableDatabaseModel.cs
index c0768ee..7278033 100644
--- a/Hyena.Data.Sqlite/Hyena.Data.Sqlite/ICacheableDatabaseModel.cs
+++ b/Hyena.Data.Sqlite/Hyena.Data.Sqlite/ICacheableDatabaseModel.cs
@@ -26,8 +26,6 @@
 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 //
 
-using System.Data;
-
 using Hyena.Data;
 
 namespace Hyena.Data.Sqlite
diff --git a/Hyena.Data.Sqlite/Hyena.Data.Sqlite/Sqlite.cs b/Hyena.Data.Sqlite/Hyena.Data.Sqlite/Sqlite.cs
new file mode 100644
index 0000000..da0e920
--- /dev/null
+++ b/Hyena.Data.Sqlite/Hyena.Data.Sqlite/Sqlite.cs
@@ -0,0 +1,481 @@
+using System;
+using System.Linq;
+using System.Text;
+using System.Collections;
+using System.Collections.Generic;
+using System.Runtime.InteropServices;
+
+namespace Hyena.Data.Sqlite
+{
+    public class Connection : IDisposable
+    {
+        IntPtr ptr;
+        internal IntPtr Ptr { get { return ptr; } }
+
+        public string DbPath { get; private set; }
+
+        public long LastInsertRowId {
+            get { return Native.sqlite3_last_insert_rowid (Ptr); }
+        }
+
+        public Connection (string dbPath)
+        {
+            DbPath = dbPath;
+            CheckError (Native.sqlite3_open (Encoding.UTF8.GetBytes (dbPath), out ptr));
+            if (ptr == IntPtr.Zero)
+                throw new Exception ("Unable to open connection");
+        }
+
+        public void Dispose ()
+        {
+            if (ptr != IntPtr.Zero) {
+                CheckError (Native.sqlite3_close (ptr));
+                ptr = IntPtr.Zero;
+            }
+        }
+
+        ~Connection ()
+        {
+            Dispose ();
+        }
+
+        internal void CheckError (int errorCode)
+        {
+            CheckError (errorCode, "");
+        }
+
+        internal void CheckError (int errorCode, string msg)
+        {
+            if (errorCode == 0 || errorCode == 100 || errorCode == 101)
+                return;
+
+            throw new Exception (msg + Native.sqlite3_errmsg16 (Ptr).PtrToString ());
+        }
+
+        public Statement CreateStatement (string sql)
+        {
+            return new Statement (this, sql);
+        }
+
+        public QueryReader Query (string sql)
+        {
+            return new Statement (this, sql) { ReaderDisposes = true }.Query ();
+        }
+
+        public object QueryScalar (string sql)
+        {
+            using (var stmt = new Statement (this, sql)) {
+                return stmt.QueryScalar ();
+            }
+        }
+
+        public void Execute (string sql)
+        {
+            using (var stmt = new Statement (this, sql)) {
+                stmt.Execute ();
+            }
+        }
+
+        const int UTF16 = 4;
+        public void AddFunction<T> () where T : SqliteFunction
+        {
+            var type = typeof (T);
+            var pr = (SqliteFunctionAttribute)type.GetCustomAttributes (typeof (SqliteFunctionAttribute), false).First ();
+            var f = (SqliteFunction) Activator.CreateInstance (typeof (T));
+
+            f._InvokeFunc = (pr.FuncType == FunctionType.Scalar) ? new SqliteCallback(f.ScalarCallback) : null;
+            f._StepFunc = (pr.FuncType == FunctionType.Aggregate) ? new SqliteCallback(f.StepCallback) : null;
+            f._FinalFunc = (pr.FuncType == FunctionType.Aggregate) ? new SqliteFinalCallback(f.FinalCallback) : null;
+            f._CompareFunc = (pr.FuncType == FunctionType.Collation) ? new SqliteCollation(f.CompareCallback) : null;
+
+            if (pr.FuncType != FunctionType.Collation) {
+                CheckError (Native.sqlite3_create_function16 (
+                    ptr, pr.Name, pr.Arguments, UTF16, IntPtr.Zero,
+                    f._InvokeFunc, f._StepFunc, f._FinalFunc
+                ));
+            } else {
+                CheckError (Native.sqlite3_create_collation16 (
+                    ptr, pr.Name, UTF16, IntPtr.Zero, f._CompareFunc
+                ));
+            }
+        }
+
+        public void RemoveFunction<T> () where T : SqliteFunction
+        {
+            var type = typeof (T);
+            var pr = (SqliteFunctionAttribute)type.GetCustomAttributes (typeof (SqliteFunctionAttribute), false).First ();
+            if (pr.FuncType != FunctionType.Collation) {
+                CheckError (Native.sqlite3_create_function16 (
+                    ptr, pr.Name, pr.Arguments, UTF16, IntPtr.Zero,
+                    null, null, null
+                ));
+            } else {
+                CheckError (Native.sqlite3_create_collation16 (
+                    ptr, pr.Name, UTF16, IntPtr.Zero, null
+                ));
+            }
+        }
+    }
+
+    public interface IDataReader : IDisposable
+    {
+        bool Read ();
+        object this[int i] { get; }
+        object this[string columnName] { get; }
+        int FieldCount { get; }
+    }
+
+    public class Statement : IDisposable, IEnumerable<IDataReader>
+    {
+        IntPtr ptr;
+        Connection connection;
+        bool bound;
+        QueryReader reader;
+
+        internal IntPtr Ptr { get { return ptr; } }
+        internal bool Bound { get { return bound; } }
+        internal Connection Connection { get { return connection; } }
+
+        public string CommandText { get; private set; }
+        public int ParameterCount { get; private set; }
+        public bool ReaderDisposes { get; set; }
+
+        internal event EventHandler Disposed;
+
+        internal Statement (Connection connection, string sql)
+        {
+            CommandText = sql;
+            this.connection = connection;
+
+            IntPtr pzTail = IntPtr.Zero;
+            connection.CheckError (Native.sqlite3_prepare16_v2 (connection.Ptr, sql, sql.Length * 2, out ptr, out pzTail), sql);
+            if (pzTail != IntPtr.Zero && Marshal.ReadByte (pzTail) != 0) {
+                Dispose ();
+                throw new ArgumentException ("sql", String.Format ("This sqlite binding does not support multiple commands in one statement:\n  {0}", sql));
+            }
+
+            ParameterCount = Native.sqlite3_bind_parameter_count (ptr);
+            reader = new QueryReader () { Statement = this };
+        }
+
+        public void Dispose ()
+        {
+            if (ptr != IntPtr.Zero) {
+                connection.CheckError (Native.sqlite3_finalize (ptr));
+                ptr = IntPtr.Zero;
+
+                var h = Disposed;
+                if (h != null) {
+                    h (this, EventArgs.Empty);
+                }
+            }
+        }
+
+        ~Statement ()
+        {
+            Dispose ();
+        }
+
+        public Statement Bind (params object [] vals)
+        {
+            if (vals == null || vals.Length != ParameterCount || ParameterCount == 0)
+                throw new ArgumentException ("vals", String.Format ("Statement has {0} parameters", ParameterCount));
+
+            connection.CheckError (Native.sqlite3_reset (ptr));
+
+            for (int i = 1; i <= vals.Length; i++) {
+                int code = 0;
+                object o = SqliteUtils.ToDbFormat (vals[i - 1]);
+
+                if (o == null)
+                    code = Native.sqlite3_bind_null (Ptr, i);
+                else if (o is double || o is float)
+                    code = Native.sqlite3_bind_double (Ptr, i, (double)o);
+                else if (o is int || o is uint)
+                    code = Native.sqlite3_bind_int (Ptr, i, (int)o);
+                else if (o is long || o is ulong)
+                    code = Native.sqlite3_bind_int64 (Ptr, i, (long)o);
+                else if (o is byte[]) {
+                    byte [] bytes = o as byte[];
+                    code = Native.sqlite3_bind_blob (Ptr, i, bytes, bytes.Length, (IntPtr)(-1));
+                } else {
+                    // C# strings are UTF-16, so 2 bytes per char
+                    // -1 for the last arg is the TRANSIENT destructor type so that sqlite will make its own copy of the string
+                    string str = o.ToString ();
+                    code = Native.sqlite3_bind_text16 (Ptr, i, str, str.Length * 2, (IntPtr)(-1));
+                }
+
+                connection.CheckError (code);
+            }
+
+            bound = true;
+            return this;
+        }
+
+        public IEnumerator<IDataReader> GetEnumerator ()
+        {
+            connection.CheckError (Native.sqlite3_reset (ptr));
+            while (reader.Read ()) {
+                yield return reader;
+            }
+        }
+
+        IEnumerator IEnumerable.GetEnumerator ()
+        {
+            return GetEnumerator ();
+        }
+
+        public Statement Execute ()
+        {
+            reader.Read ();
+            return this;
+        }
+
+        public object QueryScalar ()
+        {
+            return reader.Read () ? reader[0] : null;
+        }
+
+        public QueryReader Query ()
+        {
+            return reader;
+        }
+    }
+
+    public class QueryReader : IDisposable, IDataReader
+    {
+        Dictionary<string, int> columns;
+        int column_count = -1;
+
+        internal Statement Statement { get; set; }
+        IntPtr Ptr { get { return Statement.Ptr; } }
+
+        public void Dispose ()
+        {
+            if (Statement.ReaderDisposes) {
+                Statement.Dispose ();
+            }
+        }
+
+        public int FieldCount {
+            get {
+                if (column_count == -1) {
+                    column_count = Native.sqlite3_column_count (Ptr);
+                }
+                return column_count;
+            }
+        }
+
+        public bool Read ()
+        {
+            if (Statement.ParameterCount > 0 && !Statement.Bound)
+                throw new InvalidOperationException ("Statement not bound");
+
+            int code = Native.sqlite3_step (Ptr);
+            if (code == ROW) {
+                return true;
+            } else {
+                Statement.Connection.CheckError (code);
+                return false;
+            }
+        }
+
+        public object this[int i] {
+            get {
+                int type = Native.sqlite3_column_type (Ptr, i);
+                switch (type) {
+                    case SQLITE_INTEGER:
+                        return Native.sqlite3_column_int64 (Ptr, i);
+                    case SQLITE_FLOAT:
+                        return Native.sqlite3_column_double (Ptr, i);
+                    case SQLITE3_TEXT:
+                        return Native.sqlite3_column_text16 (Ptr, i).PtrToString ();
+                    case SQLITE_BLOB:
+                        return Native.sqlite3_column_blob (Ptr, i);
+                    case SQLITE_NULL:
+                        return null;
+                    default:
+                        throw new Exception (String.Format ("Column is of unknown type {0}", type));
+                }
+            }
+        }
+
+        public object this[string columnName] {
+            get {
+                if (columns == null) {
+                    columns = new Dictionary<string, int> ();
+                    for (int i = 0; i < FieldCount; i++) {
+                        columns[Native.sqlite3_column_name16 (Ptr, i).PtrToString ()] = i;
+                    }
+                }
+
+                int col = 0;
+                if (!columns.TryGetValue (columnName, out col))
+                    throw new ArgumentException ("columnName");
+
+                return this[col];
+            }
+        }
+
+        const int SQLITE_INTEGER = 1;
+        const int SQLITE_FLOAT   = 2;
+        const int SQLITE3_TEXT   = 3;
+        const int SQLITE_BLOB    = 4;
+        const int SQLITE_NULL    = 5;
+
+        const int ROW = 100;
+        const int DONE = 101;
+    }
+
+    internal static class Native
+    {
+        const string SQLITE_DLL = "sqlite3";
+
+        // Connection functions
+        [DllImport(SQLITE_DLL)]
+        internal static extern int sqlite3_open(byte [] utf8DbPath, out IntPtr db);
+
+        [DllImport(SQLITE_DLL)]
+        internal static extern int sqlite3_close(IntPtr db);
+
+        [DllImport(SQLITE_DLL)]
+        internal static extern long sqlite3_last_insert_rowid (IntPtr db);
+
+        [DllImport(SQLITE_DLL)]
+        internal static extern int sqlite3_busy_timeout(IntPtr db, int ms);
+
+        [DllImport(SQLITE_DLL, CharSet = CharSet.Unicode)]
+        internal static extern IntPtr sqlite3_errmsg16(IntPtr db);
+
+        [DllImport(SQLITE_DLL, CharSet = CharSet.Unicode)]
+        internal static extern int sqlite3_create_function16(IntPtr db, string strName, int nArgs, int eTextRep, IntPtr app, SqliteCallback func, SqliteCallback funcstep, SqliteFinalCallback funcfinal);
+
+        [DllImport(SQLITE_DLL)]
+        internal static extern int sqlite3_aggregate_count(IntPtr context);
+
+        [DllImport(SQLITE_DLL)]
+        internal static extern IntPtr sqlite3_aggregate_context(IntPtr context, int nBytes);
+
+        [DllImport(SQLITE_DLL, CharSet = CharSet.Unicode)]
+        internal static extern int sqlite3_create_collation16(IntPtr db, string strName, int eTextRep, IntPtr ctx, SqliteCollation fcompare);
+
+        // Statement functions
+        [DllImport(SQLITE_DLL, CharSet = CharSet.Unicode)]
+        internal static extern int sqlite3_prepare16_v2(IntPtr db, string pSql, int nBytes, out IntPtr stmt, out IntPtr ptrRemain);
+
+        [DllImport(SQLITE_DLL)]
+        internal static extern int sqlite3_step(IntPtr stmt);
+
+        [DllImport(SQLITE_DLL)]
+        internal static extern int sqlite3_column_count(IntPtr stmt);
+
+        [DllImport(SQLITE_DLL)]
+        internal static extern IntPtr sqlite3_column_name16(IntPtr stmt, int index);
+
+        [DllImport(SQLITE_DLL)]
+        internal static extern int sqlite3_column_type(IntPtr stmt, int iCol);
+
+        [DllImport(SQLITE_DLL)]
+        internal static extern byte [] sqlite3_column_blob(IntPtr stmt, int iCol);
+
+        [DllImport(SQLITE_DLL)]
+        internal static extern int sqlite3_column_bytes(IntPtr stmt, int iCol);
+
+        [DllImport(SQLITE_DLL)]
+        internal static extern double sqlite3_column_double(IntPtr stmt, int iCol);
+
+        [DllImport(SQLITE_DLL)]
+        internal static extern long sqlite3_column_int64(IntPtr stmt, int iCol);
+
+        [DllImport(SQLITE_DLL)]
+        internal static extern IntPtr sqlite3_column_text16(IntPtr stmt, int iCol);
+
+        [DllImport(SQLITE_DLL)]
+        internal static extern int sqlite3_finalize(IntPtr stmt);
+
+        [DllImport(SQLITE_DLL)]
+        internal static extern int sqlite3_reset(IntPtr stmt);
+
+        [DllImport(SQLITE_DLL)]
+        internal static extern int sqlite3_bind_parameter_index(IntPtr stmt, byte [] paramName);
+
+        [DllImport(SQLITE_DLL)]
+        internal static extern int sqlite3_bind_parameter_count(IntPtr stmt);
+
+        [DllImport(SQLITE_DLL)]
+        internal static extern int sqlite3_bind_blob(IntPtr stmt, int param, byte[] val, int nBytes, IntPtr destructorType);
+
+        [DllImport(SQLITE_DLL)]
+        internal static extern int sqlite3_bind_double(IntPtr stmt, int param, double val);
+
+        [DllImport(SQLITE_DLL)]
+        internal static extern int sqlite3_bind_int(IntPtr stmt, int param, int val);
+
+        [DllImport(SQLITE_DLL)]
+        internal static extern int sqlite3_bind_int64(IntPtr stmt, int param, long val);
+
+        [DllImport(SQLITE_DLL)]
+        internal static extern int sqlite3_bind_null(IntPtr stmt, int param);
+
+        [DllImport(SQLITE_DLL, CharSet = CharSet.Unicode)]
+        internal static extern int sqlite3_bind_text16 (IntPtr stmt, int param, string val, int numBytes, IntPtr destructorType);
+
+        //DllImport(SQLITE_DLL)]
+        //internal static extern int sqlite3_bind_zeroblob(IntPtr stmt, int, int n);
+
+        // Context functions
+        [DllImport(SQLITE_DLL)]
+        internal static extern void sqlite3_result_blob(IntPtr context, byte[] value, int nSize, IntPtr pvReserved);
+
+        [DllImport(SQLITE_DLL)]
+        internal static extern void sqlite3_result_double(IntPtr context, double value);
+
+        [DllImport(SQLITE_DLL)]
+        internal static extern void sqlite3_result_error(IntPtr context, byte[] strErr, int nLen);
+
+        [DllImport(SQLITE_DLL)]
+        internal static extern void sqlite3_result_int(IntPtr context, int value);
+
+        [DllImport(SQLITE_DLL)]
+        internal static extern void sqlite3_result_int64(IntPtr context, Int64 value);
+
+        [DllImport(SQLITE_DLL)]
+        internal static extern void sqlite3_result_null(IntPtr context);
+
+        [DllImport(SQLITE_DLL)]
+        internal static extern void sqlite3_result_text(IntPtr context, byte[] value, int nLen, IntPtr pvReserved);
+
+        [DllImport(SQLITE_DLL, CharSet = CharSet.Unicode)]
+        internal static extern void sqlite3_result_error16(IntPtr context, string strName, int nLen);
+
+        [DllImport(SQLITE_DLL, CharSet = CharSet.Unicode)]
+        internal static extern void sqlite3_result_text16(IntPtr context, string strName, int nLen, IntPtr pvReserved);
+
+        // Value methods
+        [DllImport(SQLITE_DLL)]
+        internal static extern IntPtr sqlite3_value_blob(IntPtr p);
+
+        [DllImport(SQLITE_DLL)]
+        internal static extern int sqlite3_value_bytes(IntPtr p);
+
+        [DllImport(SQLITE_DLL)]
+        internal static extern double sqlite3_value_double(IntPtr p);
+
+        [DllImport(SQLITE_DLL)]
+        internal static extern int sqlite3_value_int(IntPtr p);
+
+        [DllImport(SQLITE_DLL)]
+        internal static extern Int64 sqlite3_value_int64(IntPtr p);
+
+        [DllImport(SQLITE_DLL)]
+        internal static extern int sqlite3_value_type(IntPtr p);
+
+        [DllImport(SQLITE_DLL)]
+        internal static extern IntPtr sqlite3_value_text16(IntPtr p);
+
+        internal static string PtrToString (this IntPtr ptr)
+        {
+            return System.Runtime.InteropServices.Marshal.PtrToStringUni (ptr);
+        }
+    }
+}
diff --git a/Hyena.Data.Sqlite/Hyena.Data.Sqlite/SqliteFunction.cs b/Hyena.Data.Sqlite/Hyena.Data.Sqlite/SqliteFunction.cs
new file mode 100644
index 0000000..8057221
--- /dev/null
+++ b/Hyena.Data.Sqlite/Hyena.Data.Sqlite/SqliteFunction.cs
@@ -0,0 +1,393 @@
+//
+// Derived from
+// Mono.Data.Sqlite.SQLiteFunction.cs
+//
+// Author(s):
+//   Robert Simpson (robert blackcastlesoft com)
+//
+// Adapted and modified for the Mono Project by
+//   Marek Habersack (grendello gmail com)
+//
+// Adapted and modified for the Hyena by
+//   Gabriel Burt <gburt novell com>
+//
+// Copyright (C) 2006,2010 Novell, Inc (http://www.novell.com)
+// Copyright (C) 2007 Marek Habersack
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+/********************************************************
+ * ADO.NET 2.0 Data Provider for Sqlite Version 3.X
+ * Written by Robert Simpson (robert blackcastlesoft com)
+ *
+ * Released to the public domain, use at your own risk!
+ ********************************************************/
+namespace Hyena.Data.Sqlite
+{
+  using System;
+  using System.Collections;
+  using System.Collections.Generic;
+  using System.Runtime.InteropServices;
+  using System.Globalization;
+
+  /// <summary>
+  /// The type of user-defined function to declare
+  /// </summary>
+  public enum FunctionType
+  {
+    /// <summary>
+    /// Scalar functions are designed to be called and return a result immediately.  Examples include ABS(), Upper(), Lower(), etc.
+    /// </summary>
+    Scalar = 0,
+    /// <summary>
+    /// Aggregate functions are designed to accumulate data until the end of a call and then return a result gleaned from the accumulated data.
+    /// Examples include SUM(), COUNT(), AVG(), etc.
+    /// </summary>
+    Aggregate = 1,
+    /// <summary>
+    /// Collation sequences are used to sort textual data in a custom manner, and appear in an ORDER BY clause.  Typically text in an ORDER BY is
+    /// sorted using a straight case-insensitive comparison function.  Custom collating sequences can be used to alter the behavior of text sorting
+    /// in a user-defined manner.
+    /// </summary>
+    Collation = 2,
+  }
+
+  /// <summary>
+  /// An internal callback delegate declaration.
+  /// </summary>
+  /// <param name="context">Raw context pointer for the user function</param>
+  /// <param name="nArgs">Count of arguments to the function</param>
+  /// <param name="argsptr">A pointer to the array of argument pointers</param>
+  internal delegate void SqliteCallback(IntPtr context, int nArgs, IntPtr argsptr);
+  /// <summary>
+  /// An internal callback delegate declaration.
+  /// </summary>
+  /// <param name="context">Raw context pointer for the user function</param>
+  internal delegate void SqliteFinalCallback(IntPtr context);
+  /// <summary>
+  /// Internal callback delegate for implementing collation sequences
+  /// </summary>
+  /// <param name="len1">Length of the string pv1</param>
+  /// <param name="pv1">Pointer to the first string to compare</param>
+  /// <param name="len2">Length of the string pv2</param>
+  /// <param name="pv2">Pointer to the second string to compare</param>
+  /// <returns>Returns -1 if the first string is less than the second.  0 if they are equal, or 1 if the first string is greater
+  /// than the second.</returns>
+  internal delegate int SqliteCollation(int len1, IntPtr pv1, int len2, IntPtr pv2);
+
+  /// <summary>
+  /// This abstract class is designed to handle user-defined functions easily.  An instance of the derived class is made for each
+  /// connection to the database.
+  /// </summary>
+  /// <remarks>
+  /// Although there is one instance of a class derived from SqliteFunction per database connection, the derived class has no access
+  /// to the underlying connection.  This is necessary to deter implementers from thinking it would be a good idea to make database
+  /// calls during processing.
+  ///
+  /// It is important to distinguish between a per-connection instance, and a per-SQL statement context.  One instance of this class
+  /// services all SQL statements being stepped through on that connection, and there can be many.  One should never store per-statement
+  /// information in member variables of user-defined function classes.
+  ///
+  /// For aggregate functions, always create and store your per-statement data in the contextData object on the 1st step.  This data will
+  /// be automatically freed for you (and Dispose() called if the item supports IDisposable) when the statement completes.
+  /// </remarks>
+  public abstract class SqliteFunction : IDisposable
+  {
+    /// <summary>
+    /// The base connection this function is attached to
+    /// </summary>
+    /// <summary>
+    /// Internal array used to keep track of aggregate function context data
+    /// </summary>
+    private Dictionary<long, object> _contextDataList;
+
+    /// <summary>
+    /// Holds a reference to the callback function for user functions
+    /// </summary>
+    internal SqliteCallback  _InvokeFunc;
+    /// <summary>
+    /// Holds a reference to the callbakc function for stepping in an aggregate function
+    /// </summary>
+    internal SqliteCallback  _StepFunc;
+    /// <summary>
+    /// Holds a reference to the callback function for finalizing an aggregate function
+    /// </summary>
+    internal SqliteFinalCallback  _FinalFunc;
+    /// <summary>
+    /// Holds a reference to the callback function for collation sequences
+    /// </summary>
+    internal SqliteCollation _CompareFunc;
+
+    /// <summary>
+    /// Internal constructor, initializes the function's internal variables.
+    /// </summary>
+    protected SqliteFunction()
+    {
+      _contextDataList = new Dictionary<long, object>();
+    }
+
+    /// <summary>
+    /// Scalar functions override this method to do their magic.
+    /// </summary>
+    /// <remarks>
+    /// Parameters passed to functions have only an affinity for a certain data type, there is no underlying schema available
+    /// to force them into a certain type.  Therefore the only types you will ever see as parameters are
+    /// DBNull.Value, Int64, Double, String or byte[] array.
+    /// </remarks>
+    /// <param name="args">The arguments for the command to process</param>
+    /// <returns>You may return most simple types as a return value, null or DBNull.Value to return null, DateTime, or
+    /// you may return an Exception-derived class if you wish to return an error to Sqlite.  Do not actually throw the error,
+    /// just return it!</returns>
+    public virtual object Invoke(object[] args)
+    {
+      return null;
+    }
+
+    /// <summary>
+    /// Aggregate functions override this method to do their magic.
+    /// </summary>
+    /// <remarks>
+    /// Typically you'll be updating whatever you've placed in the contextData field and returning as quickly as possible.
+    /// </remarks>
+    /// <param name="args">The arguments for the command to process</param>
+    /// <param name="stepNumber">The 1-based step number.  This is incrememted each time the step method is called.</param>
+    /// <param name="contextData">A placeholder for implementers to store contextual data pertaining to the current context.</param>
+    public virtual void Step(object[] args, int stepNumber, ref object contextData)
+    {
+    }
+
+    /// <summary>
+    /// Aggregate functions override this method to finish their aggregate processing.
+    /// </summary>
+    /// <remarks>
+    /// If you implemented your aggregate function properly,
+    /// you've been recording and keeping track of your data in the contextData object provided, and now at this stage you should have
+    /// all the information you need in there to figure out what to return.
+    /// NOTE:  It is possible to arrive here without receiving a previous call to Step(), in which case the contextData will
+    /// be null.  This can happen when no rows were returned.  You can either return null, or 0 or some other custom return value
+    /// if that is the case.
+    /// </remarks>
+    /// <param name="contextData">Your own assigned contextData, provided for you so you can return your final results.</param>
+    /// <returns>You may return most simple types as a return value, null or DBNull.Value to return null, DateTime, or
+    /// you may return an Exception-derived class if you wish to return an error to Sqlite.  Do not actually throw the error,
+    /// just return it!
+    /// </returns>
+    public virtual object Final(object contextData)
+    {
+      return null;
+    }
+
+    /// <summary>
+    /// User-defined collation sequences override this method to provide a custom string sorting algorithm.
+    /// </summary>
+    /// <param name="param1">The first string to compare</param>
+    /// <param name="param2">The second strnig to compare</param>
+    /// <returns>1 if param1 is greater than param2, 0 if they are equal, or -1 if param1 is less than param2</returns>
+    public virtual int Compare(string param1, string param2)
+    {
+      return 0;
+    }
+
+    /// <summary>
+    /// Converts an IntPtr array of context arguments to an object array containing the resolved parameters the pointers point to.
+    /// </summary>
+    /// <remarks>
+    /// Parameters passed to functions have only an affinity for a certain data type, there is no underlying schema available
+    /// to force them into a certain type.  Therefore the only types you will ever see as parameters are
+    /// DBNull.Value, Int64, Double, String or byte[] array.
+    /// </remarks>
+    /// <param name="nArgs">The number of arguments</param>
+    /// <param name="argsptr">A pointer to the array of arguments</param>
+    /// <returns>An object array of the arguments once they've been converted to .NET values</returns>
+    internal object[] ConvertParams(int nArgs, IntPtr argsptr)
+    {
+      object[] parms = new object[nArgs];
+      IntPtr[] argint = new IntPtr[nArgs];
+      Marshal.Copy(argsptr, argint, 0, nArgs);
+
+      for (int n = 0; n < nArgs; n++) {
+        IntPtr valPtr = (IntPtr)argint[n];
+        int type = Native.sqlite3_value_type (valPtr);
+        switch (type) {
+            case SQLITE_INTEGER:
+                parms[n] = Native.sqlite3_value_int64 (valPtr);
+                break;
+            case SQLITE_FLOAT:
+                parms[n] = Native.sqlite3_value_double (valPtr);
+                break;
+            case SQLITE3_TEXT:
+                parms[n] = Native.sqlite3_value_text16 (valPtr).PtrToString ();
+                break;
+            case SQLITE_BLOB:
+                parms[n] = Native.sqlite3_value_blob (valPtr);
+                break;
+            case SQLITE_NULL:
+                parms[n] = null;
+                break;
+            default:
+                throw new Exception (String.Format ("Value is of unknown type {0}", type));
+        }
+      }
+      return parms;
+    }
+
+    /// <summary>
+    /// Takes the return value from Invoke() and Final() and figures out how to return it to Sqlite's context.
+    /// </summary>
+    /// <param name="context">The context the return value applies to</param>
+    /// <param name="returnValue">The parameter to return to Sqlite</param>
+    void SetReturnValue(IntPtr context, object r)
+    {
+        if (r is Exception) {
+            Native.sqlite3_result_error16 (context, ((Exception)r).Message, -1);
+        } else {
+            var o = SqliteUtils.ToDbFormat (r);
+            if (o == null)
+                Native.sqlite3_result_null (context);
+            else if (r is int || r is uint)
+                Native.sqlite3_result_int (context, (int)r);
+            else if (r is long || r is ulong)
+                Native.sqlite3_result_int64 (context, (long)r);
+            else if (r is double || r is float)
+                Native.sqlite3_result_double (context, (double)r);
+            else if (r is string) {
+                string str = (string)r;
+                // -1 for the last arg is the TRANSIENT destructor type so that sqlite will make its own copy of the string
+                Native.sqlite3_result_text16 (context, str, str.Length*2, (IntPtr)(-1));
+            } else if (r is byte[]) {
+                var bytes = (byte[])r;
+                Native.sqlite3_result_blob (context, bytes, bytes.Length, (IntPtr)(-1));
+            }
+        }
+    }
+
+    /// <summary>
+    /// Internal scalar callback function, which wraps the raw context pointer and calls the virtual Invoke() method.
+    /// </summary>
+    /// <param name="context">A raw context pointer</param>
+    /// <param name="nArgs">Number of arguments passed in</param>
+    /// <param name="argsptr">A pointer to the array of arguments</param>
+    internal void ScalarCallback(IntPtr context, int nArgs, IntPtr argsptr)
+    {
+      SetReturnValue(context, Invoke(ConvertParams(nArgs, argsptr)));
+    }
+
+    /// <summary>
+    /// Internal collation sequence function, which wraps up the raw string pointers and executes the Compare() virtual function.
+    /// </summary>
+    /// <param name="len1">Length of the string pv1</param>
+    /// <param name="ptr1">Pointer to the first string to compare</param>
+    /// <param name="len2">Length of the string pv2</param>
+    /// <param name="ptr2">Pointer to the second string to compare</param>
+    /// <returns>Returns -1 if the first string is less than the second.  0 if they are equal, or 1 if the first string is greater
+    /// than the second.</returns>
+    internal int CompareCallback(int len1, IntPtr ptr1, int len2, IntPtr ptr2)
+    {
+      return Compare(ptr1.PtrToString (), ptr2.PtrToString ());
+    }
+
+    /// <summary>
+    /// The internal aggregate Step function callback, which wraps the raw context pointer and calls the virtual Step() method.
+    /// </summary>
+    /// <remarks>
+    /// This function takes care of doing the lookups and getting the important information put together to call the Step() function.
+    /// That includes pulling out the user's contextData and updating it after the call is made.  We use a sorted list for this so
+    /// binary searches can be done to find the data.
+    /// </remarks>
+    /// <param name="context">A raw context pointer</param>
+    /// <param name="nArgs">Number of arguments passed in</param>
+    /// <param name="argsptr">A pointer to the array of arguments</param>
+    internal void StepCallback(IntPtr context, int nArgs, IntPtr argsptr)
+    {
+      int n = Native.sqlite3_aggregate_count (context);
+      long nAux;
+      object obj = null;
+
+      nAux = (long)Native.sqlite3_aggregate_context (context, 1);
+      if (n > 1) obj = _contextDataList[nAux];
+
+      Step(ConvertParams(nArgs, argsptr), n, ref obj);
+      _contextDataList[nAux] = obj;
+    }
+
+    /// <summary>
+    /// An internal aggregate Final function callback, which wraps the context pointer and calls the virtual Final() method.
+    /// </summary>
+    /// <param name="context">A raw context pointer</param>
+    internal void FinalCallback(IntPtr context)
+    {
+      long n = (long)Native.sqlite3_aggregate_context (context, 1);
+      object obj = null;
+
+      if (_contextDataList.ContainsKey(n))
+      {
+        obj = _contextDataList[n];
+        _contextDataList.Remove(n);
+      }
+
+      SetReturnValue(context, Final(obj));
+
+      IDisposable disp = obj as IDisposable;
+      if (disp != null) disp.Dispose();
+    }
+
+    /// <summary>
+    /// Placeholder for a user-defined disposal routine
+    /// </summary>
+    /// <param name="disposing">True if the object is being disposed explicitly</param>
+    protected virtual void Dispose(bool disposing)
+    {
+    }
+
+    /// <summary>
+    /// Disposes of any active contextData variables that were not automatically cleaned up.  Sometimes this can happen if
+    /// someone closes the connection while a DataReader is open.
+    /// </summary>
+    public void Dispose()
+    {
+      Dispose(true);
+
+      IDisposable disp;
+
+      foreach (KeyValuePair<long, object> kv in _contextDataList)
+      {
+        disp = kv.Value as IDisposable;
+        if (disp != null)
+          disp.Dispose();
+      }
+      _contextDataList.Clear();
+
+      _InvokeFunc = null;
+      _StepFunc = null;
+      _FinalFunc = null;
+      _CompareFunc = null;
+      _contextDataList = null;
+
+      GC.SuppressFinalize(this);
+    }
+
+    const int SQLITE_INTEGER = 1;
+    const int SQLITE_FLOAT   = 2;
+    const int SQLITE3_TEXT   = 3;
+    const int SQLITE_BLOB    = 4;
+    const int SQLITE_NULL    = 5;
+  }
+}
diff --git a/Hyena.Data.Sqlite/Hyena.Data.Sqlite/SqliteFunctionAttribute.cs b/Hyena.Data.Sqlite/Hyena.Data.Sqlite/SqliteFunctionAttribute.cs
new file mode 100644
index 0000000..29db318
--- /dev/null
+++ b/Hyena.Data.Sqlite/Hyena.Data.Sqlite/SqliteFunctionAttribute.cs
@@ -0,0 +1,81 @@
+//
+// Derived from
+// Mono.Data.Sqlite.SQLiteFunctionAttribute.cs
+//
+// Author(s):
+//   Robert Simpson (robert blackcastlesoft com)
+//
+// Adapted and modified for the Mono Project by
+//   Marek Habersack (grendello gmail com)
+//
+//
+// Copyright (C) 2006 Novell, Inc (http://www.novell.com)
+// Copyright (C) 2007 Marek Habersack
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+/********************************************************
+ * ADO.NET 2.0 Data Provider for Sqlite Version 3.X
+ * Written by Robert Simpson (robert blackcastlesoft com)
+ *
+ * Released to the public domain, use at your own risk!
+ ********************************************************/
+
+using System;
+using System.Runtime.InteropServices;
+
+namespace Hyena.Data.Sqlite
+{
+    /// <summary>
+    /// A simple custom attribute to enable us to easily find user-defined functions in
+    /// the loaded assemblies and initialize them in Sqlite as connections are made.
+    /// </summary>
+    [AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = true)]
+    public class SqliteFunctionAttribute : Attribute
+    {
+        internal Type        _instanceType;
+
+        /// <summary>
+        /// Default constructor, initializes the internal variables for the function.
+        /// </summary>
+        public SqliteFunctionAttribute()
+        {
+            Name = "";
+            Arguments = -1;
+            FuncType = FunctionType.Scalar;
+            }
+
+        /// <summary>
+        /// The function's name as it will be used in Sqlite command text.
+        /// </summary>
+        public string Name { get; set; }
+
+        /// <summary>
+        /// The number of arguments this function expects.  -1 if the number of arguments is variable.
+        /// </summary>
+        public int Arguments { get; set; }
+
+        /// <summary>
+        /// The type of function this implementation will be.
+        /// </summary>
+        public FunctionType FuncType { get; set; }
+    }
+}
diff --git a/Hyena.Data.Sqlite/Hyena.Data.Sqlite/SqliteModelCache.cs b/Hyena.Data.Sqlite/Hyena.Data.Sqlite/SqliteModelCache.cs
index e184e60..5fd7c94 100644
--- a/Hyena.Data.Sqlite/Hyena.Data.Sqlite/SqliteModelCache.cs
+++ b/Hyena.Data.Sqlite/Hyena.Data.Sqlite/SqliteModelCache.cs
@@ -29,7 +29,6 @@
 
 using System;
 using System.Collections.Generic;
-using System.Data;
 using System.Text;
 
 using Hyena.Query;
diff --git a/Hyena.Data.Sqlite/Hyena.Data.Sqlite/SqliteModelProvider.cs b/Hyena.Data.Sqlite/Hyena.Data.Sqlite/SqliteModelProvider.cs
index 073467f..f396ce4 100644
--- a/Hyena.Data.Sqlite/Hyena.Data.Sqlite/SqliteModelProvider.cs
+++ b/Hyena.Data.Sqlite/Hyena.Data.Sqlite/SqliteModelProvider.cs
@@ -28,7 +28,6 @@
 
 using System;
 using System.Collections.Generic;
-using System.Data;
 using System.Reflection;
 using System.Text;
 
@@ -143,7 +142,7 @@ namespace Hyena.Data.Sqlite
             if (connection.TableExists (HyenaTableName)) {
                 using (IDataReader reader = connection.Query (SelectVersionSql (TableName))) {
                     if (reader.Read ()) {
-                        int table_version = reader.GetInt32 (0);
+                        int table_version = (int)reader[0];
                         if (table_version < ModelVersion) {
                             MigrateTable (table_version);
                             UpdateVersion (TableName, ModelVersion);
@@ -363,12 +362,12 @@ namespace Hyena.Data.Sqlite
             try {
                 foreach (DatabaseColumn column in select_columns) {
                     bad_column = column;
-                    column.SetValue (target, reader, i++);
+                    column.SetFromDbValue (target, reader[i++]);
                 }
 
                 foreach (VirtualDatabaseColumn column in virtual_columns) {
                     bad_column = column;
-                    column.SetValue (target, reader, i++);
+                    column.SetFromDbValue (target, reader[i++]);
                 }
             } catch (Exception e) {
                 Log.Debug (
diff --git a/Hyena.Data.Sqlite/Hyena.Data.Sqlite/SqliteUtils.cs b/Hyena.Data.Sqlite/Hyena.Data.Sqlite/SqliteUtils.cs
index 31a8626..eb56ec2 100644
--- a/Hyena.Data.Sqlite/Hyena.Data.Sqlite/SqliteUtils.cs
+++ b/Hyena.Data.Sqlite/Hyena.Data.Sqlite/SqliteUtils.cs
@@ -29,7 +29,6 @@
 using System;
 using System.Reflection;
 using System.Text;
-using Mono.Data.Sqlite;
 using System.Collections.Generic;
 
 namespace Hyena.Data.Sqlite
@@ -58,6 +57,14 @@ namespace Hyena.Data.Sqlite
             }
         }
 
+        public static object ToDbFormat (object value)
+        {
+            if (value == null)
+                return null;
+
+            return ToDbFormat (value.GetType (), value);
+        }
+
         public static object ToDbFormat (Type type, object value)
         {
             if (type == typeof (string)) {
@@ -82,6 +89,12 @@ namespace Hyena.Data.Sqlite
             return value;
         }
 
+        public static T FromDbFormat<T> (object value)
+        {
+            object o = FromDbFormat (typeof(T), value);
+            return o == null ? default(T) : (T)o;
+        }
+
         public static object FromDbFormat (Type type, object value)
         {
             if (Convert.IsDBNull (value))
diff --git a/Hyena.Data.Sqlite/Hyena.Data.Sqlite/Tests/SqliteModelProviderTests.cs b/Hyena.Data.Sqlite/Hyena.Data.Sqlite/Tests/SqliteModelProviderTests.cs
index 7b12c5d..18c553c 100644
--- a/Hyena.Data.Sqlite/Hyena.Data.Sqlite/Tests/SqliteModelProviderTests.cs
+++ b/Hyena.Data.Sqlite/Hyena.Data.Sqlite/Tests/SqliteModelProviderTests.cs
@@ -29,7 +29,6 @@
 #if ENABLE_TESTS
 
 using System;
-using System.Data;
 using System.IO;
 using NUnit.Framework;
 using Hyena.Data.Sqlite;
@@ -172,7 +171,7 @@ namespace Hyena.Data.Sqlite.Tests
 
             using (IDataReader reader = connection.Query (command)) {
                 reader.Read ();
-                Assert.IsTrue (reader.IsDBNull (0));
+                Assert.IsTrue (reader[0] == null);
             }
 
             DbBoundType loaded_item = provider.FetchSingle (newed_item.PrimaryKey);
@@ -196,7 +195,7 @@ namespace Hyena.Data.Sqlite.Tests
             string command = String.Format ("SELECT PrivateTimeSpanProperty FROM {0} WHERE PrimaryKey = {1}", provider.TableName, newed_item.PrimaryKey);
             using (IDataReader reader = connection.Query (command)) {
                 reader.Read ();
-                Assert.IsTrue (reader.IsDBNull (0));
+                Assert.IsTrue (reader[0] == null);
             }
 
             // NUnit boxes and uses reference equality, rather than Equals()
diff --git a/Hyena.Data.Sqlite/Hyena.Data.Sqlite/Tests/SqliteTests.cs b/Hyena.Data.Sqlite/Hyena.Data.Sqlite/Tests/SqliteTests.cs
new file mode 100644
index 0000000..eb9c5bd
--- /dev/null
+++ b/Hyena.Data.Sqlite/Hyena.Data.Sqlite/Tests/SqliteTests.cs
@@ -0,0 +1,218 @@
+//
+// SqliteTests.cs
+//
+// Author:
+//   Gabriel Burt <gburt novell com>
+//
+// Copyright (C) 2010 Novell, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+#if ENABLE_TESTS
+
+using System;
+using System.Linq;
+
+using NUnit.Framework;
+using Hyena.Data.Sqlite;
+
+namespace Hyena.Data.Sqlite.Tests
+{
+    [TestFixture]
+    public class SqliteTests
+    {
+        [Test]
+        public void Test ()
+        {
+            using (var con = new Connection (":memory:")) {
+                using (var stmt = con.CreateStatement ("SELECT 'foobar' as version")) {
+                    Assert.AreEqual ("foobar", stmt.First ()[0]);
+                    Assert.AreEqual ("foobar", stmt.First ()["version"]);
+                }
+
+                using (var stmt = con.CreateStatement ("SELECT 2 + 5 as res")) {
+                    Assert.AreEqual (7, stmt.First ()[0]);
+                    Assert.AreEqual (7, stmt.First ()["res"]);
+
+                    try {
+                        stmt.Bind ();
+                        Assert.Fail ("should not be able to bind parameterless statement");
+                    } catch {}
+                }
+            }
+        }
+
+        [Test]
+        public void TestBinding ()
+        {
+            using (var con = new Connection (":memory:")) {
+                using (var stmt = con.CreateStatement ("SELECT ? as version")) {
+                    try {
+                        stmt.First ();
+                        Assert.Fail ("unbound statement should have thrown an exception");
+                    } catch {}
+
+                    try {
+                        stmt.Bind (1, 2);
+                        Assert.Fail ("bound statement with the wrong number of parameters");
+                    } catch {}
+
+                    try {
+                        stmt.Bind ();
+                        Assert.Fail ("bound statement with the wrong number of parameters");
+                    } catch {}
+
+                    stmt.Bind (21);
+                    Assert.AreEqual (21, stmt.First ()[0]);
+                    Assert.AreEqual (21, stmt.First ()["version"]);
+
+                    stmt.Bind ("ffoooo");
+                    Assert.AreEqual ("ffoooo", stmt.First ()[0]);
+                    Assert.AreEqual ("ffoooo", stmt.First ()["version"]);
+                }
+
+                using (var stmt = con.CreateStatement ("SELECT ? as a, ? as b, ?")) {
+                    stmt.Bind (1, "two", 3.3);
+                    Assert.AreEqual (1, stmt.First ()[0]);
+                    Assert.AreEqual ("two", stmt.First ()["b"]);
+                    Assert.AreEqual (3.3, stmt.First ()[2]);
+                }
+            }
+        }
+
+        [Test]
+        public void CreateTable ()
+        {
+            using (var con = new Connection (":memory:")) {
+                CreateUsers (con);
+
+                using (var stmt = con.CreateStatement ("SELECT COUNT(*) FROM Users")) {
+                    Assert.AreEqual (2, stmt.First ()[0]);
+                }
+
+                using (var stmt = con.CreateStatement ("SELECT ID, Name FROM Users ORDER BY NAME")) {
+                    var row1 = stmt.First ();
+                    Assert.AreEqual ("Aaron", row1["Name"]);
+                    Assert.AreEqual ("Aaron", row1[1]);
+                    Assert.AreEqual (2, row1["ID"]);
+                    Assert.AreEqual (2, row1[0]);
+
+                    var row2 = stmt.Skip (1).First ();
+                    Assert.AreEqual ("Gabriel", row2["Name"]);
+                    Assert.AreEqual ("Gabriel", row2[1]);
+                    Assert.AreEqual (1, row2["ID"]);
+                    Assert.AreEqual (1, row2[0]);
+                }
+            }
+        }
+
+        private void CreateUsers (Connection con)
+        {
+            using (var stmt = con.CreateStatement ("DROP TABLE IF EXISTS Users")) {
+                stmt.Execute ();
+            }
+
+            using (var stmt = con.CreateStatement ("CREATE TABLE Users (ID INTEGER PRIMARY KEY, Name TEXT)")) {
+                stmt.Execute ();
+            }
+
+            using (var stmt = con.CreateStatement ("INSERT INTO Users (Name) VALUES (?)")) {
+                stmt.Bind ("Gabriel").Execute ();
+                stmt.Bind ("Aaron").Execute ();
+            }
+        }
+
+        [Test]
+        public void CheckInterleavedAccess ()
+        {
+            using (var con = new Connection (":memory:")) {
+                CreateUsers (con);
+
+                var q1 = con.Query ("SELECT ID, Name FROM Users ORDER BY NAME");
+                var q2 = con.Query ("SELECT ID, Name FROM Users ORDER BY ID");
+
+                Assert.IsTrue (q1.Read ());
+                Assert.IsTrue (q2.Read ());
+                Assert.AreEqual ("Aaron", q1["Name"]);
+                Assert.AreEqual ("Gabriel", q2["Name"]);
+
+                Assert.IsTrue (q2.Read ());
+                Assert.AreEqual ("Aaron", q2["Name"]);
+                Assert.IsTrue (q1.Read ());
+                Assert.AreEqual ("Gabriel", q1["Name"]);
+
+                q1.Dispose ();
+                q2.Dispose ();
+            }
+        }
+
+        [Test]
+        public void MultipleCommands ()
+        {
+            using (var con = new Connection (":memory:")) {
+                try {
+                    using (var stmt = con.CreateStatement ("CREATE TABLE Lusers (ID INTEGER PRIMARY KEY, Name TEXT); INSERT INTO Lusers (Name) VALUES ('Foo')")) {
+                        stmt.Execute ();
+                    }
+                    Assert.Fail ("Mutliple commands aren't supported in this sqlite binding");
+                } catch {}
+            }
+        }
+
+        [Test]
+        public void Functions ()
+        {
+            using (var con = new Connection (":memory:")) {
+                con.AddFunction<Md5Function> ();
+
+                using (var stmt = con.CreateStatement ("SELECT HYENA_MD5(?, ?)")) {
+                    Assert.AreEqual ("ae2b1fca515949e5d54fb22b8ed95575", stmt.Bind (1, "testing").QueryScalar ());
+                    Assert.AreEqual (null, stmt.Bind (1, null).QueryScalar ());
+                }
+
+                using (var stmt = con.CreateStatement ("SELECT HYENA_MD5(?, ?, ?)")) {
+                    Assert.AreEqual ("ae2b1fca515949e5d54fb22b8ed95575", stmt.Bind (2, "test", "ing").QueryScalar ());
+                    Assert.AreEqual (null, stmt.Bind (2, null, null).QueryScalar ());
+                }
+
+                using (var stmt = con.CreateStatement ("SELECT HYENA_MD5(?, ?, ?, ?)")) {
+                    Assert.AreEqual (null, stmt.Bind (3, null, "", null).QueryScalar ());
+
+                    try {
+                        con.RemoveFunction<Md5Function> ();
+                        Assert.Fail ("Removed function while statement active");
+                    } catch (Exception e) {
+                        Assert.AreEqual ("Unable to delete/modify user-function due to active statements", e.Message);
+                    }
+                }
+
+                try {
+                    using (var stmt = con.CreateStatement ("SELECT HYENA_MD5(?, ?, ?, ?)")) {
+                        Assert.AreEqual ("ae2b1fca515949e5d54fb22b8ed95575", stmt.QueryScalar ());
+                        Assert.Fail ("Function HYENA_MD5 should no longer exist");
+                    }
+                } catch {}
+            }
+        }
+    }
+}
+
+#endif
diff --git a/Hyena.Data.Sqlite/Makefile.am b/Hyena.Data.Sqlite/Makefile.am
index 2f1d38c..6f2d697 100644
--- a/Hyena.Data.Sqlite/Makefile.am
+++ b/Hyena.Data.Sqlite/Makefile.am
@@ -1,14 +1,15 @@
 ASSEMBLY = Hyena.Data.Sqlite
 TARGET = library
-LINK = -r:Mono.Posix -r:System  -r:System.Core  -r:System.Data \
-	   -r:$(DIR_BIN)/Mono.Data.Sqlite.dll \
-	   -r:$(DIR_BIN)/Hyena.dll
+LINK = -r:Mono.Posix -r:System -r:System.Core -r:$(DIR_BIN)/Hyena.dll
+
 SOURCES =  \
+	Hyena.Data.Sqlite/Sqlite.cs \
 	Hyena.Data.Sqlite/DatabaseColumn.cs \
 	Hyena.Data.Sqlite/DatabaseColumnAttribute.cs \
-	Hyena.Data.Sqlite/HyenaSqliteArrayDataReader.cs \
 	Hyena.Data.Sqlite/HyenaSqliteCommand.cs \
 	Hyena.Data.Sqlite/HyenaSqliteConnection.cs \
+	Hyena.Data.Sqlite/SqliteFunction.cs \
+	Hyena.Data.Sqlite/SqliteFunctionAttribute.cs \
 	Hyena.Data.Sqlite/ICacheableDatabaseModel.cs \
 	Hyena.Data.Sqlite/SqliteModelCache.cs \
 	Hyena.Data.Sqlite/SqliteModelProvider.cs \
@@ -16,6 +17,7 @@ SOURCES =  \
 	Hyena.Data.Sqlite/Tests/DbBoundType.cs \
 	Hyena.Data.Sqlite/Tests/SqliteCommandTests.cs \
 	Hyena.Data.Sqlite/Tests/SqliteModelProviderTests.cs \
+	Hyena.Data.Sqlite/Tests/SqliteTests.cs \
 	Hyena.Data.Sqlite/Tests/SqliteUtilTests.cs \
 	Hyena.Metrics/DbSampleStore.cs \
 	Hyena.Metrics/HttpPoster.cs \
diff --git a/Makefile.am b/Makefile.am
index 3a83e91..b6a3955 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -2,7 +2,6 @@
 SUBDIRS =  \
     build \
     Hyena \
-	Mono.Data.Sqlite \
 	Hyena.Data.Sqlite \
 	Hyena.Gui
 



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]