flagsmith-sql-flag-engine 0.1.0__tar.gz → 0.1.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: flagsmith-sql-flag-engine
3
- Version: 0.1.0
3
+ Version: 0.1.2
4
4
  Summary: SQL translator for Flagsmith segment predicates.
5
5
  Author: Flagsmith
6
6
  Author-email: Flagsmith <engineering@flagsmith.com>
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "flagsmith-sql-flag-engine"
3
- version = "0.1.0"
3
+ version = "0.1.2"
4
4
  description = "SQL translator for Flagsmith segment predicates."
5
5
  readme = "README.md"
6
6
  authors = [{ name = "Flagsmith", email = "engineering@flagsmith.com" }]
@@ -435,19 +435,13 @@ def translate_condition(cond: SegmentCondition, ctx: TranslateContext) -> str |
435
435
  identity: dict[str, object] = ctx.evaluation_context.get("identity") or {} # type: ignore[assignment]
436
436
  kind = classification.kind
437
437
  if not prop:
438
- # Implicit `$.identity.key` engine returns False when no
439
- # identity, or when the identity lacks `key`. The engine
440
- # never synthesises one from env+identifier.
441
- if not identity.get("key"):
442
- return "FALSE"
438
+ # In traditional engine implementations, this branch implies
439
+ # an identity-less context, which makes no sense for the SQL engine.
440
+ # Assume identity key.
443
441
  value_expr = ctx.dialect.cast_string(ctx.identity_key_expr)
444
442
  elif kind == "key":
445
- if not identity.get("key"):
446
- return "FALSE"
447
443
  value_expr = ctx.dialect.cast_string(ctx.jsonpath_expr("$.identity.key"))
448
444
  elif kind == "identifier":
449
- if not identity.get("identifier"):
450
- return "FALSE"
451
445
  value_expr = ctx.dialect.cast_string(ctx.jsonpath_expr("$.identity.identifier"))
452
446
  elif kind == "identity_object":
453
447
  # PERCENTAGE_SPLIT on `$.identity` — the whole dict. Engine
@@ -517,26 +511,33 @@ def translate_condition(cond: SegmentCondition, ctx: TranslateContext) -> str |
517
511
 
518
512
 
519
513
  def translate_rule(rule: SegmentRule, ctx: TranslateContext) -> str | None:
520
- children: list[str] = []
514
+ cond_children: list[str] = []
521
515
  for cond in rule.get("conditions") or []:
522
516
  sql = translate_condition(cond, ctx)
523
517
  if sql is None:
524
518
  return None
525
- children.append(f"({sql})")
519
+ cond_children.append(f"({sql})")
520
+ rule_children: list[str] = []
526
521
  for nested in rule.get("rules") or []:
527
522
  sql = translate_rule(nested, ctx)
528
523
  if sql is None:
529
524
  return None
530
- children.append(f"({sql})")
531
-
532
- assert children, "segment rule must have at least one condition or nested rule"
533
- match rule["type"]:
534
- case "ALL":
535
- return " AND ".join(children)
536
- case "ANY":
537
- return " OR ".join(children)
538
- case "NONE":
539
- return f"NOT ({' OR '.join(children)})"
525
+ rule_children.append(f"({sql})")
526
+
527
+ # Mirror the engine's `context_matches_rule`: conditions and nested rules
528
+ # are two independent groups AND-ed together, each vacuously true when
529
+ # empty.
530
+ op = {"ALL": " AND ", "ANY": " OR ", "NONE": " OR "}[rule["type"]]
531
+ groups = [
532
+ f"NOT ({op.join(c)})" if rule["type"] == "NONE" else op.join(c)
533
+ for c in (cond_children, rule_children)
534
+ if c
535
+ ]
536
+ if not groups:
537
+ return "TRUE"
538
+ if len(groups) == 1:
539
+ return groups[0]
540
+ return " AND ".join(f"({g})" for g in groups)
540
541
 
541
542
 
542
543
  def translate_segment(segment: SegmentContext, ctx: TranslateContext) -> str | None: