Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 12 additions & 8 deletions src/flagsmith_sql_flag_engine/translator.py
Original file line number Diff line number Diff line change
Expand Up @@ -432,7 +432,6 @@ def translate_condition(cond: SegmentCondition, ctx: TranslateContext) -> str |
# Engine: float() on the threshold raises → returns False.
return "FALSE"
threshold = float(threshold_lit)
identity: dict[str, object] = ctx.evaluation_context.get("identity") or {} # type: ignore[assignment]
kind = classification.kind
if not prop:
# In traditional engine implementations, this branch implies
Expand All @@ -457,13 +456,18 @@ def translate_condition(cond: SegmentCondition, ctx: TranslateContext) -> str |
# future work and let the caller fall back to the engine.
return None
else:
# Plain trait key, or `$.identity.traits.<X>` rewritten to
# the bare key. Hash subject pulls from `i.traits:"<key>"`
# per row.
traits = identity.get("traits") or {}
if not isinstance(traits, dict) or prop not in traits:
return "FALSE"
value_expr = ctx.dialect.cast_string(ctx.trait_path(prop))
# Plain trait key, or `$.identity.traits.<X>` rewritten to the
# bare key. Hash the per-row trait value from `i.traits:"<key>"`.
# A row missing the trait has no value to bucket, so it cannot
# match — mirroring the engine's `context_value is None -> False`.
trait_sql = ctx.trait_path(prop)
split = _percentage_split_expr(
ctx,
ctx.segment_key,
ctx.dialect.cast_string(trait_sql),
threshold,
)
return f"({trait_sql} IS NOT NULL AND {split})"
return _percentage_split_expr(ctx, ctx.segment_key, value_expr, threshold)

if not prop:
Expand Down
51 changes: 25 additions & 26 deletions tests/test_translator_unit.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,6 @@
def _ctx(env_key: str = "test-env-key", env_name: str = "Test") -> TranslateContext:
eval_ctx: EvaluationContext = {
"environment": {"key": env_key, "name": env_name},
# PERCENTAGE_SPLIT short-circuits to FALSE when the prop isn't in
# the eval context's traits, so seed the traits referenced below.
"identity": {
"identifier": "u",
"key": "k",
"traits": {"plan": "growth", "country": "GB", "uuid_attr": "abc"},
},
}
return TranslateContext(evaluation_context=eval_ctx, dialect=ClickHouseDialect())

Expand Down Expand Up @@ -164,6 +157,31 @@ def test_translate_segment__percentage_split_on_trait__uses_trait_subcolumn() ->
assert "MD5(" in sql


def test_translate_segment__percentage_split_on_trait__guards_missing_trait_per_row() -> None:
# Given
seg: SegmentContext = {
"key": "101",
"name": "s",
"rules": [
{
"type": "ALL",
"conditions": [{"operator": "PERCENTAGE_SPLIT", "property": "plan", "value": "10"}],
}
],
}

# When
sql = translate_segment(seg, _ctx())

# Then
assert sql is not None
assert "FALSE" not in sql
assert "i.traits.`plan`" in sql
assert "IS NOT NULL" in sql
assert "MD5(" in sql
assert "<= 10.0" in sql
Comment thread
khvn26 marked this conversation as resolved.


def test_translate_segment__jsonpath_identity_identifier__uses_column_directly() -> None:
# Given a condition referencing $.identity.identifier
seg: SegmentContext = {
Expand Down Expand Up @@ -623,25 +641,6 @@ def test_translate_segment__percentage_split_unknown_jsonpath__returns_none() ->
assert translate_segment(seg, _ctx()) is None


def test_translate_segment__percentage_split_trait_not_in_context__compiles_to_false() -> None:
# Given a PERCENTAGE_SPLIT on a trait the eval context's identity doesn't carry
seg: SegmentContext = {
"key": "ps6",
"name": "s",
"rules": [
{
"type": "ALL",
"conditions": [
{"operator": "PERCENTAGE_SPLIT", "property": "missing_trait", "value": "50"}
],
}
],
}

# When / Then the predicate collapses to FALSE
assert translate_segment(seg, _ctx()) == "((FALSE))"


def test_translate_segment__is_set_on_identity_identifier__emits_true() -> None:
# Given an IS_SET on $.identity.identifier — every IDENTITIES row IS an
# identity, so the predicate is unconditionally true
Expand Down
2 changes: 1 addition & 1 deletion uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading