Documentation
¶
Overview ¶
Package protocol implements the PostgreSQL v3 wire protocol framing and per-type encode/decode tables used by the celeris native PostgreSQL driver.
Message framing follows the PostgreSQL documentation's "Message Formats" section: a one byte message type (frontend and backend share the same framing, though a handful of type bytes collide across directions) followed by a four byte big-endian length that *includes* the length field itself but *not* the type byte, followed by the payload.
The startup, SSL request, and cancel request messages are exceptions: they have no type byte — just a four byte length and payload. Only the client sends these, and only as the very first message on a connection.
Index ¶
- Constants
- Variables
- func AppendBind(w *Writer, portal, stmt string, paramFormats []int16, paramValues [][]byte, ...)
- func AppendDescribe(w *Writer, kind byte, name string)
- func AppendExecute(w *Writer, portal string, maxRows int32)
- func AppendParse(w *Writer, name, query string, paramOIDs []uint32)
- func AppendSync(w *Writer)
- func DecodeIntBinary(src []byte, codec *TypeCodec) (int64, error)
- func EncodeArrayBinary(dst []byte, elementOID uint32, elements []any) ([]byte, error)
- func ParseCommandComplete(payload []byte) (string, error)
- func ParseDataRow(payload []byte) ([][]byte, error)
- func ParseDataRowInto(dst [][]byte, payload []byte) ([][]byte, error)
- func ParseIntTextASCII(src []byte) (int64, error)
- func ParseParameterDescription(payload []byte) ([]uint32, error)
- func ReadByte(payload []byte, pos *int) (byte, error)
- func ReadBytes(payload []byte, pos *int, n int) ([]byte, error)
- func ReadCString(payload []byte, pos *int) (string, error)
- func ReadInt16(payload []byte, pos *int) (int16, error)
- func ReadInt32(payload []byte, pos *int) (int32, error)
- func RegisterType(c *TypeCodec)
- func RowsAffected(tag string) (int64, bool)
- func RowsAffectedBytes(tag []byte) (int64, bool)
- func WriteBind(w *Writer, portal, stmt string, paramFormats []int16, paramValues [][]byte, ...) []byte
- func WriteClose(w *Writer, kind byte, name string) []byte
- func WriteCopyData(w *Writer, rowBytes []byte) []byte
- func WriteCopyDone(w *Writer) []byte
- func WriteCopyFail(w *Writer, reason string) []byte
- func WriteDescribe(w *Writer, kind byte, name string) []byte
- func WriteExecute(w *Writer, portal string, maxRows int32) []byte
- func WriteFlush(w *Writer) []byte
- func WriteParse(w *Writer, name, query string, paramOIDs []uint32) []byte
- func WriteQuery(w *Writer, sql string) []byte
- func WriteQueryInto(w *Writer, sql string) []byte
- func WriteSync(w *Writer) []byte
- type ColumnDesc
- type CopyInState
- type CopyOutState
- type CopyResponse
- type DecodedArray
- type ExtendedQueryState
- type PGError
- type PreparedStmt
- type Reader
- type SimpleQueryState
- type StartupState
- type TypeCodec
- type Writer
- func (w *Writer) Bytes() []byte
- func (w *Writer) FinishMessage()
- func (w *Writer) Len() int
- func (w *Writer) Reset()
- func (w *Writer) StartMessage(msgType byte)
- func (w *Writer) StartStartupMessage()
- func (w *Writer) WriteByte(b byte) error
- func (w *Writer) WriteBytes(b []byte)
- func (w *Writer) WriteInt16(v int16)
- func (w *Writer) WriteInt32(v int32)
- func (w *Writer) WriteString(s string)
Constants ¶
const ( MsgQuery byte = 'Q' MsgParse byte = 'P' MsgBind byte = 'B' MsgDescribe byte = 'D' MsgExecute byte = 'E' MsgSync byte = 'S' MsgClose byte = 'C' MsgCopyData byte = 'd' MsgCopyDone byte = 'c' MsgCopyFail byte = 'f' MsgTerminate byte = 'X' MsgPasswordMessage byte = 'p' // also carries SASL responses MsgFlush byte = 'H' MsgFunctionCall byte = 'F' )
Frontend (client -> server) message type bytes.
const ( BackendAuthentication byte = 'R' BackendBackendKeyData byte = 'K' BackendParameterStatus byte = 'S' BackendReadyForQuery byte = 'Z' BackendRowDescription byte = 'T' BackendDataRow byte = 'D' BackendCommandComplete byte = 'C' BackendErrorResponse byte = 'E' BackendNoticeResponse byte = 'N' BackendParseComplete byte = '1' BackendBindComplete byte = '2' BackendCloseComplete byte = '3' BackendNoData byte = 'n' BackendParameterDesc byte = 't' BackendCopyData byte = 'd' BackendCopyDone byte = 'c' BackendCopyInResponse byte = 'G' BackendCopyOutResponse byte = 'H' BackendCopyBothResponse byte = 'W' BackendNotification byte = 'A' BackendEmptyQuery byte = 'I' BackendPortalSuspended byte = 's' BackendNegotiateProtocolVersion byte = 'v' )
Backend (server -> client) message type bytes. Some of these collide with frontend type bytes (for instance both directions use 'D', 'C', 'd', 'c') so callers must always interpret the type byte in the context of the direction of the connection.
const ( AuthOK int32 = 0 AuthKerberosV5 int32 = 2 AuthCleartextPassword int32 = 3 AuthMD5Password int32 = 5 AuthGSS int32 = 7 AuthGSSContinue int32 = 8 AuthSSPI int32 = 9 AuthSASL int32 = 10 AuthSASLContinue int32 = 11 AuthSASLFinal int32 = 12 )
Authentication subtypes carried in the 'R' message's int32 body prefix.
const ( OIDBool uint32 = 16 OIDBytea uint32 = 17 OIDInt8 uint32 = 20 OIDInt2 uint32 = 21 OIDInt4 uint32 = 23 OIDText uint32 = 25 OIDJSON uint32 = 114 OIDFloat4 uint32 = 700 OIDFloat8 uint32 = 701 OIDUnknown uint32 = 705 OIDVarchar uint32 = 1043 OIDDate uint32 = 1082 OIDTimestamp uint32 = 1114 OIDTimestamptz uint32 = 1184 OIDNumeric uint32 = 1700 OIDUUID uint32 = 2950 OIDJSONB uint32 = 3802 OIDBoolArr uint32 = 1000 OIDByteaArr uint32 = 1001 OIDInt2Arr uint32 = 1005 OIDInt4Arr uint32 = 1007 OIDTextArr uint32 = 1009 OIDInt8Arr uint32 = 1016 OIDFloat4Arr uint32 = 1021 OIDFloat8Arr uint32 = 1022 OIDVarcharArr uint32 = 1015 OIDUUIDArr uint32 = 2951 )
PostgreSQL type OIDs. These values are stable across PostgreSQL versions and are defined in src/include/catalog/pg_type.dat in the server source.
const ( FormatText int16 = 0 FormatBinary int16 = 1 )
Format codes used in Bind/Describe messages.
const ProtocolVersion int32 = 3 << 16
ProtocolVersion is the v3.0 startup major/minor encoded as a single int32.
Variables ¶
var ( PGInfinity = time.Date(9999, 12, 31, 23, 59, 59, 0, time.UTC) PGNegInfinity = time.Date(-4713, 1, 1, 0, 0, 0, 0, time.UTC) )
PGInfinity and PGNegInfinity are sentinel time.Time values returned when PostgreSQL sends infinity/−infinity for timestamp or date columns. Callers should check for these with time.Equal before arithmetic.
var CopyBinaryHeader = []byte{
'P', 'G', 'C', 'O', 'P', 'Y', '\n', 0xff, '\r', '\n', 0,
0, 0, 0, 0,
0, 0, 0, 0,
}
CopyBinaryHeader is the 19-byte fixed header required at the start of a binary-format COPY IN / COPY OUT stream. Layout:
"PGCOPY\n\377\r\n\0" (11 bytes) | int32 flags (=0) | int32 header ext length (=0)
var CopyBinaryTrailer = []byte{0xff, 0xff}
CopyBinaryTrailer is the 2-byte trailer (int16 = -1) marking the end of a binary COPY stream.
var ErrIncomplete = errors.New("postgres/protocol: incomplete message")
ErrIncomplete is returned by Reader.Next when the internal buffer does not yet hold a full message. Callers should Feed more bytes and retry.
var ErrInvalidLength = errors.New("postgres/protocol: invalid message length")
ErrInvalidLength is returned when a message advertises a length smaller than the mandatory 4-byte length prefix or larger than maxMessageSize.
Functions ¶
func AppendBind ¶
func AppendBind(w *Writer, portal, stmt string, paramFormats []int16, paramValues [][]byte, resultFormats []int16)
AppendBind appends a Bind ('B') message to the Writer without resetting.
func AppendDescribe ¶
AppendDescribe appends a Describe ('D') message to the Writer without resetting.
func AppendExecute ¶
AppendExecute appends an Execute ('E') message to the Writer without resetting.
func AppendParse ¶
AppendParse appends a Parse ('P') message to the Writer without resetting.
func AppendSync ¶
func AppendSync(w *Writer)
AppendSync appends a Sync ('S') message to the Writer without resetting.
func DecodeIntBinary ¶
DecodeIntBinary returns the integer value of src in binary format. Dispatches on codec.OID to pick int2/int4/int8 width. Callers that already know the width may call the type-specific helpers below for the tight inner loop.
func EncodeArrayBinary ¶
EncodeArrayBinary encodes a 1-D slice of values as a PostgreSQL binary array with the given element OID. Nil elements are encoded with length -1.
func ParseCommandComplete ¶
ParseCommandComplete returns the tag string (e.g., "SELECT 2", "INSERT 0 1").
func ParseDataRow ¶
ParseDataRow returns a slice of field slices; nil indicates SQL NULL. Non-nil slices ALIAS payload — callers that retain them must copy.
func ParseDataRowInto ¶
ParseDataRowInto parses a 'D' payload into dst, reusing dst's backing array if large enough. Returns the (possibly reallocated) result slice. Passing dst=nil behaves like ParseDataRow. Non-nil field slices ALIAS payload — callers that retain them must copy.
func ParseIntTextASCII ¶
ParseIntTextASCII parses a PG text-format integer without allocating the intermediate string that strconv.ParseInt would require.
func ParseParameterDescription ¶
ParseParameterDescription returns the parameter type OIDs from a 't' message payload.
func ReadBytes ¶
ReadBytes reads exactly n bytes and advances *pos. The returned slice ALIASES payload — callers that need to retain the bytes beyond the next Reader call must copy.
func ReadCString ¶
ReadCString reads a null-terminated string. The returned string is a copy of the bytes — it does not alias payload. Returns io.ErrShortBuffer if no null terminator is found before the end of payload.
func RegisterType ¶
func RegisterType(c *TypeCodec)
RegisterType registers a custom type codec. Later registrations override earlier ones for the same OID. Safe to call at init() time or at runtime.
func RowsAffected ¶
RowsAffected extracts the row count from a CommandComplete tag. Returns (0, false) if the tag does not carry a row count.
Tags and where their count lives (0-indexed):
INSERT oid rows -> rows is word 2 DELETE rows -> rows is word 1 UPDATE rows -> rows is word 1 SELECT rows -> rows is word 1 MOVE rows -> rows is word 1 FETCH rows -> rows is word 1 COPY rows -> rows is word 1
func RowsAffectedBytes ¶
RowsAffectedBytes is the []byte-input equivalent of RowsAffected. It parses the row count out of a CommandComplete tag without requiring the caller to first allocate a string. Returns (0, false) if the tag does not carry a row count.
func WriteBind ¶
func WriteBind(w *Writer, portal, stmt string, paramFormats []int16, paramValues [][]byte, resultFormats []int16) []byte
WriteBind encodes a Bind ('B') message and returns a copy of the bytes.
paramFormats may be:
- empty — all params are text (format 0)
- length 1 — format code applies to every param
- length N — one code per param
resultFormats follows the same convention for result columns.
paramValues are the already-encoded bytes; nil means SQL NULL (length -1).
func WriteClose ¶
WriteClose encodes a Close ('C') message. kind is 'S' or 'P'.
func WriteCopyData ¶
WriteCopyData encodes a CopyData ('d') message carrying rowBytes.
func WriteCopyDone ¶
WriteCopyDone encodes a CopyDone ('c') message.
func WriteCopyFail ¶
WriteCopyFail encodes a CopyFail ('f') message with the given reason.
func WriteDescribe ¶
WriteDescribe encodes a Describe ('D') message. kind is 'S' (statement) or 'P' (portal).
func WriteExecute ¶
WriteExecute encodes an Execute ('E') message. maxRows=0 means all rows.
func WriteParse ¶
WriteParse encodes a Parse ('P') message and returns a copy of the bytes.
Format:
'P' int32 len | string name | string query | int16 nparams | int32 oids[nparams]
func WriteQuery ¶
WriteQuery encodes a Query ('Q') message into w and returns a copy of the resulting bytes.
func WriteQueryInto ¶
WriteQueryInto encodes a Query ('Q') message into w's buffer without making a separate copy. The returned slice aliases w.Bytes(); the caller must consume it (by passing to loop.Write, which copies before returning) before any other goroutine can Reset the writer.
This is the zero-alloc variant of WriteQuery. Use it when the caller can hold the writer's lock across the loop.Write call.
Types ¶
type ColumnDesc ¶
type ColumnDesc struct {
Name string
TableOID uint32
ColumnAttNum int16
TypeOID uint32
TypeSize int16
TypeModifier int32
FormatCode int16 // 0=text, 1=binary
}
ColumnDesc describes a single column in a RowDescription.
func ParseRowDescription ¶
func ParseRowDescription(payload []byte) ([]ColumnDesc, error)
ParseRowDescription parses a 'T' payload into a slice of ColumnDesc.
func ParseRowDescriptionInto ¶
func ParseRowDescriptionInto(dst []ColumnDesc, payload []byte) ([]ColumnDesc, error)
ParseRowDescriptionInto parses a 'T' payload into dst, reusing dst's backing array if large enough. Returns the (possibly reallocated) result slice. Passing dst=nil behaves like ParseRowDescription.
type CopyInState ¶
type CopyInState struct {
Resp CopyResponse
Tag string
Err *PGError
// contains filtered or unexported fields
}
CopyInState tracks the client side of a COPY FROM STDIN exchange. After sending a Query that triggers COPY FROM, the driver feeds incoming messages here. Once Resp is populated and phase is ready, the driver streams CopyData messages and concludes with CopyDone or CopyFail.
func (*CopyInState) Handle ¶
func (s *CopyInState) Handle(msgType byte, payload []byte) (bool, error)
Handle processes one incoming server message.
func (*CopyInState) Ready ¶
func (s *CopyInState) Ready() bool
Ready reports whether the server has signaled CopyInResponse — i.e. the driver may now send CopyData.
type CopyOutState ¶
type CopyOutState struct {
Resp CopyResponse
Tag string
Err *PGError
// contains filtered or unexported fields
}
CopyOutState tracks the client side of a COPY TO STDOUT exchange.
type CopyResponse ¶
type CopyResponse struct {
Format int8 // 0=text, 1=binary
NumColumns int16
ColumnFormats []int16 // length == NumColumns
}
CopyResponse holds CopyInResponse / CopyOutResponse / CopyBothResponse metadata.
func ParseCopyResponse ¶
func ParseCopyResponse(payload []byte) (CopyResponse, error)
ParseCopyResponse parses a Copy{In,Out,Both}Response payload.
Format:
int8 overall format | int16 N | int16 per-column format * N
type DecodedArray ¶
DecodedArray is a lightweight view over a decoded array value.
func DecodeArrayBinary ¶
func DecodeArrayBinary(src []byte) (*DecodedArray, error)
DecodeArrayBinary decodes a PostgreSQL binary array into a DecodedArray. Elements are decoded by looking up the element OID in the codec table.
type ExtendedQueryState ¶
type ExtendedQueryState struct {
// Set true when the client sent a Describe S or Describe P. If false
// the server will not emit ParameterDescription / RowDescription and
// the state machine jumps straight from BindComplete to executing.
HasDescribe bool
// SkipParse is true when the client re-uses an already-parsed statement
// on this conn and did not emit a Parse message — so no ParseComplete is
// expected. The state machine starts at ExpectBindComplete.
SkipParse bool
ParamOIDs []uint32 // from ParameterDescription (if Describe S)
Columns []ColumnDesc // from RowDescription / NoData
Tag string // materialized on access via TagBytes (see below)
Err *PGError
// contains filtered or unexported fields
}
ExtendedQueryState drives a Parse → Bind → Describe → Execute → Sync round trip. Any ErrorResponse from the server triggers the error phase; the state machine then drains messages until ReadyForQuery, at which point Handle returns done=true with the surfaced PGError.
func (*ExtendedQueryState) Handle ¶
func (e *ExtendedQueryState) Handle( msgType byte, payload []byte, onRow func([][]byte), ) (bool, error)
Handle consumes one server message. onRow is called for each DataRow; payload alias rules match SimpleQueryState.
func (*ExtendedQueryState) Reset ¶
func (e *ExtendedQueryState) Reset()
Reset zeroes the state machine for reuse while preserving the internal fieldScratch / Columns / ParamOIDs / tagBuf backing arrays.
func (*ExtendedQueryState) TagBytes ¶
func (e *ExtendedQueryState) TagBytes() []byte
TagBytes returns the CommandComplete tag as an owned byte slice. Callers that only need RowsAffected should prefer protocol.RowsAffectedBytes(e.TagBytes()) over Tag to skip the string allocation.
type PGError ¶
type PGError struct {
Severity string
Code string // SQLSTATE
Message string
Detail string
Hint string
Position int // 1-based character position in the query, 0 if absent
Extra map[byte]string
}
PGError is a server-sent ErrorResponse. The well-known fields (S, C, M, D, H, P) are exposed as typed members; everything else is captured in Extra for flexibility.
func ParseErrorResponse ¶
ParseErrorResponse parses an ErrorResponse or NoticeResponse payload. Each field is a single-byte code followed by a CString value; the list is terminated by a zero byte.
type PreparedStmt ¶
type PreparedStmt struct {
Name string // "" for unnamed statement
Query string
ParamOIDs []uint32
Columns []ColumnDesc
}
PreparedStmt is the post-describe metadata for a prepared statement.
type Reader ¶
type Reader struct {
// contains filtered or unexported fields
}
Reader decodes a stream of PostgreSQL protocol messages out of a byte buffer that the caller feeds incrementally. Reader is not safe for concurrent use.
func (*Reader) Buffered ¶
Buffered returns the number of bytes currently in the buffer that have not yet been consumed by Next.
func (*Reader) Compact ¶
func (r *Reader) Compact()
Compact discards bytes that have already been consumed. Call periodically (typically after processing a batch of messages) to reclaim space.
func (*Reader) Feed ¶
Feed appends data to the internal buffer. The slice is not retained: the bytes are copied into the Reader's buffer.
func (*Reader) Next ¶
Next returns the type byte and payload of the next complete message in the buffer. The returned payload slice aliases the Reader's internal buffer and is only valid until the next call to Feed, Next, Compact, or Reset. When the buffer does not contain a full message, Next returns ErrIncomplete and the cursor is left unchanged.
type SimpleQueryState ¶
type SimpleQueryState struct {
Columns []ColumnDesc
// Tag is left empty on CommandComplete to avoid the per-query string
// allocation. Callers should use TagBytes() and RowsAffectedBytes()
// instead. If a caller needs the tag as a Go string, they can
// materialize it on demand via string(q.TagBytes()).
//
// Deprecated: use TagBytes() for allocation-free access.
Tag string
Err *PGError
// contains filtered or unexported fields
}
SimpleQueryState drives the client side of a Query ('Q') round trip. After calling WriteQuery, feed server messages one at a time via Handle. Handle returns done=true after ReadyForQuery.
Multi-statement queries are supported: after CommandComplete the state machine returns to phaseAwaitResult, accepting another RowDescription.
func (*SimpleQueryState) Handle ¶
func (q *SimpleQueryState) Handle( msgType byte, payload []byte, onRowDesc func([]ColumnDesc), onRow func([][]byte), ) (bool, error)
Handle processes one server message. onRowDesc is invoked once per RowDescription; onRow once per DataRow (payload slices alias Reader memory — copy if retention is needed). Either callback may be nil.
func (*SimpleQueryState) Reset ¶
func (q *SimpleQueryState) Reset()
Reset zeroes the state machine for reuse while preserving the internal fieldScratch / Columns / tagBuf backing arrays. The caller still owns the semantic fields (Columns, Tag, Err): they are re-set to their zero values with length=0 but cap retained.
func (*SimpleQueryState) TagBytes ¶
func (q *SimpleQueryState) TagBytes() []byte
TagBytes returns the CommandComplete tag as an owned byte slice (independent of the wire Reader's buffer). Callers that only need to parse a row count (via RowsAffectedBytes) should use this instead of Tag to avoid the string allocation.
type StartupState ¶
type StartupState struct {
User string
Password string
Database string
Params map[string]string
// Populated as the exchange proceeds.
PID int32
Secret int32
ServerParams map[string]string
// contains filtered or unexported fields
}
StartupState drives the client side of the PostgreSQL startup and authentication exchange. Callers feed it one received message at a time via Handle and send the returned response bytes (if any) back to the server. Handle returns done=true after ReadyForQuery.
A StartupState is single-use: once done is true, it must not receive more messages. Use a fresh instance per connection.
func (*StartupState) Handle ¶
func (s *StartupState) Handle(msgType byte, payload []byte, w *Writer) (response []byte, done bool, err error)
Handle consumes one server message. It returns the bytes to transmit (or nil), whether the startup exchange has completed, and an error. On error the caller should close the connection.
func (*StartupState) Start ¶
func (s *StartupState) Start(w *Writer) []byte
Start returns the initial StartupMessage bytes. The Writer is reset first. It must be called exactly once at the start of the exchange.
type TypeCodec ¶
type TypeCodec struct {
OID uint32
Name string
DecodeText func(src []byte) (driver.Value, error)
DecodeBinary func(src []byte) (driver.Value, error)
EncodeText func(dst []byte, v any) ([]byte, error)
EncodeBinary func(dst []byte, v any) ([]byte, error)
ScanType reflect.Type
}
TypeCodec encodes and decodes a single PostgreSQL type. The Encode functions follow the append convention: they append encoded bytes to dst and return the (possibly reallocated) slice. Encoders that receive a driver.Valuer will call Value() first and re-dispatch based on the resulting value. A nil value is the caller's responsibility (the framing layer writes a length of -1 for NULLs); codecs never see nil.
type Writer ¶
type Writer struct {
// contains filtered or unexported fields
}
Writer encodes PostgreSQL protocol messages into an internal buffer. The buffer is reused across messages, so callers that need to retain message bytes across Reset calls must copy them out. Writer is not safe for concurrent use.
func (*Writer) Bytes ¶
Bytes returns the accumulated buffer. The returned slice aliases the Writer's internal storage until Reset is called.
func (*Writer) FinishMessage ¶
func (w *Writer) FinishMessage()
FinishMessage completes the current message by patching the length field. The length includes the length prefix itself but never the frontend type byte.
func (*Writer) Reset ¶
func (w *Writer) Reset()
Reset clears the buffer for reuse while keeping the underlying array.
func (*Writer) StartMessage ¶
StartMessage begins a new frontend message with the given type byte. The caller must follow with writes for the message body and then call FinishMessage.
func (*Writer) StartStartupMessage ¶
func (w *Writer) StartStartupMessage()
StartStartupMessage begins a startup/cancel/SSL message. These messages have no type byte — just a 4-byte length prefix.
func (*Writer) WriteByte ¶
WriteByte appends a single byte to the current message body. It returns nil unconditionally to satisfy io.ByteWriter.
func (*Writer) WriteBytes ¶
WriteBytes appends b verbatim without any length prefix.
func (*Writer) WriteInt16 ¶
WriteInt16 appends a big-endian int16.
func (*Writer) WriteInt32 ¶
WriteInt32 appends a big-endian int32.
func (*Writer) WriteString ¶
WriteString appends s followed by a null terminator (PostgreSQL CString).