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);