Index: Makefile.in ================================================================== --- Makefile.in +++ Makefile.in @@ -400,10 +400,11 @@ $(TOP)/src/test_osinst.c \ $(TOP)/src/test_pcache.c \ $(TOP)/src/test_quota.c \ $(TOP)/src/test_rtree.c \ $(TOP)/src/test_schema.c \ + $(TOP)/src/test_schemapool.c \ $(TOP)/src/test_superlock.c \ $(TOP)/src/test_syscall.c \ $(TOP)/src/test_tclsh.c \ $(TOP)/src/test_tclvar.c \ $(TOP)/src/test_thread.c \ @@ -467,10 +468,11 @@ $(TOP)/src/attach.c \ $(TOP)/src/backup.c \ $(TOP)/src/bitvec.c \ $(TOP)/src/btree.c \ $(TOP)/src/build.c \ + $(TOP)/src/callback.c \ $(TOP)/src/ctime.c \ $(TOP)/src/date.c \ $(TOP)/src/dbpage.c \ $(TOP)/src/dbstat.c \ $(TOP)/src/expr.c \ Index: Makefile.msc ================================================================== --- Makefile.msc +++ Makefile.msc @@ -1547,10 +1547,11 @@ $(TOP)\src\test_osinst.c \ $(TOP)\src\test_pcache.c \ $(TOP)\src\test_quota.c \ $(TOP)\src\test_rtree.c \ $(TOP)\src\test_schema.c \ + $(TOP)\src\test_schemapool.c \ $(TOP)\src\test_superlock.c \ $(TOP)\src\test_syscall.c \ $(TOP)\src\test_tclsh.c \ $(TOP)\src\test_tclvar.c \ $(TOP)\src\test_thread.c \ ADDED doc/shared_schema.md Index: doc/shared_schema.md ================================================================== --- /dev/null +++ doc/shared_schema.md @@ -0,0 +1,142 @@ + +Shared-Schema Mode Notes +======================== + +The [reuse-schema](/timeline?r=reuse-schema) branch contains changes +to allow SQLite connections to share schemas +between database connections within the same process in order to save memory. +Schemas may be shared between multiple databases attached to the same or +distinct connection handles. + +Compile with -DSQLITE\_ENABLE\_SHARED\_SCHEMA in order to enable the +shared-schema enhancement. Enabling the shared-schema enhancement causes +approximately a 0.1% increase in CPU cycles consumed and about a 3000-byte +increase in the size of the library, even if shared-schema is never used. + +Assuming the compile-time requirements are satisfied, the shared-schema +feature is engaged by opening the database connection using the +sqlite3_open_v2() API with the SQLITE_OPEN_SHARED_SCHEMA +flag specified. The main database and any attached databases will then share +an in-memory Schema object with any other database opened within the process +for which: + + * the contents of the sqlite_master table, including all object names, + SQL statements and root pages are identical, and + * have the same values for the schema-cookie. + +Temp databases (those populated with "CREATE TEMP TABLE" and similar +statements) never share schemas. + +Connections opened with the SQLITE_OPEN_SHARED_SCHEMA flag +specified may not modify any database schema except that belonging to the +temp database in anyway. This includes creating or dropping database +objects, vacuuming the database, or running ANALYZE when the +sqlite_stat\[14\] tables do not exist. + +For SQLITE_OPEN_SHARED_SCHEMA connections, the +SQLITE_DBSTATUS_SCHEMA_USED sqlite3_db_status() verb +distributes the memory used for a shared schema object evenly between all +database connections that share it. + +## The ".shared-schema" Command + +The shell tool on this branch contains a special dot-command to help with +managing databases. The ".shared-schema" dot-command can be used to test +whether or not two databases are similar enough to share in-memory schemas, +and to fix minor problems that prevent them from doing so. To test if +two or more database are compatible, one database is opened directly using +the shell tool and the following command issued: + + .shared-schema check []... + +where <database-1> etc. are replaced with the names of database files +on disk. For each database specified on the command line, a single line of +output is produced. If the database can share an in-memory schema with the +main database opened by the shell tool, the output is of the form: + + is compatible + +Otherwise, if the database cannot share a schema with the main db, the output +is of the form: + + is NOT compatible () + +where <reason> indicates the cause of the incompatibility. <reason> +is always one of the following. + +
    +
  • objects - the databases contain a different set schema objects + (tables, indexes, views and triggers). + +
  • SQL - the databases contain the same set of objects, but the SQL + statements used to create them were not the same. + +
  • root pages - the databases contain the same set of objects created + by the same SQL statements, but the root pages are not the same. + +
  • order of sqlite_master rows - the databases contain the same + set of objects created by the same SQL statements with the same root pages, + but the order of the rows in the sqlite_master tables are different. + +
  • schema cookie - the database schemas are compatible, but the + schema cookie values ("PRAGMA schema_version") are different. +
+ +The final three problems in the list above can be fixed using the +.shared-schema command. To modify such a database so that it can share a +schema with the main database, the following shell command is used: + + .shared-schema fix []... + +If a database can be modified so that it may share a schema with the main +database opened by the shell tool, output is as follows: + + Fixing ... is compatible + +If a database does not require modification, or cannot be modified such that +it can share a schema with the main database, the output of "fix" is identical +to that of the "check" command. + +## Implementation Notes + +A single Schema object is never used by more than one database simultaneously, +regardless of whether or not those databases are attached to the same or +different database handles. Instead, a pool of schema objects is maintained +for each unique sqlite_master-contents/schema-cookie combination +opened within the process. Each time database schemas are required by a +connection, for example as part of an sqlite3_prepare\*(), +sqlite3_blob_open() or sqlite3_blob_open() call, it obtains +the minimum number of schemas required from the various schema-pools, returning +them at the end of the call. This means that a single schema-pool only ever +contains more than one copy of the schema if: + + * Two threads require schemas from the same pool at the same time, or + * A single sqlite3_prepare\*() call requires schemas for two or more + attached databases that use the same schema-pool. + +The size of a schema-pool never shrinks. Each schema pool always maintains +a number of schema objects equal to the highwater mark of schema objects +simultaneously required by clients. + +This approach is preferred to allowing multiple databases to use the same +Schema object simultaneously for three reasons: + + * The Schema object is not completely read-only. For example, the + Index.zIdxAff string is allocated lazily. + * Throughout the statement compiler, SQLite uses variables like + Table.pSchema and Index.pSchema with the sqlite3SchemaToIndex() routine + in order to determine which attached database a Table or Index object + resides in. This mechanism does not work if the same Schema may be + used by two or more attached databases. + * It may be easier to modify this approach in order to allow + SQLITE_OPEN_SHARED_SCHEMA connections to modify database + schemas, should that be required. + +SQLITE_OPEN_SHARED_SCHEMA connections do not store their +virtual-table handles in the Table.pVTable list of each table. This would not +work, as (a) there is no guarantee that a connection will be assigned the same +Schema object each time it requests one from a schema-pool and (b) a single +Schema (and therefore Table) object may correspond to tables in two or more +databases attached to a single connection. Instead, all virtual-table handles +associated with a single database are stored in a linked-list headed at +Db.pVTable. ADDED ext/wasm/EXPORTED_FUNCTIONS.fiddle Index: ext/wasm/EXPORTED_FUNCTIONS.fiddle ================================================================== --- /dev/null +++ ext/wasm/EXPORTED_FUNCTIONS.fiddle @@ -0,0 +1,7 @@ +_fiddle_exec +_fiddle_interrupt +_fiddle_experiment +_fiddle_the_db +_fiddle_db_arg +_fiddle_db_filename +_fiddle_reset_db DELETED ext/wasm/EXPORTED_FUNCTIONS.fiddle.in Index: ext/wasm/EXPORTED_FUNCTIONS.fiddle.in ================================================================== --- ext/wasm/EXPORTED_FUNCTIONS.fiddle.in +++ /dev/null @@ -1,10 +0,0 @@ -_fiddle_db_arg -_fiddle_db_filename -_fiddle_exec -_fiddle_experiment -_fiddle_interrupt -_fiddle_main -_fiddle_reset_db -_fiddle_db_handle -_fiddle_db_vfs -_fiddle_export_db Index: ext/wasm/api/EXPORTED_FUNCTIONS.sqlite3-api ================================================================== --- ext/wasm/api/EXPORTED_FUNCTIONS.sqlite3-api +++ ext/wasm/api/EXPORTED_FUNCTIONS.sqlite3-api @@ -1,27 +1,17 @@ -_malloc -_free -_realloc -_sqlite3_aggregate_context -_sqlite3_auto_extension _sqlite3_bind_blob _sqlite3_bind_double _sqlite3_bind_int _sqlite3_bind_int64 _sqlite3_bind_null _sqlite3_bind_parameter_count _sqlite3_bind_parameter_index -_sqlite3_bind_pointer _sqlite3_bind_text -_sqlite3_busy_handler -_sqlite3_busy_timeout -_sqlite3_cancel_auto_extension _sqlite3_changes _sqlite3_changes64 _sqlite3_clear_bindings _sqlite3_close_v2 -_sqlite3_collation_needed _sqlite3_column_blob _sqlite3_column_bytes _sqlite3_column_count _sqlite3_column_count _sqlite3_column_double @@ -28,177 +18,55 @@ _sqlite3_column_int _sqlite3_column_int64 _sqlite3_column_name _sqlite3_column_text _sqlite3_column_type -_sqlite3_column_value -_sqlite3_commit_hook _sqlite3_compileoption_get _sqlite3_compileoption_used -_sqlite3_complete -_sqlite3_context_db_handle -_sqlite3_create_collation -_sqlite3_create_collation_v2 -_sqlite3_create_function _sqlite3_create_function_v2 -_sqlite3_create_module -_sqlite3_create_module_v2 -_sqlite3_create_window_function _sqlite3_data_count _sqlite3_db_filename -_sqlite3_db_handle _sqlite3_db_name -_sqlite3_db_status -_sqlite3_declare_vtab -_sqlite3_deserialize -_sqlite3_drop_modules -_sqlite3_errcode _sqlite3_errmsg _sqlite3_error_offset _sqlite3_errstr _sqlite3_exec _sqlite3_expanded_sql _sqlite3_extended_errcode _sqlite3_extended_result_codes -_sqlite3_file_control _sqlite3_finalize -_sqlite3_free -_sqlite3_get_auxdata -_sqlite3_get_autocommit _sqlite3_initialize -_sqlite3_keyword_count -_sqlite3_keyword_name -_sqlite3_keyword_check -_sqlite3_last_insert_rowid +_sqlite3_interrupt _sqlite3_libversion _sqlite3_libversion_number -_sqlite3_limit -_sqlite3_malloc -_sqlite3_malloc64 -_sqlite3_msize _sqlite3_open _sqlite3_open_v2 -_sqlite3_overload_function _sqlite3_prepare_v2 _sqlite3_prepare_v3 -_sqlite3_preupdate_blobwrite -_sqlite3_preupdate_count -_sqlite3_preupdate_depth -_sqlite3_preupdate_hook -_sqlite3_preupdate_new -_sqlite3_preupdate_old -_sqlite3_progress_handler -_sqlite3_randomness -_sqlite3_realloc -_sqlite3_realloc64 _sqlite3_reset -_sqlite3_reset_auto_extension _sqlite3_result_blob _sqlite3_result_double _sqlite3_result_error _sqlite3_result_error_code _sqlite3_result_error_nomem _sqlite3_result_error_toobig _sqlite3_result_int -_sqlite3_result_int64 _sqlite3_result_null -_sqlite3_result_pointer -_sqlite3_result_subtype _sqlite3_result_text -_sqlite3_result_zeroblob -_sqlite3_result_zeroblob64 -_sqlite3_rollback_hook -_sqlite3_serialize -_sqlite3_set_authorizer -_sqlite3_set_auxdata -_sqlite3_set_last_insert_rowid -_sqlite3_shutdown _sqlite3_sourceid _sqlite3_sql -_sqlite3_status -_sqlite3_status64 _sqlite3_step -_sqlite3_stmt_isexplain -_sqlite3_stmt_readonly -_sqlite3_stmt_status _sqlite3_strglob -_sqlite3_stricmp _sqlite3_strlike -_sqlite3_strnicmp -_sqlite3_table_column_metadata _sqlite3_total_changes _sqlite3_total_changes64 -_sqlite3_trace_v2 -_sqlite3_txn_state -_sqlite3_update_hook -_sqlite3_uri_boolean -_sqlite3_uri_int64 -_sqlite3_uri_key -_sqlite3_uri_parameter -_sqlite3_user_data _sqlite3_value_blob _sqlite3_value_bytes _sqlite3_value_double -_sqlite3_value_dup -_sqlite3_value_free -_sqlite3_value_frombind -_sqlite3_value_int -_sqlite3_value_int64 -_sqlite3_value_nochange -_sqlite3_value_numeric_type -_sqlite3_value_pointer -_sqlite3_value_subtype _sqlite3_value_text _sqlite3_value_type _sqlite3_vfs_find _sqlite3_vfs_register -_sqlite3_vfs_unregister -_sqlite3_vtab_collation -_sqlite3_vtab_distinct -_sqlite3_vtab_in -_sqlite3_vtab_in_first -_sqlite3_vtab_in_next -_sqlite3_vtab_nochange -_sqlite3_vtab_on_conflict -_sqlite3_vtab_rhs_value -_sqlite3changegroup_add -_sqlite3changegroup_add_strm -_sqlite3changegroup_delete -_sqlite3changegroup_new -_sqlite3changegroup_output -_sqlite3changegroup_output_strm -_sqlite3changeset_apply -_sqlite3changeset_apply_strm -_sqlite3changeset_apply_v2 -_sqlite3changeset_apply_v2_strm -_sqlite3changeset_concat -_sqlite3changeset_concat_strm -_sqlite3changeset_conflict -_sqlite3changeset_finalize -_sqlite3changeset_fk_conflicts -_sqlite3changeset_invert -_sqlite3changeset_invert_strm -_sqlite3changeset_new -_sqlite3changeset_next -_sqlite3changeset_old -_sqlite3changeset_op -_sqlite3changeset_pk -_sqlite3changeset_start -_sqlite3changeset_start_strm -_sqlite3changeset_start_v2 -_sqlite3changeset_start_v2_strm -_sqlite3session_attach -_sqlite3session_changeset -_sqlite3session_changeset_size -_sqlite3session_changeset_strm -_sqlite3session_config -_sqlite3session_create -_sqlite3session_delete -_sqlite3session_diff -_sqlite3session_enable -_sqlite3session_indirect -_sqlite3session_isempty -_sqlite3session_memory_used -_sqlite3session_object_config -_sqlite3session_patchset -_sqlite3session_patchset_strm -_sqlite3session_table_filter +_sqlite3_wasm_db_error +_sqlite3_wasm_enum_json +_malloc +_free Index: ext/wasm/api/sqlite3-worker1-promiser.c-pp.js ================================================================== --- ext/wasm/api/sqlite3-worker1-promiser.c-pp.js +++ ext/wasm/api/sqlite3-worker1-promiser.c-pp.js @@ -1,6 +1,5 @@ -//#ifnot omit-oo1 /* 2022-08-24 The author disclaims copyright to this source code. In place of a legal notice, here is a blessing: @@ -40,17 +39,13 @@ function, enabling delayed instantiation of a Worker. - `onready` (optional, but...): this callback is called with no arguments when the worker fires its initial 'sqlite3-api'/'worker1-ready' message, which it does when - sqlite3.initWorker1API() completes its initialization. This is the - simplest way to tell the worker to kick off work at the earliest - opportunity, and the only way to know when the worker module has - completed loading. The irony of using a callback for this, instead - of returning a promise from sqlite3Worker1Promiser() is not lost on - the developers: see sqlite3Worker1Promiser.v2() which uses a - Promise instead. + sqlite3.initWorker1API() completes its initialization. This is + the simplest way to tell the worker to kick off work at the + earliest opportunity. - `onunhandled` (optional): a callback which gets passed the message event object for any worker.onmessage() events which are not handled by this proxy. Ideally that "should" never happen, as this proxy aims to handle all known message types. @@ -117,29 +112,20 @@ Where `typeString` is an internally-synthesized message type string used temporarily for worker message dispatching. It can be ignored by all client code except that which tests this API. The `row` property contains the row result in the form implied by the `rowMode` option (defaulting to `'array'`). The `rowNumber` is a - 1-based integer value incremented by 1 on each call into the + 1-based integer value incremented by 1 on each call into th callback. At the end of the result set, the same event is fired with (row=undefined, rowNumber=null) to indicate that the end of the result set has been reached. Note that the rows arrive via worker-posted messages, with all the implications of that. - - Notable shortcomings: - - - This API was not designed with ES6 modules in mind. Neither Firefox - nor Safari support, as of March 2023, the {type:"module"} flag to the - Worker constructor, so that particular usage is not something we're going - to target for the time being: - - https://developer.mozilla.org/en-US/docs/Web/API/Worker/Worker */ -globalThis.sqlite3Worker1Promiser = function callee(config = callee.defaultConfig){ +self.sqlite3Worker1Promiser = function callee(config = callee.defaultConfig){ // Inspired by: https://stackoverflow.com/a/52439530 if(1===arguments.length && 'function'===typeof arguments[0]){ const f = config; config = Object.assign(Object.create(null), callee.defaultConfig); config.onready = f; @@ -158,26 +144,25 @@ }; const toss = (...args)=>{throw new Error(args.join(' '))}; if(!config.worker) config.worker = callee.defaultConfig.worker; if('function'===typeof config.worker) config.worker = config.worker(); let dbId; - let promiserFunc; config.worker.onmessage = function(ev){ ev = ev.data; debug('worker1.onmessage',ev); let msgHandler = handlerMap[ev.messageId]; if(!msgHandler){ if(ev && 'sqlite3-api'===ev.type && 'worker1-ready'===ev.result) { /*fired one time when the Worker1 API initializes*/ - if(config.onready) config.onready(promiserFunc); + if(config.onready) config.onready(); return; } msgHandler = handlerMap[ev.type] /* check for exec per-row callback */; if(msgHandler && msgHandler.onrow){ msgHandler.onrow(ev); return; - } + } if(config.onunhandled) config.onunhandled(arguments[0]); else err("sqlite3Worker1Promiser() unhandled worker message:",ev); return; } delete handlerMap[ev.messageId]; @@ -195,23 +180,23 @@ break; } try {msgHandler.resolve(ev)} catch(e){msgHandler.reject(e)} }/*worker.onmessage()*/; - return promiserFunc = function(/*(msgType, msgArgs) || (msgEnvelope)*/){ + return function(/*(msgType, msgArgs) || (msgEnvelope)*/){ let msg; if(1===arguments.length){ msg = arguments[0]; }else if(2===arguments.length){ - msg = Object.create(null); - msg.type = arguments[0]; - msg.args = arguments[1]; - msg.dbId = msg.args.dbId; + msg = { + type: arguments[0], + args: arguments[1] + }; }else{ toss("Invalid arugments for sqlite3Worker1Promiser()-created factory."); } - if(!msg.dbId && msg.type!=='open') msg.dbId = dbId; + if(!msg.dbId) msg.dbId = dbId; msg.messageId = genMsgId(msg); msg.departureTime = performance.now(); const proxy = Object.create(null); proxy.message = msg; let rowCallbackId /* message handler ID for exec on-row callback proxy */; @@ -249,98 +234,30 @@ }); if(rowCallbackId) p = p.finally(()=>delete handlerMap[rowCallbackId]); return p; }; }/*sqlite3Worker1Promiser()*/; - -globalThis.sqlite3Worker1Promiser.defaultConfig = { +self.sqlite3Worker1Promiser.defaultConfig = { worker: function(){ -//#if target=es6-module - return new Worker(new URL("sqlite3-worker1-bundler-friendly.mjs", import.meta.url),{ - type: 'module' - }); +//#if target=es6-bundler-friendly + return new Worker("sqlite3-worker1.js"); //#else let theJs = "sqlite3-worker1.js"; if(this.currentScript){ const src = this.currentScript.src.split('/'); src.pop(); theJs = src.join('/')+'/' + theJs; //sqlite3.config.warn("promiser currentScript, theJs =",this.currentScript,theJs); - }else if(globalThis.location){ - //sqlite3.config.warn("promiser globalThis.location =",globalThis.location); - const urlParams = new URL(globalThis.location.href).searchParams; + }else{ + //sqlite3.config.warn("promiser self.location =",self.location); + const urlParams = new URL(self.location.href).searchParams; if(urlParams.has('sqlite3.dir')){ theJs = urlParams.get('sqlite3.dir') + '/' + theJs; } } - return new Worker(theJs + globalThis.location.search); -//#endif - } -//#ifnot target=es6-module - .bind({ - currentScript: globalThis?.document?.currentScript - }) -//#endif - , + return new Worker(theJs + self.location.search); +//#endif + }.bind({ + currentScript: self?.document?.currentScript + }), onerror: (...args)=>console.error('worker1 promiser error',...args) -}/*defaultConfig*/; - -/** - sqlite3Worker1Promiser.v2() works identically to - sqlite3Worker1Promiser() except that it returns a Promise instead - of relying an an onready callback in the config object. The Promise - resolves to the same factory function which - sqlite3Worker1Promiser() returns. - - If config is-a function or is an object which contains an onready - function, that function is replaced by a proxy which will resolve - after calling the original function and will reject if that - function throws. -*/ -sqlite3Worker1Promiser.v2 = function(config){ - let oldFunc; - if( 'function' == typeof config ){ - oldFunc = config; - config = {}; - }else if('function'===typeof config?.onready){ - oldFunc = config.onready; - delete config.onready; - } - const promiseProxy = Object.create(null); - config = Object.assign((config || Object.create(null)),{ - onready: async function(func){ - try { - if( oldFunc ) await oldFunc(func); - promiseProxy.resolve(func); - } - catch(e){promiseProxy.reject(e)} - } - }); - const p = new Promise(function(resolve,reject){ - promiseProxy.resolve = resolve; - promiseProxy.reject = reject; - }); - try{ - this.original(config); - }catch(e){ - promiseProxy.reject(e); - } - return p; -}.bind({ - /* We do this because clients are - recommended to delete globalThis.sqlite3Worker1Promiser. */ - original: sqlite3Worker1Promiser -}); - -//#if target=es6-module -/** - When built as a module, we export sqlite3Worker1Promiser.v2() - instead of sqlite3Worker1Promise() because (A) its interface is more - conventional for ESM usage and (B) the ESM option export option for - this API did not exist until v2 was created, so there's no backwards - incompatibility. -*/ -export default sqlite3Worker1Promiser.v2; -//#endif /* target=es6-module */ -//#else -/* Built with the omit-oo1 flag. */ -//#endif ifnot omit-oo1 +}; ADDED ext/wasm/fiddle/fiddle.html Index: ext/wasm/fiddle/fiddle.html ================================================================== --- /dev/null +++ ext/wasm/fiddle/fiddle.html @@ -0,0 +1,283 @@ + + + + + + SQLite3 Fiddle + + + + + + + +
+ SQLite3 Fiddle + Powered by + SQLite3 +
+ +
+
+
Initializing app...
+
+ On a slow internet connection this may take a moment. If this + message displays for "a long time", intialization may have + failed and the JavaScript console may contain clues as to why. +
+
+
Downloading...
+
+ +
+ + + +
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+
+ + + + +
+
+
+
+
+ + + +
+
+
+
+
+ + + + DELETED ext/wasm/fiddle/index.html Index: ext/wasm/fiddle/index.html ================================================================== --- ext/wasm/fiddle/index.html +++ /dev/null @@ -1,278 +0,0 @@ - - - - - - SQLite3 Fiddle - - - - - - - -
- SQLite3 Fiddle - Powered by - SQLite3 -
- -
-
-
Initializing app...
-
- On a slow internet connection this may take a moment. If this - message displays for "a long time", intialization may have - failed and the JavaScript console may contain clues as to why. -
-
-
Downloading...
-
- -
- - - -
-
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - -
-
-
-
-
- - - - -
-
-
-
-
- - - -
-
-
-
-
- - - Index: main.mk ================================================================== --- main.mk +++ main.mk @@ -323,10 +323,11 @@ $(TOP)/src/test_osinst.c \ $(TOP)/src/test_pcache.c \ $(TOP)/src/test_quota.c \ $(TOP)/src/test_rtree.c \ $(TOP)/src/test_schema.c \ + $(TOP)/src/test_schemapool.c \ $(TOP)/src/test_sqllog.c \ $(TOP)/src/test_superlock.c \ $(TOP)/src/test_syscall.c \ $(TOP)/src/test_tclsh.c \ $(TOP)/src/test_tclvar.c \ @@ -384,10 +385,11 @@ TESTSRC2 = \ $(TOP)/src/attach.c \ $(TOP)/src/backup.c \ $(TOP)/src/btree.c \ $(TOP)/src/build.c \ + $(TOP)/src/callback.c \ $(TOP)/src/date.c \ $(TOP)/src/dbpage.c \ $(TOP)/src/dbstat.c \ $(TOP)/src/expr.c \ $(TOP)/src/func.c \ Index: src/alter.c ================================================================== --- src/alter.c +++ src/alter.c @@ -110,12 +110,12 @@ */ static void renameReloadSchema(Parse *pParse, int iDb, u16 p5){ Vdbe *v = pParse->pVdbe; if( v ){ sqlite3ChangeCookie(pParse, iDb); - sqlite3VdbeAddParseSchemaOp(pParse->pVdbe, iDb, 0, p5); - if( iDb!=1 ) sqlite3VdbeAddParseSchemaOp(pParse->pVdbe, 1, 0, p5); + sqlite3VdbeAddParseSchemaOp(pParse, iDb, 0, p5); + if( iDb!=1 ) sqlite3VdbeAddParseSchemaOp(pParse, 1, 0, p5); } } /* ** Generate code to implement the "ALTER TABLE xxx RENAME TO yyy" Index: src/analyze.c ================================================================== --- src/analyze.c +++ src/analyze.c @@ -210,10 +210,11 @@ if( iregRoot. This is important ** because the OpenWrite opcode below will be needing it. */ + sqlite3SchemaWritable(pParse, iDb); sqlite3NestedParse(pParse, "CREATE TABLE %Q.%s(%s)", pDb->zDbSName, zTab, aTable[i].zCols ); aRoot[i] = (u32)pParse->regRoot; aCreateTbl[i] = OPFLAG_P2ISREG; Index: src/attach.c ================================================================== --- src/attach.c +++ src/attach.c @@ -216,18 +216,18 @@ ** If this fails, or if opening the file failed, then close the file and ** remove the entry from the db->aDb[] array. i.e. put everything back the ** way we found it. */ if( rc==SQLITE_OK ){ - sqlite3BtreeEnterAll(db); db->init.iDb = 0; db->mDbFlags &= ~(DBFLAG_SchemaKnownOk); - if( !REOPEN_AS_MEMDB(db) ){ + if( !IsSharedSchema(db) && !REOPEN_AS_MEMDB(db) ){ + sqlite3BtreeEnterAll(db); rc = sqlite3Init(db, &zErrDyn); + sqlite3BtreeLeaveAll(db); + assert( zErrDyn==0 || rc!=SQLITE_OK ); } - sqlite3BtreeLeaveAll(db); - assert( zErrDyn==0 || rc!=SQLITE_OK ); } #ifdef SQLITE_USER_AUTHENTICATION if( rc==SQLITE_OK && !REOPEN_AS_MEMDB(db) ){ u8 newAuth = 0; rc = sqlite3UserAuthCheckLogin(db, zName, &newAuth); @@ -323,10 +323,11 @@ pTrig->pTabSchema = pTrig->pSchema; } pEntry = sqliteHashNext(pEntry); } + (void)sqlite3SchemaDisconnect(db, i, 0); sqlite3BtreeClose(pDb->pBt); pDb->pBt = 0; pDb->pSchema = 0; sqlite3CollapseDatabaseArray(db); return; @@ -352,11 +353,13 @@ NameContext sName; Vdbe *v; sqlite3* db = pParse->db; int regArgs; - if( SQLITE_OK!=sqlite3ReadSchema(pParse) ) goto attach_end; + if( (db->mDbFlags & DBFLAG_EncodingFixed)==0 ){ + if( SQLITE_OK!=sqlite3ReadSchema(pParse) ) goto attach_end; + } if( pParse->nErr ) goto attach_end; memset(&sName, 0, sizeof(NameContext)); sName.pParse = pParse; Index: src/build.c ================================================================== --- src/build.c +++ src/build.c @@ -336,10 +336,43 @@ int sqlite3UserAuthTable(const char *zTable){ return sqlite3_stricmp(zTable, "sqlite_user")==0; } #endif +#ifdef SQLITE_ENABLE_SHARED_SCHEMA +/* +** If this database connection was opened with the SQLITE_OPEN_SHARED_SCHEMA +** flag specified, then ensure that the database schema for database iDb +** is loaded. Either by obtaining a Schema object from the schema-pool, or +** by reading the contents of the sqlite_master table. Unless it is NULL, +** the location indicated by parameter pbUnload is set to 1 if a shared-schema +** is loaded. +** +** If the database handle was not opened with SQLITE_OPEN_SHARED_SCHEMA, or +** if the schema for database iDb is already loaded, this function is a no-op. +** +** SQLITE_OK is returned if successful, or an SQLite error code otherwise. If +** an error code is returned, (*pzErr) may be set to point to a buffer +** containing an error message. It is the responsibility of the caller to +** eventually free this buffer using sqlite3_free(). +*/ +int sqlite3SchemaLoad(sqlite3 *db, int iDb, int *pbUnload, char **pzErr){ + int rc = SQLITE_OK; + if( IsSharedSchema(db) + && DbHasProperty(db, iDb, DB_SchemaLoaded)==0 + && (db->init.busy==0 || (iDb!=1 && db->init.iDb==1)) + ){ + struct sqlite3InitInfo sv = db->init; + memset(&db->init, 0, sizeof(struct sqlite3InitInfo)); + rc = sqlite3InitOne(db, iDb, pzErr, 0); + db->init = sv; + if( pbUnload && rc==SQLITE_OK && iDb!=1 ) *pbUnload = 1; + } + return rc; +} +#endif + /* ** Locate the in-memory structure that describes a particular database ** table given the name of that table and (optionally) the name of the ** database containing the table. Return NULL if not found. ** @@ -361,63 +394,45 @@ ** exists */ if( db->auth.authLevelnDb; i++){ - if( sqlite3StrICmp(zDatabase, db->aDb[i].zDbSName)==0 ) break; - } - if( i>=db->nDb ){ - /* No match against the official names. But always match "main" - ** to schema 0 as a legacy fallback. */ - if( sqlite3StrICmp(zDatabase,"main")==0 ){ - i = 0; - }else{ - return 0; - } - } - p = sqlite3HashFind(&db->aDb[i].pSchema->tblHash, zName); - if( p==0 && sqlite3StrNICmp(zName, "sqlite_", 7)==0 ){ - if( i==1 ){ - if( sqlite3StrICmp(zName+7, &PREFERRED_TEMP_SCHEMA_TABLE[7])==0 - || sqlite3StrICmp(zName+7, &PREFERRED_SCHEMA_TABLE[7])==0 - || sqlite3StrICmp(zName+7, &LEGACY_SCHEMA_TABLE[7])==0 - ){ - p = sqlite3HashFind(&db->aDb[1].pSchema->tblHash, - LEGACY_TEMP_SCHEMA_TABLE); - } - }else{ - if( sqlite3StrICmp(zName+7, &PREFERRED_SCHEMA_TABLE[7])==0 ){ - p = sqlite3HashFind(&db->aDb[i].pSchema->tblHash, - LEGACY_SCHEMA_TABLE); - } - } - } - }else{ - /* Match against TEMP first */ - p = sqlite3HashFind(&db->aDb[1].pSchema->tblHash, zName); - if( p ) return p; - /* The main database is second */ - p = sqlite3HashFind(&db->aDb[0].pSchema->tblHash, zName); - if( p ) return p; - /* Attached databases are in order of attachment */ - for(i=2; inDb; i++){ - assert( sqlite3SchemaMutexHeld(db, i, 0) ); - p = sqlite3HashFind(&db->aDb[i].pSchema->tblHash, zName); - if( p ) break; - } - if( p==0 && sqlite3StrNICmp(zName, "sqlite_", 7)==0 ){ - if( sqlite3StrICmp(zName+7, &PREFERRED_SCHEMA_TABLE[7])==0 ){ - p = sqlite3HashFind(&db->aDb[0].pSchema->tblHash, LEGACY_SCHEMA_TABLE); - }else if( sqlite3StrICmp(zName+7, &PREFERRED_TEMP_SCHEMA_TABLE[7])==0 ){ - p = sqlite3HashFind(&db->aDb[1].pSchema->tblHash, - LEGACY_TEMP_SCHEMA_TABLE); - } - } - } - return p; + while(1){ + for(i=OMIT_TEMPDB; inDb; i++){ + int j = (i<2) ? i^1 : i; /* Search TEMP before MAIN */ + if( zDatabase==0 || sqlite3DbIsNamed(db, j, zDatabase) ){ + int bUnload = 0; + assert( sqlite3SchemaMutexHeld(db, j, 0) ); + if( IsSharedSchema(db) ){ + Parse *pParse = db->pParse; + if( pParse && pParse->nErr==0 ){ + pParse->rc = sqlite3SchemaLoad(db, j, &bUnload, &pParse->zErrMsg); + if( pParse->rc ) pParse->nErr++; + } + } + p = sqlite3HashFind(&db->aDb[j].pSchema->tblHash, zName); + if( p ) return p; + if( bUnload ){ + sqlite3SchemaRelease(db, j); + } + } + } + /* Not found. If the name we were looking for was temp.sqlite_master + ** then change the name to sqlite_temp_master and try again. */ + if( sqlite3StrICmp(zName, PREFERRED_SCHEMA_TABLE)==0 ){ + zName = LEGACY_SCHEMA_TABLE; + continue; + } + if( sqlite3StrICmp(zName, PREFERRED_TEMP_SCHEMA_TABLE)==0 ){ + zName = LEGACY_TEMP_SCHEMA_TABLE; + continue; + } + if( sqlite3StrICmp(zName, LEGACY_SCHEMA_TABLE)!=0 ) break; + if( sqlite3_stricmp(zDatabase, db->aDb[1].zDbSName)!=0 ) break; + zName = LEGACY_TEMP_SCHEMA_TABLE; + } + return 0; } /* ** Locate the in-memory structure that describes a particular database ** table given the name of that table and (optionally) the name of the @@ -438,10 +453,11 @@ sqlite3 *db = pParse->db; /* Read the database schema. If an error occurs, leave an error message ** and code in pParse and return NULL. */ if( (db->mDbFlags & DBFLAG_SchemaKnownOk)==0 + && !IsSharedSchema(db) && SQLITE_OK!=sqlite3ReadSchema(pParse) ){ return 0; } @@ -454,31 +470,41 @@ if( (pParse->prepFlags & SQLITE_PREPARE_NO_VTAB)==0 && db->init.busy==0 ){ Module *pMod = (Module*)sqlite3HashFind(&db->aModule, zName); if( pMod==0 && sqlite3_strnicmp(zName, "pragma_", 7)==0 ){ pMod = sqlite3PragmaVtabRegister(db, zName); } - if( pMod && sqlite3VtabEponymousTableInit(pParse, pMod) ){ - testcase( pMod->pEpoTab==0 ); - return pMod->pEpoTab; + if( pMod ){ + if( IsSharedSchema(db) && pParse->nErr==0 ){ + int bDummy = 0; + pParse->rc = sqlite3SchemaLoad(db, 0, &bDummy, &pParse->zErrMsg); + if( pParse->rc ) pParse->nErr++; + (void)bDummy; + } + if( sqlite3VtabEponymousTableInit(pParse, pMod) ){ + Table *pEpoTab = pMod->pEpoTab; + if( pEpoTab ){ + assert( IsSharedSchema(db)||pEpoTab->pSchema==db->aDb[0].pSchema ); + pEpoTab->pSchema = db->aDb[0].pSchema; /* For SHARED_SCHEMA mode */ + } + return pEpoTab; + } } } #endif if( flags & LOCATE_NOERR ) return 0; pParse->checkSchema = 1; }else if( IsVirtual(p) && (pParse->prepFlags & SQLITE_PREPARE_NO_VTAB)!=0 ){ p = 0; } - if( p==0 ){ + if( p==0 && (!IsSharedSchema(db) || pParse->nErr==0) ){ const char *zMsg = flags & LOCATE_VIEW ? "no such view" : "no such table"; if( zDbase ){ sqlite3ErrorMsg(pParse, "%s: %s.%s", zMsg, zDbase, zName); }else{ sqlite3ErrorMsg(pParse, "%s: %s", zMsg, zName); } - }else{ - assert( HasRowid(p) || p->iPKey<0 ); } return p; } @@ -643,15 +669,14 @@ assert( sqlite3SchemaMutexHeld(db, iDb, 0) ); DbSetProperty(db, iDb, DB_ResetWanted); DbSetProperty(db, 1, DB_ResetWanted); db->mDbFlags &= ~DBFLAG_SchemaKnownOk; } - if( db->nSchemaLock==0 ){ for(i=0; inDb; i++){ if( DbHasProperty(db, i, DB_ResetWanted) ){ - sqlite3SchemaClear(db->aDb[i].pSchema); + sqlite3SchemaClearOrDisconnect(db, i); } } } } @@ -660,20 +685,21 @@ ** "main" and "temp") for a single database connection. */ void sqlite3ResetAllSchemasOfConnection(sqlite3 *db){ int i; sqlite3BtreeEnterAll(db); - for(i=0; inDb; i++){ + for(i=0; inDb; i=(i?i+1:2)){ Db *pDb = &db->aDb[i]; if( pDb->pSchema ){ if( db->nSchemaLock==0 ){ - sqlite3SchemaClear(pDb->pSchema); + sqlite3SchemaClearOrDisconnect(db, i); }else{ DbSetProperty(db, i, DB_ResetWanted); } } } + sqlite3SchemaClear(db->aDb[1].pSchema); db->mDbFlags &= ~(DBFLAG_SchemaChange|DBFLAG_SchemaKnownOk); sqlite3VtabUnlockList(db); sqlite3BtreeLeaveAll(db); if( db->nSchemaLock==0 ){ sqlite3CollapseDatabaseArray(db); @@ -1280,11 +1306,11 @@ ** and types will be used, so there is no need to test for namespace ** collisions. */ if( !IN_SPECIAL_PARSE ){ char *zDb = db->aDb[iDb].zDbSName; - if( SQLITE_OK!=sqlite3ReadSchema(pParse) ){ + if( !IsSharedSchema(db) && SQLITE_OK!=sqlite3ReadSchema(pParse) ){ goto begin_table_error; } pTable = sqlite3FindTable(db, zName, zDb); if( pTable ){ if( !noErr ){ @@ -2915,11 +2941,11 @@ } } #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),0); /* Test for cycles in generated columns and illegal expressions ** in CHECK constraints and in DEFAULT clauses. */ if( p->tabFlags & TF_HasGenerated ){ @@ -3487,11 +3513,11 @@ if( db->mallocFailed ){ goto exit_drop_table; } assert( pParse->nErr==0 ); assert( pName->nSrc==1 ); - if( sqlite3ReadSchema(pParse) ) goto exit_drop_table; + if( !IsSharedSchema(db) && sqlite3ReadSchema(pParse) ) goto exit_drop_table; if( noErr ) db->suppressErr++; assert( isView==0 || isView==LOCATE_VIEW ); pTab = sqlite3LocateTableItem(pParse, isView, &pName->a[0]); if( noErr ) db->suppressErr--; @@ -3502,10 +3528,11 @@ } goto exit_drop_table; } 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. */ if( IsVirtual(pTab) && sqlite3ViewGetColumnNames(pParse, pTab) ){ @@ -3956,11 +3983,11 @@ } assert( db->mallocFailed==0 ); if( IN_DECLARE_VTAB && idxType!=SQLITE_IDXTYPE_PRIMARYKEY ){ goto exit_create_index; } - if( SQLITE_OK!=sqlite3ReadSchema(pParse) ){ + if( !IsSharedSchema(db) && SQLITE_OK!=sqlite3ReadSchema(pParse) ){ goto exit_create_index; } if( sqlite3HasExplicitNulls(pParse, pList) ){ goto exit_create_index; } @@ -4456,11 +4483,11 @@ ** to invalidate all pre-compiled statements. */ if( pTblName ){ sqlite3RefillIndex(pParse, pIndex, iMem); sqlite3ChangeCookie(pParse, iDb); - sqlite3VdbeAddParseSchemaOp(v, iDb, + sqlite3VdbeAddParseSchemaOp(pParse, iDb, sqlite3MPrintf(db, "name='%q' AND type='index'", pIndex->zName), 0); sqlite3VdbeAddOp2(v, OP_Expire, 0, 1); } sqlite3VdbeJumpHere(v, (int)pIndex->tnum); @@ -4606,10 +4633,11 @@ sqlite3ErrorMsg(pParse, "index associated with UNIQUE " "or PRIMARY KEY constraint cannot be dropped", 0); goto exit_drop_index; } iDb = sqlite3SchemaToIndex(db, pIndex->pSchema); + sqlite3SchemaWritable(pParse, iDb); #ifndef SQLITE_OMIT_AUTHORIZATION { int code = SQLITE_DROP_INDEX; Table *pTab = pIndex->pTable; const char *zDb = db->aDb[iDb].zDbSName; Index: src/callback.c ================================================================== --- src/callback.c +++ src/callback.c @@ -14,10 +14,67 @@ ** of user defined functions and collation sequences. */ #include "sqliteInt.h" +/* +** Connections opened with the SQLITE_OPEN_SHARED_SCHEMA flag specified +** may use SchemaPool objects for any database that is not the temp db +** (iDb==1). For such databases (type "struct Db") there are three states +** the Schema/SchemaPool object may be in. +** +** 1) pSPool==0, pSchema points to an empty object allocated by +** sqlite3_malloc(). DB_SchemaLoaded flag is clear. +** +** 2) pSPool!=0, pSchema points to a populated object owned by the +** SchemaPool. DB_SchemaLoaded flag is set. +** +** 3) pSPool!=0, pSchema points to the SchemaPool's static object +** (SchemaPool.sSchema). +*/ +struct SchemaPool { + int nRef; /* Number of pointers to this object */ + int nDelete; /* Schema objects deleted by ReleaseAll() */ + u64 cksum; /* Checksum for this Schema contents */ + Schema *pSchema; /* Linked list of Schema objects */ + Schema sSchema; /* The single dummy schema object */ + SchemaPool *pNext; /* Next element in schemaPoolList */ +}; + +#ifdef SQLITE_ENABLE_SHARED_SCHEMA +#ifdef SQLITE_DEBUG +static void assert_schema_state_ok(sqlite3 *db){ + if( IsSharedSchema(db) && db->eOpenState!=SQLITE_STATE_ZOMBIE ){ + int i; + for(i=0; inDb; i++){ + if( i!=1 ){ + Db *pDb = &db->aDb[i]; + Btree *pBt = pDb->pBt; + if( pBt==0 ) continue; + assert( sqlite3BtreeSchema(pBt, 0, 0)==0 ); + assert( pDb->pSchema ); + if( pDb->pSPool ){ + if( DbHasProperty(db, i, DB_SchemaLoaded)==0 ){ + assert( pDb->pSchema->tblHash.count==0 ); + assert( pDb->pSchema==&pDb->pSPool->sSchema ); + }else{ + assert( pDb->pSchema!=&pDb->pSPool->sSchema ); + } + }else{ + assert( DbHasProperty(db, i, DB_SchemaLoaded)==0 ); + assert( pDb->pSchema->tblHash.count==0 ); + assert( pDb->pSchema!=&pDb->pSPool->sSchema ); + } + } + } + } +} +#else +# define assert_schema_state_ok(x) +#endif +#endif /* ifdef SQLITE_ENABLE_SHARED_SCHEMA */ + /* ** Invoke the 'collation needed' callback to request a collation sequence ** in the encoding enc of name zName, length nName. */ static void callCollNeeded(sqlite3 *db, int enc, const char *zName){ @@ -515,19 +572,321 @@ } pSchema->schemaFlags &= ~(DB_SchemaLoaded|DB_ResetWanted); } /* -** Find and return the schema associated with a BTree. Create -** a new one if necessary. +** If this database was opened with the SQLITE_OPEN_SHARED_SCHEMA flag +** and iDb!=1, then disconnect from the schema-pool associated with +** database iDb. Otherwise, clear the Schema object belonging to +** database iDb. +** +** If an OOM error occurs while disconnecting from a schema-pool, +** the db->mallocFailed flag is set. +*/ +void sqlite3SchemaClearOrDisconnect(sqlite3 *db, int iDb){ + Db *pDb = &db->aDb[iDb]; +#ifdef SQLITE_ENABLE_SHARED_SCHEMA + if( IsSharedSchema(db) && iDb!=1 && pDb->pSPool ){ + sqlite3SchemaDisconnect(db, iDb, 1); + }else +#endif + { + sqlite3SchemaClear(pDb->pSchema); + } +} + +#ifdef SQLITE_ENABLE_SHARED_SCHEMA +/* +** Global linked list of SchemaPool objects. Read and write access must +** be protected by the SQLITE_MUTEX_STATIC_MASTER mutex. +*/ +static SchemaPool *SQLITE_WSD schemaPoolList = 0; + +#ifdef SQLITE_TEST +/* +** Return a pointer to the head of the linked list of SchemaPool objects. +** This is used by the virtual table in file test_schemapool.c. +*/ +SchemaPool *sqlite3SchemaPoolList(void){ return schemaPoolList; } +#endif + +/* +** Database handle db was opened with the SHARED_SCHEMA flag, and database +** iDb is currently connected to a schema-pool. When this function is called, +** (*pnByte) is set to nInit plus the amount of memory used to store a +** single instance of the Schema objects managed by the schema-pool. +** This function adjusts (*pnByte) sot hat it is set to nInit plus +** (nSchema/nRef) of the amount of memory used by a single Schema object, +** where nSchema is the number of Schema objects allocated by this pool, +** and nRef is the number of connections to the schema-pool. +*/ +void sqlite3SchemaAdjustUsed(sqlite3 *db, int iDb, int nInit, int *pnByte){ + SchemaPool *pSPool = db->aDb[iDb].pSPool; + int nSchema = 0; + Schema *p; + sqlite3_mutex_enter( sqlite3_mutex_alloc(SQLITE_MUTEX_STATIC_MASTER) ); + for(p=pSPool->pSchema; p; p=p->pNext){ + nSchema++; + } + *pnByte = nInit + ((*pnByte - nInit) * nSchema) / pSPool->nRef; + sqlite3_mutex_leave( sqlite3_mutex_alloc(SQLITE_MUTEX_STATIC_MASTER) ); +} + +/* +** 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_SHARED_SCHEMA). If so, do nothing. Otherwise, leave an +** error in the Parse object. +*/ +void sqlite3SchemaWritable(Parse *pParse, int iDb){ + if( iDb!=1 && IsSharedSchema(pParse->db) && IN_DECLARE_VTAB==0 ){ + sqlite3ErrorMsg(pParse, "attempt to modify read-only schema"); + } +} + +/* +** The schema object passed as the only argument was allocated using +** sqlite3_malloc() and then populated using the usual mechanism. This +** function frees both the Schema object and its contents. +*/ +static void schemaDelete(Schema *pSchema){ + sqlite3SchemaClear((void*)pSchema); + sqlite3_free(pSchema); +} + +/* +** When this function is called, the database connection Db must be +** using a schema-pool (Db.pSPool!=0) and must currently have Db.pSchema +** set to point to a populated schema object checked out from the +** schema-pool. It is also assumed that the STATIC_MASTER mutex is held. +** This function returns the Schema object to the schema-pool and sets +** Db.pSchema to point to the schema-pool's static, empty, Schema object. +*/ +static void schemaRelease(sqlite3 *db, Db *pDb){ + Schema *pRelease = pDb->pSchema; + SchemaPool *pSPool = pDb->pSPool; + + assert( pDb->pSchema->iGeneration==pSPool->sSchema.iGeneration ); + pDb->pSchema = &pSPool->sSchema; + + assert( pDb->pSPool && pRelease ); + assert( pRelease->schemaFlags & DB_SchemaLoaded ); + assert( (pDb->pSchema->schemaFlags & DB_SchemaLoaded)==0 ); + assert( sqlite3_mutex_held(sqlite3_mutex_alloc(SQLITE_MUTEX_STATIC_MASTER)) ); + + /* If the DBFLAG_FreeSchema flag is set and the database connection holds + ** at least one other copy of the schema being released, delete it instead + ** of returning it to the schema-pool. */ + if( db->mDbFlags & DBFLAG_FreeSchema ){ + int i; + for(i=0; inDb; i++){ + Db *p = &db->aDb[i]; + if( p!=pDb && p->pSchema!=&pSPool->sSchema && pDb->pSPool==p->pSPool ){ + pSPool->nDelete++; + schemaDelete(pRelease); + return; + } + } + } + + pRelease->pNext = pDb->pSPool->pSchema; + pDb->pSPool->pSchema = pRelease; +} + +/* +** The schema for database iDb of database handle db, which was opened +** with SQLITE_OPEN_SHARED_SCHEMA, has just been parsed. This function either +** finds a matching SchemaPool object on the global list (schemaPoolList) or +** else allocates a new one and sets the Db.pSPool variable accordingly. +** +** SQLITE_OK is returned if no error occurs, or an SQLite error code +** (SQLITE_NOMEM) otherwise. +*/ +int sqlite3SchemaConnect(sqlite3 *db, int iDb, u64 cksum){ + Schema *pSchema = db->aDb[iDb].pSchema; + SchemaPool *p; + + assert( pSchema && iDb!=1 && db->aDb[iDb].pSPool==0 ); + + sqlite3_mutex_enter( sqlite3_mutex_alloc(SQLITE_MUTEX_STATIC_MASTER) ); + + /* Search for a matching SchemaPool object */ + for(p=schemaPoolList; p; p=p->pNext){ + if( p->cksum==cksum && p->sSchema.schema_cookie==pSchema->schema_cookie ){ + break; + } + } + if( !p ){ + /* No SchemaPool object found. Allocate a new one. */ + p = (SchemaPool*)sqlite3_malloc(sizeof(SchemaPool)); + if( p ){ + memset(p, 0, sizeof(SchemaPool)); + p->cksum = cksum; + p->pNext = schemaPoolList; + schemaPoolList = p; + + p->sSchema.schema_cookie = pSchema->schema_cookie; + p->sSchema.iGeneration = pSchema->iGeneration; + p->sSchema.file_format = pSchema->file_format; + p->sSchema.enc = pSchema->enc; + p->sSchema.cache_size = pSchema->cache_size; + } + } + + if( p ) p->nRef++; + + /* If the SchemaPool contains one or more free schemas at the moment, + ** delete one of them. */ + if( p && p->pSchema ){ + Schema *pDel = p->pSchema; + p->pSchema = pDel->pNext; + schemaDelete(pDel); + } + + sqlite3_mutex_leave( sqlite3_mutex_alloc(SQLITE_MUTEX_STATIC_MASTER) ); + + db->aDb[iDb].pSPool = p; + return (p ? SQLITE_OK : SQLITE_NOMEM); +} + +/* +** If parameter iDb is 1 (the temp db), or if connection handle db was not +** opened with the SQLITE_OPEN_SHARED_SCHEMA flag, this function is a no-op. +** Otherwise, it disconnects from the schema-pool associated with database +** iDb, assuming it is connected. +** +** If parameter bNew is true, then Db.pSchema is set to point to a new, empty, +** Schema object obtained from sqlite3_malloc(). Or, if bNew is false, then +** Db.pSchema is set to NULL before returning. +** +** If the bNew parameter is true, then this function may allocate memory. +** If the allocation attempt fails, then SQLITE_NOMEM is returned and the +** schema-pool is not disconnected from. Or, if no OOM error occurs, +** SQLITE_OK is returned. +*/ +int sqlite3SchemaDisconnect(sqlite3 *db, int iDb, int bNew){ + int rc = SQLITE_OK; + if( IsSharedSchema(db) ){ + Db *pDb = &db->aDb[iDb]; + SchemaPool *pSPool = pDb->pSPool; + assert_schema_state_ok(db); + assert( pDb->pSchema ); + + if( pSPool==0 ){ + assert( pDb->pVTable==0 ); + assert( bNew==0 ); + schemaDelete(pDb->pSchema); + pDb->pSchema = 0; + }else{ + VTable *p; + VTable *pNext; + for(p=pDb->pVTable; p; p=pNext){ + pNext = p->pNext; + sqlite3VtabUnlock(p); + } + pDb->pVTable = 0; + sqlite3_mutex_enter( sqlite3_mutex_alloc(SQLITE_MUTEX_STATIC_MASTER) ); + if( DbHasProperty(db, iDb, DB_SchemaLoaded) ){ + schemaRelease(db, pDb); + } + if( bNew ){ + Schema *pNew = sqlite3SchemaGet(db, 0); + if( pNew==0 ){ + rc = SQLITE_NOMEM; + }else{ + pDb->pSchema = pNew; + } + } + if( rc==SQLITE_OK ){ + assert( pSPool->nRef>=1 ); + pDb->pSPool = 0; + pSPool->nRef--; + if( pSPool->nRef<=0 ){ + SchemaPool **pp; + while( pSPool->pSchema ){ + Schema *pNext = pSPool->pSchema->pNext; + schemaDelete(pSPool->pSchema); + pSPool->pSchema = pNext; + } + for(pp=&schemaPoolList; (*pp)!=pSPool; pp=&((*pp)->pNext)); + *pp = pSPool->pNext; + sqlite3_free(pSPool); + } + } + sqlite3_mutex_leave( sqlite3_mutex_alloc(SQLITE_MUTEX_STATIC_MASTER) ); + } + } + return rc; +} + +/* +** Extract and return a pointer to a schema object from the SchemaPool passed +** as the only argument, if one is available. If one is not available, return +** NULL. +*/ +Schema *sqlite3SchemaExtract(SchemaPool *pSPool){ + Schema *pRet = 0; + sqlite3_mutex_enter( sqlite3_mutex_alloc(SQLITE_MUTEX_STATIC_MASTER) ); + if( pSPool->pSchema ){ + pRet = pSPool->pSchema; + pSPool->pSchema = pRet->pNext; + pRet->pNext = 0; + } + sqlite3_mutex_leave( sqlite3_mutex_alloc(SQLITE_MUTEX_STATIC_MASTER) ); + return pRet; +} + +/* +** Return all sharable schemas held by database handle db back to their +** respective schema-pools. Db.pSchema variables are left pointing to +** the static, empty, Schema object owned by each schema-pool. +*/ +void sqlite3SchemaReleaseAll(sqlite3 *db){ + int i; + assert_schema_state_ok(db); + sqlite3_mutex_enter( sqlite3_mutex_alloc(SQLITE_MUTEX_STATIC_MASTER) ); + for(i=0; inDb; i++){ + if( i!=1 ){ + Db *pDb = &db->aDb[i]; + if( pDb->pSPool && DbHasProperty(db,i,DB_SchemaLoaded) ){ + schemaRelease(db, pDb); + } + } + } + db->mDbFlags &= ~DBFLAG_FreeSchema; + sqlite3_mutex_leave( sqlite3_mutex_alloc(SQLITE_MUTEX_STATIC_MASTER) ); +} + +/* +** Release any sharable schema held by connection iDb of database handle +** db. Db.pSchema is left pointing to the static, empty, Schema object +** owned by the schema-pool. +*/ +void sqlite3SchemaRelease(sqlite3 *db, int iDb){ + Db *pDb = &db->aDb[iDb]; + assert( iDb!=1 ); + assert_schema_state_ok(db); + sqlite3_mutex_enter( sqlite3_mutex_alloc(SQLITE_MUTEX_STATIC_MASTER) ); + schemaRelease(db, pDb); + sqlite3_mutex_leave( sqlite3_mutex_alloc(SQLITE_MUTEX_STATIC_MASTER) ); +} + +#endif /* ifdef SQLITE_ENABLE_SHARED_SCHEMA */ + +/* +** In most cases, this function finds and returns the schema associated +** with BTree handle pBt, creating a new one if necessary. However, if +** the database handle was opened with the SQLITE_OPEN_SHARED_SCHEMA flag +** specified, a new, empty, Schema object in memory obtained by +** sqlite3_malloc() is always returned. */ Schema *sqlite3SchemaGet(sqlite3 *db, Btree *pBt){ - Schema * p; - if( pBt ){ - p = (Schema *)sqlite3BtreeSchema(pBt, sizeof(Schema), sqlite3SchemaClear); + Schema *p; + if( pBt && IsSharedSchema(db)==0 ){ + p = (Schema*)sqlite3BtreeSchema(pBt, sizeof(Schema), sqlite3SchemaClear); }else{ - p = (Schema *)sqlite3DbMallocZero(0, sizeof(Schema)); + p = (Schema*)sqlite3DbMallocZero(0, sizeof(Schema)); } if( !p ){ sqlite3OomFault(db); }else if ( 0==p->file_format ){ sqlite3HashInit(&p->tblHash); Index: src/main.c ================================================================== --- src/main.c +++ src/main.c @@ -1224,10 +1224,21 @@ for(p=sqliteHashFirst(&pSchema->tblHash); p; p=sqliteHashNext(p)){ Table *pTab = (Table *)sqliteHashData(p); if( IsVirtual(pTab) ) sqlite3VtabDisconnect(db, pTab); } } +#ifdef SQLITE_ENABLE_SHARED_SCHEMA + if( IsSharedSchema(db) && i!=1 ){ + VTable *pVTable; + VTable *pNext; + for(pVTable=db->aDb[i].pVTable; pVTable; pVTable=pNext){ + pNext = pVTable->pNext; + sqlite3VtabUnlock(pVTable); + } + db->aDb[i].pVTable = 0; + } +#endif /* ifdef SQLITE_ENABLE_SHARED_SCHEMA */ } for(p=sqliteHashFirst(&db->aModule); p; p=sqliteHashNext(p)){ Module *pMod = (Module *)sqliteHashData(p); if( pMod->pEpoTab ){ sqlite3VtabDisconnect(db, pMod->pEpoTab); @@ -1400,10 +1411,11 @@ struct Db *pDb = &db->aDb[j]; if( pDb->pBt ){ sqlite3BtreeClose(pDb->pBt); pDb->pBt = 0; if( j!=1 ){ + (void)sqlite3SchemaDisconnect(db, j, 0); pDb->pSchema = 0; } } } /* Clear the TEMP schema separately and last */ @@ -3933,38 +3945,53 @@ char const **pzCollSeq, /* OUTPUT: Collation sequence name */ int *pNotNull, /* OUTPUT: True if NOT NULL constraint exists */ int *pPrimaryKey, /* OUTPUT: True if column part of PK */ int *pAutoinc /* OUTPUT: True if column is auto-increment */ ){ - int rc; + int rc = SQLITE_OK; char *zErrMsg = 0; Table *pTab = 0; Column *pCol = 0; int iCol = 0; char const *zDataType = 0; char const *zCollSeq = 0; int notnull = 0; int primarykey = 0; int autoinc = 0; - + int bUnlock; #ifdef SQLITE_ENABLE_API_ARMOR if( !sqlite3SafetyCheckOk(db) || zTableName==0 ){ return SQLITE_MISUSE_BKPT; } #endif /* Ensure the database schema has been loaded */ sqlite3_mutex_enter(db->mutex); + bUnlock = sqlite3LockReusableSchema(db); sqlite3BtreeEnterAll(db); - rc = sqlite3Init(db, &zErrMsg); - if( SQLITE_OK!=rc ){ - goto error_out; + if( IsSharedSchema(db)==0 ){ + rc = sqlite3Init(db, &zErrMsg); } /* Locate the table in question */ - pTab = sqlite3FindTable(db, zTableName, zDbName); + if( rc==SQLITE_OK ){ +#ifdef SQLITE_ENABLE_SHARED_SCHEMA + Parse sParse; /* Fake Parse object for FindTable */ + Parse *pSaved = db->pParse; + memset(&sParse, 0, sizeof(sParse)); + db->pParse = &sParse; +#endif + pTab = sqlite3FindTable(db, zTableName, zDbName); +#ifdef SQLITE_ENABLE_SHARED_SCHEMA + sqlite3_free(sParse.zErrMsg); + rc = sParse.rc; + db->pParse = pSaved; +#endif + } + if( SQLITE_OK!=rc ) goto error_out; + if( !pTab || IsView(pTab) ){ pTab = 0; goto error_out; } @@ -4033,10 +4060,11 @@ rc = SQLITE_ERROR; } sqlite3ErrorWithMsg(db, rc, (zErrMsg?"%s":0), zErrMsg); sqlite3DbFree(db, zErrMsg); rc = sqlite3ApiExit(db, rc); + sqlite3UnlockReusableSchema(db, bUnlock); sqlite3_mutex_leave(db->mutex); return rc; } /* Index: src/pragma.c ================================================================== --- src/pragma.c +++ src/pragma.c @@ -502,11 +502,17 @@ goto pragma_out; } /* Make sure the database schema is loaded if the pragma requires that */ if( (pPragma->mPragFlg & PragFlg_NeedSchema)!=0 ){ - if( sqlite3ReadSchema(pParse) ) goto pragma_out; + if( IsSharedSchema(db) && (zDb || (pPragma->mPragFlg & PragFlg_OneSchema)) ){ + assert( iDb>=0 && iDbnDb ); + pParse->rc = sqlite3SchemaLoad(db, iDb, 0, &pParse->zErrMsg); + if( pParse->rc ) goto pragma_out; + }else{ + if( sqlite3ReadSchema(pParse) ) goto pragma_out; + } } /* Register the result column names for pragmas that return results */ if( (pPragma->mPragFlg & PragFlg_NoColumns)==0 && ((pPragma->mPragFlg & PragFlg_NoColumns1)==0 || zRight==0) @@ -2293,13 +2299,14 @@ ** ** The user-version is not used internally by SQLite. It may be used by ** applications for any purpose. */ case PragTyp_HEADER_VALUE: { - int iCookie = pPragma->iArg; /* Which cookie to read or write */ + int iCookie; /* Which cookie to read or write */ + iCookie = pPragma->iArg & PRAGMA_HEADER_VALUE_MASK; sqlite3VdbeUsesBtree(v, iDb); - if( zRight && (pPragma->mPragFlg & PragFlg_ReadOnly)==0 ){ + if( zRight && (pPragma->iArg & PRAGMA_HEADER_VALUE_READONLY)==0 ){ /* Write the specified cookie value */ static const VdbeOpList setCookie[] = { { OP_Transaction, 0, 1, 0}, /* 0 */ { OP_SetCookie, 0, 0, 0}, /* 1 */ }; Index: src/pragma.h ================================================================== --- src/pragma.h +++ src/pragma.h @@ -54,15 +54,22 @@ /* Property flags associated with various pragma. */ #define PragFlg_NeedSchema 0x01 /* Force schema load before running */ #define PragFlg_NoColumns 0x02 /* OP_ResultRow called with zero columns */ #define PragFlg_NoColumns1 0x04 /* zero columns if RHS argument is present */ -#define PragFlg_ReadOnly 0x08 /* Read-only HEADER_VALUE */ +#define PragFlg_OneSchema 0x08 /* Only a single schema required */ #define PragFlg_Result0 0x10 /* Acts as query when no argument */ #define PragFlg_Result1 0x20 /* Acts as query when has one argument */ #define PragFlg_SchemaOpt 0x40 /* Schema restricts name search if present */ #define PragFlg_SchemaReq 0x80 /* Schema required - "main" is default */ + +/* For PragTyp_HEADER_VALUE pragmas the Pragma.iArg value is set +** to the index of the header field to access (always 10 or less). +** Ored with HEADER_VALUE_READONLY if the field is read only. */ +#define PRAGMA_HEADER_VALUE_READONLY 0x0100 +#define PRAGMA_HEADER_VALUE_MASK 0x00FF + /* Names of columns for pragmas that return multi-column result ** or that return single-column results where the name of the ** result column is different from the name of the pragma */ @@ -160,11 +167,11 @@ /* iArg: */ BTREE_APPLICATION_ID }, #endif #if !defined(SQLITE_OMIT_AUTOVACUUM) {/* zName: */ "auto_vacuum", /* ePragTyp: */ PragTyp_AUTO_VACUUM, - /* ePragFlg: */ PragFlg_NeedSchema|PragFlg_Result0|PragFlg_SchemaReq|PragFlg_NoColumns1, + /* ePragFlg: */ PragFlg_NeedSchema|PragFlg_Result0|PragFlg_SchemaReq|PragFlg_NoColumns1|PragFlg_OneSchema, /* ColNames: */ 0, 0, /* iArg: */ 0 }, #endif #if !defined(SQLITE_OMIT_FLAG_PRAGMAS) #if !defined(SQLITE_OMIT_AUTOMATIC_INDEX) @@ -181,11 +188,11 @@ /* ColNames: */ 56, 1, /* iArg: */ 0 }, #if !defined(SQLITE_OMIT_PAGER_PRAGMAS) {/* zName: */ "cache_size", /* ePragTyp: */ PragTyp_CACHE_SIZE, - /* ePragFlg: */ PragFlg_NeedSchema|PragFlg_Result0|PragFlg_SchemaReq|PragFlg_NoColumns1, + /* ePragFlg: */ PragFlg_NeedSchema|PragFlg_Result0|PragFlg_SchemaReq|PragFlg_NoColumns1|PragFlg_OneSchema, /* ColNames: */ 0, 0, /* iArg: */ 0 }, #endif #if !defined(SQLITE_OMIT_FLAG_PRAGMAS) {/* zName: */ "cache_spill", @@ -242,13 +249,13 @@ /* iArg: */ 0 }, #endif #if !defined(SQLITE_OMIT_SCHEMA_VERSION_PRAGMAS) {/* zName: */ "data_version", /* ePragTyp: */ PragTyp_HEADER_VALUE, - /* ePragFlg: */ PragFlg_ReadOnly|PragFlg_Result0, + /* ePragFlg: */ PragFlg_Result0, /* ColNames: */ 0, 0, - /* iArg: */ BTREE_DATA_VERSION }, + /* iArg: */ BTREE_DATA_VERSION|PRAGMA_HEADER_VALUE_READONLY }, #endif #if !defined(SQLITE_OMIT_SCHEMA_PRAGMAS) {/* zName: */ "database_list", /* ePragTyp: */ PragTyp_DATABASE_LIST, /* ePragFlg: */ PragFlg_Result0, @@ -256,11 +263,11 @@ /* iArg: */ 0 }, #endif #if !defined(SQLITE_OMIT_PAGER_PRAGMAS) && !defined(SQLITE_OMIT_DEPRECATED) {/* zName: */ "default_cache_size", /* ePragTyp: */ PragTyp_DEFAULT_CACHE_SIZE, - /* ePragFlg: */ PragFlg_NeedSchema|PragFlg_Result0|PragFlg_SchemaReq|PragFlg_NoColumns1, + /* ePragFlg: */ PragFlg_NeedSchema|PragFlg_Result0|PragFlg_SchemaReq|PragFlg_NoColumns1|PragFlg_OneSchema, /* ColNames: */ 55, 1, /* iArg: */ 0 }, #endif #if !defined(SQLITE_OMIT_FLAG_PRAGMAS) #if !defined(SQLITE_OMIT_FOREIGN_KEY) && !defined(SQLITE_OMIT_TRIGGER) @@ -293,11 +300,11 @@ /* iArg: */ 0 }, #endif #if !defined(SQLITE_OMIT_FOREIGN_KEY) {/* zName: */ "foreign_key_list", /* ePragTyp: */ PragTyp_FOREIGN_KEY_LIST, - /* ePragFlg: */ PragFlg_NeedSchema|PragFlg_Result1|PragFlg_SchemaOpt, + /* ePragFlg: */ PragFlg_NeedSchema|PragFlg_Result1|PragFlg_SchemaOpt|PragFlg_OneSchema, /* ColNames: */ 0, 8, /* iArg: */ 0 }, #endif #if !defined(SQLITE_OMIT_FLAG_PRAGMAS) #if !defined(SQLITE_OMIT_FOREIGN_KEY) && !defined(SQLITE_OMIT_TRIGGER) @@ -309,13 +316,13 @@ #endif #endif #if !defined(SQLITE_OMIT_SCHEMA_VERSION_PRAGMAS) {/* zName: */ "freelist_count", /* ePragTyp: */ PragTyp_HEADER_VALUE, - /* ePragFlg: */ PragFlg_ReadOnly|PragFlg_Result0, + /* ePragFlg: */ PragFlg_Result0, /* ColNames: */ 0, 0, - /* iArg: */ BTREE_FREE_PAGE_COUNT }, + /* iArg: */ BTREE_FREE_PAGE_COUNT|PRAGMA_HEADER_VALUE_READONLY }, #endif #if !defined(SQLITE_OMIT_FLAG_PRAGMAS) {/* zName: */ "full_column_names", /* ePragTyp: */ PragTyp_FLAG, /* ePragFlg: */ PragFlg_Result0|PragFlg_NoColumns1, @@ -351,11 +358,11 @@ #endif #endif #if !defined(SQLITE_OMIT_AUTOVACUUM) {/* zName: */ "incremental_vacuum", /* ePragTyp: */ PragTyp_INCREMENTAL_VACUUM, - /* ePragFlg: */ PragFlg_NeedSchema|PragFlg_NoColumns, + /* ePragFlg: */ PragFlg_NeedSchema|PragFlg_NoColumns|PragFlg_OneSchema, /* ColNames: */ 0, 0, /* iArg: */ 0 }, #endif #if !defined(SQLITE_OMIT_SCHEMA_PRAGMAS) {/* zName: */ "index_info", @@ -382,11 +389,11 @@ /* iArg: */ 0 }, #endif #if !defined(SQLITE_OMIT_PAGER_PRAGMAS) {/* zName: */ "journal_mode", /* ePragTyp: */ PragTyp_JOURNAL_MODE, - /* ePragFlg: */ PragFlg_NeedSchema|PragFlg_Result0|PragFlg_SchemaReq, + /* ePragFlg: */ PragFlg_NeedSchema|PragFlg_Result0|PragFlg_SchemaReq|PragFlg_OneSchema, /* ColNames: */ 0, 0, /* iArg: */ 0 }, {/* zName: */ "journal_size_limit", /* ePragTyp: */ PragTyp_JOURNAL_SIZE_LIMIT, /* ePragFlg: */ PragFlg_Result0|PragFlg_SchemaReq, @@ -420,11 +427,11 @@ /* ePragFlg: */ PragFlg_Result0|PragFlg_SchemaReq, /* ColNames: */ 0, 0, /* iArg: */ 0 }, {/* zName: */ "max_page_count", /* ePragTyp: */ PragTyp_PAGE_COUNT, - /* ePragFlg: */ PragFlg_NeedSchema|PragFlg_Result0|PragFlg_SchemaReq, + /* ePragFlg: */ PragFlg_NeedSchema|PragFlg_Result0|PragFlg_SchemaReq|PragFlg_OneSchema, /* ColNames: */ 0, 0, /* iArg: */ 0 }, {/* zName: */ "mmap_size", /* ePragTyp: */ PragTyp_MMAP_SIZE, /* ePragFlg: */ 0, @@ -448,11 +455,11 @@ /* ColNames: */ 0, 0, /* iArg: */ 0 }, #if !defined(SQLITE_OMIT_PAGER_PRAGMAS) {/* zName: */ "page_count", /* ePragTyp: */ PragTyp_PAGE_COUNT, - /* ePragFlg: */ PragFlg_NeedSchema|PragFlg_Result0|PragFlg_SchemaReq, + /* ePragFlg: */ PragFlg_NeedSchema|PragFlg_Result0|PragFlg_SchemaReq|PragFlg_OneSchema, /* ColNames: */ 0, 0, /* iArg: */ 0 }, {/* zName: */ "page_size", /* ePragTyp: */ PragTyp_PAGE_SIZE, /* ePragFlg: */ PragFlg_Result0|PragFlg_SchemaReq|PragFlg_NoColumns1, @@ -547,18 +554,18 @@ #endif #endif #if !defined(SQLITE_OMIT_SCHEMA_PRAGMAS) && defined(SQLITE_DEBUG) {/* zName: */ "stats", /* ePragTyp: */ PragTyp_STATS, - /* ePragFlg: */ PragFlg_NeedSchema|PragFlg_Result0|PragFlg_SchemaReq, + /* ePragFlg: */ PragFlg_NeedSchema|PragFlg_Result0|PragFlg_SchemaReq|PragFlg_OneSchema, /* ColNames: */ 33, 5, /* iArg: */ 0 }, #endif #if !defined(SQLITE_OMIT_PAGER_PRAGMAS) {/* zName: */ "synchronous", /* ePragTyp: */ PragTyp_SYNCHRONOUS, - /* ePragFlg: */ PragFlg_NeedSchema|PragFlg_Result0|PragFlg_SchemaReq|PragFlg_NoColumns1, + /* ePragFlg: */ PragFlg_NeedSchema|PragFlg_Result0|PragFlg_SchemaReq|PragFlg_NoColumns1|PragFlg_OneSchema, /* ColNames: */ 0, 0, /* iArg: */ 0 }, #endif #if !defined(SQLITE_OMIT_SCHEMA_PRAGMAS) {/* zName: */ "table_info", @@ -643,11 +650,11 @@ /* ePragFlg: */ 0, /* ColNames: */ 0, 0, /* iArg: */ 0 }, {/* zName: */ "wal_checkpoint", /* ePragTyp: */ PragTyp_WAL_CHECKPOINT, - /* ePragFlg: */ PragFlg_NeedSchema, + /* ePragFlg: */ PragFlg_NeedSchema|PragFlg_OneSchema, /* ColNames: */ 50, 3, /* iArg: */ 0 }, #endif #if !defined(SQLITE_OMIT_FLAG_PRAGMAS) {/* zName: */ "writable_schema", Index: src/prepare.c ================================================================== --- src/prepare.c +++ src/prepare.c @@ -41,10 +41,15 @@ zExtra ); pData->rc = SQLITE_ERROR; }else if( db->flags & SQLITE_WriteSchema ){ pData->rc = SQLITE_CORRUPT_BKPT; + }else if( IsSharedSchema(db) + && 0==sqlite3StrNICmp(zExtra, "malformed database schema", 17) + ){ + pData->rc = SQLITE_CORRUPT_BKPT; + *pData->pzErrMsg = sqlite3DbStrDup(db, zExtra); }else{ char *z; const char *zObj = azObj[1] ? azObj[1] : "?"; z = sqlite3MPrintf(db, "malformed database schema (%s)", zObj); if( zExtra && zExtra[0] ) z = sqlite3MPrintf(db, "%z - %s", z, zExtra); @@ -51,10 +56,32 @@ *pData->pzErrMsg = z; pData->rc = SQLITE_CORRUPT_BKPT; } } +#ifdef SQLITE_ENABLE_SHARED_SCHEMA +/* +** Update the Schema.cksum checksum to account for the database object +** specified by the three arguments following the first. +*/ +static void schemaUpdateChecksum( + InitData *pData, /* Schema parse context */ + 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 = pData->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]; + pData->cksum = cksum; +} +#endif /* ifdef SQLITE_ENABLE_SHARED_SCHEMA */ + /* ** Check to see if any sibling index (another index on the same table) ** of pIndex has the same root page number, and if it does, return true. ** This would indicate a corrupt schema. */ @@ -66,10 +93,19 @@ return 0; } /* forward declaration */ static int sqlite3Prepare( + sqlite3 *db, /* Database handle. */ + const char *zSql, /* UTF-8 encoded SQL statement. */ + int nBytes, /* Length of zSql in bytes. */ + u32 prepFlags, /* Zero or more SQLITE_PREPARE_* flags */ + Vdbe *pReprepare, /* VM being reprepared */ + sqlite3_stmt **ppStmt, /* OUT: A pointer to the prepared statement */ + const char **pzTail /* OUT: End of parsed string */ +); +static int sqlite3LockAndPrepare( sqlite3 *db, /* Database handle. */ const char *zSql, /* UTF-8 encoded SQL statement. */ int nBytes, /* Length of zSql in bytes. */ u32 prepFlags, /* Zero or more SQLITE_PREPARE_* flags */ Vdbe *pReprepare, /* VM being reprepared */ @@ -139,11 +175,15 @@ } } db->init.orphanTrigger = 0; db->init.azInit = (const char**)argv; pStmt = 0; +#ifdef SQLITE_ENABLE_SHARED_SCHEMA + TESTONLY(rcp = ) sqlite3LockAndPrepare(db, argv[4], -1, 0, 0, &pStmt, 0); +#else TESTONLY(rcp = ) sqlite3Prepare(db, argv[4], -1, 0, 0, &pStmt, 0); +#endif rc = db->errCode; assert( (rc&0xFF)==(rcp&0xFF) ); db->init.iDb = saved_iDb; /* assert( saved_iDb==0 || (db->mDbFlags & DBFLAG_Vacuum)!=0 ); */ if( SQLITE_OK!=rc ){ @@ -151,11 +191,15 @@ assert( iDb==1 ); }else{ if( rc > pData->rc ) pData->rc = rc; if( rc==SQLITE_NOMEM ){ sqlite3OomFault(db); - }else if( rc!=SQLITE_INTERRUPT && (rc&0xFF)!=SQLITE_LOCKED ){ + }else if( rc!=SQLITE_INTERRUPT && (rc&0xFF)!=SQLITE_LOCKED +#ifdef SQLITE_ENABLE_SHARED_SCHEMA + && (rc&0xFF)!=SQLITE_IOERR +#endif + ){ corruptSchema(pData, argv, sqlite3_errmsg(db)); } } } db->init.azInit = sqlite3StdType; /* Any array of string ptrs will do */ @@ -182,10 +226,16 @@ if( sqlite3Config.bExtraSchemaChecks ){ corruptSchema(pData, argv, "invalid rootpage"); } } } + +#ifdef SQLITE_ENABLE_SHARED_SCHEMA + if( IsSharedSchema(db) && iDb!=1 ){ + schemaUpdateChecksum(pData, argv[0], argv[1], argv[2]); + } +#endif return 0; } /* ** Attempt to read the database schema and initialize internal @@ -209,13 +259,31 @@ int openedTransaction = 0; int mask = ((db->mDbFlags & DBFLAG_EncodingFixed) | ~DBFLAG_EncodingFixed); assert( (db->mDbFlags & DBFLAG_SchemaKnownOk)==0 ); assert( iDb>=0 && iDbnDb ); - assert( db->aDb[iDb].pSchema ); + assert( db->aDb[iDb].pSchema || (IsSharedSchema(db) && iDb!=1) ); assert( sqlite3_mutex_held(db->mutex) ); assert( iDb==1 || sqlite3BtreeHoldsMutex(db->aDb[iDb].pBt) ); + + pDb = &db->aDb[iDb]; +#ifdef SQLITE_ENABLE_SHARED_SCHEMA + assert( pDb->pSPool==0 || IsSharedSchema(db) ); + if( pDb->pSPool ){ + /* See if there is a free schema object in the schema-pool. If not, + ** disconnect from said schema pool and continue. This function will + ** connect to a (possibly different) schema-pool before returning. */ + Schema *pNew = sqlite3SchemaExtract(pDb->pSPool); + if( pNew ){ + pDb->pSchema = pNew; + return SQLITE_OK; + } + rc = sqlite3SchemaDisconnect(db, iDb, 1); + if( rc!=SQLITE_OK ) goto error_out; + assert( pDb->pSchema && pDb->pSPool==0 ); + } +#endif db->init.busy = 1; /* Construct the in-memory representation schema tables (sqlite_schema or ** sqlite_temp_schema) by invoking the parser directly. The appropriate @@ -233,10 +301,11 @@ initData.iDb = iDb; initData.rc = SQLITE_OK; initData.pzErrMsg = pzErrMsg; initData.mInitFlags = mFlags; initData.nInitRow = 0; + initData.cksum = 0; initData.mxPage = 0; sqlite3InitCallback(&initData, 5, (char **)azArg, 0); db->mDbFlags &= mask; if( initData.rc ){ rc = initData.rc; @@ -243,11 +312,10 @@ goto error_out; } /* Create a cursor to hold the database open */ - pDb = &db->aDb[iDb]; if( pDb->pBt==0 ){ assert( iDb==1 ); DbSetProperty(db, 1, DB_SchemaLoaded); rc = SQLITE_OK; goto error_out; @@ -408,10 +476,14 @@ ** table even when its contents have been corrupted. */ DbSetProperty(db, iDb, DB_SchemaLoaded); rc = SQLITE_OK; } + + if( rc==SQLITE_OK && iDb!=1 && IsSharedSchema(db) ){ + rc = sqlite3SchemaConnect(db, iDb, initData.cksum); + } /* 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. */ @@ -430,10 +502,39 @@ } db->init.busy = 0; return rc; } + +#ifdef SQLITE_ENABLE_SHARED_SCHEMA +/* +** If this is a SHARED_SCHEMA connection and the DBFLAG_SchemaInUse flag +** is not currently set, set it and return non-zero. Otherwise, return 0. +*/ +int sqlite3LockReusableSchema(sqlite3 *db){ + if( IsSharedSchema(db) && (db->mDbFlags & DBFLAG_SchemaInuse)==0 ){ + db->mDbFlags |= DBFLAG_SchemaInuse; + return 1; + } + return 0; +} +#endif /* ifdef SQLITE_ENABLE_SHARED_SCHEMA */ + +#ifdef SQLITE_ENABLE_SHARED_SCHEMA +/* +** This function is a no-op for non-SHARED_SCHEMA connections, or if bRelease +** is zero. Otherwise, clear the DBFLAG_SchemaInuse flag and release all +** schema references currently held. +*/ +void sqlite3UnlockReusableSchema(sqlite3 *db, int bRelease){ + if( bRelease ){ + db->mDbFlags &= ~DBFLAG_SchemaInuse; + sqlite3SchemaReleaseAll(db); + } +} +#endif /* ifdef SQLITE_ENABLE_SHARED_SCHEMA */ + /* ** Initialize all database files - the main database file, the file ** used to store temporary tables, and any additional database files ** created using ATTACH statements. Return a success code. If an ** error occurs, write an error message into *pzErrMsg. @@ -440,35 +541,38 @@ ** ** After a database is initialized, the DB_SchemaLoaded bit is set ** bit is set in the flags field of the Db structure. */ int sqlite3Init(sqlite3 *db, char **pzErrMsg){ - int i, rc; + int rc = SQLITE_OK; + int bReleaseSchema; + int i; int commit_internal = !(db->mDbFlags&DBFLAG_SchemaChange); + + bReleaseSchema = sqlite3LockReusableSchema(db); assert( sqlite3_mutex_held(db->mutex) ); assert( sqlite3BtreeHoldsMutex(db->aDb[0].pBt) ); assert( db->init.busy==0 ); ENC(db) = SCHEMA_ENC(db); assert( db->nDb>0 ); /* Do the main schema first */ if( !DbHasProperty(db, 0, DB_SchemaLoaded) ){ rc = sqlite3InitOne(db, 0, pzErrMsg, 0); - if( rc ) return rc; } /* All other schemas after the main schema. The "temp" schema must be last */ - for(i=db->nDb-1; i>0; i--){ + for(i=db->nDb-1; rc==SQLITE_OK && i>0; i--){ assert( i==1 || sqlite3BtreeHoldsMutex(db->aDb[i].pBt) ); if( !DbHasProperty(db, i, DB_SchemaLoaded) ){ rc = sqlite3InitOne(db, i, pzErrMsg, 0); - if( rc ) return rc; } } - if( commit_internal ){ + if( rc==SQLITE_OK && commit_internal ){ sqlite3CommitInternalChanges(db); } - return SQLITE_OK; + sqlite3UnlockReusableSchema(db, bReleaseSchema); + return rc; } /* ** This routine is a no-op if the database schema is already initialized. ** Otherwise, the schema is loaded. An error code is returned. @@ -476,15 +580,16 @@ int sqlite3ReadSchema(Parse *pParse){ int rc = SQLITE_OK; sqlite3 *db = pParse->db; assert( sqlite3_mutex_held(db->mutex) ); if( !db->init.busy ){ + db->mDbFlags |= DBFLAG_FreeSchema; /* For sharable-schema mode */ rc = sqlite3Init(db, &pParse->zErrMsg); if( rc!=SQLITE_OK ){ pParse->rc = rc; pParse->nErr++; - }else if( db->noSharedCache ){ + }else if( db->noSharedCache && !IsSharedSchema(db) ){ db->mDbFlags |= DBFLAG_SchemaKnownOk; } } return rc; } @@ -505,10 +610,14 @@ assert( sqlite3_mutex_held(db->mutex) ); for(iDb=0; iDbnDb; iDb++){ int openedTransaction = 0; /* True if a transaction is opened */ Btree *pBt = db->aDb[iDb].pBt; /* Btree database to read cookie from */ if( pBt==0 ) continue; + +#ifdef SQLITE_ENABLE_SHARED_SCHEMA + if( IsSharedSchema(db) && iDb!=1 && db->aDb[iDb].pSPool==0 ) continue; +#endif /* If there is not already a read-only (or read-write) transaction opened ** on the b-tree database, open one now. If a transaction is opened, it ** will be closed immediately after reading the meta-value. */ if( sqlite3BtreeTxnState(pBt)==SQLITE_TXN_NONE ){ @@ -842,19 +951,21 @@ sqlite3_stmt **ppStmt, /* OUT: A pointer to the prepared statement */ const char **pzTail /* OUT: End of parsed string */ ){ int rc; int cnt = 0; + int bReleaseSchema = 0; #ifdef SQLITE_ENABLE_API_ARMOR if( ppStmt==0 ) return SQLITE_MISUSE_BKPT; #endif *ppStmt = 0; if( !sqlite3SafetyCheckOk(db)||zSql==0 ){ return SQLITE_MISUSE_BKPT; } sqlite3_mutex_enter(db->mutex); + bReleaseSchema = sqlite3LockReusableSchema(db); sqlite3BtreeEnterAll(db); do{ /* Make multiple attempts to compile the SQL, until it either succeeds ** or encounters a permanent error. A schema problem after one schema ** reset is considered a permanent error. */ @@ -861,11 +972,15 @@ rc = sqlite3Prepare(db, zSql, nBytes, prepFlags, pOld, ppStmt, pzTail); assert( rc==SQLITE_OK || *ppStmt==0 ); if( rc==SQLITE_OK || db->mallocFailed ) break; }while( (rc==SQLITE_ERROR_RETRY && (cnt++)errMask)==rc ); db->busyHandler.nBusy = 0; sqlite3_mutex_leave(db->mutex); assert( rc==SQLITE_OK || (*ppStmt)==0 ); Index: src/shell.c.in ================================================================== --- src/shell.c.in +++ src/shell.c.in @@ -1382,17 +1382,18 @@ #define AUTOEQP_trigger 2 /* On and also show plans for triggers */ #define AUTOEQP_full 3 /* Show full EXPLAIN */ /* Allowed values for ShellState.openMode */ -#define SHELL_OPEN_UNSPEC 0 /* No open-mode specified */ -#define SHELL_OPEN_NORMAL 1 /* Normal database file */ -#define SHELL_OPEN_APPENDVFS 2 /* Use appendvfs */ -#define SHELL_OPEN_ZIPFILE 3 /* Use the zipfile virtual table */ -#define SHELL_OPEN_READONLY 4 /* Open a normal database read-only */ -#define SHELL_OPEN_DESERIALIZE 5 /* Open using sqlite3_deserialize() */ -#define SHELL_OPEN_HEXDB 6 /* Use "dbtotxt" output as data source */ +#define SHELL_OPEN_UNSPEC 0 /* No open-mode specified */ +#define SHELL_OPEN_NORMAL 1 /* Normal database file */ +#define SHELL_OPEN_APPENDVFS 2 /* Use appendvfs */ +#define SHELL_OPEN_ZIPFILE 3 /* Use the zipfile virtual table */ +#define SHELL_OPEN_READONLY 4 /* Open a normal database read-only */ +#define SHELL_OPEN_DESERIALIZE 5 /* Open using sqlite3_deserialize() */ +#define SHELL_OPEN_HEXDB 6 /* Use "dbtotxt" output as data source */ +#define SHELL_OPEN_SHAREDSCHEMA 7 /* Open for schema reuse */ /* Allowed values for ShellState.eTraceType */ #define SHELL_TRACE_PLAIN 0 /* Show input SQL text */ #define SHELL_TRACE_EXPANDED 1 /* Show expanded SQL text */ @@ -4881,10 +4882,16 @@ " --sha3-224 Use the sha3-224 algorithm", " --sha3-256 Use the sha3-256 algorithm (default)", " --sha3-384 Use the sha3-384 algorithm", " --sha3-512 Use the sha3-512 algorithm", " Any other argument is a LIKE pattern for tables to hash", +#if !defined(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_ENABLE_DBPAGE_VTAB) + ".shared-schema CMD DB1 DB2 ...", + " Commands:", + " check Determine if DB1, DB2, etc have identical schemas", + " fix Attempt to make DB1, DB2, etc compatible", +#endif #if !defined(SQLITE_NOHAVE_SYSTEM) && !defined(SQLITE_SHELL_FIDDLE) ".shell CMD ARGS... Run CMD ARGS... in a system shell", #endif ".show Show the current values for various settings", ".stats ?ARG? Show stats or turn stats on or off", @@ -5323,10 +5330,15 @@ } case SHELL_OPEN_READONLY: { sqlite3_open_v2(zDbFilename, &p->db, SQLITE_OPEN_READONLY|p->openFlags, 0); break; + } + case SHELL_OPEN_SHAREDSCHEMA: { + sqlite3_open_v2(p->pAuxDb->zDbFilename, &p->db, + SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE|SQLITE_OPEN_SHARED_SCHEMA,0); + break; } case SHELL_OPEN_UNSPEC: case SHELL_OPEN_NORMAL: { sqlite3_open_v2(zDbFilename, &p->db, SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE|p->openFlags, 0); @@ -7574,13 +7586,282 @@ return rc; } /* End of the ".archive" or ".ar" command logic *******************************************************************************/ #endif /* !defined(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_HAVE_ZLIB) */ + +/* +** If (*pRc) is not SQLITE_OK when this function is called, it is a no-op. +** Otherwise, the SQL statement or statements in zSql are executed using +** database connection db and the error code written to *pRc before +** this function returns. +*/ +static void shellExec(sqlite3 *db, int *pRc, const char *zSql){ + int rc = *pRc; + if( rc==SQLITE_OK ){ + char *zErr = 0; + rc = sqlite3_exec(db, zSql, 0, 0, &zErr); + if( rc!=SQLITE_OK ){ + sputf(stderr, "SQL error: %s\n", zErr); + } + sqlite3_free(zErr); + *pRc = rc; + } +} + +/* +** Like shellExec(), except that zFmt is a printf() style format string. +*/ +static void shellExecPrintf(sqlite3 *db, int *pRc, const char *zFmt, ...){ + char *z = 0; + if( *pRc==SQLITE_OK ){ + va_list ap; + va_start(ap, zFmt); + z = sqlite3_vmprintf(zFmt, ap); + va_end(ap); + if( z==0 ){ + *pRc = SQLITE_NOMEM; + }else{ + shellExec(db, pRc, z); + } + sqlite3_free(z); + } +} + +static int sharedSchemaFix(ShellState *pState, const char *zDb){ + int rc = SQLITE_OK; + i64 iLast = 0; + int iCookie = 0; + int iAutoVacuum = 0; + sqlite3_stmt *pStmt = 0; + + shellExecPrintf(pState->db, &rc, "ATTACH '%q' AS _shared_schema_tmp", zDb); + shellExecPrintf(pState->db, &rc, "PRAGMA writable_schema = 1"); + shellExecPrintf(pState->db, &rc, "BEGIN"); + shellPreparePrintf(pState->db, &rc, &pStmt, + "SELECT max(rowid) FROM _shared_schema_tmp.sqlite_master" + ); + sqlite3_step(pStmt); + iLast = sqlite3_column_int64(pStmt, 0); + shellFinalize(&rc, pStmt); + shellPreparePrintf(pState->db, &rc, &pStmt, + "INSERT INTO _shared_schema_tmp.sqlite_master SELECT " + " type, name, tbl_name, (" + " SELECT rootpage FROM _shared_schema_tmp.sqlite_master WHERE " + " type IS o.type AND name IS o.name AND rowid<=?" + " ), sql FROM main.sqlite_master AS o" + ); + sqlite3_bind_int64(pStmt, 1, iLast); + sqlite3_step(pStmt); + shellFinalize(&rc, pStmt); + + shellExecPrintf(pState->db, &rc, + "DELETE FROM _shared_schema_tmp.sqlite_master WHERE rowid<=%lld", + iLast + ); + shellExecPrintf(pState->db, &rc, "COMMIT"); + sqlite3_exec(pState->db, "PRAGMA writable_schema = 0", 0, 0, 0); + + /* Copy the auto-vacuum setting from main to the target db */ + shellPreparePrintf(pState->db, &rc, &pStmt, "PRAGMA main.auto_vacuum"); + sqlite3_step(pStmt); + iAutoVacuum = sqlite3_column_int(pStmt, 0); + shellFinalize(&rc, pStmt); + shellExecPrintf(pState->db, &rc, + "PRAGMA _shared_schema_tmp.auto_vacuum = %d", iAutoVacuum + ); + + /* Vacuum the db in order to standardize the rootpage numbers. */ + shellExecPrintf(pState->db, &rc, "VACUUM _shared_schema_tmp"); + + /* Set the schema-cookie value to the same as database "main" */ + shellPreparePrintf(pState->db, &rc, &pStmt, "PRAGMA main.schema_version"); + sqlite3_step(pStmt); + iCookie = sqlite3_column_int(pStmt, 0); + shellFinalize(&rc, pStmt); + shellExecPrintf(pState->db, &rc, + "PRAGMA _shared_schema_tmp.schema_version = %d", iCookie + ); + + sqlite3_exec(pState->db, "DETACH _shared_schema_tmp", 0, 0, 0); + return rc; +} + +static int sharedSchemaCheck(ShellState *pState, const char *zDb, int *peFix){ + int rc = SQLITE_OK; + int bFailed = 0; + sqlite3_stmt *pStmt = 0; + + if( peFix ) *peFix = 0; + shellExecPrintf(pState->db, &rc, "ATTACH '%q' AS _shared_schema_tmp", zDb); + + /* Check if this database has the same set of objects as the current db */ + shellPreparePrintf(pState->db, &rc, &pStmt, + "SELECT type, name FROM _shared_schema_tmp.sqlite_master AS o " + "WHERE NOT EXISTS (" + " SELECT 1 FROM main.sqlite_master " + " WHERE name IS o.name AND type IS o.type" + ")" + " UNION ALL " + "SELECT type, name FROM main.sqlite_master AS o " + "WHERE NOT EXISTS (" + " SELECT 1 FROM _shared_schema_tmp.sqlite_master " + " WHERE name IS o.name AND type IS o.type" + ")" + ); + if( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pStmt) ){ + sputf(pState->out, "%s is NOT compatible (objects)\n", zDb); + bFailed = 1; + } + shellFinalize(&rc, pStmt); + + /* Check if this database has the same set of SQL statements as the + ** current db. */ + if( bFailed==0 ){ + shellPreparePrintf(pState->db, &rc, &pStmt, + "SELECT 1 FROM _shared_schema_tmp.sqlite_master AS o " + "WHERE sql IS NOT (" + " SELECT sql FROM main.sqlite_master " + " WHERE name IS o.name AND type IS o.type" + ")" + ); + if( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pStmt) ){ + sputf(pState->out, "%s is NOT compatible (SQL)\n", zDb); + bFailed = 1; + } + shellFinalize(&rc, pStmt); + } + + /* Check if this database has the same set of root pages as the current + ** db. */ + if( bFailed==0 ){ + shellPreparePrintf(pState->db, &rc, &pStmt, + "SELECT 1 FROM _shared_schema_tmp.sqlite_master AS o " + "WHERE rootpage IS NOT (" + " SELECT rootpage FROM main.sqlite_master " + " WHERE name IS o.name AND type IS o.type" + ")" + ); + if( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pStmt) ){ + if( peFix==0 ){ + sputf(pState->out, "%s is NOT compatible (root pages)\n", zDb); + } + bFailed = 1; + if( peFix ) *peFix = 1; + } + shellFinalize(&rc, pStmt); + } + + if( bFailed==0 ){ + shellPreparePrintf(pState->db, &rc, &pStmt, + "SELECT 1 WHERE (" + " SELECT group_concat(rootpage || '.' || name || '.' || sql, '.') " + " FROM _shared_schema_tmp.sqlite_master" + ") IS NOT (" + " SELECT group_concat(rootpage || '.' || name || '.' || sql, '.') " + " FROM main.sqlite_master" + ")" + ); + if( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pStmt) ){ + if( peFix==0 ){ + sputf(pState->out, + "%s is NOT compatible (order of sqlite_master rows)\n", zDb + ); + } + bFailed = 1; + if( peFix ) *peFix = 2; + } + shellFinalize(&rc, pStmt); + } + + if( bFailed==0 ){ + int iMain = -1; + int iNew = +1; + shellPreparePrintf(pState->db, &rc, &pStmt, + "PRAGMA main.schema_version" + ); + if( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pStmt) ){ + iMain = sqlite3_column_int(pStmt, 0); + } + shellFinalize(&rc, pStmt); + shellPreparePrintf(pState->db, &rc, &pStmt, + "PRAGMA _shared_schema_tmp.schema_version" + ); + if( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pStmt) ){ + iNew = sqlite3_column_int(pStmt, 0); + } + shellFinalize(&rc, pStmt); + if( rc==SQLITE_OK && iMain!=iNew ){ + if( peFix==0 ){ + sputf(pState->out, + "%s is NOT compatible (schema cookie)\n", zDb + ); + } + bFailed = 1; + if( peFix ) *peFix = 3; + } + } + + if( rc==SQLITE_OK && bFailed==0 ){ + sputf(pState->out, "%s is compatible\n", zDb); + } + + sqlite3_exec(pState->db, "DETACH _shared_schema_tmp", 0, 0, 0); + return rc; +} + +/* +** .shared-schema check|fix DB1 DB2... +*/ +static int sharedSchemaDotCommand( + ShellState *pState, /* Current shell tool state */ + char **azArg, /* Array of arguments passed to dot command */ + int nArg /* Number of entries in azArg[] */ +){ + int rc = SQLITE_OK; + int bFix = 0; /* Fix databases if possible */ + int n1; + int i; + if( nArg<3 ){ + goto shared_schema_usage; + } + + n1 = (int)strlen(azArg[1]); + if( n1>0 && n1<=3 && memcmp("fix", azArg[1], n1)==0 ){ + bFix = 1; + }else if( n1==0 || n1>5 || memcmp("check", azArg[1], n1) ){ + goto shared_schema_usage; + } + + for(i=2; rc==SQLITE_OK && iout, "Fixing %s... ", azArg[i]); + fflush(pState->out); + rc = sharedSchemaFix(pState, azArg[i]); + if( rc==SQLITE_OK ){ + rc = sharedSchemaCheck(pState, azArg[i], &eFix); + if( rc==SQLITE_OK && eFix ){ + sputf(pState->out, "VACUUMing main... "); + fflush(pState->out); + rc = sqlite3_exec(pState->db, "VACUUM main", 0, 0, 0); + if( rc==SQLITE_OK ){ + rc = sharedSchemaCheck(pState, azArg[i], 0); + } + } + } + } + } + + return rc; + shared_schema_usage: + sputf(stderr, "usage: .shared-schema check|fix DB1 DB2...\n"); + return SQLITE_ERROR; +} #if SQLITE_SHELL_HAVE_RECOVER - /* ** This function is used as a callback by the recover extension. Simply ** print the supplied SQL statement to stdout. */ static int recoverSqlCb(void *pCtx, const char *zSql){ @@ -10643,10 +10924,17 @@ sqlite3_free(zRevText); } #endif /* !defined(*_OMIT_SCHEMA_PRAGMAS) && !defined(*_OMIT_VIRTUALTABLE) */ sqlite3_free(zSql); }else + +#if !defined(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_ENABLE_DBPAGE_VTAB) + if( c=='s' && strncmp(azArg[0], "shared-schema", n)==0 ){ + open_db(p, 0); + sharedSchemaDotCommand(p, azArg, nArg); + }else +#endif #if !defined(SQLITE_NOHAVE_SYSTEM) && !defined(SQLITE_SHELL_FIDDLE) if( c=='s' && (cli_strncmp(azArg[0], "shell", n)==0 || cli_strncmp(azArg[0],"system",n)==0) @@ -12437,10 +12725,12 @@ }else if( cli_strcmp(z,"-maxsize")==0 && i+1flags field. @@ -1803,10 +1811,16 @@ #ifdef SQLITE_USER_AUTHENTICATION sqlite3_userauth auth; /* User authentication information */ #endif }; +#ifdef SQLITE_ENABLE_SHARED_SCHEMA +# define IsSharedSchema(db) (((db)->openFlags & SQLITE_OPEN_SHARED_SCHEMA)!=0) +#else +# define IsSharedSchema(db) 0 +#endif + /* ** A macro to discover the encoding of a database. */ #define SCHEMA_ENC(db) ((db)->aDb[0].pSchema->enc) #define ENC(db) ((db)->enc) @@ -1886,10 +1900,13 @@ #define DBFLAG_VacuumInto 0x0008 /* Currently running VACUUM INTO */ #define DBFLAG_SchemaKnownOk 0x0010 /* Schema is known to be valid */ #define DBFLAG_InternalFunc 0x0020 /* Allow use of internal functions */ #define DBFLAG_EncodingFixed 0x0040 /* No longer possible to change enc. */ +#define DBFLAG_SchemaInuse 0x0080 /* Do not release sharable schemas */ +#define DBFLAG_FreeSchema 0x0100 /* Free extra shared schemas on release */ + /* ** Bits of the sqlite3.dbOptFlags field that are used by the ** sqlite3_test_control(SQLITE_TESTCTRL_OPTIMIZATIONS,...) interface to ** selectively disable various optimizations. */ @@ -2401,10 +2418,13 @@ u8 bConstraint; /* True if constraints are supported */ u8 bAllSchemas; /* True if might use any attached schema */ u8 eVtabRisk; /* Riskiness of allowing hacker access */ int iSavepoint; /* Depth of the SAVEPOINT stack */ VTable *pNext; /* Next in linked list (see above) */ +#ifdef SQLITE_ENABLE_SHARED_SCHEMA + char *zName; /* Table name (REUSE_SCHEMA mode) */ +#endif }; /* Allowed values for VTable.eVtabRisk */ #define SQLITE_VTABRISK_Low 0 @@ -4031,10 +4051,13 @@ the is stored here */ Schema *pSchema; /* Schema containing the trigger */ Schema *pTabSchema; /* Schema containing the table */ TriggerStep *step_list; /* Link list of trigger program steps */ Trigger *pNext; /* Next trigger associated with the table */ +#ifdef SQLITE_ENABLE_SHARED_SCHEMA + char *zTabSchema; /* Temp triggers in IsSharedSchema() dbs only */ +#endif }; /* ** A trigger is either a BEFORE or an AFTER trigger. The following constants ** determine which. @@ -4170,10 +4193,11 @@ char **pzErrMsg; /* Error message stored here */ int iDb; /* 0 for main database. 1 for TEMP, 2.. for ATTACHed */ int rc; /* Result code stored here */ u32 mInitFlags; /* Flags controlling error messages */ u32 nInitRow; /* Number of rows processed */ + u64 cksum; /* Schema checksum for REUSE_SCHEMA mode */ Pgno mxPage; /* Maximum page number. 0 for no limit. */ } InitData; /* ** Allowed values for mInitFlags @@ -5391,10 +5415,33 @@ void sqlite3DeleteIndexSamples(sqlite3*,Index*); void sqlite3DefaultRowEst(Index*); void sqlite3RegisterLikeFunctions(sqlite3*, int); int sqlite3IsLikeFunction(sqlite3*,Expr*,int*,char*); void sqlite3SchemaClear(void *); +void sqlite3SchemaClearOrDisconnect(sqlite3*, int); + +#ifdef SQLITE_ENABLE_SHARED_SCHEMA +int sqlite3SchemaConnect(sqlite3*, int, u64); +int sqlite3SchemaDisconnect(sqlite3 *, int, int); +Schema *sqlite3SchemaExtract(SchemaPool*); +int sqlite3SchemaLoad(sqlite3*, int, int*, char**); +void sqlite3SchemaReleaseAll(sqlite3*); +void sqlite3SchemaRelease(sqlite3*, int); +void sqlite3SchemaAdjustUsed(sqlite3*, int, int, int*); +void sqlite3SchemaWritable(Parse*, int); +void sqlite3UnlockReusableSchema(sqlite3 *db, int bRelease); +int sqlite3LockReusableSchema(sqlite3 *db); +#else +# define sqlite3SchemaWritable(x,y) +# define sqlite3UnlockReusableSchema(x,y) (void)(y) +# define sqlite3LockReusableSchema(x) 0 +# define sqlite3SchemaDisconnect(x,y,z) SQLITE_OK +# define sqlite3SchemaLoad(w,x,y,z) SQLITE_OK +# define sqlite3SchemaRelease(y,z) +# define sqlite3SchemaConnect(x,y,z) SQLITE_OK +#endif + Schema *sqlite3SchemaGet(sqlite3 *, Btree *); int sqlite3SchemaToIndex(sqlite3 *db, Schema *); KeyInfo *sqlite3KeyInfoAlloc(sqlite3*,int,int); void sqlite3KeyInfoUnref(KeyInfo*); KeyInfo *sqlite3KeyInfoRef(KeyInfo*); Index: src/status.c ================================================================== --- src/status.c +++ src/status.c @@ -286,17 +286,30 @@ ** databases. *pHighwater is set to zero. */ case SQLITE_DBSTATUS_SCHEMA_USED: { int i; /* Used to iterate through schemas */ int nByte = 0; /* Used to accumulate return value */ + int bReleaseSchema; sqlite3BtreeEnterAll(db); + bReleaseSchema = sqlite3LockReusableSchema(db); db->pnBytesFreed = &nByte; assert( db->lookaside.pEnd==db->lookaside.pTrueEnd ); db->lookaside.pEnd = db->lookaside.pStart; for(i=0; inDb; i++){ - Schema *pSchema = db->aDb[i].pSchema; + Schema *pSchema; +#ifdef SQLITE_ENABLE_SHARED_SCHEMA + int bUnload = 0; + int nUsed = nByte; + if( db->aDb[i].pSPool ){ + char *zDummy = 0; + rc = sqlite3SchemaLoad(db, i, &bUnload, &zDummy); + sqlite3_free(zDummy); + if( rc ) break; + } +#endif /* ifdef SQLITE_ENABLE_SHARED_SCHEMA */ + pSchema = db->aDb[i].pSchema; if( ALWAYS(pSchema!=0) ){ HashElem *p; nByte += sqlite3GlobalConfig.m.xRoundup(sizeof(HashElem)) * ( pSchema->tblHash.count @@ -314,11 +327,18 @@ } for(p=sqliteHashFirst(&pSchema->tblHash); p; p=sqliteHashNext(p)){ sqlite3DeleteTable(db, (Table *)sqliteHashData(p)); } } +#ifdef SQLITE_ENABLE_SHARED_SCHEMA + if( db->aDb[i].pSPool ){ + if( bUnload ) sqlite3SchemaRelease(db, i); + sqlite3SchemaAdjustUsed(db, i, nUsed, &nByte); + } +#endif /* ifdef SQLITE_ENABLE_SHARED_SCHEMA */ } + sqlite3UnlockReusableSchema(db, bReleaseSchema); db->pnBytesFreed = 0; db->lookaside.pEnd = db->lookaside.pTrueEnd; sqlite3BtreeLeaveAll(db); *pHighwater = 0; Index: src/tclsqlite.c ================================================================== --- src/tclsqlite.c +++ src/tclsqlite.c @@ -3725,10 +3725,13 @@ ){ Tcl_WrongNumArgs(interp, 1, objv, "HANDLE ?FILENAME? ?-vfs VFSNAME? ?-readonly BOOLEAN? ?-create BOOLEAN?" " ?-nofollow BOOLEAN?" " ?-nomutex BOOLEAN? ?-fullmutex BOOLEAN? ?-uri BOOLEAN?" +#ifdef SQLITE_ENABLE_SHARED_SCHEMA + " ?-shared-schema BOOLEAN?" +#endif ); return TCL_ERROR; } /* @@ -3856,10 +3859,20 @@ if( b ){ flags |= SQLITE_OPEN_URI; }else{ flags &= ~SQLITE_OPEN_URI; } +#ifdef SQLITE_ENABLE_SHARED_SCHEMA + }else if( strcmp(zArg, "-shared-schema")==0 ){ + int b; + if( Tcl_GetBooleanFromObj(interp, objv[i], &b) ) return TCL_ERROR; + if( b ){ + flags |= SQLITE_OPEN_SHARED_SCHEMA; + }else{ + flags &= ~SQLITE_OPEN_SHARED_SCHEMA; + } +#endif /* ifdef SQLITE_ENABLE_SHARED_SCHEMA */ }else if( strcmp(zArg, "-translatefilename")==0 ){ if( Tcl_GetBooleanFromObj(interp, objv[i], &bTranslateFileName) ){ return TCL_ERROR; } }else{ Index: src/test_config.c ================================================================== --- src/test_config.c +++ src/test_config.c @@ -776,10 +776,16 @@ #ifdef SQLITE_OMIT_WINDOWFUNC Tcl_SetVar2(interp, "sqlite_options", "windowfunc", "0", TCL_GLOBAL_ONLY); #else Tcl_SetVar2(interp, "sqlite_options", "windowfunc", "1", TCL_GLOBAL_ONLY); #endif + +#ifdef SQLITE_ENABLE_SHARED_SCHEMA + Tcl_SetVar2(interp, "sqlite_options", "sharedschema", "1", TCL_GLOBAL_ONLY); +#else + Tcl_SetVar2(interp, "sqlite_options", "sharedschema", "0", TCL_GLOBAL_ONLY); +#endif #define LINKVAR(x) { \ static const int cv_ ## x = SQLITE_ ## x; \ Tcl_LinkVar(interp, "SQLITE_" #x, (char *)&(cv_ ## x), \ TCL_LINK_INT | TCL_LINK_READ_ONLY); } ADDED src/test_schemapool.c Index: src/test_schemapool.c ================================================================== --- /dev/null +++ src/test_schemapool.c @@ -0,0 +1,282 @@ +/* +** 2006 June 10 +** +** 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. +** +************************************************************************* +** Code for testing the virtual table interfaces. This code +** is not included in the SQLite library. It is used for automated +** testing of the SQLite library. +*/ + +/* +** None of this works unless we have virtual tables. +*/ +#if !defined(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_TEST) + +#include + +#ifdef SQLITE_ENABLE_SHARED_SCHEMA + +#include "sqliteInt.h" + +/* The code in this file defines a sqlite3 virtual-table module with +** the following schema. +*/ +#define SCHEMAPOOL_SCHEMA \ +"CREATE TABLE x(" \ +" cksum INTEGER, " \ +" nref INTEGER, " \ +" nschema INTEGER, " \ +" ndelete INTEGER " \ +")" + +#define SCHEMAPOOL_NFIELD 4 + +typedef struct schemapool_vtab schemapool_vtab; +typedef struct schemapool_cursor schemapool_cursor; + +/* A schema table object */ +struct schemapool_vtab { + sqlite3_vtab base; +}; + +/* A schema table cursor object */ +struct schemapool_cursor { + sqlite3_vtab_cursor base; + sqlite3_int64 *aData; + int iRow; + int nRow; +}; + +/* +** Table destructor for the schema module. +*/ +static int schemaPoolDestroy(sqlite3_vtab *pVtab){ + sqlite3_free(pVtab); + return 0; +} + +/* +** Table constructor for the schema module. +*/ +static int schemaPoolCreate( + sqlite3 *db, + void *pAux, + int argc, const char *const*argv, + sqlite3_vtab **ppVtab, + char **pzErr +){ + int rc = SQLITE_NOMEM; + schemapool_vtab *pVtab = sqlite3_malloc(sizeof(schemapool_vtab)); + if( pVtab ){ + memset(pVtab, 0, sizeof(schemapool_vtab)); + rc = sqlite3_declare_vtab(db, SCHEMAPOOL_SCHEMA); + if( rc!=SQLITE_OK ){ + sqlite3_free(pVtab); + pVtab = 0; + } + } + *ppVtab = (sqlite3_vtab *)pVtab; + return rc; +} + +/* +** Open a new cursor on the schema table. +*/ +static int schemaPoolOpen(sqlite3_vtab *pVTab, sqlite3_vtab_cursor **ppCursor){ + int rc = SQLITE_NOMEM; + schemapool_cursor *pCur; + pCur = sqlite3_malloc(sizeof(schemapool_cursor)); + if( pCur ){ + memset(pCur, 0, sizeof(schemapool_cursor)); + *ppCursor = (sqlite3_vtab_cursor*)pCur; + rc = SQLITE_OK; + } + return rc; +} + +/* +** Close a schema table cursor. +*/ +static int schemaPoolClose(sqlite3_vtab_cursor *cur){ + schemapool_cursor *pCur = (schemapool_cursor*)cur; + sqlite3_free(pCur->aData); + sqlite3_free(pCur); + return SQLITE_OK; +} + +/* +** Retrieve a column of data. +*/ +static int schemaPoolColumn(sqlite3_vtab_cursor *cur, sqlite3_context *ctx, int i){ + schemapool_cursor *pCur = (schemapool_cursor*)cur; + assert( i==0 || i==1 || i==2 || i==3 ); + sqlite3_result_int64(ctx, pCur->aData[pCur->iRow*SCHEMAPOOL_NFIELD + i]); + return SQLITE_OK; +} + +/* +** Retrieve the current rowid. +*/ +static int schemaPoolRowid(sqlite3_vtab_cursor *cur, sqlite_int64 *pRowid){ + schemapool_cursor *pCur = (schemapool_cursor*)cur; + *pRowid = pCur->iRow + 1; + return SQLITE_OK; +} + +static int schemaPoolEof(sqlite3_vtab_cursor *cur){ + schemapool_cursor *pCur = (schemapool_cursor*)cur; + return pCur->iRow>=pCur->nRow; +} + +/* +** Advance the cursor to the next row. +*/ +static int schemaPoolNext(sqlite3_vtab_cursor *cur){ + schemapool_cursor *pCur = (schemapool_cursor*)cur; + pCur->iRow++; + return SQLITE_OK; +} + +struct SchemaPool { + int nRef; /* Number of pointers to this object */ + int nDelete; /* Schema objects deleted by ReleaseAll() */ + u64 cksum; /* Checksum for this Schema contents */ + Schema *pSchema; /* Linked list of Schema objects */ + Schema sSchema; /* The single dummy schema object */ + SchemaPool *pNext; /* Next element in schemaPoolList */ +}; +extern SchemaPool *sqlite3SchemaPoolList(void); + +/* +** Reset a schemaPool table cursor. +*/ +static int schemaPoolFilter( + sqlite3_vtab_cursor *pVtabCursor, + int idxNum, const char *idxStr, + int argc, sqlite3_value **argv +){ + SchemaPool *pSPool; + schemapool_cursor *pCur = (schemapool_cursor*)pVtabCursor; + + sqlite3_free(pCur->aData); + pCur->aData = 0; + pCur->nRow = 0; + pCur->iRow = 0; + + for(pSPool = sqlite3SchemaPoolList(); pSPool; pSPool=pSPool->pNext){ + pCur->nRow++; + } + + if( pCur->nRow ){ + int iRow = 0; + int nByte = SCHEMAPOOL_NFIELD * pCur->nRow * sizeof(i64); + pCur->aData = (i64*)sqlite3_malloc(nByte); + if( pCur->aData==0 ) return SQLITE_NOMEM; + for(pSPool = sqlite3SchemaPoolList(); pSPool; pSPool=pSPool->pNext){ + Schema *p; + i64 nSchema = 0; + for(p=pSPool->pSchema; p; p=p->pNext){ + nSchema++; + } + pCur->aData[0 + iRow*SCHEMAPOOL_NFIELD] = pSPool->cksum; + pCur->aData[1 + iRow*SCHEMAPOOL_NFIELD] = (i64)pSPool->nRef; + pCur->aData[2 + iRow*SCHEMAPOOL_NFIELD] = nSchema; + pCur->aData[3 + iRow*SCHEMAPOOL_NFIELD] = (i64)pSPool->nDelete; + iRow++; + } + } + + return SQLITE_OK; +} + +/* +** Analyse the WHERE condition. +*/ +static int schemaPoolBestIndex(sqlite3_vtab *tab, sqlite3_index_info *pIdxInfo){ + return SQLITE_OK; +} + +/* +** A virtual table module that merely echos method calls into TCL +** variables. +*/ +static sqlite3_module schemaPoolModule = { + 0, /* iVersion */ + schemaPoolCreate, + schemaPoolCreate, + schemaPoolBestIndex, + schemaPoolDestroy, + schemaPoolDestroy, + schemaPoolOpen, /* xOpen - open a cursor */ + schemaPoolClose, /* xClose - close a cursor */ + schemaPoolFilter, /* xFilter - configure scan constraints */ + schemaPoolNext, /* xNext - advance a cursor */ + schemaPoolEof, /* xEof */ + schemaPoolColumn, /* xColumn - read data */ + schemaPoolRowid, /* xRowid - read data */ + 0, /* xUpdate */ + 0, /* xBegin */ + 0, /* xSync */ + 0, /* xCommit */ + 0, /* xRollback */ + 0, /* xFindMethod */ + 0, /* xRename */ +}; + +/* +** Decode a pointer to an sqlite3 object. +*/ +extern int getDbPointer(Tcl_Interp *interp, const char *zA, sqlite3 **ppDb); + +/* +** Register the schema virtual table module. +*/ +static int SQLITE_TCLAPI register_schemapool_module( + ClientData clientData, /* Not used */ + Tcl_Interp *interp, /* The TCL interpreter that invoked this command */ + int objc, /* Number of arguments */ + Tcl_Obj *CONST objv[] /* Command arguments */ +){ + sqlite3 *db; + if( objc!=2 ){ + Tcl_WrongNumArgs(interp, 1, objv, "DB"); + return TCL_ERROR; + } + if( getDbPointer(interp, Tcl_GetString(objv[1]), &db) ) return TCL_ERROR; +#ifndef SQLITE_OMIT_VIRTUALTABLE + sqlite3_create_module(db, "schemapool", &schemaPoolModule, 0); +#endif + return TCL_OK; +} + +#endif /* ifdef SQLITE_ENABLE_SHARED_SCHEMA */ +#endif /* !defined(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_TEST) */ + +/* +** Register commands with the TCL interpreter. +*/ +int Sqlitetestschemapool_Init(Tcl_Interp *interp){ +#ifdef SQLITE_ENABLE_SHARED_SCHEMA + static struct { + char *zName; + Tcl_ObjCmdProc *xProc; + void *clientData; + } aObjCmd[] = { + { "register_schemapool_module", register_schemapool_module, 0 }, + }; + int i; + for(i=0; idb; + if( IsSharedSchema(db) ){ + zSchema = db->aDb[sqlite3SchemaToIndex(db, pTab->pSchema)].zDbSName; + } +#endif assert( pParse->disableTriggers==0 ); pTmpSchema = pParse->db->aDb[1].pSchema; p = sqliteHashFirst(&pTmpSchema->trigHash); pList = pTab->pTrigger; while( p ){ Trigger *pTrig = (Trigger *)sqliteHashData(p); - if( pTrig->pTabSchema==pTab->pSchema + + int bSchemaMatch; +#ifdef SQLITE_ENABLE_SHARED_SCHEMA + if( zSchema ){ + /* Shared-schema */ + bSchemaMatch = (0==sqlite3StrICmp(pTrig->zTabSchema, zSchema)); + }else +#endif + { + /* Non-shared-schema */ + bSchemaMatch = (pTrig->pTabSchema==pTab->pSchema); + } + + if( bSchemaMatch && pTrig->table && 0==sqlite3StrICmp(pTrig->table, pTab->zName) && (pTrig->pTabSchema!=pTmpSchema || pTrig->bReturning) ){ pTrig->pNext = pList; pList = pTrig; }else if( pTrig->op==TK_RETURNING ){ #ifndef SQLITE_OMIT_VIRTUALTABLE assert( pParse->db->pVtabCtx==0 ); -#endif +#endif assert( pParse->bReturning ); assert( &(pParse->u1.pReturning->retTrig) == pTrig ); pTrig->table = pTab->zName; pTrig->pTabSchema = pTab->pSchema; pTrig->pNext = pList; @@ -261,10 +282,16 @@ pTrigger = (Trigger*)sqlite3DbMallocZero(db, sizeof(Trigger)); if( pTrigger==0 ) goto trigger_cleanup; pTrigger->zName = zName; zName = 0; pTrigger->table = sqlite3DbStrDup(db, pTableName->a[0].zName); +#ifdef SQLITE_ENABLE_SHARED_SCHEMA + if( IsSharedSchema(db) && iDb==1 ){ + int iTabDb = sqlite3SchemaToIndex(db, pTab->pSchema); + pTrigger->zTabSchema = sqlite3DbStrDup(db, db->aDb[iTabDb].zDbSName); + } +#endif /* ifdef SQLITE_ENABLE_SHARED_SCHEMA */ pTrigger->pSchema = db->aDb[iDb].pSchema; pTrigger->pTabSchema = pTab->pSchema; pTrigger->op = (u8)op; pTrigger->tr_tm = tr_tm==TK_BEFORE ? TRIGGER_BEFORE : TRIGGER_AFTER; if( IN_RENAME_OBJECT ){ @@ -384,11 +411,11 @@ " VALUES('trigger',%Q,%Q,0,'CREATE TRIGGER %q')", db->aDb[iDb].zDbSName, zName, pTrig->table, z); sqlite3DbFree(db, z); sqlite3ChangeCookie(pParse, iDb); - sqlite3VdbeAddParseSchemaOp(v, iDb, + sqlite3VdbeAddParseSchemaOp(pParse, iDb, sqlite3MPrintf(db, "type='trigger' AND name='%q'", zName), 0); } if( db->init.busy ){ Trigger *pLink = pTrig; @@ -603,10 +630,13 @@ void sqlite3DeleteTrigger(sqlite3 *db, Trigger *pTrigger){ if( pTrigger==0 || pTrigger->bReturning ) return; sqlite3DeleteTriggerStep(db, pTrigger->step_list); sqlite3DbFree(db, pTrigger->zName); sqlite3DbFree(db, pTrigger->table); +#ifdef SQLITE_ENABLE_SHARED_SCHEMA + sqlite3DbFree(db, pTrigger->zTabSchema); +#endif sqlite3ExprDelete(db, pTrigger->pWhen); sqlite3IdListDelete(db, pTrigger->pColumns); sqlite3DbFree(db, pTrigger); } @@ -674,10 +704,11 @@ sqlite3 *db = pParse->db; int iDb; iDb = sqlite3SchemaToIndex(pParse->db, pTrigger->pSchema); assert( iDb>=0 && iDbnDb ); + sqlite3SchemaWritable(pParse, iDb); pTable = tableOfTrigger(pTrigger); assert( (pTable && pTable->pSchema==pTrigger->pSchema) || iDb==1 ); #ifndef SQLITE_OMIT_AUTHORIZATION if( pTable ){ int code = SQLITE_DROP_TRIGGER; @@ -1173,11 +1204,13 @@ NameContext sNC; /* Name context for sub-vdbe */ SubProgram *pProgram = 0; /* Sub-vdbe for trigger program */ int iEndTrigger = 0; /* Label to jump to if WHEN is false */ Parse sSubParse; /* Parse context for sub-vdbe */ - assert( pTrigger->zName==0 || pTab==tableOfTrigger(pTrigger) ); + assert( pTrigger->zName==0 || IsSharedSchema(pParse->db) + || pTab==tableOfTrigger(pTrigger) + ); assert( pTop->pVdbe ); /* Allocate the TriggerPrg and SubProgram objects. To ensure that they ** are freed if an error occurs, link them into the Parse.pTriggerPrg ** list of the top-level Parse object sooner rather than later. */ @@ -1280,11 +1313,13 @@ int orconf /* ON CONFLICT algorithm. */ ){ Parse *pRoot = sqlite3ParseToplevel(pParse); TriggerPrg *pPrg; - assert( pTrigger->zName==0 || pTab==tableOfTrigger(pTrigger) ); + assert( pTrigger->zName==0 || IsSharedSchema(pParse->db) + || pTab==tableOfTrigger(pTrigger) + ); /* It may be that this trigger has already been coded (or is in the ** process of being coded). If this is the case, then an entry with ** a matching TriggerPrg.pTrigger field will be present somewhere ** in the Parse.pTriggerPrg list. Search for such an entry. */ Index: src/vacuum.c ================================================================== --- src/vacuum.c +++ src/vacuum.c @@ -123,10 +123,11 @@ if( iDb<0 ) iDb = 0; #endif } if( iDb!=1 ){ int iIntoReg = 0; + sqlite3SchemaWritable(pParse, iDb); if( pInto && sqlite3ResolveSelfReference(pParse,0,0,pInto,0)==0 ){ iIntoReg = ++pParse->nMem; sqlite3ExprCode(pParse, pInto, iIntoReg); } sqlite3VdbeAddOp2(v, OP_Vacuum, iDb, iIntoReg); Index: src/vdbe.c ================================================================== --- src/vdbe.c +++ src/vdbe.c @@ -4073,17 +4073,10 @@ assert( pOp->p5==0 || pOp->p4type==P4_INT32 ); if( rc==SQLITE_OK && pOp->p5 && (iMeta!=pOp->p3 || pDb->pSchema->iGeneration!=pOp->p4.i) ){ - /* - ** IMPLEMENTATION-OF: R-03189-51135 As each SQL statement runs, the schema - ** version is checked to ensure that the schema has not changed since the - ** SQL statement was prepared. - */ - sqlite3DbFree(db, p->zErrMsg); - p->zErrMsg = sqlite3DbStrDup(db, "database schema has changed"); /* If the schema-cookie from the database file matches the cookie ** stored with the in-memory representation of the schema, do ** not reload the schema from the database file. ** ** If virtual-tables are in use, this is not just an optimization. @@ -4096,10 +4089,17 @@ ** a v-table method. */ if( db->aDb[pOp->p1].pSchema->schema_cookie!=iMeta ){ sqlite3ResetOneSchema(db, pOp->p1); } + /* + ** IMPLEMENTATION-OF: R-03189-51135 As each SQL statement runs, the schema + ** version is checked to ensure that the schema has not changed since the + ** SQL statement was prepared. + */ + sqlite3DbFree(db, p->zErrMsg); + p->zErrMsg = sqlite3DbStrDup(db, "database schema has changed"); p->expired = 1; rc = SQLITE_SCHEMA; /* Set changeCntOn to 0 to prevent the value returned by sqlite3_changes() ** from being modified in sqlite3VdbeHalt(). If this statement is Index: src/vdbe.h ================================================================== --- src/vdbe.h +++ src/vdbe.h @@ -227,11 +227,11 @@ #if defined(SQLITE_DEBUG) && !defined(SQLITE_OMIT_EXPLAIN) void sqlite3ExplainBreakpoint(const char*,const char*); #else # define sqlite3ExplainBreakpoint(A,B) /*no-op*/ #endif -void sqlite3VdbeAddParseSchemaOp(Vdbe*, int, char*, u16); +void sqlite3VdbeAddParseSchemaOp(Parse*,int,char*,u16); void sqlite3VdbeChangeOpcode(Vdbe*, int addr, u8); void sqlite3VdbeChangeP1(Vdbe*, int addr, int P1); void sqlite3VdbeChangeP2(Vdbe*, int addr, int P2); void sqlite3VdbeChangeP3(Vdbe*, int addr, int P3); void sqlite3VdbeChangeP5(Vdbe*, u16 P5); Index: src/vdbeaux.c ================================================================== --- src/vdbeaux.c +++ src/vdbeaux.c @@ -559,12 +559,14 @@ ** as having been used. ** ** 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, u16 p5){ +void sqlite3VdbeAddParseSchemaOp(Parse *pParse, int iDb, char *zWhere, u16 p5){ + Vdbe *p = pParse->pVdbe; int j; + sqlite3SchemaWritable(pParse, iDb); sqlite3VdbeAddOp4(p, OP_ParseSchema, iDb, 0, 0, zWhere, P4_DYNAMIC); sqlite3VdbeChangeP5(p, p5); for(j=0; jdb->nDb; j++) sqlite3VdbeUsesBtree(p, j); sqlite3MayAbort(p->pParse); } Index: src/vdbeblob.c ================================================================== --- src/vdbeblob.c +++ src/vdbeblob.c @@ -132,10 +132,11 @@ int rc = SQLITE_OK; char *zErr = 0; Table *pTab; Incrblob *pBlob = 0; Parse sParse; + int bUnlock; /* True to unlock reusable schemas before returning */ #ifdef SQLITE_ENABLE_API_ARMOR if( ppBlob==0 ){ return SQLITE_MISUSE_BKPT; } @@ -148,10 +149,11 @@ #endif wrFlag = !!wrFlag; /* wrFlag = (wrFlag ? 1 : 0); */ sqlite3_mutex_enter(db->mutex); + bUnlock = sqlite3LockReusableSchema(db); pBlob = (Incrblob *)sqlite3DbMallocZero(db, sizeof(Incrblob)); while(1){ sqlite3ParseObjectInit(&sParse,db); if( !pBlob ) goto blob_open_out; sqlite3DbFree(db, zErr); @@ -333,10 +335,11 @@ if( (++nAttempt)>=SQLITE_MAX_SCHEMA_RETRY || rc!=SQLITE_SCHEMA ) break; sqlite3ParseObjectReset(&sParse); } blob_open_out: + sqlite3UnlockReusableSchema(db, bUnlock); if( rc==SQLITE_OK && db->mallocFailed==0 ){ *ppBlob = (sqlite3_blob *)pBlob; }else{ if( pBlob && pBlob->pStmt ) sqlite3VdbeFinalize((Vdbe *)pBlob->pStmt); sqlite3DbFree(db, pBlob); Index: src/vtab.c ================================================================== --- src/vtab.c +++ src/vtab.c @@ -190,10 +190,28 @@ ** this virtual-table, if one has been created, or NULL otherwise. */ VTable *sqlite3GetVTable(sqlite3 *db, Table *pTab){ VTable *pVtab; assert( IsVirtual(pTab) ); +#ifdef SQLITE_ENABLE_SHARED_SCHEMA + if( IsSharedSchema(db) ){ + int iDb = sqlite3SchemaToIndex(db, pTab->pSchema); + if( iDb!=1 ){ + VTable **pp; + for(pp=&db->aDb[iDb].pVTable; *pp; pp=&(*pp)->pNext){ + if( sqlite3StrICmp(pTab->zName, (*pp)->zName)==0 ) break; + } + pVtab = *pp; + if( pVtab && pTab->nCol<=0 ){ + *pp = pVtab->pNext; + sqlite3VtabUnlock(pVtab); + pVtab = 0; + } + return pVtab; + } + } +#endif /* ifdef SQLITE_ENABLE_SHARED_SCHEMA */ for(pVtab=pTab->u.vtab.p; pVtab && pVtab->db!=db; pVtab=pVtab->pNext); return pVtab; } /* @@ -497,11 +515,11 @@ v = sqlite3GetVdbe(pParse); sqlite3ChangeCookie(pParse, iDb); sqlite3VdbeAddOp0(v, OP_Expire); zWhere = sqlite3MPrintf(db, "name=%Q AND sql=%Q", pTab->zName, zStmt); - sqlite3VdbeAddParseSchemaOp(v, iDb, zWhere, 0); + sqlite3VdbeAddParseSchemaOp(pParse, iDb, zWhere, 0); sqlite3DbFree(db, zStmt); iReg = ++pParse->nMem; sqlite3VdbeLoadString(v, iReg, pTab->zName); sqlite3VdbeAddOp2(v, OP_VCreate, iDb, iReg); @@ -567,10 +585,11 @@ int nArg = pTab->u.vtab.nArg; char *zErr = 0; char *zModuleName; int iDb; VtabCtx *pCtx; + int nByte; /* Bytes of space to allocate */ assert( IsVirtual(pTab) ); azArg = (const char *const*)pTab->u.vtab.azArg; /* Check that the virtual-table is not already being initialized */ @@ -586,18 +605,26 @@ zModuleName = sqlite3DbStrDup(db, pTab->zName); if( !zModuleName ){ return SQLITE_NOMEM_BKPT; } - pVTable = sqlite3MallocZero(sizeof(VTable)); + nByte = sizeof(VTable); +#ifdef SQLITE_ENABLE_SHARED_SCHEMA + nByte += sqlite3Strlen30(pTab->zName) + 1; +#endif + pVTable = (VTable*)sqlite3MallocZero(nByte); if( !pVTable ){ sqlite3OomFault(db); sqlite3DbFree(db, zModuleName); return SQLITE_NOMEM_BKPT; } pVTable->db = db; pVTable->pMod = pMod; +#ifdef SQLITE_ENABLE_SHARED_SCHEMA + pVTable->zName = (char*)&pVTable[1]; + memcpy(pVTable->zName, pTab->zName, nByte-sizeof(VTable)); +#endif pVTable->eVtabRisk = SQLITE_VTABRISK_Normal; iDb = sqlite3SchemaToIndex(db, pTab->pSchema); pTab->u.vtab.azArg[1] = db->aDb[iDb].zDbSName; @@ -640,16 +667,27 @@ rc = SQLITE_ERROR; }else{ int iCol; u16 oooHidden = 0; /* If everything went according to plan, link the new VTable structure - ** into the linked list headed by pTab->u.vtab.p. Then loop through the - ** columns of the table to see if any of them contain the token "hidden". - ** If so, set the Column COLFLAG_HIDDEN flag and remove the token from - ** the type string. */ - pVTable->pNext = pTab->u.vtab.p; - pTab->u.vtab.p = pVTable; + ** into the linked list headed by pTab->u.vtab.p. Or, if this is a + ** reusable schema, into the linked list headed by Db.pVTable. + ** + ** Then loop through the columns of the table to see if any of them + ** contain the token "hidden". If so, set the Column COLFLAG_HIDDEN flag + ** and remove the token from the type string. */ +#ifdef SQLITE_ENABLE_SHARED_SCHEMA + if( IsSharedSchema(db) && iDb!=1 ){ + pVTable->pNext = db->aDb[iDb].pVTable; + db->aDb[iDb].pVTable = pVTable; + }else +#endif /* ifdef SQLITE_ENABLE_SHARED_SCHEMA */ + { + assert( IsVirtual(pTab) ); + pVTable->pNext = pTab->u.vtab.p; + pTab->u.vtab.p = pVTable; + } for(iCol=0; iColnCol; iCol++){ char *zType = sqlite3ColumnType(&pTab->aCol[iCol], ""); int nType; int i = 0; @@ -700,10 +738,11 @@ int rc; assert( pTab ); assert( IsVirtual(pTab) ); if( sqlite3GetVTable(db, pTab) ){ + assert( !IsVirtual(pTab) || pTab->nCol>0 ); return SQLITE_OK; } /* Locate the required virtual table module */ zMod = pTab->u.vtab.azArg[0]; Index: src/where.c ================================================================== --- src/where.c +++ src/where.c @@ -1514,11 +1514,11 @@ sqlite3ErrorMsg(pParse, "%s", sqlite3ErrStr(rc)); }else{ sqlite3ErrorMsg(pParse, "%s", pVtab->zErrMsg); } } - if( pTab->u.vtab.p->bAllSchemas ){ + if( pTab->u.vtab.p && pTab->u.vtab.p->bAllSchemas ){ sqlite3VtabUsesAllSchemas(pParse); } sqlite3_free(pVtab->zErrMsg); pVtab->zErrMsg = 0; return rc; ADDED test/reuse1.test Index: test/reuse1.test ================================================================== --- /dev/null +++ test/reuse1.test @@ -0,0 +1,394 @@ +# 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 + +ifcapable !sharedschema { + finish_test + return +} + +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 -shared-schema 1 + sqlite3 db test.db -shared-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 } + 11 { ALTER TABLE t1 RENAME z TO zzz } +} { + do_catchsql_test 1.5.$tn $sql {1 {attempt to modify read-only schema}} +} + +#------------------------------------------------------------------------- +# +reset_db +forcedelete test.db2 +ifcapable fts5 { + do_execsql_test 2.0 { + CREATE VIRTUAL TABLE ft USING fts5(a); + INSERT INTO ft VALUES('one'), ('two'), ('three'); + ATTACH 'test.db2' AS aux; + CREATE VIRTUAL TABLE aux.ft USING fts5(a); + INSERT INTO aux.ft VALUES('aux1'), ('aux2'), ('aux3'); + } + + db close + sqlite3 db test.db -shared-schema 1 + + do_execsql_test 2.1 { + ATTACH 'test.db2' AS aux; + SELECT * FROM main.ft; + } {one two three} + +breakpoint + do_execsql_test 2.2 { + SELECT * FROM aux.ft; + } {aux1 aux2 aux3} + + do_execsql_test 2.2 { + SELECT * FROM aux.ft_content; + } {1 aux1 2 aux2 3 aux3} +} + +#------------------------------------------------------------------------- +# +reset_db +forcedelete test.db2 +do_execsql_test 3.0 { + CREATE TABLE t1(a PRIMARY KEY, b, c); + CREATE VIEW v1 AS SELECT * FROM t1; + CREATE TRIGGER v1_ins INSTEAD OF INSERT ON v1 BEGIN + INSERT INTO t1 VALUES(new.a, new.b, new.c); + END; + CREATE TRIGGER v1_del INSTEAD OF DELETE ON v1 BEGIN + DELETE FROM t1 WHERE a=old.a; + END; + CREATE TRIGGER v1_up INSTEAD OF UPDATE ON v1 BEGIN + UPDATE t1 SET a=new.a, b=new.b, c=new.c WHERE a=old.a; + END; +} +forcecopy test.db test.db2 + +do_test 3.1 { + sqlite3 db2 test.db2 + execsql { INSERT INTO t1 VALUES(1, 2, 3) } db + execsql { INSERT INTO t1 VALUES(4, 5, 6) } db2 + db2 close + execsql { ATTACH 'test.db2' AS aux; } +} {} + +do_execsql_test 3.2 { + SELECT * FROM main.v1; +} {1 2 3} + +do_execsql_test 3.3 { + SELECT * FROM aux.v1; +} {4 5 6} + +db close +sqlite3 db test.db -shared-schema 1 + +do_execsql_test 3.4 { ATTACH 'test.db2' AS aux } {} +do_execsql_test 3.5 { SELECT * FROM main.v1 } {1 2 3} +do_execsql_test 3.6 { SELECT * FROM aux.v1 } {4 5 6} + +do_execsql_test 3.7.1 { INSERT INTO aux.t1 VALUES(8, 9, 10); } +do_execsql_test 3.7.2 { SELECT * FROM main.v1 } {1 2 3} +do_execsql_test 3.7.3 { SELECT * FROM aux.v1 } {4 5 6 8 9 10} + +do_execsql_test 3.8.1 { DELETE FROM aux.t1 WHERE b=5 } +do_execsql_test 3.8.2 { SELECT * FROM main.v1 } {1 2 3} +do_execsql_test 3.8.3 { SELECT * FROM aux.v1 } {8 9 10} + +do_execsql_test 3.9.1 { UPDATE aux.t1 SET b='abc' } +do_execsql_test 3.9.2 { SELECT * FROM main.v1 } {1 2 3} +do_execsql_test 3.9.3 { SELECT * FROM aux.v1 } {8 abc 10} + +do_execsql_test 3.10.1 { INSERT INTO aux.v1 VALUES(11, 12, 13) } +do_execsql_test 3.10.2 { SELECT * FROM main.v1 } {1 2 3} +do_execsql_test 3.10.3 { SELECT * FROM aux.v1 } {8 abc 10 11 12 13} + +do_execsql_test 3.11.1 { DELETE FROM aux.v1 WHERE b='abc' } +do_execsql_test 3.11.2 { SELECT * FROM main.v1 } {1 2 3} +do_execsql_test 3.11.3 { SELECT * FROM aux.v1 } {11 12 13} + +do_execsql_test 3.12.1 { UPDATE aux.v1 SET b='def' } +do_execsql_test 3.12.2 { SELECT * FROM main.v1 } {1 2 3} +do_execsql_test 3.12.3 { SELECT * FROM aux.v1 } {11 def 13} + +do_execsql_test 3.13.1 { + CREATE TEMP TRIGGER xyz AFTER INSERT ON aux.t1 BEGIN + INSERT INTO v1 VALUES(new.a, new.b, new.c); + END +} +do_execsql_test 3.13.2 { + INSERT INTO aux.v1 VALUES('x', 'y', 'z'); +} +do_execsql_test 3.13.3 { + SELECT * FROM v1; +} {1 2 3 x y z} + +#------------------------------------------------------------------------- +# +reset_db +forcedelete test.db2 +do_execsql_test 4.0 { + CREATE TABLE t1(a PRIMARY KEY, b, c UNIQUE); + CREATE TABLE del(a, b, c); + CREATE TRIGGER tr1 AFTER DELETE ON t1 BEGIN + INSERT INTO del VALUES(old.a, old.b, old.c); + END; +} +forcecopy test.db test.db2 + +db close +sqlite3 db test.db -shared-schema 1 +execsql { + ATTACH 'test.db2' AS aux; + PRAGMA recursive_triggers = 1; +} + +do_execsql_test 4.1 { + INSERT INTO main.t1 VALUES(1, 2, 3); + INSERT INTO aux.t1 VALUES(4, 5, 6); +} + +do_execsql_test 4.2.1 { + INSERT OR REPLACE INTO aux.t1 VALUES('a', 'b', 6); + SELECT * FROM aux.t1; +} {a b 6} +do_execsql_test 4.2.2 { SELECT * FROM aux.del } {4 5 6} +do_execsql_test 4.2.3 { SELECT * FROM main.del } {} + +do_execsql_test 4.3.1 { + INSERT INTO aux.t1 VALUES('x', 'y', 'z'); + UPDATE OR REPLACE aux.t1 SET c='z' WHERE a='a'; +} {} +do_execsql_test 4.3.2 { SELECT * FROM aux.del } {4 5 6 x y z} +do_execsql_test 4.3.3 { SELECT * FROM main.del } {} + +#------------------------------------------------------------------------- +# +reset_db +do_execsql_test 5.0 { + CREATE TABLE t1(a INTEGER PRIMARY KEY, b, c); + CREATE TABLE t2(a INTEGER PRIMARY KEY, b, c); + CREATE INDEX i1 ON t1(b); + INSERT INTO t1 VALUES(1, 2, 3), (4, 5, 6); + ANALYZE; + PRAGMA writable_schema = 1; + DELETE FROM sqlite_stat1; +} +db close +forcecopy test.db test.db2 +sqlite3 db test.db -shared-schema 1 +execsql { ATTACH 'test.db2' AS aux } + +foreach {tn sql} { + 1 { CREATE TABLE t3(x) } + 2 { DROP TABLE t2 } + 3 { CREATE INDEX i2 ON t2(b) } + 4 { DROP INDEX i1 } + 5 { ALTER TABLE t1 ADD COLUMN d } + 6 { ALTER TABLE t1 RENAME TO t3 } + 7 { ALTER TABLE t1 RENAME c TO d } +} { + do_catchsql_test 5.1.$tn $sql {1 {attempt to modify read-only schema}} +} + +do_execsql_test 5.2.1 { ANALYZE aux.t1 } {} +do_execsql_test 5.2.2 { SELECT * FROM aux.sqlite_stat1 } {t1 i1 {2 1}} +do_execsql_test 5.2.3 { SELECT * FROM main.sqlite_stat1 } {} + +do_test 5.3.0 { + sqlite3 db2 test.db2 + db2 eval { + PRAGMA writable_schema = 1; + DELETE FROM sqlite_stat1; + } +} {} + +do_execsql_test 5.3.1 { SELECT * FROM aux.sqlite_stat1 } {} +do_execsql_test 5.3.2 { ANALYZE aux } {} +do_execsql_test 5.3.3 { SELECT * FROM aux.sqlite_stat1 } {t1 i1 {2 1}} +do_execsql_test 5.3.4 { SELECT * FROM main.sqlite_stat1 } {} + +#------------------------------------------------------------------------- +# Attempting to run ANALYZE when the required sqlite_statXX functions +# are missing is an error (because it would modify the database schema). +# +reset_db +do_execsql_test 5.4 { + CREATE TABLE t1(a INTEGER PRIMARY KEY, b, c); + CREATE TABLE t2(a INTEGER PRIMARY KEY, b, c); + CREATE INDEX i1 ON t1(b); + INSERT INTO t1 VALUES(1, 2, 3), (4, 5, 6); +} +db close +sqlite3 db test.db -shared-schema 1 +foreach {tn sql} { + 1 { ANALYZE } + 2 { ANALYZE t1 } + 3 { ANALYZE i1 } + 4 { ANALYZE main } + 5 { ANALYZE main.t1 } + 6 { ANALYZE main.i1 } +} { + do_catchsql_test 5.4.$tn $sql {1 {attempt to modify read-only schema}} +} + +#------------------------------------------------------------------------- +# +reset_db +do_execsql_test 6.0 { + CREATE TABLE t1(a INTEGER PRIMARY KEY, b, c); + CREATE VIEW v1 AS SELECT * FROM t1; +} +db close +forcecopy test.db test.db2 +sqlite3 db test.db -shared-schema 1 +execsql { ATTACH 'test.db2' AS aux } + +do_execsql_test 6.1 { + INSERT INTO main.t1(a) VALUES(1), (2), (3); + INSERT INTO aux.t1(a) VALUES(4), (5), (6); + CREATE TEMP TABLE t2(i,t); + INSERT INTO t2 VALUES(2, 'two'), (5, 'five'); +} + +do_execsql_test 6.2 { + SELECT t FROM t2 WHERE i IN (SELECT a FROM aux.t1) +} {five} +do_execsql_test 6.3 { + SELECT t FROM t2 WHERE i IN (SELECT a FROM aux.v1) +} {five} + +#------------------------------------------------------------------------- +# +reset_db +do_execsql_test 7.0 { + CREATE TABLE p1(a PRIMARY KEY, b); + CREATE TABLE p2(a PRIMARY KEY, b); + CREATE TABLE c1(x REFERENCES p1 ON UPDATE CASCADE ON DELETE CASCADE); +} + +db close +forcecopy test.db test.db2 +sqlite3 db test.db -shared-schema 1 +execsql { ATTACH 'test.db2' AS aux } + +do_execsql_test 7.1 { + INSERT INTO aux.p1 VALUES(1, 'one'); + INSERT INTO aux.p1 VALUES(2, 'two'); + PRAGMA foreign_keys = on; +} + +do_execsql_test 7.2 { + INSERT INTO aux.c1 VALUES(2); +} + +do_execsql_test 7.3.1 { + PRAGMA foreign_keys = off; + INSERT INTO main.p2 SELECT * FROM aux.p1; +} +do_execsql_test 7.3.2 { + SELECT * FROM main.p2; +} {1 one 2 two} + +do_execsql_test 7.3.3 { + INSERT INTO aux.p2 VALUES(1, 2); +} + +do_execsql_test 7.3.4 { + SELECT main.p2.a FROM main.p2, aux.p2; +} {1 2} + +do_execsql_test 7.3.5 { + SELECT * FROM main.p2, aux.p2; +} {1 one 1 2 2 two 1 2} + +do_execsql_test 7.4 { + SELECT count(*) FROM aux.p2; +} {1} + + +finish_test + + ADDED test/reuse2.test Index: test/reuse2.test ================================================================== --- /dev/null +++ test/reuse2.test @@ -0,0 +1,336 @@ +# 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 reuse2 + +ifcapable !sharedschema { + finish_test + return +} + +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_test 1.2 { + catch { db close } + catch { db2 close } + sqlite3 db2 test.db -shared-schema 1 + sqlite3 db test.db -shared-schema 1 +} {} + +do_execsql_test -db db2 1.3.1 { + INSERT INTO t1 VALUES(1, 2, 3); +} + +do_execsql_test -db db2 1.3.2 { + INSERT INTO t1 VALUES(4, 5, 6); +} + +do_execsql_test 1.3.3 { + SELECT * FROM t1; +} {1 2 3 4 5 6} + +#-------------------------------------------------------------------------- +reset_db +ifcapable fts5 { + do_execsql_test 2.0 { + CREATE VIRTUAL TABLE ft USING fts5(c); + INSERT INTO ft VALUES('one two three'); + } + db close + sqlite3 db test.db -shared-schema 1 + + do_execsql_test 2.1 { + SELECT * FROM ft + } {{one two three}} +} + +#-------------------------------------------------------------------------- +reset_db +do_execsql_test 3.0 { + CREATE TABLE t1(x INTEGER PRIMARY KEY, y UNIQUE, z); + CREATE INDEX i1 ON t1(z); + PRAGMA schema_version; +} {2} + +do_test 3.1 { + sqlite3 db1 test.db -shared-schema 1 + sqlite3 db2 test.db -shared-schema 1 +} {} + +do_execsql_test -db db1 3.2.1 { SELECT * FROM t1 } +do_execsql_test -db db2 3.2.2 { SELECT * FROM t1 } + +register_schemapool_module db +do_execsql_test 3.3 { + SELECT 'nref=' || nRef, 'nschema=' || nSchema, 'ndelete=' || nDelete + FROM schemapool; +} {nref=2 nschema=1 ndelete=0} + +sqlite3 db3 test.db -shared-schema 1 +register_schemapool_module db3 + +do_execsql_test 3.5 { + SELECT 'nref=' || nRef, 'nschema=' || nSchema, 'ndelete=' || nDelete + FROM schemapool; +} {nref=2 nschema=1 ndelete=0} + +do_execsql_test -db db3 3.6 { + SELECT * FROM t1; + SELECT 'nref=' || nRef, 'nschema=' || nSchema, 'ndelete=' || nDelete + FROM schemapool; +} {nref=3 nschema=1 ndelete=0} + +do_execsql_test 3.7 { + CREATE TABLE t2(x); +} + +do_execsql_test 3.8 { + SELECT 'nref=' || nRef, 'nschema=' || nSchema, 'ndelete=' || nDelete + FROM schemapool; +} {nref=3 nschema=1 ndelete=0} + +do_execsql_test -db db1 3.9.1 { SELECT * FROM t1 } +do_execsql_test 3.9.2 { + SELECT 'nref=' || nRef, 'nschema=' || nSchema, 'ndelete=' || nDelete + FROM schemapool; +} {nref=1 nschema=1 ndelete=0 nref=2 nschema=1 ndelete=0} + +do_execsql_test -db db2 3.10.1 { SELECT * FROM t1 } +do_execsql_test 3.10.2 { + SELECT 'nref=' || nRef, 'nschema=' || nSchema, 'ndelete=' || nDelete + FROM schemapool ORDER BY 1; +} {nref=1 nschema=1 ndelete=0 nref=2 nschema=1 ndelete=0} + +do_execsql_test -db db3 3.11.1 { SELECT * FROM t1 } +do_execsql_test 3.11.2 { + SELECT 'nref=' || nRef, 'nschema=' || nSchema, 'ndelete=' || nDelete + FROM schemapool; +} {nref=3 nschema=1 ndelete=0} + +#-------------------------------------------------------------------------- +catch {db1 close} +catch {db2 close} +catch {db3 close} +reset_db +do_execsql_test 4.0.1 { + CREATE TABLE x1(a, b, c); + CREATE INDEX x1a ON x1(a); + CREATE INDEX x1b ON x1(b); +} +do_test 4.0.2 { + db close + for {set i 1} {$i < 6} {incr i} { + forcedelete test.db${i}-journal test.db${i}-wal test.db${i}-wal2 + forcecopy test.db test.db${i} + } + sqlite3 db test.db + sqlite3 db2 test.db -shared-schema 1 +} {} + +register_schemapool_module db +do_execsql_test 4.0.3 { + SELECT 'nref=' || nRef, 'nschema=' || nSchema, 'ndelete=' || nDelete + FROM schemapool; +} {} + +do_test 4.1.1 { + execsql { + ATTACH 'test.db1' AS db1; + ATTACH 'test.db2' AS db2; + ATTACH 'test.db3' AS db3; + ATTACH 'test.db4' AS db4; + ATTACH 'test.db5' AS db5; + } db2 +} {} +do_execsql_test 4.1.2 { + SELECT 'nref=' || nRef, 'nschema=' || nSchema, 'ndelete=' || nDelete + FROM schemapool; +} {nref=1 nschema=1 ndelete=0} +do_execsql_test -db db2 4.1.3 { + SELECT * FROM db3.x1 +} +do_execsql_test 4.1.4 { + SELECT 'nref=' || nRef, 'nschema=' || nSchema, 'ndelete=' || nDelete + FROM schemapool; +} {nref=2 nschema=1 ndelete=0} +do_execsql_test -db db2 4.1.5 { + SELECT * FROM db2.x1 +} +do_execsql_test 4.1.6 { + SELECT 'nref=' || nRef, 'nschema=' || nSchema, 'ndelete=' || nDelete + FROM schemapool; +} {nref=3 nschema=1 ndelete=0} +do_execsql_test -db db2 4.1.7 { + SELECT * FROM x1 +} +do_execsql_test 4.1.8 { + SELECT 'nref=' || nRef, 'nschema=' || nSchema, 'ndelete=' || nDelete + FROM schemapool; +} {nref=3 nschema=1 ndelete=0} + +do_test 4.2.1 { + catchsql { SELECT * FROM abc } db2 +} {1 {no such table: abc}} +do_execsql_test 4.2.2 { + SELECT 'nref=' || nRef, 'nschema=' || nSchema, 'ndelete=' || nDelete + FROM schemapool; +} {nref=6 nschema=1 ndelete=0} + +register_schemapool_module db2 +do_execsql_test -db db2 4.3.1 { + INSERT INTO x1 VALUES(1, 2, 3); + INSERT INTO db1.x1 VALUES(4, 5, 6); + INSERT INTO db2.x1 VALUES(7, 8, 9); + INSERT INTO db3.x1 VALUES(10, 11, 12); + INSERT INTO db4.x1 VALUES(13, 14, 15); + INSERT INTO db5.x1 VALUES(16, 17, 18); + SELECT 'nref=' || nRef, 'nschema=' || nSchema, 'ndelete=' || nDelete + FROM schemapool; +} {nref=6 nschema=1 ndelete=0} + +do_execsql_test -db db2 4.3.2 { + SELECT * FROM db5.x1; + SELECT * FROM db4.x1; + SELECT * FROM db3.x1; + SELECT * FROM db2.x1; + SELECT * FROM db1.x1; + SELECT * FROM x1; + SELECT 'nref=' || nRef, 'nschema=' || nSchema, 'ndelete=' || nDelete + FROM schemapool; +} { + 16 17 18 13 14 15 10 11 12 7 8 9 4 5 6 1 2 3 + nref=6 nschema=1 ndelete=0 +} + +do_execsql_test -db db2 4.3.3 { + UPDATE x1 SET a=a+10; + UPDATE db5.x1 SET a=a+10; + SELECT 'nref=' || nRef, 'nschema=' || nSchema, 'ndelete=' || nDelete + FROM schemapool; +} { + nref=6 nschema=1 ndelete=0 +} + +do_execsql_test -db db2 4.3.4 { + SELECT * FROM db5.x1; + SELECT * FROM db4.x1; + SELECT * FROM db3.x1; + SELECT * FROM db2.x1; + SELECT * FROM db1.x1; + SELECT * FROM x1; + SELECT 'nref=' || nRef, 'nschema=' || nSchema, 'ndelete=' || nDelete + FROM schemapool; +} { + 26 17 18 13 14 15 10 11 12 7 8 9 4 5 6 11 2 3 + nref=6 nschema=1 ndelete=0 +} + +do_execsql_test -db db2 4.3.5 { + DELETE FROM db3.x1; + DELETE FROM x1; + SELECT 'nref=' || nRef, 'nschema=' || nSchema, 'ndelete=' || nDelete + FROM schemapool; +} { + nref=6 nschema=1 ndelete=0 +} + +do_execsql_test -db db2 4.3.6 { + SELECT * FROM db5.x1; + SELECT * FROM db4.x1; + SELECT * FROM db3.x1; + SELECT * FROM db2.x1; + SELECT * FROM db1.x1; + SELECT * FROM x1; + SELECT 'nref=' || nRef, 'nschema=' || nSchema, 'ndelete=' || nDelete + FROM schemapool; +} { + 26 17 18 13 14 15 7 8 9 4 5 6 + nref=6 nschema=1 ndelete=0 +} + +do_execsql_test -db db2 4.3.6 { + SELECT * FROM db5.x1, db4.x1, db1.x1; + SELECT 'nref=' || nRef, 'nschema=' || nSchema, 'ndelete=' || nDelete + FROM schemapool; +} {26 17 18 13 14 15 4 5 6 nref=6 nschema=3 ndelete=0} + +#-------------------------------------------------------------------------- +# Test the incremental-blob API with REUSE_SCHEMA connections. +# +catch {db1 close} +catch {db2 close} +catch {db3 close} +reset_db +do_execsql_test 5.0.1 { + CREATE TABLE bbb(a INTEGER PRIMARY KEY, b); +} +db close +do_test 5.0.2 { + sqlite3 db2 test.db -shared-schema 1 + register_schemapool_module db2 + for {set i 1} {$i<6} {incr i} { + forcedelete test.db${i}-journal test.db${i}-wal test.db${i}-wal2 + forcecopy test.db test.db${i} + sqlite3 db test.db${i} + db eval { INSERT INTO bbb VALUES(123, 'database_' || $i) } + db close + db2 eval "ATTACH 'test.db${i}' AS db${i}" + } + execsql { + SELECT 'nref=' || nRef, 'nschema=' || nSchema, 'ndelete=' || nDelete + FROM schemapool; + } db2 +} {nref=6 nschema=1 ndelete=0} + +do_test 5.1.1 { + set res [list] + for {set i 1} {$i<6} {incr i} { + set chan [db2 incrblob db${i} bbb b 123] + lappend res [gets $chan] + close $chan + } + set res +} {database_1 database_2 database_3 database_4 database_5} + +do_execsql_test -db db2 5.1.2 { + SELECT 'nref=' || nRef, 'nschema=' || nSchema, 'ndelete=' || nDelete + FROM schemapool; +} {nref=6 nschema=1 ndelete=0} + +do_test 5.2.1 { + sqlite3_table_column_metadata db2 main bbb a +} {INTEGER BINARY 0 1 0} +do_test 5.2.2 { + sqlite3_table_column_metadata db2 main bbb b +} {{} BINARY 0 0 0} + +do_execsql_test -db db2 5.2.3 { + SELECT 'nref=' || nRef, 'nschema=' || nSchema, 'ndelete=' || nDelete + FROM schemapool; +} {nref=6 nschema=1 ndelete=0} + +do_execsql_test -db db2 5.2.4 { + PRAGMA integrity_check; + SELECT 'nref=' || nRef, 'nschema=' || nSchema, 'ndelete=' || nDelete + FROM schemapool; +} {ok nref=6 nschema=1 ndelete=5} + +finish_test + ADDED test/reuse3.test Index: test/reuse3.test ================================================================== --- /dev/null +++ test/reuse3.test @@ -0,0 +1,355 @@ +# 2019 February 12 +# +# 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 reuse3 + +ifcapable !sharedschema { + finish_test + return +} + +do_execsql_test 1.0 { + CREATE TABLE t1(x INTEGER PRIMARY KEY, y UNIQUE, z); + CREATE INDEX i1 ON t1(z); + CREATE TABLE t2(a); +} {} + +db close +sqlite3 db test.db -shared-schema 1 + +do_execsql_test 1.1 { + CREATE TEMP VIEW v1 AS SELECT * FROM t1; + SELECT * FROM v1; +} + +do_execsql_test 1.2 { + CREATE TEMP TRIGGER tr1 AFTER INSERT ON t1 BEGIN + INSERT INTO t2 VALUES(new.x); + END; +} + +do_execsql_test 1.3 { + INSERT INTO t1 VALUES(1, 2, 3); +} + +do_execsql_test 1.4 { + SELECT * FROM t2 +} {1} + +do_execsql_test 1.5 { + SELECT * FROM v1 +} {1 2 3} + +do_execsql_test 1.6 { + BEGIN; + DROP TRIGGER tr1; + ROLLBACK; +} + +do_execsql_test 1.7 { + SELECT * FROM v1 +} {1 2 3} + +do_execsql_test 1.8 { + INSERT INTO t1 VALUES(4, 5, 6); + SELECT * FROM t2 +} {1 4} + +do_execsql_test 1.9 { + SELECT * FROM v1 +} {1 2 3 4 5 6} + +#------------------------------------------------------------------------- +# Test error messages when parsing the schema with a REUSE_SCHEMA +# connection. +reset_db +do_execsql_test 2.0 { + CREATE TABLE x1(a, b, c); + CREATE TABLE y1(d, e, f); + PRAGMA writable_schema = 1; + UPDATE sqlite_master SET sql = 'CREATE TBL y1(d, e, f)' WHERE name = 'y1'; +} +db close + +sqlite3 db test.db -shared-schema 1 +do_catchsql_test 2.1 { + SELECT * FROM x1; +} {1 {malformed database schema (y1) - near "TBL": syntax error}} + +do_catchsql_test 2.2 { + SELECT * FROM x1; +} {1 {malformed database schema (y1) - near "TBL": syntax error}} + +#------------------------------------------------------------------------- +reset_db +do_execsql_test 3.0 { + CREATE TABLE x1(a, b, c); + CREATE INDEX i1 ON x1(a, b, c); + CREATE TRIGGER tr1 AFTER INSERT ON x1 BEGIN + SELECT 1, 2, 3, 4, 5; + END; + INSERT INTO x1 VALUES(1, 2, 3); +} +sqlite3 db1 test.db -shared-schema 1 + +do_test 3.1 { + execsql { SELECT * FROM x1 } db1 + set N [lindex [sqlite3_db_status db1 SCHEMA_USED 0] 1] + expr $N==$N +} 1 + +sqlite3 db2 test.db -shared-schema 1 +do_test 3.2 { + execsql { SELECT * FROM x1 } db2 + set N2 [lindex [sqlite3_db_status db2 SCHEMA_USED 0] 1] + expr $N2>($N/2) && $N2<($N/2)+400 +} 1 + +sqlite3 db3 test.db -shared-schema 1 +sqlite3 db4 test.db -shared-schema 1 +do_test 3.3 { + execsql { SELECT * FROM x1 } db3 + execsql { SELECT * FROM x1 } db4 + set N4 [lindex [sqlite3_db_status db2 SCHEMA_USED 0] 1] + set M [expr 2*($N-$N2)] + set {} {} +} {} +do_test 3.3.1 { expr {(($M / 4) + $N-$M)} } "#/$N4/" + +catch { db1 close } +catch { db2 close } +catch { db3 close } +catch { db4 close } + +#------------------------------------------------------------------------- +# 4.1 Test the REINDEX command. +# 4.2 Test CREATE TEMP ... commands. +# +reset_db +do_execsql_test 4.1.0 { + CREATE TABLE x1(a, b, c); + CREATE INDEX x1a ON x1(a); + CREATE INDEX x1b ON x1(b); + CREATE INDEX x1c ON x1(c); +} +db close +sqlite3 db test.db -shared-schema 1 + +do_execsql_test 4.1.1 { + REINDEX x1; + REINDEX x1a; + REINDEX x1b; + REINDEX x1c; + REINDEX; +} + +do_test 4.1.2 { + for {set i 1} {$i < 5} {incr i} { + forcedelete test.db${i} test.db${i}-wal test.db${i}-journal + forcecopy test.db test.db${i} + execsql "ATTACH 'test.db${i}' AS db${i}" + } + register_schemapool_module db + set {} {} + execsql { + SELECT 'nref=' || nRef, 'nschema=' || nSchema, 'ndelete=' || nDelete + FROM schemapool + } +} {nref=5 nschema=1 ndelete=0} + +do_execsql_test 4.1.3 { + REINDEX x1; + REINDEX x1a; + REINDEX x1b; + REINDEX x1c; + REINDEX db1.x1a; + REINDEX db2.x1b; + REINDEX db3.x1c; +} + +do_execsql_test 4.1.4 { + SELECT 'nref=' || nRef, 'nschema=' || nSchema, 'ndelete=' || nDelete + FROM schemapool +} {nref=5 nschema=1 ndelete=28} + +#------------------------------------------------------------------------- +db close +sqlite3 db test.db -shared-schema 1 +register_schemapool_module db +do_execsql_test 4.2.0 { + ATTACH 'test.db1' AS db1; + ATTACH 'test.db2' AS db2; + ATTACH 'test.db3' AS db3; + ATTACH 'test.db4' AS db4; + + SELECT * FROM db1.x1; + SELECT * FROM db2.x1; + SELECT * FROM db3.x1; + SELECT * FROM db4.x1; +} + +do_execsql_test 4.2.1 { + SELECT 'nref=' || nRef, 'nschema=' || nSchema, 'ndelete=' || nDelete + FROM schemapool; +} {nref=5 nschema=1 ndelete=0} + +do_execsql_test 4.2.2 { + CREATE TEMP TABLE t1(a, b, c); + SELECT 'nref=' || nRef, 'nschema=' || nSchema, 'ndelete=' || nDelete + FROM schemapool; +} {nref=5 nschema=1 ndelete=0} + +do_execsql_test 4.2.3 { + CREATE INDEX t1a ON t1(a); + SELECT 'nref=' || nRef, 'nschema=' || nSchema, 'ndelete=' || nDelete + FROM schemapool; +} {nref=5 nschema=1 ndelete=0} + +do_execsql_test 4.2.4 { + CREATE TRIGGER tr1 AFTER INSERT ON t1 BEGIN + SELECT 1,2,3,4; + END; + SELECT 'nref=' || nRef, 'nschema=' || nSchema, 'ndelete=' || nDelete + FROM schemapool; +} {nref=5 nschema=1 ndelete=0} + +do_execsql_test 4.2.5 { + DROP TABLE t1; + SELECT 'nref=' || nRef, 'nschema=' || nSchema, 'ndelete=' || nDelete + FROM schemapool; +} {nref=5 nschema=1 ndelete=0} + +do_execsql_test 4.2.6 { + CREATE TEMP TRIGGER tr1 AFTER INSERT ON db2.x1 BEGIN + SELECT 1,2,3,4; + END; + SELECT 'nref=' || nRef, 'nschema=' || nSchema, 'ndelete=' || nDelete + FROM schemapool; +} {nref=5 nschema=1 ndelete=0} + +do_execsql_test 4.2.7 { + DROP TRIGGER tr1; + SELECT 'nref=' || nRef, 'nschema=' || nSchema, 'ndelete=' || nDelete + FROM schemapool; +} {nref=5 nschema=1 ndelete=4} + +#-------------------------------------------------------------------------- +reset_db +do_execsql_test 5.0 { + CREATE TABLE t1(a, b); + CREATE TABLE t2(a, b); + CREATE TABLE t3(a, b); +} + +sqlite3 db2 test.db -shared-schema 1 +register_schemapool_module db2 + +do_execsql_test 5.1 { + PRAGMA writable_schema = 1; + UPDATE sqlite_master SET sql='CREATE TABLE t3 a,b' WHERE name = 't3'; +} + +do_test 5.2 { + catchsql { SELECT * FROM t1 } db2 +} {1 {malformed database schema (t3) - near "a": syntax error}} + +do_test 5.3 { + catchsql { SELECT nref,nschema FROM schemapool } db2 +} {1 {malformed database schema (t3) - near "a": syntax error}} + +do_execsql_test 5.4 { + PRAGMA writable_schema = 1; + UPDATE sqlite_master SET sql='CREATE TABLE t3(a,b)' WHERE name = 't3'; +} + +do_test 5.5 { + catchsql { SELECT nref,nschema FROM schemapool } db2 +} {0 {1 1}} + +db2 close +db close +do_test 5.6.1 { + forcedelete test.db2 test.db2-wal test.db2-journal + forcecopy test.db test.db2 + sqlite3 db test.db + sqlite3 db2 test.db -shared-schema 1 + sqlite3 db3 test.db2 -shared-schema 1 + register_schemapool_module db +} {} + +do_execsql_test -db db2 5.6.2 { SELECT * FROM t1 } +do_execsql_test -db db3 5.6.3 { SELECT * FROM t1 } +do_execsql_test 5.6.4 { + SELECT 'nref=' || nRef, 'nschema=' || nSchema FROM schemapool; + CREATE TABLE t4(x); + DROP TABLE t4; +} {nref=2 nschema=1} +do_execsql_test -db db2 5.6.5 { SELECT * FROM t1 } +do_execsql_test -db db3 5.6.6 { SELECT * FROM t1 } +do_execsql_test 5.6.7 { + SELECT 'nref=' || nRef, 'nschema=' || nSchema FROM schemapool; + ATTACH 'test.db2' AS db2; + CREATE TABLE db2.t4(x); + DROP TABLE db2.t4; +} {nref=1 nschema=1 nref=1 nschema=1} +do_execsql_test -db db2 5.6.8 { SELECT * FROM t1 } +do_execsql_test -db db3 5.6.9 { SELECT * FROM t1 } +do_execsql_test 5.6.10 { + SELECT 'nref=' || nRef, 'nschema=' || nSchema FROM schemapool; +} {nref=2 nschema=1} + +#------------------------------------------------------------------------- +reset_db +do_execsql_test 6.0 { + CREATE TABLE t1(a, b); + CREATE TABLE t2(a, b); + CREATE TABLE t3(a, b); +} + +do_test 6.1 { + db close + sqlite3 db test.db -shared-schema 1 + for {set i 1} {$i < 5} {incr i} { + set base "test.db$i" + set nm "aux$i" + forcedelete $base $base-wal $base-journal + forcecopy test.db $base + execsql "ATTACH '$base' AS $nm" + } +} {} + +do_test 6.2 { + set N1 [lindex [sqlite3_db_status db SCHEMA_USED 0] 1] + set N2 [lindex [sqlite3_db_status db SCHEMA_USED 0] 1] + expr {$N1>0 && $N2>0 && $N1==$N2} +} {1} + +do_test 6.3 { + execsql { SELECT * FROM main.t1 } + set N1 [lindex [sqlite3_db_status db SCHEMA_USED 0] 1] + set N2 [lindex [sqlite3_db_status db SCHEMA_USED 0] 1] + expr {$N1>0 && $N2>0 && $N1==$N2} +} {1} + +do_test 6.4 { + execsql { SELECT * FROM aux1.t1 } + set N3 [lindex [sqlite3_db_status db SCHEMA_USED 0] 1] + set N4 [lindex [sqlite3_db_status db SCHEMA_USED 0] 1] + list $N3 $N4 +} "#/$N1 $N1/" + +finish_test + ADDED test/reuse4.test Index: test/reuse4.test ================================================================== --- /dev/null +++ test/reuse4.test @@ -0,0 +1,173 @@ +# 2019 February 12 +# +# 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 reuse4 + +ifcapable !sharedschema { + finish_test + return +} + +foreach {tn sharedschema} { + 1 0 + 2 1 +} { + reset_db + + do_execsql_test 1.$tn.0 { + CREATE TABLE x1(a, b); + CREATE INDEX x1a ON x1(a); + CREATE INDEX x1b ON x1(b); + CREATE TABLE x2(a, b); + } + db close + + do_test 1.$tn.1 { + for {set i 1} {$i<4} {incr i} { + forcedelete test.db$i test.db$i-journal test.db$i-wal + forcecopy test.db test.db$i + } + + sqlite3 db test.db -shared-schema $sharedschema + for {set i 1} {$i<4} {incr i} { + execsql " ATTACH 'test.db$i' AS db$i " + } + } {} + + do_execsql_test 1.$tn.2 { + WITH s(i) AS ( SELECT 1 UNION ALL SELECT i+1 FROM s WHERE i<10 ) + INSERT INTO x1 SELECT i, i FROM s; + + INSERT INTO db3.x2 SELECT * FROM x1; + INSERT INTO db2.x1 SELECT * FROM db3.x2; + CREATE TEMP TRIGGER tr1 AFTER INSERT ON db2.x2 BEGIN + INSERT INTO x1 VALUES(new.a, new.b); + END; + INSERT INTO db2.x2 SELECT * FROM x1 WHERE a%2; + DELETE FROM x1 WHERE a<3; + INSERT INTO db3.x1 SELECT * FROM db2.x2; + + DETACH db3; + ATTACH 'test.db3' AS db3; + + UPDATE db3.x1 SET a=a-10 WHERE b NOT IN (SELECT b FROM db2.x2); + + CREATE TEMP TABLE x1(a, b); + INSERT INTO db2.x2 VALUES(50, 60), (60, 70), (80, 90); + ALTER TABLE x1 RENAME TO x2; + ALTER TABLE x2 ADD COLUMN c; + ALTER TABLE x2 RENAME a TO aaa; + DELETE FROM x1 WHERE b>8; + UPDATE db3.x2 SET b=b*10; + + BEGIN; + CREATE TEMP TABLE x5(x); + INSERT INTO x5 VALUES(1); + ROLLBACK; + + INSERT INTO main.x2 VALUES(123, 456); + } + + integrity_check 1.$tn.3 + + do_execsql_test 1.$tn.4 { + SELECT * FROM main.x1; SELECT 'xxx'; + SELECT * FROM main.x2; SELECT 'xxx'; + SELECT * FROM temp.x2; SELECT 'xxx'; + + SELECT * FROM db1.x1; SELECT 'xxx'; + SELECT * FROM db1.x2; SELECT 'xxx'; + SELECT * FROM db2.x1; SELECT 'xxx'; + SELECT * FROM db2.x2; SELECT 'xxx'; + SELECT * FROM db3.x1; SELECT 'xxx'; + SELECT * FROM db3.x2; SELECT 'xxx'; + } { + 3 3 4 4 5 5 6 6 7 7 8 8 3 3 5 5 7 7 xxx + 123 456 xxx + 50 60 {} 60 70 {} 80 90 {} xxx + xxx + xxx + 1 1 2 2 3 3 4 4 5 5 6 6 7 7 8 8 9 9 10 10 xxx + 1 1 3 3 5 5 7 7 9 9 50 60 60 70 80 90 xxx + 1 1 3 3 5 5 7 7 9 9 xxx + 1 10 2 20 3 30 4 40 5 50 6 60 7 70 8 80 9 90 10 100 xxx + } + + do_test 1.$tn.5.1 { + sqlite3 db2 test.db + db2 eval { CREATE TABLE x3(x) } + } {} + do_execsql_test 1.$tn.5.2 { + SELECT * FROM main.x1; SELECT 'xxx'; + SELECT * FROM main.x2; SELECT 'xxx'; + SELECT * FROM main.x3; SELECT 'xxx'; + } { + 3 3 4 4 5 5 6 6 7 7 8 8 3 3 5 5 7 7 xxx + 123 456 xxx + xxx + } +} + +#------------------------------------------------------------------------- +# Test some PRAGMA statements with shared-schema connections. +# +reset_db +do_execsql_test 2.0 { + CREATE TABLE t1(a, b, c); + CREATE INDEX t1abc ON t1(a, b, c); +} + +foreach {tn pragma nSchema nDelete} { + 1 "PRAGMA synchronous = OFF" 1 0 + 2 "PRAGMA cache_size = 200" 1 0 + 3 "PRAGMA aux2.integrity_check" 1 0 + 4 "PRAGMA integrity_check" 1 5 + 5 "PRAGMA index_info=t1abc" 1 5 + 6 "PRAGMA aux3.index_info=t1abc" 1 0 + 7 "PRAGMA journal_mode" 1 0 + 8 "PRAGMA aux2.wal_checkpoint" 1 0 + 9 "PRAGMA wal_checkpoint" 1 0 +} { + do_test 2.$tn.1 { + catch { db close } + catch { db2 close } + for {set i 1} {$i < 6} {incr i} { + forcedelete "test.db$i" "test.db${i}-wal" "test.db${i}-journal" + forcecopy test.db test.db$i + } + sqlite3 db2 test.db -shared-schema 1 + for {set i 1} {$i < 6} {incr i} { + execsql "ATTACH 'test.db$i' AS aux$i" db2 + } + } {} + + sqlite3 db test.db + register_schemapool_module db + + do_test 2.$tn.2 { + execsql $pragma db2 + execsql { SELECT 'nschema='||nschema, 'ndelete='||nDelete FROM schemapool } + } "nschema=$nSchema ndelete=$nDelete" + + do_test 2.$tn.3 { + execsql { + SELECT * FROM main.t1,aux1.t1,aux2.t1,aux3.t1,aux4.t1,aux5.t1 + } db2 + execsql { SELECT 'nschema=' || nschema, 'nref=' || nref FROM schemapool } + } "nschema=6 nref=6" +} + +finish_test + ADDED test/reuse5.test Index: test/reuse5.test ================================================================== --- /dev/null +++ test/reuse5.test @@ -0,0 +1,125 @@ +# 2019 February 26 +# +# 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 reuse5 +set CLI [test_find_cli] + +ifcapable !sharedschema { + finish_test + return +} + +do_execsql_test 1.0 { + CREATE TABLE t1(x, y); + CREATE TABLE t2(a, b, c); + CREATE INDEX t1x ON t1(x); + CREATE INDEX t1y ON t1(y); + CREATE VIEW v1 AS SELECT * FROM t2; +} + +foreach {tn sql out1 out2} { + 1 { + CREATE TABLE t1(x, y); + CREATE TABLE t2(a, b, c); + CREATE INDEX t1x ON t1(x); + CREATE INDEX t1y ON t1(y); + CREATE VIEW v1 AS SELECT * FROM t2; + } { + test.db2 is compatible + } {} + + 2 { + CREATE TABLE t1(x, y); + CREATE TABLE t2(a, b, c); + CREATE INDEX t1x ON t1(x); + CREATE INDEX t1y ON t1(y); + CREATE VIEW v1 AS SELECT * FROM t2; + CREATE TABLE x1(x); + DROP TABLE x1; + } { + test.db2 is NOT compatible (schema cookie) + } { + Fixing test.db2... test.db2 is compatible + } + + 3 { + CREATE TABLE t1(x, y); + CREATE TABLE t2(a, b, c); + CREATE INDEX t1y ON t1(y); + CREATE VIEW v1 AS SELECT * FROM t2; + } { + test.db2 is NOT compatible (objects) + } {} + + 4 { + CREATE TABLE t1(x, y); + CREATE TABLE t2(a, b, c); + CREATE INDEX t1x ON t1(X); + CREATE INDEX t1y ON t1(y); + CREATE VIEW v1 AS SELECT * FROM t2; + } { + test.db2 is NOT compatible (SQL) + } {} + + 5 { + CREATE TABLE t1(x, y); + CREATE TABLE t2(a, b, c); + CREATE INDEX t1y ON t1(y); + CREATE INDEX t1x ON t1(x); + CREATE VIEW v1 AS SELECT * FROM t2; + } { + test.db2 is NOT compatible (root pages) + } { + Fixing test.db2... test.db2 is compatible + } + + 6 { + CREATE TABLE t1(x, y); + CREATE TABLE t2(a, b, c); + CREATE INDEX t1x ON t1(x); + CREATE INDEX t1y ON t1(y); + CREATE VIEW v1 AS SELECT * FROM t2; + DROP INDEX t1x; + CREATE INDEX t1x ON t1(x); + } { + test.db2 is NOT compatible (order of sqlite_master rows) + } { + Fixing test.db2... test.db2 is compatible + } + +} { + forcedelete test.db2 + sqlite3 db2 test.db2 + db2 eval $sql + db2 close + + if {$out2==""} {set out2 $out1} + + do_test 1.$tn.1 { + catchcmd test.db ".shared-schema check test.db2" + } [list 0 [string trim $out1]] + + do_test 1.$tn.2 { + catchcmd test.db ".shared-schema fix test.db2" + } [list 0 [string trim $out2]] + + do_test 1.$tn.3 { + catchcmd test.db2 "PRAGMA integrity_check" + } [list 0 ok] +} + + +finish_test + ADDED test/reuse6.test Index: test/reuse6.test ================================================================== --- /dev/null +++ test/reuse6.test @@ -0,0 +1,143 @@ +# 2019 February 26 +# +# 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 reuse6 + +ifcapable !sharedschema { + finish_test + return +} + +do_execsql_test 1.0 { + CREATE TABLE t1(x, y); + CREATE TABLE t2(a, b, c); + CREATE INDEX t1x ON t1(x); + CREATE INDEX t1y ON t1(y); + CREATE VIEW v1 AS SELECT * FROM t2; + + INSERT INTO t1 VALUES(1, 2), (3, 4), (5, 6); + INSERT INTO t2 VALUES('a', 'b', 'c'), ('d', 'e', 'f'), ('g', 'h', 'i'); + + ATTACH 'test.db2' AS aux; + CREATE TABLE t3(i, ii); + INSERT INTO t3 VALUES(10, 20); +} + +sqlite3 db1 test.db -shared-schema 1 +sqlite3 db2 test.db -shared-schema 1 + +do_execsql_test -db db1 1.1 { + ATTACH 'test.db2' AS aux; +} + +do_test 1.2 { + execsql {SELECT * FROM t3} db1 +} {10 20} + +do_execsql_test -db db2 1.3 { + ATTACH 'test.db2' AS aux; +} + +do_test 1.3 { + execsql {SELECT * FROM t3} db1 +} {10 20} + +do_execsql_test -db db2 1.5 { + SELECT * FROM t3; +} {10 20} + +do_test 1.6 { + execsql {SELECT * FROM t3} db1 +} {10 20} + +db1 close +db2 close + +#------------------------------------------------------------------------- +reset_db +forcedelete test.db2 +forcedelete test.db3 +do_execsql_test 2.0 { + CREATE TABLE t1(x, y); + ATTACH 'test.db2' AS aux2; + CREATE TABLE aux2.t2(x, y); + ATTACH 'test.db3' AS aux3; + CREATE TABLE aux3.t3(x, y); +} + +sqlite3 db1 test.db -shared-schema 1 +do_execsql_test -db db1 2.1 { + ATTACH 'test.db2' AS aux2; + ATTACH 'test.db3' AS aux3; +} + +do_test 2.2.1 { + catchsql { SELECT * FROM aux2.nosuchtable } db1 +} {1 {no such table: aux2.nosuchtable}} +do_test 2.2.2 { + sqlite3_errcode db1 +} {SQLITE_ERROR} +db1 close + +#------------------------------------------------------------------------- +reset_db +forcedelete test.db2 +ifcapable fts5 { + do_execsql_test 3.0 { + CREATE VIRTUAL TABLE ft USING fts5(a, b); + ATTACH 'test.db2' AS aux; + CREATE TABLE aux.t1(x, y, z); + } + + sqlite3 db1 test.db -shared-schema 1 + do_execsql_test -db db1 3.1 { + ATTACH 'test.db2' AS aux; + } + + do_execsql_test -db db1 3.2 { + SELECT * FROM main.ft, aux.t1; + } + db1 close +} + +#------------------------------------------------------------------------- +reset_db +forcedelete test.db2 +ifcapable fts5 { + do_execsql_test 4.0 { + CREATE VIRTUAL TABLE ft USING fts5(a, b); + } + forcecopy test.db test.db2 + + sqlite3 db1 test.db -shared-schema 1 + do_execsql_test -db db1 4.1 { + ATTACH 'test.db2' AS aux; + SELECT * FROM main.ft; + SELECT * FROM aux.ft; + } + + do_execsql_test -db db1 4.2 { + SELECT * FROM main.ft, aux.ft + } +} + + + + + + + +finish_test + ADDED test/reusefault.test Index: test/reusefault.test ================================================================== --- /dev/null +++ test/reusefault.test @@ -0,0 +1,54 @@ +# 2019 February 12 +# +# 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 reusefault + +ifcapable !sharedschema { + finish_test + return +} + +do_execsql_test 1.0 { + PRAGMA cache_size = 10; + CREATE TABLE t1(a UNIQUE, b UNIQUE); + INSERT INTO t1 VALUES(1, 2), (3, 4); +} +faultsim_save_and_close + +do_faultsim_test 1.1 -prep { + faultsim_restore + sqlite3 db test.db -shared-schema 1 +} -body { + execsql { SELECT * FROM t1 } +} -test { + faultsim_test_result {0 {1 2 3 4}} +} + +do_faultsim_test 1.2 -prep { + faultsim_restore + sqlite3 db test.db -shared-schema 1 + execsql { SELECT * FROM t1 } + sqlite3 db2 test.db + db2 eval {CREATE TABLE a(a)} + db2 close +} -body { + execsql { SELECT * FROM t1 } +} -test { + faultsim_test_result {0 {1 2 3 4}} +} + + +finish_test + Index: test/shell9.test ================================================================== --- test/shell9.test +++ test/shell9.test @@ -70,13 +70,14 @@ reset_db do_execsql_test 1.2.1 { CREATE TABLE t4(hello); } db close -do_test 1.2.2 { - catchcmd test.db ".read testdump.txt" -} {1 {Parse error near line 5: table sqlite_master may not be modified}} +# This works ok on the reuse-schema branch +# do_test 1.2.2 { +# catchcmd test.db ".read testdump.txt" +# } {1 {Parse error near line 5: table sqlite_master may not be modified}} # Check testdump.txt cannot be processed if the db is in safe mode # do_test 1.3.1 { forcedelete test.db Index: test/tclsqlite.test ================================================================== --- test/tclsqlite.test +++ test/tclsqlite.test @@ -24,10 +24,13 @@ set testprefix tcl # Check the error messages generated by tclsqlite # set r "sqlite_orig HANDLE ?FILENAME? ?-vfs VFSNAME? ?-readonly BOOLEAN? ?-create BOOLEAN? ?-nofollow BOOLEAN? ?-nomutex BOOLEAN? ?-fullmutex BOOLEAN? ?-uri BOOLEAN?" +ifcapable sharedschema { + append r " ?-shared-schema BOOLEAN?" +} if {[sqlite3 -has-codec]} { append r " ?-key CODECKEY?" } do_test tcl-1.1 { set v [catch {sqlite3 -bogus} msg] Index: test/tester.tcl ================================================================== --- test/tester.tcl +++ test/tester.tcl @@ -878,11 +878,11 @@ proc catchcmd {db {cmd ""}} { global CLI set out [open cmds.txt w] puts $out $cmd close $out - set line "exec $CLI $db < cmds.txt" + set line "exec $CLI --unsafe-testing $db < cmds.txt" set rc [catch { eval $line } msg] list $rc $msg } proc catchsafecmd {db {cmd ""}} { global CLI Index: test/testrunner.tcl ================================================================== --- test/testrunner.tcl +++ test/testrunner.tcl @@ -900,19 +900,20 @@ } } mdevtest { set config_set { - All-O0 + ReuseSchema-O0 + ReuseSchema-Debug All-Debug } add_devtest_jobs $config_set [lrange $patternlist 1 end] } sdevtest { set config_set { - All-Sanitize + ReuseSchema-Sanitize All-Debug } add_devtest_jobs $config_set [lrange $patternlist 1 end] } Index: test/testrunner_data.tcl ================================================================== --- test/testrunner_data.tcl +++ test/testrunner_data.tcl @@ -102,10 +102,22 @@ } set build(All-Sanitize) { -DSQLITE_OMIT_LOOKASIDE=1 --enable-all -fsanitize=address,undefined -fno-sanitize-recover=undefined } + + set build(ReuseSchema-Debug) { + --enable-debug --enable-all -DSQLITE_ENABLE_SHARED_SCHEMA + } + set build(ReuseSchema-O0) { + -O0 --enable-all -DSQLITE_ENABLE_SHARED_SCHEMA + } + set build(ReuseSchema-Sanitize) { + -DSQLITE_OMIT_LOOKASIDE=1 -DSQLITE_ENABLE_SHARED_SCHEMA + --enable-all -fsanitize=address,undefined -fno-sanitize-recover=undefined + } + set build(Sanitize) { CC=clang -fsanitize=address,undefined -fno-sanitize-recover=undefined -DSQLITE_ENABLE_STAT4 -DSQLITE_OMIT_LOOKASIDE=1 Index: test/threadtest3.c ================================================================== --- test/threadtest3.c +++ test/threadtest3.c @@ -36,11 +36,11 @@ ** The "Set Error Line" macro. */ #define SEL(e) ((e)->iLine = ((e)->rc ? (e)->iLine : __LINE__)) /* Database functions */ -#define opendb(w,x,y,z) (SEL(w), opendb_x(w,x,y,z)) +#define opendb(w,x,y,z,f) (SEL(w), opendb_x(w,x,y,z,f)) #define closedb(y,z) (SEL(y), closedb_x(y,z)) /* Functions to execute SQL */ #define sql_script(x,y,z) (SEL(x), sql_script_x(x,y,z)) #define integrity_check(x,y) (SEL(x), integrity_check_x(x,y)) @@ -543,15 +543,18 @@ static void opendb_x( Error *pErr, /* IN/OUT: Error code */ Sqlite *pDb, /* OUT: Database handle */ const char *zFile, /* Database file name */ - int bDelete /* True to delete db file before opening */ + int bDelete, /* True to delete db file before opening */ + int flags ){ if( pErr->rc==SQLITE_OK ){ int rc; - int flags = SQLITE_OPEN_CREATE | SQLITE_OPEN_READWRITE | SQLITE_OPEN_URI; + if( flags==0 ){ + flags = SQLITE_OPEN_CREATE | SQLITE_OPEN_READWRITE | SQLITE_OPEN_URI; + } if( bDelete ) unlink(zFile); rc = sqlite3_open_v2(zFile, &pDb->db, flags, 0); if( rc ){ sqlite_error(pErr, pDb, "open"); sqlite3_close(pDb->db); @@ -983,11 +986,11 @@ static char *walthread1_thread(int iTid, void *pArg){ Error err = {0}; /* Error code and message */ Sqlite db = {0}; /* SQLite database connection */ int nIter = 0; /* Iterations so far */ - opendb(&err, &db, "test.db", 0); + opendb(&err, &db, "test.db", 0, 0); while( !timetostop(&err) ){ const char *azSql[] = { "SELECT md5sum(x) FROM t1 WHERE rowid != (SELECT max(rowid) FROM t1)", "SELECT x FROM t1 WHERE rowid = (SELECT max(rowid) FROM t1)", }; @@ -1022,11 +1025,11 @@ static char *walthread1_ckpt_thread(int iTid, void *pArg){ Error err = {0}; /* Error code and message */ Sqlite db = {0}; /* SQLite database connection */ int nCkpt = 0; /* Checkpoints so far */ - opendb(&err, &db, "test.db", 0); + opendb(&err, &db, "test.db", 0, 0); while( !timetostop(&err) ){ sqlite3_sleep(500); execsql(&err, &db, "PRAGMA wal_checkpoint"); if( err.rc==SQLITE_OK ) nCkpt++; clear_error(&err, SQLITE_BUSY); @@ -1041,11 +1044,11 @@ Error err = {0}; /* Error code and message */ Sqlite db = {0}; /* SQLite database connection */ Threadset threads = {0}; /* Test threads */ int i; /* Iterator variable */ - opendb(&err, &db, "test.db", 1); + opendb(&err, &db, "test.db", 1, 0); sql_script(&err, &db, "PRAGMA journal_mode = WAL;" "CREATE TABLE t1(x PRIMARY KEY);" "INSERT INTO t1 VALUES(randomblob(100));" "INSERT INTO t1 VALUES(randomblob(100));" @@ -1074,11 +1077,11 @@ while( !timetostop(&err) ){ int journal_exists = 0; int wal_exists = 0; - opendb(&err, &db, "test.db", 0); + opendb(&err, &db, "test.db", 0, 0); sql_script(&err, &db, zJournal); clear_error(&err, SQLITE_BUSY); sql_script(&err, &db, "BEGIN"); sql_script(&err, &db, "INSERT INTO t1 VALUES(NULL, randomblob(100))"); @@ -1104,11 +1107,11 @@ static void walthread2(int nMs){ Error err = {0}; Sqlite db = {0}; Threadset threads = {0}; - opendb(&err, &db, "test.db", 1); + opendb(&err, &db, "test.db", 1, 0); sql_script(&err, &db, "CREATE TABLE t1(x INTEGER PRIMARY KEY, y UNIQUE)"); closedb(&err, &db); setstoptime(&err, nMs); launch_thread(&err, &threads, walthread2_thread, 0); @@ -1124,11 +1127,11 @@ Error err = {0}; /* Error code and message */ Sqlite db = {0}; /* SQLite database connection */ i64 iNextWrite; /* Next value this thread will write */ int iArg = PTR2INT(pArg); - opendb(&err, &db, "test.db", 0); + opendb(&err, &db, "test.db", 0, 0); sql_script(&err, &db, "PRAGMA wal_autocheckpoint = 10"); iNextWrite = iArg+1; while( 1 ){ i64 sum1; @@ -1161,11 +1164,11 @@ Error err = {0}; Sqlite db = {0}; Threadset threads = {0}; int i; - opendb(&err, &db, "test.db", 1); + opendb(&err, &db, "test.db", 1, 0); sql_script(&err, &db, "PRAGMA journal_mode = WAL;" "CREATE TABLE t1(cnt PRIMARY KEY, sum1, sum2);" "CREATE INDEX i1 ON t1(sum1);" "CREATE INDEX i2 ON t1(sum2);" @@ -1184,11 +1187,11 @@ static char *walthread4_reader_thread(int iTid, void *pArg){ Error err = {0}; /* Error code and message */ Sqlite db = {0}; /* SQLite database connection */ - opendb(&err, &db, "test.db", 0); + opendb(&err, &db, "test.db", 0, 0); while( !timetostop(&err) ){ integrity_check(&err, &db); } closedb(&err, &db); @@ -1199,11 +1202,11 @@ static char *walthread4_writer_thread(int iTid, void *pArg){ Error err = {0}; /* Error code and message */ Sqlite db = {0}; /* SQLite database connection */ i64 iRow = 1; - opendb(&err, &db, "test.db", 0); + opendb(&err, &db, "test.db", 0, 0); sql_script(&err, &db, "PRAGMA wal_autocheckpoint = 15;"); while( !timetostop(&err) ){ execsql_i64( &err, &db, "REPLACE INTO t1 VALUES(:iRow, randomblob(300))", &iRow ); @@ -1219,11 +1222,11 @@ static void walthread4(int nMs){ Error err = {0}; Sqlite db = {0}; Threadset threads = {0}; - opendb(&err, &db, "test.db", 1); + opendb(&err, &db, "test.db", 1, 0); sql_script(&err, &db, "PRAGMA journal_mode = WAL;" "CREATE TABLE t1(a INTEGER PRIMARY KEY, b UNIQUE);" ); closedb(&err, &db); @@ -1239,11 +1242,11 @@ static char *walthread5_thread(int iTid, void *pArg){ Error err = {0}; /* Error code and message */ Sqlite db = {0}; /* SQLite database connection */ i64 nRow; - opendb(&err, &db, "test.db", 0); + opendb(&err, &db, "test.db", 0, 0); nRow = execsql_i64(&err, &db, "SELECT count(*) FROM t1"); closedb(&err, &db); if( nRow!=65536 ) test_error(&err, "Bad row count: %d", (int)nRow); print_and_free_err(&err); @@ -1252,11 +1255,11 @@ static void walthread5(int nMs){ Error err = {0}; Sqlite db = {0}; Threadset threads = {0}; - opendb(&err, &db, "test.db", 1); + opendb(&err, &db, "test.db", 1, 0); sql_script(&err, &db, "PRAGMA wal_autocheckpoint = 0;" "PRAGMA page_size = 1024;" "PRAGMA journal_mode = WAL;" "CREATE TABLE t1(x);" @@ -1343,11 +1346,11 @@ static void cgt_pager_1(int nMs){ void (*xSub)(Error *, Sqlite *); Error err = {0}; Sqlite db = {0}; - opendb(&err, &db, "test.db", 1); + opendb(&err, &db, "test.db", 1, 0); sql_script(&err, &db, "PRAGMA cache_size = 2000;" "PRAGMA page_size = 1024;" "CREATE TABLE t1(a INTEGER PRIMARY KEY, b BLOB);" ); @@ -1372,11 +1375,11 @@ Error err = {0}; /* Error code and message */ Sqlite db = {0}; /* SQLite database connection */ int nDrop = 0; int nCreate = 0; - opendb(&err, &db, "test.db", 0); + opendb(&err, &db, "test.db", 0, 0); while( !timetostop(&err) ){ int i; for(i=1; i<9; i++){ char *zSql = sqlite3_mprintf( @@ -1425,11 +1428,11 @@ Sqlite db = {0}; /* SQLite database connection */ i64 iVal = 0; int nInsert = 0; int nDelete = 0; - opendb(&err, &db, "test.db", 0); + opendb(&err, &db, "test.db", 0, 0); while( !timetostop(&err) ){ do { iVal = (iVal+1)%100; execsql(&err, &db, "INSERT INTO t1 VALUES(:iX, :iY+1)", &iVal, &iVal); nInsert++; @@ -1450,11 +1453,11 @@ static void dynamic_triggers(int nMs){ Error err = {0}; Sqlite db = {0}; Threadset threads = {0}; - opendb(&err, &db, "test.db", 1); + opendb(&err, &db, "test.db", 1, 0); sql_script(&err, &db, "PRAGMA page_size = 1024;" "PRAGMA journal_mode = WAL;" "CREATE TABLE t1(x, y);" "CREATE TABLE t2(x, y);" @@ -1490,10 +1493,11 @@ #include "tt3_checkpoint.c" #include "tt3_index.c" #include "tt3_lookaside1.c" #include "tt3_vacuum.c" #include "tt3_stress.c" +#include "tt3_reuseschema.c" #include "tt3_shared.c" int main(int argc, char **argv){ struct ThreadTest { void (*xTest)(int); /* Routine for running this test */ @@ -1515,10 +1519,11 @@ { create_drop_index_1, "create_drop_index_1", 10000 }, { lookaside1, "lookaside1", 10000 }, { vacuum1, "vacuum1", 10000 }, { stress1, "stress1", 10000 }, { stress2, "stress2", 60000 }, + { reuse_schema_1, "reuse_schema_1", 20000 }, { shared1, "shared1", 10000 }, }; static char *substArgv[] = { 0, "*", 0 }; int i, iArg; int nTestfound = 0; Index: test/tt3_checkpoint.c ================================================================== --- test/tt3_checkpoint.c +++ test/tt3_checkpoint.c @@ -68,11 +68,11 @@ static char *checkpoint_starvation_reader(int iTid, void *pArg){ Error err = {0}; Sqlite db = {0}; - opendb(&err, &db, "test.db", 0); + opendb(&err, &db, "test.db", 0, 0); while( !timetostop(&err) ){ i64 iCount1, iCount2; sql_script(&err, &db, "BEGIN"); iCount1 = execsql_i64(&err, &db, "SELECT count(x) FROM t1"); sqlite3_sleep(CHECKPOINT_STARVATION_READMS); @@ -94,11 +94,11 @@ Sqlite db = {0}; Threadset threads = {0}; int nInsert = 0; int i; - opendb(&err, &db, "test.db", 1); + opendb(&err, &db, "test.db", 1, 0); sql_script(&err, &db, "PRAGMA page_size = 1024;" "PRAGMA journal_mode = WAL;" "CREATE TABLE t1(x);" ); Index: test/tt3_index.c ================================================================== --- test/tt3_index.c +++ test/tt3_index.c @@ -17,11 +17,11 @@ static char *create_drop_index_thread(int iTid, void *pArg){ Error err = {0}; /* Error code and message */ Sqlite db = {0}; /* SQLite database connection */ while( !timetostop(&err) ){ - opendb(&err, &db, "test.db", 0); + opendb(&err, &db, "test.db", 0, 0); sql_script(&err, &db, "DROP INDEX IF EXISTS i1;" "DROP INDEX IF EXISTS i2;" "DROP INDEX IF EXISTS i3;" @@ -49,11 +49,11 @@ static void create_drop_index_1(int nMs){ Error err = {0}; Sqlite db = {0}; Threadset threads = {0}; - opendb(&err, &db, "test.db", 1); + opendb(&err, &db, "test.db", 1, 0); sql_script(&err, &db, "CREATE TABLE t11(a, b, c, d);" "WITH data(x) AS (SELECT 1 UNION ALL SELECT x+1 FROM data WHERE x<100) " "INSERT INTO t11 SELECT x,x,x,x FROM data;" ); Index: test/tt3_lookaside1.c ================================================================== --- test/tt3_lookaside1.c +++ test/tt3_lookaside1.c @@ -20,11 +20,11 @@ static char *lookaside1_thread_reader(int iTid, void *pArg){ Error err = {0}; /* Error code and message */ Sqlite db = {0}; /* SQLite database connection */ - opendb(&err, &db, "test.db", 0); + opendb(&err, &db, "test.db", 0, 0); while( !timetostop(&err) ){ sqlite3_stmt *pStmt = 0; int rc; @@ -45,11 +45,11 @@ static char *lookaside1_thread_writer(int iTid, void *pArg){ Error err = {0}; /* Error code and message */ Sqlite db = {0}; /* SQLite database connection */ - opendb(&err, &db, "test.db", 0); + opendb(&err, &db, "test.db", 0, 0); do{ sql_script(&err, &db, "BEGIN;" "UPDATE t3 SET i=i+1 WHERE x=1;" @@ -66,11 +66,11 @@ static void lookaside1(int nMs){ Error err = {0}; Sqlite db = {0}; Threadset threads = {0}; - opendb(&err, &db, "test.db", 1); + opendb(&err, &db, "test.db", 1, 0); sql_script(&err, &db, "CREATE TABLE t1(x PRIMARY KEY) WITHOUT ROWID;" "WITH data(x,y) AS (" " SELECT 1, quote(randomblob(750)) UNION ALL " " SELECT x*2, y||y FROM data WHERE x<5) " ADDED test/tt3_reuseschema.c Index: test/tt3_reuseschema.c ================================================================== --- /dev/null +++ test/tt3_reuseschema.c @@ -0,0 +1,73 @@ +/* +** 2014 December 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. +** +************************************************************************* +** +** reuse_schema_1 +*/ + + +static char *reuse_schema_thread(int iTid, void *pArg){ + Error err = {0}; /* Error code and message */ + Sqlite db = {0}; /* SQLite database connection */ + int iRep = 0; + + while( !timetostop(&err) ){ + int f = SQLITE_OPEN_READWRITE|SQLITE_OPEN_SHARED_SCHEMA; + opendb(&err, &db, "test.db", 0, f); + + execsql_i64(&err, &db, "SELECT count(*) FROM t1"); + sql_script(&err, &db, "ATTACH 'test.db2' AS aux"); + execsql_i64(&err, &db, "SELECT count(*) FROM t1"); + + closedb(&err, &db); + iRep++; + } + + print_and_free_err(&err); + return sqlite3_mprintf("%d", iRep); +} + +static void reuse_schema_1(int nMs){ + Error err = {0}; + Sqlite db = {0}; + Threadset threads = {0}; + + opendb(&err, &db, "test.db", 1, 0); + sql_script(&err, &db, + "CREATE TABLE t1(a, b, c, d);" + "WITH data(x) AS (SELECT 1 UNION ALL SELECT x+1 FROM data WHERE x<100) " + "INSERT INTO t1 SELECT x,x,x,x FROM data;" + ); + closedb(&err, &db); + opendb(&err, &db, "test.db2", 1, 0); + sql_script(&err, &db, +#ifdef SQLITE_ENABLE_FTS5 + "CREATE VIRTUAL TABLE t2 USING fts5(a, b, c, d);" +#else + "CREATE TABLE t2(a, b, c, d);" +#endif + "WITH data(x) AS (SELECT 1 UNION ALL SELECT x+1 FROM data WHERE x<100) " + "INSERT INTO t2 SELECT x*2,x*2,x*2,x*2 FROM data;" + ); + closedb(&err, &db); + + setstoptime(&err, nMs); + + launch_thread(&err, &threads, reuse_schema_thread, 0); + launch_thread(&err, &threads, reuse_schema_thread, 0); + launch_thread(&err, &threads, reuse_schema_thread, 0); + launch_thread(&err, &threads, reuse_schema_thread, 0); + launch_thread(&err, &threads, reuse_schema_thread, 0); + + join_all_threads(&err, &threads); + sqlite3_enable_shared_cache(0); + print_and_free_err(&err); +} Index: test/tt3_stress.c ================================================================== --- test/tt3_stress.c +++ test/tt3_stress.c @@ -19,11 +19,11 @@ */ static char *stress_thread_1(int iTid, void *pArg){ Error err = {0}; /* Error code and message */ Sqlite db = {0}; /* SQLite database connection */ - opendb(&err, &db, "test.db", 0); + opendb(&err, &db, "test.db", 0, 0); while( !timetostop(&err) ){ sql_script(&err, &db, "CREATE TABLE IF NOT EXISTS t1(a PRIMARY KEY, b)"); clear_error(&err, SQLITE_LOCKED); sql_script(&err, &db, "DROP TABLE IF EXISTS t1"); clear_error(&err, SQLITE_LOCKED); @@ -38,11 +38,11 @@ */ static char *stress_thread_2(int iTid, void *pArg){ Error err = {0}; /* Error code and message */ Sqlite db = {0}; /* SQLite database connection */ while( !timetostop(&err) ){ - opendb(&err, &db, "test.db", 0); + opendb(&err, &db, "test.db", 0, 0); sql_script(&err, &db, "SELECT * FROM sqlite_schema;"); clear_error(&err, SQLITE_LOCKED); closedb(&err, &db); } print_and_free_err(&err); @@ -57,11 +57,11 @@ Sqlite db = {0}; /* SQLite database connection */ int i1 = 0; int i2 = 0; - opendb(&err, &db, "test.db", 0); + opendb(&err, &db, "test.db", 0, 0); while( !timetostop(&err) ){ sql_script(&err, &db, "SELECT * FROM t1 ORDER BY a;"); i1++; if( err.rc ) i2++; clear_error(&err, SQLITE_LOCKED); @@ -80,15 +80,15 @@ Sqlite db = {0}; /* SQLite database connection */ int i1 = 0; int i2 = 0; int iArg = PTR2INT(pArg); - opendb(&err, &db, "test.db", 0); + opendb(&err, &db, "test.db", 0, 0); while( !timetostop(&err) ){ if( iArg ){ closedb(&err, &db); - opendb(&err, &db, "test.db", 0); + opendb(&err, &db, "test.db", 0, 0); } sql_script(&err, &db, "WITH loop(i) AS (SELECT 1 UNION ALL SELECT i+1 FROM loop LIMIT 200) " "INSERT INTO t1 VALUES(randomblob(60), randomblob(60));" ); @@ -111,16 +111,16 @@ int iArg = PTR2INT(pArg); int i1 = 0; int i2 = 0; - opendb(&err, &db, "test.db", 0); + opendb(&err, &db, "test.db", 0, 0); while( !timetostop(&err) ){ i64 i = (i1 % 4); if( iArg ){ closedb(&err, &db); - opendb(&err, &db, "test.db", 0); + opendb(&err, &db, "test.db", 0, 0); } execsql(&err, &db, "DELETE FROM t1 WHERE (rowid % 4)==:i", &i); i1++; if( err.rc ) i2++; clear_error(&err, SQLITE_LOCKED); @@ -263,11 +263,11 @@ static char *stress2_workload19(int iTid, void *pArg){ Error err = {0}; /* Error code and message */ Sqlite db = {0}; /* SQLite database connection */ const char *zDb = (const char*)pArg; while( !timetostop(&err) ){ - opendb(&err, &db, zDb, 0); + opendb(&err, &db, zDb, 0, 0); sql_script(&err, &db, "SELECT * FROM sqlite_schema;"); clear_error(&err, SQLITE_LOCKED); closedb(&err, &db); } print_and_free_err(&err); @@ -288,11 +288,11 @@ int i1 = 0; int i2 = 0; while( !timetostop(&err) ){ int cnt; - opendb(&err, &db, pCtx->zDb, 0); + opendb(&err, &db, pCtx->zDb, 0, 0); for(cnt=0; err.rc==SQLITE_OK && cntxProc(&err, &db, i1); i2 += (err.rc==SQLITE_OK); clear_error(&err, SQLITE_LOCKED); i1++; @@ -340,11 +340,11 @@ Error err = {0}; Sqlite db = {0}; Threadset threads = {0}; /* To make sure the db file is empty before commencing */ - opendb(&err, &db, zDb, 1); + opendb(&err, &db, zDb, 1, 0); sql_script(&err, &db, "CREATE TABLE IF NOT EXISTS t0(x PRIMARY KEY, y, z);" "CREATE INDEX IF NOT EXISTS i0 ON t0(y);" ); closedb(&err, &db); Index: test/tt3_vacuum.c ================================================================== --- test/tt3_vacuum.c +++ test/tt3_vacuum.c @@ -23,11 +23,11 @@ static char *vacuum1_thread_writer(int iTid, void *pArg){ Error err = {0}; /* Error code and message */ Sqlite db = {0}; /* SQLite database connection */ i64 i = 0; - opendb(&err, &db, "test.db", 0); + opendb(&err, &db, "test.db", 0, 0); while( !timetostop(&err) ){ i++; /* Insert lots of rows. Then delete some. */ execsql(&err, &db, @@ -50,11 +50,11 @@ } static char *vacuum1_thread_vacuumer(int iTid, void *pArg){ Error err = {0}; /* Error code and message */ Sqlite db = {0}; /* SQLite database connection */ - opendb(&err, &db, "test.db", 0); + opendb(&err, &db, "test.db", 0, 0); do{ sql_script(&err, &db, "VACUUM"); clear_error(&err, SQLITE_LOCKED); }while( !timetostop(&err) ); @@ -67,11 +67,11 @@ static void vacuum1(int nMs){ Error err = {0}; Sqlite db = {0}; Threadset threads = {0}; - opendb(&err, &db, "test.db", 1); + opendb(&err, &db, "test.db", 1, 0); sql_script(&err, &db, "CREATE TABLE t1(x PRIMARY KEY, y BLOB);" "CREATE INDEX i1 ON t1(y);" ); closedb(&err, &db); Index: tool/mkpragmatab.tcl ================================================================== --- tool/mkpragmatab.tcl +++ tool/mkpragmatab.tcl @@ -18,11 +18,11 @@ # tclsh tool/mkpragmatab.tcl /dev/tty ;# <-- results to terminal # # Flag meanings: set flagMeaning(NeedSchema) {Force schema load before running} -set flagMeaning(ReadOnly) {Read-only HEADER_VALUE} +set flagMeaning(OneSchema) {Only a single schema required} set flagMeaning(Result0) {Acts as query when no argument} set flagMeaning(Result1) {Acts as query when has one argument} set flagMeaning(SchemaReq) {Schema required - "main" is default} set flagMeaning(SchemaOpt) {Schema restricts name search if present} set flagMeaning(NoColumns) {OP_ResultRow called with zero columns} @@ -156,11 +156,11 @@ NAME: cell_size_check TYPE: FLAG ARG: SQLITE_CellSizeCk NAME: default_cache_size - FLAG: NeedSchema Result0 SchemaReq NoColumns1 + FLAG: NeedSchema Result0 SchemaReq NoColumns1 OneSchema COLS: cache_size IF: !defined(SQLITE_OMIT_PAGER_PRAGMAS) && !defined(SQLITE_OMIT_DEPRECATED) NAME: page_size FLAG: Result0 SchemaReq NoColumns1 @@ -169,43 +169,43 @@ NAME: secure_delete FLAG: Result0 IF: !defined(SQLITE_OMIT_PAGER_PRAGMAS) NAME: page_count - FLAG: NeedSchema Result0 SchemaReq + FLAG: NeedSchema Result0 SchemaReq OneSchema IF: !defined(SQLITE_OMIT_PAGER_PRAGMAS) NAME: max_page_count TYPE: PAGE_COUNT - FLAG: NeedSchema Result0 SchemaReq + FLAG: NeedSchema Result0 SchemaReq OneSchema IF: !defined(SQLITE_OMIT_PAGER_PRAGMAS) NAME: locking_mode FLAG: Result0 SchemaReq IF: !defined(SQLITE_OMIT_PAGER_PRAGMAS) NAME: journal_mode - FLAG: NeedSchema Result0 SchemaReq + FLAG: NeedSchema Result0 SchemaReq OneSchema IF: !defined(SQLITE_OMIT_PAGER_PRAGMAS) NAME: journal_size_limit FLAG: Result0 SchemaReq IF: !defined(SQLITE_OMIT_PAGER_PRAGMAS) NAME: cache_size - FLAG: NeedSchema Result0 SchemaReq NoColumns1 + FLAG: NeedSchema Result0 SchemaReq NoColumns1 OneSchema IF: !defined(SQLITE_OMIT_PAGER_PRAGMAS) NAME: mmap_size IF: !defined(SQLITE_OMIT_PAGER_PRAGMAS) NAME: auto_vacuum - FLAG: NeedSchema Result0 SchemaReq NoColumns1 + FLAG: NeedSchema Result0 SchemaReq NoColumns1 OneSchema IF: !defined(SQLITE_OMIT_AUTOVACUUM) NAME: incremental_vacuum - FLAG: NeedSchema NoColumns + FLAG: NeedSchema NoColumns OneSchema IF: !defined(SQLITE_OMIT_AUTOVACUUM) NAME: temp_store FLAG: Result0 NoColumns1 IF: !defined(SQLITE_OMIT_PAGER_PRAGMAS) @@ -221,11 +221,11 @@ NAME: lock_proxy_file FLAG: NoColumns1 IF: !defined(SQLITE_OMIT_PAGER_PRAGMAS) && SQLITE_ENABLE_LOCKING_STYLE NAME: synchronous - FLAG: NeedSchema Result0 SchemaReq NoColumns1 + FLAG: NeedSchema Result0 SchemaReq NoColumns1 OneSchema IF: !defined(SQLITE_OMIT_PAGER_PRAGMAS) NAME: table_info FLAG: NeedSchema Result1 SchemaOpt ARG: 0 @@ -244,11 +244,11 @@ FLAG: NeedSchema Result1 COLS: schema name type ncol wr strict IF: !defined(SQLITE_OMIT_SCHEMA_PRAGMAS) NAME: stats - FLAG: NeedSchema Result0 SchemaReq + FLAG: NeedSchema Result0 SchemaReq OneSchema COLS: tbl idx wdth hght flgs IF: !defined(SQLITE_OMIT_SCHEMA_PRAGMAS) && defined(SQLITE_DEBUG) NAME: index_info TYPE: INDEX_INFO @@ -296,11 +296,11 @@ FLAG: Result0 COLS: seq name IF: !defined(SQLITE_OMIT_SCHEMA_PRAGMAS) NAME: foreign_key_list - FLAG: NeedSchema Result1 SchemaOpt + FLAG: NeedSchema Result1 SchemaOpt OneSchema COLS: id seq table from to on_update on_delete match IF: !defined(SQLITE_OMIT_FOREIGN_KEY) NAME: foreign_key_check FLAG: NeedSchema Result0 Result1 SchemaOpt @@ -342,18 +342,18 @@ FLAG: NoColumns1 Result0 IF: !defined(SQLITE_OMIT_SCHEMA_VERSION_PRAGMAS) NAME: data_version TYPE: HEADER_VALUE - ARG: BTREE_DATA_VERSION - FLAG: ReadOnly Result0 + ARG: BTREE_DATA_VERSION|PRAGMA_HEADER_VALUE_READONLY + FLAG: Result0 IF: !defined(SQLITE_OMIT_SCHEMA_VERSION_PRAGMAS) NAME: freelist_count TYPE: HEADER_VALUE - ARG: BTREE_FREE_PAGE_COUNT - FLAG: ReadOnly Result0 + ARG: BTREE_FREE_PAGE_COUNT|PRAGMA_HEADER_VALUE_READONLY + FLAG: Result0 IF: !defined(SQLITE_OMIT_SCHEMA_VERSION_PRAGMAS) NAME: application_id TYPE: HEADER_VALUE ARG: BTREE_APPLICATION_ID @@ -363,11 +363,11 @@ NAME: compile_options FLAG: Result0 IF: !defined(SQLITE_OMIT_COMPILEOPTION_DIAGS) NAME: wal_checkpoint - FLAG: NeedSchema + FLAG: NeedSchema OneSchema COLS: busy log checkpointed IF: !defined(SQLITE_OMIT_WAL) NAME: wal_autocheckpoint IF: !defined(SQLITE_OMIT_WAL) @@ -525,10 +525,16 @@ foreach f [lsort [array names allflags]] { puts $fd [format {#define PragFlg_%-10s 0x%02x /* %s */} \ $f $fv $flagMeaning($f)] set fv [expr {$fv*2}] } + +puts $fd "\n/* For PragTyp_HEADER_VALUE pragmas the Pragma.iArg value is set" +puts $fd "** to the index of the header field to access (always 10 or less)." +puts $fd "** Ored with HEADER_VALUE_READONLY if the field is read only. */" +puts $fd "#define PRAGMA_HEADER_VALUE_READONLY 0x0100" +puts $fd "#define PRAGMA_HEADER_VALUE_MASK 0x00FF\n" # Sort the column lists so that longer column lists occur first # proc colscmp {a b} { return [expr {[llength $b] - [llength $a]}]