Skip to content

Translate text value set Intersect, Except, Add, Count and equality where the result needs no order (StringSet, StringSet<T>) #485

Closed

Description

@CaffeinatedCoder

Revised (2026-10-26): no COLLATE "C". Measured on #483: a collated operand turns a GIN-indexed @> into a sequential scan, on an ordinary en_US column too. #483 instead rejects a collation on a string set column that may not compare bytes. So the column's own equality is byte equality, and count(DISTINCT x) and mutual containment need no collation. The seeds are a Count left as cardinality, and equality rewritten only when both sides are a text operation.

Problem

After #478, Union, Add, Intersect and Except translate canonically for every set type but text. Text can't be sorted in SQL the way memory sorts it (#479). So on 10.x, for StringSet and StringSet<T>:

  • Intersect, Except and Add don't translate at all.
  • Union translates to array_cat. The guard allows it under @>/<@/&&, cardinality(…) = 0, a null test and the outermost projection. Count and == over it throw.

#479 can change the order only in 11.0.0, because the change is breaking. 10.x keeps this for its whole life.

Many of the expressions that use the result don't need order:

Measured (PostgreSQL 16.15 and 18.1)

Change

  • For a set type without CanonicalizesInSql, the translator emits Intersect, Except and Add as values the guard follows, as it follows UnguardedUnion today. They are allowed under @>, <@, &&, cardinality(…) = 0, a null test and the outermost projection (the read canonicalizes), where the expander writes their unsorted form.
  • Two more allowed uses for such a value:
    • cardinality(x) becomes the count(DISTINCT … COLLATE "C") subquery, and ValueSetsMemberTranslator stops declining Count over a visible union.
    • x = y and x <> y become mutual containment, with COLLATE "C" on both operands, and so does NULLIF (EF Core 11's a == b ? null : a). This applies only where one side is such a value. Equality between two stored columns keeps =: it is documented to differ for rows another tool stored out of order (docs/efcore.md#rows-written-by-other-tools), and it is what a btree index serves.
  • Still refused for these values: GROUP BY, DISTINCT, ORDER BY, IN, a deduplicating set operation, the indexer, and an UPDATE setter, which would store a non-canonical array.
  • The wrapper arities (StringSet<T>) go through SetTypeRegistry and need no code of their own.

Order against #479. If #479 picks code point order for 11.0.0, text joins #478's canonical form on main, and these forms are no longer needed there. If this lands on main first, #479 replaces it there. If #479 lands first, this is release/10.x only, and the issue is closed by hand.

Does it change an answer?

No. Every newly translated shape throws today.

Done when

  • Intersect, Except and Add on StringSet and StringSet<T> translate under the allowed uses; Count, == and != translate over any such text value; the remaining refusals keep today's messages
  • UnionCanonicalityTests (integration) asserts results against PostgreSQL 16 and 18. It covers the supplementary/U+E000–U+FFFF pair, duplicates, NULL operands, rows another tool stored out of order, and a nondeterministic column collation
  • seeds that drop COLLATE "C" from the count, and that write equality as =, are each caught
  • docs/efcore.md#what-runs-where (the text row and "Count over a text Union computed in the database throws"), #combine-sets-with-union, the AGENTS.md intro and its Union rule, and claims E17 and E35 follow

Activity

  1. added this to the 10.4.0 milestone on Oct 26, 2026
  2. CaffeinatedCoder commented on Oct 26, 2026

    @CaffeinatedCoder
    OwnerAuthor

    Done in PR #493, merged into main as 40c81da. GitHub's merge call failed after writing the merge commit, so the PR shows closed rather than merged and didn't close this issue; see the comment on #493. The 10.x port is #497 (PR #501).

  3. added a commit that references this issue on Oct 26, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area/value-setsValue set families and wrapper aritiesenhancementNew feature or requestpackage/efcoreAffects CodoMetis.ValueRanges.EFCore.PostgreSQL

    Projects

    No projects

      Milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions