/*- * DBSQL - A SQL database engine. * * Copyright (C) 2007-2008 The DBSQL Group, Inc. - All rights reserved. * * This library is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * There are special exceptions to the terms and conditions of the GPL as it * is applied to this software. View the full text of the exception in file * LICENSE_EXCEPTIONS in the directory of this software distribution. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. */ #include "dbsql_config.h" #include "dbsql_int.h" /* * __vdbe_delete_trigger_step -- * Delete a linked list of TriggerStep structures. * * PUBLIC: void __vdbe_delete_trigger_step __P((trigger_step_t *)); */ void __vdbe_delete_trigger_step(ts) trigger_step_t *ts; { while(ts) { trigger_step_t * tmp = ts; ts = ts->pNext; if (tmp->target.dyn) __dbsql_free(NULL, (char *)tmp->target.z); __expr_delete(tmp->pWhere); __expr_list_delete(tmp->pExprList); __select_delete(tmp->pSelect); __id_list_delete(tmp->pIdList); __dbsql_free(NULL, tmp); } } /* * __begin_trigger -- * 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_t * structure is generated based on the information available and stored * in parser->pNewTrigger. After the trigger actions have been parsed, * the __finish_trigger() function is called to complete the trigger * construction process. * * PUBLIC: void __begin_trigger __P((parser_t *, token_t *, int, int, * PUBLIC: id_list_t *, src_list_t *, int, expr_t *, int)); * * parser The parse context of the CREATE TRIGGER * statement * trigger The trigger of the trigger * tr_tm One of TK_BEFORE, TK_AFTER, TK_INSTEAD * op One of TK_INSERT, TK_UPDATE, TK_DELETE * columns Column list if this is an UPDATE OF trigger * tab_name The table name of the table/view for this * trigger * foreach One of TK_ROW or TK_STATEMENT * when_clause WHEN clause * temp_p True if the TEMPORARY keyword is present */ void __begin_trigger(parser, trigger, tr_tm, op, columns, tab_name, foreach, when_clause, temp_p) parser_t *parser; token_t *trigger; int tr_tm; int op; id_list_t *columns; src_list_t *tab_name; int foreach; expr_t *when_clause; int temp_p; { trigger_t *nt; table_t *table; ref_normalizer_ctx_t normctx; DBSQL *dbp = parser->db; char *name = 0; /* Name of the trigger */ int db_idx; /* When database to store the trigger in */ /* * 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 either the * master or master temp tables. * 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 (parser->rc == ENOMEM) goto trigger_cleanup; DBSQL_ASSERT(tab_name->nSrc == 1); if (parser->initFlag && __ref_normalizer_ctx_init(&normctx, parser, parser->iDb, "trigger", trigger) && __ref_normalize_src_list(&normctx, tab_name)) { goto trigger_cleanup; } table = __src_list_lookup(parser, tab_name); if (!table) { goto trigger_cleanup; } db_idx = (temp_p ? 1 : table->iDb); if (db_idx >= 2 && !parser->initFlag) { __error_msg(parser, "triggers may not be added to auxiliary " "database %s", dbp->aDb[table->iDb].zName); goto trigger_cleanup; } __dbsql_strndup(dbp, trigger->z, &name, trigger->n); __str_unquote(name); if( __hash_find(&(dbp->aDb[db_idx].trigHash), name,trigger->n+1) ){ __error_msg(parser, "trigger %T already exists", trigger); goto trigger_cleanup; } if (strncasecmp(table->zName, MASTER_NAME, strlen(MASTER_NAME)) == 0) { __error_msg(parser, "cannot create trigger on system table"); parser->nErr++; goto trigger_cleanup; } if (strncasecmp(table->zName, TEMP_MASTER_NAME, strlen(TEMP_MASTER_NAME)) == 0) { __error_msg(parser, "cannot create trigger on system table"); parser->nErr++; goto trigger_cleanup; } if (table->pSelect && tr_tm != TK_INSTEAD) { __error_msg(parser, "cannot create %s trigger on view: %S", ((tr_tm == TK_BEFORE) ? "BEFORE" : "AFTER"), tab_name, 0); goto trigger_cleanup; } if (!table->pSelect && tr_tm == TK_INSTEAD) { __error_msg(parser, "cannot create INSTEAD OF" " trigger on table: %S", tab_name, 0); goto trigger_cleanup; } #ifndef DBSQL_NO_AUTH { int code = DBSQL_CREATE_TRIGGER; const char *db = dbp->aDb[table->iDb].zName; const char *db_trig = temp_p ? dbp->aDb[1].zName : db; if (table->iDb == 1 || temp_p) code = DBSQL_CREATE_TEMP_TRIGGER; if (__auth_check(parser, code, name, table->zName, db_trig)) { goto trigger_cleanup; } if (__auth_check(parser, DBSQL_INSERT, SCHEMA_TABLE(table->iDb), 0, db)) { 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. */ __dbsql_calloc(dbp, 1, sizeof(trigger_t), &nt); if (nt == 0) goto trigger_cleanup; nt->name = name; name = 0; if (__dbsql_strdup(dbp, tab_name->a[0].zName, &nt->table) == ENOMEM) goto trigger_cleanup; nt->iDb = db_idx; nt->iTabDb = table->iDb; nt->op = op; nt->tr_tm = tr_tm; nt->pWhen = __expr_dup(when_clause); nt->pColumns = __id_list_dup(columns); nt->foreach = foreach; __token_copy(&nt->nameToken,trigger); DBSQL_ASSERT( parser->pNewTrigger==0 ); parser->pNewTrigger = nt; trigger_cleanup: __dbsql_free(dbp, name); __src_list_delete(tab_name); __id_list_delete(columns); __expr_delete(when_clause); } /* * __finish_trigger -- * This routine is called after all of the trigger actions have been * parsed in order to complete the process of building the trigger. * * PUBLIC: void __finish_trigger __P((parser_t *, trigger_step_t *, * PUBLIC: token_t *)); * * parser The parse context of the CREATE TRIGGER * statement * steplist The triggered program * all Token that describes the complete * CREATE TRIGGER */ void __finish_trigger(parser, steplist, all) parser_t *parser; trigger_step_t *steplist; token_t *all; { ref_normalizer_ctx_t normctx; trigger_t *nt = 0; /* The trigger whose construction is finishing up */ DBSQL *dbp = parser->db; if (parser->nErr || parser->pNewTrigger == 0) goto triggerfinish_cleanup; nt = parser->pNewTrigger; parser->pNewTrigger = 0; nt->step_list = steplist; while(steplist) { steplist->pTrig = nt; steplist = steplist->pNext; } if (__ref_normalizer_ctx_init(&normctx, parser, nt->iDb, "trigger", &nt->nameToken) && __ref_normalize_trigger_step(&normctx, nt->step_list)) { goto triggerfinish_cleanup; } /* * if we are not initializing, and this trigger is not on a TEMP * table, build the master entry. */ if (!parser->initFlag) { static vdbe_op_t insert_trig[] = { { 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_t *v; /* * Make an entry in the master table. */ v = __parser_get_vdbe(parser); if (v == 0) goto triggerfinish_cleanup; __vdbe_prepare_write(parser, 0, 0); __open_master_table(v, nt->iDb); addr = __vdbe_add_op_list(v, ARRAY_SIZE(insert_trig), insert_trig); __vdbe_change_p3(v, (addr + 2), nt->name, 0); __vdbe_change_p3(v, (addr + 3), nt->table, 0); __vdbe_change_p3(v, (addr + 5), all->z, all->n); if (nt->iDb == 0) { __change_schema_signature(dbp, v); } __vdbe_add_op(v, OP_Close, 0, 0); __vdbe_conclude_write(parser); } if (!parser->explain) { table_t *table; __hash_insert(&dbp->aDb[nt->iDb].trigHash, nt->name, (strlen(nt->name) + 1), nt); table = __locate_table(parser, nt->table, dbp->aDb[nt->iTabDb].zName); DBSQL_ASSERT(table != 0); nt->pNext = table->pTrigger; table->pTrigger = nt; nt = 0; } triggerfinish_cleanup: __vdbe_delete_trigger(nt); __vdbe_delete_trigger(parser->pNewTrigger); parser->pNewTrigger = 0; __vdbe_delete_trigger_step(steplist); } /* * __persist_trigger_step -- * Make a copy of all components of the given trigger step. This has * the effect of copying all expr_t.token.z values into memory obtained * from __dbsql_calloc(). As initially created, the expr_t.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 dbsql_exec() * call that started the parser exits. This routine makes a persistent * copy of all the Expr.token.z strings so that the trigger_step_t * structure will be valid even after the dbsql_exec() call returns. * * STATIC: static void __persist_trigger_step __P((trigger_step_t *)); */ static void __persist_trigger_step(ts) trigger_step_t *ts; { if (ts->target.z) { __dbsql_strndup(NULL, ts->target.z, &ts->target.z, ts->target.n); ts->target.dyn = 1; } if (ts->pSelect) { select_t *new = __select_dup(ts->pSelect); __select_delete(ts->pSelect); ts->pSelect = new; } if( ts->pWhere ){ expr_t *new = __expr_dup(ts->pWhere); __expr_delete(ts->pWhere); ts->pWhere = new; } if (ts->pExprList) { expr_list_t *new = __expr_list_dup(ts->pExprList); __expr_list_delete(ts->pExprList); ts->pExprList = new; } if (ts->pIdList) { id_list_t *new = __id_list_dup(ts->pIdList); __id_list_delete(ts->pIdList); ts->pIdList = new; } } /* * __trigger_select_step -- * Turn a SELECT statement (that the 'select' parameter points to) into * a trigger step. Return a pointer to a trigger_step_t structure. * * The parser calls this routine when it finds a SELECT statement in * body of a TRIGGER. * * PUBLIC: trigger_step_t * __trigger_select_step __P((select_t *)); */ trigger_step_t * __trigger_select_step(select) select_t *select; { trigger_step_t *ts; if (__dbsql_calloc(NULL, 1, sizeof(trigger_step_t), &ts) == ENOMEM) return 0; ts->op = TK_SELECT; ts->pSelect = select; ts->orconf = OE_Default; __persist_trigger_step(ts); return ts; } /* * __trigger_insert_step -- * 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. * * PUBLIC: trigger_step_t *__trigger_insert_step __P((token_t *, id_list_t *, * PUBLIC: expr_list_t *, select_t *, int)); * * tab_name Name of the table into which we insert * column List of columns in tab_name to insert into * elist The VALUE clause: a list of values to be * inserted * select A SELECT statement that supplies values * orconf The conflict algorithm (OE_Abort, OE_Replace, * etc.) */ trigger_step_t * __trigger_insert_step(tab_name, column, elist, select, orconf) token_t *tab_name; id_list_t *column; expr_list_t *elist; select_t *select; int orconf; { trigger_step_t *ts; if (__dbsql_calloc(NULL, 1, sizeof(trigger_step_t), &ts) == ENOMEM) return 0; DBSQL_ASSERT(elist == 0 || select == 0); DBSQL_ASSERT(elist != 0 || select != 0); ts->op = TK_INSERT; ts->pSelect = select; ts->target = *tab_name; ts->pIdList = column; ts->pExprList = elist; ts->orconf = orconf; __persist_trigger_step(ts); return ts; } /* * __trigger_update_step -- * 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. * * PUBLIC: trigger_step_t *__trigger_update_step __P((token_t *, expr_list_t *, * PUBLIC: expr_t *, int)); * * tab_name Name of the table to be updated * elist The SET clause: list of column and new values * where_clause The WHERE clause * orconf The conflict algorithm. (OE_Abort, OE_Ignore, * etc) */ trigger_step_t * __trigger_update_step(tab_name, elist, where_clause, orconf) token_t *tab_name; expr_list_t *elist; expr_t *where_clause; int orconf; { trigger_step_t *ts; if (__dbsql_calloc(NULL, 1, sizeof(trigger_step_t), &ts) == ENOMEM) return 0; ts->op = TK_UPDATE; ts->target = *tab_name; ts->pExprList = elist; ts->pWhere = where_clause; ts->orconf = orconf; __persist_trigger_step(ts); return ts; } /* * __trigger_delete_step -- * 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. * * PUBLIC: trigger_step_t *__trigger_delete_step __P((token_t *, expr_t *)); */ trigger_step_t * __trigger_delete_step(tab_name, where_clause) token_t *tab_name; expr_t *where_clause; { trigger_step_t *ts; if (__dbsql_calloc(NULL, 1, sizeof(trigger_step_t), &ts) == ENOMEM) return 0; ts->op = TK_DELETE; ts->target = *tab_name; ts->pWhere = where_clause; ts->orconf = OE_Default; __persist_trigger_step(ts); return ts; } /* * __vdbe_delete_trigger -- * Recursively delete a trigger_t structure * * PUBLIC: void __vdbe_delete_trigger __P((trigger_t *)); */ void __vdbe_delete_trigger(trigger) trigger_t *trigger; { if (trigger == 0) return; __vdbe_delete_trigger_step(trigger->step_list); __dbsql_free(NULL, trigger->name); __dbsql_free(NULL, trigger->table); __expr_delete(trigger->pWhen); __id_list_delete(trigger->pColumns); if (trigger->nameToken.dyn) __dbsql_free(NULL, (char*)trigger->nameToken.z); __dbsql_free(NULL, trigger); } /* * __drop_trigger -- * 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 __drop_trigger_ptr() 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 removes it from the internal schema and places it in the trigDrop * hash table. This is so that the trigger can be restored into the * database schema if the transaction is rolled back. * * PUBLIC: void __drop_trigger __P((parser_t *, src_list_t *)); */ void __drop_trigger(parser, trig_list) parser_t *parser; src_list_t *trig_list; { int i; trigger_t *trigger; const char *db_name; const char *trig_name; int name_len; DBSQL *dbp = parser->db; if (parser->rc == ENOMEM) goto err; DBSQL_ASSERT(trig_list->nSrc == 1); db_name = trig_list->a[0].zDatabase; trig_name = trig_list->a[0].zName; name_len = strlen(trig_name); for (i = 0; i < dbp->nDb; i++) { int j = (i < 2) ? (i ^ 1) : i; /* Search TEMP before MAIN. */ if (db_name && strcasecmp(dbp->aDb[j].zName, db_name)) continue; trigger = __hash_find(&(dbp->aDb[j].trigHash), trig_name, (name_len + 1)); if (trigger) break; } if (!trigger) { __error_msg(parser, "no such trigger: %S", trig_list, 0); goto err; } __drop_trigger_ptr(parser, trigger, 0); err: __src_list_delete(trig_list); } /* * __drop_trigger_ptr -- * Drop a trigger given a pointer to that trigger. If nested is false, * then also generate code to remove the trigger from the DBSQL_MASTER * table. * * PUBLIC: void __drop_trigger_ptr __P((parser_t *, trigger_t *, int)); */ void __drop_trigger_ptr(parser, trigger, nested) parser_t *parser; trigger_t *trigger; int nested; { table_t *table; vdbe_t *v; DBSQL *dbp = parser->db; DBSQL_ASSERT(trigger->iDb < dbp->nDb); if (trigger->iDb >= 2) { __error_msg(parser, "triggers may not be removed from " "auxiliary database %s", dbp->aDb[trigger->iDb].zName); return; } table = __find_table(dbp, trigger->table, dbp->aDb[trigger->iTabDb].zName); DBSQL_ASSERT(table); DBSQL_ASSERT(table->iDb == trigger->iDb || trigger->iDb == 1); #ifndef DBSQL_NO_AUTH { int code = DBSQL_DROP_TRIGGER; const char *db = dbp->aDb[trigger->iDb].zName; const char *tab = SCHEMA_TABLE(trigger->iDb); if (trigger->iDb) code = DBSQL_DROP_TEMP_TRIGGER; if (__auth_check(parser, code, trigger->name, table->zName, db) || __auth_check(parser, DBSQL_DELETE, tab, 0, db)) { return; } } #endif /* * Generate code to destroy the database record of the trigger. */ if (table != 0 && !nested && (v = __parser_get_vdbe(parser)) != 0) { int base; static vdbe_op_t drop_trigger[] = { { OP_Rewind, 0, ADDR(9), 0}, { OP_String, 0, 0, 0}, /* 1 */ { OP_Column, 0, 1, 0}, { OP_Ne, 0, ADDR(8), 0}, { OP_String, 0, 0, "trigger"}, { OP_Column, 0, 0, 0}, { OP_Ne, 0, ADDR(8), 0}, { OP_Delete, 0, 0, 0}, { OP_Next, 0, ADDR(1), 0}, /* 8 */ }; __vdbe_prepare_write(parser, 0, 0); __open_master_table(v, trigger->iDb); base = __vdbe_add_op_list(v, ARRAY_SIZE(drop_trigger), drop_trigger); __vdbe_change_p3(v, base+1, trigger->name, 0); if (trigger->iDb == 0) { __change_schema_signature(dbp, v); } __vdbe_add_op(v, OP_Close, 0, 0); __vdbe_conclude_write(parser); } /* * If this is not an "explain", then delete the trigger structure. */ if (!parser->explain) { const char *name = trigger->name; int len = strlen(name); if (table->pTrigger == trigger) { table->pTrigger = trigger->pNext; } else { trigger_t *cc = table->pTrigger; while(cc) { if (cc->pNext == trigger) { cc->pNext = cc->pNext->pNext; break; } cc = cc->pNext; } DBSQL_ASSERT(cc); } __hash_insert(&(dbp->aDb[trigger->iDb].trigHash), name, (len + 1), 0); __vdbe_delete_trigger(trigger); } } /* * __check_column_overlap -- * 'elist' is the SET clause of an UPDATE statement. Each entry * in 'elist' is of the format =. If any of the entries * in 'elist' have an which matches an identifier in 'id_list', * then return TRUE. If 'id_list'==NULL, then it is considered a * wildcard that matches anything. Likewise if elist==NULL then * it matches anything so always return true. Return false only * if there is no match. * * STATIC: static int __check_column_overlap __P((id_list_t *, expr_list_t *)); */ static int __check_column_overlap(id_list, elist) id_list_t *id_list; expr_list_t *elist; { int e; if (!id_list || !elist) return 1; for (e = 0; e < elist->nExpr; e++) { if (__id_list_index(id_list, elist->a[e].zName) >= 0) return 1; } return 0; } /* * A global variable that is TRUE if we should always set up temp tables for * for triggers, even if there are no triggers to code. This is used to test * how much overhead the triggers algorithm is causing. * * This flag can be set or cleared using the "trigger_overhead_test" pragma. * The pragma is not documented since it is not really part of the public * interface, just the test procedure. */ int always_code_trigger_setup = 0; /* TODO, remove this global, place it in the environment. */ /* * __triggers_exist -- * Returns true if a trigger matching op, tr_tm and foreach that is NOT * already on the parser_t objects trigger-stack (to prevent recursive * trigger firing) is found in the list specified as trigger. * * PUBLIC: int __triggers_exist __P((parser_t *, trigger_t *, int, int, int, * PUBLIC: expr_list_t *)); * * parser Used to check for recursive triggers * trigger A list of triggers associated with a table * op One of TK_DELETE, TK_INSERT, TK_UPDATE * tr_tm One of TK_BEFORE, TK_AFTER * foreach One of TK_ROW or TK_STATEMENT * changes Columns that change in an UPDATE statement */ int __triggers_exist(parser, trigger, op, tr_tm, foreach, changes) parser_t *parser; trigger_t *trigger; int op; int tr_tm; int foreach; expr_list_t *changes; { trigger_t * trig_cursor; if (always_code_trigger_setup) { return 1; } trig_cursor = trigger; while (trig_cursor) { if (trig_cursor->op == op && trig_cursor->tr_tm == tr_tm && trig_cursor->foreach == foreach && __check_column_overlap(trig_cursor->pColumns, changes)) { trigger_stack_t *ss; ss = parser->trigStack; while (ss && ss->pTrigger != trigger) { ss = ss->pNext; } if (!ss) return 1; } trig_cursor = trig_cursor->pNext; } return 0; } /* * __target_src_list -- * Convert the pStep->target token into a src_list_t and return a pointer * to that src_list_t. * * This routine adds a specific database name, if needed, to the target * when forming the src_list_t. 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. * * STATIC: static src_list_t *__target_src_list __P((parser_t *, * STATIC: trigger_step_t *)); * * parser The parsing context * step The trigger containing the target token */ static src_list_t *targetSrcList(parser, step) parser_t *parser; trigger_step_t *step; { token_t db; /* Dummy database name token */ int db_idx; /* Index of the database to use */ src_list_t *src; /* SrcList to be returned */ db_idx = step->pTrig->iDb; if (db_idx == 0 || db_idx >= 2) { DBSQL_ASSERT(db_idx < parser->db->nDb); db.z = parser->db->aDb[db_idx].zName; db.n = strlen(db.z); src = __src_list_append(0, &db, &step->target); } else { src = __src_list_append(0, &step->target, 0); } return src; } /* * __code_trigger_prigram -- * Generate VDBE code for zero or more statements inside the body of a * trigger. * * STATIC: static int __code_trigger_prigram __P(()); * * parser The parser context * steplist List of statements inside the trigger body * orconfin Conflict algorithm. (OE_Abort, etc) */ static int __code_trigger_program(parser, steplist, orconfin) parser_t *parser; trigger_step_t *steplist; int orconfin; { int orconf; trigger_step_t * ts = steplist; while (ts) { int saveNTab = parser->nTab; orconf = (orconfin == OE_Default)?ts->orconf:orconfin; parser->trigStack->orconf = orconf; switch(ts->op) { case TK_SELECT: { select_t * ss = __select_dup(ts->pSelect); DBSQL_ASSERT(ss); DBSQL_ASSERT(ss->pSrc); __select(parser, ss, SRT_Discard, 0, 0, 0, 0); __select_delete(ss); break; } case TK_UPDATE: { src_list_t *src; src = targetSrcList(parser, ts); __vdbe_add_op(parser->pVdbe, OP_ListPush, 0, 0); __update(parser, src, __expr_list_dup(ts->pExprList), __expr_dup(ts->pWhere), orconf); __vdbe_add_op(parser->pVdbe, OP_ListPop, 0, 0); break; } case TK_INSERT: { src_list_t *src; src = targetSrcList(parser, ts); __insert(parser, src, __expr_list_dup(ts->pExprList), __select_dup(ts->pSelect), __id_list_dup(ts->pIdList), orconf); break; } case TK_DELETE: { src_list_t *src; __vdbe_add_op(parser->pVdbe, OP_ListPush, 0, 0); src = targetSrcList(parser, ts); __delete_from(parser, src, __expr_dup(ts->pWhere)); __vdbe_add_op(parser->pVdbe, OP_ListPop, 0, 0); break; } default: DBSQL_ASSERT(0); } parser->nTab = saveNTab; ts = ts->pNext; } return 0; } /* * __code_row_trigger -- * This is called to code FOR EACH ROW triggers. * * When the code that this function generates is executed, the following * must be true: * * 1. No cursors may be open in the main database. (But newIdx and oldIdx * can be indices of cursors in temporary tables. See below.) * * 2. If the triggers being coded are ON INSERT or ON UPDATE triggers, * then a temporary vdbe cursor (index newIdx) must be open and * pointing at a row containing values to be substituted for new.* * expressions in the trigger program(s). * * 3. If the triggers being coded are ON DELETE or ON UPDATE triggers, * then a temporary vdbe cursor (index oldIdx) must be open and * pointing at a row containing values to be substituted for old.* * expressions in the trigger program(s). * * PUBLIC: int __code_row_trigger __P(()); * * parser Parser context * op One of TK_UPDATE, TK_INSERT, TK_DELETE * changes Changes list for any UPDATE OF triggers * tr_tm One of TK_BEFORE, TK_AFTER * table The table to code triggers from * new_idx The indice of the "new" row to access * old_idx The indice of the "old" row to access * orconf ON CONFLICT policy * ignore_jump Instruction to jump to for RAISE(IGNORE) */ int __code_row_trigger(parser, op, changes, tr_tm, table, new_idx, old_idx, orconf, ignore_jump) parser_t *parser; int op; expr_list_t *changes; int tr_tm; table_t *table; int new_idx; int old_idx; int orconf; int ignore_jump; { trigger_t * trigger; trigger_stack_t * trig_stack; DBSQL *dbp = parser->db; DBSQL_ASSERT(op == TK_UPDATE || op == TK_INSERT || op == TK_DELETE); DBSQL_ASSERT(tr_tm == TK_BEFORE || tr_tm == TK_AFTER ); DBSQL_ASSERT(new_idx != -1 || old_idx != -1); trigger = table->pTrigger; while(trigger) { int fire_this = 0; /* * Determine whether we should code this trigger. */ if (trigger->op == op && trigger->tr_tm == tr_tm && trigger->foreach == TK_ROW) { fire_this = 1; trig_stack = parser->trigStack; while(trig_stack) { if (trig_stack->pTrigger == trigger) { fire_this = 0; } trig_stack = trig_stack->pNext; } if (op == TK_UPDATE && trigger->pColumns && !__check_column_overlap(trigger->pColumns, changes)) { fire_this = 0; } } if (fire_this && __dbsql_calloc(dbp, 1,sizeof(trigger_stack_t), &trig_stack) != ENOMEM) { int end_trigger; src_list_t dummy_tab_list; expr_t * when_expr; auth_context_t authctx; dummy_tab_list.nSrc = 0; /* * Push an entry on to the trigger stack. */ trig_stack->pTrigger = trigger; trig_stack->newIdx = new_idx; trig_stack->oldIdx = old_idx; trig_stack->pTab = table; trig_stack->pNext = parser->trigStack; trig_stack->ignoreJump = ignore_jump; parser->trigStack = trig_stack; __auth_context_push(parser, &authctx, trigger->name); /* * Code the WHEN clause. */ end_trigger = __vdbe_make_label(parser->pVdbe); when_expr = __expr_dup(trigger->pWhen); if (__expr_resolve_ids(parser, &dummy_tab_list, 0, when_expr)) { parser->trigStack = parser->trigStack->pNext; __dbsql_free(dbp, trig_stack); __expr_delete(when_expr); return 1; } __expr_if_false(parser, when_expr, end_trigger, 1); __expr_delete(when_expr); __code_trigger_program(parser, trigger->step_list, orconf); /* * Pop the entry off the trigger stack. */ parser->trigStack = parser->trigStack->pNext; __auth_context_pop(&authctx); __dbsql_free(dbp, trig_stack); __vdbe_resolve_label(parser->pVdbe, end_trigger); } trigger = trigger->pNext; } return 0; }