* beagled/Makefile.am, beagled/Mono.Data.SqliteClient: Pull in latest upstream version, and update Jon's read-on-demand optimization. * beagled/FileAttributesStore_Sqlite.cs, beagled/TextCache.cs: Update for slightly modified sqlite exception API. Index: beagled/FileAttributesStore_Sqlite.cs =================================================================== RCS file: /cvs/gnome/beagle/beagled/FileAttributesStore_Sqlite.cs,v retrieving revision 1.20 diff -u -B -p -r1.20 FileAttributesStore_Sqlite.cs --- beagled/FileAttributesStore_Sqlite.cs 24 Jan 2006 23:35:23 -0000 1.20 +++ beagled/FileAttributesStore_Sqlite.cs 2 Feb 2006 15:20:47 -0000 @@ -195,11 +195,8 @@ namespace Beagle.Daemon { try { command.ExecuteNonQuery (); break; - } catch (SqliteException ex) { - if (ex.SqliteError == SqliteError.BUSY) - Thread.Sleep (50); - else - throw ex; + } catch (SqliteBusyException ex) { + Thread.Sleep (50); } } @@ -224,11 +221,8 @@ namespace Beagle.Daemon { while (reader == null) { try { reader = command.ExecuteReader (); - } catch (SqliteException ex) { - if (ex.SqliteError == SqliteError.BUSY) - Thread.Sleep (50); - else - throw ex; + } catch (SqliteBusyException ex) { + Thread.Sleep (50); } } return reader; @@ -239,11 +233,8 @@ namespace Beagle.Daemon { while (true) { try { return reader.Read (); - } catch (SqliteException ex) { - if (ex.SqliteError == SqliteError.BUSY) - Thread.Sleep (50); - else - throw ex; + } catch (SqliteBusyException ex) { + Thread.Sleep (50); } } } Index: beagled/Makefile.am =================================================================== RCS file: /cvs/gnome/beagle/beagled/Makefile.am,v retrieving revision 1.150 diff -u -B -p -r1.150 Makefile.am --- beagled/Makefile.am 31 Jan 2006 22:55:32 -0000 1.150 +++ beagled/Makefile.am 2 Feb 2006 15:20:47 -0000 @@ -9,7 +9,8 @@ # 0219 = Variable is assigned but never used # 0436 = Ignore imported type (local SqliteClient) -CSC = mcs -debug -nowarn:0162,0164,0169,0219,0436 +# unsafe arg for Mono.Data.SqliteClient fork +CSC = mcs -debug -nowarn:0162,0164,0169,0219,0436 -unsafe BACKENDDIR = $(pkglibdir)/Backends @@ -244,7 +245,7 @@ SQLITE_CSFILES = \ $(sqlitedir)/Sqlite.cs \ $(sqlitedir)/SqliteDataAdapter.cs \ $(sqlitedir)/SqliteDataReader.cs \ - $(sqlitedir)/SqliteException.cs \ + $(sqlitedir)/SqliteExceptions.cs \ $(sqlitedir)/SqliteParameterCollection.cs \ $(sqlitedir)/SqliteParameter.cs \ $(sqlitedir)/SqliteRowUpdatedEventArgs.cs \ Index: beagled/TextCache.cs =================================================================== RCS file: /cvs/gnome/beagle/beagled/TextCache.cs,v retrieving revision 1.28 diff -u -B -p -r1.28 TextCache.cs --- beagled/TextCache.cs 30 Jan 2006 18:19:54 -0000 1.28 +++ beagled/TextCache.cs 2 Feb 2006 15:20:47 -0000 @@ -116,12 +116,9 @@ namespace Beagle.Daemon { try { reader = ExecuteReaderOrWait (command); - } catch (SqliteException ex) { - if (ex.SqliteError == SqliteError.NOTADB) { - Logger.Log.Warn ("Likely sqlite database version mismatch trying to read from {0}. Purging.", db_filename); - create_new_db = true; - } else - throw; + } catch (ApplicationException ex) { + Logger.Log.Warn ("Likely sqlite database version mismatch trying to read from {0}. Purging.", db_filename); + create_new_db = true; } if (reader != null) @@ -178,12 +175,9 @@ namespace Beagle.Daemon { try { command.ExecuteNonQuery (); break; - } catch (SqliteException ex) { + } catch (SqliteBusyException ex) { // FIXME: should we eventually time out? - if (ex.SqliteError == SqliteError.BUSY) - Thread.Sleep (50); - else - throw ex; + Thread.Sleep (50); } } command.Dispose (); @@ -195,11 +189,8 @@ namespace Beagle.Daemon { while (reader == null) { try { reader = command.ExecuteReader (); - } catch (SqliteException ex) { - if (ex.SqliteError == SqliteError.BUSY) - Thread.Sleep (50); - else - throw ex; + } catch (SqliteBusyException ex) { + Thread.Sleep (50); } } return reader; @@ -210,11 +201,8 @@ namespace Beagle.Daemon { while (true) { try { return reader.Read (); - } catch (SqliteException ex) { - if (ex.SqliteError == SqliteError.BUSY) - Thread.Sleep (50); - else - throw ex; + } catch (SqliteBusyException ex) { + Thread.Sleep (50); } } } Index: beagled/Mono.Data.SqliteClient/Sqlite.cs =================================================================== RCS file: /cvs/gnome/beagle/beagled/Mono.Data.SqliteClient/Sqlite.cs,v retrieving revision 1.2 diff -u -B -p -r1.2 Sqlite.cs --- beagled/Mono.Data.SqliteClient/Sqlite.cs 24 Oct 2005 21:56:28 -0000 1.2 +++ beagled/Mono.Data.SqliteClient/Sqlite.cs 2 Feb 2006 15:20:48 -0000 @@ -33,13 +33,14 @@ using System; using System.Security; using System.Runtime.InteropServices; +using System.Text; namespace Mono.Data.SqliteClient { /// /// Represents the return values for sqlite_exec() and sqlite_step() /// - public enum SqliteError : int { + internal enum SqliteError : int { /// Successful result OK = 0, /// SQL error or missing database @@ -134,14 +135,14 @@ namespace Mono.Data.SqliteClient [DllImport ("sqlite")] internal static extern SqliteError sqlite_exec (IntPtr handle, string sql, IntPtr callback, IntPtr user_data, out IntPtr errstr_ptr); - [DllImport("sqlite3")] - internal static extern int sqlite3_open (string dbname, out IntPtr handle); + [DllImport("sqlite3", CharSet = CharSet.Unicode)] + internal static extern int sqlite3_open16 (string dbname, out IntPtr handle); [DllImport("sqlite3")] internal static extern void sqlite3_close (IntPtr sqlite_handle); [DllImport("sqlite3")] - internal static extern IntPtr sqlite3_errmsg (IntPtr sqlite_handle); + internal static extern IntPtr sqlite3_errmsg16 (IntPtr sqlite_handle); [DllImport("sqlite3")] internal static extern int sqlite3_changes (IntPtr handle); @@ -150,7 +151,7 @@ namespace Mono.Data.SqliteClient internal static extern int sqlite3_last_insert_rowid (IntPtr sqlite_handle); [DllImport ("sqlite3")] - internal static extern SqliteError sqlite3_prepare (IntPtr sqlite_handle, IntPtr zSql, int zSqllen, out IntPtr pVm, out IntPtr pzTail); + internal static extern SqliteError sqlite3_prepare16 (IntPtr sqlite_handle, IntPtr zSql, int zSqllen, out IntPtr pVm, out IntPtr pzTail); [DllImport ("sqlite3")] internal static extern SqliteError sqlite3_step (IntPtr pVm); @@ -162,16 +163,16 @@ namespace Mono.Data.SqliteClient internal static extern SqliteError sqlite3_exec (IntPtr handle, string sql, IntPtr callback, IntPtr user_data, out IntPtr errstr_ptr); [DllImport ("sqlite3")] - internal static extern IntPtr sqlite3_column_name (IntPtr pVm, int col); + internal static extern IntPtr sqlite3_column_name16 (IntPtr pVm, int col); [DllImport ("sqlite3")] - internal static extern IntPtr sqlite3_column_text (IntPtr pVm, int col); + internal static extern IntPtr sqlite3_column_text16 (IntPtr pVm, int col); [DllImport ("sqlite3")] internal static extern IntPtr sqlite3_column_blob (IntPtr pVm, int col); [DllImport ("sqlite3")] - internal static extern int sqlite3_column_bytes (IntPtr pVm, int col); + internal static extern int sqlite3_column_bytes16 (IntPtr pVm, int col); [DllImport ("sqlite3")] internal static extern int sqlite3_column_count (IntPtr pVm); @@ -185,11 +186,14 @@ namespace Mono.Data.SqliteClient [DllImport ("sqlite3")] internal static extern double sqlite3_column_double (IntPtr pVm, int col); + [DllImport ("sqlite3")] + internal static extern IntPtr sqlite3_column_decltype16 (IntPtr pVm, int col); + [DllImport ("sqlite3")] internal static extern int sqlite3_bind_parameter_count (IntPtr pStmt); [DllImport ("sqlite3")] - internal static extern String sqlite3_bind_parameter_name (IntPtr pStmt, int n); + internal static extern IntPtr sqlite3_bind_parameter_name (IntPtr pStmt, int n); // UTF-8 encoded return [DllImport ("sqlite3")] internal static extern SqliteError sqlite3_bind_blob (IntPtr pStmt, int n, byte[] blob, int length, IntPtr freetype); @@ -201,18 +205,75 @@ namespace Mono.Data.SqliteClient internal static extern SqliteError sqlite3_bind_int (IntPtr pStmt, int n, int value); [DllImport ("sqlite3")] - internal static extern SqliteError sqlite3_bind_int64 (IntPtr pStmt, Int64 n, long value); + internal static extern SqliteError sqlite3_bind_int64 (IntPtr pStmt, int n, long value); [DllImport ("sqlite3")] internal static extern SqliteError sqlite3_bind_null (IntPtr pStmt, int n); - [DllImport ("sqlite3")] - internal static extern SqliteError sqlite3_bind_text (IntPtr pStmt, int n, string value, int length, IntPtr freetype); - - [DllImport ("sqlite3")] - internal static extern SqliteError sqlite3_bind_text16 (IntPtr pStmt, int n, byte[] value, int length, IntPtr freetype); + [DllImport ("sqlite3", CharSet = CharSet.Unicode)] + internal static extern SqliteError sqlite3_bind_text16 (IntPtr pStmt, int n, string value, int length, IntPtr freetype); #endregion + + // These are adapted from Mono.Unix. When encoding is null, + // use Ansi encoding, which is a superset of the default + // expected encoding (ISO-8859-1). + + public static IntPtr StringToHeap (string s, Encoding encoding) + { + if (encoding == null) + return Marshal.StringToCoTaskMemAnsi (s); + + int min_byte_count = encoding.GetMaxByteCount(1); + char[] copy = s.ToCharArray (); + byte[] marshal = new byte [encoding.GetByteCount (copy) + min_byte_count]; + + int bytes_copied = encoding.GetBytes (copy, 0, copy.Length, marshal, 0); + + if (bytes_copied != (marshal.Length-min_byte_count)) + throw new NotSupportedException ("encoding.GetBytes() doesn't equal encoding.GetByteCount()!"); + + IntPtr mem = Marshal.AllocCoTaskMem (marshal.Length); + if (mem == IntPtr.Zero) + throw new OutOfMemoryException (); + + bool copied = false; + try { + Marshal.Copy (marshal, 0, mem, marshal.Length); + copied = true; + } + finally { + if (!copied) + Marshal.FreeCoTaskMem (mem); + } + + return mem; + } + + public static unsafe string HeapToString (IntPtr p, Encoding encoding) + { + if (encoding == null) + return Marshal.PtrToStringAnsi (p); + + if (p == IntPtr.Zero) + return null; + + // This assumes a single byte terminates the string. + + int len = 0; + while (Marshal.ReadByte (p, len) != 0) + checked {++len;} + + string s = new string ((sbyte*) p, 0, len, encoding); + len = s.Length; + while (len > 0 && s [len-1] == 0) + --len; + if (len == s.Length) + return s; + return s.Substring (0, len); + } + + } } Index: beagled/Mono.Data.SqliteClient/SqliteCommand.cs =================================================================== RCS file: /cvs/gnome/beagle/beagled/Mono.Data.SqliteClient/SqliteCommand.cs,v retrieving revision 1.3 diff -u -B -p -r1.3 SqliteCommand.cs --- beagled/Mono.Data.SqliteClient/SqliteCommand.cs 15 Nov 2005 20:49:12 -0000 1.3 +++ beagled/Mono.Data.SqliteClient/SqliteCommand.cs 2 Feb 2006 15:20:48 -0000 @@ -9,6 +9,7 @@ // Chris Turchin // Jeroen Zwartepoorte // Thomas Zoechling +// Joshua Tauberer // // Copyright (C) 2002 Vladimir Vukicevic // @@ -35,7 +36,6 @@ using System; using System.Collections; using System.Text; -using Mono.Unix.Native; using System.Runtime.InteropServices; using System.Text.RegularExpressions; using System.Data; @@ -58,7 +57,6 @@ namespace Mono.Data.SqliteClient private UpdateRowSource upd_row_source; private SqliteParameterCollection sql_params; private bool prepared = false; - private ArrayList pStmts; #endregion @@ -98,7 +96,7 @@ namespace Mono.Data.SqliteClient public string CommandText { get { return sql; } - set { sql = value; } + set { sql = value; prepared = false; } } public int CommandTimeout @@ -173,27 +171,162 @@ namespace Mono.Data.SqliteClient return Sqlite.sqlite_changes(parent_conn.Handle); } - private string ReplaceParams(Match m) + private void BindParameters3 (IntPtr pStmt) { - string input = m.Value; - if (m.Groups["param"].Success) + if (sql_params == null) return; + if (sql_params.Count == 0) return; + + int pcount = Sqlite.sqlite3_bind_parameter_count (pStmt); + + for (int i = 1; i <= pcount; i++) { - Group g = m.Groups["param"]; - string find = g.Value; - //FIXME: sqlite works internally only with strings, so this assumtion is mostly legit, but what about date formatting, etc? - //Need to fix SqlLiteDataReader first to acurately describe the tables - SqliteParameter sqlp = Parameters[find]; - string replace = Convert.ToString(sqlp.Value); - if(sqlp.DbType == DbType.String) - { - replace = "\"" + replace + "\""; + String name = Sqlite.HeapToString (Sqlite.sqlite3_bind_parameter_name (pStmt, i), Encoding.UTF8); + if (name == null) continue; + + SqliteParameter param = sql_params[name]; + + if (param.Value == null) { + Sqlite.sqlite3_bind_null (pStmt, i); + continue; } + + Type ptype = param.Value.GetType (); + + SqliteError err; - input = Regex.Replace(input,find,replace); - return input; + if (ptype.Equals (typeof (String))) + { + String s = (String)param.Value; + err = Sqlite.sqlite3_bind_text16 (pStmt, i, s, -1, (IntPtr)(-1)); + } + else if (ptype.Equals (typeof (DBNull))) + { + err = Sqlite.sqlite3_bind_null (pStmt, i); + } + else if (ptype.Equals (typeof (Boolean))) + { + bool b = (bool)param.Value; + err = Sqlite.sqlite3_bind_int (pStmt, i, b ? 1 : 0); + } else if (ptype.Equals (typeof (Byte))) + { + err = Sqlite.sqlite3_bind_int (pStmt, i, (Byte)param.Value); + } + else if (ptype.Equals (typeof (Char))) + { + err = Sqlite.sqlite3_bind_int (pStmt, i, (Char)param.Value); + } + else if (ptype.Equals (typeof (Int16))) + { + err = Sqlite.sqlite3_bind_int (pStmt, i, (Int16)param.Value); + } + else if (ptype.Equals (typeof (Int32))) + { + err = Sqlite.sqlite3_bind_int (pStmt, i, (Int32)param.Value); + } + else if (ptype.Equals (typeof (SByte))) + { + err = Sqlite.sqlite3_bind_int (pStmt, i, (SByte)param.Value); + } + else if (ptype.Equals (typeof (UInt16))) + { + err = Sqlite.sqlite3_bind_int (pStmt, i, (UInt16)param.Value); + } + else if (ptype.Equals (typeof (DateTime))) + { + DateTime dt = (DateTime)param.Value; + err = Sqlite.sqlite3_bind_int64 (pStmt, i, dt.ToFileTime ()); + } + else if (ptype.Equals (typeof (Double))) + { + err = Sqlite.sqlite3_bind_double (pStmt, i, (Double)param.Value); + } + else if (ptype.Equals (typeof (Single))) + { + err = Sqlite.sqlite3_bind_double (pStmt, i, (Single)param.Value); + } + else if (ptype.Equals (typeof (UInt32))) + { + err = Sqlite.sqlite3_bind_int64 (pStmt, i, (UInt32)param.Value); + } + else if (ptype.Equals (typeof (Int64))) + { + err = Sqlite.sqlite3_bind_int64 (pStmt, i, (Int64)param.Value); + } + else if (ptype.Equals (typeof (Byte[]))) + { + err = Sqlite.sqlite3_bind_blob (pStmt, i, (Byte[])param.Value, ((Byte[])param.Value).Length, (IntPtr)(-1)); + } + else + { + throw new ApplicationException("Unkown Parameter Type"); + } + if (err != SqliteError.OK) + { + throw new ApplicationException ("Sqlite error in bind " + err); + } + } + } + + private void GetNextStatement (IntPtr pzStart, out IntPtr pzTail, out IntPtr pStmt) + { + if (parent_conn.Version == 3) + { + SqliteError err = Sqlite.sqlite3_prepare16 (parent_conn.Handle, pzStart, -1, out pStmt, out pzTail); + if (err != SqliteError.OK) + throw new SqliteSyntaxException (GetError3()); } else - return m.Value; + { + IntPtr errMsg; + SqliteError err = Sqlite.sqlite_compile (parent_conn.Handle, pzStart, out pzTail, out pStmt, out errMsg); + + if (err != SqliteError.OK) + { + string msg = "unknown error"; + if (errMsg != IntPtr.Zero) + { + msg = Marshal.PtrToStringAnsi (errMsg); + Sqlite.sqliteFree (errMsg); + } + throw new SqliteSyntaxException (msg); + } + } + } + + // Executes a statement and ignores its result. + private void ExecuteStatement (IntPtr pStmt) { + int cols; + IntPtr pazValue, pazColName; + ExecuteStatement (pStmt, out cols, out pazValue, out pazColName); + } + + // Executes a statement and returns whether there is more data available. + internal bool ExecuteStatement (IntPtr pStmt, out int cols, out IntPtr pazValue, out IntPtr pazColName) { + SqliteError err; + + if (parent_conn.Version == 3) + { + err = Sqlite.sqlite3_step (pStmt); + if (err == SqliteError.ERROR) + throw new SqliteExecutionException (GetError3()); + pazValue = IntPtr.Zero; pazColName = IntPtr.Zero; // not used for v=3 + cols = Sqlite.sqlite3_column_count (pStmt); + } + else + { + err = Sqlite.sqlite_step (pStmt, out cols, out pazValue, out pazColName); + if (err == SqliteError.ERROR) + throw new SqliteExecutionException (); + } + + if (err == SqliteError.BUSY) + throw new SqliteBusyException(); + + if (err == SqliteError.MISUSE) + throw new SqliteExecutionException(); + + // err is either ROW or DONE. + return err == SqliteError.ROW; } #endregion @@ -204,154 +337,54 @@ namespace Mono.Data.SqliteClient { } - public string ProcessParameters() + public string BindParameters2() { - string processedText = sql; - - //Regex looks odd perhaps, but it works - same impl. as in the firebird db provider - //the named parameters are using the ADO.NET standard @-prefix but sqlite is considering ":" as a prefix for v.3... - //ref: http://www.mail-archive.com/sqlite-users@sqlite.org/msg01851.html - //Regex r = new Regex(@"(('[^']*?\@[^']*')*[^'@]*?)*(?@\w+)+([^'@]*?('[^']*?\@[^']*'))*",RegexOptions.ExplicitCapture); - - //The above statement is true for the commented regEx, but I changed it to use the :-prefix, because now (12.05.2005 sqlite3) - //sqlite is using : as Standard Parameterprefix - - Regex r = new Regex(@"(('[^']*?\:[^']*')*[^':]*?)*(?:\w+)+([^':]*?('[^']*?\:[^']*'))*",RegexOptions.ExplicitCapture); - MatchEvaluator me = new MatchEvaluator(ReplaceParams); - processedText = r.Replace(sql, me); - return processedText; + string text = sql; + + // There used to be a crazy regular expression here, but it caused Mono + // to go into an infinite loop of some sort when there were no parameters + // in the SQL string. That was too complicated anyway. + + // Here we search for substrings of the form :wwwww where w is a letter or digit + // (not sure what a legitimate Sqlite3 identifier is), except those within quotes. + + char inquote = (char)0; + for (int i = 0; i < text.Length; i++) { + char c = text[i]; + if (c == inquote) { + inquote = (char)0; + } else if (inquote == (char)0 && (c == '\'' || c == '"')) { + inquote = c; + } else if (inquote == (char)0 && c == ':') { + int start = i; + while (++i < text.Length && char.IsLetterOrDigit(text[i])) { } // scan to end + string name = text.Substring(start, i-start); + string value = "'" + Convert.ToString(Parameters[name].Value).Replace("'", "''") + "'"; + text = text.Replace(name, value); + i += value.Length - name.Length - 1; + } + } + + return text; } public void Prepare () { - pStmts = new ArrayList(); - string sqlcmds = sql; + // There isn't much we can do here. If a table schema + // changes after preparing a statement, Sqlite bails, + // so we can only compile statements right before we + // want to run them. + if (prepared) return; + if (Parameters.Count > 0 && parent_conn.Version == 2) { - sqlcmds = ProcessParameters(); + sql = BindParameters2(); } - SqliteError err = SqliteError.OK; - IntPtr psql = Mono.Unix.UnixMarshal.StringToHeap(sqlcmds); - IntPtr pzTail = psql; - try { - do { // sql may contain multiple sql commands, loop until they're all processed - IntPtr pStmt = IntPtr.Zero; - if (parent_conn.Version == 3) - { - err = Sqlite.sqlite3_prepare (parent_conn.Handle, pzTail, sql.Length, out pStmt, out pzTail); - if (err != SqliteError.OK) { - string msg = Marshal.PtrToStringAnsi (Sqlite.sqlite3_errmsg (parent_conn.Handle)); - throw new SqliteException (err, msg); - } - } - else - { - IntPtr errMsg; - err = Sqlite.sqlite_compile (parent_conn.Handle, pzTail, out pzTail, out pStmt, out errMsg); - - if (err != SqliteError.OK) - { - string msg = "unknown error"; - if (errMsg != IntPtr.Zero) - { - msg = Marshal.PtrToStringAnsi (errMsg); - Sqlite.sqliteFree (errMsg); - } - throw new SqliteException (err, msg); - } - } - - pStmts.Add(pStmt); - - if (parent_conn.Version == 3) - { - int pcount = Sqlite.sqlite3_bind_parameter_count (pStmt); - if (sql_params == null) pcount = 0; - - for (int i = 1; i <= pcount; i++) - { - String name = Sqlite.sqlite3_bind_parameter_name (pStmt, i); - SqliteParameter param = sql_params[name]; - Type ptype = param.Value.GetType (); - - if (ptype.Equals (typeof (String))) - { - String s = (String)param.Value; - err = Sqlite.sqlite3_bind_text (pStmt, i, s, s.Length, (IntPtr)(-1)); - } - else if (ptype.Equals (typeof (DBNull))) - { - err = Sqlite.sqlite3_bind_null (pStmt, i); - } - else if (ptype.Equals (typeof (Boolean))) - { - bool b = (bool)param.Value; - err = Sqlite.sqlite3_bind_int (pStmt, i, b ? 1 : 0); - } else if (ptype.Equals (typeof (Byte))) - { - err = Sqlite.sqlite3_bind_int (pStmt, i, (Byte)param.Value); - } - else if (ptype.Equals (typeof (Char))) - { - err = Sqlite.sqlite3_bind_int (pStmt, i, (Char)param.Value); - } - else if (ptype.Equals (typeof (Int16))) - { - err = Sqlite.sqlite3_bind_int (pStmt, i, (Int16)param.Value); - } - else if (ptype.Equals (typeof (Int32))) - { - err = Sqlite.sqlite3_bind_int (pStmt, i, (Int32)param.Value); - } - else if (ptype.Equals (typeof (SByte))) - { - err = Sqlite.sqlite3_bind_int (pStmt, i, (SByte)param.Value); - } - else if (ptype.Equals (typeof (UInt16))) - { - err = Sqlite.sqlite3_bind_int (pStmt, i, (UInt16)param.Value); - } - else if (ptype.Equals (typeof (DateTime))) - { - DateTime dt = (DateTime)param.Value; - err = Sqlite.sqlite3_bind_int64 (pStmt, i, dt.ToFileTime ()); - } - else if (ptype.Equals (typeof (Double))) - { - err = Sqlite.sqlite3_bind_double (pStmt, i, (Double)param.Value); - } - else if (ptype.Equals (typeof (Single))) - { - err = Sqlite.sqlite3_bind_double (pStmt, i, (Single)param.Value); - } - else if (ptype.Equals (typeof (UInt32))) - { - err = Sqlite.sqlite3_bind_int64 (pStmt, i, (UInt32)param.Value); - } - else if (ptype.Equals (typeof (Int64))) - { - err = Sqlite.sqlite3_bind_int64 (pStmt, i, (Int64)param.Value); - } - else - { - throw new ApplicationException("Unkown Parameter Type"); - } - if (err != SqliteError.OK) - { - throw new ApplicationException ("Sqlite error in bind " + err); - } - } - } - } while ((int)pzTail - (int)psql < sql.Length); - } finally { - Mono.Unix.UnixMarshal.FreeHeap(psql); - } - prepared=true; + prepared = true; } - IDbDataParameter IDbCommand.CreateParameter() { return CreateParameter (); @@ -365,7 +398,6 @@ namespace Mono.Data.SqliteClient public int ExecuteNonQuery () { int rows_affected; - // Since want_results is false, this always returns null ExecuteReader (CommandBehavior.Default, false, out rows_affected); return rows_affected; } @@ -404,84 +436,97 @@ namespace Mono.Data.SqliteClient public SqliteDataReader ExecuteReader (CommandBehavior behavior, bool want_results, out int rows_affected) { - SqliteDataReader reader = null; - SqliteError err = SqliteError.OK; - IntPtr errMsg = IntPtr.Zero; + Prepare (); + + // The SQL string may contain multiple sql commands, so the main + // thing to do is have Sqlite iterate through the commands. + // If want_results, only the last command is returned as a + // DataReader. Otherwise, no command is returned as a + // DataReader. + + IntPtr psql; // pointer to SQL command + + // Sqlite 2 docs say this: By default, SQLite assumes that all data uses a fixed-size 8-bit + // character (iso8859). But if you give the --enable-utf8 option to the configure script, then the + // library assumes UTF-8 variable sized characters. This makes a difference for the LIKE and GLOB + // operators and the LENGTH() and SUBSTR() functions. The static string sqlite_encoding will be set + // to either "UTF-8" or "iso8859" to indicate how the library was compiled. In addition, the sqlite.h + // header file will define one of the macros SQLITE_UTF8 or SQLITE_ISO8859, as appropriate. + // + // We have no way of knowing whether Sqlite 2 expects ISO8859 or UTF-8, but ISO8859 seems to be the + // default. Therefore, we need to use an ISO8859(-1) compatible encoding, like ANSI. + // OTOH, the user may want to specify the encoding of the bytes stored in the database, regardless + // of what Sqlite is treating them as, + + // For Sqlite 3, we use the UTF-16 prepare function, so we need a UTF-16 string. + + if (parent_conn.Version == 2) + psql = Sqlite.StringToHeap (sql.Trim(), parent_conn.Encoding); + else + psql = Marshal.StringToCoTaskMemUni (sql.Trim()); + + IntPtr pzTail = psql; + IntPtr errMsgPtr; + parent_conn.StartExec (); - - try - { - if (!prepared) - { - Prepare (); - } - for (int i = 0; i < pStmts.Count; i++) { - IntPtr pStmt = (IntPtr)pStmts[i]; - - // If want_results, return the results of the last statement - // via the SqliteDataReader, and execute but ignore the results - // of the other statements. - if (i == pStmts.Count-1 && want_results) - { - reader = new SqliteDataReader (this, pStmt, parent_conn.Version); - break; - } - - // Execute but ignore the results of these statements. + + rows_affected = 0; + + try { + while (true) { + IntPtr pStmt; + + GetNextStatement(pzTail, out pzTail, out pStmt); + + if (pStmt == IntPtr.Zero) + throw new Exception(); + + // pzTail is positioned after the last byte in the + // statement, which will be the NULL character if + // this was the last statement. + bool last = Marshal.ReadByte(pzTail) == 0; + + try { + if (parent_conn.Version == 3) + BindParameters3 (pStmt); + + if (last && want_results) + return new SqliteDataReader (this, pStmt, parent_conn.Version); - if (parent_conn.Version == 3) - { - err = Sqlite.sqlite3_step (pStmt); - } - else - { - int cols; - IntPtr pazValue = IntPtr.Zero; - IntPtr pazColName = IntPtr.Zero; - err = Sqlite.sqlite_step (pStmt, out cols, out pazValue, out pazColName); - } - // On error, misuse, or busy, don't bother with the rest of the statements. - if (err != SqliteError.ROW && err != SqliteError.DONE) break; - } - } - finally - { - if (! want_results) - foreach (IntPtr pStmt in pStmts) { - if (parent_conn.Version == 3) - { - err = Sqlite.sqlite3_finalize (pStmt); - } - else - { - err = Sqlite.sqlite_finalize (pStmt, out errMsg); + ExecuteStatement(pStmt); + + if (last) // rows_affected is only used if !want_results + rows_affected = NumChanges (); + + } finally { + if (! want_results) { + if (parent_conn.Version == 3) + Sqlite.sqlite3_finalize (pStmt); + else + Sqlite.sqlite_finalize (pStmt, out errMsgPtr); } } + + if (last) break; + } + return null; + } finally { parent_conn.EndExec (); - prepared = false; + Marshal.FreeCoTaskMem (psql); } - - if (err != SqliteError.OK && - err != SqliteError.DONE && - err != SqliteError.ROW) - { - if (errMsg != IntPtr.Zero) - { - // TODO: Get the message text - } - throw new SqliteException (err); - } - rows_affected = NumChanges (); - return reader; } - + public int LastInsertRowID () { if (parent_conn.Version == 3) return Sqlite.sqlite3_last_insert_rowid(parent_conn.Handle); else return Sqlite.sqlite_last_insert_rowid(parent_conn.Handle); + } + + private string GetError3() { + return Marshal.PtrToStringUni (Sqlite.sqlite3_errmsg16 (parent_conn.Handle)); } #endregion } Index: beagled/Mono.Data.SqliteClient/SqliteConnection.cs =================================================================== RCS file: /cvs/gnome/beagle/beagled/Mono.Data.SqliteClient/SqliteConnection.cs,v retrieving revision 1.3 diff -u -B -p -r1.3 SqliteConnection.cs --- beagled/Mono.Data.SqliteClient/SqliteConnection.cs 26 Jan 2006 19:47:01 -0000 1.3 +++ beagled/Mono.Data.SqliteClient/SqliteConnection.cs 2 Feb 2006 15:20:48 -0000 @@ -31,7 +31,7 @@ using System; using System.Runtime.InteropServices; using System.Data; -using System.Globalization; +using System.Text; namespace Mono.Data.SqliteClient { @@ -46,7 +46,8 @@ namespace Mono.Data.SqliteClient private int db_version; private IntPtr sqlite_handle; private ConnectionState state; - + private Encoding encoding; + #endregion #region Constructors and destructors @@ -58,6 +59,7 @@ namespace Mono.Data.SqliteClient db_version = 2; state = ConnectionState.Closed; sqlite_handle = IntPtr.Zero; + encoding = null; } public SqliteConnection (string connstring) : this () @@ -91,6 +93,10 @@ namespace Mono.Data.SqliteClient get { return state; } } + public Encoding Encoding { + get { return encoding; } + } + internal int Version { get { return db_version; } } @@ -137,9 +143,9 @@ namespace Mono.Data.SqliteClient if (arg_pieces.Length != 2) { throw new InvalidOperationException ("Invalid connection string"); } - string token = arg_pieces[0].ToLower (CultureInfo.InvariantCulture).Trim (); + string token = arg_pieces[0].ToLower (System.Globalization.CultureInfo.InvariantCulture).Trim (); string tvalue = arg_pieces[1].Trim (); - string tvalue_lc = arg_pieces[1].ToLower (CultureInfo.InvariantCulture).Trim (); + string tvalue_lc = arg_pieces[1].ToLower (System.Globalization.CultureInfo.InvariantCulture).Trim (); if (token == "uri") { if (tvalue_lc.StartsWith ("file://")) { db_file = tvalue.Substring (7); @@ -154,6 +160,8 @@ namespace Mono.Data.SqliteClient db_mode = Convert.ToInt32 (tvalue); } else if (token == "version") { db_version = Convert.ToInt32 (tvalue); + } else if (token == "encoding") { // only for sqlite2 + encoding = Encoding.GetEncoding (tvalue); } } @@ -185,7 +193,7 @@ namespace Mono.Data.SqliteClient public IDbTransaction BeginTransaction () { if (state != ConnectionState.Open) - throw new InvalidOperationException("Invalid operation: The connection is close"); + throw new InvalidOperationException("Invalid operation: The connection is closed"); SqliteTransaction t = new SqliteTransaction(); t.Connection = this; @@ -197,7 +205,7 @@ namespace Mono.Data.SqliteClient public IDbTransaction BeginTransaction (IsolationLevel il) { - return null; + throw new InvalidOperationException(); } public void Close () @@ -241,18 +249,24 @@ namespace Mono.Data.SqliteClient } IntPtr errmsg = IntPtr.Zero; + + if (Version == 2){ + try { + sqlite_handle = Sqlite.sqlite_open(db_file, db_mode, out errmsg); + if (errmsg != IntPtr.Zero) { + string msg = Marshal.PtrToStringAnsi (errmsg); + Sqlite.sqliteFree (errmsg); + throw new ApplicationException (msg); + } + } catch (DllNotFoundException dll) { + db_version = 3; + } + } if (Version == 3) { - int err = Sqlite.sqlite3_open(db_file, out sqlite_handle); + int err = Sqlite.sqlite3_open16(db_file, out sqlite_handle); if (err == (int)SqliteError.ERROR) - throw new ApplicationException (Marshal.PtrToStringAnsi( Sqlite.sqlite3_errmsg (sqlite_handle))); + throw new ApplicationException (Marshal.PtrToStringUni( Sqlite.sqlite3_errmsg16 (sqlite_handle))); } else { - sqlite_handle = Sqlite.sqlite_open(db_file, db_mode, out errmsg); - - if (errmsg != IntPtr.Zero) { - string msg = Marshal.PtrToStringAnsi (errmsg); - Sqlite.sqliteFree (errmsg); - throw new ApplicationException (msg); - } } state = ConnectionState.Open; } Index: beagled/Mono.Data.SqliteClient/SqliteDataReader.cs =================================================================== RCS file: /cvs/gnome/beagle/beagled/Mono.Data.SqliteClient/SqliteDataReader.cs,v retrieving revision 1.2 diff -u -B -p -r1.2 SqliteDataReader.cs --- beagled/Mono.Data.SqliteClient/SqliteDataReader.cs 24 Oct 2005 21:56:28 -0000 1.2 +++ beagled/Mono.Data.SqliteClient/SqliteDataReader.cs 2 Feb 2006 15:20:49 -0000 @@ -6,6 +6,7 @@ // // Author(s): Vladimir Vukicevic // Everaldo Canuto +// Joshua Tauberer // // Copyright (C) 2002 Vladimir Vukicevic // @@ -45,12 +46,11 @@ namespace Mono.Data.SqliteClient private SqliteCommand command; private IntPtr pVm; private int version; - private ArrayList current_row; - - private ArrayList columns; - private Hashtable column_names; + private string[] columns; + private Hashtable column_names_sens, column_names_insens; private bool closed; + private string[] decltypes; #endregion @@ -62,10 +62,10 @@ namespace Mono.Data.SqliteClient pVm = _pVm; version = _version; - current_row = new ArrayList (); + current_row = new ArrayList(); - columns = new ArrayList (); - column_names = new Hashtable (); + column_names_sens = new Hashtable (); + column_names_insens = new Hashtable (CaseInsensitiveHashCodeProvider.DefaultInvariant, CaseInsensitiveComparer.DefaultInvariant); closed = false; } @@ -78,15 +78,17 @@ namespace Mono.Data.SqliteClient } public int FieldCount { - get { return columns.Count; } + get { return columns.Length; } } public object this[string name] { - get { return current_row [(int) column_names[name]]; } + get { + return GetValue (GetOrdinal (name)); + } } public object this[int i] { - get { return current_row [i]; } + get { return GetValue (i); } } public bool IsClosed { @@ -100,82 +102,109 @@ namespace Mono.Data.SqliteClient #endregion #region Internal Methods - + internal bool ReadNextColumn () { - int pN = 0; - IntPtr pazValue = IntPtr.Zero; - IntPtr pazColName = IntPtr.Zero; - SqliteError res; - - if (version == 3) { - res = Sqlite.sqlite3_step (pVm); - pN = Sqlite.sqlite3_column_count (pVm); - } else - res = Sqlite.sqlite_step (pVm, out pN, out pazValue, out pazColName); - - if (res == SqliteError.DONE) { - return false; - } - - if (res != SqliteError.ROW) - throw new SqliteException (res); - - // We have some data; lets read it + int pN; + IntPtr pazValue; + IntPtr pazColName; + bool first = true; + + int[] declmode = null; - // If we are reading the first column, populate the column names - if (column_names.Count == 0) { + bool hasdata = command.ExecuteStatement(pVm, out pN, out pazValue, out pazColName); + + // For the first row, get the column information (names and types) + if (columns == null) { + if (version == 3) { + // A decltype might be null if the type is unknown to sqlite. + decltypes = new string[pN]; + declmode = new int[pN]; // 1 == integer, 2 == datetime + for (int i = 0; i < pN; i++) { + IntPtr decl = Sqlite.sqlite3_column_decltype16 (pVm, i); + if (decl != IntPtr.Zero) { + decltypes[i] = Marshal.PtrToStringUni (decl).ToLower(System.Globalization.CultureInfo.InvariantCulture); + if (decltypes[i] == "int" || decltypes[i] == "integer") + declmode[i] = 1; + else if (decltypes[i] == "date" || decltypes[i] == "datetime") + declmode[i] = 2; + } + } + } + + columns = new string[pN]; for (int i = 0; i < pN; i++) { - string colName = ""; + string colName; if (version == 2) { IntPtr fieldPtr = (IntPtr)Marshal.ReadInt32 (pazColName, i*IntPtr.Size); - colName = Marshal.PtrToStringAnsi (fieldPtr); + colName = Sqlite.HeapToString (fieldPtr, command.Connection.Encoding); } else { - colName = Marshal.PtrToStringAnsi (Sqlite.sqlite3_column_name (pVm, i)); + colName = Marshal.PtrToStringUni (Sqlite.sqlite3_column_name16 (pVm, i)); } - columns.Add (colName); - column_names [colName] = i; + columns[i] = colName; + column_names_sens [colName] = i; + column_names_insens [colName] = i; } } - // Now read the actual data - current_row.Clear (); + if (!hasdata) + return false; + + current_row.Clear(); for (int i = 0; i < pN; i++) { - string colData = ""; if (version == 2) { IntPtr fieldPtr = (IntPtr)Marshal.ReadInt32 (pazValue, i*IntPtr.Size); - colData = Marshal.PtrToStringAnsi (fieldPtr); - current_row.Add (Marshal.PtrToStringAnsi (fieldPtr)); + current_row.Add (Sqlite.HeapToString (fieldPtr, command.Connection.Encoding)); } else { switch (Sqlite.sqlite3_column_type (pVm, i)) { - case 1: - Int64 sqliteint64 = Sqlite.sqlite3_column_int64 (pVm, i); - current_row.Add (sqliteint64.ToString ()); - break; - case 2: - double sqlitedouble = Sqlite.sqlite3_column_double (pVm, i); - current_row.Add (sqlitedouble.ToString ()); - break; - case 3: - colData = Marshal.PtrToStringAnsi (Sqlite.sqlite3_column_text (pVm, i)); - current_row.Add (colData); - break; - case 4: - int blobbytes = Sqlite.sqlite3_column_bytes (pVm, i); - IntPtr blobptr = Sqlite.sqlite3_column_blob (pVm, i); - byte[] blob = new byte[blobbytes]; - Marshal.Copy (blobptr, blob, 0, blobbytes); - current_row.Add (blob); - break; - case 5: - current_row.Add (null); - break; - default: - throw new ApplicationException ("FATAL: Unknown sqlite3_column_type"); + case 1: + long val = Sqlite.sqlite3_column_int64 (pVm, i); + + // If the column was declared as an 'int' or 'integer', let's play + // nice and return an int (version 3 only). + if (declmode[i] == 1 && val >= int.MinValue && val <= int.MaxValue) + current_row.Add ((int)val); + + // Or if it was declared a date or datetime, do the reverse of what we + // do for DateTime parameters. + else if (declmode[i] == 2) + current_row.Add (DateTime.FromFileTime(val)); + + else + current_row.Add (val); + + break; + case 2: + current_row.Add (Sqlite.sqlite3_column_double (pVm, i)); + break; + case 3: + string strval = Marshal.PtrToStringUni (Sqlite.sqlite3_column_text16 (pVm, i)); + current_row.Add (Marshal.PtrToStringUni (Sqlite.sqlite3_column_text16 (pVm, i))); + + // If the column was declared as a 'date' or 'datetime', let's play + // nice and return a DateTime (version 3 only). + if (declmode[i] == 2) + current_row.Add (DateTime.Parse (strval)); + + else + current_row.Add (strval); + + break; + case 4: + int blobbytes = Sqlite.sqlite3_column_bytes16 (pVm, i); + IntPtr blobptr = Sqlite.sqlite3_column_blob (pVm, i); + byte[] blob = new byte[blobbytes]; + Marshal.Copy (blobptr, blob, 0, blobbytes); + current_row.Add (blob); + break; + case 5: + current_row.Add (null); + break; + default: + throw new ApplicationException ("FATAL: Unknown sqlite3_column_type"); } - } + } } - return true; } @@ -277,7 +306,7 @@ namespace Mono.Data.SqliteClient public bool Read () { - return ReadNextColumn (); + return NextResult (); } #endregion @@ -286,12 +315,12 @@ namespace Mono.Data.SqliteClient public bool GetBoolean (int i) { - return Convert.ToBoolean ((string) current_row [i]); + return Convert.ToBoolean (current_row[i]); } public byte GetByte (int i) { - return Convert.ToByte ((string) current_row [i]); + return Convert.ToByte (current_row[i]); } public long GetBytes (int i, long fieldOffset, byte[] buffer, int bufferOffset, int length) @@ -301,7 +330,7 @@ namespace Mono.Data.SqliteClient public char GetChar (int i) { - return Convert.ToChar ((string) current_row [i]); + return Convert.ToChar (current_row[i]); } public long GetChars (int i, long fieldOffset, char[] buffer, int bufferOffset, int length) @@ -316,32 +345,37 @@ namespace Mono.Data.SqliteClient public string GetDataTypeName (int i) { + if (decltypes != null && decltypes[i] != null) + return decltypes[i]; return "text"; // SQL Lite data type } public DateTime GetDateTime (int i) { - return Convert.ToDateTime ((string) current_row [i]); + return Convert.ToDateTime (current_row[i]); } public decimal GetDecimal (int i) { - return Convert.ToDecimal ((string) current_row [i]); + return Convert.ToDecimal (current_row[i]); } public double GetDouble (int i) { - return Convert.ToDouble ((string) current_row [i]); + return Convert.ToDouble (current_row[i]); } public Type GetFieldType (int i) { - return System.Type.GetType ("System.String"); // .NET data type + if (current_row == null) + return null; + + return current_row [i].GetType (); } public float GetFloat (int i) { - return Convert.ToSingle ((string) current_row [i]); + return Convert.ToSingle (current_row[i]); } public Guid GetGuid (int i) @@ -351,45 +385,50 @@ namespace Mono.Data.SqliteClient public short GetInt16 (int i) { - return Convert.ToInt16 ((string) current_row [i]); + return Convert.ToInt16 (current_row[i]); } public int GetInt32 (int i) { - return Convert.ToInt32 ((string) current_row [i]); + return Convert.ToInt32 (current_row[i]); } public long GetInt64 (int i) { - return Convert.ToInt64 ((string) current_row [i]); + return Convert.ToInt64 (current_row[i]); } public string GetName (int i) { - return (string) columns[i]; + return columns[i]; } public int GetOrdinal (string name) { - return (int) column_names[name]; + object v = column_names_sens[name]; + if (v == null) + v = column_names_insens[name]; + if (v == null) + throw new ArgumentException("Column does not exist."); + return (int) v; } public string GetString (int i) { - return (string) current_row [i]; + return current_row[i].ToString(); } public object GetValue (int i) { - return current_row [i]; + return current_row[i]; } public int GetValues (object[] values) { - int num_to_fill = System.Math.Min (values.Length, columns.Count); + int num_to_fill = System.Math.Min (values.Length, columns.Length); for (int i = 0; i < num_to_fill; i++) { - if (current_row [i] != null) { - values[i] = current_row [i]; + if (current_row[i] != null) { + values[i] = current_row[i]; } else { values[i] = DBNull.Value; } @@ -399,7 +438,7 @@ namespace Mono.Data.SqliteClient public bool IsDBNull (int i) { - return current_row [i] == null; + return (current_row[i] == null); } #endregion Index: beagled/Mono.Data.SqliteClient/SqliteParameter.cs =================================================================== RCS file: /cvs/gnome/beagle/beagled/Mono.Data.SqliteClient/SqliteParameter.cs,v retrieving revision 1.1 diff -u -B -p -r1.1 SqliteParameter.cs --- beagled/Mono.Data.SqliteClient/SqliteParameter.cs 7 Jun 2005 20:50:14 -0000 1.1 +++ beagled/Mono.Data.SqliteClient/SqliteParameter.cs 2 Feb 2006 15:20:49 -0000 @@ -59,26 +59,26 @@ namespace Mono.Data.SqliteClient direction = ParameterDirection.Input; } - public SqliteParameter (string name_in, DbType type_in) + public SqliteParameter (string name, DbType type) { - name = name_in; - type = type_in; + this.name = name; + this.type = type; } - public SqliteParameter (string name_in, object param_value_in) + public SqliteParameter (string name, object value) { - name = name_in; + this.name = name; type = DbType.String; - param_value = param_value_in; + param_value = value; direction = ParameterDirection.Input; } - public SqliteParameter (string name_in, DbType type_in, int size_in) : this (name_in, type_in) + public SqliteParameter (string name, DbType type, int size) : this (name, type) { - size = size_in; + this.size = size; } - public SqliteParameter (string name_in, DbType type_in, int size, string src_column) : this (name_in ,type_in) + public SqliteParameter (string name, DbType type, int size, string src_column) : this (name ,type, size) { source_column = src_column; } Index: beagled/Mono.Data.SqliteClient/SqliteParameterCollection.cs =================================================================== RCS file: /cvs/gnome/beagle/beagled/Mono.Data.SqliteClient/SqliteParameterCollection.cs,v retrieving revision 1.2 diff -u -B -p -r1.2 SqliteParameterCollection.cs --- beagled/Mono.Data.SqliteClient/SqliteParameterCollection.cs 24 Oct 2005 21:56:28 -0000 1.2 +++ beagled/Mono.Data.SqliteClient/SqliteParameterCollection.cs 2 Feb 2006 15:20:49 -0000 @@ -112,12 +112,19 @@ namespace Mono.Data.SqliteClient } } + private bool isPrefixed (string parameterName) + { + return parameterName.Length > 1 && (parameterName[0] == ':' || parameterName[0] == '$'); + } + public SqliteParameter this[string parameterName] { get { if (this.Contains(parameterName)) return this[(int) named_param_hash[parameterName]]; + else if (isPrefixed(parameterName) && this.Contains(parameterName.Substring(1))) + return this[(int) named_param_hash[parameterName.Substring(1)]]; else throw new IndexOutOfRangeException("The specified name does not exist: " + parameterName); } @@ -125,6 +132,8 @@ namespace Mono.Data.SqliteClient { if (this.Contains(parameterName)) numeric_param_list[(int) named_param_hash[parameterName]] = value; + else if (parameterName.Length > 1 && this.Contains(parameterName.Substring(1))) + numeric_param_list[(int) named_param_hash[parameterName.Substring(1)]] = value; else throw new IndexOutOfRangeException("The specified name does not exist: " + parameterName); }