diff --git a/Makefile.in b/Makefile.in
index 75efe82eaf..883f50cf43 100644
--- a/Makefile.in
+++ b/Makefile.in
@@ -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:
diff --git a/configure b/configure
index 2abd69b785..42a2bc152d 100755
--- 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 <
+/* 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.
#
diff --git a/configure.in b/configure.in
index 7a4af2ab72..7b93db5035 100644
--- a/configure.in
+++ b/configure.in
@@ -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.
#
diff --git a/manifest b/manifest
index 5fef54b1d7..1ec8235794 100644
--- 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
diff --git a/manifest.uuid b/manifest.uuid
index 77a4358979..00a3e009f2 100644
--- a/manifest.uuid
+++ b/manifest.uuid
@@ -1 +1 @@
-937c27b7e18505d0f8b85d2040db8d6a8b7cd441
\ No newline at end of file
+4fe8e51c248369572637a5351bd193f07e059fa2
\ No newline at end of file
diff --git a/src/build.c b/src/build.c
index a49072e080..f7ccc6228b 100644
--- a/src/build.c
+++ b/src/build.c
@@ -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;
diff --git a/src/dbbe.c b/src/dbbe.c
index d690329beb..a3baff975b 100644
--- a/src/dbbe.c
+++ b/src/dbbe.c
@@ -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
@@ -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;
}
diff --git a/src/main.c b/src/main.c
index 06423b5453..b1233a98f8 100644
--- a/src/main.c
+++ b/src/main.c
@@ -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=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);
+ }
+}
diff --git a/src/shell.c b/src/shell.c
index b34557dd3b..55a5652074 100644
--- a/src/shell.c
+++ b/src/shell.c
@@ -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
#include
@@ -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; jcolWidth); j++){
diff --git a/src/sqlite.h b/src/sqlite.h
index e8cc2ad541..c05512ce66 100644
--- a/src/sqlite.h
+++ b/src/sqlite.h
@@ -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_ */
diff --git a/src/sqliteInt.h b/src/sqliteInt.h
index 9eef275306..963f4452f9 100644
--- a/src/sqliteInt.h
+++ b/src/sqliteInt.h
@@ -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 */
};
diff --git a/src/vdbe.c b/src/vdbe.c
index eb2b821e6b..c6b921773d 100644
--- a/src/vdbe.c
+++ b/src/vdbe.c
@@ -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
@@ -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;
+ 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;
+ }
}
- 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;
- }
- }
+ }while( busy );
p->aCsr[i].index = 0;
p->aCsr[i].keyAsData = 0;
break;
diff --git a/src/vdbe.h b/src/vdbe.h
index 70ce44e8d9..bf80b1849b 100644
--- a/src/vdbe.h
+++ b/src/vdbe.h
@@ -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
index 0000000000..1078c2a675
--- /dev/null
+++ b/test/lock.test
@@ -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
@@ -22,7 +22,8 @@ programming interface.
The API
-The interface to the SQLite library consists of 4 functions,
+
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():
@@ -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 sqlite_exec() 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.
+Changing the libraries reponse to locked files
+
+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.
+
+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.
+
+The sqlite_busy_handler() 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.
+
+The arguments to sqlite_busy_handler() are the opaque
+structure returned from sqlite_open(), 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 sqlite_busy_handler,
+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.
+
+For the common case where we want the busy callback to sleep,
+the SQLite library provides a convenience routine sqlite_busy_timeout().
+The first argument to sqlite_busy_timeout() is a pointer to
+an open SQLite database and the second argument is a number of milliseconds.
+After sqlite_busy_timeout() 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.
+
Usage Examples
For examples of how the SQLite C/C++ interface can be used,
diff --git a/www/changes.tcl b/www/changes.tcl
index d9ed33382d..63112f0c60 100644
--- a/www/changes.tcl
+++ b/www/changes.tcl
@@ -17,6 +17,11 @@ proc chng {date desc} {
puts "
"
}
+chng {2000 July 28} {
+Added the sqlite_busy_handler()
+ and sqlite_busy_timeout() interface.
+}
+
chng {2000 June 23} {
Begin writing the VDBE tutorial.
}
diff --git a/www/index.tcl b/www/index.tcl
index 3cc275189e..b958bc98ff 100644
--- a/www/index.tcl
+++ b/www/index.tcl
@@ -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 {
SQLite: An SQL Database Engine Built Atop GDBM
@@ -50,8 +50,8 @@ an separate RDBMS.
Import and export data from
PostgreSQL.
Very simple
-C/C++ interface uses only
-four functions and one opaque structure.
+C/C++ interface requires the use of only
+three functions and one opaque structure.
A Tcl interface is
included.
Command-line access program sqlite uses
diff --git a/www/sqlite.tcl b/www/sqlite.tcl
index a31133b926..44e4ffb34d 100644
--- a/www/sqlite.tcl
+++ b/www/sqlite.tcl
@@ -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 {
@@ -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 {
+
+The ".timeout" command sets the amount of time that the sqlite
+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.
+
And finally, we mention the ".exit" command which causes the
sqlite program to exit.
diff --git a/www/vdbe.tcl b/www/vdbe.tcl
index f4a887d7f5..14e259911c 100644
--- a/www/vdbe.tcl
+++ b/www/vdbe.tcl
@@ -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 {
@@ -728,7 +728,7 @@ created for every SQLite database. It looks like this:
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.