Skip to content
Merged
Show file tree
Hide file tree
Changes from 13 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
216 changes: 179 additions & 37 deletions Microsoft.Azure.Cosmos/src/Linq/ExpressionToSQL.cs
Original file line number Diff line number Diff line change
Expand Up @@ -884,6 +884,29 @@ private static SqlObjectProperty[] CreateInitializers(ReadOnlyCollection<Express
result[i] = prop;
}

return result;
}

private static SqlSelectItem[] CreateSelectItems(ReadOnlyCollection<Expression> arguments, ReadOnlyCollection<MemberInfo> members, TranslationContext context)
{
if (arguments.Count != members.Count)
{
throw new InvalidOperationException("Expected same number of arguments as members");
}

SqlSelectItem[] result = new SqlSelectItem[arguments.Count];
for (int i = 0; i < arguments.Count; i++)
{
Expression arg = arguments[i];
MemberInfo member = members[i];
SqlScalarExpression selectExpression = ExpressionToSql.VisitScalarExpression(arg, context);

string memberName = member.GetMemberName(context);
SqlIdentifier alias = SqlIdentifier.Create(memberName);
SqlSelectItem prop = SqlSelectItem.Create(selectExpression, alias);
result[i] = prop;
}

return result;
}

Expand Down Expand Up @@ -1314,6 +1337,80 @@ private static Collection VisitMethodCall(MethodCallExpression inputExpression,
context.PopMethod();
return result;
}

/// <summary>
/// Visit a method call, construct the corresponding query and return the select clause for the aggregate function.
/// At ExpressionToSql point only LINQ method calls are allowed.
/// These methods are static extension methods of IQueryable or IEnumerable.
/// </summary>
/// <param name="inputExpression">Method to translate.</param>
/// <param name="context">Query translation context.</param>
private static SqlSelectClause VisitGroupByAggregateMethodCall(MethodCallExpression inputExpression, TranslationContext context)
{
context.PushMethod(inputExpression);

Type declaringType = inputExpression.Method.DeclaringType;
if ((declaringType != typeof(Queryable) && declaringType != typeof(Enumerable))
|| !inputExpression.Method.IsStatic)
{
throw new DocumentQueryException(string.Format(CultureInfo.CurrentCulture, ClientResources.OnlyLINQMethodsAreSupported, inputExpression.Method.Name));
}

if (inputExpression.Object != null)
{
throw new DocumentQueryException(ClientResources.ExpectedMethodCallsMethods);
}

Expression inputCollection = inputExpression.Arguments[0]; // all these methods are static extension methods, so argument[0] is the collection

Collection collection = ExpressionToSql.Translate(inputCollection, context);
context.PushCollection(collection);

bool shouldBeOnNewQuery = context.CurrentQuery.ShouldBeOnNewQuery(inputExpression.Method.Name, inputExpression.Arguments.Count);
context.PushSubqueryBinding(shouldBeOnNewQuery);

if (context.LastExpressionIsGroupBy)
{
throw new DocumentQueryException(string.Format(CultureInfo.CurrentCulture, "Group By cannot be followed by other methods"));
}

SqlSelectClause select;
switch (inputExpression.Method.Name)
{
case LinqMethods.Average:
{
select = ExpressionToSql.VisitAggregateFunction(inputExpression.Arguments, context, SqlFunctionCallScalarExpression.Names.Avg);
break;
}
case LinqMethods.Count:
{
select = ExpressionToSql.VisitCount(inputExpression.Arguments, context);
break;
}
case LinqMethods.Max:
{
select = ExpressionToSql.VisitAggregateFunction(inputExpression.Arguments, context, SqlFunctionCallScalarExpression.Names.Max);
break;
}
case LinqMethods.Min:
{
select = ExpressionToSql.VisitAggregateFunction(inputExpression.Arguments, context, SqlFunctionCallScalarExpression.Names.Min);
break;
}
case LinqMethods.Sum:
{
select = ExpressionToSql.VisitAggregateFunction(inputExpression.Arguments, context, SqlFunctionCallScalarExpression.Names.Sum);
break;
}
default:
throw new DocumentQueryException(string.Format(CultureInfo.CurrentCulture, ClientResources.MethodNotSupported, inputExpression.Method.Name));
}

context.PopSubqueryBinding();
context.PopCollection();
context.PopMethod();
return select;
}

/// <summary>
/// Determine if an expression should be translated to a subquery.
Expand Down Expand Up @@ -1735,48 +1832,93 @@ private static Collection VisitGroupBy(Type returnElementType, ReadOnlyCollectio
switch (valueSelectorExpression.NodeType)
{
case ExpressionType.Constant:
{
ConstantExpression constantExpression = (ConstantExpression)valueSelectorExpression;
SqlScalarExpression selectExpression = ExpressionToSql.VisitConstant(constantExpression, context);

SqlSelectSpec sqlSpec = SqlSelectValueSpec.Create(selectExpression);
SqlSelectClause select = SqlSelectClause.Create(sqlSpec, null);
context.CurrentQuery = context.CurrentQuery.AddSelectClause(select, context);
break;
}
{
ConstantExpression constantExpression = (ConstantExpression)valueSelectorExpression;
SqlScalarExpression selectExpression = ExpressionToSql.VisitConstant(constantExpression, context);
SqlSelectSpec sqlSpec = SqlSelectValueSpec.Create(selectExpression);
SqlSelectClause select = SqlSelectClause.Create(sqlSpec, null);
context.CurrentQuery = context.CurrentQuery.AddSelectClause(select, context);
break;
}
case ExpressionType.Parameter:
{
ParameterExpression parameterValueExpression = (ParameterExpression)valueSelectorExpression;
SqlScalarExpression selectExpression = ExpressionToSql.VisitParameter(parameterValueExpression, context);
{
ParameterExpression parameterValueExpression = (ParameterExpression)valueSelectorExpression;
SqlScalarExpression selectExpression = ExpressionToSql.VisitParameter(parameterValueExpression, context);

SqlSelectSpec sqlSpec = SqlSelectValueSpec.Create(selectExpression);
SqlSelectClause select = SqlSelectClause.Create(sqlSpec, null);
context.CurrentQuery = context.CurrentQuery.AddSelectClause(select, context);
break;
}
SqlSelectSpec sqlSpec = SqlSelectValueSpec.Create(selectExpression);
SqlSelectClause select = SqlSelectClause.Create(sqlSpec, null);
context.CurrentQuery = context.CurrentQuery.AddSelectClause(select, context);
break;
}
case ExpressionType.Call:
{
// Single Value Selector
MethodCallExpression methodCallExpression = (MethodCallExpression)valueSelectorExpression;
switch (methodCallExpression.Method.Name)
{
case LinqMethods.Max:
case LinqMethods.Min:
case LinqMethods.Average:
case LinqMethods.Count:
case LinqMethods.Sum:
ExpressionToSql.VisitMethodCall(methodCallExpression, context);
break;
default:
throw new DocumentQueryException(string.Format(CultureInfo.CurrentCulture, ClientResources.MethodNotSupported, methodCallExpression.Method.Name));
}
// Single Value Selector
MethodCallExpression methodCallExpression = (MethodCallExpression)valueSelectorExpression;
SqlSelectClause select = ExpressionToSql.VisitGroupByAggregateMethodCall(methodCallExpression, context);
context.CurrentQuery = context.CurrentQuery.AddSelectClause(select, context);
break;
}
case ExpressionType.New:
{
// Add select item clause at the end of this method
NewExpression newExpression = (NewExpression)valueSelectorExpression;

break;
}
case ExpressionType.New:
// TODO: Multi Value Selector
throw new DocumentQueryException(string.Format(CultureInfo.CurrentCulture, ClientResources.ExpressionTypeIsNotSupported, ExpressionType.New));

if (newExpression.Members == null)
{
throw new DocumentQueryException(ClientResources.ConstructorInvocationNotSupported);
}

// Get the list of items and the bindings
ReadOnlyCollection<Expression> newExpressionArguments = newExpression.Arguments;
ReadOnlyCollection<MemberInfo> newExpressionMembers = newExpression.Members;

SqlSelectItem[] selectItems = new SqlSelectItem[newExpressionArguments.Count];
for (int i = 0; i < newExpressionArguments.Count; i++)
{
MemberInfo member = newExpressionMembers[i];
string memberName = member.GetMemberName(context);
SqlIdentifier alias = SqlIdentifier.Create(memberName);

Expression arg = newExpressionArguments[i];
switch (arg.NodeType)
{
case ExpressionType.Constant:
{
SqlScalarExpression selectExpression = ExpressionToSql.VisitConstant((ConstantExpression)arg, context);

SqlSelectItem prop = SqlSelectItem.Create(selectExpression, alias);
selectItems[i] = prop;
break;
}
case ExpressionType.Parameter:
{
SqlScalarExpression selectExpression = ExpressionToSql.VisitParameter((ParameterExpression)arg, context);

SqlSelectItem prop = SqlSelectItem.Create(selectExpression, alias);
selectItems[i] = prop;
break;
}
case ExpressionType.Call:
{
SqlSelectClause selectClause = ExpressionToSql.VisitGroupByAggregateMethodCall((MethodCallExpression)arg, context);
SqlScalarExpression selectExpression = ((SqlSelectValueSpec)selectClause.SelectSpec).Expression;

SqlSelectItem prop = SqlSelectItem.Create(selectExpression, alias);
selectItems[i] = prop;
break;
}
default:
throw new DocumentQueryException(string.Format(CultureInfo.CurrentCulture, ClientResources.ExpressionTypeIsNotSupported, arg.NodeType));
}
}

SqlSelectListSpec sqlSpec = SqlSelectListSpec.Create(selectItems);
SqlSelectClause select = SqlSelectClause.Create(sqlSpec, null);
context.CurrentQuery = context.CurrentQuery.AddSelectClause(select, context);

break;
}
default:
throw new DocumentQueryException(string.Format(CultureInfo.CurrentCulture, ClientResources.ExpressionTypeIsNotSupported, valueSelectorExpression.NodeType));
}
Expand Down
Loading