This is an automated email from the ASF dual-hosted git repository.

morrysnow pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/doris.git


The following commit(s) were added to refs/heads/master by this push:
     new e868c990ffa [feature](Nereids) support add constraint on table (#27627)
e868c990ffa is described below

commit e868c990ffa485d86fe34856e36c8bc697871a43
Author: 谢健 <jianx...@gmail.com>
AuthorDate: Fri Dec 1 13:28:48 2023 +0800

    [feature](Nereids) support add constraint on table (#27627)
    
    support add constraint on the table including
    - primary key constraint
    - unique constrain
    - foreign key constraint
---
 .../antlr4/org/apache/doris/nereids/DorisLexer.g4  |   4 +
 .../antlr4/org/apache/doris/nereids/DorisParser.g4 |  13 ++
 .../main/java/org/apache/doris/catalog/Table.java  |  14 +++
 .../java/org/apache/doris/catalog/TableIf.java     |  83 +++++++++++++
 .../doris/catalog/constraint/Constraint.java       |  39 ++++++
 .../catalog/constraint/ForeignKeyConstraint.java   |  89 ++++++++++++++
 .../catalog/constraint/PrimaryKeyConstraint.java   |  71 +++++++++++
 .../doris/catalog/constraint/TableIdentifier.java  |  68 +++++++++++
 .../doris/catalog/constraint/UniqueConstraint.java |  53 ++++++++
 .../doris/nereids/parser/LogicalPlanBuilder.java   |  30 +++++
 .../apache/doris/nereids/trees/plans/PlanType.java |   1 +
 .../trees/plans/commands/AddConstraintCommand.java | 101 ++++++++++++++++
 .../nereids/trees/plans/commands/Constraint.java   | 115 ++++++++++++++++++
 .../trees/plans/visitor/CommandVisitor.java        |   5 +
 .../nereids/trees/plans/AddConstraintTest.java     | 134 +++++++++++++++++++++
 .../apache/doris/utframe/TestWithFeService.java    |   1 +
 16 files changed, 821 insertions(+)

diff --git a/fe/fe-core/src/main/antlr4/org/apache/doris/nereids/DorisLexer.g4 
b/fe/fe-core/src/main/antlr4/org/apache/doris/nereids/DorisLexer.g4
index 81b6c5e351d..194b9da7caa 100644
--- a/fe/fe-core/src/main/antlr4/org/apache/doris/nereids/DorisLexer.g4
+++ b/fe/fe-core/src/main/antlr4/org/apache/doris/nereids/DorisLexer.g4
@@ -161,6 +161,7 @@ CONFIG: 'CONFIG';
 CONNECTION: 'CONNECTION';
 CONNECTION_ID: 'CONNECTION_ID';
 CONSISTENT: 'CONSISTENT';
+CONSTRAINT: 'CONSTRAINT';
 CONVERT: 'CONVERT';
 COPY: 'COPY';
 COUNT: 'COUNT';
@@ -251,6 +252,7 @@ FLOAT: 'FLOAT';
 FOLLOWER: 'FOLLOWER';
 FOLLOWING: 'FOLLOWING';
 FOR: 'FOR';
+FOREIGN: 'FOREIGN';
 FORCE: 'FORCE';
 FORMAT: 'FORMAT';
 FREE: 'FREE';
@@ -401,6 +403,7 @@ PLUGINS: 'PLUGINS';
 POLICY: 'POLICY';
 PRECEDING: 'PRECEDING';
 PREPARE: 'PREPARE';
+PRIMARY: 'PRIMARY';
 PROC: 'PROC';
 PROCEDURE: 'PROCEDURE';
 PROCESSLIST: 'PROCESSLIST';
@@ -419,6 +422,7 @@ REBALANCE: 'REBALANCE';
 RECOVER: 'RECOVER';
 RECYCLE: 'RECYCLE';
 REFRESH: 'REFRESH';
+REFERENCES: 'REFERENCES';
 REGEXP: 'REGEXP';
 RELEASE: 'RELEASE';
 RENAME: 'RENAME';
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 1dca96c1eca..64ff9ec366a 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
@@ -96,6 +96,19 @@ statement
        | (REFRESH (refreshMethod | refreshTrigger | refreshMethod 
refreshTrigger))
        | (SET  LEFT_PAREN fileProperties=propertyItemList RIGHT_PAREN))   
#alterMTMV
     | DROP MATERIALIZED VIEW (IF EXISTS)? mvName=multipartIdentifier      
#dropMTMV
+    | ALTER TABLE table=relation
+        ADD CONSTRAINT constraintName=errorCapturingIdentifier
+        constraint                                                        
#addConstraint
+    | ALTER TABLE table=relation
+        DROP CONSTRAINT constraintName=errorCapturingIdentifier           
#dropConstraint
+    ;
+
+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
+        REFERENCES referenceTable=multipartIdentifier
+        LEFT_PAREN referenceSlots+=errorCapturingIdentifier (COMMA 
referenceSlots+=errorCapturingIdentifier)* RIGHT_PAREN
     ;
 
 dataDesc
diff --git a/fe/fe-core/src/main/java/org/apache/doris/catalog/Table.java 
b/fe/fe-core/src/main/java/org/apache/doris/catalog/Table.java
index be4d846e5ec..a1dd3b915eb 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/catalog/Table.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/catalog/Table.java
@@ -19,6 +19,7 @@ package org.apache.doris.catalog;
 
 import org.apache.doris.alter.AlterCancelException;
 import org.apache.doris.analysis.CreateTableStmt;
+import org.apache.doris.catalog.constraint.Constraint;
 import org.apache.doris.common.DdlException;
 import org.apache.doris.common.MetaNotFoundException;
 import org.apache.doris.common.io.Text;
@@ -48,6 +49,7 @@ import java.io.DataOutput;
 import java.io.IOException;
 import java.time.Instant;
 import java.util.Collections;
+import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Optional;
@@ -115,6 +117,9 @@ public abstract class Table extends MetaObject implements 
Writable, TableIf {
     // sql for creating this table, default is "";
     protected String ddlSql = "";
 
+    @SerializedName(value = "constraints")
+    private HashMap<String, Constraint> constraintsMap = new HashMap<>();
+
     public Table(TableType type) {
         this.type = type;
         this.fullSchema = Lists.newArrayList();
@@ -291,6 +296,15 @@ public abstract class Table extends MetaObject implements 
Writable, TableIf {
         }
     }
 
+    public Constraint getConstraint(String name) {
+        return constraintsMap.get(name);
+    }
+
+    @Override
+    public Map<String, Constraint> getConstraintsMap() {
+        return constraintsMap;
+    }
+
     public TableType getType() {
         return type;
     }
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 a958ff50d07..0abeaa0886f 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
@@ -18,14 +18,21 @@
 package org.apache.doris.catalog;
 
 import org.apache.doris.alter.AlterCancelException;
+import org.apache.doris.catalog.constraint.Constraint;
+import org.apache.doris.catalog.constraint.ForeignKeyConstraint;
+import org.apache.doris.catalog.constraint.PrimaryKeyConstraint;
+import org.apache.doris.catalog.constraint.UniqueConstraint;
 import org.apache.doris.common.DdlException;
 import org.apache.doris.common.MetaNotFoundException;
+import org.apache.doris.nereids.exceptions.AnalysisException;
 import org.apache.doris.statistics.AnalysisInfo;
 import org.apache.doris.statistics.BaseAnalysisTask;
 import org.apache.doris.statistics.ColumnStatistic;
 import org.apache.doris.statistics.TableStatsMeta;
 import org.apache.doris.thrift.TTableDescriptor;
 
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Lists;
 import com.google.common.collect.Sets;
 import org.apache.logging.log4j.LogManager;
@@ -153,6 +160,82 @@ public interface TableIf {
 
     void write(DataOutput out) throws IOException;
 
+    default Map<String, Constraint> getConstraintsMap() {
+        throw new RuntimeException(String.format("Not implemented constraint 
for table %s", this));
+    }
+
+    // Note this function is not thread safe
+    default void checkConstraintNotExistence(String name, Constraint 
primaryKeyConstraint,
+            Map<String, Constraint> constraintMap) {
+        if (constraintMap.containsKey(name)) {
+            throw new RuntimeException(String.format("Constraint name %s has 
existed", name));
+        }
+        for (Map.Entry<String, Constraint> entry : constraintMap.entrySet()) {
+            if (entry.getValue().equals(primaryKeyConstraint)) {
+                throw new RuntimeException(String.format(
+                        "Constraint %s has existed, named %s", 
primaryKeyConstraint, entry.getKey()));
+            }
+        }
+    }
+
+    default void addUniqueConstraint(String name, ImmutableList<String> 
columns) {
+        writeLock();
+        try {
+            Map<String, Constraint> constraintMap = getConstraintsMap();
+            UniqueConstraint uniqueConstraint =  new UniqueConstraint(name, 
ImmutableSet.copyOf(columns));
+            checkConstraintNotExistence(name, uniqueConstraint, constraintMap);
+            constraintMap.put(name, uniqueConstraint);
+        } finally {
+            writeUnlock();
+        }
+    }
+
+    default void addPrimaryKeyConstraint(String name, ImmutableList<String> 
columns) {
+        writeLock();
+        try {
+            Map<String, Constraint> constraintMap = getConstraintsMap();
+            PrimaryKeyConstraint primaryKeyConstraint = new 
PrimaryKeyConstraint(name, ImmutableSet.copyOf(columns));
+            checkConstraintNotExistence(name, primaryKeyConstraint, 
constraintMap);
+            constraintMap.put(name, primaryKeyConstraint);
+        } finally {
+            writeUnlock();
+        }
+    }
+
+    default void updatePrimaryKeyForForeignKey(PrimaryKeyConstraint 
requirePrimaryKey, TableIf referencedTable) {
+        referencedTable.writeLock();
+        try {
+            Optional<Constraint> primaryKeyConstraint = 
referencedTable.getConstraintsMap().values().stream()
+                    .filter(requirePrimaryKey::equals)
+                    .findFirst();
+            if (!primaryKeyConstraint.isPresent()) {
+                throw new AnalysisException(String.format(
+                        "Foreign key constraint requires a primary key 
constraint %s in %s",
+                        requirePrimaryKey.getPrimaryKeyNames(), 
referencedTable.getName()));
+            }
+            ((PrimaryKeyConstraint) 
(primaryKeyConstraint.get())).addForeignTable(this);
+        } finally {
+            referencedTable.writeUnlock();
+        }
+    }
+
+    default void addForeignConstraint(String name, ImmutableList<String> 
columns,
+            TableIf referencedTable, ImmutableList<String> referencedColumns) {
+        writeLock();
+        try {
+            Map<String, Constraint> constraintMap = getConstraintsMap();
+            ForeignKeyConstraint foreignKeyConstraint =
+                    new ForeignKeyConstraint(name, columns, referencedTable, 
referencedColumns);
+            checkConstraintNotExistence(name, foreignKeyConstraint, 
constraintMap);
+            PrimaryKeyConstraint requirePrimaryKey = new 
PrimaryKeyConstraint(name,
+                    foreignKeyConstraint.getReferencedColumnNames());
+            updatePrimaryKeyForForeignKey(requirePrimaryKey, referencedTable);
+            constraintMap.put(name, foreignKeyConstraint);
+        } 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/Constraint.java 
b/fe/fe-core/src/main/java/org/apache/doris/catalog/constraint/Constraint.java
new file mode 100644
index 00000000000..e7333372746
--- /dev/null
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/catalog/constraint/Constraint.java
@@ -0,0 +1,39 @@
+// 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.catalog.constraint;
+
+public abstract class Constraint {
+    public enum ConstraintType {
+        FOREIGN_KEY,
+        PRIMARY_KEY,
+        UNIQUE
+    }
+
+    private final String name;
+    private final ConstraintType type;
+
+
+    protected Constraint(ConstraintType type, String name) {
+        this.name = name;
+        this.type = type;
+    }
+
+    public String getName() {
+        return name;
+    }
+}
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
new file mode 100644
index 00000000000..841f53622a3
--- /dev/null
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/catalog/constraint/ForeignKeyConstraint.java
@@ -0,0 +1,89 @@
+// 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.catalog.constraint;
+
+import org.apache.doris.catalog.Column;
+import org.apache.doris.catalog.TableIf;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableMap.Builder;
+import com.google.common.collect.ImmutableSet;
+
+import java.util.List;
+import java.util.Objects;
+
+public class ForeignKeyConstraint extends Constraint {
+    private final ImmutableMap<String, String> foreignToReference;
+    private final TableIdentifier referencedTable;
+
+    public ForeignKeyConstraint(String name, List<String> columns,
+            TableIf refTable, List<String> referencedColumns) {
+        super(ConstraintType.FOREIGN_KEY, name);
+        ImmutableMap.Builder<String, String> builder = new Builder<>();
+        Preconditions.checkArgument(columns.size() == referencedColumns.size(),
+                "Foreign keys' size must be same as the size of reference 
keys");
+        Preconditions.checkArgument(ImmutableSet.copyOf(columns).size() == 
columns.size(),
+                "Foreign keys contains duplicate slots.");
+        
Preconditions.checkArgument(ImmutableSet.copyOf(referencedColumns).size() == 
referencedColumns.size(),
+                "Reference keys contains duplicate slots.");
+        this.referencedTable = new TableIdentifier(refTable);
+        for (int i = 0; i < columns.size(); i++) {
+            builder.put(columns.get(i), referencedColumns.get(i));
+        }
+        this.foreignToReference = builder.build();
+    }
+
+    public ImmutableSet<String> getForeignKeyNames() {
+        return foreignToReference.keySet();
+    }
+
+    public ImmutableSet<String> getReferencedColumnNames() {
+        return ImmutableSet.copyOf(foreignToReference.values());
+    }
+
+    public String getReferencedColumnName(String column) {
+        return foreignToReference.get(column);
+    }
+
+    public Column getReferencedColumn(String column) {
+        return getReferencedTable().getColumn(getReferencedColumnName(column));
+    }
+
+    public TableIf getReferencedTable() {
+        return referencedTable.toTableIf();
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(foreignToReference, referencedTable);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj == null || getClass() != obj.getClass()) {
+            return false;
+        }
+        ForeignKeyConstraint other = (ForeignKeyConstraint) obj;
+        return Objects.equals(foreignToReference, other.foreignToReference)
+                && Objects.equals(referencedTable, other.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
new file mode 100644
index 00000000000..94085e49d90
--- /dev/null
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/catalog/constraint/PrimaryKeyConstraint.java
@@ -0,0 +1,71 @@
+// 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.catalog.constraint;
+
+import org.apache.doris.catalog.TableIf;
+
+import com.google.common.base.Objects;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+public class PrimaryKeyConstraint extends Constraint {
+    private final ImmutableSet<String> columns;
+
+    // record the foreign table which references the primary key
+    private final Set<TableIdentifier> foreignTables = new HashSet<>();
+
+    public PrimaryKeyConstraint(String name, Set<String> columns) {
+        super(ConstraintType.PRIMARY_KEY, name);
+        this.columns = ImmutableSet.copyOf(columns);
+    }
+
+    public ImmutableSet<String> getPrimaryKeyNames() {
+        return columns;
+    }
+
+    public void addForeignTable(TableIf table) {
+        foreignTables.add(new TableIdentifier(table));
+    }
+
+    public List<TableIf> getReferenceTables() {
+        return foreignTables.stream()
+                .map(TableIdentifier::toTableIf)
+                .collect(ImmutableList.toImmutableList());
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+        PrimaryKeyConstraint that = (PrimaryKeyConstraint) o;
+        return columns.equals(that.columns);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hashCode(columns);
+    }
+}
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/catalog/constraint/TableIdentifier.java
 
b/fe/fe-core/src/main/java/org/apache/doris/catalog/constraint/TableIdentifier.java
new file mode 100644
index 00000000000..4d5061a08f0
--- /dev/null
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/catalog/constraint/TableIdentifier.java
@@ -0,0 +1,68 @@
+// 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.catalog.constraint;
+
+import org.apache.doris.catalog.DatabaseIf;
+import org.apache.doris.catalog.Env;
+import org.apache.doris.catalog.TableIf;
+
+import com.google.common.base.Preconditions;
+
+import java.util.Objects;
+
+class TableIdentifier {
+    private final long databaseId;
+    private final long tableId;
+
+    TableIdentifier(TableIf tableIf) {
+        Preconditions.checkArgument(tableIf != null,
+                "Table can not be null in constraint");
+        databaseId = tableIf.getDatabase().getId();
+        tableId = tableIf.getId();
+    }
+
+    TableIf toTableIf() {
+        DatabaseIf databaseIf = 
Env.getCurrentEnv().getCurrentCatalog().getDbNullable(databaseId);
+        if (databaseIf == null) {
+            throw new RuntimeException(String.format("Can not find database %s 
in constraint", databaseId));
+        }
+        TableIf tableIf = databaseIf.getTableNullable(tableId);
+        if (tableIf == null) {
+            throw new RuntimeException(String.format("Can not find table %s in 
constraint", databaseId));
+        }
+        return tableIf;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+        TableIdentifier that = (TableIdentifier) o;
+        return databaseId == that.databaseId
+                && tableId == that.tableId;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(databaseId, tableId);
+    }
+}
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/catalog/constraint/UniqueConstraint.java
 
b/fe/fe-core/src/main/java/org/apache/doris/catalog/constraint/UniqueConstraint.java
new file mode 100644
index 00000000000..975ff0937b3
--- /dev/null
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/catalog/constraint/UniqueConstraint.java
@@ -0,0 +1,53 @@
+// 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.catalog.constraint;
+
+import com.google.common.base.Objects;
+import com.google.common.collect.ImmutableSet;
+
+import java.util.Set;
+
+public class UniqueConstraint extends Constraint {
+    private final ImmutableSet<String> columns;
+
+    public UniqueConstraint(String name, Set<String> columns) {
+        super(ConstraintType.UNIQUE, name);
+        this.columns = ImmutableSet.copyOf(columns);
+    }
+
+    public ImmutableSet<String> getUniqueColumnNames() {
+        return columns;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+        UniqueConstraint that = (UniqueConstraint) o;
+        return columns.equals(that.columns);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hashCode(columns);
+    }
+}
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 1a18d0d4870..1feb92bd54a 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
@@ -37,6 +37,7 @@ import org.apache.doris.mtmv.MTMVRefreshInfo;
 import org.apache.doris.mtmv.MTMVRefreshSchedule;
 import org.apache.doris.mtmv.MTMVRefreshTriggerInfo;
 import org.apache.doris.nereids.DorisParser;
+import org.apache.doris.nereids.DorisParser.AddConstraintContext;
 import org.apache.doris.nereids.DorisParser.AggClauseContext;
 import org.apache.doris.nereids.DorisParser.AliasQueryContext;
 import org.apache.doris.nereids.DorisParser.AliasedQueryContext;
@@ -232,6 +233,7 @@ import 
org.apache.doris.nereids.trees.expressions.OrderExpression;
 import org.apache.doris.nereids.trees.expressions.Properties;
 import org.apache.doris.nereids.trees.expressions.Regexp;
 import org.apache.doris.nereids.trees.expressions.ScalarSubquery;
+import org.apache.doris.nereids.trees.expressions.Slot;
 import org.apache.doris.nereids.trees.expressions.StatementScopeIdGenerator;
 import org.apache.doris.nereids.trees.expressions.Subtract;
 import org.apache.doris.nereids.trees.expressions.TimestampArithmetic;
@@ -309,8 +311,10 @@ import org.apache.doris.nereids.trees.plans.LimitPhase;
 import org.apache.doris.nereids.trees.plans.Plan;
 import org.apache.doris.nereids.trees.plans.algebra.Aggregate;
 import org.apache.doris.nereids.trees.plans.algebra.SetOperation.Qualifier;
+import org.apache.doris.nereids.trees.plans.commands.AddConstraintCommand;
 import org.apache.doris.nereids.trees.plans.commands.AlterMTMVCommand;
 import org.apache.doris.nereids.trees.plans.commands.Command;
+import org.apache.doris.nereids.trees.plans.commands.Constraint;
 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;
@@ -614,6 +618,32 @@ public class LogicalPlanBuilder extends 
DorisParserBaseVisitor<Object> {
         return new AlterMTMVCommand(alterMTMVInfo);
     }
 
+    @Override
+    public LogicalPlan visitAddConstraint(AddConstraintContext ctx) {
+        LogicalPlan curTable = visitRelation(ctx.table);
+        ImmutableList<Slot> slots = ctx.constraint().slots.stream()
+                .map(RuleContext::getText)
+                .map(UnboundSlot::new)
+                .collect(ImmutableList.toImmutableList());
+        Constraint constraint;
+        if (ctx.constraint().UNIQUE() != null) {
+            constraint = Constraint.newUniqueConstraint(curTable, slots);
+        } else if (ctx.constraint().PRIMARY() != null) {
+            constraint = Constraint.newPrimaryKeyConstraint(curTable, slots);
+        } else if (ctx.constraint().FOREIGN() != null) {
+            ImmutableList<Slot> referenceSlots = 
ctx.constraint().referenceSlots.stream()
+                    .map(RuleContext::getText)
+                    .map(UnboundSlot::new)
+                    .collect(ImmutableList.toImmutableList());
+            List<String> nameParts = 
visitMultipartIdentifier(ctx.constraint().referenceTable);
+            LogicalPlan referenceTable = new 
UnboundRelation(StatementScopeIdGenerator.newRelationId(), nameParts);
+            constraint = Constraint.newForeignKeyConstraint(curTable, slots, 
referenceTable, referenceSlots);
+        } else {
+            throw new AnalysisException("Unsupported constraint " + 
ctx.getText());
+        }
+        return new 
AddConstraintCommand(ctx.constraintName.getText().toLowerCase(), constraint);
+    }
+
     @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 c2db676a1a7..cda880c2277 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
@@ -126,6 +126,7 @@ public enum PlanType {
     UPDATE_COMMAND,
     CREATE_MTMV_COMMAND,
     ALTER_MTMV_COMMAND,
+    ADD_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
new file mode 100644
index 00000000000..7f8937c0189
--- /dev/null
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/AddConstraintCommand.java
@@ -0,0 +1,101 @@
+// 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.common.Pair;
+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.expressions.SlotReference;
+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 com.google.common.collect.ImmutableList;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+import java.util.Set;
+
+/**
+ * create multi table materialized view
+ */
+public class AddConstraintCommand extends Command implements ForwardWithSync {
+
+    public static final Logger LOG = 
LogManager.getLogger(AddConstraintCommand.class);
+    private final String name;
+    private final Constraint constraint;
+
+    /**
+     * constructor
+     */
+    public AddConstraintCommand(String name, Constraint constraint) {
+        super(PlanType.ADD_CONSTRAINT_COMMAND);
+        this.constraint = constraint;
+        this.name = name;
+    }
+
+    @Override
+    public void run(ConnectContext ctx, StmtExecutor executor) throws 
Exception {
+        Pair<ImmutableList<String>, TableIf> columnsAndTable = 
extractColumnsAndTable(ctx, constraint.toProject());
+        if (constraint.isForeignKey()) {
+            Pair<ImmutableList<String>, TableIf> foreignColumnsAndTable
+                    = extractColumnsAndTable(ctx, 
constraint.toReferenceProject());
+            columnsAndTable.second.addForeignConstraint(name, 
columnsAndTable.first,
+                    foreignColumnsAndTable.second, 
foreignColumnsAndTable.first);
+        } else if (constraint.isPrimaryKey()) {
+            columnsAndTable.second.addPrimaryKeyConstraint(name, 
columnsAndTable.first);
+        } else if (constraint.isUnique()) {
+            columnsAndTable.second.addUniqueConstraint(name, 
columnsAndTable.first);
+        }
+    }
+
+    private Pair<ImmutableList<String>, TableIf> 
extractColumnsAndTable(ConnectContext ctx, LogicalPlan plan) {
+        NereidsPlanner planner = new NereidsPlanner(ctx.getStatementContext());
+        Plan analyzedPlan = planner.plan(plan, PhysicalProperties.ANY, 
ExplainLevel.ANALYZED_PLAN);
+        Set<LogicalCatalogRelation> logicalCatalogRelationSet = analyzedPlan
+                .collect(LogicalCatalogRelation.class::isInstance);
+        if (logicalCatalogRelationSet.size() != 1) {
+            throw new AnalysisException("Can not found table in constraint " + 
constraint.toString());
+        }
+        LogicalCatalogRelation catalogRelation = 
logicalCatalogRelationSet.iterator().next();
+        Preconditions.checkArgument(catalogRelation.getTable() instanceof 
Table,
+                "We only support table now but we meet ", 
catalogRelation.getTable());
+        ImmutableList<String> columns = analyzedPlan.getOutput().stream()
+                .map(s -> {
+                    Preconditions.checkArgument(s instanceof SlotReference
+                                    && ((SlotReference) 
s).getColumn().isPresent(),
+                            "Constraint contains a invalid slot ", s);
+                    return ((SlotReference) s).getColumn().get().getName();
+                }).collect(ImmutableList.toImmutableList());
+        return Pair.of(columns, catalogRelation.getTable());
+    }
+
+    @Override
+    public <R, C> R accept(PlanVisitor<R, C> visitor, C context) {
+        return visitor.visitAddConstraintCommand(this, context);
+    }
+}
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/Constraint.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/Constraint.java
new file mode 100644
index 00000000000..f6fe938ec77
--- /dev/null
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/Constraint.java
@@ -0,0 +1,115 @@
+// 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.constraint.Constraint.ConstraintType;
+import org.apache.doris.nereids.trees.expressions.Slot;
+import org.apache.doris.nereids.trees.plans.logical.LogicalPlan;
+import org.apache.doris.nereids.trees.plans.logical.LogicalProject;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableList;
+
+import java.util.Objects;
+import javax.annotation.Nullable;
+
+/**
+ * Constraint
+ */
+public class Constraint {
+    private final ImmutableList<Slot> slots;
+    private final LogicalPlan curTable;
+    private final @Nullable LogicalPlan referenceTable;
+    private final @Nullable ImmutableList<Slot> referenceSlots;
+    private final ConstraintType type;
+
+    Constraint(ConstraintType type, LogicalPlan curTable, ImmutableList<Slot> 
slots) {
+        Preconditions.checkArgument(slots != null && !slots.isEmpty(),
+                "slots of constraint can't be null or empty");
+        this.type = type;
+        this.slots = slots;
+        this.curTable = Objects.requireNonNull(curTable,
+                "table of constraint can't be null");
+        this.referenceTable = null;
+        this.referenceSlots = null;
+    }
+
+    Constraint(LogicalPlan curTable, ImmutableList<Slot> slots,
+            LogicalPlan referenceTable, ImmutableList<Slot> referenceSlotSet) {
+        Preconditions.checkArgument(slots != null && !slots.isEmpty(),
+                "slots of constraint can't be null or empty");
+        this.type = ConstraintType.FOREIGN_KEY;
+        this.slots = slots;
+        this.curTable = Objects.requireNonNull(curTable,
+                "table of constraint can't be null");
+        this.referenceTable = Objects.requireNonNull(referenceTable,
+                "reference table in foreign key can not be null");
+        this.referenceSlots = Objects.requireNonNull(referenceSlotSet,
+                "reference slots in foreign key can not be null");
+        Preconditions.checkArgument(referenceSlots.size() == slots.size(),
+                "Foreign key's size must be same as the size of reference 
slots");
+    }
+
+    public static Constraint newUniqueConstraint(LogicalPlan curTable, 
ImmutableList<Slot> slotSet) {
+        return new Constraint(ConstraintType.UNIQUE, curTable, slotSet);
+    }
+
+    public static Constraint newPrimaryKeyConstraint(LogicalPlan curTable, 
ImmutableList<Slot> slotSet) {
+        return new Constraint(ConstraintType.PRIMARY_KEY, curTable, slotSet);
+    }
+
+    public static Constraint newForeignKeyConstraint(
+            LogicalPlan curTable, ImmutableList<Slot> slotSet,
+            LogicalPlan referenceTable, ImmutableList<Slot> referenceSlotSet) {
+        return new Constraint(curTable, slotSet, referenceTable, 
referenceSlotSet);
+    }
+
+    public boolean isForeignKey() {
+        return type == ConstraintType.FOREIGN_KEY;
+    }
+
+    public boolean isUnique() {
+        return type == ConstraintType.UNIQUE;
+    }
+
+    public boolean isPrimaryKey() {
+        return type == ConstraintType.PRIMARY_KEY;
+    }
+
+    public LogicalPlan toProject() {
+        return new LogicalProject<>(ImmutableList.copyOf(slots), curTable);
+    }
+
+    public LogicalPlan toReferenceProject() {
+        Preconditions.checkArgument(referenceSlots != null, "Reference slot 
set of foreign key cannot be null");
+        return new LogicalProject<>(ImmutableList.copyOf(referenceSlots), 
referenceTable);
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder();
+        sb.append("Constraint Type: ").append(type).append("\n");
+        sb.append("Slot Set: ").append(slots).append("\n");
+        sb.append("Current Table: ").append(curTable).append("\n");
+        if (type.equals(ConstraintType.FOREIGN_KEY)) {
+            sb.append("Reference Table: ").append(referenceTable).append("\n");
+            sb.append("Reference Slot Set: ").append(referenceSlots);
+        }
+        return sb.toString();
+    }
+}
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 4e42b6f6593..b011cee7490 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
@@ -17,6 +17,7 @@
 
 package org.apache.doris.nereids.trees.plans.visitor;
 
+import org.apache.doris.nereids.trees.plans.commands.AddConstraintCommand;
 import org.apache.doris.nereids.trees.plans.commands.AlterMTMVCommand;
 import org.apache.doris.nereids.trees.plans.commands.Command;
 import org.apache.doris.nereids.trees.plans.commands.CreateMTMVCommand;
@@ -77,6 +78,10 @@ public interface CommandVisitor<R, C> {
         return visitCommand(alterMTMVCommand, context);
     }
 
+    default R visitAddConstraintCommand(AddConstraintCommand 
addConstraintCommand, C context) {
+        return visitCommand(addConstraintCommand, 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/AddConstraintTest.java
new file mode 100644
index 00000000000..c9c394a51e1
--- /dev/null
+++ 
b/fe/fe-core/src/test/java/org/apache/doris/nereids/trees/plans/AddConstraintTest.java
@@ -0,0 +1,134 @@
+// 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;
+
+import org.apache.doris.catalog.Column;
+import org.apache.doris.catalog.TableIf;
+import org.apache.doris.catalog.constraint.Constraint;
+import org.apache.doris.catalog.constraint.ForeignKeyConstraint;
+import org.apache.doris.catalog.constraint.PrimaryKeyConstraint;
+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.util.PlanChecker;
+import org.apache.doris.nereids.util.PlanPatternMatchSupported;
+import org.apache.doris.utframe.TestWithFeService;
+
+import com.google.common.collect.Sets;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+import java.util.List;
+import java.util.Set;
+
+class AddConstraintTest extends TestWithFeService implements 
PlanPatternMatchSupported {
+    @Override
+    public void runBeforeAll() throws Exception {
+        createDatabase("test");
+        connectContext.setDatabase("default_cluster:test");
+        createTable("create table t1 (\n"
+                + "    k1 int,\n"
+                + "    k2 int\n"
+                + ")\n"
+                + "unique key(k1, k2)\n"
+                + "distributed by hash(k1) buckets 4\n"
+                + "properties(\n"
+                + "    \"replication_num\"=\"1\"\n"
+                + ")");
+        createTable("create table t2 (\n"
+                + "    k1 int,\n"
+                + "    k2 int\n"
+                + ")\n"
+                + "unique key(k1, k2)\n"
+                + "distributed by hash(k1) buckets 4\n"
+                + "properties(\n"
+                + "    \"replication_num\"=\"1\"\n"
+                + ")");
+    }
+
+    @Test
+    void addPrimaryKeyConstraintTest() throws Exception {
+        AddConstraintCommand command = (AddConstraintCommand) new 
NereidsParser().parseSingle(
+                "alter table t1 add constraint pk primary key (k1)");
+        command.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) {
+                Set<String> columns = ((PrimaryKeyConstraint) 
c).getPrimaryKeyNames();
+                return columns.size() == 1 && 
columns.iterator().next().equals("k1");
+            }
+            return false;
+        }));
+    }
+
+    @Test
+    void addUniqueConstraintTest() throws Exception {
+        AddConstraintCommand command = (AddConstraintCommand) new 
NereidsParser().parseSingle(
+                "alter table t1 add constraint un unique (k1)");
+        command.run(connectContext, null);
+        PlanChecker.from(connectContext).parse("select * from 
t1").analyze().matches(logicalOlapScan().when(o -> {
+            Constraint c = o.getTable().getConstraint("un");
+            if (c instanceof UniqueConstraint) {
+                Set<String> columns = ((UniqueConstraint) 
c).getUniqueColumnNames();
+                return columns.size() == 1 && 
columns.iterator().next().equals("k1");
+            }
+            return false;
+        }));
+    }
+
+    @Test
+    void addForeignKeyConstraintTest() throws Exception {
+        AddConstraintCommand command = (AddConstraintCommand) new 
NereidsParser().parseSingle(
+                "alter table t1 add constraint fk foreign key (k1) references 
t2(k1)");
+        try {
+            command.run(connectContext, null);
+        } catch (Exception e) {
+            Assertions.assertEquals("Foreign key constraint requires a primary 
key constraint [k1] in t2",
+                    e.getMessage());
+        }
+        ((AddConstraintCommand) new NereidsParser().parseSingle(
+                "alter table t2 add constraint pk primary key (k1, 
k2)")).run(connectContext, null);
+        ((AddConstraintCommand) new NereidsParser().parseSingle(
+                "alter table t1 add constraint fk foreign key (k1, k2) 
references t2(k1, k2)")).run(connectContext,
+                null);
+
+        PlanChecker.from(connectContext).parse("select * from 
t1").analyze().matches(logicalOlapScan().when(o -> {
+            Constraint c = o.getTable().getConstraint("fk");
+            if (c instanceof ForeignKeyConstraint) {
+                ForeignKeyConstraint f = (ForeignKeyConstraint) c;
+                Column ref1 = f.getReferencedColumn(((SlotReference) 
o.getOutput().get(0)).getColumn().get().getName());
+                Column ref2 = f.getReferencedColumn(((SlotReference) 
o.getOutput().get(1)).getColumn().get().getName());
+                return ref1.getName().equals("k1") && 
ref2.getName().equals("k2");
+            }
+            return false;
+        }));
+
+        PlanChecker.from(connectContext).parse("select * from 
t2").analyze().matches(logicalOlapScan().when(o -> {
+            Constraint c = o.getTable().getConstraint("pk");
+            if (c instanceof PrimaryKeyConstraint) {
+                Set<String> columnNames = ((PrimaryKeyConstraint) 
c).getPrimaryKeyNames();
+                List<TableIf> referenceTable = ((PrimaryKeyConstraint) 
c).getReferenceTables();
+                return columnNames.size() == 2
+                        && columnNames.equals(Sets.newHashSet("k1", "k2"))
+                        && referenceTable.size() == 1 && 
referenceTable.get(0).getName().equals("t1");
+            }
+            return false;
+        }));
+    }
+}
diff --git 
a/fe/fe-core/src/test/java/org/apache/doris/utframe/TestWithFeService.java 
b/fe/fe-core/src/test/java/org/apache/doris/utframe/TestWithFeService.java
index 3f3177b0e70..6776ec37a99 100644
--- a/fe/fe-core/src/test/java/org/apache/doris/utframe/TestWithFeService.java
+++ b/fe/fe-core/src/test/java/org/apache/doris/utframe/TestWithFeService.java
@@ -272,6 +272,7 @@ public abstract class TestWithFeService {
         ctx.setRemoteIP(host);
         ctx.setEnv(Env.getCurrentEnv());
         ctx.setThreadLocalInfo();
+        ctx.setStatementContext(new StatementContext());
         return ctx;
     }
 


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscr...@doris.apache.org
For additional commands, e-mail: commits-h...@doris.apache.org


Reply via email to