Skip to content

Commit 2d75b8c

Browse files
committed
goldeneye: install DuckDB 2.0 preview builds and check its analyze cases
DuckDB 2.0 has no release to download yet, so `install duckdb` fetches the current build of DuckDB's v2.0 preview channel, a rolling tarball per platform with no per-build download and no checksum to pin, into the user cache directory, where Locate finds it after DUCKDB and before PATH. GeneratedFrom records the build the dialect was generated from, and the dialect is regenerated from the channel's current build, which adds a few functions and lists each type alias once per schema it is visible in, which the generator now folds. The duckdb engine gains an analysis check over the DuckDB analyze cases. The CLI reports a parameter's type through the unoptimized logical plan of the prepared query explained with a string sentinel bound to each parameter, and a result column's name and type through DESCRIBE with a typed NULL in each parameter's place; it prints every column by its bare name, so which table a result column is read from and which column a parameter stands in for come from the query text resolved against the catalog, and since it tracks no nullability of expressions, the query is run over the fixture and over no rows to see which columns come back NULL. A spelling the generated types.jsonl lists as an alias, such as json for varchar, is reported by the dialect's name for it. The gen workflow gets a duckdb job that installs and generates from the channel, and the README and CLAUDE.md describe the engine. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01F7cPsawATXMfiYqWg8nBVb
1 parent f699524 commit 2d75b8c

11 files changed

Lines changed: 1844 additions & 19 deletions

File tree

‎.github/workflows/gen.yml‎

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,3 +166,27 @@ jobs:
166166
path: internal/engine/googlesql/dialect
167167
- name: Fail if the committed dialect differs
168168
run: git add -N internal/engine/googlesql && git diff --exit-code --stat -- internal/engine/googlesql
169+
170+
duckdb:
171+
name: generate duckdb dialect
172+
runs-on: ubuntu-24.04
173+
steps:
174+
- uses: actions/checkout@v7
175+
- uses: actions/setup-go@v7
176+
with:
177+
go-version-file: internal/goldeneye/go.mod
178+
check-latest: true
179+
# The current build of DuckDB's v2.0 preview channel, until 2.0 is
180+
# released: a later build than the dialect was generated from shows up
181+
# as a difference.
182+
- run: go run ./cmd/goldeneye install duckdb
183+
working-directory: internal/goldeneye
184+
- run: go run ./cmd/goldeneye generate duckdb
185+
working-directory: internal/goldeneye
186+
- name: Save results
187+
uses: actions/upload-artifact@v7
188+
with:
189+
name: dialect-duckdb
190+
path: internal/engine/duckdb/dialect
191+
- name: Fail if the committed dialect differs
192+
run: git add -N internal/engine/duckdb && git diff --exit-code --stat -- internal/engine/duckdb

‎CLAUDE.md‎

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -150,13 +150,14 @@ from a live database by `/internal/goldeneye`, a nested module, and its tests
150150
verify the committed files against one byte for byte. The same module checks
151151
the `analyze_*` cases under `/internal/endtoend/testdata/` against what the
152152
database itself reports for them, so a `fixture.sql` next to a case's schema
153-
gives the queries rows to run against. ClickHouse, MySQL, SQLite, SQL Server
154-
and Spanner have the check today; engines whose database is not available
155-
skip.
153+
gives the queries rows to run against. ClickHouse, DuckDB, MySQL, SQLite,
154+
SQL Server and Spanner have the check today; engines whose database is not
155+
available skip.
156156

157157
```bash
158158
cd internal/goldeneye
159159
go run ./cmd/goldeneye install clickhouse # download the pinned clickhouse binary once
160+
go run ./cmd/goldeneye install duckdb # download the current DuckDB 2.0 preview build once
160161
go run ./cmd/goldeneye install sqlite # build the pinned sqlite3 shells once; needs a C compiler
161162
POSTGRESQL_SERVER_URI="postgres://postgres:postgres@127.0.0.1:5432/postgres?sslmode=disable" \
162163
MYSQL_SERVER_URI="root:mysecretpassword@tcp(127.0.0.1:3306)/mysql" \

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

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
{"name":"__between","args":[{"type":"any"},{"type":"any"},{"type":"any"}],"returns":"boolean"}
22
{"name":"__cast","args":[{"type":"any"}],"returns":"any"}
3+
{"name":"__internal_barrier","args":[{"type":"any"}],"returns":"any"}
34
{"name":"__internal_compress_geometry_point","args":[{"type":"geometry"}],"returns":"uhugeint"}
45
{"name":"__internal_compress_integral_ubigint","args":[{"type":"hugeint"},{"type":"hugeint"}],"returns":"ubigint"}
56
{"name":"__internal_compress_integral_ubigint","args":[{"type":"uhugeint"},{"type":"uhugeint"}],"returns":"ubigint"}
@@ -2198,6 +2199,7 @@
21982199
{"name":"regexp_extract","args":[{"type":"varchar"},{"type":"varchar"},{"type":"varchar[]"}],"returns":"varchar"}
21992200
{"name":"regexp_extract","args":[{"type":"varchar"},{"type":"varchar"},{"type":"integer"},{"type":"varchar"}],"returns":"varchar"}
22002201
{"name":"regexp_extract","args":[{"type":"varchar"},{"type":"varchar"},{"type":"integer"}],"returns":"varchar"}
2202+
{"name":"regexp_extract","args":[{"type":"varchar"},{"type":"varchar"},{"type":"varchar"}],"returns":"varchar"}
22012203
{"name":"regexp_extract","args":[{"type":"varchar"},{"type":"varchar"}],"returns":"varchar"}
22022204
{"name":"regexp_extract_all","args":[{"type":"varchar"},{"type":"varchar"},{"type":"varchar[]"},{"type":"varchar"}],"returns":"varchar[]"}
22032205
{"name":"regexp_extract_all","args":[{"type":"varchar"},{"type":"varchar"},{"type":"varchar[]"}],"returns":"varchar[]"}
@@ -2278,6 +2280,22 @@
22782280
{"name":"round","args":[{"type":"smallint"}],"returns":"smallint"}
22792281
{"name":"round","args":[{"type":"tinyint"},{"type":"integer"}],"returns":"tinyint"}
22802282
{"name":"round","args":[{"type":"tinyint"}],"returns":"tinyint"}
2283+
{"name":"round_even","args":[{"type":"bigint"},{"type":"integer"}],"returns":"bigint"}
2284+
{"name":"round_even","args":[{"type":"decimal"},{"type":"integer"}],"returns":"decimal"}
2285+
{"name":"round_even","args":[{"type":"double"},{"type":"integer"}],"returns":"double"}
2286+
{"name":"round_even","args":[{"type":"float"},{"type":"integer"}],"returns":"float"}
2287+
{"name":"round_even","args":[{"type":"hugeint"},{"type":"integer"}],"returns":"hugeint"}
2288+
{"name":"round_even","args":[{"type":"integer"},{"type":"integer"}],"returns":"integer"}
2289+
{"name":"round_even","args":[{"type":"smallint"},{"type":"integer"}],"returns":"smallint"}
2290+
{"name":"round_even","args":[{"type":"tinyint"},{"type":"integer"}],"returns":"tinyint"}
2291+
{"name":"roundbankers","args":[{"type":"bigint"},{"type":"integer"}],"returns":"bigint"}
2292+
{"name":"roundbankers","args":[{"type":"decimal"},{"type":"integer"}],"returns":"decimal"}
2293+
{"name":"roundbankers","args":[{"type":"double"},{"type":"integer"}],"returns":"double"}
2294+
{"name":"roundbankers","args":[{"type":"float"},{"type":"integer"}],"returns":"float"}
2295+
{"name":"roundbankers","args":[{"type":"hugeint"},{"type":"integer"}],"returns":"hugeint"}
2296+
{"name":"roundbankers","args":[{"type":"integer"},{"type":"integer"}],"returns":"integer"}
2297+
{"name":"roundbankers","args":[{"type":"smallint"},{"type":"integer"}],"returns":"smallint"}
2298+
{"name":"roundbankers","args":[{"type":"tinyint"},{"type":"integer"}],"returns":"tinyint"}
22812299
{"name":"row","args":[{"type":"any","mode":"v"}],"returns":"tuple"}
22822300
{"name":"row_number","kind":"w","returns":"bigint"}
22832301
{"name":"row_to_json","args":[{"type":"any","mode":"v"}],"returns":"json"}
@@ -2457,7 +2475,9 @@
24572475
{"name":"timezone","args":[{"type":"time_ns"}],"returns":"bigint"}
24582476
{"name":"timezone","args":[{"type":"varchar"},{"type":"time with time zone"}],"returns":"time with time zone"}
24592477
{"name":"timezone","args":[{"type":"varchar"},{"type":"timestamp with time zone"}],"returns":"timestamp"}
2478+
{"name":"timezone","args":[{"type":"varchar"},{"type":"timestamptz_ns"}],"returns":"timestamp_ns"}
24602479
{"name":"timezone","args":[{"type":"varchar"},{"type":"timestamp"}],"returns":"timestamp with time zone"}
2480+
{"name":"timezone","args":[{"type":"varchar"},{"type":"timestamp_ns"}],"returns":"timestamptz_ns"}
24612481
{"name":"timezone_hour","args":[{"type":"date"}],"returns":"bigint"}
24622482
{"name":"timezone_hour","args":[{"type":"interval"}],"returns":"bigint"}
24632483
{"name":"timezone_hour","args":[{"type":"time with time zone"}],"returns":"bigint"}
@@ -2580,6 +2600,7 @@
25802600
{"name":"variant_extract","args":[{"type":"variant"},{"type":"varchar"}],"returns":"variant"}
25812601
{"name":"variant_extract_string","args":[{"type":"variant"},{"type":"varchar[]"}],"returns":"varchar[]"}
25822602
{"name":"variant_extract_string","args":[{"type":"variant"},{"type":"varchar"}],"returns":"varchar"}
2603+
{"name":"variant_group_object","kind":"a","args":[{"type":"varchar"},{"type":"variant"}],"returns":"variant","nullable":true}
25832604
{"name":"variant_keys","args":[{"type":"variant"},{"type":"varchar[]"}],"returns":"varchar[][]"}
25842605
{"name":"variant_keys","args":[{"type":"variant"},{"type":"varchar"}],"returns":"varchar[]"}
25852606
{"name":"variant_keys","args":[{"type":"variant"}],"returns":"varchar[]"}

‎internal/goldeneye/README.md‎

Lines changed: 43 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ the files: the files are the contract. Run it from this directory:
1616

1717
```bash
1818
go run ./cmd/goldeneye install clickhouse # download the pinned clickhouse binary once
19+
go run ./cmd/goldeneye install duckdb # download the current DuckDB 2.0 preview build once
1920
go run ./cmd/goldeneye install sqlite # build the pinned sqlite3 shells once; needs a C compiler
2021
go run ./cmd/goldeneye check # check every engine whose database is available
2122
go run ./cmd/goldeneye check postgresql # check one engine
@@ -61,10 +62,20 @@ the hand-written files alone, and the checks do not look at them.
6162
knows a system schema when it sees one. The server has to be the major
6263
release pinned in `mysql.Major`, since every release adds to
6364
`information_schema`.
64-
- **`duckdb`** reads the DuckDB CLI named by `DUCKDB`, or `duckdb` on `PATH`:
65-
`types.jsonl`, `functions.jsonl` and `operators.jsonl` come from
66-
`duckdb_types()` and `duckdb_functions()`. The CLI has to be the DuckDB 2.0
67-
build darkwing is pinned against, which has no release to download yet.
65+
- **`duckdb`** reads the DuckDB CLI named by `DUCKDB`, or the one `install`
66+
put in the user cache directory, or `duckdb` on `PATH`: `types.jsonl`,
67+
`functions.jsonl` and `operators.jsonl` come from `duckdb_types()` and
68+
`duckdb_functions()`. The CLI has to be a DuckDB 2.0 build, the release
69+
darkwing is pinned against, which has no release to download yet: until
70+
2.0 is out, `install` downloads the current build of DuckDB's v2.0
71+
preview channel, `duckdb.DefaultVersion`, a rolling tarball per platform
72+
under `artifacts.duckdb.org` with no per-build download and no checksum
73+
to pin, so what a run logs is the version the CLI reports, and
74+
`duckdb.GeneratedFrom` records the build the committed dialect came
75+
from. A check against a later build reports what the later build added;
76+
regenerate, and update `GeneratedFrom`, to move the dialect along. Once
77+
2.0 is released, the installer should pin the release and its checksums
78+
the way the clickhouse one does.
6879
- **`clickhouse`** needs no server: `types.jsonl` comes from
6980
`system.data_type_families` of an ephemeral `clickhouse local` process,
7081
every family that is not an alias becoming a type carrying the spellings
@@ -232,6 +243,34 @@ asks for `--ast` is skipped, since only sqlc can print that.
232243
is why a column read from a table, directly or through a derived table,
233244
is spelled the way the table declares it.
234245

246+
- **`duckdb`** runs each case through the CLI, which loads the schema and
247+
fixture into an in-memory database of their own, one process per
248+
question, and is asked four things about each query. What its
249+
parameters are: the query is prepared and explained with a string
250+
sentinel bound to each parameter, `EXECUTE q('goldeneye_1', ...)`, and
251+
the unoptimized logical plan the CLI prints under
252+
`explain_output = 'all'` shows each as `CAST('goldeneye_k' AS T)`, `T`
253+
being the type the binder gave the parameter; a sentinel the binder
254+
converts on the spot, as an `INSERT`'s `VALUES` are, is bound to NULL
255+
instead. What its result columns are: `DESCRIBE`, with each parameter
256+
replaced by a NULL of its type, names and types them; DuckDB describes
257+
no DML, so a `RETURNING` column is the target table's column it names.
258+
Which table a result column is read from and which column a parameter
259+
stands in for: DuckDB prints a plan with every column by its bare name
260+
and every aliased expression by its alias, so these are read from the
261+
query text, the select list's items, a star expanded to its table's
262+
columns, and the operand beside each parameter, resolved against the
263+
`FROM` clause and the catalog, `duckdb_columns()`, from which a column
264+
read from a table takes its declared type and nullability; a parameter
265+
the query casts takes the cast's type as DuckDB spells it. And whether
266+
an expression can be NULL, which DuckDB does not track: the query is
267+
run, with each parameter bound to a value of its type, over the fixture
268+
and over no rows, and a column is nullable when either run returns a
269+
NULL for it. DuckDB spells an enum column by its labels whether the
270+
schema named the type or not, so labels that are those of an enum the
271+
schema created name that type, and a spelling `types.jsonl` lists as an
272+
alias — `json`, which DuckDB's own catalog lists as a spelling of
273+
`varchar` — is reported by the dialect's name for it.
235274
- **`mssql`** describes each case in a database of its own on the server
236275
named by `MSSQL_SERVER_URI`, without running anything: the schema is
237276
loaded one statement at a time, since a `CREATE TYPE` has to be its own

‎internal/goldeneye/cmd/goldeneye/main.go‎

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
// Usage, from internal/goldeneye:
77
//
88
// go run ./cmd/goldeneye install clickhouse # download the pinned clickhouse binary
9+
// go run ./cmd/goldeneye install duckdb # download the current DuckDB 2.0 preview build
910
// go run ./cmd/goldeneye install sqlite # build the pinned sqlite3 shells from source
1011
// go run ./cmd/goldeneye generate [engine] # rewrite the generated files from the database
1112
// go run ./cmd/goldeneye check [engine] # compare the committed files and analyze cases with the database
@@ -44,9 +45,10 @@ func main() {
4445
}
4546

4647
const usage = `usage:
47-
goldeneye install clickhouse|sqlite [-version V]
48+
goldeneye install clickhouse|duckdb|sqlite [-version V]
4849
put the pinned release of an engine into the user cache directory: clickhouse is
49-
downloaded, sqlite is built from the downloaded amalgamation with cc or $CC
50+
downloaded, duckdb is downloaded from its v2.0 preview channel, sqlite is built from
51+
the downloaded amalgamation with cc or $CC
5052
goldeneye generate [engine]
5153
rewrite the generated dialect files from the database, for every available engine or one
5254
goldeneye check [engine]
@@ -81,7 +83,7 @@ type engine struct {
8183

8284
var engines = []engine{
8385
{clickhouse.Engine, "", "", clickhouse.Locate, clickhouse.Version, clickhouse.Generate, clickhouse.Analyze},
84-
{duckdb.Engine, "", "", duckdb.Locate, duckdb.Version, duckdb.Generate, nil},
86+
{duckdb.Engine, "", "", duckdb.Locate, duckdb.Version, duckdb.Generate, duckdb.Analyze},
8587
{mssql.Engine, "", "", mssql.Locate, mssql.Version, mssql.Generate, mssql.Analyze},
8688
{mysql.Engine, mysql.Dir, "", mysql.Locate, mysql.Version, mysql.Generate, mysql.Analyze},
8789
{postgresql.Engine, "", "", postgresql.Locate, postgresql.Version, postgresql.Generate, nil},
@@ -106,6 +108,7 @@ type installer struct {
106108

107109
var installers = map[string]installer{
108110
clickhouse.Engine: {clickhouse.DefaultVersion, clickhouse.Install},
111+
duckdb.Engine: {duckdb.DefaultVersion, duckdb.Install},
109112
sqlite.Engine: {sqlite.DefaultVersion, sqlite.Install},
110113
}
111114

@@ -131,11 +134,11 @@ func run(ctx context.Context, args []string, stdout, stderr io.Writer) error {
131134

132135
func install(ctx context.Context, args []string, stdout, stderr io.Writer) error {
133136
if len(args) == 0 {
134-
return errors.New("install takes the engine to install: clickhouse or sqlite")
137+
return errors.New("install takes the engine to install: clickhouse, duckdb or sqlite")
135138
}
136139
inst, ok := installers[args[0]]
137140
if !ok {
138-
return fmt.Errorf("install takes the engine to install, clickhouse or sqlite, not %q", args[0])
141+
return fmt.Errorf("install takes the engine to install, clickhouse, duckdb or sqlite, not %q", args[0])
139142
}
140143
fs := flag.NewFlagSet("install "+args[0], flag.ContinueOnError)
141144
fs.SetOutput(stderr)

0 commit comments

Comments
 (0)