diff options
| author | Stefan Majewsky <majewsky@gmx.net> | 2026-04-18 17:10:34 +0200 |
|---|---|---|
| committer | Stefan Majewsky <majewsky@gmx.net> | 2026-04-18 17:10:34 +0200 |
| commit | f8904431aa09796dab3c0bd159af74f8da4a153e (patch) | |
| tree | baee66ed5e4a262d60abac4a72d141b6a621c922 /select.go | |
| parent | 5afee58195a779e3c00432b1ecb9cce6c30b11d2 (diff) | |
| download | go-oblast-f8904431aa09796dab3c0bd159af74f8da4a153e.tar.gz | |
add type PreparedSelectQuery
Diffstat (limited to 'select.go')
| -rw-r--r-- | select.go | 75 |
1 files changed, 73 insertions, 2 deletions
@@ -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 +} |
