From 1383c6fbaa6b9e0b7cc5e44b74a905352d596758 Mon Sep 17 00:00:00 2001 From: Stefan Majewsky Date: Fri, 17 Jul 2026 19:35:55 +0200 Subject: cache buildPlan results --- plan.go | 70 ++++++++++++++++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 58 insertions(+), 12 deletions(-) (limited to 'plan.go') 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, -- cgit v1.3.1