aboutsummaryrefslogtreecommitdiff
path: root/query.go
diff options
context:
space:
mode:
authorStefan Majewsky <majewsky@gmx.net>2026-05-12 13:11:41 +0200
committerStefan Majewsky <majewsky@gmx.net>2026-05-12 13:11:41 +0200
commit7bdc14a145c4b5c380921abf1db3f2c00a9c66cc (patch)
treed08f4436739456ab015598eb9f02047208f981de /query.go
parentd5e652b41e68538fc7a7d8f67c3b8dfe3129a464 (diff)
downloadgo-oblast-7bdc14a145c4b5c380921abf1db3f2c00a9c66cc.tar.gz
change Handle to a generic interface without explicit dep on database/sql
Diffstat (limited to 'query.go')
-rw-r--r--query.go65
1 files changed, 15 insertions, 50 deletions
diff --git a/query.go b/query.go
index b9bc328..0296c13 100644
--- a/query.go
+++ b/query.go
@@ -5,9 +5,10 @@ package oblast
import (
"context"
- "database/sql"
"fmt"
"reflect"
+
+ "go.xyrillian.de/oblast/handle"
)
// PrepareThreshold is a tuning parameter for the strategy used by all methods of [Store] operating on batches of records provided by the caller
@@ -20,53 +21,17 @@ import (
// This tuning parameter defines the minimum number of records that will justify maintaining a prepared statement.
// Our benchmarking with the mattn/go-sqlite3 driver (and last checked with Go 1.26.2 on x86_64) indicates that this becomes a worthwhile investment at 8 or more records, so this is our default.
// If your benchmarking indicates a different tradeoff depending on your choice of Go version or SQL driver, you may adjust this variable accordingly.
+//
+// The actual effect of this setting is to control the value of the "repeated" argument in [Handle.Prepare].
var PrepareThreshold int = 8
-// preparedStatement behaves like sql.Stmt, but only uses *sql.Stmt when it is useful (see explanation above).
-type preparedStatement struct {
- db Handle
- query string
- stmt *sql.Stmt // nil for input sizes below PrepareThreshold
-}
-
// prepare behaves like [Handle.Prepare].
-func prepare(ctx context.Context, db Handle, query, operation string, inputSize int) (preparedStatement, error) {
+func prepare(ctx context.Context, db Handle, query, operation string, inputSize int) (handle.Statement, error) {
if query == "" {
- return preparedStatement{}, fmt.Errorf("cannot execute %s() because query could not be autogenerated", operation)
- }
-
- if inputSize < PrepareThreshold {
- return preparedStatement{db, query, nil}, nil
- }
- stmt, err := db.PrepareContext(ctx, query)
- if err != nil {
- return preparedStatement{}, fmt.Errorf("during Prepare(): %w", err)
+ return nil, fmt.Errorf("cannot execute %s() because query could not be autogenerated", operation)
}
- return preparedStatement{db, query, stmt}, nil
-}
-
-// Close behaves like [sql.Stmt.Close].
-func (s preparedStatement) Close() error {
- if s.stmt == nil {
- return nil
- }
- return s.stmt.Close()
-}
-// ExecContext behaves like [sql.Stmt.ExecContext].
-func (s preparedStatement) ExecContext(ctx context.Context, args ...any) (sql.Result, error) {
- if s.stmt == nil {
- return s.db.ExecContext(ctx, s.query, args...)
- }
- return s.stmt.ExecContext(ctx, args...)
-}
-
-// QueryRow behaves like [sql.Stmt.QueryRowContext].
-func (s preparedStatement) QueryRowContext(ctx context.Context, args ...any) *sql.Row {
- if s.stmt == nil {
- return s.db.QueryRowContext(ctx, s.query, args...)
- }
- return s.stmt.QueryRowContext(ctx, args...)
+ return db.Prepare(ctx, query, inputSize >= PrepareThreshold)
}
// Insert executes an SQL INSERT statement for each of the provided records.
@@ -89,7 +54,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 preparedStatement, db Handle, records []*R) error {
+func (s Store[R]) insertUsing(ctx context.Context, stmt handle.Statement, db 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.
@@ -111,7 +76,7 @@ func (s Store[R]) insertUsing(ctx context.Context, stmt preparedStatement, db Ha
return newIOError(nil, "Stmt.Close", stmt.Close())
}
-func insertRecord(ctx context.Context, v reflect.Value, recordIndex int, stmt preparedStatement, argumentIndexes [][]int, argumentSlots []any, scanIndexes [][]int, scanSlots []any) error {
+func insertRecord(ctx context.Context, v reflect.Value, recordIndex int, stmt handle.Statement, argumentIndexes [][]int, argumentSlots []any, scanIndexes [][]int, scanSlots []any) error {
for idx, index := range argumentIndexes {
argumentSlots[idx] = v.FieldByIndex(index).Interface()
}
@@ -124,10 +89,10 @@ func insertRecord(ctx context.Context, v reflect.Value, recordIndex int, stmt pr
}
var err error
if len(scanSlots) == 0 {
- _, err = stmt.ExecContext(ctx, argumentSlots...)
+ _, err = stmt.Exec(ctx, argumentSlots)
} else {
// TODO: using QueryRow for inserting is extremely expensive because database/sql allocates a Rows instance under the hood; other libraries are doing better by limiting themselves to ExecContext() + LastInsertId()
- err = stmt.QueryRowContext(ctx, argumentSlots...).Scan(scanSlots...)
+ err = stmt.QueryRow(ctx, argumentSlots, scanSlots)
}
if err != nil {
return fmt.Errorf("while inserting record with idx = %d: %w", recordIndex, err)
@@ -166,11 +131,11 @@ func (s Store[R]) Update(ctx context.Context, db Handle, records ...R) error {
return newIOError(nil, "Stmt.Close", stmt.Close())
}
-func updateRecord(ctx context.Context, v reflect.Value, recordIndex int, stmt preparedStatement, argumentIndexes [][]int, argumentSlots []any) (int64, error) {
+func updateRecord(ctx context.Context, v reflect.Value, recordIndex int, stmt handle.Statement, argumentIndexes [][]int, argumentSlots []any) (int64, error) {
for idx, index := range argumentIndexes {
argumentSlots[idx] = v.FieldByIndex(index).Interface()
}
- result, err := stmt.ExecContext(ctx, argumentSlots...)
+ result, err := stmt.Exec(ctx, argumentSlots)
if err != nil {
return 0, fmt.Errorf("while updating record with idx = %d: %w", recordIndex, err)
}
@@ -209,11 +174,11 @@ func (s Store[R]) Delete(ctx context.Context, db Handle, records ...R) error {
return newIOError(nil, "Stmt.Close", stmt.Close())
}
-func deleteRecord(ctx context.Context, v reflect.Value, recordIndex int, stmt preparedStatement, argumentIndexes [][]int, argumentSlots []any) error {
+func deleteRecord(ctx context.Context, v reflect.Value, recordIndex int, stmt handle.Statement, argumentIndexes [][]int, argumentSlots []any) error {
for idx, index := range argumentIndexes {
argumentSlots[idx] = v.FieldByIndex(index).Interface()
}
- _, err := stmt.ExecContext(ctx, argumentSlots...)
+ _, err := stmt.Exec(ctx, argumentSlots)
if err != nil {
return fmt.Errorf("while deleting record with idx = %d: %w", recordIndex, err)
}