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 4844D2226C for ; Thu, 7 Jun 2018 06:40:57 -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 0D96aPfhuLWs for ; Thu, 7 Jun 2018 06:40:57 -0400 (EDT) Received: from smtp40.i.mail.ru (smtp40.i.mail.ru [94.100.177.100]) (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 3A07522236 for ; Thu, 7 Jun 2018 06:40:56 -0400 (EDT) Subject: [tarantool-patches] Re: [PATCH] sql: rework VIEW internals References: <1528129571.147907906@f369.i.mail.ru> From: Vladislav Shpilevoy Message-ID: Date: Thu, 7 Jun 2018 13:40:53 +0300 MIME-Version: 1.0 In-Reply-To: Content-Type: text/plain; charset="utf-8"; format="flowed" Content-Language: en-US Content-Transfer-Encoding: 8bit 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: "n.pettik" , tarantool-patches@freelists.org Hello. Thanks for the fixes! I have found another crash: box.cfg{} box.sql.execute('CREATE TABLE test (id INT PRIMARY KEY)') fiber = require('fiber') function create_view() box.sql.execute('CREATE VIEW view1 AS SELECT * FROM test') end function drop_index() box.space._index:delete{box.space.TEST.id, 0} end function drop_space() box.space._space:delete{box.space.TEST.id} end box.error.injection.set("ERRINJ_WAL_DELAY", true) f1 = fiber.create(create_view) f2 = fiber.create(drop_index) f3 = fiber.create(drop_space) box.error.injection.set("ERRINJ_WAL_DELAY", false) tarantool> Assertion failed: (space_id != BOX_ID_NIL), function update_view_references, file /Users/v.shpilevoy/Work/Repositories/tarantool/src/box/alter.cc, line 1463. Process 43720 stopped * thread #1, queue = 'com.apple.main-thread', stop reason = signal SIGABRT frame #0: 0x00007fff500f3b6e libsystem_kernel.dylib`__pthread_kill + 10 libsystem_kernel.dylib`__pthread_kill: -> 0x7fff500f3b6e <+10>: jae 0x7fff500f3b78 ; <+20> 0x7fff500f3b70 <+12>: movq %rax, %rdi 0x7fff500f3b73 <+15>: jmp 0x7fff500eab00 ; cerror_nocancel 0x7fff500f3b78 <+20>: retq Target 0: (tarantool) stopped. So we can not increment view references on CREATE VIEW commit. We must do it in on_replace_dd_space, and decrement back in on_rollback trigger. For DROP VIEW almost the same - we still must decrement on commit, but be ready that some spaces could be deleted, and just skip them. I have made a small patch. But it is raw. Please, finish it, or made your own. If you will commit the test above, then please do it in a separate test file and make it release_disabled in suite.ini. Error injections do not work in release build. For instance, sql-tap/errinj.test.lua. diff --git a/src/box/alter.cc b/src/box/alter.cc index fe82006cf..76dbd99f0 100644 --- a/src/box/alter.cc +++ b/src/box/alter.cc @@ -1448,8 +1448,9 @@ alter_space_move_indexes(struct alter_space *alter, uint32_t begin, * @param select Tables from this select to be updated. * @param update_value +1 on view creation, -1 on drop. */ -static void -update_view_references(struct Select *select, int update_value) +static int +update_view_references(struct Select *select, int update_value, + bool suppress_error) { assert(update_value == 1 || update_value == -1); sql_select_expand_from_tables(select); @@ -1460,6 +1461,11 @@ update_view_references(struct Select *select, int update_value) continue; uint32_t space_id = schema_find_id(BOX_SPACE_ID, 2, space_name, strlen(space_name)); + if (space_id != BOX_ID_NIL) { + if (! suppress_error) + return -1; + continue; + } assert(space_id != BOX_ID_NIL); struct space *space = space_by_id(space_id); assert(space->def->view_ref_count > 0 || update_value > 0); @@ -1477,7 +1483,15 @@ on_create_view_commit(struct trigger *trigger, void *event) { (void) event; struct Select *select = (struct Select *)trigger->data; - update_view_references(select, 1); + sql_select_delete(sql_get(), select); +} + +static void +on_create_view_rollback(struct trigger *trigger, void *event) +{ + (void) event; + struct Select *select = (struct Select *)trigger->data; + update_view_references(select, -1, true); sql_select_delete(sql_get(), select); } @@ -1491,7 +1505,7 @@ on_drop_view_commit(struct trigger *trigger, void *event) { (void) event; struct Select *select = (struct Select *)trigger->data; - update_view_references(select, -1); + update_view_references(select, -1, true); sql_select_delete(sql_get(), select); } @@ -1501,7 +1515,7 @@ on_drop_view_commit(struct trigger *trigger, void *event) * on_replace_dd_space trigger. */ static void -on_alter_view_rollback(struct trigger *trigger, void *event) +on_drop_view_rollback(struct trigger *trigger, void *event) { (void) event; struct Select *select = (struct Select *)trigger->data; @@ -1618,12 +1632,13 @@ on_replace_dd_space(struct trigger * /* trigger */, void *event) def->opts.sql); if (select == NULL) diag_raise(); + update_view_references(select, 1, false); struct trigger *on_commit_view = txn_alter_trigger_new(on_create_view_commit, select); txn_on_commit(txn, on_commit_view); struct trigger *on_rollback_view = - txn_alter_trigger_new(on_alter_view_rollback, + txn_alter_trigger_new(on_create_view_rollback, select); txn_on_rollback(txn, on_rollback_view); } @@ -1673,7 +1688,7 @@ on_replace_dd_space(struct trigger * /* trigger */, void *event) select); txn_on_commit(txn, on_commit_view); struct trigger *on_rollback_view = - txn_alter_trigger_new(on_alter_view_rollback, + txn_alter_trigger_new(on_drop_view_rollback, select); txn_on_rollback(txn, on_rollback_view); } > > ======================================================================= > > Subject: [PATCH] sql: rework VIEW internals > > This patch significantly reworks VIEW mechanisms. > Firstly, names resolution for VIEW now occurs during its creation. > In other words, we can't run following code, since name T1 doesn't exist > at the moment of V1 creation: > > CREATE VIEW v1 AS SELECT * FROM t1; > CREATE TABLE t1(id PRIMARY KEY); > > Now, table t1 must be created before view. As a result, no circularly > defined views are allowed. Moreover, it means that space representing > view is created with appropriate field names, types, collations etc. > > Also, introduced view reference counter for each space. It is > incremented for each space participating in select statement. > For instace: > > CREATE VIEW v1 AS SELECT * FROM (SELECT * FROM t1, t2); > > In this case, view reference counter is increased for spaces T1 and T2. > Similarly, such counter is decremented for all dependent spaces when > VIEW is dropped. To support such behavior, auxiliary > on_commit triggers are added. However, it is still not enough, since > before dropping space itself, we must drop secondary indexes, clear > _sequence space etc. Such changes can't be rollbacked (due to the lack > of transactional DDL), so before executing DROP routine, special opcode > OP_CheckViewReferences checks view reference counter for space to be > dropped. > > Finally, we don't hold struct Select in struct Table or anywhere else > anymore. Instead, 'CREATE VIEW AS SELECT ...' string is stored in > def->opts.sql. At compilation time of query on view > (such as 'SELECT * FROM v1;') this string is parsed once and loaded > into struct Select, which in turn is used as before. > > Fixed tests where view was created prior to referenced table. > > Closes #3429, #3368, #3300 > --- > src/box/alter.cc | 102 +++++++++++++++ > src/box/space_def.c | 1 + > src/box/space_def.h | 2 + > src/box/sql.h | 53 ++++++++ > src/box/sql/build.c | 283 +++++++++++------------------------------ > src/box/sql/delete.c | 8 +- > src/box/sql/expr.c | 5 +- > src/box/sql/fkey.c | 4 +- > src/box/sql/insert.c | 7 +- > src/box/sql/parse.y | 19 +-- > src/box/sql/pragma.c | 6 +- > src/box/sql/select.c | 109 +++++++++++++--- > src/box/sql/sqliteInt.h | 58 +++++++-- > src/box/sql/tokenize.c | 24 +++- > src/box/sql/trigger.c | 8 +- > src/box/sql/update.c | 3 +- > src/box/sql/vdbe.c | 24 ++++ > test/sql-tap/colname.test.lua | 4 +- > test/sql-tap/drop_all.test.lua | 4 +- > test/sql-tap/fkey2.test.lua | 1 + > test/sql-tap/trigger1.test.lua | 1 + > test/sql-tap/triggerC.test.lua | 1 + > test/sql-tap/view.test.lua | 137 +++++++++++++++----- > test/sql/view.result | 60 ++++++++- > test/sql/view.test.lua | 28 +++- > 25 files changed, 643 insertions(+), 309 deletions(-) > > diff --git a/src/box/alter.cc b/src/box/alter.cc > index b62f8adea..fe82006cf 100644 > --- a/src/box/alter.cc > +++ b/src/box/alter.cc > @@ -944,6 +944,7 @@ ModifySpace::alter_def(struct alter_space *alter) > new_dict = new_def->dict; > new_def->dict = alter->old_space->def->dict; > tuple_dictionary_ref(new_def->dict); > + new_def->view_ref_count = alter->old_space->def->view_ref_count; > > space_def_delete(alter->space_def); > alter->space_def = new_def; > @@ -1440,6 +1441,73 @@ alter_space_move_indexes(struct alter_space *alter, uint32_t begin, > } > } > > +/** > + * Walk through all spaces from 'FROM' clause of given select, > + * and update their view references counters. > + * > + * @param select Tables from this select to be updated. > + * @param update_value +1 on view creation, -1 on drop. > + */ > +static void > +update_view_references(struct Select *select, int update_value) > +{ > + assert(update_value == 1 || update_value == -1); > + sql_select_expand_from_tables(select); > + int from_tables_count = sql_select_from_tables_count(select); > + for (int i = 0; i < from_tables_count; ++i) { > + const char *space_name = sql_select_from_table_name(select, i); > + if (space_name == NULL) > + continue; > + uint32_t space_id = schema_find_id(BOX_SPACE_ID, 2, space_name, > + strlen(space_name)); > + assert(space_id != BOX_ID_NIL); > + struct space *space = space_by_id(space_id); > + assert(space->def->view_ref_count > 0 || update_value > 0); > + space->def->view_ref_count += update_value; > + } > +} > + > +/** > + * Trigger which is fired on creation of new SQL view. > + * Its purpose is to increment view reference counters of > + * dependent spaces. > + */ > +static void > +on_create_view_commit(struct trigger *trigger, void *event) > +{ > + (void) event; > + struct Select *select = (struct Select *)trigger->data; > + update_view_references(select, 1); > + sql_select_delete(sql_get(), select); > +} > + > +/** > + * Trigger which is fired on drop of SQL view. > + * Its purpose is to decrement view reference counters of > + * dependent spaces. > + */ > +static void > +on_drop_view_commit(struct trigger *trigger, void *event) > +{ > + (void) event; > + struct Select *select = (struct Select *)trigger->data; > + update_view_references(select, -1); > + sql_select_delete(sql_get(), select); > +} > + > +/** > + * This trigger is invoked on drop/create of SQL view. > + * Release memory for struct SELECT compiled in > + * on_replace_dd_space trigger. > + */ > +static void > +on_alter_view_rollback(struct trigger *trigger, void *event) > +{ > + (void) event; > + struct Select *select = (struct Select *)trigger->data; > + sql_select_delete(sql_get(), select); > +} > + > /** > * A trigger which is invoked on replace in a data dictionary > * space _space. > @@ -1545,6 +1613,20 @@ on_replace_dd_space(struct trigger * /* trigger */, void *event) > struct trigger *on_commit = > txn_alter_trigger_new(on_create_space_commit, space); > txn_on_commit(txn, on_commit); > + if (def->opts.is_view) { > + struct Select *select = sql_view_compile(sql_get(), > + def->opts.sql); > + if (select == NULL) > + diag_raise(); > + struct trigger *on_commit_view = > + txn_alter_trigger_new(on_create_view_commit, > + select); > + txn_on_commit(txn, on_commit_view); > + struct trigger *on_rollback_view = > + txn_alter_trigger_new(on_alter_view_rollback, > + select); > + txn_on_rollback(txn, on_rollback_view); > + } > struct trigger *on_rollback = > txn_alter_trigger_new(on_create_space_rollback, space); > txn_on_rollback(txn, on_rollback); > @@ -1566,6 +1648,11 @@ on_replace_dd_space(struct trigger * /* trigger */, void *event) > tnt_raise(ClientError, ER_DROP_SPACE, > space_name(old_space), > "the space has truncate record"); > + if (old_space->def->view_ref_count > 0) { > + tnt_raise(ClientError, ER_DROP_SPACE, > + space_name(old_space), > + "other views depend on this space"); > + } > /** > * The space must be deleted from the space > * cache right away to achieve linearisable > @@ -1575,6 +1662,21 @@ on_replace_dd_space(struct trigger * /* trigger */, void *event) > struct trigger *on_commit = > txn_alter_trigger_new(on_drop_space_commit, space); > txn_on_commit(txn, on_commit); > + if (old_space->def->opts.is_view) { > + struct Select *select = > + sql_view_compile(sql_get(), > + old_space->def->opts.sql); > + if (select == NULL) > + diag_raise(); > + struct trigger *on_commit_view = > + txn_alter_trigger_new(on_drop_view_commit, > + select); > + txn_on_commit(txn, on_commit_view); > + struct trigger *on_rollback_view = > + txn_alter_trigger_new(on_alter_view_rollback, > + select); > + txn_on_rollback(txn, on_rollback_view); > + } > struct trigger *on_rollback = > txn_alter_trigger_new(on_drop_space_rollback, space); > txn_on_rollback(txn, on_rollback); > diff --git a/src/box/space_def.c b/src/box/space_def.c > index a39978182..21b7d82bb 100644 > --- a/src/box/space_def.c > +++ b/src/box/space_def.c > @@ -204,6 +204,7 @@ space_def_new(uint32_t id, uint32_t uid, uint32_t exact_field_count, > memcpy(def->engine_name, engine_name, engine_len); > def->engine_name[engine_len] = 0; > > + def->view_ref_count = 0; > def->field_count = field_count; > if (field_count == 0) { > def->fields = NULL; > diff --git a/src/box/space_def.h b/src/box/space_def.h > index 024e868a9..4ff9c668e 100644 > --- a/src/box/space_def.h > +++ b/src/box/space_def.h > @@ -107,6 +107,8 @@ struct space_def { > struct field_def *fields; > /** Length of @a fields. */ > uint32_t field_count; > + /** Number of SQL views which refer to this space. */ > + uint32_t view_ref_count; > struct space_opts opts; > char name[0]; > }; > diff --git a/src/box/sql.h b/src/box/sql.h > index 23021e56b..7de7c31fa 100644 > --- a/src/box/sql.h > +++ b/src/box/sql.h > @@ -65,6 +65,7 @@ sql_get(); > struct Expr; > struct Parse; > struct Select; > +struct SrcList; > struct Table; > > /** > @@ -83,6 +84,17 @@ int > sql_expr_compile(struct sqlite3 *db, const char *expr, int expr_len, > struct Expr **result); > > +/** > + * This routine executes parser on 'CREATE VIEW ...' statement > + * and loads content of SELECT into internal structs as result. > + * > + * @param db Current SQL context. > + * @param view_stmt String containing 'CREATE VIEW' statement. > + * @retval AST of SELECT statement on success, NULL otherwise. > + */ > +struct Select * > +sql_view_compile(struct sqlite3 *db, const char *view_stmt); > + > /** > * Store duplicate of a parsed expression into @a parser. > * @param parser Parser context. > @@ -293,6 +305,47 @@ sql_parser_create(struct Parse *parser, struct sqlite3 *db); > void > sql_parser_destroy(struct Parse *parser); > > +/** > + * Release memory allocated for given SELECT and all of its > + * substructures. It accepts NULL pointers. > + * > + * @param db Database handler. > + * @param select Select to be freed. > + */ > +void > +sql_select_delete(struct sqlite3 *db, struct Select *select); > + > +/** > + * Expand all spaces names from 'FROM' clause, including > + * ones from subqueries, and add those names to the original > + * select. > + * > + * @param select Select to be expanded. > + */ > +void > +sql_select_expand_from_tables(struct Select *select); > + > +/** > + * Temporary getter in order to avoid including sqliteInt.h > + * in alter.cc. > + * > + * @param select Select to be examined. > + * @retval Count of 'FROM' tables in given select. > + */ > +int > +sql_select_from_tables_count(const struct Select *select); > + > +/** > + * Temporary getter in order to avoid including sqliteInt.h > + * in alter.cc. > + * > + * @param select Select to be examined. > + * @param i Ordinal number of 'FROM' table. > + * @retval Name of i-th 'FROM' table. > + */ > +const char * > +sql_select_from_table_name(const struct Select *select, int i); > + > #if defined(__cplusplus) > } /* extern "C" { */ > #endif > diff --git a/src/box/sql/build.c b/src/box/sql/build.c > index 28e4d7a4d..53bb53ab8 100644 > --- a/src/box/sql/build.c > +++ b/src/box/sql/build.c > @@ -405,7 +405,6 @@ deleteTable(sqlite3 * db, Table * pTable) > sqlite3HashClear(&pTable->idxHash); > sqlite3DbFree(db, pTable->aCol); > sqlite3DbFree(db, pTable->zColAff); > - sqlite3SelectDelete(db, pTable->pSelect); > assert(pTable->def != NULL); > /* Do not delete pTable->def allocated on region. */ > if (!pTable->def->opts.temporary) > @@ -1849,7 +1848,6 @@ sqlite3EndTable(Parse * pParse, /* Parse context */ > p->def->id = SQLITE_PAGENO_TO_SPACEID(p->tnum); > } > > - assert(p->def->opts.is_view == (p->pSelect != NULL)); > if (!p->def->opts.is_view) { > if ((p->tabFlags & TF_HasPrimaryKey) == 0) { > sqlite3ErrorMsg(pParse, > @@ -2009,230 +2007,99 @@ sqlite3EndTable(Parse * pParse, /* Parse context */ > } > > #ifndef SQLITE_OMIT_VIEW > -/* > - * The parser calls this routine in order to create a new VIEW > - */ > void > -sqlite3CreateView(Parse * pParse, /* The parsing context */ > - Token * pBegin, /* The CREATE token that begins the statement */ > - Token * pName, /* The token that holds the name of the view */ > - ExprList * pCNames, /* Optional list of view column names */ > - Select * pSelect, /* A SELECT statement that will become the new view */ > - int noErr /* Suppress error messages if VIEW already exists */ > - ) > +sql_create_view(struct Parse *parse_context, struct Token *begin, > + struct Token *name, struct ExprList *aliases, > + struct Select *select, bool if_exists) > { > - Table *p; > - int n; > - const char *z; > - Token sEnd; > - DbFixer sFix; > - sqlite3 *db = pParse->db; > - > - if (pParse->nVar > 0) { > - sqlite3ErrorMsg(pParse, "parameters are not allowed in views"); > + struct sqlite3 *db = parse_context->db; > + if (parse_context->nVar > 0) { > + sqlite3ErrorMsg(parse_context, > + "parameters are not allowed in views"); > goto create_view_fail; > } > - sqlite3StartTable(pParse, pName, noErr); > - p = pParse->pNewTable; > - if (p == 0 || pParse->nErr) > + sqlite3StartTable(parse_context, name, if_exists); > + struct Table *p = parse_context->pNewTable; > + if (p == NULL || parse_context->nErr != 0) > goto create_view_fail; > - sqlite3FixInit(&sFix, pParse, "view", pName); > - if (sqlite3FixSelect(&sFix, pSelect)) > + struct Table *sel_tab = sqlite3ResultSetOfSelect(parse_context, select); > + if (sel_tab == NULL) > goto create_view_fail; > - > - /* Make a copy of the entire SELECT statement that defines the view. > - * This will force all the Expr.token.z values to be dynamically > - * allocated rather than point to the input string - which means that > - * they will persist after the current sqlite3_exec() call returns. > - */ > - p->pSelect = sqlite3SelectDup(db, pSelect, EXPRDUP_REDUCE); > + if (aliases != NULL) { > + if ((int)sel_tab->def->field_count != aliases->nExpr) { > + sqlite3ErrorMsg(parse_context, "expected %d columns "\ > + "for '%s' but got %d", aliases->nExpr, > + p->def->name, > + sel_tab->def->field_count); > + goto create_view_fail; > + } > + sqlite3ColumnsFromExprList(parse_context, aliases, p); > + sqlite3SelectAddColumnTypeAndCollation(parse_context, p, > + select); > + } else { > + assert(p->aCol == NULL); > + assert(sel_tab->def->opts.temporary); > + p->def->fields = sel_tab->def->fields; > + p->def->field_count = sel_tab->def->field_count; > + p->aCol = sel_tab->aCol; > + sel_tab->aCol = NULL; > + sel_tab->def->fields = NULL; > + sel_tab->def->field_count = 0; > + } > p->def->opts.is_view = true; > - p->def->opts.checks = sql_expr_list_dup(db, pCNames, EXPRDUP_REDUCE); > - if (db->mallocFailed) > - goto create_view_fail; > - > - /* Locate the end of the CREATE VIEW statement. Make sEnd point to > - * the end. > + /* > + * Locate the end of the CREATE VIEW statement. > + * Make sEnd point to the end. > */ > - sEnd = pParse->sLastToken; > - assert(sEnd.z[0] != 0); > - if (sEnd.z[0] != ';') { > - sEnd.z += sEnd.n; > - } > - sEnd.n = 0; > - n = (int)(sEnd.z - pBegin->z); > + struct Token end = parse_context->sLastToken; > + assert(end.z[0] != 0); > + if (end.z[0] != ';') > + end.z += end.n; > + end.n = 0; > + int n = end.z - begin->z; > assert(n > 0); > - z = pBegin->z; > - while (sqlite3Isspace(z[n - 1])) { > + const char *z = begin->z; > + while (sqlite3Isspace(z[n - 1])) > n--; > + end.z = &z[n - 1]; > + end.n = 1; > + p->def->opts.sql = strndup(begin->z, n); > + if (p->def->opts.sql == NULL) { > + diag_set(OutOfMemory, n, "strndup", "opts.sql"); > + parse_context->rc = SQL_TARANTOOL_ERROR; > + parse_context->nErr++; > + goto create_view_fail; > } > - sEnd.z = &z[n - 1]; > - sEnd.n = 1; > > /* Use sqlite3EndTable() to add the view to the Tarantool. */ > - sqlite3EndTable(pParse, 0, &sEnd, 0); > + sqlite3EndTable(parse_context, 0, &end, 0); > > create_view_fail: > - sqlite3SelectDelete(db, pSelect); > - sql_expr_list_delete(db, pCNames); > + sql_expr_list_delete(db, aliases); > + sql_select_delete(db, select); > return; > } > #endif /* SQLITE_OMIT_VIEW */ > > int > -sql_view_column_names(struct Parse *parse, struct Table *table) > +sql_view_assign_cursors(struct Parse *parse, const char *view_stmt) > { > - assert(table != NULL); > - assert(space_is_view(table)); > - /* A positive nCol means the columns names for this view > - * are already known. > - */ > - if (table->def->field_count > 0) > - return 0; > - > - /* A negative nCol is a special marker meaning that we are > - * currently trying to compute the column names. If we > - * enter this routine with a negative nCol, it means two > - * or more views form a loop, like this: > - * > - * CREATE VIEW one AS SELECT * FROM two; > - * CREATE VIEW two AS SELECT * FROM one; > - * > - * Actually, the error above is now caught prior to > - * reaching this point. But the following test is still > - * important as it does come up in the following: > - * > - * CREATE TABLE main.ex1(a); > - * CREATE TEMP VIEW ex1 AS SELECT a FROM ex1; > - * SELECT * FROM temp.ex1; > - */ > - if ((int)table->def->field_count < 0) { > - sqlite3ErrorMsg(parse, "view %s is circularly defined", > - table->def->name); > - return -1; > - } > - > - /* If we get this far, it means we need to compute the > - * table names. Note that the call to > - * sqlite3ResultSetOfSelect() will expand any "*" elements > - * in the results set of the view and will assign cursors > - * to the elements of the FROM clause. But we do not want > - * these changes to be permanent. So the computation is > - * done on a copy of the SELECT statement that defines the > - * view. > - */ > - assert(table->pSelect != NULL); > - sqlite3 *db = parse->db; > - struct Select *select = sqlite3SelectDup(db, table->pSelect, 0); > + assert(view_stmt != NULL); > + struct sqlite3 *db = parse->db; > + struct Select *select = sql_view_compile(db, view_stmt); > if (select == NULL) > return -1; > - int n = parse->nTab; > sqlite3SrcListAssignCursors(parse, select->pSrc); > - table->def->field_count = -1; > - db->lookaside.bDisable++; > - struct Table *sel_tab = sqlite3ResultSetOfSelect(parse, select); > - parse->nTab = n; > - int rc = 0; > - /* Get server checks. */ > - ExprList *checks = space_checks_expr_list(table->def->id); > - if (checks != NULL) { > - /* CREATE VIEW name(arglist) AS ... > - * The names of the columns in the table are taken > - * from arglist which is stored in table->pCheck. > - * The pCheck field normally holds CHECK > - * constraints on an ordinary table, but for a > - * VIEW it holds the list of column names. > - */ > - struct space_def *old_def = table->def; > - sqlite3ColumnsFromExprList(parse, checks, table); > - if (sql_table_def_rebuild(db, table) != 0) { > - rc = -1; > - } else { > - space_def_delete(old_def); > - } > - if (db->mallocFailed == 0 && parse->nErr == 0 > - && (int)table->def->field_count == select->pEList->nExpr) { > - sqlite3SelectAddColumnTypeAndCollation(parse, table, > - select); > - } > - } else if (sel_tab != NULL) { > - /* CREATE VIEW name AS... without an argument > - * list. Construct the column names from the > - * SELECT statement that defines the view. > - */ > - assert(table->aCol == NULL); > - assert(sel_tab->def->opts.temporary); > - struct space_def *old_def = table->def; > - struct space_def *new_def = > - sql_ephemeral_space_def_new(parse, > - old_def->name); > - if (new_def == NULL) { > - rc = -1; > - } else { > - memcpy(new_def, old_def, > - sizeof(struct space_def)); > - new_def->dict = NULL; > - new_def->opts.temporary = true; > - new_def->fields = sel_tab->def->fields; > - new_def->field_count = > - sel_tab->def->field_count; > - table->def = new_def; > - if (sql_table_def_rebuild(db, table) != 0) > - rc = -1; > - } > - table->aCol = sel_tab->aCol; > - sel_tab->aCol = NULL; > - sel_tab->def = old_def; > - sel_tab->def->fields = NULL; > - sel_tab->def->field_count = 0; > - } else { > - table->def->field_count = 0; > - rc = -1; > - } > - sqlite3DeleteTable(db, sel_tab); > - sqlite3SelectDelete(db, select); > - db->lookaside.bDisable--; > - > - return rc; > + return 0; > } > > -#ifndef SQLITE_OMIT_VIEW > -/* > - * Clear the column names from every VIEW in database idx. > - */ > -static void > -sqliteViewResetAll(sqlite3 * db) > +void > +sql_store_select(struct Parse *parse_context, struct Select *select) > { > - HashElem *i; > - for (i = sqliteHashFirst(&db->pSchema->tblHash); i; > - i = sqliteHashNext(i)) { > - Table *pTab = sqliteHashData(i); > - assert(pTab->def->opts.is_view == (pTab->pSelect != NULL)); > - if (pTab->def->opts.is_view) { > - sqlite3DbFree(db, pTab->aCol); > - struct space_def *old_def = pTab->def; > - assert(old_def->opts.temporary == false); > - pTab->def = space_def_new(old_def->id, old_def->uid, > - 0, old_def->name, > - strlen(old_def->name), > - old_def->engine_name, > - strlen(old_def->engine_name), > - &old_def->opts, > - NULL, 0); > - if (pTab->def == NULL) { > - sqlite3OomFault(db); > - pTab->def = old_def; > - } else { > - space_def_delete(old_def); > - } > - pTab->aCol = NULL; > - pTab->def->field_count = 0; > - } > - } > + Select *select_copy = sqlite3SelectDup(parse_context->db, select, 0); > + parse_context->parsed_ast_type = AST_TYPE_SELECT; > + parse_context->parsed_ast.select = select_copy; > } > -#else > -#define sqliteViewResetAll(A,B) > -#endif /* SQLITE_OMIT_VIEW */ > > /** > * Remove entries from the _sql_stat1 and _sql_stat4 > @@ -2283,7 +2150,6 @@ static void > sql_code_drop_table(struct Parse *parse_context, struct space *space, > bool is_view) > { > - struct sqlite3 *db = parse_context->db; > struct Vdbe *v = sqlite3GetVdbe(parse_context); > assert(v != NULL); > /* > @@ -2313,6 +2179,7 @@ sql_code_drop_table(struct Parse *parse_context, struct space *space, > int space_id_reg = ++parse_context->nMem; > int space_id = space->def->id; > sqlite3VdbeAddOp2(v, OP_Integer, space_id, space_id_reg); > + sqlite3VdbeAddOp1(v, OP_CheckViewReferences, space_id_reg); > if (space->sequence != NULL) { > /* Delete entry from _space_sequence. */ > sqlite3VdbeAddOp3(v, OP_MakeRecord, space_id_reg, 1, > @@ -2371,14 +2238,6 @@ sql_code_drop_table(struct Parse *parse_context, struct space *space, > VdbeComment((v, "Delete entry from _space")); > /* Remove the table entry from SQLite's internal schema. */ > sqlite3VdbeAddOp4(v, OP_DropTable, 0, 0, 0, space->def->name, 0); > - > - /* > - * Replace to _space/_index will fail if active > - * transaction. So, don't pretend, that we are able to > - * anything back. To be fixed when transactions > - * DDL are enabled. > - */ > - sqliteViewResetAll(db); > } > > /** > @@ -2914,7 +2773,7 @@ sql_create_index(struct Parse *parse, struct Token *token, > assert(pTab != 0); > assert(parse->nErr == 0); > #ifndef SQLITE_OMIT_VIEW > - if (pTab->pSelect) { > + if (pTab->def->opts.is_view) { > sqlite3ErrorMsg(parse, "views may not be indexed"); > goto exit_create_index; > } > @@ -3680,7 +3539,7 @@ sqlite3SrcListDelete(sqlite3 * db, SrcList * pList) > if (pItem->fg.isTabFunc) > sql_expr_list_delete(db, pItem->u1.pFuncArg); > sqlite3DeleteTable(db, pItem->pTab); > - sqlite3SelectDelete(db, pItem->pSelect); > + sql_select_delete(db, pItem->pSelect); > sql_expr_delete(db, pItem->pOn, false); > sqlite3IdListDelete(db, pItem->pUsing); > } > @@ -3739,7 +3598,7 @@ sqlite3SrcListAppendFromTerm(Parse * pParse, /* Parsing context */ > assert(p == 0); > sql_expr_delete(db, pOn, false); > sqlite3IdListDelete(db, pUsing); > - sqlite3SelectDelete(db, pSubquery); > + sql_select_delete(db, pSubquery); > return 0; > } > > @@ -4141,7 +4000,7 @@ sqlite3WithAdd(Parse * pParse, /* Parsing context */ > > if (db->mallocFailed) { > sql_expr_list_delete(db, pArglist); > - sqlite3SelectDelete(db, pQuery); > + sql_select_delete(db, pQuery); > sqlite3DbFree(db, zName); > pNew = pWith; > } else { > @@ -4166,7 +4025,7 @@ sqlite3WithDelete(sqlite3 * db, With * pWith) > for (i = 0; i < pWith->nCte; i++) { > struct Cte *pCte = &pWith->a[i]; > sql_expr_list_delete(db, pCte->pCols); > - sqlite3SelectDelete(db, pCte->pSelect); > + sql_select_delete(db, pCte->pSelect); > sqlite3DbFree(db, pCte->zName); > } > sqlite3DbFree(db, pWith); > diff --git a/src/box/sql/delete.c b/src/box/sql/delete.c > index ddad54b3e..318511f69 100644 > --- a/src/box/sql/delete.c > +++ b/src/box/sql/delete.c > @@ -68,7 +68,7 @@ sql_materialize_view(struct Parse *parse, const char *name, struct Expr *where, > struct SelectDest dest; > sqlite3SelectDestInit(&dest, SRT_EphemTab, cursor); > sqlite3Select(parse, select, &dest); > - sqlite3SelectDelete(db, select); > + sql_select_delete(db, select); > } > > void > @@ -123,7 +123,7 @@ sql_table_delete_from(struct Parse *parse, struct SrcList *tab_list, > * initialized. > */ > if (is_view) { > - if (sql_view_column_names(parse, table) != 0) > + if (sql_view_assign_cursors(parse, table->def->opts.sql) != 0) > goto delete_from_cleanup; > > if (trigger_list == NULL) { > @@ -339,7 +339,7 @@ sql_table_delete_from(struct Parse *parse, struct SrcList *tab_list, > if (one_pass != ONEPASS_OFF) { > /* OP_Found will use an unpacked key. */ > assert(key_len == pk_len); > - assert(pk != NULL || table->pSelect != NULL); > + assert(pk != NULL || table->def->opts.is_view); > sqlite3VdbeAddOp4Int(v, OP_NotFound, tab_cursor, > addr_bypass, reg_key, key_len); > > @@ -483,7 +483,7 @@ sql_generate_row_delete(struct Parse *parse, struct Table *table, > * of the DELETE statement is to fire the INSTEAD OF > * triggers). > */ > - if (table->pSelect == NULL) { > + if (!table->def->opts.is_view) { > uint8_t p5 = 0; > sqlite3VdbeAddOp2(v, OP_Delete, cursor, > (need_update_count ? OPFLAG_NCHANGE : 0)); > diff --git a/src/box/sql/expr.c b/src/box/sql/expr.c > index 8866f6fed..a0475fbf3 100644 > --- a/src/box/sql/expr.c > +++ b/src/box/sql/expr.c > @@ -953,7 +953,7 @@ sqlite3PExprAddSelect(Parse * pParse, Expr * pExpr, Select * pSelect) > sqlite3ExprSetHeightAndFlags(pParse, pExpr); > } else { > assert(pParse->db->mallocFailed); > - sqlite3SelectDelete(pParse->db, pSelect); > + sql_select_delete(pParse->db, pSelect); > } > } > > @@ -1152,7 +1152,7 @@ sqlite3ExprDeleteNN(sqlite3 * db, Expr * p, bool extern_alloc) > if (!extern_alloc) > sql_expr_delete(db, p->pRight, extern_alloc); > if (ExprHasProperty(p, EP_xIsSelect)) { > - sqlite3SelectDelete(db, p->x.pSelect); > + sql_select_delete(db, p->x.pSelect); > } else { > sql_expr_list_delete(db, p->x.pList); > } > @@ -2191,7 +2191,6 @@ isCandidateForInOpt(Expr * pX) > return 0; /* FROM is not a subquery or view */ > pTab = pSrc->a[0].pTab; > assert(pTab != 0); > - assert(pTab->def->opts.is_view == (pTab->pSelect != NULL)); > /* FROM clause is not a view */ > assert(!pTab->def->opts.is_view); > pEList = p->pEList; > diff --git a/src/box/sql/fkey.c b/src/box/sql/fkey.c > index 70ebef89f..2644a26f9 100644 > --- a/src/box/sql/fkey.c > +++ b/src/box/sql/fkey.c > @@ -744,7 +744,7 @@ fkTriggerDelete(sqlite3 * dbMem, Trigger * p) > TriggerStep *pStep = p->step_list; > sql_expr_delete(dbMem, pStep->pWhere, false); > sql_expr_list_delete(dbMem, pStep->pExprList); > - sqlite3SelectDelete(dbMem, pStep->pSelect); > + sql_select_delete(dbMem, pStep->pSelect); > sql_expr_delete(dbMem, p->pWhen, false); > sqlite3DbFree(dbMem, p); > } > @@ -1418,7 +1418,7 @@ fkActionTrigger(Parse * pParse, /* Parse context */ > sql_expr_delete(db, pWhere, false); > sql_expr_delete(db, pWhen, false); > sql_expr_list_delete(db, pList); > - sqlite3SelectDelete(db, pSelect); > + sql_select_delete(db, pSelect); > if (db->mallocFailed == 1) { > fkTriggerDelete(db, pTrigger); > return 0; > diff --git a/src/box/sql/insert.c b/src/box/sql/insert.c > index 59c61c703..041cfbec7 100644 > --- a/src/box/sql/insert.c > +++ b/src/box/sql/insert.c > @@ -371,7 +371,7 @@ sqlite3Insert(Parse * pParse, /* Parser context */ > && pSelect->pPrior == 0) { > pList = pSelect->pEList; > pSelect->pEList = 0; > - sqlite3SelectDelete(db, pSelect); > + sql_select_delete(db, pSelect); > pSelect = 0; > } > > @@ -398,7 +398,8 @@ sqlite3Insert(Parse * pParse, /* Parser context */ > /* If pTab is really a view, make sure it has been initialized. > * ViewGetColumnNames() is a no-op if pTab is not a view. > */ > - if (is_view && sql_view_column_names(pParse, pTab) != 0) > + if (is_view && > + sql_view_assign_cursors(pParse, pTab->def->opts.sql) != 0) > goto insert_cleanup; > > /* Cannot insert into a read-only table. */ > @@ -920,7 +921,7 @@ sqlite3Insert(Parse * pParse, /* Parser context */ > insert_cleanup: > sqlite3SrcListDelete(db, pTabList); > sql_expr_list_delete(db, pList); > - sqlite3SelectDelete(db, pSelect); > + sql_select_delete(db, pSelect); > sqlite3IdListDelete(db, pColumn); > sqlite3DbFree(db, aRegIdx); > } > diff --git a/src/box/sql/parse.y b/src/box/sql/parse.y > index 2e5f349c8..fc5d2f0a6 100644 > --- a/src/box/sql/parse.y > +++ b/src/box/sql/parse.y > @@ -184,7 +184,7 @@ create_table_args ::= LP columnlist conslist_opt(X) RP(E). { > } > create_table_args ::= AS select(S). { > sqlite3EndTable(pParse,0,0,S); > - sqlite3SelectDelete(pParse->db, S); > + sql_select_delete(pParse->db, S); > } > columnlist ::= columnlist COMMA columnname carglist. > columnlist ::= columnname carglist. > @@ -373,7 +373,10 @@ ifexists(A) ::= . {A = 0;} > %ifndef SQLITE_OMIT_VIEW > cmd ::= createkw(X) VIEW ifnotexists(E) nm(Y) eidlist_opt(C) > AS select(S). { > - sqlite3CreateView(pParse, &X, &Y, C, S, E); > + if (!pParse->parse_only) > + sql_create_view(pParse, &X, &Y, C, S, E); > + else > + sql_store_select(pParse, S); > } > cmd ::= DROP VIEW ifexists(E) fullname(X). { > sql_drop_table(pParse, X, 1, E); > @@ -388,15 +391,15 @@ cmd ::= select(X). { > sqlite3Select(pParse, X, &dest); > else > sql_expr_extract_select(pParse, X); > - sqlite3SelectDelete(pParse->db, X); > + sql_select_delete(pParse->db, X); > } > > %type select {Select*} > -%destructor select {sqlite3SelectDelete(pParse->db, $$);} > +%destructor select {sql_select_delete(pParse->db, $$);} > %type selectnowith {Select*} > -%destructor selectnowith {sqlite3SelectDelete(pParse->db, $$);} > +%destructor selectnowith {sql_select_delete(pParse->db, $$);} > %type oneselect {Select*} > -%destructor oneselect {sqlite3SelectDelete(pParse->db, $$);} > +%destructor oneselect {sql_select_delete(pParse->db, $$);} > > %include { > /* > @@ -453,7 +456,7 @@ selectnowith(A) ::= selectnowith(A) multiselect_op(Y) oneselect(Z). { > pRhs->selFlags &= ~SF_MultiValue; > if( Y!=TK_ALL ) pParse->hasCompound = 1; > }else{ > - sqlite3SelectDelete(pParse->db, pLhs); > + sql_select_delete(pParse->db, pLhs); > } > A = pRhs; > } > @@ -496,7 +499,7 @@ oneselect(A) ::= SELECT(S) distinct(D) selcollist(W) from(X) where_opt(Y) > oneselect(A) ::= values(A). > > %type values {Select*} > -%destructor values {sqlite3SelectDelete(pParse->db, $$);} > +%destructor values {sql_select_delete(pParse->db, $$);} > values(A) ::= VALUES LP nexprlist(X) RP. { > A = sqlite3SelectNew(pParse,X,0,0,0,0,0,SF_Values,0,0); > } > diff --git a/src/box/sql/pragma.c b/src/box/sql/pragma.c > index 9dab5a7fd..d6ec0f222 100644 > --- a/src/box/sql/pragma.c > +++ b/src/box/sql/pragma.c > @@ -357,8 +357,10 @@ sqlite3Pragma(Parse * pParse, Token * pId, /* First part of [schema.]id field */ > Column *pCol; > Index *pPk = sqlite3PrimaryKeyIndex(pTab); > pParse->nMem = 6; > - if (space_is_view(pTab)) > - sql_view_column_names(pParse, pTab); > + if (space_is_view(pTab)) { > + const char *sql = pTab->def->opts.sql; > + sql_view_assign_cursors(pParse, sql); > + } > for (i = 0, pCol = pTab->aCol; > i < (int)pTab->def->field_count; > i++, pCol++) { > diff --git a/src/box/sql/select.c b/src/box/sql/select.c > index 2aa35a114..b2ddf442f 100644 > --- a/src/box/sql/select.c > +++ b/src/box/sql/select.c > @@ -207,16 +207,28 @@ sqlite3SelectSetName(Select * p, const char *zName) > } > #endif > > -/* > - * Delete the given Select structure and all of its substructures. > - */ > void > -sqlite3SelectDelete(sqlite3 * db, Select * p) > +sql_select_delete(sqlite3 *db, Select *p) > { > if (p) > clearSelect(db, p, 1); > } > > +int > +sql_select_from_tables_count(const struct Select *select) > +{ > + assert(select != NULL && select->pSrc != NULL); > + return select->pSrc->nSrc; > +} > + > +const char * > +sql_select_from_table_name(const struct Select *select, int i) > +{ > + assert(select != NULL && select->pSrc != NULL); > + assert(i >= 0 && i < select->pSrc->nSrc); > + return select->pSrc->a[i].zName; > +} > + > /* > * Return a pointer to the right-most SELECT statement in a compound. > */ > @@ -228,6 +240,70 @@ findRightmost(Select * p) > return p; > } > > + > +/** > + * Work the same as sqlite3SrcListAppend(), but before adding to > + * list provide check on name duplicates: only values with unique > + * names are appended. > + * > + * @param db Database handler. > + * @param list List of entries. > + * @param new_name Name of entity to be added. > + * @retval @list with new element on success, old one otherwise. > + */ > +static struct SrcList * > +src_list_append_unique(struct sqlite3 *db, struct SrcList *list, > + const char *new_name) > +{ > + assert(list != NULL); > + assert(new_name != NULL); > + > + for (int i = 0; i < list->nSrc; ++i) { > + const char *name = list->a[i].zName; > + if (name != NULL && strcmp(new_name, name) == 0) > + return list; > + } > + struct Token token = { new_name, strlen(new_name), 0 }; > + return sqlite3SrcListAppend(db, list, &token); > +} > + > +/** > + * This function is an inner call of recursive traverse through > + * select AST starting from interface function > + * sql_select_expand_from_tables(). > + * > + * @param top_select The root of AST. > + * @param sub_select sub-select of current level recursion. > + */ > +static void > +expand_names_sub_select(struct Select *top_select, struct Select *sub_select) > +{ > + assert(top_select != NULL); > + assert(sub_select != NULL); > + struct SrcList_item *sub_src = sub_select->pSrc->a; > + for (int i = 0; i < sub_select->pSrc->nSrc; ++i, ++sub_src) { > + if (sub_src->zName == NULL) { > + expand_names_sub_select(top_select, sub_src->pSelect); > + } else { > + top_select->pSrc = > + src_list_append_unique(sql_get(), > + top_select->pSrc, > + sub_src->zName); > + } > + } > +} > + > +void > +sql_select_expand_from_tables(struct Select *select) > +{ > + assert(select != NULL); > + struct SrcList_item *src = select->pSrc->a; > + for (int i = 0; i < select->pSrc->nSrc; ++i, ++src) { > + if (select->pSrc->a[i].zName == NULL) > + expand_names_sub_select(select, src->pSelect); > + } > +} > + > /* > * Given 1 to 3 identifiers preceding the JOIN keyword, determine the > * type of join. Return an integer constant that expresses that type > @@ -2830,7 +2906,7 @@ multiSelect(Parse * pParse, /* Parsing context */ > multi_select_end: > pDest->iSdst = dest.iSdst; > pDest->nSdst = dest.nSdst; > - sqlite3SelectDelete(db, pDelete); > + sql_select_delete(db, pDelete); > return rc; > } > #endif /* SQLITE_OMIT_COMPOUND_SELECT */ > @@ -3432,7 +3508,7 @@ multiSelectOrderBy(Parse * pParse, /* Parsing context */ > * by the calling function > */ > if (p->pPrior) { > - sqlite3SelectDelete(db, p->pPrior); > + sql_select_delete(db, p->pPrior); > } > p->pPrior = pPrior; > pPrior->pNext = p; > @@ -4106,7 +4182,7 @@ flattenSubquery(Parse * pParse, /* Parsing context */ > /* Finially, delete what is left of the subquery and return > * success. > */ > - sqlite3SelectDelete(db, pSub1); > + sql_select_delete(db, pSub1); > > #ifdef SELECTTRACE_ENABLED > if (sqlite3SelectTrace & 0x100) { > @@ -4738,13 +4814,15 @@ selectExpander(Walker * pWalker, Select * p) > } > #if !defined(SQLITE_OMIT_VIEW) > if (space_is_view(pTab)) { > - if (sql_view_column_names(pParse, pTab) != 0) > + struct Select *select = > + sql_view_compile(db, > + pTab->def->opts.sql); > + if (select == NULL) > return WRC_Abort; > + sqlite3SrcListAssignCursors(pParse, > + select->pSrc); > assert(pFrom->pSelect == 0); > - assert(pTab->def->opts.is_view == > - (pTab->pSelect != NULL)); > - pFrom->pSelect = > - sqlite3SelectDup(db, pTab->pSelect, 0); > + pFrom->pSelect = select; > sqlite3SelectSetName(pFrom->pSelect, > pTab->def->name); > int columns = pTab->def->field_count; > @@ -6258,7 +6336,8 @@ sql_expr_extract_select(struct Parse *parser, struct Select *select) > { > struct ExprList *expr_list = select->pEList; > assert(expr_list->nExpr == 1); > - parser->parsed_expr = sqlite3ExprDup(parser->db, > - expr_list->a->pExpr, > - EXPRDUP_REDUCE); > + parser->parsed_ast_type = AST_TYPE_EXPR; > + parser->parsed_ast.expr = sqlite3ExprDup(parser->db, > + expr_list->a->pExpr, > + EXPRDUP_REDUCE); > } > diff --git a/src/box/sql/sqliteInt.h b/src/box/sql/sqliteInt.h > index 01351a183..0f5fb0ad3 100644 > --- a/src/box/sql/sqliteInt.h > +++ b/src/box/sql/sqliteInt.h > @@ -1913,7 +1913,6 @@ struct Column { > struct Table { > Column *aCol; /* Information about each column */ > Index *pIndex; /* List of SQL indexes on this table. */ > - Select *pSelect; /* NULL for tables. Points to definition if a view. */ > FKey *pFKey; /* Linked list of all foreign keys in this table */ > char *zColAff; /* String defining the affinity of each column */ > /* ... also used as column name list in a VIEW */ > @@ -2688,9 +2687,7 @@ struct Select { > LogEst nSelectRow; /* Estimated number of result rows */ > u32 selFlags; /* Various SF_* values */ > int iLimit, iOffset; /* Memory registers holding LIMIT & OFFSET counters */ > -#ifdef SELECTTRACE_ENABLED > char zSelName[12]; /* Symbolic name of this SELECT use for debugging */ > -#endif > int addrOpenEphm[2]; /* OP_OpenEphem opcodes related to this select */ > SrcList *pSrc; /* The FROM clause */ > Expr *pWhere; /* The WHERE clause */ > @@ -2860,6 +2857,12 @@ struct TriggerPrg { > u32 aColmask[2]; /* Masks of old.*, new.* columns accessed */ > }; > > +enum ast_type { > + AST_TYPE_SELECT = 0, > + AST_TYPE_EXPR, > + ast_type_MAX > +}; > + > /* > * An SQL parser context. A copy of this structure is passed through > * the parser and down into all the parser action routine in order to > @@ -2960,8 +2963,16 @@ struct Parse { > bool initiateTTrans; /* Initiate Tarantool transaction */ > /** If set - do not emit byte code at all, just parse. */ > bool parse_only; > - /** If parse_only is set to true, store parsed expression. */ > - struct Expr *parsed_expr; > + /** Type of parsed_ast member. */ > + enum ast_type parsed_ast_type; > + /** > + * Members of this union are valid only > + * if parse_only is set to true. > + */ > + union { > + struct Expr *expr; > + struct Select *select; > + } parsed_ast; > }; > > /* > @@ -3570,20 +3581,42 @@ int sqlite3ParseUri(const char *, const char *, unsigned int *, > int sqlite3FaultSim(int); > #endif > > -void sqlite3CreateView(Parse *, Token *, Token *, ExprList *, Select *, int); > +/** > + * The parser calls this routine in order to create a new VIEW. > + * > + * @param parse_context Current parsing context. > + * @param begin The CREATE token that begins the statement. > + * @param name The token that holds the name of the view. > + * @param aliases Optional list of view column names. > + * @param select A SELECT statement that will become the new view. > + * @param if_exists Suppress error messages if VIEW already exists. > + */ > +void > +sql_create_view(struct Parse *parse_context, struct Token *begin, > + struct Token *name, struct ExprList *aliases, > + struct Select *select, bool if_exists); > > /** > - * The Table structure pTable is really a VIEW. Fill in the names > - * of the columns of the view in the table structure. Return the > - * number of errors. If an error is seen leave an error message > - * in parse->zErrMsg. > + * Compile view, i.e. create struct Select from > + * 'CREATE VIEW...' string, and assign cursors to each table from > + * 'FROM' clause. > * > * @param parse Parsing context. > - * @param table Tables to process. > + * @param view_stmt String containing 'CREATE VIEW' statement. > * @retval 0 if success, -1 in case of error. > */ > int > -sql_view_column_names(struct Parse *parse, struct Table *table); > +sql_view_assign_cursors(struct Parse *parse, const char *view_stmt); > + > +/** > + * Store duplicate of SELECT into parsing context. > + * This routine is called during parsing. > + * > + * @param parse_context Current parsing context. > + * @param select Select to be stored. > + */ > +void > +sql_store_select(struct Parse *parse_context, struct Select *select); > > void > sql_drop_table(struct Parse *, struct SrcList *, bool, bool); > @@ -3656,7 +3689,6 @@ sql_drop_index(struct Parse *, struct SrcList *, struct Token *, bool); > int sqlite3Select(Parse *, Select *, SelectDest *); > Select *sqlite3SelectNew(Parse *, ExprList *, SrcList *, Expr *, ExprList *, > Expr *, ExprList *, u32, Expr *, Expr *); > -void sqlite3SelectDelete(sqlite3 *, Select *); > > /** > * While a SrcList can in general represent multiple tables and > diff --git a/src/box/sql/tokenize.c b/src/box/sql/tokenize.c > index 42c70a255..b08034cec 100644 > --- a/src/box/sql/tokenize.c > +++ b/src/box/sql/tokenize.c > @@ -558,11 +558,31 @@ sql_expr_compile(sqlite3 *db, const char *expr, int expr_len, > sprintf(stmt, "%s%.*s", outer, expr_len, expr); > > char *unused; > - if (sqlite3RunParser(&parser, stmt, &unused) != SQLITE_OK) { > + if (sqlite3RunParser(&parser, stmt, &unused) != SQLITE_OK || > + parser.parsed_ast_type != AST_TYPE_EXPR) { > diag_set(ClientError, ER_SQL_EXECUTE, expr); > return -1; > } > - *result = parser.parsed_expr; > + *result = parser.parsed_ast.expr; > sql_parser_destroy(&parser); > return 0; > } > + > +struct Select * > +sql_view_compile(struct sqlite3 *db, const char *view_stmt) > +{ > + struct Parse parser; > + sql_parser_create(&parser, db); > + parser.parse_only = true; > + char *unused; > + if (sqlite3RunParser(&parser, view_stmt, &unused) != SQLITE_OK || > + parser.parsed_ast_type != AST_TYPE_SELECT) { > + diag_set(ClientError, ER_SQL_EXECUTE, > + "can’t compile SELECT AST from CREATE VIEW statement"); > + sql_parser_destroy(&parser); > + return NULL; > + } > + struct Select *result = parser.parsed_ast.select; > + sql_parser_destroy(&parser); > + return result; > +} > diff --git a/src/box/sql/trigger.c b/src/box/sql/trigger.c > index ea3521133..fea9c9d35 100644 > --- a/src/box/sql/trigger.c > +++ b/src/box/sql/trigger.c > @@ -54,7 +54,7 @@ sqlite3DeleteTriggerStep(sqlite3 * db, TriggerStep * pTriggerStep) > > sql_expr_delete(db, pTmp->pWhere, false); > sql_expr_list_delete(db, pTmp->pExprList); > - sqlite3SelectDelete(db, pTmp->pSelect); > + sql_select_delete(db, pTmp->pSelect); > sqlite3IdListDelete(db, pTmp->pIdList); > > sqlite3DbFree(db, pTmp); > @@ -346,7 +346,7 @@ sqlite3TriggerSelectStep(sqlite3 * db, Select * pSelect) > TriggerStep *pTriggerStep = > sqlite3DbMallocZero(db, sizeof(TriggerStep)); > if (pTriggerStep == 0) { > - sqlite3SelectDelete(db, pSelect); > + sql_select_delete(db, pSelect); > return 0; > } > pTriggerStep->op = TK_SELECT; > @@ -412,7 +412,7 @@ sqlite3TriggerInsertStep(sqlite3 * db, /* The database connection */ > } else { > sqlite3IdListDelete(db, pColumn); > } > - sqlite3SelectDelete(db, pSelect); > + sql_select_delete(db, pSelect); > > return pTriggerStep; > } > @@ -758,7 +758,7 @@ codeTriggerProgram(Parse * pParse, /* The parser context */ > sqlite3SelectDup(db, pStep->pSelect, 0); > sqlite3SelectDestInit(&sDest, SRT_Discard, 0); > sqlite3Select(pParse, pSelect, &sDest); > - sqlite3SelectDelete(db, pSelect); > + sql_select_delete(db, pSelect); > break; > } > } > diff --git a/src/box/sql/update.c b/src/box/sql/update.c > index 590aad28b..8890b077b 100644 > --- a/src/box/sql/update.c > +++ b/src/box/sql/update.c > @@ -140,7 +140,8 @@ sqlite3Update(Parse * pParse, /* The parser context */ > is_view = space_is_view(pTab); > assert(pTrigger || tmask == 0); > > - if (is_view && sql_view_column_names(pParse, pTab) != 0) { > + if (is_view && > + sql_view_assign_cursors(pParse,pTab->def->opts.sql) != 0) { > goto update_cleanup; > } > if (is_view && tmask == 0) { > diff --git a/src/box/sql/vdbe.c b/src/box/sql/vdbe.c > index 3fe5875ca..5b5cf835d 100644 > --- a/src/box/sql/vdbe.c > +++ b/src/box/sql/vdbe.c > @@ -2983,6 +2983,30 @@ case OP_FkCheckCommit: { > break; > } > > +/* Opcode: CheckViewReferences P1 * * * * > + * Synopsis: r[P1] = space id > + * > + * Check that space to be dropped doesn't have any view > + * references. This opcode is needed since Tarantool lacks > + * DDL transaction. On the other hand, to drop space, we must > + * firstly drop secondary indexes from _index system space, > + * clear _truncate table etc. > + */ > +case OP_CheckViewReferences: { > + assert(pOp->p1 > 0); > + pIn1 = &aMem[pOp->p1]; > + uint32_t space_id = pIn1->u.i; > + struct space *space = space_by_id(space_id); > + assert(space != NULL); > + if (space->def->view_ref_count > 0) { > + sqlite3VdbeError(p,"Can't drop table %s: other views depend " > + "on this space", space->def->name); > + rc = SQL_TARANTOOL_ERROR; > + goto abort_due_to_error; > + } > + break; > +} > + > /* Opcode: TransactionBegin * * * * * > * > * Start Tarantool's transaction. > diff --git a/test/sql-tap/colname.test.lua b/test/sql-tap/colname.test.lua > index c96623e4e..c53a1e885 100755 > --- a/test/sql-tap/colname.test.lua > +++ b/test/sql-tap/colname.test.lua > @@ -623,14 +623,14 @@ end > test:do_test( > "colname-11.0.1", > function () > - test:drop_all_tables() > + test:drop_all_views() > end, > nil) > > test:do_test( > "colname-11.0.2", > function () > - test:drop_all_views() > + test:drop_all_tables() > end, > nil) > > diff --git a/test/sql-tap/drop_all.test.lua b/test/sql-tap/drop_all.test.lua > index b9abe7594..d4f6c6073 100755 > --- a/test/sql-tap/drop_all.test.lua > +++ b/test/sql-tap/drop_all.test.lua > @@ -22,7 +22,7 @@ test:do_test( > test:do_test( > prefix.."-1.1", > function() > - return #test:drop_all_tables() > + return #test:drop_all_views() > end, > N > ) > @@ -30,7 +30,7 @@ test:do_test( > test:do_test( > prefix.."-1.1", > function() > - return #test:drop_all_views() > + return #test:drop_all_tables() > end, > N > ) > diff --git a/test/sql-tap/fkey2.test.lua b/test/sql-tap/fkey2.test.lua > index f90c218d4..1f3a81d92 100755 > --- a/test/sql-tap/fkey2.test.lua > +++ b/test/sql-tap/fkey2.test.lua > @@ -743,6 +743,7 @@ test:do_catchsql_test( > test:do_catchsql_test( > "fkey2-7.3", > [[ > + DROP VIEW v; > DROP TABLE IF EXISTS c; > CREATE TABLE p(a COLLATE binary, b PRIMARY KEY); > CREATE UNIQUE INDEX idx ON p(a COLLATE "unicode_ci"); > diff --git a/test/sql-tap/trigger1.test.lua b/test/sql-tap/trigger1.test.lua > index 40daba4d8..dab3281a3 100755 > --- a/test/sql-tap/trigger1.test.lua > +++ b/test/sql-tap/trigger1.test.lua > @@ -460,6 +460,7 @@ test:do_catchsql_test( > -- } > test:execsql [[ > CREATE TABLE t2(x int PRIMARY KEY,y); > + DROP VIEW v1; > DROP TABLE t1; > INSERT INTO t2 VALUES(3, 4); > INSERT INTO t2 VALUES(7, 8); > diff --git a/test/sql-tap/triggerC.test.lua b/test/sql-tap/triggerC.test.lua > index 347007832..e58072e2f 100755 > --- a/test/sql-tap/triggerC.test.lua > +++ b/test/sql-tap/triggerC.test.lua > @@ -1097,6 +1097,7 @@ test:do_catchsql_test( > -- > SQL = [[ > DROP TABLE IF EXISTS t1; > + DROP VIEW v2; > DROP TABLE IF EXISTS t2; > DROP TABLE IF EXISTS t4; > DROP TABLE IF EXISTS t5; > diff --git a/test/sql-tap/view.test.lua b/test/sql-tap/view.test.lua > index 8ba2044f0..c24e3fb29 100755 > --- a/test/sql-tap/view.test.lua > +++ b/test/sql-tap/view.test.lua > @@ -1,6 +1,6 @@ > #!/usr/bin/env tarantool > test = require("sqltester") > -test:plan(65) > +test:plan(74) > > --!./tcltestrunner.lua > -- 2002 February 26 > @@ -125,13 +125,23 @@ test:do_catchsql_test( > "view-1.6", > [[ > DROP TABLE t1; > - SELECT * FROM v1 ORDER BY a; > ]], { > -- > - 1, "no such table: T1" > + 1, "Can't drop table T1: other views depend on this space" > -- > }) > > +test:do_catchsql_test( > + "view-1.7", > + [[ > + DROP VIEW v1; > + DROP TABLE t1; > + ]], { > + -- > + 0 > + -- > + }) > + > -- ORIGINAL_TEST > -- do_test view-1.7 { > -- execsql { > @@ -143,12 +153,13 @@ test:do_catchsql_test( > -- } > -- } {2 3 5 6 8 9} > test:do_execsql_test( > - "view-1.7", > + "view-1.8", > [[ > CREATE TABLE t1(x primary key,a,b,c); > INSERT INTO t1 VALUES(1,2,3,4); > INSERT INTO t1 VALUES(4,5,6,7); > INSERT INTO t1 VALUES(7,8,9,10); > + CREATE VIEW v1 AS SELECT a,b FROM t1; > SELECT * FROM v1 ORDER BY a; > ]], { > -- > @@ -156,24 +167,6 @@ test:do_execsql_test( > -- > }) > > --- MUST_WORK_TEST reopen db > -if (0 > 0) > - then > - test:do_test( > - "view-1.8", > - function() > - db("close") > - sqlite3("db", "test.db") > - return test:execsql [[ > - SELECT * FROM v1 ORDER BY a; > - ]] > - end, { > - -- > - 2, 3, 5, 6, 8, 9 > - -- > - }) > - > -end > test:do_test( > "view-2.1", > function() > @@ -1065,10 +1058,9 @@ end > test:do_execsql_test( > "view-20.1", > [[ > - DROP TABLE IF EXISTS t1; > - DROP VIEW IF EXISTS v1; > - CREATE TABLE t1(c1 primary key); > - CREATE VIEW v1 AS SELECT c1 FROM (SELECT t1.c1 FROM t1); > + DROP VIEW v10; > + CREATE TABLE t10(c1 primary key); > + CREATE VIEW v10 AS SELECT c1 FROM (SELECT t10.c1 FROM t10); > ]], { > -- > > @@ -1078,10 +1070,10 @@ test:do_execsql_test( > test:do_execsql_test( > "view-20.1", > [[ > - DROP TABLE IF EXISTS t1; > - DROP VIEW IF EXISTS v1; > - CREATE TABLE t1(c1 primary key); > - CREATE VIEW v1 AS SELECT c1 FROM (SELECT t1.c1 FROM t1); > + DROP VIEW IF EXISTS v10; > + DROP TABLE IF EXISTS t10; > + CREATE TABLE t10(c1 primary key); > + CREATE VIEW v10 AS SELECT c1 FROM (SELECT t10.c1 FROM t10); > ]], { > -- > > @@ -1171,5 +1163,90 @@ if (0 > 0) > > end > > +-- Make sure that VIEW with several internal selects works. > +test:do_catchsql_test( > + "view-23.1", > + [[ > + CREATE TABLE t11(a INT PRIMARY KEY); > + CREATE TABLE t12(b INT PRIMARY KEY); > + CREATE TABLE t13(c INT PRIMARY KEY); > + CREATE VIEW v11 AS SELECT * FROM > + (SELECT a FROM (SELECT a, b FROM t11, t12)), > + (SELECT * FROM (SELECT a, c FROM t11, t13)); > + ]], { > + -- > + 0 > + -- > + }) > + > +test:do_catchsql_test( > + "view-23.2", > + [[ > + DROP TABLE t11; > + ]], { > + -- > + 1, "Can't drop table T11: other views depend on this space" > + -- > + }) > + > +test:do_catchsql_test( > + "view-23.3", > + [[ > + DROP TABLE t12; > + ]], { > + -- > + 1, "Can't drop table T12: other views depend on this space" > + -- > + }) > + > +test:do_catchsql_test( > + "view-23.4", > + [[ > + DROP TABLE t13; > + ]], { > + -- > + 1, "Can't drop table T13: other views depend on this space" > + -- > + }) > + > +test:do_catchsql_test( > + "view-23.5", > + [[ > + DROP VIEW v11; > + ]], { > + -- > + 0 > + -- > + }) > + > +test:do_catchsql_test( > + "view-23.6", > + [[ > + DROP TABLE t11; > + ]], { > + -- > + 0 > + -- > + }) > + > +test:do_catchsql_test( > + "view-23.7", > + [[ > + DROP TABLE t12; > + ]], { > + -- > + 0 > + -- > + }) > + > +test:do_catchsql_test( > + "view-23.8", > + [[ > + DROP TABLE t13; > + ]], { > + -- > + 0 > + -- > + }) > > test:finish_test() > diff --git a/test/sql/view.result b/test/sql/view.result > index 04cb3fae8..2b90e058a 100644 > --- a/test/sql/view.result > +++ b/test/sql/view.result > @@ -73,7 +73,7 @@ raw_sp = box.space._space:get(sp.id):totable(); > sp:drop(); > --- > ... > -raw_sp[6].sql = 'fake'; > +raw_sp[6].sql = 'CREATE VIEW v as SELECT * FROM t1;'; > --- > ... > raw_sp[6].view = true; > @@ -86,13 +86,67 @@ box.space._space:select(sp['id'])[1]['name'] > --- > - test > ... > --- Cleanup > +-- Can't create view with incorrect SELECT statement. > box.space.test:drop(); > --- > ... > -box.sql.execute("DROP TABLE t1;"); > +-- This case must fail since parser converts it to expr AST. > +raw_sp[6].sql = 'SELECT 1;'; > +--- > +... > +sp = box.space._space:replace(raw_sp); > +--- > +- error: 'Failed to execute SQL statement: can’t compile SELECT AST from CREATE VIEW > + statement' > +... > +-- Can't drop space via Lua if at least one view refers to it. > +box.sql.execute('CREATE TABLE t2(id INT PRIMARY KEY);'); > +--- > +... > +box.sql.execute('CREATE VIEW v2 AS SELECT * FROM t2;'); > --- > ... > +box.space.T2:drop(); > +--- > +- error: 'Can''t drop space ''T2'': other views depend on this space' > +... > +box.sql.execute('DROP VIEW v2;'); > +--- > +... > +box.sql.execute('DROP TABLE t2;'); > +--- > +... > +-- Check that alter transfers reference counter. > +box.sql.execute("CREATE TABLE t2(id INTEGER PRIMARY KEY);"); > +--- > +... > +box.sql.execute("CREATE VIEW v2 AS SELECT * FROM t2;"); > +--- > +... > +box.sql.execute("DROP TABLE t2;"); > +--- > +- error: 'Can''t drop table T2: other views depend on this space' > +... > +sp = box.space._space:get{box.space.T2.id}; > +--- > +... > +sp = box.space._space:replace(sp); > +--- > +... > +box.sql.execute("DROP TABLE t2;"); > +--- > +- error: 'Can''t drop table T2: other views depend on this space' > +... > +box.sql.execute("DROP VIEW v2;"); > +--- > +... > +box.sql.execute("DROP TABLE t2;"); > +--- > +... > +-- Cleanup > box.sql.execute("DROP VIEW v1;"); > --- > ... > +box.sql.execute("DROP TABLE t1;"); > +--- > +... > diff --git a/test/sql/view.test.lua b/test/sql/view.test.lua > index ab2843cb6..27f449f58 100644 > --- a/test/sql/view.test.lua > +++ b/test/sql/view.test.lua > @@ -35,12 +35,34 @@ box.schema.create_space('view', {view = true}) > sp = box.schema.create_space('test'); > raw_sp = box.space._space:get(sp.id):totable(); > sp:drop(); > -raw_sp[6].sql = 'fake'; > +raw_sp[6].sql = 'CREATE VIEW v as SELECT * FROM t1;'; > raw_sp[6].view = true; > sp = box.space._space:replace(raw_sp); > box.space._space:select(sp['id'])[1]['name'] > > --- Cleanup > +-- Can't create view with incorrect SELECT statement. > box.space.test:drop(); > -box.sql.execute("DROP TABLE t1;"); > +-- This case must fail since parser converts it to expr AST. > +raw_sp[6].sql = 'SELECT 1;'; > +sp = box.space._space:replace(raw_sp); > + > +-- Can't drop space via Lua if at least one view refers to it. > +box.sql.execute('CREATE TABLE t2(id INT PRIMARY KEY);'); > +box.sql.execute('CREATE VIEW v2 AS SELECT * FROM t2;'); > +box.space.T2:drop(); > +box.sql.execute('DROP VIEW v2;'); > +box.sql.execute('DROP TABLE t2;'); > + > +-- Check that alter transfers reference counter. > +box.sql.execute("CREATE TABLE t2(id INTEGER PRIMARY KEY);"); > +box.sql.execute("CREATE VIEW v2 AS SELECT * FROM t2;"); > +box.sql.execute("DROP TABLE t2;"); > +sp = box.space._space:get{box.space.T2.id}; > +sp = box.space._space:replace(sp); > +box.sql.execute("DROP TABLE t2;"); > +box.sql.execute("DROP VIEW v2;"); > +box.sql.execute("DROP TABLE t2;"); > + > +-- Cleanup > box.sql.execute("DROP VIEW v1;"); > +box.sql.execute("DROP TABLE t1;"); >