@@ -1711,6 +1711,49 @@ func TestConnExecStatement(t *testing.T) {
17111711 ensureConnValid (t , pgConn )
17121712}
17131713
1714+ // https://github.com/jackc/pgx/issues/2626
1715+ func TestConnExecStatementCursorFetch (t * testing.T ) {
1716+ t .Parallel ()
1717+
1718+ ctx , cancel := context .WithTimeout (context .Background (), 120 * time .Second )
1719+ defer cancel ()
1720+
1721+ pgConn , err := pgconn .Connect (ctx , os .Getenv ("PGX_TEST_DATABASE" ))
1722+ require .NoError (t , err )
1723+ defer closeConn (t , pgConn )
1724+
1725+ if pgConn .ParameterStatus ("crdb_version" ) != "" {
1726+ t .Skip ("Server does not support cursors in implicit transactions" )
1727+ }
1728+
1729+ // Prepare the FETCH before the cursor exists. The server describes the result as NoData so the statement
1730+ // description has no fields. The actual fields are only known at execution time.
1731+ sd , err := pgConn .Prepare (ctx , "ps_fetch" , `fetch all in "exec_statement_cursor"` , nil )
1732+ require .NoError (t , err )
1733+ require .Empty (t , sd .Fields )
1734+
1735+ // DECLARE CURSOR requires an explicit transaction block.
1736+ _ , err = pgConn .Exec (ctx , "begin" ).ReadAll ()
1737+ require .NoError (t , err )
1738+
1739+ _ , err = pgConn .Exec (ctx , `declare "exec_statement_cursor" cursor for select n, n::text from generate_series(1, 3) n` ).ReadAll ()
1740+ require .NoError (t , err )
1741+
1742+ result := pgConn .ExecStatement (ctx , sd , nil , nil , nil ).Read ()
1743+ require .NoError (t , result .Err )
1744+ require .Len (t , result .FieldDescriptions , 2 )
1745+ require .Equal (t , uint32 (pgtype .Int4OID ), result .FieldDescriptions [0 ].DataTypeOID )
1746+ require .Equal (t , uint32 (pgtype .TextOID ), result .FieldDescriptions [1 ].DataTypeOID )
1747+ require .Len (t , result .Rows , 3 )
1748+ require .Equal (t , "1" , string (result .Rows [0 ][0 ]))
1749+ require .Equal (t , "3" , string (result .Rows [2 ][1 ]))
1750+
1751+ _ , err = pgConn .Exec (ctx , "rollback" ).ReadAll ()
1752+ require .NoError (t , err )
1753+
1754+ ensureConnValid (t , pgConn )
1755+ }
1756+
17141757type byteCounterConn struct {
17151758 conn net.Conn
17161759 bytesRead int
@@ -3682,6 +3725,71 @@ func TestPipelineQueryStatementBindError(t *testing.T) {
36823725 ensureConnValid (t , pgConn )
36833726}
36843727
3728+ // https://github.com/jackc/pgx/issues/2626
3729+ func TestPipelineQueryStatementCursorFetch (t * testing.T ) {
3730+ t .Parallel ()
3731+
3732+ ctx , cancel := context .WithTimeout (context .Background (), 120 * time .Second )
3733+ defer cancel ()
3734+
3735+ pgConn , err := pgconn .Connect (ctx , os .Getenv ("PGX_TEST_DATABASE" ))
3736+ require .NoError (t , err )
3737+ defer closeConn (t , pgConn )
3738+
3739+ if pgConn .ParameterStatus ("crdb_version" ) != "" {
3740+ t .Skip ("Server does not support cursors in implicit transactions" )
3741+ }
3742+
3743+ // Prepare the FETCH before the cursor exists. The server describes the result as NoData so the statement
3744+ // description has no fields. The actual fields are only known at execution time.
3745+ sd , err := pgConn .Prepare (ctx , "ps_fetch" , `fetch all in "pipeline_cursor"` , nil )
3746+ require .NoError (t , err )
3747+ require .Empty (t , sd .Fields )
3748+
3749+ // DECLARE CURSOR requires an explicit transaction block.
3750+ _ , err = pgConn .Exec (ctx , "begin" ).ReadAll ()
3751+ require .NoError (t , err )
3752+
3753+ pipeline := pgConn .StartPipeline (ctx )
3754+ pipeline .SendQueryParams (`declare "pipeline_cursor" cursor for select n, n::text from generate_series(1, 3) n` , nil , nil , nil , nil )
3755+ pipeline .SendQueryStatement (sd , nil , nil , nil )
3756+ err = pipeline .Sync ()
3757+ require .NoError (t , err )
3758+
3759+ results , err := pipeline .GetResults ()
3760+ require .NoError (t , err )
3761+ rr , ok := results .(* pgconn.ResultReader )
3762+ require .Truef (t , ok , "expected ResultReader, got: %#v" , results )
3763+ readResult := rr .Read ()
3764+ require .NoError (t , readResult .Err )
3765+
3766+ results , err = pipeline .GetResults ()
3767+ require .NoError (t , err )
3768+ rr , ok = results .(* pgconn.ResultReader )
3769+ require .Truef (t , ok , "expected ResultReader, got: %#v" , results )
3770+ readResult = rr .Read ()
3771+ require .NoError (t , readResult .Err )
3772+ require .Len (t , readResult .FieldDescriptions , 2 )
3773+ require .Equal (t , uint32 (pgtype .Int4OID ), readResult .FieldDescriptions [0 ].DataTypeOID )
3774+ require .Equal (t , uint32 (pgtype .TextOID ), readResult .FieldDescriptions [1 ].DataTypeOID )
3775+ require .Len (t , readResult .Rows , 3 )
3776+ require .Equal (t , "1" , string (readResult .Rows [0 ][0 ]))
3777+ require .Equal (t , "3" , string (readResult .Rows [2 ][1 ]))
3778+
3779+ results , err = pipeline .GetResults ()
3780+ require .NoError (t , err )
3781+ _ , ok = results .(* pgconn.PipelineSync )
3782+ require .Truef (t , ok , "expected PipelineSync, got: %#v" , results )
3783+
3784+ err = pipeline .Close ()
3785+ require .NoError (t , err )
3786+
3787+ _ , err = pgConn .Exec (ctx , "rollback" ).ReadAll ()
3788+ require .NoError (t , err )
3789+
3790+ ensureConnValid (t , pgConn )
3791+ }
3792+
36853793func TestPipelineGetResultsNilResultsOnError (t * testing.T ) {
36863794 t .Parallel ()
36873795
0 commit comments