summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorStefan Majewsky <majewsky@gmx.net>2026-07-31 21:39:56 +0200
committerStefan Majewsky <majewsky@gmx.net>2026-07-31 23:35:23 +0200
commit656e5dcce132716b39005814407224f989aa8c2d (patch)
treeab0a6d3a046170f0ace6a6e4ab3668ce0af811ae
parenta2f8d7a68f324d5b4086148571f990409425e22a (diff)
downloadgo-oblast-656e5dcce132716b39005814407224f989aa8c2d.tar.gz
replace types Handle, DB, Conn, Tx with their gg/gsql equivalents
-rw-r--r--CHANGELOG.md6
-rw-r--r--benchmark/benchmark_test.go5
-rw-r--r--benchmark/internal/oblast_pgx/handle.go14
-rw-r--r--benchmark/internal/oblast_pgx/results.go14
-rw-r--r--benchmark/internal/oblast_pgx/statement.go18
-rw-r--r--benchmark/postgres_test.go9
-rw-r--r--go.sum4
-rw-r--r--handle.go174
-rw-r--r--handle/handle.go54
-rw-r--r--query.go25
-rw-r--r--query_test.go25
-rw-r--r--runtimeindex_test.go3
-rw-r--r--select.go36
-rw-r--r--select_test.go17
14 files changed, 93 insertions, 311 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 87d7841..98537ce 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -3,6 +3,12 @@ SPDX-FileCopyrightText: 2026 Stefan Majewsky <majewsky@gmx.net>
SPDX-License-Identifier: Apache-2.0
-->
+# v0.13.0 (TBD)
+
+API changes:
+
+- The types `Handle`, `DB`, `Conn` and `Tx` have moved to `go.xyrillian.de/gg/gsql`.
+
# v0.12.0 (2026-07-17)
Changes:
diff --git a/benchmark/benchmark_test.go b/benchmark/benchmark_test.go
index a3e32f7..4549962 100644
--- a/benchmark/benchmark_test.go
+++ b/benchmark/benchmark_test.go
@@ -15,6 +15,7 @@ import (
"github.com/go-gorp/gorp/v3"
_ "github.com/mattn/go-sqlite3"
"go.xyrillian.de/gg/assert"
+ "go.xyrillian.de/gg/gsql"
"go.xyrillian.de/oblast"
"go.xyrillian.de/oblast/internal/testhelpers/must"
"gorm.io/driver/sqlite"
@@ -43,9 +44,9 @@ var (
batchSizesForUpdate = []int{1, 2, 4, 8, 16, 100}
)
-func makeSqliteTestDB(t testing.TB, recordCount int) (db *oblast.DB, dsn string) {
+func makeSqliteTestDB(t testing.TB, recordCount int) (db *gsql.DB, dsn string) {
dsn = fmt.Sprintf("file:%s?mode=memory&cache=shared", t.Name())
- db = oblast.NewDB(must.Return(sql.Open("sqlite3", dsn))(t))
+ db = gsql.NewDB(must.Return(sql.Open("sqlite3", dsn))(t))
_ = must.Return(db.Exec(`CREATE TABLE entries (id INTEGER, message TEXT, PRIMARY KEY (id AUTOINCREMENT))`))(t)
if recordCount > 0 {
diff --git a/benchmark/internal/oblast_pgx/handle.go b/benchmark/internal/oblast_pgx/handle.go
index 845fbe4..4bd72bd 100644
--- a/benchmark/internal/oblast_pgx/handle.go
+++ b/benchmark/internal/oblast_pgx/handle.go
@@ -12,7 +12,7 @@ import (
"github.com/jackc/pgx/v5"
"github.com/jackc/pgx/v5/pgconn"
"github.com/jackc/pgx/v5/pgxpool"
- "go.xyrillian.de/oblast/handle"
+ "go.xyrillian.de/gg/gsql"
)
type Handle interface {
@@ -27,7 +27,7 @@ var (
_ Handle = pgx.Tx(&pgxpool.Tx{})
)
-func Wrap(h Handle) handle.Handle {
+func Wrap(h Handle) gsql.Handle {
switch h := h.(type) {
case *pgx.Conn:
return wrappedHandle{h}
@@ -46,8 +46,8 @@ type wrappedHandle struct {
inner Handle
}
-// OblastPrepare implements the [handle.Handle] interface.
-func (h wrappedHandle) OblastPrepare(ctx context.Context, query string, repeated bool) (handle.Statement, error) {
+// 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
}
@@ -74,7 +74,7 @@ func deallocate(ctx context.Context, h Handle, stmt *pgconn.StatementDescription
case *pgx.Conn:
return h.Deallocate(ctx, stmt.Name)
case *pgxpool.Conn:
- panic("unreachable") // because func OblastPrepare() does not return a wrappedPreparedStatement for this underlying type
+ 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:
@@ -82,8 +82,8 @@ func deallocate(ctx context.Context, h Handle, stmt *pgconn.StatementDescription
}
}
-// OblastQuery implements the [handle.Handle] interface.
-func (h wrappedHandle) OblastQuery(ctx context.Context, query string, args []any) (handle.Rows, error) {
+// 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/benchmark/internal/oblast_pgx/results.go b/benchmark/internal/oblast_pgx/results.go
index 3ccb5ce..f842d36 100644
--- a/benchmark/internal/oblast_pgx/results.go
+++ b/benchmark/internal/oblast_pgx/results.go
@@ -9,16 +9,16 @@ import (
"github.com/jackc/pgx/v5"
"github.com/jackc/pgx/v5/pgconn"
- "go.xyrillian.de/oblast/handle"
+ "go.xyrillian.de/gg/gsql"
)
type wrappedRows struct {
inner pgx.Rows
}
-var _ handle.Rows = wrappedRows{}
+var _ gsql.Rows = wrappedRows{}
-// Columns implements the [handle.Rows] interface.
+// Columns implements the [gsql.Rows] interface.
func (r wrappedRows) Columns() ([]string, error) {
descriptions := r.inner.FieldDescriptions()
result := make([]string, len(descriptions))
@@ -28,23 +28,23 @@ func (r wrappedRows) Columns() ([]string, error) {
return result, nil
}
-// Close implements the [handle.Rows] interface.
+// Close implements the [gsql.Rows] interface.
func (r wrappedRows) Close() error {
r.inner.Close()
return nil
}
-// Err implements the [handle.Rows] interface.
+// Err implements the [gsql.Rows] interface.
func (r wrappedRows) Err() error {
return r.inner.Err()
}
-// Next implements the [handle.Rows] interface.
+// Next implements the [gsql.Rows] interface.
func (r wrappedRows) Next() bool {
return r.inner.Next()
}
-// Scan implements the [handle.Rows] interface.
+// Scan implements the [gsql.Rows] interface.
func (r wrappedRows) Scan(args ...any) error {
return r.inner.Scan(args...)
}
diff --git a/benchmark/internal/oblast_pgx/statement.go b/benchmark/internal/oblast_pgx/statement.go
index d81c579..0a33c73 100644
--- a/benchmark/internal/oblast_pgx/statement.go
+++ b/benchmark/internal/oblast_pgx/statement.go
@@ -8,7 +8,7 @@ import (
"database/sql"
"github.com/jackc/pgx/v5/pgconn"
- "go.xyrillian.de/oblast/handle"
+ "go.xyrillian.de/gg/gsql"
)
type wrappedPreparedStatement struct {
@@ -23,38 +23,38 @@ type wrappedUnpreparedStatement struct {
}
var (
- _ handle.Statement = wrappedPreparedStatement{}
- _ handle.Statement = wrappedUnpreparedStatement{}
+ _ gsql.Statement = wrappedPreparedStatement{}
+ _ gsql.Statement = wrappedUnpreparedStatement{}
)
-// Close implements the [handle.Statement] interface.
+// Close implements the [gsql.Statement] interface.
func (s wrappedPreparedStatement) Close() error {
return deallocate(s.ctx, s.handle, s.statement)
}
-// Close implements the [handle.Statement] interface.
+// Close implements the [gsql.Statement] interface.
func (s wrappedUnpreparedStatement) Close() error {
return nil
}
-// Exec implements the [handle.Statement] interface.
+// Exec implements the [gsql.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.
+// Exec implements the [gsql.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.
+// QueryRow implements the [gsql.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.
+// QueryRow implements the [gsql.Statement] interface.
func (s wrappedUnpreparedStatement) QueryRow(ctx context.Context, args, slots []any) error {
return s.handle.QueryRow(ctx, s.query, args...).Scan(slots...)
}
diff --git a/benchmark/postgres_test.go b/benchmark/postgres_test.go
index a5957f7..dba7ca8 100644
--- a/benchmark/postgres_test.go
+++ b/benchmark/postgres_test.go
@@ -16,6 +16,7 @@ import (
"github.com/jackc/pgx/v5"
_ "github.com/lib/pq"
"go.xyrillian.de/gg/assert"
+ "go.xyrillian.de/gg/gsql"
"go.xyrillian.de/oblast"
"go.xyrillian.de/oblast/benchmark/internal/oblast_pgx"
"go.xyrillian.de/oblast/internal/testhelpers/must"
@@ -36,9 +37,9 @@ func BenchmarkPostgresHeadingHeadingHeadingHeadingHeadingHeadingHeadingHeading(b
const defaultPostgresDSN = "host=localhost user=postgres dbname=oblast_benchmark sslmode=disable"
-func connectToPostgresTestDB(t testing.TB, recordCount int) *oblast.DB {
+func connectToPostgresTestDB(t testing.TB, recordCount int) *gsql.DB {
dsn := cmp.Or(os.Getenv("BENCHMARK_POSTGRES_DSN"), defaultPostgresDSN)
- db := oblast.NewDB(must.Return(sql.Open("postgres", dsn))(t))
+ db := gsql.NewDB(must.Return(sql.Open("postgres", dsn))(t))
_ = must.Return(db.Exec(`CREATE TEMPORARY TABLE entries (id BIGSERIAL, message TEXT)`))(t)
if recordCount > 0 {
@@ -204,7 +205,7 @@ func BenchmarkPostgresInsertAndDelete(b *testing.B) {
// test with different amounts of records
for _, batchSize := range batchSizesForInsertDelete {
b.Run("N="+strconv.Itoa(batchSize), func(b *testing.B) {
- insertAndDeleteWithOblast := func(b *testing.B, dbh oblast.Handle) {
+ insertAndDeleteWithOblast := func(b *testing.B, dbh gsql.Handle) {
records := make([]OblastEntry, batchSize)
recordsForInsert := make([]*OblastEntry, batchSize)
for idx := range records {
@@ -334,7 +335,7 @@ func BenchmarkPostgresUpdate(b *testing.B) {
}
}
- updateWithOblast := func(b *testing.B, dbh oblast.Handle, records []OblastEntry) func(string) {
+ updateWithOblast := func(b *testing.B, dbh gsql.Handle, records []OblastEntry) func(string) {
return func(message string) {
for idx := range records {
records[idx].Message = message
diff --git a/go.sum b/go.sum
index 2ea0055..9ddc0a6 100644
--- a/go.sum
+++ b/go.sum
@@ -1,2 +1,2 @@
-go.xyrillian.de/gg v1.11.1 h1:7P3kDFkTmR7jx2riYi0GwX5uhgrsL37QSrN16xH/n8E=
-go.xyrillian.de/gg v1.11.1/go.mod h1:DoO4fQSWIrBRlNlCjVyrYM0kAEBt/Jg2GkMH+cGRZ0k=
+go.xyrillian.de/gg v1.12.0 h1:oW9S91y36lS72D3p9a4axqjkD9zkkjkNZ12+PaZHpos=
+go.xyrillian.de/gg v1.12.0/go.mod h1:DoO4fQSWIrBRlNlCjVyrYM0kAEBt/Jg2GkMH+cGRZ0k=
diff --git a/handle.go b/handle.go
deleted file mode 100644
index f35ae35..0000000
--- a/handle.go
+++ /dev/null
@@ -1,174 +0,0 @@
-// SPDX-FileCopyrightText: 2026 Stefan Majewsky <majewsky@gmx.net>
-// SPDX-License-Identifier: Apache-2.0
-
-package oblast
-
-import (
- "context"
- "database/sql"
- "fmt"
-
- "go.xyrillian.de/oblast/handle"
-)
-
-// Handle contains behavior that database handles must offer to Oblast.
-// Custom implementations of this interface can be used to connect non-std database drivers to Oblast.
-type Handle = handle.Handle
-
-////////////////////////////////////////////////////////////////////////////////
-// public API for database/sql compatibility
-//
-// NOTE: The internal structure of these types looks weird at first glance, with
-// the pointer to the underlying instance duplicated, but of course that's deliberate.
-//
-// If our types implemented [Handle] directly, every function call taking them as an argument
-// of type [Handle] (e.g. any of the methods on [Store]) would allocate a new fat pointer
-// when converting from e.g. [*DB] at the callsite to [Handle] in the argument value.
-//
-// To circumvent this, our types only _have_ [Handle] instances within them within them
-// as an embedded field, thus implementing [Handle] indirectly instead of directly.
-
-// DB wraps [*sql.DB] into a [Handle] that can be used with Oblast.
-//
-// Because this type has [*sql.DB] as an embedded field,
-// all methods from that type work on this type as well.
-type DB struct {
- *sql.DB
- Handle
-}
-
-// NewDB wraps an instance of [*sql.DB] into Oblast's own [DB] type.
-func NewDB(db *sql.DB) *DB {
- return &DB{db, sqlHandle[*sql.DB]{db}}
-}
-
-// Begin is like [sql.DB.Begin], but wraps the resulting transaction for use with Oblast.
-func (db *DB) Begin() (*Tx, error) {
- tx, err := db.DB.Begin()
- return maybe(NewTx, tx), err
-}
-
-// BeginTx is like [sql.DB.BeginTx], but wraps the resulting transaction for use with Oblast.
-func (db *DB) BeginTx(ctx context.Context, opts *sql.TxOptions) (*Tx, error) {
- tx, err := db.DB.BeginTx(ctx, opts)
- return maybe(NewTx, tx), err
-}
-
-// Conn is like [sql.DB.Conn], but wraps the resulting connection for use with Oblast.
-func (db *DB) Conn(ctx context.Context) (*Conn, error) {
- conn, err := db.DB.Conn(ctx)
- return maybe(NewConn, conn), err
-}
-
-// Conn wraps [*sql.Conn] into a [Handle] that can be used with Oblast.
-//
-// Because this type has [*sql.Conn] as an embedded field,
-// all methods from that type work on this type as well.
-type Conn struct {
- *sql.Conn
- Handle
-}
-
-// NewConn wraps an instance of [*sql.Conn] into Oblast's own [Conn] type.
-func NewConn(db *sql.Conn) *Conn {
- return &Conn{db, sqlHandle[*sql.Conn]{db}}
-}
-
-// BeginTx is like [sql.DB.BeginTx], but wraps the resulting transaction for use with Oblast.
-func (conn *Conn) BeginTx(ctx context.Context, opts *sql.TxOptions) (*Tx, error) {
- tx, err := conn.Conn.BeginTx(ctx, opts)
- return maybe(NewTx, tx), err
-}
-
-// Tx wraps [*sql.Tx] into a [Handle] that can be used with Oblast.
-//
-// Because this type has [*sql.Tx] as an embedded field,
-// all methods from that type work on this type as well.
-type Tx struct {
- *sql.Tx
- Handle
-}
-
-// NewTx wraps an instance of [*sql.Tx] into Oblast's own [Tx] type.
-func NewTx(db *sql.Tx) *Tx {
- return &Tx{db, sqlHandle[*sql.Tx]{db}}
-}
-
-func maybe[T, U any](wrap func(*T) *U, value *T) *U {
- if value == nil {
- return nil
- }
- return wrap(value)
-}
-
-// prove that we implement the interfaces that we claim
-var (
- _ Handle = &DB{}
- _ Handle = &Conn{}
- _ Handle = &Tx{}
-)
-
-////////////////////////////////////////////////////////////////////////////////
-// Handle implementation for database/sql types
-
-// sqlExecutor is an interface covered by both [*sql.DB], [*sql.Conn] and [*sql.Tx].
-type sqlExecutor interface {
- ExecContext(ctx context.Context, query string, args ...any) (sql.Result, error)
- PrepareContext(ctx context.Context, query string) (*sql.Stmt, error)
- QueryContext(ctx context.Context, query string, args ...any) (*sql.Rows, error)
- QueryRowContext(ctx context.Context, query string, args ...any) *sql.Row
-}
-
-// sqlHandle provides the [Handle] implementation for any type that implements [sqlExecutor].
-type sqlHandle[T sqlExecutor] struct {
- Base T
-}
-
-// OblastPrepare implements the [Handle] interface.
-func (h sqlHandle[T]) OblastPrepare(ctx context.Context, query string, repeated bool) (handle.Statement, error) {
- if !repeated {
- return wrappedStatement{h.Base, query, nil}, nil
- }
- stmt, err := h.Base.PrepareContext(ctx, query)
- if err != nil {
- return nil, fmt.Errorf("during Prepare(): %w", err)
- }
- return wrappedStatement{h.Base, query, stmt}, nil
-}
-
-// OblastQuery implements the [Handle] interface.
-func (h sqlHandle[T]) OblastQuery(ctx context.Context, query string, args []any) (handle.Rows, error) {
- return h.Base.QueryContext(ctx, query, args...) //nolint:rowserrcheck // the caller does the check
-}
-
-type wrappedStatement struct {
- db sqlExecutor
- query string
- stmt *sql.Stmt // nil if repeated = false
-}
-
-// Close implements the [Statement] interface.
-func (s wrappedStatement) Close() error {
- if s.stmt == nil {
- return nil
- }
- return s.stmt.Close()
-}
-
-// Exec implements the [Statement] interface.
-func (s wrappedStatement) Exec(ctx context.Context, args []any) (sql.Result, error) {
- if s.stmt == nil {
- return s.db.ExecContext(ctx, s.query, args...)
- } else {
- return s.stmt.ExecContext(ctx, args...)
- }
-}
-
-// QueryRow implements the [Statement] interface.
-func (s wrappedStatement) QueryRow(ctx context.Context, args, slots []any) error {
- if s.stmt == nil {
- return s.db.QueryRowContext(ctx, s.query, args...).Scan(slots...)
- } else {
- return s.stmt.QueryRowContext(ctx, args...).Scan(slots...)
- }
-}
diff --git a/handle/handle.go b/handle/handle.go
deleted file mode 100644
index f6e1694..0000000
--- a/handle/handle.go
+++ /dev/null
@@ -1,54 +0,0 @@
-// SPDX-FileCopyrightText: 2026 Stefan Majewsky <majewsky@gmx.net>
-// SPDX-License-Identifier: Apache-2.0
-
-// Package handle contains type definitions for connecting non-std database drivers to Oblast.
-// Since most database drivers use the standard interface from database/sql, the Wrap() function from the main package covers the needs of most users.
-package handle
-
-import (
- "context"
- "database/sql"
-)
-
-// Handle contains behavior that database handles must offer to Oblast.
-// The standard-library types [*sql.DB], [*sql.Conn] and [*sql.Tx] can satisfy this interface through the respective types of the same name from the main Oblast package.
-// Custom implementations of this interface can be used to connect non-std database drivers to Oblast.
-//
-// The method names are deliberately clunky to avoid name clashes with well-known methods like [sql.DB.Prepare] or [sql.DB.Query].
-type Handle interface {
- // OblastPrepare prepares to execute a certain SQL query one or multiple times.
- //
- // The "repeated" flag is a hint to the implementation whether the same statement is going to be run many times.
- // If false, the implementation shall choose to forego the additional effort of a full statement preparation if possible,
- // and execute one-off queries instead.
- OblastPrepare(ctx context.Context, query string, repeated bool) (Statement, error)
-
- // OblastQuery works like db.QueryContext(ctx, query, args...).
- OblastQuery(ctx context.Context, query string, args []any) (Rows, error)
-}
-
-// Statement represents a prepared statement returned from the OblastPrepare() method of [Handle].
-// The Exec and QueryRow methods shall work similarly to the respective functions on [*sql.Tx], as indicated in the comments.
-//
-// You will not need to interact with this type except when implementing your own [Handle].
-type Statement interface {
- Close() error
-
- // Exec works like stmt.ExecContext(ctx, args...).
- Exec(ctx context.Context, args []any) (sql.Result, error)
-
- // QueryRow works like stmt.QueryRow(ctx, args...).Scan(slots...).
- QueryRow(ctx context.Context, args []any, slots []any) error
-}
-
-// Rows represents a set of rows returned from the OblastQuery() method of [Handle].
-// All methods shall behave like on the [*sql.Rows] type from std.
-//
-// You will not need to interact with this type except when implementing your own [Handle].
-type Rows interface {
- Columns() ([]string, error)
- Close() error
- Err() error
- Next() bool
- Scan(slots ...any) error
-}
diff --git a/query.go b/query.go
index b92106a..e9d6862 100644
--- a/query.go
+++ b/query.go
@@ -10,8 +10,7 @@ import (
"reflect"
"go.xyrillian.de/gg/errext"
-
- "go.xyrillian.de/oblast/handle"
+ "go.xyrillian.de/gg/gsql"
)
// PrepareThreshold is a tuning parameter for the strategy used by all methods of [Store] operating on batches of records provided by the caller
@@ -29,12 +28,12 @@ import (
var PrepareThreshold int = 8
// prepare behaves like [Handle.Prepare].
-func prepare(ctx context.Context, db Handle, query, operation string, inputSize int) (handle.Statement, error) {
+func prepare(ctx context.Context, db gsql.Handle, query, operation string, inputSize int) (gsql.Statement, error) {
if query == "" {
return nil, fmt.Errorf("cannot execute %s() because query could not be autogenerated", operation)
}
- return db.OblastPrepare(ctx, query, inputSize >= PrepareThreshold)
+ return db.GSQLPrepare(ctx, query, inputSize >= PrepareThreshold)
}
// Insert executes an SQL INSERT statement for each of the provided records.
@@ -48,7 +47,7 @@ func prepare(ctx context.Context, db Handle, query, operation string, inputSize
// Returns an error if any of the `records` has a non-zero value in any column marked as `db:",auto"`.
// Records that already exist in the database should be handled with [Store.Update] instead.
// To automatically decide between INSERT and UPDATE on a per-record basis, use [Store.Upsert] instead.
-func (s Store[R]) Insert(ctx context.Context, db Handle, records ...*R) error {
+func (s Store[R]) Insert(ctx context.Context, db gsql.Handle, records ...*R) error {
// NOTE: This function body should be as short as possible to reduce the binary size after monomorphization.
// Any expression that does not depend on type R should be factored out into a reusable function.
@@ -59,7 +58,7 @@ func (s Store[R]) Insert(ctx context.Context, db Handle, records ...*R) error {
return s.insertUsing(ctx, stmt, db, records)
}
-func (s Store[R]) insertUsing(ctx context.Context, stmt handle.Statement, db Handle, records []*R) error {
+func (s Store[R]) insertUsing(ctx context.Context, stmt gsql.Statement, db gsql.Handle, records []*R) error {
// NOTE: This function body should be as short as possible to reduce the binary size after monomorphization.
// Any expression that does not depend on type R should be factored out into a reusable function.
@@ -85,7 +84,7 @@ func (s Store[R]) insertUsing(ctx context.Context, stmt handle.Statement, db Han
return errext.WithCleanup(nil, "Stmt.Close", stmt.Close())
}
-func insertRecord(ctx context.Context, plan plan, v reflect.Value, recordIndex int, stmt handle.Statement, argumentIndexes [][]int, argumentSlots []any, scanIndexes [][]int, scanSlots []any) error {
+func insertRecord(ctx context.Context, plan plan, v reflect.Value, recordIndex int, stmt gsql.Statement, argumentIndexes [][]int, argumentSlots []any, scanIndexes [][]int, scanSlots []any) error {
for idx, index := range argumentIndexes {
argumentSlots[idx] = v.FieldByIndex(index).Interface()
}
@@ -156,7 +155,7 @@ func checkTransparentPointerStructFieldsInitialized(operation string, recordInde
// Returns [MissingRecordError] if any of the records does not exist in the database, that is, if for any of the records, the database contains no row with the same primary key values.
//
// Returns an error if [NewStore] was called without the [TableNameIs] or [PrimaryKeyIs] options, which are both required to generate a query for this method.
-func (s Store[R]) Update(ctx context.Context, db Handle, records ...R) error {
+func (s Store[R]) Update(ctx context.Context, db gsql.Handle, records ...R) error {
// NOTE: This function body should be as short as possible to reduce the binary size after monomorphization.
// Any expression that does not depend on type R should be factored out into a reusable function.
@@ -187,7 +186,7 @@ func (s Store[R]) Update(ctx context.Context, db Handle, records ...R) error {
return errext.WithCleanup(nil, "Stmt.Close", stmt.Close())
}
-func updateRecord(ctx context.Context, v reflect.Value, recordIndex int, stmt handle.Statement, argumentIndexes [][]int, argumentSlots []any) (int64, error) {
+func updateRecord(ctx context.Context, v reflect.Value, recordIndex int, stmt gsql.Statement, argumentIndexes [][]int, argumentSlots []any) (int64, error) {
for idx, index := range argumentIndexes {
argumentSlots[idx] = v.FieldByIndex(index).Interface()
}
@@ -205,7 +204,7 @@ func updateRecord(ctx context.Context, v reflect.Value, recordIndex int, stmt ha
// Delete executes an SQL DELETE statement for each of the provided records, using their primary keys to locate the respective table rows.
//
// Returns an error if [NewStore] was called without the [TableNameIs] or [PrimaryKeyIs] options, which are both required to generate a query for this method.
-func (s Store[R]) Delete(ctx context.Context, db Handle, records ...R) error {
+func (s Store[R]) Delete(ctx context.Context, db gsql.Handle, records ...R) error {
// NOTE: This function body should be as short as possible to reduce the binary size after monomorphization.
// Any expression that does not depend on type R should be factored out into a reusable function.
@@ -230,7 +229,7 @@ func (s Store[R]) Delete(ctx context.Context, db Handle, records ...R) error {
return errext.WithCleanup(nil, "Stmt.Close", stmt.Close())
}
-func deleteRecord(ctx context.Context, plan plan, v reflect.Value, recordIndex int, stmt handle.Statement, argumentIndexes [][]int, argumentSlots []any) error {
+func deleteRecord(ctx context.Context, plan plan, v reflect.Value, recordIndex int, stmt gsql.Statement, argumentIndexes [][]int, argumentSlots []any) error {
err := checkTransparentPointerStructFieldsInitialized("DELETE", recordIndex, v, plan, true)
if err != nil {
return errext.WithCleanup(err, "Stmt.Close", stmt.Close())
@@ -253,7 +252,7 @@ func deleteRecord(ctx context.Context, plan plan, v reflect.Value, recordIndex i
// Returns an error if [NewStore] was called without the [TableNameIs] or [PrimaryKeyIs] options, which are both required to generate the respective queries for this method.
// - For record types that do not have fields declared with the "auto" tag, an INSERT ... ON CONFLICT statement is used.
// Returns an error if [NewStore] was called without the [TableNameIs] option, which is required to generate a query for this method.
-func (s Store[R]) Upsert(ctx context.Context, db Handle, records ...*R) error {
+func (s Store[R]) Upsert(ctx context.Context, db gsql.Handle, records ...*R) error {
// NOTE: This function body should be as short as possible to reduce the binary size after monomorphization.
// Any expression that does not depend on type R should be factored out into a reusable function.
@@ -281,7 +280,7 @@ func (s Store[R]) Upsert(ctx context.Context, db Handle, records ...*R) error {
return err
}
-func (s Store[R]) doUpsert(ctx context.Context, db Handle, insertStmt, updateStmt handle.Statement, records []*R) error {
+func (s Store[R]) doUpsert(ctx context.Context, db gsql.Handle, insertStmt, updateStmt gsql.Statement, records []*R) error {
// NOTE: This function body should be as short as possible to reduce the binary size after monomorphization.
// Any expression that does not depend on type R should be factored out into a reusable function.
diff --git a/query_test.go b/query_test.go
index 7016c61..4dd3caf 100644
--- a/query_test.go
+++ b/query_test.go
@@ -10,6 +10,7 @@ import (
"time"
"go.xyrillian.de/gg/assert"
+ "go.xyrillian.de/gg/gsql"
"go.xyrillian.de/oblast"
"go.xyrillian.de/oblast/internal/testhelpers/mock"
"go.xyrillian.de/oblast/internal/testhelpers/must"
@@ -18,7 +19,7 @@ import (
func TestInsertBasic(t *testing.T) {
ctx := t.Context()
md := mock.NewDriver()
- db := oblast.NewDB(sql.OpenDB(md))
+ db := gsql.NewDB(sql.OpenDB(md))
type basicRecord struct {
ID int64 `db:"id,auto"`
@@ -80,7 +81,7 @@ func TestInsertBasic(t *testing.T) {
func TestInsertWithUintPrimaryKey(t *testing.T) {
ctx := t.Context()
md := mock.NewDriver()
- db := oblast.NewDB(sql.OpenDB(md))
+ db := gsql.NewDB(sql.OpenDB(md))
type exoticRecord struct {
ID uint64 `oblast:"id,auto"`
@@ -113,7 +114,7 @@ func TestInsertWithUintPrimaryKey(t *testing.T) {
func TestUpdateBasic(t *testing.T) {
ctx := t.Context()
md := mock.NewDriver()
- db := oblast.NewDB(sql.OpenDB(md))
+ db := gsql.NewDB(sql.OpenDB(md))
type basicRecord struct {
ID int64 `db:"id,auto"`
@@ -143,7 +144,7 @@ func TestUpdateBasic(t *testing.T) {
func TestDeleteBasic(t *testing.T) {
ctx := t.Context()
md := mock.NewDriver()
- db := oblast.NewDB(sql.OpenDB(md))
+ db := gsql.NewDB(sql.OpenDB(md))
type basicRecord struct {
ID int64 `db:"id,auto"`
@@ -173,7 +174,7 @@ func TestDeleteBasic(t *testing.T) {
func TestUpsertBasicWithAutoColumn(t *testing.T) {
ctx := t.Context()
md := mock.NewDriver()
- db := oblast.NewDB(sql.OpenDB(md))
+ db := gsql.NewDB(sql.OpenDB(md))
type basicRecord struct {
ID int64 `db:"id,auto"`
@@ -217,7 +218,7 @@ func TestUpsertBasicWithAutoColumn(t *testing.T) {
func TestWriteQueriesNotPossible(t *testing.T) {
ctx := t.Context()
md := mock.NewDriver()
- db := oblast.NewDB(sql.OpenDB(md))
+ db := gsql.NewDB(sql.OpenDB(md))
type basicRecord struct {
ID int64 `db:"id,auto"`
@@ -246,7 +247,7 @@ func TestWriteQueriesNotPossible(t *testing.T) {
func TestWriteQueriesFailDuringPrepare(t *testing.T) {
ctx := t.Context()
md := mock.NewDriver()
- db := oblast.NewDB(sql.OpenDB(md))
+ db := gsql.NewDB(sql.OpenDB(md))
type basicRecord struct {
ID int64 `db:"id,auto"`
@@ -295,7 +296,7 @@ func TestWriteQueriesFailDuringPrepare(t *testing.T) {
func TestUpdateOrUpsertFailsOnMissingRecord(t *testing.T) {
ctx := t.Context()
md := mock.NewDriver()
- db := oblast.NewDB(sql.OpenDB(md))
+ db := gsql.NewDB(sql.OpenDB(md))
type basicRecord struct {
ID int64 `db:"id,auto"`
@@ -330,7 +331,7 @@ func TestUpdateOrUpsertFailsOnMissingRecord(t *testing.T) {
func TestInsertFailsOnFilledAutoField(t *testing.T) {
ctx := t.Context()
md := mock.NewDriver()
- db := oblast.NewDB(sql.OpenDB(md))
+ db := gsql.NewDB(sql.OpenDB(md))
type basicRecord struct {
ID int64 `db:"id,auto"`
@@ -349,7 +350,7 @@ func TestInsertFailsOnFilledAutoField(t *testing.T) {
func TestInsertAndUpsertWithNoAutoColumns(t *testing.T) {
ctx := t.Context()
md := mock.NewDriver()
- db := oblast.NewDB(sql.OpenDB(md))
+ db := gsql.NewDB(sql.OpenDB(md))
type relation struct {
FooID int64 `db:"foo_id"`
@@ -380,7 +381,7 @@ func TestInsertAndUpsertWithNoAutoColumns(t *testing.T) {
func TestUpsertFailsOnMixedAutoFieldState(t *testing.T) {
ctx := t.Context()
md := mock.NewDriver()
- db := oblast.NewDB(sql.OpenDB(md))
+ db := gsql.NewDB(sql.OpenDB(md))
type complexRecord struct {
ID int64 `db:"id,auto"`
@@ -405,7 +406,7 @@ func TestUpsertFailsOnMixedAutoFieldState(t *testing.T) {
func TestUninitializedTransparentPointerStructs(t *testing.T) {
ctx := t.Context()
md := mock.NewDriver()
- db := oblast.NewDB(sql.OpenDB(md))
+ db := gsql.NewDB(sql.OpenDB(md))
// declare a record type that has a transparent pointer struct containing non-primary-key fields
type timestamps struct {
diff --git a/runtimeindex_test.go b/runtimeindex_test.go
index 59044c4..dd65b07 100644
--- a/runtimeindex_test.go
+++ b/runtimeindex_test.go
@@ -8,6 +8,7 @@ import (
"testing"
"go.xyrillian.de/gg/assert"
+ "go.xyrillian.de/gg/gsql"
"go.xyrillian.de/oblast"
"go.xyrillian.de/oblast/internal/testhelpers/mock"
"go.xyrillian.de/oblast/internal/testhelpers/must"
@@ -16,7 +17,7 @@ import (
func TestRuntimeIndex(t *testing.T) {
ctx := t.Context()
md := mock.NewDriver()
- db := oblast.NewDB(sql.OpenDB(md))
+ db := gsql.NewDB(sql.OpenDB(md))
type basicRecord struct {
ID int64 `db:"id"`
diff --git a/select.go b/select.go
index 37edb92..c171655 100644
--- a/select.go
+++ b/select.go
@@ -11,8 +11,8 @@ import (
"reflect"
"go.xyrillian.de/gg/errext"
+ "go.xyrillian.de/gg/gsql"
. "go.xyrillian.de/gg/option"
- "go.xyrillian.de/oblast/handle"
)
// Select executes the provided SQL query and fills an instance of the record type R for each row in the result set,
@@ -20,7 +20,7 @@ import (
//
// An error is returned if any column name in the result set does not correspond to an addressable field in R.
// Errors can be retrieved through the methods on type [Selection].
-func (s Store[R]) Select(ctx context.Context, db Handle, query string, args ...any) Selection[R] {
+func (s Store[R]) Select(ctx context.Context, db gsql.Handle, query string, args ...any) Selection[R] {
// NOTE: This function body should be as short as possible to reduce the binary size after monomorphization.
// Any expression that does not depend on type R should be factored out into a reusable function.
@@ -41,15 +41,15 @@ func (s Store[R]) Select(ctx context.Context, db Handle, query string, args ...a
//
// Returns an error if [NewStore] was called without the [TableNameIs] option, which is required to generate a query for this method.
// Errors can be retrieved through the methods on type [Selection].
-func (s Store[R]) SelectWhere(ctx context.Context, db Handle, partialQuery string, args ...any) Selection[R] {
+func (s Store[R]) SelectWhere(ctx context.Context, db gsql.Handle, partialQuery string, args ...any) Selection[R] {
// NOTE: This function body should be as short as possible to reduce the binary size after monomorphization.
// Any expression that does not depend on type R should be factored out into a reusable function.
return Selection[R]{startSelectWhereQuery(ctx, db, s.plan, partialQuery, args...)}
}
-func startSelectQuery(ctx context.Context, db Handle, plan plan, query string, args ...any) selection {
- rows, err := db.OblastQuery(ctx, query, args)
+func startSelectQuery(ctx context.Context, db gsql.Handle, plan plan, query string, args ...any) selection {
+ rows, err := db.GSQLQuery(ctx, query, args)
if err != nil {
return selection{Err: fmt.Errorf("during Query(): %w", err)}
}
@@ -81,12 +81,12 @@ func startSelectQuery(ctx context.Context, db Handle, plan plan, query string, a
}
}
-func startSelectWhereQuery(ctx context.Context, db Handle, plan plan, partialQuery string, args ...any) selection {
+func startSelectWhereQuery(ctx context.Context, db gsql.Handle, plan plan, partialQuery string, args ...any) selection {
if plan.Select.Query == "" {
return selection{Err: errors.New("cannot execute SelectWhere() because query could not be autogenerated")}
}
query := plan.Select.Query + partialQuery
- rows, err := db.OblastQuery(ctx, query, args)
+ rows, err := db.GSQLQuery(ctx, query, args)
if err != nil {
return selection{Err: fmt.Errorf("during Query(): %w", err)}
}
@@ -106,7 +106,7 @@ func startSelectWhereQuery(ctx context.Context, db Handle, plan plan, partialQue
//
// Warning: Because of limitations in the interface of database/sql, this function is built on [Store.Select] and cannot be any faster than it.
// For maximum performance, use [Store.SelectOneWhere] which avoids the overhead of potentially having to read multiple rows.
-func (s Store[R]) SelectOne(ctx context.Context, db Handle, query string, args ...any) (R, error) {
+func (s Store[R]) SelectOne(ctx context.Context, db gsql.Handle, query string, args ...any) (R, error) {
// NOTE: This function body should be as short as possible to reduce the binary size after monomorphization.
// Any expression that does not depend on type R should be factored out into a reusable function.
//
@@ -117,7 +117,7 @@ func (s Store[R]) SelectOne(ctx context.Context, db Handle, query string, args .
}
// SelectOneOrNone is like SelectOne, but returns [None] instead of [sql.ErrNoRows].
-func (s Store[R]) SelectOneOrNone(ctx context.Context, db Handle, query string, args ...any) (Option[R], error) {
+func (s Store[R]) SelectOneOrNone(ctx context.Context, db gsql.Handle, query string, args ...any) (Option[R], error) {
// NOTE: This function body should be as short as possible to reduce the binary size after monomorphization.
// Any expression that does not depend on type R should be factored out into a reusable function.
@@ -129,7 +129,7 @@ func (s Store[R]) SelectOneOrNone(ctx context.Context, db Handle, query string,
//
// This method is more efficient than [Store.SelectOne] on CPU runtime, but has a slight memory allocation overhead per call from query preparation.
// This can be avoided by using [Store.PrepareSelectQueryWhere] instead.
-func (s Store[R]) SelectOneWhere(ctx context.Context, db Handle, partialQuery string, args ...any) (R, error) {
+func (s Store[R]) SelectOneWhere(ctx context.Context, db gsql.Handle, partialQuery string, args ...any) (R, error) {
// NOTE: This function body should be as short as possible to reduce the binary size after monomorphization.
// Any expression that does not depend on type R should be factored out into a reusable function.
@@ -139,14 +139,14 @@ func (s Store[R]) SelectOneWhere(ctx context.Context, db Handle, partialQuery st
}
// SelectOneOrNoneWhere is like SelectOneWhere, but returns [None] instead of [sql.ErrNoRows].
-func (s Store[R]) SelectOneOrNoneWhere(ctx context.Context, db Handle, partialQuery string, args ...any) (Option[R], error) {
+func (s Store[R]) SelectOneOrNoneWhere(ctx context.Context, db gsql.Handle, partialQuery string, args ...any) (Option[R], error) {
// NOTE: This function body should be as short as possible to reduce the binary size after monomorphization.
// Any expression that does not depend on type R should be factored out into a reusable function.
return noRowsToNone(s.SelectOneWhere(ctx, db, partialQuery, args...))
}
-func selectOneWhere(ctx context.Context, db Handle, plan plan, v reflect.Value, partialQuery string, args []any) error {
+func selectOneWhere(ctx context.Context, db gsql.Handle, plan plan, v reflect.Value, partialQuery string, args []any) error {
if plan.Select.Query == "" {
return errors.New("cannot execute SelectOneWhere() because query could not be autogenerated")
}
@@ -154,7 +154,7 @@ func selectOneWhere(ctx context.Context, db Handle, plan plan, v reflect.Value,
return selectOne(ctx, db, plan, v, query, args)
}
-func selectOne(ctx context.Context, db Handle, plan plan, v reflect.Value, query string, args []any) error {
+func selectOne(ctx context.Context, db gsql.Handle, plan plan, v reflect.Value, query string, args []any) error {
for _, field := range plan.TransparentPointerStructFields {
f := v.FieldByIndex(field.Index)
f.Set(reflect.New(f.Type().Elem()))
@@ -163,7 +163,7 @@ func selectOne(ctx context.Context, db Handle, plan plan, v reflect.Value, query
for idx, index := range plan.Select.ScanIndexes {
slots[idx] = v.FieldByIndex(index).Addr().Interface()
}
- stmt, err := db.OblastPrepare(ctx, query, false)
+ stmt, err := db.GSQLPrepare(ctx, query, false)
if err != nil {
return err
}
@@ -218,14 +218,14 @@ type PreparedSelectQuery[R any] struct {
}
// Select behaves the same as [Store.SelectWhere], but uses the query that was precomputed when q was constructed.
-func (q PreparedSelectQuery[R]) Select(ctx context.Context, db Handle, args ...any) Selection[R] {
+func (q PreparedSelectQuery[R]) Select(ctx context.Context, db gsql.Handle, args ...any) Selection[R] {
// NOTE: This function body should be as short as possible to reduce the binary size after monomorphization.
// Any expression that does not depend on type R should be factored out into a reusable function.
return Selection[R]{startSelectQuery(ctx, db, q.store.plan, q.query, args...)}
}
// SelectOne behaves the same as [Store.SelectOneWhere], but uses the query that was precomputed when q was constructed.
-func (q PreparedSelectQuery[R]) SelectOne(ctx context.Context, db Handle, args ...any) (R, error) {
+func (q PreparedSelectQuery[R]) SelectOne(ctx context.Context, db gsql.Handle, args ...any) (R, error) {
// NOTE: This function body should be as short as possible to reduce the binary size after monomorphization.
// Any expression that does not depend on type R should be factored out into a reusable function.
@@ -235,7 +235,7 @@ func (q PreparedSelectQuery[R]) SelectOne(ctx context.Context, db Handle, args .
}
// SelectOneOrNone is like SelectOne, but returns [None] instead of [sql.ErrNoRows].
-func (q PreparedSelectQuery[R]) SelectOneOrNone(ctx context.Context, db Handle, args ...any) (Option[R], error) {
+func (q PreparedSelectQuery[R]) SelectOneOrNone(ctx context.Context, db gsql.Handle, args ...any) (Option[R], error) {
return noRowsToNone(q.SelectOne(ctx, db, args...))
}
@@ -254,7 +254,7 @@ type Selection[R any] struct {
// This separate type does not have type arguments and thus is not duplicated by monomorphization.
type selection struct {
// from startSelectQuery()
- Rows handle.Rows
+ Rows gsql.Rows
Slots []any // NOTE: len(s.Slots) == len(s.Indexes)
Err error // NOTE: if this field is set, all other fields will be unset
// from plan
diff --git a/select_test.go b/select_test.go
index 3d6ed55..c2b319e 100644
--- a/select_test.go
+++ b/select_test.go
@@ -9,6 +9,7 @@ import (
"testing"
"time"
+ "go.xyrillian.de/gg/gsql"
. "go.xyrillian.de/gg/option"
"go.xyrillian.de/gg/assert"
@@ -20,7 +21,7 @@ import (
func TestSelectReturningSomeRecords(t *testing.T) {
ctx := t.Context()
md := mock.NewDriver()
- db := oblast.NewDB(sql.OpenDB(md))
+ db := gsql.NewDB(sql.OpenDB(md))
type basicRecord struct {
ID int64 `db:"id"`
@@ -138,7 +139,7 @@ func TestSelectReturningSomeRecords(t *testing.T) {
func TestSelectReturningNoRecords(t *testing.T) {
ctx := t.Context()
md := mock.NewDriver()
- db := oblast.NewDB(sql.OpenDB(md))
+ db := gsql.NewDB(sql.OpenDB(md))
type basicRecord struct {
ID int64 `db:"id"`
@@ -229,7 +230,7 @@ func TestSelectReturningNoRecords(t *testing.T) {
func TestSelectIntoUnexpectedField(t *testing.T) {
ctx := t.Context()
md := mock.NewDriver()
- db := oblast.NewDB(sql.OpenDB(md))
+ db := gsql.NewDB(sql.OpenDB(md))
type basicRecord struct {
ID int64 `db:"id"`
@@ -268,7 +269,7 @@ func TestSelectIntoUnexpectedField(t *testing.T) {
func TestSelectWithScanError(t *testing.T) {
ctx := t.Context()
md := mock.NewDriver()
- db := oblast.NewDB(sql.OpenDB(md))
+ db := gsql.NewDB(sql.OpenDB(md))
type basicRecord struct {
ID int64 `db:"id"`
@@ -331,7 +332,7 @@ func TestSelectWithScanError(t *testing.T) {
func TestSelectIntoEmbeddedTypes(t *testing.T) {
ctx := t.Context()
md := mock.NewDriver()
- db := oblast.NewDB(sql.OpenDB(md))
+ db := gsql.NewDB(sql.OpenDB(md))
type HasCreatedAt struct {
CreatedAt time.Time `db:"created_at"`
@@ -442,7 +443,7 @@ func TestSelectIntoEmbeddedTypes(t *testing.T) {
func TestSelectCapturingQueryError(t *testing.T) {
ctx := t.Context()
md := mock.NewDriver()
- db := oblast.NewDB(sql.OpenDB(md))
+ db := gsql.NewDB(sql.OpenDB(md))
type basicRecord struct {
ID int64 `db:"id"`
@@ -490,7 +491,7 @@ func TestSelectCapturingQueryError(t *testing.T) {
func TestSelectCapturingCloseError(t *testing.T) {
ctx := t.Context()
md := mock.NewDriver()
- db := oblast.NewDB(sql.OpenDB(md))
+ db := gsql.NewDB(sql.OpenDB(md))
type basicRecord struct {
ID int64 `db:"id"`
@@ -553,7 +554,7 @@ func TestSelectCapturingCloseError(t *testing.T) {
func TestSelectNotPossibleWithoutTableName(t *testing.T) {
ctx := t.Context()
md := mock.NewDriver()
- db := oblast.NewDB(sql.OpenDB(md))
+ db := gsql.NewDB(sql.OpenDB(md))
type basicRecord struct {
ID int64 `db:"id"`