diff --git a/sql/databases.go b/sql/databases.go index e1ec634ace..9f66b81675 100644 --- a/sql/databases.go +++ b/sql/databases.go @@ -79,6 +79,10 @@ type Database interface { // SchemaDatabase is a database comprising multiple schemas that can each be queried for tables. type SchemaDatabase interface { Nameable + // SupportsDatabaseSchemas returns whether this database supports multiple schemas. This is necessary because some + // integrators may use implementations that fulfill the interface without actually supporting multiple schemas in + // all configurations. + SupportsDatabaseSchemas() bool // GetSchema returns the database with the schema name provided, matched case-insensitive. // If the schema does not exist, the boolean return value should be false. GetSchema(ctx *Context, schemaName string) (DatabaseSchema, bool, error) @@ -87,6 +91,8 @@ type SchemaDatabase interface { CreateSchema(ctx *Context, schemaName string) error // AllSchemas returns all schemas in the database. AllSchemas(ctx *Context) ([]DatabaseSchema, error) + // DropSchema drops the schema with the name given. + DropSchema(ctx *Context, schemaName string) error } // DatabaseSchema is a schema that can be queried for tables. It is functionally equivalent to a Database diff --git a/sql/plan/dbddl.go b/sql/plan/dbddl.go index 6387e4f6d1..53fac19cdb 100644 --- a/sql/plan/dbddl.go +++ b/sql/plan/dbddl.go @@ -163,6 +163,23 @@ func NewDropDatabase(dbName string, ifExists bool) *DropDB { } } +// DropSchema removes a schema from the Catalog using the currently selected database. +type DropSchema struct { + *DropDB +} + +var _ sql.Node = (*DropSchema)(nil) + +// NewDropSchema returns a new DropSchema. +func NewDropSchema(schemaName string, ifExists bool) *DropSchema { + return &DropSchema{ + &DropDB{ + DbName: schemaName, + IfExists: ifExists, + }, + } +} + // AlterDB alters a database from the Catalog. type AlterDB struct { Catalog sql.Catalog diff --git a/sql/planbuilder/ddl.go b/sql/planbuilder/ddl.go index 758b57410a..268763caf2 100644 --- a/sql/planbuilder/ddl.go +++ b/sql/planbuilder/ddl.go @@ -41,9 +41,9 @@ func (b *Builder) resolveDb(name string) sql.Database { } // todo show tables as of expects privileged - //if privilegedDatabase, ok := database.(mysql_db.PrivilegedDatabase); ok { + // if privilegedDatabase, ok := database.(mysql_db.PrivilegedDatabase); ok { // database = privilegedDatabase.Unwrap() - //} + // } return database } @@ -848,7 +848,7 @@ func (b *Builder) buildIndexDefs(_ *scope, spec *ast.TableSpec) (idxDefs sql.Ind } idxDefs = append(idxDefs, &sql.IndexDef{ Name: idxDef.Info.Name.String(), - Storage: sql.IndexUsing_Default, //TODO: add vitess support for USING + Storage: sql.IndexUsing_Default, // TODO: add vitess support for USING Constraint: constraint, Columns: columns, Comment: comment, @@ -1857,13 +1857,22 @@ func (b *Builder) buildDBDDL(inScope *scope, c *ast.DBDDL) (outScope *scope) { createSchema := plan.NewCreateSchema(c.DBName, c.IfNotExists, collation) createSchema.Catalog = b.cat node = createSchema + default: + b.handleErr(sql.ErrUnsupportedSyntax.New(ast.String(c))) } outScope.node = node case ast.DropStr: - dropDb := plan.NewDropDatabase(c.DBName, c.IfExists) - dropDb.Catalog = b.cat - outScope.node = dropDb + switch c.SchemaOrDatabase { + case "database": + node := plan.NewDropDatabase(c.DBName, c.IfExists) + node.Catalog = b.cat + outScope.node = node + case "schema": + node := plan.NewDropSchema(c.DBName, c.IfExists) + node.Catalog = b.cat + outScope.node = node + } case ast.AlterStr: if len(c.CharsetCollate) == 0 { if len(c.DBName) > 0 { diff --git a/sql/rowexec/ddl.go b/sql/rowexec/ddl.go index a5a0996cf8..75f502b501 100644 --- a/sql/rowexec/ddl.go +++ b/sql/rowexec/ddl.go @@ -451,7 +451,7 @@ func (b *BaseBuilder) buildCreateSchema(ctx *sql.Context, n *plan.CreateSchema, // If no database is selected, first try to fall back to CREATE DATABASE // since CREATE SCHEMA is a synonym for CREATE DATABASE in MySQL // https://dev.mysql.com/doc/refman/8.4/en/create-database.html - // TODO: For PostgreSQL, return an error if no database is selected. + // TODO: For PostgreSQL, return an error if no database is selected (should be impossible) if database == "" { return b.buildCreateDB(ctx, &plan.CreateDB{ Catalog: n.Catalog, @@ -466,8 +466,12 @@ func (b *BaseBuilder) buildCreateSchema(ctx *sql.Context, n *plan.CreateSchema, return nil, err } + if pdb, ok := db.(mysql_db.PrivilegedDatabase); ok { + db = pdb.Unwrap() + } + sdb, ok := db.(sql.SchemaDatabase) - if !ok { + if !ok || !sdb.SupportsDatabaseSchemas() { // If schemas aren't supported, treat CREATE SCHEMA as a synonym for CREATE DATABASE (as is the case in MySQL) return b.buildCreateDB(ctx, &plan.CreateDB{ Catalog: n.Catalog, @@ -882,6 +886,69 @@ func (b *BaseBuilder) buildDropDB(ctx *sql.Context, n *plan.DropDB, row sql.Row) return sql.RowsToRowIter(rows...), nil } +func (b *BaseBuilder) buildDropSchema(ctx *sql.Context, n *plan.DropSchema, row sql.Row) (sql.RowIter, error) { + database := ctx.GetCurrentDatabase() + + // If no database is selected, first try to fall back to CREATE DATABASE + // since CREATE SCHEMA is a synonym for CREATE DATABASE in MySQL + // https://dev.mysql.com/doc/refman/8.4/en/create-database.html + // TODO: For PostgreSQL, return an error if no database is selected (should be impossible) + if database == "" { + return b.buildDropDB(ctx, &plan.DropDB{ + Catalog: n.Catalog, + DbName: n.DbName, + IfExists: n.IfExists, + }, row) + } + + db, err := n.Catalog.Database(ctx, database) + if err != nil { + return nil, err + } + + if pdb, ok := db.(mysql_db.PrivilegedDatabase); ok { + db = pdb.Unwrap() + } + + sdb, ok := db.(sql.SchemaDatabase) + if !ok || !sdb.SupportsDatabaseSchemas() { + // If schemas aren't supported, treat DROP SCHEMA as a synonym for DROP DATABASE (as is the case in MySQL) + return b.buildDropDB(ctx, &plan.DropDB{ + Catalog: n.Catalog, + DbName: n.DbName, + IfExists: n.IfExists, + }, row) + } + + _, exists, err := sdb.GetSchema(ctx, n.DbName) + if err != nil { + return nil, err + } + + rows := []sql.Row{{types.OkResult{RowsAffected: 1}}} + + if !exists { + if n.IfExists && ctx != nil && ctx.Session != nil { + ctx.Session.Warn(&sql.Warning{ + Level: "Note", + Code: mysql.ERDbCreateExists, + Message: fmt.Sprintf("Can't drop schema %s; schema does not exist", n.DbName), + }) + + return sql.RowsToRowIter(rows...), nil + } else { + return nil, sql.ErrDatabaseSchemaNotFound.New(n.DbName) + } + } + + err = sdb.DropSchema(ctx, n.DbName) + if err != nil { + return nil, err + } + + return sql.RowsToRowIter(rows...), nil +} + func (b *BaseBuilder) buildRenameColumn(ctx *sql.Context, n *plan.RenameColumn, row sql.Row) (sql.RowIter, error) { if b.EngineOverrides.Hooks.TableRenameColumn.PreSQLExecution != nil { nn, err := b.EngineOverrides.Hooks.TableRenameColumn.PreSQLExecution(ctx, n) @@ -1128,8 +1195,8 @@ func (b *BaseBuilder) buildCreateTable(ctx *sql.Context, n *plan.CreateTable, ro } } - //TODO: in the event that foreign keys or indexes aren't supported, you'll be left with a created table and no foreign keys/indexes - //this also means that if a foreign key or index fails, you'll only have what was declared up to the failure + // TODO: in the event that foreign keys or indexes aren't supported, you'll be left with a created table and no foreign keys/indexes + // this also means that if a foreign key or index fails, you'll only have what was declared up to the failure tableNode, ok, err := n.Db.GetTableInsensitive(ctx, n.Name()) if err != nil { return sql.RowsToRowIter(), err diff --git a/sql/rowexec/node_builder.gen.go b/sql/rowexec/node_builder.gen.go index b0c48df4ed..de22144b89 100644 --- a/sql/rowexec/node_builder.gen.go +++ b/sql/rowexec/node_builder.gen.go @@ -148,6 +148,8 @@ func (b *BaseBuilder) buildNodeExecNoAnalyze(ctx *sql.Context, n sql.Node, row s return b.buildRenameColumn(ctx, n, row) case *plan.DropDB: return b.buildDropDB(ctx, n, row) + case *plan.DropSchema: + return b.buildDropSchema(ctx, n, row) case *plan.Distinct: return b.buildDistinct(ctx, n, row) case *plan.Having: