Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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
12 changes: 3 additions & 9 deletions src/flagsmith_sql_flag_engine/translator.py
Original file line number Diff line number Diff line change
Expand Up @@ -435,19 +435,13 @@ def translate_condition(cond: SegmentCondition, ctx: TranslateContext) -> str |
identity: dict[str, object] = ctx.evaluation_context.get("identity") or {} # type: ignore[assignment]
kind = classification.kind
if not prop:
# Implicit `$.identity.key` — engine returns False when no
# identity, or when the identity lacks `key`. The engine
# never synthesises one from env+identifier.
if not identity.get("key"):
return "FALSE"
# In tranditional engine implementations, this branch implies
Comment thread
khvn26 marked this conversation as resolved.
Outdated
# an identity-less context, which makes no sense for the SQL engine.
# Assume identity key.
value_expr = ctx.dialect.cast_string(ctx.identity_key_expr)
elif kind == "key":
if not identity.get("key"):
return "FALSE"
value_expr = ctx.dialect.cast_string(ctx.jsonpath_expr("$.identity.key"))
elif kind == "identifier":
if not identity.get("identifier"):
return "FALSE"
value_expr = ctx.dialect.cast_string(ctx.jsonpath_expr("$.identity.identifier"))
elif kind == "identity_object":
# PERCENTAGE_SPLIT on `$.identity` — the whole dict. Engine
Expand Down
3 changes: 3 additions & 0 deletions tests/harnesses/clickhouse.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@
# condition, so we accept the divergence on this niche shape
# (`$.`-prefixed trait names) and let callers fall back to the engine.
"test_jsonpath_like_trait__existing_jsonpath__should_match_trait",
# Does not apply to SQL engine implementation; we always have access to
# identity key.
"test_percentage_split__no_identity_key__should_match",
}


Expand Down
62 changes: 47 additions & 15 deletions tests/test_translator_unit.py
Original file line number Diff line number Diff line change
Expand Up @@ -542,9 +542,10 @@ def _ctx_identity_without(field: str) -> TranslateContext:
)


def test_translate_segment__percentage_split_implicit_key_no_identity__compiles_to_false() -> None:
# Given a PERCENTAGE_SPLIT with no property (implicit `$.identity.key`) and an eval
# context with no identity
def test_translate_segment__percentage_split_implicit_key_no_identity__inlines_md5() -> None:
# Given a PERCENTAGE_SPLIT with no property (implicit `$.identity.key`) and an
# eval context with no identity — the row-oriented case (e.g. segment-membership
# counting over the whole IDENTITIES table)
seg: SegmentContext = {
"key": "ps2",
"name": "s",
Expand All @@ -556,12 +557,25 @@ def test_translate_segment__percentage_split_implicit_key_no_identity__compiles_
],
}

# When / Then the predicate collapses to FALSE — engine returns False without identity
assert translate_segment(seg, _ctx_no_identity()) == "((FALSE))"
# When translated
sql = translate_segment(seg, _ctx_no_identity())

# Then it hashes the per-row identity-key column rather than folding to
# FALSE — the row supplies the subject, so a context identity is not needed
Comment thread
emyller marked this conversation as resolved.
Outdated
assert sql == (
"(((modulo(reinterpretAsUInt32(reverse(substring(MD5('ps2' || ',' ||"
" (toString(i.identity_key))), 1, 4))) * 7291 +"
" reinterpretAsUInt32(reverse(substring(MD5('ps2' || ',' ||"
" (toString(i.identity_key))), 5, 4))) * 1897 +"
" reinterpretAsUInt32(reverse(substring(MD5('ps2' || ',' ||"
" (toString(i.identity_key))), 9, 4))) * 6835 +"
" reinterpretAsUInt32(reverse(substring(MD5('ps2' || ',' ||"
" (toString(i.identity_key))), 13, 4))), 9999) / 9998.0 * 100.0 <= 50.0)))"
)
Comment thread
khvn26 marked this conversation as resolved.
Outdated


def test_translate_segment__percentage_split_identity_key_missing__compiles_to_false() -> None:
# Given the eval context's identity has no `key`
def test_translate_segment__percentage_split_key_no_context_identity__inlines_md5() -> None:
# Given a PERCENTAGE_SPLIT on `$.identity.key` and a context identity without `key`
seg: SegmentContext = {
"key": "ps3",
"name": "s",
Expand All @@ -575,14 +589,23 @@ def test_translate_segment__percentage_split_identity_key_missing__compiles_to_f
],
}

# When / Then the predicate collapses to FALSE
assert translate_segment(seg, _ctx_identity_without("key")) == "((FALSE))"
# When / Then it still hashes the per-row identity-key column
sql = translate_segment(seg, _ctx_identity_without("key"))
assert sql == (
"(((modulo(reinterpretAsUInt32(reverse(substring(MD5('ps3' || ',' ||"
" (toString(i.identity_key))), 1, 4))) * 7291 +"
" reinterpretAsUInt32(reverse(substring(MD5('ps3' || ',' ||"
" (toString(i.identity_key))), 5, 4))) * 1897 +"
" reinterpretAsUInt32(reverse(substring(MD5('ps3' || ',' ||"
" (toString(i.identity_key))), 9, 4))) * 6835 +"
" reinterpretAsUInt32(reverse(substring(MD5('ps3' || ',' ||"
" (toString(i.identity_key))), 13, 4))), 9999) / 9998.0 * 100.0 <= 50.0)))"
)


def test_translate_segment__percentage_split_identity_identifier_missing__compiles_to_false() -> (
None
):
# Given the eval context's identity has no `identifier`
def test_translate_segment__percentage_split_identifier_no_context_identity__inlines_md5() -> None:
# Given a PERCENTAGE_SPLIT on `$.identity.identifier` and a context identity
# without `identifier`
seg: SegmentContext = {
"key": "ps4",
"name": "s",
Expand All @@ -600,8 +623,17 @@ def test_translate_segment__percentage_split_identity_identifier_missing__compil
],
}

# When / Then the predicate collapses to FALSE
assert translate_segment(seg, _ctx_identity_without("identifier")) == "((FALSE))"
# When / Then it still hashes the per-row identifier column
sql = translate_segment(seg, _ctx_identity_without("identifier"))
assert sql == (
"(((modulo(reinterpretAsUInt32(reverse(substring(MD5('ps4' || ',' ||"
" (toString(i.identifier))), 1, 4))) * 7291 +"
" reinterpretAsUInt32(reverse(substring(MD5('ps4' || ',' || (toString(i.identifier))),"
" 5, 4))) * 1897 + reinterpretAsUInt32(reverse(substring(MD5('ps4' || ',' ||"
" (toString(i.identifier))), 9, 4))) * 6835 +"
" reinterpretAsUInt32(reverse(substring(MD5('ps4' || ',' || (toString(i.identifier))),"
" 13, 4))), 9999) / 9998.0 * 100.0 <= 50.0)))"
)


def test_translate_segment__percentage_split_unknown_jsonpath__returns_none() -> None:
Expand Down
Loading