From 5477fd3f5ca36144ccb2f7ca6bf43dbbe0e62380 Mon Sep 17 00:00:00 2001 From: Rafi Shamim Date: Wed, 14 Apr 2021 15:34:30 -0400 Subject: [PATCH 1/2] sql/pgwire: fix hint for interleaved portal error It looked like this was accidentally using WithSafeDetails, which is meant for sentry reporting. (Originally added in #40197.) Release note: None --- pkg/sql/pgwire/command_result.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/sql/pgwire/command_result.go b/pkg/sql/pgwire/command_result.go index 35551437594e..028103592ccc 100644 --- a/pkg/sql/pgwire/command_result.go +++ b/pkg/sql/pgwire/command_result.go @@ -12,6 +12,7 @@ package pgwire import ( "context" + "fmt" "time" "github.com/cockroachdb/cockroach/pkg/server/telemetry" @@ -484,9 +485,8 @@ func (r *limitedCommandResult) moreResultsNeeded(ctx context.Context) error { default: // We got some other message, but we only support executing to completion. telemetry.Inc(sqltelemetry.InterleavedPortalRequestCounter) - return errors.WithSafeDetails(sql.ErrLimitedResultNotSupported, - "cannot perform operation %T while a different portal is open", - errors.Safe(c)) + return errors.WithDetail(sql.ErrLimitedResultNotSupported, + fmt.Sprintf("cannot perform operation %T while a different portal is open", c)) } prevPos = curPos } From 0ce44436cc16b9e8a9a1f7815f85e8b8b42fe274 Mon Sep 17 00:00:00 2001 From: Rafi Shamim Date: Wed, 14 Apr 2021 17:20:20 -0400 Subject: [PATCH 2/2] sql/pgwire: implicitly destroy portal on txn finish Release note (sql change): Previously, committing a transaction when a portal was suspended would cause a "multiple active portals not supported" error. Now, the portal is automatically destroyed. --- .../testdata/java/src/main/java/MainTest.java | 43 ++ pkg/sql/pgwire/command_result.go | 96 +++- pkg/sql/pgwire/testdata/pgtest/portals | 450 ++++++++++++++++++ 3 files changed, 577 insertions(+), 12 deletions(-) diff --git a/pkg/acceptance/testdata/java/src/main/java/MainTest.java b/pkg/acceptance/testdata/java/src/main/java/MainTest.java index c950d01d03d8..6d3c43ee3c2f 100644 --- a/pkg/acceptance/testdata/java/src/main/java/MainTest.java +++ b/pkg/acceptance/testdata/java/src/main/java/MainTest.java @@ -7,9 +7,11 @@ import java.math.BigDecimal; import java.sql.Array; import java.sql.Connection; +import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.ResultSetMetaData; +import java.sql.Statement; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.List; @@ -339,4 +341,45 @@ public void testVirtualTableMetadata() throws Exception { } } } + + // Regression test for #42912: using setFetchSize in a transaction should + // not cause issues. + @Test + public void testSetFetchSize() throws Exception { + for (int fetchSize = 0; fetchSize <= 3; fetchSize++) { + testSetFetchSize(fetchSize, true); + testSetFetchSize(fetchSize, false); + } + } + + private void testSetFetchSize(int fetchSize, boolean useTransaction) throws Exception { + int expectedResults = fetchSize; + if (fetchSize == 0 || fetchSize == 3) { + expectedResults = 2; + } + + try (final Connection testConn = DriverManager.getConnection(getDBUrl(), "root", "")) { + testConn.setAutoCommit(!useTransaction); + try (final Statement stmt = testConn.createStatement()) { + stmt.setFetchSize(fetchSize); + ResultSet result = stmt.executeQuery("select n from generate_series(0,1) n"); + for (int i = 0; i < expectedResults; i++) { + Assert.assertTrue(result.next()); + Assert.assertEquals(i, result.getInt(1)); + } + if (useTransaction) { + // This should implicitly close the ResultSet (i.e. portal). + testConn.commit(); + if (fetchSize != 0 && fetchSize != 3) { + try { + result.next(); + fail("expected portal to be closed"); + } catch (PSQLException e) { + Assert.assertTrue(e.getMessage().contains("unknown portal")); + } + } + } + } + } + } } diff --git a/pkg/sql/pgwire/command_result.go b/pkg/sql/pgwire/command_result.go index 028103592ccc..e887be3064f6 100644 --- a/pkg/sql/pgwire/command_result.go +++ b/pkg/sql/pgwire/command_result.go @@ -442,24 +442,15 @@ func (r *limitedCommandResult) moreResultsNeeded(ctx context.Context) error { } switch c := cmd.(type) { case sql.DeletePreparedStmt: - // The client wants to close a portal or statement. We - // support the case where it is exactly this - // portal. This is done by closing the portal in - // the same way implicit transactions do, but also - // rewinding the stmtBuf to still point to the portal - // close so that the state machine can do its part of - // the cleanup. We are in effect peeking to see if the + // The client wants to close a portal or statement. We support the case + // where it is exactly this portal. We are in effect peeking to see if the // next message is a delete portal. if c.Type != pgwirebase.PreparePortal || c.Name != r.portalName { telemetry.Inc(sqltelemetry.InterleavedPortalRequestCounter) return errors.WithDetail(sql.ErrLimitedResultNotSupported, "cannot close a portal while a different one is open") } - r.typ = noCompletionMsg - // Rewind to before the delete so the AdvanceOne in - // connExecutor.execCmd ends up back on it. - r.conn.stmtBuf.Rewind(ctx, prevPos) - return sql.ErrLimitedResultClosed + return r.rewindAndClosePortal(ctx, prevPos) case sql.ExecPortal: // The happy case: the client wants more rows from the portal. if c.Name != r.portalName { @@ -483,6 +474,13 @@ func (r *limitedCommandResult) moreResultsNeeded(ctx context.Context) error { return err } default: + // If the portal is immediately followed by a COMMIT, we can proceed and + // let the portal be destroyed at the end of the transaction. + if isCommit, err := r.isCommit(); err != nil { + return err + } else if isCommit { + return r.rewindAndClosePortal(ctx, prevPos) + } // We got some other message, but we only support executing to completion. telemetry.Inc(sqltelemetry.InterleavedPortalRequestCounter) return errors.WithDetail(sql.ErrLimitedResultNotSupported, @@ -491,3 +489,77 @@ func (r *limitedCommandResult) moreResultsNeeded(ctx context.Context) error { prevPos = curPos } } + +// isCommit checks if the statement buffer has a COMMIT at the current +// position. It may either be (1) a COMMIT in the simple protocol, or (2) a +// Parse/Bind/Execute sequence for a COMMIT query. +func (r *limitedCommandResult) isCommit() (bool, error) { + cmd, _, err := r.conn.stmtBuf.CurCmd() + if err != nil { + return false, err + } + // Case 1: Check if cmd is a simple COMMIT statement. + if execStmt, ok := cmd.(sql.ExecStmt); ok { + if _, isCommit := execStmt.AST.(*tree.CommitTransaction); isCommit { + return true, nil + } + } + + commitStmtName := "" + commitPortalName := "" + // Case 2a: Check if cmd is a prepared COMMIT statement. + if prepareStmt, ok := cmd.(sql.PrepareStmt); ok { + if _, isCommit := prepareStmt.AST.(*tree.CommitTransaction); isCommit { + commitStmtName = prepareStmt.Name + } else { + return false, nil + } + } else { + return false, nil + } + + r.conn.stmtBuf.AdvanceOne() + cmd, _, err = r.conn.stmtBuf.CurCmd() + if err != nil { + return false, err + } + // Case 2b: The next cmd must be a bind command. + if bindStmt, ok := cmd.(sql.BindStmt); ok { + // This bind command must be for the COMMIT statement that we just saw. + if bindStmt.PreparedStatementName == commitStmtName { + commitPortalName = bindStmt.PortalName + } else { + return false, nil + } + } else { + return false, nil + } + + r.conn.stmtBuf.AdvanceOne() + cmd, _, err = r.conn.stmtBuf.CurCmd() + if err != nil { + return false, err + } + // Case 2c: The next cmd must be an exec portal command. + if execPortal, ok := cmd.(sql.ExecPortal); ok { + // This exec command must be for the portal that was just bound. + if execPortal.Name == commitPortalName { + return true, nil + } + } + return false, nil +} + +// rewindAndClosePortal closes the portal in the same way implicit transactions +// do, but also rewinds the stmtBuf to still point to the portal close so that +// the state machine can do its part of the cleanup. +func (r *limitedCommandResult) rewindAndClosePortal( + ctx context.Context, rewindTo sql.CmdPos, +) error { + // Don't send an CommandComplete for the portal; it got suspended. + r.typ = noCompletionMsg + // Rewind to before the delete so the AdvanceOne in connExecutor.execCmd ends + // up back on it. + r.conn.stmtBuf.Rewind(ctx, rewindTo) + return sql.ErrLimitedResultClosed +} diff --git a/pkg/sql/pgwire/testdata/pgtest/portals b/pkg/sql/pgwire/testdata/pgtest/portals index adc5a932518b..89001eeacce1 100644 --- a/pkg/sql/pgwire/testdata/pgtest/portals +++ b/pkg/sql/pgwire/testdata/pgtest/portals @@ -485,3 +485,453 @@ ReadyForQuery ---- {"Type":"CommandComplete","CommandTag":"COMMIT"} {"Type":"ReadyForQuery","TxStatus":"I"} + + +## No transaction; small limit + +# 83 = ASCII 'S' for Statement +# 49 = ASCII '1' +# ParameterFormatCodes = [0] for text format +send +Parse {"Name": "s1", "Query": "select n::int4 from generate_series(0,$1::int8) n", "ParameterOIDs": [20]} +Describe {"ObjectType": 83, "Name": "s1"} +Bind {"PreparedStatement": "s1", "ParameterFormatCodes": [0], "ResultFormatCodes": [0], "Parameters": [[49]]} +Execute {"MaxRows": 1} +Sync +---- + +until +ReadyForQuery +---- +{"Type":"ParseComplete"} +{"Type":"ParameterDescription","ParameterOIDs":[20]} +{"Type":"RowDescription","Fields":[{"Name":"n","TableOID":0,"TableAttributeNumber":0,"DataTypeOID":23,"DataTypeSize":4,"TypeModifier":-1,"Format":0}]} +{"Type":"BindComplete"} +{"Type":"DataRow","Values":[{"text":"0"}]} +{"Type":"PortalSuspended"} +{"Type":"ReadyForQuery","TxStatus":"I"} + + +## No transaction; exact limit + +# 83 = ASCII 'S' for Statement +# 49 = ASCII '1' +# ParameterFormatCodes = [0] for text format +send +Parse {"Name": "s2", "Query": "select n::int4 from generate_series(0,$1::int8) n", "ParameterOIDs": [20]} +Describe {"ObjectType": 83, "Name": "s2"} +Bind {"PreparedStatement": "s2", "ParameterFormatCodes": [0], "ResultFormatCodes": [0], "Parameters": [[49]]} +Execute {"MaxRows": 2} +Sync +---- + +until +ReadyForQuery +---- +{"Type":"ParseComplete"} +{"Type":"ParameterDescription","ParameterOIDs":[20]} +{"Type":"RowDescription","Fields":[{"Name":"n","TableOID":0,"TableAttributeNumber":0,"DataTypeOID":23,"DataTypeSize":4,"TypeModifier":-1,"Format":0}]} +{"Type":"BindComplete"} +{"Type":"DataRow","Values":[{"text":"0"}]} +{"Type":"DataRow","Values":[{"text":"1"}]} +{"Type":"PortalSuspended"} +{"Type":"ReadyForQuery","TxStatus":"I"} + + +## No transaction; larger limit + +# 83 = ASCII 'S' for Statement +# 49 = ASCII '1' +# ParameterFormatCodes = [0] for text format +send +Parse {"Name": "s3", "Query": "select n::int4 from generate_series(0,$1::int8) n", "ParameterOIDs": [20]} +Describe {"ObjectType": 83, "Name": "s3"} +Bind {"PreparedStatement": "s3", "ParameterFormatCodes": [0], "ResultFormatCodes": [0], "Parameters": [[49]]} +Execute {"MaxRows": 3} +Sync +---- + +until +ReadyForQuery +---- +{"Type":"ParseComplete"} +{"Type":"ParameterDescription","ParameterOIDs":[20]} +{"Type":"RowDescription","Fields":[{"Name":"n","TableOID":0,"TableAttributeNumber":0,"DataTypeOID":23,"DataTypeSize":4,"TypeModifier":-1,"Format":0}]} +{"Type":"BindComplete"} +{"Type":"DataRow","Values":[{"text":"0"}]} +{"Type":"DataRow","Values":[{"text":"1"}]} +{"Type":"CommandComplete","CommandTag":"SELECT 2"} +{"Type":"ReadyForQuery","TxStatus":"I"} + + +## Transaction; smaller limit + +send +Query {"String": "BEGIN"} +---- + +until +ReadyForQuery +---- +{"Type":"CommandComplete","CommandTag":"BEGIN"} +{"Type":"ReadyForQuery","TxStatus":"T"} + +# 83 = ASCII 'S' for Statement +# 49 = ASCII '1' +# ParameterFormatCodes = [0] for text format +send +Parse {"Name": "s4", "Query": "select n::int4 from generate_series(0,$1::int8) n", "ParameterOIDs": [20]} +Describe {"ObjectType": 83, "Name": "s4"} +Bind {"PreparedStatement": "s4", "ParameterFormatCodes": [0], "ResultFormatCodes": [0], "Parameters": [[49]]} +Execute {"MaxRows": 1} +Sync +---- + +until +ReadyForQuery +---- +{"Type":"ParseComplete"} +{"Type":"ParameterDescription","ParameterOIDs":[20]} +{"Type":"RowDescription","Fields":[{"Name":"n","TableOID":0,"TableAttributeNumber":0,"DataTypeOID":23,"DataTypeSize":4,"TypeModifier":-1,"Format":0}]} +{"Type":"BindComplete"} +{"Type":"DataRow","Values":[{"text":"0"}]} +{"Type":"PortalSuspended"} +{"Type":"ReadyForQuery","TxStatus":"T"} + +send +Query {"String": "COMMIT"} +---- + +until +ReadyForQuery +---- +{"Type":"CommandComplete","CommandTag":"COMMIT"} +{"Type":"ReadyForQuery","TxStatus":"I"} + + +## Transaction with exact limit. + +send +Query {"String": "BEGIN"} +---- + +until +ReadyForQuery +---- +{"Type":"CommandComplete","CommandTag":"BEGIN"} +{"Type":"ReadyForQuery","TxStatus":"T"} + +# 83 = ASCII 'S' for Statement +# 49 = ASCII '1' +# ParameterFormatCodes = [0] for text format +send +Parse {"Name": "s5", "Query": "select n::int4 from generate_series(0,$1::int8) n", "ParameterOIDs": [20]} +Describe {"ObjectType": 83, "Name": "s5"} +Bind {"PreparedStatement": "s5", "ParameterFormatCodes": [0], "ResultFormatCodes": [0], "Parameters": [[49]]} +Execute {"MaxRows": 2} +Sync +---- + +until +ReadyForQuery +---- +{"Type":"ParseComplete"} +{"Type":"ParameterDescription","ParameterOIDs":[20]} +{"Type":"RowDescription","Fields":[{"Name":"n","TableOID":0,"TableAttributeNumber":0,"DataTypeOID":23,"DataTypeSize":4,"TypeModifier":-1,"Format":0}]} +{"Type":"BindComplete"} +{"Type":"DataRow","Values":[{"text":"0"}]} +{"Type":"DataRow","Values":[{"text":"1"}]} +{"Type":"PortalSuspended"} +{"Type":"ReadyForQuery","TxStatus":"T"} + +send +Query {"String": "COMMIT"} +---- + +until +ReadyForQuery +---- +{"Type":"CommandComplete","CommandTag":"COMMIT"} +{"Type":"ReadyForQuery","TxStatus":"I"} + + +## Transaction with larger limit. + +send +Query {"String": "BEGIN"} +---- + +until +ReadyForQuery +---- +{"Type":"CommandComplete","CommandTag":"BEGIN"} +{"Type":"ReadyForQuery","TxStatus":"T"} + +# 83 = ASCII 'S' for Statement +# 49 = ASCII '1' +# ParameterFormatCodes = [0] for text format +send +Parse {"Name": "s6", "Query": "select n::int4 from generate_series(0,$1::int8) n", "ParameterOIDs": [20]} +Describe {"ObjectType": 83, "Name": "s6"} +Bind {"PreparedStatement": "s6", "ParameterFormatCodes": [0], "ResultFormatCodes": [0], "Parameters": [[49]]} +Execute {"MaxRows": 3} +Sync +---- + +until +ReadyForQuery +---- +{"Type":"ParseComplete"} +{"Type":"ParameterDescription","ParameterOIDs":[20]} +{"Type":"RowDescription","Fields":[{"Name":"n","TableOID":0,"TableAttributeNumber":0,"DataTypeOID":23,"DataTypeSize":4,"TypeModifier":-1,"Format":0}]} +{"Type":"BindComplete"} +{"Type":"DataRow","Values":[{"text":"0"}]} +{"Type":"DataRow","Values":[{"text":"1"}]} +{"Type":"CommandComplete","CommandTag":"SELECT 2"} +{"Type":"ReadyForQuery","TxStatus":"T"} + +send +Query {"String": "COMMIT"} +---- + +until +ReadyForQuery +---- +{"Type":"CommandComplete","CommandTag":"COMMIT"} +{"Type":"ReadyForQuery","TxStatus":"I"} + + +## Transaction; smaller limit; COMMIT in extended protocol + +send +Query {"String": "BEGIN"} +---- + +until +ReadyForQuery +---- +{"Type":"CommandComplete","CommandTag":"BEGIN"} +{"Type":"ReadyForQuery","TxStatus":"T"} + +# 83 = ASCII 'S' for Statement +# 49 = ASCII '1' +# ParameterFormatCodes = [0] for text format +send +Parse {"Name": "s7", "Query": "select n::int4 from generate_series(0,$1::int8) n", "ParameterOIDs": [20]} +Describe {"ObjectType": 83, "Name": "s7"} +Bind {"PreparedStatement": "s7", "ParameterFormatCodes": [0], "ResultFormatCodes": [0], "Parameters": [[49]]} +Execute {"MaxRows": 1} +Sync +---- + +until +ReadyForQuery +---- +{"Type":"ParseComplete"} +{"Type":"ParameterDescription","ParameterOIDs":[20]} +{"Type":"RowDescription","Fields":[{"Name":"n","TableOID":0,"TableAttributeNumber":0,"DataTypeOID":23,"DataTypeSize":4,"TypeModifier":-1,"Format":0}]} +{"Type":"BindComplete"} +{"Type":"DataRow","Values":[{"text":"0"}]} +{"Type":"PortalSuspended"} +{"Type":"ReadyForQuery","TxStatus":"T"} + +send +Parse {"Name": "commit1", "Query": "COMMIT"} +Bind {"PreparedStatement": "commit1"} +Execute +Sync +---- + +until +ReadyForQuery +---- +{"Type":"ParseComplete"} +{"Type":"BindComplete"} +{"Type":"CommandComplete","CommandTag":"COMMIT"} +{"Type":"ReadyForQuery","TxStatus":"I"} + + +## Transaction with exact limit; COMMIT in extended protocol + +send +Query {"String": "BEGIN"} +---- + +until +ReadyForQuery +---- +{"Type":"CommandComplete","CommandTag":"BEGIN"} +{"Type":"ReadyForQuery","TxStatus":"T"} + +# 83 = ASCII 'S' for Statement +# 49 = ASCII '1' +# ParameterFormatCodes = [0] for text format +send +Parse {"Name": "s8", "Query": "select n::int4 from generate_series(0,$1::int8) n", "ParameterOIDs": [20]} +Describe {"ObjectType": 83, "Name": "s8"} +Bind {"PreparedStatement": "s8", "ParameterFormatCodes": [0], "ResultFormatCodes": [0], "Parameters": [[49]]} +Execute {"MaxRows": 2} +Sync +---- + +until +ReadyForQuery +---- +{"Type":"ParseComplete"} +{"Type":"ParameterDescription","ParameterOIDs":[20]} +{"Type":"RowDescription","Fields":[{"Name":"n","TableOID":0,"TableAttributeNumber":0,"DataTypeOID":23,"DataTypeSize":4,"TypeModifier":-1,"Format":0}]} +{"Type":"BindComplete"} +{"Type":"DataRow","Values":[{"text":"0"}]} +{"Type":"DataRow","Values":[{"text":"1"}]} +{"Type":"PortalSuspended"} +{"Type":"ReadyForQuery","TxStatus":"T"} + +send +Parse {"Name": "commit2", "Query": "COMMIT"} +Bind {"PreparedStatement": "commit2"} +Execute +Sync +---- + +until +ReadyForQuery +---- +{"Type":"ParseComplete"} +{"Type":"BindComplete"} +{"Type":"CommandComplete","CommandTag":"COMMIT"} +{"Type":"ReadyForQuery","TxStatus":"I"} + + +## Transaction with larger limit; COMMIT in extended protocol + +send +Query {"String": "BEGIN"} +---- + +until +ReadyForQuery +---- +{"Type":"CommandComplete","CommandTag":"BEGIN"} +{"Type":"ReadyForQuery","TxStatus":"T"} + +# 83 = ASCII 'S' for Statement +# 49 = ASCII '1' +# ParameterFormatCodes = [0] for text format +send +Parse {"Name": "s9", "Query": "select n::int4 from generate_series(0,$1::int8) n", "ParameterOIDs": [20]} +Describe {"ObjectType": 83, "Name": "s9"} +Bind {"PreparedStatement": "s9", "ParameterFormatCodes": [0], "ResultFormatCodes": [0], "Parameters": [[49]]} +Execute {"MaxRows": 3} +Sync +---- + +until +ReadyForQuery +---- +{"Type":"ParseComplete"} +{"Type":"ParameterDescription","ParameterOIDs":[20]} +{"Type":"RowDescription","Fields":[{"Name":"n","TableOID":0,"TableAttributeNumber":0,"DataTypeOID":23,"DataTypeSize":4,"TypeModifier":-1,"Format":0}]} +{"Type":"BindComplete"} +{"Type":"DataRow","Values":[{"text":"0"}]} +{"Type":"DataRow","Values":[{"text":"1"}]} +{"Type":"CommandComplete","CommandTag":"SELECT 2"} +{"Type":"ReadyForQuery","TxStatus":"T"} + +send +Parse {"Name": "commit3", "Query": "COMMIT"} +Bind {"PreparedStatement": "commit3"} +Execute +Sync +---- + +until +ReadyForQuery +---- +{"Type":"ParseComplete"} +{"Type":"BindComplete"} +{"Type":"CommandComplete","CommandTag":"COMMIT"} +{"Type":"ReadyForQuery","TxStatus":"I"} + + +## Transaction with smaller limit; portals interleaved + +send +Query {"String": "BEGIN"} +---- + +until +ReadyForQuery +---- +{"Type":"CommandComplete","CommandTag":"BEGIN"} +{"Type":"ReadyForQuery","TxStatus":"T"} + +# 83 = ASCII 'S' for Statement +# 49 = ASCII '1' +# ParameterFormatCodes = [0] for text format +send +Parse {"Name": "s10", "Query": "select n::int4 from generate_series(0,$1::int8) n", "ParameterOIDs": [20]} +Describe {"ObjectType": 83, "Name": "s10"} +Bind {"PreparedStatement": "s10", "ParameterFormatCodes": [0], "ResultFormatCodes": [0], "Parameters": [[49]]} +Execute {"MaxRows": 1} +Sync +---- + +until +ReadyForQuery +---- +{"Type":"ParseComplete"} +{"Type":"ParameterDescription","ParameterOIDs":[20]} +{"Type":"RowDescription","Fields":[{"Name":"n","TableOID":0,"TableAttributeNumber":0,"DataTypeOID":23,"DataTypeSize":4,"TypeModifier":-1,"Format":0}]} +{"Type":"BindComplete"} +{"Type":"DataRow","Values":[{"text":"0"}]} +{"Type":"PortalSuspended"} +{"Type":"ReadyForQuery","TxStatus":"T"} + +send +Parse {"Name": "c4", "Query": "COMMIT"} +Parse {"Name": "s11", "Query": "select n::int4 from generate_series(0,1) n"} +Bind {"DestinationPortal": "por", "PreparedStatement": "s11"} +Bind {"DestinationPortal": "pc4", "PreparedStatement": "c4"} +Execute {"Portal": "por"} +Sync +---- + +until noncrdb_only +ReadyForQuery +---- +{"Type":"ParseComplete"} +{"Type":"ParseComplete"} +{"Type":"BindComplete"} +{"Type":"BindComplete"} +{"Type":"DataRow","Values":[{"text":"0"}]} +{"Type":"DataRow","Values":[{"text":"1"}]} +{"Type":"CommandComplete","CommandTag":"SELECT 2"} +{"Type":"ReadyForQuery","TxStatus":"T"} + +until crdb_only +ErrorResponse +ReadyForQuery +---- +{"Type":"ErrorResponse","Code":"0A000"} +{"Type":"ReadyForQuery","TxStatus":"E"} + +send noncrdb_only +Execute {"Portal": "pc4"} +Sync +---- + +until noncrdb_only +ReadyForQuery +---- +{"Type":"CommandComplete","CommandTag":"COMMIT"} +{"Type":"ReadyForQuery","TxStatus":"I"} + +send crdb_only +Query {"String": "ROLLBACK"} +---- + +until crdb_only +ReadyForQuery +---- +{"Type":"CommandComplete","CommandTag":"ROLLBACK"} +{"Type":"ReadyForQuery","TxStatus":"I"}