Skip to content

Commit d89c6d0

Browse files
committed
fix(sqlite): split named queries without trailing semicolons
1 parent 47bcc02 commit d89c6d0

2 files changed

Lines changed: 32 additions & 2 deletions

File tree

internal/endtoend/testdata/analyze_select/sqlite/query.sql

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
-- name: ListUsers :many
2-
SELECT * FROM users;
2+
SELECT * FROM users
33

44
-- name: CountUsers :one
55
SELECT count(*) AS total FROM users;

internal/engine/sqlite/parse.go

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package sqlite
33
import (
44
"errors"
55
"io"
6+
"strings"
67

78
meyer "github.com/sqlc-dev/meyer/ast"
89
"github.com/sqlc-dev/meyer/parser"
@@ -31,7 +32,7 @@ func (p *Parser) Parse(r io.Reader) ([]ast.Statement, error) {
3132
return nil, err
3233
}
3334
src := string(blob)
34-
parsed, err := parseOptions.ParseString(src)
35+
parsed, err := parseOptions.ParseString(terminateNamedQueries(src))
3536
if err != nil {
3637
return nil, normalizeErr(err)
3738
}
@@ -58,6 +59,35 @@ func (p *Parser) Parse(r io.Reader) ([]ast.Statement, error) {
5859
return stmts, nil
5960
}
6061

62+
// terminateNamedQueries adds a virtual terminator before a subsequent sqlc
63+
// query annotation when the preceding query omitted one. sqlc annotations
64+
// delimit queries for the other engines, and a query file may therefore
65+
// contain multiple valid queries even though the complete SQLite script would
66+
// otherwise be invalid. Replacing the newline immediately before the
67+
// annotation preserves every byte offset reported by the parser.
68+
func terminateNamedQueries(src string) string {
69+
terminated := []byte(src)
70+
for lineStart := 0; lineStart < len(terminated); {
71+
lineEnd := strings.IndexByte(src[lineStart:], '\n')
72+
if lineEnd < 0 {
73+
lineEnd = len(terminated)
74+
} else {
75+
lineEnd += lineStart
76+
}
77+
if lineStart > 0 && strings.HasPrefix(src[lineStart:lineEnd], "-- name: ") {
78+
i := lineStart - 1
79+
for i >= 0 && isSpace(terminated[i]) {
80+
i--
81+
}
82+
if i >= 0 && terminated[i] != ';' {
83+
terminated[lineStart-1] = ';'
84+
}
85+
}
86+
lineStart = lineEnd + 1
87+
}
88+
return string(terminated)
89+
}
90+
6191
// trimTerminator returns the end of stmt with its terminating semicolon, and
6292
// any space before it, removed. A statement's span runs through the
6393
// semicolon, but sqlc's statement text does not include it.

0 commit comments

Comments
 (0)