Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
744ed99
fix(playground): move useState/useCallback above early returns to fix…
Mar 22, 2026
c5063a8
feat(dialect): add DialectMariaDB constant to keyword dialect system
Mar 22, 2026
2e65aad
fix(dialect): return MYSQL_SPECIFIC for DialectMariaDB and add to val…
Mar 22, 2026
28cd2e3
fix(dialect): wire DialectMariaDB into keywords.New() to load MYSQL_S…
Mar 22, 2026
870a783
test(dialect): add TestDialectMariaDB_InheritsMySQL to guard MySQL ke…
Mar 22, 2026
c9cc078
feat(dialect): add MARIADB_SPECIFIC keyword list extending MySQL dialect
Mar 22, 2026
b488bcf
feat(dialect): add MariaDB auto-detection hints (SEQUENCE, VERSIONING…
Mar 22, 2026
0d75548
fix(dialect): remove over-broad START WITH hint and complete DetectDi…
Mar 22, 2026
c0231ce
fix(dialect): restore MariaDB CONNECT BY hint and add accumulation test
Mar 22, 2026
4e9637d
feat(ast): add CreateSequenceStatement, DropSequenceStatement, AlterS…
Mar 22, 2026
ec6c3b4
fix(ast): nil guard in sequence ToSQL, split Restart field, add seque…
Mar 22, 2026
9f1ee1c
feat(ast): add ForSystemTimeClause, PeriodDefinition, temporal table …
Mar 22, 2026
6957c80
fix(ast): add SQL() methods to temporal/CONNECT BY types and fix Sele…
Mar 22, 2026
8dab637
feat(parser): implement CREATE/DROP/ALTER SEQUENCE parsing for MariaD…
Mar 22, 2026
ddbacba
feat(parser): implement temporal table parsing (FOR SYSTEM_TIME, WITH…
Mar 22, 2026
dded46c
feat(parser): implement CONNECT BY hierarchical query parsing for Mar…
Mar 22, 2026
376c8bd
fix(parser): add bare RESTART test and nil guard for CONNECT BY condi…
Mar 22, 2026
c553acd
test(parser): add MariaDB SQL test data files and file-based integrat…
Mar 23, 2026
256d1bf
docs: add MariaDB dialect to SQL_COMPATIBILITY.md and CHANGELOG.md
Mar 23, 2026
f003e2b
fix(mariadb): address code review issues across AST, keywords, and pa…
Mar 23, 2026
a9a51be
fix(mariadb): address second code review pass — Pos, NO CACHE, CONNEC…
Mar 23, 2026
4476749
Merge branch 'main' into feat/mariadb-dialect
ajitpratap0 Mar 23, 2026
c80cffe
fix(mariadb): correct START WITH/CONNECT BY SQL order and implement P…
Mar 23, 2026
4f5d0fb
fix(mariadb): code review fixes — pool comments, expressionNode docs,…
Mar 23, 2026
64bf553
fix(dialect): reduce MariaDB CONNECT BY hint weight to 2 so Oracle wi…
Mar 23, 2026
751e859
fix(mariadb): address code review — CycleOption enum, CACHE validatio…
Mar 23, 2026
32d42de
bench(mariadb): add MariaDB-specific parsing benchmarks
Mar 24, 2026
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
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Added
- **MariaDB dialect** (`--dialect mariadb`): New SQL dialect extending MySQL with support for SEQUENCE DDL (`CREATE/DROP/ALTER SEQUENCE` with full option set), temporal tables (`FOR SYSTEM_TIME`, `WITH SYSTEM VERSIONING`, `PERIOD FOR`), and `CONNECT BY` hierarchical queries with `PRIOR`, `START WITH`, and `NOCYCLE`

## [1.13.0] - 2026-03-20

### Added
Expand Down
29 changes: 29 additions & 0 deletions docs/SQL_COMPATIBILITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -317,6 +317,27 @@ This matrix documents the comprehensive SQL feature support in GoSQLX across dif
| **AUTO_INCREMENT** | ✅ Full | ✅ Full | 95% | Column property |
| **Backtick identifiers** | ✅ Full | ✅ Full | 100% | `` `table`.`column` `` syntax |

### MariaDB-Specific Features (v1.14.0+)

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:

| Feature | Support Level | GoSQLX Parser | Test Coverage | Notes |
|---------|---------------|---------------|---------------|-------|
| **CREATE SEQUENCE** | ✅ Full | ✅ Full | 95% | Full DDL with all sequence options |
| **DROP SEQUENCE** | ✅ Full | ✅ Full | 95% | DROP SEQUENCE [IF EXISTS] |
| **ALTER SEQUENCE** | ✅ Full | ✅ Full | 90% | RESTART, RESTART WITH, and all options |
| **Sequence options** | ✅ Full | ✅ Full | 95% | START WITH, INCREMENT BY, MINVALUE, MAXVALUE, CACHE, CYCLE, NOCACHE, NOCYCLE, RESTART, RESTART WITH |
| **FOR SYSTEM_TIME AS OF** | ✅ Full | ✅ Full | 95% | Point-in-time query on system-versioned tables |
| **FOR SYSTEM_TIME BETWEEN** | ✅ Full | ✅ Full | 95% | Range query on system-versioned tables |
| **FOR SYSTEM_TIME FROM/TO** | ✅ Full | ✅ Full | 95% | Range query (inclusive/exclusive) |
| **FOR SYSTEM_TIME ALL** | ✅ Full | ✅ Full | 95% | All rows including historical |
| **WITH SYSTEM VERSIONING** | ✅ Full | ✅ Full | 90% | CREATE TABLE ... WITH SYSTEM VERSIONING |
| **PERIOD FOR** | ✅ Full | ✅ Full | 85% | Application-time period definitions |
| **CONNECT BY** | ✅ Full | ✅ Full | 90% | Hierarchical queries with PRIOR and NOCYCLE |
| **START WITH (CONNECT BY)** | ✅ Full | ✅ Full | 90% | Root condition for hierarchical traversal |
| **PRIOR operator** | ✅ Full | ✅ Full | 90% | Reference parent row in CONNECT BY |
| **NOCYCLE** | ✅ Full | ✅ Full | 85% | Prevent infinite loops in cyclic graphs |

### SQL Server-Specific Features

| Feature | Support Level | GoSQLX Parser | Test Coverage | Notes |
Expand Down Expand Up @@ -549,6 +570,7 @@ GoSQLX v1.8.0 introduces a first-class dialect mode engine that threads the SQL
| **SQLite** | `"sqlite"` | SQLite keywords | Flexible typing, simplified syntax | ⚠️ Keywords + basic parsing |
| **Snowflake** | `"snowflake"` | Snowflake keywords | Stage operations, VARIANT type | ⚠️ Keyword detection only |
| **ClickHouse** | `"clickhouse"` | ClickHouse keywords | PREWHERE, FINAL, GLOBAL IN/NOT IN, MergeTree keywords | ✅ v1.13.0 |
| **MariaDB** | `"mariadb"` | MariaDB keywords (superset of MySQL) | All MySQL features + SEQUENCE DDL, FOR SYSTEM_TIME, WITH SYSTEM VERSIONING, PERIOD FOR, CONNECT BY | ✅ v1.14.0 |

### Usage

Expand Down Expand Up @@ -597,6 +619,12 @@ gosqlx format --dialect mysql query.sql
- No Snowflake-specific parsing (stages, COPY INTO, VARIANT operations)
- QUALIFY clause not supported

#### MariaDB
- Inherits all MySQL known gaps (stored procedures, HANDLER, XA transactions, CREATE EVENT)
- JSON_TABLE not supported
- Spider storage engine syntax not parsed
- ColumnStore-specific syntax not supported

#### ClickHouse
- PREWHERE clause for pre-filter optimization before primary key scan
- FINAL modifier on table references (forces MergeTree part merge)
Expand Down Expand Up @@ -644,6 +672,7 @@ gosqlx format --dialect mysql query.sql
| **SQL Server** | 85% | 65% | ⭐⭐⭐⭐ Very Good | Keywords + MERGE |
| **Oracle** | 80% | 60% | ⭐⭐⭐⭐ Good | Keywords + basic features |
| **SQLite** | 85% | 50% | ⭐⭐⭐⭐ Good | Keywords + basic features |
| **MariaDB** | 95% | 90% | ⭐⭐⭐⭐⭐ Excellent | MySQL superset + SEQUENCE DDL, temporal tables, CONNECT BY (v1.14.0) |
| **Snowflake** | 80% | 30% | ⭐⭐⭐ Good | Keyword detection only |

## Performance Characteristics by Feature
Expand Down
221 changes: 214 additions & 7 deletions pkg/sql/ast/ast.go
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,9 @@ type TableReference struct {
Lateral bool // LATERAL keyword for correlated subqueries (PostgreSQL)
TableHints []string // SQL Server table hints: WITH (NOLOCK), WITH (ROWLOCK, UPDLOCK), etc.
Final bool // ClickHouse FINAL modifier: forces MergeTree part merge
// ForSystemTime is the MariaDB temporal table clause (10.3.4+).
// Example: SELECT * FROM t FOR SYSTEM_TIME AS OF '2024-01-01'
ForSystemTime *ForSystemTimeClause // MariaDB temporal query
}

func (t *TableReference) statementNode() {}
Expand Down Expand Up @@ -397,13 +400,19 @@ type SelectStatement struct {
Where Expression
GroupBy []Expression
Having Expression
Windows []WindowSpec
OrderBy []OrderByExpression
Limit *int
Offset *int
Fetch *FetchClause // SQL-99 FETCH FIRST/NEXT clause (F861, F862)
For *ForClause // Row-level locking clause (SQL:2003, PostgreSQL, MySQL)
Pos models.Location // Source position of the SELECT keyword (1-based line and column)
// StartWith is the optional seed condition for CONNECT BY (MariaDB 10.2+).
// Example: START WITH parent_id IS NULL
StartWith Expression // MariaDB hierarchical query seed
// ConnectBy holds the hierarchy traversal condition (MariaDB 10.2+).
// Example: CONNECT BY PRIOR id = parent_id
ConnectBy *ConnectByClause // MariaDB hierarchical query
Windows []WindowSpec
OrderBy []OrderByExpression
Limit *int
Offset *int
Fetch *FetchClause // SQL-99 FETCH FIRST/NEXT clause (F861, F862)
For *ForClause // Row-level locking clause (SQL:2003, PostgreSQL, MySQL)
Pos models.Location // Source position of the SELECT keyword (1-based line and column)
}

// TopClause represents SQL Server's TOP N [PERCENT] clause
Expand Down Expand Up @@ -518,6 +527,12 @@ func (s SelectStatement) Children() []Node {
if s.For != nil {
children = append(children, s.For)
}
if s.StartWith != nil {
children = append(children, s.StartWith)
}
if s.ConnectBy != nil {
children = append(children, s.ConnectBy)
}
return children
}

Expand Down Expand Up @@ -1275,6 +1290,14 @@ type CreateTableStatement struct {
Partitions []PartitionDefinition // Individual partition definitions
Options []TableOption
WithoutRowID bool // SQLite: CREATE TABLE ... WITHOUT ROWID

// WithSystemVersioning enables system-versioned temporal history (MariaDB 10.3.4+).
// Example: CREATE TABLE t (...) WITH SYSTEM VERSIONING
WithSystemVersioning bool

// PeriodDefinitions holds PERIOD FOR clauses for application-time or system-time periods.
// Example: PERIOD FOR app_time (start_col, end_col)
PeriodDefinitions []*PeriodDefinition
}

func (c *CreateTableStatement) statementNode() {}
Expand Down Expand Up @@ -1815,3 +1838,187 @@ func (r ReplaceStatement) Children() []Node {
}
return children
}

// ── MariaDB SEQUENCE DDL (10.3+) ───────────────────────────────────────────

// CycleOption represents the CYCLE behavior for a sequence.
type CycleOption int

const (
// CycleUnspecified means no CYCLE or NOCYCLE clause was given (database default applies).
CycleUnspecified CycleOption = iota
// CycleBehavior means CYCLE — sequence wraps around when it reaches min/max.
CycleBehavior
// NoCycleBehavior means NOCYCLE / NO CYCLE — sequence errors on overflow.
NoCycleBehavior
)

// SequenceOptions holds configuration for CREATE SEQUENCE and ALTER SEQUENCE.
// Fields are pointers so that unspecified options are distinguishable from zero values.
type SequenceOptions struct {
StartWith *LiteralValue // START WITH n
IncrementBy *LiteralValue // INCREMENT BY n (default 1)
MinValue *LiteralValue // MINVALUE n or nil when NO MINVALUE
MaxValue *LiteralValue // MAXVALUE n or nil when NO MAXVALUE
Cache *LiteralValue // CACHE n or nil when NO CACHE / NOCACHE
CycleMode CycleOption // CYCLE / NOCYCLE / NO CYCLE (CycleUnspecified if not specified)
NoCache bool // NOCACHE (explicit; Cache=nil alone is ambiguous)
Restart bool // bare RESTART (reset to start value)
RestartWith *LiteralValue // RESTART WITH n (explicit restart value)
}

// CreateSequenceStatement represents:
//
// CREATE [OR REPLACE] SEQUENCE [IF NOT EXISTS] name [options...]
type CreateSequenceStatement struct {
Name *Identifier
OrReplace bool
IfNotExists bool
Options SequenceOptions
Pos models.Location // Source position of the CREATE keyword (1-based line and column)
}

func (s *CreateSequenceStatement) statementNode() {}
func (s *CreateSequenceStatement) TokenLiteral() string { return "CREATE" }
func (s *CreateSequenceStatement) Children() []Node {
if s.Name != nil {
return []Node{s.Name}
}
return nil
}

// DropSequenceStatement represents:
//
// DROP SEQUENCE [IF EXISTS | IF NOT EXISTS] name
type DropSequenceStatement struct {
Name *Identifier
IfExists bool
Pos models.Location // Source position of the DROP keyword (1-based line and column)
}

func (s *DropSequenceStatement) statementNode() {}
func (s *DropSequenceStatement) TokenLiteral() string { return "DROP" }
func (s *DropSequenceStatement) Children() []Node {
if s.Name != nil {
return []Node{s.Name}
}
return nil
}

// AlterSequenceStatement represents:
//
// ALTER SEQUENCE [IF EXISTS] name [options...]
type AlterSequenceStatement struct {
Name *Identifier
IfExists bool
Options SequenceOptions
Pos models.Location // Source position of the ALTER keyword (1-based line and column)
}

func (s *AlterSequenceStatement) statementNode() {}
func (s *AlterSequenceStatement) TokenLiteral() string { return "ALTER" }
func (s *AlterSequenceStatement) Children() []Node {
if s.Name != nil {
return []Node{s.Name}
}
return nil
}

// ── MariaDB Temporal Table Types (10.3.4+) ────────────────────────────────

// SystemTimeClauseType identifies the kind of FOR SYSTEM_TIME clause.
type SystemTimeClauseType int

const (
SystemTimeAsOf SystemTimeClauseType = iota // FOR SYSTEM_TIME AS OF <point>
SystemTimeBetween // FOR SYSTEM_TIME BETWEEN <start> AND <end>
SystemTimeFromTo // FOR SYSTEM_TIME FROM <start> TO <end>
SystemTimeAll // FOR SYSTEM_TIME ALL
)

// ForSystemTimeClause represents a temporal query on a system-versioned table.
//
// SELECT * FROM t FOR SYSTEM_TIME AS OF TIMESTAMP '2024-01-01';
// SELECT * FROM t FOR SYSTEM_TIME BETWEEN '2020-01-01' AND '2024-01-01';
// SELECT * FROM t FOR SYSTEM_TIME ALL;
type ForSystemTimeClause struct {
Type SystemTimeClauseType
Point Expression // used for AS OF
Start Expression // used for BETWEEN, FROM
End Expression // used for BETWEEN (AND), TO
Pos models.Location // Source position of the FOR keyword (1-based line and column)
}

// expressionNode satisfies the Expression interface so ForSystemTimeClause can be
// stored in TableReference.ForSystemTime without a separate interface type.
// Semantically it is a table-level clause, not a scalar expression.
func (c *ForSystemTimeClause) expressionNode() {}
func (c ForSystemTimeClause) TokenLiteral() string { return "FOR SYSTEM_TIME" }
func (c ForSystemTimeClause) Children() []Node {
var nodes []Node
if c.Point != nil {
nodes = append(nodes, c.Point)
}
if c.Start != nil {
nodes = append(nodes, c.Start)
}
if c.End != nil {
nodes = append(nodes, c.End)
}
return nodes
}

// PeriodDefinition represents a PERIOD FOR clause in CREATE TABLE.
//
// PERIOD FOR app_time (start_col, end_col)
// PERIOD FOR SYSTEM_TIME (row_start, row_end)
type PeriodDefinition struct {
Name *Identifier // period name (e.g., "app_time") or SYSTEM_TIME
StartCol *Identifier
EndCol *Identifier
Pos models.Location // Source position of the PERIOD FOR keyword (1-based line and column)
}

// expressionNode satisfies the Expression interface so PeriodDefinition can be
// stored in CreateTableStatement.PeriodDefinitions without a separate interface type.
// Semantically it is a table column constraint, not a scalar expression.
func (p *PeriodDefinition) expressionNode() {}
func (p PeriodDefinition) TokenLiteral() string { return "PERIOD FOR" }
func (p PeriodDefinition) Children() []Node {
var nodes []Node
if p.Name != nil {
nodes = append(nodes, p.Name)
}
if p.StartCol != nil {
nodes = append(nodes, p.StartCol)
}
if p.EndCol != nil {
nodes = append(nodes, p.EndCol)
}
return nodes
}

// ── MariaDB Hierarchical Query / CONNECT BY (10.2+) ───────────────────────

// ConnectByClause represents the CONNECT BY hierarchical query clause (MariaDB 10.2+).
//
// SELECT id, name FROM t
// START WITH parent_id IS NULL
// CONNECT BY NOCYCLE PRIOR id = parent_id;
type ConnectByClause struct {
NoCycle bool // NOCYCLE modifier — prevents loops in cyclic graphs
Condition Expression // the PRIOR expression (e.g., PRIOR id = parent_id)
Pos models.Location // Source position of the CONNECT BY keyword (1-based line and column)
}

// expressionNode satisfies the Expression interface so ConnectByClause can be
// stored in SelectStatement.ConnectBy without a separate interface type.
// Semantically it is a query-level clause, not a scalar expression.
func (c *ConnectByClause) expressionNode() {}
func (c ConnectByClause) TokenLiteral() string { return "CONNECT BY" }
func (c ConnectByClause) Children() []Node {
if c.Condition != nil {
return []Node{c.Condition}
}
return nil
}
Loading
Loading