📄 trigger.c
字号:
/***** 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.*****************************************************************************/#include "sqliteInt.h"/*** Delete a linked list of TriggerStep structures.*/void sqliteDeleteTriggerStep(TriggerStep *pTriggerStep){ while( pTriggerStep ){ TriggerStep * pTmp = pTriggerStep; pTriggerStep = pTriggerStep->pNext; if( pTmp->target.dyn ) sqliteFree((char*)pTmp->target.z); sqliteExprDelete(pTmp->pWhere); sqliteExprListDelete(pTmp->pExprList); sqliteSelectDelete(pTmp->pSelect); sqliteIdListDelete(pTmp->pIdList); sqliteFree(pTmp); }}/*** 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** sqliteFinishTrigger() function is called to complete the trigger** construction process.*/void sqliteBeginTrigger( Parse *pParse, /* The parse context of the CREATE TRIGGER statement */ Token *pName, /* 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 */ int foreach, /* One of TK_ROW or TK_STATEMENT */ Expr *pWhen, /* WHEN clause */ int isTemp /* True if the TEMPORARY keyword is present */){ Trigger *nt; Table *tab; char *zName = 0; /* Name of the trigger */ sqlite *db = pParse->db; int iDb; /* When database to store the trigger in */ DbFixer sFix; /* Check that: ** 1. the trigger name does not already exist. ** 2. the table (or view) does exist in the same database as the trigger. ** 3. that we are not trying to create a trigger on the sqlite_master table ** 4. That we are not trying to create an INSTEAD OF trigger on a table. ** 5. That we are not trying to create a BEFORE or AFTER trigger on a view. */ if( sqlite_malloc_failed ) goto trigger_cleanup; assert( pTableName->nSrc==1 ); if( db->init.busy && sqliteFixInit(&sFix, pParse, db->init.iDb, "trigger", pName) && sqliteFixSrcList(&sFix, pTableName) ){ goto trigger_cleanup; } tab = sqliteSrcListLookup(pParse, pTableName); if( !tab ){ goto trigger_cleanup; } iDb = isTemp ? 1 : tab->iDb; if( iDb>=2 && !db->init.busy ){ sqliteErrorMsg(pParse, "triggers may not be added to auxiliary " "database %s", db->aDb[tab->iDb].zName); goto trigger_cleanup; } zName = sqliteStrNDup(pName->z, pName->n); sqliteDequote(zName); if( sqliteHashFind(&(db->aDb[iDb].trigHash), zName,pName->n+1) ){ sqliteErrorMsg(pParse, "trigger %T already exists", pName); goto trigger_cleanup; } if( sqliteStrNICmp(tab->zName, "sqlite_", 7)==0 ){ sqliteErrorMsg(pParse, "cannot create trigger on system table"); pParse->nErr++; goto trigger_cleanup; } if( tab->pSelect && tr_tm != TK_INSTEAD ){ sqliteErrorMsg(pParse, "cannot create %s trigger on view: %S", (tr_tm == TK_BEFORE)?"BEFORE":"AFTER", pTableName, 0); goto trigger_cleanup; } if( !tab->pSelect && tr_tm == TK_INSTEAD ){ sqliteErrorMsg(pParse, "cannot create INSTEAD OF" " trigger on table: %S", pTableName, 0); goto trigger_cleanup; }#ifndef SQLITE_OMIT_AUTHORIZATION { int code = SQLITE_CREATE_TRIGGER; const char *zDb = db->aDb[tab->iDb].zName; const char *zDbTrig = isTemp ? db->aDb[1].zName : zDb; if( tab->iDb==1 || isTemp ) code = SQLITE_CREATE_TEMP_TRIGGER; if( sqliteAuthCheck(pParse, code, zName, tab->zName, zDbTrig) ){ goto trigger_cleanup; } if( sqliteAuthCheck(pParse, SQLITE_INSERT, SCHEMA_TABLE(tab->iDb), 0, zDb)){ goto trigger_cleanup; } }#endif /* INSTEAD OF triggers can only appear on views and BEGIN 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 */ nt = (Trigger*)sqliteMalloc(sizeof(Trigger)); if( nt==0 ) goto trigger_cleanup; nt->name = zName; zName = 0; nt->table = sqliteStrDup(pTableName->a[0].zName); if( sqlite_malloc_failed ) goto trigger_cleanup; nt->iDb = iDb; nt->iTabDb = tab->iDb; nt->op = op; nt->tr_tm = tr_tm; nt->pWhen = sqliteExprDup(pWhen); nt->pColumns = sqliteIdListDup(pColumns); nt->foreach = foreach; sqliteTokenCopy(&nt->nameToken,pName); assert( pParse->pNewTrigger==0 ); pParse->pNewTrigger = nt;trigger_cleanup: sqliteFree(zName); sqliteSrcListDelete(pTableName); sqliteIdListDelete(pColumns); sqliteExprDelete(pWhen);}/*** This routine is called after all of the trigger actions have been parsed** in order to complete the process of building the trigger.*/void sqliteFinishTrigger( Parse *pParse, /* Parser context */ TriggerStep *pStepList, /* The triggered program */ Token *pAll /* Token that describes the complete CREATE TRIGGER */){ Trigger *nt = 0; /* The trigger whose construction is finishing up */ sqlite *db = pParse->db; /* The database */ DbFixer sFix; if( pParse->nErr || pParse->pNewTrigger==0 ) goto triggerfinish_cleanup; nt = pParse->pNewTrigger; pParse->pNewTrigger = 0; nt->step_list = pStepList; while( pStepList ){ pStepList->pTrig = nt; pStepList = pStepList->pNext; } if( sqliteFixInit(&sFix, pParse, nt->iDb, "trigger", &nt->nameToken) && sqliteFixTriggerStep(&sFix, nt->step_list) ){ goto triggerfinish_cleanup; } /* if we are not initializing, and this trigger is not on a TEMP table, ** build the sqlite_master entry */ if( !db->init.busy ){ static VdbeOpList insertTrig[] = { { OP_NewRecno, 0, 0, 0 }, { OP_String, 0, 0, "trigger" }, { OP_String, 0, 0, 0 }, /* 2: trigger name */ { OP_String, 0, 0, 0 }, /* 3: table name */ { OP_Integer, 0, 0, 0 }, { OP_String, 0, 0, 0 }, /* 5: SQL */ { OP_MakeRecord, 5, 0, 0 }, { OP_PutIntKey, 0, 0, 0 }, }; int addr; Vdbe *v; /* Make an entry in the sqlite_master table */ v = sqliteGetVdbe(pParse); if( v==0 ) goto triggerfinish_cleanup; sqliteBeginWriteOperation(pParse, 0, 0); sqliteOpenMasterTable(v, nt->iDb); addr = sqliteVdbeAddOpList(v, ArraySize(insertTrig), insertTrig); sqliteVdbeChangeP3(v, addr+2, nt->name, 0); sqliteVdbeChangeP3(v, addr+3, nt->table, 0); sqliteVdbeChangeP3(v, addr+5, pAll->z, pAll->n); if( nt->iDb==0 ){ sqliteChangeCookie(db, v); } sqliteVdbeAddOp(v, OP_Close, 0, 0); sqliteEndWriteOperation(pParse); } if( !pParse->explain ){ Table *pTab; sqliteHashInsert(&db->aDb[nt->iDb].trigHash, nt->name, strlen(nt->name)+1, nt); pTab = sqliteLocateTable(pParse, nt->table, db->aDb[nt->iTabDb].zName); assert( pTab!=0 ); nt->pNext = pTab->pTrigger; pTab->pTrigger = nt; nt = 0; }triggerfinish_cleanup: sqliteDeleteTrigger(nt); sqliteDeleteTrigger(pParse->pNewTrigger); pParse->pNewTrigger = 0; sqliteDeleteTriggerStep(pStepList);}/*** Make a copy of all components of the given trigger step. This has** the effect of copying all Expr.token.z values into memory obtained** from sqliteMalloc(). As initially created, the Expr.token.z values** all point to the input string that was fed to the parser. But that** string is ephemeral - it will go away as soon as the sqlite_exec()** call that started the parser exits. This routine makes a persistent** copy of all the Expr.token.z strings so that the TriggerStep structure** will be valid even after the sqlite_exec() call returns.*/static void sqlitePersistTriggerStep(TriggerStep *p){ if( p->target.z ){ p->target.z = sqliteStrNDup(p->target.z, p->target.n); p->target.dyn = 1; } if( p->pSelect ){ Select *pNew = sqliteSelectDup(p->pSelect); sqliteSelectDelete(p->pSelect); p->pSelect = pNew; } if( p->pWhere ){ Expr *pNew = sqliteExprDup(p->pWhere); sqliteExprDelete(p->pWhere); p->pWhere = pNew; } if( p->pExprList ){ ExprList *pNew = sqliteExprListDup(p->pExprList); sqliteExprListDelete(p->pExprList); p->pExprList = pNew; } if( p->pIdList ){ IdList *pNew = sqliteIdListDup(p->pIdList); sqliteIdListDelete(p->pIdList); p->pIdList = pNew; }}/*** 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 *sqliteTriggerSelectStep(Select *pSelect){ TriggerStep *pTriggerStep = sqliteMalloc(sizeof(TriggerStep)); if( pTriggerStep==0 ) return 0; pTriggerStep->op = TK_SELECT; pTriggerStep->pSelect = pSelect; pTriggerStep->orconf = OE_Default; sqlitePersistTriggerStep(pTriggerStep); 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 *sqliteTriggerInsertStep( Token *pTableName, /* Name of the table into which we insert */ IdList *pColumn, /* List of columns in pTableName to insert into */ ExprList *pEList, /* The VALUE clause: a list of values to be inserted */ Select *pSelect, /* A SELECT statement that supplies values */ int orconf /* The conflict algorithm (OE_Abort, OE_Replace, etc.) */){ TriggerStep *pTriggerStep = sqliteMalloc(sizeof(TriggerStep)); if( pTriggerStep==0 ) return 0; assert(pEList == 0 || pSelect == 0); assert(pEList != 0 || pSelect != 0); pTriggerStep->op = TK_INSERT; pTriggerStep->pSelect = pSelect; pTriggerStep->target = *pTableName; pTriggerStep->pIdList = pColumn; pTriggerStep->pExprList = pEList; pTriggerStep->orconf = orconf; sqlitePersistTriggerStep(pTriggerStep); 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 *sqliteTriggerUpdateStep( Token *pTableName, /* Name of the table to be updated */ ExprList *pEList, /* The SET clause: list of column and new values */ Expr *pWhere, /* The WHERE clause */ int orconf /* The conflict algorithm. (OE_Abort, OE_Ignore, etc) */){ TriggerStep *pTriggerStep = sqliteMalloc(sizeof(TriggerStep)); if( pTriggerStep==0 ) return 0; pTriggerStep->op = TK_UPDATE; pTriggerStep->target = *pTableName; pTriggerStep->pExprList = pEList; pTriggerStep->pWhere = pWhere; pTriggerStep->orconf = orconf; sqlitePersistTriggerStep(pTriggerStep); 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 *sqliteTriggerDeleteStep(Token *pTableName, Expr *pWhere){ TriggerStep *pTriggerStep = sqliteMalloc(sizeof(TriggerStep)); if( pTriggerStep==0 ) return 0; pTriggerStep->op = TK_DELETE; pTriggerStep->target = *pTableName; pTriggerStep->pWhere = pWhere; pTriggerStep->orconf = OE_Default; sqlitePersistTriggerStep(pTriggerStep); return pTriggerStep;}/* ** Recursively delete a Trigger structure*/void sqliteDeleteTrigger(Trigger *pTrigger){ if( pTrigger==0 ) return; sqliteDeleteTriggerStep(pTrigger->step_list); sqliteFree(pTrigger->name); sqliteFree(pTrigger->table); sqliteExprDelete(pTrigger->pWhen); sqliteIdListDelete(pTrigger->pColumns); if( pTrigger->nameToken.dyn ) sqliteFree((char*)pTrigger->nameToken.z); sqliteFree(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 sqliteDropTriggerPtr() routine does the * same job as this routine except it take a spointer to the trigger * instead of the trigger name. * * Note that this function does not delete the trigger entirely. Instead it
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -