| @@ -1,6 +1,6 @@ | |||
| # pq - A pure Go postgres driver for Go's database/sql package | |||
| [](https://travis-ci.org/lib/pq) | |||
| [](https://travis-ci.org/lib/pq) | |||
| ## Install | |||
| @@ -13,7 +13,7 @@ import ( | |||
| var typeByteSlice = reflect.TypeOf([]byte{}) | |||
| var typeDriverValuer = reflect.TypeOf((*driver.Valuer)(nil)).Elem() | |||
| var typeSqlScanner = reflect.TypeOf((*sql.Scanner)(nil)).Elem() | |||
| var typeSQLScanner = reflect.TypeOf((*sql.Scanner)(nil)).Elem() | |||
| // Array returns the optimal driver.Valuer and sql.Scanner for an array or | |||
| // slice of any dimension. | |||
| @@ -70,6 +70,9 @@ func (a *BoolArray) Scan(src interface{}) error { | |||
| 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 BoolArray", src) | |||
| @@ -80,7 +83,7 @@ func (a *BoolArray) scanBytes(src []byte) error { | |||
| if err != nil { | |||
| return err | |||
| } | |||
| if len(elems) == 0 { | |||
| if *a != nil && len(elems) == 0 { | |||
| *a = (*a)[:0] | |||
| } else { | |||
| b := make(BoolArray, len(elems)) | |||
| @@ -141,6 +144,9 @@ func (a *ByteaArray) Scan(src interface{}) error { | |||
| 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 ByteaArray", src) | |||
| @@ -151,7 +157,7 @@ func (a *ByteaArray) scanBytes(src []byte) error { | |||
| if err != nil { | |||
| return err | |||
| } | |||
| if len(elems) == 0 { | |||
| if *a != nil && len(elems) == 0 { | |||
| *a = (*a)[:0] | |||
| } else { | |||
| b := make(ByteaArray, len(elems)) | |||
| @@ -210,6 +216,9 @@ func (a *Float64Array) Scan(src interface{}) error { | |||
| 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 Float64Array", src) | |||
| @@ -220,7 +229,7 @@ func (a *Float64Array) scanBytes(src []byte) error { | |||
| if err != nil { | |||
| return err | |||
| } | |||
| if len(elems) == 0 { | |||
| if *a != nil && len(elems) == 0 { | |||
| *a = (*a)[:0] | |||
| } else { | |||
| b := make(Float64Array, len(elems)) | |||
| @@ -269,7 +278,7 @@ func (GenericArray) evaluateDestination(rt reflect.Type) (reflect.Type, func([]b | |||
| // TODO calculate the assign function for other types | |||
| // TODO repeat this section on the element type of arrays or slices (multidimensional) | |||
| { | |||
| if reflect.PtrTo(rt).Implements(typeSqlScanner) { | |||
| if reflect.PtrTo(rt).Implements(typeSQLScanner) { | |||
| // dest is always addressable because it is an element of a slice. | |||
| assign = func(src []byte, dest reflect.Value) (err error) { | |||
| ss := dest.Addr().Interface().(sql.Scanner) | |||
| @@ -320,6 +329,11 @@ func (a GenericArray) Scan(src interface{}) error { | |||
| return a.scanBytes(src, dv) | |||
| case string: | |||
| return a.scanBytes([]byte(src), dv) | |||
| case nil: | |||
| if dv.Kind() == reflect.Slice { | |||
| dv.Set(reflect.Zero(dv.Type())) | |||
| return nil | |||
| } | |||
| } | |||
| return fmt.Errorf("pq: cannot convert %T to %s", src, dv.Type()) | |||
| @@ -386,7 +400,13 @@ func (a GenericArray) Value() (driver.Value, error) { | |||
| rv := reflect.ValueOf(a.A) | |||
| if k := rv.Kind(); k != reflect.Array && k != reflect.Slice { | |||
| switch rv.Kind() { | |||
| case reflect.Slice: | |||
| if rv.IsNil() { | |||
| return nil, nil | |||
| } | |||
| case reflect.Array: | |||
| default: | |||
| return nil, fmt.Errorf("pq: Unable to convert %T to array", a.A) | |||
| } | |||
| @@ -412,6 +432,9 @@ func (a *Int64Array) Scan(src interface{}) error { | |||
| 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 Int64Array", src) | |||
| @@ -422,7 +445,7 @@ func (a *Int64Array) scanBytes(src []byte) error { | |||
| if err != nil { | |||
| return err | |||
| } | |||
| if len(elems) == 0 { | |||
| if *a != nil && len(elems) == 0 { | |||
| *a = (*a)[:0] | |||
| } else { | |||
| b := make(Int64Array, len(elems)) | |||
| @@ -470,6 +493,9 @@ func (a *StringArray) Scan(src interface{}) error { | |||
| 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 StringArray", src) | |||
| @@ -480,7 +506,7 @@ func (a *StringArray) scanBytes(src []byte) error { | |||
| if err != nil { | |||
| return err | |||
| } | |||
| if len(elems) == 0 { | |||
| if *a != nil && len(elems) == 0 { | |||
| *a = (*a)[:0] | |||
| } else { | |||
| b := make(StringArray, len(elems)) | |||
| @@ -561,7 +587,7 @@ func appendArrayElement(b []byte, rv reflect.Value) ([]byte, string, error) { | |||
| } | |||
| } | |||
| var del string = "," | |||
| var del = "," | |||
| var err error | |||
| var iv interface{} = rv.Interface() | |||
| @@ -639,6 +665,9 @@ Element: | |||
| for i < len(src) { | |||
| switch src[i] { | |||
| case '{': | |||
| if depth == len(dims) { | |||
| break Element | |||
| } | |||
| depth++ | |||
| dims[depth-1] = 0 | |||
| i++ | |||
| @@ -680,11 +709,11 @@ Element: | |||
| } | |||
| for i < len(src) { | |||
| if bytes.HasPrefix(src[i:], del) { | |||
| if bytes.HasPrefix(src[i:], del) && depth > 0 { | |||
| dims[depth-1]++ | |||
| i += len(del) | |||
| goto Element | |||
| } else if src[i] == '}' { | |||
| } else if src[i] == '}' && depth > 0 { | |||
| dims[depth-1]++ | |||
| depth-- | |||
| i++ | |||
| @@ -3,15 +3,12 @@ package pq | |||
| import ( | |||
| "bufio" | |||
| "crypto/md5" | |||
| "crypto/tls" | |||
| "crypto/x509" | |||
| "database/sql" | |||
| "database/sql/driver" | |||
| "encoding/binary" | |||
| "errors" | |||
| "fmt" | |||
| "io" | |||
| "io/ioutil" | |||
| "net" | |||
| "os" | |||
| "os/user" | |||
| @@ -30,22 +27,22 @@ 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.") | |||
| 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") | |||
| errUnexpectedReady = errors.New("unexpected ReadyForQuery") | |||
| errNoRowsAffected = errors.New("no RowsAffected available after the empty statement") | |||
| errNoLastInsertId = errors.New("no LastInsertId available after the empty statement") | |||
| errNoLastInsertID = errors.New("no LastInsertId available after the empty statement") | |||
| ) | |||
| type drv struct{} | |||
| type Driver struct{} | |||
| func (d *drv) Open(name string) (driver.Conn, error) { | |||
| func (d *Driver) Open(name string) (driver.Conn, error) { | |||
| return Open(name) | |||
| } | |||
| func init() { | |||
| sql.Register("postgres", &drv{}) | |||
| sql.Register("postgres", &Driver{}) | |||
| } | |||
| type parameterStatus struct { | |||
| @@ -101,6 +98,15 @@ type conn struct { | |||
| namei int | |||
| scratch [512]byte | |||
| txnStatus transactionStatus | |||
| txnFinish func() | |||
| // Save connection arguments to use during CancelRequest. | |||
| dialer Dialer | |||
| opts values | |||
| // Cancellation key data for use with CancelRequest messages. | |||
| processID int | |||
| secretKey int | |||
| parameterStatus parameterStatus | |||
| @@ -125,9 +131,9 @@ type conn struct { | |||
| } | |||
| // Handle driver-side settings in parsed connection string. | |||
| func (c *conn) handleDriverSettings(o values) (err error) { | |||
| func (cn *conn) handleDriverSettings(o values) (err error) { | |||
| boolSetting := func(key string, val *bool) error { | |||
| if value := o.Get(key); value != "" { | |||
| if value, ok := o[key]; ok { | |||
| if value == "yes" { | |||
| *val = true | |||
| } else if value == "no" { | |||
| @@ -139,32 +145,32 @@ func (c *conn) handleDriverSettings(o values) (err error) { | |||
| return nil | |||
| } | |||
| err = boolSetting("disable_prepared_binary_result", &c.disablePreparedBinaryResult) | |||
| err = boolSetting("disable_prepared_binary_result", &cn.disablePreparedBinaryResult) | |||
| if err != nil { | |||
| return err | |||
| } | |||
| err = boolSetting("binary_parameters", &c.binaryParameters) | |||
| if err != nil { | |||
| return err | |||
| } | |||
| return nil | |||
| return boolSetting("binary_parameters", &cn.binaryParameters) | |||
| } | |||
| func (c *conn) handlePgpass(o values) { | |||
| func (cn *conn) handlePgpass(o values) { | |||
| // if a password was supplied, do not process .pgpass | |||
| _, ok := o["password"] | |||
| if ok { | |||
| if _, ok := o["password"]; ok { | |||
| return | |||
| } | |||
| filename := os.Getenv("PGPASSFILE") | |||
| if filename == "" { | |||
| // XXX this code doesn't work on Windows where the default filename is | |||
| // XXX %APPDATA%\postgresql\pgpass.conf | |||
| user, err := user.Current() | |||
| if err != nil { | |||
| return | |||
| // Prefer $HOME over user.Current due to glibc bug: golang.org/issue/13470 | |||
| userHome := os.Getenv("HOME") | |||
| if userHome == "" { | |||
| user, err := user.Current() | |||
| if err != nil { | |||
| return | |||
| } | |||
| userHome = user.HomeDir | |||
| } | |||
| filename = filepath.Join(user.HomeDir, ".pgpass") | |||
| filename = filepath.Join(userHome, ".pgpass") | |||
| } | |||
| fileinfo, err := os.Stat(filename) | |||
| if err != nil { | |||
| @@ -181,11 +187,11 @@ func (c *conn) handlePgpass(o values) { | |||
| } | |||
| defer file.Close() | |||
| scanner := bufio.NewScanner(io.Reader(file)) | |||
| hostname := o.Get("host") | |||
| hostname := o["host"] | |||
| ntw, _ := network(o) | |||
| port := o.Get("port") | |||
| db := o.Get("dbname") | |||
| username := o.Get("user") | |||
| 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) | |||
| @@ -224,10 +230,10 @@ func (c *conn) handlePgpass(o values) { | |||
| } | |||
| } | |||
| func (c *conn) writeBuf(b byte) *writeBuf { | |||
| c.scratch[0] = b | |||
| func (cn *conn) writeBuf(b byte) *writeBuf { | |||
| cn.scratch[0] = b | |||
| return &writeBuf{ | |||
| buf: c.scratch[:5], | |||
| buf: cn.scratch[:5], | |||
| pos: 1, | |||
| } | |||
| } | |||
| @@ -250,13 +256,13 @@ func DialOpen(d Dialer, name string) (_ driver.Conn, err error) { | |||
| // * Very low precedence defaults applied in every situation | |||
| // * Environment variables | |||
| // * Explicitly passed connection information | |||
| o.Set("host", "localhost") | |||
| o.Set("port", "5432") | |||
| o["host"] = "localhost" | |||
| o["port"] = "5432" | |||
| // N.B.: Extra float digits should be set to 3, but that breaks | |||
| // Postgres 8.4 and older, where the max is 2. | |||
| o.Set("extra_float_digits", "2") | |||
| o["extra_float_digits"] = "2" | |||
| for k, v := range parseEnviron(os.Environ()) { | |||
| o.Set(k, v) | |||
| o[k] = v | |||
| } | |||
| if strings.HasPrefix(name, "postgres://") || strings.HasPrefix(name, "postgresql://") { | |||
| @@ -271,9 +277,9 @@ func DialOpen(d Dialer, name string) (_ driver.Conn, err error) { | |||
| } | |||
| // Use the "fallback" application name if necessary | |||
| if fallback := o.Get("fallback_application_name"); fallback != "" { | |||
| if !o.Isset("application_name") { | |||
| o.Set("application_name", fallback) | |||
| if fallback, ok := o["fallback_application_name"]; ok { | |||
| if _, ok := o["application_name"]; !ok { | |||
| o["application_name"] = fallback | |||
| } | |||
| } | |||
| @@ -284,33 +290,35 @@ func DialOpen(d Dialer, name string) (_ driver.Conn, err error) { | |||
| // parsing its value is not worth it. Instead, we always explicitly send | |||
| // client_encoding as a separate run-time parameter, which should override | |||
| // anything set in options. | |||
| if enc := o.Get("client_encoding"); enc != "" && !isUTF8(enc) { | |||
| if enc, ok := o["client_encoding"]; ok && !isUTF8(enc) { | |||
| return nil, errors.New("client_encoding must be absent or 'UTF8'") | |||
| } | |||
| o.Set("client_encoding", "UTF8") | |||
| o["client_encoding"] = "UTF8" | |||
| // DateStyle needs a similar treatment. | |||
| if datestyle := o.Get("datestyle"); datestyle != "" { | |||
| if datestyle, ok := o["datestyle"]; ok { | |||
| if datestyle != "ISO, MDY" { | |||
| panic(fmt.Sprintf("setting datestyle must be absent or %v; got %v", | |||
| "ISO, MDY", datestyle)) | |||
| } | |||
| } else { | |||
| o.Set("datestyle", "ISO, MDY") | |||
| o["datestyle"] = "ISO, MDY" | |||
| } | |||
| // If a user is not provided by any other means, the last | |||
| // resort is to use the current operating system provided user | |||
| // name. | |||
| if o.Get("user") == "" { | |||
| if _, ok := o["user"]; !ok { | |||
| u, err := userCurrent() | |||
| if err != nil { | |||
| return nil, err | |||
| } else { | |||
| o.Set("user", u) | |||
| } | |||
| o["user"] = u | |||
| } | |||
| cn := &conn{} | |||
| cn := &conn{ | |||
| opts: o, | |||
| dialer: d, | |||
| } | |||
| err = cn.handleDriverSettings(o) | |||
| if err != nil { | |||
| return nil, err | |||
| @@ -326,7 +334,7 @@ func DialOpen(d Dialer, name string) (_ driver.Conn, err error) { | |||
| cn.startup(o) | |||
| // reset the deadline, in case one was set (see dial) | |||
| if timeout := o.Get("connect_timeout"); timeout != "" && timeout != "0" { | |||
| if timeout, ok := o["connect_timeout"]; ok && timeout != "0" { | |||
| err = cn.c.SetDeadline(time.Time{}) | |||
| } | |||
| return cn, err | |||
| @@ -340,7 +348,7 @@ func dial(d Dialer, o values) (net.Conn, error) { | |||
| } | |||
| // Zero or not specified means wait indefinitely. | |||
| if timeout := o.Get("connect_timeout"); timeout != "" && timeout != "0" { | |||
| if timeout, ok := o["connect_timeout"]; ok && timeout != "0" { | |||
| seconds, err := strconv.ParseInt(timeout, 10, 0) | |||
| if err != nil { | |||
| return nil, fmt.Errorf("invalid value for parameter connect_timeout: %s", err) | |||
| @@ -362,31 +370,18 @@ func dial(d Dialer, o values) (net.Conn, error) { | |||
| } | |||
| func network(o values) (string, string) { | |||
| host := o.Get("host") | |||
| host := o["host"] | |||
| if strings.HasPrefix(host, "/") { | |||
| sockPath := path.Join(host, ".s.PGSQL."+o.Get("port")) | |||
| sockPath := path.Join(host, ".s.PGSQL."+o["port"]) | |||
| return "unix", sockPath | |||
| } | |||
| return "tcp", net.JoinHostPort(host, o.Get("port")) | |||
| return "tcp", net.JoinHostPort(host, o["port"]) | |||
| } | |||
| type values map[string]string | |||
| func (vs values) Set(k, v string) { | |||
| vs[k] = v | |||
| } | |||
| func (vs values) Get(k string) (v string) { | |||
| return vs[k] | |||
| } | |||
| func (vs values) Isset(k string) bool { | |||
| _, ok := vs[k] | |||
| return ok | |||
| } | |||
| // scanner implements a tokenizer for libpq-style option strings. | |||
| type scanner struct { | |||
| s []rune | |||
| @@ -457,7 +452,7 @@ func parseOpts(name string, o values) error { | |||
| // Skip any whitespace after the = | |||
| if r, ok = s.SkipSpaces(); !ok { | |||
| // If we reach the end here, the last value is just an empty string as per libpq. | |||
| o.Set(string(keyRunes), "") | |||
| o[string(keyRunes)] = "" | |||
| break | |||
| } | |||
| @@ -492,7 +487,7 @@ func parseOpts(name string, o values) error { | |||
| } | |||
| } | |||
| o.Set(string(keyRunes), string(valRunes)) | |||
| o[string(keyRunes)] = string(valRunes) | |||
| } | |||
| return nil | |||
| @@ -511,13 +506,17 @@ func (cn *conn) checkIsInTransaction(intxn bool) { | |||
| } | |||
| func (cn *conn) Begin() (_ driver.Tx, err error) { | |||
| return cn.begin("") | |||
| } | |||
| func (cn *conn) begin(mode string) (_ driver.Tx, err error) { | |||
| if cn.bad { | |||
| return nil, driver.ErrBadConn | |||
| } | |||
| defer cn.errRecover(&err) | |||
| cn.checkIsInTransaction(false) | |||
| _, commandTag, err := cn.simpleExec("BEGIN") | |||
| _, commandTag, err := cn.simpleExec("BEGIN" + mode) | |||
| if err != nil { | |||
| return nil, err | |||
| } | |||
| @@ -532,7 +531,14 @@ func (cn *conn) Begin() (_ driver.Tx, err error) { | |||
| return cn, nil | |||
| } | |||
| func (cn *conn) closeTxn() { | |||
| if finish := cn.txnFinish; finish != nil { | |||
| finish() | |||
| } | |||
| } | |||
| func (cn *conn) Commit() (err error) { | |||
| defer cn.closeTxn() | |||
| if cn.bad { | |||
| return driver.ErrBadConn | |||
| } | |||
| @@ -568,6 +574,7 @@ func (cn *conn) Commit() (err error) { | |||
| } | |||
| func (cn *conn) Rollback() (err error) { | |||
| defer cn.closeTxn() | |||
| if cn.bad { | |||
| return driver.ErrBadConn | |||
| } | |||
| @@ -647,6 +654,12 @@ func (cn *conn) simpleQuery(q string) (res *rows, err error) { | |||
| cn: cn, | |||
| } | |||
| } | |||
| // 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 { | |||
| res.result, res.tag = cn.parseComplete(r.string()) | |||
| } | |||
| res.done = true | |||
| case 'Z': | |||
| cn.processReadyForQuery(r) | |||
| @@ -685,7 +698,7 @@ var emptyRows noRows | |||
| var _ driver.Result = noRows{} | |||
| func (noRows) LastInsertId() (int64, error) { | |||
| return 0, errNoLastInsertId | |||
| return 0, errNoLastInsertID | |||
| } | |||
| func (noRows) RowsAffected() (int64, error) { | |||
| @@ -694,7 +707,7 @@ 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 []oid.Oid, forceText bool) (colFmts []format, colFmtData []byte) { | |||
| func decideColumnFormats(colTyps []fieldDesc, forceText bool) (colFmts []format, colFmtData []byte) { | |||
| if len(colTyps) == 0 { | |||
| return nil, colFmtDataAllText | |||
| } | |||
| @@ -706,8 +719,8 @@ func decideColumnFormats(colTyps []oid.Oid, forceText bool) (colFmts []format, c | |||
| allBinary := true | |||
| allText := true | |||
| for i, o := range colTyps { | |||
| switch o { | |||
| for i, t := range colTyps { | |||
| switch t.OID { | |||
| // This is the list of types to use binary mode for when receiving them | |||
| // through a prepared statement. If a type appears in this list, it | |||
| // must also be implemented in binaryDecode in encode.go. | |||
| @@ -718,6 +731,8 @@ func decideColumnFormats(colTyps []oid.Oid, forceText bool) (colFmts []format, c | |||
| case oid.T_int4: | |||
| fallthrough | |||
| case oid.T_int2: | |||
| fallthrough | |||
| case oid.T_uuid: | |||
| colFmts[i] = formatBinary | |||
| allText = false | |||
| @@ -797,7 +812,11 @@ func (cn *conn) Close() (err error) { | |||
| } | |||
| // Implement the "Queryer" interface | |||
| func (cn *conn) Query(query string, args []driver.Value) (_ driver.Rows, err error) { | |||
| func (cn *conn) Query(query string, args []driver.Value) (driver.Rows, error) { | |||
| return cn.query(query, args) | |||
| } | |||
| func (cn *conn) query(query string, args []driver.Value) (_ *rows, err error) { | |||
| if cn.bad { | |||
| return nil, driver.ErrBadConn | |||
| } | |||
| @@ -821,16 +840,15 @@ func (cn *conn) Query(query string, args []driver.Value) (_ driver.Rows, err err | |||
| rows.colNames, rows.colFmts, rows.colTyps = cn.readPortalDescribeResponse() | |||
| cn.postExecuteWorkaround() | |||
| return rows, nil | |||
| } else { | |||
| st := cn.prepareTo(query, "") | |||
| st.exec(args) | |||
| return &rows{ | |||
| cn: cn, | |||
| colNames: st.colNames, | |||
| colTyps: st.colTyps, | |||
| colFmts: st.colFmts, | |||
| }, nil | |||
| } | |||
| st := cn.prepareTo(query, "") | |||
| st.exec(args) | |||
| return &rows{ | |||
| cn: cn, | |||
| colNames: st.colNames, | |||
| colTyps: st.colTyps, | |||
| colFmts: st.colFmts, | |||
| }, nil | |||
| } | |||
| // Implement the optional "Execer" interface for one-shot queries | |||
| @@ -857,17 +875,16 @@ func (cn *conn) Exec(query string, args []driver.Value) (res driver.Result, err | |||
| cn.postExecuteWorkaround() | |||
| res, _, err = cn.readExecuteResponse("Execute") | |||
| return res, err | |||
| } else { | |||
| // Use the unnamed statement to defer planning until bind | |||
| // time, or else value-based selectivity estimates cannot be | |||
| // used. | |||
| st := cn.prepareTo(query, "") | |||
| r, err := st.Exec(args) | |||
| if err != nil { | |||
| panic(err) | |||
| } | |||
| return r, err | |||
| } | |||
| // Use the unnamed statement to defer planning until bind | |||
| // time, or else value-based selectivity estimates cannot be | |||
| // used. | |||
| st := cn.prepareTo(query, "") | |||
| r, err := st.Exec(args) | |||
| if err != nil { | |||
| panic(err) | |||
| } | |||
| return r, err | |||
| } | |||
| func (cn *conn) send(m *writeBuf) { | |||
| @@ -877,16 +894,9 @@ func (cn *conn) send(m *writeBuf) { | |||
| } | |||
| } | |||
| func (cn *conn) sendStartupPacket(m *writeBuf) { | |||
| // sanity check | |||
| if m.buf[0] != 0 { | |||
| panic("oops") | |||
| } | |||
| func (cn *conn) sendStartupPacket(m *writeBuf) error { | |||
| _, err := cn.c.Write((m.wrap())[1:]) | |||
| if err != nil { | |||
| panic(err) | |||
| } | |||
| return err | |||
| } | |||
| // Send a message of type typ to the server on the other end of cn. The | |||
| @@ -1000,45 +1010,17 @@ func (cn *conn) recv1() (t byte, r *readBuf) { | |||
| } | |||
| func (cn *conn) ssl(o values) { | |||
| verifyCaOnly := false | |||
| tlsConf := tls.Config{} | |||
| switch mode := o.Get("sslmode"); mode { | |||
| // "require" is the default. | |||
| case "", "require": | |||
| // We must skip TLS's own verification since it requires full | |||
| // verification since Go 1.3. | |||
| tlsConf.InsecureSkipVerify = true | |||
| // From http://www.postgresql.org/docs/current/static/libpq-ssl.html: | |||
| // Note: For backwards compatibility with earlier versions of PostgreSQL, if a | |||
| // root CA file exists, the behavior of sslmode=require will be the same as | |||
| // that of verify-ca, meaning the server certificate is validated against the | |||
| // CA. Relying on this behavior is discouraged, and applications that need | |||
| // certificate validation should always use verify-ca or verify-full. | |||
| if _, err := os.Stat(o.Get("sslrootcert")); err == nil { | |||
| verifyCaOnly = true | |||
| } else { | |||
| o.Set("sslrootcert", "") | |||
| } | |||
| case "verify-ca": | |||
| // We must skip TLS's own verification since it requires full | |||
| // verification since Go 1.3. | |||
| tlsConf.InsecureSkipVerify = true | |||
| verifyCaOnly = true | |||
| case "verify-full": | |||
| tlsConf.ServerName = o.Get("host") | |||
| case "disable": | |||
| upgrade := ssl(o) | |||
| if upgrade == nil { | |||
| // Nothing to do | |||
| return | |||
| default: | |||
| errorf(`unsupported sslmode %q; only "require" (default), "verify-full", "verify-ca", and "disable" supported`, mode) | |||
| } | |||
| cn.setupSSLClientCertificates(&tlsConf, o) | |||
| cn.setupSSLCA(&tlsConf, o) | |||
| w := cn.writeBuf(0) | |||
| w.int32(80877103) | |||
| cn.sendStartupPacket(w) | |||
| if err := cn.sendStartupPacket(w); err != nil { | |||
| panic(err) | |||
| } | |||
| b := cn.scratch[:1] | |||
| _, err := io.ReadFull(cn.c, b) | |||
| @@ -1050,114 +1032,7 @@ func (cn *conn) ssl(o values) { | |||
| panic(ErrSSLNotSupported) | |||
| } | |||
| client := tls.Client(cn.c, &tlsConf) | |||
| if verifyCaOnly { | |||
| cn.verifyCA(client, &tlsConf) | |||
| } | |||
| cn.c = client | |||
| } | |||
| // verifyCA carries out a TLS handshake to the server and verifies the | |||
| // presented certificate against the effective CA, i.e. the one specified in | |||
| // sslrootcert or the system CA if sslrootcert was not specified. | |||
| func (cn *conn) verifyCA(client *tls.Conn, tlsConf *tls.Config) { | |||
| err := client.Handshake() | |||
| if err != nil { | |||
| panic(err) | |||
| } | |||
| certs := client.ConnectionState().PeerCertificates | |||
| opts := x509.VerifyOptions{ | |||
| DNSName: client.ConnectionState().ServerName, | |||
| Intermediates: x509.NewCertPool(), | |||
| Roots: tlsConf.RootCAs, | |||
| } | |||
| for i, cert := range certs { | |||
| if i == 0 { | |||
| continue | |||
| } | |||
| opts.Intermediates.AddCert(cert) | |||
| } | |||
| _, err = certs[0].Verify(opts) | |||
| if err != nil { | |||
| panic(err) | |||
| } | |||
| } | |||
| // This function sets up SSL client certificates based on either the "sslkey" | |||
| // and "sslcert" settings (possibly set via the environment variables PGSSLKEY | |||
| // and PGSSLCERT, respectively), or if they aren't set, from the .postgresql | |||
| // directory in the user's home directory. If the file paths are set | |||
| // explicitly, the files must exist. The key file must also not be | |||
| // world-readable, or this function will panic with | |||
| // ErrSSLKeyHasWorldPermissions. | |||
| func (cn *conn) setupSSLClientCertificates(tlsConf *tls.Config, o values) { | |||
| var missingOk bool | |||
| sslkey := o.Get("sslkey") | |||
| sslcert := o.Get("sslcert") | |||
| if sslkey != "" && sslcert != "" { | |||
| // If the user has set an sslkey and sslcert, they *must* exist. | |||
| missingOk = false | |||
| } else { | |||
| // Automatically load certificates from ~/.postgresql. | |||
| user, err := user.Current() | |||
| if err != nil { | |||
| // user.Current() might fail when cross-compiling. We have to | |||
| // ignore the error and continue without client certificates, since | |||
| // we wouldn't know where to load them from. | |||
| return | |||
| } | |||
| sslkey = filepath.Join(user.HomeDir, ".postgresql", "postgresql.key") | |||
| sslcert = filepath.Join(user.HomeDir, ".postgresql", "postgresql.crt") | |||
| missingOk = true | |||
| } | |||
| // Check that both files exist, and report the error or stop, depending on | |||
| // which behaviour we want. Note that we don't do any more extensive | |||
| // checks than this (such as checking that the paths aren't directories); | |||
| // LoadX509KeyPair() will take care of the rest. | |||
| keyfinfo, err := os.Stat(sslkey) | |||
| if err != nil && missingOk { | |||
| return | |||
| } else if err != nil { | |||
| panic(err) | |||
| } | |||
| _, err = os.Stat(sslcert) | |||
| if err != nil && missingOk { | |||
| return | |||
| } else if err != nil { | |||
| panic(err) | |||
| } | |||
| // If we got this far, the key file must also have the correct permissions | |||
| kmode := keyfinfo.Mode() | |||
| if kmode != kmode&0600 { | |||
| panic(ErrSSLKeyHasWorldPermissions) | |||
| } | |||
| cert, err := tls.LoadX509KeyPair(sslcert, sslkey) | |||
| if err != nil { | |||
| panic(err) | |||
| } | |||
| tlsConf.Certificates = []tls.Certificate{cert} | |||
| } | |||
| // Sets up RootCAs in the TLS configuration if sslrootcert is set. | |||
| func (cn *conn) setupSSLCA(tlsConf *tls.Config, o values) { | |||
| if sslrootcert := o.Get("sslrootcert"); sslrootcert != "" { | |||
| tlsConf.RootCAs = x509.NewCertPool() | |||
| cert, err := ioutil.ReadFile(sslrootcert) | |||
| if err != nil { | |||
| panic(err) | |||
| } | |||
| ok := tlsConf.RootCAs.AppendCertsFromPEM(cert) | |||
| if !ok { | |||
| errorf("couldn't parse pem in sslrootcert") | |||
| } | |||
| } | |||
| cn.c = upgrade(cn.c) | |||
| } | |||
| // isDriverSetting returns true iff a setting is purely for configuring the | |||
| @@ -1206,12 +1081,15 @@ func (cn *conn) startup(o values) { | |||
| w.string(v) | |||
| } | |||
| w.string("") | |||
| cn.sendStartupPacket(w) | |||
| if err := cn.sendStartupPacket(w); err != nil { | |||
| panic(err) | |||
| } | |||
| for { | |||
| t, r := cn.recv() | |||
| switch t { | |||
| case 'K': | |||
| cn.processBackendKeyData(r) | |||
| case 'S': | |||
| cn.processParameterStatus(r) | |||
| case 'R': | |||
| @@ -1231,7 +1109,7 @@ func (cn *conn) auth(r *readBuf, o values) { | |||
| // OK | |||
| case 3: | |||
| w := cn.writeBuf('p') | |||
| w.string(o.Get("password")) | |||
| w.string(o["password"]) | |||
| cn.send(w) | |||
| t, r := cn.recv() | |||
| @@ -1245,7 +1123,7 @@ func (cn *conn) auth(r *readBuf, o values) { | |||
| case 5: | |||
| s := string(r.next(4)) | |||
| w := cn.writeBuf('p') | |||
| w.string("md5" + md5s(md5s(o.Get("password")+o.Get("user"))+s)) | |||
| w.string("md5" + md5s(md5s(o["password"]+o["user"])+s)) | |||
| cn.send(w) | |||
| t, r := cn.recv() | |||
| @@ -1267,10 +1145,10 @@ const formatText format = 0 | |||
| const formatBinary format = 1 | |||
| // One result-column format code with the value 1 (i.e. all binary). | |||
| var colFmtDataAllBinary []byte = []byte{0, 1, 0, 1} | |||
| var colFmtDataAllBinary = []byte{0, 1, 0, 1} | |||
| // No result-column format codes (i.e. all text). | |||
| var colFmtDataAllText []byte = []byte{0, 0} | |||
| var colFmtDataAllText = []byte{0, 0} | |||
| type stmt struct { | |||
| cn *conn | |||
| @@ -1278,7 +1156,7 @@ type stmt struct { | |||
| colNames []string | |||
| colFmts []format | |||
| colFmtData []byte | |||
| colTyps []oid.Oid | |||
| colTyps []fieldDesc | |||
| paramTyps []oid.Oid | |||
| closed bool | |||
| } | |||
| @@ -1439,21 +1317,32 @@ func (cn *conn) parseComplete(commandTag string) (driver.Result, string) { | |||
| type rows struct { | |||
| cn *conn | |||
| finish func() | |||
| colNames []string | |||
| colTyps []oid.Oid | |||
| colTyps []fieldDesc | |||
| colFmts []format | |||
| done bool | |||
| rb readBuf | |||
| result driver.Result | |||
| tag string | |||
| } | |||
| func (rs *rows) Close() error { | |||
| if finish := rs.finish; finish != nil { | |||
| defer finish() | |||
| } | |||
| // no need to look at cn.bad as Next() will | |||
| for { | |||
| err := rs.Next(nil) | |||
| switch err { | |||
| case nil: | |||
| case io.EOF: | |||
| return nil | |||
| // rs.Next can return io.EOF on both 'Z' (ready for query) and 'T' (row | |||
| // description, used with HasNextResultSet). We need to fetch messages until | |||
| // we hit a 'Z', which is done by waiting for done to be set. | |||
| if rs.done { | |||
| return nil | |||
| } | |||
| default: | |||
| return err | |||
| } | |||
| @@ -1464,6 +1353,17 @@ func (rs *rows) Columns() []string { | |||
| return rs.colNames | |||
| } | |||
| func (rs *rows) Result() driver.Result { | |||
| if rs.result == nil { | |||
| return emptyRows | |||
| } | |||
| return rs.result | |||
| } | |||
| func (rs *rows) Tag() string { | |||
| return rs.tag | |||
| } | |||
| func (rs *rows) Next(dest []driver.Value) (err error) { | |||
| if rs.done { | |||
| return io.EOF | |||
| @@ -1481,6 +1381,9 @@ func (rs *rows) Next(dest []driver.Value) (err error) { | |||
| case 'E': | |||
| err = parseError(&rs.rb) | |||
| case 'C', 'I': | |||
| if t == 'C' { | |||
| rs.result, rs.tag = conn.parseComplete(rs.rb.string()) | |||
| } | |||
| continue | |||
| case 'Z': | |||
| conn.processReadyForQuery(&rs.rb) | |||
| @@ -1504,7 +1407,7 @@ func (rs *rows) Next(dest []driver.Value) (err error) { | |||
| dest[i] = nil | |||
| continue | |||
| } | |||
| dest[i] = decode(&conn.parameterStatus, rs.rb.next(l), rs.colTyps[i], rs.colFmts[i]) | |||
| dest[i] = decode(&conn.parameterStatus, rs.rb.next(l), rs.colTyps[i].OID, rs.colFmts[i]) | |||
| } | |||
| return | |||
| case 'T': | |||
| @@ -1610,7 +1513,7 @@ func (cn *conn) sendBinaryModeQuery(query string, args []driver.Value) { | |||
| cn.send(b) | |||
| } | |||
| func (c *conn) processParameterStatus(r *readBuf) { | |||
| func (cn *conn) processParameterStatus(r *readBuf) { | |||
| var err error | |||
| param := r.string() | |||
| @@ -1621,13 +1524,13 @@ func (c *conn) processParameterStatus(r *readBuf) { | |||
| var minor int | |||
| _, err = fmt.Sscanf(r.string(), "%d.%d.%d", &major1, &major2, &minor) | |||
| if err == nil { | |||
| c.parameterStatus.serverVersion = major1*10000 + major2*100 + minor | |||
| cn.parameterStatus.serverVersion = major1*10000 + major2*100 + minor | |||
| } | |||
| case "TimeZone": | |||
| c.parameterStatus.currentLocation, err = time.LoadLocation(r.string()) | |||
| cn.parameterStatus.currentLocation, err = time.LoadLocation(r.string()) | |||
| if err != nil { | |||
| c.parameterStatus.currentLocation = nil | |||
| cn.parameterStatus.currentLocation = nil | |||
| } | |||
| default: | |||
| @@ -1635,8 +1538,8 @@ func (c *conn) processParameterStatus(r *readBuf) { | |||
| } | |||
| } | |||
| func (c *conn) processReadyForQuery(r *readBuf) { | |||
| c.txnStatus = transactionStatus(r.byte()) | |||
| func (cn *conn) processReadyForQuery(r *readBuf) { | |||
| cn.txnStatus = transactionStatus(r.byte()) | |||
| } | |||
| func (cn *conn) readReadyForQuery() { | |||
| @@ -1651,6 +1554,11 @@ func (cn *conn) readReadyForQuery() { | |||
| } | |||
| } | |||
| func (cn *conn) processBackendKeyData(r *readBuf) { | |||
| cn.processID = r.int32() | |||
| cn.secretKey = r.int32() | |||
| } | |||
| func (cn *conn) readParseResponse() { | |||
| t, r := cn.recv1() | |||
| switch t { | |||
| @@ -1666,7 +1574,7 @@ func (cn *conn) readParseResponse() { | |||
| } | |||
| } | |||
| func (cn *conn) readStatementDescribeResponse() (paramTyps []oid.Oid, colNames []string, colTyps []oid.Oid) { | |||
| func (cn *conn) readStatementDescribeResponse() (paramTyps []oid.Oid, colNames []string, colTyps []fieldDesc) { | |||
| for { | |||
| t, r := cn.recv1() | |||
| switch t { | |||
| @@ -1692,7 +1600,7 @@ func (cn *conn) readStatementDescribeResponse() (paramTyps []oid.Oid, colNames [ | |||
| } | |||
| } | |||
| func (cn *conn) readPortalDescribeResponse() (colNames []string, colFmts []format, colTyps []oid.Oid) { | |||
| func (cn *conn) readPortalDescribeResponse() (colNames []string, colFmts []format, colTyps []fieldDesc) { | |||
| t, r := cn.recv1() | |||
| switch t { | |||
| case 'T': | |||
| @@ -1788,31 +1696,33 @@ func (cn *conn) readExecuteResponse(protocolState string) (res driver.Result, co | |||
| } | |||
| } | |||
| func parseStatementRowDescribe(r *readBuf) (colNames []string, colTyps []oid.Oid) { | |||
| func parseStatementRowDescribe(r *readBuf) (colNames []string, colTyps []fieldDesc) { | |||
| n := r.int16() | |||
| colNames = make([]string, n) | |||
| colTyps = make([]oid.Oid, n) | |||
| colTyps = make([]fieldDesc, n) | |||
| for i := range colNames { | |||
| colNames[i] = r.string() | |||
| r.next(6) | |||
| colTyps[i] = r.oid() | |||
| r.next(6) | |||
| colTyps[i].OID = r.oid() | |||
| colTyps[i].Len = r.int16() | |||
| colTyps[i].Mod = r.int32() | |||
| // format code not known when describing a statement; always 0 | |||
| r.next(2) | |||
| } | |||
| return | |||
| } | |||
| func parsePortalRowDescribe(r *readBuf) (colNames []string, colFmts []format, colTyps []oid.Oid) { | |||
| func parsePortalRowDescribe(r *readBuf) (colNames []string, colFmts []format, colTyps []fieldDesc) { | |||
| n := r.int16() | |||
| colNames = make([]string, n) | |||
| colFmts = make([]format, n) | |||
| colTyps = make([]oid.Oid, n) | |||
| colTyps = make([]fieldDesc, n) | |||
| for i := range colNames { | |||
| colNames[i] = r.string() | |||
| r.next(6) | |||
| colTyps[i] = r.oid() | |||
| r.next(6) | |||
| colTyps[i].OID = r.oid() | |||
| colTyps[i].Len = r.int16() | |||
| colTyps[i].Mod = r.int32() | |||
| colFmts[i] = format(r.int16()) | |||
| } | |||
| return | |||
| @@ -0,0 +1,128 @@ | |||
| // +build go1.8 | |||
| package pq | |||
| import ( | |||
| "context" | |||
| "database/sql" | |||
| "database/sql/driver" | |||
| "fmt" | |||
| "io" | |||
| "io/ioutil" | |||
| ) | |||
| // 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)) | |||
| for i, nv := range args { | |||
| list[i] = nv.Value | |||
| } | |||
| finish := cn.watchCancel(ctx) | |||
| r, err := cn.query(query, list) | |||
| if err != nil { | |||
| if finish != nil { | |||
| finish() | |||
| } | |||
| return nil, err | |||
| } | |||
| r.finish = finish | |||
| return r, nil | |||
| } | |||
| // Implement the "ExecerContext" interface | |||
| func (cn *conn) ExecContext(ctx context.Context, query string, args []driver.NamedValue) (driver.Result, error) { | |||
| list := make([]driver.Value, len(args)) | |||
| for i, nv := range args { | |||
| list[i] = nv.Value | |||
| } | |||
| if finish := cn.watchCancel(ctx); finish != nil { | |||
| defer finish() | |||
| } | |||
| return cn.Exec(query, list) | |||
| } | |||
| // Implement the "ConnBeginTx" interface | |||
| func (cn *conn) BeginTx(ctx context.Context, opts driver.TxOptions) (driver.Tx, error) { | |||
| var mode string | |||
| switch sql.IsolationLevel(opts.Isolation) { | |||
| case sql.LevelDefault: | |||
| // Don't touch mode: use the server's default | |||
| case sql.LevelReadUncommitted: | |||
| mode = " ISOLATION LEVEL READ UNCOMMITTED" | |||
| case sql.LevelReadCommitted: | |||
| mode = " ISOLATION LEVEL READ COMMITTED" | |||
| case sql.LevelRepeatableRead: | |||
| mode = " ISOLATION LEVEL REPEATABLE READ" | |||
| case sql.LevelSerializable: | |||
| mode = " ISOLATION LEVEL SERIALIZABLE" | |||
| default: | |||
| return nil, fmt.Errorf("pq: isolation level not supported: %d", opts.Isolation) | |||
| } | |||
| if opts.ReadOnly { | |||
| mode += " READ ONLY" | |||
| } else { | |||
| mode += " READ WRITE" | |||
| } | |||
| tx, err := cn.begin(mode) | |||
| if err != nil { | |||
| return nil, err | |||
| } | |||
| cn.txnFinish = cn.watchCancel(ctx) | |||
| return tx, nil | |||
| } | |||
| func (cn *conn) watchCancel(ctx context.Context) func() { | |||
| if done := ctx.Done(); done != nil { | |||
| finished := make(chan struct{}) | |||
| go func() { | |||
| select { | |||
| case <-done: | |||
| _ = cn.cancel() | |||
| finished <- struct{}{} | |||
| case <-finished: | |||
| } | |||
| }() | |||
| return func() { | |||
| select { | |||
| case <-finished: | |||
| case finished <- struct{}{}: | |||
| } | |||
| } | |||
| } | |||
| return nil | |||
| } | |||
| func (cn *conn) cancel() error { | |||
| c, err := dial(cn.dialer, cn.opts) | |||
| if err != nil { | |||
| return err | |||
| } | |||
| defer c.Close() | |||
| { | |||
| can := conn{ | |||
| c: c, | |||
| } | |||
| can.ssl(cn.opts) | |||
| w := can.writeBuf(0) | |||
| w.int32(80877102) // cancel request code | |||
| w.int32(cn.processID) | |||
| w.int32(cn.secretKey) | |||
| if err := can.sendStartupPacket(w); err != nil { | |||
| return err | |||
| } | |||
| } | |||
| // Read until EOF to ensure that the server received the cancel. | |||
| { | |||
| _, err := io.Copy(ioutil.Discard, c) | |||
| return err | |||
| } | |||
| } | |||
| @@ -97,13 +97,13 @@ awaitCopyInResponse: | |||
| err = parseError(r) | |||
| case 'Z': | |||
| if err == nil { | |||
| cn.bad = true | |||
| ci.setBad() | |||
| errorf("unexpected ReadyForQuery in response to COPY") | |||
| } | |||
| cn.processReadyForQuery(r) | |||
| return nil, err | |||
| default: | |||
| cn.bad = true | |||
| ci.setBad() | |||
| errorf("unknown response for copy query: %q", t) | |||
| } | |||
| } | |||
| @@ -122,7 +122,7 @@ awaitCopyInResponse: | |||
| cn.processReadyForQuery(r) | |||
| return nil, err | |||
| default: | |||
| cn.bad = true | |||
| ci.setBad() | |||
| errorf("unknown response for CopyFail: %q", t) | |||
| } | |||
| } | |||
| @@ -143,7 +143,7 @@ func (ci *copyin) resploop() { | |||
| var r readBuf | |||
| t, err := ci.cn.recvMessage(&r) | |||
| if err != nil { | |||
| ci.cn.bad = true | |||
| ci.setBad() | |||
| ci.setError(err) | |||
| ci.done <- true | |||
| return | |||
| @@ -161,7 +161,7 @@ func (ci *copyin) resploop() { | |||
| err := parseError(&r) | |||
| ci.setError(err) | |||
| default: | |||
| ci.cn.bad = true | |||
| ci.setBad() | |||
| ci.setError(fmt.Errorf("unknown response during CopyIn: %q", t)) | |||
| ci.done <- true | |||
| return | |||
| @@ -169,6 +169,19 @@ func (ci *copyin) resploop() { | |||
| } | |||
| } | |||
| func (ci *copyin) setBad() { | |||
| ci.Lock() | |||
| ci.cn.bad = true | |||
| ci.Unlock() | |||
| } | |||
| func (ci *copyin) isBad() bool { | |||
| ci.Lock() | |||
| b := ci.cn.bad | |||
| ci.Unlock() | |||
| return b | |||
| } | |||
| func (ci *copyin) isErrorSet() bool { | |||
| ci.Lock() | |||
| isSet := (ci.err != nil) | |||
| @@ -206,7 +219,7 @@ func (ci *copyin) Exec(v []driver.Value) (r driver.Result, err error) { | |||
| return nil, errCopyInClosed | |||
| } | |||
| if ci.cn.bad { | |||
| if ci.isBad() { | |||
| return nil, driver.ErrBadConn | |||
| } | |||
| defer ci.cn.errRecover(&err) | |||
| @@ -244,7 +257,7 @@ func (ci *copyin) Close() (err error) { | |||
| } | |||
| ci.closed = true | |||
| if ci.cn.bad { | |||
| if ci.isBad() { | |||
| return driver.ErrBadConn | |||
| } | |||
| defer ci.cn.errRecover(&err) | |||
| @@ -76,6 +76,12 @@ func binaryDecode(parameterStatus *parameterStatus, s []byte, typ oid.Oid) inter | |||
| return int64(int32(binary.BigEndian.Uint32(s))) | |||
| case oid.T_int2: | |||
| return int64(int16(binary.BigEndian.Uint16(s))) | |||
| case oid.T_uuid: | |||
| b, err := decodeUUIDBinary(s) | |||
| if err != nil { | |||
| panic(err) | |||
| } | |||
| return b | |||
| default: | |||
| errorf("don't know how to decode binary parameter of type %d", uint32(typ)) | |||
| @@ -361,8 +367,15 @@ func ParseTimestamp(currentLocation *time.Location, str string) (time.Time, erro | |||
| timeSep := daySep + 3 | |||
| day := p.mustAtoi(str, daySep+1, timeSep) | |||
| minLen := monSep + len("01-01") + 1 | |||
| isBC := strings.HasSuffix(str, " BC") | |||
| if isBC { | |||
| minLen += 3 | |||
| } | |||
| var hour, minute, second int | |||
| if len(str) > monSep+len("01-01")+1 { | |||
| if len(str) > minLen { | |||
| p.expect(str, ' ', timeSep) | |||
| minSep := timeSep + 3 | |||
| p.expect(str, ':', minSep) | |||
| @@ -418,7 +431,8 @@ func ParseTimestamp(currentLocation *time.Location, str string) (time.Time, erro | |||
| tzOff = tzSign * ((tzHours * 60 * 60) + (tzMin * 60) + tzSec) | |||
| } | |||
| var isoYear int | |||
| if remainderIdx+3 <= len(str) && str[remainderIdx:remainderIdx+3] == " BC" { | |||
| if isBC { | |||
| isoYear = 1 - year | |||
| remainderIdx += 3 | |||
| } else { | |||
| @@ -471,7 +485,7 @@ func FormatTimestamp(t time.Time) []byte { | |||
| t = t.AddDate((-t.Year())*2+1, 0, 0) | |||
| bc = true | |||
| } | |||
| b := []byte(t.Format(time.RFC3339Nano)) | |||
| b := []byte(t.Format("2006-01-02 15:04:05.999999999Z07:00")) | |||
| _, offset := t.Zone() | |||
| offset = offset % 60 | |||
| @@ -639,7 +639,7 @@ func (l *Listener) resync(cn *ListenerConn, notificationChan <-chan *Notificatio | |||
| // close and then return the error message from the connection, as | |||
| // per ListenerConn's interface. | |||
| if err != nil { | |||
| for _ = range notificationChan { | |||
| for range notificationChan { | |||
| } | |||
| doneChan <- cn.Err() | |||
| return | |||
| @@ -1,74 +0,0 @@ | |||
| // +build ignore | |||
| // Generate the table of OID values | |||
| // Run with 'go run gen.go'. | |||
| package main | |||
| import ( | |||
| "database/sql" | |||
| "fmt" | |||
| "log" | |||
| "os" | |||
| "os/exec" | |||
| _ "github.com/lib/pq" | |||
| ) | |||
| func main() { | |||
| datname := os.Getenv("PGDATABASE") | |||
| sslmode := os.Getenv("PGSSLMODE") | |||
| if datname == "" { | |||
| os.Setenv("PGDATABASE", "pqgotest") | |||
| } | |||
| if sslmode == "" { | |||
| os.Setenv("PGSSLMODE", "disable") | |||
| } | |||
| db, err := sql.Open("postgres", "") | |||
| if err != nil { | |||
| log.Fatal(err) | |||
| } | |||
| cmd := exec.Command("gofmt") | |||
| cmd.Stderr = os.Stderr | |||
| w, err := cmd.StdinPipe() | |||
| if err != nil { | |||
| log.Fatal(err) | |||
| } | |||
| f, err := os.Create("types.go") | |||
| if err != nil { | |||
| log.Fatal(err) | |||
| } | |||
| cmd.Stdout = f | |||
| err = cmd.Start() | |||
| if err != nil { | |||
| log.Fatal(err) | |||
| } | |||
| fmt.Fprintln(w, "// generated by 'go run gen.go'; do not edit") | |||
| fmt.Fprintln(w, "\npackage oid") | |||
| fmt.Fprintln(w, "const (") | |||
| rows, err := db.Query(` | |||
| SELECT typname, oid | |||
| FROM pg_type WHERE oid < 10000 | |||
| ORDER BY oid; | |||
| `) | |||
| if err != nil { | |||
| log.Fatal(err) | |||
| } | |||
| var name string | |||
| var oid int | |||
| for rows.Next() { | |||
| err = rows.Scan(&name, &oid) | |||
| if err != nil { | |||
| log.Fatal(err) | |||
| } | |||
| fmt.Fprintf(w, "T_%s Oid = %d\n", name, oid) | |||
| } | |||
| if err = rows.Err(); err != nil { | |||
| log.Fatal(err) | |||
| } | |||
| fmt.Fprintln(w, ")") | |||
| w.Close() | |||
| cmd.Wait() | |||
| } | |||
| @@ -1,4 +1,4 @@ | |||
| // generated by 'go run gen.go'; do not edit | |||
| // Code generated by gen.go. DO NOT EDIT. | |||
| package oid | |||
| @@ -18,6 +18,7 @@ const ( | |||
| T_xid Oid = 28 | |||
| T_cid Oid = 29 | |||
| T_oidvector Oid = 30 | |||
| T_pg_ddl_command Oid = 32 | |||
| T_pg_type Oid = 71 | |||
| T_pg_attribute Oid = 75 | |||
| T_pg_proc Oid = 81 | |||
| @@ -28,6 +29,7 @@ const ( | |||
| T_pg_node_tree Oid = 194 | |||
| T__json Oid = 199 | |||
| T_smgr Oid = 210 | |||
| T_index_am_handler Oid = 325 | |||
| T_point Oid = 600 | |||
| T_lseg Oid = 601 | |||
| T_path Oid = 602 | |||
| @@ -133,6 +135,9 @@ const ( | |||
| T__uuid Oid = 2951 | |||
| T_txid_snapshot Oid = 2970 | |||
| T_fdw_handler Oid = 3115 | |||
| T_pg_lsn Oid = 3220 | |||
| T__pg_lsn Oid = 3221 | |||
| T_tsm_handler Oid = 3310 | |||
| T_anyenum Oid = 3500 | |||
| T_tsvector Oid = 3614 | |||
| T_tsquery Oid = 3615 | |||
| @@ -144,6 +149,8 @@ const ( | |||
| T__regconfig Oid = 3735 | |||
| T_regdictionary Oid = 3769 | |||
| T__regdictionary Oid = 3770 | |||
| T_jsonb Oid = 3802 | |||
| T__jsonb Oid = 3807 | |||
| T_anyrange Oid = 3831 | |||
| T_event_trigger Oid = 3838 | |||
| T_int4range Oid = 3904 | |||
| @@ -158,4 +165,179 @@ const ( | |||
| T__daterange Oid = 3913 | |||
| T_int8range Oid = 3926 | |||
| T__int8range Oid = 3927 | |||
| T_pg_shseclabel Oid = 4066 | |||
| T_regnamespace Oid = 4089 | |||
| T__regnamespace Oid = 4090 | |||
| T_regrole Oid = 4096 | |||
| T__regrole Oid = 4097 | |||
| ) | |||
| var TypeName = map[Oid]string{ | |||
| T_bool: "BOOL", | |||
| T_bytea: "BYTEA", | |||
| T_char: "CHAR", | |||
| T_name: "NAME", | |||
| T_int8: "INT8", | |||
| T_int2: "INT2", | |||
| T_int2vector: "INT2VECTOR", | |||
| T_int4: "INT4", | |||
| T_regproc: "REGPROC", | |||
| T_text: "TEXT", | |||
| T_oid: "OID", | |||
| T_tid: "TID", | |||
| T_xid: "XID", | |||
| T_cid: "CID", | |||
| T_oidvector: "OIDVECTOR", | |||
| T_pg_ddl_command: "PG_DDL_COMMAND", | |||
| T_pg_type: "PG_TYPE", | |||
| T_pg_attribute: "PG_ATTRIBUTE", | |||
| T_pg_proc: "PG_PROC", | |||
| T_pg_class: "PG_CLASS", | |||
| T_json: "JSON", | |||
| T_xml: "XML", | |||
| T__xml: "_XML", | |||
| T_pg_node_tree: "PG_NODE_TREE", | |||
| T__json: "_JSON", | |||
| T_smgr: "SMGR", | |||
| T_index_am_handler: "INDEX_AM_HANDLER", | |||
| T_point: "POINT", | |||
| T_lseg: "LSEG", | |||
| T_path: "PATH", | |||
| T_box: "BOX", | |||
| T_polygon: "POLYGON", | |||
| T_line: "LINE", | |||
| T__line: "_LINE", | |||
| T_cidr: "CIDR", | |||
| T__cidr: "_CIDR", | |||
| T_float4: "FLOAT4", | |||
| T_float8: "FLOAT8", | |||
| T_abstime: "ABSTIME", | |||
| T_reltime: "RELTIME", | |||
| T_tinterval: "TINTERVAL", | |||
| T_unknown: "UNKNOWN", | |||
| T_circle: "CIRCLE", | |||
| T__circle: "_CIRCLE", | |||
| T_money: "MONEY", | |||
| T__money: "_MONEY", | |||
| T_macaddr: "MACADDR", | |||
| T_inet: "INET", | |||
| T__bool: "_BOOL", | |||
| T__bytea: "_BYTEA", | |||
| T__char: "_CHAR", | |||
| T__name: "_NAME", | |||
| T__int2: "_INT2", | |||
| T__int2vector: "_INT2VECTOR", | |||
| T__int4: "_INT4", | |||
| T__regproc: "_REGPROC", | |||
| T__text: "_TEXT", | |||
| T__tid: "_TID", | |||
| T__xid: "_XID", | |||
| T__cid: "_CID", | |||
| T__oidvector: "_OIDVECTOR", | |||
| T__bpchar: "_BPCHAR", | |||
| T__varchar: "_VARCHAR", | |||
| T__int8: "_INT8", | |||
| T__point: "_POINT", | |||
| T__lseg: "_LSEG", | |||
| T__path: "_PATH", | |||
| T__box: "_BOX", | |||
| T__float4: "_FLOAT4", | |||
| T__float8: "_FLOAT8", | |||
| T__abstime: "_ABSTIME", | |||
| T__reltime: "_RELTIME", | |||
| T__tinterval: "_TINTERVAL", | |||
| T__polygon: "_POLYGON", | |||
| T__oid: "_OID", | |||
| T_aclitem: "ACLITEM", | |||
| T__aclitem: "_ACLITEM", | |||
| T__macaddr: "_MACADDR", | |||
| T__inet: "_INET", | |||
| T_bpchar: "BPCHAR", | |||
| T_varchar: "VARCHAR", | |||
| T_date: "DATE", | |||
| T_time: "TIME", | |||
| T_timestamp: "TIMESTAMP", | |||
| T__timestamp: "_TIMESTAMP", | |||
| T__date: "_DATE", | |||
| T__time: "_TIME", | |||
| T_timestamptz: "TIMESTAMPTZ", | |||
| T__timestamptz: "_TIMESTAMPTZ", | |||
| T_interval: "INTERVAL", | |||
| T__interval: "_INTERVAL", | |||
| T__numeric: "_NUMERIC", | |||
| T_pg_database: "PG_DATABASE", | |||
| T__cstring: "_CSTRING", | |||
| T_timetz: "TIMETZ", | |||
| T__timetz: "_TIMETZ", | |||
| T_bit: "BIT", | |||
| T__bit: "_BIT", | |||
| T_varbit: "VARBIT", | |||
| T__varbit: "_VARBIT", | |||
| T_numeric: "NUMERIC", | |||
| T_refcursor: "REFCURSOR", | |||
| T__refcursor: "_REFCURSOR", | |||
| T_regprocedure: "REGPROCEDURE", | |||
| T_regoper: "REGOPER", | |||
| T_regoperator: "REGOPERATOR", | |||
| T_regclass: "REGCLASS", | |||
| T_regtype: "REGTYPE", | |||
| T__regprocedure: "_REGPROCEDURE", | |||
| T__regoper: "_REGOPER", | |||
| T__regoperator: "_REGOPERATOR", | |||
| T__regclass: "_REGCLASS", | |||
| T__regtype: "_REGTYPE", | |||
| T_record: "RECORD", | |||
| T_cstring: "CSTRING", | |||
| T_any: "ANY", | |||
| T_anyarray: "ANYARRAY", | |||
| T_void: "VOID", | |||
| T_trigger: "TRIGGER", | |||
| T_language_handler: "LANGUAGE_HANDLER", | |||
| T_internal: "INTERNAL", | |||
| T_opaque: "OPAQUE", | |||
| T_anyelement: "ANYELEMENT", | |||
| T__record: "_RECORD", | |||
| T_anynonarray: "ANYNONARRAY", | |||
| T_pg_authid: "PG_AUTHID", | |||
| T_pg_auth_members: "PG_AUTH_MEMBERS", | |||
| T__txid_snapshot: "_TXID_SNAPSHOT", | |||
| T_uuid: "UUID", | |||
| T__uuid: "_UUID", | |||
| T_txid_snapshot: "TXID_SNAPSHOT", | |||
| T_fdw_handler: "FDW_HANDLER", | |||
| T_pg_lsn: "PG_LSN", | |||
| T__pg_lsn: "_PG_LSN", | |||
| T_tsm_handler: "TSM_HANDLER", | |||
| T_anyenum: "ANYENUM", | |||
| T_tsvector: "TSVECTOR", | |||
| T_tsquery: "TSQUERY", | |||
| T_gtsvector: "GTSVECTOR", | |||
| T__tsvector: "_TSVECTOR", | |||
| T__gtsvector: "_GTSVECTOR", | |||
| T__tsquery: "_TSQUERY", | |||
| T_regconfig: "REGCONFIG", | |||
| T__regconfig: "_REGCONFIG", | |||
| T_regdictionary: "REGDICTIONARY", | |||
| T__regdictionary: "_REGDICTIONARY", | |||
| T_jsonb: "JSONB", | |||
| T__jsonb: "_JSONB", | |||
| T_anyrange: "ANYRANGE", | |||
| T_event_trigger: "EVENT_TRIGGER", | |||
| T_int4range: "INT4RANGE", | |||
| T__int4range: "_INT4RANGE", | |||
| T_numrange: "NUMRANGE", | |||
| T__numrange: "_NUMRANGE", | |||
| T_tsrange: "TSRANGE", | |||
| T__tsrange: "_TSRANGE", | |||
| T_tstzrange: "TSTZRANGE", | |||
| T__tstzrange: "_TSTZRANGE", | |||
| T_daterange: "DATERANGE", | |||
| T__daterange: "_DATERANGE", | |||
| T_int8range: "INT8RANGE", | |||
| T__int8range: "_INT8RANGE", | |||
| T_pg_shseclabel: "PG_SHSECLABEL", | |||
| T_regnamespace: "REGNAMESPACE", | |||
| T__regnamespace: "_REGNAMESPACE", | |||
| T_regrole: "REGROLE", | |||
| T__regrole: "_REGROLE", | |||
| } | |||
| @@ -0,0 +1,93 @@ | |||
| package pq | |||
| import ( | |||
| "math" | |||
| "reflect" | |||
| "time" | |||
| "github.com/lib/pq/oid" | |||
| ) | |||
| const headerSize = 4 | |||
| type fieldDesc struct { | |||
| // The object ID of the data type. | |||
| OID oid.Oid | |||
| // The data type size (see pg_type.typlen). | |||
| // Note that negative values denote variable-width types. | |||
| Len int | |||
| // The type modifier (see pg_attribute.atttypmod). | |||
| // The meaning of the modifier is type-specific. | |||
| Mod int | |||
| } | |||
| func (fd fieldDesc) Type() reflect.Type { | |||
| switch fd.OID { | |||
| case oid.T_int8: | |||
| return reflect.TypeOf(int64(0)) | |||
| case oid.T_int4: | |||
| return reflect.TypeOf(int32(0)) | |||
| case oid.T_int2: | |||
| return reflect.TypeOf(int16(0)) | |||
| case oid.T_varchar, oid.T_text: | |||
| return reflect.TypeOf("") | |||
| case oid.T_bool: | |||
| return reflect.TypeOf(false) | |||
| case oid.T_date, oid.T_time, oid.T_timetz, oid.T_timestamp, oid.T_timestamptz: | |||
| return reflect.TypeOf(time.Time{}) | |||
| case oid.T_bytea: | |||
| return reflect.TypeOf([]byte(nil)) | |||
| default: | |||
| return reflect.TypeOf(new(interface{})).Elem() | |||
| } | |||
| } | |||
| func (fd fieldDesc) Name() string { | |||
| return oid.TypeName[fd.OID] | |||
| } | |||
| func (fd fieldDesc) Length() (length int64, ok bool) { | |||
| switch fd.OID { | |||
| case oid.T_text, oid.T_bytea: | |||
| return math.MaxInt64, true | |||
| case oid.T_varchar, oid.T_bpchar: | |||
| return int64(fd.Mod - headerSize), true | |||
| default: | |||
| return 0, false | |||
| } | |||
| } | |||
| func (fd fieldDesc) PrecisionScale() (precision, scale int64, ok bool) { | |||
| switch fd.OID { | |||
| case oid.T_numeric, oid.T__numeric: | |||
| mod := fd.Mod - headerSize | |||
| precision = int64((mod >> 16) & 0xffff) | |||
| scale = int64(mod & 0xffff) | |||
| return precision, scale, true | |||
| default: | |||
| return 0, 0, false | |||
| } | |||
| } | |||
| // ColumnTypeScanType returns the value type that can be used to scan types into. | |||
| func (rs *rows) ColumnTypeScanType(index int) reflect.Type { | |||
| return rs.colTyps[index].Type() | |||
| } | |||
| // ColumnTypeDatabaseTypeName return the database system type name. | |||
| func (rs *rows) ColumnTypeDatabaseTypeName(index int) string { | |||
| return rs.colTyps[index].Name() | |||
| } | |||
| // ColumnTypeLength returns the length of the column type if the column is a | |||
| // variable length type. If the column is not a variable length type ok | |||
| // should return false. | |||
| func (rs *rows) ColumnTypeLength(index int) (length int64, ok bool) { | |||
| return rs.colTyps[index].Length() | |||
| } | |||
| // ColumnTypePrecisionScale should return the precision and scale for decimal | |||
| // types. If not applicable, ok should be false. | |||
| func (rs *rows) ColumnTypePrecisionScale(index int) (precision, scale int64, ok bool) { | |||
| return rs.colTyps[index].PrecisionScale() | |||
| } | |||
| @@ -0,0 +1,158 @@ | |||
| package pq | |||
| import ( | |||
| "crypto/tls" | |||
| "crypto/x509" | |||
| "io/ioutil" | |||
| "net" | |||
| "os" | |||
| "os/user" | |||
| "path/filepath" | |||
| ) | |||
| // ssl generates a function to upgrade a net.Conn based on the "sslmode" and | |||
| // related settings. The function is nil when no upgrade should take place. | |||
| func ssl(o values) func(net.Conn) net.Conn { | |||
| verifyCaOnly := false | |||
| tlsConf := tls.Config{} | |||
| switch mode := o["sslmode"]; mode { | |||
| // "require" is the default. | |||
| case "", "require": | |||
| // We must skip TLS's own verification since it requires full | |||
| // verification since Go 1.3. | |||
| tlsConf.InsecureSkipVerify = true | |||
| // From http://www.postgresql.org/docs/current/static/libpq-ssl.html: | |||
| // | |||
| // Note: For backwards compatibility with earlier versions of | |||
| // PostgreSQL, if a root CA file exists, the behavior of | |||
| // sslmode=require will be the same as that of verify-ca, meaning the | |||
| // server certificate is validated against the CA. Relying on this | |||
| // behavior is discouraged, and applications that need certificate | |||
| // validation should always use verify-ca or verify-full. | |||
| if sslrootcert, ok := o["sslrootcert"]; ok { | |||
| if _, err := os.Stat(sslrootcert); err == nil { | |||
| verifyCaOnly = true | |||
| } else { | |||
| delete(o, "sslrootcert") | |||
| } | |||
| } | |||
| case "verify-ca": | |||
| // We must skip TLS's own verification since it requires full | |||
| // verification since Go 1.3. | |||
| tlsConf.InsecureSkipVerify = true | |||
| verifyCaOnly = true | |||
| case "verify-full": | |||
| tlsConf.ServerName = o["host"] | |||
| case "disable": | |||
| return nil | |||
| default: | |||
| errorf(`unsupported sslmode %q; only "require" (default), "verify-full", "verify-ca", and "disable" supported`, mode) | |||
| } | |||
| sslClientCertificates(&tlsConf, o) | |||
| sslCertificateAuthority(&tlsConf, o) | |||
| sslRenegotiation(&tlsConf) | |||
| return func(conn net.Conn) net.Conn { | |||
| client := tls.Client(conn, &tlsConf) | |||
| if verifyCaOnly { | |||
| sslVerifyCertificateAuthority(client, &tlsConf) | |||
| } | |||
| return client | |||
| } | |||
| } | |||
| // sslClientCertificates adds the certificate specified in the "sslcert" and | |||
| // "sslkey" settings, or if they aren't set, from the .postgresql directory | |||
| // in the user's home directory. The configured files must exist and have | |||
| // the correct permissions. | |||
| func sslClientCertificates(tlsConf *tls.Config, o values) { | |||
| // 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. | |||
| user, _ := user.Current() | |||
| // In libpq, the client certificate is only loaded if the setting is not blank. | |||
| // | |||
| // https://github.com/postgres/postgres/blob/REL9_6_2/src/interfaces/libpq/fe-secure-openssl.c#L1036-L1037 | |||
| sslcert := o["sslcert"] | |||
| if len(sslcert) == 0 && user != nil { | |||
| sslcert = filepath.Join(user.HomeDir, ".postgresql", "postgresql.crt") | |||
| } | |||
| // https://github.com/postgres/postgres/blob/REL9_6_2/src/interfaces/libpq/fe-secure-openssl.c#L1045 | |||
| if len(sslcert) == 0 { | |||
| return | |||
| } | |||
| // https://github.com/postgres/postgres/blob/REL9_6_2/src/interfaces/libpq/fe-secure-openssl.c#L1050:L1054 | |||
| if _, err := os.Stat(sslcert); os.IsNotExist(err) { | |||
| return | |||
| } else if err != nil { | |||
| panic(err) | |||
| } | |||
| // In libpq, the ssl key is only loaded if the setting is not blank. | |||
| // | |||
| // https://github.com/postgres/postgres/blob/REL9_6_2/src/interfaces/libpq/fe-secure-openssl.c#L1123-L1222 | |||
| sslkey := o["sslkey"] | |||
| if len(sslkey) == 0 && user != nil { | |||
| sslkey = filepath.Join(user.HomeDir, ".postgresql", "postgresql.key") | |||
| } | |||
| if len(sslkey) > 0 { | |||
| if err := sslKeyPermissions(sslkey); err != nil { | |||
| panic(err) | |||
| } | |||
| } | |||
| cert, err := tls.LoadX509KeyPair(sslcert, sslkey) | |||
| if err != nil { | |||
| panic(err) | |||
| } | |||
| tlsConf.Certificates = []tls.Certificate{cert} | |||
| } | |||
| // sslCertificateAuthority adds the RootCA specified in the "sslrootcert" setting. | |||
| func sslCertificateAuthority(tlsConf *tls.Config, o values) { | |||
| // In libpq, the root certificate is only loaded if the setting is not blank. | |||
| // | |||
| // https://github.com/postgres/postgres/blob/REL9_6_2/src/interfaces/libpq/fe-secure-openssl.c#L950-L951 | |||
| if sslrootcert := o["sslrootcert"]; len(sslrootcert) > 0 { | |||
| tlsConf.RootCAs = x509.NewCertPool() | |||
| cert, err := ioutil.ReadFile(sslrootcert) | |||
| if err != nil { | |||
| panic(err) | |||
| } | |||
| if !tlsConf.RootCAs.AppendCertsFromPEM(cert) { | |||
| errorf("couldn't parse pem in sslrootcert") | |||
| } | |||
| } | |||
| } | |||
| // sslVerifyCertificateAuthority carries out a TLS handshake to the server and | |||
| // verifies the presented certificate against the CA, i.e. the one specified in | |||
| // sslrootcert or the system CA if sslrootcert was not specified. | |||
| func sslVerifyCertificateAuthority(client *tls.Conn, tlsConf *tls.Config) { | |||
| err := client.Handshake() | |||
| if err != nil { | |||
| panic(err) | |||
| } | |||
| certs := client.ConnectionState().PeerCertificates | |||
| opts := x509.VerifyOptions{ | |||
| DNSName: client.ConnectionState().ServerName, | |||
| Intermediates: x509.NewCertPool(), | |||
| Roots: tlsConf.RootCAs, | |||
| } | |||
| for i, cert := range certs { | |||
| if i == 0 { | |||
| continue | |||
| } | |||
| opts.Intermediates.AddCert(cert) | |||
| } | |||
| _, err = certs[0].Verify(opts) | |||
| if err != nil { | |||
| panic(err) | |||
| } | |||
| } | |||
| @@ -0,0 +1,14 @@ | |||
| // +build go1.7 | |||
| package pq | |||
| import "crypto/tls" | |||
| // Accept renegotiation requests initiated by the backend. | |||
| // | |||
| // Renegotiation was deprecated then removed from PostgreSQL 9.5, but | |||
| // the default configuration of older versions has it enabled. Redshift | |||
| // also initiates renegotiations and cannot be reconfigured. | |||
| func sslRenegotiation(conf *tls.Config) { | |||
| conf.Renegotiation = tls.RenegotiateFreelyAsClient | |||
| } | |||
| @@ -0,0 +1,20 @@ | |||
| // +build !windows | |||
| package pq | |||
| import "os" | |||
| // sslKeyPermissions checks the permissions on user-supplied ssl key files. | |||
| // The key file should have very little access. | |||
| // | |||
| // libpq does not check key file permissions on Windows. | |||
| func sslKeyPermissions(sslkey string) error { | |||
| info, err := os.Stat(sslkey) | |||
| if err != nil { | |||
| return err | |||
| } | |||
| if info.Mode().Perm()&0077 != 0 { | |||
| return ErrSSLKeyHasWorldPermissions | |||
| } | |||
| return nil | |||
| } | |||
| @@ -0,0 +1,8 @@ | |||
| // +build !go1.7 | |||
| package pq | |||
| import "crypto/tls" | |||
| // Renegotiation is not supported by crypto/tls until Go 1.7. | |||
| func sslRenegotiation(*tls.Config) {} | |||
| @@ -0,0 +1,9 @@ | |||
| // +build windows | |||
| package pq | |||
| // sslKeyPermissions checks the permissions on user-supplied ssl key files. | |||
| // The key file should have very little access. | |||
| // | |||
| // libpq does not check key file permissions on Windows. | |||
| func sslKeyPermissions(string) error { return nil } | |||
| @@ -0,0 +1,23 @@ | |||
| package pq | |||
| import ( | |||
| "encoding/hex" | |||
| "fmt" | |||
| ) | |||
| // decodeUUIDBinary interprets the binary format of a uuid, returning it in text format. | |||
| func decodeUUIDBinary(src []byte) ([]byte, error) { | |||
| if len(src) != 16 { | |||
| return nil, fmt.Errorf("pq: unable to decode uuid; bad length: %d", len(src)) | |||
| } | |||
| dst := make([]byte, 36) | |||
| dst[8], dst[13], dst[18], dst[23] = '-', '-', '-', '-' | |||
| hex.Encode(dst[0:], src[0:4]) | |||
| hex.Encode(dst[9:], src[4:6]) | |||
| hex.Encode(dst[14:], src[6:8]) | |||
| hex.Encode(dst[19:], src[8:10]) | |||
| hex.Encode(dst[24:], src[10:16]) | |||
| return dst, nil | |||
| } | |||
| @@ -624,16 +624,16 @@ | |||
| "revisionTime": "2016-10-16T15:41:25Z" | |||
| }, | |||
| { | |||
| "checksumSHA1": "avqi4lkviHdrNJ92cXCwrw9x870=", | |||
| "checksumSHA1": "QV4HZTfaXvhD+5PcGM2p+7aCYYI=", | |||
| "path": "github.com/lib/pq", | |||
| "revision": "d8eeeb8bae8896dd8e1b7e514ab0d396c4f12a1b", | |||
| "revisionTime": "2016-11-03T02:43:54Z" | |||
| "revision": "456514e2defec52e0cd37f90ccf17ec8b28295e2", | |||
| "revisionTime": "2017-10-19T22:30:07Z" | |||
| }, | |||
| { | |||
| "checksumSHA1": "xppHi82MLqVx1eyQmbhTesAEjx8=", | |||
| "checksumSHA1": "AU3fA8Sm33Vj9PBoRPSeYfxLRuE=", | |||
| "path": "github.com/lib/pq/oid", | |||
| "revision": "d8eeeb8bae8896dd8e1b7e514ab0d396c4f12a1b", | |||
| "revisionTime": "2016-11-03T02:43:54Z" | |||
| "revision": "456514e2defec52e0cd37f90ccf17ec8b28295e2", | |||
| "revisionTime": "2017-10-19T22:30:07Z" | |||
| }, | |||
| { | |||
| "checksumSHA1": "O3KUfEXQPfdQ+tCMpP2RAIRJJqY=", | |||