Commit a237740
fix(catalog): synthesize Postgres system columns (xmin, ctid, ...) (#3742)
Problem
-------
sqlc errored with "column does not exist" when a PostgreSQL query
referenced any of the six system columns (tableoid, xmin, cmin, xmax,
cmax, ctid) unless `database.managed: true` was configured. The 2023 fix
(#2871 / #1745) only added the columns inside the PostgreSQL analyzer,
which is initialized only when a live database is attached. Code-only
pipelines that build the catalog purely from schema files still hit the
same "column \"xmin\" does not exist" failure 17 months later (#3742).
Fix
---
Synthesize the six PostgreSQL system columns inside
`compiler.QueryCatalog.GetTable` whenever the engine is `postgresql`.
The catalog itself is left untouched (so MySQL/SQLite tables are not
polluted), and the synthesized columns are marked with a new
`Column.IsSystem` flag so `SELECT *` / `RETURNING *` expansion skips
them — matching PostgreSQL's own behavior. Explicit references like
`SELECT xmin, ctid FROM foo` and aliased forms like `a.xmin` resolve to
the correct types (oid/xid/cid -> pgtype.Uint32, tid -> pgtype.TID).
Changed files
-------------
- internal/compiler/query.go: add `Column.IsSystem`.
- internal/compiler/query_catalog.go: track engine on QueryCatalog;
append `pgSystemColumns` in GetTable when engine == postgresql.
- internal/compiler/expand.go: skip IsSystem columns in `*` expansion.
- internal/compiler/output_columns.go: skip IsSystem columns in the
output-columns `*` branch (mirror of expand.go).
- internal/endtoend/testdata/system_columns_3742/postgresql/pgx/v5/:
new fixture exercising all six system columns, a table alias, and
SELECT * (which must omit system columns). Golden files committed.
Tests
-----
- `go test ./internal/compiler/... ./internal/sql/... ./internal/engine/... ./internal/codegen/...` — all pass.
- `go test -run TestExamples ./internal/endtoend/` — passes.
- `go vet` + `gofmt -l` clean on touched files.
Local verification
------------------
Built `/tmp/sqlc-dev-3742` from this branch and ran it against the exact
schema + query in the issue. Before fix: `EXIT=1, column "xmin" does
not exist`. After fix: `EXIT=0`, generates `pgtype.Uint32` for xmin,
`pgtype.TID` for ctid. Confirmed `SELECT *` still expands to only the
user columns (id, name, bio) — system columns are not included.
What does NOT change
--------------------
- MySQL / SQLite catalogs are untouched.
- The managed-database analyzer path (#2871) is untouched and still
works for users on `database.managed: true`.
- `SELECT *` continues to omit system columns (PG-compatible).
- The existing `select_system` endtoend fixture (managed-db) is left
alone; the new fixture covers the non-managed code path.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>1 parent a3b0cfd commit a237740
10 files changed
Lines changed: 206 additions & 1 deletion
File tree
- internal
- compiler
- endtoend/testdata/system_columns_3742/postgresql/pgx/v5
- go
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
138 | 138 | | |
139 | 139 | | |
140 | 140 | | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
141 | 147 | | |
142 | 148 | | |
143 | 149 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
273 | 273 | | |
274 | 274 | | |
275 | 275 | | |
| 276 | + | |
| 277 | + | |
| 278 | + | |
| 279 | + | |
| 280 | + | |
276 | 281 | | |
277 | 282 | | |
278 | 283 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
39 | 39 | | |
40 | 40 | | |
41 | 41 | | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
42 | 48 | | |
43 | 49 | | |
44 | 50 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
3 | 3 | | |
4 | 4 | | |
5 | 5 | | |
| 6 | + | |
6 | 7 | | |
7 | 8 | | |
8 | 9 | | |
| |||
12 | 13 | | |
13 | 14 | | |
14 | 15 | | |
| 16 | + | |
15 | 17 | | |
16 | 18 | | |
17 | 19 | | |
| |||
28 | 30 | | |
29 | 31 | | |
30 | 32 | | |
31 | | - | |
| 33 | + | |
32 | 34 | | |
33 | 35 | | |
34 | 36 | | |
| |||
90 | 92 | | |
91 | 93 | | |
92 | 94 | | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
93 | 105 | | |
94 | 106 | | |
95 | 107 | | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
96 | 132 | | |
97 | 133 | | |
98 | 134 | | |
| |||
Lines changed: 32 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 15 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 79 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 8 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
Lines changed: 5 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
Lines changed: 13 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
0 commit comments