mirror of
https://github.com/featurebasedb/featurebase.git
synced 2026-08-28 10:54:59 +00:00
367 lines
8.2 KiB
Go
367 lines
8.2 KiB
Go
// Copyright 2020 Pilosa Corp.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package message
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/binary"
|
|
)
|
|
|
|
// Type is a byte indicating the type of a Postgres message.
|
|
type Type byte
|
|
|
|
const (
|
|
// TypeAuthentication is a message used to transfer authentication info.
|
|
TypeAuthentication Type = 'R'
|
|
|
|
// TypeReadyForQuery is a message used to indicate that the server is ready for another query.
|
|
TypeReadyForQuery Type = 'Z'
|
|
|
|
// TypeCommandComplete is a message used to indicate that a query has completed.
|
|
TypeCommandComplete Type = 'C'
|
|
|
|
// TypeError is an error message.
|
|
TypeError Type = 'E'
|
|
|
|
// TypeRowDescription is a message indicating the column types of the result rows from a query.
|
|
TypeRowDescription Type = 'T'
|
|
|
|
// TypeDataRow is a message with the contents of a single row.
|
|
TypeDataRow Type = 'D'
|
|
|
|
// TypeTermination is a message indicating a request to terminate a connection.
|
|
TypeTermination Type = 'X'
|
|
|
|
// TypeNegotiateProtocolVersion is a message used when a client attempts to connect with a newer minor version than the server supports.
|
|
TypeNegotiateProtocolVersion Type = 'v'
|
|
|
|
// TypeSimpleQuery is a simple query request.
|
|
TypeSimpleQuery Type = 'Q'
|
|
|
|
// TypeBackendKeyData contains a cancellation key for the client to use later.
|
|
TypeBackendKeyData Type = 'K'
|
|
)
|
|
|
|
// AuthenticationOK is a message indicating that authentication has completed.
|
|
var AuthenticationOK = Message{
|
|
Type: TypeAuthentication,
|
|
Data: []byte{0, 0, 0, 0},
|
|
}
|
|
|
|
// Message is a Postgres message value.
|
|
type Message struct {
|
|
Type Type
|
|
Data []byte
|
|
}
|
|
|
|
// TransactionStatus is the current transaction state.
|
|
type TransactionStatus byte
|
|
|
|
const (
|
|
// TransactionStatusIdle indicates that there is no active transaction.
|
|
TransactionStatusIdle TransactionStatus = 'I'
|
|
|
|
// TransactionStatusActive indicates that the connection currently has an active transaction.
|
|
TransactionStatusActive TransactionStatus = 'T'
|
|
|
|
// TransactionStatusFailed indicates that the connection currently has a failed transaction.
|
|
TransactionStatusFailed TransactionStatus = 'E'
|
|
)
|
|
|
|
// Encoder encodes messages.
|
|
type Encoder struct {
|
|
buf bytes.Buffer
|
|
scratch [4]byte
|
|
}
|
|
|
|
func (e *Encoder) i16(i int16) error {
|
|
binary.BigEndian.PutUint16(e.scratch[:2], uint16(i))
|
|
_, err := e.buf.Write(e.scratch[:2])
|
|
return err
|
|
}
|
|
|
|
func (e *Encoder) i32(i int32) error {
|
|
binary.BigEndian.PutUint32(e.scratch[:], uint32(i))
|
|
_, err := e.buf.Write(e.scratch[:])
|
|
return err
|
|
}
|
|
|
|
// ReadyForQuery encodes a "ready for query" message.
|
|
func (e *Encoder) ReadyForQuery(status TransactionStatus) (Message, error) {
|
|
e.buf.Reset()
|
|
|
|
err := e.buf.WriteByte(byte(status))
|
|
if err != nil {
|
|
return Message{}, err
|
|
}
|
|
|
|
return Message{
|
|
Type: TypeReadyForQuery,
|
|
Data: e.buf.Bytes(),
|
|
}, nil
|
|
}
|
|
|
|
// CommandComplete encodes a command completion message.
|
|
func (e *Encoder) CommandComplete(tag string) (Message, error) {
|
|
e.buf.Reset()
|
|
|
|
_, err := e.buf.WriteString(tag)
|
|
if err != nil {
|
|
return Message{}, err
|
|
}
|
|
|
|
err = e.buf.WriteByte(0)
|
|
if err != nil {
|
|
return Message{}, err
|
|
}
|
|
|
|
return Message{
|
|
Type: TypeCommandComplete,
|
|
Data: e.buf.Bytes(),
|
|
}, nil
|
|
}
|
|
|
|
// NoticeFieldType indicates the type of a notice/error field.
|
|
// https://www.postgresql.org/docs/9.3/protocol-error-fields.html
|
|
type NoticeFieldType byte
|
|
|
|
const (
|
|
// NoticeFieldSeverity indicates the severity of a notice/error.
|
|
NoticeFieldSeverity NoticeFieldType = 'S'
|
|
|
|
// NoticeFieldMessage is a short human-readable error/notice message.
|
|
NoticeFieldMessage NoticeFieldType = 'M'
|
|
|
|
// NoticeFieldDetail is an optional extended description of the error.
|
|
NoticeFieldDetail NoticeFieldType = 'D'
|
|
|
|
// NoticeFieldHint is a suggestion of how to address the issue.
|
|
NoticeFieldHint NoticeFieldType = 'H'
|
|
)
|
|
|
|
// NoticeField is a field in an error or notice.
|
|
type NoticeField struct {
|
|
Type NoticeFieldType
|
|
Data string
|
|
}
|
|
|
|
func (e *Encoder) messageOrNotice(fields ...NoticeField) error {
|
|
for _, f := range fields {
|
|
err := e.buf.WriteByte(byte(f.Type))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
_, err = e.buf.WriteString(f.Data)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = e.buf.WriteByte(0)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return e.buf.WriteByte(0)
|
|
}
|
|
|
|
// Error encodes a Postgres error message.
|
|
func (e *Encoder) Error(fields ...NoticeField) (Message, error) {
|
|
e.buf.Reset()
|
|
err := e.messageOrNotice(fields...)
|
|
if err != nil {
|
|
return Message{}, err
|
|
}
|
|
return Message{
|
|
Type: TypeError,
|
|
Data: e.buf.Bytes(),
|
|
}, nil
|
|
}
|
|
|
|
// GoError creates a simple Postgres error message from a Go error value.
|
|
func (e *Encoder) GoError(err error) (Message, error) {
|
|
return e.Error(
|
|
NoticeField{
|
|
Type: NoticeFieldSeverity,
|
|
Data: "ERROR",
|
|
},
|
|
NoticeField{
|
|
Type: NoticeFieldMessage,
|
|
Data: err.Error(),
|
|
},
|
|
)
|
|
}
|
|
|
|
// ColumnDescription is a description of a data column.
|
|
type ColumnDescription struct {
|
|
Name string
|
|
TableID int32 //either a table/col id or 0
|
|
FieldID int16 //either a table/col id or 0
|
|
TypeID int32 //field type
|
|
TypeLen int16 //size in bytes of field
|
|
TypeModifier int32 //type modifer?
|
|
Mode int16 //0=text 1=binary
|
|
}
|
|
|
|
// RowDescription describes the response rows from a query.
|
|
func (e *Encoder) RowDescription(cols ...ColumnDescription) (Message, error) {
|
|
if len(cols) >= 1<<15 {
|
|
return Message{}, ErrMessageTooBig
|
|
}
|
|
|
|
e.buf.Reset()
|
|
|
|
err := e.i16(int16(len(cols)))
|
|
if err != nil {
|
|
return Message{}, nil
|
|
}
|
|
|
|
for _, col := range cols {
|
|
_, err := e.buf.WriteString(col.Name)
|
|
if err != nil {
|
|
return Message{}, err
|
|
}
|
|
err = e.buf.WriteByte(0)
|
|
if err != nil {
|
|
return Message{}, err
|
|
}
|
|
|
|
err = e.i32(col.TableID)
|
|
if err != nil {
|
|
return Message{}, err
|
|
}
|
|
|
|
err = e.i16(col.FieldID)
|
|
if err != nil {
|
|
return Message{}, err
|
|
}
|
|
|
|
err = e.i32(col.TypeID)
|
|
if err != nil {
|
|
return Message{}, err
|
|
}
|
|
|
|
err = e.i16(col.TypeLen)
|
|
if err != nil {
|
|
return Message{}, err
|
|
}
|
|
|
|
err = e.i32(col.TypeModifier)
|
|
if err != nil {
|
|
return Message{}, err
|
|
}
|
|
|
|
err = e.i16(col.Mode)
|
|
if err != nil {
|
|
return Message{}, err
|
|
}
|
|
}
|
|
|
|
return Message{
|
|
Type: TypeRowDescription,
|
|
Data: e.buf.Bytes(),
|
|
}, nil
|
|
}
|
|
|
|
// TextRow encodes a data row in textual format.
|
|
func (e *Encoder) TextRow(row ...string) (Message, error) {
|
|
if len(row) >= 1<<15 {
|
|
return Message{}, ErrMessageTooBig
|
|
}
|
|
|
|
e.buf.Reset()
|
|
|
|
err := e.i16(int16(len(row)))
|
|
if err != nil {
|
|
return Message{}, err
|
|
}
|
|
|
|
for _, val := range row {
|
|
if uint(len(val)) >= 1<<31 {
|
|
return Message{}, ErrMessageTooBig
|
|
}
|
|
|
|
err = e.i32(int32(len(val)))
|
|
if err != nil {
|
|
return Message{}, err
|
|
}
|
|
|
|
_, err = e.buf.WriteString(val)
|
|
if err != nil {
|
|
return Message{}, err
|
|
}
|
|
}
|
|
|
|
return Message{
|
|
Type: TypeDataRow,
|
|
Data: e.buf.Bytes(),
|
|
}, nil
|
|
}
|
|
|
|
// NegotiateProtocolVersion encodes a protocol negotiation packet.
|
|
func (e *Encoder) NegotiateProtocolVersion(maxMinor int32, unrecognizedOptions ...string) (Message, error) {
|
|
if uint64(len(unrecognizedOptions)) >= 1<<31 {
|
|
return Message{}, ErrMessageTooBig
|
|
}
|
|
|
|
e.buf.Reset()
|
|
|
|
err := e.i32(maxMinor)
|
|
if err != nil {
|
|
return Message{}, err
|
|
}
|
|
|
|
err = e.i32(int32(len(unrecognizedOptions)))
|
|
if err != nil {
|
|
return Message{}, err
|
|
}
|
|
for _, opt := range unrecognizedOptions {
|
|
_, err = e.buf.WriteString(opt)
|
|
if err != nil {
|
|
return Message{}, err
|
|
}
|
|
|
|
err = e.buf.WriteByte(0)
|
|
if err != nil {
|
|
return Message{}, err
|
|
}
|
|
}
|
|
|
|
return Message{
|
|
Type: TypeNegotiateProtocolVersion,
|
|
Data: e.buf.Bytes(),
|
|
}, nil
|
|
}
|
|
|
|
// BackendKeyData encodes a Message with a cancellation key.
|
|
func (e *Encoder) BackendKeyData(pid, key int32) (Message, error) {
|
|
e.buf.Reset()
|
|
|
|
err := e.i32(pid)
|
|
if err != nil {
|
|
return Message{}, err
|
|
}
|
|
|
|
err = e.i32(key)
|
|
if err != nil {
|
|
return Message{}, err
|
|
}
|
|
|
|
return Message{
|
|
Type: TypeBackendKeyData,
|
|
Data: e.buf.Bytes(),
|
|
}, nil
|
|
}
|