From 356a6c43d2ae409788b266c08e92cd862342e3ef Mon Sep 17 00:00:00 2001 From: Jack Christensen Date: Tue, 1 Jan 2019 13:47:37 -0600 Subject: [PATCH] Avoid allocating strings in common message types --- conn.go | 2 +- pgconn/pgconn.go | 62 ++++++++++++++++-------------------- pgmock/pgmock.go | 20 ++++++------ pgproto3/command_complete.go | 6 ++-- pgproto3/row_description.go | 4 +-- tx_test.go | 2 +- 6 files changed, 45 insertions(+), 51 deletions(-) diff --git a/conn.go b/conn.go index bb1b4c0c..822b7b3e 100644 --- a/conn.go +++ b/conn.go @@ -959,7 +959,7 @@ func (c *Conn) rxReadyForQuery(msg *pgproto3.ReadyForQuery) { func (c *Conn) rxRowDescription(msg *pgproto3.RowDescription) []FieldDescription { fields := make([]FieldDescription, len(msg.Fields)) for i := 0; i < len(fields); i++ { - fields[i].Name = msg.Fields[i].Name + fields[i].Name = string(msg.Fields[i].Name) fields[i].Table = pgtype.OID(msg.Fields[i].TableOID) fields[i].AttributeNumber = msg.Fields[i].TableAttributeNumber fields[i].DataType = pgtype.OID(msg.Fields[i].DataTypeOID) diff --git a/pgconn/pgconn.go b/pgconn/pgconn.go index ec5413de..df823042 100644 --- a/pgconn/pgconn.go +++ b/pgconn/pgconn.go @@ -199,25 +199,7 @@ func connect(ctx context.Context, config *Config, fallbackConfig *FallbackConfig // handled by ReceiveMessage case *pgproto3.ErrorResponse: pgConn.conn.Close() - return nil, &PgError{ - Severity: msg.Severity, - Code: msg.Code, - Message: msg.Message, - Detail: msg.Detail, - Hint: msg.Hint, - Position: msg.Position, - InternalPosition: msg.InternalPosition, - InternalQuery: msg.InternalQuery, - Where: msg.Where, - SchemaName: msg.SchemaName, - TableName: msg.TableName, - ColumnName: msg.ColumnName, - DataTypeName: msg.DataTypeName, - ConstraintName: msg.ConstraintName, - File: msg.File, - Line: msg.Line, - Routine: msg.Routine, - } + return nil, errorResponseToPgError(msg) default: pgConn.conn.Close() return nil, errors.New("unexpected message") @@ -348,7 +330,7 @@ func (pgConn *PgConn) ParameterStatus(key string) string { } // CommandTag is the result of an Exec function -type CommandTag string +type CommandTag []byte // RowsAffected returns the number of rows affected. If the CommandTag was not // for a row affecting command (e.g. "CREATE TABLE") then it returns 0. @@ -362,6 +344,10 @@ func (ct CommandTag) RowsAffected() int64 { return n } +func (ct CommandTag) String() string { + return string(ct) +} + // SendExec enqueues the execution of sql via the PostgreSQL simple query protocol. sql may contain multiple queries. // Execution is implicitly wrapped in a transactions unless a transaction is already in progress or sql contains // transaction control statements. It is only sent to the PostgreSQL server when Flush is called. @@ -511,6 +497,7 @@ func (pgConn *PgConn) SendExecParams(sql string, paramValues [][]byte, paramOIDs } pgConn.batchBuf = appendParse(pgConn.batchBuf, "", sql, paramOIDs) + pgConn.batchBuf = appendDescribe(pgConn.batchBuf, 'S', "") pgConn.batchBuf = appendBind(pgConn.batchBuf, "", "", paramFormats, paramValues, resultFormats) pgConn.batchBuf = appendExecute(pgConn.batchBuf, "", 0) pgConn.batchBuf = appendSync(pgConn.batchBuf) @@ -530,6 +517,7 @@ func (pgConn *PgConn) SendExecParams(sql string, paramValues [][]byte, paramOIDs // // Query is only sent to the PostgreSQL server when Flush is called. func (pgConn *PgConn) SendExecPrepared(stmtName string, paramValues [][]byte, paramFormats []int16, resultFormats []int16) { + pgConn.batchBuf = appendDescribe(pgConn.batchBuf, 'S', stmtName) pgConn.batchBuf = appendBind(pgConn.batchBuf, "", stmtName, paramFormats, paramValues, resultFormats) pgConn.batchBuf = appendExecute(pgConn.batchBuf, "", 0) pgConn.batchBuf = appendSync(pgConn.batchBuf) @@ -616,6 +604,12 @@ func (rr *PgResultReader) NextRow() bool { } } +// FieldDescriptions returns the field descriptions for the current result set. The returned slice is only valid until +// the PgResultReader is closed. +func (rr *PgResultReader) FieldDescriptions() []pgproto3.FieldDescription { + return rr.fieldDescriptions +} + // Values returns the current row data. NextRow must have been previously been called. The returned [][]byte is only // valid until the next NextRow call or the PgResultReader is closed. However, the underlying byte data is safe to // retain a reference to and mutate. @@ -914,23 +908,23 @@ func (pgConn *PgConn) Prepare(ctx context.Context, name, sql string, paramOIDs [ func errorResponseToPgError(msg *pgproto3.ErrorResponse) *PgError { return &PgError{ - Severity: msg.Severity, - Code: msg.Code, - Message: msg.Message, - Detail: msg.Detail, - Hint: msg.Hint, + Severity: string(msg.Severity), + Code: string(msg.Code), + Message: string(msg.Message), + Detail: string(msg.Detail), + Hint: string(msg.Hint), Position: msg.Position, InternalPosition: msg.InternalPosition, - InternalQuery: msg.InternalQuery, - Where: msg.Where, - SchemaName: msg.SchemaName, - TableName: msg.TableName, - ColumnName: msg.ColumnName, - DataTypeName: msg.DataTypeName, - ConstraintName: msg.ConstraintName, - File: msg.File, + InternalQuery: string(msg.InternalQuery), + Where: string(msg.Where), + SchemaName: string(msg.SchemaName), + TableName: string(msg.TableName), + ColumnName: string(msg.ColumnName), + DataTypeName: string(msg.DataTypeName), + ConstraintName: string(msg.ConstraintName), + File: string(msg.File), Line: msg.Line, - Routine: msg.Routine, + Routine: string(msg.Routine), } } diff --git a/pgmock/pgmock.go b/pgmock/pgmock.go index 4d15f7b8..04a06f50 100644 --- a/pgmock/pgmock.go +++ b/pgmock/pgmock.go @@ -224,7 +224,7 @@ where ( SendMessage(&pgproto3.ParameterDescription{}), SendMessage(&pgproto3.RowDescription{ Fields: []pgproto3.FieldDescription{ - {Name: "oid", + {Name: []byte("oid"), TableOID: 1247, TableAttributeNumber: 65534, DataTypeOID: 26, @@ -232,7 +232,7 @@ where ( TypeModifier: -1, Format: 0, }, - {Name: "typname", + {Name: []byte("typname"), TableOID: 1247, TableAttributeNumber: 1, DataTypeOID: 19, @@ -435,7 +435,7 @@ where ( steps = append(steps, step) } - steps = append(steps, SendMessage(&pgproto3.CommandComplete{CommandTag: "SELECT 163"})) + steps = append(steps, SendMessage(&pgproto3.CommandComplete{CommandTag: []byte("SELECT 163")})) steps = append(steps, SendMessage(&pgproto3.ReadyForQuery{TxStatus: 'I'})) steps = append(steps, []Step{ @@ -450,7 +450,7 @@ where ( SendMessage(&pgproto3.ParameterDescription{}), SendMessage(&pgproto3.RowDescription{ Fields: []pgproto3.FieldDescription{ - {Name: "oid", + {Name: []byte("oid"), TableOID: 1247, TableAttributeNumber: 65534, DataTypeOID: 26, @@ -458,7 +458,7 @@ where ( TypeModifier: -1, Format: 0, }, - {Name: "typname", + {Name: []byte("typname"), TableOID: 1247, TableAttributeNumber: 1, DataTypeOID: 19, @@ -475,7 +475,7 @@ where ( ExpectMessage(&pgproto3.Execute{}), ExpectMessage(&pgproto3.Sync{}), SendMessage(&pgproto3.BindComplete{}), - SendMessage(&pgproto3.CommandComplete{CommandTag: "SELECT 0"}), + SendMessage(&pgproto3.CommandComplete{CommandTag: []byte("SELECT 0")}), SendMessage(&pgproto3.ReadyForQuery{TxStatus: 'I'}), }...) @@ -491,7 +491,7 @@ where ( SendMessage(&pgproto3.ParameterDescription{}), SendMessage(&pgproto3.RowDescription{ Fields: []pgproto3.FieldDescription{ - {Name: "oid", + {Name: []byte("oid"), TableOID: 1247, TableAttributeNumber: 65534, DataTypeOID: 26, @@ -499,7 +499,7 @@ where ( TypeModifier: -1, Format: 0, }, - {Name: "typname", + {Name: []byte("typname"), TableOID: 1247, TableAttributeNumber: 1, DataTypeOID: 19, @@ -507,7 +507,7 @@ where ( TypeModifier: -1, Format: 0, }, - {Name: "typbasetype", + {Name: []byte("typbasetype"), TableOID: 1247, TableAttributeNumber: 65534, DataTypeOID: 26, @@ -524,7 +524,7 @@ where ( ExpectMessage(&pgproto3.Execute{}), ExpectMessage(&pgproto3.Sync{}), SendMessage(&pgproto3.BindComplete{}), - SendMessage(&pgproto3.CommandComplete{CommandTag: "SELECT 0"}), + SendMessage(&pgproto3.CommandComplete{CommandTag: []byte("SELECT 0")}), SendMessage(&pgproto3.ReadyForQuery{TxStatus: 'I'}), }...) diff --git a/pgproto3/command_complete.go b/pgproto3/command_complete.go index 85848532..ba5a6a63 100644 --- a/pgproto3/command_complete.go +++ b/pgproto3/command_complete.go @@ -8,7 +8,7 @@ import ( ) type CommandComplete struct { - CommandTag string + CommandTag []byte } func (*CommandComplete) Backend() {} @@ -19,7 +19,7 @@ func (dst *CommandComplete) Decode(src []byte) error { return &invalidMessageFormatErr{messageType: "CommandComplete"} } - dst.CommandTag = string(src[:idx]) + dst.CommandTag = src[:idx] return nil } @@ -43,6 +43,6 @@ func (src *CommandComplete) MarshalJSON() ([]byte, error) { CommandTag string }{ Type: "CommandComplete", - CommandTag: src.CommandTag, + CommandTag: string(src.CommandTag), }) } diff --git a/pgproto3/row_description.go b/pgproto3/row_description.go index 3c5a6faa..eb504c60 100644 --- a/pgproto3/row_description.go +++ b/pgproto3/row_description.go @@ -14,7 +14,7 @@ const ( ) type FieldDescription struct { - Name string + Name []byte TableOID uint32 TableAttributeNumber uint16 DataTypeOID uint32 @@ -45,7 +45,7 @@ func (dst *RowDescription) Decode(src []byte) error { if err != nil { return err } - fd.Name = string(bName[:len(bName)-1]) + fd.Name = bName[:len(bName)-1] // Since buf.Next() doesn't return an error if we hit the end of the buffer // check Len ahead of time diff --git a/tx_test.go b/tx_test.go index 32a2ab08..3a091a16 100644 --- a/tx_test.go +++ b/tx_test.go @@ -289,7 +289,7 @@ func TestTxCommitExCancel(t *testing.T) { script.Steps = append(script.Steps, pgmock.PgxInitSteps()...) script.Steps = append(script.Steps, pgmock.ExpectMessage(&pgproto3.Query{String: "begin"}), - pgmock.SendMessage(&pgproto3.CommandComplete{CommandTag: "BEGIN"}), + pgmock.SendMessage(&pgproto3.CommandComplete{CommandTag: []byte("BEGIN")}), pgmock.SendMessage(&pgproto3.ReadyForQuery{TxStatus: 'T'}), pgmock.WaitForClose(), )