Translate bytea.Any() as octet_length > 0#3817
Translate bytea.Any() as octet_length > 0#3817georg-jung wants to merge 3 commits intonpgsql:hotfix/10.0.2from
bytea.Any() as octet_length > 0#3817Conversation
There was a problem hiding this comment.
Pull request overview
Adds translation support for predicate-less Any() on byte[] columns mapped to PostgreSQL bytea, addressing EF Core 9/10’s rewrite of byte[].Length > 0 into Any() which previously failed translation.
Changes:
- Translate
Enumerable.Any()(no predicate) overbyteato a SQL length check (octet_length(...) > 0). - Expose
EnumerableMethods.AnyWithoutPredicatefor method matching. - Add a functional test asserting SQL generation for
byteanon-emptiness checks.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| test/EFCore.PG.FunctionalTests/Query/ArrayArrayQueryTest.cs | Adds coverage asserting SQL for querying non-empty bytea. |
| src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlByteArrayMethodTranslator.cs | Implements translation of bytea.Any() to a SQL length comparison. |
| src/EFCore.PG/Internal/EnumerableMethods.cs | Adds AnyWithoutPredicate MethodInfo for identifying predicate-less Any(). |
| if (method.GetGenericMethodDefinition().Equals(EnumerableMethods.AnyWithoutPredicate)) | ||
| { | ||
| return _sqlExpressionFactory.GreaterThan( | ||
| _sqlExpressionFactory.Function( | ||
| "octet_length", | ||
| [arguments[0]], | ||
| nullable: true, | ||
| argumentsPropagateNullability: TrueArrays[1], | ||
| typeof(int)), | ||
| _sqlExpressionFactory.Constant(0)); |
There was a problem hiding this comment.
Using octet_length here is semantically fine for bytea, but the provider already translates byte[].Length on bytea via length(...) (see src/EFCore.PG/Query/Internal/NpgsqlSqlTranslatingExpressionVisitor.cs:155-165). To keep SQL generation consistent (and match pre-EF9 behavior referenced in #3816), consider using length(arguments[0]) > 0 instead of octet_length(...) > 0.
| [ConditionalFact] | ||
| public virtual async Task Bytea_Length_greater_than_zero() | ||
| { | ||
| await AssertQuery(ss => ss.Set<ArrayEntity>().Where(e => e.Bytea.Length > 0)); | ||
|
|
||
| AssertSql( | ||
| """ | ||
| SELECT s."Id", s."ArrayContainerEntityId", s."ArrayOfStringConvertedToDelimitedString", s."Byte", s."ByteArray", s."Bytea", s."EnumConvertedToInt", s."EnumConvertedToString", s."IList", s."IntArray", s."IntList", s."ListOfStringConvertedToDelimitedString", s."NonNullableText", s."NullableEnumConvertedToString", s."NullableEnumConvertedToStringWithNonNullableLambda", s."NullableIntArray", s."NullableIntList", s."NullableStringArray", s."NullableStringList", s."NullableText", s."StringArray", s."StringList", s."ValueConvertedArrayOfEnum", s."ValueConvertedListOfEnum", s."Varchar10", s."Varchar15" | ||
| FROM "SomeEntities" AS s | ||
| WHERE octet_length(s."Bytea") > 0 | ||
| """); | ||
| } |
There was a problem hiding this comment.
This test queries e.Bytea.Length > 0 but asserts SQL for octet_length(...) > 0, which depends on EF Core rewriting Length > 0 into Any() and on the provider translating that. That makes the test name/placement (under Length/Count) a bit misleading and potentially brittle if EF changes its rewrite rules. Consider expressing the intent more directly (e.g., query e.Bytea.Any() and/or rename/move the test under the Any/All region).
My naive attempt to fix #3816
I opted for
octet_lengthhere because it communicates clearly that it is about byte length, not text length.Let me know if this goes in the right direction and/or if I should change something.
Have a nice weekend!
(Note that I chose hotfix/10.0.2 as target branch; mostly because executing the test suite with an unreleased preview sdk was somewhat hard to setup)