From 1213b6977451a98e03ac7db11dbc41e52f08154b Mon Sep 17 00:00:00 2001 From: Yuli Khodorkovskiy Date: Wed, 16 Dec 2020 03:42:55 +0000 Subject: [PATCH] Add support to ErrorResponse for unlocalized severity Add missing 'V' field for unlocalized severity added in PG versions 9.6 and greater. See https://www.postgresql.org/docs/current/protocol-error-fields.html --- error_response.go | 43 ++++++++++++++++++++++++++----------------- frontend_test.go | 37 +++++++++++++++++++++++++++++++++++++ 2 files changed, 63 insertions(+), 17 deletions(-) diff --git a/error_response.go b/error_response.go index d444798b..4eb0a196 100644 --- a/error_response.go +++ b/error_response.go @@ -7,23 +7,24 @@ import ( ) type ErrorResponse struct { - Severity string - Code string - Message string - Detail string - Hint string - Position int32 - InternalPosition int32 - InternalQuery string - Where string - SchemaName string - TableName string - ColumnName string - DataTypeName string - ConstraintName string - File string - Line int32 - Routine string + Severity string + SeverityUnlocalized string // only in 9.6 and greater + Code string + Message string + Detail string + Hint string + Position int32 + InternalPosition int32 + InternalQuery string + Where string + SchemaName string + TableName string + ColumnName string + DataTypeName string + ConstraintName string + File string + Line int32 + Routine string UnknownFields map[byte]string } @@ -56,6 +57,8 @@ func (dst *ErrorResponse) Decode(src []byte) error { switch k { case 'S': dst.Severity = v + case 'V': + dst.SeverityUnlocalized = v case 'C': dst.Code = v case 'M': @@ -123,6 +126,11 @@ func (src *ErrorResponse) marshalBinary(typeByte byte) []byte { buf.WriteString(src.Severity) buf.WriteByte(0) } + if src.SeverityUnlocalized != "" { + buf.WriteByte('V') + buf.WriteString(src.SeverityUnlocalized) + buf.WriteByte(0) + } if src.Code != "" { buf.WriteByte('C') buf.WriteString(src.Code) @@ -210,6 +218,7 @@ func (src *ErrorResponse) marshalBinary(typeByte byte) []byte { buf.WriteString(v) buf.WriteByte(0) } + buf.WriteByte(0) binary.BigEndian.PutUint32(buf.Bytes()[1:5], uint32(buf.Len()-1)) diff --git a/frontend_test.go b/frontend_test.go index 002da759..d202451f 100644 --- a/frontend_test.go +++ b/frontend_test.go @@ -6,6 +6,7 @@ import ( "github.com/jackc/pgproto3/v2" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) type interruptReader struct { @@ -78,3 +79,39 @@ func TestFrontendReceiveUnexpectedEOF(t *testing.T) { assert.Nil(t, msg) assert.Equal(t, io.ErrUnexpectedEOF, err) } + +func TestErrorResponse(t *testing.T) { + t.Parallel() + + want := &pgproto3.ErrorResponse{ + Severity: "ERROR", + SeverityUnlocalized: "ERROR", + Message: `column "foo" does not exist`, + File: "parse_relation.c", + Code: "42703", + Position: 8, + Line: 3513, + Routine: "errorMissingColumn", + } + + raw := []byte{ + 'E', 0, 0, 0, 'f', + 'S', 'E', 'R', 'R', 'O', 'R', 0, + 'V', 'E', 'R', 'R', 'O', 'R', 0, + 'C', '4', '2', '7', '0', '3', 0, + 'M', 'c', 'o', 'l', 'u', 'm', 'n', 32, '"', 'f', 'o', 'o', '"', 32, 'd', 'o', 'e', 's', 32, 'n', 'o', 't', 32, 'e', 'x', 'i', 's', 't', 0, + 'P', '8', 0, + 'F', 'p', 'a', 'r', 's', 'e', '_', 'r', 'e', 'l', 'a', 't', 'i', 'o', 'n', '.', 'c', 0, + 'L', '3', '5', '1', '3', 0, + 'R', 'e', 'r', 'r', 'o', 'r', 'M', 'i', 's', 's', 'i', 'n', 'g', 'C', 'o', 'l', 'u', 'm', 'n', 0, 0, + } + + server := &interruptReader{} + server.push(raw) + + frontend := pgproto3.NewFrontend(pgproto3.NewChunkReader(server), nil) + + got, err := frontend.Receive() + require.NoError(t, err) + assert.Equal(t, want, got) +}