Update pg dependency to latest
This commit is contained in:
667
Godeps/_workspace/src/github.com/lib/pq/conn.go
generated
vendored
667
Godeps/_workspace/src/github.com/lib/pq/conn.go
generated
vendored
@@ -10,7 +10,6 @@ import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/lib/pq/oid"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
@@ -22,6 +21,8 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
"unicode"
|
||||
|
||||
"github.com/lib/pq/oid"
|
||||
)
|
||||
|
||||
// Common error types
|
||||
@@ -105,12 +106,49 @@ type conn struct {
|
||||
// If true, this connection is bad and all public-facing functions should
|
||||
// return ErrBadConn.
|
||||
bad bool
|
||||
|
||||
// If set, this connection should never use the binary format when
|
||||
// receiving query results from prepared statements. Only provided for
|
||||
// debugging.
|
||||
disablePreparedBinaryResult bool
|
||||
|
||||
// Whether to always send []byte parameters over as binary. Enables single
|
||||
// round-trip mode for non-prepared Query calls.
|
||||
binaryParameters bool
|
||||
}
|
||||
|
||||
// Handle driver-side settings in parsed connection string.
|
||||
func (c *conn) handleDriverSettings(o values) (err error) {
|
||||
boolSetting := func(key string, val *bool) error {
|
||||
if value := o.Get(key); value != "" {
|
||||
if value == "yes" {
|
||||
*val = true
|
||||
} else if value == "no" {
|
||||
*val = false
|
||||
} else {
|
||||
return fmt.Errorf("unrecognized value %q for %s", value, key)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
err = boolSetting("disable_prepared_binary_result", &c.disablePreparedBinaryResult)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = boolSetting("binary_parameters", &c.binaryParameters)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *conn) writeBuf(b byte) *writeBuf {
|
||||
c.scratch[0] = b
|
||||
w := writeBuf(c.scratch[:5])
|
||||
return &w
|
||||
return &writeBuf{
|
||||
buf: c.scratch[:5],
|
||||
pos: 1,
|
||||
}
|
||||
}
|
||||
|
||||
func Open(name string) (_ driver.Conn, err error) {
|
||||
@@ -118,22 +156,11 @@ func Open(name string) (_ driver.Conn, err error) {
|
||||
}
|
||||
|
||||
func DialOpen(d Dialer, name string) (_ driver.Conn, err error) {
|
||||
defer func() {
|
||||
// Handle any panics during connection initialization. Note that we
|
||||
// specifically do *not* want to use errRecover(), as that would turn
|
||||
// any connection errors into ErrBadConns, hiding the real error
|
||||
// message from the user.
|
||||
e := recover()
|
||||
if e == nil {
|
||||
// Do nothing
|
||||
return
|
||||
}
|
||||
var ok bool
|
||||
err, ok = e.(error)
|
||||
if !ok {
|
||||
err = fmt.Errorf("pq: unexpected error: %#v", e)
|
||||
}
|
||||
}()
|
||||
// Handle any panics during connection initialization. Note that we
|
||||
// specifically do *not* want to use errRecover(), as that would turn any
|
||||
// connection errors into ErrBadConns, hiding the real error message from
|
||||
// the user.
|
||||
defer errRecoverNoErrBadConn(&err)
|
||||
|
||||
o := make(values)
|
||||
|
||||
@@ -151,7 +178,7 @@ func DialOpen(d Dialer, name string) (_ driver.Conn, err error) {
|
||||
o.Set(k, v)
|
||||
}
|
||||
|
||||
if strings.HasPrefix(name, "postgres://") {
|
||||
if strings.HasPrefix(name, "postgres://") || strings.HasPrefix(name, "postgresql://") {
|
||||
name, err = ParseURL(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -202,27 +229,36 @@ func DialOpen(d Dialer, name string) (_ driver.Conn, err error) {
|
||||
}
|
||||
}
|
||||
|
||||
c, err := dial(d, o)
|
||||
cn := &conn{}
|
||||
err = cn.handleDriverSettings(o)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cn := &conn{c: c}
|
||||
cn.c, err = dial(d, o)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cn.ssl(o)
|
||||
cn.buf = bufio.NewReader(cn.c)
|
||||
cn.startup(o)
|
||||
|
||||
// reset the deadline, in case one was set (see dial)
|
||||
err = cn.c.SetDeadline(time.Time{})
|
||||
if timeout := o.Get("connect_timeout"); timeout != "" && timeout != "0" {
|
||||
err = cn.c.SetDeadline(time.Time{})
|
||||
}
|
||||
return cn, err
|
||||
}
|
||||
|
||||
func dial(d Dialer, o values) (net.Conn, error) {
|
||||
ntw, addr := network(o)
|
||||
|
||||
timeout := o.Get("connect_timeout")
|
||||
// SSL is not necessary or supported over UNIX domain sockets
|
||||
if ntw == "unix" {
|
||||
o["sslmode"] = "disable"
|
||||
}
|
||||
|
||||
// Zero or not specified means wait indefinitely.
|
||||
if timeout != "" && timeout != "0" {
|
||||
if timeout := o.Get("connect_timeout"); timeout != "" && timeout != "0" {
|
||||
seconds, err := strconv.ParseInt(timeout, 10, 0)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid value for parameter connect_timeout: %s", err)
|
||||
@@ -436,6 +472,9 @@ func (cn *conn) Commit() (err error) {
|
||||
|
||||
_, commandTag, err := cn.simpleExec("COMMIT")
|
||||
if err != nil {
|
||||
if cn.isInTransaction() {
|
||||
cn.bad = true
|
||||
}
|
||||
return err
|
||||
}
|
||||
if commandTag != "COMMIT" {
|
||||
@@ -455,6 +494,9 @@ func (cn *conn) Rollback() (err error) {
|
||||
cn.checkIsInTransaction(true)
|
||||
_, commandTag, err := cn.simpleExec("ROLLBACK")
|
||||
if err != nil {
|
||||
if cn.isInTransaction() {
|
||||
cn.bad = true
|
||||
}
|
||||
return err
|
||||
}
|
||||
if commandTag != "ROLLBACK" {
|
||||
@@ -494,7 +536,7 @@ func (cn *conn) simpleExec(q string) (res driver.Result, commandTag string, err
|
||||
}
|
||||
}
|
||||
|
||||
func (cn *conn) simpleQuery(q string) (res driver.Rows, err error) {
|
||||
func (cn *conn) simpleQuery(q string) (res *rows, err error) {
|
||||
defer cn.errRecover(&err)
|
||||
|
||||
st := &stmt{cn: cn, name: ""}
|
||||
@@ -515,7 +557,13 @@ func (cn *conn) simpleQuery(q string) (res driver.Rows, err error) {
|
||||
cn.bad = true
|
||||
errorf("unexpected message %q in simple query execution", t)
|
||||
}
|
||||
res = &rows{st: st, done: true}
|
||||
res = &rows{
|
||||
cn: cn,
|
||||
colNames: st.colNames,
|
||||
colTyps: st.colTyps,
|
||||
colFmts: st.colFmts,
|
||||
done: true,
|
||||
}
|
||||
case 'Z':
|
||||
cn.processReadyForQuery(r)
|
||||
// done
|
||||
@@ -534,8 +582,8 @@ func (cn *conn) simpleQuery(q string) (res driver.Rows, err error) {
|
||||
case 'T':
|
||||
// res might be non-nil here if we received a previous
|
||||
// CommandComplete, but that's fine; just overwrite it
|
||||
res = &rows{st: st}
|
||||
st.cols, st.rowTyps = parseMeta(r)
|
||||
res = &rows{cn: cn}
|
||||
res.colNames, res.colFmts, res.colTyps = parsePortalRowDescribe(r)
|
||||
|
||||
// To work around a bug in QueryRow in Go 1.2 and earlier, wait
|
||||
// until the first DataRow has been received.
|
||||
@@ -546,47 +594,74 @@ func (cn *conn) simpleQuery(q string) (res driver.Rows, err error) {
|
||||
}
|
||||
}
|
||||
|
||||
func (cn *conn) prepareTo(q, stmtName string) (_ *stmt, err 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) {
|
||||
if len(colTyps) == 0 {
|
||||
return nil, colFmtDataAllText
|
||||
}
|
||||
|
||||
colFmts = make([]format, len(colTyps))
|
||||
if forceText {
|
||||
return colFmts, colFmtDataAllText
|
||||
}
|
||||
|
||||
allBinary := true
|
||||
allText := true
|
||||
for i, o := range colTyps {
|
||||
switch o {
|
||||
// This is the list of types to use binary mode for when receiving them
|
||||
// through a prepared statement. If a type appears in this list, it
|
||||
// must also be implemented in binaryDecode in encode.go.
|
||||
case oid.T_bytea:
|
||||
fallthrough
|
||||
case oid.T_int8:
|
||||
fallthrough
|
||||
case oid.T_int4:
|
||||
fallthrough
|
||||
case oid.T_int2:
|
||||
colFmts[i] = formatBinary
|
||||
allText = false
|
||||
|
||||
default:
|
||||
allBinary = false
|
||||
}
|
||||
}
|
||||
|
||||
if allBinary {
|
||||
return colFmts, colFmtDataAllBinary
|
||||
} else if allText {
|
||||
return colFmts, colFmtDataAllText
|
||||
} else {
|
||||
colFmtData = make([]byte, 2+len(colFmts)*2)
|
||||
binary.BigEndian.PutUint16(colFmtData, uint16(len(colFmts)))
|
||||
for i, v := range colFmts {
|
||||
binary.BigEndian.PutUint16(colFmtData[2+i*2:], uint16(v))
|
||||
}
|
||||
return colFmts, colFmtData
|
||||
}
|
||||
}
|
||||
|
||||
func (cn *conn) prepareTo(q, stmtName string) *stmt {
|
||||
st := &stmt{cn: cn, name: stmtName}
|
||||
|
||||
b := cn.writeBuf('P')
|
||||
b.string(st.name)
|
||||
b.string(q)
|
||||
b.int16(0)
|
||||
cn.send(b)
|
||||
|
||||
b = cn.writeBuf('D')
|
||||
b.next('D')
|
||||
b.byte('S')
|
||||
b.string(st.name)
|
||||
|
||||
b.next('S')
|
||||
cn.send(b)
|
||||
|
||||
cn.send(cn.writeBuf('S'))
|
||||
|
||||
for {
|
||||
t, r := cn.recv1()
|
||||
switch t {
|
||||
case '1':
|
||||
case 't':
|
||||
nparams := r.int16()
|
||||
st.paramTyps = make([]oid.Oid, nparams)
|
||||
|
||||
for i := range st.paramTyps {
|
||||
st.paramTyps[i] = r.oid()
|
||||
}
|
||||
case 'T':
|
||||
st.cols, st.rowTyps = parseMeta(r)
|
||||
case 'n':
|
||||
// no data
|
||||
case 'Z':
|
||||
cn.processReadyForQuery(r)
|
||||
return st, err
|
||||
case 'E':
|
||||
err = parseError(r)
|
||||
default:
|
||||
cn.bad = true
|
||||
errorf("unexpected describe rows response: %q", t)
|
||||
}
|
||||
}
|
||||
cn.readParseResponse()
|
||||
st.paramTyps, st.colNames, st.colTyps = cn.readStatementDescribeResponse()
|
||||
st.colFmts, st.colFmtData = decideColumnFormats(st.colTyps, cn.disablePreparedBinaryResult)
|
||||
cn.readReadyForQuery()
|
||||
return st
|
||||
}
|
||||
|
||||
func (cn *conn) Prepare(q string) (_ driver.Stmt, err error) {
|
||||
@@ -598,7 +673,7 @@ func (cn *conn) Prepare(q string) (_ driver.Stmt, err error) {
|
||||
if len(q) >= 4 && strings.EqualFold(q[:4], "COPY") {
|
||||
return cn.prepareCopyIn(q)
|
||||
}
|
||||
return cn.prepareTo(q, cn.gname())
|
||||
return cn.prepareTo(q, cn.gname()), nil
|
||||
}
|
||||
|
||||
func (cn *conn) Close() (err error) {
|
||||
@@ -630,17 +705,29 @@ func (cn *conn) Query(query string, args []driver.Value) (_ driver.Rows, err err
|
||||
return cn.simpleQuery(query)
|
||||
}
|
||||
|
||||
st, err := cn.prepareTo(query, "")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if cn.binaryParameters {
|
||||
cn.sendBinaryModeQuery(query, args)
|
||||
|
||||
st.exec(args)
|
||||
return &rows{st: st}, nil
|
||||
cn.readParseResponse()
|
||||
cn.readBindResponse()
|
||||
rows := &rows{cn: cn}
|
||||
rows.colNames, rows.colFmts, rows.colTyps = cn.readPortalDescribeResponse()
|
||||
cn.postExecuteWorkaround()
|
||||
return rows, nil
|
||||
} else {
|
||||
st := cn.prepareTo(query, "")
|
||||
st.exec(args)
|
||||
return &rows{
|
||||
cn: cn,
|
||||
colNames: st.colNames,
|
||||
colTyps: st.colTyps,
|
||||
colFmts: st.colFmts,
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
// Implement the optional "Execer" interface for one-shot queries
|
||||
func (cn *conn) Exec(query string, args []driver.Value) (_ driver.Result, err error) {
|
||||
func (cn *conn) Exec(query string, args []driver.Value) (res driver.Result, err error) {
|
||||
if cn.bad {
|
||||
return nil, driver.ErrBadConn
|
||||
}
|
||||
@@ -654,32 +741,42 @@ func (cn *conn) Exec(query string, args []driver.Value) (_ driver.Result, err er
|
||||
return r, err
|
||||
}
|
||||
|
||||
// Use the unnamed statement to defer planning until bind
|
||||
// time, or else value-based selectivity estimates cannot be
|
||||
// used.
|
||||
st, err := cn.prepareTo(query, "")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if cn.binaryParameters {
|
||||
cn.sendBinaryModeQuery(query, args)
|
||||
|
||||
r, err := st.Exec(args)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
cn.readParseResponse()
|
||||
cn.readBindResponse()
|
||||
cn.readPortalDescribeResponse()
|
||||
cn.postExecuteWorkaround()
|
||||
res, _, err = cn.readExecuteResponse("Execute")
|
||||
return res, err
|
||||
} else {
|
||||
// Use the unnamed statement to defer planning until bind
|
||||
// time, or else value-based selectivity estimates cannot be
|
||||
// used.
|
||||
st := cn.prepareTo(query, "")
|
||||
r, err := st.Exec(args)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return r, err
|
||||
}
|
||||
|
||||
return r, err
|
||||
}
|
||||
|
||||
// Assumes len(*m) is > 5
|
||||
func (cn *conn) send(m *writeBuf) {
|
||||
b := (*m)[1:]
|
||||
binary.BigEndian.PutUint32(b, uint32(len(b)))
|
||||
_, err := cn.c.Write(m.wrap())
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
if (*m)[0] == 0 {
|
||||
*m = b
|
||||
func (cn *conn) sendStartupPacket(m *writeBuf) {
|
||||
// sanity check
|
||||
if m.buf[0] != 0 {
|
||||
panic("oops")
|
||||
}
|
||||
|
||||
_, err := cn.c.Write(*m)
|
||||
_, err := cn.c.Write((m.wrap())[1:])
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@@ -819,7 +916,7 @@ func (cn *conn) ssl(o values) {
|
||||
|
||||
w := cn.writeBuf(0)
|
||||
w.int32(80877103)
|
||||
cn.send(w)
|
||||
cn.sendStartupPacket(w)
|
||||
|
||||
b := cn.scratch[:1]
|
||||
_, err := io.ReadFull(cn.c, b)
|
||||
@@ -956,6 +1053,10 @@ func isDriverSetting(key string) bool {
|
||||
return true
|
||||
case "connect_timeout":
|
||||
return true
|
||||
case "disable_prepared_binary_result":
|
||||
return true
|
||||
case "binary_parameters":
|
||||
return true
|
||||
|
||||
default:
|
||||
return false
|
||||
@@ -983,7 +1084,7 @@ func (cn *conn) startup(o values) {
|
||||
w.string(v)
|
||||
}
|
||||
w.string("")
|
||||
cn.send(w)
|
||||
cn.sendStartupPacket(w)
|
||||
|
||||
for {
|
||||
t, r := cn.recv()
|
||||
@@ -1038,13 +1139,26 @@ func (cn *conn) auth(r *readBuf, o values) {
|
||||
}
|
||||
}
|
||||
|
||||
type format int
|
||||
|
||||
const formatText format = 0
|
||||
const formatBinary format = 1
|
||||
|
||||
// One result-column format code with the value 1 (i.e. all binary).
|
||||
var colFmtDataAllBinary []byte = []byte{0, 1, 0, 1}
|
||||
|
||||
// No result-column format codes (i.e. all text).
|
||||
var colFmtDataAllText []byte = []byte{0, 0}
|
||||
|
||||
type stmt struct {
|
||||
cn *conn
|
||||
name string
|
||||
cols []string
|
||||
rowTyps []oid.Oid
|
||||
paramTyps []oid.Oid
|
||||
closed bool
|
||||
cn *conn
|
||||
name string
|
||||
colNames []string
|
||||
colFmts []format
|
||||
colFmtData []byte
|
||||
colTyps []oid.Oid
|
||||
paramTyps []oid.Oid
|
||||
closed bool
|
||||
}
|
||||
|
||||
func (st *stmt) Close() (err error) {
|
||||
@@ -1087,7 +1201,12 @@ func (st *stmt) Query(v []driver.Value) (r driver.Rows, err error) {
|
||||
defer st.cn.errRecover(&err)
|
||||
|
||||
st.exec(v)
|
||||
return &rows{st: st}, nil
|
||||
return &rows{
|
||||
cn: st.cn,
|
||||
colNames: st.colNames,
|
||||
colTyps: st.colTyps,
|
||||
colFmts: st.colFmts,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (st *stmt) Exec(v []driver.Value) (res driver.Result, err error) {
|
||||
@@ -1097,25 +1216,8 @@ func (st *stmt) Exec(v []driver.Value) (res driver.Result, err error) {
|
||||
defer st.cn.errRecover(&err)
|
||||
|
||||
st.exec(v)
|
||||
|
||||
for {
|
||||
t, r := st.cn.recv1()
|
||||
switch t {
|
||||
case 'E':
|
||||
err = parseError(r)
|
||||
case 'C':
|
||||
res, _ = st.cn.parseComplete(r.string())
|
||||
case 'Z':
|
||||
st.cn.processReadyForQuery(r)
|
||||
// done
|
||||
return
|
||||
case 'T', 'D', 'I':
|
||||
// ignore any results
|
||||
default:
|
||||
st.cn.bad = true
|
||||
errorf("unknown exec response: %q", t)
|
||||
}
|
||||
}
|
||||
res, _, err = st.cn.readExecuteResponse("simple query")
|
||||
return res, err
|
||||
}
|
||||
|
||||
func (st *stmt) exec(v []driver.Value) {
|
||||
@@ -1126,84 +1228,38 @@ func (st *stmt) exec(v []driver.Value) {
|
||||
errorf("got %d parameters but the statement requires %d", len(v), len(st.paramTyps))
|
||||
}
|
||||
|
||||
w := st.cn.writeBuf('B')
|
||||
w.string("")
|
||||
cn := st.cn
|
||||
w := cn.writeBuf('B')
|
||||
w.byte(0) // unnamed portal
|
||||
w.string(st.name)
|
||||
w.int16(0)
|
||||
w.int16(len(v))
|
||||
for i, x := range v {
|
||||
if x == nil {
|
||||
w.int32(-1)
|
||||
} else {
|
||||
b := encode(&st.cn.parameterStatus, x, st.paramTyps[i])
|
||||
w.int32(len(b))
|
||||
w.bytes(b)
|
||||
|
||||
if cn.binaryParameters {
|
||||
cn.sendBinaryParameters(w, v)
|
||||
} else {
|
||||
w.int16(0)
|
||||
w.int16(len(v))
|
||||
for i, x := range v {
|
||||
if x == nil {
|
||||
w.int32(-1)
|
||||
} else {
|
||||
b := encode(&cn.parameterStatus, x, st.paramTyps[i])
|
||||
w.int32(len(b))
|
||||
w.bytes(b)
|
||||
}
|
||||
}
|
||||
}
|
||||
w.int16(0)
|
||||
st.cn.send(w)
|
||||
w.bytes(st.colFmtData)
|
||||
|
||||
w = st.cn.writeBuf('E')
|
||||
w.string("")
|
||||
w.next('E')
|
||||
w.byte(0)
|
||||
w.int32(0)
|
||||
st.cn.send(w)
|
||||
|
||||
st.cn.send(st.cn.writeBuf('S'))
|
||||
w.next('S')
|
||||
cn.send(w)
|
||||
|
||||
var err error
|
||||
for {
|
||||
t, r := st.cn.recv1()
|
||||
switch t {
|
||||
case 'E':
|
||||
err = parseError(r)
|
||||
case '2':
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
goto workaround
|
||||
case 'Z':
|
||||
st.cn.processReadyForQuery(r)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return
|
||||
default:
|
||||
st.cn.bad = true
|
||||
errorf("unexpected bind response: %q", t)
|
||||
}
|
||||
}
|
||||
cn.readBindResponse()
|
||||
cn.postExecuteWorkaround()
|
||||
|
||||
// Work around a bug in sql.DB.QueryRow: in Go 1.2 and earlier it ignores
|
||||
// any errors from rows.Next, which masks errors that happened during the
|
||||
// execution of the query. To avoid the problem in common cases, we wait
|
||||
// here for one more message from the database. If it's not an error the
|
||||
// query will likely succeed (or perhaps has already, if it's a
|
||||
// CommandComplete), so we push the message into the conn struct; recv1
|
||||
// will return it as the next message for rows.Next or rows.Close.
|
||||
// However, if it's an error, we wait until ReadyForQuery and then return
|
||||
// the error to our caller.
|
||||
workaround:
|
||||
for {
|
||||
t, r := st.cn.recv1()
|
||||
switch t {
|
||||
case 'E':
|
||||
err = parseError(r)
|
||||
case 'C', 'D', 'I':
|
||||
// the query didn't fail, but we can't process this message
|
||||
st.cn.saveMessage(t, r)
|
||||
return
|
||||
case 'Z':
|
||||
if err == nil {
|
||||
st.cn.bad = true
|
||||
errorf("unexpected ReadyForQuery during extended query execution")
|
||||
}
|
||||
st.cn.processReadyForQuery(r)
|
||||
panic(err)
|
||||
default:
|
||||
st.cn.bad = true
|
||||
errorf("unexpected message during query execution: %q", t)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (st *stmt) NumInput() int {
|
||||
@@ -1260,9 +1316,12 @@ func (cn *conn) parseComplete(commandTag string) (driver.Result, string) {
|
||||
}
|
||||
|
||||
type rows struct {
|
||||
st *stmt
|
||||
done bool
|
||||
rb readBuf
|
||||
cn *conn
|
||||
colNames []string
|
||||
colTyps []oid.Oid
|
||||
colFmts []format
|
||||
done bool
|
||||
rb readBuf
|
||||
}
|
||||
|
||||
func (rs *rows) Close() error {
|
||||
@@ -1280,7 +1339,7 @@ func (rs *rows) Close() error {
|
||||
}
|
||||
|
||||
func (rs *rows) Columns() []string {
|
||||
return rs.st.cols
|
||||
return rs.colNames
|
||||
}
|
||||
|
||||
func (rs *rows) Next(dest []driver.Value) (err error) {
|
||||
@@ -1288,7 +1347,7 @@ func (rs *rows) Next(dest []driver.Value) (err error) {
|
||||
return io.EOF
|
||||
}
|
||||
|
||||
conn := rs.st.cn
|
||||
conn := rs.cn
|
||||
if conn.bad {
|
||||
return driver.ErrBadConn
|
||||
}
|
||||
@@ -1319,7 +1378,7 @@ func (rs *rows) Next(dest []driver.Value) (err error) {
|
||||
dest[i] = nil
|
||||
continue
|
||||
}
|
||||
dest[i] = decode(&conn.parameterStatus, rs.rb.next(l), rs.st.rowTyps[i])
|
||||
dest[i] = decode(&conn.parameterStatus, rs.rb.next(l), rs.colTyps[i], rs.colFmts[i])
|
||||
}
|
||||
return
|
||||
default:
|
||||
@@ -1352,6 +1411,68 @@ func md5s(s string) string {
|
||||
return fmt.Sprintf("%x", h.Sum(nil))
|
||||
}
|
||||
|
||||
func (cn *conn) sendBinaryParameters(b *writeBuf, args []driver.Value) {
|
||||
// Do one pass over the parameters to see if we're going to send any of
|
||||
// them over in binary. If we are, create a paramFormats array at the
|
||||
// same time.
|
||||
var paramFormats []int
|
||||
for i, x := range args {
|
||||
_, ok := x.([]byte)
|
||||
if ok {
|
||||
if paramFormats == nil {
|
||||
paramFormats = make([]int, len(args))
|
||||
}
|
||||
paramFormats[i] = 1
|
||||
}
|
||||
}
|
||||
if paramFormats == nil {
|
||||
b.int16(0)
|
||||
} else {
|
||||
b.int16(len(paramFormats))
|
||||
for _, x := range paramFormats {
|
||||
b.int16(x)
|
||||
}
|
||||
}
|
||||
|
||||
b.int16(len(args))
|
||||
for _, x := range args {
|
||||
if x == nil {
|
||||
b.int32(-1)
|
||||
} else {
|
||||
datum := binaryEncode(&cn.parameterStatus, x)
|
||||
b.int32(len(datum))
|
||||
b.bytes(datum)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (cn *conn) sendBinaryModeQuery(query string, args []driver.Value) {
|
||||
if len(args) >= 65536 {
|
||||
errorf("got %d parameters but PostgreSQL only supports 65535 parameters", len(args))
|
||||
}
|
||||
|
||||
b := cn.writeBuf('P')
|
||||
b.byte(0) // unnamed statement
|
||||
b.string(query)
|
||||
b.int16(0)
|
||||
|
||||
b.next('B')
|
||||
b.int16(0) // unnamed portal and statement
|
||||
cn.sendBinaryParameters(b, args)
|
||||
b.bytes(colFmtDataAllText)
|
||||
|
||||
b.next('D')
|
||||
b.byte('P')
|
||||
b.byte(0) // unnamed portal
|
||||
|
||||
b.next('E')
|
||||
b.byte(0)
|
||||
b.int32(0)
|
||||
|
||||
b.next('S')
|
||||
cn.send(b)
|
||||
}
|
||||
|
||||
func (c *conn) processParameterStatus(r *readBuf) {
|
||||
var err error
|
||||
|
||||
@@ -1381,15 +1502,167 @@ func (c *conn) processReadyForQuery(r *readBuf) {
|
||||
c.txnStatus = transactionStatus(r.byte())
|
||||
}
|
||||
|
||||
func parseMeta(r *readBuf) (cols []string, rowTyps []oid.Oid) {
|
||||
func (cn *conn) readReadyForQuery() {
|
||||
t, r := cn.recv1()
|
||||
switch t {
|
||||
case 'Z':
|
||||
cn.processReadyForQuery(r)
|
||||
return
|
||||
default:
|
||||
cn.bad = true
|
||||
errorf("unexpected message %q; expected ReadyForQuery", t)
|
||||
}
|
||||
}
|
||||
|
||||
func (cn *conn) readParseResponse() {
|
||||
t, r := cn.recv1()
|
||||
switch t {
|
||||
case '1':
|
||||
return
|
||||
case 'E':
|
||||
err := parseError(r)
|
||||
cn.readReadyForQuery()
|
||||
panic(err)
|
||||
default:
|
||||
cn.bad = true
|
||||
errorf("unexpected Parse response %q", t)
|
||||
}
|
||||
}
|
||||
|
||||
func (cn *conn) readStatementDescribeResponse() (paramTyps []oid.Oid, colNames []string, colTyps []oid.Oid) {
|
||||
for {
|
||||
t, r := cn.recv1()
|
||||
switch t {
|
||||
case 't':
|
||||
nparams := r.int16()
|
||||
paramTyps = make([]oid.Oid, nparams)
|
||||
for i := range paramTyps {
|
||||
paramTyps[i] = r.oid()
|
||||
}
|
||||
case 'n':
|
||||
return paramTyps, nil, nil
|
||||
case 'T':
|
||||
colNames, colTyps = parseStatementRowDescribe(r)
|
||||
return paramTyps, colNames, colTyps
|
||||
case 'E':
|
||||
err := parseError(r)
|
||||
cn.readReadyForQuery()
|
||||
panic(err)
|
||||
default:
|
||||
cn.bad = true
|
||||
errorf("unexpected Describe statement response %q", t)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (cn *conn) readPortalDescribeResponse() (colNames []string, colFmts []format, colTyps []oid.Oid) {
|
||||
t, r := cn.recv1()
|
||||
switch t {
|
||||
case 'T':
|
||||
return parsePortalRowDescribe(r)
|
||||
case 'n':
|
||||
return nil, nil, nil
|
||||
case 'E':
|
||||
err := parseError(r)
|
||||
cn.readReadyForQuery()
|
||||
panic(err)
|
||||
default:
|
||||
cn.bad = true
|
||||
errorf("unexpected Describe response %q", t)
|
||||
}
|
||||
panic("not reached")
|
||||
}
|
||||
|
||||
func (cn *conn) readBindResponse() {
|
||||
t, r := cn.recv1()
|
||||
switch t {
|
||||
case '2':
|
||||
return
|
||||
case 'E':
|
||||
err := parseError(r)
|
||||
cn.readReadyForQuery()
|
||||
panic(err)
|
||||
default:
|
||||
cn.bad = true
|
||||
errorf("unexpected Bind response %q", t)
|
||||
}
|
||||
}
|
||||
|
||||
func (cn *conn) postExecuteWorkaround() {
|
||||
// Work around a bug in sql.DB.QueryRow: in Go 1.2 and earlier it ignores
|
||||
// any errors from rows.Next, which masks errors that happened during the
|
||||
// execution of the query. To avoid the problem in common cases, we wait
|
||||
// here for one more message from the database. If it's not an error the
|
||||
// query will likely succeed (or perhaps has already, if it's a
|
||||
// CommandComplete), so we push the message into the conn struct; recv1
|
||||
// will return it as the next message for rows.Next or rows.Close.
|
||||
// However, if it's an error, we wait until ReadyForQuery and then return
|
||||
// the error to our caller.
|
||||
for {
|
||||
t, r := cn.recv1()
|
||||
switch t {
|
||||
case 'E':
|
||||
err := parseError(r)
|
||||
cn.readReadyForQuery()
|
||||
panic(err)
|
||||
case 'C', 'D', 'I':
|
||||
// the query didn't fail, but we can't process this message
|
||||
cn.saveMessage(t, r)
|
||||
return
|
||||
default:
|
||||
cn.bad = true
|
||||
errorf("unexpected message during extended query execution: %q", t)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Only for Exec(), since we ignore the returned data
|
||||
func (cn *conn) readExecuteResponse(protocolState string) (res driver.Result, commandTag string, err error) {
|
||||
for {
|
||||
t, r := cn.recv1()
|
||||
switch t {
|
||||
case 'C':
|
||||
res, commandTag = cn.parseComplete(r.string())
|
||||
case 'Z':
|
||||
cn.processReadyForQuery(r)
|
||||
return res, commandTag, err
|
||||
case 'E':
|
||||
err = parseError(r)
|
||||
case 'T', 'D', 'I':
|
||||
// ignore any results
|
||||
default:
|
||||
cn.bad = true
|
||||
errorf("unknown %s response: %q", protocolState, t)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func parseStatementRowDescribe(r *readBuf) (colNames []string, colTyps []oid.Oid) {
|
||||
n := r.int16()
|
||||
cols = make([]string, n)
|
||||
rowTyps = make([]oid.Oid, n)
|
||||
for i := range cols {
|
||||
cols[i] = r.string()
|
||||
colNames = make([]string, n)
|
||||
colTyps = make([]oid.Oid, n)
|
||||
for i := range colNames {
|
||||
colNames[i] = r.string()
|
||||
r.next(6)
|
||||
rowTyps[i] = r.oid()
|
||||
r.next(8)
|
||||
colTyps[i] = r.oid()
|
||||
r.next(6)
|
||||
// format code not known when describing a statement; always 0
|
||||
r.next(2)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func parsePortalRowDescribe(r *readBuf) (colNames []string, colFmts []format, colTyps []oid.Oid) {
|
||||
n := r.int16()
|
||||
colNames = make([]string, n)
|
||||
colFmts = make([]format, n)
|
||||
colTyps = make([]oid.Oid, n)
|
||||
for i := range colNames {
|
||||
colNames[i] = r.string()
|
||||
r.next(6)
|
||||
colTyps[i] = r.oid()
|
||||
r.next(6)
|
||||
colFmts[i] = format(r.int16())
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user