Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/examples/.gosqlx-example.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
# Validation settings
validate:
# SQL dialect to use (auto-detect if not specified)
# Options: postgresql, mysql, sqlserver, oracle, sqlite
# Options: generic, postgresql, mysql, mariadb, sqlserver, oracle, sqlite, snowflake, bigquery, redshift, clickhouse
dialect: postgresql

# Enable strict validation mode (more rigorous checks)
Expand Down
2 changes: 1 addition & 1 deletion .gosqlx.example.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ format:
# Validate Configuration
# Controls SQL validation behavior
validate:
# SQL dialect: postgresql, mysql, sqlserver, oracle, sqlite, generic
# SQL dialect: generic, postgresql, mysql, mariadb, sqlserver, oracle, sqlite, snowflake, bigquery, redshift, clickhouse
# Default: postgresql
dialect: postgresql

Expand Down
2 changes: 1 addition & 1 deletion .gosqlx.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
# Place this file in your project root as .gosqlx.yml

# SQL dialect (auto-detect if not specified)
# Options: postgresql, mysql, sqlserver, oracle, sqlite
# Options: generic, postgresql, mysql, mariadb, sqlserver, oracle, sqlite, snowflake, bigquery, redshift, clickhouse
dialect: postgresql

# Linting rules
Expand Down
13 changes: 13 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -370,6 +370,19 @@ acceptance, CLI refusals), so read **Behavior changes** before upgrading.
- **Readable-style output that did not re-parse**: inner `;` and `;;` removed
(#602, #631); readable-style re-parse failures over the testdata corpus fell
from 36 to 6.
- **Dialect lists now agree everywhere**: the `--dialect` help on `parse`,
`format`, `analyze`, `lint` and `validate`, the CLI's unknown-dialect error,
config-file validation, the MCP `validate_sql` schema, the playground WASM
bridge, the GitHub Action and the VS Code `gosqlx.dialect` setting all list or
accept the same 11 names: `generic`, `postgresql`, `mysql`, `mariadb`,
`sqlserver`, `oracle`, `sqlite`, `snowflake`, `bigquery`, `redshift`,
`clickhouse`. Before, help listed 7, the error omitted `generic` and
`clickhouse`, `pkg/config` rejected `generic`, `bigquery`, `redshift` and
`clickhouse`, and the VS Code setting offered 6 (and suggested `mysql` for
`mariadb`). The Go lists derive from the new `keywords.DialectNames()`, and a
test checks the VS Code extension and the example configs and READMEs against
it. Names stay case-sensitive in the CLI. `transpile --from/--to` keep their
own list of transpilable dialects.

### Performance
Numbers are quoted from each PR (benchstat on an Apple M4 unless stated; the
Expand Down
4 changes: 2 additions & 2 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ inputs:
default: ''

dialect:
description: 'SQL dialect: postgresql, mysql, sqlserver, oracle, sqlite (default: auto-detect)'
description: 'SQL dialect: generic, postgresql, mysql, mariadb, sqlserver, oracle, sqlite, snowflake, bigquery, redshift, clickhouse (default: auto-detect)'
required: false
default: ''

Expand Down Expand Up @@ -211,7 +211,7 @@ runs:

# Add dialect if provided
DIALECT="${{ inputs.dialect }}"
if [ -n "$DIALECT" ] && [[ "$DIALECT" =~ ^(postgresql|mysql|sqlserver|oracle|sqlite)$ ]]; then
if [ -n "$DIALECT" ] && [[ "$DIALECT" =~ ^(generic|postgresql|mysql|mariadb|sqlserver|oracle|sqlite|snowflake|bigquery|redshift|clickhouse)$ ]]; then
CMD="$CMD --dialect $DIALECT"
fi

Expand Down
2 changes: 1 addition & 1 deletion action/scripts/validate.sh
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ fi

DIALECT="${INPUT_DIALECT:-}"
if [ -n "$DIALECT" ]; then
if [[ "$DIALECT" =~ ^(postgresql|mysql|sqlserver|oracle|sqlite)$ ]]; then
if [[ "$DIALECT" =~ ^(generic|postgresql|mysql|mariadb|sqlserver|oracle|sqlite|snowflake|bigquery|redshift|clickhouse)$ ]]; then
CMD="$CMD --dialect $DIALECT"
else
echo "::warning::Invalid dialect '$DIALECT', skipping dialect flag"
Expand Down
2 changes: 1 addition & 1 deletion cmd/gosqlx/cmd/analyze.go
Original file line number Diff line number Diff line change
Expand Up @@ -212,5 +212,5 @@ func init() {
analyzeCmd.Flags().BoolVar(&analyzePerformance, "performance", false, "focus on performance optimization analysis (config: analyze.performance)")
analyzeCmd.Flags().BoolVar(&analyzeComplexity, "complexity", false, "focus on complexity metrics (config: analyze.complexity)")
analyzeCmd.Flags().BoolVar(&analyzeAll, "all", false, "comprehensive analysis (config: analyze.all)")
analyzeCmd.Flags().StringVar(&analyzeDialect, "dialect", "", "SQL dialect: postgresql, mysql, mariadb, snowflake, sqlserver, oracle, sqlite (default: postgresql)")
analyzeCmd.Flags().StringVar(&analyzeDialect, "dialect", "", dialectFlagUsage("default: postgresql"))
}
17 changes: 16 additions & 1 deletion cmd/gosqlx/cmd/cmd_utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ package cmd

import (
"fmt"
"strings"

"github.com/spf13/cobra"
"github.com/spf13/pflag"
Expand Down Expand Up @@ -53,13 +54,27 @@ func trackChangedFlags(cmd *cobra.Command) map[string]bool {
// validateDialectName returns an error if dialect is a non-empty, unrecognized
// SQL dialect name. An empty string means "use the default dialect" and is
// always accepted.
// Names are matched case-sensitively, as keywords.IsValidDialect does.
func validateDialectName(dialect string) error {
if dialect != "" && !keywords.IsValidDialect(dialect) {
return fmt.Errorf("unknown SQL dialect %q; valid dialects: postgresql, mysql, mariadb, sqlserver, oracle, sqlite, snowflake, bigquery, redshift", dialect)
return fmt.Errorf("unknown SQL dialect %q; valid dialects: %s", dialect, validDialectsText())
}
return nil
}

// validDialectsText returns the comma-separated list of user-selectable
// dialect names, derived from keywords.DialectNames so CLI help and errors
// never drift from what the library accepts.
func validDialectsText() string {
return strings.Join(keywords.DialectNames(), ", ")
}

// dialectFlagUsage builds the help text for a --dialect flag. suffix is the
// command-specific default note, e.g. "default: postgresql".
func dialectFlagUsage(suffix string) string {
return fmt.Sprintf("SQL dialect: %s (%s)", validDialectsText(), suffix)
}

// tokenizerForDialect returns a tokenizer configured for the given dialect
// together with a release function that must be called when tokenization is
// complete (use defer).
Expand Down
2 changes: 1 addition & 1 deletion cmd/gosqlx/cmd/config_template.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ format:
# Validation settings - controls SQL validation behavior
validate:
# SQL dialect for validation
# Options: postgresql, mysql, sqlserver, oracle, sqlite, generic
# Options: generic, postgresql, mysql, mariadb, sqlserver, oracle, sqlite, snowflake, bigquery, redshift, clickhouse
# Default: postgresql
dialect: postgresql

Expand Down
180 changes: 180 additions & 0 deletions cmd/gosqlx/cmd/dialect_sync_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
// Copyright 2026 GoSQLX Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// Package cmd - dialect_sync_test.go
// Drift guards: every user-facing dialect list (CLI --dialect help, CLI
// errors, VS Code extension settings) must match keywords.DialectNames().

package cmd

import (
"encoding/json"
"os"
"path/filepath"
"reflect"
"regexp"
"strings"
"testing"

"github.com/spf13/cobra"

"github.com/ajitpratap0/GoSQLX/pkg/sql/keywords"
)

// vscodeExtensionDir is the VS Code extension directory relative to this
// package (cmd/gosqlx/cmd).
var vscodeExtensionDir = filepath.Join("..", "..", "..", "vscode-extension")

func TestDialectFlagHelp_ListsEveryDialect(t *testing.T) {
names := keywords.DialectNames()
found := 0
var walk func(c *cobra.Command)
walk = func(c *cobra.Command) {
if f := c.Flags().Lookup("dialect"); f != nil {
found++
for _, name := range names {
if !strings.Contains(f.Usage, name) {
t.Errorf("%q --dialect help %q does not mention %q", c.CommandPath(), f.Usage, name)
}
}
}
for _, sub := range c.Commands() {
walk(sub)
}
}
walk(rootCmd)
// parse, format, analyze, lint, validate at minimum.
if found < 5 {
t.Errorf("found %d commands with a --dialect flag, want at least 5", found)
}
}

func TestValidateDialectName_AcceptsLibraryDialects(t *testing.T) {
for _, name := range keywords.DialectNames() {
if err := validateDialectName(name); err != nil {
t.Errorf("validateDialectName(%q) = %v, want nil", name, err)
}
}

// Case handling is unchanged: names are matched case-sensitively.
if err := validateDialectName("MySQL"); err == nil {
t.Error(`validateDialectName("MySQL") = nil, want error (case-sensitive)`)
}

err := validateDialectName("bogus")
if err == nil {
t.Fatal(`validateDialectName("bogus") = nil, want error`)
}
for _, name := range keywords.DialectNames() {
if !strings.Contains(err.Error(), name) {
t.Errorf("error %q does not list dialect %q", err.Error(), name)
}
}
}

func TestVSCodeDialectSetting_MatchesLibrary(t *testing.T) {
data, err := os.ReadFile(filepath.Join(vscodeExtensionDir, "package.json"))
if err != nil {
t.Fatalf("read vscode-extension/package.json: %v", err)
}
var manifest struct {
Contributes struct {
Configuration struct {
Properties map[string]json.RawMessage `json:"properties"`
} `json:"configuration"`
} `json:"contributes"`
}
if err := json.Unmarshal(data, &manifest); err != nil {
t.Fatalf("parse vscode-extension/package.json: %v", err)
}
raw, ok := manifest.Contributes.Configuration.Properties["gosqlx.dialect"]
if !ok {
t.Fatal("gosqlx.dialect setting not found in vscode-extension/package.json")
}
var setting struct {
Enum []string `json:"enum"`
Default string `json:"default"`
}
if err := json.Unmarshal(raw, &setting); err != nil {
t.Fatalf("parse gosqlx.dialect setting: %v", err)
}
want := keywords.DialectNames()
if !reflect.DeepEqual(setting.Enum, want) {
t.Errorf("gosqlx.dialect enum = %v, want %v (keywords.DialectNames)", setting.Enum, want)
}
if !keywords.IsValidDialect(setting.Default) {
t.Errorf("gosqlx.dialect default %q is not a valid dialect", setting.Default)
}
}

func TestVSCodeValidDialects_MatchesLibrary(t *testing.T) {
data, err := os.ReadFile(filepath.Join(vscodeExtensionDir, "src", "utils", "validation.ts"))
if err != nil {
t.Fatalf("read vscode-extension/src/utils/validation.ts: %v", err)
}
block := regexp.MustCompile(`(?s)export const VALID_DIALECTS = \[(.*?)\] as const;`).FindSubmatch(data)
if block == nil {
t.Fatal("VALID_DIALECTS declaration not found in validation.ts")
}
var got []string
for _, m := range regexp.MustCompile(`'([^']*)'`).FindAllSubmatch(block[1], -1) {
got = append(got, string(m[1]))
}
want := keywords.DialectNames()
if !reflect.DeepEqual(got, want) {
t.Errorf("VALID_DIALECTS = %v, want %v (keywords.DialectNames)", got, want)
}
}

// TestDocDialectLists_MatchLibrary checks the hand-written dialect lists in
// docs and example configs. Each file must contain at least one line naming
// postgresql and mysql, and every such line must name every dialect.
func TestDocDialectLists_MatchLibrary(t *testing.T) {
repoRoot := filepath.Join("..", "..", "..")
files := []string{
"cmd/gosqlx/doc.go",
"cmd/gosqlx/internal/config/README.md",
"pkg/config/README.md",
".gosqlx.example.yml",
".gosqlx.yml",
".github/workflows/examples/.gosqlx-example.yml",
"docs/MCP_GUIDE.md",
"docs/CLI_GUIDE.md",
"docs/CONFIGURATION.md",
}
listLine := regexp.MustCompile("`?postgresql`?, `?mysql`?")
names := keywords.DialectNames()
for _, rel := range files {
data, err := os.ReadFile(filepath.Join(repoRoot, filepath.FromSlash(rel)))
if err != nil {
t.Errorf("read %s: %v", rel, err)
continue
}
lists := 0
for i, line := range strings.Split(string(data), "\n") {
if !listLine.MatchString(line) {
continue
}
lists++
for _, name := range names {
if !strings.Contains(line, name) {
t.Errorf("%s:%d: dialect list does not mention %q: %s", rel, i+1, name, strings.TrimSpace(line))
}
}
}
if lists == 0 {
t.Errorf("%s: no dialect list found (update this test if the list moved)", rel)
}
}
}
2 changes: 1 addition & 1 deletion cmd/gosqlx/cmd/format.go
Original file line number Diff line number Diff line change
Expand Up @@ -249,7 +249,7 @@ func init() {
formatCmd.Flags().BoolVar(&formatCompact, "compact", false, "compact format (config: format.compact)")
formatCmd.Flags().BoolVar(&formatCheck, "check", false, "check if files need formatting (CI mode)")
formatCmd.Flags().IntVar(&formatMaxLine, "max-line", 80, "maximum line length (config: format.max_line_length)")
formatCmd.Flags().StringVar(&formatDialect, "dialect", "", "SQL dialect: postgresql, mysql, mariadb, snowflake, sqlserver, oracle, sqlite (default: postgresql)")
formatCmd.Flags().StringVar(&formatDialect, "dialect", "", dialectFlagUsage("default: postgresql"))

// Add negation flags
formatCmd.Flags().BoolVar(&formatUppercase, "no-uppercase", false, "keep original keyword case")
Expand Down
2 changes: 1 addition & 1 deletion cmd/gosqlx/cmd/lint.go
Original file line number Diff line number Diff line change
Expand Up @@ -445,5 +445,5 @@ func init() {
lintCmd.Flags().IntVar(&lintMaxLength, "max-length", 100, "maximum line length (L005 rule)")
lintCmd.Flags().BoolVar(&lintFailOnWarn, "fail-on-warn", false, "exit with error code on warnings")
lintCmd.Flags().BoolVar(&lintSecurity, "security", false, "run security scanner (LIKE injection, tautology, UNION injection)")
lintCmd.Flags().StringVar(&lintDialect, "dialect", "", "SQL dialect: postgresql, mysql, mariadb, snowflake, sqlserver, oracle, sqlite (default: postgresql)")
lintCmd.Flags().StringVar(&lintDialect, "dialect", "", dialectFlagUsage("default: postgresql"))
}
2 changes: 1 addition & 1 deletion cmd/gosqlx/cmd/parse.go
Original file line number Diff line number Diff line change
Expand Up @@ -182,5 +182,5 @@ func init() {
parseCmd.Flags().BoolVar(&parseShowAST, "ast", false, "show detailed AST structure")
parseCmd.Flags().BoolVar(&parseShowTokens, "tokens", false, "show tokenization output")
parseCmd.Flags().BoolVar(&parseTreeView, "tree", false, "show tree visualization")
parseCmd.Flags().StringVar(&parseDialect, "dialect", "", "SQL dialect: postgresql, mysql, mariadb, snowflake, sqlserver, oracle, sqlite (default: postgresql)")
parseCmd.Flags().StringVar(&parseDialect, "dialect", "", dialectFlagUsage("default: postgresql"))
}
3 changes: 3 additions & 0 deletions cmd/gosqlx/cmd/transpile.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,9 @@ Examples:
}

func init() {
// The transpile dialect list is deliberately separate from
// keywords.DialectNames(): it names the dialects parseDialectFlag maps for
// gosqlx.Transpile (and accepts the postgres/mssql aliases).
transpileCmd.Flags().String("from", "mysql", "Source dialect (mysql, postgres, sqlite, sqlserver, oracle, snowflake, clickhouse, mariadb)")
transpileCmd.Flags().String("to", "postgres", "Target dialect (mysql, postgres, sqlite, sqlserver, oracle, snowflake, clickhouse, mariadb)")
rootCmd.AddCommand(transpileCmd)
Expand Down
6 changes: 3 additions & 3 deletions cmd/gosqlx/cmd/validate.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,8 +88,8 @@ func validateRun(cmd *cobra.Command, args []string) error {
}

// Reject unknown dialect names early before any parsing.
if validateDialect != "" && !keywords.IsValidDialect(validateDialect) {
return fmt.Errorf("unknown SQL dialect %q; valid dialects: postgresql, mysql, mariadb, sqlserver, oracle, sqlite, snowflake, bigquery, redshift", validateDialect)
if err := validateDialectName(validateDialect); err != nil {
return err
}

// Handle stdin input
Expand Down Expand Up @@ -334,7 +334,7 @@ func init() {
validateCmd.Flags().BoolVarP(&validateQuiet, "quiet", "q", false, "quiet mode (exit code only)")
validateCmd.Flags().BoolVar(&validateQuiet, "check", false, "check mode (alias for --quiet): exit code only, no output")
validateCmd.Flags().BoolVarP(&validateStats, "stats", "s", false, "show performance statistics")
validateCmd.Flags().StringVar(&validateDialect, "dialect", "", "SQL dialect: postgresql, mysql, mariadb, snowflake, sqlserver, oracle, sqlite (config: validate.dialect)")
validateCmd.Flags().StringVar(&validateDialect, "dialect", "", dialectFlagUsage("config: validate.dialect"))
validateCmd.Flags().BoolVar(&validateStrict, "strict", false, "enable strict validation mode (config: validate.strict_mode)")
validateCmd.Flags().StringVar(&validateOutputFormat, "output-format", "text", "output format: text, json, sarif")
validateCmd.Flags().StringVar(&validateOutputFile, "output-file", "", "output file path (default: stdout)")
Expand Down
2 changes: 1 addition & 1 deletion cmd/gosqlx/doc.go
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@
// -p, --pattern string File pattern for recursive processing (default "*.sql")
// -q, --quiet Quiet mode (exit code only)
// -s, --stats Show performance statistics
// --dialect string SQL dialect: postgresql, mysql, sqlserver, oracle, sqlite
// --dialect string SQL dialect: generic, postgresql, mysql, mariadb, sqlserver, oracle, sqlite, snowflake, bigquery, redshift, clickhouse
// --strict Enable strict validation mode
// --output-format string Output format: text, json, sarif (default "text")
// --output-file string Output file path (default: stdout)
Expand Down
4 changes: 2 additions & 2 deletions cmd/gosqlx/internal/config/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ type FormatConfig struct {

```go
type ValidationConfig struct {
Dialect string // SQL dialect (postgresql, mysql, sqlserver, oracle, sqlite, generic)
Dialect string // SQL dialect (generic, postgresql, mysql, mariadb, sqlserver, oracle, sqlite, snowflake, bigquery, redshift, clickhouse)
StrictMode bool // Enable strict validation
Recursive bool // Recursively process directories
Pattern string // File pattern for recursive processing
Expand Down Expand Up @@ -298,7 +298,7 @@ The package validates configuration values to ensure correctness:

- **indent**: Must be between 0 and 8
- **max_line_length**: Must be between 0 and 500
- **dialect**: Must be one of: postgresql, mysql, sqlserver, oracle, sqlite, generic
- **dialect**: Must be one of: generic, postgresql, mysql, mariadb, sqlserver, oracle, sqlite, snowflake, bigquery, redshift, clickhouse
- **output.format**: Must be one of: json, yaml, table, tree, auto

## Testing
Expand Down
Loading
Loading