980 lines
27 KiB
C
980 lines
27 KiB
C
/*-
|
|
* 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 <id>=<expr>. If any of the entries
|
|
* in 'elist' have an <id> 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;
|
|
}
|