Skip to content

Commit 5f7c90f

Browse files
committed
sqlite: replace the ANTLR parser with meyer
meyer is a hand-written recursive descent parser for SQLite's dialect, ported from src/parse.y and src/tokenize.c and checked against SQLite itself. Moving to it deletes 1.2 MB of generated ANTLR code and the antlr4-go dependency, and replaces a grammar that only approximated SQLite with one that accepts what SQLite accepts. parse.go calls meyer and slices statement extents out of the node spans; convert.go maps meyer's tree onto sqlc's AST; reserved.go now defers to meyer's keyword table, which the hand-maintained list was missing MATERIALIZED and RETURNING from. SQLite has no schema-qualified function call, so sqlc.arg() and its siblings are not SQL meyer will parse. The parser folds "sqlc.name(" to "sqlc_name(" over the token stream -- one byte for another, so every offset still points into the original source, and string literals and comments are untouched -- and the converter puts the schema back. Behavior the old grammar got wrong and this corrects: - "col TEXT NULL" was read as a column of type "TEXTNULL", so the column generated as interface{} rather than a nullable string. This changes one golden file. - GROUP BY was dropped whenever the query also had a WHERE clause, from an off-by-one over the ANTLR expression list. Now that GROUP BY reaches the compiler, its column references are validated, which SQLite's implicit rowid would have failed; the compiler now recognizes rowid, _rowid_ and oid for SQLite. - Comparison operators other than "=" were all recorded as "=", and IS, ISNULL, NOTNULL and LIKE reached the AST as "=" too. They now carry their own spelling, and lang.IsComparisonOperator learned the keyword-spelled comparisons so they still type as bool. - ORDER BY, DISTINCT, WITH RECURSIVE, ON CONFLICT, OVER and FILTER were parsed and then discarded, so ast.Format dropped them from the SQL it rebuilds. All are carried through now. - Quoted identifiers other than "..." kept their quotes; float literals became the integer 0. One capability is lost: the pinned SQLite build meyer targets defines neither SQLITE_ENABLE_UPDATE_DELETE_LIMIT nor SQLITE_UDL_CAPABLE_PARSER, so UPDATE and DELETE no longer accept ORDER BY or LIMIT. No test covered it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RkrYmzwTktB2CA5Bvqg6z5
1 parent 5ce31fc commit 5f7c90f

22 files changed

Lines changed: 1252 additions & 40046 deletions

go.mod

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ go 1.26.0
55
toolchain go1.26.5
66

77
require (
8-
github.com/antlr4-go/antlr/v4 v4.13.1
98
github.com/cubicdaiya/gonp v1.0.4
109
github.com/davecgh/go-spew v1.1.1
1110
github.com/fatih/structtag v1.2.0
@@ -22,6 +21,7 @@ require (
2221
github.com/spf13/pflag v1.0.10
2322
github.com/sqlc-dev/doubleclick v1.0.0
2423
github.com/sqlc-dev/marino v0.1.0
24+
github.com/sqlc-dev/meyer v0.1.0
2525
github.com/sqlc-dev/zetajones v0.1.0
2626
github.com/tetratelabs/wazero v1.12.0
2727
github.com/wasilibs/go-pgquery v0.0.0-20250409022910-10ac41983c07
@@ -36,6 +36,7 @@ require (
3636
require (
3737
cel.dev/expr v0.25.2 // indirect
3838
filippo.io/edwards25519 v1.2.0 // indirect
39+
github.com/antlr4-go/antlr/v4 v4.13.1 // indirect
3940
github.com/dustin/go-humanize v1.0.1 // indirect
4041
github.com/google/uuid v1.6.0 // indirect
4142
github.com/inconshreveable/mousetrap v1.1.0 // indirect

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,8 @@ github.com/sqlc-dev/doubleclick v1.0.0 h1:2/OApfQ2eLgcfa/Fqs8WSMA6atH0G8j9hHbQIg
8383
github.com/sqlc-dev/doubleclick v1.0.0/go.mod h1:ODHRroSrk/rr5neRHlWMSRijqOak8YmNaO3VAZCNl5Y=
8484
github.com/sqlc-dev/marino v0.1.0 h1:8Fn13vFhx7OUcmDFfRZdf3zARAbNl04Lcy74211ZpIw=
8585
github.com/sqlc-dev/marino v0.1.0/go.mod h1:mQxC2dgDE0DWHMb2B5jZNk7KToJuS6wnxnffBfYnq08=
86+
github.com/sqlc-dev/meyer v0.1.0 h1:u1GU0veXPWdtVdmZsMmA1cvWE6xXc9lJDfRznVBuCg4=
87+
github.com/sqlc-dev/meyer v0.1.0/go.mod h1:pS4USCRf/SLjWtaMcnTo4YrEEFKBj8CyyqlxcVUJQH8=
8688
github.com/sqlc-dev/zetajones v0.1.0 h1:VeG0atx6lNABr9V2bSI5vL9DvOKTHX0XjMqWUE/rv40=
8789
github.com/sqlc-dev/zetajones v0.1.0/go.mod h1:dU1DxwqC6Cahbpnw16KpH1J2waWRDMdwyDSvovMZR4I=
8890
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=

internal/compiler/output_columns.go

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"errors"
55
"fmt"
66

7+
"github.com/sqlc-dev/sqlc/internal/config"
78
"github.com/sqlc-dev/sqlc/internal/sql/ast"
89
"github.com/sqlc-dev/sqlc/internal/sql/astutils"
910
"github.com/sqlc-dev/sqlc/internal/sql/catalog"
@@ -69,7 +70,7 @@ func (c *Compiler) outputColumns(qc *QueryCatalog, node ast.Node) ([]*Column, er
6970

7071
if n.GroupClause != nil {
7172
for _, item := range n.GroupClause.Items {
72-
if err := findColumnForNode(item, tables, targets); err != nil {
73+
if err := c.findColumnForNode(item, tables, targets); err != nil {
7374
return nil, err
7475
}
7576
}
@@ -85,7 +86,7 @@ func (c *Compiler) outputColumns(qc *QueryCatalog, node ast.Node) ([]*Column, er
8586
if !ok {
8687
continue
8788
}
88-
if err := findColumnForNode(sb.Node, tables, targets); err != nil {
89+
if err := c.findColumnForNode(sb.Node, tables, targets); err != nil {
8990
return nil, fmt.Errorf("%v: if you want to skip this validation, set 'strict_order_by' to false", err)
9091
}
9192
}
@@ -101,7 +102,7 @@ func (c *Compiler) outputColumns(qc *QueryCatalog, node ast.Node) ([]*Column, er
101102
if !ok {
102103
continue
103104
}
104-
if err := findColumnForNode(caseExpr.Xpr, tables, targets); err != nil {
105+
if err := c.findColumnForNode(caseExpr.Xpr, tables, targets); err != nil {
105106
return nil, fmt.Errorf("%v: if you want to skip this validation, set 'strict_order_by' to false", err)
106107
}
107108
}
@@ -713,14 +714,36 @@ func outputColumnRefs(res *ast.ResTarget, tables []*Table, node *ast.ColumnRef)
713714
return cols, nil
714715
}
715716

716-
func findColumnForNode(item ast.Node, tables []*Table, targetList *ast.List) error {
717+
func (c *Compiler) findColumnForNode(item ast.Node, tables []*Table, targetList *ast.List) error {
717718
ref, ok := item.(*ast.ColumnRef)
718719
if !ok {
719720
return nil
720721
}
722+
if c.isImplicitColumnRef(ref) {
723+
return nil
724+
}
721725
return findColumnForRef(ref, tables, targetList)
722726
}
723727

728+
// isImplicitColumnRef reports whether ref names a column that every table in
729+
// the dialect has without declaring it, and which therefore never appears in
730+
// a catalog built from the schema. SQLite gives each ordinary table a rowid,
731+
// reachable under three spellings.
732+
func (c *Compiler) isImplicitColumnRef(ref *ast.ColumnRef) bool {
733+
if c.conf.Engine != config.EngineSQLite {
734+
return false
735+
}
736+
parts := stringSlice(ref.Fields)
737+
if len(parts) == 0 {
738+
return false
739+
}
740+
switch parts[len(parts)-1] {
741+
case "rowid", "_rowid_", "oid":
742+
return true
743+
}
744+
return false
745+
}
746+
724747
func findColumnForRef(ref *ast.ColumnRef, tables []*Table, targetList *ast.List) error {
725748
parts := stringSlice(ref.Fields)
726749
var alias, name string

internal/endtoend/testdata/case_named_params/sqlite/go/models.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.

0 commit comments

Comments
 (0)