From 4a7d9870ef6a5a247ef532192988b01bf13cc110 Mon Sep 17 00:00:00 2001 From: Andrew Bosonchenko Date: Sat, 13 Jun 2020 19:05:47 +0300 Subject: [PATCH 01/65] removed minor from version fixed server version detection for builds like "11.2 (Debian 11.2-1.pgdg90+1)" --- conn.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/conn.go b/conn.go index b3ab14d3c..cfd142d45 100644 --- a/conn.go +++ b/conn.go @@ -1689,10 +1689,9 @@ func (cn *conn) processParameterStatus(r *readBuf) { case "server_version": var major1 int var major2 int - var minor int - _, err = fmt.Sscanf(r.string(), "%d.%d.%d", &major1, &major2, &minor) + _, err = fmt.Sscanf(r.string(), "%d.%d", &major1, &major2) if err == nil { - cn.parameterStatus.serverVersion = major1*10000 + major2*100 + minor + cn.parameterStatus.serverVersion = major1*10000 + major2*100 } case "TimeZone": From 73ba00e942e389de0338bade1da62d94aabf782d Mon Sep 17 00:00:00 2001 From: Dio Gado Date: Sat, 20 Jun 2020 17:08:33 -0400 Subject: [PATCH 02/65] Fix typo in comment --- connector.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/connector.go b/connector.go index 6a0ee7fc1..d7d472615 100644 --- a/connector.go +++ b/connector.go @@ -27,7 +27,7 @@ func (c *Connector) Connect(ctx context.Context) (driver.Conn, error) { return c.open(ctx) } -// Driver returnst the underlying driver of this Connector. +// Driver returns the underlying driver of this Connector. func (c *Connector) Driver() driver.Driver { return &Driver{} } From 538adf7a5f7af1f74aaf740a52fe4e4117507a2f Mon Sep 17 00:00:00 2001 From: Philip Glazman <8378656+philipglazman@users.noreply.github.com> Date: Tue, 7 Jul 2020 17:39:44 -0700 Subject: [PATCH 03/65] return rows copied for COPY command from the CommandComplete postgres message --- copy.go | 25 ++++++++++++++++++++++++- copy_test.go | 11 ++++++++++- 2 files changed, 34 insertions(+), 2 deletions(-) diff --git a/copy.go b/copy.go index d3bc1edd8..eeade12b2 100644 --- a/copy.go +++ b/copy.go @@ -49,6 +49,7 @@ type copyin struct { buffer []byte rowData chan []byte done chan bool + result driver.Result closed bool @@ -151,6 +152,8 @@ func (ci *copyin) resploop() { switch t { case 'C': // complete + res, _ := ci.cn.parseComplete(r.string()) + ci.setResult(res) case 'N': if n := ci.cn.noticeHandler; n != nil { n(parseError(&r)) @@ -201,6 +204,22 @@ func (ci *copyin) setError(err error) { ci.Unlock() } +func (ci *copyin) setResult(result driver.Result) { + ci.Lock() + ci.result = result + ci.Unlock() +} + +func (ci *copyin) getResult() driver.Result { + ci.Lock() + result := ci.result + if result == nil { + return driver.RowsAffected(0) + } + ci.Unlock() + return result +} + func (ci *copyin) NumInput() int { return -1 } @@ -231,7 +250,11 @@ func (ci *copyin) Exec(v []driver.Value) (r driver.Result, err error) { } if len(v) == 0 { - return driver.RowsAffected(0), ci.Close() + if err := ci.Close(); err != nil { + return driver.RowsAffected(0), err + } + + return ci.getResult(), nil } numValues := len(v) diff --git a/copy_test.go b/copy_test.go index a888a8948..2b6d6bca8 100644 --- a/copy_test.go +++ b/copy_test.go @@ -73,11 +73,20 @@ func TestCopyInMultipleValues(t *testing.T) { } } - _, err = stmt.Exec() + result, err := stmt.Exec() if err != nil { t.Fatal(err) } + rowsAffected, err := result.RowsAffected() + if err != nil { + t.Fatal(err) + } + + if rowsAffected != 500 { + t.Fatalf("expected 500 rows affected, not %d", rowsAffected) + } + err = stmt.Close() if err != nil { t.Fatal(err) From ad163b14c4ab99a1a4dab1e1ba66d5a23c2b2b3e Mon Sep 17 00:00:00 2001 From: Philip Glazman <8378656+philipglazman@users.noreply.github.com> Date: Tue, 7 Jul 2020 18:03:31 -0700 Subject: [PATCH 04/65] Pass travis --- copy.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/copy.go b/copy.go index eeade12b2..38d5bb693 100644 --- a/copy.go +++ b/copy.go @@ -49,7 +49,7 @@ type copyin struct { buffer []byte rowData chan []byte done chan bool - result driver.Result + driver.Result closed bool @@ -206,13 +206,13 @@ func (ci *copyin) setError(err error) { func (ci *copyin) setResult(result driver.Result) { ci.Lock() - ci.result = result + ci.Result = result ci.Unlock() } func (ci *copyin) getResult() driver.Result { ci.Lock() - result := ci.result + result := ci.Result if result == nil { return driver.RowsAffected(0) } From 36cd0f1fcaedbf947839b0a195d2f10901b48468 Mon Sep 17 00:00:00 2001 From: Matt Jibson Date: Mon, 20 Jul 2020 10:02:49 -0600 Subject: [PATCH 05/65] fix windows kerberos build --- auth/kerberos/krb_windows.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/auth/kerberos/krb_windows.go b/auth/kerberos/krb_windows.go index 973be8fc6..474517aea 100644 --- a/auth/kerberos/krb_windows.go +++ b/auth/kerberos/krb_windows.go @@ -8,7 +8,7 @@ import ( ) // GSS implements the pq.GSS interface. -type Gss struct { +type GSS struct { creds *sspi.Credentials ctx *negotiate.ClientContext } From bb1e32b8da7f495372e5118096d9f64cc40bd807 Mon Sep 17 00:00:00 2001 From: Matt Jibson Date: Mon, 27 Jul 2020 13:55:44 -0600 Subject: [PATCH 06/65] use krbsrvname for GSS auth; improve GSS docs --- README.md | 5 +---- conn.go | 8 ++++---- doc.go | 9 +++++++-- 3 files changed, 12 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index ecd01939b..c972a86a5 100644 --- a/README.md +++ b/README.md @@ -19,10 +19,7 @@ * Unix socket support * Notifications: `LISTEN`/`NOTIFY` * pgpass support - -## Optional Features - -* GSS (Kerberos) auth (to use, see GoDoc) +* GSS (Kerberos) auth ## Tests diff --git a/conn.go b/conn.go index b3ab14d3c..f313c1498 100644 --- a/conn.go +++ b/conn.go @@ -1074,9 +1074,9 @@ func isDriverSetting(key string) bool { return true case "binary_parameters": return true - case "service": + case "krbsrvname": return true - case "spn": + case "krbspn": return true default: return false @@ -1168,13 +1168,13 @@ func (cn *conn) auth(r *readBuf, o values) { var token []byte - if spn, ok := o["spn"]; ok { + if spn, ok := o["krbspn"]; ok { // Use the supplied SPN if provided.. token, err = cli.GetInitTokenFromSpn(spn) } else { // Allow the kerberos service name to be overridden service := "postgres" - if val, ok := o["service"]; ok { + if val, ok := o["krbsrvname"]; ok { service = val } diff --git a/doc.go b/doc.go index 78c670b1d..b57184801 100644 --- a/doc.go +++ b/doc.go @@ -57,8 +57,6 @@ supported: * 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. - * spn - Configures GSS (Kerberos) SPN. - * service - GSS (Kerberos) service name to use when constructing the SPN (default is `postgres`). Valid values for sslmode are: @@ -259,5 +257,12 @@ package: This package is in a separate module so that users who don't need Kerberos don't have to download unnecessary dependencies. +When imported, additional connection string parameters are supported: + + * krbsrvname - GSS (Kerberos) service name when constructing the + SPN (default is `postgres`). This will be combined with the host + to form the full SPN: `krbsrvname/host`. + * krbspn - GSS (Kerberos) SPN. This takes priority over + `krbsrvname` if present. */ package pq From 5e47d5c99fafe3f9e23a62920e4b09574ee6e169 Mon Sep 17 00:00:00 2001 From: Matt Jibson Date: Tue, 11 Aug 2020 16:05:18 -0600 Subject: [PATCH 07/65] test go 1.15 --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 3498c53dc..108f99ec0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,6 +3,7 @@ language: go go: - 1.13.x - 1.14.x + - 1.15.x - master sudo: true From e8c1c95c16249bd63907445cbbc05ee74d57e5c9 Mon Sep 17 00:00:00 2001 From: Matt Jibson Date: Tue, 11 Aug 2020 17:30:55 -0600 Subject: [PATCH 08/65] retry in TestCopyRespLoopConnectionError to prevent flakes --- copy_test.go | 37 +++++++++++++++++++++++++++++++------ 1 file changed, 31 insertions(+), 6 deletions(-) diff --git a/copy_test.go b/copy_test.go index 2b6d6bca8..852f1be5a 100644 --- a/copy_test.go +++ b/copy_test.go @@ -4,9 +4,11 @@ import ( "bytes" "database/sql" "database/sql/driver" + "fmt" "net" "strings" "testing" + "time" ) func TestCopyInStmt(t *testing.T) { @@ -130,7 +132,7 @@ func TestCopyInRaiseStmtTrigger(t *testing.T) { _, err = txn.Exec(` CREATE OR REPLACE FUNCTION pg_temp.temptest() - RETURNS trigger AS + RETURNS trigger AS $BODY$ begin raise notice 'Hello world'; return new; @@ -143,7 +145,7 @@ func TestCopyInRaiseStmtTrigger(t *testing.T) { _, err = txn.Exec(` CREATE TRIGGER temptest_trigger BEFORE INSERT - ON temp + ON temp FOR EACH ROW EXECUTE PROCEDURE pg_temp.temptest()`) if err != nil { @@ -406,10 +408,13 @@ func TestCopyRespLoopConnectionError(t *testing.T) { t.Fatal(err) } } - _, err = stmt.Exec() - if err == nil { - t.Fatalf("expected error") - } + retry(t, time.Second*5, func() error { + _, err = stmt.Exec() + if err == nil { + return fmt.Errorf("expected error") + } + return nil + }) switch pge := err.(type) { case *Error: if pge.Code.Name() != "admin_shutdown" { @@ -420,6 +425,8 @@ func TestCopyRespLoopConnectionError(t *testing.T) { default: if err == driver.ErrBadConn { // likely an EPIPE + } else if err == errCopyInClosed { + // ignore } else { t.Fatalf("unexpected error, got %+#v", err) } @@ -428,6 +435,24 @@ func TestCopyRespLoopConnectionError(t *testing.T) { _ = stmt.Close() } +// retry executes f in a backoff loop until it doesn't return an error. If this +// doesn't happen within duration, t.Fatal is called with the latest error. +func retry(t *testing.T, duration time.Duration, f func() error) { + start := time.Now() + next := time.Millisecond * 100 + for { + err := f() + if err == nil { + return + } + if time.Since(start) > duration { + t.Fatal(err) + } + time.Sleep(next) + next *= 2 + } +} + func BenchmarkCopyIn(b *testing.B) { db := openTestConn(b) defer db.Close() From 6d207a9c1857c14aa92e2d0e721a9f9772983ff6 Mon Sep 17 00:00:00 2001 From: Matt Jibson Date: Tue, 11 Aug 2020 17:39:33 -0600 Subject: [PATCH 09/65] fix ssl test for go1.15 --- .travis.yml | 1 + ssl_test.go | 5 ++++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 108f99ec0..68e89e88d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -14,6 +14,7 @@ env: - PQGOSSLTESTS=1 - PQSSLCERTTEST_PATH=$PWD/certs - PGHOST=127.0.0.1 + - GODEBUG=x509ignoreCN=0 matrix: - PGVERSION=10 - PGVERSION=9.6 diff --git a/ssl_test.go b/ssl_test.go index 3eafbfd20..e00522e76 100644 --- a/ssl_test.go +++ b/ssl_test.go @@ -81,7 +81,10 @@ func TestSSLVerifyFull(t *testing.T) { } _, ok := err.(x509.UnknownAuthorityError) if !ok { - t.Fatalf("expected x509.UnknownAuthorityError, got %#+v", err) + _, ok := err.(x509.HostnameError) + if !ok { + t.Fatalf("expected x509.UnknownAuthorityError or x509.HostnameError, got %#+v", err) + } } rootCertPath := filepath.Join(os.Getenv("PQSSLCERTTEST_PATH"), "root.crt") From d97e962beda231cb266a1d97afd01c007d029470 Mon Sep 17 00:00:00 2001 From: David Hill Date: Sat, 29 Aug 2020 00:29:21 -0400 Subject: [PATCH 10/65] unlock mutex earlier --- copy.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/copy.go b/copy.go index 38d5bb693..9d4f850c3 100644 --- a/copy.go +++ b/copy.go @@ -213,10 +213,10 @@ func (ci *copyin) setResult(result driver.Result) { func (ci *copyin) getResult() driver.Result { ci.Lock() result := ci.Result + ci.Unlock() if result == nil { return driver.RowsAffected(0) } - ci.Unlock() return result } From 4c8c217da681ae63bbd7713e3dc975113770fcac Mon Sep 17 00:00:00 2001 From: Roel van der Goot Date: Mon, 12 Oct 2020 18:02:43 -0600 Subject: [PATCH 11/65] Handle empty result sets --- conn.go | 6 +++++- conn_test.go | 30 ++++++++++++++++++++++++++++++ 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/conn.go b/conn.go index f313c1498..02e78f8a0 100644 --- a/conn.go +++ b/conn.go @@ -663,8 +663,12 @@ func (cn *conn) simpleQuery(q string) (res *rows, err error) { // Set the result and tag to the last command complete if there wasn't a // query already run. Although queries usually return from here and cede // control to Next, a query with zero results does not. - if t == 'C' && res.colNames == nil { + if t == 'C' { res.result, res.tag = cn.parseComplete(r.string()) + if res.colNames != nil { + res.tag = "" + return + } } res.done = true case 'Z': diff --git a/conn_test.go b/conn_test.go index 0d25c9554..c900f66e3 100644 --- a/conn_test.go +++ b/conn_test.go @@ -1748,6 +1748,36 @@ func TestMultipleResult(t *testing.T) { } } +func TestMultipleEmptyResult(t *testing.T) { + db := openTestConn(t) + defer db.Close() + + rows, err := db.Query("select 1 where false; select 2") + if err != nil { + t.Fatal(err) + } + defer rows.Close() + + for rows.Next() { + t.Fatal("unexpected row") + } + if !rows.NextResultSet() { + t.Fatal("expected more result sets", rows.Err()) + } + for rows.Next() { + var i int + if err := rows.Scan(&i); err != nil { + t.Fatal(err) + } + if i != 2 { + t.Fatalf("expected 2, got %d", i) + } + } + if rows.NextResultSet() { + t.Fatal("unexpected result set") + } +} + func TestCopyInStmtAffectedRows(t *testing.T) { db := openTestConn(t) defer db.Close() From 999f18fba04bbf793b27b9ec11396264086718d0 Mon Sep 17 00:00:00 2001 From: Roel van der Goot Date: Wed, 14 Oct 2020 07:31:44 -0600 Subject: [PATCH 12/65] Erroneous test? --- conn.go | 1 - conn_test.go | 4 ---- 2 files changed, 5 deletions(-) diff --git a/conn.go b/conn.go index 02e78f8a0..d7a93d6a5 100644 --- a/conn.go +++ b/conn.go @@ -666,7 +666,6 @@ func (cn *conn) simpleQuery(q string) (res *rows, err error) { if t == 'C' { res.result, res.tag = cn.parseComplete(r.string()) if res.colNames != nil { - res.tag = "" return } } diff --git a/conn_test.go b/conn_test.go index c900f66e3..3b8847055 100644 --- a/conn_test.go +++ b/conn_test.go @@ -1640,10 +1640,6 @@ func TestRowsResultTag(t *testing.T) { { query: "CREATE TEMP TABLE t (a int); DROP TABLE t; SELECT 1", }, - // Verify that an no-results query doesn't set the tag. - { - query: "CREATE TEMP TABLE t (a int); SELECT 1 WHERE FALSE; DROP TABLE t;", - }, } // If this is the only test run, this will correct the connection string. From 730d555dd5aa0f0f5bd365036f148b5dfce03e79 Mon Sep 17 00:00:00 2001 From: Igor Wiedler Date: Thu, 22 Oct 2020 14:42:20 +0200 Subject: [PATCH 13/65] Correctly implement database/sql/driver.Driver for better wrappability --- conn.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/conn.go b/conn.go index f313c1498..1b4fc02e5 100644 --- a/conn.go +++ b/conn.go @@ -38,13 +38,18 @@ var ( errNoLastInsertID = errors.New("no LastInsertId available after the empty statement") ) +// Compile time validation that our types implement the expected interfaces +var ( + _ driver.Driver = Driver{} +) + // Driver is the Postgres database driver. type Driver struct{} // Open opens a new connection to the database. name is a connection string. // Most users should only use it through database/sql package from the standard // library. -func (d *Driver) Open(name string) (driver.Conn, error) { +func (d Driver) Open(name string) (driver.Conn, error) { return Open(name) } From 25eb21e556c442fe806c846b3936e6e7fa46f4ef Mon Sep 17 00:00:00 2001 From: Danny Hermes Date: Thu, 19 Nov 2020 12:38:16 -0600 Subject: [PATCH 14/65] Mark `net.Conn` failed writes as recoverable when 0 bytes were written. --- conn.go | 13 ++++++++++++- error.go | 3 +++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/conn.go b/conn.go index f313c1498..0d96600ed 100644 --- a/conn.go +++ b/conn.go @@ -891,9 +891,20 @@ func (cn *conn) Exec(query string, args []driver.Value) (res driver.Result, err return r, err } +type safeRetryError struct { + Err error +} + +func (se *safeRetryError) Error() string { + return se.Err.Error() +} + func (cn *conn) send(m *writeBuf) { - _, err := cn.c.Write(m.wrap()) + n, err := cn.c.Write(m.wrap()) if err != nil { + if n == 0 { + err = &safeRetryError{Err: err} + } panic(err) } } diff --git a/error.go b/error.go index 3d66ba7c5..a227e0831 100644 --- a/error.go +++ b/error.go @@ -495,6 +495,9 @@ func (cn *conn) errRecover(err *error) { case *net.OpError: cn.bad = true *err = v + case *safeRetryError: + cn.bad = true + *err = driver.ErrBadConn case error: if v == io.EOF || v.(error).Error() == "remote error: handshake failure" { *err = driver.ErrBadConn From f0b8e153ff52e7d43c4934c222c96f5addfafd4e Mon Sep 17 00:00:00 2001 From: Andrew Huang Date: Thu, 1 Oct 2020 13:00:06 -0600 Subject: [PATCH 15/65] Fix query cancellation collateralizing future queries using the same connection --- conn.go | 89 +++++++++++++++++++++++++++++++--------------------- conn_go18.go | 22 +++++++++++-- conn_test.go | 13 +++++--- copy.go | 4 +-- error.go | 8 ++--- go18_test.go | 28 ++++++++++++----- 6 files changed, 108 insertions(+), 56 deletions(-) diff --git a/conn.go b/conn.go index f313c1498..a2b7d48cd 100644 --- a/conn.go +++ b/conn.go @@ -18,6 +18,7 @@ import ( "path/filepath" "strconv" "strings" + "sync/atomic" "time" "unicode" @@ -136,7 +137,7 @@ type conn struct { // If true, this connection is bad and all public-facing functions should // return ErrBadConn. - bad bool + bad *atomic.Value // If set, this connection should never use the binary format when // receiving query results from prepared statements. Only provided for @@ -294,9 +295,12 @@ func (c *Connector) open(ctx context.Context) (cn *conn, err error) { o := c.opts + bad := &atomic.Value{} + bad.Store(false) cn = &conn{ opts: o, dialer: c.dialer, + bad: bad, } err = cn.handleDriverSettings(o) if err != nil { @@ -501,9 +505,22 @@ func (cn *conn) isInTransaction() bool { cn.txnStatus == txnStatusInFailedTransaction } +func (cn *conn) setBad() { + if cn.bad != nil { + cn.bad.Store(true) + } +} + +func (cn *conn) getBad() bool { + if cn.bad != nil { + return cn.bad.Load().(bool) + } + return false +} + func (cn *conn) checkIsInTransaction(intxn bool) { if cn.isInTransaction() != intxn { - cn.bad = true + cn.setBad() errorf("unexpected transaction status %v", cn.txnStatus) } } @@ -513,7 +530,7 @@ func (cn *conn) Begin() (_ driver.Tx, err error) { } func (cn *conn) begin(mode string) (_ driver.Tx, err error) { - if cn.bad { + if cn.getBad() { return nil, driver.ErrBadConn } defer cn.errRecover(&err) @@ -524,11 +541,11 @@ func (cn *conn) begin(mode string) (_ driver.Tx, err error) { return nil, err } if commandTag != "BEGIN" { - cn.bad = true + cn.setBad() return nil, fmt.Errorf("unexpected command tag %s", commandTag) } if cn.txnStatus != txnStatusIdleInTransaction { - cn.bad = true + cn.setBad() return nil, fmt.Errorf("unexpected transaction status %v", cn.txnStatus) } return cn, nil @@ -542,7 +559,7 @@ func (cn *conn) closeTxn() { func (cn *conn) Commit() (err error) { defer cn.closeTxn() - if cn.bad { + if cn.getBad() { return driver.ErrBadConn } defer cn.errRecover(&err) @@ -564,12 +581,12 @@ func (cn *conn) Commit() (err error) { _, commandTag, err := cn.simpleExec("COMMIT") if err != nil { if cn.isInTransaction() { - cn.bad = true + cn.setBad() } return err } if commandTag != "COMMIT" { - cn.bad = true + cn.setBad() return fmt.Errorf("unexpected command tag %s", commandTag) } cn.checkIsInTransaction(false) @@ -578,7 +595,7 @@ func (cn *conn) Commit() (err error) { func (cn *conn) Rollback() (err error) { defer cn.closeTxn() - if cn.bad { + if cn.getBad() { return driver.ErrBadConn } defer cn.errRecover(&err) @@ -590,7 +607,7 @@ func (cn *conn) rollback() (err error) { _, commandTag, err := cn.simpleExec("ROLLBACK") if err != nil { if cn.isInTransaction() { - cn.bad = true + cn.setBad() } return err } @@ -630,7 +647,7 @@ func (cn *conn) simpleExec(q string) (res driver.Result, commandTag string, err case 'T', 'D': // ignore any results default: - cn.bad = true + cn.setBad() errorf("unknown response for simple query: %q", t) } } @@ -652,7 +669,7 @@ func (cn *conn) simpleQuery(q string) (res *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 + cn.setBad() errorf("unexpected message %q in simple query execution", t) } if res == nil { @@ -676,7 +693,7 @@ func (cn *conn) simpleQuery(q string) (res *rows, err error) { err = parseError(r) case 'D': if res == nil { - cn.bad = true + cn.setBad() errorf("unexpected DataRow in simple query execution") } // the query didn't fail; kick off to Next @@ -691,7 +708,7 @@ func (cn *conn) simpleQuery(q string) (res *rows, err error) { // 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 + cn.setBad() errorf("unknown response for simple query: %q", t) } } @@ -784,7 +801,7 @@ func (cn *conn) prepareTo(q, stmtName string) *stmt { } func (cn *conn) Prepare(q string) (_ driver.Stmt, err error) { - if cn.bad { + if cn.getBad() { return nil, driver.ErrBadConn } defer cn.errRecover(&err) @@ -823,7 +840,7 @@ func (cn *conn) Query(query string, args []driver.Value) (driver.Rows, error) { } func (cn *conn) query(query string, args []driver.Value) (_ *rows, err error) { - if cn.bad { + if cn.getBad() { return nil, driver.ErrBadConn } if cn.inCopy { @@ -857,7 +874,7 @@ func (cn *conn) query(query string, args []driver.Value) (_ *rows, err error) { // Implement the optional "Execer" interface for one-shot queries func (cn *conn) Exec(query string, args []driver.Value) (res driver.Result, err error) { - if cn.bad { + if cn.getBad() { return nil, driver.ErrBadConn } defer cn.errRecover(&err) @@ -918,7 +935,7 @@ 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 + cn.setBad() errorf("unexpected saveMessageType %d", cn.saveMessageType) } cn.saveMessageType = typ @@ -1288,7 +1305,7 @@ func (st *stmt) Close() (err error) { if st.closed { return nil } - if st.cn.bad { + if st.cn.getBad() { return driver.ErrBadConn } defer st.cn.errRecover(&err) @@ -1302,14 +1319,14 @@ func (st *stmt) Close() (err error) { t, _ := st.cn.recv1() if t != '3' { - st.cn.bad = true + st.cn.setBad() errorf("unexpected close response: %q", t) } st.closed = true t, r := st.cn.recv1() if t != 'Z' { - st.cn.bad = true + st.cn.setBad() errorf("expected ready for query, but got: %q", t) } st.cn.processReadyForQuery(r) @@ -1318,7 +1335,7 @@ func (st *stmt) Close() (err error) { } func (st *stmt) Query(v []driver.Value) (r driver.Rows, err error) { - if st.cn.bad { + if st.cn.getBad() { return nil, driver.ErrBadConn } defer st.cn.errRecover(&err) @@ -1331,7 +1348,7 @@ func (st *stmt) Query(v []driver.Value) (r driver.Rows, err error) { } func (st *stmt) Exec(v []driver.Value) (res driver.Result, err error) { - if st.cn.bad { + if st.cn.getBad() { return nil, driver.ErrBadConn } defer st.cn.errRecover(&err) @@ -1418,7 +1435,7 @@ func (cn *conn) parseComplete(commandTag string) (driver.Result, string) { if affectedRows == nil && strings.HasPrefix(commandTag, "INSERT ") { parts := strings.Split(commandTag, " ") if len(parts) != 3 { - cn.bad = true + cn.setBad() errorf("unexpected INSERT command tag %s", commandTag) } affectedRows = &parts[len(parts)-1] @@ -1430,7 +1447,7 @@ func (cn *conn) parseComplete(commandTag string) (driver.Result, string) { } n, err := strconv.ParseInt(*affectedRows, 10, 64) if err != nil { - cn.bad = true + cn.setBad() errorf("could not parse commandTag: %s", err) } return driver.RowsAffected(n), commandTag @@ -1497,7 +1514,7 @@ func (rs *rows) Next(dest []driver.Value) (err error) { } conn := rs.cn - if conn.bad { + if conn.getBad() { return driver.ErrBadConn } defer conn.errRecover(&err) @@ -1522,7 +1539,7 @@ func (rs *rows) Next(dest []driver.Value) (err error) { case 'D': n := rs.rb.int16() if err != nil { - conn.bad = true + conn.setBad() errorf("unexpected DataRow after error %s", err) } if n < len(dest) { @@ -1717,7 +1734,7 @@ func (cn *conn) readReadyForQuery() { cn.processReadyForQuery(r) return default: - cn.bad = true + cn.setBad() errorf("unexpected message %q; expected ReadyForQuery", t) } } @@ -1737,7 +1754,7 @@ func (cn *conn) readParseResponse() { cn.readReadyForQuery() panic(err) default: - cn.bad = true + cn.setBad() errorf("unexpected Parse response %q", t) } } @@ -1762,7 +1779,7 @@ func (cn *conn) readStatementDescribeResponse() (paramTyps []oid.Oid, colNames [ cn.readReadyForQuery() panic(err) default: - cn.bad = true + cn.setBad() errorf("unexpected Describe statement response %q", t) } } @@ -1780,7 +1797,7 @@ func (cn *conn) readPortalDescribeResponse() rowsHeader { cn.readReadyForQuery() panic(err) default: - cn.bad = true + cn.setBad() errorf("unexpected Describe response %q", t) } panic("not reached") @@ -1796,7 +1813,7 @@ func (cn *conn) readBindResponse() { cn.readReadyForQuery() panic(err) default: - cn.bad = true + cn.setBad() errorf("unexpected Bind response %q", t) } } @@ -1823,7 +1840,7 @@ func (cn *conn) postExecuteWorkaround() { cn.saveMessage(t, r) return default: - cn.bad = true + cn.setBad() errorf("unexpected message during extended query execution: %q", t) } } @@ -1836,7 +1853,7 @@ func (cn *conn) readExecuteResponse(protocolState string) (res driver.Result, co switch t { case 'C': if err != nil { - cn.bad = true + cn.setBad() errorf("unexpected CommandComplete after error %s", err) } res, commandTag = cn.parseComplete(r.string()) @@ -1850,7 +1867,7 @@ func (cn *conn) readExecuteResponse(protocolState string) (res driver.Result, co err = parseError(r) case 'T', 'D', 'I': if err != nil { - cn.bad = true + cn.setBad() errorf("unexpected %q after error %s", t, err) } if t == 'I' { @@ -1858,7 +1875,7 @@ func (cn *conn) readExecuteResponse(protocolState string) (res driver.Result, co } // ignore any results default: - cn.bad = true + cn.setBad() errorf("unknown %s response: %q", protocolState, t) } } diff --git a/conn_go18.go b/conn_go18.go index 09e2ea464..8cab67c9d 100644 --- a/conn_go18.go +++ b/conn_go18.go @@ -7,6 +7,7 @@ import ( "fmt" "io" "io/ioutil" + "sync/atomic" "time" ) @@ -89,10 +90,21 @@ func (cn *conn) Ping(ctx context.Context) error { func (cn *conn) watchCancel(ctx context.Context) func() { if done := ctx.Done(); done != nil { - finished := make(chan struct{}) + finished := make(chan struct{}, 1) go func() { select { case <-done: + select { + case finished <- struct{}{}: + default: + // We raced with the finish func, let the next query handle this with the + // context. + return + } + + // Set the connection state to bad so it does not get reused. + cn.setBad() + // At this point the function level context is canceled, // so it must not be used for the additional network // request to cancel the query. @@ -101,13 +113,14 @@ func (cn *conn) watchCancel(ctx context.Context) func() { defer cancel() _ = cn.cancel(ctxCancel) - finished <- struct{}{} case <-finished: } }() return func() { select { case <-finished: + cn.setBad() + cn.Close() case finished <- struct{}{}: } } @@ -123,8 +136,11 @@ func (cn *conn) cancel(ctx context.Context) error { defer c.Close() { + bad := &atomic.Value{} + bad.Store(false) can := conn{ - c: c, + c: c, + bad: bad, } err = can.ssl(cn.opts) if err != nil { diff --git a/conn_test.go b/conn_test.go index 0d25c9554..f50278525 100644 --- a/conn_test.go +++ b/conn_test.go @@ -10,6 +10,7 @@ import ( "os" "reflect" "strings" + "sync/atomic" "testing" "time" ) @@ -695,7 +696,9 @@ func TestErrorDuringStartupClosesConn(t *testing.T) { func TestBadConn(t *testing.T) { var err error - cn := conn{} + bad := &atomic.Value{} + bad.Store(false) + cn := conn{bad: bad} func() { defer cn.errRecover(&err) panic(io.EOF) @@ -703,11 +706,13 @@ func TestBadConn(t *testing.T) { if err != driver.ErrBadConn { t.Fatalf("expected driver.ErrBadConn, got: %#v", err) } - if !cn.bad { + if !cn.getBad() { t.Fatalf("expected cn.bad") } - cn = conn{} + badd := &atomic.Value{} + badd.Store(false) + cn = conn{bad: badd} func() { defer cn.errRecover(&err) e := &Error{Severity: Efatal} @@ -716,7 +721,7 @@ func TestBadConn(t *testing.T) { if err != driver.ErrBadConn { t.Fatalf("expected driver.ErrBadConn, got: %#v", err) } - if !cn.bad { + if !cn.getBad() { t.Fatalf("expected cn.bad") } } diff --git a/copy.go b/copy.go index 9d4f850c3..bb3cbd7b9 100644 --- a/copy.go +++ b/copy.go @@ -176,13 +176,13 @@ func (ci *copyin) resploop() { func (ci *copyin) setBad() { ci.Lock() - ci.cn.bad = true + ci.cn.setBad() ci.Unlock() } func (ci *copyin) isBad() bool { ci.Lock() - b := ci.cn.bad + b := ci.cn.getBad() ci.Unlock() return b } diff --git a/error.go b/error.go index 3d66ba7c5..cb8ef5dc7 100644 --- a/error.go +++ b/error.go @@ -484,7 +484,7 @@ func (cn *conn) errRecover(err *error) { case nil: // Do nothing case runtime.Error: - cn.bad = true + cn.setBad() panic(v) case *Error: if v.Fatal() { @@ -493,7 +493,7 @@ func (cn *conn) errRecover(err *error) { *err = v } case *net.OpError: - cn.bad = true + cn.setBad() *err = v case error: if v == io.EOF || v.(error).Error() == "remote error: handshake failure" { @@ -503,13 +503,13 @@ func (cn *conn) errRecover(err *error) { } default: - cn.bad = true + cn.setBad() 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 { - cn.bad = true + cn.setBad() } } diff --git a/go18_test.go b/go18_test.go index 72cd71fe9..95c08cd79 100644 --- a/go18_test.go +++ b/go18_test.go @@ -3,6 +3,7 @@ package pq import ( "context" "database/sql" + "database/sql/driver" "runtime" "strings" "testing" @@ -142,7 +143,7 @@ func TestContextCancelQuery(t *testing.T) { cancel() if err != nil { t.Fatal(err) - } else if err := rows.Close(); err != nil { + } else if err := rows.Close(); err != nil && err != driver.ErrBadConn { t.Fatal(err) } }() @@ -176,13 +177,26 @@ func TestIssue617(t *testing.T) { } }() } - numGoroutineFinish := runtime.NumGoroutine() - // We use N/2 and not N because the GC and other actors may increase or - // decrease the number of goroutines. - if numGoroutineFinish-numGoroutineStart >= N/2 { - t.Errorf("goroutine leak detected, was %d, now %d", numGoroutineStart, numGoroutineFinish) + // Give time for goroutines to terminate + delayTime := time.Millisecond * 50 + waitTime := time.Second + iterations := int(waitTime / delayTime) + + var numGoroutineFinish int + for i := 0; i < iterations; i++ { + time.Sleep(delayTime) + + numGoroutineFinish = runtime.NumGoroutine() + + // We use N/2 and not N because the GC and other actors may increase or + // decrease the number of goroutines. + if numGoroutineFinish-numGoroutineStart < N/2 { + return + } } + + t.Errorf("goroutine leak detected, was %d, now %d", numGoroutineStart, numGoroutineFinish) } func TestContextCancelBegin(t *testing.T) { @@ -228,7 +242,7 @@ func TestContextCancelBegin(t *testing.T) { t.Fatal(err) } else if err := tx.Rollback(); err != nil && err.Error() != "pq: canceling statement due to user request" && - err != sql.ErrTxDone { + err != sql.ErrTxDone && err != driver.ErrBadConn { t.Fatal(err) } }() From 01786cbdd08acd6334ad9746fbb633d60e7b3ab5 Mon Sep 17 00:00:00 2001 From: Matt Jibson Date: Mon, 23 Nov 2020 13:35:56 -0700 Subject: [PATCH 16/65] Remove go 1.13 tests Cockroach is now in 1.15 so we can bump the version. --- .travis.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 68e89e88d..f378207f2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,6 @@ language: go go: - - 1.13.x - 1.14.x - 1.15.x - master From f373854cec99bd835c8fd49045dbe7b44479b758 Mon Sep 17 00:00:00 2001 From: splicefracture Date: Tue, 24 Nov 2020 11:52:09 -0800 Subject: [PATCH 17/65] Update error.go Missed one. --- error.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/error.go b/error.go index ee1ca6df1..c19c349f1 100644 --- a/error.go +++ b/error.go @@ -496,7 +496,7 @@ func (cn *conn) errRecover(err *error) { cn.setBad() *err = v case *safeRetryError: - cn.bad = true + cn.setBad() *err = driver.ErrBadConn case error: if v == io.EOF || v.(error).Error() == "remote error: handshake failure" { From d726827bf762cdd26468d69f9e3f7b4d2ee4df90 Mon Sep 17 00:00:00 2001 From: Shivam Rathore Date: Thu, 20 Jun 2019 10:07:06 +0530 Subject: [PATCH 18/65] feature: inbuilt support for scanner and valuer in pq.Array for int32/float32/[]byte slices --- .gitignore | 2 + array.go | 139 ++++++++++++++++++++ array_test.go | 341 ++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 482 insertions(+) diff --git a/.gitignore b/.gitignore index 0f1d00e11..3243952a4 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,5 @@ *.test *~ *.swp +.idea +.vscode \ No newline at end of file diff --git a/array.go b/array.go index e4933e227..405da2368 100644 --- a/array.go +++ b/array.go @@ -35,19 +35,31 @@ func Array(a interface{}) interface { return (*BoolArray)(&a) case []float64: return (*Float64Array)(&a) + case []float32: + return (*Float32Array)(&a) case []int64: return (*Int64Array)(&a) + case []int32: + return (*Int32Array)(&a) case []string: return (*StringArray)(&a) + case [][]byte: + return (*ByteaArray)(&a) case *[]bool: return (*BoolArray)(a) case *[]float64: return (*Float64Array)(a) + case *[]float32: + return (*Float32Array)(a) case *[]int64: return (*Int64Array)(a) + case *[]int32: + return (*Int32Array)(a) case *[]string: return (*StringArray)(a) + case *[][]byte: + return (*ByteaArray)(a) } return GenericArray{a} @@ -267,6 +279,70 @@ func (a Float64Array) Value() (driver.Value, error) { return "{}", nil } +// Float32Array represents a one-dimensional array of the PostgreSQL double +// precision type. +type Float32Array []float32 + +// Scan implements the sql.Scanner interface. +func (a *Float32Array) Scan(src interface{}) error { + switch src := src.(type) { + case []byte: + return a.scanBytes(src) + case string: + return a.scanBytes([]byte(src)) + case nil: + *a = nil + return nil + } + + return fmt.Errorf("pq: cannot convert %T to Float32Array", src) +} + +func (a *Float32Array) scanBytes(src []byte) error { + elems, err := scanLinearArray(src, []byte{','}, "Float32Array") + if err != nil { + return err + } + if *a != nil && len(elems) == 0 { + *a = (*a)[:0] + } else { + b := make(Float32Array, len(elems)) + for i, v := range elems { + var x float64 + if x, err = strconv.ParseFloat(string(v), 32); err != nil { + return fmt.Errorf("pq: parsing array element index %d: %v", i, err) + } + b[i] = float32(x) + } + *a = b + } + return nil +} + +// Value implements the driver.Valuer interface. +func (a Float32Array) Value() (driver.Value, error) { + if a == nil { + return nil, nil + } + + if n := len(a); n > 0 { + // There will be at least two curly brackets, N bytes of values, + // and N-1 bytes of delimiters. + b := make([]byte, 1, 1+2*n) + b[0] = '{' + + b = strconv.AppendFloat(b, float64(a[0]), 'f', -1, 32) + for i := 1; i < n; i++ { + b = append(b, ',') + b = strconv.AppendFloat(b, float64(a[i]), 'f', -1, 32) + } + + return string(append(b, '}')), nil + } + + return "{}", nil +} + // GenericArray implements the driver.Valuer and sql.Scanner interfaces for // an array or slice of any dimension. type GenericArray struct{ A interface{} } @@ -483,6 +559,69 @@ func (a Int64Array) Value() (driver.Value, error) { return "{}", nil } +// Int32Array represents a one-dimensional array of the PostgreSQL integer types. +type Int32Array []int32 + +// Scan implements the sql.Scanner interface. +func (a *Int32Array) Scan(src interface{}) error { + switch src := src.(type) { + case []byte: + return a.scanBytes(src) + case string: + return a.scanBytes([]byte(src)) + case nil: + *a = nil + return nil + } + + return fmt.Errorf("pq: cannot convert %T to Int32Array", src) +} + +func (a *Int32Array) scanBytes(src []byte) error { + elems, err := scanLinearArray(src, []byte{','}, "Int32Array") + if err != nil { + return err + } + if *a != nil && len(elems) == 0 { + *a = (*a)[:0] + } else { + b := make(Int32Array, len(elems)) + for i, v := range elems { + var x int + if x, err = strconv.Atoi(string(v)); err != nil { + return fmt.Errorf("pq: parsing array element index %d: %v", i, err) + } + b[i] = int32(x) + } + *a = b + } + return nil +} + +// Value implements the driver.Valuer interface. +func (a Int32Array) Value() (driver.Value, error) { + if a == nil { + return nil, nil + } + + if n := len(a); n > 0 { + // There will be at least two curly brackets, N bytes of values, + // and N-1 bytes of delimiters. + b := make([]byte, 1, 1+2*n) + b[0] = '{' + + b = strconv.AppendInt(b, int64(a[0]), 10) + for i := 1; i < n; i++ { + b = append(b, ',') + b = strconv.AppendInt(b, int64(a[i]), 10) + } + + return string(append(b, '}')), nil + } + + return "{}", nil +} + // StringArray represents a one-dimensional array of the PostgreSQL character types. type StringArray []string diff --git a/array_test.go b/array_test.go index f724bcd88..5ca9f7a55 100644 --- a/array_test.go +++ b/array_test.go @@ -104,16 +104,33 @@ func TestArrayScanner(t *testing.T) { t.Errorf("Expected *Int64Array, got %T", s) } + s = Array(&[]float32{}) + if _, ok := s.(*Float32Array); !ok { + t.Errorf("Expected *Float32Array, got %T", s) + } + + s = Array(&[]int32{}) + if _, ok := s.(*Int32Array); !ok { + t.Errorf("Expected *Int32Array, got %T", s) + } + s = Array(&[]string{}) if _, ok := s.(*StringArray); !ok { t.Errorf("Expected *StringArray, got %T", s) } + s = Array(&[][]byte{}) + if _, ok := s.(*ByteaArray); !ok { + t.Errorf("Expected *ByteaArray, got %T", s) + } + for _, tt := range []interface{}{ &[]sql.Scanner{}, &[][]bool{}, &[][]float64{}, &[][]int64{}, + &[][]float32{}, + &[][]int32{}, &[][]string{}, } { s = Array(tt) @@ -139,17 +156,34 @@ func TestArrayValuer(t *testing.T) { t.Errorf("Expected *Int64Array, got %T", v) } + v = Array([]float32{}) + if _, ok := v.(*Float32Array); !ok { + t.Errorf("Expected *Float32Array, got %T", v) + } + + v = Array([]int32{}) + if _, ok := v.(*Int32Array); !ok { + t.Errorf("Expected *Int32Array, got %T", v) + } + v = Array([]string{}) if _, ok := v.(*StringArray); !ok { t.Errorf("Expected *StringArray, got %T", v) } + v = Array([][]byte{}) + if _, ok := v.(*ByteaArray); !ok { + t.Errorf("Expected *ByteaArray, got %T", v) + } + for _, tt := range []interface{}{ nil, []driver.Value{}, [][]bool{}, [][]float64{}, [][]int64{}, + [][]float32{}, + [][]int32{}, [][]string{}, } { v = Array(tt) @@ -773,6 +807,313 @@ func BenchmarkInt64ArrayValue(b *testing.B) { } } +func TestFloat32ArrayScanUnsupported(t *testing.T) { + var arr Float32Array + err := arr.Scan(true) + + if err == nil { + t.Fatal("Expected error when scanning from bool") + } + if !strings.Contains(err.Error(), "bool to Float32Array") { + t.Errorf("Expected type to be mentioned when scanning, got %q", err) + } +} + +func TestFloat32ArrayScanEmpty(t *testing.T) { + var arr Float32Array + err := arr.Scan(`{}`) + + if err != nil { + t.Fatalf("Expected no error, got %v", err) + } + if arr == nil || len(arr) != 0 { + t.Errorf("Expected empty, got %#v", arr) + } +} + +func TestFloat32ArrayScanNil(t *testing.T) { + arr := Float32Array{5, 5, 5} + err := arr.Scan(nil) + + if err != nil { + t.Fatalf("Expected no error, got %v", err) + } + if arr != nil { + t.Errorf("Expected nil, got %+v", arr) + } +} + +var Float32ArrayStringTests = []struct { + str string + arr Float32Array +}{ + {`{}`, Float32Array{}}, + {`{1.2}`, Float32Array{1.2}}, + {`{3.456,7.89}`, Float32Array{3.456, 7.89}}, + {`{3,1,2}`, Float32Array{3, 1, 2}}, +} + +func TestFloat32ArrayScanBytes(t *testing.T) { + for _, tt := range Float32ArrayStringTests { + bytes := []byte(tt.str) + arr := Float32Array{5, 5, 5} + err := arr.Scan(bytes) + + if err != nil { + t.Fatalf("Expected no error for %q, got %v", bytes, err) + } + if !reflect.DeepEqual(arr, tt.arr) { + t.Errorf("Expected %+v for %q, got %+v", tt.arr, bytes, arr) + } + } +} + +func BenchmarkFloat32ArrayScanBytes(b *testing.B) { + var a Float32Array + var x interface{} = []byte(`{1.2,3.4,5.6,7.8,9.01,2.34,5.67,8.90,1.234,5.678}`) + + for i := 0; i < b.N; i++ { + a = Float32Array{} + a.Scan(x) + } +} + +func TestFloat32ArrayScanString(t *testing.T) { + for _, tt := range Float32ArrayStringTests { + arr := Float32Array{5, 5, 5} + err := arr.Scan(tt.str) + + if err != nil { + t.Fatalf("Expected no error for %q, got %v", tt.str, err) + } + if !reflect.DeepEqual(arr, tt.arr) { + t.Errorf("Expected %+v for %q, got %+v", tt.arr, tt.str, arr) + } + } +} + +func TestFloat32ArrayScanError(t *testing.T) { + for _, tt := range []struct { + input, err string + }{ + {``, "unable to parse array"}, + {`{`, "unable to parse array"}, + {`{{5.6},{7.8}}`, "cannot convert ARRAY[2][1] to Float32Array"}, + {`{NULL}`, "parsing array element index 0:"}, + {`{a}`, "parsing array element index 0:"}, + {`{5.6,a}`, "parsing array element index 1:"}, + {`{5.6,7.8,a}`, "parsing array element index 2:"}, + } { + arr := Float32Array{5, 5, 5} + err := arr.Scan(tt.input) + + if err == nil { + t.Fatalf("Expected error for %q, got none", tt.input) + } + if !strings.Contains(err.Error(), tt.err) { + t.Errorf("Expected error to contain %q for %q, got %q", tt.err, tt.input, err) + } + if !reflect.DeepEqual(arr, Float32Array{5, 5, 5}) { + t.Errorf("Expected destination not to change for %q, got %+v", tt.input, arr) + } + } +} + +func TestFloat32ArrayValue(t *testing.T) { + result, err := Float32Array(nil).Value() + + if err != nil { + t.Fatalf("Expected no error for nil, got %v", err) + } + if result != nil { + t.Errorf("Expected nil, got %q", result) + } + + result, err = Float32Array([]float32{}).Value() + + if err != nil { + t.Fatalf("Expected no error for empty, got %v", err) + } + if expected := `{}`; !reflect.DeepEqual(result, expected) { + t.Errorf("Expected empty, got %q", result) + } + + result, err = Float32Array([]float32{1.2, 3.4, 5.6}).Value() + + if err != nil { + t.Fatalf("Expected no error, got %v", err) + } + if expected := `{1.2,3.4,5.6}`; !reflect.DeepEqual(result, expected) { + t.Errorf("Expected %q, got %q", expected, result) + } +} + +func BenchmarkFloat32ArrayValue(b *testing.B) { + rand.Seed(1) + x := make([]float32, 10) + for i := 0; i < len(x); i++ { + x[i] = rand.Float32() + } + a := Float32Array(x) + + for i := 0; i < b.N; i++ { + a.Value() + } +} + +func TestInt32ArrayScanUnsupported(t *testing.T) { + var arr Int32Array + err := arr.Scan(true) + + if err == nil { + t.Fatal("Expected error when scanning from bool") + } + if !strings.Contains(err.Error(), "bool to Int32Array") { + t.Errorf("Expected type to be mentioned when scanning, got %q", err) + } +} + +func TestInt32ArrayScanEmpty(t *testing.T) { + var arr Int32Array + err := arr.Scan(`{}`) + + if err != nil { + t.Fatalf("Expected no error, got %v", err) + } + if arr == nil || len(arr) != 0 { + t.Errorf("Expected empty, got %#v", arr) + } +} + +func TestInt32ArrayScanNil(t *testing.T) { + arr := Int32Array{5, 5, 5} + err := arr.Scan(nil) + + if err != nil { + t.Fatalf("Expected no error, got %v", err) + } + if arr != nil { + t.Errorf("Expected nil, got %+v", arr) + } +} + +var Int32ArrayStringTests = []struct { + str string + arr Int32Array +}{ + {`{}`, Int32Array{}}, + {`{12}`, Int32Array{12}}, + {`{345,678}`, Int32Array{345, 678}}, +} + +func TestInt32ArrayScanBytes(t *testing.T) { + for _, tt := range Int32ArrayStringTests { + bytes := []byte(tt.str) + arr := Int32Array{5, 5, 5} + err := arr.Scan(bytes) + + if err != nil { + t.Fatalf("Expected no error for %q, got %v", bytes, err) + } + if !reflect.DeepEqual(arr, tt.arr) { + t.Errorf("Expected %+v for %q, got %+v", tt.arr, bytes, arr) + } + } +} + +func BenchmarkInt32ArrayScanBytes(b *testing.B) { + var a Int32Array + var x interface{} = []byte(`{1,2,3,4,5,6,7,8,9,0}`) + + for i := 0; i < b.N; i++ { + a = Int32Array{} + a.Scan(x) + } +} + +func TestInt32ArrayScanString(t *testing.T) { + for _, tt := range Int32ArrayStringTests { + arr := Int32Array{5, 5, 5} + err := arr.Scan(tt.str) + + if err != nil { + t.Fatalf("Expected no error for %q, got %v", tt.str, err) + } + if !reflect.DeepEqual(arr, tt.arr) { + t.Errorf("Expected %+v for %q, got %+v", tt.arr, tt.str, arr) + } + } +} + +func TestInt32ArrayScanError(t *testing.T) { + for _, tt := range []struct { + input, err string + }{ + {``, "unable to parse array"}, + {`{`, "unable to parse array"}, + {`{{5},{6}}`, "cannot convert ARRAY[2][1] to Int32Array"}, + {`{NULL}`, "parsing array element index 0:"}, + {`{a}`, "parsing array element index 0:"}, + {`{5,a}`, "parsing array element index 1:"}, + {`{5,6,a}`, "parsing array element index 2:"}, + } { + arr := Int32Array{5, 5, 5} + err := arr.Scan(tt.input) + + if err == nil { + t.Fatalf("Expected error for %q, got none", tt.input) + } + if !strings.Contains(err.Error(), tt.err) { + t.Errorf("Expected error to contain %q for %q, got %q", tt.err, tt.input, err) + } + if !reflect.DeepEqual(arr, Int32Array{5, 5, 5}) { + t.Errorf("Expected destination not to change for %q, got %+v", tt.input, arr) + } + } +} + +func TestInt32ArrayValue(t *testing.T) { + result, err := Int32Array(nil).Value() + + if err != nil { + t.Fatalf("Expected no error for nil, got %v", err) + } + if result != nil { + t.Errorf("Expected nil, got %q", result) + } + + result, err = Int32Array([]int32{}).Value() + + if err != nil { + t.Fatalf("Expected no error for empty, got %v", err) + } + if expected := `{}`; !reflect.DeepEqual(result, expected) { + t.Errorf("Expected empty, got %q", result) + } + + result, err = Int32Array([]int32{1, 2, 3}).Value() + + if err != nil { + t.Fatalf("Expected no error, got %v", err) + } + if expected := `{1,2,3}`; !reflect.DeepEqual(result, expected) { + t.Errorf("Expected %q, got %q", expected, result) + } +} + +func BenchmarkInt32ArrayValue(b *testing.B) { + rand.Seed(1) + x := make([]int32, 10) + for i := 0; i < len(x); i++ { + x[i] = rand.Int31() + } + a := Int32Array(x) + + for i := 0; i < b.N; i++ { + a.Value() + } +} + func TestStringArrayScanUnsupported(t *testing.T) { var arr StringArray err := arr.Scan(true) From b9bb726ebf154627a21b50f9ffa4b28c6ed3f4d8 Mon Sep 17 00:00:00 2001 From: Eirik Date: Sun, 16 Dec 2018 22:34:36 +0100 Subject: [PATCH 19/65] Support inline SSL certificates Presently, pq only supports SSL connections by loading PEM certificates from files on disk. There are some situations (for example integration with HashiCorp Vault) where it's not so feasible to load certificates from a file system, but better to store them in-memory. This patch lets you set ?sslinline=true in the connection string, which changes the behavior of the paramters sslrootcert, sslcert and sslkey, so they contain the contents of the certificates directly, instead of file names pointing to the certificates on disk. --- ssl.go | 33 ++++++++++++++++++++++++++++++--- url.go | 4 ++-- url_test.go | 6 +++--- 3 files changed, 35 insertions(+), 8 deletions(-) diff --git a/ssl.go b/ssl.go index d90208455..5db5af83a 100644 --- a/ssl.go +++ b/ssl.go @@ -59,6 +59,9 @@ func ssl(o values) (func(net.Conn) (net.Conn, error), error) { return nil, err } + // This pseudo-parameter is not recognized by the PostgreSQL server, so let's delete it after use. + delete(o, "sslinline") + // Accept renegotiation requests initiated by the backend. // // Renegotiation was deprecated then removed from PostgreSQL 9.5, but @@ -83,6 +86,20 @@ func ssl(o values) (func(net.Conn) (net.Conn, error), error) { // in the user's home directory. The configured files must exist and have // the correct permissions. func sslClientCertificates(tlsConf *tls.Config, o values) error { + sslinline := o["sslinline"] + if "true" == sslinline { + cert, err := tls.X509KeyPair([]byte(o["sslcert"]), []byte(o["sslkey"])) + // Clear out these params, in case they were to be sent to the PostgreSQL server by mistake + o["sslcert"] = "" + o["sslkey"] = "" + if err != nil { + return err + } + tlsConf.Certificates = []tls.Certificate{cert} + return nil + } + + // user.Current() might fail when cross-compiling. We have to ignore the // error and continue without home directory defaults, since we wouldn't // know from where to load them. @@ -137,9 +154,19 @@ func sslCertificateAuthority(tlsConf *tls.Config, o values) error { if sslrootcert := o["sslrootcert"]; len(sslrootcert) > 0 { tlsConf.RootCAs = x509.NewCertPool() - cert, err := ioutil.ReadFile(sslrootcert) - if err != nil { - return err + sslinline := o["sslinline"] + + var cert []byte + if "true" == sslinline { + // // Clear out this param, in case it were to be sent to the PostgreSQL server by mistake + o["sslrootcert"] = "" + cert = []byte(sslrootcert) + } else { + var err error + cert, err = ioutil.ReadFile(sslrootcert) + if err != nil { + return err + } } if !tlsConf.RootCAs.AppendCertsFromPEM(cert) { diff --git a/url.go b/url.go index f4d8a7c20..aec6e95be 100644 --- a/url.go +++ b/url.go @@ -40,10 +40,10 @@ func ParseURL(url string) (string, error) { } var kvs []string - escaper := strings.NewReplacer(` `, `\ `, `'`, `\'`, `\`, `\\`) + escaper := strings.NewReplacer(`'`, `\'`, `\`, `\\`) accrue := func(k, v string) { if v != "" { - kvs = append(kvs, k+"="+escaper.Replace(v)) + kvs = append(kvs, k+"='"+escaper.Replace(v)+"'") } } diff --git a/url_test.go b/url_test.go index 4ff0ce034..9b6345595 100644 --- a/url_test.go +++ b/url_test.go @@ -5,7 +5,7 @@ import ( ) func TestSimpleParseURL(t *testing.T) { - expected := "host=hostname.remote" + expected := "host='hostname.remote'" str, err := ParseURL("postgres://hostname.remote") if err != nil { t.Fatal(err) @@ -17,7 +17,7 @@ func TestSimpleParseURL(t *testing.T) { } func TestIPv6LoopbackParseURL(t *testing.T) { - expected := "host=::1 port=1234" + expected := "host='::1' port='1234'" str, err := ParseURL("postgres://[::1]:1234") if err != nil { t.Fatal(err) @@ -29,7 +29,7 @@ func TestIPv6LoopbackParseURL(t *testing.T) { } func TestFullParseURL(t *testing.T) { - expected := `dbname=database host=hostname.remote password=top\ secret port=1234 user=username` + expected := `dbname='database' host='hostname.remote' password='top secret' port='1234' user='username'` str, err := ParseURL("postgres://username:top%20secret@hostname.remote:1234/database") if err != nil { t.Fatal(err) From b7c85eeec276e594e29cd6ceec5e007965ff0d51 Mon Sep 17 00:00:00 2001 From: Eirik Date: Sun, 16 Dec 2018 23:22:54 +0100 Subject: [PATCH 20/65] remove empty line --- ssl.go | 1 - 1 file changed, 1 deletion(-) diff --git a/ssl.go b/ssl.go index 5db5af83a..3c1f1fc37 100644 --- a/ssl.go +++ b/ssl.go @@ -99,7 +99,6 @@ func sslClientCertificates(tlsConf *tls.Config, o values) error { return nil } - // user.Current() might fail when cross-compiling. We have to ignore the // error and continue without home directory defaults, since we wouldn't // know from where to load them. From 1467baf0954574fc17008b8c564938ffe3bfaa95 Mon Sep 17 00:00:00 2001 From: Eirik Sletteberg Date: Fri, 12 Feb 2021 12:47:39 +0100 Subject: [PATCH 21/65] Fix code style (no yoda conditions) --- ssl.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ssl.go b/ssl.go index 3c1f1fc37..881c2219b 100644 --- a/ssl.go +++ b/ssl.go @@ -87,7 +87,7 @@ func ssl(o values) (func(net.Conn) (net.Conn, error), error) { // the correct permissions. func sslClientCertificates(tlsConf *tls.Config, o values) error { sslinline := o["sslinline"] - if "true" == sslinline { + if sslinline == "true" { cert, err := tls.X509KeyPair([]byte(o["sslcert"]), []byte(o["sslkey"])) // Clear out these params, in case they were to be sent to the PostgreSQL server by mistake o["sslcert"] = "" @@ -156,7 +156,7 @@ func sslCertificateAuthority(tlsConf *tls.Config, o values) error { sslinline := o["sslinline"] var cert []byte - if "true" == sslinline { + if sslinline == "true" { // // Clear out this param, in case it were to be sent to the PostgreSQL server by mistake o["sslrootcert"] = "" cert = []byte(sslrootcert) From 2a9e9edbc676b5886cd446089ca450693e327f73 Mon Sep 17 00:00:00 2001 From: Vegard Stikbakke Date: Wed, 14 Apr 2021 12:58:37 +0200 Subject: [PATCH 22/65] Fix string quoting in an example in Array --- array.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/array.go b/array.go index 405da2368..7806a31f3 100644 --- a/array.go +++ b/array.go @@ -22,7 +22,7 @@ var typeSQLScanner = reflect.TypeOf((*sql.Scanner)(nil)).Elem() // db.Query(`SELECT * FROM t WHERE id = ANY($1)`, pq.Array([]int{235, 401})) // // var x []sql.NullInt64 -// db.QueryRow('SELECT ARRAY[235, 401]').Scan(pq.Array(&x)) +// db.QueryRow(`SELECT ARRAY[235, 401]`).Scan(pq.Array(&x)) // // Scanning multi-dimensional arrays is not supported. Arrays where the lower // bound is not one (such as `[0:0]={1}') are not supported. From d07609080cd0b3a1552f718796a7b095c057689c Mon Sep 17 00:00:00 2001 From: Bjorn <47388782+bjornouderoelink@users.noreply.github.com> Date: Sun, 18 Apr 2021 14:54:43 +0200 Subject: [PATCH 23/65] Fix concurrent map writes. This commit should fix an issue with concurrent map writes by making the opts values map in separate connections not reference the same underlying data. --- conn.go | 8 +++++++- conn_go18.go | 13 +++++++++++-- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/conn.go b/conn.go index 368013856..4f35614f3 100644 --- a/conn.go +++ b/conn.go @@ -298,7 +298,13 @@ func (c *Connector) open(ctx context.Context) (cn *conn, err error) { // the user. defer errRecoverNoErrBadConn(&err) - o := c.opts + // Create a new values map (copy). This makes it so maps in different + // connections do not reference the same underlying data structure, so it + // is safe for multiple connections to concurrently write to their opts. + o := make(values) + for k, v := range c.opts { + o[k] = v + } bad := &atomic.Value{} bad.Store(false) diff --git a/conn_go18.go b/conn_go18.go index 8cab67c9d..2b9a9599e 100644 --- a/conn_go18.go +++ b/conn_go18.go @@ -129,7 +129,16 @@ func (cn *conn) watchCancel(ctx context.Context) func() { } func (cn *conn) cancel(ctx context.Context) error { - c, err := dial(ctx, cn.dialer, cn.opts) + // Create a new values map (copy). This makes sure the connection created + // in this method cannot write to the same underlying data, which could + // cause a concurrent map write panic. This is necessary because cancel + // is called from a goroutine in watchCancel. + o := make(values) + for k, v := range cn.opts { + o[k] = v + } + + c, err := dial(ctx, cn.dialer, o) if err != nil { return err } @@ -142,7 +151,7 @@ func (cn *conn) cancel(ctx context.Context) error { c: c, bad: bad, } - err = can.ssl(cn.opts) + err = can.ssl(o) if err != nil { return err } From 69b14f136170013aee4b2adb9b0291677770b01a Mon Sep 17 00:00:00 2001 From: Bjorn <47388782+bjornouderoelink@users.noreply.github.com> Date: Mon, 19 Apr 2021 21:51:42 +0200 Subject: [PATCH 24/65] Fix sslinline for connections after the first one. The sslinline functionality would delete the required keys after upgrading the first connection. This commit adds the sslinine key to the isDriverSetting so that it is not sent to Posgres which would cause a crash. It also removes the delete of the keys necessary for sslinline to allow SSL upgrades for connections after the first one. --- conn.go | 2 +- ssl.go | 8 -------- 2 files changed, 1 insertion(+), 9 deletions(-) diff --git a/conn.go b/conn.go index 4f35614f3..b09a17047 100644 --- a/conn.go +++ b/conn.go @@ -1106,7 +1106,7 @@ func isDriverSetting(key string) bool { return true case "password": return true - case "sslmode", "sslcert", "sslkey", "sslrootcert": + case "sslmode", "sslcert", "sslkey", "sslrootcert", "sslinline": return true case "fallback_application_name": return true diff --git a/ssl.go b/ssl.go index 881c2219b..e5eb92895 100644 --- a/ssl.go +++ b/ssl.go @@ -59,9 +59,6 @@ func ssl(o values) (func(net.Conn) (net.Conn, error), error) { return nil, err } - // This pseudo-parameter is not recognized by the PostgreSQL server, so let's delete it after use. - delete(o, "sslinline") - // Accept renegotiation requests initiated by the backend. // // Renegotiation was deprecated then removed from PostgreSQL 9.5, but @@ -89,9 +86,6 @@ func sslClientCertificates(tlsConf *tls.Config, o values) error { sslinline := o["sslinline"] if sslinline == "true" { cert, err := tls.X509KeyPair([]byte(o["sslcert"]), []byte(o["sslkey"])) - // Clear out these params, in case they were to be sent to the PostgreSQL server by mistake - o["sslcert"] = "" - o["sslkey"] = "" if err != nil { return err } @@ -157,8 +151,6 @@ func sslCertificateAuthority(tlsConf *tls.Config, o values) error { var cert []byte if sslinline == "true" { - // // Clear out this param, in case it were to be sent to the PostgreSQL server by mistake - o["sslrootcert"] = "" cert = []byte(sslrootcert) } else { var err error From feb727accbccd42cd5dfca484e5d75c33f619db9 Mon Sep 17 00:00:00 2001 From: bukowa <38087302+bukowa@users.noreply.github.com> Date: Sat, 24 Apr 2021 15:32:46 +0200 Subject: [PATCH 25/65] userCurrent for unsupported GOOS --- user_other.go | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 user_other.go diff --git a/user_other.go b/user_other.go new file mode 100644 index 000000000..f9316da5d --- /dev/null +++ b/user_other.go @@ -0,0 +1,14 @@ +// Package pq is a pure Go Postgres driver for the database/sql package. + +// +build js android hurd illumos zos + +package pq + +import ( + "os" + "os/user" +) + +func userCurrent() (string, error) { + return "", ErrCouldNotDetectUsername +} From 6ed3b8ac03dffde67cfa129c8b553e960e9795e0 Mon Sep 17 00:00:00 2001 From: bukowa <38087302+bukowa@users.noreply.github.com> Date: Sat, 24 Apr 2021 15:43:27 +0200 Subject: [PATCH 26/65] rm unused imports --- user_other.go | 5 ----- 1 file changed, 5 deletions(-) diff --git a/user_other.go b/user_other.go index f9316da5d..f1c33134d 100644 --- a/user_other.go +++ b/user_other.go @@ -4,11 +4,6 @@ package pq -import ( - "os" - "os/user" -) - func userCurrent() (string, error) { return "", ErrCouldNotDetectUsername } From a1b1a43f73b0c216a49c6ba1073779d44db726eb Mon Sep 17 00:00:00 2001 From: Oliver Tan Date: Mon, 17 May 2021 11:23:16 +1000 Subject: [PATCH 27/65] Create test.yml --- test.yml | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 test.yml diff --git a/test.yml b/test.yml new file mode 100644 index 000000000..bf6f5e367 --- /dev/null +++ b/test.yml @@ -0,0 +1,40 @@ +on: [push, pull_request] +name: Test +jobs: + test: + strategy: + matrix: + go-version: [1.15.x, 1.16.x] + postgres-version: [9.4, 9.5, 9.6, 10, 11, 12] + binary-parameter: [false, true] + os: [ubuntu-latest] + runs-on: ${{ matrix.os }} + steps: + - name: Install Go + uses: actions/setup-go@v2 + with: + go-version: ${{ matrix.go-version }} + - uses: harmon758/postgresql-action@v1 + with: + postgresql version: ${{ matrix.postgresql-version }} + postgresql db: 'pqgotest' + - name: Create users + run: | + createuser -DRS pqgossltest + createuser -DRS pqgosslcert + - name: Checkout code + uses: actions/checkout@v2 + - name: Test + run: PQTEST_BINARY_PARAMETERS=${{ matrix.binary-parameter }} go test ./... + golangci: + strategy: + matrix: + go-version: [1.15.x, 1.16.x] + name: lint + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: golangci-lint + uses: golangci/golangci-lint-action@v2 + with: + version: v1.29 From d2b13db12b5b06ce0104435dcbecc3abcbab6845 Mon Sep 17 00:00:00 2001 From: Oliver Tan Date: Mon, 17 May 2021 11:25:48 +1000 Subject: [PATCH 28/65] Delete test.yml --- test.yml | 40 ---------------------------------------- 1 file changed, 40 deletions(-) delete mode 100644 test.yml diff --git a/test.yml b/test.yml deleted file mode 100644 index bf6f5e367..000000000 --- a/test.yml +++ /dev/null @@ -1,40 +0,0 @@ -on: [push, pull_request] -name: Test -jobs: - test: - strategy: - matrix: - go-version: [1.15.x, 1.16.x] - postgres-version: [9.4, 9.5, 9.6, 10, 11, 12] - binary-parameter: [false, true] - os: [ubuntu-latest] - runs-on: ${{ matrix.os }} - steps: - - name: Install Go - uses: actions/setup-go@v2 - with: - go-version: ${{ matrix.go-version }} - - uses: harmon758/postgresql-action@v1 - with: - postgresql version: ${{ matrix.postgresql-version }} - postgresql db: 'pqgotest' - - name: Create users - run: | - createuser -DRS pqgossltest - createuser -DRS pqgosslcert - - name: Checkout code - uses: actions/checkout@v2 - - name: Test - run: PQTEST_BINARY_PARAMETERS=${{ matrix.binary-parameter }} go test ./... - golangci: - strategy: - matrix: - go-version: [1.15.x, 1.16.x] - name: lint - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - name: golangci-lint - uses: golangci/golangci-lint-action@v2 - with: - version: v1.29 From 62fa4b32ec214518913aaeabb837feaa1dbc6832 Mon Sep 17 00:00:00 2001 From: Oliver Tan Date: Mon, 17 May 2021 13:35:04 +1000 Subject: [PATCH 29/65] .travis.yml: fix CI --- .travis.sh | 12 +++++------- .travis.yml | 2 +- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/.travis.sh b/.travis.sh index ebf447030..15607b50d 100755 --- a/.travis.sh +++ b/.travis.sh @@ -1,17 +1,15 @@ #!/bin/bash -set -eu +set -eux client_configure() { sudo chmod 600 $PQSSLCERTTEST_PATH/postgresql.key } pgdg_repository() { - local sourcelist='sources.list.d/postgresql.list' - curl -sS 'https://www.postgresql.org/media/keys/ACCC4CF8.asc' | sudo apt-key add - - echo deb http://apt.postgresql.org/pub/repos/apt/ $(lsb_release -cs)-pgdg main $PGVERSION | sudo tee "/etc/apt/$sourcelist" - sudo apt-get -o Dir::Etc::sourcelist="$sourcelist" -o Dir::Etc::sourceparts='-' -o APT::Get::List-Cleanup='0' update + echo "deb http://apt.postgresql.org/pub/repos/apt/ `lsb_release -cs`-pgdg main" | sudo tee /etc/apt/sources.list.d/pgdg.list + sudo apt-get update } postgresql_configure() { @@ -51,10 +49,10 @@ postgresql_configure() { } postgresql_install() { - xargs sudo apt-get -y -o Dpkg::Options::='--force-confdef' -o Dpkg::Options::='--force-confnew' install <<-packages + xargs sudo apt-get -y install <<-packages postgresql-$PGVERSION + postgresql-client-$PGVERSION postgresql-server-dev-$PGVERSION - postgresql-contrib-$PGVERSION packages } diff --git a/.travis.yml b/.travis.yml index f378207f2..283f35f23 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,7 +3,7 @@ language: go go: - 1.14.x - 1.15.x - - master + - 1.16.x sudo: true From ad47bab1aa0f24a7e1ae63197a61b4a2f1458ad8 Mon Sep 17 00:00:00 2001 From: Oliver Tan Date: Thu, 13 May 2021 09:48:52 +1000 Subject: [PATCH 30/65] encode: fix TimeTZ with second offsets lib/pq previously did not work with TimeTZ offsets with second offsets. Rectify this by modifying the parse format string as appropriate. --- encode.go | 14 ++++++++++---- encode_test.go | 4 ++++ 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/encode.go b/encode.go index c4dafe270..51c143ee4 100644 --- a/encode.go +++ b/encode.go @@ -200,11 +200,17 @@ func appendEscapedText(buf []byte, text string) []byte { func mustParse(f string, typ oid.Oid, s []byte) time.Time { str := string(s) - // check for a 30-minute-offset timezone - if (typ == oid.T_timestamptz || typ == oid.T_timetz) && - str[len(str)-3] == ':' { - f += ":00" + // Check for a minute and second offset in the timezone. + if typ == oid.T_timestamptz || typ == oid.T_timetz { + for i := 3; i <= 6; i += 3 { + if str[len(str)-i] == ':' { + f += ":00" + continue + } + break + } } + // Special case for 24:00 time. // Unfortunately, golang does not parse 24:00 as a proper time. // In this case, we want to try "round to the next day", to differentiate. diff --git a/encode_test.go b/encode_test.go index 3406c315a..bffa83868 100644 --- a/encode_test.go +++ b/encode_test.go @@ -59,6 +59,8 @@ var timeTests = []struct { time.FixedZone("", -(7*60*60+42*60)))}, {"2001-02-03 04:05:06-07:30:09", time.Date(2001, time.February, 3, 4, 5, 6, 0, time.FixedZone("", -(7*60*60+30*60+9)))}, + {"2001-02-03 04:05:06+07:30:09", time.Date(2001, time.February, 3, 4, 5, 6, 0, + time.FixedZone("", +(7*60*60+30*60+9)))}, {"2001-02-03 04:05:06+07", time.Date(2001, time.February, 3, 4, 5, 6, 0, time.FixedZone("", 7*60*60))}, {"0011-02-03 04:05:06 BC", time.Date(-10, time.February, 3, 4, 5, 6, 0, time.FixedZone("", 0))}, @@ -251,6 +253,8 @@ func TestTimeWithTimezone(t *testing.T) { }{ {"11:59:59+00:00", time.Date(0, 1, 1, 11, 59, 59, 0, time.UTC)}, {"11:59:59+04:00", time.Date(0, 1, 1, 11, 59, 59, 0, time.FixedZone("+04", 4*60*60))}, + {"11:59:59+04:01:02", time.Date(0, 1, 1, 11, 59, 59, 0, time.FixedZone("+04:01:02", 4*60*60+1*60+2))}, + {"11:59:59-04:01:02", time.Date(0, 1, 1, 11, 59, 59, 0, time.FixedZone("-04:01:02", -(4*60*60+1*60+2)))}, {"24:00+00", time.Date(0, 1, 2, 0, 0, 0, 0, time.UTC)}, {"24:00Z", time.Date(0, 1, 2, 0, 0, 0, 0, time.UTC)}, {"24:00-04:00", time.Date(0, 1, 2, 0, 0, 0, 0, time.FixedZone("-04", -4*60*60))}, From 9e3d2ded3e7c61a1d8375ff8adb67868b05c372d Mon Sep 17 00:00:00 2001 From: Santiago De la Cruz Date: Thu, 27 May 2021 15:52:30 -0400 Subject: [PATCH 31/65] Fix build for illumos and solaris From Go 1.13, the illumos build tag implies the solaris build tag (but it's better use both to clarify) --- user_other.go | 2 +- user_posix.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/user_other.go b/user_other.go index f1c33134d..85f70ba70 100644 --- a/user_other.go +++ b/user_other.go @@ -1,6 +1,6 @@ // Package pq is a pure Go Postgres driver for the database/sql package. -// +build js android hurd illumos zos +// +build js android hurd zos package pq diff --git a/user_posix.go b/user_posix.go index a51019205..22299322a 100644 --- a/user_posix.go +++ b/user_posix.go @@ -1,6 +1,6 @@ // Package pq is a pure Go Postgres driver for the database/sql package. -// +build aix darwin dragonfly freebsd linux nacl netbsd openbsd plan9 solaris rumprun +// +build aix darwin dragonfly freebsd linux nacl netbsd openbsd plan9 solaris rumprun illumos package pq From fa7ae67f2d8f3571b63fe9abe19632aae0b5b986 Mon Sep 17 00:00:00 2001 From: Santiago De la Cruz Date: Thu, 27 May 2021 16:40:41 -0400 Subject: [PATCH 32/65] Avoid type assertion to the same type --- error.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/error.go b/error.go index c19c349f1..b0f53755a 100644 --- a/error.go +++ b/error.go @@ -499,7 +499,7 @@ func (cn *conn) errRecover(err *error) { cn.setBad() *err = driver.ErrBadConn case error: - if v == io.EOF || v.(error).Error() == "remote error: handshake failure" { + if v == io.EOF || v.Error() == "remote error: handshake failure" { *err = driver.ErrBadConn } else { *err = v From 6ede22e220e6529d9fde721f92a4ff624a00c63c Mon Sep 17 00:00:00 2001 From: Cody Smith Date: Wed, 1 Sep 2021 16:08:39 -0700 Subject: [PATCH 33/65] Clarify maintenance mode MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Looking through recent issues, this seems to be roughly the current situation. Making it clearer here, to help users evaluating this lightweight library against the heavier-weight pgx. But I confess, I might be guessing totally wrong. Whether or not this PR content is right, I just want to say thanks to the maintainers of this library. We're using it at Camus Energy to help grid operators decarbonize, a.k.a. fighting climate change. We're really happy this library exists, because it serves our needs better than pgx. Really, thanks 😃 --- README.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index c972a86a5..126ee5d35 100644 --- a/README.md +++ b/README.md @@ -27,4 +27,10 @@ ## Status -This package is effectively in maintenance mode and is not actively developed. Small patches and features are only rarely reviewed and merged. We recommend using [pgx](https://github.com/jackc/pgx) which is actively maintained. +This package is currently in maintenance mode, which means: +1. It generally does not accept new features. +2. It does accept bug fixes and version compatability changes provided by the community. +3. Maintainers usually do not resolve reported issues. +4. Community members are encouraged to help each other with reported issues. + +For users that require new features or reliable resolution of reported bugs, we recommend using [pgx](https://github.com/jackc/pgx) which is under active development. From 1c85910dd0bcec51147cde691fb33e4434a8e273 Mon Sep 17 00:00:00 2001 From: Michael Hobbs Date: Wed, 1 Sep 2021 13:12:47 -0700 Subject: [PATCH 34/65] implement gh actions workflow --- .github/workflows/test.yml | 211 +++++++++++++++++++++++++++++++++++ Makefile | 36 ++++++ auth/kerberos/krb_unix.go | 2 +- auth/kerberos/krb_windows.go | 2 +- certs/postgresql.cnf | 10 ++ certs/postgresql.crt | 85 +++----------- certs/postgresql.key | 43 ++++--- certs/root.cnf | 10 ++ certs/root.crt | 44 ++++---- certs/server.cnf | 29 +++++ certs/server.crt | 99 ++++------------ certs/server.key | 55 ++++----- 12 files changed, 414 insertions(+), 212 deletions(-) create mode 100644 .github/workflows/test.yml create mode 100644 Makefile create mode 100644 certs/postgresql.cnf create mode 100644 certs/root.cnf create mode 100644 certs/server.cnf diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 000000000..2bbc7efd0 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,211 @@ +name: Test + +on: + pull_request: + branches: [ master ] + +jobs: + test: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + postgres: + - '13' + - '12' + - '11' + - '10' + - '9.6' + go: + - '1.17' + - '1.16' + - '1.15' + - '1.14' + steps: + - name: setup postgres pre-reqs + run: | + mkdir init + cat < init/root.crt + -----BEGIN CERTIFICATE----- + MIIEBjCCAu6gAwIBAgIJAPizR+OD14YnMA0GCSqGSIb3DQEBCwUAMF4xCzAJBgNV + BAYTAlVTMQ8wDQYDVQQIDAZOZXZhZGExEjAQBgNVBAcMCUxhcyBWZWdhczEaMBgG + A1UECgwRZ2l0aHViLmNvbS9saWIvcHExDjAMBgNVBAMMBXBxIENBMB4XDTIxMDkw + MjAxNTUwMloXDTMxMDkwMzAxNTUwMlowXjELMAkGA1UEBhMCVVMxDzANBgNVBAgM + Bk5ldmFkYTESMBAGA1UEBwwJTGFzIFZlZ2FzMRowGAYDVQQKDBFnaXRodWIuY29t + L2xpYi9wcTEOMAwGA1UEAwwFcHEgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw + ggEKAoIBAQDb9d6sjdU6GdibGrXRMOHREH3MRUS8T4TFqGgPEGVDP/V5bAZlBSGP + AN0o9DTyVLcbQpBt8zMTw9KeIzIIe5NIVkSmA16lw/YckGhOM+kZIkiDuE6qt5Ia + OQCRMdXkZ8ejG/JUu+rHU8FJZL8DE+jyYherzdjkeVAQ7JfzxAwW2Dl7T/47g337 + Pwmf17AEb8ibSqmXyUN7R5NhJQs+hvaYdNagzdx91E1H+qlyBvmiNeasUQljLvZ+ + Y8wAuU79neA+d09O4PBiYwV17rSP6SZCeGE3oLZviL/0KM9Xig88oB+2FmvQ6Zxa + L7SoBlqS+5pBZwpH7eee/wCIKAnJtMAJAgMBAAGjgcYwgcMwDwYDVR0TAQH/BAUw + AwEB/zAdBgNVHQ4EFgQUfIXEczahbcM2cFrwclJF7GbdajkwgZAGA1UdIwSBiDCB + hYAUfIXEczahbcM2cFrwclJF7GbdajmhYqRgMF4xCzAJBgNVBAYTAlVTMQ8wDQYD + VQQIDAZOZXZhZGExEjAQBgNVBAcMCUxhcyBWZWdhczEaMBgGA1UECgwRZ2l0aHVi + LmNvbS9saWIvcHExDjAMBgNVBAMMBXBxIENBggkA+LNH44PXhicwDQYJKoZIhvcN + AQELBQADggEBABFyGgSz2mHVJqYgX1Y+7P+MfKt83cV2uYDGYvXrLG2OGiCilVul + oTBG+8omIMSHOsQZvWMpA5H0tnnlQHrKpKpUyKkSL+Wv5GL0UtBmHX7mVRiaK2l4 + q2BjRaQUitp/FH4NSdXtVrMME5T1JBBZHsQkNL3cNRzRKwY/Vj5UGEDxDS7lILUC + e01L4oaK0iKQn4beALU+TvKoAHdPvoxpPpnhkF5ss9HmdcvRktJrKZemDJZswZ7/ + +omx8ZPIYYUH5VJJYYE88S7guAt+ZaKIUlel/t6xPbo2ZySFSg9u1uB99n+jTo3L + 1rAxFnN3FCX2jBqgP29xMVmisaN5k04UmyI= + -----END CERTIFICATE----- + CONF + cat < init/server.crt + -----BEGIN CERTIFICATE----- + MIIDqzCCApOgAwIBAgIJAPiewLrOyYipMA0GCSqGSIb3DQEBCwUAMF4xCzAJBgNV + BAYTAlVTMQ8wDQYDVQQIDAZOZXZhZGExEjAQBgNVBAcMCUxhcyBWZWdhczEaMBgG + A1UECgwRZ2l0aHViLmNvbS9saWIvcHExDjAMBgNVBAMMBXBxIENBMB4XDTIxMDkw + MjAxNTUwMloXDTMxMDkwMzAxNTUwMlowTjELMAkGA1UEBhMCVVMxDzANBgNVBAgM + Bk5ldmFkYTESMBAGA1UEBwwJTGFzIFZlZ2FzMRowGAYDVQQKDBFnaXRodWIuY29t + L2xpYi9wcTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKf6H4UzmANN + QiQJe92Mf3ETMYmpZKNNO9DPEHyNLIkag+XwMrBTdcCK0mLvsNCYpXuBN6703KCd + WAFOeMmj7gOsWtvjt5Xm6bRHLgegekXzcG/jDwq/wyzeDzr/YkITuIlG44Lf9lhY + FLwiHlHOWHnwrZaEh6aU//02aQkzyX5INeXl/3TZm2G2eIH6AOxOKOU27MUsyVSQ + 5DE+SDKGcRP4bElueeQWvxAXNMZYb7sVSDdfHI3zr32K4k/tC8x0fZJ5XN/dvl4t + 4N4MrYlmDO5XOrb/gQH1H4iu6+5EMDfZYab4fkThnNFdfFqu4/8Scv7KZ8mWqpKM + fGAjEPctQi0CAwEAAaN8MHowHQYDVR0OBBYEFENExPbmDyFB2AJUdbMvVyhlNPD5 + MAkGA1UdEwQCMAAwCwYDVR0PBAQDAgWgMBMGA1UdEQQMMAqCCHBvc3RncmVzMCwG + CWCGSAGG+EIBDQQfFh1PcGVuU1NMIEdlbmVyYXRlZCBDZXJ0aWZpY2F0ZTANBgkq + hkiG9w0BAQsFAAOCAQEAMRVbV8RiEsmp9HAtnVCZmRXMIbgPGrqjeSwk586s4K8v + BSqNCqxv6s5GfCRmDYiqSqeuCVDtUJS1HsTmbxVV7Ke71WMo+xHR1ICGKOa8WGCb + TGsuicG5QZXWaxeMOg4s0qpKmKko0d1aErdVsanU5dkrVS7D6729Ffnzu4lwApk6 + invAB67p8u7sojwqRq5ce0vRaG+YFylTrWomF9kauEb8gKbQ9Xc7QfX+h+UH/mq9 + Nvdj8LOHp6/82bZdnsYUOtV4lS1IA/qzeXpqBphxqfWabD1yLtkyJyImZKq8uIPp + 0CG4jhObPdWcCkXD6bg3QK3mhwlC79OtFgxWmldCRQ== + -----END CERTIFICATE----- + CONF + cat < init/server.key + -----BEGIN PRIVATE KEY----- + MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCn+h+FM5gDTUIk + CXvdjH9xEzGJqWSjTTvQzxB8jSyJGoPl8DKwU3XAitJi77DQmKV7gTeu9NygnVgB + TnjJo+4DrFrb47eV5um0Ry4HoHpF83Bv4w8Kv8Ms3g86/2JCE7iJRuOC3/ZYWBS8 + Ih5Rzlh58K2WhIemlP/9NmkJM8l+SDXl5f902ZthtniB+gDsTijlNuzFLMlUkOQx + PkgyhnET+GxJbnnkFr8QFzTGWG+7FUg3XxyN8699iuJP7QvMdH2SeVzf3b5eLeDe + DK2JZgzuVzq2/4EB9R+IruvuRDA32WGm+H5E4ZzRXXxaruP/EnL+ymfJlqqSjHxg + IxD3LUItAgMBAAECggEAOE2naQ9tIZYw2EFxikZApVcooJrtx6ropMnzHbx4NBB2 + K4mChAXFj184u77ZxmGT/jzGvFcI6LE0wWNbK0NOUV7hKZk/fPhkV3AQZrAMrAu4 + IVi7PwAd3JkmA8F8XuebUDA5rDGDsgL8GD9baFJA58abeLs9eMGyuF4XgOUh4bip + hgHa76O2rcDWNY5HZqqRslw75FzlYkB0PCts/UJxSswj70kTTihyOhDlrm2TnyxI + ne54UbGRrpfs9wiheSGLjDG81qZToBHQDwoAnjjZhu1VCaBISuGbgZrxyyRyqdnn + xPW+KczMv04XyvF7v6Pz+bUEppalLXGiXnH5UtWvZQKBgQDTPCdMpNE/hwlq4nAw + Kf42zIBWfbnMLVWYoeDiAOhtl9XAUAXn76xe6Rvo0qeAo67yejdbJfRq3HvGyw+q + 4PS8r9gXYmLYIPQxSoLL5+rFoBCN3qFippfjLB1j32mp7+15KjRj8FF2r6xIN8fu + XatSRsaqmvCWYLDRv/rbHnxwkwKBgQDLkyfFLF7BtwtPWKdqrwOM7ip1UKh+oDBS + vkCQ08aEFRBU7T3jChsx5GbaW6zmsSBwBwcrHclpSkz7n3aq19DDWObJR2p80Fma + rsXeIcvtEpkvT3pVX268P5d+XGs1kxgFunqTysG9yChW+xzcs5MdKBzuMPPn7rL8 + MKAzdar6PwKBgEypkzW8x3h/4Moa3k6MnwdyVs2NGaZheaRIc95yJ+jGZzxBjrMr + h+p2PbvU4BfO0AqOkpKRBtDVrlJqlggVVp04UHvEKE16QEW3Xhr0037f5cInX3j3 + Lz6yXwRFLAsR2aTUzWjL6jTh8uvO2s/GzQuyRh3a16Ar/WBShY+K0+zjAoGATnLT + xZjWnyHRmu8X/PWakamJ9RFzDPDgDlLAgM8LVgTj+UY/LgnL9wsEU6s2UuP5ExKy + QXxGDGwUhHar/SQTj+Pnc7Mwpw6HKSOmnnY5po8fNusSwml3O9XppEkrC0c236Y/ + 7EobJO5IFVTJh4cv7vFxTJzSsRL8KFD4uzvh+nMCgYEAqY8NBYtIgNJA2B6C6hHF + +bG7v46434ZHFfGTmMQwzE4taVg7YRnzYESAlvK4bAP5ZXR90n7GRGFhrXzoMZ38 + r0bw/q9rV+ReGda7/Bjf7ciCKiq0RODcHtf4IaskjPXCoQRGJtgCPLhWPfld6g9v + /HTvO96xv9e3eG/PKSPog94= + -----END PRIVATE KEY----- + CONF + cat < init/hba.sh + cat < /var/lib/postgresql/data/pg_hba.conf + local all all trust + host all postgres all trust + hostnossl all pqgossltest all reject + hostnossl all pqgosslcert all reject + hostssl all pqgossltest all trust + hostssl all pqgosslcert all cert + host all all all trust + EOF + CONF + sudo chown 999:999 ./init/* + sudo chmod 600 ./init/* + + - name: start postgres + run: | + docker run -d \ + --name pg \ + -p 5432:5432 \ + -v $(pwd)/init:/init \ + -e POSTGRES_PASSWORD=unused \ + -e POSTGRES_USER=postgres \ + postgres:${{ matrix.postgres }} \ + -c ssl=on \ + -c ssl_ca_file=/init/root.crt \ + -c ssl_cert_file=/init/server.crt \ + -c ssl_key_file=/init/server.key + + - name: configure postgres + run: | + n=0 + until [ "$n" -ge 10 ] + do + docker exec pg pg_isready -h localhost && break + n=$((n+1)) + echo waiting for postgres to be ready... + sleep 1 + done + docker exec pg bash /init/hba.sh + n=0 + until [ "$n" -ge 10 ] + do + docker exec pg su postgres -c '/usr/lib/postgresql/${{ matrix.postgres }}/bin/pg_ctl reload' && break + n=$((n+1)) + echo waiting for postgres to reload... + sleep 1 + done + + - name: setup hosts + run: echo '127.0.0.1 postgres' | sudo tee -a /etc/hosts + + - name: create db/roles + run: | + n=0 + until [ "$n" -ge 10 ] + do + docker exec pg pg_isready -h localhost && break + n=$((n+1)) + echo waiting for postgres to be ready... + sleep 1 + done + docker exec pg createdb -h localhost -U postgres pqgotest + docker exec pg createuser -h localhost -U postgres -DRS pqgossltest + docker exec pg createuser -h localhost -U postgres -DRS pqgosslcert + + - name: check out code into the Go module directory + uses: actions/checkout@v2 + + - name: set up go + uses: actions/setup-go@v2 + with: + go-version: ${{ matrix.go }} + id: go + + - name: set key perms + run: sudo chmod 600 certs/postgresql.key + + - name: run tests + env: + PGUSER: postgres + PGHOST: localhost + PGPORT: 5432 + PQGOSSLTESTS: 1 + PQSSLCERTTEST_PATH: certs + run: | + PQTEST_BINARY_PARAMETERS=no go test -race -v ./... + PQTEST_BINARY_PARAMETERS=yes go test -race -v ./... + + - name: install goimports + run: go get golang.org/x/tools/cmd/goimports + + - name: install staticcheck + run: | + wget https://github.com/dominikh/go-tools/releases/latest/download/staticcheck_linux_amd64.tar.gz -O - | tar -xz staticcheck + + - name: run goimports + run: | + goimports -d -e . | awk '{ print } END { exit NR == 0 ? 0 : 1 }' + + - name: run staticcheck + run: ./staticcheck/staticcheck -go 1.13 ./... + + - name: build + run: go build -v . diff --git a/Makefile b/Makefile new file mode 100644 index 000000000..212b19039 --- /dev/null +++ b/Makefile @@ -0,0 +1,36 @@ +.PHONY: all root-ssl server-ssl client-ssl + +all: root-ssl server-ssl client-ssl + rm -f .srl + +root-ssl: + openssl req -new -sha256 -nodes -newkey rsa:2048 \ + -config ./certs/root.cnf \ + -keyout /tmp/root.key \ + -out /tmp/root.csr + openssl x509 -req -days 3653 -sha256 \ + -in /tmp/root.csr \ + -extfile /etc/ssl/openssl.cnf -extensions v3_ca \ + -signkey /tmp/root.key \ + -out ./certs/root.crt + +server-ssl: + openssl req -new -sha256 -nodes -newkey rsa:2048 \ + -config ./certs/server.cnf \ + -keyout ./certs/server.key \ + -out /tmp/server.csr + openssl x509 -req -days 3653 -sha256 \ + -extfile ./certs/server.cnf -extensions req_ext \ + -CA ./certs/root.crt -CAkey /tmp/root.key -CAcreateserial \ + -in /tmp/server.csr \ + -out ./certs/server.crt + +client-ssl: + openssl req -new -sha256 -nodes -newkey rsa:2048 \ + -config ./certs/postgresql.cnf \ + -keyout ./certs/postgresql.key \ + -out /tmp/postgresql.csr + openssl x509 -req -days 3653 -sha256 \ + -CA ./certs/root.crt -CAkey /tmp/root.key -CAcreateserial \ + -in /tmp/postgresql.csr \ + -out ./certs/postgresql.crt diff --git a/auth/kerberos/krb_unix.go b/auth/kerberos/krb_unix.go index 7d5ec76a5..c625a2105 100644 --- a/auth/kerberos/krb_unix.go +++ b/auth/kerberos/krb_unix.go @@ -1,4 +1,4 @@ -// +build !windows +//+build !windows package kerberos diff --git a/auth/kerberos/krb_windows.go b/auth/kerberos/krb_windows.go index 474517aea..348f64e99 100644 --- a/auth/kerberos/krb_windows.go +++ b/auth/kerberos/krb_windows.go @@ -1,4 +1,4 @@ -// +build windows +//+build windows package kerberos diff --git a/certs/postgresql.cnf b/certs/postgresql.cnf new file mode 100644 index 000000000..fa8ffc489 --- /dev/null +++ b/certs/postgresql.cnf @@ -0,0 +1,10 @@ +[req] +distinguished_name = req_distinguished_name +prompt = no + +[req_distinguished_name] +C = US +ST = Nevada +L = Las Vegas +O = github.com/lib/pq +CN = pqgosslcert diff --git a/certs/postgresql.crt b/certs/postgresql.crt index 6e6b4284a..c1815f865 100644 --- a/certs/postgresql.crt +++ b/certs/postgresql.crt @@ -1,69 +1,20 @@ -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= +MIIDPjCCAiYCCQD4nsC6zsmIqjANBgkqhkiG9w0BAQsFADBeMQswCQYDVQQGEwJV +UzEPMA0GA1UECAwGTmV2YWRhMRIwEAYDVQQHDAlMYXMgVmVnYXMxGjAYBgNVBAoM +EWdpdGh1Yi5jb20vbGliL3BxMQ4wDAYDVQQDDAVwcSBDQTAeFw0yMTA5MDIwMTU1 +MDJaFw0zMTA5MDMwMTU1MDJaMGQxCzAJBgNVBAYTAlVTMQ8wDQYDVQQIDAZOZXZh +ZGExEjAQBgNVBAcMCUxhcyBWZWdhczEaMBgGA1UECgwRZ2l0aHViLmNvbS9saWIv +cHExFDASBgNVBAMMC3BxZ29zc2xjZXJ0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A +MIIBCgKCAQEAx0ucPVUNCrVmbyithwWrmmZ1dGudBwhSyDB6af4z5Cr+S6dx2SRU +UGUw3Lv+z+tUqQ7hJj0oNddIQeYKl/Tt6JPpZsQfERP/cUGedtyt7HnCKobBL+0B +NvHnDIUiIL4LgfiZK4DWJkGmm7nTHo/7qKAw60vCMLUW98DC0Xhlk9MHYG+e9Zai +3G0vY2X6DUYcSmzBI3JakFEgMZTQg3ofUQMz8TYeK3/DYadLXkl08d18LL3Dnefx +0xRuBPNTa2tLfVnFkfFi6Z9xVB/WhG6+X4OLnO85v5xUOGTV+g154iR7FOkrrl5F +lEUBj+yaIoTRi+MyZ/oYqWwQUDYS3+Te9wIDAQABMA0GCSqGSIb3DQEBCwUAA4IB +AQCCJpwUWCx7xfXv3vH3LQcffZycyRHYPgTCbiQw3x9aBb77jUAh5O6lEj/W0nx2 +SCTEsCsRSAiFwfUb+g/AFCW84dELRWmf38eoqACebLymqnvxyZA+O87yu07XyFZR +TnmbDMzZgsyWWGwS3JoGFk+ibWY4AImYQnSJO8Pi0kZ37ngbAyJ3RtDhhEQJWw/Q +D04p3uky/ea7Gyz0QTx5o40n4gq7nEzF1OS6IHozM840J5aZrxRiXEa56fsmJHmI +IGyI07SGlWJ15r1wc8lB+8ilnAqH1QQlYzTIW0Q4NZE7n3uQg1EVuueGiGO2ex2/ +he9lDiJfOQuPuLbOxzctP9v9 -----END CERTIFICATE----- diff --git a/certs/postgresql.key b/certs/postgresql.key index eb8b20be9..8380da4bc 100644 --- a/certs/postgresql.key +++ b/certs/postgresql.key @@ -1,15 +1,28 @@ ------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----- +-----BEGIN PRIVATE KEY----- +MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQDHS5w9VQ0KtWZv +KK2HBauaZnV0a50HCFLIMHpp/jPkKv5Lp3HZJFRQZTDcu/7P61SpDuEmPSg110hB +5gqX9O3ok+lmxB8RE/9xQZ523K3secIqhsEv7QE28ecMhSIgvguB+JkrgNYmQaab +udMej/uooDDrS8IwtRb3wMLReGWT0wdgb571lqLcbS9jZfoNRhxKbMEjclqQUSAx +lNCDeh9RAzPxNh4rf8Nhp0teSXTx3XwsvcOd5/HTFG4E81Nra0t9WcWR8WLpn3FU +H9aEbr5fg4uc7zm/nFQ4ZNX6DXniJHsU6SuuXkWURQGP7JoihNGL4zJn+hipbBBQ +NhLf5N73AgMBAAECggEAHLNY1sRO0oH5NHzpMI6yfdPPimqM/JxIP6grmOQQ2QUQ +BhkhHiJLOiC4frFcKtk7IfWQmw8noUlVkJfuYp/VOy9B55jK2IzGtqq6hWeWbH3E +Zpdtbtd021LO8VCi75Au3BLPDCLLtEq0Ea0bKEWX+lrHcLtCRf1uR1OtOrlZ94Wl +DUhm7YJC4cS1bi6Kdf03R+fw2oFi7/QdywcT4ow032jGWOly/Jl7bSHZK7xLtM/i +9HfMwmusD/iuz7mtLU7VCpnlKZm6MfS5D427ybW8MruuiZEtQJ6QtRIrHBHk93aK +Op0tjJ6tMav1UsJzgVz9+uWILE9l0AjAa4AvbfNzEQKBgQD8mma9SLQPtBb6cXuT +CQgjE4vyph8mRnm/pTz3QLIpMiLy2+aKJD/u4cduzLw1vjuH1tlb7NQ9c891jAJh +JhwDwqKAXfFicfRs/PYWngx/XtGhbbpgm1yA6XuYL1D06gzmjzXgHvZMOFcts+GF +y0JEuV7v6eYrpQJRQYCwY6xTgwKBgQDJ+bHAlgOaC94DZEXZMiUznCCjBjAstiXG +BEN7Cnfn6vgvPm/b6BkKn4VrsCmbZQKT7QJDSOhYwXCC2ZlrKiF8GEUHX4mi8347 +8B+DsuokTLNmN61QAZbb1c3XQVnr15xH8ijm7yYs4tCBmVLKBmpw1T4IZXXlVE5k +gmee+AwIfQKBgGr+P0wnclVAc4cq8CusZKzux5VEtebxbPo21CbqWUxHtzPk3rZe +elIFggK1Z3bgF7kG0NQ18QQCfLoOTqe1i6IwG8KBiA+pst1DHD0iPqroj6RvpMTs +qXbU7ovcZs8GH+a8fBZtJufL6WkrSvfvyybu2X6HNP4Bi4S9WPPdlA1fAoGAE5m/ +vkjQoKp2KS4Z+TH8mj2UjT2Uf0JN+CGByvcBG+iZnTwZ7uVfSMCiWgkGgKYU0fY2 +OgFhSvu6x3gGg3fbOAfC6yxCVyX6IibzZ/x87HjlEA5nK1R8J2lgSHt3FoQeDn1Z +qs+ajNCWG32doy1sNvb6xiXSgybjVK2zEKJRyKECgYBJTk2IABebjvInNb6tagcI +nD4d2LgBmZJZsTruHXrpO0s3XCQcFKks4JKH1CVjd34f7LkxzEOGbE7wKBBd652s +ob6gFKnbqTniTo3NRUycB6ymo4LSaBvKgeY5hYbVxrYheRLPGY+gPVYb3VMKu9N9 +76rcaFqJOz7OeywRG5bHUg== +-----END PRIVATE KEY----- diff --git a/certs/root.cnf b/certs/root.cnf new file mode 100644 index 000000000..ea6def267 --- /dev/null +++ b/certs/root.cnf @@ -0,0 +1,10 @@ +[req] +distinguished_name = req_distinguished_name +prompt = no + +[req_distinguished_name] +C = US +ST = Nevada +L = Las Vegas +O = github.com/lib/pq +CN = pq CA diff --git a/certs/root.crt b/certs/root.crt index aecf8f621..390a907c3 100644 --- a/certs/root.crt +++ b/certs/root.crt @@ -1,24 +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= +MIIEBjCCAu6gAwIBAgIJAPizR+OD14YnMA0GCSqGSIb3DQEBCwUAMF4xCzAJBgNV +BAYTAlVTMQ8wDQYDVQQIDAZOZXZhZGExEjAQBgNVBAcMCUxhcyBWZWdhczEaMBgG +A1UECgwRZ2l0aHViLmNvbS9saWIvcHExDjAMBgNVBAMMBXBxIENBMB4XDTIxMDkw +MjAxNTUwMloXDTMxMDkwMzAxNTUwMlowXjELMAkGA1UEBhMCVVMxDzANBgNVBAgM +Bk5ldmFkYTESMBAGA1UEBwwJTGFzIFZlZ2FzMRowGAYDVQQKDBFnaXRodWIuY29t +L2xpYi9wcTEOMAwGA1UEAwwFcHEgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw +ggEKAoIBAQDb9d6sjdU6GdibGrXRMOHREH3MRUS8T4TFqGgPEGVDP/V5bAZlBSGP +AN0o9DTyVLcbQpBt8zMTw9KeIzIIe5NIVkSmA16lw/YckGhOM+kZIkiDuE6qt5Ia +OQCRMdXkZ8ejG/JUu+rHU8FJZL8DE+jyYherzdjkeVAQ7JfzxAwW2Dl7T/47g337 +Pwmf17AEb8ibSqmXyUN7R5NhJQs+hvaYdNagzdx91E1H+qlyBvmiNeasUQljLvZ+ +Y8wAuU79neA+d09O4PBiYwV17rSP6SZCeGE3oLZviL/0KM9Xig88oB+2FmvQ6Zxa +L7SoBlqS+5pBZwpH7eee/wCIKAnJtMAJAgMBAAGjgcYwgcMwDwYDVR0TAQH/BAUw +AwEB/zAdBgNVHQ4EFgQUfIXEczahbcM2cFrwclJF7GbdajkwgZAGA1UdIwSBiDCB +hYAUfIXEczahbcM2cFrwclJF7GbdajmhYqRgMF4xCzAJBgNVBAYTAlVTMQ8wDQYD +VQQIDAZOZXZhZGExEjAQBgNVBAcMCUxhcyBWZWdhczEaMBgGA1UECgwRZ2l0aHVi +LmNvbS9saWIvcHExDjAMBgNVBAMMBXBxIENBggkA+LNH44PXhicwDQYJKoZIhvcN +AQELBQADggEBABFyGgSz2mHVJqYgX1Y+7P+MfKt83cV2uYDGYvXrLG2OGiCilVul +oTBG+8omIMSHOsQZvWMpA5H0tnnlQHrKpKpUyKkSL+Wv5GL0UtBmHX7mVRiaK2l4 +q2BjRaQUitp/FH4NSdXtVrMME5T1JBBZHsQkNL3cNRzRKwY/Vj5UGEDxDS7lILUC +e01L4oaK0iKQn4beALU+TvKoAHdPvoxpPpnhkF5ss9HmdcvRktJrKZemDJZswZ7/ ++omx8ZPIYYUH5VJJYYE88S7guAt+ZaKIUlel/t6xPbo2ZySFSg9u1uB99n+jTo3L +1rAxFnN3FCX2jBqgP29xMVmisaN5k04UmyI= -----END CERTIFICATE----- diff --git a/certs/server.cnf b/certs/server.cnf new file mode 100644 index 000000000..ba4b55703 --- /dev/null +++ b/certs/server.cnf @@ -0,0 +1,29 @@ +[ req ] +default_bits = 2048 +distinguished_name = subject +req_extensions = req_ext +x509_extensions = x509_ext +string_mask = utf8only +prompt = no + +[ subject ] +C = US +ST = Nevada +L = Las Vegas +O = github.com/lib/pq + +[ x509_ext ] +subjectKeyIdentifier = hash +authorityKeyIdentifier = keyid,issuer + +basicConstraints = CA:FALSE +keyUsage = digitalSignature, keyEncipherment +subjectAltName = DNS:postgres +nsComment = "OpenSSL Generated Certificate" + +[ req_ext ] +subjectKeyIdentifier = hash +basicConstraints = CA:FALSE +keyUsage = digitalSignature, keyEncipherment +subjectAltName = DNS:postgres +nsComment = "OpenSSL Generated Certificate" diff --git a/certs/server.crt b/certs/server.crt index ddc995a6d..1a0ac0d43 100644 --- a/certs/server.crt +++ b/certs/server.crt @@ -1,81 +1,22 @@ -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= +MIIDqzCCApOgAwIBAgIJAPiewLrOyYipMA0GCSqGSIb3DQEBCwUAMF4xCzAJBgNV +BAYTAlVTMQ8wDQYDVQQIDAZOZXZhZGExEjAQBgNVBAcMCUxhcyBWZWdhczEaMBgG +A1UECgwRZ2l0aHViLmNvbS9saWIvcHExDjAMBgNVBAMMBXBxIENBMB4XDTIxMDkw +MjAxNTUwMloXDTMxMDkwMzAxNTUwMlowTjELMAkGA1UEBhMCVVMxDzANBgNVBAgM +Bk5ldmFkYTESMBAGA1UEBwwJTGFzIFZlZ2FzMRowGAYDVQQKDBFnaXRodWIuY29t +L2xpYi9wcTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKf6H4UzmANN +QiQJe92Mf3ETMYmpZKNNO9DPEHyNLIkag+XwMrBTdcCK0mLvsNCYpXuBN6703KCd +WAFOeMmj7gOsWtvjt5Xm6bRHLgegekXzcG/jDwq/wyzeDzr/YkITuIlG44Lf9lhY +FLwiHlHOWHnwrZaEh6aU//02aQkzyX5INeXl/3TZm2G2eIH6AOxOKOU27MUsyVSQ +5DE+SDKGcRP4bElueeQWvxAXNMZYb7sVSDdfHI3zr32K4k/tC8x0fZJ5XN/dvl4t +4N4MrYlmDO5XOrb/gQH1H4iu6+5EMDfZYab4fkThnNFdfFqu4/8Scv7KZ8mWqpKM +fGAjEPctQi0CAwEAAaN8MHowHQYDVR0OBBYEFENExPbmDyFB2AJUdbMvVyhlNPD5 +MAkGA1UdEwQCMAAwCwYDVR0PBAQDAgWgMBMGA1UdEQQMMAqCCHBvc3RncmVzMCwG +CWCGSAGG+EIBDQQfFh1PcGVuU1NMIEdlbmVyYXRlZCBDZXJ0aWZpY2F0ZTANBgkq +hkiG9w0BAQsFAAOCAQEAMRVbV8RiEsmp9HAtnVCZmRXMIbgPGrqjeSwk586s4K8v +BSqNCqxv6s5GfCRmDYiqSqeuCVDtUJS1HsTmbxVV7Ke71WMo+xHR1ICGKOa8WGCb +TGsuicG5QZXWaxeMOg4s0qpKmKko0d1aErdVsanU5dkrVS7D6729Ffnzu4lwApk6 +invAB67p8u7sojwqRq5ce0vRaG+YFylTrWomF9kauEb8gKbQ9Xc7QfX+h+UH/mq9 +Nvdj8LOHp6/82bZdnsYUOtV4lS1IA/qzeXpqBphxqfWabD1yLtkyJyImZKq8uIPp +0CG4jhObPdWcCkXD6bg3QK3mhwlC79OtFgxWmldCRQ== -----END CERTIFICATE----- diff --git a/certs/server.key b/certs/server.key index bd7b019b6..152e0f23e 100644 --- a/certs/server.key +++ b/certs/server.key @@ -1,27 +1,28 @@ ------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----- +-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCn+h+FM5gDTUIk +CXvdjH9xEzGJqWSjTTvQzxB8jSyJGoPl8DKwU3XAitJi77DQmKV7gTeu9NygnVgB +TnjJo+4DrFrb47eV5um0Ry4HoHpF83Bv4w8Kv8Ms3g86/2JCE7iJRuOC3/ZYWBS8 +Ih5Rzlh58K2WhIemlP/9NmkJM8l+SDXl5f902ZthtniB+gDsTijlNuzFLMlUkOQx +PkgyhnET+GxJbnnkFr8QFzTGWG+7FUg3XxyN8699iuJP7QvMdH2SeVzf3b5eLeDe +DK2JZgzuVzq2/4EB9R+IruvuRDA32WGm+H5E4ZzRXXxaruP/EnL+ymfJlqqSjHxg +IxD3LUItAgMBAAECggEAOE2naQ9tIZYw2EFxikZApVcooJrtx6ropMnzHbx4NBB2 +K4mChAXFj184u77ZxmGT/jzGvFcI6LE0wWNbK0NOUV7hKZk/fPhkV3AQZrAMrAu4 +IVi7PwAd3JkmA8F8XuebUDA5rDGDsgL8GD9baFJA58abeLs9eMGyuF4XgOUh4bip +hgHa76O2rcDWNY5HZqqRslw75FzlYkB0PCts/UJxSswj70kTTihyOhDlrm2TnyxI +ne54UbGRrpfs9wiheSGLjDG81qZToBHQDwoAnjjZhu1VCaBISuGbgZrxyyRyqdnn +xPW+KczMv04XyvF7v6Pz+bUEppalLXGiXnH5UtWvZQKBgQDTPCdMpNE/hwlq4nAw +Kf42zIBWfbnMLVWYoeDiAOhtl9XAUAXn76xe6Rvo0qeAo67yejdbJfRq3HvGyw+q +4PS8r9gXYmLYIPQxSoLL5+rFoBCN3qFippfjLB1j32mp7+15KjRj8FF2r6xIN8fu +XatSRsaqmvCWYLDRv/rbHnxwkwKBgQDLkyfFLF7BtwtPWKdqrwOM7ip1UKh+oDBS +vkCQ08aEFRBU7T3jChsx5GbaW6zmsSBwBwcrHclpSkz7n3aq19DDWObJR2p80Fma +rsXeIcvtEpkvT3pVX268P5d+XGs1kxgFunqTysG9yChW+xzcs5MdKBzuMPPn7rL8 +MKAzdar6PwKBgEypkzW8x3h/4Moa3k6MnwdyVs2NGaZheaRIc95yJ+jGZzxBjrMr +h+p2PbvU4BfO0AqOkpKRBtDVrlJqlggVVp04UHvEKE16QEW3Xhr0037f5cInX3j3 +Lz6yXwRFLAsR2aTUzWjL6jTh8uvO2s/GzQuyRh3a16Ar/WBShY+K0+zjAoGATnLT +xZjWnyHRmu8X/PWakamJ9RFzDPDgDlLAgM8LVgTj+UY/LgnL9wsEU6s2UuP5ExKy +QXxGDGwUhHar/SQTj+Pnc7Mwpw6HKSOmnnY5po8fNusSwml3O9XppEkrC0c236Y/ +7EobJO5IFVTJh4cv7vFxTJzSsRL8KFD4uzvh+nMCgYEAqY8NBYtIgNJA2B6C6hHF ++bG7v46434ZHFfGTmMQwzE4taVg7YRnzYESAlvK4bAP5ZXR90n7GRGFhrXzoMZ38 +r0bw/q9rV+ReGda7/Bjf7ciCKiq0RODcHtf4IaskjPXCoQRGJtgCPLhWPfld6g9v +/HTvO96xv9e3eG/PKSPog94= +-----END PRIVATE KEY----- From b81abded1992fa15f63214b13e9dea225b9155d0 Mon Sep 17 00:00:00 2001 From: Michael Hobbs Date: Wed, 1 Sep 2021 19:14:16 -0700 Subject: [PATCH 35/65] update goimports formatting for go1.17 --- auth/kerberos/krb_unix.go | 3 ++- auth/kerberos/krb_windows.go | 3 ++- connector_example_test.go | 1 + connector_test.go | 1 + go19_test.go | 1 + notice.go | 1 + notice_example_test.go | 1 + notice_test.go | 1 + oid/gen.go | 1 + ssl_permissions.go | 1 + ssl_windows.go | 1 + user_other.go | 1 + user_posix.go | 1 + 13 files changed, 15 insertions(+), 2 deletions(-) diff --git a/auth/kerberos/krb_unix.go b/auth/kerberos/krb_unix.go index c625a2105..b6f27ce57 100644 --- a/auth/kerberos/krb_unix.go +++ b/auth/kerberos/krb_unix.go @@ -1,4 +1,5 @@ -//+build !windows +//go:build !windows +// +build !windows package kerberos diff --git a/auth/kerberos/krb_windows.go b/auth/kerberos/krb_windows.go index 348f64e99..ca26d02c6 100644 --- a/auth/kerberos/krb_windows.go +++ b/auth/kerberos/krb_windows.go @@ -1,4 +1,5 @@ -//+build windows +//go:build windows +// +build windows package kerberos diff --git a/connector_example_test.go b/connector_example_test.go index 9401fa0d4..dbae4db52 100644 --- a/connector_example_test.go +++ b/connector_example_test.go @@ -1,3 +1,4 @@ +//go:build go1.10 // +build go1.10 package pq_test diff --git a/connector_test.go b/connector_test.go index 3d2c67b06..d68810e9e 100644 --- a/connector_test.go +++ b/connector_test.go @@ -1,3 +1,4 @@ +//go:build go1.10 // +build go1.10 package pq diff --git a/go19_test.go b/go19_test.go index 25566d6f8..f810e4890 100644 --- a/go19_test.go +++ b/go19_test.go @@ -1,3 +1,4 @@ +//go:build go1.9 // +build go1.9 package pq diff --git a/notice.go b/notice.go index 01dd8c723..70ad122a7 100644 --- a/notice.go +++ b/notice.go @@ -1,3 +1,4 @@ +//go:build go1.10 // +build go1.10 package pq diff --git a/notice_example_test.go b/notice_example_test.go index 9392ad0bb..04bcb1d9d 100644 --- a/notice_example_test.go +++ b/notice_example_test.go @@ -1,3 +1,4 @@ +//go:build go1.10 // +build go1.10 package pq_test diff --git a/notice_test.go b/notice_test.go index 92212e778..e9da9af3a 100644 --- a/notice_test.go +++ b/notice_test.go @@ -1,3 +1,4 @@ +//go:build go1.10 // +build go1.10 package pq diff --git a/oid/gen.go b/oid/gen.go index 7c634cdc5..29a8de8ba 100644 --- a/oid/gen.go +++ b/oid/gen.go @@ -1,3 +1,4 @@ +//go:build ignore // +build ignore // Generate the table of OID values diff --git a/ssl_permissions.go b/ssl_permissions.go index 3b7c3a2a3..014af6a16 100644 --- a/ssl_permissions.go +++ b/ssl_permissions.go @@ -1,3 +1,4 @@ +//go:build !windows // +build !windows package pq diff --git a/ssl_windows.go b/ssl_windows.go index 5d2c763ce..73663c8f1 100644 --- a/ssl_windows.go +++ b/ssl_windows.go @@ -1,3 +1,4 @@ +//go:build windows // +build windows package pq diff --git a/user_other.go b/user_other.go index 85f70ba70..3dae8f557 100644 --- a/user_other.go +++ b/user_other.go @@ -1,5 +1,6 @@ // Package pq is a pure Go Postgres driver for the database/sql package. +//go:build js || android || hurd || zos // +build js android hurd zos package pq diff --git a/user_posix.go b/user_posix.go index 22299322a..227a948e0 100644 --- a/user_posix.go +++ b/user_posix.go @@ -1,5 +1,6 @@ // Package pq is a pure Go Postgres driver for the database/sql package. +//go:build aix || darwin || dragonfly || freebsd || linux || nacl || netbsd || openbsd || plan9 || solaris || rumprun || illumos // +build aix darwin dragonfly freebsd linux nacl netbsd openbsd plan9 solaris rumprun illumos package pq From 7da16d94c4bd3467b71dcddfc6644a1678013826 Mon Sep 17 00:00:00 2001 From: Michael Hobbs Date: Wed, 1 Sep 2021 19:34:21 -0700 Subject: [PATCH 36/65] mv certs Makefile to certs dir and add explanation --- Makefile => certs/Makefile | 1 + 1 file changed, 1 insertion(+) rename Makefile => certs/Makefile (93%) diff --git a/Makefile b/certs/Makefile similarity index 93% rename from Makefile rename to certs/Makefile index 212b19039..a84e31e9c 100644 --- a/Makefile +++ b/certs/Makefile @@ -1,5 +1,6 @@ .PHONY: all root-ssl server-ssl client-ssl +# Rebuilds self-signed root/server/client certs/keys in a consistent way all: root-ssl server-ssl client-ssl rm -f .srl From e10fdd5eda0f6415113697029a355306d9ee9fab Mon Sep 17 00:00:00 2001 From: Michael Hobbs Date: Wed, 1 Sep 2021 19:41:40 -0700 Subject: [PATCH 37/65] remove travis ci artifacts --- .travis.sh | 71 ----------------------------------------------------- .travis.yml | 45 --------------------------------- 2 files changed, 116 deletions(-) delete mode 100755 .travis.sh delete mode 100644 .travis.yml diff --git a/.travis.sh b/.travis.sh deleted file mode 100755 index 15607b50d..000000000 --- a/.travis.sh +++ /dev/null @@ -1,71 +0,0 @@ -#!/bin/bash - -set -eux - -client_configure() { - sudo chmod 600 $PQSSLCERTTEST_PATH/postgresql.key -} - -pgdg_repository() { - curl -sS 'https://www.postgresql.org/media/keys/ACCC4CF8.asc' | sudo apt-key add - - echo "deb http://apt.postgresql.org/pub/repos/apt/ `lsb_release -cs`-pgdg main" | sudo tee /etc/apt/sources.list.d/pgdg.list - sudo apt-get update -} - -postgresql_configure() { - sudo tee /etc/postgresql/$PGVERSION/main/pg_hba.conf > /dev/null <<-config - local all all trust - hostnossl all pqgossltest 127.0.0.1/32 reject - hostnossl all pqgosslcert 127.0.0.1/32 reject - hostssl all pqgossltest 127.0.0.1/32 trust - hostssl all pqgosslcert 127.0.0.1/32 cert - host all all 127.0.0.1/32 trust - hostnossl all pqgossltest ::1/128 reject - hostnossl all pqgosslcert ::1/128 reject - hostssl all pqgossltest ::1/128 trust - hostssl all pqgosslcert ::1/128 cert - host all all ::1/128 trust - config - - xargs sudo install -o postgres -g postgres -m 600 -t /var/lib/postgresql/$PGVERSION/main/ <<-certificates - certs/root.crt - certs/server.crt - certs/server.key - certificates - - sort -VCu <<-versions || - $PGVERSION - 9.2 - versions - sudo tee -a /etc/postgresql/$PGVERSION/main/postgresql.conf > /dev/null <<-config - ssl_ca_file = 'root.crt' - ssl_cert_file = 'server.crt' - ssl_key_file = 'server.key' - config - - echo 127.0.0.1 postgres | sudo tee -a /etc/hosts > /dev/null - - sudo service postgresql restart -} - -postgresql_install() { - xargs sudo apt-get -y install <<-packages - postgresql-$PGVERSION - postgresql-client-$PGVERSION - postgresql-server-dev-$PGVERSION - packages -} - -postgresql_uninstall() { - sudo service postgresql stop - xargs sudo apt-get -y --purge remove <<-packages - libpq-dev - libpq5 - postgresql - postgresql-client-common - postgresql-common - packages - sudo rm -rf /var/lib/postgresql -} - -$1 diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 283f35f23..000000000 --- a/.travis.yml +++ /dev/null @@ -1,45 +0,0 @@ -language: go - -go: - - 1.14.x - - 1.15.x - - 1.16.x - -sudo: true - -env: - global: - - PGUSER=postgres - - PQGOSSLTESTS=1 - - PQSSLCERTTEST_PATH=$PWD/certs - - PGHOST=127.0.0.1 - - GODEBUG=x509ignoreCN=0 - matrix: - - PGVERSION=10 - - PGVERSION=9.6 - - PGVERSION=9.5 - - PGVERSION=9.4 - -before_install: - - ./.travis.sh postgresql_uninstall - - ./.travis.sh pgdg_repository - - ./.travis.sh postgresql_install - - ./.travis.sh postgresql_configure - - ./.travis.sh client_configure - - go get golang.org/x/tools/cmd/goimports - - go get golang.org/x/lint/golint - - GO111MODULE=on go get honnef.co/go/tools/cmd/staticcheck@2020.1.3 - -before_script: - - createdb pqgotest - - createuser -DRS pqgossltest - - createuser -DRS pqgosslcert - -script: - - > - goimports -d -e $(find -name '*.go') | awk '{ print } END { exit NR == 0 ? 0 : 1 }' - - go vet ./... - - staticcheck -go 1.13 ./... - - golint ./... - - PQTEST_BINARY_PARAMETERS=no go test -race -v ./... - - PQTEST_BINARY_PARAMETERS=yes go test -race -v ./... From 9fa33e2d5ae45e59beb296914e7b6939e8c8dc68 Mon Sep 17 00:00:00 2001 From: Michael Hobbs Date: Wed, 1 Sep 2021 16:16:18 -0700 Subject: [PATCH 38/65] implement ConnPrepareContext/StmtQueryContext/StmtExecContext interfaces --- conn.go | 4 ++ conn_go18.go | 79 +++++++++++++++++++++++- conn_test.go | 164 +++++++++++++++++++++++++++++++++++++++++++++++++ issues_test.go | 36 ++++++++++- 4 files changed, 281 insertions(+), 2 deletions(-) diff --git a/conn.go b/conn.go index b09a17047..8e445f32c 100644 --- a/conn.go +++ b/conn.go @@ -1360,6 +1360,10 @@ func (st *stmt) Close() (err error) { } func (st *stmt) Query(v []driver.Value) (r driver.Rows, err error) { + return st.query(v) +} + +func (st *stmt) query(v []driver.Value) (r *rows, err error) { if st.cn.getBad() { return nil, driver.ErrBadConn } diff --git a/conn_go18.go b/conn_go18.go index 2b9a9599e..3c83082b3 100644 --- a/conn_go18.go +++ b/conn_go18.go @@ -11,6 +11,10 @@ import ( "time" ) +const ( + watchCancelDialContextTimeout = time.Second * 10 +) + // Implement the "QueryerContext" interface func (cn *conn) QueryContext(ctx context.Context, query string, args []driver.NamedValue) (driver.Rows, error) { list := make([]driver.Value, len(args)) @@ -43,6 +47,14 @@ func (cn *conn) ExecContext(ctx context.Context, query string, args []driver.Nam return cn.Exec(query, list) } +// Implement the "ConnPrepareContext" interface +func (cn *conn) PrepareContext(ctx context.Context, query string) (driver.Stmt, error) { + if finish := cn.watchCancel(ctx); finish != nil { + defer finish() + } + return cn.Prepare(query) +} + // Implement the "ConnBeginTx" interface func (cn *conn) BeginTx(ctx context.Context, opts driver.TxOptions) (driver.Tx, error) { var mode string @@ -109,7 +121,7 @@ func (cn *conn) watchCancel(ctx context.Context) func() { // so it must not be used for the additional network // request to cancel the query. // Create a new context to pass into the dial. - ctxCancel, cancel := context.WithTimeout(context.Background(), time.Second*10) + ctxCancel, cancel := context.WithTimeout(context.Background(), watchCancelDialContextTimeout) defer cancel() _ = cn.cancel(ctxCancel) @@ -172,3 +184,68 @@ func (cn *conn) cancel(ctx context.Context) error { return err } } + +// Implement the "StmtQueryContext" interface +func (st *stmt) QueryContext(ctx context.Context, args []driver.NamedValue) (driver.Rows, error) { + list := make([]driver.Value, len(args)) + for i, nv := range args { + list[i] = nv.Value + } + finish := st.watchCancel(ctx) + r, err := st.query(list) + if err != nil { + if finish != nil { + finish() + } + return nil, err + } + r.finish = finish + return r, nil +} + +// Implement the "StmtExecContext" interface +func (st *stmt) ExecContext(ctx context.Context, args []driver.NamedValue) (driver.Result, error) { + list := make([]driver.Value, len(args)) + for i, nv := range args { + list[i] = nv.Value + } + + if finish := st.watchCancel(ctx); finish != nil { + defer finish() + } + + return st.Exec(list) +} + +// watchCancel is implemented on stmt in order to not mark the parent conn as bad +func (st *stmt) watchCancel(ctx context.Context) func() { + if done := ctx.Done(); done != nil { + finished := make(chan struct{}) + go func() { + select { + case <-done: + // At this point the function level context is canceled, + // so it must not be used for the additional network + // request to cancel the query. + // Create a new context to pass into the dial. + ctxCancel, cancel := context.WithTimeout(context.Background(), watchCancelDialContextTimeout) + defer cancel() + + _ = st.cancel(ctxCancel) + finished <- struct{}{} + case <-finished: + } + }() + return func() { + select { + case <-finished: + case finished <- struct{}{}: + } + } + } + return nil +} + +func (st *stmt) cancel(ctx context.Context) error { + return st.cn.cancel(ctx) +} diff --git a/conn_test.go b/conn_test.go index a05d81d0b..4ac3d2b82 100644 --- a/conn_test.go +++ b/conn_test.go @@ -1806,3 +1806,167 @@ func TestCopyInStmtAffectedRows(t *testing.T) { res.RowsAffected() res.LastInsertId() } + +func TestConnPrepareContext(t *testing.T) { + db := openTestConn(t) + defer db.Close() + + tests := []struct { + name string + ctx func() (context.Context, context.CancelFunc) + sql string + err error + }{ + { + name: "context.Background", + ctx: func() (context.Context, context.CancelFunc) { + return context.Background(), nil + }, + sql: "SELECT 1", + err: nil, + }, + { + name: "context.WithTimeout exceeded", + ctx: func() (context.Context, context.CancelFunc) { + return context.WithTimeout(context.Background(), time.Microsecond) + }, + sql: "SELECT 1", + err: context.DeadlineExceeded, + }, + { + name: "context.WithTimeout", + ctx: func() (context.Context, context.CancelFunc) { + return context.WithTimeout(context.Background(), time.Minute) + }, + sql: "SELECT 1", + err: nil, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ctx, cancel := tt.ctx() + if cancel != nil { + defer cancel() + } + _, err := db.PrepareContext(ctx, tt.sql) + switch { + case (err != nil) != (tt.err != nil): + t.Fatalf("conn.PrepareContext() unexpected nil err got = %v, expected = %v", err, tt.err) + case (err != nil && tt.err != nil) && (err.Error() != tt.err.Error()): + t.Errorf("conn.PrepareContext() got = %v, expected = %v", err.Error(), tt.err.Error()) + } + }) + } +} + +func TestStmtQueryContext(t *testing.T) { + db := openTestConn(t) + defer db.Close() + + tests := []struct { + name string + ctx func() (context.Context, context.CancelFunc) + sql string + err error + }{ + { + name: "context.Background", + ctx: func() (context.Context, context.CancelFunc) { + return context.Background(), nil + }, + sql: "SELECT pg_sleep(1);", + err: nil, + }, + { + name: "context.WithTimeout exceeded", + ctx: func() (context.Context, context.CancelFunc) { + return context.WithTimeout(context.Background(), 1*time.Second) + }, + sql: "SELECT pg_sleep(10);", + err: &Error{Message: "canceling statement due to user request"}, + }, + { + name: "context.WithTimeout", + ctx: func() (context.Context, context.CancelFunc) { + return context.WithTimeout(context.Background(), time.Minute) + }, + sql: "SELECT pg_sleep(1);", + err: nil, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ctx, cancel := tt.ctx() + if cancel != nil { + defer cancel() + } + stmt, err := db.PrepareContext(ctx, tt.sql) + if err != nil { + t.Fatal(err) + } + _, err = stmt.QueryContext(ctx) + switch { + case (err != nil) != (tt.err != nil): + t.Fatalf("stmt.QueryContext() unexpected nil err got = %v, expected = %v", err, tt.err) + case (err != nil && tt.err != nil) && (err.Error() != tt.err.Error()): + t.Errorf("stmt.QueryContext() got = %v, expected = %v", err.Error(), tt.err.Error()) + } + }) + } +} + +func TestStmtExecContext(t *testing.T) { + db := openTestConn(t) + defer db.Close() + + tests := []struct { + name string + ctx func() (context.Context, context.CancelFunc) + sql string + err error + }{ + { + name: "context.Background", + ctx: func() (context.Context, context.CancelFunc) { + return context.Background(), nil + }, + sql: "SELECT pg_sleep(1);", + err: nil, + }, + { + name: "context.WithTimeout exceeded", + ctx: func() (context.Context, context.CancelFunc) { + return context.WithTimeout(context.Background(), 1*time.Second) + }, + sql: "SELECT pg_sleep(10);", + err: &Error{Message: "canceling statement due to user request"}, + }, + { + name: "context.WithTimeout", + ctx: func() (context.Context, context.CancelFunc) { + return context.WithTimeout(context.Background(), time.Minute) + }, + sql: "SELECT pg_sleep(1);", + err: nil, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ctx, cancel := tt.ctx() + if cancel != nil { + defer cancel() + } + stmt, err := db.PrepareContext(ctx, tt.sql) + if err != nil { + t.Fatal(err) + } + _, err = stmt.ExecContext(ctx) + switch { + case (err != nil) != (tt.err != nil): + t.Fatalf("stmt.ExecContext() unexpected nil err got = %v, expected = %v", err, tt.err) + case (err != nil && tt.err != nil) && (err.Error() != tt.err.Error()): + t.Errorf("stmt.ExecContext() got = %v, expected = %v", err.Error(), tt.err.Error()) + } + }) + } +} diff --git a/issues_test.go b/issues_test.go index 3a330a0a9..55d3f1ec3 100644 --- a/issues_test.go +++ b/issues_test.go @@ -1,6 +1,10 @@ package pq -import "testing" +import ( + "context" + "testing" + "time" +) func TestIssue494(t *testing.T) { db := openTestConn(t) @@ -24,3 +28,33 @@ func TestIssue494(t *testing.T) { t.Fatal("expected error") } } + +func TestIssue1046(t *testing.T) { + ctxTimeout := time.Second * 2 + + db := openTestConn(t) + defer db.Close() + + ctx, cancel := context.WithTimeout(context.Background(), ctxTimeout) + defer cancel() + + stmt, err := db.PrepareContext(ctx, `SELECT pg_sleep(10) AS id`) + if err != nil { + t.Fatal(err) + } + + var d []uint8 + err = stmt.QueryRowContext(ctx).Scan(&d) + dl, _ := ctx.Deadline() + since := time.Since(dl) + if since > ctxTimeout { + t.Logf("FAIL %s: query returned after context deadline: %v\n", t.Name(), since) + t.Fail() + } + expectedErr := &Error{Message: "canceling statement due to user request"} + if err == nil || err.Error() != expectedErr.Error() { + t.Logf("ctx.Err(): [%T]%+v\n", ctx.Err(), ctx.Err()) + t.Logf("got err: [%T] %+v expected err: [%T] %+v", err, err, expectedErr, expectedErr) + t.Fail() + } +} From c01ab770915b19453b6e4fdb11be725adfce76de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Serhat=20=C5=9Eevki=20Din=C3=A7er?= Date: Thu, 9 Sep 2021 08:35:52 +0300 Subject: [PATCH 39/65] Create codeql-analysis.yml --- .github/workflows/codeql-analysis.yml | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 .github/workflows/codeql-analysis.yml diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml new file mode 100644 index 000000000..4cbfc309a --- /dev/null +++ b/.github/workflows/codeql-analysis.yml @@ -0,0 +1,26 @@ +name: "CodeQL" + +on: + push: + branches: [ master ] + +jobs: + analyze: + name: Analyze + runs-on: ubuntu-latest + permissions: + actions: read + contents: read + security-events: write + + steps: + - name: Checkout repo + uses: actions/checkout@v2 + + - name: Initialize CodeQL + uses: github/codeql-action/init@v1 + with: + languages: 'go' + + - name: CodeQL Analysis + uses: github/codeql-action/analyze@v1 From 087077605f249dfccb8cec814474bf2a7a9b23bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Serhat=20=C5=9Eevki=20Din=C3=A7er?= Date: Thu, 9 Sep 2021 09:15:17 +0300 Subject: [PATCH 40/65] fix possible integer truncation Fixes #1055 --- array.go | 4 ++-- encode.go | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/array.go b/array.go index 7806a31f3..39c8f7e2e 100644 --- a/array.go +++ b/array.go @@ -587,8 +587,8 @@ func (a *Int32Array) scanBytes(src []byte) error { } else { b := make(Int32Array, len(elems)) for i, v := range elems { - var x int - if x, err = strconv.Atoi(string(v)); err != nil { + x, err := strconv.ParseInt(string(v), 10, 32) + if err != nil { return fmt.Errorf("pq: parsing array element index %d: %v", i, err) } b[i] = int32(x) diff --git a/encode.go b/encode.go index 51c143ee4..210b1ec34 100644 --- a/encode.go +++ b/encode.go @@ -559,7 +559,7 @@ func parseBytea(s []byte) (result []byte, err error) { if len(s) < 4 { return nil, fmt.Errorf("invalid bytea sequence %v", s) } - r, err := strconv.ParseInt(string(s[1:4]), 8, 9) + r, err := strconv.ParseUint(string(s[1:4]), 8, 8) if err != nil { return nil, fmt.Errorf("could not parse bytea value: %s", err.Error()) } From 16e9cadb5a5e9211740bcadebb36c448391dca1e Mon Sep 17 00:00:00 2001 From: Ian Hu Date: Wed, 13 Oct 2021 15:45:06 +0800 Subject: [PATCH 41/65] Fix build in android --- user_posix.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/user_posix.go b/user_posix.go index 227a948e0..d465867cb 100644 --- a/user_posix.go +++ b/user_posix.go @@ -1,7 +1,7 @@ // Package pq is a pure Go Postgres driver for the database/sql package. -//go:build aix || darwin || dragonfly || freebsd || linux || nacl || netbsd || openbsd || plan9 || solaris || rumprun || illumos -// +build aix darwin dragonfly freebsd linux nacl netbsd openbsd plan9 solaris rumprun illumos +//go:build aix || darwin || dragonfly || freebsd || (linux && !android) || nacl || netbsd || openbsd || plan9 || solaris || rumprun || illumos +// +build aix darwin dragonfly freebsd (linux && !android) nacl netbsd openbsd plan9 solaris rumprun illumos package pq From b33a1b722c28a144366eec52c9f6f811fcf9efc6 Mon Sep 17 00:00:00 2001 From: Ian Hu Date: Wed, 13 Oct 2021 15:51:25 +0800 Subject: [PATCH 42/65] Fix android build --- user_posix.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/user_posix.go b/user_posix.go index d465867cb..5f2d439bc 100644 --- a/user_posix.go +++ b/user_posix.go @@ -1,7 +1,7 @@ // Package pq is a pure Go Postgres driver for the database/sql package. //go:build aix || darwin || dragonfly || freebsd || (linux && !android) || nacl || netbsd || openbsd || plan9 || solaris || rumprun || illumos -// +build aix darwin dragonfly freebsd (linux && !android) nacl netbsd openbsd plan9 solaris rumprun illumos +// +build aix darwin dragonfly freebsd linux,!android nacl netbsd openbsd plan9 solaris rumprun illumos package pq From 2b4fa17b445c38f6f7b881db1491488d74645126 Mon Sep 17 00:00:00 2001 From: Mechiel Lukkien Date: Tue, 19 Oct 2021 10:07:43 +0200 Subject: [PATCH 43/65] Fix flaky TestConnPrepareContext TestConnPrepareContext checks it receives an context.DeadlineExceeded. However, the context isn't necessarily always expired. This causes most of the test runs to fail for me, only occasionally succeeding. This change ensures the context used in the test is actually context.DeadlineExceeded. The negative duration causes the context package to return an already canceled context. --- conn_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/conn_test.go b/conn_test.go index 4ac3d2b82..f6bbfacb1 100644 --- a/conn_test.go +++ b/conn_test.go @@ -1828,7 +1828,7 @@ func TestConnPrepareContext(t *testing.T) { { name: "context.WithTimeout exceeded", ctx: func() (context.Context, context.CancelFunc) { - return context.WithTimeout(context.Background(), time.Microsecond) + return context.WithTimeout(context.Background(), -time.Minute) }, sql: "SELECT 1", err: context.DeadlineExceeded, From 8446d16b8935fdf2b5c0fe333538ac395e3e1e4b Mon Sep 17 00:00:00 2001 From: Mechiel Lukkien Date: Mon, 8 Nov 2021 21:06:35 +0100 Subject: [PATCH 44/65] issue 1062: Keep track of (context cancelled) error on connection, and make rows.Next return it (#1064) * add reproducer for issue 1062 see https://github.com/lib/pq/issues/1062 * Keep track of an error for a connection. Instead of just whether the connection is ErrBadConn. Often times, the error will still be ErrBadConn. But for expired/cancelled contexts, it will be the error for the context. Most functions still return ErrBadConn per the database/sql/driver contract ("ErrBadConn should only be returned from [...] a query method"). For rows.Next() we return the context-related error. The database/sql/driver contract doesn't look very precise. Is Next a "query method" and should database/sql handle ErrBadConns when Next returns them? Do we have more functions that should return the canceled-context error message? * in test for QueryRowContext, fail for all unexpected errors, not just for the one unexpected error * in TestContextCancel*, accept context.Canceled as valid error * explicitly test for driver.ErrBadConn in test that breaks the connection feedback from otan * move the mutex-protected data in a struct with the mutex, and fix an unsafe usage. there may be unsafeness in this file. feedback from otan --- conn.go | 149 ++++++++++++++++++++++++++++--------------------- conn_go18.go | 10 +--- conn_test.go | 17 ++---- copy.go | 76 ++++++++++++------------- error.go | 10 ++-- go18_test.go | 4 +- issues_test.go | 19 +++++++ 7 files changed, 155 insertions(+), 130 deletions(-) diff --git a/conn.go b/conn.go index 8e445f32c..e050d5358 100644 --- a/conn.go +++ b/conn.go @@ -18,7 +18,7 @@ import ( "path/filepath" "strconv" "strings" - "sync/atomic" + "sync" "time" "unicode" @@ -140,9 +140,10 @@ type conn struct { saveMessageType byte saveMessageBuffer []byte - // If true, this connection is bad and all public-facing functions should - // return ErrBadConn. - bad *atomic.Value + // If an error is set, this connection is bad and all public-facing + // functions should return the appropriate error by calling get() + // (ErrBadConn) or getForNext(). + err syncErr // If set, this connection should never use the binary format when // receiving query results from prepared statements. Only provided for @@ -166,6 +167,40 @@ type conn struct { gss GSS } +type syncErr struct { + err error + sync.Mutex +} + +// Return ErrBadConn if connection is bad. +func (e *syncErr) get() error { + e.Lock() + defer e.Unlock() + if e.err != nil { + return driver.ErrBadConn + } + return nil +} + +// Return the error set on the connection. Currently only used by rows.Next. +func (e *syncErr) getForNext() error { + e.Lock() + defer e.Unlock() + return e.err +} + +// Set error, only if it isn't set yet. +func (e *syncErr) set(err error) { + if err == nil { + panic("attempt to set nil err") + } + e.Lock() + defer e.Unlock() + if e.err == nil { + e.err = err + } +} + // Handle driver-side settings in parsed connection string. func (cn *conn) handleDriverSettings(o values) (err error) { boolSetting := func(key string, val *bool) error { @@ -306,12 +341,9 @@ func (c *Connector) open(ctx context.Context) (cn *conn, err error) { o[k] = v } - bad := &atomic.Value{} - bad.Store(false) cn = &conn{ opts: o, dialer: c.dialer, - bad: bad, } err = cn.handleDriverSettings(o) if err != nil { @@ -516,22 +548,9 @@ func (cn *conn) isInTransaction() bool { cn.txnStatus == txnStatusInFailedTransaction } -func (cn *conn) setBad() { - if cn.bad != nil { - cn.bad.Store(true) - } -} - -func (cn *conn) getBad() bool { - if cn.bad != nil { - return cn.bad.Load().(bool) - } - return false -} - func (cn *conn) checkIsInTransaction(intxn bool) { if cn.isInTransaction() != intxn { - cn.setBad() + cn.err.set(driver.ErrBadConn) errorf("unexpected transaction status %v", cn.txnStatus) } } @@ -541,8 +560,8 @@ func (cn *conn) Begin() (_ driver.Tx, err error) { } func (cn *conn) begin(mode string) (_ driver.Tx, err error) { - if cn.getBad() { - return nil, driver.ErrBadConn + if err := cn.err.get(); err != nil { + return nil, err } defer cn.errRecover(&err) @@ -552,11 +571,11 @@ func (cn *conn) begin(mode string) (_ driver.Tx, err error) { return nil, err } if commandTag != "BEGIN" { - cn.setBad() + cn.err.set(driver.ErrBadConn) return nil, fmt.Errorf("unexpected command tag %s", commandTag) } if cn.txnStatus != txnStatusIdleInTransaction { - cn.setBad() + cn.err.set(driver.ErrBadConn) return nil, fmt.Errorf("unexpected transaction status %v", cn.txnStatus) } return cn, nil @@ -570,8 +589,8 @@ func (cn *conn) closeTxn() { func (cn *conn) Commit() (err error) { defer cn.closeTxn() - if cn.getBad() { - return driver.ErrBadConn + if err := cn.err.get(); err != nil { + return err } defer cn.errRecover(&err) @@ -592,12 +611,12 @@ func (cn *conn) Commit() (err error) { _, commandTag, err := cn.simpleExec("COMMIT") if err != nil { if cn.isInTransaction() { - cn.setBad() + cn.err.set(driver.ErrBadConn) } return err } if commandTag != "COMMIT" { - cn.setBad() + cn.err.set(driver.ErrBadConn) return fmt.Errorf("unexpected command tag %s", commandTag) } cn.checkIsInTransaction(false) @@ -606,8 +625,8 @@ func (cn *conn) Commit() (err error) { func (cn *conn) Rollback() (err error) { defer cn.closeTxn() - if cn.getBad() { - return driver.ErrBadConn + if err := cn.err.get(); err != nil { + return err } defer cn.errRecover(&err) return cn.rollback() @@ -618,7 +637,7 @@ func (cn *conn) rollback() (err error) { _, commandTag, err := cn.simpleExec("ROLLBACK") if err != nil { if cn.isInTransaction() { - cn.setBad() + cn.err.set(driver.ErrBadConn) } return err } @@ -658,7 +677,7 @@ func (cn *conn) simpleExec(q string) (res driver.Result, commandTag string, err case 'T', 'D': // ignore any results default: - cn.setBad() + cn.err.set(driver.ErrBadConn) errorf("unknown response for simple query: %q", t) } } @@ -680,7 +699,7 @@ func (cn *conn) simpleQuery(q string) (res *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.setBad() + cn.err.set(driver.ErrBadConn) errorf("unexpected message %q in simple query execution", t) } if res == nil { @@ -707,7 +726,7 @@ func (cn *conn) simpleQuery(q string) (res *rows, err error) { err = parseError(r) case 'D': if res == nil { - cn.setBad() + cn.err.set(driver.ErrBadConn) errorf("unexpected DataRow in simple query execution") } // the query didn't fail; kick off to Next @@ -722,7 +741,7 @@ func (cn *conn) simpleQuery(q string) (res *rows, err error) { // To work around a bug in QueryRow in Go 1.2 and earlier, wait // until the first DataRow has been received. default: - cn.setBad() + cn.err.set(driver.ErrBadConn) errorf("unknown response for simple query: %q", t) } } @@ -815,8 +834,8 @@ func (cn *conn) prepareTo(q, stmtName string) *stmt { } func (cn *conn) Prepare(q string) (_ driver.Stmt, err error) { - if cn.getBad() { - return nil, driver.ErrBadConn + if err := cn.err.get(); err != nil { + return nil, err } defer cn.errRecover(&err) @@ -854,8 +873,8 @@ func (cn *conn) Query(query string, args []driver.Value) (driver.Rows, error) { } func (cn *conn) query(query string, args []driver.Value) (_ *rows, err error) { - if cn.getBad() { - return nil, driver.ErrBadConn + if err := cn.err.get(); err != nil { + return nil, err } if cn.inCopy { return nil, errCopyInProgress @@ -888,8 +907,8 @@ func (cn *conn) query(query string, args []driver.Value) (_ *rows, err error) { // Implement the optional "Execer" interface for one-shot queries func (cn *conn) Exec(query string, args []driver.Value) (res driver.Result, err error) { - if cn.getBad() { - return nil, driver.ErrBadConn + if err := cn.err.get(); err != nil { + return nil, err } defer cn.errRecover(&err) @@ -960,7 +979,7 @@ func (cn *conn) sendSimpleMessage(typ byte) (err error) { // the message yourself. func (cn *conn) saveMessage(typ byte, buf *readBuf) { if cn.saveMessageType != 0 { - cn.setBad() + cn.err.set(driver.ErrBadConn) errorf("unexpected saveMessageType %d", cn.saveMessageType) } cn.saveMessageType = typ @@ -1330,8 +1349,8 @@ func (st *stmt) Close() (err error) { if st.closed { return nil } - if st.cn.getBad() { - return driver.ErrBadConn + if err := st.cn.err.get(); err != nil { + return err } defer st.cn.errRecover(&err) @@ -1344,14 +1363,14 @@ func (st *stmt) Close() (err error) { t, _ := st.cn.recv1() if t != '3' { - st.cn.setBad() + st.cn.err.set(driver.ErrBadConn) errorf("unexpected close response: %q", t) } st.closed = true t, r := st.cn.recv1() if t != 'Z' { - st.cn.setBad() + st.cn.err.set(driver.ErrBadConn) errorf("expected ready for query, but got: %q", t) } st.cn.processReadyForQuery(r) @@ -1364,8 +1383,8 @@ func (st *stmt) Query(v []driver.Value) (r driver.Rows, err error) { } func (st *stmt) query(v []driver.Value) (r *rows, err error) { - if st.cn.getBad() { - return nil, driver.ErrBadConn + if err := st.cn.err.get(); err != nil { + return nil, err } defer st.cn.errRecover(&err) @@ -1377,8 +1396,8 @@ func (st *stmt) query(v []driver.Value) (r *rows, err error) { } func (st *stmt) Exec(v []driver.Value) (res driver.Result, err error) { - if st.cn.getBad() { - return nil, driver.ErrBadConn + if err := st.cn.err.get(); err != nil { + return nil, err } defer st.cn.errRecover(&err) @@ -1464,7 +1483,7 @@ func (cn *conn) parseComplete(commandTag string) (driver.Result, string) { if affectedRows == nil && strings.HasPrefix(commandTag, "INSERT ") { parts := strings.Split(commandTag, " ") if len(parts) != 3 { - cn.setBad() + cn.err.set(driver.ErrBadConn) errorf("unexpected INSERT command tag %s", commandTag) } affectedRows = &parts[len(parts)-1] @@ -1476,7 +1495,7 @@ func (cn *conn) parseComplete(commandTag string) (driver.Result, string) { } n, err := strconv.ParseInt(*affectedRows, 10, 64) if err != nil { - cn.setBad() + cn.err.set(driver.ErrBadConn) errorf("could not parse commandTag: %s", err) } return driver.RowsAffected(n), commandTag @@ -1543,8 +1562,8 @@ func (rs *rows) Next(dest []driver.Value) (err error) { } conn := rs.cn - if conn.getBad() { - return driver.ErrBadConn + if err := conn.err.getForNext(); err != nil { + return err } defer conn.errRecover(&err) @@ -1568,7 +1587,7 @@ func (rs *rows) Next(dest []driver.Value) (err error) { case 'D': n := rs.rb.int16() if err != nil { - conn.setBad() + conn.err.set(driver.ErrBadConn) errorf("unexpected DataRow after error %s", err) } if n < len(dest) { @@ -1762,7 +1781,7 @@ func (cn *conn) readReadyForQuery() { cn.processReadyForQuery(r) return default: - cn.setBad() + cn.err.set(driver.ErrBadConn) errorf("unexpected message %q; expected ReadyForQuery", t) } } @@ -1782,7 +1801,7 @@ func (cn *conn) readParseResponse() { cn.readReadyForQuery() panic(err) default: - cn.setBad() + cn.err.set(driver.ErrBadConn) errorf("unexpected Parse response %q", t) } } @@ -1807,7 +1826,7 @@ func (cn *conn) readStatementDescribeResponse() (paramTyps []oid.Oid, colNames [ cn.readReadyForQuery() panic(err) default: - cn.setBad() + cn.err.set(driver.ErrBadConn) errorf("unexpected Describe statement response %q", t) } } @@ -1825,7 +1844,7 @@ func (cn *conn) readPortalDescribeResponse() rowsHeader { cn.readReadyForQuery() panic(err) default: - cn.setBad() + cn.err.set(driver.ErrBadConn) errorf("unexpected Describe response %q", t) } panic("not reached") @@ -1841,7 +1860,7 @@ func (cn *conn) readBindResponse() { cn.readReadyForQuery() panic(err) default: - cn.setBad() + cn.err.set(driver.ErrBadConn) errorf("unexpected Bind response %q", t) } } @@ -1868,7 +1887,7 @@ func (cn *conn) postExecuteWorkaround() { cn.saveMessage(t, r) return default: - cn.setBad() + cn.err.set(driver.ErrBadConn) errorf("unexpected message during extended query execution: %q", t) } } @@ -1881,7 +1900,7 @@ func (cn *conn) readExecuteResponse(protocolState string) (res driver.Result, co switch t { case 'C': if err != nil { - cn.setBad() + cn.err.set(driver.ErrBadConn) errorf("unexpected CommandComplete after error %s", err) } res, commandTag = cn.parseComplete(r.string()) @@ -1895,7 +1914,7 @@ func (cn *conn) readExecuteResponse(protocolState string) (res driver.Result, co err = parseError(r) case 'T', 'D', 'I': if err != nil { - cn.setBad() + cn.err.set(driver.ErrBadConn) errorf("unexpected %q after error %s", t, err) } if t == 'I' { @@ -1903,7 +1922,7 @@ func (cn *conn) readExecuteResponse(protocolState string) (res driver.Result, co } // ignore any results default: - cn.setBad() + cn.err.set(driver.ErrBadConn) errorf("unknown %s response: %q", protocolState, t) } } diff --git a/conn_go18.go b/conn_go18.go index 3c83082b3..63d4ca6aa 100644 --- a/conn_go18.go +++ b/conn_go18.go @@ -7,7 +7,6 @@ import ( "fmt" "io" "io/ioutil" - "sync/atomic" "time" ) @@ -115,7 +114,7 @@ func (cn *conn) watchCancel(ctx context.Context) func() { } // Set the connection state to bad so it does not get reused. - cn.setBad() + cn.err.set(ctx.Err()) // At this point the function level context is canceled, // so it must not be used for the additional network @@ -131,7 +130,7 @@ func (cn *conn) watchCancel(ctx context.Context) func() { return func() { select { case <-finished: - cn.setBad() + cn.err.set(ctx.Err()) cn.Close() case finished <- struct{}{}: } @@ -157,11 +156,8 @@ func (cn *conn) cancel(ctx context.Context) error { defer c.Close() { - bad := &atomic.Value{} - bad.Store(false) can := conn{ - c: c, - bad: bad, + c: c, } err = can.ssl(o) if err != nil { diff --git a/conn_test.go b/conn_test.go index f6bbfacb1..b32f983ab 100644 --- a/conn_test.go +++ b/conn_test.go @@ -10,7 +10,6 @@ import ( "os" "reflect" "strings" - "sync/atomic" "testing" "time" ) @@ -696,9 +695,7 @@ func TestErrorDuringStartupClosesConn(t *testing.T) { func TestBadConn(t *testing.T) { var err error - bad := &atomic.Value{} - bad.Store(false) - cn := conn{bad: bad} + cn := conn{} func() { defer cn.errRecover(&err) panic(io.EOF) @@ -706,13 +703,11 @@ func TestBadConn(t *testing.T) { if err != driver.ErrBadConn { t.Fatalf("expected driver.ErrBadConn, got: %#v", err) } - if !cn.getBad() { - t.Fatalf("expected cn.bad") + if err := cn.err.get(); err != driver.ErrBadConn { + t.Fatalf("expected driver.ErrBadConn, got %#v", err) } - badd := &atomic.Value{} - badd.Store(false) - cn = conn{bad: badd} + cn = conn{} func() { defer cn.errRecover(&err) e := &Error{Severity: Efatal} @@ -721,8 +716,8 @@ func TestBadConn(t *testing.T) { if err != driver.ErrBadConn { t.Fatalf("expected driver.ErrBadConn, got: %#v", err) } - if !cn.getBad() { - t.Fatalf("expected cn.bad") + if err := cn.err.get(); err != driver.ErrBadConn { + t.Fatalf("expected driver.ErrBadConn, got %#v", err) } } diff --git a/copy.go b/copy.go index bb3cbd7b9..c072bc3bc 100644 --- a/copy.go +++ b/copy.go @@ -49,12 +49,14 @@ type copyin struct { buffer []byte rowData chan []byte done chan bool - driver.Result closed bool - sync.Mutex // guards err - err error + mu struct { + sync.Mutex + err error + driver.Result + } } const ciBufferSize = 64 * 1024 @@ -98,13 +100,13 @@ awaitCopyInResponse: err = parseError(r) case 'Z': if err == nil { - ci.setBad() + ci.setBad(driver.ErrBadConn) errorf("unexpected ReadyForQuery in response to COPY") } cn.processReadyForQuery(r) return nil, err default: - ci.setBad() + ci.setBad(driver.ErrBadConn) errorf("unknown response for copy query: %q", t) } } @@ -123,7 +125,7 @@ awaitCopyInResponse: cn.processReadyForQuery(r) return nil, err default: - ci.setBad() + ci.setBad(driver.ErrBadConn) errorf("unknown response for CopyFail: %q", t) } } @@ -144,7 +146,7 @@ func (ci *copyin) resploop() { var r readBuf t, err := ci.cn.recvMessage(&r) if err != nil { - ci.setBad() + ci.setBad(driver.ErrBadConn) ci.setError(err) ci.done <- true return @@ -166,7 +168,7 @@ func (ci *copyin) resploop() { err := parseError(&r) ci.setError(err) default: - ci.setBad() + ci.setBad(driver.ErrBadConn) ci.setError(fmt.Errorf("unknown response during CopyIn: %q", t)) ci.done <- true return @@ -174,46 +176,41 @@ func (ci *copyin) resploop() { } } -func (ci *copyin) setBad() { - ci.Lock() - ci.cn.setBad() - ci.Unlock() +func (ci *copyin) setBad(err error) { + ci.cn.err.set(err) } -func (ci *copyin) isBad() bool { - ci.Lock() - b := ci.cn.getBad() - ci.Unlock() - return b +func (ci *copyin) getBad() error { + return ci.cn.err.get() } -func (ci *copyin) isErrorSet() bool { - ci.Lock() - isSet := (ci.err != nil) - ci.Unlock() - return isSet +func (ci *copyin) err() error { + ci.mu.Lock() + err := ci.mu.err + ci.mu.Unlock() + return err } // 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.Lock() - if ci.err == nil { - ci.err = err + ci.mu.Lock() + if ci.mu.err == nil { + ci.mu.err = err } - ci.Unlock() + ci.mu.Unlock() } func (ci *copyin) setResult(result driver.Result) { - ci.Lock() - ci.Result = result - ci.Unlock() + ci.mu.Lock() + ci.mu.Result = result + ci.mu.Unlock() } func (ci *copyin) getResult() driver.Result { - ci.Lock() - result := ci.Result - ci.Unlock() + ci.mu.Lock() + result := ci.mu.Result + ci.mu.Unlock() if result == nil { return driver.RowsAffected(0) } @@ -240,13 +237,13 @@ func (ci *copyin) Exec(v []driver.Value) (r driver.Result, err error) { return nil, errCopyInClosed } - if ci.isBad() { - return nil, driver.ErrBadConn + if err := ci.getBad(); err != nil { + return nil, err } defer ci.cn.errRecover(&err) - if ci.isErrorSet() { - return nil, ci.err + if err := ci.err(); err != nil { + return nil, err } if len(v) == 0 { @@ -282,8 +279,8 @@ func (ci *copyin) Close() (err error) { } ci.closed = true - if ci.isBad() { - return driver.ErrBadConn + if err := ci.getBad(); err != nil { + return err } defer ci.cn.errRecover(&err) @@ -299,8 +296,7 @@ func (ci *copyin) Close() (err error) { <-ci.done ci.cn.inCopy = false - if ci.isErrorSet() { - err = ci.err + if err := ci.err(); err != nil { return err } return nil diff --git a/error.go b/error.go index b0f53755a..5cfe9c6e9 100644 --- a/error.go +++ b/error.go @@ -484,7 +484,7 @@ func (cn *conn) errRecover(err *error) { case nil: // Do nothing case runtime.Error: - cn.setBad() + cn.err.set(driver.ErrBadConn) panic(v) case *Error: if v.Fatal() { @@ -493,10 +493,10 @@ func (cn *conn) errRecover(err *error) { *err = v } case *net.OpError: - cn.setBad() + cn.err.set(driver.ErrBadConn) *err = v case *safeRetryError: - cn.setBad() + cn.err.set(driver.ErrBadConn) *err = driver.ErrBadConn case error: if v == io.EOF || v.Error() == "remote error: handshake failure" { @@ -506,13 +506,13 @@ func (cn *conn) errRecover(err *error) { } default: - cn.setBad() + cn.err.set(driver.ErrBadConn) 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 { - cn.setBad() + cn.err.set(driver.ErrBadConn) } } diff --git a/go18_test.go b/go18_test.go index 95c08cd79..27501e74a 100644 --- a/go18_test.go +++ b/go18_test.go @@ -143,7 +143,7 @@ func TestContextCancelQuery(t *testing.T) { cancel() if err != nil { t.Fatal(err) - } else if err := rows.Close(); err != nil && err != driver.ErrBadConn { + } else if err := rows.Close(); err != nil && err != driver.ErrBadConn && err != context.Canceled { t.Fatal(err) } }() @@ -242,7 +242,7 @@ func TestContextCancelBegin(t *testing.T) { t.Fatal(err) } else if err := tx.Rollback(); err != nil && err.Error() != "pq: canceling statement due to user request" && - err != sql.ErrTxDone && err != driver.ErrBadConn { + err != sql.ErrTxDone && err != driver.ErrBadConn && err != context.Canceled { t.Fatal(err) } }() diff --git a/issues_test.go b/issues_test.go index 55d3f1ec3..4d24c9ddd 100644 --- a/issues_test.go +++ b/issues_test.go @@ -58,3 +58,22 @@ func TestIssue1046(t *testing.T) { t.Fail() } } + +func TestIssue1062(t *testing.T) { + db := openTestConn(t) + defer db.Close() + + // Ensure that cancelling a QueryRowContext does not result in an ErrBadConn. + + for i := 0; i < 100; i++ { + ctx, cancel := context.WithCancel(context.Background()) + go cancel() + row := db.QueryRowContext(ctx, "select 1") + + var v int + err := row.Scan(&v) + if err != nil && err != context.Canceled && err.Error() != "pq: canceling statement due to user request" { + t.Fatalf("Scan resulted in unexpected error %v for canceled QueryRowContext at attempt %d", err, i+1) + } + } +} From 4b55993141422535e605644b07412b1b46116557 Mon Sep 17 00:00:00 2001 From: Rafi Shamim Date: Thu, 7 Apr 2022 13:50:31 -0400 Subject: [PATCH 45/65] Avoid asserting on error message for cancel tests --- conn_test.go | 59 ++++++++++++++++++++++++++------------------------ go18_test.go | 13 ++++++----- issues_test.go | 10 +++++---- 3 files changed, 45 insertions(+), 37 deletions(-) diff --git a/conn_test.go b/conn_test.go index b32f983ab..eb2595705 100644 --- a/conn_test.go +++ b/conn_test.go @@ -4,6 +4,7 @@ import ( "context" "database/sql" "database/sql/driver" + "errors" "fmt" "io" "net" @@ -1859,34 +1860,34 @@ func TestStmtQueryContext(t *testing.T) { defer db.Close() tests := []struct { - name string - ctx func() (context.Context, context.CancelFunc) - sql string - err error + name string + ctx func() (context.Context, context.CancelFunc) + sql string + cancelExpected bool }{ { name: "context.Background", ctx: func() (context.Context, context.CancelFunc) { return context.Background(), nil }, - sql: "SELECT pg_sleep(1);", - err: nil, + sql: "SELECT pg_sleep(1);", + cancelExpected: false, }, { name: "context.WithTimeout exceeded", ctx: func() (context.Context, context.CancelFunc) { return context.WithTimeout(context.Background(), 1*time.Second) }, - sql: "SELECT pg_sleep(10);", - err: &Error{Message: "canceling statement due to user request"}, + sql: "SELECT pg_sleep(10);", + cancelExpected: true, }, { name: "context.WithTimeout", ctx: func() (context.Context, context.CancelFunc) { return context.WithTimeout(context.Background(), time.Minute) }, - sql: "SELECT pg_sleep(1);", - err: nil, + sql: "SELECT pg_sleep(1);", + cancelExpected: false, }, } for _, tt := range tests { @@ -1900,11 +1901,12 @@ func TestStmtQueryContext(t *testing.T) { t.Fatal(err) } _, err = stmt.QueryContext(ctx) + pgErr := (*Error)(nil) switch { - case (err != nil) != (tt.err != nil): - t.Fatalf("stmt.QueryContext() unexpected nil err got = %v, expected = %v", err, tt.err) - case (err != nil && tt.err != nil) && (err.Error() != tt.err.Error()): - t.Errorf("stmt.QueryContext() got = %v, expected = %v", err.Error(), tt.err.Error()) + case (err != nil) != tt.cancelExpected: + t.Fatalf("stmt.QueryContext() unexpected nil err got = %v, cancelExpected = %v", err, tt.cancelExpected) + case (err != nil && tt.cancelExpected) && !(errors.As(err, &pgErr) && pgErr.Code == cancelErrorCode): + t.Errorf("stmt.QueryContext() got = %v, cancelExpected = %v", err.Error(), tt.cancelExpected) } }) } @@ -1915,34 +1917,34 @@ func TestStmtExecContext(t *testing.T) { defer db.Close() tests := []struct { - name string - ctx func() (context.Context, context.CancelFunc) - sql string - err error + name string + ctx func() (context.Context, context.CancelFunc) + sql string + cancelExpected bool }{ { name: "context.Background", ctx: func() (context.Context, context.CancelFunc) { return context.Background(), nil }, - sql: "SELECT pg_sleep(1);", - err: nil, + sql: "SELECT pg_sleep(1);", + cancelExpected: false, }, { name: "context.WithTimeout exceeded", ctx: func() (context.Context, context.CancelFunc) { return context.WithTimeout(context.Background(), 1*time.Second) }, - sql: "SELECT pg_sleep(10);", - err: &Error{Message: "canceling statement due to user request"}, + sql: "SELECT pg_sleep(10);", + cancelExpected: true, }, { name: "context.WithTimeout", ctx: func() (context.Context, context.CancelFunc) { return context.WithTimeout(context.Background(), time.Minute) }, - sql: "SELECT pg_sleep(1);", - err: nil, + sql: "SELECT pg_sleep(1);", + cancelExpected: false, }, } for _, tt := range tests { @@ -1956,11 +1958,12 @@ func TestStmtExecContext(t *testing.T) { t.Fatal(err) } _, err = stmt.ExecContext(ctx) + pgErr := (*Error)(nil) switch { - case (err != nil) != (tt.err != nil): - t.Fatalf("stmt.ExecContext() unexpected nil err got = %v, expected = %v", err, tt.err) - case (err != nil && tt.err != nil) && (err.Error() != tt.err.Error()): - t.Errorf("stmt.ExecContext() got = %v, expected = %v", err.Error(), tt.err.Error()) + case (err != nil) != tt.cancelExpected: + t.Fatalf("stmt.QueryContext() unexpected nil err got = %v, cancelExpected = %v", err, tt.cancelExpected) + case (err != nil && tt.cancelExpected) && !(errors.As(err, &pgErr) && pgErr.Code == cancelErrorCode): + t.Errorf("stmt.QueryContext() got = %v, cancelExpected = %v", err.Error(), tt.cancelExpected) } }) } diff --git a/go18_test.go b/go18_test.go index 27501e74a..bcc02006c 100644 --- a/go18_test.go +++ b/go18_test.go @@ -4,6 +4,7 @@ import ( "context" "database/sql" "database/sql/driver" + "errors" "runtime" "strings" "testing" @@ -75,6 +76,8 @@ func TestMultipleSimpleQuery(t *testing.T) { const contextRaceIterations = 100 +const cancelErrorCode ErrorCode = "57014" + func TestContextCancelExec(t *testing.T) { db := openTestConn(t) defer db.Close() @@ -87,7 +90,7 @@ func TestContextCancelExec(t *testing.T) { // Not canceled until after the exec has started. if _, err := db.ExecContext(ctx, "select pg_sleep(1)"); err == nil { t.Fatal("expected error") - } else if err.Error() != "pq: canceling statement due to user request" { + } else if pgErr := (*Error)(nil); !(errors.As(err, &pgErr) && pgErr.Code == cancelErrorCode) { t.Fatalf("unexpected error: %s", err) } @@ -125,7 +128,7 @@ func TestContextCancelQuery(t *testing.T) { // Not canceled until after the exec has started. if _, err := db.QueryContext(ctx, "select pg_sleep(1)"); err == nil { t.Fatal("expected error") - } else if err.Error() != "pq: canceling statement due to user request" { + } else if pgErr := (*Error)(nil); !(errors.As(err, &pgErr) && pgErr.Code == cancelErrorCode) { t.Fatalf("unexpected error: %s", err) } @@ -215,7 +218,7 @@ func TestContextCancelBegin(t *testing.T) { // Not canceled until after the exec has started. if _, err := tx.Exec("select pg_sleep(1)"); err == nil { t.Fatal("expected error") - } else if err.Error() != "pq: canceling statement due to user request" { + } else if pgErr := (*Error)(nil); !(errors.As(err, &pgErr) && pgErr.Code == cancelErrorCode) { t.Fatalf("unexpected error: %s", err) } @@ -240,8 +243,8 @@ func TestContextCancelBegin(t *testing.T) { cancel() if err != nil { t.Fatal(err) - } else if err := tx.Rollback(); err != nil && - err.Error() != "pq: canceling statement due to user request" && + } else if err, pgErr := tx.Rollback(), (*Error)(nil); err != nil && + !(errors.As(err, &pgErr) && pgErr.Code == cancelErrorCode) && err != sql.ErrTxDone && err != driver.ErrBadConn && err != context.Canceled { t.Fatal(err) } diff --git a/issues_test.go b/issues_test.go index 4d24c9ddd..26a70282b 100644 --- a/issues_test.go +++ b/issues_test.go @@ -2,6 +2,7 @@ package pq import ( "context" + "errors" "testing" "time" ) @@ -51,10 +52,9 @@ func TestIssue1046(t *testing.T) { t.Logf("FAIL %s: query returned after context deadline: %v\n", t.Name(), since) t.Fail() } - expectedErr := &Error{Message: "canceling statement due to user request"} - if err == nil || err.Error() != expectedErr.Error() { + if pgErr := (*Error)(nil); !(errors.As(err, &pgErr) && pgErr.Code == cancelErrorCode) { t.Logf("ctx.Err(): [%T]%+v\n", ctx.Err(), ctx.Err()) - t.Logf("got err: [%T] %+v expected err: [%T] %+v", err, err, expectedErr, expectedErr) + t.Logf("got err: [%T] %+v expected errCode: %v", err, err, cancelErrorCode) t.Fail() } } @@ -72,7 +72,9 @@ func TestIssue1062(t *testing.T) { var v int err := row.Scan(&v) - if err != nil && err != context.Canceled && err.Error() != "pq: canceling statement due to user request" { + if pgErr := (*Error)(nil); err != nil && + err != context.Canceled && + !(errors.As(err, &pgErr) && pgErr.Code == cancelErrorCode) { t.Fatalf("Scan resulted in unexpected error %v for canceled QueryRowContext at attempt %d", err, i+1) } } From b3b833258663afbe55669e5b53e5d62f7e1231bd Mon Sep 17 00:00:00 2001 From: Oliver Tan Date: Wed, 13 Apr 2022 06:05:56 +1000 Subject: [PATCH 46/65] expose raw CopyData command (#1077) --- copy.go | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/copy.go b/copy.go index c072bc3bc..a24ea3f54 100644 --- a/copy.go +++ b/copy.go @@ -1,6 +1,7 @@ package pq import ( + "context" "database/sql/driver" "encoding/binary" "errors" @@ -273,6 +274,38 @@ func (ci *copyin) Exec(v []driver.Value) (r driver.Result, err error) { return driver.RowsAffected(0), nil } +// CopyData executes a raw CopyData command using the PostgreSQL Frontend/Backend +// protocol. Use Exec(nil) to finish the command. +func (ci *copyin) CopyData(ctx context.Context, line string) (r driver.Result, err error) { + if ci.closed { + return nil, errCopyInClosed + } + + if finish := ci.cn.watchCancel(ctx); finish != nil { + defer finish() + } + + if err := ci.getBad(); err != nil { + return nil, err + } + defer ci.cn.errRecover(&err) + + if err := ci.err(); err != nil { + return nil, err + } + + ci.buffer = append(ci.buffer, []byte(line)...) + ci.buffer = append(ci.buffer, '\n') + + if len(ci.buffer) > ciBufferFlushSize { + ci.flush(ci.buffer) + // reset buffer, keep bytes for message identifier and length + ci.buffer = ci.buffer[:5] + } + + return driver.RowsAffected(0), nil +} + func (ci *copyin) Close() (err error) { if ci.closed { // Don't do anything, we're already closed return nil From 326e7d02f7cd360303d51a606825189b358a3c6f Mon Sep 17 00:00:00 2001 From: Oliver Tan Date: Wed, 13 Apr 2022 06:50:03 +1000 Subject: [PATCH 47/65] fix CopyData comment --- copy.go | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/copy.go b/copy.go index a24ea3f54..2f5c1ec8a 100644 --- a/copy.go +++ b/copy.go @@ -274,8 +274,13 @@ func (ci *copyin) Exec(v []driver.Value) (r driver.Result, err error) { return driver.RowsAffected(0), nil } -// CopyData executes a raw CopyData command using the PostgreSQL Frontend/Backend -// protocol. Use Exec(nil) to finish the command. +// CopyData inserts a raw string into the COPY stream. The insert is +// asynchronous and CopyData can return errors from previous CopyData calls to +// the same COPY stmt. +// +// You need to call Exec(nil) to sync the COPY stream and to get any +// errors from pending data, since Stmt.Close() doesn't return errors +// to the user. func (ci *copyin) CopyData(ctx context.Context, line string) (r driver.Result, err error) { if ci.closed { return nil, errCopyInClosed From 006a3f492338e7f74b87a2c16d2c4be10cc04ae6 Mon Sep 17 00:00:00 2001 From: Stephan <36155009+stephanvanzwienen@users.noreply.github.com> Date: Wed, 13 Apr 2022 03:12:02 +0200 Subject: [PATCH 48/65] Added code that accounts for the 'Z' timezone separator in the ParseTimestamp func. (#1073) Co-authored-by: Stephan van Zwienen --- encode.go | 8 ++++++-- encode_test.go | 37 +++++++++++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+), 2 deletions(-) diff --git a/encode.go b/encode.go index 210b1ec34..bffe6096a 100644 --- a/encode.go +++ b/encode.go @@ -422,7 +422,7 @@ func ParseTimestamp(currentLocation *time.Location, str string) (time.Time, erro if remainderIdx < len(str) && str[remainderIdx] == '.' { fracStart := remainderIdx + 1 - fracOff := strings.IndexAny(str[fracStart:], "-+ ") + fracOff := strings.IndexAny(str[fracStart:], "-+Z ") if fracOff < 0 { fracOff = len(str) - fracStart } @@ -432,7 +432,7 @@ func ParseTimestamp(currentLocation *time.Location, str string) (time.Time, erro remainderIdx += fracOff + 1 } if tzStart := remainderIdx; tzStart < len(str) && (str[tzStart] == '-' || str[tzStart] == '+') { - // time zone separator is always '-' or '+' (UTC is +00) + // time zone separator is always '-' or '+' or 'Z' (UTC is +00) var tzSign int switch c := str[tzStart]; c { case '-': @@ -454,7 +454,11 @@ func ParseTimestamp(currentLocation *time.Location, str string) (time.Time, erro remainderIdx += 3 } tzOff = tzSign * ((tzHours * 60 * 60) + (tzMin * 60) + tzSec) + } else if tzStart < len(str) && str[tzStart] == 'Z' { + // time zone Z separator indicates UTC is +00 + remainderIdx += 1 } + var isoYear int if isBC { diff --git a/encode_test.go b/encode_test.go index bffa83868..69f9ebb10 100644 --- a/encode_test.go +++ b/encode_test.go @@ -828,6 +828,43 @@ func TestAppendEscapedTextExistingBuffer(t *testing.T) { } } +var formatAndParseTimestamp = []struct { + time time.Time + expected string +}{ + {time.Time{}, "0001-01-01 00:00:00Z"}, + {time.Date(2001, time.February, 3, 4, 5, 6, 123456789, time.FixedZone("", 0)), "2001-02-03 04:05:06.123456789Z"}, + {time.Date(2001, time.February, 3, 4, 5, 6, 123456789, time.FixedZone("", 2*60*60)), "2001-02-03 04:05:06.123456789+02:00"}, + {time.Date(2001, time.February, 3, 4, 5, 6, 123456789, time.FixedZone("", -6*60*60)), "2001-02-03 04:05:06.123456789-06:00"}, + {time.Date(2001, time.February, 3, 4, 5, 6, 0, time.FixedZone("", -(7*60*60+30*60+9))), "2001-02-03 04:05:06-07:30:09"}, + + {time.Date(1, time.February, 3, 4, 5, 6, 123456789, time.FixedZone("", 0)), "0001-02-03 04:05:06.123456789Z"}, + {time.Date(1, time.February, 3, 4, 5, 6, 123456789, time.FixedZone("", 2*60*60)), "0001-02-03 04:05:06.123456789+02:00"}, + {time.Date(1, time.February, 3, 4, 5, 6, 123456789, time.FixedZone("", -6*60*60)), "0001-02-03 04:05:06.123456789-06:00"}, + + {time.Date(0, time.February, 3, 4, 5, 6, 123456789, time.FixedZone("", 0)), "0001-02-03 04:05:06.123456789Z BC"}, + {time.Date(0, time.February, 3, 4, 5, 6, 123456789, time.FixedZone("", 2*60*60)), "0001-02-03 04:05:06.123456789+02:00 BC"}, + {time.Date(0, time.February, 3, 4, 5, 6, 123456789, time.FixedZone("", -6*60*60)), "0001-02-03 04:05:06.123456789-06:00 BC"}, + + {time.Date(1, time.February, 3, 4, 5, 6, 0, time.FixedZone("", -(7*60*60+30*60+9))), "0001-02-03 04:05:06-07:30:09"}, + {time.Date(0, time.February, 3, 4, 5, 6, 0, time.FixedZone("", -(7*60*60+30*60+9))), "0001-02-03 04:05:06-07:30:09 BC"}, +} + +func TestFormatAndParseTimestamp(t *testing.T) { + for _, val := range formatAndParseTimestamp { + formattedTime := FormatTimestamp(val.time) + parsedTime, err := ParseTimestamp(nil, string(formattedTime)) + + if err != nil { + t.Errorf("invalid parsing, err: %v", err.Error()) + } + + if val.time.UTC() != parsedTime.UTC() { + t.Errorf("invalid parsing from formatted timestamp, got %v; expected %v", parsedTime.String(), val.time.String()) + } + } +} + func BenchmarkAppendEscapedText(b *testing.B) { longString := "" for i := 0; i < 100; i++ { From ef3111ea5aefd45f868ed58012b1d0d0dd162982 Mon Sep 17 00:00:00 2001 From: Nicola Murino Date: Fri, 6 May 2022 18:11:14 +0200 Subject: [PATCH 49/65] error: add SQLState SQLState is also implemented in pgx. This change allow to get the SQL state without importing lib/pq, see for example here https://github.com/cockroachdb/cockroach-go/blob/e1659d1d3580897bce4cea1181724872d792ce53/crdb/tx.go#L232 --- error.go | 5 +++++ go18_test.go | 16 ++++++++++++++++ 2 files changed, 21 insertions(+) diff --git a/error.go b/error.go index 5cfe9c6e9..21b3d933c 100644 --- a/error.go +++ b/error.go @@ -402,6 +402,11 @@ func (err *Error) Fatal() bool { return err.Severity == Efatal } +// SQLState returns the SQLState of the error. +func (err *Error) SQLState() string { + return string(err.Code) +} + // Get implements the legacy PGError interface. New code should use the fields // of the Error struct directly. func (err *Error) Get(k byte) (v string) { diff --git a/go18_test.go b/go18_test.go index bcc02006c..6166db275 100644 --- a/go18_test.go +++ b/go18_test.go @@ -334,3 +334,19 @@ func TestTxOptions(t *testing.T) { t.Errorf("Expected error to mention isolation level, got %q", err) } } + +func TestErrorSQLState(t *testing.T) { + r := readBuf([]byte{67, 52, 48, 48, 48, 49, 0, 0}) // 40001 + err := parseError(&r) + var sqlErr errWithSQLState + if !errors.As(err, &sqlErr) { + t.Fatal("SQLState interface not satisfied") + } + if state := err.SQLState(); state != "40001" { + t.Fatalf("unexpected SQL state %v", state) + } +} + +type errWithSQLState interface { + SQLState() string +} From cf6aeee4f29bfe6ca4eb95d3d577c98f271e40d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20W=C3=BCrbach?= Date: Tue, 10 May 2022 14:24:22 +0200 Subject: [PATCH 50/65] feat: change the connector dialer --- conn.go | 2 +- connector.go | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/conn.go b/conn.go index e050d5358..6cf1dc395 100644 --- a/conn.go +++ b/conn.go @@ -322,7 +322,7 @@ func DialOpen(d Dialer, dsn string) (_ driver.Conn, err error) { if err != nil { return nil, err } - c.dialer = d + c.Dialer(d) return c.open(context.Background()) } diff --git a/connector.go b/connector.go index d7d472615..1145e1225 100644 --- a/connector.go +++ b/connector.go @@ -27,6 +27,11 @@ func (c *Connector) Connect(ctx context.Context) (driver.Conn, error) { return c.open(ctx) } +// Dialer allows change the dialer used to open connections. +func (c *Connector) Dialer(dialer Dialer) { + c.dialer = dialer +} + // Driver returns the underlying driver of this Connector. func (c *Connector) Driver() driver.Driver { return &Driver{} From d8917faf2ecafe856e2cb2df95b51a96f58b3387 Mon Sep 17 00:00:00 2001 From: Cat J <87989074+catj-cockroach@users.noreply.github.com> Date: Fri, 6 May 2022 16:35:08 +0000 Subject: [PATCH 51/65] adds support for kubernetes mounted private keys --- conn.go | 6 ++- ssl_permissions.go | 80 +++++++++++++++++++++++++++-- ssl_permissions_test.go | 109 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 189 insertions(+), 6 deletions(-) create mode 100644 ssl_permissions_test.go diff --git a/conn.go b/conn.go index e050d5358..b4e67bc4c 100644 --- a/conn.go +++ b/conn.go @@ -31,8 +31,10 @@ var ( 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") + ErrSSLKeyUnknownOwnership = errors.New("pq: Could not get owner information for private key, may not be properly protected") + ErrSSLKeyHasWorldPermissions = errors.New("pq: Private key has world access. Permissions should be u=rw,g=r (0640) if owned by root, or u=rw (0600), or less") + + ErrCouldNotDetectUsername = errors.New("pq: Could not detect default username. Please provide one explicitly") errUnexpectedReady = errors.New("unexpected ReadyForQuery") errNoRowsAffected = errors.New("no RowsAffected available after the empty statement") diff --git a/ssl_permissions.go b/ssl_permissions.go index 014af6a16..d587f102e 100644 --- a/ssl_permissions.go +++ b/ssl_permissions.go @@ -3,7 +3,28 @@ package pq -import "os" +import ( + "errors" + "os" + "syscall" +) + +const ( + rootUserID = uint32(0) + + // The maximum permissions that a private key file owned by a regular user + // is allowed to have. This translates to u=rw. + maxUserOwnedKeyPermissions os.FileMode = 0600 + + // The maximum permissions that a private key file owned by root is allowed + // to have. This translates to u=rw,g=r. + maxRootOwnedKeyPermissions os.FileMode = 0640 +) + +var ( + errSSLKeyHasUnacceptableUserPermissions = errors.New("permissions for files not owned by root should be u=rw (0600) or less") + errSSLKeyHasUnacceptableRootPermissions = errors.New("permissions for root owned files should be u=rw,g=r (0640) or less") +) // sslKeyPermissions checks the permissions on user-supplied ssl key files. // The key file should have very little access. @@ -14,8 +35,59 @@ func sslKeyPermissions(sslkey string) error { if err != nil { return err } - if info.Mode().Perm()&0077 != 0 { - return ErrSSLKeyHasWorldPermissions + + err = hasCorrectPermissions(info) + + // return ErrSSLKeyHasWorldPermissions for backwards compatability with + // existing code. + if err == errSSLKeyHasUnacceptableUserPermissions || err == errSSLKeyHasUnacceptableRootPermissions { + err = ErrSSLKeyHasWorldPermissions } - return nil + return err +} + +// hasCorrectPermissions checks the file info (and the unix-specific stat_t +// output) to verify that the permissions on the file are correct. +// +// If the file is owned by the same user the process is running as, +// the file should only have 0600 (u=rw). If the file is owned by root, +// and the group matches the group that the process is running in, the +// permissions cannot be more than 0640 (u=rw,g=r). The file should +// never have world permissions. +// +// Returns an error when the permission check fails. +func hasCorrectPermissions(info os.FileInfo) error { + // if file's permission matches 0600, allow access. + userPermissionMask := (os.FileMode(0777) ^ maxUserOwnedKeyPermissions) + + // regardless of if we're running as root or not, 0600 is acceptable, + // so we return if we match the regular user permission mask. + if info.Mode().Perm()&userPermissionMask == 0 { + return nil + } + + // We need to pull the Unix file information to get the file's owner. + // If we can't access it, there's some sort of operating system level error + // and we should fail rather than attempting to use faulty information. + sysInfo := info.Sys() + if sysInfo == nil { + return ErrSSLKeyUnknownOwnership + } + + unixStat, ok := sysInfo.(*syscall.Stat_t) + if !ok { + return ErrSSLKeyUnknownOwnership + } + + // if the file is owned by root, we allow 0640 (u=rw,g=r) to match what + // Postgres does. + if unixStat.Uid == rootUserID { + rootPermissionMask := (os.FileMode(0777) ^ maxRootOwnedKeyPermissions) + if info.Mode().Perm()&rootPermissionMask != 0 { + return errSSLKeyHasUnacceptableRootPermissions + } + return nil + } + + return errSSLKeyHasUnacceptableUserPermissions } diff --git a/ssl_permissions_test.go b/ssl_permissions_test.go new file mode 100644 index 000000000..b0bdca107 --- /dev/null +++ b/ssl_permissions_test.go @@ -0,0 +1,109 @@ +//go:build !windows +// +build !windows + +package pq + +import ( + "os" + "syscall" + "testing" + "time" +) + +type stat_t_wrapper struct { + stat syscall.Stat_t +} + +func (stat_t *stat_t_wrapper) Name() string { + return "pem.key" +} + +func (stat_t *stat_t_wrapper) Size() int64 { + return int64(100) +} + +func (stat_t *stat_t_wrapper) Mode() os.FileMode { + return os.FileMode(stat_t.stat.Mode) +} + +func (stat_t *stat_t_wrapper) ModTime() time.Time { + return time.Now() +} + +func (stat_t *stat_t_wrapper) IsDir() bool { + return true +} + +func (stat_t *stat_t_wrapper) Sys() interface{} { + return &stat_t.stat +} + +func TestHasCorrectRootGroupPermissions(t *testing.T) { + currentUID := uint32(os.Getuid()) + currentGID := uint32(os.Getgid()) + + testData := []struct { + expectedError error + stat syscall.Stat_t + }{ + { + expectedError: nil, + stat: syscall.Stat_t{ + Mode: 0600, + Uid: currentUID, + Gid: currentGID, + }, + }, + { + expectedError: nil, + stat: syscall.Stat_t{ + Mode: 0640, + Uid: 0, + Gid: currentGID, + }, + }, + { + expectedError: errSSLKeyHasUnacceptableUserPermissions, + stat: syscall.Stat_t{ + Mode: 0666, + Uid: currentUID, + Gid: currentGID, + }, + }, + { + expectedError: errSSLKeyHasUnacceptableRootPermissions, + stat: syscall.Stat_t{ + Mode: 0666, + Uid: 0, + Gid: currentGID, + }, + }, + } + + for _, test := range testData { + wrapper := &stat_t_wrapper{ + stat: test.stat, + } + + if test.expectedError != hasCorrectPermissions(wrapper) { + if test.expectedError == nil { + t.Errorf( + "file owned by %d:%d with %s should not have failed check with error \"%s\"", + test.stat.Uid, + test.stat.Gid, + wrapper.Mode(), + hasCorrectPermissions(wrapper), + ) + continue + } + t.Errorf( + "file owned by %d:%d with %s, expected \"%s\", got \"%s\"", + test.stat.Uid, + test.stat.Gid, + wrapper.Mode(), + test.expectedError, + hasCorrectPermissions(wrapper), + ) + } + } +} From 89fee896440712bc328c9087ea185c3990053f31 Mon Sep 17 00:00:00 2001 From: Anmol Sethi Date: Wed, 25 May 2022 11:53:23 -0700 Subject: [PATCH 52/65] Use pointer receiver on pq.Error.Error() The library returns *pq.Error and not pq.Error. By using a value receiver, the library was documenting that consumers should expect returned error values to contain pq.Error. While *pq.Error implements all methods on pq.Error, *pq.Error is not assignable to pq.Error and so you can't type assert an error value into pq.Error if it actually contains *pq.Error. In particular, this is a problem with errors.As. The following if condition will always return false. var pqe pq.Error if errors.As(err, &pqe) { // Never reached as *pq.Error is not assignable to pqe. ... } --- error.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/error.go b/error.go index 21b3d933c..f67c5a5fa 100644 --- a/error.go +++ b/error.go @@ -449,7 +449,7 @@ func (err *Error) Get(k byte) (v string) { return "" } -func (err Error) Error() string { +func (err *Error) Error() string { return "pq: " + err.Message } From 957fc0b40156534f8dd356bc81679d7e1365242b Mon Sep 17 00:00:00 2001 From: Stas Kelvich Date: Sat, 20 Aug 2022 19:54:33 +0300 Subject: [PATCH 53/65] Set SNI for TSL connections This allows an SNI-aware proxy to route connections. Patch adds a new connection option (`sslsni`) for opting out of the SNI, to have the same behavior as `libpq` does. See more in `sslsni` sections at . --- conn.go | 4 +- ssl.go | 11 +++++ ssl_test.go | 139 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 153 insertions(+), 1 deletion(-) diff --git a/conn.go b/conn.go index 7d83f672b..e70b386ff 100644 --- a/conn.go +++ b/conn.go @@ -1127,7 +1127,7 @@ func isDriverSetting(key string) bool { return true case "password": return true - case "sslmode", "sslcert", "sslkey", "sslrootcert", "sslinline": + case "sslmode", "sslcert", "sslkey", "sslrootcert", "sslinline", "sslsni": return true case "fallback_application_name": return true @@ -2020,6 +2020,8 @@ func parseEnviron(env []string) (out map[string]string) { accrue("sslkey") case "PGSSLROOTCERT": accrue("sslrootcert") + case "PGSSLSNI": + accrue("sslsni") case "PGREQUIRESSL", "PGSSLCRL": unsupported() case "PGREQUIREPEER": diff --git a/ssl.go b/ssl.go index e5eb92895..36b61ba45 100644 --- a/ssl.go +++ b/ssl.go @@ -8,6 +8,7 @@ import ( "os" "os/user" "path/filepath" + "strings" ) // ssl generates a function to upgrade a net.Conn based on the "sslmode" and @@ -50,6 +51,16 @@ func ssl(o values) (func(net.Conn) (net.Conn, error), error) { return nil, fmterrorf(`unsupported sslmode %q; only "require" (default), "verify-full", "verify-ca", and "disable" supported`, mode) } + // Set Server Name Indication (SNI), if enabled by connection parameters. + // By default SNI is on, any value which is not starting with "1" disables + // SNI -- that is the same check vanilla libpq uses. + if sslsni := o["sslsni"]; sslsni == "" || strings.HasPrefix(sslsni, "1") { + // RFC 6066 asks to not set SNI if the host is a literal IP address (IPv4 + // or IPv6). This check is coded already crypto.tls.hostnameInSNI, so + // just always set ServerName here and let crypto/tls do the filtering. + tlsConf.ServerName = o["host"] + } + err := sslClientCertificates(&tlsConf, o) if err != nil { return nil, err diff --git a/ssl_test.go b/ssl_test.go index e00522e76..64d68cf4a 100644 --- a/ssl_test.go +++ b/ssl_test.go @@ -3,12 +3,19 @@ package pq // This file contains SSL tests import ( + "bytes" _ "crypto/sha256" + "crypto/tls" "crypto/x509" "database/sql" + "fmt" + "io" + "net" "os" "path/filepath" + "strings" "testing" + "time" ) func maybeSkipSSLTests(t *testing.T) { @@ -280,3 +287,135 @@ func TestSSLClientCertificates(t *testing.T) { } } } + +// Check that clint sends SNI data when `sslsni` is not disabled +func TestSNISupport(t *testing.T) { + t.Parallel() + tests := []struct { + name string + conn_param string + hostname string + expected_sni string + }{ + { + name: "SNI is set by default", + conn_param: "", + hostname: "localhost", + expected_sni: "localhost", + }, + { + name: "SNI is passed when asked for", + conn_param: "sslsni=1", + hostname: "localhost", + expected_sni: "localhost", + }, + { + name: "SNI is not passed when disabled", + conn_param: "sslsni=0", + hostname: "localhost", + expected_sni: "", + }, + { + name: "SNI is not set for IPv4", + conn_param: "", + hostname: "127.0.0.1", + expected_sni: "", + }, + } + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + // Start mock postgres server on OS-provided port + listener, err := net.Listen("tcp", "127.0.0.1:") + if err != nil { + t.Fatal(err) + } + serverErrChan := make(chan error, 1) + serverSNINameChan := make(chan string, 1) + go mockPostgresSSL(listener, serverErrChan, serverSNINameChan) + + defer listener.Close() + defer close(serverErrChan) + defer close(serverSNINameChan) + + // Try to establish a connection with the mock server. Connection will error out after TLS + // clientHello, but it is enough to catch SNI data on the server side + port := strings.Split(listener.Addr().String(), ":")[1] + connStr := fmt.Sprintf("sslmode=require host=%s port=%s %s", tt.hostname, port, tt.conn_param) + + // We are okay to skip this error as we are polling serverErrChan and we'll get an error + // or timeout from the server side in case of problems here. + db, _ := sql.Open("postgres", connStr) + _, _ = db.Exec("SELECT 1") + + // Check SNI data + select { + case sniHost := <-serverSNINameChan: + if sniHost != tt.expected_sni { + t.Fatalf("Expected SNI to be 'localhost', got '%+v' instead", sniHost) + } + case err = <-serverErrChan: + t.Fatalf("mock server failed with error: %+v", err) + case <-time.After(time.Second): + t.Fatal("exceeded connection timeout without erroring out") + } + }) + } +} + +// Make a postgres mock server to test TLS SNI +// +// Accepts postgres StartupMessage and handles TLS clientHello, then closes a connection. +// While reading clientHello catch passed SNI data and report it to nameChan. +func mockPostgresSSL(listener net.Listener, errChan chan error, nameChan chan string) { + var sniHost string + + conn, err := listener.Accept() + if err != nil { + errChan <- err + return + } + defer conn.Close() + + err = conn.SetDeadline(time.Now().Add(time.Second)) + if err != nil { + errChan <- err + return + } + + // Receive StartupMessage with SSL Request + startupMessage := make([]byte, 8) + if _, err := io.ReadFull(conn, startupMessage); err != nil { + errChan <- err + return + } + // StartupMessage: first four bytes -- total len = 8, last four bytes SslRequestNumber + if !bytes.Equal(startupMessage, []byte{0, 0, 0, 0x8, 0x4, 0xd2, 0x16, 0x2f}) { + errChan <- fmt.Errorf("unexpected startup message: %#v", startupMessage) + return + } + + // Respond with SSLOk + _, err = conn.Write([]byte("S")) + if err != nil { + errChan <- err + return + } + + // Set up TLS context to catch clientHello. It will always error out during handshake + // as no certificate is set. + srv := tls.Server(conn, &tls.Config{ + GetConfigForClient: func(argHello *tls.ClientHelloInfo) (*tls.Config, error) { + sniHost = argHello.ServerName + return nil, nil + }, + }) + defer srv.Close() + + // Do the TLS handshake ignoring errors + _ = srv.Handshake() + + nameChan <- sniHost +} From 133ac67c2960135f7e0823cb7ba858101ba3d87f Mon Sep 17 00:00:00 2001 From: Joshua Price <111247018+JCPrice0024@users.noreply.github.com> Date: Tue, 24 Jan 2023 17:56:06 -0500 Subject: [PATCH 54/65] Improved the performance of CopyIn and CopyInSchema and added BufferQuoteIdentifier (#1100) --- conn.go | 25 +++++++++++++++++++------ copy.go | 35 +++++++++++++++++++++-------------- copy_test.go | 8 ++++++++ 3 files changed, 48 insertions(+), 20 deletions(-) diff --git a/conn.go b/conn.go index e70b386ff..23d694d0e 100644 --- a/conn.go +++ b/conn.go @@ -2,6 +2,7 @@ package pq import ( "bufio" + "bytes" "context" "crypto/md5" "crypto/sha256" @@ -1631,10 +1632,10 @@ func (rs *rows) NextResultSet() error { // QuoteIdentifier quotes an "identifier" (e.g. a table or a column name) to be // used as part of an SQL statement. For example: // -// tblname := "my_table" -// data := "my_data" -// quoted := pq.QuoteIdentifier(tblname) -// err := db.Exec(fmt.Sprintf("INSERT INTO %s VALUES ($1)", quoted), data) +// tblname := "my_table" +// data := "my_data" +// quoted := pq.QuoteIdentifier(tblname) +// err := db.Exec(fmt.Sprintf("INSERT INTO %s VALUES ($1)", quoted), data) // // Any double quotes in name will be escaped. The quoted identifier will be // case sensitive when used in a query. If the input string contains a zero @@ -1647,12 +1648,24 @@ func QuoteIdentifier(name string) string { return `"` + strings.Replace(name, `"`, `""`, -1) + `"` } +// BufferQuoteIdentifier satisfies the same purpose as QuoteIdentifier, but backed by a +// byte buffer. +func BufferQuoteIdentifier(name string, buffer *bytes.Buffer) { + end := strings.IndexRune(name, 0) + if end > -1 { + name = name[:end] + } + buffer.WriteRune('"') + buffer.WriteString(strings.Replace(name, `"`, `""`, -1)) + buffer.WriteRune('"') +} + // QuoteLiteral quotes a 'literal' (e.g. a parameter, often used to pass literal // to DDL and other statements that do not accept parameters) to be used as part // of an SQL statement. For example: // -// exp_date := pq.QuoteLiteral("2023-01-05 15:00:00Z") -// err := db.Exec(fmt.Sprintf("CREATE ROLE my_user VALID UNTIL %s", exp_date)) +// exp_date := pq.QuoteLiteral("2023-01-05 15:00:00Z") +// err := db.Exec(fmt.Sprintf("CREATE ROLE my_user VALID UNTIL %s", exp_date)) // // Any single quotes in name will be escaped. Any backslashes (i.e. "\") will be // replaced by two backslashes (i.e. "\\") and the C-style escape identifier diff --git a/copy.go b/copy.go index 2f5c1ec8a..a8f16b2b2 100644 --- a/copy.go +++ b/copy.go @@ -1,6 +1,7 @@ package pq import ( + "bytes" "context" "database/sql/driver" "encoding/binary" @@ -20,29 +21,35 @@ var ( // CopyIn creates a COPY FROM statement which can be prepared with // Tx.Prepare(). The target table should be visible in search_path. func CopyIn(table string, columns ...string) string { - stmt := "COPY " + QuoteIdentifier(table) + " (" + buffer := bytes.NewBufferString("COPY ") + BufferQuoteIdentifier(table, buffer) + buffer.WriteString(" (") + makeStmt(buffer, columns...) + return buffer.String() +} + +// MakeStmt makes the stmt string for CopyIn and CopyInSchema. +func makeStmt(buffer *bytes.Buffer, columns ...string) { + //s := bytes.NewBufferString() for i, col := range columns { if i != 0 { - stmt += ", " + buffer.WriteString(", ") } - stmt += QuoteIdentifier(col) + BufferQuoteIdentifier(col, buffer) } - stmt += ") FROM STDIN" - return stmt + buffer.WriteString(") FROM STDIN") } // CopyInSchema creates a COPY FROM statement which can be prepared with // Tx.Prepare(). func CopyInSchema(schema, table string, columns ...string) string { - stmt := "COPY " + QuoteIdentifier(schema) + "." + QuoteIdentifier(table) + " (" - for i, col := range columns { - if i != 0 { - stmt += ", " - } - stmt += QuoteIdentifier(col) - } - stmt += ") FROM STDIN" - return stmt + buffer := bytes.NewBufferString("COPY ") + BufferQuoteIdentifier(schema, buffer) + buffer.WriteRune('.') + BufferQuoteIdentifier(table, buffer) + buffer.WriteString(" (") + makeStmt(buffer, columns...) + return buffer.String() } type copyin struct { diff --git a/copy_test.go b/copy_test.go index 852f1be5a..5a1cf92af 100644 --- a/copy_test.go +++ b/copy_test.go @@ -500,3 +500,11 @@ func BenchmarkCopyIn(b *testing.B) { b.Fatalf("expected %d items, not %d", b.N, num) } } + +var bigTableColumns = []string{"ABIOGENETICALLY", "ABORIGINALITIES", "ABSORBABILITIES", "ABSORBEFACIENTS", "ABSORPTIOMETERS", "ABSTRACTIONISMS", "ABSTRACTIONISTS", "ACANTHOCEPHALAN", "ACCEPTABILITIES", "ACCEPTINGNESSES", "ACCESSARINESSES", "ACCESSIBILITIES", "ACCESSORINESSES", "ACCIDENTALITIES", "ACCIDENTOLOGIES", "ACCLIMATISATION", "ACCLIMATIZATION", "ACCOMMODATINGLY", "ACCOMMODATIONAL", "ACCOMPLISHMENTS", "ACCOUNTABLENESS", "ACCOUNTANTSHIPS", "ACCULTURATIONAL", "ACETOPHENETIDIN", "ACETYLSALICYLIC", "ACHONDROPLASIAS", "ACHONDROPLASTIC", "ACHROMATICITIES", "ACHROMATISATION", "ACHROMATIZATION", "ACIDIMETRICALLY", "ACKNOWLEDGEABLE", "ACKNOWLEDGEABLY", "ACKNOWLEDGEMENT", "ACKNOWLEDGMENTS", "ACQUIRABILITIES", "ACQUISITIVENESS", "ACRIMONIOUSNESS", "ACROPARESTHESIA", "ACTINOBIOLOGIES", "ACTINOCHEMISTRY", "ACTINOTHERAPIES", "ADAPTABLENESSES", "ADDITIONALITIES", "ADENOCARCINOMAS", "ADENOHYPOPHYSES", "ADENOHYPOPHYSIS", "ADENOIDECTOMIES", "ADIATHERMANCIES", "ADJUSTABILITIES", "ADMINISTRATIONS", "ADMIRABLENESSES", "ADMISSIBILITIES", "ADRENALECTOMIES", "ADSORBABILITIES", "ADVENTUROUSNESS", "ADVERSARINESSES", "ADVISABLENESSES", "AERODYNAMICALLY", "AERODYNAMICISTS", "AEROELASTICIANS", "AEROHYDROPLANES", "AEROLITHOLOGIES", "AEROSOLISATIONS", "AEROSOLIZATIONS", "AFFECTABILITIES", "AFFECTIVENESSES", "AFFORDABILITIES", "AFFRANCHISEMENT", "AFTERSENSATIONS", "AGGLUTINABILITY", "AGGRANDISEMENTS", "AGGRANDIZEMENTS", "AGGREGATENESSES", "AGRANULOCYTOSES", "AGRANULOCYTOSIS", "AGREEABLENESSES", "AGRIBUSINESSMAN", "AGRIBUSINESSMEN", "AGRICULTURALIST", "AIRWORTHINESSES", "ALCOHOLISATIONS", "ALCOHOLIZATIONS", "ALCOHOLOMETRIES", "ALEXIPHARMAKONS", "ALGORITHMICALLY", "ALKALINISATIONS", "ALKALINIZATIONS", "ALLEGORICALNESS", "ALLEGORISATIONS", "ALLEGORIZATIONS", "ALLELOMORPHISMS", "ALLERGENICITIES", "ALLOTETRAPLOIDS", "ALLOTETRAPLOIDY", "ALLOTRIOMORPHIC", "ALLOWABLENESSES", "ALPHABETISATION", "ALPHABETIZATION", "ALTERNATIVENESS", "ALTITUDINARIANS", "ALUMINOSILICATE", "ALUMINOTHERMIES", "AMARYLLIDACEOUS", "AMBASSADORSHIPS", "AMBIDEXTERITIES", "AMBIGUOUSNESSES", "AMBISEXUALITIES", "AMBITIOUSNESSES", "AMINOPEPTIDASES", "AMINOPHENAZONES", "AMMONIFICATIONS", "AMORPHOUSNESSES", "AMPHIDIPLOIDIES", "AMPHITHEATRICAL", "ANACOLUTHICALLY", "ANACREONTICALLY", "ANAESTHESIOLOGY", "ANAESTHETICALLY", "ANAGRAMMATISING", "ANAGRAMMATIZING", "ANALOGOUSNESSES", "ANALYZABILITIES", "ANAMORPHOSCOPES", "ANCYLOSTOMIASES", "ANCYLOSTOMIASIS", "ANDROGYNOPHORES", "ANDROMEDOTOXINS", "ANDROMONOECIOUS", "ANDROMONOECISMS", "ANESTHETIZATION", "ANFRACTUOSITIES", "ANGUSTIROSTRATE", "ANIMATRONICALLY", "ANISOTROPICALLY", "ANKYLOSTOMIASES", "ANKYLOSTOMIASIS", "ANNIHILATIONISM", "ANOMALISTICALLY", "ANOMALOUSNESSES", "ANONYMOUSNESSES", "ANSWERABILITIES", "ANTAGONISATIONS", "ANTAGONIZATIONS", "ANTAPHRODISIACS", "ANTEPENULTIMATE", "ANTHROPOBIOLOGY", "ANTHROPOCENTRIC", "ANTHROPOGENESES", "ANTHROPOGENESIS", "ANTHROPOGENETIC", "ANTHROPOLATRIES", "ANTHROPOLOGICAL", "ANTHROPOLOGISTS", "ANTHROPOMETRIES", "ANTHROPOMETRIST", "ANTHROPOMORPHIC", "ANTHROPOPATHIES", "ANTHROPOPATHISM", "ANTHROPOPHAGIES", "ANTHROPOPHAGITE", "ANTHROPOPHAGOUS", "ANTHROPOPHOBIAS", "ANTHROPOPHOBICS", "ANTHROPOPHUISMS", "ANTHROPOPSYCHIC", "ANTHROPOSOPHIES", "ANTHROPOSOPHIST", "ANTIABORTIONIST", "ANTIALCOHOLISMS", "ANTIAPHRODISIAC", "ANTIARRHYTHMICS", "ANTICAPITALISMS", "ANTICAPITALISTS", "ANTICARCINOGENS", "ANTICHOLESTEROL", "ANTICHOLINERGIC", "ANTICHRISTIANLY", "ANTICLERICALISM", "ANTICLIMACTICAL", "ANTICOINCIDENCE", "ANTICOLONIALISM", "ANTICOLONIALIST", "ANTICOMPETITIVE", "ANTICONVULSANTS", "ANTICONVULSIVES", "ANTIDEPRESSANTS", "ANTIDERIVATIVES", "ANTIDEVELOPMENT", "ANTIEDUCATIONAL", "ANTIEGALITARIAN", "ANTIFASHIONABLE", "ANTIFEDERALISTS", "ANTIFERROMAGNET", "ANTIFORECLOSURE", "ANTIHELMINTHICS", "ANTIHISTAMINICS", "ANTILIBERALISMS", "ANTILIBERTARIAN", "ANTILOGARITHMIC", "ANTIMATERIALISM", "ANTIMATERIALIST", "ANTIMETABOLITES", "ANTIMILITARISMS", "ANTIMILITARISTS", "ANTIMONARCHICAL", "ANTIMONARCHISTS", "ANTIMONOPOLISTS", "ANTINATIONALIST", "ANTINUCLEARISTS", "ANTIODONTALGICS", "ANTIPERISTALSES", "ANTIPERISTALSIS", "ANTIPERISTALTIC", "ANTIPERSPIRANTS", "ANTIPHLOGISTICS", "ANTIPORNOGRAPHY", "ANTIPROGRESSIVE", "ANTIQUARIANISMS", "ANTIRADICALISMS", "ANTIRATIONALISM", "ANTIRATIONALIST", "ANTIRATIONALITY", "ANTIREPUBLICANS", "ANTIROMANTICISM", "ANTISEGREGATION", "ANTISENTIMENTAL", "ANTISEPARATISTS", "ANTISEPTICISING", "ANTISEPTICIZING", "ANTISEXUALITIES", "ANTISHOPLIFTING", "ANTISOCIALITIES", "ANTISPECULATION", "ANTISPECULATIVE", "ANTISYPHILITICS", "ANTITHEORETICAL", "ANTITHROMBOTICS", "ANTITRADITIONAL", "ANTITRANSPIRANT", "ANTITRINITARIAN", "ANTITUBERCULOUS", "ANTIVIVISECTION", "APHELIOTROPISMS", "APOCALYPTICALLY", "APOCALYPTICISMS", "APOLIPOPROTEINS", "APOLITICALITIES", "APOPHTHEGMATISE", "APOPHTHEGMATIST", "APOPHTHEGMATIZE", "APOTHEGMATISING", "APOTHEGMATIZING", "APPEALABILITIES", "APPEALINGNESSES", "APPENDICULARIAN", "APPLICABILITIES", "APPRENTICEHOODS", "APPRENTICEMENTS", "APPRENTICESHIPS", "APPROACHABILITY", "APPROPINQUATING", "APPROPINQUATION", "APPROPINQUITIES", "APPROPRIATENESS", "ARACHNOIDITISES", "ARBITRARINESSES", "ARBORICULTURIST", "ARCHAEBACTERIUM", "ARCHAEOBOTANIES", "ARCHAEOBOTANIST", "ARCHAEOMETRISTS", "ARCHAEOPTERYXES", "ARCHAEZOOLOGIES", "ARCHEOASTRONOMY", "ARCHEOBOTANISTS", "ARCHEOLOGICALLY", "ARCHEOMAGNETISM", "ARCHEOZOOLOGIES", "ARCHEOZOOLOGIST", "ARCHGENETHLIACS", "ARCHIDIACONATES", "ARCHIEPISCOPACY", "ARCHIEPISCOPATE", "ARCHITECTURALLY", "ARCHPRIESTHOODS", "ARCHPRIESTSHIPS", "ARGUMENTATIVELY", "ARIBOFLAVINOSES", "ARIBOFLAVINOSIS", "AROMATHERAPISTS", "ARRONDISSEMENTS", "ARTERIALISATION", "ARTERIALIZATION", "ARTERIOGRAPHIES", "ARTIFICIALISING", "ARTIFICIALITIES", "ARTIFICIALIZING", "ASCLEPIADACEOUS", "ASSENTIVENESSES"} + +func BenchmarkCopy(b *testing.B) { + for i := 0; i < b.N; i++ { + CopyIn("temp", bigTableColumns...) + } +} From 3a6282fb835a9901f95a9ba9c3b21e16afed61f3 Mon Sep 17 00:00:00 2001 From: Joshua Price <111247018+JCPrice0024@users.noreply.github.com> Date: Tue, 24 Jan 2023 17:57:14 -0500 Subject: [PATCH 55/65] Reduced the complexity of handlePgpass (#1101) --- conn.go | 74 +++++++++++++++++++++++++++++++-------------------------- 1 file changed, 40 insertions(+), 34 deletions(-) diff --git a/conn.go b/conn.go index 23d694d0e..c47a8b3a4 100644 --- a/conn.go +++ b/conn.go @@ -261,46 +261,52 @@ func (cn *conn) handlePgpass(o values) { } defer file.Close() scanner := bufio.NewScanner(io.Reader(file)) + // From: https://github.com/tg/pgpass/blob/master/reader.go + for scanner.Scan() { + scanText(scanner.Text(), o) + } +} + +// GetFields is a helper function for scanText. +func getFields(s string) []string { + fs := make([]string, 0, 5) + f := make([]rune, 0, len(s)) + + var esc bool + for _, c := range s { + switch { + case esc: + f = append(f, c) + esc = false + case c == '\\': + esc = true + case c == ':': + fs = append(fs, string(f)) + f = f[:0] + default: + f = append(f, c) + } + } + return append(fs, string(f)) +} + +// ScanText assists HandlePgpass in it's objective. +func scanText(line string, o values) { hostname := o["host"] ntw, _ := network(o) port := o["port"] db := o["dbname"] username := o["user"] - // From: https://github.com/tg/pgpass/blob/master/reader.go - getFields := func(s string) []string { - fs := make([]string, 0, 5) - f := make([]rune, 0, len(s)) - - var esc bool - for _, c := range s { - switch { - case esc: - f = append(f, c) - esc = false - case c == '\\': - esc = true - case c == ':': - fs = append(fs, string(f)) - f = f[:0] - default: - f = append(f, c) - } - } - return append(fs, string(f)) + if len(line) != 0 || line[0] != '#' { + return } - for scanner.Scan() { - line := scanner.Text() - if len(line) == 0 || line[0] == '#' { - continue - } - split := getFields(line) - if len(split) != 5 { - continue - } - if (split[0] == "*" || split[0] == hostname || (split[0] == "localhost" && (hostname == "" || ntw == "unix"))) && (split[1] == "*" || split[1] == port) && (split[2] == "*" || split[2] == db) && (split[3] == "*" || split[3] == username) { - o["password"] = split[4] - return - } + split := getFields(line) + if len(split) == 5 { + return + } + if (split[0] == "*" || split[0] == hostname || (split[0] == "localhost" && (hostname == "" || ntw == "unix"))) && (split[1] == "*" || split[1] == port) && (split[2] == "*" || split[2] == db) && (split[3] == "*" || split[3] == username) { + o["password"] = split[4] + return } } From a2a317360bd33f51c5804bde348c35a65f5644b1 Mon Sep 17 00:00:00 2001 From: Oliver Tan Date: Wed, 25 Jan 2023 10:00:38 +1100 Subject: [PATCH 56/65] Update test.yml --- .github/workflows/test.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 2bbc7efd0..ef1d4e388 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -1,8 +1,6 @@ name: Test -on: - pull_request: - branches: [ master ] +on: [push, pull_request] jobs: test: From 922c00e176fb3960d912dc2c7f67ea2cf18d27b0 Mon Sep 17 00:00:00 2001 From: Oliver Tan Date: Wed, 25 Jan 2023 10:01:17 +1100 Subject: [PATCH 57/65] Update codeql-analysis.yml --- .github/workflows/codeql-analysis.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 4cbfc309a..01f91366d 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -1,8 +1,6 @@ name: "CodeQL" -on: - push: - branches: [ master ] +on: [push, pull_request] jobs: analyze: From 96e73eb9aa7ba849b24eae15477456d8bbb1c9b7 Mon Sep 17 00:00:00 2001 From: Evan Jones Date: Fri, 15 Apr 2022 14:46:00 -0400 Subject: [PATCH 58/65] conn: Implement driver.Validator, SessionResetter for cancelation Commit 8446d16b89 released in 1.10.4 changed how some cancelled query errors were returned. This has caused a lib/pq application I work on to start returning "driver: bad connection". This is because we were cancelling a query, after looking at some of the rows. This causes a "bad" connection to be returned to the connection pool. To prevent this, implement the driver.Validator and driver.SessionResetter interfaces. The database/sql/driver package recommends implementing them: "All Conn implementations should implement the following interfaces: Pinger, SessionResetter, and Validator" Add two tests for this behaviour. One of these tests passed with 1.10.3 but fails with newer versions. The other never passed, but does after this change. --- conn.go | 18 ++++++++++++ issues_test.go | 77 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 95 insertions(+) diff --git a/conn.go b/conn.go index c47a8b3a4..1f7ebc657 100644 --- a/conn.go +++ b/conn.go @@ -2081,3 +2081,21 @@ func alnumLowerASCII(ch rune) rune { } return -1 // discard } + +// The database/sql/driver package says: +// All Conn implementations should implement the following interfaces: Pinger, SessionResetter, and Validator. +var _ driver.Pinger = &conn{} +var _ driver.SessionResetter = &conn{} +var _ driver.Validator = &conn{} + +func (cn *conn) ResetSession(ctx context.Context) error { + // Ensure bad connections are reported: From database/sql/driver: + // If a connection is never returned to the connection pool but immediately reused, then + // ResetSession is called prior to reuse but IsValid is not called. + return cn.err.get() +} + +func (cn *conn) IsValid() bool { + // panic("TODO IsValid") + return cn.err.get() == nil +} diff --git a/issues_test.go b/issues_test.go index 26a70282b..e2c93925a 100644 --- a/issues_test.go +++ b/issues_test.go @@ -2,6 +2,7 @@ package pq import ( "context" + "database/sql" "errors" "testing" "time" @@ -79,3 +80,79 @@ func TestIssue1062(t *testing.T) { } } } + +func connIsValid(t *testing.T, db *sql.DB) { + t.Helper() + + ctx := context.Background() + conn, err := db.Conn(ctx) + if err != nil { + t.Fatal(err) + } + defer conn.Close() + + // the connection must be valid + err = conn.PingContext(ctx) + if err != nil { + t.Errorf("PingContext err=%#v", err) + } + // close must not return an error + err = conn.Close() + if err != nil { + t.Errorf("Close err=%#v", err) + } +} + +func TestQueryCancelRace(t *testing.T) { + db := openTestConn(t) + defer db.Close() + + // cancel a query while executing on Postgres: must return the cancelled error code + ctx, cancel := context.WithCancel(context.Background()) + go func() { + time.Sleep(10 * time.Millisecond) + cancel() + }() + row := db.QueryRowContext(ctx, "select pg_sleep(0.5)") + var pgSleepVoid string + err := row.Scan(&pgSleepVoid) + if pgErr := (*Error)(nil); !(errors.As(err, &pgErr) && pgErr.Code == cancelErrorCode) { + t.Fatalf("expected cancelled error; err=%#v", err) + } + + // get a connection: it must be a valid + connIsValid(t, db) +} + +// Test cancelling a scan after it is started. This broke with 1.10.4. +func TestQueryCancelledReused(t *testing.T) { + db := openTestConn(t) + defer db.Close() + + ctx, cancel := context.WithCancel(context.Background()) + // run a query that returns a lot of data + rows, err := db.QueryContext(ctx, "select generate_series(1, 10000)") + if err != nil { + t.Fatal(err) + } + + // scan the first value + if !rows.Next() { + t.Error("expected rows.Next() to return true") + } + var i int + err = rows.Scan(&i) + if err != nil { + t.Fatal(err) + } + if i != 1 { + t.Error(i) + } + + // cancel the context and close rows, ignoring errors + cancel() + rows.Close() + + // get a connection: it must be valid + connIsValid(t, db) +} From c10fcfec9389c2e257cf50b7e6d218dde1d062f4 Mon Sep 17 00:00:00 2001 From: Evan Jones Date: Sun, 29 Jan 2023 13:16:30 -0500 Subject: [PATCH 59/65] remove stray debugging code --- conn.go | 1 - 1 file changed, 1 deletion(-) diff --git a/conn.go b/conn.go index 1f7ebc657..94f659c0f 100644 --- a/conn.go +++ b/conn.go @@ -2096,6 +2096,5 @@ func (cn *conn) ResetSession(ctx context.Context) error { } func (cn *conn) IsValid() bool { - // panic("TODO IsValid") return cn.err.get() == nil } From d8d93a38df0048951ff15830d793024f890f6c3c Mon Sep 17 00:00:00 2001 From: Thanatat Tamtan Date: Wed, 26 Apr 2023 11:27:51 +0700 Subject: [PATCH 60/65] fix handle pgpass (#1120) --- conn.go | 17 ++++++++++------- conn_test.go | 4 ---- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/conn.go b/conn.go index 94f659c0f..a981bc0ab 100644 --- a/conn.go +++ b/conn.go @@ -263,7 +263,9 @@ func (cn *conn) handlePgpass(o values) { scanner := bufio.NewScanner(io.Reader(file)) // From: https://github.com/tg/pgpass/blob/master/reader.go for scanner.Scan() { - scanText(scanner.Text(), o) + if scanText(scanner.Text(), o) { + break + } } } @@ -291,23 +293,24 @@ func getFields(s string) []string { } // ScanText assists HandlePgpass in it's objective. -func scanText(line string, o values) { +func scanText(line string, o values) bool { hostname := o["host"] ntw, _ := network(o) port := o["port"] db := o["dbname"] username := o["user"] - if len(line) != 0 || line[0] != '#' { - return + if len(line) == 0 || line[0] == '#' { + return false } split := getFields(line) - if len(split) == 5 { - return + if len(split) != 5 { + return false } if (split[0] == "*" || split[0] == hostname || (split[0] == "localhost" && (hostname == "" || ntw == "unix"))) && (split[1] == "*" || split[1] == port) && (split[2] == "*" || split[2] == db) && (split[3] == "*" || split[3] == username) { o["password"] = split[4] - return + return true } + return false } func (cn *conn) writeBuf(b byte) *writeBuf { diff --git a/conn_test.go b/conn_test.go index eb2595705..a44c0f0ff 100644 --- a/conn_test.go +++ b/conn_test.go @@ -144,10 +144,6 @@ func TestOpenURL(t *testing.T) { const pgpassFile = "/tmp/pqgotest_pgpass" func TestPgpass(t *testing.T) { - if os.Getenv("TRAVIS") != "true" { - t.Skip("not running under Travis, skipping pgpass tests") - } - testAssert := func(conninfo string, expected string, reason string) { conn, err := openTestConnConninfo(conninfo) if err != nil { From 2a217b94f5ccd3de31aec4152a541b9ff64bed05 Mon Sep 17 00:00:00 2001 From: Oliver Tan Date: Wed, 26 Apr 2023 14:34:24 +1000 Subject: [PATCH 61/65] add version check for go 1.15 (#1123) --- conn.go | 19 ++++++++++++++----- conn_go115.go | 8 ++++++++ 2 files changed, 22 insertions(+), 5 deletions(-) create mode 100644 conn_go115.go diff --git a/conn.go b/conn.go index a981bc0ab..da4ff9de6 100644 --- a/conn.go +++ b/conn.go @@ -113,7 +113,9 @@ type defaultDialer struct { func (d defaultDialer) Dial(network, address string) (net.Conn, error) { return d.d.Dial(network, address) } -func (d defaultDialer) DialTimeout(network, address string, timeout time.Duration) (net.Conn, error) { +func (d defaultDialer) DialTimeout( + network, address string, timeout time.Duration, +) (net.Conn, error) { ctx, cancel := context.WithTimeout(context.Background(), timeout) defer cancel() return d.DialContext(ctx, network, address) @@ -775,7 +777,9 @@ func (noRows) RowsAffected() (int64, error) { // 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 []fieldDesc, forceText bool) (colFmts []format, colFmtData []byte) { +func decideColumnFormats( + colTyps []fieldDesc, forceText bool, +) (colFmts []format, colFmtData []byte) { if len(colTyps) == 0 { return nil, colFmtDataAllText } @@ -1830,7 +1834,11 @@ func (cn *conn) readParseResponse() { } } -func (cn *conn) readStatementDescribeResponse() (paramTyps []oid.Oid, colNames []string, colTyps []fieldDesc) { +func (cn *conn) readStatementDescribeResponse() ( + paramTyps []oid.Oid, + colNames []string, + colTyps []fieldDesc, +) { for { t, r := cn.recv1() switch t { @@ -1918,7 +1926,9 @@ func (cn *conn) postExecuteWorkaround() { } // Only for Exec(), since we ignore the returned data -func (cn *conn) readExecuteResponse(protocolState string) (res driver.Result, commandTag string, err error) { +func (cn *conn) readExecuteResponse( + protocolState string, +) (res driver.Result, commandTag string, err error) { for { t, r := cn.recv1() switch t { @@ -2089,7 +2099,6 @@ func alnumLowerASCII(ch rune) rune { // All Conn implementations should implement the following interfaces: Pinger, SessionResetter, and Validator. var _ driver.Pinger = &conn{} var _ driver.SessionResetter = &conn{} -var _ driver.Validator = &conn{} func (cn *conn) ResetSession(ctx context.Context) error { // Ensure bad connections are reported: From database/sql/driver: diff --git a/conn_go115.go b/conn_go115.go new file mode 100644 index 000000000..f4ef030f9 --- /dev/null +++ b/conn_go115.go @@ -0,0 +1,8 @@ +//go:build go1.15 +// +build go1.15 + +package pq + +import "database/sql/driver" + +var _ driver.Validator = &conn{} From 3bc0bdfe5e117ff3f391e814fccf688db295075a Mon Sep 17 00:00:00 2001 From: Thanatat Tamtan Date: Fri, 28 Apr 2023 20:32:47 +0700 Subject: [PATCH 62/65] update workflows (#1124) --- .github/workflows/codeql-analysis.yml | 2 +- .github/workflows/test.yml | 9 ++++-- ssl_test.go | 44 +++++++++++++++++---------- 3 files changed, 36 insertions(+), 19 deletions(-) diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 01f91366d..3a09b77cb 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -13,7 +13,7 @@ jobs: steps: - name: Checkout repo - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Initialize CodeQL uses: github/codeql-action/init@v1 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index ef1d4e388..e1b413c9e 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -9,12 +9,17 @@ jobs: fail-fast: false matrix: postgres: + - '15' + - '14' - '13' - '12' - '11' - '10' - '9.6' go: + - '1.20' + - '1.19' + - '1.18' - '1.17' - '1.16' - '1.15' @@ -169,10 +174,10 @@ jobs: docker exec pg createuser -h localhost -U postgres -DRS pqgosslcert - name: check out code into the Go module directory - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: set up go - uses: actions/setup-go@v2 + uses: actions/setup-go@v4 with: go-version: ${{ matrix.go }} id: go diff --git a/ssl_test.go b/ssl_test.go index 64d68cf4a..4c631b81b 100644 --- a/ssl_test.go +++ b/ssl_test.go @@ -8,6 +8,7 @@ import ( "crypto/tls" "crypto/x509" "database/sql" + "errors" "fmt" "io" "net" @@ -86,11 +87,13 @@ func TestSSLVerifyFull(t *testing.T) { if err == nil { t.Fatal("expected error") } - _, ok := err.(x509.UnknownAuthorityError) - if !ok { - _, ok := err.(x509.HostnameError) - if !ok { - t.Fatalf("expected x509.UnknownAuthorityError or x509.HostnameError, got %#+v", err) + { + var x509err x509.UnknownAuthorityError + if !errors.As(err, &x509err) { + var x509err x509.HostnameError + if !errors.As(err, &x509err) { + t.Fatalf("expected x509.UnknownAuthorityError or x509.HostnameError, got %#+v", err) + } } } @@ -101,9 +104,11 @@ func TestSSLVerifyFull(t *testing.T) { if err == nil { t.Fatal("expected error") } - _, ok = err.(x509.HostnameError) - if !ok { - t.Fatalf("expected x509.HostnameError, got %#+v", err) + { + var x509err x509.HostnameError + if !errors.As(err, &x509err) { + t.Fatalf("expected x509.HostnameError, got %#+v", err) + } } // OK _, err = openSSLConn(t, rootCert+"host=postgres sslmode=verify-full user=pqgossltest") @@ -126,9 +131,11 @@ func TestSSLRequireWithRootCert(t *testing.T) { if err == nil { t.Fatal("expected error") } - _, ok := err.(x509.UnknownAuthorityError) - if !ok { - t.Fatalf("expected x509.UnknownAuthorityError, got %s, %#+v", err, err) + { + var x509err x509.UnknownAuthorityError + if !errors.As(err, &x509err) { + t.Fatalf("expected x509.UnknownAuthorityError, got %s, %#+v", err, err) + } } nonExistentCertPath := filepath.Join(os.Getenv("PQSSLCERTTEST_PATH"), "non_existent.crt") @@ -164,7 +171,8 @@ func TestSSLVerifyCA(t *testing.T) { // Not OK according to the system CA { _, err := openSSLConn(t, "host=postgres sslmode=verify-ca user=pqgossltest") - if _, ok := err.(x509.UnknownAuthorityError); !ok { + var x509err x509.UnknownAuthorityError + if !errors.As(err, &x509err) { t.Fatalf("expected %T, got %#+v", x509.UnknownAuthorityError{}, err) } } @@ -172,7 +180,8 @@ func TestSSLVerifyCA(t *testing.T) { // Still not OK according to the system CA; empty sslrootcert is treated as unspecified. { _, err := openSSLConn(t, "host=postgres sslmode=verify-ca user=pqgossltest sslrootcert=''") - if _, ok := err.(x509.UnknownAuthorityError); !ok { + var x509err x509.UnknownAuthorityError + if !errors.As(err, &x509err) { t.Fatalf("expected %T, got %#+v", x509.UnknownAuthorityError{}, err) } } @@ -243,7 +252,8 @@ func TestSSLClientCertificates(t *testing.T) { // Cert present, key not specified, should fail { _, err := openSSLConn(t, baseinfo+" sslcert="+sslcert) - if _, ok := err.(*os.PathError); !ok { + var pathErr *os.PathError + if !errors.As(err, &pathErr) { t.Fatalf("expected %T, got %#+v", (*os.PathError)(nil), err) } } @@ -251,7 +261,8 @@ func TestSSLClientCertificates(t *testing.T) { // Cert present, empty key specified, should fail { _, err := openSSLConn(t, baseinfo+" sslcert="+sslcert+" sslkey=''") - if _, ok := err.(*os.PathError); !ok { + var pathErr *os.PathError + if !errors.As(err, &pathErr) { t.Fatalf("expected %T, got %#+v", (*os.PathError)(nil), err) } } @@ -259,7 +270,8 @@ func TestSSLClientCertificates(t *testing.T) { // Cert present, non-existent key, should fail { _, err := openSSLConn(t, baseinfo+" sslcert="+sslcert+" sslkey=/tmp/filedoesnotexist") - if _, ok := err.(*os.PathError); !ok { + var pathErr *os.PathError + if !errors.As(err, &pathErr) { t.Fatalf("expected %T, got %#+v", (*os.PathError)(nil), err) } } From 381d253611d666974d43dfa634d29fe16ea9e293 Mon Sep 17 00:00:00 2001 From: Tamir Duberstein Date: Wed, 3 May 2023 19:09:34 -0400 Subject: [PATCH 63/65] Add support for NamedValueChecker interface (#1125) Added in 1.9: https://go.dev/doc/go1.9#minor_library_changes. --- conn_go19.go | 35 ++++++++++++++++++++ conn_go19_test.go | 83 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 118 insertions(+) create mode 100644 conn_go19.go create mode 100644 conn_go19_test.go diff --git a/conn_go19.go b/conn_go19.go new file mode 100644 index 000000000..003601565 --- /dev/null +++ b/conn_go19.go @@ -0,0 +1,35 @@ +//go:build go1.9 +// +build go1.9 + +package pq + +import ( + "database/sql/driver" + "reflect" +) + +var _ driver.NamedValueChecker = (*conn)(nil) + +func (c *conn) CheckNamedValue(nv *driver.NamedValue) error { + if _, ok := nv.Value.(driver.Valuer); ok { + // Ignore Valuer, for backward compatibility with pq.Array(). + return driver.ErrSkip + } + + // Ignoring []byte / []uint8. + if _, ok := nv.Value.([]uint8); ok { + return driver.ErrSkip + } + + v := reflect.ValueOf(nv.Value) + if v.Kind() == reflect.Ptr { + v = v.Elem() + } + if v.Kind() == reflect.Slice { + var err error + nv.Value, err = Array(v.Interface()).Value() + return err + } + + return driver.ErrSkip +} diff --git a/conn_go19_test.go b/conn_go19_test.go new file mode 100644 index 000000000..43c982631 --- /dev/null +++ b/conn_go19_test.go @@ -0,0 +1,83 @@ +//go:build go1.9 +// +build go1.9 + +package pq + +import ( + "fmt" + "reflect" + "testing" +) + +func TestArrayArg(t *testing.T) { + db := openTestConn(t) + defer db.Close() + + for _, tc := range []struct { + pgType string + in, out interface{} + }{ + { + pgType: "int[]", + in: []int{245, 231}, + out: []int64{245, 231}, + }, + { + pgType: "int[]", + in: &[]int{245, 231}, + out: []int64{245, 231}, + }, + { + pgType: "int[]", + in: []int64{245, 231}, + }, + { + pgType: "int[]", + in: &[]int64{245, 231}, + out: []int64{245, 231}, + }, + { + pgType: "varchar[]", + in: []string{"hello", "world"}, + }, + { + pgType: "varchar[]", + in: &[]string{"hello", "world"}, + out: []string{"hello", "world"}, + }, + } { + if tc.out == nil { + tc.out = tc.in + } + t.Run(fmt.Sprintf("%#v", tc.in), func(t *testing.T) { + r, err := db.Query(fmt.Sprintf("SELECT $1::%s", tc.pgType), tc.in) + if err != nil { + t.Fatal(err) + } + defer r.Close() + + if !r.Next() { + if r.Err() != nil { + t.Fatal(r.Err()) + } + t.Fatal("expected row") + } + + defer func() { + if r.Next() { + t.Fatal("unexpected row") + } + }() + + got := reflect.New(reflect.TypeOf(tc.out)) + if err := r.Scan(Array(got.Interface())); err != nil { + t.Fatal(err) + } + + if !reflect.DeepEqual(tc.out, got.Elem().Interface()) { + t.Errorf("got %v, want %v", got, tc.out) + } + }) + } + +} From 300ec9b8889edd93bf3fcd23c05d7d317b65afd4 Mon Sep 17 00:00:00 2001 From: Timofey Koolin Date: Wed, 28 Jun 2023 07:01:37 +0300 Subject: [PATCH 64/65] fix error checking in tests (#1127) * fix errors out in tests * show error if unexpected error in TestRuntimeParameters --- conn_test.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/conn_test.go b/conn_test.go index a44c0f0ff..8fb81ac8e 100644 --- a/conn_test.go +++ b/conn_test.go @@ -318,7 +318,7 @@ func TestStatment(t *testing.T) { if !r1.Next() { if r.Err() != nil { - t.Fatal(r1.Err()) + t.Fatal(r.Err()) } t.Fatal("expected row") } @@ -1290,7 +1290,7 @@ func TestNullAfterNonNull(t *testing.T) { if !r.Next() { if r.Err() != nil { - t.Fatal(err) + t.Fatal(r.Err()) } t.Fatal("expected row") } @@ -1305,7 +1305,7 @@ func TestNullAfterNonNull(t *testing.T) { if !r.Next() { if r.Err() != nil { - t.Fatal(err) + t.Fatal(r.Err()) } t.Fatal("expected row") } @@ -1501,7 +1501,7 @@ func TestRuntimeParameters(t *testing.T) { } value, success := tryGetParameterValue() - if success != test.success && !test.success { + if success != test.success && !success { t.Fatalf("%v: unexpected error: %v", test.conninfo, err) } if success != test.success { From 3d613208bca2e74f2a20e04126ed30bcb5c4cc27 Mon Sep 17 00:00:00 2001 From: Hector Rivas Gandara Date: Fri, 7 Jul 2023 16:57:34 +0100 Subject: [PATCH 65/65] Use and respect the passfile connection parameter (#1129) * Use and respect the passfile connection parameter The postgres documentation[1] regarding the password file, states that: password file to use can be specified using the connection parameter passfile or the environment variable PGPASSFILE. The current implementation of lib/pq only respects the environment variable PGPASSFILE. This is not correct, but also limiting, as the PGPASSFILE is global and we might want to use different files for different clients in the same program. Fixing that is easy, by just checking the parameter passfile first, and if not, pull the value from PGPASSFILE. This also moves the parsing of PGPASSFILE to `parseEnviron`. Now the connection only checks the parameter passfile, that is populated by `parseEnviron`. [1] https://www.postgresql.org/docs/current/libpq-pgpass.html --- conn.go | 5 ++++- conn_test.go | 25 ++++++++++++++++--------- connector_test.go | 19 +++++++++++++++++++ 3 files changed, 39 insertions(+), 10 deletions(-) diff --git a/conn.go b/conn.go index da4ff9de6..bc0983608 100644 --- a/conn.go +++ b/conn.go @@ -233,7 +233,8 @@ func (cn *conn) handlePgpass(o values) { if _, ok := o["password"]; ok { return } - filename := os.Getenv("PGPASSFILE") + // Get passfile from the options + filename := o["passfile"] if filename == "" { // XXX this code doesn't work on Windows where the default filename is // XXX %APPDATA%\postgresql\pgpass.conf @@ -2038,6 +2039,8 @@ func parseEnviron(env []string) (out map[string]string) { accrue("user") case "PGPASSWORD": accrue("password") + case "PGPASSFILE": + accrue("passfile") case "PGSERVICE", "PGSERVICEFILE", "PGREALM": unsupported() case "PGOPTIONS": diff --git a/conn_test.go b/conn_test.go index 8fb81ac8e..96f70dddf 100644 --- a/conn_test.go +++ b/conn_test.go @@ -174,7 +174,10 @@ func TestPgpass(t *testing.T) { } testAssert("", "ok", "missing .pgpass, unexpected error %#v") os.Setenv("PGPASSFILE", pgpassFile) + defer os.Unsetenv("PGPASSFILE") testAssert("host=/tmp", "fail", ", unexpected error %#v") + os.Unsetenv("PGPASSFILE") + os.Remove(pgpassFile) pgpass, err := os.OpenFile(pgpassFile, os.O_RDWR|os.O_CREATE, 0644) if err != nil { @@ -189,6 +192,7 @@ localhost:*:*:*:pass_C if err != nil { t.Fatalf("Unexpected error writing pgpass file %#v", err) } + defer os.Remove(pgpassFile) pgpass.Close() assertPassword := func(extra values, expected string) { @@ -211,19 +215,22 @@ localhost:*:*:*:pass_C t.Fatalf("For %v expected %s got %s", extra, expected, pw) } } + // missing passfile means empty psasword + assertPassword(values{"host": "server", "dbname": "some_db", "user": "some_user"}, "") // wrong permissions for the pgpass file means it should be ignored - assertPassword(values{"host": "example.com", "user": "foo"}, "") + assertPassword(values{"host": "example.com", "passfile": pgpassFile, "user": "foo"}, "") // fix the permissions and check if it has taken effect os.Chmod(pgpassFile, 0600) - assertPassword(values{"host": "server", "dbname": "some_db", "user": "some_user"}, "pass_A") - assertPassword(values{"host": "example.com", "user": "foo"}, "pass_fallback") - assertPassword(values{"host": "example.com", "dbname": "some_db", "user": "some_user"}, "pass_B") + + assertPassword(values{"host": "server", "passfile": pgpassFile, "dbname": "some_db", "user": "some_user"}, "pass_A") + assertPassword(values{"host": "example.com", "passfile": pgpassFile, "user": "foo"}, "pass_fallback") + assertPassword(values{"host": "example.com", "passfile": pgpassFile, "dbname": "some_db", "user": "some_user"}, "pass_B") // localhost also matches the default "" and UNIX sockets - assertPassword(values{"host": "", "user": "some_user"}, "pass_C") - assertPassword(values{"host": "/tmp", "user": "some_user"}, "pass_C") - // cleanup - os.Remove(pgpassFile) - os.Setenv("PGPASSFILE", "") + assertPassword(values{"host": "", "passfile": pgpassFile, "user": "some_user"}, "pass_C") + assertPassword(values{"host": "/tmp", "passfile": pgpassFile, "user": "some_user"}, "pass_C") + // passfile connection parameter takes precedence + os.Setenv("PGPASSFILE", "/tmp") + assertPassword(values{"host": "server", "passfile": pgpassFile, "dbname": "some_db", "user": "some_user"}, "pass_A") } func TestExec(t *testing.T) { diff --git a/connector_test.go b/connector_test.go index d68810e9e..ab34fc50b 100644 --- a/connector_test.go +++ b/connector_test.go @@ -7,6 +7,7 @@ import ( "context" "database/sql" "database/sql/driver" + "os" "testing" ) @@ -66,3 +67,21 @@ func TestNewConnector_Driver(t *testing.T) { } txn.Rollback() } + +func TestNewConnector_Environ(t *testing.T) { + name := "" + os.Setenv("PGPASSFILE", "/tmp/.pgpass") + defer os.Unsetenv("PGPASSFILE") + c, err := NewConnector(name) + if err != nil { + t.Fatal(err) + } + for key, expected := range map[string]string{ + "passfile": "/tmp/.pgpass", + } { + if got := c.opts[key]; got != expected { + t.Fatalf("Getting values from environment variables, for %v expected %s got %s", key, expected, got) + } + } + +}