From: Vladislav Shpilevoy <v.shpilevoy@tarantool.org>
To: Kirill Yukhin <kyukhin@tarantool.org>
Cc: tarantool-patches@freelists.org
Subject: [tarantool-patches] Re: [PATCH 2/2] sql: refactor delete routines
Date: Fri, 18 May 2018 13:33:11 +0300 [thread overview]
Message-ID: <07323a53-3c95-3b1f-1973-1a15bdde2c0f@tarantool.org> (raw)
In-Reply-To: <20180518065610.gh4icdq6zlikmidx@tarantool.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 <kyukhin@tarantool.org>
> 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 <pTab.z> WHERE
> - * <columns[0]> = <values[0]>
> - * AND ...
> - * AND <columns[nPairs - 1]> = <values[nPairs - 1]>;
> - *
> - * 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 <tbl> */
> 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,
>
next prev parent reply other threads:[~2018-05-18 10:33 UTC|newest]
Thread overview: 17+ messages / expand[flat|nested] mbox.gz Atom feed top
2018-05-16 15:24 [tarantool-patches] [PATCH 0/2] sql: refactor DELETE STMT translation Kirill Yukhin
2018-05-16 15:24 ` [tarantool-patches] [PATCH 1/2] sql: allow key_def to be NULL for ephemeral create Kirill Yukhin
2018-05-17 15:49 ` [tarantool-patches] " Kirill Yukhin
2018-05-17 16:47 ` Vladislav Shpilevoy
2018-05-18 6:57 ` Kirill Yukhin
2018-05-18 10:33 ` Vladislav Shpilevoy
2018-05-18 10:48 ` Kirill Yukhin
2018-05-18 10:50 ` Vladislav Shpilevoy
2018-05-16 15:24 ` [tarantool-patches] [PATCH 2/2] sql: refactor delete routines Kirill Yukhin
2018-05-16 16:29 ` [tarantool-patches] " Kirill Yukhin
2018-05-17 14:23 ` Vladislav Shpilevoy
2018-05-17 15:48 ` Kirill Yukhin
2018-05-17 16:47 ` Vladislav Shpilevoy
2018-05-18 6:56 ` Kirill Yukhin
2018-05-18 10:33 ` Vladislav Shpilevoy [this message]
2018-05-17 15:18 ` Vladislav Shpilevoy
2018-05-18 11:01 ` [tarantool-patches] Re: [PATCH 0/2] sql: refactor DELETE STMT translation Kirill Yukhin
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=07323a53-3c95-3b1f-1973-1a15bdde2c0f@tarantool.org \
--to=v.shpilevoy@tarantool.org \
--cc=kyukhin@tarantool.org \
--cc=tarantool-patches@freelists.org \
--subject='[tarantool-patches] Re: [PATCH 2/2] sql: refactor delete routines' \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox