summaryrefslogtreecommitdiff
path: root/ext/sqlite/libsqlite/src/update.c
diff options
context:
space:
mode:
Diffstat (limited to 'ext/sqlite/libsqlite/src/update.c')
-rw-r--r--ext/sqlite/libsqlite/src/update.c358
1 files changed, 195 insertions, 163 deletions
diff --git a/ext/sqlite/libsqlite/src/update.c b/ext/sqlite/libsqlite/src/update.c
index b5825dd12e..95a49590dc 100644
--- a/ext/sqlite/libsqlite/src/update.c
+++ b/ext/sqlite/libsqlite/src/update.c
@@ -18,81 +18,76 @@
/*
** Process an UPDATE statement.
+**
+** UPDATE OR IGNORE table_wxyz SET a=b, c=d WHERE e<5 AND f NOT NULL;
+** \_______/ \________/ \______/ \________________/
+* onError pTabList pChanges pWhere
*/
void sqliteUpdate(
Parse *pParse, /* The parser context */
- Token *pTableName, /* The table in which we should change things */
+ SrcList *pTabList, /* The table in which we should change things */
ExprList *pChanges, /* Things to be changed */
Expr *pWhere, /* The WHERE clause. May be null */
int onError /* How to handle constraint errors */
){
int i, j; /* Loop counters */
Table *pTab; /* The table to be updated */
- SrcList *pTabList = 0; /* Fake FROM clause containing only pTab */
int addr; /* VDBE instruction address of the start of the loop */
WhereInfo *pWInfo; /* Information about the WHERE clause */
Vdbe *v; /* The virtual database engine */
Index *pIdx; /* For looping over indices */
int nIdx; /* Number of indices that need updating */
int nIdxTotal; /* Total number of indices */
- int base; /* Index of first available table cursor */
+ int iCur; /* VDBE Cursor number of pTab */
sqlite *db; /* The database structure */
Index **apIdx = 0; /* An array of indices that need updating too */
- char *aIdxUsed = 0; /* aIdxUsed[i] if the i-th index is used */
+ char *aIdxUsed = 0; /* aIdxUsed[i]==1 if the i-th index is used */
int *aXRef = 0; /* aXRef[i] is the index in pChanges->a[] of the
** an expression for the i-th column of the table.
** aXRef[i]==-1 if the i-th column is not changed. */
- int openOp; /* Opcode used to open tables */
int chngRecno; /* True if the record number is being changed */
Expr *pRecnoExpr; /* Expression defining the new record number */
int openAll; /* True if all indices need to be opened */
+ int isView; /* Trying to update a view */
+ AuthContext sContext; /* The authorization context */
- int row_triggers_exist = 0;
+ int before_triggers; /* True if there are any BEFORE triggers */
+ int after_triggers; /* True if there are any AFTER triggers */
+ int row_triggers_exist = 0; /* True if any row triggers exist */
int newIdx = -1; /* index of trigger "new" temp table */
int oldIdx = -1; /* index of trigger "old" temp table */
+ sContext.pParse = 0;
if( pParse->nErr || sqlite_malloc_failed ) goto update_cleanup;
db = pParse->db;
+ assert( pTabList->nSrc==1 );
- /* Check for the special case of a VIEW with one or more ON UPDATE triggers
- * defined
- */
- {
- char *zTab = sqliteTableNameFromToken(pTableName);
-
- if( zTab != 0 ){
- pTab = sqliteFindTable(pParse->db, zTab);
- if( pTab ){
- row_triggers_exist =
- sqliteTriggersExist(pParse, pTab->pTrigger,
- TK_UPDATE, TK_BEFORE, TK_ROW, pChanges) ||
- sqliteTriggersExist(pParse, pTab->pTrigger,
- TK_UPDATE, TK_AFTER, TK_ROW, pChanges);
- }
- sqliteFree(zTab);
- if( row_triggers_exist && pTab->pSelect ){
- /* Just fire VIEW triggers */
- sqliteViewTriggers(pParse, pTab, pWhere, onError, pChanges);
- return;
- }
+ /* Locate the table which we want to update.
+ */
+ pTab = sqliteSrcListLookup(pParse, pTabList);
+ if( pTab==0 ) goto update_cleanup;
+ before_triggers = sqliteTriggersExist(pParse, pTab->pTrigger,
+ TK_UPDATE, TK_BEFORE, TK_ROW, pChanges);
+ after_triggers = sqliteTriggersExist(pParse, pTab->pTrigger,
+ TK_UPDATE, TK_AFTER, TK_ROW, pChanges);
+ row_triggers_exist = before_triggers || after_triggers;
+ isView = pTab->pSelect!=0;
+ if( sqliteIsReadOnly(pParse, pTab, before_triggers) ){
+ goto update_cleanup;
+ }
+ if( isView ){
+ if( sqliteViewGetColumnNames(pParse, pTab) ){
+ goto update_cleanup;
}
}
-
- /* Locate the table which we want to update. 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.
- */
- pTabList = sqliteTableTokenToSrcList(pParse, pTableName);
- if( pTabList==0 ) goto update_cleanup;
- pTab = pTabList->a[0].pTab;
- assert( pTab->pSelect==0 ); /* This table is not a VIEW */
aXRef = sqliteMalloc( sizeof(int) * pTab->nCol );
if( aXRef==0 ) goto update_cleanup;
for(i=0; i<pTab->nCol; i++) aXRef[i] = -1;
- /* If there are FOR EACH ROW triggers, allocate temp tables */
+ /* If there are FOR EACH ROW triggers, allocate cursors for the
+ ** special OLD and NEW tables
+ */
if( row_triggers_exist ){
newIdx = pParse->nTab++;
oldIdx = pParse->nTab++;
@@ -103,28 +98,20 @@ void sqliteUpdate(
** need to occur right after the database cursor. So go ahead and
** allocate enough space, just in case.
*/
- base = pParse->nTab++;
+ pTabList->a[0].iCursor = iCur = pParse->nTab++;
for(pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext){
pParse->nTab++;
}
- /* Resolve the column names in all the expressions in both the
- ** WHERE clause and in the new values. Also find the column index
+ /* Resolve the column names in all the expressions of the
+ ** of the UPDATE statement. Also find the column index
** for each column to be updated in the pChanges array. For each
** column to be updated, make sure we have authorization to change
** that column.
*/
- if( pWhere ){
- if( sqliteExprResolveIds(pParse, base, pTabList, 0, pWhere) ){
- goto update_cleanup;
- }
- if( sqliteExprCheck(pParse, pWhere, 0, 0) ){
- goto update_cleanup;
- }
- }
chngRecno = 0;
for(i=0; i<pChanges->nExpr; i++){
- if( sqliteExprResolveIds(pParse, base, pTabList, 0, pChanges->a[i].pExpr) ){
+ if( sqliteExprResolveIds(pParse, pTabList, 0, pChanges->a[i].pExpr) ){
goto update_cleanup;
}
if( sqliteExprCheck(pParse, pChanges->a[i].pExpr, 0, 0) ){
@@ -141,16 +128,14 @@ void sqliteUpdate(
}
}
if( j>=pTab->nCol ){
- sqliteSetString(&pParse->zErrMsg, "no such column: ",
- pChanges->a[i].zName, 0);
- pParse->nErr++;
+ sqliteErrorMsg(pParse, "no such column: %s", pChanges->a[i].zName);
goto update_cleanup;
}
#ifndef SQLITE_OMIT_AUTHORIZATION
{
int rc;
rc = sqliteAuthCheck(pParse, SQLITE_UPDATE, pTab->zName,
- pTab->aCol[j].zName);
+ pTab->aCol[j].zName, db->aDb[pTab->iDb].zName);
if( rc==SQLITE_DENY ){
goto update_cleanup;
}else if( rc==SQLITE_IGNORE ){
@@ -196,15 +181,43 @@ void sqliteUpdate(
}
}
+ /* Resolve the column names in all the expressions in the
+ ** WHERE clause.
+ */
+ if( pWhere ){
+ if( sqliteExprResolveIds(pParse, pTabList, 0, pWhere) ){
+ goto update_cleanup;
+ }
+ if( sqliteExprCheck(pParse, pWhere, 0, 0) ){
+ goto update_cleanup;
+ }
+ }
+
+ /* Start the view context
+ */
+ if( isView ){
+ sqliteAuthContextPush(pParse, &sContext, pTab->zName);
+ }
+
/* Begin generating code.
*/
v = sqliteGetVdbe(pParse);
if( v==0 ) goto update_cleanup;
- sqliteBeginWriteOperation(pParse, 1, !row_triggers_exist && pTab->isTemp);
+ sqliteBeginWriteOperation(pParse, 1, pTab->iDb);
+
+ /* If we are trying to update a view, construct that view into
+ ** a temporary table.
+ */
+ if( isView ){
+ Select *pView;
+ pView = sqliteSelectDup(pTab->pSelect);
+ sqliteSelect(pParse, pView, SRT_TempTable, iCur, 0, 0, 0);
+ sqliteSelectDelete(pView);
+ }
/* Begin the database scan
*/
- pWInfo = sqliteWhereBegin(pParse, base, pTabList, pWhere, 1, 0);
+ pWInfo = sqliteWhereBegin(pParse, pTabList, pWhere, 1, 0);
if( pWInfo==0 ) goto update_cleanup;
/* Remember the index of every item to be updated.
@@ -222,136 +235,150 @@ void sqliteUpdate(
}
if( row_triggers_exist ){
- int ii;
-
- sqliteVdbeAddOp(v, OP_OpenTemp, oldIdx, 0);
- sqliteVdbeAddOp(v, OP_OpenTemp, newIdx, 0);
+ /* Create pseudo-tables for NEW and OLD
+ */
+ sqliteVdbeAddOp(v, OP_OpenPseudo, oldIdx, 0);
+ sqliteVdbeAddOp(v, OP_OpenPseudo, newIdx, 0);
+ /* The top of the update loop for when there are triggers.
+ */
sqliteVdbeAddOp(v, OP_ListRewind, 0, 0);
addr = sqliteVdbeAddOp(v, OP_ListRead, 0, 0);
sqliteVdbeAddOp(v, OP_Dup, 0, 0);
+ /* Open a cursor and make it point to the record that is
+ ** being updated.
+ */
sqliteVdbeAddOp(v, OP_Dup, 0, 0);
- sqliteVdbeAddOp(v, (pTab->isTemp?OP_OpenAux:OP_Open), base, pTab->tnum);
- sqliteVdbeAddOp(v, OP_MoveTo, base, 0);
-
- sqliteVdbeAddOp(v, OP_Integer, 13, 0);
- for(ii = 0; ii < pTab->nCol; ii++){
- if( ii == pTab->iPKey ){
- sqliteVdbeAddOp(v, OP_Recno, base, 0);
- }else{
- sqliteVdbeAddOp(v, OP_Column, base, ii);
- }
+ if( !isView ){
+ sqliteVdbeAddOp(v, OP_Integer, pTab->iDb, 0);
+ sqliteVdbeAddOp(v, OP_OpenRead, iCur, pTab->tnum);
}
- sqliteVdbeAddOp(v, OP_MakeRecord, pTab->nCol, 0);
+ sqliteVdbeAddOp(v, OP_MoveTo, iCur, 0);
+
+ /* Generate the OLD table
+ */
+ sqliteVdbeAddOp(v, OP_Recno, iCur, 0);
+ sqliteVdbeAddOp(v, OP_RowData, iCur, 0);
sqliteVdbeAddOp(v, OP_PutIntKey, oldIdx, 0);
- sqliteVdbeAddOp(v, OP_Integer, 13, 0);
- for(ii = 0; ii < pTab->nCol; ii++){
- if( aXRef[ii] < 0 ){
- if( ii == pTab->iPKey ){
- sqliteVdbeAddOp(v, OP_Recno, base, 0);
- }else{
- sqliteVdbeAddOp(v, OP_Column, base, ii);
- }
+ /* Generate the NEW table
+ */
+ if( chngRecno ){
+ sqliteExprCode(pParse, pRecnoExpr);
+ }else{
+ sqliteVdbeAddOp(v, OP_Recno, iCur, 0);
+ }
+ for(i=0; i<pTab->nCol; i++){
+ if( i==pTab->iPKey ){
+ sqliteVdbeAddOp(v, OP_String, 0, 0);
+ continue;
+ }
+ j = aXRef[i];
+ if( j<0 ){
+ sqliteVdbeAddOp(v, OP_Column, iCur, i);
}else{
- sqliteExprCode(pParse, pChanges->a[aXRef[ii]].pExpr);
+ sqliteExprCode(pParse, pChanges->a[j].pExpr);
}
}
sqliteVdbeAddOp(v, OP_MakeRecord, pTab->nCol, 0);
sqliteVdbeAddOp(v, OP_PutIntKey, newIdx, 0);
- sqliteVdbeAddOp(v, OP_Close, base, 0);
-
- sqliteVdbeAddOp(v, OP_Rewind, oldIdx, 0);
- sqliteVdbeAddOp(v, OP_Rewind, newIdx, 0);
+ if( !isView ){
+ sqliteVdbeAddOp(v, OP_Close, iCur, 0);
+ }
+ /* Fire the BEFORE and INSTEAD OF triggers
+ */
if( sqliteCodeRowTrigger(pParse, TK_UPDATE, pChanges, TK_BEFORE, pTab,
newIdx, oldIdx, onError, addr) ){
goto update_cleanup;
}
}
- /* Rewind the list of records that need to be updated and
- ** open every index that needs updating. Note that if any
- ** index could potentially invoke a REPLACE conflict resolution
- ** action, then we need to open all indices because we might need
- ** to be deleting some records.
- */
- openOp = pTab->isTemp ? OP_OpenWrAux : OP_OpenWrite;
- sqliteVdbeAddOp(v, openOp, base, pTab->tnum);
- if( onError==OE_Replace ){
- openAll = 1;
- }else{
- openAll = 0;
- for(pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext){
- if( pIdx->onError==OE_Replace ){
- openAll = 1;
- break;
+ if( !isView ){
+ /*
+ ** Open every index that needs updating. Note that if any
+ ** index could potentially invoke a REPLACE conflict resolution
+ ** action, then we need to open all indices because we might need
+ ** to be deleting some records.
+ */
+ sqliteVdbeAddOp(v, OP_Integer, pTab->iDb, 0);
+ sqliteVdbeAddOp(v, OP_OpenWrite, iCur, pTab->tnum);
+ if( onError==OE_Replace ){
+ openAll = 1;
+ }else{
+ openAll = 0;
+ for(pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext){
+ if( pIdx->onError==OE_Replace ){
+ openAll = 1;
+ break;
+ }
}
}
- }
- for(i=0, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext, i++){
- if( openAll || aIdxUsed[i] ){
- sqliteVdbeAddOp(v, openOp, base+i+1, pIdx->tnum);
- assert( pParse->nTab>base+i+1 );
+ for(i=0, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext, i++){
+ if( openAll || aIdxUsed[i] ){
+ sqliteVdbeAddOp(v, OP_Integer, pIdx->iDb, 0);
+ sqliteVdbeAddOp(v, OP_OpenWrite, iCur+i+1, pIdx->tnum);
+ assert( pParse->nTab>iCur+i+1 );
+ }
}
- }
- /* Loop over every record that needs updating. We have to load
- ** the old data for each record to be updated because some columns
- ** might not change and we will need to copy the old value.
- ** Also, the old data is needed to delete the old index entires.
- ** So make the cursor point at the old record.
- */
- if( !row_triggers_exist ){
- sqliteVdbeAddOp(v, OP_ListRewind, 0, 0);
- addr = sqliteVdbeAddOp(v, OP_ListRead, 0, 0);
- sqliteVdbeAddOp(v, OP_Dup, 0, 0);
- }
- sqliteVdbeAddOp(v, OP_NotExists, base, addr);
-
- /* If the record number will change, push the record number as it
- ** will be after the update. (The old record number is currently
- ** on top of the stack.)
- */
- if( chngRecno ){
- sqliteExprCode(pParse, pRecnoExpr);
- sqliteVdbeAddOp(v, OP_MustBeInt, 0, 0);
- }
+ /* Loop over every record that needs updating. We have to load
+ ** the old data for each record to be updated because some columns
+ ** might not change and we will need to copy the old value.
+ ** Also, the old data is needed to delete the old index entires.
+ ** So make the cursor point at the old record.
+ */
+ if( !row_triggers_exist ){
+ sqliteVdbeAddOp(v, OP_ListRewind, 0, 0);
+ addr = sqliteVdbeAddOp(v, OP_ListRead, 0, 0);
+ sqliteVdbeAddOp(v, OP_Dup, 0, 0);
+ }
+ sqliteVdbeAddOp(v, OP_NotExists, iCur, addr);
- /* Compute new data for this record.
- */
- for(i=0; i<pTab->nCol; i++){
- if( i==pTab->iPKey ){
- sqliteVdbeAddOp(v, OP_String, 0, 0);
- continue;
+ /* If the record number will change, push the record number as it
+ ** will be after the update. (The old record number is currently
+ ** on top of the stack.)
+ */
+ if( chngRecno ){
+ sqliteExprCode(pParse, pRecnoExpr);
+ sqliteVdbeAddOp(v, OP_MustBeInt, 0, 0);
}
- j = aXRef[i];
- if( j<0 ){
- sqliteVdbeAddOp(v, OP_Column, base, i);
- }else{
- sqliteExprCode(pParse, pChanges->a[j].pExpr);
+
+ /* Compute new data for this record.
+ */
+ for(i=0; i<pTab->nCol; i++){
+ if( i==pTab->iPKey ){
+ sqliteVdbeAddOp(v, OP_String, 0, 0);
+ continue;
+ }
+ j = aXRef[i];
+ if( j<0 ){
+ sqliteVdbeAddOp(v, OP_Column, iCur, i);
+ }else{
+ sqliteExprCode(pParse, pChanges->a[j].pExpr);
+ }
}
- }
- /* Do constraint checks
- */
- sqliteGenerateConstraintChecks(pParse, pTab, base, aIdxUsed, chngRecno, 1,
- onError, addr);
+ /* Do constraint checks
+ */
+ sqliteGenerateConstraintChecks(pParse, pTab, iCur, aIdxUsed, chngRecno, 1,
+ onError, addr);
- /* Delete the old indices for the current record.
- */
- sqliteGenerateRowIndexDelete(db, v, pTab, base, aIdxUsed);
+ /* Delete the old indices for the current record.
+ */
+ sqliteGenerateRowIndexDelete(db, v, pTab, iCur, aIdxUsed);
- /* If changing the record number, delete the old record.
- */
- if( chngRecno ){
- sqliteVdbeAddOp(v, OP_Delete, base, 0);
- }
+ /* If changing the record number, delete the old record.
+ */
+ if( chngRecno ){
+ sqliteVdbeAddOp(v, OP_Delete, iCur, 0);
+ }
- /* Create the new index entries and the new record.
- */
- sqliteCompleteInsertion(pParse, pTab, base, aIdxUsed, chngRecno, 1);
+ /* Create the new index entries and the new record.
+ */
+ sqliteCompleteInsertion(pParse, pTab, iCur, aIdxUsed, chngRecno, 1, -1);
+ }
/* Increment the row counter
*/
@@ -359,14 +386,18 @@ void sqliteUpdate(
sqliteVdbeAddOp(v, OP_AddImm, 1, 0);
}
+ /* If there are triggers, close all the cursors after each iteration
+ ** through the loop. The fire the after triggers.
+ */
if( row_triggers_exist ){
- for(i=0, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext, i++){
- if( openAll || aIdxUsed[i] )
- sqliteVdbeAddOp(v, OP_Close, base+i+1, 0);
+ if( !isView ){
+ for(i=0, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext, i++){
+ if( openAll || aIdxUsed[i] )
+ sqliteVdbeAddOp(v, OP_Close, iCur+i+1, 0);
+ }
+ sqliteVdbeAddOp(v, OP_Close, iCur, 0);
+ pParse->nTab = iCur;
}
- sqliteVdbeAddOp(v, OP_Close, base, 0);
- pParse->nTab = base;
-
if( sqliteCodeRowTrigger(pParse, TK_UPDATE, pChanges, TK_AFTER, pTab,
newIdx, oldIdx, onError, addr) ){
goto update_cleanup;
@@ -384,11 +415,11 @@ void sqliteUpdate(
if( !row_triggers_exist ){
for(i=0, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext, i++){
if( openAll || aIdxUsed[i] ){
- sqliteVdbeAddOp(v, OP_Close, base+i+1, 0);
+ sqliteVdbeAddOp(v, OP_Close, iCur+i+1, 0);
}
}
- sqliteVdbeAddOp(v, OP_Close, base, 0);
- pParse->nTab = base;
+ sqliteVdbeAddOp(v, OP_Close, iCur, 0);
+ pParse->nTab = iCur;
}else{
sqliteVdbeAddOp(v, OP_Close, newIdx, 0);
sqliteVdbeAddOp(v, OP_Close, oldIdx, 0);
@@ -406,6 +437,7 @@ void sqliteUpdate(
}
update_cleanup:
+ sqliteAuthContextPop(&sContext);
sqliteFree(apIdx);
sqliteFree(aXRef);
sqliteSrcListDelete(pTabList);