Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enable LINQ ThenBy operator after OrderBy #801

Merged
merged 11 commits into from
Oct 8, 2019
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,22 @@ static TypeCheckFunctions()
{
TypeCheckFunctionsDefinitions = new Dictionary<string, BuiltinFunctionVisitor>();

TypeCheckFunctionsDefinitions.Add("IsArray",
new SqlBuiltinFunctionVisitor("IS_ARRAY",
true,
new List<Type[]>()
{
new Type[]{typeof(object)},
}));

TypeCheckFunctionsDefinitions.Add("IsBool",
new SqlBuiltinFunctionVisitor("IS_BOOL",
true,
new List<Type[]>()
{
new Type[]{typeof(object)},
}));

TypeCheckFunctionsDefinitions.Add("IsDefined",
new SqlBuiltinFunctionVisitor("IS_DEFINED",
true,
Expand All @@ -34,13 +50,37 @@ static TypeCheckFunctions()
new Type[]{typeof(object)},
}));

TypeCheckFunctionsDefinitions.Add("IsNumber",
new SqlBuiltinFunctionVisitor("IS_NUMBER",
true,
new List<Type[]>()
{
new Type[]{typeof(object)},
}));

TypeCheckFunctionsDefinitions.Add("IsObject",
new SqlBuiltinFunctionVisitor("IS_OBJECT",
true,
new List<Type[]>()
{
new Type[]{typeof(object)},
}));

TypeCheckFunctionsDefinitions.Add("IsPrimitive",
new SqlBuiltinFunctionVisitor("IS_PRIMITIVE",
true,
new List<Type[]>()
{
new Type[]{typeof(object)},
}));

TypeCheckFunctionsDefinitions.Add("IsString",
new SqlBuiltinFunctionVisitor("IS_STRING",
true,
new List<Type[]>()
{
new Type[]{typeof(object)},
}));
}

public static SqlScalarExpression Visit(MethodCallExpression methodCallExpression, TranslationContext context)
Expand Down
95 changes: 95 additions & 0 deletions Microsoft.Azure.Cosmos/src/Linq/CosmosLinqExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,44 @@ namespace Microsoft.Azure.Cosmos.Linq
/// </summary>
public static class CosmosLinqExtensions
{
/// <summary>
/// Determines if a certain property is of array JSON type.
/// This method is to be used in LINQ expressions only and will be evaluated on server.
/// There's no implementation provided in the client library.
/// </summary>
/// <param name="obj"></param>
/// <returns>Returns true if this property is an array otherwise returns false.</returns>
/// <example>
/// <code>
/// <![CDATA[
/// var isArrayQuery = documents.Where(document => document.Names.IsArray());
/// ]]>
/// </code>
/// </example>s>
public static bool IsArray(this object obj)
khdang marked this conversation as resolved.
Show resolved Hide resolved
{
throw new NotImplementedException();
khdang marked this conversation as resolved.
Show resolved Hide resolved
}

/// <summary>
/// Determines if a certain property is of boolean JSON type.
/// This method is to be used in LINQ expressions only and will be evaluated on server.
/// There's no implementation provided in the client library.
/// </summary>
/// <param name="obj"></param>
/// <returns>Returns true if this property is a boolean otherwise returns false.</returns>
/// <example>
/// <code>
/// <![CDATA[
/// var isBoolQuery = documents.Where(document => document.Name.IsBool());
/// ]]>
/// </code>
/// </example>s>
public static bool IsBool(this object obj)
{
throw new NotImplementedException();
}

/// <summary>
/// Determines if a certain property is defined or not.
/// This method is to be used in LINQ expressions only and will be evaluated on server.
Expand Down Expand Up @@ -56,6 +94,44 @@ public static bool IsNull(this object obj)
throw new NotImplementedException(ClientResources.TypeCheckExtensionFunctionsNotImplemented);
}

/// <summary>
/// Determines if a certain property is of number JSON type.
/// This method is to be used in LINQ expressions only and will be evaluated on server.
/// There's no implementation provided in the client library.
/// </summary>
/// <param name="obj"></param>
/// <returns>Returns true if this property is a number otherwise returns false.</returns>
/// <example>
/// <code>
/// <![CDATA[
/// var isNumberQuery = documents.Where(document => document.Amount.IsNumber());
/// ]]>
/// </code>
/// </example>s>
public static bool IsNumber(this object obj)
{
throw new NotImplementedException();
}

/// <summary>
/// Determines if a certain property is of object JSON type.
/// This method is to be used in LINQ expressions only and will be evaluated on server.
/// There's no implementation provided in the client library.
/// </summary>
/// <param name="obj"></param>
/// <returns>Returns true if this property is an object otherwise returns false.</returns>
/// <example>
/// <code>
/// <![CDATA[
/// var isObjectQuery = documents.Where(document => document.Person.IsObject());
/// ]]>
/// </code>
/// </example>s>
public static bool IsObject(this object obj)
{
throw new NotImplementedException();
}

/// <summary>
/// Determines if a certain property is of primitive JSON type.
/// This method is to be used in LINQ expressions only and will be evaluated on server.
Expand All @@ -78,6 +154,25 @@ public static bool IsPrimitive(this object obj)
throw new NotImplementedException(ClientResources.TypeCheckExtensionFunctionsNotImplemented);
}

/// <summary>
/// Determines if a certain property is of string JSON type.
/// This method is to be used in LINQ expressions only and will be evaluated on server.
/// There's no implementation provided in the client library.
/// </summary>
/// <param name="obj"></param>
/// <returns>Returns true if this property is a string otherwise returns false.</returns>
/// <example>
/// <code>
/// <![CDATA[
/// var isStringQuery = documents.Where(document => document.Name.IsString());
/// ]]>
/// </code>
/// </example>s>
public static bool IsString(this object obj)
{
throw new NotImplementedException();
}

/// <summary>
/// This method generate query definition from LINQ query.
/// </summary>
Expand Down
16 changes: 16 additions & 0 deletions Microsoft.Azure.Cosmos/src/Linq/ExpressionToSQL.cs
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,9 @@ public static class LinqMethods
public const string Max = "Max";
public const string Min = "Min";
public const string OrderBy = "OrderBy";
public const string ThenBy = "ThenBy";
public const string OrderByDescending = "OrderByDescending";
public const string ThenByDescending = "ThenByDescending";
public const string Select = "Select";
public const string SelectMany = "SelectMany";
public const string Sum = "Sum";
Expand Down Expand Up @@ -1192,6 +1194,18 @@ private static Collection VisitMethodCall(MethodCallExpression inputExpression,
context.currentQuery = context.currentQuery.AddOrderByClause(orderBy, context);
break;
}
case LinqMethods.ThenBy:
{
SqlOrderbyClause thenBy = ExpressionToSql.VisitOrderBy(inputExpression.Arguments, false, context);
context.currentQuery = context.currentQuery.UpdateOrderByClause(thenBy, context);
break;
}
case LinqMethods.ThenByDescending:
{
SqlOrderbyClause thenBy = ExpressionToSql.VisitOrderBy(inputExpression.Arguments, true, context);
context.currentQuery = context.currentQuery.UpdateOrderByClause(thenBy, context);
break;
}
case LinqMethods.Skip:
{
SqlOffsetSpec offsetSpec = ExpressionToSql.VisitSkip(inputExpression.Arguments, context);
Expand Down Expand Up @@ -1344,6 +1358,8 @@ private static bool IsSubqueryScalarExpression(Expression expression, out SqlObj
case LinqMethods.Where:
case LinqMethods.OrderBy:
case LinqMethods.OrderByDescending:
case LinqMethods.ThenBy:
case LinqMethods.ThenByDescending:
case LinqMethods.Skip:
case LinqMethods.Take:
case LinqMethods.Distinct:
Expand Down
13 changes: 13 additions & 0 deletions Microsoft.Azure.Cosmos/src/Linq/QueryUnderConstruction.cs
Original file line number Diff line number Diff line change
Expand Up @@ -499,6 +499,8 @@ public bool ShouldBeOnNewQuery(string methodName, int argumentCount)
case LinqMethods.Any:
case LinqMethods.OrderBy:
case LinqMethods.OrderByDescending:
case LinqMethods.ThenBy:
case LinqMethods.ThenByDescending:
case LinqMethods.Distinct:
// New query is needed when there is already a Take or a non-distinct Select
shouldPackage = (this.topSpec != null) ||
Expand Down Expand Up @@ -578,6 +580,17 @@ public QueryUnderConstruction AddOrderByClause(SqlOrderbyClause orderBy, Transla
return result;
}

public QueryUnderConstruction UpdateOrderByClause(SqlOrderbyClause thenBy, TranslationContext context)
{
List<SqlOrderByItem> items = new List<SqlOrderByItem>(context.currentQuery.orderByClause.OrderbyItems);
khdang marked this conversation as resolved.
Show resolved Hide resolved
items.AddRange(thenBy.OrderbyItems);
context.currentQuery.orderByClause = SqlOrderbyClause.Create(items);

foreach (Binding binding in context.CurrentSubqueryBinding.TakeBindings()) context.currentQuery.AddBinding(binding);

return context.currentQuery;
}

public QueryUnderConstruction AddOffsetSpec(SqlOffsetSpec offsetSpec, TranslationContext context)
{
QueryUnderConstruction result = context.PackageCurrentQueryIfNeccessary();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,6 @@ JOIN (
<Input>
<Description><![CDATA[Select number -> Skip -> Avg]]></Description>
<Expression><![CDATA[query.Select(f => f.Int).Skip(90).Average(), Object)]]></Expression>
<ErrorMessage><![CDATA['OFFSET LIMIT' clause is not supported in subqueries.]]></ErrorMessage>
</Input>
<Output>
<SqlQuery><![CDATA[
Expand All @@ -112,13 +111,13 @@ FROM (
OFFSET 90 LIMIT 2147483647
) AS r0
]]></SqlQuery>
<ErrorMessage><![CDATA[Response status code does not indicate success: 400 Substatus: 0 Reason: (Message: {"errors":[{"severity":"Error","location":{"start":62,"end":88},"code":"SC2204","message":"'OFFSET LIMIT' clause is not supported in subqueries."}]}).]]></ErrorMessage>
</Output>
</Result>
<Result>
<Input>
<Description><![CDATA[Select number -> Skip -> Take -> Avg]]></Description>
<Expression><![CDATA[query.Select(f => f.Int).Skip(90).Take(5).Average(), Object)]]></Expression>
<ErrorMessage><![CDATA['OFFSET LIMIT' clause is not supported in subqueries.]]></ErrorMessage>
</Input>
<Output>
<SqlQuery><![CDATA[
Expand All @@ -129,13 +128,13 @@ FROM (
OFFSET 90 LIMIT 5
) AS r0
]]></SqlQuery>
<ErrorMessage><![CDATA[Response status code does not indicate success: 400 Substatus: 0 Reason: (Message: {"errors":[{"severity":"Error","location":{"start":62,"end":79},"code":"SC2204","message":"'OFFSET LIMIT' clause is not supported in subqueries."}]}).]]></ErrorMessage>
</Output>
</Result>
<Result>
<Input>
<Description><![CDATA[Skip -> Take -> Select number -> Avg]]></Description>
<Expression><![CDATA[query.Skip(5).Take(5).Select(f => f.Int).Average(), Object)]]></Expression>
<ErrorMessage><![CDATA['OFFSET LIMIT' clause is not supported in subqueries.]]></ErrorMessage>
</Input>
<Output>
<SqlQuery><![CDATA[
Expand All @@ -146,13 +145,13 @@ FROM (
OFFSET 5 LIMIT 5
) AS r0
]]></SqlQuery>
<ErrorMessage><![CDATA[Response status code does not indicate success: 400 Substatus: 0 Reason: (Message: {"errors":[{"severity":"Error","location":{"start":62,"end":78},"code":"SC2204","message":"'OFFSET LIMIT' clause is not supported in subqueries."}]}).]]></ErrorMessage>
</Output>
</Result>
<Result>
<Input>
<Description><![CDATA[Skip -> Take -> SelectMany(Select) -> Skip -> Take -> Avg]]></Description>
<Expression><![CDATA[query.Skip(5).Take(5).SelectMany(f => f.Children.Select(c => c.Grade)).Skip(10).Take(20).Average(), Object)]]></Expression>
<ErrorMessage><![CDATA['OFFSET LIMIT' clause is not supported in subqueries.]]></ErrorMessage>
</Input>
<Output>
<SqlQuery><![CDATA[
Expand All @@ -168,13 +167,13 @@ FROM (
OFFSET 10 LIMIT 20
) AS r1
]]></SqlQuery>
<ErrorMessage><![CDATA[Response status code does not indicate success: 400 Substatus: 0 Reason: (Message: {"errors":[{"severity":"Error","location":{"start":86,"end":102},"code":"SC2204","message":"'OFFSET LIMIT' clause is not supported in subqueries."},{"severity":"Error","location":{"start":137,"end":155},"code":"SC2204","message":"'OFFSET LIMIT' clause is not supported in subqueries."}]}).]]></ErrorMessage>
</Output>
</Result>
<Result>
<Input>
<Description><![CDATA[Skip -> Take -> Select(new() -> Skip -> Take)]]></Description>
<Expression><![CDATA[query.Skip(1).Take(20).Where(f => (f.Children.Count() > 2)).Select(f => new AnonymousType(v0 = f.Children.Skip(1).Select(c => c.Grade).Average(), v1 = f.Children.Skip(1).Take(3).Select(c => c.Grade).Average(), v2 = f.Children.Take(3).Skip(1).Select(c => c.Grade).Average(), v3 = f.Records.Transactions.Select(t => t.Amount).OrderBy(a => a).Skip(10).Take(20).Average(), v4 = f.Children.Where(c => (c.Grade > 20)).OrderBy(c => c.Grade).Select(c => c.Grade).Skip(1).Average())).Skip(1).Take(10).Select(f => (((((f.v0 + f.v1) + f.v2) + f.v3) + f.v4) / 5)).Average(), Object)]]></Expression>
<ErrorMessage><![CDATA['OFFSET LIMIT' clause is not supported in subqueries.]]></ErrorMessage>
</Input>
<Output>
<SqlQuery><![CDATA[
Expand Down Expand Up @@ -254,6 +253,7 @@ FROM (
) AS r8
) AS r9
]]></SqlQuery>
<ErrorMessage><![CDATA[Response status code does not indicate success: 400 Substatus: 0 Reason: (Message: {"errors":[{"severity":"Error","location":{"start":230,"end":247},"code":"SC2204","message":"'OFFSET LIMIT' clause is not supported in subqueries."},{"severity":"Error","location":{"start":367,"end":392},"code":"SC2204","message":"'OFFSET LIMIT' clause is not supported in subqueries."},{"severity":"Error","location":{"start":522,"end":538},"code":"SC2204","message":"'OFFSET LIMIT' clause is not supported in subqueries."},{"severity":"Error","location":{"start":648,"end":653},"code":"SC2203","message":"'TOP' is not supported in subqueries."},{"severity":"Error","location":{"start":707,"end":732},"code":"SC2204","message":"'OFFSET LIMIT' clause is not supported in subqueries."},{"severity":"Error","location":{"start":879,"end":904},"code":"SC2202","message":"'ORDER BY' is not supported in subqueries."},{"severity":"Error","location":{"start":905,"end":923},"code":"SC2204","message":"'OFFSET LIMIT' clause is not supported in subqueries."},{"severity":"Error","location":{"start":1079,"end":1103},"code":"SC2202","message":"'ORDER BY' is not supported in subqueries."},{"severity":"Error","location":{"start":1104,"end":1129},"code":"SC2204","message":"'OFFSET LIMIT' clause is not supported in subqueries."},{"severity":"Error","location":{"start":1190,"end":1207},"code":"SC2204","message":"'OFFSET LIMIT' clause is not supported in subqueries."}]}).]]></ErrorMessage>
</Output>
</Result>
</Results>
Loading