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

yiguolei pushed a commit to branch branch-4.1
in repository https://gitbox.apache.org/repos/asf/doris.git


The following commit(s) were added to refs/heads/branch-4.1 by this push:
     new f2e81cf3deb branch-4.1: [fix](analyze) Preserve variant subfields in 
view definitions to fix select view result wrong when view select has variant 
field #62907 (#63151)
f2e81cf3deb is described below

commit f2e81cf3deb7bb275e3fc088c067ddbcb8a57e74
Author: github-actions[bot] 
<41898282+github-actions[bot]@users.noreply.github.com>
AuthorDate: Thu May 21 10:21:54 2026 +0800

    branch-4.1: [fix](analyze) Preserve variant subfields in view definitions 
to fix select view result wrong when view select has variant field #62907 
(#63151)
    
    Cherry-picked from #62907
    
    Co-authored-by: seawinde <[email protected]>
---
 .../nereids/rules/analysis/ExpressionAnalyzer.java |  52 ++++---
 .../test_create_view_variant_nested_field.groovy   | 157 +++++++++++++++++++++
 2 files changed, 191 insertions(+), 18 deletions(-)

diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/analysis/ExpressionAnalyzer.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/analysis/ExpressionAnalyzer.java
index d11a5c89bb9..e92c4b62546 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/analysis/ExpressionAnalyzer.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/analysis/ExpressionAnalyzer.java
@@ -1079,83 +1079,82 @@ public class ExpressionAnalyzer extends 
SubExprAnalyzer<ExpressionRewriteContext
 
     private List<? extends Expression> bindExpressionByCatalogDbTableColumn(
             UnboundSlot unboundSlot, List<String> nameParts, 
Optional<Pair<Integer, Integer>> idxInSql, Scope scope) {
-        List<Slot> slots = addSqlIndexInfo(bindSingleSlotByCatalog(
-                        nameParts.get(0), nameParts.get(1), nameParts.get(2), 
nameParts.get(3), scope), idxInSql);
+        List<Slot> slots = bindSingleSlotByCatalog(
+                        nameParts.get(0), nameParts.get(1), nameParts.get(2), 
nameParts.get(3), scope);
         if (slots.isEmpty()) {
             return bindExpressionByDbTableColumn(unboundSlot, nameParts, 
idxInSql, scope);
         } else if (slots.size() > 1) {
-            return slots;
+            return addSqlIndexInfo(slots, idxInSql);
         }
         if (nameParts.size() == 4) {
-            return slots;
+            return addSqlIndexInfo(slots, idxInSql);
         }
 
         Optional<Expression> expression = bindNestedFields(
                 unboundSlot, slots.get(0), nameParts.subList(4, 
nameParts.size())
         );
         if (!expression.isPresent()) {
-            return slots;
+            return addSqlIndexInfo(slots, idxInSql);
         }
         return ImmutableList.of(expression.get());
     }
 
     private List<? extends Expression> bindExpressionByDbTableColumn(
             UnboundSlot unboundSlot, List<String> nameParts, 
Optional<Pair<Integer, Integer>> idxInSql, Scope scope) {
-        List<Slot> slots = addSqlIndexInfo(
-                bindSingleSlotByDb(nameParts.get(0), nameParts.get(1), 
nameParts.get(2), scope), idxInSql);
+        List<Slot> slots = bindSingleSlotByDb(nameParts.get(0), 
nameParts.get(1), nameParts.get(2), scope);
         if (slots.isEmpty()) {
             return bindExpressionByTableColumn(unboundSlot, nameParts, 
idxInSql, scope);
         } else if (slots.size() > 1) {
-            return slots;
+            return addSqlIndexInfo(slots, idxInSql);
         }
         if (nameParts.size() == 3) {
-            return slots;
+            return addSqlIndexInfo(slots, idxInSql);
         }
 
         Optional<Expression> expression = bindNestedFields(
                 unboundSlot, slots.get(0), nameParts.subList(3, 
nameParts.size())
         );
         if (!expression.isPresent()) {
-            return slots;
+            return addSqlIndexInfo(slots, idxInSql);
         }
         return ImmutableList.of(expression.get());
     }
 
     private List<? extends Expression> bindExpressionByTableColumn(
             UnboundSlot unboundSlot, List<String> nameParts, 
Optional<Pair<Integer, Integer>> idxInSql, Scope scope) {
-        List<Slot> slots = 
addSqlIndexInfo(bindSingleSlotByTable(nameParts.get(0), nameParts.get(1), 
scope), idxInSql);
+        List<Slot> slots = bindSingleSlotByTable(nameParts.get(0), 
nameParts.get(1), scope);
         if (slots.isEmpty()) {
             return bindExpressionByColumn(unboundSlot, nameParts, idxInSql, 
scope);
         } else if (slots.size() > 1) {
-            return slots;
+            return addSqlIndexInfo(slots, idxInSql);
         }
         if (nameParts.size() == 2) {
-            return slots;
+            return addSqlIndexInfo(slots, idxInSql);
         }
 
         Optional<Expression> expression = bindNestedFields(
                 unboundSlot, slots.get(0), nameParts.subList(2, 
nameParts.size())
         );
         if (!expression.isPresent()) {
-            return slots;
+            return addSqlIndexInfo(slots, idxInSql);
         }
         return ImmutableList.of(expression.get());
     }
 
     private List<? extends Expression> bindExpressionByColumn(
             UnboundSlot unboundSlot, List<String> nameParts, 
Optional<Pair<Integer, Integer>> idxInSql, Scope scope) {
-        List<Slot> slots = 
addSqlIndexInfo(bindSingleSlotByName(nameParts.get(0), scope), idxInSql);
+        List<Slot> slots = bindSingleSlotByName(nameParts.get(0), scope);
         if (slots.size() != 1) {
-            return slots;
+            return addSqlIndexInfo(slots, idxInSql);
         }
         if (nameParts.size() == 1) {
-            return slots;
+            return addSqlIndexInfo(slots, idxInSql);
         }
         Optional<Expression> expression = bindNestedFields(
                 unboundSlot, slots.get(0), nameParts.subList(1, 
nameParts.size())
         );
         if (!expression.isPresent()) {
-            return slots;
+            return addSqlIndexInfo(slots, idxInSql);
         }
         return ImmutableList.of(expression.get());
     }
@@ -1183,9 +1182,26 @@ public class ExpressionAnalyzer extends 
SubExprAnalyzer<ExpressionRewriteContext
             }
             throw new AnalysisException("No such field '" + fieldName + "' in 
'" + lastFieldName + "'");
         }
+        addNestedFieldsSqlIndexInfo(unboundSlot, slot, fieldNames);
         return Optional.of(new Alias(expression, unboundSlot.getName(), 
slot.getQualifier()));
     }
 
+    private void addNestedFieldsSqlIndexInfo(UnboundSlot unboundSlot, Slot 
slot, List<String> fieldNames) {
+        Optional<Pair<Integer, Integer>> indexInSql = 
unboundSlot.getIndexInSqlString();
+        if (!indexInSql.isPresent()) {
+            return;
+        }
+        ConnectContext connectContext = ConnectContext.get();
+        if (connectContext == null || connectContext.getStatementContext() == 
null) {
+            return;
+        }
+        List<String> fullName = new ArrayList<>(slot.getQualifier());
+        fullName.add(slot.getName());
+        fullName.addAll(fieldNames);
+        
connectContext.getStatementContext().addIndexInSqlToString(indexInSql.get(),
+                Utils.qualifiedNameWithBackquote(fullName));
+    }
+
     public static boolean sameTableName(String boundSlot, String unboundSlot, 
int lowerCaseTableNames) {
         if (lowerCaseTableNames == 0) {
             return boundSlot.equals(unboundSlot);
diff --git 
a/regression-test/suites/ddl_p0/create_view_nereids/test_create_view_variant_nested_field.groovy
 
b/regression-test/suites/ddl_p0/create_view_nereids/test_create_view_variant_nested_field.groovy
new file mode 100644
index 00000000000..75cf62cb349
--- /dev/null
+++ 
b/regression-test/suites/ddl_p0/create_view_nereids/test_create_view_variant_nested_field.groovy
@@ -0,0 +1,157 @@
+// 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_create_view_variant_nested_field") {
+    sql "SET enable_nereids_planner=true;"
+    sql "SET enable_fallback_to_original_planner=false;"
+
+    String tableName = "test_create_view_variant_nested_field_events"
+    String videoMetaTableName = 
"test_create_view_variant_nested_field_video_meta"
+    String viewName = "test_create_view_variant_nested_field_view"
+    String cteViewName = "test_create_view_variant_nested_field_cte_view"
+    String bracketViewName = 
"test_create_view_variant_nested_field_bracket_view"
+
+    sql "DROP VIEW IF EXISTS ${viewName}"
+    sql "DROP VIEW IF EXISTS ${cteViewName}"
+    sql "DROP VIEW IF EXISTS ${bracketViewName}"
+    sql "DROP TABLE IF EXISTS ${videoMetaTableName}"
+    sql "DROP TABLE IF EXISTS ${tableName}"
+
+    sql """
+        CREATE TABLE ${tableName} (
+            event_key LARGEINT NOT NULL,
+            event_at DATETIME NOT NULL,
+            event_type VARCHAR(20) NOT NULL,
+            event_value VARIANT<'video_id':largeint, 'duration':bigint>,
+            user_connect_info VARIANT<'user_client':text>
+        )
+        DUPLICATE KEY(event_key)
+        DISTRIBUTED BY HASH(event_key) BUCKETS 1
+        PROPERTIES (
+            "replication_num" = "1",
+            "storage_format" = "V3"
+        )
+    """
+
+    sql """
+        CREATE TABLE ${videoMetaTableName} (
+            fid LARGEINT NOT NULL,
+            effective_duration_s BIGINT NULL
+        )
+        DUPLICATE KEY(fid)
+        DISTRIBUTED BY HASH(fid) BUCKETS 1
+        PROPERTIES (
+            "replication_num" = "1"
+        )
+    """
+
+    sql """
+        INSERT INTO ${tableName} VALUES
+        (1, '2026-04-21 00:00:00', 'watch_time', 
'{"video_id":100,"duration":15000}', '{"user_client":"ios"}')
+    """
+    sql "INSERT INTO ${videoMetaTableName} VALUES (100, 60)"
+
+    sql """
+        CREATE VIEW ${viewName} AS
+        SELECT
+            CAST(e.event_value.video_id AS LARGEINT) AS video_id,
+            TRY_CAST(e.event_value.duration AS INT) AS duration_ms,
+            CAST(e.user_connect_info.user_client AS VARCHAR) AS client
+        FROM ${tableName} e
+    """
+
+    def viewCount = sql """
+        SELECT COUNT(*) FROM ${viewName}
+        WHERE video_id = 100 AND duration_ms > 0 AND client = 'ios'
+    """
+    assertEquals("1", viewCount[0][0].toString())
+
+    def createViewSql = sql "SHOW CREATE VIEW ${viewName}"
+    assertTrue(createViewSql[0][1].contains("`event_value`.`video_id`"))
+    assertTrue(createViewSql[0][1].contains("`event_value`.`duration`"))
+    
assertTrue(createViewSql[0][1].contains("`user_connect_info`.`user_client`"))
+    assertFalse(createViewSql[0][1].contains("`event_value` AS LARGEINT"))
+    assertFalse(createViewSql[0][1].contains("`event_value` AS INT"))
+    assertFalse(createViewSql[0][1].contains("`user_connect_info` AS VARCHAR"))
+
+    sql """
+        CREATE VIEW ${cteViewName} AS
+        WITH extracted AS (
+            SELECT
+                DATE_TRUNC(event_at, 'DAY') AS event_day,
+                CAST(event_value.video_id AS LARGEINT) AS video_id,
+                TRY_CAST(event_value.duration AS INT) AS duration_ms,
+                CAST(user_connect_info.user_client AS VARCHAR) AS client
+            FROM ${tableName}
+            WHERE event_type = 'watch_time'
+        ), final AS (
+            SELECT
+                event_day,
+                video_id,
+                SUM(duration_ms) AS total_duration,
+                MIN_BY(client, event_day) AS client
+            FROM extracted e
+            JOIN ${videoMetaTableName} v ON e.video_id = v.fid
+            WHERE duration_ms > 0 AND client IS NOT NULL
+            GROUP BY 1, 2
+        )
+        SELECT * FROM final
+    """
+
+    def cteViewCount = sql "SELECT COUNT(*) FROM ${cteViewName}"
+    assertEquals("1", cteViewCount[0][0].toString())
+
+    def cteFilteredViewCount = sql """
+        SELECT COUNT(*) FROM ${cteViewName}
+        WHERE event_day >= '2026-04-20'
+    """
+    assertEquals("1", cteFilteredViewCount[0][0].toString())
+
+    sql """
+        ALTER VIEW ${viewName} AS
+        SELECT
+            CAST(e.event_value.video_id AS LARGEINT) AS video_id,
+            TRY_CAST(e.event_value.duration AS INT) AS duration_ms,
+            CAST(e.user_connect_info.user_client AS VARCHAR) AS client
+        FROM ${tableName} e
+    """
+
+    def alterViewCount = sql """
+        SELECT COUNT(*) FROM ${viewName}
+        WHERE video_id = 100 AND duration_ms > 0 AND client = 'ios'
+    """
+    assertEquals("1", alterViewCount[0][0].toString())
+
+    sql """
+        CREATE VIEW ${bracketViewName} AS
+        SELECT CAST(event_value['video_id'] AS LARGEINT) AS video_id
+        FROM ${tableName}
+    """
+
+    def bracketViewCount = sql "SELECT COUNT(*) FROM ${bracketViewName} WHERE 
video_id = 100"
+    assertEquals("1", bracketViewCount[0][0].toString())
+
+    def bracketCreateViewSql = sql "SHOW CREATE VIEW ${bracketViewName}"
+    
assertTrue(bracketCreateViewSql[0][1].contains("`event_value`['video_id']"))
+    
assertFalse(bracketCreateViewSql[0][1].contains("['video_id']['video_id']"))
+
+    sql "DROP VIEW IF EXISTS ${viewName}"
+    sql "DROP VIEW IF EXISTS ${cteViewName}"
+    sql "DROP VIEW IF EXISTS ${bracketViewName}"
+    sql "DROP TABLE IF EXISTS ${videoMetaTableName}"
+    sql "DROP TABLE IF EXISTS ${tableName}"
+}


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to