Skip to content
Closed
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
59 changes: 59 additions & 0 deletions server/analyzer/convert_drop_unique_constraint.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// Copyright 2025 Dolthub, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package analyzer

import (
"github.com/dolthub/go-mysql-server/sql"
"github.com/dolthub/go-mysql-server/sql/analyzer"
"github.com/dolthub/go-mysql-server/sql/plan"
"github.com/dolthub/go-mysql-server/sql/transform"
)

// convertDropUniqueConstraint converts a DropConstraint node dropping a unique constraint into
// an AlterIndex node with IndexAction_Drop that GMS can process to remove the unique index.
func convertDropUniqueConstraint(ctx *sql.Context, _ *analyzer.Analyzer, n sql.Node, _ *plan.Scope, _ analyzer.RuleSelector, _ *sql.QueryFlags) (sql.Node, transform.TreeIdentity, error) {
return transform.Node(n, func(n sql.Node) (sql.Node, transform.TreeIdentity, error) {
dropConstraint, ok := n.(*plan.DropConstraint)
if !ok {
return n, transform.SameTree, nil
}

rt, ok := dropConstraint.Child.(*plan.ResolvedTable)
if !ok {
return nil, transform.SameTree, analyzer.ErrInAnalysis.New(
"Expected a TableNode for ALTER TABLE DROP CONSTRAINT statement")
}

table := rt.Table
if it, ok := table.(sql.IndexAddressableTable); ok {
indexes, err := it.GetIndexes(ctx)
if err != nil {
return nil, transform.SameTree, err
}
for _, index := range indexes {
if index.IsUnique() && index.ID() != "PRIMARY" && index.ID() == dropConstraint.Name {
alterDropIndex := plan.NewAlterDropIndex(rt.Database(), rt, dropConstraint.IfExists, dropConstraint.Name)
newNode, err := alterDropIndex.WithTargetSchema(rt.Schema())
if err != nil {
return n, transform.SameTree, err
}
return newNode, transform.NewTree, nil
}
}
}

return n, transform.SameTree, nil
})
}
4 changes: 3 additions & 1 deletion server/analyzer/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ const (
ruleId_AssignTriggers // assignTriggers
ruleId_AssignUpdateCasts // assignUpdateCasts
ruleId_ConvertDropPrimaryKeyConstraint // convertDropPrimaryKeyConstraint
ruleId_ConvertDropUniqueConstraint // convertDropUniqueConstraint
ruleId_GenerateForeignKeyName // generateForeignKeyName
ruleId_ReplaceIndexedTables // replaceIndexedTables
ruleId_ReplaceNode // replaceNode
Expand Down Expand Up @@ -68,7 +69,8 @@ func Init() {

analyzer.OnceBeforeDefault = append([]analyzer.Rule{
{Id: ruleId_ApplyTablesForAnalyzeAllTables, Apply: applyTablesForAnalyzeAllTables},
{Id: ruleId_ConvertDropPrimaryKeyConstraint, Apply: convertDropPrimaryKeyConstraint}},
{Id: ruleId_ConvertDropPrimaryKeyConstraint, Apply: convertDropPrimaryKeyConstraint},
{Id: ruleId_ConvertDropUniqueConstraint, Apply: convertDropUniqueConstraint}},
analyzer.OnceBeforeDefault...)

// We remove several validation rules and substitute our own
Expand Down
114 changes: 114 additions & 0 deletions testing/go/alter_table_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,120 @@ func TestAlterTable(t *testing.T) {
},
},
},
{
Name: "Drop unique constraint added via ALTER TABLE",
SetUpScript: []string{
"CREATE TABLE t1 (pk int PRIMARY KEY, c1 int, c2 int);",
"INSERT INTO t1 VALUES (1, 10, 20);",
"ALTER TABLE t1 ADD CONSTRAINT uniq_c1c2 UNIQUE (c1, c2);",
},
Assertions: []ScriptTestAssertion{
{
Query: "INSERT INTO t1 VALUES (2, 10, 20);",
ExpectedErr: "unique",
},
{
Query: "ALTER TABLE t1 DROP CONSTRAINT uniq_c1c2;",
Expected: []sql.Row{},
},
{
Query: "INSERT INTO t1 VALUES (2, 10, 20);",
Expected: []sql.Row{},
},
{
Query: "SELECT * FROM t1 ORDER BY pk;",
Expected: []sql.Row{{1, 10, 20}, {2, 10, 20}},
},
},
},
{
Name: "Drop unique constraint defined inline in CREATE TABLE",
SetUpScript: []string{
"CREATE TABLE t1 (pk int PRIMARY KEY, c1 int, c2 int, CONSTRAINT uniq_inline UNIQUE (c1, c2));",
"INSERT INTO t1 VALUES (1, 10, 20);",
},
Assertions: []ScriptTestAssertion{
{
Query: "INSERT INTO t1 VALUES (2, 10, 20);",
ExpectedErr: "unique",
},
{
Query: "ALTER TABLE t1 DROP CONSTRAINT uniq_inline;",
Expected: []sql.Row{},
},
{
Query: "INSERT INTO t1 VALUES (2, 10, 20);",
Expected: []sql.Row{},
},
},
},
{
Name: "Drop unique constraint with IF EXISTS",
SetUpScript: []string{
"CREATE TABLE t1 (pk int PRIMARY KEY, c1 int);",
"ALTER TABLE t1 ADD CONSTRAINT uniq_c1 UNIQUE (c1);",
},
Assertions: []ScriptTestAssertion{
{
Query: "ALTER TABLE t1 DROP CONSTRAINT uniq_c1;",
Expected: []sql.Row{},
},
{
Query: "ALTER TABLE t1 DROP CONSTRAINT uniq_c1;",
ExpectedErr: "does not exist",
},
{
Query: "ALTER TABLE t1 DROP CONSTRAINT IF EXISTS uniq_c1;",
Expected: []sql.Row{},
},
},
},
{
Name: "Drop unique constraint on single column",
SetUpScript: []string{
"CREATE TABLE t1 (pk int PRIMARY KEY, email varchar(256), CONSTRAINT uniq_email UNIQUE (email));",
"INSERT INTO t1 VALUES (1, 'a@b.com');",
},
Assertions: []ScriptTestAssertion{
{
Query: "INSERT INTO t1 VALUES (2, 'a@b.com');",
ExpectedErr: "unique",
},
{
Query: "ALTER TABLE t1 DROP CONSTRAINT uniq_email;",
Expected: []sql.Row{},
},
{
Query: "INSERT INTO t1 VALUES (2, 'a@b.com');",
Expected: []sql.Row{},
},
},
},
{
Name: "Drop and re-add unique constraint with different columns",
SetUpScript: []string{
"CREATE TABLE t1 (pk int PRIMARY KEY, c1 int, c2 int, c3 int);",
"ALTER TABLE t1 ADD CONSTRAINT uniq1 UNIQUE (c1, c2);",
},
Assertions: []ScriptTestAssertion{
{
Query: "ALTER TABLE t1 DROP CONSTRAINT uniq1;",
Expected: []sql.Row{},
},
{
Query: "ALTER TABLE t1 ADD CONSTRAINT uniq1 UNIQUE (c1, c2, c3);",
Expected: []sql.Row{},
},
{
Query: "INSERT INTO t1 VALUES (1, 10, 20, 30);",
Expected: []sql.Row{},
},
{
Query: "INSERT INTO t1 VALUES (2, 10, 20, 30);",
ExpectedErr: "unique",
},
},
},
{
Name: "Add Primary Key",
SetUpScript: []string{
Expand Down