From c00da105654fb2bd0712acccac3e36b539805d08 Mon Sep 17 00:00:00 2001 From: danielk1977 Date: Sat, 7 Jan 2006 13:21:04 +0000 Subject: [PATCH] In shared-cache mode, lock all required tables before beginning to execute the body of the statement program. (CVS 2881) FossilOrigin-Name: 23b587b05b89727248805e6d9e5141e018cf2152 --- manifest | 40 +++++++++--------- manifest.uuid | 2 +- src/analyze.c | 13 +++++- src/btree.c | 25 +++++------ src/btree.h | 3 +- src/build.c | 106 +++++++++++++++++++++++++++++++++++++++++++++-- src/delete.c | 18 ++++---- src/insert.c | 15 ++----- src/select.c | 6 ++- src/sqliteInt.h | 48 ++++++++++++++------- src/tokenize.c | 9 +++- src/trigger.c | 4 +- src/update.c | 8 ++-- src/vdbe.c | 34 ++++++++++++++- src/where.c | 6 ++- test/shared.test | 69 ++++++++++++++++++++++++++---- 16 files changed, 310 insertions(+), 96 deletions(-) diff --git a/manifest b/manifest index 3b10934960..3c23686ee1 100644 --- 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 diff --git a/manifest.uuid b/manifest.uuid index e232b2ad10..b12a96548b 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -77ac231c0e21c09c0b612a4e72bcc863f2c95fd3 \ No newline at end of file +23b587b05b89727248805e6d9e5141e018cf2152 \ No newline at end of file diff --git a/src/analyze.c b/src/analyze.c index 3bcc8add41..4669c733b3 100644 --- a/src/analyze.c +++ b/src/analyze.c @@ -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 diff --git a/src/btree.c b/src/btree.c index 9847f72c80..9f0a2e7f55 100644 --- a/src/btree.c +++ b/src/btree.c @@ -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. diff --git a/src/btree.h b/src/btree.h index 6a65813c90..70bdbd467c 100644 --- a/src/btree.h +++ b/src/btree.h @@ -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 *); diff --git a/src/build.c b/src/build.c index 528058a597..494972b4b8 100644 --- a/src/build.c +++ b/src/build.c @@ -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 @@ -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; inTableLock; 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; inTableLock; 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 ){ diff --git a/src/delete.c b/src/delete.c index 9114304edb..9c5cf5175e 100644 --- a/src/delete.c +++ b/src/delete.c @@ -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); diff --git a/src/insert.c b/src/insert.c index 64bff8d5cf..dea6c6fc15 100644 --- a/src/insert.c +++ b/src/insert.c @@ -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); diff --git a/src/select.c b/src/select.c index af19681c52..c1eaec9bf0 100644 --- a/src/select.c +++ b/src/select.c @@ -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); diff --git a/src/sqliteInt.h b/src/sqliteInt.h index c3fadd1353..e2c618ee16 100644 --- a/src/sqliteInt.h +++ b/src/sqliteInt.h @@ -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() diff --git a/src/tokenize.c b/src/tokenize.c index b3294ef182..6ea3b68785 100644 --- a/src/tokenize.c +++ b/src/tokenize.c @@ -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); diff --git a/src/trigger.c b/src/trigger.c index fcb852d95a..b88839335e 100644 --- a/src/trigger.c +++ b/src/trigger.c @@ -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); diff --git a/src/update.c b/src/update.c index fd6edc60fb..0a47d1da50 100644 --- a/src/update.c +++ b/src/update.c @@ -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{ diff --git a/src/vdbe.c b/src/vdbe.c index 83615842cc..bf5ac0991b 100644 --- a/src/vdbe.c +++ b/src/vdbe.c @@ -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: { diff --git a/src/where.c b/src/where.c index 86d3ac3bc9..587080bf92 100644 --- a/src/where.c +++ b/src/where.c @@ -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 ){ diff --git a/test/shared.test b/test/shared.test index 35ada42dd2..6929e50a0a 100644 --- a/test/shared.test +++ b/test/shared.test @@ -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} -- 2.47.2