aboutsummaryrefslogtreecommitdiff
path: root/src/update.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/update.c')
-rw-r--r--src/update.c551
1 files changed, 378 insertions, 173 deletions
diff --git a/src/update.c b/src/update.c
index a6ad871eb..4d86aab6e 100644
--- a/src/update.c
+++ b/src/update.c
@@ -131,6 +131,122 @@ static int indexWhereClauseMightChange(
}
/*
+** This function generates VM code to run the query:
+**
+** SELECT <other-columns>, pChanges FROM pTabList WHERE pWhere
+**
+** and write the results to the ephemeral table already opened as cursor
+** iEph. None of pChanges, pTabList or pWhere are modified or consumed by
+** this function, they must be deleted by the caller.
+**
+** Exactly how results are written to table iEph, and exactly what
+** the <other-columns> in the query above are is determined by the type
+** of table pTabList->a[0].pTab.
+**
+** If the table is a WITHOUT ROWID table, then argument pPk must be its
+** PRIMARY KEY. In this case <other-columns> are the primary key columns
+** of the table, in order. The results of the query are written to ephemeral
+** table iEph as index keys, using OP_IdxInsert.
+**
+** If the table is actually a view, then <other-columns> are all columns of
+** the view. The results are written to the ephemeral table iEph as records
+** with automatically assigned integer keys.
+**
+** If the table is a virtual or ordinary intkey table, then <other-columns>
+** is its rowid. For a virtual table, the results are written to iEph as
+** records with automatically assigned integer keys For intkey tables, the
+** rowid value in <other-columns> is used as the integer key, and the
+** remaining fields make up the table record.
+*/
+static void updatePopulateEphTable(
+ Parse *pParse, /* Parse context */
+ int iEph, /* Cursor for open eph. table */
+ Index *pPk, /* PK if table 0 is WITHOUT ROWID */
+ ExprList *pChanges, /* List of expressions to return */
+ SrcList *pTabList, /* List of tables to select from */
+ Expr *pWhere, /* WHERE clause for query */
+ ExprList *pOrderBy,
+ Expr *pLimit
+){
+ int i;
+ SelectDest dest;
+ Select *pSelect = 0;
+ ExprList *pList = 0;
+ ExprList *pGroupBy = 0;
+ sqlite3 *db = pParse->db;
+ Table *pTab = pTabList->a[0].pTab;
+ SrcList *pSrc;
+ Expr *pWhere2;
+ Expr *pLimit2;
+ ExprList *pOrderBy2;
+ int eDest;
+
+ if( pOrderBy && pLimit==0 ) {
+ sqlite3ErrorMsg(pParse, "ORDER BY without LIMIT on UPDATE");
+ return;
+ }
+
+ pSrc = sqlite3SrcListDup(db, pTabList, 0);
+ pWhere2 = sqlite3ExprDup(db, pWhere, 0);
+ pLimit2 = sqlite3ExprDup(db, pLimit, 0);
+ pOrderBy2 = sqlite3ExprListDup(db, pOrderBy, 0);
+
+ assert( pTabList->nSrc>1 );
+ if( pSrc ){
+ pSrc->a[0].iCursor = -1;
+ pSrc->a[0].pTab->nTabRef--;
+ pSrc->a[0].pTab = 0;
+ }
+ if( pPk ){
+ for(i=0; i<pPk->nKeyCol; i++){
+ Expr *pNew = sqlite3PExpr(pParse, TK_DOT,
+ sqlite3Expr(db, TK_ID, pTab->zName),
+ sqlite3Expr(db, TK_ID, pTab->aCol[pPk->aiColumn[i]].zName)
+ );
+ if( pLimit ){
+ pGroupBy = sqlite3ExprListAppend(pParse, pGroupBy,
+ sqlite3ExprDup(db, pNew, 0)
+ );
+ }
+ pList = sqlite3ExprListAppend(pParse, pList, pNew);
+ }
+ eDest = SRT_Upfrom;
+ }else if( pTab->pSelect ){
+ pList = sqlite3ExprListAppend(pParse, pList,
+ sqlite3PExpr(pParse, TK_DOT,
+ sqlite3Expr(db, TK_ID, pTab->zName),
+ sqlite3PExpr(pParse, TK_ASTERISK, 0, 0)
+ ));
+ eDest = SRT_Table;
+ }else{
+ eDest = IsVirtual(pTab) ? SRT_Table : SRT_Upfrom;
+ pList = sqlite3ExprListAppend(pParse, pList,
+ sqlite3PExpr(pParse, TK_ROW, 0, 0)
+ );
+ if( pLimit ){
+ pGroupBy = sqlite3ExprListAppend(pParse, pGroupBy,
+ sqlite3PExpr(pParse, TK_ROW, 0, 0)
+ );
+ }
+ }
+ assert( pChanges || db->mallocFailed );
+ if( pChanges ){
+ for(i=0; i<pChanges->nExpr; i++){
+ pList = sqlite3ExprListAppend(pParse, pList,
+ sqlite3ExprDup(db, pChanges->a[i].pExpr, 0)
+ );
+ }
+ }
+ pSelect = sqlite3SelectNew(
+ pParse, pList, pSrc, pWhere2, pGroupBy, 0, pOrderBy2, 0, pLimit2
+ );
+ sqlite3SelectDestInit(&dest, eDest, iEph);
+ dest.iSDParm2 = (pPk ? pPk->nKeyCol : -1);
+ sqlite3Select(pParse, pSelect, &dest);
+ sqlite3SelectDelete(db, pSelect);
+}
+
+/*
** Process an UPDATE statement.
**
** UPDATE OR IGNORE table_wxyz SET a=b, c=d WHERE e<5 AND f NOT NULL;
@@ -169,6 +285,7 @@ void sqlite3Update(
u8 chngRowid; /* Rowid changed in a normal table */
u8 chngKey; /* Either chngPk or chngRowid */
Expr *pRowidExpr = 0; /* Expression defining the new record number */
+ int iRowidExpr = -1;
AuthContext sContext; /* The authorization context */
NameContext sNC; /* The name-context to resolve expressions in */
int iDb; /* Database containing the table being updated */
@@ -192,6 +309,7 @@ void sqlite3Update(
i16 nPk = 0; /* Number of components of the PRIMARY KEY */
int bReplace = 0; /* True if REPLACE conflict resolution might happen */
int bFinishSeek = 1; /* The OP_FinishSeek opcode is needed */
+ int nChangeFrom = 0;
/* Register Allocations */
int regRowCount = 0; /* A count of rows changed */
@@ -207,7 +325,6 @@ void sqlite3Update(
if( pParse->nErr || db->mallocFailed ){
goto update_cleanup;
}
- assert( pTabList->nSrc==1 );
/* Locate the table which we want to update.
*/
@@ -231,9 +348,11 @@ void sqlite3Update(
# undef isView
# define isView 0
#endif
+ nChangeFrom = (pTabList->nSrc>1) ? pChanges->nExpr : 0;
+ assert( nChangeFrom==0 || pUpsert==0 );
#ifdef SQLITE_ENABLE_UPDATE_DELETE_LIMIT
- if( !isView ){
+ if( !isView && nChangeFrom==0 ){
pWhere = sqlite3LimitWhere(
pParse, pTabList, pWhere, pOrderBy, pLimit, "UPDATE"
);
@@ -302,7 +421,9 @@ void sqlite3Update(
*/
chngRowid = chngPk = 0;
for(i=0; i<pChanges->nExpr; i++){
- if( sqlite3ResolveExprNames(&sNC, pChanges->a[i].pExpr) ){
+ /* If this is an UPDATE with a FROM clause, do not resolve expressions
+ ** here. The call to sqlite3Select() below will do that. */
+ if( nChangeFrom==0 && sqlite3ResolveExprNames(&sNC, pChanges->a[i].pExpr) ){
goto update_cleanup;
}
for(j=0; j<pTab->nCol; j++){
@@ -310,6 +431,7 @@ void sqlite3Update(
if( j==pTab->iPKey ){
chngRowid = 1;
pRowidExpr = pChanges->a[i].pExpr;
+ iRowidExpr = i;
}else if( pPk && (pTab->aCol[j].colFlags & COLFLAG_PRIMKEY)!=0 ){
chngPk = 1;
}
@@ -332,6 +454,7 @@ void sqlite3Update(
j = -1;
chngRowid = 1;
pRowidExpr = pChanges->a[i].pExpr;
+ iRowidExpr = i;
}else{
sqlite3ErrorMsg(pParse, "no such column: %s", pChanges->a[i].zEName);
pParse->checkSchema = 1;
@@ -461,7 +584,7 @@ void sqlite3Update(
** an ephemeral table.
*/
#if !defined(SQLITE_OMIT_VIEW) && !defined(SQLITE_OMIT_TRIGGER)
- if( isView ){
+ if( nChangeFrom==0 && isView ){
sqlite3MaterializeView(pParse, pTab,
pWhere, pOrderBy, pLimit, iDataCur
);
@@ -473,7 +596,7 @@ void sqlite3Update(
/* Resolve the column names in all the expressions in the
** WHERE clause.
*/
- if( sqlite3ResolveExprNames(&sNC, pWhere) ){
+ if( nChangeFrom==0 && sqlite3ResolveExprNames(&sNC, pWhere) ){
goto update_cleanup;
}
@@ -500,105 +623,122 @@ void sqlite3Update(
sqlite3VdbeAddOp2(v, OP_Integer, 0, regRowCount);
}
- if( HasRowid(pTab) ){
+ if( nChangeFrom==0 && HasRowid(pTab) ){
sqlite3VdbeAddOp3(v, OP_Null, 0, regRowSet, regOldRowid);
}else{
- assert( pPk!=0 );
- nPk = pPk->nKeyCol;
+ assert( pPk!=0 || HasRowid(pTab) );
+ nPk = pPk ? pPk->nKeyCol : 0;
iPk = pParse->nMem+1;
pParse->nMem += nPk;
+ pParse->nMem += nChangeFrom;
regKey = ++pParse->nMem;
if( pUpsert==0 ){
+ int nEphCol = nPk + nChangeFrom + (isView ? pTab->nCol : 0);
iEph = pParse->nTab++;
- sqlite3VdbeAddOp3(v, OP_Null, 0, iPk, iPk+nPk-1);
- addrOpen = sqlite3VdbeAddOp2(v, OP_OpenEphemeral, iEph, nPk);
- sqlite3VdbeSetP4KeyInfo(pParse, pPk);
+ if( pPk ) sqlite3VdbeAddOp3(v, OP_Null, 0, iPk, iPk+nPk-1);
+ addrOpen = sqlite3VdbeAddOp2(v, OP_OpenEphemeral, iEph, nEphCol);
+ if( pPk ) sqlite3VdbeSetP4KeyInfo(pParse, pPk);
+ if( nChangeFrom ){
+ updatePopulateEphTable(
+ pParse, iEph, pPk, pChanges, pTabList, pWhere, pOrderBy, pLimit
+ );
+#ifndef SQLITE_OMIT_SUBQUERY
+ if( isView ) iDataCur = iEph;
+#endif
+ }
}
}
- if( pUpsert ){
- /* If this is an UPSERT, then all cursors have already been opened by
- ** the outer INSERT and the data cursor should be pointing at the row
- ** that is to be updated. So bypass the code that searches for the
- ** row(s) to be updated.
- */
- pWInfo = 0;
- eOnePass = ONEPASS_SINGLE;
- sqlite3ExprIfFalse(pParse, pWhere, labelBreak, SQLITE_JUMPIFNULL);
- bFinishSeek = 0;
+ if( nChangeFrom ){
+ sqlite3MultiWrite(pParse);
+ eOnePass = ONEPASS_OFF;
+ nKey = nPk;
+ regKey = iPk;
}else{
- /* Begin the database scan.
- **
- ** Do not consider a single-pass strategy for a multi-row update if
- ** there are any triggers or foreign keys to process, or rows may
- ** be deleted as a result of REPLACE conflict handling. Any of these
- ** things might disturb a cursor being used to scan through the table
- ** or index, causing a single-pass approach to malfunction. */
- flags = WHERE_ONEPASS_DESIRED|WHERE_SEEK_UNIQ_TABLE;
- if( !pParse->nested && !pTrigger && !hasFK && !chngKey && !bReplace ){
- flags |= WHERE_ONEPASS_MULTIROW;
- }
- pWInfo = sqlite3WhereBegin(pParse, pTabList, pWhere, 0, 0, flags, iIdxCur);
- if( pWInfo==0 ) goto update_cleanup;
-
- /* A one-pass strategy that might update more than one row may not
- ** be used if any column of the index used for the scan is being
- ** updated. Otherwise, if there is an index on "b", statements like
- ** the following could create an infinite loop:
- **
- ** UPDATE t1 SET b=b+1 WHERE b>?
- **
- ** Fall back to ONEPASS_OFF if where.c has selected a ONEPASS_MULTI
- ** strategy that uses an index for which one or more columns are being
- ** updated. */
- eOnePass = sqlite3WhereOkOnePass(pWInfo, aiCurOnePass);
- bFinishSeek = sqlite3WhereUsesDeferredSeek(pWInfo);
- if( eOnePass!=ONEPASS_SINGLE ){
- sqlite3MultiWrite(pParse);
- if( eOnePass==ONEPASS_MULTI ){
- int iCur = aiCurOnePass[1];
- if( iCur>=0 && iCur!=iDataCur && aToOpen[iCur-iBaseCur] ){
- eOnePass = ONEPASS_OFF;
+ if( pUpsert ){
+ /* If this is an UPSERT, then all cursors have already been opened by
+ ** the outer INSERT and the data cursor should be pointing at the row
+ ** that is to be updated. So bypass the code that searches for the
+ ** row(s) to be updated.
+ */
+ pWInfo = 0;
+ eOnePass = ONEPASS_SINGLE;
+ sqlite3ExprIfFalse(pParse, pWhere, labelBreak, SQLITE_JUMPIFNULL);
+ bFinishSeek = 0;
+ }else{
+ /* Begin the database scan.
+ **
+ ** Do not consider a single-pass strategy for a multi-row update if
+ ** there are any triggers or foreign keys to process, or rows may
+ ** be deleted as a result of REPLACE conflict handling. Any of these
+ ** things might disturb a cursor being used to scan through the table
+ ** or index, causing a single-pass approach to malfunction. */
+ flags = WHERE_ONEPASS_DESIRED|WHERE_SEEK_UNIQ_TABLE;
+ if( !pParse->nested && !pTrigger && !hasFK && !chngKey && !bReplace ){
+ flags |= WHERE_ONEPASS_MULTIROW;
+ }
+ pWInfo = sqlite3WhereBegin(pParse, pTabList, pWhere, 0, 0, flags,iIdxCur);
+ if( pWInfo==0 ) goto update_cleanup;
+
+ /* A one-pass strategy that might update more than one row may not
+ ** be used if any column of the index used for the scan is being
+ ** updated. Otherwise, if there is an index on "b", statements like
+ ** the following could create an infinite loop:
+ **
+ ** UPDATE t1 SET b=b+1 WHERE b>?
+ **
+ ** Fall back to ONEPASS_OFF if where.c has selected a ONEPASS_MULTI
+ ** strategy that uses an index for which one or more columns are being
+ ** updated. */
+ eOnePass = sqlite3WhereOkOnePass(pWInfo, aiCurOnePass);
+ bFinishSeek = sqlite3WhereUsesDeferredSeek(pWInfo);
+ if( eOnePass!=ONEPASS_SINGLE ){
+ sqlite3MultiWrite(pParse);
+ if( eOnePass==ONEPASS_MULTI ){
+ int iCur = aiCurOnePass[1];
+ if( iCur>=0 && iCur!=iDataCur && aToOpen[iCur-iBaseCur] ){
+ eOnePass = ONEPASS_OFF;
+ }
+ assert( iCur!=iDataCur || !HasRowid(pTab) );
}
- assert( iCur!=iDataCur || !HasRowid(pTab) );
}
}
- }
- if( HasRowid(pTab) ){
- /* Read the rowid of the current row of the WHERE scan. In ONEPASS_OFF
- ** mode, write the rowid into the FIFO. In either of the one-pass modes,
- ** leave it in register regOldRowid. */
- sqlite3VdbeAddOp2(v, OP_Rowid, iDataCur, regOldRowid);
- if( eOnePass==ONEPASS_OFF ){
- /* We need to use regRowSet, so reallocate aRegIdx[nAllIdx] */
- aRegIdx[nAllIdx] = ++pParse->nMem;
- sqlite3VdbeAddOp2(v, OP_RowSetAdd, regRowSet, regOldRowid);
- }
- }else{
- /* Read the PK of the current row into an array of registers. In
- ** ONEPASS_OFF mode, serialize the array into a record and store it in
- ** the ephemeral table. Or, in ONEPASS_SINGLE or MULTI mode, change
- ** the OP_OpenEphemeral instruction to a Noop (the ephemeral table
- ** is not required) and leave the PK fields in the array of registers. */
- for(i=0; i<nPk; i++){
- assert( pPk->aiColumn[i]>=0 );
- sqlite3ExprCodeGetColumnOfTable(v, pTab, iDataCur,
- pPk->aiColumn[i], iPk+i);
- }
- if( eOnePass ){
- if( addrOpen ) sqlite3VdbeChangeToNoop(v, addrOpen);
- nKey = nPk;
- regKey = iPk;
+ if( HasRowid(pTab) ){
+ /* Read the rowid of the current row of the WHERE scan. In ONEPASS_OFF
+ ** mode, write the rowid into the FIFO. In either of the one-pass modes,
+ ** leave it in register regOldRowid. */
+ sqlite3VdbeAddOp2(v, OP_Rowid, iDataCur, regOldRowid);
+ if( eOnePass==ONEPASS_OFF ){
+ /* We need to use regRowSet, so reallocate aRegIdx[nAllIdx] */
+ aRegIdx[nAllIdx] = ++pParse->nMem;
+ sqlite3VdbeAddOp2(v, OP_RowSetAdd, regRowSet, regOldRowid);
+ }
}else{
- sqlite3VdbeAddOp4(v, OP_MakeRecord, iPk, nPk, regKey,
- sqlite3IndexAffinityStr(db, pPk), nPk);
- sqlite3VdbeAddOp4Int(v, OP_IdxInsert, iEph, regKey, iPk, nPk);
+ /* Read the PK of the current row into an array of registers. In
+ ** ONEPASS_OFF mode, serialize the array into a record and store it in
+ ** the ephemeral table. Or, in ONEPASS_SINGLE or MULTI mode, change
+ ** the OP_OpenEphemeral instruction to a Noop (the ephemeral table
+ ** is not required) and leave the PK fields in the array of registers. */
+ for(i=0; i<nPk; i++){
+ assert( pPk->aiColumn[i]>=0 );
+ sqlite3ExprCodeGetColumnOfTable(v, pTab, iDataCur,
+ pPk->aiColumn[i], iPk+i);
+ }
+ if( eOnePass ){
+ if( addrOpen ) sqlite3VdbeChangeToNoop(v, addrOpen);
+ nKey = nPk;
+ regKey = iPk;
+ }else{
+ sqlite3VdbeAddOp4(v, OP_MakeRecord, iPk, nPk, regKey,
+ sqlite3IndexAffinityStr(db, pPk), nPk);
+ sqlite3VdbeAddOp4Int(v, OP_IdxInsert, iEph, regKey, iPk, nPk);
+ }
}
}
if( pUpsert==0 ){
- if( eOnePass!=ONEPASS_MULTI ){
+ if( nChangeFrom==0 && eOnePass!=ONEPASS_MULTI ){
sqlite3WhereEnd(pWInfo);
}
@@ -634,12 +774,32 @@ void sqlite3Update(
sqlite3VdbeAddOp2(v, OP_IsNull, pPk ? regKey : regOldRowid, labelBreak);
VdbeCoverageIf(v, pPk==0);
VdbeCoverageIf(v, pPk!=0);
- }else if( pPk ){
+ }else if( pPk || nChangeFrom ){
labelContinue = sqlite3VdbeMakeLabel(pParse);
sqlite3VdbeAddOp2(v, OP_Rewind, iEph, labelBreak); VdbeCoverage(v);
- addrTop = sqlite3VdbeAddOp2(v, OP_RowData, iEph, regKey);
- sqlite3VdbeAddOp4Int(v, OP_NotFound, iDataCur, labelContinue, regKey, 0);
- VdbeCoverage(v);
+ addrTop = sqlite3VdbeCurrentAddr(v);
+ if( nChangeFrom ){
+ if( !isView ){
+ if( pPk ){
+ for(i=0; i<nPk; i++){
+ sqlite3VdbeAddOp3(v, OP_Column, iEph, i, iPk+i);
+ }
+ sqlite3VdbeAddOp4Int(
+ v, OP_NotFound, iDataCur, labelContinue, iPk, nPk
+ );
+ }else{
+ sqlite3VdbeAddOp2(v, OP_Rowid, iEph, regOldRowid);
+ sqlite3VdbeAddOp3(
+ v, OP_NotExists, iDataCur, labelContinue, regOldRowid
+ );
+ }
+ }
+ VdbeCoverage(v);
+ }else{
+ sqlite3VdbeAddOp2(v, OP_RowData, iEph, regKey);
+ sqlite3VdbeAddOp4Int(v, OP_NotFound, iDataCur, labelContinue, regKey,0);
+ VdbeCoverage(v);
+ }
}else{
labelContinue = sqlite3VdbeAddOp3(v, OP_RowSetRead, regRowSet,labelBreak,
regOldRowid);
@@ -655,7 +815,12 @@ void sqlite3Update(
** already populated. */
assert( chngKey || pTrigger || hasFK || regOldRowid==regNewRowid );
if( chngRowid ){
- sqlite3ExprCode(pParse, pRowidExpr, regNewRowid);
+ assert( iRowidExpr>=0 );
+ if( nChangeFrom==0 ){
+ sqlite3ExprCode(pParse, pRowidExpr, regNewRowid);
+ }else{
+ sqlite3VdbeAddOp3(v, OP_Column, iEph, iRowidExpr, regNewRowid);
+ }
sqlite3VdbeAddOp1(v, OP_MustBeInt, regNewRowid); VdbeCoverage(v);
}
@@ -708,7 +873,13 @@ void sqlite3Update(
}else{
j = aXRef[i];
if( j>=0 ){
- sqlite3ExprCode(pParse, pChanges->a[j].pExpr, k);
+ if( nChangeFrom ){
+ assert( eOnePass==ONEPASS_OFF );
+ int nOff = (isView ? pTab->nCol : nPk);
+ sqlite3VdbeAddOp3(v, OP_Column, iEph, nOff+j, k);
+ }else{
+ sqlite3ExprCode(pParse, pChanges->a[j].pExpr, k);
+ }
}else if( 0==(tmask&TRIGGER_BEFORE) || i>31 || (newmask & MASKBIT32(i)) ){
/* This branch loads the value of a column that will not be changed
** into a register. This is done if there are no BEFORE triggers, or
@@ -740,43 +911,45 @@ void sqlite3Update(
sqlite3CodeRowTrigger(pParse, pTrigger, TK_UPDATE, pChanges,
TRIGGER_BEFORE, pTab, regOldRowid, onError, labelContinue);
- /* The row-trigger may have deleted the row being updated. In this
- ** case, jump to the next row. No updates or AFTER triggers are
- ** required. This behavior - what happens when the row being updated
- ** is deleted or renamed by a BEFORE trigger - is left undefined in the
- ** documentation.
- */
- if( pPk ){
- sqlite3VdbeAddOp4Int(v, OP_NotFound, iDataCur, labelContinue,regKey,nKey);
- VdbeCoverage(v);
- }else{
- sqlite3VdbeAddOp3(v, OP_NotExists, iDataCur, labelContinue, regOldRowid);
- VdbeCoverage(v);
- }
+ if( !isView ){
+ /* The row-trigger may have deleted the row being updated. In this
+ ** case, jump to the next row. No updates or AFTER triggers are
+ ** required. This behavior - what happens when the row being updated
+ ** is deleted or renamed by a BEFORE trigger - is left undefined in the
+ ** documentation.
+ */
+ if( pPk ){
+ sqlite3VdbeAddOp4Int(v, OP_NotFound,iDataCur,labelContinue,regKey,nKey);
+ VdbeCoverage(v);
+ }else{
+ sqlite3VdbeAddOp3(v, OP_NotExists, iDataCur, labelContinue,regOldRowid);
+ VdbeCoverage(v);
+ }
- /* After-BEFORE-trigger-reload-loop:
- ** If it did not delete it, the BEFORE trigger may still have modified
- ** some of the columns of the row being updated. Load the values for
- ** all columns not modified by the update statement into their registers
- ** in case this has happened. Only unmodified columns are reloaded.
- ** The values computed for modified columns use the values before the
- ** BEFORE trigger runs. See test case trigger1-18.0 (added 2018-04-26)
- ** for an example.
- */
- for(i=0, k=regNew; i<pTab->nCol; i++, k++){
- if( pTab->aCol[i].colFlags & COLFLAG_GENERATED ){
- if( pTab->aCol[i].colFlags & COLFLAG_VIRTUAL ) k--;
- }else if( aXRef[i]<0 && i!=pTab->iPKey ){
- sqlite3ExprCodeGetColumnOfTable(v, pTab, iDataCur, i, k);
+ /* After-BEFORE-trigger-reload-loop:
+ ** If it did not delete it, the BEFORE trigger may still have modified
+ ** some of the columns of the row being updated. Load the values for
+ ** all columns not modified by the update statement into their registers
+ ** in case this has happened. Only unmodified columns are reloaded.
+ ** The values computed for modified columns use the values before the
+ ** BEFORE trigger runs. See test case trigger1-18.0 (added 2018-04-26)
+ ** for an example.
+ */
+ for(i=0, k=regNew; i<pTab->nCol; i++, k++){
+ if( pTab->aCol[i].colFlags & COLFLAG_GENERATED ){
+ if( pTab->aCol[i].colFlags & COLFLAG_VIRTUAL ) k--;
+ }else if( aXRef[i]<0 && i!=pTab->iPKey ){
+ sqlite3ExprCodeGetColumnOfTable(v, pTab, iDataCur, i, k);
+ }
}
- }
#ifndef SQLITE_OMIT_GENERATED_COLUMNS
- if( pTab->tabFlags & TF_HasGenerated ){
- testcase( pTab->tabFlags & TF_HasVirtual );
- testcase( pTab->tabFlags & TF_HasStored );
- sqlite3ComputeGeneratedColumns(pParse, regNew, pTab);
- }
+ if( pTab->tabFlags & TF_HasGenerated ){
+ testcase( pTab->tabFlags & TF_HasVirtual );
+ testcase( pTab->tabFlags & TF_HasStored );
+ sqlite3ComputeGeneratedColumns(pParse, regNew, pTab);
+ }
#endif
+ }
}
if( !isView ){
@@ -879,7 +1052,7 @@ void sqlite3Update(
}else if( eOnePass==ONEPASS_MULTI ){
sqlite3VdbeResolveLabel(v, labelContinue);
sqlite3WhereEnd(pWInfo);
- }else if( pPk ){
+ }else if( pPk || nChangeFrom ){
sqlite3VdbeResolveLabel(v, labelContinue);
sqlite3VdbeAddOp2(v, OP_Next, iEph, addrTop); VdbeCoverage(v);
}else{
@@ -982,69 +1155,101 @@ static void updateVirtualTable(
addr= sqlite3VdbeAddOp2(v, OP_OpenEphemeral, ephemTab, nArg);
regArg = pParse->nMem + 1;
pParse->nMem += nArg;
- regRec = ++pParse->nMem;
- regRowid = ++pParse->nMem;
-
- /* Start scanning the virtual table */
- pWInfo = sqlite3WhereBegin(pParse, pSrc, pWhere, 0,0,WHERE_ONEPASS_DESIRED,0);
- if( pWInfo==0 ) return;
-
- /* Populate the argument registers. */
- for(i=0; i<pTab->nCol; i++){
- assert( (pTab->aCol[i].colFlags & COLFLAG_GENERATED)==0 );
- if( aXRef[i]>=0 ){
- sqlite3ExprCode(pParse, pChanges->a[aXRef[i]].pExpr, regArg+2+i);
- }else{
- sqlite3VdbeAddOp3(v, OP_VColumn, iCsr, i, regArg+2+i);
- sqlite3VdbeChangeP5(v, OPFLAG_NOCHNG);/* Enable sqlite3_vtab_nochange() */
- }
- }
- if( HasRowid(pTab) ){
- sqlite3VdbeAddOp2(v, OP_Rowid, iCsr, regArg);
+ if( pSrc->nSrc>1 ){
+ ExprList *pList = 0;
if( pRowid ){
- sqlite3ExprCode(pParse, pRowid, regArg+1);
+ pList = sqlite3ExprListAppend(pParse, pList, sqlite3ExprDup(db,pRowid,0));
}else{
- sqlite3VdbeAddOp2(v, OP_Rowid, iCsr, regArg+1);
+ pList = sqlite3ExprListAppend(pParse, pList,
+ sqlite3PExpr(pParse, TK_DOT,
+ sqlite3Expr(db, TK_ID, pTab->zName),
+ sqlite3Expr(db, TK_ID, "_rowid_")
+ ));
}
+ for(i=0; i<pTab->nCol; i++){
+ if( aXRef[i]>=0 ){
+ pList = sqlite3ExprListAppend(pParse, pList,
+ sqlite3ExprDup(db, pChanges->a[aXRef[i]].pExpr, 0)
+ );
+ }else{
+ pList = sqlite3ExprListAppend(pParse, pList,
+ sqlite3PExpr(pParse, TK_DOT,
+ sqlite3Expr(db, TK_ID, pTab->zName),
+ sqlite3Expr(db, TK_ID, pTab->aCol[i].zName)
+ ));
+ }
+ }
+
+ updatePopulateEphTable(pParse, ephemTab, 0, pList, pSrc, pWhere, 0, 0);
+ sqlite3ExprListDelete(db, pList);
+ eOnePass = ONEPASS_OFF;
}else{
- Index *pPk; /* PRIMARY KEY index */
- i16 iPk; /* PRIMARY KEY column */
- pPk = sqlite3PrimaryKeyIndex(pTab);
- assert( pPk!=0 );
- assert( pPk->nKeyCol==1 );
- iPk = pPk->aiColumn[0];
- sqlite3VdbeAddOp3(v, OP_VColumn, iCsr, iPk, regArg);
- sqlite3VdbeAddOp2(v, OP_SCopy, regArg+2+iPk, regArg+1);
- }
+ regRec = ++pParse->nMem;
+ regRowid = ++pParse->nMem;
- eOnePass = sqlite3WhereOkOnePass(pWInfo, aDummy);
+ /* Start scanning the virtual table */
+ pWInfo = sqlite3WhereBegin(pParse, pSrc,pWhere,0,0,WHERE_ONEPASS_DESIRED,0);
+ if( pWInfo==0 ) return;
- /* There is no ONEPASS_MULTI on virtual tables */
- assert( eOnePass==ONEPASS_OFF || eOnePass==ONEPASS_SINGLE );
+ /* Populate the argument registers. */
+ for(i=0; i<pTab->nCol; i++){
+ assert( (pTab->aCol[i].colFlags & COLFLAG_GENERATED)==0 );
+ if( aXRef[i]>=0 ){
+ sqlite3ExprCode(pParse, pChanges->a[aXRef[i]].pExpr, regArg+2+i);
+ }else{
+ sqlite3VdbeAddOp3(v, OP_VColumn, iCsr, i, regArg+2+i);
+ sqlite3VdbeChangeP5(v, OPFLAG_NOCHNG);/* For sqlite3_vtab_nochange() */
+ }
+ }
+ if( HasRowid(pTab) ){
+ sqlite3VdbeAddOp2(v, OP_Rowid, iCsr, regArg);
+ if( pRowid ){
+ sqlite3ExprCode(pParse, pRowid, regArg+1);
+ }else{
+ sqlite3VdbeAddOp2(v, OP_Rowid, iCsr, regArg+1);
+ }
+ }else{
+ Index *pPk; /* PRIMARY KEY index */
+ i16 iPk; /* PRIMARY KEY column */
+ pPk = sqlite3PrimaryKeyIndex(pTab);
+ assert( pPk!=0 );
+ assert( pPk->nKeyCol==1 );
+ iPk = pPk->aiColumn[0];
+ sqlite3VdbeAddOp3(v, OP_VColumn, iCsr, iPk, regArg);
+ sqlite3VdbeAddOp2(v, OP_SCopy, regArg+2+iPk, regArg+1);
+ }
- if( eOnePass ){
- /* If using the onepass strategy, no-op out the OP_OpenEphemeral coded
- ** above. */
- sqlite3VdbeChangeToNoop(v, addr);
- sqlite3VdbeAddOp1(v, OP_Close, iCsr);
- }else{
- /* Create a record from the argument register contents and insert it into
- ** the ephemeral table. */
- sqlite3MultiWrite(pParse);
- sqlite3VdbeAddOp3(v, OP_MakeRecord, regArg, nArg, regRec);
+ eOnePass = sqlite3WhereOkOnePass(pWInfo, aDummy);
+
+ /* There is no ONEPASS_MULTI on virtual tables */
+ assert( eOnePass==ONEPASS_OFF || eOnePass==ONEPASS_SINGLE );
+
+ if( eOnePass ){
+ /* If using the onepass strategy, no-op out the OP_OpenEphemeral coded
+ ** above. */
+ sqlite3VdbeChangeToNoop(v, addr);
+ sqlite3VdbeAddOp1(v, OP_Close, iCsr);
+ }else{
+ /* Create a record from the argument register contents and insert it into
+ ** the ephemeral table. */
+ sqlite3MultiWrite(pParse);
+ sqlite3VdbeAddOp3(v, OP_MakeRecord, regArg, nArg, regRec);
#if defined(SQLITE_DEBUG) && !defined(SQLITE_ENABLE_NULL_TRIM)
- /* Signal an assert() within OP_MakeRecord that it is allowed to
- ** accept no-change records with serial_type 10 */
- sqlite3VdbeChangeP5(v, OPFLAG_NOCHNG_MAGIC);
+ /* Signal an assert() within OP_MakeRecord that it is allowed to
+ ** accept no-change records with serial_type 10 */
+ sqlite3VdbeChangeP5(v, OPFLAG_NOCHNG_MAGIC);
#endif
- sqlite3VdbeAddOp2(v, OP_NewRowid, ephemTab, regRowid);
- sqlite3VdbeAddOp3(v, OP_Insert, ephemTab, regRec, regRowid);
+ sqlite3VdbeAddOp2(v, OP_NewRowid, ephemTab, regRowid);
+ sqlite3VdbeAddOp3(v, OP_Insert, ephemTab, regRec, regRowid);
+ }
}
if( eOnePass==ONEPASS_OFF ){
/* End the virtual table scan */
- sqlite3WhereEnd(pWInfo);
+ if( pSrc->nSrc==1 ){
+ sqlite3WhereEnd(pWInfo);
+ }
/* Begin scannning through the ephemeral table. */
addr = sqlite3VdbeAddOp1(v, OP_Rewind, ephemTab); VdbeCoverage(v);