Skip to content

Fix value.Contains(enumMember) execution failure on enum document members#4610

Merged
jeremydmiller merged 1 commit into
masterfrom
fix/enum-asstring-array-contains
Jun 3, 2026
Merged

Fix value.Contains(enumMember) execution failure on enum document members#4610
jeremydmiller merged 1 commit into
masterfrom
fix/enum-asstring-array-contains

Conversation

@jeremydmiller
Copy link
Copy Markdown
Member

Problem

A user-reported execution-time bug: with a CLR enum document member and an in/Contains-shaped LINQ predicate (the form HotChocolate's [UseFiltering] in operator emits), the parsed query throws at execution with:

System.InvalidCastException : Writing values of 'YourEnum[]' is not supported
for parameters having NpgsqlDbType '-2147483639'.

Distinct from the parse-time #4599 / #4600 / #4601 family already shipped in 8.37.2 — this is downstream, at parameter-bind time.

Root cause

EnumerableContains.Parse builds:

return new IsOneOfFilter(collectionMember, new CommandParameter(constant.Value));

When the document member is an enum, constant.Value is a CLR EnumType[] (or List<EnumType>). Npgsql has no parameter mapping for an arbitrary enum array unless the enum is a registered Postgres enum type, so it fails at the parameter-bind step with NpgsqlDbType '-2147483639' (= Array | <ordinal 9>).

The parallel LinqExtensions.IsOneOf path already handled this exact case via EnumIsOneOfWhereFragment (IsOneOf.cs:36), which projects the array into string[] (for EnumStorage.AsString) or int[] (for AsInteger) with the right NpgsqlDbType. The Contains shape just didn't route through it.

The bug was actually broader than the user reported: both AsString and AsInteger enum-storage modes were affected on the Contains path — the AsString user reported it because that's the more common config.

Fix

Route the enum case in EnumerableContains.Parse through EnumIsOneOfWhereFragment, mirroring IsOneOf.cs:36. A small List<T>T[] coercion handles non-array constant collections, since EnumIsOneOfWhereFragment requires a System.Array.

Tests

New Bug_enum_asstring_array_contains covers:

  • Scalar == canary on AsString — must keep working (already worked, but pinned).
  • Array Contains on AsString — headline regression.
  • List<> Contains on AsString — the non-array constant shape.
  • Single-element collection — degenerate IN.
  • Empty collection — must not throw; matches zero rows.
  • Array Contains on AsInteger — the broader half of the bug; pinned as a canary going forward.

Pre-fix: all 5 Contains tests fail with the reported Writing values of … is not supported. Post-fix: all 6 pass.

Regression sweeps: full LinqTests (1269 passed) + full ValueTypeTests (339 passed) — strongly-typed IDs share the EnumerableContains parse path so worth pinning.

🤖 Generated with Claude Code

EnumerableContains.Parse built `new CommandParameter(constant.Value)` from the
raw constant — when the document member is a CLR enum, that's an `EnumType[]`
(or `List<EnumType>`) which Npgsql has no parameter mapping for, so the query
threw at execution time:

    Writing values of 'YourEnum[]' is not supported for parameters
    having NpgsqlDbType '-2147483639'.

This affected both EnumStorage.AsString and EnumStorage.AsInteger because the
parser path didn't route through any enum-aware conversion. The headline shape
is HotChocolate's `[UseFiltering]` `in` operator, which emits
`values.Contains(p.EnumMember)`.

The parallel `LinqExtensions.IsOneOf` path already handled enums via
`EnumIsOneOfWhereFragment` (IsOneOf.cs:36) — this routes the Contains shape
through the same fragment, projecting the constant collection into a string[]
(for AsString) or int[] (for AsInteger) with the correct NpgsqlDbType.

List<T> support: EnumIsOneOfWhereFragment requires a System.Array, so a
`List<EnumType>` constant is converted up front via `Cast<object>().ToArray()`.

Repro test covers: scalar-eq canary (must keep working), array Contains,
List<> Contains, single-element collection, empty collection boundary, and
AsInteger canary.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@jeremydmiller jeremydmiller merged commit f3bb381 into master Jun 3, 2026
7 of 8 checks passed
@jeremydmiller jeremydmiller deleted the fix/enum-asstring-array-contains branch June 3, 2026 01:03
jeremydmiller added a commit that referenced this pull request Jun 3, 2026
…sions

#4610 fixed `values.Contains(p.EnumMember)` against an EnumStorage.AsString
document member by routing the case through `EnumIsOneOfWhereFragment` in
`EnumerableContains.Parse`. That worked on net9.0 but the regression test
file failed on net10.0 in CI for PR #4613 and #4615 with the same
`Writing values of 'YourEnum[]' is not supported for parameters having
NpgsqlDbType '-2147483639'` shape from the original bug.

Root cause: on net10.0 the C# compiler resolves `array.Contains(x)` to
`MemoryExtensions.Contains` (via the implicit `T[]` → `ReadOnlySpan<T>`
conversion), not `Enumerable.Contains`. The match runs through
`MemoryExtensionsContains.Parse`, not `EnumerableContains.Parse`, and the
former had the identical enum-array-as-raw-CommandParameter bug — #4610's
fix had only patched the latter.

Mirror the same fix into `MemoryExtensionsContains.Parse`. The existing
Bug_enum_asstring_array_contains regression tests now cover both parsers
because they target the user-facing shape (`values.Contains(p.Status)`) and
each .NET version routes that to a different parser.

Verified:
  - Bug_enum_asstring_array_contains: 6/6 pass on net9.0
  - Bug_enum_asstring_array_contains: 6/6 pass on net10.0
  - Full LinqTests on net10.0: 1269 passed, 0 failed

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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