You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@doris.apache.org by mo...@apache.org on 2020/06/22 01:24:31 UTC

[incubator-doris] branch master updated: [Bug] Can not use non-key column as partition column in duplicate table (#3916)

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

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


The following commit(s) were added to refs/heads/master by this push:
     new 56bb218  [Bug] Can not use non-key column as partition column in duplicate table (#3916)
56bb218 is described below

commit 56bb218148d02fb1db4cea60ac193da0b6600cec
Author: Mingyu Chen <mo...@gmail.com>
AuthorDate: Mon Jun 22 09:24:21 2020 +0800

    [Bug] Can not use non-key column as partition column in duplicate table (#3916)
    
    The following statement will throw error:
    ```
    create table test.tbl2
    (k1 int, k2 int, k3 float)
    duplicate key(k1)
    partition by range(k2)
    (partition p1 values less than("10"))
    distributed by hash(k3) buckets 1
    properties('replication_num' = '1');
    ```
    Error: `Only key column can be partition column`
    
    But in duplicate key table, columns can be partition or distribution column
    even if they are not in duplicate keys.
    
    This bug is introduced by #3812
---
 .../doris/analysis/HashDistributionDesc.java       |   9 +-
 .../apache/doris/analysis/RangePartitionDesc.java  |  17 +-
 .../org/apache/doris/catalog/CreateTableTest.java  | 509 +++++----------------
 .../org/apache/doris/common/ExceptionChecker.java  |  44 +-
 .../org/apache/doris/http/DorisHttpTestCase.java   |   9 +-
 .../org/apache/doris/planner/QueryPlanTest.java    |   2 +-
 6 files changed, 169 insertions(+), 421 deletions(-)

diff --git a/fe/src/main/java/org/apache/doris/analysis/HashDistributionDesc.java b/fe/src/main/java/org/apache/doris/analysis/HashDistributionDesc.java
index e779f20..31e1d3b 100644
--- a/fe/src/main/java/org/apache/doris/analysis/HashDistributionDesc.java
+++ b/fe/src/main/java/org/apache/doris/analysis/HashDistributionDesc.java
@@ -17,10 +17,11 @@
 
 package org.apache.doris.analysis;
 
+import org.apache.doris.catalog.AggregateType;
 import org.apache.doris.catalog.Column;
+import org.apache.doris.catalog.DistributionInfo;
 import org.apache.doris.catalog.DistributionInfo.DistributionInfoType;
 import org.apache.doris.catalog.HashDistributionInfo;
-import org.apache.doris.catalog.DistributionInfo;
 import org.apache.doris.common.AnalysisException;
 import org.apache.doris.common.DdlException;
 import org.apache.doris.common.io.Text;
@@ -102,10 +103,14 @@ public class HashDistributionDesc extends DistributionDesc {
             boolean find = false;
             for (Column column : columns) {
                 if (column.getName().equalsIgnoreCase(colName)) {
-                    if (!column.isKey()) {
+                    if (!column.isKey() && column.getAggregationType() != AggregateType.NONE) {
                         throw new DdlException("Distribution column[" + colName + "] is not key column");
                     }
 
+                    if (column.getType().isFloatingPointType()) {
+                        throw new DdlException("Floating point type column can not be distribution column");
+                    }
+
                     distributionColumns.add(column);
                     find = true;
                     break;
diff --git a/fe/src/main/java/org/apache/doris/analysis/RangePartitionDesc.java b/fe/src/main/java/org/apache/doris/analysis/RangePartitionDesc.java
index 8f49d2e..771e208 100644
--- a/fe/src/main/java/org/apache/doris/analysis/RangePartitionDesc.java
+++ b/fe/src/main/java/org/apache/doris/analysis/RangePartitionDesc.java
@@ -18,6 +18,7 @@
 package org.apache.doris.analysis;
 
 import org.apache.doris.analysis.PartitionKeyDesc.PartitionRangeType;
+import org.apache.doris.catalog.AggregateType;
 import org.apache.doris.catalog.Column;
 import org.apache.doris.catalog.PartitionInfo;
 import org.apache.doris.catalog.PartitionType;
@@ -71,8 +72,11 @@ public class RangePartitionDesc extends PartitionDesc {
             boolean found = false;
             for (ColumnDef columnDef : columnDefs) {
                 if (columnDef.getName().equals(partitionCol)) {
-                    if (!columnDef.isKey()) {
-                        throw new AnalysisException("Only key column can be partition column");
+                    if (!columnDef.isKey() && columnDef.getAggregateType() != AggregateType.NONE) {
+                        throw new AnalysisException("The partition column could not be aggregated column");
+                    }
+                    if (columnDef.getType().isFloatingPointType()) {
+                        throw new AnalysisException("Floating point type column can not be partition column");
                     }
                     found = true;
                     break;
@@ -145,9 +149,14 @@ public class RangePartitionDesc extends PartitionDesc {
             boolean find = false;
             for (Column column : schema) {
                 if (column.getName().equalsIgnoreCase(colName)) {
-                    if (!column.isKey()) {
-                        throw new DdlException("Partition column[" + colName + "] is not key column");
+                    if (!column.isKey() && column.getAggregationType() != AggregateType.NONE) {
+                        throw new DdlException("The partition column could not be aggregated column");
+                    }
+
+                    if (column.getType().isFloatingPointType()) {
+                        throw new DdlException("Floating point type column can not be partition column");
                     }
+
                     try {
                         RangePartitionInfo.checkRangeColumnType(column);
                     } catch (AnalysisException e) {
diff --git a/fe/src/test/java/org/apache/doris/catalog/CreateTableTest.java b/fe/src/test/java/org/apache/doris/catalog/CreateTableTest.java
index 17b3648..4ee95a5 100644
--- a/fe/src/test/java/org/apache/doris/catalog/CreateTableTest.java
+++ b/fe/src/test/java/org/apache/doris/catalog/CreateTableTest.java
@@ -17,426 +17,135 @@
 
 package org.apache.doris.catalog;
 
-import org.apache.doris.analysis.Analyzer;
-import org.apache.doris.analysis.ColumnDef;
+import org.apache.doris.analysis.CreateDbStmt;
 import org.apache.doris.analysis.CreateTableStmt;
-import org.apache.doris.analysis.HashDistributionDesc;
-import org.apache.doris.analysis.KeysDesc;
-import org.apache.doris.analysis.TableName;
-import org.apache.doris.analysis.TypeDef;
 import org.apache.doris.common.AnalysisException;
 import org.apache.doris.common.DdlException;
-import org.apache.doris.common.util.PropertyAnalyzer;
-import org.apache.doris.mysql.privilege.PaloAuth;
-import org.apache.doris.mysql.privilege.PrivPredicate;
-import org.apache.doris.persist.EditLog;
+import org.apache.doris.common.ExceptionChecker;
 import org.apache.doris.qe.ConnectContext;
-import org.apache.doris.system.SystemInfoService;
-import org.apache.doris.task.AgentBatchTask;
+import org.apache.doris.utframe.UtFrameUtils;
 
-import com.google.common.collect.Lists;
-
-import org.junit.Before;
-import org.junit.Rule;
+import org.junit.AfterClass;
+import org.junit.Assert;
+import org.junit.BeforeClass;
 import org.junit.Test;
-import org.junit.rules.ExpectedException;
-
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
 
-import mockit.Expectations;
-import mockit.Injectable;
-import mockit.Mock;
-import mockit.MockUp;
+import java.io.File;
+import java.util.UUID;
 
 public class CreateTableTest {
+    private static String runningDir = "fe/mocked/CreateTableTest2/" + UUID.randomUUID().toString() + "/";
 
-    private TableName dbTableName;
-    private String dbName = "testDb";
-    private String tableName = "testTable";
-    private String clusterName = "default";
-    private List<Long> beIds = Lists.newArrayList();
-    private List<String> columnNames = Lists.newArrayList();
-    private List<ColumnDef> columnDefs = Lists.newArrayList();
-
-    private Catalog catalog = Catalog.getCurrentCatalog();
-    private Database db = new Database();
-    private Analyzer analyzer;
-
-    @Injectable
-    ConnectContext connectContext;
-
-    @Rule
-    public ExpectedException expectedEx = ExpectedException.none();
-
-    @Before
-    public void setUp() throws AnalysisException {
-        dbTableName = new TableName(dbName, tableName);
-
-        beIds.add(1L);
-        beIds.add(2L);
-        beIds.add(3L);
-
-        columnNames.add("key1");
-        columnNames.add("key2");
-
-        columnDefs.add(new ColumnDef("key1", new TypeDef(ScalarType.createType(PrimitiveType.INT))));
-        columnDefs.add(new ColumnDef("key2", new TypeDef(ScalarType.createVarchar(10))));
-
-        analyzer = new Analyzer(catalog, connectContext);
-
-        new Expectations(analyzer) {
-            {
-                analyzer.getClusterName();
-                minTimes = 0;
-                result = clusterName;
-            }
-        };
-
-        new Expectations(catalog) {
-            {
-                Catalog.getCurrentCatalog();
-                minTimes = 0;
-                result = catalog;
-
-                Catalog.getCurrentCatalog();
-                minTimes = 0;
-                result = catalog;
-            }
-        };
-
-        dbTableName.analyze(analyzer);
-    }
-
-    @Test
-    public void testNormalOlap(@Injectable SystemInfoService systemInfoService, @Injectable PaloAuth paloAuth,
-            @Injectable EditLog editLog) throws Exception {
-        new Expectations(catalog) {
-            {
-                catalog.getDb(dbTableName.getDb());
-                minTimes = 0;
-                result = db;
-
-                Catalog.getCurrentSystemInfo();
-                minTimes = 0;
-                result = systemInfoService;
-
-                catalog.getAuth();
-                minTimes = 0;
-                result = paloAuth;
-
-                catalog.getEditLog();
-                minTimes = 0;
-                result = editLog;
-            }
-        };
-
-        new Expectations() {
-            {
-                systemInfoService.checkClusterCapacity(anyString);
-                minTimes = 0;
-
-                systemInfoService.seqChooseBackendIds(anyInt, true, true, anyString);
-                minTimes = 0;
-                result = beIds;
-
-                paloAuth.checkTblPriv((ConnectContext) any, anyString, anyString, PrivPredicate.CREATE);
-                minTimes = 0;
-                result = true;
-            }
-        };
-
-        new MockUp<AgentBatchTask>() {
-            @Mock
-            void run() {
-                return;
-            }
-        };
-
-        new MockUp<CountDownLatch>() {
-            @Mock
-            boolean await(long timeout, TimeUnit unit) {
-                return true;
-            }
-        };
-
-        CreateTableStmt stmt = new CreateTableStmt(false, false, dbTableName, columnDefs, "olap",
-                new KeysDesc(KeysType.AGG_KEYS, columnNames), null,
-                new HashDistributionDesc(1, Lists.newArrayList("key1")), null, null, "");
-        stmt.analyze(analyzer);
-
-        catalog.createTable(stmt);
-    }
-
-    @Test
-    public void testUnknownDatabase(@Injectable PaloAuth paloAuth) throws Exception {
-        new Expectations(catalog) {
-            {
-                catalog.getAuth();
-                minTimes = 0;
-                result = paloAuth;
-            }
-        };
-
-        new Expectations() {
-            {
-                paloAuth.checkTblPriv((ConnectContext) any, anyString, anyString, PrivPredicate.CREATE);
-                minTimes = 0;
-                result = true;
-            }
-        };
-
-        CreateTableStmt stmt = new CreateTableStmt(false, false, dbTableName, columnDefs, "olap",
-                new KeysDesc(KeysType.AGG_KEYS, columnNames), null,
-                new HashDistributionDesc(1, Lists.newArrayList("key1")), null, null, "");
-
-        stmt.analyze(analyzer);
-
-        expectedEx.expect(DdlException.class);
-        expectedEx.expectMessage("Unknown database 'default:testDb'");
-
-        catalog.createTable(stmt);
-    }
-
-    @Test
-    public void testShortKeyTooLarge(@Injectable SystemInfoService systemInfoService, @Injectable PaloAuth paloAuth)
-            throws Exception {
-        new Expectations(catalog) {
-            {
-                catalog.getDb(dbTableName.getDb());
-                minTimes = 0;
-                result = db;
-
-                Catalog.getCurrentSystemInfo();
-                minTimes = 0;
-                result = systemInfoService;
-
-                catalog.getAuth();
-                minTimes = 0;
-                result = paloAuth;
-
-            }
-        };
-
-        new Expectations() {
-            {
-                systemInfoService.checkClusterCapacity(anyString);
-                minTimes = 0;
-
-                paloAuth.checkTblPriv((ConnectContext) any, anyString, anyString, PrivPredicate.CREATE);
-                minTimes = 0;
-                result = true;
-            }
-        };
+    private static ConnectContext connectContext;
 
-        Map<String, String> properties = new HashMap<String, String>();
-        //larger then indexColumns size
-        properties.put(PropertyAnalyzer.PROPERTIES_SHORT_KEY, "3");
+    @BeforeClass
+    public static void beforeClass() throws Exception {
+        UtFrameUtils.createMinDorisCluster(runningDir);
 
-        CreateTableStmt stmt = new CreateTableStmt(false, false, dbTableName, columnDefs, "olap",
-                new KeysDesc(KeysType.AGG_KEYS, columnNames), null,
-                new HashDistributionDesc(1, Lists.newArrayList("key1")), properties, null, "");
-        stmt.analyze(analyzer);
-
-        expectedEx.expect(DdlException.class);
-        expectedEx.expectMessage("Short key is too large. should less than: 2");
-
-        catalog.createTable(stmt);
+        // create connect context
+        connectContext = UtFrameUtils.createDefaultCtx();
+        // create database
+        String createDbStmtStr = "create database test;";
+        CreateDbStmt createDbStmt = (CreateDbStmt) UtFrameUtils.parseAndAnalyzeStmt(createDbStmtStr, connectContext);
+        Catalog.getCurrentCatalog().createDb(createDbStmt);
     }
-
-    @Test
-    public void testShortKeyVarcharMiddle(@Injectable SystemInfoService systemInfoService,
-            @Injectable PaloAuth paloAuth) throws Exception {
-        columnDefs.clear();
-        columnDefs.add(new ColumnDef("key1", new TypeDef(ScalarType.createVarchar(10))));
-        columnDefs.add(new ColumnDef("key2", new TypeDef(ScalarType.createType(PrimitiveType.INT))));
-
-        new Expectations(catalog) {
-            {
-                catalog.getDb(dbTableName.getDb());
-                minTimes = 0;
-                result = db;
-
-                Catalog.getCurrentSystemInfo();
-                minTimes = 0;
-                result = systemInfoService;
-
-                catalog.getAuth();
-                minTimes = 0;
-                result = paloAuth;
-
-            }
-        };
-
-        new Expectations() {
-            {
-                systemInfoService.checkClusterCapacity(anyString);
-                minTimes = 0;
-
-                paloAuth.checkTblPriv((ConnectContext) any, anyString, anyString, PrivPredicate.CREATE);
-                minTimes = 0;
-                result = true;
-            }
-        };
-
-        Map<String, String> properties = new HashMap<String, String>();
-        properties.put(PropertyAnalyzer.PROPERTIES_SHORT_KEY, "2");
-
-        CreateTableStmt stmt = new CreateTableStmt(false, false, dbTableName, columnDefs, "olap",
-                new KeysDesc(KeysType.AGG_KEYS, columnNames), null,
-                new HashDistributionDesc(1, Lists.newArrayList("key1")), properties, null, "");
-        stmt.analyze(analyzer);
-
-        expectedEx.expect(DdlException.class);
-        expectedEx.expectMessage("Varchar should not in the middle of short keys.");
-
-        catalog.createTable(stmt);
+    
+    @AfterClass
+    public static void tearDown() {
+        File file = new File(runningDir);
+        file.delete();
     }
 
-    @Test
-    public void testNotEnoughBackend(@Injectable SystemInfoService systemInfoService, @Injectable PaloAuth paloAuth)
-            throws Exception {
-        new Expectations(catalog) {
-            {
-                catalog.getDb(dbTableName.getDb());
-                minTimes = 0;
-                result = db;
-
-                Catalog.getCurrentSystemInfo();
-                minTimes = 0;
-                result = systemInfoService;
-
-                catalog.getAuth();
-                minTimes = 0;
-                result = paloAuth;
-
-            }
-        };
-
-        new Expectations() {
-            {
-                systemInfoService.checkClusterCapacity(anyString);
-                minTimes = 0;
-
-                systemInfoService.seqChooseBackendIds(anyInt, true, true, anyString);
-                minTimes = 0;
-                result = null;
-
-                paloAuth.checkTblPriv((ConnectContext) any, anyString, anyString, PrivPredicate.CREATE);
-                minTimes = 0;
-                result = true;
-            }
-        };
-
-        CreateTableStmt stmt = new CreateTableStmt(false, false, dbTableName, columnDefs, "olap",
-                new KeysDesc(KeysType.AGG_KEYS, columnNames), null,
-                new HashDistributionDesc(1, Lists.newArrayList("key1")), null, null, "");
-        stmt.analyze(analyzer);
-
-        expectedEx.expect(DdlException.class);
-        expectedEx.expectMessage("Failed to find enough host in all backends. need: 3");
-
-        catalog.createTable(stmt);
+    private static void createTable(String sql) throws Exception {
+        CreateTableStmt createTableStmt = (CreateTableStmt) UtFrameUtils.parseAndAnalyzeStmt(sql, connectContext);
+        Catalog.getCurrentCatalog().createTable(createTableStmt);
     }
 
     @Test
-    public void testOlapTableExists(@Injectable SystemInfoService systemInfoService, @Injectable PaloAuth paloAuth)
-            throws Exception {
-        Table olapTable = new OlapTable();
-        new Expectations(db) {
-            {
-                db.getTable(tableName);
-                minTimes = 0;
-                result = olapTable;
-            }
-        };
-
-        new Expectations(catalog) {
-            {
-
-                catalog.getDb(dbTableName.getDb());
-                minTimes = 0;
-                result = db;
-
-                Catalog.getCurrentSystemInfo();
-                minTimes = 0;
-                result = systemInfoService;
-
-                catalog.getAuth();
-                minTimes = 0;
-                result = paloAuth;
-
-            }
-        };
-
-        new Expectations() {
-            {
-                systemInfoService.checkClusterCapacity(anyString);
-                minTimes = 0;
-
-                paloAuth.checkTblPriv((ConnectContext) any, anyString, anyString, PrivPredicate.CREATE);
-                minTimes = 0;
-                result = true;
-            }
-        };
-
-        CreateTableStmt stmt = new CreateTableStmt(false, false, dbTableName, columnDefs, "olap",
-                new KeysDesc(KeysType.AGG_KEYS, columnNames), null,
-                new HashDistributionDesc(1, Lists.newArrayList("key1")), null, null, "");
-        stmt.analyze(analyzer);
-
-        expectedEx.expect(DdlException.class);
-        expectedEx.expectMessage("Table 'testTable' already exists");
-
-        catalog.createTable(stmt);
+    public void testNormal() {
+        ExceptionChecker.expectThrowsNoException(
+                () -> createTable("create table test.tbl1\n" + "(k1 int, k2 int)\n" + "duplicate key(k1)\n"
+                        + "distributed by hash(k2) buckets 1\n" + "properties('replication_num' = '1'); "));
+
+        ExceptionChecker.expectThrowsNoException(() -> createTable("create table test.tbl2\n" + "(k1 int, k2 int)\n"
+                + "duplicate key(k1)\n" + "partition by range(k2)\n" + "(partition p1 values less than(\"10\"))\n"
+                + "distributed by hash(k2) buckets 1\n" + "properties('replication_num' = '1'); "));
+
+        ExceptionChecker.expectThrowsNoException(
+                () -> createTable("create table test.tbl3\n" + "(k1 varchar(40), k2 int)\n" + "duplicate key(k1)\n"
+                        + "partition by range(k2)\n" + "(partition p1 values less than(\"10\"))\n"
+                        + "distributed by hash(k2) buckets 1\n" + "properties('replication_num' = '1');"));
+
+        ExceptionChecker.expectThrowsNoException(
+                () -> createTable("create table test.tbl4\n" + "(k1 varchar(40), k2 int, v1 int sum)\n"
+                        + "partition by range(k2)\n" + "(partition p1 values less than(\"10\"))\n"
+                        + "distributed by hash(k1) buckets 1\n" + "properties('replication_num' = '1');"));
+
+        ExceptionChecker.expectThrowsNoException(() -> createTable(
+                "create table test.tbl5\n" + "(k1 varchar(40), k2 int, v1 int sum)\n" + "aggregate key(k1,k2)\n"
+                        + "partition by range(k2)\n" + "(partition p1 values less than(\"10\"))\n"
+                        + "distributed by hash(k1) buckets 1\n" + "properties('replication_num' = '1');"));
+
+        ExceptionChecker.expectThrowsNoException(() -> createTable(
+                "create table test.tbl6\n" + "(k1 varchar(40), k2 int, k3 int)\n" + "duplicate key(k1, k2, k3)\n"
+                        + "partition by range(k2)\n" + "(partition p1 values less than(\"10\"))\n"
+                        + "distributed by hash(k1) buckets 1\n" + "properties('replication_num' = '1');"));
+
+        ExceptionChecker
+                .expectThrowsNoException(() -> createTable("create table test.tbl7\n" + "(k1 varchar(40), k2 int)\n"
+                        + "partition by range(k2)\n" + "(partition p1 values less than(\"10\"))\n"
+                        + "distributed by hash(k2) buckets 1\n" + "properties('replication_num' = '1');"));
+
+        Database db = Catalog.getCurrentCatalog().getDb("default_cluster:test");
+        OlapTable tbl6 = (OlapTable) db.getTable("tbl6");
+        Assert.assertTrue(tbl6.getColumn("k1").isKey());
+        Assert.assertTrue(tbl6.getColumn("k2").isKey());
+        Assert.assertTrue(tbl6.getColumn("k3").isKey());
+
+        OlapTable tbl7 = (OlapTable) db.getTable("tbl7");
+        Assert.assertTrue(tbl7.getColumn("k1").isKey());
+        Assert.assertFalse(tbl7.getColumn("k2").isKey());
+        Assert.assertTrue(tbl7.getColumn("k2").getAggregationType() == AggregateType.NONE);
     }
 
     @Test
-    public void testOlapTimeOut(@Injectable SystemInfoService systemInfoService, @Injectable PaloAuth paloAuth)
-            throws Exception {
-        new Expectations(catalog) {
-            {
-                catalog.getDb(dbTableName.getDb());
-                minTimes = 0;
-                result = db;
-
-                Catalog.getCurrentSystemInfo();
-                minTimes = 0;
-                result = systemInfoService;
-
-                catalog.getAuth();
-                minTimes = 0;
-                result = paloAuth;
-
-            }
-        };
-
-        new Expectations() {
-            {
-                systemInfoService.checkClusterCapacity(anyString);
-                minTimes = 0;
-
-                systemInfoService.seqChooseBackendIds(anyInt, true, true, anyString);
-                minTimes = 0;
-                result = beIds;
-
-                paloAuth.checkTblPriv((ConnectContext) any, anyString, anyString, PrivPredicate.CREATE);
-                minTimes = 0;
-                result = true;
-            }
-        };
-
-        CreateTableStmt stmt = new CreateTableStmt(false, false, dbTableName, columnDefs, "olap",
-                new KeysDesc(KeysType.AGG_KEYS, columnNames), null,
-                new HashDistributionDesc(1, Lists.newArrayList("key1")), null, null, "");
-        stmt.analyze(analyzer);
-
-        expectedEx.expect(DdlException.class);
-        expectedEx.expectMessage("Failed to create partition[testTable]. Timeout");
-
-        catalog.createTable(stmt);
+    public void testAbormal() {
+        ExceptionChecker.expectThrowsWithMsg(DdlException.class,
+                "Floating point type column can not be distribution column",
+                () -> createTable("create table test.atbl1\n" + "(k1 int, k2 float)\n" + "duplicate key(k1)\n"
+                        + "distributed by hash(k2) buckets 1\n" + "properties('replication_num' = '1'); "));
+
+        ExceptionChecker.expectThrowsWithMsg(AnalysisException.class,
+                "Floating point type column can not be partition column",
+                () -> createTable("create table test.atbl3\n" + "(k1 int, k2 int, k3 float)\n" + "duplicate key(k1)\n"
+                        + "partition by range(k3)\n" + "(partition p1 values less than(\"10\"))\n"
+                        + "distributed by hash(k2) buckets 1\n" + "properties('replication_num' = '1'); "));
+
+        ExceptionChecker.expectThrowsWithMsg(DdlException.class,
+                "Varchar should not in the middle of short keys",
+                () -> createTable("create table test.atbl3\n" + "(k1 varchar(40), k2 int, k3 int)\n"
+                        + "duplicate key(k1, k2, k3)\n" + "distributed by hash(k1) buckets 1\n"
+                        + "properties('replication_num' = '1', 'short_key' = '3');"));
+
+        ExceptionChecker.expectThrowsWithMsg(DdlException.class, "Short key is too large. should less than: 3",
+                () -> createTable("create table test.atbl4\n" + "(k1 int, k2 int, k3 int)\n"
+                        + "duplicate key(k1, k2, k3)\n" + "distributed by hash(k1) buckets 1\n"
+                        + "properties('replication_num' = '1', 'short_key' = '4');"));
+
+        ExceptionChecker
+                .expectThrowsWithMsg(DdlException.class, "Failed to find enough host in all backends. need: 3",
+                        () -> createTable("create table test.atbl5\n" + "(k1 int, k2 int, k3 int)\n"
+                                + "duplicate key(k1, k2, k3)\n" + "distributed by hash(k1) buckets 1\n"
+                                + "properties('replication_num' = '3');"));
+
+        ExceptionChecker.expectThrowsNoException(
+                () -> createTable("create table test.atbl6\n" + "(k1 int, k2 int)\n" + "duplicate key(k1)\n"
+                        + "distributed by hash(k2) buckets 1\n" + "properties('replication_num' = '1'); "));
+
+        ExceptionChecker
+                .expectThrowsWithMsg(DdlException.class, "Table 'atbl6' already exists",
+                        () -> createTable("create table test.atbl6\n" + "(k1 int, k2 int, k3 int)\n"
+                                + "duplicate key(k1, k2, k3)\n" + "distributed by hash(k1) buckets 1\n"
+                                + "properties('replication_num' = '1');"));
     }
 }
diff --git a/fe/src/test/java/org/apache/doris/common/ExceptionChecker.java b/fe/src/test/java/org/apache/doris/common/ExceptionChecker.java
index 128a8b8..62a0030 100644
--- a/fe/src/test/java/org/apache/doris/common/ExceptionChecker.java
+++ b/fe/src/test/java/org/apache/doris/common/ExceptionChecker.java
@@ -17,12 +17,21 @@
 
 package org.apache.doris.common;
 
-import org.apache.doris.http.DorisHttpTestCase;
+import com.google.common.base.Strings;
+
 import junit.framework.AssertionFailedError;
 
 public class ExceptionChecker {
 
-    public static void expectThrowsNoException(DorisHttpTestCase.ThrowingRunnable runnable) {
+    /**
+     * A runnable that can throw any checked exception.
+     */
+    @FunctionalInterface
+    public interface ThrowingRunnable {
+        void run() throws Throwable;
+    }
+
+    public static void expectThrowsNoException(ThrowingRunnable runnable) {
         try {
             runnable.run();
         } catch (Throwable e) {
@@ -33,21 +42,44 @@ public class ExceptionChecker {
     /**
      * Checks a specific exception class is thrown by the given runnable, and returns it.
      */
-    public static <T extends Throwable> T expectThrows(Class<T> expectedType, DorisHttpTestCase.ThrowingRunnable runnable) {
-        return expectThrows(expectedType, "Expected exception " + expectedType.getSimpleName() + " but no exception was thrown", runnable);
+    public static <T extends Throwable> T expectThrows(Class<T> expectedType, ThrowingRunnable runnable) {
+        return expectThrows(expectedType,
+                "Expected exception " + expectedType.getSimpleName() + " but no exception was thrown", null, runnable);
+    }
+
+    /**
+     * Checks a specific exception class is thrown by the given runnable, and
+     * returns it.
+     * Will also check if the given `exceptionMsg` is with exception.
+     */
+    public static <T extends Throwable> T expectThrowsWithMsg(Class<T> expectedType, String exceptionMsg,
+            ThrowingRunnable runnable) {
+        return expectThrows(expectedType,
+                "Expected exception " + expectedType.getSimpleName() + " but no exception was thrown", exceptionMsg,
+                runnable);
     }
 
     /**
      * Checks a specific exception class is thrown by the given runnable, and returns it.
      */
-    public static <T extends Throwable> T expectThrows(Class<T> expectedType, String noExceptionMessage, DorisHttpTestCase.ThrowingRunnable runnable) {
+    public static <T extends Throwable> T expectThrows(Class<T> expectedType, String noExceptionMessage,
+            String exceptionMsg, ThrowingRunnable runnable) {
         try {
             runnable.run();
         } catch (Throwable e) {
             if (expectedType.isInstance(e)) {
+                if (!Strings.isNullOrEmpty(exceptionMsg)) {
+                    if (!e.getMessage().contains(exceptionMsg)) {
+                        AssertionFailedError assertion = new AssertionFailedError(
+                                "expceted msg: " + exceptionMsg + ", actual: " + e.getMessage());
+                        assertion.initCause(e);
+                        throw assertion;
+                    }
+                }
                 return expectedType.cast(e);
             }
-            AssertionFailedError assertion = new AssertionFailedError("Unexpected exception type, expected " + expectedType.getSimpleName() + " but got " + e);
+            AssertionFailedError assertion = new AssertionFailedError(
+                    "Unexpected exception type, expected " + expectedType.getSimpleName() + " but got " + e);
             assertion.initCause(e);
             throw assertion;
         }
diff --git a/fe/src/test/java/org/apache/doris/http/DorisHttpTestCase.java b/fe/src/test/java/org/apache/doris/http/DorisHttpTestCase.java
index f9c24d1..94fa7f0 100644
--- a/fe/src/test/java/org/apache/doris/http/DorisHttpTestCase.java
+++ b/fe/src/test/java/org/apache/doris/http/DorisHttpTestCase.java
@@ -38,6 +38,7 @@ import org.apache.doris.catalog.TabletInvertedIndex;
 import org.apache.doris.catalog.TabletMeta;
 import org.apache.doris.common.AnalysisException;
 import org.apache.doris.common.DdlException;
+import org.apache.doris.common.ExceptionChecker.ThrowingRunnable;
 import org.apache.doris.common.jmockit.Deencapsulation;
 import org.apache.doris.load.Load;
 import org.apache.doris.mysql.privilege.PaloAuth;
@@ -345,14 +346,6 @@ abstract public class DorisHttpTestCase {
 
     }
 
-    /**
-     * A runnable that can throw any checked exception.
-     */
-    @FunctionalInterface
-    public interface ThrowingRunnable {
-        void run() throws Throwable;
-    }
-
     public void expectThrowsNoException(ThrowingRunnable runnable) {
         try {
             runnable.run();
diff --git a/fe/src/test/java/org/apache/doris/planner/QueryPlanTest.java b/fe/src/test/java/org/apache/doris/planner/QueryPlanTest.java
index 9195490..8e6f4d3 100644
--- a/fe/src/test/java/org/apache/doris/planner/QueryPlanTest.java
+++ b/fe/src/test/java/org/apache/doris/planner/QueryPlanTest.java
@@ -70,8 +70,8 @@ public class QueryPlanTest {
         
         createTable("create table test.test1\n" + 
                 "(\n" +
-                "    time datetime not null comment \"Query start time\",\n" +
                 "    query_id varchar(48) comment \"Unique query id\",\n" +
+                "    time datetime not null comment \"Query start time\",\n" +
                 "    client_ip varchar(32) comment \"Client IP\",\n" + 
                 "    user varchar(64) comment \"User name\",\n" + 
                 "    db varchar(96) comment \"Database of this query\",\n" + 


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