From 6489b01e33bbe0ad9bcad8653885b355bd1805ce Mon Sep 17 00:00:00 2001 From: Miguel Molina Date: Thu, 18 Oct 2018 17:09:53 +0200 Subject: [PATCH] *: remaining fixes to make mysqldump work with go-mysql-server Signed-off-by: Miguel Molina --- _example/main.go | 7 +- engine_test.go | 57 ++++++------ information_schema.go | 153 -------------------------------- sql/analyzer/resolve_columns.go | 57 +++++++----- sql/catalog.go | 3 +- sql/information_schema.go | 151 +++++++++++++++++++++++++++++++ sql/parse/lock.go | 4 +- sql/parse/parse.go | 14 ++- sql/parse/parse_test.go | 40 ++++++++- sql/parse/show_create.go | 40 ++++++--- sql/parse/util.go | 35 ++++++++ sql/parse/variables.go | 19 +++- sql/plan/showdatabases.go | 10 ++- sql/session.go | 2 +- sql/type.go | 4 +- 15 files changed, 356 insertions(+), 240 deletions(-) delete mode 100644 information_schema.go create mode 100644 sql/information_schema.go diff --git a/_example/main.go b/_example/main.go index 63cd882c1..4d721df07 100644 --- a/_example/main.go +++ b/_example/main.go @@ -24,8 +24,9 @@ import ( // +----------+-------------------+-------------------------------+---------------------+ // ``` func main() { - driver := sqle.NewDefault() - driver.AddDatabase(createTestDatabase()) + engine := sqle.NewDefault() + engine.AddDatabase(createTestDatabase()) + engine.AddDatabase(sql.NewInformationSchemaDB()) auth := mysql.NewAuthServerStatic() auth.Entries["user"] = []*mysql.AuthServerStaticEntry{{ @@ -38,7 +39,7 @@ func main() { Auth: auth, } - s, err := server.NewDefaultServer(config, driver) + s, err := server.NewDefaultServer(config, engine) if err != nil { panic(err) } diff --git a/engine_test.go b/engine_test.go index 49826275d..100419ed7 100644 --- a/engine_test.go +++ b/engine_test.go @@ -353,7 +353,7 @@ var queries = []struct { }, { `SHOW DATABASES`, - []sql.Row{{"mydb"}, {"foo"}, {"information_schema"}}, + []sql.Row{{"mydb"}, {"foo"}}, }, { `SELECT s FROM mytable WHERE s LIKE '%d row'`, @@ -447,7 +447,7 @@ var queries = []struct { {"max_allowed_packet", math.MaxInt32}, {"sql_mode", ""}, {"gtid_mode", int32(0)}, - {"ndbinfo_version", ""}, + {"collation_database", "utf8_bin"}, }, }, { @@ -479,42 +479,35 @@ var queries = []struct { }, { ` - SELECT DISTINCT - tablespace_name, file_name, logfile_group_name, extent_size, initial_size, engine - FROM - information_schema.files - WHERE - file_type = 'DATAFILE' - ORDER BY tablespace_name, logfile_group_name + SELECT + LOGFILE_GROUP_NAME, FILE_NAME, TOTAL_EXTENTS, INITIAL_SIZE, ENGINE, EXTRA + FROM INFORMATION_SCHEMA.FILES + WHERE FILE_TYPE = 'UNDO LOG' + AND FILE_NAME IS NOT NULL + AND LOGFILE_GROUP_NAME IS NOT NULL + GROUP BY LOGFILE_GROUP_NAME, FILE_NAME, ENGINE, TOTAL_EXTENTS, INITIAL_SIZE + ORDER BY LOGFILE_GROUP_NAME `, []sql.Row{}, }, { ` - SELECT - logfile_group_name, file_name, total_extents, initial_size, engine, extra - FROM - information_schema.files - WHERE - file_type = 'UNDO LOG' AND - file_name IS NOT NULL AND - logfile_group_name IS NOT NULL - GROUP BY - logfile_group_name, file_name, engine, total_extents, initial_size - ORDER BY - logfile_group_name + SELECT DISTINCT + TABLESPACE_NAME, FILE_NAME, LOGFILE_GROUP_NAME, EXTENT_SIZE, INITIAL_SIZE, ENGINE + FROM INFORMATION_SCHEMA.FILES + WHERE FILE_TYPE = 'DATAFILE' + ORDER BY TABLESPACE_NAME, LOGFILE_GROUP_NAME `, []sql.Row{}, }, { ` SELECT - column_name - FROM - information_schema.column_statistics - WHERE - schema_name = 'foo' AND - table_name = 'bar'; + COLUMN_NAME, + JSON_EXTRACT(HISTOGRAM, '$."number-of-buckets-specified"') + FROM information_schema.COLUMN_STATISTICS + WHERE SCHEMA_NAME = 'mydb' + AND TABLE_NAME = 'mytable' `, []sql.Row{}, }, @@ -536,9 +529,11 @@ var queries = []struct { []sql.Row{}, }, { - ` - SHOW WARNINGS LIMIT 0 - `, + `SHOW WARNINGS LIMIT 0`, + []sql.Row{}, + }, + { + `SET SESSION NET_READ_TIMEOUT= 700, SESSION NET_WRITE_TIMEOUT= 700`, []sql.Row{}, }, } @@ -1130,7 +1125,7 @@ func newEngineWithParallelism(t *testing.T, parallelism int) *sqle.Engine { catalog := sql.NewCatalog() catalog.AddDatabase(db) catalog.AddDatabase(db2) - catalog.AddDatabase(sqle.NewInformationSchemaDB()) + catalog.AddDatabase(sql.NewInformationSchemaDB()) var a *analyzer.Analyzer if parallelism > 1 { diff --git a/information_schema.go b/information_schema.go deleted file mode 100644 index 237b15303..000000000 --- a/information_schema.go +++ /dev/null @@ -1,153 +0,0 @@ -package sqle // import "gopkg.in/src-d/go-mysql-server.v0" - -import ( - "fmt" - "io" - - "gopkg.in/src-d/go-mysql-server.v0/sql" -) - -const ( - // InformationSchemaDBName is the name of the information schema table. - InformationSchemaDBName = "information_schema" - // FilesTableName is the name of the files table. - FilesTableName = "files" - // ColumnStatisticsTableName is the name of the column statistics table. - ColumnStatisticsTableName = "column_statistics" -) - -type informationSchemaDB struct { - name string - tables map[string]sql.Table -} - -var _ sql.Database = (*informationSchemaDB)(nil) - -// NewInformationSchemaDB creates a new INFORMATION_SCHEMA Database. -func NewInformationSchemaDB() sql.Database { - return &informationSchemaDB{ - name: InformationSchemaDBName, - tables: map[string]sql.Table{ - FilesTableName: newFilesTable(), - ColumnStatisticsTableName: newColumnStatisticsTable(), - }, - } -} - -// Name implements the sql.Database interface. -func (i *informationSchemaDB) Name() string { return i.name } - -// Tables implements the sql.Database interface. -func (i *informationSchemaDB) Tables() map[string]sql.Table { return i.tables } - -func newFilesTable() sql.Table { - return &emptyTable{ - name: FilesTableName, - schema: filesSchema, - } -} - -var filesSchema = sql.Schema{ - &sql.Column{Name: "file_id", Type: sql.Int64, Source: FilesTableName, Nullable: true}, - &sql.Column{Name: "file_name", Type: sql.Text, Source: FilesTableName, Nullable: true}, - &sql.Column{Name: "file_type", Type: sql.Text, Source: FilesTableName, Nullable: true}, - &sql.Column{Name: "tablespace_name", Type: sql.Text, Source: FilesTableName}, - &sql.Column{Name: "table_catalog", Type: sql.Text, Source: FilesTableName}, - &sql.Column{Name: "table_schema", Type: sql.Blob, Source: FilesTableName, Nullable: true}, - &sql.Column{Name: "table_name", Type: sql.Blob, Source: FilesTableName, Nullable: true}, - &sql.Column{Name: "logfile_group_name", Type: sql.Text, Source: FilesTableName, Nullable: true}, - &sql.Column{Name: "logfile_group_number", Type: sql.Int64, Source: FilesTableName, Nullable: true}, - &sql.Column{Name: "engine", Type: sql.Text, Source: FilesTableName}, - &sql.Column{Name: "fulltext_keys", Type: sql.Blob, Source: FilesTableName, Nullable: true}, - &sql.Column{Name: "deleted_rows", Type: sql.Blob, Source: FilesTableName, Nullable: true}, - &sql.Column{Name: "update_count", Type: sql.Blob, Source: FilesTableName, Nullable: true}, - &sql.Column{Name: "free_extents", Type: sql.Int64, Source: FilesTableName, Nullable: true}, - &sql.Column{Name: "total_extents", Type: sql.Int64, Source: FilesTableName, Nullable: true}, - &sql.Column{Name: "extent_size", Type: sql.Int64, Source: FilesTableName, Nullable: true}, - &sql.Column{Name: "initial_size", Type: sql.Int64, Source: FilesTableName, Nullable: true}, - &sql.Column{Name: "maximum_size", Type: sql.Int64, Source: FilesTableName, Nullable: true}, - &sql.Column{Name: "autoextend_size", Type: sql.Int64, Source: FilesTableName, Nullable: true}, - &sql.Column{Name: "creation_time", Type: sql.Blob, Source: FilesTableName, Nullable: true}, - &sql.Column{Name: "last_update_time", Type: sql.Blob, Source: FilesTableName, Nullable: true}, - &sql.Column{Name: "last_access_time", Type: sql.Blob, Source: FilesTableName, Nullable: true}, - &sql.Column{Name: "recover_time", Type: sql.Blob, Source: FilesTableName, Nullable: true}, - &sql.Column{Name: "transaction_counter", Type: sql.Blob, Source: FilesTableName, Nullable: true}, - &sql.Column{Name: "version", Type: sql.Int64, Source: FilesTableName, Nullable: true}, - &sql.Column{Name: "row_format", Type: sql.Text, Source: FilesTableName, Nullable: true}, - &sql.Column{Name: "table_rows", Type: sql.Blob, Source: FilesTableName, Nullable: true}, - &sql.Column{Name: "avg_row_length", Type: sql.Blob, Source: FilesTableName, Nullable: true}, - &sql.Column{Name: "data_length", Type: sql.Blob, Source: FilesTableName, Nullable: true}, - &sql.Column{Name: "max_data_length", Type: sql.Blob, Source: FilesTableName, Nullable: true}, - &sql.Column{Name: "index_length", Type: sql.Blob, Source: FilesTableName, Nullable: true}, - &sql.Column{Name: "data_free", Type: sql.Int64, Source: FilesTableName, Nullable: true}, - &sql.Column{Name: "create_time", Type: sql.Blob, Source: FilesTableName, Nullable: true}, - &sql.Column{Name: "update_time", Type: sql.Blob, Source: FilesTableName, Nullable: true}, - &sql.Column{Name: "check_time", Type: sql.Blob, Source: FilesTableName, Nullable: true}, - &sql.Column{Name: "checksum", Type: sql.Blob, Source: FilesTableName, Nullable: true}, - &sql.Column{Name: "status", Type: sql.Text, Source: FilesTableName, Nullable: true}, - &sql.Column{Name: "extra", Type: sql.Blob, Source: FilesTableName, Nullable: true}, -} - -func newColumnStatisticsTable() sql.Table { - return &emptyTable{ - name: ColumnStatisticsTableName, - schema: columnStatisticsSchema, - } -} - -var columnStatisticsSchema = sql.Schema{ - &sql.Column{Name: "schema_name", Type: sql.Text, Source: ColumnStatisticsTableName}, - &sql.Column{Name: "table_name", Type: sql.Text, Source: ColumnStatisticsTableName}, - &sql.Column{Name: "column_name", Type: sql.Text, Source: ColumnStatisticsTableName}, - &sql.Column{Name: "histogram", Type: sql.JSON, Source: ColumnStatisticsTableName}, -} - -type emptyTable struct { - name string - schema sql.Schema -} - -var _ sql.Table = (*emptyTable)(nil) - -// Name implements the sql.Table interface. -func (t *emptyTable) Name() string { return t.name } - -// String implements the sql.Table interface. -func (t *emptyTable) String() string { return printTable(t.name, t.schema) } - -// Schema implements the sql.Table interface. -func (t *emptyTable) Schema() sql.Schema { return t.schema } - -// Partitions implements the sql.Table interface. -func (t *emptyTable) Partitions(_ *sql.Context) (sql.PartitionIter, error) { - return new(partitionIter), nil -} - -// PartitionRows implements the sql.Table interface. -func (t *emptyTable) PartitionRows(_ *sql.Context, _ sql.Partition) (sql.RowIter, error) { - return sql.RowsToRowIter(), nil -} - -func printTable(name string, tableSchema sql.Schema) string { - p := sql.NewTreePrinter() - _ = p.WriteNode("Table(%s)", name) - var schema = make([]string, len(tableSchema)) - for i, col := range tableSchema { - schema[i] = fmt.Sprintf( - "Column(%s, %s, nullable=%v)", - col.Name, - col.Type.Type().String(), - col.Nullable, - ) - } - _ = p.WriteChildren(schema...) - return p.String() -} - -type partitionIter struct{} - -var _ sql.PartitionIter = (*partitionIter)(nil) - -func (p *partitionIter) Next() (sql.Partition, error) { return nil, io.EOF } - -func (p *partitionIter) Close() error { return nil } diff --git a/sql/analyzer/resolve_columns.go b/sql/analyzer/resolve_columns.go index 3ddd55fa2..e29a26189 100644 --- a/sql/analyzer/resolve_columns.go +++ b/sql/analyzer/resolve_columns.go @@ -46,7 +46,8 @@ func qualifyColumns(ctx *sql.Context, a *Analyzer, n sql.Node) (sql.Node, error) indexCols := func(table string, schema sql.Schema) { for _, col := range schema { - colIndex[col.Name] = append(colIndex[col.Name], table) + name := strings.ToLower(col.Name) + colIndex[name] = append(colIndex[name], strings.ToLower(table)) } } @@ -64,14 +65,14 @@ func qualifyColumns(ctx *sql.Context, a *Analyzer, n sql.Node) (sql.Node, error) case *plan.TableAlias: switch t := n.Child.(type) { case *plan.ResolvedTable, *plan.UnresolvedTable: - name := t.(sql.Nameable).Name() - tableAliases[n.Name()] = name + name := strings.ToLower(t.(sql.Nameable).Name()) + tableAliases[strings.ToLower(n.Name())] = name default: - tables[n.Name()] = n.Child + tables[strings.ToLower(n.Name())] = n.Child indexCols(n.Name(), n.Schema()) } case *plan.ResolvedTable, *plan.SubqueryAlias: - name := n.(sql.Nameable).Name() + name := strings.ToLower(n.(sql.Nameable).Name()) tables[name] = n indexCols(name, n.Schema()) } @@ -87,8 +88,10 @@ func qualifyColumns(ctx *sql.Context, a *Analyzer, n sql.Node) (sql.Node, error) col = expression.NewUnresolvedQualifiedColumn(col.Table(), col.Name()) - if col.Table() == "" { - tables := dedupStrings(colIndex[col.Name()]) + name := strings.ToLower(col.Name()) + table := strings.ToLower(col.Table()) + if table == "" { + tables := dedupStrings(colIndex[name]) switch len(tables) { case 0: // If there are no tables that have any column with the column @@ -104,7 +107,7 @@ func qualifyColumns(ctx *sql.Context, a *Analyzer, n sql.Node) (sql.Node, error) return nil, ErrAmbiguousColumnName.New(col.Name(), strings.Join(tables, ", ")) } } else { - if real, ok := tableAliases[col.Table()]; ok { + if real, ok := tableAliases[table]; ok { col = expression.NewUnresolvedQualifiedColumn( real, col.Name(), @@ -120,11 +123,11 @@ func qualifyColumns(ctx *sql.Context, a *Analyzer, n sql.Node) (sql.Node, error) return col, nil case *expression.Star: if col.Table != "" { - if real, ok := tableAliases[col.Table]; ok { + if real, ok := tableAliases[strings.ToLower(col.Table)]; ok { col = expression.NewQualifiedStar(real) } - if _, ok := tables[col.Table]; !ok { + if _, ok := tables[strings.ToLower(col.Table)]; !ok { return nil, sql.ErrTableNotFound.New(col.Table) } @@ -174,6 +177,8 @@ func qualifyColumns(ctx *sql.Context, a *Analyzer, n sql.Node) (sql.Node, error) continue } + col = strings.ToLower(col) + table = strings.ToLower(table) if table != "" { projected[col] = append(projected[col], table) } else { @@ -211,7 +216,8 @@ func resolveColumns(ctx *sql.Context, a *Analyzer, n sql.Node) (sql.Node, error) } for _, col := range child.Schema() { - colMap[col.Name] = append(colMap[col.Name], col) + name := strings.ToLower(col.Name) + colMap[name] = append(colMap[name], col) } } @@ -222,7 +228,7 @@ func resolveColumns(ctx *sql.Context, a *Analyzer, n sql.Node) (sql.Node, error) if project, ok := n.(*plan.Project); ok { for _, e := range project.Projections { if alias, ok := e.(*expression.Alias); ok { - aliasMap[alias.Name()] = exists + aliasMap[strings.ToLower(alias.Name())] = exists } } } @@ -256,12 +262,14 @@ func resolveColumns(ctx *sql.Context, a *Analyzer, n sql.Node) (sql.Node, error) sessionPrefix = sqlparser.SessionStr + "." globalPrefix = sqlparser.GlobalStr + "." ) - columns, ok := colMap[uc.Name()] + name := strings.ToLower(uc.Name()) + table := strings.ToLower(uc.Table()) + columns, ok := colMap[name] if !ok { switch uc := uc.(type) { case *expression.UnresolvedColumn: if isGlobalOrSessionColumn(uc) { - if uc.Table() != "" && strings.ToLower(uc.Table()) != sessionTable { + if table != "" && table != sessionTable { return nil, errGlobalVariablesNotSupported.New(uc) } @@ -279,11 +287,11 @@ func resolveColumns(ctx *sql.Context, a *Analyzer, n sql.Node) (sql.Node, error) return &deferredColumn{uc}, nil default: - if uc.Table() != "" { + if table != "" { return nil, ErrColumnTableNotFound.New(uc.Table(), uc.Name()) } - if _, ok := aliasMap[uc.Name()]; ok { + if _, ok := aliasMap[name]; ok { // no nested aliases return nil, ErrMisusedAlias.New(uc.Name()) } @@ -295,7 +303,7 @@ func resolveColumns(ctx *sql.Context, a *Analyzer, n sql.Node) (sql.Node, error) var col *sql.Column var found bool for _, c := range columns { - if c.Source == uc.Table() { + if strings.ToLower(c.Source) == table { col = c found = true break @@ -303,7 +311,7 @@ func resolveColumns(ctx *sql.Context, a *Analyzer, n sql.Node) (sql.Node, error) } if !found { - if uc.Table() != "" { + if table != "" { return nil, ErrColumnTableNotFound.New(uc.Table(), uc.Name()) } @@ -367,7 +375,7 @@ func resolveGroupingColumns(ctx *sql.Context, a *Analyzer, n sql.Node) (sql.Node var groupingColumns = make(map[string]struct{}) for _, g := range g.Grouping { for _, n := range findAllColumns(g) { - groupingColumns[n] = struct{}{} + groupingColumns[strings.ToLower(n)] = struct{}{} } } @@ -376,13 +384,13 @@ func resolveGroupingColumns(ctx *sql.Context, a *Analyzer, n sql.Node) (sql.Node // This alias is going to be pushed down, so don't bother gathering // its requirements. if alias, ok := agg.(*expression.Alias); ok { - if _, ok := groupingColumns[alias.Name()]; ok { + if _, ok := groupingColumns[strings.ToLower(alias.Name())]; ok { continue } } for _, n := range findAllColumns(agg) { - aggregateColumns[n] = struct{}{} + aggregateColumns[strings.ToLower(n)] = struct{}{} } } @@ -402,14 +410,15 @@ func resolveGroupingColumns(ctx *sql.Context, a *Analyzer, n sql.Node) (sql.Node continue } + name := strings.ToLower(alias.Name()) // Only if the alias is required in the grouping set needsReorder // to true. If it's not required, there's no need for a reorder if // no other alias is required. - _, ok = groupingColumns[alias.Name()] + _, ok = groupingColumns[name] if ok { - aliases[alias.Name()] = len(newAggregate) + aliases[name] = len(newAggregate) needsReorder = true - delete(groupingColumns, alias.Name()) + delete(groupingColumns, name) projection = append(projection, a) newAggregate = append(newAggregate, expression.NewUnresolvedColumn(alias.Name())) diff --git a/sql/catalog.go b/sql/catalog.go index edc8b0487..12683343b 100644 --- a/sql/catalog.go +++ b/sql/catalog.go @@ -93,8 +93,9 @@ type Databases []Database // Database returns the Database with the given name if it exists. func (d Databases) Database(name string) (Database, error) { + name = strings.ToLower(name) for _, db := range d { - if db.Name() == name { + if strings.ToLower(db.Name()) == name { return db, nil } } diff --git a/sql/information_schema.go b/sql/information_schema.go new file mode 100644 index 000000000..df5023066 --- /dev/null +++ b/sql/information_schema.go @@ -0,0 +1,151 @@ +package sql // import "gopkg.in/src-d/go-mysql-server.v0/sql" + +import ( + "fmt" + "io" +) + +const ( + // InformationSchemaDBName is the name of the information schema table. + InformationSchemaDBName = "information_schema" + // FilesTableName is the name of the files table. + FilesTableName = "files" + // ColumnStatisticsTableName is the name of the column statistics table. + ColumnStatisticsTableName = "column_statistics" +) + +type informationSchemaDB struct { + name string + tables map[string]Table +} + +var _ Database = (*informationSchemaDB)(nil) + +// NewInformationSchemaDB creates a new INFORMATION_SCHEMA Database. +func NewInformationSchemaDB() Database { + return &informationSchemaDB{ + name: InformationSchemaDBName, + tables: map[string]Table{ + FilesTableName: newFilesTable(), + ColumnStatisticsTableName: newColumnStatisticsTable(), + }, + } +} + +// Name implements the Database interface. +func (i *informationSchemaDB) Name() string { return i.name } + +// Tables implements the Database interface. +func (i *informationSchemaDB) Tables() map[string]Table { return i.tables } + +func newFilesTable() Table { + return &emptyTable{ + name: FilesTableName, + schema: filesSchema, + } +} + +var filesSchema = Schema{ + &Column{Name: "file_id", Type: Int64, Source: FilesTableName, Nullable: true}, + &Column{Name: "file_name", Type: Text, Source: FilesTableName, Nullable: true}, + &Column{Name: "file_type", Type: Text, Source: FilesTableName, Nullable: true}, + &Column{Name: "tablespace_name", Type: Text, Source: FilesTableName}, + &Column{Name: "table_catalog", Type: Text, Source: FilesTableName}, + &Column{Name: "table_schema", Type: Blob, Source: FilesTableName, Nullable: true}, + &Column{Name: "table_name", Type: Blob, Source: FilesTableName, Nullable: true}, + &Column{Name: "logfile_group_name", Type: Text, Source: FilesTableName, Nullable: true}, + &Column{Name: "logfile_group_number", Type: Int64, Source: FilesTableName, Nullable: true}, + &Column{Name: "engine", Type: Text, Source: FilesTableName}, + &Column{Name: "fulltext_keys", Type: Blob, Source: FilesTableName, Nullable: true}, + &Column{Name: "deleted_rows", Type: Blob, Source: FilesTableName, Nullable: true}, + &Column{Name: "update_count", Type: Blob, Source: FilesTableName, Nullable: true}, + &Column{Name: "free_extents", Type: Int64, Source: FilesTableName, Nullable: true}, + &Column{Name: "total_extents", Type: Int64, Source: FilesTableName, Nullable: true}, + &Column{Name: "extent_size", Type: Int64, Source: FilesTableName, Nullable: true}, + &Column{Name: "initial_size", Type: Int64, Source: FilesTableName, Nullable: true}, + &Column{Name: "maximum_size", Type: Int64, Source: FilesTableName, Nullable: true}, + &Column{Name: "autoextend_size", Type: Int64, Source: FilesTableName, Nullable: true}, + &Column{Name: "creation_time", Type: Blob, Source: FilesTableName, Nullable: true}, + &Column{Name: "last_update_time", Type: Blob, Source: FilesTableName, Nullable: true}, + &Column{Name: "last_access_time", Type: Blob, Source: FilesTableName, Nullable: true}, + &Column{Name: "recover_time", Type: Blob, Source: FilesTableName, Nullable: true}, + &Column{Name: "transaction_counter", Type: Blob, Source: FilesTableName, Nullable: true}, + &Column{Name: "version", Type: Int64, Source: FilesTableName, Nullable: true}, + &Column{Name: "row_format", Type: Text, Source: FilesTableName, Nullable: true}, + &Column{Name: "table_rows", Type: Blob, Source: FilesTableName, Nullable: true}, + &Column{Name: "avg_row_length", Type: Blob, Source: FilesTableName, Nullable: true}, + &Column{Name: "data_length", Type: Blob, Source: FilesTableName, Nullable: true}, + &Column{Name: "max_data_length", Type: Blob, Source: FilesTableName, Nullable: true}, + &Column{Name: "index_length", Type: Blob, Source: FilesTableName, Nullable: true}, + &Column{Name: "data_free", Type: Int64, Source: FilesTableName, Nullable: true}, + &Column{Name: "create_time", Type: Blob, Source: FilesTableName, Nullable: true}, + &Column{Name: "update_time", Type: Blob, Source: FilesTableName, Nullable: true}, + &Column{Name: "check_time", Type: Blob, Source: FilesTableName, Nullable: true}, + &Column{Name: "checksum", Type: Blob, Source: FilesTableName, Nullable: true}, + &Column{Name: "status", Type: Text, Source: FilesTableName, Nullable: true}, + &Column{Name: "extra", Type: Blob, Source: FilesTableName, Nullable: true}, +} + +func newColumnStatisticsTable() Table { + return &emptyTable{ + name: ColumnStatisticsTableName, + schema: columnStatisticsSchema, + } +} + +var columnStatisticsSchema = Schema{ + &Column{Name: "schema_name", Type: Text, Source: ColumnStatisticsTableName}, + &Column{Name: "table_name", Type: Text, Source: ColumnStatisticsTableName}, + &Column{Name: "column_name", Type: Text, Source: ColumnStatisticsTableName}, + &Column{Name: "histogram", Type: JSON, Source: ColumnStatisticsTableName}, +} + +type emptyTable struct { + name string + schema Schema +} + +var _ Table = (*emptyTable)(nil) + +// Name implements the Table interface. +func (t *emptyTable) Name() string { return t.name } + +// String implements the Table interface. +func (t *emptyTable) String() string { return printTable(t.name, t.schema) } + +// Schema implements the Table interface. +func (t *emptyTable) Schema() Schema { return t.schema } + +// Partitions implements the Table interface. +func (t *emptyTable) Partitions(_ *Context) (PartitionIter, error) { + return new(partitionIter), nil +} + +// PartitionRows implements the Table interface. +func (t *emptyTable) PartitionRows(_ *Context, _ Partition) (RowIter, error) { + return RowsToRowIter(), nil +} + +func printTable(name string, tableSchema Schema) string { + p := NewTreePrinter() + _ = p.WriteNode("Table(%s)", name) + var schema = make([]string, len(tableSchema)) + for i, col := range tableSchema { + schema[i] = fmt.Sprintf( + "Column(%s, %s, nullable=%v)", + col.Name, + col.Type.Type().String(), + col.Nullable, + ) + } + _ = p.WriteChildren(schema...) + return p.String() +} + +type partitionIter struct{} + +var _ PartitionIter = (*partitionIter)(nil) + +func (p *partitionIter) Next() (Partition, error) { return nil, io.EOF } + +func (p *partitionIter) Close() error { return nil } diff --git a/sql/parse/lock.go b/sql/parse/lock.go index d63764660..a61118f58 100644 --- a/sql/parse/lock.go +++ b/sql/parse/lock.go @@ -2,6 +2,7 @@ package parse import ( "bufio" + "fmt" "io" "strings" @@ -10,6 +11,7 @@ import ( ) func parseLockTables(ctx *sql.Context, query string) (sql.Node, error) { + fmt.Println(query) var r = bufio.NewReader(strings.NewReader(query)) var tables []*plan.TableLock err := parseFuncs{ @@ -70,7 +72,7 @@ func readTableLock(rd *bufio.Reader) (*plan.TableLock, error) { var write bool err := parseFuncs{ - readIdent(&tableName), + readQuotableIdent(&tableName), skipSpaces, maybeReadAlias, skipSpaces, diff --git a/sql/parse/parse.go b/sql/parse/parse.go index 2742193f5..dd1867e68 100644 --- a/sql/parse/parse.go +++ b/sql/parse/parse.go @@ -44,6 +44,7 @@ var ( fullProcessListRegex = regexp.MustCompile(`^show\s+(full\s+)?processlist$`) unlockTablesRegex = regexp.MustCompile(`^unlock\s+tables$`) lockTablesRegex = regexp.MustCompile(`^lock\s+tables\s`) + setRegex = regexp.MustCompile(`^set\s+`) ) // Parse parses the given SQL sentence and returns the corresponding node. @@ -86,6 +87,8 @@ func Parse(ctx *sql.Context, query string) (sql.Node, error) { return plan.NewUnlockTables(), nil case lockTablesRegex.MatchString(lowerQuery): return parseLockTables(ctx, s) + case setRegex.MatchString(lowerQuery): + s = fixSetQuery(s) } stmt, err := sqlparser.Parse(s) @@ -1070,7 +1073,7 @@ func parseShowTableStatus(query string) (sql.Node, error) { switch strings.ToUpper(clause) { case "FROM", "IN": var db string - if err := readIdent(&db)(buf); err != nil { + if err := readQuotableIdent(&db)(buf); err != nil { return nil, err } @@ -1104,3 +1107,12 @@ func parseShowTableStatus(query string) (sql.Node, error) { return nil, errUnexpectedSyntax.New("one of: FROM, IN, LIKE or WHERE", clause) } } + +var fixSessionRegex = regexp.MustCompile(`(,\s*|(set|SET)\s+)(SESSION|session)\s+([a-zA-Z0-9_]+)\s*=`) +var fixGlobalRegex = regexp.MustCompile(`(,\s*|(set|SET)\s+)(GLOBAL|global)\s+([a-zA-Z0-9_]+)\s*=`) + +func fixSetQuery(s string) string { + s = fixSessionRegex.ReplaceAllString(s, `$1@@session.$4 =`) + s = fixGlobalRegex.ReplaceAllString(s, `$1@@global.$4 =`) + return s +} diff --git a/sql/parse/parse_test.go b/sql/parse/parse_test.go index a2742232e..cce64a83a 100644 --- a/sql/parse/parse_test.go +++ b/sql/parse/parse_test.go @@ -719,6 +719,16 @@ var fixtures = map[string]sql.Node{ Value: expression.NewLiteral(false, sql.Boolean), }, ), + `SET SESSION NET_READ_TIMEOUT= 700, SESSION NET_WRITE_TIMEOUT= 700`: plan.NewSet( + plan.SetVariable{ + Name: "@@session.net_read_timeout", + Value: expression.NewLiteral(int64(700), sql.Int64), + }, + plan.SetVariable{ + Name: "@@session.net_write_timeout", + Value: expression.NewLiteral(int64(700), sql.Int64), + }, + ), `/*!40101 SET NAMES utf8 */`: plan.Nothing, `SELECT /*!40101 SET NAMES utf8 */ * FROM foo`: plan.NewProject( []sql.Expression{ @@ -820,6 +830,10 @@ var fixtures = map[string]sql.Node{ {Table: plan.NewUnresolvedTable("foo", ""), Write: true}, {Table: plan.NewUnresolvedTable("bar", "")}, }), + "LOCK TABLES `foo` WRITE, `bar` READ": plan.NewLockTables([]*plan.TableLock{ + {Table: plan.NewUnresolvedTable("foo", ""), Write: true}, + {Table: plan.NewUnresolvedTable("bar", "")}, + }), `LOCK TABLES foo READ, bar WRITE, baz READ`: plan.NewLockTables([]*plan.TableLock{ {Table: plan.NewUnresolvedTable("foo", "")}, {Table: plan.NewUnresolvedTable("bar", ""), Write: true}, @@ -837,9 +851,13 @@ var fixtures = map[string]sql.Node{ }, plan.NewUnresolvedTable("mytable", ""), ), - `SHOW WARNINGS`: plan.NewOffset(0, plan.ShowWarnings(sql.NewEmptyContext().Warnings())), - `SHOW WARNINGS LIMIT 10`: plan.NewLimit(10, plan.NewOffset(0, plan.ShowWarnings(sql.NewEmptyContext().Warnings()))), - `SHOW WARNINGS LIMIT 5,10`: plan.NewLimit(10, plan.NewOffset(5, plan.ShowWarnings(sql.NewEmptyContext().Warnings()))), + `SHOW WARNINGS`: plan.NewOffset(0, plan.ShowWarnings(sql.NewEmptyContext().Warnings())), + `SHOW WARNINGS LIMIT 10`: plan.NewLimit(10, plan.NewOffset(0, plan.ShowWarnings(sql.NewEmptyContext().Warnings()))), + `SHOW WARNINGS LIMIT 5,10`: plan.NewLimit(10, plan.NewOffset(5, plan.ShowWarnings(sql.NewEmptyContext().Warnings()))), + "SHOW CREATE DATABASE `foo`": plan.NewShowCreateDatabase(sql.UnresolvedDatabase("foo"), false), + "SHOW CREATE SCHEMA `foo`": plan.NewShowCreateDatabase(sql.UnresolvedDatabase("foo"), false), + "SHOW CREATE DATABASE IF NOT EXISTS `foo`": plan.NewShowCreateDatabase(sql.UnresolvedDatabase("foo"), true), + "SHOW CREATE SCHEMA IF NOT EXISTS `foo`": plan.NewShowCreateDatabase(sql.UnresolvedDatabase("foo"), true), } func TestParse(t *testing.T) { @@ -922,3 +940,19 @@ func TestRemoveComments(t *testing.T) { }) } } + +func TestFixSetQuery(t *testing.T) { + testCases := []struct { + in, out string + }{ + {"set session foo = 1, session bar = 2", "set @@session.foo = 1, @@session.bar = 2"}, + {"set global foo = 1, session bar = 2", "set @@global.foo = 1, @@session.bar = 2"}, + {"set SESSION foo = 1, GLOBAL bar = 2", "set @@session.foo = 1, @@global.bar = 2"}, + } + + for _, tt := range testCases { + t.Run(tt.in, func(t *testing.T) { + require.Equal(t, tt.out, fixSetQuery(tt.in)) + }) + } +} diff --git a/sql/parse/show_create.go b/sql/parse/show_create.go index 6bfd172e6..68ea2ee7b 100644 --- a/sql/parse/show_create.go +++ b/sql/parse/show_create.go @@ -34,7 +34,7 @@ func parseShowCreate(s string) (sql.Node, error) { var name string err := parseFuncs{ - readIdent(&name), + readQuotableIdent(&name), skipSpaces, checkEOF, }.exec(r) @@ -49,23 +49,37 @@ func parseShowCreate(s string) (sql.Node, error) { case "database", "schema": var ifNotExists bool var next string - if err := readIdent(&next)(r); err != nil { + + nextByte, err := r.Peek(1) + if err != nil { return nil, err } - if next == "if" { - ifNotExists = true - err := parseFuncs{ - skipSpaces, - expect("not"), - skipSpaces, - expect("exists"), - skipSpaces, - readIdent(&next), - }.exec(r) - if err != nil { + // If ` is the next character, it's a db name. Otherwise it may be + // a table name or IF NOT EXISTS. + if nextByte[0] == '`' { + if err := readQuotableIdent(&next)(r); err != nil { + return nil, err + } + } else { + if err := readIdent(&next)(r); err != nil { return nil, err } + + if next == "if" { + ifNotExists = true + err := parseFuncs{ + skipSpaces, + expect("not"), + skipSpaces, + expect("exists"), + skipSpaces, + readQuotableIdent(&next), + }.exec(r) + if err != nil { + return nil, err + } + } } err = parseFuncs{ diff --git a/sql/parse/util.go b/sql/parse/util.go index 4ba8554c7..e76de3771 100644 --- a/sql/parse/util.go +++ b/sql/parse/util.go @@ -223,3 +223,38 @@ func parseExpr(str string) (sql.Expression, error) { return exprToExpression(selectExpr.Expr) } + +func readQuotableIdent(ident *string) parseFunc { + return func(r *bufio.Reader) error { + nextChar, err := r.Peek(1) + if err != nil { + return err + } + + var steps parseFuncs + if nextChar[0] == '`' { + steps = parseFuncs{ + expectQuote, + readIdent(ident), + expectQuote, + } + } else { + steps = parseFuncs{readIdent(ident)} + } + + return steps.exec(r) + } +} + +func expectQuote(r *bufio.Reader) error { + ru, _, err := r.ReadRune() + if err != nil { + return err + } + + if ru != '`' { + return errUnexpectedSyntax.New("`", string(ru)) + } + + return nil +} diff --git a/sql/parse/variables.go b/sql/parse/variables.go index bcaf90f8c..c92b5355d 100644 --- a/sql/parse/variables.go +++ b/sql/parse/variables.go @@ -17,10 +17,16 @@ func parseShowVariables(ctx *sql.Context, s string) (sql.Node, error) { skipSpaces, func(in *bufio.Reader) error { var s string - readIdent(&s)(in) + if err := readIdent(&s)(in); err != nil { + return err + } + switch s { case "global", "session": - skipSpaces(in) + if err := skipSpaces(in); err != nil { + return err + } + return expect("variables")(in) case "variables": return nil @@ -30,8 +36,13 @@ func parseShowVariables(ctx *sql.Context, s string) (sql.Node, error) { skipSpaces, func(in *bufio.Reader) error { if expect("like")(in) == nil { - skipSpaces(in) - readValue(&pattern)(in) + if err := skipSpaces(in); err != nil { + return err + } + + if err := readValue(&pattern)(in); err != nil { + return err + } } return nil }, diff --git a/sql/plan/showdatabases.go b/sql/plan/showdatabases.go index db9d58e80..2783af4aa 100644 --- a/sql/plan/showdatabases.go +++ b/sql/plan/showdatabases.go @@ -30,7 +30,7 @@ func (*ShowDatabases) Children() []sql.Node { // Schema implements the Node interface. func (*ShowDatabases) Schema() sql.Schema { return sql.Schema{{ - Name: "database", + Name: "Database", Type: sql.Text, Nullable: false, }} @@ -39,9 +39,11 @@ func (*ShowDatabases) Schema() sql.Schema { // RowIter implements the Node interface. func (p *ShowDatabases) RowIter(ctx *sql.Context) (sql.RowIter, error) { dbs := p.Catalog.AllDatabases() - var rows = make([]sql.Row, len(dbs)) - for i, db := range dbs { - rows[i] = sql.Row{db.Name()} + var rows = make([]sql.Row, 0, len(dbs)) + for _, db := range dbs { + if sql.InformationSchemaDBName != db.Name() { + rows = append(rows, sql.Row{db.Name()}) + } } sort.Slice(rows, func(i, j int) bool { diff --git a/sql/session.go b/sql/session.go index 771e3ad9f..3753380bd 100644 --- a/sql/session.go +++ b/sql/session.go @@ -145,7 +145,7 @@ func defaultSessionConfig() map[string]TypedValue { "max_allowed_packet": TypedValue{Int32, math.MaxInt32}, "sql_mode": TypedValue{Text, ""}, "gtid_mode": TypedValue{Int32, int32(0)}, - "ndbinfo_version": TypedValue{Text, ""}, + "collation_database": TypedValue{Text, "utf8_bin"}, } } diff --git a/sql/type.go b/sql/type.go index a78036c6b..ef3e00064 100644 --- a/sql/type.go +++ b/sql/type.go @@ -71,8 +71,10 @@ func (s Schema) Contains(column string, source string) bool { // IndexOf returns the index of the given column in the schema or -1 if it's // not present. func (s Schema) IndexOf(column, source string) int { + column = strings.ToLower(column) + source = strings.ToLower(source) for i, col := range s { - if col.Name == column && col.Source == source { + if strings.ToLower(col.Name) == column && strings.ToLower(col.Source) == source { return i } }