@@ -60,6 +60,31 @@ 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+ // Flush, Sync). This lets Session.ErrorCode behave correctly for both
67+ // protocols.
68+ inExtendedQuery bool
69+
70+ // discardUntilSync is set when an error occurs during extended query
71+ // processing. Per the PostgreSQL protocol, after an error the server must
72+ // discard messages until it receives a Sync, then respond with
73+ // ReadyForQuery.
74+ discardUntilSync bool
75+ }
76+
77+ // isExtendedQueryMessage returns true for message types that belong to the
78+ // extended query protocol and should not emit ReadyForQuery on error.
79+ func isExtendedQueryMessage (t types.ClientMessage ) bool {
80+ switch t {
81+ case types .ClientParse , types .ClientBind , types .ClientDescribe ,
82+ types .ClientExecute , types .ClientClose ,
83+ types .ClientSync , types .ClientFlush :
84+ return true
85+ default :
86+ return false
87+ }
6388}
6489
6590// consumeCommands consumes incoming commands sent over the Postgres wire connection.
@@ -90,9 +115,11 @@ func (srv *Session) consumeSingleCommand(ctx context.Context, reader *buffer.Rea
90115 return err
91116 }
92117
118+ srv .inExtendedQuery = isExtendedQueryMessage (t )
119+
93120 // NOTE: we could recover from this scenario
94121 if errors .Is (err , buffer .ErrMessageSizeExceeded ) {
95- err = handleMessageSizeExceeded (reader , writer , err )
122+ err = srv . handleMessageSizeExceeded (reader , writer , err )
96123 if err != nil {
97124 return err
98125 }
@@ -130,7 +157,7 @@ func (srv *Session) consumeSingleCommand(ctx context.Context, reader *buffer.Rea
130157// type. A fatal error is returned when an unexpected error is returned while
131158// consuming the expected message size or when attempting to write the error
132159// message back to the client.
133- func handleMessageSizeExceeded (reader * buffer.Reader , writer * buffer.Writer , exceeded error ) (err error ) {
160+ func ( srv * Session ) handleMessageSizeExceeded (reader * buffer.Reader , writer * buffer.Writer , exceeded error ) (err error ) {
134161 unwrapped , has := buffer .UnwrapMessageSizeExceeded (exceeded )
135162 if ! has {
136163 return exceeded
@@ -141,14 +168,21 @@ func handleMessageSizeExceeded(reader *buffer.Reader, writer *buffer.Writer, exc
141168 return err
142169 }
143170
144- return ErrorCode (writer , exceeded )
171+ return srv . ErrorCode (writer , exceeded )
145172}
146173
147174// handleCommand handles the given client message. A client message includes a
148175// message type and reader buffer containing the actual message. The type
149176// indicates a action executed by the client.
150177// https://www.postgresql.org/docs/14/protocol-message-formats.html
151178func (srv * Session ) handleCommand (ctx context.Context , conn net.Conn , t types.ClientMessage , reader * buffer.Reader , writer * buffer.Writer ) error {
179+ // Per the PostgreSQL protocol, after an error during extended query
180+ // processing the server discards all messages until it receives a Sync.
181+ if srv .discardUntilSync && t != types .ClientSync && t != types .ClientTerminate {
182+ srv .logger .Debug ("discarding message until sync" , slog .String ("type" , t .String ()))
183+ return nil
184+ }
185+
152186 switch t {
153187 case types .ClientSimpleQuery :
154188 return srv .handleSimpleQuery (ctx , reader , writer )
@@ -232,9 +266,7 @@ func (srv *Session) handleCommand(ctx context.Context, conn net.Conn, t types.Cl
232266 // https://github.com/postgres/postgres/blob/6e1dd2773eb60a6ab87b27b8d9391b756e904ac3/src/backend/tcop/postgres.c#L4295
233267 return nil
234268 case types .ClientClose :
235- writer .Start (types .ServerCloseComplete ) //nolint:errcheck
236- writer .End () //nolint:errcheck
237- return nil
269+ return srv .handleClose (ctx , reader , writer )
238270 case types .ClientTerminate :
239271 err := srv .handleConnTerminate (ctx )
240272 if err != nil {
@@ -248,13 +280,13 @@ func (srv *Session) handleCommand(ctx context.Context, conn net.Conn, t types.Cl
248280
249281 return io .EOF
250282 default :
251- return ErrorCode (writer , NewErrUnimplementedMessageType (t ))
283+ return srv . ErrorCode (writer , NewErrUnimplementedMessageType (t ))
252284 }
253285}
254286
255287func (srv * Session ) handleSimpleQuery (ctx context.Context , reader * buffer.Reader , writer * buffer.Writer ) error {
256288 if srv .parse == nil {
257- return ErrorCode (writer , NewErrUnimplementedMessageType (types .ClientSimpleQuery ))
289+ return srv . ErrorCode (writer , NewErrUnimplementedMessageType (types .ClientSimpleQuery ))
258290 }
259291
260292 query , err := reader .GetString ()
@@ -280,23 +312,23 @@ func (srv *Session) handleSimpleQuery(ctx context.Context, reader *buffer.Reader
280312
281313 statements , err := srv .parse (ctx , query )
282314 if err != nil {
283- return ErrorCode (writer , err )
315+ return srv . ErrorCode (writer , err )
284316 }
285317
286318 if len (statements ) == 0 {
287- return ErrorCode (writer , NewErrUndefinedStatement ())
319+ return srv . ErrorCode (writer , NewErrUndefinedStatement ())
288320 }
289321
290322 // NOTE: it is possible to send multiple statements in one simple query.
291323 for index := range statements {
292324 err = statements [index ].columns .Define (ctx , writer , nil )
293325 if err != nil {
294- return ErrorCode (writer , err )
326+ return srv . ErrorCode (writer , err )
295327 }
296328
297- err = statements [index ].fn (ctx , NewDataWriter (ctx , statements [index ].columns , nil , NoLimit , reader , writer ), nil )
329+ err = statements [index ].fn (ctx , NewDataWriter (ctx , srv , statements [index ].columns , nil , NoLimit , reader , writer ), nil )
298330 if err != nil {
299- return ErrorCode (writer , err )
331+ return srv . ErrorCode (writer , err )
300332 }
301333 }
302334
@@ -309,7 +341,7 @@ func (srv *Session) handleParse(ctx context.Context, reader *buffer.Reader, writ
309341 if srv .ParallelPipeline .Enabled {
310342 return srv .drainQueueAndWriteError (ctx , writer , err )
311343 }
312- return ErrorCode (writer , err )
344+ return srv . ErrorCode (writer , err )
313345 }
314346
315347 name , err := reader .GetString ()
@@ -347,14 +379,14 @@ func (srv *Session) handleParse(ctx context.Context, reader *buffer.Reader, writ
347379
348380 statement , err := singleStatement (srv .parse (ctx , query ))
349381 if err != nil {
350- return ErrorCode (writer , err )
382+ return srv . ErrorCode (writer , err )
351383 }
352384
353385 srv .logger .Debug ("incoming extended query" , slog .String ("query" , query ), slog .String ("name" , name ), slog .Int ("parameters" , len (statement .parameters )))
354386
355387 err = srv .Statements .Set (ctx , name , statement )
356388 if err != nil {
357- return ErrorCode (writer , err )
389+ return srv . ErrorCode (writer , err )
358390 }
359391
360392 writer .Start (types .ServerParseComplete )
@@ -404,7 +436,7 @@ func (srv *Session) handleDescribe(ctx context.Context, reader *buffer.Reader, w
404436 }
405437
406438 if statement == nil {
407- return ErrorCode (writer , errors .New ("unknown statement" ))
439+ return srv . ErrorCode (writer , errors .New ("unknown statement" ))
408440 }
409441
410442 if err := srv .writeParameterDescription (writer , statement .parameters ); err != nil {
@@ -419,13 +451,13 @@ func (srv *Session) handleDescribe(ctx context.Context, reader *buffer.Reader, w
419451 }
420452
421453 if portal == nil {
422- return ErrorCode (writer , errors .New ("unknown portal" ))
454+ return srv . ErrorCode (writer , errors .New ("unknown portal" ))
423455 }
424456
425457 return srv .writeColumnDescription (ctx , writer , portal .formats , portal .statement .columns )
426458 }
427459
428- return ErrorCode (writer , fmt .Errorf ("unknown describe command: %s" , string (d [0 ])))
460+ return srv . ErrorCode (writer , fmt .Errorf ("unknown describe command: %s" , string (d [0 ])))
429461}
430462
431463// describePipelined handles Describe in parallel pipeline mode
@@ -644,7 +676,7 @@ func (srv *Session) handleExecute(ctx context.Context, reader *buffer.Reader, wr
644676 if srv .ParallelPipeline .Enabled {
645677 return srv .drainQueueAndWriteError (ctx , writer , err )
646678 }
647- return ErrorCode (writer , err )
679+ return srv . ErrorCode (writer , err )
648680 }
649681
650682 name , err := reader .GetString ()
@@ -667,12 +699,37 @@ func (srv *Session) handleExecute(ctx context.Context, reader *buffer.Reader, wr
667699
668700 err = srv .Portals .Execute (ctx , name , Limit (limit ), reader , writer )
669701 if err != nil {
670- return ErrorCode (writer , err )
702+ return srv . ErrorCode (writer , err )
671703 }
672704
673705 return nil
674706}
675707
708+ // handleClose handles the Close message (extended query protocol).
709+ // The Close message tells the server to close a prepared statement or portal.
710+ // https://www.postgresql.org/docs/current/protocol-message-formats.html
711+ func (srv * Session ) handleClose (ctx context.Context , reader * buffer.Reader , writer * buffer.Writer ) error {
712+ d , err := reader .GetBytes (1 )
713+ if err != nil {
714+ return err
715+ }
716+
717+ name , err := reader .GetString ()
718+ if err != nil {
719+ return err
720+ }
721+
722+ srv .logger .Debug ("incoming close request" , slog .String ("type" , string (d [0 ])), slog .String ("name" , name ))
723+
724+ if srv .ParallelPipeline .Enabled {
725+ srv .ResponseQueue .Enqueue (NewCloseCompleteEvent ())
726+ return nil
727+ }
728+
729+ writer .Start (types .ServerCloseComplete )
730+ return writer .End ()
731+ }
732+
676733// executePipelined handles Execute in parallel pipeline mode
677734func (srv * Session ) executePipelined (ctx context.Context , writer * buffer.Writer , name string , limit uint32 ) error {
678735 portal , err := srv .Portals .Get (ctx , name )
@@ -732,6 +789,9 @@ func (srv *Session) handleSync(ctx context.Context, writer *buffer.Writer) error
732789 }
733790 }
734791
792+ // Sync always resets discardUntilSync, even if queue processing set it.
793+ srv .discardUntilSync = false
794+
735795 // Original synchronous behavior - just return ReadyForQuery
736796 return readyForQuery (writer , types .ServerIdle )
737797}
@@ -747,7 +807,7 @@ func (srv *Session) processResponseQueue(ctx context.Context, writer *buffer.Wri
747807 }
748808
749809 if queueErr != nil {
750- if err := ErrorCode (writer , queueErr ); err != nil {
810+ if err := srv . ErrorCode (writer , queueErr ); err != nil {
751811 return err
752812 }
753813 }
@@ -782,7 +842,7 @@ func (srv *Session) drainQueueOnError(ctx context.Context, writer *buffer.Writer
782842 srv .ResponseQueue .Clear ()
783843
784844 if queueErr != nil {
785- return ErrorCode (writer , queueErr )
845+ return srv . ErrorCode (writer , queueErr )
786846 }
787847
788848 return nil
@@ -794,7 +854,7 @@ func (srv *Session) drainQueueAndWriteError(ctx context.Context, writer *buffer.
794854 if drainErr := srv .drainQueueOnError (ctx , writer ); drainErr != nil {
795855 return drainErr
796856 }
797- return ErrorCode (writer , err )
857+ return srv . ErrorCode (writer , err )
798858}
799859
800860func singleStatement (stmts PreparedStatements , err error ) (* PreparedStatement , error ) {
@@ -824,6 +884,10 @@ func (srv *Session) writeQueuedResponse(ctx context.Context, writer *buffer.Writ
824884 writer .Start (types .ServerBindComplete )
825885 return writer .End ()
826886
887+ case ResponseCloseComplete :
888+ writer .Start (types .ServerCloseComplete )
889+ return writer .End ()
890+
827891 case ResponseStmtDescribe :
828892 // Statement Describe writes ParameterDescription followed by RowDescription
829893 if err := srv .writeParameterDescription (writer , event .Parameters ); err != nil {
@@ -845,12 +909,12 @@ func (srv *Session) writeQueuedResponse(ctx context.Context, writer *buffer.Writ
845909
846910 // Check for execution error
847911 if err := event .Result .GetError (); err != nil {
848- return ErrorCode (writer , err )
912+ return srv . ErrorCode (writer , err )
849913 }
850914
851915 // Use DataWriter for correct encoding
852916 // 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 )
917+ dataWriter := NewDataWriter (ctx , srv , event .Result .Columns (), event .Formats , NoLimit , nil , writer )
854918
855919 return event .Result .Replay (ctx , dataWriter )
856920
0 commit comments