@@ -1,3 +1,21 @@
|
||||
# 3.5.0 (June 29, 2019)
|
||||
|
||||
## Features
|
||||
|
||||
* Protocol support for PortalSuspended message (avivklas)
|
||||
* Read OIDs for composite types on connection init (Nick Jones)
|
||||
|
||||
## Fixes
|
||||
|
||||
* Hstore can have empty keys (Josh Leverette)
|
||||
* Fix -0 value for numeric type (David Hudson)
|
||||
* Log error message on rows-close error (Euan Kemp)
|
||||
|
||||
## Changes
|
||||
|
||||
* Explicitly cast binary string to bytea in simple protocol (jinhua luo)
|
||||
* Skip parse and sanitize simple query when no arguments (jinhua luo)
|
||||
|
||||
# 3.4.0 (May 3, 2019)
|
||||
|
||||
## Features
|
||||
|
||||
@@ -15,6 +15,10 @@ if err != nil {
|
||||
}
|
||||
```
|
||||
|
||||
## v4 Coming Soon
|
||||
|
||||
This is the current stable v3 version. v4 is currently is in prelease status. Consider using [v4](https://github.com/jackc/pgx/tree/v4) for new development or test upgrading existing applications.
|
||||
|
||||
## Features
|
||||
|
||||
pgx supports many additional features beyond what is available through database/sql.
|
||||
|
||||
@@ -404,9 +404,11 @@ func initPostgresql(c *Conn) (*pgtype.ConnInfo, error) {
|
||||
from pg_type t
|
||||
left join pg_type base_type on t.typelem=base_type.oid
|
||||
left join pg_namespace nsp on t.typnamespace=nsp.oid
|
||||
left join pg_class cls on t.typrelid=cls.oid
|
||||
where (
|
||||
t.typtype in('b', 'p', 'r', 'e')
|
||||
t.typtype in('b', 'p', 'r', 'e', 'c')
|
||||
and (base_type.oid is null or base_type.typtype in('b', 'p', 'r'))
|
||||
and (cls.oid is null or cls.relkind='c')
|
||||
)`
|
||||
)
|
||||
|
||||
|
||||
@@ -87,7 +87,7 @@ func QuoteString(str string) string {
|
||||
}
|
||||
|
||||
func QuoteBytes(buf []byte) string {
|
||||
return `'\x` + hex.EncodeToString(buf) + "'"
|
||||
return `'\x` + hex.EncodeToString(buf) + "'::bytea"
|
||||
}
|
||||
|
||||
type sqlLexer struct {
|
||||
|
||||
@@ -108,7 +108,7 @@ func TestQuerySanitize(t *testing.T) {
|
||||
{
|
||||
query: sanitize.Query{Parts: []sanitize.Part{"select ", 1}},
|
||||
args: []interface{}{[]byte{0, 1, 2, 3, 255}},
|
||||
expected: `select '\x00010203ff'`,
|
||||
expected: `select '\x00010203ff'::bytea`,
|
||||
},
|
||||
{
|
||||
query: sanitize.Query{Parts: []sanitize.Part{"select ", 1}},
|
||||
|
||||
@@ -19,7 +19,7 @@ func (pl *Logger) Log(level pgx.LogLevel, msg string, data map[string]interface{
|
||||
fields := make([]zapcore.Field, len(data))
|
||||
i := 0
|
||||
for k, v := range data {
|
||||
fields[i] = zap.Reflect(k, v)
|
||||
fields[i] = zap.Any(k, v)
|
||||
i++
|
||||
}
|
||||
|
||||
|
||||
+3
-1
@@ -211,9 +211,11 @@ func PgxInitSteps() []Step {
|
||||
from pg_type t
|
||||
left join pg_type base_type on t.typelem=base_type.oid
|
||||
left join pg_namespace nsp on t.typnamespace=nsp.oid
|
||||
left join pg_class cls on t.typrelid=cls.oid
|
||||
where (
|
||||
t.typtype in('b', 'p', 'r', 'e')
|
||||
t.typtype in('b', 'p', 'r', 'e', 'c')
|
||||
and (base_type.oid is null or base_type.typtype in('b', 'p', 'r'))
|
||||
and (cls.oid is null or cls.relkind='c')
|
||||
)`,
|
||||
}),
|
||||
ExpectMessage(&pgproto3.Describe{
|
||||
|
||||
@@ -23,7 +23,7 @@ type Frontend struct {
|
||||
copyInResponse CopyInResponse
|
||||
copyOutResponse CopyOutResponse
|
||||
copyDone CopyDone
|
||||
copyFail CopyFail
|
||||
copyFail CopyFail
|
||||
dataRow DataRow
|
||||
emptyQueryResponse EmptyQueryResponse
|
||||
errorResponse ErrorResponse
|
||||
@@ -36,6 +36,7 @@ type Frontend struct {
|
||||
parseComplete ParseComplete
|
||||
readyForQuery ReadyForQuery
|
||||
rowDescription RowDescription
|
||||
portalSuspended PortalSuspended
|
||||
|
||||
bodyLen int
|
||||
msgType byte
|
||||
@@ -76,8 +77,8 @@ func (b *Frontend) Receive() (BackendMessage, error) {
|
||||
msg = &b.notificationResponse
|
||||
case 'c':
|
||||
msg = &b.copyDone
|
||||
case 'f':
|
||||
msg = &b.copyFail
|
||||
case 'f':
|
||||
msg = &b.copyFail
|
||||
case 'C':
|
||||
msg = &b.commandComplete
|
||||
case 'd':
|
||||
@@ -112,6 +113,8 @@ func (b *Frontend) Receive() (BackendMessage, error) {
|
||||
msg = &b.copyBothResponse
|
||||
case 'Z':
|
||||
msg = &b.readyForQuery
|
||||
case 's':
|
||||
msg = &b.portalSuspended
|
||||
default:
|
||||
return nil, errors.Errorf("unknown message type: %c", b.msgType)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
package pgproto3
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
)
|
||||
|
||||
type PortalSuspended struct{}
|
||||
|
||||
func (*PortalSuspended) Backend() {}
|
||||
|
||||
func (dst *PortalSuspended) Decode(src []byte) error {
|
||||
if len(src) != 0 {
|
||||
return &invalidMessageLenErr{messageType: "PortalSuspended", expectedLen: 0, actualLen: len(src)}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (src *PortalSuspended) Encode(dst []byte) []byte {
|
||||
return append(dst, 's', 0, 0, 0, 4)
|
||||
}
|
||||
|
||||
func (src *PortalSuspended) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(struct {
|
||||
Type string
|
||||
}{
|
||||
Type: "PortalSuspended",
|
||||
})
|
||||
}
|
||||
+1
-1
@@ -321,7 +321,7 @@ func parseNumericString(str string) (n *big.Int, exp int32, err error) {
|
||||
if len(parts) > 1 {
|
||||
exp = int32(-len(parts[1]))
|
||||
} else {
|
||||
for len(digits) > 1 && digits[len(digits)-1] == '0' {
|
||||
for len(digits) > 1 && digits[len(digits)-1] == '0' && digits[len(digits)-2] != '-' {
|
||||
digits = digits[:len(digits)-1]
|
||||
exp++
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package pgtype_test
|
||||
|
||||
import (
|
||||
"math"
|
||||
"math/big"
|
||||
"reflect"
|
||||
"testing"
|
||||
@@ -65,6 +66,13 @@ func TestNumericArraySet(t *testing.T) {
|
||||
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 1}},
|
||||
Status: pgtype.Present},
|
||||
},
|
||||
{
|
||||
source: []float32{float32(math.Copysign(0, -1))},
|
||||
result: pgtype.NumericArray{
|
||||
Elements: []pgtype.Numeric{{Int: big.NewInt(0), Status: pgtype.Present}},
|
||||
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 1}},
|
||||
Status: pgtype.Present},
|
||||
},
|
||||
{
|
||||
source: []float64{1},
|
||||
result: pgtype.NumericArray{
|
||||
@@ -72,6 +80,13 @@ func TestNumericArraySet(t *testing.T) {
|
||||
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 1}},
|
||||
Status: pgtype.Present},
|
||||
},
|
||||
{
|
||||
source: []float64{math.Copysign(0, -1)},
|
||||
result: pgtype.NumericArray{
|
||||
Elements: []pgtype.Numeric{{Int: big.NewInt(0), Status: pgtype.Present}},
|
||||
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 1}},
|
||||
Status: pgtype.Present},
|
||||
},
|
||||
{
|
||||
source: (([]float32)(nil)),
|
||||
result: pgtype.NumericArray{Status: pgtype.Null},
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package pgtype_test
|
||||
|
||||
import (
|
||||
"math"
|
||||
"math/big"
|
||||
"math/rand"
|
||||
"reflect"
|
||||
@@ -188,7 +189,9 @@ func TestNumericSet(t *testing.T) {
|
||||
result *pgtype.Numeric
|
||||
}{
|
||||
{source: float32(1), result: &pgtype.Numeric{Int: big.NewInt(1), Status: pgtype.Present}},
|
||||
{source: float32(math.Copysign(0, -1)), result: &pgtype.Numeric{Int: big.NewInt(0), Status: pgtype.Present}},
|
||||
{source: float64(1), result: &pgtype.Numeric{Int: big.NewInt(1), Status: pgtype.Present}},
|
||||
{source: float64(math.Copysign(0, -1)), result: &pgtype.Numeric{Int: big.NewInt(0), Status: pgtype.Present}},
|
||||
{source: int8(1), result: &pgtype.Numeric{Int: big.NewInt(1), Status: pgtype.Present}},
|
||||
{source: int16(1), result: &pgtype.Numeric{Int: big.NewInt(1), Status: pgtype.Present}},
|
||||
{source: int32(1), result: &pgtype.Numeric{Int: big.NewInt(1), Status: pgtype.Present}},
|
||||
|
||||
@@ -84,7 +84,7 @@ func (rows *Rows) Close() {
|
||||
rows.conn.log(LogLevelInfo, "Query", map[string]interface{}{"sql": rows.sql, "args": logQueryArgs(rows.args), "time": endTime.Sub(rows.startTime), "rowCount": rows.rowCount})
|
||||
}
|
||||
} else if rows.conn.shouldLog(LogLevelError) {
|
||||
rows.conn.log(LogLevelError, "Query", map[string]interface{}{"sql": rows.sql, "args": logQueryArgs(rows.args)})
|
||||
rows.conn.log(LogLevelError, "Query", map[string]interface{}{"sql": rows.sql, "args": logQueryArgs(rows.args), "err": rows.err})
|
||||
}
|
||||
|
||||
if rows.batch != nil && rows.err != nil {
|
||||
@@ -522,6 +522,10 @@ func (c *Conn) readUntilRowDescription() ([]FieldDescription, error) {
|
||||
}
|
||||
|
||||
func (c *Conn) sanitizeAndSendSimpleQuery(sql string, args ...interface{}) (err error) {
|
||||
if len(args) == 0 {
|
||||
return c.sendSimpleQuery(sql)
|
||||
}
|
||||
|
||||
if c.RuntimeParams["standard_conforming_strings"] != "on" {
|
||||
return errors.New("simple protocol queries must be run with standard_conforming_strings=on")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user