From 56ba42d9a95faf4cc7edf3b74f73da267ee153fd Mon Sep 17 00:00:00 2001 From: Brad Rydzewski Date: Tue, 27 Oct 2015 10:14:23 -0700 Subject: [PATCH] updated postgres dependency to fix #1259 --- vendor/github.com/lib/pq/README.md | 11 + vendor/github.com/lib/pq/bench_test.go | 15 +- vendor/github.com/lib/pq/buf.go | 30 +- vendor/github.com/lib/pq/certs/README | 3 + vendor/github.com/lib/pq/certs/postgresql.crt | 69 ++ vendor/github.com/lib/pq/certs/postgresql.key | 15 + vendor/github.com/lib/pq/certs/root.crt | 24 + vendor/github.com/lib/pq/certs/server.crt | 81 ++ vendor/github.com/lib/pq/certs/server.key | 27 + vendor/github.com/lib/pq/conn.go | 1007 ++++++++++++----- vendor/github.com/lib/pq/conn_test.go | 163 ++- vendor/github.com/lib/pq/conn_xact_test.go | 61 - vendor/github.com/lib/pq/copy.go | 76 +- vendor/github.com/lib/pq/copy_test.go | 138 +++ vendor/github.com/lib/pq/doc.go | 9 +- vendor/github.com/lib/pq/encode.go | 184 ++- vendor/github.com/lib/pq/encode_test.go | 370 +++++- vendor/github.com/lib/pq/error.go | 23 +- .../github.com/lib/pq/hstore/hstore_test.go | 6 +- .../github.com/lib/pq/listen_example/doc.go | 4 +- vendor/github.com/lib/pq/notify.go | 62 +- vendor/github.com/lib/pq/notify_test.go | 84 +- vendor/github.com/lib/pq/oid/gen.go | 2 +- vendor/github.com/lib/pq/ssl_test.go | 226 ++++ vendor/github.com/lib/pq/url.go | 2 +- vendor/github.com/lib/pq/user_posix.go | 19 +- vendor/github.com/lib/pq/user_windows.go | 2 +- 27 files changed, 2196 insertions(+), 517 deletions(-) create mode 100644 vendor/github.com/lib/pq/certs/README create mode 100644 vendor/github.com/lib/pq/certs/postgresql.crt create mode 100644 vendor/github.com/lib/pq/certs/postgresql.key create mode 100644 vendor/github.com/lib/pq/certs/root.crt create mode 100644 vendor/github.com/lib/pq/certs/server.crt create mode 100644 vendor/github.com/lib/pq/certs/server.key delete mode 100644 vendor/github.com/lib/pq/conn_xact_test.go create mode 100644 vendor/github.com/lib/pq/ssl_test.go diff --git a/vendor/github.com/lib/pq/README.md b/vendor/github.com/lib/pq/README.md index 36d05fdd..358d644f 100644 --- a/vendor/github.com/lib/pq/README.md +++ b/vendor/github.com/lib/pq/README.md @@ -55,9 +55,15 @@ code still exists in here. * Bjørn Madsen (aeons) * Blake Gentry (bgentry) * Brad Fitzpatrick (bradfitz) +* Charlie Melbye (cmelbye) +* Chris Bandy (cbandy) +* Chris Gilling (cgilling) * Chris Walsh (cwds) +* Dan Sosedoff (sosedoff) * Daniel Farina (fdr) * Eric Chlebek (echlebek) +* Eric Garrido (minusnine) +* Eric Urban (hydrogen18) * Everyone at The Go Team * Evan Shaw (edsrzf) * Ewan Chou (coocood) @@ -65,10 +71,12 @@ code still exists in here. * Fumin (fumin) * Gary Burd (garyburd) * Heroku (heroku) +* James Pozdena (jpoz) * Jason McVetta (jmcvetta) * Jeremy Jay (pbnjay) * Joakim Sernbrant (serbaut) * John Gallagher (jgallagher) +* Jonathan Rudenberg (titanous) * Joël Stemmer (jstemmer) * Kamil Kisiel (kisielk) * Kelly Dunn (kellydunn) @@ -89,4 +97,7 @@ code still exists in here. * Ryan Smith (ryandotsmith) * Samuel Stauffer (samuel) * Timothée Peignier (cyberdelia) +* Travis Cline (tmc) +* TruongSinh Tran-Nguyen (truongsinh) +* Yaismel Miranda (ympons) * notedit (notedit) diff --git a/vendor/github.com/lib/pq/bench_test.go b/vendor/github.com/lib/pq/bench_test.go index 2b8ec428..e71f41d0 100644 --- a/vendor/github.com/lib/pq/bench_test.go +++ b/vendor/github.com/lib/pq/bench_test.go @@ -7,7 +7,6 @@ import ( "bytes" "database/sql" "database/sql/driver" - "github.com/lib/pq/oid" "io" "math/rand" "net" @@ -17,6 +16,8 @@ import ( "sync" "testing" "time" + + "github.com/lib/pq/oid" ) var ( @@ -35,7 +36,6 @@ func BenchmarkSelectSeries(b *testing.B) { } func benchQuery(b *testing.B, query string, result interface{}) { - b.Skip("current pq database-backed benchmarks are inconsistent") b.StopTimer() db := openTestConn(b) defer db.Close() @@ -183,7 +183,6 @@ func BenchmarkPreparedSelectSeries(b *testing.B) { } func benchPreparedQuery(b *testing.B, query string, result interface{}) { - b.Skip("current pq database-backed benchmarks are inconsistent") b.StopTimer() db := openTestConn(b) defer db.Close() @@ -326,7 +325,7 @@ var testIntBytes = []byte("1234") func BenchmarkDecodeInt64(b *testing.B) { for i := 0; i < b.N; i++ { - decode(¶meterStatus{}, testIntBytes, oid.T_int8) + decode(¶meterStatus{}, testIntBytes, oid.T_int8, formatText) } } @@ -334,7 +333,7 @@ var testFloatBytes = []byte("3.14159") func BenchmarkDecodeFloat64(b *testing.B) { for i := 0; i < b.N; i++ { - decode(¶meterStatus{}, testFloatBytes, oid.T_float8) + decode(¶meterStatus{}, testFloatBytes, oid.T_float8, formatText) } } @@ -342,7 +341,7 @@ var testBoolBytes = []byte{'t'} func BenchmarkDecodeBool(b *testing.B) { for i := 0; i < b.N; i++ { - decode(¶meterStatus{}, testBoolBytes, oid.T_bool) + decode(¶meterStatus{}, testBoolBytes, oid.T_bool, formatText) } } @@ -359,7 +358,7 @@ 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) + decode(¶meterStatus{}, testTimestamptzBytes, oid.T_timestamptz, formatText) } } @@ -372,7 +371,7 @@ func BenchmarkDecodeTimestamptzMultiThread(b *testing.B) { f := func(wg *sync.WaitGroup, loops int) { defer wg.Done() for i := 0; i < loops; i++ { - decode(¶meterStatus{}, testTimestamptzBytes, oid.T_timestamptz) + decode(¶meterStatus{}, testTimestamptzBytes, oid.T_timestamptz, formatText) } } diff --git a/vendor/github.com/lib/pq/buf.go b/vendor/github.com/lib/pq/buf.go index 9f417a1e..666b0012 100644 --- a/vendor/github.com/lib/pq/buf.go +++ b/vendor/github.com/lib/pq/buf.go @@ -3,6 +3,7 @@ package pq import ( "bytes" "encoding/binary" + "github.com/lib/pq/oid" ) @@ -20,6 +21,7 @@ func (b *readBuf) oid() (n oid.Oid) { return } +// N.B: this is actually an unsigned 16-bit integer, unlike int32 func (b *readBuf) int16() (n int) { n = int(binary.BigEndian.Uint16(*b)) *b = (*b)[2:] @@ -46,28 +48,44 @@ func (b *readBuf) byte() byte { return b.next(1)[0] } -type writeBuf []byte +type writeBuf struct { + buf []byte + pos int +} func (b *writeBuf) int32(n int) { x := make([]byte, 4) binary.BigEndian.PutUint32(x, uint32(n)) - *b = append(*b, x...) + b.buf = append(b.buf, x...) } func (b *writeBuf) int16(n int) { x := make([]byte, 2) binary.BigEndian.PutUint16(x, uint16(n)) - *b = append(*b, x...) + b.buf = append(b.buf, x...) } func (b *writeBuf) string(s string) { - *b = append(*b, (s + "\000")...) + b.buf = append(b.buf, (s + "\000")...) } func (b *writeBuf) byte(c byte) { - *b = append(*b, c) + b.buf = append(b.buf, c) } func (b *writeBuf) bytes(v []byte) { - *b = append(*b, v...) + b.buf = append(b.buf, v...) +} + +func (b *writeBuf) wrap() []byte { + p := b.buf[b.pos:] + binary.BigEndian.PutUint32(p, uint32(len(p))) + return b.buf +} + +func (b *writeBuf) next(c byte) { + p := b.buf[b.pos:] + binary.BigEndian.PutUint32(p, uint32(len(p))) + b.pos = len(b.buf) + 1 + b.buf = append(b.buf, c, 0, 0, 0, 0) } diff --git a/vendor/github.com/lib/pq/certs/README b/vendor/github.com/lib/pq/certs/README new file mode 100644 index 00000000..24ab7b25 --- /dev/null +++ b/vendor/github.com/lib/pq/certs/README @@ -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. diff --git a/vendor/github.com/lib/pq/certs/postgresql.crt b/vendor/github.com/lib/pq/certs/postgresql.crt new file mode 100644 index 00000000..6e6b4284 --- /dev/null +++ b/vendor/github.com/lib/pq/certs/postgresql.crt @@ -0,0 +1,69 @@ +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 2 (0x2) + Signature Algorithm: sha256WithRSAEncryption + Issuer: C=US, ST=Nevada, L=Las Vegas, O=github.com/lib/pq, CN=pq CA + Validity + Not Before: Oct 11 15:10:11 2014 GMT + Not After : Oct 8 15:10:11 2024 GMT + Subject: C=US, ST=Nevada, L=Las Vegas, O=github.com/lib/pq, CN=pqgosslcert + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + RSA Public Key: (1024 bit) + Modulus (1024 bit): + 00:e3:8c:06:9a:70:54:51:d1:34:34:83:39:cd:a2: + 59:0f:05:ed:8d:d8:0e:34:d0:92:f4:09:4d:ee:8c: + 78:55:49:24:f8:3c:e0:34:58:02:b2:e7:94:58:c1: + e8:e5:bb:d1:af:f6:54:c1:40:b1:90:70:79:0d:35: + 54:9c:8f:16:e9:c2:f0:92:e6:64:49:38:c1:76:f8: + 47:66:c4:5b:4a:b6:a9:43:ce:c8:be:6c:4d:2b:94: + 97:3c:55:bc:d1:d0:6e:b7:53:ae:89:5c:4b:6b:86: + 40:be:c1:ae:1e:64:ce:9c:ae:87:0a:69:e5:c8:21: + 12:be:ae:1d:f6:45:df:16:a7 + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Subject Key Identifier: + 9B:25:31:63:A2:D8:06:FF:CB:E3:E9:96:FF:0D:BA:DC:12:7D:04:CF + X509v3 Authority Key Identifier: + keyid:52:93:ED:1E:76:0A:9F:65:4F:DE:19:66:C1:D5:22:40:35:CB:A0:72 + + X509v3 Basic Constraints: + CA:FALSE + X509v3 Key Usage: + Digital Signature, Non Repudiation, Key Encipherment + Signature Algorithm: sha256WithRSAEncryption + 3e:f5:f8:0b:4e:11:bd:00:86:1f:ce:dc:97:02:98:91:11:f5: + 65:f6:f2:8a:b2:3e:47:92:05:69:28:c9:e9:b4:f7:cf:93:d1: + 2d:81:5d:00:3c:23:be:da:70:ea:59:e1:2c:d3:25:49:ae:a6: + 95:54:c1:10:df:23:e3:fe:d6:e4:76:c7:6b:73:ad:1b:34:7c: + e2:56:cc:c0:37:ae:c5:7a:11:20:6c:3d:05:0e:99:cd:22:6c: + cf:59:a1:da:28:d4:65:ba:7d:2f:2b:3d:69:6d:a6:c1:ae:57: + bf:56:64:13:79:f8:48:46:65:eb:81:67:28:0b:7b:de:47:10: + b3:80:3c:31:d1:58:94:01:51:4a:c7:c8:1a:01:a8:af:c4:cd: + bb:84:a5:d9:8b:b4:b9:a1:64:3e:95:d9:90:1d:d5:3f:67:cc: + 3b:ba:f5:b4:d1:33:77:ee:c2:d2:3e:7e:c5:66:6e:b7:35:4c: + 60:57:b0:b8:be:36:c8:f3:d3:95:8c:28:4a:c9:f7:27:a4:0d: + e5:96:99:eb:f5:c8:bd:f3:84:6d:ef:02:f9:8a:36:7d:6b:5f: + 36:68:37:41:d9:74:ae:c6:78:2e:44:86:a1:ad:43:ca:fb:b5: + 3e:ba:10:23:09:02:ac:62:d1:d0:83:c8:95:b9:e3:5e:30:ff: + 5b:2b:38:fa +-----BEGIN CERTIFICATE----- +MIIDEzCCAfugAwIBAgIBAjANBgkqhkiG9w0BAQsFADBeMQswCQYDVQQGEwJVUzEP +MA0GA1UECBMGTmV2YWRhMRIwEAYDVQQHEwlMYXMgVmVnYXMxGjAYBgNVBAoTEWdp +dGh1Yi5jb20vbGliL3BxMQ4wDAYDVQQDEwVwcSBDQTAeFw0xNDEwMTExNTEwMTFa +Fw0yNDEwMDgxNTEwMTFaMGQxCzAJBgNVBAYTAlVTMQ8wDQYDVQQIEwZOZXZhZGEx +EjAQBgNVBAcTCUxhcyBWZWdhczEaMBgGA1UEChMRZ2l0aHViLmNvbS9saWIvcHEx +FDASBgNVBAMTC3BxZ29zc2xjZXJ0MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKB +gQDjjAaacFRR0TQ0gznNolkPBe2N2A400JL0CU3ujHhVSST4POA0WAKy55RYwejl +u9Gv9lTBQLGQcHkNNVScjxbpwvCS5mRJOMF2+EdmxFtKtqlDzsi+bE0rlJc8VbzR +0G63U66JXEtrhkC+wa4eZM6crocKaeXIIRK+rh32Rd8WpwIDAQABo1owWDAdBgNV +HQ4EFgQUmyUxY6LYBv/L4+mW/w263BJ9BM8wHwYDVR0jBBgwFoAUUpPtHnYKn2VP +3hlmwdUiQDXLoHIwCQYDVR0TBAIwADALBgNVHQ8EBAMCBeAwDQYJKoZIhvcNAQEL +BQADggEBAD71+AtOEb0Ahh/O3JcCmJER9WX28oqyPkeSBWkoyem098+T0S2BXQA8 +I77acOpZ4SzTJUmuppVUwRDfI+P+1uR2x2tzrRs0fOJWzMA3rsV6ESBsPQUOmc0i +bM9Zodoo1GW6fS8rPWltpsGuV79WZBN5+EhGZeuBZygLe95HELOAPDHRWJQBUUrH +yBoBqK/EzbuEpdmLtLmhZD6V2ZAd1T9nzDu69bTRM3fuwtI+fsVmbrc1TGBXsLi+ +Nsjz05WMKErJ9yekDeWWmev1yL3zhG3vAvmKNn1rXzZoN0HZdK7GeC5EhqGtQ8r7 +tT66ECMJAqxi0dCDyJW5414w/1srOPo= +-----END CERTIFICATE----- diff --git a/vendor/github.com/lib/pq/certs/postgresql.key b/vendor/github.com/lib/pq/certs/postgresql.key new file mode 100644 index 00000000..eb8b20be --- /dev/null +++ b/vendor/github.com/lib/pq/certs/postgresql.key @@ -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----- diff --git a/vendor/github.com/lib/pq/certs/root.crt b/vendor/github.com/lib/pq/certs/root.crt new file mode 100644 index 00000000..aecf8f62 --- /dev/null +++ b/vendor/github.com/lib/pq/certs/root.crt @@ -0,0 +1,24 @@ +-----BEGIN CERTIFICATE----- +MIIEAzCCAuugAwIBAgIJANmheROCdW1NMA0GCSqGSIb3DQEBBQUAMF4xCzAJBgNV +BAYTAlVTMQ8wDQYDVQQIEwZOZXZhZGExEjAQBgNVBAcTCUxhcyBWZWdhczEaMBgG +A1UEChMRZ2l0aHViLmNvbS9saWIvcHExDjAMBgNVBAMTBXBxIENBMB4XDTE0MTAx +MTE1MDQyOVoXDTI0MTAwODE1MDQyOVowXjELMAkGA1UEBhMCVVMxDzANBgNVBAgT +Bk5ldmFkYTESMBAGA1UEBxMJTGFzIFZlZ2FzMRowGAYDVQQKExFnaXRodWIuY29t +L2xpYi9wcTEOMAwGA1UEAxMFcHEgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw +ggEKAoIBAQCV4PxP7ShzWBzUCThcKk3qZtOLtHmszQVtbqhvgTpm1kTRtKBdVMu0 +pLAHQ3JgJCnAYgH0iZxVGoMP16T3irdgsdC48+nNTFM2T0cCdkfDURGIhSFN47cb +Pgy306BcDUD2q7ucW33+dlFSRuGVewocoh4BWM/vMtMvvWzdi4Ag/L/jhb+5wZxZ +sWymsadOVSDePEMKOvlCa3EdVwVFV40TVyDb+iWBUivDAYsS2a3KajuJrO6MbZiE +Sp2RCIkZS2zFmzWxVRi9ZhzIZhh7EVF9JAaNC3T52jhGUdlRq3YpBTMnd89iOh74 +6jWXG7wSuPj3haFzyNhmJ0ZUh+2Ynoh1AgMBAAGjgcMwgcAwHQYDVR0OBBYEFFKT +7R52Cp9lT94ZZsHVIkA1y6ByMIGQBgNVHSMEgYgwgYWAFFKT7R52Cp9lT94ZZsHV +IkA1y6ByoWKkYDBeMQswCQYDVQQGEwJVUzEPMA0GA1UECBMGTmV2YWRhMRIwEAYD +VQQHEwlMYXMgVmVnYXMxGjAYBgNVBAoTEWdpdGh1Yi5jb20vbGliL3BxMQ4wDAYD +VQQDEwVwcSBDQYIJANmheROCdW1NMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEF +BQADggEBAAEhCLWkqJNMI8b4gkbmj5fqQ/4+oO83bZ3w2Oqf6eZ8I8BC4f2NOyE6 +tRUlq5+aU7eqC1cOAvGjO+YHN/bF/DFpwLlzvUSXt+JP/pYcUjL7v+pIvwqec9hD +ndvM4iIbkD/H/OYQ3L+N3W+G1x7AcFIX+bGCb3PzYVQAjxreV6//wgKBosMGFbZo +HPxT9RPMun61SViF04H5TNs0derVn1+5eiiYENeAhJzQNyZoOOUuX1X/Inx9bEPh +C5vFBtSMgIytPgieRJVWAiMLYsfpIAStrHztRAbBs2DU01LmMgRvHdxgFEKinC/d +UHZZQDP+6pT+zADrGhQGXe4eThaO6f0= +-----END CERTIFICATE----- diff --git a/vendor/github.com/lib/pq/certs/server.crt b/vendor/github.com/lib/pq/certs/server.crt new file mode 100644 index 00000000..ddc995a6 --- /dev/null +++ b/vendor/github.com/lib/pq/certs/server.crt @@ -0,0 +1,81 @@ +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 1 (0x1) + Signature Algorithm: sha256WithRSAEncryption + Issuer: C=US, ST=Nevada, L=Las Vegas, O=github.com/lib/pq, CN=pq CA + Validity + Not Before: Oct 11 15:05:15 2014 GMT + Not After : Oct 8 15:05:15 2024 GMT + Subject: C=US, ST=Nevada, L=Las Vegas, O=github.com/lib/pq, CN=postgres + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + RSA Public Key: (2048 bit) + Modulus (2048 bit): + 00:d7:8a:4c:85:fb:17:a5:3c:8f:e0:72:11:29:ce: + 3f:b0:1f:3f:7d:c6:ee:7f:a7:fc:02:2b:35:47:08: + a6:3d:90:df:5c:56:14:94:00:c7:6d:d1:d2:e2:61: + 95:77:b8:e3:a6:66:31:f9:1f:21:7d:62:e1:27:da: + 94:37:61:4a:ea:63:53:a0:61:b8:9c:bb:a5:e2:e7: + b7:a6:d8:0f:05:04:c7:29:e2:ea:49:2b:7f:de:15: + 00:a6:18:70:50:c7:0c:de:9a:f9:5a:96:b0:e1:94: + 06:c6:6d:4a:21:3b:b4:0f:a5:6d:92:86:34:b2:4e: + d7:0e:a7:19:c0:77:0b:7b:87:c8:92:de:42:ff:86: + d2:b7:9a:a4:d4:15:23:ca:ad:a5:69:21:b8:ce:7e: + 66:cb:85:5d:b9:ed:8b:2d:09:8d:94:e4:04:1e:72: + ec:ef:d0:76:90:15:5a:a4:f7:91:4b:e9:ce:4e:9d: + 5d:9a:70:17:9c:d8:e9:73:83:ea:3d:61:99:a6:cd: + ac:91:40:5a:88:77:e5:4e:2a:8e:3d:13:f3:f9:38: + 6f:81:6b:8a:95:ca:0e:07:ab:6f:da:b4:8c:d9:ff: + aa:78:03:aa:c7:c2:cf:6f:64:92:d3:d8:83:d5:af: + f1:23:18:a7:2e:7b:17:0b:e7:7d:f1:fa:a8:41:a3: + 04:57 + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Subject Key Identifier: + EE:F0:B3:46:DC:C7:09:EB:0E:B6:2F:E5:FE:62:60:45:44:9F:59:CC + X509v3 Authority Key Identifier: + keyid:52:93:ED:1E:76:0A:9F:65:4F:DE:19:66:C1:D5:22:40:35:CB:A0:72 + + X509v3 Basic Constraints: + CA:FALSE + X509v3 Key Usage: + Digital Signature, Non Repudiation, Key Encipherment + Signature Algorithm: sha256WithRSAEncryption + 7e:5a:6e:be:bf:d2:6c:c1:d6:fa:b6:fb:3f:06:53:36:08:87: + 9d:95:b1:39:af:9e:f6:47:38:17:39:da:25:7c:f2:ad:0c:e3: + ab:74:19:ca:fb:8c:a0:50:c0:1d:19:8a:9c:21:ed:0f:3a:d1: + 96:54:2e:10:09:4f:b8:70:f7:2b:99:43:d2:c6:15:bc:3f:24: + 7d:28:39:32:3f:8d:a4:4f:40:75:7f:3e:0d:1c:d1:69:f2:4e: + 98:83:47:97:d2:25:ac:c9:36:86:2f:04:a6:c4:86:c7:c4:00: + 5f:7f:b9:ad:fc:bf:e9:f5:78:d7:82:1a:51:0d:fc:ab:9e:92: + 1d:5f:0c:18:d1:82:e0:14:c9:ce:91:89:71:ff:49:49:ff:35: + bf:7b:44:78:42:c1:d0:66:65:bb:28:2e:60:ca:9b:20:12:a9: + 90:61:b1:96:ec:15:46:c9:37:f7:07:90:8a:89:45:2a:3f:37: + ec:dc:e3:e5:8f:c3:3a:57:80:a5:54:60:0c:e1:b2:26:99:2b: + 40:7e:36:d1:9a:70:02:ec:63:f4:3b:72:ae:81:fb:30:20:6d: + cb:48:46:c6:b5:8f:39:b1:84:05:25:55:8d:f5:62:f6:1b:46: + 2e:da:a3:4c:26:12:44:d7:56:b6:b8:a9:ca:d3:ab:71:45:7c: + 9f:48:6d:1e +-----BEGIN CERTIFICATE----- +MIIDlDCCAnygAwIBAgIBATANBgkqhkiG9w0BAQsFADBeMQswCQYDVQQGEwJVUzEP +MA0GA1UECBMGTmV2YWRhMRIwEAYDVQQHEwlMYXMgVmVnYXMxGjAYBgNVBAoTEWdp +dGh1Yi5jb20vbGliL3BxMQ4wDAYDVQQDEwVwcSBDQTAeFw0xNDEwMTExNTA1MTVa +Fw0yNDEwMDgxNTA1MTVaMGExCzAJBgNVBAYTAlVTMQ8wDQYDVQQIEwZOZXZhZGEx +EjAQBgNVBAcTCUxhcyBWZWdhczEaMBgGA1UEChMRZ2l0aHViLmNvbS9saWIvcHEx +ETAPBgNVBAMTCHBvc3RncmVzMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC +AQEA14pMhfsXpTyP4HIRKc4/sB8/fcbuf6f8Ais1RwimPZDfXFYUlADHbdHS4mGV +d7jjpmYx+R8hfWLhJ9qUN2FK6mNToGG4nLul4ue3ptgPBQTHKeLqSSt/3hUAphhw +UMcM3pr5Wpaw4ZQGxm1KITu0D6VtkoY0sk7XDqcZwHcLe4fIkt5C/4bSt5qk1BUj +yq2laSG4zn5my4Vdue2LLQmNlOQEHnLs79B2kBVapPeRS+nOTp1dmnAXnNjpc4Pq +PWGZps2skUBaiHflTiqOPRPz+ThvgWuKlcoOB6tv2rSM2f+qeAOqx8LPb2SS09iD +1a/xIxinLnsXC+d98fqoQaMEVwIDAQABo1owWDAdBgNVHQ4EFgQU7vCzRtzHCesO +ti/l/mJgRUSfWcwwHwYDVR0jBBgwFoAUUpPtHnYKn2VP3hlmwdUiQDXLoHIwCQYD +VR0TBAIwADALBgNVHQ8EBAMCBeAwDQYJKoZIhvcNAQELBQADggEBAH5abr6/0mzB +1vq2+z8GUzYIh52VsTmvnvZHOBc52iV88q0M46t0Gcr7jKBQwB0Zipwh7Q860ZZU +LhAJT7hw9yuZQ9LGFbw/JH0oOTI/jaRPQHV/Pg0c0WnyTpiDR5fSJazJNoYvBKbE +hsfEAF9/ua38v+n1eNeCGlEN/Kuekh1fDBjRguAUyc6RiXH/SUn/Nb97RHhCwdBm +ZbsoLmDKmyASqZBhsZbsFUbJN/cHkIqJRSo/N+zc4+WPwzpXgKVUYAzhsiaZK0B+ +NtGacALsY/Q7cq6B+zAgbctIRsa1jzmxhAUlVY31YvYbRi7ao0wmEkTXVra4qcrT +q3FFfJ9IbR4= +-----END CERTIFICATE----- diff --git a/vendor/github.com/lib/pq/certs/server.key b/vendor/github.com/lib/pq/certs/server.key new file mode 100644 index 00000000..bd7b019b --- /dev/null +++ b/vendor/github.com/lib/pq/certs/server.key @@ -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----- diff --git a/vendor/github.com/lib/pq/conn.go b/vendor/github.com/lib/pq/conn.go index cef29b55..ce661d66 100644 --- a/vendor/github.com/lib/pq/conn.go +++ b/vendor/github.com/lib/pq/conn.go @@ -4,27 +4,34 @@ import ( "bufio" "crypto/md5" "crypto/tls" + "crypto/x509" "database/sql" "database/sql/driver" "encoding/binary" "errors" "fmt" - "github.com/lib/pq/oid" "io" + "io/ioutil" "net" "os" + "os/user" "path" + "path/filepath" "strconv" "strings" "time" "unicode" + + "github.com/lib/pq/oid" ) // Common error types var ( - ErrSSLNotSupported = errors.New("pq: SSL is not enabled on the server") - ErrNotSupported = errors.New("pq: Unsupported command") - ErrInFailedTransaction = errors.New("pq: Could not complete operation in a failed transaction") + ErrNotSupported = errors.New("pq: Unsupported command") + ErrInFailedTransaction = errors.New("pq: Could not complete operation in a failed transaction") + ErrSSLNotSupported = errors.New("pq: SSL is not enabled on the server") + ErrSSLKeyHasWorldPermissions = errors.New("pq: Private key file has group or world access. Permissions should be u=rw (0600) or less.") + ErrCouldNotDetectUsername = errors.New("pq: Could not detect default username. Please provide one explicitly.") ) type drv struct{} @@ -66,6 +73,7 @@ func (s transactionStatus) String() string { default: errorf("unknown transactionStatus %d", s) } + panic("not reached") } @@ -93,13 +101,54 @@ type conn struct { parameterStatus parameterStatus saveMessageType byte - saveMessageBuffer *readBuf + saveMessageBuffer []byte + + // If true, this connection is bad and all public-facing functions should + // return ErrBadConn. + bad bool + + // If set, this connection should never use the binary format when + // receiving query results from prepared statements. Only provided for + // debugging. + disablePreparedBinaryResult bool + + // Whether to always send []byte parameters over as binary. Enables single + // round-trip mode for non-prepared Query calls. + binaryParameters bool +} + +// Handle driver-side settings in parsed connection string. +func (c *conn) handleDriverSettings(o values) (err error) { + boolSetting := func(key string, val *bool) error { + if value := o.Get(key); value != "" { + if value == "yes" { + *val = true + } else if value == "no" { + *val = false + } else { + return fmt.Errorf("unrecognized value %q for %s", value, key) + } + } + return nil + } + + err = boolSetting("disable_prepared_binary_result", &c.disablePreparedBinaryResult) + if err != nil { + return err + } + err = boolSetting("binary_parameters", &c.binaryParameters) + if err != nil { + return err + } + return nil } func (c *conn) writeBuf(b byte) *writeBuf { c.scratch[0] = b - w := writeBuf(c.scratch[:5]) - return &w + return &writeBuf{ + buf: c.scratch[:5], + pos: 1, + } } func Open(name string) (_ driver.Conn, err error) { @@ -107,22 +156,11 @@ func Open(name string) (_ driver.Conn, err error) { } func DialOpen(d Dialer, name string) (_ driver.Conn, err error) { - defer func() { - // Handle any panics during connection initialization. Note that we - // specifically do *not* want to use errRecover(), as that would turn - // any connection errors into ErrBadConns, hiding the real error - // message from the user. - e := recover() - if e == nil { - // Do nothing - return - } - var ok bool - err, ok = e.(error) - if !ok { - err = fmt.Errorf("pq: unexpected error: %#v", e) - } - }() + // Handle any panics during connection initialization. Note that we + // specifically do *not* want to use errRecover(), as that would turn any + // connection errors into ErrBadConns, hiding the real error message from + // the user. + defer errRecoverNoErrBadConn(&err) o := make(values) @@ -140,7 +178,7 @@ func DialOpen(d Dialer, name string) (_ driver.Conn, err error) { o.Set(k, v) } - if strings.HasPrefix(name, "postgres://") { + if strings.HasPrefix(name, "postgres://") || strings.HasPrefix(name, "postgresql://") { name, err = ParseURL(name) if err != nil { return nil, err @@ -157,7 +195,6 @@ func DialOpen(d Dialer, name string) (_ driver.Conn, err error) { o.Set("application_name", fallback) } } - o.Unset("fallback_application_name") // 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 @@ -192,29 +229,36 @@ func DialOpen(d Dialer, name string) (_ driver.Conn, err error) { } } - c, err := dial(d, o) + cn := &conn{} + err = cn.handleDriverSettings(o) if err != nil { return nil, err } - cn := &conn{c: c} + cn.c, err = dial(d, o) + if err != nil { + return nil, err + } cn.ssl(o) cn.buf = bufio.NewReader(cn.c) cn.startup(o) + // reset the deadline, in case one was set (see dial) - err = cn.c.SetDeadline(time.Time{}) + if timeout := o.Get("connect_timeout"); timeout != "" && timeout != "0" { + err = cn.c.SetDeadline(time.Time{}) + } return cn, err } func dial(d Dialer, o values) (net.Conn, error) { ntw, addr := network(o) - - timeout := o.Get("connect_timeout") - // Ensure the option will not be sent. - o.Unset("connect_timeout") + // SSL is not necessary or supported over UNIX domain sockets + if ntw == "unix" { + o["sslmode"] = "disable" + } // Zero or not specified means wait indefinitely. - if timeout != "" && timeout != "0" { + if timeout := o.Get("connect_timeout"); timeout != "" && timeout != "0" { seconds, err := strconv.ParseInt(timeout, 10, 0) if err != nil { return nil, fmt.Errorf("invalid value for parameter connect_timeout: %s", err) @@ -261,10 +305,6 @@ func (vs values) Isset(k string) bool { return ok } -func (vs values) Unset(k string) { - delete(vs, k) -} - // scanner implements a tokenizer for libpq-style option strings. type scanner struct { s []rune @@ -383,12 +423,16 @@ func (cn *conn) isInTransaction() bool { func (cn *conn) checkIsInTransaction(intxn bool) { if cn.isInTransaction() != intxn { + cn.bad = true errorf("unexpected transaction status %v", cn.txnStatus) } } func (cn *conn) Begin() (_ driver.Tx, err error) { - defer errRecover(&err) + if cn.bad { + return nil, driver.ErrBadConn + } + defer cn.errRecover(&err) cn.checkIsInTransaction(false) _, commandTag, err := cn.simpleExec("BEGIN") @@ -396,16 +440,21 @@ func (cn *conn) Begin() (_ driver.Tx, err error) { return nil, err } if commandTag != "BEGIN" { + cn.bad = true return nil, fmt.Errorf("unexpected command tag %s", commandTag) } if cn.txnStatus != txnStatusIdleInTransaction { + cn.bad = true return nil, fmt.Errorf("unexpected transaction status %v", cn.txnStatus) } return cn, nil } func (cn *conn) Commit() (err error) { - defer errRecover(&err) + if cn.bad { + return driver.ErrBadConn + } + defer cn.errRecover(&err) cn.checkIsInTransaction(true) // We don't want the client to think that everything is okay if it tries @@ -423,9 +472,13 @@ func (cn *conn) Commit() (err error) { _, commandTag, err := cn.simpleExec("COMMIT") if err != nil { + if cn.isInTransaction() { + cn.bad = true + } return err } if commandTag != "COMMIT" { + cn.bad = true return fmt.Errorf("unexpected command tag %s", commandTag) } cn.checkIsInTransaction(false) @@ -433,11 +486,17 @@ func (cn *conn) Commit() (err error) { } func (cn *conn) Rollback() (err error) { - defer errRecover(&err) + if cn.bad { + return driver.ErrBadConn + } + defer cn.errRecover(&err) cn.checkIsInTransaction(true) _, commandTag, err := cn.simpleExec("ROLLBACK") if err != nil { + if cn.isInTransaction() { + cn.bad = true + } return err } if commandTag != "ROLLBACK" { @@ -453,8 +512,6 @@ func (cn *conn) gname() string { } func (cn *conn) simpleExec(q string) (res driver.Result, commandTag string, err error) { - defer errRecover(&err) - b := cn.writeBuf('Q') b.string(q) cn.send(b) @@ -463,7 +520,7 @@ func (cn *conn) simpleExec(q string) (res driver.Result, commandTag string, err t, r := cn.recv1() switch t { case 'C': - res, commandTag = parseComplete(r.string()) + res, commandTag = cn.parseComplete(r.string()) case 'Z': cn.processReadyForQuery(r) // done @@ -473,16 +530,16 @@ func (cn *conn) simpleExec(q string) (res driver.Result, commandTag string, err case 'T', 'D', 'I': // ignore any results default: + cn.bad = true errorf("unknown response for simple query: %q", t) } } - panic("not reached") } -func (cn *conn) simpleQuery(q string) (res driver.Rows, err error) { - defer errRecover(&err) +func (cn *conn) simpleQuery(q string) (res *rows, err error) { + defer cn.errRecover(&err) - st := &stmt{cn: cn, name: "", query: q} + st := &stmt{cn: cn, name: ""} b := cn.writeBuf('Q') b.string(q) @@ -497,9 +554,16 @@ func (cn *conn) simpleQuery(q string) (res driver.Rows, err error) { // the user can close, though, to avoid connections from being // leaked. A "rows" with done=true works fine for that purpose. if err != nil { + cn.bad = true errorf("unexpected message %q in simple query execution", t) } - res = &rows{st: st, done: true} + res = &rows{ + cn: cn, + colNames: st.colNames, + colTyps: st.colTyps, + colFmts: st.colFmts, + done: true, + } case 'Z': cn.processReadyForQuery(r) // done @@ -509,6 +573,7 @@ func (cn *conn) simpleQuery(q string) (res driver.Rows, err error) { err = parseError(r) case 'D': if res == nil { + cn.bad = true errorf("unexpected DataRow in simple query execution") } // the query didn't fail; kick off to Next @@ -517,73 +582,105 @@ func (cn *conn) simpleQuery(q string) (res driver.Rows, err error) { case 'T': // res might be non-nil here if we received a previous // CommandComplete, but that's fine; just overwrite it - res = &rows{st: st} - st.cols, st.rowTyps = parseMeta(r) + res = &rows{cn: cn} + res.colNames, res.colFmts, res.colTyps = parsePortalRowDescribe(r) // To work around a bug in QueryRow in Go 1.2 and earlier, wait // until the first DataRow has been received. default: + cn.bad = true errorf("unknown response for simple query: %q", t) } } - panic("not reached") } -func (cn *conn) prepareTo(q, stmtName string) (_ *stmt, err error) { - defer errRecover(&err) +// Decides which column formats to use for a prepared statement. The input is +// an array of type oids, one element per result column. +func decideColumnFormats(colTyps []oid.Oid, forceText bool) (colFmts []format, colFmtData []byte) { + if len(colTyps) == 0 { + return nil, colFmtDataAllText + } - st := &stmt{cn: cn, name: stmtName, query: q} + colFmts = make([]format, len(colTyps)) + if forceText { + return colFmts, colFmtDataAllText + } + + allBinary := true + allText := true + for i, o := range colTyps { + switch o { + // This is the list of types to use binary mode for when receiving them + // through a prepared statement. If a type appears in this list, it + // must also be implemented in binaryDecode in encode.go. + case oid.T_bytea: + fallthrough + case oid.T_int8: + fallthrough + case oid.T_int4: + fallthrough + case oid.T_int2: + colFmts[i] = formatBinary + allText = false + + default: + allBinary = false + } + } + + if allBinary { + return colFmts, colFmtDataAllBinary + } else if allText { + return colFmts, colFmtDataAllText + } else { + colFmtData = make([]byte, 2+len(colFmts)*2) + binary.BigEndian.PutUint16(colFmtData, uint16(len(colFmts))) + for i, v := range colFmts { + binary.BigEndian.PutUint16(colFmtData[2+i*2:], uint16(v)) + } + return colFmts, colFmtData + } +} + +func (cn *conn) prepareTo(q, stmtName string) *stmt { + st := &stmt{cn: cn, name: stmtName} b := cn.writeBuf('P') b.string(st.name) b.string(q) b.int16(0) - cn.send(b) - b = cn.writeBuf('D') + b.next('D') b.byte('S') b.string(st.name) + + b.next('S') cn.send(b) - cn.send(cn.writeBuf('S')) - - for { - t, r := cn.recv1() - switch t { - case '1': - case 't': - nparams := int(r.int16()) - st.paramTyps = make([]oid.Oid, nparams) - - for i := range st.paramTyps { - st.paramTyps[i] = r.oid() - } - case 'T': - st.cols, st.rowTyps = parseMeta(r) - case 'n': - // no data - case 'Z': - cn.processReadyForQuery(r) - return st, err - case 'E': - err = parseError(r) - default: - errorf("unexpected describe rows response: %q", t) - } - } - - panic("not reached") + cn.readParseResponse() + st.paramTyps, st.colNames, st.colTyps = cn.readStatementDescribeResponse() + st.colFmts, st.colFmtData = decideColumnFormats(st.colTyps, cn.disablePreparedBinaryResult) + cn.readReadyForQuery() + return st } -func (cn *conn) Prepare(q string) (driver.Stmt, error) { +func (cn *conn) Prepare(q string) (_ driver.Stmt, err error) { + if cn.bad { + return nil, driver.ErrBadConn + } + defer cn.errRecover(&err) + if len(q) >= 4 && strings.EqualFold(q[:4], "COPY") { return cn.prepareCopyIn(q) } - return cn.prepareTo(q, cn.gname()) + return cn.prepareTo(q, cn.gname()), nil } func (cn *conn) Close() (err error) { - defer errRecover(&err) + if cn.bad { + return driver.ErrBadConn + } + defer cn.errRecover(&err) // Don't go through send(); ListenerConn relies on us not scribbling on the // scratch buffer of this connection. @@ -597,7 +694,10 @@ func (cn *conn) Close() (err error) { // Implement the "Queryer" interface func (cn *conn) Query(query string, args []driver.Value) (_ driver.Rows, err error) { - defer errRecover(&err) + if cn.bad { + return nil, driver.ErrBadConn + } + defer cn.errRecover(&err) // Check to see if we can use the "simpleQuery" interface, which is // *much* faster than going through prepare/exec @@ -605,18 +705,33 @@ func (cn *conn) Query(query string, args []driver.Value) (_ driver.Rows, err err return cn.simpleQuery(query) } - st, err := cn.prepareTo(query, "") - if err != nil { - panic(err) - } + if cn.binaryParameters { + cn.sendBinaryModeQuery(query, args) - st.exec(args) - return &rows{st: st}, nil + cn.readParseResponse() + cn.readBindResponse() + rows := &rows{cn: cn} + rows.colNames, rows.colFmts, rows.colTyps = cn.readPortalDescribeResponse() + cn.postExecuteWorkaround() + return rows, nil + } else { + st := cn.prepareTo(query, "") + st.exec(args) + return &rows{ + cn: cn, + colNames: st.colNames, + colTyps: st.colTyps, + colFmts: st.colFmts, + }, nil + } } // Implement the optional "Execer" interface for one-shot queries -func (cn *conn) Exec(query string, args []driver.Value) (_ driver.Result, err error) { - defer errRecover(&err) +func (cn *conn) Exec(query string, args []driver.Value) (res driver.Result, err error) { + if cn.bad { + return nil, driver.ErrBadConn + } + defer cn.errRecover(&err) // Check to see if we can use the "simpleExec" interface, which is // *much* faster than going through prepare/exec @@ -626,32 +741,42 @@ func (cn *conn) Exec(query string, args []driver.Value) (_ driver.Result, err er return r, err } - // Use the unnamed statement to defer planning until bind - // time, or else value-based selectivity estimates cannot be - // used. - st, err := cn.prepareTo(query, "") - if err != nil { - panic(err) - } + if cn.binaryParameters { + cn.sendBinaryModeQuery(query, args) - r, err := st.Exec(args) - if err != nil { - panic(err) + cn.readParseResponse() + cn.readBindResponse() + cn.readPortalDescribeResponse() + cn.postExecuteWorkaround() + res, _, err = cn.readExecuteResponse("Execute") + return res, err + } else { + // Use the unnamed statement to defer planning until bind + // time, or else value-based selectivity estimates cannot be + // used. + st := cn.prepareTo(query, "") + r, err := st.Exec(args) + if err != nil { + panic(err) + } + return r, err } - - return r, err } -// Assumes len(*m) is > 5 func (cn *conn) send(m *writeBuf) { - b := (*m)[1:] - binary.BigEndian.PutUint32(b, uint32(len(b))) + _, err := cn.c.Write(m.wrap()) + if err != nil { + panic(err) + } +} - if (*m)[0] == 0 { - *m = b +func (cn *conn) sendStartupPacket(m *writeBuf) { + // sanity check + if m.buf[0] != 0 { + panic("oops") } - _, err := cn.c.Write(*m) + _, err := cn.c.Write((m.wrap())[1:]) if err != nil { panic(err) } @@ -672,27 +797,29 @@ func (cn *conn) sendSimpleMessage(typ byte) (err error) { // the message yourself. func (cn *conn) saveMessage(typ byte, buf *readBuf) { if cn.saveMessageType != 0 { + cn.bad = true errorf("unexpected saveMessageType %d", cn.saveMessageType) } cn.saveMessageType = typ - cn.saveMessageBuffer = buf + cn.saveMessageBuffer = *buf } // recvMessage receives any message from the backend, or returns an error if // a problem occurred while reading the message. -func (cn *conn) recvMessage() (byte, *readBuf, error) { +func (cn *conn) recvMessage(r *readBuf) (byte, error) { // workaround for a QueryRow bug, see exec if cn.saveMessageType != 0 { - t, r := cn.saveMessageType, cn.saveMessageBuffer + t := cn.saveMessageType + *r = cn.saveMessageBuffer cn.saveMessageType = 0 cn.saveMessageBuffer = nil - return t, r, nil + return t, nil } x := cn.scratch[:5] _, err := io.ReadFull(cn.buf, x) if err != nil { - return 0, nil, err + return 0, err } // read the type and length of the message that follows @@ -706,10 +833,10 @@ func (cn *conn) recvMessage() (byte, *readBuf, error) { } _, err = io.ReadFull(cn.buf, y) if err != nil { - return 0, nil, err + return 0, err } - - return t, (*readBuf)(&y), nil + *r = y + return t, nil } // recv receives a message from the backend, but if an error happened while @@ -719,7 +846,8 @@ func (cn *conn) recvMessage() (byte, *readBuf, error) { func (cn *conn) recv() (t byte, r *readBuf) { for { var err error - t, r, err = cn.recvMessage() + r = &readBuf{} + t, err = cn.recvMessage(r) if err != nil { panic(err) } @@ -733,17 +861,13 @@ func (cn *conn) recv() (t byte, r *readBuf) { return } } - - panic("not reached") } -// recv1 receives a message from the backend, panicking if an error occurs -// while attempting to read it. All asynchronous messages are ignored, with -// the exception of ErrorResponse. -func (cn *conn) recv1() (t byte, r *readBuf) { +// recv1Buf is exactly equivalent to recv1, except it uses a buffer supplied by +// the caller to avoid an allocation. +func (cn *conn) recv1Buf(r *readBuf) byte { for { - var err error - t, r, err = cn.recvMessage() + t, err := cn.recvMessage(r) if err != nil { panic(err) } @@ -754,18 +878,31 @@ func (cn *conn) recv1() (t byte, r *readBuf) { case 'S': cn.processParameterStatus(r) default: - return + return t } } +} - panic("not reached") +// recv1 receives a message from the backend, panicking if an error occurs +// while attempting to read it. All asynchronous messages are ignored, with +// the exception of ErrorResponse. +func (cn *conn) recv1() (t byte, r *readBuf) { + r = &readBuf{} + t = cn.recv1Buf(r) + return t, r } func (cn *conn) ssl(o values) { + verifyCaOnly := false tlsConf := tls.Config{} switch mode := o.Get("sslmode"); mode { case "require", "": tlsConf.InsecureSkipVerify = true + case "verify-ca": + // We must skip TLS's own verification since it requires full + // verification since Go 1.3. + tlsConf.InsecureSkipVerify = true + verifyCaOnly = true case "verify-full": tlsConf.ServerName = o.Get("host") case "disable": @@ -774,9 +911,12 @@ func (cn *conn) ssl(o values) { errorf(`unsupported sslmode %q; only "require" (default), "verify-full", and "disable" supported`, mode) } + cn.setupSSLClientCertificates(&tlsConf, o) + cn.setupSSLCA(&tlsConf, o) + w := cn.writeBuf(0) w.int32(80877103) - cn.send(w) + cn.sendStartupPacket(w) b := cn.scratch[:1] _, err := io.ReadFull(cn.c, b) @@ -788,7 +928,139 @@ func (cn *conn) ssl(o values) { panic(ErrSSLNotSupported) } - cn.c = tls.Client(cn.c, &tlsConf) + client := tls.Client(cn.c, &tlsConf) + if verifyCaOnly { + cn.verifyCA(client, &tlsConf) + } + cn.c = client +} + +// verifyCA carries out a TLS handshake to the server and verifies the +// presented certificate against the effective CA, i.e. the one specified in +// sslrootcert or the system CA if sslrootcert was not specified. +func (cn *conn) verifyCA(client *tls.Conn, tlsConf *tls.Config) { + err := client.Handshake() + if err != nil { + panic(err) + } + certs := client.ConnectionState().PeerCertificates + opts := x509.VerifyOptions{ + DNSName: client.ConnectionState().ServerName, + Intermediates: x509.NewCertPool(), + Roots: tlsConf.RootCAs, + } + for i, cert := range certs { + if i == 0 { + continue + } + opts.Intermediates.AddCert(cert) + } + _, err = certs[0].Verify(opts) + if err != nil { + panic(err) + } +} + +// This function sets up SSL client certificates based on either the "sslkey" +// and "sslcert" settings (possibly set via the environment variables PGSSLKEY +// and PGSSLCERT, respectively), or if they aren't set, from the .postgresql +// directory in the user's home directory. If the file paths are set +// explicitly, the files must exist. The key file must also not be +// world-readable, or this function will panic with +// ErrSSLKeyHasWorldPermissions. +func (cn *conn) setupSSLClientCertificates(tlsConf *tls.Config, o values) { + var missingOk bool + + sslkey := o.Get("sslkey") + sslcert := o.Get("sslcert") + if sslkey != "" && sslcert != "" { + // If the user has set an sslkey and sslcert, they *must* exist. + missingOk = false + } else { + // Automatically load certificates from ~/.postgresql. + user, err := user.Current() + if err != nil { + // user.Current() might fail when cross-compiling. We have to + // ignore the error and continue without client certificates, since + // we wouldn't know where to load them from. + return + } + + sslkey = filepath.Join(user.HomeDir, ".postgresql", "postgresql.key") + sslcert = filepath.Join(user.HomeDir, ".postgresql", "postgresql.crt") + missingOk = true + } + + // Check that both files exist, and report the error or stop, depending on + // which behaviour we want. Note that we don't do any more extensive + // checks than this (such as checking that the paths aren't directories); + // LoadX509KeyPair() will take care of the rest. + keyfinfo, err := os.Stat(sslkey) + if err != nil && missingOk { + return + } else if err != nil { + panic(err) + } + _, err = os.Stat(sslcert) + if err != nil && missingOk { + return + } else if err != nil { + panic(err) + } + + // If we got this far, the key file must also have the correct permissions + kmode := keyfinfo.Mode() + if kmode != kmode&0600 { + panic(ErrSSLKeyHasWorldPermissions) + } + + cert, err := tls.LoadX509KeyPair(sslcert, sslkey) + if err != nil { + panic(err) + } + tlsConf.Certificates = []tls.Certificate{cert} +} + +// Sets up RootCAs in the TLS configuration if sslrootcert is set. +func (cn *conn) setupSSLCA(tlsConf *tls.Config, o values) { + if sslrootcert := o.Get("sslrootcert"); sslrootcert != "" { + tlsConf.RootCAs = x509.NewCertPool() + + cert, err := ioutil.ReadFile(sslrootcert) + if err != nil { + panic(err) + } + + ok := tlsConf.RootCAs.AppendCertsFromPEM(cert) + if !ok { + errorf("couldn't parse pem in sslrootcert") + } + } +} + +// isDriverSetting returns true iff a setting is purely for configuring the +// driver's options and should not be sent to the server in the connection +// startup packet. +func isDriverSetting(key string) bool { + switch key { + case "host", "port": + return true + case "password": + return true + case "sslmode", "sslcert", "sslkey", "sslrootcert": + return true + case "fallback_application_name": + return true + case "connect_timeout": + return true + case "disable_prepared_binary_result": + return true + case "binary_parameters": + return true + + default: + return false + } } func (cn *conn) startup(o values) { @@ -799,9 +1071,8 @@ func (cn *conn) startup(o values) { // parameters potentially included in the connection string. If the server // doesn't recognize any of them, it will reply with an error. for k, v := range o { - // skip options which can't be run-time parameters - if k == "password" || k == "host" || - k == "port" || k == "sslmode" { + if isDriverSetting(k) { + // skip options which can't be run-time parameters continue } // The protocol requires us to supply the database name as "database" @@ -813,7 +1084,7 @@ func (cn *conn) startup(o values) { w.string(v) } w.string("") - cn.send(w) + cn.sendStartupPacket(w) for { t, r := cn.recv() @@ -868,22 +1139,36 @@ func (cn *conn) auth(r *readBuf, o values) { } } +type format int + +const formatText format = 0 +const formatBinary format = 1 + +// One result-column format code with the value 1 (i.e. all binary). +var colFmtDataAllBinary []byte = []byte{0, 1, 0, 1} + +// No result-column format codes (i.e. all text). +var colFmtDataAllText []byte = []byte{0, 0} + type stmt struct { - cn *conn - name string - query string - cols []string - rowTyps []oid.Oid - paramTyps []oid.Oid - closed bool + cn *conn + name string + colNames []string + colFmts []format + colFmtData []byte + colTyps []oid.Oid + paramTyps []oid.Oid + closed bool } func (st *stmt) Close() (err error) { if st.closed { return nil } - - defer errRecover(&err) + if st.cn.bad { + return driver.ErrBadConn + } + defer st.cn.errRecover(&err) w := st.cn.writeBuf('C') w.byte('S') @@ -894,12 +1179,14 @@ func (st *stmt) Close() (err error) { t, _ := st.cn.recv1() if t != '3' { + st.cn.bad = true errorf("unexpected close response: %q", t) } st.closed = true t, r := st.cn.recv1() if t != 'Z' { + st.cn.bad = true errorf("expected ready for query, but got: %q", t) } st.cn.processReadyForQuery(r) @@ -908,122 +1195,71 @@ func (st *stmt) Close() (err error) { } func (st *stmt) Query(v []driver.Value) (r driver.Rows, err error) { - defer errRecover(&err) + if st.cn.bad { + return nil, driver.ErrBadConn + } + defer st.cn.errRecover(&err) + st.exec(v) - return &rows{st: st}, nil + return &rows{ + cn: st.cn, + colNames: st.colNames, + colTyps: st.colTyps, + colFmts: st.colFmts, + }, nil } func (st *stmt) Exec(v []driver.Value) (res driver.Result, err error) { - defer errRecover(&err) - - if len(v) == 0 { - // ignore commandTag, our caller doesn't care - r, _, err := st.cn.simpleExec(st.query) - return r, err + if st.cn.bad { + return nil, driver.ErrBadConn } + defer st.cn.errRecover(&err) + st.exec(v) - - for { - t, r := st.cn.recv1() - switch t { - case 'E': - err = parseError(r) - case 'C': - res, _ = parseComplete(r.string()) - case 'Z': - st.cn.processReadyForQuery(r) - // done - return - case 'T', 'D': - // ignore any results - default: - errorf("unknown exec response: %q", t) - } - } - - panic("not reached") + res, _, err = st.cn.readExecuteResponse("simple query") + return res, err } func (st *stmt) exec(v []driver.Value) { + if len(v) >= 65536 { + errorf("got %d parameters but PostgreSQL only supports 65535 parameters", len(v)) + } if len(v) != len(st.paramTyps) { errorf("got %d parameters but the statement requires %d", len(v), len(st.paramTyps)) } - w := st.cn.writeBuf('B') - w.string("") + cn := st.cn + w := cn.writeBuf('B') + w.byte(0) // unnamed portal w.string(st.name) - w.int16(0) - w.int16(len(v)) - for i, x := range v { - if x == nil { - w.int32(-1) - } else { - b := encode(&st.cn.parameterStatus, x, st.paramTyps[i]) - w.int32(len(b)) - w.bytes(b) + + if cn.binaryParameters { + cn.sendBinaryParameters(w, v) + } else { + w.int16(0) + w.int16(len(v)) + for i, x := range v { + if x == nil { + w.int32(-1) + } else { + b := encode(&cn.parameterStatus, x, st.paramTyps[i]) + w.int32(len(b)) + w.bytes(b) + } } } - w.int16(0) - st.cn.send(w) + w.bytes(st.colFmtData) - w = st.cn.writeBuf('E') - w.string("") + w.next('E') + w.byte(0) w.int32(0) - st.cn.send(w) - st.cn.send(st.cn.writeBuf('S')) + w.next('S') + cn.send(w) - var err error - for { - t, r := st.cn.recv1() - switch t { - case 'E': - err = parseError(r) - case '2': - if err != nil { - panic(err) - } - goto workaround - case 'Z': - st.cn.processReadyForQuery(r) - if err != nil { - panic(err) - } - return - default: - errorf("unexpected bind response: %q", t) - } - } + cn.readBindResponse() + cn.postExecuteWorkaround() - // Work around a bug in sql.DB.QueryRow: in Go 1.2 and earlier it ignores - // any errors from rows.Next, which masks errors that happened during the - // execution of the query. To avoid the problem in common cases, we wait - // here for one more message from the database. If it's not an error the - // query will likely succeed (or perhaps has already, if it's a - // CommandComplete), so we push the message into the conn struct; recv1 - // will return it as the next message for rows.Next or rows.Close. - // However, if it's an error, we wait until ReadyForQuery and then return - // the error to our caller. -workaround: - for { - t, r := st.cn.recv1() - switch t { - case 'E': - err = parseError(r) - case 'C', 'D', 'I': - // the query didn't fail, but we can't process this message - st.cn.saveMessage(t, r) - return - case 'Z': - if err == nil { - errorf("unexpected ReadyForQuery during extended query execution") - } - st.cn.processReadyForQuery(r) - panic(err) - default: - errorf("unexpected message during query execution: %q", t) - } - } } func (st *stmt) NumInput() int { @@ -1034,7 +1270,7 @@ func (st *stmt) NumInput() int { // returns the number of rows affected (if applicable) and a string // identifying only the command that was executed, e.g. "ALTER TABLE". If the // command tag could not be parsed, parseComplete panics. -func parseComplete(commandTag string) (driver.Result, string) { +func (cn *conn) parseComplete(commandTag string) (driver.Result, string) { commandsWithAffectedRows := []string{ "SELECT ", // INSERT is handled below @@ -1061,6 +1297,7 @@ func parseComplete(commandTag string) (driver.Result, string) { if affectedRows == nil && strings.HasPrefix(commandTag, "INSERT ") { parts := strings.Split(commandTag, " ") if len(parts) != 3 { + cn.bad = true errorf("unexpected INSERT command tag %s", commandTag) } affectedRows = &parts[len(parts)-1] @@ -1072,17 +1309,23 @@ func parseComplete(commandTag string) (driver.Result, string) { } n, err := strconv.ParseInt(*affectedRows, 10, 64) if err != nil { + cn.bad = true errorf("could not parse commandTag: %s", err) } return driver.RowsAffected(n), commandTag } type rows struct { - st *stmt - done bool + cn *conn + colNames []string + colTyps []oid.Oid + colFmts []format + done bool + rb readBuf } func (rs *rows) Close() error { + // no need to look at cn.bad as Next() will for { err := rs.Next(nil) switch err { @@ -1093,11 +1336,10 @@ func (rs *rows) Close() error { return err } } - panic("not reached") } func (rs *rows) Columns() []string { - return rs.st.cols + return rs.colNames } func (rs *rows) Next(dest []driver.Value) (err error) { @@ -1105,43 +1347,48 @@ func (rs *rows) Next(dest []driver.Value) (err error) { return io.EOF } - defer errRecover(&err) + conn := rs.cn + if conn.bad { + return driver.ErrBadConn + } + defer conn.errRecover(&err) - conn := rs.st.cn for { - t, r := conn.recv1() + t := conn.recv1Buf(&rs.rb) switch t { case 'E': - err = parseError(r) + err = parseError(&rs.rb) case 'C', 'I': continue case 'Z': - conn.processReadyForQuery(r) + conn.processReadyForQuery(&rs.rb) rs.done = true if err != nil { return err } return io.EOF case 'D': - n := r.int16() + n := rs.rb.int16() + if err != nil { + conn.bad = true + errorf("unexpected DataRow after error %s", err) + } if n < len(dest) { dest = dest[:n] } for i := range dest { - l := r.int32() + l := rs.rb.int32() if l == -1 { dest[i] = nil continue } - dest[i] = decode(&conn.parameterStatus, r.next(l), rs.st.rowTyps[i]) + dest[i] = decode(&conn.parameterStatus, rs.rb.next(l), rs.colTyps[i], rs.colFmts[i]) } return default: errorf("unexpected message after execute: %q", t) } } - - panic("not reached") } // QuoteIdentifier quotes an "identifier" (e.g. a table or a column name) to be @@ -1168,6 +1415,68 @@ func md5s(s string) string { return fmt.Sprintf("%x", h.Sum(nil)) } +func (cn *conn) sendBinaryParameters(b *writeBuf, args []driver.Value) { + // Do one pass over the parameters to see if we're going to send any of + // them over in binary. If we are, create a paramFormats array at the + // same time. + var paramFormats []int + for i, x := range args { + _, ok := x.([]byte) + if ok { + if paramFormats == nil { + paramFormats = make([]int, len(args)) + } + paramFormats[i] = 1 + } + } + if paramFormats == nil { + b.int16(0) + } else { + b.int16(len(paramFormats)) + for _, x := range paramFormats { + b.int16(x) + } + } + + b.int16(len(args)) + for _, x := range args { + if x == nil { + b.int32(-1) + } else { + datum := binaryEncode(&cn.parameterStatus, x) + b.int32(len(datum)) + b.bytes(datum) + } + } +} + +func (cn *conn) sendBinaryModeQuery(query string, args []driver.Value) { + if len(args) >= 65536 { + errorf("got %d parameters but PostgreSQL only supports 65535 parameters", len(args)) + } + + b := cn.writeBuf('P') + b.byte(0) // unnamed statement + b.string(query) + b.int16(0) + + b.next('B') + b.int16(0) // unnamed portal and statement + cn.sendBinaryParameters(b, args) + b.bytes(colFmtDataAllText) + + b.next('D') + b.byte('P') + b.byte(0) // unnamed portal + + b.next('E') + b.byte(0) + b.int32(0) + + b.next('S') + cn.send(b) +} + func (c *conn) processParameterStatus(r *readBuf) { var err error @@ -1197,15 +1506,175 @@ func (c *conn) processReadyForQuery(r *readBuf) { c.txnStatus = transactionStatus(r.byte()) } -func parseMeta(r *readBuf) (cols []string, rowTyps []oid.Oid) { +func (cn *conn) readReadyForQuery() { + t, r := cn.recv1() + switch t { + case 'Z': + cn.processReadyForQuery(r) + return + default: + cn.bad = true + errorf("unexpected message %q; expected ReadyForQuery", t) + } +} + +func (cn *conn) readParseResponse() { + t, r := cn.recv1() + switch t { + case '1': + return + case 'E': + err := parseError(r) + cn.readReadyForQuery() + panic(err) + default: + cn.bad = true + errorf("unexpected Parse response %q", t) + } +} + +func (cn *conn) readStatementDescribeResponse() (paramTyps []oid.Oid, colNames []string, colTyps []oid.Oid) { + for { + t, r := cn.recv1() + switch t { + case 't': + nparams := r.int16() + paramTyps = make([]oid.Oid, nparams) + for i := range paramTyps { + paramTyps[i] = r.oid() + } + case 'n': + return paramTyps, nil, nil + case 'T': + colNames, colTyps = parseStatementRowDescribe(r) + return paramTyps, colNames, colTyps + case 'E': + err := parseError(r) + cn.readReadyForQuery() + panic(err) + default: + cn.bad = true + errorf("unexpected Describe statement response %q", t) + } + } +} + +func (cn *conn) readPortalDescribeResponse() (colNames []string, colFmts []format, colTyps []oid.Oid) { + t, r := cn.recv1() + switch t { + case 'T': + return parsePortalRowDescribe(r) + case 'n': + return nil, nil, nil + case 'E': + err := parseError(r) + cn.readReadyForQuery() + panic(err) + default: + cn.bad = true + errorf("unexpected Describe response %q", t) + } + panic("not reached") +} + +func (cn *conn) readBindResponse() { + t, r := cn.recv1() + switch t { + case '2': + return + case 'E': + err := parseError(r) + cn.readReadyForQuery() + panic(err) + default: + cn.bad = true + errorf("unexpected Bind response %q", t) + } +} + +func (cn *conn) postExecuteWorkaround() { + // Work around a bug in sql.DB.QueryRow: in Go 1.2 and earlier it ignores + // any errors from rows.Next, which masks errors that happened during the + // execution of the query. To avoid the problem in common cases, we wait + // here for one more message from the database. If it's not an error the + // query will likely succeed (or perhaps has already, if it's a + // CommandComplete), so we push the message into the conn struct; recv1 + // will return it as the next message for rows.Next or rows.Close. + // However, if it's an error, we wait until ReadyForQuery and then return + // the error to our caller. + for { + t, r := cn.recv1() + switch t { + case 'E': + err := parseError(r) + cn.readReadyForQuery() + panic(err) + case 'C', 'D', 'I': + // the query didn't fail, but we can't process this message + cn.saveMessage(t, r) + return + default: + cn.bad = true + errorf("unexpected message during extended query execution: %q", t) + } + } +} + +// Only for Exec(), since we ignore the returned data +func (cn *conn) readExecuteResponse(protocolState string) (res driver.Result, commandTag string, err error) { + for { + t, r := cn.recv1() + switch t { + case 'C': + if err != nil { + cn.bad = true + errorf("unexpected CommandComplete after error %s", err) + } + res, commandTag = cn.parseComplete(r.string()) + case 'Z': + cn.processReadyForQuery(r) + return res, commandTag, err + case 'E': + err = parseError(r) + case 'T', 'D', 'I': + if err != nil { + cn.bad = true + errorf("unexpected %q after error %s", t, err) + } + // ignore any results + default: + cn.bad = true + errorf("unknown %s response: %q", protocolState, t) + } + } +} + +func parseStatementRowDescribe(r *readBuf) (colNames []string, colTyps []oid.Oid) { n := r.int16() - cols = make([]string, n) - rowTyps = make([]oid.Oid, n) - for i := range cols { - cols[i] = r.string() + colNames = make([]string, n) + colTyps = make([]oid.Oid, n) + for i := range colNames { + colNames[i] = r.string() r.next(6) - rowTyps[i] = r.oid() - r.next(8) + colTyps[i] = r.oid() + r.next(6) + // format code not known when describing a statement; always 0 + r.next(2) + } + return +} + +func parsePortalRowDescribe(r *readBuf) (colNames []string, colFmts []format, colTyps []oid.Oid) { + n := r.int16() + colNames = make([]string, n) + colFmts = make([]format, n) + colTyps = make([]oid.Oid, n) + for i := range colNames { + colNames[i] = r.string() + r.next(6) + colTyps[i] = r.oid() + r.next(6) + colFmts[i] = format(r.int16()) } return } @@ -1258,7 +1727,13 @@ func parseEnviron(env []string) (out map[string]string) { accrue("application_name") case "PGSSLMODE": accrue("sslmode") - case "PGREQUIRESSL", "PGSSLCERT", "PGSSLKEY", "PGSSLROOTCERT", "PGSSLCRL": + case "PGSSLCERT": + accrue("sslcert") + case "PGSSLKEY": + accrue("sslkey") + case "PGSSLROOTCERT": + accrue("sslrootcert") + case "PGREQUIRESSL", "PGSSLCRL": unsupported() case "PGREQUIREPEER": unsupported() diff --git a/vendor/github.com/lib/pq/conn_test.go b/vendor/github.com/lib/pq/conn_test.go index e5e42812..af07e559 100644 --- a/vendor/github.com/lib/pq/conn_test.go +++ b/vendor/github.com/lib/pq/conn_test.go @@ -7,7 +7,6 @@ import ( "io" "os" "reflect" - "runtime" "strings" "testing" "time" @@ -17,21 +16,31 @@ type Fatalistic interface { Fatal(args ...interface{}) } +func forceBinaryParameters() bool { + bp := os.Getenv("PQTEST_BINARY_PARAMETERS") + if bp == "yes" { + return true + } else if bp == "" || bp == "no" { + return false + } else { + panic("unexpected value for PQTEST_BINARY_PARAMETERS") + } +} + func openTestConnConninfo(conninfo string) (*sql.DB, error) { - datname := os.Getenv("PGDATABASE") - sslmode := os.Getenv("PGSSLMODE") - timeout := os.Getenv("PGCONNECT_TIMEOUT") - - if datname == "" { - os.Setenv("PGDATABASE", "pqgotest") + defaultTo := func(envvar string, value string) { + if os.Getenv(envvar) == "" { + os.Setenv(envvar, value) + } } + defaultTo("PGDATABASE", "pqgotest") + defaultTo("PGSSLMODE", "disable") + defaultTo("PGCONNECT_TIMEOUT", "20") - if sslmode == "" { - os.Setenv("PGSSLMODE", "disable") - } - - if timeout == "" { - os.Setenv("PGCONNECT_TIMEOUT", "20") + if forceBinaryParameters() && + !strings.HasPrefix(conninfo, "postgres://") && + !strings.HasPrefix(conninfo, "postgresql://") { + conninfo = conninfo + " binary_parameters=yes" } return sql.Open("postgres", conninfo) @@ -56,12 +65,6 @@ func getServerVersion(t *testing.T, db *sql.DB) int { } func TestReconnect(t *testing.T) { - if runtime.Version() == "go1.0.2" { - fmt.Println("Skipping failing test; " + - "fixed in database/sql on go1.0.3+") - return - } - db1 := openTestConn(t) defer db1.Close() tx, err := db1.Begin() @@ -114,18 +117,22 @@ func TestCommitInFailedTransaction(t *testing.T) { } func TestOpenURL(t *testing.T) { - db, err := openTestConnConninfo("postgres://") - if err != nil { - t.Fatal(err) + testURL := func(url string) { + db, err := openTestConnConninfo(url) + 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.Begin() + if err != nil { + t.Fatal(err) + } + txn.Rollback() } - 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() + testURL("postgres://") + testURL("postgresql://") } func TestExec(t *testing.T) { @@ -350,6 +357,7 @@ func TestEncodeDecode(t *testing.T) { '2000-1-1 01:02:03.04-7'::timestamptz, 0::boolean, 123, + -321, 3.14::float8 WHERE E'\\000\\001\\002'::bytea = $1 @@ -378,9 +386,9 @@ func TestEncodeDecode(t *testing.T) { var got2 string var got3 = sql.NullInt64{Valid: true} var got4 time.Time - var got5, got6, got7 interface{} + var got5, got6, got7, got8 interface{} - err = r.Scan(&got1, &got2, &got3, &got4, &got5, &got6, &got7) + err = r.Scan(&got1, &got2, &got3, &got4, &got5, &got6, &got7, &got8) if err != nil { t.Fatal(err) } @@ -409,8 +417,12 @@ func TestEncodeDecode(t *testing.T) { t.Fatalf("expected 123, got %d", got6) } - if got7 != float64(3.14) { - t.Fatalf("expected 3.14, got %f", got7) + if got7 != int64(-321) { + t.Fatalf("expected -321, got %d", got7) + } + + if got8 != float64(3.14) { + t.Fatalf("expected 3.14, got %f", got8) } } @@ -465,32 +477,38 @@ func TestErrorDuringStartup(t *testing.T) { e, ok := err.(*Error) if !ok { t.Fatalf("expected Error, got %#v", err) - } else if e.Code.Name() != "invalid_authorization_specification" { - t.Fatalf("expected invalid_authorization_specification, got %s (%+v)", e.Code.Name(), err) + } else if e.Code.Name() != "invalid_authorization_specification" && e.Code.Name() != "invalid_password" { + t.Fatalf("expected invalid_authorization_specification or invalid_password, got %s (%+v)", e.Code.Name(), err) } } func TestBadConn(t *testing.T) { var err error + cn := conn{} func() { - defer errRecover(&err) + defer cn.errRecover(&err) panic(io.EOF) }() - if err != driver.ErrBadConn { t.Fatalf("expected driver.ErrBadConn, got: %#v", err) } + if !cn.bad { + t.Fatalf("expected cn.bad") + } + cn = conn{} func() { - defer errRecover(&err) + defer cn.errRecover(&err) e := &Error{Severity: Efatal} panic(e) }() - if err != driver.ErrBadConn { t.Fatalf("expected driver.ErrBadConn, got: %#v", err) } + if !cn.bad { + t.Fatalf("expected cn.bad") + } } func TestErrorOnExec(t *testing.T) { @@ -832,11 +850,6 @@ func TestIssue196(t *testing.T) { // Test that any CommandComplete messages sent before the query results are // ignored. func TestIssue282(t *testing.T) { - if strings.HasPrefix(runtime.Version(), "go1.0") { - fmt.Println("Skipping test due to lack of Queryer implementation") - return - } - db := openTestConn(t) defer db.Close() @@ -872,6 +885,60 @@ func TestReadFloatPrecision(t *testing.T) { } } +func TestXactMultiStmt(t *testing.T) { + // minified test case based on bug reports from + // pico303@gmail.com and rangelspam@gmail.com + t.Skip("Skipping failing test") + db := openTestConn(t) + defer db.Close() + + tx, err := db.Begin() + if err != nil { + t.Fatal(err) + } + defer tx.Commit() + + rows, err := tx.Query("select 1") + if err != nil { + t.Fatal(err) + } + + if rows.Next() { + var val int32 + if err = rows.Scan(&val); err != nil { + t.Fatal(err) + } + } else { + t.Fatal("Expected at least one row in first query in xact") + } + + rows2, err := tx.Query("select 2") + if err != nil { + t.Fatal(err) + } + + if rows2.Next() { + var val2 int32 + if err := rows2.Scan(&val2); err != nil { + t.Fatal(err) + } + } else { + t.Fatal("Expected at least one row in second query in xact") + } + + if err = rows.Err(); err != nil { + t.Fatal(err) + } + + if err = rows2.Err(); err != nil { + t.Fatal(err) + } + + if err = tx.Commit(); err != nil { + t.Fatal(err) + } +} + var envParseTests = []struct { Expected map[string]string Env []string @@ -908,7 +975,8 @@ func TestParseComplete(t *testing.T) { } } }() - res, c := parseComplete(commandTag) + cn := &conn{} + res, c := cn.parseComplete(commandTag) if c != command { t.Errorf("Expected %v, got %v", command, c) } @@ -1161,14 +1229,15 @@ func TestRuntimeParameters(t *testing.T) { if err != nil { t.Fatal(err) } - defer db.Close() // application_name didn't exist before 9.0 if test.param == "application_name" && getServerVersion(t, db) < 90000 { + db.Close() continue } tryGetParameterValue := func() (value string, outcome RuntimeTestResult) { + defer db.Close() row := db.QueryRow("SELECT current_setting($1)", test.param) err = row.Scan(&value) if err != nil { diff --git a/vendor/github.com/lib/pq/conn_xact_test.go b/vendor/github.com/lib/pq/conn_xact_test.go deleted file mode 100644 index 4c635d43..00000000 --- a/vendor/github.com/lib/pq/conn_xact_test.go +++ /dev/null @@ -1,61 +0,0 @@ -// +build go1.1 - -package pq - -import ( - "testing" -) - -func TestXactMultiStmt(t *testing.T) { - // minified test case based on bug reports from - // pico303@gmail.com and rangelspam@gmail.com - t.Skip("Skipping failing test") - db := openTestConn(t) - defer db.Close() - - tx, err := db.Begin() - if err != nil { - t.Fatal(err) - } - defer tx.Commit() - - rows, err := tx.Query("select 1") - if err != nil { - t.Fatal(err) - } - - if rows.Next() { - var val int32 - if err = rows.Scan(&val); err != nil { - t.Fatal(err) - } - } else { - t.Fatal("Expected at least one row in first query in xact") - } - - rows2, err := tx.Query("select 2") - if err != nil { - t.Fatal(err) - } - - if rows2.Next() { - var val2 int32 - if err := rows2.Scan(&val2); err != nil { - t.Fatal(err) - } - } else { - t.Fatal("Expected at least one row in second query in xact") - } - - if err = rows.Err(); err != nil { - t.Fatal(err) - } - - if err = rows2.Err(); err != nil { - t.Fatal(err) - } - - if err = tx.Commit(); err != nil { - t.Fatal(err) - } -} diff --git a/vendor/github.com/lib/pq/copy.go b/vendor/github.com/lib/pq/copy.go index 0a9cca8b..101f1113 100644 --- a/vendor/github.com/lib/pq/copy.go +++ b/vendor/github.com/lib/pq/copy.go @@ -4,7 +4,8 @@ import ( "database/sql/driver" "encoding/binary" "errors" - "sync/atomic" + "fmt" + "sync" ) var ( @@ -48,9 +49,10 @@ type copyin struct { rowData chan []byte done chan bool - closed bool - err error - errorset int32 + closed bool + + sync.Mutex // guards err + err error } const ciBufferSize = 64 * 1024 @@ -59,8 +61,6 @@ const ciBufferSize = 64 * 1024 const ciBufferFlushSize = 63 * 1024 func (cn *conn) prepareCopyIn(q string) (_ driver.Stmt, err error) { - defer errRecover(&err) - if !cn.isInTransaction() { return nil, errCopyNotSupportedOutsideTxn } @@ -69,7 +69,7 @@ func (cn *conn) prepareCopyIn(q string) (_ driver.Stmt, err error) { cn: cn, buffer: make([]byte, 0, ciBufferSize), rowData: make(chan []byte), - done: make(chan bool), + done: make(chan bool, 1), } // add CopyData identifier + 4 bytes for message length ci.buffer = append(ci.buffer, 'd', 0, 0, 0, 0) @@ -96,11 +96,13 @@ awaitCopyInResponse: err = parseError(r) case 'Z': if err == nil { + cn.bad = true errorf("unexpected ReadyForQuery in response to COPY") } cn.processReadyForQuery(r) return nil, err default: + cn.bad = true errorf("unknown response for copy query: %q", t) } } @@ -119,11 +121,10 @@ awaitCopyInResponse: cn.processReadyForQuery(r) return nil, err default: + cn.bad = true errorf("unknown response for CopyFail: %q", t) } } - - panic("not reached") } func (ci *copyin) flush(buf []byte) { @@ -138,30 +139,50 @@ func (ci *copyin) flush(buf []byte) { func (ci *copyin) resploop() { for { - t, r := ci.cn.recv1() + var r readBuf + t, err := ci.cn.recvMessage(&r) + if err != nil { + ci.cn.bad = true + ci.setError(err) + ci.done <- true + return + } switch t { case 'C': // complete + case 'N': + // NoticeResponse case 'Z': - ci.cn.processReadyForQuery(r) + ci.cn.processReadyForQuery(&r) ci.done <- true return case 'E': - err := parseError(r) + err := parseError(&r) ci.setError(err) default: - errorf("unknown response: %q", t) + ci.cn.bad = true + ci.setError(fmt.Errorf("unknown response during CopyIn: %q", t)) + ci.done <- true + return } } } func (ci *copyin) isErrorSet() bool { - return atomic.LoadInt32(&ci.errorset) != 0 + ci.Lock() + isSet := (ci.err != nil) + ci.Unlock() + return isSet } +// setError() sets ci.err if one has not been set already. Caller must not be +// holding ci.Mutex. func (ci *copyin) setError(err error) { - ci.err = err - atomic.StoreInt32(&ci.errorset, 1) + ci.Lock() + if ci.err == nil { + ci.err = err + } + ci.Unlock() } func (ci *copyin) NumInput() int { @@ -180,20 +201,21 @@ func (ci *copyin) Query(v []driver.Value) (r driver.Rows, err error) { // errors from pending data, since Stmt.Close() doesn't return errors // to the user. func (ci *copyin) Exec(v []driver.Value) (r driver.Result, err error) { - defer errRecover(&err) - if ci.closed { return nil, errCopyInClosed } + if ci.cn.bad { + return nil, driver.ErrBadConn + } + defer ci.cn.errRecover(&err) + if ci.isErrorSet() { return nil, ci.err } if len(v) == 0 { - err = ci.Close() - ci.closed = true - return nil, err + return nil, ci.Close() } numValues := len(v) @@ -216,11 +238,15 @@ func (ci *copyin) Exec(v []driver.Value) (r driver.Result, err error) { } func (ci *copyin) Close() (err error) { - defer errRecover(&err) - - if ci.closed { - return errCopyInClosed + if ci.closed { // Don't do anything, we're already closed + return nil } + ci.closed = true + + if ci.cn.bad { + return driver.ErrBadConn + } + defer ci.cn.errRecover(&err) if len(ci.buffer) > 0 { ci.flush(ci.buffer) diff --git a/vendor/github.com/lib/pq/copy_test.go b/vendor/github.com/lib/pq/copy_test.go index fbd5a11b..6af4c9c7 100644 --- a/vendor/github.com/lib/pq/copy_test.go +++ b/vendor/github.com/lib/pq/copy_test.go @@ -94,6 +94,86 @@ func TestCopyInMultipleValues(t *testing.T) { } } +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() @@ -275,6 +355,64 @@ func TestCopySyntaxError(t *testing.T) { } } +// 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) + } + + _, 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") + } + pge, ok := err.(*Error) + if !ok { + t.Fatalf("expected *pq.Error, got %+#v", err) + } else if pge.Code.Name() != "admin_shutdown" { + t.Fatalf("expected admin_shutdown, got %s", pge.Code.Name()) + } + + err = stmt.Close() + if err != nil { + t.Fatal(err) + } +} + func BenchmarkCopyIn(b *testing.B) { db := openTestConn(b) defer db.Close() diff --git a/vendor/github.com/lib/pq/doc.go b/vendor/github.com/lib/pq/doc.go index b5155801..f772117d 100644 --- a/vendor/github.com/lib/pq/doc.go +++ b/vendor/github.com/lib/pq/doc.go @@ -5,8 +5,9 @@ In most cases clients will use the database/sql package instead of using this package directly. For example: import ( - _ "github.com/lib/pq" "database/sql" + + _ "github.com/lib/pq" ) func main() { @@ -47,12 +48,16 @@ supported: * sslmode - Whether or not to use SSL (default is require, this is not the default for libpq) * fallback_application_name - An application_name to fall back to if one isn't provided. * connect_timeout - Maximum wait for connection, in seconds. Zero or not specified means wait indefinitely. + * sslcert - Cert file location. The file must contain PEM encoded data. + * sslkey - Key file location. The file must contain PEM encoded data. + * sslrootcert - The location of the root certificate file. The file must contain PEM encoded data. Valid values for sslmode are: * disable - No SSL * require - Always SSL (skip verification) - * verify-full - Always SSL (require verification) + * verify-ca - Always SSL (verify that the certificate presented by the server was signed by a trusted CA) + * verify-full - Always SSL (verify that the certification presented by the server was signed by a trusted CA and the server host name matches the one in the certificate) See http://www.postgresql.org/docs/current/static/libpq-connect.html#LIBPQ-CONNSTRING for more information about connection string parameters. diff --git a/vendor/github.com/lib/pq/encode.go b/vendor/github.com/lib/pq/encode.go index a7599910..88422eb3 100644 --- a/vendor/github.com/lib/pq/encode.go +++ b/vendor/github.com/lib/pq/encode.go @@ -3,24 +3,34 @@ package pq import ( "bytes" "database/sql/driver" + "encoding/binary" "encoding/hex" "fmt" - "github.com/lib/pq/oid" "math" "strconv" "strings" "sync" "time" + + "github.com/lib/pq/oid" ) +func binaryEncode(parameterStatus *parameterStatus, x interface{}) []byte { + switch v := x.(type) { + case []byte: + return v + default: + return encode(parameterStatus, x, oid.T_unknown) + } + panic("not reached") +} + func encode(parameterStatus *parameterStatus, x interface{}, pgtypOid oid.Oid) []byte { switch v := x.(type) { case int64: - return []byte(fmt.Sprintf("%d", v)) - case float32: - return []byte(fmt.Sprintf("%.9f", v)) + return strconv.AppendInt(nil, v, 10) case float64: - return []byte(fmt.Sprintf("%.17f", v)) + return strconv.AppendFloat(nil, v, 'f', -1, 64) case []byte: if pgtypOid == oid.T_bytea { return encodeBytea(parameterStatus.serverVersion, v) @@ -34,7 +44,7 @@ func encode(parameterStatus *parameterStatus, x interface{}, pgtypOid oid.Oid) [ return []byte(v) case bool: - return []byte(fmt.Sprintf("%t", v)) + return strconv.AppendBool(nil, v) case time.Time: return formatTs(v) @@ -45,7 +55,33 @@ func encode(parameterStatus *parameterStatus, x interface{}, pgtypOid oid.Oid) [ panic("not reached") } -func decode(parameterStatus *parameterStatus, s []byte, typ oid.Oid) interface{} { +func decode(parameterStatus *parameterStatus, s []byte, typ oid.Oid, f format) interface{} { + if f == formatBinary { + return binaryDecode(parameterStatus, s, typ) + } else { + return textDecode(parameterStatus, s, typ) + } +} + +func binaryDecode(parameterStatus *parameterStatus, s []byte, typ oid.Oid) interface{} { + switch typ { + case oid.T_bytea: + return s + case oid.T_int8: + return int64(binary.BigEndian.Uint64(s)) + case oid.T_int4: + return int64(int32(binary.BigEndian.Uint32(s))) + case oid.T_int2: + return int64(int16(binary.BigEndian.Uint16(s))) + + default: + errorf("don't know how to decode binary parameter of type %u", uint32(typ)) + } + + panic("not reached") +} + +func textDecode(parameterStatus *parameterStatus, s []byte, typ oid.Oid) interface{} { switch typ { case oid.T_bytea: return parseBytea(s) @@ -59,7 +95,7 @@ func decode(parameterStatus *parameterStatus, s []byte, typ oid.Oid) interface{} return mustParse("15:04:05-07", typ, s) case oid.T_bool: return s[0] == 't' - case oid.T_int8, oid.T_int2, oid.T_int4: + case oid.T_int8, oid.T_int4, oid.T_int2: i, err := strconv.ParseInt(string(s), 10, 64) if err != nil { errorf("%s", err) @@ -86,8 +122,6 @@ func appendEncodedText(parameterStatus *parameterStatus, buf []byte, x interface switch v := x.(type) { case int64: return strconv.AppendInt(buf, v, 10) - case float32: - return strconv.AppendFloat(buf, float64(v), 'f', -1, 32) case float64: return strconv.AppendFloat(buf, v, 'f', -1, 64) case []byte: @@ -149,12 +183,6 @@ func appendEscapedText(buf []byte, text string) []byte { func mustParse(f string, typ oid.Oid, s []byte) time.Time { str := string(s) - // Special case until time.Parse bug is fixed: - // http://code.google.com/p/go/issues/detail?id=3487 - if str[len(str)-2] == '.' { - str += "0" - } - // check for a 30-minute-offset timezone if (typ == oid.T_timestamptz || typ == oid.T_timetz) && str[len(str)-3] == ':' { @@ -212,12 +240,75 @@ func (c *locationCache) getLocation(offset int) *time.Location { return location } +var infinityTsEnabled = false +var infinityTsNegative time.Time +var infinityTsPositive time.Time + +const ( + infinityTsEnabledAlready = "pq: infinity timestamp enabled already" + infinityTsNegativeMustBeSmaller = "pq: infinity timestamp: negative value must be smaller (before) than positive" +) + +/* + * If EnableInfinityTs is not called, "-infinity" and "infinity" will return + * []byte("-infinity") and []byte("infinity") respectively, and potentially + * cause error "sql: Scan error on column index 0: unsupported driver -> Scan pair: []uint8 -> *time.Time", + * when scanning into a time.Time value. + * + * Once EnableInfinityTs has been called, all connections created using this + * driver will decode Postgres' "-infinity" and "infinity" for "timestamp", + * "timestamp with time zone" and "date" types to the predefined minimum and + * maximum times, respectively. When encoding time.Time values, any time which + * equals or preceeds the predefined minimum time will be encoded to + * "-infinity". Any values at or past the maximum time will similarly be + * encoded to "infinity". + * + * + * If EnableInfinityTs is called with negative >= positive, it will panic. + * Calling EnableInfinityTs after a connection has been established results in + * undefined behavior. If EnableInfinityTs is called more than once, it will + * panic. + */ +func EnableInfinityTs(negative time.Time, positive time.Time) { + if infinityTsEnabled { + panic(infinityTsEnabledAlready) + } + if !negative.Before(positive) { + panic(infinityTsNegativeMustBeSmaller) + } + infinityTsEnabled = true + infinityTsNegative = negative + infinityTsPositive = positive +} + +/* + * Testing might want to toggle infinityTsEnabled + */ +func disableInfinityTs() { + infinityTsEnabled = false +} + // This is a time function specific to the Postgres default DateStyle // setting ("ISO, MDY"), the only one we currently support. This // accounts for the discrepancies between the parsing available with // time.Parse and the Postgres date formatting quirks. -func parseTs(currentLocation *time.Location, str string) (result time.Time) { +func parseTs(currentLocation *time.Location, str string) interface{} { + switch str { + case "-infinity": + if infinityTsEnabled { + return infinityTsNegative + } + return []byte(str) + case "infinity": + if infinityTsEnabled { + return infinityTsPositive + } + return []byte(str) + } + monSep := strings.IndexRune(str, '-') + // this is Gregorian year, not ISO Year + // In Gregorian system, the year 1 BC is followed by AD 1 year := mustAtoi(str[:monSep]) daySep := monSep + 3 month := mustAtoi(str[monSep+1 : daySep]) @@ -245,7 +336,6 @@ func parseTs(currentLocation *time.Location, str string) (result time.Time) { nanoSec := 0 tzOff := 0 - bcSign := 1 if remainderIdx < len(str) && str[remainderIdx:remainderIdx+1] == "." { fracStart := remainderIdx + 1 @@ -281,14 +371,17 @@ func parseTs(currentLocation *time.Location, str string) (result time.Time) { } tzOff = tzSign * ((tzHours * 60 * 60) + (tzMin * 60) + tzSec) } + var isoYear int if remainderIdx < len(str) && str[remainderIdx:remainderIdx+3] == " BC" { - bcSign = -1 + isoYear = 1 - year remainderIdx += 3 + } else { + isoYear = year } if remainderIdx < len(str) { errorf("expected end of input, got %v", str[remainderIdx:]) } - t := time.Date(bcSign*year, time.Month(month), day, + t := time.Date(isoYear, time.Month(month), day, hour, minute, second, nanoSec, globalLocationCache.getLocation(tzOff)) @@ -306,26 +399,48 @@ func parseTs(currentLocation *time.Location, str string) (result time.Time) { return t } -// formatTs formats t as time.RFC3339Nano and appends time zone seconds if -// needed. +// formatTs formats t into a format postgres understands. func formatTs(t time.Time) (b []byte) { + if infinityTsEnabled { + // t <= -infinity : ! (t > -infinity) + if !t.After(infinityTsNegative) { + return []byte("-infinity") + } + // t >= infinity : ! (!t < infinity) + if !t.Before(infinityTsPositive) { + return []byte("infinity") + } + } + // Need to send dates before 0001 A.D. with " BC" suffix, instead of the + // minus sign preferred by Go. + // Beware, "0000" in ISO is "1 BC", "-0001" is "2 BC" and so on + bc := false + if t.Year() <= 0 { + // flip year sign, and add 1, e.g: "0" will be "1", and "-10" will be "11" + t = t.AddDate((-t.Year())*2+1, 0, 0) + bc = true + } b = []byte(t.Format(time.RFC3339Nano)) _, offset := t.Zone() offset = offset % 60 - if offset == 0 { - return b + if offset != 0 { + // RFC3339Nano already printed the minus sign + if offset < 0 { + offset = -offset + } + + b = append(b, ':') + if offset < 10 { + b = append(b, '0') + } + b = strconv.AppendInt(b, int64(offset), 10) } - if offset < 0 { - offset = -offset + if bc { + b = append(b, " BC"...) } - - b = append(b, ':') - if offset < 10 { - b = append(b, '0') - } - return strconv.AppendInt(b, int64(offset), 10) + return b } // Parse a bytea value received from the server. Both "hex" and the legacy @@ -380,7 +495,10 @@ func parseBytea(s []byte) (result []byte) { func encodeBytea(serverVersion int, v []byte) (result []byte) { if serverVersion >= 90000 { // Use the hex format if we know that the server supports it - result = []byte(fmt.Sprintf("\\x%x", v)) + result = make([]byte, 2+hex.EncodedLen(len(v))) + result[0] = '\\' + result[1] = 'x' + hex.Encode(result[2:], v) } else { // .. or resort to "escape" for _, b := range v { diff --git a/vendor/github.com/lib/pq/encode_test.go b/vendor/github.com/lib/pq/encode_test.go index e199a5e7..97b66388 100644 --- a/vendor/github.com/lib/pq/encode_test.go +++ b/vendor/github.com/lib/pq/encode_test.go @@ -1,12 +1,13 @@ package pq import ( - "github.com/lib/pq/oid" - "bytes" + "database/sql" "fmt" "testing" "time" + + "github.com/lib/pq/oid" ) func TestScanTimestamp(t *testing.T) { @@ -30,8 +31,8 @@ func TestScanNilTimestamp(t *testing.T) { } var timeTests = []struct { - str string - expected time.Time + 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))}, @@ -57,13 +58,20 @@ var timeTests = []struct { 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))}, - {"10000-02-03 04:05:06 BC", time.Date(-10000, time.February, 3, 4, 5, 6, 0, time.FixedZone("", 0))}, - {"0010-02-03 04:05:06 BC", time.Date(-10, time.February, 3, 4, 5, 6, 0, time.FixedZone("", 0))}, - {"0010-02-03 04:05:06.123 BC", time.Date(-10, time.February, 3, 4, 5, 6, 123000000, time.FixedZone("", 0))}, - {"0010-02-03 04:05:06.123-07 BC", time.Date(-10, time.February, 3, 4, 5, 6, 123000000, + {"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))}, } +// Helper function for the two tests below func tryParse(str string) (t time.Time, err error) { defer func() { if p := recover(); p != nil { @@ -71,19 +79,52 @@ func tryParse(str string) (t time.Time, err error) { return } }() - t = parseTs(nil, str) + i := parseTs(nil, str) + t, ok := i.(time.Time) + if !ok { + err = fmt.Errorf("Not a time.Time type, got %#v", i) + } return } +// Test that parsing the string results in the expected value. func TestParseTs(t *testing.T) { for i, tt := range timeTests { val, err := tryParse(tt.str) - if val.String() != tt.expected.String() { - t.Errorf("%d: expected to parse %q into %q; got %q", - i, tt.str, tt.expected, val) - } 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) + } + } +} + +// 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 := tryParse(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) } } } @@ -96,8 +137,18 @@ var formatTimeTests = []struct { {time.Date(2001, time.February, 3, 4, 5, 6, 123456789, time.FixedZone("", 0)), "2001-02-03T04:05:06.123456789Z"}, {time.Date(2001, time.February, 3, 4, 5, 6, 123456789, time.FixedZone("", 2*60*60)), "2001-02-03T04:05:06.123456789+02:00"}, {time.Date(2001, time.February, 3, 4, 5, 6, 123456789, time.FixedZone("", -6*60*60)), "2001-02-03T04:05:06.123456789-06:00"}, - {time.Date(1, time.January, 1, 0, 0, 0, 0, time.FixedZone("", 19*60+32)), "0001-01-01T00:00:00+00:19:32"}, {time.Date(2001, time.February, 3, 4, 5, 6, 0, time.FixedZone("", -(7*60*60+30*60+9))), "2001-02-03T04:05:06-07:30:09"}, + + {time.Date(1, time.February, 3, 4, 5, 6, 123456789, time.FixedZone("", 0)), "0001-02-03T04:05:06.123456789Z"}, + {time.Date(1, time.February, 3, 4, 5, 6, 123456789, time.FixedZone("", 2*60*60)), "0001-02-03T04:05:06.123456789+02:00"}, + {time.Date(1, time.February, 3, 4, 5, 6, 123456789, time.FixedZone("", -6*60*60)), "0001-02-03T04:05:06.123456789-06:00"}, + + {time.Date(0, time.February, 3, 4, 5, 6, 123456789, time.FixedZone("", 0)), "0001-02-03T04:05:06.123456789Z BC"}, + {time.Date(0, time.February, 3, 4, 5, 6, 123456789, time.FixedZone("", 2*60*60)), "0001-02-03T04:05:06.123456789+02:00 BC"}, + {time.Date(0, time.February, 3, 4, 5, 6, 123456789, time.FixedZone("", -6*60*60)), "0001-02-03T04: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-03T04: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-03T04:05:06-07:30:09 BC"}, } func TestFormatTs(t *testing.T) { @@ -213,6 +264,131 @@ func TestTimestampWithOutTimezone(t *testing.T) { 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 + + expectedError := fmt.Errorf(`sql: Scan error on column index 0: unsupported driver -> Scan pair: []uint8 -> *time.Time`) + type testCases []struct { + Query string + Param string + ExpectedErr error + ExpectedVal interface{} + } + tc := testCases{ + {"SELECT $1::timestamp", "-infinity", expectedError, "-infinity"}, + {"SELECT $1::timestamptz", "-infinity", expectedError, "-infinity"}, + {"SELECT $1::timestamp", "infinity", expectedError, "infinity"}, + {"SELECT $1::timestamptz", "infinity", expectedError, "infinity"}, + } + // try to assert []byte to time.Time + for _, q := range tc { + err = db.QueryRow(q.Query, q.Param).Scan(&resultT) + if err.Error() != q.ExpectedErr.Error() { + t.Errorf("Scanning -/+infinity, expected error, %q, got %q", q.ExpectedErr, 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()) + } + + y_1500 := 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", y_1500).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", y_1500).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() @@ -225,7 +401,7 @@ func TestStringWithNul(t *testing.T) { } } -func TestByteaToText(t *testing.T) { +func TestByteSliceToText(t *testing.T) { db := openTestConn(t) defer db.Close() @@ -243,7 +419,7 @@ func TestByteaToText(t *testing.T) { } } -func TestTextToBytea(t *testing.T) { +func TestStringToBytea(t *testing.T) { db := openTestConn(t) defer db.Close() @@ -261,6 +437,136 @@ func TestTextToBytea(t *testing.T) { } } +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 TestByteaOutputFormatEncoding(t *testing.T) { input := []byte("\\x\x00\x01\x02\xFF\xFEabcdefg0123") want := []byte("\\x5c78000102fffe6162636465666730313233") @@ -285,7 +591,7 @@ func TestByteaOutputFormats(t *testing.T) { return } - testByteaOutputFormat := func(f string) { + testByteaOutputFormat := func(f string, usePrepared bool) { expectedData := []byte("\x5c\x78\x00\xff\x61\x62\x63\x01\x08") sqlQuery := "SELECT decode('5c7800ff6162630108', 'hex')" @@ -302,8 +608,18 @@ func TestByteaOutputFormats(t *testing.T) { if err != nil { t.Fatal(err) } - // use Query; QueryRow would hide the actual error - rows, err := txn.Query(sqlQuery) + 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) } @@ -321,13 +637,21 @@ func TestByteaOutputFormats(t *testing.T) { 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") - testByteaOutputFormat("escape") + testByteaOutputFormat("hex", false) + testByteaOutputFormat("escape", false) + testByteaOutputFormat("hex", true) + testByteaOutputFormat("escape", true) } func TestAppendEncodedText(t *testing.T) { @@ -335,15 +659,13 @@ func TestAppendEncodedText(t *testing.T) { buf = appendEncodedText(¶meterStatus{serverVersion: 90000}, buf, int64(10)) buf = append(buf, '\t') - buf = appendEncodedText(¶meterStatus{serverVersion: 90000}, buf, float32(42.0000000001)) - 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\t42.0000000001\thello\\tworld\t\\\\x0080ff" { + if string(buf) != "10\t42.0000000001\thello\\tworld\t\\\\x0080ff" { t.Fatal(string(buf)) } } diff --git a/vendor/github.com/lib/pq/error.go b/vendor/github.com/lib/pq/error.go index 874c3564..b4bb44ce 100644 --- a/vendor/github.com/lib/pq/error.go +++ b/vendor/github.com/lib/pq/error.go @@ -459,12 +459,26 @@ func errorf(s string, args ...interface{}) { panic(fmt.Errorf("pq: %s", fmt.Sprintf(s, args...))) } -func errRecover(err *error) { +func errRecoverNoErrBadConn(err *error) { + e := recover() + if e == nil { + // Do nothing + return + } + var ok bool + *err, ok = e.(error) + if !ok { + *err = fmt.Errorf("pq: unexpected error: %#v", e) + } +} + +func (c *conn) errRecover(err *error) { e := recover() switch v := e.(type) { case nil: // Do nothing case runtime.Error: + c.bad = true panic(v) case *Error: if v.Fatal() { @@ -482,6 +496,13 @@ func errRecover(err *error) { } default: + c.bad = true panic(fmt.Sprintf("unknown error: %#v", e)) } + + // Any time we return ErrBadConn, we need to remember it since *Tx doesn't + // mark the connection bad in database/sql. + if *err == driver.ErrBadConn { + c.bad = true + } } diff --git a/vendor/github.com/lib/pq/hstore/hstore_test.go b/vendor/github.com/lib/pq/hstore/hstore_test.go index 73a988f9..c9c108fc 100644 --- a/vendor/github.com/lib/pq/hstore/hstore_test.go +++ b/vendor/github.com/lib/pq/hstore/hstore_test.go @@ -2,9 +2,10 @@ package hstore import ( "database/sql" - _ "github.com/lib/pq" "os" "testing" + + _ "github.com/lib/pq" ) type Fatalistic interface { @@ -38,8 +39,7 @@ func TestHstore(t *testing.T) { // quitely create hstore if it doesn't exist _, err := db.Exec("CREATE EXTENSION IF NOT EXISTS hstore") if err != nil { - t.Log("Skipping hstore tests - hstore extension create failed. " + err.Error()) - return + t.Skipf("Skipping hstore tests - hstore extension create failed: %s", err.Error()) } hs := Hstore{} diff --git a/vendor/github.com/lib/pq/listen_example/doc.go b/vendor/github.com/lib/pq/listen_example/doc.go index 34496f4f..5bc99f5c 100644 --- a/vendor/github.com/lib/pq/listen_example/doc.go +++ b/vendor/github.com/lib/pq/listen_example/doc.go @@ -18,11 +18,11 @@ mechanism to avoid polling the database while waiting for more work to arrive. package main import ( - "github.com/lib/pq" - "database/sql" "fmt" "time" + + "github.com/lib/pq" ) func doWork(db *sql.DB, work int64) { diff --git a/vendor/github.com/lib/pq/notify.go b/vendor/github.com/lib/pq/notify.go index 2c3b4cd4..8cad5781 100644 --- a/vendor/github.com/lib/pq/notify.go +++ b/vendor/github.com/lib/pq/notify.go @@ -6,7 +6,6 @@ package pq import ( "errors" "fmt" - "io" "sync" "sync/atomic" "time" @@ -87,12 +86,16 @@ func NewListenerConn(name string, notificationChan chan<- *Notification) (*Liste // Returns an error if an unrecoverable error has occurred and the ListenerConn // should be abandoned. func (l *ListenerConn) acquireSenderLock() error { - l.connectionLock.Lock() - defer l.connectionLock.Unlock() - if l.err != nil { - return l.err - } + // we must acquire senderLock first to avoid deadlocks; see ExecSimpleQuery l.senderLock.Lock() + + l.connectionLock.Lock() + err := l.err + l.connectionLock.Unlock() + if err != nil { + l.senderLock.Unlock() + return err + } return nil } @@ -125,10 +128,11 @@ func (l *ListenerConn) setState(newState int32) bool { // away or should be discarded because we couldn't agree on the state with the // server backend. func (l *ListenerConn) listenerConnLoop() (err error) { - defer errRecover(&err) + defer errRecoverNoErrBadConn(&err) + r := &readBuf{} for { - t, r, err := l.cn.recvMessage() + t, err := l.cn.recvMessage(r) if err != nil { return err } @@ -139,6 +143,9 @@ func (l *ListenerConn) listenerConnLoop() (err error) { // about the scratch buffer being overwritten. l.notificationChan <- recvNotification(r) + case 'T', 'D': + // only used by tests; ignore + case 'E': // We might receive an ErrorResponse even when not in a query; it // is expected that the server will close the connection after @@ -169,8 +176,6 @@ func (l *ListenerConn) listenerConnLoop() (err error) { return fmt.Errorf("unexpected message %q from server in listenerConnLoop", t) } } - - panic("not reached") } // This is the main routine for the goroutine receiving on the database @@ -239,7 +244,7 @@ func (l *ListenerConn) Ping() error { // The caller must be holding senderLock (see acquireSenderLock and // releaseSenderLock). func (l *ListenerConn) sendSimpleQuery(q string) (err error) { - defer errRecover(&err) + defer errRecoverNoErrBadConn(&err) // must set connection state before sending the query if !l.setState(connStateExpectResponse) { @@ -248,8 +253,10 @@ func (l *ListenerConn) sendSimpleQuery(q string) (err error) { // Can't use l.cn.writeBuf here because it uses the scratch buffer which // might get overwritten by listenerConnLoop. - data := writeBuf([]byte("Q\x00\x00\x00\x00")) - b := &data + b := &writeBuf{ + buf: []byte("Q\x00\x00\x00\x00"), + pos: 1, + } b.string(q) l.cn.send(b) @@ -278,13 +285,13 @@ func (l *ListenerConn) ExecSimpleQuery(q string) (executed bool, err error) { // We can't know what state the protocol is in, so we need to abandon // this connection. l.connectionLock.Lock() - defer l.connectionLock.Unlock() // Set the error pointer if it hasn't been set already; see // listenerConnMain. if l.err == nil { l.err = err } - l.cn.Close() + l.connectionLock.Unlock() + l.cn.c.Close() return false, err } @@ -293,8 +300,11 @@ func (l *ListenerConn) ExecSimpleQuery(q string) (executed bool, err error) { m, ok := <-l.replyChan if !ok { // We lost the connection to server, don't bother waiting for a - // a response. - return false, io.EOF + // a response. err should have been set already. + l.connectionLock.Lock() + err := l.err + l.connectionLock.Unlock() + return false, err } switch m.typ { case 'Z': @@ -317,18 +327,19 @@ func (l *ListenerConn) ExecSimpleQuery(q string) (executed bool, err error) { return false, fmt.Errorf("unknown response for simple query: %q", m.typ) } } - - panic("not reached") } func (l *ListenerConn) Close() error { l.connectionLock.Lock() - defer l.connectionLock.Unlock() if l.err != nil { + l.connectionLock.Unlock() return errListenerConnClosed } l.err = errListenerConnClosed - return l.cn.Close() + l.connectionLock.Unlock() + // We can't send anything on the connection without holding senderLock. + // Simply close the net.Conn to wake up everyone operating on it. + return l.cn.c.Close() } // Err() returns the reason the connection was closed. It is not safe to call @@ -427,6 +438,13 @@ func NewListener(name string, return l } +// Returns the notification channel for this listener. This is the same +// channel as Notify, and will not be recreated during the life time of the +// Listener. +func (l *Listener) NotificationChannel() <-chan *Notification { + return l.Notify +} + // Listen starts listening for notifications on a channel. Calls to this // function will block until an acknowledgement has been received from the // server. Note that Listener automatically re-establishes the connection @@ -630,8 +648,6 @@ func (l *Listener) resync(cn *ListenerConn, notificationChan <-chan *Notificatio return err } } - - panic("not reached") } // caller should NOT be holding l.lock diff --git a/vendor/github.com/lib/pq/notify_test.go b/vendor/github.com/lib/pq/notify_test.go index 12c70bb4..fe8941a4 100644 --- a/vendor/github.com/lib/pq/notify_test.go +++ b/vendor/github.com/lib/pq/notify_test.go @@ -5,6 +5,9 @@ import ( "fmt" "io" "os" + "runtime" + "sync" + "sync/atomic" "testing" "time" ) @@ -24,7 +27,6 @@ func expectNotification(t *testing.T, ch <-chan *Notification, relname string, e case <-time.After(1500 * time.Millisecond): return fmt.Errorf("timeout") } - panic("not reached") } func expectNoNotification(t *testing.T, ch <-chan *Notification) error { @@ -34,7 +36,6 @@ func expectNoNotification(t *testing.T, ch <-chan *Notification) error { case <-time.After(100 * time.Millisecond): return nil } - panic("not reached") } func expectEvent(t *testing.T, eventch <-chan ListenerEventType, et ListenerEventType) error { @@ -45,9 +46,8 @@ func expectEvent(t *testing.T, eventch <-chan ListenerEventType, et ListenerEven } return nil case <-time.After(1500 * time.Millisecond): - return fmt.Errorf("timeout") + panic("expectEvent timeout") } - panic("not reached") } func expectNoEvent(t *testing.T, eventch <-chan ListenerEventType) error { @@ -57,7 +57,6 @@ func expectNoEvent(t *testing.T, eventch <-chan ListenerEventType) error { case <-time.After(100 * time.Millisecond): return nil } - panic("not reached") } func newTestListenerConn(t *testing.T) (*ListenerConn, <-chan *Notification) { @@ -214,13 +213,82 @@ func TestConnPing(t *testing.T) { } } +// 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() + + var done int32 = 0 + go func() { + time.Sleep(10 * time.Second) + if atomic.LoadInt32(&done) != 1 { + panic("timed out") + } + }() + wg.Wait() + atomic.StoreInt32(&done, 1) +} + +// 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) + } + var done int32 = 0 + go func() { + time.Sleep(10 * time.Second) + if atomic.LoadInt32(&done) != 1 { + panic("timed out") + } + }() + wg.Wait() + atomic.StoreInt32(&done, 1) +} + func TestNotifyExtra(t *testing.T) { db := openTestConn(t) defer db.Close() - //if getServerVersion(t, db) < 90000 { - return - //} + 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() diff --git a/vendor/github.com/lib/pq/oid/gen.go b/vendor/github.com/lib/pq/oid/gen.go index f16a51c0..cd4aea80 100644 --- a/vendor/github.com/lib/pq/oid/gen.go +++ b/vendor/github.com/lib/pq/oid/gen.go @@ -5,12 +5,12 @@ package main import ( + "database/sql" "fmt" "log" "os" "os/exec" - "database/sql" _ "github.com/lib/pq" ) diff --git a/vendor/github.com/lib/pq/ssl_test.go b/vendor/github.com/lib/pq/ssl_test.go new file mode 100644 index 00000000..932b336f --- /dev/null +++ b/vendor/github.com/lib/pq/ssl_test.go @@ -0,0 +1,226 @@ +package pq + +// This file contains SSL tests + +import ( + _ "crypto/sha256" + "crypto/x509" + "database/sql" + "fmt" + "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) { + db, err := openSSLConn(t, conninfo) + if err == nil { + db.Close() + t.Fatalf("expected error with conninfo=%q", conninfo) + } +} + +// 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=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 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, but that's OK + _, err = openSSLConn(t, rootCert+"host=127.0.0.1 sslmode=verify-ca user=pqgossltest") + if err != nil { + t.Fatal(err) + } + // Everything OK + _, err = openSSLConn(t, rootCert+"host=postgres sslmode=verify-ca user=pqgossltest") + if err != nil { + t.Fatal(err) + } +} + +func getCertConninfo(t *testing.T, source string) string { + var sslkey string + var sslcert string + + certpath := os.Getenv("PQSSLCERTTEST_PATH") + + switch source { + case "missingkey": + sslkey = "/tmp/filedoesnotexist" + sslcert = filepath.Join(certpath, "postgresql.crt") + case "missingcert": + sslkey = filepath.Join(certpath, "postgresql.key") + sslcert = "/tmp/filedoesnotexist" + case "certtwice": + sslkey = filepath.Join(certpath, "postgresql.crt") + sslcert = filepath.Join(certpath, "postgresql.crt") + case "valid": + sslkey = filepath.Join(certpath, "postgresql.key") + sslcert = filepath.Join(certpath, "postgresql.crt") + default: + t.Fatalf("invalid source %q", source) + } + return fmt.Sprintf("sslmode=require user=pqgosslcert sslkey=%s sslcert=%s", sslkey, sslcert) +} + +// 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") + + // Should also fail without a valid certificate + db, err := openSSLConn(t, "sslmode=require user=pqgosslcert") + if err == nil { + db.Close() + t.Fatal("expected error") + } + pge, ok := err.(*Error) + if !ok { + t.Fatal("expected pq.Error") + } + if pge.Code.Name() != "invalid_authorization_specification" { + t.Fatalf("unexpected error code %q", pge.Code.Name()) + } + + // Should work + db, err = openSSLConn(t, getCertConninfo(t, "valid")) + if err != nil { + t.Fatal(err) + } + rows, err := db.Query("SELECT 1") + if err != nil { + t.Fatal(err) + } + rows.Close() +} + +// Test errors with ssl certificates +func TestSSLClientCertificatesMissingFiles(t *testing.T) { + maybeSkipSSLTests(t) + // Environment sanity check: should fail without SSL + checkSSLSetup(t, "sslmode=disable user=pqgossltest") + + // Key missing, should fail + _, err := openSSLConn(t, getCertConninfo(t, "missingkey")) + if err == nil { + t.Fatal("expected error") + } + // should be a PathError + _, ok := err.(*os.PathError) + if !ok { + t.Fatalf("expected PathError, got %#+v", err) + } + + // Cert missing, should fail + _, err = openSSLConn(t, getCertConninfo(t, "missingcert")) + if err == nil { + t.Fatal("expected error") + } + // should be a PathError + _, ok = err.(*os.PathError) + if !ok { + t.Fatalf("expected PathError, got %#+v", err) + } + + // Key has wrong permissions, should fail + _, err = openSSLConn(t, getCertConninfo(t, "certtwice")) + if err == nil { + t.Fatal("expected error") + } + if err != ErrSSLKeyHasWorldPermissions { + t.Fatalf("expected ErrSSLKeyHasWorldPermissions, got %#+v", err) + } +} diff --git a/vendor/github.com/lib/pq/url.go b/vendor/github.com/lib/pq/url.go index b83e806b..9bac95c4 100644 --- a/vendor/github.com/lib/pq/url.go +++ b/vendor/github.com/lib/pq/url.go @@ -34,7 +34,7 @@ func ParseURL(url string) (string, error) { return "", err } - if u.Scheme != "postgres" { + if u.Scheme != "postgres" && u.Scheme != "postgresql" { return "", fmt.Errorf("invalid connection protocol: %s", u.Scheme) } diff --git a/vendor/github.com/lib/pq/user_posix.go b/vendor/github.com/lib/pq/user_posix.go index aa5a3da3..e937d7d0 100644 --- a/vendor/github.com/lib/pq/user_posix.go +++ b/vendor/github.com/lib/pq/user_posix.go @@ -1,15 +1,24 @@ // Package pq is a pure Go Postgres driver for the database/sql package. -// +build darwin freebsd linux nacl netbsd openbsd solaris +// +build darwin dragonfly freebsd linux nacl netbsd openbsd solaris package pq -import "os/user" +import ( + "os" + "os/user" +) func userCurrent() (string, error) { u, err := user.Current() - if err != nil { - return "", err + if err == nil { + return u.Username, nil } - return u.Username, nil + + name := os.Getenv("USER") + if name != "" { + return name, nil + } + + return "", ErrCouldNotDetectUsername } diff --git a/vendor/github.com/lib/pq/user_windows.go b/vendor/github.com/lib/pq/user_windows.go index a7593ffb..2b691267 100644 --- a/vendor/github.com/lib/pq/user_windows.go +++ b/vendor/github.com/lib/pq/user_windows.go @@ -19,7 +19,7 @@ func userCurrent() (string, error) { pwname_size := uint32(len(pw_name)) - 1 err := syscall.GetUserNameEx(syscall.NameSamCompatible, &pw_name[0], &pwname_size) if err != nil { - return "", err + return "", ErrCouldNotDetectUsername } s := syscall.UTF16ToString(pw_name) u := filepath.Base(s)