Skip to content
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
Fix to #35983 - Cosmos/FTS: update translation of FullTextScore to us…
…e multiple keywords rather than keyword array

Port of #35984
Fixes #35983

Description
Reacting to Cosmos changing the signature of FullTextScore function.

Customer impact
TBD

How found
Partner team ask.

Regression
No

Testing
Extensively tested on EF 10, manual testing on EF9. End-to-end testing is not possible because we can't create containers programmatically (no support for it inside EF Core itself, and the Cosmos SDK which supports it is currently only available in beta, so we can't take dependency on it). Instead, we created containers and data using EF 10, ported all the query tests from EF 10 and ran them using the EF9 bits. Partner team will conduct additional testing on their end.

Risk
Low. Localized change to translation - method signature exposed by EF is already in place. New translation now resembles other FullText methods, so same techniques can be used for translation (essentially re-using code that has been in place for other methods and working). Full text search support is experimental on EF9. Added quirk just in case.
  • Loading branch information
maumar committed May 12, 2025
commit 04ebaf93ab2f3d03dde5e226bf543290feb9b661
2 changes: 1 addition & 1 deletion Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
<PackageVersion Include="Microsoft.DotNet.Build.Tasks.Templating" Version="$(MicrosoftDotNetBuildTasksTemplatingVersion)" />

<!-- Azure SDK for .NET dependencies -->
<PackageVersion Include="Microsoft.Azure.Cosmos" Version="3.48.0" />
<PackageVersion Include="Microsoft.Azure.Cosmos" Version="3.49.0" />

<!-- SQL Server dependencies -->
<PackageVersion Include="Microsoft.Data.SqlClient" Version="5.1.6" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,10 @@ namespace Microsoft.EntityFrameworkCore.Cosmos.Query.Internal;
public partial class CosmosShapedQueryCompilingExpressionVisitor
{
private static readonly bool UseOldBehavior35476 =
AppContext.TryGetSwitch("Microsoft.EntityFrameworkCore.Issue35476", out var enabled35476) && enabled35476;
AppContext.TryGetSwitch("Microsoft.EntityFrameworkCore.Issue35476", out var enabled35476) && enabled35476;

private static readonly bool UseOldBehavior35983 =
AppContext.TryGetSwitch("Microsoft.EntityFrameworkCore.Issue35983", out var enabled35983) && enabled35983;

private sealed class ParameterInliner(
ISqlExpressionFactory sqlExpressionFactory,
Expand Down Expand Up @@ -85,7 +88,7 @@ protected override Expression VisitExtension(Expression expression)
return base.VisitExtension(expression);
}

// Inlines array parameter of full-text functions, transforming FullTextContainsAll(x, @keywordsArray) to FullTextContainsAll(x, keyword1, keyword2))
// Inlines array parameter of full-text functions, transforming FullTextContainsAll(x, @keywordsArray) to FullTextContainsAll(x, keyword1, keyword2)
case SqlFunctionExpression
{
Name: "FullTextContainsAny" or "FullTextContainsAll",
Expand All @@ -106,14 +109,38 @@ protected override Expression VisitExtension(Expression expression)
fullTextContainsAllAnyFunction.TypeMapping);
}

// Inlines array parameter of full-text score, transforming FullTextScore(x, @keywordsArray) to FullTextScore(x, [keyword1, keyword2]))
// Inlines array parameter of full-text score, transforming FullTextScore(x, @keywordsArray) to FullTextScore(x, keyword1, keyword2)
case SqlFunctionExpression
{
Name: "FullTextScore",
IsScoringFunction: true,
Arguments: [var property, SqlParameterExpression { TypeMapping: { ElementTypeMapping: var elementTypeMapping }, Type: Type type } keywords]
} fullTextScoreFunction
when !UseOldBehavior35476 && !UseOldBehavior35983 && type == typeof(string[]):
{
var keywordValues = new List<SqlExpression>();
foreach (var value in (IEnumerable)parametersValues[keywords.Name])
{
keywordValues.Add(sqlExpressionFactory.Constant(value, typeof(string), elementTypeMapping));
}

return new SqlFunctionExpression(
fullTextScoreFunction.Name,
isScoringFunction: true,
[property, .. keywordValues],
fullTextScoreFunction.Type,
fullTextScoreFunction.TypeMapping);
}

// Legacy path for #35983
// Inlines array parameter of full-text score, transforming FullTextScore(x, @keywordsArray) to FullTextScore(x, [keyword1, keyword2])
case SqlFunctionExpression
{
Name: "FullTextScore",
IsScoringFunction: true,
Arguments: [var property, SqlParameterExpression { TypeMapping: { ElementTypeMapping: not null } typeMapping } keywords]
} fullTextScoreFunction
when !UseOldBehavior35476:
when !UseOldBehavior35476 && UseOldBehavior35983:
{
var keywordValues = new List<string>();
foreach (var value in (IEnumerable)parametersValues[keywords.Name])
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,10 @@ public class CosmosFullTextSearchTranslator(ISqlExpressionFactory sqlExpressionF
: IMethodCallTranslator
{
private static readonly bool UseOldBehavior35476 =
AppContext.TryGetSwitch("Microsoft.EntityFrameworkCore.Issue35476", out var enabled35476) && enabled35476;
AppContext.TryGetSwitch("Microsoft.EntityFrameworkCore.Issue35476", out var enabled35476) && enabled35476;

private static readonly bool UseOldBehavior35983 =
AppContext.TryGetSwitch("Microsoft.EntityFrameworkCore.Issue35983", out var enabled35983) && enabled35983;

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
Expand Down Expand Up @@ -47,13 +50,36 @@ public class CosmosFullTextSearchTranslator(ISqlExpressionFactory sqlExpressionF
typeMappingSource.FindMapping(typeof(bool))),

nameof(CosmosDbFunctionsExtensions.FullTextScore)
when arguments is [_, var property, var keywords] => BuildScoringFunction(
when !UseOldBehavior35983 && arguments is [_, SqlExpression property, SqlConstantExpression { Type: var keywordClrType, Value: string[] values } keywords]
&& keywordClrType == typeof(string[]) => BuildScoringFunction(
sqlExpressionFactory,
"FullTextScore",
[property, .. values.Select(x => sqlExpressionFactory.Constant(x))],
typeof(double),
typeMappingSource.FindMapping(typeof(double))),

nameof(CosmosDbFunctionsExtensions.FullTextScore)
when !UseOldBehavior35983 && arguments is [_, SqlExpression property, SqlParameterExpression { Type: var keywordClrType } keywords]
&& keywordClrType == typeof(string[]) => BuildScoringFunction(
sqlExpressionFactory,
"FullTextScore",
[property, keywords],
typeof(double),
typeMappingSource.FindMapping(typeof(double))),

nameof(CosmosDbFunctionsExtensions.FullTextScore)
when !UseOldBehavior35983 && arguments is [_, SqlExpression property, ArrayConstantExpression keywords] => BuildScoringFunction(
sqlExpressionFactory,
"FullTextScore",
[
property,
keywords,
],
[property, .. keywords.Items],
typeof(double),
typeMappingSource.FindMapping(typeof(double))),

nameof(CosmosDbFunctionsExtensions.FullTextScore)
when UseOldBehavior35983 && arguments is [_, var property, var keywords] => BuildScoringFunction(
sqlExpressionFactory,
"FullTextScore",
[property, keywords],
typeof(double),
typeMappingSource.FindMapping(typeof(double))),

Expand Down