Skip to content
This repository has been archived by the owner on Jan 28, 2021. It is now read-only.

sql/*: fix show tables to match MySQL spec #588

Merged
merged 1 commit into from
Jan 17, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
43 changes: 43 additions & 0 deletions engine_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -800,6 +800,49 @@ var queries = []struct {
{nil},
},
},
{
"SHOW TABLES",
[]sql.Row{
{"mytable"},
{"othertable"},
{"tabletest"},
},
},
{
"SHOW FULL TABLES",
[]sql.Row{
{"mytable", "BASE TABLE"},
{"othertable", "BASE TABLE"},
{"tabletest", "BASE TABLE"},
},
},
{
"SHOW FULL TABLES",
[]sql.Row{
{"mytable", "BASE TABLE"},
{"othertable", "BASE TABLE"},
{"tabletest", "BASE TABLE"},
},
},
{
"SHOW TABLES FROM foo",
[]sql.Row{
{"other_table"},
},
},
{
"SHOW TABLES LIKE '%table'",
[]sql.Row{
{"mytable"},
{"othertable"},
},
},
{
"SHOW TABLES WHERE `Table` = 'mytable'",
[]sql.Row{
{"mytable"},
},
},
}

func TestQueries(t *testing.T) {
Expand Down
95 changes: 51 additions & 44 deletions sql/analyzer/resolve_database.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,53 +11,60 @@ func resolveDatabase(ctx *sql.Context, a *Analyzer, n sql.Node) (sql.Node, error

a.Log("resolve database, node of type: %T", n)

switch v := n.(type) {
case *plan.ShowIndexes:
db, err := a.Catalog.Database(a.Catalog.CurrentDatabase())
if err != nil {
return nil, err
}
return n.TransformUp(func(n sql.Node) (sql.Node, error) {
switch v := n.(type) {
case *plan.ShowIndexes:
db, err := a.Catalog.Database(a.Catalog.CurrentDatabase())
if err != nil {
return nil, err
}

nc := *v
nc.Database = db
return &nc, nil
case *plan.ShowTables:
db, err := a.Catalog.Database(a.Catalog.CurrentDatabase())
if err != nil {
return nil, err
}
nc := *v
nc.Database = db
return &nc, nil
case *plan.ShowTables:
var dbName = v.Database.Name()
if dbName == "" {
dbName = a.Catalog.CurrentDatabase()
}

nc := *v
nc.Database = db
return &nc, nil
case *plan.CreateTable:
db, err := a.Catalog.Database(a.Catalog.CurrentDatabase())
if err != nil {
return nil, err
}
db, err := a.Catalog.Database(dbName)
if err != nil {
return nil, err
}

nc := *v
nc.Database = db
return &nc, nil
case *plan.Use:
db, err := a.Catalog.Database(v.Database.Name())
if err != nil {
return nil, err
}
nc := *v
nc.Database = db
return &nc, nil
case *plan.CreateTable:
db, err := a.Catalog.Database(a.Catalog.CurrentDatabase())
if err != nil {
return nil, err
}

nc := *v
nc.Database = db
return &nc, nil
case *plan.ShowCreateDatabase:
db, err := a.Catalog.Database(v.Database.Name())
if err != nil {
return nil, err
}
nc := *v
nc.Database = db
return &nc, nil
case *plan.Use:
db, err := a.Catalog.Database(v.Database.Name())
if err != nil {
return nil, err
}

nc := *v
nc.Database = db
return &nc, nil
case *plan.ShowCreateDatabase:
db, err := a.Catalog.Database(v.Database.Name())
if err != nil {
return nil, err
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This kind of block is almost in every case. Maybe it's good time to extract it to some function.

Copy link
Contributor Author

@erizocosmico erizocosmico Jan 17, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But the type is different, so you can't extract it. We could extract the db, err := a.Catalog.Database(v.Database.Name()), but after error handling it would be the same amount of lines, so no point in extracting that either.

The only way I can imagine that we can use to extract this is create an interface that these nodes implement.

type Databaser interface {
  Database() sql.Database
  WithDatabase(sql.Database) (sql.Node, error)
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ohh, you're right - it sucks. Let's leave it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#generics 😜

}

nc := *v
nc.Database = db
return &nc, nil
default:
return n, nil
}
nc := *v
nc.Database = db
return &nc, nil
default:
return n, nil
}
})
}
30 changes: 29 additions & 1 deletion sql/parse/parse.go
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,35 @@ func convertSet(ctx *sql.Context, n *sqlparser.Set) (sql.Node, error) {
func convertShow(s *sqlparser.Show, query string) (sql.Node, error) {
switch s.Type {
case sqlparser.KeywordString(sqlparser.TABLES):
return plan.NewShowTables(sql.UnresolvedDatabase("")), nil
var dbName string
var filter sql.Expression
var full bool
if s.ShowTablesOpt != nil {
dbName = s.ShowTablesOpt.DbName
full = s.ShowTablesOpt.Full != ""

if s.ShowTablesOpt.Filter != nil {
if s.ShowTablesOpt.Filter.Filter != nil {
var err error
filter, err = exprToExpression(s.ShowTablesOpt.Filter.Filter)
if err != nil {
return nil, err
}
} else if s.ShowTablesOpt.Filter.Like != "" {
filter = expression.NewLike(
expression.NewUnresolvedColumn("Table"),
expression.NewLiteral(s.ShowTablesOpt.Filter.Like, sql.Text),
)
}
}
}

var node sql.Node = plan.NewShowTables(sql.UnresolvedDatabase(dbName), full)
if filter != nil {
node = plan.NewFilter(filter, node)
}

return node, nil
case sqlparser.KeywordString(sqlparser.DATABASES):
return plan.NewShowDatabases(), nil
case sqlparser.KeywordString(sqlparser.FIELDS), sqlparser.KeywordString(sqlparser.COLUMNS):
Expand Down
49 changes: 48 additions & 1 deletion sql/parse/parse_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -246,7 +246,54 @@ var fixtures = map[string]sql.Node{
}}),
[]string{"col1", "col2"},
),
`SHOW TABLES`: plan.NewShowTables(sql.UnresolvedDatabase("")),
`SHOW TABLES`: plan.NewShowTables(sql.UnresolvedDatabase(""), false),
`SHOW FULL TABLES`: plan.NewShowTables(sql.UnresolvedDatabase(""), true),
`SHOW TABLES FROM foo`: plan.NewShowTables(sql.UnresolvedDatabase("foo"), false),
`SHOW TABLES IN foo`: plan.NewShowTables(sql.UnresolvedDatabase("foo"), false),
`SHOW FULL TABLES FROM foo`: plan.NewShowTables(sql.UnresolvedDatabase("foo"), true),
`SHOW FULL TABLES IN foo`: plan.NewShowTables(sql.UnresolvedDatabase("foo"), true),
`SHOW TABLES LIKE 'foo'`: plan.NewFilter(
expression.NewLike(
expression.NewUnresolvedColumn("Table"),
expression.NewLiteral("foo", sql.Text),
),
plan.NewShowTables(sql.UnresolvedDatabase(""), false),
),
"SHOW TABLES WHERE `Table` = 'foo'": plan.NewFilter(
expression.NewEquals(
expression.NewUnresolvedColumn("Table"),
expression.NewLiteral("foo", sql.Text),
),
plan.NewShowTables(sql.UnresolvedDatabase(""), false),
),
`SHOW FULL TABLES LIKE 'foo'`: plan.NewFilter(
expression.NewLike(
expression.NewUnresolvedColumn("Table"),
expression.NewLiteral("foo", sql.Text),
),
plan.NewShowTables(sql.UnresolvedDatabase(""), true),
),
"SHOW FULL TABLES WHERE `Table` = 'foo'": plan.NewFilter(
expression.NewEquals(
expression.NewUnresolvedColumn("Table"),
expression.NewLiteral("foo", sql.Text),
),
plan.NewShowTables(sql.UnresolvedDatabase(""), true),
),
`SHOW FULL TABLES FROM bar LIKE 'foo'`: plan.NewFilter(
expression.NewLike(
expression.NewUnresolvedColumn("Table"),
expression.NewLiteral("foo", sql.Text),
),
plan.NewShowTables(sql.UnresolvedDatabase("bar"), true),
),
"SHOW FULL TABLES FROM bar WHERE `Table` = 'foo'": plan.NewFilter(
expression.NewEquals(
expression.NewUnresolvedColumn("Table"),
expression.NewLiteral("foo", sql.Text),
),
plan.NewShowTables(sql.UnresolvedDatabase("bar"), true),
),
`SELECT DISTINCT foo, bar FROM foo;`: plan.NewDistinct(
plan.NewProject(
[]sql.Expression{
Expand Down
5 changes: 3 additions & 2 deletions sql/plan/show_create_table.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"gopkg.in/src-d/go-mysql-server.v0/sql"
)

// ErrTableNotFound is returned when the table could not be found.
var ErrTableNotFound = errors.NewKind("Table `%s` not found")

// ShowCreateTable is a node that shows the CREATE TABLE statement for a table.
Expand Down Expand Up @@ -56,8 +57,8 @@ func (n *ShowCreateTable) String() string {
}

type showCreateTablesIter struct {
db sql.Database
table string
db sql.Database
table string
didIteration bool
}

Expand Down
59 changes: 29 additions & 30 deletions sql/plan/show_tables.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package plan

import (
"io"
"sort"

"gopkg.in/src-d/go-mysql-server.v0/sql"
Expand All @@ -10,12 +9,23 @@ import (
// ShowTables is a node that shows the database tables.
type ShowTables struct {
Database sql.Database
Full bool
}

var showTablesSchema = sql.Schema{
{Name: "Table", Type: sql.Text},
}

var showTablesFullSchema = sql.Schema{
{Name: "Table", Type: sql.Text},
{Name: "Table_type", Type: sql.Text},
}

// NewShowTables creates a new show tables node given a database.
func NewShowTables(database sql.Database) *ShowTables {
func NewShowTables(database sql.Database, full bool) *ShowTables {
return &ShowTables{
Database: database,
Full: full,
}
}

Expand All @@ -31,12 +41,12 @@ func (*ShowTables) Children() []sql.Node {
}

// Schema implements the Node interface.
func (*ShowTables) Schema() sql.Schema {
return sql.Schema{{
Name: "table",
Type: sql.Text,
Nullable: false,
}}
func (p *ShowTables) Schema() sql.Schema {
if p.Full {
return showTablesFullSchema
}

return showTablesSchema
}

// RowIter implements the Node interface.
Expand All @@ -48,12 +58,21 @@ func (p *ShowTables) RowIter(ctx *sql.Context) (sql.RowIter, error) {

sort.Strings(tableNames)

return &showTablesIter{tableNames: tableNames}, nil
var rows = make([]sql.Row, len(tableNames))
for i, n := range tableNames {
row := sql.Row{n}
if p.Full {
row = append(row, "BASE TABLE")
}
rows[i] = row
}

return sql.RowsToRowIter(rows...), nil
}

// TransformUp implements the Transformable interface.
func (p *ShowTables) TransformUp(f sql.TransformNodeFunc) (sql.Node, error) {
return f(NewShowTables(p.Database))
return f(NewShowTables(p.Database, p.Full))
}

// TransformExpressionsUp implements the Transformable interface.
Expand All @@ -64,23 +83,3 @@ func (p *ShowTables) TransformExpressionsUp(f sql.TransformExprFunc) (sql.Node,
func (p ShowTables) String() string {
return "ShowTables"
}

type showTablesIter struct {
tableNames []string
idx int
}

func (i *showTablesIter) Next() (sql.Row, error) {
if i.idx >= len(i.tableNames) {
return nil, io.EOF
}
row := sql.NewRow(i.tableNames[i.idx])
i.idx++

return row, nil
}

func (i *showTablesIter) Close() error {
i.tableNames = nil
return nil
}
4 changes: 2 additions & 2 deletions sql/plan/show_tables_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ func TestShowTables(t *testing.T) {
require := require.New(t)
ctx := sql.NewEmptyContext()

unresolvedShowTables := NewShowTables(sql.UnresolvedDatabase(""))
unresolvedShowTables := NewShowTables(sql.UnresolvedDatabase(""), false)

require.False(unresolvedShowTables.Resolved())
require.Nil(unresolvedShowTables.Children())
Expand All @@ -23,7 +23,7 @@ func TestShowTables(t *testing.T) {
db.AddTable("test2", mem.NewTable("test2", nil))
db.AddTable("test3", mem.NewTable("test3", nil))

resolvedShowTables := NewShowTables(db)
resolvedShowTables := NewShowTables(db, false)
require.True(resolvedShowTables.Resolved())
require.Nil(resolvedShowTables.Children())

Expand Down