Skip to content

Commit ea6e341

Browse files
committed
goldeneye: build SQLite from source and treat compile options as extensions
Which functions a SQLite has is decided when it is compiled, and the official sqlite3 binary's option set matches neither the driver sqlc's tests run against nor any other in particular. So instead of downloading that binary, `install sqlite` downloads the pinned release's amalgamation, checked against the SHA3-256 sqlite.org lists, and compiles the shell from it with cc or $CC: once with the options sqlite.org's own configure turns on by default, which gives functions.jsonl, and once per option in the extension list, each of which gets a directory under extensions/ holding the functions its build adds over the default one — found the way the PostgreSQL generator finds what CREATE EXTENSION adds, by comparing the catalog with and without. GEOPOLY lives inside the RTREE module, so its build carries both options. Each shell is checked against pragma compile_options before it is read, so a stale build is caught. The eight builds take about fifteen seconds unoptimised. A schema says which options it needs the way SQLite itself does, with CREATE VIRTUAL TABLE ... USING fts5. dialect.json gains a modules map from a virtual table module to its extension, the seed resolves a module or extension name through it, and both the legacy catalog and the core load the extension when a virtual table is declared, so the fts5 case keeps its typed bm25, highlight and snippet results. The legacy catalog's createExtension also now records what it loaded, which it never did. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RYkxvtwH6GgysuWYP87vyj
1 parent daf744a commit ea6e341

23 files changed

Lines changed: 604 additions & 208 deletions

File tree

CLAUDE.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,7 @@ is not available skip.
153153
```bash
154154
cd internal/goldeneye
155155
go run ./cmd/goldeneye install clickhouse # download the pinned clickhouse binary once
156-
go run ./cmd/goldeneye install sqlite # download the pinned sqlite3 shell once
156+
go run ./cmd/goldeneye install sqlite # build the pinned sqlite3 shells once; needs a C compiler
157157
POSTGRESQL_SERVER_URI="postgres://postgres:postgres@127.0.0.1:5432/postgres?sslmode=disable" go test ./...
158158
go run ./cmd/goldeneye generate postgresql # rewrite the files after a change
159159
```

internal/core/schema/schema.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,13 @@ func applyCreateTable(cat *core.Catalog, stmt *ast.CreateTableStmt) error {
133133
if stmt.Name == nil {
134134
return fmt.Errorf("create table with nil name")
135135
}
136+
// A virtual table's module is the dialect's word for the extension it
137+
// needs, the way CREATE EXTENSION is PostgreSQL's.
138+
if stmt.Using != "" {
139+
if err := cat.LoadExtension(stmt.Using); err != nil {
140+
return err
141+
}
142+
}
136143
nsOID, err := resolveOrCreateNamespace(cat, stmt.Name.Schema)
137144
if err != nil {
138145
return err

internal/core/seed/extension.go

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9,17 +9,12 @@ import (
99
"github.com/sqlc-dev/sqlc/internal/core"
1010
)
1111

12-
// applyExtension applies the named extension's directory to a catalog that
13-
// has already been seeded. Unlike the dialect's own seed, an extension lands
14-
// in a catalog full of types, so everything it names is resolved against what
15-
// is there before being created.
16-
func applyExtension(cat *core.Catalog, fsys fs.FS, name string) error {
17-
dir := path.Join(ExtensionsDir, name)
18-
if _, err := fs.Stat(fsys, dir); err != nil {
19-
// An extension sqlc has no data for adds nothing, the way the legacy
20-
// catalog has always treated one.
21-
return nil
22-
}
12+
// applyExtension applies the extension directory dir, relative to the
13+
// dialect, to a catalog that has already been seeded. Unlike the dialect's
14+
// own seed, an extension lands in a catalog full of types, so everything it
15+
// names is resolved against what is there before being created.
16+
func applyExtension(cat *core.Catalog, fsys fs.FS, dir string) error {
17+
name := path.Base(dir)
2318
sub, err := fs.Sub(fsys, dir)
2419
if err != nil {
2520
return fmt.Errorf("seed: extension %s: %w", name, err)

internal/core/seed/seed.go

Lines changed: 57 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@
1818
//
1919
// A dialect may also hold an extensions/ directory with one directory per
2020
// extension, each a smaller bundle of the same files, applied when a schema
21-
// says CREATE EXTENSION.
21+
// says CREATE EXTENSION — or, for a dialect whose settings map virtual table
22+
// modules to extensions, CREATE VIRTUAL TABLE ... USING.
2223
package seed
2324

2425
import (
@@ -28,6 +29,7 @@ import (
2829
"fmt"
2930
"io"
3031
"io/fs"
32+
"path"
3133
"slices"
3234
"strings"
3335

@@ -78,6 +80,15 @@ type Settings struct {
7880
// same kind of value resolves. "*" makes every seeded type implicitly
7981
// castable to every other, for dialects that compare across categories.
8082
CastCategories string `json:"cast_categories,omitempty"`
83+
84+
// Modules names the extension a virtual table module belongs to, for a
85+
// dialect whose schemas say CREATE VIRTUAL TABLE ... USING rather than
86+
// CREATE EXTENSION: SQLite's fts5 module comes with the functions its
87+
// enable_fts5 compile option adds.
88+
Modules map[string]string `json:"modules,omitempty"`
89+
90+
// fsys is the dialect directory the settings were read from.
91+
fsys fs.FS
8192
}
8293

8394
// Type is a type the dialect defines. Aliases are spellings of the same type
@@ -155,21 +166,60 @@ func Dialect(fsys fs.FS, dir string) core.Option {
155166
if err != nil {
156167
return fmt.Errorf("seed: %s: %w", dir, err)
157168
}
158-
if err := apply(cat, sub); err != nil {
169+
settings, err := loadSettings(sub)
170+
if err != nil {
171+
return err
172+
}
173+
if err := apply(cat, sub, settings); err != nil {
159174
return err
160175
}
161176
cat.SetExtensionLoader(func(name string) error {
162-
return applyExtension(cat, sub, name)
177+
dir, ok := settings.extensionDir(name)
178+
if !ok {
179+
// An extension sqlc has no data for adds nothing, the way
180+
// the legacy catalog has always treated one.
181+
return nil
182+
}
183+
return applyExtension(cat, sub, dir)
163184
})
164185
return nil
165186
})
166187
}
167188

168-
func apply(cat *core.Catalog, fsys fs.FS) error {
169-
settings, err := loadSettings(fsys)
189+
// ExtensionDir resolves what a schema named — an extension, or a virtual
190+
// table module the dialect's settings map to one — to the extension's
191+
// directory under dir, reporting whether the dialect has data for it.
192+
func ExtensionDir(fsys fs.FS, dir, name string) (string, bool) {
193+
sub, err := fs.Sub(fsys, dir)
170194
if err != nil {
171-
return err
195+
return "", false
172196
}
197+
settings, err := loadSettings(sub)
198+
if err != nil {
199+
return "", false
200+
}
201+
rel, ok := settings.extensionDir(name)
202+
if !ok {
203+
return "", false
204+
}
205+
return path.Join(dir, rel), true
206+
}
207+
208+
// extensionDir is the directory of the extension a name refers to,
209+
// relative to the dialect, if the dialect ships one.
210+
func (s Settings) extensionDir(name string) (string, bool) {
211+
if ext, ok := s.Modules[strings.ToLower(name)]; ok {
212+
name = ext
213+
}
214+
dir := path.Join(ExtensionsDir, name)
215+
if _, err := fs.Stat(s.fsys, dir); err != nil {
216+
return "", false
217+
}
218+
return dir, true
219+
}
220+
221+
func apply(cat *core.Catalog, fsys fs.FS, settings Settings) error {
222+
var err error
173223
b := &builder{
174224
cat: cat,
175225
settings: settings,
@@ -222,6 +272,7 @@ func loadSettings(fsys fs.FS) (Settings, error) {
222272
if settings.Dialect == "" {
223273
return Settings{}, fmt.Errorf("seed: %s: dialect has no name", SettingsFile)
224274
}
275+
settings.fsys = fsys
225276
return settings, nil
226277
}
227278

internal/engine/sqlite/catalog.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ func NewCatalog() *catalog.Catalog {
99
Schemas: []*catalog.Schema{
1010
defaultSchema(def),
1111
},
12-
Extensions: map[string]struct{}{},
12+
LoadExtension: loadExtension,
13+
Extensions: map[string]struct{}{},
1314
}
1415
}

internal/engine/sqlite/dialect/dialect.json

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,5 +11,13 @@
1111
"comparison_categories": "BNSDU",
1212
"arithmetic": ["+", "-", "*", "/", "%"],
1313
"arithmetic_categories": "N",
14-
"cast_categories": "*"
14+
"cast_categories": "*",
15+
"modules": {
16+
"fts3": "enable_fts3",
17+
"fts4": "enable_fts3",
18+
"fts5": "enable_fts5",
19+
"geopoly": "enable_geopoly",
20+
"rtree": "enable_rtree",
21+
"rtree_i32": "enable_rtree"
22+
}
1523
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{"name":"matchinfo","args":[{"type":"any"}],"returns":"blob"}
2+
{"name":"matchinfo","args":[{"type":"any"},{"type":"text"}],"returns":"blob"}
3+
{"name":"offsets","args":[{"type":"any"}],"returns":"text"}
4+
{"name":"optimize","args":[{"type":"any"}],"returns":"text"}
5+
{"name":"snippet","args":[{"type":"text","has_default":true},{"type":"integer","has_default":true},{"type":"text","has_default":true},{"type":"text","has_default":true},{"type":"text","has_default":true},{"type":"integer","has_default":true},{"type":"any","mode":"v"}],"returns":"text"}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{"name":"bm25","args":[{"type":"text","has_default":true},{"type":"real","mode":"v"}],"returns":"real"}
2+
{"name":"fts5_get_locale","args":[{"type":"any","has_default":true},{"type":"any","has_default":true},{"type":"any","mode":"v"}],"returns":"text","nullable":true}
3+
{"name":"fts5_insttoken","args":[{"type":"text"}],"returns":"text"}
4+
{"name":"fts5_locale","args":[{"type":"text"},{"type":"text"}],"returns":"text"}
5+
{"name":"fts5_source_id","returns":"text"}
6+
{"name":"highlight","args":[{"type":"text","has_default":true},{"type":"integer","has_default":true},{"type":"text","has_default":true},{"type":"text","has_default":true},{"type":"any","mode":"v"}],"returns":"text"}
7+
{"name":"snippet","args":[{"type":"text","has_default":true},{"type":"integer","has_default":true},{"type":"text","has_default":true},{"type":"text","has_default":true},{"type":"text","has_default":true},{"type":"integer","has_default":true},{"type":"any","mode":"v"}],"returns":"text"}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{"name":"geopoly_area","args":[{"type":"any"}],"returns":"real","nullable":true}
2+
{"name":"geopoly_bbox","args":[{"type":"any"}],"returns":"blob","nullable":true}
3+
{"name":"geopoly_blob","args":[{"type":"any"}],"returns":"blob","nullable":true}
4+
{"name":"geopoly_ccw","args":[{"type":"any"}],"returns":"blob","nullable":true}
5+
{"name":"geopoly_contains_point","args":[{"type":"any"},{"type":"real"},{"type":"real"}],"returns":"integer","nullable":true}
6+
{"name":"geopoly_group_bbox","kind":"a","args":[{"type":"any"}],"returns":"blob","nullable":true}
7+
{"name":"geopoly_json","args":[{"type":"any"}],"returns":"text","nullable":true}
8+
{"name":"geopoly_overlap","args":[{"type":"any"},{"type":"any"}],"returns":"integer","nullable":true}
9+
{"name":"geopoly_regular","args":[{"type":"real"},{"type":"real"},{"type":"real"},{"type":"integer"}],"returns":"blob"}
10+
{"name":"geopoly_svg","args":[{"type":"any","has_default":true},{"type":"text","has_default":true},{"type":"any","mode":"v"}],"returns":"text","nullable":true}
11+
{"name":"geopoly_within","args":[{"type":"any"},{"type":"any"}],"returns":"integer","nullable":true}
12+
{"name":"geopoly_xform","args":[{"type":"any"},{"type":"real"},{"type":"real"},{"type":"real"},{"type":"real"},{"type":"real"},{"type":"real"}],"returns":"blob","nullable":true}
13+
{"name":"rtreecheck","args":[{"type":"text","has_default":true},{"type":"text","has_default":true},{"type":"any","mode":"v"}],"returns":"text"}
14+
{"name":"rtreedepth","args":[{"type":"blob"}],"returns":"integer"}
15+
{"name":"rtreenode","args":[{"type":"integer"},{"type":"blob"}],"returns":"text"}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"name":"sqlite_offset","args":[{"type":"any"}],"returns":"integer","nullable":true}

0 commit comments

Comments
 (0)