@@ -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)
|
# 3.4.0 (May 3, 2019)
|
||||||
|
|
||||||
## Features
|
## 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
|
## Features
|
||||||
|
|
||||||
pgx supports many additional features beyond what is available through database/sql.
|
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
|
from pg_type t
|
||||||
left join pg_type base_type on t.typelem=base_type.oid
|
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_namespace nsp on t.typnamespace=nsp.oid
|
||||||
|
left join pg_class cls on t.typrelid=cls.oid
|
||||||
where (
|
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 (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 {
|
func QuoteBytes(buf []byte) string {
|
||||||
return `'\x` + hex.EncodeToString(buf) + "'"
|
return `'\x` + hex.EncodeToString(buf) + "'::bytea"
|
||||||
}
|
}
|
||||||
|
|
||||||
type sqlLexer struct {
|
type sqlLexer struct {
|
||||||
|
|||||||
@@ -108,7 +108,7 @@ func TestQuerySanitize(t *testing.T) {
|
|||||||
{
|
{
|
||||||
query: sanitize.Query{Parts: []sanitize.Part{"select ", 1}},
|
query: sanitize.Query{Parts: []sanitize.Part{"select ", 1}},
|
||||||
args: []interface{}{[]byte{0, 1, 2, 3, 255}},
|
args: []interface{}{[]byte{0, 1, 2, 3, 255}},
|
||||||
expected: `select '\x00010203ff'`,
|
expected: `select '\x00010203ff'::bytea`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
query: sanitize.Query{Parts: []sanitize.Part{"select ", 1}},
|
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))
|
fields := make([]zapcore.Field, len(data))
|
||||||
i := 0
|
i := 0
|
||||||
for k, v := range data {
|
for k, v := range data {
|
||||||
fields[i] = zap.Reflect(k, v)
|
fields[i] = zap.Any(k, v)
|
||||||
i++
|
i++
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+3
-1
@@ -211,9 +211,11 @@ func PgxInitSteps() []Step {
|
|||||||
from pg_type t
|
from pg_type t
|
||||||
left join pg_type base_type on t.typelem=base_type.oid
|
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_namespace nsp on t.typnamespace=nsp.oid
|
||||||
|
left join pg_class cls on t.typrelid=cls.oid
|
||||||
where (
|
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 (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{
|
ExpectMessage(&pgproto3.Describe{
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ type Frontend struct {
|
|||||||
copyInResponse CopyInResponse
|
copyInResponse CopyInResponse
|
||||||
copyOutResponse CopyOutResponse
|
copyOutResponse CopyOutResponse
|
||||||
copyDone CopyDone
|
copyDone CopyDone
|
||||||
copyFail CopyFail
|
copyFail CopyFail
|
||||||
dataRow DataRow
|
dataRow DataRow
|
||||||
emptyQueryResponse EmptyQueryResponse
|
emptyQueryResponse EmptyQueryResponse
|
||||||
errorResponse ErrorResponse
|
errorResponse ErrorResponse
|
||||||
@@ -36,6 +36,7 @@ type Frontend struct {
|
|||||||
parseComplete ParseComplete
|
parseComplete ParseComplete
|
||||||
readyForQuery ReadyForQuery
|
readyForQuery ReadyForQuery
|
||||||
rowDescription RowDescription
|
rowDescription RowDescription
|
||||||
|
portalSuspended PortalSuspended
|
||||||
|
|
||||||
bodyLen int
|
bodyLen int
|
||||||
msgType byte
|
msgType byte
|
||||||
@@ -76,8 +77,8 @@ func (b *Frontend) Receive() (BackendMessage, error) {
|
|||||||
msg = &b.notificationResponse
|
msg = &b.notificationResponse
|
||||||
case 'c':
|
case 'c':
|
||||||
msg = &b.copyDone
|
msg = &b.copyDone
|
||||||
case 'f':
|
case 'f':
|
||||||
msg = &b.copyFail
|
msg = &b.copyFail
|
||||||
case 'C':
|
case 'C':
|
||||||
msg = &b.commandComplete
|
msg = &b.commandComplete
|
||||||
case 'd':
|
case 'd':
|
||||||
@@ -112,6 +113,8 @@ func (b *Frontend) Receive() (BackendMessage, error) {
|
|||||||
msg = &b.copyBothResponse
|
msg = &b.copyBothResponse
|
||||||
case 'Z':
|
case 'Z':
|
||||||
msg = &b.readyForQuery
|
msg = &b.readyForQuery
|
||||||
|
case 's':
|
||||||
|
msg = &b.portalSuspended
|
||||||
default:
|
default:
|
||||||
return nil, errors.Errorf("unknown message type: %c", b.msgType)
|
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 {
|
if len(parts) > 1 {
|
||||||
exp = int32(-len(parts[1]))
|
exp = int32(-len(parts[1]))
|
||||||
} else {
|
} 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]
|
digits = digits[:len(digits)-1]
|
||||||
exp++
|
exp++
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package pgtype_test
|
package pgtype_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"math"
|
||||||
"math/big"
|
"math/big"
|
||||||
"reflect"
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
@@ -65,6 +66,13 @@ func TestNumericArraySet(t *testing.T) {
|
|||||||
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 1}},
|
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 1}},
|
||||||
Status: pgtype.Present},
|
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},
|
source: []float64{1},
|
||||||
result: pgtype.NumericArray{
|
result: pgtype.NumericArray{
|
||||||
@@ -72,6 +80,13 @@ func TestNumericArraySet(t *testing.T) {
|
|||||||
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 1}},
|
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 1}},
|
||||||
Status: pgtype.Present},
|
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)),
|
source: (([]float32)(nil)),
|
||||||
result: pgtype.NumericArray{Status: pgtype.Null},
|
result: pgtype.NumericArray{Status: pgtype.Null},
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package pgtype_test
|
package pgtype_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"math"
|
||||||
"math/big"
|
"math/big"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"reflect"
|
"reflect"
|
||||||
@@ -188,7 +189,9 @@ func TestNumericSet(t *testing.T) {
|
|||||||
result *pgtype.Numeric
|
result *pgtype.Numeric
|
||||||
}{
|
}{
|
||||||
{source: float32(1), result: &pgtype.Numeric{Int: big.NewInt(1), Status: pgtype.Present}},
|
{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(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: 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: 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}},
|
{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})
|
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) {
|
} 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 {
|
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) {
|
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" {
|
if c.RuntimeParams["standard_conforming_strings"] != "on" {
|
||||||
return errors.New("simple protocol queries must be run with 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