Skip to content

Commit 2c11559

Browse files
committed
core: carry enum types through the analysis core to codegen
The core catalog created an enum type on CREATE TYPE ... AS ENUM but kept none of its labels, and the dump that hands the catalog to codegen carried relations only. Under SQLCEXPERIMENT=coreanalyzer every enum was missing from models.go, PostgreSQL enum columns were typed any and MySQL ENUM columns string. The catalog now stores enum labels (sql_enum_label, modeled on pg_enum) and the schema package applies the statements that change them: ALTER TYPE ADD VALUE (with BEFORE/AFTER and IF NOT EXISTS), RENAME VALUE, RENAME TO, SET SCHEMA, and DROP TYPE. A MySQL ENUM or SET column declares an enum of its own, named <table>_<column> as the legacy catalog names it; it follows the column and the table through renames, is replaced on MODIFY, and goes with a dropped column or table. The dump adds each enum to its schema so codegen builds the same Go types either way a query set was analyzed. Type names now keep the schema an engine reports on the TypeName itself and drop a spelled-out "public.", so foo.mood and public.status resolve the way their column references do. In the core replay context 46 more cases pass, with no case regressing. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XZcKb4GFiZQbeo9oVmyF3m
1 parent 7e46437 commit 2c11559

8 files changed

Lines changed: 543 additions & 9 deletions

File tree

‎internal/compiler/catalog_core.go‎

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,17 +10,19 @@ import (
1010

1111
// coreResultCatalog dumps the core catalog into the legacy catalog shape a
1212
// Result carries, so codegen sees the same table models either way a query
13-
// set was analyzed. Only relations make the trip: codegen reads tables and
14-
// their columns to build models, and none of the types, functions or
15-
// operators the core catalog also holds.
13+
// set was analyzed. Relations and enums make the trip: codegen reads tables
14+
// and their columns to build models and enums to build their Go types, and
15+
// none of the functions or operators the core catalog also holds.
1616
func coreResultCatalog(c *core.Catalog) (*catalog.Catalog, error) {
1717
cat := catalog.New("public")
1818
namespaces, err := c.Namespaces()
1919
if err != nil {
2020
return nil, err
2121
}
22+
schemas := map[string]*catalog.Schema{}
2223
for _, ns := range namespaces {
2324
schema := &catalog.Schema{Name: ns.Name}
25+
schemas[ns.Name] = schema
2426
tables, err := c.TablesInNamespace(ns.OID)
2527
if err != nil {
2628
return nil, err
@@ -52,5 +54,29 @@ func coreResultCatalog(c *core.Catalog) (*catalog.Catalog, error) {
5254
}
5355
cat.Schemas = append(cat.Schemas, schema)
5456
}
57+
58+
// The catalog spells an enum's schema in its name, and a schema that
59+
// holds only types has no namespace of its own, so one is made here.
60+
enums, err := c.Enums()
61+
if err != nil {
62+
return nil, err
63+
}
64+
for _, enum := range enums {
65+
labels, err := c.EnumLabels(enum.OID)
66+
if err != nil {
67+
return nil, err
68+
}
69+
schemaName, name := core.SplitTypeName(enum.Name)
70+
if schemaName == "" {
71+
schemaName = cat.DefaultSchema
72+
}
73+
schema, ok := schemas[schemaName]
74+
if !ok {
75+
schema = &catalog.Schema{Name: schemaName}
76+
schemas[schemaName] = schema
77+
cat.Schemas = append(cat.Schemas, schema)
78+
}
79+
schema.Types = append(schema.Types, &catalog.Enum{Name: name, Vals: labels})
80+
}
5581
return cat, nil
5682
}

‎internal/core/catalogdb/models.go‎

Lines changed: 6 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎internal/core/catalogdb/query.sql.go‎

Lines changed: 108 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎internal/core/catalogdef/query.sql‎

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,26 @@ SELECT oid, name, category, typtype, preferred
6767
FROM sql_type
6868
WHERE oid = ?;
6969

70+
-- name: ListEnumTypes :many
71+
SELECT oid, name FROM sql_type
72+
WHERE typtype = 'e'
73+
ORDER BY oid;
74+
75+
-- name: RenameType :exec
76+
UPDATE sql_type SET name = sqlc.arg(name) WHERE oid = sqlc.arg(oid);
77+
78+
-- name: DeleteType :exec
79+
DELETE FROM sql_type WHERE oid = ?;
80+
81+
-- name: CreateEnumLabel :exec
82+
INSERT INTO sql_enum_label (type_oid, ord, label) VALUES (?, ?, ?);
83+
84+
-- name: ListEnumLabels :many
85+
SELECT label FROM sql_enum_label WHERE type_oid = ? ORDER BY ord;
86+
87+
-- name: DeleteEnumLabels :exec
88+
DELETE FROM sql_enum_label WHERE type_oid = ?;
89+
7090
-- =============================== sql_class =============================
7191

7292
-- name: CreateClass :execlastid

‎internal/core/catalogdef/schema.sql‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,16 @@ CREATE TABLE sql_type (
4141
);
4242
CREATE INDEX idx_sql_type_name ON sql_type(name);
4343

44+
-- sql_enum_label: the labels of an enum type, in declaration order.
45+
-- Modeled on pg_enum. A MySQL ENUM or SET column declares one of these
46+
-- for the column alone, under the name the legacy catalog gives it.
47+
CREATE TABLE sql_enum_label (
48+
type_oid INTEGER NOT NULL REFERENCES sql_type(oid),
49+
ord INTEGER NOT NULL,
50+
label TEXT NOT NULL,
51+
PRIMARY KEY (type_oid, ord)
52+
);
53+
4454
-- sql_class: relations (tables, views, indexes).
4555
-- kind: 'r' = table, 'v' = view, 'i' = index, 'c' = composite type, 'f' = foreign
4656
CREATE TABLE sql_class (

‎internal/core/enum.go‎

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
package core
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"strings"
7+
8+
"github.com/sqlc-dev/sqlc/internal/core/catalogdb"
9+
)
10+
11+
// EnumInfo describes an enum type the schema declared.
12+
type EnumInfo struct {
13+
OID int64
14+
// Name is the name the catalog stores, which carries the schema when the
15+
// type was declared with one ("foo.mood").
16+
Name string
17+
}
18+
19+
// CreateEnumType registers an enum type with its labels in declaration order.
20+
func (c *Catalog) CreateEnumType(name string, labels []string) (int64, error) {
21+
oid, err := c.CreateUserType(name, "E")
22+
if err != nil {
23+
return 0, err
24+
}
25+
if err := c.SetEnumLabels(oid, labels); err != nil {
26+
return 0, err
27+
}
28+
return oid, nil
29+
}
30+
31+
// EnumLabels returns an enum type's labels in declaration order.
32+
func (c *Catalog) EnumLabels(oid int64) ([]string, error) {
33+
labels, err := c.q.ListEnumLabels(context.Background(), oid)
34+
if err != nil {
35+
return nil, fmt.Errorf("enum labels of type oid %d: %w", oid, err)
36+
}
37+
return labels, nil
38+
}
39+
40+
// SetEnumLabels replaces an enum type's labels.
41+
func (c *Catalog) SetEnumLabels(oid int64, labels []string) error {
42+
ctx := context.Background()
43+
if err := c.q.DeleteEnumLabels(ctx, oid); err != nil {
44+
return fmt.Errorf("enum labels of type oid %d: %w", oid, err)
45+
}
46+
for i, label := range labels {
47+
if err := c.q.CreateEnumLabel(ctx, catalogdb.CreateEnumLabelParams{TypeOid: oid, Ord: int64(i + 1), Label: label}); err != nil {
48+
return fmt.Errorf("enum label %q of type oid %d: %w", label, oid, err)
49+
}
50+
}
51+
return nil
52+
}
53+
54+
// Enums lists the enum types the schema declared, in declaration order.
55+
func (c *Catalog) Enums() ([]EnumInfo, error) {
56+
rows, err := c.q.ListEnumTypes(context.Background())
57+
if err != nil {
58+
return nil, fmt.Errorf("list enum types: %w", err)
59+
}
60+
out := make([]EnumInfo, 0, len(rows))
61+
for _, r := range rows {
62+
out = append(out, EnumInfo{OID: r.Oid, Name: r.Name})
63+
}
64+
return out, nil
65+
}
66+
67+
// RenameType gives a type a new name. Columns refer to the type by OID, so
68+
// they follow the rename.
69+
func (c *Catalog) RenameType(oid int64, name string) error {
70+
if err := c.q.RenameType(context.Background(), catalogdb.RenameTypeParams{Oid: oid, Name: strings.ToLower(name)}); err != nil {
71+
return fmt.Errorf("rename type oid %d to %q: %w", oid, name, err)
72+
}
73+
return nil
74+
}
75+
76+
// DropType removes a type and, when it is an enum, its labels.
77+
func (c *Catalog) DropType(oid int64) error {
78+
ctx := context.Background()
79+
if err := c.q.DeleteEnumLabels(ctx, oid); err != nil {
80+
return fmt.Errorf("drop type oid %d: %w", oid, err)
81+
}
82+
if err := c.q.DeleteType(ctx, oid); err != nil {
83+
return fmt.Errorf("drop type oid %d: %w", oid, err)
84+
}
85+
return nil
86+
}
87+
88+
// SplitTypeName separates the schema a type name carries from the name
89+
// itself: "foo.mood" is the type mood in schema foo, and a bare name is in
90+
// the default schema.
91+
func SplitTypeName(name string) (schema, typ string) {
92+
if i := strings.LastIndex(name, "."); i >= 0 {
93+
return name[:i], name[i+1:]
94+
}
95+
return "", name
96+
}
97+
98+
// JoinTypeName is the inverse of SplitTypeName: the name the catalog stores
99+
// for a type in a schema. The default schema is not spelled.
100+
func JoinTypeName(schema, typ string) string {
101+
if schema == "" || schema == "public" {
102+
return typ
103+
}
104+
return schema + "." + typ
105+
}

0 commit comments

Comments
 (0)