Skip to content

Commit

Permalink
Cosmos: work on Skip/Take, undefined
Browse files Browse the repository at this point in the history
Continues #25765, #25701
Closes #17722
Closes #33904
  • Loading branch information
roji committed Jun 11, 2024
1 parent 3ec1b6c commit 7db029e
Show file tree
Hide file tree
Showing 20 changed files with 1,053 additions and 257 deletions.
50 changes: 50 additions & 0 deletions src/EFCore.Cosmos/Extensions/CosmosDbFunctionsExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

namespace Microsoft.EntityFrameworkCore.Cosmos.Extensions;

/// <summary>
/// Provides CLR methods that get translated to database functions when used in LINQ to Entities queries.
/// The methods on this class are accessed via <see cref="EF.Functions" />.
/// </summary>
/// <remarks>
/// See <see href="https://aka.ms/efcore-docs-database-functions">Database functions</see>, and
/// <see href="https://aka.ms/efcore-docs-cosmos">Accessing Cosmos with EF Core</see> for more information and examples.
/// </remarks>
public static class CosmosDbFunctionsExtensions
{
/// <summary>
/// Returns a boolean indicating if the property has been assigned a value. Corresponds to the Cosmos <c>IS_DEFINED</c> function.
/// </summary>
/// <remarks>
/// See <see href="https://aka.ms/efcore-docs-database-functions">Database functions</see>, and
/// <see href="https://aka.ms/efcore-docs-cosmos">Accessing Cosmos with EF Core</see>
/// for more information and examples.
/// </remarks>
/// <param name="_">The <see cref="DbFunctions" /> instance.</param>
/// <param name="expression">The expression to check.</param>
/// <seealso href="https://learn.microsoft.com/azure/cosmos-db/nosql/query/is-defined">Cosmos <c>IS_DEFINED_</c> function</seealso>
public static bool IsDefined(this DbFunctions _, object? expression)
=> throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(IsDefined)));

/// <summary>
/// Coalesces a Cosmos <c>undefined</c> value via the <c>??</c> operator.
/// </summary>
/// <remarks>
/// See <see href="https://aka.ms/efcore-docs-database-functions">Database functions</see>, and
/// <see href="https://aka.ms/efcore-docs-cosmos">Accessing Cosmos with EF Core</see>
/// for more information and examples.
/// </remarks>
/// <param name="_">The <see cref="DbFunctions" /> instance.</param>
/// <param name="expression1">
/// The expression to coalesce. This expression will be returned unless it is <c>undefined</c>, in which case
/// <paramref name="expression2" /> will be returned.
/// </param>
/// <param name="expression2">The expression to be returned if <paramref name="expression1" /> is <c>undefined</c>.</param>
/// <seealso href="https://learn.microsoft.com/azure/cosmos-db/nosql/query/ternary-coalesce-operators#coalesce-operator">Cosmos coalesce operator</seealso>
public static T CoalesceUndefined<T>(
this DbFunctions _,
T expression1,
T expression2)
=> throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(CoalesceUndefined)));
}
6 changes: 6 additions & 0 deletions src/EFCore.Cosmos/Properties/CosmosStrings.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions src/EFCore.Cosmos/Properties/CosmosStrings.resx
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,9 @@
<data name="JsonPropertyCollision" xml:space="preserve">
<value>Both properties '{property1}' and '{property2}' on entity type '{entityType}' are mapped to '{storeName}'. Map one of the properties to a different JSON property.</value>
</data>
<data name="LimitOffsetNotSupportedInSubqueries" xml:space="preserve">
<value>Skip, Take, First/FirstOrDefault and Single/SingleOrDefault aren't supported in subqueries since Cosmos doesn't support LIMIT/OFFSET in subqueries.</value>
</data>
<data name="LogExecutedCreateItem" xml:space="preserve">
<value>Executed CreateItem ({elapsed} ms, {charge} RU) ActivityId='{activityId}', Container='{container}', Id='{id}', Partition='{partitionKey}'</value>
<comment>Information CosmosEventId.ExecutedCreateItem string string string string string string?</comment>
Expand Down Expand Up @@ -243,6 +246,9 @@
<data name="OffsetRequiresLimit" xml:space="preserve">
<value>Cosmos SQL does not allow Offset without Limit. Consider specifying a 'Take' operation on the query.</value>
</data>
<data name="SingleFirstOrDefaultNotSupportedOnNonNullableQueries" xml:space="preserve">
<value>SingleOrDefault and FirstOrDefault cannot be used Cosmos SQL does not allow Offset without Limit. Consider specifying a 'Take' operation on the query.</value>
</data>
<data name="OneOfTwoValuesMustBeSet" xml:space="preserve">
<value>Exactly one of '{param1}' or '{param2}' must be set.</value>
</data>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@ public CosmosMethodCallTranslatorProvider(
new CosmosStringMethodTranslator(sqlExpressionFactory),
new CosmosRandomTranslator(sqlExpressionFactory),
new CosmosMathTranslator(sqlExpressionFactory),
new CosmosRegexTranslator(sqlExpressionFactory)
new CosmosRegexTranslator(sqlExpressionFactory),
new CosmosTypeCheckingTranslator(sqlExpressionFactory)
//new LikeTranslator(sqlExpressionFactory),
//new EnumHasFlagTranslator(sqlExpressionFactory),
//new GetValueOrDefaultTranslator(sqlExpressionFactory),
Expand Down
80 changes: 43 additions & 37 deletions src/EFCore.Cosmos/Query/Internal/CosmosQuerySqlGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,40 +21,6 @@ public class CosmosQuerySqlGenerator(ITypeMappingSource typeMappingSource) : Sql
private List<SqlParameter> _sqlParameters = null!;
private ParameterNameGenerator _parameterNameGenerator = null!;

private readonly IDictionary<ExpressionType, string> _operatorMap = new Dictionary<ExpressionType, string>
{
// Arithmetic
{ ExpressionType.Add, " + " },
{ ExpressionType.Subtract, " - " },
{ ExpressionType.Multiply, " * " },
{ ExpressionType.Divide, " / " },
{ ExpressionType.Modulo, " % " },

// Bitwise >>> (zero-fill right shift) not available in C#
{ ExpressionType.Or, " | " },
{ ExpressionType.And, " & " },
{ ExpressionType.ExclusiveOr, " ^ " },
{ ExpressionType.LeftShift, " << " },
{ ExpressionType.RightShift, " >> " },

// Logical
{ ExpressionType.AndAlso, " AND " },
{ ExpressionType.OrElse, " OR " },

// Comparison
{ ExpressionType.Equal, " = " },
{ ExpressionType.NotEqual, " != " },
{ ExpressionType.GreaterThan, " > " },
{ ExpressionType.GreaterThanOrEqual, " >= " },
{ ExpressionType.LessThan, " < " },
{ ExpressionType.LessThanOrEqual, " <= " },

// Unary
{ ExpressionType.UnaryPlus, "+" },
{ ExpressionType.Negate, "-" },
{ ExpressionType.Not, "~" }
};

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
Expand Down Expand Up @@ -116,7 +82,7 @@ protected override Expression VisitExists(ExistsExpression existsExpression)
/// </summary>
protected override Expression VisitArray(ArrayExpression arrayExpression)
{
_sqlBuilder.AppendLine("ARRAY (");
_sqlBuilder.AppendLine("ARRAY(");

using (_sqlBuilder.Indent())
{
Expand Down Expand Up @@ -457,7 +423,40 @@ protected override Expression VisitSqlBinary(SqlBinaryExpression sqlBinaryExpres
return sqlBinaryExpression;
}

var op = _operatorMap[sqlBinaryExpression.OperatorType];
var op = sqlBinaryExpression.OperatorType switch
{
// Arithmetic
ExpressionType.Add => " + ",
ExpressionType.Subtract => " - ",
ExpressionType.Multiply => " * " ,
ExpressionType.Divide => " / " ,
ExpressionType.Modulo => " % ",

// Bitwise >>> (zero-fill right shift) not available in C#
ExpressionType.Or => " | ",
ExpressionType.And => " & ",
ExpressionType.ExclusiveOr => " ^ ",
ExpressionType.LeftShift => " << ",
ExpressionType.RightShift => " >> ",

// Logical
ExpressionType.AndAlso => " AND ",
ExpressionType.OrElse => " OR ",

// Comparison
ExpressionType.Equal => " = ",
ExpressionType.NotEqual => " != ",
ExpressionType.GreaterThan => " > ",
ExpressionType.GreaterThanOrEqual => " >= ",
ExpressionType.LessThan => " < ",
ExpressionType.LessThanOrEqual => " <= ",

// Other
ExpressionType.Coalesce => " ?? ",

_ => throw new UnreachableException($"Unsupported unary OperatorType: {sqlBinaryExpression.OperatorType}")
};

_sqlBuilder.Append('(');
Visit(sqlBinaryExpression.Left);

Expand All @@ -483,7 +482,14 @@ protected override Expression VisitSqlBinary(SqlBinaryExpression sqlBinaryExpres
/// </summary>
protected override Expression VisitSqlUnary(SqlUnaryExpression sqlUnaryExpression)
{
var op = _operatorMap[sqlUnaryExpression.OperatorType];
var op = sqlUnaryExpression.OperatorType switch
{
ExpressionType.UnaryPlus => "+",
ExpressionType.Negate => "-",
ExpressionType.Not => "~",

_ => throw new UnreachableException($"Unsupported unary OperatorType: {sqlUnaryExpression.OperatorType}")
};

if (sqlUnaryExpression.OperatorType == ExpressionType.Not
&& sqlUnaryExpression.Operand.Type == typeof(bool))
Expand Down
2 changes: 1 addition & 1 deletion src/EFCore.Cosmos/Query/Internal/CosmosQueryUtils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ public static bool TryExtractBareArray(
public static bool TryExtractBareArray(
ShapedQueryExpression source,
[NotNullWhen(true)] out SqlExpression? array,
[NotNullWhen(true)] out ScalarReferenceExpression? projectedScalarReference,
[NotNullWhen(true)] out SqlExpression? projectedScalarReference,
bool ignoreOrderings = false)
{
if (source.QueryExpression is not SelectExpression
Expand Down
Loading

0 comments on commit 7db029e

Please sign in to comment.