Skip to content

Query: Fixes function signature for RRF, OrderByRank and FullTextScore LINQ extension methods #5163

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

Merged
merged 4 commits into from
May 2, 2025
Merged
Show file tree
Hide file tree
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
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ public RRFVisit()
true,
new List<Type[]>()
{
new Type[]{typeof(Func<object, object>[])}
new Type[]{typeof(double[])}
})
{
}
Expand All @@ -36,7 +36,27 @@ protected override SqlScalarExpression VisitImplicit(MethodCallExpression method
List<SqlScalarExpression> arguments = new List<SqlScalarExpression>();
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());
Expand Down
10 changes: 5 additions & 5 deletions Microsoft.Azure.Cosmos/src/Linq/CosmosLinqExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -318,7 +318,7 @@ public static bool FullTextContainsAny(this object obj, params string[] searches
/// ]]>
/// </code>
/// </example>
public static Func<TSource, object> FullTextScore<TSource>(this TSource obj, params string[] terms)
public static double FullTextScore<TSource>(this TSource obj, params string[] terms)
{
throw new NotImplementedException(ClientResources.ExtensionMethodNotImplemented);
}
Expand All @@ -339,7 +339,7 @@ public static Func<TSource, object> FullTextScore<TSource>(this TSource obj, par
/// ]]>
/// </code>
/// </example>
public static IOrderedQueryable<TSource> OrderByRank<TSource>(this IQueryable<TSource> source, Expression<Func<TSource, object>> scoreFunction)
public static IOrderedQueryable<TSource> OrderByRank<TSource, TKey>(this IQueryable<TSource> source, Expression<Func<TSource, TKey>> scoreFunction)
{
if (!(source is CosmosLinqQuery<TSource>))
{
Expand All @@ -349,7 +349,7 @@ public static IOrderedQueryable<TSource> OrderByRank<TSource>(this IQueryable<TS
return (IOrderedQueryable<TSource>)source.Provider.CreateQuery<TSource>(
Expression.Call(
null,
typeof(CosmosLinqExtensions).GetMethod("OrderByRank").MakeGenericMethod(typeof(TSource)),
typeof(CosmosLinqExtensions).GetMethod("OrderByRank").MakeGenericMethod(typeof(TSource), typeof(TKey)),
source.Expression,
Expression.Quote(scoreFunction)));
}
Expand All @@ -360,7 +360,7 @@ public static IOrderedQueryable<TSource> OrderByRank<TSource>(this IQueryable<TS
/// This method is to be used in LINQ expressions only and will be evaluated on server.
/// There's no implementation provided in the client library.
/// </summary>
/// <param name="scoringFunctions">the scoring functions to combine.</param>
/// <param name="scoringFunctions">the scoring functions to combine. Valid functions are FullTextScore and VectorDistance</param>
/// <returns>Returns the the combined scores of the scoring functions.</returns>
/// <example>
/// <code>
Expand All @@ -369,7 +369,7 @@ public static IOrderedQueryable<TSource> OrderByRank<TSource>(this IQueryable<TS
/// ]]>
/// </code>
/// </example>
public static Func<TSource, object> RRF<TSource>(params Func<TSource, object>[] 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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,52 +66,52 @@ ORDER BY RANK FullTextScore(root["StringField"], "test1", "test2", "test3")]]></
<Result>
<Input>
<Description><![CDATA[FullTextScore in WHERE clause]]></Description>
<Expression><![CDATA[query.Where(doc => (doc.StringField.FullTextScore(new [] {"test1"}) != null))]]></Expression>
<Expression><![CDATA[query.Where(doc => (doc.StringField.FullTextScore(new [] {"test1"}) != 123))]]></Expression>
</Input>
<Output>
<SqlQuery><![CDATA[
SELECT VALUE root
FROM root
WHERE (FullTextScore(root["StringField"], "test1") != null)]]></SqlQuery>
WHERE (FullTextScore(root["StringField"], "test1") != 123)]]></SqlQuery>
<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>
</Output>
</Result>
<Result>
<Input>
<Description><![CDATA[FullTextScore in WHERE clause 2]]></Description>
<Expression><![CDATA[query.Where(doc => (doc.StringField.FullTextScore(new [] {"test1", "test2", "test3"}) != null))]]></Expression>
<Expression><![CDATA[query.Where(doc => (doc.StringField.FullTextScore(new [] {"test1", "test2", "test3"}) != 123))]]></Expression>
</Input>
<Output>
<SqlQuery><![CDATA[
SELECT VALUE root
FROM root
WHERE (FullTextScore(root["StringField"], "test1", "test2", "test3") != null)]]></SqlQuery>
WHERE (FullTextScore(root["StringField"], "test1", "test2", "test3") != 123)]]></SqlQuery>
<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>
</Output>
</Result>
<Result>
<Input>
<Description><![CDATA[FullTextScore in WHERE clause]]></Description>
<Expression><![CDATA[query.Where(doc => (doc.StringField.FullTextScore(new [] {"test1"}) != null))]]></Expression>
<Expression><![CDATA[query.Where(doc => (doc.StringField.FullTextScore(new [] {"test1"}) != 123))]]></Expression>
</Input>
<Output>
<SqlQuery><![CDATA[
SELECT VALUE root
FROM root
WHERE (FullTextScore(root["StringField"], "test1") != null)]]></SqlQuery>
WHERE (FullTextScore(root["StringField"], "test1") != 123)]]></SqlQuery>
<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>
</Output>
</Result>
<Result>
<Input>
<Description><![CDATA[FullTextScore in WHERE clause 2]]></Description>
<Expression><![CDATA[query.Where(doc => (doc.StringField.FullTextScore(new [] {"test1", "test2", "test3"}) != null))]]></Expression>
<Expression><![CDATA[query.Where(doc => (doc.StringField.FullTextScore(new [] {"test1", "test2", "test3"}) != 123))]]></Expression>
</Input>
<Output>
<SqlQuery><![CDATA[
SELECT VALUE root
FROM root
WHERE (FullTextScore(root["StringField"], "test1", "test2", "test3") != null)]]></SqlQuery>
WHERE (FullTextScore(root["StringField"], "test1", "test2", "test3") != 123)]]></SqlQuery>
<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>
</Output>
</Result>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,27 +47,77 @@ ORDER BY RANK RRF(FullTextScore(root["StringField"], "test1"))]]></SqlQuery>
<Result>
<Input>
<Description><![CDATA[RRF in WHERE clause]]></Description>
<Expression><![CDATA[query.Where(doc => (RRF(new [] {doc.StringField.FullTextScore(new [] {"test1"})}) != null))]]></Expression>
<Expression><![CDATA[query.Where(doc => (RRF(new [] {doc.StringField.FullTextScore(new [] {"test1"})}) != 123))]]></Expression>
</Input>
<Output>
<SqlQuery><![CDATA[
SELECT VALUE root
FROM root
WHERE (RRF(FullTextScore(root["StringField"], "test1")) != null)]]></SqlQuery>
WHERE (RRF(FullTextScore(root["StringField"], "test1")) != 123)]]></SqlQuery>
<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>
</Output>
</Result>
<Result>
<Input>
<Description><![CDATA[RRF in WHERE clause 2]]></Description>
<Expression><![CDATA[query.Where(doc => (RRF(new [] {doc.StringField.FullTextScore(new [] {"test1"}), doc.StringField2.FullTextScore(new [] {"test1", "test2", "test3"})}) != null))]]></Expression>
<Expression><![CDATA[query.Where(doc => (RRF(new [] {doc.StringField.FullTextScore(new [] {"test1"}), doc.StringField2.FullTextScore(new [] {"test1", "test2", "test3"})}) != 123))]]></Expression>
</Input>
<Output>
<SqlQuery><![CDATA[
SELECT VALUE root
FROM root
WHERE (RRF(FullTextScore(root["StringField"], "test1"), FullTextScore(root["StringField2"], "test1", "test2", "test3")) != null)]]></SqlQuery>
WHERE (RRF(FullTextScore(root["StringField"], "test1"), FullTextScore(root["StringField2"], "test1", "test2", "test3")) != 123)]]></SqlQuery>
<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>
</Output>
</Result>
<Result>
<Input>
<Description><![CDATA[RRF with non scoring function]]></Description>
<Expression><![CDATA[query.OrderByRank(doc => RRF(new [] {doc.StringField.FullTextScore(new [] {"test1"}), 123}))]]></Expression>
</Input>
<Output>
<SqlQuery><![CDATA[]]></SqlQuery>
<ErrorMessage><![CDATA[Expressions of type System.Double is not supported as an argument to CosmosLinqExtensions.RRF. Supported expressions are method calls to FullTextScore.]]></ErrorMessage>
</Output>
</Result>
<Result>
<Input>
<Description><![CDATA[RRF with non scoring function 2]]></Description>
<Expression><![CDATA[query.OrderByRank(doc => RRF(new [] {doc.StringField.FullTextScore(new [] {"test1"}), (doc.IntField * 1)}))]]></Expression>
</Input>
<Output>
<SqlQuery><![CDATA[]]></SqlQuery>
<ErrorMessage><![CDATA[Expressions of type System.Double is not supported as an argument to CosmosLinqExtensions.RRF. Supported expressions are method calls to FullTextScore.]]></ErrorMessage>
</Output>
</Result>
<Result>
<Input>
<Description><![CDATA[RRF with non scoring function 3]]></Description>
<Expression><![CDATA[query.OrderByRank(doc => RRF(new [] {doc.StringField.FullTextScore(new [] {"test1"}), Convert(doc.StringField2.Length, Double)}))]]></Expression>
</Input>
<Output>
<SqlQuery><![CDATA[]]></SqlQuery>
<ErrorMessage><![CDATA[Expressions of type System.Double is not supported as an argument to CosmosLinqExtensions.RRF. Supported expressions are method calls to FullTextScore.]]></ErrorMessage>
</Output>
</Result>
<Result>
<Input>
<Description><![CDATA[RRF with non scoring function 4]]></Description>
<Expression><![CDATA[query.OrderByRank(doc => RRF(new [] {doc.StringField.FullTextScore(new [] {"test1"}), Convert(doc.ArrayField.Count(), Double)}))]]></Expression>
</Input>
<Output>
<SqlQuery><![CDATA[]]></SqlQuery>
<ErrorMessage><![CDATA[Expressions of type System.Double is not supported as an argument to CosmosLinqExtensions.RRF. Supported expressions are method calls to FullTextScore.]]></ErrorMessage>
</Output>
</Result>
<Result>
<Input>
<Description><![CDATA[RRF with RRF]]></Description>
<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>
</Input>
<Output>
<SqlQuery><![CDATA[]]></SqlQuery>
<ErrorMessage><![CDATA[Method RRF is not supported as an argument to CosmosLinqExtensions.RRF. Supported methods are FullTextScore.]]></ErrorMessage>
</Output>
</Result>
</Results>
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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"
};
Expand All @@ -466,25 +467,39 @@ static DataObject createDataObj(Random random)
List<LinqTestInput> inputs = new List<LinqTestInput>
{
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)
Expand Down
Loading
Loading