├── .gitignore ├── raw ├── README.md ├── sql.go ├── data_row_reader.go ├── message_reader.go ├── sanitize.go ├── messages.go ├── value_transcoder.go └── conn.go ├── person.sql.erb ├── results └── 2020-03-20 │ ├── README.md │ ├── different-hosts-tls.txt │ ├── same-host-unix-socket.txt │ └── different-hosts-no-tls.txt ├── go.mod ├── noalloc_chunkreader.go ├── noalloc_test.go ├── README.md ├── main.go ├── go.sum └── bench_test.go /.gitignore: -------------------------------------------------------------------------------- 1 | .envrc 2 | -------------------------------------------------------------------------------- /raw/README.md: -------------------------------------------------------------------------------- 1 | # raw 2 | 3 | The raw package is a modified version of pgx for benchmarking optimal database 4 | driver performance. It is used to establish a connection, then to send and 5 | receive raw byte slices with no other processing. 6 | -------------------------------------------------------------------------------- /person.sql.erb: -------------------------------------------------------------------------------- 1 | -- erb -r ffaker -r date person.sql.erb 2 | insert into person(first_name, last_name, sex, birth_date, weight, height, update_time) values 3 | <% 10_000.times do |n| %><%= "," unless n==0 %>('<%= Faker::Name.first_name.gsub("'", "''") %>', '<%= Faker::Name.last_name.gsub("'", "''") %>', '<%= %w[male female].sample %>', '<%= Date.today - rand(10_000) %>', <%= 5 + rand(400) %>, <%= 20 + rand(60) %>, '<%= (Time.now - rand(1_000_000_000)).strftime("%Y-%m-%d %H:%M:%S") %>') 4 | <% end %>; 5 | -------------------------------------------------------------------------------- /results/2020-03-20/README.md: -------------------------------------------------------------------------------- 1 | These benchmarks were run using Go version 1.14.1 with the latest released versions of all libraries. 2 | 3 | The hosts were Digital Ocean CPU-Optimized 4 GB / 2 CPUs in the same data center. 4 | 5 | The tests were run under 3 conditions: 6 | 7 | 1. PostgreSQL and benchmark application on same host connected with Unix socket. 8 | 2. PostgreSQL and benchmark application on separate hosts connected over TCP with TLS encryption. 9 | 3. PostgreSQL and benchmark application on separate hosts connected over TCP without TLS encryption. 10 | 11 | Remote TCP appears to add about 120µs of latency on top beyond a Unix socket on the local host. TLS adds an addition ~15% of latency. 12 | -------------------------------------------------------------------------------- /raw/sql.go: -------------------------------------------------------------------------------- 1 | package raw 2 | 3 | import ( 4 | "strconv" 5 | ) 6 | 7 | // QueryArgs is a container for arguments to an SQL query. It is helpful when 8 | // building SQL statements where the number of arguments is variable. 9 | type QueryArgs []interface{} 10 | 11 | var placeholders []string 12 | 13 | func init() { 14 | placeholders = make([]string, 64) 15 | 16 | for i := 1; i < 64; i++ { 17 | placeholders[i] = "$" + strconv.FormatInt(int64(i), 10) 18 | } 19 | } 20 | 21 | // Append adds a value to qa and returns the placeholder value for the 22 | // argument. e.g. $1, $2, etc. 23 | func (qa *QueryArgs) Append(v interface{}) string { 24 | *qa = append(*qa, v) 25 | if len(*qa) < len(placeholders) { 26 | return placeholders[len(*qa)] 27 | } 28 | return "$" + strconv.FormatInt(int64(len(*qa)), 10) 29 | } 30 | -------------------------------------------------------------------------------- /raw/data_row_reader.go: -------------------------------------------------------------------------------- 1 | package raw 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | // DataRowReader is used by SelectFunc to process incoming rows. 8 | type DataRowReader struct { 9 | mr *MessageReader 10 | FieldDescriptions []FieldDescription 11 | currentFieldIdx int 12 | } 13 | 14 | func (r *DataRowReader) MessageReader() *MessageReader { 15 | return r.mr 16 | } 17 | 18 | // ReadValue returns the next value from the current row. 19 | func (r *DataRowReader) ReadValue() interface{} { 20 | fieldDescription := r.FieldDescriptions[r.currentFieldIdx] 21 | r.currentFieldIdx++ 22 | 23 | size := r.mr.ReadInt32() 24 | if size > -1 { 25 | if vt, present := ValueTranscoders[fieldDescription.DataType]; present { 26 | switch fieldDescription.FormatCode { 27 | case 0: 28 | return vt.DecodeText(r.mr, size) 29 | case 1: 30 | return vt.DecodeBinary(r.mr, size) 31 | default: 32 | return ProtocolError(fmt.Sprintf("Unknown field description format code: %v", fieldDescription.FormatCode)) 33 | } 34 | } else { 35 | return r.mr.ReadString(size) 36 | } 37 | } else { 38 | return nil 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /raw/message_reader.go: -------------------------------------------------------------------------------- 1 | package raw 2 | 3 | import ( 4 | "bytes" 5 | "encoding/binary" 6 | ) 7 | 8 | // MessageReader is a helper that reads values from a PostgreSQL message. 9 | type MessageReader bytes.Buffer 10 | 11 | func (r *MessageReader) ReadByte() (b byte) { 12 | b, _ = (*bytes.Buffer)(r).ReadByte() 13 | return 14 | } 15 | 16 | func (r *MessageReader) ReadInt16() (n int16) { 17 | b := (*bytes.Buffer)(r).Next(2) 18 | return int16(binary.BigEndian.Uint16(b)) 19 | } 20 | 21 | func (r *MessageReader) ReadInt32() (n int32) { 22 | b := (*bytes.Buffer)(r).Next(4) 23 | return int32(binary.BigEndian.Uint32(b)) 24 | } 25 | 26 | func (r *MessageReader) ReadInt64() (n int64) { 27 | b := (*bytes.Buffer)(r).Next(8) 28 | return int64(binary.BigEndian.Uint64(b)) 29 | } 30 | 31 | func (r *MessageReader) ReadOid() (oid Oid) { 32 | b := (*bytes.Buffer)(r).Next(4) 33 | return Oid(binary.BigEndian.Uint32(b)) 34 | } 35 | 36 | // ReadString reads a null terminated string 37 | func (r *MessageReader) ReadCString() (s string) { 38 | b, _ := (*bytes.Buffer)(r).ReadBytes(0) 39 | return string(b[:len(b)-1]) 40 | } 41 | 42 | // ReadString reads count bytes and returns as string 43 | func (r *MessageReader) ReadString(count int32) (s string) { 44 | size := int(count) 45 | b := (*bytes.Buffer)(r).Next(size) 46 | return string(b) 47 | } 48 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/jackc/go_db_bench 2 | 3 | go 1.17 4 | 5 | require ( 6 | github.com/go-pg/pg/v10 v10.10.6 7 | github.com/jackc/pgconn v1.11.0 8 | github.com/jackc/pgproto3/v2 v2.2.0 9 | github.com/jackc/pgtype v1.10.0 10 | github.com/jackc/pgx/v4 v4.15.0 11 | github.com/jackc/pgx/v5 v5.0.0-20220227022815-a8f6674a07b2 12 | github.com/lib/pq v1.10.4 13 | gopkg.in/inconshreveable/log15.v2 v2.0.0-20200109203555-b30bc20e4fd1 14 | ) 15 | 16 | require ( 17 | github.com/go-pg/zerochecker v0.2.0 // indirect 18 | github.com/go-stack/stack v1.8.1 // indirect 19 | github.com/jackc/chunkreader/v2 v2.0.1 // indirect 20 | github.com/jackc/pgio v1.0.0 // indirect 21 | github.com/jackc/pgpassfile v1.0.0 // indirect 22 | github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b // indirect 23 | github.com/jackc/puddle v1.2.1 // indirect 24 | github.com/jinzhu/inflection v1.0.0 // indirect 25 | github.com/mattn/go-colorable v0.1.12 // indirect 26 | github.com/mattn/go-isatty v0.0.14 // indirect 27 | github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc // indirect 28 | github.com/vmihailenco/bufpool v0.1.11 // indirect 29 | github.com/vmihailenco/msgpack/v5 v5.3.4 // indirect 30 | github.com/vmihailenco/tagparser v0.1.2 // indirect 31 | github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect 32 | golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3 // indirect 33 | golang.org/x/net v0.0.0-20211216030914-fe4d6282115f // indirect 34 | golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e // indirect 35 | golang.org/x/text v0.3.7 // indirect 36 | mellium.im/sasl v0.2.1 // indirect 37 | ) 38 | -------------------------------------------------------------------------------- /raw/sanitize.go: -------------------------------------------------------------------------------- 1 | package raw 2 | 3 | import ( 4 | "encoding/hex" 5 | "fmt" 6 | "regexp" 7 | "strconv" 8 | "strings" 9 | "time" 10 | ) 11 | 12 | var literalPattern *regexp.Regexp = regexp.MustCompile(`\$\d+`) 13 | 14 | // QuoteString escapes and quotes a string making it safe for interpolation 15 | // into an SQL string. 16 | func (c *Conn) QuoteString(input string) (output string) { 17 | output = "'" + strings.Replace(input, "'", "''", -1) + "'" 18 | return 19 | } 20 | 21 | // QuoteIdentifier escapes and quotes an identifier making it safe for 22 | // interpolation into an SQL string 23 | func (c *Conn) QuoteIdentifier(input string) (output string) { 24 | output = `"` + strings.Replace(input, `"`, `""`, -1) + `"` 25 | return 26 | } 27 | 28 | // SanitizeSql substitutely args positionaly into sql. Placeholder values are 29 | // $ prefixed integers like $1, $2, $3, etc. args are sanitized and quoted as 30 | // appropriate. 31 | func (c *Conn) SanitizeSql(sql string, args ...interface{}) (output string, err error) { 32 | replacer := func(match string) (replacement string) { 33 | n, _ := strconv.ParseInt(match[1:], 10, 0) 34 | switch arg := args[n-1].(type) { 35 | case string: 36 | return c.QuoteString(arg) 37 | case int: 38 | return strconv.FormatInt(int64(arg), 10) 39 | case int8: 40 | return strconv.FormatInt(int64(arg), 10) 41 | case int16: 42 | return strconv.FormatInt(int64(arg), 10) 43 | case int32: 44 | return strconv.FormatInt(int64(arg), 10) 45 | case int64: 46 | return strconv.FormatInt(int64(arg), 10) 47 | case time.Time: 48 | return c.QuoteString(arg.Format("2006-01-02 15:04:05.999999 -0700")) 49 | case uint: 50 | return strconv.FormatUint(uint64(arg), 10) 51 | case uint8: 52 | return strconv.FormatUint(uint64(arg), 10) 53 | case uint16: 54 | return strconv.FormatUint(uint64(arg), 10) 55 | case uint32: 56 | return strconv.FormatUint(uint64(arg), 10) 57 | case uint64: 58 | return strconv.FormatUint(uint64(arg), 10) 59 | case float32: 60 | return strconv.FormatFloat(float64(arg), 'f', -1, 32) 61 | case float64: 62 | return strconv.FormatFloat(arg, 'f', -1, 64) 63 | case bool: 64 | return strconv.FormatBool(arg) 65 | case []byte: 66 | return `E'\\x` + hex.EncodeToString(arg) + `'` 67 | case []int16: 68 | var s string 69 | s, err = int16SliceToArrayString(arg) 70 | return c.QuoteString(s) 71 | case []int32: 72 | var s string 73 | s, err = int32SliceToArrayString(arg) 74 | return c.QuoteString(s) 75 | case []int64: 76 | var s string 77 | s, err = int64SliceToArrayString(arg) 78 | return c.QuoteString(s) 79 | case nil: 80 | return "null" 81 | default: 82 | err = fmt.Errorf("Unable to sanitize type: %T", arg) 83 | return "" 84 | } 85 | } 86 | 87 | output = literalPattern.ReplaceAllStringFunc(sql, replacer) 88 | return 89 | } 90 | -------------------------------------------------------------------------------- /raw/messages.go: -------------------------------------------------------------------------------- 1 | package raw 2 | 3 | import ( 4 | "encoding/binary" 5 | ) 6 | 7 | const ( 8 | protocolVersionNumber = 196608 // 3.0 9 | ) 10 | 11 | const ( 12 | backendKeyData = 'K' 13 | authenticationX = 'R' 14 | readyForQuery = 'Z' 15 | rowDescription = 'T' 16 | dataRow = 'D' 17 | commandComplete = 'C' 18 | errorResponse = 'E' 19 | noticeResponse = 'N' 20 | parseComplete = '1' 21 | parameterDescription = 't' 22 | bindComplete = '2' 23 | notificationResponse = 'A' 24 | noData = 'n' 25 | ) 26 | 27 | type startupMessage struct { 28 | options map[string]string 29 | } 30 | 31 | func newStartupMessage() *startupMessage { 32 | return &startupMessage{map[string]string{}} 33 | } 34 | 35 | func (self *startupMessage) Bytes() (buf []byte) { 36 | buf = make([]byte, 8, 128) 37 | binary.BigEndian.PutUint32(buf[4:8], uint32(protocolVersionNumber)) 38 | for key, value := range self.options { 39 | buf = append(buf, key...) 40 | buf = append(buf, 0) 41 | buf = append(buf, value...) 42 | buf = append(buf, 0) 43 | } 44 | buf = append(buf, ("\000")...) 45 | binary.BigEndian.PutUint32(buf[0:4], uint32(len(buf))) 46 | return buf 47 | } 48 | 49 | type Oid int32 50 | 51 | type FieldDescription struct { 52 | Name string 53 | Table Oid 54 | AttributeNumber int16 55 | DataType Oid 56 | DataTypeSize int16 57 | Modifier int32 58 | FormatCode int16 59 | } 60 | 61 | type PgError struct { 62 | Severity string 63 | Code string 64 | Message string 65 | } 66 | 67 | func (self PgError) Error() string { 68 | return self.Severity + ": " + self.Message + " (SQLSTATE " + self.Code + ")" 69 | } 70 | 71 | func newWriteBuf(buf []byte, t byte) *WriteBuf { 72 | buf = append(buf, t, 0, 0, 0, 0) 73 | return &WriteBuf{buf: buf, sizeIdx: 1} 74 | } 75 | 76 | type WriteBuf struct { 77 | buf []byte 78 | sizeIdx int 79 | } 80 | 81 | func (wb *WriteBuf) startMsg(t byte) { 82 | wb.closeMsg() 83 | wb.buf = append(wb.buf, t, 0, 0, 0, 0) 84 | wb.sizeIdx = len(wb.buf) - 4 85 | } 86 | 87 | func (wb *WriteBuf) closeMsg() { 88 | binary.BigEndian.PutUint32(wb.buf[wb.sizeIdx:wb.sizeIdx+4], uint32(len(wb.buf)-wb.sizeIdx)) 89 | } 90 | 91 | func (wb *WriteBuf) WriteByte(b byte) { 92 | wb.buf = append(wb.buf, b) 93 | } 94 | 95 | func (wb *WriteBuf) WriteCString(s string) { 96 | wb.buf = append(wb.buf, []byte(s)...) 97 | wb.buf = append(wb.buf, 0) 98 | } 99 | 100 | func (wb *WriteBuf) WriteInt16(n int16) { 101 | b := make([]byte, 2) 102 | binary.BigEndian.PutUint16(b, uint16(n)) 103 | wb.buf = append(wb.buf, b...) 104 | } 105 | 106 | func (wb *WriteBuf) WriteInt32(n int32) { 107 | b := make([]byte, 4) 108 | binary.BigEndian.PutUint32(b, uint32(n)) 109 | wb.buf = append(wb.buf, b...) 110 | } 111 | 112 | func (wb *WriteBuf) WriteInt64(n int64) { 113 | b := make([]byte, 8) 114 | binary.BigEndian.PutUint64(b, uint64(n)) 115 | wb.buf = append(wb.buf, b...) 116 | } 117 | 118 | func (wb *WriteBuf) WriteBytes(b []byte) { 119 | wb.buf = append(wb.buf, b...) 120 | } 121 | -------------------------------------------------------------------------------- /noalloc_chunkreader.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "io" 5 | ) 6 | 7 | // ChunkReader is a io.Reader wrapper that minimizes IO reads and memory allocations. It allocates memory in chunks and 8 | // will read as much as will fit in the current buffer in a single call regardless of how large a read is actually 9 | // requested. The memory returned via Next is owned by the caller. This avoids the need for an additional copy. 10 | // 11 | // The downside of this approach is that a large buffer can be pinned in memory even if only a small slice is 12 | // referenced. For example, an entire 4096 byte block could be pinned in memory by even a 1 byte slice. In these rare 13 | // cases it would be advantageous to copy the bytes to another slice. 14 | type ChunkReader struct { 15 | r io.Reader 16 | 17 | buf []byte 18 | rp, wp int // buf read position and write position 19 | 20 | config Config 21 | } 22 | 23 | // Config contains configuration parameters for ChunkReader. 24 | type Config struct { 25 | MinBufLen int // Minimum buffer length 26 | } 27 | 28 | // New creates and returns a new ChunkReader for r with default configuration. 29 | func New(r io.Reader) *ChunkReader { 30 | cr, err := NewConfig(r, Config{}) 31 | if err != nil { 32 | panic("default config can't be bad") 33 | } 34 | 35 | return cr 36 | } 37 | 38 | // NewConfig creates and a new ChunkReader for r configured by config. 39 | func NewConfig(r io.Reader, config Config) (*ChunkReader, error) { 40 | if config.MinBufLen == 0 { 41 | config.MinBufLen = 4096 42 | } 43 | 44 | return &ChunkReader{ 45 | r: r, 46 | buf: make([]byte, config.MinBufLen), 47 | config: config, 48 | }, nil 49 | } 50 | 51 | // Next returns buf filled with the next n bytes. The caller gains ownership of buf. It is not necessary to make a copy 52 | // of buf. If an error occurs, buf will be nil. 53 | func (r *ChunkReader) Next(n int) (buf []byte, err error) { 54 | // n bytes already in buf 55 | if (r.wp - r.rp) >= n { 56 | buf = r.buf[r.rp : r.rp+n] 57 | r.rp += n 58 | return buf, err 59 | } 60 | 61 | // available space in buf is less than n 62 | if len(r.buf) < n { 63 | r.copyBufContents(r.newBuf(n)) 64 | } 65 | 66 | // buf is large enough, but need to shift filled area to start to make enough contiguous space 67 | minReadCount := n - (r.wp - r.rp) 68 | if (len(r.buf) - r.wp) < minReadCount { 69 | newBuf := r.newBuf(n) 70 | r.copyBufContents(newBuf) 71 | } 72 | 73 | if err := r.appendAtLeast(minReadCount); err != nil { 74 | return nil, err 75 | } 76 | 77 | buf = r.buf[r.rp : r.rp+n] 78 | r.rp += n 79 | return buf, nil 80 | } 81 | 82 | func (r *ChunkReader) Reset() { 83 | r.rp = 0 84 | r.wp = 0 85 | } 86 | 87 | func (r *ChunkReader) appendAtLeast(fillLen int) error { 88 | n, err := io.ReadAtLeast(r.r, r.buf[r.wp:], fillLen) 89 | r.wp += n 90 | return err 91 | } 92 | 93 | func (r *ChunkReader) newBuf(size int) []byte { 94 | if size < r.config.MinBufLen { 95 | size = r.config.MinBufLen 96 | } 97 | return make([]byte, size) 98 | } 99 | 100 | func (r *ChunkReader) copyBufContents(dest []byte) { 101 | r.wp = copy(dest, r.buf[r.rp:r.wp]) 102 | r.rp = 0 103 | r.buf = dest 104 | } 105 | -------------------------------------------------------------------------------- /noalloc_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "encoding/binary" 6 | "fmt" 7 | "io" 8 | "testing" 9 | 10 | "github.com/jackc/pgconn" 11 | "github.com/jackc/pgproto3/v2" 12 | "github.com/jackc/pgtype" 13 | ) 14 | 15 | func BenchmarkPgConnSelectMultipleRowsWithWithDecodeBinaryZeroAlloc(b *testing.B) { 16 | setup(b) 17 | 18 | config, err := pgconn.ParseConfig("") 19 | if err != nil { 20 | b.Fatalf("pgconn.Connect() failed: %v", err) 21 | } 22 | 23 | var cr *ChunkReader 24 | 25 | config.BuildFrontend = func(r io.Reader, w io.Writer) pgconn.Frontend { 26 | cr, err = NewConfig(r, Config{MinBufLen: 64 * 1024}) 27 | if err != nil { 28 | panic(fmt.Sprintf("BUG: chunkreader.NewConfig failed: %v", err)) 29 | } 30 | frontend := pgproto3.NewFrontend(cr, w) 31 | 32 | return frontend 33 | } 34 | 35 | pgConn, err := pgconn.ConnectConfig(context.Background(), config) 36 | if err != nil { 37 | b.Fatalf("pgconn.Connect() failed: %v", err) 38 | } 39 | _, err = pgConn.Prepare(context.Background(), "selectMultiplePeople", selectMultiplePeopleSQL, nil) 40 | if err != nil { 41 | b.Fatalf("pgConn.Prepare() failed: %v", err) 42 | } 43 | 44 | type personRaw struct { 45 | Id pgtype.Int4 46 | FirstName pgtype.GenericBinary 47 | LastName pgtype.GenericBinary 48 | Sex pgtype.GenericBinary 49 | BirthDate pgtype.Date 50 | Weight pgtype.Int4 51 | Height pgtype.Int4 52 | UpdateTime pgtype.Timestamptz 53 | } 54 | 55 | b.ResetTimer() 56 | for i := 0; i < b.N; i++ { 57 | id := randPersonIDs[i%len(randPersonIDs)] 58 | 59 | buf := []byte{0, 0, 0, 0} 60 | binary.BigEndian.PutUint32(buf, uint32(id)) 61 | 62 | rr := pgConn.ExecPrepared(context.Background(), "selectMultiplePeople", [][]byte{buf}, []int16{1}, []int16{1}) 63 | 64 | var p personRaw 65 | for rr.NextRow() { 66 | var err error 67 | vi := 0 68 | 69 | err = p.Id.DecodeBinary(nil, rr.Values()[vi]) 70 | if err != nil { 71 | b.Fatalf("DecodeBinary failed: %v", err) 72 | } 73 | vi += 1 74 | 75 | err = p.FirstName.DecodeBinary(nil, rr.Values()[vi]) 76 | if err != nil { 77 | b.Fatalf("DecodeBinary failed: %v", err) 78 | } 79 | vi += 1 80 | 81 | err = p.LastName.DecodeBinary(nil, rr.Values()[vi]) 82 | if err != nil { 83 | b.Fatalf("DecodeBinary failed: %v", err) 84 | } 85 | vi += 1 86 | 87 | err = p.Sex.DecodeBinary(nil, rr.Values()[vi]) 88 | if err != nil { 89 | b.Fatalf("DecodeBinary failed: %v", err) 90 | } 91 | vi += 1 92 | 93 | err = p.BirthDate.DecodeBinary(nil, rr.Values()[vi]) 94 | if err != nil { 95 | b.Fatalf("DecodeBinary failed: %v", err) 96 | } 97 | vi += 1 98 | 99 | err = p.Weight.DecodeBinary(nil, rr.Values()[vi]) 100 | if err != nil { 101 | b.Fatalf("DecodeBinary failed: %v", err) 102 | } 103 | vi += 1 104 | 105 | err = p.Height.DecodeBinary(nil, rr.Values()[vi]) 106 | if err != nil { 107 | b.Fatalf("DecodeBinary failed: %v", err) 108 | } 109 | vi += 1 110 | 111 | err = p.UpdateTime.DecodeBinary(nil, rr.Values()[vi]) 112 | if err != nil { 113 | b.Fatalf("DecodeBinary failed: %v", err) 114 | } 115 | 116 | } 117 | 118 | _, err := rr.Close() 119 | if err != nil { 120 | b.Fatalf("pgConn.ExecPrepared failed: %v", err) 121 | } 122 | 123 | cr.Reset() 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Go Database Benchmark 2 | 3 | This tests the performance of [pgx native](https://github.com/jackc/pgx), [pgx through 4 | database/sql](https://github.com/jackc/pgx/tree/master/stdlib), [pq](https://github.com/lib/pq) through database/sql, 5 | [go-pg](https://github.com/go-pg/pg), and theoretical maximum PostgreSQL performance. Unless the test specifically 6 | states otherwise it always uses prepared statements. 7 | 8 | ## Interesting Results 9 | 10 | * Network latency and PostgreSQL server processing time dominate most real world tests. 11 | * A simple query executed locally via Unix domain sockets can be several times faster than over the network. 12 | * TLS is cheap but it is not free (~15% impact). 13 | 14 | All the Go drivers perform similarly when performing queries that return small result sets over TCP. Larger differences are apparent with large result sets especially if one driver uses the binary format and another uses the text format for results. 15 | 16 | In addition, using driver-specific features can yield significant performance deltas. For example: 17 | 18 | * Batch operations make a huge difference when network latency is a significant factor ([pgx](https://github.com/jackc/pgx) and [go-pg](https://github.com/go-pg/pg) support in various ways). 19 | * [pgx](https://github.com/jackc/pgx) automatically prepares and caches SQL. This can make a large difference for code that does not explicitly prepare statements, but has no advantage if it does. 20 | * [go-pg](https://github.com/go-pg/pg) is an ORM as well as a driver. Idiomatic usage is different and does more work than other drivers. This makes direct comparison difficult. 21 | 22 | The raw results analyzed above are in the results directory. You can also run the benchmarks for yourself in your own environment. 23 | 24 | ## Configuration 25 | 26 | go_db_bench reads its configuration from the standard PostgreSQL environment variables such as `PGHOST`, `PGPORT`, `PGUSER`, `PGPASSWORD`, `PGDATABASE`, and `PGSSLMODE`. 27 | 28 | ## Core Benchmarks 29 | 30 | go_db_bench includes tests selecting one value, one row, and multiple rows. 31 | 32 | Example execution: 33 | 34 | PGHOST=/private/tmp go test -test.bench=. -test.benchmem 35 | 36 | ## HTTP Benchmarks 37 | 38 | go_db_bench includes a simple HTTP server that serves JSON directly from 39 | PostgreSQL. This allows testing the performance of database drivers in a more 40 | real-world environment. 41 | 42 | Example execution: 43 | 44 | go build && PGHOST=/private/tmp ./go_db_bench 45 | 46 | It exposes the following endpoints: 47 | 48 | * /people/pgx-native - pgx through its native interface 49 | * /people/pgx-stdlib - pgx through database/sql 50 | * /people/pq - pq through database/sql 51 | 52 | Start the server and use your favorite HTTP load tester to benchmark (I 53 | recommend [siege](http://www.joedog.org/siege-home/) or 54 | [overload](https://github.com/jackc/overload)). 55 | 56 | ## Theoretical Max PostgreSQL Performance 57 | 58 | This benchmark includes a minimum PostgreSQL driver sufficient to establish a 59 | connection and prepare statements. Query execution is benchmarked by sending a 60 | []byte filled with the query command and reading until the ready for query 61 | message is received. This should be the theoretical best performance a Go 62 | PostgreSQL driver could achieve. 63 | 64 | Caveat: The returned data is not checked or parsed. It is only read until the 65 | ready for query message is received. If an error occurs it may not be apparent 66 | which could cause the timing to be misleading. 67 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "database/sql" 6 | "fmt" 7 | "io" 8 | "math/rand" 9 | "net/http" 10 | "os" 11 | "path/filepath" 12 | "strings" 13 | "time" 14 | 15 | gopg "github.com/go-pg/pg/v10" 16 | pgxv4 "github.com/jackc/pgx/v4" 17 | pgxpoolv4 "github.com/jackc/pgx/v4/pgxpool" 18 | stdlibv4 "github.com/jackc/pgx/v4/stdlib" 19 | pgxpoolv5 "github.com/jackc/pgx/v5/pgxpool" 20 | _ "github.com/lib/pq" 21 | ) 22 | 23 | var selectPeopleJSONSQL = ` 24 | select coalesce(json_agg(row_to_json(person)), '[]'::json) 25 | from person 26 | where id between $1 and $1 + 25 27 | ` 28 | 29 | func main() { 30 | connPoolConfig, err := pgxpoolv4.ParseConfig("") 31 | if err != nil { 32 | fmt.Fprintln(os.Stderr, "pool.ParseConfig failed:", err) 33 | os.Exit(1) 34 | } 35 | 36 | err = loadTestData(connPoolConfig.ConnConfig) 37 | if err != nil { 38 | fmt.Fprintln(os.Stderr, "loadTestData failed:", err) 39 | os.Exit(1) 40 | } 41 | 42 | connPoolConfig.AfterConnect = func(ctx context.Context, conn *pgxv4.Conn) error { 43 | _, err := conn.Prepare(ctx, "selectPeopleJSON", selectPeopleJSONSQL) 44 | if err != nil { 45 | return err 46 | } 47 | return nil 48 | } 49 | 50 | pgxPool, err := openPgxNativeV4(connPoolConfig) 51 | if err != nil { 52 | fmt.Fprintln(os.Stderr, "openPgxNative failed:", err) 53 | os.Exit(1) 54 | } 55 | 56 | pgxStdlib, err := openPgxStdlibV4(connPoolConfig) 57 | if err != nil { 58 | fmt.Fprintln(os.Stderr, "openPgxNative failed:", err) 59 | os.Exit(1) 60 | } 61 | pgxStmt, err := pgxStdlib.Prepare(selectPeopleJSONSQL) 62 | if err != nil { 63 | fmt.Fprintln(os.Stderr, "pgxStdlib.Prepare failed:", err) 64 | os.Exit(1) 65 | } 66 | 67 | pq, err := openPq(connPoolConfig.ConnConfig) 68 | if err != nil { 69 | fmt.Fprintln(os.Stderr, "openPq failed:", err) 70 | os.Exit(1) 71 | } 72 | pqStmt, err := pq.Prepare(selectPeopleJSONSQL) 73 | if err != nil { 74 | fmt.Fprintln(os.Stderr, "pq.Prepare failed:", err) 75 | os.Exit(1) 76 | } 77 | 78 | pg, err := openPg(*connPoolConfig.ConnConfig) 79 | if err != nil { 80 | fmt.Fprintln(os.Stderr, "openPg failed:", err) 81 | os.Exit(1) 82 | } 83 | pgStmt, err := pg.Prepare(selectPeopleJSONSQL) 84 | if err != nil { 85 | fmt.Fprintln(os.Stderr, "pg.Prepare failed:", err) 86 | os.Exit(1) 87 | } 88 | 89 | http.HandleFunc("/people/pgx-native", func(w http.ResponseWriter, req *http.Request) { 90 | w.Header().Set("Content-Type", "application/json") 91 | 92 | var json string 93 | 94 | err := pgxPool.QueryRow(context.Background(), "selectPeopleJSON", rand.Int31n(10000)).Scan(&json) 95 | if err != nil { 96 | fmt.Fprintln(os.Stderr, err) 97 | http.Error(w, "Internal server error", http.StatusInternalServerError) 98 | return 99 | } 100 | io.WriteString(w, json) 101 | }) 102 | 103 | http.HandleFunc("/people/pgx-stdlib", func(w http.ResponseWriter, req *http.Request) { 104 | w.Header().Set("Content-Type", "application/json") 105 | 106 | row := pgxStmt.QueryRow(rand.Int31n(10000)) 107 | var json string 108 | err := row.Scan(&json) 109 | if err != nil { 110 | fmt.Fprintln(os.Stderr, err) 111 | http.Error(w, "Internal server error", http.StatusInternalServerError) 112 | return 113 | } 114 | 115 | io.WriteString(w, json) 116 | }) 117 | 118 | http.HandleFunc("/people/pq", func(w http.ResponseWriter, req *http.Request) { 119 | w.Header().Set("Content-Type", "application/json") 120 | 121 | row := pqStmt.QueryRow(rand.Int31n(10000)) 122 | var json string 123 | err := row.Scan(&json) 124 | if err != nil { 125 | fmt.Fprintln(os.Stderr, err) 126 | http.Error(w, "Internal server error", http.StatusInternalServerError) 127 | return 128 | } 129 | 130 | io.WriteString(w, json) 131 | }) 132 | 133 | http.HandleFunc("/people/pg", func(w http.ResponseWriter, req *http.Request) { 134 | w.Header().Set("Content-Type", "application/json") 135 | 136 | var json string 137 | 138 | _, err := pgStmt.QueryOne(&json, rand.Int31n(10000)) 139 | if err != nil { 140 | fmt.Fprintln(os.Stderr, err) 141 | http.Error(w, "Internal server error", http.StatusInternalServerError) 142 | return 143 | } 144 | io.WriteString(w, json) 145 | }) 146 | 147 | fmt.Println("Starting Go DB Bench on localhost:8080") 148 | err = http.ListenAndServe("localhost:8080", nil) 149 | if err != nil { 150 | fmt.Fprintln(os.Stderr, "Unable to start web server: ", err) 151 | os.Exit(1) 152 | } 153 | } 154 | 155 | func loadTestData(config *pgxv4.ConnConfig) error { 156 | ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) 157 | defer cancel() 158 | 159 | conn, err := pgxv4.ConnectConfig(ctx, config) 160 | if err != nil { 161 | return err 162 | } 163 | defer conn.Close(ctx) 164 | 165 | _, err = conn.Exec(ctx, personCreateSQL) 166 | if err != nil { 167 | return err 168 | } 169 | 170 | _, err = conn.Exec(ctx, personInsertSQL) 171 | if err != nil { 172 | return err 173 | } 174 | 175 | _, err = conn.Exec(ctx, "analyze person") 176 | if err != nil { 177 | return err 178 | } 179 | 180 | return nil 181 | } 182 | 183 | func openPgxNativeV4(config *pgxpoolv4.Config) (*pgxpoolv4.Pool, error) { 184 | return pgxpoolv4.ConnectConfig(context.Background(), config) 185 | } 186 | 187 | func openPgxStdlibV4(config *pgxpoolv4.Config) (*sql.DB, error) { 188 | db := stdlibv4.OpenDB(*config.ConnConfig) 189 | return db, db.Ping() 190 | } 191 | 192 | func openPgxNativeV5(config *pgxpoolv5.Config) (*pgxpoolv5.Pool, error) { 193 | return pgxpoolv5.ConnectConfig(context.Background(), config) 194 | } 195 | 196 | func openPq(config *pgxv4.ConnConfig) (*sql.DB, error) { 197 | var options []string 198 | options = append(options, fmt.Sprintf("host=%s", config.Host)) 199 | options = append(options, fmt.Sprintf("port=%v", config.Port)) 200 | options = append(options, fmt.Sprintf("user=%s", config.User)) 201 | options = append(options, fmt.Sprintf("dbname=%s", config.Database)) 202 | if config.TLSConfig == nil { 203 | options = append(options, "sslmode=disable") 204 | } else { 205 | options = append(options, "sslmode=require") 206 | } 207 | if config.Password != "" { 208 | options = append(options, fmt.Sprintf("password=%s", config.Password)) 209 | } 210 | 211 | return sql.Open("postgres", strings.Join(options, " ")) 212 | } 213 | 214 | func openPg(config pgxv4.ConnConfig) (*gopg.DB, error) { 215 | var options gopg.Options 216 | 217 | options.Addr = fmt.Sprintf("%s:%d", config.Host, config.Port) 218 | _, err := os.Stat(config.Host) 219 | if err == nil { 220 | options.Network = "unix" 221 | if !strings.Contains(config.Host, "/.s.PGSQL.") { 222 | options.Addr = filepath.Join(config.Host, ".s.PGSQL.5432") 223 | } 224 | } 225 | 226 | options.User = config.User 227 | options.Database = config.Database 228 | options.Password = config.Password 229 | options.TLSConfig = config.TLSConfig 230 | 231 | return gopg.Connect(&options), nil 232 | } 233 | -------------------------------------------------------------------------------- /results/2020-03-20/different-hosts-tls.txt: -------------------------------------------------------------------------------- 1 | root@ubuntu-c-2-4gib-nyc1-02:~/dev/go_db_bench# go version 2 | go version go1.14.1 linux/amd64 3 | root@ubuntu-c-2-4gib-nyc1-02:~/dev/go_db_bench# PGHOST=xxx PGDATABASE=bench PGUSER=bench PGPASSWORD=xxx go test -test.bench=. -test.benchmem 4 | goos: linux 5 | goarch: amd64 6 | pkg: github.com/jackc/go_db_bench 7 | BenchmarkPgxNativeSelectSingleShortString-2 6334 189657 ns/op 443 B/op 6 allocs/op 8 | BenchmarkPgxStdlibSelectSingleShortString-2 5974 202474 ns/op 888 B/op 23 allocs/op 9 | BenchmarkPgSelectSingleShortString-2 6300 203343 ns/op 237 B/op 12 allocs/op 10 | BenchmarkPqSelectSingleShortString-2 5853 194308 ns/op 608 B/op 20 allocs/op 11 | BenchmarkRawSelectSingleShortValue-2 5694 182722 ns/op 106 B/op 3 allocs/op 12 | BenchmarkPgxNativeSelectSingleShortBytes-2 5863 205596 ns/op 458 B/op 7 allocs/op 13 | BenchmarkPgxStdlibSelectSingleShortBytes-2 6060 203889 ns/op 915 B/op 24 allocs/op 14 | BenchmarkPqSelectSingleShortBytes-2 6187 195039 ns/op 632 B/op 21 allocs/op 15 | BenchmarkPgxNativeSelectSingleRow-2 6081 196405 ns/op 911 B/op 8 allocs/op 16 | BenchmarkPgxNativeSelectSingleRowNotPreparedWithStatementCacheModePrepare-2 5211 199712 ns/op 913 B/op 8 allocs/op 17 | BenchmarkPgxNativeSelectSingleRowNotPreparedWithStatementCacheModeDescribe-2 4698 223857 ns/op 912 B/op 8 allocs/op 18 | BenchmarkPgxNativeSelectSingleRowNotPreparedWithStatementCacheDisabled-2 2804 430126 ns/op 1653 B/op 13 allocs/op 19 | BenchmarkPgconnSelectSingleRowTextProtocolNoParsing-2 6782 185834 ns/op 372 B/op 1 allocs/op 20 | BenchmarkPgconnSelectSingleRowBinaryProtocolNoParsing-2 6220 184334 ns/op 359 B/op 1 allocs/op 21 | BenchmarkPgxStdlibSelectSingleRow-2 6132 208435 ns/op 1940 B/op 47 allocs/op 22 | BenchmarkPgxStdlibSelectSingleRowNotPreparedStatementCacheModePrepare-2 6248 200914 ns/op 1908 B/op 46 allocs/op 23 | BenchmarkPgxStdlibSelectSingleRowNotPreparedStatementCacheModeDescribe-2 4538 225328 ns/op 1907 B/op 46 allocs/op 24 | BenchmarkPgxStdlibSelectSingleRowNotPreparedStatementCacheModeDisabled-2 2680 439808 ns/op 2646 B/op 51 allocs/op 25 | BenchmarkPgSelectSingleRow-2 5655 197440 ns/op 410 B/op 12 allocs/op 26 | BenchmarkPgSelectSingleRowNotPrepared-2 4941 240091 ns/op 394 B/op 11 allocs/op 27 | BenchmarkPqSelectSingleRow-2 6009 202787 ns/op 1007 B/op 32 allocs/op 28 | BenchmarkPqSelectSingleRowNotPrepared-2 2922 427269 ns/op 1662 B/op 46 allocs/op 29 | BenchmarkRawSelectSingleRow-2 6375 187441 ns/op 95 B/op 3 allocs/op 30 | BenchmarkPgxNativeSelectMultipleRows-2 4495 269759 ns/op 6446 B/op 104 allocs/op 31 | BenchmarkPgxNativeSelectMultipleRowsIntoGenericBinary-2 5074 243237 ns/op 6043 B/op 29 allocs/op 32 | BenchmarkPgConnSelectMultipleRowsWithWithDecodeBinary-2 5660 214161 ns/op 2303 B/op 1 allocs/op 33 | BenchmarkPgxNativeSelectMultipleRowsWithoutScan-2 4993 230476 ns/op 2591 B/op 3 allocs/op 34 | BenchmarkPgxNativeSelectMultipleRowsIntoGenericBinaryWithoutScan-2 5269 228376 ns/op 2587 B/op 3 allocs/op 35 | BenchmarkPgxStdlibSelectMultipleRows-2 4404 277611 ns/op 7816 B/op 351 allocs/op 36 | BenchmarkPgSelectMultipleRowsCollect-2 3529 319756 ns/op 9442 B/op 116 allocs/op 37 | BenchmarkPgSelectMultipleRowsAndDiscard-2 4948 235735 ns/op 120 B/op 6 allocs/op 38 | BenchmarkPqSelectMultipleRows-2 4405 287796 ns/op 5973 B/op 384 allocs/op 39 | BenchmarkRawSelectMultipleRows-2 5371 194812 ns/op 112 B/op 3 allocs/op 40 | BenchmarkPgxNativeSelectMultipleRowsBytes-2 4365 274569 ns/op 6973 B/op 179 allocs/op 41 | BenchmarkPgxStdlibSelectMultipleRowsBytes-2 3932 287927 ns/op 8598 B/op 427 allocs/op 42 | BenchmarkPqSelectMultipleRowsBytes-2 4238 290548 ns/op 6853 B/op 459 allocs/op 43 | BenchmarkPgxNativeSelectBatch3Query-2 5745 209186 ns/op 1553 B/op 13 allocs/op 44 | BenchmarkPgxNativeSelectNoBatch3Query-2 2186 568916 ns/op 1234 B/op 12 allocs/op 45 | BenchmarkPgxStdlibSelectNoBatch3Query-2 2161 579282 ns/op 2522 B/op 61 allocs/op 46 | BenchmarkPqSelectNoBatch3Query-2 2149 565013 ns/op 1728 B/op 52 allocs/op 47 | BenchmarkPgxNativeSelectLargeTextString1KB-2 5952 201780 ns/op 2551 B/op 6 allocs/op 48 | BenchmarkPgxNativeSelectLargeTextString8KB-2 4822 252229 ns/op 26226 B/op 8 allocs/op 49 | BenchmarkPgxNativeSelectLargeTextString64KB-2 1972 650887 ns/op 147890 B/op 10 allocs/op 50 | BenchmarkPgxNativeSelectLargeTextString512KB-2 283 4223046 ns/op 1066014 B/op 29 allocs/op 51 | BenchmarkPgxNativeSelectLargeTextString4096KB-2 33 30504553 ns/op 8410522 B/op 178 allocs/op 52 | BenchmarkPgxStdlibSelectLargeTextString1KB-2 5896 199839 ns/op 2991 B/op 23 allocs/op 53 | BenchmarkPgxStdlibSelectLargeTextString8KB-2 5006 240792 ns/op 26666 B/op 25 allocs/op 54 | BenchmarkPgxStdlibSelectLargeTextString64KB-2 1861 748838 ns/op 148321 B/op 27 allocs/op 55 | BenchmarkPgxStdlibSelectLargeTextString512KB-2 286 4233902 ns/op 1066413 B/op 46 allocs/op 56 | BenchmarkPgxStdlibSelectLargeTextString4096KB-2 33 30412941 ns/op 8411439 B/op 195 allocs/op 57 | BenchmarkPgSelectLargeTextString1KB-2 5931 199103 ns/op 1256 B/op 12 allocs/op 58 | BenchmarkPgSelectLargeTextString8KB-2 4863 233147 ns/op 8442 B/op 12 allocs/op 59 | BenchmarkPgSelectLargeTextString64KB-2 2005 701968 ns/op 65867 B/op 14 allocs/op 60 | BenchmarkPgSelectLargeTextString512KB-2 294 3883836 ns/op 525649 B/op 33 allocs/op 61 | BenchmarkPgSelectLargeTextString4096KB-2 38 27286552 ns/op 4205255 B/op 182 allocs/op 62 | BenchmarkPqSelectLargeTextString1KB-2 5893 198650 ns/op 2776 B/op 21 allocs/op 63 | BenchmarkPqSelectLargeTextString8KB-2 5013 231391 ns/op 18278 B/op 21 allocs/op 64 | BenchmarkPqSelectLargeTextString64KB-2 1890 737900 ns/op 139933 B/op 23 allocs/op 65 | BenchmarkPqSelectLargeTextString512KB-2 283 4177129 ns/op 1058046 B/op 42 allocs/op 66 | BenchmarkPqSelectLargeTextString4096KB-2 34 30503232 ns/op 8402844 B/op 191 allocs/op 67 | BenchmarkPgxNativeSelectLargeTextBytes1KB-2 5670 201456 ns/op 3592 B/op 7 allocs/op 68 | BenchmarkPgxNativeSelectLargeTextBytes8KB-2 4827 253104 ns/op 34435 B/op 9 allocs/op 69 | BenchmarkPgxNativeSelectLargeTextBytes64KB-2 1966 691095 ns/op 213435 B/op 11 allocs/op 70 | BenchmarkPgxNativeSelectLargeTextBytes512KB-2 331 3516163 ns/op 1590319 B/op 30 allocs/op 71 | BenchmarkPgxNativeSelectLargeTextBytes4096KB-2 32 32974240 ns/op 12605012 B/op 179 allocs/op 72 | BenchmarkPgxStdlibSelectLargeTextBytes1KB-2 6211 198363 ns/op 4029 B/op 24 allocs/op 73 | BenchmarkPgxStdlibSelectLargeTextBytes8KB-2 5192 247658 ns/op 34874 B/op 26 allocs/op 74 | BenchmarkPgxStdlibSelectLargeTextBytes64KB-2 2108 708825 ns/op 213882 B/op 28 allocs/op 75 | BenchmarkPgxStdlibSelectLargeTextBytes512KB-2 325 3719918 ns/op 1590739 B/op 47 allocs/op 76 | BenchmarkPgxStdlibSelectLargeTextBytes4096KB-2 31 32936737 ns/op 12605787 B/op 196 allocs/op 77 | BenchmarkPqSelectLargeTextBytes1KB-2 6148 196083 ns/op 3816 B/op 22 allocs/op 78 | BenchmarkPqSelectLargeTextBytes8KB-2 4887 243736 ns/op 26482 B/op 22 allocs/op 79 | BenchmarkPqSelectLargeTextBytes64KB-2 1815 720709 ns/op 205485 B/op 24 allocs/op 80 | BenchmarkPqSelectLargeTextBytes512KB-2 313 3745078 ns/op 1582353 B/op 43 allocs/op 81 | BenchmarkPqSelectLargeTextBytes4096KB-2 37 32917883 ns/op 12597166 B/op 192 allocs/op 82 | BenchmarkPgConnSelectMultipleRowsWithWithDecodeBinaryZeroAlloc-2 4318 235828 ns/op 36 B/op 1 allocs/op 83 | PASS 84 | ok github.com/jackc/go_db_bench 98.320s 85 | -------------------------------------------------------------------------------- /results/2020-03-20/same-host-unix-socket.txt: -------------------------------------------------------------------------------- 1 | root@ubuntu-c-2-4gib-nyc1-01:~/dev/go_db_bench# go version 2 | go version go1.14.1 linux/amd64 3 | root@ubuntu-c-2-4gib-nyc1-01:~/dev/go_db_bench# PGHOST=/var/run/postgresql PGDATABASE=bench PGUSER=bench go test -test.bench=. -test.benchmem 4 | goos: linux 5 | goarch: amd64 6 | pkg: github.com/jackc/go_db_bench 7 | BenchmarkPgxNativeSelectSingleShortString-2 20143 55288 ns/op 410 B/op 5 allocs/op 8 | BenchmarkPgxStdlibSelectSingleShortString-2 20799 59287 ns/op 859 B/op 22 allocs/op 9 | BenchmarkPgSelectSingleShortString-2 27370 55631 ns/op 206 B/op 11 allocs/op 10 | BenchmarkPqSelectSingleShortString-2 19606 53873 ns/op 576 B/op 19 allocs/op 11 | BenchmarkRawSelectSingleShortValue-2 29330 49450 ns/op 20 B/op 0 allocs/op 12 | BenchmarkPgxNativeSelectSingleShortBytes-2 22268 55194 ns/op 430 B/op 6 allocs/op 13 | BenchmarkPgxStdlibSelectSingleShortBytes-2 19738 60892 ns/op 883 B/op 23 allocs/op 14 | BenchmarkPqSelectSingleShortBytes-2 20754 56711 ns/op 600 B/op 20 allocs/op 15 | BenchmarkPgxNativeSelectSingleRow-2 19856 58308 ns/op 879 B/op 7 allocs/op 16 | BenchmarkPgxNativeSelectSingleRowNotPreparedWithStatementCacheModePrepare-2 20964 62768 ns/op 880 B/op 7 allocs/op 17 | BenchmarkPgxNativeSelectSingleRowNotPreparedWithStatementCacheModeDescribe-2 8592 134098 ns/op 880 B/op 7 allocs/op 18 | BenchmarkPgxNativeSelectSingleRowNotPreparedWithStatementCacheDisabled-2 6690 168094 ns/op 1589 B/op 11 allocs/op 19 | BenchmarkPgconnSelectSingleRowTextProtocolNoParsing-2 23823 55354 ns/op 341 B/op 0 allocs/op 20 | BenchmarkPgconnSelectSingleRowBinaryProtocolNoParsing-2 21124 56965 ns/op 327 B/op 0 allocs/op 21 | BenchmarkPgxStdlibSelectSingleRow-2 18283 71827 ns/op 1908 B/op 46 allocs/op 22 | BenchmarkPgxStdlibSelectSingleRowNotPreparedStatementCacheModePrepare-2 16537 72749 ns/op 1876 B/op 45 allocs/op 23 | BenchmarkPgxStdlibSelectSingleRowNotPreparedStatementCacheModeDescribe-2 8014 145770 ns/op 1877 B/op 45 allocs/op 24 | BenchmarkPgxStdlibSelectSingleRowNotPreparedStatementCacheModeDisabled-2 6040 203351 ns/op 2585 B/op 49 allocs/op 25 | BenchmarkPgSelectSingleRow-2 19494 64480 ns/op 378 B/op 11 allocs/op 26 | BenchmarkPgSelectSingleRowNotPrepared-2 10000 118128 ns/op 368 B/op 10 allocs/op 27 | BenchmarkPqSelectSingleRow-2 18320 66083 ns/op 975 B/op 31 allocs/op 28 | BenchmarkPqSelectSingleRowNotPrepared-2 6568 174408 ns/op 1598 B/op 44 allocs/op 29 | BenchmarkRawSelectSingleRow-2 25914 48419 ns/op 23 B/op 0 allocs/op 30 | BenchmarkPgxNativeSelectMultipleRows-2 8263 147626 ns/op 6405 B/op 103 allocs/op 31 | BenchmarkPgxNativeSelectMultipleRowsIntoGenericBinary-2 10000 120834 ns/op 6007 B/op 28 allocs/op 32 | BenchmarkPgConnSelectMultipleRowsWithWithDecodeBinary-2 10000 106007 ns/op 2265 B/op 0 allocs/op 33 | BenchmarkPgxNativeSelectMultipleRowsWithoutScan-2 10000 114726 ns/op 2555 B/op 2 allocs/op 34 | BenchmarkPgxNativeSelectMultipleRowsIntoGenericBinaryWithoutScan-2 10000 107487 ns/op 2555 B/op 2 allocs/op 35 | BenchmarkPgxStdlibSelectMultipleRows-2 6612 184247 ns/op 7778 B/op 350 allocs/op 36 | BenchmarkPgSelectMultipleRowsCollect-2 10000 208531 ns/op 9397 B/op 114 allocs/op 37 | BenchmarkPgSelectMultipleRowsAndDiscard-2 9640 106172 ns/op 84 B/op 5 allocs/op 38 | BenchmarkPqSelectMultipleRows-2 10000 188643 ns/op 5932 B/op 383 allocs/op 39 | BenchmarkRawSelectMultipleRows-2 14858 93228 ns/op 40 B/op 1 allocs/op 40 | BenchmarkPgxNativeSelectMultipleRowsBytes-2 8376 153622 ns/op 6936 B/op 178 allocs/op 41 | BenchmarkPgxStdlibSelectMultipleRowsBytes-2 6481 193125 ns/op 8554 B/op 425 allocs/op 42 | BenchmarkPqSelectMultipleRowsBytes-2 10000 192368 ns/op 6809 B/op 458 allocs/op 43 | BenchmarkPgxNativeSelectBatch3Query-2 17118 74701 ns/op 1518 B/op 12 allocs/op 44 | BenchmarkPgxNativeSelectNoBatch3Query-2 10000 122265 ns/op 1137 B/op 9 allocs/op 45 | BenchmarkPgxStdlibSelectNoBatch3Query-2 10000 133941 ns/op 2428 B/op 58 allocs/op 46 | BenchmarkPqSelectNoBatch3Query-2 10000 129271 ns/op 1632 B/op 49 allocs/op 47 | BenchmarkPgxNativeSelectLargeTextString1KB-2 17418 69181 ns/op 2520 B/op 5 allocs/op 48 | BenchmarkPgxNativeSelectLargeTextString8KB-2 7268 159875 ns/op 26182 B/op 7 allocs/op 49 | BenchmarkPgxNativeSelectLargeTextString64KB-2 2796 388372 ns/op 147779 B/op 7 allocs/op 50 | BenchmarkPgxNativeSelectLargeTextString512KB-2 403 2965380 ns/op 1065274 B/op 7 allocs/op 51 | BenchmarkPgxNativeSelectLargeTextString4096KB-2 50 23430036 ns/op 8405468 B/op 7 allocs/op 52 | BenchmarkPgxStdlibSelectLargeTextString1KB-2 18954 64485 ns/op 2957 B/op 22 allocs/op 53 | BenchmarkPgxStdlibSelectLargeTextString8KB-2 7490 157539 ns/op 26620 B/op 24 allocs/op 54 | BenchmarkPgxStdlibSelectLargeTextString64KB-2 2509 460298 ns/op 148219 B/op 24 allocs/op 55 | BenchmarkPgxStdlibSelectLargeTextString512KB-2 414 3087248 ns/op 1065744 B/op 24 allocs/op 56 | BenchmarkPgxStdlibSelectLargeTextString4096KB-2 44 23426734 ns/op 8405554 B/op 24 allocs/op 57 | BenchmarkPgSelectLargeTextString1KB-2 20878 56650 ns/op 1224 B/op 11 allocs/op 58 | BenchmarkPgSelectLargeTextString8KB-2 8616 136778 ns/op 8407 B/op 11 allocs/op 59 | BenchmarkPgSelectLargeTextString64KB-2 3237 370022 ns/op 65737 B/op 11 allocs/op 60 | BenchmarkPgSelectLargeTextString512KB-2 435 2684198 ns/op 524948 B/op 11 allocs/op 61 | BenchmarkPgSelectLargeTextString4096KB-2 52 22021187 ns/op 4205907 B/op 11 allocs/op 62 | BenchmarkPqSelectLargeTextString1KB-2 18442 64944 ns/op 2744 B/op 20 allocs/op 63 | BenchmarkPqSelectLargeTextString8KB-2 7682 148381 ns/op 18232 B/op 20 allocs/op 64 | BenchmarkPqSelectLargeTextString64KB-2 2796 416081 ns/op 139832 B/op 20 allocs/op 65 | BenchmarkPqSelectLargeTextString512KB-2 394 2944675 ns/op 1057336 B/op 20 allocs/op 66 | BenchmarkPqSelectLargeTextString4096KB-2 49 23712511 ns/op 8397371 B/op 20 allocs/op 67 | BenchmarkPgxNativeSelectLargeTextBytes1KB-2 17844 65003 ns/op 3560 B/op 6 allocs/op 68 | BenchmarkPgxNativeSelectLargeTextBytes8KB-2 7002 160503 ns/op 34389 B/op 8 allocs/op 69 | BenchmarkPgxNativeSelectLargeTextBytes64KB-2 3030 419322 ns/op 213333 B/op 8 allocs/op 70 | BenchmarkPgxNativeSelectLargeTextBytes512KB-2 488 2433634 ns/op 1589576 B/op 8 allocs/op 71 | BenchmarkPgxNativeSelectLargeTextBytes4096KB-2 38 27524142 ns/op 12599731 B/op 8 allocs/op 72 | BenchmarkPgxStdlibSelectLargeTextBytes1KB-2 19038 68114 ns/op 3998 B/op 23 allocs/op 73 | BenchmarkPgxStdlibSelectLargeTextBytes8KB-2 7308 184905 ns/op 34827 B/op 25 allocs/op 74 | BenchmarkPgxStdlibSelectLargeTextBytes64KB-2 3026 394954 ns/op 213771 B/op 25 allocs/op 75 | BenchmarkPgxStdlibSelectLargeTextBytes512KB-2 511 2440017 ns/op 1590030 B/op 25 allocs/op 76 | BenchmarkPgxStdlibSelectLargeTextBytes4096KB-2 44 25635376 ns/op 12599875 B/op 25 allocs/op 77 | BenchmarkPqSelectLargeTextBytes1KB-2 19498 61228 ns/op 3784 B/op 21 allocs/op 78 | BenchmarkPqSelectLargeTextBytes8KB-2 8101 153856 ns/op 26440 B/op 21 allocs/op 79 | BenchmarkPqSelectLargeTextBytes64KB-2 2852 379920 ns/op 205384 B/op 21 allocs/op 80 | BenchmarkPqSelectLargeTextBytes512KB-2 487 2509786 ns/op 1581640 B/op 21 allocs/op 81 | BenchmarkPqSelectLargeTextBytes4096KB-2 42 25683541 ns/op 12591691 B/op 21 allocs/op 82 | BenchmarkPgConnSelectMultipleRowsWithWithDecodeBinaryZeroAlloc-2 10000 103259 ns/op 0 B/op 0 allocs/op 83 | PASS 84 | ok github.com/jackc/go_db_bench 115.653s 85 | -------------------------------------------------------------------------------- /results/2020-03-20/different-hosts-no-tls.txt: -------------------------------------------------------------------------------- 1 | root@ubuntu-c-2-4gib-nyc1-02:~/dev/go_db_bench# go version 2 | go version go1.14.1 linux/amd64 3 | root@ubuntu-c-2-4gib-nyc1-02:~/dev/go_db_bench# PGSSLMODE=disable PGHOST=142.93.124.18 PGDATABASE=bench PGUSER=bench PGPASSWORD=NSw8mHvCPDF61K1b6 go test -test.bench=. -test.benchmem 4 | goos: linux 5 | goarch: amd64 6 | pkg: github.com/jackc/go_db_bench 7 | BenchmarkPgxNativeSelectSingleShortString-2 6847 176852 ns/op 409 B/op 5 allocs/op 8 | BenchmarkPgxStdlibSelectSingleShortString-2 6822 174158 ns/op 859 B/op 22 allocs/op 9 | BenchmarkPgSelectSingleShortString-2 7143 167522 ns/op 205 B/op 11 allocs/op 10 | BenchmarkPqSelectSingleShortString-2 6991 176577 ns/op 576 B/op 19 allocs/op 11 | BenchmarkRawSelectSingleShortValue-2 5941 181941 ns/op 101 B/op 3 allocs/op 12 | BenchmarkPgxNativeSelectSingleShortBytes-2 7154 178455 ns/op 431 B/op 6 allocs/op 13 | BenchmarkPgxStdlibSelectSingleShortBytes-2 6738 178363 ns/op 884 B/op 23 allocs/op 14 | BenchmarkPqSelectSingleShortBytes-2 6805 186021 ns/op 600 B/op 20 allocs/op 15 | BenchmarkPgxNativeSelectSingleRow-2 7147 170798 ns/op 879 B/op 7 allocs/op 16 | BenchmarkPgxNativeSelectSingleRowNotPreparedWithStatementCacheModePrepare-2 6237 180988 ns/op 881 B/op 7 allocs/op 17 | BenchmarkPgxNativeSelectSingleRowNotPreparedWithStatementCacheModeDescribe-2 5023 236196 ns/op 879 B/op 7 allocs/op 18 | BenchmarkPgxNativeSelectSingleRowNotPreparedWithStatementCacheDisabled-2 2828 384432 ns/op 1593 B/op 11 allocs/op 19 | BenchmarkPgconnSelectSingleRowTextProtocolNoParsing-2 6618 189182 ns/op 341 B/op 0 allocs/op 20 | BenchmarkPgconnSelectSingleRowBinaryProtocolNoParsing-2 6680 190559 ns/op 327 B/op 0 allocs/op 21 | BenchmarkPgxStdlibSelectSingleRow-2 6279 180130 ns/op 1907 B/op 46 allocs/op 22 | BenchmarkPgxStdlibSelectSingleRowNotPreparedStatementCacheModePrepare-2 6141 204249 ns/op 1875 B/op 45 allocs/op 23 | BenchmarkPgxStdlibSelectSingleRowNotPreparedStatementCacheModeDescribe-2 5266 220928 ns/op 1877 B/op 45 allocs/op 24 | BenchmarkPgxStdlibSelectSingleRowNotPreparedStatementCacheModeDisabled-2 3106 424031 ns/op 2586 B/op 49 allocs/op 25 | BenchmarkPgSelectSingleRow-2 6483 175543 ns/op 378 B/op 11 allocs/op 26 | BenchmarkPgSelectSingleRowNotPrepared-2 5943 202135 ns/op 362 B/op 10 allocs/op 27 | BenchmarkPqSelectSingleRow-2 6685 182228 ns/op 975 B/op 31 allocs/op 28 | BenchmarkPqSelectSingleRowNotPrepared-2 3223 393109 ns/op 1598 B/op 44 allocs/op 29 | BenchmarkRawSelectSingleRow-2 6302 187669 ns/op 96 B/op 3 allocs/op 30 | BenchmarkPgxNativeSelectMultipleRows-2 5008 232952 ns/op 6407 B/op 103 allocs/op 31 | BenchmarkPgxNativeSelectMultipleRowsIntoGenericBinary-2 5695 212503 ns/op 6008 B/op 28 allocs/op 32 | BenchmarkPgConnSelectMultipleRowsWithWithDecodeBinary-2 5887 202470 ns/op 2265 B/op 0 allocs/op 33 | BenchmarkPgxNativeSelectMultipleRowsWithoutScan-2 5793 206595 ns/op 2556 B/op 2 allocs/op 34 | BenchmarkPgxNativeSelectMultipleRowsIntoGenericBinaryWithoutScan-2 5899 204490 ns/op 2554 B/op 2 allocs/op 35 | BenchmarkPgxStdlibSelectMultipleRows-2 4057 303827 ns/op 7777 B/op 350 allocs/op 36 | BenchmarkPgSelectMultipleRowsCollect-2 4610 274349 ns/op 9387 B/op 114 allocs/op 37 | BenchmarkPgSelectMultipleRowsAndDiscard-2 5852 199241 ns/op 84 B/op 5 allocs/op 38 | BenchmarkPqSelectMultipleRows-2 4995 252278 ns/op 5933 B/op 383 allocs/op 39 | BenchmarkRawSelectMultipleRows-2 6120 194978 ns/op 98 B/op 3 allocs/op 40 | BenchmarkPgxNativeSelectMultipleRowsBytes-2 5286 231428 ns/op 6933 B/op 178 allocs/op 41 | BenchmarkPgxStdlibSelectMultipleRowsBytes-2 4084 295818 ns/op 8554 B/op 425 allocs/op 42 | BenchmarkPqSelectMultipleRowsBytes-2 4591 259588 ns/op 6812 B/op 458 allocs/op 43 | BenchmarkPgxNativeSelectBatch3Query-2 6738 180473 ns/op 1520 B/op 12 allocs/op 44 | BenchmarkPgxNativeSelectNoBatch3Query-2 2400 478872 ns/op 1136 B/op 9 allocs/op 45 | BenchmarkPgxStdlibSelectNoBatch3Query-2 2311 559251 ns/op 2423 B/op 58 allocs/op 46 | BenchmarkPqSelectNoBatch3Query-2 2473 507205 ns/op 1632 B/op 49 allocs/op 47 | BenchmarkPgxNativeSelectLargeTextString1KB-2 7284 167729 ns/op 2520 B/op 5 allocs/op 48 | BenchmarkPgxNativeSelectLargeTextString8KB-2 5479 225769 ns/op 26183 B/op 7 allocs/op 49 | BenchmarkPgxNativeSelectLargeTextString64KB-2 2377 612938 ns/op 147786 B/op 7 allocs/op 50 | BenchmarkPgxNativeSelectLargeTextString512KB-2 333 3616707 ns/op 1065288 B/op 7 allocs/op 51 | BenchmarkPgxNativeSelectLargeTextString4096KB-2 43 25626713 ns/op 8405365 B/op 7 allocs/op 52 | BenchmarkPgxStdlibSelectLargeTextString1KB-2 6174 179580 ns/op 2957 B/op 22 allocs/op 53 | BenchmarkPgxStdlibSelectLargeTextString8KB-2 5358 230006 ns/op 26620 B/op 24 allocs/op 54 | BenchmarkPgxStdlibSelectLargeTextString64KB-2 2126 641497 ns/op 148225 B/op 24 allocs/op 55 | BenchmarkPgxStdlibSelectLargeTextString512KB-2 348 3467988 ns/op 1065753 B/op 24 allocs/op 56 | BenchmarkPgxStdlibSelectLargeTextString4096KB-2 40 26020621 ns/op 8405555 B/op 24 allocs/op 57 | BenchmarkPgSelectLargeTextString1KB-2 7216 168766 ns/op 1224 B/op 11 allocs/op 58 | BenchmarkPgSelectLargeTextString8KB-2 6006 202801 ns/op 8392 B/op 11 allocs/op 59 | BenchmarkPgSelectLargeTextString64KB-2 2107 572666 ns/op 65737 B/op 11 allocs/op 60 | BenchmarkPgSelectLargeTextString512KB-2 373 3170248 ns/op 524847 B/op 11 allocs/op 61 | BenchmarkPgSelectLargeTextString4096KB-2 52 23308708 ns/op 4200865 B/op 11 allocs/op 62 | BenchmarkPqSelectLargeTextString1KB-2 6376 173190 ns/op 2744 B/op 20 allocs/op 63 | BenchmarkPqSelectLargeTextString8KB-2 6099 212339 ns/op 18232 B/op 20 allocs/op 64 | BenchmarkPqSelectLargeTextString64KB-2 2277 632063 ns/op 139832 B/op 20 allocs/op 65 | BenchmarkPqSelectLargeTextString512KB-2 351 3385050 ns/op 1057336 B/op 20 allocs/op 66 | BenchmarkPqSelectLargeTextString4096KB-2 49 24187082 ns/op 8397371 B/op 20 allocs/op 67 | BenchmarkPgxNativeSelectLargeTextBytes1KB-2 6352 169107 ns/op 3559 B/op 6 allocs/op 68 | BenchmarkPgxNativeSelectLargeTextBytes8KB-2 5522 234963 ns/op 34391 B/op 8 allocs/op 69 | BenchmarkPgxNativeSelectLargeTextBytes64KB-2 2473 616048 ns/op 213337 B/op 8 allocs/op 70 | BenchmarkPgxNativeSelectLargeTextBytes512KB-2 376 2922004 ns/op 1589596 B/op 8 allocs/op 71 | BenchmarkPgxNativeSelectLargeTextBytes4096KB-2 39 28352163 ns/op 12599858 B/op 8 allocs/op 72 | BenchmarkPgxStdlibSelectLargeTextBytes1KB-2 5714 179159 ns/op 3997 B/op 23 allocs/op 73 | BenchmarkPgxStdlibSelectLargeTextBytes8KB-2 5509 224304 ns/op 34827 B/op 25 allocs/op 74 | BenchmarkPgxStdlibSelectLargeTextBytes64KB-2 2270 639604 ns/op 213775 B/op 25 allocs/op 75 | BenchmarkPgxStdlibSelectLargeTextBytes512KB-2 414 3016407 ns/op 1590048 B/op 25 allocs/op 76 | BenchmarkPgxStdlibSelectLargeTextBytes4096KB-2 40 26951944 ns/op 12599875 B/op 25 allocs/op 77 | BenchmarkPqSelectLargeTextBytes1KB-2 5824 181424 ns/op 3784 B/op 21 allocs/op 78 | BenchmarkPqSelectLargeTextBytes8KB-2 5624 213886 ns/op 26440 B/op 21 allocs/op 79 | BenchmarkPqSelectLargeTextBytes64KB-2 2377 600837 ns/op 205384 B/op 21 allocs/op 80 | BenchmarkPqSelectLargeTextBytes512KB-2 434 2888717 ns/op 1581640 B/op 21 allocs/op 81 | BenchmarkPqSelectLargeTextBytes4096KB-2 45 26591240 ns/op 12591692 B/op 21 allocs/op 82 | BenchmarkPgConnSelectMultipleRowsWithWithDecodeBinaryZeroAlloc-2 5890 228595 ns/op 0 B/op 0 allocs/op 83 | PASS 84 | ok github.com/jackc/go_db_bench 103.171s 85 | -------------------------------------------------------------------------------- /raw/value_transcoder.go: -------------------------------------------------------------------------------- 1 | package raw 2 | 3 | import ( 4 | "bytes" 5 | "encoding/hex" 6 | "fmt" 7 | "math" 8 | "regexp" 9 | "strconv" 10 | "time" 11 | "unsafe" 12 | ) 13 | 14 | // ValueTranscoder stores all the data necessary to encode and decode values from 15 | // a PostgreSQL server 16 | type ValueTranscoder struct { 17 | // DecodeText decodes values returned from the server in text format 18 | DecodeText func(*MessageReader, int32) interface{} 19 | // DecodeBinary decodes values returned from the server in binary format 20 | DecodeBinary func(*MessageReader, int32) interface{} 21 | // EncodeTo encodes values to send to the server 22 | EncodeTo func(*WriteBuf, interface{}) error 23 | // EncodeFormat is the format values are encoded for transmission. 24 | // 0 = text 25 | // 1 = binary 26 | EncodeFormat int16 27 | } 28 | 29 | // ValueTranscoders is used to transcode values being sent to and received from 30 | // the PostgreSQL server. Additional types can be transcoded by adding a 31 | // *ValueTranscoder for the appropriate Oid to the map. 32 | var ValueTranscoders map[Oid]*ValueTranscoder 33 | 34 | var defaultTranscoder *ValueTranscoder 35 | 36 | func init() { 37 | ValueTranscoders = make(map[Oid]*ValueTranscoder) 38 | 39 | // bool 40 | ValueTranscoders[Oid(16)] = &ValueTranscoder{ 41 | DecodeText: decodeBoolFromText, 42 | DecodeBinary: decodeBoolFromBinary, 43 | EncodeTo: encodeBool, 44 | EncodeFormat: 1} 45 | 46 | // bytea 47 | ValueTranscoders[Oid(17)] = &ValueTranscoder{ 48 | DecodeText: decodeByteaFromText, 49 | EncodeTo: encodeBytea, 50 | EncodeFormat: 1} 51 | 52 | // int8 53 | ValueTranscoders[Oid(20)] = &ValueTranscoder{ 54 | DecodeText: decodeInt8FromText, 55 | DecodeBinary: decodeInt8FromBinary, 56 | EncodeTo: encodeInt8, 57 | EncodeFormat: 1} 58 | 59 | // int2 60 | ValueTranscoders[Oid(21)] = &ValueTranscoder{ 61 | DecodeText: decodeInt2FromText, 62 | DecodeBinary: decodeInt2FromBinary, 63 | EncodeTo: encodeInt2, 64 | EncodeFormat: 1} 65 | 66 | // int4 67 | ValueTranscoders[Oid(23)] = &ValueTranscoder{ 68 | DecodeText: decodeInt4FromText, 69 | DecodeBinary: decodeInt4FromBinary, 70 | EncodeTo: encodeInt4, 71 | EncodeFormat: 1} 72 | 73 | // text 74 | ValueTranscoders[Oid(25)] = &ValueTranscoder{ 75 | DecodeText: decodeTextFromText, 76 | EncodeTo: encodeText} 77 | 78 | // float4 79 | ValueTranscoders[Oid(700)] = &ValueTranscoder{ 80 | DecodeText: decodeFloat4FromText, 81 | DecodeBinary: decodeFloat4FromBinary, 82 | EncodeTo: encodeFloat4, 83 | EncodeFormat: 1} 84 | 85 | // float8 86 | ValueTranscoders[Oid(701)] = &ValueTranscoder{ 87 | DecodeText: decodeFloat8FromText, 88 | DecodeBinary: decodeFloat8FromBinary, 89 | EncodeTo: encodeFloat8, 90 | EncodeFormat: 1} 91 | 92 | // int2[] 93 | ValueTranscoders[Oid(1005)] = &ValueTranscoder{ 94 | DecodeText: decodeInt2ArrayFromText, 95 | EncodeTo: encodeInt2Array} 96 | 97 | // int4[] 98 | ValueTranscoders[Oid(1007)] = &ValueTranscoder{ 99 | DecodeText: decodeInt4ArrayFromText, 100 | EncodeTo: encodeInt4Array} 101 | 102 | // int8[] 103 | ValueTranscoders[Oid(1016)] = &ValueTranscoder{ 104 | DecodeText: decodeInt8ArrayFromText, 105 | EncodeTo: encodeInt8Array} 106 | 107 | // varchar -- same as text 108 | ValueTranscoders[Oid(1043)] = ValueTranscoders[Oid(25)] 109 | 110 | // date 111 | ValueTranscoders[Oid(1082)] = &ValueTranscoder{ 112 | DecodeText: decodeDateFromText, 113 | DecodeBinary: decodeDateFromBinary, 114 | EncodeTo: encodeDate} 115 | 116 | // timestamptz 117 | ValueTranscoders[Oid(1184)] = &ValueTranscoder{ 118 | DecodeText: decodeTimestampTzFromText, 119 | DecodeBinary: decodeTimestampTzFromBinary, 120 | EncodeTo: encodeTimestampTz} 121 | 122 | // use text transcoder for anything we don't understand 123 | defaultTranscoder = ValueTranscoders[Oid(25)] 124 | } 125 | 126 | var arrayEl *regexp.Regexp = regexp.MustCompile(`[{,](?:"((?:[^"\\]|\\.)*)"|(NULL)|([^,}]+))`) 127 | 128 | // SplitArrayText is used by array transcoders to split array text into elements 129 | func SplitArrayText(text string) (elements []string) { 130 | matches := arrayEl.FindAllStringSubmatch(text, -1) 131 | elements = make([]string, 0, len(matches)) 132 | for _, match := range matches { 133 | if match[1] != "" { 134 | elements = append(elements, match[1]) 135 | } else if match[2] != "" { 136 | elements = append(elements, match[2]) 137 | } else if match[3] != "" { 138 | elements = append(elements, match[3]) 139 | } 140 | } 141 | return 142 | } 143 | 144 | func decodeBoolFromText(mr *MessageReader, size int32) interface{} { 145 | s := mr.ReadString(size) 146 | switch s { 147 | case "t": 148 | return true 149 | case "f": 150 | return false 151 | default: 152 | return ProtocolError(fmt.Sprintf("Received invalid bool: %v", s)) 153 | } 154 | } 155 | 156 | func decodeBoolFromBinary(mr *MessageReader, size int32) interface{} { 157 | if size != 1 { 158 | return ProtocolError(fmt.Sprintf("Received an invalid size for an bool: %d", size)) 159 | } 160 | b := mr.ReadByte() 161 | return b != 0 162 | } 163 | 164 | func encodeBool(w *WriteBuf, value interface{}) error { 165 | v, ok := value.(bool) 166 | if !ok { 167 | return fmt.Errorf("Expected bool, received %T", value) 168 | } 169 | 170 | w.WriteInt32(1) 171 | 172 | var n byte 173 | if v { 174 | n = 1 175 | } 176 | 177 | w.WriteByte(n) 178 | 179 | return nil 180 | } 181 | 182 | func decodeInt8FromText(mr *MessageReader, size int32) interface{} { 183 | s := mr.ReadString(size) 184 | n, err := strconv.ParseInt(s, 10, 64) 185 | if err != nil { 186 | return ProtocolError(fmt.Sprintf("Received invalid int8: %v", s)) 187 | } 188 | return n 189 | } 190 | 191 | func decodeInt8FromBinary(mr *MessageReader, size int32) interface{} { 192 | if size != 8 { 193 | return ProtocolError(fmt.Sprintf("Received an invalid size for an int8: %d", size)) 194 | } 195 | return mr.ReadInt64() 196 | } 197 | 198 | func encodeInt8(w *WriteBuf, value interface{}) error { 199 | var v int64 200 | switch value := value.(type) { 201 | case int8: 202 | v = int64(value) 203 | case uint8: 204 | v = int64(value) 205 | case int16: 206 | v = int64(value) 207 | case uint16: 208 | v = int64(value) 209 | case int32: 210 | v = int64(value) 211 | case uint32: 212 | v = int64(value) 213 | case int64: 214 | v = int64(value) 215 | case uint64: 216 | if value > math.MaxInt64 { 217 | return fmt.Errorf("uint64 %d is larger than max int64 %d", value, math.MaxInt64) 218 | } 219 | v = int64(value) 220 | case int: 221 | v = int64(value) 222 | default: 223 | return fmt.Errorf("Expected integer representable in int64, received %T %v", value, value) 224 | } 225 | 226 | w.WriteInt32(8) 227 | w.WriteInt64(v) 228 | 229 | return nil 230 | } 231 | 232 | func decodeInt2FromText(mr *MessageReader, size int32) interface{} { 233 | s := mr.ReadString(size) 234 | n, err := strconv.ParseInt(s, 10, 16) 235 | if err != nil { 236 | return ProtocolError(fmt.Sprintf("Received invalid int2: %v", s)) 237 | } 238 | return int16(n) 239 | } 240 | 241 | func decodeInt2FromBinary(mr *MessageReader, size int32) interface{} { 242 | if size != 2 { 243 | return ProtocolError(fmt.Sprintf("Received an invalid size for an int2: %d", size)) 244 | } 245 | return mr.ReadInt16() 246 | } 247 | 248 | func encodeInt2(w *WriteBuf, value interface{}) error { 249 | var v int16 250 | switch value := value.(type) { 251 | case int8: 252 | v = int16(value) 253 | case uint8: 254 | v = int16(value) 255 | case int16: 256 | v = int16(value) 257 | case uint16: 258 | if value > math.MaxInt16 { 259 | return fmt.Errorf("%T %d is larger than max int16 %d", value, value, math.MaxInt16) 260 | } 261 | v = int16(value) 262 | case int32: 263 | if value > math.MaxInt16 { 264 | return fmt.Errorf("%T %d is larger than max int16 %d", value, value, math.MaxInt16) 265 | } 266 | v = int16(value) 267 | case uint32: 268 | if value > math.MaxInt16 { 269 | return fmt.Errorf("%T %d is larger than max int16 %d", value, value, math.MaxInt16) 270 | } 271 | v = int16(value) 272 | case int64: 273 | if value > math.MaxInt16 { 274 | return fmt.Errorf("%T %d is larger than max int16 %d", value, value, math.MaxInt16) 275 | } 276 | v = int16(value) 277 | case uint64: 278 | if value > math.MaxInt16 { 279 | return fmt.Errorf("%T %d is larger than max int16 %d", value, value, math.MaxInt16) 280 | } 281 | v = int16(value) 282 | case int: 283 | if value > math.MaxInt16 { 284 | return fmt.Errorf("%T %d is larger than max int16 %d", value, value, math.MaxInt16) 285 | } 286 | v = int16(value) 287 | default: 288 | return fmt.Errorf("Expected integer representable in int16, received %T %v", value, value) 289 | } 290 | 291 | w.WriteInt32(2) 292 | w.WriteInt16(v) 293 | 294 | return nil 295 | } 296 | 297 | func decodeInt4FromText(mr *MessageReader, size int32) interface{} { 298 | s := mr.ReadString(size) 299 | n, err := strconv.ParseInt(s, 10, 32) 300 | if err != nil { 301 | return ProtocolError(fmt.Sprintf("Received invalid int4: %v", s)) 302 | } 303 | return int32(n) 304 | } 305 | 306 | func decodeInt4FromBinary(mr *MessageReader, size int32) interface{} { 307 | if size != 4 { 308 | return ProtocolError(fmt.Sprintf("Received an invalid size for an int4: %d", size)) 309 | } 310 | return mr.ReadInt32() 311 | } 312 | 313 | func encodeInt4(w *WriteBuf, value interface{}) error { 314 | var v int32 315 | switch value := value.(type) { 316 | case int8: 317 | v = int32(value) 318 | case uint8: 319 | v = int32(value) 320 | case int16: 321 | v = int32(value) 322 | case uint16: 323 | v = int32(value) 324 | case int32: 325 | v = int32(value) 326 | case uint32: 327 | if value > math.MaxInt32 { 328 | return fmt.Errorf("%T %d is larger than max int64 %d", value, value, math.MaxInt32) 329 | } 330 | v = int32(value) 331 | case int64: 332 | if value > math.MaxInt32 { 333 | return fmt.Errorf("%T %d is larger than max int64 %d", value, value, math.MaxInt32) 334 | } 335 | v = int32(value) 336 | case uint64: 337 | if value > math.MaxInt32 { 338 | return fmt.Errorf("%T %d is larger than max int64 %d", value, value, math.MaxInt32) 339 | } 340 | v = int32(value) 341 | case int: 342 | if value > math.MaxInt32 { 343 | return fmt.Errorf("%T %d is larger than max int64 %d", value, value, math.MaxInt32) 344 | } 345 | v = int32(value) 346 | default: 347 | return fmt.Errorf("Expected integer representable in int32, received %T %v", value, value) 348 | } 349 | 350 | w.WriteInt32(4) 351 | w.WriteInt32(v) 352 | 353 | return nil 354 | } 355 | 356 | func decodeFloat4FromText(mr *MessageReader, size int32) interface{} { 357 | s := mr.ReadString(size) 358 | n, err := strconv.ParseFloat(s, 32) 359 | if err != nil { 360 | return ProtocolError(fmt.Sprintf("Received invalid float4: %v", s)) 361 | } 362 | return float32(n) 363 | } 364 | 365 | func decodeFloat4FromBinary(mr *MessageReader, size int32) interface{} { 366 | if size != 4 { 367 | return ProtocolError(fmt.Sprintf("Received an invalid size for an float4: %d", size)) 368 | } 369 | 370 | i := mr.ReadInt32() 371 | p := unsafe.Pointer(&i) 372 | return *(*float32)(p) 373 | } 374 | 375 | func encodeFloat4(w *WriteBuf, value interface{}) error { 376 | var v float32 377 | switch value := value.(type) { 378 | case float32: 379 | v = float32(value) 380 | case float64: 381 | if value > math.MaxFloat32 { 382 | return fmt.Errorf("%T %f is larger than max float32 %f", value, math.MaxFloat32) 383 | } 384 | v = float32(value) 385 | default: 386 | return fmt.Errorf("Expected float representable in float32, received %T %v", value, value) 387 | } 388 | 389 | w.WriteInt32(4) 390 | 391 | p := unsafe.Pointer(&v) 392 | w.WriteInt32(*(*int32)(p)) 393 | 394 | return nil 395 | } 396 | 397 | func decodeFloat8FromText(mr *MessageReader, size int32) interface{} { 398 | s := mr.ReadString(size) 399 | v, err := strconv.ParseFloat(s, 64) 400 | if err != nil { 401 | return ProtocolError(fmt.Sprintf("Received invalid float8: %v", s)) 402 | } 403 | return v 404 | } 405 | 406 | func decodeFloat8FromBinary(mr *MessageReader, size int32) interface{} { 407 | if size != 8 { 408 | return ProtocolError(fmt.Sprintf("Received an invalid size for an float8: %d", size)) 409 | } 410 | 411 | i := mr.ReadInt64() 412 | p := unsafe.Pointer(&i) 413 | return *(*float64)(p) 414 | } 415 | 416 | func encodeFloat8(w *WriteBuf, value interface{}) error { 417 | var v float64 418 | switch value := value.(type) { 419 | case float32: 420 | v = float64(value) 421 | case float64: 422 | v = float64(value) 423 | default: 424 | return fmt.Errorf("Expected float representable in float64, received %T %v", value, value) 425 | } 426 | 427 | w.WriteInt32(8) 428 | 429 | p := unsafe.Pointer(&v) 430 | w.WriteInt64(*(*int64)(p)) 431 | 432 | return nil 433 | } 434 | 435 | func decodeTextFromText(mr *MessageReader, size int32) interface{} { 436 | return mr.ReadString(size) 437 | } 438 | 439 | func encodeText(w *WriteBuf, value interface{}) error { 440 | s, ok := value.(string) 441 | if !ok { 442 | return fmt.Errorf("Expected string, received %T", value) 443 | } 444 | 445 | w.WriteInt32(int32(len(s))) 446 | w.WriteBytes([]byte(s)) 447 | 448 | return nil 449 | } 450 | 451 | func decodeByteaFromText(mr *MessageReader, size int32) interface{} { 452 | s := mr.ReadString(size) 453 | b, err := hex.DecodeString(s[2:]) 454 | if err != nil { 455 | return ProtocolError(fmt.Sprintf("Can't decode byte array: %v - %v", err, s)) 456 | } 457 | return b 458 | } 459 | 460 | func encodeBytea(w *WriteBuf, value interface{}) error { 461 | b, ok := value.([]byte) 462 | if !ok { 463 | return fmt.Errorf("Expected []byte, received %T", value) 464 | } 465 | 466 | w.WriteInt32(int32(len(b))) 467 | w.WriteBytes(b) 468 | 469 | return nil 470 | } 471 | 472 | func decodeDateFromText(mr *MessageReader, size int32) interface{} { 473 | s := mr.ReadString(size) 474 | t, err := time.ParseInLocation("2006-01-02", s, time.Local) 475 | if err != nil { 476 | return ProtocolError(fmt.Sprintf("Can't decode date: %v", s)) 477 | } 478 | return t 479 | } 480 | 481 | func decodeDateFromBinary(mr *MessageReader, size int32) interface{} { 482 | dayOffset := mr.ReadInt32() 483 | return time.Date(2000, 1, int(1+dayOffset), 0, 0, 0, 0, time.Local) 484 | } 485 | 486 | func encodeDate(w *WriteBuf, value interface{}) error { 487 | t, ok := value.(time.Time) 488 | if !ok { 489 | return fmt.Errorf("Expected time.Time, received %T", value) 490 | } 491 | 492 | s := t.Format("2006-01-02") 493 | return encodeText(w, s) 494 | } 495 | 496 | func decodeTimestampTzFromText(mr *MessageReader, size int32) interface{} { 497 | s := mr.ReadString(size) 498 | t, err := time.Parse("2006-01-02 15:04:05.999999-07", s) 499 | if err != nil { 500 | return ProtocolError(fmt.Sprintf("Can't decode timestamptz: %v - %v", err, s)) 501 | } 502 | return t 503 | } 504 | 505 | func decodeTimestampTzFromBinary(mr *MessageReader, size int32) interface{} { 506 | if size != 8 { 507 | return ProtocolError(fmt.Sprintf("Received an invalid size for an int8: %d", size)) 508 | } 509 | microsecFromUnixEpochToY2K := int64(946684800 * 1000000) 510 | microsecSinceY2K := mr.ReadInt64() 511 | microsecSinceUnixEpoch := microsecFromUnixEpochToY2K + microsecSinceY2K 512 | return time.Unix(microsecSinceUnixEpoch/1000000, (microsecSinceUnixEpoch%1000000)*1000) 513 | 514 | // 2000-01-01 00:00:00 in 946684800 515 | // 946684800 * 1000000 516 | 517 | } 518 | 519 | func encodeTimestampTz(w *WriteBuf, value interface{}) error { 520 | t, ok := value.(time.Time) 521 | if !ok { 522 | return fmt.Errorf("Expected time.Time, received %T", value) 523 | } 524 | 525 | s := t.Format("2006-01-02 15:04:05.999999 -0700") 526 | return encodeText(w, s) 527 | } 528 | 529 | func decodeInt2ArrayFromText(mr *MessageReader, size int32) interface{} { 530 | s := mr.ReadString(size) 531 | 532 | elements := SplitArrayText(s) 533 | 534 | numbers := make([]int16, 0, len(elements)) 535 | 536 | for _, e := range elements { 537 | n, err := strconv.ParseInt(e, 10, 16) 538 | if err != nil { 539 | return ProtocolError(fmt.Sprintf("Received invalid int2[]: %v", s)) 540 | } 541 | numbers = append(numbers, int16(n)) 542 | } 543 | 544 | return numbers 545 | } 546 | 547 | func int16SliceToArrayString(nums []int16) (string, error) { 548 | w := &bytes.Buffer{} 549 | _, err := w.WriteString("{") 550 | if err != nil { 551 | return "", err 552 | } 553 | 554 | for i, n := range nums { 555 | if i > 0 { 556 | _, err = w.WriteString(",") 557 | if err != nil { 558 | return "", err 559 | } 560 | } 561 | 562 | _, err = w.WriteString(strconv.FormatInt(int64(n), 10)) 563 | if err != nil { 564 | return "", err 565 | } 566 | } 567 | 568 | _, err = w.WriteString("}") 569 | if err != nil { 570 | return "", err 571 | } 572 | 573 | return w.String(), nil 574 | } 575 | 576 | func encodeInt2Array(w *WriteBuf, value interface{}) error { 577 | v, ok := value.([]int16) 578 | if !ok { 579 | return fmt.Errorf("Expected []int16, received %T", value) 580 | } 581 | 582 | s, err := int16SliceToArrayString(v) 583 | if err != nil { 584 | return fmt.Errorf("Failed to encode []int16: %v", err) 585 | } 586 | 587 | return encodeText(w, s) 588 | } 589 | 590 | func decodeInt4ArrayFromText(mr *MessageReader, size int32) interface{} { 591 | s := mr.ReadString(size) 592 | 593 | elements := SplitArrayText(s) 594 | 595 | numbers := make([]int32, 0, len(elements)) 596 | 597 | for _, e := range elements { 598 | n, err := strconv.ParseInt(e, 10, 16) 599 | if err != nil { 600 | return ProtocolError(fmt.Sprintf("Received invalid int4[]: %v", s)) 601 | } 602 | numbers = append(numbers, int32(n)) 603 | } 604 | 605 | return numbers 606 | } 607 | 608 | func int32SliceToArrayString(nums []int32) (string, error) { 609 | w := &bytes.Buffer{} 610 | 611 | _, err := w.WriteString("{") 612 | if err != nil { 613 | return "", err 614 | } 615 | 616 | for i, n := range nums { 617 | if i > 0 { 618 | _, err = w.WriteString(",") 619 | if err != nil { 620 | return "", err 621 | } 622 | } 623 | 624 | _, err = w.WriteString(strconv.FormatInt(int64(n), 10)) 625 | if err != nil { 626 | return "", err 627 | } 628 | } 629 | 630 | _, err = w.WriteString("}") 631 | if err != nil { 632 | return "", err 633 | } 634 | 635 | return w.String(), nil 636 | } 637 | 638 | func encodeInt4Array(w *WriteBuf, value interface{}) error { 639 | v, ok := value.([]int32) 640 | if !ok { 641 | return fmt.Errorf("Expected []int32, received %T", value) 642 | } 643 | 644 | s, err := int32SliceToArrayString(v) 645 | if err != nil { 646 | return fmt.Errorf("Failed to encode []int32: %v", err) 647 | } 648 | 649 | return encodeText(w, s) 650 | } 651 | 652 | func decodeInt8ArrayFromText(mr *MessageReader, size int32) interface{} { 653 | s := mr.ReadString(size) 654 | 655 | elements := SplitArrayText(s) 656 | 657 | numbers := make([]int64, 0, len(elements)) 658 | 659 | for _, e := range elements { 660 | n, err := strconv.ParseInt(e, 10, 16) 661 | if err != nil { 662 | return ProtocolError(fmt.Sprintf("Received invalid int8[]: %v", s)) 663 | } 664 | numbers = append(numbers, int64(n)) 665 | } 666 | 667 | return numbers 668 | } 669 | 670 | func int64SliceToArrayString(nums []int64) (string, error) { 671 | w := &bytes.Buffer{} 672 | 673 | _, err := w.WriteString("{") 674 | if err != nil { 675 | return "", err 676 | } 677 | 678 | for i, n := range nums { 679 | if i > 0 { 680 | _, err = w.WriteString(",") 681 | if err != nil { 682 | return "", err 683 | } 684 | } 685 | 686 | _, err = w.WriteString(strconv.FormatInt(int64(n), 10)) 687 | if err != nil { 688 | return "", err 689 | } 690 | } 691 | 692 | _, err = w.WriteString("}") 693 | if err != nil { 694 | return "", err 695 | } 696 | 697 | return w.String(), nil 698 | } 699 | 700 | func encodeInt8Array(w *WriteBuf, value interface{}) error { 701 | v, ok := value.([]int64) 702 | if !ok { 703 | return fmt.Errorf("Expected []int64, received %T", value) 704 | } 705 | 706 | s, err := int64SliceToArrayString(v) 707 | if err != nil { 708 | return fmt.Errorf("Failed to encode []int64: %v", err) 709 | } 710 | 711 | return encodeText(w, s) 712 | } 713 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 2 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 3 | github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc= 4 | github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= 5 | github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= 6 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= 7 | github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I= 8 | github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= 9 | github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= 10 | github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= 11 | github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= 12 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 13 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 14 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 15 | github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 16 | github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= 17 | github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= 18 | github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= 19 | github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= 20 | github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= 21 | github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= 22 | github.com/go-pg/pg/v10 v10.10.6 h1:1vNtPZ4Z9dWUw/TjJwOfFUbF5nEq1IkR6yG8Mq/Iwso= 23 | github.com/go-pg/pg/v10 v10.10.6/go.mod h1:GLmFXufrElQHf5uzM3BQlcfwV3nsgnHue5uzjQ6Nqxg= 24 | github.com/go-pg/zerochecker v0.2.0 h1:pp7f72c3DobMWOb2ErtZsnrPaSvHd2W4o9//8HtF4mU= 25 | github.com/go-pg/zerochecker v0.2.0/go.mod h1:NJZ4wKL0NmTtz0GKCoJ8kym6Xn/EQzXRl2OnAe7MmDo= 26 | github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= 27 | github.com/go-stack/stack v1.8.1 h1:ntEHSVwIt7PNXNpgPmVfMrNhLtgjlmnZha2kOpuRiDw= 28 | github.com/go-stack/stack v1.8.1/go.mod h1:dcoOX6HbPZSZptuspn9bctJ+N/CnF5gGygcUP3XYfe4= 29 | github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw= 30 | github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= 31 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= 32 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 33 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 34 | github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 35 | github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= 36 | github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= 37 | github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= 38 | github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= 39 | github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= 40 | github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= 41 | github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= 42 | github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= 43 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= 44 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 45 | github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 46 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 47 | github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 48 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 49 | github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= 50 | github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= 51 | github.com/jackc/chunkreader v1.0.0 h1:4s39bBR8ByfqH+DKm8rQA3E1LHZWB9XWcrz8fqaZbe0= 52 | github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo= 53 | github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= 54 | github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8= 55 | github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= 56 | github.com/jackc/pgconn v0.0.0-20190420214824-7e0022ef6ba3/go.mod h1:jkELnwuX+w9qN5YIfX0fl88Ehu4XC3keFuOJJk9pcnA= 57 | github.com/jackc/pgconn v0.0.0-20190824142844-760dd75542eb/go.mod h1:lLjNuW/+OfW9/pnVKPazfWOgNfH2aPem8YQ7ilXGvJE= 58 | github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsUgOEh9hBm+xYTstcNHg7UPMVJqRfQxq4s= 59 | github.com/jackc/pgconn v1.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfGIB/o= 60 | github.com/jackc/pgconn v1.9.0/go.mod h1:YctiPyvzfU11JFxoXokUOOKQXQmDMoJL9vJzHH8/2JY= 61 | github.com/jackc/pgconn v1.9.1-0.20210724152538-d89c8390a530/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI= 62 | github.com/jackc/pgconn v1.11.0 h1:HiHArx4yFbwl91X3qqIHtUFoiIfLNJXCQRsnzkiwwaQ= 63 | github.com/jackc/pgconn v1.11.0/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI= 64 | github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE= 65 | github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8= 66 | github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE= 67 | github.com/jackc/pgmock v0.0.0-20201204152224-4fe30f7445fd/go.mod h1:hrBW0Enj2AZTNpt/7Y5rr2xe/9Mn757Wtb2xeBzPv2c= 68 | github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65 h1:DadwsjnMwFjfWc9y5Wi/+Zz7xoE5ALHsRQlOctkOiHc= 69 | github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65/go.mod h1:5R2h2EEX+qri8jOWMbJCtaPWkrrNc7OHwsp2TCqp7ak= 70 | github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= 71 | github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= 72 | github.com/jackc/pgproto3 v1.1.0 h1:FYYE4yRw+AgI8wXIinMlNjBbp/UitDJwfj5LqqewP1A= 73 | github.com/jackc/pgproto3 v1.1.0/go.mod h1:eR5FA3leWg7p9aeAqi37XOTgTIbkABlvcPB3E5rlc78= 74 | github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db/go.mod h1:bhq50y+xrl9n5mRYyCBFKkpRVTLYJVWeCc+mEAI3yXA= 75 | github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod h1:uH0AWtUmuShn0bcesswc4aBTWGvw0cAxIJp+6OB//Wg= 76 | github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= 77 | github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= 78 | github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= 79 | github.com/jackc/pgproto3/v2 v2.1.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= 80 | github.com/jackc/pgproto3/v2 v2.2.0 h1:r7JypeP2D3onoQTCxWdTpCtJ4D+qpKr0TxvoyMhZ5ns= 81 | github.com/jackc/pgproto3/v2 v2.2.0/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= 82 | github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b h1:C8S2+VttkHFdOOCXJe+YGfa4vHYwlt4Zx+IVXQ97jYg= 83 | github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E= 84 | github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg= 85 | github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc= 86 | github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw= 87 | github.com/jackc/pgtype v1.8.1-0.20210724151600-32e20a603178/go.mod h1:C516IlIV9NKqfsMCXTdChteoXmwgUceqaLfjg2e3NlM= 88 | github.com/jackc/pgtype v1.10.0 h1:ILnBWrRMSXGczYvmkYD6PsYyVFUNLTnIUJHHDLmqk38= 89 | github.com/jackc/pgtype v1.10.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4= 90 | github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y= 91 | github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM= 92 | github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc= 93 | github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c/go.mod h1:1QD0+tgSXP7iUjYm9C1NxKhny7lq6ee99u/z+IHFcgs= 94 | github.com/jackc/pgx/v4 v4.15.0 h1:B7dTkXsdILD3MF987WGGCcg+tvLW6bZJdEcqVFeU//w= 95 | github.com/jackc/pgx/v4 v4.15.0/go.mod h1:D/zyOyXiaM1TmVWnOM18p0xdDtdakRBa0RsVGI3U3bw= 96 | github.com/jackc/pgx/v5 v5.0.0-20220227022815-a8f6674a07b2 h1:ILsvMxsLmIVqSc3EuvAfBlkmyr0wNOoT3esX6BvzNuU= 97 | github.com/jackc/pgx/v5 v5.0.0-20220227022815-a8f6674a07b2/go.mod h1:hpqr/HW4qanKY/8S2BFVFWYaOpch/IjnvAAt6YdntZQ= 98 | github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= 99 | github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= 100 | github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= 101 | github.com/jackc/puddle v1.2.1 h1:gI8os0wpRXFd4FiAY2dWiqRK037tjj3t7rKFeO4X5iw= 102 | github.com/jackc/puddle v1.2.1/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= 103 | github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= 104 | github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= 105 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 106 | github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= 107 | github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= 108 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 109 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 110 | github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= 111 | github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= 112 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 113 | github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= 114 | github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= 115 | github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= 116 | github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= 117 | github.com/lib/pq v1.10.4 h1:SO9z7FRPzA03QhHKJrH5BXA6HU1rS4V2nIVrrNC1iYk= 118 | github.com/lib/pq v1.10.4/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= 119 | github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= 120 | github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= 121 | github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40= 122 | github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= 123 | github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= 124 | github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= 125 | github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= 126 | github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= 127 | github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= 128 | github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= 129 | github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= 130 | github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78= 131 | github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= 132 | github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= 133 | github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= 134 | github.com/onsi/ginkgo v1.14.2 h1:8mVmC9kjFFmA8H4pKMUhcblgifdkOIXPvbhN1T36q1M= 135 | github.com/onsi/ginkgo v1.14.2/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= 136 | github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= 137 | github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= 138 | github.com/onsi/gomega v1.10.3 h1:gph6h/qe9GSUw1NhH1gp+qb+h8rXD8Cy60Z32Qw3ELA= 139 | github.com/onsi/gomega v1.10.3/go.mod h1:V9xEwhxec5O8UDM77eCW8vLymOMltsqPVYWrpDsH8xc= 140 | github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= 141 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 142 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 143 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 144 | github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 145 | github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= 146 | github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= 147 | github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU= 148 | github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc= 149 | github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= 150 | github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= 151 | github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ= 152 | github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= 153 | github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= 154 | github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= 155 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 156 | github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 157 | github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= 158 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 159 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 160 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 161 | github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= 162 | github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 163 | github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= 164 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 165 | github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc h1:9lRDQMhESg+zvGYmW5DyG0UqvY96Bu5QYsTLvCHdrgo= 166 | github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc/go.mod h1:bciPuU6GHm1iF1pBvUfxfsH0Wmnc2VbpgvbI9ZWuIRs= 167 | github.com/vmihailenco/bufpool v0.1.11 h1:gOq2WmBrq0i2yW5QJ16ykccQ4wH9UyEsgLm6czKAd94= 168 | github.com/vmihailenco/bufpool v0.1.11/go.mod h1:AFf/MOy3l2CFTKbxwt0mp2MwnqjNEs5H/UxrkA5jxTQ= 169 | github.com/vmihailenco/msgpack/v5 v5.3.4 h1:qMKAwOV+meBw2Y8k9cVwAy7qErtYCwBzZ2ellBfvnqc= 170 | github.com/vmihailenco/msgpack/v5 v5.3.4/go.mod h1:7xyJ9e+0+9SaZT0Wt1RGleJXzli6Q/V5KbhBonMG9jc= 171 | github.com/vmihailenco/tagparser v0.1.2 h1:gnjoVuB/kljJ5wICEEOpx98oXMWPLj22G67Vbd1qPqc= 172 | github.com/vmihailenco/tagparser v0.1.2/go.mod h1:OeAg3pn3UbLjkWt+rN9oFYB6u/cQgqMEUPoW2WPyhdI= 173 | github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g= 174 | github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds= 175 | github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= 176 | go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= 177 | go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= 178 | go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= 179 | go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= 180 | go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= 181 | go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= 182 | go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= 183 | go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= 184 | go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= 185 | go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= 186 | go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= 187 | golang.org/x/crypto v0.0.0-20180910181607-0e37d006457b/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 188 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 189 | golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= 190 | golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 191 | golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 192 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 193 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 194 | golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= 195 | golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 196 | golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 197 | golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 198 | golang.org/x/crypto v0.0.0-20211209193657-4570a0811e8b/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= 199 | golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3 h1:0es+/5331RGQPcXlMfP+WrnIIS6dNnNRe0WB02W0F4M= 200 | golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= 201 | golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 202 | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 203 | golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= 204 | golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 205 | golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 206 | golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= 207 | golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= 208 | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 209 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 210 | golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 211 | golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 212 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 213 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 214 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 215 | golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 216 | golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 217 | golang.org/x/net v0.0.0-20201006153459-a7d1128ccaa0/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 218 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 219 | golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 220 | golang.org/x/net v0.0.0-20211216030914-fe4d6282115f h1:hEYJvxw1lSnWIl8X9ofsYMklzaDs90JI2az5YMd4fPM= 221 | golang.org/x/net v0.0.0-20211216030914-fe4d6282115f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 222 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 223 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 224 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 225 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 226 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 227 | golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 228 | golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 229 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 230 | golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 231 | golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 232 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 233 | golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 234 | golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 235 | golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 236 | golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 237 | golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 238 | golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 239 | golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 240 | golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 241 | golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 242 | golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 243 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 244 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 245 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 246 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 247 | golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 248 | golang.org/x/sys v0.0.0-20210923061019-b8560ed6a9b7/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 249 | golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 250 | golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e h1:fLOSk5Q00efkSvAm+4xcoXD+RRmLmmulPn5I3Y9F2EM= 251 | golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 252 | golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= 253 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 254 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 255 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 256 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 257 | golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 258 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 259 | golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= 260 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 261 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 262 | golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 263 | golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= 264 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 265 | golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 266 | golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 267 | golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 268 | golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 269 | golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 270 | golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 271 | golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 272 | golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 273 | golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 274 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 275 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 276 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 277 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= 278 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 279 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= 280 | google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 281 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= 282 | google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= 283 | google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= 284 | google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= 285 | google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= 286 | google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= 287 | google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= 288 | google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= 289 | google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= 290 | google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= 291 | google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= 292 | google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 293 | google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 294 | google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 295 | google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= 296 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 297 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 298 | gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= 299 | gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 300 | gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= 301 | gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= 302 | gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s= 303 | gopkg.in/inconshreveable/log15.v2 v2.0.0-20200109203555-b30bc20e4fd1 h1:iiHuQZCNgYPmFQxd3BBN/Nc5+dAwzZuq5y40s20oQw0= 304 | gopkg.in/inconshreveable/log15.v2 v2.0.0-20200109203555-b30bc20e4fd1/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s= 305 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= 306 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= 307 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 308 | gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 309 | gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= 310 | gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 311 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 312 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= 313 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 314 | honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 315 | honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 316 | honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= 317 | mellium.im/sasl v0.2.1 h1:nspKSRg7/SyO0cRGY71OkfHab8tf9kCts6a6oTDut0w= 318 | mellium.im/sasl v0.2.1/go.mod h1:ROaEDLQNuf9vjKqE1SrAfnsobm2YKXT1gnN1uDp1PjQ= 319 | -------------------------------------------------------------------------------- /raw/conn.go: -------------------------------------------------------------------------------- 1 | // Package raw is a modified pgx 2 | package raw 3 | 4 | import ( 5 | "bufio" 6 | "bytes" 7 | "crypto/md5" 8 | "crypto/tls" 9 | "encoding/binary" 10 | "encoding/hex" 11 | "errors" 12 | "fmt" 13 | log "gopkg.in/inconshreveable/log15.v2" 14 | "io" 15 | "io/ioutil" 16 | "net" 17 | "net/url" 18 | "os" 19 | "os/user" 20 | "path/filepath" 21 | "strconv" 22 | "strings" 23 | "time" 24 | ) 25 | 26 | // Transaction isolation levels 27 | const ( 28 | Serializable = "serializable" 29 | RepeatableRead = "repeatable read" 30 | ReadCommitted = "read committed" 31 | ReadUncommitted = "read uncommitted" 32 | ) 33 | 34 | // ConnConfig contains all the options used to establish a connection. 35 | type ConnConfig struct { 36 | Host string // host (e.g. localhost) or path to unix domain socket directory (e.g. /private/tmp) 37 | Port uint16 // default: 5432 38 | Database string 39 | User string // default: OS user name 40 | Password string 41 | MsgBufSize int // Size of work buffer used for transcoding messages. For optimal performance, it should be large enough to store a single row from any result set. Default: 1024 42 | TLSConfig *tls.Config // config for TLS connection -- nil disables TLS 43 | Logger log.Logger 44 | } 45 | 46 | // Conn is a PostgreSQL connection handle. It is not safe for concurrent usage. 47 | // Use ConnPool to manage access to multiple database connections from multiple 48 | // goroutines. 49 | type Conn struct { 50 | conn net.Conn // the underlying TCP or unix domain socket connection 51 | reader *bufio.Reader // buffered reader to improve read performance 52 | wbuf [1024]byte 53 | buf *bytes.Buffer // work buffer to avoid constant alloc and dealloc 54 | bufSize int // desired size of buf 55 | Pid int32 // backend pid 56 | SecretKey int32 // key to use to send a cancel query message to the server 57 | RuntimeParams map[string]string // parameters that have been reported by the server 58 | config ConnConfig // config used when establishing this connection 59 | TxStatus byte 60 | preparedStatements map[string]*PreparedStatement 61 | notifications []*Notification 62 | alive bool 63 | causeOfDeath error 64 | logger log.Logger 65 | drr DataRowReader 66 | } 67 | 68 | type PreparedStatement struct { 69 | Name string 70 | FieldDescriptions []FieldDescription 71 | ParameterOids []Oid 72 | } 73 | 74 | type Notification struct { 75 | Pid int32 // backend pid that sent the notification 76 | Channel string // channel from which notification was received 77 | Payload string 78 | } 79 | 80 | type CommandTag string 81 | 82 | // RowsAffected returns the number of rows affected. If the CommandTag was not 83 | // for a row affecting command (such as "CREATE TABLE") then it returns 0 84 | func (ct CommandTag) RowsAffected() int64 { 85 | words := strings.Split(string(ct), " ") 86 | n, _ := strconv.ParseInt(words[len(words)-1], 10, 64) 87 | return n 88 | } 89 | 90 | // NotSingleRowError is returned when exactly 1 row is expected, but 0 or more than 91 | // 1 row is returned 92 | type NotSingleRowError struct { 93 | RowCount int64 94 | } 95 | 96 | func (e NotSingleRowError) Error() string { 97 | return fmt.Sprintf("Expected to find 1 row exactly, instead found %d", e.RowCount) 98 | } 99 | 100 | // UnexpectedColumnCountError is returned when an unexpected number of columns is 101 | // returned from a Select. 102 | type UnexpectedColumnCountError struct { 103 | ExpectedCount int16 104 | ActualCount int16 105 | } 106 | 107 | func (e UnexpectedColumnCountError) Error() string { 108 | return fmt.Sprintf("Expected result to have %d column(s), instead it has %d", e.ExpectedCount, e.ActualCount) 109 | } 110 | 111 | type ProtocolError string 112 | 113 | func (e ProtocolError) Error() string { 114 | return string(e) 115 | } 116 | 117 | var NotificationTimeoutError = errors.New("Notification Timeout") 118 | var DeadConnError = errors.New("Connection is dead") 119 | 120 | // Connect establishes a connection with a PostgreSQL server using config. 121 | // config.Host must be specified. config.User will default to the OS user name. 122 | // Other config fields are optional. 123 | func Connect(config ConnConfig) (c *Conn, err error) { 124 | c = new(Conn) 125 | 126 | c.config = config 127 | if c.config.Logger != nil { 128 | c.logger = c.config.Logger 129 | } else { 130 | c.logger = log.New() 131 | c.logger.SetHandler(log.DiscardHandler()) 132 | } 133 | 134 | if c.config.User == "" { 135 | user, err := user.Current() 136 | if err != nil { 137 | return nil, err 138 | } 139 | c.config.User = user.Username 140 | c.logger.Debug("Using default connection config", "User", c.config.User) 141 | } 142 | 143 | if c.config.Port == 0 { 144 | c.config.Port = 5432 145 | c.logger.Debug("Using default connection config", "Port", c.config.Port) 146 | } 147 | if c.config.MsgBufSize == 0 { 148 | c.config.MsgBufSize = 1024 149 | c.logger.Debug("Using default connection config", "MsgBufSize", c.config.MsgBufSize) 150 | } 151 | 152 | // See if host is a valid path, if yes connect with a socket 153 | _, err = os.Stat(c.config.Host) 154 | if err == nil { 155 | // For backward compatibility accept socket file paths -- but directories are now preferred 156 | socket := c.config.Host 157 | if !strings.Contains(socket, "/.s.PGSQL.") { 158 | socket = filepath.Join(socket, ".s.PGSQL.") + strconv.FormatInt(int64(c.config.Port), 10) 159 | } 160 | 161 | c.logger.Info(fmt.Sprintf("Dialing PostgreSQL server at socket: %s", socket)) 162 | c.conn, err = net.Dial("unix", socket) 163 | if err != nil { 164 | c.logger.Error(fmt.Sprintf("Connection failed: %v", err)) 165 | return nil, err 166 | } 167 | } else { 168 | c.logger.Info(fmt.Sprintf("Dialing PostgreSQL server at host: %s:%d", c.config.Host, c.config.Port)) 169 | c.conn, err = net.Dial("tcp", fmt.Sprintf("%s:%d", c.config.Host, c.config.Port)) 170 | if err != nil { 171 | c.logger.Error(fmt.Sprintf("Connection failed: %v", err)) 172 | return nil, err 173 | } 174 | } 175 | defer func() { 176 | if c != nil && err != nil { 177 | c.conn.Close() 178 | c.alive = false 179 | c.logger.Error(err.Error()) 180 | } 181 | }() 182 | 183 | c.bufSize = c.config.MsgBufSize 184 | c.buf = bytes.NewBuffer(make([]byte, 0, c.bufSize)) 185 | c.RuntimeParams = make(map[string]string) 186 | c.preparedStatements = make(map[string]*PreparedStatement) 187 | c.alive = true 188 | 189 | if config.TLSConfig != nil { 190 | c.logger.Debug("Starting TLS handshake") 191 | if err = c.startTLS(); err != nil { 192 | c.logger.Error(fmt.Sprintf("TLS failed: %v", err)) 193 | return 194 | } 195 | } 196 | 197 | c.reader = bufio.NewReader(c.conn) 198 | 199 | msg := newStartupMessage() 200 | msg.options["user"] = c.config.User 201 | if c.config.Database != "" { 202 | msg.options["database"] = c.config.Database 203 | } 204 | if err = c.txStartupMessage(msg); err != nil { 205 | return 206 | } 207 | 208 | for { 209 | var t byte 210 | var r *MessageReader 211 | t, r, err = c.rxMsg() 212 | if err != nil { 213 | return nil, err 214 | } 215 | 216 | switch t { 217 | case backendKeyData: 218 | c.rxBackendKeyData(r) 219 | case authenticationX: 220 | if err = c.rxAuthenticationX(r); err != nil { 221 | return nil, err 222 | } 223 | case readyForQuery: 224 | c.rxReadyForQuery(r) 225 | c.logger = c.logger.New("pid", c.Pid) 226 | c.logger.Info("Connection established") 227 | return c, nil 228 | default: 229 | if err = c.processContextFreeMsg(t, r); err != nil { 230 | return nil, err 231 | } 232 | } 233 | } 234 | } 235 | 236 | func (c *Conn) Conn() net.Conn { 237 | return c.conn 238 | } 239 | 240 | // Close closes a connection. It is safe to call Close on a already closed 241 | // connection. 242 | func (c *Conn) Close() (err error) { 243 | if !c.IsAlive() { 244 | return nil 245 | } 246 | 247 | err = c.txMsg('X', c.getBuf()) 248 | c.die(errors.New("Closed")) 249 | c.logger.Info("Closed connection") 250 | return err 251 | } 252 | 253 | // ParseURI parses a database URI into ConnConfig 254 | func ParseURI(uri string) (ConnConfig, error) { 255 | var cp ConnConfig 256 | 257 | url, err := url.Parse(uri) 258 | if err != nil { 259 | return cp, err 260 | } 261 | 262 | if url.User != nil { 263 | cp.User = url.User.Username() 264 | cp.Password, _ = url.User.Password() 265 | } 266 | 267 | parts := strings.SplitN(url.Host, ":", 2) 268 | cp.Host = parts[0] 269 | if len(parts) == 2 { 270 | p, err := strconv.ParseUint(parts[1], 10, 16) 271 | if err != nil { 272 | return cp, err 273 | } 274 | cp.Port = uint16(p) 275 | } 276 | cp.Database = strings.TrimLeft(url.Path, "/") 277 | 278 | return cp, nil 279 | } 280 | 281 | // SelectFunc executes sql and for each row returned calls onDataRow. sql can be 282 | // either a prepared statement name or an SQL string. arguments will be sanitized 283 | // before being interpolated into sql strings. arguments should be referenced 284 | // positionally from the sql string as $1, $2, etc. 285 | // 286 | // SelectFunc calls onDataRow as the rows are received. This means that it does not 287 | // need to simultaneously store the entire result set in memory. It also means that 288 | // it is possible to process some rows and then for an error to occur. Callers 289 | // should be aware of this possibility. 290 | func (c *Conn) SelectFunc(sql string, onDataRow func(*DataRowReader) error, arguments ...interface{}) error { 291 | startTime := time.Now() 292 | err := c.selectFunc(sql, onDataRow, arguments...) 293 | if err != nil { 294 | c.logger.Error("SelectFunc", "sql", sql, "args", arguments, "error", err) 295 | return err 296 | } 297 | 298 | endTime := time.Now() 299 | c.logger.Info("SelectFunc", "sql", sql, "args", arguments, "time", endTime.Sub(startTime)) 300 | return nil 301 | } 302 | 303 | func (c *Conn) selectFunc(sql string, onDataRow func(*DataRowReader) error, arguments ...interface{}) (err error) { 304 | var fields []FieldDescription 305 | 306 | if ps, present := c.preparedStatements[sql]; present { 307 | fields = ps.FieldDescriptions 308 | err = c.sendPreparedQuery(ps, arguments...) 309 | } else { 310 | err = c.sendSimpleQuery(sql, arguments...) 311 | } 312 | if err != nil { 313 | return 314 | } 315 | 316 | var softErr error 317 | 318 | for { 319 | var t byte 320 | var r *MessageReader 321 | t, r, err = c.rxMsg() 322 | if err != nil { 323 | return err 324 | } 325 | 326 | switch t { 327 | case readyForQuery: 328 | c.rxReadyForQuery(r) 329 | return softErr 330 | case rowDescription: 331 | fields = c.rxRowDescription(r) 332 | case dataRow: 333 | if softErr == nil { 334 | c.drr.mr = r 335 | c.drr.FieldDescriptions = fields 336 | c.drr.currentFieldIdx = 0 337 | 338 | fieldCount := int(r.ReadInt16()) 339 | if fieldCount != len(fields) { 340 | softErr = ProtocolError(fmt.Sprintf("Row description field count (%v) and data row field count (%v) do not match", len(fields), fieldCount)) 341 | } 342 | if softErr == nil { 343 | softErr = onDataRow(&c.drr) 344 | } 345 | } 346 | case commandComplete: 347 | case bindComplete: 348 | default: 349 | if e := c.processContextFreeMsg(t, r); e != nil && softErr == nil { 350 | softErr = e 351 | } 352 | } 353 | } 354 | } 355 | 356 | // SelectRows executes sql and returns a slice of maps representing the found rows. 357 | // sql can be either a prepared statement name or an SQL string. arguments will be 358 | // sanitized before being interpolated into sql strings. arguments should be referenced 359 | // positionally from the sql string as $1, $2, etc. 360 | func (c *Conn) SelectRows(sql string, arguments ...interface{}) ([]map[string]interface{}, error) { 361 | startTime := time.Now() 362 | 363 | rows := make([]map[string]interface{}, 0, 8) 364 | onDataRow := func(r *DataRowReader) error { 365 | rows = append(rows, c.rxDataRow(r)) 366 | return nil 367 | } 368 | err := c.selectFunc(sql, onDataRow, arguments...) 369 | if err != nil { 370 | c.logger.Error("SelectRows", "sql", sql, "args", arguments, "error", err) 371 | return nil, err 372 | } 373 | 374 | endTime := time.Now() 375 | c.logger.Info("SelectRows", "sql", sql, "args", arguments, "rowsFound", len(rows), "time", endTime.Sub(startTime)) 376 | return rows, nil 377 | } 378 | 379 | // SelectRow executes sql and returns a map representing the found row. 380 | // sql can be either a prepared statement name or an SQL string. arguments will be 381 | // sanitized before being interpolated into sql strings. arguments should be referenced 382 | // positionally from the sql string as $1, $2, etc. 383 | // 384 | // Returns a NotSingleRowError if exactly one row is not found 385 | func (c *Conn) SelectRow(sql string, arguments ...interface{}) (map[string]interface{}, error) { 386 | startTime := time.Now() 387 | 388 | var numRowsFound int64 389 | var row map[string]interface{} 390 | 391 | onDataRow := func(r *DataRowReader) error { 392 | numRowsFound++ 393 | row = c.rxDataRow(r) 394 | return nil 395 | } 396 | err := c.selectFunc(sql, onDataRow, arguments...) 397 | if err != nil { 398 | c.logger.Error("SelectRow", "sql", sql, "args", arguments, "error", err) 399 | return nil, err 400 | } 401 | if numRowsFound != 1 { 402 | err = NotSingleRowError{RowCount: numRowsFound} 403 | row = nil 404 | } 405 | 406 | endTime := time.Now() 407 | c.logger.Info("SelectRow", "sql", sql, "args", arguments, "rowsFound", numRowsFound, "time", endTime.Sub(startTime)) 408 | 409 | return row, err 410 | } 411 | 412 | // SelectValue executes sql and returns a single value. sql can be either a prepared 413 | // statement name or an SQL string. arguments will be sanitized before being 414 | // interpolated into sql strings. arguments should be referenced positionally from 415 | // the sql string as $1, $2, etc. 416 | // 417 | // Returns a UnexpectedColumnCountError if exactly one column is not found 418 | // Returns a NotSingleRowError if exactly one row is not found 419 | func (c *Conn) SelectValue(sql string, arguments ...interface{}) (interface{}, error) { 420 | startTime := time.Now() 421 | 422 | var numRowsFound int64 423 | var v interface{} 424 | 425 | onDataRow := func(r *DataRowReader) error { 426 | if len(r.FieldDescriptions) != 1 { 427 | return UnexpectedColumnCountError{ExpectedCount: 1, ActualCount: int16(len(r.FieldDescriptions))} 428 | } 429 | 430 | numRowsFound++ 431 | v = r.ReadValue() 432 | return nil 433 | } 434 | err := c.selectFunc(sql, onDataRow, arguments...) 435 | if err != nil { 436 | c.logger.Error("SelectValue", "sql", sql, "args", arguments, "error", err) 437 | return nil, err 438 | } 439 | if numRowsFound != 1 { 440 | err = NotSingleRowError{RowCount: numRowsFound} 441 | v = nil 442 | } 443 | 444 | endTime := time.Now() 445 | c.logger.Info("SelectValue", "sql", sql, "args", arguments, "rowsFound", numRowsFound, "time", endTime.Sub(startTime)) 446 | 447 | return v, err 448 | } 449 | 450 | // SelectValueTo executes sql that returns a single value and writes that value to w. 451 | // No type conversions will be performed. The raw bytes will be written directly to w. 452 | // This is ideal for returning JSON, files, or large text values directly over HTTP. 453 | 454 | // sql can be either a prepared statement name or an SQL string. arguments will be 455 | // sanitized before being interpolated into sql strings. arguments should be 456 | // referenced positionally from the sql string as $1, $2, etc. 457 | // 458 | // Returns a UnexpectedColumnCountError if exactly one column is not found 459 | // Returns a NotSingleRowError if exactly one row is not found 460 | func (c *Conn) SelectValueTo(w io.Writer, sql string, arguments ...interface{}) (err error) { 461 | startTime := time.Now() 462 | 463 | defer func() { 464 | if err == nil { 465 | endTime := time.Now() 466 | c.logger.Info("SelectValueTo", "sql", sql, "args", arguments, "time", endTime.Sub(startTime)) 467 | } else { 468 | c.logger.Error(fmt.Sprintf("SelectValueTo `%s` with %v failed: %v", sql, arguments, err)) 469 | } 470 | }() 471 | 472 | err = c.sendQuery(sql, arguments...) 473 | if err != nil { 474 | return err 475 | } 476 | 477 | var numRowsFound int64 478 | var softErr error 479 | 480 | for { 481 | var t byte 482 | var bodySize int32 483 | 484 | t, bodySize, err = c.rxMsgHeader() 485 | if err != nil { 486 | return err 487 | } 488 | 489 | if t == dataRow { 490 | numRowsFound++ 491 | 492 | if numRowsFound > 1 { 493 | softErr = NotSingleRowError{RowCount: numRowsFound} 494 | } 495 | 496 | if softErr != nil { 497 | c.rxMsgBody(bodySize) // Read and discard rest of message 498 | continue 499 | } 500 | 501 | softErr = c.rxDataRowValueTo(w, bodySize) 502 | } else { 503 | var body *bytes.Buffer 504 | body, err = c.rxMsgBody(bodySize) 505 | if err != nil { 506 | return err 507 | } 508 | 509 | r := (*MessageReader)(body) 510 | switch t { 511 | case readyForQuery: 512 | c.rxReadyForQuery(r) 513 | return softErr 514 | case rowDescription: 515 | case commandComplete: 516 | case bindComplete: 517 | default: 518 | if e := c.processContextFreeMsg(t, r); e != nil && softErr == nil { 519 | softErr = e 520 | } 521 | } 522 | } 523 | } 524 | } 525 | 526 | func (c *Conn) rxDataRowValueTo(w io.Writer, bodySize int32) (err error) { 527 | b := make([]byte, 2) 528 | _, err = io.ReadFull(c.reader, b) 529 | if err != nil { 530 | c.die(err) 531 | return 532 | } 533 | columnCount := int16(binary.BigEndian.Uint16(b)) 534 | 535 | if columnCount != 1 { 536 | // Read the rest of the data row so it can be discarded 537 | if _, err = io.CopyN(ioutil.Discard, c.reader, int64(bodySize-2)); err != nil { 538 | c.die(err) 539 | return 540 | } 541 | err = UnexpectedColumnCountError{ExpectedCount: 1, ActualCount: columnCount} 542 | return 543 | } 544 | 545 | b = make([]byte, 4) 546 | _, err = io.ReadFull(c.reader, b) 547 | if err != nil { 548 | c.die(err) 549 | return 550 | } 551 | valueSize := int32(binary.BigEndian.Uint32(b)) 552 | 553 | if valueSize == -1 { 554 | err = errors.New("SelectValueTo cannot handle null") 555 | return 556 | } 557 | 558 | _, err = io.CopyN(w, c.reader, int64(valueSize)) 559 | if err != nil { 560 | c.die(err) 561 | } 562 | 563 | return 564 | } 565 | 566 | // SelectValues executes sql and returns a slice of values. sql can be either a prepared 567 | // statement name or an SQL string. arguments will be sanitized before being 568 | // interpolated into sql strings. arguments should be referenced positionally from 569 | // the sql string as $1, $2, etc. 570 | // 571 | // Returns a UnexpectedColumnCountError if exactly one column is not found 572 | func (c *Conn) SelectValues(sql string, arguments ...interface{}) ([]interface{}, error) { 573 | startTime := time.Now() 574 | 575 | values := make([]interface{}, 0, 8) 576 | onDataRow := func(r *DataRowReader) error { 577 | if len(r.FieldDescriptions) != 1 { 578 | return UnexpectedColumnCountError{ExpectedCount: 1, ActualCount: int16(len(r.FieldDescriptions))} 579 | } 580 | 581 | values = append(values, r.ReadValue()) 582 | return nil 583 | } 584 | err := c.selectFunc(sql, onDataRow, arguments...) 585 | if err != nil { 586 | c.logger.Error("SelectValues", "sql", sql, "args", arguments, "error", err) 587 | return nil, err 588 | } 589 | 590 | endTime := time.Now() 591 | c.logger.Info("SelectValues", "sql", sql, "args", arguments, "valuesFound", len(values), "time", endTime.Sub(startTime)) 592 | return values, nil 593 | } 594 | 595 | // Prepare creates a prepared statement with name and sql. sql can contain placeholders 596 | // for bound parameters. These placeholders are referenced positional as $1, $2, etc. 597 | func (c *Conn) Prepare(name, sql string) (ps *PreparedStatement, err error) { 598 | defer func() { 599 | if err != nil { 600 | c.logger.Error(fmt.Sprintf("Prepare `%s` as `%s` failed: %v", name, sql, err)) 601 | } 602 | }() 603 | 604 | // parse 605 | buf := c.getBuf() 606 | buf.WriteString(name) 607 | buf.WriteByte(0) 608 | buf.WriteString(sql) 609 | buf.WriteByte(0) 610 | binary.Write(buf, binary.BigEndian, int16(0)) 611 | err = c.txMsg('P', buf) 612 | if err != nil { 613 | return nil, err 614 | } 615 | 616 | // describe 617 | buf = c.getBuf() 618 | buf.WriteByte('S') 619 | buf.WriteString(name) 620 | buf.WriteByte(0) 621 | err = c.txMsg('D', buf) 622 | if err != nil { 623 | return nil, err 624 | } 625 | 626 | // sync 627 | err = c.txMsg('S', c.getBuf()) 628 | if err != nil { 629 | return nil, err 630 | } 631 | 632 | ps = &PreparedStatement{Name: name} 633 | 634 | var softErr error 635 | 636 | for { 637 | var t byte 638 | var r *MessageReader 639 | t, r, err := c.rxMsg() 640 | if err != nil { 641 | return nil, err 642 | } 643 | 644 | switch t { 645 | case parseComplete: 646 | case parameterDescription: 647 | ps.ParameterOids = c.rxParameterDescription(r) 648 | case rowDescription: 649 | ps.FieldDescriptions = c.rxRowDescription(r) 650 | for i := range ps.FieldDescriptions { 651 | oid := ps.FieldDescriptions[i].DataType 652 | if ValueTranscoders[oid] != nil && ValueTranscoders[oid].DecodeBinary != nil { 653 | ps.FieldDescriptions[i].FormatCode = 1 654 | } 655 | } 656 | case noData: 657 | case readyForQuery: 658 | c.rxReadyForQuery(r) 659 | c.preparedStatements[name] = ps 660 | return ps, softErr 661 | default: 662 | if e := c.processContextFreeMsg(t, r); e != nil && softErr == nil { 663 | softErr = e 664 | } 665 | } 666 | } 667 | } 668 | 669 | // Deallocate released a prepared statement 670 | func (c *Conn) Deallocate(name string) (err error) { 671 | delete(c.preparedStatements, name) 672 | _, err = c.Execute("deallocate " + c.QuoteIdentifier(name)) 673 | return 674 | } 675 | 676 | // Listen establishes a PostgreSQL listen/notify to channel 677 | func (c *Conn) Listen(channel string) (err error) { 678 | _, err = c.Execute("listen " + channel) 679 | return 680 | } 681 | 682 | // WaitForNotification waits for a PostgreSQL notification for up to timeout. 683 | // If the timeout occurs it returns pgx.NotificationTimeoutError 684 | func (c *Conn) WaitForNotification(timeout time.Duration) (*Notification, error) { 685 | if len(c.notifications) > 0 { 686 | notification := c.notifications[0] 687 | c.notifications = c.notifications[1:] 688 | return notification, nil 689 | } 690 | 691 | var zeroTime time.Time 692 | stopTime := time.Now().Add(timeout) 693 | 694 | for { 695 | // Use SetReadDeadline to implement the timeout. SetReadDeadline will 696 | // cause operations to fail with a *net.OpError that has a Timeout() 697 | // of true. Because the normal pgx rxMsg path considers any error to 698 | // have potentially corrupted the state of the connection, it dies 699 | // on any errors. So to avoid timeout errors in rxMsg we set the 700 | // deadline and peek into the reader. If a timeout error occurs there 701 | // we don't break the pgx connection. If the Peek returns that data 702 | // is available then we turn off the read deadline before the rxMsg. 703 | err := c.conn.SetReadDeadline(stopTime) 704 | if err != nil { 705 | return nil, err 706 | } 707 | 708 | // Wait until there is a byte available before continuing onto the normal msg reading path 709 | _, err = c.reader.Peek(1) 710 | if err != nil { 711 | c.conn.SetReadDeadline(zeroTime) // we can only return one error and we already have one -- so ignore possiple error from SetReadDeadline 712 | if err, ok := err.(*net.OpError); ok && err.Timeout() { 713 | return nil, NotificationTimeoutError 714 | } 715 | return nil, err 716 | } 717 | 718 | err = c.conn.SetReadDeadline(zeroTime) 719 | if err != nil { 720 | return nil, err 721 | } 722 | 723 | var t byte 724 | var r *MessageReader 725 | if t, r, err = c.rxMsg(); err == nil { 726 | if err = c.processContextFreeMsg(t, r); err != nil { 727 | return nil, err 728 | } 729 | } else { 730 | return nil, err 731 | } 732 | 733 | if len(c.notifications) > 0 { 734 | notification := c.notifications[0] 735 | c.notifications = c.notifications[1:] 736 | return notification, nil 737 | } 738 | } 739 | } 740 | 741 | func (c *Conn) IsAlive() bool { 742 | return c.alive 743 | } 744 | 745 | func (c *Conn) CauseOfDeath() error { 746 | return c.causeOfDeath 747 | } 748 | 749 | func (c *Conn) sendQuery(sql string, arguments ...interface{}) (err error) { 750 | if ps, present := c.preparedStatements[sql]; present { 751 | return c.sendPreparedQuery(ps, arguments...) 752 | } else { 753 | return c.sendSimpleQuery(sql, arguments...) 754 | } 755 | } 756 | 757 | func (c *Conn) sendSimpleQuery(sql string, arguments ...interface{}) (err error) { 758 | if len(arguments) > 0 { 759 | sql, err = c.SanitizeSql(sql, arguments...) 760 | if err != nil { 761 | return 762 | } 763 | } 764 | 765 | buf := c.getBuf() 766 | 767 | _, err = buf.WriteString(sql) 768 | if err != nil { 769 | return 770 | } 771 | err = buf.WriteByte(0) 772 | if err != nil { 773 | return 774 | } 775 | 776 | return c.txMsg('Q', buf) 777 | } 778 | 779 | func (c *Conn) BuildPreparedQueryBuf(ps *PreparedStatement, arguments ...interface{}) ([]byte, error) { 780 | if len(ps.ParameterOids) != len(arguments) { 781 | return nil, fmt.Errorf("Prepared statement \"%v\" requires %d parameters, but %d were provided", ps.Name, len(ps.ParameterOids), len(arguments)) 782 | } 783 | 784 | // bind 785 | wbuf := newWriteBuf(c.wbuf[0:0], 'B') 786 | wbuf.WriteByte(0) 787 | wbuf.WriteCString(ps.Name) 788 | 789 | wbuf.WriteInt16(int16(len(ps.ParameterOids))) 790 | for _, oid := range ps.ParameterOids { 791 | transcoder := ValueTranscoders[oid] 792 | if transcoder == nil { 793 | transcoder = defaultTranscoder 794 | } 795 | wbuf.WriteInt16(transcoder.EncodeFormat) 796 | } 797 | 798 | wbuf.WriteInt16(int16(len(arguments))) 799 | for i, oid := range ps.ParameterOids { 800 | if arguments[i] != nil { 801 | transcoder := ValueTranscoders[oid] 802 | if transcoder == nil { 803 | transcoder = defaultTranscoder 804 | } 805 | err := transcoder.EncodeTo(wbuf, arguments[i]) 806 | if err != nil { 807 | return nil, err 808 | } 809 | } else { 810 | wbuf.WriteInt32(int32(-1)) 811 | } 812 | } 813 | 814 | wbuf.WriteInt16(int16(len(ps.FieldDescriptions))) 815 | for _, fd := range ps.FieldDescriptions { 816 | transcoder := ValueTranscoders[fd.DataType] 817 | if transcoder != nil && transcoder.DecodeBinary != nil { 818 | wbuf.WriteInt16(1) 819 | } else { 820 | wbuf.WriteInt16(0) 821 | } 822 | } 823 | 824 | // execute 825 | wbuf.startMsg('E') 826 | wbuf.WriteByte(0) 827 | wbuf.WriteInt32(0) 828 | 829 | // sync 830 | wbuf.startMsg('S') 831 | wbuf.closeMsg() 832 | 833 | return wbuf.buf, nil 834 | } 835 | 836 | func (c *Conn) sendPreparedQuery(ps *PreparedStatement, arguments ...interface{}) error { 837 | buf, err := c.BuildPreparedQueryBuf(ps, arguments...) 838 | if err != nil { 839 | return err 840 | } 841 | _, err = c.conn.Write(buf) 842 | 843 | return err 844 | } 845 | 846 | // Execute executes sql. sql can be either a prepared statement name or an SQL string. 847 | // arguments will be sanitized before being interpolated into sql strings. arguments 848 | // should be referenced positionally from the sql string as $1, $2, etc. 849 | func (c *Conn) Execute(sql string, arguments ...interface{}) (commandTag CommandTag, err error) { 850 | startTime := time.Now() 851 | 852 | defer func() { 853 | if err == nil { 854 | endTime := time.Now() 855 | c.logger.Info("Execute", "sql", sql, "args", arguments, "time", endTime.Sub(startTime)) 856 | } else { 857 | c.logger.Error("Execute", "sql", sql, "args", arguments, "error", err) 858 | } 859 | }() 860 | 861 | if err = c.sendQuery(sql, arguments...); err != nil { 862 | return 863 | } 864 | 865 | var softErr error 866 | 867 | for { 868 | var t byte 869 | var r *MessageReader 870 | t, r, err = c.rxMsg() 871 | if err != nil { 872 | return commandTag, err 873 | } 874 | 875 | switch t { 876 | case readyForQuery: 877 | c.rxReadyForQuery(r) 878 | return commandTag, softErr 879 | case rowDescription: 880 | case dataRow: 881 | case bindComplete: 882 | case commandComplete: 883 | commandTag = CommandTag(r.ReadCString()) 884 | default: 885 | if e := c.processContextFreeMsg(t, r); e != nil && softErr == nil { 886 | softErr = e 887 | } 888 | } 889 | } 890 | } 891 | 892 | // Transaction runs f in a transaction. f should return true if the transaction 893 | // should be committed or false if it should be rolled back. Return value committed 894 | // is if the transaction was committed or not. committed should be checked separately 895 | // from err as an explicit rollback is not an error. Transaction will use the default 896 | // isolation level for the current connection. To use a specific isolation level see 897 | // TransactionIso 898 | func (c *Conn) Transaction(f func() bool) (committed bool, err error) { 899 | return c.transaction("", f) 900 | } 901 | 902 | // TransactionIso is the same as Transaction except it takes an isoLevel argument that 903 | // it uses as the transaction isolation level. 904 | // 905 | // Valid isolation levels (and their constants) are: 906 | // serializable (pgx.Serializable) 907 | // repeatable read (pgx.RepeatableRead) 908 | // read committed (pgx.ReadCommitted) 909 | // read uncommitted (pgx.ReadUncommitted) 910 | func (c *Conn) TransactionIso(isoLevel string, f func() bool) (committed bool, err error) { 911 | return c.transaction(isoLevel, f) 912 | } 913 | 914 | func (c *Conn) transaction(isoLevel string, f func() bool) (committed bool, err error) { 915 | var beginSql string 916 | if isoLevel == "" { 917 | beginSql = "begin" 918 | } else { 919 | beginSql = fmt.Sprintf("begin isolation level %s", isoLevel) 920 | } 921 | 922 | if _, err = c.Execute(beginSql); err != nil { 923 | return 924 | } 925 | defer func() { 926 | if committed && c.TxStatus == 'T' { 927 | _, err = c.Execute("commit") 928 | if err != nil { 929 | committed = false 930 | } 931 | } else { 932 | _, err = c.Execute("rollback") 933 | committed = false 934 | } 935 | }() 936 | 937 | committed = f() 938 | return 939 | } 940 | 941 | // Processes messages that are not exclusive to one context such as 942 | // authentication or query response. The response to these messages 943 | // is the same regardless of when they occur. 944 | func (c *Conn) processContextFreeMsg(t byte, r *MessageReader) (err error) { 945 | switch t { 946 | case 'S': 947 | c.rxParameterStatus(r) 948 | return nil 949 | case errorResponse: 950 | return c.rxErrorResponse(r) 951 | case noticeResponse: 952 | return nil 953 | case notificationResponse: 954 | return c.rxNotificationResponse(r) 955 | default: 956 | return fmt.Errorf("Received unknown message type: %c", t) 957 | } 958 | } 959 | 960 | func (c *Conn) rxMsg() (t byte, r *MessageReader, err error) { 961 | var bodySize int32 962 | t, bodySize, err = c.rxMsgHeader() 963 | if err != nil { 964 | return 965 | } 966 | 967 | var body *bytes.Buffer 968 | if body, err = c.rxMsgBody(bodySize); err != nil { 969 | return 970 | } 971 | 972 | r = (*MessageReader)(body) 973 | return 974 | } 975 | 976 | func (c *Conn) rxMsgHeader() (t byte, bodySize int32, err error) { 977 | if !c.alive { 978 | return 0, 0, DeadConnError 979 | } 980 | 981 | t, err = c.reader.ReadByte() 982 | if err != nil { 983 | c.die(err) 984 | return 0, 0, err 985 | } 986 | 987 | b := make([]byte, 4) 988 | _, err = io.ReadFull(c.reader, b) 989 | if err != nil { 990 | c.die(err) 991 | return 0, 0, err 992 | } 993 | 994 | bodySize = int32(binary.BigEndian.Uint32(b)) 995 | 996 | bodySize -= 4 // remove self from size 997 | return t, bodySize, err 998 | } 999 | 1000 | func (c *Conn) rxMsgBody(bodySize int32) (*bytes.Buffer, error) { 1001 | if !c.alive { 1002 | return nil, DeadConnError 1003 | } 1004 | 1005 | buf := c.getBuf() 1006 | _, err := io.CopyN(buf, c.reader, int64(bodySize)) 1007 | if err != nil { 1008 | c.die(err) 1009 | return nil, err 1010 | } 1011 | 1012 | return buf, nil 1013 | } 1014 | 1015 | func (c *Conn) rxAuthenticationX(r *MessageReader) (err error) { 1016 | code := r.ReadInt32() 1017 | switch code { 1018 | case 0: // AuthenticationOk 1019 | case 3: // AuthenticationCleartextPassword 1020 | err = c.txPasswordMessage(c.config.Password) 1021 | case 5: // AuthenticationMD5Password 1022 | salt := r.ReadString(4) 1023 | digestedPassword := "md5" + hexMD5(hexMD5(c.config.Password+c.config.User)+salt) 1024 | err = c.txPasswordMessage(digestedPassword) 1025 | default: 1026 | err = errors.New("Received unknown authentication message") 1027 | } 1028 | 1029 | return 1030 | } 1031 | 1032 | func hexMD5(s string) string { 1033 | hash := md5.New() 1034 | io.WriteString(hash, s) 1035 | return hex.EncodeToString(hash.Sum(nil)) 1036 | } 1037 | 1038 | func (c *Conn) rxParameterStatus(r *MessageReader) { 1039 | key := r.ReadCString() 1040 | value := r.ReadCString() 1041 | c.RuntimeParams[key] = value 1042 | } 1043 | 1044 | func (c *Conn) rxErrorResponse(r *MessageReader) (err PgError) { 1045 | for { 1046 | switch r.ReadByte() { 1047 | case 'S': 1048 | err.Severity = r.ReadCString() 1049 | case 'C': 1050 | err.Code = r.ReadCString() 1051 | case 'M': 1052 | err.Message = r.ReadCString() 1053 | case 0: // End of error message 1054 | return 1055 | default: // Ignore other error fields 1056 | r.ReadCString() 1057 | } 1058 | } 1059 | } 1060 | 1061 | func (c *Conn) rxBackendKeyData(r *MessageReader) { 1062 | c.Pid = r.ReadInt32() 1063 | c.SecretKey = r.ReadInt32() 1064 | } 1065 | 1066 | func (c *Conn) rxReadyForQuery(r *MessageReader) { 1067 | c.TxStatus = r.ReadByte() 1068 | } 1069 | 1070 | func (c *Conn) rxRowDescription(r *MessageReader) (fields []FieldDescription) { 1071 | fieldCount := r.ReadInt16() 1072 | fields = make([]FieldDescription, fieldCount) 1073 | for i := int16(0); i < fieldCount; i++ { 1074 | f := &fields[i] 1075 | f.Name = r.ReadCString() 1076 | f.Table = r.ReadOid() 1077 | f.AttributeNumber = r.ReadInt16() 1078 | f.DataType = r.ReadOid() 1079 | f.DataTypeSize = r.ReadInt16() 1080 | f.Modifier = r.ReadInt32() 1081 | f.FormatCode = r.ReadInt16() 1082 | } 1083 | return 1084 | } 1085 | 1086 | func (c *Conn) rxParameterDescription(r *MessageReader) (parameters []Oid) { 1087 | parameterCount := r.ReadInt16() 1088 | parameters = make([]Oid, 0, parameterCount) 1089 | for i := int16(0); i < parameterCount; i++ { 1090 | parameters = append(parameters, r.ReadOid()) 1091 | } 1092 | return 1093 | } 1094 | 1095 | func (c *Conn) rxDataRow(r *DataRowReader) (row map[string]interface{}) { 1096 | fieldCount := len(r.FieldDescriptions) 1097 | 1098 | row = make(map[string]interface{}, fieldCount) 1099 | for i := 0; i < fieldCount; i++ { 1100 | row[r.FieldDescriptions[i].Name] = r.ReadValue() 1101 | } 1102 | return 1103 | } 1104 | 1105 | func (c *Conn) rxCommandComplete(r *MessageReader) string { 1106 | return r.ReadCString() 1107 | } 1108 | 1109 | func (c *Conn) rxNotificationResponse(r *MessageReader) (err error) { 1110 | n := new(Notification) 1111 | n.Pid = r.ReadInt32() 1112 | n.Channel = r.ReadCString() 1113 | n.Payload = r.ReadCString() 1114 | c.notifications = append(c.notifications, n) 1115 | return 1116 | } 1117 | 1118 | func (c *Conn) startTLS() (err error) { 1119 | err = binary.Write(c.conn, binary.BigEndian, []int32{8, 80877103}) 1120 | if err != nil { 1121 | return 1122 | } 1123 | 1124 | response := make([]byte, 1) 1125 | if _, err = io.ReadFull(c.conn, response); err != nil { 1126 | return 1127 | } 1128 | 1129 | if response[0] != 'S' { 1130 | err = errors.New("Could not use TLS") 1131 | return 1132 | } 1133 | 1134 | c.conn = tls.Client(c.conn, c.config.TLSConfig) 1135 | 1136 | return nil 1137 | } 1138 | 1139 | func (c *Conn) txStartupMessage(msg *startupMessage) error { 1140 | _, err := c.conn.Write(msg.Bytes()) 1141 | return err 1142 | } 1143 | 1144 | func (c *Conn) txMsg(identifier byte, buf *bytes.Buffer) (err error) { 1145 | if !c.alive { 1146 | return DeadConnError 1147 | } 1148 | 1149 | defer func() { 1150 | if err != nil { 1151 | c.die(err) 1152 | } 1153 | }() 1154 | 1155 | err = binary.Write(c.conn, binary.BigEndian, identifier) 1156 | if err != nil { 1157 | return 1158 | } 1159 | 1160 | err = binary.Write(c.conn, binary.BigEndian, int32(buf.Len()+4)) 1161 | if err != nil { 1162 | return 1163 | } 1164 | 1165 | _, err = buf.WriteTo(c.conn) 1166 | if err != nil { 1167 | return 1168 | } 1169 | 1170 | return 1171 | } 1172 | 1173 | func (c *Conn) txPasswordMessage(password string) (err error) { 1174 | buf := c.getBuf() 1175 | 1176 | _, err = buf.WriteString(password) 1177 | if err != nil { 1178 | return 1179 | } 1180 | buf.WriteByte(0) 1181 | if err != nil { 1182 | return 1183 | } 1184 | err = c.txMsg('p', buf) 1185 | return 1186 | } 1187 | 1188 | // Gets the shared connection buffer. Since bytes.Buffer never releases memory from 1189 | // its internal byte array, check on the size and create a new bytes.Buffer so the 1190 | // old one can get GC'ed 1191 | func (c *Conn) getBuf() *bytes.Buffer { 1192 | c.buf.Reset() 1193 | if cap(c.buf.Bytes()) > c.bufSize { 1194 | c.logger.Debug(fmt.Sprintf("c.buf (%d) is larger than c.bufSize (%d) -- resetting", cap(c.buf.Bytes()), c.bufSize)) 1195 | c.buf = bytes.NewBuffer(make([]byte, 0, c.bufSize)) 1196 | } 1197 | return c.buf 1198 | } 1199 | 1200 | func (c *Conn) die(err error) { 1201 | c.alive = false 1202 | c.causeOfDeath = err 1203 | c.conn.Close() 1204 | } 1205 | -------------------------------------------------------------------------------- /bench_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "database/sql" 6 | "encoding/binary" 7 | "fmt" 8 | "sync" 9 | "testing" 10 | "time" 11 | 12 | gopg "github.com/go-pg/pg/v10" 13 | "github.com/go-pg/pg/v10/orm" 14 | "github.com/go-pg/pg/v10/types" 15 | "github.com/jackc/go_db_bench/raw" 16 | pgconnv4 "github.com/jackc/pgconn" 17 | stmtcachev4 "github.com/jackc/pgconn/stmtcache" 18 | pgtypev4 "github.com/jackc/pgtype" 19 | pgxv4 "github.com/jackc/pgx/v4" 20 | pgxpoolv4 "github.com/jackc/pgx/v4/pgxpool" 21 | pgxv5 "github.com/jackc/pgx/v5" 22 | pgconnv5 "github.com/jackc/pgx/v5/pgconn" 23 | pgtypev5 "github.com/jackc/pgx/v5/pgtype" 24 | pgxpoolv5 "github.com/jackc/pgx/v5/pgxpool" 25 | ) 26 | 27 | var ( 28 | setupOnce sync.Once 29 | pgxPoolV4 *pgxpoolv4.Pool 30 | pgxStdlibV4 *sql.DB 31 | pgxPoolV5 *pgxpoolv5.Pool 32 | pq *sql.DB 33 | pg *gopg.DB 34 | pgConnV4 *pgconnv4.PgConn 35 | pgConnV5 *pgconnv5.PgConn 36 | rawConn *raw.Conn 37 | randPersonIDs []int32 38 | ) 39 | 40 | var selectPersonNameSQL = `select first_name from person where id=$1` 41 | var selectPersonNameSQLQuestionMark = `select first_name from person where id=?` 42 | 43 | var selectPersonSQL = ` 44 | select id, first_name, last_name, sex, birth_date, weight, height, update_time 45 | from person 46 | where id=$1` 47 | var selectPersonSQLQuestionMark = ` 48 | select id, first_name, last_name, sex, birth_date, weight, height, update_time 49 | from person 50 | where id=?` 51 | 52 | var selectMultiplePeopleSQL = ` 53 | select id, first_name, last_name, sex, birth_date, weight, height, update_time 54 | from person 55 | where id between $1 and $1 + 24` 56 | var selectMultiplePeopleSQLQuestionMark = ` 57 | select id, first_name, last_name, sex, birth_date, weight, height, update_time 58 | from person 59 | where id between ? and ? + 24` 60 | 61 | var selectLargeTextSQL = `select repeat('*', $1)` 62 | 63 | var rawSelectPersonNameStmt *raw.PreparedStatement 64 | var rawSelectPersonStmt *raw.PreparedStatement 65 | var rawSelectMultiplePeopleStmt *raw.PreparedStatement 66 | 67 | var rxBuf []byte 68 | 69 | type person struct { 70 | Id int32 71 | FirstName string 72 | LastName string 73 | Sex string 74 | BirthDate time.Time 75 | Weight int32 76 | Height int32 77 | UpdateTime time.Time 78 | } 79 | 80 | type personBytes struct { 81 | Id int32 82 | FirstName []byte 83 | LastName []byte 84 | Sex []byte 85 | BirthDate time.Time 86 | Weight int32 87 | Height int32 88 | UpdateTime time.Time 89 | } 90 | 91 | // Implements pg.ColumnScanner. 92 | var _ orm.ColumnScanner = (*person)(nil) 93 | 94 | func (p *person) ScanColumn(colInfo types.ColumnInfo, rd types.Reader, n int) error { 95 | var err error 96 | var n64 int64 97 | 98 | switch colInfo.Name { 99 | case "id": 100 | n64, err = types.ScanInt64(rd, n) 101 | p.Id = int32(n64) 102 | case "first_name": 103 | p.FirstName, err = types.ScanString(rd, n) 104 | case "last_name": 105 | p.LastName, err = types.ScanString(rd, n) 106 | case "sex": 107 | p.Sex, err = types.ScanString(rd, n) 108 | case "birth_date": 109 | p.BirthDate, err = types.ScanTime(rd, n) 110 | case "weight": 111 | n64, err = types.ScanInt64(rd, n) 112 | p.Weight = int32(n64) 113 | case "height": 114 | n64, err = types.ScanInt64(rd, n) 115 | p.Weight = int32(n64) 116 | case "update_time": 117 | p.UpdateTime, err = types.ScanTime(rd, n) 118 | default: 119 | panic(fmt.Sprintf("unsupported column: %s", colInfo.Name)) 120 | } 121 | 122 | return err 123 | } 124 | 125 | func setup(b *testing.B) { 126 | setupOnce.Do(func() { 127 | configV4, err := pgxpoolv4.ParseConfig("") 128 | if err != nil { 129 | b.Fatalf("extractConfig failed: %v", err) 130 | } 131 | 132 | configV4.AfterConnect = func(ctx context.Context, conn *pgxv4.Conn) error { 133 | _, err := conn.Prepare(ctx, "selectPersonName", selectPersonNameSQL) 134 | if err != nil { 135 | return err 136 | } 137 | 138 | _, err = conn.Prepare(ctx, "selectPerson", selectPersonSQL) 139 | if err != nil { 140 | return err 141 | } 142 | 143 | _, err = conn.Prepare(ctx, "selectMultiplePeople", selectMultiplePeopleSQL) 144 | if err != nil { 145 | return err 146 | } 147 | 148 | _, err = conn.Prepare(ctx, "selectLargeText", selectLargeTextSQL) 149 | if err != nil { 150 | return err 151 | } 152 | 153 | return nil 154 | } 155 | 156 | err = loadTestData(configV4.ConnConfig) 157 | if err != nil { 158 | b.Fatalf("loadTestData failed: %v", err) 159 | } 160 | 161 | pgxPoolV4, err = openPgxNativeV4(configV4) 162 | if err != nil { 163 | b.Fatalf("openPgxNative failed: %v", err) 164 | } 165 | 166 | configV5, err := pgxpoolv5.ParseConfig("") 167 | if err != nil { 168 | b.Fatalf("extractConfig failed: %v", err) 169 | } 170 | 171 | configV5.AfterConnect = func(ctx context.Context, conn *pgxv5.Conn) error { 172 | _, err := conn.Prepare(ctx, "selectPersonName", selectPersonNameSQL) 173 | if err != nil { 174 | return err 175 | } 176 | 177 | _, err = conn.Prepare(ctx, "selectPerson", selectPersonSQL) 178 | if err != nil { 179 | return err 180 | } 181 | 182 | _, err = conn.Prepare(ctx, "selectMultiplePeople", selectMultiplePeopleSQL) 183 | if err != nil { 184 | return err 185 | } 186 | 187 | _, err = conn.Prepare(ctx, "selectLargeText", selectLargeTextSQL) 188 | if err != nil { 189 | return err 190 | } 191 | 192 | return nil 193 | } 194 | 195 | pgxPoolV5, err = openPgxNativeV5(configV5) 196 | if err != nil { 197 | b.Fatalf("openPgxNative failed: %v", err) 198 | } 199 | 200 | pgxStdlibV4, err = openPgxStdlibV4(configV4) 201 | if err != nil { 202 | b.Fatalf("openPgxNative failed: %v", err) 203 | } 204 | 205 | pq, err = openPq(configV4.ConnConfig) 206 | if err != nil { 207 | b.Fatalf("openPq failed: %v", err) 208 | } 209 | 210 | pg, err = openPg(*configV4.ConnConfig) 211 | if err != nil { 212 | b.Fatalf("openPg failed: %v", err) 213 | } 214 | 215 | pgConnV4, err = pgconnv4.Connect(context.Background(), "") 216 | if err != nil { 217 | b.Fatalf("pgconn.Connect() failed: %v", err) 218 | } 219 | _, err = pgConnV4.Prepare(context.Background(), "selectPerson", selectPersonSQL, nil) 220 | if err != nil { 221 | b.Fatalf("pgConn.Prepare() failed: %v", err) 222 | } 223 | _, err = pgConnV4.Prepare(context.Background(), "selectMultiplePeople", selectMultiplePeopleSQL, nil) 224 | if err != nil { 225 | b.Fatalf("pgConn.Prepare() failed: %v", err) 226 | } 227 | 228 | pgConnV5, err = pgconnv5.Connect(context.Background(), "") 229 | if err != nil { 230 | b.Fatalf("pgconn.Connect() failed: %v", err) 231 | } 232 | _, err = pgConnV5.Prepare(context.Background(), "selectPerson", selectPersonSQL, nil) 233 | if err != nil { 234 | b.Fatalf("pgConn.Prepare() failed: %v", err) 235 | } 236 | _, err = pgConnV5.Prepare(context.Background(), "selectMultiplePeople", selectMultiplePeopleSQL, nil) 237 | if err != nil { 238 | b.Fatalf("pgConn.Prepare() failed: %v", err) 239 | } 240 | 241 | rawConfig := raw.ConnConfig{ 242 | Host: configV4.ConnConfig.Host, 243 | Port: configV4.ConnConfig.Port, 244 | User: configV4.ConnConfig.User, 245 | Password: configV4.ConnConfig.Password, 246 | Database: configV4.ConnConfig.Database, 247 | } 248 | rawConn, err = raw.Connect(rawConfig) 249 | if err != nil { 250 | b.Fatalf("raw.Connect failed: %v", err) 251 | } 252 | rawSelectPersonNameStmt, err = rawConn.Prepare("selectPersonName", selectPersonNameSQL) 253 | if err != nil { 254 | b.Fatalf("rawConn.Prepare failed: %v", err) 255 | } 256 | rawSelectPersonStmt, err = rawConn.Prepare("selectPerson", selectPersonSQL) 257 | if err != nil { 258 | b.Fatalf("rawConn.Prepare failed: %v", err) 259 | } 260 | rawSelectMultiplePeopleStmt, err = rawConn.Prepare("selectMultiplePeople", selectMultiplePeopleSQL) 261 | if err != nil { 262 | b.Fatalf("rawConn.Prepare failed: %v", err) 263 | } 264 | 265 | rxBuf = make([]byte, 16384) 266 | 267 | // Get random person ids in random order outside of timing 268 | rows, _ := pgxPoolV4.Query(context.Background(), "select id from person order by random()") 269 | for rows.Next() { 270 | var id int32 271 | rows.Scan(&id) 272 | randPersonIDs = append(randPersonIDs, id) 273 | } 274 | 275 | if rows.Err() != nil { 276 | b.Fatalf("pgxPool.Query failed: %v", err) 277 | } 278 | }) 279 | } 280 | 281 | func BenchmarkPgxV4NativeSelectSingleShortString(b *testing.B) { 282 | setup(b) 283 | 284 | b.ResetTimer() 285 | for i := 0; i < b.N; i++ { 286 | id := randPersonIDs[i%len(randPersonIDs)] 287 | var firstName string 288 | err := pgxPoolV4.QueryRow(context.Background(), "selectPersonName", id).Scan(&firstName) 289 | if err != nil { 290 | b.Fatalf("pgxPool.QueryRow Scan failed: %v", err) 291 | } 292 | if len(firstName) == 0 { 293 | b.Fatal("FirstName was empty") 294 | } 295 | } 296 | } 297 | 298 | func BenchmarkPgxV5NativeSelectSingleShortString(b *testing.B) { 299 | setup(b) 300 | 301 | b.ResetTimer() 302 | for i := 0; i < b.N; i++ { 303 | id := randPersonIDs[i%len(randPersonIDs)] 304 | var firstName string 305 | err := pgxPoolV5.QueryRow(context.Background(), "selectPersonName", id).Scan(&firstName) 306 | if err != nil { 307 | b.Fatalf("pgxPoolV5.QueryRow Scan failed: %v", err) 308 | } 309 | if len(firstName) == 0 { 310 | b.Fatal("FirstName was empty") 311 | } 312 | } 313 | } 314 | 315 | func BenchmarkPgxV4StdlibSelectSingleShortString(b *testing.B) { 316 | setup(b) 317 | stmt, err := pgxStdlibV4.Prepare(selectPersonNameSQL) 318 | if err != nil { 319 | b.Fatalf("Prepare failed: %v", err) 320 | } 321 | defer stmt.Close() 322 | 323 | benchmarkSelectSingleShortString(b, stmt) 324 | } 325 | 326 | func BenchmarkPgSelectSingleShortString(b *testing.B) { 327 | setup(b) 328 | 329 | stmt, err := pg.Prepare(selectPersonNameSQL) 330 | if err != nil { 331 | b.Fatalf("Prepare failed: %v", err) 332 | } 333 | defer stmt.Close() 334 | 335 | b.ResetTimer() 336 | for i := 0; i < b.N; i++ { 337 | var firstName string 338 | id := randPersonIDs[i%len(randPersonIDs)] 339 | _, err := stmt.QueryOne(gopg.Scan(&firstName), id) 340 | if err != nil { 341 | b.Fatalf("stmt.QueryOne failed: %v", err) 342 | } 343 | if len(firstName) == 0 { 344 | b.Fatal("FirstName was empty") 345 | } 346 | } 347 | } 348 | 349 | func BenchmarkPqSelectSingleShortString(b *testing.B) { 350 | setup(b) 351 | stmt, err := pq.Prepare(selectPersonNameSQL) 352 | if err != nil { 353 | b.Fatalf("Prepare failed: %v", err) 354 | } 355 | defer stmt.Close() 356 | 357 | benchmarkSelectSingleShortString(b, stmt) 358 | } 359 | 360 | func benchmarkSelectSingleShortString(b *testing.B, stmt *sql.Stmt) { 361 | b.ResetTimer() 362 | for i := 0; i < b.N; i++ { 363 | id := randPersonIDs[i%len(randPersonIDs)] 364 | row := stmt.QueryRow(id) 365 | var firstName string 366 | err := row.Scan(&firstName) 367 | if err != nil { 368 | b.Fatalf("row.Scan failed: %v", err) 369 | } 370 | if len(firstName) == 0 { 371 | b.Fatal("FirstName was empty") 372 | } 373 | } 374 | } 375 | 376 | func BenchmarkRawSelectSingleShortValue(b *testing.B) { 377 | setup(b) 378 | 379 | b.ResetTimer() 380 | 381 | txBufs := make([][]byte, len(randPersonIDs)) 382 | for i, personID := range randPersonIDs { 383 | var err error 384 | txBufs[i], err = rawConn.BuildPreparedQueryBuf(rawSelectPersonNameStmt, personID) 385 | if err != nil { 386 | b.Fatalf("rawConn.BuildQueryBuf failed: %v", err) 387 | } 388 | } 389 | 390 | for i := 0; i < b.N; i++ { 391 | txBuf := txBufs[i%len(txBufs)] 392 | _, err := rawConn.Conn().Write(txBuf) 393 | if err != nil { 394 | b.Fatalf("rawConn.Conn.Write failed: %v", err) 395 | } 396 | 397 | rxRawUntilReady(b) 398 | } 399 | } 400 | 401 | func BenchmarkPgxV4NativeSelectSingleShortBytes(b *testing.B) { 402 | setup(b) 403 | 404 | b.ResetTimer() 405 | for i := 0; i < b.N; i++ { 406 | id := randPersonIDs[i%len(randPersonIDs)] 407 | var firstName []byte 408 | err := pgxPoolV4.QueryRow(context.Background(), "selectPersonName", id).Scan(&firstName) 409 | if err != nil { 410 | b.Fatalf("pgxPool.QueryRow Scan failed: %v", err) 411 | } 412 | if len(firstName) == 0 { 413 | b.Fatal("FirstName was empty") 414 | } 415 | } 416 | } 417 | 418 | func BenchmarkPgxV5NativeSelectSingleShortBytes(b *testing.B) { 419 | setup(b) 420 | 421 | b.ResetTimer() 422 | for i := 0; i < b.N; i++ { 423 | id := randPersonIDs[i%len(randPersonIDs)] 424 | var firstName []byte 425 | err := pgxPoolV5.QueryRow(context.Background(), "selectPersonName", id).Scan(&firstName) 426 | if err != nil { 427 | b.Fatalf("pgxPoolV5.QueryRow Scan failed: %v", err) 428 | } 429 | if len(firstName) == 0 { 430 | b.Fatal("FirstName was empty") 431 | } 432 | } 433 | } 434 | 435 | func BenchmarkPgxV4StdlibSelectSingleShortBytes(b *testing.B) { 436 | setup(b) 437 | stmt, err := pgxStdlibV4.Prepare(selectPersonNameSQL) 438 | if err != nil { 439 | b.Fatalf("Prepare failed: %v", err) 440 | } 441 | defer stmt.Close() 442 | 443 | benchmarkSelectSingleShortBytes(b, stmt) 444 | } 445 | 446 | func BenchmarkPqSelectSingleShortBytes(b *testing.B) { 447 | setup(b) 448 | stmt, err := pq.Prepare(selectPersonNameSQL) 449 | if err != nil { 450 | b.Fatalf("Prepare failed: %v", err) 451 | } 452 | defer stmt.Close() 453 | 454 | benchmarkSelectSingleShortBytes(b, stmt) 455 | } 456 | 457 | func benchmarkSelectSingleShortBytes(b *testing.B, stmt *sql.Stmt) { 458 | b.ResetTimer() 459 | for i := 0; i < b.N; i++ { 460 | id := randPersonIDs[i%len(randPersonIDs)] 461 | row := stmt.QueryRow(id) 462 | var firstName []byte 463 | err := row.Scan(&firstName) 464 | if err != nil { 465 | b.Fatalf("row.Scan failed: %v", err) 466 | } 467 | if len(firstName) == 0 { 468 | b.Fatal("FirstName was empty") 469 | } 470 | } 471 | } 472 | 473 | func checkPersonWasFilled(b *testing.B, p person) { 474 | if p.Id == 0 { 475 | b.Fatal("id was 0") 476 | } 477 | if len(p.FirstName) == 0 { 478 | b.Fatal("FirstName was empty") 479 | } 480 | if len(p.LastName) == 0 { 481 | b.Fatal("LastName was empty") 482 | } 483 | if len(p.Sex) == 0 { 484 | b.Fatal("Sex was empty") 485 | } 486 | var zeroTime time.Time 487 | if p.BirthDate == zeroTime { 488 | b.Fatal("BirthDate was zero time") 489 | } 490 | if p.Weight == 0 { 491 | b.Fatal("Weight was 0") 492 | } 493 | if p.Height == 0 { 494 | b.Fatal("Height was 0") 495 | } 496 | if p.UpdateTime == zeroTime { 497 | b.Fatal("UpdateTime was zero time") 498 | } 499 | } 500 | 501 | func BenchmarkPgxV4NativeSelectSingleRow(b *testing.B) { 502 | setup(b) 503 | 504 | b.ResetTimer() 505 | for i := 0; i < b.N; i++ { 506 | var p person 507 | id := randPersonIDs[i%len(randPersonIDs)] 508 | 509 | rows, _ := pgxPoolV4.Query(context.Background(), "selectPerson", id) 510 | for rows.Next() { 511 | rows.Scan(&p.Id, &p.FirstName, &p.LastName, &p.Sex, &p.BirthDate, &p.Weight, &p.Height, &p.UpdateTime) 512 | } 513 | if rows.Err() != nil { 514 | b.Fatalf("pgxPool.Query failed: %v", rows.Err()) 515 | } 516 | 517 | checkPersonWasFilled(b, p) 518 | } 519 | } 520 | 521 | func BenchmarkPgxV4NativeSelectSingleRowNotPreparedWithStatementCacheModePrepare(b *testing.B) { 522 | setup(b) 523 | 524 | config, err := pgxpoolv4.ParseConfig("") 525 | if err != nil { 526 | b.Fatalf("ParseConfig failed: %v", err) 527 | } 528 | config.ConnConfig.BuildStatementCache = func(conn *pgconnv4.PgConn) stmtcachev4.Cache { 529 | return stmtcachev4.New(conn, stmtcachev4.ModePrepare, 512) 530 | } 531 | 532 | db, err := openPgxNativeV4(config) 533 | if err != nil { 534 | b.Fatalf("openPgxNative failed: %v", err) 535 | } 536 | 537 | b.ResetTimer() 538 | for i := 0; i < b.N; i++ { 539 | var p person 540 | id := randPersonIDs[i%len(randPersonIDs)] 541 | 542 | rows, _ := db.Query(context.Background(), selectPersonSQL, id) 543 | for rows.Next() { 544 | rows.Scan(&p.Id, &p.FirstName, &p.LastName, &p.Sex, &p.BirthDate, &p.Weight, &p.Height, &p.UpdateTime) 545 | } 546 | if rows.Err() != nil { 547 | b.Fatalf("pgxPool.Query failed: %v", rows.Err()) 548 | } 549 | 550 | checkPersonWasFilled(b, p) 551 | } 552 | } 553 | 554 | func BenchmarkPgxV4NativeSelectSingleRowNotPreparedWithStatementCacheModeDescribe(b *testing.B) { 555 | setup(b) 556 | 557 | config, err := pgxpoolv4.ParseConfig("") 558 | if err != nil { 559 | b.Fatalf("ParseConfig failed: %v", err) 560 | } 561 | config.ConnConfig.BuildStatementCache = func(conn *pgconnv4.PgConn) stmtcachev4.Cache { 562 | return stmtcachev4.New(conn, stmtcachev4.ModeDescribe, 512) 563 | } 564 | 565 | db, err := openPgxNativeV4(config) 566 | if err != nil { 567 | b.Fatalf("openPgxNative failed: %v", err) 568 | } 569 | 570 | b.ResetTimer() 571 | for i := 0; i < b.N; i++ { 572 | var p person 573 | id := randPersonIDs[i%len(randPersonIDs)] 574 | 575 | rows, _ := db.Query(context.Background(), selectPersonSQL, id) 576 | for rows.Next() { 577 | rows.Scan(&p.Id, &p.FirstName, &p.LastName, &p.Sex, &p.BirthDate, &p.Weight, &p.Height, &p.UpdateTime) 578 | } 579 | if rows.Err() != nil { 580 | b.Fatalf("pgxPool.Query failed: %v", rows.Err()) 581 | } 582 | 583 | checkPersonWasFilled(b, p) 584 | } 585 | } 586 | 587 | func BenchmarkPgxV4NativeSelectSingleRowNotPreparedWithStatementCacheDisabled(b *testing.B) { 588 | setup(b) 589 | 590 | config, err := pgxpoolv4.ParseConfig("") 591 | if err != nil { 592 | b.Fatalf("ParseConfig failed: %v", err) 593 | } 594 | config.ConnConfig.BuildStatementCache = nil 595 | 596 | db, err := openPgxNativeV4(config) 597 | if err != nil { 598 | b.Fatalf("openPgxNative failed: %v", err) 599 | } 600 | 601 | b.ResetTimer() 602 | for i := 0; i < b.N; i++ { 603 | var p person 604 | id := randPersonIDs[i%len(randPersonIDs)] 605 | 606 | rows, _ := db.Query(context.Background(), selectPersonSQL, id) 607 | for rows.Next() { 608 | rows.Scan(&p.Id, &p.FirstName, &p.LastName, &p.Sex, &p.BirthDate, &p.Weight, &p.Height, &p.UpdateTime) 609 | } 610 | if rows.Err() != nil { 611 | b.Fatalf("pgxPool.Query failed: %v", rows.Err()) 612 | } 613 | 614 | checkPersonWasFilled(b, p) 615 | } 616 | } 617 | 618 | func BenchmarkPgxV5NativeSelectSingleRow(b *testing.B) { 619 | setup(b) 620 | 621 | b.ResetTimer() 622 | for i := 0; i < b.N; i++ { 623 | var p person 624 | id := randPersonIDs[i%len(randPersonIDs)] 625 | 626 | rows, _ := pgxPoolV5.Query(context.Background(), "selectPerson", id) 627 | for rows.Next() { 628 | rows.Scan(&p.Id, &p.FirstName, &p.LastName, &p.Sex, &p.BirthDate, &p.Weight, &p.Height, &p.UpdateTime) 629 | } 630 | if rows.Err() != nil { 631 | b.Fatalf("pgxPoolV5.Query failed: %v", rows.Err()) 632 | } 633 | 634 | checkPersonWasFilled(b, p) 635 | } 636 | } 637 | 638 | func BenchmarkPgConnV4SelectSingleRowTextProtocolNoParsing(b *testing.B) { 639 | setup(b) 640 | 641 | b.ResetTimer() 642 | 643 | for i := 0; i < b.N; i++ { 644 | id := randPersonIDs[i%len(randPersonIDs)] 645 | 646 | buf := []byte{0, 0, 0, 0} 647 | binary.BigEndian.PutUint32(buf, uint32(id)) 648 | 649 | rr := pgConnV4.ExecPrepared(context.Background(), "selectPerson", [][]byte{buf}, []int16{1}, nil) 650 | _, err := rr.Close() 651 | if err != nil { 652 | b.Fatalf("pgConn.ExecPrepared failed: %v", err) 653 | } 654 | } 655 | } 656 | 657 | func BenchmarkPgConnV5SelectSingleRowTextProtocolNoParsing(b *testing.B) { 658 | setup(b) 659 | 660 | b.ResetTimer() 661 | 662 | for i := 0; i < b.N; i++ { 663 | id := randPersonIDs[i%len(randPersonIDs)] 664 | 665 | buf := []byte{0, 0, 0, 0} 666 | binary.BigEndian.PutUint32(buf, uint32(id)) 667 | 668 | rr := pgConnV5.ExecPrepared(context.Background(), "selectPerson", [][]byte{buf}, []int16{1}, nil) 669 | _, err := rr.Close() 670 | if err != nil { 671 | b.Fatalf("pgConnV5.ExecPrepared failed: %v", err) 672 | } 673 | } 674 | } 675 | 676 | func BenchmarkPgConnV4SelectSingleRowBinaryProtocolNoParsing(b *testing.B) { 677 | setup(b) 678 | 679 | b.ResetTimer() 680 | 681 | for i := 0; i < b.N; i++ { 682 | id := randPersonIDs[i%len(randPersonIDs)] 683 | 684 | buf := []byte{0, 0, 0, 0} 685 | binary.BigEndian.PutUint32(buf, uint32(id)) 686 | 687 | rr := pgConnV4.ExecPrepared(context.Background(), "selectPerson", [][]byte{buf}, []int16{1}, []int16{1}) 688 | _, err := rr.Close() 689 | if err != nil { 690 | b.Fatalf("pgConn.ExecPrepared failed: %v", err) 691 | } 692 | } 693 | } 694 | 695 | func BenchmarkPgConnV5SelectSingleRowBinaryProtocolNoParsing(b *testing.B) { 696 | setup(b) 697 | 698 | b.ResetTimer() 699 | 700 | for i := 0; i < b.N; i++ { 701 | id := randPersonIDs[i%len(randPersonIDs)] 702 | 703 | buf := []byte{0, 0, 0, 0} 704 | binary.BigEndian.PutUint32(buf, uint32(id)) 705 | 706 | rr := pgConnV5.ExecPrepared(context.Background(), "selectPerson", [][]byte{buf}, []int16{1}, []int16{1}) 707 | _, err := rr.Close() 708 | if err != nil { 709 | b.Fatalf("pgConnV5.ExecPrepared failed: %v", err) 710 | } 711 | } 712 | } 713 | 714 | func BenchmarkPgxV4StdlibSelectSingleRow(b *testing.B) { 715 | setup(b) 716 | stmt, err := pgxStdlibV4.Prepare(selectPersonSQL) 717 | if err != nil { 718 | b.Fatalf("Prepare failed: %v", err) 719 | } 720 | defer stmt.Close() 721 | 722 | benchmarkSelectSingleRow(b, stmt) 723 | } 724 | 725 | func BenchmarkPgxV4StdlibSelectSingleRowNotPreparedStatementCacheModePrepare(b *testing.B) { 726 | setup(b) 727 | 728 | config, err := pgxpoolv4.ParseConfig("") 729 | if err != nil { 730 | b.Fatalf("ParseConfig failed: %v", err) 731 | } 732 | config.ConnConfig.BuildStatementCache = func(conn *pgconnv4.PgConn) stmtcachev4.Cache { 733 | return stmtcachev4.New(conn, stmtcachev4.ModePrepare, 512) 734 | } 735 | 736 | pgxStdlibV4, err = openPgxStdlibV4(config) 737 | if err != nil { 738 | b.Fatalf("openPgxStdlib failed: %v", err) 739 | } 740 | 741 | benchmarkSelectSingleRowNotPrepared(b, pgxStdlibV4, selectPersonSQL) 742 | } 743 | 744 | func BenchmarkPgxV4StdlibSelectSingleRowNotPreparedStatementCacheModeDescribe(b *testing.B) { 745 | setup(b) 746 | 747 | config, err := pgxpoolv4.ParseConfig("") 748 | if err != nil { 749 | b.Fatalf("ParseConfig failed: %v", err) 750 | } 751 | config.ConnConfig.BuildStatementCache = func(conn *pgconnv4.PgConn) stmtcachev4.Cache { 752 | return stmtcachev4.New(conn, stmtcachev4.ModeDescribe, 512) 753 | } 754 | 755 | pgxStdlibV4, err = openPgxStdlibV4(config) 756 | if err != nil { 757 | b.Fatalf("openPgxStdlib failed: %v", err) 758 | } 759 | 760 | benchmarkSelectSingleRowNotPrepared(b, pgxStdlibV4, selectPersonSQL) 761 | } 762 | 763 | func BenchmarkPgxV4StdlibSelectSingleRowNotPreparedStatementCacheModeDisabled(b *testing.B) { 764 | setup(b) 765 | 766 | config, err := pgxpoolv4.ParseConfig("") 767 | if err != nil { 768 | b.Fatalf("ParseConfig failed: %v", err) 769 | } 770 | config.ConnConfig.BuildStatementCache = nil 771 | 772 | pgxStdlibV4, err = openPgxStdlibV4(config) 773 | if err != nil { 774 | b.Fatalf("openPgxStdlib failed: %v", err) 775 | } 776 | 777 | benchmarkSelectSingleRowNotPrepared(b, pgxStdlibV4, selectPersonSQL) 778 | } 779 | 780 | func BenchmarkPgSelectSingleRow(b *testing.B) { 781 | setup(b) 782 | 783 | stmt, err := pg.Prepare(selectPersonSQL) 784 | if err != nil { 785 | b.Fatalf("Prepare failed: %v", err) 786 | } 787 | defer stmt.Close() 788 | 789 | b.ResetTimer() 790 | for i := 0; i < b.N; i++ { 791 | var p person 792 | id := randPersonIDs[i%len(randPersonIDs)] 793 | _, err := stmt.QueryOne(&p, id) 794 | if err != nil { 795 | b.Fatalf("stmt.QueryOne failed: %v", err) 796 | } 797 | 798 | checkPersonWasFilled(b, p) 799 | } 800 | } 801 | 802 | func BenchmarkPgSelectSingleRowNotPrepared(b *testing.B) { 803 | setup(b) 804 | 805 | b.ResetTimer() 806 | for i := 0; i < b.N; i++ { 807 | var p person 808 | id := randPersonIDs[i%len(randPersonIDs)] 809 | _, err := pg.QueryOne(&p, selectPersonSQLQuestionMark, id) 810 | if err != nil { 811 | b.Fatalf("pg.QueryOne failed: %v", err) 812 | } 813 | 814 | checkPersonWasFilled(b, p) 815 | } 816 | } 817 | 818 | func BenchmarkPqSelectSingleRow(b *testing.B) { 819 | setup(b) 820 | stmt, err := pq.Prepare(selectPersonSQL) 821 | if err != nil { 822 | b.Fatalf("Prepare failed: %v", err) 823 | } 824 | defer stmt.Close() 825 | 826 | benchmarkSelectSingleRow(b, stmt) 827 | } 828 | 829 | func BenchmarkPqSelectSingleRowNotPrepared(b *testing.B) { 830 | setup(b) 831 | benchmarkSelectSingleRowNotPrepared(b, pq, selectPersonSQL) 832 | } 833 | 834 | func benchmarkSelectSingleRow(b *testing.B, stmt *sql.Stmt) { 835 | b.ResetTimer() 836 | for i := 0; i < b.N; i++ { 837 | id := randPersonIDs[i%len(randPersonIDs)] 838 | row := stmt.QueryRow(id) 839 | var p person 840 | err := row.Scan(&p.Id, &p.FirstName, &p.LastName, &p.Sex, &p.BirthDate, &p.Weight, &p.Height, &p.UpdateTime) 841 | if err != nil { 842 | b.Fatalf("row.Scan failed: %v", err) 843 | } 844 | 845 | checkPersonWasFilled(b, p) 846 | } 847 | } 848 | 849 | func benchmarkSelectSingleRowNotPrepared(b *testing.B, db *sql.DB, sql string) { 850 | b.ResetTimer() 851 | for i := 0; i < b.N; i++ { 852 | id := randPersonIDs[i%len(randPersonIDs)] 853 | row := db.QueryRow(sql, id) 854 | var p person 855 | err := row.Scan(&p.Id, &p.FirstName, &p.LastName, &p.Sex, &p.BirthDate, &p.Weight, &p.Height, &p.UpdateTime) 856 | if err != nil { 857 | b.Fatalf("row.Scan failed: %v", err) 858 | } 859 | 860 | checkPersonWasFilled(b, p) 861 | } 862 | } 863 | 864 | func BenchmarkRawSelectSingleRow(b *testing.B) { 865 | setup(b) 866 | b.ResetTimer() 867 | 868 | txBufs := make([][]byte, len(randPersonIDs)) 869 | for i, personID := range randPersonIDs { 870 | var err error 871 | txBufs[i], err = rawConn.BuildPreparedQueryBuf(rawSelectPersonStmt, personID) 872 | if err != nil { 873 | b.Fatalf("rawConn.BuildPreparedQueryBuf failed: %v", err) 874 | } 875 | } 876 | 877 | for i := 0; i < b.N; i++ { 878 | txBuf := txBufs[i%len(txBufs)] 879 | _, err := rawConn.Conn().Write(txBuf) 880 | if err != nil { 881 | b.Fatalf("rawConn.Conn.Write failed: %v", err) 882 | } 883 | 884 | rxRawUntilReady(b) 885 | } 886 | } 887 | 888 | func BenchmarkPgxV4NativeSelectMultipleRows(b *testing.B) { 889 | setup(b) 890 | 891 | b.ResetTimer() 892 | for i := 0; i < b.N; i++ { 893 | id := randPersonIDs[i%len(randPersonIDs)] 894 | 895 | rows, _ := pgxPoolV4.Query(context.Background(), "selectMultiplePeople", id) 896 | var p person 897 | for rows.Next() { 898 | err := rows.Scan(&p.Id, &p.FirstName, &p.LastName, &p.Sex, &p.BirthDate, &p.Weight, &p.Height, &p.UpdateTime) 899 | if err != nil { 900 | b.Fatalf("rows.Scan failed: %v", err) 901 | } 902 | checkPersonWasFilled(b, p) 903 | } 904 | if rows.Err() != nil { 905 | b.Fatalf("pgxPool.Query failed: %v", rows.Err()) 906 | } 907 | } 908 | } 909 | 910 | func BenchmarkPgxV5NativeSelectMultipleRows(b *testing.B) { 911 | setup(b) 912 | 913 | b.ResetTimer() 914 | for i := 0; i < b.N; i++ { 915 | id := randPersonIDs[i%len(randPersonIDs)] 916 | 917 | rows, _ := pgxPoolV5.Query(context.Background(), "selectMultiplePeople", id) 918 | var p person 919 | for rows.Next() { 920 | err := rows.Scan(&p.Id, &p.FirstName, &p.LastName, &p.Sex, &p.BirthDate, &p.Weight, &p.Height, &p.UpdateTime) 921 | if err != nil { 922 | b.Fatalf("rows.Scan failed: %v", err) 923 | } 924 | checkPersonWasFilled(b, p) 925 | } 926 | if rows.Err() != nil { 927 | b.Fatalf("pgxPool.Query failed: %v", rows.Err()) 928 | } 929 | } 930 | } 931 | 932 | func BenchmarkPgConnV4SelectMultipleRowsWithWithDecodeBinary(b *testing.B) { 933 | setup(b) 934 | 935 | type personRaw struct { 936 | Id pgtypev4.Int4 937 | FirstName pgtypev4.GenericBinary 938 | LastName pgtypev4.GenericBinary 939 | Sex pgtypev4.GenericBinary 940 | BirthDate pgtypev4.Date 941 | Weight pgtypev4.Int4 942 | Height pgtypev4.Int4 943 | UpdateTime pgtypev4.Timestamptz 944 | } 945 | 946 | b.ResetTimer() 947 | for i := 0; i < b.N; i++ { 948 | id := randPersonIDs[i%len(randPersonIDs)] 949 | 950 | buf := []byte{0, 0, 0, 0} 951 | binary.BigEndian.PutUint32(buf, uint32(id)) 952 | 953 | rr := pgConnV4.ExecPrepared(context.Background(), "selectMultiplePeople", [][]byte{buf}, []int16{1}, []int16{1}) 954 | 955 | var p personRaw 956 | for rr.NextRow() { 957 | var err error 958 | vi := 0 959 | 960 | err = p.Id.DecodeBinary(nil, rr.Values()[vi]) 961 | if err != nil { 962 | b.Fatalf("DecodeBinary failed: %v", err) 963 | } 964 | vi += 1 965 | 966 | err = p.FirstName.DecodeBinary(nil, rr.Values()[vi]) 967 | if err != nil { 968 | b.Fatalf("DecodeBinary failed: %v", err) 969 | } 970 | vi += 1 971 | 972 | err = p.LastName.DecodeBinary(nil, rr.Values()[vi]) 973 | if err != nil { 974 | b.Fatalf("DecodeBinary failed: %v", err) 975 | } 976 | vi += 1 977 | 978 | err = p.Sex.DecodeBinary(nil, rr.Values()[vi]) 979 | if err != nil { 980 | b.Fatalf("DecodeBinary failed: %v", err) 981 | } 982 | vi += 1 983 | 984 | err = p.BirthDate.DecodeBinary(nil, rr.Values()[vi]) 985 | if err != nil { 986 | b.Fatalf("DecodeBinary failed: %v", err) 987 | } 988 | vi += 1 989 | 990 | err = p.Weight.DecodeBinary(nil, rr.Values()[vi]) 991 | if err != nil { 992 | b.Fatalf("DecodeBinary failed: %v", err) 993 | } 994 | vi += 1 995 | 996 | err = p.Height.DecodeBinary(nil, rr.Values()[vi]) 997 | if err != nil { 998 | b.Fatalf("DecodeBinary failed: %v", err) 999 | } 1000 | vi += 1 1001 | 1002 | err = p.UpdateTime.DecodeBinary(nil, rr.Values()[vi]) 1003 | if err != nil { 1004 | b.Fatalf("DecodeBinary failed: %v", err) 1005 | } 1006 | 1007 | } 1008 | 1009 | _, err := rr.Close() 1010 | if err != nil { 1011 | b.Fatalf("pgConn.ExecPrepared failed: %v", err) 1012 | } 1013 | } 1014 | } 1015 | 1016 | func BenchmarkPgxV4NativeSelectMultipleRowsWithoutScan(b *testing.B) { 1017 | setup(b) 1018 | 1019 | type personRaw struct { 1020 | Id pgtypev4.Int4 1021 | FirstName pgtypev4.GenericBinary 1022 | LastName pgtypev4.GenericBinary 1023 | Sex pgtypev4.GenericBinary 1024 | BirthDate pgtypev4.Date 1025 | Weight pgtypev4.Int4 1026 | Height pgtypev4.Int4 1027 | UpdateTime pgtypev4.Timestamptz 1028 | } 1029 | 1030 | b.ResetTimer() 1031 | for i := 0; i < b.N; i++ { 1032 | id := randPersonIDs[i%len(randPersonIDs)] 1033 | 1034 | rows, _ := pgxPoolV4.Query(context.Background(), "selectMultiplePeople", id) 1035 | var p personRaw 1036 | for rows.Next() { 1037 | var err error 1038 | vi := 0 1039 | 1040 | err = p.Id.DecodeBinary(nil, rows.RawValues()[vi]) 1041 | if err != nil { 1042 | b.Fatalf("DecodeBinary failed: %v", err) 1043 | } 1044 | vi += 1 1045 | 1046 | err = p.FirstName.DecodeBinary(nil, rows.RawValues()[vi]) 1047 | if err != nil { 1048 | b.Fatalf("DecodeBinary failed: %v", err) 1049 | } 1050 | vi += 1 1051 | 1052 | err = p.LastName.DecodeBinary(nil, rows.RawValues()[vi]) 1053 | if err != nil { 1054 | b.Fatalf("DecodeBinary failed: %v", err) 1055 | } 1056 | vi += 1 1057 | 1058 | err = p.Sex.DecodeBinary(nil, rows.RawValues()[vi]) 1059 | if err != nil { 1060 | b.Fatalf("DecodeBinary failed: %v", err) 1061 | } 1062 | vi += 1 1063 | 1064 | err = p.BirthDate.DecodeBinary(nil, rows.RawValues()[vi]) 1065 | if err != nil { 1066 | b.Fatalf("DecodeBinary failed: %v", err) 1067 | } 1068 | vi += 1 1069 | 1070 | err = p.Weight.DecodeBinary(nil, rows.RawValues()[vi]) 1071 | if err != nil { 1072 | b.Fatalf("DecodeBinary failed: %v", err) 1073 | } 1074 | vi += 1 1075 | 1076 | err = p.Height.DecodeBinary(nil, rows.RawValues()[vi]) 1077 | if err != nil { 1078 | b.Fatalf("DecodeBinary failed: %v", err) 1079 | } 1080 | vi += 1 1081 | 1082 | err = p.UpdateTime.DecodeBinary(nil, rows.RawValues()[vi]) 1083 | if err != nil { 1084 | b.Fatalf("DecodeBinary failed: %v", err) 1085 | } 1086 | } 1087 | if rows.Err() != nil { 1088 | b.Fatalf("pgxPool.Query failed: %v", rows.Err()) 1089 | } 1090 | } 1091 | } 1092 | 1093 | func BenchmarkPgxV4NativeSelectMultipleRowsIntoGenericBinaryWithoutScan(b *testing.B) { 1094 | setup(b) 1095 | 1096 | type personRaw struct { 1097 | Id pgtypev4.GenericBinary 1098 | FirstName pgtypev4.GenericBinary 1099 | LastName pgtypev4.GenericBinary 1100 | Sex pgtypev4.GenericBinary 1101 | BirthDate pgtypev4.GenericBinary 1102 | Weight pgtypev4.GenericBinary 1103 | Height pgtypev4.GenericBinary 1104 | UpdateTime pgtypev4.GenericBinary 1105 | } 1106 | 1107 | b.ResetTimer() 1108 | for i := 0; i < b.N; i++ { 1109 | id := randPersonIDs[i%len(randPersonIDs)] 1110 | 1111 | rows, _ := pgxPoolV4.Query(context.Background(), "selectMultiplePeople", id) 1112 | var p personRaw 1113 | for rows.Next() { 1114 | var err error 1115 | vi := 0 1116 | 1117 | err = p.Id.DecodeBinary(nil, rows.RawValues()[vi]) 1118 | if err != nil { 1119 | b.Fatalf("DecodeBinary failed: %v", err) 1120 | } 1121 | vi += 1 1122 | 1123 | err = p.FirstName.DecodeBinary(nil, rows.RawValues()[vi]) 1124 | if err != nil { 1125 | b.Fatalf("DecodeBinary failed: %v", err) 1126 | } 1127 | vi += 1 1128 | 1129 | err = p.LastName.DecodeBinary(nil, rows.RawValues()[vi]) 1130 | if err != nil { 1131 | b.Fatalf("DecodeBinary failed: %v", err) 1132 | } 1133 | vi += 1 1134 | 1135 | err = p.Sex.DecodeBinary(nil, rows.RawValues()[vi]) 1136 | if err != nil { 1137 | b.Fatalf("DecodeBinary failed: %v", err) 1138 | } 1139 | vi += 1 1140 | 1141 | err = p.BirthDate.DecodeBinary(nil, rows.RawValues()[vi]) 1142 | if err != nil { 1143 | b.Fatalf("DecodeBinary failed: %v", err) 1144 | } 1145 | vi += 1 1146 | 1147 | err = p.Weight.DecodeBinary(nil, rows.RawValues()[vi]) 1148 | if err != nil { 1149 | b.Fatalf("DecodeBinary failed: %v", err) 1150 | } 1151 | vi += 1 1152 | 1153 | err = p.Height.DecodeBinary(nil, rows.RawValues()[vi]) 1154 | if err != nil { 1155 | b.Fatalf("DecodeBinary failed: %v", err) 1156 | } 1157 | vi += 1 1158 | 1159 | err = p.UpdateTime.DecodeBinary(nil, rows.RawValues()[vi]) 1160 | if err != nil { 1161 | b.Fatalf("DecodeBinary failed: %v", err) 1162 | } 1163 | } 1164 | if rows.Err() != nil { 1165 | b.Fatalf("pgxPool.Query failed: %v", rows.Err()) 1166 | } 1167 | } 1168 | } 1169 | 1170 | func BenchmarkPgxV4StdlibSelectMultipleRows(b *testing.B) { 1171 | setup(b) 1172 | 1173 | stmt, err := pgxStdlibV4.Prepare(selectMultiplePeopleSQL) 1174 | if err != nil { 1175 | b.Fatalf("Prepare failed: %v", err) 1176 | } 1177 | defer stmt.Close() 1178 | 1179 | benchmarkSelectMultipleRows(b, stmt) 1180 | } 1181 | 1182 | // This benchmark is different than the other multiple rows in that it collects 1183 | // all rows where as the others process and discard. So it is not apples-to- 1184 | // apples for *SelectMultipleRows*. 1185 | func BenchmarkPgSelectMultipleRowsCollect(b *testing.B) { 1186 | setup(b) 1187 | 1188 | stmt, err := pg.Prepare(selectMultiplePeopleSQL) 1189 | if err != nil { 1190 | b.Fatalf("Prepare failed: %v", err) 1191 | } 1192 | defer stmt.Close() 1193 | 1194 | b.ResetTimer() 1195 | for i := 0; i < b.N; i++ { 1196 | var people []person 1197 | id := randPersonIDs[i%len(randPersonIDs)] 1198 | _, err := stmt.Query(&people, id) 1199 | if err != nil { 1200 | b.Fatalf("stmt.Query failed: %v", err) 1201 | } 1202 | 1203 | for i := range people { 1204 | checkPersonWasFilled(b, people[i]) 1205 | } 1206 | } 1207 | } 1208 | 1209 | func BenchmarkPgSelectMultipleRowsAndDiscard(b *testing.B) { 1210 | setup(b) 1211 | 1212 | stmt, err := pg.Prepare(selectMultiplePeopleSQL) 1213 | if err != nil { 1214 | b.Fatalf("Prepare failed: %v", err) 1215 | } 1216 | defer stmt.Close() 1217 | 1218 | b.ResetTimer() 1219 | for i := 0; i < b.N; i++ { 1220 | id := randPersonIDs[i%len(randPersonIDs)] 1221 | _, err := stmt.Query(gopg.Discard, id) 1222 | if err != nil { 1223 | b.Fatalf("stmt.Query failed: %v", err) 1224 | } 1225 | } 1226 | } 1227 | 1228 | func BenchmarkPqSelectMultipleRows(b *testing.B) { 1229 | setup(b) 1230 | 1231 | stmt, err := pq.Prepare(selectMultiplePeopleSQL) 1232 | if err != nil { 1233 | b.Fatalf("Prepare failed: %v", err) 1234 | } 1235 | defer stmt.Close() 1236 | 1237 | benchmarkSelectMultipleRows(b, stmt) 1238 | } 1239 | 1240 | func benchmarkSelectMultipleRows(b *testing.B, stmt *sql.Stmt) { 1241 | b.ResetTimer() 1242 | for i := 0; i < b.N; i++ { 1243 | id := randPersonIDs[i%len(randPersonIDs)] 1244 | rows, err := stmt.Query(id) 1245 | if err != nil { 1246 | b.Fatalf("db.Query failed: %v", err) 1247 | } 1248 | 1249 | var p person 1250 | for rows.Next() { 1251 | err := rows.Scan(&p.Id, &p.FirstName, &p.LastName, &p.Sex, &p.BirthDate, &p.Weight, &p.Height, &p.UpdateTime) 1252 | if err != nil { 1253 | b.Fatalf("rows.Scan failed: %v", err) 1254 | } 1255 | checkPersonWasFilled(b, p) 1256 | } 1257 | 1258 | if rows.Err() != nil { 1259 | b.Fatalf("rows.Err() returned an error: %v", err) 1260 | } 1261 | } 1262 | } 1263 | 1264 | func BenchmarkRawSelectMultipleRows(b *testing.B) { 1265 | setup(b) 1266 | 1267 | b.ResetTimer() 1268 | 1269 | txBufs := make([][]byte, len(randPersonIDs)) 1270 | for i, personID := range randPersonIDs { 1271 | var err error 1272 | txBufs[i], err = rawConn.BuildPreparedQueryBuf(rawSelectMultiplePeopleStmt, personID) 1273 | if err != nil { 1274 | b.Fatalf("rawConn.BuildPreparedQueryBuf failed: %v", err) 1275 | } 1276 | } 1277 | 1278 | for i := 0; i < b.N; i++ { 1279 | txBuf := txBufs[i%len(txBufs)] 1280 | _, err := rawConn.Conn().Write(txBuf) 1281 | if err != nil { 1282 | b.Fatalf("rawConn.Conn.Write failed: %v", err) 1283 | } 1284 | 1285 | rxRawUntilReady(b) 1286 | } 1287 | } 1288 | 1289 | func rxRawUntilReady(b *testing.B) { 1290 | for { 1291 | n, err := rawConn.Conn().Read(rxBuf) 1292 | if err != nil { 1293 | b.Fatalf("rawConn.Conn.Read failed: %v", err) 1294 | } 1295 | if rxBuf[n-6] == 'Z' && rxBuf[n-2] == 5 && rxBuf[n-1] == 'I' { 1296 | return 1297 | } 1298 | } 1299 | } 1300 | 1301 | func checkPersonBytesWasFilled(b *testing.B, p personBytes) { 1302 | if p.Id == 0 { 1303 | b.Fatal("id was 0") 1304 | } 1305 | if len(p.FirstName) == 0 { 1306 | b.Fatal("FirstName was empty") 1307 | } 1308 | if len(p.LastName) == 0 { 1309 | b.Fatal("LastName was empty") 1310 | } 1311 | if len(p.Sex) == 0 { 1312 | b.Fatal("Sex was empty") 1313 | } 1314 | var zeroTime time.Time 1315 | if p.BirthDate == zeroTime { 1316 | b.Fatal("BirthDate was zero time") 1317 | } 1318 | if p.Weight == 0 { 1319 | b.Fatal("Weight was 0") 1320 | } 1321 | if p.Height == 0 { 1322 | b.Fatal("Height was 0") 1323 | } 1324 | if p.UpdateTime == zeroTime { 1325 | b.Fatal("UpdateTime was zero time") 1326 | } 1327 | } 1328 | 1329 | func BenchmarkPgxV4NativeSelectMultipleRowsBytes(b *testing.B) { 1330 | setup(b) 1331 | 1332 | b.ResetTimer() 1333 | for i := 0; i < b.N; i++ { 1334 | id := randPersonIDs[i%len(randPersonIDs)] 1335 | 1336 | rows, _ := pgxPoolV4.Query(context.Background(), "selectMultiplePeople", id) 1337 | var p personBytes 1338 | for rows.Next() { 1339 | err := rows.Scan(&p.Id, &p.FirstName, &p.LastName, &p.Sex, &p.BirthDate, &p.Weight, &p.Height, &p.UpdateTime) 1340 | if err != nil { 1341 | b.Fatalf("rows.Scan failed: %v", err) 1342 | } 1343 | checkPersonBytesWasFilled(b, p) 1344 | } 1345 | if rows.Err() != nil { 1346 | b.Fatalf("pgxPool.Query failed: %v", rows.Err()) 1347 | } 1348 | } 1349 | } 1350 | 1351 | func BenchmarkPgxV5NativeSelectMultipleRowsBytes(b *testing.B) { 1352 | setup(b) 1353 | 1354 | b.ResetTimer() 1355 | for i := 0; i < b.N; i++ { 1356 | id := randPersonIDs[i%len(randPersonIDs)] 1357 | 1358 | rows, _ := pgxPoolV5.Query(context.Background(), "selectMultiplePeople", id) 1359 | var p personBytes 1360 | for rows.Next() { 1361 | err := rows.Scan(&p.Id, &p.FirstName, &p.LastName, &p.Sex, &p.BirthDate, &p.Weight, &p.Height, &p.UpdateTime) 1362 | if err != nil { 1363 | b.Fatalf("rows.Scan failed: %v", err) 1364 | } 1365 | checkPersonBytesWasFilled(b, p) 1366 | } 1367 | if rows.Err() != nil { 1368 | b.Fatalf("pgxPool.Query failed: %v", rows.Err()) 1369 | } 1370 | } 1371 | } 1372 | 1373 | func BenchmarkPgxV4StdlibSelectMultipleRowsBytes(b *testing.B) { 1374 | setup(b) 1375 | 1376 | stmt, err := pgxStdlibV4.Prepare(selectMultiplePeopleSQL) 1377 | if err != nil { 1378 | b.Fatalf("Prepare failed: %v", err) 1379 | } 1380 | defer stmt.Close() 1381 | 1382 | benchmarkSelectMultipleRowsBytes(b, stmt) 1383 | } 1384 | 1385 | func BenchmarkPqSelectMultipleRowsBytes(b *testing.B) { 1386 | setup(b) 1387 | 1388 | stmt, err := pq.Prepare(selectMultiplePeopleSQL) 1389 | if err != nil { 1390 | b.Fatalf("Prepare failed: %v", err) 1391 | } 1392 | defer stmt.Close() 1393 | 1394 | benchmarkSelectMultipleRowsBytes(b, stmt) 1395 | } 1396 | 1397 | func benchmarkSelectMultipleRowsBytes(b *testing.B, stmt *sql.Stmt) { 1398 | b.ResetTimer() 1399 | for i := 0; i < b.N; i++ { 1400 | id := randPersonIDs[i%len(randPersonIDs)] 1401 | rows, err := stmt.Query(id) 1402 | if err != nil { 1403 | b.Fatalf("db.Query failed: %v", err) 1404 | } 1405 | 1406 | var p personBytes 1407 | for rows.Next() { 1408 | err := rows.Scan(&p.Id, &p.FirstName, &p.LastName, &p.Sex, &p.BirthDate, &p.Weight, &p.Height, &p.UpdateTime) 1409 | if err != nil { 1410 | b.Fatalf("rows.Scan failed: %v", err) 1411 | } 1412 | checkPersonBytesWasFilled(b, p) 1413 | } 1414 | 1415 | if rows.Err() != nil { 1416 | b.Fatalf("rows.Err() returned an error: %v", err) 1417 | } 1418 | } 1419 | } 1420 | 1421 | func BenchmarkPgxV4NativeSelectBatch3Query(b *testing.B) { 1422 | setup(b) 1423 | 1424 | b.ResetTimer() 1425 | batch := &pgxv4.Batch{} 1426 | results := make([]string, 3) 1427 | for j := range results { 1428 | batch.Queue("selectLargeText", j) 1429 | } 1430 | 1431 | for i := 0; i < b.N; i++ { 1432 | br := pgxPoolV4.SendBatch(context.Background(), batch) 1433 | 1434 | for j := range results { 1435 | if err := br.QueryRow().Scan(&results[j]); err != nil { 1436 | b.Fatal(err) 1437 | } 1438 | } 1439 | 1440 | if err := br.Close(); err != nil { 1441 | b.Fatal(err) 1442 | } 1443 | } 1444 | } 1445 | 1446 | func BenchmarkPgxV5NativeSelectBatch3Query(b *testing.B) { 1447 | setup(b) 1448 | 1449 | b.ResetTimer() 1450 | batch := &pgxv5.Batch{} 1451 | results := make([]string, 3) 1452 | for j := range results { 1453 | batch.Queue("selectLargeText", j) 1454 | } 1455 | 1456 | for i := 0; i < b.N; i++ { 1457 | br := pgxPoolV5.SendBatch(context.Background(), batch) 1458 | 1459 | for j := range results { 1460 | if err := br.QueryRow().Scan(&results[j]); err != nil { 1461 | b.Fatal(err) 1462 | } 1463 | } 1464 | 1465 | if err := br.Close(); err != nil { 1466 | b.Fatal(err) 1467 | } 1468 | } 1469 | } 1470 | 1471 | func BenchmarkPgxV4NativeSelectNoBatch3Query(b *testing.B) { 1472 | setup(b) 1473 | 1474 | b.ResetTimer() 1475 | results := make([]string, 3) 1476 | for i := 0; i < b.N; i++ { 1477 | for j := range results { 1478 | if err := pgxPoolV4.QueryRow(context.Background(), "selectLargeText", j).Scan(&results[j]); err != nil { 1479 | b.Fatal(err) 1480 | } 1481 | } 1482 | } 1483 | } 1484 | 1485 | func BenchmarkPgxV5NativeSelectNoBatch3Query(b *testing.B) { 1486 | setup(b) 1487 | 1488 | b.ResetTimer() 1489 | results := make([]string, 3) 1490 | for i := 0; i < b.N; i++ { 1491 | for j := range results { 1492 | if err := pgxPoolV5.QueryRow(context.Background(), "selectLargeText", j).Scan(&results[j]); err != nil { 1493 | b.Fatal(err) 1494 | } 1495 | } 1496 | } 1497 | } 1498 | 1499 | func BenchmarkPgxV4StdlibSelectNoBatch3Query(b *testing.B) { 1500 | setup(b) 1501 | stmt, err := pgxStdlibV4.Prepare(selectLargeTextSQL) 1502 | if err != nil { 1503 | b.Fatalf("Prepare failed: %v", err) 1504 | } 1505 | defer stmt.Close() 1506 | 1507 | benchmarkSelectNoBatch3Query(b, stmt) 1508 | } 1509 | 1510 | func BenchmarkPqSelectNoBatch3Query(b *testing.B) { 1511 | setup(b) 1512 | stmt, err := pq.Prepare(selectLargeTextSQL) 1513 | if err != nil { 1514 | b.Fatalf("Prepare failed: %v", err) 1515 | } 1516 | defer stmt.Close() 1517 | 1518 | benchmarkSelectNoBatch3Query(b, stmt) 1519 | } 1520 | 1521 | func benchmarkSelectNoBatch3Query(b *testing.B, stmt *sql.Stmt) { 1522 | b.ResetTimer() 1523 | results := make([]string, 3) 1524 | for i := 0; i < b.N; i++ { 1525 | for j := range results { 1526 | if err := stmt.QueryRow(j).Scan(&results[j]); err != nil { 1527 | b.Fatal(err) 1528 | } 1529 | } 1530 | } 1531 | } 1532 | 1533 | func BenchmarkPgxV4NativeSelectLargeTextString1KB(b *testing.B) { 1534 | benchmarkPgxV4NativeSelectLargeTextString(b, 1024) 1535 | } 1536 | 1537 | func BenchmarkPgxV4NativeSelectLargeTextString8KB(b *testing.B) { 1538 | benchmarkPgxV4NativeSelectLargeTextString(b, 8*1024) 1539 | } 1540 | 1541 | func BenchmarkPgxV4NativeSelectLargeTextString64KB(b *testing.B) { 1542 | benchmarkPgxV4NativeSelectLargeTextString(b, 64*1024) 1543 | } 1544 | 1545 | func benchmarkPgxV4NativeSelectLargeTextString(b *testing.B, size int) { 1546 | setup(b) 1547 | 1548 | b.ResetTimer() 1549 | for i := 0; i < b.N; i++ { 1550 | var s string 1551 | err := pgxPoolV4.QueryRow(context.Background(), "selectLargeText", size).Scan(&s) 1552 | if err != nil { 1553 | b.Fatalf("row.Scan failed: %v", err) 1554 | } 1555 | if len(s) != size { 1556 | b.Fatalf("expected length %v, got %v", size, len(s)) 1557 | } 1558 | } 1559 | } 1560 | 1561 | func BenchmarkPgxV5NativeSelectLargeTextString1KB(b *testing.B) { 1562 | benchmarkPgxV5NativeSelectLargeTextString(b, 1024) 1563 | } 1564 | 1565 | func BenchmarkPgxV5NativeSelectLargeTextString8KB(b *testing.B) { 1566 | benchmarkPgxV5NativeSelectLargeTextString(b, 8*1024) 1567 | } 1568 | 1569 | func BenchmarkPgxV5NativeSelectLargeTextString64KB(b *testing.B) { 1570 | benchmarkPgxV5NativeSelectLargeTextString(b, 64*1024) 1571 | } 1572 | 1573 | func benchmarkPgxV5NativeSelectLargeTextString(b *testing.B, size int) { 1574 | setup(b) 1575 | 1576 | b.ResetTimer() 1577 | for i := 0; i < b.N; i++ { 1578 | var s string 1579 | err := pgxPoolV5.QueryRow(context.Background(), "selectLargeText", size).Scan(&s) 1580 | if err != nil { 1581 | b.Fatalf("row.Scan failed: %v", err) 1582 | } 1583 | if len(s) != size { 1584 | b.Fatalf("expected length %v, got %v", size, len(s)) 1585 | } 1586 | } 1587 | } 1588 | 1589 | func BenchmarkPgxV4StdlibSelectLargeTextString1KB(b *testing.B) { 1590 | benchmarkPgxV4StdlibSelectLargeTextString(b, 1024) 1591 | } 1592 | 1593 | func BenchmarkPgxV4StdlibSelectLargeTextString8KB(b *testing.B) { 1594 | benchmarkPgxV4StdlibSelectLargeTextString(b, 8*1024) 1595 | } 1596 | 1597 | func BenchmarkPgxV4StdlibSelectLargeTextString64KB(b *testing.B) { 1598 | benchmarkPgxV4StdlibSelectLargeTextString(b, 64*1024) 1599 | } 1600 | 1601 | func benchmarkPgxV4StdlibSelectLargeTextString(b *testing.B, size int) { 1602 | setup(b) 1603 | stmt, err := pgxStdlibV4.Prepare(selectLargeTextSQL) 1604 | if err != nil { 1605 | b.Fatalf("Prepare failed: %v", err) 1606 | } 1607 | defer stmt.Close() 1608 | 1609 | benchmarkSelectLargeTextString(b, stmt, size) 1610 | } 1611 | 1612 | func BenchmarkPgSelectLargeTextString1KB(b *testing.B) { 1613 | benchmarkPgSelectLargeTextString(b, 1024) 1614 | } 1615 | 1616 | func BenchmarkPgSelectLargeTextString8KB(b *testing.B) { 1617 | benchmarkPgSelectLargeTextString(b, 8*1024) 1618 | } 1619 | 1620 | func BenchmarkPgSelectLargeTextString64KB(b *testing.B) { 1621 | benchmarkPgSelectLargeTextString(b, 64*1024) 1622 | } 1623 | 1624 | func benchmarkPgSelectLargeTextString(b *testing.B, size int) { 1625 | setup(b) 1626 | 1627 | stmt, err := pg.Prepare(selectLargeTextSQL) 1628 | if err != nil { 1629 | b.Fatalf("Prepare failed: %v", err) 1630 | } 1631 | defer stmt.Close() 1632 | 1633 | b.ResetTimer() 1634 | for i := 0; i < b.N; i++ { 1635 | var s string 1636 | _, err := stmt.QueryOne(gopg.Scan(&s), size) 1637 | if err != nil { 1638 | b.Fatalf("stmt.QueryOne failed: %v", err) 1639 | } 1640 | if len(s) != size { 1641 | b.Fatalf("expected length %v, got %v", size, len(s)) 1642 | } 1643 | } 1644 | } 1645 | 1646 | func BenchmarkPqSelectLargeTextString1KB(b *testing.B) { 1647 | benchmarkPqSelectLargeTextString(b, 1024) 1648 | } 1649 | 1650 | func BenchmarkPqSelectLargeTextString8KB(b *testing.B) { 1651 | benchmarkPqSelectLargeTextString(b, 8*1024) 1652 | } 1653 | 1654 | func BenchmarkPqSelectLargeTextString64KB(b *testing.B) { 1655 | benchmarkPqSelectLargeTextString(b, 64*1024) 1656 | } 1657 | 1658 | func benchmarkPqSelectLargeTextString(b *testing.B, size int) { 1659 | setup(b) 1660 | stmt, err := pq.Prepare(selectLargeTextSQL) 1661 | if err != nil { 1662 | b.Fatalf("Prepare failed: %v", err) 1663 | } 1664 | defer stmt.Close() 1665 | 1666 | benchmarkSelectLargeTextString(b, stmt, size) 1667 | } 1668 | 1669 | func benchmarkSelectLargeTextString(b *testing.B, stmt *sql.Stmt, size int) { 1670 | b.ResetTimer() 1671 | for i := 0; i < b.N; i++ { 1672 | var s string 1673 | err := stmt.QueryRow(size).Scan(&s) 1674 | if err != nil { 1675 | b.Fatalf("row.Scan failed: %v", err) 1676 | } 1677 | if len(s) != size { 1678 | b.Fatalf("expected length %v, got %v", size, len(s)) 1679 | } 1680 | } 1681 | } 1682 | 1683 | func BenchmarkPgxV4NativeSelectLargeTextBytes1KB(b *testing.B) { 1684 | benchmarkPgxV4NativeSelectLargeTextBytes(b, 1024) 1685 | } 1686 | 1687 | func BenchmarkPgxV4NativeSelectLargeTextBytes8KB(b *testing.B) { 1688 | benchmarkPgxV4NativeSelectLargeTextBytes(b, 8*1024) 1689 | } 1690 | 1691 | func BenchmarkPgxV4NativeSelectLargeTextBytes64KB(b *testing.B) { 1692 | benchmarkPgxV4NativeSelectLargeTextBytes(b, 64*1024) 1693 | } 1694 | 1695 | func benchmarkPgxV4NativeSelectLargeTextBytes(b *testing.B, size int) { 1696 | setup(b) 1697 | 1698 | b.ResetTimer() 1699 | for i := 0; i < b.N; i++ { 1700 | var s []byte 1701 | err := pgxPoolV4.QueryRow(context.Background(), "selectLargeText", size).Scan(&s) 1702 | if err != nil { 1703 | b.Fatalf("row.Scan failed: %v", err) 1704 | } 1705 | if len(s) != size { 1706 | b.Fatalf("expected length %v, got %v", size, len(s)) 1707 | } 1708 | } 1709 | } 1710 | 1711 | func BenchmarkPgxV5NativeSelectLargeTextBytes1KB(b *testing.B) { 1712 | benchmarkPgxV5NativeSelectLargeTextBytes(b, 1024) 1713 | } 1714 | 1715 | func BenchmarkPgxV5NativeSelectLargeTextBytes8KB(b *testing.B) { 1716 | benchmarkPgxV5NativeSelectLargeTextBytes(b, 8*1024) 1717 | } 1718 | 1719 | func BenchmarkPgxV5NativeSelectLargeTextBytes64KB(b *testing.B) { 1720 | benchmarkPgxV5NativeSelectLargeTextBytes(b, 64*1024) 1721 | } 1722 | 1723 | func benchmarkPgxV5NativeSelectLargeTextBytes(b *testing.B, size int) { 1724 | setup(b) 1725 | 1726 | b.ResetTimer() 1727 | for i := 0; i < b.N; i++ { 1728 | var s []byte 1729 | err := pgxPoolV5.QueryRow(context.Background(), "selectLargeText", size).Scan(&s) 1730 | if err != nil { 1731 | b.Fatalf("row.Scan failed: %v", err) 1732 | } 1733 | if len(s) != size { 1734 | b.Fatalf("expected length %v, got %v", size, len(s)) 1735 | } 1736 | } 1737 | } 1738 | 1739 | func BenchmarkPgxV5NativeSelectLargeTextDriverBytes1KB(b *testing.B) { 1740 | benchmarkPgxV5NativeSelectLargeTextDriverBytes(b, 1024) 1741 | } 1742 | 1743 | func BenchmarkPgxV5NativeSelectLargeTextDriverBytes8KB(b *testing.B) { 1744 | benchmarkPgxV5NativeSelectLargeTextDriverBytes(b, 8*1024) 1745 | } 1746 | 1747 | func BenchmarkPgxV5NativeSelectLargeTextDriverBytes64KB(b *testing.B) { 1748 | benchmarkPgxV5NativeSelectLargeTextDriverBytes(b, 64*1024) 1749 | } 1750 | 1751 | func benchmarkPgxV5NativeSelectLargeTextDriverBytes(b *testing.B, size int) { 1752 | setup(b) 1753 | 1754 | b.ResetTimer() 1755 | for i := 0; i < b.N; i++ { 1756 | var s []byte 1757 | // Can't use QueryRow with DriverBytes 1758 | rows, err := pgxPoolV5.Query(context.Background(), "selectLargeText", size) 1759 | if !rows.Next() { 1760 | b.Fatalf("rows.Next unexpectedly was false") 1761 | } 1762 | err = rows.Scan((*pgtypev5.DriverBytes)(&s)) 1763 | if err != nil { 1764 | b.Fatalf("row.Scan failed: %v", err) 1765 | } 1766 | if len(s) != size { 1767 | b.Fatalf("expected length %v, got %v", size, len(s)) 1768 | } 1769 | rows.Close() 1770 | err = rows.Err() 1771 | if err != nil { 1772 | b.Fatalf("rows.Err(): %v", err) 1773 | } 1774 | } 1775 | } 1776 | 1777 | func BenchmarkPgxV4StdlibSelectLargeTextBytes1KB(b *testing.B) { 1778 | benchmarkPgxV4StdlibSelectLargeTextBytes(b, 1024) 1779 | } 1780 | 1781 | func BenchmarkPgxV4StdlibSelectLargeTextBytes8KB(b *testing.B) { 1782 | benchmarkPgxV4StdlibSelectLargeTextBytes(b, 8*1024) 1783 | } 1784 | 1785 | func BenchmarkPgxV4StdlibSelectLargeTextBytes64KB(b *testing.B) { 1786 | benchmarkPgxV4StdlibSelectLargeTextBytes(b, 64*1024) 1787 | } 1788 | 1789 | func benchmarkPgxV4StdlibSelectLargeTextBytes(b *testing.B, size int) { 1790 | setup(b) 1791 | stmt, err := pgxStdlibV4.Prepare(selectLargeTextSQL) 1792 | if err != nil { 1793 | b.Fatalf("Prepare failed: %v", err) 1794 | } 1795 | defer stmt.Close() 1796 | 1797 | benchmarkSelectLargeTextBytes(b, stmt, size) 1798 | } 1799 | 1800 | func BenchmarkPqSelectLargeTextBytes1KB(b *testing.B) { 1801 | benchmarkPqSelectLargeTextBytes(b, 1024) 1802 | } 1803 | 1804 | func BenchmarkPqSelectLargeTextBytes8KB(b *testing.B) { 1805 | benchmarkPqSelectLargeTextBytes(b, 8*1024) 1806 | } 1807 | 1808 | func BenchmarkPqSelectLargeTextBytes64KB(b *testing.B) { 1809 | benchmarkPqSelectLargeTextBytes(b, 64*1024) 1810 | } 1811 | 1812 | func benchmarkPqSelectLargeTextBytes(b *testing.B, size int) { 1813 | setup(b) 1814 | stmt, err := pq.Prepare(selectLargeTextSQL) 1815 | if err != nil { 1816 | b.Fatalf("Prepare failed: %v", err) 1817 | } 1818 | defer stmt.Close() 1819 | 1820 | benchmarkSelectLargeTextBytes(b, stmt, size) 1821 | } 1822 | 1823 | func benchmarkSelectLargeTextBytes(b *testing.B, stmt *sql.Stmt, size int) { 1824 | b.ResetTimer() 1825 | for i := 0; i < b.N; i++ { 1826 | var s []byte 1827 | err := stmt.QueryRow(size).Scan(&s) 1828 | if err != nil { 1829 | b.Fatalf("row.Scan failed: %v", err) 1830 | } 1831 | if len(s) != size { 1832 | b.Fatalf("expected length %v, got %v", size, len(s)) 1833 | } 1834 | } 1835 | } 1836 | --------------------------------------------------------------------------------