Files
upstream-mirror 0f4df23290
Some checks failed
kotlin / test (push) Has been skipped
python / test (push) Has been skipped
typescript / test (push) Has been skipped
go / build darwin/amd64 (push) Has been cancelled
go / build linux/amd64 (push) Has been cancelled
go / build windows/amd64 (push) Has been cancelled
go / build darwin/arm64 (push) Has been cancelled
go / build linux/arm64 (push) Has been cancelled
go / build windows/arm64 (push) Has been cancelled
go / test (push) Has been cancelled
go / vuln_check (push) Has been cancelled
snapshot of https://github.com/sqlc-dev/sqlc.git (main, 2026-09-16; blobs > 131072B omitted)
2026-09-16 02:17:34 +05:00

159 lines
3.9 KiB
Go

package sqlite
import (
"errors"
"io"
"strings"
meyer "github.com/sqlc-dev/meyer/ast"
"github.com/sqlc-dev/meyer/parser"
mtoken "github.com/sqlc-dev/meyer/token"
"github.com/sqlc-dev/sqlc/internal/source"
"github.com/sqlc-dev/sqlc/internal/sql/ast"
"github.com/sqlc-dev/sqlc/internal/sql/sqlerr"
)
func NewParser() *Parser {
return &Parser{}
}
type Parser struct {
}
// parseOptions accepts ORDER BY and LIMIT on UPDATE and DELETE. SQLite has
// them only when built with SQLITE_ENABLE_UPDATE_DELETE_LIMIT, which meyer
// cannot tell from the SQL, so the caller has to ask. sqlc has always
// accepted them.
var parseOptions = parser.Options{UpdateDeleteLimit: true}
func (p *Parser) Parse(r io.Reader) ([]ast.Statement, error) {
f, err := p.ParseFile(r)
if err != nil {
return nil, err
}
// The compiler skips statements sqlc has no node for (PRAGMA and
// friends); the formatter must see them, so the filter lives here,
// not in ParseFile.
var stmts []ast.Statement
for _, stmt := range f.Stmts {
if _, ok := stmt.Raw.Stmt.(*ast.TODO); ok {
continue
}
stmts = append(stmts, stmt)
}
return stmts, nil
}
// ParseFile parses like Parse and also carries the file's comments, taken
// from the trivia meyer's single lexer pass produces alongside the tokens.
func (p *Parser) ParseFile(r io.Reader) (*ast.File, error) {
blob, err := io.ReadAll(r)
if err != nil {
return nil, err
}
src := string(blob)
parsed, err := parseOptions.ParseFile(src)
if err != nil {
return nil, normalizeErr(err)
}
var stmts []ast.Statement
// loc tracks the first byte after the previous statement's terminator, so
// that a statement's extent covers the comments written above it. sqlc
// reads the "-- name:" annotation out of that range.
loc := 0
for _, raw := range parsed.Stmts {
converter := &cc{src: src}
// A statement sqlc has no node for converts to a TODO and stays in
// the list: the formatter needs its extent to keep it as written,
// and Parse filters it out for the compiler.
stmts = append(stmts, ast.Statement{
Raw: &ast.RawStmt{
Stmt: converter.convert(raw),
StmtLocation: loc,
StmtLen: trimTerminator(src, raw) - loc,
},
})
loc = raw.End()
}
var comments []ast.Comment
for _, tr := range parsed.Trivia {
if tr.Kind != mtoken.COMMENT {
continue
}
comments = append(comments, ast.Comment{
Text: strings.TrimRight(tr.Text(src), " \t\r\n"),
Start: tr.Pos,
End: tr.End,
OwnLine: ownLine(src, tr.Pos),
})
}
return &ast.File{Stmts: stmts, Comments: comments}, nil
}
// ownLine reports that only blank space sits between the preceding line
// break and pos.
func ownLine(src string, pos int) bool {
for j := pos - 1; j >= 0; j-- {
switch src[j] {
case '\n':
return true
case ' ', '\t', '\r':
continue
default:
return false
}
}
return true
}
// trimTerminator returns the end of stmt with its terminating semicolon, and
// any space before it, removed. A statement's span runs through the
// semicolon, but sqlc's statement text does not include it.
func trimTerminator(src string, stmt meyer.Stmt) int {
end := stmt.End()
if end > stmt.Pos() && end <= len(src) && src[end-1] == ';' {
end--
}
for end > stmt.Pos() && isSpace(src[end-1]) {
end--
}
return end
}
func isSpace(b byte) bool {
switch b {
case ' ', '\t', '\n', '\r', '\f':
return true
}
return false
}
// normalizeErr turns a meyer parse failure into the positioned error sqlc
// reports to the user. meyer's message and byte offset match SQLite's own.
func normalizeErr(err error) error {
var perr *parser.Error
if !errors.As(err, &perr) {
return err
}
line, column := parser.LineCol(perr.SQL, perr.Offset)
if line == 0 {
return errors.New(perr.Message)
}
return &sqlerr.Error{
Message: perr.Message,
Line: line,
Column: column,
}
}
func (p *Parser) CommentSyntax() source.CommentSyntax {
return source.CommentSyntax{
Dash: true,
Hash: false,
SlashStar: true,
}
}