Index: Doc/Extra/version.html
==================================================================
--- Doc/Extra/version.html
+++ Doc/Extra/version.html
@@ -47,10 +47,11 @@
- In the SQLiteFunction class, when calling user-provided methods from a delegate called by native code, avoid throwing exceptions, optionally tracing the caught exceptions. Fix for [8a426d12eb].
- Add Visual Studio 2005 support to all the applicable solution/project files, their associated supporting files, and the test suite.
- Add Visual Studio 2005 support to the redesigned designer support installer.
- Add experimental support for "pre-loading" the native SQLite library based on the processor architecture of the current process. This feature is now enabled by default at compile-time.
+ - Add support for the native SQLite Online Backup API. Fix for [c71846ed57].
- Acquire and hold a static data lock while checking if the native SQLite library has been initialized to prevent a subtle race condition that can result in superfluous error messages. Fix for [72905c9a77].
- Support tracing of all parameter binding activity and use the connection flags to control what is traced.
- When converting a DateTime instance of an "Unspecified" kind to a string, use the same kind as the connection, if available.
- Add overload of the SQLiteDataReader.GetValues method that returns a NameValueCollection.
- Add static ToUnixEpoch method to the SQLiteConvert class to convert a DateTime value to the number of whole seconds since the Unix epoch.
Index: System.Data.SQLite/SQLite3.cs
==================================================================
--- System.Data.SQLite/SQLite3.cs
+++ System.Data.SQLite/SQLite3.cs
@@ -1367,10 +1367,201 @@
int rc = UnsafeNativeMethods.sqlite3_config(
(int)SQLiteConfigOpsEnum.SQLITE_CONFIG_LOG, func, (IntPtr)0);
return rc;
}
+
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+
+ ///
+ /// Creates a new SQLite backup object based on the provided destination
+ /// database connection. The source database connection is the one
+ /// associated with this object. The source and destination database
+ /// connections cannot be the same.
+ ///
+ /// The destination database connection.
+ /// The destination database name.
+ /// The source database name.
+ /// The newly created backup object.
+ internal override SQLiteBackup InitializeBackup(
+ SQLiteConnection destCnn,
+ string destName,
+ string sourceName
+ )
+ {
+ if (destCnn == null)
+ throw new ArgumentNullException("destCnn");
+
+ if (destName == null)
+ throw new ArgumentNullException("destName");
+
+ if (sourceName == null)
+ throw new ArgumentNullException("sourceName");
+
+ SQLite3 destSqlite3 = destCnn._sql as SQLite3;
+
+ if (destSqlite3 == null)
+ throw new ArgumentException(
+ "Destination connection has no wrapper.",
+ "destCnn");
+
+ SQLiteConnectionHandle destHandle = destSqlite3._sql;
+
+ if (destHandle == null)
+ throw new ArgumentException(
+ "Destination connection has an invalid handle.",
+ "destCnn");
+
+ SQLiteConnectionHandle sourceHandle = _sql;
+
+ if (sourceHandle == null)
+ throw new InvalidOperationException(
+ "Source connection has an invalid handle.");
+
+ byte[] zDestName = ToUTF8(destName);
+ byte[] zSourceName = ToUTF8(sourceName);
+
+ IntPtr backup = UnsafeNativeMethods.sqlite3_backup_init(
+ destHandle, zDestName, sourceHandle, zSourceName);
+
+ if (backup == IntPtr.Zero)
+ throw new SQLiteException(ResultCode(), SQLiteLastError());
+
+ return new SQLiteBackup(
+ this, backup, destHandle, zDestName, sourceHandle, zSourceName);
+ }
+
+ ///
+ /// Copies up to N pages from the source database to the destination
+ /// database associated with the specified backup object.
+ ///
+ /// The backup object to use.
+ ///
+ /// The number of pages to copy, negative to copy all remaining pages.
+ ///
+ ///
+ /// Set to true if the operation needs to be retried due to database
+ /// locking issues; otherwise, set to false.
+ ///
+ ///
+ /// True if there are more pages to be copied, false otherwise.
+ ///
+ internal override bool StepBackup(
+ SQLiteBackup backup,
+ int nPage,
+ out bool retry
+ )
+ {
+ retry = false;
+
+ if (backup == null)
+ throw new ArgumentNullException("backup");
+
+ SQLiteBackupHandle handle = backup._sqlite_backup;
+
+ if (handle == null)
+ throw new InvalidOperationException(
+ "Backup object has an invalid handle.");
+
+ int n = UnsafeNativeMethods.sqlite3_backup_step(handle, nPage);
+ backup._stepResult = n; /* NOTE: Save for use by FinishBackup. */
+
+ if (n == (int)SQLiteErrorCode.Ok)
+ {
+ return true;
+ }
+ else if (n == (int)SQLiteErrorCode.Busy)
+ {
+ retry = true;
+ return true;
+ }
+ else if (n == (int)SQLiteErrorCode.Locked)
+ {
+ retry = true;
+ return true;
+ }
+ else if (n == (int)SQLiteErrorCode.Done)
+ {
+ return false;
+ }
+ else
+ {
+ throw new SQLiteException(n, SQLiteLastError());
+ }
+ }
+
+ ///
+ /// Returns the number of pages remaining to be copied from the source
+ /// database to the destination database associated with the specified
+ /// backup object.
+ ///
+ /// The backup object to check.
+ /// The number of pages remaining to be copied.
+ internal override int RemainingBackup(
+ SQLiteBackup backup
+ )
+ {
+ if (backup == null)
+ throw new ArgumentNullException("backup");
+
+ SQLiteBackupHandle handle = backup._sqlite_backup;
+
+ if (handle == null)
+ throw new InvalidOperationException(
+ "Backup object has an invalid handle.");
+
+ return UnsafeNativeMethods.sqlite3_backup_remaining(handle);
+ }
+
+ ///
+ /// Returns the total number of pages in the source database associated
+ /// with the specified backup object.
+ ///
+ /// The backup object to check.
+ /// The total number of pages in the source database.
+ internal override int PageCountBackup(
+ SQLiteBackup backup
+ )
+ {
+ if (backup == null)
+ throw new ArgumentNullException("backup");
+
+ SQLiteBackupHandle handle = backup._sqlite_backup;
+
+ if (handle == null)
+ throw new InvalidOperationException(
+ "Backup object has an invalid handle.");
+
+ return UnsafeNativeMethods.sqlite3_backup_pagecount(handle);
+ }
+
+ ///
+ /// Destroys the backup object, rolling back any backup that may be in
+ /// progess.
+ ///
+ /// The backup object to destroy.
+ internal override void FinishBackup(
+ SQLiteBackup backup
+ )
+ {
+ if (backup == null)
+ throw new ArgumentNullException("backup");
+
+ SQLiteBackupHandle handle = backup._sqlite_backup;
+
+ if (handle == null)
+ throw new InvalidOperationException(
+ "Backup object has an invalid handle.");
+
+ int n = UnsafeNativeMethods.sqlite3_backup_finish(handle);
+ handle.SetHandleAsInvalid();
+
+ if ((n > 0) && (n != backup._stepResult))
+ throw new SQLiteException(n, SQLiteLastError());
+ }
+
+ ///////////////////////////////////////////////////////////////////////////////////////////////
///
/// Determines if the SQLite core library has been initialized for the
/// current process.
///
ADDED System.Data.SQLite/SQLiteBackup.cs
Index: System.Data.SQLite/SQLiteBackup.cs
==================================================================
--- /dev/null
+++ System.Data.SQLite/SQLiteBackup.cs
@@ -0,0 +1,149 @@
+/********************************************************
+ * ADO.NET 2.0 Data Provider for SQLite Version 3.X
+ * Written by Robert Simpson (robert@blackcastlesoft.com)
+ *
+ * Released to the public domain, use at your own risk!
+ ********************************************************/
+
+namespace System.Data.SQLite
+{
+ using System;
+
+ ///
+ /// Represents a single SQL backup in SQLite.
+ ///
+ internal sealed class SQLiteBackup : IDisposable
+ {
+ ///
+ /// The underlying SQLite object this backup is bound to.
+ ///
+ internal SQLiteBase _sql;
+
+ ///
+ /// The actual backup handle.
+ ///
+ internal SQLiteBackupHandle _sqlite_backup;
+
+ ///
+ /// The destination database for the backup.
+ ///
+ internal IntPtr _destDb;
+
+ ///
+ /// The destination database name for the backup.
+ ///
+ internal byte[] _zDestName;
+
+ ///
+ /// The source database for the backup.
+ ///
+ internal IntPtr _sourceDb;
+
+ ///
+ /// The source database name for the backup.
+ ///
+ internal byte[] _zSourceName;
+
+ ///
+ /// The last result from the StepBackup method of the SQLite3 class.
+ /// This is used to determine if the call to the FinishBackup method of
+ /// the SQLite3 class should throw an exception when it receives a non-Ok
+ /// return code from the core SQLite library.
+ ///
+ internal int _stepResult;
+
+ ///
+ /// Initializes the backup.
+ ///
+ /// The base SQLite object.
+ /// The backup handle.
+ /// The destination database for the backup.
+ /// The destination database name for the backup.
+ /// The source database for the backup.
+ /// The source database name for the backup.
+ internal SQLiteBackup(
+ SQLiteBase sqlbase,
+ SQLiteBackupHandle backup,
+ IntPtr destDb,
+ byte[] zDestName,
+ IntPtr sourceDb,
+ byte[] zSourceName
+ )
+ {
+ _sql = sqlbase;
+ _sqlite_backup = backup;
+ _destDb = destDb;
+ _zDestName = zDestName;
+ _sourceDb = sourceDb;
+ _zSourceName = zSourceName;
+ }
+
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+
+ #region IDisposable Members
+ ///
+ /// Disposes and finalizes the backup.
+ ///
+ public void Dispose()
+ {
+ Dispose(true);
+ GC.SuppressFinalize(this);
+ }
+ #endregion
+
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+
+ #region IDisposable "Pattern" Members
+ private bool disposed;
+ private void CheckDisposed() /* throw */
+ {
+#if THROW_ON_DISPOSED
+ if (disposed)
+ throw new ObjectDisposedException(typeof(SQLiteBackup).Name);
+#endif
+ }
+
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+
+ private void Dispose(bool disposing)
+ {
+ if (!disposed)
+ {
+ if (disposing)
+ {
+ ////////////////////////////////////
+ // dispose managed resources here...
+ ////////////////////////////////////
+
+ if (_sqlite_backup != null)
+ {
+ _sqlite_backup.Dispose();
+ _sqlite_backup = null;
+ }
+
+ _zSourceName = null;
+ _sourceDb = IntPtr.Zero;
+ _zDestName = null;
+ _destDb = IntPtr.Zero;
+ _sql = null;
+ }
+
+ //////////////////////////////////////
+ // release unmanaged resources here...
+ //////////////////////////////////////
+
+ disposed = true;
+ }
+ }
+ #endregion
+
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+
+ #region Destructor
+ ~SQLiteBackup()
+ {
+ Dispose(false);
+ }
+ #endregion
+ }
+}
Index: System.Data.SQLite/SQLiteBase.cs
==================================================================
--- System.Data.SQLite/SQLiteBase.cs
+++ System.Data.SQLite/SQLiteBase.cs
@@ -221,10 +221,65 @@
get;
}
internal abstract int FileControl(string zDbName, int op, IntPtr pArg);
+ ///
+ /// Creates a new SQLite backup object based on the provided destination
+ /// database connection. The source database connection is the one
+ /// associated with this object. The source and destination database
+ /// connections cannot be the same.
+ ///
+ /// The destination database connection.
+ /// The destination database name.
+ /// The source database name.
+ /// The newly created backup object.
+ internal abstract SQLiteBackup InitializeBackup(
+ SQLiteConnection destCnn, string destName,
+ string sourceName);
+
+ ///
+ /// Copies up to N pages from the source database to the destination
+ /// database associated with the specified backup object.
+ ///
+ /// The backup object to use.
+ ///
+ /// The number of pages to copy or negative to copy all remaining pages.
+ ///
+ ///
+ /// Set to true if the operation needs to be retried due to database
+ /// locking issues.
+ ///
+ ///
+ /// True if there are more pages to be copied, false otherwise.
+ ///
+ internal abstract bool StepBackup(SQLiteBackup backup, int nPage, out bool retry);
+
+ ///
+ /// Returns the number of pages remaining to be copied from the source
+ /// database to the destination database associated with the specified
+ /// backup object.
+ ///
+ /// The backup object to check.
+ /// The number of pages remaining to be copied.
+ internal abstract int RemainingBackup(SQLiteBackup backup);
+
+ ///
+ /// Returns the total number of pages in the source database associated
+ /// with the specified backup object.
+ ///
+ /// The backup object to check.
+ /// The total number of pages in the source database.
+ internal abstract int PageCountBackup(SQLiteBackup backup);
+
+ ///
+ /// Destroys the backup object, rolling back any backup that may be in
+ /// progess.
+ ///
+ /// The backup object to destroy.
+ internal abstract void FinishBackup(SQLiteBackup backup);
+
///////////////////////////////////////////////////////////////////////////////////////////////
#region IDisposable Members
public void Dispose()
{
@@ -290,10 +345,20 @@
return UTF8ToString(UnsafeNativeMethods.sqlite3_errmsg_interop(db, out len), len);
#else
return UTF8ToString(UnsafeNativeMethods.sqlite3_errmsg(db), -1);
#endif
}
+
+ internal static void FinishBackup(SQLiteBackupHandle backup)
+ {
+ lock (_lock)
+ {
+ int n = UnsafeNativeMethods.sqlite3_backup_finish(backup);
+ backup.SetHandleAsInvalid();
+ if (n > 0) throw new SQLiteException(n, null);
+ }
+ }
internal static void FinalizeStatement(SQLiteStatementHandle stmt)
{
lock (_lock)
{
@@ -400,14 +465,20 @@
/// Enable logging of all exceptions caught from user-provided
/// managed code called from native code via delegates.
///
LogCallbackException = 0x8,
+ ///
+ /// Enable logging of backup API errors.
+ ///
+ LogBackup = 0x10,
+
///
/// Enable all logging.
///
- LogAll = LogPrepare | LogPreBind | LogBind | LogCallbackException,
+ LogAll = LogPrepare | LogPreBind | LogBind |
+ LogCallbackException | LogBackup,
///
/// The default extra flags for new connections.
///
Default = LogCallbackException
Index: System.Data.SQLite/SQLiteConnection.cs
==================================================================
--- System.Data.SQLite/SQLiteConnection.cs
+++ System.Data.SQLite/SQLiteConnection.cs
@@ -327,10 +327,148 @@
}
}
}
}
}
+
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+
+ #region Backup API Members
+ ///
+ /// Raised between each backup step.
+ ///
+ ///
+ /// The source database connection.
+ ///
+ ///
+ /// The destination database connection.
+ ///
+ ///
+ /// The number of pages remaining to be copied.
+ ///
+ ///
+ /// The total number of pages in the source database.
+ ///
+ ///
+ /// Set to true if the operation needs to be retried due to database
+ /// locking issues; otherwise, set to false.
+ ///
+ ///
+ /// True to continue with the backup process or false to halt the backup
+ /// process, rolling back any changes that have been made so far.
+ ///
+ public delegate bool SQLiteBackupCallback(
+ SQLiteConnection source, SQLiteConnection destination,
+ int remainingPages, int totalPages, bool retry);
+
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+
+ ///
+ /// Backs up the database, using the specified database connection as the
+ /// destination.
+ ///
+ /// The destination database connection.
+ /// The destination database name.
+ /// The source database name.
+ ///
+ /// The number of pages to copy or negative to copy all remaining pages.
+ ///
+ ///
+ /// The method to invoke between each step of the backup process. This
+ /// parameter may be null (i.e. no callbacks will be performed).
+ ///
+ ///
+ /// The number of milliseconds to sleep after encountering a locking error
+ /// during the backup process. A value less than zero means that no sleep
+ /// should be performed.
+ ///
+ public void BackupDatabase(
+ SQLiteConnection destination,
+ string destinationName,
+ string sourceName,
+ int nPage,
+ SQLiteBackupCallback callback,
+ int retryMilliseconds
+ )
+ {
+ CheckDisposed();
+
+ if (_connectionState != ConnectionState.Open)
+ throw new InvalidOperationException(
+ "Source database is not open.");
+
+ if (destination == null)
+ throw new ArgumentNullException("destination");
+
+ if (destination._connectionState != ConnectionState.Open)
+ throw new ArgumentException(
+ "Destination database is not open.", "destination");
+
+ if (destinationName == null)
+ throw new ArgumentNullException("destinationName");
+
+ if (sourceName == null)
+ throw new ArgumentNullException("sourceName");
+
+ SQLiteBase sqliteBase = _sql;
+
+ if (sqliteBase == null)
+ throw new InvalidOperationException(
+ "Connection object has an invalid handle.");
+
+ SQLiteBackup backup = null;
+
+ try
+ {
+ backup = sqliteBase.InitializeBackup(
+ destination, destinationName, sourceName); /* throw */
+
+ bool retry;
+
+ while (sqliteBase.StepBackup(backup, nPage, out retry)) /* throw */
+ {
+ //
+ // NOTE: If a callback was supplied by our caller, call it.
+ // If it returns false, halt the backup process.
+ //
+ if ((callback != null) && !callback(this,
+ destination, sqliteBase.RemainingBackup(backup),
+ sqliteBase.PageCountBackup(backup), retry))
+ {
+ break;
+ }
+
+ //
+ // NOTE: If we need to retry the previous operation, wait for
+ // the number of milliseconds specified by our caller
+ // unless the caller used a negative number, in that case
+ // skip sleeping at all because we do not want to block
+ // this thread forever.
+ //
+ if (retry && (retryMilliseconds >= 0))
+ System.Threading.Thread.Sleep(retryMilliseconds);
+ }
+ }
+ catch (Exception e)
+ {
+#if !PLATFORM_COMPACTFRAMEWORK
+ if ((_flags & SQLiteConnectionFlags.LogBackup) == SQLiteConnectionFlags.LogBackup)
+ {
+ SQLiteLog.LogMessage(0, String.Format(
+ "Caught exception while backing up database: {0}", e));
+ }
+#endif
+
+ throw;
+ }
+ finally
+ {
+ if (backup != null)
+ sqliteBase.FinishBackup(backup); /* throw */
+ }
+ }
+ #endregion
///////////////////////////////////////////////////////////////////////////////////////////////
#region IDisposable "Pattern" Members
private bool disposed;
Index: System.Data.SQLite/System.Data.SQLite.Files.targets
==================================================================
--- System.Data.SQLite/System.Data.SQLite.Files.targets
+++ System.Data.SQLite/System.Data.SQLite.Files.targets
@@ -15,10 +15,11 @@
+
Component
Index: System.Data.SQLite/UnsafeNativeMethods.cs
==================================================================
--- System.Data.SQLite/UnsafeNativeMethods.cs
+++ System.Data.SQLite/UnsafeNativeMethods.cs
@@ -1233,10 +1233,44 @@
#else
[DllImport(SQLITE_DLL)]
#endif
internal static extern int sqlite3_file_control(IntPtr db, byte[] zDbName, int op, IntPtr pArg);
+#if !PLATFORM_COMPACTFRAMEWORK
+ [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)]
+#else
+ [DllImport(SQLITE_DLL)]
+#endif
+ internal static extern IntPtr sqlite3_backup_init(IntPtr destDb, byte[] zDestName, IntPtr sourceDb, byte[] zSourceName);
+
+#if !PLATFORM_COMPACTFRAMEWORK
+ [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)]
+#else
+ [DllImport(SQLITE_DLL)]
+#endif
+ internal static extern int sqlite3_backup_step(IntPtr backup, int nPage);
+
+#if !PLATFORM_COMPACTFRAMEWORK
+ [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)]
+#else
+ [DllImport(SQLITE_DLL)]
+#endif
+ internal static extern int sqlite3_backup_finish(IntPtr backup);
+
+#if !PLATFORM_COMPACTFRAMEWORK
+ [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)]
+#else
+ [DllImport(SQLITE_DLL)]
+#endif
+ internal static extern int sqlite3_backup_remaining(IntPtr backup);
+
+#if !PLATFORM_COMPACTFRAMEWORK
+ [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)]
+#else
+ [DllImport(SQLITE_DLL)]
+#endif
+ internal static extern int sqlite3_backup_pagecount(IntPtr backup);
#endregion
}
#if PLATFORM_COMPACTFRAMEWORK
internal abstract class CriticalHandle : IDisposable
@@ -1459,6 +1493,82 @@
public override bool IsInvalid
{
get { return (handle == IntPtr.Zero); }
}
}
+
+ // Provides finalization support for unmanaged SQLite backup objects.
+ internal class SQLiteBackupHandle : CriticalHandle
+ {
+ public static implicit operator IntPtr(SQLiteBackupHandle backup)
+ {
+ return (backup != null) ? backup.handle : IntPtr.Zero;
+ }
+
+ public static implicit operator SQLiteBackupHandle(IntPtr backup)
+ {
+ return new SQLiteBackupHandle(backup);
+ }
+
+ private SQLiteBackupHandle(IntPtr backup)
+ : this()
+ {
+ SetHandle(backup);
+ }
+
+ internal SQLiteBackupHandle()
+ : base(IntPtr.Zero)
+ {
+ }
+
+ protected override bool ReleaseHandle()
+ {
+ try
+ {
+ SQLiteBase.FinishBackup(this);
+
+#if DEBUG && !NET_COMPACT_20
+ try
+ {
+ Trace.WriteLine(String.Format(
+ "FinishBackup: {0}", handle));
+ }
+ catch
+ {
+ }
+#endif
+
+#if DEBUG
+ return true;
+#endif
+ }
+#if DEBUG && !NET_COMPACT_20
+ catch (SQLiteException e)
+#else
+ catch (SQLiteException)
+#endif
+ {
+#if DEBUG && !NET_COMPACT_20
+ try
+ {
+ Trace.WriteLine(String.Format(
+ "FinishBackup: {0}, exception: {1}",
+ handle, e));
+ }
+ catch
+ {
+ }
+#endif
+ }
+#if DEBUG
+ return false;
+#else
+ return true;
+#endif
+ }
+
+ public override bool IsInvalid
+ {
+ get { return (handle == IntPtr.Zero); }
+ }
+ }
}
ADDED Tests/backup.eagle
Index: Tests/backup.eagle
==================================================================
--- /dev/null
+++ Tests/backup.eagle
@@ -0,0 +1,118 @@
+###############################################################################
+#
+# backup.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
+
+###############################################################################
+
+runTest {test backup-1.1 {BackupDatabase method, memory to disk} -setup {
+ setupDb [set fileName(1) :memory:] "" "" "" "" "" false memDb
+ setupDb [set fileName(2) backup-1.1.db]
+} -body {
+ set id [object invoke Interpreter.GetActive NextId]
+ set dataSource [file join [getDatabaseDirectory] $fileName(2)]
+
+ sql execute $memDb {
+ CREATE TABLE t1(x TEXT);
+ }
+
+ for {set index 0} {$index < 10} {incr index} {
+ sql execute $memDb [subst {
+ INSERT INTO t1 (x) VALUES('[string repeat ! 1048576]');
+ }]
+ }
+
+ set memSource [object invoke -flags +NonPublic -objectflags +NoDispose \
+ Interpreter.GetActive.connections get_Item $memDb]
+
+ unset -nocomplain results errors
+
+ set code [compileCSharpWith [subst {
+ using System.Data.SQLite;
+ using System.Text;
+
+ namespace _Dynamic${id}
+ {
+ public class Test${id}
+ {
+ public static string GetRows(
+ SQLiteConnection source
+ )
+ {
+ using (SQLiteConnection destination = new SQLiteConnection(
+ "Data Source=${dataSource};"))
+ {
+ destination.Open();
+ source.BackupDatabase(destination, "main", "main", -1, null, 0);
+
+ using (SQLiteCommand command = new SQLiteCommand(
+ "SELECT length(x) FROM t1;", destination))
+ {
+ using (SQLiteDataReader dataReader = command.ExecuteReader())
+ {
+ int rowCount = 0;
+ StringBuilder builder = new StringBuilder();
+
+ builder.Append(dataReader.FieldCount);
+ builder.Append(' ');
+
+ while (dataReader.Read())
+ {
+ builder.Append(dataReader.GetInt64(0));
+ builder.Append(' ');
+ rowCount++;
+ }
+
+ builder.Append(rowCount);
+ return builder.ToString();
+ }
+ }
+ }
+ }
+
+ ///////////////////////////////////////////////////////////////////////
+
+ public static void Main()
+ {
+ // do nothing.
+ }
+ }
+ }
+ }] true true true results errors System.Data.SQLite.dll]
+
+ list $code $results \
+ [expr {[info exists errors] ? $errors : ""}] \
+ [expr {$code eq "Ok" ? [catch {
+ object invoke _Dynamic${id}.Test${id} GetRows $memSource
+ } result] : [set result ""]}] $result
+} -cleanup {
+ cleanupDb $fileName(2)
+ cleanupDb $fileName(1) memDb
+
+ unset -nocomplain result results errors code index memSource dataSource id \
+ memDb db fileName
+} -constraints \
+{eagle monoBug28 command.sql compile.DATA SQLite System.Data.SQLite} \
+-match regexp -result {^Ok System#CodeDom#Compiler#CompilerResults#\d+ \{\} 0\
+\{1 1048576 1048576 1048576 1048576 1048576 1048576 1048576 1048576 1048576\
+1048576 10\}$}}
+
+###############################################################################
+
+runSQLiteTestEpilogue
+runTestEpilogue
Index: Tests/basic.eagle
==================================================================
--- Tests/basic.eagle
+++ Tests/basic.eagle
@@ -193,10 +193,11 @@
[expr {$code eq "Ok" ? [catch {
object invoke _Dynamic${id}.Test${id} GetReservedWords
} result] : [set result ""]}] $result
} -cleanup {
cleanupDb $fileName
+
unset -nocomplain result results errors code dataSource id db fileName
} -constraints \
{eagle monoBug28 command.sql compile.DATA SQLite System.Data.SQLite} \
-match regexp -result {^Ok System#CodeDom#Compiler#CompilerResults#\d+ \{\} 0\
System#Data#DataTable#\d+$}}
Index: Tests/common.eagle
==================================================================
--- Tests/common.eagle
+++ Tests/common.eagle
@@ -444,26 +444,40 @@
# NOTE: Evaluate the constructed [compileCSharp] command and return the
# result.
#
eval $command
}
+
+ proc isMemoryDb { fileName } {
+ #
+ # NOTE: Is the specified database file name really an in-memory database?
+ #
+ return [expr {$fileName eq ":memory:"}]
+ }
proc setupDb {
fileName {mode ""} {dateTimeFormat ""} {dateTimeKind ""} {flags ""}
{extra ""} {delete true} {varName db} } {
+ #
+ # NOTE: First, see if the caller has requested an in-memory database.
+ #
+ set isMemory [isMemoryDb $fileName]
+
#
# NOTE: For now, all test databases used by the test suite are placed into
# the temporary directory. Each database used by a test should be
# cleaned up by that test using the "cleanupDb" procedure, below.
#
- set fileName [file join [getDatabaseDirectory] [file tail $fileName]]
+ if {!$isMemory} then {
+ set fileName [file join [getDatabaseDirectory] [file tail $fileName]]
+ }
#
# NOTE: By default, delete any pre-existing database with the same file
# name if it currently exists.
#
- if {$delete && [file exists $fileName]} then {
+ if {!$isMemory && $delete && [file exists $fileName]} then {
#
# NOTE: Attempt to delete any pre-existing database with the same file
# name.
#
if {[catch {file delete $fileName} error]} then {
@@ -576,20 +590,27 @@
tputs $::test_channel [appendArgs \
"==== WARNING: failed to close database \"" $db "\", error: " \
\n\t $error \n]
}
+ #
+ # NOTE: First, see if the caller has requested an in-memory database.
+ #
+ set isMemory [isMemoryDb $fileName]
+
#
# NOTE: Build the full path to the database file name. For now, all test
# database files are stored in the temporary directory.
#
- set fileName [file join [getDatabaseDirectory] [file tail $fileName]]
+ if {!$isMemory} then {
+ set fileName [file join [getDatabaseDirectory] [file tail $fileName]]
+ }
#
# NOTE: Check if the file still exists.
#
- if {[file exists $fileName]} then {
+ if {!$isMemory && [file exists $fileName]} then {
#
# NOTE: Skip deleting database files if somebody sets the global
# variable to prevent it.
#
if {![info exists ::no(cleanupDb)]} then {
Index: Tests/tkt-448d663d11.eagle
==================================================================
--- Tests/tkt-448d663d11.eagle
+++ Tests/tkt-448d663d11.eagle
@@ -23,10 +23,11 @@
runTest {test tkt-448d663d11-1.1 {missing journal mode, new db} -body {
setupDb [set fileName tkt-448d663d11-1.1.db]
sql execute -execute scalar $db "PRAGMA journal_mode;"
} -cleanup {
cleanupDb $fileName
+
unset -nocomplain db fileName
} -constraints \
{eagle monoBug28 command.sql compile.DATA SQLite System.Data.SQLite} -result \
{delete}}
@@ -38,10 +39,11 @@
[file join [getDatabaseDirectory] $fileName]
setupDb $fileName "" "" "" "" "" false
sql execute -execute scalar $db "PRAGMA journal_mode;"
} -cleanup {
cleanupDb $fileName
+
unset -nocomplain db fileName
} -constraints \
{eagle monoBug28 command.sql compile.DATA SQLite System.Data.SQLite} -result \
{wal}}
@@ -53,10 +55,11 @@
[file join [getDatabaseDirectory] $fileName]
setupDb $fileName "" "" "" "" "" false
sql execute -execute scalar $db "PRAGMA journal_mode;"
} -cleanup {
cleanupDb $fileName
+
unset -nocomplain db fileName
} -constraints \
{eagle monoBug28 command.sql compile.DATA SQLite System.Data.SQLite} -result \
{delete}}
@@ -65,10 +68,11 @@
runTest {test tkt-448d663d11-1.4 {'Default' journal mode, new db} -body {
setupDb [set fileName tkt-448d663d11-1.4.db] Default
sql execute -execute scalar $db "PRAGMA journal_mode;"
} -cleanup {
cleanupDb $fileName
+
unset -nocomplain db fileName
} -constraints \
{eagle monoBug28 command.sql compile.DATA SQLite System.Data.SQLite} -result \
{delete}}
@@ -80,10 +84,11 @@
[file join [getDatabaseDirectory] $fileName]
setupDb $fileName Default "" "" "" "" false
sql execute -execute scalar $db "PRAGMA journal_mode;"
} -cleanup {
cleanupDb $fileName
+
unset -nocomplain db fileName
} -constraints \
{eagle monoBug28 command.sql compile.DATA SQLite System.Data.SQLite} -result \
{wal}}
@@ -95,10 +100,11 @@
[file join [getDatabaseDirectory] $fileName]
setupDb $fileName Default "" "" "" "" false
sql execute -execute scalar $db "PRAGMA journal_mode;"
} -cleanup {
cleanupDb $fileName
+
unset -nocomplain db fileName
} -constraints \
{eagle monoBug28 command.sql compile.DATA SQLite System.Data.SQLite} -result \
{delete}}
@@ -107,10 +113,11 @@
runTest {test tkt-448d663d11-1.7 {'Delete' journal mode, new db} -body {
setupDb [set fileName tkt-448d663d11-1.7.db] Delete
sql execute -execute scalar $db "PRAGMA journal_mode;"
} -cleanup {
cleanupDb $fileName
+
unset -nocomplain db fileName
} -constraints \
{eagle monoBug28 command.sql compile.DATA SQLite System.Data.SQLite} -result \
{delete}}
@@ -122,10 +129,11 @@
[file join [getDatabaseDirectory] $fileName]
setupDb $fileName Delete "" "" "" "" false
sql execute -execute scalar $db "PRAGMA journal_mode;"
} -cleanup {
cleanupDb $fileName
+
unset -nocomplain db fileName
} -constraints \
{eagle monoBug28 command.sql compile.DATA SQLite System.Data.SQLite} -result \
{delete}}
@@ -137,10 +145,11 @@
[file join [getDatabaseDirectory] $fileName]
setupDb $fileName Delete "" "" "" "" false
sql execute -execute scalar $db "PRAGMA journal_mode;"
} -cleanup {
cleanupDb $fileName
+
unset -nocomplain db fileName
} -constraints \
{eagle monoBug28 command.sql compile.DATA SQLite System.Data.SQLite} -result \
{delete}}
@@ -149,10 +158,11 @@
runTest {test tkt-448d663d11-1.10 {'Persist' journal mode, new db} -body {
setupDb [set fileName tkt-448d663d11-1.10.db] Persist
sql execute -execute scalar $db "PRAGMA journal_mode;"
} -cleanup {
cleanupDb $fileName
+
unset -nocomplain db fileName
} -constraints \
{eagle monoBug28 command.sql compile.DATA SQLite System.Data.SQLite} -result \
{persist}}
@@ -161,10 +171,11 @@
runTest {test tkt-448d663d11-1.11 {'Off' journal mode, new db} -body {
setupDb [set fileName tkt-448d663d11-1.11.db] Off
sql execute -execute scalar $db "PRAGMA journal_mode;"
} -cleanup {
cleanupDb $fileName
+
unset -nocomplain db fileName
} -constraints \
{eagle monoBug28 command.sql compile.DATA SQLite System.Data.SQLite} -result \
{off}}
@@ -173,10 +184,11 @@
runTest {test tkt-448d663d11-1.12 {'Truncate' journal mode, new db} -body {
setupDb [set fileName tkt-448d663d11-1.12.db] Truncate
sql execute -execute scalar $db "PRAGMA journal_mode;"
} -cleanup {
cleanupDb $fileName
+
unset -nocomplain db fileName
} -constraints \
{eagle monoBug28 command.sql compile.DATA SQLite System.Data.SQLite} -result \
{truncate}}
@@ -185,10 +197,11 @@
runTest {test tkt-448d663d11-1.13 {'Memory' journal mode, new db} -body {
setupDb [set fileName tkt-448d663d11-1.13.db] Memory
sql execute -execute scalar $db "PRAGMA journal_mode;"
} -cleanup {
cleanupDb $fileName
+
unset -nocomplain db fileName
} -constraints \
{eagle monoBug28 command.sql compile.DATA SQLite System.Data.SQLite} -result \
{memory}}
@@ -197,10 +210,11 @@
runTest {test tkt-448d663d11-1.14 {'Wal' journal mode, new db} -body {
setupDb [set fileName tkt-448d663d11-1.14.db] Wal
sql execute -execute scalar $db "PRAGMA journal_mode;"
} -cleanup {
cleanupDb $fileName
+
unset -nocomplain db fileName
} -constraints \
{eagle monoBug28 command.sql compile.DATA SQLite System.Data.SQLite} -result \
{wal}}
@@ -212,10 +226,11 @@
[file join [getDatabaseDirectory] $fileName]
setupDb $fileName Wal "" "" "" "" false
sql execute -execute scalar $db "PRAGMA journal_mode;"
} -cleanup {
cleanupDb $fileName
+
unset -nocomplain db fileName
} -constraints \
{eagle monoBug28 command.sql compile.DATA SQLite System.Data.SQLite} -result \
{wal}}
@@ -227,10 +242,11 @@
[file join [getDatabaseDirectory] $fileName]
setupDb $fileName Wal "" "" "" "" false
sql execute -execute scalar $db "PRAGMA journal_mode;"
} -cleanup {
cleanupDb $fileName
+
unset -nocomplain db fileName
} -constraints \
{eagle monoBug28 command.sql compile.DATA SQLite System.Data.SQLite} -result \
{wal}}
@@ -239,10 +255,11 @@
runTest {test tkt-448d663d11-1.17 {'Bad' journal mode, new db} -body {
setupDb [set fileName tkt-448d663d11-1.17.db] Bad
sql execute -execute scalar $db "PRAGMA journal_mode;"
} -cleanup {
cleanupDb $fileName
+
unset -nocomplain db fileName
} -constraints \
{eagle monoBug28 command.sql compile.DATA SQLite System.Data.SQLite} -result \
{delete}}
@@ -254,10 +271,11 @@
[file join [getDatabaseDirectory] $fileName]
setupDb $fileName Bad "" "" "" "" false
sql execute -execute scalar $db "PRAGMA journal_mode;"
} -cleanup {
cleanupDb $fileName
+
unset -nocomplain db fileName
} -constraints \
{eagle monoBug28 command.sql compile.DATA SQLite System.Data.SQLite} -result \
{delete}}
@@ -269,14 +287,15 @@
[file join [getDatabaseDirectory] $fileName]
setupDb $fileName Bad "" "" "" "" false
sql execute -execute scalar $db "PRAGMA journal_mode;"
} -cleanup {
cleanupDb $fileName
+
unset -nocomplain db fileName
} -constraints \
{eagle monoBug28 command.sql compile.DATA SQLite System.Data.SQLite} -result \
{wal}}
###############################################################################
runSQLiteTestEpilogue
runTestEpilogue
Index: Tests/tkt-b4a7ddc83f.eagle
==================================================================
--- Tests/tkt-b4a7ddc83f.eagle
+++ Tests/tkt-b4a7ddc83f.eagle
@@ -50,10 +50,11 @@
list $appDomainId(1) $appDomainId(2) \
[expr {$appDomainId(1) != $appDomainId(2)}] [setupDb $fileName]
} -cleanup {
cleanupDb $fileName
+
unset -nocomplain appDomainId db fileName
} -constraints {eagle monoBug28 command.sql compile.DATA\
compile.ISOLATED_INTERPRETERS SQLite System.Data.SQLite} -isolationLevel \
AppDomain -match regexp -result {^\d+ \d+ True\
System#Data#SQLite#SQLiteConnection#\d+$}}
Index: readme.htm
==================================================================
--- readme.htm
+++ readme.htm
@@ -192,10 +192,11 @@
- In the SQLiteFunction class, when calling user-provided methods from a delegate called by native code, avoid throwing exceptions, optionally tracing the caught exceptions. Fix for [8a426d12eb].
- Add Visual Studio 2005 support to all the applicable solution/project files, their associated supporting files, and the test suite.
- Add Visual Studio 2005 support to the redesigned designer support installer.
- Add experimental support for "pre-loading" the native SQLite library based on the processor architecture of the current process. This feature is now enabled by default at compile-time.
+ - Add support for the native SQLite Online Backup API. Fix for [c71846ed57].
- Acquire and hold a static data lock while checking if the native SQLite library has been initialized to prevent a subtle race condition that can result in superfluous error messages. Fix for [72905c9a77].
- Support tracing of all parameter binding activity and use the connection flags to control what is traced.
- When converting a DateTime instance of an "Unspecified" kind to a string, use the same kind as the connection, if available.
- Add overload of the SQLiteDataReader.GetValues method that returns a NameValueCollection.
- Add static ToUnixEpoch method to the SQLiteConvert class to convert a DateTime value to the number of whole seconds since the Unix epoch.
Index: www/news.wiki
==================================================================
--- www/news.wiki
+++ www/news.wiki
@@ -8,10 +8,11 @@
- In the SQLiteFunction class, when calling user-provided methods from a delegate called by native code, avoid throwing exceptions, optionally tracing the caught exceptions. Fix for [8a426d12eb].
- Add Visual Studio 2005 support to all the applicable solution/project files, their associated supporting files, and the test suite.
- Add Visual Studio 2005 support to the redesigned designer support installer.
- Add experimental support for "pre-loading" the native SQLite library based on the processor architecture of the current process. This feature is now enabled by default at compile-time.
+ - Add support for the native [http://www.sqlite.org/backup.html|SQLite Online Backup API]. Fix for [c71846ed57].
- Acquire and hold a static data lock while checking if the native SQLite library has been initialized to prevent a subtle race condition that can result in superfluous error messages. Fix for [72905c9a77].
- Support tracing of all parameter binding activity and use the connection flags to control what is traced.
- When converting a DateTime instance of an "Unspecified" kind to a string, use the same kind as the connection, if available.
- Add overload of the SQLiteDataReader.GetValues method that returns a NameValueCollection.
- Add static ToUnixEpoch method to the SQLiteConvert class to convert a DateTime value to the number of whole seconds since the Unix epoch.