[tarantool-patches] Re: [PATCH 2/2] sql: refactor delete routines
Kirill Yukhin
kyukhin at tarantool.org
Fri May 18 09:56:10 MSK 2018
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 at 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,
More information about the Tarantool-patches
mailing list