/********************************************************
* 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
}