From a86a346ecceb7ad409f116474c1593b201012cf2 Mon Sep 17 00:00:00 2001 From: Stefan Majewsky Date: Tue, 12 May 2026 23:32:28 +0200 Subject: add PostgreSQL benchmark, comparing lib/pq against pgx both with and w/o Oblast --- benchmark/internal/oblast_pgx/handle.go | 81 ++++++++++++++++++++++++++++++ benchmark/internal/oblast_pgx/results.go | 66 ++++++++++++++++++++++++ benchmark/internal/oblast_pgx/statement.go | 60 ++++++++++++++++++++++ 3 files changed, 207 insertions(+) create mode 100644 benchmark/internal/oblast_pgx/handle.go create mode 100644 benchmark/internal/oblast_pgx/results.go create mode 100644 benchmark/internal/oblast_pgx/statement.go (limited to 'benchmark/internal') diff --git a/benchmark/internal/oblast_pgx/handle.go b/benchmark/internal/oblast_pgx/handle.go new file mode 100644 index 0000000..6a88e2b --- /dev/null +++ b/benchmark/internal/oblast_pgx/handle.go @@ -0,0 +1,81 @@ +// SPDX-FileCopyrightText: 2026 Stefan Majewsky +// SPDX-License-Identifier: Apache-2.0 + +package oblast_pgx + +import ( + "context" + "fmt" + "strconv" + "sync/atomic" + + "github.com/jackc/pgx/v5" + "github.com/jackc/pgx/v5/pgconn" + "go.xyrillian.de/oblast/handle" +) + +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 = pgx.Tx(nil) +) + +// TODO: offer wrapping for pgxpool.Pool and pgxpool.Conn? +func Wrap(h Handle) handle.Handle { + switch h := h.(type) { + case *pgx.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 +} + +// Prepare implements the [handle.Handle] interface. +func (h wrappedHandle) Prepare(ctx context.Context, query string, repeated bool) (handle.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 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 pgx.Tx: + return h.Conn().Deallocate(ctx, stmt.Name) + default: + panic("unreachable") // because of the check in func Wrap() + } +} + +// Query implements the [handle.Handle] interface. +func (h wrappedHandle) Query(ctx context.Context, query string, args []any) (handle.Rows, error) { + rows, err := h.inner.Query(ctx, query, args...) + return wrappedRows{rows}, err +} diff --git a/benchmark/internal/oblast_pgx/results.go b/benchmark/internal/oblast_pgx/results.go new file mode 100644 index 0000000..3ccb5ce --- /dev/null +++ b/benchmark/internal/oblast_pgx/results.go @@ -0,0 +1,66 @@ +// SPDX-FileCopyrightText: 2026 Stefan Majewsky +// SPDX-License-Identifier: Apache-2.0 + +package oblast_pgx + +import ( + "database/sql" + "errors" + + "github.com/jackc/pgx/v5" + "github.com/jackc/pgx/v5/pgconn" + "go.xyrillian.de/oblast/handle" +) + +type wrappedRows struct { + inner pgx.Rows +} + +var _ handle.Rows = wrappedRows{} + +// Columns implements the [handle.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 [handle.Rows] interface. +func (r wrappedRows) Close() error { + r.inner.Close() + return nil +} + +// Err implements the [handle.Rows] interface. +func (r wrappedRows) Err() error { + return r.inner.Err() +} + +// Next implements the [handle.Rows] interface. +func (r wrappedRows) Next() bool { + return r.inner.Next() +} + +// Scan implements the [handle.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/benchmark/internal/oblast_pgx/statement.go b/benchmark/internal/oblast_pgx/statement.go new file mode 100644 index 0000000..d81c579 --- /dev/null +++ b/benchmark/internal/oblast_pgx/statement.go @@ -0,0 +1,60 @@ +// SPDX-FileCopyrightText: 2026 Stefan Majewsky +// SPDX-License-Identifier: Apache-2.0 + +package oblast_pgx + +import ( + "context" + "database/sql" + + "github.com/jackc/pgx/v5/pgconn" + "go.xyrillian.de/oblast/handle" +) + +type wrappedPreparedStatement struct { + ctx context.Context + statement *pgconn.StatementDescription + handle Handle +} + +type wrappedUnpreparedStatement struct { + query string + handle Handle +} + +var ( + _ handle.Statement = wrappedPreparedStatement{} + _ handle.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.2.3