snapshot of https://github.com/sqlc-dev/sqlc.git (main, 2026-09-16; blobs > 131072B omitted)
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

This commit is contained in:
upstream-mirror
2026-09-16 02:17:34 +05:00
commit 0f4df23290
7340 changed files with 238743 additions and 0 deletions

View File

@@ -0,0 +1,140 @@
// Package endtoend finds the analyze cases under internal/endtoend/testdata
// and compares an engine's own answer with the output a case committed,
// the way the dialect package compares a generated dialect with the
// committed one.
package endtoend
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"os"
"path/filepath"
"runtime"
"strings"
"github.com/sqlc-dev/sqlc/internal/goldeneye/dialect"
)
// Case is one analyze case: the files sqlc analyze ran with, the fixture
// loaded before the queries run against a real database, and the output
// sqlc committed.
type Case struct {
Name string // analyze_params/clickhouse
Dir string
Schema string
Query string
Fixture string // empty when the case has no fixture.sql
Output string
}
// Testdata returns the end-to-end testdata directory, found relative to this
// source file so the working directory does not matter.
func Testdata() (string, error) {
_, file, _, ok := runtime.Caller(0)
if !ok {
return "", errors.New("cannot locate the testcheck source directory")
}
dir := filepath.Join(filepath.Dir(file), "..", "..", "endtoend", "testdata")
if _, err := os.Stat(dir); err != nil {
return "", err
}
return filepath.Clean(dir), nil
}
// Cases lists the analyze cases for an engine: every analyze_*/<engine>
// directory whose exec.json runs the analyze command. A case that asks for
// the AST is skipped, since only sqlc can print that.
func Cases(engine string) ([]Case, error) {
root, err := Testdata()
if err != nil {
return nil, err
}
dirs, err := filepath.Glob(filepath.Join(root, "analyze_*", engine))
if err != nil {
return nil, err
}
var cases []Case
for _, dir := range dirs {
c, ok, err := load(dir)
if err != nil {
return nil, fmt.Errorf("%s: %w", dir, err)
}
if ok {
cases = append(cases, c)
}
}
return cases, nil
}
func load(dir string) (Case, bool, error) {
blob, err := os.ReadFile(filepath.Join(dir, "exec.json"))
if errors.Is(err, os.ErrNotExist) {
return Case{}, false, nil
}
if err != nil {
return Case{}, false, err
}
var exec struct {
Command string `json:"command"`
Args []string `json:"args"`
}
if err := json.Unmarshal(blob, &exec); err != nil {
return Case{}, false, fmt.Errorf("exec.json: %w", err)
}
if exec.Command != "analyze" {
return Case{}, false, nil
}
var schema, query string
for i := 0; i < len(exec.Args); i++ {
switch arg := exec.Args[i]; arg {
case "--schema", "-s", "--dialect", "-d":
i++
if arg == "--schema" || arg == "-s" {
if i < len(exec.Args) {
schema = exec.Args[i]
}
}
case "--ast":
return Case{}, false, nil
default:
if !strings.HasPrefix(arg, "-") {
query = arg
}
}
}
if schema == "" || query == "" {
return Case{}, false, errors.New("exec.json: analyze needs --schema and a query file")
}
c := Case{
Name: filepath.Join(filepath.Base(filepath.Dir(dir)), filepath.Base(dir)),
Dir: dir,
Schema: filepath.Join(dir, schema),
Query: filepath.Join(dir, query),
Output: filepath.Join(dir, "stdout.json"),
}
if fixture := filepath.Join(dir, "fixture.sql"); fileExists(fixture) {
c.Fixture = fixture
}
return c, true, nil
}
func fileExists(path string) bool {
_, err := os.Stat(path)
return err == nil
}
// Compare checks an engine's answer against the case's committed output
// byte for byte and returns a line diff when they differ, or "" when they
// match.
func (c Case) Compare(got []byte) (string, error) {
want, err := os.ReadFile(c.Output)
if err != nil {
return "", err
}
if bytes.Equal(want, got) {
return "", nil
}
return dialect.Diff(string(want), string(got)), nil
}

View File

@@ -0,0 +1,183 @@
package endtoend
import (
"fmt"
"os"
"regexp"
"strings"
"unicode"
)
// Query is one entry of a sqlc query file.
type Query struct {
Name string
Cmd string
SQL string
}
// Queries reads the case's query file and splits it into its queries.
func (c Case) Queries() ([]Query, error) {
src, err := os.ReadFile(c.Query)
if err != nil {
return nil, err
}
queries, err := ParseQueries(string(src))
if err != nil {
return nil, fmt.Errorf("%s: %w", c.Query, err)
}
return queries, nil
}
var headerRe = regexp.MustCompile(`^\s*--\s*name:\s*(\S+)\s+(:\S+)\s*$`)
// ParseQueries splits a sqlc query file on its `-- name: X :cmd` headers.
func ParseQueries(src string) ([]Query, error) {
var queries []Query
var cur *Query
var body []string
flush := func() error {
if cur == nil {
return nil
}
sql := strings.TrimSpace(strings.Join(body, "\n"))
sql = strings.TrimRight(sql, "; \t\r\n")
if sql == "" {
return fmt.Errorf("query %s has no body", cur.Name)
}
cur.SQL = sql
queries = append(queries, *cur)
return nil
}
for _, line := range strings.Split(src, "\n") {
if m := headerRe.FindStringSubmatch(line); m != nil {
if err := flush(); err != nil {
return nil, err
}
cur = &Query{Name: m[1], Cmd: m[2]}
body = body[:0]
continue
}
if cur != nil {
body = append(body, line)
}
}
if err := flush(); err != nil {
return nil, err
}
if len(queries) == 0 {
return nil, fmt.Errorf("no queries found: expected `-- name: Name :cmd` headers")
}
return queries, nil
}
var namedArgRe = regexp.MustCompile(`^sqlc\.(n?arg|slice)\(\s*'?([A-Za-z_][A-Za-z0-9_]*)'?\s*\)`)
// typedParamRe matches ClickHouse's {name:Type} parameter, whose type is
// the query's own business: the engine binds it as it binds any other.
var typedParamRe = regexp.MustCompile(`^\{\s*([A-Za-z_][A-Za-z0-9_]*)\s*:\s*[^}]+\}`)
// Rewrite replaces every parameter reference in a query — ?, sqlc.arg(name),
// sqlc.narg(name), sqlc.slice(name) and ClickHouse's {name:Type} — with
// what bind returns for it, in order of appearance, skipping string
// literals, quoted identifiers and comments. bind is handed the name, empty for a ?, and the word before the
// reference, so that a LIMIT or OFFSET can be bound differently from a
// value; the second count of a LIMIT ?, ? is handed LIMIT as well. Each
// engine decides what a reference becomes and how the references are
// numbered.
func Rewrite(sql string, bind func(name, lastWord string) string) string {
var (
out strings.Builder
lastWord string
i = 0
)
for i < len(sql) {
c := sql[i]
switch {
case c == '\'' || c == '"' || c == '`':
end := skipQuoted(sql, i)
out.WriteString(sql[i:end])
i = end
case strings.HasPrefix(sql[i:], "--"):
end := strings.IndexByte(sql[i:], '\n')
if end < 0 {
end = len(sql)
} else {
end += i
}
out.WriteString(sql[i:end])
i = end
case strings.HasPrefix(sql[i:], "/*"):
end := strings.Index(sql[i:], "*/")
if end < 0 {
end = len(sql)
} else {
end += i + 2
}
out.WriteString(sql[i:end])
i = end
case c == '?':
out.WriteString(bind("", lastWord))
lastWord = afterReference(lastWord)
i++
case c == 's' && namedArgRe.MatchString(sql[i:]):
m := namedArgRe.FindStringSubmatch(sql[i:])
out.WriteString(bind(m[2], lastWord))
lastWord = afterReference(lastWord)
i += len(m[0])
case c == '{' && typedParamRe.MatchString(sql[i:]):
m := typedParamRe.FindStringSubmatch(sql[i:])
out.WriteString(bind(m[1], lastWord))
lastWord = afterReference(lastWord)
i += len(m[0])
case isWordByte(c):
end := i
for end < len(sql) && isWordByte(sql[end]) {
end++
}
lastWord = sql[i:end]
out.WriteString(lastWord)
i = end
default:
if !unicode.IsSpace(rune(c)) && c != ',' && c != '(' {
lastWord = ""
}
out.WriteByte(c)
i++
}
}
return out.String()
}
// afterReference is the word the reference after one is preceded by: a
// LIMIT's, so that both counts of LIMIT ?, ? are bound as counts, and
// otherwise none.
func afterReference(lastWord string) string {
if strings.EqualFold(lastWord, "limit") {
return lastWord
}
return ""
}
func isWordByte(c byte) bool {
return c == '_' || c == '.' || c >= '0' && c <= '9' || c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z'
}
// skipQuoted returns the index just past the quoted token starting at i,
// honouring backslash escapes and doubled quotes.
func skipQuoted(s string, i int) int {
q := s[i]
j := i + 1
for j < len(s) {
switch {
case s[j] == '\\' && j+1 < len(s):
j += 2
case s[j] == q && j+1 < len(s) && s[j+1] == q:
j += 2
case s[j] == q:
return j + 1
default:
j++
}
}
return len(s)
}