]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
added the sqlite_busy_handler() interface (CVS 109)
authordrh <drh@noemail.net>
Fri, 28 Jul 2000 14:32:48 +0000 (14:32 +0000)
committerdrh <drh@noemail.net>
Fri, 28 Jul 2000 14:32:48 +0000 (14:32 +0000)
FossilOrigin-Name: 4fe8e51c248369572637a5351bd193f07e059fa2

19 files changed:
Makefile.in
configure
configure.in
manifest
manifest.uuid
src/build.c
src/dbbe.c
src/main.c
src/shell.c
src/sqlite.h
src/sqliteInt.h
src/vdbe.c
src/vdbe.h
test/lock.test [new file with mode: 0644]
www/c_interface.tcl
www/changes.tcl
www/index.tcl
www/sqlite.tcl
www/vdbe.tcl

index 75efe82eafc4c562aa10b0f53cb14a23e4b2c8f3..883f50cf431560bf37db2882350fd379798d1142 100644 (file)
@@ -164,7 +164,7 @@ tclsqlite:  $(TOP)/src/tclsqlite.c libsqlite.a
        $(TCC) $(TCL_FLAGS) -DTCLSH=1 -o tclsqlite \
                $(TOP)/src/tclsqlite.c libsqlite.a $(LIBGDBM) $(LIBTCL)
 
-test:  tclsqlite
+test:  tclsqlite sqlite
        ./tclsqlite $(TOP)/test/all.test
 
 sqlite.tar.gz: 
index 2abd69b78504828828c852b1aebda73ed4442c31..42a2bc152d350201f82f3a5424e2083a6d5baffa 100755 (executable)
--- a/configure
+++ b/configure
@@ -525,7 +525,7 @@ fi
 
 
 # The following RCS revision string applies to configure.in
-# $Revision: 1.3 $
+# $Revision: 1.4 $
 
 #########
 # Make sure we are not building in a subdirectory of the source tree.
@@ -1727,6 +1727,58 @@ fi
 
 
 
+#########
+# Figure out whether or not we have a "usleep()" function.
+#
+echo $ac_n "checking for usleep""... $ac_c" 1>&6
+echo "configure:1735: checking for usleep" >&5
+if eval "test \"`echo '$''{'ac_cv_func_usleep'+set}'`\" = set"; then
+  echo $ac_n "(cached) $ac_c" 1>&6
+else
+  cat > conftest.$ac_ext <<EOF
+#line 1740 "configure"
+#include "confdefs.h"
+/* System header to define __stub macros and hopefully few prototypes,
+    which can conflict with char usleep(); below.  */
+#include <assert.h>
+/* Override any gcc2 internal prototype to avoid an error.  */
+/* We use char because int might match the return type of a gcc2
+    builtin and then its argument prototype would still apply.  */
+char usleep();
+
+int main() {
+
+/* The GNU C library defines this for functions which it implements
+    to always fail with ENOSYS.  Some functions are actually named
+    something starting with __ and the normal name is an alias.  */
+#if defined (__stub_usleep) || defined (__stub___usleep)
+choke me
+#else
+usleep();
+#endif
+
+; return 0; }
+EOF
+if { (eval echo configure:1763: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext}; then
+  rm -rf conftest*
+  eval "ac_cv_func_usleep=yes"
+else
+  echo "configure: failed program was:" >&5
+  cat conftest.$ac_ext >&5
+  rm -rf conftest*
+  eval "ac_cv_func_usleep=no"
+fi
+rm -f conftest*
+fi
+
+if eval "test \"`echo '$ac_cv_func_'usleep`\" = yes"; then
+  echo "$ac_t""yes" 1>&6
+  TARGET_CFLAGS="$TARGET_CFLAGS -DHAVE_USLEEP=1"
+else
+  echo "$ac_t""no" 1>&6
+fi
+
+
 #########
 # Generate the output files.
 #
index 7a4af2ab725ddfad61d06b0ebdaf49bf33242325..7b93db503519a044de608d6075ab9b596a784a20 100644 (file)
@@ -151,7 +151,7 @@ AC_INIT(src/sqlite.h)
 dnl Put the RCS revision string after AC_INIT so that it will also
 dnl show in in configure.
 # The following RCS revision string applies to configure.in
-# $Revision: 1.3 $
+# $Revision: 1.4 $
 
 #########
 # Make sure we are not building in a subdirectory of the source tree.
@@ -509,6 +509,11 @@ fi
 AC_SUBST(TARGET_READLINE_INC)
 AC_SUBST(TARGET_HAVE_READLINE)
 
+#########
+# Figure out whether or not we have a "usleep()" function.
+#
+AC_CHECK_FUNC(usleep, [TARGET_CFLAGS="$TARGET_CFLAGS -DHAVE_USLEEP=1"])
+
 #########
 # Generate the output files.
 #
index 5fef54b1d7a72d18ab15eefd2530ff6f6b48d419..1ec8235794ad74caae739224adfc91ca16d49bf3 100644 (file)
--- a/manifest
+++ b/manifest
@@ -1,29 +1,29 @@
-C :-)\s(CVS\s108)
-D 2000-06-26T12:02:51
+C added\sthe\ssqlite_busy_handler()\sinterface\s(CVS\s109)
+D 2000-07-28T14:32:48
 F COPYRIGHT 74a8a6531a42e124df07ab5599aad63870fa0bd4
-F Makefile.in 02ecb0cd0de7ddf7b4623d480061870798787556
+F Makefile.in 9e6dcd232e594fb599a5e9ba8bcf45e6c6e2fe72
 F README 51f6a4e7408b34afa5bc1c0485f61b6a4efb6958
-F configure c366a0402bce79ef11fe1bf703ad6ce4ff6afbb0 x
-F configure.in 1085ff994a334b131325de906ed318e926673588
+F configure 1354f60305c781609c94cd4b677ab4ff4d830b85 x
+F configure.in 77732d0a7d3ec66b7fc2303ae823fa5419ca7e6c
 F doc/lemon.html e233a3e97a779c7a87e1bc4528c664a58e49dd47
-F src/build.c 55edb404bbf4476c73c81604ddb9738281a689a4
-F src/dbbe.c 99aa6daca9a039eebb284dd459bef712ea3843f9
+F src/build.c ac2e238356008411c3aa09c96823529d4103afcc
+F src/dbbe.c 3604cf7dec6856a4963ab8f2220449f3d02e759a
 F src/dbbe.h 8718b718b36d37584e9bbdfccec10588fa91271f
 F src/delete.c 4d491eaf61b515516749c7ed68fa3b2ee8a09065
 F src/expr.c 2fa63f086707176d09092e71832f9bbdc6a8ac85
 F src/insert.c f146f149ad2422a1dc3bfa7a1651a25940f98958
-F src/main.c 30b33b6e0cdd5ae1c0af9f626e78a1dc7b835e26
+F src/main.c 82dba47063cb9837910c3bcefacb47de7486fb47
 F src/parse.y 86e268c29a0f00ffc062bbe934d95ea0d6308b0a
 F src/select.c aaf23d4a6ef44e4378840ec94b6aa64641c01b5c
-F src/shell.c 8387580e44878022c88c02b189bf23bff1862bda
-F src/sqlite.h 58da0a8590133777b741f9836beaef3d58f40268
-F src/sqliteInt.h ddc6f8081ef469ede272cf6a382773dac5758dfc
+F src/shell.c ffcb11569f6f1756148b389ac0f1fc480859698e
+F src/sqlite.h 82ae53028e27919250f886ff9d7c4927de81978a
+F src/sqliteInt.h cf4b8f3c7fbb50adf3d879770defe72502a39022
 F src/tclsqlite.c 9f358618ae803bedf4fb96da5154fd45023bc1f7
 F src/tokenize.c 77ff8164a8751994bc9926ce282847f653ac0c16
 F src/update.c 51b9ef7434b15e31096155da920302e9db0d27fc
 F src/util.c fcd7ac9d2be8353f746e52f665e6c4f5d6b3b805
-F src/vdbe.c 38cec3e88db70b7689018377c1594ac18f746b19
-F src/vdbe.h 5f58611b19799de2dbcdefa4eef33a255cfa8d0d
+F src/vdbe.c 72b533a452953aca618a935b5155d1d4eed3193c
+F src/vdbe.h 6c5653241633c583549c2d8097394ab52550eb63
 F src/where.c 420f666a38b405cd58bd7af832ed99f1dbc7d336
 F test/all.test 0950c135cab7e60c07bd745ccfad1476211e5bd7
 F test/copy.test b77a1214bd7756f2849d5c4fa6e715c0ff0c34eb
@@ -34,6 +34,7 @@ F test/in.test 2c560c0f55fb777029fd9bb5378f2997582aa603
 F test/index.test 620ceab7165dd078d1266bdc2cac6147f04534ac
 F test/insert.test 66f4c3bd600fec8eb1e733b928cbe6fa885eff0c
 F test/insert2.test 732405e30331635af8d159fccabe835eea5cd0c6
+F test/lock.test 42a2d171eba1078cf3fd58ab64241eb8f1b08d69
 F test/main.test b7366cc6f3690915a11834bc1090deeff08acaf9
 F test/select1.test 4e57b0b5eae0c991d9cc51d1288be0476110e6f6
 F test/select2.test ed6e7fc3437079686d7ae4390a00347bbd5f7bf8
@@ -57,15 +58,15 @@ F tool/renumberOps.awk 6d067177ad5f8d711b79577b462da9b3634bd0a9
 F www/arch.fig 4e26e9dca3c49724fc8f554c695ddea9f2413156
 F www/arch.png c4d908b79065a72e7dcf19317f36d1324c550e87
 F www/arch.tcl 4f6a9afecc099a27bba17b4f8cc9561abc15dc40
-F www/c_interface.tcl 8eb800f67e6896b1894d666b81c0b418cea09fc7
-F www/changes.tcl dc7ae83bf05845c043c6d2315413f2dae989658d
+F www/c_interface.tcl 29593cf77025bab137b7ba64b9459eb5eb6b4873
+F www/changes.tcl 6c14cc0f1c1a8929aa0b44304b0e2450d801b5b5
 F www/fileformat.tcl f3a70650e942262f8285d53097d48f0b3aa59862
-F www/index.tcl 4116afce6a8c63d68882d2b00aa10b079e0129cd
+F www/index.tcl 58c9a33ceba12f5efee446c6b10b4f6523a214e1
 F www/lang.tcl 1645e9107d75709be4c6099b643db235bbe0a151
 F www/opcode.tcl 401bdc639509c2f17d3bb97cbbdfdc22a61faa07
-F www/sqlite.tcl b685dc3ce345a6db0441e6d5716ed29abb96dd29
-F www/vdbe.tcl 3ea62769f7a09ee0ee803c8de000182909a31e4e
-P 79ce59cf79df3da2c9dcb944dba15c64c99fbad1
-R 7f9d7add7ab2d3c72acbddbd24bcb674
+F www/sqlite.tcl 69781eaffb02e17aa4af28b76a2bedb19baa8e9f
+F www/vdbe.tcl 3330c700ef9c212a169f568a595361e4cce749ed
+P 937c27b7e18505d0f8b85d2040db8d6a8b7cd441
+R c695a62141a00b7f6ab88cfdecb913c6
 U drh
-Z 7a87be763e4ccb46d3ff76e2d3d669ce
+Z 65d7af2246a10e3639543f77b1956b6b
index 77a4358979cee42b74150e9316d6051b483e1cc3..00a3e009f28c40cee3c56451dc8c493886ac7fbe 100644 (file)
@@ -1 +1 @@
-937c27b7e18505d0f8b85d2040db8d6a8b7cd441
\ No newline at end of file
+4fe8e51c248369572637a5351bd193f07e059fa2
\ No newline at end of file
index a49072e080c697c47adb4f4b5edc4ad535242728..f7ccc6228bc1c4a144b3f85eba06abe1860a08d5 100644 (file)
@@ -33,7 +33,7 @@
 **     COPY
 **     VACUUM
 **
-** $Id: build.c,v 1.19 2000/06/21 13:59:11 drh Exp $
+** $Id: build.c,v 1.20 2000/07/28 14:32:49 drh Exp $
 */
 #include "sqliteInt.h"
 
@@ -56,7 +56,8 @@ void sqliteExec(Parse *pParse){
       FILE *trace = (pParse->db->flags & SQLITE_VdbeTrace)!=0 ? stderr : 0;
       sqliteVdbeTrace(pParse->pVdbe, trace);
       sqliteVdbeExec(pParse->pVdbe, pParse->xCallback, pParse->pArg, 
-                     &pParse->zErrMsg);
+                     &pParse->zErrMsg, pParse->db->pBusyArg,
+                     pParse->db->xBusyCallback);
     }
     sqliteVdbeDelete(pParse->pVdbe);
     pParse->pVdbe = 0;
index d690329beb336cdbc0fdc418cb187f81d845846b..a3baff975ba19eb50224b3e22d9f48fd704dd4b3 100644 (file)
@@ -30,7 +30,7 @@
 ** relatively simple to convert to a different database such
 ** as NDBM, SDBM, or BerkeleyDB.
 **
-** $Id: dbbe.c,v 1.15 2000/06/21 13:59:11 drh Exp $
+** $Id: dbbe.c,v 1.16 2000/07/28 14:32:49 drh Exp $
 */
 #include "sqliteInt.h"
 #include <gdbm.h>
@@ -382,7 +382,12 @@ int sqliteDbbeOpenCursor(
   pCursr->pFile = pFile;
   pCursr->readPending = 0;
   pCursr->needRewind = 1;
-  *ppCursr = pCursr;
+  if( rc!=SQLITE_OK ){
+    sqliteDbbeCloseCursor(pCursr);
+    *ppCursr = 0;
+  }else{
+    *ppCursr = pCursr;
+  }
   return rc;
 }
 
index 06423b54539732ae8f23c9086d5b08814726f2d4..b1233a98f8083452c1c7b986c8b055b047454866 100644 (file)
@@ -26,7 +26,7 @@
 ** other files are for internal use by SQLite and should not be
 ** accessed by users of the library.
 **
-** $Id: main.c,v 1.13 2000/06/21 13:59:12 drh Exp $
+** $Id: main.c,v 1.14 2000/07/28 14:32:49 drh Exp $
 */
 #include "sqliteInt.h"
 
@@ -134,7 +134,8 @@ static int sqliteInit(sqlite *db, char **pzErrMsg){
     return 1;
   }
   sqliteVdbeAddOpList(vdbe, sizeof(initProg)/sizeof(initProg[0]), initProg);
-  rc = sqliteVdbeExec(vdbe, sqliteOpenCb, db, pzErrMsg);
+  rc = sqliteVdbeExec(vdbe, sqliteOpenCb, db, pzErrMsg, 
+                      db->pBusyArg, db->xBusyCallback);
   sqliteVdbeDelete(vdbe);
   if( rc==SQLITE_OK ){
     Table *pTab;
@@ -276,3 +277,71 @@ int sqlite_exec(
   sqliteStrRealloc(pzErrMsg);
   return rc;
 }
+
+/*
+** This routine implements a busy callback that sleeps and tries
+** again until a timeout value is reached.  The timeout value is
+** an integer number of milliseconds passed in as the first
+** argument.
+*/
+static int sqlite_default_busy_callback(
+ void *Timeout,           /* Maximum amount of time to wait */
+ const char *NotUsed,     /* The name of the table that is busy */
+ int count                /* Number of times table has been busy */
+){
+  int rc;
+#ifdef HAVE_USLEEP
+  int delay = 10000;
+  int prior_delay = 0;
+  int timeout = (int)Timeout;
+  int i;
+
+  for(i=1; i<count; i++){ 
+    prior_delay += delay;
+    delay = delay*2;
+    if( delay>=1000000 ){
+      delay = 1000000;
+      prior_delay += 1000000*(count - i - 1);
+      break;
+    }
+  }
+  if( prior_delay + delay > timeout*1000 ){
+    delay = timeout*1000 - prior_delay;
+    if( delay<=0 ) return 0;
+  }
+  usleep(delay);
+  return 1;
+#else
+  int timeout = (int)Timeout;
+  if( (count+1)*1000 > timeout ){
+    return 0;
+  }
+  sleep(1);
+  return 1;
+#endif
+}
+
+/*
+** This routine sets the busy callback for an Sqlite database to the
+** given callback function with the given argument.
+*/
+void sqlite_busy_handler(
+  sqlite *db,
+  int (*xBusy)(void*,const char*,int),
+  void *pArg
+){
+  db->xBusyCallback = xBusy;
+  db->pBusyArg = pArg;
+}
+
+/*
+** This routine installs a default busy handler that waits for the
+** specified number of milliseconds before returning 0.
+*/
+void sqlite_busy_timeout(sqlite *db, int ms){
+  if( ms>0 ){
+    sqlite_busy_handler(db, sqlite_default_busy_callback, (void*)ms);
+  }else{
+    sqlite_busy_handler(db, 0, 0);
+  }
+}
index b34557dd3b8036e3810b3ced66835ae6218730d3..55a56520741de8de9d563206708f0a5260d3a4ee 100644 (file)
@@ -24,7 +24,7 @@
 ** This file contains code to implement the "sqlite" command line
 ** utility for accessing SQLite databases.
 **
-** $Id: shell.c,v 1.15 2000/06/21 13:59:12 drh Exp $
+** $Id: shell.c,v 1.16 2000/07/28 14:32:49 drh Exp $
 */
 #include <stdlib.h>
 #include <string.h>
@@ -112,7 +112,7 @@ static char *one_input_line(const char *zPrior, int isatty){
     zPrompt = "sqlite> ";
   }
   zResult = readline(zPrompt);
-  add_history(zResult);
+  if( zResult ) add_history(zResult);
   return zResult;
 }
 
@@ -382,6 +382,7 @@ static char zHelp[] =
   ".schema ?TABLE?        Show the CREATE statements\n"
   ".separator STRING      Change separator string for \"list\" mode\n"
   ".tables                List names all tables in the database\n"
+  ".timeout MS            Try opening locked tables for MS milliseconds\n"
   ".width NUM NUM ...     Set column widths for \"column\" mode\n"
 ;
 
@@ -554,7 +555,7 @@ static void do_meta_command(char *zLine, sqlite *db, struct callback_data *p){
     sprintf(p->separator, "%.*s", (int)ArraySize(p->separator)-1, azArg[1]);
   }else
 
-  if( c=='t' && strncmp(azArg[0], "tables", n)==0 ){
+  if( c=='t' && n>1 && strncmp(azArg[0], "tables", n)==0 ){
     struct callback_data data;
     char *zErrMsg = 0;
     static char zSql[] = 
@@ -569,6 +570,10 @@ static void do_meta_command(char *zLine, sqlite *db, struct callback_data *p){
     }
   }else
 
+  if( c=='t' && n>1 && strncmp(azArg[0], "timeout", n)==0 && nArg>=2 ){
+    sqlite_busy_timeout(db, atoi(azArg[1]));
+  }else
+
   if( c=='w' && strncmp(azArg[0], "width", n)==0 ){
     int j;
     for(j=1; j<nArg && j<ArraySize(p->colWidth); j++){
index e8cc2ad54173048d10f995c91e7d866db16bd3b1..c05512ce66e5078a017d28e344a4b140e6ce5b4d 100644 (file)
@@ -24,7 +24,7 @@
 ** This header file defines the interface that the sqlite library
 ** presents to client programs.
 **
-** @(#) $Id: sqlite.h,v 1.3 2000/06/02 01:51:20 drh Exp $
+** @(#) $Id: sqlite.h,v 1.4 2000/07/28 14:32:50 drh Exp $
 */
 #ifndef _SQLITE_H_
 #define _SQLITE_H_
@@ -94,12 +94,16 @@ typedef int (*sqlite_callback)(void*,int,char**, char**);
 ** message is written into memory obtained from malloc() and
 ** *errmsg is made to point to that message.  If errmsg==NULL,
 ** then no error message is ever written.  The return value is
-** SQLITE_ERROR if an error occurs.
+** SQLITE_ERROR if an error occurs.  The calling function is
+** responsible for freeing the memory that holds the error
+** message.
 **
 ** If the query could not be executed because a database file is
-** locked or busy, then this function returns SQLITE_BUSY.  If
-** the query could not be executed because a file is missing or
-** has incorrect permissions, this function returns SQLITE_ERROR.
+** locked or busy, then this function returns SQLITE_BUSY.  (This
+** behavior can be modified somewhat using the sqlite_busy_handler()
+** and sqlite_busy_timeout() functions below.) If the query could 
+** not be executed because a file is missing or has incorrect 
+** permissions, this function returns SQLITE_ERROR.
 */
 int sqlite_exec(
   sqlite*,                      /* An open database */
@@ -121,8 +125,6 @@ int sqlite_exec(
 #define SQLITE_NOMEM     6    /* A malloc() failed */
 #define SQLITE_READONLY  7    /* Attempt to write a readonly database */
 
-
-
 /* This function returns true if the given input string comprises
 ** one or more complete SQL statements.
 **
@@ -132,4 +134,40 @@ int sqlite_exec(
 */
 int sqlite_complete(const char *sql);
 
+/*
+** This routine identifies a callback function that is invoked
+** whenever an attempt is made to open a database table that is
+** currently locked by another process or thread.  If the busy callback
+** is NULL, then sqlite_exec() returns SQLITE_BUSY immediately if
+** it finds a locked table.  If the busy callback is not NULL, then
+** sqlite_exec() invokes the callback with three arguments.  The
+** second argument is the name of the locked table and the third
+** argument is the number of times the table has been busy.  If the
+** busy callback returns 0, then sqlite_exec() immediately returns
+** SQLITE_BUSY.  If the callback returns non-zero, then sqlite_exec()
+** tries to open the table again and the cycle repeats.
+**
+** The default busy callback is NULL.
+**
+** Sqlite is re-entrant, so the busy handler may start a new query. 
+** (It is not clear why anyone would every want to do this, but it
+** is allowed, in theory.)  But the busy handler may not close the
+** database.  Closing the database from a busy handler will delete 
+** data structures out from under the executing query and will 
+** probably result in a coredump.
+*/
+void sqlite_busy_handler(sqlite*, int(*)(void*,const char*,int), void*);
+
+/*
+** This routine sets a busy handler that sleeps for a while when a
+** table is locked.  The handler will sleep multiple times until 
+** at least "ms" milleseconds of sleeping have been done.  After
+** "ms" milleseconds of sleeping, the handler returns 0 which
+** causes sqlite_exec() to return SQLITE_BUSY.
+**
+** Calling this routine with an argument less than or equal to zero
+** turns off all busy handlers.
+*/
+void sqlite_busy_timeout(sqlite*, int ms);
+
 #endif /* _SQLITE_H_ */
index 9eef275306b09ffbaea855056ba2ec3fa55a041f..963f4452f93e3a1f00ff3ea9389ec1d7198e4dc9 100644 (file)
@@ -23,7 +23,7 @@
 *************************************************************************
 ** Internal interface definitions for SQLite.
 **
-** @(#) $Id: sqliteInt.h,v 1.25 2000/06/21 13:59:12 drh Exp $
+** @(#) $Id: sqliteInt.h,v 1.26 2000/07/28 14:32:50 drh Exp $
 */
 #include "sqlite.h"
 #include "dbbe.h"
@@ -122,6 +122,8 @@ typedef struct AggExpr AggExpr;
 struct sqlite {
   Dbbe *pBe;                 /* The backend driver */
   int flags;                 /* Miscellanous flags */
+  void *pBusyArg;            /* 1st Argument to the busy callback */
+  int (*xBusyCallback)(void *,const char*,int);
   Table *apTblHash[N_HASH];  /* All tables of the database */
   Index *apIdxHash[N_HASH];  /* All indices of the database */
 };
index eb2b821e6b89def30b8564d75ba7561a4ae66bcd..c6b921773d72a5150a08e0d009b19d07a63e5ba5 100644 (file)
@@ -41,7 +41,7 @@
 ** But other routines are also provided to help in building up
 ** a program instruction by instruction.
 **
-** $Id: vdbe.c,v 1.34 2000/06/21 13:59:13 drh Exp $
+** $Id: vdbe.c,v 1.35 2000/07/28 14:32:50 drh Exp $
 */
 #include "sqliteInt.h"
 #include <unistd.h>
@@ -868,13 +868,22 @@ static Sorter *Merge(Sorter *pLeft, Sorter *pRight){
 ** Other fatal errors return SQLITE_ERROR.
 **
 ** If a database file could not be opened because it is locked by
-** another database instance, then this routine returns SQLITE_BUSY.
+** another database instance, then the xBusy() callback is invoked
+** with pBusyArg as its first argument, the name of the table as the
+** second argument, and the number of times the open has been attempted
+** as the third argument.  The xBusy() callback will typically wait
+** for the database file to be openable, then return.  If xBusy()
+** returns non-zero, another attempt is made to open the file.  If
+** xBusy() returns zero, or if xBusy is NULL, then execution halts
+** and this routine returns SQLITE_BUSY.
 */
 int sqliteVdbeExec(
   Vdbe *p,                   /* The VDBE */
   sqlite_callback xCallback, /* The callback */
   void *pArg,                /* 1st argument to callback */
-  char **pzErrMsg            /* Error msg written here */
+  char **pzErrMsg,           /* Error msg written here */
+  void *pBusyArg,            /* 1st argument to the busy callback */
+  int (*xBusy)(void*,const char*,int)  /* Called when a file is busy */
 ){
   int pc;                    /* The program counter */
   Op *pOp;                   /* Current operation */
@@ -1698,6 +1707,7 @@ int sqliteVdbeExec(
       ** deleted when the cursor is closed.
       */
       case OP_Open: {
+        int busy = 0;
         int i = pOp->p1;
         if( i<0 ) goto bad_instruction;
         if( i>=p->nCursor ){
@@ -1709,26 +1719,35 @@ int sqliteVdbeExec(
         }else if( p->aCsr[i].pCursor ){
           sqliteDbbeCloseCursor(p->aCsr[i].pCursor);
         }
-        rc = sqliteDbbeOpenCursor(p->pBe, pOp->p3, pOp->p2,&p->aCsr[i].pCursor);
-        switch( rc ){
-          case SQLITE_BUSY: {
-            sqliteSetString(pzErrMsg,"table ", pOp->p3, " is locked", 0);
-            break;
-          }
-          case SQLITE_PERM: {
-            sqliteSetString(pzErrMsg, pOp->p2 ? "write" : "read",
-              " permission denied for table ", pOp->p3, 0);
-            break;
-          }
-          case SQLITE_READONLY: {
-            sqliteSetString(pzErrMsg,"table ", pOp->p3, 
-               " is readonly", 0);
-            break;
-          }
-          case SQLITE_NOMEM: {
-            goto no_mem;
+        do {
+          rc = sqliteDbbeOpenCursor(p->pBe,pOp->p3,pOp->p2,&p->aCsr[i].pCursor);
+          switch( rc ){
+            case SQLITE_BUSY: {
+              if( xBusy==0 || (*xBusy)(pBusyArg, pOp->p3, ++busy)==0 ){
+                sqliteSetString(pzErrMsg,"table ", pOp->p3, " is locked", 0);
+                busy = 0;
+              }
+              break;
+            }
+            case SQLITE_PERM: {
+              sqliteSetString(pzErrMsg, pOp->p2 ? "write" : "read",
+                " permission denied for table ", pOp->p3, 0);
+              break;
+            }
+            case SQLITE_READONLY: {
+              sqliteSetString(pzErrMsg,"table ", pOp->p3, 
+                 " is readonly", 0);
+              break;
+            }
+            case SQLITE_NOMEM: {
+              goto no_mem;
+            }
+            case SQLITE_OK: {
+              busy = 0;
+              break;
+            }
           }
-        }
+        }while( busy );
         p->aCsr[i].index = 0;
         p->aCsr[i].keyAsData = 0;
         break;
index 70ce44e8d961acc4e96db2c16bc9e8f063281a0d..bf80b1849b434738daa0b9cc8612eed58b033f40 100644 (file)
@@ -27,7 +27,7 @@
 ** or VDBE.  The VDBE implements an abstract machine that runs a
 ** simple program to access and modify the underlying database.
 **
-** $Id: vdbe.h,v 1.10 2000/06/11 23:50:13 drh Exp $
+** $Id: vdbe.h,v 1.11 2000/07/28 14:32:50 drh Exp $
 */
 #ifndef _SQLITE_VDBE_H_
 #define _SQLITE_VDBE_H_
@@ -187,7 +187,8 @@ void sqliteVdbeDequoteP3(Vdbe*, int addr);
 int sqliteVdbeMakeLabel(Vdbe*);
 void sqliteVdbeDelete(Vdbe*);
 int sqliteVdbeOpcode(const char *zName);
-int sqliteVdbeExec(Vdbe*,sqlite_callback,void*,char**);
+int sqliteVdbeExec(Vdbe*,sqlite_callback,void*,char**,void*,
+                   int(*)(void*,const char*,int));
 int sqliteVdbeList(Vdbe*,sqlite_callback,void*,char**);
 void sqliteVdbeResolveLabel(Vdbe*, int);
 int sqliteVdbeCurrentAddr(Vdbe*);
diff --git a/test/lock.test b/test/lock.test
new file mode 100644 (file)
index 0000000..1078c2a
--- /dev/null
@@ -0,0 +1,101 @@
+# Copyright (c) 1999, 2000 D. Richard Hipp
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public
+# License as published by the Free Software Foundation; either
+# version 2 of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# General Public License for more details.
+# 
+# You should have received a copy of the GNU General Public
+# License along with this library; if not, write to the
+# Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+# Boston, MA  02111-1307, USA.
+#
+# Author contact information:
+#   drh@hwaci.com
+#   http://www.hwaci.com/drh/
+#
+#***********************************************************************
+# This file implements regression tests for SQLite library.  The
+# focus of this script is database locks.
+#
+# $Id: lock.test,v 1.1 2000/07/28 14:32:50 drh Exp $
+
+set testdir [file dirname $argv0]
+source $testdir/tester.tcl
+
+
+# Create a largish table
+#
+do_test lock-1.0 {
+  execsql {CREATE TABLE big(f1 int, f2 int, f3 int)}
+  set f [open ./testdata1.txt w]
+  for {set i 1} {$i<=500} {incr i} {
+    puts $f "$i\t[expr {$i*2}]\t[expr {$i*3}]"
+  }
+  close $f
+  execsql {COPY big FROM './testdata1.txt'}
+  file delete -force ./testdata1.txt
+} {}
+
+do_test lock-1.1 {
+  # Create a background query that gives us a read lock on the big table
+  #
+  set f [open slow.sql w]
+  puts $f "SELECT a.f1, b.f1 FROM big AS a, big AS B"
+  puts $f "WHERE a.f1+b.f1==0.5;"
+  close $f
+  set ::lock_pid [exec ./sqlite testdb <slow.sql &]
+  after 10
+  set v {}
+} {}
+
+do_test lock-1.2 {
+  # Now try to update the database
+  #
+  set v [catch {execsql {UPDATE big SET f2='xyz' WHERE f1=11}} msg]
+  lappend v $msg
+} {1 {table big is locked}}
+
+do_test lock-1.3 {
+  # Try to update the database in a separate process
+  #
+  set f [open update.sql w]
+  puts $f ".timeout 0"
+  puts $f "UPDATE big SET f2='xyz' WHERE f1=11;"
+  puts $f "SELECT f2 FROM big WHERE f1=11;"
+  close $f
+  exec ./sqlite testdb <update.sql
+} "SQL error: table big is locked\n22"
+
+do_test lock-1.4 {
+  # Try to update the database using a timeout
+  #
+  set f [open update.sql w]
+  puts $f ".timeout 1000"
+  puts $f "UPDATE big SET f2='xyz' WHERE f1=11;"
+  puts $f "SELECT f2 FROM big WHERE f1=11;"
+  close $f
+  exec ./sqlite testdb <update.sql
+} "SQL error: table big is locked\n22"
+
+do_test lock-1.5 {
+  # Try to update the database using a timeout
+  #
+  set f [open update.sql w]
+  puts $f ".timeout 10000"
+  puts $f "UPDATE big SET f2='xyz' WHERE f1=11;"
+  puts $f "SELECT f2 FROM big WHERE f1=11;"
+  close $f
+  exec ./sqlite testdb <update.sql
+} {xyz}
+
+catch {exec ps -uax | grep $::lock_pid}
+catch {exec kill -HUP $::lock_pid}
+catch {exec kill -9 $::lock_pid}
+
+finish_test
index ea5e03a51674d6b894310e44acfaa7b1013fe66a..c144716d835aaf1f629fc2692c02950a049a4287 100644 (file)
@@ -1,7 +1,7 @@
 #
 # Run this Tcl script to generate the sqlite.html file.
 #
-set rcsid {$Id: c_interface.tcl,v 1.5 2000/06/21 13:59:14 drh Exp $}
+set rcsid {$Id: c_interface.tcl,v 1.6 2000/07/28 14:32:51 drh Exp $}
 
 puts {<html>
 <head>
@@ -22,7 +22,8 @@ programming interface.</p>
 
 <h2>The API</h2>
 
-<p>The interface to the SQLite library consists of 4 functions,
+<p>The interface to the SQLite library consists of six functions
+(only three of which are required),
 one opaque data structure, and some constants used as return
 values from sqlite_exec():</p>
 
@@ -43,6 +44,10 @@ int sqlite_exec(
 
 int sqlite_complete(const char *sql);
 
+void sqlite_busy_handler(sqlite*, int (*)(void*,const char*,int), void*);
+
+void sqlite_busy_timeout(sqlite*, int ms);
+
 #define SQLITE_OK        0    /* Successful result */
 #define SQLITE_INTERNAL  1    /* An internal logic error in SQLite */
 #define SQLITE_ERROR     2    /* SQL error or missing database */
@@ -225,6 +230,47 @@ then <b>sqlite_exec()</b> is called and the input buffer is reset.  If
 the continuation prompt and another line of text is read and added to
 the input buffer.</p>
 
+<h2>Changing the libraries reponse to locked files</h2>
+
+<p>The GDBM library supports database locks at the file level.
+If a GDBM database file is opened for reading, then that same
+file cannot be reopened for writing until all readers have closed
+the file.  If a GDBM file is open for writing, then the file cannot
+be reopened for reading or writing until it is closed.</p>
+
+<p>If the SQLite library attempts to open a GDBM file and finds that
+the file is locked, the default action is to abort the current
+operation and return SQLITE_BUSY.  But this is not always the most
+convenient behavior, so a mechanism exists to change it.</p>
+
+<p>The <b>sqlite_busy_handler()</b> procedure can be used to register
+a busy callback with an open SQLite database.  The busy callback will
+be invoked whenever SQLite tries to open a GDBM file that is locked.
+The callback will typically do some other useful work, or perhaps sleep,
+in order to give the lock a chance to clear.  If the callback returns
+non-zero, then SQLite tries again to open the GDBM file and the cycle
+repeats.  If the callback returns zero, then SQLite aborts the current
+operation and returns SQLITE_BUSY.</p>
+
+<p>The arguments to <b>sqlite_busy_handler()</b> are the opaque
+structure returned from <b>sqlite_open()</b>, a pointer to the busy
+callback function, and a generic pointer that will be passed as
+the first argument to the busy callback.  When SQLite invokes the
+busy callback, it sends it three arguments:  the generic pointer
+that was passed in as the third argument to <b>sqlite_busy_handler</b>,
+the name of the database table or index that the library is trying
+to open, and the number of times that the library has attempted to
+open the database table or index.</p>
+
+<p>For the common case where we want the busy callback to sleep,
+the SQLite library provides a convenience routine <b>sqlite_busy_timeout()</b>.
+The first argument to <b>sqlite_busy_timeout()</b> is a pointer to
+an open SQLite database and the second argument is a number of milliseconds.
+After <b>sqlite_busy_timeout()</b> has been executed, the SQLite library
+will wait for the lock to clear for at least the number of milliseconds 
+specified before it returns SQLITE_BUSY.  Specifying zero milliseconds for
+the timeout restores the default behavior.</p>
+
 <h2>Usage Examples</h2>
 
 <p>For examples of how the SQLite C/C++ interface can be used,
index d9ed33382dde724fae1f1ecd983e442293238b77..63112f0c6078ce75ce164985b9f2d2d5f63f2cef 100644 (file)
@@ -17,6 +17,11 @@ proc chng {date desc} {
   puts "<DD><P><UL>$desc</UL></P></DD>"
 }
 
+chng {2000 July 28} {
+<li>Added the <b>sqlite_busy_handler()</b> 
+    and <b>sqlite_busy_timeout()</b> interface.</li>
+}
+
 chng {2000 June 23} {
 <li>Begin writing the <a href="vdbe.html">VDBE tutorial</a>.</li>
 }
index 3cc275189e2f14448867592865683ee460e1b1c2..b958bc98ffa28ae632443cd7eca23e0128b7a9e7 100644 (file)
@@ -1,7 +1,7 @@
 #
 # Run this TCL script to generate HTML for the index.html file.
 #
-set rcsid {$Id: index.tcl,v 1.18 2000/06/09 14:14:34 drh Exp $}
+set rcsid {$Id: index.tcl,v 1.19 2000/07/28 14:32:51 drh Exp $}
 
 puts {<html>
 <head><title>SQLite: An SQL Database Engine Built Atop GDBM</title></head>
@@ -50,8 +50,8 @@ an separate RDBMS.</p>
 <li>Import and export data from 
 <a href="http://www.postgresql.org/">PostgreSQL</a>.</li>
 <li>Very simple 
-<a href="c_interface.html">C/C++ interface</a> uses only
-four functions and one opaque structure.</li>
+<a href="c_interface.html">C/C++ interface</a> requires the use of only
+three functions and one opaque structure.</li>
 <li>A <a href="http://dev.scriptics.com/">Tcl</a> interface is
 included.</li>
 <li>Command-line access program <a href="sqlite.html">sqlite</a> uses
index a31133b92629cd2def55eb06c4481f568da8d938..44e4ffb34da2db42e8ece773e66ebf3aef7d7e3f 100644 (file)
@@ -1,7 +1,7 @@
 #
 # Run this Tcl script to generate the sqlite.html file.
 #
-set rcsid {$Id: sqlite.tcl,v 1.10 2000/06/23 17:02:09 drh Exp $}
+set rcsid {$Id: sqlite.tcl,v 1.11 2000/07/28 14:32:51 drh Exp $}
 
 puts {<html>
 <head>
@@ -156,6 +156,7 @@ sqlite> (((.help)))
 .schema ?TABLE?        Show the CREATE statements
 .separator STRING      Change separator string for "list" mode
 .tables                List names all tables in the database
+.timeout MS            Try opening locked tables for MS milliseconds
 .width NUM NUM ...     Set column widths for "column" mode
 sqlite> 
 }
@@ -467,6 +468,13 @@ addr  opcode        p1     p2     p3
 }
 
 puts {
+
+<p>The ".timeout" command sets the amount of time that the <b>sqlite</b>
+program will wait for locks to clear on files it is trying to access
+before returning an error.  The default value of the timeout is zero so
+that an error is returned immediately if any needed database table or
+index is locked.</p>
+
 <p>And finally, we mention the ".exit" command which causes the
 sqlite program to exit.</p>
 
index f4a887d7f5974ced884888012d9cd6699d794290..14e259911c4c8a6616ec4b94dd1f50e7c42e802c 100644 (file)
@@ -1,7 +1,7 @@
 #
 # Run this Tcl script to generate the vdbe.html file.
 #
-set rcsid {$Id: vdbe.tcl,v 1.3 2000/06/26 12:02:51 drh Exp $}
+set rcsid {$Id: vdbe.tcl,v 1.4 2000/07/28 14:32:51 drh Exp $}
 
 puts {<html>
 <head>
@@ -728,7 +728,7 @@ created for every SQLite database.  It looks like this:</p>
 <blockquote><pre>
 CREATE TABLE sqlite_master (
   type      TEXT,    -- either "table" or "index"
-  name      TEXT,    -- name of the table or index
+  name      TEXT,    -- name of this table or index
   tbl_name  TEXT,    -- for indices: name of associated table
   sql       TEXT     -- SQL text of the original CREATE statement
 )
@@ -751,7 +751,7 @@ the first thing it does is a SELECT to read the "sql"
 columns from all entries of the sqlite_master table.
 The "sql" column contains the complete SQL text of the
 CREATE statement that originally generated the index or
-table.  This text is fed back into the SQLite parse
+table.  This text is fed back into the SQLite parser
 and used to reconstruct the
 internal data structures describing the index or table.</p>