Skip to content

Commit c70c5d6

Browse files
committed
core: answer the review: canonical type field, label parsing, namespaces, lookups
A type's canonical rendering now travels in its own field of ast.TypeName, so DuckDB, GoogleSQL and ClickHouse hand the core struct(a: integer) and Enum8('a' = 1) while the formatter keeps printing the author's spelling; ParseTypeExpr reads a label before a colon, and before a space only when one word follows, so a field typed timestamp with time zone keeps its type. A lookup that may not write no longer probes by inserting an empty-named row. A bare type name resolves in the default namespaces only, and CREATE TYPE deduplicates within the namespace it names, so foo.mood and mood are two types. IN with a subquery types its left side once, and an operator's overloads are read once per resolution. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Qryf7a1doPFuCz2eGT3bCr
1 parent 271b10f commit c70c5d6

13 files changed

Lines changed: 259 additions & 81 deletions

File tree

‎internal/core/analyzer/expr.go‎

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -405,8 +405,8 @@ func (a *analyzer) typeIn(e *ast.In) (exprType, error) {
405405
if err != nil {
406406
return exprType{}, err
407407
}
408-
if len(cols) > 0 {
409-
if err := a.typeOperands(e.Expr, columnExprType(cols[0])); err != nil {
408+
if pr, ok := e.Expr.(*ast.ParamRef); ok && len(cols) > 0 {
409+
if err := a.typeOperands(pr, columnExprType(cols[0])); err != nil {
410410
return exprType{}, err
411411
}
412412
}
@@ -705,24 +705,29 @@ func opNameFromList(l *ast.List) string {
705705
func (a *analyzer) resolveOperator(name string, leftT, rightT exprType) (core.OperatorOverload, error) {
706706
leftChain := a.cat.ResolutionChain(leftT.typeOID)
707707
rightChain := a.cat.ResolutionChain(rightT.typeOID)
708+
all, err := a.cat.FindOperators(name, 0, 0)
709+
if err != nil {
710+
return core.OperatorOverload{}, err
711+
}
712+
// The operator's overloads are read once; the pairs along the two
713+
// chains are tried against them in order, nearest first.
714+
byOperands := make(map[[2]int64]core.OperatorOverload, len(all))
715+
for _, ov := range all {
716+
key := [2]int64{ov.LeftTypeOID, ov.RightTypeOID}
717+
if _, seen := byOperands[key]; !seen {
718+
byOperands[key] = ov
719+
}
720+
}
708721
for _, l := range leftChain {
709722
for _, r := range rightChain {
710-
candidates, err := a.cat.FindOperators(name, l, r)
711-
if err != nil {
712-
return core.OperatorOverload{}, err
713-
}
714-
if len(candidates) > 0 {
715-
return candidates[0], nil
723+
if ov, ok := byOperands[[2]int64{l, r}]; ok && l != 0 && r != 0 {
724+
return ov, nil
716725
}
717726
}
718727
}
719728
leftOID := leftChain[len(leftChain)-1]
720729
rightOID := rightChain[len(rightChain)-1]
721730

722-
all, err := a.cat.FindOperators(name, 0, 0)
723-
if err != nil {
724-
return core.OperatorOverload{}, err
725-
}
726731
for _, ov := range all {
727732
if leftOID != 0 && ov.LeftTypeOID != 0 && leftOID != ov.LeftTypeOID {
728733
ok, _ := a.cat.CastAllowed(leftOID, ov.LeftTypeOID, "i")

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

Lines changed: 50 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: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,11 @@ ORDER BY
6666
ns.name
6767
LIMIT 1;
6868

69+
-- name: TypeOIDsByNameInNamespaces :many
70+
SELECT oid, namespace_oid FROM sql_type
71+
WHERE name = sqlc.arg(name) AND family_oid IS NULL
72+
AND namespace_oid IN (sqlc.slice(namespace_oids));
73+
6974
-- name: TypeOIDByNameInNamespace :one
7075
SELECT oid FROM sql_type
7176
WHERE namespace_oid = ? AND name = ? AND family_oid IS NULL;

‎internal/core/schema/schema.go‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -367,7 +367,7 @@ func applyCreateEnum(cat *core.Catalog, stmt *ast.CreateEnumStmt) error {
367367
if name == "" {
368368
return fmt.Errorf("create type with empty name")
369369
}
370-
if _, err := cat.TypeOID(name); err == nil {
370+
if cat.TypeDeclared(name) {
371371
return nil
372372
}
373373
var labels []core.TypeArg
@@ -386,7 +386,7 @@ func applyCreateDomain(cat *core.Catalog, stmt *ast.CreateDomainStmt) error {
386386
if name == "" || stmt.TypeName == nil {
387387
return fmt.Errorf("create domain: missing name or type")
388388
}
389-
if _, err := cat.TypeOID(name); err == nil {
389+
if cat.TypeDeclared(name) {
390390
return nil
391391
}
392392
baseOID, err := cat.ResolveType(stmt.TypeName)
@@ -423,7 +423,7 @@ func applyCompositeType(cat *core.Catalog, stmt *ast.CompositeTypeStmt) error {
423423
if name == "" {
424424
return fmt.Errorf("create type with empty name")
425425
}
426-
if _, err := cat.TypeOID(name); err == nil {
426+
if cat.TypeDeclared(name) {
427427
return nil
428428
}
429429
var fields []core.TypeArg
@@ -449,7 +449,7 @@ func applyCreateRange(cat *core.Catalog, stmt *ast.CreateRangeStmt) error {
449449
if name == "" {
450450
return fmt.Errorf("create type with empty name")
451451
}
452-
if _, err := cat.TypeOID(name); err == nil {
452+
if cat.TypeDeclared(name) {
453453
return nil
454454
}
455455
spec := core.TypeSpec{Name: name, Typtype: "r", Category: "R"}

‎internal/core/typeexpr.go‎

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -156,16 +156,19 @@ func parseTypeArg(a string) TypeArg {
156156
b := strings.EqualFold(a, "true")
157157
return TypeArg{Bool: &b}
158158
}
159-
// A label is a word before a space that comes before any parenthesis,
160-
// as in `lat Float64` or `tags Array(String)`.
159+
// A label is a word before a colon, as the canonical form writes it:
160+
// `lat: Float64`. A word before a space is a label too, as ClickHouse
161+
// writes `lat Float64`, but only when what follows is a single word,
162+
// since `timestamp with time zone` is a name and not a label.
161163
head := a
162164
if p := strings.IndexByte(a, '('); p >= 0 {
163165
head = a[:p]
164166
}
165-
if i := strings.IndexByte(head, ' '); i > 0 {
166-
arg := parseTypeArg(a[i+1:])
167-
arg.Label = a[:i]
168-
return arg
167+
if i := strings.IndexByte(head, ':'); i > 0 && !strings.ContainsAny(head[:i], " '\"") {
168+
return TypeArg{Label: strings.TrimSpace(a[:i]), Type: ParseTypeExpr(a[i+1:])}
169+
}
170+
if i := strings.IndexByte(head, ' '); i > 0 && !strings.Contains(strings.TrimSpace(head[i+1:]), " ") {
171+
return TypeArg{Label: a[:i], Type: ParseTypeExpr(a[i+1:])}
169172
}
170173
return TypeArg{Type: ParseTypeExpr(a)}
171174
}

‎internal/core/typename.go‎

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,20 @@ import (
88
)
99

1010
// TypeExprOfTypeName reads the type an AST node names into an expression.
11-
// An engine that folds the whole type into a spelling — ClickHouse's
12-
// Array(Nullable(String)), SQLite's VARYING CHARACTER(10) — hands it over
13-
// in Spelling and the spelling is read as written. Otherwise the name comes
14-
// from Name or the qualifying parts of Names, the type modifiers become
15-
// integer or string arguments, and each array bound wraps the result in an
16-
// array.
11+
// An engine that renders the whole type as a call expression — DuckDB's
12+
// struct(a: integer, b: varchar) — hands it over in Canonical; one that
13+
// folds it into the spelling the formatter prints back — ClickHouse's
14+
// Array(Nullable(String)), SQLite's VARYING CHARACTER(10) — in Spelling.
15+
// Otherwise the name comes from Name or the qualifying parts of Names, the
16+
// type modifiers become integer or string arguments, and each array bound
17+
// wraps the result in an array.
1718
func TypeExprOfTypeName(tn *ast.TypeName) *TypeExpr {
1819
if tn == nil {
1920
return nil
2021
}
22+
if tn.Canonical != "" {
23+
return ParseTypeExpr(tn.Canonical)
24+
}
2125
if tn.Spelling != "" {
2226
return ParseTypeExpr(tn.Spelling)
2327
}
@@ -79,7 +83,7 @@ func ColumnTypeExpr(col *ast.ColumnDef) *TypeExpr {
7983
}
8084
}
8185
}
82-
if col.TypeName.Spelling != "" || listItems(col.TypeName.ArrayBounds) != nil {
86+
if col.TypeName.Canonical != "" || col.TypeName.Spelling != "" || listItems(col.TypeName.ArrayBounds) != nil {
8387
return t
8488
}
8589
dims := col.ArrayDims

0 commit comments

Comments
 (0)