]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
In shared-cache mode, lock all required tables before beginning to execute the body...
authordanielk1977 <danielk1977@noemail.net>
Sat, 7 Jan 2006 13:21:04 +0000 (13:21 +0000)
committerdanielk1977 <danielk1977@noemail.net>
Sat, 7 Jan 2006 13:21:04 +0000 (13:21 +0000)
FossilOrigin-Name: 23b587b05b89727248805e6d9e5141e018cf2152

16 files changed:
manifest
manifest.uuid
src/analyze.c
src/btree.c
src/btree.h
src/build.c
src/delete.c
src/insert.c
src/select.c
src/sqliteInt.h
src/tokenize.c
src/trigger.c
src/update.c
src/vdbe.c
src/where.c
test/shared.test

index 3b109349601c25b1e332ddd02c6ed88adae023ba..3c23686ee1bc507d04124ebb7412e2ff91d5efb9 100644 (file)
--- a/manifest
+++ b/manifest
@@ -1,5 +1,5 @@
-C Drop\sthe\smutex\sif\sthe\sTSD\skey\sallocation\sfails.\s\sTicket\s#1585.\s(CVS\s2880)
-D 2006-01-07T04:06:55
+C In\sshared-cache\smode,\slock\sall\srequired\stables\sbefore\sbeginning\sto\sexecute\sthe\sbody\sof\sthe\sstatement\sprogram.\s(CVS\s2881)
+D 2006-01-07T13:21:04
 F Makefile.in c79fbdaa264c6afcd435f2fb492551de5a8cf80d
 F Makefile.linux-gcc aee18d8a05546dcf1888bd4547e442008a49a092
 F README 9c4e2d6706bdcc3efdd773ce752a8cdab4f90028
@@ -31,22 +31,22 @@ F sqlite3.1 6be1ad09113570e1fc8dcaff84c9b0b337db5ffc
 F sqlite3.def c413e514217736884254739a105c8c942fdf0c2f
 F sqlite3.pc.in 985b9bf34192a549d7d370e0f0b6b34a4f61369a
 F src/alter.c e9deb3f4fd7c663a0d1f235d541bc5ea1f2cfa8b
-F src/analyze.c d821684cdb4d0403e327e4a3440a832e9e54fa3a
+F src/analyze.c 82e334a1be4bb6961387a4c1441aed90fe9a2d8a
 F src/attach.c 4a04ffcd17357a7848aa17c12c955d109f533bd0
 F src/auth.c cdec356a5cd8b217c346f816c5912221537fe87f
-F src/btree.c dff0ebc0ba1188b5a5586e4108de942549acc6f7
-F src/btree.h f7ba8e2f9f387cca4978e1495504a0bf556dcbf2
-F src/build.c 715ac7d49bbfcae5f3fdfd60885397b2133c283b
+F src/btree.c 902d0d3f5c32b665a2184be5997ae0597bd47d56
+F src/btree.h 5663c4f43e8521546ccebc8fc95acb013b8f3184
+F src/build.c 0cf9f744911826ded1c8ee8adbf881ed5a2ef70a
 F src/callback.c 62066afd516f220575e81b1a1239ab92a2eae252
 F src/complete.c df1681cef40dec33a286006981845f87b194e7a4
 F src/date.c 9a1fe548e31a9b14a43b88f711254a968929659d
-F src/delete.c 2479a8419c76cd4c13f2181d399425ec215ceeb9
+F src/delete.c 32ba37cced50d26ed996d67dc7d19195e081bbb7
 F src/experimental.c 50c1e3b34f752f4ac10c36f287db095c2b61766d
 F src/expr.c ed2a272c7afd63232ca04881159ce2366266e35d
 F src/func.c 5e4204b2ebe89de5228d78eef9352a0ed253f4c0
 F src/hash.c 8747cf51d12de46512880dfcf1b68b4e24072863
 F src/hash.h 1b0c445e1c89ff2aaad9b4605ba61375af001e84
-F src/insert.c d167f9d41932ddaff9162f116e2abc514b0680b6
+F src/insert.c 66f2e745bd3db551bed9756c7d7df191f7b72ba9
 F src/legacy.c 59757d857ab95fcbb0ac27692d3201e35f093dd7
 F src/main.c 8c2d64f1460200d79d7de4605a9489c0506be5fe
 F src/md5.c c5fdfa5c2593eaee2e32a5ce6c6927c986eaf217
@@ -66,10 +66,10 @@ F src/pragma.c ae8b135531e7a4d692c60bd909c819d0b3fc588a
 F src/prepare.c fef89dc92703d345251142af966b60e44a66cfc5
 F src/printf.c f47a2f4b5387cd2ebb12e9117a1a5d6bd9a2b812
 F src/random.c d40f8d356cecbd351ccfab6eaedd7ec1b54f5261
-F src/select.c a60e5c7fad9ce7adc78d9eb32a0a89dd5acd04fb
+F src/select.c 77bcb71f609ff95247a529acf7dfc1c1ec09154b
 F src/shell.c 66b073375efbdee19045e7e0cd38b85f9aff71da
 F src/sqlite.h.in ba3a29daa6a16e054191ccb384a981964e882a1d
-F src/sqliteInt.h 8e28cfdaf55761f054904c9a5c573e90b96e2433
+F src/sqliteInt.h 40c3511c05df83c7c1c244d0432baa9497feee4d
 F src/table.c 486dcfce532685b53b5a2b5da8bba0ded6fb2316
 F src/tclsqlite.c 42151e6c78502277be77b88c00535d53ce89b917
 F src/test1.c 1171547fad57a104c716116695fb8c5c7ef43345
@@ -78,20 +78,20 @@ F src/test3.c 9742aa146eb750cab81c1d5605286c3a0eb88054
 F src/test4.c a8fd681e139e1c61f22a77d07fc3a99cb28fff3f
 F src/test5.c 7162f8526affb771c4ed256826eee7bb9eca265f
 F src/test6.c 74d91b487c68154156eded457925d96aa2a3fdbb
-F src/tokenize.c 7a3a3d3cc734f684a77c4dfd09eb46fcee25394c
-F src/trigger.c 13c449e61cb2c831e07b9fd7175d0c16dcbb28ac
-F src/update.c c72e9cbbc0adf8d728c1c39ace03d4adb29b5cfb
+F src/tokenize.c 10c32e980e3d8db98cfe32e3485a458fda5b3117
+F src/trigger.c d8e0fe913fcd3819649faf2863cd881ff60ffc2e
+F src/update.c 29ba0385c8639803cd8e6e616e99096a0bc10443
 F src/utf.c b7bffac4260177ae7f83c01d025fe0f5ed70ce71
 F src/util.c 8a3ef3c1b345cdadcee33ce4537c63bb0fda0ed8
 F src/vacuum.c a7301804d4f849da0ce9d869219c71c6d621c34e
-F src/vdbe.c 61f37ba6f30c3f1f5c27c9daee517c7e5949c5f9
+F src/vdbe.c 9ff36ae7887ba918e7c319729038eab59cfeb2b9
 F src/vdbe.h 8729a4ee16ff9aeab2af9667df3cf300ff978e13
 F src/vdbeInt.h 9b78ba00cc006bff17e04a54ba3ded9fc7810a10
 F src/vdbeapi.c 7335569b1bad946ba53892384b4b1534e877b1ee
 F src/vdbeaux.c a4eea656afebc6161771ddfa7a9c91186a5d7888
 F src/vdbefifo.c 9efb94c8c3f4c979ebd0028219483f88e57584f5
 F src/vdbemem.c deba8d6e3727643924b210a8c531a496c2b8d386
-F src/where.c 3ec45076e7cce523aad34eaf9bd119237b56942a
+F src/where.c de22a3a84c595ca1ad206dd19818f65f950e79f8
 F tclinstaller.tcl 046e3624671962dc50f0481d7c25b38ef803eb42
 F test/all.test 90cf64bb655e3d474b0dda04e63ece03e36b0ce2
 F test/alter.test b94b640063e725d062b2997bd2810ac39195c718
@@ -220,7 +220,7 @@ F test/select4.test c239f516aa31f42f2ef7c6d7cd01105f08f934ca
 F test/select5.test 07a90ab3c7e3f0a241a9cdea1d997b2c8a89ff0b
 F test/select6.test f459a19bdac0501c4d3eb1a4df4b7a76f1bb8ad4
 F test/select7.test 1bf795b948c133a15a2a5e99d3270e652ec58ce6
-F test/shared.test 0fc3ba42901d31a9c0c48edbbe6cc61008438b16
+F test/shared.test a19b3db0d10dcb965ab73b04830f94149f6eb85b
 F test/sort.test 0e4456e729e5a92a625907c63dcdedfbe72c5dc5
 F test/subquery.test e6de53332c0301b3cfa34edc3f3cd5fa1e859efd
 F test/subselect.test 2d13fb7f450db3595adcdd24079a0dd1d2d6abc2
@@ -335,7 +335,7 @@ F www/tclsqlite.tcl bb0d1357328a42b1993d78573e587c6dcbc964b9
 F www/vdbe.tcl 87a31ace769f20d3627a64fa1fade7fed47b90d0
 F www/version3.tcl a99cf5f6d8bd4d5537584a2b342f0fb9fa601d8b
 F www/whentouse.tcl 97e2b5cd296f7d8057e11f44427dea8a4c2db513
-P 22bf1a2ffac503020dfa78d228b561d1cf6f3894
-R 4f67d90864139abe40a0021c48eb6474
-U drh
-Z 8cbffd6d58c70ff076573e18f16ee469
+P 77ac231c0e21c09c0b612a4e72bcc863f2c95fd3
+R a3383f6d4bdf9c6fcebb44953df9373f
+U danielk1977
+Z 6669b8779258a46a23e7780534ef4f13
index e232b2ad10c0071c59adfa07350ac2a1126e5142..b12a96548bf9898b1c0bdb59ca9054c4fcec3aaa 100644 (file)
@@ -1 +1 @@
-77ac231c0e21c09c0b612a4e72bcc863f2c95fd3
\ No newline at end of file
+23b587b05b89727248805e6d9e5141e018cf2152
\ No newline at end of file
index 3bcc8add4110c483913e2452e598af232bbcd879..4669c733b350c14af97b3790f34aa67cb9c02230 100644 (file)
@@ -11,7 +11,7 @@
 *************************************************************************
 ** This file contains code associated with the ANALYZE command.
 **
-** @(#) $Id: analyze.c,v 1.12 2006/01/05 11:34:33 danielk1977 Exp $
+** @(#) $Id: analyze.c,v 1.13 2006/01/07 13:21:04 danielk1977 Exp $
 */
 #ifndef SQLITE_OMIT_ANALYZE
 #include "sqliteInt.h"
@@ -61,8 +61,14 @@ static void openStatTable(
     sqlite3VdbeAddOp(v, OP_Clear, pStat->tnum, iDb);
   }
 
-  /* Open the sqlite_stat1 table for writing.
+  /* Open the sqlite_stat1 table for writing. Unless it was created
+  ** by this vdbe program, lock it for writing at the shared-cache level. 
+  ** If this vdbe did create the sqlite_stat1 table, then it must have 
+  ** already obtained a schema-lock, making the write-lock redundant.
   */
+  if( iRootPage>0 ){
+    sqlite3TableLock(pParse, iDb, iRootPage, 1, "sqlite_stat1");
+  }
   sqlite3VdbeAddOp(v, OP_Integer, iDb, 0);
   sqlite3VdbeAddOp(v, OP_OpenWrite, iStatCur, iRootPage);
   sqlite3VdbeAddOp(v, OP_SetNumColumns, iStatCur, 3);
@@ -103,6 +109,9 @@ static void analyzeOneTable(
   }
 #endif
 
+  /* Establish a read-lock on the table at the shared-cache level. */
+  sqlite3TableLock(pParse, iDb, pTab->tnum, 0, pTab->zName);
+
   iIdxCur = pParse->nTab;
   for(pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext){
     /* Open a cursor to the index to be analyzed
index 9847f72c80cb72b8fba3ee8d8c1da4e600fc3275..9f0a2e7f55223bbd6a2e75e19feda882df545ffd 100644 (file)
@@ -9,7 +9,7 @@
 **    May you share freely, never taking more than you give.
 **
 *************************************************************************
-** $Id: btree.c,v 1.284 2006/01/06 21:52:50 drh Exp $
+** $Id: btree.c,v 1.285 2006/01/07 13:21:04 danielk1977 Exp $
 **
 ** This file implements a external (disk-based) database using BTrees.
 ** For a detailed discussion of BTrees, refer to
@@ -2686,13 +2686,6 @@ int sqlite3BtreeCursor(
     }
   }
 
-#ifndef SQLITE_OMIT_SHARED_CACHE
-  rc = queryTableLock(p, iTable, wrFlag?WRITE_LOCK:READ_LOCK);
-  if( rc!=SQLITE_OK ){
-    return rc;
-  }
-#endif
-
   if( pBt->pPage1==0 ){
     rc = lockBtreeWithRetry(p);
     if( rc!=SQLITE_OK ){
@@ -2715,13 +2708,6 @@ int sqlite3BtreeCursor(
     goto create_cursor_exception;
   }
 
-  /* Obtain the table-lock on the shared-btree. */
-  rc = lockTable(p, iTable, wrFlag?WRITE_LOCK:READ_LOCK);
-  if( rc!=SQLITE_OK ){
-    assert( rc==SQLITE_NOMEM );
-    goto create_cursor_exception;
-  }
-
   /* Now that no other errors can occur, finish filling in the BtCursor
   ** variables, link the cursor into the BtShared list and set *ppCur (the
   ** output argument to this function).
@@ -6492,6 +6478,15 @@ int sqlite3BtreeSchemaLocked(Btree *p){
   return (queryTableLock(p, MASTER_ROOT, READ_LOCK)!=SQLITE_OK);
 }
 
+int sqlite3BtreeLockTable(Btree *p, int iTab, u8 isWriteLock){
+  u8 lockType = (isWriteLock?WRITE_LOCK:READ_LOCK);
+  int rc = queryTableLock(p, iTab, lockType);
+  if( rc==SQLITE_OK ){
+    rc = lockTable(p, iTab, lockType);
+  }
+  return rc;
+}
+
 #ifndef SQLITE_OMIT_SHARED_CACHE
 /*
 ** Enable the shared pager and schema features.
index 6a65813c906a7f56240b87475a283444750ee1b7..70bdbd467ce311b2de013a25c4051b354c42c6d1 100644 (file)
@@ -13,7 +13,7 @@
 ** subsystem.  See comments in the source code for a detailed description
 ** of what each interface routine does.
 **
-** @(#) $Id: btree.h,v 1.68 2006/01/06 13:00:30 danielk1977 Exp $
+** @(#) $Id: btree.h,v 1.69 2006/01/07 13:21:04 danielk1977 Exp $
 */
 #ifndef _BTREE_H_
 #define _BTREE_H_
@@ -78,6 +78,7 @@ int sqlite3BtreeIsInStmt(Btree*);
 int sqlite3BtreeSync(Btree*, const char *zMaster);
 void *sqlite3BtreeSchema(Btree *, int, void(*)(void *));
 int sqlite3BtreeSchemaLocked(Btree *);
+int sqlite3BtreeLockTable(Btree *, int, u8);
 
 const char *sqlite3BtreeGetFilename(Btree *);
 const char *sqlite3BtreeGetDirname(Btree *);
index 528058a597b3373f4b74987e926cdbadbae45c82..494972b4b8f13754ab8a3bf70511695e089c6fab 100644 (file)
@@ -22,7 +22,7 @@
 **     COMMIT
 **     ROLLBACK
 **
-** $Id: build.c,v 1.368 2006/01/06 06:33:13 danielk1977 Exp $
+** $Id: build.c,v 1.369 2006/01/07 13:21:04 danielk1977 Exp $
 */
 #include "sqliteInt.h"
 #include <ctype.h>
@@ -36,6 +36,87 @@ void sqlite3BeginParse(Parse *pParse, int explainFlag){
   pParse->nVar = 0;
 }
 
+#ifndef SQLITE_OMIT_SHARED_CACHE
+/*
+** The TableLock structure is only used by the sqlite3TableLock() and
+** codeTableLocks() functions.
+*/
+struct TableLock {
+  int iDb;
+  int iTab;
+  u8 isWriteLock;
+  const char *zName;
+};
+
+/*
+** Have the compiled statement lock the table with rootpage iTab in database
+** iDb at the shared-cache level when executed. The isWriteLock argument 
+** is zero for a read-lock, or non-zero for a write-lock.
+**
+** The zName parameter should point to the unqualified table name. This is
+** used to provide a more informative error message should the lock fail.
+*/
+void sqlite3TableLock(
+  Parse *pParse, 
+  int iDb, 
+  int iTab, 
+  u8 isWriteLock,  
+  const char *zName
+){
+  int i;
+  int nBytes;
+  TableLock *p;
+  SqliteTsd *pTsd = sqlite3Tsd();
+
+  if( 0==pTsd->useSharedData ){
+    return;
+  }
+
+  for(i=0; i<pParse->nTableLock; i++){
+    p = &pParse->aTableLock[i];
+    if( p->iDb==iDb && p->iTab==iTab ){
+      p->isWriteLock = (p->isWriteLock || isWriteLock);
+      return;
+    }
+  }
+
+  nBytes = sizeof(TableLock) * (pParse->nTableLock+1);
+  sqliteReallocOrFree((void **)&pParse->aTableLock, nBytes);
+  if( pParse->aTableLock ){
+    p = &pParse->aTableLock[pParse->nTableLock++];
+    p->iDb = iDb;
+    p->iTab = iTab;
+    p->isWriteLock = isWriteLock;
+    p->zName = zName;
+  }
+}
+
+/*
+** Code an OP_TableLock instruction for each table locked by the
+** statement (configured by calls to sqlite3TableLock()).
+*/
+static void codeTableLocks(Parse *pParse){
+  int i;
+  Vdbe *pVdbe; 
+  assert( sqlite3Tsd()->useSharedData || pParse->nTableLock==0 );
+
+  if( 0==(pVdbe = sqlite3GetVdbe(pParse)) ){
+    return;
+  }
+
+  for(i=0; i<pParse->nTableLock; i++){
+    TableLock *p = &pParse->aTableLock[i];
+    int p1 = p->iDb;
+    if( p->isWriteLock ){
+      p1 = -1*(p1+1);
+    }
+    sqlite3VdbeOp3(pVdbe, OP_TableLock, p1, p->iTab, p->zName, P3_STATIC);
+  }
+}
+#else
+  #define codeTableLocks(x)
+#endif
+
 /*
 ** This routine is called after a single SQL statement has been
 ** parsed and a VDBE program to execute that statement has been
@@ -73,6 +154,7 @@ void sqlite3FinishCoding(Parse *pParse){
     ** transaction on each used database and to verify the schema cookie
     ** on each used database.
     */
+    assert( pParse->cookieGoto>0 || pParse->nTableLock==0 );
     if( pParse->cookieGoto>0 ){
       u32 mask;
       int iDb;
@@ -82,6 +164,12 @@ void sqlite3FinishCoding(Parse *pParse){
         sqlite3VdbeAddOp(v, OP_Transaction, iDb, (mask & pParse->writeMask)!=0);
         sqlite3VdbeAddOp(v, OP_VerifyCookie, iDb, pParse->cookieValue[iDb]);
       }
+
+      /* Once all the cookies have been verified and transactions opened, 
+      ** obtain the required table-locks. This is a no-op unless the 
+      ** shared-cache feature is enabled.
+      */
+      codeTableLocks(pParse);
       sqlite3VdbeAddOp(v, OP_Goto, 0, pParse->cookieGoto);
     }
 
@@ -509,7 +597,9 @@ char *sqlite3NameFromToken(Token *pName){
 ** Open the sqlite_master table stored in database number iDb for
 ** writing. The table is opened using cursor 0.
 */
-void sqlite3OpenMasterTable(Vdbe *v, int iDb){
+void sqlite3OpenMasterTable(Parse *p, int iDb){
+  Vdbe *v = sqlite3GetVdbe(p);
+  sqlite3TableLock(p, iDb, MASTER_ROOT, 1, SCHEMA_TABLE(iDb));
   sqlite3VdbeAddOp(v, OP_Integer, iDb, 0);
   sqlite3VdbeAddOp(v, OP_OpenWrite, 0, MASTER_ROOT);
   sqlite3VdbeAddOp(v, OP_SetNumColumns, 0, 5); /* sqlite_master has 5 columns */
@@ -777,7 +867,7 @@ void sqlite3StartTable(
     {
       sqlite3VdbeAddOp(v, OP_CreateTable, iDb, 0);
     }
-    sqlite3OpenMasterTable(v, iDb);
+    sqlite3OpenMasterTable(pParse, iDb);
     sqlite3VdbeAddOp(v, OP_NewRowid, 0, 0);
     sqlite3VdbeAddOp(v, OP_Dup, 0, 0);
     sqlite3VdbeAddOp(v, OP_Null, 0, 0);
@@ -1374,6 +1464,11 @@ void sqlite3EndTable(
     ** Once the SELECT has been coded by sqlite3Select(), it is in a
     ** suitable state to query for the column names and types to be used
     ** by the new table.
+    **
+    ** A shared-cache write-lock is not required to write to the new table,
+    ** as a schema-lock must have already been obtained to create it. Since
+    ** a schema-lock excludes all other database users, the write-lock would
+    ** be redundant.
     */
     if( pSelect ){
       Table *pSelTab;
@@ -2052,6 +2147,9 @@ static void sqlite3RefillIndex(Parse *pParse, Index *pIndex, int memRootPage){
     return;
   }
 
+  /* Require a write-lock on the table to perform this operation */
+  sqlite3TableLock(pParse, iDb, pTab->tnum, 1, pTab->zName);
+
   v = sqlite3GetVdbe(pParse);
   if( v==0 ) return;
   if( memRootPage>=0 ){
@@ -2064,7 +2162,7 @@ static void sqlite3RefillIndex(Parse *pParse, Index *pIndex, int memRootPage){
   sqlite3VdbeAddOp(v, OP_Integer, iDb, 0);
   sqlite3VdbeOp3(v, OP_OpenWrite, iIdx, tnum,
                     (char*)&pIndex->keyInfo, P3_KEYINFO);
-  sqlite3OpenTableForReading(v, iTab, iDb, pTab);
+  sqlite3OpenTable(pParse, iTab, iDb, pTab, OP_OpenRead);
   addr1 = sqlite3VdbeAddOp(v, OP_Rewind, iTab, 0);
   sqlite3GenerateIndexKey(v, pIndex, iTab);
   if( pIndex->onError!=OE_None ){
index 9114304edbc4ce416571ec01e70912ad304ee7b8..9c5cf5175e9333fd928adbff6427201276b04128 100644 (file)
@@ -12,7 +12,7 @@
 ** This file contains C code routines that are called by the parser
 ** in order to generate code for DELETE FROM statements.
 **
-** $Id: delete.c,v 1.114 2006/01/05 11:34:34 danielk1977 Exp $
+** $Id: delete.c,v 1.115 2006/01/07 13:21:04 danielk1977 Exp $
 */
 #include "sqliteInt.h"
 
@@ -59,15 +59,19 @@ int sqlite3IsReadOnly(Parse *pParse, Table *pTab, int viewOk){
 /*
 ** Generate code that will open a table for reading.
 */
-void sqlite3OpenTableForReading(
-  Vdbe *v,        /* Generate code into this VDBE */
+void sqlite3OpenTable(
+  Parse *p,       /* Generate code into this VDBE */
   int iCur,       /* The cursor number of the table */
   int iDb,        /* The database index in sqlite3.aDb[] */
-  Table *pTab     /* The table to be opened */
+  Table *pTab,    /* The table to be opened */
+  int opcode      /* OP_OpenRead or OP_OpenWrite */
 ){
+  Vdbe *v = sqlite3GetVdbe(p);
+  assert( opcode==OP_OpenWrite || opcode==OP_OpenRead );
+  sqlite3TableLock(p, iDb, pTab->tnum, (opcode==OP_OpenWrite), pTab->zName);
   sqlite3VdbeAddOp(v, OP_Integer, iDb, 0);
   VdbeComment((v, "# %s", pTab->zName));
-  sqlite3VdbeAddOp(v, OP_OpenRead, iCur, pTab->tnum);
+  sqlite3VdbeAddOp(v, opcode, iCur, pTab->tnum);
   sqlite3VdbeAddOp(v, OP_SetNumColumns, iCur, pTab->nCol);
 }
 
@@ -208,7 +212,7 @@ void sqlite3DeleteFrom(
       int endOfLoop = sqlite3VdbeMakeLabel(v);
       int addr;
       if( !isView ){
-        sqlite3OpenTableForReading(v, iCur, iDb, pTab);
+        sqlite3OpenTable(pParse, iCur, iDb, pTab, OP_OpenRead);
       }
       sqlite3VdbeAddOp(v, OP_Rewind, iCur, sqlite3VdbeCurrentAddr(v)+2);
       addr = sqlite3VdbeAddOp(v, OP_AddImm, 1, 0);
@@ -276,7 +280,7 @@ void sqlite3DeleteFrom(
       addr = sqlite3VdbeAddOp(v, OP_FifoRead, 0, end);
       if( !isView ){
         sqlite3VdbeAddOp(v, OP_Dup, 0, 0);
-        sqlite3OpenTableForReading(v, iCur, iDb, pTab);
+        sqlite3OpenTable(pParse, iCur, iDb, pTab, OP_OpenRead);
       }
       sqlite3VdbeAddOp(v, OP_MoveGe, iCur, 0);
       sqlite3VdbeAddOp(v, OP_Rowid, iCur, 0);
index 64bff8d5cf57a589d235a0aaf1a53f984017b95c..dea6c6fc1537d6bbf56664f5bf22814b577d5df0 100644 (file)
@@ -12,7 +12,7 @@
 ** This file contains C code routines that are called by the parser
 ** to handle INSERT statements in SQLite.
 **
-** $Id: insert.c,v 1.152 2006/01/05 11:34:34 danielk1977 Exp $
+** $Id: insert.c,v 1.153 2006/01/07 13:21:04 danielk1977 Exp $
 */
 #include "sqliteInt.h"
 
@@ -305,9 +305,7 @@ void sqlite3Insert(
     int base = sqlite3VdbeCurrentAddr(v);
     counterRowid = pParse->nMem++;
     counterMem = pParse->nMem++;
-    sqlite3VdbeAddOp(v, OP_Integer, iDb, 0);
-    sqlite3VdbeAddOp(v, OP_OpenRead, iCur, pDb->pSchema->pSeqTab->tnum);
-    sqlite3VdbeAddOp(v, OP_SetNumColumns, iCur, 2);
+    sqlite3OpenTable(pParse, iCur, iDb, pDb->pSchema->pSeqTab, OP_OpenRead);
     sqlite3VdbeAddOp(v, OP_Rewind, iCur, base+13);
     sqlite3VdbeAddOp(v, OP_Column, iCur, 0);
     sqlite3VdbeOp3(v, OP_String8, 0, 0, pTab->zName, 0);
@@ -686,9 +684,7 @@ void sqlite3Insert(
   if( pTab->autoInc ){
     int iCur = pParse->nTab;
     int base = sqlite3VdbeCurrentAddr(v);
-    sqlite3VdbeAddOp(v, OP_Integer, iDb, 0);
-    sqlite3VdbeAddOp(v, OP_OpenWrite, iCur, pDb->pSchema->pSeqTab->tnum);
-    sqlite3VdbeAddOp(v, OP_SetNumColumns, iCur, 2);
+    sqlite3OpenTable(pParse, iCur, iDb, pDb->pSchema->pSeqTab, OP_OpenWrite);
     sqlite3VdbeAddOp(v, OP_MemLoad, counterRowid, 0);
     sqlite3VdbeAddOp(v, OP_NotNull, -1, base+7);
     sqlite3VdbeAddOp(v, OP_Pop, 1, 0);
@@ -1110,10 +1106,7 @@ void sqlite3OpenTableAndIndices(
   Index *pIdx;
   Vdbe *v = sqlite3GetVdbe(pParse);
   assert( v!=0 );
-  sqlite3VdbeAddOp(v, OP_Integer, iDb, 0);
-  VdbeComment((v, "# %s", pTab->zName));
-  sqlite3VdbeAddOp(v, op, base, pTab->tnum);
-  sqlite3VdbeAddOp(v, OP_SetNumColumns, base, pTab->nCol);
+  sqlite3OpenTable(pParse, base, iDb, pTab, op);
   for(i=1, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext, i++){
     assert( pIdx->pSchema==pTab->pSchema );
     sqlite3VdbeAddOp(v, OP_Integer, iDb, 0);
index af19681c524e722aef7218d77b45c538b7054544..c1eaec9bf02fed07c9ee12f174e8c75b9f7808d1 100644 (file)
@@ -12,7 +12,7 @@
 ** This file contains C code routines that are called by the parser
 ** to handle SELECT statements in SQLite.
 **
-** $Id: select.c,v 1.285 2006/01/05 14:22:34 danielk1977 Exp $
+** $Id: select.c,v 1.286 2006/01/07 13:21:04 danielk1977 Exp $
 */
 #include "sqliteInt.h"
 
@@ -2229,6 +2229,7 @@ static int simpleMinMaxQuery(Parse *pParse, Select *p, int eDest, int iParm){
   iCol = pExpr->iColumn;
   pTab = pSrc->a[0].pTab;
 
+
   /* If we get to here, it means the query is of the correct form.
   ** Check to make sure we have an index and make pIdx point to the
   ** appropriate index.  If the min() or max() is on an INTEGER PRIMARY
@@ -2266,11 +2267,12 @@ static int simpleMinMaxQuery(Parse *pParse, Select *p, int eDest, int iParm){
   */
   iDb = sqlite3SchemaToIndex(pParse->db, pTab->pSchema);
   sqlite3CodeVerifySchema(pParse, iDb);
+  sqlite3TableLock(pParse, iDb, pTab->tnum, 0, pTab->zName);
   base = pSrc->a[0].iCursor;
   brk = sqlite3VdbeMakeLabel(v);
   computeLimitRegisters(pParse, p, brk);
   if( pSrc->a[0].pSelect==0 ){
-    sqlite3OpenTableForReading(v, base, iDb, pTab);
+    sqlite3OpenTable(pParse, base, iDb, pTab, OP_OpenRead);
   }
   if( pIdx==0 ){
     sqlite3VdbeAddOp(v, seekOp, base, 0);
index c3fadd13537427989b3fe62c1d442e4c8ad6417b..e2c618ee165d7b48c213e848424e5e9df7c87ad3 100644 (file)
@@ -11,7 +11,7 @@
 *************************************************************************
 ** Internal interface definitions for SQLite.
 **
-** @(#) $Id: sqliteInt.h,v 1.452 2006/01/06 15:03:48 danielk1977 Exp $
+** @(#) $Id: sqliteInt.h,v 1.453 2006/01/07 13:21:04 danielk1977 Exp $
 */
 #ifndef _SQLITEINT_H_
 #define _SQLITEINT_H_
@@ -255,7 +255,7 @@ struct BusyHandler {
 #ifdef SQLITE_MEMDEBUG
 /*
 ** The following global variables are used for testing and debugging
-** only.  They only work if SQLITE_DEBUG is defined.
+** only.  They only work if SQLITE_MEMDEBUG is defined.
 */
 extern int sqlite3_nMalloc;      /* Number of sqliteMalloc() calls */
 extern int sqlite3_nFree;        /* Number of sqliteFree() calls */
@@ -264,19 +264,21 @@ extern int sqlite3_iMallocReset; /* Set iMallocFail to this when it reaches 0 */
 #define ENTER_MALLOC (\
   sqlite3Tsd()->zFile = __FILE__, sqlite3Tsd()->iLine = __LINE__ \
 )
-#define sqliteMalloc(x)        (ENTER_MALLOC, sqlite3Malloc(x))
-#define sqliteMallocRaw(x)     (ENTER_MALLOC, sqlite3MallocRaw(x))
-#define sqliteRealloc(x,y)     (ENTER_MALLOC, sqlite3Realloc(x,y))
-#define sqliteStrDup(x)        (ENTER_MALLOC, sqlite3StrDup(x))
-#define sqliteStrNDup(x,y)     (ENTER_MALLOC, sqlite3StrNDup(x,y))
+#define sqliteMalloc(x)          (ENTER_MALLOC, sqlite3Malloc(x))
+#define sqliteMallocRaw(x)       (ENTER_MALLOC, sqlite3MallocRaw(x))
+#define sqliteRealloc(x,y)       (ENTER_MALLOC, sqlite3Realloc(x,y))
+#define sqliteStrDup(x)          (ENTER_MALLOC, sqlite3StrDup(x))
+#define sqliteStrNDup(x,y)       (ENTER_MALLOC, sqlite3StrNDup(x,y))
+#define sqliteReallocOrFree(x,y) (ENTER_MALLOC, sqlite3ReallocOrFree(x,y))
 
 #else
 
-#define sqliteMalloc(x)        sqlite3Malloc(x)
-#define sqliteMallocRaw(x)     sqlite3MallocRaw(x)
-#define sqliteRealloc(x,y)     sqlite3Realloc(x,y)
-#define sqliteStrDup(x)        sqlite3StrDup(x)
-#define sqliteStrNDup(x,y)     sqlite3StrNDup(x,y)
+#define sqliteMalloc(x)          sqlite3Malloc(x)
+#define sqliteMallocRaw(x)       sqlite3MallocRaw(x)
+#define sqliteRealloc(x,y)       sqlite3Realloc(x,y)
+#define sqliteStrDup(x)          sqlite3StrDup(x)
+#define sqliteStrNDup(x,y)       sqlite3StrNDup(x,y)
+#define sqliteReallocOrFree(x,y) sqlite3ReallocOrFree(x,y)
 
 #endif
 
@@ -359,6 +361,7 @@ typedef struct Select Select;
 typedef struct SrcList SrcList;
 typedef struct SqliteTsd SqliteTsd;
 typedef struct Table Table;
+typedef struct TableLock TableLock;
 typedef struct Token Token;
 typedef struct TriggerStack TriggerStack;
 typedef struct TriggerStep TriggerStep;
@@ -1224,6 +1227,12 @@ struct Select {
 ** generate call themselves recursively, the first part of the structure
 ** is constant but the second part is reset at the beginning and end of
 ** each recursion.
+**
+** The nTableLock and aTableLock variables are only used if the shared-cache 
+** feature is enabled (if sqlite3Tsd()->useSharedData is true). They are
+** used to store the set of table-locks required by the statement being
+** compiled. Function sqlite3TableLock() is used to add entries to the
+** list.
 */
 struct Parse {
   sqlite3 *db;         /* The main database structure */
@@ -1243,6 +1252,10 @@ struct Parse {
   u32 cookieMask;      /* Bitmask of schema verified databases */
   int cookieGoto;      /* Address of OP_Goto to cookie verifier subroutine */
   int cookieValue[MAX_ATTACHED+2];  /* Values of cookies to verify */
+#ifndef SQLITE_OMIT_SHARED_CACHE
+  int nTableLock;        /* Number of locks in aTableLock */
+  TableLock *aTableLock; /* Required table locks for shared-cache mode */
+#endif
 
   /* Above is constant between recursions.  Below is reset before and after
   ** each recursion */
@@ -1505,7 +1518,7 @@ void sqlite3BeginParse(Parse*,int);
 void sqlite3RollbackInternalChanges(sqlite3*);
 void sqlite3CommitInternalChanges(sqlite3*);
 Table *sqlite3ResultSetOfSelect(Parse*,char*,Select*);
-void sqlite3OpenMasterTable(Vdbe *v, int);
+void sqlite3OpenMasterTable(Parse *, int);
 void sqlite3StartTable(Parse*,Token*,Token*,Token*,int,int,int);
 void sqlite3AddColumn(Parse*,Token*);
 void sqlite3AddNotNull(Parse*, int);
@@ -1546,8 +1559,7 @@ void sqlite3SelectDelete(Select*);
 void sqlite3SelectUnbind(Select*);
 Table *sqlite3SrcListLookup(Parse*, SrcList*);
 int sqlite3IsReadOnly(Parse*, Table*, int);
-void sqlite3OpenTableForReading(Vdbe*, int iCur, int iDb, Table*);
-void sqlite3OpenTable(Vdbe*, int iCur, Table*, int);
+void sqlite3OpenTable(Parse*, int iCur, int iDb, Table*, int);
 void sqlite3DeleteFrom(Parse*, SrcList*, Expr*);
 void sqlite3Update(Parse*, SrcList*, ExprList*, Expr*, int);
 WhereInfo *sqlite3WhereBegin(Parse*, SrcList*, Expr*, ExprList**);
@@ -1723,6 +1735,12 @@ void sqlite3SchemaFree(void *);
 DbSchema *sqlite3SchemaGet(Btree *);
 int sqlite3SchemaToIndex(sqlite3 *db, DbSchema *);
 
+#ifndef SQLITE_OMIT_SHARED_CACHE
+  void sqlite3TableLock(Parse *, int, int, u8, const char *);
+#else
+  #define sqlite3TableLock(v,w,x,y,z)
+#endif
+
 void sqlite3MallocClearFailed();
 #ifdef NDEBUG
   #define sqlite3MallocDisallow()
index b3294ef18294e41ed8e4c15b96e8f1e52d83c0ae..6ea3b68785132c71258be490d879b6121ed3e628 100644 (file)
@@ -15,7 +15,7 @@
 ** individual tokens and sends those tokens one-by-one over to the
 ** parser for analysis.
 **
-** $Id: tokenize.c,v 1.110 2005/12/09 20:02:06 drh Exp $
+** $Id: tokenize.c,v 1.111 2006/01/07 13:21:04 danielk1977 Exp $
 */
 #include "sqliteInt.h"
 #include "os.h"
@@ -425,6 +425,13 @@ abort_parse:
     sqlite3VdbeDelete(pParse->pVdbe);
     pParse->pVdbe = 0;
   }
+#ifndef SQLITE_OMIT_SHARED_CACHE
+  if( pParse->nested==0 ){
+    sqliteFree(pParse->aTableLock);
+    pParse->aTableLock = 0;
+    pParse->nTableLock = 0;
+  }
+#endif
   sqlite3DeleteTable(pParse->db, pParse->pNewTable);
   sqlite3DeleteTrigger(pParse->pNewTrigger);
   sqliteFree(pParse->apVarExpr);
index fcb852d95a71bf978f79b8d759fce06ddbdb8ff6..b88839335eed62ea67f52ef77ce3207339cacabd 100644 (file)
@@ -237,7 +237,7 @@ void sqlite3FinishTrigger(
     v = sqlite3GetVdbe(pParse);
     if( v==0 ) goto triggerfinish_cleanup;
     sqlite3BeginWriteOperation(pParse, 0, iDb);
-    sqlite3OpenMasterTable(v, iDb);
+    sqlite3OpenMasterTable(pParse, iDb);
     addr = sqlite3VdbeAddOpList(v, ArraySize(insertTrig), insertTrig);
     sqlite3VdbeChangeP3(v, addr+2, pTrig->name, 0); 
     sqlite3VdbeChangeP3(v, addr+3, pTrig->table, 0); 
@@ -520,7 +520,7 @@ void sqlite3DropTriggerPtr(Parse *pParse, Trigger *pTrigger, int nested){
     };
 
     sqlite3BeginWriteOperation(pParse, 0, iDb);
-    sqlite3OpenMasterTable(v, iDb);
+    sqlite3OpenMasterTable(pParse, iDb);
     base = sqlite3VdbeAddOpList(v,  ArraySize(dropTrigger), dropTrigger);
     sqlite3VdbeChangeP3(v, base+1, pTrigger->name, 0);
     sqlite3ChangeCookie(db, v, iDb);
index fd6edc60fbcb27a080dc840e6dd897862a65399e..0a47d1da502eca611e9efe121cf50a8caf6452cc 100644 (file)
@@ -12,7 +12,7 @@
 ** This file contains C code routines that are called by the parser
 ** to handle UPDATE statements.
 **
-** $Id: update.c,v 1.115 2006/01/05 11:34:34 danielk1977 Exp $
+** $Id: update.c,v 1.116 2006/01/07 13:21:04 danielk1977 Exp $
 */
 #include "sqliteInt.h"
 
@@ -309,7 +309,7 @@ void sqlite3Update(
       /* Open a cursor and make it point to the record that is
       ** being updated.
       */
-      sqlite3OpenTableForReading(v, iCur, iDb, pTab);
+      sqlite3OpenTable(pParse, iCur, iDb, pTab, OP_OpenRead);
     }
     sqlite3VdbeAddOp(v, OP_MoveGe, iCur, 0);
 
@@ -364,9 +364,7 @@ void sqlite3Update(
     ** action, then we need to open all indices because we might need
     ** to be deleting some records.
     */
-    sqlite3VdbeAddOp(v, OP_Integer, iDb, 0);
-    sqlite3VdbeAddOp(v, OP_OpenWrite, iCur, pTab->tnum);
-    sqlite3VdbeAddOp(v, OP_SetNumColumns, iCur, pTab->nCol);
+    sqlite3OpenTable(pParse, iCur, iDb, pTab, OP_OpenWrite); 
     if( onError==OE_Replace ){
       openAll = 1;
     }else{
index 83615842cc6d437d3cc45dd5ac513285cae9a6c0..bf5ac0991bab362469f9e8217c8e690856803d50 100644 (file)
@@ -43,7 +43,7 @@
 ** in this file for details.  If in doubt, do not deviate from existing
 ** commenting and indentation practices when changing or adding code.
 **
-** $Id: vdbe.c,v 1.513 2006/01/06 14:32:20 drh Exp $
+** $Id: vdbe.c,v 1.514 2006/01/07 13:21:04 danielk1977 Exp $
 */
 #include "sqliteInt.h"
 #include "os.h"
@@ -4437,6 +4437,38 @@ case OP_Expire: {        /* no-push */
   break;
 }
 
+#ifndef SQLITE_OMIT_SHARED_CACHE
+/* Opcode: TableLock P1 P2 P3
+**
+** Obtain a lock on a particular table. This instruction is only used when
+** the shared-cache feature is enabled. 
+**
+** If P1 is not negative, then it is the index of the index of the database
+** in sqlite3.aDb[] and a read-lock is required. If P1 is negative, a 
+** write-lock is required. In this case the index of the database is the 
+** absolute value of P1 minus one (iDb = abs(P1) - 1;) and a write-lock is
+** required. 
+**
+** P2 contains the root-page of the table to lock.
+**
+** P3 contains a pointer to the name of the table being locked. This is only
+** used to generate an error message if the lock cannot be obtained.
+*/
+case OP_TableLock: {        /* no-push */
+  int p1 = pOp->p1; 
+  u8 isWriteLock = (p1<0);
+  if( isWriteLock ){
+    p1 = (-1*p1)-1;
+  }
+  rc = sqlite3BtreeLockTable(db->aDb[p1].pBt, pOp->p2, isWriteLock);
+  if( rc==SQLITE_LOCKED ){
+    const char *z = (const char *)pOp->p3;
+    sqlite3SetString(&p->zErrMsg, "database table is locked: ", z, (char*)0);
+  }
+  break;
+}
+#endif /* SHARED_OMIT_SHARED_CACHE */
+
 /* An other opcode is illegal...
 */
 default: {
index 86d3ac3bc99c19be18afdbe7eb99242dc74ff1b0..587080bf92130d174b79c04ed61f0b194422217f 100644 (file)
@@ -16,7 +16,7 @@
 ** so is applicable.  Because this module is responsible for selecting
 ** indices, you might also think of this module as the "query optimizer".
 **
-** $Id: where.c,v 1.190 2006/01/05 11:34:34 danielk1977 Exp $
+** $Id: where.c,v 1.191 2006/01/07 13:21:04 danielk1977 Exp $
 */
 #include "sqliteInt.h"
 
@@ -1580,7 +1580,9 @@ WhereInfo *sqlite3WhereBegin(
     iDb = sqlite3SchemaToIndex(pParse->db, pTab->pSchema);
     if( pTab->isTransient || pTab->pSelect ) continue;
     if( (pLevel->flags & WHERE_IDX_ONLY)==0 ){
-      sqlite3OpenTableForReading(v, pTabItem->iCursor, iDb, pTab);
+      sqlite3OpenTable(pParse, pTabItem->iCursor, iDb, pTab, OP_OpenRead);
+    }else{
+      sqlite3TableLock(pParse, iDb, pTab->tnum, 0, pTab->zName);
     }
     pLevel->iTabCur = pTabItem->iCursor;
     if( (pIx = pLevel->pIdx)!=0 ){
index 35ada42dd251d050969579e52448f7448976197d..6929e50a0a8cf34f72202f543482669a485dda4c 100644 (file)
@@ -11,7 +11,7 @@
 # This file implements regression tests for SQLite library.  The
 # focus of this file is testing the SELECT statement.
 #
-# $Id: shared.test,v 1.5 2006/01/06 15:03:48 danielk1977 Exp $
+# $Id: shared.test,v 1.6 2006/01/07 13:21:04 danielk1977 Exp $
 
 set testdir [file dirname $argv0]
 source $testdir/tester.tcl
@@ -34,6 +34,7 @@ set ::enable_shared_cache [sqlite3_enable_shared_cache 1]
 # shared-4.*: Check that the schema is locked and unlocked correctly.
 # shared-5.*: Test that creating/dropping schema items works when databases
 #             are attached in different orders to different handles.
+# shared-6.*: Locking, UNION ALL queries and sub-queries.
 #
 
 do_test shared-1.1 {
@@ -74,7 +75,7 @@ do_test shared-1.4 {
   catchsql {
     INSERT INTO abc VALUES(4, 5, 6);
   } db2
-} {1 {database table is locked}}
+} {1 {database table is locked: abc}}
 do_test shared-1.5 {
   # Using connection 2 (the one without the open transaction), try to create
   # a new table. This should fail because of the open read transaction 
@@ -82,7 +83,7 @@ do_test shared-1.5 {
   catchsql {
     CREATE TABLE def(d, e, f);
   } db2
-} {1 {database table is locked}}
+} {1 {database table is locked: sqlite_master}}
 do_test shared-1.6 {
   # Upgrade connection 1's transaction to a write transaction. Create
   # a new table - def - and insert a row into it. Because the connection 1
@@ -134,7 +135,7 @@ do_test shared-2.2 {
   catchsql {
     INSERT INTO abc VALUES(1, 2, 3);
   } db2
-} {1 {database table is locked}}
+} {1 {database table is locked: abc}}
 do_test shared-2.3 {
   # Turn db's transaction into a write-transaction. db3 should still be
   # able to read from table def (but will not see the new row). Connection
@@ -158,7 +159,7 @@ do_test shared-2.3 {
   ] [
     catchsql { SELECT * FROM def; } db2
   ]
-} {0 {IV V VI} 1 {database table is locked}}
+} {0 {IV V VI} 1 {database table is locked: def}}
 do_test shared-2.4 {
   # Commit the open transaction on db. db2 still holds a read-transaction.
   # This should prevent db3 from writing to the database, but not from 
@@ -290,12 +291,12 @@ do_test shared-4.3.2 {
   catchsql {
     INSERT INTO abc VALUES('iv', 'v', 'vi');
   }
-} {1 {database table is locked}}
+} {1 {database table is locked: abc}}
 do_test shared-4.3.3 {
   catchsql {
     CREATE TABLE ghi(g, h, i);
   }
-} {1 {database table is locked}}
+} {1 {database table is locked: sqlite_master}}
 do_test shared-4.3.3 {
   catchsql {
     INSERT INTO def VALUES('IV', 'V', 'VI');
@@ -407,6 +408,60 @@ do_test shared-5.1.2 {
   } db1
 } {}
 
+#--------------------------------------------------------------------------
+# Tests shared-6.* test that a query obtains all the read-locks it needs
+# before starting execution of the query. This means that there is no chance
+# some rows of data will be returned before a lock fails and SQLITE_LOCK
+# is returned.
+#
+do_test shared-6.1.1 {
+  execsql {
+    CREATE TABLE t1(a, b);
+    CREATE TABLE t2(a, b);
+    INSERT INTO t1 VALUES(1, 2);
+    INSERT INTO t2 VALUES(3, 4);
+  } db1
+  execsql {
+    SELECT * FROM t1 UNION ALL SELECT * FROM t2;
+  } db2
+} {1 2 3 4}
+do_test shared-6.1.2 {
+  # Establish a write lock on table t2 via connection db2. Then make a 
+  # UNION all query using connection db1 that first accesses t1, followed 
+  # by t2. If the locks are grabbed at the start of the statement (as 
+  # they should be), no rows are returned. If (as was previously the case)
+  # they are grabbed as the tables are accessed, the t1 rows will be 
+  # returned before the query fails.
+  #
+  execsql {
+    BEGIN;
+    INSERT INTO t2 VALUES(5, 6);
+  } db2
+  set ret [list]
+  catch {
+    db1 eval {SELECT * FROM t1 UNION ALL SELECT * FROM t2} {
+      lappend ret $a $b
+    }
+  }
+  set ret
+} {}
+do_test shared-6.1.3 {
+  execsql {
+    COMMIT;
+    BEGIN;
+    INSERT INTO t1 VALUES(7, 8);
+  } db2
+  set ret [list]
+  catch {
+    db1 eval {
+      SELECT (CASE WHEN a>4 THEN (SELECT a FROM t1) ELSE 0 END) AS d FROM t2;
+    } {
+      lappend ret $d
+    }
+  }
+  set ret
+} {}
+
 catch {db1 close}
 catch {db2 close}