@@ -15,7 +15,8 @@ namespace Microsoft.Azure.Cosmos.Linq
15
15
using System . Linq . Expressions ;
16
16
using System . Reflection ;
17
17
using System . Text . RegularExpressions ;
18
- using Microsoft . Azure . Cosmos . CosmosElements ;
18
+ using Microsoft . Azure . Cosmos . CosmosElements ;
19
+ using Microsoft . Azure . Cosmos . Query . Core . ClientDistributionPlan . Cql ;
19
20
using Microsoft . Azure . Cosmos . Serialization . HybridRow ;
20
21
using Microsoft . Azure . Cosmos . Serializer ;
21
22
using Microsoft . Azure . Cosmos . Spatial ;
@@ -1361,14 +1362,6 @@ private static SqlSelectClause VisitGroupByAggregateMethodCall(MethodCallExpress
1361
1362
throw new DocumentQueryException ( ClientResources . ExpectedMethodCallsMethods ) ;
1362
1363
}
1363
1364
1364
- Expression inputCollection = inputExpression . Arguments [ 0 ] ; // all these methods are static extension methods, so argument[0] is the collection
1365
-
1366
- Collection collection = ExpressionToSql . Translate ( inputCollection , context ) ;
1367
- context . PushCollection ( collection ) ;
1368
-
1369
- bool shouldBeOnNewQuery = context . CurrentQuery . ShouldBeOnNewQuery ( inputExpression . Method . Name , inputExpression . Arguments . Count ) ;
1370
- context . PushSubqueryBinding ( shouldBeOnNewQuery ) ;
1371
-
1372
1365
if ( context . LastExpressionIsGroupBy )
1373
1366
{
1374
1367
throw new DocumentQueryException ( string . Format ( CultureInfo . CurrentCulture , "Group By cannot be followed by other methods" ) ) ;
@@ -1406,8 +1399,6 @@ private static SqlSelectClause VisitGroupByAggregateMethodCall(MethodCallExpress
1406
1399
throw new DocumentQueryException ( string . Format ( CultureInfo . CurrentCulture , ClientResources . MethodNotSupported , inputExpression . Method . Name ) ) ;
1407
1400
}
1408
1401
1409
- context . PopSubqueryBinding ( ) ;
1410
- context . PopCollection ( ) ;
1411
1402
context . PopMethod ( ) ;
1412
1403
return select ;
1413
1404
}
@@ -1793,44 +1784,111 @@ private static Collection VisitGroupBy(Type returnElementType, ReadOnlyCollectio
1793
1784
{
1794
1785
throw new DocumentQueryException ( string . Format ( CultureInfo . CurrentCulture , ClientResources . InvalidArgumentsCount , LinqMethods . GroupBy , 3 , arguments . Count ) ) ;
1795
1786
}
1796
-
1797
- // bind the parameters in the value selector to the current input
1798
- foreach ( ParameterExpression par in Utilities . GetLambda ( arguments [ 2 ] ) . Parameters )
1799
- {
1800
- context . PushParameter ( par , context . CurrentSubqueryBinding . ShouldBeOnNewQuery ) ;
1801
- }
1802
-
1787
+
1788
+ // Key Selector handling
1803
1789
// First argument is input, second is key selector and third is value selector
1804
- LambdaExpression keySelectorLambda = Utilities . GetLambda ( arguments [ 1 ] ) ;
1805
-
1806
- // Current GroupBy doesn't allow subquery, so we need to visit non subquery scalar lambda
1807
- SqlScalarExpression keySelectorFunc = ExpressionToSql . VisitNonSubqueryScalarLambda ( keySelectorLambda , context ) ;
1808
-
1809
- SqlGroupByClause groupby = SqlGroupByClause . Create ( keySelectorFunc ) ;
1810
-
1811
- context . CurrentQuery = context . CurrentQuery . AddGroupByClause ( groupby , context ) ;
1812
-
1813
- // Create a GroupBy collection and bind the new GroupBy collection to the new parameters created from the key
1814
- Collection collection = ExpressionToSql . ConvertToCollection ( keySelectorFunc ) ;
1815
- collection . isOuter = true ;
1816
- collection . Name = "GroupBy" ;
1817
-
1818
- ParameterExpression parameterExpression = context . GenerateFreshParameter ( returnElementType , keySelectorFunc . ToString ( ) , includeSuffix : false ) ;
1819
- Binding binding = new Binding ( parameterExpression , collection . inner , isInCollection : false , isInputParameter : true ) ;
1820
-
1821
- context . CurrentQuery . GroupByParameter = new FromParameterBindings ( ) ;
1822
- context . CurrentQuery . GroupByParameter . Add ( binding ) ;
1823
-
1824
- // 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
1825
- ParameterExpression valueSelectorKeyExpressionAlias = Utilities . GetLambda ( arguments [ 2 ] ) . Parameters [ 0 ] ;
1826
- context . GroupByKeySubstitution . AddSubstitution ( valueSelectorKeyExpressionAlias , parameterExpression /*Utilities.GetLambda(arguments[1]).Body*/ ) ;
1827
-
1828
- // Translate the body of the value selector lambda
1790
+ LambdaExpression keySelectorLambda = Utilities . GetLambda ( arguments [ 1 ] ) ;
1791
+
1792
+ Collection collection = new Collection ( "Group By" ) ;
1793
+ context . CurrentQuery . GroupByParameter = new FromParameterBindings ( ) ;
1794
+
1795
+ SqlGroupByClause groupby ;
1796
+ ParameterExpression parameterExpression ;
1797
+ switch ( keySelectorLambda . Body . NodeType )
1798
+ {
1799
+ case ExpressionType . Parameter :
1800
+ case ExpressionType . Call :
1801
+ case ExpressionType . MemberAccess :
1802
+ {
1803
+ // bind the parameters in the value selector to the current input
1804
+ foreach ( ParameterExpression par in Utilities . GetLambda ( arguments [ 2 ] ) . Parameters )
1805
+ {
1806
+ context . PushParameter ( par , context . CurrentSubqueryBinding . ShouldBeOnNewQuery ) ;
1807
+ }
1808
+
1809
+ //Current GroupBy doesn't allow subquery, so we need to visit non subquery scalar lambda
1810
+ SqlScalarExpression keySelectorFunc = ExpressionToSql . VisitNonSubqueryScalarLambda ( keySelectorLambda , context ) ;
1811
+
1812
+ // The group by clause don't need to handle the value selector, so adding the clause to the uery now.
1813
+ groupby = SqlGroupByClause . Create ( keySelectorFunc ) ;
1814
+ parameterExpression = context . GenerateFreshParameter ( returnElementType , keySelectorFunc . ToString ( ) , includeSuffix : false ) ;
1815
+
1816
+ break ;
1817
+ }
1818
+ case ExpressionType . New :
1819
+ {
1820
+ // bind the parameters in the key selector to the current input - in this case, the value selector key is being substituted by the key selector
1821
+ foreach ( ParameterExpression par in Utilities . GetLambda ( arguments [ 1 ] ) . Parameters )
1822
+ {
1823
+ context . PushParameter ( par , context . CurrentSubqueryBinding . ShouldBeOnNewQuery ) ;
1824
+ }
1825
+
1826
+ NewExpression newExpression = ( NewExpression ) keySelectorLambda . Body ;
1827
+
1828
+ if ( newExpression . Members == null )
1829
+ {
1830
+ throw new DocumentQueryException ( ClientResources . ConstructorInvocationNotSupported ) ;
1831
+ }
1832
+
1833
+ ReadOnlyCollection < Expression > newExpressionArguments = newExpression . Arguments ;
1834
+
1835
+ List < SqlScalarExpression > keySelectorFunctions = new List < SqlScalarExpression > ( ) ;
1836
+ for ( int i = 0 ; i < newExpressionArguments . Count ; i ++ )
1837
+ {
1838
+ //Current GroupBy doesn't allow subquery, so we need to visit non subquery scalara
1839
+ SqlScalarExpression keySelectorFunc = ExpressionToSql . VisitNonSubqueryScalarExpression ( newExpressionArguments [ i ] , context ) ;
1840
+ keySelectorFunctions . Add ( keySelectorFunc ) ;
1841
+ }
1842
+
1843
+ groupby = SqlGroupByClause . Create ( keySelectorFunctions . ToImmutableArray ( ) ) ;
1844
+ parameterExpression = context . GenerateFreshParameter ( returnElementType , keySelectorFunctions . ToString ( ) , includeSuffix : false ) ;
1845
+
1846
+ break ;
1847
+ }
1848
+ default :
1849
+ throw new DocumentQueryException ( string . Format ( CultureInfo . CurrentCulture , ClientResources . ExpressionTypeIsNotSupported , keySelectorLambda . Body . NodeType ) ) ;
1850
+ }
1851
+
1852
+ // The group by clause don't need to handle the value selector, so adding the clause to the qery now.
1853
+ context . CurrentQuery = context . CurrentQuery . AddGroupByClause ( groupby , context ) ;
1854
+
1855
+ // Bind the alias
1856
+ Binding binding = new Binding ( parameterExpression , collection . inner , isInCollection : false , isInputParameter : true ) ;
1857
+ context . CurrentQuery . GroupByParameter . Add ( binding ) ;
1858
+
1859
+ // 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
1860
+ ParameterExpression valueSelectorKeyExpressionAlias = Utilities . GetLambda ( arguments [ 2 ] ) . Parameters [ 0 ] ;
1861
+ context . GroupByKeySubstitution . AddSubstitution ( valueSelectorKeyExpressionAlias , parameterExpression ) ;
1862
+
1863
+ // Value Selector Handingling
1864
+ // Translate the body of the value selector lambda
1829
1865
Expression valueSelectorExpression = Utilities . GetLambda ( arguments [ 2 ] ) . Body ;
1830
1866
1831
1867
// The value selector function needs to be either a MethodCall or an AnonymousType
1832
1868
switch ( valueSelectorExpression . NodeType )
1833
- {
1869
+ {
1870
+ case ExpressionType . MemberAccess :
1871
+ {
1872
+ MemberExpression memberAccessExpression = ( MemberExpression ) valueSelectorExpression ;
1873
+
1874
+ if ( memberAccessExpression . Expression . NodeType == ExpressionType . Parameter )
1875
+ {
1876
+ // Look up the object of the expression to see if it is the key
1877
+ ParameterExpression memberAccessObject = ( ParameterExpression ) memberAccessExpression . Expression ;
1878
+ Expression subst = context . GroupByKeySubstitution . Lookup ( memberAccessObject ) ;
1879
+ if ( subst != null )
1880
+ {
1881
+ // If there is a match, we construct a new Member Access expression with the substituted expression and visit it to create a select clause
1882
+ MemberExpression newMemberAccessExpression = memberAccessExpression . Update ( keySelectorLambda . Body ) ;
1883
+ SqlScalarExpression selectExpression = ExpressionToSql . VisitMemberAccess ( newMemberAccessExpression , context ) ;
1884
+
1885
+ SqlSelectSpec sqlSpec = SqlSelectValueSpec . Create ( selectExpression ) ;
1886
+ SqlSelectClause select = SqlSelectClause . Create ( sqlSpec , null ) ;
1887
+ context . CurrentQuery = context . CurrentQuery . AddSelectClause ( select , context ) ;
1888
+ }
1889
+ }
1890
+ break ;
1891
+ }
1834
1892
case ExpressionType . Constant :
1835
1893
{
1836
1894
ConstantExpression constantExpression = ( ConstantExpression ) valueSelectorExpression ;
@@ -1908,6 +1966,27 @@ private static Collection VisitGroupBy(Type returnElementType, ReadOnlyCollectio
1908
1966
selectItems [ i ] = prop ;
1909
1967
break ;
1910
1968
}
1969
+ case ExpressionType . MemberAccess :
1970
+ {
1971
+ MemberExpression memberAccessExpression = ( MemberExpression ) arg ;
1972
+
1973
+ if ( memberAccessExpression . Expression . NodeType == ExpressionType . Parameter )
1974
+ {
1975
+ // Look up the object of the expression to see if it is the key
1976
+ ParameterExpression memberAccessObject = ( ParameterExpression ) memberAccessExpression . Expression ;
1977
+ Expression subst = context . GroupByKeySubstitution . Lookup ( memberAccessObject ) ;
1978
+ if ( subst != null )
1979
+ {
1980
+ // If there is a match, we construct a new Member Access expression with the substituted expression and visit it to create a select clause
1981
+ MemberExpression newMemberAccessExpression = memberAccessExpression . Update ( keySelectorLambda . Body ) ; /*System.Linq.Expressions.Expression.Field(subst, memberAccessExpression.Member.Name);*/
1982
+ SqlScalarExpression selectExpression = ExpressionToSql . VisitMemberAccess ( newMemberAccessExpression , context ) ;
1983
+
1984
+ SqlSelectItem prop = SqlSelectItem . Create ( selectExpression , alias ) ;
1985
+ selectItems [ i ] = prop ;
1986
+ }
1987
+ }
1988
+ break ;
1989
+ }
1911
1990
default :
1912
1991
throw new DocumentQueryException ( string . Format ( CultureInfo . CurrentCulture , ClientResources . ExpressionTypeIsNotSupported , arg . NodeType ) ) ;
1913
1992
}
@@ -1921,13 +2000,34 @@ private static Collection VisitGroupBy(Type returnElementType, ReadOnlyCollectio
1921
2000
}
1922
2001
default :
1923
2002
throw new DocumentQueryException ( string . Format ( CultureInfo . CurrentCulture , ClientResources . ExpressionTypeIsNotSupported , valueSelectorExpression . NodeType ) ) ;
1924
- }
1925
-
1926
- foreach ( ParameterExpression par in Utilities . GetLambda ( arguments [ 2 ] ) . Parameters )
1927
- {
1928
- context . PopParameter ( ) ;
1929
- }
1930
-
2003
+ }
2004
+
2005
+ // Pop the correct number of items off the parameter stack
2006
+ switch ( keySelectorLambda . Body . NodeType )
2007
+ {
2008
+ case ExpressionType . Parameter :
2009
+ case ExpressionType . Call :
2010
+ case ExpressionType . MemberAccess :
2011
+ {
2012
+ foreach ( ParameterExpression param in Utilities . GetLambda ( arguments [ 2 ] ) . Parameters )
2013
+ {
2014
+ context . PopParameter ( ) ;
2015
+ }
2016
+ break ;
2017
+ }
2018
+ case ExpressionType . New :
2019
+ {
2020
+ //bind the parameters in the value selector to the current input
2021
+ foreach ( ParameterExpression param in Utilities . GetLambda ( arguments [ 1 ] ) . Parameters )
2022
+ {
2023
+ context . PopParameter ( ) ;
2024
+ }
2025
+ break ;
2026
+ }
2027
+ default :
2028
+ break ;
2029
+ }
2030
+
1931
2031
return collection ;
1932
2032
}
1933
2033
@@ -2234,7 +2334,7 @@ private static SqlInputPathCollection ConvertMemberIndexerToPath(SqlMemberIndexe
2234
2334
if ( parent == null )
2235
2335
{
2236
2336
break ;
2237
- }
2337
+ }
2238
2338
2239
2339
if ( parent is SqlPropertyRefScalarExpression sqlPropertyRefScalarExpression )
2240
2340
{
0 commit comments