From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from localhost (localhost [127.0.0.1]) by turing.freelists.org (Avenir Technologies Mail Multiplex) with ESMTP id 3932E21D03 for ; Tue, 26 Jun 2018 12:13:37 -0400 (EDT) Received: from turing.freelists.org ([127.0.0.1]) by localhost (turing.freelists.org [127.0.0.1]) (amavisd-new, port 10024) with ESMTP id VcVvjLpGh0kH for ; Tue, 26 Jun 2018 12:13:37 -0400 (EDT) Received: from smtpng1.m.smailru.net (smtpng1.m.smailru.net [94.100.181.251]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by turing.freelists.org (Avenir Technologies Mail Multiplex) with ESMTPS id 55A4821C97 for ; Tue, 26 Jun 2018 12:13:36 -0400 (EDT) From: Kirill Shcherbatov Subject: [tarantool-patches] [PATCH v4 6/8] sql: refactor AST trigger object name Date: Tue, 26 Jun 2018 19:13:31 +0300 Message-Id: <81986d1f9307191bd3d3e37514dc32fadb7e6970.1530029141.git.kshcherbatov@tarantool.org> In-Reply-To: References: Sender: tarantool-patches-bounce@freelists.org Errors-to: tarantool-patches-bounce@freelists.org Reply-To: tarantool-patches@freelists.org List-help: List-unsubscribe: List-software: Ecartis version 1.0.0 List-Id: tarantool-patches List-subscribe: List-owner: List-post: List-archive: To: tarantool-patches@freelists.org Cc: n.pettik@corp.mail.ru, Kirill Shcherbatov Part of #3273. --- src/box/alter.cc | 14 +- src/box/space.h | 2 +- src/box/sql.h | 17 +- src/box/sql/build.c | 6 +- src/box/sql/callback.c | 3 +- src/box/sql/delete.c | 24 +- src/box/sql/expr.c | 2 - src/box/sql/fkey.c | 162 +++++------ src/box/sql/insert.c | 37 ++- src/box/sql/parse.y | 9 +- src/box/sql/pragma.c | 2 - src/box/sql/pragma.h | 6 +- src/box/sql/resolve.c | 2 - src/box/sql/select.c | 2 - src/box/sql/sqliteInt.h | 253 ++++++++++++++--- src/box/sql/status.c | 2 +- src/box/sql/tokenize.c | 4 +- src/box/sql/treeview.c | 2 - src/box/sql/trigger.c | 709 +++++++++++++++++++++--------------------------- src/box/sql/update.c | 32 +-- src/box/sql/vdbe.c | 10 +- src/box/sql/vdbe.h | 2 - 22 files changed, 678 insertions(+), 624 deletions(-) diff --git a/src/box/alter.cc b/src/box/alter.cc index 1c0e889..449b4b1 100644 --- a/src/box/alter.cc +++ b/src/box/alter.cc @@ -554,7 +554,7 @@ space_swap_triggers(struct space *new_space, struct space *old_space) rlist_swap(&new_space->on_replace, &old_space->on_replace); rlist_swap(&new_space->on_stmt_begin, &old_space->on_stmt_begin); /** Swap SQL Triggers pointer. */ - struct Trigger *new_value = new_space->sql_triggers; + struct sql_trigger *new_value = new_space->sql_triggers; new_space->sql_triggers = old_space->sql_triggers; old_space->sql_triggers = new_value; } @@ -3257,8 +3257,8 @@ static void on_replace_trigger_rollback(struct trigger *trigger, void *event) { struct txn_stmt *stmt = txn_last_stmt((struct txn*) event); - struct Trigger *old_trigger = (struct Trigger *)trigger->data; - struct Trigger *new_trigger; + struct sql_trigger *old_trigger = (struct sql_trigger *)trigger->data; + struct sql_trigger *new_trigger; if (stmt->old_tuple != NULL && stmt->new_tuple == NULL) { /* Rollback DELETE trigger. */ @@ -3294,7 +3294,7 @@ on_replace_trigger_rollback(struct trigger *trigger, void *event) static void on_replace_trigger_commit(struct trigger *trigger, void * /* event */) { - struct Trigger *old_trigger = (struct Trigger *)trigger->data; + struct sql_trigger *old_trigger = (struct sql_trigger *)trigger->data; sql_trigger_delete(sql_get(), old_trigger); } @@ -3328,7 +3328,7 @@ on_replace_dd_trigger(struct trigger * /* trigger */, void *event) memcpy(trigger_name, trigger_name_src, trigger_name_len); trigger_name[trigger_name_len] = 0; - struct Trigger *old_trigger; + struct sql_trigger *old_trigger; int rc = sql_trigger_replace(sql_get(), trigger_name, NULL, &old_trigger); (void)rc; @@ -3351,7 +3351,7 @@ on_replace_dd_trigger(struct trigger * /* trigger */, void *event) struct space_opts opts; struct region *region = &fiber()->gc; space_opts_decode(&opts, space_opts, region); - struct Trigger *new_trigger = + struct sql_trigger *new_trigger = sql_trigger_compile(sql_get(), opts.sql); if (new_trigger == NULL) diag_raise(); @@ -3377,7 +3377,7 @@ on_replace_dd_trigger(struct trigger * /* trigger */, void *event) "resolved on AST building from SQL"); } - struct Trigger *old_trigger; + struct sql_trigger *old_trigger; if (sql_trigger_replace(sql_get(), trigger_name, new_trigger, &old_trigger) != 0) diag_raise(); diff --git a/src/box/space.h b/src/box/space.h index 64aa8c7..7da2ee5 100644 --- a/src/box/space.h +++ b/src/box/space.h @@ -147,7 +147,7 @@ struct space { /** Triggers fired before space statement */ struct rlist on_stmt_begin; /** SQL Trigger list. */ - struct Trigger *sql_triggers; + struct sql_trigger *sql_triggers; /** * The number of *enabled* indexes in the space. * diff --git a/src/box/sql.h b/src/box/sql.h index 2572a15..f483921 100644 --- a/src/box/sql.h +++ b/src/box/sql.h @@ -66,7 +66,7 @@ struct Expr; struct Parse; struct Select; struct Table; -struct Trigger; +struct sql_trigger; /** * Perform parsing of provided expression. This is done by @@ -100,9 +100,9 @@ sql_view_compile(struct sqlite3 *db, const char *view_stmt); * @param sql request to parse. * * @retval NULL on error - * @retval not NULL Trigger AST pointer on success. + * @retval not NULL sql_trigger AST pointer on success. */ -struct Trigger * +struct sql_trigger * sql_trigger_compile(struct sqlite3 *db, const char *sql); /** @@ -111,7 +111,7 @@ sql_trigger_compile(struct sqlite3 *db, const char *sql); * @param trigger AST object. */ void -sql_trigger_delete(struct sqlite3 *db, struct Trigger *trigger); +sql_trigger_delete(struct sqlite3 *db, struct sql_trigger *trigger); /** * Get server triggers list by space_id. @@ -119,7 +119,7 @@ sql_trigger_delete(struct sqlite3 *db, struct Trigger *trigger); * * @retval trigger AST list. */ -struct Trigger * +struct sql_trigger * space_trigger_list(uint32_t space_id); /** @@ -134,7 +134,8 @@ space_trigger_list(uint32_t space_id); */ int sql_trigger_replace(struct sqlite3 *db, const char *name, - struct Trigger *trigger, struct Trigger **old_trigger); + struct sql_trigger *trigger, + struct sql_trigger **old_trigger); /** * Get trigger name by trigger AST object. @@ -142,7 +143,7 @@ sql_trigger_replace(struct sqlite3 *db, const char *name, * @return trigger name string. */ const char * -sql_trigger_name(struct Trigger *trigger); +sql_trigger_name(struct sql_trigger *trigger); /** * Get space_id of the space that trigger has been built for. @@ -150,7 +151,7 @@ sql_trigger_name(struct Trigger *trigger); * @return space identifier. */ uint32_t -sql_trigger_space_id(struct Trigger *trigger); +sql_trigger_space_id(struct sql_trigger *trigger); /** * Store duplicate of a parsed expression into @a parser. diff --git a/src/box/sql/build.c b/src/box/sql/build.c index 2b4e6c7..b88b8fe 100644 --- a/src/box/sql/build.c +++ b/src/box/sql/build.c @@ -2119,10 +2119,10 @@ sql_code_drop_table(struct Parse *parse_context, struct space *space, * accounted in DELETE from _space below. */ parse_context->nested++; - struct Trigger *trigger = space->sql_triggers; + struct sql_trigger *trigger = space->sql_triggers; while (trigger != NULL) { - sqlite3DropTriggerPtr(parse_context, trigger); - trigger = trigger->pNext; + vdbe_code_drop_trigger_ptr(parse_context, trigger); + trigger = trigger->next; } parse_context->nested--; /* diff --git a/src/box/sql/callback.c b/src/box/sql/callback.c index bd8db99..c3c38cb 100644 --- a/src/box/sql/callback.c +++ b/src/box/sql/callback.c @@ -292,7 +292,8 @@ sqlite3SchemaClear(sqlite3 * db) sqlite3HashInit(&pSchema->trigHash); for (pElem = sqliteHashFirst(&temp2); pElem != NULL; pElem = sqliteHashNext(pElem)) - sql_trigger_delete(NULL, (Trigger *) sqliteHashData(pElem)); + sql_trigger_delete(NULL, + (struct sql_trigger *)sqliteHashData(pElem)); sqlite3HashClear(&temp2); sqlite3HashInit(&pSchema->tblHash); for (pElem = sqliteHashFirst(&temp1); pElem; diff --git a/src/box/sql/delete.c b/src/box/sql/delete.c index 8b13f60..818bbbd 100644 --- a/src/box/sql/delete.c +++ b/src/box/sql/delete.c @@ -93,7 +93,7 @@ sql_table_delete_from(struct Parse *parse, struct SrcList *tab_list, /* Figure out if we have any triggers and if the table * being deleted from is a view. */ - struct Trigger *trigger_list = NULL; + struct sql_trigger *trigger_list = NULL; /* True if there are triggers or FKs or subqueries in the * WHERE clause. */ @@ -124,8 +124,7 @@ sql_table_delete_from(struct Parse *parse, struct SrcList *tab_list, space_id = SQLITE_PAGENO_TO_SPACEID(table->tnum); space = space_by_id(space_id); assert(space != NULL); - trigger_list =sqlite3TriggersExist(table, TK_DELETE, - NULL, NULL); + trigger_list = sql_triggers_exist(table, TK_DELETE, NULL, NULL); is_complex = trigger_list != NULL || sqlite3FkRequired(table, NULL); } @@ -424,8 +423,8 @@ sql_table_delete_from(struct Parse *parse, struct SrcList *tab_list, void sql_generate_row_delete(struct Parse *parse, struct Table *table, - struct Trigger *trigger_list, int cursor, int reg_pk, - short npk, bool need_update_count, + struct sql_trigger *trigger_list, int cursor, + int reg_pk, short npk, bool need_update_count, enum on_conflict_action onconf, u8 mode, int idx_noseek) { @@ -457,9 +456,10 @@ sql_generate_row_delete(struct Parse *parse, struct Table *table, /* Mask of OLD.* columns in use */ /* TODO: Could use temporary registers here. */ uint32_t mask = - sqlite3TriggerColmask(parse, trigger_list, 0, 0, - TRIGGER_BEFORE | TRIGGER_AFTER, table, - onconf); + sql_trigger_colmask(parse, trigger_list, 0, 0, + TRIGGER_BEFORE | TRIGGER_AFTER, + table, + onconf); mask |= sqlite3FkOldmask(parse, table); first_old_reg = parse->nMem + 1; parse->nMem += (1 + (int)table->def->field_count); @@ -483,7 +483,7 @@ sql_generate_row_delete(struct Parse *parse, struct Table *table, /* Invoke BEFORE DELETE trigger programs. */ int addr_start = sqlite3VdbeCurrentAddr(v); - sqlite3CodeRowTrigger(parse, trigger_list, TK_DELETE, NULL, + vdbe_code_row_trigger(parse, trigger_list, TK_DELETE, NULL, TRIGGER_BEFORE, table, first_old_reg, onconf, label); @@ -537,9 +537,9 @@ sql_generate_row_delete(struct Parse *parse, struct Table *table, sqlite3FkActions(parse, table, 0, first_old_reg, 0); /* Invoke AFTER DELETE trigger programs. */ - sqlite3CodeRowTrigger(parse, trigger_list, - TK_DELETE, 0, TRIGGER_AFTER, table, - first_old_reg, onconf, label); + vdbe_code_row_trigger(parse, trigger_list, TK_DELETE, 0, + TRIGGER_AFTER, table, first_old_reg, + onconf, label); } /* Jump here if the row had already been deleted before diff --git a/src/box/sql/expr.c b/src/box/sql/expr.c index 59e7cb4..70e134f 100644 --- a/src/box/sql/expr.c +++ b/src/box/sql/expr.c @@ -4289,7 +4289,6 @@ sqlite3ExprCodeTarget(Parse * pParse, Expr * pExpr, int target) sqlite3VdbeResolveLabel(v, endLabel); break; } -#ifndef SQLITE_OMIT_TRIGGER case TK_RAISE:{ assert(pExpr->affinity == ON_CONFLICT_ACTION_ROLLBACK || pExpr->affinity == ON_CONFLICT_ACTION_ABORT @@ -4319,7 +4318,6 @@ sqlite3ExprCodeTarget(Parse * pParse, Expr * pExpr, int target) break; } -#endif } sqlite3ReleaseTempReg(pParse, regFree1); sqlite3ReleaseTempReg(pParse, regFree2); diff --git a/src/box/sql/fkey.c b/src/box/sql/fkey.c index ce63ff0..121831b 100644 --- a/src/box/sql/fkey.c +++ b/src/box/sql/fkey.c @@ -40,7 +40,6 @@ #include "tarantoolInt.h" #ifndef SQLITE_OMIT_FOREIGN_KEY -#ifndef SQLITE_OMIT_TRIGGER /* * Deferred and Immediate FKs @@ -730,25 +729,29 @@ sqlite3FkReferences(Table * pTab) pTab->def->name); } -/* +/** * The second argument is a Trigger structure allocated by the - * fkActionTrigger() routine. This function deletes the Trigger structure - * and all of its sub-components. + * fkActionTrigger() routine. This function deletes the sql_trigger + * structure and all of its sub-components. * - * The Trigger structure or any of its sub-components may be allocated from - * the lookaside buffer belonging to database handle dbMem. + * The Trigger structure or any of its sub-components may be + * allocated from the lookaside buffer belonging to database + * handle dbMem. + * + * @param db Database connection. + * @param trigger AST object. */ static void -fkTriggerDelete(sqlite3 * dbMem, Trigger * p) +sql_fk_trigger_delete(struct sqlite3 *db, struct sql_trigger *trigger) { - if (p) { - TriggerStep *pStep = p->step_list; - sql_expr_delete(dbMem, pStep->pWhere, false); - sql_expr_list_delete(dbMem, pStep->pExprList); - sql_select_delete(dbMem, pStep->pSelect); - sql_expr_delete(dbMem, p->pWhen, false); - sqlite3DbFree(dbMem, p); - } + if (trigger == NULL) + return; + struct TriggerStep *trigger_step = trigger->step_list; + sql_expr_delete(db, trigger_step->pWhere, false); + sql_expr_list_delete(db, trigger_step->pExprList); + sql_select_delete(db, trigger_step->pSelect); + sql_expr_delete(db, trigger->pWhen, false); + sqlite3DbFree(db, trigger); } /** @@ -858,15 +861,13 @@ static int isSetNullAction(Parse * pParse, FKey * pFKey) { Parse *pTop = sqlite3ParseToplevel(pParse); - if (pTop->pTriggerPrg) { - Trigger *p = pTop->pTriggerPrg->pTrigger; - if ((p == pFKey->apTrigger[0] - && pFKey->aAction[0] == OE_SetNull) - || (p == pFKey->apTrigger[1] - && pFKey->aAction[1] == OE_SetNull) - ) { + if (pTop->pTriggerPrg != NULL) { + struct sql_trigger *trigger = pTop->pTriggerPrg->trigger; + if ((trigger == pFKey->apTrigger[0] && + pFKey->aAction[0] == OE_SetNull) || + (trigger == pFKey->apTrigger[1] + && pFKey->aAction[1] == OE_SetNull)) return 1; - } } return 0; } @@ -1175,21 +1176,24 @@ sqlite3FkRequired(Table * pTab, /* Table being modified */ return 0; } -/* - * This function is called when an UPDATE or DELETE operation is being - * compiled on table pTab, which is the parent table of foreign-key pFKey. - * If the current operation is an UPDATE, then the pChanges parameter is - * passed a pointer to the list of columns being modified. If it is a - * DELETE, pChanges is passed a NULL pointer. - * - * It returns a pointer to a Trigger structure containing a trigger - * equivalent to the ON UPDATE or ON DELETE action specified by pFKey. - * If the action is "NO ACTION" or "RESTRICT", then a NULL pointer is - * returned (these actions require no special handling by the triggers - * sub-system, code for them is created by fkScanChildren()). - * - * For example, if pFKey is the foreign key and pTab is table "p" in - * the following schema: +/** + * This function is called when an UPDATE or DELETE operation is + * being compiled on table pTab, which is the parent table of + * foreign-key pFKey. + * If the current operation is an UPDATE, then the pChanges + * parameter is passed a pointer to the list of columns being + * modified. If it is a DELETE, pChanges is passed a NULL pointer. + * + * It returns a pointer to a sql_trigger structure containing a + * trigger equivalent to the ON UPDATE or ON DELETE action + * specified by pFKey. + * If the action is "NO ACTION" or "RESTRICT", then a NULL pointer + * is returned (these actions require no special handling by the + * triggers sub-system, code for them is created by + * fkScanChildren()). + * + * For example, if pFKey is the foreign key and pTab is table "p" + * in the following schema: * * CREATE TABLE p(pk PRIMARY KEY); * CREATE TABLE c(ck REFERENCES p ON DELETE CASCADE); @@ -1200,20 +1204,25 @@ sqlite3FkRequired(Table * pTab, /* Table being modified */ * DELETE FROM c WHERE ck = old.pk; * END; * - * The returned pointer is cached as part of the foreign key object. It - * is eventually freed along with the rest of the foreign key object by - * sqlite3FkDelete(). + * The returned pointer is cached as part of the foreign key + * object. It is eventually freed along with the rest of the + * foreign key object by sqlite3FkDelete(). + * + * @param pParse Parse context. + * @param pTab Table being updated or deleted from. + * @param pFKey Foreign key to get action for. + * @param pChanges Change-list for UPDATE, NULL for DELETE. + * + * @retval not NULL on success. + * @retval NULL on failure. */ -static Trigger * -fkActionTrigger(Parse * pParse, /* Parse context */ - Table * pTab, /* Table being updated or deleted from */ - FKey * pFKey, /* Foreign key to get action for */ - ExprList * pChanges /* Change-list for UPDATE, NULL for DELETE */ - ) +static struct sql_trigger * +fkActionTrigger(Parse * pParse, Table * pTab, FKey * pFKey, ExprList * pChanges) { sqlite3 *db = pParse->db; /* Database handle */ int action; /* One of OE_None, OE_Cascade etc. */ - Trigger *pTrigger; /* Trigger definition to return */ + /* Trigger definition to return. */ + struct sql_trigger *trigger; int iAction = (pChanges != 0); /* 1 for UPDATE, 0 for DELETE */ struct session *user_session = current_session(); @@ -1222,9 +1231,9 @@ fkActionTrigger(Parse * pParse, /* Parse context */ && (user_session->sql_flags & SQLITE_DeferFKs)) { return 0; } - pTrigger = pFKey->apTrigger[iAction]; + trigger = pFKey->apTrigger[iAction]; - if (action != ON_CONFLICT_ACTION_NONE && !pTrigger) { + if (action != ON_CONFLICT_ACTION_NONE && trigger == NULL) { char const *zFrom; /* Name of child table */ int nFrom; /* Length in bytes of zFrom */ Index *pIdx = 0; /* Parent key index for this FK */ @@ -1379,13 +1388,13 @@ fkActionTrigger(Parse * pParse, /* Parse context */ /* Disable lookaside memory allocation */ db->lookaside.bDisable++; - pTrigger = (Trigger *) sqlite3DbMallocZero(db, sizeof(Trigger) + /* struct Trigger */ - sizeof(TriggerStep) + /* Single step in trigger program */ - nFrom + 1 /* Space for pStep->zTarget */ - ); - if (pTrigger) { - pStep = pTrigger->step_list = - (TriggerStep *) & pTrigger[1]; + size_t trigger_size = sizeof(struct sql_trigger) + + sizeof(TriggerStep) + nFrom + 1; + trigger = + (struct sql_trigger *)sqlite3DbMallocZero(db, + trigger_size); + if (trigger != NULL) { + pStep = trigger->step_list = (TriggerStep *)&trigger[1]; pStep->zTarget = (char *)&pStep[1]; memcpy((char *)pStep->zTarget, zFrom, nFrom); @@ -1397,7 +1406,7 @@ fkActionTrigger(Parse * pParse, /* Parse context */ sqlite3SelectDup(db, pSelect, EXPRDUP_REDUCE); if (pWhen) { pWhen = sqlite3PExpr(pParse, TK_NOT, pWhen, 0); - pTrigger->pWhen = + trigger->pWhen = sqlite3ExprDup(db, pWhen, EXPRDUP_REDUCE); } } @@ -1410,7 +1419,7 @@ fkActionTrigger(Parse * pParse, /* Parse context */ sql_expr_list_delete(db, pList); sql_select_delete(db, pSelect); if (db->mallocFailed == 1) { - fkTriggerDelete(db, pTrigger); + sql_fk_trigger_delete(db, trigger); return 0; } assert(pStep != 0); @@ -1428,12 +1437,12 @@ fkActionTrigger(Parse * pParse, /* Parse context */ default: pStep->op = TK_UPDATE; } - pStep->pTrig = pTrigger; - pFKey->apTrigger[iAction] = pTrigger; - pTrigger->op = (pChanges ? TK_UPDATE : TK_DELETE); + pStep->trigger = trigger; + pFKey->apTrigger[iAction] = trigger; + trigger->op = pChanges ? TK_UPDATE : TK_DELETE; } - return pTrigger; + return trigger; } /* @@ -1460,23 +1469,20 @@ sqlite3FkActions(Parse * pParse, /* Parse context */ pFKey = pFKey->pNextTo) { if (aChange == 0 || fkParentIsModified(pTab, pFKey, aChange)) { - Trigger *pAct = - fkActionTrigger(pParse, pTab, pFKey, - pChanges); - if (pAct) { - sqlite3CodeRowTriggerDirect(pParse, - pAct, pTab, - regOld, - ON_CONFLICT_ACTION_ABORT, - 0); - } + struct sql_trigger *pAct = + fkActionTrigger(pParse, pTab, pFKey, + pChanges); + if (pAct == NULL) + continue; + vdbe_code_row_trigger_direct(pParse, pAct, pTab, + regOld, + ON_CONFLICT_ACTION_ABORT, + 0); } } } } -#endif /* ifndef SQLITE_OMIT_TRIGGER */ - /* * Free all memory associated with foreign key definitions attached to * table pTab. Remove the deleted foreign keys from the Schema.fkeyHash @@ -1511,10 +1517,8 @@ sqlite3FkDelete(sqlite3 * db, Table * pTab) assert(pFKey->isDeferred == 0 || pFKey->isDeferred == 1); /* Delete any triggers created to implement actions for this FK. */ -#ifndef SQLITE_OMIT_TRIGGER - fkTriggerDelete(db, pFKey->apTrigger[0]); - fkTriggerDelete(db, pFKey->apTrigger[1]); -#endif + sql_fk_trigger_delete(db, pFKey->apTrigger[0]); + sql_fk_trigger_delete(db, pFKey->apTrigger[1]); pNext = pFKey->pNextFrom; sqlite3DbFree(db, pFKey); diff --git a/src/box/sql/insert.c b/src/box/sql/insert.c index db8165a..dac6965 100644 --- a/src/box/sql/insert.c +++ b/src/box/sql/insert.c @@ -357,7 +357,8 @@ sqlite3Insert(Parse * pParse, /* Parser context */ int regData; /* register holding first column to insert */ int *aRegIdx = 0; /* One register allocated to each index */ uint32_t space_id = 0; - Trigger *pTrigger; /* List of triggers on pTab, if required */ + /* List of triggers on pTab, if required. */ + struct sql_trigger *trigger; int tmask; /* Mask of trigger times */ db = pParse->db; @@ -393,9 +394,10 @@ sqlite3Insert(Parse * pParse, /* Parser context */ /* Figure out if we have any triggers and if the table being * inserted into is a view */ - pTrigger = sqlite3TriggersExist(pTab, TK_INSERT, 0, &tmask); + trigger = sql_triggers_exist(pTab, TK_INSERT, NULL, &tmask); bool is_view = pTab->def->opts.is_view; - assert((pTrigger && tmask) || (pTrigger == 0 && tmask == 0)); + assert((trigger != NULL && tmask != 0) || + (trigger == NULL && tmask == 0)); /* If pTab is really a view, make sure it has been initialized. * ViewGetColumnNames() is a no-op if pTab is not a view. @@ -419,7 +421,7 @@ sqlite3Insert(Parse * pParse, /* Parser context */ goto insert_cleanup; if (pParse->nested == 0) sqlite3VdbeCountChanges(v); - sql_set_multi_write(pParse, pSelect || pTrigger); + sql_set_multi_write(pParse, pSelect != NULL || trigger != NULL); #ifndef SQLITE_OMIT_XFER_OPT /* If the statement is of the form @@ -432,7 +434,7 @@ sqlite3Insert(Parse * pParse, /* Parser context */ * This is the 2nd template. */ if (pColumn == 0 && xferOptimization(pParse, pTab, pSelect, on_error)) { - assert(!pTrigger); + assert(trigger == NULL); assert(pList == 0); goto insert_end; } @@ -536,9 +538,8 @@ sqlite3Insert(Parse * pParse, /* Parser context */ * of the tables being read by the SELECT statement. Also use a * temp table in the case of row triggers. */ - if (pTrigger || readsTable(pParse, pTab)) { + if (trigger != NULL || readsTable(pParse, pTab)) useTempTable = 1; - } if (useTempTable) { /* Invoke the coroutine to extract information from the SELECT @@ -731,7 +732,7 @@ sqlite3Insert(Parse * pParse, /* Parser context */ } /* Fire BEFORE or INSTEAD OF triggers */ - sqlite3CodeRowTrigger(pParse, pTrigger, TK_INSERT, 0, + vdbe_code_row_trigger(pParse, trigger, TK_INSERT, 0, TRIGGER_BEFORE, pTab, regCols - def->field_count - 1, on_error, endOfLoop); @@ -880,9 +881,9 @@ sqlite3Insert(Parse * pParse, /* Parser context */ sqlite3VdbeAddOp2(v, OP_AddImm, regRowCount, 1); } - if (pTrigger) { + if (trigger != NULL) { /* Code AFTER triggers */ - sqlite3CodeRowTrigger(pParse, pTrigger, TK_INSERT, 0, + vdbe_code_row_trigger(pParse, trigger, TK_INSERT, 0, TRIGGER_AFTER, pTab, regData - 2 - def->field_count, on_error, endOfLoop); @@ -1360,9 +1361,8 @@ sqlite3GenerateConstraintChecks(Parse * pParse, /* The parser context */ bool no_delete_triggers = (0 == (user_session->sql_flags & SQLITE_RecTriggers) || - 0 == sqlite3TriggersExist(pTab, - TK_DELETE, - 0, 0)); + sql_triggers_exist(pTab, TK_DELETE, NULL, NULL) == + NULL); bool no_foreign_keys = (0 == (user_session->sql_flags & SQLITE_ForeignKeys) || @@ -1473,15 +1473,14 @@ sqlite3GenerateConstraintChecks(Parse * pParse, /* The parser context */ sqlite3VdbeGoto(v, ignoreDest); break; default: { - Trigger *pTrigger = NULL; + struct sql_trigger *trigger = NULL; assert(on_error == ON_CONFLICT_ACTION_REPLACE); sql_set_multi_write(pParse, true); - if (user_session-> - sql_flags & SQLITE_RecTriggers) { - pTrigger = sqlite3TriggersExist(pTab, TK_DELETE, - NULL, NULL); + if (user_session->sql_flags & SQLITE_RecTriggers) { + trigger = sql_triggers_exist(pTab, TK_DELETE, + NULL, NULL); } - sql_generate_row_delete(pParse, pTab, pTrigger, + sql_generate_row_delete(pParse, pTab, trigger, iDataCur, regR, nPkField, false, ON_CONFLICT_ACTION_REPLACE, pIdx == pPk ? ONEPASS_SINGLE : diff --git a/src/box/sql/parse.y b/src/box/sql/parse.y index ccd9d02..91bdc6e 100644 --- a/src/box/sql/parse.y +++ b/src/box/sql/parse.y @@ -1300,20 +1300,18 @@ plus_num(A) ::= number(A). minus_num(A) ::= MINUS number(X). {A = X;} //////////////////////////// The CREATE TRIGGER command ///////////////////// -%ifndef SQLITE_OMIT_TRIGGER - cmd ::= createkw trigger_decl(A) BEGIN trigger_cmd_list(S) END(Z). { Token all; all.z = A.z; all.n = (int)(Z.z - A.z) + Z.n; pParse->initiateTTrans = false; - sqlite3FinishTrigger(pParse, S, &all); + sql_trigger_finish(pParse, S, &all); } trigger_decl(A) ::= TRIGGER ifnotexists(NOERR) nm(B) trigger_time(C) trigger_event(D) ON fullname(E) foreach_clause when_clause(G). { - sqlite3BeginTrigger(pParse, &B, C, D.a, D.b, E, G, NOERR); + sql_trigger_begin(pParse, &B, C, D.a, D.b, E, G, NOERR); A = B; /*A-overwrites-T*/ } @@ -1414,7 +1412,6 @@ expr(A) ::= RAISE(X) LP raisetype(T) COMMA STRING(Z) RP(Y). { A.pExpr->affinity = (char)T; } } -%endif !SQLITE_OMIT_TRIGGER %type raisetype {int} raisetype(A) ::= ROLLBACK. {A = ON_CONFLICT_ACTION_ROLLBACK;} @@ -1423,11 +1420,9 @@ raisetype(A) ::= FAIL. {A = ON_CONFLICT_ACTION_FAIL;} //////////////////////// DROP TRIGGER statement ////////////////////////////// -%ifndef SQLITE_OMIT_TRIGGER cmd ::= DROP TRIGGER ifexists(NOERR) fullname(X). { sqlite3DropTrigger(pParse,X,NOERR); } -%endif !SQLITE_OMIT_TRIGGER ////////////////////////// REINDEX collation ////////////////////////////////// /* gh-2174: Commended until REINDEX is implemented in scope of gh-3195 */ diff --git a/src/box/sql/pragma.c b/src/box/sql/pragma.c index 5fb29c7..be6a01c 100644 --- a/src/box/sql/pragma.c +++ b/src/box/sql/pragma.c @@ -583,7 +583,6 @@ sqlite3Pragma(Parse * pParse, Token * pId, /* First part of [schema.]id field */ #endif /* !defined(SQLITE_OMIT_FOREIGN_KEY) */ #ifndef SQLITE_OMIT_FOREIGN_KEY -#ifndef SQLITE_OMIT_TRIGGER case PragTyp_FOREIGN_KEY_CHECK:{ FKey *pFK; /* A foreign key constraint */ Table *pTab; /* Child table contain "REFERENCES" @@ -755,7 +754,6 @@ sqlite3Pragma(Parse * pParse, Token * pId, /* First part of [schema.]id field */ } break; } -#endif /* !defined(SQLITE_OMIT_TRIGGER) */ #endif /* !defined(SQLITE_OMIT_FOREIGN_KEY) */ #ifndef NDEBUG diff --git a/src/box/sql/pragma.h b/src/box/sql/pragma.h index f966018..06b7eea 100644 --- a/src/box/sql/pragma.h +++ b/src/box/sql/pragma.h @@ -126,7 +126,7 @@ static const PragmaName aPragmaName[] = { /* iArg: */ SQLITE_CountRows}, #endif #if !defined(SQLITE_OMIT_FLAG_PRAGMAS) -#if !defined(SQLITE_OMIT_FOREIGN_KEY) && !defined(SQLITE_OMIT_TRIGGER) +#if !defined(SQLITE_OMIT_FOREIGN_KEY) { /* zName: */ "defer_foreign_keys", /* ePragTyp: */ PragTyp_FLAG, /* ePragFlg: */ PragFlg_Result0 | PragFlg_NoColumns1, @@ -134,7 +134,7 @@ static const PragmaName aPragmaName[] = { /* iArg: */ SQLITE_DeferFKs}, #endif #endif -#if !defined(SQLITE_OMIT_FOREIGN_KEY) && !defined(SQLITE_OMIT_TRIGGER) +#if !defined(SQLITE_OMIT_FOREIGN_KEY) { /* zName: */ "foreign_key_check", /* ePragTyp: */ PragTyp_FOREIGN_KEY_CHECK, /* ePragFlg: */ PragFlg_NeedSchema, @@ -150,7 +150,7 @@ static const PragmaName aPragmaName[] = { /* iArg: */ 0}, #endif #if !defined(SQLITE_OMIT_FLAG_PRAGMAS) -#if !defined(SQLITE_OMIT_FOREIGN_KEY) && !defined(SQLITE_OMIT_TRIGGER) +#if !defined(SQLITE_OMIT_FOREIGN_KEY) { /* zName: */ "foreign_keys", /* ePragTyp: */ PragTyp_FLAG, /* ePragFlg: */ PragFlg_Result0 | PragFlg_NoColumns1, diff --git a/src/box/sql/resolve.c b/src/box/sql/resolve.c index 23e1618..10c717f 100644 --- a/src/box/sql/resolve.c +++ b/src/box/sql/resolve.c @@ -309,7 +309,6 @@ lookupName(Parse * pParse, /* The parsing context */ } } /* if( pSrcList ) */ -#ifndef SQLITE_OMIT_TRIGGER /* If we have not already resolved the name, then maybe * it is a new.* or old.* trigger argument reference */ @@ -369,7 +368,6 @@ lookupName(Parse * pParse, /* The parsing context */ } } } -#endif /* !defined(SQLITE_OMIT_TRIGGER) */ /* * If the input is of the form Z (not Y.Z or X.Y.Z) then the name Z diff --git a/src/box/sql/select.c b/src/box/sql/select.c index 368bcd6..4e61ec1 100644 --- a/src/box/sql/select.c +++ b/src/box/sql/select.c @@ -1270,7 +1270,6 @@ selectInnerLoop(Parse * pParse, /* The parser context */ } #endif /* SQLITE_OMIT_CTE */ -#if !defined(SQLITE_OMIT_TRIGGER) /* Discard the results. This is used for SELECT statements inside * the body of a TRIGGER. The purpose of such selects is to call * user-defined functions that have side effects. We do not care @@ -1280,7 +1279,6 @@ selectInnerLoop(Parse * pParse, /* The parser context */ assert(eDest == SRT_Discard); break; } -#endif } /* Jump to the end of the loop if the LIMIT is reached. Except, if diff --git a/src/box/sql/sqliteInt.h b/src/box/sql/sqliteInt.h index d5e3263..aa7d48e 100644 --- a/src/box/sql/sqliteInt.h +++ b/src/box/sql/sqliteInt.h @@ -1493,7 +1493,6 @@ typedef struct StrAccum StrAccum; typedef struct Table Table; typedef struct Token Token; typedef struct TreeView TreeView; -typedef struct Trigger Trigger; typedef struct TriggerPrg TriggerPrg; typedef struct TriggerStep TriggerStep; typedef struct UnpackedRecord UnpackedRecord; @@ -1994,7 +1993,8 @@ struct FKey { /* EV: R-30323-21917 */ u8 isDeferred; /* True if constraint checking is deferred till COMMIT */ u8 aAction[2]; /* ON DELETE and ON UPDATE actions, respectively */ - Trigger *apTrigger[2]; /* Triggers for aAction[] actions */ + /** Triggers for aAction[] actions. */ + struct sql_trigger *apTrigger[2]; struct sColMap { /* Mapping of columns in pFrom to columns in zTo */ int iFrom; /* Index of column in pFrom */ char *zCol; /* Name of column in zTo. If NULL use PRIMARY KEY */ @@ -2842,7 +2842,8 @@ struct SelectDest { * a mask of new.* columns used by the program. */ struct TriggerPrg { - Trigger *pTrigger; /* Trigger this program was coded from */ + /** Trigger this program was coded from. */ + struct sql_trigger *trigger; TriggerPrg *pNext; /* Next entry in Parse.pTriggerPrg list */ SubProgram *pProgram; /* Program implementing pTrigger/orconf */ int orconf; /* Default ON CONFLICT policy */ @@ -2965,7 +2966,7 @@ struct Parse { union { struct Expr *expr; struct Select *select; - struct Trigger *trigger; + struct sql_trigger *trigger; } parsed_ast; }; @@ -3018,21 +3019,23 @@ struct Parse { */ /* - * Each trigger present in the database schema is stored as an instance of - * struct Trigger. + * Each trigger present in the database schema is stored as an + * instance of struct sql_trigger. * * Pointers to instances of struct Trigger are stored in two ways. - * 1. In the "trigHash" hash table (part of the sqlite3* that represents the - * database). This allows Trigger structures to be retrieved by name. - * 2. All triggers associated with a single table form a linked list, using the - * pNext member of struct Trigger. A pointer to the first element of the - * linked list is stored as the "pTrigger" member of the associated - * struct Table. - * - * The "step_list" member points to the first element of a linked list - * containing the SQL statements specified as the trigger program. - */ -struct Trigger { + * 1. In the "trigHash" hash table (part of the sqlite3* that + * represents the database). This allows Trigger structures to + * be retrieved by name. + * 2. All triggers associated with a single table form a linked + * list, using the next member of struct sql_trigger. A pointer + * to the first element of the linked list is stored as the + * "pTrigger" member of the associated struct Table. + * + * The "step_list" member points to the first element of a linked + * list containing the SQL statements specified as the trigger + * program. + */ +struct sql_trigger { char *zName; /* The name of the trigger */ /** The ID of space the trigger refers to. */ uint32_t space_id; @@ -3042,7 +3045,8 @@ struct Trigger { IdList *pColumns; /* If this is an UPDATE OF trigger, the is stored here */ TriggerStep *step_list; /* Link list of trigger program steps */ - Trigger *pNext; /* Next trigger associated with the table */ + /** Next trigger associated with the table. */ + struct sql_trigger *next; }; /* @@ -3096,7 +3100,8 @@ struct Trigger { struct TriggerStep { u8 op; /* One of TK_DELETE, TK_UPDATE, TK_INSERT, TK_SELECT */ u8 orconf; /* ON_CONFLICT_ACTION_ROLLBACK etc. */ - Trigger *pTrig; /* The trigger that this step is a part of */ + /** The trigger that this step is a part of */ + struct sql_trigger *trigger; Select *pSelect; /* SELECT statement or RHS of INSERT INTO SELECT ... */ char *zTarget; /* Target table for DELETE, UPDATE, INSERT */ Expr *pWhere; /* The WHERE clause for DELETE or UPDATE steps */ @@ -3933,8 +3938,8 @@ int sqlite3ExprNeedsNoAffinityChange(const Expr *, char); */ void sql_generate_row_delete(struct Parse *parse, struct Table *table, - struct Trigger *trigger_list, int cursor, int reg_pk, - short npk, bool need_update_count, + struct sql_trigger *trigger_list, int cursor, + int reg_pk, short npk, bool need_update_count, enum on_conflict_action onconf, u8 mode, int idx_noseek); @@ -4064,17 +4069,143 @@ void sql_materialize_view(struct Parse *parse, const char *name, struct Expr *where, int cursor); -#ifndef SQLITE_OMIT_TRIGGER -void sqlite3BeginTrigger(Parse *, Token *, int, int, IdList *, SrcList *, - Expr *, int); -void sqlite3FinishTrigger(Parse *, TriggerStep *, Token *); +/** + * 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 sql_trigger structure is generated based on the + * information available and stored in parse->parsed_ast.trigger. + * After the trigger actions have been parsed, the + * sql_trigger_finish() function is called to complete the trigger + * construction process. + * + * @param parse The parse context of the CREATE TRIGGER statement. + * @param name The name of the trigger. + * @param tr_tm One of TK_BEFORE, TK_AFTER, TK_INSTEAD. + * @param op One of TK_INSERT, TK_UPDATE, TK_DELETE. + * @param columns column list if this is an UPDATE OF trigger. + * @param table The name of the table/view the trigger applies to. + * @param when WHEN clause. + * @param no_err Suppress errors if the trigger already exists. + */ +void +sql_trigger_begin(struct Parse *parse, struct Token *name, int tr_tm, + int op, struct IdList *columns, struct SrcList *table, + struct Expr *when, int no_err); + +/** + * This routine is called after all of the trigger actions have + * been parsed in order to complete the process of building the + * trigger. + * + * @param parse Parser context. + * @param step_list The triggered program. + * @param token Token that describes the complete CREATE TRIGGER. + */ +void sql_trigger_finish(Parse *, TriggerStep *, Token *); + void sqlite3DropTrigger(Parse *, SrcList *, int); -void sqlite3DropTriggerPtr(Parse *, Trigger *); -Trigger *sqlite3TriggersExist(Table *, int, ExprList *, int *pMask); -void sqlite3CodeRowTrigger(Parse *, Trigger *, int, ExprList *, int, Table *, - int, int, int); -void sqlite3CodeRowTriggerDirect(Parse *, Trigger *, Table *, int, int, int); -void sqliteViewTriggers(Parse *, Table *, Expr *, int, ExprList *); + +/** + * Drop a trigger given a pointer to that trigger. + * + * @param parser Parse context. + * @param trigger Trigger to drop. + */ +void +vdbe_code_drop_trigger_ptr(struct Parse *parser, struct sql_trigger *trigger); + +/** + * 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 changes_list is being + * modified. + * + * @param table The table the contains the triggers. + * @param op operation one of TK_DELETE, TK_INSERT, TK_UPDATE. + * @param changes_list Columns that change in an UPDATE statement. + * @param[out] pMask Mask of TRIGGER_BEFORE|TRIGGER_AFTER + */ +struct sql_trigger * +sql_triggers_exist(struct Table *table, int op, struct ExprList *changes_list, + int *mask_ptr); + +/** + * This is called to code the required FOR EACH ROW triggers for + * an operation on table. 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 + * changes_list is passed the list of columns being modified. + * + * If there are no triggers that fire at the specified time for + * the specified operation on table, 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 table, then registers are populated as + * follows: + * + * Register Contains + * ------------------------------------------------------ + * reg+0 OLD.PK + * reg+1 OLD.* value of left-most column of pTab + * ... ... + * reg+N OLD.* value of right-most column of pTab + * reg+N+1 NEW.PK + * reg+N+2 OLD.* 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. + * + * @param parser Parse context. + * @param trigger List of triggers on table. + * @param op operation, one of TK_UPDATE, TK_INSERT, TK_DELETE. + * @param changes_list Changes list for any UPDATE OF triggers. + * @param tr_tm One of TRIGGER_BEFORE, TRIGGER_AFTER. + * @param table The table to code triggers from. + * @param reg The first in an array of registers. + * @param orconf ON CONFLICT policy. + * @param ignore_jump Instruction to jump to for RAISE(IGNORE). + */ +void +vdbe_code_row_trigger(struct Parse *parser, struct sql_trigger *trigger, + int op, struct ExprList *changes_list, int tr_tm, + struct Table *table, int reg, int orconf, int ignore_jump); + +/** + * Generate code for the trigger program associated with trigger + * p on table table. The reg, orconf and ignoreJump parameters + * passed to this function are the same as those described in the + * header function for sql_code_row_trigger(). + * + * @param parser Parse context. + * @param trigger Trigger to code. + * @param table The table to code triggers from. + * @param reg Reg array containing OLD.* and NEW.* values. + * @param orconf ON CONFLICT policy. + * @param ignore_jump Instruction to jump to for RAISE(IGNORE). + */ +void +vdbe_code_row_trigger_direct(struct Parse *parser, struct sql_trigger *trigger, + struct Table *table, int reg, int orconf, + int ignore_jump); + void sqlite3DeleteTriggerStep(sqlite3 *, TriggerStep *); TriggerStep *sqlite3TriggerSelectStep(sqlite3 *, Select *); TriggerStep *sqlite3TriggerInsertStep(sqlite3 *, Token *, IdList *, @@ -4082,21 +4213,53 @@ TriggerStep *sqlite3TriggerInsertStep(sqlite3 *, Token *, IdList *, TriggerStep *sqlite3TriggerUpdateStep(sqlite3 *, Token *, ExprList *, Expr *, u8); TriggerStep *sqlite3TriggerDeleteStep(sqlite3 *, Token *, Expr *); -u32 sqlite3TriggerColmask(Parse *, Trigger *, ExprList *, int, int, Table *, - int); + +/** + * 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]. 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.PK or new.PK 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. + * + * @param parser Parse context. + * @param trigger List of triggers on table. + * @param changes_list Changes list for any UPDATE OF triggers. + * @param new 1 for new.* ref mask, 0 for old.* ref mask. + * @param tr_tm Mask of TRIGGER_BEFORE|TRIGGER_AFTER. + * @param table The table to code triggers from. + * @param orconf Default ON CONFLICT policy for trigger steps. + * + * @retval mask value. + */ +u32 +sql_trigger_colmask(Parse *parser, struct sql_trigger *trigger, + ExprList *changes_list, int new, int tr_tm, + Table *table, int orconf); #define sqlite3ParseToplevel(p) ((p)->pToplevel ? (p)->pToplevel : (p)) #define sqlite3IsToplevel(p) ((p)->pToplevel==0) -#else -#define sqlite3TriggersExist(C,D,E,F) 0 -#define sql_trigger_delete(A,B) -#define sqlite3DropTriggerPtr(A,B) -#define sqlite3UnlinkAndDeleteTrigger(A,B,C) -#define sqlite3CodeRowTrigger(A,B,C,D,E,F,G,H,I) -#define sqlite3CodeRowTriggerDirect(A,B,C,D,E,F) -#define sqlite3ParseToplevel(p) p -#define sqlite3IsToplevel(p) 1 -#define sqlite3TriggerColmask(A,B,C,D,E,F,G) 0 -#endif int sqlite3JoinType(Parse *, Token *, Token *, Token *); void sqlite3CreateForeignKey(Parse *, ExprList *, Token *, ExprList *, int); @@ -4465,7 +4628,7 @@ void sqlite3WithPush(Parse *, With *, u8); * this case foreign keys are parsed, but no other functionality is * provided (enforcement of FK constraints requires the triggers sub-system). */ -#if !defined(SQLITE_OMIT_FOREIGN_KEY) && !defined(SQLITE_OMIT_TRIGGER) +#if !defined(SQLITE_OMIT_FOREIGN_KEY) void sqlite3FkCheck(Parse *, Table *, int, int, int *); void sqlite3FkDropTable(Parse *, SrcList *, Table *); void sqlite3FkActions(Parse *, Table *, ExprList *, int, int *); diff --git a/src/box/sql/status.c b/src/box/sql/status.c index dda91c5..161136c 100644 --- a/src/box/sql/status.c +++ b/src/box/sql/status.c @@ -257,7 +257,7 @@ sqlite3_db_status(sqlite3 * db, /* The database connection whose status is desir for (p = sqliteHashFirst(&pSchema->trigHash); p; p = sqliteHashNext(p)) { sql_trigger_delete(db, - (Trigger *) + (struct sql_trigger *) sqliteHashData(p)); } for (p = sqliteHashFirst(&pSchema->tblHash); p; diff --git a/src/box/sql/tokenize.c b/src/box/sql/tokenize.c index edcc45b..7c3dabe 100644 --- a/src/box/sql/tokenize.c +++ b/src/box/sql/tokenize.c @@ -597,14 +597,14 @@ sql_view_compile(struct sqlite3 *db, const char *view_stmt) return select; } -struct Trigger * +struct sql_trigger * sql_trigger_compile(struct sqlite3 *db, const char *sql) { struct Parse parser; sql_parser_create(&parser, db); parser.parse_only = true; char *sql_error; - struct Trigger *trigger = NULL; + struct sql_trigger *trigger = NULL; if (sqlite3RunParser(&parser, sql, &sql_error) != SQLITE_OK || parser.parsed_ast_type != AST_TYPE_TRIGGER) { if (parser.rc != SQL_TARANTOOL_ERROR) diff --git a/src/box/sql/treeview.c b/src/box/sql/treeview.c index 84d839e..4261e73 100644 --- a/src/box/sql/treeview.c +++ b/src/box/sql/treeview.c @@ -565,7 +565,6 @@ sqlite3TreeViewExpr(TreeView * pView, const Expr * pExpr, u8 moreToFollow) sqlite3TreeViewExprList(pView, pExpr->x.pList, 0, 0); break; } -#ifndef SQLITE_OMIT_TRIGGER case TK_RAISE:{ const char *zType = "unk"; switch (pExpr->affinity) { @@ -586,7 +585,6 @@ sqlite3TreeViewExpr(TreeView * pView, const Expr * pExpr, u8 moreToFollow) pExpr->u.zToken); break; } -#endif case TK_MATCH:{ sqlite3TreeViewLine(pView, "MATCH {%d:%d}%s", pExpr->iTable, pExpr->iColumn, diff --git a/src/box/sql/trigger.c b/src/box/sql/trigger.c index 3ec77c7..b23827d 100644 --- a/src/box/sql/trigger.c +++ b/src/box/sql/trigger.c @@ -42,7 +42,6 @@ /* See comment in sqliteInt.h */ int sqlSubProgramsRemaining; -#ifndef SQLITE_OMIT_TRIGGER /* * Delete a linked list of TriggerStep structures. */ @@ -62,46 +61,36 @@ sqlite3DeleteTriggerStep(sqlite3 * db, TriggerStep * pTriggerStep) } } -/* - * 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 * 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 */ - Expr * pWhen, /* WHEN clause */ - int noErr /* Suppress errors if the trigger already exists */ - ) +sql_trigger_begin(struct Parse *parse, struct Token *name, int tr_tm, + int op, struct IdList *columns, struct SrcList *table, + struct Expr *when, int no_err) { - Trigger *pTrigger = 0; /* The new trigger */ - char *zName = 0; /* Name of the trigger */ - sqlite3 *db = pParse->db; /* The database connection */ - DbFixer sFix; /* State vector for the DB fixer */ + /* The new trigger. */ + struct sql_trigger *trigger = NULL; + /* The database connection. */ + struct sqlite3 *db = parse->db; + /* State vector for the DB fixer. */ + struct DbFixer fixdb; + /* The name of the Trigger. */ + char *trigger_name = NULL; /* * Do not account nested operations: the count of such * operations depends on Tarantool data dictionary * internals, such as data layout in system spaces. */ - if (!pParse->nested) { - Vdbe *v = sqlite3GetVdbe(pParse); + if (!parse->nested) { + struct Vdbe *v = sqlite3GetVdbe(parse); if (v != NULL) sqlite3VdbeCountChanges(v); } /* pName->z might be NULL, but not pName itself. */ - assert(pName != NULL); + assert(name != NULL); assert(op == TK_INSERT || op == TK_UPDATE || op == TK_DELETE); assert(op > 0 && op < 0xff); - if (pTableName == NULL || db->mallocFailed) + if (table == NULL || db->mallocFailed) goto trigger_cleanup; /* @@ -110,31 +99,31 @@ sqlite3BeginTrigger(Parse * pParse, /* The parse context of the CREATE TRIGGER s */ if (db->mallocFailed) goto trigger_cleanup; - assert(pTableName->nSrc == 1); - sqlite3FixInit(&sFix, pParse, "trigger", pName); - if (sqlite3FixSrcList(&sFix, pTableName) != 0) + assert(table->nSrc == 1); + sqlite3FixInit(&fixdb, parse, "trigger", name); + if (sqlite3FixSrcList(&fixdb, table) != 0) goto trigger_cleanup; - zName = sqlite3NameFromToken(db, pName); - if (zName == NULL) + trigger_name = sqlite3NameFromToken(db, name); + if (trigger_name == NULL) goto trigger_cleanup; - if (sqlite3CheckIdentifierName(pParse, zName) != SQLITE_OK) + if (sqlite3CheckIdentifierName(parse, trigger_name) != SQLITE_OK) goto trigger_cleanup; - if (!pParse->parse_only && - sqlite3HashFind(&db->pSchema->trigHash, zName) != NULL) { - if (!noErr) { - diag_set(ClientError, ER_TRIGGER_EXISTS, zName); - pParse->rc = SQL_TARANTOOL_ERROR; - pParse->nErr++; + if (!parse->parse_only && + sqlite3HashFind(&db->pSchema->trigHash, trigger_name) != NULL) { + if (!no_err) { + diag_set(ClientError, ER_TRIGGER_EXISTS, trigger_name); + parse->rc = SQL_TARANTOOL_ERROR; + parse->nErr++; } else { assert(!db->init.busy); } goto trigger_cleanup; } - const char *table_name = pTableName->a[0].zName; + const char *table_name = table->a[0].zName; uint32_t space_id; if (schema_find_id(BOX_SPACE_ID, 2, table_name, strlen(table_name), &space_id) != 0) @@ -145,159 +134,161 @@ sqlite3BeginTrigger(Parse * pParse, /* The parse context of the CREATE TRIGGER s } /* Build the Trigger object. */ - pTrigger = (Trigger *)sqlite3DbMallocZero(db, sizeof(Trigger)); - if (pTrigger == NULL) + trigger = (struct sql_trigger *)sqlite3DbMallocZero(db, + sizeof(struct + sql_trigger)); + if (trigger == NULL) goto trigger_cleanup; - pTrigger->space_id = space_id; - pTrigger->zName = zName; - zName = NULL; - - pTrigger->op = (u8) op; - pTrigger->tr_tm = tr_tm; - pTrigger->pWhen = sqlite3ExprDup(db, pWhen, EXPRDUP_REDUCE); - pTrigger->pColumns = sqlite3IdListDup(db, pColumns); - if ((pWhen != NULL && pTrigger->pWhen == NULL) || - (pColumns != NULL && pTrigger->pColumns == NULL)) + trigger->space_id = space_id; + trigger->zName = trigger_name; + trigger_name = NULL; + + trigger->op = (u8) op; + trigger->tr_tm = tr_tm; + trigger->pWhen = sqlite3ExprDup(db, when, EXPRDUP_REDUCE); + trigger->pColumns = sqlite3IdListDup(db, columns); + if ((when != NULL && trigger->pWhen == NULL) || + (columns != NULL && trigger->pColumns == NULL)) goto trigger_cleanup; - assert(pParse->parsed_ast.trigger == NULL); - pParse->parsed_ast.trigger = pTrigger; - pParse->parsed_ast_type = AST_TYPE_TRIGGER; + assert(parse->parsed_ast.trigger == NULL); + parse->parsed_ast.trigger = trigger; + parse->parsed_ast_type = AST_TYPE_TRIGGER; trigger_cleanup: - sqlite3DbFree(db, zName); - sqlite3SrcListDelete(db, pTableName); - sqlite3IdListDelete(db, pColumns); - sql_expr_delete(db, pWhen, false); - if (pParse->parsed_ast.trigger == NULL) - sql_trigger_delete(db, pTrigger); + sqlite3DbFree(db, trigger_name); + sqlite3SrcListDelete(db, table); + sqlite3IdListDelete(db, columns); + sql_expr_delete(db, when, false); + if (parse->parsed_ast.trigger == NULL) + sql_trigger_delete(db, trigger); else - assert(pParse->parsed_ast.trigger == pTrigger); + assert(parse->parsed_ast.trigger == trigger); return; set_tarantool_error_and_cleanup: - pParse->rc = SQL_TARANTOOL_ERROR; - pParse->nErr++; + parse->rc = SQL_TARANTOOL_ERROR; + parse->nErr++; 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 */ - ) +sql_trigger_finish(struct Parse *parse, struct TriggerStep *step_list, + struct Token *token) { /* Trigger being finished. */ - Trigger *pTrig = pParse->parsed_ast.trigger; - char *zName; /* Name of trigger */ - char *zSql = 0; /* SQL text */ - char *zOpts = 0; /* MsgPack containing SQL options */ - sqlite3 *db = pParse->db; /* The database */ - DbFixer sFix; /* Fixer object */ - Token nameToken; /* Trigger name for error reporting */ - - pParse->parsed_ast.trigger = NULL; - if (NEVER(pParse->nErr) || !pTrig) + struct sql_trigger *trigger = parse->parsed_ast.trigger; + /* Name of trigger. */ + char *trigger_name; + /* SQL text. */ + char *sql_str = NULL; + /* MsgPack containing SQL options. */ + char *opts_buff = NULL; + /* The database. */ + struct sqlite3 *db = parse->db; + + parse->parsed_ast.trigger = NULL; + if (NEVER(parse->nErr) || trigger == NULL) goto triggerfinish_cleanup; - zName = pTrig->zName; - pTrig->step_list = pStepList; - while (pStepList) { - pStepList->pTrig = pTrig; - pStepList = pStepList->pNext; + trigger_name = trigger->zName; + trigger->step_list = step_list; + while (step_list != NULL) { + step_list->trigger = trigger; + step_list = step_list->pNext; } - sqlite3TokenInit(&nameToken, pTrig->zName); - sqlite3FixInit(&sFix, pParse, "trigger", &nameToken); - if (sqlite3FixTriggerStep(&sFix, pTrig->step_list) - || sqlite3FixExpr(&sFix, pTrig->pWhen) - ) { + + /* Trigger name for error reporting. */ + struct Token trigger_name_token; + /* Fixer object. */ + struct DbFixer fixdb; + sqlite3TokenInit(&trigger_name_token, trigger->zName); + sqlite3FixInit(&fixdb, parse, "trigger", &trigger_name_token); + if (sqlite3FixTriggerStep(&fixdb, trigger->step_list) + || sqlite3FixExpr(&fixdb, trigger->pWhen)) goto triggerfinish_cleanup; - } /* * Generate byte code to insert a new trigger into * Tarantool for non-parsing mode or export trigger. */ - if (!pParse->parse_only) { - Vdbe *v; - int zOptsSz; - Table *pSysTrigger; - int iFirstCol; - int iCursor = pParse->nTab++; - int iRecord; - - /* Make an entry in the _trigger space. */ - v = sqlite3GetVdbe(pParse); + if (!parse->parse_only) { + /* Make an entry in the _trigger space. */ + struct Vdbe *v = sqlite3GetVdbe(parse); if (v == 0) goto triggerfinish_cleanup; - pSysTrigger = sqlite3HashFind(&pParse->db->pSchema->tblHash, - TARANTOOL_SYS_TRIGGER_NAME); - if (NEVER(!pSysTrigger)) + struct Table *sys_trigger = + sqlite3HashFind(&parse->db->pSchema->tblHash, + TARANTOOL_SYS_TRIGGER_NAME); + if (NEVER(sys_trigger == NULL)) goto triggerfinish_cleanup; - zSql = sqlite3MPrintf(db, "CREATE TRIGGER %s", pAll->z); + sql_str = sqlite3MPrintf(db, "CREATE TRIGGER %s", token->z); if (db->mallocFailed) goto triggerfinish_cleanup; - sqlite3OpenTable(pParse, iCursor, pSysTrigger, OP_OpenWrite); + int cursor = parse->nTab++; + sqlite3OpenTable(parse, cursor, sys_trigger, OP_OpenWrite); - /* makerecord(cursor(iRecord), [reg(iFirstCol), reg(iFirstCol+1)]) */ - iFirstCol = pParse->nMem + 1; - pParse->nMem += 3; - iRecord = ++pParse->nMem; + /* + * makerecord(cursor(iRecord), + * [reg(first_col), reg(first_col+1)]). + */ + int first_col = parse->nMem + 1; + parse->nMem += 3; + int record = ++parse->nMem; - zOpts = sqlite3DbMallocRaw(pParse->db, + opts_buff = + sqlite3DbMallocRaw(parse->db, tarantoolSqlite3MakeTableOpts(0, - zSql, + sql_str, NULL) + 1); if (db->mallocFailed) goto triggerfinish_cleanup; - zOptsSz = tarantoolSqlite3MakeTableOpts(0, zSql, zOpts); + int opts_buff_sz = + tarantoolSqlite3MakeTableOpts(0, sql_str, opts_buff); - zName = sqlite3DbStrDup(pParse->db, zName); + trigger_name = sqlite3DbStrDup(parse->db, trigger_name); if (db->mallocFailed) goto triggerfinish_cleanup; sqlite3VdbeAddOp4(v, - OP_String8, 0, iFirstCol, 0, - zName, P4_DYNAMIC); - sqlite3VdbeAddOp2(v, OP_Integer, pTrig->space_id, iFirstCol + 1); - sqlite3VdbeAddOp4(v, OP_Blob, zOptsSz, iFirstCol + 2, - MSGPACK_SUBTYPE, zOpts, P4_DYNAMIC); - sqlite3VdbeAddOp3(v, OP_MakeRecord, iFirstCol, 3, iRecord); - sqlite3VdbeAddOp2(v, OP_IdxInsert, iCursor, iRecord); - /* Do not account nested operations: the count of such - * operations depends on Tarantool data dictionary internals, - * such as data layout in system spaces. + OP_String8, 0, first_col, 0, + trigger_name, P4_DYNAMIC); + sqlite3VdbeAddOp2(v, OP_Integer, trigger->space_id, + first_col + 1); + sqlite3VdbeAddOp4(v, OP_Blob, opts_buff_sz, first_col + 2, + MSGPACK_SUBTYPE, opts_buff, P4_DYNAMIC); + sqlite3VdbeAddOp3(v, OP_MakeRecord, first_col, 3, record); + sqlite3VdbeAddOp2(v, OP_IdxInsert, cursor, record); + /* + * Do not account nested operations: the count of + * such operations depends on Tarantool data + * dictionary internals, such as data layout in + * system spaces. */ - if (!pParse->nested) + if (!parse->nested) sqlite3VdbeChangeP5(v, OPFLAG_NCHANGE); - sqlite3VdbeAddOp1(v, OP_Close, iCursor); + sqlite3VdbeAddOp1(v, OP_Close, cursor); - sql_set_multi_write(pParse, false); - sqlite3ChangeCookie(pParse); + sql_set_multi_write(parse, false); + sqlite3ChangeCookie(parse); } else { - pParse->parsed_ast.trigger = pTrig; - pParse->parsed_ast_type = AST_TYPE_TRIGGER; - pTrig = NULL; + parse->parsed_ast.trigger = trigger; + parse->parsed_ast_type = AST_TYPE_TRIGGER; + trigger = NULL; } triggerfinish_cleanup: if (db->mallocFailed) { - sqlite3DbFree(db, zSql); - sqlite3DbFree(db, zOpts); - /* No need to free zName sinceif we reach this point - alloc for it either wasn't called at all or failed. */ + sqlite3DbFree(db, sql_str); + sqlite3DbFree(db, opts_buff); } - sql_trigger_delete(db, pTrig); - assert(pParse->parsed_ast.trigger == NULL || pParse->parse_only); - sqlite3DeleteTriggerStep(db, pStepList); + sql_trigger_delete(db, trigger); + assert(parse->parsed_ast.trigger == NULL || parse->parse_only); + sqlite3DeleteTriggerStep(db, step_list); } /* @@ -439,7 +430,7 @@ sqlite3TriggerDeleteStep(sqlite3 * db, /* Database connection */ } void -sql_trigger_delete(struct sqlite3 *db, struct Trigger *trigger) +sql_trigger_delete(struct sqlite3 *db, struct sql_trigger *trigger) { if (trigger == NULL) return; @@ -451,17 +442,18 @@ sql_trigger_delete(struct sqlite3 *db, struct Trigger *trigger) } /* - * This function is called to drop a trigger from the database schema. + * 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. + * This may be called directly from the parser and therefore + * identifies the trigger by name. The sql_drop_trigger_ptr() + * 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; + struct sql_trigger *trigger = NULL; const char *zName; sqlite3 *db = pParse->db; @@ -483,8 +475,8 @@ sqlite3DropTrigger(Parse * pParse, SrcList * pName, int noErr) assert(pName->nSrc == 1); zName = pName->a[0].zName; - pTrigger = sqlite3HashFind(&(db->pSchema->trigHash), zName); - if (!pTrigger) { + trigger = sqlite3HashFind(&(db->pSchema->trigHash), zName); + if (trigger == NULL) { if (!noErr) { sqlite3ErrorMsg(pParse, "no such trigger: %S", pName, 0); @@ -492,48 +484,47 @@ sqlite3DropTrigger(Parse * pParse, SrcList * pName, int noErr) pParse->checkSchema = 1; goto drop_trigger_cleanup; } - sqlite3DropTriggerPtr(pParse, pTrigger); + vdbe_code_drop_trigger_ptr(pParse, trigger); drop_trigger_cleanup: sqlite3SrcListDelete(db, pName); } -/* - * Drop a trigger given a pointer to that trigger. - */ void -sqlite3DropTriggerPtr(Parse * pParse, Trigger * pTrigger) +vdbe_code_drop_trigger_ptr(struct Parse *parser, struct sql_trigger *trigger) { - Vdbe *v; - /* Generate code to delete entry from _trigger and + struct Vdbe *v = sqlite3GetVdbe(parser); + if (v == NULL) + return; + /* + * Generate code to delete entry from _trigger and * internal SQL structures. */ - if ((v = sqlite3GetVdbe(pParse)) != 0) { - int trig_name_reg = ++pParse->nMem; - int record_to_delete = ++pParse->nMem; - sqlite3VdbeAddOp4(v, OP_String8, 0, trig_name_reg, 0, - pTrigger->zName, P4_STATIC); - sqlite3VdbeAddOp3(v, OP_MakeRecord, trig_name_reg, 1, - record_to_delete); - sqlite3VdbeAddOp2(v, OP_SDelete, BOX_TRIGGER_ID, - record_to_delete); - if (!pParse->nested) - sqlite3VdbeChangeP5(v, OPFLAG_NCHANGE); - - sqlite3ChangeCookie(pParse); - } + int trig_name_reg = ++parser->nMem; + int record_to_delete = ++parser->nMem; + sqlite3VdbeAddOp4(v, OP_String8, 0, trig_name_reg, 0, + trigger->zName, P4_STATIC); + sqlite3VdbeAddOp3(v, OP_MakeRecord, trig_name_reg, 1, + record_to_delete); + sqlite3VdbeAddOp2(v, OP_SDelete, BOX_TRIGGER_ID, + record_to_delete); + if (!parser->nested) + sqlite3VdbeChangeP5(v, OPFLAG_NCHANGE); + + sqlite3ChangeCookie(parser); } int sql_trigger_replace(struct sqlite3 *db, const char *name, - struct Trigger *trigger, struct Trigger **old_trigger) + struct sql_trigger *trigger, + struct sql_trigger **old_trigger) { assert(db->pSchema != NULL); assert(trigger == NULL || strcmp(name, trigger->zName) == 0); struct Hash *hash = &db->pSchema->trigHash; - struct Trigger *src_trigger = + struct sql_trigger *src_trigger = trigger != NULL ? trigger : sqlite3HashFind(hash, name); assert(src_trigger != NULL); struct space *space = space_cache_find(src_trigger->space_id); @@ -580,31 +571,31 @@ sql_trigger_replace(struct sqlite3 *db, const char *name, } if (*old_trigger != NULL) { - struct Trigger **pp; + struct sql_trigger **pp; for (pp = &space->sql_triggers; *pp != *old_trigger; - pp = &((*pp)->pNext)); - *pp = (*pp)->pNext; + pp = &((*pp)->next)); + *pp = (*pp)->next; } if (trigger != NULL) { - trigger->pNext = space->sql_triggers; + trigger->next = space->sql_triggers; space->sql_triggers = trigger; } return 0; } const char * -sql_trigger_name(struct Trigger *trigger) +sql_trigger_name(struct sql_trigger *trigger) { return trigger->zName; } uint32_t -sql_trigger_space_id(struct Trigger *trigger) +sql_trigger_space_id(struct sql_trigger *trigger) { return trigger->space_id; } -struct Trigger * +struct sql_trigger * space_trigger_list(uint32_t space_id) { struct space *space = space_cache_find(space_id); @@ -635,31 +626,22 @@ checkColumnOverlap(IdList * pIdList, ExprList * pEList) return 0; } -/* - * Return a list of all triggers on table pTab if there exists at least - * one trigger that must be fired when an operation of type 'op' is - * performed on the table, and, if that operation is an UPDATE, if at - * least one of the columns in pChanges is being modified. - */ -Trigger * -sqlite3TriggersExist(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 */ - ) +struct sql_trigger * +sql_triggers_exist(struct Table *table, int op, struct ExprList *changes_list, + int *mask_ptr) { int mask = 0; - struct Trigger *trigger_list = NULL; + struct sql_trigger *trigger_list = NULL; struct session *user_session = current_session(); if ((user_session->sql_flags & SQLITE_EnableTrigger) != 0) - trigger_list = space_trigger_list(pTab->def->id); - for (struct Trigger *p = trigger_list; p != NULL; p = p->pNext) { + trigger_list = space_trigger_list(table->def->id); + for (struct sql_trigger *p = trigger_list; p != NULL; p = p->next) { if (p->op == op && checkColumnOverlap(p->pColumns, - pChanges) != 0) + changes_list) != 0) mask |= p->tr_tm; } - if (pMask != NULL) - *pMask = mask; + if (mask_ptr != NULL) + *mask_ptr = mask; return mask != 0 ? trigger_list : NULL; } @@ -829,28 +811,33 @@ transferParseError(Parse * pTo, Parse * pFrom) pFrom->zErrMsg = NULL; } -/* +/** * Create and populate a new TriggerPrg object with a sub-program * implementing trigger pTrigger with ON CONFLICT policy orconf. + * + * @param parser Current parse context. + * @param trigger sql_trigger to code. + * @param table trigger is attached to. + * @param orconf ON CONFLICT policy to code trigger program with. + * + * @retval not NULL on success. + * @retval NULL on error. */ 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 */ - ) +sql_row_trigger_program(struct Parse *parser, struct sql_trigger *trigger, + struct Table *table, int orconf) { - Parse *pTop = sqlite3ParseToplevel(pParse); - sqlite3 *db = pParse->db; /* Database handle */ + Parse *pTop = sqlite3ParseToplevel(parser); + /* Database handle. */ + sqlite3 *db = parser->db; 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 */ Parse *pSubParse; /* Parse context for sub-vdbe */ int iEndTrigger = 0; /* Label to jump to if WHEN is false */ - assert(pTrigger->zName == NULL || pTab->def->id == pTrigger->space_id); + assert(trigger->zName == NULL || table->def->id == trigger->space_id); assert(pTop->pVdbe); /* Allocate the TriggerPrg and SubProgram objects. To ensure that they @@ -866,13 +853,14 @@ codeRowTrigger(Parse * pParse, /* Current parse context */ if (!pProgram) return 0; sqlite3VdbeLinkSubProgram(pTop->pVdbe, pProgram); - pPrg->pTrigger = pTrigger; + pPrg->trigger = trigger; 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. + /* + * Allocate and populate a new Parse context to use for + * coding the trigger sub-program. */ pSubParse = sqlite3StackAllocZero(db, sizeof(Parse)); if (!pSubParse) @@ -880,34 +868,37 @@ codeRowTrigger(Parse * pParse, /* Current parse context */ sql_parser_create(pSubParse, db); memset(&sNC, 0, sizeof(sNC)); sNC.pParse = pSubParse; - pSubParse->pTriggerTab = pTab; + pSubParse->pTriggerTab = table; pSubParse->pToplevel = pTop; - pSubParse->eTriggerOp = pTrigger->op; - pSubParse->nQueryLoop = pParse->nQueryLoop; + pSubParse->eTriggerOp = trigger->op; + pSubParse->nQueryLoop = parser->nQueryLoop; - v = sqlite3GetVdbe(pSubParse); - if (v) { + /* Temporary VM. */ + struct Vdbe *v = sqlite3GetVdbe(pSubParse); + if (v != NULL) { VdbeComment((v, "Start: %s.%s (%s %s%s%s ON %s)", - pTrigger->zName, onErrorText(orconf), - (pTrigger->tr_tm == + trigger->zName, onErrorText(orconf), + (trigger->tr_tm == TRIGGER_BEFORE ? "BEFORE" : "AFTER"), - (pTrigger->op == TK_UPDATE ? "UPDATE" : ""), - (pTrigger->op == TK_INSERT ? "INSERT" : ""), - (pTrigger->op == TK_DELETE ? "DELETE" : ""), - pTab->def->name)); + (trigger->op == TK_UPDATE ? "UPDATE" : ""), + (trigger->op == TK_INSERT ? "INSERT" : ""), + (trigger->op == TK_DELETE ? "DELETE" : ""), + table->def->name)); #ifndef SQLITE_OMIT_TRACE sqlite3VdbeChangeP4(v, -1, sqlite3MPrintf(db, "-- TRIGGER %s", - pTrigger->zName), + trigger->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 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 (trigger->pWhen != NULL) { + pWhen = sqlite3ExprDup(db, trigger->pWhen, 0); if (SQLITE_OK == sqlite3ResolveExprNames(&sNC, pWhen) && db->mallocFailed == 0) { iEndTrigger = sqlite3VdbeMakeLabel(v); @@ -919,17 +910,16 @@ codeRowTrigger(Parse * pParse, /* Current parse context */ } /* Code the trigger program into the sub-vdbe. */ - codeTriggerProgram(pSubParse, pTrigger->step_list, orconf); + codeTriggerProgram(pSubParse, trigger->step_list, orconf); /* Insert an OP_Halt at the end of the sub-program. */ - if (iEndTrigger) { + if (iEndTrigger) sqlite3VdbeResolveLabel(v, iEndTrigger); - } sqlite3VdbeAddOp0(v, OP_Halt); - VdbeComment((v, "End: %s.%s", pTrigger->zName, + VdbeComment((v, "End: %s.%s", trigger->zName, onErrorText(orconf))); - transferParseError(pParse, pSubParse); + transferParseError(parser, pSubParse); if (db->mallocFailed == 0) { pProgram->aOp = sqlite3VdbeTakeOpArray(v, &pProgram->nOp, @@ -937,7 +927,7 @@ codeRowTrigger(Parse * pParse, /* Current parse context */ } pProgram->nMem = pSubParse->nMem; pProgram->nCsr = pSubParse->nTab; - pProgram->token = (void *)pTrigger; + pProgram->token = (void *)trigger; pPrg->aColmask[0] = pSubParse->oldmask; pPrg->aColmask[1] = pSubParse->newmask; sqlite3VdbeDelete(v); @@ -951,211 +941,130 @@ codeRowTrigger(Parse * pParse, /* Current parse context */ 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. +/** + * Return a pointer to a TriggerPrg object containing the + * sub-program for trigger with default ON CONFLICT algorithm + * orconf. If no such TriggerPrg object exists, a new object is + * allocated and populated before being returned. + * + * @param parser Current parse context. + * @param trigger Trigger to code. + * @param table table trigger is attached to. + * @param orconf ON CONFLICT algorithm. + * + * @retval not NULL on success. + * @retval NULL on error. */ 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. */ - ) +sql_row_trigger(struct Parse *parser, struct sql_trigger *trigger, + struct Table *table, int orconf) { - Parse *pRoot = sqlite3ParseToplevel(pParse); + Parse *pRoot = sqlite3ParseToplevel(parser); TriggerPrg *pPrg; - assert(pTrigger->zName == NULL || pTab->def->id == pTrigger->space_id); + assert(trigger->zName == NULL || table->def->id == trigger->space_id); - /* 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. + /* + * 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->trigger != trigger || 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); - } + /* + * If an existing TriggerPrg could not be located, create + * a new one. + */ + if (pPrg == NULL) + pPrg = sql_row_trigger_program(parser, trigger, table, orconf); 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_code_row_trigger_direct(struct Parse *parser, struct sql_trigger *trigger, + struct Table *table, int reg, int orconf, + int ignore_jump) { - Vdbe *v = sqlite3GetVdbe(pParse); /* Main VM */ - TriggerPrg *pPrg; - struct session *user_session = current_session(); + /* Main VM. */ + struct Vdbe *v = sqlite3GetVdbe(parser); - pPrg = getRowTrigger(pParse, p, pTab, orconf); - assert(pPrg || pParse->nErr || pParse->db->mallocFailed); + TriggerPrg *pPrg = sql_row_trigger(parser, trigger, table, orconf); + assert(pPrg != NULL || parser->nErr != 0 || + parser->db->mallocFailed != 0); - /* 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. + /* + * 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 == - (user_session-> - sql_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))); + if (pPrg == NULL) + return; - /* 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); - } + struct session *user_session = current_session(); + bool recursive = (trigger->zName && !(user_session->sql_flags & + SQLITE_RecTriggers)); + + sqlite3VdbeAddOp4(v, OP_Program, reg, ignore_jump, + ++parser->nMem, (const char *)pPrg->pProgram, + P4_SUBPROGRAM); + VdbeComment((v, "Call: %s.%s", (trigger->zName ? trigger->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)recursive); } -/* - * 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.PK - * reg+1 OLD.* value of left-most column of pTab - * ... ... - * reg+N OLD.* value of right-most column of pTab - * reg+N+1 NEW.PK - * reg+N+2 OLD.* 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) */ - ) +vdbe_code_row_trigger(struct Parse *parser, struct sql_trigger *trigger, + int op, struct ExprList *changes_list, int tr_tm, + struct Table *table, int reg, int orconf, int ignore_jump) { - 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) { - /* Determine whether we should code this trigger */ - if (p->op == op - && p->tr_tm == tr_tm - && checkColumnOverlap(p->pColumns, pChanges) - ) { - sqlite3CodeRowTriggerDirect(pParse, p, pTab, reg, - orconf, ignoreJump); + assert((op == TK_UPDATE) == (changes_list != NULL)); + + for (struct sql_trigger *p = trigger; p != NULL; p = p->next) { + /* Determine whether we should code trigger. */ + if (p->op == op && p->tr_tm == tr_tm && + checkColumnOverlap(p->pColumns, changes_list)) { + vdbe_code_row_trigger_direct(parser, p, table, reg, + orconf, ignore_jump); } } } -/* - * 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]. 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.PK or new.PK 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 */ - ) +sql_trigger_colmask(Parse *parser, struct sql_trigger *trigger, + ExprList *changes_list, int new, int tr_tm, + Table *table, int orconf) { - const int op = pChanges ? TK_UPDATE : TK_DELETE; + const int op = changes_list != NULL ? TK_UPDATE : TK_DELETE; u32 mask = 0; - Trigger *p; - assert(isNew == 1 || isNew == 0); - for (p = pTrigger; p; p = p->pNext) { + assert(new == 1 || new == 0); + for (struct sql_trigger *p = trigger; p != NULL; p = p->next) { if (p->op == op && (tr_tm & p->tr_tm) - && checkColumnOverlap(p->pColumns, pChanges) - ) { - TriggerPrg *pPrg; - pPrg = getRowTrigger(pParse, p, pTab, orconf); - if (pPrg) { - mask |= pPrg->aColmask[isNew]; - } + && checkColumnOverlap(p->pColumns, changes_list)) { + TriggerPrg *prg = + sql_row_trigger(parser, p, table, orconf); + if (prg != NULL) + mask |= prg->aColmask[new]; } } return mask; } - -#endif /* !defined(SQLITE_OMIT_TRIGGER) */ diff --git a/src/box/sql/update.c b/src/box/sql/update.c index 10385eb..212adbc 100644 --- a/src/box/sql/update.c +++ b/src/box/sql/update.c @@ -106,7 +106,8 @@ sqlite3Update(Parse * pParse, /* The parser context */ struct session *user_session = current_session(); bool is_view; /* True when updating a view (INSTEAD OF trigger) */ - Trigger *pTrigger; /* List of triggers on pTab, if required */ + /* List of triggers on pTab, if required. */ + struct sql_trigger *trigger; int tmask; /* Mask of TRIGGER_BEFORE|TRIGGER_AFTER */ int newmask; /* Mask of NEW.* columns accessed by BEFORE triggers */ int iEph = 0; /* Ephemeral table holding all primary key values */ @@ -136,9 +137,9 @@ sqlite3Update(Parse * pParse, /* The parser context */ /* Figure out if we have any triggers and if the table being * updated is a view. */ - pTrigger = sqlite3TriggersExist(pTab, TK_UPDATE, pChanges, &tmask); + trigger = sql_triggers_exist(pTab, TK_UPDATE, pChanges, &tmask); is_view = pTab->def->opts.is_view; - assert(pTrigger || tmask == 0); + assert(trigger != NULL || tmask == 0); if (is_view && sql_view_assign_cursors(pParse,pTab->def->opts.sql) != 0) { @@ -269,11 +270,9 @@ sqlite3Update(Parse * pParse, /* The parser context */ /* Allocate required registers. */ regOldPk = regNewPk = ++pParse->nMem; - if (chngPk || pTrigger || hasFK) { + if (chngPk != 0 || trigger != NULL || hasFK != 0) { regOld = pParse->nMem + 1; pParse->nMem += def->field_count; - } - if (chngPk || pTrigger || hasFK) { regNewPk = ++pParse->nMem; } regNew = pParse->nMem + 1; @@ -424,18 +423,18 @@ sqlite3Update(Parse * pParse, /* The parser context */ * then regNewPk is the same register as regOldPk, which is * already populated. */ - assert(chngPk || pTrigger || hasFK || regOldPk == regNewPk); + assert(chngPk != 0 || trigger != NULL || hasFK != 0 || + regOldPk == regNewPk); /* Compute the old pre-UPDATE content of the row being changed, if that * information is needed */ - if (chngPk || hasFK || pTrigger) { + if (chngPk != 0 || hasFK != 0 || trigger != NULL) { u32 oldmask = (hasFK ? sqlite3FkOldmask(pParse, pTab) : 0); - oldmask |= sqlite3TriggerColmask(pParse, - pTrigger, pChanges, 0, - TRIGGER_BEFORE | TRIGGER_AFTER, - pTab, on_error); + oldmask |= sql_trigger_colmask(pParse, trigger, pChanges, 0, + TRIGGER_BEFORE | TRIGGER_AFTER, + pTab, on_error); for (i = 0; i < (int)def->field_count; i++) { if (oldmask == 0xffffffff || (i < 32 && (oldmask & MASKBIT32(i)) != 0) @@ -464,9 +463,8 @@ sqlite3Update(Parse * pParse, /* The parser context */ * may have modified them). So not loading those that are not going to * be used eliminates some redundant opcodes. */ - newmask = - sqlite3TriggerColmask(pParse, pTrigger, pChanges, 1, TRIGGER_BEFORE, - pTab, on_error); + newmask = sql_trigger_colmask(pParse, trigger, pChanges, 1, + TRIGGER_BEFORE, pTab, on_error); for (i = 0; i < (int)def->field_count; i++) { if (i == pTab->iPKey) { sqlite3VdbeAddOp2(v, OP_Null, 0, regNew + i); @@ -498,7 +496,7 @@ sqlite3Update(Parse * pParse, /* The parser context */ */ if (tmask & TRIGGER_BEFORE) { sqlite3TableAffinity(v, pTab, regNew); - sqlite3CodeRowTrigger(pParse, pTrigger, TK_UPDATE, pChanges, + vdbe_code_row_trigger(pParse, trigger, TK_UPDATE, pChanges, TRIGGER_BEFORE, pTab, regOldPk, on_error, labelContinue); @@ -611,7 +609,7 @@ sqlite3Update(Parse * pParse, /* The parser context */ sqlite3VdbeAddOp2(v, OP_AddImm, regRowCount, 1); } - sqlite3CodeRowTrigger(pParse, pTrigger, TK_UPDATE, pChanges, + vdbe_code_row_trigger(pParse, trigger, TK_UPDATE, pChanges, TRIGGER_AFTER, pTab, regOldPk, on_error, labelContinue); diff --git a/src/box/sql/vdbe.c b/src/box/sql/vdbe.c index cac1ad3..b0ab916 100644 --- a/src/box/sql/vdbe.c +++ b/src/box/sql/vdbe.c @@ -4733,7 +4733,7 @@ case OP_RenameTable: { space = space_by_id(space_id); assert(space); /* Rename space op doesn't change triggers. */ - struct Trigger *triggers = space->sql_triggers; + struct sql_trigger *triggers = space->sql_triggers; zOldTableName = space_name(space); assert(zOldTableName); pTab = sqlite3HashFind(&db->pSchema->tblHash, zOldTableName); @@ -4784,9 +4784,9 @@ case OP_RenameTable: { * due to lack of transactional DDL, but just do the best * effort. */ - for (struct Trigger *trigger = triggers; trigger != NULL; ) { + for (struct sql_trigger *trigger = triggers; trigger != NULL; ) { /* Store pointer as trigger will be destructed. */ - struct Trigger *next_trigger = trigger->pNext; + struct sql_trigger *next_trigger = trigger->next; rc = tarantoolSqlite3RenameTrigger(trigger->zName, zOldTableName, zNewTableName); if (rc != SQLITE_OK) { @@ -4849,8 +4849,6 @@ case OP_DropIndex: { break; } -#ifndef SQLITE_OMIT_TRIGGER - /* Opcode: Program P1 P2 P3 P4 P5 * * Execute the trigger program passed as P4 (type P4_SUBPROGRAM). @@ -5006,8 +5004,6 @@ case OP_Param: { /* out2 */ break; } -#endif /* #ifndef SQLITE_OMIT_TRIGGER */ - #ifndef SQLITE_OMIT_FOREIGN_KEY /* Opcode: FkCounter P1 P2 * * * * Synopsis: fkctr[P1]+=P2 diff --git a/src/box/sql/vdbe.h b/src/box/sql/vdbe.h index 45a89d9..03ae44e 100644 --- a/src/box/sql/vdbe.h +++ b/src/box/sql/vdbe.h @@ -302,9 +302,7 @@ UnpackedRecord *sqlite3VdbeAllocUnpackedRecord(struct sqlite3 *, struct key_def *); int sql_vdbe_mem_alloc_region(Mem *, uint32_t); -#ifndef SQLITE_OMIT_TRIGGER void sqlite3VdbeLinkSubProgram(Vdbe *, SubProgram *); -#endif /* Use SQLITE_ENABLE_COMMENTS to enable generation of extra comments on * each VDBE opcode. -- 2.7.4