diff options
Diffstat (limited to 'ext/sqlite/libsqlite/src/update.c')
-rw-r--r-- | ext/sqlite/libsqlite/src/update.c | 358 |
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); |