aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--query.go103
1 files changed, 54 insertions, 49 deletions
diff --git a/query.go b/query.go
index f8f8edd..bf1ea2d 100644
--- a/query.go
+++ b/query.go
@@ -5,7 +5,6 @@ package oblast
import (
"database/sql"
- "errors"
"fmt"
"reflect"
)
@@ -30,7 +29,11 @@ type preparedStatement struct {
}
// prepare behaves like [Handle.Prepare].
-func prepare(db Handle, query string, inputSize int) (preparedStatement, error) {
+func prepare(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)
+ }
+
if inputSize < PrepareThreshold {
return preparedStatement{db, query, nil}, nil
}
@@ -77,9 +80,13 @@ func (s Store[R]) Insert(db Handle, records ...R) (returnedRecords []R, returned
// Any expression that does not depend on type R should be factored out into a reusable function.
// TODO: minimize
- if s.plan.Insert.Query == "" {
- return nil, errors.New("cannot execute Insert() because query could not be autogenerated")
+ stmt, err := prepare(db, s.plan.Insert.Query, "Insert", len(records))
+ if err != nil {
+ return nil, err
}
+ defer func() {
+ returnedError = newIOError(returnedError, "Stmt.Close", stmt.Close())
+ }()
var (
argumentIndexes = s.plan.Insert.ArgumentIndexes
@@ -91,14 +98,6 @@ func (s Store[R]) Insert(db Handle, records ...R) (returnedRecords []R, returned
scanSlots = make([]any, len(scanIndexes))
}
- stmt, err := prepare(db, s.plan.Insert.Query, len(records))
- if err != nil {
- return nil, err
- }
- defer func() {
- returnedError = newIOError(returnedError, "Stmt.Close", stmt.Close())
- }()
-
for idx := range records {
v := reflect.ValueOf(&records[idx]).Elem()
for idx, index := range argumentIndexes {
@@ -141,8 +140,12 @@ func (s Store[R]) Insert(db Handle, records ...R) (returnedRecords []R, returned
//
// 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) (returnedError error) {
- if s.plan.Update.Query == "" {
- return errors.New("cannot execute Update() because query could not be autogenerated")
+ // 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))
+ if err != nil {
+ return err
}
var (
@@ -150,32 +153,32 @@ func (s Store[R]) Update(db Handle, records ...R) (returnedError error) {
argumentSlots = make([]any, len(argumentIndexes))
)
- stmt, err := prepare(db, s.plan.Update.Query, len(records))
- if err != nil {
- return err
- }
- defer func() {
- returnedError = newIOError(returnedError, "Stmt.Close", stmt.Close())
- }()
-
for idx, r := range records {
v := reflect.ValueOf(&r).Elem()
- for idx, index := range argumentIndexes {
- argumentSlots[idx] = v.FieldByIndex(index).Addr().Interface()
+ rowsAffected, err := updateRecord(v, idx, stmt, argumentIndexes, argumentSlots)
+ if err == nil && rowsAffected == 0 {
+ err = MissingRecordError[R]{r, s.plan}
}
- result, err := stmt.Exec(argumentSlots...)
if err != nil {
- return fmt.Errorf("during Exec() for record with idx = %d: %w", idx, err)
- }
- rowsAffected, err := result.RowsAffected()
- if err != nil {
- return fmt.Errorf("during RowsAffected() for record with idx = %d: %w", idx, err)
- }
- if rowsAffected == 0 {
- return MissingRecordError[R]{r, s.plan}
+ return newIOError(err, "Stmt.Close", stmt.Close())
}
}
- return nil
+ return newIOError(nil, "Stmt.Close", stmt.Close())
+}
+
+func updateRecord(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...)
+ if err != nil {
+ return 0, fmt.Errorf("during Exec() for record with idx = %d: %w", recordIndex, err)
+ }
+ rowsAffected, err := result.RowsAffected()
+ if err != nil {
+ return 0, fmt.Errorf("during RowsAffected() for record with idx = %d: %w", recordIndex, err)
+ }
+ return rowsAffected, nil
}
// Delete executes an SQL DELETE statement for each of the provided records, using their primary keys to locate the respective table rows.
@@ -186,8 +189,9 @@ func (s Store[R]) Delete(db Handle, records ...R) (returnedError error) {
// Any expression that does not depend on type R should be factored out into a reusable function.
// TODO: minimize
- if s.plan.Delete.Query == "" {
- return errors.New("cannot execute Delete() because query could not be autogenerated")
+ stmt, err := prepare(db, s.plan.Delete.Query, "Delete", len(records))
+ if err != nil {
+ return err
}
var (
@@ -195,23 +199,24 @@ func (s Store[R]) Delete(db Handle, records ...R) (returnedError error) {
argumentSlots = make([]any, len(argumentIndexes))
)
- stmt, err := prepare(db, s.plan.Delete.Query, len(records))
- if err != nil {
- return err
- }
- defer func() {
- returnedError = newIOError(returnedError, "Stmt.Close", stmt.Close())
- }()
-
for idx, r := range records {
v := reflect.ValueOf(&r).Elem()
- for idx, index := range argumentIndexes {
- argumentSlots[idx] = v.FieldByIndex(index).Addr().Interface()
- }
- _, err := stmt.Exec(argumentSlots...)
+ err := deleteRecord(v, idx, stmt, argumentIndexes, argumentSlots)
if err != nil {
- return fmt.Errorf("during Exec() for record with idx = %d: %w", idx, err)
+ return newIOError(err, "Stmt.Close", stmt.Close())
}
}
+
+ return newIOError(nil, "Stmt.Close", stmt.Close())
+}
+
+func deleteRecord(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...)
+ if err != nil {
+ return fmt.Errorf("during Exec() for record with idx = %d: %w", recordIndex, err)
+ }
return nil
}