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