Index: Makefile.msc ================================================================== --- Makefile.msc +++ Makefile.msc @@ -712,11 +712,11 @@ LIBRESOBJS = !ENDIF # All of the source code files. # -SRC = \ +SRC1 = \ $(TOP)\src\alter.c \ $(TOP)\src\analyze.c \ $(TOP)\src\attach.c \ $(TOP)\src\auth.c \ $(TOP)\src\backup.c \ @@ -762,11 +762,12 @@ $(TOP)\src\os.h \ $(TOP)\src\os_common.h \ $(TOP)\src\os_setup.h \ $(TOP)\src\os_unix.c \ $(TOP)\src\os_win.c \ - $(TOP)\src\os_win.h \ + $(TOP)\src\os_win.h +SRC2 = \ $(TOP)\src\pager.c \ $(TOP)\src\pager.h \ $(TOP)\src\parse.y \ $(TOP)\src\pcache.c \ $(TOP)\src\pcache.h \ @@ -809,29 +810,28 @@ $(TOP)\src\where.c \ $(TOP)\src\whereInt.h # Source code for extensions # -SRC = $(SRC) \ +SRC3 = \ $(TOP)\ext\fts1\fts1.c \ $(TOP)\ext\fts1\fts1.h \ $(TOP)\ext\fts1\fts1_hash.c \ $(TOP)\ext\fts1\fts1_hash.h \ $(TOP)\ext\fts1\fts1_porter.c \ $(TOP)\ext\fts1\fts1_tokenizer.h \ - $(TOP)\ext\fts1\fts1_tokenizer1.c -SRC = $(SRC) \ + $(TOP)\ext\fts1\fts1_tokenizer1.c \ $(TOP)\ext\fts2\fts2.c \ $(TOP)\ext\fts2\fts2.h \ $(TOP)\ext\fts2\fts2_hash.c \ $(TOP)\ext\fts2\fts2_hash.h \ $(TOP)\ext\fts2\fts2_icu.c \ $(TOP)\ext\fts2\fts2_porter.c \ $(TOP)\ext\fts2\fts2_tokenizer.h \ $(TOP)\ext\fts2\fts2_tokenizer.c \ $(TOP)\ext\fts2\fts2_tokenizer1.c -SRC = $(SRC) \ +SRC4 = \ $(TOP)\ext\fts3\fts3.c \ $(TOP)\ext\fts3\fts3.h \ $(TOP)\ext\fts3\fts3Int.h \ $(TOP)\ext\fts3\fts3_aux.c \ $(TOP)\ext\fts3\fts3_expr.c \ @@ -844,29 +844,31 @@ $(TOP)\ext\fts3\fts3_tokenizer.c \ $(TOP)\ext\fts3\fts3_tokenizer1.c \ $(TOP)\ext\fts3\fts3_tokenize_vtab.c \ $(TOP)\ext\fts3\fts3_unicode.c \ $(TOP)\ext\fts3\fts3_unicode2.c \ - $(TOP)\ext\fts3\fts3_write.c -SRC = $(SRC) \ + $(TOP)\ext\fts3\fts3_write.c \ $(TOP)\ext\icu\sqliteicu.h \ - $(TOP)\ext\icu\icu.c -SRC = $(SRC) \ + $(TOP)\ext\icu\icu.c \ $(TOP)\ext\rtree\rtree.h \ $(TOP)\ext\rtree\rtree.c # Generated source code files # -SRC = $(SRC) \ +SRC5 = \ keywordhash.h \ opcodes.c \ opcodes.h \ parse.c \ parse.h \ sqlite3.h +# All source code files. +# +SRC = $(SRC1) $(SRC2) $(SRC3) $(SRC4) $(SRC5) + # Source code to the test files. # TESTSRC = \ $(TOP)\src\test1.c \ $(TOP)\src\test2.c \ @@ -1050,11 +1052,15 @@ # all that automatic generation. # .target_source: $(SRC) $(TOP)\tool\vdbe-compress.tcl -rmdir /S/Q tsrc -mkdir tsrc - for %i in ($(SRC)) do copy /Y %i tsrc + for %i in ($(SRC1)) do copy /Y %i tsrc + for %i in ($(SRC2)) do copy /Y %i tsrc + for %i in ($(SRC3)) do copy /Y %i tsrc + for %i in ($(SRC4)) do copy /Y %i tsrc + for %i in ($(SRC5)) do copy /Y %i tsrc del /Q tsrc\sqlite.h.in tsrc\parse.y $(TCLSH_CMD) $(TOP)\tool\vdbe-compress.tcl $(OPTS) < tsrc\vdbe.c > vdbe.new move vdbe.new tsrc\vdbe.c echo > .target_source Index: src/delete.c ================================================================== --- src/delete.c +++ src/delete.c @@ -245,11 +245,10 @@ i16 nKey; /* Number of memory cells in the row key */ int iEphCur = 0; /* Ephemeral table holding all primary key values */ int iRowSet = 0; /* Register for rowset of rows to delete */ int addrBypass = 0; /* Address of jump over the delete logic */ int addrLoop = 0; /* Top of the delete loop */ - int addrDelete = 0; /* Jump directly to the delete logic */ int addrEphOpen = 0; /* Instruction to open the Ephemeral table */ #ifndef SQLITE_OMIT_TRIGGER int isView; /* True if attempting to delete from a view */ Trigger *pTrigger; /* List of table triggers, if required */ @@ -435,11 +434,10 @@ memset(aToOpen, 1, nIdx+1); aToOpen[nIdx+1] = 0; if( aiCurOnePass[0]>=0 ) aToOpen[aiCurOnePass[0]-iTabCur] = 0; if( aiCurOnePass[1]>=0 ) aToOpen[aiCurOnePass[1]-iTabCur] = 0; if( addrEphOpen ) sqlite3VdbeChangeToNoop(v, addrEphOpen); - addrDelete = sqlite3VdbeAddOp0(v, OP_Goto); /* Jump to DELETE logic */ }else if( pPk ){ /* Construct a composite key for the row to be deleted and remember it */ iKey = ++pParse->nMem; nKey = 0; /* Zero tells OP_Found to use a composite key */ sqlite3VdbeAddOp4(v, OP_MakeRecord, iPk, nPk, iKey, @@ -449,17 +447,15 @@ /* Get the rowid of the row to be deleted and remember it in the RowSet */ nKey = 1; /* OP_Seek always uses a single rowid */ sqlite3VdbeAddOp2(v, OP_RowSetAdd, iRowSet, iKey); } - /* End of the WHERE loop */ - sqlite3WhereEnd(pWInfo); + /* End of the WHERE loop. */ if( okOnePass ){ - /* Bypass the delete logic below if the WHERE loop found zero rows */ addrBypass = sqlite3VdbeMakeLabel(v); - sqlite3VdbeAddOp2(v, OP_Goto, 0, addrBypass); - sqlite3VdbeJumpHere(v, addrDelete); + }else{ + sqlite3WhereEnd(pWInfo); } /* Unless this is a view, open cursors for the table we are ** deleting from and all its indices. If this is a view, then the ** only effect this statement has is to fire the INSTEAD OF @@ -511,10 +507,11 @@ iKey, nKey, count, OE_Default, okOnePass); } /* End of the loop over all rowids/primary-keys. */ if( okOnePass ){ + sqlite3WhereEnd(pWInfo); sqlite3VdbeResolveLabel(v, addrBypass); }else if( pPk ){ sqlite3VdbeAddOp2(v, OP_Next, iEphCur, addrLoop+1); VdbeCoverage(v); sqlite3VdbeJumpHere(v, addrLoop); }else{ @@ -672,13 +669,15 @@ /* Delete the index and table entries. Skip this step if pTab is really ** a view (in which case the only effect of the DELETE statement is to ** fire the INSTEAD OF triggers). */ if( pTab->pSelect==0 ){ sqlite3GenerateRowIndexDelete(pParse, pTab, iDataCur, iIdxCur, 0); - sqlite3VdbeAddOp2(v, OP_Delete, iDataCur, (count?OPFLAG_NCHANGE:0)); - if( count ){ - sqlite3VdbeChangeP4(v, -1, pTab->zName, P4_TRANSIENT); + if( !WRITE_RESTRICT(pParse->db, pTab->tnum) ){ + sqlite3VdbeAddOp2(v, OP_Delete, iDataCur, (count?OPFLAG_NCHANGE:0)); + if( count ){ + sqlite3VdbeChangeP4(v, -1, pTab->zName, P4_TRANSIENT); + } } } /* Do any ON CASCADE, SET NULL or SET DEFAULT operations required to ** handle rows (possibly in other tables) that refer via a foreign key @@ -734,10 +733,11 @@ pPk = HasRowid(pTab) ? 0 : sqlite3PrimaryKeyIndex(pTab); for(i=0, pIdx=pTab->pIndex; pIdx; i++, pIdx=pIdx->pNext){ assert( iIdxCur+i!=iDataCur || pPk==pIdx ); if( aRegIdx!=0 && aRegIdx[i]==0 ) continue; if( pIdx==pPk ) continue; + if( WRITE_RESTRICT(pParse->db, pIdx->tnum) ) continue; VdbeModuleComment((v, "GenRowIdxDel for %s", pIdx->zName)); r1 = sqlite3GenerateIndexKey(pParse, pIdx, iDataCur, 0, 1, &iPartIdxLabel, pPrior, r1); sqlite3VdbeAddOp3(v, OP_IdxDelete, iIdxCur+i, r1, pIdx->uniqNotNull ? pIdx->nKeyCol : pIdx->nColumn); Index: src/insert.c ================================================================== --- src/insert.c +++ src/insert.c @@ -1364,10 +1364,11 @@ int regR; /* Range of registers holding conflicting PK */ int iThisCur; /* Cursor for this UNIQUE index */ int addrUniqueOk; /* Jump here if the UNIQUE constraint is satisfied */ if( aRegIdx[ix]==0 ) continue; /* Skip indices that do not change */ + if( WRITE_RESTRICT(db, pIdx->tnum) ) continue; if( bAffinityDone==0 ){ sqlite3TableAffinity(v, pTab, regNewData+1); bAffinityDone = 1; } iThisCur = iIdxCur+ix; @@ -1543,21 +1544,24 @@ int appendBias, /* True if this is likely to be an append */ int useSeekResult /* True to set the USESEEKRESULT flag on OP_[Idx]Insert */ ){ Vdbe *v; /* Prepared statements under construction */ Index *pIdx; /* An index being inserted or updated */ + sqlite3 *db; u8 pik_flags; /* flag values passed to the btree insert */ int regData; /* Content registers (after the rowid) */ int regRec; /* Register holding assembled record for the table */ int i; /* Loop counter */ u8 bAffinityDone = 0; /* True if OP_Affinity has been run already */ v = sqlite3GetVdbe(pParse); assert( v!=0 ); assert( pTab->pSelect==0 ); /* This table is not a VIEW */ + db = pParse->db; for(i=0, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext, i++){ if( aRegIdx[i]==0 ) continue; + if( WRITE_RESTRICT(db, pIdx->tnum) ) continue; bAffinityDone = 1; if( pIdx->pPartIdxWhere ){ sqlite3VdbeAddOp2(v, OP_IsNull, aRegIdx[i], sqlite3VdbeCurrentAddr(v)+2); VdbeCoverage(v); } @@ -1569,10 +1573,11 @@ pik_flags |= OPFLAG_NCHANGE; } if( pik_flags ) sqlite3VdbeChangeP5(v, pik_flags); } if( !HasRowid(pTab) ) return; + if( WRITE_RESTRICT(db, pTab->tnum) ) return; regData = regNewData + 1; regRec = sqlite3GetTempReg(pParse); sqlite3VdbeAddOp3(v, OP_MakeRecord, regData, pTab->nCol, regRec); if( !bAffinityDone ) sqlite3TableAffinity(v, pTab, 0); sqlite3ExprCacheAffinityChange(pParse, regData, pTab->nCol); Index: src/main.c ================================================================== --- src/main.c +++ src/main.c @@ -722,10 +722,15 @@ void *pBuf = va_arg(ap, void*); /* IMP: R-26835-10964 */ int sz = va_arg(ap, int); /* IMP: R-47871-25994 */ int cnt = va_arg(ap, int); /* IMP: R-04460-53386 */ rc = setupLookaside(db, pBuf, sz, cnt); break; + } + case SQLITE_DBCONFIG_WRITABLE_BTREE: { + db->onlyWritableBtree = va_arg(ap,int); + rc = SQLITE_OK; + break; } default: { static const struct { int op; /* The opcode */ u32 mask; /* Mask of the bit in sqlite3.flags to set/clear */ Index: src/shell.c ================================================================== --- src/shell.c +++ src/shell.c @@ -2565,10 +2565,56 @@ fprintf(stderr,"Error: %s\n", zErrMsg); sqlite3_free(zErrMsg); rc = 1; } }else + + if( c=='d' && n>1 && strncmp(azArg[0], "dbconfig", n)==0 ){ + int nHit = 0, x; + open_db(p, 0); + if( nArg>=2 ){ + n = (int)strlen(azArg[1]); + if( strncmp(azArg[1], "writable_btree",n)==0 ){ + if( nArg!=3 ){ + fprintf(stderr, "Usage: .dbconfig writable_btree N\n"); + rc = 1; + }else{ + sqlite3_db_config(p->db, SQLITE_DBCONFIG_WRITABLE_BTREE, + integerValue(azArg[2])); + } + nHit = 1; + }else + if( strncmp(azArg[1], "enable_fkey",n)==0 ){ + if( nArg!=3 ){ + fprintf(stderr, "Usage: .dbconfig enable_fkey (1|0|-1)\n"); + rc = 1; + }else{ + sqlite3_db_config(p->db, SQLITE_DBCONFIG_ENABLE_FKEY, + integerValue(azArg[2]), &x); + printf("result: %d\n", x); + } + nHit = 1; + }else + if( strncmp(azArg[1], "enable_trigger",n)==0 ){ + if( nArg!=3 ){ + fprintf(stderr, "Usage: .dbconfig enable_trigger (1|0|-1)\n"); + rc = 1; + }else{ + sqlite3_db_config(p->db, SQLITE_DBCONFIG_ENABLE_TRIGGER, + integerValue(azArg[2]), &x); + printf("result: %d\n", x); + } + nHit = 1; + } + } + if( nHit==0 ){ + fprintf(stderr, "Usage: .dbconfig COMMAND ARGS...\n"); + fprintf(stderr, + "COMMAND is one of: writable_btree enable_fkey enable_trigger\n"); + rc = 1; + } + }else if( c=='d' && strncmp(azArg[0], "dump", n)==0 ){ open_db(p, 0); /* When playing back a "dump", the content might appear in an order ** which causes immediate foreign key constraints to be violated. Index: src/sqlite.h.in ================================================================== --- src/sqlite.h.in +++ src/sqlite.h.in @@ -1838,15 +1838,24 @@ ** The second parameter is a pointer to an integer into which ** is written 0 or 1 to indicate whether triggers are disabled or enabled ** following this call. The second parameter may be a NULL pointer, in ** which case the trigger setting is not reported back. ** +**
SQLITE_DBCONFIG_WRITABLE_BTREE
+**
^This option is used to disable INSERT and DELETE operations +** against all attached b-trees, except for b-trees that have a +** particular root page. +** There must be one additional integer argument which is the root page +** that is allowed to be written. If the argument is zero, then +** writing is allowed to all b-trees, as is normally the case. +** ** */ #define SQLITE_DBCONFIG_LOOKASIDE 1001 /* void* int int */ #define SQLITE_DBCONFIG_ENABLE_FKEY 1002 /* int int* */ #define SQLITE_DBCONFIG_ENABLE_TRIGGER 1003 /* int int* */ +#define SQLITE_DBCONFIG_WRITABLE_BTREE 1004 /* int */ /* ** CAPI3REF: Enable Or Disable Extended Result Codes ** Index: src/sqliteInt.h ================================================================== --- src/sqliteInt.h +++ src/sqliteInt.h @@ -1075,10 +1075,11 @@ signed char nextAutovac; /* Autovac setting after VACUUM if >=0 */ u8 suppressErr; /* Do not issue error messages if true */ u8 vtabOnConflict; /* Value to return for s3_vtab_on_conflict() */ u8 isTransactionSavepoint; /* True if the outermost savepoint is a TS */ int nextPagesize; /* Pagesize after VACUUM if >0 */ + int onlyWritableBtree; /* Do not write to any other b-tree */ u32 magic; /* Magic number for detect library misuse */ int nChange; /* Value returned by sqlite3_changes() */ int nTotalChange; /* Value returned by sqlite3_total_changes() */ int aLimit[SQLITE_N_LIMIT]; /* Limits */ int nMaxSorterMmap; /* Maximum size of regions mapped by sorter */ @@ -1164,10 +1165,17 @@ #ifdef SQLITE_USER_AUTHENTICATION sqlite3_userauth auth; /* User authentication information */ #endif }; +/* +** This macro returns true if the only_writable_btree pragma is turned +** on and is set to a btree root node other than N. +*/ +#define WRITE_RESTRICT(db,N) ((db)->onlyWritableBtree>0 && \ + (db)->onlyWritableBtree!=(N)) + /* ** A macro to discover the encoding of a database. */ #define SCHEMA_ENC(db) ((db)->aDb[0].pSchema->enc) #define ENC(db) ((db)->enc) Index: src/vdbe.c ================================================================== --- src/vdbe.c +++ src/vdbe.c @@ -4189,11 +4189,12 @@ assert( pOp->p1>=0 && pOp->p1nCursor ); pC = p->apCsr[pOp->p1]; assert( pC!=0 ); assert( pC->pCursor!=0 ); /* Only valid for real tables, no pseudotables */ - assert( pC->deferredMoveto==0 ); + rc = sqlite3VdbeCursorMoveto(pC); + if( rc ) goto abort_due_to_error; #ifdef SQLITE_DEBUG /* The seek operation that positioned the cursor prior to OP_Delete will ** have also set the pC->movetoTarget field to the rowid of the row that ** is being deleted */