mirror of
https://github.com/documize/community.git
synced 2025-07-19 21:29:42 +02:00
Update PostgreSQL driver library
This commit is contained in:
parent
f3df43efe0
commit
c538fc9eb1
31 changed files with 7381 additions and 162 deletions
2
vendor/github.com/lib/pq/README.md
generated
vendored
2
vendor/github.com/lib/pq/README.md
generated
vendored
|
@ -10,7 +10,7 @@
|
||||||
## Docs
|
## Docs
|
||||||
|
|
||||||
For detailed documentation and basic usage examples, please see the package
|
For detailed documentation and basic usage examples, please see the package
|
||||||
documentation at <http://godoc.org/github.com/lib/pq>.
|
documentation at <https://godoc.org/github.com/lib/pq>.
|
||||||
|
|
||||||
## Tests
|
## Tests
|
||||||
|
|
||||||
|
|
1311
vendor/github.com/lib/pq/array_test.go
generated
vendored
Normal file
1311
vendor/github.com/lib/pq/array_test.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load diff
434
vendor/github.com/lib/pq/bench_test.go
generated
vendored
Normal file
434
vendor/github.com/lib/pq/bench_test.go
generated
vendored
Normal file
|
@ -0,0 +1,434 @@
|
||||||
|
package pq
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"database/sql"
|
||||||
|
"database/sql/driver"
|
||||||
|
"io"
|
||||||
|
"math/rand"
|
||||||
|
"net"
|
||||||
|
"runtime"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/lib/pq/oid"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
selectStringQuery = "SELECT '" + strings.Repeat("0123456789", 10) + "'"
|
||||||
|
selectSeriesQuery = "SELECT generate_series(1, 100)"
|
||||||
|
)
|
||||||
|
|
||||||
|
func BenchmarkSelectString(b *testing.B) {
|
||||||
|
var result string
|
||||||
|
benchQuery(b, selectStringQuery, &result)
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkSelectSeries(b *testing.B) {
|
||||||
|
var result int
|
||||||
|
benchQuery(b, selectSeriesQuery, &result)
|
||||||
|
}
|
||||||
|
|
||||||
|
func benchQuery(b *testing.B, query string, result interface{}) {
|
||||||
|
b.StopTimer()
|
||||||
|
db := openTestConn(b)
|
||||||
|
defer db.Close()
|
||||||
|
b.StartTimer()
|
||||||
|
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
benchQueryLoop(b, db, query, result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func benchQueryLoop(b *testing.B, db *sql.DB, query string, result interface{}) {
|
||||||
|
rows, err := db.Query(query)
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
for rows.Next() {
|
||||||
|
err = rows.Scan(result)
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal("failed to scan", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// reading from circularConn yields content[:prefixLen] once, followed by
|
||||||
|
// content[prefixLen:] over and over again. It never returns EOF.
|
||||||
|
type circularConn struct {
|
||||||
|
content string
|
||||||
|
prefixLen int
|
||||||
|
pos int
|
||||||
|
net.Conn // for all other net.Conn methods that will never be called
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *circularConn) Read(b []byte) (n int, err error) {
|
||||||
|
n = copy(b, r.content[r.pos:])
|
||||||
|
r.pos += n
|
||||||
|
if r.pos >= len(r.content) {
|
||||||
|
r.pos = r.prefixLen
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *circularConn) Write(b []byte) (n int, err error) { return len(b), nil }
|
||||||
|
|
||||||
|
func (r *circularConn) Close() error { return nil }
|
||||||
|
|
||||||
|
func fakeConn(content string, prefixLen int) *conn {
|
||||||
|
c := &circularConn{content: content, prefixLen: prefixLen}
|
||||||
|
return &conn{buf: bufio.NewReader(c), c: c}
|
||||||
|
}
|
||||||
|
|
||||||
|
// This benchmark is meant to be the same as BenchmarkSelectString, but takes
|
||||||
|
// out some of the factors this package can't control. The numbers are less noisy,
|
||||||
|
// but also the costs of network communication aren't accurately represented.
|
||||||
|
func BenchmarkMockSelectString(b *testing.B) {
|
||||||
|
b.StopTimer()
|
||||||
|
// taken from a recorded run of BenchmarkSelectString
|
||||||
|
// See: http://www.postgresql.org/docs/current/static/protocol-message-formats.html
|
||||||
|
const response = "1\x00\x00\x00\x04" +
|
||||||
|
"t\x00\x00\x00\x06\x00\x00" +
|
||||||
|
"T\x00\x00\x00!\x00\x01?column?\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\xc1\xff\xfe\xff\xff\xff\xff\x00\x00" +
|
||||||
|
"Z\x00\x00\x00\x05I" +
|
||||||
|
"2\x00\x00\x00\x04" +
|
||||||
|
"D\x00\x00\x00n\x00\x01\x00\x00\x00d0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789" +
|
||||||
|
"C\x00\x00\x00\rSELECT 1\x00" +
|
||||||
|
"Z\x00\x00\x00\x05I" +
|
||||||
|
"3\x00\x00\x00\x04" +
|
||||||
|
"Z\x00\x00\x00\x05I"
|
||||||
|
c := fakeConn(response, 0)
|
||||||
|
b.StartTimer()
|
||||||
|
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
benchMockQuery(b, c, selectStringQuery)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var seriesRowData = func() string {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
for i := 1; i <= 100; i++ {
|
||||||
|
digits := byte(2)
|
||||||
|
if i >= 100 {
|
||||||
|
digits = 3
|
||||||
|
} else if i < 10 {
|
||||||
|
digits = 1
|
||||||
|
}
|
||||||
|
buf.WriteString("D\x00\x00\x00")
|
||||||
|
buf.WriteByte(10 + digits)
|
||||||
|
buf.WriteString("\x00\x01\x00\x00\x00")
|
||||||
|
buf.WriteByte(digits)
|
||||||
|
buf.WriteString(strconv.Itoa(i))
|
||||||
|
}
|
||||||
|
return buf.String()
|
||||||
|
}()
|
||||||
|
|
||||||
|
func BenchmarkMockSelectSeries(b *testing.B) {
|
||||||
|
b.StopTimer()
|
||||||
|
var response = "1\x00\x00\x00\x04" +
|
||||||
|
"t\x00\x00\x00\x06\x00\x00" +
|
||||||
|
"T\x00\x00\x00!\x00\x01?column?\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\xc1\xff\xfe\xff\xff\xff\xff\x00\x00" +
|
||||||
|
"Z\x00\x00\x00\x05I" +
|
||||||
|
"2\x00\x00\x00\x04" +
|
||||||
|
seriesRowData +
|
||||||
|
"C\x00\x00\x00\x0fSELECT 100\x00" +
|
||||||
|
"Z\x00\x00\x00\x05I" +
|
||||||
|
"3\x00\x00\x00\x04" +
|
||||||
|
"Z\x00\x00\x00\x05I"
|
||||||
|
c := fakeConn(response, 0)
|
||||||
|
b.StartTimer()
|
||||||
|
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
benchMockQuery(b, c, selectSeriesQuery)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func benchMockQuery(b *testing.B, c *conn, query string) {
|
||||||
|
stmt, err := c.Prepare(query)
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
defer stmt.Close()
|
||||||
|
rows, err := stmt.(driver.StmtQueryContext).QueryContext(context.Background(), nil)
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
var dest [1]driver.Value
|
||||||
|
for {
|
||||||
|
if err := rows.Next(dest[:]); err != nil {
|
||||||
|
if err == io.EOF {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkPreparedSelectString(b *testing.B) {
|
||||||
|
var result string
|
||||||
|
benchPreparedQuery(b, selectStringQuery, &result)
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkPreparedSelectSeries(b *testing.B) {
|
||||||
|
var result int
|
||||||
|
benchPreparedQuery(b, selectSeriesQuery, &result)
|
||||||
|
}
|
||||||
|
|
||||||
|
func benchPreparedQuery(b *testing.B, query string, result interface{}) {
|
||||||
|
b.StopTimer()
|
||||||
|
db := openTestConn(b)
|
||||||
|
defer db.Close()
|
||||||
|
stmt, err := db.Prepare(query)
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
defer stmt.Close()
|
||||||
|
b.StartTimer()
|
||||||
|
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
benchPreparedQueryLoop(b, db, stmt, result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func benchPreparedQueryLoop(b *testing.B, db *sql.DB, stmt *sql.Stmt, result interface{}) {
|
||||||
|
rows, err := stmt.Query()
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
if !rows.Next() {
|
||||||
|
rows.Close()
|
||||||
|
b.Fatal("no rows")
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
for rows.Next() {
|
||||||
|
err = rows.Scan(&result)
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal("failed to scan")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// See the comment for BenchmarkMockSelectString.
|
||||||
|
func BenchmarkMockPreparedSelectString(b *testing.B) {
|
||||||
|
b.StopTimer()
|
||||||
|
const parseResponse = "1\x00\x00\x00\x04" +
|
||||||
|
"t\x00\x00\x00\x06\x00\x00" +
|
||||||
|
"T\x00\x00\x00!\x00\x01?column?\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\xc1\xff\xfe\xff\xff\xff\xff\x00\x00" +
|
||||||
|
"Z\x00\x00\x00\x05I"
|
||||||
|
const responses = parseResponse +
|
||||||
|
"2\x00\x00\x00\x04" +
|
||||||
|
"D\x00\x00\x00n\x00\x01\x00\x00\x00d0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789" +
|
||||||
|
"C\x00\x00\x00\rSELECT 1\x00" +
|
||||||
|
"Z\x00\x00\x00\x05I"
|
||||||
|
c := fakeConn(responses, len(parseResponse))
|
||||||
|
|
||||||
|
stmt, err := c.Prepare(selectStringQuery)
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
b.StartTimer()
|
||||||
|
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
benchPreparedMockQuery(b, c, stmt)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkMockPreparedSelectSeries(b *testing.B) {
|
||||||
|
b.StopTimer()
|
||||||
|
const parseResponse = "1\x00\x00\x00\x04" +
|
||||||
|
"t\x00\x00\x00\x06\x00\x00" +
|
||||||
|
"T\x00\x00\x00!\x00\x01?column?\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\xc1\xff\xfe\xff\xff\xff\xff\x00\x00" +
|
||||||
|
"Z\x00\x00\x00\x05I"
|
||||||
|
var responses = parseResponse +
|
||||||
|
"2\x00\x00\x00\x04" +
|
||||||
|
seriesRowData +
|
||||||
|
"C\x00\x00\x00\x0fSELECT 100\x00" +
|
||||||
|
"Z\x00\x00\x00\x05I"
|
||||||
|
c := fakeConn(responses, len(parseResponse))
|
||||||
|
|
||||||
|
stmt, err := c.Prepare(selectSeriesQuery)
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
b.StartTimer()
|
||||||
|
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
benchPreparedMockQuery(b, c, stmt)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func benchPreparedMockQuery(b *testing.B, c *conn, stmt driver.Stmt) {
|
||||||
|
rows, err := stmt.(driver.StmtQueryContext).QueryContext(context.Background(), nil)
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
var dest [1]driver.Value
|
||||||
|
for {
|
||||||
|
if err := rows.Next(dest[:]); err != nil {
|
||||||
|
if err == io.EOF {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkEncodeInt64(b *testing.B) {
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
encode(¶meterStatus{}, int64(1234), oid.T_int8)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkEncodeFloat64(b *testing.B) {
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
encode(¶meterStatus{}, 3.14159, oid.T_float8)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var testByteString = []byte("abcdefghijklmnopqrstuvwxyz")
|
||||||
|
|
||||||
|
func BenchmarkEncodeByteaHex(b *testing.B) {
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
encode(¶meterStatus{serverVersion: 90000}, testByteString, oid.T_bytea)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func BenchmarkEncodeByteaEscape(b *testing.B) {
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
encode(¶meterStatus{serverVersion: 84000}, testByteString, oid.T_bytea)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkEncodeBool(b *testing.B) {
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
encode(¶meterStatus{}, true, oid.T_bool)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var testTimestamptz = time.Date(2001, time.January, 1, 0, 0, 0, 0, time.Local)
|
||||||
|
|
||||||
|
func BenchmarkEncodeTimestamptz(b *testing.B) {
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
encode(¶meterStatus{}, testTimestamptz, oid.T_timestamptz)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var testIntBytes = []byte("1234")
|
||||||
|
|
||||||
|
func BenchmarkDecodeInt64(b *testing.B) {
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
decode(¶meterStatus{}, testIntBytes, oid.T_int8, formatText)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var testFloatBytes = []byte("3.14159")
|
||||||
|
|
||||||
|
func BenchmarkDecodeFloat64(b *testing.B) {
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
decode(¶meterStatus{}, testFloatBytes, oid.T_float8, formatText)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var testBoolBytes = []byte{'t'}
|
||||||
|
|
||||||
|
func BenchmarkDecodeBool(b *testing.B) {
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
decode(¶meterStatus{}, testBoolBytes, oid.T_bool, formatText)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDecodeBool(t *testing.T) {
|
||||||
|
db := openTestConn(t)
|
||||||
|
rows, err := db.Query("select true")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
rows.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
var testTimestamptzBytes = []byte("2013-09-17 22:15:32.360754-07")
|
||||||
|
|
||||||
|
func BenchmarkDecodeTimestamptz(b *testing.B) {
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
decode(¶meterStatus{}, testTimestamptzBytes, oid.T_timestamptz, formatText)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkDecodeTimestamptzMultiThread(b *testing.B) {
|
||||||
|
oldProcs := runtime.GOMAXPROCS(0)
|
||||||
|
defer runtime.GOMAXPROCS(oldProcs)
|
||||||
|
runtime.GOMAXPROCS(runtime.NumCPU())
|
||||||
|
globalLocationCache = newLocationCache()
|
||||||
|
|
||||||
|
f := func(wg *sync.WaitGroup, loops int) {
|
||||||
|
defer wg.Done()
|
||||||
|
for i := 0; i < loops; i++ {
|
||||||
|
decode(¶meterStatus{}, testTimestamptzBytes, oid.T_timestamptz, formatText)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
wg := &sync.WaitGroup{}
|
||||||
|
b.ResetTimer()
|
||||||
|
for j := 0; j < 10; j++ {
|
||||||
|
wg.Add(1)
|
||||||
|
go f(wg, b.N/10)
|
||||||
|
}
|
||||||
|
wg.Wait()
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkLocationCache(b *testing.B) {
|
||||||
|
globalLocationCache = newLocationCache()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
globalLocationCache.getLocation(rand.Intn(10000))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkLocationCacheMultiThread(b *testing.B) {
|
||||||
|
oldProcs := runtime.GOMAXPROCS(0)
|
||||||
|
defer runtime.GOMAXPROCS(oldProcs)
|
||||||
|
runtime.GOMAXPROCS(runtime.NumCPU())
|
||||||
|
globalLocationCache = newLocationCache()
|
||||||
|
|
||||||
|
f := func(wg *sync.WaitGroup, loops int) {
|
||||||
|
defer wg.Done()
|
||||||
|
for i := 0; i < loops; i++ {
|
||||||
|
globalLocationCache.getLocation(rand.Intn(10000))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
wg := &sync.WaitGroup{}
|
||||||
|
b.ResetTimer()
|
||||||
|
for j := 0; j < 10; j++ {
|
||||||
|
wg.Add(1)
|
||||||
|
go f(wg, b.N/10)
|
||||||
|
}
|
||||||
|
wg.Wait()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stress test the performance of parsing results from the wire.
|
||||||
|
func BenchmarkResultParsing(b *testing.B) {
|
||||||
|
b.StopTimer()
|
||||||
|
|
||||||
|
db := openTestConn(b)
|
||||||
|
defer db.Close()
|
||||||
|
_, err := db.Exec("BEGIN")
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
b.StartTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
res, err := db.Query("SELECT generate_series(1, 50000)")
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
res.Close()
|
||||||
|
}
|
||||||
|
}
|
3
vendor/github.com/lib/pq/certs/README
generated
vendored
Normal file
3
vendor/github.com/lib/pq/certs/README
generated
vendored
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
This directory contains certificates and private keys for testing some
|
||||||
|
SSL-related functionality in Travis. Do NOT use these certificates for
|
||||||
|
anything other than testing.
|
15
vendor/github.com/lib/pq/certs/postgresql.key
generated
vendored
Normal file
15
vendor/github.com/lib/pq/certs/postgresql.key
generated
vendored
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
-----BEGIN RSA PRIVATE KEY-----
|
||||||
|
MIICWwIBAAKBgQDjjAaacFRR0TQ0gznNolkPBe2N2A400JL0CU3ujHhVSST4POA0
|
||||||
|
WAKy55RYwejlu9Gv9lTBQLGQcHkNNVScjxbpwvCS5mRJOMF2+EdmxFtKtqlDzsi+
|
||||||
|
bE0rlJc8VbzR0G63U66JXEtrhkC+wa4eZM6crocKaeXIIRK+rh32Rd8WpwIDAQAB
|
||||||
|
AoGAM5dM6/kp9P700i8qjOgRPym96Zoh5nGfz/rIE5z/r36NBkdvIg8OVZfR96nH
|
||||||
|
b0b9TOMR5lsPp0sI9yivTWvX6qyvLJRWy2vvx17hXK9NxXUNTAm0PYZUTvCtcPeX
|
||||||
|
RnJpzQKNZQPkFzF0uXBc4CtPK2Vz0+FGvAelrhYAxnw1dIkCQQD+9qaW5QhXjsjb
|
||||||
|
Nl85CmXgxPmGROcgLQCO+omfrjf9UXrituU9Dz6auym5lDGEdMFnkzfr+wpasEy9
|
||||||
|
mf5ZZOhDAkEA5HjXfVGaCtpydOt6hDon/uZsyssCK2lQ7NSuE3vP+sUsYMzIpEoy
|
||||||
|
t3VWXqKbo+g9KNDTP4WEliqp1aiSIylzzQJANPeqzihQnlgEdD4MdD4rwhFJwVIp
|
||||||
|
Le8Lcais1KaN7StzOwxB/XhgSibd2TbnPpw+3bSg5n5lvUdo+e62/31OHwJAU1jS
|
||||||
|
I+F09KikQIr28u3UUWT2IzTT4cpVv1AHAQyV3sG3YsjSGT0IK20eyP9BEBZU2WL0
|
||||||
|
7aNjrvR5aHxKc5FXsQJABsFtyGpgI5X4xufkJZVZ+Mklz2n7iXa+XPatMAHFxAtb
|
||||||
|
EEMt60rngwMjXAzBSC6OYuYogRRAY3UCacNC5VhLYQ==
|
||||||
|
-----END RSA PRIVATE KEY-----
|
27
vendor/github.com/lib/pq/certs/server.key
generated
vendored
Normal file
27
vendor/github.com/lib/pq/certs/server.key
generated
vendored
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
-----BEGIN RSA PRIVATE KEY-----
|
||||||
|
MIIEogIBAAKCAQEA14pMhfsXpTyP4HIRKc4/sB8/fcbuf6f8Ais1RwimPZDfXFYU
|
||||||
|
lADHbdHS4mGVd7jjpmYx+R8hfWLhJ9qUN2FK6mNToGG4nLul4ue3ptgPBQTHKeLq
|
||||||
|
SSt/3hUAphhwUMcM3pr5Wpaw4ZQGxm1KITu0D6VtkoY0sk7XDqcZwHcLe4fIkt5C
|
||||||
|
/4bSt5qk1BUjyq2laSG4zn5my4Vdue2LLQmNlOQEHnLs79B2kBVapPeRS+nOTp1d
|
||||||
|
mnAXnNjpc4PqPWGZps2skUBaiHflTiqOPRPz+ThvgWuKlcoOB6tv2rSM2f+qeAOq
|
||||||
|
x8LPb2SS09iD1a/xIxinLnsXC+d98fqoQaMEVwIDAQABAoIBAF3ZoihUhJ82F4+r
|
||||||
|
Gz4QyDpv4L1reT2sb1aiabhcU8ZK5nbWJG+tRyjSS/i2dNaEcttpdCj9HR/zhgZM
|
||||||
|
bm0OuAgG58rVwgS80CZUruq++Qs+YVojq8/gWPTiQD4SNhV2Fmx3HkwLgUk3oxuT
|
||||||
|
SsvdqzGE3okGVrutCIcgy126eA147VPMoej1Bb3fO6npqK0pFPhZfAc0YoqJuM+k
|
||||||
|
obRm5pAnGUipyLCFXjA9HYPKwYZw2RtfdA3CiImHeanSdqS+ctrC9y8BV40Th7gZ
|
||||||
|
haXdKUNdjmIxV695QQ1mkGqpKLZFqhzKioGQ2/Ly2d1iaKN9fZltTusu8unepWJ2
|
||||||
|
tlT9qMECgYEA9uHaF1t2CqE+AJvWTihHhPIIuLxoOQXYea1qvxfcH/UMtaLKzCNm
|
||||||
|
lQ5pqCGsPvp+10f36yttO1ZehIvlVNXuJsjt0zJmPtIolNuJY76yeussfQ9jHheB
|
||||||
|
5uPEzCFlHzxYbBUyqgWaF6W74okRGzEGJXjYSP0yHPPdU4ep2q3bGiUCgYEA34Af
|
||||||
|
wBSuQSK7uLxArWHvQhyuvi43ZGXls6oRGl+Ysj54s8BP6XGkq9hEJ6G4yxgyV+BR
|
||||||
|
DUOs5X8/TLT8POuIMYvKTQthQyCk0eLv2FLdESDuuKx0kBVY3s8lK3/z5HhrdOiN
|
||||||
|
VMNZU+xDKgKc3hN9ypkk8vcZe6EtH7Y14e0rVcsCgYBTgxi8F/M5K0wG9rAqphNz
|
||||||
|
VFBA9XKn/2M33cKjO5X5tXIEKzpAjaUQvNxexG04rJGljzG8+mar0M6ONahw5yD1
|
||||||
|
O7i/XWgazgpuOEkkVYiYbd8RutfDgR4vFVMn3hAP3eDnRtBplRWH9Ec3HTiNIys6
|
||||||
|
F8PKBOQjyRZQQC7jyzW3hQKBgACe5HeuFwXLSOYsb6mLmhR+6+VPT4wR1F95W27N
|
||||||
|
USk9jyxAnngxfpmTkiziABdgS9N+pfr5cyN4BP77ia/Jn6kzkC5Cl9SN5KdIkA3z
|
||||||
|
vPVtN/x/ThuQU5zaymmig1ThGLtMYggYOslG4LDfLPxY5YKIhle+Y+259twdr2yf
|
||||||
|
Mf2dAoGAaGv3tWMgnIdGRk6EQL/yb9PKHo7ShN+tKNlGaK7WwzBdKs+Fe8jkgcr7
|
||||||
|
pz4Ne887CmxejdISzOCcdT+Zm9Bx6I/uZwWOtDvWpIgIxVX9a9URj/+D1MxTE/y4
|
||||||
|
d6H+c89yDY62I2+drMpdjCd3EtCaTlxpTbRS+s1eAHMH7aEkcCE=
|
||||||
|
-----END RSA PRIVATE KEY-----
|
279
vendor/github.com/lib/pq/conn.go
generated
vendored
279
vendor/github.com/lib/pq/conn.go
generated
vendored
|
@ -2,7 +2,9 @@ package pq
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
|
"context"
|
||||||
"crypto/md5"
|
"crypto/md5"
|
||||||
|
"crypto/sha256"
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"database/sql/driver"
|
"database/sql/driver"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
|
@ -20,6 +22,7 @@ import (
|
||||||
"unicode"
|
"unicode"
|
||||||
|
|
||||||
"github.com/lib/pq/oid"
|
"github.com/lib/pq/oid"
|
||||||
|
"github.com/lib/pq/scram"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Common error types
|
// Common error types
|
||||||
|
@ -89,13 +92,24 @@ type Dialer interface {
|
||||||
DialTimeout(network, address string, timeout time.Duration) (net.Conn, error)
|
DialTimeout(network, address string, timeout time.Duration) (net.Conn, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type defaultDialer struct{}
|
type DialerContext interface {
|
||||||
|
DialContext(ctx context.Context, network, address string) (net.Conn, error)
|
||||||
func (d defaultDialer) Dial(ntw, addr string) (net.Conn, error) {
|
|
||||||
return net.Dial(ntw, addr)
|
|
||||||
}
|
}
|
||||||
func (d defaultDialer) DialTimeout(ntw, addr string, timeout time.Duration) (net.Conn, error) {
|
|
||||||
return net.DialTimeout(ntw, addr, timeout)
|
type defaultDialer struct {
|
||||||
|
d net.Dialer
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d defaultDialer) Dial(network, address string) (net.Conn, error) {
|
||||||
|
return d.d.Dial(network, address)
|
||||||
|
}
|
||||||
|
func (d defaultDialer) DialTimeout(network, address string, timeout time.Duration) (net.Conn, error) {
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
||||||
|
defer cancel()
|
||||||
|
return d.DialContext(ctx, network, address)
|
||||||
|
}
|
||||||
|
func (d defaultDialer) DialContext(ctx context.Context, network, address string) (net.Conn, error) {
|
||||||
|
return d.d.DialContext(ctx, network, address)
|
||||||
}
|
}
|
||||||
|
|
||||||
type conn struct {
|
type conn struct {
|
||||||
|
@ -244,90 +258,35 @@ func (cn *conn) writeBuf(b byte) *writeBuf {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Open opens a new connection to the database. name is a connection string.
|
// Open opens a new connection to the database. dsn is a connection string.
|
||||||
// Most users should only use it through database/sql package from the standard
|
// Most users should only use it through database/sql package from the standard
|
||||||
// library.
|
// library.
|
||||||
func Open(name string) (_ driver.Conn, err error) {
|
func Open(dsn string) (_ driver.Conn, err error) {
|
||||||
return DialOpen(defaultDialer{}, name)
|
return DialOpen(defaultDialer{}, dsn)
|
||||||
}
|
}
|
||||||
|
|
||||||
// DialOpen opens a new connection to the database using a dialer.
|
// DialOpen opens a new connection to the database using a dialer.
|
||||||
func DialOpen(d Dialer, name string) (_ driver.Conn, err error) {
|
func DialOpen(d Dialer, dsn string) (_ driver.Conn, err error) {
|
||||||
|
c, err := NewConnector(dsn)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
c.dialer = d
|
||||||
|
return c.open(context.Background())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Connector) open(ctx context.Context) (cn *conn, err error) {
|
||||||
// Handle any panics during connection initialization. Note that we
|
// Handle any panics during connection initialization. Note that we
|
||||||
// specifically do *not* want to use errRecover(), as that would turn any
|
// specifically do *not* want to use errRecover(), as that would turn any
|
||||||
// connection errors into ErrBadConns, hiding the real error message from
|
// connection errors into ErrBadConns, hiding the real error message from
|
||||||
// the user.
|
// the user.
|
||||||
defer errRecoverNoErrBadConn(&err)
|
defer errRecoverNoErrBadConn(&err)
|
||||||
|
|
||||||
o := make(values)
|
o := c.opts
|
||||||
|
|
||||||
// A number of defaults are applied here, in this order:
|
cn = &conn{
|
||||||
//
|
|
||||||
// * Very low precedence defaults applied in every situation
|
|
||||||
// * Environment variables
|
|
||||||
// * Explicitly passed connection information
|
|
||||||
o["host"] = "localhost"
|
|
||||||
o["port"] = "5432"
|
|
||||||
// N.B.: Extra float digits should be set to 3, but that breaks
|
|
||||||
// Postgres 8.4 and older, where the max is 2.
|
|
||||||
o["extra_float_digits"] = "2"
|
|
||||||
for k, v := range parseEnviron(os.Environ()) {
|
|
||||||
o[k] = v
|
|
||||||
}
|
|
||||||
|
|
||||||
if strings.HasPrefix(name, "postgres://") || strings.HasPrefix(name, "postgresql://") {
|
|
||||||
name, err = ParseURL(name)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := parseOpts(name, o); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Use the "fallback" application name if necessary
|
|
||||||
if fallback, ok := o["fallback_application_name"]; ok {
|
|
||||||
if _, ok := o["application_name"]; !ok {
|
|
||||||
o["application_name"] = fallback
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// We can't work with any client_encoding other than UTF-8 currently.
|
|
||||||
// However, we have historically allowed the user to set it to UTF-8
|
|
||||||
// explicitly, and there's no reason to break such programs, so allow that.
|
|
||||||
// Note that the "options" setting could also set client_encoding, but
|
|
||||||
// parsing its value is not worth it. Instead, we always explicitly send
|
|
||||||
// client_encoding as a separate run-time parameter, which should override
|
|
||||||
// anything set in options.
|
|
||||||
if enc, ok := o["client_encoding"]; ok && !isUTF8(enc) {
|
|
||||||
return nil, errors.New("client_encoding must be absent or 'UTF8'")
|
|
||||||
}
|
|
||||||
o["client_encoding"] = "UTF8"
|
|
||||||
// DateStyle needs a similar treatment.
|
|
||||||
if datestyle, ok := o["datestyle"]; ok {
|
|
||||||
if datestyle != "ISO, MDY" {
|
|
||||||
panic(fmt.Sprintf("setting datestyle must be absent or %v; got %v",
|
|
||||||
"ISO, MDY", datestyle))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
o["datestyle"] = "ISO, MDY"
|
|
||||||
}
|
|
||||||
|
|
||||||
// If a user is not provided by any other means, the last
|
|
||||||
// resort is to use the current operating system provided user
|
|
||||||
// name.
|
|
||||||
if _, ok := o["user"]; !ok {
|
|
||||||
u, err := userCurrent()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
o["user"] = u
|
|
||||||
}
|
|
||||||
|
|
||||||
cn := &conn{
|
|
||||||
opts: o,
|
opts: o,
|
||||||
dialer: d,
|
dialer: c.dialer,
|
||||||
}
|
}
|
||||||
err = cn.handleDriverSettings(o)
|
err = cn.handleDriverSettings(o)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -335,13 +294,16 @@ func DialOpen(d Dialer, name string) (_ driver.Conn, err error) {
|
||||||
}
|
}
|
||||||
cn.handlePgpass(o)
|
cn.handlePgpass(o)
|
||||||
|
|
||||||
cn.c, err = dial(d, o)
|
cn.c, err = dial(ctx, c.dialer, o)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = cn.ssl(o)
|
err = cn.ssl(o)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if cn.c != nil {
|
||||||
|
cn.c.Close()
|
||||||
|
}
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -364,10 +326,10 @@ func DialOpen(d Dialer, name string) (_ driver.Conn, err error) {
|
||||||
return cn, err
|
return cn, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func dial(d Dialer, o values) (net.Conn, error) {
|
func dial(ctx context.Context, d Dialer, o values) (net.Conn, error) {
|
||||||
ntw, addr := network(o)
|
network, address := network(o)
|
||||||
// SSL is not necessary or supported over UNIX domain sockets
|
// SSL is not necessary or supported over UNIX domain sockets
|
||||||
if ntw == "unix" {
|
if network == "unix" {
|
||||||
o["sslmode"] = "disable"
|
o["sslmode"] = "disable"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -378,19 +340,30 @@ func dial(d Dialer, o values) (net.Conn, error) {
|
||||||
return nil, fmt.Errorf("invalid value for parameter connect_timeout: %s", err)
|
return nil, fmt.Errorf("invalid value for parameter connect_timeout: %s", err)
|
||||||
}
|
}
|
||||||
duration := time.Duration(seconds) * time.Second
|
duration := time.Duration(seconds) * time.Second
|
||||||
|
|
||||||
// connect_timeout should apply to the entire connection establishment
|
// connect_timeout should apply to the entire connection establishment
|
||||||
// procedure, so we both use a timeout for the TCP connection
|
// procedure, so we both use a timeout for the TCP connection
|
||||||
// establishment and set a deadline for doing the initial handshake.
|
// establishment and set a deadline for doing the initial handshake.
|
||||||
// The deadline is then reset after startup() is done.
|
// The deadline is then reset after startup() is done.
|
||||||
deadline := time.Now().Add(duration)
|
deadline := time.Now().Add(duration)
|
||||||
conn, err := d.DialTimeout(ntw, addr, duration)
|
var conn net.Conn
|
||||||
|
if dctx, ok := d.(DialerContext); ok {
|
||||||
|
ctx, cancel := context.WithTimeout(ctx, duration)
|
||||||
|
defer cancel()
|
||||||
|
conn, err = dctx.DialContext(ctx, network, address)
|
||||||
|
} else {
|
||||||
|
conn, err = d.DialTimeout(network, address, duration)
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
err = conn.SetDeadline(deadline)
|
err = conn.SetDeadline(deadline)
|
||||||
return conn, err
|
return conn, err
|
||||||
}
|
}
|
||||||
return d.Dial(ntw, addr)
|
if dctx, ok := d.(DialerContext); ok {
|
||||||
|
return dctx.DialContext(ctx, network, address)
|
||||||
|
}
|
||||||
|
return d.Dial(network, address)
|
||||||
}
|
}
|
||||||
|
|
||||||
func network(o values) (string, string) {
|
func network(o values) (string, string) {
|
||||||
|
@ -704,7 +677,7 @@ func (cn *conn) simpleQuery(q string) (res *rows, err error) {
|
||||||
// res might be non-nil here if we received a previous
|
// res might be non-nil here if we received a previous
|
||||||
// CommandComplete, but that's fine; just overwrite it
|
// CommandComplete, but that's fine; just overwrite it
|
||||||
res = &rows{cn: cn}
|
res = &rows{cn: cn}
|
||||||
res.colNames, res.colFmts, res.colTyps = parsePortalRowDescribe(r)
|
res.rowsHeader = parsePortalRowDescribe(r)
|
||||||
|
|
||||||
// To work around a bug in QueryRow in Go 1.2 and earlier, wait
|
// To work around a bug in QueryRow in Go 1.2 and earlier, wait
|
||||||
// until the first DataRow has been received.
|
// until the first DataRow has been received.
|
||||||
|
@ -861,7 +834,7 @@ func (cn *conn) query(query string, args []driver.Value) (_ *rows, err error) {
|
||||||
cn.readParseResponse()
|
cn.readParseResponse()
|
||||||
cn.readBindResponse()
|
cn.readBindResponse()
|
||||||
rows := &rows{cn: cn}
|
rows := &rows{cn: cn}
|
||||||
rows.colNames, rows.colFmts, rows.colTyps = cn.readPortalDescribeResponse()
|
rows.rowsHeader = cn.readPortalDescribeResponse()
|
||||||
cn.postExecuteWorkaround()
|
cn.postExecuteWorkaround()
|
||||||
return rows, nil
|
return rows, nil
|
||||||
}
|
}
|
||||||
|
@ -869,9 +842,7 @@ func (cn *conn) query(query string, args []driver.Value) (_ *rows, err error) {
|
||||||
st.exec(args)
|
st.exec(args)
|
||||||
return &rows{
|
return &rows{
|
||||||
cn: cn,
|
cn: cn,
|
||||||
colNames: st.colNames,
|
rowsHeader: st.rowsHeader,
|
||||||
colTyps: st.colTyps,
|
|
||||||
colFmts: st.colFmts,
|
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -992,7 +963,6 @@ func (cn *conn) recv() (t byte, r *readBuf) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
switch t {
|
switch t {
|
||||||
case 'E':
|
case 'E':
|
||||||
panic(parseError(r))
|
panic(parseError(r))
|
||||||
|
@ -1163,6 +1133,55 @@ func (cn *conn) auth(r *readBuf, o values) {
|
||||||
if r.int32() != 0 {
|
if r.int32() != 0 {
|
||||||
errorf("unexpected authentication response: %q", t)
|
errorf("unexpected authentication response: %q", t)
|
||||||
}
|
}
|
||||||
|
case 10:
|
||||||
|
sc := scram.NewClient(sha256.New, o["user"], o["password"])
|
||||||
|
sc.Step(nil)
|
||||||
|
if sc.Err() != nil {
|
||||||
|
errorf("SCRAM-SHA-256 error: %s", sc.Err().Error())
|
||||||
|
}
|
||||||
|
scOut := sc.Out()
|
||||||
|
|
||||||
|
w := cn.writeBuf('p')
|
||||||
|
w.string("SCRAM-SHA-256")
|
||||||
|
w.int32(len(scOut))
|
||||||
|
w.bytes(scOut)
|
||||||
|
cn.send(w)
|
||||||
|
|
||||||
|
t, r := cn.recv()
|
||||||
|
if t != 'R' {
|
||||||
|
errorf("unexpected password response: %q", t)
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.int32() != 11 {
|
||||||
|
errorf("unexpected authentication response: %q", t)
|
||||||
|
}
|
||||||
|
|
||||||
|
nextStep := r.next(len(*r))
|
||||||
|
sc.Step(nextStep)
|
||||||
|
if sc.Err() != nil {
|
||||||
|
errorf("SCRAM-SHA-256 error: %s", sc.Err().Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
scOut = sc.Out()
|
||||||
|
w = cn.writeBuf('p')
|
||||||
|
w.bytes(scOut)
|
||||||
|
cn.send(w)
|
||||||
|
|
||||||
|
t, r = cn.recv()
|
||||||
|
if t != 'R' {
|
||||||
|
errorf("unexpected password response: %q", t)
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.int32() != 12 {
|
||||||
|
errorf("unexpected authentication response: %q", t)
|
||||||
|
}
|
||||||
|
|
||||||
|
nextStep = r.next(len(*r))
|
||||||
|
sc.Step(nextStep)
|
||||||
|
if sc.Err() != nil {
|
||||||
|
errorf("SCRAM-SHA-256 error: %s", sc.Err().Error())
|
||||||
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
errorf("unknown authentication response: %d", code)
|
errorf("unknown authentication response: %d", code)
|
||||||
}
|
}
|
||||||
|
@ -1182,10 +1201,8 @@ var colFmtDataAllText = []byte{0, 0}
|
||||||
type stmt struct {
|
type stmt struct {
|
||||||
cn *conn
|
cn *conn
|
||||||
name string
|
name string
|
||||||
colNames []string
|
rowsHeader
|
||||||
colFmts []format
|
|
||||||
colFmtData []byte
|
colFmtData []byte
|
||||||
colTyps []fieldDesc
|
|
||||||
paramTyps []oid.Oid
|
paramTyps []oid.Oid
|
||||||
closed bool
|
closed bool
|
||||||
}
|
}
|
||||||
|
@ -1232,9 +1249,7 @@ func (st *stmt) Query(v []driver.Value) (r driver.Rows, err error) {
|
||||||
st.exec(v)
|
st.exec(v)
|
||||||
return &rows{
|
return &rows{
|
||||||
cn: st.cn,
|
cn: st.cn,
|
||||||
colNames: st.colNames,
|
rowsHeader: st.rowsHeader,
|
||||||
colTyps: st.colTyps,
|
|
||||||
colFmts: st.colFmts,
|
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1344,16 +1359,22 @@ func (cn *conn) parseComplete(commandTag string) (driver.Result, string) {
|
||||||
return driver.RowsAffected(n), commandTag
|
return driver.RowsAffected(n), commandTag
|
||||||
}
|
}
|
||||||
|
|
||||||
type rows struct {
|
type rowsHeader struct {
|
||||||
cn *conn
|
|
||||||
finish func()
|
|
||||||
colNames []string
|
colNames []string
|
||||||
colTyps []fieldDesc
|
colTyps []fieldDesc
|
||||||
colFmts []format
|
colFmts []format
|
||||||
|
}
|
||||||
|
|
||||||
|
type rows struct {
|
||||||
|
cn *conn
|
||||||
|
finish func()
|
||||||
|
rowsHeader
|
||||||
done bool
|
done bool
|
||||||
rb readBuf
|
rb readBuf
|
||||||
result driver.Result
|
result driver.Result
|
||||||
tag string
|
tag string
|
||||||
|
|
||||||
|
next *rowsHeader
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rs *rows) Close() error {
|
func (rs *rows) Close() error {
|
||||||
|
@ -1440,7 +1461,8 @@ func (rs *rows) Next(dest []driver.Value) (err error) {
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
case 'T':
|
case 'T':
|
||||||
rs.colNames, rs.colFmts, rs.colTyps = parsePortalRowDescribe(&rs.rb)
|
next := parsePortalRowDescribe(&rs.rb)
|
||||||
|
rs.next = &next
|
||||||
return io.EOF
|
return io.EOF
|
||||||
default:
|
default:
|
||||||
errorf("unexpected message after execute: %q", t)
|
errorf("unexpected message after execute: %q", t)
|
||||||
|
@ -1449,10 +1471,16 @@ func (rs *rows) Next(dest []driver.Value) (err error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rs *rows) HasNextResultSet() bool {
|
func (rs *rows) HasNextResultSet() bool {
|
||||||
return !rs.done
|
hasNext := rs.next != nil && !rs.done
|
||||||
|
return hasNext
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rs *rows) NextResultSet() error {
|
func (rs *rows) NextResultSet() error {
|
||||||
|
if rs.next == nil {
|
||||||
|
return io.EOF
|
||||||
|
}
|
||||||
|
rs.rowsHeader = *rs.next
|
||||||
|
rs.next = nil
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1475,6 +1503,39 @@ func QuoteIdentifier(name string) string {
|
||||||
return `"` + strings.Replace(name, `"`, `""`, -1) + `"`
|
return `"` + strings.Replace(name, `"`, `""`, -1) + `"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// QuoteLiteral quotes a 'literal' (e.g. a parameter, often used to pass literal
|
||||||
|
// to DDL and other statements that do not accept parameters) to be used as part
|
||||||
|
// of an SQL statement. For example:
|
||||||
|
//
|
||||||
|
// exp_date := pq.QuoteLiteral("2023-01-05 15:00:00Z")
|
||||||
|
// err := db.Exec(fmt.Sprintf("CREATE ROLE my_user VALID UNTIL %s", exp_date))
|
||||||
|
//
|
||||||
|
// Any single quotes in name will be escaped. Any backslashes (i.e. "\") will be
|
||||||
|
// replaced by two backslashes (i.e. "\\") and the C-style escape identifier
|
||||||
|
// that PostgreSQL provides ('E') will be prepended to the string.
|
||||||
|
func QuoteLiteral(literal string) string {
|
||||||
|
// This follows the PostgreSQL internal algorithm for handling quoted literals
|
||||||
|
// from libpq, which can be found in the "PQEscapeStringInternal" function,
|
||||||
|
// which is found in the libpq/fe-exec.c source file:
|
||||||
|
// https://git.postgresql.org/gitweb/?p=postgresql.git;a=blob;f=src/interfaces/libpq/fe-exec.c
|
||||||
|
//
|
||||||
|
// substitute any single-quotes (') with two single-quotes ('')
|
||||||
|
literal = strings.Replace(literal, `'`, `''`, -1)
|
||||||
|
// determine if the string has any backslashes (\) in it.
|
||||||
|
// if it does, replace any backslashes (\) with two backslashes (\\)
|
||||||
|
// then, we need to wrap the entire string with a PostgreSQL
|
||||||
|
// C-style escape. Per how "PQEscapeStringInternal" handles this case, we
|
||||||
|
// also add a space before the "E"
|
||||||
|
if strings.Contains(literal, `\`) {
|
||||||
|
literal = strings.Replace(literal, `\`, `\\`, -1)
|
||||||
|
literal = ` E'` + literal + `'`
|
||||||
|
} else {
|
||||||
|
// otherwise, we can just wrap the literal with a pair of single quotes
|
||||||
|
literal = `'` + literal + `'`
|
||||||
|
}
|
||||||
|
return literal
|
||||||
|
}
|
||||||
|
|
||||||
func md5s(s string) string {
|
func md5s(s string) string {
|
||||||
h := md5.New()
|
h := md5.New()
|
||||||
h.Write([]byte(s))
|
h.Write([]byte(s))
|
||||||
|
@ -1630,13 +1691,13 @@ func (cn *conn) readStatementDescribeResponse() (paramTyps []oid.Oid, colNames [
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cn *conn) readPortalDescribeResponse() (colNames []string, colFmts []format, colTyps []fieldDesc) {
|
func (cn *conn) readPortalDescribeResponse() rowsHeader {
|
||||||
t, r := cn.recv1()
|
t, r := cn.recv1()
|
||||||
switch t {
|
switch t {
|
||||||
case 'T':
|
case 'T':
|
||||||
return parsePortalRowDescribe(r)
|
return parsePortalRowDescribe(r)
|
||||||
case 'n':
|
case 'n':
|
||||||
return nil, nil, nil
|
return rowsHeader{}
|
||||||
case 'E':
|
case 'E':
|
||||||
err := parseError(r)
|
err := parseError(r)
|
||||||
cn.readReadyForQuery()
|
cn.readReadyForQuery()
|
||||||
|
@ -1742,11 +1803,11 @@ func parseStatementRowDescribe(r *readBuf) (colNames []string, colTyps []fieldDe
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func parsePortalRowDescribe(r *readBuf) (colNames []string, colFmts []format, colTyps []fieldDesc) {
|
func parsePortalRowDescribe(r *readBuf) rowsHeader {
|
||||||
n := r.int16()
|
n := r.int16()
|
||||||
colNames = make([]string, n)
|
colNames := make([]string, n)
|
||||||
colFmts = make([]format, n)
|
colFmts := make([]format, n)
|
||||||
colTyps = make([]fieldDesc, n)
|
colTyps := make([]fieldDesc, n)
|
||||||
for i := range colNames {
|
for i := range colNames {
|
||||||
colNames[i] = r.string()
|
colNames[i] = r.string()
|
||||||
r.next(6)
|
r.next(6)
|
||||||
|
@ -1755,7 +1816,11 @@ func parsePortalRowDescribe(r *readBuf) (colNames []string, colFmts []format, co
|
||||||
colTyps[i].Mod = r.int32()
|
colTyps[i].Mod = r.int32()
|
||||||
colFmts[i] = format(r.int16())
|
colFmts[i] = format(r.int16())
|
||||||
}
|
}
|
||||||
return
|
return rowsHeader{
|
||||||
|
colNames: colNames,
|
||||||
|
colFmts: colFmts,
|
||||||
|
colTyps: colTyps,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// parseEnviron tries to mimic some of libpq's environment handling
|
// parseEnviron tries to mimic some of libpq's environment handling
|
||||||
|
|
28
vendor/github.com/lib/pq/conn_go18.go
generated
vendored
28
vendor/github.com/lib/pq/conn_go18.go
generated
vendored
|
@ -1,5 +1,3 @@
|
||||||
// +build go1.8
|
|
||||||
|
|
||||||
package pq
|
package pq
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
@ -9,6 +7,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Implement the "QueryerContext" interface
|
// Implement the "QueryerContext" interface
|
||||||
|
@ -76,13 +75,32 @@ func (cn *conn) BeginTx(ctx context.Context, opts driver.TxOptions) (driver.Tx,
|
||||||
return tx, nil
|
return tx, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (cn *conn) Ping(ctx context.Context) error {
|
||||||
|
if finish := cn.watchCancel(ctx); finish != nil {
|
||||||
|
defer finish()
|
||||||
|
}
|
||||||
|
rows, err := cn.simpleQuery("SELECT 'lib/pq ping test';")
|
||||||
|
if err != nil {
|
||||||
|
return driver.ErrBadConn // https://golang.org/pkg/database/sql/driver/#Pinger
|
||||||
|
}
|
||||||
|
rows.Close()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (cn *conn) watchCancel(ctx context.Context) func() {
|
func (cn *conn) watchCancel(ctx context.Context) func() {
|
||||||
if done := ctx.Done(); done != nil {
|
if done := ctx.Done(); done != nil {
|
||||||
finished := make(chan struct{})
|
finished := make(chan struct{})
|
||||||
go func() {
|
go func() {
|
||||||
select {
|
select {
|
||||||
case <-done:
|
case <-done:
|
||||||
_ = cn.cancel()
|
// At this point the function level context is canceled,
|
||||||
|
// so it must not be used for the additional network
|
||||||
|
// request to cancel the query.
|
||||||
|
// Create a new context to pass into the dial.
|
||||||
|
ctxCancel, cancel := context.WithTimeout(context.Background(), time.Second*10)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
_ = cn.cancel(ctxCancel)
|
||||||
finished <- struct{}{}
|
finished <- struct{}{}
|
||||||
case <-finished:
|
case <-finished:
|
||||||
}
|
}
|
||||||
|
@ -97,8 +115,8 @@ func (cn *conn) watchCancel(ctx context.Context) func() {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cn *conn) cancel() error {
|
func (cn *conn) cancel(ctx context.Context) error {
|
||||||
c, err := dial(cn.dialer, cn.opts)
|
c, err := dial(ctx, cn.dialer, cn.opts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
1741
vendor/github.com/lib/pq/conn_test.go
generated
vendored
Normal file
1741
vendor/github.com/lib/pq/conn_test.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load diff
91
vendor/github.com/lib/pq/connector.go
generated
vendored
91
vendor/github.com/lib/pq/connector.go
generated
vendored
|
@ -1,10 +1,12 @@
|
||||||
// +build go1.10
|
|
||||||
|
|
||||||
package pq
|
package pq
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"database/sql/driver"
|
"database/sql/driver"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Connector represents a fixed configuration for the pq driver with a given
|
// Connector represents a fixed configuration for the pq driver with a given
|
||||||
|
@ -14,30 +16,95 @@ import (
|
||||||
//
|
//
|
||||||
// See https://golang.org/pkg/database/sql/driver/#Connector.
|
// See https://golang.org/pkg/database/sql/driver/#Connector.
|
||||||
// See https://golang.org/pkg/database/sql/#OpenDB.
|
// See https://golang.org/pkg/database/sql/#OpenDB.
|
||||||
type connector struct {
|
type Connector struct {
|
||||||
name string
|
opts values
|
||||||
|
dialer Dialer
|
||||||
}
|
}
|
||||||
|
|
||||||
// Connect returns a connection to the database using the fixed configuration
|
// Connect returns a connection to the database using the fixed configuration
|
||||||
// of this Connector. Context is not used.
|
// of this Connector. Context is not used.
|
||||||
func (c *connector) Connect(_ context.Context) (driver.Conn, error) {
|
func (c *Connector) Connect(ctx context.Context) (driver.Conn, error) {
|
||||||
return (&Driver{}).Open(c.name)
|
return c.open(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Driver returnst the underlying driver of this Connector.
|
// Driver returnst the underlying driver of this Connector.
|
||||||
func (c *connector) Driver() driver.Driver {
|
func (c *Connector) Driver() driver.Driver {
|
||||||
return &Driver{}
|
return &Driver{}
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ driver.Connector = &connector{}
|
|
||||||
|
|
||||||
// NewConnector returns a connector for the pq driver in a fixed configuration
|
// NewConnector returns a connector for the pq driver in a fixed configuration
|
||||||
// with the given name. The returned connector can be used to create any number
|
// with the given dsn. The returned connector can be used to create any number
|
||||||
// of equivalent Conn's. The returned connector is intended to be used with
|
// of equivalent Conn's. The returned connector is intended to be used with
|
||||||
// database/sql.OpenDB.
|
// database/sql.OpenDB.
|
||||||
//
|
//
|
||||||
// See https://golang.org/pkg/database/sql/driver/#Connector.
|
// See https://golang.org/pkg/database/sql/driver/#Connector.
|
||||||
// See https://golang.org/pkg/database/sql/#OpenDB.
|
// See https://golang.org/pkg/database/sql/#OpenDB.
|
||||||
func NewConnector(name string) (driver.Connector, error) {
|
func NewConnector(dsn string) (*Connector, error) {
|
||||||
return &connector{name: name}, nil
|
var err error
|
||||||
|
o := make(values)
|
||||||
|
|
||||||
|
// A number of defaults are applied here, in this order:
|
||||||
|
//
|
||||||
|
// * Very low precedence defaults applied in every situation
|
||||||
|
// * Environment variables
|
||||||
|
// * Explicitly passed connection information
|
||||||
|
o["host"] = "localhost"
|
||||||
|
o["port"] = "5432"
|
||||||
|
// N.B.: Extra float digits should be set to 3, but that breaks
|
||||||
|
// Postgres 8.4 and older, where the max is 2.
|
||||||
|
o["extra_float_digits"] = "2"
|
||||||
|
for k, v := range parseEnviron(os.Environ()) {
|
||||||
|
o[k] = v
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.HasPrefix(dsn, "postgres://") || strings.HasPrefix(dsn, "postgresql://") {
|
||||||
|
dsn, err = ParseURL(dsn)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := parseOpts(dsn, o); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use the "fallback" application name if necessary
|
||||||
|
if fallback, ok := o["fallback_application_name"]; ok {
|
||||||
|
if _, ok := o["application_name"]; !ok {
|
||||||
|
o["application_name"] = fallback
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// We can't work with any client_encoding other than UTF-8 currently.
|
||||||
|
// However, we have historically allowed the user to set it to UTF-8
|
||||||
|
// explicitly, and there's no reason to break such programs, so allow that.
|
||||||
|
// Note that the "options" setting could also set client_encoding, but
|
||||||
|
// parsing its value is not worth it. Instead, we always explicitly send
|
||||||
|
// client_encoding as a separate run-time parameter, which should override
|
||||||
|
// anything set in options.
|
||||||
|
if enc, ok := o["client_encoding"]; ok && !isUTF8(enc) {
|
||||||
|
return nil, errors.New("client_encoding must be absent or 'UTF8'")
|
||||||
|
}
|
||||||
|
o["client_encoding"] = "UTF8"
|
||||||
|
// DateStyle needs a similar treatment.
|
||||||
|
if datestyle, ok := o["datestyle"]; ok {
|
||||||
|
if datestyle != "ISO, MDY" {
|
||||||
|
return nil, fmt.Errorf("setting datestyle must be absent or %v; got %v", "ISO, MDY", datestyle)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
o["datestyle"] = "ISO, MDY"
|
||||||
|
}
|
||||||
|
|
||||||
|
// If a user is not provided by any other means, the last
|
||||||
|
// resort is to use the current operating system provided user
|
||||||
|
// name.
|
||||||
|
if _, ok := o["user"]; !ok {
|
||||||
|
u, err := userCurrent()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
o["user"] = u
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Connector{opts: o, dialer: defaultDialer{}}, nil
|
||||||
}
|
}
|
||||||
|
|
33
vendor/github.com/lib/pq/connector_example_test.go
generated
vendored
Normal file
33
vendor/github.com/lib/pq/connector_example_test.go
generated
vendored
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
// +build go1.10
|
||||||
|
|
||||||
|
package pq_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/lib/pq"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ExampleNewConnector() {
|
||||||
|
name := ""
|
||||||
|
connector, err := pq.NewConnector(name)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
db := sql.OpenDB(connector)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
// Use the DB
|
||||||
|
txn, err := db.Begin()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
txn.Rollback()
|
||||||
|
}
|
67
vendor/github.com/lib/pq/connector_test.go
generated
vendored
Normal file
67
vendor/github.com/lib/pq/connector_test.go
generated
vendored
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
// +build go1.10
|
||||||
|
|
||||||
|
package pq
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"database/sql"
|
||||||
|
"database/sql/driver"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestNewConnector_WorksWithOpenDB(t *testing.T) {
|
||||||
|
name := ""
|
||||||
|
c, err := NewConnector(name)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
db := sql.OpenDB(c)
|
||||||
|
defer db.Close()
|
||||||
|
// database/sql might not call our Open at all unless we do something with
|
||||||
|
// the connection
|
||||||
|
txn, err := db.Begin()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
txn.Rollback()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewConnector_Connect(t *testing.T) {
|
||||||
|
name := ""
|
||||||
|
c, err := NewConnector(name)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
db, err := c.Connect(context.Background())
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer db.Close()
|
||||||
|
// database/sql might not call our Open at all unless we do something with
|
||||||
|
// the connection
|
||||||
|
txn, err := db.(driver.ConnBeginTx).BeginTx(context.Background(), driver.TxOptions{})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
txn.Rollback()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewConnector_Driver(t *testing.T) {
|
||||||
|
name := ""
|
||||||
|
c, err := NewConnector(name)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
db, err := c.Driver().Open(name)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer db.Close()
|
||||||
|
// database/sql might not call our Open at all unless we do something with
|
||||||
|
// the connection
|
||||||
|
txn, err := db.(driver.ConnBeginTx).BeginTx(context.Background(), driver.TxOptions{})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
txn.Rollback()
|
||||||
|
}
|
468
vendor/github.com/lib/pq/copy_test.go
generated
vendored
Normal file
468
vendor/github.com/lib/pq/copy_test.go
generated
vendored
Normal file
|
@ -0,0 +1,468 @@
|
||||||
|
package pq
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"database/sql"
|
||||||
|
"database/sql/driver"
|
||||||
|
"net"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestCopyInStmt(t *testing.T) {
|
||||||
|
stmt := CopyIn("table name")
|
||||||
|
if stmt != `COPY "table name" () FROM STDIN` {
|
||||||
|
t.Fatal(stmt)
|
||||||
|
}
|
||||||
|
|
||||||
|
stmt = CopyIn("table name", "column 1", "column 2")
|
||||||
|
if stmt != `COPY "table name" ("column 1", "column 2") FROM STDIN` {
|
||||||
|
t.Fatal(stmt)
|
||||||
|
}
|
||||||
|
|
||||||
|
stmt = CopyIn(`table " name """`, `co"lumn""`)
|
||||||
|
if stmt != `COPY "table "" name """"""" ("co""lumn""""") FROM STDIN` {
|
||||||
|
t.Fatal(stmt)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCopyInSchemaStmt(t *testing.T) {
|
||||||
|
stmt := CopyInSchema("schema name", "table name")
|
||||||
|
if stmt != `COPY "schema name"."table name" () FROM STDIN` {
|
||||||
|
t.Fatal(stmt)
|
||||||
|
}
|
||||||
|
|
||||||
|
stmt = CopyInSchema("schema name", "table name", "column 1", "column 2")
|
||||||
|
if stmt != `COPY "schema name"."table name" ("column 1", "column 2") FROM STDIN` {
|
||||||
|
t.Fatal(stmt)
|
||||||
|
}
|
||||||
|
|
||||||
|
stmt = CopyInSchema(`schema " name """`, `table " name """`, `co"lumn""`)
|
||||||
|
if stmt != `COPY "schema "" name """"""".`+
|
||||||
|
`"table "" name """"""" ("co""lumn""""") FROM STDIN` {
|
||||||
|
t.Fatal(stmt)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCopyInMultipleValues(t *testing.T) {
|
||||||
|
db := openTestConn(t)
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
txn, err := db.Begin()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer txn.Rollback()
|
||||||
|
|
||||||
|
_, err = txn.Exec("CREATE TEMP TABLE temp (a int, b varchar)")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
stmt, err := txn.Prepare(CopyIn("temp", "a", "b"))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
longString := strings.Repeat("#", 500)
|
||||||
|
|
||||||
|
for i := 0; i < 500; i++ {
|
||||||
|
_, err = stmt.Exec(int64(i), longString)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = stmt.Exec()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = stmt.Close()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var num int
|
||||||
|
err = txn.QueryRow("SELECT COUNT(*) FROM temp").Scan(&num)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if num != 500 {
|
||||||
|
t.Fatalf("expected 500 items, not %d", num)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCopyInRaiseStmtTrigger(t *testing.T) {
|
||||||
|
db := openTestConn(t)
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
if getServerVersion(t, db) < 90000 {
|
||||||
|
var exists int
|
||||||
|
err := db.QueryRow("SELECT 1 FROM pg_language WHERE lanname = 'plpgsql'").Scan(&exists)
|
||||||
|
if err == sql.ErrNoRows {
|
||||||
|
t.Skip("language PL/PgSQL does not exist; skipping TestCopyInRaiseStmtTrigger")
|
||||||
|
} else if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
txn, err := db.Begin()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer txn.Rollback()
|
||||||
|
|
||||||
|
_, err = txn.Exec("CREATE TEMP TABLE temp (a int, b varchar)")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = txn.Exec(`
|
||||||
|
CREATE OR REPLACE FUNCTION pg_temp.temptest()
|
||||||
|
RETURNS trigger AS
|
||||||
|
$BODY$ begin
|
||||||
|
raise notice 'Hello world';
|
||||||
|
return new;
|
||||||
|
end $BODY$
|
||||||
|
LANGUAGE plpgsql`)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = txn.Exec(`
|
||||||
|
CREATE TRIGGER temptest_trigger
|
||||||
|
BEFORE INSERT
|
||||||
|
ON temp
|
||||||
|
FOR EACH ROW
|
||||||
|
EXECUTE PROCEDURE pg_temp.temptest()`)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
stmt, err := txn.Prepare(CopyIn("temp", "a", "b"))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
longString := strings.Repeat("#", 500)
|
||||||
|
|
||||||
|
_, err = stmt.Exec(int64(1), longString)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = stmt.Exec()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = stmt.Close()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var num int
|
||||||
|
err = txn.QueryRow("SELECT COUNT(*) FROM temp").Scan(&num)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if num != 1 {
|
||||||
|
t.Fatalf("expected 1 items, not %d", num)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCopyInTypes(t *testing.T) {
|
||||||
|
db := openTestConn(t)
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
txn, err := db.Begin()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer txn.Rollback()
|
||||||
|
|
||||||
|
_, err = txn.Exec("CREATE TEMP TABLE temp (num INTEGER, text VARCHAR, blob BYTEA, nothing VARCHAR)")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
stmt, err := txn.Prepare(CopyIn("temp", "num", "text", "blob", "nothing"))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = stmt.Exec(int64(1234567890), "Héllö\n ☃!\r\t\\", []byte{0, 255, 9, 10, 13}, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = stmt.Exec()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = stmt.Close()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var num int
|
||||||
|
var text string
|
||||||
|
var blob []byte
|
||||||
|
var nothing sql.NullString
|
||||||
|
|
||||||
|
err = txn.QueryRow("SELECT * FROM temp").Scan(&num, &text, &blob, ¬hing)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if num != 1234567890 {
|
||||||
|
t.Fatal("unexpected result", num)
|
||||||
|
}
|
||||||
|
if text != "Héllö\n ☃!\r\t\\" {
|
||||||
|
t.Fatal("unexpected result", text)
|
||||||
|
}
|
||||||
|
if !bytes.Equal(blob, []byte{0, 255, 9, 10, 13}) {
|
||||||
|
t.Fatal("unexpected result", blob)
|
||||||
|
}
|
||||||
|
if nothing.Valid {
|
||||||
|
t.Fatal("unexpected result", nothing.String)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCopyInWrongType(t *testing.T) {
|
||||||
|
db := openTestConn(t)
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
txn, err := db.Begin()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer txn.Rollback()
|
||||||
|
|
||||||
|
_, err = txn.Exec("CREATE TEMP TABLE temp (num INTEGER)")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
stmt, err := txn.Prepare(CopyIn("temp", "num"))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer stmt.Close()
|
||||||
|
|
||||||
|
_, err = stmt.Exec("Héllö\n ☃!\r\t\\")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = stmt.Exec()
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("expected error")
|
||||||
|
}
|
||||||
|
if pge := err.(*Error); pge.Code.Name() != "invalid_text_representation" {
|
||||||
|
t.Fatalf("expected 'invalid input syntax for integer' error, got %s (%+v)", pge.Code.Name(), pge)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCopyOutsideOfTxnError(t *testing.T) {
|
||||||
|
db := openTestConn(t)
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
_, err := db.Prepare(CopyIn("temp", "num"))
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("COPY outside of transaction did not return an error")
|
||||||
|
}
|
||||||
|
if err != errCopyNotSupportedOutsideTxn {
|
||||||
|
t.Fatalf("expected %s, got %s", err, err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCopyInBinaryError(t *testing.T) {
|
||||||
|
db := openTestConn(t)
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
txn, err := db.Begin()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer txn.Rollback()
|
||||||
|
|
||||||
|
_, err = txn.Exec("CREATE TEMP TABLE temp (num INTEGER)")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
_, err = txn.Prepare("COPY temp (num) FROM STDIN WITH binary")
|
||||||
|
if err != errBinaryCopyNotSupported {
|
||||||
|
t.Fatalf("expected %s, got %+v", errBinaryCopyNotSupported, err)
|
||||||
|
}
|
||||||
|
// check that the protocol is in a valid state
|
||||||
|
err = txn.Rollback()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCopyFromError(t *testing.T) {
|
||||||
|
db := openTestConn(t)
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
txn, err := db.Begin()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer txn.Rollback()
|
||||||
|
|
||||||
|
_, err = txn.Exec("CREATE TEMP TABLE temp (num INTEGER)")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
_, err = txn.Prepare("COPY temp (num) TO STDOUT")
|
||||||
|
if err != errCopyToNotSupported {
|
||||||
|
t.Fatalf("expected %s, got %+v", errCopyToNotSupported, err)
|
||||||
|
}
|
||||||
|
// check that the protocol is in a valid state
|
||||||
|
err = txn.Rollback()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCopySyntaxError(t *testing.T) {
|
||||||
|
db := openTestConn(t)
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
txn, err := db.Begin()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer txn.Rollback()
|
||||||
|
|
||||||
|
_, err = txn.Prepare("COPY ")
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("expected error")
|
||||||
|
}
|
||||||
|
if pge := err.(*Error); pge.Code.Name() != "syntax_error" {
|
||||||
|
t.Fatalf("expected syntax error, got %s (%+v)", pge.Code.Name(), pge)
|
||||||
|
}
|
||||||
|
// check that the protocol is in a valid state
|
||||||
|
err = txn.Rollback()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tests for connection errors in copyin.resploop()
|
||||||
|
func TestCopyRespLoopConnectionError(t *testing.T) {
|
||||||
|
db := openTestConn(t)
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
txn, err := db.Begin()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer txn.Rollback()
|
||||||
|
|
||||||
|
var pid int
|
||||||
|
err = txn.QueryRow("SELECT pg_backend_pid()").Scan(&pid)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = txn.Exec("CREATE TEMP TABLE temp (a int)")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
stmt, err := txn.Prepare(CopyIn("temp", "a"))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer stmt.Close()
|
||||||
|
|
||||||
|
_, err = db.Exec("SELECT pg_terminate_backend($1)", pid)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if getServerVersion(t, db) < 90500 {
|
||||||
|
// We have to try and send something over, since postgres before
|
||||||
|
// version 9.5 won't process SIGTERMs while it's waiting for
|
||||||
|
// CopyData/CopyEnd messages; see tcop/postgres.c.
|
||||||
|
_, err = stmt.Exec(1)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_, err = stmt.Exec()
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("expected error")
|
||||||
|
}
|
||||||
|
switch pge := err.(type) {
|
||||||
|
case *Error:
|
||||||
|
if pge.Code.Name() != "admin_shutdown" {
|
||||||
|
t.Fatalf("expected admin_shutdown, got %s", pge.Code.Name())
|
||||||
|
}
|
||||||
|
case *net.OpError:
|
||||||
|
// ignore
|
||||||
|
default:
|
||||||
|
if err == driver.ErrBadConn {
|
||||||
|
// likely an EPIPE
|
||||||
|
} else {
|
||||||
|
t.Fatalf("unexpected error, got %+#v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_ = stmt.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkCopyIn(b *testing.B) {
|
||||||
|
db := openTestConn(b)
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
txn, err := db.Begin()
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
defer txn.Rollback()
|
||||||
|
|
||||||
|
_, err = txn.Exec("CREATE TEMP TABLE temp (a int, b varchar)")
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
stmt, err := txn.Prepare(CopyIn("temp", "a", "b"))
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
_, err = stmt.Exec(int64(i), "hello world!")
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = stmt.Exec()
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = stmt.Close()
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var num int
|
||||||
|
err = txn.QueryRow("SELECT COUNT(*) FROM temp").Scan(&num)
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if num != b.N {
|
||||||
|
b.Fatalf("expected %d items, not %d", b.N, num)
|
||||||
|
}
|
||||||
|
}
|
2
vendor/github.com/lib/pq/doc.go
generated
vendored
2
vendor/github.com/lib/pq/doc.go
generated
vendored
|
@ -239,7 +239,7 @@ for more information). Note that the channel name will be truncated to 63
|
||||||
bytes by the PostgreSQL server.
|
bytes by the PostgreSQL server.
|
||||||
|
|
||||||
You can find a complete, working example of Listener usage at
|
You can find a complete, working example of Listener usage at
|
||||||
http://godoc.org/github.com/lib/pq/example/listen.
|
https://godoc.org/github.com/lib/pq/example/listen.
|
||||||
|
|
||||||
*/
|
*/
|
||||||
package pq
|
package pq
|
||||||
|
|
9
vendor/github.com/lib/pq/encode.go
generated
vendored
9
vendor/github.com/lib/pq/encode.go
generated
vendored
|
@ -117,11 +117,10 @@ func textDecode(parameterStatus *parameterStatus, s []byte, typ oid.Oid) interfa
|
||||||
}
|
}
|
||||||
return i
|
return i
|
||||||
case oid.T_float4, oid.T_float8:
|
case oid.T_float4, oid.T_float8:
|
||||||
bits := 64
|
// We always use 64 bit parsing, regardless of whether the input text is for
|
||||||
if typ == oid.T_float4 {
|
// a float4 or float8, because clients expect float64s for all float datatypes
|
||||||
bits = 32
|
// and returning a 32-bit parsed float64 produces lossy results.
|
||||||
}
|
f, err := strconv.ParseFloat(string(s), 64)
|
||||||
f, err := strconv.ParseFloat(string(s), bits)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errorf("%s", err)
|
errorf("%s", err)
|
||||||
}
|
}
|
||||||
|
|
766
vendor/github.com/lib/pq/encode_test.go
generated
vendored
Normal file
766
vendor/github.com/lib/pq/encode_test.go
generated
vendored
Normal file
|
@ -0,0 +1,766 @@
|
||||||
|
package pq
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"database/sql"
|
||||||
|
"fmt"
|
||||||
|
"regexp"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/lib/pq/oid"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestScanTimestamp(t *testing.T) {
|
||||||
|
var nt NullTime
|
||||||
|
tn := time.Now()
|
||||||
|
nt.Scan(tn)
|
||||||
|
if !nt.Valid {
|
||||||
|
t.Errorf("Expected Valid=false")
|
||||||
|
}
|
||||||
|
if nt.Time != tn {
|
||||||
|
t.Errorf("Time value mismatch")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestScanNilTimestamp(t *testing.T) {
|
||||||
|
var nt NullTime
|
||||||
|
nt.Scan(nil)
|
||||||
|
if nt.Valid {
|
||||||
|
t.Errorf("Expected Valid=false")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var timeTests = []struct {
|
||||||
|
str string
|
||||||
|
timeval time.Time
|
||||||
|
}{
|
||||||
|
{"22001-02-03", time.Date(22001, time.February, 3, 0, 0, 0, 0, time.FixedZone("", 0))},
|
||||||
|
{"2001-02-03", time.Date(2001, time.February, 3, 0, 0, 0, 0, time.FixedZone("", 0))},
|
||||||
|
{"0001-12-31 BC", time.Date(0, time.December, 31, 0, 0, 0, 0, time.FixedZone("", 0))},
|
||||||
|
{"2001-02-03 BC", time.Date(-2000, time.February, 3, 0, 0, 0, 0, time.FixedZone("", 0))},
|
||||||
|
{"2001-02-03 04:05:06", time.Date(2001, time.February, 3, 4, 5, 6, 0, time.FixedZone("", 0))},
|
||||||
|
{"2001-02-03 04:05:06.000001", time.Date(2001, time.February, 3, 4, 5, 6, 1000, time.FixedZone("", 0))},
|
||||||
|
{"2001-02-03 04:05:06.00001", time.Date(2001, time.February, 3, 4, 5, 6, 10000, time.FixedZone("", 0))},
|
||||||
|
{"2001-02-03 04:05:06.0001", time.Date(2001, time.February, 3, 4, 5, 6, 100000, time.FixedZone("", 0))},
|
||||||
|
{"2001-02-03 04:05:06.001", time.Date(2001, time.February, 3, 4, 5, 6, 1000000, time.FixedZone("", 0))},
|
||||||
|
{"2001-02-03 04:05:06.01", time.Date(2001, time.February, 3, 4, 5, 6, 10000000, time.FixedZone("", 0))},
|
||||||
|
{"2001-02-03 04:05:06.1", time.Date(2001, time.February, 3, 4, 5, 6, 100000000, time.FixedZone("", 0))},
|
||||||
|
{"2001-02-03 04:05:06.12", time.Date(2001, time.February, 3, 4, 5, 6, 120000000, time.FixedZone("", 0))},
|
||||||
|
{"2001-02-03 04:05:06.123", time.Date(2001, time.February, 3, 4, 5, 6, 123000000, time.FixedZone("", 0))},
|
||||||
|
{"2001-02-03 04:05:06.1234", time.Date(2001, time.February, 3, 4, 5, 6, 123400000, time.FixedZone("", 0))},
|
||||||
|
{"2001-02-03 04:05:06.12345", time.Date(2001, time.February, 3, 4, 5, 6, 123450000, time.FixedZone("", 0))},
|
||||||
|
{"2001-02-03 04:05:06.123456", time.Date(2001, time.February, 3, 4, 5, 6, 123456000, time.FixedZone("", 0))},
|
||||||
|
{"2001-02-03 04:05:06.123-07", time.Date(2001, time.February, 3, 4, 5, 6, 123000000,
|
||||||
|
time.FixedZone("", -7*60*60))},
|
||||||
|
{"2001-02-03 04:05:06-07", time.Date(2001, time.February, 3, 4, 5, 6, 0,
|
||||||
|
time.FixedZone("", -7*60*60))},
|
||||||
|
{"2001-02-03 04:05:06-07:42", time.Date(2001, time.February, 3, 4, 5, 6, 0,
|
||||||
|
time.FixedZone("", -(7*60*60+42*60)))},
|
||||||
|
{"2001-02-03 04:05:06-07:30:09", time.Date(2001, time.February, 3, 4, 5, 6, 0,
|
||||||
|
time.FixedZone("", -(7*60*60+30*60+9)))},
|
||||||
|
{"2001-02-03 04:05:06+07", time.Date(2001, time.February, 3, 4, 5, 6, 0,
|
||||||
|
time.FixedZone("", 7*60*60))},
|
||||||
|
{"0011-02-03 04:05:06 BC", time.Date(-10, time.February, 3, 4, 5, 6, 0, time.FixedZone("", 0))},
|
||||||
|
{"0011-02-03 04:05:06.123 BC", time.Date(-10, time.February, 3, 4, 5, 6, 123000000, time.FixedZone("", 0))},
|
||||||
|
{"0011-02-03 04:05:06.123-07 BC", time.Date(-10, time.February, 3, 4, 5, 6, 123000000,
|
||||||
|
time.FixedZone("", -7*60*60))},
|
||||||
|
{"0001-02-03 04:05:06.123", time.Date(1, time.February, 3, 4, 5, 6, 123000000, time.FixedZone("", 0))},
|
||||||
|
{"0001-02-03 04:05:06.123 BC", time.Date(1, time.February, 3, 4, 5, 6, 123000000, time.FixedZone("", 0)).AddDate(-1, 0, 0)},
|
||||||
|
{"0001-02-03 04:05:06.123 BC", time.Date(0, time.February, 3, 4, 5, 6, 123000000, time.FixedZone("", 0))},
|
||||||
|
{"0002-02-03 04:05:06.123 BC", time.Date(0, time.February, 3, 4, 5, 6, 123000000, time.FixedZone("", 0)).AddDate(-1, 0, 0)},
|
||||||
|
{"0002-02-03 04:05:06.123 BC", time.Date(-1, time.February, 3, 4, 5, 6, 123000000, time.FixedZone("", 0))},
|
||||||
|
{"12345-02-03 04:05:06.1", time.Date(12345, time.February, 3, 4, 5, 6, 100000000, time.FixedZone("", 0))},
|
||||||
|
{"123456-02-03 04:05:06.1", time.Date(123456, time.February, 3, 4, 5, 6, 100000000, time.FixedZone("", 0))},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test that parsing the string results in the expected value.
|
||||||
|
func TestParseTs(t *testing.T) {
|
||||||
|
for i, tt := range timeTests {
|
||||||
|
val, err := ParseTimestamp(nil, tt.str)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("%d: got error: %v", i, err)
|
||||||
|
} else if val.String() != tt.timeval.String() {
|
||||||
|
t.Errorf("%d: expected to parse %q into %q; got %q",
|
||||||
|
i, tt.str, tt.timeval, val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var timeErrorTests = []string{
|
||||||
|
"BC",
|
||||||
|
" BC",
|
||||||
|
"2001",
|
||||||
|
"2001-2-03",
|
||||||
|
"2001-02-3",
|
||||||
|
"2001-02-03 ",
|
||||||
|
"2001-02-03 B",
|
||||||
|
"2001-02-03 04",
|
||||||
|
"2001-02-03 04:",
|
||||||
|
"2001-02-03 04:05",
|
||||||
|
"2001-02-03 04:05 B",
|
||||||
|
"2001-02-03 04:05 BC",
|
||||||
|
"2001-02-03 04:05:",
|
||||||
|
"2001-02-03 04:05:6",
|
||||||
|
"2001-02-03 04:05:06 B",
|
||||||
|
"2001-02-03 04:05:06BC",
|
||||||
|
"2001-02-03 04:05:06.123 B",
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test that parsing the string results in an error.
|
||||||
|
func TestParseTsErrors(t *testing.T) {
|
||||||
|
for i, tt := range timeErrorTests {
|
||||||
|
_, err := ParseTimestamp(nil, tt)
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("%d: expected an error from parsing: %v", i, tt)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now test that sending the value into the database and parsing it back
|
||||||
|
// returns the same time.Time value.
|
||||||
|
func TestEncodeAndParseTs(t *testing.T) {
|
||||||
|
db, err := openTestConnConninfo("timezone='Etc/UTC'")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
for i, tt := range timeTests {
|
||||||
|
var dbstr string
|
||||||
|
err = db.QueryRow("SELECT ($1::timestamptz)::text", tt.timeval).Scan(&dbstr)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("%d: could not send value %q to the database: %s", i, tt.timeval, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
val, err := ParseTimestamp(nil, dbstr)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("%d: could not parse value %q: %s", i, dbstr, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
val = val.In(tt.timeval.Location())
|
||||||
|
if val.String() != tt.timeval.String() {
|
||||||
|
t.Errorf("%d: expected to parse %q into %q; got %q", i, dbstr, tt.timeval, val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var formatTimeTests = []struct {
|
||||||
|
time time.Time
|
||||||
|
expected string
|
||||||
|
}{
|
||||||
|
{time.Time{}, "0001-01-01 00:00:00Z"},
|
||||||
|
{time.Date(2001, time.February, 3, 4, 5, 6, 123456789, time.FixedZone("", 0)), "2001-02-03 04:05:06.123456789Z"},
|
||||||
|
{time.Date(2001, time.February, 3, 4, 5, 6, 123456789, time.FixedZone("", 2*60*60)), "2001-02-03 04:05:06.123456789+02:00"},
|
||||||
|
{time.Date(2001, time.February, 3, 4, 5, 6, 123456789, time.FixedZone("", -6*60*60)), "2001-02-03 04:05:06.123456789-06:00"},
|
||||||
|
{time.Date(2001, time.February, 3, 4, 5, 6, 0, time.FixedZone("", -(7*60*60+30*60+9))), "2001-02-03 04:05:06-07:30:09"},
|
||||||
|
|
||||||
|
{time.Date(1, time.February, 3, 4, 5, 6, 123456789, time.FixedZone("", 0)), "0001-02-03 04:05:06.123456789Z"},
|
||||||
|
{time.Date(1, time.February, 3, 4, 5, 6, 123456789, time.FixedZone("", 2*60*60)), "0001-02-03 04:05:06.123456789+02:00"},
|
||||||
|
{time.Date(1, time.February, 3, 4, 5, 6, 123456789, time.FixedZone("", -6*60*60)), "0001-02-03 04:05:06.123456789-06:00"},
|
||||||
|
|
||||||
|
{time.Date(0, time.February, 3, 4, 5, 6, 123456789, time.FixedZone("", 0)), "0001-02-03 04:05:06.123456789Z BC"},
|
||||||
|
{time.Date(0, time.February, 3, 4, 5, 6, 123456789, time.FixedZone("", 2*60*60)), "0001-02-03 04:05:06.123456789+02:00 BC"},
|
||||||
|
{time.Date(0, time.February, 3, 4, 5, 6, 123456789, time.FixedZone("", -6*60*60)), "0001-02-03 04:05:06.123456789-06:00 BC"},
|
||||||
|
|
||||||
|
{time.Date(1, time.February, 3, 4, 5, 6, 0, time.FixedZone("", -(7*60*60+30*60+9))), "0001-02-03 04:05:06-07:30:09"},
|
||||||
|
{time.Date(0, time.February, 3, 4, 5, 6, 0, time.FixedZone("", -(7*60*60+30*60+9))), "0001-02-03 04:05:06-07:30:09 BC"},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFormatTs(t *testing.T) {
|
||||||
|
for i, tt := range formatTimeTests {
|
||||||
|
val := string(formatTs(tt.time))
|
||||||
|
if val != tt.expected {
|
||||||
|
t.Errorf("%d: incorrect time format %q, want %q", i, val, tt.expected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFormatTsBackend(t *testing.T) {
|
||||||
|
db := openTestConn(t)
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
var str string
|
||||||
|
err := db.QueryRow("SELECT '2001-02-03T04:05:06.007-08:09:10'::time::text").Scan(&str)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("PostgreSQL is accepting an ISO timestamp input for time")
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, tt := range formatTimeTests {
|
||||||
|
for _, typ := range []string{"date", "time", "timetz", "timestamp", "timestamptz"} {
|
||||||
|
err = db.QueryRow("SELECT $1::"+typ+"::text", tt.time).Scan(&str)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("%d: incorrect time format for %v on the backend: %v", i, typ, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTimestampWithTimeZone(t *testing.T) {
|
||||||
|
db := openTestConn(t)
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
tx, err := db.Begin()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer tx.Rollback()
|
||||||
|
|
||||||
|
// try several different locations, all included in Go's zoneinfo.zip
|
||||||
|
for _, locName := range []string{
|
||||||
|
"UTC",
|
||||||
|
"America/Chicago",
|
||||||
|
"America/New_York",
|
||||||
|
"Australia/Darwin",
|
||||||
|
"Australia/Perth",
|
||||||
|
} {
|
||||||
|
loc, err := time.LoadLocation(locName)
|
||||||
|
if err != nil {
|
||||||
|
t.Logf("Could not load time zone %s - skipping", locName)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Postgres timestamps have a resolution of 1 microsecond, so don't
|
||||||
|
// use the full range of the Nanosecond argument
|
||||||
|
refTime := time.Date(2012, 11, 6, 10, 23, 42, 123456000, loc)
|
||||||
|
|
||||||
|
for _, pgTimeZone := range []string{"US/Eastern", "Australia/Darwin"} {
|
||||||
|
// Switch Postgres's timezone to test different output timestamp formats
|
||||||
|
_, err = tx.Exec(fmt.Sprintf("set time zone '%s'", pgTimeZone))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var gotTime time.Time
|
||||||
|
row := tx.QueryRow("select $1::timestamp with time zone", refTime)
|
||||||
|
err = row.Scan(&gotTime)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !refTime.Equal(gotTime) {
|
||||||
|
t.Errorf("timestamps not equal: %s != %s", refTime, gotTime)
|
||||||
|
}
|
||||||
|
|
||||||
|
// check that the time zone is set correctly based on TimeZone
|
||||||
|
pgLoc, err := time.LoadLocation(pgTimeZone)
|
||||||
|
if err != nil {
|
||||||
|
t.Logf("Could not load time zone %s - skipping", pgLoc)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
translated := refTime.In(pgLoc)
|
||||||
|
if translated.String() != gotTime.String() {
|
||||||
|
t.Errorf("timestamps not equal: %s != %s", translated, gotTime)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTimestampWithOutTimezone(t *testing.T) {
|
||||||
|
db := openTestConn(t)
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
test := func(ts, pgts string) {
|
||||||
|
r, err := db.Query("SELECT $1::timestamp", pgts)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Could not run query: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !r.Next() {
|
||||||
|
t.Fatal("Expected at least one row")
|
||||||
|
}
|
||||||
|
|
||||||
|
var result time.Time
|
||||||
|
err = r.Scan(&result)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Did not expect error scanning row: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
expected, err := time.Parse(time.RFC3339, ts)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Could not parse test time literal: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !result.Equal(expected) {
|
||||||
|
t.Fatalf("Expected time to match %v: got mismatch %v",
|
||||||
|
expected, result)
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.Next() {
|
||||||
|
t.Fatal("Expected only one row")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
test("2000-01-01T00:00:00Z", "2000-01-01T00:00:00")
|
||||||
|
|
||||||
|
// Test higher precision time
|
||||||
|
test("2013-01-04T20:14:58.80033Z", "2013-01-04 20:14:58.80033")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInfinityTimestamp(t *testing.T) {
|
||||||
|
db := openTestConn(t)
|
||||||
|
defer db.Close()
|
||||||
|
var err error
|
||||||
|
var resultT time.Time
|
||||||
|
|
||||||
|
expectedErrorStrRegexp := regexp.MustCompile(
|
||||||
|
`^sql: Scan error on column index 0(, name "timestamp(tz)?"|): unsupported`)
|
||||||
|
|
||||||
|
type testCases []struct {
|
||||||
|
Query string
|
||||||
|
Param string
|
||||||
|
ExpectedErrorStrRegexp *regexp.Regexp
|
||||||
|
ExpectedVal interface{}
|
||||||
|
}
|
||||||
|
tc := testCases{
|
||||||
|
{"SELECT $1::timestamp", "-infinity", expectedErrorStrRegexp, "-infinity"},
|
||||||
|
{"SELECT $1::timestamptz", "-infinity", expectedErrorStrRegexp, "-infinity"},
|
||||||
|
{"SELECT $1::timestamp", "infinity", expectedErrorStrRegexp, "infinity"},
|
||||||
|
{"SELECT $1::timestamptz", "infinity", expectedErrorStrRegexp, "infinity"},
|
||||||
|
}
|
||||||
|
// try to assert []byte to time.Time
|
||||||
|
for _, q := range tc {
|
||||||
|
err = db.QueryRow(q.Query, q.Param).Scan(&resultT)
|
||||||
|
if !q.ExpectedErrorStrRegexp.MatchString(err.Error()) {
|
||||||
|
t.Errorf("Scanning -/+infinity, expected error to match regexp %q, got %q",
|
||||||
|
q.ExpectedErrorStrRegexp, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// yield []byte
|
||||||
|
for _, q := range tc {
|
||||||
|
var resultI interface{}
|
||||||
|
err = db.QueryRow(q.Query, q.Param).Scan(&resultI)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Scanning -/+infinity, expected no error, got %q", err)
|
||||||
|
}
|
||||||
|
result, ok := resultI.([]byte)
|
||||||
|
if !ok {
|
||||||
|
t.Errorf("Scanning -/+infinity, expected []byte, got %#v", resultI)
|
||||||
|
}
|
||||||
|
if string(result) != q.ExpectedVal {
|
||||||
|
t.Errorf("Scanning -/+infinity, expected %q, got %q", q.ExpectedVal, result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
y1500 := time.Date(1500, time.January, 1, 0, 0, 0, 0, time.UTC)
|
||||||
|
y2500 := time.Date(2500, time.January, 1, 0, 0, 0, 0, time.UTC)
|
||||||
|
EnableInfinityTs(y1500, y2500)
|
||||||
|
|
||||||
|
err = db.QueryRow("SELECT $1::timestamp", "infinity").Scan(&resultT)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Scanning infinity, expected no error, got %q", err)
|
||||||
|
}
|
||||||
|
if !resultT.Equal(y2500) {
|
||||||
|
t.Errorf("Scanning infinity, expected %q, got %q", y2500, resultT)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = db.QueryRow("SELECT $1::timestamptz", "infinity").Scan(&resultT)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Scanning infinity, expected no error, got %q", err)
|
||||||
|
}
|
||||||
|
if !resultT.Equal(y2500) {
|
||||||
|
t.Errorf("Scanning Infinity, expected time %q, got %q", y2500, resultT.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
err = db.QueryRow("SELECT $1::timestamp", "-infinity").Scan(&resultT)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Scanning -infinity, expected no error, got %q", err)
|
||||||
|
}
|
||||||
|
if !resultT.Equal(y1500) {
|
||||||
|
t.Errorf("Scanning -infinity, expected time %q, got %q", y1500, resultT.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
err = db.QueryRow("SELECT $1::timestamptz", "-infinity").Scan(&resultT)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Scanning -infinity, expected no error, got %q", err)
|
||||||
|
}
|
||||||
|
if !resultT.Equal(y1500) {
|
||||||
|
t.Errorf("Scanning -infinity, expected time %q, got %q", y1500, resultT.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
ym1500 := time.Date(-1500, time.January, 1, 0, 0, 0, 0, time.UTC)
|
||||||
|
y11500 := time.Date(11500, time.January, 1, 0, 0, 0, 0, time.UTC)
|
||||||
|
var s string
|
||||||
|
err = db.QueryRow("SELECT $1::timestamp::text", ym1500).Scan(&s)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Encoding -infinity, expected no error, got %q", err)
|
||||||
|
}
|
||||||
|
if s != "-infinity" {
|
||||||
|
t.Errorf("Encoding -infinity, expected %q, got %q", "-infinity", s)
|
||||||
|
}
|
||||||
|
err = db.QueryRow("SELECT $1::timestamptz::text", ym1500).Scan(&s)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Encoding -infinity, expected no error, got %q", err)
|
||||||
|
}
|
||||||
|
if s != "-infinity" {
|
||||||
|
t.Errorf("Encoding -infinity, expected %q, got %q", "-infinity", s)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = db.QueryRow("SELECT $1::timestamp::text", y11500).Scan(&s)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Encoding infinity, expected no error, got %q", err)
|
||||||
|
}
|
||||||
|
if s != "infinity" {
|
||||||
|
t.Errorf("Encoding infinity, expected %q, got %q", "infinity", s)
|
||||||
|
}
|
||||||
|
err = db.QueryRow("SELECT $1::timestamptz::text", y11500).Scan(&s)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Encoding infinity, expected no error, got %q", err)
|
||||||
|
}
|
||||||
|
if s != "infinity" {
|
||||||
|
t.Errorf("Encoding infinity, expected %q, got %q", "infinity", s)
|
||||||
|
}
|
||||||
|
|
||||||
|
disableInfinityTs()
|
||||||
|
|
||||||
|
var panicErrorString string
|
||||||
|
func() {
|
||||||
|
defer func() {
|
||||||
|
panicErrorString, _ = recover().(string)
|
||||||
|
}()
|
||||||
|
EnableInfinityTs(y2500, y1500)
|
||||||
|
}()
|
||||||
|
if panicErrorString != infinityTsNegativeMustBeSmaller {
|
||||||
|
t.Errorf("Expected error, %q, got %q", infinityTsNegativeMustBeSmaller, panicErrorString)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStringWithNul(t *testing.T) {
|
||||||
|
db := openTestConn(t)
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
hello0world := string("hello\x00world")
|
||||||
|
_, err := db.Query("SELECT $1::text", &hello0world)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("Postgres accepts a string with nul in it; " +
|
||||||
|
"injection attacks may be plausible")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestByteSliceToText(t *testing.T) {
|
||||||
|
db := openTestConn(t)
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
b := []byte("hello world")
|
||||||
|
row := db.QueryRow("SELECT $1::text", b)
|
||||||
|
|
||||||
|
var result []byte
|
||||||
|
err := row.Scan(&result)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if string(result) != string(b) {
|
||||||
|
t.Fatalf("expected %v but got %v", b, result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStringToBytea(t *testing.T) {
|
||||||
|
db := openTestConn(t)
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
b := "hello world"
|
||||||
|
row := db.QueryRow("SELECT $1::bytea", b)
|
||||||
|
|
||||||
|
var result []byte
|
||||||
|
err := row.Scan(&result)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !bytes.Equal(result, []byte(b)) {
|
||||||
|
t.Fatalf("expected %v but got %v", b, result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTextByteSliceToUUID(t *testing.T) {
|
||||||
|
db := openTestConn(t)
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
b := []byte("a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11")
|
||||||
|
row := db.QueryRow("SELECT $1::uuid", b)
|
||||||
|
|
||||||
|
var result string
|
||||||
|
err := row.Scan(&result)
|
||||||
|
if forceBinaryParameters() {
|
||||||
|
pqErr := err.(*Error)
|
||||||
|
if pqErr == nil {
|
||||||
|
t.Errorf("Expected to get error")
|
||||||
|
} else if pqErr.Code != "22P03" {
|
||||||
|
t.Fatalf("Expected to get invalid binary encoding error (22P03), got %s", pqErr.Code)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if result != string(b) {
|
||||||
|
t.Fatalf("expected %v but got %v", b, result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBinaryByteSlicetoUUID(t *testing.T) {
|
||||||
|
db := openTestConn(t)
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
b := []byte{'\xa0', '\xee', '\xbc', '\x99',
|
||||||
|
'\x9c', '\x0b',
|
||||||
|
'\x4e', '\xf8',
|
||||||
|
'\xbb', '\x00', '\x6b',
|
||||||
|
'\xb9', '\xbd', '\x38', '\x0a', '\x11'}
|
||||||
|
row := db.QueryRow("SELECT $1::uuid", b)
|
||||||
|
|
||||||
|
var result string
|
||||||
|
err := row.Scan(&result)
|
||||||
|
if forceBinaryParameters() {
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if result != string("a0eebc99-9c0b-4ef8-bb00-6bb9bd380a11") {
|
||||||
|
t.Fatalf("expected %v but got %v", b, result)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
pqErr := err.(*Error)
|
||||||
|
if pqErr == nil {
|
||||||
|
t.Errorf("Expected to get error")
|
||||||
|
} else if pqErr.Code != "22021" {
|
||||||
|
t.Fatalf("Expected to get invalid byte sequence for encoding error (22021), got %s", pqErr.Code)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStringToUUID(t *testing.T) {
|
||||||
|
db := openTestConn(t)
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
s := "a0eebc99-9c0b-4ef8-bb00-6bb9bd380a11"
|
||||||
|
row := db.QueryRow("SELECT $1::uuid", s)
|
||||||
|
|
||||||
|
var result string
|
||||||
|
err := row.Scan(&result)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if result != s {
|
||||||
|
t.Fatalf("expected %v but got %v", s, result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTextByteSliceToInt(t *testing.T) {
|
||||||
|
db := openTestConn(t)
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
expected := 12345678
|
||||||
|
b := []byte(fmt.Sprintf("%d", expected))
|
||||||
|
row := db.QueryRow("SELECT $1::int", b)
|
||||||
|
|
||||||
|
var result int
|
||||||
|
err := row.Scan(&result)
|
||||||
|
if forceBinaryParameters() {
|
||||||
|
pqErr := err.(*Error)
|
||||||
|
if pqErr == nil {
|
||||||
|
t.Errorf("Expected to get error")
|
||||||
|
} else if pqErr.Code != "22P03" {
|
||||||
|
t.Fatalf("Expected to get invalid binary encoding error (22P03), got %s", pqErr.Code)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if result != expected {
|
||||||
|
t.Fatalf("expected %v but got %v", expected, result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBinaryByteSliceToInt(t *testing.T) {
|
||||||
|
db := openTestConn(t)
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
expected := 12345678
|
||||||
|
b := []byte{'\x00', '\xbc', '\x61', '\x4e'}
|
||||||
|
row := db.QueryRow("SELECT $1::int", b)
|
||||||
|
|
||||||
|
var result int
|
||||||
|
err := row.Scan(&result)
|
||||||
|
if forceBinaryParameters() {
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if result != expected {
|
||||||
|
t.Fatalf("expected %v but got %v", expected, result)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
pqErr := err.(*Error)
|
||||||
|
if pqErr == nil {
|
||||||
|
t.Errorf("Expected to get error")
|
||||||
|
} else if pqErr.Code != "22021" {
|
||||||
|
t.Fatalf("Expected to get invalid byte sequence for encoding error (22021), got %s", pqErr.Code)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTextDecodeIntoString(t *testing.T) {
|
||||||
|
input := []byte("hello world")
|
||||||
|
want := string(input)
|
||||||
|
for _, typ := range []oid.Oid{oid.T_char, oid.T_varchar, oid.T_text} {
|
||||||
|
got := decode(¶meterStatus{}, input, typ, formatText)
|
||||||
|
if got != want {
|
||||||
|
t.Errorf("invalid string decoding output for %T(%+v), got %v but expected %v", typ, typ, got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestByteaOutputFormatEncoding(t *testing.T) {
|
||||||
|
input := []byte("\\x\x00\x01\x02\xFF\xFEabcdefg0123")
|
||||||
|
want := []byte("\\x5c78000102fffe6162636465666730313233")
|
||||||
|
got := encode(¶meterStatus{serverVersion: 90000}, input, oid.T_bytea)
|
||||||
|
if !bytes.Equal(want, got) {
|
||||||
|
t.Errorf("invalid hex bytea output, got %v but expected %v", got, want)
|
||||||
|
}
|
||||||
|
|
||||||
|
want = []byte("\\\\x\\000\\001\\002\\377\\376abcdefg0123")
|
||||||
|
got = encode(¶meterStatus{serverVersion: 84000}, input, oid.T_bytea)
|
||||||
|
if !bytes.Equal(want, got) {
|
||||||
|
t.Errorf("invalid escape bytea output, got %v but expected %v", got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestByteaOutputFormats(t *testing.T) {
|
||||||
|
db := openTestConn(t)
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
if getServerVersion(t, db) < 90000 {
|
||||||
|
// skip
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
testByteaOutputFormat := func(f string, usePrepared bool) {
|
||||||
|
expectedData := []byte("\x5c\x78\x00\xff\x61\x62\x63\x01\x08")
|
||||||
|
sqlQuery := "SELECT decode('5c7800ff6162630108', 'hex')"
|
||||||
|
|
||||||
|
var data []byte
|
||||||
|
|
||||||
|
// use a txn to avoid relying on getting the same connection
|
||||||
|
txn, err := db.Begin()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer txn.Rollback()
|
||||||
|
|
||||||
|
_, err = txn.Exec("SET LOCAL bytea_output TO " + f)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
var rows *sql.Rows
|
||||||
|
var stmt *sql.Stmt
|
||||||
|
if usePrepared {
|
||||||
|
stmt, err = txn.Prepare(sqlQuery)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
rows, err = stmt.Query()
|
||||||
|
} else {
|
||||||
|
// use Query; QueryRow would hide the actual error
|
||||||
|
rows, err = txn.Query(sqlQuery)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if !rows.Next() {
|
||||||
|
if rows.Err() != nil {
|
||||||
|
t.Fatal(rows.Err())
|
||||||
|
}
|
||||||
|
t.Fatal("shouldn't happen")
|
||||||
|
}
|
||||||
|
err = rows.Scan(&data)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
err = rows.Close()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if stmt != nil {
|
||||||
|
err = stmt.Close()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !bytes.Equal(data, expectedData) {
|
||||||
|
t.Errorf("unexpected bytea value %v for format %s; expected %v", data, f, expectedData)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
testByteaOutputFormat("hex", false)
|
||||||
|
testByteaOutputFormat("escape", false)
|
||||||
|
testByteaOutputFormat("hex", true)
|
||||||
|
testByteaOutputFormat("escape", true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAppendEncodedText(t *testing.T) {
|
||||||
|
var buf []byte
|
||||||
|
|
||||||
|
buf = appendEncodedText(¶meterStatus{serverVersion: 90000}, buf, int64(10))
|
||||||
|
buf = append(buf, '\t')
|
||||||
|
buf = appendEncodedText(¶meterStatus{serverVersion: 90000}, buf, 42.0000000001)
|
||||||
|
buf = append(buf, '\t')
|
||||||
|
buf = appendEncodedText(¶meterStatus{serverVersion: 90000}, buf, "hello\tworld")
|
||||||
|
buf = append(buf, '\t')
|
||||||
|
buf = appendEncodedText(¶meterStatus{serverVersion: 90000}, buf, []byte{0, 128, 255})
|
||||||
|
|
||||||
|
if string(buf) != "10\t42.0000000001\thello\\tworld\t\\\\x0080ff" {
|
||||||
|
t.Fatal(string(buf))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAppendEscapedText(t *testing.T) {
|
||||||
|
if esc := appendEscapedText(nil, "hallo\tescape"); string(esc) != "hallo\\tescape" {
|
||||||
|
t.Fatal(string(esc))
|
||||||
|
}
|
||||||
|
if esc := appendEscapedText(nil, "hallo\\tescape\n"); string(esc) != "hallo\\\\tescape\\n" {
|
||||||
|
t.Fatal(string(esc))
|
||||||
|
}
|
||||||
|
if esc := appendEscapedText(nil, "\n\r\t\f"); string(esc) != "\\n\\r\\t\f" {
|
||||||
|
t.Fatal(string(esc))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAppendEscapedTextExistingBuffer(t *testing.T) {
|
||||||
|
buf := []byte("123\t")
|
||||||
|
if esc := appendEscapedText(buf, "hallo\tescape"); string(esc) != "123\thallo\\tescape" {
|
||||||
|
t.Fatal(string(esc))
|
||||||
|
}
|
||||||
|
buf = []byte("123\t")
|
||||||
|
if esc := appendEscapedText(buf, "hallo\\tescape\n"); string(esc) != "123\thallo\\\\tescape\\n" {
|
||||||
|
t.Fatal(string(esc))
|
||||||
|
}
|
||||||
|
buf = []byte("123\t")
|
||||||
|
if esc := appendEscapedText(buf, "\n\r\t\f"); string(esc) != "123\t\\n\\r\\t\f" {
|
||||||
|
t.Fatal(string(esc))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkAppendEscapedText(b *testing.B) {
|
||||||
|
longString := ""
|
||||||
|
for i := 0; i < 100; i++ {
|
||||||
|
longString += "123456789\n"
|
||||||
|
}
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
appendEscapedText(nil, longString)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkAppendEscapedTextNoEscape(b *testing.B) {
|
||||||
|
longString := ""
|
||||||
|
for i := 0; i < 100; i++ {
|
||||||
|
longString += "1234567890"
|
||||||
|
}
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
appendEscapedText(nil, longString)
|
||||||
|
}
|
||||||
|
}
|
98
vendor/github.com/lib/pq/example/listen/doc.go
generated
vendored
Normal file
98
vendor/github.com/lib/pq/example/listen/doc.go
generated
vendored
Normal file
|
@ -0,0 +1,98 @@
|
||||||
|
/*
|
||||||
|
|
||||||
|
Package listen is a self-contained Go program which uses the LISTEN / NOTIFY
|
||||||
|
mechanism to avoid polling the database while waiting for more work to arrive.
|
||||||
|
|
||||||
|
//
|
||||||
|
// You can see the program in action by defining a function similar to
|
||||||
|
// the following:
|
||||||
|
//
|
||||||
|
// CREATE OR REPLACE FUNCTION public.get_work()
|
||||||
|
// RETURNS bigint
|
||||||
|
// LANGUAGE sql
|
||||||
|
// AS $$
|
||||||
|
// SELECT CASE WHEN random() >= 0.2 THEN int8 '1' END
|
||||||
|
// $$
|
||||||
|
// ;
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/lib/pq"
|
||||||
|
)
|
||||||
|
|
||||||
|
func doWork(db *sql.DB, work int64) {
|
||||||
|
// work here
|
||||||
|
}
|
||||||
|
|
||||||
|
func getWork(db *sql.DB) {
|
||||||
|
for {
|
||||||
|
// get work from the database here
|
||||||
|
var work sql.NullInt64
|
||||||
|
err := db.QueryRow("SELECT get_work()").Scan(&work)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("call to get_work() failed: ", err)
|
||||||
|
time.Sleep(10 * time.Second)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !work.Valid {
|
||||||
|
// no more work to do
|
||||||
|
fmt.Println("ran out of work")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println("starting work on ", work.Int64)
|
||||||
|
go doWork(db, work.Int64)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func waitForNotification(l *pq.Listener) {
|
||||||
|
select {
|
||||||
|
case <-l.Notify:
|
||||||
|
fmt.Println("received notification, new work available")
|
||||||
|
case <-time.After(90 * time.Second):
|
||||||
|
go l.Ping()
|
||||||
|
// Check if there's more work available, just in case it takes
|
||||||
|
// a while for the Listener to notice connection loss and
|
||||||
|
// reconnect.
|
||||||
|
fmt.Println("received no work for 90 seconds, checking for new work")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
var conninfo string = ""
|
||||||
|
|
||||||
|
db, err := sql.Open("postgres", conninfo)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
reportProblem := func(ev pq.ListenerEventType, err error) {
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
minReconn := 10 * time.Second
|
||||||
|
maxReconn := time.Minute
|
||||||
|
listener := pq.NewListener(conninfo, minReconn, maxReconn, reportProblem)
|
||||||
|
err = listener.Listen("getwork")
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println("entering main loop")
|
||||||
|
for {
|
||||||
|
// process all available work before waiting for notifications
|
||||||
|
getWork(db)
|
||||||
|
waitForNotification(listener)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
*/
|
||||||
|
package listen
|
319
vendor/github.com/lib/pq/go18_test.go
generated
vendored
Normal file
319
vendor/github.com/lib/pq/go18_test.go
generated
vendored
Normal file
|
@ -0,0 +1,319 @@
|
||||||
|
package pq
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"database/sql"
|
||||||
|
"runtime"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestMultipleSimpleQuery(t *testing.T) {
|
||||||
|
db := openTestConn(t)
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
rows, err := db.Query("select 1; set time zone default; select 2; select 3")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
|
||||||
|
var i int
|
||||||
|
for rows.Next() {
|
||||||
|
if err := rows.Scan(&i); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if i != 1 {
|
||||||
|
t.Fatalf("expected 1, got %d", i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !rows.NextResultSet() {
|
||||||
|
t.Fatal("expected more result sets", rows.Err())
|
||||||
|
}
|
||||||
|
for rows.Next() {
|
||||||
|
if err := rows.Scan(&i); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if i != 2 {
|
||||||
|
t.Fatalf("expected 2, got %d", i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure that if we ignore a result we can still query.
|
||||||
|
|
||||||
|
rows, err = db.Query("select 4; select 5")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
|
||||||
|
for rows.Next() {
|
||||||
|
if err := rows.Scan(&i); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if i != 4 {
|
||||||
|
t.Fatalf("expected 4, got %d", i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !rows.NextResultSet() {
|
||||||
|
t.Fatal("expected more result sets", rows.Err())
|
||||||
|
}
|
||||||
|
for rows.Next() {
|
||||||
|
if err := rows.Scan(&i); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if i != 5 {
|
||||||
|
t.Fatalf("expected 5, got %d", i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if rows.NextResultSet() {
|
||||||
|
t.Fatal("unexpected result set")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const contextRaceIterations = 100
|
||||||
|
|
||||||
|
func TestContextCancelExec(t *testing.T) {
|
||||||
|
db := openTestConn(t)
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
|
||||||
|
// Delay execution for just a bit until db.ExecContext has begun.
|
||||||
|
defer time.AfterFunc(time.Millisecond*10, cancel).Stop()
|
||||||
|
|
||||||
|
// Not canceled until after the exec has started.
|
||||||
|
if _, err := db.ExecContext(ctx, "select pg_sleep(1)"); err == nil {
|
||||||
|
t.Fatal("expected error")
|
||||||
|
} else if err.Error() != "pq: canceling statement due to user request" {
|
||||||
|
t.Fatalf("unexpected error: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Context is already canceled, so error should come before execution.
|
||||||
|
if _, err := db.ExecContext(ctx, "select pg_sleep(1)"); err == nil {
|
||||||
|
t.Fatal("expected error")
|
||||||
|
} else if err.Error() != "context canceled" {
|
||||||
|
t.Fatalf("unexpected error: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < contextRaceIterations; i++ {
|
||||||
|
func() {
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
defer cancel()
|
||||||
|
if _, err := db.ExecContext(ctx, "select 1"); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
if _, err := db.Exec("select 1"); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestContextCancelQuery(t *testing.T) {
|
||||||
|
db := openTestConn(t)
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
|
||||||
|
// Delay execution for just a bit until db.QueryContext has begun.
|
||||||
|
defer time.AfterFunc(time.Millisecond*10, cancel).Stop()
|
||||||
|
|
||||||
|
// Not canceled until after the exec has started.
|
||||||
|
if _, err := db.QueryContext(ctx, "select pg_sleep(1)"); err == nil {
|
||||||
|
t.Fatal("expected error")
|
||||||
|
} else if err.Error() != "pq: canceling statement due to user request" {
|
||||||
|
t.Fatalf("unexpected error: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Context is already canceled, so error should come before execution.
|
||||||
|
if _, err := db.QueryContext(ctx, "select pg_sleep(1)"); err == nil {
|
||||||
|
t.Fatal("expected error")
|
||||||
|
} else if err.Error() != "context canceled" {
|
||||||
|
t.Fatalf("unexpected error: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < contextRaceIterations; i++ {
|
||||||
|
func() {
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
rows, err := db.QueryContext(ctx, "select 1")
|
||||||
|
cancel()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
} else if err := rows.Close(); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
if rows, err := db.Query("select 1"); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
} else if err := rows.Close(); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestIssue617 tests that a failed query in QueryContext doesn't lead to a
|
||||||
|
// goroutine leak.
|
||||||
|
func TestIssue617(t *testing.T) {
|
||||||
|
db := openTestConn(t)
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
const N = 10
|
||||||
|
|
||||||
|
numGoroutineStart := runtime.NumGoroutine()
|
||||||
|
for i := 0; i < N; i++ {
|
||||||
|
func() {
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
defer cancel()
|
||||||
|
_, err := db.QueryContext(ctx, `SELECT * FROM DOESNOTEXIST`)
|
||||||
|
pqErr, _ := err.(*Error)
|
||||||
|
// Expecting "pq: relation \"doesnotexist\" does not exist" error.
|
||||||
|
if err == nil || pqErr == nil || pqErr.Code != "42P01" {
|
||||||
|
t.Fatalf("expected undefined table error, got %v", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
numGoroutineFinish := runtime.NumGoroutine()
|
||||||
|
|
||||||
|
// We use N/2 and not N because the GC and other actors may increase or
|
||||||
|
// decrease the number of goroutines.
|
||||||
|
if numGoroutineFinish-numGoroutineStart >= N/2 {
|
||||||
|
t.Errorf("goroutine leak detected, was %d, now %d", numGoroutineStart, numGoroutineFinish)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestContextCancelBegin(t *testing.T) {
|
||||||
|
db := openTestConn(t)
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
tx, err := db.BeginTx(ctx, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delay execution for just a bit until tx.Exec has begun.
|
||||||
|
defer time.AfterFunc(time.Millisecond*10, cancel).Stop()
|
||||||
|
|
||||||
|
// Not canceled until after the exec has started.
|
||||||
|
if _, err := tx.Exec("select pg_sleep(1)"); err == nil {
|
||||||
|
t.Fatal("expected error")
|
||||||
|
} else if err.Error() != "pq: canceling statement due to user request" {
|
||||||
|
t.Fatalf("unexpected error: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Transaction is canceled, so expect an error.
|
||||||
|
if _, err := tx.Query("select pg_sleep(1)"); err == nil {
|
||||||
|
t.Fatal("expected error")
|
||||||
|
} else if err != sql.ErrTxDone {
|
||||||
|
t.Fatalf("unexpected error: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Context is canceled, so cannot begin a transaction.
|
||||||
|
if _, err := db.BeginTx(ctx, nil); err == nil {
|
||||||
|
t.Fatal("expected error")
|
||||||
|
} else if err.Error() != "context canceled" {
|
||||||
|
t.Fatalf("unexpected error: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < contextRaceIterations; i++ {
|
||||||
|
func() {
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
tx, err := db.BeginTx(ctx, nil)
|
||||||
|
cancel()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
} else if err := tx.Rollback(); err != nil &&
|
||||||
|
err.Error() != "pq: canceling statement due to user request" &&
|
||||||
|
err != sql.ErrTxDone {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
if tx, err := db.Begin(); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
} else if err := tx.Rollback(); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTxOptions(t *testing.T) {
|
||||||
|
db := openTestConn(t)
|
||||||
|
defer db.Close()
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
level sql.IsolationLevel
|
||||||
|
isolation string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
level: sql.LevelDefault,
|
||||||
|
isolation: "",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
level: sql.LevelReadUncommitted,
|
||||||
|
isolation: "read uncommitted",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
level: sql.LevelReadCommitted,
|
||||||
|
isolation: "read committed",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
level: sql.LevelRepeatableRead,
|
||||||
|
isolation: "repeatable read",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
level: sql.LevelSerializable,
|
||||||
|
isolation: "serializable",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
for _, ro := range []bool{true, false} {
|
||||||
|
tx, err := db.BeginTx(ctx, &sql.TxOptions{
|
||||||
|
Isolation: test.level,
|
||||||
|
ReadOnly: ro,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var isolation string
|
||||||
|
err = tx.QueryRow("select current_setting('transaction_isolation')").Scan(&isolation)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if test.isolation != "" && isolation != test.isolation {
|
||||||
|
t.Errorf("wrong isolation level: %s != %s", isolation, test.isolation)
|
||||||
|
}
|
||||||
|
|
||||||
|
var isRO string
|
||||||
|
err = tx.QueryRow("select current_setting('transaction_read_only')").Scan(&isRO)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if ro != (isRO == "on") {
|
||||||
|
t.Errorf("read/[write,only] not set: %t != %s for level %s",
|
||||||
|
ro, isRO, test.isolation)
|
||||||
|
}
|
||||||
|
|
||||||
|
tx.Rollback()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := db.BeginTx(ctx, &sql.TxOptions{
|
||||||
|
Isolation: sql.LevelLinearizable,
|
||||||
|
})
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("expected LevelLinearizable to fail")
|
||||||
|
}
|
||||||
|
if !strings.Contains(err.Error(), "isolation level not supported") {
|
||||||
|
t.Errorf("Expected error to mention isolation level, got %q", err)
|
||||||
|
}
|
||||||
|
}
|
69
vendor/github.com/lib/pq/go19_test.go
generated
vendored
Normal file
69
vendor/github.com/lib/pq/go19_test.go
generated
vendored
Normal file
|
@ -0,0 +1,69 @@
|
||||||
|
// +build go1.9
|
||||||
|
|
||||||
|
package pq
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"database/sql"
|
||||||
|
"database/sql/driver"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestPing(t *testing.T) {
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
db := openTestConn(t)
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
if _, ok := reflect.TypeOf(db).MethodByName("Conn"); !ok {
|
||||||
|
t.Skipf("Conn method undefined on type %T, skipping test (requires at least go1.9)", db)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := db.PingContext(ctx); err != nil {
|
||||||
|
t.Fatal("expected Ping to succeed")
|
||||||
|
}
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
// grab a connection
|
||||||
|
conn, err := db.Conn(ctx)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// start a transaction and read backend pid of our connection
|
||||||
|
tx, err := conn.BeginTx(ctx, &sql.TxOptions{
|
||||||
|
Isolation: sql.LevelDefault,
|
||||||
|
ReadOnly: true,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
rows, err := tx.Query("SELECT pg_backend_pid()")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
|
||||||
|
// read the pid from result
|
||||||
|
var pid int
|
||||||
|
for rows.Next() {
|
||||||
|
if err := rows.Scan(&pid); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if rows.Err() != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := tx.Rollback(); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// kill the process which handles our connection and test if the ping fails
|
||||||
|
if _, err := db.Exec("SELECT pg_terminate_backend($1)", pid); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := conn.PingContext(ctx); err != driver.ErrBadConn {
|
||||||
|
t.Fatalf("expected error %s, instead got %s", driver.ErrBadConn, err)
|
||||||
|
}
|
||||||
|
}
|
118
vendor/github.com/lib/pq/hstore/hstore.go
generated
vendored
Normal file
118
vendor/github.com/lib/pq/hstore/hstore.go
generated
vendored
Normal file
|
@ -0,0 +1,118 @@
|
||||||
|
package hstore
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql"
|
||||||
|
"database/sql/driver"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Hstore is a wrapper for transferring Hstore values back and forth easily.
|
||||||
|
type Hstore struct {
|
||||||
|
Map map[string]sql.NullString
|
||||||
|
}
|
||||||
|
|
||||||
|
// escapes and quotes hstore keys/values
|
||||||
|
// s should be a sql.NullString or string
|
||||||
|
func hQuote(s interface{}) string {
|
||||||
|
var str string
|
||||||
|
switch v := s.(type) {
|
||||||
|
case sql.NullString:
|
||||||
|
if !v.Valid {
|
||||||
|
return "NULL"
|
||||||
|
}
|
||||||
|
str = v.String
|
||||||
|
case string:
|
||||||
|
str = v
|
||||||
|
default:
|
||||||
|
panic("not a string or sql.NullString")
|
||||||
|
}
|
||||||
|
|
||||||
|
str = strings.Replace(str, "\\", "\\\\", -1)
|
||||||
|
return `"` + strings.Replace(str, "\"", "\\\"", -1) + `"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scan implements the Scanner interface.
|
||||||
|
//
|
||||||
|
// Note h.Map is reallocated before the scan to clear existing values. If the
|
||||||
|
// hstore column's database value is NULL, then h.Map is set to nil instead.
|
||||||
|
func (h *Hstore) Scan(value interface{}) error {
|
||||||
|
if value == nil {
|
||||||
|
h.Map = nil
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
h.Map = make(map[string]sql.NullString)
|
||||||
|
var b byte
|
||||||
|
pair := [][]byte{{}, {}}
|
||||||
|
pi := 0
|
||||||
|
inQuote := false
|
||||||
|
didQuote := false
|
||||||
|
sawSlash := false
|
||||||
|
bindex := 0
|
||||||
|
for bindex, b = range value.([]byte) {
|
||||||
|
if sawSlash {
|
||||||
|
pair[pi] = append(pair[pi], b)
|
||||||
|
sawSlash = false
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
switch b {
|
||||||
|
case '\\':
|
||||||
|
sawSlash = true
|
||||||
|
continue
|
||||||
|
case '"':
|
||||||
|
inQuote = !inQuote
|
||||||
|
if !didQuote {
|
||||||
|
didQuote = true
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
default:
|
||||||
|
if !inQuote {
|
||||||
|
switch b {
|
||||||
|
case ' ', '\t', '\n', '\r':
|
||||||
|
continue
|
||||||
|
case '=':
|
||||||
|
continue
|
||||||
|
case '>':
|
||||||
|
pi = 1
|
||||||
|
didQuote = false
|
||||||
|
continue
|
||||||
|
case ',':
|
||||||
|
s := string(pair[1])
|
||||||
|
if !didQuote && len(s) == 4 && strings.ToLower(s) == "null" {
|
||||||
|
h.Map[string(pair[0])] = sql.NullString{String: "", Valid: false}
|
||||||
|
} else {
|
||||||
|
h.Map[string(pair[0])] = sql.NullString{String: string(pair[1]), Valid: true}
|
||||||
|
}
|
||||||
|
pair[0] = []byte{}
|
||||||
|
pair[1] = []byte{}
|
||||||
|
pi = 0
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pair[pi] = append(pair[pi], b)
|
||||||
|
}
|
||||||
|
if bindex > 0 {
|
||||||
|
s := string(pair[1])
|
||||||
|
if !didQuote && len(s) == 4 && strings.ToLower(s) == "null" {
|
||||||
|
h.Map[string(pair[0])] = sql.NullString{String: "", Valid: false}
|
||||||
|
} else {
|
||||||
|
h.Map[string(pair[0])] = sql.NullString{String: string(pair[1]), Valid: true}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Value implements the driver Valuer interface. Note if h.Map is nil, the
|
||||||
|
// database column value will be set to NULL.
|
||||||
|
func (h Hstore) Value() (driver.Value, error) {
|
||||||
|
if h.Map == nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
parts := []string{}
|
||||||
|
for key, val := range h.Map {
|
||||||
|
thispart := hQuote(key) + "=>" + hQuote(val)
|
||||||
|
parts = append(parts, thispart)
|
||||||
|
}
|
||||||
|
return []byte(strings.Join(parts, ",")), nil
|
||||||
|
}
|
148
vendor/github.com/lib/pq/hstore/hstore_test.go
generated
vendored
Normal file
148
vendor/github.com/lib/pq/hstore/hstore_test.go
generated
vendored
Normal file
|
@ -0,0 +1,148 @@
|
||||||
|
package hstore
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
_ "github.com/lib/pq"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Fatalistic interface {
|
||||||
|
Fatal(args ...interface{})
|
||||||
|
}
|
||||||
|
|
||||||
|
func openTestConn(t Fatalistic) *sql.DB {
|
||||||
|
datname := os.Getenv("PGDATABASE")
|
||||||
|
sslmode := os.Getenv("PGSSLMODE")
|
||||||
|
|
||||||
|
if datname == "" {
|
||||||
|
os.Setenv("PGDATABASE", "pqgotest")
|
||||||
|
}
|
||||||
|
|
||||||
|
if sslmode == "" {
|
||||||
|
os.Setenv("PGSSLMODE", "disable")
|
||||||
|
}
|
||||||
|
|
||||||
|
conn, err := sql.Open("postgres", "")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return conn
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHstore(t *testing.T) {
|
||||||
|
db := openTestConn(t)
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
// quitely create hstore if it doesn't exist
|
||||||
|
_, err := db.Exec("CREATE EXTENSION IF NOT EXISTS hstore")
|
||||||
|
if err != nil {
|
||||||
|
t.Skipf("Skipping hstore tests - hstore extension create failed: %s", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
hs := Hstore{}
|
||||||
|
|
||||||
|
// test for null-valued hstores
|
||||||
|
err = db.QueryRow("SELECT NULL::hstore").Scan(&hs)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if hs.Map != nil {
|
||||||
|
t.Fatalf("expected null map")
|
||||||
|
}
|
||||||
|
|
||||||
|
err = db.QueryRow("SELECT $1::hstore", hs).Scan(&hs)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("re-query null map failed: %s", err.Error())
|
||||||
|
}
|
||||||
|
if hs.Map != nil {
|
||||||
|
t.Fatalf("expected null map")
|
||||||
|
}
|
||||||
|
|
||||||
|
// test for empty hstores
|
||||||
|
err = db.QueryRow("SELECT ''::hstore").Scan(&hs)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if hs.Map == nil {
|
||||||
|
t.Fatalf("expected empty map, got null map")
|
||||||
|
}
|
||||||
|
if len(hs.Map) != 0 {
|
||||||
|
t.Fatalf("expected empty map, got len(map)=%d", len(hs.Map))
|
||||||
|
}
|
||||||
|
|
||||||
|
err = db.QueryRow("SELECT $1::hstore", hs).Scan(&hs)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("re-query empty map failed: %s", err.Error())
|
||||||
|
}
|
||||||
|
if hs.Map == nil {
|
||||||
|
t.Fatalf("expected empty map, got null map")
|
||||||
|
}
|
||||||
|
if len(hs.Map) != 0 {
|
||||||
|
t.Fatalf("expected empty map, got len(map)=%d", len(hs.Map))
|
||||||
|
}
|
||||||
|
|
||||||
|
// a few example maps to test out
|
||||||
|
hsOnePair := Hstore{
|
||||||
|
Map: map[string]sql.NullString{
|
||||||
|
"key1": {String: "value1", Valid: true},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
hsThreePairs := Hstore{
|
||||||
|
Map: map[string]sql.NullString{
|
||||||
|
"key1": {String: "value1", Valid: true},
|
||||||
|
"key2": {String: "value2", Valid: true},
|
||||||
|
"key3": {String: "value3", Valid: true},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
hsSmorgasbord := Hstore{
|
||||||
|
Map: map[string]sql.NullString{
|
||||||
|
"nullstring": {String: "NULL", Valid: true},
|
||||||
|
"actuallynull": {String: "", Valid: false},
|
||||||
|
"NULL": {String: "NULL string key", Valid: true},
|
||||||
|
"withbracket": {String: "value>42", Valid: true},
|
||||||
|
"withequal": {String: "value=42", Valid: true},
|
||||||
|
`"withquotes1"`: {String: `this "should" be fine`, Valid: true},
|
||||||
|
`"withquotes"2"`: {String: `this "should\" also be fine`, Valid: true},
|
||||||
|
"embedded1": {String: "value1=>x1", Valid: true},
|
||||||
|
"embedded2": {String: `"value2"=>x2`, Valid: true},
|
||||||
|
"withnewlines": {String: "\n\nvalue\t=>2", Valid: true},
|
||||||
|
"<<all sorts of crazy>>": {String: `this, "should,\" also, => be fine`, Valid: true},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// test encoding in query params, then decoding during Scan
|
||||||
|
testBidirectional := func(h Hstore) {
|
||||||
|
err = db.QueryRow("SELECT $1::hstore", h).Scan(&hs)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("re-query %d-pair map failed: %s", len(h.Map), err.Error())
|
||||||
|
}
|
||||||
|
if hs.Map == nil {
|
||||||
|
t.Fatalf("expected %d-pair map, got null map", len(h.Map))
|
||||||
|
}
|
||||||
|
if len(hs.Map) != len(h.Map) {
|
||||||
|
t.Fatalf("expected %d-pair map, got len(map)=%d", len(h.Map), len(hs.Map))
|
||||||
|
}
|
||||||
|
|
||||||
|
for key, val := range hs.Map {
|
||||||
|
otherval, found := h.Map[key]
|
||||||
|
if !found {
|
||||||
|
t.Fatalf(" key '%v' not found in %d-pair map", key, len(h.Map))
|
||||||
|
}
|
||||||
|
if otherval.Valid != val.Valid {
|
||||||
|
t.Fatalf(" value %v <> %v in %d-pair map", otherval, val, len(h.Map))
|
||||||
|
}
|
||||||
|
if otherval.String != val.String {
|
||||||
|
t.Fatalf(" value '%v' <> '%v' in %d-pair map", otherval.String, val.String, len(h.Map))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
testBidirectional(hsOnePair)
|
||||||
|
testBidirectional(hsThreePairs)
|
||||||
|
testBidirectional(hsSmorgasbord)
|
||||||
|
}
|
26
vendor/github.com/lib/pq/issues_test.go
generated
vendored
Normal file
26
vendor/github.com/lib/pq/issues_test.go
generated
vendored
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
package pq
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
func TestIssue494(t *testing.T) {
|
||||||
|
db := openTestConn(t)
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
query := `CREATE TEMP TABLE t (i INT PRIMARY KEY)`
|
||||||
|
if _, err := db.Exec(query); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
txn, err := db.Begin()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := txn.Prepare(CopyIn("t", "i")); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := txn.Query("SELECT 1"); err == nil {
|
||||||
|
t.Fatal("expected error")
|
||||||
|
}
|
||||||
|
}
|
570
vendor/github.com/lib/pq/notify_test.go
generated
vendored
Normal file
570
vendor/github.com/lib/pq/notify_test.go
generated
vendored
Normal file
|
@ -0,0 +1,570 @@
|
||||||
|
package pq
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"runtime"
|
||||||
|
"sync"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
var errNilNotification = errors.New("nil notification")
|
||||||
|
|
||||||
|
func expectNotification(t *testing.T, ch <-chan *Notification, relname string, extra string) error {
|
||||||
|
select {
|
||||||
|
case n := <-ch:
|
||||||
|
if n == nil {
|
||||||
|
return errNilNotification
|
||||||
|
}
|
||||||
|
if n.Channel != relname || n.Extra != extra {
|
||||||
|
return fmt.Errorf("unexpected notification %v", n)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
case <-time.After(1500 * time.Millisecond):
|
||||||
|
return fmt.Errorf("timeout")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func expectNoNotification(t *testing.T, ch <-chan *Notification) error {
|
||||||
|
select {
|
||||||
|
case n := <-ch:
|
||||||
|
return fmt.Errorf("unexpected notification %v", n)
|
||||||
|
case <-time.After(100 * time.Millisecond):
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func expectEvent(t *testing.T, eventch <-chan ListenerEventType, et ListenerEventType) error {
|
||||||
|
select {
|
||||||
|
case e := <-eventch:
|
||||||
|
if e != et {
|
||||||
|
return fmt.Errorf("unexpected event %v", e)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
case <-time.After(1500 * time.Millisecond):
|
||||||
|
panic("expectEvent timeout")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func expectNoEvent(t *testing.T, eventch <-chan ListenerEventType) error {
|
||||||
|
select {
|
||||||
|
case e := <-eventch:
|
||||||
|
return fmt.Errorf("unexpected event %v", e)
|
||||||
|
case <-time.After(100 * time.Millisecond):
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func newTestListenerConn(t *testing.T) (*ListenerConn, <-chan *Notification) {
|
||||||
|
datname := os.Getenv("PGDATABASE")
|
||||||
|
sslmode := os.Getenv("PGSSLMODE")
|
||||||
|
|
||||||
|
if datname == "" {
|
||||||
|
os.Setenv("PGDATABASE", "pqgotest")
|
||||||
|
}
|
||||||
|
|
||||||
|
if sslmode == "" {
|
||||||
|
os.Setenv("PGSSLMODE", "disable")
|
||||||
|
}
|
||||||
|
|
||||||
|
notificationChan := make(chan *Notification)
|
||||||
|
l, err := NewListenerConn("", notificationChan)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return l, notificationChan
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewListenerConn(t *testing.T) {
|
||||||
|
l, _ := newTestListenerConn(t)
|
||||||
|
|
||||||
|
defer l.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestConnListen(t *testing.T) {
|
||||||
|
l, channel := newTestListenerConn(t)
|
||||||
|
|
||||||
|
defer l.Close()
|
||||||
|
|
||||||
|
db := openTestConn(t)
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
ok, err := l.Listen("notify_test")
|
||||||
|
if !ok || err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = db.Exec("NOTIFY notify_test")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = expectNotification(t, channel, "notify_test", "")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestConnUnlisten(t *testing.T) {
|
||||||
|
l, channel := newTestListenerConn(t)
|
||||||
|
|
||||||
|
defer l.Close()
|
||||||
|
|
||||||
|
db := openTestConn(t)
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
ok, err := l.Listen("notify_test")
|
||||||
|
if !ok || err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = db.Exec("NOTIFY notify_test")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = expectNotification(t, channel, "notify_test", "")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ok, err = l.Unlisten("notify_test")
|
||||||
|
if !ok || err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = db.Exec("NOTIFY notify_test")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = expectNoNotification(t, channel)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestConnUnlistenAll(t *testing.T) {
|
||||||
|
l, channel := newTestListenerConn(t)
|
||||||
|
|
||||||
|
defer l.Close()
|
||||||
|
|
||||||
|
db := openTestConn(t)
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
ok, err := l.Listen("notify_test")
|
||||||
|
if !ok || err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = db.Exec("NOTIFY notify_test")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = expectNotification(t, channel, "notify_test", "")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ok, err = l.UnlistenAll()
|
||||||
|
if !ok || err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = db.Exec("NOTIFY notify_test")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = expectNoNotification(t, channel)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestConnClose(t *testing.T) {
|
||||||
|
l, _ := newTestListenerConn(t)
|
||||||
|
defer l.Close()
|
||||||
|
|
||||||
|
err := l.Close()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
err = l.Close()
|
||||||
|
if err != errListenerConnClosed {
|
||||||
|
t.Fatalf("expected errListenerConnClosed; got %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestConnPing(t *testing.T) {
|
||||||
|
l, _ := newTestListenerConn(t)
|
||||||
|
defer l.Close()
|
||||||
|
err := l.Ping()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
err = l.Close()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
err = l.Ping()
|
||||||
|
if err != errListenerConnClosed {
|
||||||
|
t.Fatalf("expected errListenerConnClosed; got %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test for deadlock where a query fails while another one is queued
|
||||||
|
func TestConnExecDeadlock(t *testing.T) {
|
||||||
|
l, _ := newTestListenerConn(t)
|
||||||
|
defer l.Close()
|
||||||
|
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
wg.Add(2)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
l.ExecSimpleQuery("SELECT pg_sleep(60)")
|
||||||
|
wg.Done()
|
||||||
|
}()
|
||||||
|
runtime.Gosched()
|
||||||
|
go func() {
|
||||||
|
l.ExecSimpleQuery("SELECT 1")
|
||||||
|
wg.Done()
|
||||||
|
}()
|
||||||
|
// give the two goroutines some time to get into position
|
||||||
|
runtime.Gosched()
|
||||||
|
// calls Close on the net.Conn; equivalent to a network failure
|
||||||
|
l.Close()
|
||||||
|
|
||||||
|
defer time.AfterFunc(10*time.Second, func() {
|
||||||
|
panic("timed out")
|
||||||
|
}).Stop()
|
||||||
|
wg.Wait()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test for ListenerConn being closed while a slow query is executing
|
||||||
|
func TestListenerConnCloseWhileQueryIsExecuting(t *testing.T) {
|
||||||
|
l, _ := newTestListenerConn(t)
|
||||||
|
defer l.Close()
|
||||||
|
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
wg.Add(1)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
sent, err := l.ExecSimpleQuery("SELECT pg_sleep(60)")
|
||||||
|
if sent {
|
||||||
|
panic("expected sent=false")
|
||||||
|
}
|
||||||
|
// could be any of a number of errors
|
||||||
|
if err == nil {
|
||||||
|
panic("expected error")
|
||||||
|
}
|
||||||
|
wg.Done()
|
||||||
|
}()
|
||||||
|
// give the above goroutine some time to get into position
|
||||||
|
runtime.Gosched()
|
||||||
|
err := l.Close()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
defer time.AfterFunc(10*time.Second, func() {
|
||||||
|
panic("timed out")
|
||||||
|
}).Stop()
|
||||||
|
wg.Wait()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNotifyExtra(t *testing.T) {
|
||||||
|
db := openTestConn(t)
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
if getServerVersion(t, db) < 90000 {
|
||||||
|
t.Skip("skipping NOTIFY payload test since the server does not appear to support it")
|
||||||
|
}
|
||||||
|
|
||||||
|
l, channel := newTestListenerConn(t)
|
||||||
|
defer l.Close()
|
||||||
|
|
||||||
|
ok, err := l.Listen("notify_test")
|
||||||
|
if !ok || err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = db.Exec("NOTIFY notify_test, 'something'")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = expectNotification(t, channel, "notify_test", "something")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// create a new test listener and also set the timeouts
|
||||||
|
func newTestListenerTimeout(t *testing.T, min time.Duration, max time.Duration) (*Listener, <-chan ListenerEventType) {
|
||||||
|
datname := os.Getenv("PGDATABASE")
|
||||||
|
sslmode := os.Getenv("PGSSLMODE")
|
||||||
|
|
||||||
|
if datname == "" {
|
||||||
|
os.Setenv("PGDATABASE", "pqgotest")
|
||||||
|
}
|
||||||
|
|
||||||
|
if sslmode == "" {
|
||||||
|
os.Setenv("PGSSLMODE", "disable")
|
||||||
|
}
|
||||||
|
|
||||||
|
eventch := make(chan ListenerEventType, 16)
|
||||||
|
l := NewListener("", min, max, func(t ListenerEventType, err error) { eventch <- t })
|
||||||
|
err := expectEvent(t, eventch, ListenerEventConnected)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
return l, eventch
|
||||||
|
}
|
||||||
|
|
||||||
|
func newTestListener(t *testing.T) (*Listener, <-chan ListenerEventType) {
|
||||||
|
return newTestListenerTimeout(t, time.Hour, time.Hour)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestListenerListen(t *testing.T) {
|
||||||
|
l, _ := newTestListener(t)
|
||||||
|
defer l.Close()
|
||||||
|
|
||||||
|
db := openTestConn(t)
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
err := l.Listen("notify_listen_test")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = db.Exec("NOTIFY notify_listen_test")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = expectNotification(t, l.Notify, "notify_listen_test", "")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestListenerUnlisten(t *testing.T) {
|
||||||
|
l, _ := newTestListener(t)
|
||||||
|
defer l.Close()
|
||||||
|
|
||||||
|
db := openTestConn(t)
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
err := l.Listen("notify_listen_test")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = db.Exec("NOTIFY notify_listen_test")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = l.Unlisten("notify_listen_test")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = expectNotification(t, l.Notify, "notify_listen_test", "")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = db.Exec("NOTIFY notify_listen_test")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = expectNoNotification(t, l.Notify)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestListenerUnlistenAll(t *testing.T) {
|
||||||
|
l, _ := newTestListener(t)
|
||||||
|
defer l.Close()
|
||||||
|
|
||||||
|
db := openTestConn(t)
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
err := l.Listen("notify_listen_test")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = db.Exec("NOTIFY notify_listen_test")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = l.UnlistenAll()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = expectNotification(t, l.Notify, "notify_listen_test", "")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = db.Exec("NOTIFY notify_listen_test")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = expectNoNotification(t, l.Notify)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestListenerFailedQuery(t *testing.T) {
|
||||||
|
l, eventch := newTestListener(t)
|
||||||
|
defer l.Close()
|
||||||
|
|
||||||
|
db := openTestConn(t)
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
err := l.Listen("notify_listen_test")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = db.Exec("NOTIFY notify_listen_test")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = expectNotification(t, l.Notify, "notify_listen_test", "")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// shouldn't cause a disconnect
|
||||||
|
ok, err := l.cn.ExecSimpleQuery("SELECT error")
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("could not send query to server: %v", err)
|
||||||
|
}
|
||||||
|
_, ok = err.(PGError)
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("unexpected error %v", err)
|
||||||
|
}
|
||||||
|
err = expectNoEvent(t, eventch)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// should still work
|
||||||
|
_, err = db.Exec("NOTIFY notify_listen_test")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = expectNotification(t, l.Notify, "notify_listen_test", "")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestListenerReconnect(t *testing.T) {
|
||||||
|
l, eventch := newTestListenerTimeout(t, 20*time.Millisecond, time.Hour)
|
||||||
|
defer l.Close()
|
||||||
|
|
||||||
|
db := openTestConn(t)
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
err := l.Listen("notify_listen_test")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = db.Exec("NOTIFY notify_listen_test")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = expectNotification(t, l.Notify, "notify_listen_test", "")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// kill the connection and make sure it comes back up
|
||||||
|
ok, err := l.cn.ExecSimpleQuery("SELECT pg_terminate_backend(pg_backend_pid())")
|
||||||
|
if ok {
|
||||||
|
t.Fatalf("could not kill the connection: %v", err)
|
||||||
|
}
|
||||||
|
if err != io.EOF {
|
||||||
|
t.Fatalf("unexpected error %v", err)
|
||||||
|
}
|
||||||
|
err = expectEvent(t, eventch, ListenerEventDisconnected)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
err = expectEvent(t, eventch, ListenerEventReconnected)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// should still work
|
||||||
|
_, err = db.Exec("NOTIFY notify_listen_test")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// should get nil after Reconnected
|
||||||
|
err = expectNotification(t, l.Notify, "", "")
|
||||||
|
if err != errNilNotification {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = expectNotification(t, l.Notify, "notify_listen_test", "")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestListenerClose(t *testing.T) {
|
||||||
|
l, _ := newTestListenerTimeout(t, 20*time.Millisecond, time.Hour)
|
||||||
|
defer l.Close()
|
||||||
|
|
||||||
|
err := l.Close()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
err = l.Close()
|
||||||
|
if err != errListenerClosed {
|
||||||
|
t.Fatalf("expected errListenerClosed; got %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestListenerPing(t *testing.T) {
|
||||||
|
l, _ := newTestListenerTimeout(t, 20*time.Millisecond, time.Hour)
|
||||||
|
defer l.Close()
|
||||||
|
|
||||||
|
err := l.Ping()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = l.Close()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = l.Ping()
|
||||||
|
if err != errListenerClosed {
|
||||||
|
t.Fatalf("expected errListenerClosed; got %v", err)
|
||||||
|
}
|
||||||
|
}
|
218
vendor/github.com/lib/pq/rows_test.go
generated
vendored
Normal file
218
vendor/github.com/lib/pq/rows_test.go
generated
vendored
Normal file
|
@ -0,0 +1,218 @@
|
||||||
|
package pq
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/lib/pq/oid"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestDataTypeName(t *testing.T) {
|
||||||
|
tts := []struct {
|
||||||
|
typ oid.Oid
|
||||||
|
name string
|
||||||
|
}{
|
||||||
|
{oid.T_int8, "INT8"},
|
||||||
|
{oid.T_int4, "INT4"},
|
||||||
|
{oid.T_int2, "INT2"},
|
||||||
|
{oid.T_varchar, "VARCHAR"},
|
||||||
|
{oid.T_text, "TEXT"},
|
||||||
|
{oid.T_bool, "BOOL"},
|
||||||
|
{oid.T_numeric, "NUMERIC"},
|
||||||
|
{oid.T_date, "DATE"},
|
||||||
|
{oid.T_time, "TIME"},
|
||||||
|
{oid.T_timetz, "TIMETZ"},
|
||||||
|
{oid.T_timestamp, "TIMESTAMP"},
|
||||||
|
{oid.T_timestamptz, "TIMESTAMPTZ"},
|
||||||
|
{oid.T_bytea, "BYTEA"},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, tt := range tts {
|
||||||
|
dt := fieldDesc{OID: tt.typ}
|
||||||
|
if name := dt.Name(); name != tt.name {
|
||||||
|
t.Errorf("(%d) got: %s want: %s", i, name, tt.name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDataType(t *testing.T) {
|
||||||
|
tts := []struct {
|
||||||
|
typ oid.Oid
|
||||||
|
kind reflect.Kind
|
||||||
|
}{
|
||||||
|
{oid.T_int8, reflect.Int64},
|
||||||
|
{oid.T_int4, reflect.Int32},
|
||||||
|
{oid.T_int2, reflect.Int16},
|
||||||
|
{oid.T_varchar, reflect.String},
|
||||||
|
{oid.T_text, reflect.String},
|
||||||
|
{oid.T_bool, reflect.Bool},
|
||||||
|
{oid.T_date, reflect.Struct},
|
||||||
|
{oid.T_time, reflect.Struct},
|
||||||
|
{oid.T_timetz, reflect.Struct},
|
||||||
|
{oid.T_timestamp, reflect.Struct},
|
||||||
|
{oid.T_timestamptz, reflect.Struct},
|
||||||
|
{oid.T_bytea, reflect.Slice},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, tt := range tts {
|
||||||
|
dt := fieldDesc{OID: tt.typ}
|
||||||
|
if kind := dt.Type().Kind(); kind != tt.kind {
|
||||||
|
t.Errorf("(%d) got: %s want: %s", i, kind, tt.kind)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDataTypeLength(t *testing.T) {
|
||||||
|
tts := []struct {
|
||||||
|
typ oid.Oid
|
||||||
|
len int
|
||||||
|
mod int
|
||||||
|
length int64
|
||||||
|
ok bool
|
||||||
|
}{
|
||||||
|
{oid.T_int4, 0, -1, 0, false},
|
||||||
|
{oid.T_varchar, 65535, 9, 5, true},
|
||||||
|
{oid.T_text, 65535, -1, math.MaxInt64, true},
|
||||||
|
{oid.T_bytea, 65535, -1, math.MaxInt64, true},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, tt := range tts {
|
||||||
|
dt := fieldDesc{OID: tt.typ, Len: tt.len, Mod: tt.mod}
|
||||||
|
if l, k := dt.Length(); k != tt.ok || l != tt.length {
|
||||||
|
t.Errorf("(%d) got: %d, %t want: %d, %t", i, l, k, tt.length, tt.ok)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDataTypePrecisionScale(t *testing.T) {
|
||||||
|
tts := []struct {
|
||||||
|
typ oid.Oid
|
||||||
|
mod int
|
||||||
|
precision, scale int64
|
||||||
|
ok bool
|
||||||
|
}{
|
||||||
|
{oid.T_int4, -1, 0, 0, false},
|
||||||
|
{oid.T_numeric, 589830, 9, 2, true},
|
||||||
|
{oid.T_text, -1, 0, 0, false},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, tt := range tts {
|
||||||
|
dt := fieldDesc{OID: tt.typ, Mod: tt.mod}
|
||||||
|
p, s, k := dt.PrecisionScale()
|
||||||
|
if k != tt.ok {
|
||||||
|
t.Errorf("(%d) got: %t want: %t", i, k, tt.ok)
|
||||||
|
}
|
||||||
|
if p != tt.precision {
|
||||||
|
t.Errorf("(%d) wrong precision got: %d want: %d", i, p, tt.precision)
|
||||||
|
}
|
||||||
|
if s != tt.scale {
|
||||||
|
t.Errorf("(%d) wrong scale got: %d want: %d", i, s, tt.scale)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRowsColumnTypes(t *testing.T) {
|
||||||
|
columnTypesTests := []struct {
|
||||||
|
Name string
|
||||||
|
TypeName string
|
||||||
|
Length struct {
|
||||||
|
Len int64
|
||||||
|
OK bool
|
||||||
|
}
|
||||||
|
DecimalSize struct {
|
||||||
|
Precision int64
|
||||||
|
Scale int64
|
||||||
|
OK bool
|
||||||
|
}
|
||||||
|
ScanType reflect.Type
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
Name: "a",
|
||||||
|
TypeName: "INT4",
|
||||||
|
Length: struct {
|
||||||
|
Len int64
|
||||||
|
OK bool
|
||||||
|
}{
|
||||||
|
Len: 0,
|
||||||
|
OK: false,
|
||||||
|
},
|
||||||
|
DecimalSize: struct {
|
||||||
|
Precision int64
|
||||||
|
Scale int64
|
||||||
|
OK bool
|
||||||
|
}{
|
||||||
|
Precision: 0,
|
||||||
|
Scale: 0,
|
||||||
|
OK: false,
|
||||||
|
},
|
||||||
|
ScanType: reflect.TypeOf(int32(0)),
|
||||||
|
}, {
|
||||||
|
Name: "bar",
|
||||||
|
TypeName: "TEXT",
|
||||||
|
Length: struct {
|
||||||
|
Len int64
|
||||||
|
OK bool
|
||||||
|
}{
|
||||||
|
Len: math.MaxInt64,
|
||||||
|
OK: true,
|
||||||
|
},
|
||||||
|
DecimalSize: struct {
|
||||||
|
Precision int64
|
||||||
|
Scale int64
|
||||||
|
OK bool
|
||||||
|
}{
|
||||||
|
Precision: 0,
|
||||||
|
Scale: 0,
|
||||||
|
OK: false,
|
||||||
|
},
|
||||||
|
ScanType: reflect.TypeOf(""),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
db := openTestConn(t)
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
rows, err := db.Query("SELECT 1 AS a, text 'bar' AS bar, 1.28::numeric(9, 2) AS dec")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
columns, err := rows.ColumnTypes()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if len(columns) != 3 {
|
||||||
|
t.Errorf("expected 3 columns found %d", len(columns))
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, tt := range columnTypesTests {
|
||||||
|
c := columns[i]
|
||||||
|
if c.Name() != tt.Name {
|
||||||
|
t.Errorf("(%d) got: %s, want: %s", i, c.Name(), tt.Name)
|
||||||
|
}
|
||||||
|
if c.DatabaseTypeName() != tt.TypeName {
|
||||||
|
t.Errorf("(%d) got: %s, want: %s", i, c.DatabaseTypeName(), tt.TypeName)
|
||||||
|
}
|
||||||
|
l, ok := c.Length()
|
||||||
|
if l != tt.Length.Len {
|
||||||
|
t.Errorf("(%d) got: %d, want: %d", i, l, tt.Length.Len)
|
||||||
|
}
|
||||||
|
if ok != tt.Length.OK {
|
||||||
|
t.Errorf("(%d) got: %t, want: %t", i, ok, tt.Length.OK)
|
||||||
|
}
|
||||||
|
p, s, ok := c.DecimalSize()
|
||||||
|
if p != tt.DecimalSize.Precision {
|
||||||
|
t.Errorf("(%d) got: %d, want: %d", i, p, tt.DecimalSize.Precision)
|
||||||
|
}
|
||||||
|
if s != tt.DecimalSize.Scale {
|
||||||
|
t.Errorf("(%d) got: %d, want: %d", i, s, tt.DecimalSize.Scale)
|
||||||
|
}
|
||||||
|
if ok != tt.DecimalSize.OK {
|
||||||
|
t.Errorf("(%d) got: %t, want: %t", i, ok, tt.DecimalSize.OK)
|
||||||
|
}
|
||||||
|
if c.ScanType() != tt.ScanType {
|
||||||
|
t.Errorf("(%d) got: %v, want: %v", i, c.ScanType(), tt.ScanType)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
264
vendor/github.com/lib/pq/scram/scram.go
generated
vendored
Normal file
264
vendor/github.com/lib/pq/scram/scram.go
generated
vendored
Normal file
|
@ -0,0 +1,264 @@
|
||||||
|
// Copyright (c) 2014 - Gustavo Niemeyer <gustavo@niemeyer.net>
|
||||||
|
//
|
||||||
|
// All rights reserved.
|
||||||
|
//
|
||||||
|
// Redistribution and use in source and binary forms, with or without
|
||||||
|
// modification, are permitted provided that the following conditions are met:
|
||||||
|
//
|
||||||
|
// 1. Redistributions of source code must retain the above copyright notice, this
|
||||||
|
// list of conditions and the following disclaimer.
|
||||||
|
// 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
// this list of conditions and the following disclaimer in the documentation
|
||||||
|
// and/or other materials provided with the distribution.
|
||||||
|
//
|
||||||
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||||
|
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||||
|
// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||||
|
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
// Pacakage scram implements a SCRAM-{SHA-1,etc} client per RFC5802.
|
||||||
|
//
|
||||||
|
// http://tools.ietf.org/html/rfc5802
|
||||||
|
//
|
||||||
|
package scram
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto/hmac"
|
||||||
|
"crypto/rand"
|
||||||
|
"encoding/base64"
|
||||||
|
"fmt"
|
||||||
|
"hash"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Client implements a SCRAM-* client (SCRAM-SHA-1, SCRAM-SHA-256, etc).
|
||||||
|
//
|
||||||
|
// A Client may be used within a SASL conversation with logic resembling:
|
||||||
|
//
|
||||||
|
// var in []byte
|
||||||
|
// var client = scram.NewClient(sha1.New, user, pass)
|
||||||
|
// for client.Step(in) {
|
||||||
|
// out := client.Out()
|
||||||
|
// // send out to server
|
||||||
|
// in := serverOut
|
||||||
|
// }
|
||||||
|
// if client.Err() != nil {
|
||||||
|
// // auth failed
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
type Client struct {
|
||||||
|
newHash func() hash.Hash
|
||||||
|
|
||||||
|
user string
|
||||||
|
pass string
|
||||||
|
step int
|
||||||
|
out bytes.Buffer
|
||||||
|
err error
|
||||||
|
|
||||||
|
clientNonce []byte
|
||||||
|
serverNonce []byte
|
||||||
|
saltedPass []byte
|
||||||
|
authMsg bytes.Buffer
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewClient returns a new SCRAM-* client with the provided hash algorithm.
|
||||||
|
//
|
||||||
|
// For SCRAM-SHA-256, for example, use:
|
||||||
|
//
|
||||||
|
// client := scram.NewClient(sha256.New, user, pass)
|
||||||
|
//
|
||||||
|
func NewClient(newHash func() hash.Hash, user, pass string) *Client {
|
||||||
|
c := &Client{
|
||||||
|
newHash: newHash,
|
||||||
|
user: user,
|
||||||
|
pass: pass,
|
||||||
|
}
|
||||||
|
c.out.Grow(256)
|
||||||
|
c.authMsg.Grow(256)
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
// Out returns the data to be sent to the server in the current step.
|
||||||
|
func (c *Client) Out() []byte {
|
||||||
|
if c.out.Len() == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return c.out.Bytes()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Err returns the error that ocurred, or nil if there were no errors.
|
||||||
|
func (c *Client) Err() error {
|
||||||
|
return c.err
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetNonce sets the client nonce to the provided value.
|
||||||
|
// If not set, the nonce is generated automatically out of crypto/rand on the first step.
|
||||||
|
func (c *Client) SetNonce(nonce []byte) {
|
||||||
|
c.clientNonce = nonce
|
||||||
|
}
|
||||||
|
|
||||||
|
var escaper = strings.NewReplacer("=", "=3D", ",", "=2C")
|
||||||
|
|
||||||
|
// Step processes the incoming data from the server and makes the
|
||||||
|
// next round of data for the server available via Client.Out.
|
||||||
|
// Step returns false if there are no errors and more data is
|
||||||
|
// still expected.
|
||||||
|
func (c *Client) Step(in []byte) bool {
|
||||||
|
c.out.Reset()
|
||||||
|
if c.step > 2 || c.err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
c.step++
|
||||||
|
switch c.step {
|
||||||
|
case 1:
|
||||||
|
c.err = c.step1(in)
|
||||||
|
case 2:
|
||||||
|
c.err = c.step2(in)
|
||||||
|
case 3:
|
||||||
|
c.err = c.step3(in)
|
||||||
|
}
|
||||||
|
return c.step > 2 || c.err != nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) step1(in []byte) error {
|
||||||
|
if len(c.clientNonce) == 0 {
|
||||||
|
const nonceLen = 16
|
||||||
|
buf := make([]byte, nonceLen+b64.EncodedLen(nonceLen))
|
||||||
|
if _, err := rand.Read(buf[:nonceLen]); err != nil {
|
||||||
|
return fmt.Errorf("cannot read random SCRAM-SHA-256 nonce from operating system: %v", err)
|
||||||
|
}
|
||||||
|
c.clientNonce = buf[nonceLen:]
|
||||||
|
b64.Encode(c.clientNonce, buf[:nonceLen])
|
||||||
|
}
|
||||||
|
c.authMsg.WriteString("n=")
|
||||||
|
escaper.WriteString(&c.authMsg, c.user)
|
||||||
|
c.authMsg.WriteString(",r=")
|
||||||
|
c.authMsg.Write(c.clientNonce)
|
||||||
|
|
||||||
|
c.out.WriteString("n,,")
|
||||||
|
c.out.Write(c.authMsg.Bytes())
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var b64 = base64.StdEncoding
|
||||||
|
|
||||||
|
func (c *Client) step2(in []byte) error {
|
||||||
|
c.authMsg.WriteByte(',')
|
||||||
|
c.authMsg.Write(in)
|
||||||
|
|
||||||
|
fields := bytes.Split(in, []byte(","))
|
||||||
|
if len(fields) != 3 {
|
||||||
|
return fmt.Errorf("expected 3 fields in first SCRAM-SHA-256 server message, got %d: %q", len(fields), in)
|
||||||
|
}
|
||||||
|
if !bytes.HasPrefix(fields[0], []byte("r=")) || len(fields[0]) < 2 {
|
||||||
|
return fmt.Errorf("server sent an invalid SCRAM-SHA-256 nonce: %q", fields[0])
|
||||||
|
}
|
||||||
|
if !bytes.HasPrefix(fields[1], []byte("s=")) || len(fields[1]) < 6 {
|
||||||
|
return fmt.Errorf("server sent an invalid SCRAM-SHA-256 salt: %q", fields[1])
|
||||||
|
}
|
||||||
|
if !bytes.HasPrefix(fields[2], []byte("i=")) || len(fields[2]) < 6 {
|
||||||
|
return fmt.Errorf("server sent an invalid SCRAM-SHA-256 iteration count: %q", fields[2])
|
||||||
|
}
|
||||||
|
|
||||||
|
c.serverNonce = fields[0][2:]
|
||||||
|
if !bytes.HasPrefix(c.serverNonce, c.clientNonce) {
|
||||||
|
return fmt.Errorf("server SCRAM-SHA-256 nonce is not prefixed by client nonce: got %q, want %q+\"...\"", c.serverNonce, c.clientNonce)
|
||||||
|
}
|
||||||
|
|
||||||
|
salt := make([]byte, b64.DecodedLen(len(fields[1][2:])))
|
||||||
|
n, err := b64.Decode(salt, fields[1][2:])
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("cannot decode SCRAM-SHA-256 salt sent by server: %q", fields[1])
|
||||||
|
}
|
||||||
|
salt = salt[:n]
|
||||||
|
iterCount, err := strconv.Atoi(string(fields[2][2:]))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("server sent an invalid SCRAM-SHA-256 iteration count: %q", fields[2])
|
||||||
|
}
|
||||||
|
c.saltPassword(salt, iterCount)
|
||||||
|
|
||||||
|
c.authMsg.WriteString(",c=biws,r=")
|
||||||
|
c.authMsg.Write(c.serverNonce)
|
||||||
|
|
||||||
|
c.out.WriteString("c=biws,r=")
|
||||||
|
c.out.Write(c.serverNonce)
|
||||||
|
c.out.WriteString(",p=")
|
||||||
|
c.out.Write(c.clientProof())
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) step3(in []byte) error {
|
||||||
|
var isv, ise bool
|
||||||
|
var fields = bytes.Split(in, []byte(","))
|
||||||
|
if len(fields) == 1 {
|
||||||
|
isv = bytes.HasPrefix(fields[0], []byte("v="))
|
||||||
|
ise = bytes.HasPrefix(fields[0], []byte("e="))
|
||||||
|
}
|
||||||
|
if ise {
|
||||||
|
return fmt.Errorf("SCRAM-SHA-256 authentication error: %s", fields[0][2:])
|
||||||
|
} else if !isv {
|
||||||
|
return fmt.Errorf("unsupported SCRAM-SHA-256 final message from server: %q", in)
|
||||||
|
}
|
||||||
|
if !bytes.Equal(c.serverSignature(), fields[0][2:]) {
|
||||||
|
return fmt.Errorf("cannot authenticate SCRAM-SHA-256 server signature: %q", fields[0][2:])
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) saltPassword(salt []byte, iterCount int) {
|
||||||
|
mac := hmac.New(c.newHash, []byte(c.pass))
|
||||||
|
mac.Write(salt)
|
||||||
|
mac.Write([]byte{0, 0, 0, 1})
|
||||||
|
ui := mac.Sum(nil)
|
||||||
|
hi := make([]byte, len(ui))
|
||||||
|
copy(hi, ui)
|
||||||
|
for i := 1; i < iterCount; i++ {
|
||||||
|
mac.Reset()
|
||||||
|
mac.Write(ui)
|
||||||
|
mac.Sum(ui[:0])
|
||||||
|
for j, b := range ui {
|
||||||
|
hi[j] ^= b
|
||||||
|
}
|
||||||
|
}
|
||||||
|
c.saltedPass = hi
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) clientProof() []byte {
|
||||||
|
mac := hmac.New(c.newHash, c.saltedPass)
|
||||||
|
mac.Write([]byte("Client Key"))
|
||||||
|
clientKey := mac.Sum(nil)
|
||||||
|
hash := c.newHash()
|
||||||
|
hash.Write(clientKey)
|
||||||
|
storedKey := hash.Sum(nil)
|
||||||
|
mac = hmac.New(c.newHash, storedKey)
|
||||||
|
mac.Write(c.authMsg.Bytes())
|
||||||
|
clientProof := mac.Sum(nil)
|
||||||
|
for i, b := range clientKey {
|
||||||
|
clientProof[i] ^= b
|
||||||
|
}
|
||||||
|
clientProof64 := make([]byte, b64.EncodedLen(len(clientProof)))
|
||||||
|
b64.Encode(clientProof64, clientProof)
|
||||||
|
return clientProof64
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) serverSignature() []byte {
|
||||||
|
mac := hmac.New(c.newHash, c.saltedPass)
|
||||||
|
mac.Write([]byte("Server Key"))
|
||||||
|
serverKey := mac.Sum(nil)
|
||||||
|
|
||||||
|
mac = hmac.New(c.newHash, serverKey)
|
||||||
|
mac.Write(c.authMsg.Bytes())
|
||||||
|
serverSignature := mac.Sum(nil)
|
||||||
|
|
||||||
|
encoded := make([]byte, b64.EncodedLen(len(serverSignature)))
|
||||||
|
b64.Encode(encoded, serverSignature)
|
||||||
|
return encoded
|
||||||
|
}
|
8
vendor/github.com/lib/pq/ssl.go
generated
vendored
8
vendor/github.com/lib/pq/ssl.go
generated
vendored
|
@ -58,7 +58,13 @@ func ssl(o values) (func(net.Conn) (net.Conn, error), error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
sslRenegotiation(&tlsConf)
|
|
||||||
|
// Accept renegotiation requests initiated by the backend.
|
||||||
|
//
|
||||||
|
// Renegotiation was deprecated then removed from PostgreSQL 9.5, but
|
||||||
|
// the default configuration of older versions has it enabled. Redshift
|
||||||
|
// also initiates renegotiations and cannot be reconfigured.
|
||||||
|
tlsConf.Renegotiation = tls.RenegotiateFreelyAsClient
|
||||||
|
|
||||||
return func(conn net.Conn) (net.Conn, error) {
|
return func(conn net.Conn) (net.Conn, error) {
|
||||||
client := tls.Client(conn, &tlsConf)
|
client := tls.Client(conn, &tlsConf)
|
||||||
|
|
14
vendor/github.com/lib/pq/ssl_go1.7.go
generated
vendored
14
vendor/github.com/lib/pq/ssl_go1.7.go
generated
vendored
|
@ -1,14 +0,0 @@
|
||||||
// +build go1.7
|
|
||||||
|
|
||||||
package pq
|
|
||||||
|
|
||||||
import "crypto/tls"
|
|
||||||
|
|
||||||
// Accept renegotiation requests initiated by the backend.
|
|
||||||
//
|
|
||||||
// Renegotiation was deprecated then removed from PostgreSQL 9.5, but
|
|
||||||
// the default configuration of older versions has it enabled. Redshift
|
|
||||||
// also initiates renegotiations and cannot be reconfigured.
|
|
||||||
func sslRenegotiation(conf *tls.Config) {
|
|
||||||
conf.Renegotiation = tls.RenegotiateFreelyAsClient
|
|
||||||
}
|
|
8
vendor/github.com/lib/pq/ssl_renegotiation.go
generated
vendored
8
vendor/github.com/lib/pq/ssl_renegotiation.go
generated
vendored
|
@ -1,8 +0,0 @@
|
||||||
// +build !go1.7
|
|
||||||
|
|
||||||
package pq
|
|
||||||
|
|
||||||
import "crypto/tls"
|
|
||||||
|
|
||||||
// Renegotiation is not supported by crypto/tls until Go 1.7.
|
|
||||||
func sslRenegotiation(*tls.Config) {}
|
|
279
vendor/github.com/lib/pq/ssl_test.go
generated
vendored
Normal file
279
vendor/github.com/lib/pq/ssl_test.go
generated
vendored
Normal file
|
@ -0,0 +1,279 @@
|
||||||
|
package pq
|
||||||
|
|
||||||
|
// This file contains SSL tests
|
||||||
|
|
||||||
|
import (
|
||||||
|
_ "crypto/sha256"
|
||||||
|
"crypto/x509"
|
||||||
|
"database/sql"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func maybeSkipSSLTests(t *testing.T) {
|
||||||
|
// Require some special variables for testing certificates
|
||||||
|
if os.Getenv("PQSSLCERTTEST_PATH") == "" {
|
||||||
|
t.Skip("PQSSLCERTTEST_PATH not set, skipping SSL tests")
|
||||||
|
}
|
||||||
|
|
||||||
|
value := os.Getenv("PQGOSSLTESTS")
|
||||||
|
if value == "" || value == "0" {
|
||||||
|
t.Skip("PQGOSSLTESTS not enabled, skipping SSL tests")
|
||||||
|
} else if value != "1" {
|
||||||
|
t.Fatalf("unexpected value %q for PQGOSSLTESTS", value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func openSSLConn(t *testing.T, conninfo string) (*sql.DB, error) {
|
||||||
|
db, err := openTestConnConninfo(conninfo)
|
||||||
|
if err != nil {
|
||||||
|
// should never fail
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
// Do something with the connection to see whether it's working or not.
|
||||||
|
tx, err := db.Begin()
|
||||||
|
if err == nil {
|
||||||
|
return db, tx.Rollback()
|
||||||
|
}
|
||||||
|
_ = db.Close()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkSSLSetup(t *testing.T, conninfo string) {
|
||||||
|
_, err := openSSLConn(t, conninfo)
|
||||||
|
if pge, ok := err.(*Error); ok {
|
||||||
|
if pge.Code.Name() != "invalid_authorization_specification" {
|
||||||
|
t.Fatalf("unexpected error code '%s'", pge.Code.Name())
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
t.Fatalf("expected %T, got %v", (*Error)(nil), err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Connect over SSL and run a simple query to test the basics
|
||||||
|
func TestSSLConnection(t *testing.T) {
|
||||||
|
maybeSkipSSLTests(t)
|
||||||
|
// Environment sanity check: should fail without SSL
|
||||||
|
checkSSLSetup(t, "sslmode=disable user=pqgossltest")
|
||||||
|
|
||||||
|
db, err := openSSLConn(t, "sslmode=require user=pqgossltest")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
rows, err := db.Query("SELECT 1")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
rows.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test sslmode=verify-full
|
||||||
|
func TestSSLVerifyFull(t *testing.T) {
|
||||||
|
maybeSkipSSLTests(t)
|
||||||
|
// Environment sanity check: should fail without SSL
|
||||||
|
checkSSLSetup(t, "sslmode=disable user=pqgossltest")
|
||||||
|
|
||||||
|
// Not OK according to the system CA
|
||||||
|
_, err := openSSLConn(t, "host=postgres sslmode=verify-full user=pqgossltest")
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("expected error")
|
||||||
|
}
|
||||||
|
_, ok := err.(x509.UnknownAuthorityError)
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("expected x509.UnknownAuthorityError, got %#+v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
rootCertPath := filepath.Join(os.Getenv("PQSSLCERTTEST_PATH"), "root.crt")
|
||||||
|
rootCert := "sslrootcert=" + rootCertPath + " "
|
||||||
|
// No match on Common Name
|
||||||
|
_, err = openSSLConn(t, rootCert+"host=127.0.0.1 sslmode=verify-full user=pqgossltest")
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("expected error")
|
||||||
|
}
|
||||||
|
_, ok = err.(x509.HostnameError)
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("expected x509.HostnameError, got %#+v", err)
|
||||||
|
}
|
||||||
|
// OK
|
||||||
|
_, err = openSSLConn(t, rootCert+"host=postgres sslmode=verify-full user=pqgossltest")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test sslmode=require sslrootcert=rootCertPath
|
||||||
|
func TestSSLRequireWithRootCert(t *testing.T) {
|
||||||
|
maybeSkipSSLTests(t)
|
||||||
|
// Environment sanity check: should fail without SSL
|
||||||
|
checkSSLSetup(t, "sslmode=disable user=pqgossltest")
|
||||||
|
|
||||||
|
bogusRootCertPath := filepath.Join(os.Getenv("PQSSLCERTTEST_PATH"), "bogus_root.crt")
|
||||||
|
bogusRootCert := "sslrootcert=" + bogusRootCertPath + " "
|
||||||
|
|
||||||
|
// Not OK according to the bogus CA
|
||||||
|
_, err := openSSLConn(t, bogusRootCert+"host=postgres sslmode=require user=pqgossltest")
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("expected error")
|
||||||
|
}
|
||||||
|
_, ok := err.(x509.UnknownAuthorityError)
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("expected x509.UnknownAuthorityError, got %s, %#+v", err, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
nonExistentCertPath := filepath.Join(os.Getenv("PQSSLCERTTEST_PATH"), "non_existent.crt")
|
||||||
|
nonExistentCert := "sslrootcert=" + nonExistentCertPath + " "
|
||||||
|
|
||||||
|
// No match on Common Name, but that's OK because we're not validating anything.
|
||||||
|
_, err = openSSLConn(t, nonExistentCert+"host=127.0.0.1 sslmode=require user=pqgossltest")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
rootCertPath := filepath.Join(os.Getenv("PQSSLCERTTEST_PATH"), "root.crt")
|
||||||
|
rootCert := "sslrootcert=" + rootCertPath + " "
|
||||||
|
|
||||||
|
// No match on Common Name, but that's OK because we're not validating the CN.
|
||||||
|
_, err = openSSLConn(t, rootCert+"host=127.0.0.1 sslmode=require user=pqgossltest")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
// Everything OK
|
||||||
|
_, err = openSSLConn(t, rootCert+"host=postgres sslmode=require user=pqgossltest")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test sslmode=verify-ca
|
||||||
|
func TestSSLVerifyCA(t *testing.T) {
|
||||||
|
maybeSkipSSLTests(t)
|
||||||
|
// Environment sanity check: should fail without SSL
|
||||||
|
checkSSLSetup(t, "sslmode=disable user=pqgossltest")
|
||||||
|
|
||||||
|
// Not OK according to the system CA
|
||||||
|
{
|
||||||
|
_, err := openSSLConn(t, "host=postgres sslmode=verify-ca user=pqgossltest")
|
||||||
|
if _, ok := err.(x509.UnknownAuthorityError); !ok {
|
||||||
|
t.Fatalf("expected %T, got %#+v", x509.UnknownAuthorityError{}, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Still not OK according to the system CA; empty sslrootcert is treated as unspecified.
|
||||||
|
{
|
||||||
|
_, err := openSSLConn(t, "host=postgres sslmode=verify-ca user=pqgossltest sslrootcert=''")
|
||||||
|
if _, ok := err.(x509.UnknownAuthorityError); !ok {
|
||||||
|
t.Fatalf("expected %T, got %#+v", x509.UnknownAuthorityError{}, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rootCertPath := filepath.Join(os.Getenv("PQSSLCERTTEST_PATH"), "root.crt")
|
||||||
|
rootCert := "sslrootcert=" + rootCertPath + " "
|
||||||
|
// No match on Common Name, but that's OK
|
||||||
|
if _, err := openSSLConn(t, rootCert+"host=127.0.0.1 sslmode=verify-ca user=pqgossltest"); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
// Everything OK
|
||||||
|
if _, err := openSSLConn(t, rootCert+"host=postgres sslmode=verify-ca user=pqgossltest"); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Authenticate over SSL using client certificates
|
||||||
|
func TestSSLClientCertificates(t *testing.T) {
|
||||||
|
maybeSkipSSLTests(t)
|
||||||
|
// Environment sanity check: should fail without SSL
|
||||||
|
checkSSLSetup(t, "sslmode=disable user=pqgossltest")
|
||||||
|
|
||||||
|
const baseinfo = "sslmode=require user=pqgosslcert"
|
||||||
|
|
||||||
|
// Certificate not specified, should fail
|
||||||
|
{
|
||||||
|
_, err := openSSLConn(t, baseinfo)
|
||||||
|
if pge, ok := err.(*Error); ok {
|
||||||
|
if pge.Code.Name() != "invalid_authorization_specification" {
|
||||||
|
t.Fatalf("unexpected error code '%s'", pge.Code.Name())
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
t.Fatalf("expected %T, got %v", (*Error)(nil), err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Empty certificate specified, should fail
|
||||||
|
{
|
||||||
|
_, err := openSSLConn(t, baseinfo+" sslcert=''")
|
||||||
|
if pge, ok := err.(*Error); ok {
|
||||||
|
if pge.Code.Name() != "invalid_authorization_specification" {
|
||||||
|
t.Fatalf("unexpected error code '%s'", pge.Code.Name())
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
t.Fatalf("expected %T, got %v", (*Error)(nil), err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Non-existent certificate specified, should fail
|
||||||
|
{
|
||||||
|
_, err := openSSLConn(t, baseinfo+" sslcert=/tmp/filedoesnotexist")
|
||||||
|
if pge, ok := err.(*Error); ok {
|
||||||
|
if pge.Code.Name() != "invalid_authorization_specification" {
|
||||||
|
t.Fatalf("unexpected error code '%s'", pge.Code.Name())
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
t.Fatalf("expected %T, got %v", (*Error)(nil), err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
certpath, ok := os.LookupEnv("PQSSLCERTTEST_PATH")
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("PQSSLCERTTEST_PATH not present in environment")
|
||||||
|
}
|
||||||
|
|
||||||
|
sslcert := filepath.Join(certpath, "postgresql.crt")
|
||||||
|
|
||||||
|
// Cert present, key not specified, should fail
|
||||||
|
{
|
||||||
|
_, err := openSSLConn(t, baseinfo+" sslcert="+sslcert)
|
||||||
|
if _, ok := err.(*os.PathError); !ok {
|
||||||
|
t.Fatalf("expected %T, got %#+v", (*os.PathError)(nil), err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cert present, empty key specified, should fail
|
||||||
|
{
|
||||||
|
_, err := openSSLConn(t, baseinfo+" sslcert="+sslcert+" sslkey=''")
|
||||||
|
if _, ok := err.(*os.PathError); !ok {
|
||||||
|
t.Fatalf("expected %T, got %#+v", (*os.PathError)(nil), err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cert present, non-existent key, should fail
|
||||||
|
{
|
||||||
|
_, err := openSSLConn(t, baseinfo+" sslcert="+sslcert+" sslkey=/tmp/filedoesnotexist")
|
||||||
|
if _, ok := err.(*os.PathError); !ok {
|
||||||
|
t.Fatalf("expected %T, got %#+v", (*os.PathError)(nil), err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Key has wrong permissions (passing the cert as the key), should fail
|
||||||
|
if _, err := openSSLConn(t, baseinfo+" sslcert="+sslcert+" sslkey="+sslcert); err != ErrSSLKeyHasWorldPermissions {
|
||||||
|
t.Fatalf("expected %s, got %#+v", ErrSSLKeyHasWorldPermissions, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
sslkey := filepath.Join(certpath, "postgresql.key")
|
||||||
|
|
||||||
|
// Should work
|
||||||
|
if db, err := openSSLConn(t, baseinfo+" sslcert="+sslcert+" sslkey="+sslkey); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
} else {
|
||||||
|
rows, err := db.Query("SELECT 1")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := rows.Close(); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := db.Close(); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
66
vendor/github.com/lib/pq/url_test.go
generated
vendored
Normal file
66
vendor/github.com/lib/pq/url_test.go
generated
vendored
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
package pq
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestSimpleParseURL(t *testing.T) {
|
||||||
|
expected := "host=hostname.remote"
|
||||||
|
str, err := ParseURL("postgres://hostname.remote")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if str != expected {
|
||||||
|
t.Fatalf("unexpected result from ParseURL:\n+ %v\n- %v", str, expected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIPv6LoopbackParseURL(t *testing.T) {
|
||||||
|
expected := "host=::1 port=1234"
|
||||||
|
str, err := ParseURL("postgres://[::1]:1234")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if str != expected {
|
||||||
|
t.Fatalf("unexpected result from ParseURL:\n+ %v\n- %v", str, expected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFullParseURL(t *testing.T) {
|
||||||
|
expected := `dbname=database host=hostname.remote password=top\ secret port=1234 user=username`
|
||||||
|
str, err := ParseURL("postgres://username:top%20secret@hostname.remote:1234/database")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if str != expected {
|
||||||
|
t.Fatalf("unexpected result from ParseURL:\n+ %s\n- %s", str, expected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInvalidProtocolParseURL(t *testing.T) {
|
||||||
|
_, err := ParseURL("http://hostname.remote")
|
||||||
|
switch err {
|
||||||
|
case nil:
|
||||||
|
t.Fatal("Expected an error from parsing invalid protocol")
|
||||||
|
default:
|
||||||
|
msg := "invalid connection protocol: http"
|
||||||
|
if err.Error() != msg {
|
||||||
|
t.Fatalf("Unexpected error message:\n+ %s\n- %s",
|
||||||
|
err.Error(), msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMinimalURL(t *testing.T) {
|
||||||
|
cs, err := ParseURL("postgres://")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if cs != "" {
|
||||||
|
t.Fatalf("expected blank connection string, got: %q", cs)
|
||||||
|
}
|
||||||
|
}
|
46
vendor/github.com/lib/pq/uuid_test.go
generated
vendored
Normal file
46
vendor/github.com/lib/pq/uuid_test.go
generated
vendored
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
package pq
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestDecodeUUIDBinaryError(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
_, err := decodeUUIDBinary([]byte{0x12, 0x34})
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("Expected error, got none")
|
||||||
|
}
|
||||||
|
if !strings.HasPrefix(err.Error(), "pq:") {
|
||||||
|
t.Errorf("Expected error to start with %q, got %q", "pq:", err.Error())
|
||||||
|
}
|
||||||
|
if !strings.Contains(err.Error(), "bad length: 2") {
|
||||||
|
t.Errorf("Expected error to contain length, got %q", err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkDecodeUUIDBinary(b *testing.B) {
|
||||||
|
x := []byte{0x03, 0xa3, 0x52, 0x2f, 0x89, 0x28, 0x49, 0x87, 0x84, 0xd6, 0x93, 0x7b, 0x36, 0xec, 0x27, 0x6f}
|
||||||
|
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
decodeUUIDBinary(x)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDecodeUUIDBackend(t *testing.T) {
|
||||||
|
db := openTestConn(t)
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
var s = "a0ecc91d-a13f-4fe4-9fce-7e09777cc70a"
|
||||||
|
var scanned interface{}
|
||||||
|
|
||||||
|
err := db.QueryRow(`SELECT $1::uuid`, s).Scan(&scanned)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Expected no error, got %v", err)
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(scanned, []byte(s)) {
|
||||||
|
t.Errorf("Expected []byte(%q), got %T(%q)", s, scanned, scanned)
|
||||||
|
}
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue