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

Reply via email to