Skip to content

Commit 550bdbb

Browse files
committed
sqlite: write sum and abs once per numeric type instead of as "$1"
A function that returns an integer for an integer and a real for a real is now written the way PostgreSQL's catalog writes sum: once over any, returning the real a text or blob argument gets, and once more per spelling types.jsonl gives integer and real, returning that type. The analysis core picks the overload whose parameter is the argument's type, so sum over an INTEGER or BIGINT column is an integer; the legacy compiler resolves by arity alone and takes the first overload, the one over any, so what it generates is unchanged. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PdKaVjSmdkmXPeUkxetyDY
1 parent 4c5cc40 commit 550bdbb

10 files changed

Lines changed: 178 additions & 39 deletions

File tree

‎internal/endtoend/testdata/builtins/sqlite/go/aggfunc.sql.go‎

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

‎internal/endtoend/testdata/builtins/sqlite/go/mathfunc.sql.go‎

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

‎internal/endtoend/testdata/builtins/sqlite/go/scalarfunc.sql.go‎

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

‎internal/endtoend/testdata/func_match_types/sqlite/go/query.sql.go‎

Lines changed: 2 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎internal/engine/sqlite/dialect/functions.jsonl‎

Lines changed: 84 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,19 @@
11
{"name":"-\u003e","args":[{"type":"any"},{"type":"any"}],"returns":"text","nullable":true}
22
{"name":"-\u003e\u003e","args":[{"type":"any"},{"type":"any"}],"returns":"any","nullable":true}
3-
{"name":"abs","args":[{"type":"any"}],"returns":"$1"}
3+
{"name":"abs","args":[{"type":"any"}],"returns":"real"}
4+
{"name":"abs","args":[{"type":"integer"}],"returns":"integer"}
5+
{"name":"abs","args":[{"type":"int"}],"returns":"integer"}
6+
{"name":"abs","args":[{"type":"tinyint"}],"returns":"integer"}
7+
{"name":"abs","args":[{"type":"smallint"}],"returns":"integer"}
8+
{"name":"abs","args":[{"type":"mediumint"}],"returns":"integer"}
9+
{"name":"abs","args":[{"type":"bigint"}],"returns":"integer"}
10+
{"name":"abs","args":[{"type":"unsigned big int"}],"returns":"integer"}
11+
{"name":"abs","args":[{"type":"int2"}],"returns":"integer"}
12+
{"name":"abs","args":[{"type":"int8"}],"returns":"integer"}
13+
{"name":"abs","args":[{"type":"real"}],"returns":"real"}
14+
{"name":"abs","args":[{"type":"double"}],"returns":"real"}
15+
{"name":"abs","args":[{"type":"double precision"}],"returns":"real"}
16+
{"name":"abs","args":[{"type":"float"}],"returns":"real"}
417
{"name":"acos","args":[{"type":"real"}],"returns":"real"}
518
{"name":"acosh","args":[{"type":"real"}],"returns":"real"}
619
{"name":"asin","args":[{"type":"real"}],"returns":"real"}
@@ -9,8 +22,34 @@
922
{"name":"atan2","args":[{"type":"real"},{"type":"real"}],"returns":"real"}
1023
{"name":"atanh","args":[{"type":"real"}],"returns":"real"}
1124
{"name":"avg","kind":"a","args":[{"type":"any"}],"returns":"real","nullable":true}
12-
{"name":"ceil","args":[{"type":"any"}],"returns":"$1"}
13-
{"name":"ceiling","args":[{"type":"any"}],"returns":"$1"}
25+
{"name":"ceil","args":[{"type":"any"}],"returns":"real"}
26+
{"name":"ceil","args":[{"type":"integer"}],"returns":"integer"}
27+
{"name":"ceil","args":[{"type":"int"}],"returns":"integer"}
28+
{"name":"ceil","args":[{"type":"tinyint"}],"returns":"integer"}
29+
{"name":"ceil","args":[{"type":"smallint"}],"returns":"integer"}
30+
{"name":"ceil","args":[{"type":"mediumint"}],"returns":"integer"}
31+
{"name":"ceil","args":[{"type":"bigint"}],"returns":"integer"}
32+
{"name":"ceil","args":[{"type":"unsigned big int"}],"returns":"integer"}
33+
{"name":"ceil","args":[{"type":"int2"}],"returns":"integer"}
34+
{"name":"ceil","args":[{"type":"int8"}],"returns":"integer"}
35+
{"name":"ceil","args":[{"type":"real"}],"returns":"real"}
36+
{"name":"ceil","args":[{"type":"double"}],"returns":"real"}
37+
{"name":"ceil","args":[{"type":"double precision"}],"returns":"real"}
38+
{"name":"ceil","args":[{"type":"float"}],"returns":"real"}
39+
{"name":"ceiling","args":[{"type":"any"}],"returns":"real"}
40+
{"name":"ceiling","args":[{"type":"integer"}],"returns":"integer"}
41+
{"name":"ceiling","args":[{"type":"int"}],"returns":"integer"}
42+
{"name":"ceiling","args":[{"type":"tinyint"}],"returns":"integer"}
43+
{"name":"ceiling","args":[{"type":"smallint"}],"returns":"integer"}
44+
{"name":"ceiling","args":[{"type":"mediumint"}],"returns":"integer"}
45+
{"name":"ceiling","args":[{"type":"bigint"}],"returns":"integer"}
46+
{"name":"ceiling","args":[{"type":"unsigned big int"}],"returns":"integer"}
47+
{"name":"ceiling","args":[{"type":"int2"}],"returns":"integer"}
48+
{"name":"ceiling","args":[{"type":"int8"}],"returns":"integer"}
49+
{"name":"ceiling","args":[{"type":"real"}],"returns":"real"}
50+
{"name":"ceiling","args":[{"type":"double"}],"returns":"real"}
51+
{"name":"ceiling","args":[{"type":"double precision"}],"returns":"real"}
52+
{"name":"ceiling","args":[{"type":"float"}],"returns":"real"}
1453
{"name":"changes","returns":"integer"}
1554
{"name":"char","args":[{"type":"integer","mode":"v"}],"returns":"text"}
1655
{"name":"coalesce","args":[{"type":"any"},{"type":"any"},{"type":"any","mode":"v"}],"returns":"any"}
@@ -30,7 +69,20 @@
3069
{"name":"dense_rank","kind":"w","returns":"integer"}
3170
{"name":"exp","args":[{"type":"real"}],"returns":"real"}
3271
{"name":"first_value","kind":"w","args":[{"type":"any"}],"returns":"any","nullable":true}
33-
{"name":"floor","args":[{"type":"any"}],"returns":"$1"}
72+
{"name":"floor","args":[{"type":"any"}],"returns":"real"}
73+
{"name":"floor","args":[{"type":"integer"}],"returns":"integer"}
74+
{"name":"floor","args":[{"type":"int"}],"returns":"integer"}
75+
{"name":"floor","args":[{"type":"tinyint"}],"returns":"integer"}
76+
{"name":"floor","args":[{"type":"smallint"}],"returns":"integer"}
77+
{"name":"floor","args":[{"type":"mediumint"}],"returns":"integer"}
78+
{"name":"floor","args":[{"type":"bigint"}],"returns":"integer"}
79+
{"name":"floor","args":[{"type":"unsigned big int"}],"returns":"integer"}
80+
{"name":"floor","args":[{"type":"int2"}],"returns":"integer"}
81+
{"name":"floor","args":[{"type":"int8"}],"returns":"integer"}
82+
{"name":"floor","args":[{"type":"real"}],"returns":"real"}
83+
{"name":"floor","args":[{"type":"double"}],"returns":"real"}
84+
{"name":"floor","args":[{"type":"double precision"}],"returns":"real"}
85+
{"name":"floor","args":[{"type":"float"}],"returns":"real"}
3486
{"name":"format","args":[{"type":"text","has_default":true},{"type":"any","mode":"v"}],"returns":"text"}
3587
{"name":"glob","args":[{"type":"text"},{"type":"text"}],"returns":"integer"}
3688
{"name":"group_concat","kind":"a","args":[{"type":"text"}],"returns":"text","nullable":true}
@@ -136,7 +188,20 @@
136188
{"name":"substring","args":[{"type":"any"},{"type":"integer"}],"returns":"text"}
137189
{"name":"substring","args":[{"type":"any"},{"type":"integer"},{"type":"integer"}],"returns":"text"}
138190
{"name":"subtype","args":[{"type":"any"}],"returns":"integer"}
139-
{"name":"sum","kind":"a","args":[{"type":"any"}],"returns":"$1","nullable":true}
191+
{"name":"sum","kind":"a","args":[{"type":"any"}],"returns":"real","nullable":true}
192+
{"name":"sum","kind":"a","args":[{"type":"integer"}],"returns":"integer","nullable":true}
193+
{"name":"sum","kind":"a","args":[{"type":"int"}],"returns":"integer","nullable":true}
194+
{"name":"sum","kind":"a","args":[{"type":"tinyint"}],"returns":"integer","nullable":true}
195+
{"name":"sum","kind":"a","args":[{"type":"smallint"}],"returns":"integer","nullable":true}
196+
{"name":"sum","kind":"a","args":[{"type":"mediumint"}],"returns":"integer","nullable":true}
197+
{"name":"sum","kind":"a","args":[{"type":"bigint"}],"returns":"integer","nullable":true}
198+
{"name":"sum","kind":"a","args":[{"type":"unsigned big int"}],"returns":"integer","nullable":true}
199+
{"name":"sum","kind":"a","args":[{"type":"int2"}],"returns":"integer","nullable":true}
200+
{"name":"sum","kind":"a","args":[{"type":"int8"}],"returns":"integer","nullable":true}
201+
{"name":"sum","kind":"a","args":[{"type":"real"}],"returns":"real","nullable":true}
202+
{"name":"sum","kind":"a","args":[{"type":"double"}],"returns":"real","nullable":true}
203+
{"name":"sum","kind":"a","args":[{"type":"double precision"}],"returns":"real","nullable":true}
204+
{"name":"sum","kind":"a","args":[{"type":"float"}],"returns":"real","nullable":true}
140205
{"name":"tan","args":[{"type":"real"}],"returns":"real"}
141206
{"name":"tanh","args":[{"type":"real"}],"returns":"real"}
142207
{"name":"time","args":[{"type":"any","mode":"v"}],"returns":"text","nullable":true}
@@ -145,7 +210,20 @@
145210
{"name":"total_changes","returns":"integer"}
146211
{"name":"trim","args":[{"type":"text"}],"returns":"text"}
147212
{"name":"trim","args":[{"type":"text"},{"type":"text"}],"returns":"text"}
148-
{"name":"trunc","args":[{"type":"any"}],"returns":"$1"}
213+
{"name":"trunc","args":[{"type":"any"}],"returns":"real"}
214+
{"name":"trunc","args":[{"type":"integer"}],"returns":"integer"}
215+
{"name":"trunc","args":[{"type":"int"}],"returns":"integer"}
216+
{"name":"trunc","args":[{"type":"tinyint"}],"returns":"integer"}
217+
{"name":"trunc","args":[{"type":"smallint"}],"returns":"integer"}
218+
{"name":"trunc","args":[{"type":"mediumint"}],"returns":"integer"}
219+
{"name":"trunc","args":[{"type":"bigint"}],"returns":"integer"}
220+
{"name":"trunc","args":[{"type":"unsigned big int"}],"returns":"integer"}
221+
{"name":"trunc","args":[{"type":"int2"}],"returns":"integer"}
222+
{"name":"trunc","args":[{"type":"int8"}],"returns":"integer"}
223+
{"name":"trunc","args":[{"type":"real"}],"returns":"real"}
224+
{"name":"trunc","args":[{"type":"double"}],"returns":"real"}
225+
{"name":"trunc","args":[{"type":"double precision"}],"returns":"real"}
226+
{"name":"trunc","args":[{"type":"float"}],"returns":"real"}
149227
{"name":"typeof","args":[{"type":"any"}],"returns":"text"}
150228
{"name":"unhex","args":[{"type":"text"}],"returns":"blob","nullable":true}
151229
{"name":"unhex","args":[{"type":"text"},{"type":"text"}],"returns":"blob","nullable":true}

‎internal/goldeneye/README.md‎

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,8 +80,15 @@ the hand-written files alone, and the checks do not look at them.
8080
`sqlite/signatures.go`, since a SQLite function returns NULL as often by
8181
setting no result as by saying so. The pinned release is the one the main module's
8282
driver embeds. SQLite has no catalog of types or operators, so
83-
`types.jsonl` and `operators.jsonl` are hand-written. `install` builds one
84-
more shell, for the analysis check below; nothing is generated from it.
83+
`types.jsonl` and `operators.jsonl` are hand-written. A function that
84+
returns an integer for an integer and a real for a real — `abs`, `ceil`,
85+
`floor`, `trunc`, `sum` — is written once over `any`, returning the real
86+
that a text or blob argument gets, and once more per spelling
87+
`types.jsonl` gives integer and real, returning that type, the way
88+
PostgreSQL's catalog has a `sum` per numeric type; the overload over `any`
89+
comes first, since the legacy compiler resolves by arity alone and takes
90+
it. `install` builds one more shell, for the analysis check below;
91+
nothing is generated from it.
8592

8693
## Layout
8794

‎internal/goldeneye/dialect/dialect.go‎

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,25 @@ func JSONL[T any](records []T) ([]byte, error) {
118118
return buf.Bytes(), nil
119119
}
120120

121+
// ReadTypes reads the hand-written types.jsonl of a dialect directory, for
122+
// a generator that writes a function once per spelling of a type.
123+
func ReadTypes(dir string) ([]Type, error) {
124+
blob, err := os.ReadFile(filepath.Join(dir, TypesFile))
125+
if err != nil {
126+
return nil, err
127+
}
128+
var types []Type
129+
dec := json.NewDecoder(bytes.NewReader(blob))
130+
for dec.More() {
131+
var t Type
132+
if err := dec.Decode(&t); err != nil {
133+
return nil, fmt.Errorf("%s: %w", filepath.Join(dir, TypesFile), err)
134+
}
135+
types = append(types, t)
136+
}
137+
return types, nil
138+
}
139+
121140
// Dir returns the dialect directory of an engine,
122141
// internal/engine/<engine>/dialect, found relative to this source file so
123142
// the working directory does not matter.

‎internal/goldeneye/sqlite/signatures.go‎

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,14 @@ import "github.com/sqlc-dev/sqlc/internal/goldeneye/dialect"
1010
// many arguments an overload takes, and how many of them it requires,
1111
// comes from the shell. Nullable is decided afterwards: for an aggregate by
1212
// running it over no rows, for a scalar by the nullable list below.
13+
// Numeric says the function returns an integer for an integer argument
14+
// and a real for a real one, and Returns what it returns for anything
15+
// else.
1316
type signature struct {
1417
Args []string
1518
Variadic string
1619
Returns string
20+
Numeric bool
1721
Nullable bool
1822
}
1923

@@ -59,8 +63,8 @@ var inlineReturns = map[string]string{
5963

6064
// realResults are the functions that return an integer or a real on
6165
// something other than which their argument is, and so are typed real
62-
// rather than as the argument: unixepoch takes a time string and returns a
63-
// real only with the 'subsec' modifier.
66+
// alone: unixepoch takes a time string and returns a real only with the
67+
// 'subsec' modifier.
6468
var realResults = map[string]bool{
6569
"unixepoch": true,
6670
}

‎internal/goldeneye/sqlite/source.go‎

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -442,12 +442,12 @@ func single(kinds map[string]bool) string {
442442
// A result of one kind is that type. A function that returns one of its
443443
// arguments, or a mixture of kinds, takes the type of its first argument,
444444
// which the seed spells "any" — except that integer and real together are
445-
// the argument's own type, spelled "$1", since abs, ceil and sum hand back
446-
// an integer for an integer and a real for a real, unless the function is
447-
// one of the few whose choice turns on something else; and text and blob
448-
// together are text, since a function that returns either is handing back
449-
// the bytes it was given, and the legacy compiler cannot follow "any" to
450-
// an argument.
445+
// real, and Numeric, since abs, ceil and sum hand back an integer for an
446+
// integer and a real for a real, and a real for the text or blob they
447+
// convert, unless the function is one of the few whose choice turns on
448+
// something else; and text and blob together are text, since a function
449+
// that returns either is handing back the bytes it was given, and the
450+
// legacy compiler cannot follow "any" to an argument.
451451
func (s *source) signature(name string) (signature, error) {
452452
r, ok := s.regs[strings.ToLower(name)]
453453
if !ok {
@@ -484,11 +484,8 @@ func (s *source) signature(name string) (signature, error) {
484484
// registration says which this form gets.
485485
sig.Returns = r.json
486486
case len(kinds) == 2 && kinds["integer"] && kinds["real"]:
487-
if realResults[strings.ToLower(name)] {
488-
sig.Returns = "real"
489-
} else {
490-
sig.Returns = "$1"
491-
}
487+
sig.Returns = "real"
488+
sig.Numeric = !realResults[strings.ToLower(name)]
492489
case len(kinds) == 2 && kinds["text"] && kinds["blob"]:
493490
sig.Returns = "text"
494491
default:

‎internal/goldeneye/sqlite/sqlite.go‎

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,9 @@ type generator struct {
231231
ctx context.Context
232232
src *source
233233
reported map[string]bool
234+
// numeric holds every spelling types.jsonl gives integer and real, in
235+
// its order, for the functions written once per spelling.
236+
numeric map[string][]string
234237
}
235238

236239
// overload is one row of a shell's list with what was found out about it.
@@ -283,13 +286,30 @@ func (g *generator) functions(s *shell, base bool) ([]dialect.Function, error) {
283286
if o.kind == "a" {
284287
isNullable = empty[o.row.key()] == "null"
285288
}
286-
funcs = append(funcs, dialect.Function{
289+
fn := dialect.Function{
287290
Name: o.row.Name,
288291
Kind: o.kind,
289292
Args: o.sig.args(o.row.NArg),
290293
Returns: o.sig.Returns,
291294
Nullable: isNullable,
292-
})
295+
}
296+
funcs = append(funcs, fn)
297+
// A function that returns an integer for an integer and a real for
298+
// a real is written once more per spelling of each, the way
299+
// PostgreSQL's catalog has a sum per numeric type. The overload over
300+
// any comes first: the analysis core picks the overload whose
301+
// parameter is the argument's type, but the legacy compiler takes
302+
// the first of the right arity, and keeps what it had.
303+
if o.sig.Numeric && len(fn.Args) > 0 {
304+
for _, typ := range []string{"integer", "real"} {
305+
for _, spelling := range g.numeric[typ] {
306+
typed := fn
307+
typed.Args = append([]dialect.Arg{{Type: spelling}}, fn.Args[1:]...)
308+
typed.Returns = typ
309+
funcs = append(funcs, typed)
310+
}
311+
}
312+
}
293313
}
294314
return funcs, nil
295315
}
@@ -365,7 +385,20 @@ func Generate(ctx context.Context, dir string) (dialect.Files, error) {
365385
if err != nil {
366386
return nil, err
367387
}
368-
g := &generator{ctx: ctx, src: src, reported: map[string]bool{}}
388+
g := &generator{ctx: ctx, src: src, reported: map[string]bool{}, numeric: map[string][]string{}}
389+
dialectDir, err := dialect.Dir(Engine)
390+
if err != nil {
391+
return nil, err
392+
}
393+
types, err := dialect.ReadTypes(dialectDir)
394+
if err != nil {
395+
return nil, err
396+
}
397+
for _, t := range types {
398+
if t.Name == "integer" || t.Name == "real" {
399+
g.numeric[t.Name] = append([]string{t.Name}, t.Aliases...)
400+
}
401+
}
369402
all := builds()
370403
base, err := readShell(ctx, dir, all[0])
371404
if err != nil {

0 commit comments

Comments
 (0)