Skip to content

Commit 253d4ae

Browse files
brianpursleyroji
andcommitted
Fix a bug where the result of a function that returns an array is not able to be accessed by array index. (#3720)
Fixes #3383 Co-authored-by: Shay Rojansky <roji@roji.org> (cherry picked from commit 5604097)
1 parent b452d2b commit 253d4ae

3 files changed

Lines changed: 46 additions & 14 deletions

File tree

src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlArrayMethodTranslator.cs

Lines changed: 21 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -79,21 +79,28 @@ public NpgsqlArrayMethodTranslator(NpgsqlSqlExpressionFactory sqlExpressionFacto
7979
// During preprocessing, ArrayIndex and List[] get normalized to ElementAt; so we handle indexing into array/list here
8080
if (method.IsClosedFormOf(Enumerable_ElementAt))
8181
{
82-
// Indexing over bytea is special, we have to use function rather than subscript
83-
if (arguments[0].TypeMapping is NpgsqlByteArrayTypeMapping)
82+
return arguments[0].TypeMapping switch
8483
{
85-
return _sqlExpressionFactory.Function(
86-
"get_byte",
87-
[arguments[0], arguments[1]],
88-
nullable: true,
89-
argumentsPropagateNullability: TrueArrays[2],
90-
typeof(byte));
91-
}
92-
93-
// Try translating indexing inside JSON column
94-
// Note that Length over PG arrays (not within JSON) gets translated by QueryableMethodTranslatingEV, since arrays are primitive
95-
// collections
96-
return _jsonPocoTranslator.TranslateMemberAccess(arguments[0], arguments[1], method.ReturnType);
84+
// Indexing over bytea is special, we have to use function rather than subscript
85+
NpgsqlByteArrayTypeMapping
86+
=> _sqlExpressionFactory.Function(
87+
"get_byte",
88+
[arguments[0], arguments[1]],
89+
nullable: true,
90+
argumentsPropagateNullability: TrueArrays[2],
91+
typeof(byte)),
92+
93+
NpgsqlArrayTypeMapping typeMapping
94+
=> _sqlExpressionFactory.ArrayIndex(
95+
arguments[0],
96+
_sqlExpressionFactory.GenerateOneBasedIndexExpression(arguments[1]),
97+
nullable: true),
98+
99+
// Try translating indexing inside JSON column
100+
// Note that Length over PG arrays (not within JSON) gets translated by QueryableMethodTranslatingEV, since arrays are primitive
101+
// collections
102+
_ => _jsonPocoTranslator.TranslateMemberAccess(arguments[0], arguments[1], method.ReturnType)
103+
};
97104
}
98105

99106
if (method.IsClosedFormOf(Enumerable_SequenceEqual)

src/EFCore.PG/Query/Internal/NpgsqlQuerySqlGenerator.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1485,6 +1485,9 @@ protected override bool RequiresParentheses(SqlExpression outerExpression, SqlEx
14851485
return true;
14861486
}
14871487

1488+
// PG requires function calls to be wrapped in parentheses before indexing on the returned array:
1489+
// (string_to_array(c."ContactName", ' '))[1]
1490+
case SqlFunctionExpression when outerExpression is PgArrayIndexExpression:
14881491
case PgUnknownBinaryExpression:
14891492
return true;
14901493

test/EFCore.PG.FunctionalTests/Query/NorthwindDbFunctionsQueryNpgsqlTest.cs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -228,6 +228,28 @@ WHERE string_to_array(c."ContactName", ' ', 'Maria') = ARRAY[NULL,'Anders']::tex
228228
""");
229229
}
230230

231+
[Fact]
232+
public void StringToArray_with_index()
233+
{
234+
using var context = CreateContext();
235+
var count = context.Customers
236+
.Select(c => EF.Functions.StringToArray(c.ContactName, " ")[0])
237+
.Distinct()
238+
.Count(c => c == "Maria");
239+
240+
Assert.Equal(1, count);
241+
242+
AssertSql(
243+
"""
244+
SELECT count(*)::int
245+
FROM (
246+
SELECT DISTINCT (string_to_array(c."ContactName", ' '))[1] AS c
247+
FROM "Customers" AS c
248+
WHERE (string_to_array(c."ContactName", ' '))[1] = 'Maria'
249+
) AS c0
250+
""");
251+
}
252+
231253
[Fact]
232254
public void ToDate()
233255
{

0 commit comments

Comments
 (0)