Skip to content

fix(eql_v3): empty-string ordered text sorts first instead of dropping out#316

Merged
tobyhede merged 3 commits into
eql_v3from
fix/262-empty-text-sorts-first-rebased
Jun 23, 2026
Merged

fix(eql_v3): empty-string ordered text sorts first instead of dropping out#316
tobyhede merged 3 commits into
eql_v3from
fix/262-empty-text-sorts-first-rebased

Conversation

@tobyhede

@tobyhede tobyhede commented Jun 22, 2026

Copy link
Copy Markdown
Contributor

Summary

Encrypting "" as ordered text produces an empty ORE term (ob: []). The eql_v3.ore_block_256 extractor collapsed that to NULL terms (array_agg over zero rows is NULL), so compare_ore_block_256_terms returned NULL and an empty-text row silently:

  • dropped out of ORDER BY,
  • was wrongly returned by eql_v3.max, and
  • threw off range-query counts.

The comparator's existing "empty sorts first" cardinality = 0 guard was effectively dead code, because an empty ob never reached it as an empty array.

Fix

COALESCE the empty array_agg to an empty ore_block_256_term[] so the extractor yields a zero-term composite instead of NULL terms. The cardinality = 0 guard now engages and orders empty before every non-empty value.

Storage and equality are unaffected ("" keeps a real c and hm); a genuine SQL NULL row is unchanged (the extractor is STRICT).

Tests

  • ore_block_comparator_tests: empty term sorts before non-empty (unit).
  • sem.rs T7: characterization updated (empty ob -> non-NULL, zero terms).
  • v3_text_empty_order_tests + v3_text_empty fixture: end-to-end ORDER BY / min / max over text_ord with a real "" ciphertext. Proven non-vacuous by reverting the fix (max wrongly returns "").

Notes

v2 carries the identical latent bug but is being removed, so it is left unchanged (issue #262 part 2 is moot).

Refs #262

Summary by CodeRabbit

  • Bug Fixes

    • Fixed ordering of empty strings in encrypted ordered-text columns. Empty values now correctly sort first in ascending order and last in descending order, and are properly included in min/max aggregates and range queries. Storage and equality behavior remain unchanged.
  • Tests

    • Added comprehensive test coverage for empty string ordering behavior in encrypted operations.

…g out

Encrypting "" as ordered text produces an empty ORE term (ob: []). The
eql_v3.ore_block_256 extractor collapsed that to NULL terms (array_agg over
zero rows is NULL), so compare_ore_block_256_terms returned NULL and an
empty-text row silently dropped out of ORDER BY, was wrongly returned by
eql_v3.max, and threw off range-query counts. The comparator's existing
"empty sorts first" cardinality guard was dead code because empty ob never
reached it as an empty array.

Fix: COALESCE the empty array_agg to an empty ore_block_256_term[] so the
extractor yields a zero-term composite. The cardinality = 0 guard now engages
and orders empty before every non-empty value. Storage and equality are
unaffected ("" keeps a real c and hm); a genuine SQL NULL row is unchanged
(the extractor is STRICT).

Tests:
- ore_block_comparator_tests: empty term sorts before non-empty (unit).
- sem.rs T7: characterization updated (empty ob -> non-NULL, zero terms).
- v3_text_empty_order_tests + v3_text_empty fixture: end-to-end ORDER BY /
  min / max over text_ord with a real "" ciphertext. Proven non-vacuous by
  reverting the fix (max wrongly returns "").

v2 carries the identical latent bug but is being removed, so it is left
unchanged (issue #262 part 2 is moot).

Refs: #262
@coderabbitai

coderabbitai Bot commented Jun 22, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: e7383914-0493-47ab-91f0-166b20d12fb4

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/262-empty-text-sorts-first-rebased

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@auxesis auxesis requested review from auxesis and freshtonic and removed request for auxesis June 23, 2026 00:25
Encrypting the empty string "" as ordered text produces an empty ORE
term (ob: []) -- the only value that does. The ORE-bearing eql_v3
domains (_ord / _ord_ore, and text _search) now carry a CHECK requiring
ob to be a non-empty array, so casting or inserting an empty-ob payload
into an ordered column fails loudly with a check violation (23514)
rather than producing an unorderable row.

The rule lives where term behaviour belongs (CLAUDE.md): a typed
per-term property Term::nonempty_array_key (Ore => "ob"), collected by
Term::nonempty_array_keys symmetric to term_json_keys, with unit tests.
DomainBlock derives nonempty_array_keys from the domain's terms exactly
like keys, and the types.sql.j2 template renders a non-empty-array CHECK
per key generically -- no hardcoded ob, no ORE concept leaking into the
codegen-context or template layers. Storage / equality / match / bool
domains are unaffected; fixed-width scalars never produce an empty ob,
so the clause is a structural invariant that is a no-op for them.

The comparator's "empty sorts first" cardinality guard (02ab24b) is
retained as defense-in-depth for any path that bypasses the domain
(e.g. a composite built directly).

- crates/eql-scalars: Term::nonempty_array_key{,s} + tests.
- crates/eql-codegen: DomainBlock.nonempty_array_keys + template clause
  + unit test pinning the constraint to ORE-bearing domains only.
- tests/codegen/reference/*/*_types.sql regenerated.
- Rewrote v3_text_empty_order_tests -> v3_text_empty_constraint_tests:
  empty "" rejected on text_ord/text_ord_ore; controls accepted/ordered.
- CHANGELOG + adding-a-scalar docs updated.

Refs: #262
@tobyhede tobyhede merged commit d122e62 into eql_v3 Jun 23, 2026
18 checks passed
@tobyhede tobyhede deleted the fix/262-empty-text-sorts-first-rebased branch June 23, 2026 09:33
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