diff --git a/Directory.Packages.props b/Directory.Packages.props index bb90845f3f8..a367f152413 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -37,7 +37,7 @@ - + diff --git a/src/EFCore.Cosmos/Query/Internal/CosmosShapedQueryCompilingExpressionVisitor.ParameterInliner.cs b/src/EFCore.Cosmos/Query/Internal/CosmosShapedQueryCompilingExpressionVisitor.ParameterInliner.cs index 50068ca74ba..723c8206a38 100644 --- a/src/EFCore.Cosmos/Query/Internal/CosmosShapedQueryCompilingExpressionVisitor.ParameterInliner.cs +++ b/src/EFCore.Cosmos/Query/Internal/CosmosShapedQueryCompilingExpressionVisitor.ParameterInliner.cs @@ -80,12 +80,14 @@ protected override Expression VisitExtension(Expression expression) } // Inlines array parameter of full-text functions, transforming FullTextContainsAll(x, @keywordsArray) to FullTextContainsAll(x, keyword1, keyword2)) + // we do this for FullTextContainsAll, FullTextContainsAny and FullTextScore case SqlFunctionExpression { - Name: "FullTextContainsAny" or "FullTextContainsAll", + Name: string name, + IsScoringFunction: bool scoringFunction, Arguments: [var property, SqlParameterExpression { TypeMapping: { ElementTypeMapping: var elementTypeMapping }, Type: Type type } keywords] } fullTextContainsAllAnyFunction - when type == typeof(string[]): + when (name is "FullTextContainsAny" or "FullTextContainsAll" or "FullTextScore") && type == typeof(string[]): { var keywordValues = new List(); foreach (var value in (IEnumerable)parametersValues[keywords.Name]) @@ -93,35 +95,14 @@ protected override Expression VisitExtension(Expression expression) keywordValues.Add(sqlExpressionFactory.Constant(value, typeof(string), elementTypeMapping)); } - return sqlExpressionFactory.Function( + return new SqlFunctionExpression( fullTextContainsAllAnyFunction.Name, + scoringFunction, [property, .. keywordValues], fullTextContainsAllAnyFunction.Type, fullTextContainsAllAnyFunction.TypeMapping); } - // 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: - { - var keywordValues = new List(); - foreach (var value in (IEnumerable)parametersValues[keywords.Name]) - { - keywordValues.Add((string)value); - } - - return new SqlFunctionExpression( - fullTextScoreFunction.Name, - scoringFunction: true, - [property, sqlExpressionFactory.Constant(keywordValues, typeMapping)], - fullTextScoreFunction.Type, - fullTextScoreFunction.TypeMapping); - } - default: return expression; } diff --git a/src/EFCore.Cosmos/Query/Internal/Translators/CosmosFullTextSearchTranslator.cs b/src/EFCore.Cosmos/Query/Internal/Translators/CosmosFullTextSearchTranslator.cs index b7de73b0257..7331a1878ff 100644 --- a/src/EFCore.Cosmos/Query/Internal/Translators/CosmosFullTextSearchTranslator.cs +++ b/src/EFCore.Cosmos/Query/Internal/Translators/CosmosFullTextSearchTranslator.cs @@ -41,12 +41,25 @@ public class CosmosFullTextSearchTranslator(ISqlExpressionFactory sqlExpressionF typeMappingSource.FindMapping(typeof(bool))), nameof(CosmosDbFunctionsExtensions.FullTextScore) - when arguments is [_, var property, var keywords] => sqlExpressionFactory.ScoringFunction( + when arguments is [_, SqlExpression property, SqlConstantExpression { Type: var keywordClrType, Value: string[] values } keywords] + && keywordClrType == typeof(string[]) => sqlExpressionFactory.ScoringFunction( + "FullTextScore", + [property, .. values.Select(x => sqlExpressionFactory.Constant(x))], + typeof(double), + typeMappingSource.FindMapping(typeof(double))), + + nameof(CosmosDbFunctionsExtensions.FullTextScore) + when arguments is [_, SqlExpression property, SqlParameterExpression { Type: var keywordClrType } keywords] + && keywordClrType == typeof(string[]) => sqlExpressionFactory.ScoringFunction( + "FullTextScore", + [property, keywords], + typeof(double), + typeMappingSource.FindMapping(typeof(double))), + + nameof(CosmosDbFunctionsExtensions.FullTextScore) + when arguments is [_, SqlExpression property, ArrayConstantExpression keywords] => sqlExpressionFactory.ScoringFunction( "FullTextScore", - [ - property, - keywords, - ], + [property, .. keywords.Items], typeof(double), typeMappingSource.FindMapping(typeof(double))), diff --git a/test/EFCore.Cosmos.FunctionalTests/FullTextSearchCosmosTest.cs b/test/EFCore.Cosmos.FunctionalTests/FullTextSearchCosmosTest.cs index b74d601792b..1e99de310ce 100644 --- a/test/EFCore.Cosmos.FunctionalTests/FullTextSearchCosmosTest.cs +++ b/test/EFCore.Cosmos.FunctionalTests/FullTextSearchCosmosTest.cs @@ -292,7 +292,7 @@ public virtual async Task OrderByRank_FullTextScore_constant() """ SELECT VALUE c FROM root c -ORDER BY RANK FullTextScore(c["Description"], ["otter"]) +ORDER BY RANK FullTextScore(c["Description"], "otter") """); } @@ -309,7 +309,7 @@ public virtual async Task OrderByRank_FullTextScore_constants() """ SELECT VALUE c FROM root c -ORDER BY RANK FullTextScore(c["Description"], ["otter","beaver"]) +ORDER BY RANK FullTextScore(c["Description"], "otter", "beaver") """); } @@ -326,7 +326,7 @@ public virtual async Task OrderByRank_FullTextScore_constant_array() """ SELECT VALUE c FROM root c -ORDER BY RANK FullTextScore(c["Description"], ["otter","beaver"]) +ORDER BY RANK FullTextScore(c["Description"], "otter", "beaver") """); } @@ -343,7 +343,7 @@ public virtual async Task OrderByRank_FullTextScore_constant_array_with_one_elem """ SELECT VALUE c FROM root c -ORDER BY RANK FullTextScore(c["Description"], ["otter"]) +ORDER BY RANK FullTextScore(c["Description"], "otter") """); } @@ -361,7 +361,7 @@ public virtual async Task OrderByRank_FullTextScore_parameter_array() """ SELECT VALUE c FROM root c -ORDER BY RANK FullTextScore(c["Description"], ["otter","beaver"]) +ORDER BY RANK FullTextScore(c["Description"], "otter", "beaver") """); } @@ -384,7 +384,7 @@ public virtual async Task OrderByRank_FullTextScore_using_parameters() SELECT VALUE c FROM root c -ORDER BY RANK FullTextScore(c["Description"], [@otter, @beaver]) +ORDER BY RANK FullTextScore(c["Description"], @otter, @beaver) """); } @@ -405,7 +405,7 @@ public virtual async Task OrderByRank_FullTextScore_using_one_parameter() SELECT VALUE c FROM root c -ORDER BY RANK FullTextScore(c["Description"], [@otter]) +ORDER BY RANK FullTextScore(c["Description"], @otter) """); } @@ -426,7 +426,28 @@ public virtual async Task OrderByRank_FullTextScore_using_parameters_constant_mi SELECT VALUE c FROM root c -ORDER BY RANK FullTextScore(c["Description"], ["otter", @beaver]) +ORDER BY RANK FullTextScore(c["Description"], "otter", @beaver) +"""); + } + + [ConditionalFact] + public virtual async Task OrderByRank_FullTextScore_using_parameters_constant_mix_inline() + { + await using var context = CreateContext(); + + var beaver = "beaver"; + + var result = await context.Set() + .OrderBy(x => EF.Functions.FullTextScore(x.Description, "otter", beaver)) + .ToListAsync(); + + AssertSql( +""" +@beaver='beaver' + +SELECT VALUE c +FROM root c +ORDER BY RANK FullTextScore(c["Description"], "otter", @beaver) """); } @@ -447,7 +468,7 @@ public virtual async Task OrderByRank_FullTextScore_using_parameter() SELECT VALUE c FROM root c -ORDER BY RANK FullTextScore(c["Description"], [@otter]) +ORDER BY RANK FullTextScore(c["Description"], @otter) """); } @@ -464,7 +485,7 @@ public virtual async Task OrderByRank_FullTextScore_using_complex_expression() .ToListAsync())).Message; Assert.Contains( - "The second argument of the FullTextScore function must be a non-empty array of string literals.", + "The second through last arguments of the FullTextScore function must be string literals.", message); } @@ -495,7 +516,7 @@ public virtual async Task OrderByRank_FullTextScore_on_non_FTS_property() """ SELECT VALUE c FROM root c -ORDER BY RANK FullTextScore(c["PartitionKey"], ["taxonomy"]) +ORDER BY RANK FullTextScore(c["PartitionKey"], "taxonomy") """); } @@ -514,7 +535,7 @@ public virtual async Task OrderByRank_with_RRF_using_two_FullTextScore_functions """ SELECT VALUE c FROM root c -ORDER BY RANK RRF(FullTextScore(c["Description"], ["beaver"]), FullTextScore(c["Description"], ["otter","bat"])) +ORDER BY RANK RRF(FullTextScore(c["Description"], "beaver"), FullTextScore(c["Description"], "otter", "bat")) """); } @@ -584,7 +605,7 @@ public virtual async Task OrderByRank_Take() """ SELECT VALUE c FROM root c -ORDER BY RANK FullTextScore(c["Description"], ["beaver"]) +ORDER BY RANK FullTextScore(c["Description"], "beaver") OFFSET 0 LIMIT 10 """); } @@ -603,7 +624,7 @@ public virtual async Task OrderByRank_Skip_Take() """ SELECT VALUE c FROM root c -ORDER BY RANK FullTextScore(c["Description"], ["beaver","dolphin"]) +ORDER BY RANK FullTextScore(c["Description"], "beaver", "dolphin") OFFSET 1 LIMIT 20 """); } @@ -633,7 +654,7 @@ public virtual async Task OrderBy_scoring_function_overridden_by_another() """ SELECT VALUE c FROM root c -ORDER BY RANK FullTextScore(c["Description"], ["beaver","dolphin","second"]) +ORDER BY RANK FullTextScore(c["Description"], "beaver", "dolphin", "second") """); } @@ -667,7 +688,7 @@ public virtual async Task Regular_OrderBy_overridden_by_OrderBy_using_scoring_fu """ SELECT VALUE c FROM root c -ORDER BY RANK FullTextScore(c["Description"], ["beaver","dolphin"]) +ORDER BY RANK FullTextScore(c["Description"], "beaver", "dolphin") """); } @@ -725,7 +746,7 @@ public virtual async Task OrderByRank_Where() SELECT VALUE c FROM root c WHERE ((c["PartitionKey"] || "Foo") = "habitatFoo") -ORDER BY RANK FullTextScore(c["Description"], ["beaver","dolphin"]) +ORDER BY RANK FullTextScore(c["Description"], "beaver", "dolphin") """); } @@ -779,7 +800,7 @@ public virtual async Task OrderByRank_with_FullTextScore_on_nested_owned_type() """ SELECT VALUE c FROM root c -ORDER BY RANK FullTextScore(c["Owned"]["NestedReference"]["AnotherDescription"], ["beaver","dolphin"]) +ORDER BY RANK FullTextScore(c["Owned"]["NestedReference"]["AnotherDescription"], "beaver", "dolphin") """); } @@ -816,7 +837,7 @@ public virtual async Task OrderByRank_with_FullTextScore_on_nested_owned_collect """ SELECT VALUE c FROM root c -ORDER BY RANK FullTextScore(c["Owned"]["NestedCollection"][0]["AnotherDescription"], ["beaver","dolphin"]) +ORDER BY RANK FullTextScore(c["Owned"]["NestedCollection"][0]["AnotherDescription"], "beaver", "dolphin") """); } @@ -832,7 +853,7 @@ public virtual async Task OrderBy_scoring_function_on_property_with_modified_jso """ SELECT VALUE c FROM root c -ORDER BY RANK FullTextScore(c["CustomDecription"], ["beaver","dolphin"]) +ORDER BY RANK FullTextScore(c["CustomDecription"], "beaver", "dolphin") """); } @@ -849,7 +870,7 @@ public virtual async Task OrderByRank_with_FullTextScore_on_nested_owned_type_wi """ SELECT VALUE c FROM root c -ORDER BY RANK FullTextScore(c["Owned"]["CustomNestedReference"]["AnotherDescription"], ["beaver","dolphin"]) +ORDER BY RANK FullTextScore(c["Owned"]["CustomNestedReference"]["AnotherDescription"], "beaver", "dolphin") """); } @@ -866,7 +887,7 @@ public virtual async Task OrderByRank_with_FullTextScore_on_property_without_ind """ SELECT VALUE c FROM root c -ORDER BY RANK FullTextScore(c["DescriptionNoIndex"], ["beaver","dolphin"]) +ORDER BY RANK FullTextScore(c["DescriptionNoIndex"], "beaver", "dolphin") """); } diff --git a/test/EFCore.Cosmos.FunctionalTests/HybridSearchCosmosTest.cs b/test/EFCore.Cosmos.FunctionalTests/HybridSearchCosmosTest.cs index 50007892eee..9aa78fbcfa4 100644 --- a/test/EFCore.Cosmos.FunctionalTests/HybridSearchCosmosTest.cs +++ b/test/EFCore.Cosmos.FunctionalTests/HybridSearchCosmosTest.cs @@ -39,7 +39,7 @@ public virtual async Task Hybrid_search_vector_distance_and_FullTextScore_in_Ord SELECT VALUE c FROM root c -ORDER BY RANK RRF(FullTextScore(c["Description"], ["beaver","otter"]), VectorDistance(c["SBytes"], @inputVector, false, {'distanceFunction':'dotproduct', 'dataType':'int8'})) +ORDER BY RANK RRF(FullTextScore(c["Description"], "beaver", "otter"), VectorDistance(c["SBytes"], @inputVector, false, {'distanceFunction':'dotproduct', 'dataType':'int8'})) """); } @@ -62,7 +62,7 @@ public virtual async Task Hybrid_search_vector_distance_and_FullTextScore_with_s SELECT VALUE c FROM root c -ORDER BY RANK RRF(FullTextScore(c["Description"], ["beaver"]), VectorDistance(c["SBytes"], @inputVector, false, {'distanceFunction':'dotproduct', 'dataType':'int8'})) +ORDER BY RANK RRF(FullTextScore(c["Description"], "beaver"), VectorDistance(c["SBytes"], @inputVector, false, {'distanceFunction':'dotproduct', 'dataType':'int8'})) """); } @@ -84,7 +84,7 @@ public virtual async Task Hybrid_search_vector_distance_and_FullTextScore_in_Ord SELECT VALUE c FROM root c -ORDER BY RANK RRF(FullTextScore(c["Owned"]["AnotherDescription"], ["beaver"]), VectorDistance(c["Owned"]["Singles"], @inputVector, false, {'distanceFunction':'cosine', 'dataType':'float32'})) +ORDER BY RANK RRF(FullTextScore(c["Owned"]["AnotherDescription"], "beaver"), VectorDistance(c["Owned"]["Singles"], @inputVector, false, {'distanceFunction':'cosine', 'dataType':'float32'})) """); } @@ -107,7 +107,7 @@ public virtual async Task Hybrid_search_vector_distance_and_FullTextScore_in_Ord SELECT VALUE c FROM root c -ORDER BY RANK RRF(VectorDistance(c["Owned"]["Singles"], @inputVector, false, {'distanceFunction':'cosine', 'dataType':'float32'}), FullTextScore(c["Owned"]["AnotherDescription"], ["beaver","otter"])) +ORDER BY RANK RRF(VectorDistance(c["Owned"]["Singles"], @inputVector, false, {'distanceFunction':'cosine', 'dataType':'float32'}), FullTextScore(c["Owned"]["AnotherDescription"], "beaver", "otter")) """); }