diff --git a/Microsoft.Azure.Cosmos/src/Linq/BuiltinFunctions/OtherBuiltinSystemFunctions.cs b/Microsoft.Azure.Cosmos/src/Linq/BuiltinFunctions/OtherBuiltinSystemFunctions.cs index 684a68f770..09f218bd4a 100644 --- a/Microsoft.Azure.Cosmos/src/Linq/BuiltinFunctions/OtherBuiltinSystemFunctions.cs +++ b/Microsoft.Azure.Cosmos/src/Linq/BuiltinFunctions/OtherBuiltinSystemFunctions.cs @@ -21,7 +21,7 @@ public RRFVisit() true, new List() { - new Type[]{typeof(Func[])} + new Type[]{typeof(double[])} }) { } @@ -36,7 +36,27 @@ protected override SqlScalarExpression VisitImplicit(MethodCallExpression method List arguments = new List(); foreach (Expression argument in functionListExpression) { - arguments.Add(ExpressionToSql.VisitScalarExpression(argument, context)); + if (!(argument is MethodCallExpression functionCallExpression)) + { + throw new ArgumentException( + string.Format( + CultureInfo.CurrentCulture, + "Expressions of type {0} is not supported as an argument to CosmosLinqExtensions.RRF. Supported expressions are method calls to {1}.", + argument.Type, + nameof(CosmosLinqExtensions.FullTextScore))); + } + + if (functionCallExpression.Method.Name != nameof(CosmosLinqExtensions.FullTextScore)) + { + throw new ArgumentException( + string.Format( + CultureInfo.CurrentCulture, + "Method {0} is not supported as an argument to CosmosLinqExtensions.RRF. Supported methods are {1}.", + functionCallExpression.Method.Name, + nameof(CosmosLinqExtensions.FullTextScore))); + } + + arguments.Add(ExpressionToSql.VisitNonSubqueryScalarExpression(argument, context)); } return SqlFunctionCallScalarExpression.CreateBuiltin(SqlFunctionCallScalarExpression.Names.RRF, arguments.ToImmutableArray()); diff --git a/Microsoft.Azure.Cosmos/src/Linq/CosmosLinqExtensions.cs b/Microsoft.Azure.Cosmos/src/Linq/CosmosLinqExtensions.cs index 14eab9aa33..b4ba33b323 100644 --- a/Microsoft.Azure.Cosmos/src/Linq/CosmosLinqExtensions.cs +++ b/Microsoft.Azure.Cosmos/src/Linq/CosmosLinqExtensions.cs @@ -318,7 +318,7 @@ public static bool FullTextContainsAny(this object obj, params string[] searches /// ]]> /// /// - public static Func FullTextScore(this TSource obj, params string[] terms) + public static double FullTextScore(this TSource obj, params string[] terms) { throw new NotImplementedException(ClientResources.ExtensionMethodNotImplemented); } @@ -339,7 +339,7 @@ public static Func FullTextScore(this TSource obj, par /// ]]> /// /// - public static IOrderedQueryable OrderByRank(this IQueryable source, Expression> scoreFunction) + public static IOrderedQueryable OrderByRank(this IQueryable source, Expression> scoreFunction) { if (!(source is CosmosLinqQuery)) { @@ -349,7 +349,7 @@ public static IOrderedQueryable OrderByRank(this IQueryable)source.Provider.CreateQuery( Expression.Call( null, - typeof(CosmosLinqExtensions).GetMethod("OrderByRank").MakeGenericMethod(typeof(TSource)), + typeof(CosmosLinqExtensions).GetMethod("OrderByRank").MakeGenericMethod(typeof(TSource), typeof(TKey)), source.Expression, Expression.Quote(scoreFunction))); } @@ -360,7 +360,7 @@ public static IOrderedQueryable OrderByRank(this IQueryable - /// the scoring functions to combine. + /// the scoring functions to combine. Valid functions are FullTextScore and VectorDistance /// Returns the the combined scores of the scoring functions. /// /// @@ -369,7 +369,7 @@ public static IOrderedQueryable OrderByRank(this IQueryable /// /// - public static Func RRF(params Func[] scoringFunctions) + public static double RRF(params double[] scoringFunctions) { // The reason for not defining "this" keyword is because this causes undesirable serialization when call Expression.ToString() on this method throw new NotImplementedException(ClientResources.ExtensionMethodNotImplemented); diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/LinqTranslationBaselineTests.TestFullTextScoreOrderByRankFunction.xml b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/LinqTranslationBaselineTests.TestFullTextScoreOrderByRankFunction.xml index ff3cc7f370..9a9e5dc372 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/LinqTranslationBaselineTests.TestFullTextScoreOrderByRankFunction.xml +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/LinqTranslationBaselineTests.TestFullTextScoreOrderByRankFunction.xml @@ -66,52 +66,52 @@ ORDER BY RANK FullTextScore(root["StringField"], "test1", "test2", "test3")]]> - (doc.StringField.FullTextScore(new [] {"test1"}) != null))]]> + (doc.StringField.FullTextScore(new [] {"test1"}) != 123))]]> +WHERE (FullTextScore(root["StringField"], "test1") != 123)]]> - (doc.StringField.FullTextScore(new [] {"test1", "test2", "test3"}) != null))]]> + (doc.StringField.FullTextScore(new [] {"test1", "test2", "test3"}) != 123))]]> +WHERE (FullTextScore(root["StringField"], "test1", "test2", "test3") != 123)]]> - (doc.StringField.FullTextScore(new [] {"test1"}) != null))]]> + (doc.StringField.FullTextScore(new [] {"test1"}) != 123))]]> +WHERE (FullTextScore(root["StringField"], "test1") != 123)]]> - (doc.StringField.FullTextScore(new [] {"test1", "test2", "test3"}) != null))]]> + (doc.StringField.FullTextScore(new [] {"test1", "test2", "test3"}) != 123))]]> +WHERE (FullTextScore(root["StringField"], "test1", "test2", "test3") != 123)]]> diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/LinqTranslationBaselineTests.TestRRFOrderByRankFunction.xml b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/LinqTranslationBaselineTests.TestRRFOrderByRankFunction.xml index ccd04e4bbb..57e4f43d6b 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/LinqTranslationBaselineTests.TestRRFOrderByRankFunction.xml +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/LinqTranslationBaselineTests.TestRRFOrderByRankFunction.xml @@ -47,27 +47,77 @@ ORDER BY RANK RRF(FullTextScore(root["StringField"], "test1"))]]> - (RRF(new [] {doc.StringField.FullTextScore(new [] {"test1"})}) != null))]]> + (RRF(new [] {doc.StringField.FullTextScore(new [] {"test1"})}) != 123))]]> +WHERE (RRF(FullTextScore(root["StringField"], "test1")) != 123)]]> - (RRF(new [] {doc.StringField.FullTextScore(new [] {"test1"}), doc.StringField2.FullTextScore(new [] {"test1", "test2", "test3"})}) != null))]]> + (RRF(new [] {doc.StringField.FullTextScore(new [] {"test1"}), doc.StringField2.FullTextScore(new [] {"test1", "test2", "test3"})}) != 123))]]> +WHERE (RRF(FullTextScore(root["StringField"], "test1"), FullTextScore(root["StringField2"], "test1", "test2", "test3")) != 123)]]> + + + + RRF(new [] {doc.StringField.FullTextScore(new [] {"test1"}), 123}))]]> + + + + + + + + + + RRF(new [] {doc.StringField.FullTextScore(new [] {"test1"}), (doc.IntField * 1)}))]]> + + + + + + + + + + RRF(new [] {doc.StringField.FullTextScore(new [] {"test1"}), Convert(doc.StringField2.Length, Double)}))]]> + + + + + + + + + + RRF(new [] {doc.StringField.FullTextScore(new [] {"test1"}), Convert(doc.ArrayField.Count(), Double)}))]]> + + + + + + + + + + RRF(new [] {doc.StringField.FullTextScore(new [] {"test1"}), RRF(new [] {doc.StringField2.FullTextScore(new [] {"test1", "test2", "test3"}), doc.StringField.FullTextScore(new [] {"test1", "test2"})})}))]]> + + + + + + \ No newline at end of file diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Linq/LinqTranslationBaselineTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Linq/LinqTranslationBaselineTests.cs index eb4f4c178f..c88b9e4418 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Linq/LinqTranslationBaselineTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Linq/LinqTranslationBaselineTests.cs @@ -425,14 +425,14 @@ static DataObject createDataObj(Random random) // Negative case: FullTextScore in non order by clause new LinqTestInput("FullTextScore in WHERE clause", b => getQuery(b) - .Where(doc => doc.StringField.FullTextScore(new string[] { "test1" }) != null)), + .Where(doc => doc.StringField.FullTextScore(new string[] { "test1" }) != 123)), new LinqTestInput("FullTextScore in WHERE clause 2", b => getQuery(b) - .Where(doc => doc.StringField.FullTextScore(new string[] { "test1", "test2", "test3" }) != null)), + .Where(doc => doc.StringField.FullTextScore(new string[] { "test1", "test2", "test3" }) != 123)), new LinqTestInput("FullTextScore in WHERE clause", b => getQuery(b) - .Where(doc => doc.StringField.FullTextScore("test1") != null)), + .Where(doc => doc.StringField.FullTextScore("test1") != 123)), new LinqTestInput("FullTextScore in WHERE clause 2", b => getQuery(b) - .Where(doc => doc.StringField.FullTextScore("test1", "test2", "test3") != null)), + .Where(doc => doc.StringField.FullTextScore("test1", "test2", "test3") != 123)), }; foreach (LinqTestInput input in inputs) @@ -455,7 +455,8 @@ static DataObject createDataObj(Random random) { DataObject obj = new DataObject { - StringField = LinqTestsCommon.RandomString(random, random.Next(MaxStringLength)), + StringField = LinqTestsCommon.RandomString(random, random.Next(MaxStringLength)), + IntField = 1, Id = Guid.NewGuid().ToString(), Pk = "Test" }; @@ -466,25 +467,39 @@ static DataObject createDataObj(Random random) List inputs = new List { new LinqTestInput("RRF with 2 functions", b => getQuery(b) - .OrderByRank(doc => RRF(doc.StringField.FullTextScore(new string[] { "test1" }), doc.StringField2.FullTextScore(new string[] { "test1", "test2", "test3" }))) + .OrderByRank(doc => RRF(doc.StringField.FullTextScore(new string[] { "test1" }), + doc.StringField2.FullTextScore(new string[] { "test1", "test2", "test3" }))) .Select(doc => doc.Pk)), new LinqTestInput("RRF with 3 functions", b => getQuery(b) .OrderByRank(doc => RRF(doc.StringField.FullTextScore(new string[] { "test1" }), - doc.StringField.FullTextScore(new string[] { "test1", "text2" }), + doc.StringField.FullTextScore(new string[] { "test1", "text2" }), doc.StringField2.FullTextScore(new string[] { "test1", "test2", "test3" }))) .Select(doc => doc.Pk)), // Negative case: FullTextScore in non order by clause new LinqTestInput("RRF with 1 function", b => getQuery(b) - .OrderByRank(doc => CosmosLinqExtensions.RRF(doc.StringField.FullTextScore(new string[] { "test1" }))) + .OrderByRank(doc => RRF(doc.StringField.FullTextScore(new string[] { "test1" }))) .Select(doc => doc.Pk)), new LinqTestInput("RRF in WHERE clause", b => getQuery(b) - .Where(doc => RRF(doc.StringField.FullTextScore(new string[] { "test1" })) != null)), + .Where(doc => RRF(doc.StringField.FullTextScore(new string[] { "test1" })) != 123)), new LinqTestInput("RRF in WHERE clause 2", b => getQuery(b) .Where(doc => RRF(doc.StringField.FullTextScore(new string[] { "test1" }), - doc.StringField2.FullTextScore(new string[] { "test1", "test2", "test3" })) != null)), + doc.StringField2.FullTextScore(new string[] { "test1", "test2", "test3" })) != 123)), + + new LinqTestInput("RRF with non scoring function", b => getQuery(b) + .OrderByRank(doc => RRF(doc.StringField.FullTextScore(new string[] { "test1" }), 123))), + new LinqTestInput("RRF with non scoring function 2", b => getQuery(b) + .OrderByRank(doc => RRF(doc.StringField.FullTextScore(new string[] { "test1" }), doc.IntField * 1.0))), + new LinqTestInput("RRF with non scoring function 3", b => getQuery(b) + .OrderByRank(doc => RRF(doc.StringField.FullTextScore(new string[] { "test1" }), doc.StringField2.Length))), + new LinqTestInput("RRF with non scoring function 4", b => getQuery(b) + .OrderByRank(doc => RRF(doc.StringField.FullTextScore(new string[] { "test1" }), doc.ArrayField.Count()))), + new LinqTestInput("RRF with RRF", b => getQuery(b) + .OrderByRank(doc => RRF(doc.StringField.FullTextScore(new string[] { "test1" }), + RRF(doc.StringField2.FullTextScore(new string[] { "test1", "test2", "test3" }), + doc.StringField.FullTextScore(new string[] { "test1", "test2"}))))) }; foreach (LinqTestInput input in inputs) diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Contracts/DotNetSDKAPI.json b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Contracts/DotNetSDKAPI.json index 08870f891f..1bd7fcc2f7 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Contracts/DotNetSDKAPI.json +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Contracts/DotNetSDKAPI.json @@ -6344,6 +6344,18 @@ ], "MethodInfo": "Boolean RegexMatch(System.Object, System.String);IsAbstract:False;IsStatic:True;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" }, + "Double FullTextScore[TSource](TSource, System.String[])[System.Runtime.CompilerServices.ExtensionAttribute()]": { + "Type": "Method", + "Attributes": [ + "ExtensionAttribute" + ], + "MethodInfo": "Double FullTextScore[TSource](TSource, System.String[]);IsAbstract:False;IsStatic:True;IsVirtual:False;IsGenericMethod:True;IsConstructor:False;IsFinal:False;" + }, + "Double RRF(Double[])": { + "Type": "Method", + "Attributes": [], + "MethodInfo": "Double RRF(Double[]);IsAbstract:False;IsStatic:True;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" + }, "Int32 DocumentId(System.Object)[System.Runtime.CompilerServices.ExtensionAttribute()]": { "Type": "Method", "Attributes": [ @@ -6372,24 +6384,12 @@ ], "MethodInfo": "Microsoft.Azure.Cosmos.QueryDefinition ToQueryDefinition[T](System.Linq.IQueryable`1[T]);IsAbstract:False;IsStatic:True;IsVirtual:False;IsGenericMethod:True;IsConstructor:False;IsFinal:False;" }, - "System.Func`2[TSource,System.Object] FullTextScore[TSource](TSource, System.String[])[System.Runtime.CompilerServices.ExtensionAttribute()]": { - "Type": "Method", - "Attributes": [ - "ExtensionAttribute" - ], - "MethodInfo": "System.Func`2[TSource,System.Object] FullTextScore[TSource](TSource, System.String[]);IsAbstract:False;IsStatic:True;IsVirtual:False;IsGenericMethod:True;IsConstructor:False;IsFinal:False;" - }, - "System.Func`2[TSource,System.Object] RRF[TSource](System.Func`2[TSource,System.Object][])": { - "Type": "Method", - "Attributes": [], - "MethodInfo": "System.Func`2[TSource,System.Object] RRF[TSource](System.Func`2[TSource,System.Object][]);IsAbstract:False;IsStatic:True;IsVirtual:False;IsGenericMethod:True;IsConstructor:False;IsFinal:False;" - }, - "System.Linq.IOrderedQueryable`1[TSource] OrderByRank[TSource](System.Linq.IQueryable`1[TSource], System.Linq.Expressions.Expression`1[System.Func`2[TSource,System.Object]])[System.Runtime.CompilerServices.ExtensionAttribute()]": { + "System.Linq.IOrderedQueryable`1[TSource] OrderByRank[TSource,TKey](System.Linq.IQueryable`1[TSource], System.Linq.Expressions.Expression`1[System.Func`2[TSource,TKey]])[System.Runtime.CompilerServices.ExtensionAttribute()]": { "Type": "Method", "Attributes": [ "ExtensionAttribute" ], - "MethodInfo": "System.Linq.IOrderedQueryable`1[TSource] OrderByRank[TSource](System.Linq.IQueryable`1[TSource], System.Linq.Expressions.Expression`1[System.Func`2[TSource,System.Object]]);IsAbstract:False;IsStatic:True;IsVirtual:False;IsGenericMethod:True;IsConstructor:False;IsFinal:False;" + "MethodInfo": "System.Linq.IOrderedQueryable`1[TSource] OrderByRank[TSource,TKey](System.Linq.IQueryable`1[TSource], System.Linq.Expressions.Expression`1[System.Func`2[TSource,TKey]]);IsAbstract:False;IsStatic:True;IsVirtual:False;IsGenericMethod:True;IsConstructor:False;IsFinal:False;" }, "System.Threading.Tasks.Task`1[Microsoft.Azure.Cosmos.Response`1[System.Decimal]] AverageAsync(System.Linq.IQueryable`1[System.Decimal], System.Threading.CancellationToken)[System.Runtime.CompilerServices.ExtensionAttribute()]": { "Type": "Method",