aboutsummaryrefslogtreecommitdiff
path: root/query.go
diff options
context:
space:
mode:
Diffstat (limited to 'query.go')
-rw-r--r--query.go73
1 files changed, 37 insertions, 36 deletions
diff --git a/query.go b/query.go
index 73c2233..b9bc328 100644
--- a/query.go
+++ b/query.go
@@ -4,6 +4,7 @@
package oblast
import (
+ "context"
"database/sql"
"fmt"
"reflect"
@@ -29,7 +30,7 @@ type preparedStatement struct {
}
// prepare behaves like [Handle.Prepare].
-func prepare(db Handle, query, operation string, inputSize int) (preparedStatement, error) {
+func prepare(ctx context.Context, db Handle, query, operation string, inputSize int) (preparedStatement, error) {
if query == "" {
return preparedStatement{}, fmt.Errorf("cannot execute %s() because query could not be autogenerated", operation)
}
@@ -37,7 +38,7 @@ func prepare(db Handle, query, operation string, inputSize int) (preparedStateme
if inputSize < PrepareThreshold {
return preparedStatement{db, query, nil}, nil
}
- stmt, err := db.Prepare(query)
+ stmt, err := db.PrepareContext(ctx, query)
if err != nil {
return preparedStatement{}, fmt.Errorf("during Prepare(): %w", err)
}
@@ -52,20 +53,20 @@ func (s preparedStatement) Close() error {
return s.stmt.Close()
}
-// Exec behaves like [sql.Stmt.Exec].
-func (s preparedStatement) Exec(args ...any) (sql.Result, error) {
+// 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.Exec(s.query, args...)
+ return s.db.ExecContext(ctx, s.query, args...)
}
- return s.stmt.Exec(args...)
+ return s.stmt.ExecContext(ctx, args...)
}
-// QueryRow behaves like [sql.Stmt.QueryRow].
-func (s preparedStatement) QueryRow(args ...any) *sql.Row {
+// QueryRow behaves like [sql.Stmt.QueryRowContext].
+func (s preparedStatement) QueryRowContext(ctx context.Context, args ...any) *sql.Row {
if s.stmt == nil {
- return s.db.QueryRow(s.query, args...)
+ return s.db.QueryRowContext(ctx, s.query, args...)
}
- return s.stmt.QueryRow(args...)
+ return s.stmt.QueryRowContext(ctx, args...)
}
// Insert executes an SQL INSERT statement for each of the provided records.
@@ -77,18 +78,18 @@ func (s preparedStatement) QueryRow(args ...any) *sql.Row {
//
// 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.
-func (s Store[R]) Insert(db Handle, records ...*R) error {
+func (s Store[R]) Insert(ctx context.Context, 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.
- stmt, err := prepare(db, s.plan.Insert.Query, "Insert", len(records))
+ stmt, err := prepare(ctx, db, s.plan.Insert.Query, "Insert", len(records))
if err != nil {
return err
}
- return s.insertUsing(stmt, db, records)
+ return s.insertUsing(ctx, stmt, db, records)
}
-func (s Store[R]) insertUsing(stmt preparedStatement, db Handle, records []*R) error {
+func (s Store[R]) insertUsing(ctx context.Context, stmt preparedStatement, 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.
@@ -101,7 +102,7 @@ func (s Store[R]) insertUsing(stmt preparedStatement, db Handle, records []*R) e
for idx, r := range records {
v := reflect.ValueOf(r).Elem()
- err := insertRecord(v, idx, stmt, argumentIndexes, argumentSlots, scanIndexes, scanSlots)
+ err := insertRecord(ctx, v, idx, stmt, argumentIndexes, argumentSlots, scanIndexes, scanSlots)
if err != nil {
return newIOError(err, "Stmt.Close", stmt.Close())
}
@@ -110,7 +111,7 @@ func (s Store[R]) insertUsing(stmt preparedStatement, db Handle, records []*R) e
return newIOError(nil, "Stmt.Close", stmt.Close())
}
-func insertRecord(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 preparedStatement, argumentIndexes [][]int, argumentSlots []any, scanIndexes [][]int, scanSlots []any) error {
for idx, index := range argumentIndexes {
argumentSlots[idx] = v.FieldByIndex(index).Interface()
}
@@ -123,10 +124,10 @@ func insertRecord(v reflect.Value, recordIndex int, stmt preparedStatement, argu
}
var err error
if len(scanSlots) == 0 {
- _, err = stmt.Exec(argumentSlots...)
+ _, err = stmt.ExecContext(ctx, argumentSlots...)
} else {
- // TODO: using QueryRow for inserting is somehow extremely expensive in terms of allocs; other libraries are doing better by limiting themselves to Exec() + LastInsertId()
- err = stmt.QueryRow(argumentSlots...).Scan(scanSlots...)
+ // 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...)
}
if err != nil {
return fmt.Errorf("while inserting record with idx = %d: %w", recordIndex, err)
@@ -138,11 +139,11 @@ func insertRecord(v reflect.Value, recordIndex int, stmt preparedStatement, argu
// 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(db Handle, records ...R) error {
+func (s Store[R]) Update(ctx context.Context, 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.
- stmt, err := prepare(db, s.plan.Update.Query, "Update", len(records))
+ stmt, err := prepare(ctx, db, s.plan.Update.Query, "Update", len(records))
if err != nil {
return err
}
@@ -154,7 +155,7 @@ func (s Store[R]) Update(db Handle, records ...R) error {
for idx := range records {
v := reflect.ValueOf(&records[idx]).Elem()
- rowsAffected, err := updateRecord(v, idx, stmt, argumentIndexes, argumentSlots)
+ rowsAffected, err := updateRecord(ctx, v, idx, stmt, argumentIndexes, argumentSlots)
if err == nil && rowsAffected == 0 {
err = MissingRecordError[R]{records[idx], s.plan}
}
@@ -165,11 +166,11 @@ func (s Store[R]) Update(db Handle, records ...R) error {
return newIOError(nil, "Stmt.Close", stmt.Close())
}
-func updateRecord(v reflect.Value, recordIndex int, stmt preparedStatement, argumentIndexes [][]int, argumentSlots []any) (int64, error) {
+func updateRecord(ctx context.Context, v reflect.Value, recordIndex int, stmt preparedStatement, argumentIndexes [][]int, argumentSlots []any) (int64, error) {
for idx, index := range argumentIndexes {
argumentSlots[idx] = v.FieldByIndex(index).Interface()
}
- result, err := stmt.Exec(argumentSlots...)
+ result, err := stmt.ExecContext(ctx, argumentSlots...)
if err != nil {
return 0, fmt.Errorf("while updating record with idx = %d: %w", recordIndex, err)
}
@@ -183,11 +184,11 @@ func updateRecord(v reflect.Value, recordIndex int, stmt preparedStatement, argu
// 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(db Handle, records ...R) error {
+func (s Store[R]) Delete(ctx context.Context, 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.
- stmt, err := prepare(db, s.plan.Delete.Query, "Delete", len(records))
+ stmt, err := prepare(ctx, db, s.plan.Delete.Query, "Delete", len(records))
if err != nil {
return err
}
@@ -199,7 +200,7 @@ func (s Store[R]) Delete(db Handle, records ...R) error {
for idx := range records {
v := reflect.ValueOf(&records[idx]).Elem()
- err := deleteRecord(v, idx, stmt, argumentIndexes, argumentSlots)
+ err := deleteRecord(ctx, v, idx, stmt, argumentIndexes, argumentSlots)
if err != nil {
return newIOError(err, "Stmt.Close", stmt.Close())
}
@@ -208,11 +209,11 @@ func (s Store[R]) Delete(db Handle, records ...R) error {
return newIOError(nil, "Stmt.Close", stmt.Close())
}
-func deleteRecord(v reflect.Value, recordIndex int, stmt preparedStatement, argumentIndexes [][]int, argumentSlots []any) error {
+func deleteRecord(ctx context.Context, v reflect.Value, recordIndex int, stmt preparedStatement, argumentIndexes [][]int, argumentSlots []any) error {
for idx, index := range argumentIndexes {
argumentSlots[idx] = v.FieldByIndex(index).Interface()
}
- _, err := stmt.Exec(argumentSlots...)
+ _, err := stmt.ExecContext(ctx, argumentSlots...)
if err != nil {
return fmt.Errorf("while deleting record with idx = %d: %w", recordIndex, err)
}
@@ -227,24 +228,24 @@ func deleteRecord(v reflect.Value, recordIndex int, stmt preparedStatement, argu
// 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(db Handle, records ...*R) error {
+func (s Store[R]) Upsert(ctx context.Context, 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.
if len(s.plan.AutoColumnNames) == 0 {
- stmt, err := prepare(db, s.plan.Upsert.Query, "Upsert", len(records))
+ stmt, err := prepare(ctx, db, s.plan.Upsert.Query, "Upsert", len(records))
if err != nil {
return err
}
- return s.insertUsing(stmt, db, records)
+ return s.insertUsing(ctx, stmt, db, records)
}
// TODO: respect PrepareThreshold (or not? may be too much bookkeeping overhead for not a whole lot of benefit)
- insertStmt, err := prepare(db, s.plan.Insert.Query, "Insert", 0)
+ insertStmt, err := prepare(ctx, db, s.plan.Insert.Query, "Insert", 0)
if err != nil {
return err
}
- updateStmt, err := prepare(db, s.plan.Update.Query, "Update", 0)
+ updateStmt, err := prepare(ctx, db, s.plan.Update.Query, "Update", 0)
if err != nil {
return err
}
@@ -266,10 +267,10 @@ func (s Store[R]) Upsert(db Handle, records ...*R) error {
}
if isInsert {
- err = insertRecord(v, idx, insertStmt, insertArgumentIndexes, insertArgumentSlots, insertScanIndexes, insertScanSlots)
+ err = insertRecord(ctx, v, idx, insertStmt, insertArgumentIndexes, insertArgumentSlots, insertScanIndexes, insertScanSlots)
} else {
var rowsAffected int64
- rowsAffected, err = updateRecord(v, idx, updateStmt, updateArgumentIndexes, updateArgumentSlots)
+ rowsAffected, err = updateRecord(ctx, v, idx, updateStmt, updateArgumentIndexes, updateArgumentSlots)
if err == nil && rowsAffected == 0 {
err = MissingRecordError[R]{*r, s.plan}
}