aboutsummaryrefslogtreecommitdiff
path: root/plan.go
diff options
context:
space:
mode:
authorStefan Majewsky <majewsky@gmx.net>2026-07-17 19:35:55 +0200
committerStefan Majewsky <majewsky@gmx.net>2026-07-17 19:35:55 +0200
commit1383c6fbaa6b9e0b7cc5e44b74a905352d596758 (patch)
tree5aa59347be173f8c16ab9d8246e89def0a0aadcd /plan.go
parent3d1fce7b843891f789887655a9ce0c9ce1ace2de (diff)
downloadgo-oblast-1383c6fbaa6b9e0b7cc5e44b74a905352d596758.tar.gz
cache buildPlan results
Diffstat (limited to 'plan.go')
-rw-r--r--plan.go70
1 files changed, 58 insertions, 12 deletions
diff --git a/plan.go b/plan.go
index dbcc012..010569d 100644
--- a/plan.go
+++ b/plan.go
@@ -10,8 +10,66 @@ import (
"reflect"
"slices"
"strings"
+ "sync"
)
+// planOpts holds additional arguments to buildPlan().
+type planOpts struct {
+ StructTagKey string // defaults to "db"
+ TableName string
+ PrimaryKeyColumnNames []string
+}
+
+func collectPlanOptions(popts []PlanOption) planOpts {
+ opts := planOpts{
+ StructTagKey: "db",
+ }
+ for _, popt := range popts {
+ popt(&opts)
+ }
+ return opts
+}
+
+type planCacheKey struct {
+ Type reflect.Type
+ Dialect string
+ StructTagKey string
+ TableName string
+ PrimaryKeyColumnNames string
+}
+
+var (
+ generatedPlans = make(map[planCacheKey]plan)
+ generatedPlansMutex sync.RWMutex
+)
+
+// getOrBuildPlan is like [buildPlan], but caches generated plans and tries to reuse cached plans.
+func getOrBuildPlan(t reflect.Type, dialect Dialect, opts planOpts) (plan, error) {
+ key := planCacheKey{
+ Type: t,
+ Dialect: dialect.String(),
+ StructTagKey: opts.StructTagKey,
+ TableName: opts.TableName,
+ PrimaryKeyColumnNames: strings.Join(opts.PrimaryKeyColumnNames, "\000"),
+ }
+
+ generatedPlansMutex.RLock()
+ p, ok := generatedPlans[key]
+ generatedPlansMutex.RUnlock()
+ if ok {
+ return p, nil
+ }
+
+ p, err := buildPlan(t, dialect, opts)
+ if err != nil {
+ return plan{}, err
+ }
+ generatedPlansMutex.Lock()
+ generatedPlans[key] = p
+ generatedPlansMutex.Unlock()
+ return p, nil
+}
+
// plan holds all information that we can derive from reflecting on a given type.
// The queries held within are only valid within the context of a given SQL dialect.
type plan struct {
@@ -61,24 +119,12 @@ type plannedQuery struct {
ScanIndexes [][]int
}
-// planOpts holds additional arguments to buildPlan().
-type planOpts struct {
- StructTagKey string // defaults to "db"
- TableName string
- PrimaryKeyColumnNames []string
-}
-
// buildPlan creates a new plan for the given struct type.
func buildPlan(t reflect.Type, dialect Dialect, opts planOpts) (plan, error) {
if t.Kind() != reflect.Struct {
return plan{}, fmt.Errorf("expected struct type, but got kind %q", t.Kind().String())
}
- // apply defaults to planOpts fields
- if opts.StructTagKey == "" {
- opts.StructTagKey = "db"
- }
-
var p = plan{
TypeName: t.Name(),
TableName: opts.TableName,