diff --git a/rust/cubesql/cubesql/src/compile/mod.rs b/rust/cubesql/cubesql/src/compile/mod.rs index 95f14b8a098f1..a15607dc004c1 100644 --- a/rust/cubesql/cubesql/src/compile/mod.rs +++ b/rust/cubesql/cubesql/src/compile/mod.rs @@ -16875,4 +16875,71 @@ LIMIT {{ limit }}{% endif %}"#.to_string(), } ) } + + #[tokio::test] + async fn test_tableau_relative_dates() { + init_testing_logger(); + + let logical_plan = convert_select_to_query_plan( + r#" + SELECT + CAST("KibanaSampleDataEcommerce"."customer_gender" AS TEXT) AS "customer_gendder", + SUM("KibanaSampleDataEcommerce"."sumPrice") AS "sum:sumPrice:ok" + FROM + "public"."KibanaSampleDataEcommerce" "KibanaSampleDataEcommerce" + WHERE + ( + CASE + WHEN ( + NOT ( + CAST( + CAST( + TO_TIMESTAMP( + CAST( + CAST("KibanaSampleDataEcommerce"."order_date" AS TEXT) AS TEXT + ), + 'YYYY-MM-DD"T"HH24:MI:SS.MS' + ) AS TIMESTAMP + ) AS DATE + ) IS NULL + ) + ) THEN CAST( + CAST( + TO_TIMESTAMP( + CAST( + CAST("KibanaSampleDataEcommerce"."order_date" AS TEXT) AS TEXT + ), + 'YYYY-MM-DD"T"HH24:MI:SS.MS' + ) AS TIMESTAMP + ) AS DATE + ) + ELSE NULL + END + ) < (TIMESTAMP '2025-01-01 00:00:00.000') + GROUP BY + 1 + "# + .to_string(), + DatabaseProtocol::PostgreSQL, + ) + .await + .as_logical_plan(); + + assert_eq!( + logical_plan.find_cube_scan().request, + V1LoadRequestQuery { + measures: Some(vec!["KibanaSampleDataEcommerce.sumPrice".to_string()]), + dimensions: Some(vec!["KibanaSampleDataEcommerce.customer_gender".to_string()]), + segments: Some(vec![]), + order: Some(vec![]), + filters: Some(vec![V1LoadRequestQueryFilterItem { + member: Some("KibanaSampleDataEcommerce.order_date".to_string()), + operator: Some("beforeDate".to_string()), + values: Some(vec!["2025-01-01T00:00:00.000Z".to_string()]), + ..Default::default() + }]), + ..Default::default() + } + ) + } } diff --git a/rust/cubesql/cubesql/src/compile/rewrite/rules/filters.rs b/rust/cubesql/cubesql/src/compile/rewrite/rules/filters.rs index a9c425c1fa3d5..c1cb83075fce6 100644 --- a/rust/cubesql/cubesql/src/compile/rewrite/rules/filters.rs +++ b/rust/cubesql/cubesql/src/compile/rewrite/rules/filters.rs @@ -3,14 +3,14 @@ use crate::{ compile::rewrite::{ alias_expr, analysis::{ConstantFolding, Member, OriginalExpr}, - between_expr, binary_expr, case_expr, case_expr_var_arg, cast_expr, change_user_member, - column_expr, cube_scan, cube_scan_filters, cube_scan_filters_empty_tail, cube_scan_members, - dimension_expr, expr_column_name, filter, filter_member, filter_op, filter_op_filters, - filter_op_filters_empty_tail, filter_replacer, filter_simplify_pull_up_replacer, - filter_simplify_push_down_replacer, fun_expr, fun_expr_args_legacy, fun_expr_var_arg, - inlist_expr, inlist_expr_list, is_not_null_expr, is_null_expr, like_expr, limit, - list_rewrite, literal_bool, literal_expr, literal_int, literal_string, measure_expr, - negative_expr, not_expr, projection, rewrite, + between_expr, binary_expr, case_expr, case_expr_var_arg, cast_expr, cast_expr_explicit, + change_user_member, column_expr, cube_scan, cube_scan_filters, + cube_scan_filters_empty_tail, cube_scan_members, dimension_expr, expr_column_name, filter, + filter_member, filter_op, filter_op_filters, filter_op_filters_empty_tail, filter_replacer, + filter_simplify_pull_up_replacer, filter_simplify_push_down_replacer, fun_expr, + fun_expr_args_legacy, fun_expr_var_arg, inlist_expr, inlist_expr_list, is_not_null_expr, + is_null_expr, like_expr, limit, list_rewrite, literal_bool, literal_expr, literal_int, + literal_null, literal_string, measure_expr, negative_expr, not_expr, projection, rewrite, rewriter::{CubeEGraph, CubeRewrite, RewriteRules}, scalar_fun_expr_args_empty_tail, segment_member, time_dimension_date_range_replacer, time_dimension_expr, transform_original_expr_to_alias, transforming_chain_rewrite, @@ -2461,6 +2461,67 @@ impl RewriteRules for FilterRules { ), self.transform_not_column_equals_date("?literal", "?one_day"), ), + rewrite( + "filter-tableau-case-when-not-null", + filter_replacer( + binary_expr( + case_expr( + None, + vec![( + not_expr(is_null_expr("?left_expr")), + "?left_expr".to_string(), + )], + Some(literal_null()), + ), + "?op", + "?right_expr", + ), + "?alias_to_cube", + "?members", + "?filter_aliases", + ), + filter_replacer( + binary_expr("?left_expr", "?op", "?right_expr"), + "?alias_to_cube", + "?members", + "?filter_aliases", + ), + ), + rewrite( + "filter-tableau-cast-text-to-timestamp-to-date", + filter_replacer( + binary_expr( + cast_expr_explicit( + udf_expr( + "str_to_date", + vec![ + column_expr("?column"), + literal_string("YYYY-MM-DD\"T\"HH24:MI:SS.MS"), + ], + ), + DataType::Date32, + ), + "?op", + "?right_expr", + ), + "?alias_to_cube", + "?members", + "?filter_aliases", + ), + filter_replacer( + binary_expr( + self.fun_expr( + "DateTrunc", + vec![literal_string("day"), column_expr("?column")], + ), + "?op", + "?right_expr", + ), + "?alias_to_cube", + "?members", + "?filter_aliases", + ), + ), rewrite( "in-date-range-to-time-dimension-pull-up-left", cube_scan_filters(