You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@kylin.apache.org by xx...@apache.org on 2020/06/15 02:51:21 UTC

[kylin] 05/15: KYLIN-4422 Allow to change data type from varchar to datetime

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

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

commit 6bd5b43bb06b6bf19d1a096d99146396aab8d5b2
Author: Zhong, Yanghong <nj...@apache.org>
AuthorDate: Mon Apr 13 16:35:07 2020 +0800

    KYLIN-4422 Allow to change data type from varchar to datetime
---
 .../org/apache/kylin/common/KylinConfigBase.java   |   8 ++
 .../kylin/cube/gridtable/CubeCodeSystem.java       |  21 +++++
 .../org/apache/kylin/cube/model/RowKeyColDesc.java |  12 ++-
 .../org/apache/kylin/cube/model/RowKeyDesc.java    |   4 +
 .../org/apache/kylin/dict/DictionaryGenerator.java | 104 ++-------------------
 .../kylin/dict/lookup/LookupStringTable.java       |   8 +-
 .../apache/kylin/dict/DictionaryProviderTest.java  |  26 +-----
 .../org/apache/kylin/metadata/tuple/Tuple.java     |  19 ++--
 .../org/apache/kylin/metadata/tuple/TupleInfo.java |   4 +
 .../kylin/storage/gtrecord/CubeTupleConverter.java |  42 ++++++++-
 .../test/resources/query/sql_casewhen/query58.sql  |  22 +++++
 .../rest/service/TableSchemaUpdateChecker.java     |   4 +
 .../service/update/TableSchemaUpdaterTest.java     |   1 -
 13 files changed, 146 insertions(+), 129 deletions(-)

diff --git a/core-common/src/main/java/org/apache/kylin/common/KylinConfigBase.java b/core-common/src/main/java/org/apache/kylin/common/KylinConfigBase.java
index 99123ec..13e73d9 100644
--- a/core-common/src/main/java/org/apache/kylin/common/KylinConfigBase.java
+++ b/core-common/src/main/java/org/apache/kylin/common/KylinConfigBase.java
@@ -507,6 +507,10 @@ public abstract class KylinConfigBase implements Serializable {
         return Boolean.parseBoolean(getOptional("kylin.metadata.model-schema-updater-checker-enabled", "false"));
     }
 
+    public boolean isAbleChangeStringToDateTime() {
+        return Boolean.parseBoolean(getOptional("kylin.metadata.able-change-string-to-datetime", "false"));
+    }
+
     // ============================================================================
     // DICTIONARY & SNAPSHOT
     // ============================================================================
@@ -706,6 +710,10 @@ public abstract class KylinConfigBase implements Serializable {
         return getOptional("kylin.cube.cuboid-scheduler", "org.apache.kylin.cube.cuboid.DefaultCuboidScheduler");
     }
 
+    public boolean isRowKeyEncodingAutoConvert() {
+        return Boolean.parseBoolean(getOptional("kylin.cube.kylin.cube.rowkey-encoding-auto-convert", "true"));
+    }
+    
     public String getSegmentAdvisor() {
         return getOptional("kylin.cube.segment-advisor", "org.apache.kylin.cube.CubeSegmentAdvisor");
     }
diff --git a/core-cube/src/main/java/org/apache/kylin/cube/gridtable/CubeCodeSystem.java b/core-cube/src/main/java/org/apache/kylin/cube/gridtable/CubeCodeSystem.java
index 4c71fea..c049027 100644
--- a/core-cube/src/main/java/org/apache/kylin/cube/gridtable/CubeCodeSystem.java
+++ b/core-cube/src/main/java/org/apache/kylin/cube/gridtable/CubeCodeSystem.java
@@ -18,12 +18,15 @@
 
 package org.apache.kylin.cube.gridtable;
 
+import static org.apache.kylin.metadata.filter.FilterOptimizeTransformer.logger;
+
 import java.nio.ByteBuffer;
 import java.nio.charset.Charset;
 import java.util.Collections;
 import java.util.Map;
 
 import org.apache.kylin.common.util.Bytes;
+import org.apache.kylin.common.util.DateFormat;
 import org.apache.kylin.common.util.ImmutableBitSet;
 import org.apache.kylin.dimension.DictionaryDimEnc;
 import org.apache.kylin.dimension.DictionaryDimEnc.DictionarySerializer;
@@ -33,6 +36,7 @@ import org.apache.kylin.gridtable.GTInfo;
 import org.apache.kylin.gridtable.IGTCodeSystem;
 import org.apache.kylin.gridtable.IGTComparator;
 import org.apache.kylin.measure.MeasureAggregator;
+import org.apache.kylin.metadata.datatype.DataType;
 import org.apache.kylin.metadata.datatype.DataTypeSerializer;
 import org.apache.kylin.metadata.datatype.DynamicDimSerializer;
 
@@ -127,6 +131,23 @@ public class CubeCodeSystem implements IGTCodeSystem {
             if (dictEnc.getRoundingFlag() != roundingFlag) {
                 serializer = dictEnc.copy(roundingFlag).asDataTypeSerializer();
             }
+
+            // Deal with data type change from string to datetime
+            DataType dataType = info.getColumnType(col);
+            if (dataType.isDateTimeFamily()) {
+                try {
+                    long ts = DateFormat.stringToMillis((String) value);
+                    if (dataType.isDate()) {
+                        value = DateFormat.formatToDateStr(ts);
+                    } else {
+                        value = DateFormat.formatToTimeWithoutMilliStr(ts);
+                    }
+                    logger.info("Convert value from {} to {}", ts, value);
+                } catch (Exception e) {
+                    logger.warn("Fail to convert value {} to string due to {}", value, e);
+                }
+            }
+            
             try {
                 serializer.serialize(value, buf);
             } catch (IllegalArgumentException ex) {
diff --git a/core-cube/src/main/java/org/apache/kylin/cube/model/RowKeyColDesc.java b/core-cube/src/main/java/org/apache/kylin/cube/model/RowKeyColDesc.java
index f1d5645..1e95f51 100644
--- a/core-cube/src/main/java/org/apache/kylin/cube/model/RowKeyColDesc.java
+++ b/core-cube/src/main/java/org/apache/kylin/cube/model/RowKeyColDesc.java
@@ -45,6 +45,14 @@ import org.apache.kylin.shaded.com.google.common.base.Preconditions;
 public class RowKeyColDesc implements java.io.Serializable {
     private static final Logger logger = LoggerFactory.getLogger(RowKeyColDesc.class);
 
+    public static boolean isDateDimEnc(RowKeyColDesc rowKeyColDesc) {
+        return DateDimEnc.ENCODING_NAME.equals(rowKeyColDesc.getEncodingName());
+    }
+
+    public static boolean isTimeDimEnc(RowKeyColDesc rowKeyColDesc) {
+        return TimeDimEnc.ENCODING_NAME.equals(rowKeyColDesc.getEncodingName());
+    }
+
     @JsonProperty("column")
     private String column;
     @JsonProperty("encoding")
@@ -81,12 +89,14 @@ public class RowKeyColDesc implements java.io.Serializable {
         // convert date/time dictionary on date/time column to DimensionEncoding implicitly
         // however date/time dictionary on varchar column is still required
         DataType type = colRef.getType();
-        if (DictionaryDimEnc.ENCODING_NAME.equals(encodingName)) {
+        if (DictionaryDimEnc.ENCODING_NAME.equals(encodingName) && cubeDesc.getConfig().isRowKeyEncodingAutoConvert()) {
             if (type.isDate()) {
                 encoding = encodingName = DateDimEnc.ENCODING_NAME;
+                logger.info("Implicitly convert encoding to {}", encodingName);
             }
             if (type.isTimeFamily()) {
                 encoding = encodingName = TimeDimEnc.ENCODING_NAME;
+                logger.info("Implicitly convert encoding to {}", encodingName);
             }
         }
 
diff --git a/core-cube/src/main/java/org/apache/kylin/cube/model/RowKeyDesc.java b/core-cube/src/main/java/org/apache/kylin/cube/model/RowKeyDesc.java
index c28c828..8934a3b 100644
--- a/core-cube/src/main/java/org/apache/kylin/cube/model/RowKeyDesc.java
+++ b/core-cube/src/main/java/org/apache/kylin/cube/model/RowKeyDesc.java
@@ -67,6 +67,10 @@ public class RowKeyDesc implements java.io.Serializable {
         return desc;
     }
 
+    public RowKeyColDesc getColDescUncheck(TblColRef col) {
+        return columnMap.get(col);
+    }
+
     public boolean isUseDictionary(TblColRef col) {
         return getColDesc(col).isUsingDictionary();
     }
diff --git a/core-dictionary/src/main/java/org/apache/kylin/dict/DictionaryGenerator.java b/core-dictionary/src/main/java/org/apache/kylin/dict/DictionaryGenerator.java
index f4aaa45..a0730ff 100644
--- a/core-dictionary/src/main/java/org/apache/kylin/dict/DictionaryGenerator.java
+++ b/core-dictionary/src/main/java/org/apache/kylin/dict/DictionaryGenerator.java
@@ -22,19 +22,18 @@ import java.io.IOException;
 import java.util.ArrayList;
 import java.util.List;
 
-import com.google.common.base.Function;
-import com.google.common.collect.Lists;
+import javax.annotation.Nullable;
+
 import org.apache.commons.lang.StringUtils;
 import org.apache.kylin.common.KylinConfig;
-import org.apache.kylin.common.util.DateFormat;
 import org.apache.kylin.common.util.Dictionary;
 import org.apache.kylin.metadata.datatype.DataType;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import com.google.common.base.Function;
 import com.google.common.base.Preconditions;
-
-import javax.annotation.Nullable;
+import com.google.common.collect.Lists;
 
 /**
  * @author yangli9
@@ -49,18 +48,11 @@ public class DictionaryGenerator {
 
         // build dict, case by data type
         IDictionaryBuilder builder;
-        if (dataType.isDateTimeFamily()) {
-            if (dataType.isDate())
-                builder = new DateDictBuilder();
-            else
-                builder = new TimeDictBuilder();
-        } else {
-            boolean useForest = KylinConfig.getInstanceFromEnv().isUseForestTrieDictionary();
-            if (dataType.isNumberFamily())
-                builder = useForest ? new NumberTrieDictForestBuilder() : new NumberTrieDictBuilder();
-            else
-                builder = useForest ? new StringTrieDictForestBuilder() : new StringTrieDictBuilder();
-        }
+        boolean useForest = KylinConfig.getInstanceFromEnv().isUseForestTrieDictionary();
+        if (dataType.isNumberFamily())
+            builder = useForest ? new NumberTrieDictForestBuilder() : new NumberTrieDictBuilder();
+        else
+            builder = useForest ? new StringTrieDictForestBuilder() : new StringTrieDictBuilder();
         return builder;
     }
 
@@ -123,84 +115,6 @@ public class DictionaryGenerator {
         return buildDictionary(dataType, new MultipleDictionaryValueEnumerator(dataType, dictList));
     }
 
-    private static class DateDictBuilder implements IDictionaryBuilder {
-        private static final String[] DATE_PATTERNS = new String[] { "yyyy-MM-dd", "yyyyMMdd" };
-
-        private int baseId;
-        private String datePattern;
-
-        @Override
-        public void init(DictionaryInfo info, int baseId, String hdfsDir) throws IOException {
-            this.baseId = baseId;
-        }
-
-        @Override
-        public boolean addValue(String value) {
-            if (StringUtils.isBlank(value)) // empty string is treated as null
-                return false;
-
-            // detect date pattern on the first value
-            if (datePattern == null) {
-                for (String p : DATE_PATTERNS) {
-                    try {
-                        DateFormat.stringToDate(value, p);
-                        datePattern = p;
-                        break;
-                    } catch (Exception e) {
-                        // continue;
-                    }
-                }
-                if (datePattern == null)
-                    throw new IllegalArgumentException("Unknown date pattern for input value: " + value);
-            }
-
-            // check the date format
-            DateFormat.stringToDate(value, datePattern);
-            return true;
-        }
-
-        @Override
-        public Dictionary<String> build() throws IOException {
-            if (datePattern == null)
-                datePattern = DATE_PATTERNS[0];
-
-            return new DateStrDictionary(datePattern, baseId);
-        }
-
-
-        @Override
-        public void clear() {
-            // do nothing
-        }
-    }
-
-    private static class TimeDictBuilder implements IDictionaryBuilder {
-
-        @Override
-        public void init(DictionaryInfo info, int baseId, String hdfsDir) throws IOException {
-        }
-
-        @Override
-        public boolean addValue(String value) {
-            if (StringUtils.isBlank(value)) // empty string is treated as null
-                return false;
-
-            // check the time format
-            DateFormat.stringToMillis(value);
-            return true;
-        }
-
-        @Override
-        public Dictionary<String> build() throws IOException {
-            return new TimeStrDictionary(); // base ID is always 0
-        }
-
-        @Override
-        public void clear() {
-
-        }
-    }
-
     private static class StringTrieDictBuilder implements IDictionaryBuilder {
         int baseId;
         TrieDictionaryBuilder builder;
diff --git a/core-dictionary/src/main/java/org/apache/kylin/dict/lookup/LookupStringTable.java b/core-dictionary/src/main/java/org/apache/kylin/dict/lookup/LookupStringTable.java
index 1d0348a..fae1cfb 100644
--- a/core-dictionary/src/main/java/org/apache/kylin/dict/lookup/LookupStringTable.java
+++ b/core-dictionary/src/main/java/org/apache/kylin/dict/lookup/LookupStringTable.java
@@ -84,8 +84,12 @@ public class LookupStringTable extends LookupTable<String> implements ILookupTab
     protected String[] convertRow(String[] cols) {
         for (int i = 0; i < cols.length; i++) {
             if (colIsDateTime[i]) {
-                if (cols[i] != null)
-                    cols[i] = String.valueOf(DateFormat.stringToMillis(cols[i]));
+                if (cols[i] != null) {
+                    if (cols[i].isEmpty())
+                        cols[i] = null;
+                    else
+                        cols[i] = String.valueOf(DateFormat.stringToMillis(cols[i]));
+                }
             }
         }
         return cols;
diff --git a/core-dictionary/src/test/java/org/apache/kylin/dict/DictionaryProviderTest.java b/core-dictionary/src/test/java/org/apache/kylin/dict/DictionaryProviderTest.java
index 7e2e218..82ce587 100644
--- a/core-dictionary/src/test/java/org/apache/kylin/dict/DictionaryProviderTest.java
+++ b/core-dictionary/src/test/java/org/apache/kylin/dict/DictionaryProviderTest.java
@@ -19,7 +19,6 @@
 package org.apache.kylin.dict;
 
 import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
 
 import java.io.DataInputStream;
 import java.io.DataOutputStream;
@@ -37,7 +36,7 @@ import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 
-public class DictionaryProviderTest extends LocalFileMetadataTestCase{
+public class DictionaryProviderTest extends LocalFileMetadataTestCase {
 
     @Before
     public void setUp() throws Exception {
@@ -52,28 +51,13 @@ public class DictionaryProviderTest extends LocalFileMetadataTestCase{
     @Test
     public void testReadWrite() throws Exception {
         //string dict
-        Dictionary<String> dict = getDict(DataType.getType("string"), Arrays.asList(new String[] { "a", "b" }).iterator());
+        Dictionary<String> dict = getDict(DataType.getType("string"),
+                Arrays.asList(new String[] { "a", "b" }).iterator());
         readWriteTest(dict);
         //number dict
-        Dictionary<String> dict2 = getDict(DataType.getType("long"), Arrays.asList(new String[] { "1", "2" }).iterator());
+        Dictionary<String> dict2 = getDict(DataType.getType("long"),
+                Arrays.asList(new String[] { "1", "2" }).iterator());
         readWriteTest(dict2);
-
-        //date dict
-        Dictionary<String> dict3 = getDict(DataType.getType("datetime"), Arrays.asList(new String[] { "20161122", "20161123" }).iterator());
-        readWriteTest(dict3);
-
-        //date dict
-        Dictionary<String> dict4 = getDict(DataType.getType("datetime"), Arrays.asList(new String[] { "2016-11-22", "2016-11-23" }).iterator());
-        readWriteTest(dict4);
-
-        //date dict
-        try {
-            Dictionary<String> dict5 = getDict(DataType.getType("date"), Arrays.asList(new String[] { "2016-11-22", "20161122" }).iterator());
-            readWriteTest(dict5);
-            fail("Date format not correct.Should throw exception");
-        } catch (IllegalArgumentException e) {
-            //correct
-        }
     }
 
     @Test
diff --git a/core-metadata/src/main/java/org/apache/kylin/metadata/tuple/Tuple.java b/core-metadata/src/main/java/org/apache/kylin/metadata/tuple/Tuple.java
index 6a0cda9..506e7ca 100644
--- a/core-metadata/src/main/java/org/apache/kylin/metadata/tuple/Tuple.java
+++ b/core-metadata/src/main/java/org/apache/kylin/metadata/tuple/Tuple.java
@@ -103,6 +103,10 @@ public class Tuple implements ITuple {
         values[idx] = objectValue;
     }
 
+    public void setDimensionValueDirectly(int idx, Object objectValue) {
+        values[idx] = objectValue;
+    }
+
     public void setMeasureValue(String fieldName, Object fieldValue) {
         setMeasureValue(info.getFieldIndex(fieldName), fieldValue);
     }
@@ -182,10 +186,14 @@ public class Tuple implements ITuple {
         }
     }
 
-    private static long epicDaysToMillis(int days) {
+    public static long epicDaysToMillis(int days) {
         return 1L * days * (1000 * 3600 * 24);
     }
 
+    public static int millisToEpicDays(long millis) {
+        return (int) (millis / (1000 * 3600 * 24));
+    }
+
     public static Object convertOptiqCellValue(String strValue, String dataTypeName) {
         if (strValue == null)
             return null;
@@ -197,10 +205,10 @@ public class Tuple implements ITuple {
         switch (dataTypeName) {
         case "date":
             // convert epoch time
-            return Integer.valueOf(dateToEpicDays(strValue));// Optiq expects Integer instead of Long. by honma
+            return millisToEpicDays(DateFormat.stringToMillis(strValue));// Optiq expects Integer instead of Long. by honma
         case "datetime":
         case "timestamp":
-            return Long.valueOf(DateFormat.stringToMillis(strValue));
+            return DateFormat.stringToMillis(strValue);
         case "tinyint":
             return Byte.valueOf(strValue);
         case "smallint":
@@ -222,9 +230,4 @@ public class Tuple implements ITuple {
         }
     }
 
-    private static int dateToEpicDays(String strValue) {
-        long millis = DateFormat.stringToMillis(strValue);
-        return (int) (millis / (1000 * 3600 * 24));
-    }
-
 }
diff --git a/core-metadata/src/main/java/org/apache/kylin/metadata/tuple/TupleInfo.java b/core-metadata/src/main/java/org/apache/kylin/metadata/tuple/TupleInfo.java
index c3e88b5..213688d 100644
--- a/core-metadata/src/main/java/org/apache/kylin/metadata/tuple/TupleInfo.java
+++ b/core-metadata/src/main/java/org/apache/kylin/metadata/tuple/TupleInfo.java
@@ -52,6 +52,10 @@ public class TupleInfo {
         return columns.get(idx);
     }
 
+    public TblColRef getColumn(int idx) {
+        return columns.get(idx);
+    }
+
     public int getColumnIndex(TblColRef col) {
         return columnMap.get(col);
     }
diff --git a/core-storage/src/main/java/org/apache/kylin/storage/gtrecord/CubeTupleConverter.java b/core-storage/src/main/java/org/apache/kylin/storage/gtrecord/CubeTupleConverter.java
index ddf2a5a..5d6f750 100644
--- a/core-storage/src/main/java/org/apache/kylin/storage/gtrecord/CubeTupleConverter.java
+++ b/core-storage/src/main/java/org/apache/kylin/storage/gtrecord/CubeTupleConverter.java
@@ -28,11 +28,14 @@ import java.util.TimeZone;
 
 import org.apache.kylin.common.KylinConfig;
 import org.apache.kylin.common.util.Array;
+import org.apache.kylin.common.util.DateFormat;
 import org.apache.kylin.common.util.Dictionary;
 import org.apache.kylin.cube.CubeManager;
 import org.apache.kylin.cube.CubeSegment;
 import org.apache.kylin.cube.cuboid.Cuboid;
 import org.apache.kylin.cube.model.CubeDesc.DeriveInfo;
+import org.apache.kylin.cube.model.RowKeyColDesc;
+import org.apache.kylin.cube.model.RowKeyDesc;
 import org.apache.kylin.dict.lookup.ILookupTable;
 import org.apache.kylin.dimension.TimeDerivedColumnType;
 import org.apache.kylin.measure.MeasureType;
@@ -75,6 +78,8 @@ public class CubeTupleConverter implements ITupleConverter {
 
     public final int nSelectedDims;
 
+    private final RowKeyDesc rowKeyDesc;
+
     public CubeTupleConverter(CubeSegment cubeSeg, Cuboid cuboid, //
             Set<TblColRef> selectedDimensions, Set<FunctionDesc> selectedMetrics, int[] gtColIdx, TupleInfo returnTupleInfo) {
         this.cubeSeg = cubeSeg;
@@ -148,6 +153,8 @@ public class CubeTupleConverter implements ITupleConverter {
                 }
             }
         }
+
+        rowKeyDesc = cubeSeg.getCubeDesc().getRowkey();
     }
 
     // load only needed dictionaries
@@ -169,6 +176,7 @@ public class CubeTupleConverter implements ITupleConverter {
             if (ti >= 0) {
                 // add offset to return result according to timezone
                 if (autoJustByTimezone && timestampColumn.contains(ti)) {
+                    // For streaming
                     try {
                         String v = toString(gtValues[i]);
                         if (v != null) {
@@ -179,7 +187,8 @@ public class CubeTupleConverter implements ITupleConverter {
                         tuple.setDimensionValue(ti, toString(gtValues[i]));
                     }
                 } else {
-                    tuple.setDimensionValue(ti, toString(gtValues[i]));
+                    // For batch
+                    setDimensionValue(tuple, ti, toString(gtValues[i]));
                 }
             }
         }
@@ -209,6 +218,33 @@ public class CubeTupleConverter implements ITupleConverter {
         }
     }
 
+    private void setDimensionValue(Tuple tuple, int idx, String valueStr) {
+        if (valueStr == null) {
+            tuple.setDimensionValueDirectly(idx, valueStr);
+            return;
+        }
+
+        Object valueConvert = null;
+        TblColRef col = tupleInfo.getColumn(idx);
+        RowKeyColDesc rowKeyColDesc = rowKeyDesc.getColDescUncheck(col);
+        if (rowKeyColDesc != null) {
+            // convert value if inconsistency exists between rowkey col encoding & col data type
+            if (col.getType().isDate() && !RowKeyColDesc.isDateDimEnc(rowKeyColDesc)) {
+                long tmpValue = (Long) Tuple.convertOptiqCellValue(valueStr, "timestamp");
+                valueConvert = Tuple.millisToEpicDays(tmpValue);
+            } else if (col.getType().isDatetime() && !RowKeyColDesc.isTimeDimEnc(rowKeyColDesc)) {
+                int tmpValue = (Integer) Tuple.convertOptiqCellValue(valueStr, "date");
+                valueConvert = Tuple.epicDaysToMillis(tmpValue);
+            }
+        }
+
+        if (valueConvert != null) {
+            tuple.setDimensionValueDirectly(idx, valueConvert);
+        } else {
+            tuple.setDimensionValue(idx, valueStr);
+        }
+    }
+
     @Override
     public void close() throws IOException {
         for (ILookupTable usedLookupTable : usedLookupTables) {
@@ -262,6 +298,10 @@ public class CubeTupleConverter implements ITupleConverter {
                 public void fillDerivedColumns(Object[] gtValues, Tuple tuple) {
                     for (int i = 0; i < hostTmpIdx.length; i++) {
                         lookupKey.data[i] = CubeTupleConverter.toString(gtValues[hostTmpIdx[i]]);
+                        // if the primary key of lookup table is date time type, do this change in case of data type inconsistency
+                        if (deriveInfo.join.getPrimaryKeyColumns()[i].getType().isDateTimeFamily()) {
+                            lookupKey.data[i] = String.valueOf(DateFormat.stringToMillis(lookupKey.data[i]));
+                        }
                     }
 
                     String[] lookupRow = lookupTable.getRow(lookupKey);
diff --git a/kylin-it/src/test/resources/query/sql_casewhen/query58.sql b/kylin-it/src/test/resources/query/sql_casewhen/query58.sql
new file mode 100644
index 0000000..c65c911
--- /dev/null
+++ b/kylin-it/src/test/resources/query/sql_casewhen/query58.sql
@@ -0,0 +1,22 @@
+--
+-- 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.
+--
+
+SELECT (CASE WHEN ("TEST_KYLIN_FACT"."LSTG_FORMAT_NAME" = 'Auction') THEN 'Auction2' ELSE 'Auction1' END) AS "LSTG_FORMAT_NAME__group_",
+  SUM("TEST_KYLIN_FACT"."PRICE") AS "sum_PRICE_ok"
+FROM "TEST_KYLIN_FACT" "TEST_KYLIN_FACT"
+GROUP BY (CASE WHEN ("TEST_KYLIN_FACT"."LSTG_FORMAT_NAME" =  'Auction') THEN 'Auction2' ELSE 'Auction1' END)
\ No newline at end of file
diff --git a/server-base/src/main/java/org/apache/kylin/rest/service/TableSchemaUpdateChecker.java b/server-base/src/main/java/org/apache/kylin/rest/service/TableSchemaUpdateChecker.java
index 84cc19b..c0acff4 100644
--- a/server-base/src/main/java/org/apache/kylin/rest/service/TableSchemaUpdateChecker.java
+++ b/server-base/src/main/java/org/apache/kylin/rest/service/TableSchemaUpdateChecker.java
@@ -128,6 +128,10 @@ public class TableSchemaUpdateChecker {
         } else if (column.getType().isNumberFamily()) {
             // Both are float/double should be fine.
             return newCol.getType().isNumberFamily();
+        } else if ((column.getType().isStringFamily() && newCol.getType().isDateTimeFamily())
+                && metadataManager.getConfig().isAbleChangeStringToDateTime()) {
+            // String can be converted to Date or Time
+            return true;
         } else {
             // only compare base type name, changing precision or scale should be fine
             return column.getTypeName().equals(newCol.getTypeName());
diff --git a/server-base/src/test/java/org/apache/kylin/rest/service/update/TableSchemaUpdaterTest.java b/server-base/src/test/java/org/apache/kylin/rest/service/update/TableSchemaUpdaterTest.java
index 7b8eecb..288877c 100644
--- a/server-base/src/test/java/org/apache/kylin/rest/service/update/TableSchemaUpdaterTest.java
+++ b/server-base/src/test/java/org/apache/kylin/rest/service/update/TableSchemaUpdaterTest.java
@@ -24,7 +24,6 @@ import java.io.DataInputStream;
 import java.io.DataOutputStream;
 import java.io.File;
 import java.io.FileInputStream;
-import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.nio.charset.StandardCharsets;
 import java.nio.file.Files;