Skip to content

fix(vm): treat a present-but-empty collection as not EXISTS - #60

Open
sumankarki wants to merge 1 commit into
masterfrom
fix/exists-empty-collection-semantics
Open

fix(vm): treat a present-but-empty collection as not EXISTS#60
sumankarki wants to merge 1 commit into
masterfrom
fix/exists-empty-collection-semantics

Conversation

@sumankarki

Copy link
Copy Markdown
Contributor

What

EXISTS <field> in the VM returned true for any present value that was not nil/NilValue. A present-but-empty collection (an empty StringsValue, SliceValue, or any Map*Value) is not nil, so it evaluated as EXISTS = true / NOT EXISTS = false.

Elasticsearch does not index empty arrays, so the esgen path treats a present empty array as not existing (NOT EXISTS = true). The two engines therefore disagreed on NOT EXISTS <collection> whenever a field was persisted as a present empty value.

This scopes the VM's EXISTS to return false for empty collections, matching ES.

field state before after
absent EXISTS=false EXISTS=false
present, empty [] / {} EXISTS=true EXISTS=false
present, populated EXISTS=true EXISTS=true

NOT EXISTS is the negation, so it flips correspondingly for the empty case.

Why it matters

In a downstream CDP, an ES-backed segment scan selected members where NOT EXISTS <field> was true (field stored as empty []), but the VM re-eval on the same entities returned "not a member" and dropped them — so a whole audience matched in the UI/backfill yet produced nothing on export. Verified against a real 108k-member audience: 100% had the field present-but-empty.

Scope / blast radius

  • Collections only. Change is limited to the collection value types (StringsValue, SliceValue, ByteSliceValue, and the Map*Value family). Scalars are untouched — a present empty string (EXISTS "") is still true, as pinned by the existing empty_str test.
  • Behavior changes for any filter using EXISTS/NOT EXISTS on a collection field that can be present-but-empty. This is the intended correction (align VM with ES).

Tests

  • Added cases in vm/vm_test.go: EXISTS empty_strs → false, NOT EXISTS empty_strs → true, EXISTS empty_map → false, plus populated (urls, hits) → true and scalar empty_str → true (unchanged).
  • Full module go test ./... passes; no existing test relied on the old behavior.
  • Pinning verified via -overlay: reverting the logic makes EXISTS empty_strs fail with true != false, confirming the new tests actually exercise the change.

EXISTS on a present field returned true for any value that was not
nil/NilValue, so an empty list or map (e.g. an empty StringsValue)
evaluated as EXISTS=true, NOT EXISTS=false. Elasticsearch does not index
empty arrays and reports them as not existing, so the VM and the esgen
path disagreed on `NOT EXISTS <collection>` for a present empty value --
a segment could match in an ES-backed scan yet be dropped by the VM
re-eval.

Scope EXISTS to return false for empty collections (lists and maps),
matching ES. Scalars, including an empty string, are unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@sumankarki

sumankarki commented Aug 21, 2026

Copy link
Copy Markdown
Contributor Author

Fixes the EXISTS/empty-collection divergence behind lytics/lio#39226 (LYT-1022). Verified on a real 108k-member audience.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant