Skip to content

Commit 40f953e

Browse files
committed
core/analyzer: analyze without writing to the catalog
A cast to a type the catalog did not hold, and an array expression whose array type it did not hold, registered the type before reporting it. That made analysis a writer: the catalog a query was analyzed against depended on which queries had been analyzed before it, and a query could quietly define a type. An expression's type now carries the name the query used when the catalog has no such type, so the same result is reported without inserting anything. Nothing in the analyzer calls a catalog mutator any more. Seeding is deterministic to match: the constant types were written in map order, so building the same catalog twice produced two byte-different databases and the cache stored one copy per run instead of one per catalog. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011MnoUabwBWW9gaEn2Nj7eG
1 parent 961e6b0 commit 40f953e

4 files changed

Lines changed: 52 additions & 41 deletions

File tree

internal/core/analyzer/analyzer.go

Lines changed: 0 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ package analyzer
22

33
import (
44
"fmt"
5-
"strings"
65

76
"github.com/sqlc-dev/sqlc/internal/core"
87
"github.com/sqlc-dev/sqlc/internal/sql/ast"
@@ -43,23 +42,6 @@ func Prepare(cat *core.Catalog, stmt ast.Node) (core.PrepareResult, error) {
4342
return a.result(), nil
4443
}
4544

46-
// dataType reports the type's name and whether it is an array of that type.
47-
// The catalog names an array after its element, so a caller that reports the
48-
// two separately gets them apart here.
49-
func (a *analyzer) dataType(oid int64) (string, bool) {
50-
if oid == 0 {
51-
return "", false
52-
}
53-
name, err := a.cat.TypeName(oid)
54-
if err != nil {
55-
return "", false
56-
}
57-
if element, ok := strings.CutSuffix(name, core.ArraySuffix); ok {
58-
return element, true
59-
}
60-
return name, false
61-
}
62-
6345
type analyzer struct {
6446
cat *core.Catalog
6547
scope *scope

internal/core/analyzer/expr.go

Lines changed: 40 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,12 @@ import (
99
)
1010

1111
type exprType struct {
12-
typeOID int64
12+
typeOID int64
13+
// typeName names a type the catalog does not hold — a cast to a type no
14+
// dialect seeded and no schema declared, or an array of one. Analysis
15+
// never adds a type: it reports the name the query used and carries on,
16+
// so a query can be analyzed against a catalog it cannot write to.
17+
typeName string
1318
nullable bool
1419
sourceClassOID int64
1520
sourceAttributeOID int64
@@ -202,9 +207,9 @@ func (a *analyzer) inferParam(number int, t exprType) {
202207
if !ok {
203208
cur = core.Parameter{Number: number}
204209
}
205-
if cur.TypeOID == 0 && t.typeOID != 0 {
210+
if cur.TypeOID == 0 && cur.DataType == "" && (t.typeOID != 0 || t.typeName != "") {
206211
cur.TypeOID = t.typeOID
207-
cur.DataType, cur.IsArray = a.dataType(t.typeOID)
212+
cur.DataType, cur.IsArray = a.typeNameOf(t)
208213
cur.NotNull = !t.nullable
209214
}
210215
if cur.Source == nil && t.sourceAttributeOID != 0 {
@@ -413,22 +418,36 @@ func (a *analyzer) typeArrayExpr(e *ast.A_ArrayExpr) (exprType, error) {
413418
if err != nil {
414419
return exprType{}, err
415420
}
416-
if elemT.typeOID == 0 {
421+
element, _ := a.typeNameOf(elemT)
422+
if element == "" {
417423
return exprType{}, nil
418424
}
419-
name, err := a.cat.TypeName(elemT.typeOID)
420-
if err != nil {
421-
return exprType{}, nil
425+
return a.namedType(element + core.ArraySuffix), nil
426+
}
427+
428+
// namedType is the type a name refers to, or the name itself when the catalog
429+
// has no such type.
430+
func (a *analyzer) namedType(name string) exprType {
431+
if oid, err := a.cat.TypeOID(name); err == nil {
432+
return exprType{typeOID: oid}
422433
}
423-
arrayName := name + core.ArraySuffix
424-
oid, err := a.cat.TypeOID(arrayName)
425-
if err != nil {
426-
oid, err = a.cat.CreateArrayType(arrayName, elemT.typeOID)
427-
if err != nil {
428-
return exprType{}, err
434+
return exprType{typeName: name}
435+
}
436+
437+
// typeNameOf reports a type's name and whether it is an array of that name,
438+
// whether the type is one the catalog holds or one only the query named.
439+
func (a *analyzer) typeNameOf(t exprType) (string, bool) {
440+
name := t.typeName
441+
if t.typeOID != 0 {
442+
var err error
443+
if name, err = a.cat.TypeName(t.typeOID); err != nil {
444+
return "", false
429445
}
430446
}
431-
return exprType{typeOID: oid}, nil
447+
if element, ok := strings.CutSuffix(name, core.ArraySuffix); ok {
448+
return element, true
449+
}
450+
return name, false
432451
}
433452

434453
// typeSubLink types a subquery used as an expression: EXISTS and IN yield a
@@ -497,7 +516,7 @@ func (a *analyzer) typeNullIf(e *ast.A_Expr) (exprType, error) {
497516
// placeholder that type.
498517
func (a *analyzer) typeOperands(n ast.Node, other exprType) error {
499518
if pr, ok := n.(*ast.ParamRef); ok {
500-
if other.typeOID != 0 {
519+
if other.typeOID != 0 || other.typeName != "" {
501520
a.inferParam(pr.Number, other)
502521
}
503522
return nil
@@ -710,13 +729,14 @@ func (a *analyzer) typeTypeCast(c *ast.TypeCast) (exprType, error) {
710729
if c.TypeName == nil {
711730
return exprType{}, fmt.Errorf("cast: missing target type")
712731
}
713-
oid, err := a.cat.ResolveType(c.TypeName)
714-
if err != nil {
715-
return exprType{}, fmt.Errorf("cast target %q: %w", core.TypeNameString(c.TypeName), err)
732+
name := core.TypeNameString(c.TypeName)
733+
if name == "" {
734+
return exprType{}, fmt.Errorf("cast: missing target type")
716735
}
736+
t := a.namedType(name)
717737
// A cast is how a query says what an otherwise untyped placeholder holds.
718-
if err := a.typeOperands(c.Arg, exprType{typeOID: oid}); err != nil {
738+
if err := a.typeOperands(c.Arg, t); err != nil {
719739
return exprType{}, err
720740
}
721-
return exprType{typeOID: oid}, nil
741+
return t, nil
722742
}

internal/core/analyzer/projection.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ func (a *analyzer) projectTarget(rt *ast.ResTarget) error {
3030
SourceClassOID: t.sourceClassOID,
3131
SourceAttributeOID: t.sourceAttributeOID,
3232
}
33-
col.DataType, col.IsArray = a.dataType(t.typeOID)
33+
col.DataType, col.IsArray = a.typeNameOf(t)
3434
a.decorateSource(&col, t.sourceAttributeOID, t.sourceTableAlias)
3535
a.columns = append(a.columns, col)
3636
return nil
@@ -97,7 +97,7 @@ func (a *analyzer) emitStar(fields []string) {
9797
SourceClassOID: rel.classOID,
9898
SourceAttributeOID: c.AttOID,
9999
}
100-
col.DataType, col.IsArray = a.dataType(c.TypeOID)
100+
col.DataType, col.IsArray = a.typeNameOf(exprType{typeOID: c.TypeOID})
101101
a.decorateSource(&col, c.AttOID, rel.alias)
102102
a.columns = append(a.columns, col)
103103
}

internal/core/seed/seed.go

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -414,11 +414,20 @@ func (b *builder) createType(name, category string) (int64, error) {
414414
var constKinds = []string{core.ConstInteger, core.ConstFloat, core.ConstString, core.ConstBool}
415415

416416
func (b *builder) consts() error {
417-
for kind, name := range b.settings.Const {
417+
for kind := range b.settings.Const {
418418
if !slices.Contains(constKinds, kind) {
419419
return fmt.Errorf("seed %s: unknown constant kind %q, want one of %s",
420420
b.settings.Dialect, kind, strings.Join(constKinds, ", "))
421421
}
422+
}
423+
// Written in a fixed order rather than the map's: a catalog built from the
424+
// same dialect twice has to come out byte for byte the same, so that the
425+
// cache stores one copy of it rather than one per run.
426+
for _, kind := range constKinds {
427+
name, ok := b.settings.Const[kind]
428+
if !ok {
429+
continue
430+
}
422431
if _, ok := b.oids[strings.ToLower(name)]; !ok {
423432
return fmt.Errorf("seed %s: constant %s names unknown type %q", b.settings.Dialect, kind, name)
424433
}

0 commit comments

Comments
 (0)