/******************************************************** * ADO.NET 2.0 Data Provider for SQLite Version 3.X * Written by Joe Mistachkin (joe@mistachkin.com) * * Released to the public domain, use at your own risk! ********************************************************/ using System.Collections; using System.Collections.Generic; #if DEBUG using System.Diagnostics; #endif using System.IO; using System.Globalization; using System.Runtime.InteropServices; namespace System.Data.SQLite { #region Session Extension Enumerations /// /// This enumerated type represents a type of conflict seen when apply /// changes from a change set or patch set. /// public enum SQLiteChangeSetConflictType { /// /// This value is seen when processing a DELETE or UPDATE change if a /// row with the required PRIMARY KEY fields is present in the /// database, but one or more other (non primary-key) fields modified /// by the update do not contain the expected "before" values. /// Data = 1, /// /// This value is seen when processing a DELETE or UPDATE change if a /// row with the required PRIMARY KEY fields is not present in the /// database. There is no conflicting row in this case. /// /// The results of invoking the /// /// method are undefined. /// NotFound = 2, /// /// This value is seen when processing an INSERT change if the /// operation would result in duplicate primary key values. /// The conflicting row in this case is the database row with the /// matching primary key. /// Conflict = 3, /// /// If a non-foreign key constraint violation occurs while applying a /// change (i.e. a UNIQUE, CHECK or NOT NULL constraint), the conflict /// callback will see this value. /// /// There is no conflicting row in this case. The results of invoking /// the /// method are undefined. /// Constraint = 4, /// /// If foreign key handling is enabled, and applying a changes leaves /// the database in a state containing foreign key violations, this /// value will be seen exactly once before the changes are committed. /// If the conflict handler /// , the changes, /// including those that caused the foreign key constraint violation, /// are committed. Or, if it returns /// , the changes are /// rolled back. /// /// No current or conflicting row information is provided. The only /// method it is possible to call on the supplied /// object is /// . /// ForeignKey = 5 } /////////////////////////////////////////////////////////////////////////// /// /// This enumerated type represents the result of a user-defined conflict /// resolution callback. /// public enum SQLiteChangeSetConflictResult { /// /// If a conflict callback returns this value no special action is /// taken. The change that caused the conflict is not applied. The /// application of changes continues with the next change. /// Omit = 0, /// /// This value may only be returned from a conflict callback if the /// type of conflict was /// or . If this is /// not the case, any changes applied so far are rolled back and the /// call to /// /// will raise a with an error code of /// . /// /// If this value is returned for a /// conflict, then the /// conflicting row is either updated or deleted, depending on the type /// of change. /// /// If this value is returned for a /// conflict, then /// the conflicting row is removed from the database and a second /// attempt to apply the change is made. If this second attempt fails, /// the original row is restored to the database before continuing. /// Replace = 1, /// /// If this value is returned, any changes applied so far are rolled /// back and the call to /// /// will raise a with an error code of /// . /// Abort = 2 } /////////////////////////////////////////////////////////////////////////// /// /// This enumerated type represents possible flags that may be passed /// to the appropriate overloads of various change set creation methods. /// public enum SQLiteChangeSetStartFlags { /// /// No special handling. /// None = 0x0, /// /// Invert the change set while iterating through it. /// This is equivalent to inverting a change set using /// before /// applying it. It is an error to specify this flag /// with a patch set. /// Invert = 0x2 } #endregion /////////////////////////////////////////////////////////////////////////// #region Session Extension Delegates /// /// This callback is invoked when a determination must be made about /// whether changes to a specific table should be tracked -OR- applied. /// It will not be called for tables that are already attached to a /// . /// /// /// The optional application-defined context data that was originally /// passed to the or /// /// methods. This value may be null. /// /// /// The name of the table. /// /// /// Non-zero if changes to the table should be considered; otherwise, /// zero. Throwing an exception from this callback will result in /// undefined behavior. /// public delegate bool SessionTableFilterCallback( object clientData, string name ); /////////////////////////////////////////////////////////////////////////// /// /// This callback is invoked when there is a conflict while apply changes /// to a database. /// /// /// The optional application-defined context data that was originally /// passed to the /// /// method. This value may be null. /// /// /// The type of this conflict. /// /// /// The object associated with /// this conflict. This value may not be null; however, only properties /// that are applicable to the conflict type will be available. Further /// information on this is available within the descriptions of the /// available values. /// /// /// A value that indicates the /// action to be taken in order to resolve the conflict. Throwing an /// exception from this callback will result in undefined behavior. /// public delegate SQLiteChangeSetConflictResult SessionConflictCallback( object clientData, SQLiteChangeSetConflictType type, ISQLiteChangeSetMetadataItem item ); #endregion /////////////////////////////////////////////////////////////////////////// #region ISQLiteChangeSet Interface /// /// This interface contains methods used to manipulate a set of changes for /// a database. /// public interface ISQLiteChangeSet : IEnumerable, IDisposable { /// /// This method "inverts" the set of changes within this instance. /// Applying an inverted set of changes to a database reverses the /// effects of applying the uninverted changes. Specifically: /// ]]>]]> /// Each DELETE change is changed to an INSERT, and /// ]]>]]> /// Each INSERT change is changed to a DELETE, and /// ]]>]]> /// For each UPDATE change, the old.* and new.* values are exchanged. /// ]]>]]> /// This method does not change the order in which changes appear /// within the set of changes. It merely reverses the sense of each /// individual change. /// /// /// The new instance that represents /// the resulting set of changes -OR- null if it is not available. /// ISQLiteChangeSet Invert(); /// /// This method combines the specified set of changes with the ones /// contained in this instance. /// /// /// The changes to be combined with those in this instance. /// /// /// The new instance that represents /// the resulting set of changes -OR- null if it is not available. /// ISQLiteChangeSet CombineWith(ISQLiteChangeSet changeSet); /// /// Attempts to apply the set of changes in this instance to the /// associated database. /// /// /// The delegate that will need /// to handle any conflicting changes that may arise. /// /// /// The optional application-defined context data. This value may be /// null. /// void Apply( SessionConflictCallback conflictCallback, object clientData ); /// /// Attempts to apply the set of changes in this instance to the /// associated database. /// /// /// The delegate that will need /// to handle any conflicting changes that may arise. /// /// /// The optional delegate /// that can be used to filter the list of tables impacted by the set /// of changes. /// /// /// The optional application-defined context data. This value may be /// null. /// void Apply( SessionConflictCallback conflictCallback, SessionTableFilterCallback tableFilterCallback, object clientData ); } #endregion /////////////////////////////////////////////////////////////////////////// #region ISQLiteChangeGroup Interface /// /// This interface contains methods used to manipulate multiple sets of /// changes for a database. /// public interface ISQLiteChangeGroup : IDisposable { /// /// Attempts to add a change set (or patch set) to this change group /// instance. The underlying data must be contained entirely within /// the byte array. /// /// /// The raw byte data for the specified change set (or patch set). /// void AddChangeSet(byte[] rawData); /// /// Attempts to add a change set (or patch set) to this change group /// instance. The underlying data will be read from the specified /// . /// /// /// The instance containing the raw change set /// (or patch set) data to read. /// void AddChangeSet(Stream stream); /// /// Attempts to create and return, via , the /// combined set of changes represented by this change group instance. /// /// /// Upon success, this will contain the raw byte data for all the /// changes in this change group instance. /// void CreateChangeSet(ref byte[] rawData); /// /// Attempts to create and write, via , the /// combined set of changes represented by this change group instance. /// /// /// Upon success, the raw byte data for all the changes in this change /// group instance will be written to this . /// void CreateChangeSet(Stream stream); } #endregion /////////////////////////////////////////////////////////////////////////// #region ISQLiteChangeSetMetadataItem Interface /// /// This interface contains properties and methods used to fetch metadata /// about one change within a set of changes for a database. /// public interface ISQLiteChangeSetMetadataItem : IDisposable { /// /// The name of the table the change was made to. /// string TableName { get; } /// /// The number of columns impacted by this change. This value can be /// used to determine the highest valid column index that may be used /// with the , , /// and methods of this interface. It /// will be this value minus one. /// int NumberOfColumns { get; } /// /// This will contain the value /// , /// , or /// , corresponding to /// the overall type of change this item represents. /// SQLiteAuthorizerActionCode OperationCode { get; } /// /// Non-zero if this change is considered to be indirect (i.e. as /// though they were made via a trigger or foreign key action). /// bool Indirect { get; } /// /// This array contains a for each column in /// the table associated with this change. The element will be zero /// if the column is not part of the primary key; otherwise, it will /// be non-zero. /// bool[] PrimaryKeyColumns { get; } /// /// This method may only be called from within a /// delegate when the conflict /// type is . It /// returns the total number of known foreign key violations in the /// destination database. /// int NumberOfForeignKeyConflicts { get; } /// /// Queries and returns the original value of a given column for this /// change. This method may only be called when the /// has a value of /// or /// . /// /// /// The index for the column. This value must be between zero and one /// less than the total number of columns for this table. /// /// /// The original value of a given column for this change. /// SQLiteValue GetOldValue(int columnIndex); /// /// Queries and returns the updated value of a given column for this /// change. This method may only be called when the /// has a value of /// or /// . /// /// /// The index for the column. This value must be between zero and one /// less than the total number of columns for this table. /// /// /// The updated value of a given column for this change. /// SQLiteValue GetNewValue(int columnIndex); /// /// Queries and returns the conflicting value of a given column for /// this change. This method may only be called from within a /// delegate when the conflict /// type is or /// . /// /// /// The index for the column. This value must be between zero and one /// less than the total number of columns for this table. /// /// /// The conflicting value of a given column for this change. /// SQLiteValue GetConflictValue(int columnIndex); } #endregion /////////////////////////////////////////////////////////////////////////// #region ISQLiteSession Interface /// /// This interface contains methods to query and manipulate the state of a /// change tracking session for a database. /// public interface ISQLiteSession : IDisposable { /// /// Determines if this session is currently tracking changes to its /// associated database. /// /// /// Non-zero if changes to the associated database are being trakced; /// otherwise, zero. /// bool IsEnabled(); /// /// Enables tracking of changes to the associated database. /// void SetToEnabled(); /// /// Disables tracking of changes to the associated database. /// void SetToDisabled(); /// /// Determines if this session is currently set to mark changes as /// indirect (i.e. as though they were made via a trigger or foreign /// key action). /// /// /// Non-zero if changes to the associated database are being marked as /// indirect; otherwise, zero. /// bool IsIndirect(); /// /// Sets the indirect flag for this session. Subsequent changes will /// be marked as indirect until this flag is changed again. /// void SetToIndirect(); /// /// Clears the indirect flag for this session. Subsequent changes will /// be marked as direct until this flag is changed again. /// void SetToDirect(); /// /// Determines if there are any tracked changes currently within the /// data for this session. /// /// /// Non-zero if there are no changes within the data for this session; /// otherwise, zero. /// bool IsEmpty(); /// /// Upon success, causes changes to the specified table(s) to start /// being tracked. Any tables impacted by calls to this method will /// not cause the callback /// to be invoked. /// /// /// The name of the table to be tracked -OR- null to track all /// applicable tables within this database. /// void AttachTable(string name); /// /// This method is used to set the table filter for this instance. /// /// /// The table filter callback -OR- null to clear any existing table /// filter callback. /// /// /// The optional application-defined context data. This value may be /// null. /// void SetTableFilter( SessionTableFilterCallback callback, object clientData ); /// /// Attempts to create and return, via , the /// combined set of changes represented by this session instance. /// /// /// Upon success, this will contain the raw byte data for all the /// changes in this session instance. /// void CreateChangeSet(ref byte[] rawData); /// /// Attempts to create and write, via , the /// combined set of changes represented by this session instance. /// /// /// Upon success, the raw byte data for all the changes in this session /// instance will be written to this . /// void CreateChangeSet(Stream stream); /// /// Attempts to create and return, via , the /// combined set of changes represented by this session instance as a /// patch set. /// /// /// Upon success, this will contain the raw byte data for all the /// changes in this session instance. /// void CreatePatchSet(ref byte[] rawData); /// /// Attempts to create and write, via , the /// combined set of changes represented by this session instance as a /// patch set. /// /// /// Upon success, the raw byte data for all the changes in this session /// instance will be written to this . /// void CreatePatchSet(Stream stream); /// /// This method loads the differences between two tables [with the same /// name, set of columns, and primary key definition] into this session /// instance. /// /// /// The name of the database containing the table with the original /// data (i.e. it will need updating in order to be identical to the /// one within the database associated with this session instance). /// /// /// The name of the table. /// void LoadDifferencesFromTable( string fromDatabaseName, string tableName ); } #endregion /////////////////////////////////////////////////////////////////////////// #region SQLiteSessionHelpers Class /// /// This class contains some static helper methods for use within this /// subsystem. /// internal static class SQLiteSessionHelpers { #region Public Methods /// /// This method checks the byte array specified by the caller to make /// sure it will be usable. /// /// /// A byte array provided by the caller into one of the public methods /// for the classes that belong to this subsystem. This value cannot /// be null or represent an empty array; otherwise, an appropriate /// exception will be thrown. /// public static void CheckRawData( byte[] rawData ) { if (rawData == null) throw new ArgumentNullException("rawData"); if (rawData.Length == 0) { throw new ArgumentException( "empty change set data", "rawData"); } } #endregion } #endregion /////////////////////////////////////////////////////////////////////////// #region SQLiteConnectionLock Class /// /// This class is used to hold the native connection handle associated with /// a open until this subsystem is totally /// done with it. This class is for internal use by this subsystem only. /// internal abstract class SQLiteConnectionLock : IDisposable { #region Private Constants /// /// The SQL statement used when creating the native statement handle. /// There are no special requirements for this other than counting as /// an "open statement handle". /// private const string LockNopSql = "SELECT 1;"; /////////////////////////////////////////////////////////////////////// /// /// The format of the error message used when reporting, during object /// disposal, that the statement handle is still open (i.e. because /// this situation is considered a fairly serious programming error). /// private const string StatementMessageFormat = "Connection lock object was {0} with statement {1}"; #endregion /////////////////////////////////////////////////////////////////////// #region Private Data /// /// The wrapped native connection handle associated with this lock. /// private SQLiteConnectionHandle handle; /// /// The flags associated with the connection represented by the /// value. /// private SQLiteConnectionFlags flags; /////////////////////////////////////////////////////////////////////// /// /// The native statement handle for this lock. The garbage collector /// cannot cause this statement to be finalized; therefore, it will /// serve to hold the associated native connection open until it is /// freed manually using the method. /// private IntPtr statement; #endregion /////////////////////////////////////////////////////////////////////// #region Public Constructors /// /// Constructs a new instance of this class using the specified wrapped /// native connection handle and associated flags. /// /// /// The wrapped native connection handle to be associated with this /// lock. /// /// /// The flags associated with the connection represented by the /// value. /// /// /// Non-zero if the method should be called prior /// to returning from this constructor. /// public SQLiteConnectionLock( SQLiteConnectionHandle handle, SQLiteConnectionFlags flags, bool autoLock ) { this.handle = handle; this.flags = flags; if (autoLock) Lock(); } #endregion /////////////////////////////////////////////////////////////////////// #region Protected Methods /// /// Queries and returns the wrapped native connection handle for this /// instance. /// /// /// The wrapped native connection handle for this instance -OR- null /// if it is unavailable. /// protected SQLiteConnectionHandle GetHandle() { return handle; } /////////////////////////////////////////////////////////////////////// /// /// Queries and returns the flags associated with the connection for /// this instance. /// /// /// The value. There is no return /// value reserved to indicate an error. /// protected SQLiteConnectionFlags GetFlags() { return flags; } /////////////////////////////////////////////////////////////////////// /// /// Queries and returns the native connection handle for this instance. /// /// /// The native connection handle for this instance. If this value is /// unavailable or invalid an exception will be thrown. /// protected IntPtr GetIntPtr() { if (handle == null) { throw new InvalidOperationException( "Connection lock object has an invalid handle."); } IntPtr handlePtr = handle; if (handlePtr == IntPtr.Zero) { throw new InvalidOperationException( "Connection lock object has an invalid handle pointer."); } return handlePtr; } #endregion /////////////////////////////////////////////////////////////////////// #region Public Methods /// /// This method attempts to "lock" the associated native connection /// handle by preparing a SQL statement that will not be finalized /// until the method is called (i.e. and which /// cannot be done by the garbage collector). If the statement is /// already prepared, nothing is done. If the statement cannot be /// prepared for any reason, an exception will be thrown. /// public void Lock() { CheckDisposed(); if (statement != IntPtr.Zero) return; IntPtr pSql = IntPtr.Zero; try { int nSql = 0; pSql = SQLiteString.Utf8IntPtrFromString(LockNopSql, ref nSql); IntPtr pRemain = IntPtr.Zero; #if !SQLITE_STANDARD int nRemain = 0; string functionName = "sqlite3_prepare_interop"; SQLiteErrorCode rc = UnsafeNativeMethods.sqlite3_prepare_interop( GetIntPtr(), pSql, nSql, ref statement, ref pRemain, ref nRemain); #else #if USE_PREPARE_V2 string functionName = "sqlite3_prepare_v2"; SQLiteErrorCode rc = UnsafeNativeMethods.sqlite3_prepare_v2( GetIntPtr(), pSql, nSql, ref statement, ref pRemain); #else string functionName = "sqlite3_prepare"; SQLiteErrorCode rc = UnsafeNativeMethods.sqlite3_prepare( GetIntPtr(), pSql, nSql, ref statement, ref pRemain); #endif #endif if (rc != SQLiteErrorCode.Ok) throw new SQLiteException(rc, functionName); } finally { if (pSql != IntPtr.Zero) { SQLiteMemory.Free(pSql); pSql = IntPtr.Zero; } } } /////////////////////////////////////////////////////////////////////// /// /// This method attempts to "unlock" the associated native connection /// handle by finalizing the previously prepared statement. If the /// statement is already finalized, nothing is done. If the statement /// cannot be finalized for any reason, an exception will be thrown. /// public void Unlock() { CheckDisposed(); if (statement == IntPtr.Zero) return; #if !SQLITE_STANDARD string functionName = "sqlite3_finalize_interop"; SQLiteErrorCode rc = UnsafeNativeMethods.sqlite3_finalize_interop( statement); #else string functionName = "sqlite3_finalize"; SQLiteErrorCode rc = UnsafeNativeMethods.sqlite3_finalize( statement); #endif if (rc != SQLiteErrorCode.Ok) throw new SQLiteException(rc, functionName); statement = IntPtr.Zero; } #endregion /////////////////////////////////////////////////////////////////////// #region IDisposable Members /// /// Disposes of this object instance. /// public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } #endregion /////////////////////////////////////////////////////////////////////// #region IDisposable "Pattern" Members /// /// Non-zero if this object instance has been disposed. /// private bool disposed; /// /// Throws an exception if this object instance has been disposed. /// private void CheckDisposed() /* throw */ { #if THROW_ON_DISPOSED if (disposed) { throw new ObjectDisposedException( typeof(SQLiteConnectionLock).Name); } #endif } /////////////////////////////////////////////////////////////////////// /// /// Disposes or finalizes this object instance. /// /// /// Non-zero if this object is being disposed; otherwise, this object /// is being finalized. /// protected virtual void Dispose(bool disposing) { try { if (!disposed) { //if (disposing) //{ // //////////////////////////////////// // // dispose managed resources here... // //////////////////////////////////// //} ////////////////////////////////////// // release unmanaged resources here... ////////////////////////////////////// if (statement != IntPtr.Zero) { // // NOTE: This should never happen. This object was // disposed (or finalized) without the Unlock // method being called first. // try { if (HelperMethods.LogPrepare(GetFlags())) { /* throw */ SQLiteLog.LogMessage( SQLiteErrorCode.Misuse, HelperMethods.StringFormat( CultureInfo.CurrentCulture, StatementMessageFormat, disposing ? "disposed" : "finalized", statement)); } } catch { // do nothing. } #if DEBUG Debugger.Break(); #endif } } } finally { // // NOTE: Everything should be fully disposed at this point. // disposed = true; } } #endregion /////////////////////////////////////////////////////////////////////// #region Destructor /// /// Finalizes this object instance. /// ~SQLiteConnectionLock() { Dispose(false); } #endregion } #endregion /////////////////////////////////////////////////////////////////////////// #region SQLiteChangeSetIterator Class /// /// This class manages the native change set iterator. It is used as the /// base class for the and /// classes. It knows how to /// advance the native iterator handle as well as finalize it. /// internal class SQLiteChangeSetIterator : IDisposable { #region Private Data /// /// The native change set (a.k.a. iterator) handle. /// private IntPtr iterator; /////////////////////////////////////////////////////////////////////// /// /// Non-zero if this instance owns the native iterator handle in the /// field. In that case, this instance will /// finalize the native iterator handle upon being disposed or /// finalized. /// private bool ownHandle; #endregion /////////////////////////////////////////////////////////////////////// #region Protected Constructors /// /// Constructs a new instance of this class using the specified native /// iterator handle. /// /// /// The native iterator handle to use. /// /// /// Non-zero if this instance is to take ownership of the native /// iterator handle specified by . /// protected SQLiteChangeSetIterator( IntPtr iterator, bool ownHandle ) { this.iterator = iterator; this.ownHandle = ownHandle; } #endregion /////////////////////////////////////////////////////////////////////// #region Private Methods /// /// Throws an exception if the native iterator handle is invalid. /// internal void CheckHandle() { if (iterator == IntPtr.Zero) throw new InvalidOperationException("iterator is not open"); } /////////////////////////////////////////////////////////////////////// /// /// Used to query the native iterator handle. This method is only used /// by the class. /// /// /// The native iterator handle -OR- if it /// is not available. /// internal IntPtr GetIntPtr() { return iterator; } #endregion /////////////////////////////////////////////////////////////////////// #region Public Methods /// /// Attempts to advance the native iterator handle to its next item. /// /// /// Non-zero if the native iterator handle was advanced and contains /// more data; otherwise, zero. If the underlying native API returns /// an unexpected value then an exception will be thrown. /// public bool Next() { CheckDisposed(); CheckHandle(); SQLiteErrorCode rc = UnsafeNativeMethods.sqlite3changeset_next( iterator); switch (rc) { case SQLiteErrorCode.Ok: { throw new SQLiteException(SQLiteErrorCode.Ok, "sqlite3changeset_next: unexpected result Ok"); } case SQLiteErrorCode.Row: { return true; } case SQLiteErrorCode.Done: { return false; } default: { throw new SQLiteException(rc, "sqlite3changeset_next"); } } } #endregion /////////////////////////////////////////////////////////////////////// #region Static "Factory" Methods /// /// Attempts to create an instance of this class that is associated /// with the specified native iterator handle. Ownership of the /// native iterator handle is NOT transferred to the new instance of /// this class. /// /// /// The native iterator handle to use. /// /// /// The new instance of this class. No return value is reserved to /// indicate an error; however, if the native iterator handle is not /// valid, any subsequent attempt to make use of it via the returned /// instance of this class may throw exceptions. /// public static SQLiteChangeSetIterator Attach( IntPtr iterator ) { return new SQLiteChangeSetIterator(iterator, false); } #endregion /////////////////////////////////////////////////////////////////////// #region IDisposable Members /// /// Disposes of this object instance. /// public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } #endregion /////////////////////////////////////////////////////////////////////// #region IDisposable "Pattern" Members /// /// Non-zero if this object instance has been disposed. /// private bool disposed; /// /// Throws an exception if this object instance has been disposed. /// private void CheckDisposed() /* throw */ { #if THROW_ON_DISPOSED if (disposed) { throw new ObjectDisposedException( typeof(SQLiteChangeSetIterator).Name); } #endif } /////////////////////////////////////////////////////////////////////// /// /// Disposes or finalizes this object instance. /// /// /// Non-zero if this object is being disposed; otherwise, this object /// is being finalized. /// protected virtual void Dispose(bool disposing) { try { if (!disposed) { //if (disposing) //{ // //////////////////////////////////// // // dispose managed resources here... // //////////////////////////////////// //} ////////////////////////////////////// // release unmanaged resources here... ////////////////////////////////////// if (iterator != IntPtr.Zero) { if (ownHandle) { UnsafeNativeMethods.sqlite3changeset_finalize( iterator); } iterator = IntPtr.Zero; } } } finally { // // NOTE: Everything should be fully disposed at this point. // disposed = true; } } #endregion /////////////////////////////////////////////////////////////////////// #region Destructor /// /// Finalizes this object instance. /// ~SQLiteChangeSetIterator() { Dispose(false); } #endregion } #endregion /////////////////////////////////////////////////////////////////////////// #region SQLiteMemoryChangeSetIterator Class /// /// This class manages the native change set iterator for a set of changes /// contained entirely in memory. /// internal sealed class SQLiteMemoryChangeSetIterator : SQLiteChangeSetIterator { #region Private Data /// /// The native memory buffer allocated to contain the set of changes /// associated with this instance. This will always be freed when this /// instance is disposed or finalized. /// private IntPtr pData; #endregion /////////////////////////////////////////////////////////////////////// #region Private Constructors /// /// Constructs an instance of this class using the specified native /// memory buffer and native iterator handle. /// /// /// The native memory buffer to use. /// /// /// The native iterator handle to use. /// /// /// Non-zero if this instance is to take ownership of the native /// iterator handle specified by . /// private SQLiteMemoryChangeSetIterator( IntPtr pData, IntPtr iterator, bool ownHandle ) : base(iterator, ownHandle) { this.pData = pData; } #endregion /////////////////////////////////////////////////////////////////////// #region Static "Factory" Methods /// /// Attempts to create an instance of this class using the specified /// raw byte data. /// /// /// The raw byte data containing the set of changes for this native /// iterator. /// /// /// The new instance of this class -OR- null if it cannot be created. /// public static SQLiteMemoryChangeSetIterator Create( byte[] rawData ) { SQLiteSessionHelpers.CheckRawData(rawData); SQLiteMemoryChangeSetIterator result = null; IntPtr pData = IntPtr.Zero; IntPtr iterator = IntPtr.Zero; try { int nData = 0; pData = SQLiteBytes.ToIntPtr(rawData, ref nData); if (pData == IntPtr.Zero) throw new SQLiteException(SQLiteErrorCode.NoMem, null); SQLiteErrorCode rc = UnsafeNativeMethods.sqlite3changeset_start( ref iterator, nData, pData); if (rc != SQLiteErrorCode.Ok) throw new SQLiteException(rc, "sqlite3changeset_start"); result = new SQLiteMemoryChangeSetIterator( pData, iterator, true); } finally { if (result == null) { if (iterator != IntPtr.Zero) { UnsafeNativeMethods.sqlite3changeset_finalize( iterator); iterator = IntPtr.Zero; } if (pData != IntPtr.Zero) { SQLiteMemory.Free(pData); pData = IntPtr.Zero; } } } return result; } /////////////////////////////////////////////////////////////////////// /// /// Attempts to create an instance of this class using the specified /// raw byte data. /// /// /// The raw byte data containing the set of changes for this native /// iterator. /// /// /// The flags used to create the change set iterator. /// /// /// The new instance of this class -OR- null if it cannot be created. /// public static SQLiteMemoryChangeSetIterator Create( byte[] rawData, SQLiteChangeSetStartFlags flags ) { SQLiteSessionHelpers.CheckRawData(rawData); SQLiteMemoryChangeSetIterator result = null; IntPtr pData = IntPtr.Zero; IntPtr iterator = IntPtr.Zero; try { int nData = 0; pData = SQLiteBytes.ToIntPtr(rawData, ref nData); if (pData == IntPtr.Zero) throw new SQLiteException(SQLiteErrorCode.NoMem, null); SQLiteErrorCode rc = UnsafeNativeMethods.sqlite3changeset_start_v2( ref iterator, nData, pData, flags); if (rc != SQLiteErrorCode.Ok) throw new SQLiteException(rc, "sqlite3changeset_start_v2"); result = new SQLiteMemoryChangeSetIterator( pData, iterator, true); } finally { if (result == null) { if (iterator != IntPtr.Zero) { UnsafeNativeMethods.sqlite3changeset_finalize( iterator); iterator = IntPtr.Zero; } if (pData != IntPtr.Zero) { SQLiteMemory.Free(pData); pData = IntPtr.Zero; } } } return result; } #endregion /////////////////////////////////////////////////////////////////////// #region IDisposable "Pattern" Members /// /// Non-zero if this object instance has been disposed. /// private bool disposed; /// /// Throws an exception if this object instance has been disposed. /// private void CheckDisposed() /* throw */ { #if THROW_ON_DISPOSED if (disposed) { throw new ObjectDisposedException( typeof(SQLiteMemoryChangeSetIterator).Name); } #endif } /////////////////////////////////////////////////////////////////////// /// /// Disposes or finalizes this object instance. /// /// /// Non-zero if this object is being disposed; otherwise, this object /// is being finalized. /// protected override void Dispose(bool disposing) { // // NOTE: Must dispose of the base class first (leaky abstraction) // because it contains the iterator handle, which must be // closed *prior* to freeing the underlying memory. // base.Dispose(disposing); try { if (!disposed) { //if (disposing) //{ // //////////////////////////////////// // // dispose managed resources here... // //////////////////////////////////// //} ////////////////////////////////////// // release unmanaged resources here... ////////////////////////////////////// if (pData != IntPtr.Zero) { SQLiteMemory.Free(pData); pData = IntPtr.Zero; } } } finally { // // NOTE: Everything should be fully disposed at this point. // disposed = true; } } #endregion } #endregion /////////////////////////////////////////////////////////////////////////// #region SQLiteStreamChangeSetIterator Class /// /// This class manages the native change set iterator for a set of changes /// backed by a instance. /// internal sealed class SQLiteStreamChangeSetIterator : SQLiteChangeSetIterator { #region Private Data /// /// The instance that is managing /// the underlying used as the backing store for /// the set of changes associated with this native change set iterator. /// private SQLiteStreamAdapter streamAdapter; #endregion /////////////////////////////////////////////////////////////////////// #region Private Constructors /// /// Constructs an instance of this class using the specified native /// iterator handle and . /// /// /// The instance to use. /// /// /// The native iterator handle to use. /// /// /// Non-zero if this instance is to take ownership of the native /// iterator handle specified by . /// private SQLiteStreamChangeSetIterator( SQLiteStreamAdapter streamAdapter, IntPtr iterator, bool ownHandle ) : base(iterator, ownHandle) { this.streamAdapter = streamAdapter; } #endregion /////////////////////////////////////////////////////////////////////// #region Static "Factory" Methods /// /// Attempts to create an instance of this class using the specified /// . /// /// /// The where the raw byte data for the set of /// changes may be read. /// /// /// The flags associated with the parent connection. /// /// /// The new instance of this class -OR- null if it cannot be created. /// public static SQLiteStreamChangeSetIterator Create( Stream stream, SQLiteConnectionFlags connectionFlags ) { if (stream == null) throw new ArgumentNullException("stream"); SQLiteStreamAdapter streamAdapter = null; SQLiteStreamChangeSetIterator result = null; IntPtr iterator = IntPtr.Zero; try { streamAdapter = new SQLiteStreamAdapter(stream, connectionFlags); SQLiteErrorCode rc = UnsafeNativeMethods.sqlite3changeset_start_strm( ref iterator, streamAdapter.GetInputDelegate(), IntPtr.Zero); if (rc != SQLiteErrorCode.Ok) { throw new SQLiteException( rc, "sqlite3changeset_start_strm"); } result = new SQLiteStreamChangeSetIterator( streamAdapter, iterator, true); } finally { if (result == null) { if (iterator != IntPtr.Zero) { UnsafeNativeMethods.sqlite3changeset_finalize( iterator); iterator = IntPtr.Zero; } if (streamAdapter != null) { streamAdapter.Dispose(); streamAdapter = null; } } } return result; } /////////////////////////////////////////////////////////////////////// /// /// Attempts to create an instance of this class using the specified /// . /// /// /// The where the raw byte data for the set of /// changes may be read. /// /// /// The flags associated with the parent connection. /// /// /// The flags used to create the change set iterator. /// /// /// The new instance of this class -OR- null if it cannot be created. /// public static SQLiteStreamChangeSetIterator Create( Stream stream, SQLiteConnectionFlags connectionFlags, SQLiteChangeSetStartFlags startFlags ) { if (stream == null) throw new ArgumentNullException("stream"); SQLiteStreamAdapter streamAdapter = null; SQLiteStreamChangeSetIterator result = null; IntPtr iterator = IntPtr.Zero; try { streamAdapter = new SQLiteStreamAdapter(stream, connectionFlags); SQLiteErrorCode rc = UnsafeNativeMethods.sqlite3changeset_start_v2_strm( ref iterator, streamAdapter.GetInputDelegate(), IntPtr.Zero, startFlags); if (rc != SQLiteErrorCode.Ok) { throw new SQLiteException( rc, "sqlite3changeset_start_v2_strm"); } result = new SQLiteStreamChangeSetIterator( streamAdapter, iterator, true); } finally { if (result == null) { if (iterator != IntPtr.Zero) { UnsafeNativeMethods.sqlite3changeset_finalize( iterator); iterator = IntPtr.Zero; } if (streamAdapter != null) { streamAdapter.Dispose(); streamAdapter = null; } } } return result; } #endregion /////////////////////////////////////////////////////////////////////// #region IDisposable "Pattern" Members /// /// Non-zero if this object instance has been disposed. /// private bool disposed; /// /// Throws an exception if this object instance has been disposed. /// private void CheckDisposed() /* throw */ { #if THROW_ON_DISPOSED if (disposed) { throw new ObjectDisposedException( typeof(SQLiteStreamChangeSetIterator).Name); } #endif } /////////////////////////////////////////////////////////////////////// /// /// Disposes or finalizes this object instance. /// /// /// Non-zero if this object is being disposed; otherwise, this object /// is being finalized. /// protected override void Dispose(bool disposing) { try { if (!disposed) { //if (disposing) //{ // //////////////////////////////////// // // dispose managed resources here... // //////////////////////////////////// //} ////////////////////////////////////// // release unmanaged resources here... ////////////////////////////////////// } } finally { base.Dispose(disposing); // // NOTE: Everything should be fully disposed at this point. // disposed = true; } } #endregion } #endregion /////////////////////////////////////////////////////////////////////////// #region SQLiteStreamAdapter Class /// /// This class is used to act as a bridge between a /// instance and the delegates used with the native streaming API. /// internal sealed class SQLiteStreamAdapter : IDisposable { #region Private Data /// /// The managed stream instance used to in order to service the native /// delegates for both input and output. /// private Stream stream; /// /// The flags associated with the connection. /// private SQLiteConnectionFlags flags; /////////////////////////////////////////////////////////////////////// /// /// The delegate used to provide input to the native streaming API. /// It will be null -OR- point to the method. /// private UnsafeNativeMethods.xSessionInput xInput; /// /// The delegate used to provide output to the native streaming API. /// It will be null -OR- point to the method. /// private UnsafeNativeMethods.xSessionOutput xOutput; #endregion /////////////////////////////////////////////////////////////////////// #region Public Constructors /// /// Constructs a new instance of this class using the specified managed /// stream and connection flags. /// /// /// The managed stream instance to be used in order to service the /// native delegates for both input and output. /// /// /// The flags associated with the parent connection. /// public SQLiteStreamAdapter( Stream stream, SQLiteConnectionFlags flags ) { this.stream = stream; this.flags = flags; } #endregion /////////////////////////////////////////////////////////////////////// #region Private Methods /// /// Queries and returns the flags associated with the connection for /// this instance. /// /// /// The value. There is no return /// value reserved to indicate an error. /// private SQLiteConnectionFlags GetFlags() { return flags; } #endregion /////////////////////////////////////////////////////////////////////// #region Public Methods /// /// Returns a delegate that wraps the method, /// creating it first if necessary. /// /// /// A delegate that refers to the method. /// public UnsafeNativeMethods.xSessionInput GetInputDelegate() { CheckDisposed(); if (xInput == null) xInput = new UnsafeNativeMethods.xSessionInput(Input); return xInput; } /////////////////////////////////////////////////////////////////////// /// /// Returns a delegate that wraps the method, /// creating it first if necessary. /// /// /// A delegate that refers to the method. /// public UnsafeNativeMethods.xSessionOutput GetOutputDelegate() { CheckDisposed(); if (xOutput == null) xOutput = new UnsafeNativeMethods.xSessionOutput(Output); return xOutput; } #endregion /////////////////////////////////////////////////////////////////////// #region Native Callback Methods /// /// This method attempts to read bytes from /// the managed stream, writing them to the /// buffer. /// /// /// Optional extra context information. Currently, this will always /// have a value of . /// /// /// A preallocated native buffer to receive the requested input bytes. /// It must be at least bytes in size. /// /// /// Upon entry, the number of bytes to read. Upon exit, the number of /// bytes actually read. This value may be zero upon exit. /// /// /// The value upon success -OR- an /// appropriate error code upon failure. /// private SQLiteErrorCode Input( IntPtr context, IntPtr pData, ref int nData ) { try { Stream localStream = stream; if (localStream == null) return SQLiteErrorCode.Misuse; if (nData > 0) { byte[] bytes = new byte[nData]; int nRead = localStream.Read(bytes, 0, nData); if ((nRead > 0) && (pData != IntPtr.Zero)) Marshal.Copy(bytes, 0, pData, nRead); nData = nRead; } return SQLiteErrorCode.Ok; } catch (Exception e) { try { if (HelperMethods.LogCallbackExceptions(GetFlags())) { SQLiteLog.LogMessage( SQLiteBase.COR_E_EXCEPTION, HelperMethods.StringFormat( CultureInfo.CurrentCulture, UnsafeNativeMethods.ExceptionMessageFormat, "xSessionInput", e)); /* throw */ } } catch { // do nothing. } } return SQLiteErrorCode.IoErr_Read; } /////////////////////////////////////////////////////////////////////// /// /// This method attempts to write bytes to /// the managed stream, reading them from the /// buffer. /// /// /// Optional extra context information. Currently, this will always /// have a value of . /// /// /// A preallocated native buffer containing the requested output /// bytes. It must be at least bytes in /// size. /// /// /// The number of bytes to write. /// /// /// The value upon success -OR- an /// appropriate error code upon failure. /// private SQLiteErrorCode Output( IntPtr context, IntPtr pData, int nData ) { try { Stream localStream = stream; if (localStream == null) return SQLiteErrorCode.Misuse; if (nData > 0) { byte[] bytes = new byte[nData]; if (pData != IntPtr.Zero) Marshal.Copy(pData, bytes, 0, nData); localStream.Write(bytes, 0, nData); } localStream.Flush(); return SQLiteErrorCode.Ok; } catch (Exception e) { try { if (HelperMethods.LogCallbackExceptions(GetFlags())) { SQLiteLog.LogMessage( SQLiteBase.COR_E_EXCEPTION, HelperMethods.StringFormat( CultureInfo.CurrentCulture, UnsafeNativeMethods.ExceptionMessageFormat, "xSessionOutput", e)); /* throw */ } } catch { // do nothing. } } return SQLiteErrorCode.IoErr_Write; } #endregion /////////////////////////////////////////////////////////////////////// #region IDisposable Members /// /// Disposes of this object instance. /// public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } #endregion /////////////////////////////////////////////////////////////////////// #region IDisposable "Pattern" Members /// /// Non-zero if this object instance has been disposed. /// private bool disposed; /// /// Throws an exception if this object instance has been disposed. /// private void CheckDisposed() /* throw */ { #if THROW_ON_DISPOSED if (disposed) { throw new ObjectDisposedException( typeof(SQLiteStreamAdapter).Name); } #endif } /////////////////////////////////////////////////////////////////////// /// /// Disposes or finalizes this object instance. /// /// /// Non-zero if this object is being disposed; otherwise, this object /// is being finalized. /// private /* protected virtual */ void Dispose(bool disposing) { try { if (!disposed) { if (disposing) { //////////////////////////////////// // dispose managed resources here... //////////////////////////////////// if (xInput != null) xInput = null; if (xOutput != null) xOutput = null; if (stream != null) stream = null; /* NOT OWNED */ } ////////////////////////////////////// // release unmanaged resources here... ////////////////////////////////////// } } finally { // // NOTE: Everything should be fully disposed at this point. // disposed = true; } } #endregion /////////////////////////////////////////////////////////////////////// #region Destructor /// /// Finalizes this object instance. /// ~SQLiteStreamAdapter() { Dispose(false); } #endregion } #endregion /////////////////////////////////////////////////////////////////////////// #region SQLiteSessionStreamManager Class /// /// This class manages a collection of /// instances. When used, it takes responsibility for creating, returning, /// and disposing of its instances. /// internal sealed class SQLiteSessionStreamManager : IDisposable { #region Private Data /// /// The managed collection of /// instances, keyed by their associated /// instance. /// private Dictionary streamAdapters; /// /// The flags associated with the connection. /// private SQLiteConnectionFlags flags; #endregion /////////////////////////////////////////////////////////////////////// #region Public Constructors /// /// Constructs a new instance of this class using the specified /// connection flags. /// /// /// The flags associated with the parent connection. /// public SQLiteSessionStreamManager( SQLiteConnectionFlags flags ) { this.flags = flags; InitializeStreamAdapters(); } #endregion /////////////////////////////////////////////////////////////////////// #region Private Methods /// /// Makes sure the collection of /// is created. /// private void InitializeStreamAdapters() { if (streamAdapters != null) return; streamAdapters = new Dictionary(); } /////////////////////////////////////////////////////////////////////// /// /// Makes sure the collection of /// is disposed. /// private void DisposeStreamAdapters() { if (streamAdapters == null) return; foreach (KeyValuePair pair in streamAdapters) { SQLiteStreamAdapter streamAdapter = pair.Value; if (streamAdapter == null) continue; streamAdapter.Dispose(); } streamAdapters.Clear(); streamAdapters = null; } #endregion /////////////////////////////////////////////////////////////////////// #region Public Methods /// /// Attempts to return a instance /// suitable for the specified . /// /// /// The instance. If this value is null, a null /// value will be returned. /// /// /// A instance. Typically, these /// are always freshly created; however, this method is designed to /// return the existing instance /// associated with the specified stream, should one exist. /// public SQLiteStreamAdapter GetAdapter( Stream stream ) { CheckDisposed(); if (stream == null) return null; SQLiteStreamAdapter streamAdapter; if (streamAdapters.TryGetValue(stream, out streamAdapter)) return streamAdapter; streamAdapter = new SQLiteStreamAdapter(stream, flags); streamAdapters.Add(stream, streamAdapter); return streamAdapter; } #endregion /////////////////////////////////////////////////////////////////////// #region IDisposable Members /// /// Disposes of this object instance. /// public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } #endregion /////////////////////////////////////////////////////////////////////// #region IDisposable "Pattern" Members /// /// Non-zero if this object instance has been disposed. /// private bool disposed; /// /// Throws an exception if this object instance has been disposed. /// private void CheckDisposed() /* throw */ { #if THROW_ON_DISPOSED if (disposed) { throw new ObjectDisposedException( typeof(SQLiteSessionStreamManager).Name); } #endif } /////////////////////////////////////////////////////////////////////// /// /// Disposes or finalizes this object instance. /// /// /// Non-zero if this object is being disposed; otherwise, this object /// is being finalized. /// private /* protected virtual */ void Dispose(bool disposing) { try { if (!disposed) { if (disposing) { //////////////////////////////////// // dispose managed resources here... //////////////////////////////////// DisposeStreamAdapters(); } ////////////////////////////////////// // release unmanaged resources here... ////////////////////////////////////// } } finally { // // NOTE: Everything should be fully disposed at this point. // disposed = true; } } #endregion /////////////////////////////////////////////////////////////////////// #region Destructor /// /// Finalizes this object instance. /// ~SQLiteSessionStreamManager() { Dispose(false); } #endregion } #endregion /////////////////////////////////////////////////////////////////////////// #region SQLiteChangeGroup Class /// /// This class represents a group of change sets (or patch sets). /// internal sealed class SQLiteChangeGroup : ISQLiteChangeGroup { #region Private Data /// /// The instance associated /// with this change group. /// private SQLiteSessionStreamManager streamManager; /// /// The flags associated with the connection. /// private SQLiteConnectionFlags flags; /////////////////////////////////////////////////////////////////////// /// /// The native handle for this change group. This will be deleted when /// this instance is disposed or finalized. /// private IntPtr changeGroup; #endregion /////////////////////////////////////////////////////////////////////// #region Public Constructors /// /// Constructs a new instance of this class using the specified /// connection flags. /// /// /// The flags associated with the parent connection. /// public SQLiteChangeGroup( SQLiteConnectionFlags flags ) { this.flags = flags; InitializeHandle(); } #endregion /////////////////////////////////////////////////////////////////////// #region Private Methods /// /// Throws an exception if the native change group handle is invalid. /// private void CheckHandle() { if (changeGroup == IntPtr.Zero) throw new InvalidOperationException("change group not open"); } /////////////////////////////////////////////////////////////////////// /// /// Makes sure the native change group handle is valid, creating it if /// necessary. /// private void InitializeHandle() { if (changeGroup != IntPtr.Zero) return; SQLiteErrorCode rc = UnsafeNativeMethods.sqlite3changegroup_new( ref changeGroup); if (rc != SQLiteErrorCode.Ok) throw new SQLiteException(rc, "sqlite3changegroup_new"); } /////////////////////////////////////////////////////////////////////// /// /// Makes sure the instance /// is available, creating it if necessary. /// private void InitializeStreamManager() { if (streamManager != null) return; streamManager = new SQLiteSessionStreamManager(flags); } /////////////////////////////////////////////////////////////////////// /// /// Attempts to return a instance /// suitable for the specified . /// /// /// The instance. If this value is null, a null /// value will be returned. /// /// /// A instance. Typically, these /// are always freshly created; however, this method is designed to /// return the existing instance /// associated with the specified stream, should one exist. /// private SQLiteStreamAdapter GetStreamAdapter( Stream stream ) { InitializeStreamManager(); return streamManager.GetAdapter(stream); } #endregion /////////////////////////////////////////////////////////////////////// #region ISQLiteChangeGroup Members /// /// Attempts to add a change set (or patch set) to this change group /// instance. The underlying data must be contained entirely within /// the byte array. /// /// /// The raw byte data for the specified change set (or patch set). /// public void AddChangeSet( byte[] rawData ) { CheckDisposed(); CheckHandle(); SQLiteSessionHelpers.CheckRawData(rawData); IntPtr pData = IntPtr.Zero; try { int nData = 0; pData = SQLiteBytes.ToIntPtr(rawData, ref nData); SQLiteErrorCode rc = UnsafeNativeMethods.sqlite3changegroup_add( changeGroup, nData, pData); if (rc != SQLiteErrorCode.Ok) throw new SQLiteException(rc, "sqlite3changegroup_add"); } finally { if (pData != IntPtr.Zero) { SQLiteMemory.Free(pData); pData = IntPtr.Zero; } } } /////////////////////////////////////////////////////////////////////// /// /// Attempts to add a change set (or patch set) to this change group /// instance. The underlying data will be read from the specified /// . /// /// /// The instance containing the raw change set /// (or patch set) data to read. /// public void AddChangeSet( Stream stream ) { CheckDisposed(); CheckHandle(); if (stream == null) throw new ArgumentNullException("stream"); SQLiteStreamAdapter streamAdapter = GetStreamAdapter(stream); if (streamAdapter == null) { throw new SQLiteException( "could not get or create adapter for input stream"); } SQLiteErrorCode rc = UnsafeNativeMethods.sqlite3changegroup_add_strm( changeGroup, streamAdapter.GetInputDelegate(), IntPtr.Zero); if (rc != SQLiteErrorCode.Ok) throw new SQLiteException(rc, "sqlite3changegroup_add_strm"); } /////////////////////////////////////////////////////////////////////// /// /// Attempts to create and return, via , the /// combined set of changes represented by this change group instance. /// /// /// Upon success, this will contain the raw byte data for all the /// changes in this change group instance. /// public void CreateChangeSet( ref byte[] rawData ) { CheckDisposed(); CheckHandle(); IntPtr pData = IntPtr.Zero; try { int nData = 0; SQLiteErrorCode rc = UnsafeNativeMethods.sqlite3changegroup_output( changeGroup, ref nData, ref pData); if (rc != SQLiteErrorCode.Ok) throw new SQLiteException(rc, "sqlite3changegroup_output"); rawData = SQLiteBytes.FromIntPtr(pData, nData); } finally { if (pData != IntPtr.Zero) { SQLiteMemory.FreeUntracked(pData); pData = IntPtr.Zero; } } } /////////////////////////////////////////////////////////////////////// /// /// Attempts to create and write, via , the /// combined set of changes represented by this change group instance. /// /// /// Upon success, the raw byte data for all the changes in this change /// group instance will be written to this . /// public void CreateChangeSet( Stream stream ) { CheckDisposed(); CheckHandle(); if (stream == null) throw new ArgumentNullException("stream"); SQLiteStreamAdapter streamAdapter = GetStreamAdapter(stream); if (streamAdapter == null) { throw new SQLiteException( "could not get or create adapter for output stream"); } SQLiteErrorCode rc = UnsafeNativeMethods.sqlite3changegroup_output_strm( changeGroup, streamAdapter.GetOutputDelegate(), IntPtr.Zero); if (rc != SQLiteErrorCode.Ok) throw new SQLiteException(rc, "sqlite3changegroup_output_strm"); } #endregion /////////////////////////////////////////////////////////////////////// #region IDisposable Members /// /// Disposes of this object instance. /// public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } #endregion /////////////////////////////////////////////////////////////////////// #region IDisposable "Pattern" Members /// /// Non-zero if this object instance has been disposed. /// private bool disposed; /// /// Throws an exception if this object instance has been disposed. /// private void CheckDisposed() /* throw */ { #if THROW_ON_DISPOSED if (disposed) { throw new ObjectDisposedException( typeof(SQLiteChangeGroup).Name); } #endif } /////////////////////////////////////////////////////////////////////// /// /// Disposes or finalizes this object instance. /// /// /// Non-zero if this object is being disposed; otherwise, this object /// is being finalized. /// private /* protected virtual */ void Dispose(bool disposing) { try { if (!disposed) { if (disposing) { //////////////////////////////////// // dispose managed resources here... //////////////////////////////////// if (streamManager != null) { streamManager.Dispose(); streamManager = null; } } ////////////////////////////////////// // release unmanaged resources here... ////////////////////////////////////// if (changeGroup != IntPtr.Zero) { UnsafeNativeMethods.sqlite3changegroup_delete( changeGroup); changeGroup = IntPtr.Zero; } } } finally { // // NOTE: Everything should be fully disposed at this point. // disposed = true; } } #endregion /////////////////////////////////////////////////////////////////////// #region Destructor /// /// Finalizes this object instance. /// ~SQLiteChangeGroup() { Dispose(false); } #endregion } #endregion /////////////////////////////////////////////////////////////////////////// #region SQLiteSession Class /// /// This class represents the change tracking session associated with a /// database. /// internal sealed class SQLiteSession : SQLiteConnectionLock, ISQLiteSession { #region Private Data /// /// The instance associated /// with this session. /// private SQLiteSessionStreamManager streamManager; /// /// The name of the database (e.g. "main") for this session. /// private string databaseName; /////////////////////////////////////////////////////////////////////// /// /// The native handle for this session. This will be deleted when /// this instance is disposed or finalized. /// private IntPtr session; /////////////////////////////////////////////////////////////////////// /// /// The delegate used to provide table filtering to the native API. /// It will be null -OR- point to the method. /// private UnsafeNativeMethods.xSessionFilter xFilter; /// /// The managed callback used to filter tables for this session. Set /// via the method. /// private SessionTableFilterCallback tableFilterCallback; /// /// The optional application-defined context data that was passed to /// the method. This value may be null. /// private object tableFilterClientData; #endregion /////////////////////////////////////////////////////////////////////// #region Public Constructors /// /// Constructs a new instance of this class using the specified wrapped /// native connection handle and associated flags. /// /// /// The wrapped native connection handle to be associated with this /// session. /// /// /// The flags associated with the connection represented by the /// value. /// /// /// The name of the database (e.g. "main") for this session. /// public SQLiteSession( SQLiteConnectionHandle handle, SQLiteConnectionFlags flags, string databaseName ) : base(handle, flags, true) { this.databaseName = databaseName; InitializeHandle(); } #endregion /////////////////////////////////////////////////////////////////////// #region Private Methods /// /// Throws an exception if the native session handle is invalid. /// private void CheckHandle() { if (session == IntPtr.Zero) throw new InvalidOperationException("session is not open"); } /////////////////////////////////////////////////////////////////////// /// /// Makes sure the native session handle is valid, creating it if /// necessary. /// private void InitializeHandle() { if (session != IntPtr.Zero) return; SQLiteErrorCode rc = UnsafeNativeMethods.sqlite3session_create( GetIntPtr(), SQLiteString.GetUtf8BytesFromString(databaseName), ref session); if (rc != SQLiteErrorCode.Ok) throw new SQLiteException(rc, "sqlite3session_create"); } /////////////////////////////////////////////////////////////////////// /// /// This method sets up the internal table filtering associated state /// of this instance. /// /// /// The table filter callback -OR- null to clear any existing table /// filter callback. /// /// /// The optional application-defined context data. This value may be /// null. /// /// /// The native /// delegate -OR- null to clear any existing table filter. /// private UnsafeNativeMethods.xSessionFilter ApplyTableFilter( SessionTableFilterCallback callback, /* in: NULL OK */ object clientData /* in: NULL OK */ ) { tableFilterCallback = callback; tableFilterClientData = clientData; if (callback == null) { if (xFilter != null) xFilter = null; return null; } if (xFilter == null) xFilter = new UnsafeNativeMethods.xSessionFilter(Filter); return xFilter; } /////////////////////////////////////////////////////////////////////// /// /// Makes sure the instance /// is available, creating it if necessary. /// private void InitializeStreamManager() { if (streamManager != null) return; streamManager = new SQLiteSessionStreamManager(GetFlags()); } /////////////////////////////////////////////////////////////////////// /// /// Attempts to return a instance /// suitable for the specified . /// /// /// The instance. If this value is null, a null /// value will be returned. /// /// /// A instance. Typically, these /// are always freshly created; however, this method is designed to /// return the existing instance /// associated with the specified stream, should one exist. /// private SQLiteStreamAdapter GetStreamAdapter( Stream stream ) { InitializeStreamManager(); return streamManager.GetAdapter(stream); } #endregion /////////////////////////////////////////////////////////////////////// #region Native Callback Methods /// /// This method is called when determining if a table needs to be /// included in the tracked changes for the associated database. /// /// /// Optional extra context information. Currently, this will always /// have a value of . /// /// /// The native pointer to the name of the table. /// /// /// Non-zero if changes to the specified table should be considered; /// otherwise, zero. /// private int Filter( IntPtr context, /* NOT USED */ IntPtr pTblName ) { try { return tableFilterCallback(tableFilterClientData, SQLiteString.StringFromUtf8IntPtr(pTblName)) ? 1 : 0; } catch (Exception e) { try { if (HelperMethods.LogCallbackExceptions(GetFlags())) { SQLiteLog.LogMessage( /* throw */ SQLiteBase.COR_E_EXCEPTION, HelperMethods.StringFormat( CultureInfo.CurrentCulture, UnsafeNativeMethods.ExceptionMessageFormat, "xSessionFilter", e)); } } catch { // do nothing. } } return 0; } #endregion /////////////////////////////////////////////////////////////////////// #region ISQLiteSession Members /// /// Determines if this session is currently tracking changes to its /// associated database. /// /// /// Non-zero if changes to the associated database are being trakced; /// otherwise, zero. /// public bool IsEnabled() { CheckDisposed(); CheckHandle(); return UnsafeNativeMethods.sqlite3session_enable(session, -1) != 0; } /////////////////////////////////////////////////////////////////////// /// /// Enables tracking of changes to the associated database. /// public void SetToEnabled() { CheckDisposed(); CheckHandle(); UnsafeNativeMethods.sqlite3session_enable(session, 1); } /////////////////////////////////////////////////////////////////////// /// /// Disables tracking of changes to the associated database. /// public void SetToDisabled() { CheckDisposed(); CheckHandle(); UnsafeNativeMethods.sqlite3session_enable(session, 0); } /////////////////////////////////////////////////////////////////////// /// /// Determines if this session is currently set to mark changes as /// indirect (i.e. as though they were made via a trigger or foreign /// key action). /// /// /// Non-zero if changes to the associated database are being marked as /// indirect; otherwise, zero. /// public bool IsIndirect() { CheckDisposed(); CheckHandle(); return UnsafeNativeMethods.sqlite3session_indirect(session, -1) != 0; } /////////////////////////////////////////////////////////////////////// /// /// Sets the indirect flag for this session. Subsequent changes will /// be marked as indirect until this flag is changed again. /// public void SetToIndirect() { CheckDisposed(); CheckHandle(); UnsafeNativeMethods.sqlite3session_indirect(session, 1); } /////////////////////////////////////////////////////////////////////// /// /// Clears the indirect flag for this session. Subsequent changes will /// be marked as direct until this flag is changed again. /// public void SetToDirect() { CheckDisposed(); CheckHandle(); UnsafeNativeMethods.sqlite3session_indirect(session, 0); } /////////////////////////////////////////////////////////////////////// /// /// Determines if there are any tracked changes currently within the /// data for this session. /// /// /// Non-zero if there are no changes within the data for this session; /// otherwise, zero. /// public bool IsEmpty() { CheckDisposed(); CheckHandle(); return UnsafeNativeMethods.sqlite3session_isempty(session) != 0; } /////////////////////////////////////////////////////////////////////// /// /// Upon success, causes changes to the specified table(s) to start /// being tracked. Any tables impacted by calls to this method will /// not cause the callback /// to be invoked. /// /// /// The name of the table to be tracked -OR- null to track all /// applicable tables within this database. /// public void AttachTable( string name /* in: NULL OK */ ) { CheckDisposed(); CheckHandle(); SQLiteErrorCode rc = UnsafeNativeMethods.sqlite3session_attach( session, SQLiteString.GetUtf8BytesFromString(name)); if (rc != SQLiteErrorCode.Ok) throw new SQLiteException(rc, "sqlite3session_attach"); } /////////////////////////////////////////////////////////////////////// /// /// This method is used to set the table filter for this instance. /// /// /// The table filter callback -OR- null to clear any existing table /// filter callback. /// /// /// The optional application-defined context data. This value may be /// null. /// public void SetTableFilter( SessionTableFilterCallback callback, /* in: NULL OK */ object clientData /* in: NULL OK */ ) { CheckDisposed(); CheckHandle(); UnsafeNativeMethods.sqlite3session_table_filter( session, ApplyTableFilter(callback, clientData), IntPtr.Zero); } /////////////////////////////////////////////////////////////////////// /// /// Attempts to create and return, via , the /// set of changes represented by this session instance. /// /// /// Upon success, this will contain the raw byte data for all the /// changes in this session instance. /// public void CreateChangeSet( ref byte[] rawData ) { CheckDisposed(); CheckHandle(); IntPtr pData = IntPtr.Zero; try { int nData = 0; SQLiteErrorCode rc = UnsafeNativeMethods.sqlite3session_changeset( session, ref nData, ref pData); if (rc != SQLiteErrorCode.Ok) throw new SQLiteException(rc, "sqlite3session_changeset"); rawData = SQLiteBytes.FromIntPtr(pData, nData); } finally { if (pData != IntPtr.Zero) { SQLiteMemory.FreeUntracked(pData); pData = IntPtr.Zero; } } } /////////////////////////////////////////////////////////////////////// /// /// Attempts to create and write, via , the /// set of changes represented by this session instance. /// /// /// Upon success, the raw byte data for all the changes in this session /// instance will be written to this . /// public void CreateChangeSet( Stream stream ) { CheckDisposed(); CheckHandle(); if (stream == null) throw new ArgumentNullException("stream"); SQLiteStreamAdapter streamAdapter = GetStreamAdapter(stream); if (streamAdapter == null) { throw new SQLiteException( "could not get or create adapter for output stream"); } SQLiteErrorCode rc = UnsafeNativeMethods.sqlite3session_changeset_strm( session, streamAdapter.GetOutputDelegate(), IntPtr.Zero); if (rc != SQLiteErrorCode.Ok) throw new SQLiteException(rc, "sqlite3session_changeset_strm"); } /////////////////////////////////////////////////////////////////////// /// /// Attempts to create and return, via , the /// set of changes represented by this session instance as a patch set. /// /// /// Upon success, this will contain the raw byte data for all the /// changes in this session instance. /// public void CreatePatchSet( ref byte[] rawData ) { CheckDisposed(); CheckHandle(); IntPtr pData = IntPtr.Zero; try { int nData = 0; SQLiteErrorCode rc = UnsafeNativeMethods.sqlite3session_patchset( session, ref nData, ref pData); if (rc != SQLiteErrorCode.Ok) throw new SQLiteException(rc, "sqlite3session_patchset"); rawData = SQLiteBytes.FromIntPtr(pData, nData); } finally { if (pData != IntPtr.Zero) { SQLiteMemory.FreeUntracked(pData); pData = IntPtr.Zero; } } } /////////////////////////////////////////////////////////////////////// /// /// Attempts to create and write, via , the /// set of changes represented by this session instance as a patch set. /// /// /// Upon success, the raw byte data for all the changes in this session /// instance will be written to this . /// public void CreatePatchSet( Stream stream ) { CheckDisposed(); CheckHandle(); if (stream == null) throw new ArgumentNullException("stream"); SQLiteStreamAdapter streamAdapter = GetStreamAdapter(stream); if (streamAdapter == null) { throw new SQLiteException( "could not get or create adapter for output stream"); } SQLiteErrorCode rc = UnsafeNativeMethods.sqlite3session_patchset_strm( session, streamAdapter.GetOutputDelegate(), IntPtr.Zero); if (rc != SQLiteErrorCode.Ok) throw new SQLiteException(rc, "sqlite3session_patchset_strm"); } /////////////////////////////////////////////////////////////////////// /// /// This method loads the differences between two tables [with the same /// name, set of columns, and primary key definition] into this session /// instance. /// /// /// The name of the database containing the table with the original /// data (i.e. it will need updating in order to be identical to the /// one within the database associated with this session instance). /// /// /// The name of the table. /// public void LoadDifferencesFromTable( string fromDatabaseName, string tableName ) { CheckDisposed(); CheckHandle(); if (fromDatabaseName == null) throw new ArgumentNullException("fromDatabaseName"); if (tableName == null) throw new ArgumentNullException("tableName"); IntPtr pError = IntPtr.Zero; try { SQLiteErrorCode rc = UnsafeNativeMethods.sqlite3session_diff( session, SQLiteString.GetUtf8BytesFromString(fromDatabaseName), SQLiteString.GetUtf8BytesFromString(tableName), ref pError); if (rc != SQLiteErrorCode.Ok) { string error = null; if (pError != IntPtr.Zero) { error = SQLiteString.StringFromUtf8IntPtr(pError); if (!String.IsNullOrEmpty(error)) { error = HelperMethods.StringFormat( CultureInfo.CurrentCulture, ": {0}", error); } } throw new SQLiteException(rc, HelperMethods.StringFormat( CultureInfo.CurrentCulture, "{0}{1}", "sqlite3session_diff", error)); } } finally { if (pError != IntPtr.Zero) { SQLiteMemory.FreeUntracked(pError); pError = IntPtr.Zero; } } } #endregion /////////////////////////////////////////////////////////////////////// #region IDisposable "Pattern" Members /// /// Non-zero if this object instance has been disposed. /// private bool disposed; /// /// Throws an exception if this object instance has been disposed. /// private void CheckDisposed() /* throw */ { #if THROW_ON_DISPOSED if (disposed) throw new ObjectDisposedException(typeof(SQLiteSession).Name); #endif } /////////////////////////////////////////////////////////////////////// /// /// Disposes or finalizes this object instance. /// /// /// Non-zero if this object is being disposed; otherwise, this object /// is being finalized. /// protected override void Dispose(bool disposing) { try { if (!disposed) { if (disposing) { //////////////////////////////////// // dispose managed resources here... //////////////////////////////////// if (xFilter != null) xFilter = null; if (streamManager != null) { streamManager.Dispose(); streamManager = null; } } ////////////////////////////////////// // release unmanaged resources here... ////////////////////////////////////// if (session != IntPtr.Zero) { UnsafeNativeMethods.sqlite3session_delete(session); session = IntPtr.Zero; } Unlock(); } } finally { base.Dispose(disposing); // // NOTE: Everything should be fully disposed at this point. // disposed = true; } } #endregion } #endregion /////////////////////////////////////////////////////////////////////////// #region SQLiteChangeSetBase Class /// /// This class represents the abstract concept of a set of changes. It /// acts as the base class for the /// and classes. It derives from /// the class, which is used to hold /// the underlying native connection handle open until the instances of /// this class are disposed or finalized. It also provides the ability /// to construct wrapped native delegates of the /// and /// types. /// internal class SQLiteChangeSetBase : SQLiteConnectionLock { #region Private Constructors /// /// Constructs an instance of this class using the specified wrapped /// native connection handle. /// /// /// The wrapped native connection handle to be associated with this /// change set. /// /// /// The flags associated with the connection represented by the /// value. /// internal SQLiteChangeSetBase( SQLiteConnectionHandle handle, SQLiteConnectionFlags flags ) : base(handle, flags, true) { // do nothing. } #endregion /////////////////////////////////////////////////////////////////////// #region Private Methods /// /// Creates and returns a concrete implementation of the /// interface. /// /// /// The native iterator handle to use. /// /// /// An instance of the /// interface, which can be used to fetch metadata associated with /// the current item in this set of changes. /// private ISQLiteChangeSetMetadataItem CreateMetadataItem( IntPtr iterator ) { return new SQLiteChangeSetMetadataItem( SQLiteChangeSetIterator.Attach(iterator)); } #endregion /////////////////////////////////////////////////////////////////////// #region Protected Methods /// /// Attempts to create a /// native delegate /// that invokes the specified /// delegate. /// /// /// The to invoke when the /// native delegate /// is called. If this value is null then null is returned. /// /// /// The optional application-defined context data. This value may be /// null. /// /// /// The created /// native delegate -OR- null if it cannot be created. /// protected UnsafeNativeMethods.xSessionFilter GetDelegate( SessionTableFilterCallback tableFilterCallback, object clientData ) { if (tableFilterCallback == null) return null; UnsafeNativeMethods.xSessionFilter xFilter; xFilter = new UnsafeNativeMethods.xSessionFilter( delegate(IntPtr context, IntPtr pTblName) { try { string name = SQLiteString.StringFromUtf8IntPtr( pTblName); return tableFilterCallback(clientData, name) ? 1 : 0; } catch (Exception e) { try { if (HelperMethods.LogCallbackExceptions(GetFlags())) { SQLiteLog.LogMessage( /* throw */ SQLiteBase.COR_E_EXCEPTION, HelperMethods.StringFormat( CultureInfo.CurrentCulture, UnsafeNativeMethods.ExceptionMessageFormat, "xSessionFilter", e)); } } catch { // do nothing. } } return 0; }); return xFilter; } /////////////////////////////////////////////////////////////////////// /// /// Attempts to create a /// native delegate /// that invokes the specified /// delegate. /// /// /// The to invoke when the /// native delegate /// is called. If this value is null then null is returned. /// /// /// The optional application-defined context data. This value may be /// null. /// /// /// The created /// native delegate -OR- null if it cannot be created. /// protected UnsafeNativeMethods.xSessionConflict GetDelegate( SessionConflictCallback conflictCallback, object clientData ) { if (conflictCallback == null) return null; UnsafeNativeMethods.xSessionConflict xConflict; xConflict = new UnsafeNativeMethods.xSessionConflict( delegate(IntPtr context, SQLiteChangeSetConflictType type, IntPtr iterator) { try { ISQLiteChangeSetMetadataItem item = CreateMetadataItem( iterator); if (item == null) { throw new SQLiteException( "could not create metadata item"); } return conflictCallback(clientData, type, item); } catch (Exception e) { try { if (HelperMethods.LogCallbackExceptions(GetFlags())) { SQLiteLog.LogMessage( /* throw */ SQLiteBase.COR_E_EXCEPTION, HelperMethods.StringFormat( CultureInfo.CurrentCulture, UnsafeNativeMethods.ExceptionMessageFormat, "xSessionConflict", e)); } } catch { // do nothing. } } return SQLiteChangeSetConflictResult.Abort; }); return xConflict; } #endregion /////////////////////////////////////////////////////////////////////// #region IDisposable "Pattern" Members /// /// Non-zero if this object instance has been disposed. /// private bool disposed; /// /// Throws an exception if this object instance has been disposed. /// private void CheckDisposed() /* throw */ { #if THROW_ON_DISPOSED if (disposed) { throw new ObjectDisposedException( typeof(SQLiteChangeSetBase).Name); } #endif } /////////////////////////////////////////////////////////////////////// /// /// Disposes or finalizes this object instance. /// /// /// Non-zero if this object is being disposed; otherwise, this object /// is being finalized. /// protected override void Dispose(bool disposing) { try { if (!disposed) { if (disposing) { //////////////////////////////////// // dispose managed resources here... //////////////////////////////////// } ////////////////////////////////////// // release unmanaged resources here... ////////////////////////////////////// Unlock(); } } finally { base.Dispose(disposing); // // NOTE: Everything should be fully disposed at this point. // disposed = true; } } #endregion } #endregion /////////////////////////////////////////////////////////////////////////// #region SQLiteMemoryChangeSet Class /// /// This class represents a set of changes contained entirely in memory. /// internal sealed class SQLiteMemoryChangeSet : SQLiteChangeSetBase, ISQLiteChangeSet { #region Private Data /// /// The raw byte data for this set of changes. Since this data must /// be marshalled to a native memory buffer before being used, there /// must be enough memory available to store at least two times the /// amount of data contained within it. /// private byte[] rawData; /// /// The flags used to create the change set iterator. /// private SQLiteChangeSetStartFlags startFlags; #endregion /////////////////////////////////////////////////////////////////////// #region Private Constructors /// /// Constructs an instance of this class using the specified raw byte /// data and wrapped native connection handle. /// /// /// The raw byte data for the specified change set (or patch set). /// /// /// The wrapped native connection handle to be associated with this /// set of changes. /// /// /// The flags associated with the connection represented by the /// value. /// internal SQLiteMemoryChangeSet( byte[] rawData, SQLiteConnectionHandle handle, SQLiteConnectionFlags connectionFlags ) : base(handle, connectionFlags) { this.rawData = rawData; this.startFlags = SQLiteChangeSetStartFlags.None; } /////////////////////////////////////////////////////////////////////// /// /// Constructs an instance of this class using the specified raw byte /// data and wrapped native connection handle. /// /// /// The raw byte data for the specified change set (or patch set). /// /// /// The wrapped native connection handle to be associated with this /// set of changes. /// /// /// The flags associated with the connection represented by the /// value. /// /// /// The flags used to create the change set iterator. /// internal SQLiteMemoryChangeSet( byte[] rawData, SQLiteConnectionHandle handle, SQLiteConnectionFlags connectionFlags, SQLiteChangeSetStartFlags startFlags ) : base(handle, connectionFlags) { this.rawData = rawData; this.startFlags = startFlags; } #endregion /////////////////////////////////////////////////////////////////////// #region ISQLiteChangeSet Members /// /// This method "inverts" the set of changes within this instance. /// Applying an inverted set of changes to a database reverses the /// effects of applying the uninverted changes. Specifically: /// ]]>]]> /// Each DELETE change is changed to an INSERT, and /// ]]>]]> /// Each INSERT change is changed to a DELETE, and /// ]]>]]> /// For each UPDATE change, the old.* and new.* values are exchanged. /// ]]>]]> /// This method does not change the order in which changes appear /// within the set of changes. It merely reverses the sense of each /// individual change. /// /// /// The new instance that represents /// the resulting set of changes. /// public ISQLiteChangeSet Invert() { CheckDisposed(); SQLiteSessionHelpers.CheckRawData(rawData); IntPtr pInData = IntPtr.Zero; IntPtr pOutData = IntPtr.Zero; try { int nInData = 0; pInData = SQLiteBytes.ToIntPtr(rawData, ref nInData); int nOutData = 0; SQLiteErrorCode rc = UnsafeNativeMethods.sqlite3changeset_invert( nInData, pInData, ref nOutData, ref pOutData); if (rc != SQLiteErrorCode.Ok) throw new SQLiteException(rc, "sqlite3changeset_invert"); byte[] newData = SQLiteBytes.FromIntPtr(pOutData, nOutData); return new SQLiteMemoryChangeSet( newData, GetHandle(), GetFlags()); } finally { if (pOutData != IntPtr.Zero) { SQLiteMemory.FreeUntracked(pOutData); pOutData = IntPtr.Zero; } if (pInData != IntPtr.Zero) { SQLiteMemory.Free(pInData); pInData = IntPtr.Zero; } } } /////////////////////////////////////////////////////////////////////// /// /// This method combines the specified set of changes with the ones /// contained in this instance. /// /// /// The changes to be combined with those in this instance. /// /// /// The new instance that represents /// the resulting set of changes. /// public ISQLiteChangeSet CombineWith( ISQLiteChangeSet changeSet ) { CheckDisposed(); SQLiteSessionHelpers.CheckRawData(rawData); SQLiteMemoryChangeSet memoryChangeSet = changeSet as SQLiteMemoryChangeSet; if (memoryChangeSet == null) { throw new ArgumentException( "not a memory based change set", "changeSet"); } SQLiteSessionHelpers.CheckRawData(memoryChangeSet.rawData); IntPtr pInData1 = IntPtr.Zero; IntPtr pInData2 = IntPtr.Zero; IntPtr pOutData = IntPtr.Zero; try { int nInData1 = 0; pInData1 = SQLiteBytes.ToIntPtr(rawData, ref nInData1); int nInData2 = 0; pInData2 = SQLiteBytes.ToIntPtr( memoryChangeSet.rawData, ref nInData2); int nOutData = 0; SQLiteErrorCode rc = UnsafeNativeMethods.sqlite3changeset_concat( nInData1, pInData1, nInData2, pInData2, ref nOutData, ref pOutData); if (rc != SQLiteErrorCode.Ok) throw new SQLiteException(rc, "sqlite3changeset_concat"); byte[] newData = SQLiteBytes.FromIntPtr(pOutData, nOutData); return new SQLiteMemoryChangeSet( newData, GetHandle(), GetFlags()); } finally { if (pOutData != IntPtr.Zero) { SQLiteMemory.FreeUntracked(pOutData); pOutData = IntPtr.Zero; } if (pInData2 != IntPtr.Zero) { SQLiteMemory.Free(pInData2); pInData2 = IntPtr.Zero; } if (pInData1 != IntPtr.Zero) { SQLiteMemory.Free(pInData1); pInData1 = IntPtr.Zero; } } } /////////////////////////////////////////////////////////////////////// /// /// Attempts to apply the set of changes in this instance to the /// associated database. /// /// /// The delegate that will need /// to handle any conflicting changes that may arise. /// /// /// The optional application-defined context data. This value may be /// null. /// public void Apply( SessionConflictCallback conflictCallback, object clientData ) { CheckDisposed(); Apply(conflictCallback, null, clientData); } /////////////////////////////////////////////////////////////////////// /// /// Attempts to apply the set of changes in this instance to the /// associated database. /// /// /// The delegate that will need /// to handle any conflicting changes that may arise. /// /// /// The optional delegate /// that can be used to filter the list of tables impacted by the set /// of changes. /// /// /// The optional application-defined context data. This value may be /// null. /// public void Apply( SessionConflictCallback conflictCallback, SessionTableFilterCallback tableFilterCallback, object clientData ) { CheckDisposed(); SQLiteSessionHelpers.CheckRawData(rawData); if (conflictCallback == null) throw new ArgumentNullException("conflictCallback"); UnsafeNativeMethods.xSessionFilter xFilter = GetDelegate( tableFilterCallback, clientData); UnsafeNativeMethods.xSessionConflict xConflict = GetDelegate( conflictCallback, clientData); IntPtr pData = IntPtr.Zero; try { int nData = 0; pData = SQLiteBytes.ToIntPtr(rawData, ref nData); SQLiteErrorCode rc = UnsafeNativeMethods.sqlite3changeset_apply( GetIntPtr(), nData, pData, xFilter, xConflict, IntPtr.Zero); if (rc != SQLiteErrorCode.Ok) throw new SQLiteException(rc, "sqlite3changeset_apply"); } finally { if (pData != IntPtr.Zero) { SQLiteMemory.Free(pData); pData = IntPtr.Zero; } } } #endregion /////////////////////////////////////////////////////////////////////// #region IEnumerable Members /// /// Creates an capable of iterating over the /// items within this set of changes. /// /// /// The new /// instance. /// public IEnumerator GetEnumerator() { if (startFlags != SQLiteChangeSetStartFlags.None) { return new SQLiteMemoryChangeSetEnumerator( rawData, startFlags); } else { return new SQLiteMemoryChangeSetEnumerator(rawData); } } #endregion /////////////////////////////////////////////////////////////////////// #region IEnumerable Members /// /// Creates an capable of iterating over the /// items within this set of changes. /// /// /// The new instance. /// IEnumerator System.Collections.IEnumerable.GetEnumerator() { return GetEnumerator(); } #endregion /////////////////////////////////////////////////////////////////////// #region IDisposable "Pattern" Members /// /// Non-zero if this object instance has been disposed. /// private bool disposed; /// /// Throws an exception if this object instance has been disposed. /// private void CheckDisposed() /* throw */ { #if THROW_ON_DISPOSED if (disposed) { throw new ObjectDisposedException( typeof(SQLiteMemoryChangeSet).Name); } #endif } /////////////////////////////////////////////////////////////////////// /// /// Disposes or finalizes this object instance. /// /// /// Non-zero if this object is being disposed; otherwise, this object /// is being finalized. /// protected override void Dispose(bool disposing) { try { if (!disposed) { if (disposing) { //////////////////////////////////// // dispose managed resources here... //////////////////////////////////// if (rawData != null) rawData = null; } ////////////////////////////////////// // release unmanaged resources here... ////////////////////////////////////// } } finally { base.Dispose(disposing); // // NOTE: Everything should be fully disposed at this point. // disposed = true; } } #endregion } #endregion /////////////////////////////////////////////////////////////////////////// #region SQLiteStreamChangeSet Class /// /// This class represents a set of changes that are backed by a /// instance. /// internal sealed class SQLiteStreamChangeSet : SQLiteChangeSetBase, ISQLiteChangeSet { #region Private Data /// /// The instance that is managing /// the underlying input used as the backing /// store for the set of changes associated with this instance. /// private SQLiteStreamAdapter inputStreamAdapter; /// /// The instance that is managing /// the underlying output used as the backing /// store for the set of changes generated by the /// or methods. /// private SQLiteStreamAdapter outputStreamAdapter; /// /// The instance used as the backing store for /// the set of changes associated with this instance. /// private Stream inputStream; /// /// The instance used as the backing store for /// the set of changes generated by the or /// methods. /// private Stream outputStream; /// /// The flags used to create the change set iterator. /// private SQLiteChangeSetStartFlags startFlags; #endregion /////////////////////////////////////////////////////////////////////// #region Private Constructors /// /// Constructs an instance of this class using the specified streams /// and wrapped native connection handle. /// /// /// The where the raw byte data for the set of /// changes may be read. /// /// /// The where the raw byte data for resulting /// sets of changes may be written. /// /// /// The wrapped native connection handle to be associated with this /// set of changes. /// /// /// The flags associated with the connection represented by the /// value. /// internal SQLiteStreamChangeSet( Stream inputStream, Stream outputStream, SQLiteConnectionHandle handle, SQLiteConnectionFlags connectionFlags ) : base(handle, connectionFlags) { this.inputStream = inputStream; this.outputStream = outputStream; } /////////////////////////////////////////////////////////////////////// /// /// Constructs an instance of this class using the specified streams /// and wrapped native connection handle. /// /// /// The where the raw byte data for the set of /// changes may be read. /// /// /// The where the raw byte data for resulting /// sets of changes may be written. /// /// /// The wrapped native connection handle to be associated with this /// set of changes. /// /// /// The flags associated with the connection represented by the /// value. /// /// /// The flags used to create the change set iterator. /// internal SQLiteStreamChangeSet( Stream inputStream, Stream outputStream, SQLiteConnectionHandle handle, SQLiteConnectionFlags connectionFlags, SQLiteChangeSetStartFlags startFlags ) : base(handle, connectionFlags) { this.inputStream = inputStream; this.outputStream = outputStream; this.startFlags = startFlags; } #endregion /////////////////////////////////////////////////////////////////////// #region Private Methods /// /// Throws an exception if the input stream or its associated stream /// adapter are invalid. /// private void CheckInputStream() { if (inputStream == null) { throw new InvalidOperationException( "input stream unavailable"); } if (inputStreamAdapter == null) { inputStreamAdapter = new SQLiteStreamAdapter( inputStream, GetFlags()); } } /////////////////////////////////////////////////////////////////////// /// /// Throws an exception if the output stream or its associated stream /// adapter are invalid. /// private void CheckOutputStream() { if (outputStream == null) { throw new InvalidOperationException( "output stream unavailable"); } if (outputStreamAdapter == null) { outputStreamAdapter = new SQLiteStreamAdapter( outputStream, GetFlags()); } } #endregion /////////////////////////////////////////////////////////////////////// #region ISQLiteChangeSet Members /// /// This method "inverts" the set of changes within this instance. /// Applying an inverted set of changes to a database reverses the /// effects of applying the uninverted changes. Specifically: /// ]]>]]> /// Each DELETE change is changed to an INSERT, and /// ]]>]]> /// Each INSERT change is changed to a DELETE, and /// ]]>]]> /// For each UPDATE change, the old.* and new.* values are exchanged. /// ]]>]]> /// This method does not change the order in which changes appear /// within the set of changes. It merely reverses the sense of each /// individual change. /// /// /// Since the resulting set of changes is written to the output stream, /// this method always returns null. /// public ISQLiteChangeSet Invert() { CheckDisposed(); CheckInputStream(); CheckOutputStream(); SQLiteErrorCode rc = UnsafeNativeMethods.sqlite3changeset_invert_strm( inputStreamAdapter.GetInputDelegate(), IntPtr.Zero, outputStreamAdapter.GetOutputDelegate(), IntPtr.Zero); if (rc != SQLiteErrorCode.Ok) throw new SQLiteException(rc, "sqlite3changeset_invert_strm"); return null; } /////////////////////////////////////////////////////////////////////// /// /// This method combines the specified set of changes with the ones /// contained in this instance. /// /// /// The changes to be combined with those in this instance. /// /// /// Since the resulting set of changes is written to the output stream, /// this method always returns null. /// public ISQLiteChangeSet CombineWith( ISQLiteChangeSet changeSet ) { CheckDisposed(); CheckInputStream(); CheckOutputStream(); SQLiteStreamChangeSet streamChangeSet = changeSet as SQLiteStreamChangeSet; if (streamChangeSet == null) { throw new ArgumentException( "not a stream based change set", "changeSet"); } streamChangeSet.CheckInputStream(); SQLiteErrorCode rc = UnsafeNativeMethods.sqlite3changeset_concat_strm( inputStreamAdapter.GetInputDelegate(), IntPtr.Zero, streamChangeSet.inputStreamAdapter.GetInputDelegate(), IntPtr.Zero, outputStreamAdapter.GetOutputDelegate(), IntPtr.Zero); if (rc != SQLiteErrorCode.Ok) throw new SQLiteException(rc, "sqlite3changeset_concat_strm"); return null; } /////////////////////////////////////////////////////////////////////// /// /// Attempts to apply the set of changes in this instance to the /// associated database. /// /// /// The delegate that will need /// to handle any conflicting changes that may arise. /// /// /// The optional application-defined context data. This value may be /// null. /// public void Apply( SessionConflictCallback conflictCallback, object clientData ) { CheckDisposed(); Apply(conflictCallback, null, clientData); } /////////////////////////////////////////////////////////////////////// /// /// Attempts to apply the set of changes in this instance to the /// associated database. /// /// /// The delegate that will need /// to handle any conflicting changes that may arise. /// /// /// The optional delegate /// that can be used to filter the list of tables impacted by the set /// of changes. /// /// /// The optional application-defined context data. This value may be /// null. /// public void Apply( SessionConflictCallback conflictCallback, SessionTableFilterCallback tableFilterCallback, object clientData ) { CheckDisposed(); CheckInputStream(); if (conflictCallback == null) throw new ArgumentNullException("conflictCallback"); UnsafeNativeMethods.xSessionFilter xFilter = GetDelegate( tableFilterCallback, clientData); UnsafeNativeMethods.xSessionConflict xConflict = GetDelegate( conflictCallback, clientData); SQLiteErrorCode rc = UnsafeNativeMethods.sqlite3changeset_apply_strm( GetIntPtr(), inputStreamAdapter.GetInputDelegate(), IntPtr.Zero, xFilter, xConflict, IntPtr.Zero); if (rc != SQLiteErrorCode.Ok) throw new SQLiteException(rc, "sqlite3changeset_apply_strm"); } #endregion /////////////////////////////////////////////////////////////////////// #region IEnumerable Members /// /// Creates an capable of iterating over the /// items within this set of changes. /// /// /// The new /// instance. /// public IEnumerator GetEnumerator() { if (startFlags != SQLiteChangeSetStartFlags.None) { return new SQLiteStreamChangeSetEnumerator( inputStream, GetFlags(), startFlags); } else { return new SQLiteStreamChangeSetEnumerator( inputStream, GetFlags()); } } #endregion /////////////////////////////////////////////////////////////////////// #region IEnumerable Members /// /// Creates an capable of iterating over the /// items within this set of changes. /// /// /// The new instance. /// IEnumerator System.Collections.IEnumerable.GetEnumerator() { return GetEnumerator(); } #endregion /////////////////////////////////////////////////////////////////////// #region IDisposable "Pattern" Members /// /// Non-zero if this object instance has been disposed. /// private bool disposed; /// /// Throws an exception if this object instance has been disposed. /// private void CheckDisposed() /* throw */ { #if THROW_ON_DISPOSED if (disposed) { throw new ObjectDisposedException( typeof(SQLiteStreamChangeSet).Name); } #endif } /////////////////////////////////////////////////////////////////////// /// /// Disposes or finalizes this object instance. /// /// /// Non-zero if this object is being disposed; otherwise, this object /// is being finalized. /// protected override void Dispose(bool disposing) { try { if (!disposed) { if (disposing) { //////////////////////////////////// // dispose managed resources here... //////////////////////////////////// if (outputStreamAdapter != null) { outputStreamAdapter.Dispose(); outputStreamAdapter = null; } if (inputStreamAdapter != null) { inputStreamAdapter.Dispose(); inputStreamAdapter = null; } if (outputStream != null) outputStream = null; /* NOT OWNED */ if (inputStream != null) inputStream = null; /* NOT OWNED */ } ////////////////////////////////////// // release unmanaged resources here... ////////////////////////////////////// } } finally { base.Dispose(disposing); // // NOTE: Everything should be fully disposed at this point. // disposed = true; } } #endregion } #endregion /////////////////////////////////////////////////////////////////////////// #region SQLiteChangeSetEnumerator Class /// /// This class represents an that is capable of /// enumerating over a set of changes. It serves as the base class for the /// and /// classes. It manages and /// owns an instance of the class. /// internal abstract class SQLiteChangeSetEnumerator : IEnumerator { #region Private Data /// /// This managed change set iterator is managed and owned by this /// class. It will be disposed when this class is disposed. /// private SQLiteChangeSetIterator iterator; #endregion /////////////////////////////////////////////////////////////////////// #region Public Constructors /// /// Constructs an instance of this class using the specified managed /// change set iterator. /// /// /// The managed iterator instance to use. /// public SQLiteChangeSetEnumerator( SQLiteChangeSetIterator iterator ) { SetIterator(iterator); } #endregion /////////////////////////////////////////////////////////////////////// #region Private Methods /// /// Throws an exception if the managed iterator instance is invalid. /// private void CheckIterator() { if (iterator == null) throw new InvalidOperationException("iterator unavailable"); iterator.CheckHandle(); } /////////////////////////////////////////////////////////////////////// /// /// Sets the managed iterator instance to a new value. /// /// /// The new managed iterator instance to use. /// private void SetIterator( SQLiteChangeSetIterator iterator ) { this.iterator = iterator; } /////////////////////////////////////////////////////////////////////// /// /// Disposes of the managed iterator instance and sets its value to /// null. /// private void CloseIterator() { if (iterator != null) { iterator.Dispose(); iterator = null; } } #endregion /////////////////////////////////////////////////////////////////////// #region Protected Methods /// /// Disposes of the existing managed iterator instance and then sets it /// to a new value. /// /// /// The new managed iterator instance to use. /// protected void ResetIterator( SQLiteChangeSetIterator iterator ) { CloseIterator(); SetIterator(iterator); } #endregion /////////////////////////////////////////////////////////////////////// #region IEnumerator Members /// /// Returns the current change within the set of changes, represented /// by a instance. /// public ISQLiteChangeSetMetadataItem Current { get { CheckDisposed(); return new SQLiteChangeSetMetadataItem(iterator); } } #endregion /////////////////////////////////////////////////////////////////////// #region IEnumerator Members /// /// Returns the current change within the set of changes, represented /// by a instance. /// object Collections.IEnumerator.Current { get { CheckDisposed(); return Current; } } /////////////////////////////////////////////////////////////////////// /// /// Attempts to advance to the next item in the set of changes. /// /// /// Non-zero if more items are available; otherwise, zero. /// public bool MoveNext() { CheckDisposed(); CheckIterator(); return iterator.Next(); } /////////////////////////////////////////////////////////////////////// /// /// Throws because not all the /// derived classes are able to support reset functionality. /// public virtual void Reset() { CheckDisposed(); throw new NotImplementedException(); } #endregion /////////////////////////////////////////////////////////////////////// #region IDisposable Members /// /// Disposes of this object instance. /// public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } #endregion /////////////////////////////////////////////////////////////////////// #region IDisposable "Pattern" Members /// /// Non-zero if this object instance has been disposed. /// private bool disposed; /// /// Throws an exception if this object instance has been disposed. /// private void CheckDisposed() /* throw */ { #if THROW_ON_DISPOSED if (disposed) { throw new ObjectDisposedException( typeof(SQLiteChangeSetEnumerator).Name); } #endif } /////////////////////////////////////////////////////////////////////// /// /// Disposes or finalizes this object instance. /// /// /// Non-zero if this object is being disposed; otherwise, this object /// is being finalized. /// protected virtual void Dispose(bool disposing) { try { if (!disposed) { if (disposing) { //////////////////////////////////// // dispose managed resources here... //////////////////////////////////// CloseIterator(); } ////////////////////////////////////// // release unmanaged resources here... ////////////////////////////////////// } } finally { // // NOTE: Everything should be fully disposed at this point. // disposed = true; } } #endregion /////////////////////////////////////////////////////////////////////// #region Destructor /// /// Finalizes this object instance. /// ~SQLiteChangeSetEnumerator() { Dispose(false); } #endregion } #endregion /////////////////////////////////////////////////////////////////////////// #region SQLiteMemoryChangeSetEnumerator Class /// /// This class represents an that is capable of /// enumerating over a set of changes contained entirely in memory. /// internal sealed class SQLiteMemoryChangeSetEnumerator : SQLiteChangeSetEnumerator { #region Private Data /// /// The raw byte data for this set of changes. Since this data must /// be marshalled to a native memory buffer before being used, there /// must be enough memory available to store at least two times the /// amount of data contained within it. /// private byte[] rawData; /// /// The flags used to create the change set iterator. /// private SQLiteChangeSetStartFlags flags; #endregion /////////////////////////////////////////////////////////////////////// #region Public Constructors /// /// Constructs an instance of this class using the specified raw byte /// data. /// /// /// The raw byte data containing the set of changes for this /// enumerator. /// public SQLiteMemoryChangeSetEnumerator( byte[] rawData ) : base(SQLiteMemoryChangeSetIterator.Create(rawData)) { this.rawData = rawData; this.flags = SQLiteChangeSetStartFlags.None; } /////////////////////////////////////////////////////////////////////// /// /// Constructs an instance of this class using the specified raw byte /// data. /// /// /// The raw byte data containing the set of changes for this /// enumerator. /// /// /// The flags used to create the change set iterator. /// public SQLiteMemoryChangeSetEnumerator( byte[] rawData, SQLiteChangeSetStartFlags flags ) : base(SQLiteMemoryChangeSetIterator.Create(rawData, flags)) { this.rawData = rawData; this.flags = flags; } #endregion /////////////////////////////////////////////////////////////////////// #region IEnumerator Overrides /// /// Resets the enumerator to its initial position. /// public override void Reset() { CheckDisposed(); SQLiteMemoryChangeSetIterator result; if (flags != SQLiteChangeSetStartFlags.None) result = SQLiteMemoryChangeSetIterator.Create(rawData, flags); else result = SQLiteMemoryChangeSetIterator.Create(rawData); ResetIterator(result); } #endregion /////////////////////////////////////////////////////////////////////// #region IDisposable "Pattern" Members /// /// Non-zero if this object instance has been disposed. /// private bool disposed; /// /// Throws an exception if this object instance has been disposed. /// private void CheckDisposed() /* throw */ { #if THROW_ON_DISPOSED if (disposed) { throw new ObjectDisposedException( typeof(SQLiteMemoryChangeSetEnumerator).Name); } #endif } /////////////////////////////////////////////////////////////////////// /// /// Disposes or finalizes this object instance. /// /// /// Non-zero if this object is being disposed; otherwise, this object /// is being finalized. /// protected override void Dispose(bool disposing) { try { if (!disposed) { if (disposing) { //////////////////////////////////// // dispose managed resources here... //////////////////////////////////// } ////////////////////////////////////// // release unmanaged resources here... ////////////////////////////////////// } } finally { base.Dispose(disposing); // // NOTE: Everything should be fully disposed at this point. // disposed = true; } } #endregion } #endregion /////////////////////////////////////////////////////////////////////////// #region SQLiteStreamChangeSetEnumerator Class /// /// This class represents an that is capable of /// enumerating over a set of changes backed by a /// instance. /// internal sealed class SQLiteStreamChangeSetEnumerator : SQLiteChangeSetEnumerator { #region Public Constructors /// /// Constructs an instance of this class using the specified stream. /// /// /// The where the raw byte data for the set of /// changes may be read. /// /// /// The flags associated with the parent connection. /// public SQLiteStreamChangeSetEnumerator( Stream stream, SQLiteConnectionFlags connectionFlags ) : base(SQLiteStreamChangeSetIterator.Create( stream, connectionFlags)) { // do nothing. } /////////////////////////////////////////////////////////////////////// /// /// Constructs an instance of this class using the specified stream. /// /// /// The where the raw byte data for the set of /// changes may be read. /// /// /// The flags associated with the parent connection. /// /// /// The flags used to create the change set iterator. /// public SQLiteStreamChangeSetEnumerator( Stream stream, SQLiteConnectionFlags connectionFlags, SQLiteChangeSetStartFlags startFlags ) : base(SQLiteStreamChangeSetIterator.Create( stream, connectionFlags, startFlags)) { // do nothing. } #endregion /////////////////////////////////////////////////////////////////////// #region IDisposable "Pattern" Members /// /// Non-zero if this object instance has been disposed. /// private bool disposed; /// /// Throws an exception if this object instance has been disposed. /// private void CheckDisposed() /* throw */ { #if THROW_ON_DISPOSED if (disposed) { throw new ObjectDisposedException( typeof(SQLiteStreamChangeSetEnumerator).Name); } #endif } /////////////////////////////////////////////////////////////////////// /// /// Disposes or finalizes this object instance. /// /// /// Non-zero if this object is being disposed; otherwise, this object /// is being finalized. /// protected override void Dispose(bool disposing) { try { //if (!disposed) //{ // if (disposing) // { // //////////////////////////////////// // // dispose managed resources here... // //////////////////////////////////// // } // ////////////////////////////////////// // // release unmanaged resources here... // ////////////////////////////////////// //} } finally { base.Dispose(disposing); // // NOTE: Everything should be fully disposed at this point. // disposed = true; } } #endregion } #endregion /////////////////////////////////////////////////////////////////////////// #region SQLiteChangeSetMetadataItem Class /// /// This interface implements properties and methods used to fetch metadata /// about one change within a set of changes for a database. /// internal sealed class SQLiteChangeSetMetadataItem : ISQLiteChangeSetMetadataItem { #region Private Data /// /// The instance to use. This /// will NOT be owned by this class and will not be disposed upon this /// class being disposed or finalized. /// private SQLiteChangeSetIterator iterator; #endregion /////////////////////////////////////////////////////////////////////// #region Public Constructors /// /// Constructs an instance of this class using the specified iterator /// instance. /// /// /// The managed iterator instance to use. /// public SQLiteChangeSetMetadataItem( SQLiteChangeSetIterator iterator ) { this.iterator = iterator; } #endregion /////////////////////////////////////////////////////////////////////// #region Private Methods /// /// Throws an exception if the managed iterator instance is invalid. /// private void CheckIterator() { if (iterator == null) throw new InvalidOperationException("iterator unavailable"); iterator.CheckHandle(); } /////////////////////////////////////////////////////////////////////// /// /// Populates the underlying data for the , /// , , and /// properties, using the appropriate native /// API. /// private void PopulateOperationMetadata() { if ((tableName == null) || (numberOfColumns == null) || (operationCode == null) || (indirect == null)) { CheckIterator(); IntPtr pTblName = IntPtr.Zero; SQLiteAuthorizerActionCode op = SQLiteAuthorizerActionCode.None; int bIndirect = 0; int nColumns = 0; SQLiteErrorCode rc = UnsafeNativeMethods.sqlite3changeset_op( iterator.GetIntPtr(), ref pTblName, ref nColumns, ref op, ref bIndirect); if (rc != SQLiteErrorCode.Ok) throw new SQLiteException(rc, "sqlite3changeset_op"); tableName = SQLiteString.StringFromUtf8IntPtr(pTblName); numberOfColumns = nColumns; operationCode = op; indirect = (bIndirect != 0); } } /////////////////////////////////////////////////////////////////////// /// /// Populates the underlying data for the /// property using the appropriate /// native API. /// private void PopulatePrimaryKeyColumns() { if (primaryKeyColumns == null) { CheckIterator(); IntPtr pPrimaryKeys = IntPtr.Zero; int nColumns = 0; SQLiteErrorCode rc = UnsafeNativeMethods.sqlite3changeset_pk( iterator.GetIntPtr(), ref pPrimaryKeys, ref nColumns); if (rc != SQLiteErrorCode.Ok) throw new SQLiteException(rc, "sqlite3changeset_pk"); byte[] bytes = SQLiteBytes.FromIntPtr(pPrimaryKeys, nColumns); if (bytes != null) { primaryKeyColumns = new bool[nColumns]; for (int index = 0; index < bytes.Length; index++) primaryKeyColumns[index] = (bytes[index] != 0); } } } /////////////////////////////////////////////////////////////////////// /// /// Populates the underlying data for the /// property using the /// appropriate native API. /// private void PopulateNumberOfForeignKeyConflicts() { if (numberOfForeignKeyConflicts == null) { CheckIterator(); int conflicts = 0; SQLiteErrorCode rc = UnsafeNativeMethods.sqlite3changeset_fk_conflicts( iterator.GetIntPtr(), ref conflicts); if (rc != SQLiteErrorCode.Ok) { throw new SQLiteException(rc, "sqlite3changeset_fk_conflicts"); } numberOfForeignKeyConflicts = conflicts; } } #endregion /////////////////////////////////////////////////////////////////////// #region ISQLiteChangeSetMetadataItem Members /// /// Backing field for the property. This value /// will be null if this field has not yet been populated via the /// underlying native API. /// private string tableName; /// /// The name of the table the change was made to. /// public string TableName { get { CheckDisposed(); PopulateOperationMetadata(); return tableName; } } /////////////////////////////////////////////////////////////////////// /// /// Backing field for the property. This /// value will be null if this field has not yet been populated via the /// underlying native API. /// private int? numberOfColumns; /// /// The number of columns impacted by this change. This value can be /// used to determine the highest valid column index that may be used /// with the , , /// and methods of this interface. It /// will be this value minus one. /// public int NumberOfColumns { get { CheckDisposed(); PopulateOperationMetadata(); return (int)numberOfColumns; } } /////////////////////////////////////////////////////////////////////// /// /// Backing field for the property. This /// value will be null if this field has not yet been populated via the /// underlying native API. /// private SQLiteAuthorizerActionCode? operationCode; /// /// This will contain the value /// , /// , or /// , corresponding to /// the overall type of change this item represents. /// public SQLiteAuthorizerActionCode OperationCode { get { CheckDisposed(); PopulateOperationMetadata(); return (SQLiteAuthorizerActionCode)operationCode; } } /////////////////////////////////////////////////////////////////////// /// /// Backing field for the property. This value /// will be null if this field has not yet been populated via the /// underlying native API. /// private bool? indirect; /// /// Non-zero if this change is considered to be indirect (i.e. as /// though they were made via a trigger or foreign key action). /// public bool Indirect { get { CheckDisposed(); PopulateOperationMetadata(); return (bool)indirect; } } /////////////////////////////////////////////////////////////////////// /// /// Backing field for the property. /// This value will be null if this field has not yet been populated /// via the underlying native API. /// private bool[] primaryKeyColumns; /// /// This array contains a for each column in /// the table associated with this change. The element will be zero /// if the column is not part of the primary key; otherwise, it will /// be non-zero. /// public bool[] PrimaryKeyColumns { get { CheckDisposed(); PopulatePrimaryKeyColumns(); return primaryKeyColumns; } } /////////////////////////////////////////////////////////////////////// /// /// Backing field for the /// property. This value will be null if this field has not yet been /// populated via the underlying native API. /// private int? numberOfForeignKeyConflicts; /// /// This method may only be called from within a /// delegate when the conflict /// type is . It /// returns the total number of known foreign key violations in the /// destination database. /// public int NumberOfForeignKeyConflicts { get { CheckDisposed(); PopulateNumberOfForeignKeyConflicts(); return (int)numberOfForeignKeyConflicts; } } /////////////////////////////////////////////////////////////////////// /// /// Queries and returns the original value of a given column for this /// change. This method may only be called when the /// has a value of /// or /// . /// /// /// The index for the column. This value must be between zero and one /// less than the total number of columns for this table. /// /// /// The original value of a given column for this change. /// public SQLiteValue GetOldValue( int columnIndex ) { CheckDisposed(); CheckIterator(); IntPtr pValue = IntPtr.Zero; SQLiteErrorCode rc = UnsafeNativeMethods.sqlite3changeset_old( iterator.GetIntPtr(), columnIndex, ref pValue); return SQLiteValue.FromIntPtr(pValue); } /////////////////////////////////////////////////////////////////////// /// /// Queries and returns the updated value of a given column for this /// change. This method may only be called when the /// has a value of /// or /// . /// /// /// The index for the column. This value must be between zero and one /// less than the total number of columns for this table. /// /// /// The updated value of a given column for this change. /// public SQLiteValue GetNewValue( int columnIndex ) { CheckDisposed(); CheckIterator(); IntPtr pValue = IntPtr.Zero; SQLiteErrorCode rc = UnsafeNativeMethods.sqlite3changeset_new( iterator.GetIntPtr(), columnIndex, ref pValue); return SQLiteValue.FromIntPtr(pValue); } /////////////////////////////////////////////////////////////////////// /// /// Queries and returns the conflicting value of a given column for /// this change. This method may only be called from within a /// delegate when the conflict /// type is or /// . /// /// /// The index for the column. This value must be between zero and one /// less than the total number of columns for this table. /// /// /// The conflicting value of a given column for this change. /// public SQLiteValue GetConflictValue( int columnIndex ) { CheckDisposed(); CheckIterator(); IntPtr pValue = IntPtr.Zero; SQLiteErrorCode rc = UnsafeNativeMethods.sqlite3changeset_conflict( iterator.GetIntPtr(), columnIndex, ref pValue); return SQLiteValue.FromIntPtr(pValue); } #endregion /////////////////////////////////////////////////////////////////////// #region IDisposable Members /// /// Disposes of this object instance. /// public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } #endregion /////////////////////////////////////////////////////////////////////// #region IDisposable "Pattern" Members /// /// Non-zero if this object instance has been disposed. /// private bool disposed; /// /// Throws an exception if this object instance has been disposed. /// private void CheckDisposed() /* throw */ { #if THROW_ON_DISPOSED if (disposed) { throw new ObjectDisposedException( typeof(SQLiteChangeSetMetadataItem).Name); } #endif } /////////////////////////////////////////////////////////////////////// /// /// Disposes or finalizes this object instance. /// /// /// Non-zero if this object is being disposed; otherwise, this object /// is being finalized. /// private /* protected virtual */ void Dispose(bool disposing) { try { if (!disposed) { if (disposing) { //////////////////////////////////// // dispose managed resources here... //////////////////////////////////// if (iterator != null) iterator = null; /* NOT OWNED */ } ////////////////////////////////////// // release unmanaged resources here... ////////////////////////////////////// } } finally { // // NOTE: Everything should be fully disposed at this point. // disposed = true; } } #endregion /////////////////////////////////////////////////////////////////////// #region Destructor /// /// Finalizes this object instance. /// ~SQLiteChangeSetMetadataItem() { Dispose(false); } #endregion } #endregion }