aboutsummaryrefslogtreecommitdiff
path: root/src/trigger.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/trigger.c')
-rw-r--r--src/trigger.c507
1 files changed, 399 insertions, 108 deletions
diff --git a/src/trigger.c b/src/trigger.c
index 50de878dd..97ca249be 100644
--- a/src/trigger.c
+++ b/src/trigger.c
@@ -26,6 +26,7 @@ void sqlite3DeleteTriggerStep(sqlite3 *db, TriggerStep *pTriggerStep){
sqlite3SelectDelete(db, pTmp->pSelect);
sqlite3IdListDelete(db, pTmp->pIdList);
sqlite3UpsertDelete(db, pTmp->pUpsert);
+ sqlite3SrcListDelete(db, pTmp->pFrom);
sqlite3DbFree(db, pTmp->zSpan);
sqlite3DbFree(db, pTmp);
@@ -47,28 +48,48 @@ void sqlite3DeleteTriggerStep(sqlite3 *db, TriggerStep *pTriggerStep){
** pTab as well as the triggers lised in pTab->pTrigger.
*/
Trigger *sqlite3TriggerList(Parse *pParse, Table *pTab){
- Schema * const pTmpSchema = pParse->db->aDb[1].pSchema;
- Trigger *pList = 0; /* List of triggers to return */
-
- if( pParse->disableTriggers ){
- return 0;
+ Schema *pTmpSchema; /* Schema of the pTab table */
+ Trigger *pList; /* List of triggers to return */
+ HashElem *p; /* Loop variable for TEMP triggers */
+
+ assert( pParse->disableTriggers==0 );
+ pTmpSchema = pParse->db->aDb[1].pSchema;
+ p = sqliteHashFirst(&pTmpSchema->trigHash);
+ pList = pTab->pTrigger;
+ while( p ){
+ Trigger *pTrig = (Trigger *)sqliteHashData(p);
+ if( pTrig->pTabSchema==pTab->pSchema
+ && pTrig->table
+ && 0==sqlite3StrICmp(pTrig->table, pTab->zName)
+ && (pTrig->pTabSchema!=pTmpSchema || pTrig->bReturning)
+ ){
+ pTrig->pNext = pList;
+ pList = pTrig;
+ }else if( pTrig->op==TK_RETURNING ){
+#ifndef SQLITE_OMIT_VIRTUALTABLE
+ assert( pParse->db->pVtabCtx==0 );
+#endif
+ assert( pParse->bReturning );
+ assert( &(pParse->u1.pReturning->retTrig) == pTrig );
+ pTrig->table = pTab->zName;
+ pTrig->pTabSchema = pTab->pSchema;
+ pTrig->pNext = pList;
+ pList = pTrig;
+ }
+ p = sqliteHashNext(p);
}
-
- if( pTmpSchema!=pTab->pSchema ){
- HashElem *p;
- assert( sqlite3SchemaMutexHeld(pParse->db, 0, pTmpSchema) );
- for(p=sqliteHashFirst(&pTmpSchema->trigHash); p; p=sqliteHashNext(p)){
- Trigger *pTrig = (Trigger *)sqliteHashData(p);
- if( pTrig->pTabSchema==pTab->pSchema
- && 0==sqlite3StrICmp(pTrig->table, pTab->zName)
- ){
- pTrig->pNext = (pList ? pList : pTab->pTrigger);
- pList = pTrig;
- }
+#if 0
+ if( pList ){
+ Trigger *pX;
+ printf("Triggers for %s:", pTab->zName);
+ for(pX=pList; pX; pX=pX->pNext){
+ printf(" %s", pX->zName);
}
+ printf("\n");
+ fflush(stdout);
}
-
- return (pList ? pList : pTab->pTrigger);
+#endif
+ return pList;
}
/*
@@ -156,22 +177,15 @@ void sqlite3BeginTrigger(
pTab = sqlite3SrcListLookup(pParse, pTableName);
if( !pTab ){
/* The table does not exist. */
- if( db->init.iDb==1 ){
- /* Ticket #3810.
- ** Normally, whenever a table is dropped, all associated triggers are
- ** dropped too. But if a TEMP trigger is created on a non-TEMP table
- ** and the table is dropped by a different database connection, the
- ** trigger is not visible to the database connection that does the
- ** drop so the trigger cannot be dropped. This results in an
- ** "orphaned trigger" - a trigger whose associated table is missing.
- */
- db->init.orphanTrigger = 1;
- }
- goto trigger_cleanup;
+ goto trigger_orphan_error;
}
if( IsVirtual(pTab) ){
sqlite3ErrorMsg(pParse, "cannot create triggers on virtual tables");
- goto trigger_cleanup;
+ goto trigger_orphan_error;
+ }
+ if( (pTab->tabFlags & TF_Shadow)!=0 && sqlite3ReadOnlyShadowTables(db) ){
+ sqlite3ErrorMsg(pParse, "cannot create triggers on shadow tables");
+ goto trigger_orphan_error;
}
/* Check that the trigger name is not reserved and that no trigger of the
@@ -192,6 +206,7 @@ void sqlite3BeginTrigger(
}else{
assert( !db->init.busy );
sqlite3CodeVerifySchema(pParse, iDb);
+ VVA_ONLY( pParse->ifNotExists = 1; )
}
goto trigger_cleanup;
}
@@ -206,15 +221,15 @@ void sqlite3BeginTrigger(
/* INSTEAD of triggers are only for views and views only support INSTEAD
** of triggers.
*/
- if( pTab->pSelect && tr_tm!=TK_INSTEAD ){
+ if( IsView(pTab) && tr_tm!=TK_INSTEAD ){
sqlite3ErrorMsg(pParse, "cannot create %s trigger on view: %S",
- (tr_tm == TK_BEFORE)?"BEFORE":"AFTER", pTableName, 0);
- goto trigger_cleanup;
+ (tr_tm == TK_BEFORE)?"BEFORE":"AFTER", pTableName->a);
+ goto trigger_orphan_error;
}
- if( !pTab->pSelect && tr_tm==TK_INSTEAD ){
+ if( !IsView(pTab) && tr_tm==TK_INSTEAD ){
sqlite3ErrorMsg(pParse, "cannot create INSTEAD OF"
- " trigger on table: %S", pTableName, 0);
- goto trigger_cleanup;
+ " trigger on table: %S", pTableName->a);
+ goto trigger_orphan_error;
}
#ifndef SQLITE_OMIT_AUTHORIZATION
@@ -274,6 +289,23 @@ trigger_cleanup:
}else{
assert( pParse->pNewTrigger==pTrigger );
}
+ return;
+
+trigger_orphan_error:
+ if( db->init.iDb==1 ){
+ /* Ticket #3810.
+ ** Normally, whenever a table is dropped, all associated triggers are
+ ** dropped too. But if a TEMP trigger is created on a non-TEMP table
+ ** and the table is dropped by a different database connection, the
+ ** trigger is not visible to the database connection that does the
+ ** drop so the trigger cannot be dropped. This results in an
+ ** "orphaned trigger" - a trigger whose associated table is missing.
+ **
+ ** 2020-11-05 see also https://sqlite.org/forum/forumpost/157dc791df
+ */
+ db->init.orphanTrigger = 1;
+ }
+ goto trigger_cleanup;
}
/*
@@ -324,6 +356,23 @@ void sqlite3FinishTrigger(
Vdbe *v;
char *z;
+ /* If this is a new CREATE TABLE statement, and if shadow tables
+ ** are read-only, and the trigger makes a change to a shadow table,
+ ** then raise an error - do not allow the trigger to be created. */
+ if( sqlite3ReadOnlyShadowTables(db) ){
+ TriggerStep *pStep;
+ for(pStep=pTrig->step_list; pStep; pStep=pStep->pNext){
+ if( pStep->zTarget!=0
+ && sqlite3ShadowTableName(db, pStep->zTarget)
+ ){
+ sqlite3ErrorMsg(pParse,
+ "trigger \"%s\" may not write to shadow table \"%s\"",
+ pTrig->zName, pStep->zTarget);
+ goto triggerfinish_cleanup;
+ }
+ }
+ }
+
/* Make an entry in the sqlite_schema table */
v = sqlite3GetVdbe(pParse);
if( v==0 ) goto triggerfinish_cleanup;
@@ -331,14 +380,14 @@ void sqlite3FinishTrigger(
z = sqlite3DbStrNDup(db, (char*)pAll->z, pAll->n);
testcase( z==0 );
sqlite3NestedParse(pParse,
- "INSERT INTO %Q." DFLT_SCHEMA_TABLE
+ "INSERT INTO %Q." LEGACY_SCHEMA_TABLE
" VALUES('trigger',%Q,%Q,0,'CREATE TRIGGER %q')",
db->aDb[iDb].zDbSName, zName,
pTrig->table, z);
sqlite3DbFree(db, z);
sqlite3ChangeCookie(pParse, iDb);
sqlite3VdbeAddParseSchemaOp(v, iDb,
- sqlite3MPrintf(db, "type='trigger' AND name='%q'", zName));
+ sqlite3MPrintf(db, "type='trigger' AND name='%q'", zName), 0);
}
if( db->init.busy ){
@@ -416,6 +465,7 @@ static TriggerStep *triggerStepAllocate(
sqlite3 *db = pParse->db;
TriggerStep *pTriggerStep;
+ if( pParse->nErr ) return 0;
pTriggerStep = sqlite3DbMallocZero(db, sizeof(TriggerStep) + pName->n + 1);
if( pTriggerStep ){
char *z = (char*)&pTriggerStep[1];
@@ -486,6 +536,7 @@ TriggerStep *sqlite3TriggerInsertStep(
TriggerStep *sqlite3TriggerUpdateStep(
Parse *pParse, /* Parser */
Token *pTableName, /* Name of the table to be updated */
+ SrcList *pFrom, /* FROM clause for an UPDATE-FROM, or NULL */
ExprList *pEList, /* The SET clause: list of column and new values */
Expr *pWhere, /* The WHERE clause */
u8 orconf, /* The conflict algorithm. (OE_Abort, OE_Ignore, etc) */
@@ -500,16 +551,20 @@ TriggerStep *sqlite3TriggerUpdateStep(
if( IN_RENAME_OBJECT ){
pTriggerStep->pExprList = pEList;
pTriggerStep->pWhere = pWhere;
+ pTriggerStep->pFrom = pFrom;
pEList = 0;
pWhere = 0;
+ pFrom = 0;
}else{
pTriggerStep->pExprList = sqlite3ExprListDup(db, pEList, EXPRDUP_REDUCE);
pTriggerStep->pWhere = sqlite3ExprDup(db, pWhere, EXPRDUP_REDUCE);
+ pTriggerStep->pFrom = sqlite3SrcListDup(db, pFrom, EXPRDUP_REDUCE);
}
pTriggerStep->orconf = orconf;
}
sqlite3ExprListDelete(db, pEList);
sqlite3ExprDelete(db, pWhere);
+ sqlite3SrcListDelete(db, pFrom);
return pTriggerStep;
}
@@ -546,7 +601,7 @@ TriggerStep *sqlite3TriggerDeleteStep(
** Recursively delete a Trigger structure
*/
void sqlite3DeleteTrigger(sqlite3 *db, Trigger *pTrigger){
- if( pTrigger==0 ) return;
+ if( pTrigger==0 || pTrigger->bReturning ) return;
sqlite3DeleteTriggerStep(db, pTrigger->step_list);
sqlite3DbFree(db, pTrigger->zName);
sqlite3DbFree(db, pTrigger->table);
@@ -588,7 +643,7 @@ void sqlite3DropTrigger(Parse *pParse, SrcList *pName, int noErr){
}
if( !pTrigger ){
if( !noErr ){
- sqlite3ErrorMsg(pParse, "no such trigger: %S", pName, 0);
+ sqlite3ErrorMsg(pParse, "no such trigger: %S", pName->a);
}else{
sqlite3CodeVerifyNamedSchema(pParse, zDb);
}
@@ -640,7 +695,7 @@ void sqlite3DropTriggerPtr(Parse *pParse, Trigger *pTrigger){
*/
if( (v = sqlite3GetVdbe(pParse))!=0 ){
sqlite3NestedParse(pParse,
- "DELETE FROM %Q." DFLT_SCHEMA_TABLE " WHERE name=%Q AND type='trigger'",
+ "DELETE FROM %Q." LEGACY_SCHEMA_TABLE " WHERE name=%Q AND type='trigger'",
db->aDb[iDb].zDbSName, pTrigger->zName
);
sqlite3ChangeCookie(pParse, iDb);
@@ -695,12 +750,21 @@ static int checkColumnOverlap(IdList *pIdList, ExprList *pEList){
}
/*
+** Return true if any TEMP triggers exist
+*/
+static int tempTriggersExist(sqlite3 *db){
+ if( NEVER(db->aDb[1].pSchema==0) ) return 0;
+ if( sqliteHashFirst(&db->aDb[1].pSchema->trigHash)==0 ) return 0;
+ return 1;
+}
+
+/*
** Return a list of all triggers on table pTab if there exists at least
** one trigger that must be fired when an operation of type 'op' is
** performed on the table, and, if that operation is an UPDATE, if at
** least one of the columns in pChanges is being modified.
*/
-Trigger *sqlite3TriggersExist(
+static SQLITE_NOINLINE Trigger *triggersReallyExist(
Parse *pParse, /* Parse context */
Table *pTab, /* The table the contains the triggers */
int op, /* one of TK_DELETE, TK_INSERT, TK_UPDATE */
@@ -711,20 +775,74 @@ Trigger *sqlite3TriggersExist(
Trigger *pList = 0;
Trigger *p;
- if( (pParse->db->flags & SQLITE_EnableTrigger)!=0 ){
- pList = sqlite3TriggerList(pParse, pTab);
- }
- assert( pList==0 || IsVirtual(pTab)==0 );
- for(p=pList; p; p=p->pNext){
- if( p->op==op && checkColumnOverlap(p->pColumns, pChanges) ){
- mask |= p->tr_tm;
+ pList = sqlite3TriggerList(pParse, pTab);
+ assert( pList==0 || IsVirtual(pTab)==0
+ || (pList->bReturning && pList->pNext==0) );
+ if( pList!=0 ){
+ p = pList;
+ if( (pParse->db->flags & SQLITE_EnableTrigger)==0
+ && pTab->pTrigger!=0
+ ){
+ /* The SQLITE_DBCONFIG_ENABLE_TRIGGER setting is off. That means that
+ ** only TEMP triggers are allowed. Truncate the pList so that it
+ ** includes only TEMP triggers */
+ if( pList==pTab->pTrigger ){
+ pList = 0;
+ goto exit_triggers_exist;
+ }
+ while( ALWAYS(p->pNext) && p->pNext!=pTab->pTrigger ) p = p->pNext;
+ p->pNext = 0;
+ p = pList;
}
+ do{
+ if( p->op==op && checkColumnOverlap(p->pColumns, pChanges) ){
+ mask |= p->tr_tm;
+ }else if( p->op==TK_RETURNING ){
+ /* The first time a RETURNING trigger is seen, the "op" value tells
+ ** us what time of trigger it should be. */
+ assert( sqlite3IsToplevel(pParse) );
+ p->op = op;
+ if( IsVirtual(pTab) ){
+ if( op!=TK_INSERT ){
+ sqlite3ErrorMsg(pParse,
+ "%s RETURNING is not available on virtual tables",
+ op==TK_DELETE ? "DELETE" : "UPDATE");
+ }
+ p->tr_tm = TRIGGER_BEFORE;
+ }else{
+ p->tr_tm = TRIGGER_AFTER;
+ }
+ mask |= p->tr_tm;
+ }else if( p->bReturning && p->op==TK_INSERT && op==TK_UPDATE
+ && sqlite3IsToplevel(pParse) ){
+ /* Also fire a RETURNING trigger for an UPSERT */
+ mask |= p->tr_tm;
+ }
+ p = p->pNext;
+ }while( p );
}
+exit_triggers_exist:
if( pMask ){
*pMask = mask;
}
return (mask ? pList : 0);
}
+Trigger *sqlite3TriggersExist(
+ Parse *pParse, /* Parse context */
+ Table *pTab, /* The table the contains the triggers */
+ int op, /* one of TK_DELETE, TK_INSERT, TK_UPDATE */
+ ExprList *pChanges, /* Columns that change in an UPDATE statement */
+ int *pMask /* OUT: Mask of TRIGGER_BEFORE|TRIGGER_AFTER */
+){
+ assert( pTab!=0 );
+ if( (pTab->pTrigger==0 && !tempTriggersExist(pParse->db))
+ || pParse->disableTriggers
+ ){
+ if( pMask ) *pMask = 0;
+ return 0;
+ }
+ return triggersReallyExist(pParse,pTab,op,pChanges,pMask);
+}
/*
** Convert the pStep->zTarget string into a SrcList and return a pointer
@@ -736,30 +854,188 @@ Trigger *sqlite3TriggersExist(
** trigger is in TEMP in which case it can refer to any other database it
** wants.
*/
-static SrcList *targetSrcList(
+SrcList *sqlite3TriggerStepSrc(
Parse *pParse, /* The parsing context */
TriggerStep *pStep /* The trigger containing the target token */
){
sqlite3 *db = pParse->db;
- int iDb; /* Index of the database to use */
- SrcList *pSrc; /* SrcList to be returned */
-
+ SrcList *pSrc; /* SrcList to be returned */
+ char *zName = sqlite3DbStrDup(db, pStep->zTarget);
pSrc = sqlite3SrcListAppend(pParse, 0, 0, 0);
+ assert( pSrc==0 || pSrc->nSrc==1 );
+ assert( zName || pSrc==0 );
if( pSrc ){
- assert( pSrc->nSrc>0 );
- pSrc->a[pSrc->nSrc-1].zName = sqlite3DbStrDup(db, pStep->zTarget);
- iDb = sqlite3SchemaToIndex(db, pStep->pTrig->pSchema);
- if( iDb==0 || iDb>=2 ){
- const char *zDb;
- assert( iDb<db->nDb );
- zDb = db->aDb[iDb].zDbSName;
- pSrc->a[pSrc->nSrc-1].zDatabase = sqlite3DbStrDup(db, zDb);
+ Schema *pSchema = pStep->pTrig->pSchema;
+ pSrc->a[0].zName = zName;
+ if( pSchema!=db->aDb[1].pSchema ){
+ pSrc->a[0].pSchema = pSchema;
+ }
+ if( pStep->pFrom ){
+ SrcList *pDup = sqlite3SrcListDup(db, pStep->pFrom, 0);
+ if( pDup && pDup->nSrc>1 && !IN_RENAME_OBJECT ){
+ Select *pSubquery;
+ Token as;
+ pSubquery = sqlite3SelectNew(pParse,0,pDup,0,0,0,0,SF_NestedFrom,0);
+ as.n = 0;
+ as.z = 0;
+ pDup = sqlite3SrcListAppendFromTerm(pParse,0,0,0,&as,pSubquery,0);
+ }
+ pSrc = sqlite3SrcListAppendList(pParse, pSrc, pDup);
}
+ }else{
+ sqlite3DbFree(db, zName);
}
return pSrc;
}
/*
+** Return true if the pExpr term from the RETURNING clause argument
+** list is of the form "*". Raise an error if the terms if of the
+** form "table.*".
+*/
+static int isAsteriskTerm(
+ Parse *pParse, /* Parsing context */
+ Expr *pTerm /* A term in the RETURNING clause */
+){
+ assert( pTerm!=0 );
+ if( pTerm->op==TK_ASTERISK ) return 1;
+ if( pTerm->op!=TK_DOT ) return 0;
+ assert( pTerm->pRight!=0 );
+ assert( pTerm->pLeft!=0 );
+ if( pTerm->pRight->op!=TK_ASTERISK ) return 0;
+ sqlite3ErrorMsg(pParse, "RETURNING may not use \"TABLE.*\" wildcards");
+ return 1;
+}
+
+/* The input list pList is the list of result set terms from a RETURNING
+** clause. The table that we are returning from is pTab.
+**
+** This routine makes a copy of the pList, and at the same time expands
+** any "*" wildcards to be the complete set of columns from pTab.
+*/
+static ExprList *sqlite3ExpandReturning(
+ Parse *pParse, /* Parsing context */
+ ExprList *pList, /* The arguments to RETURNING */
+ Table *pTab /* The table being updated */
+){
+ ExprList *pNew = 0;
+ sqlite3 *db = pParse->db;
+ int i;
+
+ for(i=0; i<pList->nExpr; i++){
+ Expr *pOldExpr = pList->a[i].pExpr;
+ if( NEVER(pOldExpr==0) ) continue;
+ if( isAsteriskTerm(pParse, pOldExpr) ){
+ int jj;
+ for(jj=0; jj<pTab->nCol; jj++){
+ Expr *pNewExpr;
+ if( IsHiddenColumn(pTab->aCol+jj) ) continue;
+ pNewExpr = sqlite3Expr(db, TK_ID, pTab->aCol[jj].zCnName);
+ pNew = sqlite3ExprListAppend(pParse, pNew, pNewExpr);
+ if( !db->mallocFailed ){
+ struct ExprList_item *pItem = &pNew->a[pNew->nExpr-1];
+ pItem->zEName = sqlite3DbStrDup(db, pTab->aCol[jj].zCnName);
+ pItem->fg.eEName = ENAME_NAME;
+ }
+ }
+ }else{
+ Expr *pNewExpr = sqlite3ExprDup(db, pOldExpr, 0);
+ pNew = sqlite3ExprListAppend(pParse, pNew, pNewExpr);
+ if( !db->mallocFailed && ALWAYS(pList->a[i].zEName!=0) ){
+ struct ExprList_item *pItem = &pNew->a[pNew->nExpr-1];
+ pItem->zEName = sqlite3DbStrDup(db, pList->a[i].zEName);
+ pItem->fg.eEName = pList->a[i].fg.eEName;
+ }
+ }
+ }
+ return pNew;
+}
+
+/*
+** Generate code for the RETURNING trigger. Unlike other triggers
+** that invoke a subprogram in the bytecode, the code for RETURNING
+** is generated in-line.
+*/
+static void codeReturningTrigger(
+ Parse *pParse, /* Parse context */
+ Trigger *pTrigger, /* The trigger step that defines the RETURNING */
+ Table *pTab, /* The table to code triggers from */
+ int regIn /* The first in an array of registers */
+){
+ Vdbe *v = pParse->pVdbe;
+ sqlite3 *db = pParse->db;
+ ExprList *pNew;
+ Returning *pReturning;
+ Select sSelect;
+ SrcList sFrom;
+
+ assert( v!=0 );
+ if( !pParse->bReturning ){
+ /* This RETURNING trigger must be for a different statement as
+ ** this statement lacks a RETURNING clause. */
+ return;
+ }
+ assert( db->pParse==pParse );
+ pReturning = pParse->u1.pReturning;
+ if( pTrigger != &(pReturning->retTrig) ){
+ /* This RETURNING trigger is for a different statement */
+ return;
+ }
+ memset(&sSelect, 0, sizeof(sSelect));
+ memset(&sFrom, 0, sizeof(sFrom));
+ sSelect.pEList = sqlite3ExprListDup(db, pReturning->pReturnEL, 0);
+ sSelect.pSrc = &sFrom;
+ sFrom.nSrc = 1;
+ sFrom.a[0].pTab = pTab;
+ sFrom.a[0].iCursor = -1;
+ sqlite3SelectPrep(pParse, &sSelect, 0);
+ if( pParse->nErr==0 ){
+ assert( db->mallocFailed==0 );
+ sqlite3GenerateColumnNames(pParse, &sSelect);
+ }
+ sqlite3ExprListDelete(db, sSelect.pEList);
+ pNew = sqlite3ExpandReturning(pParse, pReturning->pReturnEL, pTab);
+ if( pParse->nErr==0 ){
+ NameContext sNC;
+ memset(&sNC, 0, sizeof(sNC));
+ if( pReturning->nRetCol==0 ){
+ pReturning->nRetCol = pNew->nExpr;
+ pReturning->iRetCur = pParse->nTab++;
+ }
+ sNC.pParse = pParse;
+ sNC.uNC.iBaseReg = regIn;
+ sNC.ncFlags = NC_UBaseReg;
+ pParse->eTriggerOp = pTrigger->op;
+ pParse->pTriggerTab = pTab;
+ if( sqlite3ResolveExprListNames(&sNC, pNew)==SQLITE_OK
+ && ALWAYS(!db->mallocFailed)
+ ){
+ int i;
+ int nCol = pNew->nExpr;
+ int reg = pParse->nMem+1;
+ pParse->nMem += nCol+2;
+ pReturning->iRetReg = reg;
+ for(i=0; i<nCol; i++){
+ Expr *pCol = pNew->a[i].pExpr;
+ assert( pCol!=0 ); /* Due to !db->mallocFailed ~9 lines above */
+ sqlite3ExprCodeFactorable(pParse, pCol, reg+i);
+ if( sqlite3ExprAffinity(pCol)==SQLITE_AFF_REAL ){
+ sqlite3VdbeAddOp1(v, OP_RealAffinity, reg+i);
+ }
+ }
+ sqlite3VdbeAddOp3(v, OP_MakeRecord, reg, i, reg+i);
+ sqlite3VdbeAddOp2(v, OP_NewRowid, pReturning->iRetCur, reg+i+1);
+ sqlite3VdbeAddOp3(v, OP_Insert, pReturning->iRetCur, reg+i, reg+i+1);
+ }
+ }
+ sqlite3ExprListDelete(db, pNew);
+ pParse->eTriggerOp = 0;
+ pParse->pTriggerTab = 0;
+}
+
+
+
+/*
** Generate VDBE code for the statements inside the body of a single
** trigger.
*/
@@ -803,28 +1079,31 @@ static int codeTriggerProgram(
switch( pStep->op ){
case TK_UPDATE: {
sqlite3Update(pParse,
- targetSrcList(pParse, pStep),
+ sqlite3TriggerStepSrc(pParse, pStep),
sqlite3ExprListDup(db, pStep->pExprList, 0),
sqlite3ExprDup(db, pStep->pWhere, 0),
pParse->eOrconf, 0, 0, 0
);
+ sqlite3VdbeAddOp0(v, OP_ResetCount);
break;
}
case TK_INSERT: {
sqlite3Insert(pParse,
- targetSrcList(pParse, pStep),
+ sqlite3TriggerStepSrc(pParse, pStep),
sqlite3SelectDup(db, pStep->pSelect, 0),
sqlite3IdListDup(db, pStep->pIdList),
pParse->eOrconf,
sqlite3UpsertDup(db, pStep->pUpsert)
);
+ sqlite3VdbeAddOp0(v, OP_ResetCount);
break;
}
case TK_DELETE: {
sqlite3DeleteFrom(pParse,
- targetSrcList(pParse, pStep),
+ sqlite3TriggerStepSrc(pParse, pStep),
sqlite3ExprDup(db, pStep->pWhere, 0), 0, 0
);
+ sqlite3VdbeAddOp0(v, OP_ResetCount);
break;
}
default: assert( pStep->op==TK_SELECT ); {
@@ -836,9 +1115,6 @@ static int codeTriggerProgram(
break;
}
}
- if( pStep->op!=TK_SELECT ){
- sqlite3VdbeAddOp0(v, OP_ResetCount);
- }
}
return 0;
@@ -896,8 +1172,8 @@ static TriggerPrg *codeRowTrigger(
Vdbe *v; /* Temporary VM */
NameContext sNC; /* Name context for sub-vdbe */
SubProgram *pProgram = 0; /* Sub-vdbe for trigger program */
- Parse *pSubParse; /* Parse context for sub-vdbe */
int iEndTrigger = 0; /* Label to jump to if WHEN is false */
+ Parse sSubParse; /* Parse context for sub-vdbe */
assert( pTrigger->zName==0 || pTab==tableOfTrigger(pTrigger) );
assert( pTop->pVdbe );
@@ -919,19 +1195,17 @@ static TriggerPrg *codeRowTrigger(
/* Allocate and populate a new Parse context to use for coding the
** trigger sub-program. */
- pSubParse = sqlite3StackAllocZero(db, sizeof(Parse));
- if( !pSubParse ) return 0;
+ sqlite3ParseObjectInit(&sSubParse, db);
memset(&sNC, 0, sizeof(sNC));
- sNC.pParse = pSubParse;
- pSubParse->db = db;
- pSubParse->pTriggerTab = pTab;
- pSubParse->pToplevel = pTop;
- pSubParse->zAuthContext = pTrigger->zName;
- pSubParse->eTriggerOp = pTrigger->op;
- pSubParse->nQueryLoop = pParse->nQueryLoop;
- pSubParse->disableVtab = pParse->disableVtab;
-
- v = sqlite3GetVdbe(pSubParse);
+ sNC.pParse = &sSubParse;
+ sSubParse.pTriggerTab = pTab;
+ sSubParse.pToplevel = pTop;
+ sSubParse.zAuthContext = pTrigger->zName;
+ sSubParse.eTriggerOp = pTrigger->op;
+ sSubParse.nQueryLoop = pParse->nQueryLoop;
+ sSubParse.prepFlags = pParse->prepFlags;
+
+ v = sqlite3GetVdbe(&sSubParse);
if( v ){
VdbeComment((v, "Start: %s.%s (%s %s%s%s ON %s)",
pTrigger->zName, onErrorText(orconf),
@@ -954,17 +1228,17 @@ static TriggerPrg *codeRowTrigger(
** OP_Halt inserted at the end of the program. */
if( pTrigger->pWhen ){
pWhen = sqlite3ExprDup(db, pTrigger->pWhen, 0);
- if( SQLITE_OK==sqlite3ResolveExprNames(&sNC, pWhen)
- && db->mallocFailed==0
+ if( db->mallocFailed==0
+ && SQLITE_OK==sqlite3ResolveExprNames(&sNC, pWhen)
){
- iEndTrigger = sqlite3VdbeMakeLabel(pSubParse);
- sqlite3ExprIfFalse(pSubParse, pWhen, iEndTrigger, SQLITE_JUMPIFNULL);
+ iEndTrigger = sqlite3VdbeMakeLabel(&sSubParse);
+ sqlite3ExprIfFalse(&sSubParse, pWhen, iEndTrigger, SQLITE_JUMPIFNULL);
}
sqlite3ExprDelete(db, pWhen);
}
/* Code the trigger program into the sub-vdbe. */
- codeTriggerProgram(pSubParse, pTrigger->step_list, orconf);
+ codeTriggerProgram(&sSubParse, pTrigger->step_list, orconf);
/* Insert an OP_Halt at the end of the sub-program. */
if( iEndTrigger ){
@@ -972,24 +1246,24 @@ static TriggerPrg *codeRowTrigger(
}
sqlite3VdbeAddOp0(v, OP_Halt);
VdbeComment((v, "End: %s.%s", pTrigger->zName, onErrorText(orconf)));
+ transferParseError(pParse, &sSubParse);
- transferParseError(pParse, pSubParse);
- if( db->mallocFailed==0 && pParse->nErr==0 ){
+ if( pParse->nErr==0 ){
+ assert( db->mallocFailed==0 );
pProgram->aOp = sqlite3VdbeTakeOpArray(v, &pProgram->nOp, &pTop->nMaxArg);
}
- pProgram->nMem = pSubParse->nMem;
- pProgram->nCsr = pSubParse->nTab;
+ pProgram->nMem = sSubParse.nMem;
+ pProgram->nCsr = sSubParse.nTab;
pProgram->token = (void *)pTrigger;
- pPrg->aColmask[0] = pSubParse->oldmask;
- pPrg->aColmask[1] = pSubParse->newmask;
+ pPrg->aColmask[0] = sSubParse.oldmask;
+ pPrg->aColmask[1] = sSubParse.newmask;
sqlite3VdbeDelete(v);
+ }else{
+ transferParseError(pParse, &sSubParse);
}
- assert( !pSubParse->pAinc && !pSubParse->pZombieTab );
- assert( !pSubParse->pTriggerPrg && !pSubParse->nMaxArg );
- sqlite3ParserReset(pSubParse);
- sqlite3StackFree(db, pSubParse);
-
+ assert( !sSubParse.pTriggerPrg && !sSubParse.nMaxArg );
+ sqlite3ParseObjectReset(&sSubParse);
return pPrg;
}
@@ -1022,6 +1296,7 @@ static TriggerPrg *getRowTrigger(
/* If an existing TriggerPrg could not be located, create a new one. */
if( !pPrg ){
pPrg = codeRowTrigger(pParse, pTrigger, pTab, orconf);
+ pParse->db->errByteOffset = -1;
}
return pPrg;
@@ -1044,7 +1319,7 @@ void sqlite3CodeRowTriggerDirect(
Vdbe *v = sqlite3GetVdbe(pParse); /* Main VM */
TriggerPrg *pPrg;
pPrg = getRowTrigger(pParse, p, pTab, orconf);
- assert( pPrg || pParse->nErr || pParse->db->mallocFailed );
+ assert( pPrg || pParse->nErr );
/* Code the OP_Program opcode in the parent VDBE. P4 of the OP_Program
** is a pointer to the sub-vdbe containing the trigger program. */
@@ -1087,7 +1362,7 @@ void sqlite3CodeRowTriggerDirect(
** ... ...
** reg+N OLD.* value of right-most column of pTab
** reg+N+1 NEW.rowid
-** reg+N+2 OLD.* value of left-most column of pTab
+** reg+N+2 NEW.* value of left-most column of pTab
** ... ...
** reg+N+N+1 NEW.* value of right-most column of pTab
**
@@ -1132,12 +1407,20 @@ void sqlite3CodeRowTrigger(
assert( p->pSchema==p->pTabSchema
|| p->pSchema==pParse->db->aDb[1].pSchema );
- /* Determine whether we should code this trigger */
- if( p->op==op
+ /* Determine whether we should code this trigger. One of two choices:
+ ** 1. The trigger is an exact match to the current DML statement
+ ** 2. This is a RETURNING trigger for INSERT but we are currently
+ ** doing the UPDATE part of an UPSERT.
+ */
+ if( (p->op==op || (p->bReturning && p->op==TK_INSERT && op==TK_UPDATE))
&& p->tr_tm==tr_tm
&& checkColumnOverlap(p->pColumns, pChanges)
){
- sqlite3CodeRowTriggerDirect(pParse, p, pTab, reg, orconf, ignoreJump);
+ if( !p->bReturning ){
+ sqlite3CodeRowTriggerDirect(pParse, p, pTab, reg, orconf, ignoreJump);
+ }else if( sqlite3IsToplevel(pParse) ){
+ codeReturningTrigger(pParse, p, pTab, reg);
+ }
}
}
}
@@ -1181,14 +1464,22 @@ u32 sqlite3TriggerColmask(
Trigger *p;
assert( isNew==1 || isNew==0 );
+ if( IsView(pTab) ){
+ return 0xffffffff;
+ }
for(p=pTrigger; p; p=p->pNext){
- if( p->op==op && (tr_tm&p->tr_tm)
+ if( p->op==op
+ && (tr_tm&p->tr_tm)
&& checkColumnOverlap(p->pColumns,pChanges)
){
- TriggerPrg *pPrg;
- pPrg = getRowTrigger(pParse, p, pTab, orconf);
- if( pPrg ){
- mask |= pPrg->aColmask[isNew];
+ if( p->bReturning ){
+ mask = 0xffffffff;
+ }else{
+ TriggerPrg *pPrg;
+ pPrg = getRowTrigger(pParse, p, pTab, orconf);
+ if( pPrg ){
+ mask |= pPrg->aColmask[isNew];
+ }
}
}
}