@@ -4,6 +4,7 @@ use crate::planner::filter::{BaseFilter, FilterOperator};
44use crate :: planner:: sql_evaluator:: DebugSql ;
55use crate :: test_fixtures:: cube_bridge:: MockSchema ;
66use crate :: test_fixtures:: test_utils:: TestContext ;
7+ use std:: rc:: Rc ;
78
89#[ test]
910fn test_dimension_basic ( ) {
@@ -201,3 +202,137 @@ fn test_filter_group_and_collapsed() {
201202 "AND: [\n {visitors.source} equals: ['google'],\n {visitors.id} gt: ['100']\n ]" ;
202203 assert_eq ! ( sql, expected) ;
203204}
205+
206+ #[ test]
207+ fn test_find_subtree_for_members_excludes_segments_from_and_group ( ) {
208+ let schema = MockSchema :: from_yaml_file ( "common/integration_basic.yaml" ) ;
209+ let ctx = TestContext :: new ( schema) . unwrap ( ) ;
210+ let city_symbol = ctx. create_symbol ( "customers.city" ) . unwrap ( ) ;
211+ let amount_symbol = ctx. create_symbol ( "orders.amount" ) . unwrap ( ) ;
212+
213+ let city_filter = BaseFilter :: try_new (
214+ ctx. query_tools ( ) . clone ( ) ,
215+ city_symbol. clone ( ) ,
216+ crate :: planner:: filter:: base_filter:: FilterType :: Dimension ,
217+ FilterOperator :: Equal ,
218+ Some ( vec ! [ Some ( "New York" . to_string( ) ) ] ) ,
219+ )
220+ . unwrap ( ) ;
221+
222+ let amount_filter = BaseFilter :: try_new (
223+ ctx. query_tools ( ) . clone ( ) ,
224+ amount_symbol. clone ( ) ,
225+ crate :: planner:: filter:: base_filter:: FilterType :: Dimension ,
226+ FilterOperator :: Gte ,
227+ Some ( vec ! [ Some ( "100" . to_string( ) ) ] ) ,
228+ )
229+ . unwrap ( ) ;
230+
231+ let completed_segment = ctx. create_segment ( "orders.completed_orders" ) . unwrap ( ) ;
232+
233+ let filter_tree = FilterItem :: Group ( Rc :: new ( FilterGroup :: new (
234+ FilterGroupOperator :: And ,
235+ vec ! [
236+ FilterItem :: Item ( city_filter) ,
237+ FilterItem :: Item ( amount_filter) ,
238+ FilterItem :: Segment ( completed_segment) ,
239+ ] ,
240+ ) ) ) ;
241+
242+ let city_member = city_symbol. resolve_reference_chain ( ) . full_name ( ) ;
243+ let amount_member = amount_symbol. resolve_reference_chain ( ) . full_name ( ) ;
244+ let targets = vec ! [ & city_member, & amount_member] ;
245+
246+ let subtree = filter_tree
247+ . find_subtree_for_members ( & targets)
248+ . expect ( "matching filters should still be extracted" ) ;
249+
250+ assert_eq ! (
251+ subtree. debug_sql( false ) ,
252+ "AND: [\n {customers.city} equals: ['New York'],\n {orders.amount} gte: ['100']\n ]"
253+ ) ;
254+ }
255+
256+ #[ test]
257+ fn test_find_subtree_for_members_keeps_or_groups_all_or_nothing ( ) {
258+ let schema = MockSchema :: from_yaml_file ( "common/integration_basic.yaml" ) ;
259+ let ctx = TestContext :: new ( schema) . unwrap ( ) ;
260+ let city_symbol = ctx. create_symbol ( "customers.city" ) . unwrap ( ) ;
261+
262+ let city_filter = BaseFilter :: try_new (
263+ ctx. query_tools ( ) . clone ( ) ,
264+ city_symbol. clone ( ) ,
265+ crate :: planner:: filter:: base_filter:: FilterType :: Dimension ,
266+ FilterOperator :: Equal ,
267+ Some ( vec ! [ Some ( "New York" . to_string( ) ) ] ) ,
268+ )
269+ . unwrap ( ) ;
270+
271+ let completed_segment = ctx. create_segment ( "orders.completed_orders" ) . unwrap ( ) ;
272+
273+ let filter_tree = FilterItem :: Group ( Rc :: new ( FilterGroup :: new (
274+ FilterGroupOperator :: Or ,
275+ vec ! [
276+ FilterItem :: Item ( city_filter) ,
277+ FilterItem :: Segment ( completed_segment) ,
278+ ] ,
279+ ) ) ) ;
280+
281+ let city_member = city_symbol. resolve_reference_chain ( ) . full_name ( ) ;
282+ let targets = vec ! [ & city_member] ;
283+
284+ assert ! (
285+ filter_tree. find_subtree_for_members( & targets) . is_none( ) ,
286+ "partial matches should not be extracted from OR groups"
287+ ) ;
288+ }
289+
290+ #[ test]
291+ fn test_find_subtree_for_members_rejects_or_groups_with_partially_matching_children ( ) {
292+ let schema = MockSchema :: from_yaml_file ( "common/integration_basic.yaml" ) ;
293+ let ctx = TestContext :: new ( schema) . unwrap ( ) ;
294+ let city_symbol = ctx. create_symbol ( "customers.city" ) . unwrap ( ) ;
295+ let amount_symbol = ctx. create_symbol ( "orders.amount" ) . unwrap ( ) ;
296+
297+ let city_filter = BaseFilter :: try_new (
298+ ctx. query_tools ( ) . clone ( ) ,
299+ city_symbol. clone ( ) ,
300+ crate :: planner:: filter:: base_filter:: FilterType :: Dimension ,
301+ FilterOperator :: Equal ,
302+ Some ( vec ! [ Some ( "New York" . to_string( ) ) ] ) ,
303+ )
304+ . unwrap ( ) ;
305+
306+ let amount_filter = BaseFilter :: try_new (
307+ ctx. query_tools ( ) . clone ( ) ,
308+ amount_symbol. clone ( ) ,
309+ crate :: planner:: filter:: base_filter:: FilterType :: Dimension ,
310+ FilterOperator :: Gte ,
311+ Some ( vec ! [ Some ( "100" . to_string( ) ) ] ) ,
312+ )
313+ . unwrap ( ) ;
314+
315+ let completed_segment = ctx. create_segment ( "orders.completed_orders" ) . unwrap ( ) ;
316+
317+ let partially_matching_branch = FilterItem :: Group ( Rc :: new ( FilterGroup :: new (
318+ FilterGroupOperator :: And ,
319+ vec ! [
320+ FilterItem :: Item ( city_filter) ,
321+ FilterItem :: Segment ( completed_segment) ,
322+ ] ,
323+ ) ) ) ;
324+
325+ let filter_tree = FilterItem :: Group ( Rc :: new ( FilterGroup :: new (
326+ FilterGroupOperator :: Or ,
327+ vec ! [ partially_matching_branch, FilterItem :: Item ( amount_filter) ] ,
328+ ) ) ) ;
329+
330+ let city_member = city_symbol. resolve_reference_chain ( ) . full_name ( ) ;
331+ let amount_member = amount_symbol. resolve_reference_chain ( ) . full_name ( ) ;
332+ let targets = vec ! [ & city_member, & amount_member] ;
333+
334+ assert ! (
335+ filter_tree. find_subtree_for_members( & targets) . is_none( ) ,
336+ "OR groups should only match when every branch matches without pruning"
337+ ) ;
338+ }
0 commit comments