Skip to content

Commit 042e803

Browse files
authored
Fix DETACH/ATTACH DATABASE, EXCHANGE TABLES, and SHOW CREATE FORMAT parsing (#104)
1 parent 0beceeb commit 042e803

File tree

164 files changed

+249
-1113
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

164 files changed

+249
-1113
lines changed

ast/ast.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -656,6 +656,7 @@ type ShowQuery struct {
656656
Like string `json:"like,omitempty"`
657657
Where Expression `json:"where,omitempty"`
658658
Limit Expression `json:"limit,omitempty"`
659+
Format string `json:"format,omitempty"`
659660
}
660661

661662
func (s *ShowQuery) Pos() token.Position { return s.Position }
@@ -780,7 +781,9 @@ func (r *RenameQuery) statementNode() {}
780781
// ExchangeQuery represents an EXCHANGE TABLES statement.
781782
type ExchangeQuery struct {
782783
Position token.Position `json:"-"`
784+
Database1 string `json:"database1,omitempty"`
783785
Table1 string `json:"table1"`
786+
Database2 string `json:"database2,omitempty"`
784787
Table2 string `json:"table2"`
785788
OnCluster string `json:"on_cluster,omitempty"`
786789
}

internal/explain/explain.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,8 @@ func Node(sb *strings.Builder, node interface{}, depth int) {
115115
explainDropQuery(sb, n, indent, depth)
116116
case *ast.RenameQuery:
117117
explainRenameQuery(sb, n, indent, depth)
118+
case *ast.ExchangeQuery:
119+
explainExchangeQuery(sb, n, indent)
118120
case *ast.SetQuery:
119121
explainSetQuery(sb, indent)
120122
case *ast.SystemQuery:

internal/explain/statements.go

Lines changed: 80 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -462,6 +462,37 @@ func explainRenameQuery(sb *strings.Builder, n *ast.RenameQuery, indent string,
462462
}
463463
}
464464

465+
func explainExchangeQuery(sb *strings.Builder, n *ast.ExchangeQuery, indent string) {
466+
if n == nil {
467+
fmt.Fprintf(sb, "%s*ast.ExchangeQuery\n", indent)
468+
return
469+
}
470+
// Count identifiers: 2 per table (db + table if qualified, or just table)
471+
// EXCHANGE TABLES outputs as "Rename" in ClickHouse
472+
children := 0
473+
if n.Database1 != "" {
474+
children += 2 // db1 + table1
475+
} else {
476+
children += 1 // just table1
477+
}
478+
if n.Database2 != "" {
479+
children += 2 // db2 + table2
480+
} else {
481+
children += 1 // just table2
482+
}
483+
fmt.Fprintf(sb, "%sRename (children %d)\n", indent, children)
484+
// First table
485+
if n.Database1 != "" {
486+
fmt.Fprintf(sb, "%s Identifier %s\n", indent, n.Database1)
487+
}
488+
fmt.Fprintf(sb, "%s Identifier %s\n", indent, n.Table1)
489+
// Second table
490+
if n.Database2 != "" {
491+
fmt.Fprintf(sb, "%s Identifier %s\n", indent, n.Database2)
492+
}
493+
fmt.Fprintf(sb, "%s Identifier %s\n", indent, n.Table2)
494+
}
495+
465496
func explainSetQuery(sb *strings.Builder, indent string) {
466497
fmt.Fprintf(sb, "%sSet\n", indent)
467498
}
@@ -554,18 +585,39 @@ func explainShowQuery(sb *strings.Builder, n *ast.ShowQuery, indent string) {
554585

555586
// SHOW CREATE TABLE has special output format with database and table identifiers
556587
if n.ShowType == ast.ShowCreate && (n.Database != "" || n.From != "") {
557-
// Format: ShowCreateTableQuery database table (children 2)
588+
// Format: ShowCreateTableQuery database table (children 2) or with FORMAT
558589
name := n.From
559590
if n.Database != "" && n.From != "" {
560-
fmt.Fprintf(sb, "%sShowCreateTableQuery %s %s (children 2)\n", indent, n.Database, n.From)
591+
children := 2
592+
if n.Format != "" {
593+
children = 3
594+
}
595+
fmt.Fprintf(sb, "%sShowCreateTableQuery %s %s (children %d)\n", indent, n.Database, n.From, children)
561596
fmt.Fprintf(sb, "%s Identifier %s\n", indent, n.Database)
562597
fmt.Fprintf(sb, "%s Identifier %s\n", indent, n.From)
598+
if n.Format != "" {
599+
fmt.Fprintf(sb, "%s Identifier %s\n", indent, n.Format)
600+
}
563601
} else if n.From != "" {
564-
fmt.Fprintf(sb, "%sShowCreateTableQuery %s (children 1)\n", indent, name)
602+
children := 1
603+
if n.Format != "" {
604+
children = 2
605+
}
606+
fmt.Fprintf(sb, "%sShowCreateTableQuery %s (children %d)\n", indent, name, children)
565607
fmt.Fprintf(sb, "%s Identifier %s\n", indent, name)
608+
if n.Format != "" {
609+
fmt.Fprintf(sb, "%s Identifier %s\n", indent, n.Format)
610+
}
566611
} else if n.Database != "" {
567-
fmt.Fprintf(sb, "%sShowCreateTableQuery %s (children 1)\n", indent, n.Database)
612+
children := 1
613+
if n.Format != "" {
614+
children = 2
615+
}
616+
fmt.Fprintf(sb, "%sShowCreateTableQuery %s (children %d)\n", indent, n.Database, children)
568617
fmt.Fprintf(sb, "%s Identifier %s\n", indent, n.Database)
618+
if n.Format != "" {
619+
fmt.Fprintf(sb, "%s Identifier %s\n", indent, n.Format)
620+
}
569621
} else {
570622
fmt.Fprintf(sb, "%sShow%s\n", indent, showType)
571623
}
@@ -714,17 +766,20 @@ func explainDetachQuery(sb *strings.Builder, n *ast.DetachQuery, indent string)
714766
fmt.Fprintf(sb, "%s Identifier %s\n", indent, n.Table)
715767
return
716768
}
717-
// Single name (table only or database only for DETACH DATABASE)
718-
name := n.Table
719-
if name == "" {
720-
name = n.Database
769+
// DETACH DATABASE db: Database set, Table empty -> "DetachQuery db (children 1)"
770+
if n.Database != "" && n.Table == "" {
771+
fmt.Fprintf(sb, "%sDetachQuery %s (children 1)\n", indent, n.Database)
772+
fmt.Fprintf(sb, "%s Identifier %s\n", indent, n.Database)
773+
return
721774
}
722-
if name != "" {
723-
fmt.Fprintf(sb, "%sDetachQuery %s (children 1)\n", indent, name)
724-
fmt.Fprintf(sb, "%s Identifier %s\n", indent, name)
725-
} else {
726-
fmt.Fprintf(sb, "%sDetachQuery\n", indent)
775+
// DETACH TABLE table: Database empty, Table set -> "DetachQuery table (children 1)"
776+
if n.Table != "" {
777+
fmt.Fprintf(sb, "%sDetachQuery %s (children 1)\n", indent, n.Table)
778+
fmt.Fprintf(sb, "%s Identifier %s\n", indent, n.Table)
779+
return
727780
}
781+
// No name
782+
fmt.Fprintf(sb, "%sDetachQuery\n", indent)
728783
}
729784

730785
func explainAttachQuery(sb *strings.Builder, n *ast.AttachQuery, indent string) {
@@ -736,17 +791,20 @@ func explainAttachQuery(sb *strings.Builder, n *ast.AttachQuery, indent string)
736791
fmt.Fprintf(sb, "%s Identifier %s\n", indent, n.Table)
737792
return
738793
}
739-
// Single name (table only or database only for ATTACH DATABASE)
740-
name := n.Table
741-
if name == "" {
742-
name = n.Database
794+
// ATTACH DATABASE db: Database set, Table empty -> "AttachQuery db (children 1)"
795+
if n.Database != "" && n.Table == "" {
796+
fmt.Fprintf(sb, "%sAttachQuery %s (children 1)\n", indent, n.Database)
797+
fmt.Fprintf(sb, "%s Identifier %s\n", indent, n.Database)
798+
return
743799
}
744-
if name != "" {
745-
fmt.Fprintf(sb, "%sAttachQuery %s (children 1)\n", indent, name)
746-
fmt.Fprintf(sb, "%s Identifier %s\n", indent, name)
747-
} else {
748-
fmt.Fprintf(sb, "%sAttachQuery\n", indent)
800+
// ATTACH TABLE table: Database empty, Table set -> "AttachQuery table (children 1)" (single space)
801+
if n.Table != "" {
802+
fmt.Fprintf(sb, "%sAttachQuery %s (children 1)\n", indent, n.Table)
803+
fmt.Fprintf(sb, "%s Identifier %s\n", indent, n.Table)
804+
return
749805
}
806+
// No name
807+
fmt.Fprintf(sb, "%sAttachQuery\n", indent)
750808
}
751809

752810
func explainAlterQuery(sb *strings.Builder, n *ast.AlterQuery, indent string, depth int) {

parser/parser.go

Lines changed: 53 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -3652,6 +3652,15 @@ func (p *Parser) parseShow() ast.Statement {
36523652
show.Limit = p.parseExpression(LOWEST)
36533653
}
36543654

3655+
// Parse FORMAT clause
3656+
if p.currentIs(token.FORMAT) {
3657+
p.nextToken()
3658+
if p.currentIs(token.IDENT) || p.current.Token.IsKeyword() {
3659+
show.Format = p.current.Value
3660+
p.nextToken()
3661+
}
3662+
}
3663+
36553664
return show
36563665
}
36573666

@@ -3931,15 +3940,29 @@ func (p *Parser) parseExchange() *ast.ExchangeQuery {
39313940
return nil
39323941
}
39333942

3934-
// Parse first table name (can start with a number in ClickHouse)
3935-
exchange.Table1 = p.parseIdentifierName()
3943+
// Parse first table name (can be database.table)
3944+
name1 := p.parseIdentifierName()
3945+
if p.currentIs(token.DOT) {
3946+
p.nextToken()
3947+
exchange.Database1 = name1
3948+
exchange.Table1 = p.parseIdentifierName()
3949+
} else {
3950+
exchange.Table1 = name1
3951+
}
39363952

39373953
if !p.expect(token.AND) {
39383954
return nil
39393955
}
39403956

3941-
// Parse second table name (can start with a number in ClickHouse)
3942-
exchange.Table2 = p.parseIdentifierName()
3957+
// Parse second table name (can be database.table)
3958+
name2 := p.parseIdentifierName()
3959+
if p.currentIs(token.DOT) {
3960+
p.nextToken()
3961+
exchange.Database2 = name2
3962+
exchange.Table2 = p.parseIdentifierName()
3963+
} else {
3964+
exchange.Table2 = name2
3965+
}
39433966

39443967
// Handle ON CLUSTER
39453968
if p.currentIs(token.ON) {
@@ -3960,19 +3983,25 @@ func (p *Parser) parseDetach() *ast.DetachQuery {
39603983

39613984
p.nextToken() // skip DETACH
39623985

3963-
// Skip optional TABLE keyword
3964-
if p.currentIs(token.TABLE) {
3986+
// Check for DATABASE keyword - if present, store in Database field
3987+
isDatabase := false
3988+
if p.currentIs(token.DATABASE) {
3989+
isDatabase = true
3990+
p.nextToken()
3991+
} else if p.currentIs(token.TABLE) {
39653992
p.nextToken()
39663993
}
39673994

3968-
// Parse table name (can be qualified: database.table)
3969-
tableName := p.parseIdentifierName()
3970-
if p.currentIs(token.DOT) {
3995+
// Parse name (can be qualified: database.table for TABLE, not for DATABASE)
3996+
name := p.parseIdentifierName()
3997+
if p.currentIs(token.DOT) && !isDatabase {
39713998
p.nextToken()
3972-
detach.Database = tableName
3999+
detach.Database = name
39734000
detach.Table = p.parseIdentifierName()
4001+
} else if isDatabase {
4002+
detach.Database = name
39744003
} else {
3975-
detach.Table = tableName
4004+
detach.Table = name
39764005
}
39774006

39784007
return detach
@@ -3985,19 +4014,25 @@ func (p *Parser) parseAttach() *ast.AttachQuery {
39854014

39864015
p.nextToken() // skip ATTACH
39874016

3988-
// Skip optional TABLE keyword
3989-
if p.currentIs(token.TABLE) {
4017+
// Check for DATABASE keyword - if present, store in Database field
4018+
isDatabase := false
4019+
if p.currentIs(token.DATABASE) {
4020+
isDatabase = true
4021+
p.nextToken()
4022+
} else if p.currentIs(token.TABLE) {
39904023
p.nextToken()
39914024
}
39924025

3993-
// Parse table name (can be qualified: database.table)
3994-
tableName := p.parseIdentifierName()
3995-
if p.currentIs(token.DOT) {
4026+
// Parse name (can be qualified: database.table for TABLE, not for DATABASE)
4027+
name := p.parseIdentifierName()
4028+
if p.currentIs(token.DOT) && !isDatabase {
39964029
p.nextToken()
3997-
attach.Database = tableName
4030+
attach.Database = name
39984031
attach.Table = p.parseIdentifierName()
4032+
} else if isDatabase {
4033+
attach.Database = name
39994034
} else {
4000-
attach.Table = tableName
4035+
attach.Table = name
40014036
}
40024037

40034038
return attach

parser/testdata/00077_log_tinylog_stripelog/metadata.json

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,9 @@
11
{
22
"explain_todo": {
33
"stmt10": true,
4-
"stmt11": true,
5-
"stmt12": true,
6-
"stmt13": true,
74
"stmt18": true,
85
"stmt19": true,
96
"stmt20": true,
10-
"stmt5": true,
11-
"stmt6": true,
12-
"stmt7": true,
137
"stmt8": true,
148
"stmt9": true
159
}
Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
{
22
"explain_todo": {
33
"stmt2": true,
4-
"stmt4": true,
54
"stmt7": true
65
}
76
}
Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1 @@
1-
{
2-
"explain_todo": {
3-
"stmt13": true,
4-
"stmt17": true
5-
}
6-
}
1+
{}
Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1 @@
1-
{
2-
"explain_todo": {
3-
"stmt12": true
4-
}
5-
}
1+
{}
Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
{
22
"explain_todo": {
33
"stmt10": true,
4-
"stmt5": true,
54
"stmt9": true
65
}
76
}
Lines changed: 1 addition & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1 @@
1-
{
2-
"explain_todo": {
3-
"stmt11": true,
4-
"stmt14": true,
5-
"stmt17": true,
6-
"stmt22": true,
7-
"stmt25": true,
8-
"stmt28": true,
9-
"stmt31": true,
10-
"stmt34": true,
11-
"stmt8": true
12-
}
13-
}
1+
{}

0 commit comments

Comments
 (0)