mirror of
https://github.com/hanzo-ds/sqlparser.git
synced 2026-10-30 20:53:50 +00:00
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.
150 lines
4.4 KiB
Go
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)
|
|
}
|
|
}
|