/***** 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 the implementation for TRIGGERs*/#include "sqliteInt.h"#ifndef SQLITE_OMIT_TRIGGER/*** Delete a linked list of TriggerStep structures.*/void sqlite3DeleteTriggerStep(sqlite3 *db, TriggerStep *pTriggerStep){while( pTriggerStep ){TriggerStep * pTmp = pTriggerStep;pTriggerStep = pTriggerStep->pNext;sqlite3ExprDelete(db, pTmp->pWhere);sqlite3ExprListDelete(db, pTmp->pExprList);sqlite3SelectDelete(db, pTmp->pSelect);sqlite3IdListDelete(db, pTmp->pIdList);sqlite3UpsertDelete(db, pTmp->pUpsert);sqlite3SrcListDelete(db, pTmp->pFrom);sqlite3DbFree(db, pTmp->zSpan);sqlite3DbFree(db, pTmp);}}/*** Given table pTab, return a list of all the triggers attached to** the table. The list is connected by Trigger.pNext pointers.**** All of the triggers on pTab that are in the same database as pTab** are already attached to pTab->pTrigger. But there might be additional** triggers on pTab in the TEMP schema. This routine prepends all** TEMP triggers on pTab to the beginning of the pTab->pTrigger list** and returns the combined list.**** To state it another way: This routine returns a list of all triggers** that fire off of pTab. The list will include any TEMP triggers on** pTab as well as the triggers lised in pTab->pTrigger.*/Trigger *sqlite3TriggerList(Parse *pParse, Table *pTab){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_VIRTUALTABLEassert( pParse->db->pVtabCtx==0 );#endifassert( 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 0if( pList ){Trigger *pX;printf("Triggers for %s:", pTab->zName);for(pX=pList; pX; pX=pX->pNext){printf(" %s", pX->zName);}printf("\n");fflush(stdout);}#endifreturn pList;}/*** This is called by the parser when it sees a CREATE TRIGGER statement** up to the point of the BEGIN before the trigger actions. A Trigger** structure is generated based on the information available and stored** in pParse->pNewTrigger. After the trigger actions have been parsed, the** sqlite3FinishTrigger() function is called to complete the trigger** construction process.*/void sqlite3BeginTrigger(Parse *pParse, /* The parse context of the CREATE TRIGGER statement */Token *pName1, /* The name of the trigger */Token *pName2, /* The name of the trigger */int tr_tm, /* One of TK_BEFORE, TK_AFTER, TK_INSTEAD */int op, /* One of TK_INSERT, TK_UPDATE, TK_DELETE */IdList *pColumns, /* column list if this is an UPDATE OF trigger */SrcList *pTableName,/* The name of the table/view the trigger applies to */Expr *pWhen, /* WHEN clause */int isTemp, /* True if the TEMPORARY keyword is present */int noErr /* Suppress errors if the trigger already exists */){Trigger *pTrigger = 0; /* The new trigger */Table *pTab; /* Table that the trigger fires off of */char *zName = 0; /* Name of the trigger */sqlite3 *db = pParse->db; /* The database connection */int iDb; /* The database to store the trigger in */Token *pName; /* The unqualified db name */DbFixer sFix; /* State vector for the DB fixer */assert( pName1!=0 ); /* pName1->z might be NULL, but not pName1 itself */assert( pName2!=0 );assert( op==TK_INSERT || op==TK_UPDATE || op==TK_DELETE );assert( op>0 && op<0xff );if( isTemp ){/* If TEMP was specified, then the trigger name may not be qualified. */if( pName2->n>0 ){sqlite3ErrorMsg(pParse, "temporary trigger may not have qualified name");goto trigger_cleanup;}iDb = 1;pName = pName1;}else{/* Figure out the db that the trigger will be created in */iDb = sqlite3TwoPartName(pParse, pName1, pName2, &pName);if( iDb<0 ){goto trigger_cleanup;}}if( !pTableName || db->mallocFailed ){goto trigger_cleanup;}/* A long-standing parser bug is that this syntax was allowed:**** CREATE TRIGGER attached.demo AFTER INSERT ON attached.tab ....** ^^^^^^^^**** To maintain backwards compatibility, ignore the database** name on pTableName if we are reparsing out of the schema table*/if( db->init.busy && iDb!=1 ){sqlite3DbFree(db, pTableName->a[0].zDatabase);pTableName->a[0].zDatabase = 0;}/* If the trigger name was unqualified, and the table is a temp table,** then set iDb to 1 to create the trigger in the temporary database.** If sqlite3SrcListLookup() returns 0, indicating the table does not** exist, the error is caught by the block below.*/pTab = sqlite3SrcListLookup(pParse, pTableName);if( db->init.busy==0 && pName2->n==0 && pTab&& pTab->pSchema==db->aDb[1].pSchema ){iDb = 1;}/* Ensure the table name matches database name and that the table exists */if( db->mallocFailed ) goto trigger_cleanup;assert( pTableName->nSrc==1 );sqlite3FixInit(&sFix, pParse, iDb, "trigger", pName);if( sqlite3FixSrcList(&sFix, pTableName) ){goto trigger_cleanup;}pTab = sqlite3SrcListLookup(pParse, pTableName);if( !pTab ){/* The table does not exist. */goto trigger_orphan_error;}if( IsVirtual(pTab) ){sqlite3ErrorMsg(pParse, "cannot create triggers on virtual tables");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** specified name exists */zName = sqlite3NameFromToken(db, pName);if( zName==0 ){assert( db->mallocFailed );goto trigger_cleanup;}if( sqlite3CheckObjectName(pParse, zName, "trigger", pTab->zName) ){goto trigger_cleanup;}assert( sqlite3SchemaMutexHeld(db, iDb, 0) );if( !IN_RENAME_OBJECT ){if( sqlite3HashFind(&(db->aDb[iDb].pSchema->trigHash),zName) ){if( !noErr ){sqlite3ErrorMsg(pParse, "trigger %T already exists", pName);}else{assert( !db->init.busy );sqlite3CodeVerifySchema(pParse, iDb);VVA_ONLY( pParse->ifNotExists = 1; )}goto trigger_cleanup;}}/* Do not create a trigger on a system table */if( sqlite3StrNICmp(pTab->zName, "sqlite_", 7)==0 ){sqlite3ErrorMsg(pParse, "cannot create trigger on system table");goto trigger_cleanup;}/* INSTEAD of triggers are only for views and views only support INSTEAD** of triggers.*/if( IsView(pTab) && tr_tm!=TK_INSTEAD ){sqlite3ErrorMsg(pParse, "cannot create %s trigger on view: %S",(tr_tm == TK_BEFORE)?"BEFORE":"AFTER", pTableName->a);goto trigger_orphan_error;}if( !IsView(pTab) && tr_tm==TK_INSTEAD ){sqlite3ErrorMsg(pParse, "cannot create INSTEAD OF"" trigger on table: %S", pTableName->a);goto trigger_orphan_error;}#ifndef SQLITE_OMIT_AUTHORIZATIONif( !IN_RENAME_OBJECT ){int iTabDb = sqlite3SchemaToIndex(db, pTab->pSchema);int code = SQLITE_CREATE_TRIGGER;const char *zDb = db->aDb[iTabDb].zDbSName;const char *zDbTrig = isTemp ? db->aDb[1].zDbSName : zDb;if( iTabDb==1 || isTemp ) code = SQLITE_CREATE_TEMP_TRIGGER;if( sqlite3AuthCheck(pParse, code, zName, pTab->zName, zDbTrig) ){goto trigger_cleanup;}if( sqlite3AuthCheck(pParse, SQLITE_INSERT, SCHEMA_TABLE(iTabDb),0,zDb)){goto trigger_cleanup;}}#endif/* INSTEAD OF triggers can only appear on views and BEFORE triggers** cannot appear on views. So we might as well translate every** INSTEAD OF trigger into a BEFORE trigger. It simplifies code** elsewhere.*/if (tr_tm == TK_INSTEAD){tr_tm = TK_BEFORE;}/* Build the Trigger object */pTrigger = (Trigger*)sqlite3DbMallocZero(db, sizeof(Trigger));if( pTrigger==0 ) goto trigger_cleanup;pTrigger->zName = zName;zName = 0;pTrigger->table = sqlite3DbStrDup(db, pTableName->a[0].zName);pTrigger->pSchema = db->aDb[iDb].pSchema;pTrigger->pTabSchema = pTab->pSchema;pTrigger->op = (u8)op;pTrigger->tr_tm = tr_tm==TK_BEFORE ? TRIGGER_BEFORE : TRIGGER_AFTER;if( IN_RENAME_OBJECT ){sqlite3RenameTokenRemap(pParse, pTrigger->table, pTableName->a[0].zName);pTrigger->pWhen = pWhen;pWhen = 0;}else{pTrigger->pWhen = sqlite3ExprDup(db, pWhen, EXPRDUP_REDUCE);}pTrigger->pColumns = pColumns;pColumns = 0;assert( pParse->pNewTrigger==0 );pParse->pNewTrigger = pTrigger;trigger_cleanup:sqlite3DbFree(db, zName);sqlite3SrcListDelete(db, pTableName);sqlite3IdListDelete(db, pColumns);sqlite3ExprDelete(db, pWhen);if( !pParse->pNewTrigger ){sqlite3DeleteTrigger(db, pTrigger);}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;}/*** This routine is called after all of the trigger actions have been parsed** in order to complete the process of building the trigger.*/void sqlite3FinishTrigger(Parse *pParse, /* Parser context */TriggerStep *pStepList, /* The triggered program */Token *pAll /* Token that describes the complete CREATE TRIGGER */){Trigger *pTrig = pParse->pNewTrigger; /* Trigger being finished */char *zName; /* Name of trigger */sqlite3 *db = pParse->db; /* The database */DbFixer sFix; /* Fixer object */int iDb; /* Database containing the trigger */Token nameToken; /* Trigger name for error reporting */pParse->pNewTrigger = 0;if( NEVER(pParse->nErr) || !pTrig ) goto triggerfinish_cleanup;zName = pTrig->zName;iDb = sqlite3SchemaToIndex(pParse->db, pTrig->pSchema);pTrig->step_list = pStepList;while( pStepList ){pStepList->pTrig = pTrig;pStepList = pStepList->pNext;}sqlite3TokenInit(&nameToken, pTrig->zName);sqlite3FixInit(&sFix, pParse, iDb, "trigger", &nameToken);if( sqlite3FixTriggerStep(&sFix, pTrig->step_list)|| sqlite3FixExpr(&sFix, pTrig->pWhen)){goto triggerfinish_cleanup;}#ifndef SQLITE_OMIT_ALTERTABLEif( IN_RENAME_OBJECT ){assert( !db->init.busy );pParse->pNewTrigger = pTrig;pTrig = 0;}else#endif/* if we are not initializing,** build the sqlite_schema entry*/if( !db->init.busy ){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;sqlite3BeginWriteOperation(pParse, 0, iDb);z = sqlite3DbStrNDup(db, (char*)pAll->z, pAll->n);testcase( z==0 );sqlite3NestedParse(pParse,"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), 0);}if( db->init.busy ){Trigger *pLink = pTrig;Hash *pHash = &db->aDb[iDb].pSchema->trigHash;assert( sqlite3SchemaMutexHeld(db, iDb, 0) );assert( pLink!=0 );pTrig = sqlite3HashInsert(pHash, zName, pTrig);if( pTrig ){sqlite3OomFault(db);}else if( pLink->pSchema==pLink->pTabSchema ){Table *pTab;pTab = sqlite3HashFind(&pLink->pTabSchema->tblHash, pLink->table);assert( pTab!=0 );pLink->pNext = pTab->pTrigger;pTab->pTrigger = pLink;}}triggerfinish_cleanup:sqlite3DeleteTrigger(db, pTrig);assert( IN_RENAME_OBJECT || !pParse->pNewTrigger );sqlite3DeleteTriggerStep(db, pStepList);}/*** Duplicate a range of text from an SQL statement, then convert all** whitespace characters into ordinary space characters.*/static char *triggerSpanDup(sqlite3 *db, const char *zStart, const char *zEnd){char *z = sqlite3DbSpanDup(db, zStart, zEnd);int i;if( z ) for(i=0; z[i]; i++) if( sqlite3Isspace(z[i]) ) z[i] = ' ';return z;}/*** Turn a SELECT statement (that the pSelect parameter points to) into** a trigger step. Return a pointer to a TriggerStep structure.**** The parser calls this routine when it finds a SELECT statement in** body of a TRIGGER.*/TriggerStep *sqlite3TriggerSelectStep(sqlite3 *db, /* Database connection */Select *pSelect, /* The SELECT statement */const char *zStart, /* Start of SQL text */const char *zEnd /* End of SQL text */){TriggerStep *pTriggerStep = sqlite3DbMallocZero(db, sizeof(TriggerStep));if( pTriggerStep==0 ) {sqlite3SelectDelete(db, pSelect);return 0;}pTriggerStep->op = TK_SELECT;pTriggerStep->pSelect = pSelect;pTriggerStep->orconf = OE_Default;pTriggerStep->zSpan = triggerSpanDup(db, zStart, zEnd);return pTriggerStep;}/*** Allocate space to hold a new trigger step. The allocated space** holds both the TriggerStep object and the TriggerStep.target.z string.**** If an OOM error occurs, NULL is returned and db->mallocFailed is set.*/static TriggerStep *triggerStepAllocate(Parse *pParse, /* Parser context */u8 op, /* Trigger opcode */Token *pName, /* The target name */const char *zStart, /* Start of SQL text */const char *zEnd /* End of SQL text */){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];memcpy(z, pName->z, pName->n);sqlite3Dequote(z);pTriggerStep->zTarget = z;pTriggerStep->op = op;pTriggerStep->zSpan = triggerSpanDup(db, zStart, zEnd);if( IN_RENAME_OBJECT ){sqlite3RenameTokenMap(pParse, pTriggerStep->zTarget, pName);}}return pTriggerStep;}/*** Build a trigger step out of an INSERT statement. Return a pointer** to the new trigger step.**** The parser calls this routine when it sees an INSERT inside the** body of a trigger.*/TriggerStep *sqlite3TriggerInsertStep(Parse *pParse, /* Parser */Token *pTableName, /* Name of the table into which we insert */IdList *pColumn, /* List of columns in pTableName to insert into */Select *pSelect, /* A SELECT statement that supplies values */u8 orconf, /* The conflict algorithm (OE_Abort, OE_Replace, etc.) */Upsert *pUpsert, /* ON CONFLICT clauses for upsert */const char *zStart, /* Start of SQL text */const char *zEnd /* End of SQL text */){sqlite3 *db = pParse->db;TriggerStep *pTriggerStep;assert(pSelect != 0 || db->mallocFailed);pTriggerStep = triggerStepAllocate(pParse, TK_INSERT, pTableName,zStart,zEnd);if( pTriggerStep ){if( IN_RENAME_OBJECT ){pTriggerStep->pSelect = pSelect;pSelect = 0;}else{pTriggerStep->pSelect = sqlite3SelectDup(db, pSelect, EXPRDUP_REDUCE);}pTriggerStep->pIdList = pColumn;pTriggerStep->pUpsert = pUpsert;pTriggerStep->orconf = orconf;if( pUpsert ){sqlite3HasExplicitNulls(pParse, pUpsert->pUpsertTarget);}}else{testcase( pColumn );sqlite3IdListDelete(db, pColumn);testcase( pUpsert );sqlite3UpsertDelete(db, pUpsert);}sqlite3SelectDelete(db, pSelect);return pTriggerStep;}/*** Construct a trigger step that implements an UPDATE statement and return** a pointer to that trigger step. The parser calls this routine when it** sees an UPDATE statement inside the body of a CREATE TRIGGER.*/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) */const char *zStart, /* Start of SQL text */const char *zEnd /* End of SQL text */){sqlite3 *db = pParse->db;TriggerStep *pTriggerStep;pTriggerStep = triggerStepAllocate(pParse, TK_UPDATE, pTableName,zStart,zEnd);if( pTriggerStep ){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;}/*** Construct a trigger step that implements a DELETE statement and return** a pointer to that trigger step. The parser calls this routine when it** sees a DELETE statement inside the body of a CREATE TRIGGER.*/TriggerStep *sqlite3TriggerDeleteStep(Parse *pParse, /* Parser */Token *pTableName, /* The table from which rows are deleted */Expr *pWhere, /* The WHERE clause */const char *zStart, /* Start of SQL text */const char *zEnd /* End of SQL text */){sqlite3 *db = pParse->db;TriggerStep *pTriggerStep;pTriggerStep = triggerStepAllocate(pParse, TK_DELETE, pTableName,zStart,zEnd);if( pTriggerStep ){if( IN_RENAME_OBJECT ){pTriggerStep->pWhere = pWhere;pWhere = 0;}else{pTriggerStep->pWhere = sqlite3ExprDup(db, pWhere, EXPRDUP_REDUCE);}pTriggerStep->orconf = OE_Default;}sqlite3ExprDelete(db, pWhere);return pTriggerStep;}/*** Recursively delete a Trigger structure*/void sqlite3DeleteTrigger(sqlite3 *db, Trigger *pTrigger){if( pTrigger==0 || pTrigger->bReturning ) return;sqlite3DeleteTriggerStep(db, pTrigger->step_list);sqlite3DbFree(db, pTrigger->zName);sqlite3DbFree(db, pTrigger->table);sqlite3ExprDelete(db, pTrigger->pWhen);sqlite3IdListDelete(db, pTrigger->pColumns);sqlite3DbFree(db, pTrigger);}/*** This function is called to drop a trigger from the database schema.**** This may be called directly from the parser and therefore identifies** the trigger by name. The sqlite3DropTriggerPtr() routine does the** same job as this routine except it takes a pointer to the trigger** instead of the trigger name.**/void sqlite3DropTrigger(Parse *pParse, SrcList *pName, int noErr){Trigger *pTrigger = 0;int i;const char *zDb;const char *zName;sqlite3 *db = pParse->db;if( db->mallocFailed ) goto drop_trigger_cleanup;if( SQLITE_OK!=sqlite3ReadSchema(pParse) ){goto drop_trigger_cleanup;}assert( pName->nSrc==1 );zDb = pName->a[0].zDatabase;zName = pName->a[0].zName;assert( zDb!=0 || sqlite3BtreeHoldsAllMutexes(db) );for(i=OMIT_TEMPDB; i<db->nDb; i++){int j = (i<2) ? i^1 : i; /* Search TEMP before MAIN */if( zDb && sqlite3DbIsNamed(db, j, zDb)==0 ) continue;assert( sqlite3SchemaMutexHeld(db, j, 0) );pTrigger = sqlite3HashFind(&(db->aDb[j].pSchema->trigHash), zName);if( pTrigger ) break;}if( !pTrigger ){if( !noErr ){sqlite3ErrorMsg(pParse, "no such trigger: %S", pName->a);}else{sqlite3CodeVerifyNamedSchema(pParse, zDb);}pParse->checkSchema = 1;goto drop_trigger_cleanup;}sqlite3DropTriggerPtr(pParse, pTrigger);drop_trigger_cleanup:sqlite3SrcListDelete(db, pName);}/*** Return a pointer to the Table structure for the table that a trigger** is set on.*/static Table *tableOfTrigger(Trigger *pTrigger){return sqlite3HashFind(&pTrigger->pTabSchema->tblHash, pTrigger->table);}/*** Drop a trigger given a pointer to that trigger.*/void sqlite3DropTriggerPtr(Parse *pParse, Trigger *pTrigger){Table *pTable;Vdbe *v;sqlite3 *db = pParse->db;int iDb;iDb = sqlite3SchemaToIndex(pParse->db, pTrigger->pSchema);assert( iDb>=0 && iDb<db->nDb );pTable = tableOfTrigger(pTrigger);assert( (pTable && pTable->pSchema==pTrigger->pSchema) || iDb==1 );#ifndef SQLITE_OMIT_AUTHORIZATIONif( pTable ){int code = SQLITE_DROP_TRIGGER;const char *zDb = db->aDb[iDb].zDbSName;const char *zTab = SCHEMA_TABLE(iDb);if( iDb==1 ) code = SQLITE_DROP_TEMP_TRIGGER;if( sqlite3AuthCheck(pParse, code, pTrigger->zName, pTable->zName, zDb) ||sqlite3AuthCheck(pParse, SQLITE_DELETE, zTab, 0, zDb) ){return;}}#endif/* Generate code to destroy the database record of the trigger.*/if( (v = sqlite3GetVdbe(pParse))!=0 ){sqlite3NestedParse(pParse,"DELETE FROM %Q." LEGACY_SCHEMA_TABLE " WHERE name=%Q AND type='trigger'",db->aDb[iDb].zDbSName, pTrigger->zName);sqlite3ChangeCookie(pParse, iDb);sqlite3VdbeAddOp4(v, OP_DropTrigger, iDb, 0, 0, pTrigger->zName, 0);}}/*** Remove a trigger from the hash tables of the sqlite* pointer.*/void sqlite3UnlinkAndDeleteTrigger(sqlite3 *db, int iDb, const char *zName){Trigger *pTrigger;Hash *pHash;assert( sqlite3SchemaMutexHeld(db, iDb, 0) );pHash = &(db->aDb[iDb].pSchema->trigHash);pTrigger = sqlite3HashInsert(pHash, zName, 0);if( ALWAYS(pTrigger) ){if( pTrigger->pSchema==pTrigger->pTabSchema ){Table *pTab = tableOfTrigger(pTrigger);if( pTab ){Trigger **pp;for(pp=&pTab->pTrigger; *pp; pp=&((*pp)->pNext)){if( *pp==pTrigger ){*pp = (*pp)->pNext;break;}}}}sqlite3DeleteTrigger(db, pTrigger);db->mDbFlags |= DBFLAG_SchemaChange;}}/*** pEList is the SET clause of an UPDATE statement. Each entry** in pEList is of the format <id>=<expr>. If any of the entries** in pEList have an <id> which matches an identifier in pIdList,** then return TRUE. If pIdList==NULL, then it is considered a** wildcard that matches anything. Likewise if pEList==NULL then** it matches anything so always return true. Return false only** if there is no match.*/static int checkColumnOverlap(IdList *pIdList, ExprList *pEList){int e;if( pIdList==0 || NEVER(pEList==0) ) return 1;for(e=0; e<pEList->nExpr; e++){if( sqlite3IdListIndex(pIdList, pEList->a[e].zEName)>=0 ) return 1;}return 0;}/*** 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.*/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 */ExprList *pChanges, /* Columns that change in an UPDATE statement */int *pMask /* OUT: Mask of TRIGGER_BEFORE|TRIGGER_AFTER */){int mask = 0;Trigger *pList = 0;Trigger *p;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** to that SrcList.**** This routine adds a specific database name, if needed, to the target when** forming the SrcList. This prevents a trigger in one database from** referring to a target in another database. An exception is when the** trigger is in TEMP in which case it can refer to any other database it** wants.*/SrcList *sqlite3TriggerStepSrc(Parse *pParse, /* The parsing context */TriggerStep *pStep /* The trigger containing the target token */){sqlite3 *db = pParse->db;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 ){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;}/* If the Expr node is a subquery or an EXISTS operator or an IN operator that** uses a subquery, and if the subquery is SF_Correlated, then mark the** expression as EP_VarSelect.*/static int sqlite3ReturningSubqueryVarSelect(Walker *NotUsed, Expr *pExpr){UNUSED_PARAMETER(NotUsed);if( ExprUseXSelect(pExpr)&& (pExpr->x.pSelect->selFlags & SF_Correlated)!=0){testcase( ExprHasProperty(pExpr, EP_VarSelect) );ExprSetProperty(pExpr, EP_VarSelect);}return WRC_Continue;}/*** If the SELECT references the table pWalker->u.pTab, then do two things:**** (1) Mark the SELECT as as SF_Correlated.** (2) Set pWalker->eCode to non-zero so that the caller will know** that (1) has happened.*/static int sqlite3ReturningSubqueryCorrelated(Walker *pWalker, Select *pSelect){int i;SrcList *pSrc;assert( pSelect!=0 );pSrc = pSelect->pSrc;assert( pSrc!=0 );for(i=0; i<pSrc->nSrc; i++){if( pSrc->a[i].pTab==pWalker->u.pTab ){testcase( pSelect->selFlags & SF_Correlated );pSelect->selFlags |= SF_Correlated;pWalker->eCode = 1;break;}}return WRC_Continue;}/*** Scan the expression list that is the argument to RETURNING looking** for subqueries that depend on the table which is being modified in the** statement that is hosting the RETURNING clause (pTab). Mark all such** subqueries as SF_Correlated. If the subqueries are part of an** expression, mark the expression as EP_VarSelect.**** https://sqlite.org/forum/forumpost/2c83569ce8945d39*/static void sqlite3ProcessReturningSubqueries(ExprList *pEList,Table *pTab){Walker w;memset(&w, 0, sizeof(w));w.xExprCallback = sqlite3ExprWalkNoop;w.xSelectCallback = sqlite3ReturningSubqueryCorrelated;w.u.pTab = pTab;sqlite3WalkExprList(&w, pEList);if( w.eCode ){w.xExprCallback = sqlite3ReturningSubqueryVarSelect;w.xSelectCallback = sqlite3SelectWalkNoop;sqlite3WalkExprList(&w, pEList);}}/*** 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].zName = pTab->zName; /* tag-20240424-1 */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;sqlite3ProcessReturningSubqueries(pNew, pTab);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.*/static int codeTriggerProgram(Parse *pParse, /* The parser context */TriggerStep *pStepList, /* List of statements inside the trigger body */int orconf /* Conflict algorithm. (OE_Abort, etc) */){TriggerStep *pStep;Vdbe *v = pParse->pVdbe;sqlite3 *db = pParse->db;assert( pParse->pTriggerTab && pParse->pToplevel );assert( pStepList );assert( v!=0 );for(pStep=pStepList; pStep; pStep=pStep->pNext){/* Figure out the ON CONFLICT policy that will be used for this step** of the trigger program. If the statement that caused this trigger** to fire had an explicit ON CONFLICT, then use it. Otherwise, use** the ON CONFLICT policy that was specified as part of the trigger** step statement. Example:**** CREATE TRIGGER AFTER INSERT ON t1 BEGIN;** INSERT OR REPLACE INTO t2 VALUES(new.a, new.b);** END;**** INSERT INTO t1 ... ; -- insert into t2 uses REPLACE policy** INSERT OR IGNORE INTO t1 ... ; -- insert into t2 uses IGNORE policy*/pParse->eOrconf = (orconf==OE_Default)?pStep->orconf:(u8)orconf;assert( pParse->okConstFactor==0 );#ifndef SQLITE_OMIT_TRACEif( pStep->zSpan ){sqlite3VdbeAddOp4(v, OP_Trace, 0x7fffffff, 1, 0,sqlite3MPrintf(db, "-- %s", pStep->zSpan),P4_DYNAMIC);}#endifswitch( pStep->op ){case TK_UPDATE: {sqlite3Update(pParse,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,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,sqlite3TriggerStepSrc(pParse, pStep),sqlite3ExprDup(db, pStep->pWhere, 0), 0, 0);sqlite3VdbeAddOp0(v, OP_ResetCount);break;}default: assert( pStep->op==TK_SELECT ); {SelectDest sDest;Select *pSelect = sqlite3SelectDup(db, pStep->pSelect, 0);sqlite3SelectDestInit(&sDest, SRT_Discard, 0);sqlite3Select(pParse, pSelect, &sDest);sqlite3SelectDelete(db, pSelect);break;}}}return 0;}#ifdef SQLITE_ENABLE_EXPLAIN_COMMENTS/*** This function is used to add VdbeComment() annotations to a VDBE** program. It is not used in production code, only for debugging.*/static const char *onErrorText(int onError){switch( onError ){case OE_Abort: return "abort";case OE_Rollback: return "rollback";case OE_Fail: return "fail";case OE_Replace: return "replace";case OE_Ignore: return "ignore";case OE_Default: return "default";}return "n/a";}#endif/*** Parse context structure pFrom has just been used to create a sub-vdbe** (trigger program). If an error has occurred, transfer error information** from pFrom to pTo.*/static void transferParseError(Parse *pTo, Parse *pFrom){assert( pFrom->zErrMsg==0 || pFrom->nErr );assert( pTo->zErrMsg==0 || pTo->nErr );if( pTo->nErr==0 ){pTo->zErrMsg = pFrom->zErrMsg;pTo->nErr = pFrom->nErr;pTo->rc = pFrom->rc;}else{sqlite3DbFree(pFrom->db, pFrom->zErrMsg);}}/*** Create and populate a new TriggerPrg object with a sub-program** implementing trigger pTrigger with ON CONFLICT policy orconf.*/static TriggerPrg *codeRowTrigger(Parse *pParse, /* Current parse context */Trigger *pTrigger, /* Trigger to code */Table *pTab, /* The table pTrigger is attached to */int orconf /* ON CONFLICT policy to code trigger program with */){Parse *pTop = sqlite3ParseToplevel(pParse);sqlite3 *db = pParse->db; /* Database handle */TriggerPrg *pPrg; /* Value to return */Expr *pWhen = 0; /* Duplicate of trigger WHEN expression */Vdbe *v; /* Temporary VM */NameContext sNC; /* Name context for sub-vdbe */SubProgram *pProgram = 0; /* Sub-vdbe for trigger program */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 );/* Allocate the TriggerPrg and SubProgram objects. To ensure that they** are freed if an error occurs, link them into the Parse.pTriggerPrg** list of the top-level Parse object sooner rather than later. */pPrg = sqlite3DbMallocZero(db, sizeof(TriggerPrg));if( !pPrg ) return 0;pPrg->pNext = pTop->pTriggerPrg;pTop->pTriggerPrg = pPrg;pPrg->pProgram = pProgram = sqlite3DbMallocZero(db, sizeof(SubProgram));if( !pProgram ) return 0;sqlite3VdbeLinkSubProgram(pTop->pVdbe, pProgram);pPrg->pTrigger = pTrigger;pPrg->orconf = orconf;pPrg->aColmask[0] = 0xffffffff;pPrg->aColmask[1] = 0xffffffff;/* Allocate and populate a new Parse context to use for coding the** trigger sub-program. */sqlite3ParseObjectInit(&sSubParse, db);memset(&sNC, 0, sizeof(sNC));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),(pTrigger->tr_tm==TRIGGER_BEFORE ? "BEFORE" : "AFTER"),(pTrigger->op==TK_UPDATE ? "UPDATE" : ""),(pTrigger->op==TK_INSERT ? "INSERT" : ""),(pTrigger->op==TK_DELETE ? "DELETE" : ""),pTab->zName));#ifndef SQLITE_OMIT_TRACEif( pTrigger->zName ){sqlite3VdbeChangeP4(v, -1,sqlite3MPrintf(db, "-- TRIGGER %s", pTrigger->zName), P4_DYNAMIC);}#endif/* If one was specified, code the WHEN clause. If it evaluates to false** (or NULL) the sub-vdbe is immediately halted by jumping to the** OP_Halt inserted at the end of the program. */if( pTrigger->pWhen ){pWhen = sqlite3ExprDup(db, pTrigger->pWhen, 0);if( db->mallocFailed==0&& SQLITE_OK==sqlite3ResolveExprNames(&sNC, pWhen)){iEndTrigger = sqlite3VdbeMakeLabel(&sSubParse);sqlite3ExprIfFalse(&sSubParse, pWhen, iEndTrigger, SQLITE_JUMPIFNULL);}sqlite3ExprDelete(db, pWhen);}/* Code the trigger program into the sub-vdbe. */codeTriggerProgram(&sSubParse, pTrigger->step_list, orconf);/* Insert an OP_Halt at the end of the sub-program. */if( iEndTrigger ){sqlite3VdbeResolveLabel(v, iEndTrigger);}sqlite3VdbeAddOp0(v, OP_Halt);VdbeComment((v, "End: %s.%s", pTrigger->zName, onErrorText(orconf)));transferParseError(pParse, &sSubParse);if( pParse->nErr==0 ){assert( db->mallocFailed==0 );pProgram->aOp = sqlite3VdbeTakeOpArray(v, &pProgram->nOp, &pTop->nMaxArg);}pProgram->nMem = sSubParse.nMem;pProgram->nCsr = sSubParse.nTab;pProgram->token = (void *)pTrigger;pPrg->aColmask[0] = sSubParse.oldmask;pPrg->aColmask[1] = sSubParse.newmask;sqlite3VdbeDelete(v);}else{transferParseError(pParse, &sSubParse);}assert( !sSubParse.pTriggerPrg && !sSubParse.nMaxArg );sqlite3ParseObjectReset(&sSubParse);return pPrg;}/*** Return a pointer to a TriggerPrg object containing the sub-program for** trigger pTrigger with default ON CONFLICT algorithm orconf. If no such** TriggerPrg object exists, a new object is allocated and populated before** being returned.*/static TriggerPrg *getRowTrigger(Parse *pParse, /* Current parse context */Trigger *pTrigger, /* Trigger to code */Table *pTab, /* The table trigger pTrigger is attached to */int orconf /* ON CONFLICT algorithm. */){Parse *pRoot = sqlite3ParseToplevel(pParse);TriggerPrg *pPrg;assert( pTrigger->zName==0 || pTab==tableOfTrigger(pTrigger) );/* It may be that this trigger has already been coded (or is in the** process of being coded). If this is the case, then an entry with** a matching TriggerPrg.pTrigger field will be present somewhere** in the Parse.pTriggerPrg list. Search for such an entry. */for(pPrg=pRoot->pTriggerPrg;pPrg && (pPrg->pTrigger!=pTrigger || pPrg->orconf!=orconf);pPrg=pPrg->pNext);/* 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;}/*** Generate code for the trigger program associated with trigger p on** table pTab. The reg, orconf and ignoreJump parameters passed to this** function are the same as those described in the header function for** sqlite3CodeRowTrigger()*/void sqlite3CodeRowTriggerDirect(Parse *pParse, /* Parse context */Trigger *p, /* Trigger to code */Table *pTab, /* The table to code triggers from */int reg, /* Reg array containing OLD.* and NEW.* values */int orconf, /* ON CONFLICT policy */int ignoreJump /* Instruction to jump to for RAISE(IGNORE) */){Vdbe *v = sqlite3GetVdbe(pParse); /* Main VM */TriggerPrg *pPrg;pPrg = getRowTrigger(pParse, p, pTab, orconf);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. */if( pPrg ){int bRecursive = (p->zName && 0==(pParse->db->flags&SQLITE_RecTriggers));sqlite3VdbeAddOp4(v, OP_Program, reg, ignoreJump, ++pParse->nMem,(const char *)pPrg->pProgram, P4_SUBPROGRAM);VdbeComment((v, "Call: %s.%s", (p->zName?p->zName:"fkey"), onErrorText(orconf)));/* Set the P5 operand of the OP_Program instruction to non-zero if** recursive invocation of this trigger program is disallowed. Recursive** invocation is disallowed if (a) the sub-program is really a trigger,** not a foreign key action, and (b) the flag to enable recursive triggers** is clear. */sqlite3VdbeChangeP5(v, (u8)bRecursive);}}/*** This is called to code the required FOR EACH ROW triggers for an operation** on table pTab. The operation to code triggers for (INSERT, UPDATE or DELETE)** is given by the op parameter. The tr_tm parameter determines whether the** BEFORE or AFTER triggers are coded. If the operation is an UPDATE, then** parameter pChanges is passed the list of columns being modified.**** If there are no triggers that fire at the specified time for the specified** operation on pTab, this function is a no-op.**** The reg argument is the address of the first in an array of registers** that contain the values substituted for the new.* and old.* references** in the trigger program. If N is the number of columns in table pTab** (a copy of pTab->nCol), then registers are populated as follows:**** Register Contains** ------------------------------------------------------** reg+0 OLD.rowid** reg+1 OLD.* value of left-most column of pTab** ... ...** reg+N OLD.* value of right-most column of pTab** reg+N+1 NEW.rowid** reg+N+2 NEW.* value of left-most column of pTab** ... ...** reg+N+N+1 NEW.* value of right-most column of pTab**** For ON DELETE triggers, the registers containing the NEW.* values will** never be accessed by the trigger program, so they are not allocated or** populated by the caller (there is no data to populate them with anyway).** Similarly, for ON INSERT triggers the values stored in the OLD.* registers** are never accessed, and so are not allocated by the caller. So, for an** ON INSERT trigger, the value passed to this function as parameter reg** is not a readable register, although registers (reg+N) through** (reg+N+N+1) are.**** Parameter orconf is the default conflict resolution algorithm for the** trigger program to use (REPLACE, IGNORE etc.). Parameter ignoreJump** is the instruction that control should jump to if a trigger program** raises an IGNORE exception.*/void sqlite3CodeRowTrigger(Parse *pParse, /* Parse context */Trigger *pTrigger, /* List of triggers on table pTab */int op, /* One of TK_UPDATE, TK_INSERT, TK_DELETE */ExprList *pChanges, /* Changes list for any UPDATE OF triggers */int tr_tm, /* One of TRIGGER_BEFORE, TRIGGER_AFTER */Table *pTab, /* The table to code triggers from */int reg, /* The first in an array of registers (see above) */int orconf, /* ON CONFLICT policy */int ignoreJump /* Instruction to jump to for RAISE(IGNORE) */){Trigger *p; /* Used to iterate through pTrigger list */assert( op==TK_UPDATE || op==TK_INSERT || op==TK_DELETE );assert( tr_tm==TRIGGER_BEFORE || tr_tm==TRIGGER_AFTER );assert( (op==TK_UPDATE)==(pChanges!=0) );for(p=pTrigger; p; p=p->pNext){/* Sanity checking: The schema for the trigger and for the table are** always defined. The trigger must be in the same schema as the table** or else it must be a TEMP trigger. */assert( p->pSchema!=0 );assert( p->pTabSchema!=0 );assert( p->pSchema==p->pTabSchema|| p->pSchema==pParse->db->aDb[1].pSchema );/* 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)){if( !p->bReturning ){sqlite3CodeRowTriggerDirect(pParse, p, pTab, reg, orconf, ignoreJump);}else if( sqlite3IsToplevel(pParse) ){codeReturningTrigger(pParse, p, pTab, reg);}}}}/*** Triggers may access values stored in the old.* or new.* pseudo-table.** This function returns a 32-bit bitmask indicating which columns of the** old.* or new.* tables actually are used by triggers. This information** may be used by the caller, for example, to avoid having to load the entire** old.* record into memory when executing an UPDATE or DELETE command.**** Bit 0 of the returned mask is set if the left-most column of the** table may be accessed using an [old|new].<col> reference. Bit 1 is set if** the second leftmost column value is required, and so on. If there** are more than 32 columns in the table, and at least one of the columns** with an index greater than 32 may be accessed, 0xffffffff is returned.**** It is not possible to determine if the old.rowid or new.rowid column is** accessed by triggers. The caller must always assume that it is.**** Parameter isNew must be either 1 or 0. If it is 0, then the mask returned** applies to the old.* table. If 1, the new.* table.**** Parameter tr_tm must be a mask with one or both of the TRIGGER_BEFORE** and TRIGGER_AFTER bits set. Values accessed by BEFORE triggers are only** included in the returned mask if the TRIGGER_BEFORE bit is set in the** tr_tm parameter. Similarly, values accessed by AFTER triggers are only** included in the returned mask if the TRIGGER_AFTER bit is set in tr_tm.*/u32 sqlite3TriggerColmask(Parse *pParse, /* Parse context */Trigger *pTrigger, /* List of triggers on table pTab */ExprList *pChanges, /* Changes list for any UPDATE OF triggers */int isNew, /* 1 for new.* ref mask, 0 for old.* ref mask */int tr_tm, /* Mask of TRIGGER_BEFORE|TRIGGER_AFTER */Table *pTab, /* The table to code triggers from */int orconf /* Default ON CONFLICT policy for trigger steps */){const int op = pChanges ? TK_UPDATE : TK_DELETE;u32 mask = 0;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)&& checkColumnOverlap(p->pColumns,pChanges)){if( p->bReturning ){mask = 0xffffffff;}else{TriggerPrg *pPrg;pPrg = getRowTrigger(pParse, p, pTab, orconf);if( pPrg ){mask |= pPrg->aColmask[isNew];}}}}return mask;}#endif /* !defined(SQLITE_OMIT_TRIGGER) */
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。