Skip to content

Commit 94f869c

Browse files
leminh98Minh Lekirankumarkolli
authored
Query: Fixes function signature for RRF, OrderByRank and FullTextScore LINQ extension methods (#5163)
# Pull Request Template ## Description This PR changes the function signature for RRF, OrderByRank and FullTextScore for LINQ extension methods to more closely align with vector distance ## Type of change Please delete options that are not relevant. - [x] Bug fix (non-breaking change which fixes an issue) - [] New feature (non-breaking change which adds functionality) - [] Breaking change (fix or feature that would cause existing functionality to not work as expected) - [] This change requires a documentation update ## Closing issues To automatically close an issue: closes #IssueNumber --------- Co-authored-by: Minh Le <[email protected]> Co-authored-by: Kiran Kumar Kolli <[email protected]>
1 parent 60ccb10 commit 94f869c

File tree

6 files changed

+128
-43
lines changed

6 files changed

+128
-43
lines changed

Microsoft.Azure.Cosmos/src/Linq/BuiltinFunctions/OtherBuiltinSystemFunctions.cs

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ public RRFVisit()
2121
true,
2222
new List<Type[]>()
2323
{
24-
new Type[]{typeof(Func<object, object>[])}
24+
new Type[]{typeof(double[])}
2525
})
2626
{
2727
}
@@ -36,7 +36,27 @@ protected override SqlScalarExpression VisitImplicit(MethodCallExpression method
3636
List<SqlScalarExpression> arguments = new List<SqlScalarExpression>();
3737
foreach (Expression argument in functionListExpression)
3838
{
39-
arguments.Add(ExpressionToSql.VisitScalarExpression(argument, context));
39+
if (!(argument is MethodCallExpression functionCallExpression))
40+
{
41+
throw new ArgumentException(
42+
string.Format(
43+
CultureInfo.CurrentCulture,
44+
"Expressions of type {0} is not supported as an argument to CosmosLinqExtensions.RRF. Supported expressions are method calls to {1}.",
45+
argument.Type,
46+
nameof(CosmosLinqExtensions.FullTextScore)));
47+
}
48+
49+
if (functionCallExpression.Method.Name != nameof(CosmosLinqExtensions.FullTextScore))
50+
{
51+
throw new ArgumentException(
52+
string.Format(
53+
CultureInfo.CurrentCulture,
54+
"Method {0} is not supported as an argument to CosmosLinqExtensions.RRF. Supported methods are {1}.",
55+
functionCallExpression.Method.Name,
56+
nameof(CosmosLinqExtensions.FullTextScore)));
57+
}
58+
59+
arguments.Add(ExpressionToSql.VisitNonSubqueryScalarExpression(argument, context));
4060
}
4161

4262
return SqlFunctionCallScalarExpression.CreateBuiltin(SqlFunctionCallScalarExpression.Names.RRF, arguments.ToImmutableArray());

Microsoft.Azure.Cosmos/src/Linq/CosmosLinqExtensions.cs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -318,7 +318,7 @@ public static bool FullTextContainsAny(this object obj, params string[] searches
318318
/// ]]>
319319
/// </code>
320320
/// </example>
321-
public static Func<TSource, object> FullTextScore<TSource>(this TSource obj, params string[] terms)
321+
public static double FullTextScore<TSource>(this TSource obj, params string[] terms)
322322
{
323323
throw new NotImplementedException(ClientResources.ExtensionMethodNotImplemented);
324324
}
@@ -339,7 +339,7 @@ public static Func<TSource, object> FullTextScore<TSource>(this TSource obj, par
339339
/// ]]>
340340
/// </code>
341341
/// </example>
342-
public static IOrderedQueryable<TSource> OrderByRank<TSource>(this IQueryable<TSource> source, Expression<Func<TSource, object>> scoreFunction)
342+
public static IOrderedQueryable<TSource> OrderByRank<TSource, TKey>(this IQueryable<TSource> source, Expression<Func<TSource, TKey>> scoreFunction)
343343
{
344344
if (!(source is CosmosLinqQuery<TSource>))
345345
{
@@ -349,7 +349,7 @@ public static IOrderedQueryable<TSource> OrderByRank<TSource>(this IQueryable<TS
349349
return (IOrderedQueryable<TSource>)source.Provider.CreateQuery<TSource>(
350350
Expression.Call(
351351
null,
352-
typeof(CosmosLinqExtensions).GetMethod("OrderByRank").MakeGenericMethod(typeof(TSource)),
352+
typeof(CosmosLinqExtensions).GetMethod("OrderByRank").MakeGenericMethod(typeof(TSource), typeof(TKey)),
353353
source.Expression,
354354
Expression.Quote(scoreFunction)));
355355
}
@@ -360,7 +360,7 @@ public static IOrderedQueryable<TSource> OrderByRank<TSource>(this IQueryable<TS
360360
/// This method is to be used in LINQ expressions only and will be evaluated on server.
361361
/// There's no implementation provided in the client library.
362362
/// </summary>
363-
/// <param name="scoringFunctions">the scoring functions to combine.</param>
363+
/// <param name="scoringFunctions">the scoring functions to combine. Valid functions are FullTextScore and VectorDistance</param>
364364
/// <returns>Returns the the combined scores of the scoring functions.</returns>
365365
/// <example>
366366
/// <code>
@@ -369,7 +369,7 @@ public static IOrderedQueryable<TSource> OrderByRank<TSource>(this IQueryable<TS
369369
/// ]]>
370370
/// </code>
371371
/// </example>
372-
public static Func<TSource, object> RRF<TSource>(params Func<TSource, object>[] scoringFunctions)
372+
public static double RRF(params double[] scoringFunctions)
373373
{
374374
// The reason for not defining "this" keyword is because this causes undesirable serialization when call Expression.ToString() on this method
375375
throw new NotImplementedException(ClientResources.ExtensionMethodNotImplemented);

Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/LinqTranslationBaselineTests.TestFullTextScoreOrderByRankFunction.xml

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -66,52 +66,52 @@ ORDER BY RANK FullTextScore(root["StringField"], "test1", "test2", "test3")]]></
6666
<Result>
6767
<Input>
6868
<Description><![CDATA[FullTextScore in WHERE clause]]></Description>
69-
<Expression><![CDATA[query.Where(doc => (doc.StringField.FullTextScore(new [] {"test1"}) != null))]]></Expression>
69+
<Expression><![CDATA[query.Where(doc => (doc.StringField.FullTextScore(new [] {"test1"}) != 123))]]></Expression>
7070
</Input>
7171
<Output>
7272
<SqlQuery><![CDATA[
7373
SELECT VALUE root
7474
FROM root
75-
WHERE (FullTextScore(root["StringField"], "test1") != null)]]></SqlQuery>
75+
WHERE (FullTextScore(root["StringField"], "test1") != 123)]]></SqlQuery>
7676
<ErrorMessage><![CDATA[Status Code: BadRequest,{"errors":[{"severity":"Error","location":{"start":35,"end":78},"code":"SC2240","message":"The FullTextScore function is only allowed in the ORDER BY RANK clause."}]},0x800A0B00]]></ErrorMessage>
7777
</Output>
7878
</Result>
7979
<Result>
8080
<Input>
8181
<Description><![CDATA[FullTextScore in WHERE clause 2]]></Description>
82-
<Expression><![CDATA[query.Where(doc => (doc.StringField.FullTextScore(new [] {"test1", "test2", "test3"}) != null))]]></Expression>
82+
<Expression><![CDATA[query.Where(doc => (doc.StringField.FullTextScore(new [] {"test1", "test2", "test3"}) != 123))]]></Expression>
8383
</Input>
8484
<Output>
8585
<SqlQuery><![CDATA[
8686
SELECT VALUE root
8787
FROM root
88-
WHERE (FullTextScore(root["StringField"], "test1", "test2", "test3") != null)]]></SqlQuery>
88+
WHERE (FullTextScore(root["StringField"], "test1", "test2", "test3") != 123)]]></SqlQuery>
8989
<ErrorMessage><![CDATA[Status Code: BadRequest,{"errors":[{"severity":"Error","location":{"start":35,"end":96},"code":"SC2240","message":"The FullTextScore function is only allowed in the ORDER BY RANK clause."}]},0x800A0B00]]></ErrorMessage>
9090
</Output>
9191
</Result>
9292
<Result>
9393
<Input>
9494
<Description><![CDATA[FullTextScore in WHERE clause]]></Description>
95-
<Expression><![CDATA[query.Where(doc => (doc.StringField.FullTextScore(new [] {"test1"}) != null))]]></Expression>
95+
<Expression><![CDATA[query.Where(doc => (doc.StringField.FullTextScore(new [] {"test1"}) != 123))]]></Expression>
9696
</Input>
9797
<Output>
9898
<SqlQuery><![CDATA[
9999
SELECT VALUE root
100100
FROM root
101-
WHERE (FullTextScore(root["StringField"], "test1") != null)]]></SqlQuery>
101+
WHERE (FullTextScore(root["StringField"], "test1") != 123)]]></SqlQuery>
102102
<ErrorMessage><![CDATA[Status Code: BadRequest,{"errors":[{"severity":"Error","location":{"start":35,"end":78},"code":"SC2240","message":"The FullTextScore function is only allowed in the ORDER BY RANK clause."}]},0x800A0B00]]></ErrorMessage>
103103
</Output>
104104
</Result>
105105
<Result>
106106
<Input>
107107
<Description><![CDATA[FullTextScore in WHERE clause 2]]></Description>
108-
<Expression><![CDATA[query.Where(doc => (doc.StringField.FullTextScore(new [] {"test1", "test2", "test3"}) != null))]]></Expression>
108+
<Expression><![CDATA[query.Where(doc => (doc.StringField.FullTextScore(new [] {"test1", "test2", "test3"}) != 123))]]></Expression>
109109
</Input>
110110
<Output>
111111
<SqlQuery><![CDATA[
112112
SELECT VALUE root
113113
FROM root
114-
WHERE (FullTextScore(root["StringField"], "test1", "test2", "test3") != null)]]></SqlQuery>
114+
WHERE (FullTextScore(root["StringField"], "test1", "test2", "test3") != 123)]]></SqlQuery>
115115
<ErrorMessage><![CDATA[Status Code: BadRequest,{"errors":[{"severity":"Error","location":{"start":35,"end":96},"code":"SC2240","message":"The FullTextScore function is only allowed in the ORDER BY RANK clause."}]},0x800A0B00]]></ErrorMessage>
116116
</Output>
117117
</Result>

Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/LinqTranslationBaselineTests.TestRRFOrderByRankFunction.xml

Lines changed: 54 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -47,27 +47,77 @@ ORDER BY RANK RRF(FullTextScore(root["StringField"], "test1"))]]></SqlQuery>
4747
<Result>
4848
<Input>
4949
<Description><![CDATA[RRF in WHERE clause]]></Description>
50-
<Expression><![CDATA[query.Where(doc => (RRF(new [] {doc.StringField.FullTextScore(new [] {"test1"})}) != null))]]></Expression>
50+
<Expression><![CDATA[query.Where(doc => (RRF(new [] {doc.StringField.FullTextScore(new [] {"test1"})}) != 123))]]></Expression>
5151
</Input>
5252
<Output>
5353
<SqlQuery><![CDATA[
5454
SELECT VALUE root
5555
FROM root
56-
WHERE (RRF(FullTextScore(root["StringField"], "test1")) != null)]]></SqlQuery>
56+
WHERE (RRF(FullTextScore(root["StringField"], "test1")) != 123)]]></SqlQuery>
5757
<ErrorMessage><![CDATA[Status Code: BadRequest,{"errors":[{"severity":"Error","location":{"start":39,"end":82},"code":"SC2240","message":"The FullTextScore function is only allowed in the ORDER BY RANK clause."},{"severity":"Error","location":{"start":35,"end":38},"code":"SC2005","message":"'RRF' is not a recognized built-in function name."}]},0x800A0B00]]></ErrorMessage>
5858
</Output>
5959
</Result>
6060
<Result>
6161
<Input>
6262
<Description><![CDATA[RRF in WHERE clause 2]]></Description>
63-
<Expression><![CDATA[query.Where(doc => (RRF(new [] {doc.StringField.FullTextScore(new [] {"test1"}), doc.StringField2.FullTextScore(new [] {"test1", "test2", "test3"})}) != null))]]></Expression>
63+
<Expression><![CDATA[query.Where(doc => (RRF(new [] {doc.StringField.FullTextScore(new [] {"test1"}), doc.StringField2.FullTextScore(new [] {"test1", "test2", "test3"})}) != 123))]]></Expression>
6464
</Input>
6565
<Output>
6666
<SqlQuery><![CDATA[
6767
SELECT VALUE root
6868
FROM root
69-
WHERE (RRF(FullTextScore(root["StringField"], "test1"), FullTextScore(root["StringField2"], "test1", "test2", "test3")) != null)]]></SqlQuery>
69+
WHERE (RRF(FullTextScore(root["StringField"], "test1"), FullTextScore(root["StringField2"], "test1", "test2", "test3")) != 123)]]></SqlQuery>
7070
<ErrorMessage><![CDATA[Status Code: BadRequest,{"errors":[{"severity":"Error","location":{"start":39,"end":82},"code":"SC2240","message":"The FullTextScore function is only allowed in the ORDER BY RANK clause."},{"severity":"Error","location":{"start":84,"end":146},"code":"SC2240","message":"The FullTextScore function is only allowed in the ORDER BY RANK clause."},{"severity":"Error","location":{"start":35,"end":38},"code":"SC2005","message":"'RRF' is not a recognized built-in function name."}]},0x800A0B00]]></ErrorMessage>
7171
</Output>
7272
</Result>
73+
<Result>
74+
<Input>
75+
<Description><![CDATA[RRF with non scoring function]]></Description>
76+
<Expression><![CDATA[query.OrderByRank(doc => RRF(new [] {doc.StringField.FullTextScore(new [] {"test1"}), 123}))]]></Expression>
77+
</Input>
78+
<Output>
79+
<SqlQuery><![CDATA[]]></SqlQuery>
80+
<ErrorMessage><![CDATA[Expressions of type System.Double is not supported as an argument to CosmosLinqExtensions.RRF. Supported expressions are method calls to FullTextScore.]]></ErrorMessage>
81+
</Output>
82+
</Result>
83+
<Result>
84+
<Input>
85+
<Description><![CDATA[RRF with non scoring function 2]]></Description>
86+
<Expression><![CDATA[query.OrderByRank(doc => RRF(new [] {doc.StringField.FullTextScore(new [] {"test1"}), (doc.IntField * 1)}))]]></Expression>
87+
</Input>
88+
<Output>
89+
<SqlQuery><![CDATA[]]></SqlQuery>
90+
<ErrorMessage><![CDATA[Expressions of type System.Double is not supported as an argument to CosmosLinqExtensions.RRF. Supported expressions are method calls to FullTextScore.]]></ErrorMessage>
91+
</Output>
92+
</Result>
93+
<Result>
94+
<Input>
95+
<Description><![CDATA[RRF with non scoring function 3]]></Description>
96+
<Expression><![CDATA[query.OrderByRank(doc => RRF(new [] {doc.StringField.FullTextScore(new [] {"test1"}), Convert(doc.StringField2.Length, Double)}))]]></Expression>
97+
</Input>
98+
<Output>
99+
<SqlQuery><![CDATA[]]></SqlQuery>
100+
<ErrorMessage><![CDATA[Expressions of type System.Double is not supported as an argument to CosmosLinqExtensions.RRF. Supported expressions are method calls to FullTextScore.]]></ErrorMessage>
101+
</Output>
102+
</Result>
103+
<Result>
104+
<Input>
105+
<Description><![CDATA[RRF with non scoring function 4]]></Description>
106+
<Expression><![CDATA[query.OrderByRank(doc => RRF(new [] {doc.StringField.FullTextScore(new [] {"test1"}), Convert(doc.ArrayField.Count(), Double)}))]]></Expression>
107+
</Input>
108+
<Output>
109+
<SqlQuery><![CDATA[]]></SqlQuery>
110+
<ErrorMessage><![CDATA[Expressions of type System.Double is not supported as an argument to CosmosLinqExtensions.RRF. Supported expressions are method calls to FullTextScore.]]></ErrorMessage>
111+
</Output>
112+
</Result>
113+
<Result>
114+
<Input>
115+
<Description><![CDATA[RRF with RRF]]></Description>
116+
<Expression><![CDATA[query.OrderByRank(doc => RRF(new [] {doc.StringField.FullTextScore(new [] {"test1"}), RRF(new [] {doc.StringField2.FullTextScore(new [] {"test1", "test2", "test3"}), doc.StringField.FullTextScore(new [] {"test1", "test2"})})}))]]></Expression>
117+
</Input>
118+
<Output>
119+
<SqlQuery><![CDATA[]]></SqlQuery>
120+
<ErrorMessage><![CDATA[Method RRF is not supported as an argument to CosmosLinqExtensions.RRF. Supported methods are FullTextScore.]]></ErrorMessage>
121+
</Output>
122+
</Result>
73123
</Results>

Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Linq/LinqTranslationBaselineTests.cs

Lines changed: 25 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -425,14 +425,14 @@ static DataObject createDataObj(Random random)
425425

426426
// Negative case: FullTextScore in non order by clause
427427
new LinqTestInput("FullTextScore in WHERE clause", b => getQuery(b)
428-
.Where(doc => doc.StringField.FullTextScore(new string[] { "test1" }) != null)),
428+
.Where(doc => doc.StringField.FullTextScore(new string[] { "test1" }) != 123)),
429429
new LinqTestInput("FullTextScore in WHERE clause 2", b => getQuery(b)
430-
.Where(doc => doc.StringField.FullTextScore(new string[] { "test1", "test2", "test3" }) != null)),
430+
.Where(doc => doc.StringField.FullTextScore(new string[] { "test1", "test2", "test3" }) != 123)),
431431

432432
new LinqTestInput("FullTextScore in WHERE clause", b => getQuery(b)
433-
.Where(doc => doc.StringField.FullTextScore("test1") != null)),
433+
.Where(doc => doc.StringField.FullTextScore("test1") != 123)),
434434
new LinqTestInput("FullTextScore in WHERE clause 2", b => getQuery(b)
435-
.Where(doc => doc.StringField.FullTextScore("test1", "test2", "test3") != null)),
435+
.Where(doc => doc.StringField.FullTextScore("test1", "test2", "test3") != 123)),
436436
};
437437

438438
foreach (LinqTestInput input in inputs)
@@ -455,7 +455,8 @@ static DataObject createDataObj(Random random)
455455
{
456456
DataObject obj = new DataObject
457457
{
458-
StringField = LinqTestsCommon.RandomString(random, random.Next(MaxStringLength)),
458+
StringField = LinqTestsCommon.RandomString(random, random.Next(MaxStringLength)),
459+
IntField = 1,
459460
Id = Guid.NewGuid().ToString(),
460461
Pk = "Test"
461462
};
@@ -466,25 +467,39 @@ static DataObject createDataObj(Random random)
466467
List<LinqTestInput> inputs = new List<LinqTestInput>
467468
{
468469
new LinqTestInput("RRF with 2 functions", b => getQuery(b)
469-
.OrderByRank(doc => RRF(doc.StringField.FullTextScore(new string[] { "test1" }), doc.StringField2.FullTextScore(new string[] { "test1", "test2", "test3" })))
470+
.OrderByRank(doc => RRF(doc.StringField.FullTextScore(new string[] { "test1" }),
471+
doc.StringField2.FullTextScore(new string[] { "test1", "test2", "test3" })))
470472
.Select(doc => doc.Pk)),
471473

472474
new LinqTestInput("RRF with 3 functions", b => getQuery(b)
473475
.OrderByRank(doc => RRF(doc.StringField.FullTextScore(new string[] { "test1" }),
474-
doc.StringField.FullTextScore(new string[] { "test1", "text2" }),
476+
doc.StringField.FullTextScore(new string[] { "test1", "text2" }),
475477
doc.StringField2.FullTextScore(new string[] { "test1", "test2", "test3" })))
476478
.Select(doc => doc.Pk)),
477479

478480
// Negative case: FullTextScore in non order by clause
479481
new LinqTestInput("RRF with 1 function", b => getQuery(b)
480-
.OrderByRank(doc => CosmosLinqExtensions.RRF(doc.StringField.FullTextScore(new string[] { "test1" })))
482+
.OrderByRank(doc => RRF(doc.StringField.FullTextScore(new string[] { "test1" })))
481483
.Select(doc => doc.Pk)),
482484

483485
new LinqTestInput("RRF in WHERE clause", b => getQuery(b)
484-
.Where(doc => RRF(doc.StringField.FullTextScore(new string[] { "test1" })) != null)),
486+
.Where(doc => RRF(doc.StringField.FullTextScore(new string[] { "test1" })) != 123)),
485487
new LinqTestInput("RRF in WHERE clause 2", b => getQuery(b)
486488
.Where(doc => RRF(doc.StringField.FullTextScore(new string[] { "test1" }),
487-
doc.StringField2.FullTextScore(new string[] { "test1", "test2", "test3" })) != null)),
489+
doc.StringField2.FullTextScore(new string[] { "test1", "test2", "test3" })) != 123)),
490+
491+
new LinqTestInput("RRF with non scoring function", b => getQuery(b)
492+
.OrderByRank(doc => RRF(doc.StringField.FullTextScore(new string[] { "test1" }), 123))),
493+
new LinqTestInput("RRF with non scoring function 2", b => getQuery(b)
494+
.OrderByRank(doc => RRF(doc.StringField.FullTextScore(new string[] { "test1" }), doc.IntField * 1.0))),
495+
new LinqTestInput("RRF with non scoring function 3", b => getQuery(b)
496+
.OrderByRank(doc => RRF(doc.StringField.FullTextScore(new string[] { "test1" }), doc.StringField2.Length))),
497+
new LinqTestInput("RRF with non scoring function 4", b => getQuery(b)
498+
.OrderByRank(doc => RRF(doc.StringField.FullTextScore(new string[] { "test1" }), doc.ArrayField.Count()))),
499+
new LinqTestInput("RRF with RRF", b => getQuery(b)
500+
.OrderByRank(doc => RRF(doc.StringField.FullTextScore(new string[] { "test1" }),
501+
RRF(doc.StringField2.FullTextScore(new string[] { "test1", "test2", "test3" }),
502+
doc.StringField.FullTextScore(new string[] { "test1", "test2"})))))
488503
};
489504

490505
foreach (LinqTestInput input in inputs)

0 commit comments

Comments
 (0)