Skip to content

Commit e95491e

Browse files
committed
sqlite: drop the sqlc.arg fold, now that preprocessing handles it
#4537 rewrites sqlc's syntax to native placeholders above the engines, and SQLite is one of the preprocessed dialects, so sqlc.arg() and @name never reach the parser. The fold this branch carried is dead. One gap follows from the pass's rule that a statement whose sqlc syntax did not validate is copied through for the engine to parse: that assumes the engine can parse it. SQLite cannot, so an invalid sqlc.arg() surfaced as `near "(": syntax error` and the preprocessor's own diagnostics were never reported -- from parseCatalog, since a schema file and a query file are often the same file. On a parse failure the compiler now reports the sqlc syntax errors recorded for that file, if any, in place of the failure they produced. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RkrYmzwTktB2CA5Bvqg6z5
1 parent b2a250a commit e95491e

3 files changed

Lines changed: 42 additions & 65 deletions

File tree

internal/compiler/compile.go

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,12 @@ func (c *Compiler) parseCatalog(schemas []string) error {
4848
// but don't update the catalog - the database will be the source of truth
4949
stmts, err := c.parser.Parse(strings.NewReader(contents))
5050
if err != nil {
51-
merr.Add(filename, contents, 0, err)
51+
// A schema file and a query file are often the same file, so a
52+
// query's sqlc syntax can fail here. Look for an explanation
53+
// before reporting the syntax error it caused.
54+
if reported := addSyntaxErrors(merr, filename, contents, preprocess.File(c.conf.Engine, contents)); !reported {
55+
merr.Add(filename, contents, 0, err)
56+
}
5257
continue
5358
}
5459

@@ -111,7 +116,9 @@ func (c *Compiler) parseQueries(o opts.Parser) (*Result, error) {
111116

112117
stmts, err := c.parser.Parse(strings.NewReader(pp.Text))
113118
if err != nil {
114-
merr.Add(filename, src, 0, err)
119+
if reported := addSyntaxErrors(merr, filename, src, pp); !reported {
120+
merr.Add(filename, src, 0, err)
121+
}
115122
continue
116123
}
117124
for _, stmt := range stmts {
@@ -162,3 +169,32 @@ func (c *Compiler) parseQueries(o opts.Parser) (*Result, error) {
162169
Queries: q,
163170
}, nil
164171
}
172+
173+
// addSyntaxErrors reports every sqlc syntax error the preprocessor recorded
174+
// for a file, in source order, and says whether there were any. Locations come
175+
// back in the rewritten text's coordinates, so they are mapped through Origin
176+
// to point at what the user wrote.
177+
//
178+
// A statement whose sqlc syntax did not validate is copied through for the
179+
// engine to parse, which assumes the engine can parse it. SQLite cannot: it
180+
// has no schema-qualified function call, so a bad sqlc.arg() is a syntax error
181+
// there rather than a call the preprocessor's message can be attached to.
182+
// These messages name the cause, so they are reported in place of the failure
183+
// they produced; anything else wrong with the file surfaces on the next run.
184+
func addSyntaxErrors(merr *multierr.Error, filename, src string, pp *preprocess.Result) bool {
185+
var found bool
186+
for _, stmt := range pp.Statements() {
187+
if stmt.Err == nil {
188+
continue
189+
}
190+
found = true
191+
loc := pp.Origin(stmt.Start)
192+
var e *sqlerr.Error
193+
if errors.As(stmt.Err, &e) && e.Location != 0 {
194+
loc = pp.Origin(e.Location)
195+
e.Location = loc
196+
}
197+
merr.Add(filename, src, loc, stmt.Err)
198+
}
199+
return found
200+
}

internal/engine/sqlite/convert.go

Lines changed: 2 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,6 @@ import (
1717
// markers within it.
1818
type cc struct {
1919
paramCount int
20-
// folded holds the offset of every "sqlc.name(" call the parser rewrote
21-
// so that meyer would accept it. See foldSqlcCalls.
22-
folded map[int]bool
2320
}
2421

2522
func todo(funcname string, n meyer.Node) *ast.TODO {
@@ -1050,13 +1047,7 @@ func (c *cc) convertFuncCall(n *meyer.FuncCall) ast.Node {
10501047
funcName := identifier(n.Name)
10511048
args := c.convertExprList(n.Args)
10521049

1053-
var schema string
1054-
if c.folded[n.Name.Pos()] {
1055-
schema = sqlcSchema
1056-
funcName = strings.TrimPrefix(funcName, sqlcSchema+"_")
1057-
}
1058-
1059-
if schema == "" && funcName == "coalesce" {
1050+
if funcName == "coalesce" {
10601051
return &ast.CoalesceExpr{
10611052
Args: args,
10621053
Location: n.Pos(),
@@ -1065,8 +1056,7 @@ func (c *cc) convertFuncCall(n *meyer.FuncCall) ast.Node {
10651056

10661057
call := &ast.FuncCall{
10671058
Func: &ast.FuncName{
1068-
Schema: schema,
1069-
Name: funcName,
1059+
Name: funcName,
10701060
},
10711061
Funcname: &ast.List{Items: []ast.Node{
10721062
&ast.String{Str: funcName},

internal/engine/sqlite/parse.go

Lines changed: 2 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,9 @@ package sqlite
33
import (
44
"errors"
55
"io"
6-
"strings"
76

87
meyer "github.com/sqlc-dev/meyer/ast"
9-
"github.com/sqlc-dev/meyer/lexer"
108
"github.com/sqlc-dev/meyer/parser"
11-
"github.com/sqlc-dev/meyer/token"
129

1310
"github.com/sqlc-dev/sqlc/internal/source"
1411
"github.com/sqlc-dev/sqlc/internal/sql/ast"
@@ -27,7 +24,7 @@ func (p *Parser) Parse(r io.Reader) ([]ast.Statement, error) {
2724
if err != nil {
2825
return nil, err
2926
}
30-
src, folded := foldSqlcCalls(string(blob))
27+
src := string(blob)
3128
parsed, err := parser.ParseString(src)
3229
if err != nil {
3330
return nil, normalizeErr(err)
@@ -39,7 +36,7 @@ func (p *Parser) Parse(r io.Reader) ([]ast.Statement, error) {
3936
// reads the "-- name:" annotation out of that range.
4037
loc := 0
4138
for _, raw := range parsed {
42-
converter := &cc{folded: folded}
39+
converter := &cc{}
4340
out := converter.convert(raw)
4441
if _, ok := out.(*ast.TODO); !ok {
4542
stmts = append(stmts, ast.Statement{
@@ -55,52 +52,6 @@ func (p *Parser) Parse(r io.Reader) ([]ast.Statement, error) {
5552
return stmts, nil
5653
}
5754

58-
// sqlcSchema is the pseudo-schema sqlc's own functions are written under.
59-
const sqlcSchema = "sqlc"
60-
61-
// foldSqlcCalls rewrites "sqlc.name(" to "sqlc_name(" and reports the offset
62-
// of every identifier it folded.
63-
//
64-
// SQLite has no schema-qualified function call, so sqlc.arg(), sqlc.narg(),
65-
// sqlc.slice() and sqlc.embed() are not SQL that meyer will parse. Folding
66-
// the dot into the name substitutes one byte for another, which leaves every
67-
// offset in the tree pointing at the same place in the original source;
68-
// convertFuncCall restores the schema for the calls listed here. Working from
69-
// the token stream rather than the raw text keeps the rewrite out of string
70-
// literals and comments.
71-
func foldSqlcCalls(src string) (string, map[int]bool) {
72-
toks := lexer.Lex(src)
73-
var out []byte
74-
var folded map[int]bool
75-
for i := 0; i+3 < len(toks); i++ {
76-
if toks[i].Kind != token.ID || toks[i+1].Kind != token.DOT || toks[i+3].Kind != token.LP {
77-
continue
78-
}
79-
if name := toks[i+2].Kind; name != token.ID && !token.CanFallback(name) {
80-
continue
81-
}
82-
// The three tokens have to be written as one word for the fold to
83-
// produce one. Anything else is left to fail as the syntax error it
84-
// is, rather than silently reparsed as something else.
85-
if toks[i].End != toks[i+1].Pos || toks[i+1].End != toks[i+2].Pos {
86-
continue
87-
}
88-
if !strings.EqualFold(src[toks[i].Pos:toks[i].End], sqlcSchema) {
89-
continue
90-
}
91-
if out == nil {
92-
out = []byte(src)
93-
folded = map[int]bool{}
94-
}
95-
out[toks[i+1].Pos] = '_'
96-
folded[toks[i].Pos] = true
97-
}
98-
if out == nil {
99-
return src, nil
100-
}
101-
return string(out), folded
102-
}
103-
10455
// trimTerminator returns the end of stmt with its terminating semicolon, and
10556
// any space before it, removed. A statement's span runs through the
10657
// semicolon, but sqlc's statement text does not include it.

0 commit comments

Comments
 (0)