Index: SQLite.Interop/SQLite.Interop.vcproj
===================================================================
RCS file: /cvsroot/sqlite-dotnet2/SQLite.NET/SQLite.Interop/SQLite.Interop.vcproj,v
retrieving revision 1.31
diff -u -r1.31 SQLite.Interop.vcproj
--- SQLite.Interop/SQLite.Interop.vcproj 3 Sep 2006 18:18:22 -0000 1.31
+++ SQLite.Interop/SQLite.Interop.vcproj 21 Sep 2006 00:50:47 -0000
@@ -55,7 +55,7 @@
AdditionalOptions="/GS-"
Optimization="2"
FavorSizeOrSpeed="1"
- PreprocessorDefinitions="WIN32;NDEBUG;_WINDOWS;_USRDLL;_CRT_SECURE_NO_DEPRECATE;NO_TCL;THREADSAFE;SQLITE_HAS_CODEC;SQLITE_ENABLE_COLUMN_METADATA"
+ PreprocessorDefinitions="WIN32;NDEBUG;_WINDOWS;_USRDLL;_CRT_SECURE_NO_DEPRECATE;NO_TCL;THREADSAFE;SQLITE_HAS_CODEC;SQLITE_ENABLE_COLUMN_METADATA;SQLITE_SERVER"
StringPooling="true"
ExceptionHandling="0"
BufferSecurityCheck="false"
@@ -143,7 +143,7 @@
Name="VCCLCompilerTool"
ExecutionBucket="7"
AdditionalOptions="/GS-"
- PreprocessorDefinitions="_WIN32_WCE=$(CEVER);UNDER_CE=$(CEVER);WINCE;$(PLATFORMDEFINES);NDEBUG;_WINDOWS;_USRDLL;CPPSMART_EXPORTS;$(ARCHFAM);$(_ARCHFAM_);UNICODE;_UNICODE;SQLITE_HAS_CODEC;SQLITE_ENABLE_COLUMN_METADATA"
+ PreprocessorDefinitions="_WIN32_WCE=$(CEVER);UNDER_CE=$(CEVER);WINCE;$(PLATFORMDEFINES);NDEBUG;_WINDOWS;_USRDLL;CPPSMART_EXPORTS;$(ARCHFAM);$(_ARCHFAM_);UNICODE;_UNICODE;SQLITE_HAS_CODEC;SQLITE_ENABLE_COLUMN_METADATA;SQLITE_SERVER"
/>
+
+
Index: SQLite.Interop/src/sqlite3.def
===================================================================
RCS file: /cvsroot/sqlite-dotnet2/SQLite.NET/SQLite.Interop/src/sqlite3.def,v
retrieving revision 1.8
diff -u -r1.8 sqlite3.def
--- SQLite.Interop/src/sqlite3.def 2 Jul 2006 06:48:16 -0000 1.8
+++ SQLite.Interop/src/sqlite3.def 14 Aug 2006 12:43:42 -0000
@@ -102,3 +102,12 @@
sqlite3_key
sqlite3_rekey
+
+sqlite3_server_start
+sqlite3_server_stop
+sqlite3_client_open
+sqlite3_client_prepare
+sqlite3_client_step
+sqlite3_client_reset
+sqlite3_client_finalize
+sqlite3_client_close
Index: System.Data.SQLite/SQLiteConnection.cs
===================================================================
RCS file: /cvsroot/sqlite-dotnet2/SQLite.NET/System.Data.SQLite/SQLiteConnection.cs,v
retrieving revision 1.59
diff -u -r1.59 SQLiteConnection.cs
--- System.Data.SQLite/SQLiteConnection.cs 8 Sep 2006 20:19:23 -0000 1.59
+++ System.Data.SQLite/SQLiteConnection.cs 21 Sep 2006 00:49:25 -0000
@@ -86,6 +86,12 @@
/// N
/// Y
///
+ /// -
+ /// UseSharedServer
+ /// True
False
+ /// N
+ /// False
+ ///
///
///
public sealed class SQLiteConnection : DbConnection, ICloneable
@@ -537,6 +543,12 @@
/// N
/// Y
///
+ /// -
+ /// UseSharedServer
+ /// True
False
+ /// N
+ /// False
+ ///
///
///
#if !PLATFORM_COMPACTFRAMEWORK
@@ -708,10 +720,13 @@
#endif
try
{
+ bool bSharedServer = (Convert.ToBoolean(FindKey(opts, "UseSharedServer", "False"), CultureInfo.InvariantCulture) == true);
bool bUTF16 = (Convert.ToBoolean(FindKey(opts, "UseUTF16Encoding", "False"), CultureInfo.InvariantCulture) == true);
SQLiteDateFormats dateFormat = String.Compare(FindKey(opts, "DateTimeFormat", "ISO8601"), "ticks", true, CultureInfo.InvariantCulture) == 0 ? SQLiteDateFormats.Ticks : SQLiteDateFormats.ISO8601;
- if (bUTF16) // SQLite automatically sets the encoding of the database to UTF16 if called from sqlite3_open16()
+ if (bSharedServer)
+ _sql = new SQLite3_SharedServer(dateFormat);
+ else if (bUTF16) // SQLite automatically sets the encoding of the database to UTF16 if called from sqlite3_open16()
_sql = new SQLite3_UTF16(dateFormat);
else
_sql = new SQLite3(dateFormat);
Index: System.Data.SQLite/SQLiteConvert.cs
===================================================================
RCS file: /cvsroot/sqlite-dotnet2/SQLite.NET/System.Data.SQLite/SQLiteConvert.cs,v
retrieving revision 1.22
diff -u -r1.22 SQLiteConvert.cs
--- System.Data.SQLite/SQLiteConvert.cs 3 Sep 2006 18:19:02 -0000 1.22
+++ System.Data.SQLite/SQLiteConvert.cs 21 Sep 2006 00:49:25 -0000
@@ -183,6 +183,24 @@
}
///
+ /// Converts a UTF-8 encoded IntPtr into a .NET string
+ ///
+ /// The pointer to the memory where the UTF-8 string is encoded
+ /// A string containing the translated character(s)
+ public virtual unsafe string ToString(IntPtr nativestring)
+ {
+ int len = 0;
+
+ if (nativestring == IntPtr.Zero)
+ return "";
+
+ while (Marshal.ReadByte(nativestring, len) != 0)
+ checked { ++len; }
+
+ return new string((sbyte*)nativestring, 0, len, _utf8);
+ }
+
+ ///
/// Converts a UTF-8 encoded IntPtr of the specified length into a .NET string
///
/// The pointer to the memory where the UTF-8 string is encoded
Index: System.Data.SQLite/System.Data.SQLite - Full.csproj
===================================================================
RCS file: /cvsroot/sqlite-dotnet2/SQLite.NET/System.Data.SQLite/System.Data.SQLite - Full.csproj,v
retrieving revision 1.7
diff -u -r1.7 System.Data.SQLite - Full.csproj
--- System.Data.SQLite/System.Data.SQLite - Full.csproj 2 Jul 2006 06:49:09 -0000 1.7
+++ System.Data.SQLite/System.Data.SQLite - Full.csproj 14 Aug 2006 13:14:39 -0000
@@ -54,6 +54,7 @@
+
Index: System.Data.SQLite/UnsafeNativeMethods.cs
===================================================================
RCS file: /cvsroot/sqlite-dotnet2/SQLite.NET/System.Data.SQLite/UnsafeNativeMethods.cs,v
retrieving revision 1.15
diff -u -r1.15 UnsafeNativeMethods.cs
--- System.Data.SQLite/UnsafeNativeMethods.cs 2 Jul 2006 06:48:46 -0000 1.15
+++ System.Data.SQLite/UnsafeNativeMethods.cs 14 Aug 2006 12:54:23 -0000
@@ -268,5 +268,32 @@
[DllImport(SQLITE_DLL)]
internal static extern IntPtr sqlite3_rollback_hook_interop(IntPtr db, SQLiteRollbackCallback func);
+
+ [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)]
+ internal static extern int sqlite3_server_start();
+
+ [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)]
+ internal static extern int sqlite3_server_stop();
+
+ [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)]
+ internal static extern int sqlite3_client_open(byte[] utf8Filename, out IntPtr db);
+
+ [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)]
+ internal static extern int sqlite3_client_close(IntPtr db);
+
+ [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)]
+ internal static extern int sqlite3_client_exec(IntPtr db, byte[] strSql, IntPtr pvCallback, IntPtr pvParam, out IntPtr errMsg);
+
+ [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)]
+ internal static extern int sqlite3_client_finalize(IntPtr stmt);
+
+ [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)]
+ internal static extern int sqlite3_client_reset(IntPtr stmt);
+
+ [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)]
+ internal static extern int sqlite3_client_step(IntPtr stmt);
+
+ [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)]
+ internal static extern int sqlite3_client_prepare(IntPtr db, IntPtr strSql, int nBytes, out IntPtr stmt, out IntPtr ptrRemain);
}
}
--- /dev/null 1970-01-01 01:00:00.000000000 +0100
+++ System.Data.SQLite/SQLite3_SharedServer.cs 2006-08-14 14:56:21.046875000 +0200
@@ -0,0 +1,184 @@
+/********************************************************
+ * 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;
+ using System.Runtime.InteropServices;
+ using System.Collections.Generic;
+ using System.Globalization;
+
+ ///
+ /// Alternate SQLite3 object, overriding many behaviors to support shared server
+ ///
+ internal class SQLite3_SharedServer : SQLite3
+ {
+ private static object serverLock = new object();
+ private static int serverRefCount = 0;
+
+ internal SQLite3_SharedServer(SQLiteDateFormats fmt)
+ : base(fmt)
+ {
+ }
+
+ internal override void Close()
+ {
+ if (_sql != IntPtr.Zero)
+ {
+ //int n = UnsafeNativeMethods.sqlite3_close_interop(_sql);
+ int n = UnsafeNativeMethods.sqlite3_client_close(_sql);
+ if (n > 0) throw new SQLiteException(n, SQLiteLastError());
+ SQLiteFunction.UnbindFunctions(this, _functionsArray);
+ lock (serverLock)
+ {
+ if (--serverRefCount == 0)
+ UnsafeNativeMethods.sqlite3_server_stop();
+ }
+ }
+ _sql = IntPtr.Zero;
+ }
+
+ internal override void Open(string strFilename)
+ {
+ if (_sql != IntPtr.Zero) return;
+ lock (serverLock)
+ {
+ if (serverRefCount++ == 0)
+ UnsafeNativeMethods.sqlite3_server_start();
+ int n = UnsafeNativeMethods.sqlite3_client_open(ToUTF8(strFilename), out _sql);
+ if (n > 0) throw new SQLiteException(n, SQLiteLastError());
+
+ _functionsArray = SQLiteFunction.BindFunctions(this);
+ }
+ }
+
+ internal override bool Step(SQLiteStatement stmt)
+ {
+ int n;
+ long dwtick = 0;
+ Random rnd = null;
+
+ while (true)
+ {
+ n = UnsafeNativeMethods.sqlite3_client_step(stmt._sqlite_stmt);
+
+ if (n == 100) return true;
+ if (n == 101) return false;
+
+ if (n > 0)
+ {
+ int r;
+
+ // An error occurred, attempt to reset the statement. If the reset worked because the
+ // schema has changed, re-try the step again. If it errored our because the database
+ // is locked, then keep retrying until the command timeout occurs.
+ r = Reset(stmt);
+
+ if (r == 0)
+ throw new SQLiteException(n, SQLiteLastError());
+
+ else if (r == 6 && stmt._command != null) // SQLITE_LOCKED
+ {
+ // Keep trying
+ if (dwtick == 0) // First time we've encountered the lock
+ {
+ dwtick = DateTime.Now.Ticks + (stmt._command._commandTimeout * 10000000);
+ rnd = new Random();
+ }
+ // If we've exceeded the command's timeout, give up and throw an error
+ if (DateTime.Now.Ticks - dwtick > 0)
+ {
+ throw new SQLiteException(r, SQLiteLastError());
+ }
+ else
+ {
+ // Otherwise sleep for a random amount of time up to 250ms
+ UnsafeNativeMethods.sqlite3_sleep_interop((uint)rnd.Next(1, 250));
+ }
+ }
+ }
+ }
+ }
+
+ internal override void FinalizeStatement(SQLiteStatement stmt)
+ {
+ if (stmt._sqlite_stmt != IntPtr.Zero)
+ {
+ int n = UnsafeNativeMethods.sqlite3_client_finalize(stmt._sqlite_stmt);
+ if (n > 0) throw new SQLiteException(n, SQLiteLastError());
+ }
+ stmt._sqlite_stmt = IntPtr.Zero;
+ }
+
+ internal override int Reset(SQLiteStatement stmt)
+ {
+ int n;
+
+ n = UnsafeNativeMethods.sqlite3_client_reset(stmt._sqlite_stmt);
+
+ // If the schema changed, try and re-prepare it
+ if (n == 17) // SQLITE_SCHEMA
+ {
+ // Recreate a dummy statement
+ string str;
+ using (SQLiteStatement tmp = Prepare(stmt._sqlStatement, null, out str))
+ {
+ // Finalize the existing statement
+ FinalizeStatement(stmt);
+
+ // Reassign a new statement pointer to the old statement and clear the temporary one
+ stmt._sqlite_stmt = tmp._sqlite_stmt;
+ tmp._sqlite_stmt = IntPtr.Zero;
+
+ // Reapply parameters
+ stmt.BindParameters();
+ }
+ return -1; // Reset was OK, with schema change
+ }
+ else if (n == 6) // SQLITE_LOCKED
+ return n;
+
+ if (n > 0)
+ throw new SQLiteException(n, SQLiteLastError());
+
+ return 0; // We reset OK, no schema changes
+ }
+
+ internal override SQLiteStatement Prepare(string strSql, SQLiteStatement previous, out string strRemain)
+ {
+ IntPtr stmt = IntPtr.Zero;
+ IntPtr ptr = IntPtr.Zero;
+ byte[] b = ToUTF8(strSql);
+ int n = 17;
+ int retries = 0;
+
+ GCHandle handle = GCHandle.Alloc(b, GCHandleType.Pinned);
+
+ try
+ {
+ while (n == 17 && retries < 3)
+ {
+ n = UnsafeNativeMethods.sqlite3_client_prepare(_sql, handle.AddrOfPinnedObject(), b.Length - 1, out stmt, out ptr);
+ retries++;
+ }
+
+ if (n > 0) throw new SQLiteException(n, SQLiteLastError());
+
+ strRemain = ToString(ptr);
+ }
+ finally
+ {
+ handle.Free();
+ }
+
+ SQLiteStatement cmd = null;
+ if (stmt != IntPtr.Zero) cmd = new SQLiteStatement(this, stmt, strSql.Substring(0, strSql.Length - strRemain.Length), previous);
+
+ return cmd;
+ }
+ }
+}
--- /dev/null 1970-01-01 01:00:00.000000000 +0100
+++ SQLite.Interop/src/test_server.c 2006-07-16 20:53:38.742670400 +0200
@@ -0,0 +1,496 @@
+/*
+** 2006 January 07
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+******************************************************************************
+**
+** This file contains demonstration code. Nothing in this file gets compiled
+** or linked into the SQLite library unless you use a non-standard option:
+**
+** -DSQLITE_SERVER=1
+**
+** The configure script will never generate a Makefile with the option
+** above. You will need to manually modify the Makefile if you want to
+** include any of the code from this file in your project. Or, at your
+** option, you may copy and paste the code from this file and
+** thereby avoiding a recompile of SQLite.
+**
+**
+** This source file demonstrates how to use SQLite to create an SQL database
+** server thread in a multiple-threaded program. One or more client threads
+** send messages to the server thread and the server thread processes those
+** messages in the order received and returns the results to the client.
+**
+** One might ask: "Why bother? Why not just let each thread connect
+** to the database directly?" There are a several of reasons to
+** prefer the client/server approach.
+**
+** (1) Some systems (ex: Redhat9) have broken threading implementations
+** that prevent SQLite database connections from being used in
+** a thread different from the one where they were created. With
+** the client/server approach, all database connections are created
+** and used within the server thread. Client calls to the database
+** can be made from multiple threads (though not at the same time!)
+**
+** (2) Beginning with SQLite version 3.3.0, when two or more
+** connections to the same database occur within the same thread,
+** they can optionally share their database cache. This reduces
+** I/O and memory requirements. Cache shared is controlled using
+** the sqlite3_enable_shared_cache() API.
+**
+** (3) Database connections on a shared cache use table-level locking
+** instead of file-level locking for improved concurrency.
+**
+** (4) Database connections on a shared cache can by optionally
+** set to READ UNCOMMITTED isolation. (The default isolation for
+** SQLite is SERIALIZABLE.) When this occurs, readers will
+** never be blocked by a writer and writers will not be
+** blocked by readers. There can still only be a single writer
+** at a time, but multiple readers can simultaneously exist with
+** that writer. This is a huge increase in concurrency.
+**
+** To summarize the rational for using a client/server approach: prior
+** to SQLite version 3.3.0 it probably was not worth the trouble. But
+** with SQLite version 3.3.0 and beyond you can get significant performance
+** and concurrency improvements and memory usage reductions by going
+** client/server.
+**
+** Note: The extra features of version 3.3.0 described by points (2)
+** through (4) above are only available if you compile without the
+** option -DSQLITE_OMIT_SHARED_CACHE.
+**
+** Here is how the client/server approach works: The database server
+** thread is started on this procedure:
+**
+** void *sqlite3_server(void *NotUsed);
+**
+** The sqlite_server procedure runs as long as the g.serverHalt variable
+** is false. A mutex is used to make sure no more than one server runs
+** at a time. The server waits for messages to arrive on a message
+** queue and processes the messages in order.
+**
+** Two convenience routines are provided for starting and stopping the
+** server thread:
+**
+** void sqlite3_server_start(void);
+** void sqlite3_server_stop(void);
+**
+** Both of the convenience routines return immediately. Neither will
+** ever give an error. If a server is already started or already halted,
+** then the routines are effectively no-ops.
+**
+** Clients use the following interfaces:
+**
+** sqlite3_client_open
+** sqlite3_client_prepare
+** sqlite3_client_step
+** sqlite3_client_reset
+** sqlite3_client_finalize
+** sqlite3_client_close
+**
+** These interfaces work exactly like the standard core SQLite interfaces
+** having the same names without the "_client_" infix. Many other SQLite
+** interfaces can be used directly without having to send messages to the
+** server as long as SQLITE_ENABLE_MEMORY_MANAGEMENT is not defined.
+** The following interfaces fall into this second category:
+**
+** sqlite3_bind_*
+** sqlite3_changes
+** sqlite3_clear_bindings
+** sqlite3_column_*
+** sqlite3_complete
+** sqlite3_create_collation
+** sqlite3_create_function
+** sqlite3_data_count
+** sqlite3_db_handle
+** sqlite3_errcode
+** sqlite3_errmsg
+** sqlite3_last_insert_rowid
+** sqlite3_total_changes
+** sqlite3_transfer_bindings
+**
+** A single SQLite connection (an sqlite3* object) or an SQLite statement
+** (an sqlite3_stmt* object) should only be passed to a single interface
+** function at a time. The connections and statements can be passed from
+** any thread to any of the functions listed in the second group above as
+** long as the same connection is not in use by two threads at once and
+** as long as SQLITE_ENABLE_MEMORY_MANAGEMENT is not defined. Additional
+** information about the SQLITE_ENABLE_MEMORY_MANAGEMENT constraint is
+** below.
+**
+** The busy handler for all database connections should remain turned
+** off. That means that any lock contention will cause the associated
+** sqlite3_client_step() call to return immediately with an SQLITE_BUSY
+** error code. If a busy handler is enabled and lock contention occurs,
+** then the entire server thread will block. This will cause not only
+** the requesting client to block but every other database client as
+** well. It is possible to enhance the code below so that lock
+** contention will cause the message to be placed back on the top of
+** the queue to be tried again later. But such enhanced processing is
+** not included here, in order to keep the example simple.
+**
+** This example code assumes the use of pthreads. Pthreads
+** implementations are available for windows. (See, for example
+** http://sourceware.org/pthreads-win32/announcement.html.) Or, you
+** can translate the locking and thread synchronization code to use
+** windows primitives easily enough. The details are left as an
+** exercise to the reader.
+**
+**** Restrictions Associated With SQLITE_ENABLE_MEMORY_MANAGEMENT ****
+**
+** If you compile with SQLITE_ENABLE_MEMORY_MANAGEMENT defined, then
+** SQLite includes code that tracks how much memory is being used by
+** each thread. These memory counts can become confused if memory
+** is allocated by one thread and then freed by another. For that
+** reason, when SQLITE_ENABLE_MEMORY_MANAGEMENT is used, all operations
+** that might allocate or free memory should be performanced in the same
+** thread that originally created the database connection. In that case,
+** many of the operations that are listed above as safe to be performed
+** in separate threads would need to be sent over to the server to be
+** done there. If SQLITE_ENABLE_MEMORY_MANAGEMENT is defined, then
+** the following functions can be used safely from different threads
+** without messing up the allocation counts:
+**
+** sqlite3_bind_parameter_name
+** sqlite3_bind_parameter_index
+** sqlite3_changes
+** sqlite3_column_blob
+** sqlite3_column_count
+** sqlite3_complete
+** sqlite3_data_count
+** sqlite3_db_handle
+** sqlite3_errcode
+** sqlite3_errmsg
+** sqlite3_last_insert_rowid
+** sqlite3_total_changes
+**
+** The remaining functions are not thread-safe when memory management
+** is enabled. So one would have to define some new interface routines
+** along the following lines:
+**
+** sqlite3_client_bind_*
+** sqlite3_client_clear_bindings
+** sqlite3_client_column_*
+** sqlite3_client_create_collation
+** sqlite3_client_create_function
+** sqlite3_client_transfer_bindings
+**
+** The example code in this file is intended for use with memory
+** management turned off. So the implementation of these additional
+** client interfaces is left as an exercise to the reader.
+**
+** It may seem surprising to the reader that the list of safe functions
+** above does not include things like sqlite3_bind_int() or
+** sqlite3_column_int(). But those routines might, in fact, allocate
+** or deallocate memory. In the case of sqlite3_bind_int(), if the
+** parameter was previously bound to a string that string might need
+** to be deallocated before the new integer value is inserted. In
+** the case of sqlite3_column_int(), the value of the column might be
+** a UTF-16 string which will need to be converted to UTF-8 then into
+** an integer.
+*/
+
+/*
+** Only compile the code in this file on UNIX with a THREADSAFE build
+** and only if the SQLITE_SERVER macro is defined.
+*/
+#if defined(SQLITE_SERVER) && !defined(SQLITE_OMIT_SHARED_CACHE)
+#if 1 //defined(OS_WIN) && OS_WIN && defined(THREADSAFE) && THREADSAFE
+
+/*
+** We require only pthreads and the public interface of SQLite.
+*/
+#include
+#include
+#include "sqlite3.h"
+
+/*
+** Messages are passed from client to server and back again as
+** instances of the following structure.
+*/
+typedef struct SqlMessage SqlMessage;
+struct SqlMessage {
+ int op; /* Opcode for the message */
+ sqlite3 *pDb; /* The SQLite connection */
+ sqlite3_stmt *pStmt; /* A specific statement */
+ int errCode; /* Error code returned */
+ const char *zIn; /* Input filename or SQL statement */
+ int nByte; /* Size of the zIn parameter for prepare() */
+ const char *zOut; /* Tail of the SQL statement */
+ SqlMessage *pNext; /* Next message in the queue */
+ SqlMessage *pPrev; /* Previous message in the queue */
+ HANDLE clientWakeup; /* Signal to wake up the client */
+};
+
+/*
+** Legal values for SqlMessage.op
+*/
+#define MSG_Open 1 /* sqlite3_open(zIn, &pDb) */
+#define MSG_Prepare 2 /* sqlite3_prepare(pDb, zIn, nByte, &pStmt, &zOut) */
+#define MSG_Step 3 /* sqlite3_step(pStmt) */
+#define MSG_Reset 4 /* sqlite3_reset(pStmt) */
+#define MSG_Finalize 5 /* sqlite3_finalize(pStmt) */
+#define MSG_Close 6 /* sqlite3_close(pDb) */
+#define MSG_Shutdown 7 /* Shutdown the server thread */
+
+
+/*
+** State information about the server is stored in a static variable
+** named "g" as follows:
+*/
+static struct ServerState {
+ volatile int serverLock; /* Set to 1 when the server is running */
+ CRITICAL_SECTION queueMutex; /* Hold this mutex to access the msg queue */
+ HANDLE serverWakeup; /* Signal this condvar to wake up the server */
+ HANDLE serverThread;
+ SqlMessage *pQueueHead; /* Head of the message queue */
+ SqlMessage *pQueueTail; /* Tail of the message queue */
+} g = {
+ 0
+};
+
+/*
+** Send a message to the server. Block until we get a reply.
+**
+** The mutex and condition variable in the message are uninitialized
+** when this routine is called. This routine takes care of
+** initializing them and destroying them when it has finished.
+*/
+static void sendToServer(SqlMessage *pMsg){
+ /* Initialize the mutex and condition variable on the message
+ */
+ pMsg->clientWakeup = CreateEvent(NULL, FALSE, FALSE, NULL);
+
+ /* Add the message to the head of the server's message queue.
+ */
+ EnterCriticalSection(&g.queueMutex);
+ pMsg->pNext = g.pQueueHead;
+ if( g.pQueueHead==0 ){
+ g.pQueueTail = pMsg;
+ }else{
+ g.pQueueHead->pPrev = pMsg;
+ }
+ pMsg->pPrev = 0;
+ g.pQueueHead = pMsg;
+ LeaveCriticalSection(&g.queueMutex);
+
+ /* Signal the server that the new message has be queued, then
+ ** block waiting for the server to process the message.
+ */
+ SetEvent(g.serverWakeup);
+ WaitForSingleObject(pMsg->clientWakeup, INFINITE);
+
+ /* Destroy the condition variable of the message.
+ */
+ CloseHandle(pMsg->clientWakeup);
+}
+
+/*
+** The following 6 routines are client-side implementations of the
+** core SQLite interfaces:
+**
+** sqlite3_open
+** sqlite3_prepare
+** sqlite3_step
+** sqlite3_reset
+** sqlite3_finalize
+** sqlite3_close
+**
+** Clients should use the following client-side routines instead of
+** the core routines above.
+**
+** sqlite3_client_open
+** sqlite3_client_prepare
+** sqlite3_client_step
+** sqlite3_client_reset
+** sqlite3_client_finalize
+** sqlite3_client_close
+**
+** Each of these routines creates a message for the desired operation,
+** sends that message to the server, waits for the server to process
+** then message and return a response.
+*/
+int sqlite3_client_open(const char *zDatabaseName, sqlite3 **ppDb){
+ SqlMessage msg;
+ msg.op = MSG_Open;
+ msg.zIn = zDatabaseName;
+ sendToServer(&msg);
+ *ppDb = msg.pDb;
+ return msg.errCode;
+}
+int sqlite3_client_prepare(
+ sqlite3 *pDb,
+ const char *zSql,
+ int nByte,
+ sqlite3_stmt **ppStmt,
+ const char **pzTail
+){
+ SqlMessage msg;
+ msg.op = MSG_Prepare;
+ msg.pDb = pDb;
+ msg.zIn = zSql;
+ msg.nByte = nByte;
+ sendToServer(&msg);
+ *ppStmt = msg.pStmt;
+ if( pzTail ) *pzTail = msg.zOut;
+ return msg.errCode;
+}
+int sqlite3_client_step(sqlite3_stmt *pStmt){
+ SqlMessage msg;
+ msg.op = MSG_Step;
+ msg.pStmt = pStmt;
+ sendToServer(&msg);
+ return msg.errCode;
+}
+int sqlite3_client_reset(sqlite3_stmt *pStmt){
+ SqlMessage msg;
+ msg.op = MSG_Reset;
+ msg.pStmt = pStmt;
+ sendToServer(&msg);
+ return msg.errCode;
+}
+int sqlite3_client_finalize(sqlite3_stmt *pStmt){
+ SqlMessage msg;
+ msg.op = MSG_Finalize;
+ msg.pStmt = pStmt;
+ sendToServer(&msg);
+ return msg.errCode;
+}
+int sqlite3_client_close(sqlite3 *pDb){
+ SqlMessage msg;
+ msg.op = MSG_Close;
+ msg.pDb = pDb;
+ sendToServer(&msg);
+ return msg.errCode;
+}
+
+/*
+** This routine implements the server. To start the server, first
+** make sure g.serverHalt is false, then create a new detached thread
+** on this procedure. See the sqlite3_server_start() routine below
+** for an example. This procedure loops until g.serverHalt becomes
+** true.
+*/
+DWORD WINAPI sqlite3_server(LPVOID InitializedEvent){
+ int bShutdown = 0;
+
+ if( InterlockedCompareExchange(&g.serverLock, 1, 0) != 0 ){
+ SetEvent((HANDLE)InitializedEvent);
+ return 0; /* Another server is already running */
+ }
+ InitializeCriticalSection(&g.queueMutex);
+ g.serverWakeup = CreateEvent(NULL, FALSE, FALSE, NULL);
+ SetEvent((HANDLE)InitializedEvent);
+ sqlite3_enable_shared_cache(1);
+
+ do{
+ SqlMessage *pMsg;
+
+ /* Remove the last message from the message queue.
+ */
+ ResetEvent(g.serverWakeup);
+ EnterCriticalSection(&g.queueMutex);
+ pMsg = g.pQueueTail;
+ if( pMsg ){
+ if( pMsg->pPrev ){
+ pMsg->pPrev->pNext = 0;
+ }else{
+ g.pQueueHead = 0;
+ }
+ g.pQueueTail = pMsg->pPrev;
+ }
+ LeaveCriticalSection(&g.queueMutex);
+ if (pMsg == 0){
+ WaitForSingleObject(g.serverWakeup, INFINITE);
+ continue;
+ }
+
+ /* Process the message just removed
+ */
+ switch( pMsg->op ){
+ case MSG_Open: {
+ pMsg->errCode = sqlite3_open(pMsg->zIn, &pMsg->pDb);
+ break;
+ }
+ case MSG_Prepare: {
+ pMsg->errCode = sqlite3_prepare(pMsg->pDb, pMsg->zIn, pMsg->nByte,
+ &pMsg->pStmt, &pMsg->zOut);
+ break;
+ }
+ case MSG_Step: {
+ pMsg->errCode = sqlite3_step(pMsg->pStmt);
+ break;
+ }
+ case MSG_Reset: {
+ pMsg->errCode = sqlite3_reset(pMsg->pStmt);
+ break;
+ }
+ case MSG_Finalize: {
+ pMsg->errCode = sqlite3_finalize(pMsg->pStmt);
+ break;
+ }
+ case MSG_Close: {
+ pMsg->errCode = sqlite3_close(pMsg->pDb);
+ break;
+ }
+ case MSG_Shutdown: {
+ bShutdown = 1;
+ break;
+ }
+ }
+
+ /* Signal the client that the message has been processed.
+ */
+ SetEvent(pMsg->clientWakeup);
+ } while (!bShutdown);
+ CloseHandle(g.serverWakeup);
+ DeleteCriticalSection(&g.queueMutex);
+ g.serverLock = 0;
+ sqlite3_thread_cleanup();
+ return 0;
+}
+
+/*
+** Start a server thread if one is not already running. If there
+** is aleady a server thread running, the new thread will quickly
+** die and this routine is effectively a no-op.
+*/
+void sqlite3_server_start(void){
+ DWORD ThreadId;
+ HANDLE InitializedEvent;
+
+ InitializedEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
+ g.serverThread = CreateThread(NULL, 0, sqlite3_server, (LPVOID)InitializedEvent, 0, &ThreadId);
+ if (g.serverThread != NULL)
+ {
+ WaitForSingleObject(InitializedEvent, INFINITE);
+ CloseHandle(InitializedEvent);
+ }
+}
+
+/*
+** If a server thread is running, then stop it. If no server is
+** running, this routine is effectively a no-op.
+**
+** This routine returns immediately without waiting for the server
+** thread to stop. But be assured that the server will eventually stop.
+*/
+void sqlite3_server_stop(void){
+ if (g.serverLock)
+ {
+ SqlMessage msg;
+ msg.op = MSG_Shutdown;
+ sendToServer(&msg);
+ WaitForSingleObject(g.serverThread, INFINITE);
+ CloseHandle(g.serverThread);
+ }
+}
+
+#endif /* defined(OS_UNIX) && OS_UNIX && defined(THREADSAFE) && THREADSAFE */
+#endif /* defined(SQLITE_SERVER) */