diff --git a/java/yb-pgsql/src/test/java/org/yb/pgsql/TestPgBatch.java b/java/yb-pgsql/src/test/java/org/yb/pgsql/TestPgBatch.java index 1515b868a7f2..fd37b0f9ccd1 100644 --- a/java/yb-pgsql/src/test/java/org/yb/pgsql/TestPgBatch.java +++ b/java/yb-pgsql/src/test/java/org/yb/pgsql/TestPgBatch.java @@ -294,7 +294,7 @@ protected void testMultipleStatementsPerQueryHelper(boolean extendedProtocol, startSignal.await(); String query = new String(); if (explicitTransaction) { - query = "BEGIN TRANSACTION ISOLATION LEVEL " + isolationLevel.sql + ";"; + s.execute("BEGIN TRANSACTION ISOLATION LEVEL " + isolationLevel.sql); } else { s.execute( "SET SESSION CHARACTERISTICS AS TRANSACTION ISOLATION LEVEL " + isolationLevel.sql); @@ -306,10 +306,10 @@ protected void testMultipleStatementsPerQueryHelper(boolean extendedProtocol, for (int i = 9; i < 12; i++) { query += String.format("UPDATE t SET v=2 WHERE k=%d;", i); } + s.execute(query); if (explicitTransaction) { - query += "COMMIT;"; + s.execute("COMMIT"); } - s.execute(query); t.join(); assertFalse(failureDetected.get()); @@ -318,32 +318,36 @@ protected void testMultipleStatementsPerQueryHelper(boolean extendedProtocol, expected.add(new Row(i, 2)); } assertRowSet(s, "SELECT * FROM t ORDER BY k", expected); + LOG.info("Parameters: extendedProtocol: " + extendedProtocol + + ", explicitTransaction: " + explicitTransaction + + ", isolationLevel: " + isolationLevel + " success!"); } catch (PSQLException e) { - LOG.info(e.toString()); - // In RR using the simple query protocol, we hit "Restart isn't possible because statement - // isn't one of SELECT/UPDATE/INSERT/DELETE" (due to the "command" being detected as "BEGIN"), - // and expect a PSQLException. - // In RR and RC using the extended query protocol, it executes as above, a batch statement - // after the first statement which can't be retried at the query layer. + LOG.info("Parameters: extendedProtocol: " + extendedProtocol + + ", explicitTransaction: " + explicitTransaction + + ", isolationLevel: " + isolationLevel + " error: " + e); + + // In RR and RC: + // (1) If using the extended query protocol, it executes the multi-statement query as a batch + // statement for which non-first statements which can't be retried at the query layer. + // (2) If using the simple query protocol, retries are blocked due to the error mentioned + // below. assertNotEquals(isolationLevel, SR); - assertTrue(isolationLevel == RR || (isolationLevel == RC && extendedProtocol == true)); - if (isolationLevel == RR) { - assertFalse(explicitTransaction == false && extendedProtocol == false); + if (extendedProtocol) { + assertTrue(e.toString().contains( + "could not serialize access due to concurrent update (query layer retries aren't " + + "supported when executing non-first statement in batch, will be unable to replay " + + "earlier commands)")); + } else { + assertTrue(e.toString().contains( + "could not serialize access due to concurrent update (query layer retries aren't " + + "supported for multi-statement queries issued via the simple query protocol, upvote " + + "github issue #21833 if you want this)")); } return; } // In SR, the query pauses on the docdb side until the conflicting transaction commits. Then the // transaction continues and succeeds. - // In RC, in the simple protocol only, in case of a transaction conflict, the entire query - // string is restarted, giving us correct results. - // In RR, in the simple protocol and where there is no explicit transaction, it's able to retry. - if (isolationLevel == RC) { - assertFalse(extendedProtocol); - } - if (isolationLevel == RR) { - assertFalse(extendedProtocol); - assertFalse(explicitTransaction); - } + assertEquals(isolationLevel, SR); } @Test @@ -357,11 +361,8 @@ public void testMultipleStatementsPerQuery() throws Throwable { testMultipleStatementsPerQueryHelper(true, true, RR); testMultipleStatementsPerQueryHelper(true, true, SR); - // TODO(Piyush): This test crashes in the debug build due to transaction name being null in the - // following line of postgres.c: - // Assert(strcmp(GetCurrentTransactionName(), YB_READ_COMMITTED_INTERNAL_SUB_TXN_NAME) == 0); - // testMultipleStatementsPerQueryHelper(false, false, RC); + testMultipleStatementsPerQueryHelper(false, false, RC); testMultipleStatementsPerQueryHelper(false, false, RR); testMultipleStatementsPerQueryHelper(false, false, SR); diff --git a/java/yb-pgsql/src/test/java/org/yb/pgsql/TestPgTransparentRestarts.java b/java/yb-pgsql/src/test/java/org/yb/pgsql/TestPgTransparentRestarts.java index 43ab3c8f5a4f..243c9a4077a3 100644 --- a/java/yb-pgsql/src/test/java/org/yb/pgsql/TestPgTransparentRestarts.java +++ b/java/yb-pgsql/src/test/java/org/yb/pgsql/TestPgTransparentRestarts.java @@ -211,20 +211,6 @@ public void selectStarShort() throws Exception { ).runTest(); } - /** - * Doing SELECT * operation on short strings with savepoints enabled and created and expect - * restarts to to *not* happen transparently. - *

- * todo - */ - @Test - public void selectStarShortWithSavepoints() throws Exception { - new SavepointStatementTester( - getConnectionBuilder(), - getShortString() - ).runTest(); - } - /** * Same as the previous test but uses PreparedStatements (with no parameters) */ @@ -1206,75 +1192,6 @@ public ResultSet executeQuery(PreparedStatement stmt) throws Exception { } } - /** SavepointStatementTester that uses regular Statement and savepoints in various places */ - private class SavepointStatementTester extends ConcurrentInsertQueryTester { - public SavepointStatementTester( - ConnectionBuilder cb, - String valueToInsert) { - super(cb, valueToInsert, 50 /* numInserts */); - } - - private ThrowingRunnable getRunnableThread( - ConnectionBuilder cb, BooleanSupplier isExecutionDone, String secondSavepointOpString) { - return () -> { - int selectsAttempted = 0; - int selectsRestartRequired = 0; - int selectsSucceeded = 0; - try (Connection selectTxnConn = - cb.withIsolationLevel(IsolationLevel.REPEATABLE_READ).connect(); - Statement stmt = selectTxnConn.createStatement()) { - selectTxnConn.setAutoCommit(false); - for (/* No setup */; !isExecutionDone.getAsBoolean(); ++selectsAttempted) { - try { - stmt.execute("SAVEPOINT a"); - if (!secondSavepointOpString.isEmpty()) { - stmt.execute(secondSavepointOpString); - } - stmt.executeQuery("SELECT count(*) from test_rr"); - selectTxnConn.commit(); - ++selectsSucceeded; - } catch (Exception ex) { - try { - selectTxnConn.rollback(); - } catch (SQLException ex1) { - fail("Rollback failed: " + ex1.getMessage()); - } - if (isRestartReadError(ex)) { - ++selectsRestartRequired; - } - if (!isTxnError(ex)) { - throw ex; - } - } - } - } catch (Exception ex) { - fail("SELECT in savepoint thread failed: " + ex.getMessage()); - } - LOG.info(String.format( - "SELECT in savepoint thread with second savepoint op: \"%s\": selectsSucceeded=%d" + - " selectsAttempted=%d selectsRestartRequired=%d", secondSavepointOpString, - selectsSucceeded, selectsAttempted, selectsRestartRequired)); - assertTrue( - "No SELECTs after second savepoint statement: " + secondSavepointOpString - + " resulted in 'restart read required' on second operation" - + " - but we expected them to!" - + " " + selectsAttempted + " attempted, " + selectsSucceeded + " succeeded", - selectsRestartRequired > 0); - }; - } - - @Override - public List getRunnableThreads( - ConnectionBuilder cb, BooleanSupplier isExecutionDone) { - List runnables = new ArrayList<>(); - runnables.add(getRunnableThread(cb, isExecutionDone, "")); - runnables.add(getRunnableThread(cb, isExecutionDone, "SAVEPOINT b")); - runnables.add(getRunnableThread(cb, isExecutionDone, "ROLLBACK TO a")); - runnables.add(getRunnableThread(cb, isExecutionDone, "RELEASE a")); - return runnables; - } - } - /* DmlTester tests read restarts for DML statements. */ private abstract class DmlTester extends ConcurrentInsertQueryTester { diff --git a/src/postgres/src/backend/access/transam/xact.c b/src/postgres/src/backend/access/transam/xact.c index e2f22e20d164..7e2eab14dae6 100644 --- a/src/postgres/src/backend/access/transam/xact.c +++ b/src/postgres/src/backend/access/transam/xact.c @@ -3646,10 +3646,6 @@ IsInTransactionBlock(bool isTopLevel) if (!isTopLevel) return true; - if (CurrentTransactionState->blockState != TBLOCK_DEFAULT && - CurrentTransactionState->blockState != TBLOCK_STARTED) - return true; - return false; } diff --git a/src/postgres/src/backend/tcop/postgres.c b/src/postgres/src/backend/tcop/postgres.c index 7097d846179a..35f305dab1f5 100644 --- a/src/postgres/src/backend/tcop/postgres.c +++ b/src/postgres/src/backend/tcop/postgres.c @@ -187,6 +187,9 @@ static StringInfoData row_description_buf; /* Flag to mark cache as invalid if discovered within a txn block. */ static bool yb_need_cache_refresh = false; +/* whether or not we are executing a multi-statement query received via simple query protocol */ +static bool yb_is_multi_statement_query = false; + /* * String constants used for redacting text after the password token in * CREATE/ALTER ROLE commands. @@ -1065,6 +1068,7 @@ exec_simple_query(const char *query_string) * transaction block. */ use_implicit_block = (list_length(parsetree_list) > 1); + yb_is_multi_statement_query = use_implicit_block; /* * Run through the raw parsetree(s) and process each one. @@ -4162,21 +4166,21 @@ YBIsDmlCommandTag(const char *command_tag) /* Whether we are allowed to restart current query/txn. */ static bool -yb_is_restart_possible(const ErrorData* edata, +yb_is_restart_possible(ErrorData* edata, int attempt, - const YBQueryRestartData* restart_data, - bool* retries_exhausted, - bool* rc_ignoring_ddl_statement) + const YBQueryRestartData* restart_data) { + if (yb_debug_log_internal_restarts) + elog(LOG, "Error details: edata->message=%s, edata->filename=%s, edata->lineno=%d", + edata->message, edata->filename, edata->lineno); + if (!IsYugaByteEnabled()) { if (yb_debug_log_internal_restarts) - elog(LOG, "Restart isn't possible, YB is not enabled"); + elog(LOG, "query layer retry isn't possible, YB is not enabled"); return false; } - elog(DEBUG1, "Error details: edata->message=%s edata->filename=%s edata->lineno=%d", - edata->message, edata->filename, edata->lineno); bool is_read_restart_error = YBCIsRestartReadError(edata->yb_txn_errcode); bool is_conflict_error = YBCIsTxnConflictError(edata->yb_txn_errcode); bool is_deadlock_error = YBCIsTxnDeadlockError(edata->yb_txn_errcode); @@ -4185,16 +4189,18 @@ yb_is_restart_possible(const ErrorData* edata, { if (yb_debug_log_internal_restarts) elog( - LOG, "Restart isn't possible, code %d isn't a read restart/conflict/deadlock error", - edata->yb_txn_errcode); + LOG, "query layer retry isn't possible, txn error %s isn't one of " + "kConflict/kReadRestart/kDeadlock", YBCTxnErrCodeToString(edata->yb_txn_errcode)); return false; } - if (attempt >= yb_max_query_layer_retries) + if (yb_is_multi_statement_query) { + const char* retry_err = "query layer retries aren't supported for multi-statement queries " + "issued via the simple query protocol, upvote github issue #21833 if you want this"; + edata->message = psprintf("%s (%s)", edata->message, retry_err); if (yb_debug_log_internal_restarts) - elog(LOG, "Query layer is out of retries, retry limit is %d", yb_max_query_layer_retries); - *retries_exhausted = true; + elog(LOG, "%s", retry_err); return false; } @@ -4221,9 +4227,11 @@ yb_is_restart_possible(const ErrorData* edata, * previous statement had executed. */ if (IsYBReadCommitted() && is_deadlock_error) { + const char* retry_err = "query layer retries are not supported for deadlock errors in " + "READ COMMITTED isolation, upvote github issue #18616 if you want this"; + edata->message = psprintf("%s (%s)", edata->message, retry_err); if (yb_debug_log_internal_restarts) - elog(LOG, "Restart isn't possible, encountered deadlock in READ COMMITTED isolation. " - "See #18616"); + elog(LOG, "%s", retry_err); return false; } @@ -4239,8 +4247,13 @@ yb_is_restart_possible(const ErrorData* edata, if ((!IsYBReadCommitted() && YBIsDataSent()) || (IsYBReadCommitted() && YBIsDataSentForCurrQuery())) { - elog(LOG, "Restart isn't possible, data was already sent. Txn error code=%d", - edata->yb_txn_errcode); + const char* retry_err = "query layer retry isn't possible because data was already sent, " + "if this is the read committed isolation (or) the first statement in repeatable read/ " + "serializable isolation transaction, consider increasing the tserver gflag " + "ysql_output_buffer_size"; + edata->message = psprintf("%s (%s)", edata->message, retry_err); + if (yb_debug_log_internal_restarts) + elog(LOG, "%s", retry_err); return false; } @@ -4254,29 +4267,36 @@ yb_is_restart_possible(const ErrorData* edata, */ if (YbIsBatchedExecution() && (GetCurrentCommandId(false) > FirstCommandId)) { - elog(LOG, "Restart isn't possible: executing non-first statement in batch, will be unable " - "to replay earlier commands. Txn error code=%d", edata->yb_txn_errcode); + const char* retry_err = "query layer retries aren't supported when executing non-first " + "statement in batch, will be unable to replay earlier commands"; + edata->message = psprintf("%s (%s)", edata->message, retry_err); + if (yb_debug_log_internal_restarts) + elog(LOG, "%s", retry_err); return false; } - if (!restart_data) + if (attempt >= yb_max_query_layer_retries) { + const char* retry_err = psprintf( + "yb_max_query_layer_retries set to %d are exhausted", yb_max_query_layer_retries); + edata->message = psprintf("%s (%s)", edata->message, retry_err); if (yb_debug_log_internal_restarts) - elog(LOG, "Restart isn't possible, restart data is missing"); + elog(LOG, "%s", retry_err); return false; } - /* can only restart SELECT queries */ - if (!restart_data->query_string) + if (!restart_data) { if (yb_debug_log_internal_restarts) - elog(LOG, "Restart isn't possible, query string is missing"); + elog(LOG, "query layer retry isn't possible, restart data is missing"); return false; } - if (IsSubTransaction() && !IsYBReadCommitted()) { + /* can only restart SELECT queries */ + if (!restart_data->query_string) + { if (yb_debug_log_internal_restarts) - elog(LOG, "Restart isn't possible, savepoints have been used"); + elog(LOG, "query layer retry isn't possible, query string is missing"); return false; } @@ -4307,8 +4327,11 @@ yb_is_restart_possible(const ErrorData* edata, if (IsYBReadCommitted()) { if (YBGetDdlNestingLevel() != 0) { - elog(LOG, "READ COMMITTED retry semantics don't support DDLs"); - *rc_ignoring_ddl_statement = true; + const char* retry_err = "query layer retries aren't supported for DDLs inside a " + "read committed isolation transaction block"; + edata->message = psprintf("%s (%s)", edata->message, retry_err); + if (yb_debug_log_internal_restarts) + elog(LOG, "%s", retry_err); return false; } } @@ -4318,13 +4341,11 @@ yb_is_restart_possible(const ErrorData* edata, // statements that might result in a kReadRestart/kConflict like CREATE INDEX. We don't retry // those as of now. if (yb_debug_log_internal_restarts) - elog(LOG, - "Restart isn't possible because statement isn't one of SELECT/UPDATE/INSERT/DELETE"); + elog(LOG, "query layer retries not possible because statement isn't one of " + "SELECT/UPDATE/INSERT/DELETE"); return false; } - if (yb_debug_log_internal_restarts) - elog(LOG, "Restart is possible"); return true; } @@ -4554,20 +4575,11 @@ yb_attempt_to_restart_on_error(int attempt, */ MemoryContext error_context = MemoryContextSwitchTo(exec_context); ErrorData* edata = CopyErrorData(); - bool retries_exhausted = false; - bool rc_ignoring_ddl_statement = false; - if (yb_is_restart_possible( - edata, attempt, restart_data, &retries_exhausted, &rc_ignoring_ddl_statement)) { + if (yb_is_restart_possible(edata, attempt, restart_data)) { if (yb_debug_log_internal_restarts) - { - ereport(LOG, - (errmsg("Restarting statement due to kReadRestart/kConflict error:" - "\nQuery: %s\nError: %s\nAttempt No: %d", - restart_data->query_string, - edata->message, - attempt))); - } + ereport(LOG, (errmsg("Performing query layer retry with attempt number: %d", attempt))); + /* * Cleanup the error and restart portal. */ @@ -4575,7 +4587,8 @@ yb_attempt_to_restart_on_error(int attempt, if (restart_data->portal_name) { - elog(DEBUG1, "Restarting portal %s for retry", restart_data->portal_name); + if (yb_debug_log_internal_restarts) + elog(LOG, "Restarting portal %s for retry", restart_data->portal_name); yb_restart_portal(restart_data->portal_name); } YBRestoreOutputBufferPosition(); @@ -4586,7 +4599,8 @@ yb_attempt_to_restart_on_error(int attempt, * In this case the txn is not restarted, just the statement is restarted after rolling back * to the internal savepoint registered at start of the statement. */ - elog(DEBUG1, "Rolling back statement"); + if (yb_debug_log_internal_restarts) + elog(LOG, "Rolling back and retrying current statement"); /* * Presence of triggers pushes additional snapshots. Pop all of them. @@ -4655,7 +4669,8 @@ yb_attempt_to_restart_on_error(int attempt, * In this case the txn is restarted, which can be done since we haven't executed even the * first statement fully and no data has been sent to the client. */ - elog(DEBUG1, "Restarting txn"); + if (yb_debug_log_internal_restarts) + elog(LOG, "Restarting transaction"); /* * The txn might or might not have performed writes. Reset the state in @@ -4691,20 +4706,7 @@ yb_attempt_to_restart_on_error(int attempt, } } else { /* if we shouldn't restart - propagate the error */ - - if (rc_ignoring_ddl_statement) { - edata->message = psprintf( - "Read Committed txn cannot proceed because of error in DDL. %s", edata->message); - ReThrowError(edata); - } - - if (retries_exhausted) { - edata->message = psprintf("%s. %s", "All transparent retries exhausted", edata->message); - ReThrowError(edata); - } - - MemoryContextSwitchTo(error_context); - PG_RE_THROW(); + ReThrowError(edata); } } @@ -5378,6 +5380,7 @@ PostgresMain(int argc, char *argv[], if (IsYsqlUpgrade && yb_catalog_version_type != CATALOG_VERSION_CATALOG_TABLE) yb_catalog_version_type = CATALOG_VERSION_UNSET; + yb_is_multi_statement_query = false; } switch (firstchar) diff --git a/src/postgres/src/test/isolation/expected/yb-check-constraint-locking_1.out b/src/postgres/src/test/isolation/expected/yb-check-constraint-locking_1.out index 2e5229596c88..e2456ee2b1f5 100644 --- a/src/postgres/src/test/isolation/expected/yb-check-constraint-locking_1.out +++ b/src/postgres/src/test/isolation/expected/yb-check-constraint-locking_1.out @@ -110,7 +110,7 @@ step s1_upd_price: UPDATE products SET price = 3 WHERE product_id = 1; step s2_upd_disc_price: UPDATE products SET discounted_price = 5 WHERE product_id = 1; step s1_commit: COMMIT; step s2_upd_disc_price: <... completed> -error in steps s1_commit s2_upd_disc_price: ERROR: All transparent retries exhausted. could not serialize access due to concurrent update +error in steps s1_commit s2_upd_disc_price: ERROR: could not serialize access due to concurrent update (yb_max_query_layer_retries set to 0 are exhausted) step s2_commit: COMMIT; step s1_select: SELECT * FROM products; product_id date_added name price discounted_pricedate_expires @@ -128,7 +128,7 @@ step s1_upd_pk_date_added: UPDATE products SET date_added = '2022-06-01 01:00:00 step s2_upd_date_expires: UPDATE products SET date_expires = '2022-05-01 02:00:00' WHERE product_id = 1; step s1_commit: COMMIT; step s2_upd_date_expires: <... completed> -error in steps s1_commit s2_upd_date_expires: ERROR: All transparent retries exhausted. could not serialize access due to concurrent update +error in steps s1_commit s2_upd_date_expires: ERROR: could not serialize access due to concurrent update (yb_max_query_layer_retries set to 0 are exhausted) step s2_commit: COMMIT; step s1_select: SELECT * FROM products; product_id date_added name price discounted_pricedate_expires @@ -149,7 +149,7 @@ step s2_insert_on_conflict1: step s1_commit: COMMIT; step s2_insert_on_conflict1: <... completed> -error in steps s1_commit s2_insert_on_conflict1: ERROR: All transparent retries exhausted. could not serialize access due to concurrent update +error in steps s1_commit s2_insert_on_conflict1: ERROR: could not serialize access due to concurrent update (yb_max_query_layer_retries set to 0 are exhausted) step s2_commit: COMMIT; step s1_select: SELECT * FROM products; product_id date_added name price discounted_pricedate_expires @@ -170,7 +170,7 @@ step s2_insert_on_conflict3: step s1_commit: COMMIT; step s2_insert_on_conflict3: <... completed> -error in steps s1_commit s2_insert_on_conflict3: ERROR: All transparent retries exhausted. could not serialize access due to concurrent update +error in steps s1_commit s2_insert_on_conflict3: ERROR: could not serialize access due to concurrent update (yb_max_query_layer_retries set to 0 are exhausted) step s2_commit: COMMIT; step s1_select: SELECT * FROM products; product_id date_added name price discounted_pricedate_expires diff --git a/src/postgres/src/test/isolation/expected/yb-fk-relationship_1.out b/src/postgres/src/test/isolation/expected/yb-fk-relationship_1.out index 2ec34d1fc69b..32911d48dd02 100644 --- a/src/postgres/src/test/isolation/expected/yb-fk-relationship_1.out +++ b/src/postgres/src/test/isolation/expected/yb-fk-relationship_1.out @@ -31,7 +31,7 @@ step s1_update_fk1: UPDATE tb SET fk1 = 10 WHERE k = 1; step s2_update_fk2: UPDATE tb SET fk2 = 10 WHERE k = 1; step s1_commit: COMMIT; step s2_update_fk2: <... completed> -error in steps s1_commit s2_update_fk2: ERROR: All transparent retries exhausted. could not serialize access due to concurrent update +error in steps s1_commit s2_update_fk2: ERROR: could not serialize access due to concurrent update (yb_max_query_layer_retries set to 0 are exhausted) step s2_commit: COMMIT; step s1_select_tb: SELECT * FROM tb; k fk1 fk2 diff --git a/src/postgres/src/test/isolation/expected/yb-lock-status-waiters.out b/src/postgres/src/test/isolation/expected/yb-lock-status-waiters.out index 66fb29bfd8c6..087f179d972d 100644 --- a/src/postgres/src/test/isolation/expected/yb-lock-status-waiters.out +++ b/src/postgres/src/test/isolation/expected/yb-lock-status-waiters.out @@ -25,7 +25,7 @@ relation foo {WEAK_READ,WEAK_WRITE}t f row foo {STRONG_READ,STRONG_WRITE}t f f {2} f step s1_commit: COMMIT; step s2_insert: <... completed> -error in steps s1_commit s2_insert: ERROR: All transparent retries exhausted. could not serialize access due to concurrent update +error in steps s1_commit s2_insert: ERROR: could not serialize access due to concurrent update (yb_max_query_layer_retries set to 0 are exhausted) step s2_abort: ABORT; starting permutation: s1_insert s2_begin s2_insert s1_lock_status_blockers s1_commit s2_abort @@ -46,5 +46,5 @@ query step s1_commit: COMMIT; step s2_insert: <... completed> -error in steps s1_commit s2_insert: ERROR: All transparent retries exhausted. could not serialize access due to concurrent update +error in steps s1_commit s2_insert: ERROR: could not serialize access due to concurrent update (yb_max_query_layer_retries set to 0 are exhausted) step s2_abort: ABORT; diff --git a/src/postgres/src/test/isolation/expected/yb-lock-status-waiters_1.out b/src/postgres/src/test/isolation/expected/yb-lock-status-waiters_1.out index 02fa36423156..d69894f6a861 100644 --- a/src/postgres/src/test/isolation/expected/yb-lock-status-waiters_1.out +++ b/src/postgres/src/test/isolation/expected/yb-lock-status-waiters_1.out @@ -27,7 +27,7 @@ column foo {STRONG_READ,STRONG_WRITE}t f column foo {STRONG_READ,STRONG_WRITE}t f f {2} 2 11 f step s1_commit: COMMIT; step s2_insert: <... completed> -error in steps s1_commit s2_insert: ERROR: All transparent retries exhausted. could not serialize access due to concurrent update +error in steps s1_commit s2_insert: ERROR: could not serialize access due to concurrent update (yb_max_query_layer_retries set to 0 are exhausted) step s2_abort: ABORT; starting permutation: s1_insert s2_begin s2_insert s1_lock_status_blockers s1_commit s2_abort @@ -48,5 +48,5 @@ query step s1_commit: COMMIT; step s2_insert: <... completed> -error in steps s1_commit s2_insert: ERROR: All transparent retries exhausted. could not serialize access due to concurrent update +error in steps s1_commit s2_insert: ERROR: could not serialize access due to concurrent update (yb_max_query_layer_retries set to 0 are exhausted) step s2_abort: ABORT; diff --git a/src/postgres/src/test/isolation/expected/yb-user-managed-constraint_1.out b/src/postgres/src/test/isolation/expected/yb-user-managed-constraint_1.out index 70d943c53273..9b3d30c4fd34 100644 --- a/src/postgres/src/test/isolation/expected/yb-user-managed-constraint_1.out +++ b/src/postgres/src/test/isolation/expected/yb-user-managed-constraint_1.out @@ -20,7 +20,7 @@ step s1_update: UPDATE t SET v1 = add_with_limit(v1, v2, 35) WHERE k = 1; step s2_update: UPDATE t SET v2 = add_with_limit(v2, v1, 35) WHERE k = 1; step s1_commit: COMMIT; step s2_update: <... completed> -error in steps s1_commit s2_update: ERROR: All transparent retries exhausted. could not serialize access due to concurrent update +error in steps s1_commit s2_update: ERROR: could not serialize access due to concurrent update (yb_max_query_layer_retries set to 0 are exhausted) step s2_commit: COMMIT; step s1_select: select *, v1 + v2 from t; k v1 v2 ?column? diff --git a/src/postgres/src/test/isolation/expected/yb_query_layer_retries_for_a_multi_statement_query.out b/src/postgres/src/test/isolation/expected/yb_query_layer_retries_for_a_multi_statement_query.out new file mode 100644 index 000000000000..3bb3da23fb05 --- /dev/null +++ b/src/postgres/src/test/isolation/expected/yb_query_layer_retries_for_a_multi_statement_query.out @@ -0,0 +1,99 @@ +Parsed test spec with 2 sessions + +starting permutation: s1_begin s1_update s2_implicit_txn_a s1_commit s2_select_test s2_select_test2 +step s1_begin: BEGIN; +step s1_update: UPDATE test SET v=0 where k=1; +step s2_implicit_txn_a: INSERT INTO test2 (v) values (1); UPDATE test SET v=v+1; +step s1_commit: COMMIT; +step s2_implicit_txn_a: <... completed> +error in steps s1_commit s2_implicit_txn_a: ERROR: could not serialize access due to concurrent update (query layer retries aren't supported for multi-statement queries issued via the simple query protocol, upvote github issue #21833 if you want this) +step s2_select_test: SELECT * FROM test; +k v + +1 0 +step s2_select_test2: SELECT * FROM test2; +k v + + +starting permutation: s1_begin s1_update s2_implicit_txn_b s1_commit s2_select_test s2_select_test2 +step s1_begin: BEGIN; +step s1_update: UPDATE test SET v=0 where k=1; +step s2_implicit_txn_b: UPDATE test SET v=v+1; INSERT INTO test2 (v) values (1); +step s1_commit: COMMIT; +step s2_implicit_txn_b: <... completed> +error in steps s1_commit s2_implicit_txn_b: ERROR: could not serialize access due to concurrent update (query layer retries aren't supported for multi-statement queries issued via the simple query protocol, upvote github issue #21833 if you want this) +step s2_select_test: SELECT * FROM test; +k v + +1 0 +step s2_select_test2: SELECT * FROM test2; +k v + + +starting permutation: s1_begin s1_update s2_explicit_txn_block_a s1_commit s2_select_test s2_commit s2_select_test s2_select_test2 +step s1_begin: BEGIN; +step s1_update: UPDATE test SET v=0 where k=1; +step s2_explicit_txn_block_a: BEGIN; INSERT INTO test2 (v) values (1); UPDATE test SET v=v+1; COMMIT; +step s1_commit: COMMIT; +step s2_explicit_txn_block_a: <... completed> +error in steps s1_commit s2_explicit_txn_block_a: ERROR: could not serialize access due to concurrent update (query layer retries aren't supported for multi-statement queries issued via the simple query protocol, upvote github issue #21833 if you want this) +step s2_select_test: SELECT * FROM test; +ERROR: current transaction is aborted, commands ignored until end of transaction block +step s2_commit: COMMIT; +step s2_select_test: SELECT * FROM test; +k v + +1 0 +step s2_select_test2: SELECT * FROM test2; +k v + + +starting permutation: s1_begin s1_update s2_explicit_txn_block_b s1_commit s2_select_test s2_commit s2_select_test s2_select_test2 +step s1_begin: BEGIN; +step s1_update: UPDATE test SET v=0 where k=1; +step s2_explicit_txn_block_b: BEGIN; UPDATE test SET v=v+1; INSERT INTO test2 (v) values (1); COMMIT; +step s1_commit: COMMIT; +step s2_explicit_txn_block_b: <... completed> +error in steps s1_commit s2_explicit_txn_block_b: ERROR: could not serialize access due to concurrent update (query layer retries aren't supported for multi-statement queries issued via the simple query protocol, upvote github issue #21833 if you want this) +step s2_select_test: SELECT * FROM test; +ERROR: current transaction is aborted, commands ignored until end of transaction block +step s2_commit: COMMIT; +step s2_select_test: SELECT * FROM test; +k v + +1 0 +step s2_select_test2: SELECT * FROM test2; +k v + + +starting permutation: s1_begin s1_update s2_statement_after_commit_a s1_commit s2_select_test s2_select_test2 +step s1_begin: BEGIN; +step s1_update: UPDATE test SET v=0 where k=1; +step s2_statement_after_commit_a: BEGIN; INSERT INTO test2 (v) values (1); COMMIT; UPDATE test SET v=v+1; +step s1_commit: COMMIT; +step s2_statement_after_commit_a: <... completed> +error in steps s1_commit s2_statement_after_commit_a: ERROR: could not serialize access due to concurrent update (query layer retries aren't supported for multi-statement queries issued via the simple query protocol, upvote github issue #21833 if you want this) +step s2_select_test: SELECT * FROM test; +k v + +1 0 +step s2_select_test2: SELECT * FROM test2; +k v + +1 1 + +starting permutation: s1_begin s1_update s2_statement_after_commit_b s1_commit s2_select_test s2_select_test2 +step s1_begin: BEGIN; +step s1_update: UPDATE test SET v=0 where k=1; +step s2_statement_after_commit_b: BEGIN; INSERT INTO test2 (v) values (1); COMMIT; INSERT INTO test2 (v) values (2); UPDATE test SET v=v+1; +step s1_commit: COMMIT; +step s2_statement_after_commit_b: <... completed> +error in steps s1_commit s2_statement_after_commit_b: ERROR: could not serialize access due to concurrent update (query layer retries aren't supported for multi-statement queries issued via the simple query protocol, upvote github issue #21833 if you want this) +step s2_select_test: SELECT * FROM test; +k v + +1 0 +step s2_select_test2: SELECT * FROM test2; +k v + +1 1 diff --git a/src/postgres/src/test/isolation/specs/yb_query_layer_retries_for_a_multi_statement_query.spec b/src/postgres/src/test/isolation/specs/yb_query_layer_retries_for_a_multi_statement_query.spec new file mode 100644 index 000000000000..fc7d40a70e0c --- /dev/null +++ b/src/postgres/src/test/isolation/specs/yb_query_layer_retries_for_a_multi_statement_query.spec @@ -0,0 +1,49 @@ +setup +{ + CREATE TABLE test (k INT PRIMARY KEY, v INT); + INSERT INTO test values (1, 1); + CREATE TABLE test2 (k SERIAL PRIMARY KEY, v INT); +} + +teardown +{ + DROP TABLE test, test2; +} + +session "s1" +step "s1_begin" { BEGIN; } +step "s1_update" { UPDATE test SET v=0 where k=1; } +step "s1_commit" { COMMIT; } + +session "s2" +step "s2_implicit_txn_a" { INSERT INTO test2 (v) values (1); UPDATE test SET v=v+1; } +step "s2_implicit_txn_b" { UPDATE test SET v=v+1; INSERT INTO test2 (v) values (1); } +step "s2_explicit_txn_block_a" { BEGIN; INSERT INTO test2 (v) values (1); UPDATE test SET v=v+1; COMMIT; } +step "s2_explicit_txn_block_b" { BEGIN; UPDATE test SET v=v+1; INSERT INTO test2 (v) values (1); COMMIT; } +step "s2_statement_after_commit_a" { BEGIN; INSERT INTO test2 (v) values (1); COMMIT; UPDATE test SET v=v+1; } +step "s2_statement_after_commit_b" { BEGIN; INSERT INTO test2 (v) values (1); COMMIT; INSERT INTO test2 (v) values (2); UPDATE test SET v=v+1; } +step "s2_commit" { COMMIT; } +step "s2_select_test" { SELECT * FROM test; } +step "s2_select_test2" { SELECT * FROM test2; } + +# Test to ensure that statements in an implicit transaction are not retried. +permutation "s1_begin" "s1_update" "s2_implicit_txn_a" "s1_commit" "s2_select_test" "s2_select_test2" +permutation "s1_begin" "s1_update" "s2_implicit_txn_b" "s1_commit" "s2_select_test" "s2_select_test2" + +# Test to ensure that a statement within an explicit transaction block isn't retried, be it the +# first statement, or a later one. +# +# Note that the error is thrown back to the external client immediately and later statements of the +# query are not executed. This leaves the transaction block in an open state requiring a closing +# statement before new statements. Without closing the block, we will receive a +# "current transaction is aborted, commands ignored until end of transaction block" error. Pg has +# this same quirk: it can't be seen with an RC transaction because a conflict will be resolved in Pg +# using READ COMMITTED update checking rules. So, to observe it in Pg, use REPEATABLE READ in s2. +permutation "s1_begin" "s1_update" "s2_explicit_txn_block_a" "s1_commit" "s2_select_test" "s2_commit" "s2_select_test" "s2_select_test2" +permutation "s1_begin" "s1_update" "s2_explicit_txn_block_b" "s1_commit" "s2_select_test" "s2_commit" "s2_select_test" "s2_select_test2" + +# Test to ensure that a statement in an implicit transaction after an intervening "COMMIT;" isn't +# retried. The s2_statement_after_commit_b case is to ensure that the tablet test2 only has 1 row +# inserted (i.e., the whole implicit transaction after the "commit;" is aborted). +permutation "s1_begin" "s1_update" "s2_statement_after_commit_a" "s1_commit" "s2_select_test" "s2_select_test2" +permutation "s1_begin" "s1_update" "s2_statement_after_commit_b" "s1_commit" "s2_select_test" "s2_select_test2" diff --git a/src/postgres/src/test/isolation/yb_pg_isolation_schedule b/src/postgres/src/test/isolation/yb_pg_isolation_schedule index 184662dd5893..bbbbcffcd8ab 100644 --- a/src/postgres/src/test/isolation/yb_pg_isolation_schedule +++ b/src/postgres/src/test/isolation/yb_pg_isolation_schedule @@ -48,4 +48,5 @@ test: skip-locked-3 test: tuplelock-conflict test: multixact-no-forget test: drop-probe-waiting-on-aborted-blocker-subtxn -test: yb_conflict_with_index_only_scan_in_serializable \ No newline at end of file +test: yb_conflict_with_index_only_scan_in_serializable +test: yb_query_layer_retries_for_a_multi_statement_query diff --git a/src/yb/yql/pggate/util/ybc_util.cc b/src/yb/yql/pggate/util/ybc_util.cc index 1cd8f49bea6e..2731eee1c227 100644 --- a/src/yb/yql/pggate/util/ybc_util.cc +++ b/src/yb/yql/pggate/util/ybc_util.cc @@ -325,6 +325,10 @@ bool YBCIsTxnAbortedError(uint16_t txn_errcode) { return txn_errcode == to_underlying(TransactionErrorCode::kAborted); } +const char* YBCTxnErrCodeToString(uint16_t txn_errcode) { + return YBCPAllocStdString(ToString(TransactionErrorCode(txn_errcode))); +} + uint16_t YBCGetTxnConflictErrorCode() { return to_underlying(TransactionErrorCode::kConflict); } diff --git a/src/yb/yql/pggate/util/ybc_util.h b/src/yb/yql/pggate/util/ybc_util.h index 216b23d3f591..f0bb3caaef45 100644 --- a/src/yb/yql/pggate/util/ybc_util.h +++ b/src/yb/yql/pggate/util/ybc_util.h @@ -208,6 +208,7 @@ bool YBCIsTxnConflictError(uint16_t txn_errcode); bool YBCIsTxnSkipLockingError(uint16_t txn_errcode); bool YBCIsTxnDeadlockError(uint16_t txn_errcode); bool YBCIsTxnAbortedError(uint16_t txn_errcode); +const char* YBCTxnErrCodeToString(uint16_t txn_errcode); uint16_t YBCGetTxnConflictErrorCode(); void YBCResolveHostname(); diff --git a/src/yb/yql/pgwrapper/pg_wait_on_conflict-test.cc b/src/yb/yql/pgwrapper/pg_wait_on_conflict-test.cc index 1b0e0ff79b4a..acc5c4550669 100644 --- a/src/yb/yql/pgwrapper/pg_wait_on_conflict-test.cc +++ b/src/yb/yql/pgwrapper/pg_wait_on_conflict-test.cc @@ -859,7 +859,9 @@ TEST_F(PgWaitQueuesTest, YB_DISABLE_TEST_IN_TSAN(TablegroupUpdateAndSelectForSha auto value = conn2.FetchRow("select v from t1 where k=1 for share"); // Should detect the conflict and raise serializable error. ASSERT_NOK(value); - ASSERT_TRUE(value.status().message().Contains("All transparent retries exhausted")); + ASSERT_TRUE(value.status().message().Contains( + "could not serialize access due to concurrent update (yb_max_query_layer_retries set to 0 " + "are exhausted)")); }); SleepFor(1s);