Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
120 commits
Select commit Hold shift + click to select a range
07a248b
fix(parser): model EXPLAIN <stmt> as ExplainStatement
Oct 22, 2026
a109ed9
test(realworld): add 2.7k-statement multi-dialect real-world corpus w…
Oct 22, 2026
0c00506
fix(parser): operators and predicates (IS forms, ANY/ALL arrays, AT T…
Oct 22, 2026
0c3e6a0
fix(parser): complete DML grammar for UPDATE, DELETE, INSERT, REPLACE…
Oct 22, 2026
eb4c736
fix(parser): parse utility statements and keep identifier quoting
Oct 22, 2026
2e052b4
fix(tokenizer): dialect-correct string escapes and missing lexical forms
Oct 22, 2026
109d352
fix(parser): accept keywords used as column names
Oct 22, 2026
263f657
fix(parser): parse and render SELECT-body dialect clauses without dro…
Oct 22, 2026
c1caf6d
fix(parser): reject utility keywords without arguments
Oct 22, 2026
b1891d8
fix(parser): parse keyword-named, qualified and special-form function…
Oct 22, 2026
5839845
fix(formatter): map LIMIT expressions to TOP/FETCH and allow APPLY ta…
Oct 22, 2026
2e28e81
fix(parser,ast): keep dotted quoted collations and index COLLATE; wal…
Oct 22, 2026
ed30f40
fix(parser,ast): tighten LIMIT BY, ASOF and star-modifier edge cases
Oct 22, 2026
5028943
fix(parser): reject bare CALL, SET = and savepoint statements without…
Oct 22, 2026
573bb2a
fix(parser,ast,security): release and scan new primary-expression nodes
Oct 22, 2026
ea03678
Merge branch 'fix/dialect-pkg-h' into test/dialect-audit-2026-10
Oct 22, 2026
6a2d602
Merge branch 'fix/dialect-pkg-c2' into test/dialect-audit-2026-10
Oct 22, 2026
9fce72d
Merge branch 'fix/dialect-pkg-d1' into test/dialect-audit-2026-10
Oct 22, 2026
c3df487
Merge branch 'fix/dialect-pkg-e' into test/dialect-audit-2026-10
Oct 22, 2026
42d361c
Merge branch 'fix/dialect-pkg-c1' into test/dialect-audit-2026-10
Oct 22, 2026
2b98e05
Merge branch 'fix/dialect-pkg-b' into test/dialect-audit-2026-10
Oct 22, 2026
c4d2f34
test(bench): add real-world corpus parse and parse+format benchmarks
Oct 22, 2026
087e4dc
fix(gosqlx): keep procedural bodies and honour GO/DELIMITER in SplitS…
Oct 22, 2026
53bfdb5
fix(parser): end T-SQL utility statements at the next statement and r…
Oct 22, 2026
12a3af1
fix(parser,ast): MERGE OUTPUT INTO, MySQL DELETE rejection, MERGE/REP…
Oct 22, 2026
2410f92
fix(transform): ReplaceTable and AddTableAlias handle multi-table UPD…
Oct 22, 2026
4e2eefd
fix(tokenizer,ast,parser): never change a string literal's value on f…
Oct 22, 2026
b536299
fix(transpiler): re-render string literals for the target dialect
Oct 22, 2026
6e21bfc
feat(parser): parse ALTER TABLE across dialects and ALTER <object> st…
Oct 22, 2026
02f9245
fix(parser,ast,formatter): set-op precedence, CTE and table-factor mo…
Oct 22, 2026
ded32a0
feat(parser,ast,formatter): column definitions and table constraints …
Oct 22, 2026
577571d
fix(formatter): render ALTER ROLE, POLICY and CONNECTOR instead of a …
Oct 22, 2026
d5515c1
feat(parser,ast,formatter): model CREATE TABLE AS, table options, eng…
Oct 22, 2026
975d359
fix(gosqlx,linter): report syntax errors instead of "no violations"
Oct 22, 2026
a774147
fix(formatter,gosqlx): keep comments and optimizer hints when formatting
Oct 22, 2026
956503a
fix(gosqlx): ParseReaderMultiple skips comment-only segments
Oct 22, 2026
145483e
fix(cli): exit non-zero on syntax errors and fix flag, dialect and I/…
Oct 22, 2026
dd9bf6a
docs: remove APIs and settings that do not exist; document CLI changes
Oct 22, 2026
d2c7f72
fix(parser,ast,formatter): CTE keyword names, SEARCH/CYCLE, PIVOT forms
Oct 22, 2026
989ae70
fix(ast,parser,formatter,transpiler): keep needed identifier quotes a…
Oct 22, 2026
4c1fffd
fix(gosqlx,lsp,cbinding): keep comments in dialect formatting; splitt…
Oct 22, 2026
f1b319c
fix(parser,ast): qualified positional columns, db..t, OPENJSON schema
Oct 22, 2026
759210e
perf(ast): keep TableReference compact by grouping rare modifiers
Oct 22, 2026
56868e6
fix(ast,parser,formatter): keep needed quotes on DDL object names
Oct 22, 2026
16804ff
fix(ast,parser,formatter): keep needed quotes on window names
Oct 22, 2026
af2f539
perf(parser,ast): keep name-quote recording off the hot path
Oct 22, 2026
283285e
fix(parser): keep ClickHouse projection/index columns and quoted lite…
Oct 22, 2026
15e13c6
fix(gosqlx,formatter): do not merge statements after a begin column; …
Oct 22, 2026
7cd6403
fix(tokenizer): decode ClickHouse \/ and \= escapes; refuse \e and \N
Oct 22, 2026
f9dd1a1
fix(parser,formatter): end ALTER TABLE at the next statement; allow-l…
Oct 22, 2026
0b788eb
fix(formatter,parser): keep ';' after verbatim statements; reject bar…
Oct 22, 2026
8863acd
fix(parser,ast): query tail after nested paren operand, looser left o…
Oct 22, 2026
30a04df
fix(parser,transform): keep cursor set operations whole, scope-aware …
Oct 22, 2026
9ec87a4
fix(transpiler): render output for the target dialect
Oct 22, 2026
2be588e
Merge branch 'fix/dialect-f-obj' into test/dialect-audit-2026-10
Oct 22, 2026
fb317db
Merge branch 'fix/dialect-d2' into test/dialect-audit-2026-10
Oct 22, 2026
085a948
Merge branch 'fix/dialect-g' into test/dialect-audit-2026-10
Oct 22, 2026
58738e6
Merge branch 'fix/dialect-f-col' into test/dialect-audit-2026-10
Oct 22, 2026
09da766
Merge branch 'fix/dialect-fu-quoting' into test/dialect-audit-2026-10
Oct 22, 2026
44e61dd
Merge branch 'fix/dialect-fu-strings' into test/dialect-audit-2026-10
Oct 22, 2026
89ef1bb
Merge branch 'fix/dialect-fu-batch' into test/dialect-audit-2026-10
Oct 22, 2026
059b52b
fix(transform): drop recorded quotes of names a table transform replaces
Oct 22, 2026
8494795
Merge branch 'fix/dialect-i' into test/dialect-audit-2026-10
Oct 22, 2026
6c81d7c
chore(lint): remove dead helpers left by the dialect merges; scope er…
Oct 22, 2026
82976e5
fix(ast,parser,formatter): AlterStatement.SQL, keep quote kinds in ty…
Oct 22, 2026
4fd2a9a
fix(formatter,parser,cli): dialect-aware Format, hoist WITH over set …
Oct 22, 2026
f237faa
perf(tokenizer,parser,gosqlx): tokenize internal parse paths into a r…
Oct 22, 2026
efeef37
fix(parser): statement boundaries in scripts without semicolons; whol…
Oct 22, 2026
aa8b1a8
fix(gosqlx): keep T-SQL "stmt; ELSE" in one SplitStatements piece; ch…
Oct 22, 2026
8cb9a41
test(realworld): T-SQL batches without semicolons and PL/SQL blocks
Oct 22, 2026
1717b3a
perf(ast)!: move the rare dialect SELECT clauses behind SelectStateme…
Oct 22, 2026
a5d6bea
fix(parser,ast): parse VALUES, ^, BINARY, PG prefix operators, string…
Oct 22, 2026
e459379
perf(tokenizer): build a compound keyword's joined Value only when ne…
Oct 22, 2026
2a9772a
fix(parser): bound wasted block scans; END/ABORT/RELEASE end a txn sc…
Oct 22, 2026
d6761db
perf(formatter,gosqlx): presize the SELECT render buffer; skip the op…
Oct 22, 2026
f6d8075
fix(parser,ast,formatter): close the real-world corpus failures (join…
Oct 22, 2026
8771e1e
perf(parser): keep the new syntax checks off the common parse path
Oct 22, 2026
a4a3d5c
Merge branch 'fix/dialect-w3-render' into test/dialect-audit-2026-10
Oct 22, 2026
b609a11
Merge branch 'fix/dialect-w3-boundaries' into test/dialect-audit-2026-10
Oct 22, 2026
78362f5
Merge branch 'fix/dialect-w3-perf' into test/dialect-audit-2026-10
Oct 22, 2026
9192563
fix(dialect): preserve GO batches and finish audit validation
Oct 22, 2026
0b5172b
chore(test): remove obsolete splitter tiling wrapper
Oct 22, 2026
73c0ae0
test(formatter): measure UNION allocation growth
Oct 22, 2026
0db001a
chore(sync): integrate main PIVOT updates
Oct 22, 2026
2acf38d
fix(render): keep PostgreSQL @ apart from its operand
Oct 25, 2026
0061884
fix(render): hoist WITH over nested parenthesized set operands
Oct 25, 2026
6025360
fix(formatter): keep nested optimizer hints with their SELECT
Oct 25, 2026
db952d2
fix(parser): treat a GO line as a hard batch boundary
Oct 25, 2026
c7587d7
fix(lsp): leave GO separators out of the document outline
Oct 25, 2026
c9fd57f
fix(analyze): exclude GO separators from statement counts
Oct 25, 2026
1c61555
fix(cli): re-enable the statement-count guard for SQL Server
Oct 25, 2026
25af930
fix(ast): release standalone VALUES and top-level set operations
Oct 25, 2026
5d60ce6
fix(transpiler): rewrite or refuse dialect-specific operators
Oct 25, 2026
861eb08
refactor(test): share the dropped-word detector
Oct 25, 2026
b14f14c
docs(changelog): note dialect audit review fixes
Oct 25, 2026
297b3c7
perf(parser): skip operator and clause probes at operand and clause ends
Oct 25, 2026
cd49a3c
perf(parser): screen dialect probes inline and parse DML targets in p…
Oct 25, 2026
3cf9323
perf(parser,ast): remove the dialect-audit parse regression
Oct 25, 2026
40af323
perf(parser): skip keyword probes for literals and trim DML statement…
Oct 25, 2026
8de2981
perf(parser): inline the function-call and DML clause screens
Oct 25, 2026
2a6b534
Merge origin/main into test/dialect-audit-2026-10
Oct 25, 2026
1cd9c76
perf(ast,parser): move UPDATE/DELETE dialect fields behind Extras
Oct 26, 2026
c8bd83b
Merge origin/test/dialect-audit-2026-10 (3cf93234) into the audit branch
Oct 26, 2026
3e964a2
test(realworld): add multi-dialect real-world corpus with ratchet
Oct 26, 2026
e3a7095
Merge origin/test/realworld-corpus into fix/dialect-audit-fixes
Oct 26, 2026
680f5a5
docs(changelog): describe the dialect fixes against main
Oct 26, 2026
5065e71
refactor(parser): leave the GO-inside-a-statement rule to its own PR
Oct 26, 2026
fb1ffcb
test(realworld): flag malformed known-failure entries and truncate by…
Oct 26, 2026
81eee98
fix(gosqlx): make SplitStatements linear on unterminated q-quotes and…
Oct 26, 2026
c6834da
fix(formatter): linear comment placement; attach comments by parser s…
Oct 26, 2026
32d7634
fix(ast): render MERGE source column aliases, walk DDL/TOP/ARRAY JOIN…
Oct 26, 2026
19b36aa
fix(parser): stop verbatim utility statements from swallowing the nex…
Oct 26, 2026
f08a4df
Merge branch 'fix/rv-splitter-dos' into fix/dialect-audit-fixes
Oct 26, 2026
0c0ad03
Merge branch 'fix/rv-fmt-linear' into fix/dialect-audit-fixes
Oct 26, 2026
8e3d9cd
Merge branch 'fix/rv-ast-render' into fix/dialect-audit-fixes
Oct 26, 2026
5dcaf7c
Merge branch 'fix/rv-parser-implicit-end' into fix/dialect-audit-fixes
Oct 26, 2026
11ec38c
Merge branch 'test/realworld-corpus' into fix/dialect-audit-fixes
Oct 26, 2026
29f8c3e
fix(cli): exclude GO separators from parse's statement count; documen…
Oct 26, 2026
d008b6f
Merge origin/main (#866) into fix/dialect-audit-fixes
Oct 26, 2026
9e927bc
test(gosqlx): check splitter scaling by ratio instead of wall-clock b…
Oct 26, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
4 changes: 4 additions & 0 deletions .golangci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ linters:
- fmt.Print
- fmt.Printf
- fmt.Println
# ast.textWriter is implemented only by *bytes.Buffer and
# *strings.Builder, whose writes never return an error.
- (github.com/ajitpratap0/GoSQLX/pkg/sql/ast.textWriter).WriteString
- (github.com/ajitpratap0/GoSQLX/pkg/sql/ast.textWriter).WriteByte
staticcheck:
checks:
- "all"
Expand Down
144 changes: 137 additions & 7 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]

### Added
- `ast.PivotClause.AggregateAlias`, `ast.PivotClause.ExtraAggregates`
(`[]ast.PivotAggregate`), `ast.UnpivotClause.NullHandling`,
`ast.UnpivotClause.ValueColumns` and `ast.UnpivotNullHandling`; rendered by
`SQL()` and `pkg/formatter`.
- **Real-world dialect corpus**: `testdata/realworld/<dialect>/` holds 2,788
production-style statements across PostgreSQL, MySQL, MariaDB, SQL Server,
Oracle, SQLite, Snowflake and ClickHouse. `TestRealWorldCorpus` runs each
Expand All @@ -17,8 +21,72 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
listed failure that now passes. `scripts/dialect-audit` runs the same checks
on any SQL file; `BenchmarkRealWorldParse` and `BenchmarkRealWorldParseFormat`
measure the corpus.
- **Dialect coverage**: every statement of the real-world corpus now parses,
formats, re-parses identically and keeps all its words; `known_failures.txt`
is empty (1,327 failures before).
- **AST representation**: `ExplainStatement` retains the explained statement;
`BatchSeparator` retains SQL Server `GO` and its optional `RepeatCount` without
expanding repeated batches. `SQL()` and `FormatAST` keep GO on its own line.
Callers enumerating `AST.Statements` or recovery results must account for
batch-separator nodes.
- **Literal and identifier metadata**: string literals retain their introducer
(`N`, `X`, `B`, `_utf8mb4`, ...) and escape rules (`LiteralValue.DialectExtras()`,
`EnsureExtras()`, `SetRawBackslash()`). `Identifier.QuoteStyle`,
`Identifier.Quotes` and the statement/table/column `NameQuote` fields retain
identifier delimiters through rendering.

### Changed
- **Dialect extension fields**: the new dialect-specific clauses live behind
optional pointers so that common statements do not carry them:
`SelectStatement.Extras` (`SelectExtras`: SELECT modifiers, INTO, LIMIT BY,
ROWS EXAMINED, INTERPOLATE, query hints, output formats),
`InsertStatement.Extras`, `UpdateStatement.Extras` / `DeleteStatement.Extras`
(`DMLTargetFields`: modifiers, TOP, ONLY, extra targets, joins, OUTPUT,
ORDER BY / LIMIT, RETURNING INTO), `FunctionCall.Extras`,
`BinaryExpression.Extras` and `TableReference.Modifiers` (including derived
`VALUES`). Read them through `DialectExtras()` / `DialectModifiers()`, which
return a shared read-only empty value when the pointer is nil, and call
`EnsureExtras()` / `EnsureModifiers()` before writing.
- **Transpilation**: string literals and quoted identifiers are rendered using
the target dialect's escape rules and delimiters. Unrepresentable literals
return an error rather than changing their value. SQL Server GO scripts
targeting another dialect return an error because batch scopes and repeat
counts cannot be represented there.
- **Transpiling dialect-specific operators**: `^` is exponentiation in
PostgreSQL and bitwise XOR in MySQL, MariaDB and SQL Server (recorded as
`BinaryExpression.BitwiseXor`). Exponentiation is written `POWER(a, b)`
outside PostgreSQL; XOR keeps `^` with explicit grouping between the MySQL
family and SQL Server, becomes `#` in PostgreSQL, `BITXOR()` in Snowflake and
`bitXor()` in ClickHouse, and returns an error for Oracle, SQLite and the
generic dialect. PostgreSQL `@`, `|/` and `||/` become `ABS()`, `SQRT()` and
`CBRT()` (an error where CBRT is missing); `!!`, SQL Server money literals
(`$5.00`, `$5`) and Snowflake object constants (`{'a': 1}`) return an error
in dialects without them.

### Fixed
- **AST walkers** (`ast.Inspect`, `Walk`, the security scanner, linter and
table extraction) reach SELECT `TOP` and ClickHouse `ARRAY JOIN`, the
ENGINE/ORDER BY/PARTITION BY/TTL/SETTINGS clauses of `CREATE TABLE` and
`CREATE MATERIALIZED VIEW`, Snowflake `CLONE ... AT`, and PostgreSQL
partition bounds. `AST.SQL()` no longer drops `CREATE`/`ALTER`/`DROP
SEQUENCE` statements, and MERGE keeps the source's column alias list
(`USING (VALUES ...) AS s (id, val)`).
- PostgreSQL `@` (absolute value) is always written as `@ x`; `@a`, `@t.a` and
`@abs(a)` re-parsed as placeholders. `@` directly followed by operator
characters (`@-@`, `@-5`) is now rejected, as PostgreSQL lexes it as one
operator.
- A WITH clause before nested parenthesized set operands
(`WITH c ... ((A UNION B) INTERSECT C) EXCEPT D`) stays in front of the whole
operation in `SQL()`, `FormatAST` and `Transpile`.
- Optimizer hints in subqueries stay with their SELECT when the renderer
reorders clauses (Oracle `START WITH` / `CONNECT BY`); when the hinted SELECT
cannot be identified, formatting fails with `ErrUnplaceableComment`.
- LSP document outline no longer lists GO separators, and statements before a
GO line no longer extend to the end of the file.
- `ReleaseAST` returns the pooled nodes under standalone `VALUES` statements and
top-level set operations.
- `gosqlx format --dialect sqlserver` runs the statement-count guard again,
counting GO lines and statements that end without `;`.
- **`ExtractColumns` / `ExtractColumnsQualified` no longer report SELECT aliases**
(#849): an unqualified name in `ORDER BY` or `HAVING` that matches a
SELECT-list alias of the same query block is treated as the alias, not a
Expand All @@ -44,14 +112,76 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
rendered. This preserves BigQuery backticks and Oracle alias case.
- Numeric UNPIVOT row aliases after `AS` are limited to BigQuery and Oracle;
Snowflake and SQL Server reject them.

### Added
- `ast.PivotClause.AggregateAlias`, `ast.PivotClause.ExtraAggregates`
(`[]ast.PivotAggregate`), `ast.UnpivotClause.NullHandling`,
`ast.UnpivotClause.ValueColumns` and `ast.UnpivotNullHandling`; rendered by
`SQL()` and `pkg/formatter`.
- **SQL round trips**: preserve ALTER actions, grouped joins, set-operation
query tails and CTE placement, standalone VALUES, JSON_TABLE/XMLTABLE literal
quoting, and nested optimizer hints. Hint placement uses the source dialect's
lexer so a keyword inside a string or quoted identifier is never a hint slot.
- **Pooled parsing**: internal tokenize/parse paths reuse scratch token storage
without retaining it in returned ASTs. Concurrent retained-tree regression
tests cover the public parse entry points; SELECT release clears all Extras.
- **`SplitStatements` keeps compound statements whole**: `BEGIN ... END` blocks
(trigger, procedure and function bodies, T-SQL `IF ... BEGIN`, `BEGIN TRY`,
PostgreSQL `BEGIN ATOMIC`), `CASE ... END`, `IF`/`LOOP`/`WHILE`/`REPEAT`
inside a block and Oracle `DECLARE`/`IS`/`AS` sections are no longer cut at
the first inner `;`. Transaction statements (`BEGIN;`, `BEGIN TRANSACTION`,
`BEGIN IMMEDIATE`) are not blocks, and a script whose blocks do not balance
falls back to plain `;` splitting. Client batch separators are honoured and
removed from the output: a `GO [n]` line for SQL Server, `DELIMITER` for
MySQL/MariaDB and a lone `/` line for Oracle.
- **`gosqlx.Lint` / `LintWithDialect` report invalid SQL**: SQL that does not
tokenize or parse returns an error wrapping `ErrTokenize` or `ErrSyntax`
together with the violations of the text and token rules. Callers that
ignored the error and treated an empty result as clean must now check it.
`linter.FileResult` gains `TokenizeError`, `ParseError` and `SyntaxError()`.
- **`gosqlx.Format` and `gosqlx format` keep comments and optimizer hints**:
comments stay with their statement (a comment inside a statement moves to the
line above it) and `/*+ ... */` hints directly after a statement's first
keyword stay in place. Input whose hints or MySQL `/*! ... */` comments
cannot be placed faithfully is refused (`formatter.ErrUnplaceableComment`)
instead of being rewritten without them, and `format -i` verifies that the
output holds exactly the input's comments. LSP and C-binding dialect
formatting no longer drop comments either. New `formatter.JoinWithComments`,
`gosqlx.FormatWithDialect` and `gosqlx.WithComments()`.
- **CLI**: `lint` exits 1 on syntax errors and `--auto-fix` judges the fixed
text; `format --no-uppercase` lower-cases clause keywords (it used to upper-case
them; operators such as `AND`, `IN`, `IS NULL`, `DESC` and function names
are still written in upper case); `postgres`, `pg`, `mssql`, `tsql` and `sqlite3` are accepted by every
`--dialect` flag; errors are printed once and usage only for flag and
argument errors; `validate --quiet` exits silently; `validate` names stdin
`<stdin>`, validates `GO`/`DELIMITER` scripts and files over the token
limit statement by statement; empty and comment-only input is valid for
`validate`, `lint` and `format`; `-o` works for `parse` and `transpile`,
`-f`/`-o` map onto `validate`'s output flags and are rejected where
unsupported; `format` keeps a leading byte order mark.
- **`ParseReaderMultiple`** skips segments that hold only comments.
- **Docs**: removed `parser.NewTokenConverter`, `SetMaxTokens`,
`SetMaxRecursionDepth` and the `parser.max_tokens` config key, none of which
exist; the limits are constants.

### Behavior changes
- A raw NUL or Ctrl-Z byte inside a string literal is an error in dialects
whose literals cannot spell it back (NUL without backslash escapes; both
without a dialect; Ctrl-Z in ClickHouse; both in Snowflake). Formatting used
to drop or rewrite it, changing the value.
- Statement counts (`gosqlx analyze`, `gosqlx parse`, the MCP `parse_sql`
tool and the C binding's `statement_count` / statement types) exclude GO
separators.
- `fingerprint.Normalize` and `Fingerprint` keep identifier quoting
(`SELECT "A" FROM "T"` used to normalize to `SELECT A FROM T`), so
fingerprints of queries with quoted identifiers differ from earlier
releases. Re-compute stored fingerprints after upgrading.
- Outside SQL Server, a utility statement (`SET`, `USE`, `GRANT`, `COPY`,
`CALL`, ...) followed on the next line by another statement without `;` is
an error; only a transaction statement on its own line is accepted without
a separator. Statements whose body may hold other statements (`CREATE
FUNCTION`/`TRIGGER`/`RULE`, `PREPARE ... AS`, `DECLARE ... CURSOR`) still
run to the next `;`, as does a second statement on the same line as a
`GRANT` or `COPY`.
- Transaction statements are checked against each dialect's grammar
(`BEGIN banana` is rejected); `<=>` is accepted only in MySQL, MariaDB and
PostgreSQL (pgvector), `==` only in SQLite and ClickHouse; an unbalanced
SQL Server `BEGIN ... END` block, or an `IF`/`WHILE` without a body, is an
error.
- PIVOT / UNPIVOT `IN` lists now require commas between entries and an alias
after `AS` in every PIVOT dialect. Where bare aliases are supported,
`IN (a b)` is one entry (`a AS b`); otherwise a missing comma is rejected.
Expand Down Expand Up @@ -2801,4 +2931,4 @@ For questions about upgrading or changelog entries:
[1.0.1]: https://github.com/ajitpratap0/GoSQLX/compare/v1.0.0...v1.0.1
[1.0.0]: https://github.com/ajitpratap0/GoSQLX/compare/v0.9.0...v1.0.0
[0.9.0]: https://github.com/ajitpratap0/GoSQLX/compare/v0.8.0...v0.9.0
[0.8.0]: https://github.com/ajitpratap0/GoSQLX/releases/tag/v0.8.0
[0.8.0]: https://github.com/ajitpratap0/GoSQLX/releases/tag/v0.8.0
2 changes: 1 addition & 1 deletion cmd/gosqlx/cmd/analyze.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ This is a basic implementation for CLI foundation.`,

func analyzeRun(cmd *cobra.Command, args []string) error {
// Reject unknown dialect names early before any parsing.
if err := validateDialectName(analyzeDialect); err != nil {
if err := normalizeDialectFlag(&analyzeDialect); err != nil {
return err
}

Expand Down
5 changes: 4 additions & 1 deletion cmd/gosqlx/cmd/analyzer.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,10 @@ func (a *Analyzer) Analyze(input string) (*AnalyzerResult, error) {
return result, result.Error
}

// Convert TokenWithSpan to Token using centralized converter
if !hasStatementTokens(tokens) {
result.Error = errNoStatements
return result, result.Error
}

// Parse with proper error handling for memory management
p := parserForDialect(a.Opts.Dialect)
Expand Down
35 changes: 35 additions & 0 deletions cmd/gosqlx/cmd/batch_count_validation_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// Copyright 2026 GoSQLX Authors
// SPDX-License-Identifier: Apache-2.0

package cmd

import (
"io"
"testing"
)

func TestValidatorRejectsInvalidGOBatchCount(t *testing.T) {
for _, count := range []string{"0", "999999999999999999999999999999", "1.5", "-1"} {
t.Run(count, func(t *testing.T) {
v := NewValidator(io.Discard, io.Discard, ValidatorOptions{Dialect: "sqlserver"})
src := "SELECT 1\nGO " + count + "\nSELECT 2"
result := v.ValidateInput("batch.sql", []byte(src))
if result.InvalidFiles != 1 || result.ValidFiles != 0 {
t.Fatalf("ValidateInput(%q): valid=%d invalid=%d; want invalid", src, result.ValidFiles, result.InvalidFiles)
}
})
}
}

func TestValidatorAcceptsValidGOBatchCount(t *testing.T) {
for _, directive := range []string{"GO", "GO 3", "GO 3 -- repeat", "GO 3 -- repeat\r"} {
t.Run(directive, func(t *testing.T) {
v := NewValidator(io.Discard, io.Discard, ValidatorOptions{Dialect: "sqlserver"})
src := "SELECT 1\r\n" + directive + "\nSELECT 2"
result := v.ValidateInput("batch.sql", []byte(src))
if result.ValidFiles != 1 || result.InvalidFiles != 0 {
t.Fatalf("ValidateInput(%q): valid=%d invalid=%d, error=%v; want valid", src, result.ValidFiles, result.InvalidFiles, result.Files[0].Error)
}
})
}
}
Loading
Loading