1- use crate :: cube_bridge:: member_expression:: MemberExpressionExpressionDef ;
1+ use crate :: cube_bridge:: member_expression:: { ExpressionStruct , MemberExpressionExpressionDef } ;
22use crate :: cube_bridge:: member_sql:: MemberSql ;
33use crate :: cube_bridge:: options_member:: OptionsMember ;
44use crate :: test_fixtures:: cube_bridge:: {
5- members_from_strings, MockBaseQueryOptions , MockMemberExpressionDefinition , MockMemberSql ,
6- MockSchema ,
5+ members_from_strings, MockBaseQueryOptions , MockExpressionStruct ,
6+ MockMemberExpressionDefinition , MockMemberSql , MockSchema , MockStructWithSqlMember ,
77} ;
88use crate :: test_fixtures:: test_utils:: TestContext ;
9+ use cubenativeutils:: CubeError ;
910use indoc:: indoc;
1011use std:: rc:: Rc ;
1112
@@ -25,6 +26,34 @@ fn make_member_expression(expression_name: &str, cube_name: &str, sql: &str) ->
2526 OptionsMember :: MemberExpression ( Rc :: new ( expr) )
2627}
2728
29+ // Builds a `PatchMeasure` member expression that adds one ad-hoc CASE-WHEN
30+ // filter to `source_measure` — the SQL-API mechanism for pushing a filter
31+ // inside a measure's aggregation.
32+ fn make_patched_measure (
33+ expression_name : & str ,
34+ cube_name : & str ,
35+ source_measure : & str ,
36+ filter_sql : & str ,
37+ ) -> OptionsMember {
38+ let filter = MockStructWithSqlMember :: builder ( )
39+ . sql ( filter_sql. to_string ( ) )
40+ . build ( ) ;
41+ let expr_struct = MockExpressionStruct :: builder ( )
42+ . expression_type ( "PatchMeasure" . to_string ( ) )
43+ . source_measure ( Some ( source_measure. to_string ( ) ) )
44+ . add_filters ( Some ( vec ! [ Rc :: new( filter) ] ) )
45+ . build ( ) ;
46+ let expr = MockMemberExpressionDefinition :: builder ( )
47+ . expression_name ( Some ( expression_name. to_string ( ) ) )
48+ . name ( Some ( expression_name. to_string ( ) ) )
49+ . cube_name ( Some ( cube_name. to_string ( ) ) )
50+ . expression ( MemberExpressionExpressionDef :: Struct (
51+ Rc :: new ( expr_struct) as Rc < dyn ExpressionStruct >
52+ ) )
53+ . build ( ) ;
54+ OptionsMember :: MemberExpression ( Rc :: new ( expr) )
55+ }
56+
2857fn build_options_with_member_expression (
2958 ctx : & TestContext ,
3059 extra_measure : OptionsMember ,
@@ -183,3 +212,45 @@ async fn test_many_to_one_view_child_distinct_dim() {
183212 insta:: assert_snapshot!( result) ;
184213 }
185214}
215+
216+ // A PatchMeasure adding a CASE-WHEN filter to a measure exposed through a view
217+ // (`root_val_sum`, a SUM) must resolve the reference chain to the owning cube
218+ // measure so the filter is pushed inside the aggregation.
219+ // root_test_dim='rt_x' → roots 1,2 → SUM = 10 + 20 = 30.
220+ #[ tokio:: test( flavor = "multi_thread" ) ]
221+ async fn test_many_to_one_view_patched_measure_filter ( ) -> Result < ( ) , CubeError > {
222+ let ctx = create_test_context ( ) ;
223+ let expr = make_patched_measure (
224+ "filtered_root_sum" ,
225+ "many_to_one_view" ,
226+ "many_to_one_view.root_val_sum" ,
227+ "{many_to_one_view.root_test_dim} = 'rt_x'" ,
228+ ) ;
229+
230+ let options = Rc :: new (
231+ MockBaseQueryOptions :: builder ( )
232+ . cube_evaluator ( ctx. query_tools ( ) . cube_evaluator ( ) . clone ( ) )
233+ . base_tools ( ctx. query_tools ( ) . base_tools ( ) . clone ( ) )
234+ . join_graph ( ctx. query_tools ( ) . join_graph ( ) . clone ( ) )
235+ . security_context ( ctx. security_context ( ) . clone ( ) )
236+ . measures ( Some ( vec ! [ expr] ) )
237+ . build ( ) ,
238+ ) ;
239+
240+ let sql = ctx. build_sql_from_options ( options. clone ( ) ) ?;
241+ // The ad-hoc filter is pushed inside the aggregation (measure_filter.rs
242+ // renders `CASE WHEN <filter> THEN <result> END`), not as an outer WHERE.
243+ assert ! (
244+ sql. contains( "CASE WHEN" ) ,
245+ "ad-hoc filter must be pushed inside the aggregation, got: {sql}"
246+ ) ;
247+
248+ if let Some ( result) = ctx
249+ . try_execute_pg_from_options ( options, "many_to_one_tables.sql" )
250+ . await
251+ {
252+ insta:: assert_snapshot!( result) ;
253+ }
254+
255+ Ok ( ( ) )
256+ }
0 commit comments