From 1186412d28857b9bfa14e7e3106ecd845e71fe71 Mon Sep 17 00:00:00 2001 From: Stefan Majewsky Date: Thu, 30 Jul 2026 20:20:42 +0200 Subject: import code from oblast/benchmark/internal Minimally edited to s/oblast/gsql/. --- README.md | 2 +- REUSE.toml | 1 + connector.go | 9 +++++- go.mod | 13 +++++++++ go.sum | 28 +++++++++++++++++++ handle.go | 89 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ results.go | 66 ++++++++++++++++++++++++++++++++++++++++++++ statement.go | 60 ++++++++++++++++++++++++++++++++++++++++ 8 files changed, 266 insertions(+), 2 deletions(-) create mode 100644 go.sum create mode 100644 handle.go create mode 100644 results.go create mode 100644 statement.go diff --git a/README.md b/README.md index c24135a..3bccaad 100644 --- a/README.md +++ b/README.md @@ -5,4 +5,4 @@ SPDX-License-Identifier: Apache-2.0 # gg-pgx -Integration for connecting [gg/gsql](https://pkg.go.dev/go.xyrillian.de/gg/gsql) (and libraries based on it, such as [Oblast](https://pkg.go.dev/go.xyrillian.de/oblast)) to the PostgreSQL driver library [pgx](https://github.com/jackc/pgx). +Integration for using [gg/gsql](https://pkg.go.dev/go.xyrillian.de/gg/gsql) (and libraries based on it, such as [Oblast](https://pkg.go.dev/go.xyrillian.de/oblast)) with the PostgreSQL driver library [pgx](https://github.com/jackc/pgx). diff --git a/REUSE.toml b/REUSE.toml index 7f498ec..2d17d24 100644 --- a/REUSE.toml +++ b/REUSE.toml @@ -9,6 +9,7 @@ path = [ ".gitignore", "description", "go.mod", + "go.sum", ] SPDX-FileCopyrightText = "Stefan Majewsky " SPDX-License-Identifier = "Apache-2.0" diff --git a/connector.go b/connector.go index cb497da..d7f1242 100644 --- a/connector.go +++ b/connector.go @@ -1,6 +1,13 @@ // SPDX-FileCopyrightText: 2026 Stefan Majewsky // SPDX-License-Identifier: Apache-2.0 +// Package gg_pgx contains integration for using [gg/gsql] (and libraries based on it, such as [Oblast]) with the PostgreSQL driver library [pgx]. +// +// [gg/gsql]: https://pkg.go.dev/go.xyrillian.de/gg/gsql +// [Oblast]: https://pkg.go.dev/go.xyrillian.de/oblast +// [pgx]: https://github.com/jackc/pgx package gg_pgx -// TODO: implement +// TODO: implement Handle (handle.go is copied from an early prototype in the Oblast benchmark suite and uses the extremely old Wrap() style) +// TODO: implement ConnectionHandle +// TODO: test coverage (via gg/pgruntime) diff --git a/go.mod b/go.mod index 8e24db2..ffd7e8a 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,16 @@ module go.xyrillian.de/gg-pgx go 1.26 + +require ( + github.com/jackc/pgx/v5 v5.10.0 + go.xyrillian.de/gg v1.11.2-0.20260730175754-81e32cec3629 +) + +require ( + github.com/jackc/pgpassfile v1.0.0 // indirect + github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect + github.com/jackc/puddle/v2 v2.2.2 // indirect + golang.org/x/sync v0.17.0 // indirect + golang.org/x/text v0.29.0 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..87edcf1 --- /dev/null +++ b/go.sum @@ -0,0 +1,28 @@ +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= +github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= +github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= +github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= +github.com/jackc/pgx/v5 v5.10.0 h1:VhSvgU2jSli8o3AqIEOTJr7rZwAEUVo4E4XhR94Zfr0= +github.com/jackc/pgx/v5 v5.10.0/go.mod h1:mal1tBGAFfLHvZzaYh77YS/eC6IX9OWbRV1QIIM0Jn4= +github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= +github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +go.xyrillian.de/gg v1.11.2-0.20260730175754-81e32cec3629 h1:3IOrqdh3N/X4FLts7prxJ4Sy1IGZsOHl7NOym4hTMdk= +go.xyrillian.de/gg v1.11.2-0.20260730175754-81e32cec3629/go.mod h1:DoO4fQSWIrBRlNlCjVyrYM0kAEBt/Jg2GkMH+cGRZ0k= +golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug= +golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk= +golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/handle.go b/handle.go new file mode 100644 index 0000000..7f6514d --- /dev/null +++ b/handle.go @@ -0,0 +1,89 @@ +// SPDX-FileCopyrightText: 2026 Stefan Majewsky +// SPDX-License-Identifier: Apache-2.0 + +package gg_pgx + +import ( + "context" + "fmt" + "strconv" + "sync/atomic" + + "github.com/jackc/pgx/v5" + "github.com/jackc/pgx/v5/pgconn" + "github.com/jackc/pgx/v5/pgxpool" + "go.xyrillian.de/gg/gsql" +) + +type Handle interface { + Exec(ctx context.Context, sql string, args ...any) (pgconn.CommandTag, error) + Query(ctx context.Context, sql string, args ...any) (pgx.Rows, error) + QueryRow(ctx context.Context, sql string, args ...any) pgx.Row +} + +var ( + _ Handle = &pgx.Conn{} + _ Handle = &pgxpool.Conn{} + _ Handle = pgx.Tx(&pgxpool.Tx{}) +) + +func Wrap(h Handle) gsql.Handle { + switch h := h.(type) { + case *pgx.Conn: + return wrappedHandle{h} + case *pgxpool.Conn: + return wrappedHandle{h} + case pgx.Tx: + return wrappedHandle{h} + default: + panic(fmt.Sprintf("unexpected type: %#v", h)) + } +} + +var preparedStatementId atomic.Uint64 + +type wrappedHandle struct { + inner Handle +} + +// GSQLPrepare implements the [gsql.Handle] interface. +func (h wrappedHandle) GSQLPrepare(ctx context.Context, query string, repeated bool) (gsql.Statement, error) { + if !repeated { + return wrappedUnpreparedStatement{query, h.inner}, nil + } + + name := "oblast_pgx_" + strconv.FormatUint(preparedStatementId.Add(1), 10) + switch inner := h.inner.(type) { + case *pgx.Conn: + stmt, err := inner.Prepare(ctx, name, query) + return wrappedPreparedStatement{ctx, stmt, h.inner}, err + case *pgxpool.Conn: + // pgxpool.Conn does not have Prepare() + return wrappedUnpreparedStatement{query, h.inner}, nil + case pgx.Tx: + stmt, err := inner.Conn().Prepare(ctx, name, query) + return wrappedPreparedStatement{ctx, stmt, h.inner}, err + default: + panic("unreachable") // because of the check in func Wrap() + } +} + +// Releases a prepared statement. +func deallocate(ctx context.Context, h Handle, stmt *pgconn.StatementDescription) error { + switch h := h.(type) { + case *pgx.Conn: + return h.Deallocate(ctx, stmt.Name) + case *pgxpool.Conn: + panic("unreachable") // because func GSQLPrepare() does not return a wrappedPreparedStatement for this underlying type + case pgx.Tx: + return h.Conn().Deallocate(ctx, stmt.Name) + default: + panic("unreachable") // because of the check in func Wrap() + } +} + +// GSQLQuery implements the [gsql.Handle] interface. +func (h wrappedHandle) GSQLQuery(ctx context.Context, query string, args []any) (gsql.Rows, error) { + rows, err := h.inner.Query(ctx, query, args...) + return wrappedRows{rows}, err +} diff --git a/results.go b/results.go new file mode 100644 index 0000000..510bb08 --- /dev/null +++ b/results.go @@ -0,0 +1,66 @@ +// SPDX-FileCopyrightText: 2026 Stefan Majewsky +// SPDX-License-Identifier: Apache-2.0 + +package gg_pgx + +import ( + "database/sql" + "errors" + + "github.com/jackc/pgx/v5" + "github.com/jackc/pgx/v5/pgconn" + "go.xyrillian.de/gg/gsql" +) + +type wrappedRows struct { + inner pgx.Rows +} + +var _ gsql.Rows = wrappedRows{} + +// Columns implements the [gsql.Rows] interface. +func (r wrappedRows) Columns() ([]string, error) { + descriptions := r.inner.FieldDescriptions() + result := make([]string, len(descriptions)) + for idx, desc := range descriptions { + result[idx] = desc.Name + } + return result, nil +} + +// Close implements the [gsql.Rows] interface. +func (r wrappedRows) Close() error { + r.inner.Close() + return nil +} + +// Err implements the [gsql.Rows] interface. +func (r wrappedRows) Err() error { + return r.inner.Err() +} + +// Next implements the [gsql.Rows] interface. +func (r wrappedRows) Next() bool { + return r.inner.Next() +} + +// Scan implements the [gsql.Rows] interface. +func (r wrappedRows) Scan(args ...any) error { + return r.inner.Scan(args...) +} + +type wrappedResult struct { + inner pgconn.CommandTag +} + +var _ sql.Result = wrappedResult{} + +// LastInsertId implements the [sql.Result] interface. +func (r wrappedResult) LastInsertId() (int64, error) { + return 0, errors.New("PostgreSQL does not support LastInsertId()") +} + +// LastInsertId implements the [sql.Result] interface. +func (r wrappedResult) RowsAffected() (int64, error) { + return r.inner.RowsAffected(), nil +} diff --git a/statement.go b/statement.go new file mode 100644 index 0000000..bb95252 --- /dev/null +++ b/statement.go @@ -0,0 +1,60 @@ +// SPDX-FileCopyrightText: 2026 Stefan Majewsky +// SPDX-License-Identifier: Apache-2.0 + +package gg_pgx + +import ( + "context" + "database/sql" + + "github.com/jackc/pgx/v5/pgconn" + "go.xyrillian.de/gg/gsql" +) + +type wrappedPreparedStatement struct { + ctx context.Context + statement *pgconn.StatementDescription + handle Handle +} + +type wrappedUnpreparedStatement struct { + query string + handle Handle +} + +var ( + _ gsql.Statement = wrappedPreparedStatement{} + _ gsql.Statement = wrappedUnpreparedStatement{} +) + +// Close implements the [handle.Statement] interface. +func (s wrappedPreparedStatement) Close() error { + return deallocate(s.ctx, s.handle, s.statement) +} + +// Close implements the [handle.Statement] interface. +func (s wrappedUnpreparedStatement) Close() error { + return nil +} + +// Exec implements the [handle.Statement] interface. +func (s wrappedPreparedStatement) Exec(ctx context.Context, args []any) (sql.Result, error) { + result, err := s.handle.Exec(ctx, s.statement.Name, args...) + return wrappedResult{result}, err +} + +// Exec implements the [handle.Statement] interface. +func (s wrappedUnpreparedStatement) Exec(ctx context.Context, args []any) (sql.Result, error) { + result, err := s.handle.Exec(ctx, s.query, args...) + return wrappedResult{result}, err +} + +// QueryRow implements the [handle.Statement] interface. +func (s wrappedPreparedStatement) QueryRow(ctx context.Context, args, slots []any) error { + return s.handle.QueryRow(ctx, s.statement.Name, args...).Scan(slots...) +} + +// QueryRow implements the [handle.Statement] interface. +func (s wrappedUnpreparedStatement) QueryRow(ctx context.Context, args, slots []any) error { + return s.handle.QueryRow(ctx, s.query, args...).Scan(slots...) +} -- cgit v1.3.1