From d6af93a92c9292a0d596b8619ffd2ec4f589cffb Mon Sep 17 00:00:00 2001 From: dan Date: Wed, 9 Aug 2017 20:35:10 +0000 Subject: [PATCH] Add experimental sqlite3_open_v2() flag SQLITE_OPEN_REUSE_SCHEMA. For sharing identical in-memory schema objects between connections. FossilOrigin-Name: a625698048c05375f12ea8875892fdbf3518291f10917fe68dd2294280cd3b42 --- manifest | 46 ++++++++++--------- manifest.uuid | 2 +- src/alter.c | 4 +- src/analyze.c | 1 + src/build.c | 37 +++++++--------- src/callback.c | 105 +++++++++++++++++++++++++++++++++++++++++++- src/main.c | 4 ++ src/prepare.c | 34 ++++++++++++++ src/sqlite.h.in | 2 + src/sqliteInt.h | 7 +++ src/tclsqlite.c | 9 ++++ src/trigger.c | 3 +- src/vacuum.c | 1 + src/vdbe.h | 2 +- src/vdbeaux.c | 4 +- src/vtab.c | 2 +- test/reuse1.test | 94 +++++++++++++++++++++++++++++++++++++++ test/tclsqlite.test | 2 +- 18 files changed, 309 insertions(+), 50 deletions(-) create mode 100644 test/reuse1.test diff --git a/manifest b/manifest index fb5ecc63fa..29e51aef83 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Various\sbug\sfixes\sfor\sthe\snew\sLSM1\svirtual\stable\sdesign. -D 2017-08-09T19:27:24.812 +C Add\sexperimental\ssqlite3_open_v2()\sflag\sSQLITE_OPEN_REUSE_SCHEMA.\sFor\ssharing\nidentical\sin-memory\sschema\sobjects\sbetween\sconnections. +D 2017-08-09T20:35:10.397 F Makefile.in d9873c9925917cca9990ee24be17eb9613a668012c85a343aef7e5536ae266e8 F Makefile.linux-gcc 7bc79876b875010e8c8f9502eb935ca92aa3c434 F Makefile.msc 02b469e9dcd5b7ee63fc1fb05babc174260ee4cfa4e0ef2e48c3c6801567a016 @@ -388,8 +388,8 @@ F spec.template 86a4a43b99ebb3e75e6b9a735d5fd293a24e90ca F sqlite.pc.in 42b7bf0d02e08b9e77734a47798d1a55a9e0716b F sqlite3.1 fc7ad8990fc8409983309bb80de8c811a7506786 F sqlite3.pc.in 48fed132e7cb71ab676105d2a4dc77127d8c1f3a -F src/alter.c cf7a8af45cb0ace672f47a1b29ab24092a9e8cd8d945a9974e3b5d925f548594 -F src/analyze.c 0d0ccf7520a201d8747ea2f02c92c26e26f801bc161f714f27b9f7630dde0421 +F src/alter.c 748950c55b18099a82a9ee4da59272fa3ce935dc7edbdf22d3c84cbb56a31463 +F src/analyze.c a40d5a3012254c5d921a02a7c2eedf96118f541eb0a866dcdb6b6f2d64a03e43 F src/attach.c 07b706e336fd3cedbd855e1f8266d10e82fecae07daf86717b5760cd7784c584 F src/auth.c 79f96c6f33bf0e5da8d1c282cee5ebb1852bb8a6ccca3e485d7c459b035d9c3c F src/backup.c faf17e60b43233c214aae6a8179d24503a61e83b @@ -398,8 +398,8 @@ F src/btmutex.c 0e9ce2d56159b89b9bc8e197e023ee11e39ff8ca F src/btree.c 1a17ba1a765d80c3ca39ce33ff55f92e1f51eb84bbbdab5377f11d36b1515fa1 F src/btree.h 3edc5329bc59534d2d15b4f069a9f54b779a7e51289e98fa481ae3c0e526a5ca F src/btreeInt.h 97700795edf8a43245720414798b7b29d8e465aef46bf301ffacd431910c0da1 -F src/build.c 33b0f6055bd990ed052b96e71368acefcd98daa21ccf21f91aa90e8b769c2219 -F src/callback.c 930648a084a3adc741c6471adfbdc50ba47ba3542421cb80a26f259f467de65e +F src/build.c 5b857c4302cb4b333beabfca7f4e79aa9705984cc77a3f09d2bd9cea4a58f855 +F src/callback.c cc17a561f8035b50e3ded141d1ceb582ae12cb79d879adcc43c6a3f28a698a57 F src/complete.c a3634ab1e687055cd002e11b8f43eb75c17da23e F src/ctime.c ff1be3eed7bdd75aaca61ca8dc848f7c9f850ef2fb9cb56f2734e922a098f9c0 F src/date.c 48f743d88bbe88f848532d333cca84f26e52a4f217e86f86be7fc1b919c33d74 @@ -417,7 +417,7 @@ F src/in-operator.md 10cd8f4bcd225a32518407c2fb2484089112fd71 F src/insert.c d2d1bf12d2b5382450620d7cede84c7ffe57e6a89fa9a908f1aba68df2731cd9 F src/legacy.c 134ab3e3fae00a0f67a5187981d6935b24b337bcf0f4b3e5c9fa5763da95bf4e F src/loadext.c 20865b183bb8a3723d59cf1efffc3c50217eb452c1021d077b908c94da26b0b2 -F src/main.c 42f6a2660c7a1d643cc7e863d2dcd630c6aa1e8343f5478b0592120ab84c97ba +F src/main.c bd53babb6dbe4df7e601b79bb1a9c1cc3ca0f135bc62f5151a2ebebc9ad6f5c4 F src/malloc.c e20bb2b48abec52d3faf01cce12e8b4f95973755fafec98d45162dfdab111978 F src/mem0.c 6a55ebe57c46ca1a7d98da93aaa07f99f1059645 F src/mem1.c c12a42539b1ba105e3707d0e628ad70e611040d8f5e38cf942cee30c867083de @@ -447,7 +447,7 @@ F src/pcache.h 521bb9610d38ef17a3cc9b5ddafd4546c2ea67fa3d0e464823d73c2a28d50e11 F src/pcache1.c 0b793738b5dddaf0a645784835c6b5557b1ecfaee339af9c26810c6ecdb273aa F src/pragma.c cd6aeda3587be6c5c08f9b2d45eae6068666a03c9d077c8c43cdb85fb0aa70f2 F src/pragma.h bb83728944b42f6d409c77f5838a8edbdb0fe83046c5496ffc9602b40340a324 -F src/prepare.c 3cbb99757d7295997674972f9dd2331c5c544368854ca08954c9beb1e9b6145a +F src/prepare.c 955b618566c42ddb28a38bd80e804c477bc2eda5ebc474a1e152847a6013d38d F src/printf.c 8757834f1b54dae512fb25eb1acc8e94a0d15dd2290b58f2563f65973265adb2 F src/random.c 80f5d666f23feb3e6665a6ce04c7197212a88384 F src/resolve.c 4324a94573b1e29286f8121e4881db59eaedc014afeb274c8d3e07ed282e0e20 @@ -455,14 +455,14 @@ F src/rowset.c 7b7e7e479212e65b723bf40128c7b36dc5afdfac F src/select.c 3fd19c98c5223d411b883502d1ac928ddb762a1ea8f031d910210316545fc67c F src/shell.c bd6a37cbe8bf64ef6a6a74fdc50f067d3148149b4ce2b4d03154663e66ded55f F src/shell.c.in b5725acacba95ccefa57b6d068f710e29ba8239c3aa704628a1902a1f729c175 -F src/sqlite.h.in 72f1775c7a134f9e358eedafe1ebc703c28b0d705d976464ddbf6a9219448952 +F src/sqlite.h.in e1574e78bfa86255774679106ac63e5a5ed732535930257acefd1855a0471e29 F src/sqlite3.rc 5121c9e10c3964d5755191c80dd1180c122fc3a8 F src/sqlite3ext.h a1fd3aa82f967da436164e0728a7d6841651fd0c6e27b9044e0eb9f6c8462e47 -F src/sqliteInt.h 07e4d3c8021aea80e3bbafab4dd52833cfcfa4f000210af0d15c7fdaed2f09fc +F src/sqliteInt.h 21a575b64a829fe1951030bab40135078ab0a152aea45535b2ad3057f31ee009 F src/sqliteLimit.h 1513bfb7b20378aa0041e7022d04acb73525de35b80b252f1b83fedb4de6a76b F src/status.c a9e66593dfb28a9e746cba7153f84d49c1ddc4b1 F src/table.c b46ad567748f24a326d9de40e5b9659f96ffff34 -F src/tclsqlite.c 487951d81f9704800fd9f0ffdaa2f935a83ccb6be3575c2c4ef83e4789b4c828 +F src/tclsqlite.c e117575fed5cef7c2dc60d53eb7003578c67aaf50bde9cc8e83ba876450a2ce5 F src/test1.c 8513b17ca4a7a9ba28748535d178b6e472ec7394ae0eea53907f2d3bcdbab2df F src/test2.c 3efb99ab7f1fc8d154933e02ae1378bac9637da5 F src/test3.c b8434949dfb8aff8dfa082c8b592109e77844c2135ed3c492113839b6956255b @@ -515,21 +515,21 @@ F src/test_wsd.c 41cadfd9d97fe8e3e4e44f61a4a8ccd6f7ca8fe9 F src/threads.c 4ae07fa022a3dc7c5beb373cf744a85d3c5c6c3c F src/tokenize.c 1003d6d90c6783206c711f0a9397656fa5b055209f4d092caa43bb3bf5215db5 F src/treeview.c 2ee4a5dada213d5ab08a742af5c876cee6f1aaae65f10a61923f3fb63846afef -F src/trigger.c 48e0f7ed6749ce4d50a695e09e20ce9cf84ecabf2691852c965a51e0b620eccc +F src/trigger.c bda24e1772804abd4db550f60bc3be3ef2b09248589362163772b257054a9270 F src/update.c c443935c652af9365e033f756550b5032d02e1b06eb2cb890ed7511ae0c051dc F src/utf.c 810fbfebe12359f10bc2a011520a6e10879ab2a163bcb26c74768eab82ea62a5 F src/util.c fc081ec6f63448dcd80d3dfad35baecfa104823254a815b081a4d9fe76e1db23 -F src/vacuum.c 90839322fd5f00df9617eb21b68beda9b6e2a2937576b0d65985e4aeb1c53739 +F src/vacuum.c c31a0fe4ca640e0aabf483bc2c65065b41622a3f6e820cd83817cce3e55a2581 F src/vdbe.c 821b3edde2d17ec60da0617db1018a88f38634c359d22f3c8f7be10336c82636 -F src/vdbe.h d50cadf12bcf9fb99117ef392ce1ea283aa429270481426b6e8b0280c101fd97 +F src/vdbe.h 5aa766d2c95fd47a2d5d41e183ee052d21d3d8c3181c2255a56eac15aa6d62b4 F src/vdbeInt.h ff2b7db0968d20e6184aee256d2e535d565f5a172e3588a78adb166a41fc4911 F src/vdbeapi.c 05d6b14ab73952db0d73f6452d6960216997bd966a710266b2fe051f25326abc -F src/vdbeaux.c 1bef372f59f9e1dba5ead70cc5c24bf978bab0b9fdc2f69692afaa3a2d4dd8f3 +F src/vdbeaux.c c30be9a7030f62418b3159a950702aff49839b43130f1ab25fc06b77261d76a7 F src/vdbeblob.c db3cf91060f6f4b2f1358a4200e844697990752177784c7c95da00b7ac9f1c7b F src/vdbemem.c b7fac20534c79b7554dab2e8a180c585a8bc1b9c85149d1b2d9746cf314d06ed F src/vdbesort.c fea2bea25f5e9ccd91e0760d7359f0365f9fba1aaeac7216c71cad78765f58e3 F src/vdbetrace.c 41963d5376f0349842b5fc4aaaaacd7d9cdc0834 -F src/vtab.c a305582d3a6c7090982ac1610b8e5724fa954d8b28899fa6c633cb4de9c2f8ee +F src/vtab.c 33ee6b2afdd43379b068e570fc88b8b8d4364ad02fcea5f3bc4d1d4dc0a33ae2 F src/vxworks.h d2988f4e5a61a4dfe82c6524dd3d6e4f2ce3cdb9 F src/wal.c 40c543f0a2195d1b0dc88ef12142bea690009344 F src/wal.h 06b2a0b599cc0f53ea97f497cf8c6b758c999f71 @@ -1115,6 +1115,7 @@ F test/regexp2.test 40e894223b3d6672655481493f1be12012f2b33c F test/reindex.test 44edd3966b474468b823d481eafef0c305022254 F test/releasetest.tcl 7bb585433ce7fb2a2c255ae4b5e24f1bc27fe177ec1120f886cc4852f48f5ee9 x F test/resolver01.test f4022acafda7f4d40eca94dbf16bc5fc4ac30ceb +F test/reuse1.test 04ae701b4000113fedc472719e4cf4bb1d34d22dcaf8d3e643d41d1256a9590d F test/rollback.test f580934279800d480a19176c6b44909df31ce7ad45267ea475a541daa522f3d3 F test/rollback2.test 8435d6ff0f13f51d2a4181c232e706005fa90fc5 F test/rollbackfault.test 0e646aeab8840c399cfbfa43daab46fd609cf04a @@ -1244,7 +1245,7 @@ F test/tabfunc01.test c47171c36b3d411df2bd49719dcaa5d034f8d277477fd41d253940723b F test/table.test b708f3e5fa2542fa51dfab21fc07b36ea445cb2f F test/tableapi.test 2674633fa95d80da917571ebdd759a14d9819126 F test/tableopts.test dba698ba97251017b7c80d738c198d39ab747930 -F test/tclsqlite.test c3d7ac9449634b9f17fd048a3c0212e88a7448be810a9c5bd051acc1ffa00d2f +F test/tclsqlite.test d1fc287635da11f60b9243022b2332b667fbb9bdfb3d22b884a2e7d506afffd1 F test/tempdb.test bd92eba8f20e16a9136e434e20b280794de3cdb6 F test/tempdb2.test 27e41ed540b2f9b056c2e77e9bddc1b875358507 F test/tempfault.test 0c0d349c9a99bf5f374655742577f8712c647900 @@ -1644,7 +1645,10 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P 313df946668b943b0a9a9a91fd7bafa7212d05765c7714fa6c0de46aa9062a74 -R 51c1f235e2778744da3264339c3e2f94 -U drh -Z d3fda50f76603a97e7bd0f2862a316b1 +P 94434a252f0f2b57f325fd8fb82534f20cc1340ff13076cd88deeb47740ef6a2 +R ed2f8a5054bbb201ae29fcc7757dba48 +T *branch * reuse-schema +T *sym-reuse-schema * +T -sym-trunk * +U dan +Z 6d545754cdf84dd62577616fb01f7f9c diff --git a/manifest.uuid b/manifest.uuid index fbd3774187..fe6f444d26 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -94434a252f0f2b57f325fd8fb82534f20cc1340ff13076cd88deeb47740ef6a2 \ No newline at end of file +a625698048c05375f12ea8875892fdbf3518291f10917fe68dd2294280cd3b42 \ No newline at end of file diff --git a/src/alter.c b/src/alter.c index 51d4a40067..b1aedd3a75 100644 --- a/src/alter.c +++ b/src/alter.c @@ -354,14 +354,14 @@ static void reloadTableSchema(Parse *pParse, Table *pTab, const char *zName){ /* Reload the table, index and permanent trigger schemas. */ zWhere = sqlite3MPrintf(pParse->db, "tbl_name=%Q", zName); if( !zWhere ) return; - sqlite3VdbeAddParseSchemaOp(v, iDb, zWhere); + sqlite3VdbeAddParseSchemaOp(pParse, iDb, zWhere); #ifndef SQLITE_OMIT_TRIGGER /* Now, if the table is not stored in the temp database, reload any temp ** triggers. Don't use IN(...) in case SQLITE_OMIT_SUBQUERY is defined. */ if( (zWhere=whereTempTriggers(pParse, pTab))!=0 ){ - sqlite3VdbeAddParseSchemaOp(v, 1, zWhere); + sqlite3VdbeAddParseSchemaOp(pParse, 1, zWhere); } #endif } diff --git a/src/analyze.c b/src/analyze.c index 495cc954ac..6a28e16c3e 100644 --- a/src/analyze.c +++ b/src/analyze.c @@ -1314,6 +1314,7 @@ static void analyzeDatabase(Parse *pParse, int iDb){ int iMem; int iTab; + sqlite3SchemaWritable(pParse, iDb); sqlite3BeginWriteOperation(pParse, 0, iDb); iStatCur = pParse->nTab; pParse->nTab += 3; diff --git a/src/build.c b/src/build.c index 074041b3f0..f60f3a62b1 100644 --- a/src/build.c +++ b/src/build.c @@ -517,23 +517,19 @@ void sqlite3CollapseDatabaseArray(sqlite3 *db){ ** TEMP schema. */ void sqlite3ResetOneSchema(sqlite3 *db, int iDb){ - Db *pDb; - assert( iDbnDb ); - - /* Case 1: Reset the single schema identified by iDb */ - pDb = &db->aDb[iDb]; - assert( sqlite3SchemaMutexHeld(db, iDb, 0) ); + /* If any database other than TEMP is reset, then also reset TEMP + ** since TEMP might be holding triggers that reference tables in the + ** other database. */ + Db *pDb = &db->aDb[1]; assert( pDb->pSchema!=0 ); sqlite3SchemaClear(pDb->pSchema); - /* If any database other than TEMP is reset, then also reset TEMP - ** since TEMP might be holding triggers that reference tables in the - ** other database. - */ + assert( iDbnDb ); + assert( sqlite3SchemaMutexHeld(db, iDb, 0) ); + assert( db->aDb[iDb].pSchema!=0 ); + if( iDb!=1 ){ - pDb = &db->aDb[1]; - assert( pDb->pSchema!=0 ); - sqlite3SchemaClear(pDb->pSchema); + sqlite3SchemaUnuse(db, iDb); } return; } @@ -545,12 +541,11 @@ void sqlite3ResetOneSchema(sqlite3 *db, int iDb){ void sqlite3ResetAllSchemasOfConnection(sqlite3 *db){ int i; sqlite3BtreeEnterAll(db); - for(i=0; inDb; i++){ - Db *pDb = &db->aDb[i]; - if( pDb->pSchema ){ - sqlite3SchemaClear(pDb->pSchema); - } + for(i=0; inDb; i = (i?i+1:2)){ + sqlite3SchemaUnuse(db, i); } + sqlite3SchemaClear(db->aDb[1].pSchema); + db->mDbFlags &= ~DBFLAG_SchemaChange; sqlite3VtabUnlockList(db); sqlite3BtreeLeaveAll(db); @@ -2038,7 +2033,7 @@ void sqlite3EndTable( #endif /* Reparse everything to update our internal data structures */ - sqlite3VdbeAddParseSchemaOp(v, iDb, + sqlite3VdbeAddParseSchemaOp(pParse, iDb, sqlite3MPrintf(db, "tbl_name='%q' AND type!='trigger'", p->zName)); } @@ -2533,6 +2528,7 @@ void sqlite3DropTable(Parse *pParse, SrcList *pName, int isView, int noErr){ } iDb = sqlite3SchemaToIndex(db, pTab->pSchema); assert( iDb>=0 && iDbnDb ); + sqlite3SchemaWritable(pParse, iDb); /* If pTab is a virtual table, call ViewGetColumnNames() to ensure ** it is initialized. @@ -3396,7 +3392,7 @@ void sqlite3CreateIndex( if( pTblName ){ sqlite3RefillIndex(pParse, pIndex, iMem); sqlite3ChangeCookie(pParse, iDb); - sqlite3VdbeAddParseSchemaOp(v, iDb, + sqlite3VdbeAddParseSchemaOp(pParse, iDb, sqlite3MPrintf(db, "name='%q' AND type='index'", pIndex->zName)); sqlite3VdbeAddOp0(v, OP_Expire); } @@ -3515,6 +3511,7 @@ void sqlite3DropIndex(Parse *pParse, SrcList *pName, int ifExists){ goto exit_drop_index; } iDb = sqlite3SchemaToIndex(db, pIndex->pSchema); + sqlite3SchemaWritable(pParse, iDb); #ifndef SQLITE_OMIT_AUTHORIZATION { int code = SQLITE_DROP_INDEX; diff --git a/src/callback.c b/src/callback.c index 10505414c1..217c83a031 100644 --- a/src/callback.c +++ b/src/callback.c @@ -461,13 +461,115 @@ void sqlite3SchemaClear(void *p){ } } +/* +** Global linked list of sharable Schema objects. Read and write access must +** be protected by the SQLITE_MUTEX_STATIC_MASTER mutex. +*/ +static Schema *SQLITE_WSD sharedSchemaList = 0; + +/* +** Check that the schema of db iDb is writable (either because it is the temp +** db schema or because the db handle was opened without +** SQLITE_OPEN_REUSE_SCHEMA). If so, do nothing. Otherwise, leave an +** error in the Parse object. +*/ +void sqlite3SchemaWritable(Parse *pParse, int iDb){ + if( iDb!=1 && (pParse->db->openFlags & SQLITE_OPEN_REUSE_SCHEMA) ){ + sqlite3ErrorMsg(pParse, "attempt to modify read-only schema"); + } +} + +/* +** Replace the Schema object currently associated with database iDb with +** an empty schema object, ready to be populated. If there are multiple +** users of the Schema, this means decrementing the current objects ref +** count and replacing it with a pointer to a new, empty, object. Or, if +** the database handle passed as the first argument of the schema object +** is the only user, remove it from the shared list (if applicable) and +** clear it in place. +*/ +void sqlite3SchemaUnuse(sqlite3 *db, int iDb){ + Db *pDb = &db->aDb[iDb]; + Schema *pSchema; + assert( iDb!=1 ); + if( (pSchema = pDb->pSchema) ){ + + if( (db->openFlags & SQLITE_OPEN_REUSE_SCHEMA) ){ + sqlite3_mutex_enter( sqlite3_mutex_alloc(SQLITE_MUTEX_STATIC_MASTER) ); + assert( pSchema->nRef>=1 ); + if( pSchema->nRef==1 ){ + Schema **pp; + for(pp=&sharedSchemaList; (*pp); pp=&(*pp)->pNext){ + if( *pp==pSchema ){ + *pp = pSchema->pNext; + break; + } + } + pSchema->pNext = 0; + }else{ + assert( db->openFlags & SQLITE_OPEN_REUSE_SCHEMA ); + pSchema->nRef--; + pDb->pSchema = pSchema = 0; + } + sqlite3_mutex_leave( sqlite3_mutex_alloc(SQLITE_MUTEX_STATIC_MASTER) ); + } + + if( pSchema==0 ){ + db->aDb[iDb].pSchema = sqlite3SchemaGet(db, 0); + }else{ + sqlite3SchemaClear(pSchema); + } + } +} + +/* +** The schema for database iDb of database handle db, which was opened +** with SQLITE_OPEN_REUSE_SCHEMA, has just been parsed. This function +** checks the global list (sharedSchemaList) for a matching schema and, +** if one is found, frees the newly parsed Schema object and adds a pointer +** to the existing shared schema in its place. Or, if there is no matching +** schema in the list, then the new schema is added to it. +*/ +void sqlite3SchemaReuse(sqlite3 *db, int iDb){ + Schema *pSchema = db->aDb[iDb].pSchema; + Schema *p; + assert( pSchema && iDb!=1 ); + + sqlite3_mutex_enter( sqlite3_mutex_alloc(SQLITE_MUTEX_STATIC_MASTER) ); + for(p=sharedSchemaList; p; p=p->pNext){ + if( p->cksum==pSchema->cksum + && p->schema_cookie==pSchema->schema_cookie + ){ + break; + } + } + if( !p ){ + /* No matching schema was found. */ + pSchema->pNext = sharedSchemaList; + sharedSchemaList = pSchema; + }else{ + /* Found a matching schema. Increase its ref count. */ + p->nRef++; + } + sqlite3_mutex_leave( sqlite3_mutex_alloc(SQLITE_MUTEX_STATIC_MASTER) ); + + /* If a matching schema was found in the shared schema list, free the + ** schema object just parsed, and add a pointer to the matching schema + ** to the db handle. */ + if( p ){ + sqlite3SchemaClear(pSchema); + sqlite3DbFree(0, pSchema); + db->aDb[iDb].pSchema = p; + } +} + /* ** Find and return the schema associated with a BTree. Create ** a new one if necessary. */ Schema *sqlite3SchemaGet(sqlite3 *db, Btree *pBt){ Schema * p; - if( pBt ){ + if( pBt && (db->openFlags & SQLITE_OPEN_REUSE_SCHEMA)==0 ){ p = (Schema *)sqlite3BtreeSchema(pBt, sizeof(Schema), sqlite3SchemaClear); }else{ p = (Schema *)sqlite3DbMallocZero(0, sizeof(Schema)); @@ -480,6 +582,7 @@ Schema *sqlite3SchemaGet(sqlite3 *db, Btree *pBt){ sqlite3HashInit(&p->trigHash); sqlite3HashInit(&p->fkeyHash); p->enc = SQLITE_UTF8; + p->nRef = 1; } return p; } diff --git a/src/main.c b/src/main.c index 2d81358370..10cb5cef8a 100644 --- a/src/main.c +++ b/src/main.c @@ -1157,6 +1157,10 @@ void sqlite3LeaveMutexAndCloseZombie(sqlite3 *db){ sqlite3BtreeClose(pDb->pBt); pDb->pBt = 0; if( j!=1 ){ + if( db->openFlags & SQLITE_OPEN_REUSE_SCHEMA ){ + sqlite3SchemaUnuse(db, j); + sqlite3DbFree(0, pDb->pSchema); + } pDb->pSchema = 0; } } diff --git a/src/prepare.c b/src/prepare.c index 4fa59e5bef..b0e1d0bce7 100644 --- a/src/prepare.c +++ b/src/prepare.c @@ -36,6 +36,26 @@ static void corruptSchema( pData->rc = db->mallocFailed ? SQLITE_NOMEM_BKPT : SQLITE_CORRUPT_BKPT; } +/* +** Update the Schema.cksum checksum to account for the database object +** specified by the three arguments following the first. +*/ +void schemaUpdateChecksum( + Schema *pSchema, /* Schema object being parsed */ + const char *zName, /* Name of new database object */ + const char *zRoot, /* Root page of new database object */ + const char *zSql /* SQL used to create new database object */ +){ + int i; + u64 cksum = pSchema->cksum; + if( zName ){ + for(i=0; zName[i]; i++) cksum += (cksum<<3) + zName[i]; + } + if( zRoot ) for(i=0; zRoot[i]; i++) cksum += (cksum<<3) + zRoot[i]; + if( zSql ) for(i=0; zSql[i]; i++) cksum += (cksum<<3) + zSql[i]; + pSchema->cksum = cksum; +} + /* ** This is the callback routine for the code that initializes the ** database. See sqlite3Init() below for additional information. @@ -121,6 +141,16 @@ int sqlite3InitCallback(void *pInit, int argc, char **argv, char **NotUsed){ corruptSchema(pData, argv[0], "invalid rootpage"); } } + + if( iDb!=1 && (db->openFlags & SQLITE_OPEN_REUSE_SCHEMA) ){ + /* If this schema might be used by multiple connections, ensure that + ** the affinity string is allocated here. Otherwise, there might be + ** a race condition where two threads attempt to allocate it + ** simultaneously. */ + Index *pIndex = sqlite3FindIndex(db, argv[0], db->aDb[iDb].zDbSName); + if( pIndex ) sqlite3IndexAffinityStr(db, pIndex); + schemaUpdateChecksum(db->aDb[iDb].pSchema, argv[0], argv[1], argv[2]); + } return 0; } @@ -325,6 +355,10 @@ static int sqlite3InitOne(sqlite3 *db, int iDb, char **pzErrMsg){ rc = SQLITE_OK; } + if( iDb!=1 && (db->openFlags & SQLITE_OPEN_REUSE_SCHEMA) ){ + sqlite3SchemaReuse(db, iDb); + } + /* Jump here for an error that occurs after successfully allocating ** curMain and calling sqlite3BtreeEnter(). For an error that occurs ** before that point, jump to error_out. diff --git a/src/sqlite.h.in b/src/sqlite.h.in index ad97b9a0de..33e4b3b508 100644 --- a/src/sqlite.h.in +++ b/src/sqlite.h.in @@ -555,6 +555,8 @@ int sqlite3_exec( #define SQLITE_OPEN_WAL 0x00080000 /* VFS only */ /* Reserved: 0x00F00000 */ +#define SQLITE_OPEN_REUSE_SCHEMA 0x01000000 /* Ok for sqlite3_open_v2() */ + /* ** CAPI3REF: Device Characteristics diff --git a/src/sqliteInt.h b/src/sqliteInt.h index 7222fcda94..a5e19f39ae 100644 --- a/src/sqliteInt.h +++ b/src/sqliteInt.h @@ -1189,6 +1189,10 @@ struct Schema { u8 enc; /* Text encoding used by this database */ u16 schemaFlags; /* Flags associated with this schema */ int cache_size; /* Number of pages to use in the cache */ + + int nRef; /* Number of connections using this schema */ + u64 cksum; /* Checksum for this database schema */ + Schema *pNext; /* Next schema in shared schema list */ }; /* @@ -4085,6 +4089,9 @@ void sqlite3DefaultRowEst(Index*); void sqlite3RegisterLikeFunctions(sqlite3*, int); int sqlite3IsLikeFunction(sqlite3*,Expr*,int*,char*); void sqlite3SchemaClear(void *); +void sqlite3SchemaUnuse(sqlite3*, int); +void sqlite3SchemaReuse(sqlite3*, int); +void sqlite3SchemaWritable(Parse*, int); Schema *sqlite3SchemaGet(sqlite3 *, Btree *); int sqlite3SchemaToIndex(sqlite3 *db, Schema *); KeyInfo *sqlite3KeyInfoAlloc(sqlite3*,int,int); diff --git a/src/tclsqlite.c b/src/tclsqlite.c index 1b9f91405e..dbed46a437 100644 --- a/src/tclsqlite.c +++ b/src/tclsqlite.c @@ -3428,6 +3428,14 @@ static int SQLITE_TCLAPI DbMain( }else{ flags &= ~SQLITE_OPEN_URI; } + }else if( strcmp(zArg, "-reuse-schema")==0 ){ + int b; + if( Tcl_GetBooleanFromObj(interp, objv[i+1], &b) ) return TCL_ERROR; + if( b ){ + flags |= SQLITE_OPEN_REUSE_SCHEMA; + }else{ + flags &= ~SQLITE_OPEN_REUSE_SCHEMA; + } }else{ Tcl_AppendResult(interp, "unknown option: ", zArg, (char*)0); return TCL_ERROR; @@ -3437,6 +3445,7 @@ static int SQLITE_TCLAPI DbMain( Tcl_WrongNumArgs(interp, 1, objv, "HANDLE FILENAME ?-vfs VFSNAME? ?-readonly BOOLEAN? ?-create BOOLEAN?" " ?-nomutex BOOLEAN? ?-fullmutex BOOLEAN? ?-uri BOOLEAN?" + " ?-reuse-schema BOOLEAN?" #if defined(SQLITE_HAS_CODEC) && !defined(SQLITE_OMIT_CODEC_FROM_TCL) " ?-key CODECKEY?" #endif diff --git a/src/trigger.c b/src/trigger.c index 2a4fdd8ed4..450ebfff67 100644 --- a/src/trigger.c +++ b/src/trigger.c @@ -313,7 +313,7 @@ void sqlite3FinishTrigger( pTrig->table, z); sqlite3DbFree(db, z); sqlite3ChangeCookie(pParse, iDb); - sqlite3VdbeAddParseSchemaOp(v, iDb, + sqlite3VdbeAddParseSchemaOp(pParse, iDb, sqlite3MPrintf(db, "type='trigger' AND name='%q'", zName)); } @@ -538,6 +538,7 @@ void sqlite3DropTriggerPtr(Parse *pParse, Trigger *pTrigger){ iDb = sqlite3SchemaToIndex(pParse->db, pTrigger->pSchema); assert( iDb>=0 && iDbnDb ); + sqlite3SchemaWritable(pParse, iDb); pTable = tableOfTrigger(pTrigger); assert( pTable ); assert( pTable->pSchema==pTrigger->pSchema || iDb==1 ); diff --git a/src/vacuum.c b/src/vacuum.c index fde08ddc2a..133cf63c56 100644 --- a/src/vacuum.c +++ b/src/vacuum.c @@ -117,6 +117,7 @@ void sqlite3Vacuum(Parse *pParse, Token *pNm){ #endif } if( iDb!=1 ){ + sqlite3SchemaWritable(pParse, iDb); sqlite3VdbeAddOp1(v, OP_Vacuum, iDb); sqlite3VdbeUsesBtree(v, iDb); } diff --git a/src/vdbe.h b/src/vdbe.h index 3e77eb9db5..889ca549a3 100644 --- a/src/vdbe.h +++ b/src/vdbe.h @@ -197,7 +197,7 @@ void sqlite3VdbeEndCoroutine(Vdbe*,int); # define sqlite3VdbeVerifyNoResultRow(A) #endif VdbeOp *sqlite3VdbeAddOpList(Vdbe*, int nOp, VdbeOpList const *aOp, int iLineno); -void sqlite3VdbeAddParseSchemaOp(Vdbe*,int,char*); +void sqlite3VdbeAddParseSchemaOp(Parse*,int,char*); void sqlite3VdbeChangeOpcode(Vdbe*, u32 addr, u8); void sqlite3VdbeChangeP1(Vdbe*, u32 addr, int P1); void sqlite3VdbeChangeP2(Vdbe*, u32 addr, int P2); diff --git a/src/vdbeaux.c b/src/vdbeaux.c index 226b2152ed..efa32d1ee4 100644 --- a/src/vdbeaux.c +++ b/src/vdbeaux.c @@ -309,8 +309,10 @@ int sqlite3VdbeAddOp4Dup8( ** The zWhere string must have been obtained from sqlite3_malloc(). ** This routine will take ownership of the allocated memory. */ -void sqlite3VdbeAddParseSchemaOp(Vdbe *p, int iDb, char *zWhere){ +void sqlite3VdbeAddParseSchemaOp(Parse *pParse, int iDb, char *zWhere){ + Vdbe *p = pParse->pVdbe; int j; + sqlite3SchemaWritable(pParse, iDb); sqlite3VdbeAddOp4(p, OP_ParseSchema, iDb, 0, 0, zWhere, P4_DYNAMIC); for(j=0; jdb->nDb; j++) sqlite3VdbeUsesBtree(p, j); } diff --git a/src/vtab.c b/src/vtab.c index cb76b98f14..917c26f5fa 100644 --- a/src/vtab.c +++ b/src/vtab.c @@ -434,7 +434,7 @@ void sqlite3VtabFinishParse(Parse *pParse, Token *pEnd){ sqlite3VdbeAddOp0(v, OP_Expire); zWhere = sqlite3MPrintf(db, "name='%q' AND type='table'", pTab->zName); - sqlite3VdbeAddParseSchemaOp(v, iDb, zWhere); + sqlite3VdbeAddParseSchemaOp(pParse, iDb, zWhere); iReg = ++pParse->nMem; sqlite3VdbeLoadString(v, iReg, pTab->zName); diff --git a/test/reuse1.test b/test/reuse1.test new file mode 100644 index 0000000000..b21779b994 --- /dev/null +++ b/test/reuse1.test @@ -0,0 +1,94 @@ +# 2017 August 9 +# +# The author disclaims copyright to this source code. In place of +# a legal notice, here is a blessing: +# +# May you do good and not evil. +# May you find forgiveness for yourself and forgive others. +# May you share freely, never taking more than you give. +# +#*********************************************************************** +# +# + + +set testdir [file dirname $argv0] +source $testdir/tester.tcl +set testprefix reuse1 + + +forcedelete test.db2 +sqlite3 db2 test.db2 + +do_execsql_test 1.0 { + CREATE TABLE t1(x INTEGER PRIMARY KEY, y UNIQUE, z); + CREATE INDEX i1 ON t1(z); + PRAGMA schema_version; +} {2} + +do_execsql_test -db db2 1.1 { + CREATE TABLE t1(x INTEGER PRIMARY KEY, y UNIQUE, z); + CREATE INDEX i1 ON t1(z); + PRAGMA schema_version; +} {2} + +do_test 1.2 { + db close + db2 close + sqlite3 db2 test.db2 -reuse-schema 1 + sqlite3 db test.db -reuse-schema 1 +} {} + +do_execsql_test -db db2 1.3.1 { + INSERT INTO t1 VALUES(1, 2, 3); + INSERT INTO t1 VALUES(4, 5, 6); +} + +do_execsql_test 1.3.2 { + SELECT * FROM t1; + PRAGMA integrity_check; +} {ok} + +do_execsql_test -db db2 1.3.3 { + SELECT * FROM t1; + PRAGMA integrity_check; +} {1 2 3 4 5 6 ok} + +sqlite3 db3 test.db2 +do_execsql_test -db db3 1.4.1 { + ALTER TABLE t1 ADD COLUMN a; +} +do_execsql_test -db db2 1.4.2 { + SELECT * FROM t1; +} {1 2 3 {} 4 5 6 {}} +do_execsql_test 1.4.3 { + SELECT * FROM t1; +} {} + +db3 close +sqlite3 db3 test.db +do_execsql_test -db db3 1.5.0 { + CREATE TRIGGER tr1 AFTER INSERT ON t1 BEGIN + SELECT 1, 2, 3; + END; +} + +# Check that the schema cannot be modified if the db was opened with +# SQLITE_OPEN_REUSE_SCHEMA. +# +foreach {tn sql} { + 1 { CREATE TABLE t2(x, y) } + 2 { CREATE INDEX i2 ON t1(z) } + 3 { CREATE VIEW v2 AS SELECT * FROM t2 } + 4 { ALTER TABLE t1 RENAME TO t3 } + 5 { ALTER TABLE t1 ADD COLUMN xyz } + 6 { VACUUM } + 7 { DROP INDEX i1 } + 8 { DROP TABLE t1 } + 9 { DROP TRIGGER tr1 } + 10 { ANALYZE } +} { + do_catchsql_test 1.5.$tn $sql {1 {attempt to modify read-only schema}} +} + +finish_test diff --git a/test/tclsqlite.test b/test/tclsqlite.test index 1b95a45a5c..c0d503140a 100644 --- a/test/tclsqlite.test +++ b/test/tclsqlite.test @@ -22,7 +22,7 @@ source $testdir/tester.tcl # Check the error messages generated by tclsqlite # -set r "sqlite_orig HANDLE FILENAME ?-vfs VFSNAME? ?-readonly BOOLEAN? ?-create BOOLEAN? ?-nomutex BOOLEAN? ?-fullmutex BOOLEAN? ?-uri BOOLEAN?" +set r "sqlite_orig HANDLE FILENAME ?-vfs VFSNAME? ?-readonly BOOLEAN? ?-create BOOLEAN? ?-nomutex BOOLEAN? ?-fullmutex BOOLEAN? ?-uri BOOLEAN? ?-reuse-schema BOOLEAN?" if {[sqlite3 -has-codec]} { append r " ?-key CODECKEY?" }