diff --git a/bdb/bdb_api.h b/bdb/bdb_api.h index b4b0f2aa13..bd636b7cd4 100644 --- a/bdb/bdb_api.h +++ b/bdb/bdb_api.h @@ -2498,8 +2498,17 @@ int bdb_queuedb_has_seq(bdb_state_type *); void dispatch_waiting_clients(void); struct sqlclntstate; -int release_locks_int(const char *trace, const char *func, int line, struct sqlclntstate *); -#define release_locks(trace) release_locks_int(trace, __func__, __LINE__, NULL) +typedef enum { + RLOCKS_REASON_SI_LOCKWAIT, /* page lock waiter on SI/serial session */ + RLOCKS_REASON_LOCKWAIT, /* page lock waiter on non-SI session */ + RLOCKS_REASON_RANDOM, /* random release (testing) */ + RLOCKS_REASON_LOCK_DESIRED, /* global BDB write lock desired */ + RLOCKS_REASON_EMIT_ROW, /* waiters at row emit */ + RLOCKS_REASON_LONG_REPWAIT, /* long rep wait at row emit */ + RLOCKS_REASON_SLOW_READER, /* slow reader */ +} rlocks_reason_t; +int release_locks_int(rlocks_reason_t reason, const char *func, int line, struct sqlclntstate *); +#define release_locks(reason) release_locks_int(reason, __func__, __LINE__, NULL) int bdb_keylen(bdb_state_type *bdb_state, int ixnum); diff --git a/bdb/bdb_osqlcur.c b/bdb/bdb_osqlcur.c index c1f62be051..2ea98abc11 100644 --- a/bdb/bdb_osqlcur.c +++ b/bdb/bdb_osqlcur.c @@ -463,11 +463,11 @@ int bdb_osql_update_shadows(bdb_cursor_ifn_t *pcur_ifn, bdb_osql_trn_t *trn, logmsg(LOGMSG_WARN, "%s: releasing locks while updating shadows\n", __func__); - rc = release_locks("update shadows"); + rc = release_locks(RLOCKS_REASON_SI_LOCKWAIT); released_locks = 1; } else if (gbl_sql_random_release_interval && !(rand() % gbl_sql_random_release_interval)) { - rc = release_locks("random release update shadows"); + rc = release_locks(RLOCKS_REASON_RANDOM); released_locks = 1; } if (rc != 0) { diff --git a/db/db_tunables.c b/db/db_tunables.c index 456a479070..65bdbb231c 100644 --- a/db/db_tunables.c +++ b/db/db_tunables.c @@ -309,6 +309,8 @@ extern int gbl_create_default_user; extern int gbl_allow_neg_column_size; extern int gbl_client_heartbeat_ms; extern int gbl_rep_wait_release_ms; +extern int gbl_debug_sleep_in_cursor_move; +extern int gbl_recover_deadlock_sync_dta; extern int gbl_rep_wait_core_ms; extern int gbl_random_get_curtran_failures; extern int gbl_txn_fop_noblock; diff --git a/db/db_tunables.h b/db/db_tunables.h index 4e1ac5bbe4..92dfe00086 100644 --- a/db/db_tunables.h +++ b/db/db_tunables.h @@ -1742,6 +1742,13 @@ REGISTER_TUNABLE("client_heartbeat_ms", TUNABLE_INTEGER, &gbl_client_heartbeat_ms, EXPERIMENTAL | INTERNAL, NULL, NULL, NULL, NULL); +REGISTER_TUNABLE("debug_sleep_in_cursor_move", "Sleep N ms on each cursor move (testing only). (Default: 0)", + TUNABLE_INTEGER, &gbl_debug_sleep_in_cursor_move, EXPERIMENTAL | INTERNAL, NULL, NULL, NULL, NULL); + +REGISTER_TUNABLE("recover_deadlock_sync_dta", + "Sync index/data cursors before lock release in recover_deadlock. (Default: 0)", TUNABLE_BOOLEAN, + &gbl_recover_deadlock_sync_dta, EXPERIMENTAL | INTERNAL, NULL, NULL, NULL, NULL); + REGISTER_TUNABLE("rep_release_wait_ms", "Release sql-locks if rep-thd is blocked for this many ms." " (Default: 60000)", diff --git a/db/sql.h b/db/sql.h index 2acd28f23d..1c7d86fd66 100644 --- a/db/sql.h +++ b/db/sql.h @@ -1286,6 +1286,7 @@ struct BtCursor { void *query_preparer_data; int permissions; /* permissions for read/write access to table */ + BtCursor *pCursorHintTableCursor; }; struct sql_hist { @@ -1494,6 +1495,7 @@ int sqlite3LockStmtTables(sqlite3_stmt *pStmt); int sqlite3UnlockStmtTablesRemotes(struct sqlclntstate *clnt); void sql_remote_schema_changed(struct sqlclntstate *clnt, sqlite3_stmt *pStmt); int release_locks_on_emit_row(struct sqlclntstate *clnt); +void sync_index_data_cursors(struct sql_thread *thd); void clearClientSideRow(struct sqlclntstate *clnt); struct temptable get_tbl_by_rootpg(const sqlite3 *, int); diff --git a/db/sqlglue.c b/db/sqlglue.c index c0a3bf86ee..32487eb29f 100644 --- a/db/sqlglue.c +++ b/db/sqlglue.c @@ -3248,6 +3248,54 @@ static inline int sqlite3VdbeCompareRecordPacked(KeyInfo *pKeyInfo, int k1len, } unsigned long long release_locks_on_si_lockwait_cnt = 0; +int gbl_debug_sleep_in_cursor_move = 0; /* ms to sleep on each cursor move (testing only) */ +int gbl_recover_deadlock_sync_dta = 0; /* sync index/data cursors before lock release */ + +/* + * Before releasing cursor locks, walk all open index cursors and sync each + * one's paired data cursor (set via BTREE_HINT_TABLECURSOR) to the index + * cursor's current genid if they differ. Must be called while locks are + * still held so the target row is guaranteed to exist at find time. + */ +void sync_index_data_cursors(struct sql_thread *thd) +{ + BtCursor *cur; + + Pthread_mutex_lock(&thd->lk); + if (!thd->bt) + goto done; + + LISTC_FOR_EACH(&thd->bt->cursors, cur, lnk) + { + BtCursor *dta = cur->pCursorHintTableCursor; + if (!dta || !cur->bdbcur || !dta->bdbcur) + continue; + if (cur->cursor_class != CURSORCLASS_INDEX) + continue; + unsigned long long idx_genid = cur->genid; + if (idx_genid == 0 || idx_genid == dta->genid) + continue; + /* data cursor hasn't caught up to the index cursor -- find it now. + * use bdbcur->find directly (not ddguard) to avoid re-entering + * recover_deadlock which also needs thd->lk */ + int bdberr; + int rc = dta->bdbcur->find(dta->bdbcur, &idx_genid, sizeof(idx_genid), 0, &bdberr); + if (rc != IX_FND) + continue; + int fndlen; + void *buf; + uint8_t ver; + dta->bdbcur->get_found_data(dta->bdbcur, &dta->rrn, &dta->genid, &fndlen, &buf, &ver); + vtag_to_ondisk(dta->db, buf, &fndlen, ver, dta->genid); + dta->ondisk_buf = buf; + dta->dtabuf = buf; + dta->dtabuflen = fndlen; + } + +done: + Pthread_mutex_unlock(&thd->lk); +} + /* Release pagelocks if the replicant is waiting on this sql thread */ static int cursor_move_postop(BtCursor *pCur) { @@ -3257,16 +3305,21 @@ static int cursor_move_postop(BtCursor *pCur) extern int gbl_locks_check_waiters; int rc = 0; - if (gbl_locks_check_waiters && gbl_sql_release_locks_on_si_lockwait && - (clnt->dbtran.mode == TRANLEVEL_SNAPISOL || - clnt->dbtran.mode == TRANLEVEL_SERIAL)) { + if (gbl_debug_sleep_in_cursor_move) + poll(NULL, 0, gbl_debug_sleep_in_cursor_move); + + if (gbl_locks_check_waiters) { extern int gbl_sql_random_release_interval; + int is_si = (clnt->dbtran.mode == TRANLEVEL_SNAPISOL || clnt->dbtran.mode == TRANLEVEL_SERIAL); + if (is_si && !gbl_sql_release_locks_on_si_lockwait) + return 0; + rlocks_reason_t reason = is_si ? RLOCKS_REASON_SI_LOCKWAIT : RLOCKS_REASON_LOCKWAIT; if (bdb_curtran_has_waiters(thedb->bdb_env, clnt->dbtran.cursor_tran)) { - rc = release_locks("replication is waiting on si-session"); + rc = release_locks(reason); release_locks_on_si_lockwait_cnt++; } else if (gbl_sql_random_release_interval && !(rand() % gbl_sql_random_release_interval)) { - rc = release_locks("random release cursor_move_postop"); + rc = release_locks(RLOCKS_REASON_RANDOM); release_locks_on_si_lockwait_cnt++; } } @@ -5986,6 +6039,17 @@ int sqlite3BtreeMovetoUnpacked(BtCursor *pCur, /* The cursor to be moved */ } } + /* If the cursor was pre-synced by sync_index_data_cursors before a + * lock release, the cursor is already positioned at the requested + * genid and the data is in the cursor's buffers. Skip the BDB seek + * to avoid a "Dta lookup lost the race" failure if the row was deleted + * during the lock-release window. */ + if (gbl_recover_deadlock_sync_dta && bias == OP_DeferredSeek && pCur->genid == (unsigned long long)intKey && + !pCur->eof) { + *pRes = 0; + goto done; + } + /* TODO: we already found the data record. find some way to map between * index/data cursors and don't do extra data fetches unless we * move the cursor */ @@ -11658,6 +11722,14 @@ const char *comdb2_get_sql(void) } int gbl_fdb_track_hints = 0; +static void sqlite3BtreeCursorHint_TableCursor(BtCursor *pCur, BtCursor *pTableCsr) +{ + assert(pCur->cursor_class == CURSORCLASS_INDEX); + assert(pTableCsr->cursor_class == CURSORCLASS_TABLE); + assert(pCur->db == pTableCsr->db); + pCur->pCursorHintTableCursor = pTableCsr; +} + static void sqlite3BtreeCursorHint_Range(BtCursor *pCur, const Expr *pExpr) { char *expr = "?no vdbe engine?"; @@ -11713,10 +11785,20 @@ void sqlite3BtreeCursorHint(BtCursor *pCur, int eHintType, ...) break; } + + case BTREE_HINT_TABLECURSOR: { + sqlite3BtreeCursorHint_TableCursor(pCur, va_arg(ap, BtCursor *)); + break; + } } va_end(ap); } +BtCursor *sqlite3BtreeCursorHintTblCsr(BtCursor *pCsr) +{ + return pCsr->pCursorHintTableCursor; +} + int fdb_packedsqlite_extract_genid(char *key, int *outlen, char *outbuf) { int hdroffset = 0; diff --git a/db/sqlinterfaces.c b/db/sqlinterfaces.c index d11c1aaa43..d114ff4041 100644 --- a/db/sqlinterfaces.c +++ b/db/sqlinterfaces.c @@ -2828,18 +2828,43 @@ static int check_thd_gen(struct sqlthdstate *thd, struct sqlclntstate *clnt, int return SQLITE_OK; } -int release_locks_int(const char *trace, const char *func, int line, struct sqlclntstate *clnt) +static const char *rlocks_reason_str(rlocks_reason_t reason) +{ + switch (reason) { + case RLOCKS_REASON_SI_LOCKWAIT: + return "replication is waiting on si-session"; + case RLOCKS_REASON_LOCKWAIT: + return "replication is waiting on session"; + case RLOCKS_REASON_RANDOM: + return "random release"; + case RLOCKS_REASON_LOCK_DESIRED: + return "release locks on emit-row for lock-desired"; + case RLOCKS_REASON_EMIT_ROW: + return "release locks on emit-row"; + case RLOCKS_REASON_LONG_REPWAIT: + return "long repwait at emit-row"; + case RLOCKS_REASON_SLOW_READER: + return "slow reader"; + default: + return "unknown"; + } +} + +int release_locks_int(rlocks_reason_t reason, const char *func, int line, struct sqlclntstate *clnt) { + struct sql_thread *thd = pthread_getspecific(query_info_key); if (!clnt) { - struct sql_thread *thd = pthread_getspecific(query_info_key); if (thd) clnt = thd->clnt; } if (!clnt || !clnt->dbtran.cursor_tran) return -1; extern int gbl_sql_release_locks_trace; if (gbl_sql_release_locks_trace) { logmsg(LOGMSG_USER, "Releasing locks for lockid %d, %s\n", - bdb_get_lid_from_cursortran(clnt->dbtran.cursor_tran), trace); + bdb_get_lid_from_cursortran(clnt->dbtran.cursor_tran), rlocks_reason_str(reason)); } + extern int gbl_recover_deadlock_sync_dta; + if (reason == RLOCKS_REASON_LOCKWAIT && gbl_recover_deadlock_sync_dta && thd) + sync_index_data_cursors(thd); return recover_deadlock_flags(thedb->bdb_env, clnt, NULL, -1, func, line, 0); } @@ -2856,7 +2881,7 @@ int release_locks_on_emit_row(struct sqlclntstate *clnt) /* Always release if we're emitting during a master change */ if (bdb_lock_desired(thedb->bdb_env)) - return release_locks_int("release locks on emit-row for lock-desired", __func__, __LINE__, clnt); + return release_locks_int(RLOCKS_REASON_LOCK_DESIRED, __func__, __LINE__, clnt); /* Short circuit if check-waiters or tunable is disabled */ if (!gbl_locks_check_waiters) @@ -2868,7 +2893,7 @@ int release_locks_on_emit_row(struct sqlclntstate *clnt) /* Release locks randomly for testing */ if (gbl_sql_random_release_interval && !(rand() % gbl_sql_random_release_interval)) - return release_locks_int("random release emit-row", __func__, __LINE__, clnt); + return release_locks_int(RLOCKS_REASON_RANDOM, __func__, __LINE__, clnt); /* Short circuit if we don't have any waiters */ if (!bdb_curtran_has_waiters(thedb->bdb_env, clnt->dbtran.cursor_tran)) @@ -2876,12 +2901,12 @@ int release_locks_on_emit_row(struct sqlclntstate *clnt) /* We're emitting a row & have waiters */ if (!gbl_rep_wait_release_ms || thedb->master == gbl_myhostname) - return release_locks_int("release locks on emit-row", __func__, __LINE__, clnt); + return release_locks_int(RLOCKS_REASON_EMIT_ROW, __func__, __LINE__, clnt); /* We're emitting a row and are blocking replication */ if (rep_lock_time_ms && (comdb2_time_epochms() - rep_lock_time_ms) > gbl_rep_wait_release_ms) - return release_locks_int("long repwait at emit-row", __func__, __LINE__, clnt); + return release_locks_int(RLOCKS_REASON_LONG_REPWAIT, __func__, __LINE__, clnt); return 0; } @@ -5663,7 +5688,7 @@ static int recover_deadlock_sbuf(struct sqlclntstate *clnt) /* Sql thread */ if (thd) { - if (release_locks_int("slow reader", __func__, __LINE__, clnt) != 0) { + if (release_locks_int(RLOCKS_REASON_SLOW_READER, __func__, __LINE__, clnt) != 0) { assert(bdb_lockref() == 0); logmsg(LOGMSG_ERROR, "%s release_locks failed\n", __func__); return 1; diff --git a/lua/sp.c b/lua/sp.c index a4eb76abae..be50c4f974 100644 --- a/lua/sp.c +++ b/lua/sp.c @@ -5512,7 +5512,7 @@ static int l_send_back_row(Lua lua, sqlite3_stmt *stmt, int nargs) * air to check if bdb_lock_desired */ while ((rc = pthread_mutex_trylock(parent->emit_mutex)) == EBUSY) { if (bdb_lock_desired(thedb->bdb_env)) { - rc = release_locks("release locks on emit-row for lock-desired"); + rc = release_locks(RLOCKS_REASON_LOCK_DESIRED); if (rc) { logmsg(LOGMSG_ERROR, "%s release_locks_on_emit_row %d\n", __func__, rc); return rc; diff --git a/sqlite/src/sqlite_btree.h b/sqlite/src/sqlite_btree.h index 92c8c25bc5..a86d7aff9e 100644 --- a/sqlite/src/sqlite_btree.h +++ b/sqlite/src/sqlite_btree.h @@ -185,6 +185,7 @@ int sqlite3BtreeNewDb(Btree *p); */ #define BTREE_HINT_FLAGS 1 /* Set flags indicating cursor usage */ #define BTREE_HINT_RANGE 2 /* Range constraints on queries */ +#define BTREE_HINT_TABLECURSOR 3 /* Table csr associated with this index csr */ /* ** Values that may be OR'd together to form the second argument to the @@ -230,6 +231,7 @@ int sqlite3BtreeNewDb(Btree *p); #ifdef SQLITE_ENABLE_CURSOR_HINTS void sqlite3BtreeCursorHint(BtCursor*, int, ...); +BtCursor *sqlite3BtreeCursorHintTblCsr(BtCursor*); #endif #define BTREE_CUR_RD 0x00000001 diff --git a/sqlite/src/vdbe.c b/sqlite/src/vdbe.c index 37da81d238..df5a2c6181 100644 --- a/sqlite/src/vdbe.c +++ b/sqlite/src/vdbe.c @@ -6563,6 +6563,9 @@ case OP_IdxRowid: { /* out2 */ assert( pTabCur->eCurType==CURTYPE_BTREE ); assert( pTabCur->uc.pCursor!=0 ); assert( pTabCur->isTable ); + assert( + sqlite3BtreeCursorHintTblCsr(pC->uc.pCursor)==pTabCur->uc.pCursor + ); pTabCur->nullRow = 0; pTabCur->movetoTarget = rowid; pTabCur->deferredMoveto = 1; @@ -8661,25 +8664,39 @@ case OP_Init: { /* jump */ } #ifdef SQLITE_ENABLE_CURSOR_HINTS -/* Opcode: CursorHint P1 * * P4 * +/* Opcode: CursorHint P1 * P3 P4 * +** +** Provide a hint to cursor P1. ** -** Provide a hint to cursor P1 that it only needs to return rows that -** satisfy the Expr in P4. TK_REGISTER terms in the P4 expression refer -** to values currently held in registers. TK_COLUMN terms in the P4 +** If P4 is of type P4_EXPR, then the hint is that the cursor need only return +** rows that satisfy the Expr in P4. TK_REGISTER terms in the P4 expression +** refer to values currently held in registers. TK_COLUMN terms in the P4 ** expression refer to columns in the b-tree to which cursor P1 is pointing. +** P3 is ignored in this case. +** +** Or, if P4 is P4_NOTUSED, then the hint is that cursor P1 is an index cursor +** used to drive table cursor P3. In other words, that this VM may execute +** OP_DeferredSeek instructions to lazily position P3 based on current +** position of P1. */ case OP_CursorHint: { VdbeCursor *pC; + pC = p->apCsr[pOp->p1]; assert( pOp->p1>=0 && pOp->p1nCursor ); - assert( pOp->p4type==P4_EXPR ); - pC = p->apCsr[pOp->p1]; + if( pC ){ #if !defined(SQLITE_BUILDING_FOR_COMDB2) assert( pC->eCurType==CURTYPE_BTREE ); #endif /* !defined(SQLITE_BUILDING_FOR_COMDB2) */ - sqlite3BtreeCursorHint(pC->uc.pCursor, BTREE_HINT_RANGE, - pOp->p4.pExpr, aMem); + if( pOp->p4type==P4_EXPR ){ + sqlite3BtreeCursorHint(pC->uc.pCursor, BTREE_HINT_RANGE, + pOp->p4.pExpr, aMem); + }else if( p->apCsr[pOp->p3] ){ + sqlite3BtreeCursorHint( + pC->uc.pCursor, BTREE_HINT_TABLECURSOR, p->apCsr[pOp->p3]->uc.pCursor + ); + } } break; } diff --git a/sqlite/src/vdbeaux.c b/sqlite/src/vdbeaux.c index 37bf07c2c3..354932e747 100644 --- a/sqlite/src/vdbeaux.c +++ b/sqlite/src/vdbeaux.c @@ -3597,7 +3597,7 @@ int SQLITE_NOINLINE sqlite3VdbeFinishMoveto(VdbeCursor *p){ snprintf(errmsg, sizeof(errmsg), "Dta lookup lost the race for tbl %s genid=%llu (%llx) [new]\n", sqlite3BtreeGetTblName(p->uc.pCursor), - bdb_genid_to_host_order(p->movetoTarget), + bdb_genid_to_host_order(p->movetoTarget), bdb_genid_to_host_order(p->movetoTarget)); logmsg(LOGMSG_ERROR, "%s\n", errmsg); if (gbl_abort_on_dta_lookup_error) diff --git a/sqlite/src/where.c b/sqlite/src/where.c index 8a5377ed17..41f5942065 100644 --- a/sqlite/src/where.c +++ b/sqlite/src/where.c @@ -5366,6 +5366,11 @@ WhereInfo *sqlite3WhereBegin( (u8*)&colUsed, P4_INT64); } #endif /* SQLITE_ENABLE_COLUMN_USED_MASK */ +#ifdef SQLITE_ENABLE_CURSOR_HINTS + if( HasRowid(pTab) ){ + sqlite3VdbeAddOp3(v, OP_CursorHint, iIndexCur, 0, pTabItem->iCursor); + } +#endif } } if( iDb>=0 ) sqlite3CodeVerifySchema(pParse, iDb); diff --git a/tests/recovdlock.test/Makefile b/tests/recovdlock.test/Makefile new file mode 100644 index 0000000000..6ec8e1ef75 --- /dev/null +++ b/tests/recovdlock.test/Makefile @@ -0,0 +1,5 @@ +ifeq ($(TESTSROOTDIR),) + include ../testcase.mk +else + include $(TESTSROOTDIR)/testcase.mk +endif diff --git a/tests/recovdlock.test/runit b/tests/recovdlock.test/runit new file mode 100755 index 0000000000..639654adc6 --- /dev/null +++ b/tests/recovdlock.test/runit @@ -0,0 +1,175 @@ +#!/usr/bin/env bash +bash -n "$0" | exit 1 + +# Test: DeferredSeek data lookup failure after recover_deadlock (non-snapshot) +# +# Scenario: +# A 2-table join uses a non-covering index on the outer table. The outer index +# cursor positions on row R and DeferredSeek records R's rowid (data cursor +# deferred -- still at its initial position). The inner loop scans slowly +# (debug_sleep_in_cursor_move injects a delay on each Next), giving a +# concurrent DELETE time to block on the outer index page lock. +# When cursor_move_postop detects the waiter it runs recover_deadlock: releases +# all locks (letting the DELETE commit), then repositions cursors. The inner +# loop eventually finds a match. To produce output the join must access +# outer_t.data_col via the deferred seek -- but the row was deleted during +# recover_deadlock. This should produce a DTA lookup failure. +# +# The test currently demonstrates the bug (wrong behavior). After the fix the +# server should handle the missing row gracefully and return 0 rows. + +set -e +source ${TESTSROOTDIR}/tools/runit_common.sh + +dbnm=$1 +if [ "x$dbnm" == "x" ] ; then + failexit "need a DB name" +fi + +# 100 inner rows is enough -- the sleep tunable slows each cursor move. +INNER_ROWS=${INNER_ROWS:-100} +# ms to sleep on each inner cursor move +CURSOR_SLEEP_MS=${CURSOR_SLEEP_MS:-100} + +CDB2SQL="${CDB2SQL_EXE} ${CDB2_OPTIONS} $dbnm default" + +echo "Creating tables" +$CDB2SQL "drop table if exists outer_t" >/dev/null +$CDB2SQL "drop table if exists inner_t" >/dev/null + +# outer_t: key_col is indexed but data_col is NOT -- forces DeferredSeek. +$CDB2SQL "create table outer_t ( + id int, + key_col int, + data_col int, + primary key (id), + index \"outer_key_idx\" (key_col) +)" >/dev/null + +# inner_t: outer_key indexed for join; val NOT indexed -- forces a full index +# scan of all inner rows (one Next per row) to find the single matching row. +$CDB2SQL "create table inner_t ( + id int, + outer_key int, + val int, + primary key (id), + index \"inner_outer_key_idx\" (outer_key) +)" >/dev/null + +echo "Populating outer_t (1 row)" +$CDB2SQL "insert into outer_t values(1, 1, 42)" >/dev/null + +echo "Populating inner_t ($INNER_ROWS rows, match only on last)" +$CDB2SQL "insert into inner_t + select value, 1, value from generate_series(1, $INNER_ROWS)" >/dev/null + +echo "Data population complete" + +# Ensure page-lock waiter checking is on (fires the RLOCKS_REASON_LOCKWAIT path) +$CDB2SQL "put tunable 'locks_check_waiters' 1" >/dev/null + +# Slow down each cursor move so the background DELETE has time to block on +# the outer index page lock before the inner scan completes. +$CDB2SQL "put tunable 'debug_sleep_in_cursor_move' $CURSOR_SLEEP_MS" >/dev/null + +# The join: outer_t uses outer_key_idx (non-covering), inner loop scans all +# inner_t rows via inner_outer_key_idx checking val = INNER_ROWS (last row). +# CROSS JOIN forces outer_t as the outer loop. +# DeferredSeek on outer_t data cursor is set before the inner loop starts and +# is only executed when a match is found (at val = INNER_ROWS). +JOIN_SQL="select outer_t.data_col + from outer_t + cross join inner_t on inner_t.outer_key = outer_t.key_col + where outer_t.key_col > 0 + and inner_t.val = $INNER_ROWS" + +# Verify query plan: outer_t uses index (DeferredSeek), inner_t uses index. +echo "Checking query plan" +EXPLAIN=$(${CDB2SQL_EXE} --tabs ${CDB2_OPTIONS} $dbnm default "explain $JOIN_SQL" 2>&1) \ + || failexit "explain failed: $EXPLAIN" +echo "$EXPLAIN" + +if ! echo "$EXPLAIN" | grep -qi "OUTER_KEY_IDX"; then + failexit "outer_t does not use outer_key_idx -- DeferredSeek won't fire; test setup invalid" +fi +if ! echo "$EXPLAIN" | grep -qi "INNER_OUTER_KEY_IDX"; then + failexit "inner_t does not use inner_outer_key_idx -- test setup invalid" +fi +if ! echo "$EXPLAIN" | grep -q "DeferredSeek"; then + failexit "no DeferredSeek in plan -- outer data cursor not deferred; test setup invalid" +fi +echo "Query plan ok" + +# Helper: set tunable on all nodes in the cluster (or just default if single node) +set_tunable() { + local tunable=$1 + local value=$2 + if [[ -n "$CLUSTER" ]]; then + for node in $CLUSTER; do + ${CDB2SQL_EXE} ${CDB2_OPTIONS} $dbnm --host $node \ + "put tunable '$tunable' $value" >/dev/null 2>&1 || true + done + else + $CDB2SQL "put tunable '$tunable' $value" >/dev/null + fi +} + +# Helper: run the join + concurrent DELETE, check expected outcome. +# $1 = pass label, $2 = expected_rc (0=success, nonzero=error), $3 = expect_dta_log (1/0) +run_pass() { + local label=$1 + local expect_error=$2 + local expect_dta_log=$3 + + # Re-insert outer_t row (may have been deleted by previous pass) + $CDB2SQL "delete from outer_t where id = 1" >/dev/null 2>&1 || true + $CDB2SQL "insert into outer_t values(1, 1, 42)" >/dev/null + + (sleep 1 && ${CDB2SQL_EXE} ${CDB2_OPTIONS} $dbnm default \ + "delete from outer_t where id = 1" >/dev/null 2>&1) & + local DELETE_PID=$! + + echo "[$label] Running join (inner scan ~$((INNER_ROWS * CURSOR_SLEEP_MS / 1000))s, DELETE after 1s)" + set +e + local result + result=$(${CDB2SQL_EXE} --tabs ${CDB2_OPTIONS} $dbnm default "$JOIN_SQL" 2>&1) + local rc=$? + set -e + + wait $DELETE_PID 2>/dev/null || true + echo "[$label] rc=$rc result='$result'" + + # Verify server hit the DTA code path + DB_LOG=${TESTDIR}/logs/${dbnm}.db + if grep -q "Dta lookup lost the race" "$DB_LOG"; then + echo "[$label] Server logged 'Dta lookup lost the race' -- scenario triggered" + else + if [ "$expect_dta_log" -eq 1 ]; then + failexit "[$label] 'Dta lookup lost the race' not logged -- scenario not triggered" + fi + fi + + if [ "$expect_error" -eq 1 ]; then + if [ $rc -eq 0 ]; then + failexit "[$label] expected join error but got rc=0" + fi + echo "[$label] ok: join correctly returned error (rc=$rc)" + else + if [ $rc -ne 0 ]; then + failexit "[$label] join returned error -- DTA lookup not handled: $result" + fi + echo "[$label] ok: join succeeded (rc=0), result='$result'" + fi +} + +# Pass 1: tunable OFF -- fix not active, expect DTA lookup failure (rc 203) +echo "=== Pass 1: recover_deadlock_sync_dta=0 (bug expected) ===" +set_tunable "recover_deadlock_sync_dta" 0 +run_pass "tunable=off" 1 1 + +# Pass 2: tunable ON -- fix active, expect graceful 0-row result +echo "=== Pass 2: recover_deadlock_sync_dta=1 (fix expected) ===" +set_tunable "recover_deadlock_sync_dta" 1 +run_pass "tunable=on" 0 0 + +echo "Test passed" diff --git a/tests/yast.test/analyze1.test b/tests/yast.test/analyze1.test index a002ea2697..37334ba1ff 100644 --- a/tests/yast.test/analyze1.test +++ b/tests/yast.test/analyze1.test @@ -374,7 +374,7 @@ do_test analyze-98.1.2 { EXPLAIN QUERY PLAN SELECT * FROM t; EXPLAIN QUERY PLAN SELECT * FROM t ORDER BY i; } -} {3 0 0 {SCAN TABLE t (~1048576 rows)} 4 0 0 {SCAN TABLE t USING COVERING INDEX $I_3AA4168B (~1048576 rows)}} +} {3 0 0 {SCAN TABLE t (~1048576 rows)} 5 0 0 {SCAN TABLE t USING COVERING INDEX $I_3AA4168B (~1048576 rows)}} do_test analyze-98.1.3 { execsql { ANALYZE; @@ -387,7 +387,7 @@ do_test analyze-98.1.4 { EXPLAIN QUERY PLAN SELECT * FROM t; EXPLAIN QUERY PLAN SELECT * FROM t ORDER BY i; } -} {3 0 0 {SCAN TABLE t (~960 rows)} 4 0 0 {SCAN TABLE t USING COVERING INDEX $I_3AA4168B (~960 rows)}} +} {3 0 0 {SCAN TABLE t (~960 rows)} 5 0 0 {SCAN TABLE t USING COVERING INDEX $I_3AA4168B (~960 rows)}} # # This test corrupts the database file so it must be the last test diff --git a/tests/yast.test/index6.test b/tests/yast.test/index6.test index 9c1e6f8d01..6ac7d45eb4 100644 --- a/tests/yast.test/index6.test +++ b/tests/yast.test/index6.test @@ -200,7 +200,7 @@ do_test index6-2.2 { EXPLAIN QUERY PLAN SELECT * FROM t2 WHERE a=5; } -} {5 0 0 {SEARCH TABLE t2 USING INDEX $T2A1_49F2A39 (a=?) (~10 rows)}} +} {6 0 0 {SEARCH TABLE t2 USING INDEX $T2A1_49F2A39 (a=?) (~10 rows)}} #{/.* TABLE t2 USING INDEX t2a1 .*/} #ifcapable stat4||stat3 { # execsql ANALYZE @@ -224,7 +224,7 @@ execsql { EXPLAIN QUERY PLAN SELECT * FROM t2 WHERE a IS NOT NULL; } -} {5 0 0 {SEARCH TABLE t2 USING INDEX $T2A1_49F2A39 (a>?) (~416 rows)}} +} {6 0 0 {SEARCH TABLE t2 USING INDEX $T2A1_49F2A39 (a>?) (~416 rows)}} #{/.* TABLE t2 USING INDEX t2a1 .*/} do_test index6-2.4 { execsql { @@ -256,7 +256,7 @@ do_execsql_test index6-2.103 { do_execsql_test index6-2.103eqp { EXPLAIN QUERY PLAN SELECT b FROM t2 WHERE a=15 AND a<100; -} {5 0 0 {SEARCH TABLE t2 USING INDEX $T2A2_49F2A39 (a=?) (~9 rows)}} +} {6 0 0 {SEARCH TABLE t2 USING INDEX $T2A2_49F2A39 (a=?) (~9 rows)}} #{/.*INDEX t2a2.*/} do_execsql_test index6-2.104 { SELECT b FROM t2 WHERE a=515 AND a>200; @@ -264,7 +264,7 @@ do_execsql_test index6-2.104 { do_execsql_test index6-2.104eqp { EXPLAIN QUERY PLAN SELECT b FROM t2 WHERE a=515 AND a>200; -} {5 0 0 {SEARCH TABLE t2 USING INDEX $T2A2_49F2A39 (a=?) (~9 rows)}} +} {6 0 0 {SEARCH TABLE t2 USING INDEX $T2A2_49F2A39 (a=?) (~9 rows)}} #{/.*INDEX t2a2.*/} # Partial UNIQUE indices @@ -369,8 +369,8 @@ do_execsql_test index6-7.3 { do_execsql_test index6-7.4 { EXPLAIN QUERY PLAN SELECT * FROM t7a JOIN t7b ON (x=99) ORDER BY x; -} {6 0 0 {SEARCH TABLE t7a USING COVERING INDEX $T7AX_A202C5C5 (x=?) (~10 rows)} - 10 0 0 {SCAN TABLE t7b (~1048576 rows)} +} {7 0 0 {SEARCH TABLE t7a USING COVERING INDEX $T7AX_A202C5C5 (x=?) (~10 rows)} + 11 0 0 {SCAN TABLE t7b (~1048576 rows)} } #{/USING COVERING INDEX t7ax/} @@ -441,7 +441,7 @@ execsql ANALYZE do_execsql_test index6-10.1eqp { EXPLAIN QUERY PLAN SELECT e FROM t10 WHERE a=1 AND b=2 AND c=3 ORDER BY d; -} {6 0 0 {SCAN TABLE t10 USING INDEX $T10X_65299CEC (~1 row)}} +} {7 0 0 {SCAN TABLE t10 USING INDEX $T10X_65299CEC (~1 row)}} #{/USING INDEX t10x/} do_execsql_test index6-10.2 { SELECT e FROM t10 WHERE c=3 AND 2=b AND a=1 ORDER BY d DESC; @@ -449,7 +449,7 @@ do_execsql_test index6-10.2 { do_execsql_test index6-10.2eqp { EXPLAIN QUERY PLAN SELECT e FROM t10 WHERE c=3 AND 2=b AND a=1 ORDER BY d DESC; -} {6 0 0 {SCAN TABLE t10 USING INDEX $T10X_65299CEC (~1 row)}} +} {7 0 0 {SCAN TABLE t10 USING INDEX $T10X_65299CEC (~1 row)}} #{/USING INDEX t10x/} do_execsql_test index6-10.3 { SELECT e FROM t10 WHERE a=1 AND b=2 ORDER BY d DESC; diff --git a/tests/yast.test/index7.test b/tests/yast.test/index7.test index 1545bfe798..7ebba0343f 100644 --- a/tests/yast.test/index7.test +++ b/tests/yast.test/index7.test @@ -250,7 +250,7 @@ do_test index7-2.2 { EXPLAIN QUERY PLAN SELECT * FROM t2 WHERE a=5; } -} {5 0 0 {SEARCH TABLE t2 USING INDEX $T2A1_49F2A39 (a=?) (~10 rows)}} +} {6 0 0 {SEARCH TABLE t2 USING INDEX $T2A1_49F2A39 (a=?) (~10 rows)}} #{/.* TABLE t2 USING COVERING INDEX t2a1 .*/} #ifcapable stat4||stat3 { # do_test index7-2.3stat4 { @@ -304,7 +304,7 @@ do_execsql_test index7-2.103 { do_execsql_test index7-2.103eqp { EXPLAIN QUERY PLAN SELECT b FROM t2 WHERE a=15 AND a<100; -} {5 0 0 {SEARCH TABLE t2 USING INDEX $T2A2_49F2A39 (a=?) (~9 rows)}} +} {6 0 0 {SEARCH TABLE t2 USING INDEX $T2A2_49F2A39 (a=?) (~9 rows)}} #{/.*INDEX t2a2.*/} do_execsql_test index7-2.104 { SELECT b FROM t2 WHERE a=515 AND a>200; @@ -312,7 +312,7 @@ do_execsql_test index7-2.104 { do_execsql_test index7-2.104eqp { EXPLAIN QUERY PLAN SELECT b FROM t2 WHERE a=515 AND a>200; -} {5 0 0 {SEARCH TABLE t2 USING INDEX $T2A2_49F2A39 (a=?) (~9 rows)}} +} {6 0 0 {SEARCH TABLE t2 USING INDEX $T2A2_49F2A39 (a=?) (~9 rows)}} #{/.*INDEX t2a2.*/} # Partial UNIQUE indices diff --git a/tests/yast.test/indexexpr1.test b/tests/yast.test/indexexpr1.test index a881c23e8a..c989895c78 100644 --- a/tests/yast.test/indexexpr1.test +++ b/tests/yast.test/indexexpr1.test @@ -129,8 +129,8 @@ do_execsql_test indexexpr1-210eqp { EXPLAIN QUERY PLAN SELECT b, c, '|' FROM t1 WHERE substr(a,1,12)=='and_the_Word' ORDER BY b, c; } { - 6 0 0 {SEARCH TABLE t1 USING INDEX $T1A1_FDB7EC21 (=?) (~10 rows)} - 19 0 0 {USE TEMP B-TREE FOR ORDER BY} + 7 0 0 {SEARCH TABLE t1 USING INDEX $T1A1_FDB7EC21 (=?) (~10 rows)} + 20 0 0 {USE TEMP B-TREE FOR ORDER BY} } #{/USING INDEX t1a1/} do_execsql_test indexexpr1-220 { @@ -140,8 +140,8 @@ do_execsql_test indexexpr1-220eqp { EXPLAIN QUERY PLAN SELECT b, c, '|' FROM t1 WHERE 'and_the_Word'==substr(a,1,12) ORDER BY b, c; } { - 6 0 0 {SEARCH TABLE t1 USING INDEX $T1A1_FDB7EC21 (=?) (~10 rows)} - 19 0 0 {USE TEMP B-TREE FOR ORDER BY} + 7 0 0 {SEARCH TABLE t1 USING INDEX $T1A1_FDB7EC21 (=?) (~10 rows)} + 20 0 0 {USE TEMP B-TREE FOR ORDER BY} } #{/USING INDEX t1a1/} @@ -153,7 +153,7 @@ do_execsql_test indexexpr1-230 { do_execsql_test indexexpr1-230eqp { EXPLAIN QUERY PLAN SELECT c FROM t1 WHERE b=1 AND substr(a,2,3)='nd_' ORDER BY c; -} {6 0 0 {SEARCH TABLE t1 USING INDEX $T1BA_2C1C85D (b=? AND =?) (~8 rows)}} +} {7 0 0 {SEARCH TABLE t1 USING INDEX $T1BA_2C1C85D (b=? AND =?) (~8 rows)}} #{/USING INDEX t1ba/} do_execsql_test indexexpr1-240 { @@ -168,8 +168,8 @@ do_execsql_test indexexpr1-241eqp { EXPLAIN QUERY PLAN SELECT id FROM t1 WHERE substr(a,b,3)<='and' ORDER BY +id; } { - 6 0 0 {SEARCH TABLE t1 USING INDEX $T1ABX_BB942037 (=?) (~30 rows)} - 34 0 0 {USE TEMP B-TREE FOR ORDER BY} +} {7 0 0 {SEARCH TABLE t1 USING INDEX $T1ABX_BB942037 (=?) (~30 rows)} + 35 0 0 {USE TEMP B-TREE FOR ORDER BY} } #{/USING INDEX t1abx/} @@ -200,7 +200,7 @@ do_execsql_test indexexpr1-260eqp { EXPLAIN QUERY PLAN SELECT id, b, c FROM t1 WHERE substr(a,27,3)=='ord' AND d>=29; -} {5 0 0 {SEARCH TABLE t1 USING INDEX $T1A2_654C9157 (=?) (~9 rows)}} +} {6 0 0 {SEARCH TABLE t1 USING INDEX $T1A2_654C9157 (=?) (~9 rows)}} #{/USING INDEX t1a2/} # ORDER BY using an indexed expression @@ -213,7 +213,7 @@ do_execsql_test indexexpr1-270 { do_execsql_test indexexpr1-270eqp { EXPLAIN QUERY PLAN SELECT length(a) FROM t1 ORDER BY length(a); -} {6 0 0 {SCAN TABLE t1 USING INDEX $T1ALEN_6DE19296 (~1048576 rows)}} +} {7 0 0 {SCAN TABLE t1 USING INDEX $T1ALEN_6DE19296 (~1048576 rows)}} #{/SCAN TABLE t1 USING INDEX t1alen/} do_execsql_test indexexpr1-271 { SELECT length(a) FROM t1 ORDER BY length(a) DESC; @@ -221,7 +221,7 @@ do_execsql_test indexexpr1-271 { do_execsql_test indexexpr1-271eqp { EXPLAIN QUERY PLAN SELECT length(a) FROM t1 ORDER BY length(a) DESC; -} {6 0 0 {SCAN TABLE t1 USING INDEX $T1ALEN_6DE19296 (~1048576 rows)}} +} {7 0 0 {SCAN TABLE t1 USING INDEX $T1ALEN_6DE19296 (~1048576 rows)}} #{/SCAN TABLE t1 USING INDEX t1alen/} do_catchsql_test indexexpr1-300 { @@ -295,8 +295,8 @@ do_execsql_test indexexpr1-510 { do_execsql_test indexexpr1-510eqp { EXPLAIN QUERY PLAN SELECT substr(a,4,3) AS k FROM cnt, t5 WHERE k=printf('%03d',x); -} {7 0 0 {SCAN TABLE cnt (~1048576 rows)} - 9 0 0 {SEARCH TABLE t5 USING INDEX $T5AX_26FA5786 (=?) (~10 rows)} +} {8 0 0 {SCAN TABLE cnt (~1048576 rows)} + 10 0 0 {SEARCH TABLE t5 USING INDEX $T5AX_26FA5786 (=?) (~10 rows)} } #{/USING INDEX t5ax/} diff --git a/tests/yast.test/orderby1.test b/tests/yast.test/orderby1.test index 41300ce33f..9fea3237c4 100644 --- a/tests/yast.test/orderby1.test +++ b/tests/yast.test/orderby1.test @@ -214,7 +214,7 @@ do_test 2.1b { EXPLAIN QUERY PLAN SELECT name FROM album JOIN track USING (aid) ORDER BY title, tn } -} {10 0 0 {SCAN TABLE track USING INDEX $SQLITE_AUTOINDEX_TRACK_1_4AAADA26 (~1048576 rows)} 13 0 0 {SEARCH TABLE album USING INDEX $PRIMARY_3FC26355 (aid=?) (~1 row)} 31 0 0 {USE TEMP B-TREE FOR ORDER BY}} +} {12 0 0 {SCAN TABLE track USING INDEX $SQLITE_AUTOINDEX_TRACK_1_4AAADA26 (~1048576 rows)} 15 0 0 {SEARCH TABLE album USING INDEX $PRIMARY_3FC26355 (aid=?) (~1 row)} 33 0 0 {USE TEMP B-TREE FOR ORDER BY}} #{/ORDER BY/} ;# ORDER BY required because of missing aid term in ORDER BY do_test 2.1c { @@ -227,7 +227,7 @@ do_test 2.1d { EXPLAIN QUERY PLAN SELECT name FROM album JOIN track USING (aid) ORDER BY title, aid, tn } -} {10 0 0 {SCAN TABLE track USING INDEX $SQLITE_AUTOINDEX_TRACK_1_4AAADA26 (~1048576 rows)} 13 0 0 {SEARCH TABLE album USING INDEX $PRIMARY_3FC26355 (aid=?) (~1 row)} 32 0 0 {USE TEMP B-TREE FOR ORDER BY}} +} {12 0 0 {SCAN TABLE track USING INDEX $SQLITE_AUTOINDEX_TRACK_1_4AAADA26 (~1048576 rows)} 15 0 0 {SEARCH TABLE album USING INDEX $PRIMARY_3FC26355 (aid=?) (~1 row)} 34 0 0 {USE TEMP B-TREE FOR ORDER BY}} #{/ORDER BY/} ;# ORDER BY required in this case # The same query with ORDER BY clause optimization disabled via + operators @@ -246,7 +246,7 @@ do_test 2.2b { EXPLAIN QUERY PLAN SELECT name FROM album JOIN track USING (aid) ORDER BY +title, +tn } -} {8 0 0 {SCAN TABLE track (~1048576 rows)} 10 0 0 {SEARCH TABLE album USING INDEX $PRIMARY_3FC26355 (aid=?) (~1 row)} 27 0 0 {USE TEMP B-TREE FOR ORDER BY}} +} {9 0 0 {SCAN TABLE track (~1048576 rows)} 11 0 0 {SEARCH TABLE album USING INDEX $PRIMARY_3FC26355 (aid=?) (~1 row)} 28 0 0 {USE TEMP B-TREE FOR ORDER BY}} #{/ORDER BY/} ;# separate sorting pass due to "+" on ORDER BY terms # The same query with ORDER BY optimizations turned off via built-in test. @@ -287,7 +287,7 @@ do_test 2.4c { EXPLAIN QUERY PLAN SELECT name FROM album JOIN track USING (aid) ORDER BY title DESC, tn } -} {10 0 0 {SCAN TABLE track USING INDEX $SQLITE_AUTOINDEX_TRACK_1_4AAADA26 (~1048576 rows)} 13 0 0 {SEARCH TABLE album USING INDEX $PRIMARY_3FC26355 (aid=?) (~1 row)} 31 0 0 {USE TEMP B-TREE FOR ORDER BY}} +} {12 0 0 {SCAN TABLE track USING INDEX $SQLITE_AUTOINDEX_TRACK_1_4AAADA26 (~1048576 rows)} 15 0 0 {SEARCH TABLE album USING INDEX $PRIMARY_3FC26355 (aid=?) (~1 row)} 33 0 0 {USE TEMP B-TREE FOR ORDER BY}} #{/ORDER BY/} ;# separate sorting pass due to mixed DESC/ASC @@ -306,7 +306,7 @@ do_test 2.5c { EXPLAIN QUERY PLAN SELECT name FROM album JOIN track USING (aid) ORDER BY title, tn DESC } -} {10 0 0 {SCAN TABLE track USING INDEX $SQLITE_AUTOINDEX_TRACK_1_4AAADA26 (~1048576 rows)} 13 0 0 {SEARCH TABLE album USING INDEX $PRIMARY_3FC26355 (aid=?) (~1 row)} 31 0 0 {USE TEMP B-TREE FOR ORDER BY}} +} {12 0 0 {SCAN TABLE track USING INDEX $SQLITE_AUTOINDEX_TRACK_1_4AAADA26 (~1048576 rows)} 15 0 0 {SEARCH TABLE album USING INDEX $PRIMARY_3FC26355 (aid=?) (~1 row)} 33 0 0 {USE TEMP B-TREE FOR ORDER BY}} #{/ORDER BY/} ;# separate sorting pass due to mixed ASC/DESC do_test 2.6a { @@ -324,7 +324,7 @@ do_test 2.6c { EXPLAIN QUERY PLAN SELECT name FROM album JOIN track USING (aid) ORDER BY title DESC, tn DESC } -} {10 0 0 {SCAN TABLE track USING INDEX $SQLITE_AUTOINDEX_TRACK_1_4AAADA26 (~1048576 rows)} 13 0 0 {SEARCH TABLE album USING INDEX $PRIMARY_3FC26355 (aid=?) (~1 row)} 31 0 0 {USE TEMP B-TREE FOR ORDER BY}} +} {12 0 0 {SCAN TABLE track USING INDEX $SQLITE_AUTOINDEX_TRACK_1_4AAADA26 (~1048576 rows)} 15 0 0 {SEARCH TABLE album USING INDEX $PRIMARY_3FC26355 (aid=?) (~1 row)} 33 0 0 {USE TEMP B-TREE FOR ORDER BY}} #{/ORDER BY/} ;# ORDER BY required @@ -398,7 +398,7 @@ do_test 3.2b { EXPLAIN QUERY PLAN SELECT name FROM album JOIN track USING (aid) ORDER BY +title, +tn DESC } -} {8 0 0 {SCAN TABLE track (~1048576 rows)} 10 0 0 {SEARCH TABLE album USING INDEX $PRIMARY_3FC26355 (aid=?) (~1 row)} 27 0 0 {USE TEMP B-TREE FOR ORDER BY}} +} {9 0 0 {SCAN TABLE track (~1048576 rows)} 11 0 0 {SEARCH TABLE album USING INDEX $PRIMARY_3FC26355 (aid=?) (~1 row)} 28 0 0 {USE TEMP B-TREE FOR ORDER BY}} #{/ORDER BY/} ;# separate sorting pass due to "+" on ORDER BY terms # The same query with ORDER BY optimizations turned off via built-in test. @@ -549,7 +549,7 @@ do_execsql_test 7.0 { EXPLAIN QUERY PLAN SELECT * FROM t7 WHERE a=1 ORDER BY rowid; #SELECT * FROM t7 WHERE a=?1 ORDER BY rowid; -} {6 0 0 {SEARCH TABLE t7 USING INDEX $T7A_CC7FA549 (a=?) (~10 rows)}} +} {7 0 0 {SEARCH TABLE t7 USING INDEX $T7A_CC7FA549 (a=?) (~10 rows)}} #{~/ORDER BY/} #------------------------------------------------------------------------- diff --git a/tests/yast.test/orderby2.test b/tests/yast.test/orderby2.test index 557c3700ee..0157de8ae4 100644 --- a/tests/yast.test/orderby2.test +++ b/tests/yast.test/orderby2.test @@ -71,7 +71,7 @@ do_test 1.3b { EXPLAIN QUERY PLAN SELECT e, b FROM t1, t2 WHERE a=1 ORDER BY d, e; } -} {8 0 0 {SCAN TABLE t2 USING COVERING INDEX $SQLITE_AUTOINDEX_T2_1_DEBFE25D (~1048576 rows)} 10 0 0 {SEARCH TABLE t1 USING INDEX $PRIMARY_50514FE2 (a=?) (~1 row)}} +} {10 0 0 {SCAN TABLE t2 USING COVERING INDEX $SQLITE_AUTOINDEX_T2_1_DEBFE25D (~1048576 rows)} 12 0 0 {SEARCH TABLE t1 USING INDEX $PRIMARY_50514FE2 (a=?) (~1 row)}} #{~/ORDER BY/} # The following tests derived from TH3 test module cov1/where34.test diff --git a/tests/yast.test/orderby5.test b/tests/yast.test/orderby5.test index bbf8ed07ee..64101944ab 100644 --- a/tests/yast.test/orderby5.test +++ b/tests/yast.test/orderby5.test @@ -27,12 +27,12 @@ do_execsql_test 1.1 { EXPLAIN QUERY PLAN SELECT DISTINCT a, b, c FROM t1 WHERE a=0; -} {6 0 0 {SCAN TABLE t1 USING INDEX $T1BC_F401E920 (~524288 rows)}} +} {7 0 0 {SCAN TABLE t1 USING INDEX $T1BC_F401E920 (~524288 rows)}} #{~/B-TREE/} do_execsql_test 1.2.1 { EXPLAIN QUERY PLAN SELECT DISTINCT a, c, b FROM t1 WHERE a=0; -} {6 0 0 {SCAN TABLE t1 USING INDEX $T1BC_F401E920 (~524288 rows)}} +} {7 0 0 {SCAN TABLE t1 USING INDEX $T1BC_F401E920 (~524288 rows)}} #{~/B-TREE/} do_execsql_test 1.2.2 { EXPLAIN QUERY PLAN @@ -47,32 +47,32 @@ do_execsql_test 1.2.3 { do_execsql_test 1.2.4 { EXPLAIN QUERY PLAN SELECT DISTINCT a COLLATE nocase, c, b FROM t1 WHERE a='xyz' COLLATE nocase; -} {6 0 0 {SCAN TABLE t1 USING INDEX $T1BC_F401E920 (~262144 rows)}} +} {7 0 0 {SCAN TABLE t1 USING INDEX $T1BC_F401E920 (~262144 rows)}} #{~/B-TREE/} do_execsql_test 1.3 { EXPLAIN QUERY PLAN SELECT DISTINCT b, a, c FROM t1 WHERE a=0; -} {6 0 0 {SCAN TABLE t1 USING INDEX $T1BC_F401E920 (~524288 rows)}} +} {7 0 0 {SCAN TABLE t1 USING INDEX $T1BC_F401E920 (~524288 rows)}} #{~/B-TREE/} do_execsql_test 1.4 { EXPLAIN QUERY PLAN SELECT DISTINCT b, c, a FROM t1 WHERE a=0; -} {6 0 0 {SCAN TABLE t1 USING INDEX $T1BC_F401E920 (~524288 rows)}} +} {7 0 0 {SCAN TABLE t1 USING INDEX $T1BC_F401E920 (~524288 rows)}} #{~/B-TREE/} do_execsql_test 1.5 { EXPLAIN QUERY PLAN SELECT DISTINCT c, a, b FROM t1 WHERE a=0; -} {6 0 0 {SCAN TABLE t1 USING INDEX $T1BC_F401E920 (~524288 rows)}} +} {7 0 0 {SCAN TABLE t1 USING INDEX $T1BC_F401E920 (~524288 rows)}} #{~/B-TREE/} do_execsql_test 1.6 { EXPLAIN QUERY PLAN SELECT DISTINCT c, b, a FROM t1 WHERE a=0; -} {6 0 0 {SCAN TABLE t1 USING INDEX $T1BC_F401E920 (~524288 rows)}} +} {7 0 0 {SCAN TABLE t1 USING INDEX $T1BC_F401E920 (~524288 rows)}} #{~/B-TREE/} do_execsql_test 1.7 { EXPLAIN QUERY PLAN SELECT DISTINCT c, b, a FROM t1 WHERE +a=0; -} {6 0 0 {SCAN TABLE t1 USING INDEX $T1BC_F401E920 (~983040 rows)} 21 0 0 {USE TEMP B-TREE FOR DISTINCT}} +} {7 0 0 {SCAN TABLE t1 USING INDEX $T1BC_F401E920 (~983040 rows)} 22 0 0 {USE TEMP B-TREE FOR DISTINCT}} #{/B-TREE/} # In some cases, it is faster to do repeated index lookups than it is to @@ -89,13 +89,13 @@ do_execsql_test 2.1a { EXPLAIN QUERY PLAN SELECT * FROM t2 WHERE a=0 ORDER BY a, b, c; -} {6 0 0 {SCAN TABLE t2 USING INDEX $T2BC_72959B8E (~524288 rows)}} +} {7 0 0 {SCAN TABLE t2 USING INDEX $T2BC_72959B8E (~524288 rows)}} #{~/B-TREE/} do_execsql_test 2.1b { EXPLAIN QUERY PLAN SELECT * FROM t1 WHERE likelihood(a=0, 0.05) ORDER BY a, b, c; -} {6 0 0 {SCAN TABLE t1 USING INDEX $T1BC_F401E920 (~49152 rows)}} +} {7 0 0 {SCAN TABLE t1 USING INDEX $T1BC_F401E920 (~49152 rows)}} #{/B-TREE/} do_execsql_test 2.2 { @@ -106,12 +106,12 @@ do_execsql_test 2.2 { do_execsql_test 2.3 { EXPLAIN QUERY PLAN SELECT * FROM t1 WHERE a=0 ORDER BY b, a, c; -} {6 0 0 {SCAN TABLE t1 USING INDEX $T1BC_F401E920 (~524288 rows)}} +} {7 0 0 {SCAN TABLE t1 USING INDEX $T1BC_F401E920 (~524288 rows)}} #{~/B-TREE/} do_execsql_test 2.4 { EXPLAIN QUERY PLAN SELECT * FROM t1 WHERE a=0 ORDER BY b, c, a; -} {6 0 0 {SCAN TABLE t1 USING INDEX $T1BC_F401E920 (~524288 rows)}} +} {7 0 0 {SCAN TABLE t1 USING INDEX $T1BC_F401E920 (~524288 rows)}} #{~/B-TREE/} do_execsql_test 2.5 { EXPLAIN QUERY PLAN @@ -135,7 +135,7 @@ do_execsql_test 3.0comdb2 { CREATE INDEX t3bcde ON t3(b, c, d, e); EXPLAIN QUERY PLAN SELECT a FROM t3 WHERE b=2 AND c=3 ORDER BY d DESC, e DESC, b, c; -} {6 0 0 {SEARCH TABLE t3 USING INDEX $T3BCDE_73E7A489 (b=? AND c=?) (~8 rows)}} +} {7 0 0 {SEARCH TABLE t3 USING INDEX $T3BCDE_73E7A489 (b=? AND c=?) (~8 rows)}} # TODO: comdb2 behaves differently ifcapable comdb2_skip { do_execsql_test 3.0 { diff --git a/tests/yast.test/single-table-unique-index.test b/tests/yast.test/single-table-unique-index.test index 22e3a70e62..3db697746d 100644 --- a/tests/yast.test/single-table-unique-index.test +++ b/tests/yast.test/single-table-unique-index.test @@ -19,6 +19,6 @@ do_test simple-unique-key-test-2 { execsql { EXPLAIN QUERY PLAN SELECT d FROM t1 WHERE a=1 and b=2 and c=3 } -} {5 0 0 {SEARCH TABLE t1 USING INDEX $I1_4882B606 (a=? AND b=? AND c=?) (~1 row)}} +} {6 0 0 {SEARCH TABLE t1 USING INDEX $I1_4882B606 (a=? AND b=? AND c=?) (~1 row)}} finish_test diff --git a/tests/yast.test/where2.test b/tests/yast.test/where2.test index d8342e113c..4b582875b1 100644 --- a/tests/yast.test/where2.test +++ b/tests/yast.test/where2.test @@ -136,7 +136,7 @@ ifcapable subquery { AND x>0 AND x<10 ORDER BY w } - } {99 6 10000 10006 100 6 10201 10207 sort t1 i1zyx (id=10, parent=0, notused=0, detail='LIST SUBQUERY 2') (id=11, parent=10, notused=0, detail='COMPOUND QUERY') (id=12, parent=11, notused=0, detail='LEFT-MOST SUBQUERY') (id=14, parent=12, notused=0, detail='SCAN CONSTANT ROW') (id=18, parent=11, notused=0, detail='UNION USING TEMP B-TREE') (id=19, parent=18, notused=0, detail='SCAN CONSTANT ROW')} + } {99 6 10000 10006 100 6 10201 10207 sort t1 i1zyx (id=11, parent=0, notused=0, detail='LIST SUBQUERY 2') (id=12, parent=11, notused=0, detail='COMPOUND QUERY') (id=13, parent=12, notused=0, detail='LEFT-MOST SUBQUERY') (id=15, parent=13, notused=0, detail='SCAN CONSTANT ROW') (id=19, parent=12, notused=0, detail='UNION USING TEMP B-TREE') (id=20, parent=19, notused=0, detail='SCAN CONSTANT ROW')} do_test where2-4.5 { queryplan { SELECT * FROM t1 WHERE z IN (SELECT 10207 UNION SELECT 10006) @@ -144,7 +144,7 @@ ifcapable subquery { AND x>0 AND x<10 ORDER BY w } - } {99 6 10000 10006 100 6 10201 10207 sort t1 i1zyx (id=10, parent=0, notused=0, detail='LIST SUBQUERY 2') (id=11, parent=10, notused=0, detail='COMPOUND QUERY') (id=12, parent=11, notused=0, detail='LEFT-MOST SUBQUERY') (id=14, parent=12, notused=0, detail='SCAN CONSTANT ROW') (id=18, parent=11, notused=0, detail='UNION USING TEMP B-TREE') (id=19, parent=18, notused=0, detail='SCAN CONSTANT ROW') (id=36, parent=0, notused=0, detail='LIST SUBQUERY 4') (id=37, parent=36, notused=0, detail='COMPOUND QUERY') (id=38, parent=37, notused=0, detail='LEFT-MOST SUBQUERY') (id=40, parent=38, notused=0, detail='SCAN CONSTANT ROW') (id=44, parent=37, notused=0, detail='UNION USING TEMP B-TREE') (id=45, parent=44, notused=0, detail='SCAN CONSTANT ROW')} + } {99 6 10000 10006 100 6 10201 10207 sort t1 i1zyx (id=11, parent=0, notused=0, detail='LIST SUBQUERY 2') (id=12, parent=11, notused=0, detail='COMPOUND QUERY') (id=13, parent=12, notused=0, detail='LEFT-MOST SUBQUERY') (id=15, parent=13, notused=0, detail='SCAN CONSTANT ROW') (id=19, parent=12, notused=0, detail='UNION USING TEMP B-TREE') (id=20, parent=19, notused=0, detail='SCAN CONSTANT ROW') (id=37, parent=0, notused=0, detail='LIST SUBQUERY 4') (id=38, parent=37, notused=0, detail='COMPOUND QUERY') (id=39, parent=38, notused=0, detail='LEFT-MOST SUBQUERY') (id=41, parent=39, notused=0, detail='SCAN CONSTANT ROW') (id=45, parent=38, notused=0, detail='UNION USING TEMP B-TREE') (id=46, parent=45, notused=0, detail='SCAN CONSTANT ROW')} } do_test where2-4.6a { queryplan { @@ -202,7 +202,7 @@ ifcapable subquery { UNION ALL SELECT 10006 UNION ALL SELECT 10207) ORDER BY w } - } {99 6 10000 10006 100 6 10201 10207 sort t1 i1zyx (id=10, parent=0, notused=0, detail='LIST SUBQUERY 4') (id=11, parent=10, notused=0, detail='COMPOUND QUERY') (id=12, parent=11, notused=0, detail='LEFT-MOST SUBQUERY') (id=13, parent=12, notused=0, detail='SCAN CONSTANT ROW') (id=17, parent=11, notused=0, detail='UNION ALL') (id=18, parent=17, notused=0, detail='SCAN CONSTANT ROW') (id=22, parent=11, notused=0, detail='UNION ALL') (id=23, parent=22, notused=0, detail='SCAN CONSTANT ROW') (id=27, parent=11, notused=0, detail='UNION ALL') (id=28, parent=27, notused=0, detail='SCAN CONSTANT ROW')} + } {99 6 10000 10006 100 6 10201 10207 sort t1 i1zyx (id=11, parent=0, notused=0, detail='LIST SUBQUERY 4') (id=12, parent=11, notused=0, detail='COMPOUND QUERY') (id=13, parent=12, notused=0, detail='LEFT-MOST SUBQUERY') (id=14, parent=13, notused=0, detail='SCAN CONSTANT ROW') (id=18, parent=12, notused=0, detail='UNION ALL') (id=19, parent=18, notused=0, detail='SCAN CONSTANT ROW') (id=23, parent=12, notused=0, detail='UNION ALL') (id=24, parent=23, notused=0, detail='SCAN CONSTANT ROW') (id=28, parent=12, notused=0, detail='UNION ALL') (id=29, parent=28, notused=0, detail='SCAN CONSTANT ROW')} } } ;# ifcapable subquery diff --git a/tests/yast.test/where3.test b/tests/yast.test/where3.test index c64a37e6d8..4b0e4982e3 100644 --- a/tests/yast.test/where3.test +++ b/tests/yast.test/where3.test @@ -318,7 +318,7 @@ do_execsql_test where3-5.0 { AND LENGTH(bbb.title) > 0 AND bbb.parent = 4 ORDER BY bbb.title COLLATE NOCASE ASC; -} {10 0 0 {SEARCH TABLE aaa USING INDEX $AAA_333_48C951EC (fk=?) (~10 rows)} 16 0 0 {SEARCH TABLE bbb USING INDEX $PRIMARY_14C8659 (id=?) (~1 row)} 38 0 0 {USE TEMP B-TREE FOR ORDER BY}} +} {12 0 0 {SEARCH TABLE aaa USING INDEX $AAA_333_48C951EC (fk=?) (~10 rows)} 18 0 0 {SEARCH TABLE bbb USING INDEX $PRIMARY_14C8659 (id=?) (~1 row)} 40 0 0 {USE TEMP B-TREE FOR ORDER BY}} do_execsql_test where3-5.1 { EXPLAIN QUERY PLAN SELECT bbb.title AS tag_title @@ -327,7 +327,7 @@ do_execsql_test where3-5.1 { AND LENGTH(bbb.title) > 0 AND bbb.parent = 4 ORDER BY bbb.title COLLATE NOCASE ASC; -} {10 0 0 {SEARCH TABLE aaa USING INDEX $AAA_333_48C951EC (fk=?) (~10 rows)} 16 0 0 {SEARCH TABLE aaa AS bbb USING INDEX $PRIMARY_561FED3E (id=?) (~1 row)} 38 0 0 {USE TEMP B-TREE FOR ORDER BY}} +} {12 0 0 {SEARCH TABLE aaa USING INDEX $AAA_333_48C951EC (fk=?) (~10 rows)} 18 0 0 {SEARCH TABLE aaa AS bbb USING INDEX $PRIMARY_561FED3E (id=?) (~1 row)} 40 0 0 {USE TEMP B-TREE FOR ORDER BY}} do_execsql_test where3-5.2 { EXPLAIN QUERY PLAN SELECT bbb.title AS tag_title @@ -336,7 +336,7 @@ do_execsql_test where3-5.2 { AND LENGTH(bbb.title) > 0 AND bbb.parent = 4 ORDER BY bbb.title COLLATE NOCASE ASC; -} {10 0 0 {SEARCH TABLE aaa USING INDEX $AAA_333_48C951EC (fk=?) (~10 rows)} 16 0 0 {SEARCH TABLE bbb USING INDEX $PRIMARY_14C8659 (id=?) (~1 row)} 38 0 0 {USE TEMP B-TREE FOR ORDER BY}} +} {12 0 0 {SEARCH TABLE aaa USING INDEX $AAA_333_48C951EC (fk=?) (~10 rows)} 18 0 0 {SEARCH TABLE bbb USING INDEX $PRIMARY_14C8659 (id=?) (~1 row)} 40 0 0 {USE TEMP B-TREE FOR ORDER BY}} do_execsql_test where3-5.3 { EXPLAIN QUERY PLAN SELECT bbb.title AS tag_title @@ -345,7 +345,7 @@ do_execsql_test where3-5.3 { AND LENGTH(bbb.title) > 0 AND bbb.parent = 4 ORDER BY bbb.title COLLATE NOCASE ASC; -} {10 0 0 {SEARCH TABLE aaa USING INDEX $AAA_333_48C951EC (fk=?) (~10 rows)} 16 0 0 {SEARCH TABLE aaa AS bbb USING INDEX $PRIMARY_561FED3E (id=?) (~1 row)} 38 0 0 {USE TEMP B-TREE FOR ORDER BY}} +} {12 0 0 {SEARCH TABLE aaa USING INDEX $AAA_333_48C951EC (fk=?) (~10 rows)} 18 0 0 {SEARCH TABLE aaa AS bbb USING INDEX $PRIMARY_561FED3E (id=?) (~1 row)} 40 0 0 {USE TEMP B-TREE FOR ORDER BY}} do_test where3-6.setup { db eval { CREATE TABLE t6w(a, w TEXT); diff --git a/tests/yast.test/where9.test b/tests/yast.test/where9.test index 5bd5c96fc0..57eba3856f 100644 --- a/tests/yast.test/where9.test +++ b/tests/yast.test/where9.test @@ -455,7 +455,7 @@ ifcapable explain { # do_test where9-5.1 { queryplan { SELECT a FROM t1 WHERE b>1000 AND (c=31031 OR d IS NULL) } - } {92 93 97 nosort (id=5, parent=0, notused=0, detail='MULTI-INDEX OR') (id=6, parent=5, notused=0, detail='INDEX 1') t1 t1c (id=20, parent=5, notused=0, detail='INDEX 2') t1 t1d} + } {92 93 97 nosort (id=5, parent=0, notused=0, detail='MULTI-INDEX OR') (id=6, parent=5, notused=0, detail='INDEX 1') t1 t1c (id=21, parent=5, notused=0, detail='INDEX 2') t1 t1d} # In contrast, b=1000 is preferred over any OR-clause. #