Skip to content

Commit 81f0531

Browse files
committed
goldeneye: move the shape of an engine's answer into its own package
The JSON sqlc analyze prints — a query, its columns, its parameters and their types — lived in endtoend beside the case finder. It is its own thing, so it moves to the analysis package, and the Analyzed prefix goes with it: analysis.Query, analysis.Column, analysis.Param. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PdKaVjSmdkmXPeUkxetyDY
1 parent 550bdbb commit 81f0531

9 files changed

Lines changed: 107 additions & 105 deletions

File tree

‎internal/goldeneye/README.md‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -96,8 +96,8 @@ the hand-written files alone, and the checks do not look at them.
9696
`internal/core/seed`, and the helpers that write a generated set of files
9797
into an engine directory or diff it against what is committed.
9898
- `endtoend/` — finds the analyze cases, splits their query files, and
99-
holds the shape of an engine's answer, which it compares with a case's
100-
committed output.
99+
compares an engine's answer with a case's committed output.
100+
- `analysis/` — the shape of that answer: the JSON `sqlc analyze` prints.
101101
- `postgresql/`, `duckdb/`, `clickhouse/`, `sqlite/` — one package per
102102
engine, each exposing `Locate`, `Version` and `Generate`, `Analyze` where
103103
the engine has an analysis check, and tests that run the checks.
Lines changed: 18 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,33 @@
1-
package endtoend
1+
// Package analysis is the shape of an engine's answer about a query: the
2+
// JSON `sqlc analyze` prints, so that a case's committed stdout.json can be
3+
// compared with what the database itself reports, byte for byte.
4+
package analysis
25

36
import (
47
"bytes"
58
"encoding/json"
69
)
710

8-
// An engine's answer is written in the JSON `sqlc analyze` prints, so that
9-
// a case's committed stdout.json can be compared with it byte for byte.
10-
11-
// AnalyzedQuery is what was found out about one query.
12-
type AnalyzedQuery struct {
13-
Name string `json:"name"`
14-
Cmd string `json:"cmd"`
15-
Columns []AnalyzedColumn `json:"columns"`
16-
Params []AnalyzedParam `json:"params"`
11+
// Query is what was found out about one query.
12+
type Query struct {
13+
Name string `json:"name"`
14+
Cmd string `json:"cmd"`
15+
Columns []Column `json:"columns"`
16+
Params []Param `json:"params"`
1717
}
1818

19-
// AnalyzedColumn describes a result column, or the column a parameter
20-
// stands in for.
21-
type AnalyzedColumn struct {
19+
// Column describes a result column, or the column a parameter stands in
20+
// for.
21+
type Column struct {
2222
Name string `json:"name"`
2323
Type *TypeExpr `json:"type,omitempty"`
2424
Table string `json:"table,omitempty"`
2525
}
2626

27-
// AnalyzedParam is one parameter and what it is compared with or assigned
28-
// to.
29-
type AnalyzedParam struct {
30-
Number int `json:"number"`
31-
Column AnalyzedColumn `json:"column"`
27+
// Param is one parameter and what it is compared with or assigned to.
28+
type Param struct {
29+
Number int `json:"number"`
30+
Column Column `json:"column"`
3231
}
3332

3433
// TypeExpr is a type as a call expression: a lowercased name applied to an
@@ -51,7 +50,7 @@ type TypeArg struct {
5150
}
5251

5352
// Encode prints the answer the way sqlc analyze does.
54-
func Encode(queries []AnalyzedQuery) ([]byte, error) {
53+
func Encode(queries []Query) ([]byte, error) {
5554
var buf bytes.Buffer
5655
enc := json.NewEncoder(&buf)
5756
enc.SetIndent("", " ")

‎internal/goldeneye/clickhouse/analyze.go‎

Lines changed: 26 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,14 @@ import (
88
"strconv"
99
"strings"
1010

11+
"github.com/sqlc-dev/sqlc/internal/goldeneye/analysis"
1112
"github.com/sqlc-dev/sqlc/internal/goldeneye/endtoend"
1213
)
1314

1415
// analyze runs every query against the schema and fixture and records what
1516
// ClickHouse reports about each.
16-
func analyze(ctx context.Context, l local, schema, fixture string, queries []endtoend.Query) ([]endtoend.AnalyzedQuery, error) {
17-
out := make([]endtoend.AnalyzedQuery, 0, len(queries))
17+
func analyze(ctx context.Context, l local, schema, fixture string, queries []endtoend.Query) ([]analysis.Query, error) {
18+
out := make([]analysis.Query, 0, len(queries))
1819
for _, q := range queries {
1920
aq, err := analyzeQuery(ctx, l, schema, fixture, q)
2021
if err != nil {
@@ -25,7 +26,7 @@ func analyze(ctx context.Context, l local, schema, fixture string, queries []end
2526
return out, nil
2627
}
2728

28-
func analyzeQuery(ctx context.Context, l local, schema, fixture string, q endtoend.Query) (endtoend.AnalyzedQuery, error) {
29+
func analyzeQuery(ctx context.Context, l local, schema, fixture string, q endtoend.Query) (analysis.Query, error) {
2930
sql, phs := bindPlaceholders(q.SQL)
3031
explain := returnsRows(sql)
3132

@@ -43,33 +44,33 @@ func analyzeQuery(ctx context.Context, l local, schema, fixture string, q endtoe
4344

4445
results, err := l.run(ctx, script.String())
4546
if err != nil {
46-
return endtoend.AnalyzedQuery{}, err
47+
return analysis.Query{}, err
4748
}
4849

49-
aq := endtoend.AnalyzedQuery{
50+
aq := analysis.Query{
5051
Name: q.Name,
5152
Cmd: q.Cmd,
52-
Columns: []endtoend.AnalyzedColumn{},
53-
Params: []endtoend.AnalyzedParam{},
53+
Columns: []analysis.Column{},
54+
Params: []analysis.Param{},
5455
}
5556
if !explain {
5657
return analyzeExec(ctx, l, script.String(), sql, phs, aq)
5758
}
5859
if len(results) != 2 {
59-
return endtoend.AnalyzedQuery{}, fmt.Errorf("expected the query tree and one result set, got %d results", len(results))
60+
return analysis.Query{}, fmt.Errorf("expected the query tree and one result set, got %d results", len(results))
6061
}
6162

6263
var lines []string
6364
for _, row := range results[0].Data {
6465
var line string
6566
if err := json.Unmarshal(row["explain"], &line); err != nil {
66-
return endtoend.AnalyzedQuery{}, fmt.Errorf("reading query tree: %w", err)
67+
return analysis.Query{}, fmt.Errorf("reading query tree: %w", err)
6768
}
6869
lines = append(lines, line)
6970
}
7071
tree, err := parseQueryTree(lines)
7172
if err != nil {
72-
return endtoend.AnalyzedQuery{}, err
73+
return analysis.Query{}, err
7374
}
7475

7576
// Names and types come from the block header of the executed query, the
@@ -85,23 +86,23 @@ func analyzeQuery(ctx context.Context, l local, schema, fixture string, q endtoe
8586

8687
sentinels := tree.sentinels()
8788
for i, ph := range phs {
88-
ac := endtoend.AnalyzedColumn{}
89+
ac := analysis.Column{}
8990
if sentinel := sentinels[i+1]; sentinel != nil {
9091
ac = tree.paramColumn(sentinel)
9192
}
9293
if ph.Name != "" {
9394
ac.Name = ph.Name
9495
}
95-
aq.Params = append(aq.Params, endtoend.AnalyzedParam{Number: ph.Number, Column: ac})
96+
aq.Params = append(aq.Params, analysis.Param{Number: ph.Number, Column: ac})
9697
}
9798
return aq, nil
9899
}
99100

100-
func column(name, typ string) endtoend.AnalyzedColumn {
101+
func column(name, typ string) analysis.Column {
101102
if typ == "" {
102-
return endtoend.AnalyzedColumn{Name: name}
103+
return analysis.Column{Name: name}
103104
}
104-
return endtoend.AnalyzedColumn{Name: name, Type: parseType(typ)}
105+
return analysis.Column{Name: name, Type: parseType(typ)}
105106
}
106107

107108
// returnsRows reports whether a statement produces a result set and so can
@@ -166,7 +167,7 @@ func sentinelOrdinal(c *treeNode) (int, bool) {
166167
// paramColumn describes what a placeholder is compared with or assigned to:
167168
// the other operand of the function it is an argument of, preferring a
168169
// column over an expression, or the projected column it stands for.
169-
func (t *queryTree) paramColumn(sentinel *treeNode) endtoend.AnalyzedColumn {
170+
func (t *queryTree) paramColumn(sentinel *treeNode) analysis.Column {
170171
list := sentinel.parent
171172
if list != nil && list.kind == "LIST" && list.parent != nil {
172173
switch owner := list.parent; {
@@ -199,7 +200,7 @@ func (t *queryTree) paramColumn(sentinel *treeNode) endtoend.AnalyzedColumn {
199200
}
200201

201202
// describe turns a tree expression into a column description.
202-
func (t *queryTree) describe(n *treeNode) endtoend.AnalyzedColumn {
203+
func (t *queryTree) describe(n *treeNode) analysis.Column {
203204
switch n.kind {
204205
case "COLUMN":
205206
ac := column(n.attrs["column_name"], n.attrs["result_type"])
@@ -217,28 +218,28 @@ func (t *queryTree) describe(n *treeNode) endtoend.AnalyzedColumn {
217218
}
218219
return column(name, n.attrs["constant_value_type"])
219220
}
220-
return endtoend.AnalyzedColumn{}
221+
return analysis.Column{}
221222
}
222223

223224
var insertValuesRe = regexp.MustCompile(`(?is)^insert\s+into\s+(?:table\s+)?([\w.` + "`" + `"]+)\s*(?:\(([^)]*)\))?\s*(?:format\s+)?values\b`)
224225

225226
// analyzeExec runs a statement that returns no rows. The only parameters it
226227
// can describe are those of an INSERT ... VALUES, which map positionally
227228
// onto the target columns reported by DESCRIBE TABLE.
228-
func analyzeExec(ctx context.Context, l local, script, sql string, phs []placeholder, aq endtoend.AnalyzedQuery) (endtoend.AnalyzedQuery, error) {
229+
func analyzeExec(ctx context.Context, l local, script, sql string, phs []placeholder, aq analysis.Query) (analysis.Query, error) {
229230
m := insertValuesRe.FindStringSubmatch(sql)
230231
if m != nil {
231232
script += "DESCRIBE TABLE " + m[1] + ";\n"
232233
}
233234
results, err := l.run(ctx, script)
234235
if err != nil {
235-
return endtoend.AnalyzedQuery{}, err
236+
return analysis.Query{}, err
236237
}
237238

238-
var targets []endtoend.AnalyzedColumn
239+
var targets []analysis.Column
239240
if m != nil && len(results) == 1 {
240-
byName := map[string]endtoend.AnalyzedColumn{}
241-
var all []endtoend.AnalyzedColumn
241+
byName := map[string]analysis.Column{}
242+
var all []analysis.Column
242243
table := strings.Trim(m[1][strings.LastIndexByte(m[1], '.')+1:], "`\"")
243244
for _, row := range results[0].Data {
244245
var name, typ string
@@ -258,14 +259,14 @@ func analyzeExec(ctx context.Context, l local, script, sql string, phs []placeho
258259
}
259260
}
260261
for i, ph := range phs {
261-
ac := endtoend.AnalyzedColumn{}
262+
ac := analysis.Column{}
262263
if len(targets) > 0 {
263264
ac = targets[i%len(targets)]
264265
}
265266
if ph.Name != "" {
266267
ac.Name = ph.Name
267268
}
268-
aq.Params = append(aq.Params, endtoend.AnalyzedParam{Number: ph.Number, Column: ac})
269+
aq.Params = append(aq.Params, analysis.Param{Number: ph.Number, Column: ac})
269270
}
270271
return aq, nil
271272
}

‎internal/goldeneye/clickhouse/check.go‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"context"
55
"os"
66

7+
"github.com/sqlc-dev/sqlc/internal/goldeneye/analysis"
78
"github.com/sqlc-dev/sqlc/internal/goldeneye/endtoend"
89
)
910

@@ -28,7 +29,7 @@ func Analyze(ctx context.Context, binary string, c endtoend.Case) ([]byte, error
2829
if err != nil {
2930
return nil, err
3031
}
31-
return endtoend.Encode(out)
32+
return analysis.Encode(out)
3233
}
3334

3435
// Check compares what ClickHouse reports for a case with the output the

‎internal/goldeneye/clickhouse/types.go‎

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import (
44
"strconv"
55
"strings"
66

7-
"github.com/sqlc-dev/sqlc/internal/goldeneye/endtoend"
7+
"github.com/sqlc-dev/sqlc/internal/goldeneye/analysis"
88
)
99

1010
// A type is a call expression, the way ClickHouse itself models one: a
@@ -38,7 +38,7 @@ import (
3838
// is the reader's job; the output only records what was said.
3939

4040
// parseType turns a ClickHouse type string into its expression.
41-
func parseType(t string) *endtoend.TypeExpr {
41+
func parseType(t string) *analysis.TypeExpr {
4242
name, args := splitType(t)
4343
name = strings.ToLower(strings.TrimSpace(name))
4444
if name == "nullable" && len(args) == 1 {
@@ -49,7 +49,7 @@ func parseType(t string) *endtoend.TypeExpr {
4949
if name == "" {
5050
name = "nothing"
5151
}
52-
expr := &endtoend.TypeExpr{Name: name}
52+
expr := &analysis.TypeExpr{Name: name}
5353
for _, a := range args {
5454
expr.Args = append(expr.Args, parseArg(a))
5555
}
@@ -59,7 +59,7 @@ func parseType(t string) *endtoend.TypeExpr {
5959
// parseArg parses one argument: a quoted string, an integer, a boolean, a
6060
// labelled argument (`lat Float64` in a Tuple, `'a' = 1` in an Enum), or a
6161
// type.
62-
func parseArg(a string) endtoend.TypeArg {
62+
func parseArg(a string) analysis.TypeArg {
6363
a = strings.TrimSpace(a)
6464
if strings.HasPrefix(a, "'") {
6565
end := skipQuoted(a, 0)
@@ -69,22 +69,22 @@ func parseArg(a string) endtoend.TypeArg {
6969
arg.Label = lit
7070
return arg
7171
}
72-
return endtoend.TypeArg{String: &lit}
72+
return analysis.TypeArg{String: &lit}
7373
}
7474
if n, err := strconv.ParseInt(a, 10, 64); err == nil {
75-
return endtoend.TypeArg{Int: &n}
75+
return analysis.TypeArg{Int: &n}
7676
}
7777
switch strings.ToLower(a) {
7878
case "true", "false":
7979
b := strings.EqualFold(a, "true")
80-
return endtoend.TypeArg{Bool: &b}
80+
return analysis.TypeArg{Bool: &b}
8181
}
8282
if i := labelEnd(a); i > 0 {
8383
arg := parseArg(a[i+1:])
8484
arg.Label = a[:i]
8585
return arg
8686
}
87-
return endtoend.TypeArg{Type: parseType(a)}
87+
return analysis.TypeArg{Type: parseType(a)}
8888
}
8989

9090
// labelEnd returns the index of the space separating a label from the type

0 commit comments

Comments
 (0)