Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,24 @@ public static T[] JsonbAgg<T>(this DbFunctions _, IEnumerable<T> input)
public static TimeSpan? Average(this DbFunctions _, IEnumerable<TimeSpan> input)
=> throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(Average)));

/// <summary>
/// Returns true if all non-null input values are true, otherwise false. Corresponds to the PostgreSQL <c>bool_and</c> aggregate function.
/// </summary>
/// <param name="_">The <see cref="DbFunctions" /> instance.</param>
/// <param name="input">The input values to be AND'ed together.</param>
/// <seealso href="https://www.postgresql.org/docs/current/functions-aggregate.html">PostgreSQL documentation for aggregate functions.</seealso>
public static bool BoolAnd(this DbFunctions _, IEnumerable<bool> input)
=> throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(BoolAnd)));

/// <summary>
/// Returns true if any non-null input value is true, otherwise false. Corresponds to the PostgreSQL <c>bool_or</c> aggregate function.
/// </summary>
/// <param name="_">The <see cref="DbFunctions" /> instance.</param>
/// <param name="input">The input values to be OR'ed together.</param>
/// <seealso href="https://www.postgresql.org/docs/current/functions-aggregate.html">PostgreSQL documentation for aggregate functions.</seealso>
public static bool BoolOr(this DbFunctions _, IEnumerable<bool> input)
=> throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(BoolOr)));

// See additional range aggregate functions in NpgsqlRangeDbfunctionsExtensions

#region JsonObjectAgg
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,26 @@ public NpgsqlMiscAggregateMethodTranslator(
argumentsPropagateNullability: FalseArrays[2],
returnType: method.ReturnType,
_typeMappingSource.FindMapping(method.ReturnType, isJsonb ? "jsonb" : "json"));

case nameof(NpgsqlAggregateDbFunctionsExtensions.BoolAnd):
return _sqlExpressionFactory.AggregateFunction(
"bool_and",
[sqlExpression],
source,
nullable: true,
argumentsPropagateNullability: FalseArrays[1],
returnType: sqlExpression.Type,
sqlExpression.TypeMapping);

case nameof(NpgsqlAggregateDbFunctionsExtensions.BoolOr):
return _sqlExpressionFactory.AggregateFunction(
"bool_or",
[sqlExpression],
source,
nullable: true,
argumentsPropagateNullability: FalseArrays[1],
returnType: sqlExpression.Type,
sqlExpression.TypeMapping);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,31 @@ public NpgsqlQueryableAggregateMethodTranslator(
argumentsPropagateNullability: FalseArrays[1],
sumInputType,
sumSqlExpression.TypeMapping);

case nameof(Queryable.Any)
when (methodInfo == QueryableMethods.AnyWithoutPredicate
|| methodInfo == QueryableMethods.AnyWithPredicate)
&& source.Selector is SqlExpression anySqlExpression:
return _sqlExpressionFactory.AggregateFunction(
"bool_any",
[anySqlExpression],
source,
nullable: true,
argumentsPropagateNullability: FalseArrays[1],
anySqlExpression.Type,
anySqlExpression.TypeMapping);

case nameof(Queryable.All)
when (methodInfo == QueryableMethods.All)
&& source.Selector is SqlExpression allSqlExpression:
return _sqlExpressionFactory.AggregateFunction(
"bool_all",
[allSqlExpression],
source,
nullable: true,
argumentsPropagateNullability: FalseArrays[1],
allSqlExpression.Type,
allSqlExpression.TypeMapping);
}
}

Expand Down
42 changes: 42 additions & 0 deletions test/EFCore.PG.FunctionalTests/Query/GearsOfWarQueryNpgsqlTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,48 @@ GROUP BY m."Id"
""");
}

[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public virtual async Task GroupBy_Property_Select_BoolAnd_over_Bool(bool async)
{
await AssertQueryScalar(
async,
ss => ss.Set<Mission>()
.GroupBy(o => o.CodeName)
.Select(g => EF.Functions.BoolAnd(g.Select(o => o.Id == 1))),
ss => ss.Set<Mission>()
.GroupBy(o => o.CodeName)
.Select(g => g.All(o => o.Id == 1)));

AssertSql(
"""
SELECT bool_and(m."Id" = 1)
FROM "Missions" AS m
GROUP BY m."CodeName"
""");
}

[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public virtual async Task GroupBy_Property_Select_BoolOr_over_Bool(bool async)
{
await AssertQueryScalar(
async,
ss => ss.Set<Mission>()
.GroupBy(o => o.CodeName)
.Select(g => EF.Functions.BoolOr(g.Select(o => o.Id == 1))),
ss => ss.Set<Mission>()
.GroupBy(o => o.CodeName)
.Select(g => g.Any(o => o.Id == 1)));

AssertSql(
"""
SELECT bool_or(m."Id" = 1)
FROM "Missions" AS m
GROUP BY m."CodeName"
""");
}

private void AssertSql(params string[] expected)
=> Fixture.TestSqlLoggerFactory.AssertBaseline(expected);
}