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

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


The following commit(s) were added to refs/heads/branch-4.0 by this push:
     new 9546f5c7cf8 branch-4.0: [feature](nereids) Support dereference 
expression #57532 (#58546)
9546f5c7cf8 is described below

commit 9546f5c7cf82c33b27a1a2ba4bc60eb3449e201c
Author: 924060929 <[email protected]>
AuthorDate: Tue Dec 2 10:03:55 2025 +0800

    branch-4.0: [feature](nereids) Support dereference expression #57532 
(#58546)
    
    picked from #57532
---
 .../doris/nereids/parser/LogicalPlanBuilder.java   |   4 +-
 .../nereids/rules/analysis/BindExpression.java     |  22 +--
 .../nereids/rules/analysis/ExpressionAnalyzer.java | 156 +++++++++++++++++++--
 .../trees/expressions/DereferenceExpression.java   |  41 ++++++
 .../expressions/visitor/ExpressionVisitor.java     |   5 +
 .../org/apache/doris/nereids/types/StructType.java |   4 +
 .../nereids/rules/analysis/TestDereference.java    |  86 ++++++++++++
 .../suites/query_p0/test_dereference.groovy        |  69 +++++++++
 8 files changed, 360 insertions(+), 27 deletions(-)

diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/parser/LogicalPlanBuilder.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/parser/LogicalPlanBuilder.java
index 7d08db6595e..72b4317021c 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/parser/LogicalPlanBuilder.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/parser/LogicalPlanBuilder.java
@@ -522,6 +522,7 @@ import org.apache.doris.nereids.trees.expressions.BitXor;
 import org.apache.doris.nereids.trees.expressions.CaseWhen;
 import org.apache.doris.nereids.trees.expressions.Cast;
 import org.apache.doris.nereids.trees.expressions.DefaultValueSlot;
+import org.apache.doris.nereids.trees.expressions.DereferenceExpression;
 import org.apache.doris.nereids.trees.expressions.Divide;
 import org.apache.doris.nereids.trees.expressions.EqualTo;
 import org.apache.doris.nereids.trees.expressions.Exists;
@@ -3418,8 +3419,7 @@ public class LogicalPlanBuilder extends 
DorisParserBaseVisitor<Object> {
                 UnboundSlot slot = new UnboundSlot(nameParts, 
Optional.empty());
                 return slot;
             } else {
-                // todo: base is an expression, may be not a table name.
-                throw new ParseException("Unsupported dereference expression: 
" + ctx.getText(), ctx);
+                return new DereferenceExpression(e, new 
StringLiteral(ctx.identifier().getText()));
             }
         });
     }
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/analysis/BindExpression.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/analysis/BindExpression.java
index b3adae21841..bf082635c8d 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/analysis/BindExpression.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/analysis/BindExpression.java
@@ -469,12 +469,12 @@ public class BindExpression implements 
AnalysisRuleFactory {
             Scope groupBySlotsScope = toScope(cascadesContext, 
groupBySlots.build());
 
             return (analyzer, unboundSlot) -> {
-                List<Slot> boundInGroupBy = 
analyzer.bindSlotByScope(unboundSlot, groupBySlotsScope);
+                List<Expression> boundInGroupBy = 
analyzer.bindSlotByScope(unboundSlot, groupBySlotsScope);
                 if (!boundInGroupBy.isEmpty()) {
                     return ImmutableList.of(boundInGroupBy.get(0));
                 }
 
-                List<Slot> boundInAggOutput = 
analyzer.bindSlotByScope(unboundSlot, aggOutputScope);
+                List<Expression> boundInAggOutput = 
analyzer.bindSlotByScope(unboundSlot, aggOutputScope);
                 if (!boundInAggOutput.isEmpty()) {
                     return ImmutableList.of(boundInAggOutput.get(0));
                 }
@@ -553,7 +553,7 @@ public class BindExpression implements AnalysisRuleFactory {
         SimpleExprAnalyzer analyzer = buildCustomSlotBinderAnalyzer(
                 having, cascadesContext, defaultScope, false, true,
                 (self, unboundSlot) -> {
-                    List<Slot> slots = self.bindSlotByScope(unboundSlot, 
defaultScope);
+                    List<Expression> slots = self.bindSlotByScope(unboundSlot, 
defaultScope);
                     if (!slots.isEmpty()) {
                         return slots;
                     }
@@ -1006,7 +1006,7 @@ public class BindExpression implements 
AnalysisRuleFactory {
         SimpleExprAnalyzer analyzer = buildCustomSlotBinderAnalyzer(
                 qualify, cascadesContext, defaultScope.get(), true, true,
                 (self, unboundSlot) -> {
-                List<Slot> slots = self.bindSlotByScope(unboundSlot, 
defaultScope.get());
+                List<Expression> slots = self.bindSlotByScope(unboundSlot, 
defaultScope.get());
                 if (!slots.isEmpty()) {
                     return slots;
                 }
@@ -1044,11 +1044,11 @@ public class BindExpression implements 
AnalysisRuleFactory {
             Scope groupBySlotsScope = toScope(cascadesContext, 
groupBySlots.build());
 
             return (analyzer, unboundSlot) -> {
-                List<Slot> boundInGroupBy = 
analyzer.bindSlotByScope(unboundSlot, groupBySlotsScope);
+                List<Expression> boundInGroupBy = 
analyzer.bindSlotByScope(unboundSlot, groupBySlotsScope);
                 if (!boundInGroupBy.isEmpty()) {
                     return ImmutableList.of(boundInGroupBy.get(0));
                 }
-                List<Slot> boundInAggOutput = 
analyzer.bindSlotByScope(unboundSlot, aggOutputScope);
+                List<Expression> boundInAggOutput = 
analyzer.bindSlotByScope(unboundSlot, aggOutputScope);
                 if (!boundInAggOutput.isEmpty()) {
                     return ImmutableList.of(boundInAggOutput.get(0));
                 }
@@ -1368,7 +1368,7 @@ public class BindExpression implements 
AnalysisRuleFactory {
                     // see: https://github.com/apache/doris/pull/15240
                     //
                     // first, try to bind by agg.child.output
-                    List<Slot> slotsInChildren = 
self.bindExactSlotsByThisScope(unboundSlot, childOutputScope);
+                    List<Expression> slotsInChildren = 
self.bindExactSlotsByThisScope(unboundSlot, childOutputScope);
                     if (slotsInChildren.size() == 1) {
                         // bind succeed
                         return slotsInChildren;
@@ -1376,7 +1376,7 @@ public class BindExpression implements 
AnalysisRuleFactory {
                     // second, bind failed:
                     // if the slot not found, or more than one candidate slots 
found in agg.child.output,
                     // then try to bind by agg.output
-                    List<Slot> slotsInOutput = self.bindExactSlotsByThisScope(
+                    List<Expression> slotsInOutput = 
self.bindExactSlotsByThisScope(
                             unboundSlot, aggOutputScopeWithoutAggFun.get());
                     if (slotsInOutput.isEmpty()) {
                         // if slotsInChildren.size() > 1 && 
slotsInOutput.isEmpty(),
@@ -1385,7 +1385,7 @@ public class BindExpression implements 
AnalysisRuleFactory {
                     }
 
                     Builder<Expression> useOutputExpr = 
ImmutableList.builderWithExpectedSize(slotsInOutput.size());
-                    for (Slot slotInOutput : slotsInOutput) {
+                    for (Expression slotInOutput : slotsInOutput) {
                         // mappingSlot is provided by 
aggOutputScopeWithoutAggFun
                         // and no non-MappingSlot slot exist in the Scope, so 
we
                         // can direct cast it safely
@@ -1476,7 +1476,7 @@ public class BindExpression implements 
AnalysisRuleFactory {
                 sort, cascadesContext, inputScope, true, false,
                 (self, unboundSlot) -> {
                     // first, try to bind slot in Scope(input.output)
-                    List<Slot> slotsInInput = 
self.bindExactSlotsByThisScope(unboundSlot, inputScope);
+                    List<Expression> slotsInInput = 
self.bindExactSlotsByThisScope(unboundSlot, inputScope);
                     if (!slotsInInput.isEmpty()) {
                         // bind succeed
                         return ImmutableList.of(slotsInInput.get(0));
@@ -1678,7 +1678,7 @@ public class BindExpression implements 
AnalysisRuleFactory {
                 sort, cascadesContext, inputScope, true, false,
                 (analyzer, unboundSlot) -> {
                     if (finalInput instanceof LogicalAggregate) {
-                        List<Slot> boundInOutputWithoutAggFunc = 
analyzer.bindSlotByScope(unboundSlot,
+                        List<Expression> boundInOutputWithoutAggFunc = 
analyzer.bindSlotByScope(unboundSlot,
                                 outputWithoutAggFunc);
                         if (!boundInOutputWithoutAggFunc.isEmpty()) {
                             return 
ImmutableList.of(boundInOutputWithoutAggFunc.get(0));
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 bb1b844520e..e631ffe1c54 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
@@ -49,6 +49,7 @@ import org.apache.doris.nereids.trees.expressions.BoundStar;
 import org.apache.doris.nereids.trees.expressions.CaseWhen;
 import org.apache.doris.nereids.trees.expressions.Cast;
 import org.apache.doris.nereids.trees.expressions.ComparisonPredicate;
+import org.apache.doris.nereids.trees.expressions.DereferenceExpression;
 import org.apache.doris.nereids.trees.expressions.Divide;
 import org.apache.doris.nereids.trees.expressions.EqualTo;
 import org.apache.doris.nereids.trees.expressions.ExprId;
@@ -74,7 +75,9 @@ import 
org.apache.doris.nereids.trees.expressions.functions.RewriteWhenAnalyze;
 import 
org.apache.doris.nereids.trees.expressions.functions.agg.AggregateFunction;
 import 
org.apache.doris.nereids.trees.expressions.functions.agg.NullableAggregateFunction;
 import 
org.apache.doris.nereids.trees.expressions.functions.agg.SupportMultiDistinct;
+import org.apache.doris.nereids.trees.expressions.functions.scalar.ElementAt;
 import org.apache.doris.nereids.trees.expressions.functions.scalar.Lambda;
+import 
org.apache.doris.nereids.trees.expressions.functions.scalar.StructElement;
 import 
org.apache.doris.nereids.trees.expressions.functions.udf.AliasUdfBuilder;
 import org.apache.doris.nereids.trees.expressions.functions.udf.JavaUdaf;
 import org.apache.doris.nereids.trees.expressions.functions.udf.JavaUdf;
@@ -93,6 +96,8 @@ import org.apache.doris.nereids.types.BigIntType;
 import org.apache.doris.nereids.types.BooleanType;
 import org.apache.doris.nereids.types.DataType;
 import org.apache.doris.nereids.types.StringType;
+import org.apache.doris.nereids.types.StructField;
+import org.apache.doris.nereids.types.StructType;
 import org.apache.doris.nereids.types.TinyIntType;
 import org.apache.doris.nereids.util.ExpressionUtils;
 import org.apache.doris.nereids.util.TypeCoercionUtils;
@@ -240,6 +245,25 @@ public class ExpressionAnalyzer extends 
SubExprAnalyzer<ExpressionRewriteContext
         }
     }
 
+    @Override
+    public Expression visitDereferenceExpression(DereferenceExpression 
dereferenceExpression,
+            ExpressionRewriteContext context) {
+        Expression expression = dereferenceExpression.child(0).accept(this, 
context);
+        DataType dataType = expression.getDataType();
+        if (dataType.isStructType()) {
+            StructType structType = (StructType) dataType;
+            StructField field = 
structType.getField(dereferenceExpression.fieldName);
+            if (field != null) {
+                return new StructElement(expression, 
dereferenceExpression.child(1));
+            }
+        } else if (dataType.isMapType()) {
+            return new ElementAt(expression, dereferenceExpression.child(1));
+        } else if (dataType.isVariantType()) {
+            return new ElementAt(expression, dereferenceExpression.child(1));
+        }
+        throw new AnalysisException("Can not dereference field: " + 
dereferenceExpression.fieldName);
+    }
+
     @Override
     public Expression visitUnboundSlot(UnboundSlot unboundSlot, 
ExpressionRewriteContext context) {
         Optional<Scope> outerScope = getScope().getOuterScope();
@@ -913,13 +937,13 @@ public class ExpressionAnalyzer extends 
SubExprAnalyzer<ExpressionRewriteContext
         return bindSlotByScope(unboundSlot, getScope());
     }
 
-    protected List<Slot> bindExactSlotsByThisScope(UnboundSlot unboundSlot, 
Scope scope) {
-        List<Slot> candidates = bindSlotByScope(unboundSlot, scope);
+    protected List<Expression> bindExactSlotsByThisScope(UnboundSlot 
unboundSlot, Scope scope) {
+        List<Expression> candidates = bindSlotByScope(unboundSlot, scope);
         if (candidates.size() == 1) {
             return candidates;
         }
-        List<Slot> extractSlots = Utils.filterImmutableList(candidates, bound 
->
-                unboundSlot.getNameParts().size() == 
bound.getQualifier().size() + 1
+        List<Expression> extractSlots = Utils.filterImmutableList(candidates, 
bound ->
+                bound instanceof Slot && unboundSlot.getNameParts().size() == 
((Slot) bound).getQualifier().size() + 1
         );
         // we should return origin candidates slots if extract slots is empty,
         // and then throw an ambiguous exception
@@ -938,33 +962,137 @@ public class ExpressionAnalyzer extends 
SubExprAnalyzer<ExpressionRewriteContext
     }
 
     /** bindSlotByScope */
-    public List<Slot> bindSlotByScope(UnboundSlot unboundSlot, Scope scope) {
+    public List<Expression> bindSlotByScope(UnboundSlot unboundSlot, Scope 
scope) {
         List<String> nameParts = unboundSlot.getNameParts();
         Optional<Pair<Integer, Integer>> idxInSql = 
unboundSlot.getIndexInSqlString();
         int namePartSize = nameParts.size();
         switch (namePartSize) {
             // column
             case 1: {
-                return addSqlIndexInfo(bindSingleSlotByName(nameParts.get(0), 
scope), idxInSql);
+                return (List<Expression>) bindExpressionByColumn(unboundSlot, 
nameParts, idxInSql, scope);
             }
             // table.column
             case 2: {
-                return addSqlIndexInfo(bindSingleSlotByTable(nameParts.get(0), 
nameParts.get(1), scope), idxInSql);
+                return (List<Expression>) 
bindExpressionByTableColumn(unboundSlot, nameParts, idxInSql, scope);
             }
             // db.table.column
             case 3: {
-                return addSqlIndexInfo(bindSingleSlotByDb(nameParts.get(0), 
nameParts.get(1), nameParts.get(2), scope),
-                        idxInSql);
+                return (List<Expression>) 
bindExpressionByDbTableColumn(unboundSlot, nameParts, idxInSql, scope);
             }
             // catalog.db.table.column
-            case 4: {
-                return addSqlIndexInfo(bindSingleSlotByCatalog(
-                        nameParts.get(0), nameParts.get(1), nameParts.get(2), 
nameParts.get(3), scope), idxInSql);
-            }
             default: {
-                throw new AnalysisException("Not supported name: " + 
StringUtils.join(nameParts, "."));
+                return (List<Expression>) 
bindExpressionByCatalogDbTableColumn(unboundSlot, nameParts, idxInSql, scope);
+            }
+        }
+    }
+
+    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);
+        if (slots.isEmpty()) {
+            return bindExpressionByDbTableColumn(unboundSlot, nameParts, 
idxInSql, scope);
+        } else if (slots.size() > 1) {
+            return slots;
+        }
+        if (nameParts.size() == 4) {
+            return slots;
+        }
+
+        Optional<Expression> expression = bindNestedFields(
+                unboundSlot, slots.get(0), nameParts.subList(4, 
nameParts.size())
+        );
+        if (!expression.isPresent()) {
+            return slots;
+        }
+        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);
+        if (slots.isEmpty()) {
+            return bindExpressionByTableColumn(unboundSlot, nameParts, 
idxInSql, scope);
+        } else if (slots.size() > 1) {
+            return slots;
+        }
+        if (nameParts.size() == 3) {
+            return slots;
+        }
+
+        Optional<Expression> expression = bindNestedFields(
+                unboundSlot, slots.get(0), nameParts.subList(3, 
nameParts.size())
+        );
+        if (!expression.isPresent()) {
+            return slots;
+        }
+        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);
+        if (slots.isEmpty()) {
+            return bindExpressionByColumn(unboundSlot, nameParts, idxInSql, 
scope);
+        } else if (slots.size() > 1) {
+            return slots;
+        }
+        if (nameParts.size() == 2) {
+            return slots;
+        }
+
+        Optional<Expression> expression = bindNestedFields(
+                unboundSlot, slots.get(0), nameParts.subList(2, 
nameParts.size())
+        );
+        if (!expression.isPresent()) {
+            return slots;
+        }
+        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);
+        if (slots.size() != 1) {
+            return slots;
+        }
+        if (nameParts.size() == 1) {
+            return slots;
+        }
+        Optional<Expression> expression = bindNestedFields(
+                unboundSlot, slots.get(0), nameParts.subList(1, 
nameParts.size())
+        );
+        if (!expression.isPresent()) {
+            return slots;
+        }
+        return ImmutableList.of(expression.get());
+    }
+
+    private Optional<Expression> bindNestedFields(UnboundSlot unboundSlot, 
Slot slot, List<String> fieldNames) {
+        Expression expression = slot;
+        String lastFieldName = slot.getName();
+        for (String fieldName : fieldNames) {
+            DataType dataType = expression.getDataType();
+            if (dataType.isStructType()) {
+                StructType structType = (StructType) dataType;
+                StructField field = structType.getField(fieldName);
+                if (field == null) {
+                    throw new AnalysisException("No such struct field '" + 
fieldName + "' in '" + lastFieldName + "'");
+                }
+                lastFieldName = fieldName;
+                expression = new StructElement(expression, new 
StringLiteral(fieldName));
+                continue;
+            } else if (dataType.isMapType()) {
+                expression = new ElementAt(expression, new 
StringLiteral(fieldName));
+                continue;
+            } else if (dataType.isVariantType()) {
+                expression = new ElementAt(expression, new 
StringLiteral(fieldName));
+                continue;
             }
+            throw new AnalysisException("No such field '" + fieldName + "' in 
'" + lastFieldName + "'");
         }
+        return Optional.of(new Alias(expression));
     }
 
     public static boolean sameTableName(String boundSlot, String unboundSlot) {
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/DereferenceExpression.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/DereferenceExpression.java
new file mode 100644
index 00000000000..41f6048cf30
--- /dev/null
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/DereferenceExpression.java
@@ -0,0 +1,41 @@
+// 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.
+
+package org.apache.doris.nereids.trees.expressions;
+
+import org.apache.doris.nereids.analyzer.Unbound;
+import org.apache.doris.nereids.trees.expressions.functions.PropagateNullable;
+import org.apache.doris.nereids.trees.expressions.literal.StringLiteral;
+import org.apache.doris.nereids.trees.expressions.shape.BinaryExpression;
+import org.apache.doris.nereids.trees.expressions.visitor.ExpressionVisitor;
+
+import com.google.common.collect.ImmutableList;
+
+/** DereferenceExpression*/
+public class DereferenceExpression extends Expression implements 
BinaryExpression, PropagateNullable, Unbound {
+    public final String fieldName;
+
+    public DereferenceExpression(Expression expression, StringLiteral 
fieldName) {
+        super(ImmutableList.of(expression, fieldName));
+        this.fieldName = fieldName.getValue();
+    }
+
+    @Override
+    public <R, C> R accept(ExpressionVisitor<R, C> visitor, C context) {
+        return visitor.visitDereferenceExpression(this, context);
+    }
+}
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/visitor/ExpressionVisitor.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/visitor/ExpressionVisitor.java
index 1b508a9aaa6..981e0e964ce 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/visitor/ExpressionVisitor.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/visitor/ExpressionVisitor.java
@@ -41,6 +41,7 @@ import org.apache.doris.nereids.trees.expressions.Cast;
 import org.apache.doris.nereids.trees.expressions.ComparisonPredicate;
 import org.apache.doris.nereids.trees.expressions.CompoundPredicate;
 import org.apache.doris.nereids.trees.expressions.DefaultValueSlot;
+import org.apache.doris.nereids.trees.expressions.DereferenceExpression;
 import org.apache.doris.nereids.trees.expressions.Divide;
 import org.apache.doris.nereids.trees.expressions.EqualTo;
 import org.apache.doris.nereids.trees.expressions.Exists;
@@ -557,4 +558,8 @@ public abstract class ExpressionVisitor<R, C>
     public R visitUnboundVariable(UnboundVariable unboundVariable, C context) {
         return visit(unboundVariable, context);
     }
+
+    public R visitDereferenceExpression(DereferenceExpression 
dereferenceExpression, C context) {
+        return visit(dereferenceExpression, context);
+    }
 }
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/types/StructType.java 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/types/StructType.java
index ffbff7c61e1..fb8557923aa 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/nereids/types/StructType.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/types/StructType.java
@@ -73,6 +73,10 @@ public class StructType extends DataType implements 
ComplexDataType {
         return nameToFields;
     }
 
+    public StructField getField(String name) {
+        return nameToFields.get(name.toLowerCase());
+    }
+
     @Override
     public DataType conversion() {
         return new 
StructType(fields.stream().map(StructField::conversion).collect(Collectors.toList()));
diff --git 
a/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/analysis/TestDereference.java
 
b/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/analysis/TestDereference.java
new file mode 100644
index 00000000000..4731a4c988b
--- /dev/null
+++ 
b/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/analysis/TestDereference.java
@@ -0,0 +1,86 @@
+// 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.
+
+package org.apache.doris.nereids.rules.analysis;
+
+import org.apache.doris.catalog.Column;
+import org.apache.doris.catalog.PrimitiveType;
+import org.apache.doris.catalog.VariantType;
+import org.apache.doris.common.FeConstants;
+import 
org.apache.doris.datasource.test.TestExternalCatalog.TestCatalogProvider;
+import org.apache.doris.nereids.util.PlanChecker;
+import org.apache.doris.utframe.TestWithFeService;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import org.junit.jupiter.api.Test;
+
+import java.util.List;
+import java.util.Map;
+
+public class TestDereference extends TestWithFeService {
+
+    private static final Map<String, Map<String, List<Column>>> CATALOG_META = 
ImmutableMap.of(
+            "t", ImmutableMap.of(
+                    "t", ImmutableList.of(
+                            new Column("id", PrimitiveType.INT),
+                            new Column("t", new VariantType())
+                    )
+            )
+    );
+
+    @Override
+    protected void runBeforeAll() throws Exception {
+        FeConstants.runningUnitTest = true;
+        createCatalog("create catalog t properties("
+                + " \"type\"=\"test\","
+                + " 
\"catalog_provider.class\"=\"org.apache.doris.nereids.rules.analysis.TestDereference$CustomCatalogProvider\""
+                + ")");
+        connectContext.changeDefaultCatalog("t");
+        useDatabase("t");
+    }
+
+    @Test
+    public void testBindPriority() {
+        // column
+        testBind("select t from t");
+        // table.column
+        testBind("select t.t from t");
+        // db.table.column
+        testBind("select t.t.t from t");
+        // catalog.db.table.column
+        testBind("select t.t.t.t from t");
+        // catalog.db.table.column.subColumn
+        testBind("select t.t.t.t.t from t");
+        // catalog.db.table.column.subColumn.subColumn2
+        testBind("select t.t.t.t.t.t from t");
+    }
+
+    private void testBind(String sql) {
+        PlanChecker.from(connectContext)
+                .analyze(sql)
+                .rewrite();
+    }
+
+    public static class CustomCatalogProvider implements TestCatalogProvider {
+
+        @Override
+        public Map<String, Map<String, List<Column>>> getMetadata() {
+            return CATALOG_META;
+        }
+    }
+}
diff --git a/regression-test/suites/query_p0/test_dereference.groovy 
b/regression-test/suites/query_p0/test_dereference.groovy
new file mode 100644
index 00000000000..30c123e3c37
--- /dev/null
+++ b/regression-test/suites/query_p0/test_dereference.groovy
@@ -0,0 +1,69 @@
+// 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 com.google.common.collect.Lists
+
+suite("test_dereference") {
+    multi_sql """
+        drop table if exists test_dereference;
+        create table test_dereference(
+          id int,
+          a array<int>,
+          m map<string, int>,
+          s struct<a: int, b: double>,
+          v variant
+        )
+        distributed by hash(id)
+        properties(
+          'replication_num'='1'
+        );
+        
+        insert into test_dereference
+        values (1, array(1, 2, 3, 4, 5), map('a', 1, 'b', 2, 'c', 3), 
struct(1, 2), '{"v": {"v":200}}')
+        """
+
+    test {
+        sql "select cardinality(a), map_size(m), map_keys(m), map_values(m), 
m.a, m.b, m.c, s.a, s.b, v.v.v from test_dereference"
+        result([[5L, 3L, '["a", "b", "c"]', '[1, 2, 3]', 1, 2, 3, 1, 2d, 
"200"]])
+    }
+
+    multi_sql """
+        drop table if exists test_dereference2;
+        create table test_dereference2(
+          id int,
+          s struct<s:struct<s:struct<s:int>>>,
+          v variant
+        )
+        distributed by hash(id)
+        properties(
+          'replication_num'='1'
+        );
+        
+        insert into test_dereference2
+        values (1, struct(struct(struct(100))), '{"v": {"v": 200}}')
+        """
+
+    test {
+        sql "select s.s.s.s, v.v.v from test_dereference2"
+        result([[100, "200"]])
+    }
+
+    test {
+        sql "select s.a from test_dereference2"
+        exception "No such struct field 'a' in 's'"
+    }
+}
\ No newline at end of file


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

Reply via email to