Compare commits

...

8 Commits

Author SHA1 Message Date
dan 1ec3727504 Update some stale comments in delete.c. No changes to code.
FossilOrigin-Name: f59a33260cc853bb35f4259b05fd05bbbeb13945
2015-12-12 19:23:41 +00:00
dan 2e001ce59a Add further tests for the changes on this branch. Also fix a memory-leak that could follow a malloc failure.
FossilOrigin-Name: 21526012c274e709d672dbafb9e16158c0749341
2015-12-12 17:31:40 +00:00
drh 3b95f1b475 Merge recent changes from trunk. Also remove unused variables to permit
compiling with -Werror.

FossilOrigin-Name: 57b700baa690f73894cd53b8e87839760fe4019b
2015-12-11 13:59:45 +00:00
dan 4bde226f7f Add extra test cases to delete4.test.
FossilOrigin-Name: 571b64b923c82060f34bf6baa21d72d2a9797e6f
2015-12-09 17:45:58 +00:00
dan 352133bf21 Fix a problem with the DROP TABLE command on this branch.
FossilOrigin-Name: c80bbf14b365d2131210a849f26a69a5ea15101d
2015-12-09 16:08:22 +00:00
dan 54815111e2 Add simple tests and bugfixes for DELETE statements that qualify for the OR-optimization.
FossilOrigin-Name: 10ca7357b27d0657127d553738be3ae6d715b74a
2015-12-09 10:06:24 +00:00
dan f0a6803d4f Merge latest trunk with this branch.
FossilOrigin-Name: dc236f11181e235ddfe58a7943a462686679bf94
2015-12-09 08:13:55 +00:00
dan 4445c0fa21 Experimental optimization for DELETE statements with WHERE clauses that qualify for the OR-optimization.
FossilOrigin-Name: d52376df53f8d17c6f6e7ca8cc8da2a24f257a7f
2015-12-08 19:50:52 +00:00
9 changed files with 614 additions and 216 deletions
+13 -12
View File
@@ -1,5 +1,5 @@
C Simplified\salignment\sconstraints\sin\sthe\smemory\sreuse\slogic\sof\nsqlite3VdbeMakeReady().
D 2015-12-11T13:51:02.921
C Update\ssome\sstale\scomments\sin\sdelete.c.\sNo\schanges\sto\scode.
D 2015-12-12T19:23:41.225
F Makefile.in 28bcd6149e050dff35d4dcfd97e890cd387a499d
F Makefile.linux-gcc 7bc79876b875010e8c8f9502eb935ca92aa3c434
F Makefile.msc e8fdca1cb89a1b58b5f4d3a130ea9a3d28cb314d
@@ -291,7 +291,7 @@ F src/complete.c addcd8160b081131005d5bc2d34adf20c1c5c92f
F src/ctime.c 60e135af364d777a9ab41c97e5e89cd224da6198
F src/date.c fb1c99172017dcc8e237339132c91a21a0788584
F src/dbstat.c ffd63fc8ba7541476ced189b95e95d7f2bc63f78
F src/delete.c 00af9f08a15ddc5cba5962d3d3e5bf2d67b2e7da
F src/delete.c ddc1a52614b0a30d154d5284495eaf819b56b4d7
F src/expr.c ccb93d7b7e1ac5d187c9b153bae145933f93ee5c
F src/fault.c 160a0c015b6c2629d3899ed2daf63d75754a32bb
F src/fkey.c 31900763094a3736a5fc887469202eb579fef2d0
@@ -344,7 +344,7 @@ F src/shell.c abbc74ea43dbf2f306ea18282d666683fb5efab2
F src/sqlite.h.in 7d87d71b9a4689c51fa092f48f16590ff71558e3
F src/sqlite3.rc 992c9f5fb8285ae285d6be28240a7e8d3a7f2bad
F src/sqlite3ext.h dfbe62ffd95b99afe2140d8c35b180d11924072d
F src/sqliteInt.h beb4a63b94428f52a3d7c7af2ba8bdc7d4682a03
F src/sqliteInt.h dd30e0e695e56795ce6cd0694499bb64af48cc16
F src/sqliteLimit.h 216557999cb45f2e3578ed53ebefe228d779cb46
F src/status.c 70912d7be68e9e2dbc4010c93d344af61d4c59ba
F src/table.c 51b46b2a62d1b3a959633d593b89bab5e2c9155e
@@ -418,7 +418,7 @@ F src/vxworks.h c18586c8edc1bddbc15c004fa16aeb1e1342b4fb
F src/wal.c cb709aa42fc11b1ea92a00c8c7e6214de0995fa3
F src/wal.h 907943dfdef10b583e81906679a347e0ec6f1b1b
F src/walker.c 2e14d17f592d176b6dc879c33fbdec4fbccaa2ba
F src/where.c b18edbb9e5afabb77f4f27550c471c5c824e0fe7
F src/where.c b7462d37a9819071d252bf167d5e951a2a9c5c1e
F src/whereInt.h e20801d89e34de1912bb6a3babb30c390da27add
F src/wherecode.c dfbfe198e418b01f208b489e088edd230c91a4e7
F src/whereexpr.c eebba8340c90de73b3d3bbe8c43b84559b8e6e2c
@@ -576,7 +576,8 @@ F test/default.test 0cb49b1c315a0d81c81d775e407f66906a2a604d
F test/delete.test e1bcdf8926234e27aac24b346ad83d3329ec8b6f
F test/delete2.test 3a03f2cca1f9a67ec469915cb8babd6485db43fa
F test/delete3.test 555e84a00a99230b7d049d477a324a631126a6ab
F test/delete4.test 3ac0b8d23689ba764c2e8b78c1b56b8f1b942fa2
F test/delete4.test dbd922693b52332e5e1f1b748513f11d28414bbc
F test/delete_fault.test bb4a1ab823f77df32db237ad4e96562d1b06795a
F test/descidx1.test 6d03b44c8538fe0eb4924e19fba10cdd8f3c9240
F test/descidx2.test 9f1a0c83fd57f8667c82310ca21b30a350888b5d
F test/descidx3.test 09ddbe3f5295f482d2f8b687cf6db8bad7acd9a2
@@ -634,7 +635,7 @@ F test/fkey6.test abb59f866c1b44926fd02d1fdd217d831fe04f48
F test/fkey7.test 72e915890ee4a005daaf3002cb208e8fe973ac13
F test/fkey8.test 8f08203458321e6c19a263829de4cfc936274ab0
F test/fkey_malloc.test 594a7ea1fbab553c036c70813cd8bd9407d63749
F test/fordelete.test ba12ec1d27cc34a4c23db4446029126d773f3849
F test/fordelete.test 2b8b9a2d8f430ceacb314b06b020116d3d136931
F test/format4.test 1f0cac8ff3895e9359ed87e41aaabee982a812eb
F test/fts-9fd058691.test 78b887e30ae6816df0e1fed6259de4b5a64ad33c
F test/fts1a.test 46090311f85da51bb33bd5ce84f7948359c6d8d7
@@ -1058,7 +1059,7 @@ F test/sync.test a34cd43e98b7fb84eabbf38f7ed8f7349b3f3d85
F test/syscall.test 2aa9e111b79fb385681ff8940124def6f8faab87
F test/sysfault.test c9f2b0d8d677558f74de750c75e12a5454719d04
F test/tabfunc01.test cc33684f9480fcf1fd5ce287ac28d22971cad1cc
F test/table.test b708f3e5fa2542fa51dfab21fc07b36ea445cb2f
F test/table.test 9ed9aed4a81a0369531b697186ce3a611e57295d
F test/tableapi.test 2674633fa95d80da917571ebdd759a14d9819126
F test/tableopts.test dba698ba97251017b7c80d738c198d39ab747930
F test/tclsqlite.test 7fb866443c7deceed22b63948ccd6f76b52ad054
@@ -1410,7 +1411,7 @@ F tool/vdbe_profile.tcl 246d0da094856d72d2c12efec03250d71639d19f
F tool/warnings-clang.sh f6aa929dc20ef1f856af04a730772f59283631d4
F tool/warnings.sh 48bd54594752d5be3337f12c72f28d2080cb630b
F tool/win/sqlite.vsix deb315d026cc8400325c5863eef847784a219a2f
P 9b124a5a310aec0145495c9980bc0c1308fcca02
R 8b7bf5c5d9b6c5d6524021fe34935458
U drh
Z 8b682652b257be08de396beec767150c
P 21526012c274e709d672dbafb9e16158c0749341
R 72360abeca2d214a51c8718f62041bc1
U dan
Z f9b468b08638e33ce475509a59673042
+1 -1
View File
@@ -1 +1 @@
e998513e442ce1206b12dc28bdc996d7b5f9f94d
f59a33260cc853bb35f4259b05fd05bbbeb13945
+318 -193
View File
@@ -208,6 +208,318 @@ limit_where_cleanup_2:
#endif /* defined(SQLITE_ENABLE_UPDATE_DELETE_LIMIT) */
/* && !defined(SQLITE_OMIT_SUBQUERY) */
/*
** sqlite3WalkExpr() callback used by deleteSetColUsed().
*/
static int deleteSetColUsedExpr(Walker *pWalker, Expr *pExpr){
int iCol;
if( pExpr->op==TK_COLUMN && (iCol = pExpr->iColumn)>=0 ){
struct SrcList_item *pItem = &pWalker->u.pSrcList->a[0];
if( pItem->iCursor==pExpr->iTable ){
pItem->colUsed |= ((Bitmask)1)<<(iCol>=BMS ? BMS-1 : iCol);
}
}
return WRC_Continue;
}
/*
** No-op sqlite3WalkExpr() callback used by deleteSetColUsed().
*/
static void deleteSetColUsedSelect(Walker *pWalker, Select *pSelect){
UNUSED_PARAMETER2(pWalker, pSelect);
}
/*
** Argument pSrc is guaranteed to contain a single table. All column
** references within expression pExpr have already been successfully
** resolved to this table. This function sets the pSrc->a[0].colUsed
** field to reflect the set of table columns used by pExpr (and no
** others).
*/
static void deleteSetColUsed(Parse *pParse, SrcList *pSrc, Expr *pExpr){
Walker w;
assert( pSrc->nSrc==1 );
memset(&w, 0, sizeof(w));
w.pParse = pParse;
w.u.pSrcList = pSrc;
w.xExprCallback = deleteSetColUsedExpr;
w.xSelectCallback2 = deleteSetColUsedSelect;
pSrc->a[0].colUsed = 0;
sqlite3WalkExpr(&w, pExpr);
}
/*
** Generate code for a DELETE statement. This function is called by
** sqlite3DeleteFrom() after all table names and column references have
** been resolved. SQLITE_OK is returned if successful, or an SQLite
** error code otherwise.
**
** DELETE FROM table_wxyz WHERE a<5 AND b NOT NULL;
** \________/ \________________/
** pTabList pWhere
*/
int deleteFrom(
Parse *pParse, /* The parser context */
SrcList *pTabList, /* Table from which we should delete things */
Expr *pWhere, /* The WHERE clause. May be null */
int isView, /* True if attempting to delete from a view */
Trigger *pTrigger, /* List of table triggers, if required */
int bComplex, /* True if there are either triggers or FKs */
int memCnt, /* Cell used for count-changes */
int nIdx /* Number of indexes on table */
){
Vdbe *v = pParse->pVdbe; /* VM being coded */
sqlite3 *db = pParse->db; /* Database handle */
int iEphCur = 0; /* Ephemeral table holding all primary key values */
int iRowSet = 0; /* Register for rowset of rows to delete */
int iKey; /* Memory cell holding key of row to be deleted */
i16 nKey; /* Number of memory cells in the row key */
int addrBypass = 0; /* Address of jump over the delete logic */
int addrLoop = 0; /* Top of the delete loop */
int addrEphOpen = 0; /* Instruction to open the Ephemeral table */
WhereInfo *pWInfo; /* Information about the WHERE clause */
int eOnePass; /* ONEPASS_OFF or _SINGLE or _MULTI */
int aiCurOnePass[2]; /* The write cursors opened by WHERE_ONEPASS */
u8 *aToOpen = 0; /* Open cursor iTabCur+j if aToOpen[j] is true */
Index *pPk = 0; /* The PRIMARY KEY index on the table */
int iPk = 0; /* First of nPk reg holding PRIMARY KEY value */
i16 nPk = 1; /* Number of columns in the PRIMARY KEY */
int iTabCur; /* Cursor number for the table */
int iDataCur = 0; /* VDBE cursor for the canonical data source */
int iIdxCur = 0; /* Cursor number of the first index */
Table *pTab = pTabList->a[0].pTab;
u16 wcf = WHERE_ONEPASS_DESIRED|WHERE_DUPLICATES_OK;
iTabCur = pTabList->a[0].iCursor;
if( !HasRowid(pTab) ){
pPk = sqlite3PrimaryKeyIndex(pTab);
assert( pPk!=0 );
nPk = pPk->nKeyCol;
iPk = pParse->nMem+1;
pParse->nMem += nPk;
}
if( isView ){
iDataCur = iIdxCur = iTabCur;
}
/* If there are triggers to fire or foreign keys to enforce, or the
** table is a virtual table, the two pass strategy may be required. In
** this case allocate either the rowset register (IPK tables) or an
** ephemeral table (WITHOUT ROWID tables) in which the list of rows
** to delete will be accumulated.
**
** Otherwise, if there are no triggers or foreign keys and this is not
** a virtual table, set the WHERE_ONEPASS_MULTIROW flag on the mask of
** flags that will be passed to sqlite3WhereBegin(). */
if( bComplex || IsVirtual(pTab) ){
if( HasRowid(pTab) ){
/* For a rowid table, initialize the RowSet to an empty set */
pPk = 0;
nPk = 1;
iRowSet = ++pParse->nMem;
sqlite3VdbeAddOp2(v, OP_Null, 0, iRowSet);
}else{
/* For a WITHOUT ROWID table, create an ephemeral table used to
** hold all primary keys for rows to be deleted. */
iEphCur = pParse->nTab++;
addrEphOpen = sqlite3VdbeAddOp2(v, OP_OpenEphemeral, iEphCur, nPk);
sqlite3VdbeSetP4KeyInfo(pParse, pPk);
}
}else{
wcf |= WHERE_ONEPASS_MULTIROW;
}
/* Construct a query to find the rowid or primary key for every row
** to be deleted, based on the WHERE clause. Set variable eOnePass
** to indicate the strategy used to implement this delete:
**
** ONEPASS_OFF: Two-pass approach - use a FIFO for rowids/PK values.
** ONEPASS_SINGLE: One-pass approach - at most one row deleted.
** ONEPASS_MULTI: One-pass approach - any number of rows may be deleted.
** ONEPASS_SPLIT_DELETE: See special case below.
*/
pWInfo = sqlite3WhereBegin(pParse, pTabList, pWhere, 0, 0, wcf, iTabCur+1);
if( pWInfo==0 ) return SQLITE_NOMEM;
eOnePass = sqlite3WhereOkOnePass(pWInfo, aiCurOnePass);
assert( IsVirtual(pTab)==0 || eOnePass!=ONEPASS_MULTI );
assert( IsVirtual(pTab) || bComplex || eOnePass!=ONEPASS_OFF );
/* If sqlite3WhereOkOnePass() returns SPLIT_DELETE, then the WHERE clause
** consists of one or more indexable terms connected by OR operators. In
** this case it is more efficient to run a separate delete program for
** each OR'd term. */
if( eOnePass==ONEPASS_SPLIT_DELETE ){
int rc = SQLITE_OK; /* Return code */
Expr *pExpr = 0; /* OR'd expression */
int i; /* Counter variable */
assert( !IsVirtual(pTab) && bComplex==0 );
/* This loop iterates once for each OR-connected term in the WHERE clause */
for(i=0; rc==SQLITE_OK && (pExpr=sqlite3WhereSplitExpr(pWInfo, i)); i++){
if( db->mallocFailed==0 ){
deleteSetColUsed(pParse, pTabList, pExpr);
rc = deleteFrom(pParse, pTabList, pExpr, 0, 0, 0, memCnt, nIdx);
}
sqlite3ExprDelete(db, pExpr);
}
sqlite3WhereInfoFree(db, pWInfo);
return rc;
}
/* Keep track of the number of rows to be deleted */
if( db->flags & SQLITE_CountRows ){
sqlite3VdbeAddOp2(v, OP_AddImm, memCnt, 1);
}
/* Extract the rowid or primary key for the current row */
if( pPk ){
int i;
for(i=0; i<nPk; i++){
assert( pPk->aiColumn[i]>=0 );
sqlite3ExprCodeGetColumnOfTable(v, pTab, iTabCur,
pPk->aiColumn[i], iPk+i);
}
iKey = iPk;
}else{
iKey = pParse->nMem + 1;
iKey = sqlite3ExprCodeGetColumn(pParse, pTab, -1, iTabCur, iKey, 0);
if( iKey>pParse->nMem ) pParse->nMem = iKey;
}
if( eOnePass!=ONEPASS_OFF ){
/* For a one-pass strategy, no need to store the rowid/primary-key in
** a RowSet or temporary table. It is read directly from its register(s)
** by the delete code below. */
nKey = nPk; /* OP_Found will use an unpacked key */
aToOpen = sqlite3DbMallocRaw(db, nIdx+2);
if( aToOpen==0 ){
sqlite3WhereEnd(pWInfo);
return SQLITE_NOMEM;
}
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);
}else{
if( pPk ){
/* Add the PK key for this row to the temporary table */
iKey = ++pParse->nMem;
nKey = 0; /* Zero tells OP_Found to use a composite key */
sqlite3VdbeAddOp4(v, OP_MakeRecord, iPk, nPk, iKey,
sqlite3IndexAffinityStr(pParse->db, pPk), nPk);
sqlite3VdbeAddOp2(v, OP_IdxInsert, iEphCur, iKey);
}else{
/* Add the rowid of the row to be deleted to the RowSet */
nKey = 1; /* OP_Seek always uses a single rowid */
sqlite3VdbeAddOp2(v, OP_RowSetAdd, iRowSet, iKey);
}
}
/* If not using a one-pass strategy, this is the end of the first loop.
** A second loop, to iterate through the contents of the RowSet or
** temporary table populated above, is opened below. */
if( eOnePass!=ONEPASS_OFF ){
addrBypass = sqlite3VdbeMakeLabel(v);
}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
** triggers. */
if( !isView ){
int iAddrOnce = 0;
u8 p5 = (eOnePass==ONEPASS_OFF ? 0 : OPFLAG_FORDELETE);
if( eOnePass==ONEPASS_MULTI ){
iAddrOnce = sqlite3CodeOnce(pParse); VdbeCoverage(v);
}
testcase( IsVirtual(pTab) );
sqlite3OpenTableAndIndices(pParse, pTab, OP_OpenWrite, p5, iTabCur,
aToOpen, &iDataCur, &iIdxCur);
assert( pPk || IsVirtual(pTab) || iDataCur==iTabCur );
assert( pPk || IsVirtual(pTab) || iIdxCur==iDataCur+1 );
if( eOnePass==ONEPASS_MULTI ) sqlite3VdbeJumpHere(v, iAddrOnce);
}
/* If using a one-pass strategy, seek the data-cursor to the entry
** in the main table b-tree if where.c has not already done so.
**
** If using the two-pass strategy, start a loop over the contents
** of the RowSet or temporary table populated by the first loop. */
if( eOnePass!=ONEPASS_OFF ){
assert( nKey==nPk ); /* OP_Found will use an unpacked key */
if( !IsVirtual(pTab) && aToOpen[iDataCur-iTabCur] ){
assert( pPk!=0 || pTab->pSelect!=0 );
sqlite3VdbeAddOp4Int(v, OP_NotFound, iDataCur, addrBypass, iKey, nKey);
VdbeCoverage(v);
}
}else if( pPk ){
addrLoop = sqlite3VdbeAddOp1(v, OP_Rewind, iEphCur); VdbeCoverage(v);
sqlite3VdbeAddOp2(v, OP_RowKey, iEphCur, iKey);
assert( nKey==0 ); /* OP_Found will use a composite key */
}else{
addrLoop = sqlite3VdbeAddOp3(v, OP_RowSetRead, iRowSet, 0, iKey);
VdbeCoverage(v);
assert( nKey==1 );
}
/* Delete the row */
#ifndef SQLITE_OMIT_VIRTUALTABLE
if( IsVirtual(pTab) ){
const char *pVTab = (const char *)sqlite3GetVTable(db, pTab);
sqlite3VtabMakeWritable(pParse, pTab);
sqlite3VdbeAddOp4(v, OP_VUpdate, 0, 1, iKey, pVTab, P4_VTAB);
sqlite3VdbeChangeP5(v, OE_Abort);
assert( eOnePass==ONEPASS_OFF || eOnePass==ONEPASS_SINGLE );
sqlite3MayAbort(pParse);
if( eOnePass==ONEPASS_SINGLE && sqlite3IsToplevel(pParse) ){
pParse->isMultiWrite = 0;
}
}else
#endif
{
int count = (pParse->nested==0); /* True to count changes */
int iIdxNoSeek = -1;
if( bComplex==0 && aiCurOnePass[1]!=iDataCur ){
iIdxNoSeek = aiCurOnePass[1];
}
sqlite3GenerateRowDelete(pParse, pTab, pTrigger, iDataCur, iIdxCur,
iKey, nKey, count, OE_Default, eOnePass, iIdxNoSeek);
}
/* For a one-pass strategy, this is the end of the single loop. For a
** two-pass strategy, the end of the loop over the rowids/primary-keys
** stored in the RowSet/temporary table. */
if( eOnePass!=ONEPASS_OFF ){
sqlite3VdbeResolveLabel(v, addrBypass);
sqlite3WhereEnd(pWInfo);
}else if( pPk ){
sqlite3VdbeAddOp2(v, OP_Next, iEphCur, addrLoop+1); VdbeCoverage(v);
sqlite3VdbeJumpHere(v, addrLoop);
}else{
sqlite3VdbeGoto(v, addrLoop);
sqlite3VdbeJumpHere(v, addrLoop);
}
/* Close the cursors open on the table and its indexes. */
if( !isView && !IsVirtual(pTab) ){
Index *pIdx;
int i;
if( !pPk ) sqlite3VdbeAddOp1(v, OP_Close, iDataCur);
for(i=0, pIdx=pTab->pIndex; pIdx; i++, pIdx=pIdx->pNext){
sqlite3VdbeAddOp1(v, OP_Close, iIdxCur + i);
}
}
sqlite3DbFree(db, aToOpen);
return SQLITE_OK;
}
/*
** Generate code for a DELETE FROM statement.
**
@@ -220,35 +532,18 @@ void sqlite3DeleteFrom(
SrcList *pTabList, /* The table from which we should delete things */
Expr *pWhere /* The WHERE clause. May be null */
){
sqlite3 *db;
Vdbe *v; /* The virtual database engine */
Table *pTab; /* The table from which records will be deleted */
const char *zDb; /* Name of database holding pTab */
int i; /* Loop counter */
WhereInfo *pWInfo; /* Information about the WHERE clause */
Index *pIdx; /* For looping over indices of the table */
int iTabCur; /* Cursor number for the table */
int iDataCur = 0; /* VDBE cursor for the canonical data source */
int iIdxCur = 0; /* Cursor number of the first index */
int nIdx; /* Number of indices */
sqlite3 *db; /* Main database structure */
AuthContext sContext; /* Authorization context */
NameContext sNC; /* Name context to resolve expressions in */
int iDb; /* Database number */
int memCnt = -1; /* Memory cell used for change counting */
int rcauth; /* Value returned by authorization callback */
int eOnePass; /* ONEPASS_OFF or _SINGLE or _MULTI */
int aiCurOnePass[2]; /* The write cursors opened by WHERE_ONEPASS */
u8 *aToOpen = 0; /* Open cursor iTabCur+j if aToOpen[j] is true */
Index *pPk; /* The PRIMARY KEY index on the table */
int iPk = 0; /* First of nPk registers holding PRIMARY KEY value */
i16 nPk = 1; /* Number of columns in the PRIMARY KEY */
int iKey; /* Memory cell holding key of row to be deleted */
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 addrEphOpen = 0; /* Instruction to open the Ephemeral table */
#ifndef SQLITE_OMIT_TRIGGER
int isView; /* True if attempting to delete from a view */
@@ -336,7 +631,6 @@ void sqlite3DeleteFrom(
#if !defined(SQLITE_OMIT_VIEW) && !defined(SQLITE_OMIT_TRIGGER)
if( isView ){
sqlite3MaterializeView(pParse, pTab, pWhere, iTabCur);
iDataCur = iIdxCur = iTabCur;
}
#endif
@@ -379,180 +673,12 @@ void sqlite3DeleteFrom(
}
}else
#endif /* SQLITE_OMIT_TRUNCATE_OPTIMIZATION */
{
u16 wcf = WHERE_ONEPASS_DESIRED|WHERE_DUPLICATES_OK;
wcf |= (bComplex ? 0 : WHERE_ONEPASS_MULTIROW);
if( HasRowid(pTab) ){
/* For a rowid table, initialize the RowSet to an empty set */
pPk = 0;
nPk = 1;
iRowSet = ++pParse->nMem;
sqlite3VdbeAddOp2(v, OP_Null, 0, iRowSet);
}else{
/* For a WITHOUT ROWID table, create an ephemeral table used to
** hold all primary keys for rows to be deleted. */
pPk = sqlite3PrimaryKeyIndex(pTab);
assert( pPk!=0 );
nPk = pPk->nKeyCol;
iPk = pParse->nMem+1;
pParse->nMem += nPk;
iEphCur = pParse->nTab++;
addrEphOpen = sqlite3VdbeAddOp2(v, OP_OpenEphemeral, iEphCur, nPk);
sqlite3VdbeSetP4KeyInfo(pParse, pPk);
}
/* Construct a query to find the rowid or primary key for every row
** to be deleted, based on the WHERE clause. Set variable eOnePass
** to indicate the strategy used to implement this delete:
**
** ONEPASS_OFF: Two-pass approach - use a FIFO for rowids/PK values.
** ONEPASS_SINGLE: One-pass approach - at most one row deleted.
** ONEPASS_MULTI: One-pass approach - any number of rows may be deleted.
*/
pWInfo = sqlite3WhereBegin(pParse, pTabList, pWhere, 0, 0, wcf, iTabCur+1);
if( pWInfo==0 ) goto delete_from_cleanup;
eOnePass = sqlite3WhereOkOnePass(pWInfo, aiCurOnePass);
assert( IsVirtual(pTab)==0 || eOnePass!=ONEPASS_MULTI );
assert( IsVirtual(pTab) || bComplex || eOnePass!=ONEPASS_OFF );
/* Keep track of the number of rows to be deleted */
if( db->flags & SQLITE_CountRows ){
sqlite3VdbeAddOp2(v, OP_AddImm, memCnt, 1);
}
/* Extract the rowid or primary key for the current row */
if( pPk ){
for(i=0; i<nPk; i++){
assert( pPk->aiColumn[i]>=0 );
sqlite3ExprCodeGetColumnOfTable(v, pTab, iTabCur,
pPk->aiColumn[i], iPk+i);
}
iKey = iPk;
}else{
iKey = pParse->nMem + 1;
iKey = sqlite3ExprCodeGetColumn(pParse, pTab, -1, iTabCur, iKey, 0);
if( iKey>pParse->nMem ) pParse->nMem = iKey;
}
if( eOnePass!=ONEPASS_OFF ){
/* For ONEPASS, no need to store the rowid/primary-key. There is only
** one, so just keep it in its register(s) and fall through to the
** delete code. */
nKey = nPk; /* OP_Found will use an unpacked key */
aToOpen = sqlite3DbMallocRaw(db, nIdx+2);
if( aToOpen==0 ){
sqlite3WhereEnd(pWInfo);
goto delete_from_cleanup;
}
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);
}else{
if( pPk ){
/* Add the PK key for this row to the temporary table */
iKey = ++pParse->nMem;
nKey = 0; /* Zero tells OP_Found to use a composite key */
sqlite3VdbeAddOp4(v, OP_MakeRecord, iPk, nPk, iKey,
sqlite3IndexAffinityStr(pParse->db, pPk), nPk);
sqlite3VdbeAddOp2(v, OP_IdxInsert, iEphCur, iKey);
}else{
/* Add the rowid of the row to be deleted to the RowSet */
nKey = 1; /* OP_Seek always uses a single rowid */
sqlite3VdbeAddOp2(v, OP_RowSetAdd, iRowSet, iKey);
}
}
/* If this DELETE cannot use the ONEPASS strategy, this is the
** end of the WHERE loop */
if( eOnePass!=ONEPASS_OFF ){
addrBypass = sqlite3VdbeMakeLabel(v);
}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
** triggers.
*/
if( !isView ){
int iAddrOnce = 0;
u8 p5 = (eOnePass==ONEPASS_OFF ? 0 : OPFLAG_FORDELETE);
if( eOnePass==ONEPASS_MULTI ){
iAddrOnce = sqlite3CodeOnce(pParse); VdbeCoverage(v);
}
testcase( IsVirtual(pTab) );
sqlite3OpenTableAndIndices(pParse, pTab, OP_OpenWrite, p5, iTabCur,
aToOpen, &iDataCur, &iIdxCur);
assert( pPk || IsVirtual(pTab) || iDataCur==iTabCur );
assert( pPk || IsVirtual(pTab) || iIdxCur==iDataCur+1 );
if( eOnePass==ONEPASS_MULTI ) sqlite3VdbeJumpHere(v, iAddrOnce);
}
/* Set up a loop over the rowids/primary-keys that were found in the
** where-clause loop above.
*/
if( eOnePass!=ONEPASS_OFF ){
assert( nKey==nPk ); /* OP_Found will use an unpacked key */
if( !IsVirtual(pTab) && aToOpen[iDataCur-iTabCur] ){
assert( pPk!=0 || pTab->pSelect!=0 );
sqlite3VdbeAddOp4Int(v, OP_NotFound, iDataCur, addrBypass, iKey, nKey);
VdbeCoverage(v);
}
}else if( pPk ){
addrLoop = sqlite3VdbeAddOp1(v, OP_Rewind, iEphCur); VdbeCoverage(v);
sqlite3VdbeAddOp2(v, OP_RowKey, iEphCur, iKey);
assert( nKey==0 ); /* OP_Found will use a composite key */
}else{
addrLoop = sqlite3VdbeAddOp3(v, OP_RowSetRead, iRowSet, 0, iKey);
VdbeCoverage(v);
assert( nKey==1 );
}
/* Delete the row */
#ifndef SQLITE_OMIT_VIRTUALTABLE
if( IsVirtual(pTab) ){
const char *pVTab = (const char *)sqlite3GetVTable(db, pTab);
sqlite3VtabMakeWritable(pParse, pTab);
sqlite3VdbeAddOp4(v, OP_VUpdate, 0, 1, iKey, pVTab, P4_VTAB);
sqlite3VdbeChangeP5(v, OE_Abort);
assert( eOnePass==ONEPASS_OFF || eOnePass==ONEPASS_SINGLE );
sqlite3MayAbort(pParse);
if( eOnePass==ONEPASS_SINGLE && sqlite3IsToplevel(pParse) ){
pParse->isMultiWrite = 0;
}
}else
#endif
{
int count = (pParse->nested==0); /* True to count changes */
int iIdxNoSeek = -1;
if( bComplex==0 && aiCurOnePass[1]!=iDataCur ){
iIdxNoSeek = aiCurOnePass[1];
}
sqlite3GenerateRowDelete(pParse, pTab, pTrigger, iDataCur, iIdxCur,
iKey, nKey, count, OE_Default, eOnePass, iIdxNoSeek);
}
/* End of the loop over all rowids/primary-keys. */
if( eOnePass!=ONEPASS_OFF ){
sqlite3VdbeResolveLabel(v, addrBypass);
sqlite3WhereEnd(pWInfo);
}else if( pPk ){
sqlite3VdbeAddOp2(v, OP_Next, iEphCur, addrLoop+1); VdbeCoverage(v);
sqlite3VdbeJumpHere(v, addrLoop);
}else{
sqlite3VdbeGoto(v, addrLoop);
sqlite3VdbeJumpHere(v, addrLoop);
}
/* Close the cursors open on the table and its indexes. */
if( !isView && !IsVirtual(pTab) ){
if( !pPk ) sqlite3VdbeAddOp1(v, OP_Close, iDataCur);
for(i=0, pIdx=pTab->pIndex; pIdx; i++, pIdx=pIdx->pNext){
sqlite3VdbeAddOp1(v, OP_Close, iIdxCur + i);
}
if( deleteFrom(pParse, pTabList, pWhere, isView, pTrigger, bComplex,
memCnt, nIdx)
){
goto delete_from_cleanup;
}
} /* End non-truncate path */
@@ -578,7 +704,6 @@ delete_from_cleanup:
sqlite3AuthContextPop(&sContext);
sqlite3SrcListDelete(db, pTabList);
sqlite3ExprDelete(db, pWhere);
sqlite3DbFree(db, aToOpen);
return;
}
/* Make sure "isView" and other macros defined above are undefined. Otherwise
+6 -3
View File
@@ -3439,6 +3439,8 @@ void sqlite3DeleteFrom(Parse*, SrcList*, Expr*);
void sqlite3Update(Parse*, SrcList*, ExprList*, Expr*, int);
WhereInfo *sqlite3WhereBegin(Parse*,SrcList*,Expr*,ExprList*,ExprList*,u16,int);
void sqlite3WhereEnd(WhereInfo*);
void sqlite3WhereInfoFree(sqlite3*, WhereInfo*);
Expr *sqlite3WhereSplitExpr(WhereInfo*, int);
u64 sqlite3WhereOutputRowCount(WhereInfo*);
int sqlite3WhereIsDistinct(WhereInfo*);
int sqlite3WhereIsOrdered(WhereInfo*);
@@ -3446,9 +3448,10 @@ int sqlite3WhereIsSorted(WhereInfo*);
int sqlite3WhereContinueLabel(WhereInfo*);
int sqlite3WhereBreakLabel(WhereInfo*);
int sqlite3WhereOkOnePass(WhereInfo*, int*);
#define ONEPASS_OFF 0 /* Use of ONEPASS not allowed */
#define ONEPASS_SINGLE 1 /* ONEPASS valid for a single row update */
#define ONEPASS_MULTI 2 /* ONEPASS is valid for multiple rows */
#define ONEPASS_OFF 0 /* Use of ONEPASS not allowed */
#define ONEPASS_SINGLE 1 /* ONEPASS valid for a single row update */
#define ONEPASS_MULTI 2 /* ONEPASS is valid for multiple rows */
#define ONEPASS_SPLIT_DELETE 3 /* DELETE should be split into multiple ops */
void sqlite3ExprCodeLoadIndexColumn(Parse*, Index*, int, int, int);
int sqlite3ExprCodeGetColumn(Parse*, Table*, int, int, int, u8);
void sqlite3ExprCodeGetColumnToReg(Parse*, Table*, int, int, int);
+62 -4
View File
@@ -1774,7 +1774,7 @@ static void whereLoopDelete(sqlite3 *db, WhereLoop *p){
/*
** Free a WhereInfo structure
*/
static void whereInfoFree(sqlite3 *db, WhereInfo *pWInfo){
void sqlite3WhereInfoFree(sqlite3 *db, WhereInfo *pWInfo){
if( ALWAYS(pWInfo) ){
int i;
for(i=0; i<pWInfo->nLevel; i++){
@@ -1793,6 +1793,53 @@ static void whereInfoFree(sqlite3 *db, WhereInfo *pWInfo){
}
}
/*
** This function may only be called if sqlite3WhereOkOnePass() on the same
** WhereInfo object has returned ONEPASS_SPLIT_DELETE, indicating that the
** WHERE clause consists of a series of sub-expressions connected by OR
** operators. This function is used to access elements of this set of
** sub-expressions.
**
** OR-connected sub-expressions are numbered contiguously starting from
** zero. The sub-expression to return is identified by the iExpr parameter
** passed to this function. If iExpr is equal to or greater than the number
** of sub-expressions, NULL is returned. Otherwise, a pointer to a copy of
** sub-expression iExpr is returned. The caller is responsible for eventually
** deleting this object using sqlite3ExprDelete().
**
** If an OOM error occurs, the mallocFailed field of the database handle
** (pWInfo->pParse->db->mallocFailed) is set to record the error. Even
** if an OOM error occurs, this function may return a non-NULL pointer. In
** this case the caller is still responsible for deleting the returned
** object, even though it is not safe to use.
*/
Expr *sqlite3WhereSplitExpr(WhereInfo *pWInfo, int iExpr){
sqlite3 *db = pWInfo->pParse->db;
WhereLoop *pLoop = pWInfo->a[0].pWLoop;
WhereTerm *pTerm = pLoop->aLTerm[0];
WhereClause *pOrWC = &pTerm->u.pOrInfo->wc;
Expr *pExpr = 0;
assert( pWInfo->eOnePass==ONEPASS_SPLIT_DELETE );
assert( (pTerm->wtFlags & TERM_ORINFO)!=0 );
if( iExpr<pOrWC->nTerm ){
pExpr = sqlite3ExprDup(db, pOrWC->a[iExpr].pExpr, 0);
if( pExpr ){
WhereClause *pWC = &pWInfo->sWC;
int ii;
for(ii=0; ii<pWC->nTerm; ii++){
if( &pWC->a[ii]!=pTerm ){
Expr *pLeft = sqlite3ExprDup(db, pWC->a[ii].pExpr, 0);
pExpr = sqlite3ExprAnd(db, pLeft, pExpr);
}
}
}
}
return pExpr;
}
/*
** Return TRUE if all of the following are true:
**
@@ -4255,7 +4302,6 @@ WhereInfo *sqlite3WhereBegin(
}
}
WHERETRACE(0xffff,("*** Optimizer Finished ***\n"));
pWInfo->pParse->nQueryLoop += pWInfo->nRowOut;
/* If the caller is an UPDATE or DELETE statement that is requesting
** to use a one-pass algorithm, determine if this is appropriate.
@@ -4269,6 +4315,17 @@ WhereInfo *sqlite3WhereBegin(
if( bOnerow || ( (wctrlFlags & WHERE_ONEPASS_MULTIROW)
&& 0==(wsFlags & WHERE_VIRTUALTABLE)
)){
if( (wsFlags & WHERE_MULTI_OR) && (wctrlFlags & WHERE_ONEPASS_MULTIROW) ){
/* This call is being made as part of a DELETE program and the
** optimizer has indicated that the OR-optimization is the best
** approach. In this case it is better to let the caller generate
** a separate loop for each OR'd term than to actually go ahead
** and code the OR-optimized loop. Set the value returned by
** sqlite3WhereOkOnePass() to ONEPASS_SPLIT_DELETE to communicate
** this to the caller and return early. */
pWInfo->eOnePass = ONEPASS_SPLIT_DELETE;
return pWInfo;
}
pWInfo->eOnePass = bOnerow ? ONEPASS_SINGLE : ONEPASS_MULTI;
if( HasRowid(pTabList->a[0].pTab) && (wsFlags & WHERE_IDX_ONLY) ){
if( wctrlFlags & WHERE_ONEPASS_MULTIROW ){
@@ -4278,6 +4335,7 @@ WhereInfo *sqlite3WhereBegin(
}
}
}
pWInfo->pParse->nQueryLoop += pWInfo->nRowOut;
/* Open all tables in the pTabList and any indices selected for
** searching those tables.
@@ -4437,7 +4495,7 @@ WhereInfo *sqlite3WhereBegin(
whereBeginError:
if( pWInfo ){
pParse->nQueryLoop = pWInfo->savedNQueryLoop;
whereInfoFree(db, pWInfo);
sqlite3WhereInfoFree(db, pWInfo);
}
return 0;
}
@@ -4621,6 +4679,6 @@ void sqlite3WhereEnd(WhereInfo *pWInfo){
/* Final cleanup
*/
pParse->nQueryLoop = pWInfo->savedNQueryLoop;
whereInfoFree(db, pWInfo);
sqlite3WhereInfoFree(db, pWInfo);
return;
}
+85
View File
@@ -139,7 +139,92 @@ do_execsql_test 4.12 {
PRAGMA integrity_check;
} {ok}
#-------------------------------------------------------------------------
# Further tests for DELETE statements that use the OR optimization.
#
array unset -nocomplain res
foreach {tn tbl} {
0 { CREATE TABLE t5(i INTEGER, a, b, c) }
1 {
CREATE TABLE t5(i INTEGER PRIMARY KEY, a, b, c);
CREATE INDEX t5a ON t5(a);
CREATE INDEX t5bc ON t5(b, c);
CREATE INDEX t5c ON t5(c);
}
2 {
CREATE TABLE t5(i INT PRIMARY KEY, a, b, c) WITHOUT ROWID;
CREATE INDEX t5a ON t5(a);
CREATE INDEX t5bc ON t5(b, c);
CREATE INDEX t5c ON t5(c);
}
} {
do_test 5.$tn.0 {
execsql { DROP TABLE IF EXISTS t5 }
execsql $tbl
for {set i 1} {$i <= 100} {incr i} {
set a [expr $i*2]
set b [expr ($i*7 % 100)]
set c [expr 100-$i]
execsql { INSERT INTO t5 VALUES($i, $a, $b, $c) }
}
} {}
foreach {tn2 sql} {
1 { DELETE FROM t5 WHERE a=4 OR b=10 OR c=17 }
2 { DELETE FROM t5 WHERE (a=4 OR b=22) AND c=98 }
3 { DELETE FROM t5 WHERE (b=49 AND c=93) OR a=14 }
4 { DELETE FROM t5 WHERE (b=70 AND c=90) OR a=150 }
5 { DELETE FROM t5 WHERE (a BETWEEN 2 AND 20) OR (b BETWEEN 1 AND 10) }
6 { DELETE FROM t5 WHERE a IN (2, 4, 6, 8) OR b IN (63, 70, 77) }
} {
execsql BEGIN
execsql $sql
do_execsql_test 5.$tn.$tn2.1 { PRAGMA integrity_check } ok
if {$tn==0} {
set res($tn2) [db eval { SELECT count(*), md5sum(i, a, b, c) FROM t5 }]
}
do_execsql_test 5.$tn.$tn2.2 {
SELECT count(*), md5sum(i, a, b, c) FROM t5
} $res($tn2)
execsql ROLLBACK
}
}
#-------------------------------------------------------------------------
# Test that nested OR optimizations work.
#
do_execsql_test 6.0 {
CREATE TABLE t6(a, b, c, d);
CREATE INDEX t6a ON t6(a);
CREATE INDEX t6b ON t6(b);
CREATE INDEX t6c ON t6(c);
WITH s(i) AS ( SELECT 1 UNION ALL SELECT i+1 FROM s WHERE i<1000 )
INSERT INTO t6 SELECT i, i*2, i*3, i%2 FROM s;
}
proc do_61_test {tn sql lDel} {
uplevel [list do_execsql_test $tn "
BEGIN;
$sql;
WITH s(i) AS ( SELECT 1 UNION ALL SELECT i+1 FROM s WHERE i<1000 )
SELECT i FROM s EXCEPT SELECT a FROM t6 ORDER BY 1;
ROLLBACK;
" $lDel]
}
do_61_test 6.1 {
DELETE FROM t6 WHERE a=22 OR b=90 OR (d=0 AND (b=400 OR c=303));
} {22 45 200}
do_61_test 6.2 {
DELETE FROM t6 WHERE a=22 OR b=90 OR (d=1 AND (b=400 OR c=303));
} {22 45 101}
do_61_test 6.3 {
DELETE FROM t6 WHERE (d=0 AND (a=100 OR b=150)) OR (d=1 AND (b=50 OR c=603));
} {25 100 201}
finish_test
+50
View File
@@ -0,0 +1,50 @@
# 2015 December 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.
#
#***********************************************************************
# This file implements regression tests for SQLite library. The focus
# of this file is fault-injection into DELETE statements.
#
set testdir [file dirname $argv0]
source $testdir/tester.tcl
set testprefix delete_fault
do_execsql_test 1.0 {
CREATE TABLE t6(a, b, c, d);
CREATE INDEX t6a ON t6(a);
CREATE INDEX t6b ON t6(b);
CREATE INDEX t6c ON t6(c);
WITH s(i) AS ( SELECT 1 UNION ALL SELECT i+1 FROM s WHERE i<1000 )
INSERT INTO t6 SELECT i, i*2, i*3, i%2 FROM s;
}
faultsim_save_and_close
proc deleted_t6_rows {} {
db eval {
WITH s(i) AS ( SELECT 1 UNION ALL SELECT i+1 FROM s WHERE i<1000 )
SELECT i FROM s EXCEPT SELECT a FROM t6 ORDER BY 1;
}
}
do_faultsim_test 1 -faults oom-t* -prep {
faultsim_restore_and_reopen
} -body {
execsql {
DELETE FROM t6 WHERE (d=0 AND (a=100 OR b=150)) OR (d=1 AND (b=50 OR c=603))
}
} -test {
faultsim_test_result {0 {}}
if {$testrc==0} {
set lDel [deleted_t6_rows]
if {$lDel != "25 100 201"} { error "lDel is $lDel" }
}
}
finish_test
+68 -3
View File
@@ -61,7 +61,7 @@ foreach {tn sql res} {
do_adp_test 1.$tn $sql $res
}
do_execsql_test 2.0 {
do_execsql_test 2.1.0 {
CREATE TABLE t2(a, b, c);
CREATE INDEX t2a ON t2(a);
CREATE INDEX t2b ON t2(b);
@@ -70,13 +70,78 @@ do_execsql_test 2.0 {
foreach {tn sql res} {
1 { DELETE FROM t2 WHERE a=?} { t2* t2a t2b* t2c* }
2 { DELETE FROM t2 WHERE a=? AND +b=?} { t2 t2a t2b* t2c* }
3 { DELETE FROM t2 WHERE a=? OR b=?} { t2 t2a* t2b* t2c* }
3 { DELETE FROM t2 WHERE a=? OR b=?} { t2* t2* t2a t2a* t2b t2b* t2c* t2c* }
4 { DELETE FROM t2 WHERE +a=? } { t2 t2a* t2b* t2c* }
5 { DELETE FROM t2 WHERE rowid=? } { t2 t2a* t2b* t2c* }
} {
do_adp_test 2.$tn $sql $res
do_adp_test 2.1.$tn $sql $res
}
foreach {tn where res} {
1 { a=? OR b=? } { t2* t2* t2a t2a* t2b t2b* t2c* t2c* }
2 { a=? OR (b=? AND +c=?) } { t2 t2* t2a t2a* t2b t2b* t2c* t2c* }
3 { (a=? OR b=?) AND +c=? } { t2 t2 t2a t2a* t2b t2b* t2c* t2c* }
4 { (c=? OR b=?) } { t2* t2* t2a* t2a* t2b t2b* t2c t2c* }
5 { (c=? OR b=?) AND EXISTS (SELECT 1 FROM t1 WHERE t1.a=?) }
{ t2* t2* t2a* t2a* t2b t2b* t2c t2c* }
} {
do_adp_test 2.2.$tn "DELETE FROM t2 WHERE $where" $res
}
do_execsql_test 2.3.0 {
CREATE TABLE t3(x, y, z);
CREATE INDEX t3xy ON t3(x, y);
CREATE INDEX t3zy ON t3(z, y);
CREATE INDEX t3zx ON t3(z, x);
CREATE INDEX t3x ON t3(x);
}
foreach {tn where res} {
1 { x=? OR z=? }
{ t3* t3* t3x t3x* t3xy* t3xy* t3zx t3zx* t3zy* t3zy* }
2 { (x=? OR z=?) AND y=? }
{ t3* t3* t3x* t3x* t3xy t3xy* t3zx* t3zx* t3zy t3zy* }
3 { (y=? OR z=?) AND x=? } { t3 t3x t3xy* t3zx* t3zy* }
} {
do_adp_test 2.3.$tn "DELETE FROM t3 WHERE $where" $res
}
do_execsql_test 2.4.0 {
CREATE TABLE t6(a, b, c, d);
CREATE INDEX t6a ON t6(a);
CREATE INDEX t6b ON t6(b);
CREATE INDEX t6c ON t6(c);
}
do_adp_test 2.4.1 {
DELETE FROM t6 WHERE a=22 OR b=90 OR (d=0 AND (b=400 OR c=303));
} [lsort {
t6* t6a t6b* t6c*
t6* t6a* t6b t6c*
t6 t6a* t6b t6c*
t6 t6a* t6b* t6c
}]
do_adp_test 2.4.2 {
DELETE FROM t6 WHERE a=22 OR b=90 OR (d=1 AND (b=400 OR c=303));
} [lsort {
t6* t6a t6b* t6c*
t6* t6a* t6b t6c*
t6 t6a* t6b t6c*
t6 t6a* t6b* t6c
}]
do_adp_test 2.4.3 {
DELETE FROM t6 WHERE (d=0 AND (a=100 OR b=150)) OR (d=1 AND (b=50 OR c=603));
} [lsort {
t6 t6a t6b* t6c*
t6 t6a* t6b t6c*
t6 t6a* t6b t6c*
t6 t6a* t6b* t6c
}]
#-------------------------------------------------------------------------
# Test that a record that consists of the bytes:
#
+11
View File
@@ -834,6 +834,17 @@ do_execsql_test table-19.1 {
SELECT name FROM t19 ORDER BY name;
} {{} savepoint t10 t11 t12 t13 t16 t2 t3 t3\"xyz t4\"abc t7 t8 t9 tablet8 test1 weird}
# At one point the DROP TABLE in the following was causing problems on an
# experimental branch.
#
reset_db
do_execsql_test table-20.0 {
CREATE TABLE t20(a PRIMARY KEY, b, c);
CREATE TRIGGER tr6 AFTER INSERT ON t20 BEGIN SELECT 1+1+1; END;
ANALYZE;
DROP TABLE t20;
SELECT name FROM sqlite_master
} {sqlite_stat1 sqlite_stat4}
finish_test