Files
zeekay 3583e59e56 debrand: rename the CLI, docs and fixture content the server does not define
This repo was previously a module-path-only rebrand; the CLI, build and docs
still described the upstream project.

The binary is now sqlparser. Makefile's PKG_FILES sed still stripped the
UPSTREAM module prefix, so it matched nothing and left full import paths in the
coverage list. main.go dropped its vendor import alias, and the package no
longer shadows itself.

README described the upstream project end to end: its CI, coverage and report
card badges, its install path, and a brew formula we do not publish. Rewritten
for this fork, and its Go examples were compiled and run rather than hand
written.

Fixture content that is merely arbitrary — a greeting string literal and a
table name — was renamed, and the golden files REGENERATED with -update rather
than edited, because they encode character offsets that shift with the text.

Two classes of fixture content are deliberately left alone, and CI records why:
ReplicatedMergeTree '/clickhouse/tables/...' ZooKeeper paths are persisted
state (our own production server stores replicas under exactly that prefix),
and SOURCE(CLICKHOUSE(...)) is a keyword in the server's dictionary grammar
that this parser must accept. Renaming either would make the parser reject
real SQL.
2026-07-26 11:55:25 -07:00

150 lines
4.4 KiB
Go

package parser
import (
"encoding/json"
"flag"
"os"
"path/filepath"
"strings"
"testing"
"github.com/sebdah/goldie/v2"
"github.com/stretchr/testify/require"
)
var runCompatible = flag.Bool("compatible", false, "run compatible test")
func TestParser_Compatible(t *testing.T) {
if !*runCompatible {
t.Skip("Compatible test runs only if -compatible is set")
}
dir := "./testdata/query/compatible/1_stateful"
entries, err := os.ReadDir(dir)
if err != nil {
require.NoError(t, err)
}
for _, entry := range entries {
if !strings.HasSuffix(entry.Name(), ".sql") {
continue
}
t.Run(entry.Name(), func(t *testing.T) {
fileBytes, err := os.ReadFile(filepath.Join(dir, entry.Name()))
require.NoError(t, err)
parser := Parser{
lexer: NewLexer(string(fileBytes)),
}
_, err = parser.ParseStmts()
require.NoError(t, err)
})
}
}
func TestParser_ParseStatements(t *testing.T) {
for _, dir := range []string{"./testdata/dml", "./testdata/ddl", "./testdata/query", "./testdata/basic"} {
outputDir := dir + "/output"
entries, err := os.ReadDir(dir)
if err != nil {
require.NoError(t, err)
}
for _, entry := range entries {
if !strings.HasSuffix(entry.Name(), ".sql") {
continue
}
t.Run(entry.Name(), func(t *testing.T) {
fileBytes, err := os.ReadFile(filepath.Join(dir, entry.Name()))
require.NoError(t, err)
parser := Parser{
lexer: NewLexer(string(fileBytes)),
}
stmts, err := parser.ParseStmts()
require.NoError(t, err)
outputBytes, _ := json.MarshalIndent(stmts, "", " ")
g := goldie.New(t,
goldie.WithNameSuffix(".golden.json"),
goldie.WithDiffEngine(goldie.ColoredDiff),
goldie.WithFixtureDir(outputDir))
g.Assert(t, entry.Name(), outputBytes)
})
}
}
}
func TestParser_Format(t *testing.T) {
for _, dir := range []string{"./testdata/dml", "./testdata/ddl", "./testdata/query", "./testdata/basic"} {
outputDir := dir + "/format"
entries, err := os.ReadDir(dir)
if err != nil {
require.NoError(t, err)
}
for _, entry := range entries {
if !strings.HasSuffix(entry.Name(), ".sql") {
continue
}
t.Run(entry.Name(), func(t *testing.T) {
fileBytes, err := os.ReadFile(filepath.Join(dir, entry.Name()))
require.NoError(t, err)
parser := Parser{
lexer: NewLexer(string(fileBytes)),
}
stmts, err := parser.ParseStmts()
require.NoError(t, err)
var builder strings.Builder
builder.WriteString("-- Origin SQL:\n")
builder.Write(fileBytes)
builder.WriteString("\n\n-- Format SQL:\n")
var formatSQLBuilder strings.Builder
for _, stmt := range stmts {
formatSQLBuilder.WriteString(stmt.String())
formatSQLBuilder.WriteByte(';')
formatSQLBuilder.WriteByte('\n')
}
formatSQL := formatSQLBuilder.String()
builder.WriteString(formatSQL)
validFormatSQL(t, formatSQL)
g := goldie.New(t,
goldie.WithNameSuffix(""),
goldie.WithDiffEngine(goldie.ColoredDiff),
goldie.WithFixtureDir(outputDir))
g.Assert(t, entry.Name(), []byte(builder.String()))
})
}
}
}
// validFormatSQL Verify that the format sql can be re-parsed with consistent results
func validFormatSQL(t *testing.T, sql string) {
parser := NewParser(sql)
stmts, err := parser.ParseStmts()
require.NoError(t, err)
var builder strings.Builder
for _, stmt := range stmts {
builder.WriteString(stmt.String())
builder.WriteByte(';')
builder.WriteByte('\n')
}
require.Equal(t, sql, builder.String())
}
func TestParser_InvalidSyntax(t *testing.T) {
invalidSQLs := []string{
"SELECT * FROM",
// WITH FILL error cases
"SELECT n FROM t ORDER BY n WITH", // WITH without FILL
"SELECT n FROM t ORDER BY n FILL", // FILL without WITH
"SELECT n FROM t ORDER BY n WITH FILL FROM", // FROM without value
"SELECT n FROM t ORDER BY n WITH FILL TO", // TO without value
"SELECT n FROM t ORDER BY n WITH FILL STEP", // STEP without value
"SELECT n FROM t ORDER BY n WITH FILL STALENESS", // STALENESS without value
"SELECT n FROM t ORDER BY n WITH FILL INTERPOLATE (x", // Missing closing paren
"SELECT n FROM t ORDER BY n WITH FILL INTERPOLATE x AS x + 1", // Missing parens around column list
}
for _, sql := range invalidSQLs {
parser := NewParser(sql)
_, err := parser.ParseStmts()
require.Error(t, err, "Expected error for SQL: %s", sql)
}
}