Skip to content

Commit

Permalink
support pointer-type (nullable) query parameters for cache and invali…
Browse files Browse the repository at this point in the history
…dation
  • Loading branch information
Stumble committed Feb 18, 2023
1 parent 40e5d68 commit 0727b2d
Show file tree
Hide file tree
Showing 4 changed files with 56 additions and 28 deletions.
28 changes: 25 additions & 3 deletions GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -581,15 +603,15 @@ 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 | | |
# \d test2
Table "public.test2"
Column | Type | Collation | Nullable | Default
------------+---------+-----------+----------+------------------------------
------------|---------|-----------|----------|------------------------------
id | integer | | not null | generated always as identity
CamelCase | integer | | |
snake_case | integer | | |
Expand Down
37 changes: 16 additions & 21 deletions internal/codegen/golang/query.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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.
Expand All @@ -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
Expand All @@ -247,27 +241,28 @@ 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")
}
prefix := q.CacheUniqueLabel()
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 {
Expand Down
12 changes: 8 additions & 4 deletions internal/codegen/golang/result.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
7 changes: 7 additions & 0 deletions internal/codegen/golang/templates/wpgx/queryCode.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -333,6 +333,13 @@ func hashIfLong(v string) string {
return v
}

func ptrStr[T any](v *T) string {
if v == nil {
return "<nil>"
}
return fmt.Sprintf("%+v", *v)
}

// eliminate unused error
var _ = log.Logger
var _ = fmt.Sprintf("")
Expand Down

0 comments on commit 0727b2d

Please sign in to comment.