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

Commit

Permalink
Merge pull request #435 from Zarkopafilis/show-create-table
Browse files Browse the repository at this point in the history
sql: plan: Add SHOW CREATE TABLE [Fix #406]
  • Loading branch information
ajnavarro authored Oct 17, 2018
2 parents 2542783 + 549bfad commit b830c9a
Show file tree
Hide file tree
Showing 7 changed files with 341 additions and 40 deletions.
75 changes: 38 additions & 37 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,56 +1,57 @@
module gopkg.in/src-d/go-mysql-server.v0

require (
github.com/BurntSushi/toml v0.3.1 // indirect
github.com/CAFxX/gcnotifier v0.0.0-20170518020117-39b0596a2da3 // indirect
github.com/DataDog/datadog-go v0.0.0-20180822151419-281ae9f2d895 // indirect
github.com/StackExchange/wmi v0.0.0-20180725035823-b12b22c5341f // indirect
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da // indirect
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973 // indirect
github.com/BurntSushi/toml v0.3.1 // indirect
github.com/CAFxX/gcnotifier v0.0.0-20170518020117-39b0596a2da3 // indirect
github.com/DataDog/datadog-go v0.0.0-20180822151419-281ae9f2d895 // indirect
github.com/StackExchange/wmi v0.0.0-20180725035823-b12b22c5341f // indirect
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da // indirect
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973 // indirect
github.com/boltdb/bolt v1.3.1
github.com/cespare/xxhash v1.1.0 // indirect
github.com/circonus-labs/circonus-gometrics v2.2.4+incompatible // indirect
github.com/circonus-labs/circonusllhist v0.0.0-20180430145027-5eb751da55c6 // indirect
github.com/go-ole/go-ole v1.2.1 // indirect
github.com/circonus-labs/circonus-gometrics v2.2.4+incompatible // indirect
github.com/circonus-labs/circonusllhist v0.0.0-20180430145027-5eb751da55c6 // indirect
github.com/go-ole/go-ole v1.2.1 // indirect
github.com/gogo/protobuf v1.1.1 // indirect
github.com/google/go-cmp v0.2.0 // indirect
github.com/gorilla/context v1.1.1 // indirect
github.com/gorilla/handlers v1.4.0 // indirect
github.com/gorilla/mux v1.6.2 // indirect
github.com/hashicorp/consul v1.2.3 // indirect
github.com/hashicorp/go-cleanhttp v0.5.0 // indirect
github.com/hashicorp/go-immutable-radix v1.0.0 // indirect
github.com/hashicorp/go-msgpack v0.0.0-20150518234257-fa3f63826f7c // indirect
github.com/hashicorp/go-multierror v1.0.0 // indirect
github.com/hashicorp/go-retryablehttp v0.0.0-20180718195005-e651d75abec6 // indirect
github.com/hashicorp/go-sockaddr v0.0.0-20180320115054-6d291a969b86 // indirect
github.com/hashicorp/memberlist v0.1.0 // indirect
github.com/hashicorp/serf v0.8.1 // indirect
github.com/hashicorp/yamux v0.0.0-20180917205041-7221087c3d28 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
github.com/miekg/dns v1.0.12 // indirect
github.com/google/go-cmp v0.2.0 // indirect
github.com/gorilla/context v1.1.1 // indirect
github.com/gorilla/handlers v1.4.0 // indirect
github.com/gorilla/mux v1.6.2 // indirect
github.com/hashicorp/consul v1.2.3 // indirect
github.com/hashicorp/go-cleanhttp v0.5.0 // indirect
github.com/hashicorp/go-immutable-radix v1.0.0 // indirect
github.com/hashicorp/go-msgpack v0.0.0-20150518234257-fa3f63826f7c // indirect
github.com/hashicorp/go-multierror v1.0.0 // indirect
github.com/hashicorp/go-retryablehttp v0.0.0-20180718195005-e651d75abec6 // indirect
github.com/hashicorp/go-sockaddr v0.0.0-20180320115054-6d291a969b86 // indirect
github.com/hashicorp/memberlist v0.1.0 // indirect
github.com/hashicorp/serf v0.8.1 // indirect
github.com/hashicorp/yamux v0.0.0-20180917205041-7221087c3d28 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
github.com/miekg/dns v1.0.12 // indirect
github.com/mitchellh/hashstructure v1.0.0
github.com/oliveagle/jsonpath v0.0.0-20180606110733-2e52cf6e6852
github.com/opentracing/opentracing-go v1.0.2
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c // indirect
github.com/pelletier/go-toml v1.2.0 // indirect
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c // indirect
github.com/pelletier/go-toml v1.2.0 // indirect
github.com/pilosa/pilosa v1.1.0
github.com/pkg/errors v0.8.0 // indirect
github.com/prometheus/client_golang v0.8.0 // indirect
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910 // indirect
github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e // indirect
github.com/prometheus/procfs v0.0.0-20180920065004-418d78d0b9a7 // indirect
github.com/prometheus/client_golang v0.8.0 // indirect
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910 // indirect
github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e // indirect
github.com/prometheus/procfs v0.0.0-20180920065004-418d78d0b9a7 // indirect
github.com/satori/go.uuid v1.2.0 // indirect
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 // indirect
github.com/shirou/gopsutil v2.17.12+incompatible // indirect
github.com/shirou/w32 v0.0.0-20160930032740-bb4de0191aa4 // indirect
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 // indirect
github.com/shirou/gopsutil v2.17.12+incompatible // indirect
github.com/shirou/w32 v0.0.0-20160930032740-bb4de0191aa4 // indirect
github.com/sirupsen/logrus v1.1.0
github.com/spf13/cast v1.2.0
github.com/stretchr/testify v1.2.2
github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926 // indirect
golang.org/x/net v0.0.0-20180926154720-4dfa2610cdf3 // indirect
github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926 // indirect
golang.org/x/net v0.0.0-20180926154720-4dfa2610cdf3 // indirect
google.golang.org/grpc v1.15.0 // indirect
gopkg.in/src-d/go-errors.v1 v1.0.0
gopkg.in/src-d/go-vitess.v1 v1.1.0
gopkg.in/vmihailenco/msgpack.v2 v2.9.1 // indirect
gopkg.in/vmihailenco/msgpack.v2 v2.9.1 // indirect
gopkg.in/yaml.v2 v2.2.1
)
5 changes: 5 additions & 0 deletions sql/analyzer/assign_catalog.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,11 @@ func assignCatalog(ctx *sql.Context, a *Analyzer, n sql.Node) (sql.Node, error)
nc := *node
nc.Catalog = a.Catalog
return &nc, nil
case *plan.ShowCreateTable:
nc := *node
nc.Catalog = a.Catalog
nc.CurrentDatabase = a.Catalog.CurrentDatabase()
return &nc, nil
case *plan.ShowProcessList:
nc := *node
nc.Database = a.Catalog.CurrentDatabase()
Expand Down
8 changes: 5 additions & 3 deletions sql/parse/parse.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,12 @@ import (
"strconv"
"strings"

"gopkg.in/src-d/go-mysql-server.v0/sql/expression/function"

opentracing "github.com/opentracing/opentracing-go"
"github.com/opentracing/opentracing-go"
"github.com/sirupsen/logrus"
"gopkg.in/src-d/go-errors.v1"
"gopkg.in/src-d/go-mysql-server.v0/sql"
"gopkg.in/src-d/go-mysql-server.v0/sql/expression"
"gopkg.in/src-d/go-mysql-server.v0/sql/expression/function"
"gopkg.in/src-d/go-mysql-server.v0/sql/plan"
"gopkg.in/src-d/go-vitess.v1/vt/sqlparser"
)
Expand All @@ -39,6 +38,7 @@ var (
createIndexRegex = regexp.MustCompile(`^create\s+index\s+`)
dropIndexRegex = regexp.MustCompile(`^drop\s+index\s+`)
showIndexRegex = regexp.MustCompile(`^show\s+(index|indexes|keys)\s+(from|in)\s+\S+\s*`)
showCreateRegex = regexp.MustCompile(`^show create\s+\S+\s*`)
showVariablesRegex = regexp.MustCompile(`^show\s+(.*)?variables\s*`)
describeRegex = regexp.MustCompile(`^(describe|desc|explain)\s+(.*)\s+`)
fullProcessListRegex = regexp.MustCompile(`^show\s+(full\s+)?processlist$`)
Expand Down Expand Up @@ -70,6 +70,8 @@ func Parse(ctx *sql.Context, query string) (sql.Node, error) {
return parseDropIndex(s)
case showIndexRegex.MatchString(lowerQuery):
return parseShowIndex(s)
case showCreateRegex.MatchString(lowerQuery):
return parseShowCreate(s)
case showVariablesRegex.MatchString(lowerQuery):
return parseShowVariables(ctx, s)
case describeRegex.MatchString(lowerQuery):
Expand Down
48 changes: 48 additions & 0 deletions sql/parse/show_create.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package parse

import (
"bufio"
"strings"
)

import (
"gopkg.in/src-d/go-errors.v1"
"gopkg.in/src-d/go-mysql-server.v0/sql"
"gopkg.in/src-d/go-mysql-server.v0/sql/plan"
)

var errUnsupportedShowCreateQuery = errors.NewKind("Unsupported query: SHOW CREATE %s")

func parseShowCreate(s string) (sql.Node, error) {
r := bufio.NewReader(strings.NewReader(s))

var thingToShow string
var name string
steps := []parseFunc{
expect("show"),
skipSpaces,
expect("create"),
skipSpaces,
readIdent(&thingToShow),
skipSpaces,
readIdent(&name),
skipSpaces,
checkEOF,
}

for _, step := range steps {
if err := step(r); err != nil {
return nil, err
}
}

switch strings.ToLower(thingToShow) {
case "table":
return plan.NewShowCreateTable(
sql.UnresolvedDatabase("").Name(),
nil,
name), nil
default:
return nil, errUnsupportedShowCreateQuery.New(name)
}
}
56 changes: 56 additions & 0 deletions sql/parse/show_create_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package parse

import (
"testing"

"github.com/stretchr/testify/require"
"gopkg.in/src-d/go-errors.v1"
"gopkg.in/src-d/go-mysql-server.v0/sql"
"gopkg.in/src-d/go-mysql-server.v0/sql/plan"
)

func TestParseShowCreateTableQuery(t *testing.T) {
testCases := []struct {
query string
result sql.Node
err *errors.Kind
}{
{
"SHOW CREATE",
nil,
errUnsupportedShowCreateQuery,
},
{
"SHOW CREATE ANYTHING",
nil,
errUnsupportedShowCreateQuery,
},
{
"SHOW CREATE ASDF foo",
nil,
errUnsupportedShowCreateQuery,
},
{
"SHOW CREATE TABLE mytable",
plan.NewShowCreateTable(sql.UnresolvedDatabase("").Name(),
nil,
"mytable"),
nil,
},
}

for _, tt := range testCases {
t.Run(tt.query, func(t *testing.T) {
require := require.New(t)

result, err := parseShowCreate(tt.query)
if tt.err != nil {
require.Error(err)
require.True(tt.err.Is(err))
} else {
require.NoError(err)
require.Equal(tt.result, result)
}
})
}
}
143 changes: 143 additions & 0 deletions sql/plan/show_create_table.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
package plan

import (
"fmt"
"io"
"strings"

"gopkg.in/src-d/go-errors.v1"
"gopkg.in/src-d/go-mysql-server.v0/sql"
)

var ErrTableNotFound = errors.NewKind("Table `%s` not found")

// ShowCreateTable is a node that shows the CREATE TABLE statement for a table.
type ShowCreateTable struct {
Catalog *sql.Catalog
CurrentDatabase string
Table string
}

// Schema implements the Node interface.
func (n *ShowCreateTable) Schema() sql.Schema {
return sql.Schema{
&sql.Column{Name: "Table", Type: sql.Text, Nullable: false},
&sql.Column{Name: "Create Table", Type: sql.Text, Nullable: false},
}
}

// TransformExpressionsUp implements the Transformable interface.
func (n *ShowCreateTable) TransformExpressionsUp(f sql.TransformExprFunc) (sql.Node, error) {
return n, nil
}

// TransformUp implements the Transformable interface.
func (n *ShowCreateTable) TransformUp(f sql.TransformNodeFunc) (sql.Node, error) {
return f(NewShowCreateTable(n.CurrentDatabase, n.Catalog, n.Table))
}

// RowIter implements the Node interface
func (n *ShowCreateTable) RowIter(*sql.Context) (sql.RowIter, error) {
db, err := n.Catalog.Database(n.CurrentDatabase)

if err != nil {
return nil, sql.ErrDatabaseNotFound.New(n.CurrentDatabase)
}

return &showCreateTablesIter{
db: db,
table: n.Table,
}, nil
}

// String implements the Stringer interface.
func (n *ShowCreateTable) String() string {
return fmt.Sprintf("SHOW CREATE TABLE %s", n.Table)
}

type createTableStmt struct {
colName string
colType sql.Type
}

type showCreateTablesIter struct {
db sql.Database
table string

createStmt *createTableStmt
didIteration bool
}

func (i *showCreateTablesIter) Next() (sql.Row, error) {
if i.didIteration {
return nil, io.EOF
}

i.didIteration = true

table, found := i.db.Tables()[i.table]

if !found {
return nil, ErrTableNotFound.New(i.table)
}

composedCreateTableStatement := produceCreateStatement(table)

return sql.NewRow(
i.table, // "Table" string
composedCreateTableStatement, // "Create Table" string
), nil
}

func produceCreateStatement(table sql.Table) string {
schema := table.Schema()
colCreateStatements := make([]string, len(schema), len(schema))

// Statement creation parts for each column
for indx, col := range schema {
createStmtPart := fmt.Sprintf("`%s` %s", col.Name, col.Type.Type())

if !col.Nullable {
createStmtPart = fmt.Sprintf("%s NOT NULL", createStmtPart)
}

switch def := col.Default.(type) {
case string:
if def != "" {
createStmtPart = fmt.Sprintf("%s DEFAULT %s", createStmtPart, def)
}
default:
if def != nil {
createStmtPart = fmt.Sprintf("%s DEFAULT %v", createStmtPart, col.Default)
}
}

colCreateStatements[indx] = createStmtPart
}

prettyColCreateStmts := fmt.Sprintf("%s", strings.Join(colCreateStatements, ",\n"))
composedCreateTableStatement :=
fmt.Sprintf("CREATE TABLE `%s` (%s) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4", table.Name(), prettyColCreateStmts)

return composedCreateTableStatement
}

func (i *showCreateTablesIter) Close() error {
return nil
}

// NewShowCreateTable creates a new ShowCreateTable node.
func NewShowCreateTable(db string, ctl *sql.Catalog, table string) sql.Node {
return &ShowCreateTable{
CurrentDatabase: db,
Table: table,
Catalog: ctl}
}

// Resolved implements the Resolvable interface.
func (n *ShowCreateTable) Resolved() bool {
return true
}

// Children implements the Node interface.
func (n *ShowCreateTable) Children() []sql.Node { return nil }
Loading

0 comments on commit b830c9a

Please sign in to comment.