From 8b793ef10a0a83d1f0a83c9e7c38e12450fc90ce Mon Sep 17 00:00:00 2001 From: Georg Jung Date: Fri, 24 Apr 2026 17:52:30 +0200 Subject: [PATCH 1/7] Add bytea length regression test --- .../Query/ArrayArrayQueryTest.cs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/test/EFCore.PG.FunctionalTests/Query/ArrayArrayQueryTest.cs b/test/EFCore.PG.FunctionalTests/Query/ArrayArrayQueryTest.cs index 083d4fa224..10441fa98d 100644 --- a/test/EFCore.PG.FunctionalTests/Query/ArrayArrayQueryTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/ArrayArrayQueryTest.cs @@ -679,6 +679,19 @@ WHERE cardinality(s."IntArray") = 2 """); } + [ConditionalFact] + public virtual async Task Bytea_Length_greater_than_zero() + { + await AssertQuery(ss => ss.Set().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 length(s."Bytea") > 0 +"""); + } + #endregion Length/Count #region Any/All From 403572f3c9297d5c90591ef00fc787491281bbfd Mon Sep 17 00:00:00 2001 From: Georg Jung Date: Fri, 24 Apr 2026 17:54:29 +0200 Subject: [PATCH 2/7] Translate Any over bytea --- src/EFCore.PG/Internal/EnumerableMethods.cs | 7 ++++--- .../Internal/NpgsqlByteArrayMethodTranslator.cs | 12 ++++++++++++ 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/src/EFCore.PG/Internal/EnumerableMethods.cs b/src/EFCore.PG/Internal/EnumerableMethods.cs index 3d86265345..de3799fa17 100644 --- a/src/EFCore.PG/Internal/EnumerableMethods.cs +++ b/src/EFCore.PG/Internal/EnumerableMethods.cs @@ -10,7 +10,7 @@ internal static class EnumerableMethods public static MethodInfo All { get; } - // public static MethodInfo AnyWithoutPredicate { get; } + public static MethodInfo AnyWithoutPredicate { get; } public static MethodInfo AnyWithPredicate { get; } @@ -259,8 +259,9 @@ static EnumerableMethods() nameof(Enumerable.All), 1, types => [typeof(IEnumerable<>).MakeGenericType(types[0]), typeof(Func<,>).MakeGenericType(types[0], typeof(bool))]); - // AnyWithoutPredicate = GetMethod(nameof(Enumerable.Any), 1, - // types => new[] { typeof(IEnumerable<>).MakeGenericType(types[0]) }); + AnyWithoutPredicate = GetMethod( + nameof(Enumerable.Any), 1, + types => [typeof(IEnumerable<>).MakeGenericType(types[0])]); AnyWithPredicate = GetMethod( nameof(Enumerable.Any), 1, diff --git a/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlByteArrayMethodTranslator.cs b/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlByteArrayMethodTranslator.cs index 4a6a226593..61dc892610 100644 --- a/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlByteArrayMethodTranslator.cs +++ b/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlByteArrayMethodTranslator.cs @@ -46,6 +46,18 @@ public NpgsqlByteArrayMethodTranslator(ISqlExpressionFactory sqlExpressionFactor // Note: we only translate if the array argument is a column mapped to bytea. There are various other // cases (e.g. Where(b => new byte[] { 1, 2, 3 }.Contains(b.SomeByte))) where we prefer to translate via // regular PostgreSQL array logic. + if (method.GetGenericMethodDefinition().Equals(EnumerableMethods.AnyWithoutPredicate)) + { + return _sqlExpressionFactory.GreaterThan( + _sqlExpressionFactory.Function( + "length", + [arguments[0]], + nullable: true, + argumentsPropagateNullability: TrueArrays[1], + typeof(int)), + _sqlExpressionFactory.Constant(0)); + } + if (method.GetGenericMethodDefinition().Equals(EnumerableMethods.Contains)) { var source = arguments[0]; From f9b705e35b4df225aecb4e9650588c6b27085bfd Mon Sep 17 00:00:00 2001 From: Georg Jung Date: Fri, 24 Apr 2026 18:06:30 +0200 Subject: [PATCH 3/7] Use octet_length for bytea Any --- .../Internal/NpgsqlByteArrayMethodTranslator.cs | 2 +- test/EFCore.PG.FunctionalTests/Query/ArrayArrayQueryTest.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlByteArrayMethodTranslator.cs b/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlByteArrayMethodTranslator.cs index 61dc892610..e9207953ef 100644 --- a/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlByteArrayMethodTranslator.cs +++ b/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlByteArrayMethodTranslator.cs @@ -50,7 +50,7 @@ public NpgsqlByteArrayMethodTranslator(ISqlExpressionFactory sqlExpressionFactor { return _sqlExpressionFactory.GreaterThan( _sqlExpressionFactory.Function( - "length", + "octet_length", [arguments[0]], nullable: true, argumentsPropagateNullability: TrueArrays[1], diff --git a/test/EFCore.PG.FunctionalTests/Query/ArrayArrayQueryTest.cs b/test/EFCore.PG.FunctionalTests/Query/ArrayArrayQueryTest.cs index 10441fa98d..944ee8db03 100644 --- a/test/EFCore.PG.FunctionalTests/Query/ArrayArrayQueryTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/ArrayArrayQueryTest.cs @@ -688,7 +688,7 @@ public virtual async Task Bytea_Length_greater_than_zero() """ 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 length(s."Bytea") > 0 +WHERE octet_length(s."Bytea") > 0 """); } From e995de391d0adc651bc2a8ee16b508fa320c648b Mon Sep 17 00:00:00 2001 From: Georg Jung Date: Sun, 26 Apr 2026 10:57:43 +0200 Subject: [PATCH 4/7] Revert "Use octet_length for bytea Any" This reverts commit f9b705e35b4df225aecb4e9650588c6b27085bfd. --- .../Internal/NpgsqlByteArrayMethodTranslator.cs | 2 +- test/EFCore.PG.FunctionalTests/Query/ArrayArrayQueryTest.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlByteArrayMethodTranslator.cs b/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlByteArrayMethodTranslator.cs index e9207953ef..61dc892610 100644 --- a/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlByteArrayMethodTranslator.cs +++ b/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlByteArrayMethodTranslator.cs @@ -50,7 +50,7 @@ public NpgsqlByteArrayMethodTranslator(ISqlExpressionFactory sqlExpressionFactor { return _sqlExpressionFactory.GreaterThan( _sqlExpressionFactory.Function( - "octet_length", + "length", [arguments[0]], nullable: true, argumentsPropagateNullability: TrueArrays[1], diff --git a/test/EFCore.PG.FunctionalTests/Query/ArrayArrayQueryTest.cs b/test/EFCore.PG.FunctionalTests/Query/ArrayArrayQueryTest.cs index 944ee8db03..10441fa98d 100644 --- a/test/EFCore.PG.FunctionalTests/Query/ArrayArrayQueryTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/ArrayArrayQueryTest.cs @@ -688,7 +688,7 @@ public virtual async Task Bytea_Length_greater_than_zero() """ 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 +WHERE length(s."Bytea") > 0 """); } From 05db0b8fb155b63ae5f8c98656868b757f7707d7 Mon Sep 17 00:00:00 2001 From: Georg Jung Date: Sun, 26 Apr 2026 11:26:18 +0200 Subject: [PATCH 5/7] Move test to ByteArrayTranslationsNpgsqlTest, add test for Any --- .../Query/ArrayArrayQueryTest.cs | 13 --------- .../ByteArrayTranslationsNpgsqlTest.cs | 28 +++++++++++++++++++ 2 files changed, 28 insertions(+), 13 deletions(-) diff --git a/test/EFCore.PG.FunctionalTests/Query/ArrayArrayQueryTest.cs b/test/EFCore.PG.FunctionalTests/Query/ArrayArrayQueryTest.cs index 10441fa98d..083d4fa224 100644 --- a/test/EFCore.PG.FunctionalTests/Query/ArrayArrayQueryTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/ArrayArrayQueryTest.cs @@ -679,19 +679,6 @@ WHERE cardinality(s."IntArray") = 2 """); } - [ConditionalFact] - public virtual async Task Bytea_Length_greater_than_zero() - { - await AssertQuery(ss => ss.Set().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 length(s."Bytea") > 0 -"""); - } - #endregion Length/Count #region Any/All diff --git a/test/EFCore.PG.FunctionalTests/Query/Translations/ByteArrayTranslationsNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/Translations/ByteArrayTranslationsNpgsqlTest.cs index c280c208d6..a3b4d4f899 100644 --- a/test/EFCore.PG.FunctionalTests/Query/Translations/ByteArrayTranslationsNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/Translations/ByteArrayTranslationsNpgsqlTest.cs @@ -1,3 +1,5 @@ +using Microsoft.EntityFrameworkCore.TestModels.BasicTypesModel; + namespace Microsoft.EntityFrameworkCore.Query.Translations; public class ByteArrayTranslationsNpgsqlTest : ByteArrayTranslationsTestBase @@ -98,6 +100,32 @@ public override async Task SequenceEqual() """); } + [ConditionalFact] + public virtual async Task Length_greater_than_zero() + { + await AssertQuery(ss => ss.Set().Where(e => e.ByteArray.Length > 0)); + + AssertSql( +""" +SELECT b."Id", b."Bool", b."Byte", b."ByteArray", b."DateOnly", b."DateTime", b."DateTimeOffset", b."Decimal", b."Double", b."Enum", b."FlagsEnum", b."Float", b."Guid", b."Int", b."Long", b."Short", b."String", b."TimeOnly", b."TimeSpan" +FROM "BasicTypesEntities" AS b +WHERE length(b."ByteArray") > 0 +"""); + } + + [ConditionalFact] + public virtual async Task Any() + { + await AssertQuery(ss => ss.Set().Where(e => e.ByteArray.Any())); + + AssertSql( +""" +SELECT b."Id", b."Bool", b."Byte", b."ByteArray", b."DateOnly", b."DateTime", b."DateTimeOffset", b."Decimal", b."Double", b."Enum", b."FlagsEnum", b."Float", b."Guid", b."Int", b."Long", b."Short", b."String", b."TimeOnly", b."TimeSpan" +FROM "BasicTypesEntities" AS b +WHERE length(b."ByteArray") > 0 +"""); + } + private void AssertSql(params string[] expected) => Fixture.TestSqlLoggerFactory.AssertBaseline(expected); } From eaf214fb1140d9b988c0be38a91b18dd815a1a50 Mon Sep 17 00:00:00 2001 From: Georg Jung Date: Sun, 26 Apr 2026 12:42:07 +0200 Subject: [PATCH 6/7] Move Any translation below Contains translation for comment clarity --- .../NpgsqlByteArrayMethodTranslator.cs | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlByteArrayMethodTranslator.cs b/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlByteArrayMethodTranslator.cs index 61dc892610..d16b608c45 100644 --- a/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlByteArrayMethodTranslator.cs +++ b/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlByteArrayMethodTranslator.cs @@ -46,18 +46,6 @@ public NpgsqlByteArrayMethodTranslator(ISqlExpressionFactory sqlExpressionFactor // Note: we only translate if the array argument is a column mapped to bytea. There are various other // cases (e.g. Where(b => new byte[] { 1, 2, 3 }.Contains(b.SomeByte))) where we prefer to translate via // regular PostgreSQL array logic. - if (method.GetGenericMethodDefinition().Equals(EnumerableMethods.AnyWithoutPredicate)) - { - return _sqlExpressionFactory.GreaterThan( - _sqlExpressionFactory.Function( - "length", - [arguments[0]], - nullable: true, - argumentsPropagateNullability: TrueArrays[1], - typeof(int)), - _sqlExpressionFactory.Constant(0)); - } - if (method.GetGenericMethodDefinition().Equals(EnumerableMethods.Contains)) { var source = arguments[0]; @@ -91,6 +79,18 @@ public NpgsqlByteArrayMethodTranslator(ISqlExpressionFactory sqlExpressionFactor _sqlExpressionFactory.Constant(0)); } + if (method.GetGenericMethodDefinition().Equals(EnumerableMethods.AnyWithoutPredicate)) + { + return _sqlExpressionFactory.GreaterThan( + _sqlExpressionFactory.Function( + "length", + [arguments[0]], + nullable: true, + argumentsPropagateNullability: TrueArrays[1], + typeof(int)), + _sqlExpressionFactory.Constant(0)); + } + if (method.GetGenericMethodDefinition().Equals(EnumerableMethods.FirstWithoutPredicate)) { return _sqlExpressionFactory.Convert( From e2ad4b0eba06d813100b3d3f910a13abd0f0603c Mon Sep 17 00:00:00 2001 From: Georg Jung Date: Sun, 26 Apr 2026 12:49:45 +0200 Subject: [PATCH 7/7] Remove Length > 0 test, because Any is sufficient --- .../Translations/ByteArrayTranslationsNpgsqlTest.cs | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/test/EFCore.PG.FunctionalTests/Query/Translations/ByteArrayTranslationsNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/Translations/ByteArrayTranslationsNpgsqlTest.cs index a3b4d4f899..5ccd996d0e 100644 --- a/test/EFCore.PG.FunctionalTests/Query/Translations/ByteArrayTranslationsNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/Translations/ByteArrayTranslationsNpgsqlTest.cs @@ -100,19 +100,6 @@ public override async Task SequenceEqual() """); } - [ConditionalFact] - public virtual async Task Length_greater_than_zero() - { - await AssertQuery(ss => ss.Set().Where(e => e.ByteArray.Length > 0)); - - AssertSql( -""" -SELECT b."Id", b."Bool", b."Byte", b."ByteArray", b."DateOnly", b."DateTime", b."DateTimeOffset", b."Decimal", b."Double", b."Enum", b."FlagsEnum", b."Float", b."Guid", b."Int", b."Long", b."Short", b."String", b."TimeOnly", b."TimeSpan" -FROM "BasicTypesEntities" AS b -WHERE length(b."ByteArray") > 0 -"""); - } - [ConditionalFact] public virtual async Task Any() {