Research: sqlc-gen-zombiezen types:many — fixable or workaround? #7

Closed
opened 2026-08-06 16:45:54 +00:00 by ginjiruu · 2 comments
Owner

Question

Is the sqlc-gen-zombiezen types:many IN-clause behavior fixable, or is the ReadAll + Go-filter workaround the answer?

WHERE id IN (/* types:many */?) generates a single string param instead of a usable list, so multi-ID IN lookups are broken (go-template inherits the same quirk). The generator is sqlc-gen-zombiezen — a third-party codegen plugin. The handoff's workaround is ReadAllBoards + Go-side filtering.

Research this: is the generator patchable/forkable (is its source available, does it accept the full list type?), and what would adopting a fork in tools/'s sqlc tool block cost? Or is the workaround the sanctioned answer and the toolbelt should codify it (helper, docs)?

The decision: fix upstream/fork, or make the workaround canonical. Resolve with a /research subagent on a throwaway research/<name> branch, then write the plan to docs/plans/.

## Question Is the `sqlc-gen-zombiezen` `types:many` IN-clause behavior fixable, or is the ReadAll + Go-filter workaround the answer? `WHERE id IN (/* types:many */?)` generates a single `string` param instead of a usable list, so multi-ID IN lookups are broken (go-template inherits the same quirk). The generator is `sqlc-gen-zombiezen` — a third-party codegen plugin. The handoff's workaround is `ReadAllBoards` + Go-side filtering. Research this: is the generator patchable/forkable (is its source available, does it accept the full list type?), and what would adopting a fork in `tools/`'s sqlc tool block cost? Or is the workaround the sanctioned answer and the toolbelt should codify it (helper, docs)? The decision: fix upstream/fork, or make the workaround canonical. Resolve with a `/research` subagent on a throwaway `research/<name>` branch, then write the plan to `docs/plans/`.
Author
Owner

RESOLVED — research complete.

Answer: fixable — via a one-line query annotation, NOT a plugin fork and NOT the workaround.

Key findings:

  • /* types:many */ is not a sqlc annotation — it appears in zero sqlc source files (v1.31.1). sqlc only marks a param as a slice via the documented sqlc.slice('ids') meta-function, which rewrites to the /*SLICE:ids*/? sentinel the zombiezen plugin already expands.
  • The plugin (github.com/delaneyj/toolbelt v0.9.1, already installed and latest) has full, correct slice support. Verified end-to-end in a scratch repro: WHERE id IN (sqlc.slice('ids'))Run(ids []string), placeholder expansion, per-element binding — runtime test passed. Empty slice degrades to IN (NULL) → 0 rows (friendlier than stock sqlc).
  • The only affected query is go-template/internal/db/queries/expenses.sql:67 (ReadExpensesByIdsOrdered). The toolbelt example and ttrpg have no IN clauses.
  • Do not fork (high cost, zero benefit). Do not keep the ReadAll+Go-filter workaround as canonical — it was a symptom of the mis-annotation, now a needless full-table scan.

Decision: use sqlc.slice('name') for any IN-clause; fix the one consumer query on migration (consumer, out of scope); the ReadAll+filter workaround demotes to a documented fallback for truly dynamic predicates.

Separate findings for later tickets: sqlc-gen-zombiezen is unpinned (resolved from PATH, not in any tool block) — a reproducibility gap; and two genuine but unrelated upstream plugin bugs ({{.Name.Lower}} leaked in error strings; CRUD time-import, upstream #26).

Full findings: docs/research/sqlc-types-many.md on branch research/sqlc-types-many (commit aad089d).

**RESOLVED — research complete.** **Answer: fixable — via a one-line query annotation, NOT a plugin fork and NOT the workaround.** Key findings: - `/* types:many */` is **not** a sqlc annotation — it appears in zero sqlc source files (v1.31.1). sqlc only marks a param as a slice via the documented `sqlc.slice('ids')` meta-function, which rewrites to the `/*SLICE:ids*/?` sentinel the zombiezen plugin already expands. - The plugin (`github.com/delaneyj/toolbelt` v0.9.1, already installed and latest) has **full, correct slice support**. Verified end-to-end in a scratch repro: `WHERE id IN (sqlc.slice('ids'))` → `Run(ids []string)`, placeholder expansion, per-element binding — runtime test passed. Empty slice degrades to `IN (NULL)` → 0 rows (friendlier than stock sqlc). - The only affected query is `go-template/internal/db/queries/expenses.sql:67` (`ReadExpensesByIdsOrdered`). The toolbelt `example` and `ttrpg` have **no** IN clauses. - **Do not fork** (high cost, zero benefit). **Do not keep the ReadAll+Go-filter workaround as canonical** — it was a symptom of the mis-annotation, now a needless full-table scan. Decision: use `sqlc.slice('name')` for any IN-clause; fix the one consumer query on migration (consumer, out of scope); the ReadAll+filter workaround demotes to a documented fallback for truly dynamic predicates. Separate findings for later tickets: `sqlc-gen-zombiezen` is **unpinned** (resolved from PATH, not in any tool block) — a reproducibility gap; and two genuine but unrelated upstream plugin bugs (`{{.Name.Lower}}` leaked in error strings; CRUD `time`-import, upstream #26). Full findings: `docs/research/sqlc-types-many.md` on branch `research/sqlc-types-many` (commit `aad089d`).
Author
Owner

Research resolved: fixable via sqlc.slice() annotation, no fork. Full findings on research/sqlc-types-many branch.

Research resolved: fixable via sqlc.slice() annotation, no fork. Full findings on research/sqlc-types-many branch.
Sign in to join this conversation.
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
ginjiruu/toolbelt#7
No description provided.