This is an automated email from the ASF dual-hosted git repository. eldenmoon 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 def0ed37f84 [improve](schema-change) support nested type with varchar type to support length growing (#46639) def0ed37f84 is described below commit def0ed37f844d9f48150ed813c6f76fd0d7a9fff Author: amory <wangqian...@selectdb.com> AuthorDate: Wed Jan 15 11:13:54 2025 +0800 [improve](schema-change) support nested type with varchar type to support length growing (#46639) The array|map|struct schema-change behavior supports moditfy to change the length of the varchar type in the current column type. before ``` mysql> alter table t_sc MODIFY COLUMN s struct<col:varchar(20)>; ERROR 1105 (HY000): errCode = 2, detailMessage = Can not change struct<col:varchar(10)> to `__doris_shadow_s` struct<col:varchar(20)> NULL ``` --- .../apache/doris/alter/SchemaChangeHandler.java | 8 +- .../main/java/org/apache/doris/catalog/Column.java | 15 +- .../java/org/apache/doris/catalog/ColumnType.java | 55 ++++++ .../test_varchar_sc_in_complex.out | 34 ++++ .../test_varchar_sc_in_complex.groovy | 187 +++++++++++++++++++++ 5 files changed, 286 insertions(+), 13 deletions(-) diff --git a/fe/fe-core/src/main/java/org/apache/doris/alter/SchemaChangeHandler.java b/fe/fe-core/src/main/java/org/apache/doris/alter/SchemaChangeHandler.java index af2a0a7a9bd..45064092069 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/alter/SchemaChangeHandler.java +++ b/fe/fe-core/src/main/java/org/apache/doris/alter/SchemaChangeHandler.java @@ -38,6 +38,7 @@ import org.apache.doris.analysis.SlotRef; import org.apache.doris.catalog.AggregateType; import org.apache.doris.catalog.BinlogConfig; import org.apache.doris.catalog.Column; +import org.apache.doris.catalog.ColumnType; import org.apache.doris.catalog.Database; import org.apache.doris.catalog.DistributionInfo; import org.apache.doris.catalog.DistributionInfo.DistributionInfoType; @@ -629,7 +630,12 @@ public class SchemaChangeHandler extends AlterHandler { // TODO:the case where columnPos is not empty has not been considered if (columnPos == null && col.getDataType() == PrimitiveType.VARCHAR && modColumn.getDataType() == PrimitiveType.VARCHAR) { - col.checkSchemaChangeAllowed(modColumn); + ColumnType.checkSupportSchemaChangeForCharType(col.getType(), modColumn.getType()); + lightSchemaChange = olapTable.getEnableLightSchemaChange(); + } + if (columnPos == null && col.getDataType().isComplexType() + && modColumn.getDataType().isComplexType()) { + ColumnType.checkSupportSchemaChangeForComplexType(col.getType(), modColumn.getType(), true); lightSchemaChange = olapTable.getEnableLightSchemaChange(); } if (col.isClusterKey()) { diff --git a/fe/fe-core/src/main/java/org/apache/doris/catalog/Column.java b/fe/fe-core/src/main/java/org/apache/doris/catalog/Column.java index 5a87563bd18..fa7c7feb7f0 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/catalog/Column.java +++ b/fe/fe-core/src/main/java/org/apache/doris/catalog/Column.java @@ -847,11 +847,6 @@ public class Column implements GsonPostProcessable { throw new DdlException("Dest column name is empty"); } - // now nested type can only support change order - if (type.isComplexType() && !type.equals(other.type)) { - throw new DdlException("Can not change " + type + " to " + other); - } - if (!ColumnType.isSchemaChangeAllowed(type, other.type)) { throw new DdlException("Can not change " + getDataType() + " to " + other.getDataType()); } @@ -888,13 +883,9 @@ public class Column implements GsonPostProcessable { } } - if ((getDataType() == PrimitiveType.VARCHAR && other.getDataType() == PrimitiveType.VARCHAR) || ( - getDataType() == PrimitiveType.CHAR && other.getDataType() == PrimitiveType.VARCHAR) || ( - getDataType() == PrimitiveType.CHAR && other.getDataType() == PrimitiveType.CHAR)) { - if (getStrLen() > other.getStrLen()) { - throw new DdlException("Cannot shorten string length"); - } - } + // Nested types only support changing the order and increasing the length of the nested char type + // Char-type only support length growing + ColumnType.checkSupportSchemaChangeForComplexType(type, other.type, false); // now we support convert decimal to varchar type if ((getDataType() == PrimitiveType.DECIMALV2 || getDataType().isDecimalV3Type()) diff --git a/fe/fe-core/src/main/java/org/apache/doris/catalog/ColumnType.java b/fe/fe-core/src/main/java/org/apache/doris/catalog/ColumnType.java index 458a6a46110..234c0d4eef0 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/catalog/ColumnType.java +++ b/fe/fe-core/src/main/java/org/apache/doris/catalog/ColumnType.java @@ -17,6 +17,7 @@ package org.apache.doris.catalog; +import org.apache.doris.common.DdlException; import org.apache.doris.common.FeMetaVersion; import org.apache.doris.common.io.Text; import org.apache.doris.persist.gson.GsonUtils; @@ -168,6 +169,60 @@ public abstract class ColumnType { return schemaChangeMatrix[lhs.getPrimitiveType().ordinal()][rhs.getPrimitiveType().ordinal()]; } + // This method defines the char type + // to support the schema-change behavior of length growth. + // return true if the checkType and other are both char-type otherwise return false, + // which used in checkSupportSchemaChangeForComplexType + public static boolean checkSupportSchemaChangeForCharType(Type checkType, Type other) throws DdlException { + if ((checkType.getPrimitiveType() == PrimitiveType.VARCHAR && other.getPrimitiveType() == PrimitiveType.VARCHAR) + || (checkType.getPrimitiveType() == PrimitiveType.CHAR + && other.getPrimitiveType() == PrimitiveType.VARCHAR) + || (checkType.getPrimitiveType() == PrimitiveType.CHAR + && other.getPrimitiveType() == PrimitiveType.CHAR)) { + if (checkType.getLength() > other.getLength()) { + throw new DdlException("Cannot shorten string length"); + } + return true; + } else { + // types equal can return true + return checkType.equals(other); + } + } + + // This method defines the complex type which is struct, array, map if nested char-type + // to support the schema-change behavior of length growth. + public static void checkSupportSchemaChangeForComplexType(Type checkType, Type other, boolean nested) + throws DdlException { + if (checkType.isStructType() && other.isStructType()) { + StructType thisStructType = (StructType) checkType; + StructType otherStructType = (StructType) other; + if (thisStructType.getFields().size() != otherStructType.getFields().size()) { + throw new DdlException("Cannot change struct type with different field size"); + } + for (int i = 0; i < thisStructType.getFields().size(); i++) { + checkSupportSchemaChangeForComplexType(thisStructType.getFields().get(i).getType(), + otherStructType.getFields().get(i).getType(), true); + } + } else if (checkType.isArrayType()) { + if (!other.isArrayType()) { + throw new DdlException("Cannot change " + checkType.toSql() + " to " + other.toSql()); + } + checkSupportSchemaChangeForComplexType(((ArrayType) checkType).getItemType(), + ((ArrayType) other).getItemType(), true); + } else if (checkType.isMapType() && other.isMapType()) { + checkSupportSchemaChangeForComplexType(((MapType) checkType).getKeyType(), + ((MapType) other).getKeyType(), true); + checkSupportSchemaChangeForComplexType(((MapType) checkType).getValueType(), + ((MapType) other).getValueType(), true); + } else { + // only support char-type schema change behavior for nested complex type + // if nested is false, we do not check return value. + if (nested && !checkSupportSchemaChangeForCharType(checkType, other)) { + throw new DdlException("Cannot change " + checkType.toSql() + " to " + other.toSql()); + } + } + } + public static void write(DataOutput out, Type type) throws IOException { Preconditions.checkArgument(type.isScalarType() || type.isAggStateType() || type.isArrayType() || type.isMapType() || type.isStructType(), diff --git a/regression-test/data/schema_change_p0/test_varchar_sc_in_complex.out b/regression-test/data/schema_change_p0/test_varchar_sc_in_complex.out new file mode 100644 index 00000000000..c852a4008fe --- /dev/null +++ b/regression-test/data/schema_change_p0/test_varchar_sc_in_complex.out @@ -0,0 +1,34 @@ +-- This file is automatically generated. You should know what you did if you want to edit this +-- !sc_origin -- +0 ["2025-01-01"] {"amory":"better"} {"col":"commiter"} +1 ["2025-01-02"] {"doris":"better"} {"col":"amory"} + +-- !sc_after -- +0 ["2025-01-01"] {"amory":"better"} {"col":"commiter"} +1 ["2025-01-02"] {"doris":"better"} {"col":"amory"} +2 ["2025-01-03-22-33"] {"doris":"better"} {"col":"amory"} + +-- !sc_origin -- +0 ["2025-01-01"] {"amory":"better"} {"col":"commiter"} +1 ["2025-01-02"] {"doris":"better"} {"col":"amory"} +2 ["2025-01-03-22-33"] {"doris":"better"} {"col":"amory"} + +-- !sc_after -- +0 ["2025-01-01"] {"amory":"better"} {"col":"commiter"} +1 ["2025-01-02"] {"doris":"better"} {"col":"amory"} +2 ["2025-01-03-22-33"] {"doris":"better"} {"col":"amory"} +3 ["2025-01-03-22-33"] {"doris111111111":"better2222222222"} {"col":"amory"} + +-- !sc_origin -- +0 ["2025-01-01"] {"amory":"better"} {"col":"commiter"} +1 ["2025-01-02"] {"doris":"better"} {"col":"amory"} +2 ["2025-01-03-22-33"] {"doris":"better"} {"col":"amory"} +3 ["2025-01-03-22-33"] {"doris111111111":"better2222222222"} {"col":"amory"} + +-- !sc_after -- +0 ["2025-01-01"] {"amory":"better"} {"col":"commiter"} +1 ["2025-01-02"] {"doris":"better"} {"col":"amory"} +2 ["2025-01-03-22-33"] {"doris":"better"} {"col":"amory"} +3 ["2025-01-03-22-33"] {"doris111111111":"better2222222222"} {"col":"amory"} +4 ["2025-01-03-22-33"] {"doris":"better"} {"col":"amoryIsBetter"} + diff --git a/regression-test/suites/schema_change_p0/test_varchar_sc_in_complex.groovy b/regression-test/suites/schema_change_p0/test_varchar_sc_in_complex.groovy new file mode 100644 index 00000000000..d3d2554025f --- /dev/null +++ b/regression-test/suites/schema_change_p0/test_varchar_sc_in_complex.groovy @@ -0,0 +1,187 @@ +// 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. + +import org.codehaus.groovy.runtime.IOGroovyMethods +import java.util.concurrent.TimeUnit +import org.awaitility.Awaitility + +suite ("test_varchar_sc_in_complex") { + def getJobState = { tableName -> + def jobStateResult = sql """ SHOW ALTER TABLE COLUMN WHERE IndexName='${tableName}' ORDER BY createtime DESC LIMIT 1 """ + return jobStateResult[0][9] + } + + def tableName = "test_varchar_sc_in_complex" + + try { + + String backend_id; + def backendId_to_backendIP = [:] + def backendId_to_backendHttpPort = [:] + getBackendIpHttpPort(backendId_to_backendIP, backendId_to_backendHttpPort); + + sql """ DROP TABLE IF EXISTS ${tableName} """ + sql """ + CREATE TABLE IF NOT EXISTS ${tableName} ( + `c0` LARGEINT NOT NULL, + `c_a` ARRAY<VARCHAR(10)>, + `c_m` MAP<VARCHAR(10),VARCHAR(10)>, + `c_s` STRUCT<col:VARCHAR(10)> + ) DISTRIBUTED BY HASH(c0) BUCKETS 1 + PROPERTIES ( "replication_num" = "1", "light_schema_change" = "true" ) + """ + + sql """ insert into ${tableName} values + (0,['2025-01-01'], {'amory':'better'}, named_struct('col','commiter')); + """ + sql """ insert into ${tableName} values + (1,['2025-01-02'], {'doris':'better'}, named_struct('col','amory')); + """ + // this can be insert but with cut off the left string to 10 + test { + sql """ insert into ${tableName} values + (11, ['2025-01-03-22-33'], {'doris111111111':'better2222222222'}, named_struct('col','amoryIsBetter')); + """ + exception "Insert has filtered data in strict mode" + } + + test { + sql """ alter table ${tableName} modify column c_a array<varchar(3)> + """ + exception "Cannot shorten string length" + } + + test { + sql """ alter table ${tableName} modify column c_m map<varchar(3),varchar(3)> + """ + exception "Cannot shorten string length" + } + + test { + sql """ alter table ${tableName} modify column c_s struct<col:varchar(3)> + """ + exception "Cannot shorten string length" + } + + // add case alter modify array/map/struct to other type + // test array to struct + test { + sql """ alter table ${tableName} modify column c_a struct<col:varchar(3)> + """ + exception "Can not change ARRAY to STRUCT" + } + // test array to map + test { + sql """ alter table ${tableName} modify column c_a map<varchar(3),varchar(3)> + """ + exception "Can not change ARRAY to MAP" + } + // test map to array + test { + sql """ alter table ${tableName} modify column c_m array<varchar(3)> + """ + exception "Can not change MAP to ARRAY" + } + // test map to struct + test { + sql """ alter table ${tableName} modify column c_m struct<col:varchar(3)> + """ + exception "Can not change MAP to STRUCT" + } + // test struct to array + test { + sql """ alter table ${tableName} modify column c_s array<varchar(3)> + """ + exception "Can not change STRUCT to ARRAY" + } + // test struct to map + test { + sql """ alter table ${tableName} modify column c_s map<varchar(3),varchar(3)> + """ + exception "Can not change STRUCT to MAP" + } + + + sql """ alter table ${tableName} modify column c_a array<varchar(20)> """ + int max_try_secs = 300 + Awaitility.await().atMost(max_try_secs, TimeUnit.SECONDS).with().pollDelay(100, TimeUnit.MILLISECONDS).await().until(() -> { + String result = getJobState(tableName) + if (result == "FINISHED") { + return true; + } + return false; + }); + + String[][] res = sql """ desc ${tableName} """ + logger.info(res[1][1]) + // array varchar(10) + assertEquals(res[1][1].toLowerCase(),"array<varchar(20)>") + + qt_sc_origin " select * from ${tableName} order by c0; " + + // then insert some data to modified array with varchar(20) + sql """ insert into ${tableName} values + (2,['2025-01-03-22-33'], {'doris':'better'}, named_struct('col','amory')); + """ + qt_sc_after " select * from ${tableName} order by c0; " + + // map varchar(10) + sql """ alter table ${tableName} modify column c_m map<varchar(20),varchar(20)> """ + Awaitility.await().atMost(max_try_secs, TimeUnit.SECONDS).with().pollDelay(100, TimeUnit.MILLISECONDS).await().until(() -> { + String result = getJobState(tableName) + if (result == "FINISHED") { + return true; + } + return false; + }); + + res = sql """ desc ${tableName} """ + logger.info(res[2][1]) + assertEquals(res[2][1].toLowerCase(),"map<varchar(20),varchar(20)>") + + // insert some data to modified map with varchar(20) + qt_sc_origin " select * from ${tableName} order by c0; " + sql """ insert into ${tableName} values + (3,['2025-01-03-22-33'], {'doris111111111':'better2222222222'}, named_struct('col','amory')); + """ + qt_sc_after " select * from ${tableName} order by c0; " + + // struct varchar(10) + sql """ alter table ${tableName} modify column c_s struct<col:varchar(20)> """ + Awaitility.await().atMost(max_try_secs, TimeUnit.SECONDS).with().pollDelay(100, TimeUnit.MILLISECONDS).await().until(() -> { + String result = getJobState(tableName) + if (result == "FINISHED") { + return true; + } + return false; + }); + + res = sql """ desc ${tableName} """ + logger.info(res[3][1]) + assertEquals(res[3][1].toLowerCase(),"struct<col:varchar(20)>") + + // insert some data to modified struct with varchar(20) + qt_sc_origin " select * from ${tableName} order by c0; " + sql """ insert into ${tableName} values + (4,['2025-01-03-22-33'], {'doris':'better'}, named_struct('col','amoryIsBetter')); + """ + qt_sc_after " select * from ${tableName} order by c0; " + } finally { + try_sql("DROP TABLE IF EXISTS ${tableName}") + } + +} --------------------------------------------------------------------- To unsubscribe, e-mail: commits-unsubscr...@doris.apache.org For additional commands, e-mail: commits-h...@doris.apache.org