Skip to content

Commit 0933839

Browse files
committed
Cosmos Full Text Search support
- Adding model building API to configure property as full-text search enabled, as well as setup the index for it, - Adding model validation (e.g. FTS index not matching FTS property), - Adding EF.Functions stubs and translations for FullTextContains, FullTextContainsAll, FullTextContainsAny, FullTextScore and RRF (for hybrid), - Adding logic in SelectExpression to produce ORDER BY RANK when necessary, - Adding validation when attempting to mix with ORDER BY RANK with regular ORDER BY, - Rewrite OFFSET/LIMIT from parameter to constant when ORDER BY RANK is present. Also fixed / added support for vector search on owned types (since it shares logic with FTS) and added some tests. outstanding work: - support for FTS Container building using Azure.ResourceManager.CosmosDb (currently blocked on updated package being released) - add model building support for default language (superfluous for now, since only one language is supported), Fixes #35476 Fixes #35853 Fixes #35867 Fixes #35852
1 parent 667c647 commit 0933839

31 files changed

+2267
-83
lines changed

Diff for: Directory.Packages.props

+1-1
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@
3737
<PackageVersion Include="Microsoft.DotNet.Build.Tasks.Templating" Version="$(MicrosoftDotNetBuildTasksTemplatingVersion)" />
3838

3939
<!-- Azure SDK for .NET dependencies -->
40-
<PackageVersion Include="Microsoft.Azure.Cosmos" Version="3.46.0" />
40+
<PackageVersion Include="Microsoft.Azure.Cosmos" Version="3.49.0-preview.0" />
4141

4242
<!-- SQL Server dependencies -->
4343
<PackageVersion Include="Microsoft.Data.SqlClient" Version="6.0.1" />

Diff for: src/EFCore.Cosmos/EFCore.Cosmos.csproj

+1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
<NoWarn>$(NoWarn);EF9101</NoWarn> <!-- Metrics is experimental -->
1313
<NoWarn>$(NoWarn);EF9102</NoWarn> <!-- Paging is experimental -->
1414
<NoWarn>$(NoWarn);EF9103</NoWarn> <!-- Vector search is experimental -->
15+
<NoWarn>$(NoWarn);EF9104</NoWarn> <!-- Full-text search is experimental -->
1516
</PropertyGroup>
1617

1718
<ItemGroup>

Diff for: src/EFCore.Cosmos/Extensions/CosmosDbFunctionsExtensions.cs

+54
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,60 @@ public static T CoalesceUndefined<T>(
5252
T expression2)
5353
=> throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(CoalesceUndefined)));
5454

55+
/// <summary>
56+
/// Checks if the specified property contains the given keyword using full-text search.
57+
/// </summary>
58+
/// <param name="_">The <see cref="DbFunctions" /> instance.</param>
59+
/// <param name="property">The property to search.</param>
60+
/// <param name="keyword">The keyword to search for.</param>
61+
/// <returns><see langword="true" /> if the property contains the keyword; otherwise, <see langword="false" />.</returns>
62+
[Experimental(EFDiagnostics.CosmosFullTextSearchExperimental)]
63+
public static bool FullTextContains(this DbFunctions _, string property, string keyword)
64+
=> throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(FullTextContains)));
65+
66+
/// <summary>
67+
/// Checks if the specified property contains all the given keywords using full-text search.
68+
/// </summary>
69+
/// <param name="_">The <see cref="DbFunctions" /> instance.</param>
70+
/// <param name="property">The property to search.</param>
71+
/// <param name="keywords">The keywords to search for.</param>
72+
/// <returns><see langword="true" /> if the property contains all the keywords; otherwise, <see langword="false" />.</returns>
73+
[Experimental(EFDiagnostics.CosmosFullTextSearchExperimental)]
74+
public static bool FullTextContainsAll(this DbFunctions _, string property, params string[] keywords)
75+
=> throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(FullTextContainsAll)));
76+
77+
/// <summary>
78+
/// Checks if the specified property contains any of the given keywords using full-text search.
79+
/// </summary>
80+
/// <param name="_">The <see cref="DbFunctions" /> instance.</param>
81+
/// <param name="property">The property to search.</param>
82+
/// <param name="keywords">The keywords to search for.</param>
83+
/// <returns><see langword="true" /> if the property contains any of the keywords; otherwise, <see langword="false" />.</returns>
84+
[Experimental(EFDiagnostics.CosmosFullTextSearchExperimental)]
85+
public static bool FullTextContainsAny(this DbFunctions _, string property, params string[] keywords)
86+
=> throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(FullTextContainsAny)));
87+
88+
/// <summary>
89+
/// Returns the full-text search score for the specified property and keywords.
90+
/// </summary>
91+
/// <param name="_">The <see cref="DbFunctions" /> instance.</param>
92+
/// <param name="property">The property to search.</param>
93+
/// <param name="keywords">The keywords to search for.</param>
94+
/// <returns>The full-text search score.</returns>
95+
[Experimental(EFDiagnostics.CosmosFullTextSearchExperimental)]
96+
public static double FullTextScore(this DbFunctions _, string property, string[] keywords)
97+
=> throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(FullTextScore)));
98+
99+
/// <summary>
100+
/// Combines scores provided by two or more specified functions.
101+
/// </summary>
102+
/// <param name="_">The <see cref="DbFunctions" /> instance.</param>
103+
/// <param name="functions">The functions to compute the score for.</param>
104+
/// <returns>The combined score.</returns>
105+
[Experimental(EFDiagnostics.CosmosFullTextSearchExperimental)]
106+
public static double Rrf(this DbFunctions _, params double[] functions)
107+
=> throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(Rrf)));
108+
55109
/// <summary>
56110
/// Returns the distance between two vectors, using the distance function and data type defined using
57111
/// <see

Diff for: src/EFCore.Cosmos/Extensions/CosmosIndexBuilderExtensions.cs

+88-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ namespace Microsoft.EntityFrameworkCore;
1414
/// See <see href="https://aka.ms/efcore-docs-modeling">Modeling entity types and relationships</see>, and
1515
/// <see href="https://aka.ms/efcore-docs-cosmos">Accessing Azure Cosmos DB with EF Core</see> for more information and examples.
1616
/// </remarks>
17-
[Experimental(EFDiagnostics.CosmosVectorSearchExperimental)]
1817
public static class CosmosIndexBuilderExtensions
1918
{
2019
/// <summary>
@@ -28,6 +27,7 @@ public static class CosmosIndexBuilderExtensions
2827
/// <param name="indexBuilder">The builder for the index being configured.</param>
2928
/// <param name="indexType">The type of vector index to create.</param>
3029
/// <returns>A builder to further configure the index.</returns>
30+
[Experimental(EFDiagnostics.CosmosVectorSearchExperimental)]
3131
public static IndexBuilder ForVectors(this IndexBuilder indexBuilder, VectorIndexType? indexType)
3232
{
3333
indexBuilder.Metadata.SetVectorIndexType(indexType);
@@ -46,6 +46,7 @@ public static IndexBuilder ForVectors(this IndexBuilder indexBuilder, VectorInde
4646
/// <param name="indexBuilder">The builder for the index being configured.</param>
4747
/// <param name="indexType">The type of vector index to create.</param>
4848
/// <returns>A builder to further configure the index.</returns>
49+
[Experimental(EFDiagnostics.CosmosVectorSearchExperimental)]
4950
public static IndexBuilder<TEntity> ForVectors<TEntity>(
5051
this IndexBuilder<TEntity> indexBuilder,
5152
VectorIndexType? indexType)
@@ -66,6 +67,7 @@ public static IndexBuilder<TEntity> ForVectors<TEntity>(
6667
/// The same builder instance if the configuration was applied,
6768
/// <see langword="null" /> otherwise.
6869
/// </returns>
70+
[Experimental(EFDiagnostics.CosmosVectorSearchExperimental)]
6971
public static IConventionIndexBuilder? ForVectors(
7072
this IConventionIndexBuilder indexBuilder,
7173
VectorIndexType? indexType,
@@ -91,9 +93,94 @@ public static IndexBuilder<TEntity> ForVectors<TEntity>(
9193
/// <param name="indexType">The index type to use.</param>
9294
/// <param name="fromDataAnnotation">Indicates whether the configuration was specified using a data annotation.</param>
9395
/// <returns><see langword="true" /> if the index can be configured for vectors.</returns>
96+
[Experimental(EFDiagnostics.CosmosVectorSearchExperimental)]
9497
public static bool CanSetVectorIndexType(
9598
this IConventionIndexBuilder indexBuilder,
9699
VectorIndexType? indexType,
97100
bool fromDataAnnotation = false)
98101
=> indexBuilder.CanSetAnnotation(CosmosAnnotationNames.VectorIndexType, indexType, fromDataAnnotation);
102+
103+
/// <summary>
104+
/// Configures the index as a full-text index.
105+
/// See <see href="https://learn.microsoft.com/azure/cosmos-db/gen-ai/full-text-search">Full-text search in Azure Cosmos DB for NoSQL</see> for more information.
106+
/// </summary>
107+
/// <remarks>
108+
/// See <see href="https://aka.ms/efcore-docs-modeling">Modeling entity types and relationships</see>, and
109+
/// <see href="https://aka.ms/efcore-docs-cosmos">Accessing Azure Cosmos DB with EF Core</see> for more information and examples.
110+
/// </remarks>
111+
/// <param name="indexBuilder">The builder for the index being configured.</param>
112+
/// <param name="value">The value indicating whether the index is configured for Full-text search.</param>
113+
/// <returns>A builder to further configure the index.</returns>
114+
[Experimental(EFDiagnostics.CosmosFullTextSearchExperimental)]
115+
public static IndexBuilder IsFullTextIndex(this IndexBuilder indexBuilder, bool? value = true)
116+
{
117+
indexBuilder.Metadata.SetIsFullTextIndex(value);
118+
119+
return indexBuilder;
120+
}
121+
122+
/// <summary>
123+
/// Configures the index as a full-text index.
124+
/// See <see href="https://learn.microsoft.com/azure/cosmos-db/gen-ai/full-text-search">Full-text search in Azure Cosmos DB for NoSQL</see> for more information.
125+
/// </summary>
126+
/// <remarks>
127+
/// See <see href="https://aka.ms/efcore-docs-modeling">Modeling entity types and relationships</see>, and
128+
/// <see href="https://aka.ms/efcore-docs-cosmos">Accessing Azure Cosmos DB with EF Core</see> for more information and examples.
129+
/// </remarks>
130+
/// <param name="indexBuilder">The builder for the index being configured.</param>
131+
/// <param name="value">The value indicating whether the index is configured for Full-text search.</param>
132+
/// <returns>A builder to further configure the index.</returns>
133+
[Experimental(EFDiagnostics.CosmosFullTextSearchExperimental)]
134+
public static IndexBuilder<TEntity> IsFullTextIndex<TEntity>(
135+
this IndexBuilder<TEntity> indexBuilder,
136+
bool? value = true)
137+
=> (IndexBuilder<TEntity>)IsFullTextIndex((IndexBuilder)indexBuilder, value);
138+
139+
/// <summary>
140+
/// Configures the index as a full-text index.
141+
/// See <see href="https://learn.microsoft.com/azure/cosmos-db/gen-ai/full-text-search">Full-text search in Azure Cosmos DB for NoSQL</see> for more information.
142+
/// </summary>
143+
/// <remarks>
144+
/// See <see href="https://aka.ms/efcore-docs-modeling">Modeling entity types and relationships</see>, and
145+
/// <see href="https://aka.ms/efcore-docs-cosmos">Accessing Azure Cosmos DB with EF Core</see> for more information and examples.
146+
/// </remarks>
147+
/// <param name="indexBuilder">The builder for the index being configured.</param>
148+
/// <param name="value">The value indicating whether the index is configured for Full-text search.</param>
149+
/// <param name="fromDataAnnotation">Indicates whether the configuration was specified using a data annotation.</param>
150+
/// <returns>
151+
/// The same builder instance if the configuration was applied,
152+
/// <see langword="null" /> otherwise.
153+
/// </returns>
154+
[Experimental(EFDiagnostics.CosmosFullTextSearchExperimental)]
155+
public static IConventionIndexBuilder? IsFullTextIndex(
156+
this IConventionIndexBuilder indexBuilder,
157+
bool? value,
158+
bool fromDataAnnotation = false)
159+
{
160+
if (indexBuilder.CanSetIsFullTextIndex(fromDataAnnotation))
161+
{
162+
indexBuilder.Metadata.SetIsFullTextIndex(value, fromDataAnnotation);
163+
return indexBuilder;
164+
}
165+
166+
return null;
167+
}
168+
169+
/// <summary>
170+
/// Returns a value indicating whether the index can be configured as a full-text index.
171+
/// </summary>
172+
/// <remarks>
173+
/// See <see href="https://aka.ms/efcore-docs-modeling">Modeling entity types and relationships</see>, and
174+
/// <see href="https://aka.ms/efcore-docs-cosmos">Accessing Azure Cosmos DB with EF Core</see> for more information and examples.
175+
/// </remarks>
176+
/// <param name="indexBuilder">The builder for the index being configured.</param>
177+
/// <param name="value">The value indicating whether the index is configured for Full-text search.</param>
178+
/// <param name="fromDataAnnotation">Indicates whether the configuration was specified using a data annotation.</param>
179+
/// <returns><see langword="true" /> if the index can be configured as a Full-text index.</returns>
180+
[Experimental(EFDiagnostics.CosmosFullTextSearchExperimental)]
181+
public static bool CanSetIsFullTextIndex(
182+
this IConventionIndexBuilder indexBuilder,
183+
bool? value,
184+
bool fromDataAnnotation = false)
185+
=> indexBuilder.CanSetAnnotation(CosmosAnnotationNames.FullTextIndex, value, fromDataAnnotation);
99186
}

Diff for: src/EFCore.Cosmos/Extensions/CosmosIndexExtensions.cs

+53-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ namespace Microsoft.EntityFrameworkCore;
1414
/// See <see href="https://aka.ms/efcore-docs-modeling">Modeling entity types and relationships</see>, and
1515
/// <see href="https://aka.ms/efcore-docs-cosmos">Accessing Azure Cosmos DB with EF Core</see> for more information and examples.
1616
/// </remarks>
17-
[Experimental(EFDiagnostics.CosmosVectorSearchExperimental)]
1817
public static class CosmosIndexExtensions
1918
{
2019
/// <summary>
@@ -23,6 +22,7 @@ public static class CosmosIndexExtensions
2322
/// </summary>
2423
/// <param name="index">The index.</param>
2524
/// <returns>The index type to use, or <see langword="null" /> if none is set.</returns>
25+
[Experimental(EFDiagnostics.CosmosVectorSearchExperimental)]
2626
public static VectorIndexType? GetVectorIndexType(this IReadOnlyIndex index)
2727
=> (index is RuntimeIndex)
2828
? throw new InvalidOperationException(CoreStrings.RuntimeModelMissingData)
@@ -34,6 +34,7 @@ public static class CosmosIndexExtensions
3434
/// </summary>
3535
/// <param name="index">The index.</param>
3636
/// <param name="indexType">The index type to use.</param>
37+
[Experimental(EFDiagnostics.CosmosVectorSearchExperimental)]
3738
public static void SetVectorIndexType(this IMutableIndex index, VectorIndexType? indexType)
3839
=> index.SetAnnotation(CosmosAnnotationNames.VectorIndexType, indexType);
3940

@@ -45,6 +46,7 @@ public static void SetVectorIndexType(this IMutableIndex index, VectorIndexType?
4546
/// <param name="index">The index.</param>
4647
/// <param name="fromDataAnnotation">Indicates whether the configuration was specified using a data annotation.</param>
4748
/// <returns>The configured value.</returns>
49+
[Experimental(EFDiagnostics.CosmosVectorSearchExperimental)]
4850
public static string? SetVectorIndexType(
4951
this IConventionIndex index,
5052
VectorIndexType? indexType,
@@ -59,6 +61,56 @@ public static void SetVectorIndexType(this IMutableIndex index, VectorIndexType?
5961
/// </summary>
6062
/// <param name="property">The property.</param>
6163
/// <returns>The <see cref="ConfigurationSource" /> for whether the index is clustered.</returns>
64+
[Experimental(EFDiagnostics.CosmosVectorSearchExperimental)]
6265
public static ConfigurationSource? GetVectorIndexTypeConfigurationSource(this IConventionIndex property)
6366
=> property.FindAnnotation(CosmosAnnotationNames.VectorIndexType)?.GetConfigurationSource();
67+
68+
/// <summary>
69+
/// Returns the value indicating whether the index is configured for full-text search.
70+
/// See <see href="https://learn.microsoft.com/azure/cosmos-db/gen-ai/full-text-search">Full-text search in Azure Cosmos DB for NoSQL</see> for more information.
71+
/// </summary>
72+
/// <param name="index">The index.</param>
73+
/// <returns>The index type to use, or <see langword="null" /> if none is set.</returns>
74+
[Experimental(EFDiagnostics.CosmosFullTextSearchExperimental)]
75+
public static bool? IsFullTextIndex(this IReadOnlyIndex index)
76+
=> (index is RuntimeIndex)
77+
? throw new InvalidOperationException(CoreStrings.RuntimeModelMissingData)
78+
: (bool?)index[CosmosAnnotationNames.FullTextIndex];
79+
80+
/// <summary>
81+
/// Configures the index for full-text search.
82+
/// See <see href="https://learn.microsoft.com/azure/cosmos-db/gen-ai/full-text-search">Full-text search in Azure Cosmos DB for NoSQL</see> for more information.
83+
/// </summary>
84+
/// <param name="index">The index.</param>
85+
/// <param name="value">The value indicating whether the index is configured for full-text search.</param>
86+
[Experimental(EFDiagnostics.CosmosFullTextSearchExperimental)]
87+
public static void SetIsFullTextIndex(this IMutableIndex index, bool? value)
88+
=> index.SetAnnotation(CosmosAnnotationNames.FullTextIndex, value);
89+
90+
/// <summary>
91+
/// Configures the index for full-text search.
92+
/// See <see href="https://learn.microsoft.com/azure/cosmos-db/gen-ai/full-text-search">Full-text search in Azure Cosmos DB for NoSQL</see> for more information.
93+
/// </summary>
94+
/// <param name="index">The index.</param>
95+
/// <param name="value">The value indicating whether the index is configured for full-text search.</param>
96+
/// <param name="fromDataAnnotation">Indicates whether the configuration was specified using a data annotation.</param>
97+
/// <returns>The configured value.</returns>
98+
[Experimental(EFDiagnostics.CosmosFullTextSearchExperimental)]
99+
public static string? SetIsFullTextIndex(
100+
this IConventionIndex index,
101+
bool? value,
102+
bool fromDataAnnotation = false)
103+
=> (string?)index.SetAnnotation(
104+
CosmosAnnotationNames.FullTextIndex,
105+
value,
106+
fromDataAnnotation)?.Value;
107+
108+
/// <summary>
109+
/// Returns the <see cref="ConfigurationSource" /> for whether the <see cref="IsFullTextIndex" />.
110+
/// </summary>
111+
/// <param name="property">The property.</param>
112+
/// <returns>The <see cref="ConfigurationSource" /> for whether the index is clustered.</returns>
113+
[Experimental(EFDiagnostics.CosmosFullTextSearchExperimental)]
114+
public static ConfigurationSource? GetIsFullTextIndexConfigurationSource(this IConventionIndex property)
115+
=> property.FindAnnotation(CosmosAnnotationNames.FullTextIndex)?.GetConfigurationSource();
64116
}

0 commit comments

Comments
 (0)