diff options
Diffstat (limited to 'query.go')
| -rw-r--r-- | query.go | 65 |
1 files changed, 15 insertions, 50 deletions
@@ -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) } |
