Skip to content

Commit 82b9d36

Browse files
authored
Support FQN in statements (#178)
* Support FQN in SHOW COLUMNS * Support FQN in SHOW CREATE TABLE * Support FQN in SHOW INDEX * Add description about FQN support in some commands * Add tests of FQN * Refine extractQualifiedName * Simplify extractSchemaAndTable * fix function name in test output
1 parent a0ff8ae commit 82b9d36

3 files changed

Lines changed: 150 additions & 26 deletions

File tree

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -193,9 +193,9 @@ and `{}` for a mutually exclusive keyword.
193193
| Create database | `CREATE DATABSE <database>;` | |
194194
| Drop database | `DROP DATABASE <database>;` | |
195195
| List tables | `SHOW TABLES [<schema>];` | If schema is not provided, default schema is used |
196-
| Show table schema | `SHOW CREATE TABLE <table>;` | |
197-
| Show columns | `SHOW COLUMNS FROM <table>;` | |
198-
| Show indexes | `SHOW INDEX FROM <table>;` | |
196+
| Show table schema | `SHOW CREATE TABLE <table>;` | The table can be a FQN.|
197+
| Show columns | `SHOW COLUMNS FROM <table>;` | The table can be a FQN.|
198+
| Show indexes | `SHOW INDEX FROM <table>;` | The table can be a FQN.|
199199
| Create table | `CREATE TABLE ...;` | |
200200
| Change table schema | `ALTER TABLE ...;` | |
201201
| Delete table | `DROP TABLE ...;` | |

statement.go

Lines changed: 44 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,8 @@ func BuildStatementWithComments(stripped, raw string) (Statement, error) {
166166
return &ShowDatabasesStatement{}, nil
167167
case showCreateTableRe.MatchString(stripped):
168168
matched := showCreateTableRe.FindStringSubmatch(stripped)
169-
return &ShowCreateTableStatement{Table: unquoteIdentifier(matched[1])}, nil
169+
schema, table := extractSchemaAndTable(unquoteIdentifier(matched[1]))
170+
return &ShowCreateTableStatement{Schema: schema, Table: table}, nil
170171
case showTablesRe.MatchString(stripped):
171172
matched := showTablesRe.FindStringSubmatch(stripped)
172173
return &ShowTablesStatement{Schema: unquoteIdentifier(matched[1])}, nil
@@ -186,10 +187,12 @@ func BuildStatementWithComments(stripped, raw string) (Statement, error) {
186187
}
187188
case showColumnsRe.MatchString(stripped):
188189
matched := showColumnsRe.FindStringSubmatch(stripped)
189-
return &ShowColumnsStatement{Table: unquoteIdentifier(matched[1])}, nil
190+
schema, table := extractSchemaAndTable(unquoteIdentifier(matched[1]))
191+
return &ShowColumnsStatement{Schema: schema, Table: table}, nil
190192
case showIndexRe.MatchString(stripped):
191193
matched := showIndexRe.FindStringSubmatch(stripped)
192-
return &ShowIndexStatement{Table: unquoteIdentifier(matched[1])}, nil
194+
schema, table := extractSchemaAndTable(unquoteIdentifier(matched[1]))
195+
return &ShowIndexStatement{Schema: schema, Table: table}, nil
193196
case dmlRe.MatchString(stripped):
194197
return &DmlStatement{Dml: raw}, nil
195198
case pdmlRe.MatchString(stripped):
@@ -211,7 +214,7 @@ func BuildStatementWithComments(stripped, raw string) (Statement, error) {
211214
}
212215

213216
func unquoteIdentifier(input string) string {
214-
return strings.Trim(input, "`")
217+
return strings.Trim(strings.TrimSpace(input), "`")
215218
}
216219

217220
type SelectStatement struct {
@@ -431,7 +434,8 @@ func (s *ShowDatabasesStatement) Execute(ctx context.Context, session *Session)
431434
}
432435

433436
type ShowCreateTableStatement struct {
434-
Table string
437+
Schema string
438+
Table string
435439
}
436440

437441
func (s *ShowCreateTableStatement) Execute(ctx context.Context, session *Session) (*Result, error) {
@@ -444,26 +448,38 @@ func (s *ShowCreateTableStatement) Execute(ctx context.Context, session *Session
444448
return nil, err
445449
}
446450
for _, stmt := range ddlResponse.Statements {
447-
if isCreateTableDDL(stmt, s.Table) {
451+
if isCreateTableDDL(stmt, s.Schema, s.Table) {
452+
var fqn string
453+
if s.Schema == "" {
454+
fqn = s.Table
455+
} else {
456+
fqn = fmt.Sprintf("%s.%s", s.Schema, s.Table)
457+
}
458+
448459
resultRow := Row{
449-
Columns: []string{s.Table, stmt},
460+
Columns: []string{fqn, stmt},
450461
}
451462
result.Rows = append(result.Rows, resultRow)
452463
break
453464
}
454465
}
455466
if len(result.Rows) == 0 {
456-
return nil, fmt.Errorf("table %q doesn't exist", s.Table)
467+
return nil, fmt.Errorf("table %q doesn't exist in schema %q", s.Table, s.Schema)
457468
}
458469

459470
result.AffectedRows = len(result.Rows)
460471

461472
return result, nil
462473
}
463474

464-
func isCreateTableDDL(ddl string, table string) bool {
475+
func isCreateTableDDL(ddl string, schema string, table string) bool {
465476
table = regexp.QuoteMeta(table)
466-
re := fmt.Sprintf("(?i)^CREATE TABLE (%s|`%s`)\\s*\\(", table, table)
477+
var re string
478+
if schema == "" {
479+
re = fmt.Sprintf("(?i)^CREATE TABLE (%s|`%s`)\\s*\\(", table, table)
480+
} else {
481+
re = fmt.Sprintf("(?i)^CREATE TABLE (%s|`%s`)\\.(%s|`%s`)\\s*\\(", schema, schema, table, table)
482+
}
467483
return regexp.MustCompile(re).MatchString(ddl)
468484
}
469485

@@ -620,7 +636,8 @@ func processPlanImpl(plan *pb.QueryPlan, withStats bool) (rows []Row, predicates
620636
}
621637

622638
type ShowColumnsStatement struct {
623-
Table string
639+
Schema string
640+
Table string
624641
}
625642

626643
func (s *ShowColumnsStatement) Execute(ctx context.Context, session *Session) (*Result, error) {
@@ -646,10 +663,10 @@ LEFT JOIN
646663
LEFT JOIN
647664
INFORMATION_SCHEMA.COLUMN_OPTIONS CO USING(TABLE_SCHEMA, TABLE_NAME, COLUMN_NAME)
648665
WHERE
649-
C.TABLE_SCHEMA = '' AND LOWER(C.TABLE_NAME) = LOWER(@table_name)
666+
LOWER(C.TABLE_SCHEMA) = LOWER(@table_schema) AND LOWER(C.TABLE_NAME) = LOWER(@table_name)
650667
ORDER BY
651668
C.ORDINAL_POSITION ASC`,
652-
Params: map[string]interface{}{"table_name": s.Table}}
669+
Params: map[string]interface{}{"table_name": s.Table, "table_schema": s.Schema}}
653670

654671
iter, _ := session.RunQuery(ctx, stmt)
655672
defer iter.Stop()
@@ -659,7 +676,7 @@ ORDER BY
659676
return nil, err
660677
}
661678
if len(rows) == 0 {
662-
return nil, fmt.Errorf("table %q doesn't exist", s.Table)
679+
return nil, fmt.Errorf("table %q doesn't exist in schema %q", s.Table, s.Schema)
663680
}
664681

665682
return &Result{
@@ -669,8 +686,17 @@ ORDER BY
669686
}, nil
670687
}
671688

689+
func extractSchemaAndTable(s string) (string, string) {
690+
schema, table, found := strings.Cut(s, ".")
691+
if !found {
692+
return "", unquoteIdentifier(s)
693+
}
694+
return unquoteIdentifier(schema), unquoteIdentifier(table)
695+
}
696+
672697
type ShowIndexStatement struct {
673-
Table string
698+
Schema string
699+
Table string
674700
}
675701

676702
func (s *ShowIndexStatement) Execute(ctx context.Context, session *Session) (*Result, error) {
@@ -692,8 +718,8 @@ func (s *ShowIndexStatement) Execute(ctx context.Context, session *Session) (*Re
692718
FROM
693719
INFORMATION_SCHEMA.INDEXES I
694720
WHERE
695-
I.TABLE_SCHEMA = '' AND LOWER(TABLE_NAME) = LOWER(@table_name)`,
696-
Params: map[string]interface{}{"table_name": s.Table}}
721+
LOWER(I.TABLE_SCHEMA) = @table_schema AND LOWER(TABLE_NAME) = LOWER(@table_name)`,
722+
Params: map[string]interface{}{"table_name": s.Table, "table_schema": s.Schema}}
697723

698724
iter, _ := session.RunQuery(ctx, stmt)
699725
defer iter.Stop()
@@ -703,7 +729,7 @@ WHERE
703729
return nil, err
704730
}
705731
if len(rows) == 0 {
706-
return nil, fmt.Errorf("table %q doesn't exist", s.Table)
732+
return nil, fmt.Errorf("table %q doesn't exist in schema %q", s.Table, s.Schema)
707733
}
708734

709735
return &Result{

statement_test.go

Lines changed: 103 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -442,6 +442,11 @@ func TestBuildStatement(t *testing.T) {
442442
input: "SHOW CREATE TABLE t1",
443443
want: &ShowCreateTableStatement{Table: "t1"},
444444
},
445+
{
446+
desc: "SHOW CREATE TABLE statement with a named schema",
447+
input: "SHOW CREATE TABLE sch1.t1",
448+
want: &ShowCreateTableStatement{Schema: "sch1", Table: "t1"},
449+
},
445450
{
446451
desc: "SHOW CREATE TABLE statement with quoted identifier",
447452
input: "SHOW CREATE TABLE `TABLE`",
@@ -467,6 +472,11 @@ func TestBuildStatement(t *testing.T) {
467472
input: "SHOW INDEX FROM t1",
468473
want: &ShowIndexStatement{Table: "t1"},
469474
},
475+
{
476+
desc: "SHOW INDEX statement with a named schema",
477+
input: "SHOW INDEX FROM sch1.t1",
478+
want: &ShowIndexStatement{Schema: "sch1", Table: "t1"},
479+
},
470480
{
471481
desc: "SHOW INDEXES statement",
472482
input: "SHOW INDEXES FROM t1",
@@ -487,6 +497,11 @@ func TestBuildStatement(t *testing.T) {
487497
input: "SHOW COLUMNS FROM t1",
488498
want: &ShowColumnsStatement{Table: "t1"},
489499
},
500+
{
501+
desc: "SHOW COLUMNS statement with a named schema",
502+
input: "SHOW COLUMNS FROM sch1.t1",
503+
want: &ShowColumnsStatement{Schema: "sch1", Table: "t1"},
504+
},
490505
{
491506
desc: "SHOW COLUMNS statement with quoted identifier",
492507
input: "SHOW COLUMNS FROM `TABLE`",
@@ -593,10 +608,11 @@ func TestBuildStatement(t *testing.T) {
593608

594609
func TestIsCreateTableDDL(t *testing.T) {
595610
for _, tt := range []struct {
596-
desc string
597-
ddl string
598-
table string
599-
want bool
611+
desc string
612+
ddl string
613+
schema string
614+
table string
615+
want bool
600616
}{
601617
{
602618
desc: "exact match",
@@ -636,9 +652,91 @@ func TestIsCreateTableDDL(t *testing.T) {
636652
},
637653
} {
638654
t.Run(tt.desc, func(t *testing.T) {
639-
if got := isCreateTableDDL(tt.ddl, tt.table); got != tt.want {
655+
if got := isCreateTableDDL(tt.ddl, tt.schema, tt.table); got != tt.want {
640656
t.Errorf("isCreateTableDDL(%q, %q) = %v, but want %v", tt.ddl, tt.table, got, tt.want)
641657
}
642658
})
643659
}
644660
}
661+
662+
func TestExtractSchemaAndTable(t *testing.T) {
663+
for _, tt := range []struct {
664+
desc string
665+
input string
666+
schema string
667+
table string
668+
}{
669+
{
670+
desc: "raw table",
671+
input: "table",
672+
schema: "",
673+
table: "table",
674+
},
675+
{
676+
desc: "quoted table",
677+
input: "`table`",
678+
schema: "",
679+
table: "table",
680+
},
681+
{
682+
desc: "FQN",
683+
input: "schema.table",
684+
schema: "schema",
685+
table: "table",
686+
},
687+
{
688+
desc: "FQN with spaces",
689+
input: "schema . table",
690+
schema: "schema",
691+
table: "table",
692+
},
693+
{
694+
desc: "FQN, both schema and table are quoted",
695+
input: "`schema`.`table`",
696+
schema: "schema",
697+
table: "table",
698+
},
699+
{
700+
desc: "FQN with spaces, both schema and table are quoted",
701+
input: "`schema` . `table`",
702+
schema: "schema",
703+
table: "table",
704+
},
705+
{
706+
desc: "FQN, only schema is quoted",
707+
input: "`schema`.table",
708+
schema: "schema",
709+
table: "table",
710+
},
711+
{
712+
desc: "FQN with spaces, only schema is quoted",
713+
input: "`schema` . table",
714+
schema: "schema",
715+
table: "table",
716+
},
717+
{
718+
desc: "FQN, only table is quoted",
719+
input: "schema.`table`",
720+
schema: "schema",
721+
table: "table",
722+
},
723+
{
724+
desc: "FQN with spaces, only table is quoted",
725+
input: "schema . `table`",
726+
schema: "schema",
727+
table: "table",
728+
},
729+
{
730+
desc: "whole quoted FQN",
731+
input: "`schema.table`",
732+
schema: "schema",
733+
table: "table",
734+
},
735+
} {
736+
t.Run(tt.desc, func(t *testing.T) {
737+
if schema, table := extractSchemaAndTable(tt.input); schema != tt.schema || table != tt.table {
738+
t.Errorf("extractSchemaAndTable(%q) = (%v, %v), but want (%v, %v)", tt.input, schema, table, tt.schema, tt.table)
739+
}
740+
})
741+
}
742+
}

0 commit comments

Comments
 (0)