aboutsummaryrefslogtreecommitdiff
path: root/select.go
diff options
context:
space:
mode:
authorStefan Majewsky <majewsky@gmx.net>2026-04-18 17:10:34 +0200
committerStefan Majewsky <majewsky@gmx.net>2026-04-18 17:10:34 +0200
commitf8904431aa09796dab3c0bd159af74f8da4a153e (patch)
treebaee66ed5e4a262d60abac4a72d141b6a621c922 /select.go
parent5afee58195a779e3c00432b1ecb9cce6c30b11d2 (diff)
downloadgo-oblast-f8904431aa09796dab3c0bd159af74f8da4a153e.tar.gz
add type PreparedSelectQuery
Diffstat (limited to 'select.go')
-rw-r--r--select.go75
1 files changed, 73 insertions, 2 deletions
diff --git a/select.go b/select.go
index 5e4ab42..2cd1e44 100644
--- a/select.go
+++ b/select.go
@@ -153,7 +153,8 @@ func (s Store[R]) SelectOne(db Handle, query string, args ...any) (result R, err
// SelectOneWhere is like [Store.SelectOne], but you only provide the part of the SELECT query that comes after the WHERE.
// See [Store.SelectWhere] for an explanation of how the full query is constructed from this partial query.
//
-// This method is more efficient than [Store.SelectOne] on CPU runtime, but has a slight memory allocation overhead.
+// 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(db Handle, partialQuery string, args ...any) (result R, err 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.
@@ -167,6 +168,10 @@ func selectOneWhere(db Handle, plan plan, v reflect.Value, partialQuery string,
return errors.New("cannot execute SelectOneWhere() because query could not be autogenerated")
}
query := plan.Select.Query + partialQuery
+ return selectOne(db, plan, v, query, args)
+}
+
+func selectOne(db Handle, plan plan, v reflect.Value, query string, args []any) error {
for _, index := range plan.IndexesOfTransparentPointerStructs {
f := v.FieldByIndex(index)
f.Set(reflect.New(f.Type().Elem()))
@@ -178,4 +183,70 @@ func selectOneWhere(db Handle, plan plan, v reflect.Value, partialQuery string,
return db.QueryRow(query, args...).Scan(slots...)
}
-// TODO: variant of SelectWhere/SelectOneWhere that has the full query precomputed
+// PrepareSelectQueryWhere performs the same query string preparation as [Store.SelectWhere] or [Store.SelectOneWhere].
+// The resulting query can then be executed multiple times without incurring repeated memory allocation overhead from this preparation step.
+func (s Store[R]) PrepareSelectQueryWhere(partialQuery string) (PreparedSelectQuery[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.
+
+ query, err := prepareSelectQueryWhere(s.plan, partialQuery)
+ return PreparedSelectQuery[R]{s, query}, err
+}
+
+// MustPrepareSelectQueryWhere is like [Store.PrepareSelectQueryWhere], but panics on error.
+func (s Store[R]) MustPrepareSelectQueryWhere(partialQuery string) PreparedSelectQuery[R] {
+ q, err := s.PrepareSelectQueryWhere(partialQuery)
+ if err != nil {
+ panic(err.Error())
+ }
+ return q
+}
+
+func prepareSelectQueryWhere(plan plan, partialQuery string) (string, error) {
+ if plan.Select.Query == "" {
+ return "", errors.New("cannot execute PrepareSelectQueryWhere() because query could not be autogenerated")
+ }
+ return plan.Select.Query + partialQuery, nil
+}
+
+// PreparedSelectQuery holds a pre-computed SELECT query that was customized by the user.
+// This type is an optimization to avoid performing the same query string manipulations over and over again in hot paths.
+//
+// It is returned by [Store.PrepareSelectQueryWhere].
+type PreparedSelectQuery[R any] struct {
+ store Store[R]
+ query string
+}
+
+// Select behaves the same as [Store.SelectWhere], but uses the query that was precomputed when q was constructed.
+func (q PreparedSelectQuery[R]) Select(db 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.
+
+ rows, indexes, err := startSelectQuery(db, q.store.plan, q.query, args...)
+ if err != nil {
+ return nil, err
+ }
+
+ var result []R
+ slots := make([]any, len(indexes))
+ for rows.Next() {
+ var target R
+ err = collectRow(rows, q.store.plan, reflect.ValueOf(&target).Elem(), slots, indexes)
+ if err != nil {
+ return nil, err
+ }
+ result = append(result, target)
+ }
+
+ return result, newIOError(err, "Rows.Err", rows.Err())
+}
+
+// SelectOne behaves the same as [Store.SelectOneWhere], but uses the query that was precomputed when q was constructed.
+func (q PreparedSelectQuery[R]) SelectOne(db Handle, args ...any) (result R, err 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.
+
+ err = selectOne(db, q.store.plan, reflect.ValueOf(&result).Elem(), q.query, args)
+ return
+}