| Index: third_party/sqlite/src/src/delete.c
|
| diff --git a/third_party/sqlite/src/src/delete.c b/third_party/sqlite/src/src/delete.c
|
| index e5389e2b6a743fada5ffecdb04dd080603be31a5..d81dd3f6b48d8eab35a073dcceedbd8b1555550a 100644
|
| --- a/third_party/sqlite/src/src/delete.c
|
| +++ b/third_party/sqlite/src/src/delete.c
|
| @@ -32,7 +32,7 @@ Table *sqlite3SrcListLookup(Parse *pParse, SrcList *pSrc){
|
| struct SrcList_item *pItem = pSrc->a;
|
| Table *pTab;
|
| assert( pItem && pSrc->nSrc==1 );
|
| - pTab = sqlite3LocateTable(pParse, 0, pItem->zName, pItem->zDatabase);
|
| + pTab = sqlite3LocateTableItem(pParse, 0, pItem);
|
| sqlite3DeleteTable(pParse->db, pItem->pTab);
|
| pItem->pTab = pTab;
|
| if( pTab ){
|
| @@ -90,32 +90,26 @@ void sqlite3MaterializeView(
|
| Parse *pParse, /* Parsing context */
|
| Table *pView, /* View definition */
|
| Expr *pWhere, /* Optional WHERE clause to be added */
|
| - int iCur /* Cursor number for ephemerial table */
|
| + int iCur /* Cursor number for ephemeral table */
|
| ){
|
| SelectDest dest;
|
| - Select *pDup;
|
| + Select *pSel;
|
| + SrcList *pFrom;
|
| sqlite3 *db = pParse->db;
|
| -
|
| - pDup = sqlite3SelectDup(db, pView->pSelect, 0);
|
| - if( pWhere ){
|
| - SrcList *pFrom;
|
| -
|
| - pWhere = sqlite3ExprDup(db, pWhere, 0);
|
| - pFrom = sqlite3SrcListAppend(db, 0, 0, 0);
|
| - if( pFrom ){
|
| - assert( pFrom->nSrc==1 );
|
| - pFrom->a[0].zAlias = sqlite3DbStrDup(db, pView->zName);
|
| - pFrom->a[0].pSelect = pDup;
|
| - assert( pFrom->a[0].pOn==0 );
|
| - assert( pFrom->a[0].pUsing==0 );
|
| - }else{
|
| - sqlite3SelectDelete(db, pDup);
|
| - }
|
| - pDup = sqlite3SelectNew(pParse, 0, pFrom, pWhere, 0, 0, 0, 0, 0, 0);
|
| + int iDb = sqlite3SchemaToIndex(db, pView->pSchema);
|
| + pWhere = sqlite3ExprDup(db, pWhere, 0);
|
| + pFrom = sqlite3SrcListAppend(db, 0, 0, 0);
|
| + if( pFrom ){
|
| + assert( pFrom->nSrc==1 );
|
| + pFrom->a[0].zName = sqlite3DbStrDup(db, pView->zName);
|
| + pFrom->a[0].zDatabase = sqlite3DbStrDup(db, db->aDb[iDb].zName);
|
| + assert( pFrom->a[0].pOn==0 );
|
| + assert( pFrom->a[0].pUsing==0 );
|
| }
|
| + pSel = sqlite3SelectNew(pParse, 0, pFrom, pWhere, 0, 0, 0, 0, 0, 0);
|
| sqlite3SelectDestInit(&dest, SRT_EphemTab, iCur);
|
| - sqlite3Select(pParse, pDup, &dest);
|
| - sqlite3SelectDelete(db, pDup);
|
| + sqlite3Select(pParse, pSel, &dest);
|
| + sqlite3SelectDelete(db, pSel);
|
| }
|
| #endif /* !defined(SQLITE_OMIT_VIEW) && !defined(SQLITE_OMIT_TRIGGER) */
|
|
|
| @@ -135,7 +129,7 @@ Expr *sqlite3LimitWhere(
|
| ExprList *pOrderBy, /* The ORDER BY clause. May be null */
|
| Expr *pLimit, /* The LIMIT clause. May be null */
|
| Expr *pOffset, /* The OFFSET clause. May be null */
|
| - char *zStmtType /* Either DELETE or UPDATE. For error messages. */
|
| + char *zStmtType /* Either DELETE or UPDATE. For err msgs. */
|
| ){
|
| Expr *pWhereRowid = NULL; /* WHERE rowid .. */
|
| Expr *pInClause = NULL; /* WHERE rowid IN ( select ) */
|
| @@ -148,7 +142,6 @@ Expr *sqlite3LimitWhere(
|
| */
|
| if( pOrderBy && (pLimit == 0) ) {
|
| sqlite3ErrorMsg(pParse, "ORDER BY without LIMIT on %s", zStmtType);
|
| - pParse->parseError = 1;
|
| goto limit_where_cleanup_2;
|
| }
|
|
|
| @@ -211,7 +204,8 @@ limit_where_cleanup_2:
|
| sqlite3ExprDelete(pParse->db, pOffset);
|
| return 0;
|
| }
|
| -#endif /* defined(SQLITE_ENABLE_UPDATE_DELETE_LIMIT) && !defined(SQLITE_OMIT_SUBQUERY) */
|
| +#endif /* defined(SQLITE_ENABLE_UPDATE_DELETE_LIMIT) */
|
| + /* && !defined(SQLITE_OMIT_SUBQUERY) */
|
|
|
| /*
|
| ** Generate code for a DELETE FROM statement.
|
| @@ -228,18 +222,34 @@ void sqlite3DeleteFrom(
|
| 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 end, addr = 0; /* A couple addresses of generated code */
|
| int i; /* Loop counter */
|
| WhereInfo *pWInfo; /* Information about the WHERE clause */
|
| Index *pIdx; /* For looping over indices of the table */
|
| - int iCur; /* VDBE Cursor number for pTab */
|
| + int iTabCur; /* Cursor number for the table */
|
| + int iDataCur; /* VDBE cursor for the canonical data source */
|
| + int iIdxCur; /* 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 okOnePass; /* True for one-pass algorithm without the FIFO */
|
| + 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 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 */
|
| @@ -294,11 +304,11 @@ void sqlite3DeleteFrom(
|
| }
|
| assert(!isView || pTrigger);
|
|
|
| - /* Assign cursor number to the table and all its indices.
|
| + /* Assign cursor numbers to the table and all its indices.
|
| */
|
| assert( pTabList->nSrc==1 );
|
| - iCur = pTabList->a[0].iCursor = pParse->nTab++;
|
| - for(pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext){
|
| + iTabCur = pTabList->a[0].iCursor = pParse->nTab++;
|
| + for(nIdx=0, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext, nIdx++){
|
| pParse->nTab++;
|
| }
|
|
|
| @@ -318,11 +328,12 @@ void sqlite3DeleteFrom(
|
| sqlite3BeginWriteOperation(pParse, 1, iDb);
|
|
|
| /* If we are trying to delete from a view, realize that view into
|
| - ** a ephemeral table.
|
| + ** an ephemeral table.
|
| */
|
| #if !defined(SQLITE_OMIT_VIEW) && !defined(SQLITE_OMIT_TRIGGER)
|
| if( isView ){
|
| - sqlite3MaterializeView(pParse, pTab, pWhere, iCur);
|
| + sqlite3MaterializeView(pParse, pTab, pWhere, iTabCur);
|
| + iDataCur = iIdxCur = iTabCur;
|
| }
|
| #endif
|
|
|
| @@ -352,75 +363,173 @@ void sqlite3DeleteFrom(
|
| && 0==sqlite3FkRequired(pParse, pTab, 0, 0)
|
| ){
|
| assert( !isView );
|
| - sqlite3VdbeAddOp4(v, OP_Clear, pTab->tnum, iDb, memCnt,
|
| - pTab->zName, P4_STATIC);
|
| + sqlite3TableLock(pParse, iDb, pTab->tnum, 1, pTab->zName);
|
| + if( HasRowid(pTab) ){
|
| + sqlite3VdbeAddOp4(v, OP_Clear, pTab->tnum, iDb, memCnt,
|
| + pTab->zName, P4_STATIC);
|
| + }
|
| for(pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext){
|
| assert( pIdx->pSchema==pTab->pSchema );
|
| sqlite3VdbeAddOp2(v, OP_Clear, pIdx->tnum, iDb);
|
| }
|
| }else
|
| #endif /* SQLITE_OMIT_TRUNCATE_OPTIMIZATION */
|
| - /* The usual case: There is a WHERE clause so we have to scan through
|
| - ** the table and pick which records to delete.
|
| - */
|
| {
|
| - int iRowSet = ++pParse->nMem; /* Register for rowset of rows to delete */
|
| - int iRowid = ++pParse->nMem; /* Used for storing rowid values. */
|
| - int regRowid; /* Actual register containing rowids */
|
| -
|
| - /* Collect rowids of every row to be deleted.
|
| + 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.
|
| */
|
| - sqlite3VdbeAddOp2(v, OP_Null, 0, iRowSet);
|
| - pWInfo = sqlite3WhereBegin(pParse, pTabList, pWhere,0,WHERE_DUPLICATES_OK);
|
| + pWInfo = sqlite3WhereBegin(pParse, pTabList, pWhere, 0, 0,
|
| + WHERE_ONEPASS_DESIRED|WHERE_DUPLICATES_OK,
|
| + iTabCur+1);
|
| if( pWInfo==0 ) goto delete_from_cleanup;
|
| - regRowid = sqlite3ExprCodeGetColumn(pParse, pTab, -1, iCur, iRowid);
|
| - sqlite3VdbeAddOp2(v, OP_RowSetAdd, iRowSet, regRowid);
|
| + okOnePass = sqlite3WhereOkOnePass(pWInfo, aiCurOnePass);
|
| +
|
| + /* 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++){
|
| + 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( okOnePass ){
|
| + /* 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);
|
| + 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,
|
| + sqlite3IndexAffinityStr(v, pPk), nPk);
|
| + sqlite3VdbeAddOp2(v, OP_IdxInsert, iEphCur, iKey);
|
| + }else{
|
| + /* 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);
|
| -
|
| - /* Delete every item whose key was written to the list during the
|
| - ** database scan. We have to delete items after the scan is complete
|
| - ** because deleting an item can change the scan order. */
|
| - end = sqlite3VdbeMakeLabel(v);
|
| -
|
| + 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);
|
| + }
|
| +
|
| /* 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. */
|
| + ** triggers.
|
| + */
|
| if( !isView ){
|
| - sqlite3OpenTableAndIndices(pParse, pTab, iCur, OP_OpenWrite);
|
| + testcase( IsVirtual(pTab) );
|
| + sqlite3OpenTableAndIndices(pParse, pTab, OP_OpenWrite, iTabCur, aToOpen,
|
| + &iDataCur, &iIdxCur);
|
| + assert( pPk || IsVirtual(pTab) || iDataCur==iTabCur );
|
| + assert( pPk || IsVirtual(pTab) || iIdxCur==iDataCur+1 );
|
| }
|
| -
|
| - addr = sqlite3VdbeAddOp3(v, OP_RowSetRead, iRowSet, end, iRowid);
|
| -
|
| +
|
| + /* Set up a loop over the rowids/primary-keys that were found in the
|
| + ** where-clause loop above.
|
| + */
|
| + if( okOnePass ){
|
| + /* Just one row. Hence the top-of-loop is a no-op */
|
| + assert( nKey==nPk ); /* OP_Found will use an unpacked key */
|
| + assert( !IsVirtual(pTab) );
|
| + if( 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, iRowid, pVTab, P4_VTAB);
|
| + sqlite3VdbeAddOp4(v, OP_VUpdate, 0, 1, iKey, pVTab, P4_VTAB);
|
| + sqlite3VdbeChangeP5(v, OE_Abort);
|
| sqlite3MayAbort(pParse);
|
| }else
|
| #endif
|
| {
|
| int count = (pParse->nested==0); /* True to count changes */
|
| - sqlite3GenerateRowDelete(pParse, pTab, iCur, iRowid, count, pTrigger, OE_Default);
|
| + sqlite3GenerateRowDelete(pParse, pTab, pTrigger, iDataCur, iIdxCur,
|
| + iKey, nKey, count, OE_Default, okOnePass);
|
| }
|
| -
|
| - /* End of the delete loop */
|
| - sqlite3VdbeAddOp2(v, OP_Goto, 0, addr);
|
| - sqlite3VdbeResolveLabel(v, end);
|
| -
|
| +
|
| + /* End of the loop over all rowids/primary-keys. */
|
| + if( okOnePass ){
|
| + sqlite3VdbeResolveLabel(v, addrBypass);
|
| + }else if( pPk ){
|
| + sqlite3VdbeAddOp2(v, OP_Next, iEphCur, addrLoop+1); VdbeCoverage(v);
|
| + sqlite3VdbeJumpHere(v, addrLoop);
|
| + }else{
|
| + sqlite3VdbeAddOp2(v, OP_Goto, 0, addrLoop);
|
| + sqlite3VdbeJumpHere(v, addrLoop);
|
| + }
|
| +
|
| /* Close the cursors open on the table and its indexes. */
|
| if( !isView && !IsVirtual(pTab) ){
|
| - for(i=1, pIdx=pTab->pIndex; pIdx; i++, pIdx=pIdx->pNext){
|
| - sqlite3VdbeAddOp2(v, OP_Close, iCur + i, pIdx->tnum);
|
| + if( !pPk ) sqlite3VdbeAddOp1(v, OP_Close, iDataCur);
|
| + for(i=0, pIdx=pTab->pIndex; pIdx; i++, pIdx=pIdx->pNext){
|
| + sqlite3VdbeAddOp1(v, OP_Close, iIdxCur + i);
|
| }
|
| - sqlite3VdbeAddOp1(v, OP_Close, iCur);
|
| }
|
| - }
|
| + } /* End non-truncate path */
|
|
|
| /* Update the sqlite_sequence table by storing the content of the
|
| ** maximum rowid counter values recorded while inserting into
|
| @@ -444,10 +553,11 @@ 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
|
| -** thely may interfere with compilation of other functions in this file
|
| +** they may interfere with compilation of other functions in this file
|
| ** (or in another file, if this file becomes part of the amalgamation). */
|
| #ifdef isView
|
| #undef isView
|
| @@ -458,50 +568,63 @@ delete_from_cleanup:
|
|
|
| /*
|
| ** This routine generates VDBE code that causes a single row of a
|
| -** single table to be deleted.
|
| +** single table to be deleted. Both the original table entry and
|
| +** all indices are removed.
|
| **
|
| -** The VDBE must be in a particular state when this routine is called.
|
| -** These are the requirements:
|
| +** Preconditions:
|
| **
|
| -** 1. A read/write cursor pointing to pTab, the table containing the row
|
| -** to be deleted, must be opened as cursor number $iCur.
|
| +** 1. iDataCur is an open cursor on the btree that is the canonical data
|
| +** store for the table. (This will be either the table itself,
|
| +** in the case of a rowid table, or the PRIMARY KEY index in the case
|
| +** of a WITHOUT ROWID table.)
|
| **
|
| ** 2. Read/write cursors for all indices of pTab must be open as
|
| -** cursor number base+i for the i-th index.
|
| +** cursor number iIdxCur+i for the i-th index.
|
| **
|
| -** 3. The record number of the row to be deleted must be stored in
|
| -** memory cell iRowid.
|
| -**
|
| -** This routine generates code to remove both the table record and all
|
| -** index entries that point to that record.
|
| +** 3. The primary key for the row to be deleted must be stored in a
|
| +** sequence of nPk memory cells starting at iPk. If nPk==0 that means
|
| +** that a search record formed from OP_MakeRecord is contained in the
|
| +** single memory location iPk.
|
| */
|
| void sqlite3GenerateRowDelete(
|
| Parse *pParse, /* Parsing context */
|
| Table *pTab, /* Table containing the row to be deleted */
|
| - int iCur, /* Cursor number for the table */
|
| - int iRowid, /* Memory cell that contains the rowid to delete */
|
| - int count, /* If non-zero, increment the row change counter */
|
| Trigger *pTrigger, /* List of triggers to (potentially) fire */
|
| - int onconf /* Default ON CONFLICT policy for triggers */
|
| + int iDataCur, /* Cursor from which column data is extracted */
|
| + int iIdxCur, /* First index cursor */
|
| + int iPk, /* First memory cell containing the PRIMARY KEY */
|
| + i16 nPk, /* Number of PRIMARY KEY memory cells */
|
| + u8 count, /* If non-zero, increment the row change counter */
|
| + u8 onconf, /* Default ON CONFLICT policy for triggers */
|
| + u8 bNoSeek /* iDataCur is already pointing to the row to delete */
|
| ){
|
| Vdbe *v = pParse->pVdbe; /* Vdbe */
|
| int iOld = 0; /* First register in OLD.* array */
|
| int iLabel; /* Label resolved to end of generated code */
|
| + u8 opSeek; /* Seek opcode */
|
|
|
| /* Vdbe is guaranteed to have been allocated by this stage. */
|
| assert( v );
|
| + VdbeModuleComment((v, "BEGIN: GenRowDel(%d,%d,%d,%d)",
|
| + iDataCur, iIdxCur, iPk, (int)nPk));
|
|
|
| /* Seek cursor iCur to the row to delete. If this row no longer exists
|
| ** (this can happen if a trigger program has already deleted it), do
|
| ** not attempt to delete it or fire any DELETE triggers. */
|
| iLabel = sqlite3VdbeMakeLabel(v);
|
| - sqlite3VdbeAddOp3(v, OP_NotExists, iCur, iLabel, iRowid);
|
| + opSeek = HasRowid(pTab) ? OP_NotExists : OP_NotFound;
|
| + if( !bNoSeek ){
|
| + sqlite3VdbeAddOp4Int(v, opSeek, iDataCur, iLabel, iPk, nPk);
|
| + VdbeCoverageIf(v, opSeek==OP_NotExists);
|
| + VdbeCoverageIf(v, opSeek==OP_NotFound);
|
| + }
|
|
|
| /* If there are any triggers to fire, allocate a range of registers to
|
| ** use for the old.* references in the triggers. */
|
| if( sqlite3FkRequired(pParse, pTab, 0, 0) || pTrigger ){
|
| u32 mask; /* Mask of OLD.* columns in use */
|
| int iCol; /* Iterator used while populating OLD.* */
|
| + int addrStart; /* Start of BEFORE trigger programs */
|
|
|
| /* TODO: Could use temporary registers here. Also could attempt to
|
| ** avoid copying the contents of the rowid register. */
|
| @@ -514,36 +637,44 @@ void sqlite3GenerateRowDelete(
|
|
|
| /* Populate the OLD.* pseudo-table register array. These values will be
|
| ** used by any BEFORE and AFTER triggers that exist. */
|
| - sqlite3VdbeAddOp2(v, OP_Copy, iRowid, iOld);
|
| + sqlite3VdbeAddOp2(v, OP_Copy, iPk, iOld);
|
| for(iCol=0; iCol<pTab->nCol; iCol++){
|
| - if( mask==0xffffffff || mask&(1<<iCol) ){
|
| - sqlite3ExprCodeGetColumnOfTable(v, pTab, iCur, iCol, iOld+iCol+1);
|
| + testcase( mask!=0xffffffff && iCol==31 );
|
| + testcase( mask!=0xffffffff && iCol==32 );
|
| + if( mask==0xffffffff || (iCol<=31 && (mask & MASKBIT32(iCol))!=0) ){
|
| + sqlite3ExprCodeGetColumnOfTable(v, pTab, iDataCur, iCol, iOld+iCol+1);
|
| }
|
| }
|
|
|
| /* Invoke BEFORE DELETE trigger programs. */
|
| + addrStart = sqlite3VdbeCurrentAddr(v);
|
| sqlite3CodeRowTrigger(pParse, pTrigger,
|
| TK_DELETE, 0, TRIGGER_BEFORE, pTab, iOld, onconf, iLabel
|
| );
|
|
|
| - /* Seek the cursor to the row to be deleted again. It may be that
|
| - ** the BEFORE triggers coded above have already removed the row
|
| - ** being deleted. Do not attempt to delete the row a second time, and
|
| - ** do not fire AFTER triggers. */
|
| - sqlite3VdbeAddOp3(v, OP_NotExists, iCur, iLabel, iRowid);
|
| + /* If any BEFORE triggers were coded, then seek the cursor to the
|
| + ** row to be deleted again. It may be that the BEFORE triggers moved
|
| + ** the cursor or of already deleted the row that the cursor was
|
| + ** pointing to.
|
| + */
|
| + if( addrStart<sqlite3VdbeCurrentAddr(v) ){
|
| + sqlite3VdbeAddOp4Int(v, opSeek, iDataCur, iLabel, iPk, nPk);
|
| + VdbeCoverageIf(v, opSeek==OP_NotExists);
|
| + VdbeCoverageIf(v, opSeek==OP_NotFound);
|
| + }
|
|
|
| /* Do FK processing. This call checks that any FK constraints that
|
| ** refer to this table (i.e. constraints attached to other tables)
|
| ** are not violated by deleting this row. */
|
| - sqlite3FkCheck(pParse, pTab, iOld, 0);
|
| + sqlite3FkCheck(pParse, pTab, iOld, 0, 0, 0);
|
| }
|
|
|
| /* 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, iCur, 0);
|
| - sqlite3VdbeAddOp2(v, OP_Delete, iCur, (count?OPFLAG_NCHANGE: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);
|
| }
|
| @@ -552,7 +683,7 @@ void sqlite3GenerateRowDelete(
|
| /* Do any ON CASCADE, SET NULL or SET DEFAULT operations required to
|
| ** handle rows (possibly in other tables) that refer via a foreign key
|
| ** to the row just deleted. */
|
| - sqlite3FkActions(pParse, pTab, 0, iOld);
|
| + sqlite3FkActions(pParse, pTab, 0, iOld, 0, 0);
|
|
|
| /* Invoke AFTER DELETE trigger programs. */
|
| sqlite3CodeRowTrigger(pParse, pTrigger,
|
| @@ -563,58 +694,98 @@ void sqlite3GenerateRowDelete(
|
| ** trigger programs were invoked. Or if a trigger program throws a
|
| ** RAISE(IGNORE) exception. */
|
| sqlite3VdbeResolveLabel(v, iLabel);
|
| + VdbeModuleComment((v, "END: GenRowDel()"));
|
| }
|
|
|
| /*
|
| ** This routine generates VDBE code that causes the deletion of all
|
| -** index entries associated with a single row of a single table.
|
| +** index entries associated with a single row of a single table, pTab
|
| **
|
| -** The VDBE must be in a particular state when this routine is called.
|
| -** These are the requirements:
|
| +** Preconditions:
|
| **
|
| -** 1. A read/write cursor pointing to pTab, the table containing the row
|
| -** to be deleted, must be opened as cursor number "iCur".
|
| +** 1. A read/write cursor "iDataCur" must be open on the canonical storage
|
| +** btree for the table pTab. (This will be either the table itself
|
| +** for rowid tables or to the primary key index for WITHOUT ROWID
|
| +** tables.)
|
| **
|
| ** 2. Read/write cursors for all indices of pTab must be open as
|
| -** cursor number iCur+i for the i-th index.
|
| +** cursor number iIdxCur+i for the i-th index. (The pTab->pIndex
|
| +** index is the 0-th index.)
|
| **
|
| -** 3. The "iCur" cursor must be pointing to the row that is to be
|
| -** deleted.
|
| +** 3. The "iDataCur" cursor must be already be positioned on the row
|
| +** that is to be deleted.
|
| */
|
| void sqlite3GenerateRowIndexDelete(
|
| Parse *pParse, /* Parsing and code generating context */
|
| Table *pTab, /* Table containing the row to be deleted */
|
| - int iCur, /* Cursor number for the table */
|
| + int iDataCur, /* Cursor of table holding data. */
|
| + int iIdxCur, /* First index cursor */
|
| int *aRegIdx /* Only delete if aRegIdx!=0 && aRegIdx[i]>0 */
|
| ){
|
| - int i;
|
| - Index *pIdx;
|
| - int r1;
|
| -
|
| - for(i=1, pIdx=pTab->pIndex; pIdx; i++, pIdx=pIdx->pNext){
|
| - if( aRegIdx!=0 && aRegIdx[i-1]==0 ) continue;
|
| - r1 = sqlite3GenerateIndexKey(pParse, pIdx, iCur, 0, 0);
|
| - sqlite3VdbeAddOp3(pParse->pVdbe, OP_IdxDelete, iCur+i, r1,pIdx->nColumn+1);
|
| + int i; /* Index loop counter */
|
| + int r1 = -1; /* Register holding an index key */
|
| + int iPartIdxLabel; /* Jump destination for skipping partial index entries */
|
| + Index *pIdx; /* Current index */
|
| + Index *pPrior = 0; /* Prior index */
|
| + Vdbe *v; /* The prepared statement under construction */
|
| + Index *pPk; /* PRIMARY KEY index, or NULL for rowid tables */
|
| +
|
| + v = pParse->pVdbe;
|
| + 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;
|
| + 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);
|
| + sqlite3ResolvePartIdxLabel(pParse, iPartIdxLabel);
|
| + pPrior = pIdx;
|
| }
|
| }
|
|
|
| /*
|
| -** Generate code that will assemble an index key and put it in register
|
| +** Generate code that will assemble an index key and stores it in register
|
| ** regOut. The key with be for index pIdx which is an index on pTab.
|
| ** iCur is the index of a cursor open on the pTab table and pointing to
|
| -** the entry that needs indexing.
|
| +** the entry that needs indexing. If pTab is a WITHOUT ROWID table, then
|
| +** iCur must be the cursor of the PRIMARY KEY index.
|
| **
|
| ** Return a register number which is the first in a block of
|
| ** registers that holds the elements of the index key. The
|
| ** block of registers has already been deallocated by the time
|
| ** this routine returns.
|
| +**
|
| +** If *piPartIdxLabel is not NULL, fill it in with a label and jump
|
| +** to that label if pIdx is a partial index that should be skipped.
|
| +** The label should be resolved using sqlite3ResolvePartIdxLabel().
|
| +** A partial index should be skipped if its WHERE clause evaluates
|
| +** to false or null. If pIdx is not a partial index, *piPartIdxLabel
|
| +** will be set to zero which is an empty label that is ignored by
|
| +** sqlite3ResolvePartIdxLabel().
|
| +**
|
| +** The pPrior and regPrior parameters are used to implement a cache to
|
| +** avoid unnecessary register loads. If pPrior is not NULL, then it is
|
| +** a pointer to a different index for which an index key has just been
|
| +** computed into register regPrior. If the current pIdx index is generating
|
| +** its key into the same sequence of registers and if pPrior and pIdx share
|
| +** a column in common, then the register corresponding to that column already
|
| +** holds the correct value and the loading of that register is skipped.
|
| +** This optimization is helpful when doing a DELETE or an INTEGRITY_CHECK
|
| +** on a table with multiple indices, and especially with the ROWID or
|
| +** PRIMARY KEY columns of the index.
|
| */
|
| int sqlite3GenerateIndexKey(
|
| - Parse *pParse, /* Parsing context */
|
| - Index *pIdx, /* The index for which to generate a key */
|
| - int iCur, /* Cursor number for the pIdx->pTable table */
|
| - int regOut, /* Write the new index key to this register */
|
| - int doMakeRec /* Run the OP_MakeRecord instruction if true */
|
| + Parse *pParse, /* Parsing context */
|
| + Index *pIdx, /* The index for which to generate a key */
|
| + int iDataCur, /* Cursor number from which to take column data */
|
| + int regOut, /* Put the new key into this register if not 0 */
|
| + int prefixOnly, /* Compute only a unique prefix of the key */
|
| + int *piPartIdxLabel, /* OUT: Jump to this label to skip partial index */
|
| + Index *pPrior, /* Previously generated index key */
|
| + int regPrior /* Register holding previous generated key */
|
| ){
|
| Vdbe *v = pParse->pVdbe;
|
| int j;
|
| @@ -622,22 +793,47 @@ int sqlite3GenerateIndexKey(
|
| int regBase;
|
| int nCol;
|
|
|
| - nCol = pIdx->nColumn;
|
| - regBase = sqlite3GetTempRange(pParse, nCol+1);
|
| - sqlite3VdbeAddOp2(v, OP_Rowid, iCur, regBase+nCol);
|
| - for(j=0; j<nCol; j++){
|
| - int idx = pIdx->aiColumn[j];
|
| - if( idx==pTab->iPKey ){
|
| - sqlite3VdbeAddOp2(v, OP_SCopy, regBase+nCol, regBase+j);
|
| + if( piPartIdxLabel ){
|
| + if( pIdx->pPartIdxWhere ){
|
| + *piPartIdxLabel = sqlite3VdbeMakeLabel(v);
|
| + pParse->iPartIdxTab = iDataCur;
|
| + sqlite3ExprCachePush(pParse);
|
| + sqlite3ExprIfFalse(pParse, pIdx->pPartIdxWhere, *piPartIdxLabel,
|
| + SQLITE_JUMPIFNULL);
|
| }else{
|
| - sqlite3VdbeAddOp3(v, OP_Column, iCur, idx, regBase+j);
|
| - sqlite3ColumnDefault(v, pTab, idx, -1);
|
| + *piPartIdxLabel = 0;
|
| }
|
| }
|
| - if( doMakeRec ){
|
| - sqlite3VdbeAddOp3(v, OP_MakeRecord, regBase, nCol+1, regOut);
|
| - sqlite3VdbeChangeP4(v, -1, sqlite3IndexAffinityStr(v, pIdx), P4_TRANSIENT);
|
| + nCol = (prefixOnly && pIdx->uniqNotNull) ? pIdx->nKeyCol : pIdx->nColumn;
|
| + regBase = sqlite3GetTempRange(pParse, nCol);
|
| + if( pPrior && (regBase!=regPrior || pPrior->pPartIdxWhere) ) pPrior = 0;
|
| + for(j=0; j<nCol; j++){
|
| + if( pPrior && pPrior->aiColumn[j]==pIdx->aiColumn[j] ) continue;
|
| + sqlite3ExprCodeGetColumnOfTable(v, pTab, iDataCur, pIdx->aiColumn[j],
|
| + regBase+j);
|
| + /* If the column affinity is REAL but the number is an integer, then it
|
| + ** might be stored in the table as an integer (using a compact
|
| + ** representation) then converted to REAL by an OP_RealAffinity opcode.
|
| + ** But we are getting ready to store this value back into an index, where
|
| + ** it should be converted by to INTEGER again. So omit the OP_RealAffinity
|
| + ** opcode if it is present */
|
| + sqlite3VdbeDeletePriorOpcode(v, OP_RealAffinity);
|
| + }
|
| + if( regOut ){
|
| + sqlite3VdbeAddOp3(v, OP_MakeRecord, regBase, nCol, regOut);
|
| }
|
| - sqlite3ReleaseTempRange(pParse, regBase, nCol+1);
|
| + sqlite3ReleaseTempRange(pParse, regBase, nCol);
|
| return regBase;
|
| }
|
| +
|
| +/*
|
| +** If a prior call to sqlite3GenerateIndexKey() generated a jump-over label
|
| +** because it was a partial index, then this routine should be called to
|
| +** resolve that label.
|
| +*/
|
| +void sqlite3ResolvePartIdxLabel(Parse *pParse, int iLabel){
|
| + if( iLabel ){
|
| + sqlite3VdbeResolveLabel(pParse->pVdbe, iLabel);
|
| + sqlite3ExprCachePop(pParse);
|
| + }
|
| +}
|
|
|