11// Package seed builds a core catalog for a SQL dialect from a declarative
22// description of its type system: the types it defines, the operators and
33// casts between them, and the functions it ships with.
4+ //
5+ // A dialect is described by a JSON document, which each engine package embeds
6+ // as its dialect.json and hands to Dialect. Load parses one.
47package seed
58
69import (
10+ "bytes"
11+ "encoding/json"
712 "fmt"
13+ "slices"
814 "strings"
915
1016 "github.com/sqlc-dev/sqlc/internal/core"
@@ -16,66 +22,108 @@ import (
1622// that a schema may use in a column definition; each becomes its own catalog
1723// type, with implicit casts registered between them.
1824type Type struct {
19- Name string
20- Category string
21- Aliases []string
25+ Name string `json:"name"`
26+ Category string `json:"category"`
27+ Aliases []string `json:"aliases,omitempty"`
2228}
2329
2430// Operator is a single operator overload.
2531type Operator struct {
26- Name string
27- Left string
28- Right string
29- Result string
32+ Name string `json:"name"`
33+ Left string `json:"left"`
34+ Right string `json:"right"`
35+ Result string `json:"result"`
3036}
3137
3238// Cast is a single cast between two types. Context is 'i'mplicit, 'a'ssignment
3339// or 'e'xplicit.
3440type Cast struct {
35- Source string
36- Target string
37- Context string
41+ Source string `json:"source"`
42+ Target string `json:"target"`
43+ Context string `json:"context,omitempty"`
44+ }
45+
46+ // Function is a function the dialect ships with. Kind is 'f'unction,
47+ // 'a'ggregate, 'w'indow or 'p'rocedure.
48+ type Function struct {
49+ Name string `json:"name"`
50+ Kind string `json:"kind,omitempty"`
51+ Args []string `json:"args,omitempty"`
52+ Returns string `json:"returns"`
53+ Nullable bool `json:"nullable,omitempty"`
3854}
3955
4056// Spec describes a dialect. Apply turns it into catalog rows.
4157type Spec struct {
4258 // Dialect is the name recorded in the catalog, e.g. "postgresql".
43- Dialect string
59+ Dialect string `json:"dialect"`
4460
4561 // Types are the dialect's types.
46- Types []Type
62+ Types []Type `json:"types"`
4763
4864 // Const names the type each kind of literal takes on, keyed by the
4965 // core.Const* constants. Kinds left out fall back to PostgreSQL's names.
50- Const map [string ]string
66+ Const map [string ]string `json:"const,omitempty"`
5167
5268 // Comparison operators are registered as (T, T) -> Bool for every type in
5369 // ComparisonCategories.
54- Comparison []string
55- ComparisonCategories string
70+ Comparison []string `json:"comparison,omitempty"`
71+ ComparisonCategories string `json:"comparison_categories,omitempty"`
5672
5773 // Arithmetic operators are registered as (T, T) -> T for every type in
5874 // ArithmeticCategories.
59- Arithmetic []string
60- ArithmeticCategories string
75+ Arithmetic []string `json:"arithmetic,omitempty"`
76+ ArithmeticCategories string `json:"arithmetic_categories,omitempty"`
6177
6278 // Bool names the type comparisons return.
63- Bool string
79+ Bool string `json:"bool,omitempty"`
6480
6581 // Operators and Casts are registered as given, on top of the ones the
6682 // category rules above generate.
67- Operators []Operator
68- Casts []Cast
83+ Operators []Operator `json:"operators,omitempty"`
84+ Casts []Cast `json:"casts,omitempty"`
6985
7086 // CastCategories lists the categories whose types are all implicitly
7187 // castable to one another, so that an operator on two spellings of the
7288 // same kind of value resolves. "*" makes every seeded type implicitly
7389 // castable to every other, for dialects that compare across categories.
74- CastCategories string
90+ CastCategories string `json:"cast_categories,omitempty"`
91+
92+ // Functions are the dialect's built-in functions.
93+ Functions []Function `json:"functions,omitempty"`
94+
95+ // Funcs are further built-in functions, in the form the engine packages
96+ // already describe them. A dialect whose function catalog is generated
97+ // passes it here rather than restating it as JSON.
98+ Funcs []* catalog.Function `json:"-"`
99+ }
75100
76- // Funcs are the dialect's built-in functions, in the form the engine
77- // packages already describe them.
78- Funcs []* catalog.Function
101+ // Load parses a dialect description. An unknown field is an error rather than
102+ // something to ignore, so that a misspelled key is caught where it is written.
103+ func Load (data []byte ) (Spec , error ) {
104+ dec := json .NewDecoder (bytes .NewReader (data ))
105+ dec .DisallowUnknownFields ()
106+ var s Spec
107+ if err := dec .Decode (& s ); err != nil {
108+ return Spec {}, fmt .Errorf ("seed: parse dialect: %w" , err )
109+ }
110+ if s .Dialect == "" {
111+ return Spec {}, fmt .Errorf ("seed: dialect has no name" )
112+ }
113+ return s , nil
114+ }
115+
116+ // Dialect returns the catalog option that seeds the dialect the JSON
117+ // describes, together with any function catalog the engine generates.
118+ func Dialect (data []byte , funcs []* catalog.Function ) core.Option {
119+ return core .WithSeed (func (cat * core.Catalog ) error {
120+ spec , err := Load (data )
121+ if err != nil {
122+ return err
123+ }
124+ spec .Funcs = funcs
125+ return spec .Apply (cat )
126+ })
79127}
80128
81129// Apply registers everything the spec describes on cat.
@@ -103,6 +151,9 @@ func (s Spec) Apply(cat *core.Catalog) error {
103151 if err := b .casts (s ); err != nil {
104152 return err
105153 }
154+ if err := b .functions (s ); err != nil {
155+ return err
156+ }
106157 return b .funcs (s )
107158}
108159
@@ -175,8 +226,15 @@ func (b *builder) createType(name, category string) (int64, error) {
175226 return oid , nil
176227}
177228
229+ // constKinds are the kinds of literal a dialect may name a type for.
230+ var constKinds = []string {core .ConstInteger , core .ConstFloat , core .ConstString , core .ConstBool }
231+
178232func (b * builder ) consts (s Spec ) error {
179233 for kind , name := range s .Const {
234+ if ! slices .Contains (constKinds , kind ) {
235+ return fmt .Errorf ("seed %s: unknown constant kind %q, want one of %s" ,
236+ s .Dialect , kind , strings .Join (constKinds , ", " ))
237+ }
180238 if _ , ok := b .oids [strings .ToLower (name )]; ! ok {
181239 return fmt .Errorf ("seed %s: constant %s names unknown type %q" , s .Dialect , kind , name )
182240 }
@@ -307,6 +365,34 @@ func (b *builder) cast(c Cast) error {
307365 })
308366}
309367
368+ func (b * builder ) functions (s Spec ) error {
369+ for _ , fn := range s .Functions {
370+ returnOID , err := b .funcTypeName (fn .Returns )
371+ if err != nil {
372+ return fmt .Errorf ("seed %s function %q: %w" , s .Dialect , fn .Name , err )
373+ }
374+ args := make ([]core.ProcArg , 0 , len (fn .Args ))
375+ for _ , arg := range fn .Args {
376+ argOID , err := b .funcTypeName (arg )
377+ if err != nil {
378+ return fmt .Errorf ("seed %s function %q: %w" , s .Dialect , fn .Name , err )
379+ }
380+ args = append (args , core.ProcArg {TypeOID : argOID })
381+ }
382+ if _ , err := b .cat .CreateProc (core.ProcSpec {
383+ Name : fn .Name ,
384+ DialectOID : b .dialectOID ,
385+ Kind : fn .Kind ,
386+ ReturnTypeOID : returnOID ,
387+ ReturnNullable : fn .Nullable ,
388+ Args : args ,
389+ }); err != nil {
390+ return fmt .Errorf ("seed %s function %q: %w" , s .Dialect , fn .Name , err )
391+ }
392+ }
393+ return nil
394+ }
395+
310396func (b * builder ) funcs (s Spec ) error {
311397 for _ , fn := range s .Funcs {
312398 returnOID , err := b .funcType (fn .ReturnType )
@@ -342,8 +428,15 @@ func (b *builder) funcs(s Spec) error {
342428// pseudo types ("any", "record") and types no dialect spec bothers to list, so
343429// an unknown name is registered rather than rejected.
344430func (b * builder ) funcType (tn * ast.TypeName ) (int64 , error ) {
345- if tn == nil || tn .Name == "" {
431+ if tn == nil {
432+ return 0 , nil
433+ }
434+ return b .funcTypeName (tn .Name )
435+ }
436+
437+ func (b * builder ) funcTypeName (name string ) (int64 , error ) {
438+ if name == "" {
346439 return 0 , nil
347440 }
348- return b .createType (tn . Name , "U" )
441+ return b .createType (name , "U" )
349442}
0 commit comments