diff --git a/fe/fe-core/src/main/antlr4/org/apache/doris/nereids/DorisParser.g4 b/fe/fe-core/src/main/antlr4/org/apache/doris/nereids/DorisParser.g4 index 64ff9ec366..faa4f1b2e2 100644 --- a/fe/fe-core/src/main/antlr4/org/apache/doris/nereids/DorisParser.g4 +++ b/fe/fe-core/src/main/antlr4/org/apache/doris/nereids/DorisParser.g4 @@ -104,11 +104,11 @@ statement ; constraint - : PRIMARY KEY LEFT_PAREN slots+=errorCapturingIdentifier (COMMA slots+=errorCapturingIdentifier)* RIGHT_PAREN - | UNIQUE LEFT_PAREN slots+=errorCapturingIdentifier (COMMA slots+=errorCapturingIdentifier)* RIGHT_PAREN - | FOREIGN KEY LEFT_PAREN slots+=errorCapturingIdentifier (COMMA slots+=errorCapturingIdentifier)* RIGHT_PAREN + : PRIMARY KEY slots=identifierList + | UNIQUE slots=identifierList + | FOREIGN KEY slots=identifierList REFERENCES referenceTable=multipartIdentifier - LEFT_PAREN referenceSlots+=errorCapturingIdentifier (COMMA referenceSlots+=errorCapturingIdentifier)* RIGHT_PAREN + referencedSlots=identifierList ; dataDesc diff --git a/fe/fe-core/src/main/java/org/apache/doris/catalog/TableIf.java b/fe/fe-core/src/main/java/org/apache/doris/catalog/TableIf.java index 0abeaa0886..e21a4ee289 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/catalog/TableIf.java +++ b/fe/fe-core/src/main/java/org/apache/doris/catalog/TableIf.java @@ -236,6 +236,36 @@ public interface TableIf { } } + default void dropConstraint(String name) { + writeLock(); + try { + Map constraintMap = getConstraintsMap(); + if (!constraintMap.containsKey(name)) { + throw new AnalysisException( + String.format("Unknown constraint %s on table %s.", name, this.getName())); + } + Constraint constraint = constraintMap.get(name); + constraintMap.remove(name); + if (constraint instanceof PrimaryKeyConstraint) { + ((PrimaryKeyConstraint) constraint).getForeignTables() + .forEach(t -> t.dropFKReferringPK(this, (PrimaryKeyConstraint) constraint)); + } + } finally { + writeUnlock(); + } + } + + default void dropFKReferringPK(TableIf table, PrimaryKeyConstraint constraint) { + writeLock(); + try { + Map constraintMap = getConstraintsMap(); + constraintMap.entrySet().removeIf(e -> e.getValue() instanceof ForeignKeyConstraint + && ((ForeignKeyConstraint) e.getValue()).isReferringPK(table, constraint)); + } finally { + writeUnlock(); + } + } + /** * return true if this kind of table need read lock when doing query plan. * diff --git a/fe/fe-core/src/main/java/org/apache/doris/catalog/constraint/ForeignKeyConstraint.java b/fe/fe-core/src/main/java/org/apache/doris/catalog/constraint/ForeignKeyConstraint.java index 841f53622a..cae63abe13 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/catalog/constraint/ForeignKeyConstraint.java +++ b/fe/fe-core/src/main/java/org/apache/doris/catalog/constraint/ForeignKeyConstraint.java @@ -69,6 +69,11 @@ public class ForeignKeyConstraint extends Constraint { return referencedTable.toTableIf(); } + public Boolean isReferringPK(TableIf table, PrimaryKeyConstraint constraint) { + return constraint.getPrimaryKeyNames().equals(getForeignKeyNames()) + && getReferencedTable().equals(table); + } + @Override public int hashCode() { return Objects.hash(foreignToReference, referencedTable); diff --git a/fe/fe-core/src/main/java/org/apache/doris/catalog/constraint/PrimaryKeyConstraint.java b/fe/fe-core/src/main/java/org/apache/doris/catalog/constraint/PrimaryKeyConstraint.java index 94085e49d9..02d59788ad 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/catalog/constraint/PrimaryKeyConstraint.java +++ b/fe/fe-core/src/main/java/org/apache/doris/catalog/constraint/PrimaryKeyConstraint.java @@ -46,7 +46,7 @@ public class PrimaryKeyConstraint extends Constraint { foreignTables.add(new TableIdentifier(table)); } - public List getReferenceTables() { + public List getForeignTables() { return foreignTables.stream() .map(TableIdentifier::toTableIf) .collect(ImmutableList.toImmutableList()); diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/parser/LogicalPlanBuilder.java b/fe/fe-core/src/main/java/org/apache/doris/nereids/parser/LogicalPlanBuilder.java index 1feb92bd54..45a49ba798 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/nereids/parser/LogicalPlanBuilder.java +++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/parser/LogicalPlanBuilder.java @@ -75,6 +75,7 @@ import org.apache.doris.nereids.DorisParser.Date_subContext; import org.apache.doris.nereids.DorisParser.DecimalLiteralContext; import org.apache.doris.nereids.DorisParser.DeleteContext; import org.apache.doris.nereids.DorisParser.DereferenceContext; +import org.apache.doris.nereids.DorisParser.DropConstraintContext; import org.apache.doris.nereids.DorisParser.DropMTMVContext; import org.apache.doris.nereids.DorisParser.ElementAtContext; import org.apache.doris.nereids.DorisParser.ExistContext; @@ -319,6 +320,7 @@ import org.apache.doris.nereids.trees.plans.commands.CreateMTMVCommand; import org.apache.doris.nereids.trees.plans.commands.CreatePolicyCommand; import org.apache.doris.nereids.trees.plans.commands.CreateTableCommand; import org.apache.doris.nereids.trees.plans.commands.DeleteCommand; +import org.apache.doris.nereids.trees.plans.commands.DropConstraintCommand; import org.apache.doris.nereids.trees.plans.commands.DropMTMVCommand; import org.apache.doris.nereids.trees.plans.commands.ExplainCommand; import org.apache.doris.nereids.trees.plans.commands.ExplainCommand.ExplainLevel; @@ -621,8 +623,7 @@ public class LogicalPlanBuilder extends DorisParserBaseVisitor { @Override public LogicalPlan visitAddConstraint(AddConstraintContext ctx) { LogicalPlan curTable = visitRelation(ctx.table); - ImmutableList slots = ctx.constraint().slots.stream() - .map(RuleContext::getText) + ImmutableList slots = visitIdentifierList(ctx.constraint().slots).stream() .map(UnboundSlot::new) .collect(ImmutableList.toImmutableList()); Constraint constraint; @@ -631,19 +632,24 @@ public class LogicalPlanBuilder extends DorisParserBaseVisitor { } else if (ctx.constraint().PRIMARY() != null) { constraint = Constraint.newPrimaryKeyConstraint(curTable, slots); } else if (ctx.constraint().FOREIGN() != null) { - ImmutableList referenceSlots = ctx.constraint().referenceSlots.stream() - .map(RuleContext::getText) + ImmutableList referencedSlots = visitIdentifierList(ctx.constraint().referencedSlots).stream() .map(UnboundSlot::new) .collect(ImmutableList.toImmutableList()); List nameParts = visitMultipartIdentifier(ctx.constraint().referenceTable); LogicalPlan referenceTable = new UnboundRelation(StatementScopeIdGenerator.newRelationId(), nameParts); - constraint = Constraint.newForeignKeyConstraint(curTable, slots, referenceTable, referenceSlots); + constraint = Constraint.newForeignKeyConstraint(curTable, slots, referenceTable, referencedSlots); } else { throw new AnalysisException("Unsupported constraint " + ctx.getText()); } return new AddConstraintCommand(ctx.constraintName.getText().toLowerCase(), constraint); } + @Override + public LogicalPlan visitDropConstraint(DropConstraintContext ctx) { + LogicalPlan curTable = visitRelation(ctx.table); + return new DropConstraintCommand(ctx.constraintName.getText().toLowerCase(), curTable); + } + @Override public LogicalPlan visitUpdate(UpdateContext ctx) { LogicalPlan query = LogicalPlanBuilderAssistant.withCheckPolicy(new UnboundRelation( diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/PlanType.java b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/PlanType.java index cda880c227..32d9915b52 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/PlanType.java +++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/PlanType.java @@ -127,6 +127,7 @@ public enum PlanType { CREATE_MTMV_COMMAND, ALTER_MTMV_COMMAND, ADD_CONSTRAINT_COMMAND, + DROP_CONSTRAINT_COMMAND, REFRESH_MTMV_COMMAND, DROP_MTMV_COMMAND } diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/AddConstraintCommand.java b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/AddConstraintCommand.java index 7f8937c018..4086758e74 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/AddConstraintCommand.java +++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/AddConstraintCommand.java @@ -41,7 +41,7 @@ import org.apache.logging.log4j.Logger; import java.util.Set; /** - * create multi table materialized view + * add constraint command */ public class AddConstraintCommand extends Command implements ForwardWithSync { @@ -62,10 +62,10 @@ public class AddConstraintCommand extends Command implements ForwardWithSync { public void run(ConnectContext ctx, StmtExecutor executor) throws Exception { Pair, TableIf> columnsAndTable = extractColumnsAndTable(ctx, constraint.toProject()); if (constraint.isForeignKey()) { - Pair, TableIf> foreignColumnsAndTable + Pair, TableIf> referencedColumnsAndTable = extractColumnsAndTable(ctx, constraint.toReferenceProject()); columnsAndTable.second.addForeignConstraint(name, columnsAndTable.first, - foreignColumnsAndTable.second, foreignColumnsAndTable.first); + referencedColumnsAndTable.second, referencedColumnsAndTable.first); } else if (constraint.isPrimaryKey()) { columnsAndTable.second.addPrimaryKeyConstraint(name, columnsAndTable.first); } else if (constraint.isUnique()) { diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/DropConstraintCommand.java b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/DropConstraintCommand.java new file mode 100644 index 0000000000..202f53197d --- /dev/null +++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/DropConstraintCommand.java @@ -0,0 +1,82 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 org.apache.doris.nereids.trees.plans.commands; + +import org.apache.doris.catalog.Table; +import org.apache.doris.catalog.TableIf; +import org.apache.doris.nereids.NereidsPlanner; +import org.apache.doris.nereids.exceptions.AnalysisException; +import org.apache.doris.nereids.properties.PhysicalProperties; +import org.apache.doris.nereids.trees.plans.Plan; +import org.apache.doris.nereids.trees.plans.PlanType; +import org.apache.doris.nereids.trees.plans.commands.ExplainCommand.ExplainLevel; +import org.apache.doris.nereids.trees.plans.logical.LogicalCatalogRelation; +import org.apache.doris.nereids.trees.plans.logical.LogicalPlan; +import org.apache.doris.nereids.trees.plans.visitor.PlanVisitor; +import org.apache.doris.qe.ConnectContext; +import org.apache.doris.qe.StmtExecutor; + +import com.google.common.base.Preconditions; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.util.Set; + +/** + * drop constraint command + */ +public class DropConstraintCommand extends Command implements ForwardWithSync { + + public static final Logger LOG = LogManager.getLogger(DropConstraintCommand.class); + private final String name; + private final LogicalPlan plan; + + /** + * constructor + */ + public DropConstraintCommand(String name, LogicalPlan plan) { + super(PlanType.DROP_CONSTRAINT_COMMAND); + this.name = name; + this.plan = plan; + } + + @Override + public void run(ConnectContext ctx, StmtExecutor executor) throws Exception { + TableIf table = extractTable(ctx, plan); + table.dropConstraint(name); + } + + private TableIf extractTable(ConnectContext ctx, LogicalPlan plan) { + NereidsPlanner planner = new NereidsPlanner(ctx.getStatementContext()); + Plan analyzedPlan = planner.plan(plan, PhysicalProperties.ANY, ExplainLevel.ANALYZED_PLAN); + Set logicalCatalogRelationSet = analyzedPlan + .collect(LogicalCatalogRelation.class::isInstance); + if (logicalCatalogRelationSet.size() != 1) { + throw new AnalysisException("Can not found table when dropping constraint"); + } + LogicalCatalogRelation catalogRelation = logicalCatalogRelationSet.iterator().next(); + Preconditions.checkArgument(catalogRelation.getTable() instanceof Table, + "Don't support table ", catalogRelation.getTable()); + return catalogRelation.getTable(); + } + + @Override + public R accept(PlanVisitor visitor, C context) { + return visitor.visitDropConstraintCommand(this, context); + } +} diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/visitor/CommandVisitor.java b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/visitor/CommandVisitor.java index b011cee749..8641005115 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/visitor/CommandVisitor.java +++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/visitor/CommandVisitor.java @@ -24,6 +24,7 @@ import org.apache.doris.nereids.trees.plans.commands.CreateMTMVCommand; import org.apache.doris.nereids.trees.plans.commands.CreatePolicyCommand; import org.apache.doris.nereids.trees.plans.commands.CreateTableCommand; import org.apache.doris.nereids.trees.plans.commands.DeleteCommand; +import org.apache.doris.nereids.trees.plans.commands.DropConstraintCommand; import org.apache.doris.nereids.trees.plans.commands.DropMTMVCommand; import org.apache.doris.nereids.trees.plans.commands.ExplainCommand; import org.apache.doris.nereids.trees.plans.commands.ExportCommand; @@ -82,6 +83,10 @@ public interface CommandVisitor { return visitCommand(addConstraintCommand, context); } + default R visitDropConstraintCommand(DropConstraintCommand dropConstraintCommand, C context) { + return visitCommand(dropConstraintCommand, context); + } + default R visitRefreshMTMVCommand(RefreshMTMVCommand refreshMTMVCommand, C context) { return visitCommand(refreshMTMVCommand, context); } diff --git a/fe/fe-core/src/test/java/org/apache/doris/nereids/trees/plans/AddConstraintTest.java b/fe/fe-core/src/test/java/org/apache/doris/nereids/trees/plans/ConstraintTest.java similarity index 68% rename from fe/fe-core/src/test/java/org/apache/doris/nereids/trees/plans/AddConstraintTest.java rename to fe/fe-core/src/test/java/org/apache/doris/nereids/trees/plans/ConstraintTest.java index c9c394a51e..9a61f6575d 100644 --- a/fe/fe-core/src/test/java/org/apache/doris/nereids/trees/plans/AddConstraintTest.java +++ b/fe/fe-core/src/test/java/org/apache/doris/nereids/trees/plans/ConstraintTest.java @@ -26,6 +26,7 @@ import org.apache.doris.catalog.constraint.UniqueConstraint; import org.apache.doris.nereids.parser.NereidsParser; import org.apache.doris.nereids.trees.expressions.SlotReference; import org.apache.doris.nereids.trees.plans.commands.AddConstraintCommand; +import org.apache.doris.nereids.trees.plans.commands.DropConstraintCommand; import org.apache.doris.nereids.util.PlanChecker; import org.apache.doris.nereids.util.PlanPatternMatchSupported; import org.apache.doris.utframe.TestWithFeService; @@ -37,7 +38,7 @@ import org.junit.jupiter.api.Test; import java.util.List; import java.util.Set; -class AddConstraintTest extends TestWithFeService implements PlanPatternMatchSupported { +class ConstraintTest extends TestWithFeService implements PlanPatternMatchSupported { @Override public void runBeforeAll() throws Exception { createDatabase("test"); @@ -63,10 +64,10 @@ class AddConstraintTest extends TestWithFeService implements PlanPatternMatchSup } @Test - void addPrimaryKeyConstraintTest() throws Exception { - AddConstraintCommand command = (AddConstraintCommand) new NereidsParser().parseSingle( + void primaryKeyConstraintTest() throws Exception { + AddConstraintCommand addCommand = (AddConstraintCommand) new NereidsParser().parseSingle( "alter table t1 add constraint pk primary key (k1)"); - command.run(connectContext, null); + addCommand.run(connectContext, null); PlanChecker.from(connectContext).parse("select * from t1").analyze().matches(logicalOlapScan().when(o -> { Constraint c = o.getTable().getConstraint("pk"); if (c instanceof PrimaryKeyConstraint) { @@ -75,10 +76,16 @@ class AddConstraintTest extends TestWithFeService implements PlanPatternMatchSup } return false; })); + + DropConstraintCommand dropCommand = (DropConstraintCommand) new NereidsParser().parseSingle( + "alter table t1 drop constraint pk"); + dropCommand.run(connectContext, null); + PlanChecker.from(connectContext).parse("select * from t1").analyze().matches( + logicalOlapScan().when(o -> o.getTable().getConstraintsMap().isEmpty())); } @Test - void addUniqueConstraintTest() throws Exception { + void uniqueConstraintTest() throws Exception { AddConstraintCommand command = (AddConstraintCommand) new NereidsParser().parseSingle( "alter table t1 add constraint un unique (k1)"); command.run(connectContext, null); @@ -90,12 +97,18 @@ class AddConstraintTest extends TestWithFeService implements PlanPatternMatchSup } return false; })); + + DropConstraintCommand dropCommand = (DropConstraintCommand) new NereidsParser().parseSingle( + "alter table t1 drop constraint un"); + dropCommand.run(connectContext, null); + PlanChecker.from(connectContext).parse("select * from t1").analyze().matches( + logicalOlapScan().when(o -> o.getTable().getConstraintsMap().isEmpty())); } @Test - void addForeignKeyConstraintTest() throws Exception { + void foreignKeyConstraintTest() throws Exception { AddConstraintCommand command = (AddConstraintCommand) new NereidsParser().parseSingle( - "alter table t1 add constraint fk foreign key (k1) references t2(k1)"); + "alter table t1 add constraint fk foreign key (k1) references t2 (k1)"); try { command.run(connectContext, null); } catch (Exception e) { @@ -123,12 +136,30 @@ class AddConstraintTest extends TestWithFeService implements PlanPatternMatchSup Constraint c = o.getTable().getConstraint("pk"); if (c instanceof PrimaryKeyConstraint) { Set columnNames = ((PrimaryKeyConstraint) c).getPrimaryKeyNames(); - List referenceTable = ((PrimaryKeyConstraint) c).getReferenceTables(); + List foreignTables = ((PrimaryKeyConstraint) c).getForeignTables(); return columnNames.size() == 2 && columnNames.equals(Sets.newHashSet("k1", "k2")) - && referenceTable.size() == 1 && referenceTable.get(0).getName().equals("t1"); + && foreignTables.size() == 1 && foreignTables.get(0).getName().equals("t1"); } return false; })); + + // drop fk + DropConstraintCommand dropCommand = (DropConstraintCommand) new NereidsParser().parseSingle( + "alter table t1 drop constraint fk"); + dropCommand.run(connectContext, null); + PlanChecker.from(connectContext).parse("select * from t1").analyze().matches( + logicalOlapScan().when(o -> o.getTable().getConstraintsMap().isEmpty())); + // drop pk and fk referenced it also should be dropped + ((AddConstraintCommand) new NereidsParser().parseSingle( + "alter table t1 add constraint fk foreign key (k1, k2) references t2(k1, k2)")).run(connectContext, + null); + ((DropConstraintCommand) new NereidsParser().parseSingle("alter table t2 drop constraint pk")) + .run(connectContext, null); + + PlanChecker.from(connectContext).parse("select * from t1").analyze().matches( + logicalOlapScan().when(o -> o.getTable().getConstraintsMap().isEmpty())); + PlanChecker.from(connectContext).parse("select * from t2").analyze().matches( + logicalOlapScan().when(o -> o.getTable().getConstraintsMap().isEmpty())); } }