You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
list.Contains(valueSetColumn) throws InvalidCastException in Npgsql #46
The list is bound as a jagged array (text[][]), which Npgsql can't write. It fails loudly. Either translate it to something Npgsql can bind, or refuse it in translation with a message that says what to write instead.
Found during the 9.0.0 final review. The exact repro needs pinning down before the fix.
Repro pinned down (live PostgreSQL 17, Npgsql EF 10.0.0)
Only a captured collection fails. Constant and inline collections already translated and answered correctly.
How the collection reaches the query
SQL before
Result before
captured List<TSet> / TSet[] / IEnumerable<TSet>
r."Tags" = ANY (@wanted)
InvalidCastException: Writing values of 'System.String[][]' is not supported…
EF.Parameter(list)
r."Tags" = ANY (@wanted)
same InvalidCastException
constant list, EF.Constant(list)
r."Tags" IN (ARRAY['a','b']::text[], ARRAY['c']::text[])
correct
inline new[] { a, b }
r."Tags" IN (@p, @p0)
correct
captured HashSet<TSet> (resolves to HashSet.Contains)
r."Tags" = ANY (@hash)
same InvalidCastException
Every family fails the same way in the captured case: StringSet, StringSet? (with array_position(@p, NULL) for the null arm), GuidSet, Int16Set, Int32Set, Int64Set, DecimalSet, DateSet, TimeSet, DateTimeSet, DateTimeOffsetSet, LocalDateSet, InstantSet, YearMonthSet (LocalDate[][]), and the wrapper arities (StringSet<TestKey> → String[][], Int32Set<SmallCode> → Int32[][]). Any/Count over the captured collection (unnest(@p)) fail the same way.
Cause: Npgsql translates a parameterized collection as one array parameter. A value set is already a PostgreSQL array, so the collection becomes an array of arrays. PostgreSQL has no such type (its multi-dimensional arrays are not jagged), so Npgsql cannot write it.
Decision: translate
EF Core 10's ParameterTranslationMode.MultipleParameters sends each set as its own parameter, through the set's own mapping. Contains becomes IN (@p1, @p2, …), and Any/Count read the collection from VALUES (@p1), (@p2). Each comparison is array equality between two canonical arrays, which is set equality, so the answer equals the in-memory one. It is the same SQL the constant and inline forms already produce. The query interceptor switches only a collection whose element type is a registered value set and whose effective mode is Parameter, so a context configured for Constant or MultipleParameters keeps its mode.
EF.Parameter(list) explicitly asks for the one array parameter that can't exist, so it is refused in translation with a message that says to remove it. A server-side Union stays refused under IN by ValueSetSqlGuard, as it already was under = ANY.
Side finding, filed separately: a captured List<Int32Range> fails too, with 42809: op ANY/ALL (array) requires array on right side.
Oracle check (main at 3ecfa10):no oracle on main catches this (pending). I reverted #75's value-set branch on 3ecfa10 (hand-reverted, because #77 has since changed the same interceptor) and ran every class in oracles.json, including the live ones. All of them stayed green. The fix's own regression tests did fail (SetCollectionContainsTests: 8 methods, InvalidCastException: Writing values of 'System.String[][]'), which shows the revert put the defect back. Gap: the EF Core Operand position axis in axes.md has no captured collection value (wanted.Contains(col), Any/Count over a List<T>). DifferentialFuzzParityTests binds each operand only as a column, a constant or a parameter. Claim E36 is held only by the regression tests SetCollectionContainsTests and SetCollectionTranslationTests. The missing pair is captured collection × every set type, and route B steps 2 and 3 don't vary it.
Update: would be caught by CapturedCollectionProbeTests (#138, the probe sweep for the gap named above). Its seed set-collection-sent-as-one-parameter is this defect, and dotnet run tools/oracles.cs seeds reports it CAUGHT: 5,760 failing lines, every value set type through every collection form, NotSupportedException: Writing is not supported for jagged collections. Labelled found-by/oracle retroactively.
Activity
CaffeinatedCoder commentedon Oct 17, 2026
Repro pinned down (live PostgreSQL 17, Npgsql EF 10.0.0)
Only a captured collection fails. Constant and inline collections already translated and answered correctly.
List<TSet>/TSet[]/IEnumerable<TSet>r."Tags" = ANY (@wanted)InvalidCastException: Writing values of 'System.String[][]' is not supported…EF.Parameter(list)r."Tags" = ANY (@wanted)InvalidCastExceptionEF.Constant(list)r."Tags" IN (ARRAY['a','b']::text[], ARRAY['c']::text[])new[] { a, b }r."Tags" IN (@p, @p0)HashSet<TSet>(resolves toHashSet.Contains)r."Tags" = ANY (@hash)InvalidCastExceptionEvery family fails the same way in the captured case:
StringSet,StringSet?(witharray_position(@p, NULL)for the null arm),GuidSet,Int16Set,Int32Set,Int64Set,DecimalSet,DateSet,TimeSet,DateTimeSet,DateTimeOffsetSet,LocalDateSet,InstantSet,YearMonthSet(LocalDate[][]), and the wrapper arities (StringSet<TestKey>→String[][],Int32Set<SmallCode>→Int32[][]).Any/Countover the captured collection (unnest(@p)) fail the same way.Cause: Npgsql translates a parameterized collection as one array parameter. A value set is already a PostgreSQL array, so the collection becomes an array of arrays. PostgreSQL has no such type (its multi-dimensional arrays are not jagged), so Npgsql cannot write it.
Decision: translate
EF Core 10's
ParameterTranslationMode.MultipleParameterssends each set as its own parameter, through the set's own mapping.ContainsbecomesIN (@p1, @p2, …), andAny/Countread the collection fromVALUES (@p1), (@p2). Each comparison is array equality between two canonical arrays, which is set equality, so the answer equals the in-memory one. It is the same SQL the constant and inline forms already produce. The query interceptor switches only a collection whose element type is a registered value set and whose effective mode isParameter, so a context configured forConstantorMultipleParameterskeeps its mode.EF.Parameter(list)explicitly asks for the one array parameter that can't exist, so it is refused in translation with a message that says to remove it. A server-sideUnionstays refused underINbyValueSetSqlGuard, as it already was under= ANY.Side finding, filed separately: a captured
List<Int32Range>fails too, with42809: op ANY/ALL (array) requires array on right side.feat: translate Contains over a captured collection of value sets
CaffeinatedCoder commentedon Oct 17, 2026
Oracle check (main at 3ecfa10): no oracle on main catches this (pending). I reverted #75's value-set branch on 3ecfa10 (hand-reverted, because #77 has since changed the same interceptor) and ran every class in
oracles.json, including the live ones. All of them stayed green. The fix's own regression tests did fail (SetCollectionContainsTests: 8 methods,InvalidCastException: Writing values of 'System.String[][]'), which shows the revert put the defect back.Gap: the EF Core Operand position axis in
axes.mdhas no captured collection value (wanted.Contains(col),Any/Countover aList<T>).DifferentialFuzzParityTestsbinds each operand only as a column, a constant or a parameter. Claim E36 is held only by the regression testsSetCollectionContainsTestsandSetCollectionTranslationTests. The missing pair is captured collection × every set type, and route B steps 2 and 3 don't vary it.CaffeinatedCoder commentedon Oct 17, 2026
Update: would be caught by
CapturedCollectionProbeTests(#138, the probe sweep for the gap named above). Its seedset-collection-sent-as-one-parameteris this defect, anddotnet run tools/oracles.cs seedsreports it CAUGHT: 5,760 failing lines, every value set type through every collection form,NotSupportedException: Writing is not supported for jagged collections. Labelledfound-by/oracleretroactively.test: captured collection probe sweep over every range and set type