@@ -60,6 +60,30 @@ type Session struct {
6060 // pipelining
6161 ParallelPipeline ParallelPipelineConfig
6262 ResponseQueue * ResponseQueue
63+
64+ // inExtendedQuery is true when the current message being handled is an
65+ // extended query protocol message (Parse, Bind, Describe, Execute, Close).
66+ // This lets Session.ErrorCode behave correctly for both protocols.
67+ inExtendedQuery bool
68+
69+ // discardUntilSync is set when an error occurs during extended query
70+ // processing. Per the PostgreSQL protocol, after an error the server must
71+ // discard messages until it receives a Sync, then respond with
72+ // ReadyForQuery.
73+ discardUntilSync bool
74+ }
75+
76+ // isExtendedQueryMessage returns true for message types that belong to the
77+ // extended query protocol and should not emit ReadyForQuery on error.
78+ func isExtendedQueryMessage (t types.ClientMessage ) bool {
79+ switch t {
80+ case types .ClientParse , types .ClientBind , types .ClientDescribe ,
81+ types .ClientExecute , types .ClientClose ,
82+ types .ClientSync , types .ClientFlush :
83+ return true
84+ default :
85+ return false
86+ }
6387}
6488
6589// consumeCommands consumes incoming commands sent over the Postgres wire connection.
@@ -90,9 +114,11 @@ func (srv *Session) consumeSingleCommand(ctx context.Context, reader *buffer.Rea
90114 return err
91115 }
92116
117+ srv .inExtendedQuery = isExtendedQueryMessage (t )
118+
93119 // NOTE: we could recover from this scenario
94120 if errors .Is (err , buffer .ErrMessageSizeExceeded ) {
95- err = handleMessageSizeExceeded (reader , writer , err )
121+ err = srv . handleMessageSizeExceeded (reader , writer , err )
96122 if err != nil {
97123 return err
98124 }
@@ -130,7 +156,7 @@ func (srv *Session) consumeSingleCommand(ctx context.Context, reader *buffer.Rea
130156// type. A fatal error is returned when an unexpected error is returned while
131157// consuming the expected message size or when attempting to write the error
132158// message back to the client.
133- func handleMessageSizeExceeded (reader * buffer.Reader , writer * buffer.Writer , exceeded error ) (err error ) {
159+ func ( srv * Session ) handleMessageSizeExceeded (reader * buffer.Reader , writer * buffer.Writer , exceeded error ) (err error ) {
134160 unwrapped , has := buffer .UnwrapMessageSizeExceeded (exceeded )
135161 if ! has {
136162 return exceeded
@@ -141,14 +167,21 @@ func handleMessageSizeExceeded(reader *buffer.Reader, writer *buffer.Writer, exc
141167 return err
142168 }
143169
144- return ErrorCode (writer , exceeded )
170+ return srv . ErrorCode (writer , exceeded )
145171}
146172
147173// handleCommand handles the given client message. A client message includes a
148174// message type and reader buffer containing the actual message. The type
149175// indicates a action executed by the client.
150176// https://www.postgresql.org/docs/14/protocol-message-formats.html
151177func (srv * Session ) handleCommand (ctx context.Context , conn net.Conn , t types.ClientMessage , reader * buffer.Reader , writer * buffer.Writer ) error {
178+ // Per the PostgreSQL protocol, after an error during extended query
179+ // processing the server discards all messages until it receives a Sync.
180+ if srv .discardUntilSync && t != types .ClientSync && t != types .ClientTerminate {
181+ srv .logger .Debug ("discarding message until sync" , slog .String ("type" , t .String ()))
182+ return nil
183+ }
184+
152185 switch t {
153186 case types .ClientSimpleQuery :
154187 return srv .handleSimpleQuery (ctx , reader , writer )
@@ -232,9 +265,7 @@ func (srv *Session) handleCommand(ctx context.Context, conn net.Conn, t types.Cl
232265 // https://github.com/postgres/postgres/blob/6e1dd2773eb60a6ab87b27b8d9391b756e904ac3/src/backend/tcop/postgres.c#L4295
233266 return nil
234267 case types .ClientClose :
235- writer .Start (types .ServerCloseComplete ) //nolint:errcheck
236- writer .End () //nolint:errcheck
237- return nil
268+ return srv .handleClose (ctx , reader , writer )
238269 case types .ClientTerminate :
239270 err := srv .handleConnTerminate (ctx )
240271 if err != nil {
@@ -248,13 +279,13 @@ func (srv *Session) handleCommand(ctx context.Context, conn net.Conn, t types.Cl
248279
249280 return io .EOF
250281 default :
251- return ErrorCode (writer , NewErrUnimplementedMessageType (t ))
282+ return srv . ErrorCode (writer , NewErrUnimplementedMessageType (t ))
252283 }
253284}
254285
255286func (srv * Session ) handleSimpleQuery (ctx context.Context , reader * buffer.Reader , writer * buffer.Writer ) error {
256287 if srv .parse == nil {
257- return ErrorCode (writer , NewErrUnimplementedMessageType (types .ClientSimpleQuery ))
288+ return srv . ErrorCode (writer , NewErrUnimplementedMessageType (types .ClientSimpleQuery ))
258289 }
259290
260291 query , err := reader .GetString ()
@@ -280,23 +311,23 @@ func (srv *Session) handleSimpleQuery(ctx context.Context, reader *buffer.Reader
280311
281312 statements , err := srv .parse (ctx , query )
282313 if err != nil {
283- return ErrorCode (writer , err )
314+ return srv . ErrorCode (writer , err )
284315 }
285316
286317 if len (statements ) == 0 {
287- return ErrorCode (writer , NewErrUndefinedStatement ())
318+ return srv . ErrorCode (writer , NewErrUndefinedStatement ())
288319 }
289320
290321 // NOTE: it is possible to send multiple statements in one simple query.
291322 for index := range statements {
292323 err = statements [index ].columns .Define (ctx , writer , nil )
293324 if err != nil {
294- return ErrorCode (writer , err )
325+ return srv . ErrorCode (writer , err )
295326 }
296327
297- err = statements [index ].fn (ctx , NewDataWriter (ctx , statements [index ].columns , nil , NoLimit , reader , writer ), nil )
328+ err = statements [index ].fn (ctx , NewDataWriter (ctx , srv , statements [index ].columns , nil , NoLimit , reader , writer ), nil )
298329 if err != nil {
299- return ErrorCode (writer , err )
330+ return srv . ErrorCode (writer , err )
300331 }
301332 }
302333
@@ -309,7 +340,7 @@ func (srv *Session) handleParse(ctx context.Context, reader *buffer.Reader, writ
309340 if srv .ParallelPipeline .Enabled {
310341 return srv .drainQueueAndWriteError (ctx , writer , err )
311342 }
312- return ErrorCode (writer , err )
343+ return srv . ErrorCode (writer , err )
313344 }
314345
315346 name , err := reader .GetString ()
@@ -347,14 +378,14 @@ func (srv *Session) handleParse(ctx context.Context, reader *buffer.Reader, writ
347378
348379 statement , err := singleStatement (srv .parse (ctx , query ))
349380 if err != nil {
350- return ErrorCode (writer , err )
381+ return srv . ErrorCode (writer , err )
351382 }
352383
353384 srv .logger .Debug ("incoming extended query" , slog .String ("query" , query ), slog .String ("name" , name ), slog .Int ("parameters" , len (statement .parameters )))
354385
355386 err = srv .Statements .Set (ctx , name , statement )
356387 if err != nil {
357- return ErrorCode (writer , err )
388+ return srv . ErrorCode (writer , err )
358389 }
359390
360391 writer .Start (types .ServerParseComplete )
@@ -404,7 +435,7 @@ func (srv *Session) handleDescribe(ctx context.Context, reader *buffer.Reader, w
404435 }
405436
406437 if statement == nil {
407- return ErrorCode (writer , errors .New ("unknown statement" ))
438+ return srv . ErrorCode (writer , errors .New ("unknown statement" ))
408439 }
409440
410441 if err := srv .writeParameterDescription (writer , statement .parameters ); err != nil {
@@ -419,13 +450,13 @@ func (srv *Session) handleDescribe(ctx context.Context, reader *buffer.Reader, w
419450 }
420451
421452 if portal == nil {
422- return ErrorCode (writer , errors .New ("unknown portal" ))
453+ return srv . ErrorCode (writer , errors .New ("unknown portal" ))
423454 }
424455
425456 return srv .writeColumnDescription (ctx , writer , portal .formats , portal .statement .columns )
426457 }
427458
428- return ErrorCode (writer , fmt .Errorf ("unknown describe command: %s" , string (d [0 ])))
459+ return srv . ErrorCode (writer , fmt .Errorf ("unknown describe command: %s" , string (d [0 ])))
429460}
430461
431462// describePipelined handles Describe in parallel pipeline mode
@@ -644,7 +675,7 @@ func (srv *Session) handleExecute(ctx context.Context, reader *buffer.Reader, wr
644675 if srv .ParallelPipeline .Enabled {
645676 return srv .drainQueueAndWriteError (ctx , writer , err )
646677 }
647- return ErrorCode (writer , err )
678+ return srv . ErrorCode (writer , err )
648679 }
649680
650681 name , err := reader .GetString ()
@@ -667,12 +698,37 @@ func (srv *Session) handleExecute(ctx context.Context, reader *buffer.Reader, wr
667698
668699 err = srv .Portals .Execute (ctx , name , Limit (limit ), reader , writer )
669700 if err != nil {
670- return ErrorCode (writer , err )
701+ return srv . ErrorCode (writer , err )
671702 }
672703
673704 return nil
674705}
675706
707+ // handleClose handles the Close message (extended query protocol).
708+ // The Close message tells the server to close a prepared statement or portal.
709+ // https://www.postgresql.org/docs/current/protocol-message-formats.html
710+ func (srv * Session ) handleClose (ctx context.Context , reader * buffer.Reader , writer * buffer.Writer ) error {
711+ d , err := reader .GetBytes (1 )
712+ if err != nil {
713+ return err
714+ }
715+
716+ name , err := reader .GetString ()
717+ if err != nil {
718+ return err
719+ }
720+
721+ srv .logger .Debug ("incoming close request" , slog .String ("type" , string (d [0 ])), slog .String ("name" , name ))
722+
723+ if srv .ParallelPipeline .Enabled {
724+ srv .ResponseQueue .Enqueue (NewCloseCompleteEvent ())
725+ return nil
726+ }
727+
728+ writer .Start (types .ServerCloseComplete )
729+ return writer .End ()
730+ }
731+
676732// executePipelined handles Execute in parallel pipeline mode
677733func (srv * Session ) executePipelined (ctx context.Context , writer * buffer.Writer , name string , limit uint32 ) error {
678734 portal , err := srv .Portals .Get (ctx , name )
@@ -732,6 +788,9 @@ func (srv *Session) handleSync(ctx context.Context, writer *buffer.Writer) error
732788 }
733789 }
734790
791+ // Sync always resets discardUntilSync, even if queue processing set it.
792+ srv .discardUntilSync = false
793+
735794 // Original synchronous behavior - just return ReadyForQuery
736795 return readyForQuery (writer , types .ServerIdle )
737796}
@@ -747,7 +806,7 @@ func (srv *Session) processResponseQueue(ctx context.Context, writer *buffer.Wri
747806 }
748807
749808 if queueErr != nil {
750- if err := ErrorCode (writer , queueErr ); err != nil {
809+ if err := srv . ErrorCode (writer , queueErr ); err != nil {
751810 return err
752811 }
753812 }
@@ -782,7 +841,7 @@ func (srv *Session) drainQueueOnError(ctx context.Context, writer *buffer.Writer
782841 srv .ResponseQueue .Clear ()
783842
784843 if queueErr != nil {
785- return ErrorCode (writer , queueErr )
844+ return srv . ErrorCode (writer , queueErr )
786845 }
787846
788847 return nil
@@ -794,7 +853,7 @@ func (srv *Session) drainQueueAndWriteError(ctx context.Context, writer *buffer.
794853 if drainErr := srv .drainQueueOnError (ctx , writer ); drainErr != nil {
795854 return drainErr
796855 }
797- return ErrorCode (writer , err )
856+ return srv . ErrorCode (writer , err )
798857}
799858
800859func singleStatement (stmts PreparedStatements , err error ) (* PreparedStatement , error ) {
@@ -824,6 +883,10 @@ func (srv *Session) writeQueuedResponse(ctx context.Context, writer *buffer.Writ
824883 writer .Start (types .ServerBindComplete )
825884 return writer .End ()
826885
886+ case ResponseCloseComplete :
887+ writer .Start (types .ServerCloseComplete )
888+ return writer .End ()
889+
827890 case ResponseStmtDescribe :
828891 // Statement Describe writes ParameterDescription followed by RowDescription
829892 if err := srv .writeParameterDescription (writer , event .Parameters ); err != nil {
@@ -845,12 +908,12 @@ func (srv *Session) writeQueuedResponse(ctx context.Context, writer *buffer.Writ
845908
846909 // Check for execution error
847910 if err := event .Result .GetError (); err != nil {
848- return ErrorCode (writer , err )
911+ return srv . ErrorCode (writer , err )
849912 }
850913
851914 // Use DataWriter for correct encoding
852915 // Note: We use NoLimit here because the result is already limited during execution
853- dataWriter := NewDataWriter (ctx , event .Result .Columns (), event .Formats , NoLimit , nil , writer )
916+ dataWriter := NewDataWriter (ctx , srv , event .Result .Columns (), event .Formats , NoLimit , nil , writer )
854917
855918 return event .Result .Replay (ctx , dataWriter )
856919
0 commit comments