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 617AA25647 for ; Fri, 18 May 2018 06:33:16 -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 WnnY4tdEa8Vu for ; Fri, 18 May 2018 06:33:16 -0400 (EDT) Received: from smtp51.i.mail.ru (smtp51.i.mail.ru [94.100.177.111]) (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 88A2125620 for ; Fri, 18 May 2018 06:33:14 -0400 (EDT) Subject: [tarantool-patches] Re: [PATCH 2/2] sql: refactor delete routines References: <36b38ab497e52e9552d07bc98e11ec52697ce1e7.1526483543.git.kyukhin@tarantool.org> <20180516162905.krtf6mksrndo7equ@tarantool.org> <20180517154845.ipvrre3ra27oaqb7@tarantool.org> <9b596c0e-177e-b74d-b1a1-7e8443c8d8fc@tarantool.org> <20180518065610.gh4icdq6zlikmidx@tarantool.org> From: Vladislav Shpilevoy Message-ID: <07323a53-3c95-3b1f-1973-1a15bdde2c0f@tarantool.org> Date: Fri, 18 May 2018 13:33:11 +0300 MIME-Version: 1.0 In-Reply-To: <20180518065610.gh4icdq6zlikmidx@tarantool.org> 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: Kirill Yukhin Cc: tarantool-patches@freelists.org Hello. Thanks for fixes! I pushed commit with more minor fixes on the branch. You can squash it, if you want. The patch is LGTM, even if you decide to do not squash. On 18/05/2018 09:56, Kirill Yukhin wrote: > Hi Vlad, > Thanks a lot for your inputs. > My answers inlined, updated patch in the bottom. > > On 17 мая 19:47, Vladislav Shpilevoy wrote: >> Thanks for review fixes! Almost done! See 13 comments below. >> >>>> On 16/05/2018 19:29, Kirill Yukhin wrote: >>>>> Hi Vlad, >>>>> On 16 мая 18:24, Kirill Yukhin wrote: >>>>> --- a/src/box/sql/delete.c >>>>> +++ b/src/box/sql/delete.c >>>>> -Table * >>>>> -sqlite3SrcListLookup(Parse * pParse, SrcList * pSrc) >>>>> +struct Table * >>>>> +sql_list_lookup_table(struct Parse *parse, SrcList *src_list) >>>>> { >>>> >>>>> + struct SrcList_item *item = src_list->a; >>>>> + struct Table *table; >>>>> + assert(item != NULL && src_list->nSrc == 1); >>>>> + table = sqlite3LocateTable(parse, 0, item->zName); >>>>> + sqlite3DeleteTable(parse->db, item->pTab); >>>>> + item->pTab = table; >>>>> + if (table != NULL) >>>>> + table->nTabRef++; >>>>> + if (sqlite3IndexedByLookup(parse, item)) >>>>> + table = NULL; >>>> >>>> 1. What about --nTabRef; ? sqlite3IndexedByLookup does not unref >>>> table. Looks like original SQLite bug. >>> I'll submit a question to SQLite author. >> >> 1. Obviously, it is a bug. The code is very simple, and it is easy to see, >> that table is never unreferenced on the error. Lets fix this in Tarantool. >> Will Hipp fix this or not in vanila SQLite after you write him - it will be >> his problem. > Looks like this is not. nTabRef++ correspond not to table, which ultimately unused. > It rather correspond to item->pTab, which is not nullified and used around. I've > updated ref count increment to reflect the fact. > >>>>> +sql_delete_by_where(struct Parse *parse, char *t_name, const char **columns, >>>>> + struct Expr **values, int pairs_count) >>>>> { >>>>> - Expr *where = NULL; >>>>> - SrcList *src; >>>>> - >>>>> - assert(nPairs > 0); >>>>> - if (pParse->nErr > 0 || pParse->db->mallocFailed) >>>>> + if (parse->nErr > 0 || parse->db->mallocFailed) >>>>> goto error; >>>>> - src = sql_alloc_src_list(pParse->db); >>>>> - src->a[0].zName = sqlite3DbStrDup(pParse->db, zTab); >>>>> + struct SrcList *src = sql_alloc_src_list(parse->db); >>>>> + src->a[0].zName = sqlite3DbStrDup(parse->db, t_name); >>>>> if (src == NULL) >>>> 2. Maybe first check src on NULL and then use it? Looks like the second SQLite bug. >>> Fixed. >> >> 2. Now I see, that sql_delete_by_where is never called. > Removed. > >>>>> +sql_generate_row_delete(struct Parse *parse, struct Table *table, >>>>> + struct Trigger *trigger_list, int cursor, int reg_pk, >>>> >>>> 3. I think, it is time to invent a standard name for cursors: I see, that both 'cursor' and 'reg_pk' >>>> are cursors, but their names differs very. My proposal: each cursor variable must end with '_cursor'. >>>> So data_cursor and pk_cursor instead of cursor and reg_pk. >>> Well, actualy no. Cursor is a cursor, this is immediate value in VDBE. Register (reg_pk) is a memory >>> cell. They're not the same thing for sure. >>> >>>>> + short npk, bool is_count, >> >> 3. Oh, now I see. I was confused by names mismatch in the function declaration and implementation. >> In the implementation you use 'reg_pk' name, but in the declaration you use 'ipk'. Please, fix >> this too. > Fixed. > >>>>> diff --git a/src/box/sql/sqliteInt.h b/src/box/sql/sqliteInt.h >>>>> --- a/src/box/sql/sqliteInt.h >>>>> +++ b/src/box/sql/sqliteInt.h >>>>> @@ -3671,15 +3670,69 @@ int sqlite3Select(Parse *, Select *, SelectDest *); >>>>> Select *sqlite3SelectNew(Parse *, ExprList *, SrcList *, Expr *, ExprList *, >>>>> Expr *, ExprList *, u32, Expr *, Expr *); >>>>> void sqlite3SelectDelete(sqlite3 *, Select *); >>>>> -Table *sqlite3SrcListLookup(Parse *, SrcList *); >>>>> + >>>>> +/** >>>>> + * While a SrcList can in general represent multiple tables and >>>>> + * subqueries (as in the FROM clause of a SELECT statement) in >>>>> + * this case it contains the name of a single table, as one might >>>>> + * find in an INSERT, DELETE, or UPDATE statement. Look up that >>>>> + * table in the symbol table and return a pointer. Set an error >>>>> + * message and return NULL if the table name is not found or if >>>>> + * any other error occurs. >>>>> + * >>>>> + * The following fields are initialized appropriate in src_list: >>>>> + * >>>>> + * pSrc->a[0].pTab Pointer to the Table object. >>>> 5. No pSrc anymore. >>> Fixed. >>>>> + * pSrc->a[0].pIndex Pointer to the INDEXED BY index, if >> 4. Now it is out of 66 symbols. Sorry, it is not my rules( > Fixed. > >>> @@ -3782,11 +3834,118 @@ int sqlite3ExprContainsSubquery(Expr *); >>> + * @param parse Parsing context. >>> + * @param table Table containing the row to be deleted. >>> + * @param trigger_list List of triggers to (potentially) fire. >>> + * @param cursor Cursor from which column data is extracted/ >>> + * @param ipk First memory cell containing the PRIMARY KEY. >>> + * @param npk umber of PRIMARY KEY memory cells. >>> + * @param need_update_count. If non-zero, increment the row change >> >> 5. Non-zero -> true. Now it is boolean variable. > Fixed. > >>> + * counter. >>> + * @param onconf Default ON CONFLICT policy for triggers. >>> + * @param mode ONEPASS_OFF, _SINGLE, or _MULTI. See above. >> >> 6. 'mode' in @param, but 'eMode' in arguments. > Fixed. > >>> + * @param idx_noseek If it is a valid cursor number (>=0), >>> + * then it identifies an index cursor that already points >>> + * to the index entry to be deleted. >>> + */ >>> +void >>> +sql_generate_row_delete(struct Parse *parse, struct Table *table, >>> + struct Trigger *trigger_list, int cursor, int ipk, >>> + short npk, bool need_update_count, >>> + enum on_conflict_action onconf, u8 eMode, >>> + int idx_noseek); >>> + >>> +/** >>> + * Generate code that will assemble an index key and stores it in >>> + * register reg_out. The key with be for index pIdx which is an >> 7. No pIdx. 'The key will be' maybe? > Fixed. > >>> + * index on table. cursor is the index of a cursor open on the >>> + * table table and pointing to the entry that needs indexing. >>> + * cursor must be the cursor of the PRIMARY KEY index. >>> + * >>> + * Return a register number which is the first in a block of >>> + * registers that holds the elements of the index key. The >>> + * block of registers has already been deallocated by the time >>> + * this routine returns. >>> + * >>> + * If *part_idx_label is not NULL, fill it in with a label and >> 8. *part_idx_label can not be NULL - it is integer. Maybe part_idx_label >> with no '*'? > Fixed. > >>> + * jump to that label if pIdx is a partial index that should be >> 9. No pIdx. > Fixed. > >>> + * skipped. The label should be resolved using >>> + * sql_resolve_part_idx_label(). A partial index should be skipped >>> + * if its WHERE clause evaluates to false or null. If index is >>> + * not a partial index, *piPartIdxLabel will be set to zero which >> 10. No piPartIdxLabel. > Fixed. > >>> + * is an empty label that is ignored by sql_resolve_part_idx_label(). >>> + * >>> + * The pPrior and regPrior parameters are used to implement a >> 11. No pPrior and regPrior. > Fixed. > >>> + * cache to avoid unnecessary register loads. If prev is not >>> + * NULL, then it is a pointer to a different index for which an >>> + * index key has just been computed into register reg_prev. If the >>> + * current pIdx index is generating its key into the same >> 12. No pIdx. > Fixed. > >>> + * sequence of registers and if prev and index share a column in >>> + * common, then the register corresponding to that column already >>> + * holds the correct value and the loading of that register is >>> + * skipped. This optimization is helpful when doing a DELETE or >>> + * an INTEGRITY_CHECK on a table with multiple indices, and >>> + * especially with the PRIMARY KEY columns of the index. >>> + * >>> + * @param parse Parsing context. >>> + * @param index The index for which to generate a key. >> 13. Index is a key? > This is the index for which key is being generated. > > -- > Regards, Kirill Yukhin > > commit f4b0303612c87e2202293d0b1118b35e2f0d2ad6 > Author: Kirill Yukhin > Date: Tue May 15 22:09:36 2018 +0300 > > sql: refactor SQL delete routines > > Refactor DELETE FROM statements translation, update > all live routines according to Tarantool's coding style. > Use key_def instead of Table* where possible. > Remove useless index delete routine as well. > > Part of #3235 > > diff --git a/src/box/sql/build.c b/src/box/sql/build.c > index d39f110..9a96553 100644 > --- a/src/box/sql/build.c > +++ b/src/box/sql/build.c > @@ -2664,10 +2664,10 @@ sqlite3RefillIndex(Parse * pParse, Index * pIndex, int memRootPage) > VdbeCoverage(v); > regRecord = sqlite3GetTempReg(pParse); > > - sqlite3GenerateIndexKey(pParse, pIndex, iTab, regRecord, > - &iPartIdxLabel, 0, 0); > + sql_generate_index_key(pParse, pIndex, iTab, regRecord, > + &iPartIdxLabel, 0, 0); > sqlite3VdbeAddOp2(v, OP_SorterInsert, iSorter, regRecord); > - sqlite3ResolvePartIdxLabel(pParse, iPartIdxLabel); > + sql_resolve_part_idx_label(pParse, iPartIdxLabel); > sqlite3VdbeAddOp2(v, OP_Next, iTab, addr1 + 1); > VdbeCoverage(v); > sqlite3VdbeJumpHere(v, addr1); > diff --git a/src/box/sql/delete.c b/src/box/sql/delete.c > index 2c1ce44..3056a2c 100644 > --- a/src/box/sql/delete.c > +++ b/src/box/sql/delete.c > @@ -29,929 +29,538 @@ > * SUCH DAMAGE. > */ > > -/* > - * This file contains C code routines that are called by the parser > - * in order to generate code for DELETE FROM statements. > - */ > -#include "sqliteInt.h" > #include "box/session.h" > +#include "box/schema.h" > +#include "sqliteInt.h" > +#include "tarantoolInt.h" > > -/* > - * While a SrcList can in general represent multiple tables and subqueries > - * (as in the FROM clause of a SELECT statement) in this case it contains > - * the name of a single table, as one might find in an INSERT, DELETE, > - * or UPDATE statement. Look up that table in the symbol table and > - * return a pointer. Set an error message and return NULL if the table > - * name is not found or if any other error occurs. > - * > - * The following fields are initialized appropriate in pSrc: > - * > - * pSrc->a[0].pTab Pointer to the Table object > - * pSrc->a[0].pIndex Pointer to the INDEXED BY index, if there is one > - * > - */ > -Table * > -sqlite3SrcListLookup(Parse * pParse, SrcList * pSrc) > -{ > - struct SrcList_item *pItem = pSrc->a; > - Table *pTab; > - assert(pItem && pSrc->nSrc == 1); > - pTab = sqlite3LocateTable(pParse, 0, pItem->zName); > - sqlite3DeleteTable(pParse->db, pItem->pTab); > - pItem->pTab = pTab; > - if (pTab != NULL) > - pTab->nTabRef++; > - if (sqlite3IndexedByLookup(pParse, pItem)) > - pTab = NULL; > - return pTab; > -} > - > -/* > - * Check to make sure the given table is writable. If it is not > - * writable, generate an error message and return 1. If it is > - * writable return 0; > - */ > -int > -sqlite3IsReadOnly(Parse * pParse, Table * pTab, int viewOk) > +struct Table * > +sql_list_lookup_table(struct Parse *parse, SrcList *src_list) > { > - /* > - * A table is not writable if it is a system table > - * (i.e. _sql_stat1), this call is not part of a > - * nested parse. In either case leave an error message in > - * pParse and return non-zero. > - */ > - if ((pTab->tabFlags & TF_Readonly) != 0 && pParse->nested == 0) { > - sqlite3ErrorMsg(pParse, "table %s may not be modified", > - pTab->zName); > - return 1; > - } > -#ifndef SQLITE_OMIT_VIEW > - if (!viewOk && space_is_view(pTab)) { > - sqlite3ErrorMsg(pParse, "cannot modify %s because it is a view", > - pTab->zName); > - return 1; > - } > -#endif > - return 0; > + struct SrcList_item *item = src_list->a; > + assert(item != NULL && src_list->nSrc == 1); > + struct Table *table = sqlite3LocateTable(parse, 0, item->zName); > + sqlite3DeleteTable(parse->db, item->pTab); > + item->pTab = table; > + if (table != NULL) > + item->pTab->nTabRef++; > + if (sqlite3IndexedByLookup(parse, item)) > + table = NULL; > + return table; > } > > -#if !defined(SQLITE_OMIT_VIEW) && !defined(SQLITE_OMIT_TRIGGER) > -/* > - * Evaluate a view and store its result in an ephemeral table. The > - * pWhere argument is an optional WHERE clause that restricts the > - * set of rows in the view that are to be added to the ephemeral table. > - */ > void > -sqlite3MaterializeView(Parse * pParse, /* Parsing context */ > - Table * pView, /* View definition */ > - Expr * pWhere, /* Optional WHERE clause to be added */ > - int iCur) /* Cursor number for ephemeral table */ > -{ > - SelectDest dest; > - Select *pSel; > - SrcList *pFrom; > - sqlite3 *db = pParse->db; > - pWhere = sqlite3ExprDup(db, pWhere, 0); > - pFrom = sqlite3SrcListAppend(db, 0, 0); > - if (pFrom) { > - assert(pFrom->nSrc == 1); > - pFrom->a[0].zName = sqlite3DbStrDup(db, pView->zName); > - assert(pFrom->a[0].pOn == 0); > - assert(pFrom->a[0].pUsing == 0); > - } > - pSel = sqlite3SelectNew(pParse, 0, pFrom, pWhere, 0, 0, 0, > - 0, 0, 0); > - sqlite3SelectDestInit(&dest, SRT_EphemTab, iCur); > - sqlite3Select(pParse, pSel, &dest); > - sqlite3SelectDelete(db, pSel); > -} > -#endif /* !defined(SQLITE_OMIT_VIEW) && !defined(SQLITE_OMIT_TRIGGER) */ > - > -#if defined(SQLITE_ENABLE_UPDATE_DELETE_LIMIT) > -/* > - * Generate an expression tree to implement the WHERE, ORDER BY, > - * and LIMIT/OFFSET portion of DELETE and UPDATE statements. > - * > - * DELETE FROM table_wxyz WHERE a<5 ORDER BY a LIMIT 1; > - * \__________________________/ > - * pLimitWhere (pInClause) > - */ > -Expr * > -sqlite3LimitWhere(Parse * pParse, /* The parser context */ > - SrcList * pSrc, /* the FROM clause -- which tables to scan */ > - Expr * pWhere, /* The WHERE clause. May be null */ > - ExprList * pOrderBy, /* The ORDER BY clause. May be null */ > - Expr * pLimit, /* The LIMIT clause. May be null */ > - Expr * pOffset, /* The OFFSET clause. May be null */ > - char *zStmtType /* Either DELETE or UPDATE. For err msgs. */ > - ) > +sql_materialize_view(struct Parse *parse, const char *name, struct Expr *where, > + int cursor) > { > - Expr *pWhereRowid = NULL; /* WHERE rowid .. */ > - Expr *pInClause = NULL; /* WHERE rowid IN ( select ) */ > - Expr *pSelectRowid = NULL; /* SELECT rowid ... */ > - ExprList *pEList = NULL; /* Expression list contaning only pSelectRowid */ > - SrcList *pSelectSrc = NULL; /* SELECT rowid FROM x ... (dup of pSrc) */ > - Select *pSelect = NULL; /* Complete SELECT tree */ > - > - /* Check that there isn't an ORDER BY without a LIMIT clause. > - */ > - if (pOrderBy && (pLimit == 0)) { > - sqlite3ErrorMsg(pParse, "ORDER BY without LIMIT on %s", > - zStmtType); > - goto limit_where_cleanup; > - } > - > - /* We only need to generate a select expression if there > - * is a limit/offset term to enforce. > - */ > - if (pLimit == 0) { > - /* if pLimit is null, pOffset will always be null as well. */ > - assert(pOffset == 0); > - return pWhere; > - } > - > - /* Generate a select expression tree to enforce the limit/offset > - * term for the DELETE or UPDATE statement. For example: > - * DELETE FROM table_a WHERE col1=1 ORDER BY col2 LIMIT 1 OFFSET 1 > - * becomes: > - * DELETE FROM table_a WHERE rowid IN ( > - * SELECT rowid FROM table_a WHERE col1=1 ORDER BY col2 LIMIT 1 OFFSET 1 > - * ); > - */ > - > - pSelectRowid = sqlite3PExpr(pParse, TK_ROW, 0, 0); > - if (pSelectRowid == 0) > - goto limit_where_cleanup; > - pEList = sqlite3ExprListAppend(pParse, 0, pSelectRowid); > - if (pEList == 0) > - goto limit_where_cleanup; > - > - /* duplicate the FROM clause as it is needed by both the DELETE/UPDATE tree > - * and the SELECT subtree. > - */ > - pSelectSrc = sqlite3SrcListDup(pParse->db, pSrc, 0); > - if (pSelectSrc == 0) { > - sqlite3ExprListDelete(pParse->db, pEList); > - goto limit_where_cleanup; > + struct sqlite3 *db = parse->db; > + where = sqlite3ExprDup(db, where, 0); > + struct SrcList *from = sqlite3SrcListAppend(db, NULL, NULL); > + if (from != NULL) { > + assert(from->nSrc == 1); > + from->a[0].zName = sqlite3DbStrDup(db, name); > + assert(from->a[0].pOn == NULL); > + assert(from->a[0].pUsing == NULL); > } > - > - /* generate the SELECT expression tree. */ > - pSelect = sqlite3SelectNew(pParse, pEList, pSelectSrc, pWhere, 0, 0, > - pOrderBy, 0, pLimit, pOffset); > - if (pSelect == 0) > - return 0; > - > - /* now generate the new WHERE rowid IN clause for the DELETE/UDPATE */ > - pWhereRowid = sqlite3PExpr(pParse, TK_ROW, 0, 0); > - pInClause = > - pWhereRowid ? sqlite3PExpr(pParse, TK_IN, pWhereRowid, 0) : 0; > - sqlite3PExprAddSelect(pParse, pInClause, pSelect); > - return pInClause; > - > - limit_where_cleanup: > - sql_expr_free(pParse->db, pWhere); > - sqlite3ExprListDelete(pParse->db, pOrderBy); > - sql_expr_free(pParse->db, pLimit); > - sql_expr_free(pParse->db, pOffset); > - return 0; > + struct Select *select = sqlite3SelectNew(parse, NULL, from, where, NULL, > + NULL, NULL, 0, NULL, NULL); > + struct SelectDest dest; > + sqlite3SelectDestInit(&dest, SRT_EphemTab, cursor); > + sqlite3Select(parse, select, &dest); > + sqlite3SelectDelete(db, select); > } > -#endif /* defined(SQLITE_ENABLE_UPDATE_DELETE_LIMIT) */ > > -/* > - * Generate code for a DELETE FROM statement. > - * > - * DELETE FROM table_wxyz WHERE a<5 AND b NOT NULL; > - * \________/ \________________/ > - * pTabList pWhere > - */ > void > -sqlite3DeleteFrom(Parse * pParse, /* The parser context */ > - SrcList * pTabList, /* The table from which we should delete things */ > - Expr * pWhere) /* The WHERE clause. May be null */ > +sql_table_delete_from(struct Parse *parse, struct SrcList *tab_list, > + struct Expr *where) > { > - Vdbe *v; /* The virtual database engine */ > - Table *pTab; /* The table from which records will be deleted */ > - int i; /* Loop counter */ > - WhereInfo *pWInfo; /* Information about the WHERE clause */ > - Index *pIdx; /* For looping over indices of the table */ > - int iTabCur; /* Cursor number for the table */ > - int iDataCur = 0; /* VDBE cursor for the canonical data source */ > - int iIdxCur = 0; /* Cursor number of the first index */ > - int nIdx; /* Number of indices */ > - sqlite3 *db; /* Main database structure */ > - NameContext sNC; /* Name context to resolve expressions in */ > - int memCnt = -1; /* Memory cell used for change counting */ > - int eOnePass; /* ONEPASS_OFF or _SINGLE or _MULTI */ > - int aiCurOnePass[2]; /* The write cursors opened by WHERE_ONEPASS */ > - u8 *aToOpen = 0; /* Open cursor iTabCur+j if aToOpen[j] is true */ > - Index *pPk = 0; /* The PRIMARY KEY index on the table */ > - int iPk = 0; /* First of nPk registers holding PRIMARY KEY value */ > - i16 nPk; /* Number of columns in the PRIMARY KEY */ > - int iKey; /* Memory cell holding key of row to be deleted */ > - i16 nKey; /* Number of memory cells in the row key */ > - int iEphCur = 0; /* Ephemeral table holding all primary key values */ > - int addrBypass = 0; /* Address of jump over the delete logic */ > - int addrLoop = 0; /* Top of the delete loop */ > - int addrEphOpen = 0; /* Instruction to open the Ephemeral table */ > - int bComplex; /* True if there are triggers or FKs or > - * subqueries in the WHERE clause > - */ > - struct session *user_session = current_session(); > + struct sqlite3 *db = parse->db; > + if (parse->nErr || db->mallocFailed) > + goto delete_from_cleanup; > > -#ifndef SQLITE_OMIT_TRIGGER > - int isView; /* True if attempting to delete from a view */ > - Trigger *pTrigger; /* List of table triggers, if required */ > -#endif > + assert(tab_list->nSrc == 1); > > - db = pParse->db; > - if (pParse->nErr || db->mallocFailed) { > + /* Locate the table which we want to delete. This table > + * has to be put in an SrcList structure because some of > + * the subroutines we will be calling are designed to work > + * with multiple tables and expect an SrcList* parameter > + * instead of just a Table* parameter. > + */ > + struct Table *table = sql_list_lookup_table(parse, tab_list); > + if (table == NULL) > goto delete_from_cleanup; > - } > - assert(pTabList->nSrc == 1); > > - /* Locate the table which we want to delete. This table has to be > - * put in an SrcList structure because some of the subroutines we > - * will be calling are designed to work with multiple tables and expect > - * an SrcList* parameter instead of just a Table* parameter. > + struct space *space = space_by_id(SQLITE_PAGENO_TO_SPACEID(table->tnum)); > + assert(space != NULL); > + /* Figure out if we have any triggers and if the table > + * being deleted from is a view. > */ > - pTab = sqlite3SrcListLookup(pParse, pTabList); > - if (pTab == 0) > - goto delete_from_cleanup; > + struct Trigger *trigger_list = sqlite3TriggersExist(table, TK_DELETE, > + NULL, NULL); > > - /* Figure out if we have any triggers and if the table being > - * deleted from is a view > + bool is_view = space->def->opts.is_view; > + /* True if there are triggers or FKs or subqueries in the > + * WHERE clause. > */ > -#ifndef SQLITE_OMIT_TRIGGER > - pTrigger = sqlite3TriggersExist(pTab, TK_DELETE, 0, 0); > - isView = pTab->pSelect != 0; > - bComplex = pTrigger || sqlite3FkRequired(pTab, 0); > -#else > -#define pTrigger 0 > -#define isView 0 > -#endif > -#ifdef SQLITE_OMIT_VIEW > -#undef isView > -#define isView 0 > -#endif > - > - /* If pTab is really a view, make sure it has been initialized. > + bool is_complex = (trigger_list != NULL) || sqlite3FkRequired(table, 0); > + > + /* If table is really a view, make sure it has been > + * initialized. > */ > - if (sqlite3ViewGetColumnNames(pParse, pTab)) { > + if (sqlite3ViewGetColumnNames(parse, table)) > goto delete_from_cleanup; > - } > > - if (sqlite3IsReadOnly(pParse, pTab, (pTrigger ? 1 : 0))) { > + if (is_view && trigger_list == NULL) { > + sqlite3ErrorMsg(parse, "cannot modify %s because it is a view", > + space->def->name); > goto delete_from_cleanup; > } > - assert(!isView || pTrigger); > > /* Assign cursor numbers to the table and all its indices. > */ > - assert(pTabList->nSrc == 1); > - iTabCur = pTabList->a[0].iCursor = pParse->nTab++; > - for (nIdx = 0, pIdx = pTab->pIndex; pIdx; pIdx = pIdx->pNext, nIdx++) { > - pParse->nTab++; > - } > + int tab_cursor = tab_list->a[0].iCursor = parse->nTab++; > + parse->nTab += space->index_count; > > - /* Begin generating code. > - */ > - v = sqlite3GetVdbe(pParse); > - if (v == 0) { > + /* Begin generating code. */ > + struct Vdbe *v = sqlite3GetVdbe(parse); > + if (v == NULL) > goto delete_from_cleanup; > - } > - if (pParse->nested == 0) > + > + if (parse->nested == 0) > sqlite3VdbeCountChanges(v); > - sql_set_multi_write(pParse, true); > + sql_set_multi_write(parse, true); > > - /* If we are trying to delete from a view, realize that view into > - * an ephemeral table. > + /* If we are trying to delete from a view, realize that > + * view into an ephemeral table. > */ > -#if !defined(SQLITE_OMIT_VIEW) && !defined(SQLITE_OMIT_TRIGGER) > - if (isView) { > - sqlite3MaterializeView(pParse, pTab, pWhere, iTabCur); > - iDataCur = iIdxCur = iTabCur; > + if (is_view) { > + sql_materialize_view(parse, space->def->name, where, > + tab_cursor); > } > -#endif > > - /* Resolve the column names in the WHERE clause. > - */ > - memset(&sNC, 0, sizeof(sNC)); > - sNC.pParse = pParse; > - sNC.pSrcList = pTabList; > - if (sqlite3ResolveExprNames(&sNC, pWhere)) { > + /* Resolve the column names in the WHERE clause. */ > + struct NameContext nc; > + memset(&nc, 0, sizeof(nc)); > + nc.pParse = parse; > + nc.pSrcList = tab_list; > + if (sqlite3ResolveExprNames(&nc, where)) > goto delete_from_cleanup; > - } > > - /* Initialize the counter of the number of rows deleted, if > - * we are counting rows. > + /* Initialize the counter of the number of rows deleted, > + * if we are counting rows. > */ > + int reg_count = -1; > + struct session *user_session = current_session(); > if (user_session->sql_flags & SQLITE_CountRows) { > - memCnt = ++pParse->nMem; > - sqlite3VdbeAddOp2(v, OP_Integer, 0, memCnt); > + reg_count = ++parse->nMem; > + sqlite3VdbeAddOp2(v, OP_Integer, 0, reg_count); > } > -#ifndef SQLITE_OMIT_TRUNCATE_OPTIMIZATION > - /* Special case: A DELETE without a WHERE clause deletes everything. > - * It is easier just to erase the whole table. Prior to version 3.6.5, > - * this optimization caused the row change count (the value returned by > - * API function sqlite3_count_changes) to be set incorrectly. > + /* Special case: A DELETE without a WHERE clause deletes > + * everything. It is easier just to erase the whole table. > */ > - if (pWhere == 0 && !bComplex > -#ifdef SQLITE_ENABLE_PREUPDATE_HOOK > - && db->xPreUpdateCallback == 0 > -#endif > - ) { > - assert(!isView); > - > - sqlite3VdbeAddOp1(v, OP_Clear, pTab->tnum); > - > - /* Do not start Tarantool's transaction in case of truncate optimization. > - This is workaround until system tables cannot be changes inside a > - transaction (_truncate). */ > - pParse->initiateTTrans = false; > - } else > -#endif /* SQLITE_OMIT_TRUNCATE_OPTIMIZATION */ > - { > - u16 wcf = > - WHERE_ONEPASS_DESIRED | WHERE_DUPLICATES_OK | > - WHERE_SEEK_TABLE; > - if (sNC.ncFlags & NC_VarSelect) > - bComplex = 1; > - wcf |= (bComplex ? 0 : WHERE_ONEPASS_MULTIROW); > - /* Create an ephemeral table used to hold all primary keys for > - * rows to be deleted. Since VIEW is held in ephemeral table, > - * there is no PK for it, so columns should be loaded manually. > + if (where == NULL && !is_complex) { > + assert(!is_view); > + > + sqlite3VdbeAddOp1(v, OP_Clear, table->tnum); > + > + /* Do not start Tarantool's transaction in case of > + * truncate optimization. This is workaround until > + * system tables cannot be changes inside a > + * transaction (_truncate). > + */ > + parse->initiateTTrans = false; > + } else { > + uint16_t wcf = WHERE_ONEPASS_DESIRED | WHERE_DUPLICATES_OK | > + WHERE_SEEK_TABLE; > + if (nc.ncFlags & NC_VarSelect) > + is_complex = true; > + wcf |= (is_complex ? 0 : WHERE_ONEPASS_MULTIROW); > + /* Create an ephemeral table used to hold all > + * primary keys for rows to be deleted. Since VIEW > + * is held in ephemeral table, there is no PK for > + * it, so columns should be loaded manually. > */ > - if (isView) { > - nPk = pTab->nCol; > - iPk = pParse->nMem + 1; > - pParse->nMem += nPk; > - iEphCur = pParse->nTab++; > - addrEphOpen = > - sqlite3VdbeAddOp4(v, OP_OpenTEphemeral, iEphCur, > - nPk); > + struct Index *pk = NULL; > + int reg_pk = parse->nMem + 1; > + int pk_len; > + int eph_cursor = parse->nTab++; > + int addr_eph_open = sqlite3VdbeCurrentAddr(v); > + if (is_view) { > + pk_len = table->nCol; > + parse->nMem += pk_len; > + sqlite3VdbeAddOp2(v, OP_OpenTEphemeral, > + eph_cursor, pk_len); > } else { > - pPk = sqlite3PrimaryKeyIndex(pTab); > - assert(pPk != 0); > - nPk = index_column_count(pPk); > - iPk = pParse->nMem + 1; > - pParse->nMem += nPk; > - iEphCur = pParse->nTab++; > - addrEphOpen = > - sqlite3VdbeAddOp2(v, OP_OpenTEphemeral, iEphCur, > - nPk); > - sql_vdbe_set_p4_key_def(pParse, pPk); > + pk = sqlite3PrimaryKeyIndex(table); > + assert(pk != NULL); > + pk_len = index_column_count(pk); > + parse->nMem += pk_len; > + sqlite3VdbeAddOp2(v, OP_OpenTEphemeral, eph_cursor, > + pk_len); > + sql_vdbe_set_p4_key_def(parse, pk); > } > > - /* Construct a query to find the primary key for every row > - * to be deleted, based on the WHERE clause. Set variable eOnePass > - * to indicate the strategy used to implement this delete: > + /* Construct a query to find the primary key for > + * every row to be deleted, based on the WHERE > + * clause. Set variable one_pass to indicate the > + * strategy used to implement this delete: > * > - * ONEPASS_OFF: Two-pass approach - use a FIFO for PK values. > - * ONEPASS_SINGLE: One-pass approach - at most one row deleted. > - * ONEPASS_MULTI: One-pass approach - any number of rows may be deleted. > + * ONEPASS_OFF: Two-pass approach - use a FIFO > + * for PK values. > + * ONEPASS_SINGLE: One-pass approach - at most one > + * row deleted. > + * ONEPASS_MULTI: One-pass approach - any number > + * of rows may be deleted. > */ > - pWInfo = > - sqlite3WhereBegin(pParse, pTabList, pWhere, 0, 0, wcf, > - iTabCur + 1); > - if (pWInfo == 0) > + struct WhereInfo *winfo = > + sqlite3WhereBegin(parse, tab_list, where, NULL, NULL, wcf, > + tab_cursor + 1); > + if (winfo == NULL) > goto delete_from_cleanup; > - eOnePass = sqlite3WhereOkOnePass(pWInfo, aiCurOnePass); > - assert(eOnePass != ONEPASS_MULTI); > - /* Tarantool workaround: see comment in sqlite3WhereBegin. */ > - /* assert( bComplex || eOnePass!=ONEPASS_OFF ); */ > - > - /* Keep track of the number of rows to be deleted */ > - if (user_session->sql_flags & SQLITE_CountRows) { > - sqlite3VdbeAddOp2(v, OP_AddImm, memCnt, 1); > - } > + > + /* The write cursors opened by WHERE_ONEPASS */ > + int one_pass_cur[2]; > + int one_pass = sqlite3WhereOkOnePass(winfo, one_pass_cur); > + assert(one_pass != ONEPASS_MULTI); > + /* Tarantool: see comment in > + * sqlite3WhereOkOnePass. > + */ > + /* assert(is_complex || one_pass != ONEPASS_OFF); */ > + > + /* Keep track of the number of rows to be > + * deleted. > + */ > + if (user_session->sql_flags & SQLITE_CountRows) > + sqlite3VdbeAddOp2(v, OP_AddImm, reg_count, 1); > > /* Extract the primary key for the current row */ > - if (!isView) { > - for (i = 0; i < nPk; i++) { > - assert(pPk->aiColumn[i] >= 0); > - sqlite3ExprCodeGetColumnOfTable(v, pTab, > - iTabCur, > - pPk-> > + if (!is_view) { > + for (int i = 0; i < pk_len; i++) { > + assert(pk->aiColumn[i] >= 0); > + sqlite3ExprCodeGetColumnOfTable(v, table, > + tab_cursor, > + pk-> > aiColumn[i], > - iPk + i); > + reg_pk + i); > } > } else { > - for (i = 0; i < nPk; i++) { > - sqlite3VdbeAddOp3(v, OP_Column, iDataCur, > - i, iPk + i); > + for (int i = 0; i < pk_len; i++) { > + sqlite3VdbeAddOp3(v, OP_Column, tab_cursor, > + i, reg_pk + i); > } > } > - iKey = iPk; > > - if (eOnePass != ONEPASS_OFF) { > - /* For ONEPASS, no need to store the primary-key. There is only > - * one, so just keep it in its register(s) and fall through to the > - * delete code. > + int reg_key; > + int key_len; > + if (one_pass != ONEPASS_OFF) { > + /* For ONEPASS, no need to store the > + * primary-key. There is only one, so just > + * keep it in its register(s) and fall > + * through to the delete code. > */ > - nKey = nPk; /* OP_Found will use an unpacked key */ > - aToOpen = sqlite3DbMallocRawNN(db, nIdx + 2); > - if (aToOpen == 0) { > - sqlite3WhereEnd(pWInfo); > - goto delete_from_cleanup; > - } > - memset(aToOpen, 1, nIdx + 1); > - aToOpen[nIdx + 1] = 0; > - if (aiCurOnePass[0] >= 0) > - aToOpen[aiCurOnePass[0] - iTabCur] = 0; > - if (aiCurOnePass[1] >= 0) > - aToOpen[aiCurOnePass[1] - iTabCur] = 0; > - if (addrEphOpen) > - sqlite3VdbeChangeToNoop(v, addrEphOpen); > + reg_key = reg_pk; > + /* OP_Found will use an unpacked key */ > + key_len = pk_len; > + sqlite3VdbeChangeToNoop(v, addr_eph_open); > } else { > - /* Add the PK key for this row to the temporary table */ > - iKey = ++pParse->nMem; > - nKey = 0; /* Zero tells OP_Found to use a composite key */ > - const char *zAff = isView ? 0 : > - sqlite3IndexAffinityStr(pParse->db, pPk); > - sqlite3VdbeAddOp4(v, OP_MakeRecord, iPk, nPk, iKey, zAff, nPk); > - /* Set flag to save memory allocating one by malloc. */ > + /* Add the PK key for this row to the > + * temporary table. > + */ > + reg_key = ++parse->nMem; > + /* Zero tells OP_Found to use a composite > + * key. > + */ > + key_len = 0; > + const char *zAff = is_view ? NULL : > + sqlite3IndexAffinityStr(parse->db, pk); > + sqlite3VdbeAddOp4(v, OP_MakeRecord, reg_pk, pk_len, > + reg_key, zAff, pk_len); > + /* Set flag to save memory allocating one > + * by malloc. > + */ > sqlite3VdbeChangeP5(v, 1); > - sqlite3VdbeAddOp2(v, OP_IdxInsert, iEphCur, iKey); > + sqlite3VdbeAddOp2(v, OP_IdxInsert, eph_cursor, reg_key); > } > > - /* If this DELETE cannot use the ONEPASS strategy, this is the > - * end of the WHERE loop > + /* If this DELETE cannot use the ONEPASS strategy, > + * this is the end of the WHERE loop. > */ > - if (eOnePass != ONEPASS_OFF) { > - addrBypass = sqlite3VdbeMakeLabel(v); > - } else { > - sqlite3WhereEnd(pWInfo); > - } > - > - /* Unless this is a view, open cursors for the table we are > - * deleting from and all its indices. If this is a view, then the > - * only effect this statement has is to fire the INSTEAD OF > + int addr_bypass = 0; > + if (one_pass != ONEPASS_OFF) > + addr_bypass = sqlite3VdbeMakeLabel(v); > + else > + sqlite3WhereEnd(winfo); > + > + /* Unless this is a view, open cursors for the > + * table we are deleting from and all its indices. > + * If this is a view, then the only effect this > + * statement has is to fire the INSTEAD OF > * triggers. > */ > - if (!isView) { > + if (!is_view) { > int iAddrOnce = 0; > - if (eOnePass == ONEPASS_MULTI) { > + if (one_pass == ONEPASS_MULTI) { > iAddrOnce = sqlite3VdbeAddOp0(v, OP_Once); > VdbeCoverage(v); > } > - sqlite3OpenTableAndIndices(pParse, pTab, OP_OpenWrite, > - OPFLAG_FORDELETE, iTabCur, > - aToOpen, &iDataCur, > - &iIdxCur, > - ON_CONFLICT_ACTION_NONE, 0); > - assert(pPk || iDataCur == iTabCur); > - assert(pPk || iIdxCur == iDataCur + 1); > - if (eOnePass == ONEPASS_MULTI) > + > + int space_ptr_reg = ++parse->nMem; > + sqlite3VdbeAddOp4(v, OP_LoadPtr, 0, space_ptr_reg, 0, > + (void *)space, P4_SPACEPTR); > + sqlite3VdbeAddOp3(v, OP_OpenWrite, tab_cursor, > + table->tnum, space_ptr_reg); > + sql_vdbe_set_p4_key_def(parse, pk); > + VdbeComment((v, "%s", pk->zName)); > + > + if (one_pass == ONEPASS_MULTI) > sqlite3VdbeJumpHere(v, iAddrOnce); > } > > - /* Set up a loop over the primary-keys that were found in the > - * where-clause loop above. > + /* Set up a loop over the primary-keys that were > + * found in the where-clause loop above. > */ > - if (eOnePass != ONEPASS_OFF) { > - assert(nKey == nPk); /* OP_Found will use an unpacked key */ > - if (aToOpen[iDataCur - iTabCur]) { > - assert(pPk != 0 || pTab->pSelect != 0); > - sqlite3VdbeAddOp4Int(v, OP_NotFound, iDataCur, > - addrBypass, iKey, nKey); > + int addr_loop = 0; > + if (one_pass != ONEPASS_OFF) { > + /* OP_Found will use an unpacked key. */ > + assert(key_len == pk_len); > + assert(pk != NULL || table->pSelect != NULL); > + sqlite3VdbeAddOp4Int(v, OP_NotFound, tab_cursor, > + addr_bypass, reg_key, key_len); > > - VdbeCoverage(v); > - } > + VdbeCoverage(v); > } else { > - addrLoop = sqlite3VdbeAddOp1(v, OP_Rewind, iEphCur); > + addr_loop = sqlite3VdbeAddOp1(v, OP_Rewind, eph_cursor); > VdbeCoverage(v); > - sqlite3VdbeAddOp2(v, OP_RowData, iEphCur, iKey); > + sqlite3VdbeAddOp2(v, OP_RowData, eph_cursor, reg_key); > } > > /* Delete the row */ > - { > - int count = (pParse->nested == 0); /* True to count changes */ > - int iIdxNoSeek = -1; > - if (bComplex == 0 && aiCurOnePass[1] != iDataCur > - /* Tarantool: as far as ONEPASS is disabled, there's no index > - w/o need of seeking. */ > - && eOnePass != ONEPASS_OFF) { > - iIdxNoSeek = aiCurOnePass[1]; > - } > - sqlite3GenerateRowDelete(pParse, pTab, pTrigger, > - iDataCur, iIdxCur, iKey, nKey, > - count, > - ON_CONFLICT_ACTION_DEFAULT, > - eOnePass, > - iIdxNoSeek); > - } > + int idx_noseek = -1; > + if (!is_complex && one_pass_cur[1] != tab_cursor > + /* Tarantool: as far as ONEPASS is disabled, > + * there's no index w/o need of seeking. > + */ > + && one_pass != ONEPASS_OFF) > + idx_noseek = one_pass_cur[1]; > + > + sql_generate_row_delete(parse, table, trigger_list, tab_cursor, > + reg_key, key_len, parse->nested == 0, > + ON_CONFLICT_ACTION_DEFAULT, one_pass, > + idx_noseek); > > /* End of the loop over all primary-keys. */ > - if (eOnePass != ONEPASS_OFF) { > - sqlite3VdbeResolveLabel(v, addrBypass); > - sqlite3WhereEnd(pWInfo); > + if (one_pass != ONEPASS_OFF) { > + sqlite3VdbeResolveLabel(v, addr_bypass); > + sqlite3WhereEnd(winfo); > } else { > - sqlite3VdbeAddOp2(v, OP_Next, iEphCur, addrLoop + 1); > + sqlite3VdbeAddOp2(v, OP_Next, eph_cursor, > + addr_loop + 1); > VdbeCoverage(v); > - sqlite3VdbeJumpHere(v, addrLoop); > + sqlite3VdbeJumpHere(v, addr_loop); > } > - } /* End non-truncate path */ > + } > > - /* Return the number of rows that were deleted. If this routine is > - * generating code because of a call to sqlite3NestedParse(), do not > - * invoke the callback function. > + /* Return the number of rows that were deleted. If this > + * routine is generating code because of a call to > + * sqlite3NestedParse(), do not invoke the callback > + * function. > */ > if ((user_session->sql_flags & SQLITE_CountRows) && > - !pParse->nested && !pParse->pTriggerTab) { > - sqlite3VdbeAddOp2(v, OP_ResultRow, memCnt, 1); > + parse->nested == 0 && !parse->pTriggerTab) { > + sqlite3VdbeAddOp2(v, OP_ResultRow, reg_count, 1); > sqlite3VdbeSetNumCols(v, 1); > sqlite3VdbeSetColName(v, 0, COLNAME_NAME, "rows deleted", > SQLITE_STATIC); > } > > delete_from_cleanup: > - sqlite3SrcListDelete(db, pTabList); > - sql_expr_free(db, pWhere, false); > - sqlite3DbFree(db, aToOpen); > - return; > -} > - > -/* Generate VDBE code for > - * DELETE FROM WHERE > - * = > - * AND ... > - * AND = ; > - * > - * This function does not increment the nested counter and is > - * faster than nested parsing of the request above. > - * @param pParse Parser context. > - * @param pTab Table name. > - * @param columns Column names array. > - * @param values Column values array. > - * @param nPairs Length of @columns and @values. > - * > - * In case of error the @values elements are deleted. > - */ > -void > -sqlite3DeleteByKey(Parse *pParse, char *zTab, const char **columns, > - Expr **values, int nPairs) > -{ > - Expr *where = NULL; > - SrcList *src; > - > - assert(nPairs > 0); > - if (pParse->nErr > 0 || pParse->db->mallocFailed) > - goto error; > - src = sql_alloc_src_list(pParse->db); > - src->a[0].zName = sqlite3DbStrDup(pParse->db, zTab); > - if (src == NULL) > - goto error; > - /* Dummy init of INDEXED BY clause. */ > - Token t = { NULL, 0, false }; > - sqlite3SrcListIndexedBy(pParse, src, &t); > - > - for (int i = 0; i < nPairs; ++i) { > - Expr *col_expr = sqlite3Expr(pParse->db, TK_ID, columns[i]); > - if (col_expr == NULL || values[i] == NULL) > - goto error; > - Expr *eq_expr = > - sqlite3PExpr(pParse, TK_EQ, col_expr, values[i]); > - /* In case of error the values[i] had been deleted in > - * sqlite3PExpr already. Do not delete it second time in the > - * cycle below. > - */ > - values[i] = NULL; > - if (eq_expr == NULL) > - goto error; > - if (i == 0) { > - where = eq_expr; > - } else { > - where = sqlite3ExprAnd(pParse->db, where, eq_expr); > - if (where == NULL) > - goto error; > - } > - } > - /* DeleteFrom frees the src and exprs in case of error. */ > - sqlite3DeleteFrom(pParse, src, where); > - return; > - > - error: > - sql_expr_free(pParse->db, where, false); > - for (int i = 0; i < nPairs; ++i) > - sql_expr_free(pParse->db, values[i], false); > + sqlite3SrcListDelete(db, tab_list); > + sql_expr_free(db, where, false); > } > > -/* Make sure "isView" and other macros defined above are undefined. Otherwise > - * they may interfere with compilation of other functions in this file > - * (or in another file, if this file becomes part of the amalgamation). > - */ > -#ifdef isView > -#undef isView > -#endif > -#ifdef pTrigger > -#undef pTrigger > -#endif > - > -/* > - * This routine generates VDBE code that causes a single row of a > - * single table to be deleted. Both the original table entry and > - * all indices are removed. > - * > - * Preconditions: > - * > - * 1. iDataCur is an open cursor on the btree that is the canonical data > - * store for the table. (This will be the PRIMARY KEY index) > - * > - * 2. Read/write cursors for all indices of pTab must be open as > - * cursor number iIdxCur+i for the i-th index. > - * > - * 3. The primary key for the row to be deleted must be stored in a > - * sequence of nPk memory cells starting at iPk. If nPk==0 that means > - * that a search record formed from OP_MakeRecord is contained in the > - * single memory location iPk. > - * > - * eMode: > - * Parameter eMode may be passed either ONEPASS_OFF (0), ONEPASS_SINGLE, or > - * ONEPASS_MULTI. If eMode is not ONEPASS_OFF, then the cursor > - * iDataCur already points to the row to delete. If eMode is ONEPASS_OFF > - * then this function must seek iDataCur to the entry identified by iPk > - * and nPk before reading from it. > - * > - * If eMode is ONEPASS_MULTI, then this call is being made as part > - * of a ONEPASS delete that affects multiple rows. In this case, if > - * iIdxNoSeek is a valid cursor number (>=0), then its position should > - * be preserved following the delete operation. Or, if iIdxNoSeek is not > - * a valid cursor number, the position of iDataCur should be preserved > - * instead. > - * > - * iIdxNoSeek: > - * If iIdxNoSeek is a valid cursor number (>=0), then it identifies an > - * index cursor (from within array of cursors starting at iIdxCur) that > - * already points to the index entry to be deleted. > - */ > void > -sqlite3GenerateRowDelete(Parse * pParse, /* Parsing context */ > - Table * pTab, /* Table containing the row to be deleted */ > - Trigger * pTrigger, /* List of triggers to (potentially) fire */ > - int iDataCur, /* Cursor from which column data is extracted */ > - int iIdxCur, /* First index cursor */ > - int iPk, /* First memory cell containing the PRIMARY KEY */ > - i16 nPk, /* Number of PRIMARY KEY memory cells */ > - u8 count, /* If non-zero, increment the row change counter */ > - enum on_conflict_action onconf, /* Default ON CONFLICT policy for triggers */ > - u8 eMode, /* ONEPASS_OFF, _SINGLE, or _MULTI. See above */ > - int iIdxNoSeek) /* Cursor number of cursor that does not need seeking */ > +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, > + enum on_conflict_action onconf, u8 mode, > + int idx_noseek) > { > - Vdbe *v = pParse->pVdbe; /* Vdbe */ > - int iOld = 0; /* First register in OLD.* array */ > - int iLabel; /* Label resolved to end of generated code */ > - > - /* Vdbe is guaranteed to have been allocated by this stage. */ > - assert(v); > - (void)iIdxCur; > + struct Vdbe *v = parse->pVdbe; > + /* Vdbe is guaranteed to have been allocated by this > + * stage. > + */ > + assert(v != NULL); > VdbeModuleComment((v, "BEGIN: GenRowDel(%d,%d,%d,%d)", > - iDataCur, iIdxCur, iPk, (int)nPk)); > + cursor, iIdxCur, reg_pk, (int)nPk)); > > - /* Seek cursor iCur to the row to delete. If this row no longer exists > - * (this can happen if a trigger program has already deleted it), do > - * not attempt to delete it or fire any DELETE triggers. > + /* Seek cursor iCur to the row to delete. If this row no > + * longer exists (this can happen if a trigger program has > + * already deleted it), do not attempt to delete it or > + * fire any DELETE triggers. > */ > - iLabel = sqlite3VdbeMakeLabel(v); > - if (eMode == ONEPASS_OFF) { > - sqlite3VdbeAddOp4Int(v, OP_NotFound, iDataCur, iLabel, iPk, nPk); > + int label = sqlite3VdbeMakeLabel(v); > + if (mode == ONEPASS_OFF) { > + sqlite3VdbeAddOp4Int(v, OP_NotFound, cursor, label, reg_pk, npk); > VdbeCoverageIf(v, opSeek == OP_NotFound); > } > > + int first_old_reg = 0; > /* If there are any triggers to fire, allocate a range of registers to > * use for the old.* references in the triggers. > */ > - if (sqlite3FkRequired(pTab, 0) || pTrigger) { > - u32 mask; /* Mask of OLD.* columns in use */ > - int iCol; /* Iterator used while populating OLD.* */ > - int addrStart; /* Start of BEFORE trigger programs */ > - > - /* TODO: Could use temporary registers here. > - */ > - mask = > - sqlite3TriggerColmask(pParse, pTrigger, 0, 0, > - TRIGGER_BEFORE | TRIGGER_AFTER, pTab, > + if (sqlite3FkRequired(table, 0) || trigger_list != NULL) { > + /* 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); > - mask |= sqlite3FkOldmask(pParse, pTab); > - iOld = pParse->nMem + 1; > - pParse->nMem += (1 + pTab->nCol); > + mask |= sqlite3FkOldmask(parse, table); > + first_old_reg = parse->nMem + 1; > + parse->nMem += (1 + table->nCol); > > - /* Populate the OLD.* pseudo-table register array. These values will be > - * used by any BEFORE and AFTER triggers that exist. > + /* Populate the OLD.* pseudo-table register array. > + * These values will be used by any BEFORE and > + * AFTER triggers that exist. > */ > - sqlite3VdbeAddOp2(v, OP_Copy, iPk, iOld); > - for (iCol = 0; iCol < pTab->nCol; iCol++) { > + sqlite3VdbeAddOp2(v, OP_Copy, reg_pk, first_old_reg); > + for (int i = 0; i < table->nCol; i++) { > testcase(mask != 0xffffffff && iCol == 31); > testcase(mask != 0xffffffff && iCol == 32); > if (mask == 0xffffffff > - || (iCol <= 31 && (mask & MASKBIT32(iCol)) != 0)) { > - sqlite3ExprCodeGetColumnOfTable(v, pTab, > - iDataCur, iCol, > - iOld + iCol + > - 1); > + || (i <= 31 && (mask & MASKBIT32(i)) != 0)) { > + sqlite3ExprCodeGetColumnOfTable(v, table, > + cursor, i, > + first_old_reg + > + i + 1); > } > } > > /* Invoke BEFORE DELETE trigger programs. */ > - addrStart = sqlite3VdbeCurrentAddr(v); > - sqlite3CodeRowTrigger(pParse, pTrigger, > - TK_DELETE, 0, TRIGGER_BEFORE, pTab, iOld, > - onconf, iLabel); > - > - /* If any BEFORE triggers were coded, then seek the cursor to the > - * row to be deleted again. It may be that the BEFORE triggers moved > - * the cursor or of already deleted the row that the cursor was > - * pointing to. > + int addr_start = sqlite3VdbeCurrentAddr(v); > + sqlite3CodeRowTrigger(parse, trigger_list, > + TK_DELETE, 0, TRIGGER_BEFORE, table, > + first_old_reg, onconf, label); > + > + /* If any BEFORE triggers were coded, then seek > + * the cursor to the row to be deleted again. It > + * may be that the BEFORE triggers moved the > + * cursor or of already deleted the row that the > + * cursor was pointing to. > */ > - if (addrStart < sqlite3VdbeCurrentAddr(v)) { > - sqlite3VdbeAddOp4Int(v, OP_NotFound, iDataCur, iLabel, iPk, > - nPk); > + if (addr_start < sqlite3VdbeCurrentAddr(v)) { > + sqlite3VdbeAddOp4Int(v, OP_NotFound, cursor, label, > + reg_pk, npk); > VdbeCoverageIf(v, opSeek == OP_NotFound); > } > > - /* Do FK processing. This call checks that any FK constraints that > - * refer to this table (i.e. constraints attached to other tables) > - * are not violated by deleting this row. > + /* Do FK processing. This call checks that any FK > + * constraints that refer to this table (i.e. > + * constraints attached to other tables) are not > + * violated by deleting this row. > */ > - sqlite3FkCheck(pParse, pTab, iOld, 0, 0); > + sqlite3FkCheck(parse, table, first_old_reg, 0, NULL); > } > > - /* Delete the index and table entries. Skip this step if pTab is really > - * a view (in which case the only effect of the DELETE statement is to > - * fire the INSTEAD OF triggers). > - * > - * If variable 'count' is non-zero, then this OP_Delete instruction should > - * invoke the update-hook. The pre-update-hook, on the other hand should > - * be invoked unless table pTab is a system table. The difference is that > - * the update-hook is not invoked for rows removed by REPLACE, but the > - * pre-update-hook is. > + /* Delete the index and table entries. Skip this step if > + * table is really a view (in which case the only effect > + * of the DELETE statement is to fire the INSTEAD OF > + * triggers). > */ > - if (pTab->pSelect == 0) { > - u8 p5 = 0; > - /* kyukhin: Tarantool handles indices uypdate automatically. */ > - /* sqlite3GenerateRowIndexDelete(pParse, pTab, iDataCur, iIdxCur,0,iIdxNoSeek); */ > - sqlite3VdbeAddOp2(v, OP_Delete, iDataCur, > - (count ? OPFLAG_NCHANGE : 0)); > - if (eMode != ONEPASS_OFF) { > + if (table->pSelect == NULL) { > + uint8_t p5 = 0; > + sqlite3VdbeAddOp2(v, OP_Delete, cursor, > + (need_update_count ? OPFLAG_NCHANGE : 0)); > + if (mode != ONEPASS_OFF) > sqlite3VdbeChangeP5(v, OPFLAG_AUXDELETE); > - } > - if (iIdxNoSeek >= 0) { > - sqlite3VdbeAddOp1(v, OP_Delete, iIdxNoSeek); > - } > - if (eMode == ONEPASS_MULTI) > + > + if (idx_noseek >= 0) > + sqlite3VdbeAddOp1(v, OP_Delete, idx_noseek); > + > + if (mode == ONEPASS_MULTI) > p5 |= OPFLAG_SAVEPOSITION; > sqlite3VdbeChangeP5(v, p5); > } > > - /* Do any ON CASCADE, SET NULL or SET DEFAULT operations required to > - * handle rows (possibly in other tables) that refer via a foreign key > - * to the row just deleted. > + /* Do any ON CASCADE, SET NULL or SET DEFAULT operations > + * required to handle rows (possibly in other tables) that > + * refer via a foreign key to the row just deleted. > */ > - sqlite3FkActions(pParse, pTab, 0, iOld, 0); > + sqlite3FkActions(parse, table, 0, first_old_reg, 0); > > /* Invoke AFTER DELETE trigger programs. */ > - sqlite3CodeRowTrigger(pParse, pTrigger, > - TK_DELETE, 0, TRIGGER_AFTER, pTab, iOld, onconf, > - iLabel); > + sqlite3CodeRowTrigger(parse, trigger_list, > + TK_DELETE, 0, TRIGGER_AFTER, table, first_old_reg, > + onconf, label); > > - /* Jump here if the row had already been deleted before any BEFORE > - * trigger programs were invoked. Or if a trigger program throws a > - * RAISE(IGNORE) exception. > + /* Jump here if the row had already been deleted before > + * any BEFORE trigger programs were invoked. Or if a trigger program > + * throws a RAISE(IGNORE) exception. > */ > - sqlite3VdbeResolveLabel(v, iLabel); > + sqlite3VdbeResolveLabel(v, label); > VdbeModuleComment((v, "END: GenRowDel()")); > } > > -/* > - * This routine generates VDBE code that causes the deletion of all > - * index entries associated with a single row of a single table, pTab > - * > - * Preconditions: > - * > - * 1. A read/write cursor "iDataCur" must be open on the canonical storage > - * btree for the table pTab. (This will be primary key index) > - * > - * 2. Read/write cursor for primary index of pTab must be open as > - * cursor number iIdxCur. (The pTab->pIndex index is the 0-th index.) > - * > - * 3. The "iDataCur" cursor must be already positioned on the row > - * that is to be deleted. > - */ > -void > -sqlite3GenerateRowIndexDelete(Parse * pParse, /* Parsing and code generating context */ > - Table * pTab, /* Table containing the row to be deleted */ > - int iDataCur, /* Cursor of table holding data. */ > - int iIdxCur) /* Primary index cursor */ > -{ > - int r1 = -1; /* Register holding an index key */ > - int iPartIdxLabel; /* Jump destination for skipping partial index entries */ > - Vdbe *v; /* The prepared statement under construction */ > - Index *pPk; /* PRIMARY KEY index */ > - > - v = pParse->pVdbe; > - pPk = sqlite3PrimaryKeyIndex(pTab); > - /* In Tarantool it is enough to delete row just from pk */ > - VdbeModuleComment((v, "GenRowIdxDel for %s", pPk->zName)); > - r1 = sqlite3GenerateIndexKey(pParse, pPk, iDataCur, 0, &iPartIdxLabel, > - NULL, r1); > - sqlite3VdbeAddOp3(v, OP_IdxDelete, iIdxCur, r1, index_column_count(pPk)); > - sqlite3ResolvePartIdxLabel(pParse, iPartIdxLabel); > -} > - > -/* > - * Generate code that will assemble an index key and stores it in register > - * regOut. The key with be for index pIdx which is an index on pTab. > - * iCur is the index of a cursor open on the pTab table and pointing to > - * the entry that needs indexing. > - * iCur must be the cursor of the PRIMARY KEY index. > - * > - * Return a register number which is the first in a block of > - * registers that holds the elements of the index key. The > - * block of registers has already been deallocated by the time > - * this routine returns. > - * > - * If *piPartIdxLabel is not NULL, fill it in with a label and jump > - * to that label if pIdx is a partial index that should be skipped. > - * The label should be resolved using sqlite3ResolvePartIdxLabel(). > - * A partial index should be skipped if its WHERE clause evaluates > - * to false or null. If pIdx is not a partial index, *piPartIdxLabel > - * will be set to zero which is an empty label that is ignored by > - * sqlite3ResolvePartIdxLabel(). > - * > - * The pPrior and regPrior parameters are used to implement a cache to > - * avoid unnecessary register loads. If pPrior is not NULL, then it is > - * a pointer to a different index for which an index key has just been > - * computed into register regPrior. If the current pIdx index is generating > - * its key into the same sequence of registers and if pPrior and pIdx share > - * a column in common, then the register corresponding to that column already > - * holds the correct value and the loading of that register is skipped. > - * This optimization is helpful when doing a DELETE or an INTEGRITY_CHECK > - * on a table with multiple indices, and especially with > - * the PRIMARY KEY columns of the index. > - */ > int > -sqlite3GenerateIndexKey(Parse * pParse, /* Parsing context */ > - Index * pIdx, /* The index for which to generate a key */ > - int iDataCur, /* Cursor number from which to take column data */ > - int regOut, /* Put the new key into this register if not 0 */ > - int *piPartIdxLabel, /* OUT: Jump to this label to skip partial index */ > - Index * pPrior, /* Previously generated index key */ > - int regPrior) /* Register holding previous generated key */ > +sql_generate_index_key(struct Parse *parse, struct Index *index, int cursor, > + int reg_out, int *part_idx_label, struct Index *prev, > + int reg_prev) > { > - Vdbe *v = pParse->pVdbe; > - int j; > - int regBase; > - int nCol; > - > - if (piPartIdxLabel) { > - if (pIdx->pPartIdxWhere) { > - *piPartIdxLabel = sqlite3VdbeMakeLabel(v); > - pParse->iSelfTab = iDataCur; > - sqlite3ExprCachePush(pParse); > - sqlite3ExprIfFalseDup(pParse, pIdx->pPartIdxWhere, > - *piPartIdxLabel, > + struct Vdbe *v = parse->pVdbe; > + > + if (part_idx_label != NULL) { > + if (index->pPartIdxWhere != NULL) { > + *part_idx_label = sqlite3VdbeMakeLabel(v); > + parse->iSelfTab = cursor; > + sqlite3ExprCachePush(parse); > + sqlite3ExprIfFalseDup(parse, index->pPartIdxWhere, > + *part_idx_label, > SQLITE_JUMPIFNULL); > } else { > - *piPartIdxLabel = 0; > + *part_idx_label = 0; > } > } > - nCol = index_column_count(pIdx); > - regBase = sqlite3GetTempRange(pParse, nCol); > - if (pPrior && (regBase != regPrior || pPrior->pPartIdxWhere)) > - pPrior = 0; > - for (j = 0; j < nCol; j++) { > - if (pPrior && pPrior->aiColumn[j] == pIdx->aiColumn[j] > - && pPrior->aiColumn[j] != XN_EXPR) { > - /* This column was already computed by the previous index */ > + int col_cnt = index_column_count(index); > + int reg_base = sqlite3GetTempRange(parse, col_cnt); > + if (prev != NULL && (reg_base != reg_prev || > + prev->pPartIdxWhere != NULL)) > + prev = NULL; > + for (int j = 0; j < col_cnt; j++) { > + if (prev != NULL && prev->aiColumn[j] == index->aiColumn[j] > + && prev->aiColumn[j] != XN_EXPR) { > + /* > + * This column was already computed by the > + * previous index. > + */ > continue; > } > - sqlite3ExprCodeLoadIndexColumn(pParse, pIdx, iDataCur, j, > - regBase + j); > - /* If the column affinity is REAL but the number is an integer, then it > - * might be stored in the table as an integer (using a compact > - * representation) then converted to REAL by an OP_RealAffinity opcode. > - * But we are getting ready to store this value back into an index, where > - * it should be converted by to INTEGER again. So omit the OP_RealAffinity > - * opcode if it is present > + sqlite3ExprCodeLoadIndexColumn(parse, index, cursor, j, > + reg_base + j); > + /* > + * If the column affinity is REAL but the number > + * is an integer, then it might be stored in the > + * table as an integer (using a compact > + * representation) then converted to REAL by an > + * OP_RealAffinity opcode. But we are getting > + * ready to store this value back into an index, > + * where it should be converted by to INTEGER > + * again. So omit the OP_RealAffinity opcode if > + * it is present > */ > sqlite3VdbeDeletePriorOpcode(v, OP_RealAffinity); > } > - if (regOut) { > - sqlite3VdbeAddOp3(v, OP_MakeRecord, regBase, nCol, regOut); > - } > - sqlite3ReleaseTempRange(pParse, regBase, nCol); > - return regBase; > + if (reg_out != 0) > + sqlite3VdbeAddOp3(v, OP_MakeRecord, reg_base, col_cnt, reg_out); > + > + sqlite3ReleaseTempRange(parse, reg_base, col_cnt); > + return reg_base; > } > > -/* > - * If a prior call to sqlite3GenerateIndexKey() generated a jump-over label > - * because it was a partial index, then this routine should be called to > - * resolve that label. > - */ > void > -sqlite3ResolvePartIdxLabel(Parse * pParse, int iLabel) > +sql_resolve_part_idx_label(struct Parse *parse, int label) > { > - if (iLabel) { > - sqlite3VdbeResolveLabel(pParse->pVdbe, iLabel); > - sqlite3ExprCachePop(pParse); > + if (label != 0) { > + sqlite3VdbeResolveLabel(parse->pVdbe, label); > + sqlite3ExprCachePop(parse); > } > } > diff --git a/src/box/sql/fkey.c b/src/box/sql/fkey.c > index 60b4786..a21dd2d 100644 > --- a/src/box/sql/fkey.c > +++ b/src/box/sql/fkey.c > @@ -784,7 +784,8 @@ sqlite3FkDropTable(Parse * pParse, SrcList * pName, Table * pTab) > pParse->disableTriggers = 1; > /* Staring new transaction before DELETE FROM */ > sqlite3VdbeAddOp0(v, OP_TTransaction); > - sqlite3DeleteFrom(pParse, sqlite3SrcListDup(db, pName, 0), 0); > + sql_table_delete_from(pParse, sqlite3SrcListDup(db, pName, 0), > + NULL); > pParse->disableTriggers = 0; > > /* If the DELETE has generated immediate foreign key constraint > diff --git a/src/box/sql/insert.c b/src/box/sql/insert.c > index 54a7e4a..97d18ae 100644 > --- a/src/box/sql/insert.c > +++ b/src/box/sql/insert.c > @@ -379,7 +379,7 @@ sqlite3Insert(Parse * pParse, /* Parser context */ > zTab = pTabList->a[0].zName; > if (NEVER(zTab == 0)) > goto insert_cleanup; > - pTab = sqlite3SrcListLookup(pParse, pTabList); > + pTab = sql_list_lookup_table(pParse, pTabList); > if (pTab == 0) { > goto insert_cleanup; > } > @@ -406,13 +406,13 @@ 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 (sqlite3ViewGetColumnNames(pParse, pTab)) { > + if (sqlite3ViewGetColumnNames(pParse, pTab)) > goto insert_cleanup; > - } > > - /* Cannot insert into a read-only table. > - */ > - if (sqlite3IsReadOnly(pParse, pTab, tmask)) { > + /* Cannot insert into a read-only table. */ > + if (isView && tmask == 0) { > + sqlite3ErrorMsg(pParse, "cannot modify %s because it is a view", > + pTab->zName); > goto insert_cleanup; > } > > @@ -1510,13 +1510,13 @@ sqlite3GenerateConstraintChecks(Parse * pParse, /* The parser context */ > TK_DELETE, 0, > 0); > } > - sqlite3GenerateRowDelete(pParse, pTab, pTrigger, > - iDataCur, iIdxCur, > - regR, nPkField, 0, > - ON_CONFLICT_ACTION_REPLACE, > - (pIdx == > - pPk ? ONEPASS_SINGLE : > - ONEPASS_OFF), -1); > + sql_generate_row_delete(pParse, pTab, pTrigger, > + iDataCur, > + regR, nPkField, 0, > + ON_CONFLICT_ACTION_REPLACE, > + (pIdx == > + pPk ? ONEPASS_SINGLE : > + ONEPASS_OFF), -1); > seenReplace = 1; > break; > } > diff --git a/src/box/sql/parse.y b/src/box/sql/parse.y > index 872647d..259243d 100644 > --- a/src/box/sql/parse.y > +++ b/src/box/sql/parse.y > @@ -719,28 +719,14 @@ limit_opt(A) ::= LIMIT expr(X) COMMA expr(Y). > > /////////////////////////// The DELETE statement ///////////////////////////// > // > -%ifdef SQLITE_ENABLE_UPDATE_DELETE_LIMIT > -cmd ::= with(C) DELETE FROM fullname(X) indexed_opt(I) where_opt(W) > - orderby_opt(O) limit_opt(L). { > - sqlite3WithPush(pParse, C, 1); > - sqlite3SrcListIndexedBy(pParse, X, &I); > - W = sqlite3LimitWhere(pParse, X, W, O, L.pLimit, L.pOffset, "DELETE"); > - sqlSubProgramsRemaining = SQL_MAX_COMPILING_TRIGGERS; > - /* Instruct SQL to initate Tarantool's transaction. */ > - pParse->initiateTTrans = true; > - sqlite3DeleteFrom(pParse,X,W); > -} > -%endif > -%ifndef SQLITE_ENABLE_UPDATE_DELETE_LIMIT > cmd ::= with(C) DELETE FROM fullname(X) indexed_opt(I) where_opt(W). { > sqlite3WithPush(pParse, C, 1); > sqlite3SrcListIndexedBy(pParse, X, &I); > sqlSubProgramsRemaining = SQL_MAX_COMPILING_TRIGGERS; > /* Instruct SQL to initate Tarantool's transaction. */ > pParse->initiateTTrans = true; > - sqlite3DeleteFrom(pParse,X,W); > + sql_table_delete_from(pParse,X,W); > } > -%endif > > %type where_opt {Expr*} > %destructor where_opt {sql_expr_free(pParse->db, $$, false);} > diff --git a/src/box/sql/sqliteInt.h b/src/box/sql/sqliteInt.h > index e056d63..8c6df2e 100644 > --- a/src/box/sql/sqliteInt.h > +++ b/src/box/sql/sqliteInt.h > @@ -1994,7 +1994,6 @@ sql_space_tuple_log_count(struct Table *tab); > /* > * Allowed values for Table.tabFlags. > */ > -#define TF_Readonly 0x01 /* Read-only system table */ > #define TF_Ephemeral 0x02 /* An ephemeral table */ > #define TF_HasPrimaryKey 0x04 /* Table has a primary key */ > #define TF_Autoincrement 0x08 /* Integer primary key is autoincrement */ > @@ -3671,15 +3670,46 @@ int sqlite3Select(Parse *, Select *, SelectDest *); > Select *sqlite3SelectNew(Parse *, ExprList *, SrcList *, Expr *, ExprList *, > Expr *, ExprList *, u32, Expr *, Expr *); > void sqlite3SelectDelete(sqlite3 *, Select *); > -Table *sqlite3SrcListLookup(Parse *, SrcList *); > -int sqlite3IsReadOnly(Parse *, Table *, int); > + > +/** > + * While a SrcList can in general represent multiple tables and > + * subqueries (as in the FROM clause of a SELECT statement) in > + * this case it contains the name of a single table, as one might > + * find in an INSERT, DELETE, or UPDATE statement. Look up that > + * table in the symbol table and return a pointer. Set an error > + * message and return NULL if the table name is not found or if > + * any other error occurs. > + * > + * The following fields are initialized appropriate in src_list: > + * > + * src_list->a[0].pTab Pointer to the Table object. > + * src_list->a[0].pIndex Pointer to the INDEXED BY index, > + * if there is one. > + * > + * @param parse Parsing context. > + * @param src_list List containing single table element. > + * @retval Table object if found, NULL oterwise. > + */ > +struct Table * > +sql_list_lookup_table(struct Parse *parse, struct SrcList *src_list); > + > void sqlite3OpenTable(Parse *, int iCur, Table *, int); > -#if defined(SQLITE_ENABLE_UPDATE_DELETE_LIMIT) > -Expr *sqlite3LimitWhere(Parse *, SrcList *, Expr *, ExprList *, Expr *, Expr *, > - char *); > -#endif > -void sqlite3DeleteFrom(Parse *, SrcList *, Expr *); > -void sqlite3DeleteByKey(Parse *, char *, const char **, Expr **, int); > +/** > + * Generate code for a DELETE FROM statement. > + * > + * DELETE FROM table_wxyz WHERE a<5 AND b NOT NULL; > + * \________/ \________________/ > + * tab_list where > + * > + * @param parse Parsing context. > + * @param tab_list List of single element which table from which > + * deletetion if performed. > + * @param where The WHERE clause. May be NULL. > + */ > +void > +sql_table_delete_from(struct Parse *parse, struct SrcList *tab_list, > + struct Expr *where); > + > void sqlite3Update(Parse *, SrcList *, ExprList *, Expr *, > enum on_conflict_action); > WhereInfo *sqlite3WhereBegin(Parse *, SrcList *, Expr *, ExprList *, ExprList *, > @@ -3782,11 +3812,118 @@ int sqlite3ExprContainsSubquery(Expr *); > int sqlite3ExprIsInteger(Expr *, int *); > int sqlite3ExprCanBeNull(const Expr *); > int sqlite3ExprNeedsNoAffinityChange(const Expr *, char); > -void sqlite3GenerateRowDelete(Parse *, Table *, Trigger *, int, int, int, i16, > - u8, enum on_conflict_action, u8, int); > -void sqlite3GenerateRowIndexDelete(Parse *, Table *, int, int); > -int sqlite3GenerateIndexKey(Parse *, Index *, int, int, int *, Index *, int); > -void sqlite3ResolvePartIdxLabel(Parse *, int); > + > +/** > + * This routine generates VDBE code that causes a single row of a > + * single table to be deleted. Both the original table entry and > + * all indices are removed. > + * > + * Preconditions: > + * > + * 1. cursor is an open cursor on the btree that is the > + * canonical data store for the table. (This will be the > + * PRIMARY KEY index) > + * > + * 2. The primary key for the row to be deleted must be stored > + * in a sequence of npk memory cells starting at reg_pk. If > + * npk==0 that means that a search record formed from > + * OP_MakeRecord is contained in the single memory location > + * reg_pk. > + * > + * mode: > + * Parameter mode may be passed either ONEPASS_OFF (0), > + * ONEPASS_SINGLE, or ONEPASS_MULTI. If mode is not > + * ONEPASS_OFF, then the cursor already points to the row to > + * delete. If mode is ONEPASS_OFF then this function must seek > + * cursor to the entry identified by reg_pk and npk before > + * reading from it. > + * > + * If mode is ONEPASS_MULTI, then this call is being made as > + * part of a ONEPASS delete that affects multiple rows. In this > + * case, if idx_noseek is a valid cursor number (>=0), then its > + * position should be preserved following the delete operation. > + * Or, if idx_noseek is not a valid cursor number, the position > + * of cursor should be preserved instead. > + * > + * @param parse Parsing context. > + * @param table Table containing the row to be deleted. > + * @param trigger_list List of triggers to (potentially) fire. > + * @param cursor Cursor from which column data is extracted/ > + * @param reg_pk First memory cell containing the PRIMARY KEY. > + * @param npk umber of PRIMARY KEY memory cells. > + * @param need_update_count. If non-zero, increment the row change > + * counter. > + * @param onconf Default ON CONFLICT policy for triggers. > + * @param mode ONEPASS_OFF, _SINGLE, or _MULTI. See above. > + * @param idx_noseek If it is a valid cursor number (>=0), > + * then it identifies an index cursor that already points > + * to the index entry to be deleted. > + */ > +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, > + enum on_conflict_action onconf, u8 mode, > + int idx_noseek); > + > +/** > + * Generate code that will assemble an index key and stores it in > + * register reg_out. The key will be for index which is an > + * index on table. cursor is the index of a cursor open on the > + * table table and pointing to the entry that needs indexing. > + * cursor must be the cursor of the PRIMARY KEY index. > + * > + * Return a register number which is the first in a block of > + * registers that holds the elements of the index key. The > + * block of registers has already been deallocated by the time > + * this routine returns. > + * > + * If part_idx_label is not NULL, fill it in with a label and > + * jump to that label if index is a partial index that should be > + * skipped. The label should be resolved using > + * sql_resolve_part_idx_label(). A partial index should be skipped > + * if its WHERE clause evaluates to false or null. If index is > + * not a partial index, *part_idx_label will be set to zero which > + * is an empty label that is ignored by sql_resolve_part_idx_label(). > + * > + * The prev and reg_prev parameters are used to implement a > + * cache to avoid unnecessary register loads. If prev is not > + * NULL, then it is a pointer to a different index for which an > + * index key has just been computed into register reg_prev. If the > + * current index is generating its key into the same > + * sequence of registers and if prev and index share a column in > + * common, then the register corresponding to that column already > + * holds the correct value and the loading of that register is > + * skipped. This optimization is helpful when doing a DELETE or > + * an INTEGRITY_CHECK on a table with multiple indices, and > + * especially with the PRIMARY KEY columns of the index. > + * > + * @param parse Parsing context. > + * @param index The index for which to generate a key. > + * @param cursor Cursor number from which to take column data. > + * @param reg_out Put the new key into this register if not NULL. > + * @param[out] part_idx_label Jump to this label to skip partial > + * index. > + * @param prev Previously generated index key > + * @param reg_prev Register holding previous generated key. > + * @retval Register containing new record > + */ > +int > +sql_generate_index_key(struct Parse *parse, struct Index *index, int cursor, > + int reg_out, int *part_idx_label, struct Index *prev, > + int reg_prev); > + > +/** > + * If a prior call to sql_generate_index_key() generated a > + * jump-over label because it was a partial index, then this > + * routine should be called to resolve that label. > + * > + * @param parse Parsing context. > + * @param label Label to resolve. > + */ > +void > +sql_resolve_part_idx_label(struct Parse *parse, int label); > + > void sqlite3GenerateConstraintChecks(Parse *, Table *, int *, int, int, int, > int, u8, struct on_conflict *, int, int *, > int *); > @@ -3831,9 +3968,20 @@ int sqlite3SafetyCheckOk(sqlite3 *); > int sqlite3SafetyCheckSickOrOk(sqlite3 *); > void sqlite3ChangeCookie(Parse *); > > -#if !defined(SQLITE_OMIT_VIEW) && !defined(SQLITE_OMIT_TRIGGER) > -void sqlite3MaterializeView(Parse *, Table *, Expr *, int); > -#endif > +/** > + * Evaluate a view and store its result in an ephemeral table. > + * The where argument is an optional WHERE clause that restricts > + * the set of rows in the view that are to be added to the > + * ephemeral table. > + * > + * @param parse Parsing context. > + * @param name View name. > + * @param where Option WHERE clause to be added. > + * @param cursor Cursor number for ephemeral table. > + */ > +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 *, > diff --git a/src/box/sql/trigger.c b/src/box/sql/trigger.c > index 28c56db..e6d9925 100644 > --- a/src/box/sql/trigger.c > +++ b/src/box/sql/trigger.c > @@ -111,7 +111,7 @@ sqlite3BeginTrigger(Parse * pParse, /* The parse context of the CREATE TRIGGER s > if (sqlite3FixSrcList(&sFix, pTableName)) { > goto trigger_cleanup; > } > - pTab = sqlite3SrcListLookup(pParse, pTableName); > + pTab = sql_list_lookup_table(pParse, pTableName); > if (!pTab) { > goto trigger_cleanup; > } > @@ -744,11 +744,11 @@ codeTriggerProgram(Parse * pParse, /* The parser context */ > break; > } > case TK_DELETE:{ > - sqlite3DeleteFrom(pParse, > - targetSrcList(pParse, pStep), > - sqlite3ExprDup(db, > - pStep->pWhere, > - 0) > + sql_table_delete_from(pParse, > + targetSrcList(pParse, pStep), > + sqlite3ExprDup(db, > + pStep->pWhere, > + 0) > ); > break; > } > diff --git a/src/box/sql/update.c b/src/box/sql/update.c > index 204adc2..c521a3b 100644 > --- a/src/box/sql/update.c > +++ b/src/box/sql/update.c > @@ -163,7 +163,7 @@ sqlite3Update(Parse * pParse, /* The parser context */ > > /* Locate the table which we want to update. > */ > - pTab = sqlite3SrcListLookup(pParse, pTabList); > + pTab = sql_list_lookup_table(pParse, pTabList); > if (pTab == 0) > goto update_cleanup; > > @@ -187,7 +187,9 @@ sqlite3Update(Parse * pParse, /* The parser context */ > if (sqlite3ViewGetColumnNames(pParse, pTab)) { > goto update_cleanup; > } > - if (sqlite3IsReadOnly(pParse, pTab, tmask)) { > + if (isView && tmask == 0) { > + sqlite3ErrorMsg(pParse, "cannot modify %s because it is a view", > + pTab->zName); > goto update_cleanup; > } > > @@ -324,7 +326,7 @@ sqlite3Update(Parse * pParse, /* The parser context */ > */ > #if !defined(SQLITE_OMIT_VIEW) && !defined(SQLITE_OMIT_TRIGGER) > if (isView) { > - sqlite3MaterializeView(pParse, pTab, pWhere, iDataCur); > + sql_materialize_view(pParse, pTab->zName, pWhere, iDataCur); > /* Number of columns from SELECT plus ID.*/ > nKey = pTab->nCol + 1; > } > @@ -603,7 +605,6 @@ sqlite3Update(Parse * pParse, /* The parser context */ > nKey); > VdbeCoverageNeverTaken(v); > } > - sqlite3GenerateRowIndexDelete(pParse, pTab, iDataCur, iIdxCur); > > /* If changing the PK value, or if there are foreign key constraints > * to process, delete the old record. Otherwise, add a noop OP_Delete > diff --git a/src/box/sql/vdbeaux.c b/src/box/sql/vdbeaux.c > index 1304149..7ac14e2 100644 > --- a/src/box/sql/vdbeaux.c > +++ b/src/box/sql/vdbeaux.c > @@ -1124,7 +1124,7 @@ sqlite3VdbeChangeP4(Vdbe * p, int addr, const char *zP4, int n) > } if (n == P4_BOOL) { > pOp->p4.b = *(bool*)zP4; > pOp->p4type = P4_BOOL; > - } else if (zP4 != 0) { > + } else { > assert(n < 0); > pOp->p4.p = (void *)zP4; > pOp->p4type = (signed char)n; > @@ -1561,7 +1561,10 @@ displayP4(Op * pOp, char *zTemp, int nTemp) > #endif > case P4_COLLSEQ:{ > struct coll *pColl = pOp->p4.pColl; > - sqlite3XPrintf(&x, "(%.20s)", pColl->name); > + if (pColl != NULL) > + sqlite3XPrintf(&x, "(%.20s)", pColl->name); > + else > + sqlite3XPrintf(&x, "(binary)"); > break; > } > case P4_FUNCDEF:{ > diff --git a/src/box/sql/where.c b/src/box/sql/where.c > index b497a5b..3519f34 100644 > --- a/src/box/sql/where.c > +++ b/src/box/sql/where.c > @@ -840,11 +840,11 @@ constructAutomaticIndex(Parse * pParse, /* The parsing context */ > } > regRecord = sqlite3GetTempReg(pParse); > regBase = > - sqlite3GenerateIndexKey(pParse, pIdx, pLevel->iTabCur, regRecord, 0, > - 0, 0); > + sql_generate_index_key(pParse, pIdx, pLevel->iTabCur, regRecord, 0, > + 0, 0); > sqlite3VdbeAddOp2(v, OP_IdxInsert, pLevel->iIdxCur, regRecord); > if (pPartial) > - sqlite3VdbeResolveLabel(v, iContinue); > + sql_resolve_part_idx_label(v, iContinue); > if (pTabItem->fg.viaCoroutine) { > sqlite3VdbeChangeP2(v, addrCounter, regBase + n); > translateColumnToCopy(v, addrTop, pLevel->iTabCur, >