This matrix documents the comprehensive SQL feature support in GoSQLX across different SQL dialects and standards. The testing was conducted using the comprehensive integration test suite with 700+ test cases covering real-world SQL patterns.
The real-world corpus contains 2,813 production-style statements: SQL written
by hand to imitate what ORMs, migration tools and applications send, not SQL
collected from production systems. Every statement passes
split → dialect-aware parse → format → reparse → format idempotency, plus a
check for dropped SQL words. testdata/realworld/known_failures.txt lists no
failures.
These results describe this corpus; they do not establish complete dialect
coverage or database execution equivalence. Some routine and utility statements
retain their text rather than receiving a complete semantic AST.
Dialect
Passing statements
Known failures
Pass rate
PostgreSQL
305 / 305
0
100%
MySQL
414 / 414
0
100%
MariaDB
380 / 380
0
100%
SQL Server
277 / 277
0
100%
Oracle
368 / 368
0
100%
SQLite
348 / 348
0
100%
Snowflake
327 / 327
0
100%
ClickHouse
394 / 394
0
100%
The table totals 2,813 statements and was checked with scripts/dialect-audit
at commit fda9a5ef. Reproduce one dialect's row from the repository root
(replace <d> with a dialect string such as mysql); the last line of output
reads, for example:
go run ./scripts/dialect-audit -dialect <d> testdata/realworld/<d>/*.sql
The same command audits your own SQL files. To reproduce the ratchet and every
per-dialect summary at once:
go test ./pkg/gosqlx -run '^TestRealWorldCorpus$' -count=1for dir in testdata/realworld/*/; do go run ./scripts/dialect-audit -dialect "$(basename "$dir")" "$dir"*.sqldone
TestRealWorldCorpus is a ratchet against known_failures.txt: a statement
that fails and is not listed fails the test, and so does a listed statement
that now passes, so the list can only shrink. Regenerate it with
go test ./pkg/gosqlx -run TestRealWorldCorpus -update-known-failures.
BenchmarkRealWorldParse and BenchmarkRealWorldParseFormat measure the corpus.
SQL Server batch boundaries also have whole-script round-trip tests: the AST
retains each GO as a BatchSeparator, including its optional repeat count,
and both library and CLI rendering keep it on a separate line. The splitter
removes client directives when returning individual SQL statements, so the
statement-by-statement corpus totals above do not count GO directives.
LATERAL JOIN - Correlated subqueries and table functions in FROM/JOIN clauses; select the PostgreSQL dialect for table functions
Table Function Aliases - Column alias lists and WITH ORDINALITY on FROM/JOIN table functions: unnest(arr) AS g(elem), generate_series(1, 3) WITH ORDINALITY AS s(n, idx). WITH ORDINALITY is PostgreSQL-only.
Composite Field Access - (get_item(payload)).name, (payload).*, quoted fields and chained field/subscript access such as (payload).items[1].name and (items)[1].name. Any keyword, reserved ones included, is accepted as a field name, e.g. (payload).order. Field access parses for PostgreSQL, BigQuery, the generic dialect and the default (no dialect) parser; MySQL, MariaDB, SQL Server, SQLite, Oracle, Snowflake, ClickHouse and Redshift reject it with E4001.
Set operation / WITH / double-parenthesized query as derived table
✅
✅
✅
✅
✅
✅ Full
90%
Parenthesized operands such as (SELECT ... ORDER BY a LIMIT 5) UNION ALL (SELECT ...) and
SELECT ... EXCEPT (SELECT ... UNION SELECT ...) keep their parentheses when rendered. An
ORDER BY/LIMIT written after a parenthesized query is accepted when it can be represented
without changing meaning (for example (SELECT 1) ORDER BY 1); a second ORDER BY/LIMIT
level over a parenthesized query that already has one, such as
(SELECT a FROM t ORDER BY a LIMIT 10) ORDER BY b, returns E4001 (unsupported feature).
Note: This section documents dialect-specific features where "Support Level" refers to the native database's support, while "GoSQLX Parser" and "Test Coverage" indicate GoSQLX implementation status. Many features listed have keyword recognition only (🔧 Syntax) without full parsing logic.
In MySQL and MariaDB || is a synonym for OR unless the server runs with the
PIPES_AS_CONCAT SQL mode. GoSQLX follows the server default:
With the mysql or mariadb dialect, a = 1 || b parses as (a = 1) OR b,
with OR precedence (looser than AND, NOT, comparisons and arithmetic).
The AST is the same as for the OR keyword
(BinaryExpression{Operator: "OR"}), so linter rules, the security scanner
and other analysis see an OR. The node also sets PipesOr: true, and
SQL(), the formatter, the CLI and LSP formatting write || back, so
formatting never changes the meaning for a server that runs with
PIPES_AS_CONCAT.
To mirror PIPES_AS_CONCAT, pass parser.WithPipesAsConcat() (or
gosqlx.WithPipesAsConcat() with gosqlx.ParseTree); || is then string
concatenation. The CLI, LSP and linter configuration do not expose this
option yet, so with --dialect mysql they analyse || as OR (for example
OR-based lint rules may report a concatenation).
Precedence in the opt-in mode differs from MySQL: MySQL gives || under
PIPES_AS_CONCAT a precedence between ^ and the unary operators, tighter
than *, /, + and -. GoSQLX places concatenation just below + and
-, so a + b || c parses as (a + b) || c, while MySQL reads
a + (b || c). Parenthesise mixed arithmetic and concatenation.
Every other dialect (PostgreSQL, SQL Server, Oracle, SQLite, Snowflake,
ClickHouse, and no dialect) always parses || as concatenation.
Transpiling from any of those dialects to MySQL or MariaDB rewrites a || b
to CONCAT(a, b). Transpiling from MySQL or MariaDB to any of them writes a
|| OR as OR; between MySQL and MariaDB it stays ||.
// MySQL default: || is logical ORp := parser.NewParser(parser.WithDialect("mysql"))// MySQL with PIPES_AS_CONCAT: || is concatenationp2 := parser.NewParser(parser.WithDialect("mysql"), parser.WithPipesAsConcat())tree, err := gosqlx.ParseTree(ctx, sql, gosqlx.WithDialect("mysql"), gosqlx.WithPipesAsConcat())
Before v1.16.0 (#781), || was concatenation in every dialect, including MySQL and MariaDB.
MariaDB inherits all MySQL features (SHOW, DESCRIBE, REPLACE INTO, ON DUPLICATE KEY UPDATE, GROUP_CONCAT, MATCH/AGAINST, REGEXP/RLIKE, backtick identifiers, etc.) and adds the following extensions:
Parsed and rendered since v1.14.0 (FROM s PIVOT (...) AS p). Also parsed for Oracle, Snowflake and BigQuery: PIVOT/UNPIVOT is gated on the dialect capability SupportsPivotUnpivot
START WITH, CONNECT BY [NOCYCLE] with general expressions
PRIOR operator
✅ Full
✅ Full
—
Prefix operator (CONNECT BY PRIOR id = parent_id + 1); a column named prior stays an identifier
DECODE function
✅ Full
⚠️ Partial
40%
Recognized as function, no special handling
NVL/NVL2
✅ Full
⚠️ Partial
40%
Recognized as function, no special handling
Dual table
✅ Full
✅ Full
100%
Standard table reference
Bind variables (:1, :name)
✅ Full
✅ Full
90%
Positional and named binds tokenized as placeholders (dialect-gated). In Oracle mode : followed by a digit or identifier is always a bind, so array slices such as arr[1:2] and arr[1:n] do not parse (expected ], got PLACEHOLDER); Oracle has no array slices, and arr[:2] still parses
These forms are available in v1.17.0. Each example was parsed with
gosqlx.ParseWithDialect and formatted with gosqlx.FormatWithDialect in the
named dialect.
Syntax
Dialects
GoSQLX Parser
Notes
Multi-table UPDATE
MySQL, MariaDB
✅ Full
UPDATE a JOIN b ON a.id = b.id SET a.x = b.x; joins and extra targets live in UpdateStatement.Extras
Multi-table DELETE
MySQL, MariaDB
✅ Full
DELETE a, b FROM a JOIN b ON a.id = b.id WHERE ...; DeleteStatement.Extras
UPDATE / DELETE TOP
SQL Server
✅ Full
UPDATE TOP (10) t SET ..., DELETE TOP (5) FROM t ...
OUTPUT clause
SQL Server
✅ Full
UPDATE ... OUTPUT inserted.id WHERE ..., DELETE ... OUTPUT deleted.id
GO [n] batch separator
SQL Server
✅ Full
Kept as ast.BatchSeparator with its optional RepeatCount; repeated batches are not expanded. SQL() and FormatAST keep GO on its own line. Statement counts reported by the CLI, the MCP server and the C binding exclude GO, but AST.Statements contains a BatchSeparator node for each GO. A GO line inside a statement is an error. Transpiling a GO script to another dialect returns an error
RETURNING ... INTO
Oracle
✅ Full
UPDATE t SET x = 1 WHERE id = 2 RETURNING x INTO :v
ARRAY JOIN / LIMIT BY
ClickHouse
✅ Full
SELECT a FROM t ARRAY JOIN arr AS x LIMIT 1 BY a. Parsed since earlier releases; AST walkers (ast.Inspect, Walk, security scanner, linter, table extraction) now reach ARRAY JOIN
MERGE with a VALUES source
PostgreSQL, SQL Server
✅ Full
MERGE INTO t USING (VALUES (1, 'a')) AS s (id, val) ON ...; the source's column alias list is kept when rendering
CREATE TABLE ... CLONE ... AT
Snowflake
✅ Full
CREATE TABLE t2 CLONE t1 AT (TIMESTAMP => ...); walkers now reach the clone clause
CREATE / ALTER / DROP SEQUENCE
PostgreSQL, Oracle, Snowflake
⚠️ Partial
Parsed and kept as statement text (UtilityStatement); AST.SQL() no longer drops them. MariaDB has a full CreateSequenceStatement (see MariaDB-Specific Features)
EXPLAIN
PostgreSQL, MySQL, SQLite
✅ Full
ExplainStatement now retains the explained statement as an AST (EXPLAIN ANALYZE ..., SQLite EXPLAIN QUERY PLAN ...)
SQL*Plus / line
Oracle
✅ Full
A line holding only / ends a statement in the parser and in SplitStatements; / with other SQL on the line is still division. Known gap: formatting drops the / lines
<=> operator
MySQL, MariaDB, PostgreSQL
✅ Full
Null-safe equality (MySQL family) and pgvector distance (PostgreSQL). Accepted only in these dialects and rejected in the others; v1.16.0 accepted <=> in no dialect
== operator
SQLite, ClickHouse
✅ Full
Accepted only in these dialects and rejected in the others; v1.16.0 accepted == in no dialect
Related grammar checks: outside SQL Server, a utility statement
(SET, USE, GRANT, COPY, CALL, ...) followed on the next line by
another statement without ; is an error (E2002); transaction statements are
checked against each dialect's grammar (BEGIN banana is rejected); an
unbalanced SQL Server BEGIN ... END block is an error.
-- Extract field as JSON (->)SELECT data->'user' FROM events;-- Extract field as text (->>)SELECT data->>'email' FROM users;-- Extract nested path as JSON (#>)SELECT data#>'{user,address,city}' FROM profiles;-- Extract nested path as text (#>>)SELECT data#>>'{user,name}' FROM profiles;-- Contains (@>)SELECT * FROM products WHERE attributes @> '{"color": "red"}';-- Contained by (<@)SELECT * FROM users WHERE tags <@ '["admin", "user"]';-- Key exists (?)SELECT * FROM profiles WHERE data ? 'email';-- Any key exists (?|)SELECT * FROM users WHERE profile ?| array['phone', 'mobile'];-- All keys exist (?&)SELECT * FROM users WHERE profile ?& array['name', 'email'];-- Delete path (#-)SELECT data #- '{user,temp}' FROM cache;
-- Correlated subquery in FROM clauseSELECT u.name, recent.order_dateFROM users u,LATERAL ( SELECT order_date FROM orders WHERE user_id = u.id ORDER BY order_date DESC LIMIT 3) recent;-- LEFT LATERAL JOINSELECT u.name, stats.totalFROM users uLEFT JOIN LATERAL ( SELECT SUM(amount) as total FROM transactions WHERE user_id = u.id) stats ON true;
-- Get first row per departmentSELECT DISTINCT ON (dept_id) dept_id, name, salaryFROM employeesORDER BY dept_id, salary DESC;-- Latest status per userSELECT DISTINCT ON (user_id) user_id, status, updated_atFROM user_status_logORDER BY user_id, updated_at DESC;
GoSQLX provides a first-class dialect mode engine that threads the SQL dialect through the tokenizer and parser. This enables dialect-specific keyword recognition, syntax parsing, and validation.
PREWHERE, FINAL, GLOBAL IN/NOT IN, nested types, parametric aggregates, bare-bracket arrays, ORDER BY WITH FILL, CODEC, WITH TOTALS, LIMIT BY, ANY/ALL JOIN, SETTINGS, TTL, INSERT FORMAT
✅ 83/83 QA pass (was 69/83 in v1.14.0)
MariaDB
"mariadb"
MariaDB keywords (superset of MySQL)
All MySQL features + SEQUENCE DDL, FOR SYSTEM_TIME, WITH SYSTEM VERSIONING, PERIOD FOR, CONNECT BY
✅
BigQuery
"bigquery"
BigQuery keywords
Composite field access (expr).field, table functions, QUALIFY, PIVOT/UNPIVOT
⚠️ Keywords + basic parsing
Redshift
"redshift"
Redshift keywords
Keyword recognition
⚠️ Keywords + basic parsing
Generic
"generic"
Standard SQL keywords
No dialect-specific syntax; composite field access allowed
83/83 QA corpus pass (100%). It closed the remaining QA gaps: ARRAY JOIN / LEFT ARRAY JOIN, scalar CTEs (WITH <expr> AS <name>), CREATE MATERIALIZED VIEW with ENGINE/TO, CREATE TABLE ... AS <source> ENGINE = ..., numeric SAMPLE arguments with OFFSET, and LIMIT offset, count
PREWHERE clause for pre-filter optimization before primary key scan
FINAL modifier on table references (forces MergeTree part merge)
GLOBAL IN / GLOBAL NOT IN for distributed query execution
PIVOT / UNPIVOT (#854): aggregate aliases and multiple aggregates (PIVOT (SUM(s) AS total, COUNT(*) AS n FOR q IN ('Q1' AS q1, 'Q2' q2))), UNPIVOT INCLUDE NULLS / EXCLUDE NULLS, row-value aliases with or without AS (string or INT64), and multi-column UNPIVOT (UNPIVOT ((a, b) FOR k IN ((c1, c2) AS 'x', (c3, c4) AS 'y')))
PIVOT IN values must be literals or names; constant expressions such as IN (1 + 1) or IN (DATE '2024-01-01') are not parsed
QUALIFY, UNNEST in FROM and (struct).field access are capability-gated; BigQuery has no dedicated keyword set
Quoted PIVOT aggregate aliases and UNPIVOT value-column names retain their source delimiters, including BigQuery backticks and Oracle double quotes
Oracle also accepts aggregate aliases, multiple aggregates, INCLUDE/EXCLUDE NULLS and multi-column UNPIVOT; Snowflake accepts INCLUDE/EXCLUDE NULLS. SQL Server keeps the single-aggregate form without aliases or a NULLS modifier. Oracle's tuple row-value aliases ((c1, c2) AS ('a', 'b')) are not parsed.