Index: Doc/Extra/Provider/version.html
==================================================================
--- Doc/Extra/Provider/version.html
+++ Doc/Extra/Provider/version.html
@@ -45,10 +45,11 @@
Version History
1.0.106.0 - October XX, 2017 (release scheduled)
- Updated to SQLite 3.20.1.
- Add BindDecimalAsText and GetDecimalAsText connection flags to force binding and returning of decimal values as text. Pursuant to [b167206ad3].
+ - Add support for the native session extension.
1.0.105.2 - June 12, 2017
- Updated to SQLite 3.19.3.
- Fix issues that prevented SQLiteBlob creation from succeeding for tables that did not have an integer primary key.
Index: SQLite.Interop/props/SQLite.Interop.2005.vsprops
==================================================================
--- SQLite.Interop/props/SQLite.Interop.2005.vsprops
+++ SQLite.Interop/props/SQLite.Interop.2005.vsprops
@@ -57,11 +57,11 @@
Value="INTEROP_DEBUG=0x31F;INTEROP_LOG=1;INTEROP_TEST_EXTENSION=1"
PerformEnvironmentSet="true"
/>
1,0,106,0
src\core
INTEROP_DEBUG=0x31F;INTEROP_LOG=1;INTEROP_TEST_EXTENSION=1
- INTEROP_PLACEHOLDER=1;INTEROP_EXTENSION_FUNCTIONS=1;INTEROP_VIRTUAL_TABLE=1;INTEROP_FTS5_EXTENSION=1;INTEROP_PERCENTILE_EXTENSION=1;INTEROP_TOTYPE_EXTENSION=1;INTEROP_REGEXP_EXTENSION=1;INTEROP_JSON1_EXTENSION=1;INTEROP_SHA1_EXTENSION=1
+ INTEROP_PLACEHOLDER=1;INTEROP_EXTENSION_FUNCTIONS=1;INTEROP_VIRTUAL_TABLE=1;INTEROP_FTS5_EXTENSION=1;INTEROP_PERCENTILE_EXTENSION=1;INTEROP_TOTYPE_EXTENSION=1;INTEROP_REGEXP_EXTENSION=1;INTEROP_JSON1_EXTENSION=1;INTEROP_SHA1_EXTENSION=1;INTEROP_SESSION_EXTENSION=1
/ASSEMBLYRESOURCE:..\System.Data.SQLite\Resources\SQLiteCommand.bmp,System.Data.SQLite.SQLiteCommand.bmp /ASSEMBLYRESOURCE:..\System.Data.SQLite\Resources\SQLiteConnection.bmp,System.Data.SQLite.SQLiteConnection.bmp /ASSEMBLYRESOURCE:..\System.Data.SQLite\Resources\SQLiteDataAdapter.bmp,System.Data.SQLite.SQLiteDataAdapter.bmp
$(ProjectDir)..\Keys\System.Data.SQLite.snk
SQLite.Interop
System.Data.SQLite
Index: SQLite.Interop/props/SQLite.Interop.2012.props
==================================================================
--- SQLite.Interop/props/SQLite.Interop.2012.props
+++ SQLite.Interop/props/SQLite.Interop.2012.props
@@ -16,11 +16,11 @@
1,0,106,0
src\core
INTEROP_DEBUG=0x31F;INTEROP_LOG=1;INTEROP_TEST_EXTENSION=1
- INTEROP_PLACEHOLDER=1;INTEROP_EXTENSION_FUNCTIONS=1;INTEROP_VIRTUAL_TABLE=1;INTEROP_FTS5_EXTENSION=1;INTEROP_PERCENTILE_EXTENSION=1;INTEROP_TOTYPE_EXTENSION=1;INTEROP_REGEXP_EXTENSION=1;INTEROP_JSON1_EXTENSION=1;INTEROP_SHA1_EXTENSION=1
+ INTEROP_PLACEHOLDER=1;INTEROP_EXTENSION_FUNCTIONS=1;INTEROP_VIRTUAL_TABLE=1;INTEROP_FTS5_EXTENSION=1;INTEROP_PERCENTILE_EXTENSION=1;INTEROP_TOTYPE_EXTENSION=1;INTEROP_REGEXP_EXTENSION=1;INTEROP_JSON1_EXTENSION=1;INTEROP_SHA1_EXTENSION=1;INTEROP_SESSION_EXTENSION=1
/ASSEMBLYRESOURCE:..\System.Data.SQLite\Resources\SQLiteCommand.bmp,System.Data.SQLite.SQLiteCommand.bmp /ASSEMBLYRESOURCE:..\System.Data.SQLite\Resources\SQLiteConnection.bmp,System.Data.SQLite.SQLiteConnection.bmp /ASSEMBLYRESOURCE:..\System.Data.SQLite\Resources\SQLiteDataAdapter.bmp,System.Data.SQLite.SQLiteDataAdapter.bmp
$(ProjectDir)..\Keys\System.Data.SQLite.snk
SQLite.Interop
System.Data.SQLite
Index: SQLite.Interop/props/SQLite.Interop.2013.props
==================================================================
--- SQLite.Interop/props/SQLite.Interop.2013.props
+++ SQLite.Interop/props/SQLite.Interop.2013.props
@@ -16,11 +16,11 @@
1,0,106,0
src\core
INTEROP_DEBUG=0x31F;INTEROP_LOG=1;INTEROP_TEST_EXTENSION=1
- INTEROP_PLACEHOLDER=1;INTEROP_EXTENSION_FUNCTIONS=1;INTEROP_VIRTUAL_TABLE=1;INTEROP_FTS5_EXTENSION=1;INTEROP_PERCENTILE_EXTENSION=1;INTEROP_TOTYPE_EXTENSION=1;INTEROP_REGEXP_EXTENSION=1;INTEROP_JSON1_EXTENSION=1;INTEROP_SHA1_EXTENSION=1
+ INTEROP_PLACEHOLDER=1;INTEROP_EXTENSION_FUNCTIONS=1;INTEROP_VIRTUAL_TABLE=1;INTEROP_FTS5_EXTENSION=1;INTEROP_PERCENTILE_EXTENSION=1;INTEROP_TOTYPE_EXTENSION=1;INTEROP_REGEXP_EXTENSION=1;INTEROP_JSON1_EXTENSION=1;INTEROP_SHA1_EXTENSION=1;INTEROP_SESSION_EXTENSION=1
/ASSEMBLYRESOURCE:..\System.Data.SQLite\Resources\SQLiteCommand.bmp,System.Data.SQLite.SQLiteCommand.bmp /ASSEMBLYRESOURCE:..\System.Data.SQLite\Resources\SQLiteConnection.bmp,System.Data.SQLite.SQLiteConnection.bmp /ASSEMBLYRESOURCE:..\System.Data.SQLite\Resources\SQLiteDataAdapter.bmp,System.Data.SQLite.SQLiteDataAdapter.bmp
$(ProjectDir)..\Keys\System.Data.SQLite.snk
SQLite.Interop
System.Data.SQLite
Index: SQLite.Interop/props/SQLite.Interop.2015.props
==================================================================
--- SQLite.Interop/props/SQLite.Interop.2015.props
+++ SQLite.Interop/props/SQLite.Interop.2015.props
@@ -16,11 +16,11 @@
1,0,106,0
src\core
INTEROP_DEBUG=0x31F;INTEROP_LOG=1;INTEROP_TEST_EXTENSION=1
- INTEROP_PLACEHOLDER=1;INTEROP_EXTENSION_FUNCTIONS=1;INTEROP_VIRTUAL_TABLE=1;INTEROP_FTS5_EXTENSION=1;INTEROP_PERCENTILE_EXTENSION=1;INTEROP_TOTYPE_EXTENSION=1;INTEROP_REGEXP_EXTENSION=1;INTEROP_JSON1_EXTENSION=1;INTEROP_SHA1_EXTENSION=1
+ INTEROP_PLACEHOLDER=1;INTEROP_EXTENSION_FUNCTIONS=1;INTEROP_VIRTUAL_TABLE=1;INTEROP_FTS5_EXTENSION=1;INTEROP_PERCENTILE_EXTENSION=1;INTEROP_TOTYPE_EXTENSION=1;INTEROP_REGEXP_EXTENSION=1;INTEROP_JSON1_EXTENSION=1;INTEROP_SHA1_EXTENSION=1;INTEROP_SESSION_EXTENSION=1
/ASSEMBLYRESOURCE:..\System.Data.SQLite\Resources\SQLiteCommand.bmp,System.Data.SQLite.SQLiteCommand.bmp /ASSEMBLYRESOURCE:..\System.Data.SQLite\Resources\SQLiteConnection.bmp,System.Data.SQLite.SQLiteConnection.bmp /ASSEMBLYRESOURCE:..\System.Data.SQLite\Resources\SQLiteDataAdapter.bmp,System.Data.SQLite.SQLiteDataAdapter.bmp
$(ProjectDir)..\Keys\System.Data.SQLite.snk
SQLite.Interop
System.Data.SQLite
Index: SQLite.Interop/src/generic/interop.c
==================================================================
--- SQLite.Interop/src/generic/interop.c
+++ SQLite.Interop/src/generic/interop.c
@@ -8,10 +8,19 @@
#ifdef _WIN32
#define SQLITE_API __declspec(dllexport)
#else
#define WINAPI
#endif
+
+#if /* SQLITE_VERSION_NUMBER >= 3013000 && */ defined(INTEROP_SESSION_EXTENSION)
+#ifndef SQLITE_ENABLE_SESSION
+#define SQLITE_ENABLE_SESSION (1)
+#endif
+#ifndef SQLITE_ENABLE_PREUPDATE_HOOK
+#define SQLITE_ENABLE_PREUPDATE_HOOK (1)
+#endif
+#endif
#include "../core/sqlite3.c"
#if !SQLITE_OS_WIN
#include
@@ -129,10 +138,13 @@
#ifdef INTEROP_PERCENTILE_EXTENSION
"PERCENTILE_EXTENSION",
#endif
#ifdef INTEROP_REGEXP_EXTENSION
"REGEXP_EXTENSION",
+#endif
+#ifdef INTEROP_SESSION_EXTENSION
+ "SESSION_EXTENSION",
#endif
#ifdef INTEROP_SHA1_EXTENSION
"SHA1_EXTENSION",
#endif
#ifdef INTEROP_TEST_EXTENSION
Index: Setup/compile-interop-assembly-debug.sh
==================================================================
--- Setup/compile-interop-assembly-debug.sh
+++ Setup/compile-interop-assembly-debug.sh
@@ -10,9 +10,9 @@
libname=libSQLite.Interop.so
gccflags=""
fi
pushd "$scriptdir/../SQLite.Interop/src/generic"
-gcc -g -fPIC -shared $gccflags -o $libname interop.c -I../core -DSQLITE_THREADSAFE=1 -DSQLITE_USE_URI=1 -DSQLITE_ENABLE_COLUMN_METADATA=1 -DSQLITE_ENABLE_STAT4=1 -DSQLITE_ENABLE_FTS3=1 -DSQLITE_ENABLE_LOAD_EXTENSION=1 -DSQLITE_ENABLE_RTREE=1 -DSQLITE_SOUNDEX=1 -DSQLITE_ENABLE_MEMORY_MANAGEMENT=1 -DSQLITE_ENABLE_API_ARMOR=1 -DSQLITE_ENABLE_DBSTAT_VTAB=1 -DSQLITE_DEBUG=1 -DSQLITE_MEMDEBUG=1 -DSQLITE_ENABLE_EXPENSIVE_ASSERT=1 -DINTEROP_LOG=1 -DINTEROP_TEST_EXTENSION=1 -DINTEROP_EXTENSION_FUNCTIONS=1 -DINTEROP_VIRTUAL_TABLE=1 -DINTEROP_FTS5_EXTENSION=1 -DINTEROP_PERCENTILE_EXTENSION=1 -DINTEROP_TOTYPE_EXTENSION=1 -DINTEROP_REGEXP_EXTENSION=1 -DINTEROP_JSON1_EXTENSION=1 -DINTEROP_SHA1_EXTENSION=1 $extradefs -lm -lpthread -ldl
+gcc -g -fPIC -shared $gccflags -o $libname interop.c -I../core -DSQLITE_THREADSAFE=1 -DSQLITE_USE_URI=1 -DSQLITE_ENABLE_COLUMN_METADATA=1 -DSQLITE_ENABLE_STAT4=1 -DSQLITE_ENABLE_FTS3=1 -DSQLITE_ENABLE_LOAD_EXTENSION=1 -DSQLITE_ENABLE_RTREE=1 -DSQLITE_SOUNDEX=1 -DSQLITE_ENABLE_MEMORY_MANAGEMENT=1 -DSQLITE_ENABLE_API_ARMOR=1 -DSQLITE_ENABLE_DBSTAT_VTAB=1 -DSQLITE_DEBUG=1 -DSQLITE_MEMDEBUG=1 -DSQLITE_ENABLE_EXPENSIVE_ASSERT=1 -DINTEROP_LOG=1 -DINTEROP_TEST_EXTENSION=1 -DINTEROP_EXTENSION_FUNCTIONS=1 -DINTEROP_VIRTUAL_TABLE=1 -DINTEROP_FTS5_EXTENSION=1 -DINTEROP_PERCENTILE_EXTENSION=1 -DINTEROP_TOTYPE_EXTENSION=1 -DINTEROP_REGEXP_EXTENSION=1 -DINTEROP_JSON1_EXTENSION=1 -DINTEROP_SHA1_EXTENSION=1 -DINTEROP_SESSION_EXTENSION=1 $extradefs -lm -lpthread -ldl
mkdir -p ../../../bin/2013/Debug/bin
mv $libname ../../../bin/2013/Debug/bin
popd
Index: Setup/compile-interop-assembly-release.sh
==================================================================
--- Setup/compile-interop-assembly-release.sh
+++ Setup/compile-interop-assembly-release.sh
@@ -10,9 +10,9 @@
libname=libSQLite.Interop.so
gccflags=""
fi
pushd "$scriptdir/../SQLite.Interop/src/generic"
-gcc -g -fPIC -shared $gccflags -o $libname interop.c -I../core -DSQLITE_THREADSAFE=1 -DSQLITE_USE_URI=1 -DSQLITE_ENABLE_COLUMN_METADATA=1 -DSQLITE_ENABLE_STAT4=1 -DSQLITE_ENABLE_FTS3=1 -DSQLITE_ENABLE_LOAD_EXTENSION=1 -DSQLITE_ENABLE_RTREE=1 -DSQLITE_SOUNDEX=1 -DSQLITE_ENABLE_MEMORY_MANAGEMENT=1 -DSQLITE_ENABLE_API_ARMOR=1 -DSQLITE_ENABLE_DBSTAT_VTAB=1 -DINTEROP_TEST_EXTENSION=1 -DINTEROP_EXTENSION_FUNCTIONS=1 -DINTEROP_VIRTUAL_TABLE=1 -DINTEROP_FTS5_EXTENSION=1 -DINTEROP_PERCENTILE_EXTENSION=1 -DINTEROP_TOTYPE_EXTENSION=1 -DINTEROP_REGEXP_EXTENSION=1 -DINTEROP_JSON1_EXTENSION=1 -DINTEROP_SHA1_EXTENSION=1 $extradefs -lm -lpthread -ldl
+gcc -g -fPIC -shared $gccflags -o $libname interop.c -I../core -DSQLITE_THREADSAFE=1 -DSQLITE_USE_URI=1 -DSQLITE_ENABLE_COLUMN_METADATA=1 -DSQLITE_ENABLE_STAT4=1 -DSQLITE_ENABLE_FTS3=1 -DSQLITE_ENABLE_LOAD_EXTENSION=1 -DSQLITE_ENABLE_RTREE=1 -DSQLITE_SOUNDEX=1 -DSQLITE_ENABLE_MEMORY_MANAGEMENT=1 -DSQLITE_ENABLE_API_ARMOR=1 -DSQLITE_ENABLE_DBSTAT_VTAB=1 -DINTEROP_TEST_EXTENSION=1 -DINTEROP_EXTENSION_FUNCTIONS=1 -DINTEROP_VIRTUAL_TABLE=1 -DINTEROP_FTS5_EXTENSION=1 -DINTEROP_PERCENTILE_EXTENSION=1 -DINTEROP_TOTYPE_EXTENSION=1 -DINTEROP_REGEXP_EXTENSION=1 -DINTEROP_JSON1_EXTENSION=1 -DINTEROP_SHA1_EXTENSION=1 -DINTEROP_SESSION_EXTENSION=1 $extradefs -lm -lpthread -ldl
mkdir -p ../../../bin/2013/Release/bin
mv $libname ../../../bin/2013/Release/bin
popd
Index: Setup/data/verify.lst
==================================================================
--- Setup/data/verify.lst
+++ Setup/data/verify.lst
@@ -213,11 +213,10 @@
Externals/Eagle/bin/EagleShell.exe.config
Externals/Eagle/bin/EagleShell.exe.mda.config
Externals/Eagle/lib/
Externals/Eagle/lib/Eagle1.0/
Externals/Eagle/lib/Eagle1.0/vendor.eagle
- Externals/Eagle/lib/Test1.0/
Keys/
Keys/System.Data.SQLite.CF.snk
Keys/System.Data.SQLite.snk
lib/
lib/System.Data.SQLite/
@@ -589,10 +588,11 @@
System.Data.SQLite/SQLiteModuleEnumerable.cs
System.Data.SQLite/SQLiteModuleNoop.cs
System.Data.SQLite/SQLiteParameter.cs
System.Data.SQLite/SQLiteParameterCollection.cs
System.Data.SQLite/SQLitePatchLevel.cs
+ System.Data.SQLite/SQLiteSession.cs
System.Data.SQLite/SQLiteStatement.cs
System.Data.SQLite/SQLiteTransaction.cs
System.Data.SQLite/SQLiteTransaction2.cs
System.Data.SQLite/SQLiteTransactionBase.cs
System.Data.SQLite/System.Data.SQLite.2005.csproj
@@ -783,10 +783,11 @@
Tests/data/wal.db
Tests/installer.eagle
Tests/linq.eagle
Tests/memory.eagle
Tests/progress.eagle
+ Tests/session.eagle
Tests/speed.eagle
Tests/stress.eagle
Tests/template/
Tests/template/empty.eagle
Tests/thread.eagle
@@ -847,10 +848,11 @@
Tests/tkt-a4d9c7ee94.eagle
Tests/tkt-a7d04fb111.eagle
Tests/tkt-aba4549801.eagle
Tests/tkt-ac47dd230a.eagle
Tests/tkt-ae5267b863.eagle
+ Tests/tkt-b167206ad3.eagle
Tests/tkt-b4a7ddc83f.eagle
Tests/tkt-bb4b04d457.eagle
Tests/tkt-c010fa6584.eagle
Tests/tkt-c28d7fe915.eagle
Tests/tkt-ccfa69fc32.eagle
@@ -911,22 +913,25 @@
Externals/Eagle/lib/Eagle1.0/init.eagle
Externals/Eagle/lib/Eagle1.0/list.eagle
Externals/Eagle/lib/Eagle1.0/object.eagle
Externals/Eagle/lib/Eagle1.0/pkgIndex.eagle
Externals/Eagle/lib/Eagle1.0/pkgIndex.tcl
+ Externals/Eagle/lib/Eagle1.0/pkgt.eagle
Externals/Eagle/lib/Eagle1.0/platform.eagle
Externals/Eagle/lib/Eagle1.0/process.eagle
Externals/Eagle/lib/Eagle1.0/runopt.eagle
Externals/Eagle/lib/Eagle1.0/safe.eagle
Externals/Eagle/lib/Eagle1.0/shell.eagle
Externals/Eagle/lib/Eagle1.0/shim.eagle
Externals/Eagle/lib/Eagle1.0/test.eagle
Externals/Eagle/lib/Eagle1.0/testlog.eagle
Externals/Eagle/lib/Eagle1.0/unkobj.eagle
+ Externals/Eagle/lib/Eagle1.0/unzip.eagle
Externals/Eagle/lib/Eagle1.0/update.eagle
Externals/Eagle/lib/Eagle1.0/vendor.eagle
Externals/Eagle/lib/Eagle1.0/word.tcl
+ Externals/Eagle/lib/Test1.0/
Externals/Eagle/lib/Test1.0/all.eagle
Externals/Eagle/lib/Test1.0/constraints.eagle
Externals/Eagle/lib/Test1.0/epilogue.eagle
Externals/Eagle/lib/Test1.0/pkgIndex.eagle
Externals/Eagle/lib/Test1.0/pkgIndex.tcl
Index: System.Data.SQLite/SQLiteConnection.cs
==================================================================
--- System.Data.SQLite/SQLiteConnection.cs
+++ System.Data.SQLite/SQLiteConnection.cs
@@ -1758,10 +1758,51 @@
}
}
///////////////////////////////////////////////////////////////////////////////////////////////
+ ///
+ /// Attempts to lookup the native handle associated with the connection. An exception will
+ /// be thrown if this cannot be accomplished.
+ ///
+ ///
+ /// The connection associated with the desired native handle.
+ ///
+ ///
+ /// The native handle associated with the connection or if it
+ /// cannot be determined.
+ ///
+ private static SQLiteConnectionHandle GetNativeHandle(
+ SQLiteConnection connection
+ )
+ {
+ if (connection == null)
+ throw new ArgumentNullException("connection");
+
+ SQLite3 sqlite3 = connection._sql as SQLite3;
+
+ if (sqlite3 == null)
+ throw new InvalidOperationException("Connection has no wrapper");
+
+ SQLiteConnectionHandle handle = sqlite3._sql;
+
+ if (handle == null)
+ throw new InvalidOperationException("Connection has an invalid handle.");
+
+ IntPtr handlePtr = handle;
+
+ if (handlePtr == IntPtr.Zero)
+ {
+ throw new InvalidOperationException(
+ "Connection has an invalid handle pointer.");
+ }
+
+ return handle;
+ }
+
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+
///
/// Raises the event.
///
///
/// The connection associated with this event. If this parameter is not
@@ -2971,10 +3012,90 @@
///
protected override DbCommand CreateDbCommand()
{
return CreateCommand();
}
+
+#if INTEROP_SESSION_EXTENSION
+ ///
+ /// Attempts to create a new object instance
+ /// using this connection and the specified database name.
+ ///
+ ///
+ /// The name of the database for the newly created session.
+ ///
+ ///
+ /// The newly created session -OR- null if it cannot be created.
+ ///
+ public ISQLiteSession CreateSession(
+ string databaseName
+ )
+ {
+ CheckDisposed();
+
+ return new SQLiteSession(GetNativeHandle(this), _flags, databaseName);
+ }
+
+ ///
+ /// Attempts to create a new object instance
+ /// using this connection and the specified raw data.
+ ///
+ ///
+ /// The raw data that contains a change set (or patch set).
+ ///
+ ///
+ /// The newly created change set -OR- null if it cannot be created.
+ ///
+ public ISQLiteChangeSet CreateChangeSet(
+ byte[] rawData
+ )
+ {
+ CheckDisposed();
+
+ return new SQLiteMemoryChangeSet(rawData, GetNativeHandle(this), _flags);
+ }
+
+ ///
+ /// Attempts to create a new object instance
+ /// using this connection and the specified stream.
+ ///
+ ///
+ /// The stream where the raw data that contains a change set (or patch set)
+ /// may be read.
+ ///
+ ///
+ /// The stream where the raw data that contains a change set (or patch set)
+ /// may be written.
+ ///
+ ///
+ /// The newly created change set -OR- null if it cannot be created.
+ ///
+ public ISQLiteChangeSet CreateChangeSet(
+ Stream inputStream,
+ Stream outputStream
+ )
+ {
+ CheckDisposed();
+
+ return new SQLiteStreamChangeSet(
+ inputStream, outputStream, GetNativeHandle(this), _flags);
+ }
+
+ ///
+ /// Attempts to create a new object
+ /// instance using this connection.
+ ///
+ ///
+ /// The newly created change group -OR- null if it cannot be created.
+ ///
+ public ISQLiteChangeGroup CreateChangeGroup()
+ {
+ CheckDisposed();
+
+ return new SQLiteChangeGroup(_flags);
+ }
+#endif
///
/// Returns the data source file name without extension or path.
///
#if !PLATFORM_COMPACTFRAMEWORK
Index: System.Data.SQLite/SQLiteDefineConstants.cs
==================================================================
--- System.Data.SQLite/SQLiteDefineConstants.cs
+++ System.Data.SQLite/SQLiteDefineConstants.cs
@@ -73,10 +73,14 @@
#endif
#if INTEROP_REGEXP_EXTENSION
"INTEROP_REGEXP_EXTENSION",
#endif
+
+#if INTEROP_SESSION_EXTENSION
+ "INTEROP_SESSION_EXTENSION",
+#endif
#if INTEROP_SHA1_EXTENSION
"INTEROP_SHA1_EXTENSION",
#endif
Index: System.Data.SQLite/SQLiteModule.cs
==================================================================
--- System.Data.SQLite/SQLiteModule.cs
+++ System.Data.SQLite/SQLiteModule.cs
@@ -335,10 +335,31 @@
#endregion
///////////////////////////////////////////////////////////////////////
#region Internal Marshal Helper Methods
+ ///
+ /// Converts a native pointer to a native sqlite3_value structure into
+ /// a managed object instance.
+ ///
+ ///
+ /// The native pointer to a native sqlite3_value structure to convert.
+ ///
+ ///
+ /// The managed object instance or null upon
+ /// failure.
+ ///
+ internal static SQLiteValue FromIntPtr(
+ IntPtr pValue
+ )
+ {
+ if (pValue == IntPtr.Zero) return null;
+ return new SQLiteValue(pValue);
+ }
+
+ ///////////////////////////////////////////////////////////////////////
+
///
/// Converts a logical array of native pointers to native sqlite3_value
/// structures into a managed array of
/// object instances.
///
@@ -570,10 +591,56 @@
if (pValue == IntPtr.Zero) return null;
return SQLiteBytes.FromIntPtr(
UnsafeNativeMethods.sqlite3_value_blob(pValue), GetBytes());
}
+
+ ///////////////////////////////////////////////////////////////////////
+
+ ///
+ /// Gets and returns an instance associated with
+ /// this value.
+ ///
+ ///
+ /// The associated with this value. If the type
+ /// affinity of the object is unknown or cannot be determined, a null
+ /// value will be returned.
+ ///
+ public object GetObject()
+ {
+ switch (GetTypeAffinity())
+ {
+ case TypeAffinity.Uninitialized:
+ {
+ return null;
+ }
+ case TypeAffinity.Int64:
+ {
+ return GetInt64();
+ }
+ case TypeAffinity.Double:
+ {
+ return GetDouble();
+ }
+ case TypeAffinity.Text:
+ {
+ return GetString();
+ }
+ case TypeAffinity.Blob:
+ {
+ return GetBytes();
+ }
+ case TypeAffinity.Null:
+ {
+ return DBNull.Value;
+ }
+ default:
+ {
+ return null;
+ }
+ }
+ }
///////////////////////////////////////////////////////////////////////
///
/// Uses the native value handle to obtain and store the managed value
@@ -3481,10 +3548,37 @@
/// upon failure.
///
public static IntPtr Utf8IntPtrFromString(
string value
)
+ {
+ int length = 0;
+
+ return Utf8IntPtrFromString(value, ref length);
+ }
+
+ ///////////////////////////////////////////////////////////////////////
+
+ ///
+ /// Converts the specified managed string into a native NUL-terminated
+ /// UTF-8 string pointer using memory obtained from the SQLite core
+ /// library.
+ ///
+ ///
+ /// The managed string to convert.
+ ///
+ ///
+ /// The length of the native string, in bytes.
+ ///
+ ///
+ /// The native NUL-terminated UTF-8 string pointer or
+ /// upon failure.
+ ///
+ public static IntPtr Utf8IntPtrFromString(
+ string value,
+ ref int length
+ )
{
if (value == null)
return IntPtr.Zero;
IntPtr result = IntPtr.Zero;
@@ -3491,11 +3585,11 @@
byte[] bytes = GetUtf8BytesFromString(value);
if (bytes == null)
return IntPtr.Zero;
- int length = bytes.Length;
+ length = bytes.Length;
result = SQLiteMemory.Allocate(length + 1);
if (result == IntPtr.Zero)
return IntPtr.Zero;
@@ -3637,15 +3731,40 @@
/// The native pointer to a logical byte array or null upon failure.
///
public static IntPtr ToIntPtr(
byte[] value
)
+ {
+ int length = 0;
+
+ return ToIntPtr(value, ref length);
+ }
+
+ ///////////////////////////////////////////////////////////////////////
+
+ ///
+ /// Converts a managed byte array into a native pointer to a logical
+ /// array of bytes.
+ ///
+ ///
+ /// The managed byte array to convert.
+ ///
+ ///
+ /// The length, in bytes, of the converted logical array of bytes.
+ ///
+ ///
+ /// The native pointer to a logical byte array or null upon failure.
+ ///
+ public static IntPtr ToIntPtr(
+ byte[] value,
+ ref int length
+ )
{
if (value == null)
return IntPtr.Zero;
- int length = value.Length;
+ length = value.Length;
if (length == 0)
return IntPtr.Zero;
IntPtr result = SQLiteMemory.Allocate(length);
ADDED System.Data.SQLite/SQLiteSession.cs
Index: System.Data.SQLite/SQLiteSession.cs
==================================================================
--- /dev/null
+++ System.Data.SQLite/SQLiteSession.cs
@@ -0,0 +1,5223 @@
+/********************************************************
+ * 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
+ }
+ #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;
+ int nRemain = 0;
+
+ SQLiteErrorCode rc = UnsafeNativeMethods.sqlite3_prepare_interop(
+ GetIntPtr(), pSql, nSql, ref statement, ref pRemain,
+ ref nRemain);
+
+ if (rc != SQLiteErrorCode.Ok)
+ throw new SQLiteException(rc, "sqlite3_prepare_interop");
+ }
+ 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;
+
+ SQLiteErrorCode rc = UnsafeNativeMethods.sqlite3_finalize_interop(
+ statement);
+
+ if (rc != SQLiteErrorCode.Ok)
+ throw new SQLiteException(rc, "sqlite3_finalize_interop");
+
+ 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;
+ }
+ #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 flags
+ )
+ {
+ if (stream == null)
+ throw new ArgumentNullException("stream");
+
+ SQLiteStreamAdapter streamAdapter = null;
+ SQLiteStreamChangeSetIterator result = null;
+ IntPtr iterator = IntPtr.Zero;
+
+ try
+ {
+ streamAdapter = new SQLiteStreamAdapter(stream, flags);
+
+ 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;
+ }
+ #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();
+
+ SQLiteSessionHelpers.CheckRawData(rawData);
+
+ 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.Free(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
+ /// 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.
+ ///
+ 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;
+ }
+ }
+ }
+
+ ///////////////////////////////////////////////////////////////////////
+
+ ///
+ /// 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 .
+ ///
+ 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
+ /// 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.
+ ///
+ 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;
+ }
+ }
+ }
+
+ ///////////////////////////////////////////////////////////////////////
+
+ ///
+ /// 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 .
+ ///
+ 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.Free(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;
+ #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 flags
+ )
+ : base(handle, flags)
+ {
+ this.rawData = rawData;
+ }
+ #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.Free(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.Free(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()
+ {
+ 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;
+ #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 flags
+ )
+ : base(handle, flags)
+ {
+ this.inputStream = inputStream;
+ this.outputStream = outputStream;
+ }
+ #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()
+ {
+ 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;
+ #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;
+ }
+ #endregion
+
+ ///////////////////////////////////////////////////////////////////////
+
+ #region IEnumerator Overrides
+ ///
+ /// Resets the enumerator to its initial position.
+ ///
+ public override void Reset()
+ {
+ CheckDisposed();
+
+ ResetIterator(SQLiteMemoryChangeSetIterator.Create(rawData));
+ }
+ #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 flags
+ )
+ : base(SQLiteStreamChangeSetIterator.Create(stream, flags))
+ {
+ // 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
+}
Index: System.Data.SQLite/Targets/System.Data.SQLite.Files.targets
==================================================================
--- System.Data.SQLite/Targets/System.Data.SQLite.Files.targets
+++ System.Data.SQLite/Targets/System.Data.SQLite.Files.targets
@@ -114,6 +114,16 @@
+
+
+
+
+
+
Index: System.Data.SQLite/Targets/System.Data.SQLite.Properties.targets
==================================================================
--- System.Data.SQLite/Targets/System.Data.SQLite.Properties.targets
+++ System.Data.SQLite/Targets/System.Data.SQLite.Properties.targets
@@ -324,10 +324,18 @@
-->
$(DefineConstants);INTEROP_VIRTUAL_TABLE
+
+
+ $(DefineConstants);INTEROP_SESSION_EXTENSION
+
+
Index: System.Data.SQLite/UnsafeNativeMethods.cs
==================================================================
--- System.Data.SQLite/UnsafeNativeMethods.cs
+++ System.Data.SQLite/UnsafeNativeMethods.cs
@@ -26,13 +26,11 @@
#if (NET_40 || NET_45 || NET_451 || NET_452 || NET_46 || NET_461 || NET_462) && !PLATFORM_COMPACTFRAMEWORK
using System.Runtime.Versioning;
#endif
-#if !PLATFORM_COMPACTFRAMEWORK
using System.Text;
-#endif
#if !PLATFORM_COMPACTFRAMEWORK || COUNT_HANDLE
using System.Threading;
#endif
@@ -228,10 +226,35 @@
/// This static class provides some methods that are shared between the
/// native library pre-loader and other classes.
///
internal static class HelperMethods
{
+ #region Private Constants
+ private const string DisplayNullObject = "";
+ private const string DisplayEmptyString = "";
+ private const string DisplayStringFormat = "\"{0}\"";
+
+ /////////////////////////////////////////////////////////////////////////
+
+ private const string DisplayNullArray = "";
+ private const string DisplayEmptyArray = "";
+
+ /////////////////////////////////////////////////////////////////////////
+
+ private const char ArrayOpen = '[';
+ private const string ElementSeparator = ", ";
+ private const char ArrayClose = ']';
+
+ /////////////////////////////////////////////////////////////////////////
+
+ private static readonly char[] SpaceChars = {
+ '\t', '\n', '\r', '\v', '\f', ' '
+ };
+ #endregion
+
+ /////////////////////////////////////////////////////////////////////////
+
#region Private Data
///
/// This lock is used to protect the static field.
///
private static readonly object staticSyncRoot = new object();
@@ -487,10 +510,70 @@
return String.Format(format, args);
else
return String.Format(provider, format, args);
}
#endregion
+
+ /////////////////////////////////////////////////////////////////////////
+
+ #region Public Methods
+ public static string ToDisplayString(
+ object value
+ )
+ {
+ if (value == null)
+ return DisplayNullObject;
+
+ string stringValue = value.ToString();
+
+ if (stringValue.Length == 0)
+ return DisplayEmptyString;
+
+ if (stringValue.IndexOfAny(SpaceChars) < 0)
+ return stringValue;
+
+ return HelperMethods.StringFormat(
+ CultureInfo.InvariantCulture, DisplayStringFormat,
+ stringValue);
+ }
+
+ /////////////////////////////////////////////////////////////////////////
+
+ public static string ToDisplayString(
+ Array array
+ )
+ {
+ if (array == null)
+ return DisplayNullArray;
+
+ if (array.Length == 0)
+ return DisplayEmptyArray;
+
+ StringBuilder result = new StringBuilder();
+
+ foreach (object value in array)
+ {
+ if (result.Length > 0)
+ result.Append(ElementSeparator);
+
+ result.Append(ToDisplayString(value));
+ }
+
+ if (result.Length > 0)
+ {
+#if PLATFORM_COMPACTFRAMEWORK
+ result.Insert(0, ArrayOpen.ToString());
+#else
+ result.Insert(0, ArrayOpen);
+#endif
+
+ result.Append(ArrayClose);
+ }
+
+ return result.ToString();
+ }
+ #endregion
}
#endregion
/////////////////////////////////////////////////////////////////////////////
@@ -3699,10 +3782,11 @@
#endregion
// SQLite API calls that are provided by "well-known" extensions that may be statically
// linked with the SQLite core native library currently in use.
#region extension sqlite api calls
+ #region virtual table
#if INTEROP_VIRTUAL_TABLE
#if !PLATFORM_COMPACTFRAMEWORK
[DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)]
#else
[DllImport(SQLITE_DLL)]
@@ -3715,10 +3799,277 @@
[DllImport(SQLITE_DLL)]
#endif
internal static extern void sqlite3_dispose_module(IntPtr pModule);
#endif
#endregion
+
+ ///////////////////////////////////////////////////////////////////////////
+
+ #region session extension
+#if INTEROP_SESSION_EXTENSION
+#if !PLATFORM_COMPACTFRAMEWORK
+ [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
+#endif
+ internal delegate int xSessionFilter(IntPtr context, IntPtr pTblName);
+
+#if !PLATFORM_COMPACTFRAMEWORK
+ [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
+#endif
+ internal delegate SQLiteChangeSetConflictResult xSessionConflict(IntPtr context, SQLiteChangeSetConflictType type, IntPtr iterator);
+
+#if !PLATFORM_COMPACTFRAMEWORK
+ [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
+#endif
+ internal delegate SQLiteErrorCode xSessionInput(IntPtr context, IntPtr pData, ref int nData);
+
+#if !PLATFORM_COMPACTFRAMEWORK
+ [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
+#endif
+ internal delegate SQLiteErrorCode xSessionOutput(IntPtr context, IntPtr pData, int nData);
+
+ ///////////////////////////////////////////////////////////////////////////
+
+#if !PLATFORM_COMPACTFRAMEWORK
+ [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)]
+#else
+ [DllImport(SQLITE_DLL)]
+#endif
+ internal static extern SQLiteErrorCode sqlite3session_create(IntPtr db, byte[] dbName, ref IntPtr session);
+
+#if !PLATFORM_COMPACTFRAMEWORK
+ [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)]
+#else
+ [DllImport(SQLITE_DLL)]
+#endif
+ internal static extern void sqlite3session_delete(IntPtr session);
+
+#if !PLATFORM_COMPACTFRAMEWORK
+ [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)]
+#else
+ [DllImport(SQLITE_DLL)]
+#endif
+ internal static extern int sqlite3session_enable(IntPtr session, int enable);
+
+#if !PLATFORM_COMPACTFRAMEWORK
+ [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)]
+#else
+ [DllImport(SQLITE_DLL)]
+#endif
+ internal static extern int sqlite3session_indirect(IntPtr session, int indirect);
+
+#if !PLATFORM_COMPACTFRAMEWORK
+ [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)]
+#else
+ [DllImport(SQLITE_DLL)]
+#endif
+ internal static extern SQLiteErrorCode sqlite3session_attach(IntPtr session, byte[] tblName);
+
+#if !PLATFORM_COMPACTFRAMEWORK
+ [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)]
+#else
+ [DllImport(SQLITE_DLL)]
+#endif
+ internal static extern void sqlite3session_table_filter(IntPtr session, xSessionFilter xFilter, IntPtr context);
+
+#if !PLATFORM_COMPACTFRAMEWORK
+ [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)]
+#else
+ [DllImport(SQLITE_DLL)]
+#endif
+ internal static extern SQLiteErrorCode sqlite3session_changeset(IntPtr session, ref int nChangeSet, ref IntPtr pChangeSet);
+
+#if !PLATFORM_COMPACTFRAMEWORK
+ [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)]
+#else
+ [DllImport(SQLITE_DLL)]
+#endif
+ internal static extern SQLiteErrorCode sqlite3session_diff(IntPtr session, byte[] fromDbName, byte[] tblName, ref IntPtr errMsg);
+
+#if !PLATFORM_COMPACTFRAMEWORK
+ [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)]
+#else
+ [DllImport(SQLITE_DLL)]
+#endif
+ internal static extern SQLiteErrorCode sqlite3session_patchset(IntPtr session, ref int nPatchSet, ref IntPtr pPatchSet);
+
+#if !PLATFORM_COMPACTFRAMEWORK
+ [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)]
+#else
+ [DllImport(SQLITE_DLL)]
+#endif
+ internal static extern int sqlite3session_isempty(IntPtr session);
+
+#if !PLATFORM_COMPACTFRAMEWORK
+ [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)]
+#else
+ [DllImport(SQLITE_DLL)]
+#endif
+ internal static extern SQLiteErrorCode sqlite3changeset_start(ref IntPtr iterator, int nChangeSet, IntPtr pChangeSet);
+
+#if !PLATFORM_COMPACTFRAMEWORK
+ [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)]
+#else
+ [DllImport(SQLITE_DLL)]
+#endif
+ internal static extern SQLiteErrorCode sqlite3changeset_next(IntPtr iterator);
+
+#if !PLATFORM_COMPACTFRAMEWORK
+ [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)]
+#else
+ [DllImport(SQLITE_DLL)]
+#endif
+ internal static extern SQLiteErrorCode sqlite3changeset_op(IntPtr iterator, ref IntPtr pTblName, ref int nColumns, ref SQLiteAuthorizerActionCode op, ref int bIndirect);
+
+#if !PLATFORM_COMPACTFRAMEWORK
+ [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)]
+#else
+ [DllImport(SQLITE_DLL)]
+#endif
+ internal static extern SQLiteErrorCode sqlite3changeset_pk(IntPtr iterator, ref IntPtr pPrimaryKeys, ref int nColumns);
+
+#if !PLATFORM_COMPACTFRAMEWORK
+ [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)]
+#else
+ [DllImport(SQLITE_DLL)]
+#endif
+ internal static extern SQLiteErrorCode sqlite3changeset_old(IntPtr iterator, int columnIndex, ref IntPtr pValue);
+
+#if !PLATFORM_COMPACTFRAMEWORK
+ [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)]
+#else
+ [DllImport(SQLITE_DLL)]
+#endif
+ internal static extern SQLiteErrorCode sqlite3changeset_new(IntPtr iterator, int columnIndex, ref IntPtr pValue);
+
+#if !PLATFORM_COMPACTFRAMEWORK
+ [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)]
+#else
+ [DllImport(SQLITE_DLL)]
+#endif
+ internal static extern SQLiteErrorCode sqlite3changeset_conflict(IntPtr iterator, int columnIndex, ref IntPtr pValue);
+
+#if !PLATFORM_COMPACTFRAMEWORK
+ [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)]
+#else
+ [DllImport(SQLITE_DLL)]
+#endif
+ internal static extern SQLiteErrorCode sqlite3changeset_fk_conflicts(IntPtr iterator, ref int conflicts);
+
+#if !PLATFORM_COMPACTFRAMEWORK
+ [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)]
+#else
+ [DllImport(SQLITE_DLL)]
+#endif
+ internal static extern SQLiteErrorCode sqlite3changeset_finalize(IntPtr iterator);
+
+#if !PLATFORM_COMPACTFRAMEWORK
+ [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)]
+#else
+ [DllImport(SQLITE_DLL)]
+#endif
+ internal static extern SQLiteErrorCode sqlite3changeset_invert(int nIn, IntPtr pIn, ref int nOut, ref IntPtr pOut);
+
+#if !PLATFORM_COMPACTFRAMEWORK
+ [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)]
+#else
+ [DllImport(SQLITE_DLL)]
+#endif
+ internal static extern SQLiteErrorCode sqlite3changeset_concat(int nA, IntPtr pA, int nB, IntPtr pB, ref int nOut, ref IntPtr pOut);
+
+#if !PLATFORM_COMPACTFRAMEWORK
+ [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)]
+#else
+ [DllImport(SQLITE_DLL)]
+#endif
+ internal static extern SQLiteErrorCode sqlite3changegroup_new(ref IntPtr changeGroup);
+
+#if !PLATFORM_COMPACTFRAMEWORK
+ [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)]
+#else
+ [DllImport(SQLITE_DLL)]
+#endif
+ internal static extern SQLiteErrorCode sqlite3changegroup_add(IntPtr changeGroup, int nData, IntPtr pData);
+
+#if !PLATFORM_COMPACTFRAMEWORK
+ [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)]
+#else
+ [DllImport(SQLITE_DLL)]
+#endif
+ internal static extern SQLiteErrorCode sqlite3changegroup_output(IntPtr changeGroup, ref int nData, ref IntPtr pData);
+
+#if !PLATFORM_COMPACTFRAMEWORK
+ [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)]
+#else
+ [DllImport(SQLITE_DLL)]
+#endif
+ internal static extern void sqlite3changegroup_delete(IntPtr changeGroup);
+
+#if !PLATFORM_COMPACTFRAMEWORK
+ [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)]
+#else
+ [DllImport(SQLITE_DLL)]
+#endif
+ internal static extern SQLiteErrorCode sqlite3changeset_apply(IntPtr db, int nChangeSet, IntPtr pChangeSet, xSessionFilter xFilter, xSessionConflict xConflict, IntPtr context);
+
+#if !PLATFORM_COMPACTFRAMEWORK
+ [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)]
+#else
+ [DllImport(SQLITE_DLL)]
+#endif
+ internal static extern SQLiteErrorCode sqlite3changeset_apply_strm(IntPtr db, xSessionInput xInput, IntPtr pIn, xSessionFilter xFilter, xSessionConflict xConflict, IntPtr context);
+
+#if !PLATFORM_COMPACTFRAMEWORK
+ [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)]
+#else
+ [DllImport(SQLITE_DLL)]
+#endif
+ internal static extern SQLiteErrorCode sqlite3changeset_concat_strm(xSessionInput xInputA, IntPtr pInA, xSessionInput xInputB, IntPtr pInB, xSessionOutput xOutput, IntPtr pOut);
+
+#if !PLATFORM_COMPACTFRAMEWORK
+ [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)]
+#else
+ [DllImport(SQLITE_DLL)]
+#endif
+ internal static extern SQLiteErrorCode sqlite3changeset_invert_strm(xSessionInput xInput, IntPtr pIn, xSessionOutput xOutput, IntPtr pOut);
+
+#if !PLATFORM_COMPACTFRAMEWORK
+ [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)]
+#else
+ [DllImport(SQLITE_DLL)]
+#endif
+ internal static extern SQLiteErrorCode sqlite3changeset_start_strm(ref IntPtr iterator, xSessionInput xInput, IntPtr pIn);
+
+#if !PLATFORM_COMPACTFRAMEWORK
+ [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)]
+#else
+ [DllImport(SQLITE_DLL)]
+#endif
+ internal static extern SQLiteErrorCode sqlite3session_changeset_strm(IntPtr session, xSessionOutput xOutput, IntPtr pOut);
+
+#if !PLATFORM_COMPACTFRAMEWORK
+ [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)]
+#else
+ [DllImport(SQLITE_DLL)]
+#endif
+ internal static extern SQLiteErrorCode sqlite3session_patchset_strm(IntPtr session, xSessionOutput xOutput, IntPtr pOut);
+
+#if !PLATFORM_COMPACTFRAMEWORK
+ [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)]
+#else
+ [DllImport(SQLITE_DLL)]
+#endif
+ internal static extern SQLiteErrorCode sqlite3changegroup_add_strm(IntPtr changeGroup, xSessionInput xInput, IntPtr pIn);
+
+#if !PLATFORM_COMPACTFRAMEWORK
+ [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)]
+#else
+ [DllImport(SQLITE_DLL)]
+#endif
+ internal static extern SQLiteErrorCode sqlite3changegroup_output_strm(IntPtr changeGroup, xSessionOutput xOutput, IntPtr pOut);
+#endif
+ #endregion
+ #endregion
///////////////////////////////////////////////////////////////////////////
#region sqlite interop api calls (.NET Compact Framework only)
#if PLATFORM_COMPACTFRAMEWORK && !SQLITE_STANDARD
Index: Targets/SQLite.NET.Settings.targets
==================================================================
--- Targets/SQLite.NET.Settings.targets
+++ Targets/SQLite.NET.Settings.targets
@@ -684,10 +684,25 @@
both for the corresponding version(s) of Visual Studio.
-->
true
+
+ true
+
ADDED Tests/session.eagle
Index: Tests/session.eagle
==================================================================
--- /dev/null
+++ Tests/session.eagle
@@ -0,0 +1,1216 @@
+###############################################################################
+#
+# session.eagle --
+#
+# Written by Joe Mistachkin.
+# Released to the public domain, use at your own risk!
+#
+###############################################################################
+
+package require Eagle
+package require Eagle.Library
+package require Eagle.Test
+
+runTestPrologue
+
+###############################################################################
+
+package require System.Data.SQLite.Test
+runSQLiteTestPrologue
+
+###############################################################################
+
+proc getSomeText { random count } {
+ set items [list \
+ Alpha Bravo Charlie Delta Echo Foxtrot Golf Hotel \
+ India Juliet Kilo Lima Mike November Oscar Papa \
+ Quebec Romeo Sierra Tango Uniform Victor Whiskey X-ray \
+ Yankee Zulu]
+
+ if {!$random} then {
+ variable some_text_index
+
+ if {![info exists some_text_index]} then {
+ set some_text_index 0
+ }
+ }
+
+ set length [llength $items]
+ set result [list]
+
+ for {set i 0} {$i < $count} {incr i} {
+ if {$random} then {
+ set item [lindex $items [expr {int(rand() * $length)}]]
+ } else {
+ set item [lindex $items $some_text_index]
+ incr some_text_index
+
+ if {$some_text_index >= $length} then {
+ set some_text_index 0
+ }
+ }
+
+ lappend result $item
+ }
+
+ return $result
+}
+
+###############################################################################
+
+proc cleanupSomeText {} {
+ variable some_text_index
+ unset -nocomplain some_text_index
+}
+
+###############################################################################
+
+proc forDisplay { object member } {
+ set isArray false
+
+ if {[isObjectHandle $object] && $object ne "null"} then {
+ set value [object invoke -create -- $object $member]
+
+ if {[isObjectHandle $value] && $value ne "null"} then {
+ set isArray [object invoke -- $value GetType.IsArray]
+ }
+ } else {
+ set value null
+ }
+
+ if {$isArray} then {
+ return [object invoke -flags +NonPublic \
+ -parametertypes Array System.Data.SQLite.HelperMethods \
+ ToDisplayString $value]
+ } else {
+ return [object invoke -flags +NonPublic \
+ System.Data.SQLite.HelperMethods ToDisplayString $value]
+ }
+}
+
+###############################################################################
+
+proc createMemoryChangeSetForConnection { connection rawData } {
+ if {[isObjectHandle $connection] && $connection ne "null"} then {
+ return [$connection -alias CreateChangeSet $rawData]
+ }
+
+ return null
+}
+
+###############################################################################
+
+proc createMemoryChangeSetForSession { session {patch false} } {
+ if {[isObjectHandle $session] && $session ne "null"} then {
+ set byteArray null
+
+ if {$patch} then {
+ $session -alias CreatePatchSet byteArray
+ } else {
+ $session -alias CreateChangeSet byteArray
+ }
+
+ set rawData [createByteArray [arrayToList byteArray]]
+ object removeref $rawData
+
+ return $rawData
+ }
+
+ return null
+}
+
+###############################################################################
+
+proc writeRawDataToFile { fileName rawData } {
+ if {[isObjectHandle $rawData] && $rawData ne "null"} then {
+ set stream [object create -alias \
+ System.IO.FileStream $fileName Create Write]
+
+ $stream Write $rawData 0 [$rawData Length]
+ $stream Flush; $stream Close
+
+ return true
+ }
+
+ return false
+}
+
+###############################################################################
+
+proc openStreamChangeSetForConnection {
+ connection inputFileName outputFileName {varName ""} } {
+ if {[isObjectHandle $connection] && $connection ne "null"} then {
+ if {[string length $varName] > 0} then {
+ upvar 1 $varName state
+ }
+
+ if {[string length $inputFileName] > 0} then {
+ set state(inputStream) [object create -alias \
+ System.IO.FileStream $inputFileName Open Read]
+ } else {
+ set state(inputStream) null
+ }
+
+ if {[string length $outputFileName] > 0} then {
+ set state(outputStream) [object create -alias \
+ System.IO.FileStream $outputFileName Create Write]
+ } else {
+ set state(outputStream) null
+ }
+
+ set state(changeSet) [$connection -alias \
+ CreateChangeSet $state(inputStream) $state(outputStream)]
+
+ return true
+ }
+
+ return false
+}
+
+###############################################################################
+
+proc writeStreamChangeSetForSession { session fileName {patch false} } {
+ if {[isObjectHandle $session] && $session ne "null"} then {
+ set stream [object create -alias \
+ System.IO.FileStream $fileName Create Write]
+
+ if {$patch} then {
+ $session -alias CreatePatchSet $stream
+ } else {
+ $session -alias CreateChangeSet $stream
+ }
+
+ $stream Flush; $stream Close
+
+ return true
+ }
+
+ return false
+}
+
+###############################################################################
+
+proc changeSetFileToString { connection fileName includeValues } {
+ set result [list]
+
+ if {[isObjectHandle $connection] && $connection ne "null"} then {
+ if {[openStreamChangeSetForConnection \
+ $connection $fileName "" state]} then {
+ return [changeSetToString $state(changeSet) $includeValues]
+ }
+ }
+
+ return $result
+}
+
+###############################################################################
+
+proc metadataItemToString { item includeValues } {
+ set result [list]
+
+ if {[isObjectHandle $item] && $item ne "null"} then {
+ lappend result TableName [$item TableName]
+ lappend result NumberOfColumns [$item NumberOfColumns]
+ lappend result OperationCode [$item OperationCode]
+ lappend result Indirect [$item Indirect]
+
+ lappend result PrimaryKeyColumns \
+ [forDisplay $item PrimaryKeyColumns]
+
+ if {$includeValues} then {
+ set numberOfColumns [$item NumberOfColumns]
+
+ for {set index 0} {$index < $numberOfColumns} {incr index} {
+ set oldValue [$item GetOldValue $index]
+
+ lappend result OldValue $index \
+ [forDisplay $oldValue GetObject]
+
+ set newValue [$item GetNewValue $index]
+
+ lappend result NewValue $index \
+ [forDisplay $newValue GetObject]
+
+ set conflictValue [$item GetConflictValue $index]
+
+ lappend result ConflictValue $index \
+ [forDisplay $conflictValue GetObject]
+ }
+ }
+ }
+
+ return $result
+}
+
+###############################################################################
+
+proc changeSetToString { changeSet includeValues } {
+ set result [list]
+
+ if {[isObjectHandle $changeSet] && $changeSet ne "null"} then {
+ object foreach -alias item $changeSet {
+ lappend result [metadataItemToString $item $includeValues]
+ }
+ }
+
+ return $result
+}
+
+###############################################################################
+
+proc matchSession { connection session expr } {
+ if {[isObjectHandle $session] && $session ne "null"} then {
+ if {![$session IsEmpty]} then {
+ set rawData [createMemoryChangeSetForSession $session]
+ object removeref $rawData
+
+ if {[isObjectHandle $rawData] && $rawData ne "null"} then {
+ return [matchChangeSet [set changeSet \
+ [$connection -alias CreateChangeSet $rawData]] $expr]
+ }
+ }
+ }
+
+ return false
+}
+
+###############################################################################
+
+proc matchChangeSet { changeSet expr } {
+ if {[isObjectHandle $changeSet] && $changeSet ne "null"} then {
+ object foreach -alias item $changeSet {
+ if {[expr $expr]} then {return true}
+ }
+ }
+
+ return false
+}
+
+###############################################################################
+
+proc createTheSchema { db databaseName } {
+ sql execute $db [subst {
+ CREATE TABLE ${databaseName}.t1(x INTEGER PRIMARY KEY, y TEXT);
+ CREATE TABLE ${databaseName}.t2(x INTEGER PRIMARY KEY, y TEXT);
+ }]
+}
+
+###############################################################################
+
+proc makeSomeChanges { db table types {rowId ""} {count 5} } {
+ foreach type $types {
+ switch -nocase -- $type {
+ insert {
+ set text [appendArgs "inserted: " [getSomeText false $count]]
+
+ if {[string is integer -strict $rowId]} then {
+ sql execute $db [subst {
+ INSERT INTO ${table}(x, y) VALUES(?, ?);
+ }] [list param1 Int32 $rowId] [list param2 String $text]
+ } else {
+ sql execute $db [subst {
+ INSERT INTO ${table}(y) VALUES(?);
+ }] [list param1 String $text]
+ }
+ }
+ update {
+ set text [appendArgs "updated: " [getSomeText false $count]]
+
+ if {[string is integer -strict $rowId]} then {
+ sql execute $db [subst {
+ UPDATE ${table} SET y = ? WHERE x = ?;
+ }] [list param1 String $text] [list param2 Int32 $rowId]
+ } else {
+ sql execute $db [subst {
+ UPDATE ${table} SET y = ? WHERE x NOT IN (
+ (SELECT MIN(x) FROM ${table}), (SELECT MAX(x) FROM ${table})
+ );
+ }] [list param1 String $text]
+ }
+ }
+ delete {
+ if {[string is integer -strict $rowId]} then {
+ sql execute $db [subst {
+ DELETE FROM ${table} WHERE x = ?;
+ }] [list param1 Int32 $rowId]
+ } else {
+ sql execute $db [subst {
+ DELETE FROM ${table} WHERE x = (SELECT MIN(x) FROM ${table});
+ }]
+ }
+ }
+ }
+ }
+}
+
+###############################################################################
+
+proc captureChangeSetRawData {
+ connection databaseName tableName script {patch false} } {
+ if {![isObjectHandle $connection] || $connection eq "null"} then {
+ error "connection is invalid"
+ }
+
+ set session [$connection -alias CreateSession $databaseName]
+ $session AttachTable $tableName
+
+ if {![isObjectHandle $session] || $session eq "null"} then {
+ error "cannot create session"
+ }
+
+ catch {uplevel 1 $script}
+ set rawData [createMemoryChangeSetForSession $session $patch]
+ object removeref $rawData
+
+ return $rawData
+}
+
+###############################################################################
+
+proc captureChangeSetFile {
+ connection databaseName tableName script fileName {patch false} } {
+ if {![isObjectHandle $connection] || $connection eq "null"} then {
+ error "connection is invalid"
+ }
+
+ set session [$connection -alias CreateSession $databaseName]
+ $session AttachTable $tableName
+
+ if {![isObjectHandle $session] || $session eq "null"} then {
+ error "cannot create session"
+ }
+
+ catch {uplevel 1 $script}
+ writeStreamChangeSetForSession $session $fileName $patch
+}
+
+###############################################################################
+
+proc getChangeSetFileName { {suffix ""} } {
+ return [file join \
+ [getTemporaryDirectory] [appendArgs changes $suffix .bin]]
+}
+
+###############################################################################
+
+proc arrayToList { varName } {
+ set result [list]
+
+ upvar 1 $varName array
+
+ if {[array exists array]} then {
+ foreach name [lsort -integer [array names array]] {
+ lappend result $array($name)
+ }
+ }
+
+ return $result
+}
+
+###############################################################################
+
+proc createByteArray { list } {
+ set length [llength $list]
+ set result [object create -alias System.Byte\[\] $length]
+
+ for {set index 0} {$index < $length} {incr index} {
+ set element [lindex $list $index]
+ set value [object invoke -create Byte Parse $element]
+ $result SetValue $value $index
+ }
+
+ return $result
+}
+
+###############################################################################
+
+proc tableFilterCallbackT1 { clientData name } {
+ lappend ::callbackResults [object invoke -create \
+ System.Boolean Parse [expr {[$name ToString] in [list t1]}]]
+
+ return [lindex $::callbackResults end]
+}
+
+###############################################################################
+
+proc conflictCallback { clientData type item } {
+ set result Abort
+
+ if {[isObjectHandle $item] && $item ne "null"} then {
+ set result Omit
+
+ if {[$item OperationCode] ne "Delete"} then {
+ if {[isObjectHandle $type] && $type ne "null" && \
+ [$type ToString] in [list Data Conflict]} then {
+ set result Replace
+ }
+ }
+ }
+
+ lappend ::callbackResults [object invoke -create Enum Parse \
+ System.Data.SQLite.SQLiteChangeSetConflictResult $result false]
+
+ return [lindex $::callbackResults end]
+}
+
+###############################################################################
+
+runTest {test session-1.1 {basic extension usage} -setup {
+ setupDb [set fileName(0) session-1.1.db]
+
+ set fileName(1) [getChangeSetFileName 1]
+ set fileName(2) [getChangeSetFileName 2]
+
+ cleanupSomeText
+} -body {
+ createTheSchema $db main
+ makeSomeChanges $db t1 [list insert insert]
+
+ set connection [getDbConnection]
+
+ set session [$connection -alias CreateSession main]
+ $session AttachTable null
+
+ makeSomeChanges $db t1 [list insert update delete]
+
+ set rawData [createMemoryChangeSetForSession $session]
+ object removeref $rawData
+
+ writeRawDataToFile $fileName(1) $rawData
+ writeStreamChangeSetForSession $session $fileName(2)
+ openStreamChangeSetForConnection $connection $fileName(2) "" state
+
+ list [expr {[file size $fileName(1)] > 0}] \
+ [string equal [readFile $fileName(1)] [readFile $fileName(2)]] \
+ [changeSetToString $state(changeSet) true]
+} -cleanup {
+ cleanupSomeText
+
+ unset -nocomplain state rawData byteArray session
+
+ freeDbConnection
+
+ unset -nocomplain connection
+
+ cleanupFile $fileName(2)
+ cleanupFile $fileName(1)
+
+ cleanupDb $fileName(0)
+
+ unset -nocomplain db fileName
+} -constraints {eagle command.object monoBug28 command.sql compile.DATA SQLite\
+System.Data.SQLite SQLiteInterop\
+defineConstant.System.Data.SQLite.INTEROP_SESSION_EXTENSION} -result {True True\
+{{TableName t1 NumberOfColumns 2 OperationCode Delete Indirect False\
+PrimaryKeyColumns {[True, False]} OldValue 0 1 NewValue 0 \
+ConflictValue 0 OldValue 1 {"inserted: Alpha Bravo Charlie Delta\
+Echo"} NewValue 1 ConflictValue 1 } {TableName t1\
+NumberOfColumns 2 OperationCode Update Indirect False PrimaryKeyColumns {[True,\
+False]} OldValue 0 2 NewValue 0 ConflictValue 0 \
+OldValue 1 {"inserted: Foxtrot Golf Hotel India Juliet"} NewValue 1 {"updated:\
+Papa Quebec Romeo Sierra Tango"} ConflictValue 1 } {TableName t1\
+NumberOfColumns 2 OperationCode Insert Indirect False PrimaryKeyColumns {[True,\
+False]} OldValue 0 NewValue 0 3 ConflictValue 0 \
+OldValue 1 NewValue 1 {"inserted: Kilo Lima Mike November Oscar"}\
+ConflictValue 1 }}}}
+
+###############################################################################
+
+runTest {test session-1.2.1 {change set read/write/invert (memory)} -setup {
+ setupDb [set fileName session-1.2.1.db]
+
+ cleanupSomeText
+} -body {
+ createTheSchema $db main
+ makeSomeChanges $db t1 [list insert insert insert]
+
+ set connection [getDbConnection]
+
+ set session [$connection -alias CreateSession main]
+ $session AttachTable null
+
+ makeSomeChanges $db t1 [list insert update delete]
+
+ set rawData [createMemoryChangeSetForSession $session]
+ object removeref $rawData
+
+ set changeSet(1) \
+ [createMemoryChangeSetForConnection $connection $rawData]
+
+ object removeref $changeSet(1)
+
+ set changeSet(2) [$changeSet(1) -alias Invert]
+
+ list [changeSetToString $changeSet(2) true]
+} -cleanup {
+ cleanupSomeText
+
+ unset -nocomplain changeSet rawData byteArray session
+
+ freeDbConnection
+
+ unset -nocomplain connection
+
+ cleanupDb $fileName
+
+ unset -nocomplain db fileName
+} -constraints {eagle command.object monoBug28 command.sql compile.DATA SQLite\
+System.Data.SQLite SQLiteInterop\
+defineConstant.System.Data.SQLite.INTEROP_SESSION_EXTENSION} -result \
+{{{TableName t1 NumberOfColumns 2 OperationCode Insert Indirect False\
+PrimaryKeyColumns {[True, False]} OldValue 0 NewValue 0 1\
+ConflictValue 0 OldValue 1 NewValue 1 {"inserted:\
+Alpha Bravo Charlie Delta Echo"} ConflictValue 1 } {TableName t1\
+NumberOfColumns 2 OperationCode Update Indirect False PrimaryKeyColumns {[True,\
+False]} OldValue 0 2 NewValue 0 ConflictValue 0 \
+OldValue 1 {"updated: Uniform Victor Whiskey X-ray Yankee"} NewValue 1\
+{"inserted: Foxtrot Golf Hotel India Juliet"} ConflictValue 1 }\
+{TableName t1 NumberOfColumns 2 OperationCode Update Indirect False\
+PrimaryKeyColumns {[True, False]} OldValue 0 3 NewValue 0 \
+ConflictValue 0 OldValue 1 {"updated: Uniform Victor Whiskey X-ray\
+Yankee"} NewValue 1 {"inserted: Kilo Lima Mike November Oscar"} ConflictValue 1\
+} {TableName t1 NumberOfColumns 2 OperationCode Delete Indirect\
+False PrimaryKeyColumns {[True, False]} OldValue 0 4 NewValue 0 \
+ConflictValue 0 OldValue 1 {"inserted: Papa Quebec Romeo Sierra\
+Tango"} NewValue 1 ConflictValue 1 }}}}
+
+###############################################################################
+
+runTest {test session-1.2.2 {change set read/write/invert (stream)} -setup {
+ setupDb [set fileName(0) session-1.2.2.db]
+
+ set fileName(1) [getChangeSetFileName 1]
+ set fileName(2) [getChangeSetFileName 2]
+
+ cleanupSomeText
+} -body {
+ createTheSchema $db main
+ makeSomeChanges $db t1 [list insert insert insert]
+
+ set connection [getDbConnection]
+
+ set session [$connection -alias CreateSession main]
+ $session AttachTable null
+
+ makeSomeChanges $db t1 [list insert update delete]
+
+ writeStreamChangeSetForSession $session $fileName(1)
+
+ openStreamChangeSetForConnection \
+ $connection $fileName(1) $fileName(2) state
+
+ $state(changeSet) -alias Invert; unset state
+ list [changeSetFileToString $connection $fileName(2) true]
+} -cleanup {
+ cleanupSomeText
+
+ unset -nocomplain state byteArray session
+
+ freeDbConnection
+
+ unset -nocomplain connection
+
+ cleanupDb $fileName(0)
+
+ unset -nocomplain db fileName
+} -constraints {eagle command.object monoBug28 command.sql compile.DATA SQLite\
+System.Data.SQLite SQLiteInterop\
+defineConstant.System.Data.SQLite.INTEROP_SESSION_EXTENSION} -result \
+{{{TableName t1 NumberOfColumns 2 OperationCode Insert Indirect False\
+PrimaryKeyColumns {[True, False]} OldValue 0 NewValue 0 1\
+ConflictValue 0 OldValue 1 NewValue 1 {"inserted:\
+Alpha Bravo Charlie Delta Echo"} ConflictValue 1 } {TableName t1\
+NumberOfColumns 2 OperationCode Update Indirect False PrimaryKeyColumns {[True,\
+False]} OldValue 0 2 NewValue 0 ConflictValue 0 \
+OldValue 1 {"updated: Uniform Victor Whiskey X-ray Yankee"} NewValue 1\
+{"inserted: Foxtrot Golf Hotel India Juliet"} ConflictValue 1 }\
+{TableName t1 NumberOfColumns 2 OperationCode Update Indirect False\
+PrimaryKeyColumns {[True, False]} OldValue 0 3 NewValue 0 \
+ConflictValue 0 OldValue 1 {"updated: Uniform Victor Whiskey X-ray\
+Yankee"} NewValue 1 {"inserted: Kilo Lima Mike November Oscar"} ConflictValue 1\
+} {TableName t1 NumberOfColumns 2 OperationCode Delete Indirect\
+False PrimaryKeyColumns {[True, False]} OldValue 0 4 NewValue 0 \
+ConflictValue 0 OldValue 1 {"inserted: Papa Quebec Romeo Sierra\
+Tango"} NewValue 1 ConflictValue 1 }}}}
+
+###############################################################################
+
+runTest {test session-1.3 {enabled/disabled state} -setup {
+ setupDb [set fileName session-1.3.db]
+
+ cleanupSomeText
+} -body {
+ createTheSchema $db main
+ makeSomeChanges $db t1 [list insert]
+
+ set connection [getDbConnection]
+
+ set session [$connection -alias CreateSession main]
+ lappend result IsEnabled [$session IsEnabled]
+
+ $session AttachTable null
+ lappend result IsEnabled [$session IsEnabled]
+
+ $session SetToDisabled
+ lappend result IsEnabled [$session IsEnabled]
+
+ makeSomeChanges $db t1 [list insert]
+ lappend result IsEmpty [$session IsEmpty]
+
+ set rawData [createMemoryChangeSetForSession $session]
+ object removeref $rawData
+
+ lappend result Length [$rawData Length]
+
+ $session SetToEnabled
+ lappend result IsEnabled [$session IsEnabled]
+
+ makeSomeChanges $db t1 [list insert]
+ lappend result IsEmpty [$session IsEmpty]
+
+ set rawData [createMemoryChangeSetForSession $session]
+ object removeref $rawData
+
+ set changeSet(1) \
+ [createMemoryChangeSetForConnection $connection $rawData]
+
+ object removeref $changeSet(1)
+
+ lappend result [changeSetToString $changeSet(1) false]
+} -cleanup {
+ cleanupSomeText
+
+ unset -nocomplain result changeSet rawData byteArray session
+
+ freeDbConnection
+
+ unset -nocomplain connection
+
+ cleanupDb $fileName
+
+ unset -nocomplain db fileName
+} -constraints {eagle command.object monoBug28 command.sql compile.DATA SQLite\
+System.Data.SQLite SQLiteInterop\
+defineConstant.System.Data.SQLite.INTEROP_SESSION_EXTENSION} -result {IsEnabled\
+True IsEnabled True IsEnabled False IsEmpty True Length 0 IsEnabled True\
+IsEmpty False {{TableName t1 NumberOfColumns 2 OperationCode Insert Indirect\
+False PrimaryKeyColumns {[True, False]}}}}}
+
+###############################################################################
+
+runTest {test session-1.4 {direct/indirect state} -setup {
+ setupDb [set fileName session-1.4.db]
+
+ cleanupSomeText
+} -body {
+ createTheSchema $db main
+ makeSomeChanges $db t1 [list insert]
+
+ set connection [getDbConnection]
+
+ set session [$connection -alias CreateSession main]
+ lappend result IsIndirect [$session IsIndirect]
+
+ $session AttachTable null
+ lappend result IsIndirect [$session IsIndirect]
+
+ $session SetToIndirect
+ lappend result IsIndirect [$session IsIndirect]
+
+ makeSomeChanges $db t1 [list insert]
+ lappend result IsEmpty [$session IsEmpty]
+
+ set rawData [createMemoryChangeSetForSession $session]
+ object removeref $rawData
+
+ $session SetToDirect
+ lappend result IsIndirect [$session IsIndirect]
+
+ makeSomeChanges $db t1 [list insert]
+ lappend result IsEmpty [$session IsEmpty]
+
+ set rawData [createMemoryChangeSetForSession $session]
+ object removeref $rawData
+
+ set changeSet(1) \
+ [createMemoryChangeSetForConnection $connection $rawData]
+
+ object removeref $changeSet(1)
+
+ lappend result [changeSetToString $changeSet(1) false]
+} -cleanup {
+ cleanupSomeText
+
+ unset -nocomplain result changeSet rawData byteArray session
+
+ freeDbConnection
+
+ unset -nocomplain connection
+
+ cleanupDb $fileName
+
+ unset -nocomplain db fileName
+} -constraints {eagle command.object monoBug28 command.sql compile.DATA SQLite\
+System.Data.SQLite SQLiteInterop\
+defineConstant.System.Data.SQLite.INTEROP_SESSION_EXTENSION} -result \
+{IsIndirect False IsIndirect False IsIndirect True IsEmpty False IsIndirect\
+False IsEmpty False {{TableName t1 NumberOfColumns 2 OperationCode Insert\
+Indirect True PrimaryKeyColumns {[True, False]}} {TableName t1 NumberOfColumns\
+2 OperationCode Insert Indirect False PrimaryKeyColumns {[True, False]}}}}}
+
+###############################################################################
+
+runTest {test session-1.5 {table filter} -setup {
+ setupDb [set fileName session-1.5.db]
+
+ cleanupSomeText
+} -body {
+ createTheSchema $db main
+ makeSomeChanges $db t1 [list insert]
+
+ set connection [getDbConnection]
+
+ set session [$connection -alias CreateSession main]
+
+ $session -marshalflags +DynamicCallback \
+ SetTableFilter tableFilterCallbackT1 null
+
+ makeSomeChanges $db t2 [list insert]
+ lappend result IsEmpty [$session IsEmpty]
+
+ lappend result MatchT2 [matchSession $connection $session {
+ [$item TableName] eq "t2"
+ }]
+
+ makeSomeChanges $db t1 [list insert]
+ lappend result IsEmpty [$session IsEmpty]
+
+ lappend result MatchT1 [matchSession $connection $session {
+ [$item TableName] eq "t1"
+ }]
+
+ $session SetTableFilter null null
+
+ makeSomeChanges $db t2 [list insert]
+ lappend result IsEmpty [$session IsEmpty]
+
+ lappend result MatchT2 [matchSession $connection $session {
+ [$item TableName] eq "t2"
+ }]
+
+ makeSomeChanges $db t1 [list insert]
+ lappend result IsEmpty [$session IsEmpty]
+
+ lappend result MatchT1 [matchSession $connection $session {
+ [$item TableName] eq "t1"
+ }]
+
+ set result
+} -cleanup {
+ cleanupSomeText
+
+ unset -nocomplain result session
+
+ freeDbConnection
+
+ unset -nocomplain connection
+
+ cleanupDb $fileName
+
+ catch {object removecallback tableFilterCallbackT1}
+
+ catch {
+ foreach callbackResult $callbackResults {
+ catch {object dispose $callbackResult}
+ }
+ }
+
+ unset -nocomplain callbackResult callbackResults db fileName
+} -constraints {eagle command.object monoBug28 command.sql compile.DATA SQLite\
+System.Data.SQLite SQLiteInterop\
+defineConstant.System.Data.SQLite.INTEROP_SESSION_EXTENSION} -result {IsEmpty\
+True MatchT2 false IsEmpty False MatchT1 true IsEmpty False MatchT2 true\
+IsEmpty False MatchT1 true}}
+
+###############################################################################
+
+runTest {test session-1.6.1 {combine/apply change sets (memory)} -setup {
+ setupDb [set fileName session-1.6.1.db]
+
+ cleanupSomeText
+} -body {
+ createTheSchema $db main
+ makeSomeChanges $db t1 [list insert insert]
+
+ set connection [getDbConnection]
+
+ set rawData(1) [captureChangeSetRawData $connection main null {
+ makeSomeChanges $db t1 [list delete] 1
+ }]; object removeref $rawData(1)
+
+ makeSomeChanges $db t1 [list insert] 1
+
+ set rawData(2) [captureChangeSetRawData $connection main null {
+ makeSomeChanges $db t1 [list insert]
+ }]; object removeref $rawData(2)
+
+ set changeSet(1) \
+ [createMemoryChangeSetForConnection $connection $rawData(1)]
+
+ object removeref $changeSet(1)
+
+ set changeSet(2) \
+ [createMemoryChangeSetForConnection $connection $rawData(2)]
+
+ object removeref $changeSet(2)
+
+ set changeSet(3) [$changeSet(1) -alias CombineWith $changeSet(2)]
+
+ $changeSet(3) -marshalflags +DynamicCallback \
+ Apply conflictCallback null
+
+ list [changeSetToString $changeSet(3) true] Data \
+ [sql execute -execute reader -format list $db \
+ {SELECT x, y FROM t1 ORDER BY x;}]
+} -cleanup {
+ cleanupSomeText
+
+ unset -nocomplain changeSet rawData
+
+ freeDbConnection
+
+ unset -nocomplain connection
+
+ cleanupDb $fileName
+
+ catch {object removecallback conflictCallback}
+
+ catch {
+ foreach callbackResult $callbackResults {
+ catch {object dispose $callbackResult}
+ }
+ }
+
+ unset -nocomplain callbackResult callbackResults db fileName
+} -constraints {eagle command.object monoBug28 command.sql compile.DATA SQLite\
+System.Data.SQLite SQLiteInterop\
+defineConstant.System.Data.SQLite.INTEROP_SESSION_EXTENSION} -result \
+{{{TableName t1 NumberOfColumns 2 OperationCode Delete Indirect False\
+PrimaryKeyColumns {[True, False]} OldValue 0 1 NewValue 0 \
+ConflictValue 0 OldValue 1 {"inserted: Alpha Bravo Charlie Delta\
+Echo"} NewValue 1 ConflictValue 1 } {TableName t1\
+NumberOfColumns 2 OperationCode Insert Indirect False PrimaryKeyColumns {[True,\
+False]} OldValue 0 NewValue 0 3 ConflictValue 0 \
+OldValue 1 NewValue 1 {"inserted: Papa Quebec Romeo Sierra Tango"}\
+ConflictValue 1 }} Data {1 {inserted: Kilo Lima Mike November\
+Oscar} 2 {inserted: Foxtrot Golf Hotel India Juliet} 3 {inserted: Papa Quebec\
+Romeo Sierra Tango}}}}
+
+###############################################################################
+
+runTest {test session-1.6.2 {combine/apply change sets (stream)} -setup {
+ setupDb [set fileName(0) session-1.6.2.db]
+
+ set fileName(1) [getChangeSetFileName 1]
+ set fileName(2) [getChangeSetFileName 2]
+ set fileName(3) [getChangeSetFileName 3]
+
+ cleanupSomeText
+} -body {
+ createTheSchema $db main
+ makeSomeChanges $db t1 [list insert insert]
+
+ set connection [getDbConnection]
+
+ captureChangeSetFile $connection main null {
+ makeSomeChanges $db t1 [list delete] 1
+ } $fileName(1)
+
+ makeSomeChanges $db t1 [list insert] 1
+
+ captureChangeSetFile $connection main null {
+ makeSomeChanges $db t1 [list insert]
+ } $fileName(2)
+
+ openStreamChangeSetForConnection \
+ $connection $fileName(1) $fileName(3) state1
+
+ openStreamChangeSetForConnection \
+ $connection $fileName(2) "" state2
+
+ $state1(changeSet) CombineWith $state2(changeSet); unset state1
+
+ openStreamChangeSetForConnection \
+ $connection $fileName(3) "" state3
+
+ $state3(changeSet) -marshalflags +DynamicCallback \
+ Apply conflictCallback null; unset state3
+
+ openStreamChangeSetForConnection \
+ $connection $fileName(3) "" state4
+
+ list [changeSetToString $state4(changeSet) true] Data \
+ [sql execute -execute reader -format list $db \
+ {SELECT x, y FROM t1 ORDER BY x;}]
+} -cleanup {
+ cleanupSomeText
+
+ unset -nocomplain state4 state3 state2 state1
+
+ freeDbConnection
+
+ unset -nocomplain connection
+
+ cleanupDb $fileName(0)
+
+ catch {object removecallback conflictCallback}
+
+ catch {
+ foreach callbackResult $callbackResults {
+ catch {object dispose $callbackResult}
+ }
+ }
+
+ unset -nocomplain callbackResult callbackResults db fileName
+} -constraints {eagle command.object monoBug28 command.sql compile.DATA SQLite\
+System.Data.SQLite SQLiteInterop\
+defineConstant.System.Data.SQLite.INTEROP_SESSION_EXTENSION} -result \
+{{{TableName t1 NumberOfColumns 2 OperationCode Delete Indirect False\
+PrimaryKeyColumns {[True, False]} OldValue 0 1 NewValue 0 \
+ConflictValue 0 OldValue 1 {"inserted: Alpha Bravo Charlie Delta\
+Echo"} NewValue 1 ConflictValue 1 } {TableName t1\
+NumberOfColumns 2 OperationCode Insert Indirect False PrimaryKeyColumns {[True,\
+False]} OldValue 0 NewValue 0 3 ConflictValue 0 \
+OldValue 1 NewValue 1 {"inserted: Papa Quebec Romeo Sierra Tango"}\
+ConflictValue 1 }} Data {1 {inserted: Kilo Lima Mike November\
+Oscar} 2 {inserted: Foxtrot Golf Hotel India Juliet} 3 {inserted: Papa Quebec\
+Romeo Sierra Tango}}}}
+
+###############################################################################
+
+runTest {test session-1.7.1 {combine/apply patch sets (memory)} -setup {
+ setupDb [set fileName session-1.7.1.db]
+
+ cleanupSomeText
+} -body {
+ createTheSchema $db main
+ makeSomeChanges $db t1 [list insert insert]
+
+ set connection [getDbConnection]
+
+ set rawData(1) [captureChangeSetRawData $connection main null {
+ makeSomeChanges $db t1 [list delete] 1
+ } true]; object removeref $rawData(1)
+
+ makeSomeChanges $db t1 [list insert] 1
+
+ set rawData(2) [captureChangeSetRawData $connection main null {
+ makeSomeChanges $db t1 [list insert]
+ } true]; object removeref $rawData(2)
+
+ set changeSet(1) \
+ [createMemoryChangeSetForConnection $connection $rawData(1)]
+
+ object removeref $changeSet(1)
+
+ set changeSet(2) \
+ [createMemoryChangeSetForConnection $connection $rawData(2)]
+
+ object removeref $changeSet(2)
+
+ set changeSet(3) [$changeSet(1) -alias CombineWith $changeSet(2)]
+
+ $changeSet(3) -marshalflags +DynamicCallback \
+ Apply conflictCallback null
+
+ list [changeSetToString $changeSet(3) true] Data \
+ [sql execute -execute reader -format list $db \
+ {SELECT x, y FROM t1 ORDER BY x;}]
+} -cleanup {
+ cleanupSomeText
+
+ unset -nocomplain changeSet rawData
+
+ freeDbConnection
+
+ unset -nocomplain connection
+
+ cleanupDb $fileName
+
+ catch {object removecallback conflictCallback}
+
+ catch {
+ foreach callbackResult $callbackResults {
+ catch {object dispose $callbackResult}
+ }
+ }
+
+ unset -nocomplain callbackResult callbackResults db fileName
+} -constraints {eagle command.object monoBug28 command.sql compile.DATA SQLite\
+System.Data.SQLite SQLiteInterop\
+defineConstant.System.Data.SQLite.INTEROP_SESSION_EXTENSION} -result \
+{{{TableName t1 NumberOfColumns 2 OperationCode Delete Indirect False\
+PrimaryKeyColumns {[True, False]} OldValue 0 1 NewValue 0 \
+ConflictValue 0 OldValue 1 NewValue 1 \
+ConflictValue 1 } {TableName t1 NumberOfColumns 2 OperationCode\
+Insert Indirect False PrimaryKeyColumns {[True, False]} OldValue 0 \
+NewValue 0 3 ConflictValue 0 OldValue 1 NewValue 1\
+{"inserted: Papa Quebec Romeo Sierra Tango"} ConflictValue 1 }}\
+Data {2 {inserted: Foxtrot Golf Hotel India Juliet} 3 {inserted: Papa Quebec\
+Romeo Sierra Tango}}}}
+
+###############################################################################
+
+runTest {test session-1.7.2 {combine/apply patch sets (stream)} -setup {
+ setupDb [set fileName(0) session-1.7.2.db]
+
+ set fileName(1) [getChangeSetFileName 1]
+ set fileName(2) [getChangeSetFileName 2]
+ set fileName(3) [getChangeSetFileName 3]
+
+ cleanupSomeText
+} -body {
+ createTheSchema $db main
+ makeSomeChanges $db t1 [list insert insert]
+
+ set connection [getDbConnection]
+
+ captureChangeSetFile $connection main null {
+ makeSomeChanges $db t1 [list delete] 1
+ } $fileName(1) true
+
+ makeSomeChanges $db t1 [list insert] 1
+
+ captureChangeSetFile $connection main null {
+ makeSomeChanges $db t1 [list insert]
+ } $fileName(2) true
+
+ openStreamChangeSetForConnection \
+ $connection $fileName(1) $fileName(3) state1
+
+ openStreamChangeSetForConnection \
+ $connection $fileName(2) "" state2
+
+ $state1(changeSet) CombineWith $state2(changeSet); unset state1
+
+ openStreamChangeSetForConnection \
+ $connection $fileName(3) "" state3
+
+ $state3(changeSet) -marshalflags +DynamicCallback \
+ Apply conflictCallback null; unset state3
+
+ openStreamChangeSetForConnection \
+ $connection $fileName(3) "" state4
+
+ list [changeSetToString $state4(changeSet) true] Data \
+ [sql execute -execute reader -format list $db \
+ {SELECT x, y FROM t1 ORDER BY x;}]
+} -cleanup {
+ cleanupSomeText
+
+ unset -nocomplain state4 state3 state2 state1
+
+ freeDbConnection
+
+ unset -nocomplain connection
+
+ cleanupDb $fileName(0)
+
+ catch {object removecallback conflictCallback}
+
+ catch {
+ foreach callbackResult $callbackResults {
+ catch {object dispose $callbackResult}
+ }
+ }
+
+ unset -nocomplain callbackResult callbackResults db fileName
+} -constraints {eagle command.object monoBug28 command.sql compile.DATA SQLite\
+System.Data.SQLite SQLiteInterop\
+defineConstant.System.Data.SQLite.INTEROP_SESSION_EXTENSION} -result \
+{{{TableName t1 NumberOfColumns 2 OperationCode Delete Indirect False\
+PrimaryKeyColumns {[True, False]} OldValue 0 1 NewValue 0 \
+ConflictValue 0 OldValue 1 NewValue 1 \
+ConflictValue 1 } {TableName t1 NumberOfColumns 2 OperationCode\
+Insert Indirect False PrimaryKeyColumns {[True, False]} OldValue 0 \
+NewValue 0 3 ConflictValue 0 OldValue 1 NewValue 1\
+{"inserted: Papa Quebec Romeo Sierra Tango"} ConflictValue 1 }}\
+Data {2 {inserted: Foxtrot Golf Hotel India Juliet} 3 {inserted: Papa Quebec\
+Romeo Sierra Tango}}}}
+
+###############################################################################
+
+runTest {test session-1.8 {differences between tables} -setup {
+ setupDb [set fileName session-1.8.db]
+
+ cleanupSomeText
+} -body {
+ createTheSchema $db main
+ makeSomeChanges $db t1 [list insert insert]
+
+ set result [list]
+
+ lappend result [sql execute -execute reader -format list $db \
+ {SELECT x, y FROM t1 ORDER BY x;}]
+
+ createTheSchema $db temp
+ sql execute $db {INSERT INTO temp.t1 SELECT * FROM main.t1;}
+ makeSomeChanges $db temp.t1 [list insert insert]
+
+ set connection [getDbConnection]
+
+ set session [$connection -alias CreateSession main]
+ $session AttachTable null
+ $session LoadDifferencesFromTable temp t1
+
+ lappend result [sql execute -execute reader -format list $db \
+ {SELECT x, y FROM t1 ORDER BY x;}]
+} -cleanup {
+ cleanupSomeText
+
+ unset -nocomplain result session
+
+ freeDbConnection
+
+ unset -nocomplain connection
+
+ cleanupDb $fileName
+
+ unset -nocomplain db fileName
+} -constraints {eagle command.object monoBug28 command.sql compile.DATA SQLite\
+System.Data.SQLite SQLiteInterop\
+defineConstant.System.Data.SQLite.INTEROP_SESSION_EXTENSION} -result {{1\
+{inserted: Alpha Bravo Charlie Delta Echo} 2 {inserted: Foxtrot Golf Hotel\
+India Juliet}} {1 {inserted: Alpha Bravo Charlie Delta Echo} 2 {inserted:\
+Foxtrot Golf Hotel India Juliet} 3 {inserted: Kilo Lima Mike November Oscar} 4\
+{inserted: Papa Quebec Romeo Sierra Tango}}}}
+
+###############################################################################
+
+rename conflictCallback ""
+rename tableFilterCallbackT1 ""
+rename createByteArray ""
+rename arrayToList ""
+rename getChangeSetFileName ""
+rename captureChangeSetFile ""
+rename captureChangeSetRawData ""
+rename makeSomeChanges ""
+rename createTheSchema ""
+rename matchChangeSet ""
+rename matchSession ""
+rename changeSetToString ""
+rename metadataItemToString ""
+rename changeSetFileToString ""
+rename writeStreamChangeSetForSession ""
+rename openStreamChangeSetForConnection ""
+rename writeRawDataToFile ""
+rename createMemoryChangeSetForSession ""
+rename createMemoryChangeSetForConnection ""
+rename forDisplay ""
+rename cleanupSomeText ""
+rename getSomeText ""
+
+###############################################################################
+
+runSQLiteTestEpilogue
+runTestEpilogue
Index: data/exclude_src.txt
==================================================================
--- data/exclude_src.txt
+++ data/exclude_src.txt
@@ -36,31 +36,29 @@
Externals/Eagle/lib/Eagle1.0/init.eagle
Externals/Eagle/lib/Eagle1.0/list.eagle
Externals/Eagle/lib/Eagle1.0/object.eagle
Externals/Eagle/lib/Eagle1.0/pkgIndex.eagle
Externals/Eagle/lib/Eagle1.0/pkgIndex.tcl
+Externals/Eagle/lib/Eagle1.0/pkgt.eagle
Externals/Eagle/lib/Eagle1.0/platform.eagle
Externals/Eagle/lib/Eagle1.0/process.eagle
Externals/Eagle/lib/Eagle1.0/runopt.eagle
Externals/Eagle/lib/Eagle1.0/safe.eagle
Externals/Eagle/lib/Eagle1.0/shell.eagle
Externals/Eagle/lib/Eagle1.0/shim.eagle
Externals/Eagle/lib/Eagle1.0/test.eagle
Externals/Eagle/lib/Eagle1.0/testlog.eagle
Externals/Eagle/lib/Eagle1.0/unkobj.eagle
+Externals/Eagle/lib/Eagle1.0/unzip.eagle
Externals/Eagle/lib/Eagle1.0/update.eagle
Externals/Eagle/lib/Eagle1.0/word.tcl
-Externals/Eagle/lib/Test1.0/all.eagle
-Externals/Eagle/lib/Test1.0/constraints.eagle
-Externals/Eagle/lib/Test1.0/epilogue.eagle
-Externals/Eagle/lib/Test1.0/pkgIndex.eagle
-Externals/Eagle/lib/Test1.0/pkgIndex.tcl
-Externals/Eagle/lib/Test1.0/prologue.eagle
+Externals/Eagle/lib/Test1.0/*
Externals/EntityFramework/*
Externals/HtmlHelp/*
Externals/MSVCPP/*
Externals/NDoc3/*
+Externals/vswhere/*
obj/*
Setup/Output/*
Setup/set_user_*.bat
SQLite.Designer/obj/*
SQLite.Designer/Properties/*
Index: lib/System.Data.SQLite/common.eagle
==================================================================
--- lib/System.Data.SQLite/common.eagle
+++ lib/System.Data.SQLite/common.eagle
@@ -1530,12 +1530,13 @@
if {![info exists ::no(shimSQLiteDefineConstantConstraints)]} then {
foreach defineConstant [list \
INTEROP_EXTENSION_FUNCTIONS INTEROP_FTS5_EXTENSION \
INTEROP_JSON1_EXTENSION INTEROP_PERCENTILE_EXTENSION \
INTEROP_REGEXP_EXTENSION INTEROP_TEST_EXTENSION \
- INTEROP_SHA1_EXTENSION INTEROP_TOTYPE_EXTENSION \
- INTEROP_VIRTUAL_TABLE USE_INTEROP_DLL] {
+ INTEROP_SESSION_EXTENSION INTEROP_SHA1_EXTENSION \
+ INTEROP_TOTYPE_EXTENSION INTEROP_VIRTUAL_TABLE \
+ USE_INTEROP_DLL] {
addConstraint [appendArgs \
[getSQLiteDefineConstantPrefix] $defineConstant]
}
}
@@ -4573,13 +4574,13 @@
CHECK_STATE COUNT_HANDLE DEBUG INTEROP_CODEC INTEROP_DEBUG \
INTEROP_EXTENSION_FUNCTIONS INTEROP_FTS5_EXTENSION \
INTEROP_INCLUDE_CEROD INTEROP_INCLUDE_EXTRA INTEROP_INCLUDE_SEE \
INTEROP_INCLUDE_ZIPVFS INTEROP_JSON1_EXTENSION \
INTEROP_LEGACY_CLOSE INTEROP_LOG INTEROP_PERCENTILE_EXTENSION \
- INTEROP_REGEXP_EXTENSION INTEROP_SHA1_EXTENSION \
- INTEROP_TEST_EXTENSION INTEROP_TOTYPE_EXTENSION \
- INTEROP_VIRTUAL_TABLE \
+ INTEROP_REGEXP_EXTENSION INTEROP_SESSION_EXTENSION \
+ INTEROP_SHA1_EXTENSION INTEROP_TEST_EXTENSION \
+ INTEROP_TOTYPE_EXTENSION INTEROP_VIRTUAL_TABLE \
NET_20 NET_35 NET_40 NET_45 NET_451 NET_452 NET_46 NET_461 \
NET_462 NET_COMPACT_20 PLATFORM_COMPACTFRAMEWORK \
PRELOAD_NATIVE_LIBRARY RETARGETABLE SQLITE_STANDARD \
THROW_ON_DISPOSED TRACE TRACE_CONNECTION TRACE_DETECTION \
TRACE_HANDLE TRACE_PRELOAD TRACE_SHARED TRACE_STATEMENT \
Index: readme.htm
==================================================================
--- readme.htm
+++ readme.htm
@@ -211,10 +211,11 @@
1.0.106.0 - October XX, 2017 (release scheduled)
- Updated to SQLite 3.20.1.
- Add BindDecimalAsText and GetDecimalAsText connection flags to force binding and returning of decimal values as text. Pursuant to [b167206ad3].
+ - Add support for the native session extension.
1.0.105.2 - June 12, 2017
Index: www/news.wiki
==================================================================
--- www/news.wiki
+++ www/news.wiki
@@ -48,10 +48,11 @@
1.0.106.0 - October XX, 2017 (release scheduled)
- Updated to [https://www.sqlite.org/releaselog/3_20_1.html|SQLite 3.20.1].
- Add BindDecimalAsText and GetDecimalAsText connection flags to force binding and returning of decimal values as text. Pursuant to [b167206ad3].
+ - Add support for the native [https://www.sqlite.org/sessionintro.html|session] extension.
1.0.105.2 - June 12, 2017