750 lines
No EOL
25 KiB
C#
750 lines
No EOL
25 KiB
C#
using System.Diagnostics;
|
|
|
|
using u32 = System.UInt32;
|
|
|
|
namespace Community.CsharpSqlite
|
|
{
|
|
public partial class Sqlite3
|
|
{
|
|
/*
|
|
** 2001 September 15
|
|
**
|
|
** 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 contains C code routines that are called by the parser
|
|
** in order to generate code for DELETE FROM statements.
|
|
*************************************************************************
|
|
** Included in SQLite3 port to C#-SQLite; 2008 Noah B Hart
|
|
** C#-SQLite is an independent reimplementation of the SQLite software library
|
|
**
|
|
** SQLITE_SOURCE_ID: 2011-06-23 19:49:22 4374b7e83ea0a3fbc3691f9c0c936272862f32f2
|
|
**
|
|
*************************************************************************
|
|
*/
|
|
//#include "sqliteInt.h"
|
|
|
|
/*
|
|
** While a SrcList can in general represent multiple tables and subqueries
|
|
** (as in the FROM clause of a SELECT statement) in this case it contains
|
|
** the name of a single table, as one might find in an INSERT, DELETE,
|
|
** or UPDATE statement. Look up that table in the symbol table and
|
|
** return a pointer. Set an error message and return NULL if the table
|
|
** name is not found or if any other error occurs.
|
|
**
|
|
** The following fields are initialized appropriate in pSrc:
|
|
**
|
|
** pSrc->a[0].pTab Pointer to the Table object
|
|
** pSrc->a[0].pIndex Pointer to the INDEXED BY index, if there is one
|
|
**
|
|
*/
|
|
|
|
private static Table sqlite3SrcListLookup(Parse pParse, SrcList pSrc)
|
|
{
|
|
SrcList_item pItem = pSrc.a[0];
|
|
Table pTab;
|
|
Debug.Assert(pItem != null && pSrc.nSrc == 1);
|
|
pTab = sqlite3LocateTable(pParse, 0, pItem.zName, pItem.zDatabase);
|
|
sqlite3DeleteTable(pParse.db, ref pItem.pTab);
|
|
pItem.pTab = pTab;
|
|
if (pTab != null)
|
|
{
|
|
pTab.nRef++;
|
|
}
|
|
if (sqlite3IndexedByLookup(pParse, pItem) != 0)
|
|
{
|
|
pTab = null;
|
|
}
|
|
return pTab;
|
|
}
|
|
|
|
/*
|
|
** Check to make sure the given table is writable. If it is not
|
|
** writable, generate an error message and return 1. If it is
|
|
** writable return 0;
|
|
*/
|
|
|
|
private static bool sqlite3IsReadOnly(Parse pParse, Table pTab, int viewOk)
|
|
{
|
|
/* A table is not writable under the following circumstances:
|
|
**
|
|
** 1) It is a virtual table and no implementation of the xUpdate method
|
|
** has been provided, or
|
|
** 2) It is a system table (i.e. sqlite_master), this call is not
|
|
** part of a nested parse and writable_schema pragma has not
|
|
** been specified.
|
|
**
|
|
** In either case leave an error message in pParse and return non-zero.
|
|
*/
|
|
if (
|
|
(IsVirtual(pTab)
|
|
&& sqlite3GetVTable(pParse.db, pTab).pMod.pModule.xUpdate == null)
|
|
|| ((pTab.tabFlags & TF_Readonly) != 0
|
|
&& (pParse.db.flags & SQLITE_WriteSchema) == 0
|
|
&& pParse.nested == 0)
|
|
)
|
|
{
|
|
sqlite3ErrorMsg(pParse, "table %s may not be modified", pTab.zName);
|
|
return true;
|
|
}
|
|
|
|
#if !SQLITE_OMIT_VIEW
|
|
if (viewOk == 0 && pTab.pSelect != null)
|
|
{
|
|
sqlite3ErrorMsg(pParse, "cannot modify %s because it is a view", pTab.zName);
|
|
return true;
|
|
}
|
|
#endif
|
|
return false;
|
|
}
|
|
|
|
#if !SQLITE_OMIT_VIEW && !SQLITE_OMIT_TRIGGER
|
|
/*
|
|
** Evaluate a view and store its result in an ephemeral table. The
|
|
** pWhere argument is an optional WHERE clause that restricts the
|
|
** set of rows in the view that are to be added to the ephemeral table.
|
|
*/
|
|
|
|
private static void sqlite3MaterializeView(
|
|
Parse pParse, /* Parsing context */
|
|
Table pView, /* View definition */
|
|
Expr pWhere, /* Optional WHERE clause to be added */
|
|
int iCur /* VdbeCursor number for ephemerial table */
|
|
)
|
|
{
|
|
SelectDest dest = new SelectDest();
|
|
Select pDup;
|
|
sqlite3 db = pParse.db;
|
|
|
|
pDup = sqlite3SelectDup(db, pView.pSelect, 0);
|
|
if (pWhere != null)
|
|
{
|
|
SrcList pFrom;
|
|
|
|
pWhere = sqlite3ExprDup(db, pWhere, 0);
|
|
pFrom = sqlite3SrcListAppend(db, null, null, null);
|
|
//if ( pFrom != null )
|
|
//{
|
|
Debug.Assert(pFrom.nSrc == 1);
|
|
pFrom.a[0].zAlias = pView.zName;// sqlite3DbStrDup( db, pView.zName );
|
|
pFrom.a[0].pSelect = pDup;
|
|
Debug.Assert(pFrom.a[0].pOn == null);
|
|
Debug.Assert(pFrom.a[0].pUsing == null);
|
|
//}
|
|
//else
|
|
//{
|
|
// sqlite3SelectDelete( db, ref pDup );
|
|
//}
|
|
pDup = sqlite3SelectNew(pParse, null, pFrom, pWhere, null, null, null, 0, null, null);
|
|
}
|
|
sqlite3SelectDestInit(dest, SRT_EphemTab, iCur);
|
|
sqlite3Select(pParse, pDup, ref dest);
|
|
sqlite3SelectDelete(db, ref pDup);
|
|
}
|
|
|
|
#endif //* !SQLITE_OMIT_VIEW) && !SQLITE_OMIT_TRIGGER) */
|
|
|
|
#if (SQLITE_ENABLE_UPDATE_DELETE_LIMIT) && !(SQLITE_OMIT_SUBQUERY)
|
|
/*
|
|
** Generate an expression tree to implement the WHERE, ORDER BY,
|
|
** and LIMIT/OFFSET portion of DELETE and UPDATE statements.
|
|
**
|
|
** DELETE FROM table_wxyz WHERE a<5 ORDER BY a LIMIT 1;
|
|
** \__________________________/
|
|
** pLimitWhere (pInClause)
|
|
*/
|
|
Expr sqlite3LimitWhere(
|
|
Parse pParse, /* The parser context */
|
|
SrcList pSrc, /* the FROM clause -- which tables to scan */
|
|
Expr pWhere, /* The WHERE clause. May be null */
|
|
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. */
|
|
){
|
|
Expr pWhereRowid = null; /* WHERE rowid .. */
|
|
Expr pInClause = null; /* WHERE rowid IN ( select ) */
|
|
Expr pSelectRowid = null; /* SELECT rowid ... */
|
|
ExprList pEList = null; /* Expression list contaning only pSelectRowid */
|
|
SrcList pSelectSrc = null; /* SELECT rowid FROM x ... (dup of pSrc) */
|
|
Select pSelect = null; /* Complete SELECT tree */
|
|
|
|
/* Check that there isn't an ORDER BY without a LIMIT clause.
|
|
*/
|
|
if( pOrderBy!=null && (pLimit == null) ) {
|
|
sqlite3ErrorMsg(pParse, "ORDER BY without LIMIT on %s", zStmtType);
|
|
pParse.parseError = 1;
|
|
goto limit_where_cleanup_2;
|
|
}
|
|
|
|
/* We only need to generate a select expression if there
|
|
** is a limit/offset term to enforce.
|
|
*/
|
|
if ( pLimit == null )
|
|
{
|
|
/* if pLimit is null, pOffset will always be null as well. */
|
|
Debug.Assert( pOffset == null );
|
|
return pWhere;
|
|
}
|
|
|
|
/* Generate a select expression tree to enforce the limit/offset
|
|
** term for the DELETE or UPDATE statement. For example:
|
|
** DELETE FROM table_a WHERE col1=1 ORDER BY col2 LIMIT 1 OFFSET 1
|
|
** becomes:
|
|
** DELETE FROM table_a WHERE rowid IN (
|
|
** SELECT rowid FROM table_a WHERE col1=1 ORDER BY col2 LIMIT 1 OFFSET 1
|
|
** );
|
|
*/
|
|
|
|
pSelectRowid = sqlite3PExpr( pParse, TK_ROW, null, null, null );
|
|
if( pSelectRowid == null ) goto limit_where_cleanup_2;
|
|
pEList = sqlite3ExprListAppend( pParse, null, pSelectRowid);
|
|
if( pEList == null ) goto limit_where_cleanup_2;
|
|
|
|
/* duplicate the FROM clause as it is needed by both the DELETE/UPDATE tree
|
|
** and the SELECT subtree. */
|
|
pSelectSrc = sqlite3SrcListDup(pParse.db, pSrc,0);
|
|
if( pSelectSrc == null ) {
|
|
sqlite3ExprListDelete(pParse.db, pEList);
|
|
goto limit_where_cleanup_2;
|
|
}
|
|
|
|
/* generate the SELECT expression tree. */
|
|
pSelect = sqlite3SelectNew( pParse, pEList, pSelectSrc, pWhere, null, null,
|
|
pOrderBy, 0, pLimit, pOffset );
|
|
if( pSelect == null ) return null;
|
|
|
|
/* now generate the new WHERE rowid IN clause for the DELETE/UDPATE */
|
|
pWhereRowid = sqlite3PExpr( pParse, TK_ROW, null, null, null );
|
|
if( pWhereRowid == null ) goto limit_where_cleanup_1;
|
|
pInClause = sqlite3PExpr( pParse, TK_IN, pWhereRowid, null, null );
|
|
if( pInClause == null ) goto limit_where_cleanup_1;
|
|
|
|
pInClause->x.pSelect = pSelect;
|
|
pInClause->flags |= EP_xIsSelect;
|
|
sqlite3ExprSetHeight(pParse, pInClause);
|
|
return pInClause;
|
|
|
|
/* something went wrong. clean up anything allocated. */
|
|
limit_where_cleanup_1:
|
|
sqlite3SelectDelete(pParse.db, pSelect);
|
|
return null;
|
|
|
|
limit_where_cleanup_2:
|
|
sqlite3ExprDelete(pParse.db, ref pWhere);
|
|
sqlite3ExprListDelete(pParse.db, pOrderBy);
|
|
sqlite3ExprDelete(pParse.db, ref pLimit);
|
|
sqlite3ExprDelete(pParse.db, ref pOffset);
|
|
return null;
|
|
}
|
|
#endif //* defined(SQLITE_ENABLE_UPDATE_DELETE_LIMIT) && !defined(SQLITE_OMIT_SUBQUERY) */
|
|
|
|
/*
|
|
** Generate code for a DELETE FROM statement.
|
|
**
|
|
** DELETE FROM table_wxyz WHERE a<5 AND b NOT NULL;
|
|
** \________/ \________________/
|
|
** pTabList pWhere
|
|
*/
|
|
|
|
private static void sqlite3DeleteFrom(
|
|
Parse pParse, /* The parser context */
|
|
SrcList pTabList, /* The table from which we should delete things */
|
|
Expr pWhere /* The WHERE clause. May be null */
|
|
)
|
|
{
|
|
Vdbe v; /* The virtual database engine */
|
|
Table pTab; /* The table from which records will be deleted */
|
|
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 VdbeCursor number for pTab */
|
|
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 */
|
|
|
|
#if !SQLITE_OMIT_TRIGGER
|
|
bool isView; /* True if attempting to delete from a view */
|
|
Trigger pTrigger; /* List of table triggers, if required */
|
|
#endif
|
|
sContext = new AuthContext();//memset(&sContext, 0, sizeof(sContext));
|
|
|
|
db = pParse.db;
|
|
if (pParse.nErr != 0 /*|| db.mallocFailed != 0 */ )
|
|
{
|
|
goto delete_from_cleanup;
|
|
}
|
|
Debug.Assert(pTabList.nSrc == 1);
|
|
|
|
/* Locate the table which we want to delete. This table has to be
|
|
** put in an SrcList structure because some of the subroutines we
|
|
** will be calling are designed to work with multiple tables and expect
|
|
** an SrcList* parameter instead of just a Table* parameter.
|
|
*/
|
|
pTab = sqlite3SrcListLookup(pParse, pTabList);
|
|
if (pTab == null)
|
|
goto delete_from_cleanup;
|
|
|
|
/* Figure out if we have any triggers and if the table being
|
|
** deleted from is a view
|
|
*/
|
|
#if !SQLITE_OMIT_TRIGGER
|
|
int iDummy;
|
|
pTrigger = sqlite3TriggersExist(pParse, pTab, TK_DELETE, null, out iDummy);
|
|
isView = pTab.pSelect != null;
|
|
#else
|
|
const Trigger pTrigger = null;
|
|
bool isView = false;
|
|
#endif
|
|
#if SQLITE_OMIT_VIEW
|
|
//# undef isView
|
|
isView = false;
|
|
#endif
|
|
|
|
/* If pTab is really a view, make sure it has been initialized.
|
|
*/
|
|
if (sqlite3ViewGetColumnNames(pParse, pTab) != 0)
|
|
{
|
|
goto delete_from_cleanup;
|
|
}
|
|
|
|
if (sqlite3IsReadOnly(pParse, pTab, (pTrigger != null ? 1 : 0)))
|
|
{
|
|
goto delete_from_cleanup;
|
|
}
|
|
iDb = sqlite3SchemaToIndex(db, pTab.pSchema);
|
|
Debug.Assert(iDb < db.nDb);
|
|
#if !SQLITE_OMIT_AUTHORIZATION
|
|
string zDb = db.aDb[iDb].zName; /* Name of database holding pTab */
|
|
rcauth = sqlite3AuthCheck(pParse, SQLITE_DELETE, pTab->zName, 0, zDb);
|
|
#else
|
|
rcauth = SQLITE_OK;
|
|
#endif
|
|
Debug.Assert(rcauth == SQLITE_OK || rcauth == SQLITE_DENY || rcauth == SQLITE_IGNORE);
|
|
if (rcauth == SQLITE_DENY)
|
|
{
|
|
goto delete_from_cleanup;
|
|
}
|
|
Debug.Assert(!isView || pTrigger != null);
|
|
|
|
/* Assign cursor number to the table and all its indices.
|
|
*/
|
|
Debug.Assert(pTabList.nSrc == 1);
|
|
iCur = pTabList.a[0].iCursor = pParse.nTab++;
|
|
for (pIdx = pTab.pIndex; pIdx != null; pIdx = pIdx.pNext)
|
|
{
|
|
pParse.nTab++;
|
|
}
|
|
|
|
#if !SQLITE_OMIT_AUTHORIZATION
|
|
/* Start the view context
|
|
*/
|
|
if( isView ){
|
|
sqlite3AuthContextPush(pParse, sContext, pTab.zName);
|
|
}
|
|
#endif
|
|
/* Begin generating code.
|
|
*/
|
|
v = sqlite3GetVdbe(pParse);
|
|
if (v == null)
|
|
{
|
|
goto delete_from_cleanup;
|
|
}
|
|
if (pParse.nested == 0)
|
|
sqlite3VdbeCountChanges(v);
|
|
sqlite3BeginWriteOperation(pParse, 1, iDb);
|
|
|
|
/* If we are trying to delete from a view, realize that view into
|
|
** a ephemeral table.
|
|
*/
|
|
#if !(SQLITE_OMIT_VIEW) && !(SQLITE_OMIT_TRIGGER)
|
|
if (isView)
|
|
{
|
|
sqlite3MaterializeView(pParse, pTab, pWhere, iCur);
|
|
}
|
|
#endif
|
|
/* Resolve the column names in the WHERE clause.
|
|
*/
|
|
sNC = new NameContext();// memset( &sNC, 0, sizeof( sNC ) );
|
|
sNC.pParse = pParse;
|
|
sNC.pSrcList = pTabList;
|
|
if (sqlite3ResolveExprNames(sNC, ref pWhere) != 0)
|
|
{
|
|
goto delete_from_cleanup;
|
|
}
|
|
|
|
/* Initialize the counter of the number of rows deleted, if
|
|
** we are counting rows.
|
|
*/
|
|
if ((db.flags & SQLITE_CountRows) != 0)
|
|
{
|
|
memCnt = ++pParse.nMem;
|
|
sqlite3VdbeAddOp2(v, OP_Integer, 0, memCnt);
|
|
}
|
|
|
|
#if !SQLITE_OMIT_TRUNCATE_OPTIMIZATION
|
|
/* Special case: A DELETE without a WHERE clause deletes everything.
|
|
** It is easier just to erase the whole table. Prior to version 3.6.5,
|
|
** this optimization caused the row change count (the value returned by
|
|
** API function sqlite3_count_changes) to be set incorrectly. */
|
|
if (rcauth == SQLITE_OK && pWhere == null && null == pTrigger && !IsVirtual(pTab)
|
|
&& 0 == sqlite3FkRequired(pParse, pTab, null, 0)
|
|
)
|
|
{
|
|
Debug.Assert(!isView);
|
|
sqlite3VdbeAddOp4(v, OP_Clear, pTab.tnum, iDb, memCnt,
|
|
pTab.zName, P4_STATIC);
|
|
for (pIdx = pTab.pIndex; pIdx != null; pIdx = pIdx.pNext)
|
|
{
|
|
Debug.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.
|
|
*/
|
|
sqlite3VdbeAddOp2(v, OP_Null, 0, iRowSet);
|
|
ExprList elDummy = null;
|
|
pWInfo = sqlite3WhereBegin(pParse, pTabList, pWhere, ref elDummy, WHERE_DUPLICATES_OK);
|
|
if (pWInfo == null)
|
|
goto delete_from_cleanup;
|
|
regRowid = sqlite3ExprCodeGetColumn(pParse, pTab, -1, iCur, iRowid);
|
|
sqlite3VdbeAddOp2(v, OP_RowSetAdd, iRowSet, regRowid);
|
|
if ((db.flags & SQLITE_CountRows) != 0)
|
|
{
|
|
sqlite3VdbeAddOp2(v, OP_AddImm, memCnt, 1);
|
|
}
|
|
|
|
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);
|
|
|
|
/* 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)
|
|
{
|
|
sqlite3OpenTableAndIndices(pParse, pTab, iCur, OP_OpenWrite);
|
|
}
|
|
|
|
addr = sqlite3VdbeAddOp3(v, OP_RowSetRead, iRowSet, end, iRowid);
|
|
|
|
/* Delete the row */
|
|
#if !SQLITE_OMIT_VIRTUALTABLE
|
|
if (IsVirtual(pTab))
|
|
{
|
|
VTable pVTab = sqlite3GetVTable(db, pTab);
|
|
sqlite3VtabMakeWritable(pParse, pTab);
|
|
sqlite3VdbeAddOp4(v, OP_VUpdate, 0, 1, iRowid, pVTab, P4_VTAB);
|
|
sqlite3VdbeChangeP5(v, OE_Abort);
|
|
sqlite3MayAbort(pParse);
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
int count = (pParse.nested == 0) ? 1 : 0; /* True to count changes */
|
|
sqlite3GenerateRowDelete(pParse, pTab, iCur, iRowid, count, pTrigger, OE_Default);
|
|
}
|
|
|
|
/* End of the delete loop */
|
|
sqlite3VdbeAddOp2(v, OP_Goto, 0, addr);
|
|
sqlite3VdbeResolveLabel(v, end);
|
|
|
|
/* Close the cursors open on the table and its indexes. */
|
|
if (!isView && !IsVirtual(pTab))
|
|
{
|
|
for (i = 1, pIdx = pTab.pIndex; pIdx != null; i++, pIdx = pIdx.pNext)
|
|
{
|
|
sqlite3VdbeAddOp2(v, OP_Close, iCur + i, pIdx.tnum);
|
|
}
|
|
sqlite3VdbeAddOp1(v, OP_Close, iCur);
|
|
}
|
|
}
|
|
|
|
/* Update the sqlite_sequence table by storing the content of the
|
|
** maximum rowid counter values recorded while inserting into
|
|
** autoincrement tables.
|
|
*/
|
|
if (pParse.nested == 0 && pParse.pTriggerTab == null)
|
|
{
|
|
sqlite3AutoincrementEnd(pParse);
|
|
}
|
|
|
|
/* Return the number of rows that were deleted. If this routine is
|
|
** generating code because of a call to sqlite3NestedParse(), do not
|
|
** invoke the callback function.
|
|
*/
|
|
|
|
if ((db.flags & SQLITE_CountRows) != 0 && 0 == pParse.nested && null == pParse.pTriggerTab)
|
|
{
|
|
sqlite3VdbeAddOp2(v, OP_ResultRow, memCnt, 1);
|
|
sqlite3VdbeSetNumCols(v, 1);
|
|
sqlite3VdbeSetColName(v, 0, COLNAME_NAME, "rows deleted", SQLITE_STATIC);
|
|
}
|
|
|
|
delete_from_cleanup:
|
|
#if !SQLITE_OMIT_AUTHORIZATION
|
|
sqlite3AuthContextPop(sContext);
|
|
#endif
|
|
sqlite3SrcListDelete(db, ref pTabList);
|
|
sqlite3ExprDelete(db, ref pWhere);
|
|
return;
|
|
}
|
|
|
|
/* Make sure "isView" and other macros defined above are undefined. Otherwise
|
|
** thely may interfere with compilation of other functions in this file
|
|
** (or in another file, if this file becomes part of the amalgamation). */
|
|
//#if isView
|
|
// #undef isView
|
|
//#endif
|
|
//#if pTrigger
|
|
// #undef pTrigger
|
|
//#endif
|
|
|
|
/*
|
|
** This routine generates VDBE code that causes a single row of a
|
|
** single table to be deleted.
|
|
**
|
|
** The VDBE must be in a particular state when this routine is called.
|
|
** These are the requirements:
|
|
**
|
|
** 1. A read/write cursor pointing to pTab, the table containing the row
|
|
** to be deleted, must be opened as cursor number $iCur.
|
|
**
|
|
** 2. Read/write cursors for all indices of pTab must be open as
|
|
** cursor number base+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.
|
|
*/
|
|
|
|
private static void sqlite3GenerateRowDelete(
|
|
Parse pParse, /* Parsing context */
|
|
Table pTab, /* Table containing the row to be deleted */
|
|
int iCur, /* VdbeCursor 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 */
|
|
)
|
|
{
|
|
Vdbe v = pParse.pVdbe; /* Vdbe */
|
|
int iOld = 0; /* First register in OLD.* array */
|
|
int iLabel; /* Label resolved to end of generated code */
|
|
|
|
/* Vdbe is guaranteed to have been allocated by this stage. */
|
|
Debug.Assert(v != null);
|
|
|
|
/* 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);
|
|
|
|
/* 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, null, 0) != 0 || pTrigger != null)
|
|
{
|
|
u32 mask; /* Mask of OLD.* columns in use */
|
|
int iCol; /* Iterator used while populating OLD.* */
|
|
|
|
/* TODO: Could use temporary registers here. Also could attempt to
|
|
** avoid copying the contents of the rowid register. */
|
|
mask = sqlite3TriggerColmask(
|
|
pParse, pTrigger, null, 0, TRIGGER_BEFORE | TRIGGER_AFTER, pTab, onconf
|
|
);
|
|
mask |= sqlite3FkOldmask(pParse, pTab);
|
|
iOld = pParse.nMem + 1;
|
|
pParse.nMem += (1 + pTab.nCol);
|
|
|
|
/* 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);
|
|
for (iCol = 0; iCol < pTab.nCol; iCol++)
|
|
{
|
|
if (mask == 0xffffffff || (mask & (1 << iCol)) != 0)
|
|
{
|
|
sqlite3ExprCodeGetColumnOfTable(v, pTab, iCur, iCol, iOld + iCol + 1);
|
|
}
|
|
}
|
|
|
|
/* Invoke BEFORE DELETE trigger programs. */
|
|
sqlite3CodeRowTrigger(pParse, pTrigger,
|
|
TK_DELETE, null, 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);
|
|
|
|
/* 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);
|
|
}
|
|
|
|
/* 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 == null)
|
|
{
|
|
sqlite3GenerateRowIndexDelete(pParse, pTab, iCur, 0);
|
|
sqlite3VdbeAddOp2(v, OP_Delete, iCur, (count != 0 ? (int)OPFLAG_NCHANGE : 0));
|
|
if (count != 0)
|
|
{
|
|
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
|
|
** to the row just deleted. */
|
|
sqlite3FkActions(pParse, pTab, null, iOld);
|
|
|
|
/* Invoke AFTER DELETE trigger programs. */
|
|
sqlite3CodeRowTrigger(pParse, pTrigger,
|
|
TK_DELETE, null, TRIGGER_AFTER, pTab, iOld, onconf, iLabel
|
|
);
|
|
|
|
/* Jump here if the row had already been deleted before any BEFORE
|
|
** trigger programs were invoked. Or if a trigger program throws a
|
|
** RAISE(IGNORE) exception. */
|
|
sqlite3VdbeResolveLabel(v, iLabel);
|
|
}
|
|
|
|
/*
|
|
** This routine generates VDBE code that causes the deletion of all
|
|
** index entries associated with a single row of a single table.
|
|
**
|
|
** The VDBE must be in a particular state when this routine is called.
|
|
** These are the requirements:
|
|
**
|
|
** 1. A read/write cursor pointing to pTab, the table containing the row
|
|
** to be deleted, must be opened as cursor number "iCur".
|
|
**
|
|
** 2. Read/write cursors for all indices of pTab must be open as
|
|
** cursor number iCur+i for the i-th index.
|
|
**
|
|
** 3. The "iCur" cursor must be pointing to the row that is to be
|
|
** deleted.
|
|
*/
|
|
|
|
private static void sqlite3GenerateRowIndexDelete(
|
|
Parse pParse, /* Parsing and code generating context */
|
|
Table pTab, /* Table containing the row to be deleted */
|
|
int iCur, /* VdbeCursor number for the table */
|
|
int nothing /* Only delete if aRegIdx!=0 && aRegIdx[i]>0 */
|
|
)
|
|
{
|
|
int[] aRegIdx = null;
|
|
sqlite3GenerateRowIndexDelete(pParse, pTab, iCur, aRegIdx);
|
|
}
|
|
|
|
private static void sqlite3GenerateRowIndexDelete(
|
|
Parse pParse, /* Parsing and code generating context */
|
|
Table pTab, /* Table containing the row to be deleted */
|
|
int iCur, /* VdbeCursor number for the table */
|
|
int[] aRegIdx /* Only delete if aRegIdx!=0 && aRegIdx[i]>0 */
|
|
)
|
|
{
|
|
int i;
|
|
Index pIdx;
|
|
int r1;
|
|
|
|
for (i = 1, pIdx = pTab.pIndex; pIdx != null; i++, pIdx = pIdx.pNext)
|
|
{
|
|
if (aRegIdx != null && aRegIdx[i - 1] == 0)
|
|
continue;
|
|
r1 = sqlite3GenerateIndexKey(pParse, pIdx, iCur, 0, false);
|
|
sqlite3VdbeAddOp3(pParse.pVdbe, OP_IdxDelete, iCur + i, r1, pIdx.nColumn + 1);
|
|
}
|
|
}
|
|
|
|
/*
|
|
** Generate code that will assemble an index key and put 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.
|
|
**
|
|
** 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.
|
|
*/
|
|
|
|
private static int sqlite3GenerateIndexKey(
|
|
Parse pParse, /* Parsing context */
|
|
Index pIdx, /* The index for which to generate a key */
|
|
int iCur, /* VdbeCursor number for the pIdx.pTable table */
|
|
int regOut, /* Write the new index key to this register */
|
|
bool doMakeRec /* Run the OP_MakeRecord instruction if true */
|
|
)
|
|
{
|
|
Vdbe v = pParse.pVdbe;
|
|
int j;
|
|
Table pTab = pIdx.pTable;
|
|
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);
|
|
}
|
|
else
|
|
{
|
|
sqlite3VdbeAddOp3(v, OP_Column, iCur, idx, regBase + j);
|
|
sqlite3ColumnDefault(v, pTab, idx, -1);
|
|
}
|
|
}
|
|
if (doMakeRec)
|
|
{
|
|
string zAff;
|
|
if (pTab.pSelect != null || (pParse.db.flags & SQLITE_IdxRealAsInt) != 0)
|
|
{
|
|
zAff = "";
|
|
}
|
|
else
|
|
{
|
|
zAff = sqlite3IndexAffinityStr(v, pIdx);
|
|
}
|
|
sqlite3VdbeAddOp3(v, OP_MakeRecord, regBase, nCol + 1, regOut);
|
|
sqlite3VdbeChangeP4(v, -1, zAff, P4_TRANSIENT);
|
|
}
|
|
sqlite3ReleaseTempRange(pParse, regBase, nCol + 1);
|
|
return regBase;
|
|
}
|
|
}
|
|
} |