diff --git a/GUIDE.md b/GUIDE.md index 2c0fbdce5b..7940b7cbb1 100644 --- a/GUIDE.md +++ b/GUIDE.md @@ -18,7 +18,7 @@ as this combo. Production versions: -+ sqlc: v2.1.3-wicked-fork ++ sqlc: v2.1.4-wicked-fork + dcache: v0.1.0 + wgpx: v0.1.8 @@ -493,6 +493,28 @@ WHERE id = sqlc.arg('id'); ``` +##### Versatile query + +Although it is **not** recommended, you can use `coalesce` function and `sqlc.narg` +to build a versatile query that filter rows based different sets of conditions. + +```sql +-- NOTE: dummy is a null-able column. +-- name: GetBookBySpec :one +-- -- cache : 10m +SELECT * FROM books WHERE + name LIKE coalesce(sqlc.narg('name'), name) AND + price = coalesce(sqlc.narg('price'), price) AND + (sqlc.narg('dummy')::int is NULL or dummy_field = sqlc.narg('dummy')); +``` + +However, please note that you **CANNOT** apply this trick on null-able columns. +The reason is: null never equals to null. In the above example, if we change the cond around +'dummy' column to `dummy = coalesce(sqlc.narg('dummy'), dummy)`, all rows will be filtered out +when `sqlc.narg('dummy')` is substituted by `null`. +The correct way to shown in the example: instead of use 'coalesce', explicitly check if the value +is null. + ##### Refresh materialized view Refresh statement is supported, you can just list it as a query. @@ -581,7 +603,7 @@ original camcal-case style. See below logs from psql. # \d test Table "public.test" Column | Type | Collation | Nullable | Default -------------+---------+-----------+----------+------------------------------ +------------|---------|-----------|----------|------------------------------ id | integer | | not null | generated always as identity camelcase | integer | | | snake_case | integer | | | @@ -589,7 +611,7 @@ original camcal-case style. See below logs from psql. # \d test2 Table "public.test2" Column | Type | Collation | Nullable | Default -------------+---------+-----------+----------+------------------------------ +------------|---------|-----------|----------|------------------------------ id | integer | | not null | generated always as identity CamelCase | integer | | | snake_case | integer | | | diff --git a/internal/codegen/golang/query.go b/internal/codegen/golang/query.go index 615cfbe79d..eedc1fe309 100644 --- a/internal/codegen/golang/query.go +++ b/internal/codegen/golang/query.go @@ -161,15 +161,12 @@ func (v QueryValue) CacheKeySprintf() string { format := make([]string, 0) args := make([]string, 0) for _, f := range v.Struct.Fields { - // TODO(yumin): pointer not supported for now. - if strings.Contains(f.Type, "*") { - panic(fmt.Errorf("pointer arguments query cache not supported: %+v", v)) - } - // if strings.HasPrefix(f.Type, "[]*") { - // panic(fmt.Errorf("[]*T typed arguments query cache not supported: %+v", v)) - // } format = append(format, "%+v") - args = append(args, v.Name+"."+f.Name) + if strings.HasPrefix(f.Type, "*") { + args = append(args, wrapPtrStr(v.Name+"."+f.Name)) + } else { + args = append(args, v.Name+"."+f.Name) + } } formatStr := `"` + strings.Join(format, ",") + `"` if len(args) <= 3 { @@ -215,7 +212,7 @@ func (q Query) TableIdentifier() string { // CacheKey is used by WPgx only. func (q Query) CacheKey() string { - return genCacheKeyWithArgName(q, q.Arg.Name, q.Arg.IsTypePointer()) + return genCacheKeyWithArgName(q, q.Arg.Name) } // InvalidateArgs is used by WPgx only. @@ -228,10 +225,7 @@ func (q Query) InvalidateArgs() string { if inv.NoArg { continue } - t := inv.Q.Arg.Type() - if !inv.Q.Arg.IsPointer() && !strings.HasPrefix(t, "[]") { - t = "*" + t - } + t := "*" + inv.Q.Arg.Type() rv += fmt.Sprintf("%s %s,", inv.ArgName, t) } return rv @@ -247,7 +241,7 @@ func (q Query) CacheUniqueLabel() string { return fmt.Sprintf("%s:%s:", q.Pkg, q.MethodName) } -func genCacheKeyWithArgName(q Query, argName string, genPointerArgType bool) string { +func genCacheKeyWithArgName(q Query, argName string) string { if len(q.Pkg) == 0 { panic("empty pkg name is invalid") } @@ -255,19 +249,20 @@ func genCacheKeyWithArgName(q Query, argName string, genPointerArgType bool) str if q.Arg.isEmpty() { return `"` + prefix + `"` } + // when it's non-struct parameter, generate inline fmt.Sprintf. if q.Arg.Struct == nil { - return fmt.Sprintf("\"%s\" + %s", - prefix, singleArgCacheSprintf(argName, genPointerArgType)) + if q.Arg.IsTypePointer() { + argName = wrapPtrStr(argName) + } + fmtStr := `hashIfLong(fmt.Sprintf("%+v",` + argName + `))` + return fmt.Sprintf("\"%s\" + %s", prefix, fmtStr) } else { return argName + `.CacheKey()` } } -func singleArgCacheSprintf(argName string, isPointer bool) string { - if isPointer { - argName = "*" + argName - } - return `hashIfLong(fmt.Sprintf("%+v",` + argName + `))` +func wrapPtrStr(v string) string { + return fmt.Sprintf("ptrStr(%s)", v) } type InvalidateParam struct { diff --git a/internal/codegen/golang/result.go b/internal/codegen/golang/result.go index 6afef25b11..a98f824d6a 100644 --- a/internal/codegen/golang/result.go +++ b/internal/codegen/golang/result.go @@ -303,16 +303,20 @@ func buildQueryInvalidates(queries []Query) error { mutation.Invalidates = append(mutation.Invalidates, InvalidateParam{ Q: query, NoArg: true, - CacheKey: genCacheKeyWithArgName(*query, "", false), // string key + CacheKey: genCacheKeyWithArgName(*query, ""), // string key }) } else { if query.Arg.IsTypePointer() { - return fmt.Errorf( - "invalidate pointer-typed argument is not supported: %s tries to invalidate %s", + err := fmt.Errorf( + "Although invalidate pointer-typed argument is supported (%s tries to invalidate %s) , the generated type will be **T", mutation.MethodName, query.MethodName) + fmt.Printf("WARNING: %s\n", err) } argName := unamer.UniqueName(methodName) - cacheKey := genCacheKeyWithArgName(*query, argName, true) + // additional pointer will be added to invalidate query key, + // so when we generate cache key, add 1 additional deref. + derefArgName := fmt.Sprintf("(*%s)", argName) + cacheKey := genCacheKeyWithArgName(*query, derefArgName) mutation.Invalidates = append(mutation.Invalidates, InvalidateParam{ Q: query, ArgName: argName, diff --git a/internal/codegen/golang/templates/wpgx/queryCode.tmpl b/internal/codegen/golang/templates/wpgx/queryCode.tmpl index 2b99ed3f4e..7fb715d735 100644 --- a/internal/codegen/golang/templates/wpgx/queryCode.tmpl +++ b/internal/codegen/golang/templates/wpgx/queryCode.tmpl @@ -333,6 +333,13 @@ func hashIfLong(v string) string { return v } +func ptrStr[T any](v *T) string { + if v == nil { + return "" + } + return fmt.Sprintf("%+v", *v) +} + // eliminate unused error var _ = log.Logger var _ = fmt.Sprintf("")