Hi,

Previously, SqliteClient did not properly check the return values of some of its calls into the sqlite API.

The sqlite API can return BUSY in some locking-related situations, but this wasn't being handled appropriately, and strange things would happen.

This patch wraps Sqlite errors into a new exception class, which allows the user to respond accordingly to BUSY states. For example, Beagle sleeps a bit and then retries when it sees a SqliteException with BUSY inside it.

This work was done by Jon Trowbridge, but I've been maintaining it and keeping it up-to-date with the more recent SqliteClient changes applied to the tree. After a long period of testing in Beagle I feel its time this should be fixed in the main tree :)

Any comments? Does this look ok for me to commit?

Thanks,
Daniel

Index: SqliteDataReader.cs
===================================================================
--- SqliteDataReader.cs	(revision 53531)
+++ SqliteDataReader.cs	(working copy)
@@ -43,29 +43,30 @@
 		#region Fields
 		
 		private SqliteCommand command;
-		private ArrayList rows;
+		private IntPtr pVm;
+		private int version;
+
+		private ArrayList current_row;
+
 		private ArrayList columns;
 		private Hashtable column_names;
-		private int current_row;
 		private bool closed;
-		private bool reading;
-		private int records_affected;
 		
 		#endregion
 
 		#region Constructors and destructors
 		
-		internal SqliteDataReader (SqliteCommand cmd, IntPtr pVm, int version)
+		internal SqliteDataReader (SqliteCommand cmd, IntPtr _pVm, int _version)
 		{
 			command = cmd;
-			rows = new ArrayList ();
+			pVm = _pVm;
+			version = _version;
+
+			current_row = new ArrayList ();
+
 			columns = new ArrayList ();
 			column_names = new Hashtable ();
 			closed = false;
-			current_row = -1;
-			reading = true;
-			ReadpVm (pVm, version);
-			ReadingDone ();
 		}
 		
 		#endregion
@@ -81,11 +82,11 @@
 		}
 		
 		public object this[string name] {
-			get { return ((ArrayList) rows[current_row])[(int) column_names[name]]; }
+			get { return current_row [(int) column_names[name]]; }
 		}
 		
 		public object this[int i] {
-			get { return ((ArrayList) rows[current_row])[i]; }
+			get { return current_row [i]; }
 		}
 		
 		public bool IsClosed {
@@ -93,90 +94,90 @@
 		}
 		
 		public int RecordsAffected {
-			get { return records_affected; }
+			get { return command.NumChanges (); }
 		}
 		
 		#endregion
 
 		#region Internal Methods
-		
-		internal void ReadpVm (IntPtr pVm, int version)
+
+		internal bool ReadNextColumn ()
 		{
 			int pN = 0;
 			IntPtr pazValue = IntPtr.Zero;
 			IntPtr pazColName = IntPtr.Zero;
 			SqliteError res;
 
-			while (true) {
-				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.ERROR) {		
-					throw new ApplicationException ("Sqlite error");
-				}
-				if (res == SqliteError.DONE) {
-					break;
-				}
-				// We have some data; lets read it
-				if (column_names.Count == 0) {
-					for (int i = 0; i < pN; i++) {
-						string colName = "";
-						if (version == 2) {
-							IntPtr fieldPtr = (IntPtr)Marshal.ReadInt32 (pazColName, i*IntPtr.Size);
-							colName = Marshal.PtrToStringAnsi (fieldPtr);
-						} else {
-							colName = Marshal.PtrToStringAnsi (Sqlite.sqlite3_column_name (pVm, i));
-						}
-						columns.Add (colName);
-						column_names [colName] = i;
-					}
-				}
-				ArrayList data_row = new ArrayList (pN);
+			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
+
+			// If we are reading the first column, populate the column names
+			if (column_names.Count == 0) {
 				for (int i = 0; i < pN; i++) {
-					string colData = "";
+					string colName = "";
 					if (version == 2) {
-						IntPtr fieldPtr = (IntPtr)Marshal.ReadInt32 (pazValue, i*IntPtr.Size);
-						colData = Marshal.PtrToStringAnsi (fieldPtr);
-						data_row.Add (Marshal.PtrToStringAnsi (fieldPtr));
+						IntPtr fieldPtr = (IntPtr)Marshal.ReadInt32 (pazColName, i*IntPtr.Size);
+						colName = Marshal.PtrToStringAnsi (fieldPtr);
 					} else {
-						switch (Sqlite.sqlite3_column_type (pVm, i)) {
-							case 1:
-								Int64 sqliteint64 = Sqlite.sqlite3_column_int64 (pVm, i);
-								data_row.Add (sqliteint64.ToString ());
-								break;
-							case 2:
-								double sqlitedouble = Sqlite.sqlite3_column_double (pVm, i);
-								data_row.Add (sqlitedouble.ToString ());
-								break;
-							case 3:
-								colData = Marshal.PtrToStringAnsi (Sqlite.sqlite3_column_text (pVm, i));
-								data_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);
-								data_row.Add (blob);
-								break;
-							case 5:
-								data_row.Add (null);
-								break;
-							default:
-								throw new ApplicationException ("FATAL: Unknown sqlite3_column_type");
-						}
-					}	
+						colName = Marshal.PtrToStringAnsi (Sqlite.sqlite3_column_name (pVm, i));
+					}
+					columns.Add (colName);
+					column_names [colName] = i;
 				}
-				rows.Add (data_row);
 			}
+
+			// Now read the actual data
+			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));
+				} 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");
+					}
+				}	
+			}
+			
+			return true;
 		}
-		internal void ReadingDone ()
-		{
-			records_affected = command.NumChanges ();
-			reading = false;
-		}
 		
 		#endregion
 
@@ -185,6 +186,15 @@
 		public void Close ()
 		{
 			closed = true;
+
+			if (pVm != IntPtr.Zero) {
+				IntPtr errMsg;
+				if (version == 3)
+					Sqlite.sqlite3_finalize (pVm);
+				else
+					Sqlite.sqlite_finalize (pVm, out errMsg);
+				pVm = IntPtr.Zero;
+			}
 		}
 		
 		public void Dispose ()
@@ -262,14 +272,12 @@
 		
 		public bool NextResult ()
 		{
-			current_row++;
-			
-			return (current_row < rows.Count);
+			return ReadNextColumn ();
 		}
 		
 		public bool Read ()
 		{
-			return NextResult ();
+			return ReadNextColumn ();
 		}
 
 		#endregion
@@ -278,12 +286,12 @@
 		
 		public bool GetBoolean (int i)
 		{
-			return Convert.ToBoolean ((string) ((ArrayList) rows[current_row])[i]);
+			return Convert.ToBoolean ((string) current_row [i]);
 		}
 		
 		public byte GetByte (int i)
 		{
-			return Convert.ToByte ((string) ((ArrayList) rows[current_row])[i]);
+			return Convert.ToByte ((string) current_row [i]);
 		}
 		
 		public long GetBytes (int i, long fieldOffset, byte[] buffer, int bufferOffset, int length)
@@ -293,7 +301,7 @@
 		
 		public char GetChar (int i)
 		{
-			return Convert.ToChar ((string) ((ArrayList) rows[current_row])[i]);
+			return Convert.ToChar ((string) current_row [i]);
 		}
 		
 		public long GetChars (int i, long fieldOffset, char[] buffer, int bufferOffset, int length)
@@ -313,17 +321,17 @@
 		
 		public DateTime GetDateTime (int i)
 		{
-			return Convert.ToDateTime ((string) ((ArrayList) rows[current_row])[i]);
+			return Convert.ToDateTime ((string) current_row [i]);
 		}
 		
 		public decimal GetDecimal (int i)
 		{
-			return Convert.ToDecimal ((string) ((ArrayList) rows[current_row])[i]);
+			return Convert.ToDecimal ((string) current_row [i]);
 		}
 		
 		public double GetDouble (int i)
 		{
-			return Convert.ToDouble ((string) ((ArrayList) rows[current_row])[i]);
+			return Convert.ToDouble ((string) current_row [i]);
 		}
 		
 		public Type GetFieldType (int i)
@@ -333,7 +341,7 @@
 		
 		public float GetFloat (int i)
 		{
-			return Convert.ToSingle ((string) ((ArrayList) rows[current_row])[i]);
+			return Convert.ToSingle ((string) current_row [i]);
 		}
 		
 		public Guid GetGuid (int i)
@@ -343,17 +351,17 @@
 		
 		public short GetInt16 (int i)
 		{
-			return Convert.ToInt16 ((string) ((ArrayList) rows[current_row])[i]);
+			return Convert.ToInt16 ((string) current_row [i]);
 		}
 		
 		public int GetInt32 (int i)
 		{
-			return Convert.ToInt32 ((string) ((ArrayList) rows[current_row])[i]);
+			return Convert.ToInt32 ((string) current_row [i]);
 		}
 		
 		public long GetInt64 (int i)
 		{
-			return Convert.ToInt64 ((string) ((ArrayList) rows[current_row])[i]);
+			return Convert.ToInt64 ((string) current_row [i]);
 		}
 		
 		public string GetName (int i)
@@ -368,20 +376,20 @@
 		
 		public string GetString (int i)
 		{
-			return ((string) ((ArrayList) rows[current_row])[i]);
+			return (string) current_row [i];
 		}
 		
 		public object GetValue (int i)
 		{
-			return ((ArrayList) rows[current_row])[i];
+			return current_row [i];
 		}
 		
 		public int GetValues (object[] values)
 		{
 			int num_to_fill = System.Math.Min (values.Length, columns.Count);
 			for (int i = 0; i < num_to_fill; i++) {
-				if (((ArrayList) rows[current_row])[i] != null) {
-					values[i] = ((ArrayList) rows[current_row])[i];
+				if (current_row [i] != null) {
+					values[i] = current_row [i];
 				} else {
 					values[i] = DBNull.Value;
 				}
@@ -391,7 +399,7 @@
 		
 		public bool IsDBNull (int i)
 		{
-			return (((ArrayList) rows[current_row])[i] == null);
+			return current_row [i] == null;
 		}
 		        
 		#endregion
Index: SqliteCommand.cs
===================================================================
--- SqliteCommand.cs	(revision 53531)
+++ SqliteCommand.cs	(working copy)
@@ -35,7 +35,7 @@
 using System;
 using System.Collections;
 using System.Text;
-using Mono.Unix;
+using Mono.Unix.Native;
 using System.Runtime.InteropServices;
 using System.Text.RegularExpressions;
 using System.Data;
@@ -46,6 +46,7 @@
 {
 	public class SqliteCommand : IDbCommand
 	{
+
 		#region Fields
 		
 		private SqliteConnection parent_conn;
@@ -232,7 +233,7 @@
 			}
 			
 			SqliteError err = SqliteError.OK;
-			IntPtr psql = UnixMarshal.StringToAlloc(sqlcmds);
+			IntPtr psql = UnixMarshal.StringToHeap(sqlcmds);
 			IntPtr pzTail = psql;
 			try {
 				do { // sql may contain multiple sql commands, loop until they're all processed
@@ -242,7 +243,7 @@
 						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 ApplicationException (msg);
+							throw new SqliteException (err, msg);
 						}
 					}
 					else
@@ -258,7 +259,7 @@
 								msg = Marshal.PtrToStringAnsi (errMsg);
 								Sqlite.sqliteFree (errMsg);
 							}
-							throw new ApplicationException ("Sqlite error: " + msg);
+							throw new SqliteException (err, msg);
 						}
 					}
 						
@@ -345,7 +346,7 @@
 					}
 				} while ((int)pzTail - (int)psql < sql.Length);
 			} finally {
-				UnixMarshal.Free(psql);
+				UnixMarshal.FreeHeap(psql);
 			}
 			prepared=true;
 		}
@@ -364,7 +365,8 @@
 		public int ExecuteNonQuery ()
 		{
 			int rows_affected;
-			SqliteDataReader r = ExecuteReader (CommandBehavior.Default, false, out rows_affected);
+			// Since want_results is false, this always returns null
+			ExecuteReader (CommandBehavior.Default, false, out rows_affected);
 			return rows_affected;
 		}
 		
@@ -426,6 +428,7 @@
 					} 
 					
 					// Execute but ignore the results of these statements.
+
 					if (parent_conn.Version == 3) 
 					{
 						err = Sqlite.sqlite3_step (pStmt);
@@ -443,16 +446,18 @@
 			}
 			finally 
 			{	
-				foreach (IntPtr pStmt in pStmts) {
-					if (parent_conn.Version == 3) 
-					{
-						err = Sqlite.sqlite3_finalize (pStmt);
+				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);
+						}
 					}
-					else
-					{
-						err = Sqlite.sqlite_finalize (pStmt, out errMsg);
-					}
-				}
+
 				parent_conn.EndExec ();
 				prepared = false;
 			}
@@ -461,11 +466,11 @@
 			    err != SqliteError.DONE &&
 			    err != SqliteError.ROW) 
 			{
- 				if (errMsg != IntPtr.Zero) 
+				if (errMsg != IntPtr.Zero) 
 				{
 					// TODO: Get the message text
 				}
-				throw new ApplicationException ("Sqlite error");
+				throw new SqliteException (err);
 			}
 			rows_affected = NumChanges ();
 			return reader;
Index: SqliteException.cs
===================================================================
--- SqliteException.cs	(revision 0)
+++ SqliteException.cs	(revision 0)
@@ -0,0 +1,58 @@
+//
+// Mono.Data.SqliteClient.SqliteException.cs
+//
+// An exception that wraps an SqliteError value.
+// 
+// Author(s): Jon Trowbridge <[EMAIL PROTECTED]>
+//
+// 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.
+//
+
+using System;
+
+namespace Mono.Data.SqliteClient
+{
+	public class SqliteException : Exception
+	{
+		public readonly SqliteError SqliteError;
+
+		private string message;
+
+		public SqliteException (SqliteError err, string message)
+		{
+			this.SqliteError = err;
+
+			if (message == null)
+				message = String.Format ("Sqlite returned '{0}'", err);
+			else
+				message = String.Format ("Sqlite returned '{0}': {1}", err, message);
+			this.message = message;
+		}
+
+		public SqliteException (SqliteError err) : this (err, null)
+		{
+
+		}
+
+		override public string Message {
+			get { return message; }
+		}
+	}
+}
Index: Sqlite.cs
===================================================================
--- Sqlite.cs	(revision 53531)
+++ Sqlite.cs	(working copy)
@@ -39,7 +39,7 @@
 	/// <summary>
 	/// Represents the return values for sqlite_exec() and sqlite_step()
 	/// </summary>
-	internal enum SqliteError : int {
+	public enum SqliteError : int {
 		/// <value>Successful result</value>
 		OK        =  0,
 		/// <value>SQL error or missing database</value>
_______________________________________________
Mono-devel-list mailing list
Mono-devel-list@lists.ximian.com
http://lists.ximian.com/mailman/listinfo/mono-devel-list

Reply via email to