/******************************************************** * 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.Generic; using System.IO; using System.Globalization; using System.Runtime.InteropServices; namespace System.Data.SQLite { #region Session Extension Enumerations public enum SQLiteChangeSetConflictType { Data = 1, NotFound = 2, Conflict = 3, Constraint = 4, ForeignKey = 5 } /////////////////////////////////////////////////////////////////////////// public enum SQLiteChangeSetConflictResult { Omit = 0, Replace = 1, Abort = 2 } #endregion /////////////////////////////////////////////////////////////////////////// #region Session Extension Delegates public delegate bool TableFilterDelegate( object context, string name ); /////////////////////////////////////////////////////////////////////////// public delegate SQLiteChangeSetConflictResult ConflictDelegate( object context, SQLiteChangeSetConflictType type, ISQLiteChangeSetMetadataItem item ); #endregion /////////////////////////////////////////////////////////////////////////// #region Session Extension Interfaces public interface ISQLiteChangeSet { bool? IsPatchSet { get; } void Apply(SQLiteConnection connection, ConflictDelegate conflictCallback); void Apply(SQLiteConnection connection, ConflictDelegate conflictCallback, TableFilterDelegate tableFilterCallback); ISQLiteChangeSet Invert(); ISQLiteChangeSet CombineWith(ISQLiteChangeSet changeSet); } /////////////////////////////////////////////////////////////////////////// public interface ISQLiteChangeGroup { void AddChangeSet(byte[] rawData); void AddChangeSet(Stream stream); void CreateChangeSet(ref byte[] rawData); void CreateChangeSet(Stream stream, SQLiteConnectionFlags flags); } /////////////////////////////////////////////////////////////////////////// public interface ISQLiteChangeSetMetadataItem { SQLiteAuthorizerActionCode OperationCode { get; } string TableName { get; } int NumberOfColumns { get; } bool Indirect { get; } bool[] PrimaryKeyColumns { get; } SQLiteValue GetOldValue(int columnIndex); SQLiteValue GetNewValue(int columnIndex); SQLiteValue GetConflictValue(int columnIndex); int NumberOfForeignKeyConflicts { get; } } /////////////////////////////////////////////////////////////////////////// public interface ISQLiteSession { bool IsEnabled(); void SetToEnabled(); void SetToDisabled(); bool IsIndirect(); void SetToIndirect(); void SetToDirect(); bool IsEmpty(); void AttachTable(string name); void SetTableFilter(TableFilterDelegate callback, object context); void CreateChangeSet(ref byte[] rawData); void CreateChangeSet(Stream stream, SQLiteConnectionFlags flags); void CreatePatchSet(ref byte[] rawData); void CreatePatchSet(Stream stream, SQLiteConnectionFlags flags); void LoadDifferencesFromTable(string fromDatabaseName, string tableName); } #endregion /////////////////////////////////////////////////////////////////////////// #region SQLiteChangeSetIterator Class internal sealed class SQLiteChangeSetIterator : IDisposable { #region Private Data private IntPtr pData; private IntPtr iterator; #endregion /////////////////////////////////////////////////////////////////////// #region Private Constructors private SQLiteChangeSetIterator( IntPtr pData, IntPtr iterator ) { this.pData = pData; this.iterator = iterator; } #endregion /////////////////////////////////////////////////////////////////////// #region Static "Factory" Methods public static SQLiteChangeSetIterator Create( byte[] rawData ) { if (rawData == null) throw new ArgumentNullException("rawData"); SQLiteChangeSetIterator 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 SQLiteChangeSetIterator(pData, iterator); } 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 Members public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } #endregion /////////////////////////////////////////////////////////////////////// #region IDisposable "Pattern" Members private bool disposed; private void CheckDisposed() /* throw */ { #if THROW_ON_DISPOSED if (disposed) { throw new ObjectDisposedException( typeof(SQLiteChangeSetIterator).Name); } #endif } /////////////////////////////////////////////////////////////////////// private /* protected virtual */ void Dispose(bool disposing) { try { if (!disposed) { //if (disposing) //{ // //////////////////////////////////// // // dispose managed resources here... // //////////////////////////////////// //} ////////////////////////////////////// // release unmanaged resources here... ////////////////////////////////////// if (iterator != IntPtr.Zero) { UnsafeNativeMethods.sqlite3changeset_finalize( iterator); iterator = IntPtr.Zero; } if (pData != IntPtr.Zero) { SQLiteMemory.Free(pData); pData = IntPtr.Zero; } } } finally { // // NOTE: Everything should be fully disposed at this point. // disposed = true; } } #endregion /////////////////////////////////////////////////////////////////////// #region Destructor ~SQLiteChangeSetIterator() { Dispose(false); } #endregion } #endregion /////////////////////////////////////////////////////////////////////////// #region SQLiteStreamAdapter Class internal sealed class SQLiteStreamAdapter : IDisposable { #region Private Data private Stream stream; private SQLiteConnectionFlags flags; #endregion /////////////////////////////////////////////////////////////////////// #region Public Constructors public SQLiteStreamAdapter( Stream stream, SQLiteConnectionFlags flags ) { this.stream = stream; this.flags = flags; } #endregion /////////////////////////////////////////////////////////////////////// #region Native Callback Methods public SQLiteErrorCode xInput( IntPtr context, IntPtr pData, ref int nData ) { try { if (stream == null) return SQLiteErrorCode.Misuse; if (nData > 0) { byte[] bytes = new byte[nData]; nData = stream.Read(bytes, 0, nData); Marshal.Copy(bytes, 0, pData, nData); } return SQLiteErrorCode.Ok; } catch (Exception e) { try { if ((flags & SQLiteConnectionFlags.LogCallbackException) == SQLiteConnectionFlags.LogCallbackException) { SQLiteLog.LogMessage(SQLiteBase.COR_E_EXCEPTION, HelperMethods.StringFormat(CultureInfo.CurrentCulture, "Caught exception in \"xInput\" method: {0}", e)); /* throw */ } } catch { // do nothing. } } return SQLiteErrorCode.IoErr_Read; } /////////////////////////////////////////////////////////////////////// public SQLiteErrorCode xOutput( IntPtr context, IntPtr pData, int nData ) { try { if (stream == null) return SQLiteErrorCode.Misuse; if (nData > 0) { byte[] bytes = new byte[nData]; Marshal.Copy(pData, bytes, 0, nData); stream.Write(bytes, 0, nData); } stream.Flush(); return SQLiteErrorCode.Ok; } catch (Exception e) { try { if ((flags & SQLiteConnectionFlags.LogCallbackException) == SQLiteConnectionFlags.LogCallbackException) { SQLiteLog.LogMessage(SQLiteBase.COR_E_EXCEPTION, HelperMethods.StringFormat(CultureInfo.CurrentCulture, "Caught exception in \"xOutput\" method: {0}", e)); /* throw */ } } catch { // do nothing. } } return SQLiteErrorCode.IoErr_Write; } #endregion /////////////////////////////////////////////////////////////////////// #region IDisposable Members public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } #endregion /////////////////////////////////////////////////////////////////////// #region IDisposable "Pattern" Members private bool disposed; private void CheckDisposed() /* throw */ { #if THROW_ON_DISPOSED if (disposed) { throw new ObjectDisposedException( typeof(SQLiteStreamAdapter).Name); } #endif } /////////////////////////////////////////////////////////////////////// private /* protected virtual */ void Dispose(bool disposing) { try { if (!disposed) { if (disposing) { //////////////////////////////////// // dispose managed resources here... //////////////////////////////////// if (stream != null) stream = null; /* NOT OWNED: Do not dispose. */ } ////////////////////////////////////// // release unmanaged resources here... ////////////////////////////////////// } } finally { // // NOTE: Everything should be fully disposed at this point. // disposed = true; } } #endregion /////////////////////////////////////////////////////////////////////// #region Destructor ~SQLiteStreamAdapter() { Dispose(false); } #endregion } #endregion /////////////////////////////////////////////////////////////////////////// #region SQLiteSession Class public sealed class SQLiteSession : ISQLiteSession, IDisposable { #region Private Data private IntPtr session; /////////////////////////////////////////////////////////////////////// private TableFilterDelegate tableFilterCallback; private object tableFilterContext; #endregion /////////////////////////////////////////////////////////////////////// #region Public Constructors public SQLiteSession( SQLiteConnection connection, string databaseName ) { UnsafeNativeMethods.sqlite3session_create( SQLiteConnection.GetNativeHandle(connection), SQLiteString.GetUtf8BytesFromString(databaseName), ref session); } #endregion /////////////////////////////////////////////////////////////////////// #region Private Methods private void CheckHandle() { if (session == IntPtr.Zero) throw new InvalidOperationException("session is not open"); } /////////////////////////////////////////////////////////////////////// #region Native Callback Methods private int xFilter( IntPtr context, /* NOT USED */ byte[] tblName ) { return tableFilterCallback(tableFilterContext, SQLiteString.GetStringFromUtf8Bytes(tblName)) ? 1 : 0; } #endregion #endregion /////////////////////////////////////////////////////////////////////// #region ISQLiteSession Members public bool IsEnabled() { CheckDisposed(); CheckHandle(); return UnsafeNativeMethods.sqlite3session_enable(session, -1) != 0; } /////////////////////////////////////////////////////////////////////// public void SetToEnabled() { CheckDisposed(); CheckHandle(); UnsafeNativeMethods.sqlite3session_enable(session, 1); } /////////////////////////////////////////////////////////////////////// public void SetToDisabled() { CheckDisposed(); CheckHandle(); UnsafeNativeMethods.sqlite3session_enable(session, 0); } /////////////////////////////////////////////////////////////////////// public bool IsIndirect() { CheckDisposed(); CheckHandle(); return UnsafeNativeMethods.sqlite3session_indirect(session, -1) != 0; } /////////////////////////////////////////////////////////////////////// public void SetToIndirect() { CheckDisposed(); CheckHandle(); UnsafeNativeMethods.sqlite3session_indirect(session, 1); } /////////////////////////////////////////////////////////////////////// public void SetToDirect() { CheckDisposed(); CheckHandle(); UnsafeNativeMethods.sqlite3session_indirect(session, 0); } /////////////////////////////////////////////////////////////////////// public bool IsEmpty() { CheckDisposed(); CheckHandle(); return UnsafeNativeMethods.sqlite3session_isempty(session) != 0; } /////////////////////////////////////////////////////////////////////// public void AttachTable(string name) { CheckDisposed(); CheckHandle(); SQLiteErrorCode rc = UnsafeNativeMethods.sqlite3session_attach( session, SQLiteString.GetUtf8BytesFromString(name)); if (rc != SQLiteErrorCode.Ok) throw new SQLiteException(rc, "sqlite3session_attach"); } /////////////////////////////////////////////////////////////////////// public void SetTableFilter( TableFilterDelegate callback, object context ) { CheckDisposed(); CheckHandle(); this.tableFilterCallback = callback; this.tableFilterContext = context; UnsafeNativeMethods.sqlite3session_table_filter( session, xFilter, IntPtr.Zero); } /////////////////////////////////////////////////////////////////////// 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.Free(pData); pData = IntPtr.Zero; } } } /////////////////////////////////////////////////////////////////////// public void CreateChangeSet( Stream stream, SQLiteConnectionFlags flags ) { CheckDisposed(); CheckHandle(); SQLiteErrorCode rc = UnsafeNativeMethods.sqlite3session_changeset_strm( session, new SQLiteStreamAdapter(stream, flags).xOutput, IntPtr.Zero); if (rc != SQLiteErrorCode.Ok) throw new SQLiteException(rc, "sqlite3session_changeset_strm"); } /////////////////////////////////////////////////////////////////////// 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.Free(pData); pData = IntPtr.Zero; } } } /////////////////////////////////////////////////////////////////////// public void CreatePatchSet( Stream stream, SQLiteConnectionFlags flags ) { CheckDisposed(); CheckHandle(); SQLiteErrorCode rc = UnsafeNativeMethods.sqlite3session_patchset_strm( session, new SQLiteStreamAdapter(stream, flags).xOutput, IntPtr.Zero); if (rc != SQLiteErrorCode.Ok) throw new SQLiteException(rc, "sqlite3session_patchset_strm"); } /////////////////////////////////////////////////////////////////////// public void LoadDifferencesFromTable( string fromDatabaseName, string tableName ) { CheckDisposed(); CheckHandle(); 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.Free(pError); pError = IntPtr.Zero; } } } #endregion /////////////////////////////////////////////////////////////////////// #region IDisposable Members public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } #endregion /////////////////////////////////////////////////////////////////////// #region IDisposable "Pattern" Members private bool disposed; private void CheckDisposed() /* throw */ { #if THROW_ON_DISPOSED if (disposed) throw new ObjectDisposedException(typeof(SQLiteSession).Name); #endif } /////////////////////////////////////////////////////////////////////// private /* protected virtual */ void Dispose(bool disposing) { try { if (!disposed) { //if (disposing) //{ // //////////////////////////////////// // // dispose managed resources here... // //////////////////////////////////// //} ////////////////////////////////////// // release unmanaged resources here... ////////////////////////////////////// if (session != IntPtr.Zero) { UnsafeNativeMethods.sqlite3session_delete(session); session = IntPtr.Zero; } } } finally { // // NOTE: Everything should be fully disposed at this point. // disposed = true; } } #endregion /////////////////////////////////////////////////////////////////////// #region Destructor ~SQLiteSession() { Dispose(false); } #endregion } #endregion /////////////////////////////////////////////////////////////////////////// public sealed class SQLiteChangeSet : ISQLiteChangeSet, IDisposable { #region Private Data private SQLiteChangeSetIterator iterator; #endregion /////////////////////////////////////////////////////////////////////// internal SQLiteChangeSet( SQLiteChangeSetIterator iterator ) { this.iterator = iterator; } /////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////// #region ISQLiteChangeSet Members public bool? IsPatchSet { get { throw new NotImplementedException(); } } /////////////////////////////////////////////////////////////////////// public void Apply( SQLiteConnection connection, ConflictDelegate conflictCallback ) { throw new NotImplementedException(); } /////////////////////////////////////////////////////////////////////// public void Apply( SQLiteConnection connection, ConflictDelegate conflictCallback, TableFilterDelegate tableFilterCallback ) { throw new NotImplementedException(); } /////////////////////////////////////////////////////////////////////// public ISQLiteChangeSet Invert() { throw new NotImplementedException(); } /////////////////////////////////////////////////////////////////////// public ISQLiteChangeSet CombineWith( ISQLiteChangeSet changeSet ) { throw new NotImplementedException(); } #endregion /////////////////////////////////////////////////////////////////////// #region IDisposable Members public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } #endregion /////////////////////////////////////////////////////////////////////// #region IDisposable "Pattern" Members private bool disposed; private void CheckDisposed() /* throw */ { #if THROW_ON_DISPOSED if (disposed) { throw new ObjectDisposedException( typeof(SQLiteChangeSet).Name); } #endif } /////////////////////////////////////////////////////////////////////// private /* protected virtual */ void Dispose(bool disposing) { try { if (!disposed) { if (disposing) { //////////////////////////////////// // dispose managed resources here... //////////////////////////////////// if (iterator != null) { iterator.Dispose(); iterator = null; } } ////////////////////////////////////// // release unmanaged resources here... ////////////////////////////////////// } } finally { // // NOTE: Everything should be fully disposed at this point. // disposed = true; } } #endregion /////////////////////////////////////////////////////////////////////// #region Destructor ~SQLiteChangeSet() { Dispose(false); } #endregion } /////////////////////////////////////////////////////////////////////////// public sealed class SQLiteChangeSetEnumerator : IEnumerator { public SQLiteChangeSetEnumerator( ISQLiteChangeSet changeSet ) { } #region IEnumerator Members public ISQLiteChangeSetMetadataItem Current { get { throw new NotImplementedException(); } } #endregion #region IDisposable Members public void Dispose() { throw new NotImplementedException(); } #endregion #region IEnumerator Members object Collections.IEnumerator.Current { get { throw new NotImplementedException(); } } public bool MoveNext() { throw new NotImplementedException(); } public void Reset() { throw new NotImplementedException(); } #endregion } /////////////////////////////////////////////////////////////////////////// public sealed class SQLiteChangeSetMetadataItem : ISQLiteChangeSetMetadataItem { internal SQLiteChangeSetMetadataItem(IntPtr iterator) { } #region ISQLiteChangeSetMetadataItem Members public SQLiteAuthorizerActionCode OperationCode { get { throw new NotImplementedException(); } } public string TableName { get { throw new NotImplementedException(); } } public int NumberOfColumns { get { throw new NotImplementedException(); } } public bool Indirect { get { throw new NotImplementedException(); } } public bool[] PrimaryKeyColumns { get { throw new NotImplementedException(); } } public SQLiteValue GetOldValue(int columnIndex) { throw new NotImplementedException(); } public SQLiteValue GetNewValue(int columnIndex) { throw new NotImplementedException(); } public SQLiteValue GetConflictValue(int columnIndex) { throw new NotImplementedException(); } public int NumberOfForeignKeyConflicts { get { throw new NotImplementedException(); } } #endregion } }