Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions internal/compiler/expand.go
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,12 @@ func (c *Compiler) expandStmt(qc *QueryCatalog, raw *ast.RawStmt, node ast.Node)
tableName := c.quoteIdent(t.Rel.Name)
scopeName := c.quoteIdent(scope)
for _, column := range t.Columns {
// Skip PostgreSQL system columns (xmin, ctid, ...) when expanding *.
// This matches PostgreSQL's own behavior — they must be referenced
// explicitly. See issue #3742.
if column.IsSystem {
continue
}
cname := column.Name
if res.Name != nil {
cname = *res.Name
Expand Down
5 changes: 5 additions & 0 deletions internal/compiler/output_columns.go
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,11 @@ func (c *Compiler) outputColumns(qc *QueryCatalog, node ast.Node) ([]*Column, er
continue
}
for _, c := range t.Columns {
// Skip PostgreSQL system columns on SELECT * to match PG behavior.
// See issue #3742.
if c.IsSystem {
continue
}
cname := c.Name
if res.Name != nil {
cname = *res.Name
Expand Down
6 changes: 6 additions & 0 deletions internal/compiler/query.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,12 @@ type Column struct {

IsSqlcSlice bool // is this sqlc.slice()

// IsSystem indicates this is a PostgreSQL system column synthesized by
// QueryCatalog.GetTable (tableoid, xmin, cmin, xmax, cmax, ctid). System
// columns are excluded from SELECT * / RETURNING * expansion to match
// PostgreSQL's own behavior.
IsSystem bool

skipTableRequiredCheck bool
}

Expand Down
38 changes: 37 additions & 1 deletion internal/compiler/query_catalog.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package compiler
import (
"fmt"

"github.com/sqlc-dev/sqlc/internal/config"
"github.com/sqlc-dev/sqlc/internal/sql/ast"
"github.com/sqlc-dev/sqlc/internal/sql/catalog"
"github.com/sqlc-dev/sqlc/internal/sql/rewrite"
Expand All @@ -12,6 +13,7 @@ type QueryCatalog struct {
catalog *catalog.Catalog
ctes map[string]*Table
embeds rewrite.EmbedSet
engine config.Engine
}

func (comp *Compiler) buildQueryCatalog(c *catalog.Catalog, node ast.Node, embeds rewrite.EmbedSet) (*QueryCatalog, error) {
Expand All @@ -28,7 +30,7 @@ func (comp *Compiler) buildQueryCatalog(c *catalog.Catalog, node ast.Node, embed
default:
with = nil
}
qc := &QueryCatalog{catalog: c, ctes: map[string]*Table{}, embeds: embeds}
qc := &QueryCatalog{catalog: c, ctes: map[string]*Table{}, embeds: embeds, engine: comp.conf.Engine}
if with != nil {
for _, item := range with.Ctes.Items {
if cte, ok := item.(*ast.CommonTableExpr); ok {
Expand Down Expand Up @@ -90,9 +92,43 @@ func (qc QueryCatalog) GetTable(rel *ast.TableName) (*Table, error) {
for _, c := range src.Columns {
cols = append(cols, ConvertColumn(rel, c))
}
// PostgreSQL exposes six system columns on every user table
// (tableoid, xmin, cmin, xmax, cmax, ctid). They are not part of the
// CREATE TABLE definition, so the catalog has no record of them — but
// queries are allowed to reference them by name. Synthesize them here
// so the compiler can resolve refs like `SELECT xmin, ctid FROM foo`.
// They are marked IsSystem so SELECT * / RETURNING * skip them, which
// matches PostgreSQL's own behavior. See issues #1745, #3742.
if qc.engine == config.EnginePostgreSQL {
cols = append(cols, pgSystemColumns(rel)...)
}
return &Table{Rel: rel, Columns: cols}, nil
}

// pgSystemColumns returns the six PostgreSQL system columns synthesized for
// every user table. See https://www.postgresql.org/docs/current/ddl-system-columns.html
func pgSystemColumns(rel *ast.TableName) []*Column {
mk := func(name, typ string) *Column {
t := &ast.TypeName{Name: typ}
return &Column{
Name: name,
DataType: typ,
NotNull: true,
Table: rel,
Type: t,
IsSystem: true,
}
}
return []*Column{
mk("tableoid", "oid"),
mk("xmin", "xid"),
mk("cmin", "cid"),
mk("xmax", "xid"),
mk("cmax", "cid"),
mk("ctid", "tid"),
}
}

func (qc QueryCatalog) GetFunc(rel *ast.FuncName) (*Function, error) {
funcs, err := qc.catalog.ListFuncsByName(rel)
if err != nil {
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
-- name: GetSystemColumns :one
SELECT xmin, cmin, xmax, cmax, ctid, tableoid FROM authors LIMIT 1;

-- name: GetSystemColumnsAliased :one
SELECT a.xmin, a.ctid FROM authors a LIMIT 1;

-- name: SelectStar :many
SELECT * FROM authors;
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
CREATE TABLE authors (
id BIGSERIAL PRIMARY KEY,
name text NOT NULL,
bio text
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"version": "1",
"packages": [
{
"path": "go",
"engine": "postgresql",
"sql_package": "pgx/v5",
"name": "querytest",
"schema": "schema.sql",
"queries": "query.sql"
}
]
}
Loading