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

Query: Adds LINQ support for Multi-key Group By translation #4857

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
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
126 changes: 101 additions & 25 deletions Microsoft.Azure.Cosmos/src/Linq/ExpressionToSQL.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ namespace Microsoft.Azure.Cosmos.Linq
using System.Linq.Expressions;
using System.Reflection;
using System.Text.RegularExpressions;
using Microsoft.Azure.Cosmos.CosmosElements;
using Microsoft.Azure.Cosmos.CosmosElements;
using Microsoft.Azure.Cosmos.Query.Core.ClientDistributionPlan.Cql;
using Microsoft.Azure.Cosmos.Serialization.HybridRow;
using Microsoft.Azure.Cosmos.Serializer;
using Microsoft.Azure.Cosmos.Spatial;
Expand Down Expand Up @@ -1799,33 +1800,108 @@ private static Collection VisitGroupBy(Type returnElementType, ReadOnlyCollectio
{
context.PushParameter(par, context.CurrentSubqueryBinding.ShouldBeOnNewQuery);
}

// Key Selector handling

// First argument is input, second is key selector and third is value selector
LambdaExpression keySelectorLambda = Utilities.GetLambda(arguments[1]);

// Current GroupBy doesn't allow subquery, so we need to visit non subquery scalar lambda
SqlScalarExpression keySelectorFunc = ExpressionToSql.VisitNonSubqueryScalarLambda(keySelectorLambda, context);

SqlGroupByClause groupby = SqlGroupByClause.Create(keySelectorFunc);

context.CurrentQuery = context.CurrentQuery.AddGroupByClause(groupby, context);

// Create a GroupBy collection and bind the new GroupBy collection to the new parameters created from the key
Collection collection = ExpressionToSql.ConvertToCollection(keySelectorFunc);
collection.isOuter = true;
collection.Name = "GroupBy";

ParameterExpression parameterExpression = context.GenerateFreshParameter(returnElementType, keySelectorFunc.ToString(), includeSuffix: false);
Binding binding = new Binding(parameterExpression, collection.inner, isInCollection: false, isInputParameter: true);

context.CurrentQuery.GroupByParameter = new FromParameterBindings();
context.CurrentQuery.GroupByParameter.Add(binding);

// The alias for the key in the value selector lambda is the first arguemt lambda - we bound it to the parameter expression, which already has substitution
ParameterExpression valueSelectorKeyExpressionAlias = Utilities.GetLambda(arguments[2]).Parameters[0];
context.GroupByKeySubstitution.AddSubstitution(valueSelectorKeyExpressionAlias, parameterExpression/*Utilities.GetLambda(arguments[1]).Body*/);
LambdaExpression keySelectorLambda = Utilities.GetLambda(arguments[1]);

//// Current GroupBy doesn't allow subquery, so we need to visit non subquery scalar lambda
//SqlScalarExpression keySelectorFunc = ExpressionToSql.VisitNonSubqueryScalarLambda(keySelectorLambda, context);
Collection collection = new Collection("Group By");

switch (keySelectorLambda.Body.NodeType)
{
case ExpressionType.Constant:
case ExpressionType.Parameter:
case ExpressionType.Call:
{
//Current GroupBy doesn't allow subquery, so we need to visit non subquery scalar lambda
SqlScalarExpression keySelectorFunc = ExpressionToSql.VisitNonSubqueryScalarLambda(keySelectorLambda, context);

// The group by clause don't need to handle the value selector, so adding the clause to the uery now.
SqlGroupByClause groupby = SqlGroupByClause.Create(keySelectorFunc);

context.CurrentQuery = context.CurrentQuery.AddGroupByClause(groupby, context);

// Create a GroupBy collection and bind the new GroupBy collection to the new parameters created from the key
collection = ExpressionToSql.ConvertToCollection(keySelectorFunc);
collection.isOuter = true;
collection.Name = "GroupBy";

ParameterExpression parameterExpression = context.GenerateFreshParameter(returnElementType, keySelectorFunc.ToString(), includeSuffix: false);
Binding binding = new Binding(parameterExpression, collection.inner, isInCollection: false, isInputParameter: true);

context.CurrentQuery.GroupByParameter = new FromParameterBindings();
context.CurrentQuery.GroupByParameter.Add(binding);
// The alias for the key in the value selector lambda is the first arguemt lambda - we bound it to the parameter expression, which already has substitution
ParameterExpression valueSelectorKeyExpressionAlias = Utilities.GetLambda(arguments[2]).Parameters[0];
context.GroupByKeySubstitution.AddSubstitution(valueSelectorKeyExpressionAlias, parameterExpression/*Utilities.GetLambda(arguments[1]).Body*/);

break;
}
case ExpressionType.New:
{
// GroupBy(k =>
//new
//{
// key1 = 123,
// key2 = "abc"
//} /*keySelector*/,
// (key, values) => key.key1
NewExpression newExpression = (NewExpression)(keySelectorLambda.Body);

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

// Step 1: visit all of the member expressions to bind them to the current input
// Step 2: create group by clause with all the member expressions
// Step ??: Push bindings?
// Step 2: create a look up dict of member name to ParamExpression(MemberExpression.ToString)
// Step 4: in value selector clause, during look up check for aliasing. Override the alias with the value clause alias if its multivalue
// Get the list of items and the bindings
ReadOnlyCollection<Expression> newExpressionArguments = newExpression.Arguments;

List<SqlScalarExpression> keySelectorFunctions = new List<SqlScalarExpression>();

for (int i = 0; i < newExpressionArguments.Count; i++)
{
//Current GroupBy doesn't allow subquery, so we need to visit non subquery scalara
SqlScalarExpression keySelectorFunc = ExpressionToSql.VisitNonSubqueryScalarExpression(newExpressionArguments[i], context);
keySelectorFunctions.Add(keySelectorFunc);
}

// The group by clause don't need to handle the value selector, so adding the clause to the uery now.
SqlGroupByClause groupby = SqlGroupByClause.Create(keySelectorFunctions.ToImmutableArray());

context.CurrentQuery = context.CurrentQuery.AddGroupByClause(groupby, context);

// Handle the expression aliasing
ReadOnlyCollection<MemberInfo> newExpressionMembers = newExpression.Members;
for (int i = 0; i < newExpressionMembers.Count; i++)
{
MemberInfo member = newExpressionMembers[i];
string memberName = member.GetMemberName(context);
SqlIdentifier alias = SqlIdentifier.Create(memberName);

// TODO: add these alias to a dict
}

// Translate the body of the value selector lambda
//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, keySelectorLambda.Body.NodeType));
}

// Value Selector Handingling

// Translate the body of the value selector lambda
Expression valueSelectorExpression = Utilities.GetLambda(arguments[2]).Body;

// The value selector function needs to be either a MethodCall or an AnonymousType
Expand Down
Loading