[hyena/sqlite: 1/2] [Hyena.Data.Sqlite] New light-weight sqlite binding
- From: Gabriel Burt <gburt src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [hyena/sqlite: 1/2] [Hyena.Data.Sqlite] New light-weight sqlite binding
- Date: Wed, 10 Nov 2010 21:42:37 +0000 (UTC)
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]