Index: SQLite.Interop/src/win/interop.c ================================================================== --- SQLite.Interop/src/win/interop.c +++ SQLite.Interop/src/win/interop.c @@ -389,15 +389,10 @@ pModule->xSavepoint = xSavepoint; pModule->xRelease = xRelease; pModule->xRollbackTo = xRollbackTo; return sqlite3_create_disposable_module(db, zName, pModule, pClientData, xDestroyModule); } - -SQLITE_API void WINAPI sqlite3_dispose_module_interop(void *pModule) -{ - sqlite3_dispose_module(pModule); -} #endif SQLITE_API int WINAPI sqlite3_bind_double_interop(sqlite3_stmt *stmt, int iCol, double *val) { return sqlite3_bind_double(stmt,iCol,*val); Index: System.Data.SQLite/SQLite3.cs ================================================================== --- System.Data.SQLite/SQLite3.cs +++ System.Data.SQLite/SQLite3.cs @@ -159,41 +159,57 @@ ////////////////////////////////////// // release unmanaged resources here... ////////////////////////////////////// #if INTEROP_VIRTUAL_TABLE - // - // NOTE: If any modules were created, attempt to dispose of - // them now. This code is designed to avoid throwing - // exceptions unless the Dispose method of the module - // itself throws an exception. - // - if (_modules != null) - { - foreach (KeyValuePair pair in _modules) - { - SQLiteModule module = pair.Value; - - if (module == null) - continue; - - module.Dispose(); - } - } + DisposeModules(); #endif Close(false); /* Disposing, cannot throw. */ - - disposed = true; } } finally { base.Dispose(disposing); + + // + // NOTE: Everything should be fully disposed at this point. + // + disposed = true; } } #endregion + + /////////////////////////////////////////////////////////////////////////////////////////////// + +#if INTEROP_VIRTUAL_TABLE + /// + /// This method attempts to dispose of all the derived + /// object instances currently associated with the native database connection. + /// + private void DisposeModules() + { + // + // NOTE: If any modules were created, attempt to dispose of + // them now. This code is designed to avoid throwing + // exceptions unless the Dispose method of the module + // itself throws an exception. + // + if (_modules != null) + { + foreach (KeyValuePair pair in _modules) + { + SQLiteModule module = pair.Value; + + if (module == null) + continue; + + module.Dispose(); + } + } + } +#endif /////////////////////////////////////////////////////////////////////////////////////////////// // It isn't necessary to cleanup any functions we've registered. If the connection // goes to the pool and is resurrected later, re-registered functions will overwrite the @@ -211,10 +227,14 @@ if (_usePool) { if (SQLiteBase.ResetConnection(_sql, _sql, canThrow)) { +#if INTEROP_VIRTUAL_TABLE + DisposeModules(); +#endif + SQLiteConnectionPool.Add(_fileName, _sql, _poolVersion); #if !NET_COMPACT_20 && TRACE_CONNECTION Trace.WriteLine(String.Format("Close (Pool) Success: {0}", _sql)); #endif @@ -1709,61 +1729,20 @@ throw new SQLiteException("connection has an invalid handle"); SetLoadExtension(true); LoadExtension(UnsafeNativeMethods.SQLITE_DLL, "sqlite3_vtshim_init"); - IntPtr pName = IntPtr.Zero; - - try - { - pName = SQLiteString.Utf8IntPtrFromString(module.Name); - - UnsafeNativeMethods.sqlite3_module nativeModule = - module.CreateNativeModule(); - -#if !PLATFORM_COMPACTFRAMEWORK - if (UnsafeNativeMethods.sqlite3_create_disposable_module( - _sql, pName, ref nativeModule, IntPtr.Zero, - null) != IntPtr.Zero) -#elif !SQLITE_STANDARD - if (UnsafeNativeMethods.sqlite3_create_disposable_module_interop( - _sql, pName, module.CreateNativeModuleInterop(), - nativeModule.iVersion, nativeModule.xCreate, - nativeModule.xConnect, nativeModule.xBestIndex, - nativeModule.xDisconnect, nativeModule.xDestroy, - nativeModule.xOpen, nativeModule.xClose, - nativeModule.xFilter, nativeModule.xNext, - nativeModule.xEof, nativeModule.xColumn, - nativeModule.xRowId, nativeModule.xUpdate, - nativeModule.xBegin, nativeModule.xSync, - nativeModule.xCommit, nativeModule.xRollback, - nativeModule.xFindFunction, nativeModule.xRename, - nativeModule.xSavepoint, nativeModule.xRelease, - nativeModule.xRollbackTo, IntPtr.Zero, null) != IntPtr.Zero) -#else - throw new NotImplementedException(); -#endif -#if !PLATFORM_COMPACTFRAMEWORK || !SQLITE_STANDARD - { - if (_modules == null) - _modules = new Dictionary(); - - _modules.Add(module.Name, module); - } - else - { - throw new SQLiteException(GetLastError()); - } -#endif - } - finally - { - if (pName != IntPtr.Zero) - { - SQLiteMemory.Free(pName); - pName = IntPtr.Zero; - } + if (module.CreateDisposableModule(_sql)) + { + if (_modules == null) + _modules = new Dictionary(); + + _modules.Add(module.Name, module); + } + else + { + throw new SQLiteException(GetLastError()); } } /// /// Calls the native SQLite core library in order to cleanup the resources Index: System.Data.SQLite/SQLite3_UTF16.cs ================================================================== --- System.Data.SQLite/SQLite3_UTF16.cs +++ System.Data.SQLite/SQLite3_UTF16.cs @@ -88,17 +88,20 @@ //} ////////////////////////////////////// // release unmanaged resources here... ////////////////////////////////////// - - disposed = true; } } finally { base.Dispose(disposing); + + // + // NOTE: Everything should be fully disposed at this point. + // + disposed = true; } } #endregion /////////////////////////////////////////////////////////////////////////////////////////////// Index: System.Data.SQLite/SQLiteCommand.cs ================================================================== --- System.Data.SQLite/SQLiteCommand.cs +++ System.Data.SQLite/SQLiteCommand.cs @@ -184,10 +184,12 @@ /// Disposes of the command and clears all member variables /// /// Whether or not the class is being explicitly or implicitly disposed protected override void Dispose(bool disposing) { + bool skippedDispose = false; + try { if (!disposed) { if (disposing) @@ -211,10 +213,11 @@ if (reader != null) { reader._disposeCommand = true; _activeReader = null; + skippedDispose = true; return; } Connection = null; _parameterCollection.Clear(); @@ -222,17 +225,23 @@ } ////////////////////////////////////// // release unmanaged resources here... ////////////////////////////////////// - - disposed = true; } } finally { - base.Dispose(disposing); + if (!skippedDispose) + { + base.Dispose(disposing); + + // + // NOTE: Everything should be fully disposed at this point. + // + disposed = true; + } } } #endregion /////////////////////////////////////////////////////////////////////////////////////////////// Index: System.Data.SQLite/SQLiteCommandBuilder.cs ================================================================== --- System.Data.SQLite/SQLiteCommandBuilder.cs +++ System.Data.SQLite/SQLiteCommandBuilder.cs @@ -1,41 +1,41 @@ -/******************************************************** - * 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 System.Data.SQLite -{ - using System; - using System.Data; - using System.Data.Common; - using System.Globalization; - using System.ComponentModel; - - /// - /// SQLite implementation of DbCommandBuilder. - /// - public sealed class SQLiteCommandBuilder : DbCommandBuilder - { - /// - /// Default constructor - /// - public SQLiteCommandBuilder() : this(null) - { - } - - /// - /// Initializes the command builder and associates it with the specified data adapter. - /// - /// - public SQLiteCommandBuilder(SQLiteDataAdapter adp) - { - QuotePrefix = "["; - QuoteSuffix = "]"; - DataAdapter = adp; +/******************************************************** + * 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 System.Data.SQLite +{ + using System; + using System.Data; + using System.Data.Common; + using System.Globalization; + using System.ComponentModel; + + /// + /// SQLite implementation of DbCommandBuilder. + /// + public sealed class SQLiteCommandBuilder : DbCommandBuilder + { + /// + /// Default constructor + /// + public SQLiteCommandBuilder() : this(null) + { + } + + /// + /// Initializes the command builder and associates it with the specified data adapter. + /// + /// + public SQLiteCommandBuilder(SQLiteDataAdapter adp) + { + QuotePrefix = "["; + QuoteSuffix = "]"; + DataAdapter = adp; } /////////////////////////////////////////////////////////////////////////////////////////////// #region IDisposable "Pattern" Members @@ -64,343 +64,346 @@ //} ////////////////////////////////////// // release unmanaged resources here... ////////////////////////////////////// - - disposed = true; } } finally { base.Dispose(disposing); + + // + // NOTE: Everything should be fully disposed at this point. + // + disposed = true; } } #endregion /////////////////////////////////////////////////////////////////////////////////////////////// - - /// - /// Minimal amount of parameter processing. Primarily sets the DbType for the parameter equal to the provider type in the schema - /// - /// The parameter to use in applying custom behaviors to a row - /// The row to apply the parameter to - /// The type of statement - /// Whether the application of the parameter is part of a WHERE clause - protected override void ApplyParameterInfo(DbParameter parameter, DataRow row, StatementType statementType, bool whereClause) - { - SQLiteParameter param = (SQLiteParameter)parameter; - param.DbType = (DbType)row[SchemaTableColumn.ProviderType]; - } - - /// - /// Returns a valid named parameter - /// - /// The name of the parameter - /// Error - protected override string GetParameterName(string parameterName) - { - return String.Format(CultureInfo.InvariantCulture, "@{0}", parameterName); - } - - /// - /// Returns a named parameter for the given ordinal - /// - /// The i of the parameter - /// Error - protected override string GetParameterName(int parameterOrdinal) - { - return String.Format(CultureInfo.InvariantCulture, "@param{0}", parameterOrdinal); - } - - /// - /// Returns a placeholder character for the specified parameter i. - /// - /// The index of the parameter to provide a placeholder for - /// Returns a named parameter - protected override string GetParameterPlaceholder(int parameterOrdinal) - { - return GetParameterName(parameterOrdinal); - } - - /// - /// Sets the handler for receiving row updating events. Used by the DbCommandBuilder to autogenerate SQL - /// statements that may not have previously been generated. - /// - /// A data adapter to receive events on. - protected override void SetRowUpdatingHandler(DbDataAdapter adapter) - { - if (adapter == base.DataAdapter) - { - ((SQLiteDataAdapter)adapter).RowUpdating -= new EventHandler(RowUpdatingEventHandler); - } - else - { - ((SQLiteDataAdapter)adapter).RowUpdating += new EventHandler(RowUpdatingEventHandler); - } - } - - private void RowUpdatingEventHandler(object sender, RowUpdatingEventArgs e) - { - base.RowUpdatingHandler(e); - } - - /// - /// Gets/sets the DataAdapter for this CommandBuilder - /// - public new SQLiteDataAdapter DataAdapter + + /// + /// Minimal amount of parameter processing. Primarily sets the DbType for the parameter equal to the provider type in the schema + /// + /// The parameter to use in applying custom behaviors to a row + /// The row to apply the parameter to + /// The type of statement + /// Whether the application of the parameter is part of a WHERE clause + protected override void ApplyParameterInfo(DbParameter parameter, DataRow row, StatementType statementType, bool whereClause) + { + SQLiteParameter param = (SQLiteParameter)parameter; + param.DbType = (DbType)row[SchemaTableColumn.ProviderType]; + } + + /// + /// Returns a valid named parameter + /// + /// The name of the parameter + /// Error + protected override string GetParameterName(string parameterName) + { + return String.Format(CultureInfo.InvariantCulture, "@{0}", parameterName); + } + + /// + /// Returns a named parameter for the given ordinal + /// + /// The i of the parameter + /// Error + protected override string GetParameterName(int parameterOrdinal) + { + return String.Format(CultureInfo.InvariantCulture, "@param{0}", parameterOrdinal); + } + + /// + /// Returns a placeholder character for the specified parameter i. + /// + /// The index of the parameter to provide a placeholder for + /// Returns a named parameter + protected override string GetParameterPlaceholder(int parameterOrdinal) + { + return GetParameterName(parameterOrdinal); + } + + /// + /// Sets the handler for receiving row updating events. Used by the DbCommandBuilder to autogenerate SQL + /// statements that may not have previously been generated. + /// + /// A data adapter to receive events on. + protected override void SetRowUpdatingHandler(DbDataAdapter adapter) + { + if (adapter == base.DataAdapter) + { + ((SQLiteDataAdapter)adapter).RowUpdating -= new EventHandler(RowUpdatingEventHandler); + } + else + { + ((SQLiteDataAdapter)adapter).RowUpdating += new EventHandler(RowUpdatingEventHandler); + } + } + + private void RowUpdatingEventHandler(object sender, RowUpdatingEventArgs e) + { + base.RowUpdatingHandler(e); + } + + /// + /// Gets/sets the DataAdapter for this CommandBuilder + /// + public new SQLiteDataAdapter DataAdapter { get { CheckDisposed(); return (SQLiteDataAdapter)base.DataAdapter; } - set { CheckDisposed(); base.DataAdapter = value; } - } - - /// - /// Returns the automatically-generated SQLite command to delete rows from the database - /// - /// + set { CheckDisposed(); base.DataAdapter = value; } + } + + /// + /// Returns the automatically-generated SQLite command to delete rows from the database + /// + /// public new SQLiteCommand GetDeleteCommand() { - CheckDisposed(); - return (SQLiteCommand)base.GetDeleteCommand(); - } - - /// - /// Returns the automatically-generated SQLite command to delete rows from the database - /// - /// - /// - public new SQLiteCommand GetDeleteCommand(bool useColumnsForParameterNames) - { - CheckDisposed(); - return (SQLiteCommand)base.GetDeleteCommand(useColumnsForParameterNames); - } - - /// - /// Returns the automatically-generated SQLite command to update rows in the database - /// - /// + CheckDisposed(); + return (SQLiteCommand)base.GetDeleteCommand(); + } + + /// + /// Returns the automatically-generated SQLite command to delete rows from the database + /// + /// + /// + public new SQLiteCommand GetDeleteCommand(bool useColumnsForParameterNames) + { + CheckDisposed(); + return (SQLiteCommand)base.GetDeleteCommand(useColumnsForParameterNames); + } + + /// + /// Returns the automatically-generated SQLite command to update rows in the database + /// + /// public new SQLiteCommand GetUpdateCommand() { - CheckDisposed(); - return (SQLiteCommand)base.GetUpdateCommand(); - } - - /// - /// Returns the automatically-generated SQLite command to update rows in the database - /// - /// - /// - public new SQLiteCommand GetUpdateCommand(bool useColumnsForParameterNames) - { - CheckDisposed(); - return (SQLiteCommand)base.GetUpdateCommand(useColumnsForParameterNames); - } - - /// - /// Returns the automatically-generated SQLite command to insert rows into the database - /// - /// - public new SQLiteCommand GetInsertCommand() - { - CheckDisposed(); - return (SQLiteCommand)base.GetInsertCommand(); - } - - /// - /// Returns the automatically-generated SQLite command to insert rows into the database - /// - /// - /// - public new SQLiteCommand GetInsertCommand(bool useColumnsForParameterNames) - { - CheckDisposed(); - return (SQLiteCommand)base.GetInsertCommand(useColumnsForParameterNames); - } - - /// - /// Overridden to hide its property from the designer - /// -#if !PLATFORM_COMPACTFRAMEWORK - [Browsable(false)] -#endif - public override CatalogLocation CatalogLocation - { - get - { - CheckDisposed(); - return base.CatalogLocation; - } - set - { - CheckDisposed(); - base.CatalogLocation = value; - } - } - - /// - /// Overridden to hide its property from the designer - /// -#if !PLATFORM_COMPACTFRAMEWORK - [Browsable(false)] -#endif - public override string CatalogSeparator - { - get - { - CheckDisposed(); - return base.CatalogSeparator; - } - set - { - CheckDisposed(); - base.CatalogSeparator = value; - } - } - - /// - /// Overridden to hide its property from the designer - /// -#if !PLATFORM_COMPACTFRAMEWORK - [Browsable(false)] -#endif - [DefaultValue("[")] - public override string QuotePrefix - { - get - { - CheckDisposed(); - return base.QuotePrefix; - } - set - { - CheckDisposed(); - base.QuotePrefix = value; - } - } - - /// - /// Overridden to hide its property from the designer - /// -#if !PLATFORM_COMPACTFRAMEWORK - [Browsable(false)] -#endif - public override string QuoteSuffix - { - get - { - CheckDisposed(); - return base.QuoteSuffix; - } - set - { - CheckDisposed(); - base.QuoteSuffix = value; - } - } - - /// - /// Places brackets around an identifier - /// - /// The identifier to quote - /// The bracketed identifier + CheckDisposed(); + return (SQLiteCommand)base.GetUpdateCommand(); + } + + /// + /// Returns the automatically-generated SQLite command to update rows in the database + /// + /// + /// + public new SQLiteCommand GetUpdateCommand(bool useColumnsForParameterNames) + { + CheckDisposed(); + return (SQLiteCommand)base.GetUpdateCommand(useColumnsForParameterNames); + } + + /// + /// Returns the automatically-generated SQLite command to insert rows into the database + /// + /// + public new SQLiteCommand GetInsertCommand() + { + CheckDisposed(); + return (SQLiteCommand)base.GetInsertCommand(); + } + + /// + /// Returns the automatically-generated SQLite command to insert rows into the database + /// + /// + /// + public new SQLiteCommand GetInsertCommand(bool useColumnsForParameterNames) + { + CheckDisposed(); + return (SQLiteCommand)base.GetInsertCommand(useColumnsForParameterNames); + } + + /// + /// Overridden to hide its property from the designer + /// +#if !PLATFORM_COMPACTFRAMEWORK + [Browsable(false)] +#endif + public override CatalogLocation CatalogLocation + { + get + { + CheckDisposed(); + return base.CatalogLocation; + } + set + { + CheckDisposed(); + base.CatalogLocation = value; + } + } + + /// + /// Overridden to hide its property from the designer + /// +#if !PLATFORM_COMPACTFRAMEWORK + [Browsable(false)] +#endif + public override string CatalogSeparator + { + get + { + CheckDisposed(); + return base.CatalogSeparator; + } + set + { + CheckDisposed(); + base.CatalogSeparator = value; + } + } + + /// + /// Overridden to hide its property from the designer + /// +#if !PLATFORM_COMPACTFRAMEWORK + [Browsable(false)] +#endif + [DefaultValue("[")] + public override string QuotePrefix + { + get + { + CheckDisposed(); + return base.QuotePrefix; + } + set + { + CheckDisposed(); + base.QuotePrefix = value; + } + } + + /// + /// Overridden to hide its property from the designer + /// +#if !PLATFORM_COMPACTFRAMEWORK + [Browsable(false)] +#endif + public override string QuoteSuffix + { + get + { + CheckDisposed(); + return base.QuoteSuffix; + } + set + { + CheckDisposed(); + base.QuoteSuffix = value; + } + } + + /// + /// Places brackets around an identifier + /// + /// The identifier to quote + /// The bracketed identifier public override string QuoteIdentifier(string unquotedIdentifier) { - CheckDisposed(); - - if (String.IsNullOrEmpty(QuotePrefix) - || String.IsNullOrEmpty(QuoteSuffix) - || String.IsNullOrEmpty(unquotedIdentifier)) - return unquotedIdentifier; - - return QuotePrefix + unquotedIdentifier.Replace(QuoteSuffix, QuoteSuffix + QuoteSuffix) + QuoteSuffix; - } - - /// - /// Removes brackets around an identifier - /// - /// The quoted (bracketed) identifier - /// The undecorated identifier + CheckDisposed(); + + if (String.IsNullOrEmpty(QuotePrefix) + || String.IsNullOrEmpty(QuoteSuffix) + || String.IsNullOrEmpty(unquotedIdentifier)) + return unquotedIdentifier; + + return QuotePrefix + unquotedIdentifier.Replace(QuoteSuffix, QuoteSuffix + QuoteSuffix) + QuoteSuffix; + } + + /// + /// Removes brackets around an identifier + /// + /// The quoted (bracketed) identifier + /// The undecorated identifier public override string UnquoteIdentifier(string quotedIdentifier) { - CheckDisposed(); - - if (String.IsNullOrEmpty(QuotePrefix) - || String.IsNullOrEmpty(QuoteSuffix) - || String.IsNullOrEmpty(quotedIdentifier)) - return quotedIdentifier; - - if (quotedIdentifier.StartsWith(QuotePrefix, StringComparison.OrdinalIgnoreCase) == false - || quotedIdentifier.EndsWith(QuoteSuffix, StringComparison.OrdinalIgnoreCase) == false) - return quotedIdentifier; - - return quotedIdentifier.Substring(QuotePrefix.Length, quotedIdentifier.Length - (QuotePrefix.Length + QuoteSuffix.Length)).Replace(QuoteSuffix + QuoteSuffix, QuoteSuffix); - } - - /// - /// Overridden to hide its property from the designer - /// -#if !PLATFORM_COMPACTFRAMEWORK - [Browsable(false)] -#endif - public override string SchemaSeparator - { - get - { - CheckDisposed(); - return base.SchemaSeparator; - } + CheckDisposed(); + + if (String.IsNullOrEmpty(QuotePrefix) + || String.IsNullOrEmpty(QuoteSuffix) + || String.IsNullOrEmpty(quotedIdentifier)) + return quotedIdentifier; + + if (quotedIdentifier.StartsWith(QuotePrefix, StringComparison.OrdinalIgnoreCase) == false + || quotedIdentifier.EndsWith(QuoteSuffix, StringComparison.OrdinalIgnoreCase) == false) + return quotedIdentifier; + + return quotedIdentifier.Substring(QuotePrefix.Length, quotedIdentifier.Length - (QuotePrefix.Length + QuoteSuffix.Length)).Replace(QuoteSuffix + QuoteSuffix, QuoteSuffix); + } + + /// + /// Overridden to hide its property from the designer + /// +#if !PLATFORM_COMPACTFRAMEWORK + [Browsable(false)] +#endif + public override string SchemaSeparator + { + get + { + CheckDisposed(); + return base.SchemaSeparator; + } set { - CheckDisposed(); - base.SchemaSeparator = value; - } - } - - /// - /// Override helper, which can help the base command builder choose the right keys for the given query - /// - /// - /// - protected override DataTable GetSchemaTable(DbCommand sourceCommand) - { - using (IDataReader reader = sourceCommand.ExecuteReader(CommandBehavior.KeyInfo | CommandBehavior.SchemaOnly)) - { - DataTable schema = reader.GetSchemaTable(); - - // If the query contains a primary key, turn off the IsUnique property - // for all the non-key columns - if (HasSchemaPrimaryKey(schema)) - ResetIsUniqueSchemaColumn(schema); - - // if table has no primary key we use unique columns as a fall back - return schema; - } - } - - private bool HasSchemaPrimaryKey(DataTable schema) - { - DataColumn IsKeyColumn = schema.Columns[SchemaTableColumn.IsKey]; - - foreach (DataRow schemaRow in schema.Rows) - { - if ((bool)schemaRow[IsKeyColumn] == true) - return true; - } - - return false; - } - - private void ResetIsUniqueSchemaColumn(DataTable schema) - { - DataColumn IsUniqueColumn = schema.Columns[SchemaTableColumn.IsUnique]; - DataColumn IsKeyColumn = schema.Columns[SchemaTableColumn.IsKey]; - - foreach (DataRow schemaRow in schema.Rows) - { - if ((bool)schemaRow[IsKeyColumn] == false) - schemaRow[IsUniqueColumn] = false; - } - - schema.AcceptChanges(); - } - } -} + CheckDisposed(); + base.SchemaSeparator = value; + } + } + + /// + /// Override helper, which can help the base command builder choose the right keys for the given query + /// + /// + /// + protected override DataTable GetSchemaTable(DbCommand sourceCommand) + { + using (IDataReader reader = sourceCommand.ExecuteReader(CommandBehavior.KeyInfo | CommandBehavior.SchemaOnly)) + { + DataTable schema = reader.GetSchemaTable(); + + // If the query contains a primary key, turn off the IsUnique property + // for all the non-key columns + if (HasSchemaPrimaryKey(schema)) + ResetIsUniqueSchemaColumn(schema); + + // if table has no primary key we use unique columns as a fall back + return schema; + } + } + + private bool HasSchemaPrimaryKey(DataTable schema) + { + DataColumn IsKeyColumn = schema.Columns[SchemaTableColumn.IsKey]; + + foreach (DataRow schemaRow in schema.Rows) + { + if ((bool)schemaRow[IsKeyColumn] == true) + return true; + } + + return false; + } + + private void ResetIsUniqueSchemaColumn(DataTable schema) + { + DataColumn IsUniqueColumn = schema.Columns[SchemaTableColumn.IsUnique]; + DataColumn IsKeyColumn = schema.Columns[SchemaTableColumn.IsKey]; + + foreach (DataRow schemaRow in schema.Rows) + { + if ((bool)schemaRow[IsKeyColumn] == false) + schemaRow[IsUniqueColumn] = false; + } + + schema.AcceptChanges(); + } + } +} Index: System.Data.SQLite/SQLiteConnection.cs ================================================================== --- System.Data.SQLite/SQLiteConnection.cs +++ System.Data.SQLite/SQLiteConnection.cs @@ -976,17 +976,20 @@ ////////////////////////////////////// // release unmanaged resources here... ////////////////////////////////////// Close(); - - disposed = true; } } finally { base.Dispose(disposing); + + // + // NOTE: Everything should be fully disposed at this point. + // + disposed = true; } } #endregion /////////////////////////////////////////////////////////////////////////////////////////////// Index: System.Data.SQLite/SQLiteDataAdapter.cs ================================================================== --- System.Data.SQLite/SQLiteDataAdapter.cs +++ System.Data.SQLite/SQLiteDataAdapter.cs @@ -177,17 +177,20 @@ } ////////////////////////////////////// // release unmanaged resources here... ////////////////////////////////////// - - disposed = true; } } finally { base.Dispose(disposing); + + // + // NOTE: Everything should be fully disposed at this point. + // + disposed = true; } } #endregion /////////////////////////////////////////////////////////////////////////////////////////////// Index: System.Data.SQLite/SQLiteDataReader.cs ================================================================== --- System.Data.SQLite/SQLiteDataReader.cs +++ System.Data.SQLite/SQLiteDataReader.cs @@ -146,16 +146,20 @@ // // NOTE: Fix for ticket [e1b2e0f769], do NOT throw exceptions // while we are being disposed. // _throwOnDisposed = false; - disposed = true; } } finally { base.Dispose(disposing); + + // + // NOTE: Everything should be fully disposed at this point. + // + disposed = true; } } #endregion /////////////////////////////////////////////////////////////////////////////////////////////// Index: System.Data.SQLite/SQLiteFunction.cs ================================================================== --- System.Data.SQLite/SQLiteFunction.cs +++ System.Data.SQLite/SQLiteFunction.cs @@ -885,17 +885,20 @@ //} ////////////////////////////////////// // release unmanaged resources here... ////////////////////////////////////// - - disposed = true; } } finally { base.Dispose(disposing); + + // + // NOTE: Everything should be fully disposed at this point. + // + disposed = true; } } #endregion } Index: System.Data.SQLite/SQLiteModule.cs ================================================================== --- System.Data.SQLite/SQLiteModule.cs +++ System.Data.SQLite/SQLiteModule.cs @@ -1102,50 +1102,55 @@ int offset = 0; int nConstraint = SQLiteMarshal.ReadInt32(pIndex, offset); - offset += sizeof(int); + offset += SQLiteMarshal.SizeOfStructInt(); IntPtr pConstraint = SQLiteMarshal.ReadIntPtr(pIndex, offset); offset += IntPtr.Size; int nOrderBy = SQLiteMarshal.ReadInt32(pIndex, offset); - offset += sizeof(int); + offset += SQLiteMarshal.SizeOfStructInt(); IntPtr pOrderBy = SQLiteMarshal.ReadIntPtr(pIndex, offset); index = new SQLiteIndex(nConstraint, nOrderBy); - int sizeOfConstraintType = Marshal.SizeOf(typeof( - UnsafeNativeMethods.sqlite3_index_constraint)); + Type indexConstraintType = typeof( + UnsafeNativeMethods.sqlite3_index_constraint); + + int sizeOfConstraintType = Marshal.SizeOf(indexConstraintType); for (int iConstraint = 0; iConstraint < nConstraint; iConstraint++) { + IntPtr pOffset = SQLiteMarshal.IntPtrForOffset( + pConstraint, iConstraint * sizeOfConstraintType); + UnsafeNativeMethods.sqlite3_index_constraint constraint = - new UnsafeNativeMethods.sqlite3_index_constraint(); - - Marshal.PtrToStructure(SQLiteMarshal.IntPtrForOffset( - pConstraint, iConstraint * sizeOfConstraintType), - constraint); + (UnsafeNativeMethods.sqlite3_index_constraint) + Marshal.PtrToStructure(pOffset, indexConstraintType); index.Inputs.Constraints[iConstraint] = new SQLiteIndexConstraint(constraint); } - int sizeOfOrderByType = Marshal.SizeOf(typeof( - UnsafeNativeMethods.sqlite3_index_orderby)); + Type indexOrderByType = typeof( + UnsafeNativeMethods.sqlite3_index_orderby); + + int sizeOfOrderByType = Marshal.SizeOf(indexOrderByType); for (int iOrderBy = 0; iOrderBy < nOrderBy; iOrderBy++) { + IntPtr pOffset = SQLiteMarshal.IntPtrForOffset( + pOrderBy, iOrderBy * sizeOfOrderByType); + UnsafeNativeMethods.sqlite3_index_orderby orderBy = - new UnsafeNativeMethods.sqlite3_index_orderby(); - - Marshal.PtrToStructure(SQLiteMarshal.IntPtrForOffset( - pOrderBy, iOrderBy * sizeOfOrderByType), orderBy); + (UnsafeNativeMethods.sqlite3_index_orderby) + Marshal.PtrToStructure(pOffset, indexOrderByType); index.Inputs.OrderBys[iOrderBy] = new SQLiteIndexOrderBy(orderBy); } } @@ -1189,11 +1194,12 @@ return; if (nConstraint != index.Outputs.ConstraintUsages.Length) return; - offset += sizeof(int) + IntPtr.Size + sizeof(int) + IntPtr.Size; + offset += SQLiteMarshal.SizeOfStructInt() + IntPtr.Size + + SQLiteMarshal.SizeOfStructInt() + IntPtr.Size; IntPtr pConstraintUsage = SQLiteMarshal.ReadIntPtr(pIndex, offset); int sizeOfConstraintUsageType = Marshal.SizeOf(typeof( UnsafeNativeMethods.sqlite3_index_constraint_usage)); @@ -1206,21 +1212,18 @@ Marshal.StructureToPtr( constraintUsage, SQLiteMarshal.IntPtrForOffset( pConstraintUsage, iConstraint * sizeOfConstraintUsageType), false); - - index.Outputs.ConstraintUsages[iConstraint] = - new SQLiteIndexConstraintUsage(constraintUsage); } offset += IntPtr.Size; SQLiteMarshal.WriteInt32(pIndex, offset, index.Outputs.IndexNumber); - offset += sizeof(int); + offset += SQLiteMarshal.SizeOfStructInt(); SQLiteMarshal.WriteIntPtr(pIndex, offset, SQLiteString.Utf8IntPtrFromString(index.Outputs.IndexString)); offset += IntPtr.Size; @@ -1229,16 +1232,16 @@ // NOTE: We just allocated the IndexString field; therefore, we // need to set the NeedToFreeIndexString field to non-zero. // SQLiteMarshal.WriteInt32(pIndex, offset, 1); - offset += sizeof(int); + offset += SQLiteMarshal.SizeOfStructInt(); SQLiteMarshal.WriteInt32(pIndex, offset, index.Outputs.OrderByConsumed); - offset += sizeof(int); + offset += SQLiteMarshal.SizeOfStructInt(); SQLiteMarshal.WriteDouble(pIndex, offset, index.Outputs.EstimatedCost); } #endregion @@ -4105,10 +4108,25 @@ int offset ) { return new IntPtr(pointer.ToInt64() + offset); } + + /////////////////////////////////////////////////////////////////////// + + /// + /// Determines the size of a when it resides + /// inside of a native structure. + /// + /// + /// The size of the type, in bytes, when it + /// resides inside a native structure. + /// + public static int SizeOfStructInt() + { + return IntPtr.Size; + } #endregion /////////////////////////////////////////////////////////////////////// #region Marshal Read Helper Methods @@ -5164,10 +5182,19 @@ /// private UnsafeNativeMethods.sqlite3_module nativeModule; /////////////////////////////////////////////////////////////////////// + /// + /// This field is used to store a pointer to the native sqlite3_module + /// structure returned by the sqlite3_create_disposable_module + /// function. + /// + private IntPtr disposableModule; + + /////////////////////////////////////////////////////////////////////// + #if PLATFORM_COMPACTFRAMEWORK /// /// This field is used to hold the block of native memory that contains /// the native sqlite3_module structure associated with this object /// instance when running on the .NET Compact Framework. @@ -5227,10 +5254,78 @@ #endregion /////////////////////////////////////////////////////////////////////// #region Internal Methods + /// + /// Calls the native SQLite core library in order to create a new + /// disposable module containing the implementation of a virtual table. + /// + /// + /// The native database connection pointer to use. + /// + /// + /// Non-zero upon success. + /// + internal bool CreateDisposableModule( + IntPtr pDb + ) + { + if (disposableModule != IntPtr.Zero) + return true; + + IntPtr pName = IntPtr.Zero; + + try + { + pName = SQLiteString.Utf8IntPtrFromString(name); + + UnsafeNativeMethods.sqlite3_module nativeModule = + AllocateNativeModule(); + +#if !PLATFORM_COMPACTFRAMEWORK + disposableModule = + UnsafeNativeMethods.sqlite3_create_disposable_module( + pDb, pName, ref nativeModule, IntPtr.Zero, null); + + return (disposableModule != IntPtr.Zero); +#elif !SQLITE_STANDARD + disposableModule = + UnsafeNativeMethods.sqlite3_create_disposable_module_interop( + pDb, pName, AllocateNativeModuleInterop(), + nativeModule.iVersion, nativeModule.xCreate, + nativeModule.xConnect, nativeModule.xBestIndex, + nativeModule.xDisconnect, nativeModule.xDestroy, + nativeModule.xOpen, nativeModule.xClose, + nativeModule.xFilter, nativeModule.xNext, + nativeModule.xEof, nativeModule.xColumn, + nativeModule.xRowId, nativeModule.xUpdate, + nativeModule.xBegin, nativeModule.xSync, + nativeModule.xCommit, nativeModule.xRollback, + nativeModule.xFindFunction, nativeModule.xRename, + nativeModule.xSavepoint, nativeModule.xRelease, + nativeModule.xRollbackTo, IntPtr.Zero, null); + + return (disposableModule != IntPtr.Zero); +#else + throw new NotImplementedException(); +#endif + } + finally + { + if (pName != IntPtr.Zero) + { + SQLiteMemory.Free(pName); + pName = IntPtr.Zero; + } + } + } + #endregion + + /////////////////////////////////////////////////////////////////////// + + #region Private Methods /// /// Creates and returns the native sqlite_module structure using the /// configured (or default) /// interface implementation. /// @@ -5237,13 +5332,13 @@ /// /// The native sqlite_module structure using the configured (or /// default) interface /// implementation. /// - internal UnsafeNativeMethods.sqlite3_module CreateNativeModule() + private UnsafeNativeMethods.sqlite3_module AllocateNativeModule() { - return CreateNativeModule(GetNativeModuleImpl()); + return AllocateNativeModule(GetNativeModuleImpl()); } /////////////////////////////////////////////////////////////////////// #if PLATFORM_COMPACTFRAMEWORK @@ -5253,11 +5348,11 @@ /// object instance when running on the .NET Compact Framework. /// /// /// The native pointer to the native sqlite3_module structure. /// - internal IntPtr CreateNativeModuleInterop() + private IntPtr AllocateNativeModuleInterop() { if (pNativeModule == IntPtr.Zero) { // // HACK: No easy way to determine the size of the native @@ -5267,24 +5362,22 @@ // // There is one integer member. // There are 22 function pointer members. // pNativeModule = SQLiteMemory.Allocate( - sizeof(int) + (22 * IntPtr.Size)); + SQLiteMarshal.SizeOfStructInt() + (22 * IntPtr.Size)); if (pNativeModule == IntPtr.Zero) throw new OutOfMemoryException("sqlite3_module"); } return pNativeModule; } #endif - #endregion /////////////////////////////////////////////////////////////////////// - #region Private Methods /// /// Creates and returns the native sqlite_module structure using the /// specified interface /// implementation. /// @@ -5294,11 +5387,11 @@ /// /// /// The native sqlite_module structure using the specified /// interface implementation. /// - private UnsafeNativeMethods.sqlite3_module CreateNativeModule( + private UnsafeNativeMethods.sqlite3_module AllocateNativeModule( ISQLiteNativeModule module ) { nativeModule = new UnsafeNativeMethods.sqlite3_module(); nativeModule.iVersion = DefaultModuleVersion; @@ -5664,11 +5757,11 @@ // condition back to the caller; therefore, use the // logging facility instead. // try { - if (LogExceptions) + if (LogExceptionsNoThrow) { /* throw */ SQLiteLog.LogMessage(SQLiteBase.COR_E_EXCEPTION, String.Format(CultureInfo.CurrentCulture, "Caught exception in \"{0}\" method: {1}", @@ -5734,11 +5827,11 @@ } if (pVtab == IntPtr.Zero) return false; - int offset = IntPtr.Size + sizeof(int); + int offset = IntPtr.Size + SQLiteMarshal.SizeOfStructInt(); IntPtr pError = SQLiteMarshal.ReadIntPtr(pVtab, offset); if (pError != IntPtr.Zero) { SQLiteMemory.Free(pError); pError = IntPtr.Zero; @@ -5979,11 +6072,11 @@ offset += IntPtr.Size; SQLiteMarshal.WriteInt32(pVtab, offset, 0); - offset += sizeof(int); + offset += SQLiteMarshal.SizeOfStructInt(); SQLiteMarshal.WriteIntPtr(pVtab, offset, IntPtr.Zero); } /////////////////////////////////////////////////////////////////////// @@ -6418,10 +6511,42 @@ } #endregion /////////////////////////////////////////////////////////////////////// + #region Error Handling Properties + private bool logErrors; + /// + /// Returns or sets a boolean value indicating whether virtual table + /// errors should be logged using the class. + /// + protected virtual bool LogErrorsNoThrow + { + get { return logErrors; } + set { logErrors = value; } + } + + /////////////////////////////////////////////////////////////////////// + + private bool logExceptions; + /// + /// Returns or sets a boolean value indicating whether exceptions + /// caught in the + /// method, + /// method, and the + /// method should be logged using the + /// class. + /// + protected virtual bool LogExceptionsNoThrow + { + get { return logExceptions; } + set { logExceptions = value; } + } + #endregion + + /////////////////////////////////////////////////////////////////////// + #region Error Handling Helper Methods /// /// Arranges for the specified error message to be placed into the /// zErrMsg field of a sqlite3_vtab derived structure, freeing the /// existing error message, if any. @@ -6438,11 +6563,11 @@ protected virtual bool SetTableError( IntPtr pVtab, string error ) { - return SetTableError(this, pVtab, LogErrors, error); + return SetTableError(this, pVtab, LogErrorsNoThrow, error); } /////////////////////////////////////////////////////////////////////// /// @@ -6463,11 +6588,11 @@ protected virtual bool SetTableError( SQLiteVirtualTable table, string error ) { - return SetTableError(this, table, LogErrors, error); + return SetTableError(this, table, LogErrorsNoThrow, error); } /////////////////////////////////////////////////////////////////////// /// @@ -6488,11 +6613,11 @@ protected virtual bool SetCursorError( SQLiteVirtualTableCursor cursor, string error ) { - return SetCursorError(this, cursor, LogErrors, error); + return SetCursorError(this, cursor, LogErrorsNoThrow, error); } #endregion /////////////////////////////////////////////////////////////////////// @@ -6544,24 +6669,22 @@ #endregion /////////////////////////////////////////////////////////////////////// #region Public Properties - private bool logErrors; /// /// Returns or sets a boolean value indicating whether virtual table /// errors should be logged using the class. /// public virtual bool LogErrors { - get { CheckDisposed(); return logErrors; } - set { CheckDisposed(); logErrors = value; } + get { CheckDisposed(); return LogErrorsNoThrow; } + set { CheckDisposed(); LogErrorsNoThrow = value; } } /////////////////////////////////////////////////////////////////////// - private bool logExceptions; /// /// Returns or sets a boolean value indicating whether exceptions /// caught in the /// method, /// method, and the @@ -6568,12 +6691,12 @@ /// method should be logged using the /// class. /// public virtual bool LogExceptions { - get { CheckDisposed(); return logExceptions; } - set { CheckDisposed(); logExceptions = value; } + get { CheckDisposed(); return LogExceptionsNoThrow; } + set { CheckDisposed(); LogExceptionsNoThrow = value; } } #endregion /////////////////////////////////////////////////////////////////////// @@ -8032,35 +8155,23 @@ // release unmanaged resources here... ////////////////////////////////////// try { -#if !PLATFORM_COMPACTFRAMEWORK - UnsafeNativeMethods.sqlite3_dispose_module( - ref nativeModule); -#elif !SQLITE_STANDARD - if (pNativeModule != IntPtr.Zero) - { - try - { - UnsafeNativeMethods.sqlite3_dispose_module_interop( - pNativeModule); - } - finally - { - SQLiteMemory.Free(pNativeModule); - } - } -#else - throw new NotImplementedException(); -#endif + if (disposableModule != IntPtr.Zero) + { + UnsafeNativeMethods.sqlite3_dispose_module( + disposableModule); + + disposableModule = IntPtr.Zero; + } } catch (Exception e) { try { - if (LogExceptions) + if (LogExceptionsNoThrow) { SQLiteLog.LogMessage(SQLiteBase.COR_E_EXCEPTION, String.Format(CultureInfo.CurrentCulture, "Caught exception in \"Dispose\" method: {0}", e)); /* throw */ @@ -8069,10 +8180,20 @@ catch { // do nothing. } } +#if PLATFORM_COMPACTFRAMEWORK + finally + { + if (pNativeModule != IntPtr.Zero) + { + SQLiteMemory.Free(pNativeModule); + pNativeModule = IntPtr.Zero; + } + } +#endif disposed = true; } } #endregion Index: System.Data.SQLite/SQLiteModuleEnumerable.cs ================================================================== --- System.Data.SQLite/SQLiteModuleEnumerable.cs +++ System.Data.SQLite/SQLiteModuleEnumerable.cs @@ -225,17 +225,20 @@ ////////////////////////////////////// // release unmanaged resources here... ////////////////////////////////////// Close(); - - disposed = true; } } finally { base.Dispose(disposing); + + // + // NOTE: Everything should be fully disposed at this point. + // + disposed = true; } } #endregion } #endregion @@ -943,17 +946,20 @@ //} ////////////////////////////////////// // release unmanaged resources here... ////////////////////////////////////// - - disposed = true; } } finally { base.Dispose(disposing); + + // + // NOTE: Everything should be fully disposed at this point. + // + disposed = true; } } #endregion } #endregion @@ -1092,17 +1098,20 @@ ////////////////////////////////////// // release unmanaged resources here... ////////////////////////////////////// Close(); - - disposed = true; } } finally { base.Dispose(disposing); + + // + // NOTE: Everything should be fully disposed at this point. + // + disposed = true; } } #endregion } #endregion @@ -1267,19 +1276,22 @@ //} ////////////////////////////////////// // release unmanaged resources here... ////////////////////////////////////// - - disposed = true; } } finally { base.Dispose(disposing); + + // + // NOTE: Everything should be fully disposed at this point. + // + disposed = true; } } #endregion } #endregion } #endregion Index: System.Data.SQLite/SQLiteModuleNoop.cs ================================================================== --- System.Data.SQLite/SQLiteModuleNoop.cs +++ System.Data.SQLite/SQLiteModuleNoop.cs @@ -767,17 +767,20 @@ //} ////////////////////////////////////// // release unmanaged resources here... ////////////////////////////////////// - - disposed = true; } } finally { base.Dispose(disposing); + + // + // NOTE: Everything should be fully disposed at this point. + // + disposed = true; } } #endregion } } Index: System.Data.SQLite/SQLiteTransaction.cs ================================================================== --- System.Data.SQLite/SQLiteTransaction.cs +++ System.Data.SQLite/SQLiteTransaction.cs @@ -1,64 +1,64 @@ -/******************************************************** - * 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 System.Data.SQLite -{ - using System; - using System.Data; - using System.Data.Common; - using System.Threading; - - /// - /// SQLite implementation of DbTransaction. - /// - public sealed class SQLiteTransaction : DbTransaction - { - /// - /// The connection to which this transaction is bound - /// - internal SQLiteConnection _cnn; - internal long _version; // Matches the version of the connection - private IsolationLevel _level; - - /// - /// Constructs the transaction object, binding it to the supplied connection - /// - /// The connection to open a transaction on - /// TRUE to defer the writelock, or FALSE to lock immediately - internal SQLiteTransaction(SQLiteConnection connection, bool deferredLock) - { - _cnn = connection; - _version = _cnn._version; - - _level = (deferredLock == true) ? IsolationLevel.ReadCommitted : IsolationLevel.Serializable; - - if (_cnn._transactionLevel++ == 0) - { - try - { - using (SQLiteCommand cmd = _cnn.CreateCommand()) - { - if (!deferredLock) - cmd.CommandText = "BEGIN IMMEDIATE"; - else - cmd.CommandText = "BEGIN"; - - cmd.ExecuteNonQuery(); - } - } - catch (SQLiteException) - { - _cnn._transactionLevel--; - _cnn = null; - throw; - } - } +/******************************************************** + * 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 System.Data.SQLite +{ + using System; + using System.Data; + using System.Data.Common; + using System.Threading; + + /// + /// SQLite implementation of DbTransaction. + /// + public sealed class SQLiteTransaction : DbTransaction + { + /// + /// The connection to which this transaction is bound + /// + internal SQLiteConnection _cnn; + internal long _version; // Matches the version of the connection + private IsolationLevel _level; + + /// + /// Constructs the transaction object, binding it to the supplied connection + /// + /// The connection to open a transaction on + /// TRUE to defer the writelock, or FALSE to lock immediately + internal SQLiteTransaction(SQLiteConnection connection, bool deferredLock) + { + _cnn = connection; + _version = _cnn._version; + + _level = (deferredLock == true) ? IsolationLevel.ReadCommitted : IsolationLevel.Serializable; + + if (_cnn._transactionLevel++ == 0) + { + try + { + using (SQLiteCommand cmd = _cnn.CreateCommand()) + { + if (!deferredLock) + cmd.CommandText = "BEGIN IMMEDIATE"; + else + cmd.CommandText = "BEGIN"; + + cmd.ExecuteNonQuery(); + } + } + catch (SQLiteException) + { + _cnn._transactionLevel--; + _cnn = null; + throw; + } + } } /////////////////////////////////////////////////////////////////////////////////////////////// #region IDisposable "Pattern" Members @@ -73,11 +73,11 @@ /////////////////////////////////////////////////////////////////////////////////////////////// /// /// Disposes the transaction. If it is currently active, any changes are rolled back. - /// + /// protected override void Dispose(bool disposing) { try { if (!disposed) @@ -95,84 +95,87 @@ } ////////////////////////////////////// // release unmanaged resources here... ////////////////////////////////////// - - disposed = true; } } finally { base.Dispose(disposing); + + // + // NOTE: Everything should be fully disposed at this point. + // + disposed = true; } } #endregion /////////////////////////////////////////////////////////////////////////////////////////////// - - /// - /// Commits the current transaction. - /// - public override void Commit() - { - CheckDisposed(); - SQLiteConnection.Check(_cnn); - IsValid(true); - - if (_cnn._transactionLevel - 1 == 0) - { - using (SQLiteCommand cmd = _cnn.CreateCommand()) - { - cmd.CommandText = "COMMIT"; - cmd.ExecuteNonQuery(); - } - } - _cnn._transactionLevel--; - _cnn = null; - } - - /// - /// Returns the underlying connection to which this transaction applies. - /// - public new SQLiteConnection Connection - { - get { CheckDisposed(); return _cnn; } - } - - /// - /// Forwards to the local Connection property - /// - protected override DbConnection DbConnection - { - get { return Connection; } - } - - /// - /// Gets the isolation level of the transaction. SQLite only supports Serializable transactions. - /// - public override IsolationLevel IsolationLevel - { - get { CheckDisposed(); return _level; } - } - - /// - /// Rolls back the active transaction. - /// - public override void Rollback() - { - CheckDisposed(); - SQLiteConnection.Check(_cnn); - IsValid(true); - IssueRollback(true); - } - - internal void IssueRollback(bool throwError) - { - SQLiteConnection cnn = Interlocked.Exchange(ref _cnn, null); - - if (cnn != null) + + /// + /// Commits the current transaction. + /// + public override void Commit() + { + CheckDisposed(); + SQLiteConnection.Check(_cnn); + IsValid(true); + + if (_cnn._transactionLevel - 1 == 0) + { + using (SQLiteCommand cmd = _cnn.CreateCommand()) + { + cmd.CommandText = "COMMIT"; + cmd.ExecuteNonQuery(); + } + } + _cnn._transactionLevel--; + _cnn = null; + } + + /// + /// Returns the underlying connection to which this transaction applies. + /// + public new SQLiteConnection Connection + { + get { CheckDisposed(); return _cnn; } + } + + /// + /// Forwards to the local Connection property + /// + protected override DbConnection DbConnection + { + get { return Connection; } + } + + /// + /// Gets the isolation level of the transaction. SQLite only supports Serializable transactions. + /// + public override IsolationLevel IsolationLevel + { + get { CheckDisposed(); return _level; } + } + + /// + /// Rolls back the active transaction. + /// + public override void Rollback() + { + CheckDisposed(); + SQLiteConnection.Check(_cnn); + IsValid(true); + IssueRollback(true); + } + + internal void IssueRollback(bool throwError) + { + SQLiteConnection cnn = Interlocked.Exchange(ref _cnn, null); + + if (cnn != null) { try { using (SQLiteCommand cmd = cnn.CreateCommand()) { @@ -182,40 +185,40 @@ } catch { if (throwError) throw; - } - cnn._transactionLevel = 0; - } - } - - internal bool IsValid(bool throwError) - { - if (_cnn == null) - { - if (throwError == true) throw new ArgumentNullException("No connection associated with this transaction"); - else return false; - } - - if (_cnn._version != _version) - { - if (throwError == true) throw new SQLiteException("The connection was closed and re-opened, changes were already rolled back"); - else return false; - } - if (_cnn.State != ConnectionState.Open) - { - if (throwError == true) throw new SQLiteException("Connection was closed"); - else return false; - } - - if (_cnn._transactionLevel == 0 || _cnn._sql.AutoCommit == true) - { - _cnn._transactionLevel = 0; // Make sure the transaction level is reset before returning - if (throwError == true) throw new SQLiteException("No transaction is active on this connection"); - else return false; - } - - return true; - } - } -} + } + cnn._transactionLevel = 0; + } + } + + internal bool IsValid(bool throwError) + { + if (_cnn == null) + { + if (throwError == true) throw new ArgumentNullException("No connection associated with this transaction"); + else return false; + } + + if (_cnn._version != _version) + { + if (throwError == true) throw new SQLiteException("The connection was closed and re-opened, changes were already rolled back"); + else return false; + } + if (_cnn.State != ConnectionState.Open) + { + if (throwError == true) throw new SQLiteException("Connection was closed"); + else return false; + } + + if (_cnn._transactionLevel == 0 || _cnn._sql.AutoCommit == true) + { + _cnn._transactionLevel = 0; // Make sure the transaction level is reset before returning + if (throwError == true) throw new SQLiteException("No transaction is active on this connection"); + else return false; + } + + return true; + } + } +} Index: System.Data.SQLite/UnsafeNativeMethods.cs ================================================================== --- System.Data.SQLite/UnsafeNativeMethods.cs +++ System.Data.SQLite/UnsafeNativeMethods.cs @@ -1605,11 +1605,11 @@ #if !PLATFORM_COMPACTFRAMEWORK [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)] #else [DllImport(SQLITE_DLL)] #endif - internal static extern void sqlite3_dispose_module(ref sqlite3_module pModule); + internal static extern void sqlite3_dispose_module(IntPtr pModule); #endif #endregion /////////////////////////////////////////////////////////////////////////// @@ -1659,13 +1659,10 @@ xNext xNext, xEof xEof, xColumn xColumn, xRowId xRowId, xUpdate xUpdate, xBegin xBegin, xSync xSync, xCommit xCommit, xRollback xRollback, xFindFunction xFindFunction, xRename xRename, xSavepoint xSavepoint, xRelease xRelease, xRollbackTo xRollbackTo, IntPtr pClientData, xDestroyModule xDestroyModule); - - [DllImport(SQLITE_DLL)] - internal static extern void sqlite3_dispose_module_interop(IntPtr pModule); #endif // PLATFORM_COMPACTFRAMEWORK && !SQLITE_STANDARD #endregion /////////////////////////////////////////////////////////////////////////// Index: Tests/vtab.eagle ================================================================== --- Tests/vtab.eagle +++ Tests/vtab.eagle @@ -208,11 +208,11 @@ unset -nocomplain result code results errors sql dataSource id fileName } -constraints \ {eagle monoBug28 command.sql compile.DATA SQLite System.Data.SQLite\ defineConstant.System.Data.SQLite.INTEROP_VIRTUAL_TABLE} -match regexp -result \ [string map [list \n \r\n] {^Ok System#CodeDom#Compiler#CompilerResults#\d+\ -\{\} 0 \{one two three 4 5.0 Error \{SQL logic error or missing database +\{\} 0 \{one two three 4 5\.0 Error \{SQL logic error or missing database virtual table "t\d+" is read-only\}\}$}]} ############################################################################### runTest {test vtab-1.3 {IEnumerable virtual table} -setup { @@ -1000,10 +1000,110 @@ } -constraints \ {eagle monoBug28 command.sql compile.DATA SQLite System.Data.SQLite\ defineConstant.System.Data.SQLite.INTEROP_VIRTUAL_TABLE} -match regexp -result \ [string map [list \n \r\n] {^Ok System#CodeDom#Compiler#CompilerResults#\d+\ \{\} 0 \{\{\} t\d+ \{\} x\d+\}$}]} + +############################################################################### + +runTest {test vtab-1.7 {virtual table xBestIndex marshalling} -setup { + set fileName vtab-1.7.db +} -body { + set id [object invoke Interpreter.GetActive NextId] + set dataSource [file join [getDatabaseDirectory] $fileName] + + set sql(1) { \ + CREATE TABLE t${id}(y CHAR(10) NOT NULL PRIMARY KEY); \ + CREATE VIRTUAL TABLE u${id} USING mod${id}; \ + } + + set sql(2) { \ + INSERT INTO t${id} SELECT x FROM u${id}; \ + } + + set sql(3) { \ + SELECT v${id}.y FROM t${id} v${id} LEFT OUTER JOIN \ + u${id} ON u${id}.x = v${id}.y WHERE u${id}.x IS NOT NULL \ + ORDER BY v${id}.y DESC; \ + } + + unset -nocomplain results errors + + set code [compileCSharpWith [subst { + using System; + using System.Data.SQLite; + using Eagle._Containers.Public; + + namespace _Dynamic${id} + { + public static class Test${id} + { + public static StringList GetList(params string\[\] strings) + { + StringList result = new StringList(); + + using (SQLiteConnection connection = new SQLiteConnection( + "Data Source=${dataSource};")) + { + connection.Open(); + + connection.CreateModule(new SQLiteModuleEnumerable( + "mod${id}", strings)); + + using (SQLiteCommand command = connection.CreateCommand()) + { + command.CommandText = "[subst ${sql(1)}]"; + result.Add(command.ExecuteNonQuery().ToString()); + } + + using (SQLiteCommand command = connection.CreateCommand()) + { + command.CommandText = "[subst ${sql(2)}]"; + result.Add(command.ExecuteNonQuery().ToString()); + } + + using (SQLiteCommand command = connection.CreateCommand()) + { + command.CommandText = "[subst ${sql(3)}]"; + + using (SQLiteDataReader dataReader = command.ExecuteReader()) + { + while (dataReader.Read()) + result.Add(dataReader\[0\].ToString()); + } + } + + connection.Close(); + } + + return result; + } + + /////////////////////////////////////////////////////////////////////// + + public static void Main() + { + // do nothing. + } + } + } + }] true true true results errors [list System.Data.SQLite.dll Eagle.dll]] + + list $code $results \ + [expr {[info exists errors] ? $errors : ""}] \ + [expr {$code eq "Ok" ? [catch { + object invoke _Dynamic${id}.Test${id} GetList one two three 4 5.0 + } result] : [set result ""]}] $result +} -cleanup { + cleanupDb $fileName + + unset -nocomplain result code results errors sql dataSource id fileName +} -constraints \ +{eagle monoBug28 command.sql compile.DATA SQLite System.Data.SQLite\ +defineConstant.System.Data.SQLite.INTEROP_VIRTUAL_TABLE} -match regexp -result \ +[string map [list \n \r\n] {^Ok System#CodeDom#Compiler#CompilerResults#\d+\ +\{\} 0 \{0 5 two three one 5\.0 4\}$}]} ############################################################################### runSQLiteTestEpilogue runTestEpilogue