This is an automated email from the ASF dual-hosted git repository. yiguolei pushed a commit to branch branch-2.1 in repository https://gitbox.apache.org/repos/asf/doris.git
The following commit(s) were added to refs/heads/branch-2.1 by this push: new 6bd93b119fa [pick](cast)Feature cast complexttype2 json (#38632) 6bd93b119fa is described below commit 6bd93b119fa2546cd59b334f307acab4e63c4b3f Author: amory <wangqian...@selectdb.com> AuthorDate: Thu Aug 1 09:18:15 2024 +0800 [pick](cast)Feature cast complexttype2 json (#38632) ## Proposed changes backport: https://github.com/apache/doris/pull/36548 Issue Number: close #xxx <!--Describe your changes.--> --- .../main/java/org/apache/doris/catalog/Type.java | 3 + .../java/org/apache/doris/analysis/CastExpr.java | 4 + .../nereids/rules/expression/check/CheckCast.java | 22 ++++ .../doris/nereids/trees/expressions/Cast.java | 2 +- .../query_p0/cast/test_complextype_to_json.out | 67 ++++++++++++ .../query_p0/cast/test_complextype_to_json.groovy | 119 +++++++++++++++++++++ 6 files changed, 216 insertions(+), 1 deletion(-) diff --git a/fe/fe-common/src/main/java/org/apache/doris/catalog/Type.java b/fe/fe-common/src/main/java/org/apache/doris/catalog/Type.java index 46cda54753e..f2eccaaf54a 100644 --- a/fe/fe-common/src/main/java/org/apache/doris/catalog/Type.java +++ b/fe/fe-common/src/main/java/org/apache/doris/catalog/Type.java @@ -833,6 +833,9 @@ public abstract class Type { } public static boolean canCastTo(Type sourceType, Type targetType) { + if (targetType.isJsonbType() && sourceType.isComplexType()) { + return true; + } if (sourceType.isVariantType() && (targetType.isScalarType() || targetType.isArrayType())) { // variant could cast to scalar types and array return true; diff --git a/fe/fe-core/src/main/java/org/apache/doris/analysis/CastExpr.java b/fe/fe-core/src/main/java/org/apache/doris/analysis/CastExpr.java index 130b2d43ed0..6ca52e226aa 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/analysis/CastExpr.java +++ b/fe/fe-core/src/main/java/org/apache/doris/analysis/CastExpr.java @@ -152,6 +152,10 @@ public class CastExpr extends Expr { Type from = getActualArgTypes(collectChildReturnTypes())[0]; Type to = getActualType(type); NullableMode nullableMode = TYPE_NULLABLE_MODE.get(Pair.of(from, to)); + // for complex type cast to jsonb we make ret is always nullable + if (from.isComplexType() && type.isJsonbType()) { + nullableMode = Function.NullableMode.ALWAYS_NULLABLE; + } Preconditions.checkState(nullableMode != null, "cannot find nullable node for cast from " + from + " to " + to); fn = new Function(new FunctionName(getFnName(type)), Lists.newArrayList(e.type), type, diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/expression/check/CheckCast.java b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/expression/check/CheckCast.java index 69a9105d653..e76d7ef344d 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/expression/check/CheckCast.java +++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/expression/check/CheckCast.java @@ -92,6 +92,9 @@ public class CheckCast implements ExpressionPatternRuleFactory { } return true; } else if (originalType instanceof JsonType || targetType instanceof JsonType) { + if (originalType.isComplexType() && !checkMapKeyIsStringLikeForJson(originalType)) { + return false; + } return true; } else { return checkPrimitiveType(originalType, targetType); @@ -127,4 +130,23 @@ public class CheckCast implements ExpressionPatternRuleFactory { } return true; } + + /** + * check if complexType type which contains map, make sure key is string like for json + * + * @param complexType need to check + * @return true if complexType can cast to json + */ + public static boolean checkMapKeyIsStringLikeForJson(DataType complexType) { + if (complexType.isMapType()) { + return ((MapType) complexType).getKeyType().isStringLikeType(); + } else if (complexType.isArrayType()) { + return checkMapKeyIsStringLikeForJson(((ArrayType) complexType).getItemType()); + } else if (complexType.isStructType()) { + for (StructField f : ((StructType) complexType).getFields()) { + return checkMapKeyIsStringLikeForJson(f.getDataType()); + } + } + return true; + } } diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/Cast.java b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/Cast.java index 62bd3639b5a..124ed589d49 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/Cast.java +++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/Cast.java @@ -79,7 +79,7 @@ public class Cast extends Expression implements UnaryExpression { return true; } else if (!childDataType.isTimeLikeType() && targetType.isTimeLikeType()) { return true; - } else if (childDataType.isJsonType()) { + } else if (childDataType.isJsonType() || targetType.isJsonType()) { return true; } else { return child().nullable(); diff --git a/regression-test/data/query_p0/cast/test_complextype_to_json.out b/regression-test/data/query_p0/cast/test_complextype_to_json.out new file mode 100644 index 00000000000..7412966f767 --- /dev/null +++ b/regression-test/data/query_p0/cast/test_complextype_to_json.out @@ -0,0 +1,67 @@ +-- This file is automatically generated. You should know what you did if you want to edit this +-- !select -- +[[],[]] + +-- !select -- +{"k1":"v31","k2":"300"} + +-- !select -- +[] + +-- !select -- +[123,456] + +-- !select -- +["abc","def"] + +-- !select -- +[null,"1","0","100","6.18","abc"] + +-- !select -- +[{"k1":"v41","k2":"400"},{"k1":"v41","k2":"400"}] + +-- !select -- +["[['k1', 'k2'], ['v41', '400']]","1","a","3.14"] + +-- !select -- +{"k1":"v31","k2":"300","a1":"['[['k1', 'k2'], ['v41', '400']]', '1', 'a', '3.14']"} + +-- !select -- +{"col1":"a","col2":1,"col3":"doris","col4":"aaaaa","col5":1.32} + +-- !sql_arr_agg_cast -- +1 ["LC","LB","alex"] ["V1_3","V1_2",null] +2 ["LC","LB","LA"] ["V2_3","V2_2","V2_1"] +3 ["LC",null,"LA"] ["V3_3",null,"V3_1"] +4 ["LC","LB","LA"] ["V4_3","V4_2","V4_1"] +5 [null,"LC","LB","LA"] ["V5_3","V5_3","V5_2","V5_1"] +6 [null,"LC","LC","LC","LC"] ["V6_3",null,"V6_3",null,"V6_3"] +7 [null,"LC","LC","LC","LC"] ["V7_3",null,"V7_3",null,"V7_3"] + +-- !sql_arr_agg_cast_json_object -- +{"id":1,"label":"[\\"LC\\",\\"LB\\",\\"alex\\"]","field":"[\\"V1_3\\",\\"V1_2\\",null]"} +{"id":2,"label":"[\\"LC\\",\\"LB\\",\\"LA\\"]","field":"[\\"V2_3\\",\\"V2_2\\",\\"V2_1\\"]"} +{"id":3,"label":"[\\"LC\\",null,\\"LA\\"]","field":"[\\"V3_3\\",null,\\"V3_1\\"]"} +{"id":4,"label":"[\\"LC\\",\\"LB\\",\\"LA\\"]","field":"[\\"V4_3\\",\\"V4_2\\",\\"V4_1\\"]"} +{"id":5,"label":"[null,\\"LC\\",\\"LB\\",\\"LA\\"]","field":"[\\"V5_3\\",\\"V5_3\\",\\"V5_2\\",\\"V5_1\\"]"} +{"id":6,"label":"[null,\\"LC\\",\\"LC\\",\\"LC\\",\\"LC\\"]","field":"[\\"V6_3\\",null,\\"V6_3\\",null,\\"V6_3\\"]"} +{"id":7,"label":"[null,\\"LC\\",\\"LC\\",\\"LC\\",\\"LC\\"]","field":"[\\"V7_3\\",null,\\"V7_3\\",null,\\"V7_3\\"]"} + +-- !sql_map_agg_cast -- +1 {"LC":"V1_3","LB":"V1_2","alex":null} +2 {"LC":"V2_3","LB":"V2_2","LA":"V2_1"} +3 {"LC":"V3_3","LA":"V3_1"} +4 {"LC":"V4_3","LB":"V4_2","LA":"V4_1"} +5 {"LC":"V5_3","LB":"V5_2","LA":"V5_1"} +6 {"LC":null} +7 {"LC":null} + +-- !sql_map_agg_cast_json_object -- +{"id":1,"map_label":"{\\"LC\\":\\"V1_3\\",\\"LB\\":\\"V1_2\\",\\"alex\\":null}"} +{"id":2,"map_label":"{\\"LC\\":\\"V2_3\\",\\"LB\\":\\"V2_2\\",\\"LA\\":\\"V2_1\\"}"} +{"id":3,"map_label":"{\\"LC\\":\\"V3_3\\",\\"LA\\":\\"V3_1\\"}"} +{"id":4,"map_label":"{\\"LC\\":\\"V4_3\\",\\"LB\\":\\"V4_2\\",\\"LA\\":\\"V4_1\\"}"} +{"id":5,"map_label":"{\\"LC\\":\\"V5_3\\",\\"LB\\":\\"V5_2\\",\\"LA\\":\\"V5_1\\"}"} +{"id":6,"map_label":"{\\"LC\\":null}"} +{"id":7,"map_label":"{\\"LC\\":null}"} + diff --git a/regression-test/suites/query_p0/cast/test_complextype_to_json.groovy b/regression-test/suites/query_p0/cast/test_complextype_to_json.groovy new file mode 100644 index 00000000000..18d83d11124 --- /dev/null +++ b/regression-test/suites/query_p0/cast/test_complextype_to_json.groovy @@ -0,0 +1,119 @@ +// 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. + +suite('test_complextype_to_json', "query_p0") { + // do support in nereids + sql """ set experimental_enable_nereids_planner=true""" + sql """ set enable_fallback_to_original_planner=false; """ + + // literal cast + qt_select """SELECT CAST({} AS JSON)""" + qt_select """SELECT CAST({"k1":"v31", "k2": 300} AS JSON)""" + qt_select """SELECT CAST([] AS JSON)""" + qt_select """SELECT CAST([123, 456] AS JSON)""" + qt_select """SELECT CAST(["abc", "def"] AS JSON)""" + qt_select """SELECT CAST([null, true, false, 100, 6.18, "abc"] AS JSON)""" + qt_select """SELECT CAST([{"k1":"v41", "k2": 400}, {"k1":"v41", "k2": 400}] AS JSON)""" + qt_select """SELECT CAST([{"k1":"v41", "k2": 400}, 1, "a", 3.14] AS JSON)""" + qt_select """SELECT CAST({"k1":"v31", "k2": 300, "a1": [{"k1":"v41", "k2": 400}, 1, "a", 3.14]} AS JSON)""" + qt_select """SELECT CAST(struct('a', 1, 'doris', 'aaaaa', 1.32) AS JSON)""" + // invalid map key cast + test { + sql """SELECT CAST(map(1, 'a', 2, 'b') AS JSON)""" + exception "errCode = 2," + } + test { + sql """SELECT CAST([{1:"v41", 2: 400}] AS JSON)""" + exception "errCode = 2," + } + + + sql """ DROP TABLE IF EXISTS test_agg_to_json; """ + sql """ + CREATE TABLE `test_agg_to_json` ( + `id` int(11) NOT NULL, + `label_name` varchar(32) default null, + `value_field` string default null + ) ENGINE=OLAP + DUPLICATE KEY(`id`) + COMMENT 'OLAP' + DISTRIBUTED BY HASH(`id`) BUCKETS 1 + PROPERTIES ( + "replication_allocation" = "tag.location.default: 1", + "storage_format" = "V2", + "light_schema_change" = "true", + "disable_auto_compaction" = "false", + "enable_single_replica_compaction" = "false" + ); + """ + + sql """ + insert into `test_agg_to_json` values + (1, "alex",NULL), + (1, "LB", "V1_2"), + (1, "LC", "V1_3"), + (2, "LA", "V2_1"), + (2, "LB", "V2_2"), + (2, "LC", "V2_3"), + (3, "LA", "V3_1"), + (3, NULL, NULL), + (3, "LC", "V3_3"), + (4, "LA", "V4_1"), + (4, "LB", "V4_2"), + (4, "LC", "V4_3"), + (5, "LA", "V5_1"), + (5, "LB", "V5_2"), + (5, "LC", "V5_3"), + (5, NULL, "V5_3"), + (6, "LC", "V6_3"), + (6, "LC", NULL), + (6, "LC", "V6_3"), + (6, "LC", NULL), + (6, NULL, "V6_3"), + (7, "LC", "V7_3"), + (7, "LC", NULL), + (7, "LC", "V7_3"), + (7, "LC", NULL), + (7, NULL, "V7_3"); + """ + + // array_agg result cast to json then combination to json_object + qt_sql_arr_agg_cast """ select t.id, cast(t.label_name as json), cast(t.value_field as json) from (select id, array_agg(label_name) as label_name, array_agg(value_field) as value_field from test_agg_to_json group by id) t order by t.id; """ + qt_sql_arr_agg_cast_json_object """ select json_object("id", t.id, "label", cast(t.label_name as json), "field", cast(t.value_field as json)) from (select id, array_agg(label_name) as label_name, array_agg(value_field) as value_field from test_agg_to_json group by id) t order by t.id; """ + + // map_agg result cast to json then combination to json_object + qt_sql_map_agg_cast """ + WITH `labels` as ( + SELECT `id`, map_agg(`label_name`, `value_field`) m FROM test_agg_to_json GROUP BY `id` + ) + SELECT + id, + cast(m as json) + FROM `labels` + ORDER BY `id`; + """ + qt_sql_map_agg_cast_json_object """ + WITH `labels` as ( + SELECT `id`, map_agg(`label_name`, `value_field`) m FROM test_agg_to_json GROUP BY `id` + ) + SELECT + json_object("id", id, "map_label", cast(m as json)) + FROM `labels` + ORDER BY `id`; + """ + +} \ No newline at end of file --------------------------------------------------------------------- To unsubscribe, e-mail: commits-unsubscr...@doris.apache.org For additional commands, e-mail: commits-h...@doris.apache.org