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 "

    $desc

" } +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.