From: Kirill Yukhin <kyukhin@tarantool.org> To: v.shpilevoy@tarantool.org Cc: tarantool-patches@freelists.org, Kirill Yukhin <kyukhin@tarantool.org> Subject: [tarantool-patches] [PATCH 2/2] sql: refactor delete routines Date: Wed, 16 May 2018 18:24:54 +0300 [thread overview] Message-ID: <36b38ab497e52e9552d07bc98e11ec52697ce1e7.1526483543.git.kyukhin@tarantool.org> (raw) In-Reply-To: <cover.1526483543.git.kyukhin@tarantool.org> In-Reply-To: <cover.1526483543.git.kyukhin@tarantool.org> 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 --- src/box/sql/build.c | 6 +- src/box/sql/delete.c | 1046 ++++++++++++++++++----------------------------- src/box/sql/fkey.c | 3 +- src/box/sql/insert.c | 26 +- src/box/sql/parse.y | 2 +- src/box/sql/sqliteInt.h | 202 ++++++++- src/box/sql/trigger.c | 12 +- src/box/sql/update.c | 9 +- src/box/sql/vdbe.c | 6 +- src/box/sql/vdbeaux.c | 7 +- 10 files changed, 619 insertions(+), 700 deletions(-) diff --git a/src/box/sql/build.c b/src/box/sql/build.c index c96d351..e3aadab 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 3455f52..ea4f36a 100644 --- a/src/box/sql/delete.c +++ b/src/box/sql/delete.c @@ -29,103 +29,47 @@ * 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 Table * +sql_list_lookup_table(struct Parse *parse, SrcList *src_list) { - 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; + 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; + return table; } -/* - * 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) -{ - /* - * 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; -} - -#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 */ +sql_materialize_view(struct Parse *parse, const char *name, struct Expr *where, + int cursor) { - 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); + 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); } - pSel = sqlite3SelectNew(pParse, 0, pFrom, pWhere, 0, 0, 0, - 0, 0, 0); - sqlite3SelectDestInit(&dest, SRT_EphemTab, iCur); - sqlite3Select(pParse, pSel, &dest); - sqlite3SelectDelete(db, pSel); + 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_OMIT_VIEW) && !defined(SQLITE_OMIT_TRIGGER) */ #if defined(SQLITE_ENABLE_UPDATE_DELETE_LIMIT) /* @@ -217,405 +161,338 @@ sqlite3LimitWhere(Parse * pParse, /* The parser context */ } #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_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) - sqlite3VdbeCountChanges(v); - sql_set_multi_write(pParse, true); - /* 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; - } -#endif + if (parse->nested == 0) + sqlite3VdbeCountChanges(v); + sql_set_multi_write(parse, true); - /* Resolve the column names in the WHERE clause. + /* If we are trying to delete from a view, realize that + * view into an ephemeral table. */ - memset(&sNC, 0, sizeof(sNC)); - sNC.pParse = pParse; - sNC.pSrcList = pTabList; - if (sqlite3ResolveExprNames(&sNC, pWhere)) { + if (is_view) + sql_materialize_view(parse, space->def->name, where, tab_cursor); + + /* 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). */ - if (isView) { - nPk = pTab->nCol; - iPk = pParse->nMem + 1; - pParse->nMem += nPk; - iEphCur = pParse->nTab++; - struct key_def *def = key_def_new(nPk); - if (def == NULL) { - sqlite3OomFault(db); - goto delete_from_cleanup; - } - addrEphOpen = - sqlite3VdbeAddOp4(v, OP_OpenTEphemeral, iEphCur, - nPk, 0, (char*)def, P4_KEYDEF); + 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. + */ + 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; + sqlite3VdbeAddOp4(v, OP_OpenTEphemeral, + eph_cursor, pk_len, 0, + (char*)NULL, P4_KEYDEF); } 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; + sqlite3VdbeAddOp4Ptr(v, OP_LoadPtr, 0, space_ptr_reg, 0, + (void *) space); + 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; + sqlite3SrcListDelete(db, tab_list); + sql_expr_free(db, where, false); } -/* 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) +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) goto error; /* Dummy init of INDEXED BY clause. */ - Token t = { NULL, 0, false }; - sqlite3SrcListIndexedBy(pParse, src, &t); + struct Token t = {NULL, 0, false}; + sqlite3SrcListIndexedBy(parse, src, &t); - for (int i = 0; i < nPairs; ++i) { - Expr *col_expr = sqlite3Expr(pParse->db, TK_ID, columns[i]); + struct Expr *where = NULL; + assert(pairs_count > 0); + for (int i = 0; i < pairs_count; ++i) { + struct Expr *col_expr = sqlite3Expr(parse->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]); + struct Expr *eq_expr = + sqlite3PExpr(parse, 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. @@ -626,311 +503,175 @@ sqlite3DeleteByKey(Parse *pParse, char *zTab, const char **columns, if (i == 0) { where = eq_expr; } else { - where = sqlite3ExprAnd(pParse->db, where, eq_expr); + where = sqlite3ExprAnd(parse->db, where, eq_expr); if (where == NULL) goto error; } } /* DeleteFrom frees the src and exprs in case of error. */ - sqlite3DeleteFrom(pParse, src, where); + sql_table_delete_from(parse, 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); + sql_expr_free(parse->db, where, false); + for (int i = 0; i < pairs_count; ++i) + sql_expr_free(parse->db, values[i], 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 is_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, + (is_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, + Vdbe *v = parse->pVdbe; + + if (part_idx_label != NULL) { + if (index->pPartIdxWhere) { + *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) { + int col_cnt = index_column_count(index); + int reg_base = sqlite3GetTempRange(parse, col_cnt); + if (prev != NULL && (reg_base != reg_prev || prev->pPartIdxWhere)) + 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); + 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. @@ -940,23 +681,18 @@ sqlite3GenerateIndexKey(Parse * pParse, /* Parsing context */ */ sqlite3VdbeDeletePriorOpcode(v, OP_RealAffinity); } - if (regOut) { - sqlite3VdbeAddOp3(v, OP_MakeRecord, regBase, nCol, regOut); - } - sqlite3ReleaseTempRange(pParse, regBase, nCol); - return regBase; + if (reg_out) + 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 1ad7469..2d49296 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; } @@ -1511,13 +1511,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..9343c0e 100644 --- a/src/box/sql/parse.y +++ b/src/box/sql/parse.y @@ -738,7 +738,7 @@ cmd ::= with(C) DELETE FROM fullname(X) indexed_opt(I) where_opt(W). { 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 diff --git a/src/box/sql/sqliteInt.h b/src/box/sql/sqliteInt.h index e056d63..eb12ebd 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,73 @@ 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. + * pSrc->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, SrcList *src_list); + int sqlite3IsReadOnly(Parse *, Table *, int); 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; + * \________/ \________________/ + * pTabList pWhere + * + * @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); + +/** + * Generate VDBE code for + * DELETE FROM <t_name> 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 parse Parser context. + * @param t_name Table name. + * @param columns Column names array. + * @param values Column values array. + * @param pairs_count Length of @columns and @values. + * + * In case of error the @values elements are deleted. + */ +void +sql_delete_by_where(struct Parse *parse, char *t_name, const char **columns, + struct Expr **values, int pairs_count); + void sqlite3Update(Parse *, SrcList *, ExprList *, Expr *, enum on_conflict_action); WhereInfo *sqlite3WhereBegin(Parse *, SrcList *, Expr *, ExprList *, ExprList *, @@ -3782,11 +3839,121 @@ 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 iPk. If + * nPk==0 that means that a search record formed from + * OP_MakeRecord is contained in the single memory location + * iPk. + * + * mode: + * Parameter mode may be passed either ONEPASS_OFF (0), + * ONEPASS_SINGLE, or ONEPASS_MULTI. If eMode is not + * ONEPASS_OFF, then the cursor already points to the row to + * delete. If eMode is ONEPASS_OFF then this function must seek + * cursor to the entry identified by ipk 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 iIdxNoSeek is not a valid cursor number, the position + * of cursor should be preserved instead. + * + * idx_noseek: + * If idx_noseek is a valid cursor number (>=0), then it + * identifies an index cursor that already points to the index + * entry to be deleted. + * + * @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 is_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 Cursor number of cursor that does not need + seeking. + */ +void +sql_generate_row_delete(struct Parse *parse, struct Table *table, + struct Trigger *trigger_list, int cursor, int ipk, + short npk, bool is_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 + * 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 pIdx 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, *piPartIdxLabel will be set to zero which + * is an empty label that is ignored by sql_resolve_part_idx_label(). + * + * The pPrior and regPrior 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 pIdx 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 sqlite3GenerateIndexKey() 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 +3998,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 de1349b..60cb229 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/vdbe.c b/src/box/sql/vdbe.c index b8d4c1a..2ad6f3e 100644 --- a/src/box/sql/vdbe.c +++ b/src/box/sql/vdbe.c @@ -3236,20 +3236,20 @@ case OP_OpenTEphemeral: { BtCursor *pBtCur; assert(pOp->p1 >= 0); assert(pOp->p2 > 0); - assert(pOp->p4type != P4_KEYDEF || pOp->p4.key_def != NULL); + assert(pOp->p4type == P4_KEYDEF); pCx = allocateCursor(p, pOp->p1, pOp->p2, CURTYPE_TARANTOOL); if (pCx == 0) goto no_mem; pCx->nullRow = 1; - pCx->key_def = pOp->p4.key_def; pBtCur = pCx->uc.pCursor; /* Ephemeral spaces don't have space_id */ pBtCur->eState = CURSOR_INVALID; pBtCur->curFlags = BTCF_TEphemCursor; rc = tarantoolSqlite3EphemeralCreate(pCx->uc.pCursor, pOp->p2, - pCx->key_def); + pOp->p4.key_def); + pCx->key_def = pCx->uc.pCursor->index->def->key_def; if (rc) goto abort_due_to_error; break; } diff --git a/src/box/sql/vdbeaux.c b/src/box/sql/vdbeaux.c index e67fcae..4a8a9d2 100644 --- a/src/box/sql/vdbeaux.c +++ b/src/box/sql/vdbeaux.c @@ -1134,7 +1134,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; @@ -1571,7 +1571,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:{ -- 2.16.2
next prev parent reply other threads:[~2018-05-16 15:25 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 ` Kirill Yukhin [this message] 2018-05-16 16:29 ` [tarantool-patches] Re: [PATCH 2/2] sql: refactor delete routines 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 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=36b38ab497e52e9552d07bc98e11ec52697ce1e7.1526483543.git.kyukhin@tarantool.org \ --to=kyukhin@tarantool.org \ --cc=tarantool-patches@freelists.org \ --cc=v.shpilevoy@tarantool.org \ --subject='Re: [tarantool-patches] [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