From 11a3af04cd675667d22e8e88e4253aa2a76d6db6 Mon Sep 17 00:00:00 2001 From: jaxxjj Date: Thu, 11 Jun 2026 16:32:17 +0800 Subject: [PATCH] Skip wpgx cache invalidation when DML affects zero rows The exec/execrows/execresult invalidation hooks run unconditionally: an UPDATE guarded by a WHERE that matches nothing still evicts every listed cache key. On hot write paths this evicts entries that back unrelated hot reads, forcing them to the database for no benefit. Gate the PostExec hook on the command tag: skip when RowsAffected() is zero and the statement is row-level DML. The DML-type check keeps zero-row-tag commands that do mutate state (e.g. TRUNCATE) invalidating. Queries without an invalidate annotation generate byte-identical code, and no signatures change. --- .../codegen/golang/templates/wpgx/queryCode.tmpl | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/internal/codegen/golang/templates/wpgx/queryCode.tmpl b/internal/codegen/golang/templates/wpgx/queryCode.tmpl index a2305372d0..27e47e8060 100644 --- a/internal/codegen/golang/templates/wpgx/queryCode.tmpl +++ b/internal/codegen/golang/templates/wpgx/queryCode.tmpl @@ -237,11 +237,17 @@ func (q *Queries) {{.MethodName}}(ctx context.Context, {{.Arg.Pair}} {{.Invalida qctx, cancel := context.WithTimeout(ctx, time.Millisecond * {{.Option.Timeout.Milliseconds}}) defer cancel() {{- end}} - _, err := q.db.WExec(qctx, "{{.UniqueLabel}}", {{.ConstantName}}, {{.Arg.Params}}) + {{ if .Option.Invalidates }}cmd{{ else }}_{{ end }}, err := q.db.WExec(qctx, "{{.UniqueLabel}}", {{.ConstantName}}, {{.Arg.Params}}) if err != nil { return err } {{ if .Option.Invalidates -}} + // Zero-row DML changed nothing: cached entries are still valid, skip + // invalidation. Non-row commands (e.g. TRUNCATE) report zero rows but + // do mutate state, so they always invalidate. + if cmd.RowsAffected() == 0 && (cmd.Insert() || cmd.Update() || cmd.Delete()) { + return nil + } // invalidate _ = q.db.PostExec(func() error { if q.cache == nil { @@ -290,6 +296,10 @@ func (q *Queries) {{.MethodName}}(ctx context.Context, {{.Arg.Pair}} {{.Invalida return 0, err } {{ if .Option.Invalidates -}} + // Zero-row DML changed nothing: skip invalidation (see :exec). + if result.RowsAffected() == 0 && (result.Insert() || result.Update() || result.Delete()) { + return 0, nil + } // invalidate _ = q.db.PostExec(func() error { if q.cache == nil { @@ -338,6 +348,10 @@ func (q *Queries) {{.MethodName}}(ctx context.Context, {{.Arg.Pair}} {{.Invalida return rv, err } {{ if .Option.Invalidates -}} + // Zero-row DML changed nothing: skip invalidation (see :exec). + if rv.RowsAffected() == 0 && (rv.Insert() || rv.Update() || rv.Delete()) { + return rv, nil + } // invalidate _ = q.db.PostExec(func() error { if q.cache == nil {