@@ -26,8 +26,8 @@ use sqlparser::ast::{
26
26
} ;
27
27
28
28
use datafusion_common:: {
29
- internal_datafusion_err, internal_err, not_impl_err, plan_err, Column , DFSchema ,
30
- Result , ScalarValue ,
29
+ internal_datafusion_err, internal_err, not_impl_err, plan_err, DFSchema , Result ,
30
+ ScalarValue ,
31
31
} ;
32
32
33
33
use datafusion_expr:: expr:: ScalarFunction ;
@@ -983,14 +983,77 @@ impl<S: ContextProvider> SqlToRel<'_, S> {
983
983
Ok ( Expr :: Cast ( Cast :: new ( Box :: new ( expr) , dt) ) )
984
984
}
985
985
986
+ /// Extracts the root expression and access chain from a compound expression.
987
+ ///
988
+ /// This function attempts to identify if a compound expression (like `a.b.c`) should be treated
989
+ /// as a column reference with a qualifier (like `table.column`) or as a field access expression.
990
+ ///
991
+ /// # Arguments
992
+ ///
993
+ /// * `root` - The root SQL expression (e.g., the first part of `a.b.c`)
994
+ /// * `access_chain` - Vector of access expressions (e.g., `.b` and `.c` parts)
995
+ /// * `schema` - The schema to resolve column references against
996
+ /// * `planner_context` - Context for planning expressions
997
+ ///
998
+ /// # Returns
999
+ ///
1000
+ /// A tuple containing:
1001
+ /// * The resolved root expression
1002
+ /// * The remaining access chain that should be processed as field accesses
1003
+ fn extract_root_and_access_chain (
1004
+ & self ,
1005
+ root : SQLExpr ,
1006
+ mut access_chain : Vec < AccessExpr > ,
1007
+ schema : & DFSchema ,
1008
+ planner_context : & mut PlannerContext ,
1009
+ ) -> Result < ( Expr , Vec < AccessExpr > ) > {
1010
+ let SQLExpr :: Identifier ( root_ident) = root else {
1011
+ let root = self . sql_expr_to_logical_expr ( root, schema, planner_context) ?;
1012
+ return Ok ( ( root, access_chain) ) ;
1013
+ } ;
1014
+
1015
+ let mut compound_idents = vec ! [ root_ident] ;
1016
+ let first_non_ident = access_chain
1017
+ . iter ( )
1018
+ . position ( |access| !matches ! ( access, AccessExpr :: Dot ( SQLExpr :: Identifier ( _) ) ) )
1019
+ . unwrap_or ( access_chain. len ( ) ) ;
1020
+ for access in access_chain. drain ( 0 ..first_non_ident) {
1021
+ if let AccessExpr :: Dot ( SQLExpr :: Identifier ( ident) ) = access {
1022
+ compound_idents. push ( ident) ;
1023
+ } else {
1024
+ return internal_err ! ( "Expected identifier in access chain" ) ;
1025
+ }
1026
+ }
1027
+
1028
+ let root = if compound_idents. len ( ) == 1 {
1029
+ self . sql_identifier_to_expr (
1030
+ compound_idents. pop ( ) . unwrap ( ) ,
1031
+ schema,
1032
+ planner_context,
1033
+ ) ?
1034
+ } else {
1035
+ self . sql_compound_identifier_to_expr (
1036
+ compound_idents,
1037
+ schema,
1038
+ planner_context,
1039
+ ) ?
1040
+ } ;
1041
+ Ok ( ( root, access_chain) )
1042
+ }
1043
+
986
1044
fn sql_compound_field_access_to_expr (
987
1045
& self ,
988
1046
root : SQLExpr ,
989
1047
access_chain : Vec < AccessExpr > ,
990
1048
schema : & DFSchema ,
991
1049
planner_context : & mut PlannerContext ,
992
1050
) -> Result < Expr > {
993
- let mut root = self . sql_expr_to_logical_expr ( root, schema, planner_context) ?;
1051
+ let ( root, access_chain) = self . extract_root_and_access_chain (
1052
+ root,
1053
+ access_chain,
1054
+ schema,
1055
+ planner_context,
1056
+ ) ?;
994
1057
let fields = access_chain
995
1058
. into_iter ( )
996
1059
. map ( |field| match field {
@@ -1064,45 +1127,18 @@ impl<S: ContextProvider> SqlToRel<'_, S> {
1064
1127
}
1065
1128
}
1066
1129
}
1067
- AccessExpr :: Dot ( expr) => {
1068
- let expr =
1069
- self . sql_expr_to_logical_expr ( expr, schema, planner_context) ?;
1070
- match expr {
1071
- Expr :: Column ( Column {
1072
- name,
1073
- relation,
1074
- spans,
1075
- } ) => {
1076
- if let Some ( relation) = & relation {
1077
- // If the first part of the dot access is a column reference, we should
1078
- // check if the column is from the same table as the root expression.
1079
- // If it is, we should replace the root expression with the column reference.
1080
- // Otherwise, we should treat the dot access as a named field access.
1081
- if relation. table ( ) == root. schema_name ( ) . to_string ( ) {
1082
- root = Expr :: Column ( Column {
1083
- name,
1084
- relation : Some ( relation. clone ( ) ) ,
1085
- spans,
1086
- } ) ;
1087
- Ok ( None )
1088
- } else {
1089
- plan_err ! (
1090
- "table name mismatch: {} != {}" ,
1091
- relation. table( ) ,
1092
- root. schema_name( )
1093
- )
1094
- }
1095
- } else {
1096
- Ok ( Some ( GetFieldAccess :: NamedStructField {
1097
- name : ScalarValue :: from ( name) ,
1098
- } ) )
1099
- }
1100
- }
1101
- _ => not_impl_err ! (
1102
- "Dot access not supported for non-column expr: {expr:?}"
1103
- ) ,
1130
+ AccessExpr :: Dot ( expr) => match expr {
1131
+ SQLExpr :: Value (
1132
+ Value :: SingleQuotedString ( s) | Value :: DoubleQuotedString ( s) ,
1133
+ ) => Ok ( Some ( GetFieldAccess :: NamedStructField {
1134
+ name : ScalarValue :: from ( s) ,
1135
+ } ) ) ,
1136
+ _ => {
1137
+ not_impl_err ! (
1138
+ "Dot access not supported for non-string expr: {expr:?}"
1139
+ )
1104
1140
}
1105
- }
1141
+ } ,
1106
1142
} )
1107
1143
. collect :: < Result < Vec < _ > > > ( ) ?;
1108
1144
0 commit comments