@@ -294,6 +294,86 @@ def test_translate_segment__rule_with_no_conditions_or_nested_rules__matches_all
294294 assert translate_segment (seg , _ctx ()) == "(TRUE)"
295295
296296
297+ def test_translate_segment__rule_with_conditions_and_nested_rule__ands_the_two_groups () -> None :
298+ # Given a rule carrying both a condition and a (translatable) nested rule
299+ seg : SegmentContext = {
300+ "key" : "11" ,
301+ "name" : "s" ,
302+ "rules" : [
303+ {
304+ "type" : "ALL" ,
305+ "conditions" : [{"operator" : "EQUAL" , "property" : "plan" , "value" : "growth" }],
306+ "rules" : [
307+ {
308+ "type" : "ALL" ,
309+ "conditions" : [{"operator" : "EQUAL" , "property" : "country" , "value" : "GB" }],
310+ }
311+ ],
312+ }
313+ ],
314+ }
315+
316+ # When translated
317+ sql = translate_segment (seg , _ctx ())
318+
319+ # Then the condition group and the nested-rule group are AND-ed together
320+ # (mirroring the engine: matches_conditions AND matches_rules)
321+ assert sql == (
322+ "(((("
323+ "if(i.traits.`plan` IS NULL, NULL, toString(i.traits.`plan`)) IS NOT NULL"
324+ " AND ((toString(i.traits.`plan`) = 'growth') OR (i.traits.`plan`.:Bool = true))"
325+ "))) AND (((("
326+ "if(i.traits.`country` IS NULL, NULL, toString(i.traits.`country`)) IS NOT NULL"
327+ " AND ((toString(i.traits.`country`) = 'GB') OR (i.traits.`country`.:Bool = true))"
328+ ")))))"
329+ )
330+
331+
332+ def test_translate_segment__any_rule_with_conditions_and_nested_rule__ors_within_ands_across () -> (
333+ None
334+ ):
335+ # Given an ANY rule with two conditions and a nested rule. The engine
336+ # evaluates this as `any(conditions) AND any(rules)`, NOT a single OR over
337+ # the merged set — so the two conditions OR together but the groups AND.
338+ seg : SegmentContext = {
339+ "key" : "12" ,
340+ "name" : "s" ,
341+ "rules" : [
342+ {
343+ "type" : "ANY" ,
344+ "conditions" : [
345+ {"operator" : "EQUAL" , "property" : "plan" , "value" : "growth" },
346+ {"operator" : "EQUAL" , "property" : "plan" , "value" : "scale" },
347+ ],
348+ "rules" : [
349+ {
350+ "type" : "ALL" ,
351+ "conditions" : [{"operator" : "EQUAL" , "property" : "country" , "value" : "GB" }],
352+ }
353+ ],
354+ }
355+ ],
356+ }
357+
358+ # When translated
359+ sql = translate_segment (seg , _ctx ())
360+
361+ # Then the two conditions OR together within their group, and that group is
362+ # AND-ed with the nested-rule group — `any(conditions) AND any(rules)`.
363+ assert sql == (
364+ "(((("
365+ "if(i.traits.`plan` IS NULL, NULL, toString(i.traits.`plan`)) IS NOT NULL"
366+ " AND ((toString(i.traits.`plan`) = 'growth') OR (i.traits.`plan`.:Bool = true))"
367+ ")) OR (("
368+ "if(i.traits.`plan` IS NULL, NULL, toString(i.traits.`plan`)) IS NOT NULL"
369+ " AND ((toString(i.traits.`plan`) = 'scale') OR (i.traits.`plan`.:Bool = true))"
370+ "))) AND (((("
371+ "if(i.traits.`country` IS NULL, NULL, toString(i.traits.`country`)) IS NOT NULL"
372+ " AND ((toString(i.traits.`country`) = 'GB') OR (i.traits.`country`.:Bool = true))"
373+ ")))))"
374+ )
375+
376+
297377def test_translate_segment__trait_key_with_hyphens__quotes_subcolumn_path () -> None :
298378 # Given a trait key with a hyphen (illegal as an unquoted SQL identifier)
299379 seg : SegmentContext = {
0 commit comments