@@ -28,7 +28,12 @@ use iceberg_rust::spec::namespace::Namespace;
2828use iceberg_rust:: spec:: schema:: Schema ;
2929use iceberg_rust:: spec:: types:: StructType ;
3030use snafu:: ResultExt ;
31- use sqlparser:: ast:: { MergeAction , MergeClauseKind , MergeInsertKind , Query as AstQuery } ;
31+ use sqlparser:: ast:: helpers:: attached_token:: AttachedToken ;
32+ use sqlparser:: ast:: {
33+ BinaryOperator , GroupByExpr , MergeAction , MergeClauseKind , MergeInsertKind , Query as AstQuery ,
34+ Select , SelectItem ,
35+ } ;
36+ use sqlparser:: tokenizer:: Span ;
3237use std:: collections:: hash_map:: Entry ;
3338use std:: collections:: HashMap ;
3439use std:: sync:: Arc ;
@@ -77,11 +82,17 @@ impl SqlExecutor {
7782 | Statement :: StartTransaction { .. }
7883 | Statement :: Commit { .. }
7984 | Statement :: Insert { .. }
80- | Statement :: Query { .. }
8185 | Statement :: ShowSchemas { .. }
8286 | Statement :: ShowVariable { .. } => {
8387 return Box :: pin ( self . execute_with_custom_plan ( & query, warehouse_name) ) . await ;
8488 }
89+ Statement :: Query ( mut subquery) => {
90+ self . update_qualify_in_query ( subquery. as_mut ( ) ) ;
91+ return Box :: pin (
92+ self . execute_with_custom_plan ( & subquery. to_string ( ) , warehouse_name) ,
93+ )
94+ . await ;
95+ }
8596 Statement :: Drop { .. } => {
8697 return Box :: pin ( self . drop_table_query ( & query, warehouse_name) ) . await ;
8798 }
@@ -110,8 +121,9 @@ impl SqlExecutor {
110121 pub fn preprocess_query ( & self , query : & str ) -> String {
111122 // Replace field[0].subfield -> json_get(json_get(field, 0), 'subfield')
112123 // TODO: This regex should be a static allocation
113- let re = regex:: Regex :: new ( r"(\w+)\[(\d+)][:\.](\w+)" ) . unwrap ( ) ;
114- let date_add = regex:: Regex :: new ( r"(date|time|timestamp)(_?add|_?diff)\(\s*([a-zA-Z]+)," ) . unwrap ( ) ;
124+ let re = regex:: Regex :: new ( r"(\w+.\w+)\[(\d+)][:\.](\w+)" ) . unwrap ( ) ;
125+ let date_add =
126+ regex:: Regex :: new ( r"(date|time|timestamp)(_?add|_?diff)\(\s*([a-zA-Z]+)," ) . unwrap ( ) ;
115127
116128 let query = re
117129 . replace_all ( query, "json_get(json_get($1, $2), '$3')" )
@@ -150,13 +162,19 @@ impl SqlExecutor {
150162
151163 // Replace the name of table that needs creation (for ex. "warehouse"."database"."table" -> "table")
152164 // And run the query - this will create an InMemory table
153- let modified_statement = CreateTableStatement {
165+ let mut modified_statement = CreateTableStatement {
154166 name : ObjectName ( vec ! [ new_table_name. clone( ) ] ) ,
155167 transient : false ,
156168 ..create_table_statement
157169 } ;
170+
171+ // Replace qualify with nested select
172+ if let Some ( ref mut query) = modified_statement. query {
173+ self . update_qualify_in_query ( query) ;
174+ }
158175 // Create InMemory table since external tables with "AS SELECT" are not supported
159176 let updated_query = modified_statement. to_string ( ) ;
177+
160178 let plan = self
161179 . get_custom_logical_plan ( & updated_query, warehouse_name)
162180 . await ?;
@@ -481,7 +499,7 @@ impl SqlExecutor {
481499 }
482500 }
483501 }
484- // println!("Tables: {:?}", ctx_provider.tables.keys());
502+
485503 let planner = ExtendedSqlToRel :: new ( & ctx_provider) ;
486504 planner
487505 . sql_statement_to_plan ( * s)
@@ -509,6 +527,97 @@ impl SqlExecutor {
509527 . context ( super :: error:: DataFusionSnafu )
510528 }
511529
530+ #[ allow( clippy:: only_used_in_recursion) ]
531+ fn update_qualify_in_query ( & self , query : & mut Query ) {
532+ if let Some ( with) = query. with . as_mut ( ) {
533+ for cte in & mut with. cte_tables {
534+ self . update_qualify_in_query ( & mut cte. query ) ;
535+ }
536+ }
537+
538+ match query. body . as_mut ( ) {
539+ sqlparser:: ast:: SetExpr :: Select ( select) => {
540+ if let Some ( Expr :: BinaryOp { left, op, right } ) = select. qualify . as_ref ( ) {
541+ if matches ! (
542+ op,
543+ BinaryOperator :: Eq | BinaryOperator :: Lt | BinaryOperator :: LtEq
544+ ) {
545+ let mut inner_select = select. clone ( ) ;
546+ inner_select. qualify = None ;
547+ inner_select. projection . push ( SelectItem :: ExprWithAlias {
548+ expr : * ( left. clone ( ) ) ,
549+ alias : Ident {
550+ value : "qualify_alias" . to_string ( ) ,
551+ quote_style : None ,
552+ span : Span :: empty ( ) ,
553+ } ,
554+ } ) ;
555+ let subquery = Query {
556+ with : None ,
557+ body : Box :: new ( sqlparser:: ast:: SetExpr :: Select ( inner_select) ) ,
558+ order_by : None ,
559+ limit : None ,
560+ limit_by : vec ! [ ] ,
561+ offset : None ,
562+ fetch : None ,
563+ locks : vec ! [ ] ,
564+ for_clause : None ,
565+ settings : None ,
566+ format_clause : None ,
567+ } ;
568+ let outer_select = Select {
569+ select_token : AttachedToken :: empty ( ) ,
570+ distinct : None ,
571+ top : None ,
572+ top_before_distinct : false ,
573+ projection : vec ! [ SelectItem :: UnnamedExpr ( Expr :: Identifier ( Ident {
574+ value: "*" . to_string( ) ,
575+ quote_style: None ,
576+ span: Span :: empty( ) ,
577+ } ) ) ] ,
578+ into : None ,
579+ from : vec ! [ TableWithJoins {
580+ relation: TableFactor :: Derived {
581+ lateral: false ,
582+ subquery: Box :: new( subquery) ,
583+ alias: None ,
584+ } ,
585+ joins: vec![ ] ,
586+ } ] ,
587+ lateral_views : vec ! [ ] ,
588+ prewhere : None ,
589+ selection : Some ( Expr :: BinaryOp {
590+ left : Box :: new ( Expr :: Identifier ( Ident {
591+ value : "qualify_alias" . to_string ( ) ,
592+ quote_style : None ,
593+ span : Span :: empty ( ) ,
594+ } ) ) ,
595+ op : op. clone ( ) ,
596+ right : Box :: new ( * right. clone ( ) ) ,
597+ } ) ,
598+ group_by : GroupByExpr :: Expressions ( vec ! [ ] , vec ! [ ] ) ,
599+ cluster_by : vec ! [ ] ,
600+ distribute_by : vec ! [ ] ,
601+ sort_by : vec ! [ ] ,
602+ having : None ,
603+ named_window : vec ! [ ] ,
604+ qualify : None ,
605+ window_before_qualify : false ,
606+ value_table_mode : None ,
607+ connect_by : None ,
608+ } ;
609+
610+ * query. body = sqlparser:: ast:: SetExpr :: Select ( Box :: new ( outer_select) ) ;
611+ }
612+ }
613+ }
614+ sqlparser:: ast:: SetExpr :: Query ( q) => {
615+ self . update_qualify_in_query ( q) ;
616+ }
617+ _ => { }
618+ }
619+ }
620+
512621 #[ allow( clippy:: only_used_in_recursion) ]
513622 fn get_expr_where_clause ( & self , expr : Expr , target_alias : & str ) -> Vec < String > {
514623 match expr {
0 commit comments