Index: src/alter.c ================================================================== --- src/alter.c +++ src/alter.c @@ -104,11 +104,11 @@ assert( pSrc->nSrc==1 ); assert( sqlite3BtreeHoldsAllMutexes(pParse->db) ); pTab = sqlite3LocateTableItem(pParse, 0, &pSrc->a[0]); if( !pTab ) goto exit_rename_table; - iDb = sqlite3SchemaToIndex(pParse->db, pTab->pSchema); + iDb = sqlite3SchemaToIndex2(pParse->db, pTab->pSchema, 0); zDb = db->aDb[iDb].zDbSName; db->mDbFlags |= DBFLAG_PreferBuiltin; /* Get a NULL terminated version of the new table name. */ zName = sqlite3NameFromToken(db, pName); @@ -274,11 +274,11 @@ if( pParse->nErr || db->mallocFailed ) return; pNew = pParse->pNewTable; assert( pNew ); assert( sqlite3BtreeHoldsAllMutexes(db) ); - iDb = sqlite3SchemaToIndex(db, pNew->pSchema); + iDb = sqlite3SchemaToIndex2(db, pNew->pSchema, 0); zDb = db->aDb[iDb].zDbSName; zTab = &pNew->zName[16]; /* Skip the "sqlite_altertab_" prefix on the name */ pCol = &pNew->aCol[pNew->nCol-1]; pDflt = pCol->pDflt; pTab = sqlite3FindTable(db, zTab, zDb); @@ -427,11 +427,11 @@ if( SQLITE_OK!=isSystemTable(pParse, pTab->zName) ){ goto exit_begin_add_column; } assert( pTab->addColOffset>0 ); - iDb = sqlite3SchemaToIndex(db, pTab->pSchema); + iDb = sqlite3SchemaToIndex2(db, pTab->pSchema, 0); /* Put a copy of the Table struct in Parse.pNewTable for the ** sqlite3AddColumn() function and friends to modify. But modify ** the name by adding an "sqlite_altertab_" prefix. By adding this ** prefix, we insure that the name will not collide with an existing @@ -528,11 +528,11 @@ /* Cannot alter a system table */ if( SQLITE_OK!=isSystemTable(pParse, pTab->zName) ) goto exit_rename_column; if( SQLITE_OK!=isRealTable(pParse, pTab) ) goto exit_rename_column; /* Which schema holds the table to be altered */ - iSchema = sqlite3SchemaToIndex(db, pTab->pSchema); + iSchema = sqlite3SchemaToIndex2(db, pTab->pSchema, 0); assert( iSchema>=0 ); zDb = db->aDb[iSchema].zDbSName; #ifndef SQLITE_OMIT_AUTHORIZATION /* Invoke the authorization callback. */ @@ -1056,11 +1056,11 @@ memset(&sNC, 0, sizeof(sNC)); sNC.pParse = pParse; assert( pNew->pTabSchema ); pParse->pTriggerTab = sqlite3FindTable(db, pNew->table, - db->aDb[sqlite3SchemaToIndex(db, pNew->pTabSchema)].zDbSName + db->aDb[sqlite3SchemaToIndex2(db, pNew->pTabSchema, 0)].zDbSName ); pParse->eTriggerOp = pNew->op; /* ALWAYS() because if the table of the trigger does not exist, the ** error would have been hit before this point */ if( ALWAYS(pParse->pTriggerTab) ){ @@ -1573,11 +1573,11 @@ else if( sParse.pNewTrigger ){ if( isLegacy==0 ){ rc = renameResolveTrigger(&sParse, bTemp ? 0 : zDb); } if( rc==SQLITE_OK ){ - int i1 = sqlite3SchemaToIndex(db, sParse.pNewTrigger->pTabSchema); + int i1 = sqlite3SchemaToIndex2(db, sParse.pNewTrigger->pTabSchema, 0); int i2 = sqlite3FindDbName(db, zDb); if( i1==i2 ) sqlite3_result_int(context, 1); } } } Index: src/analyze.c ================================================================== --- src/analyze.c +++ src/analyze.c @@ -977,10 +977,11 @@ ** Generate code to do an analysis of all indices associated with ** a single table. */ static void analyzeOneTable( Parse *pParse, /* Parser context */ + int iDb, /* Database that contains table pTab */ Table *pTab, /* Table whose indices are to be analyzed */ Index *pOnlyIdx, /* If not NULL, only analyze this one index */ int iStatCur, /* Index of VdbeCursor that writes the sqlite_stat1 table */ int iMem, /* Available memory locations begin here */ int iTab /* Next available cursor */ @@ -990,11 +991,10 @@ int iIdxCur; /* Cursor open on index being analyzed */ int iTabCur; /* Table cursor */ Vdbe *v; /* The virtual machine being built up */ int i; /* Loop counter */ int jZeroRows = -1; /* Jump from here if number of rows is zero */ - int iDb; /* Index of database containing pTab */ u8 needTableCnt = 1; /* True to count the table */ int regNewRowid = iMem++; /* Rowid for the inserted record */ int regStat4 = iMem++; /* Register to hold Stat4Accum object */ int regChng = iMem++; /* Index of changed index field */ #ifdef SQLITE_ENABLE_STAT3_OR_STAT4 @@ -1021,11 +1021,10 @@ if( sqlite3_strlike("sqlite\\_%", pTab->zName, '\\')==0 ){ /* Do not gather statistics on system tables */ return; } assert( sqlite3BtreeHoldsAllMutexes(db) ); - iDb = sqlite3SchemaToIndex(db, pTab->pSchema); assert( iDb>=0 ); assert( sqlite3SchemaMutexHeld(db, iDb, 0) ); #ifndef SQLITE_OMIT_AUTHORIZATION if( sqlite3AuthCheck(pParse, SQLITE_ANALYZE, pTab->zName, 0, db->aDb[iDb].zDbSName ) ){ @@ -1116,11 +1115,11 @@ ** when building a record to insert into the sample column of ** the sqlite_stat4 table. */ pParse->nMem = MAX(pParse->nMem, regPrev+nColTest); /* Open a read-only cursor on the index being analyzed. */ - assert( iDb==sqlite3SchemaToIndex(db, pIdx->pSchema) ); + assert( db->aDb[iDb].pSchema==pIdx->pSchema ); sqlite3VdbeAddOp3(v, OP_OpenRead, iIdxCur, pIdx->tnum, iDb); sqlite3VdbeSetP4KeyInfo(pParse, pIdx); VdbeComment((v, "%s", pIdx->zName)); /* Invoke the stat_init() function. The arguments are: @@ -1337,46 +1336,45 @@ HashElem *k; int iStatCur; int iMem; int iTab; - sqlite3SchemaWritable(pParse, iDb); sqlite3BeginWriteOperation(pParse, 0, iDb); iStatCur = pParse->nTab; pParse->nTab += 3; openStatTable(pParse, iDb, iStatCur, 0, 0); iMem = pParse->nMem+1; iTab = pParse->nTab; assert( sqlite3SchemaMutexHeld(db, iDb, 0) ); for(k=sqliteHashFirst(&pSchema->tblHash); k; k=sqliteHashNext(k)){ Table *pTab = (Table*)sqliteHashData(k); - analyzeOneTable(pParse, pTab, 0, iStatCur, iMem, iTab); + analyzeOneTable(pParse, iDb, pTab, 0, iStatCur, iMem, iTab); } loadAnalysis(pParse, iDb); } /* ** Generate code that will do an analysis of a single table in ** a database. If pOnlyIdx is not NULL then it is a single index ** in pTab that should be analyzed. */ -static void analyzeTable(Parse *pParse, Table *pTab, Index *pOnlyIdx){ - int iDb; +static void analyzeTable(Parse *pParse, int iDb, Table *pTab, Index *pOnlyIdx){ int iStatCur; assert( pTab!=0 ); assert( sqlite3BtreeHoldsAllMutexes(pParse->db) ); - iDb = sqlite3SchemaToIndex(pParse->db, pTab->pSchema); sqlite3BeginWriteOperation(pParse, 0, iDb); iStatCur = pParse->nTab; pParse->nTab += 3; if( pOnlyIdx ){ openStatTable(pParse, iDb, iStatCur, pOnlyIdx->zName, "idx"); }else{ openStatTable(pParse, iDb, iStatCur, pTab->zName, "tbl"); } - analyzeOneTable(pParse, pTab, pOnlyIdx, iStatCur,pParse->nMem+1,pParse->nTab); + analyzeOneTable(pParse, iDb, pTab, + pOnlyIdx, iStatCur, pParse->nMem+1, pParse->nTab + ); loadAnalysis(pParse, iDb); } /* ** Generate code for the ANALYZE command. The parser calls this routine @@ -1423,13 +1421,13 @@ if( iDb>=0 ){ zDb = pName2->n ? db->aDb[iDb].zDbSName : 0; z = sqlite3NameFromToken(db, pTableName); if( z ){ if( (pIdx = sqlite3FindIndex(db, z, zDb))!=0 ){ - analyzeTable(pParse, pIdx->pTable, pIdx); + analyzeTable(pParse, iDb, pIdx->pTable, pIdx); }else if( (pTab = sqlite3LocateTable(pParse, 0, z, zDb))!=0 ){ - analyzeTable(pParse, pTab, 0); + analyzeTable(pParse, iDb, pTab, 0); } sqlite3DbFree(db, z); } } } Index: src/build.c ================================================================== --- src/build.c +++ src/build.c @@ -264,11 +264,13 @@ } pParse->nested++; memcpy(saveBuf, PARSE_TAIL(pParse), PARSE_TAIL_SZ); memset(PARSE_TAIL(pParse), 0, PARSE_TAIL_SZ); sqlite3RunParser(pParse, zSql, &zErrMsg); - sqlite3DbFree(db, zErrMsg); + if( zErrMsg ){ + sqlite3ErrorMsg(pParse, "%z", zErrMsg); + } sqlite3DbFree(db, zSql); memcpy(PARSE_TAIL(pParse), saveBuf, PARSE_TAIL_SZ); pParse->nested--; } @@ -3533,11 +3535,11 @@ /* When adding an index to the list of indices for a table, make ** sure all indices labeled OE_Replace come after all those labeled ** OE_Ignore. This is necessary for the correct constraint check ** processing (in sqlite3GenerateConstraintChecks()) as part of - ** UPDATE and INSERT statements. + ** UPDATE and INSERT statements. */ if( db->init.busy || pTblName==0 ){ if( onError!=OE_Replace || pTab->pIndex==0 || pTab->pIndex->onError==OE_Replace){ pIndex->pNext = pTab->pIndex; Index: src/delete.c ================================================================== --- src/delete.c +++ src/delete.c @@ -661,11 +661,11 @@ ** Except, this optimization is disabled if there are BEFORE triggers since ** the trigger body might have moved the cursor. */ void sqlite3GenerateRowDelete( Parse *pParse, /* Parsing context */ - int iDb, + int iDb, /* Database containing pTab */ Table *pTab, /* Table containing the row to be deleted */ Trigger *pTrigger, /* List of triggers to (potentially) fire */ int iDataCur, /* Cursor from which column data is extracted */ int iIdxCur, /* First index cursor */ int iPk, /* First memory cell containing the PRIMARY KEY */ Index: src/insert.c ================================================================== --- src/insert.c +++ src/insert.c @@ -1035,12 +1035,13 @@ }else #endif { int isReplace; /* Set to true if constraints may cause a replace */ int bUseSeek; /* True to use OPFLAG_SEEKRESULT */ - sqlite3GenerateConstraintChecks(pParse, pTab, aRegIdx, iDataCur, iIdxCur, - regIns, 0, ipkColumn>=0, onError, endOfLoop, &isReplace, 0, pUpsert + sqlite3GenerateConstraintChecks(pParse, iDb, pTab, aRegIdx, iDataCur, + iIdxCur, regIns, 0, ipkColumn>=0, onError, endOfLoop, &isReplace, + 0, pUpsert ); sqlite3FkCheck(pParse, pTab, 0, regIns, 0, 0); /* Set the OPFLAG_USESEEKRESULT flag if either (a) there are no REPLACE ** constraints or (b) there are no triggers and this table is not a @@ -1272,10 +1273,11 @@ ** is used. Or if pParse->onError==OE_Default then the onError value ** for the constraint is used. */ void sqlite3GenerateConstraintChecks( Parse *pParse, /* The parser context */ + int iDb, /* Databse that contains pTab */ Table *pTab, /* The table being inserted or updated */ int *aRegIdx, /* Use register aRegIdx[i] for index i. 0 for unused */ int iDataCur, /* Canonical data cursor (main table or PK index) */ int iIdxCur, /* First index cursor */ int regNewData, /* First register in a range holding values to insert */ @@ -1555,12 +1557,12 @@ if( db->flags&SQLITE_RecTriggers ){ pTrigger = sqlite3TriggersExist(pParse, pTab, TK_DELETE, 0, 0); } if( pTrigger || sqlite3FkRequired(pParse, pTab, 0, 0) ){ sqlite3MultiWrite(pParse); - sqlite3GenerateRowDelete(pParse, 0, pTab, pTrigger, iDataCur, iIdxCur, - regNewData, 1, 0, OE_Replace, 1, -1); + sqlite3GenerateRowDelete(pParse, iDb, pTab, pTrigger, iDataCur, + iIdxCur, regNewData, 1, 0, OE_Replace, 1, -1); }else{ #ifdef SQLITE_ENABLE_PREUPDATE_HOOK assert( HasRowid(pTab) ); /* This OP_Delete opcode fires the pre-update-hook only. It does ** not modify the b-tree. It is more efficient to let the coming @@ -1804,11 +1806,11 @@ pTrigger = sqlite3TriggersExist(pParse, pTab, TK_DELETE, 0, 0); } if( pTrigger || sqlite3FkRequired(pParse, pTab, 0, 0) ){ sqlite3MultiWrite(pParse); } - sqlite3GenerateRowDelete(pParse, 0, pTab, pTrigger, iDataCur, iIdxCur, + sqlite3GenerateRowDelete(pParse, iDb, pTab, pTrigger, iDataCur, iIdxCur, regR, nPkField, 0, OE_Replace, (pIdx==pPk ? ONEPASS_SINGLE : ONEPASS_OFF), iThisCur); seenReplace = 1; break; } Index: src/sqliteInt.h ================================================================== --- src/sqliteInt.h +++ src/sqliteInt.h @@ -4034,11 +4034,11 @@ Parse*,int,Table*,Trigger*,int,int,int,i16,u8,u8,u8,int); void sqlite3GenerateRowIndexDelete(Parse*, Table*, int, int, int*, int); int sqlite3GenerateIndexKey(Parse*, Index*, int, int, int, int*,Index*,int); void sqlite3ResolvePartIdxLabel(Parse*,int); int sqlite3ExprReferencesUpdatedColumn(Expr*,int*,int); -void sqlite3GenerateConstraintChecks(Parse*,Table*,int*,int,int,int,int, +void sqlite3GenerateConstraintChecks(Parse*,int,Table*,int*,int,int,int,int, u8,u8,int,int*,int*,Upsert*); #ifdef SQLITE_ENABLE_NULL_TRIM void sqlite3SetMakeRecordP5(Vdbe*,Table*); #else # define sqlite3SetMakeRecordP5(A,B) Index: src/update.c ================================================================== --- src/update.c +++ src/update.c @@ -708,13 +708,13 @@ if( !isView ){ int addr1 = 0; /* Address of jump instruction */ /* Do constraint checks. */ assert( regOldRowid>0 ); - sqlite3GenerateConstraintChecks(pParse, pTab, aRegIdx, iDataCur, iIdxCur, - regNewRowid, regOldRowid, chngKey, onError, labelContinue, &bReplace, - aXRef, 0); + sqlite3GenerateConstraintChecks(pParse, iDb, pTab, aRegIdx, iDataCur, + iIdxCur, regNewRowid, regOldRowid, chngKey, onError, labelContinue, + &bReplace, aXRef, 0); /* Do FK constraint checks. */ if( hasFK ){ sqlite3FkCheck(pParse, pTab, regOldRowid, 0, aXRef, chngKey); } Index: test/reuse1.test ================================================================== --- test/reuse1.test +++ test/reuse1.test @@ -119,10 +119,12 @@ 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; @@ -195,8 +197,119 @@ } 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 -reuse-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 -reuse-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 -reuse-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}} +} + + finish_test +