Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 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
59 changes: 59 additions & 0 deletions enginetest/queries/index_queries.go
Original file line number Diff line number Diff line change
Expand Up @@ -4338,4 +4338,63 @@ var IndexQueries = []ScriptTest{
},
},
},
{
Name: "indexes and if exists",
SetUpScript: []string{
"create table t (i int, j int);",
"create index idx on t (i);",
},
Assertions: []ScriptTestAssertion{
{
Query: "create index idx on t(j)",
ExpectedErr: sql.ErrDuplicateKey,
},
{
Query: "create index if not exists idx on t(j)",
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm not sure MySQL actually supports this syntax, based on their docs:
https://dev.mysql.com/doc/refman/9.3/en/create-index.html

I tested with MySQL 8.4, but didn't try with MySQL 9.x.

That said, I think it's still useful and I'm not opposed to supporting it, although it will likely be hard for customers to discover if it isn't in the MySQL docs. We should just figure out if we want to be consistent with CREATE TABLE IF NOT EXISTS or CREATE IF NOT EXISTS VIEW, which unfortunately seems to be inconsistent in MySQL.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah, I noticed this too.
MariaDB has been doing create view IF NOT EXISTS, which is consistent with all the other CREATE ... IF NOT EXISTS queries... it might be possible to do both?
So CREATE [IF NOT EXISTS] VIEW [IF NOT EXISTS] ...?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I downloaded MySQL 9.3 and tested it out; their docs are just messed up.

mysql> select version();
+-----------+
| version() |
+-----------+
| 9.3.0     |
+-----------+
1 row in set (0.0003 sec)

mysql> create if not exists view v as select 1;
ERROR: 1064: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'if not exists view v as select 1' at line 1

mysql> create view if not exists v as select 1;
Query OK, 0 rows affected (0.0044 sec)

mysql> select * from v;
+---+
| 1 |
+---+
| 1 |
+---+
1 row in set (0.0009 sec)

Expected: []sql.Row{
{types.NewOkResult(0)},
},
},
{
Query: "show create table t",
Expected: []sql.Row{
{"t", "CREATE TABLE `t` (\n" +
" `i` int,\n" +
" `j` int,\n" +
" KEY `idx` (`i`)\n" +
") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_bin"},
},
},
{
Query: "alter table t add index idx (j)",
ExpectedErr: sql.ErrDuplicateKey,
},
{
Query: "alter table t add index if not exists idx (j)",
Expected: []sql.Row{
{types.NewOkResult(0)},
},
},
{
Query: "show create table t",
Expected: []sql.Row{
{"t", "CREATE TABLE `t` (\n" +
" `i` int,\n" +
" `j` int,\n" +
" KEY `idx` (`i`)\n" +
") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_bin"},
},
},
{
Query: "alter table t drop index notanidx",
ExpectedErr: sql.ErrCantDropFieldOrKey,
},
{
Query: "alter table t drop index if exists notanidx",
Expected: []sql.Row{
{types.NewOkResult(0)},
},
},
},
},
}
30 changes: 30 additions & 0 deletions enginetest/queries/view_queries.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,36 @@ import (
)

var ViewScripts = []ScriptTest{
{
Name: "existing views",
SetUpScript: []string{
"create view v as select 1;",
"create table t (i int);",
"insert into t values (1);",
},
Assertions: []ScriptTestAssertion{
{
Query: "create view if not exists v as select 2;",
Copy link
Contributor

Choose a reason for hiding this comment

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

Sorry I didn't notice this while looking at the parser updates... it looks like we are slightly off of MySQL's syntax here... it seems like support for IF NOT EXISTS was added very recently, in MySQL 9.1. The docs show that that the form should be CREATE IF NOT EXISTS VIEW v AS SELECT 2;:
https://dev.mysql.com/doc/refman/9.1/en/create-view.html

I haven't tested this with MySQL 9.x, so it could be that the documentation is incorrect. I know that syntax is a little inconsistent with CREATE TABLE IF NOT EXISTS, too.

Expected: []sql.Row{
{types.NewOkResult(0)},
},
},
{
Query: "select * from v;",
Expected: []sql.Row{{1}},
},
{
Query: "create view if not exists t as select 2;",
Expected: []sql.Row{
{types.NewOkResult(0)},
},
},
{
Query: "select * from t;",
Expected: []sql.Row{{1}},
},
},
},
{
Name: "multi database view",
SetUpScript: []string{
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ require (
github.com/dolthub/go-icu-regex v0.0.0-20250327004329-6799764f2dad
github.com/dolthub/jsonpath v0.0.2-0.20240227200619-19675ab05c71
github.com/dolthub/sqllogictest/go v0.0.0-20201107003712-816f3ae12d81
github.com/dolthub/vitess v0.0.0-20250530231040-bfd522856394
github.com/dolthub/vitess v0.0.0-20250604183826-4944a453aecb
github.com/go-kit/kit v0.10.0
github.com/go-sql-driver/mysql v1.7.2-0.20231213112541-0004702b931d
github.com/gocraft/dbr/v2 v2.7.2
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,8 @@ github.com/dolthub/jsonpath v0.0.2-0.20240227200619-19675ab05c71 h1:bMGS25NWAGTE
github.com/dolthub/jsonpath v0.0.2-0.20240227200619-19675ab05c71/go.mod h1:2/2zjLQ/JOOSbbSboojeg+cAwcRV0fDLzIiWch/lhqI=
github.com/dolthub/sqllogictest/go v0.0.0-20201107003712-816f3ae12d81 h1:7/v8q9XGFa6q5Ap4Z/OhNkAMBaK5YeuEzwJt+NZdhiE=
github.com/dolthub/sqllogictest/go v0.0.0-20201107003712-816f3ae12d81/go.mod h1:siLfyv2c92W1eN/R4QqG/+RjjX5W2+gCTRjZxBjI3TY=
github.com/dolthub/vitess v0.0.0-20250530231040-bfd522856394 h1:sMwntvk7O9dttaJLqnOvy8zgk0ah9qnyWkAahfOgnIo=
github.com/dolthub/vitess v0.0.0-20250530231040-bfd522856394/go.mod h1:1gQZs/byeHLMSul3Lvl3MzioMtOW1je79QYGyi2fd70=
github.com/dolthub/vitess v0.0.0-20250604183826-4944a453aecb h1:/Tti0IJASR0xHMA5TRFalVUTGLKZsjcAhpWpCtJMadY=
github.com/dolthub/vitess v0.0.0-20250604183826-4944a453aecb/go.mod h1:1gQZs/byeHLMSul3Lvl3MzioMtOW1je79QYGyi2fd70=
github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU=
Expand Down
2 changes: 1 addition & 1 deletion memory/database.go
Original file line number Diff line number Diff line change
Expand Up @@ -555,7 +555,7 @@ func (d *Database) Database() *BaseDatabase {
func (d *Database) CreateView(ctx *sql.Context, name string, selectStatement, createViewStmt string) error {
_, ok := d.views[strings.ToLower(name)]
if ok {
return sql.ErrExistingView.New(name)
return sql.ErrExistingView.New(d.Name(), name)
}

sqlMode := sql.LoadSqlMode(ctx)
Expand Down
3 changes: 3 additions & 0 deletions sql/analyzer/validate_create_table.go
Original file line number Diff line number Diff line change
Expand Up @@ -657,6 +657,9 @@ func validateAlterIndex(ctx *sql.Context, initialSch, sch sql.Schema, ai *plan.A
}
}
if savedIdx == -1 {
if ai.IfExists {
return nil, nil
}
return nil, sql.ErrCantDropFieldOrKey.New(ai.IndexName)
}
// Remove the index from the list
Expand Down
2 changes: 1 addition & 1 deletion sql/index_driver.go
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ var (
ErrIndexExpressionAlreadyRegistered = errors.NewKind("there is already an index registered for the expressions: %s")

// ErrIndexNotFound is returned when the index could not be found.
ErrIndexNotFound = errors.NewKind("index %q was not found")
ErrIndexNotFound = errors.NewKind("index %q was not found")

// ErrIndexDeleteInvalidStatus is returned when the index trying to delete
// does not have a ready or outdated state.
Expand Down
61 changes: 35 additions & 26 deletions sql/plan/alter_index.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,13 @@ type AlterIndex struct {
// Action states whether it's a CREATE, DROP, or RENAME
Action IndexAction
// ddlNode references to the database that is being operated on
ddlNode
Db sql.Database
// Table is the table that is being referenced
Table sql.TableNode
// IfExists indicates if we should error when deleting an index that doesn't exist
IfExists bool
// IfNotExists indicates if we should error when creating a duplicate index
IfNotExists bool
// IndexName is the index name, and in the case of a RENAME it represents the new name
IndexName string
// PreviousIndexName states the old name when renaming an index
Expand All @@ -72,32 +76,34 @@ var _ sql.Expressioner = (*AlterIndex)(nil)
var _ sql.Node = (*AlterIndex)(nil)
var _ sql.CollationCoercible = (*AlterIndex)(nil)

func NewAlterCreateIndex(db sql.Database, table sql.TableNode, indexName string, using sql.IndexUsing, constraint sql.IndexConstraint, columns []sql.IndexColumn, comment string) *AlterIndex {
func NewAlterCreateIndex(db sql.Database, table sql.TableNode, ifNotExists bool, indexName string, using sql.IndexUsing, constraint sql.IndexConstraint, columns []sql.IndexColumn, comment string) *AlterIndex {
return &AlterIndex{
Action: IndexAction_Create,
ddlNode: ddlNode{Db: db},
Table: table,
IndexName: indexName,
Using: using,
Constraint: constraint,
Columns: columns,
Comment: comment,
Action: IndexAction_Create,
Db: db,
Table: table,
IfNotExists: ifNotExists,
IndexName: indexName,
Using: using,
Constraint: constraint,
Columns: columns,
Comment: comment,
}
}

func NewAlterDropIndex(db sql.Database, table sql.TableNode, indexName string) *AlterIndex {
func NewAlterDropIndex(db sql.Database, table sql.TableNode, ifExists bool, indexName string) *AlterIndex {
return &AlterIndex{
Action: IndexAction_Drop,
ddlNode: ddlNode{Db: db},
Db: db,
Table: table,
IfExists: ifExists,
IndexName: indexName,
}
}

func NewAlterRenameIndex(db sql.Database, table sql.TableNode, fromIndexName, toIndexName string) *AlterIndex {
return &AlterIndex{
Action: IndexAction_Rename,
ddlNode: ddlNode{Db: db},
Db: db,
Table: table,
IndexName: toIndexName,
PreviousIndexName: fromIndexName,
Expand All @@ -107,7 +113,7 @@ func NewAlterRenameIndex(db sql.Database, table sql.TableNode, fromIndexName, to
func NewAlterDisableEnableKeys(db sql.Database, table sql.TableNode, disableKeys bool) *AlterIndex {
return &AlterIndex{
Action: IndexAction_DisableEnableKeys,
ddlNode: ddlNode{Db: db},
Db: db,
Table: table,
DisableKeys: disableKeys,
}
Expand All @@ -120,7 +126,7 @@ func (p *AlterIndex) Schema() sql.Schema {

// WithChildren implements the Node interface. For AlterIndex, the only appropriate input is
// a single child - The Table.
func (p AlterIndex) WithChildren(children ...sql.Node) (sql.Node, error) {
func (p *AlterIndex) WithChildren(children ...sql.Node) (sql.Node, error) {
if len(children) != 1 {
return nil, sql.ErrInvalidChildrenNumber.New(p, len(children), 1)
}
Expand All @@ -131,21 +137,24 @@ func (p AlterIndex) WithChildren(children ...sql.Node) (sql.Node, error) {
}
switch p.Action {
case IndexAction_Create, IndexAction_Drop, IndexAction_Rename, IndexAction_DisableEnableKeys:
p.Table = child
return &p, nil
np := *p
np.Table = child
return &np, nil
default:
return nil, ErrIndexActionNotImplemented.New(p.Action)
}
}

func (p AlterIndex) WithColumns(columns []sql.IndexColumn) (sql.Node, error) {
p.Columns = columns
return &p, nil
func (p *AlterIndex) WithColumns(columns []sql.IndexColumn) (sql.Node, error) {
np := *p
np.Columns = columns
return &np, nil
}

func (p AlterIndex) WithTargetSchema(schema sql.Schema) (sql.Node, error) {
p.targetSchema = schema
return &p, nil
func (p *AlterIndex) WithTargetSchema(schema sql.Schema) (sql.Node, error) {
np := *p
np.targetSchema = schema
return &np, nil
}

func (p *AlterIndex) TargetSchema() sql.Schema {
Expand All @@ -164,7 +173,7 @@ func (p *AlterIndex) Expressions() []sql.Expression {

// WithExpressions implements the Node Interface. For AlterIndex, expressions represent column defaults on the
// targetSchema instance - required to be the same number of columns on the target schema.
func (p AlterIndex) WithExpressions(expressions ...sql.Expression) (sql.Node, error) {
func (p *AlterIndex) WithExpressions(expressions ...sql.Expression) (sql.Node, error) {
columns := p.TargetSchema().Copy()

if len(columns) != len(expressions) {
Expand Down Expand Up @@ -209,7 +218,7 @@ func (p *AlterIndex) WithDatabase(database sql.Database) (sql.Node, error) {
return &np, nil
}

func (p AlterIndex) String() string {
func (p *AlterIndex) String() string {
pr := sql.NewTreePrinter()
switch p.Action {
case IndexAction_Create:
Expand Down Expand Up @@ -259,7 +268,7 @@ func (p AlterIndex) String() string {
}

func (p *AlterIndex) Resolved() bool {
return p.Table.Resolved() && p.ddlNode.Resolved() && p.targetSchema.Resolved()
return p.Table.Resolved() && p.targetSchema.Resolved()
}

func (p *AlterIndex) IsReadOnly() bool {
Expand Down
4 changes: 3 additions & 1 deletion sql/plan/create_view.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ type CreateView struct {
database sql.Database
targetSchema sql.Schema
Name string
IfNotExists bool
IsReplace bool
Definition *SubqueryAlias
CreateViewString string
Expand All @@ -46,11 +47,12 @@ var _ sql.SchemaTarget = (*CreateView)(nil)

// NewCreateView creates a CreateView node with the specified parameters,
// setting its catalog to nil.
func NewCreateView(database sql.Database, name string, definition *SubqueryAlias, isReplace bool, createViewStr, algorithm, definer, security string) *CreateView {
func NewCreateView(database sql.Database, name string, definition *SubqueryAlias, ifNotExists, isReplace bool, createViewStr, algorithm, definer, security string) *CreateView {
return &CreateView{
UnaryNode: UnaryNode{Child: definition},
database: database,
Name: name,
IfNotExists: ifNotExists,
IsReplace: isReplace,
Definition: definition,
CreateViewString: createViewStr,
Expand Down
2 changes: 1 addition & 1 deletion sql/planbuilder/create_ddl.go
Original file line number Diff line number Diff line change
Expand Up @@ -672,7 +672,7 @@ func (b *Builder) buildCreateView(inScope *scope, subQuery string, fullQuery str
if !ok {
b.handleErr(sql.ErrDatabaseSchemaNotFound.New(c.Table.SchemaQualifier.String()))
}
createView := plan.NewCreateView(db, c.ViewSpec.ViewName.Name.String(), queryAlias, c.OrReplace, subQuery, c.ViewSpec.Algorithm, definer, c.ViewSpec.Security)
createView := plan.NewCreateView(db, c.ViewSpec.ViewName.Name.String(), queryAlias, c.IfNotExists, c.OrReplace, subQuery, c.ViewSpec.Algorithm, definer, c.ViewSpec.Security)
outScope.node = b.modifySchemaTarget(queryScope, createView, createView.Definition.Schema())

return outScope
Expand Down
14 changes: 12 additions & 2 deletions sql/planbuilder/ddl.go
Original file line number Diff line number Diff line change
Expand Up @@ -612,6 +612,7 @@ func (b *Builder) buildAlterTableClause(inScope *scope, ddl *ast.DDL) []*scope {
createIndex := plan.NewAlterCreateIndex(
rt.Database(),
rt,
ddl.IfNotExists,
column.Name.String(),
sql.IndexUsing_BTree,
sql.IndexConstraint_Unique,
Expand Down Expand Up @@ -994,15 +995,24 @@ func (b *Builder) buildAlterIndex(inScope *scope, ddl *ast.DDL, table *plan.Reso
b.handleErr(err)
}

createIndex := plan.NewAlterCreateIndex(table.SqlDatabase, table, ddl.IndexSpec.ToName.String(), using, constraint, columns, comment)
createIndex := plan.NewAlterCreateIndex(
table.SqlDatabase,
table,
ddl.IfNotExists,
ddl.IndexSpec.ToName.String(),
using,
constraint,
columns,
comment,
)
outScope.node = b.modifySchemaTarget(inScope, createIndex, table.Schema())
return
case ast.DropStr:
if ddl.IndexSpec.Type == ast.PrimaryStr {
outScope.node = plan.NewAlterDropPk(table.SqlDatabase, table)
return
}
outScope.node = plan.NewAlterDropIndex(table.Database(), table, ddl.IndexSpec.ToName.String())
outScope.node = plan.NewAlterDropIndex(table.Database(), table, ddl.IfExists, ddl.IndexSpec.ToName.String())
return
case ast.RenameStr:
outScope.node = plan.NewAlterRenameIndex(table.Database(), table, ddl.IndexSpec.FromName.String(), ddl.IndexSpec.ToName.String())
Expand Down
Loading
Loading