[Tarantool-patches] [PATCH v1 6/9] sql: rework CREATE TABLE rule in parser
Kirill Shcherbatov
kshcherbatov at tarantool.org
Mon Oct 14 19:03:21 MSK 2019
SQL grammar used to construct the sql_trigger object or to
generate a related VDBE instructions in a two functions with
a shared context: sql_trigger_begin and sql_trigger_end.
It used to be an AST compile or VDBE code generation in according
to parser::is_parse_only flag state - that is messy.
This patch reworks SQL rule a bit to call a function once, when
the whole trigger context is assembled to call the first function
that constructs AST or the second to emit VDBE code.
Needed for #4343.
---
src/box/sql/build.c | 82 ++++++++++++++++++++++++++
src/box/sql/parse.y | 26 ++++-----
src/box/sql/parse_def.h | 13 ++++-
src/box/sql/sqlInt.h | 18 ++----
src/box/sql/trigger.c | 121 +++------------------------------------
test/sql/triggers.result | 16 +++---
test/sql/upgrade.result | 4 +-
7 files changed, 128 insertions(+), 152 deletions(-)
diff --git a/src/box/sql/build.c b/src/box/sql/build.c
index 9f9f80b70..5040d04d1 100644
--- a/src/box/sql/build.c
+++ b/src/box/sql/build.c
@@ -1281,6 +1281,88 @@ sqlEndTable(struct Parse *pParse)
}
}
+void
+sql_create_trigger(struct Parse *parse_context)
+{
+ struct create_trigger_def *trigger_def =
+ &parse_context->create_trigger_def;
+ struct create_entity_def *create_def = &trigger_def->base;
+ struct alter_entity_def *alter_def = &create_def->base;
+ assert(alter_def->entity_type == ENTITY_TYPE_TRIGGER);
+ assert(alter_def->alter_action == ALTER_ACTION_CREATE);
+ struct sql *db = parse_context->db;
+
+ char *trigger_name = sql_name_from_token(db, &create_def->name);
+ if (trigger_name == NULL ||
+ sqlCheckIdentifierName(parse_context, trigger_name) != 0) {
+ parse_context->is_aborted = true;
+ goto end;
+ }
+
+ struct Vdbe *v = sqlGetVdbe(parse_context);
+ if (v == NULL)
+ goto end;
+ sqlVdbeCountChanges(v);
+ const char *error_msg = tt_sprintf(tnt_errcode_desc(ER_TRIGGER_EXISTS),
+ trigger_name);
+ int name_reg = ++parse_context->nMem;
+ sqlVdbeAddOp4(v, OP_String8, 0, name_reg, 0,
+ sqlDbStrDup(db, trigger_name), P4_DYNAMIC);
+ if (vdbe_emit_halt_with_presence_test(parse_context, BOX_TRIGGER_ID, 0,
+ name_reg, 1, ER_TRIGGER_EXISTS,
+ error_msg,
+ create_def->if_not_exist,
+ OP_NoConflict) != 0) {
+ parse_context->is_aborted = true;
+ goto end;
+ }
+
+ const char *table_name = alter_def->entity_name->a[0].zName;
+ uint32_t space_id = box_space_id_by_name(table_name,
+ strlen(table_name));
+ if (space_id == BOX_ID_NIL) {
+ diag_set(ClientError, ER_NO_SUCH_SPACE, table_name);
+ parse_context->is_aborted = true;
+ goto end;
+ }
+
+ struct space *_trigger = space_by_id(BOX_TRIGGER_ID);
+ assert(_trigger != NULL);
+
+ int first_col = parse_context->nMem + 1;
+ parse_context->nMem += 3;
+ int record = ++parse_context->nMem;
+
+ uint32_t opts_buff_sz = mp_sizeof_map(1) +
+ mp_sizeof_str(strlen("sql")) +
+ mp_sizeof_str(trigger_def->sql_len);
+ char *opts_buff = (char *) sqlDbMallocRaw(db, opts_buff_sz);
+ if (opts_buff == NULL) {
+ parse_context->is_aborted = true;
+ goto end;
+ }
+
+ char *data = mp_encode_map(opts_buff, 1);
+ data = mp_encode_str(data, "sql", strlen("sql"));
+ data = mp_encode_str(data, trigger_def->sql, trigger_def->sql_len);
+ sqlVdbeAddOp4(v, OP_String8, 0, first_col, 0, trigger_name, P4_DYNAMIC);
+ sqlVdbeAddOp2(v, OP_Integer, space_id, first_col + 1);
+ sqlVdbeAddOp4(v, OP_Blob, opts_buff_sz, first_col + 2,
+ SQL_SUBTYPE_MSGPACK, opts_buff, P4_DYNAMIC);
+ sqlVdbeAddOp3(v, OP_MakeRecord, first_col, 3, record);
+ sqlVdbeAddOp4(v, OP_IdxInsert, record, 0, 0, (char *)_trigger,
+ P4_SPACEPTR);
+ sqlVdbeChangeP5(v, OPFLAG_NCHANGE);
+ trigger_name = NULL;
+
+end:
+ sqlDbFree(db, trigger_name);
+ sqlSrcListDelete(db, alter_def->entity_name);
+ sqlIdListDelete(db, trigger_def->cols);
+ sql_expr_delete(db, trigger_def->when, false);
+ sqlDeleteTriggerStep(db, trigger_def->step_list);
+}
+
void
sql_create_view(struct Parse *parse_context)
{
diff --git a/src/box/sql/parse.y b/src/box/sql/parse.y
index ed59a875a..264b24f99 100644
--- a/src/box/sql/parse.y
+++ b/src/box/sql/parse.y
@@ -1574,21 +1574,19 @@ plus_num(A) ::= number(A).
minus_num(A) ::= MINUS number(X). {A = X;}
//////////////////////////// The CREATE TRIGGER command /////////////////////
-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 = true;
- 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). {
+cmd ::= createkw(Y) TRIGGER ifnotexists(NOERR) nm(B) trigger_time(C)
+ trigger_event(D) ON fullname(E) foreach_clause when_clause(G)
+ BEGIN trigger_cmd_list(S) END(Z). {
+ int sql_len = (int)(Z.z - Y.z) + Z.n;
+ const char *sql = Y.z;
create_trigger_def_init(&pParse->create_trigger_def, E, &B, C, D.a, D.b, G,
- NOERR);
- sql_trigger_begin(pParse);
- A = B; /*A-overwrites-T*/
+ NOERR, sql, sql_len, S);
+ if (!pParse->parse_only) {
+ pParse->initiateTTrans = true;
+ sql_create_trigger(pParse);
+ } else {
+ sql_store_trigger(pParse);
+ }
}
%type trigger_time {int}
diff --git a/src/box/sql/parse_def.h b/src/box/sql/parse_def.h
index 2502fc46c..71fa89959 100644
--- a/src/box/sql/parse_def.h
+++ b/src/box/sql/parse_def.h
@@ -278,6 +278,12 @@ struct create_trigger_def {
struct IdList *cols;
/** When clause. */
struct Expr *when;
+ /** Source SQL Expression. */
+ const char *sql;
+ /** Source SQL Expression length. */
+ uint32_t sql_len;
+ /** Steps describing SQL trigger program. */
+ struct TriggerStep *step_list;
};
struct create_constraint_def {
@@ -412,7 +418,9 @@ static inline void
create_trigger_def_init(struct create_trigger_def *trigger_def,
struct SrcList *table_name, struct Token *name,
int tr_tm, int op, struct IdList *cols,
- struct Expr *when, bool if_not_exists)
+ struct Expr *when, bool if_not_exists,
+ const char *sql, uint32_t sql_len,
+ struct TriggerStep *step_list)
{
create_entity_def_init(&trigger_def->base, ENTITY_TYPE_TRIGGER,
table_name, name, if_not_exists);
@@ -420,6 +428,9 @@ create_trigger_def_init(struct create_trigger_def *trigger_def,
trigger_def->event_manipulation = trigger_event_manipulation_by_op(op);
trigger_def->cols = cols;
trigger_def->when = when;
+ trigger_def->sql = sql;
+ trigger_def->sql_len = sql_len;
+ trigger_def->step_list = step_list;
}
static inline void
diff --git a/src/box/sql/sqlInt.h b/src/box/sql/sqlInt.h
index 8c9a69a00..180997ce4 100644
--- a/src/box/sql/sqlInt.h
+++ b/src/box/sql/sqlInt.h
@@ -3364,25 +3364,17 @@ sql_materialize_view(struct Parse *parse, const char *name, struct Expr *where,
* 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.
*/
void
-sql_trigger_begin(struct Parse *parse);
+sql_store_trigger(struct Parse *parse);
/**
- * 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.
+ * This function is called from parser to generate create trigger
+ * VDBE code.
+ * @param parser Parser context.
*/
void
-sql_trigger_finish(struct Parse *parse, struct TriggerStep *step_list,
- struct Token *token);
+sql_create_trigger(struct Parse *parse);
/**
* This function is called from parser to generate drop trigger
diff --git a/src/box/sql/trigger.c b/src/box/sql/trigger.c
index 269c09a3d..3a0ee6b7d 100644
--- a/src/box/sql/trigger.c
+++ b/src/box/sql/trigger.c
@@ -63,7 +63,7 @@ sqlDeleteTriggerStep(sql * db, TriggerStep * pTriggerStep)
}
void
-sql_trigger_begin(struct Parse *parse)
+sql_store_trigger(struct Parse *parse)
{
/* The new trigger. */
struct sql_trigger *trigger = NULL;
@@ -95,28 +95,6 @@ sql_trigger_begin(struct Parse *parse)
goto set_tarantool_error_and_cleanup;
}
- if (!parse->parse_only) {
- struct Vdbe *v = sqlGetVdbe(parse);
- if (v != NULL)
- sqlVdbeCountChanges(v);
- const char *error_msg =
- tt_sprintf(tnt_errcode_desc(ER_TRIGGER_EXISTS),
- trigger_name);
- char *name_copy = sqlDbStrDup(db, trigger_name);
- if (name_copy == NULL)
- goto trigger_cleanup;
- int name_reg = ++parse->nMem;
- sqlVdbeAddOp4(parse->pVdbe, OP_String8, 0, name_reg, 0,
- name_copy, P4_DYNAMIC);
- bool no_err = create_def->if_not_exist;
- if (vdbe_emit_halt_with_presence_test(parse, BOX_TRIGGER_ID, 0,
- name_reg, 1,
- ER_TRIGGER_EXISTS,
- error_msg, (no_err != 0),
- OP_NoConflict) != 0)
- goto trigger_cleanup;
- }
-
/* Build the Trigger object. */
trigger = (struct sql_trigger *)sqlDbMallocZero(db,
sizeof(struct
@@ -131,9 +109,12 @@ sql_trigger_begin(struct Parse *parse)
trigger->pWhen = sqlExprDup(db, trigger_def->when, EXPRDUP_REDUCE);
trigger->pColumns = sqlIdListDup(db, trigger_def->cols);
rlist_create(&trigger->link);
- if ((trigger->pWhen != NULL && trigger->pWhen == NULL) ||
- (trigger->pColumns != NULL && trigger->pColumns == NULL))
+ if ((trigger_def->when != NULL && trigger->pWhen == NULL) ||
+ (trigger_def->cols != NULL && trigger->pColumns == NULL))
goto trigger_cleanup;
+ trigger->step_list = parse->create_trigger_def.step_list;
+ parse->create_trigger_def.step_list = NULL;
+
assert(parse->parsed_ast.trigger == NULL);
parse->parsed_ast.trigger = trigger;
parse->parsed_ast_type = AST_TYPE_TRIGGER;
@@ -143,6 +124,7 @@ sql_trigger_begin(struct Parse *parse)
sqlSrcListDelete(db, alter_def->entity_name);
sqlIdListDelete(db, trigger_def->cols);
sql_expr_delete(db, trigger_def->when, false);
+ sqlDeleteTriggerStep(db, parse->create_trigger_def.step_list);
if (parse->parsed_ast.trigger == NULL)
sql_trigger_delete(db, trigger);
else
@@ -155,95 +137,6 @@ set_tarantool_error_and_cleanup:
goto trigger_cleanup;
}
-void
-sql_trigger_finish(struct Parse *parse, struct TriggerStep *step_list,
- struct Token *token)
-{
- /* Trigger being finished. */
- struct sql_trigger *trigger = parse->parsed_ast.trigger;
- /* The database. */
- struct sql *db = parse->db;
-
- parse->parsed_ast.trigger = NULL;
- if (NEVER(parse->is_aborted) || trigger == NULL)
- goto cleanup;
- char *trigger_name = trigger->zName;
- trigger->step_list = step_list;
- while (step_list != NULL) {
- step_list = step_list->pNext;
- }
-
- /* Trigger name for error reporting. */
- struct Token trigger_name_token;
- sqlTokenInit(&trigger_name_token, trigger->zName);
-
- /*
- * Generate byte code to insert a new trigger into
- * Tarantool for non-parsing mode or export trigger.
- */
- if (!parse->parse_only) {
- /* Make an entry in the _trigger space. */
- struct Vdbe *v = sqlGetVdbe(parse);
- if (v == 0)
- goto cleanup;
-
- char *sql_str =
- sqlMPrintf(db, "CREATE TRIGGER %s", token->z);
- if (db->mallocFailed)
- goto cleanup;
-
- struct space *_trigger = space_by_id(BOX_TRIGGER_ID);
- assert(_trigger != NULL);
-
- int first_col = parse->nMem + 1;
- parse->nMem += 3;
- int record = ++parse->nMem;
- int sql_str_len = strlen(sql_str);
- int sql_len = strlen("sql");
-
- uint32_t opts_buff_sz = mp_sizeof_map(1) +
- mp_sizeof_str(sql_len) +
- mp_sizeof_str(sql_str_len);
- char *opts_buff = (char *) sqlDbMallocRaw(db, opts_buff_sz);
- if (opts_buff == NULL)
- goto cleanup;
-
- char *data = mp_encode_map(opts_buff, 1);
- data = mp_encode_str(data, "sql", sql_len);
- data = mp_encode_str(data, sql_str, sql_str_len);
- sqlDbFree(db, sql_str);
-
- trigger_name = sqlDbStrDup(db, trigger_name);
- if (trigger_name == NULL) {
- sqlDbFree(db, opts_buff);
- goto cleanup;
- }
-
- sqlVdbeAddOp4(v, OP_String8, 0, first_col, 0,
- trigger_name, P4_DYNAMIC);
- sqlVdbeAddOp2(v, OP_Integer, trigger->space_id,
- first_col + 1);
- sqlVdbeAddOp4(v, OP_Blob, opts_buff_sz, first_col + 2,
- SQL_SUBTYPE_MSGPACK, opts_buff, P4_DYNAMIC);
- sqlVdbeAddOp3(v, OP_MakeRecord, first_col, 3, record);
- sqlVdbeAddOp4(v, OP_IdxInsert, record, 0, 0,
- (char *)_trigger, P4_SPACEPTR);
-
- sqlVdbeChangeP5(v, OPFLAG_NCHANGE);
-
- sql_set_multi_write(parse, false);
- } else {
- parse->parsed_ast.trigger = trigger;
- parse->parsed_ast_type = AST_TYPE_TRIGGER;
- trigger = NULL;
- }
-
-cleanup:
- sql_trigger_delete(db, trigger);
- assert(parse->parsed_ast.trigger == NULL || parse->parse_only);
- sqlDeleteTriggerStep(db, step_list);
-}
-
struct TriggerStep *
sql_trigger_select_step(struct sql *db, struct Select *select)
{
diff --git a/test/sql/triggers.result b/test/sql/triggers.result
index 9dfe981a0..1a70ddf2b 100644
--- a/test/sql/triggers.result
+++ b/test/sql/triggers.result
@@ -34,7 +34,7 @@ immutable_part(box.space._trigger:select())
---
- - - T1T
- {'sql': 'CREATE TRIGGER t1t AFTER INSERT ON t1 FOR EACH ROW BEGIN INSERT INTO
- t2 VALUES(1); END; '}
+ t2 VALUES(1); END'}
...
space_id = box.space._space.index["name"]:get('T1').id
---
@@ -68,7 +68,7 @@ immutable_part(box.space._trigger:select())
---
- - - T1T
- {'sql': 'CREATE TRIGGER t1t AFTER INSERT ON t1 FOR EACH ROW BEGIN INSERT INTO
- t2 VALUES(1); END; '}
+ t2 VALUES(1); END'}
...
box.execute("DROP TABLE T1;")
---
@@ -90,7 +90,7 @@ immutable_part(box.space._trigger:select())
---
- - - T1T
- {'sql': 'CREATE TRIGGER t1t AFTER INSERT ON t1 FOR EACH ROW BEGIN INSERT INTO
- t2 VALUES(1); END; '}
+ t2 VALUES(1); END'}
...
space_id = box.space._space.index["name"]:get('T1').id
---
@@ -129,7 +129,7 @@ immutable_part(box.space._trigger:select())
---
- - - T1T
- {'sql': 'CREATE TRIGGER t1t AFTER INSERT ON t1 FOR EACH ROW BEGIN INSERT INTO
- t2 VALUES(1); END; '}
+ t2 VALUES(1); END'}
- - T2T
- {'sql': 'CREATE TRIGGER t2t AFTER INSERT ON t1 FOR EACH ROW BEGIN INSERT INTO
t2 VALUES(2); END;'}
@@ -167,7 +167,7 @@ immutable_part(box.space._trigger:select())
---
- - - T1T
- {'sql': 'CREATE TRIGGER t1t AFTER INSERT ON t1 FOR EACH ROW BEGIN INSERT INTO
- t2 VALUES(1); END; '}
+ t2 VALUES(1); END'}
...
box.execute("INSERT INTO t1 VALUES(3);")
---
@@ -198,13 +198,13 @@ immutable_part(box.space._trigger:select())
---
- - - T1T
- {'sql': 'CREATE TRIGGER t1t AFTER INSERT ON t1 FOR EACH ROW BEGIN INSERT INTO
- t2 VALUES(1); END; '}
+ t2 VALUES(1); END'}
- - T2T
- {'sql': 'CREATE TRIGGER t2t AFTER INSERT ON t1 FOR EACH ROW BEGIN INSERT INTO
- t2 VALUES(2); END; '}
+ t2 VALUES(2); END'}
- - T3T
- {'sql': 'CREATE TRIGGER t3t AFTER INSERT ON t1 FOR EACH ROW BEGIN INSERT INTO
- t2 VALUES(3); END; '}
+ t2 VALUES(3); END'}
...
box.execute("INSERT INTO t1 VALUES(4);")
---
diff --git a/test/sql/upgrade.result b/test/sql/upgrade.result
index f0997e17f..b5c4ad500 100644
--- a/test/sql/upgrade.result
+++ b/test/sql/upgrade.result
@@ -86,7 +86,7 @@ t1t.name
t1t.opts
---
- {'sql': 'CREATE TRIGGER t1t AFTER INSERT ON t FOR EACH ROW BEGIN INSERT INTO t_out
- VALUES(1); END;'}
+ VALUES(1); END'}
...
t2t.name
---
@@ -95,7 +95,7 @@ t2t.name
t2t.opts
---
- {'sql': 'CREATE TRIGGER t2t AFTER INSERT ON t FOR EACH ROW BEGIN INSERT INTO t_out
- VALUES(2); END;'}
+ VALUES(2); END'}
...
assert(t1t.space_id == t2t.space_id)
---
--
2.23.0
More information about the Tarantool-patches
mailing list