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;