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

117
internal/endtoend/CLAUDE.md Normal file
View File

@@ -0,0 +1,117 @@
# End-to-End Tests - Native Database Setup
This document describes how to set up MySQL and PostgreSQL for running end-to-end tests in environments without Docker, particularly when using an HTTP proxy.
## Overview
The end-to-end tests support three methods for connecting to databases:
1. **Environment Variables**: Set `POSTGRESQL_SERVER_URI` and `MYSQL_SERVER_URI` directly
2. **Docker**: Automatically starts containers via the docker package
3. **Native Installation**: Starts existing database services on Linux
## Installing Databases with HTTP Proxy
In environments where DNS doesn't work directly but an HTTP proxy is available (e.g., some CI environments), you need to configure apt to use the proxy before installing packages.
### Configure apt Proxy
```bash
# Check if HTTP_PROXY is set
echo $HTTP_PROXY
# Configure apt to use the proxy
sudo tee /etc/apt/apt.conf.d/99proxy << EOF
Acquire::http::Proxy "$HTTP_PROXY";
Acquire::https::Proxy "$HTTPS_PROXY";
EOF
# Update package lists
sudo apt-get update -qq
```
### Install PostgreSQL
```bash
# Install PostgreSQL
sudo DEBIAN_FRONTEND=noninteractive apt-get install -y postgresql postgresql-contrib
# Start the service
sudo service postgresql start
# Set password for postgres user
sudo -u postgres psql -c "ALTER USER postgres PASSWORD 'postgres';"
# Configure pg_hba.conf for password authentication
# Find the hba_file location:
sudo -u postgres psql -t -c "SHOW hba_file;"
# Add md5 authentication for localhost (add to the beginning of pg_hba.conf):
# host all all 127.0.0.1/32 md5
# Reload PostgreSQL
sudo service postgresql reload
```
### Install MySQL
```bash
# Pre-configure MySQL root password
echo "mysql-server mysql-server/root_password password mysecretpassword" | sudo debconf-set-selections
echo "mysql-server mysql-server/root_password_again password mysecretpassword" | sudo debconf-set-selections
# Install MySQL
sudo DEBIAN_FRONTEND=noninteractive apt-get install -y mysql-server
# Start the service
sudo service mysql start
# Verify connection
mysql -uroot -pmysecretpassword -e "SELECT 1;"
```
## Expected Database Credentials
The native database support expects the following credentials:
### PostgreSQL
- **URI**: `postgres://postgres:postgres@localhost:5432/postgres?sslmode=disable`
- **User**: `postgres`
- **Password**: `postgres`
- **Port**: `5432`
### MySQL
- **URI**: `root:mysecretpassword@tcp(localhost:3306)/mysql?multiStatements=true&parseTime=true`
- **User**: `root`
- **Password**: `mysecretpassword`
- **Port**: `3306`
## Running Tests
```bash
# Run end-to-end tests
go test -v -run TestReplay -timeout 20m ./internal/endtoend/...
# With verbose logging
go test -v -run TestReplay -timeout 20m ./internal/endtoend/... 2>&1 | tee test.log
```
## Troubleshooting
### apt-get times out or fails
- Ensure HTTP proxy is configured in `/etc/apt/apt.conf.d/99proxy`
- Check that the proxy URL is correct: `echo $HTTP_PROXY`
- Try running `sudo apt-get update` first to verify connectivity
### MySQL connection refused
- Check if MySQL is running: `sudo service mysql status`
- Verify the password: `mysql -uroot -pmysecretpassword -e "SELECT 1;"`
- Check if MySQL is listening on TCP: `netstat -tlnp | grep 3306`
### PostgreSQL authentication failed
- Verify pg_hba.conf has md5 authentication for localhost
- Check password: `PGPASSWORD=postgres psql -h localhost -U postgres -c "SELECT 1;"`
- Reload PostgreSQL after config changes: `sudo service postgresql reload`
### DNS resolution fails
This is expected in some environments. Configure apt proxy as shown above.

View File

@@ -0,0 +1,143 @@
package main
import (
"encoding/json"
"fmt"
"os"
"path/filepath"
"runtime"
"strings"
"testing"
)
type Testcase struct {
Name string
Path string
ConfigName string
Stderr []byte
Stdout []byte
Exec *Exec
}
type ExecMeta struct {
InvalidSchema bool `json:"invalid_schema"`
}
type Exec struct {
Command string `json:"command"`
Args []string `json:"args"`
Contexts []string `json:"contexts"`
Process string `json:"process"`
OS []string `json:"os"`
Env map[string]string `json:"env"`
Meta ExecMeta `json:"meta"`
}
func parseStderr(t *testing.T, dir, testctx string) []byte {
t.Helper()
paths := []string{
filepath.Join(dir, "stderr", fmt.Sprintf("%s.txt", testctx)),
filepath.Join(dir, fmt.Sprintf("stderr_%s.txt", runtime.GOOS)),
filepath.Join(dir, "stderr.txt"),
}
for _, path := range paths {
if _, err := os.Stat(path); !os.IsNotExist(err) {
blob, err := os.ReadFile(path)
if err != nil {
t.Fatal(err)
}
return blob
}
}
return nil
}
// parseStdout reads the command's expected output: stdout.json for a
// command that prints JSON, so editors highlight it, otherwise stdout.txt.
func parseStdout(t *testing.T, dir string) []byte {
t.Helper()
for _, name := range []string{"stdout.json", "stdout.txt"} {
path := filepath.Join(dir, name)
if _, err := os.Stat(path); os.IsNotExist(err) {
continue
}
blob, err := os.ReadFile(path)
if err != nil {
t.Fatal(err)
}
return blob
}
return nil
}
// hasSQLCConfig reports whether dir contains an sqlc configuration file.
func hasSQLCConfig(dir string) bool {
for _, name := range []string{"sqlc.json", "sqlc.yaml", "sqlc.yml"} {
if _, err := os.Stat(filepath.Join(dir, name)); err == nil {
return true
}
}
return false
}
func parseExec(t *testing.T, dir string) *Exec {
t.Helper()
path := filepath.Join(dir, "exec.json")
if _, err := os.Stat(path); os.IsNotExist(err) {
return nil
}
var e Exec
blob, err := os.ReadFile(path)
if err != nil {
t.Fatalf("%s: %s", path, err)
}
if err := json.Unmarshal(blob, &e); err != nil {
t.Fatalf("%s: %s", path, err)
}
if e.Command == "" {
e.Command = "generate"
}
return &e
}
func FindTests(t *testing.T, root, testctx string) []*Testcase {
var tcs []*Testcase
err := filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
name := info.Name()
if name == "sqlc.json" || name == "sqlc.yaml" || name == "sqlc.yml" {
dir := filepath.Dir(path)
tcs = append(tcs, &Testcase{
Path: dir,
Name: strings.TrimPrefix(dir, root+string(filepath.Separator)),
ConfigName: name,
Stderr: parseStderr(t, dir, testctx),
Stdout: parseStdout(t, dir),
Exec: parseExec(t, dir),
})
return filepath.SkipDir
}
// Config-less command tests (e.g. parse, analyze) are discovered by
// their exec.json when no sqlc config is present in the directory.
if name == "exec.json" {
dir := filepath.Dir(path)
if !hasSQLCConfig(dir) {
tcs = append(tcs, &Testcase{
Path: dir,
Name: strings.TrimPrefix(dir, root+string(filepath.Separator)),
Stderr: parseStderr(t, dir, testctx),
Stdout: parseStdout(t, dir),
Exec: parseExec(t, dir),
})
return filepath.SkipDir
}
}
return nil
})
if err != nil {
t.Fatal(err)
}
return tcs
}

View File

@@ -0,0 +1,64 @@
package main
import (
"fmt"
"os"
"path/filepath"
"testing"
"github.com/sqlc-dev/sqlc/internal/config"
"github.com/sqlc-dev/sqlc/internal/sqltest/local"
)
func TestValidSchema(t *testing.T) {
for _, replay := range FindTests(t, "testdata", "base") {
if replay.Exec != nil {
if replay.Exec.Meta.InvalidSchema {
continue
}
}
// Config-less command tests (parse, analyze) have no schema to validate.
if replay.ConfigName == "" {
continue
}
file := filepath.Join(replay.Path, replay.ConfigName)
rd, err := os.Open(file)
if err != nil {
t.Fatal(err)
}
conf, err := config.ParseConfig(rd)
if err != nil {
t.Fatal(err)
}
for j, pkg := range conf.SQL {
switch pkg.Engine {
case config.EnginePostgreSQL:
// pass
case config.EngineMySQL:
// pass
default:
continue
}
t.Run(fmt.Sprintf("endtoend-%s-%d", file, j), func(t *testing.T) {
t.Parallel()
var schema []string
for _, path := range pkg.Schema {
schema = append(schema, filepath.Join(filepath.Dir(file), path))
}
switch pkg.Engine {
case config.EnginePostgreSQL:
local.PostgreSQL(t, schema)
case config.EngineMySQL:
local.MySQL(t, schema)
}
})
}
}
}

View File

@@ -0,0 +1,456 @@
package main
import (
"bytes"
"context"
"fmt"
"os"
osexec "os/exec"
"path/filepath"
"runtime"
"slices"
"strings"
"testing"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"github.com/sqlc-dev/sqlc/internal/cmd"
"github.com/sqlc-dev/sqlc/internal/config"
"github.com/sqlc-dev/sqlc/internal/opts"
"github.com/sqlc-dev/sqlc/internal/sqlcdebug"
"github.com/sqlc-dev/sqlc/internal/sqltest/docker"
"github.com/sqlc-dev/sqlc/internal/sqltest/native"
)
// withSQLCDEBUG installs the given SQLCDEBUG-formatted string for the
// duration of the test and restores the empty default afterwards.
//
// Callers in TestReplay are sequential, so this does not need a mutex:
// Go's test scheduler does not run TestReplay concurrently with the
// parallel top-level tests in this package.
func withSQLCDEBUG(t *testing.T, raw string) func() {
t.Helper()
if raw == "" {
return func() {}
}
sqlcdebug.Update(raw)
return func() { sqlcdebug.Update("") }
}
func lineEndings() cmp.Option {
return cmp.Transformer("LineEndings", func(in string) string {
// Replace Windows new lines with Unix newlines
return strings.Replace(in, "\r\n", "\n", -1)
})
}
func stderrTransformer() cmp.Option {
return cmp.Transformer("Stderr", func(in string) string {
s := strings.Replace(in, "\r", "", -1)
return strings.Replace(s, "\\", "/", -1)
})
}
func TestExamples(t *testing.T) {
t.Parallel()
ctx := context.Background()
examples, err := filepath.Abs(filepath.Join("..", "..", "examples"))
if err != nil {
t.Fatal(err)
}
files, err := os.ReadDir(examples)
if err != nil {
t.Fatal(err)
}
for _, replay := range files {
if !replay.IsDir() {
continue
}
tc := replay.Name()
t.Run(tc, func(t *testing.T) {
t.Parallel()
path := filepath.Join(examples, tc)
var stderr bytes.Buffer
opts := &cmd.Options{
Env: cmd.Env{},
Stderr: &stderr,
}
output, err := cmd.Generate(ctx, path, "", opts)
if err != nil {
t.Fatalf("sqlc generate failed: %s", stderr.String())
}
cmpDirectory(t, path, output)
})
}
}
func BenchmarkExamples(b *testing.B) {
ctx := context.Background()
examples, err := filepath.Abs(filepath.Join("..", "..", "examples"))
if err != nil {
b.Fatal(err)
}
files, err := os.ReadDir(examples)
if err != nil {
b.Fatal(err)
}
for _, replay := range files {
if !replay.IsDir() {
continue
}
tc := replay.Name()
b.Run(tc, func(b *testing.B) {
path := filepath.Join(examples, tc)
for i := 0; i < b.N; i++ {
var stderr bytes.Buffer
opts := &cmd.Options{
Env: cmd.Env{},
Stderr: &stderr,
}
cmd.Generate(ctx, path, "", opts)
}
})
}
}
type textContext struct {
Mutate func(*testing.T, string) func(*config.Config)
Enabled func() bool
// Experiments names the experiments every case in this context runs
// with. A case's own SQLCEXPERIMENT is appended to these, so a case can
// still turn one back off with the "no" prefix.
Experiments func() []string
}
func TestReplay(t *testing.T) {
// Ensure that this environment variable is always set to true when running
// end-to-end tests
os.Setenv("SQLC_DUMMY_VALUE", "true")
// t.Parallel()
ctx := context.Background()
var mysqlURI, postgresURI string
// First, check environment variables
if uri := os.Getenv("POSTGRESQL_SERVER_URI"); uri != "" {
postgresURI = uri
}
if uri := os.Getenv("MYSQL_SERVER_URI"); uri != "" {
mysqlURI = uri
}
// Try Docker for any missing databases
if postgresURI == "" || mysqlURI == "" {
if err := docker.Installed(); err == nil {
if postgresURI == "" {
host, err := docker.StartPostgreSQLServer(ctx)
if err != nil {
t.Logf("docker postgresql startup failed: %s", err)
} else {
postgresURI = host
}
}
if mysqlURI == "" {
host, err := docker.StartMySQLServer(ctx)
if err != nil {
t.Logf("docker mysql startup failed: %s", err)
} else {
mysqlURI = host
}
}
}
}
// Try native installation for any missing databases (Linux only)
if postgresURI == "" || mysqlURI == "" {
if err := native.Supported(); err == nil {
if postgresURI == "" {
host, err := native.StartPostgreSQLServer(ctx)
if err != nil {
t.Logf("native postgresql startup failed: %s", err)
} else {
postgresURI = host
}
}
if mysqlURI == "" {
host, err := native.StartMySQLServer(ctx)
if err != nil {
t.Logf("native mysql startup failed: %s", err)
} else {
mysqlURI = host
}
}
}
}
// Log which databases are available
t.Logf("PostgreSQL available: %v (URI: %s)", postgresURI != "", postgresURI)
t.Logf("MySQL available: %v (URI: %s)", mysqlURI != "", mysqlURI)
contexts := map[string]textContext{
"base": {
Mutate: func(t *testing.T, path string) func(*config.Config) { return func(c *config.Config) {} },
Enabled: func() bool { return true },
},
"managed-db": {
Mutate: func(t *testing.T, path string) func(*config.Config) {
return func(c *config.Config) {
// Add all servers - tests will fail if database isn't available
c.Servers = []config.Server{
{
Name: "postgres",
Engine: config.EnginePostgreSQL,
URI: postgresURI,
},
{
Name: "mysql",
Engine: config.EngineMySQL,
URI: mysqlURI,
},
}
for i := range c.SQL {
switch c.SQL[i].Engine {
case config.EnginePostgreSQL:
c.SQL[i].Database = &config.Database{
Managed: true,
}
case config.EngineMySQL:
c.SQL[i].Database = &config.Database{
Managed: true,
}
case config.EngineSQLite:
c.SQL[i].Database = &config.Database{
Managed: true,
}
default:
// pass
}
}
}
},
Enabled: func() bool {
// Enabled if at least one database URI is available
return postgresURI != "" || mysqlURI != ""
},
},
"core": {
Mutate: func(t *testing.T, path string) func(*config.Config) { return func(c *config.Config) {} },
Experiments: func() []string { return []string{"coreanalyzer"} },
Enabled: func() bool {
// Running the whole corpus through the analysis core is opt-in
// while the two paths still disagree. The core needs no
// database, so nothing else gates this.
return os.Getenv("SQLC_TEST_CORE") != ""
},
},
}
for name, testctx := range contexts {
if !testctx.Enabled() {
continue
}
for _, replay := range FindTests(t, "testdata", name) {
tc := replay
t.Run(filepath.Join(name, tc.Name), func(t *testing.T) {
var stderr bytes.Buffer
var output map[string]string
var err error
path, _ := filepath.Abs(tc.Path)
args := tc.Exec
if args == nil {
args = &Exec{Command: "generate"}
}
expected := string(tc.Stderr)
if args.Process != "" {
_, err := osexec.LookPath(args.Process)
if err != nil {
t.Skipf("executable not found: %s %s", args.Process, err)
}
}
if len(args.Contexts) > 0 {
if !slices.Contains(args.Contexts, name) {
t.Skipf("unsupported context: %s", name)
}
}
if len(args.OS) > 0 {
if !slices.Contains(args.OS, runtime.GOOS) {
t.Skipf("unsupported os: %s", runtime.GOOS)
}
}
experiments := args.Env["SQLCEXPERIMENT"]
if testctx.Experiments != nil {
experiments = strings.Join(append(testctx.Experiments(), experiments), ",")
}
opts := cmd.Options{
Env: cmd.Env{
Experiment: opts.ExperimentFromString(experiments),
},
Stderr: &stderr,
MutateConfig: testctx.Mutate(t, path),
}
release := withSQLCDEBUG(t, args.Env["SQLCDEBUG"])
defer release()
switch args.Command {
case "diff":
err = cmd.Diff(ctx, path, "", &opts)
case "generate":
output, err = cmd.Generate(ctx, path, "", &opts)
if err == nil {
cmpDirectory(t, path, output)
}
case "vet":
err = cmd.Vet(ctx, path, "", &opts)
case "parse", "analyze", "fmt":
// These commands are flag-driven (fmt cases pass --diff so
// nothing is rewritten). Run them through the real CLI
// entry point from inside the test directory so file
// arguments resolve and the output stays independent of
// the absolute path.
var stdout bytes.Buffer
wd, werr := os.Getwd()
if werr != nil {
t.Fatal(werr)
}
if cerr := os.Chdir(path); cerr != nil {
t.Fatal(cerr)
}
code := cmd.Do(append([]string{args.Command}, args.Args...), nil, &stdout, &stderr)
if cerr := os.Chdir(wd); cerr != nil {
t.Fatal(cerr)
}
if code != 0 {
err = fmt.Errorf("%s exited with code %d", args.Command, code)
} else if diff := cmp.Diff(strings.TrimSpace(string(tc.Stdout)), strings.TrimSpace(stdout.String()), lineEndings()); diff != "" {
t.Errorf("stdout differed (-want +got):\n%s", diff)
}
default:
t.Fatalf("unknown command")
}
if len(expected) == 0 && err != nil {
t.Fatalf("sqlc %s failed: %s", args.Command, stderr.String())
}
diff := cmp.Diff(
strings.TrimSpace(expected),
strings.TrimSpace(stderr.String()),
stderrTransformer(),
)
if diff != "" {
t.Fatalf("stderr differed (-want +got):\n%s", diff)
}
})
}
}
}
func cmpDirectory(t *testing.T, dir string, actual map[string]string) {
expected := map[string]string{}
var ff = func(path string, file os.FileInfo, err error) error {
if err != nil {
return err
}
if file.IsDir() {
return nil
}
if !strings.HasSuffix(path, ".go") && !strings.HasSuffix(path, ".kt") && !strings.HasSuffix(path, ".py") && !strings.HasSuffix(path, ".json") && !strings.HasSuffix(path, ".txt") {
return nil
}
// TODO: Figure out a better way to ignore certain files
if strings.HasSuffix(path, ".txt") && filepath.Base(path) != "hello.txt" {
return nil
}
if filepath.Base(path) == "sqlc.json" {
return nil
}
if filepath.Base(path) == "exec.json" {
return nil
}
if strings.Contains(path, "/kotlin/build") {
return nil
}
if strings.HasSuffix(path, "_test.go") || strings.Contains(path, "src/test/") {
return nil
}
if strings.Contains(path, "/python/.venv") || strings.Contains(path, "/python/src/tests/") ||
strings.HasSuffix(path, "__init__.py") || strings.Contains(path, "/python/src/dbtest/") ||
strings.Contains(path, "/python/.mypy_cache") {
return nil
}
blob, err := os.ReadFile(path)
if err != nil {
return err
}
expected[path] = string(blob)
return nil
}
if err := filepath.Walk(dir, ff); err != nil {
t.Fatal(err)
}
opts := []cmp.Option{
cmpopts.EquateEmpty(),
lineEndings(),
}
if !cmp.Equal(expected, actual, opts...) {
t.Errorf("%s contents differ", dir)
for name, contents := range expected {
if actual[name] == "" {
t.Errorf("%s is empty", name)
return
}
if diff := cmp.Diff(contents, actual[name], opts...); diff != "" {
t.Errorf("%s differed (-want +got):\n%s", name, diff)
}
}
}
}
func BenchmarkReplay(b *testing.B) {
ctx := context.Background()
var dirs []string
err := filepath.Walk("testdata", func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if info.Name() == "sqlc.json" || info.Name() == "sqlc.yaml" || info.Name() == "sqlc.yml" {
dirs = append(dirs, filepath.Dir(path))
return filepath.SkipDir
}
return nil
})
if err != nil {
b.Fatal(err)
}
for _, replay := range dirs {
tc := replay
b.Run(tc, func(b *testing.B) {
path, _ := filepath.Abs(tc)
for i := 0; i < b.N; i++ {
var stderr bytes.Buffer
opts := &cmd.Options{
Env: cmd.Env{},
Stderr: &stderr,
}
cmd.Generate(ctx, path, "", opts)
}
})
}
}

View File

@@ -0,0 +1,194 @@
package main
import (
"bytes"
"fmt"
"io"
"os"
"path/filepath"
"strings"
"testing"
"github.com/sqlc-dev/sqlc/internal/config"
"github.com/sqlc-dev/sqlc/internal/debug"
"github.com/sqlc-dev/sqlc/internal/engine/dolphin"
"github.com/sqlc-dev/sqlc/internal/engine/postgresql"
"github.com/sqlc-dev/sqlc/internal/engine/sqlite"
"github.com/sqlc-dev/sqlc/internal/sql/ast"
"github.com/sqlc-dev/sqlc/internal/sql/format"
)
// sqlParser is an interface for SQL parsers
type sqlParser interface {
Parse(r io.Reader) ([]ast.Statement, error)
}
// sqlFormatter is an interface for formatters
type sqlFormatter interface {
format.Dialect
}
func TestFormat(t *testing.T) {
t.Parallel()
for _, tc := range FindTests(t, "testdata", "base") {
// Config-less command tests (parse, analyze) have no config to format.
if tc.ConfigName == "" {
continue
}
t.Run(tc.Name, func(t *testing.T) {
// Parse the config file to determine the engine
configPath := filepath.Join(tc.Path, tc.ConfigName)
configFile, err := os.Open(configPath)
if err != nil {
t.Fatal(err)
}
conf, err := config.ParseConfig(configFile)
configFile.Close()
if err != nil {
t.Fatal(err)
}
// Skip if there are no SQL packages configured
if len(conf.SQL) == 0 {
return
}
engine := conf.SQL[0].Engine
// Select the appropriate parser and fingerprint function based on engine
var parse sqlParser
var formatter sqlFormatter
var fingerprint func(string) (string, error)
switch engine {
case config.EnginePostgreSQL:
pgParser := postgresql.NewParser()
parse = pgParser
formatter = pgParser
fingerprint = postgresql.Fingerprint
case config.EngineMySQL:
mysqlParser := dolphin.NewParser()
parse = mysqlParser
formatter = mysqlParser
// For MySQL, we use a "round-trip" fingerprint: parse the SQL, format it,
// and return the formatted string. This tests that our formatting produces
// valid SQL that parses to the same AST structure.
fingerprint = func(sql string) (string, error) {
stmts, err := mysqlParser.Parse(strings.NewReader(sql))
if err != nil {
return "", err
}
if len(stmts) == 0 {
return "", nil
}
return ast.Format(stmts[0].Raw, mysqlParser), nil
}
case config.EngineSQLite:
sqliteParser := sqlite.NewParser()
parse = sqliteParser
formatter = sqliteParser
// For SQLite, we use the same "round-trip" fingerprint strategy as MySQL:
// parse the SQL, format it, and return the formatted string.
fingerprint = func(sql string) (string, error) {
stmts, err := sqliteParser.Parse(strings.NewReader(sql))
if err != nil {
return "", err
}
if len(stmts) == 0 {
return "", nil
}
return strings.ToLower(ast.Format(stmts[0].Raw, sqliteParser)), nil
}
default:
// Skip unsupported engines
return
}
// Find query files from config
var queryFiles []string
for _, sql := range conf.SQL {
for _, q := range sql.Queries {
queryPath := filepath.Join(tc.Path, q)
info, err := os.Stat(queryPath)
if err != nil {
continue
}
if info.IsDir() {
// If it's a directory, glob for .sql files
matches, err := filepath.Glob(filepath.Join(queryPath, "*.sql"))
if err != nil {
continue
}
queryFiles = append(queryFiles, matches...)
} else {
queryFiles = append(queryFiles, queryPath)
}
}
}
if len(queryFiles) == 0 {
return
}
for _, queryFile := range queryFiles {
if _, err := os.Stat(queryFile); os.IsNotExist(err) {
continue
}
contents, err := os.ReadFile(queryFile)
if err != nil {
t.Fatal(err)
}
// Parse the entire file to get proper statement boundaries
stmts, err := parse.Parse(bytes.NewReader(contents))
if err != nil {
// Skip files with parse errors (e.g., syntax_errors test cases)
return
}
for i, stmt := range stmts {
t.Run(fmt.Sprintf("%d", i), func(t *testing.T) {
// Extract the original query text using statement location and length
start := stmt.Raw.StmtLocation
length := stmt.Raw.StmtLen
if length == 0 {
// If StmtLen is 0, it means the statement goes to the end of the input
length = len(contents) - start
}
query := strings.TrimSpace(string(contents[start : start+length]))
expected, err := fingerprint(query)
if err != nil {
t.Fatal(err)
}
if false {
r, err := postgresql.Parse(query)
debug.Dump(r, err)
}
out := ast.Format(stmt.Raw, formatter)
// An empty rendering is the formatter's signal that
// the statement carries syntax the AST does not
// model; sqlc fmt keeps such statements as written.
if strings.TrimSpace(strings.TrimSuffix(strings.TrimSpace(out), ";")) == "" {
t.Skip("statement has no faithful rendering")
}
actual, err := fingerprint(out)
if err != nil {
t.Error(err)
}
if expected != actual {
debug.Dump(stmt.Raw)
t.Errorf("- %s", expected)
t.Errorf("- %s", query)
t.Errorf("+ %s", actual)
t.Errorf("+ %s", out)
}
})
}
}
})
}
}

View File

@@ -0,0 +1,82 @@
package main
import (
"encoding/json"
"io/fs"
"os"
"path/filepath"
"testing"
"github.com/xeipuuv/gojsonschema"
)
type conf struct {
Version string `json:"version"`
}
func loadSchema(t *testing.T, path string) *gojsonschema.Schema {
t.Helper()
schemaBytes, err := os.ReadFile(path)
if err != nil {
t.Fatal(err)
}
loader := gojsonschema.NewStringLoader(string(schemaBytes))
schema, err := gojsonschema.NewSchema(loader)
if err != nil {
t.Fatalf("invalid schema: %s", err)
}
return schema
}
func TestJsonSchema(t *testing.T) {
t.Parallel()
schemaOne := loadSchema(t, filepath.Join("..", "config", "v_one.json"))
schemaTwo := loadSchema(t, filepath.Join("..", "config", "v_two.json"))
srcs := []string{
filepath.Join("..", "..", "examples"),
filepath.Join("testdata"),
}
for _, dir := range srcs {
err := filepath.WalkDir(dir, func(path string, d fs.DirEntry, err error) error {
if err != nil {
return err
}
if filepath.Base(path) != "sqlc.json" {
return nil
}
t.Run(path, func(t *testing.T) {
t.Parallel()
contents, err := os.ReadFile(path)
if err != nil {
t.Fatal(err)
}
var c conf
if err := json.Unmarshal(contents, &c); err != nil {
t.Fatal(err)
}
l := gojsonschema.NewStringLoader(string(contents))
switch c.Version {
case "1":
if _, err := schemaOne.Validate(l); err != nil {
t.Fatal(err)
}
case "2":
if _, err := schemaTwo.Validate(l); err != nil {
t.Fatal(err)
}
default:
t.Fatalf("unknown schema version: %s", c.Version)
}
})
return nil
})
if err != nil {
t.Error(err)
}
}
}

View File

@@ -0,0 +1,31 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.31.1
package querytest
import (
"context"
"database/sql"
)
type DBTX interface {
ExecContext(context.Context, string, ...any) (sql.Result, error)
PrepareContext(context.Context, string) (*sql.Stmt, error)
QueryContext(context.Context, string, ...any) (*sql.Rows, error)
QueryRowContext(context.Context, string, ...any) *sql.Row
}
func New(db DBTX) *Queries {
return &Queries{db: db}
}
type Queries struct {
db DBTX
}
func (q *Queries) WithTx(tx *sql.Tx) *Queries {
return &Queries{
db: tx,
}
}

View File

@@ -0,0 +1,9 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.31.1
package querytest
type Bar struct {
ID uint64
}

View File

@@ -0,0 +1,20 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.31.1
// source: query.sql
package querytest
import (
"context"
)
const aliasBar = `-- name: AliasBar :exec
DELETE FROM bar b
WHERE b.id = ?
`
func (q *Queries) AliasBar(ctx context.Context, id uint64) error {
_, err := q.db.ExecContext(ctx, aliasBar, id)
return err
}

View File

@@ -0,0 +1,3 @@
-- name: AliasBar :exec
DELETE FROM bar b
WHERE b.id = ?;

View File

@@ -0,0 +1,2 @@
CREATE TABLE bar (id serial not null);

View File

@@ -0,0 +1,12 @@
{
"version": "1",
"packages": [
{
"path": "go",
"engine": "mysql",
"name": "querytest",
"schema": "schema.sql",
"queries": "query.sql"
}
]
}

View File

@@ -0,0 +1,32 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.31.1
package querytest
import (
"context"
"github.com/jackc/pgconn"
"github.com/jackc/pgx/v4"
)
type DBTX interface {
Exec(context.Context, string, ...any) (pgconn.CommandTag, error)
Query(context.Context, string, ...any) (pgx.Rows, error)
QueryRow(context.Context, string, ...any) pgx.Row
}
func New(db DBTX) *Queries {
return &Queries{db: db}
}
type Queries struct {
db DBTX
}
func (q *Queries) WithTx(tx pgx.Tx) *Queries {
return &Queries{
db: tx,
}
}

View File

@@ -0,0 +1,9 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.31.1
package querytest
type Bar struct {
ID int32
}

View File

@@ -0,0 +1,20 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.31.1
// source: query.sql
package querytest
import (
"context"
)
const aliasBar = `-- name: AliasBar :exec
DELETE FROM bar b
WHERE b.id = $1
`
func (q *Queries) AliasBar(ctx context.Context, id int32) error {
_, err := q.db.Exec(ctx, aliasBar, id)
return err
}

View File

@@ -0,0 +1,3 @@
-- name: AliasBar :exec
DELETE FROM bar b
WHERE b.id = $1;

View File

@@ -0,0 +1,2 @@
CREATE TABLE bar (id serial not null);

View File

@@ -0,0 +1,13 @@
{
"version": "1",
"packages": [
{
"path": "go",
"engine": "postgresql",
"sql_package": "pgx/v4",
"name": "querytest",
"schema": "schema.sql",
"queries": "query.sql"
}
]
}

View File

@@ -0,0 +1,32 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.31.1
package querytest
import (
"context"
"github.com/jackc/pgx/v5"
"github.com/jackc/pgx/v5/pgconn"
)
type DBTX interface {
Exec(context.Context, string, ...any) (pgconn.CommandTag, error)
Query(context.Context, string, ...any) (pgx.Rows, error)
QueryRow(context.Context, string, ...any) pgx.Row
}
func New(db DBTX) *Queries {
return &Queries{db: db}
}
type Queries struct {
db DBTX
}
func (q *Queries) WithTx(tx pgx.Tx) *Queries {
return &Queries{
db: tx,
}
}

View File

@@ -0,0 +1,9 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.31.1
package querytest
type Bar struct {
ID int32
}

View File

@@ -0,0 +1,20 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.31.1
// source: query.sql
package querytest
import (
"context"
)
const aliasBar = `-- name: AliasBar :exec
DELETE FROM bar b
WHERE b.id = $1
`
func (q *Queries) AliasBar(ctx context.Context, id int32) error {
_, err := q.db.Exec(ctx, aliasBar, id)
return err
}

View File

@@ -0,0 +1,3 @@
-- name: AliasBar :exec
DELETE FROM bar b
WHERE b.id = $1;

View File

@@ -0,0 +1,2 @@
CREATE TABLE bar (id serial not null);

View File

@@ -0,0 +1,13 @@
{
"version": "1",
"packages": [
{
"path": "go",
"engine": "postgresql",
"sql_package": "pgx/v5",
"name": "querytest",
"schema": "schema.sql",
"queries": "query.sql"
}
]
}

View File

@@ -0,0 +1,31 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.31.1
package querytest
import (
"context"
"database/sql"
)
type DBTX interface {
ExecContext(context.Context, string, ...any) (sql.Result, error)
PrepareContext(context.Context, string) (*sql.Stmt, error)
QueryContext(context.Context, string, ...any) (*sql.Rows, error)
QueryRowContext(context.Context, string, ...any) *sql.Row
}
func New(db DBTX) *Queries {
return &Queries{db: db}
}
type Queries struct {
db DBTX
}
func (q *Queries) WithTx(tx *sql.Tx) *Queries {
return &Queries{
db: tx,
}
}

View File

@@ -0,0 +1,9 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.31.1
package querytest
type Bar struct {
ID int32
}

View File

@@ -0,0 +1,20 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.31.1
// source: query.sql
package querytest
import (
"context"
)
const aliasBar = `-- name: AliasBar :exec
DELETE FROM bar b
WHERE b.id = $1
`
func (q *Queries) AliasBar(ctx context.Context, id int32) error {
_, err := q.db.ExecContext(ctx, aliasBar, id)
return err
}

View File

@@ -0,0 +1,3 @@
-- name: AliasBar :exec
DELETE FROM bar b
WHERE b.id = $1;

View File

@@ -0,0 +1 @@
CREATE TABLE bar (id serial not null);

View File

@@ -0,0 +1,12 @@
{
"version": "1",
"packages": [
{
"path": "go",
"engine": "postgresql",
"name": "querytest",
"schema": "schema.sql",
"queries": "query.sql"
}
]
}

View File

@@ -0,0 +1,31 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.31.1
package querytest
import (
"context"
"database/sql"
)
type DBTX interface {
ExecContext(context.Context, string, ...any) (sql.Result, error)
PrepareContext(context.Context, string) (*sql.Stmt, error)
QueryContext(context.Context, string, ...any) (*sql.Rows, error)
QueryRowContext(context.Context, string, ...any) *sql.Row
}
func New(db DBTX) *Queries {
return &Queries{db: db}
}
type Queries struct {
db DBTX
}
func (q *Queries) WithTx(tx *sql.Tx) *Queries {
return &Queries{
db: tx,
}
}

View File

@@ -0,0 +1,9 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.31.1
package querytest
type Bar struct {
ID int64
}

View File

@@ -0,0 +1,20 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.31.1
// source: query.sql
package querytest
import (
"context"
)
const aliasBar = `-- name: AliasBar :exec
DELETE FROM bar AS b
WHERE b.id = ?
`
func (q *Queries) AliasBar(ctx context.Context, id int64) error {
_, err := q.db.ExecContext(ctx, aliasBar, id)
return err
}

View File

@@ -0,0 +1,3 @@
-- name: AliasBar :exec
DELETE FROM bar AS b
WHERE b.id = ?;

View File

@@ -0,0 +1,2 @@
CREATE TABLE bar (id integer NOT NULL PRIMARY KEY AUTOINCREMENT);

View File

@@ -0,0 +1,12 @@
{
"version": "1",
"packages": [
{
"path": "go",
"engine": "sqlite",
"name": "querytest",
"schema": "schema.sql",
"queries": "query.sql"
}
]
}

View File

@@ -0,0 +1,5 @@
{
"command": "analyze",
"args": ["--dialect", "postgresql", "--schema", "schema.sql", "--ast", "query.sql"],
"contexts": ["base"]
}

View File

@@ -0,0 +1,2 @@
-- name: GetAuthorName :one
SELECT name FROM authors WHERE id = $1;

View File

@@ -0,0 +1,5 @@
CREATE TABLE authors (
id BIGSERIAL PRIMARY KEY,
name text NOT NULL,
bio text
);

View File

@@ -0,0 +1,136 @@
[
{
"name": "GetAuthorName",
"cmd": ":one",
"columns": [
{
"name": "name",
"type": {
"name": "text"
},
"table": "authors"
}
],
"params": [
{
"number": 1,
"column": {
"name": "id",
"type": {
"name": "bigint"
},
"table": "authors"
}
}
],
"ast": {
"tag": "RawStmt",
"stmt": {
"tag": "SelectStmt",
"distinct_clause": {
"tag": "List"
},
"target_list": {
"tag": "List",
"items": [
{
"tag": "ResTarget",
"indirection": {
"tag": "List"
},
"val": {
"tag": "ColumnRef",
"name": "",
"fields": {
"tag": "List",
"items": [
{
"tag": "String",
"str": "name"
}
]
},
"location": 35
},
"location": 35
}
]
},
"from_clause": {
"tag": "List",
"items": [
{
"tag": "RangeVar",
"relname": "authors",
"inh": true,
"relpersistence": 112,
"location": 45
}
]
},
"where_clause": {
"tag": "A_Expr",
"kind": 1,
"name": {
"tag": "List",
"items": [
{
"tag": "String",
"str": "="
}
]
},
"lexpr": {
"tag": "ColumnRef",
"name": "",
"fields": {
"tag": "List",
"items": [
{
"tag": "String",
"str": "id"
}
]
},
"location": 59
},
"rexpr": {
"tag": "ParamRef",
"number": 1,
"location": 64,
"dollar": true
},
"location": 62
},
"group_clause": {
"tag": "List"
},
"having_clause": {
"tag": "TODO"
},
"window_clause": {
"tag": "List"
},
"values_lists": {
"tag": "List"
},
"sort_clause": {
"tag": "List"
},
"limit_offset": {
"tag": "TODO"
},
"limit_count": {
"tag": "TODO"
},
"locking_clause": {
"tag": "List"
},
"op": 0,
"all": false
},
"stmt_location": 0,
"stmt_len": 66
}
}
]

View File

@@ -0,0 +1,5 @@
{
"command": "analyze",
"args": ["--dialect", "clickhouse", "--schema", "schema.sql", "query.sql"],
"contexts": ["base"]
}

View File

@@ -0,0 +1 @@
INSERT INTO events (id, name, tag, amount, created) VALUES (1, 'signup', NULL, 9.5, '2024-01-01 00:00:00');

View File

@@ -0,0 +1,2 @@
-- name: ListEvents :many
SELECT id, name, tag, amount, created FROM events;

View File

@@ -0,0 +1,7 @@
CREATE TABLE events (
id UInt64,
name String,
tag Nullable(String),
amount Float64,
created DateTime
) ENGINE = MergeTree ORDER BY id;

View File

@@ -0,0 +1,45 @@
[
{
"name": "ListEvents",
"cmd": ":many",
"columns": [
{
"name": "id",
"type": {
"name": "uint64"
},
"table": "events"
},
{
"name": "name",
"type": {
"name": "string"
},
"table": "events"
},
{
"name": "tag",
"type": {
"name": "string",
"nullable": true
},
"table": "events"
},
{
"name": "amount",
"type": {
"name": "float64"
},
"table": "events"
},
{
"name": "created",
"type": {
"name": "datetime"
},
"table": "events"
}
],
"params": []
}
]

View File

@@ -0,0 +1,5 @@
{
"command": "analyze",
"args": ["--dialect", "duckdb", "--schema", "schema.sql", "query.sql"],
"contexts": ["base"]
}

View File

@@ -0,0 +1,2 @@
-- name: ListAuthors :many
SELECT id, name, bio, royalties, created FROM authors;

View File

@@ -0,0 +1,7 @@
CREATE TABLE authors (
id BIGINT PRIMARY KEY,
name TEXT NOT NULL,
bio TEXT,
royalties DECIMAL(10,2) NOT NULL,
created TIMESTAMP NOT NULL
);

View File

@@ -0,0 +1,53 @@
[
{
"name": "ListAuthors",
"cmd": ":many",
"columns": [
{
"name": "id",
"type": {
"name": "bigint"
},
"table": "authors"
},
{
"name": "name",
"type": {
"name": "varchar"
},
"table": "authors"
},
{
"name": "bio",
"type": {
"name": "varchar",
"nullable": true
},
"table": "authors"
},
{
"name": "royalties",
"type": {
"name": "decimal",
"args": [
{
"int": 10
},
{
"int": 2
}
]
},
"table": "authors"
},
{
"name": "created",
"type": {
"name": "timestamp"
},
"table": "authors"
}
],
"params": []
}
]

View File

@@ -0,0 +1,5 @@
{
"command": "analyze",
"args": ["--dialect", "googlesql", "--schema", "schema.sql", "query.sql"],
"contexts": ["base"]
}

View File

@@ -0,0 +1,2 @@
-- name: GetUser :one
SELECT id, name FROM users WHERE id = @id;

View File

@@ -0,0 +1,5 @@
CREATE TABLE users (
id INT64 NOT NULL,
name STRING(MAX) NOT NULL,
bio STRING(MAX),
) PRIMARY KEY (id);

View File

@@ -0,0 +1,39 @@
[
{
"name": "GetUser",
"cmd": ":one",
"columns": [
{
"name": "id",
"type": {
"name": "int64"
},
"table": "users"
},
{
"name": "name",
"type": {
"name": "string",
"args": [
{
"ident": "max"
}
]
},
"table": "users"
}
],
"params": [
{
"number": 1,
"column": {
"name": "id",
"type": {
"name": "int64"
},
"table": "users"
}
}
]
}
]

View File

@@ -0,0 +1,5 @@
{
"command": "analyze",
"args": ["--dialect", "mssql", "--schema", "schema.sql", "query.sql"],
"contexts": ["base"]
}

View File

@@ -0,0 +1,2 @@
-- name: ListAuthors :many
SELECT id, name, bio, royalties, created FROM authors;

View File

@@ -0,0 +1,7 @@
CREATE TABLE authors (
id BIGINT IDENTITY(1,1) PRIMARY KEY,
name NVARCHAR(100) NOT NULL,
bio NVARCHAR(MAX),
royalties DECIMAL(10,2) NOT NULL,
created DATETIME2 NOT NULL
);

View File

@@ -0,0 +1,68 @@
[
{
"name": "ListAuthors",
"cmd": ":many",
"columns": [
{
"name": "id",
"type": {
"name": "bigint"
},
"table": "authors"
},
{
"name": "name",
"type": {
"name": "nvarchar",
"args": [
{
"int": 100
}
]
},
"table": "authors"
},
{
"name": "bio",
"type": {
"name": "nvarchar",
"nullable": true,
"args": [
{
"ident": "max"
}
]
},
"table": "authors"
},
{
"name": "royalties",
"type": {
"name": "decimal",
"args": [
{
"int": 10
},
{
"int": 2
}
]
},
"table": "authors"
},
{
"name": "created",
"type": {
"name": "datetime2",
"args": [
{
"int": 7
}
]
},
"table": "authors"
}
],
"params": []
}
]

View File

@@ -0,0 +1,5 @@
{
"command": "analyze",
"args": ["--dialect", "mysql", "--schema", "schema.sql", "query.sql"],
"contexts": ["base"]
}

View File

@@ -0,0 +1,2 @@
-- name: GetUser :one
SELECT id, name FROM users WHERE id = ?;

View File

@@ -0,0 +1,5 @@
CREATE TABLE users (
id BIGINT PRIMARY KEY,
name VARCHAR(255) NOT NULL,
bio TEXT
);

View File

@@ -0,0 +1,39 @@
[
{
"name": "GetUser",
"cmd": ":one",
"columns": [
{
"name": "id",
"type": {
"name": "bigint"
},
"table": "users"
},
{
"name": "name",
"type": {
"name": "varchar",
"args": [
{
"int": 255
}
]
},
"table": "users"
}
],
"params": [
{
"number": 1,
"column": {
"name": "id",
"type": {
"name": "bigint"
},
"table": "users"
}
}
]
}
]

View File

@@ -0,0 +1,5 @@
{
"command": "analyze",
"args": ["--dialect", "postgresql", "--schema", "schema.sql", "query.sql"],
"contexts": ["base"]
}

View File

@@ -0,0 +1,2 @@
-- name: GetAuthor :one
SELECT * FROM authors WHERE id = $1;

View File

@@ -0,0 +1,5 @@
CREATE TABLE authors (
id BIGSERIAL PRIMARY KEY,
name text NOT NULL,
bio text
);

View File

@@ -0,0 +1,42 @@
[
{
"name": "GetAuthor",
"cmd": ":one",
"columns": [
{
"name": "id",
"type": {
"name": "bigint"
},
"table": "authors"
},
{
"name": "name",
"type": {
"name": "text"
},
"table": "authors"
},
{
"name": "bio",
"type": {
"name": "text",
"nullable": true
},
"table": "authors"
}
],
"params": [
{
"number": 1,
"column": {
"name": "id",
"type": {
"name": "bigint"
},
"table": "authors"
}
}
]
}
]

View File

@@ -0,0 +1,5 @@
{
"command": "analyze",
"args": ["--dialect", "sqlite", "--schema", "schema.sql", "query.sql"],
"contexts": ["base"]
}

View File

@@ -0,0 +1,2 @@
-- name: GetUser :one
SELECT id, name FROM users WHERE id = ?;

View File

@@ -0,0 +1,5 @@
CREATE TABLE users (
id INTEGER PRIMARY KEY,
name TEXT NOT NULL,
age INTEGER
);

View File

@@ -0,0 +1,34 @@
[
{
"name": "GetUser",
"cmd": ":one",
"columns": [
{
"name": "id",
"type": {
"name": "integer"
},
"table": "users"
},
{
"name": "name",
"type": {
"name": "text"
},
"table": "users"
}
],
"params": [
{
"number": 1,
"column": {
"name": "id",
"type": {
"name": "integer"
},
"table": "users"
}
}
]
}
]

View File

@@ -0,0 +1,5 @@
{
"command": "analyze",
"args": ["--dialect", "duckdb", "--schema", "schema.sql", "query.sql"],
"contexts": ["base"]
}

View File

@@ -0,0 +1,13 @@
-- name: CreateAuthor :one
INSERT INTO authors (id, name, bio) VALUES ($1, $2, $3) RETURNING id;
-- name: UpdateAuthor :exec
UPDATE authors SET name = $name, bio = $bio WHERE id = $id;
-- name: DeleteBooksByAuthor :exec
DELETE FROM books USING authors a WHERE books.author_id = a.id AND a.name = $1;
-- name: TitlesFromBooks :many
INSERT INTO books (id, author_id, title)
SELECT id + 1000, author_id, upper(title) FROM books WHERE price > $min_price
RETURNING id, title;

View File

@@ -0,0 +1,12 @@
CREATE TABLE authors (
id BIGINT PRIMARY KEY,
name TEXT NOT NULL,
bio TEXT
);
CREATE TABLE books (
id BIGINT PRIMARY KEY,
author_id BIGINT NOT NULL,
title TEXT NOT NULL,
price DECIMAL(10,2)
);

View File

@@ -0,0 +1,144 @@
[
{
"name": "CreateAuthor",
"cmd": ":one",
"columns": [
{
"name": "id",
"type": {
"name": "bigint"
},
"table": "authors"
}
],
"params": [
{
"number": 1,
"column": {
"name": "id",
"type": {
"name": "bigint"
},
"table": "authors"
}
},
{
"number": 2,
"column": {
"name": "name",
"type": {
"name": "varchar"
},
"table": "authors"
}
},
{
"number": 3,
"column": {
"name": "bio",
"type": {
"name": "varchar",
"nullable": true
},
"table": "authors"
}
}
]
},
{
"name": "UpdateAuthor",
"cmd": ":exec",
"columns": [],
"params": [
{
"number": 1,
"column": {
"name": "name",
"type": {
"name": "varchar"
},
"table": "authors"
}
},
{
"number": 2,
"column": {
"name": "bio",
"type": {
"name": "varchar",
"nullable": true
},
"table": "authors"
}
},
{
"number": 3,
"column": {
"name": "id",
"type": {
"name": "bigint"
},
"table": "authors"
}
}
]
},
{
"name": "DeleteBooksByAuthor",
"cmd": ":exec",
"columns": [],
"params": [
{
"number": 1,
"column": {
"name": "name",
"type": {
"name": "varchar"
},
"table": "authors"
}
}
]
},
{
"name": "TitlesFromBooks",
"cmd": ":many",
"columns": [
{
"name": "id",
"type": {
"name": "bigint"
},
"table": "books"
},
{
"name": "title",
"type": {
"name": "varchar"
},
"table": "books"
}
],
"params": [
{
"number": 1,
"column": {
"name": "price",
"type": {
"name": "decimal",
"nullable": true,
"args": [
{
"int": 10
},
{
"int": 2
}
]
},
"table": "books"
}
}
]
}
]

View File

@@ -0,0 +1,5 @@
{
"command": "analyze",
"args": ["--dialect", "googlesql", "--schema", "schema.sql", "query.sql"],
"contexts": ["base"]
}

View File

@@ -0,0 +1,17 @@
-- name: CreateUser :exec
INSERT INTO users (id, name, bio) VALUES (@id, @name, @bio);
-- name: CreateUserReturning :one
INSERT INTO users (id, name) VALUES (@id, @name) THEN RETURN id, name;
-- name: UpdateBio :exec
UPDATE users SET bio = @bio WHERE id = @id;
-- name: UpdateNameReturning :one
UPDATE users SET name = @name WHERE id = @id THEN RETURN id, name;
-- name: DeleteUser :exec
DELETE FROM users WHERE id = @id;
-- name: DeleteUserReturning :one
DELETE FROM users WHERE id = @id THEN RETURN id;

View File

@@ -0,0 +1,5 @@
CREATE TABLE users (
id INT64 NOT NULL,
name STRING(MAX) NOT NULL,
bio STRING(MAX),
) PRIMARY KEY (id);

View File

@@ -0,0 +1,229 @@
[
{
"name": "CreateUser",
"cmd": ":exec",
"columns": [],
"params": [
{
"number": 1,
"column": {
"name": "id",
"type": {
"name": "int64"
},
"table": "users"
}
},
{
"number": 2,
"column": {
"name": "name",
"type": {
"name": "string",
"args": [
{
"ident": "max"
}
]
},
"table": "users"
}
},
{
"number": 3,
"column": {
"name": "bio",
"type": {
"name": "string",
"nullable": true,
"args": [
{
"ident": "max"
}
]
},
"table": "users"
}
}
]
},
{
"name": "CreateUserReturning",
"cmd": ":one",
"columns": [
{
"name": "id",
"type": {
"name": "int64"
},
"table": "users"
},
{
"name": "name",
"type": {
"name": "string",
"args": [
{
"ident": "max"
}
]
},
"table": "users"
}
],
"params": [
{
"number": 1,
"column": {
"name": "id",
"type": {
"name": "int64"
},
"table": "users"
}
},
{
"number": 2,
"column": {
"name": "name",
"type": {
"name": "string",
"args": [
{
"ident": "max"
}
]
},
"table": "users"
}
}
]
},
{
"name": "UpdateBio",
"cmd": ":exec",
"columns": [],
"params": [
{
"number": 1,
"column": {
"name": "bio",
"type": {
"name": "string",
"nullable": true,
"args": [
{
"ident": "max"
}
]
},
"table": "users"
}
},
{
"number": 2,
"column": {
"name": "id",
"type": {
"name": "int64"
},
"table": "users"
}
}
]
},
{
"name": "UpdateNameReturning",
"cmd": ":one",
"columns": [
{
"name": "id",
"type": {
"name": "int64"
},
"table": "users"
},
{
"name": "name",
"type": {
"name": "string",
"args": [
{
"ident": "max"
}
]
},
"table": "users"
}
],
"params": [
{
"number": 1,
"column": {
"name": "name",
"type": {
"name": "string",
"args": [
{
"ident": "max"
}
]
},
"table": "users"
}
},
{
"number": 2,
"column": {
"name": "id",
"type": {
"name": "int64"
},
"table": "users"
}
}
]
},
{
"name": "DeleteUser",
"cmd": ":exec",
"columns": [],
"params": [
{
"number": 1,
"column": {
"name": "id",
"type": {
"name": "int64"
},
"table": "users"
}
}
]
},
{
"name": "DeleteUserReturning",
"cmd": ":one",
"columns": [
{
"name": "id",
"type": {
"name": "int64"
},
"table": "users"
}
],
"params": [
{
"number": 1,
"column": {
"name": "id",
"type": {
"name": "int64"
},
"table": "users"
}
}
]
}
]

View File

@@ -0,0 +1,5 @@
{
"command": "analyze",
"args": ["--dialect", "mssql", "--schema", "schema.sql", "query.sql"],
"contexts": ["base"]
}

View File

@@ -0,0 +1,11 @@
-- name: CreateAuthor :one
INSERT INTO authors (name, bio) OUTPUT INSERTED.id VALUES (@name, @bio);
-- name: UpdateAuthorAlias :exec
UPDATE a SET name = @name FROM authors a WHERE a.id = @id;
-- name: UpdateBookPrices :exec
UPDATE b SET price = @price FROM books b JOIN authors a ON b.author_id = a.id WHERE a.name = @author;
-- name: DeleteBooksByAuthor :exec
DELETE b FROM books b INNER JOIN authors a ON b.author_id = a.id WHERE a.name = @name;

View File

@@ -0,0 +1,12 @@
CREATE TABLE authors (
id BIGINT IDENTITY(1,1) PRIMARY KEY,
name NVARCHAR(100) NOT NULL,
bio NVARCHAR(MAX)
);
CREATE TABLE books (
id BIGINT IDENTITY(1,1) PRIMARY KEY,
author_id BIGINT NOT NULL,
title NVARCHAR(200) NOT NULL,
price DECIMAL(10,2)
);

View File

@@ -0,0 +1,143 @@
[
{
"name": "CreateAuthor",
"cmd": ":one",
"columns": [
{
"name": "id",
"type": {
"name": "bigint"
},
"table": "authors"
}
],
"params": [
{
"number": 1,
"column": {
"name": "name",
"type": {
"name": "nvarchar",
"args": [
{
"int": 100
}
]
},
"table": "authors"
}
},
{
"number": 2,
"column": {
"name": "bio",
"type": {
"name": "nvarchar",
"nullable": true,
"args": [
{
"ident": "max"
}
]
},
"table": "authors"
}
}
]
},
{
"name": "UpdateAuthorAlias",
"cmd": ":exec",
"columns": [],
"params": [
{
"number": 1,
"column": {
"name": "name",
"type": {
"name": "nvarchar",
"args": [
{
"int": 100
}
]
},
"table": "authors"
}
},
{
"number": 2,
"column": {
"name": "id",
"type": {
"name": "bigint"
},
"table": "authors"
}
}
]
},
{
"name": "UpdateBookPrices",
"cmd": ":exec",
"columns": [],
"params": [
{
"number": 1,
"column": {
"name": "price",
"type": {
"name": "decimal",
"nullable": true,
"args": [
{
"int": 10
},
{
"int": 2
}
]
},
"table": "books"
}
},
{
"number": 2,
"column": {
"name": "name",
"type": {
"name": "nvarchar",
"args": [
{
"int": 100
}
]
},
"table": "authors"
}
}
]
},
{
"name": "DeleteBooksByAuthor",
"cmd": ":exec",
"columns": [],
"params": [
{
"number": 1,
"column": {
"name": "name",
"type": {
"name": "nvarchar",
"args": [
{
"int": 100
}
]
},
"table": "authors"
}
}
]
}
]

View File

@@ -0,0 +1,5 @@
{
"command": "analyze",
"args": ["--dialect", "mysql", "--schema", "schema.sql", "query.sql"],
"contexts": ["base"]
}

View File

@@ -0,0 +1,11 @@
-- name: CreateUser :exec
INSERT INTO users (id, name, bio) VALUES (?, ?, ?);
-- name: UpdateBio :exec
UPDATE users SET bio = ? WHERE id = ?;
-- name: DeleteUser :exec
DELETE FROM users WHERE id = ?;
-- name: DeleteUserPosts :exec
DELETE p FROM posts p JOIN users u ON u.id = p.user_id WHERE u.name = ?;

View File

@@ -0,0 +1,12 @@
CREATE TABLE users (
id BIGINT PRIMARY KEY,
name VARCHAR(255) NOT NULL,
bio TEXT
);
CREATE TABLE posts (
id BIGINT PRIMARY KEY,
user_id BIGINT NOT NULL,
title VARCHAR(255),
created DATETIME NOT NULL
);

View File

@@ -0,0 +1,112 @@
[
{
"name": "CreateUser",
"cmd": ":exec",
"columns": [],
"params": [
{
"number": 1,
"column": {
"name": "id",
"type": {
"name": "bigint"
},
"table": "users"
}
},
{
"number": 2,
"column": {
"name": "name",
"type": {
"name": "varchar",
"args": [
{
"int": 255
}
]
},
"table": "users"
}
},
{
"number": 3,
"column": {
"name": "bio",
"type": {
"name": "text",
"nullable": true
},
"table": "users"
}
}
]
},
{
"name": "UpdateBio",
"cmd": ":exec",
"columns": [],
"params": [
{
"number": 1,
"column": {
"name": "bio",
"type": {
"name": "text",
"nullable": true
},
"table": "users"
}
},
{
"number": 2,
"column": {
"name": "id",
"type": {
"name": "bigint"
},
"table": "users"
}
}
]
},
{
"name": "DeleteUser",
"cmd": ":exec",
"columns": [],
"params": [
{
"number": 1,
"column": {
"name": "id",
"type": {
"name": "bigint"
},
"table": "users"
}
}
]
},
{
"name": "DeleteUserPosts",
"cmd": ":exec",
"columns": [],
"params": [
{
"number": 1,
"column": {
"name": "name",
"type": {
"name": "varchar",
"args": [
{
"int": 255
}
]
},
"table": "users"
}
}
]
}
]

View File

@@ -0,0 +1,5 @@
{
"command": "analyze",
"args": ["--dialect", "postgresql", "--schema", "schema.sql", "query.sql"],
"contexts": ["base"]
}

View File

@@ -0,0 +1,14 @@
-- name: CreateUser :one
INSERT INTO users (name, bio) VALUES ($1, $2) RETURNING *;
-- name: UpdateBio :exec
UPDATE users SET bio = $1 WHERE id = $2;
-- name: UpdateNameReturning :one
UPDATE users SET name = $1 WHERE id = $2 RETURNING id, name;
-- name: DeleteUser :exec
DELETE FROM users WHERE id = $1;
-- name: DeleteUserReturning :one
DELETE FROM users WHERE id = $1 RETURNING id;

View File

@@ -0,0 +1,12 @@
CREATE TABLE users (
id BIGSERIAL PRIMARY KEY,
name text NOT NULL,
bio text
);
CREATE TABLE posts (
id BIGSERIAL PRIMARY KEY,
user_id bigint NOT NULL,
title varchar(255),
created timestamptz NOT NULL
);

View File

@@ -0,0 +1,165 @@
[
{
"name": "CreateUser",
"cmd": ":one",
"columns": [
{
"name": "id",
"type": {
"name": "bigint"
},
"table": "users"
},
{
"name": "name",
"type": {
"name": "text"
},
"table": "users"
},
{
"name": "bio",
"type": {
"name": "text",
"nullable": true
},
"table": "users"
}
],
"params": [
{
"number": 1,
"column": {
"name": "name",
"type": {
"name": "text"
},
"table": "users"
}
},
{
"number": 2,
"column": {
"name": "bio",
"type": {
"name": "text",
"nullable": true
},
"table": "users"
}
}
]
},
{
"name": "UpdateBio",
"cmd": ":exec",
"columns": [],
"params": [
{
"number": 1,
"column": {
"name": "bio",
"type": {
"name": "text",
"nullable": true
},
"table": "users"
}
},
{
"number": 2,
"column": {
"name": "id",
"type": {
"name": "bigint"
},
"table": "users"
}
}
]
},
{
"name": "UpdateNameReturning",
"cmd": ":one",
"columns": [
{
"name": "id",
"type": {
"name": "bigint"
},
"table": "users"
},
{
"name": "name",
"type": {
"name": "text"
},
"table": "users"
}
],
"params": [
{
"number": 1,
"column": {
"name": "name",
"type": {
"name": "text"
},
"table": "users"
}
},
{
"number": 2,
"column": {
"name": "id",
"type": {
"name": "bigint"
},
"table": "users"
}
}
]
},
{
"name": "DeleteUser",
"cmd": ":exec",
"columns": [],
"params": [
{
"number": 1,
"column": {
"name": "id",
"type": {
"name": "bigint"
},
"table": "users"
}
}
]
},
{
"name": "DeleteUserReturning",
"cmd": ":one",
"columns": [
{
"name": "id",
"type": {
"name": "bigint"
},
"table": "users"
}
],
"params": [
{
"number": 1,
"column": {
"name": "id",
"type": {
"name": "bigint"
},
"table": "users"
}
}
]
}
]

View File

@@ -0,0 +1,5 @@
{
"command": "analyze",
"args": ["--dialect", "sqlite", "--schema", "schema.sql", "query.sql"],
"contexts": ["base"]
}

View File

@@ -0,0 +1,8 @@
-- name: CreateUser :one
INSERT INTO users (name, bio) VALUES (?, ?) RETURNING *;
-- name: UpdateBio :exec
UPDATE users SET bio = ? WHERE id = ?;
-- name: DeleteUser :exec
DELETE FROM users WHERE id = ?;

View File

@@ -0,0 +1,12 @@
CREATE TABLE users (
id INTEGER PRIMARY KEY,
name TEXT NOT NULL,
bio TEXT
);
CREATE TABLE posts (
id INTEGER PRIMARY KEY,
user_id INTEGER NOT NULL,
title TEXT,
created TEXT NOT NULL
);

View File

@@ -0,0 +1,98 @@
[
{
"name": "CreateUser",
"cmd": ":one",
"columns": [
{
"name": "id",
"type": {
"name": "integer"
},
"table": "users"
},
{
"name": "name",
"type": {
"name": "text"
},
"table": "users"
},
{
"name": "bio",
"type": {
"name": "text",
"nullable": true
},
"table": "users"
}
],
"params": [
{
"number": 1,
"column": {
"name": "name",
"type": {
"name": "text"
},
"table": "users"
}
},
{
"number": 2,
"column": {
"name": "bio",
"type": {
"name": "text",
"nullable": true
},
"table": "users"
}
}
]
},
{
"name": "UpdateBio",
"cmd": ":exec",
"columns": [],
"params": [
{
"number": 1,
"column": {
"name": "bio",
"type": {
"name": "text",
"nullable": true
},
"table": "users"
}
},
{
"number": 2,
"column": {
"name": "id",
"type": {
"name": "integer"
},
"table": "users"
}
}
]
},
{
"name": "DeleteUser",
"cmd": ":exec",
"columns": [],
"params": [
{
"number": 1,
"column": {
"name": "id",
"type": {
"name": "integer"
},
"table": "users"
}
}
]
}
]

View File

@@ -0,0 +1,5 @@
{
"command": "analyze",
"args": ["--dialect", "clickhouse", "--schema", "schema.sql", "query.sql"],
"contexts": ["base"]
}

View File

@@ -0,0 +1,2 @@
INSERT INTO events VALUES (1, 'signup', NULL, 9.5), (2, 'login', 'web', 0.5);
INSERT INTO users VALUES (1, 'a@example.com');

View File

@@ -0,0 +1,11 @@
-- name: CreateUser :exec
INSERT INTO users (id, email) VALUES (?, ?);
-- name: CreateEvent :exec
INSERT INTO events VALUES (?, ?, ?, ?);
-- name: CreateEvents :exec
INSERT INTO events (id, name) VALUES (?, ?), (?, ?);
-- name: DropUsers :exec
TRUNCATE TABLE users;

View File

@@ -0,0 +1,11 @@
CREATE TABLE events (
id UInt64,
name String,
tag Nullable(String),
amount Float64
) ENGINE = MergeTree ORDER BY id;
CREATE TABLE users (
id UInt64,
email String
) ENGINE = MergeTree ORDER BY id;

View File

@@ -0,0 +1,130 @@
[
{
"name": "CreateUser",
"cmd": ":exec",
"columns": [],
"params": [
{
"number": 1,
"column": {
"name": "id",
"type": {
"name": "uint64"
},
"table": "users"
}
},
{
"number": 2,
"column": {
"name": "email",
"type": {
"name": "string"
},
"table": "users"
}
}
]
},
{
"name": "CreateEvent",
"cmd": ":exec",
"columns": [],
"params": [
{
"number": 1,
"column": {
"name": "id",
"type": {
"name": "uint64"
},
"table": "events"
}
},
{
"number": 2,
"column": {
"name": "name",
"type": {
"name": "string"
},
"table": "events"
}
},
{
"number": 3,
"column": {
"name": "tag",
"type": {
"name": "string",
"nullable": true
},
"table": "events"
}
},
{
"number": 4,
"column": {
"name": "amount",
"type": {
"name": "float64"
},
"table": "events"
}
}
]
},
{
"name": "CreateEvents",
"cmd": ":exec",
"columns": [],
"params": [
{
"number": 1,
"column": {
"name": "id",
"type": {
"name": "uint64"
},
"table": "events"
}
},
{
"number": 2,
"column": {
"name": "name",
"type": {
"name": "string"
},
"table": "events"
}
},
{
"number": 3,
"column": {
"name": "id",
"type": {
"name": "uint64"
},
"table": "events"
}
},
{
"number": 4,
"column": {
"name": "name",
"type": {
"name": "string"
},
"table": "events"
}
}
]
},
{
"name": "DropUsers",
"cmd": ":exec",
"columns": [],
"params": []
}
]

View File

@@ -0,0 +1,5 @@
{
"command": "analyze",
"args": ["--dialect", "clickhouse", "--schema", "schema.sql", "query.sql"],
"contexts": ["base"]
}

View File

@@ -0,0 +1,2 @@
INSERT INTO events VALUES (1, 'signup', NULL, 9.5), (2, 'login', 'web', 0.5);
INSERT INTO users VALUES (1, 'a@example.com');

Some files were not shown because too many files have changed in this diff Show More