Skip to content

Commit

Permalink
Merge branch 'jackc:master' into flat-array-scan
Browse files Browse the repository at this point in the history
  • Loading branch information
regeda authored Nov 13, 2023
2 parents 127b3e1 + df5d00e commit da4ddcf
Show file tree
Hide file tree
Showing 9 changed files with 228 additions and 19 deletions.
12 changes: 1 addition & 11 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,18 +17,8 @@ jobs:
strategy:
matrix:
go-version: ["1.20", "1.21"]
pg-version: [11, 12, 13, 14, 15, 16, cockroachdb]
pg-version: [12, 13, 14, 15, 16, cockroachdb]
include:
- pg-version: 11
pgx-test-database: "host=127.0.0.1 user=pgx_md5 password=secret dbname=pgx_test"
pgx-test-unix-socket-conn-string: "host=/var/run/postgresql dbname=pgx_test"
pgx-test-tcp-conn-string: "host=127.0.0.1 user=pgx_md5 password=secret dbname=pgx_test"
pgx-test-scram-password-conn-string: "host=127.0.0.1 user=pgx_scram password=secret dbname=pgx_test"
pgx-test-md5-password-conn-string: "host=127.0.0.1 user=pgx_md5 password=secret dbname=pgx_test"
pgx-test-plain-password-conn-string: "host=127.0.0.1 user=pgx_pw password=secret dbname=pgx_test"
pgx-test-tls-conn-string: "host=localhost user=pgx_ssl password=secret sslmode=verify-full sslrootcert=/tmp/ca.pem dbname=pgx_test"
pgx-ssl-password: certpw
pgx-test-tls-client-conn-string: "host=localhost user=pgx_sslcert sslmode=verify-full sslrootcert=/tmp/ca.pem sslcert=/tmp/pgx_sslcert.crt sslkey=/tmp/pgx_sslcert.key dbname=pgx_test"
- pg-version: 12
pgx-test-database: "host=127.0.0.1 user=pgx_md5 password=secret dbname=pgx_test"
pgx-test-unix-socket-conn-string: "host=/var/run/postgresql dbname=pgx_test"
Expand Down
14 changes: 14 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,17 @@
# 5.5.0 (November 4, 2023)

* Add CollectExactlyOneRow. (Julien GOTTELAND)
* Add OpenDBFromPool to create *database/sql.DB from *pgxpool.Pool. (Lev Zakharov)
* Prepare can automatically choose statement name based on sql. This makes it easier to explicitly manage prepared statements.
* Statement cache now uses deterministic, stable statement names.
* database/sql prepared statement names are deterministically generated.
* Fix: SendBatch wasn't respecting context cancellation.
* Fix: Timeout error from pipeline is now normalized.
* Fix: database/sql encoding json.RawMessage to []byte.
* CancelRequest: Wait for the cancel request to be acknowledged by the server. This should improve PgBouncer compatibility. (Anton Levakin)
* stdlib: Use Ping instead of CheckConn in ResetSession
* Add json.Marshaler and json.Unmarshaler for Float4, Float8 (Kirill Mironov)

# 5.4.3 (August 5, 2023)

* Fix: QCharArrayOID was defined with the wrong OID (Christoph Engelbert)
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ See the presentation at Golang Estonia, [PGX Top to Bottom](https://www.youtube.

## Supported Go and PostgreSQL Versions

pgx supports the same versions of Go and PostgreSQL that are supported by their respective teams. For [Go](https://golang.org/doc/devel/release.html#policy) that is the two most recent major releases and for [PostgreSQL](https://www.postgresql.org/support/versioning/) the major releases in the last 5 years. This means pgx supports Go 1.20 and higher and PostgreSQL 11 and higher. pgx also is tested against the latest version of [CockroachDB](https://www.cockroachlabs.com/product/).
pgx supports the same versions of Go and PostgreSQL that are supported by their respective teams. For [Go](https://golang.org/doc/devel/release.html#policy) that is the two most recent major releases and for [PostgreSQL](https://www.postgresql.org/support/versioning/) the major releases in the last 5 years. This means pgx supports Go 1.20 and higher and PostgreSQL 12 and higher. pgx also is tested against the latest version of [CockroachDB](https://www.cockroachlabs.com/product/).

## Version Policy

Expand Down
27 changes: 27 additions & 0 deletions copy_from.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,33 @@ func (cts *copyFromSlice) Err() error {
return cts.err
}

// CopyFromFunc returns a CopyFromSource interface that relies on nxtf for values.
// nxtf returns rows until it either signals an 'end of data' by returning row=nil and err=nil,
// or it returns an error. If nxtf returns an error, the copy is aborted.
func CopyFromFunc(nxtf func() (row []any, err error)) CopyFromSource {
return &copyFromFunc{next: nxtf}
}

type copyFromFunc struct {
next func() ([]any, error)
valueRow []any
err error
}

func (g *copyFromFunc) Next() bool {
g.valueRow, g.err = g.next()
// only return true if valueRow exists and no error
return g.valueRow != nil && g.err == nil
}

func (g *copyFromFunc) Values() ([]any, error) {
return g.valueRow, g.err
}

func (g *copyFromFunc) Err() error {
return g.err
}

// CopyFromSource is the interface used by *Conn.CopyFrom as the source for copy data.
type CopyFromSource interface {
// Next returns true if there is another row and makes the next row data
Expand Down
56 changes: 56 additions & 0 deletions copy_from_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -802,3 +802,59 @@ func TestConnCopyFromAutomaticStringConversion(t *testing.T) {

ensureConnValid(t, conn)
}

func TestCopyFromFunc(t *testing.T) {
t.Parallel()

conn := mustConnectString(t, os.Getenv("PGX_TEST_DATABASE"))
defer closeConn(t, conn)

mustExec(t, conn, `create temporary table foo(
a int
)`)

dataCh := make(chan int, 1)

const channelItems = 10
go func() {
for i := 0; i < channelItems; i++ {
dataCh <- i
}
close(dataCh)
}()

copyCount, err := conn.CopyFrom(context.Background(), pgx.Identifier{"foo"}, []string{"a"},
pgx.CopyFromFunc(func() ([]any, error) {
v, ok := <-dataCh
if !ok {
return nil, nil
}
return []any{v}, nil
}))

require.ErrorIs(t, err, nil)
require.EqualValues(t, channelItems, copyCount)

rows, err := conn.Query(context.Background(), "select * from foo order by a")
require.NoError(t, err)
nums, err := pgx.CollectRows(rows, pgx.RowTo[int64])
require.NoError(t, err)
require.Equal(t, []int64{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}, nums)

// simulate a failure
copyCount, err = conn.CopyFrom(context.Background(), pgx.Identifier{"foo"}, []string{"a"},
pgx.CopyFromFunc(func() func() ([]any, error) {
x := 9
return func() ([]any, error) {
x++
if x > 100 {
return nil, fmt.Errorf("simulated error")
}
return []any{x}, nil
}
}()))
require.NotErrorIs(t, err, nil)
require.EqualValues(t, 0, copyCount) // no change, due to error

ensureConnValid(t, conn)
}
24 changes: 24 additions & 0 deletions pgtype/float4.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package pgtype
import (
"database/sql/driver"
"encoding/binary"
"encoding/json"
"fmt"
"math"
"strconv"
Expand Down Expand Up @@ -65,6 +66,29 @@ func (f Float4) Value() (driver.Value, error) {
return float64(f.Float32), nil
}

func (f Float4) MarshalJSON() ([]byte, error) {
if !f.Valid {
return []byte("null"), nil
}
return json.Marshal(f.Float32)
}

func (f *Float4) UnmarshalJSON(b []byte) error {
var n *float32
err := json.Unmarshal(b, &n)
if err != nil {
return err
}

if n == nil {
*f = Float4{}
} else {
*f = Float4{Float32: *n, Valid: true}
}

return nil
}

type Float4Codec struct{}

func (Float4Codec) FormatSupported(format int16) bool {
Expand Down
41 changes: 41 additions & 0 deletions pgtype/float4_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,44 @@ func TestFloat4Codec(t *testing.T) {
{nil, new(*float32), isExpectedEq((*float32)(nil))},
})
}

func TestFloat4MarshalJSON(t *testing.T) {
successfulTests := []struct {
source pgtype.Float4
result string
}{
{source: pgtype.Float4{Float32: 0}, result: "null"},
{source: pgtype.Float4{Float32: 1.23, Valid: true}, result: "1.23"},
}
for i, tt := range successfulTests {
r, err := tt.source.MarshalJSON()
if err != nil {
t.Errorf("%d: %v", i, err)
}

if string(r) != tt.result {
t.Errorf("%d: expected %v to convert to %v, but it was %v", i, tt.source, tt.result, string(r))
}
}
}

func TestFloat4UnmarshalJSON(t *testing.T) {
successfulTests := []struct {
source string
result pgtype.Float4
}{
{source: "null", result: pgtype.Float4{Float32: 0}},
{source: "1.23", result: pgtype.Float4{Float32: 1.23, Valid: true}},
}
for i, tt := range successfulTests {
var r pgtype.Float4
err := r.UnmarshalJSON([]byte(tt.source))
if err != nil {
t.Errorf("%d: %v", i, err)
}

if r != tt.result {
t.Errorf("%d: expected %v to convert to %v, but it was %v", i, tt.source, tt.result, r)
}
}
}
30 changes: 23 additions & 7 deletions pgtype/float8.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,29 @@ func (f Float8) Value() (driver.Value, error) {
return f.Float64, nil
}

func (f Float8) MarshalJSON() ([]byte, error) {
if !f.Valid {
return []byte("null"), nil
}
return json.Marshal(f.Float64)
}

func (f *Float8) UnmarshalJSON(b []byte) error {
var n *float64
err := json.Unmarshal(b, &n)
if err != nil {
return err
}

if n == nil {
*f = Float8{}
} else {
*f = Float8{Float64: *n, Valid: true}
}

return nil
}

type Float8Codec struct{}

func (Float8Codec) FormatSupported(format int16) bool {
Expand Down Expand Up @@ -109,13 +132,6 @@ func (Float8Codec) PlanEncode(m *Map, oid uint32, format int16, value any) Encod
return nil
}

func (f *Float8) MarshalJSON() ([]byte, error) {
if !f.Valid {
return []byte("null"), nil
}
return json.Marshal(f.Float64)
}

type encodePlanFloat8CodecBinaryFloat64 struct{}

func (encodePlanFloat8CodecBinaryFloat64) Encode(value any, buf []byte) (newBuf []byte, err error) {
Expand Down
41 changes: 41 additions & 0 deletions pgtype/float8_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,44 @@ func TestFloat8Codec(t *testing.T) {
{nil, new(*float64), isExpectedEq((*float64)(nil))},
})
}

func TestFloat8MarshalJSON(t *testing.T) {
successfulTests := []struct {
source pgtype.Float8
result string
}{
{source: pgtype.Float8{Float64: 0}, result: "null"},
{source: pgtype.Float8{Float64: 1.23, Valid: true}, result: "1.23"},
}
for i, tt := range successfulTests {
r, err := tt.source.MarshalJSON()
if err != nil {
t.Errorf("%d: %v", i, err)
}

if string(r) != tt.result {
t.Errorf("%d: expected %v to convert to %v, but it was %v", i, tt.source, tt.result, string(r))
}
}
}

func TestFloat8UnmarshalJSON(t *testing.T) {
successfulTests := []struct {
source string
result pgtype.Float8
}{
{source: "null", result: pgtype.Float8{Float64: 0}},
{source: "1.23", result: pgtype.Float8{Float64: 1.23, Valid: true}},
}
for i, tt := range successfulTests {
var r pgtype.Float8
err := r.UnmarshalJSON([]byte(tt.source))
if err != nil {
t.Errorf("%d: %v", i, err)
}

if r != tt.result {
t.Errorf("%d: expected %v to convert to %v, but it was %v", i, tt.source, tt.result, r)
}
}
}

0 comments on commit da4ddcf

Please sign in to comment.