@@ -106,12 +106,11 @@ impl SqlExecutor {
106106 warehouse_name : & str ,
107107 ) -> IceBucketSQLResult < Vec < RecordBatch > > {
108108 // Update query to use custom JSON functions
109- let query = self . preprocess_query ( query) ;
109+ let query = Self :: preprocess_query ( query) ;
110110 let mut statement = self
111111 . parse_query ( query. as_str ( ) )
112112 . context ( super :: error:: DataFusionSnafu ) ?;
113113 Self :: postprocess_query_statement ( & mut statement) ;
114-
115114 // statement = self.update_statement_references(statement, warehouse_name);
116115 // query = statement.to_string();
117116
@@ -244,18 +243,15 @@ impl SqlExecutor {
244243 /// Panics if .
245244 #[ must_use]
246245 #[ allow( clippy:: unwrap_used) ]
247- #[ tracing:: instrument( level = "trace" , skip ( self ) , ret) ]
248- pub fn preprocess_query ( & self , query : & str ) -> String {
246+ #[ tracing:: instrument( level = "trace" , ret) ]
247+ pub fn preprocess_query ( query : & str ) -> String {
249248 // Replace field[0].subfield -> json_get(json_get(field, 0), 'subfield')
250249 // TODO: This regex should be a static allocation
251250 let re = regex:: Regex :: new ( r"(\w+.\w+)\[(\d+)][:\.](\w+)" ) . unwrap ( ) ;
252- let date_add =
253- regex:: Regex :: new ( r"(date|time|timestamp)(_?add|_?diff)\(\s*([a-zA-Z]+)," ) . unwrap ( ) ;
254-
251+ //date_add processing moved to `postprocess_query_statement`
255252 let mut query = re
256253 . replace_all ( query, "json_get(json_get($1, $2), '$3')" )
257254 . to_string ( ) ;
258- query = date_add. replace_all ( & query, "$1$2('$3'," ) . to_string ( ) ;
259255 let alter_iceberg_table = regex:: Regex :: new ( r"alter\s+iceberg\s+table" ) . unwrap ( ) ;
260256 query = alter_iceberg_table
261257 . replace_all ( & query, "alter table" )
@@ -398,6 +394,7 @@ impl SqlExecutor {
398394 . await
399395 . context ( ih_error:: IcebergSnafu ) ?;
400396 } ;
397+
401398 // Create new table
402399 rest_catalog
403400 . create_table (
@@ -1336,8 +1333,144 @@ pub fn created_entity_response() -> Result<Vec<RecordBatch>, arrow::error::Arrow
13361333}
13371334
13381335#[ cfg( test) ]
1339- mod test {
1340- use crate :: datafusion:: execution:: SqlExecutor ;
1336+ mod tests {
1337+ use super :: SqlExecutor ;
1338+ use crate :: datafusion:: { session:: SessionParams , type_planner:: CustomTypePlanner } ;
1339+ use datafusion:: sql:: parser:: Statement as DFStatement ;
1340+ use datafusion:: sql:: sqlparser:: ast:: visit_expressions;
1341+ use datafusion:: sql:: sqlparser:: ast:: { Expr , ObjectName } ;
1342+ use datafusion:: {
1343+ execution:: SessionStateBuilder ,
1344+ prelude:: { SessionConfig , SessionContext } ,
1345+ } ;
1346+ use datafusion_iceberg:: planner:: IcebergQueryPlanner ;
1347+ use sqlparser:: ast:: Value ;
1348+ use sqlparser:: ast:: {
1349+ Function , FunctionArg , FunctionArgExpr , FunctionArgumentList , FunctionArguments ,
1350+ } ;
1351+ use std:: ops:: ControlFlow ;
1352+ use std:: sync:: Arc ;
1353+
1354+ struct Test < ' a , T > {
1355+ input : & ' a str ,
1356+ expected : T ,
1357+ should_work : bool ,
1358+ }
1359+ impl < ' a , T > Test < ' a , T > {
1360+ pub const fn new ( input : & ' a str , expected : T , should_work : bool ) -> Self {
1361+ Self {
1362+ input,
1363+ expected,
1364+ should_work,
1365+ }
1366+ }
1367+ }
1368+ #[ test]
1369+ #[ allow(
1370+ clippy:: unwrap_used,
1371+ clippy:: explicit_iter_loop,
1372+ clippy:: collapsible_match
1373+ ) ]
1374+ fn test_timestamp_keywords_postprocess ( ) {
1375+ let state = SessionStateBuilder :: new ( )
1376+ . with_config (
1377+ SessionConfig :: new ( )
1378+ . with_information_schema ( true )
1379+ . with_option_extension ( SessionParams :: default ( ) )
1380+ . set_str ( "datafusion.sql_parser.dialect" , "SNOWFLAKE" ) ,
1381+ )
1382+ . with_default_features ( )
1383+ . with_query_planner ( Arc :: new ( IcebergQueryPlanner { } ) )
1384+ . with_type_planner ( Arc :: new ( CustomTypePlanner { } ) )
1385+ . build ( ) ;
1386+ let ctx = SessionContext :: new_with_state ( state) ;
1387+ let executor = SqlExecutor :: new ( ctx) . unwrap ( ) ;
1388+ let test = vec ! [
1389+ Test :: new(
1390+ "SELECT dateadd(year, 5, '2025-06-01')" ,
1391+ Value :: SingleQuotedString ( "year" . to_owned( ) ) ,
1392+ true ,
1393+ ) ,
1394+ Test :: new(
1395+ "SELECT dateadd(\" year\" , 5, '2025-06-01')" ,
1396+ Value :: SingleQuotedString ( "year" . to_owned( ) ) ,
1397+ true ,
1398+ ) ,
1399+ Test :: new(
1400+ "SELECT dateadd('year', 5, '2025-06-01')" ,
1401+ Value :: SingleQuotedString ( "year" . to_owned( ) ) ,
1402+ true ,
1403+ ) ,
1404+ Test :: new(
1405+ "SELECT dateadd(\" 'year'\" , 5, '2025-06-01')" ,
1406+ Value :: SingleQuotedString ( "year" . to_owned( ) ) ,
1407+ false ,
1408+ ) ,
1409+ Test :: new(
1410+ "SELECT dateadd(\' year\' , 5, '2025-06-01')" ,
1411+ Value :: SingleQuotedString ( "year" . to_owned( ) ) ,
1412+ true ,
1413+ ) ,
1414+ Test :: new(
1415+ "SELECT datediff(day, 5, '2025-06-01')" ,
1416+ Value :: SingleQuotedString ( "day" . to_owned( ) ) ,
1417+ true ,
1418+ ) ,
1419+ Test :: new(
1420+ "SELECT datediff('week', 5, '2025-06-01')" ,
1421+ Value :: SingleQuotedString ( "week" . to_owned( ) ) ,
1422+ true ,
1423+ ) ,
1424+ Test :: new(
1425+ "SELECT datediff(nsecond, 10000000, '2025-06-01')" ,
1426+ Value :: SingleQuotedString ( "nsecond" . to_owned( ) ) ,
1427+ true ,
1428+ ) ,
1429+ Test :: new(
1430+ "SELECT date_diff(hour, 5, '2025-06-01')" ,
1431+ Value :: SingleQuotedString ( "hour" . to_owned( ) ) ,
1432+ true ,
1433+ ) ,
1434+ Test :: new(
1435+ "SELECT date_add(us, 100000, '2025-06-01')" ,
1436+ Value :: SingleQuotedString ( "us" . to_owned( ) ) ,
1437+ true ,
1438+ ) ,
1439+ ] ;
1440+ for test in test. iter ( ) {
1441+ let mut statement = executor. parse_query ( test. input ) . unwrap ( ) ;
1442+ SqlExecutor :: postprocess_query_statement ( & mut statement) ;
1443+ if let DFStatement :: Statement ( statement) = statement {
1444+ visit_expressions ( & statement, |expr| {
1445+ if let Expr :: Function ( Function {
1446+ name : ObjectName ( idents) ,
1447+ args : FunctionArguments :: List ( FunctionArgumentList { args, .. } ) ,
1448+ ..
1449+ } ) = expr
1450+ {
1451+ match idents. first ( ) . unwrap ( ) . value . as_str ( ) {
1452+ "dateadd" | "date_add" | "datediff" | "date_diff" => {
1453+ if let FunctionArg :: Unnamed ( FunctionArgExpr :: Expr ( ident) ) =
1454+ args. iter ( ) . next ( ) . unwrap ( )
1455+ {
1456+ if let Expr :: Value ( found) = ident {
1457+ if test. should_work {
1458+ assert_eq ! ( * found, test. expected) ;
1459+ } else {
1460+ assert_ne ! ( * found, test. expected) ;
1461+ }
1462+ }
1463+ }
1464+ }
1465+ _ => { }
1466+ }
1467+ }
1468+ ControlFlow :: < ( ) > :: Continue ( ( ) )
1469+ } ) ;
1470+ }
1471+ }
1472+ }
1473+
13411474 use datafusion:: sql:: parser:: DFParser ;
13421475
13431476 #[ allow( clippy:: unwrap_used) ]
0 commit comments