Skip to content
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

Support binary columns with SQL Server FREETEXT/CONTAINS #24033

Merged
merged 4 commits into from
Feb 9, 2021
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 @@ -25,8 +25,12 @@ public static class RelationalDbFunctionsExtensions
/// information.
/// </para>
/// </summary>
/// <remarks>
/// This DbFunction method has no in-memory implementation and will throw if the query switches to client-evaluation.
/// This can happen if the query contains one or more expressions that could not be translated to the store.
/// </remarks>
/// <typeparam name="TProperty"> The type of the operand on which the collation is being specified. </typeparam>
/// <param name="_"> The DbFunctions instance. </param>
/// <param name="_"> The <see cref="DbFunctions" /> instance. </param>
/// <param name="operand"> The operand to which to apply the collation. </param>
/// <param name="collation"> The name of the collation. </param>
public static TProperty Collate<TProperty>(
Expand Down
425 changes: 215 additions & 210 deletions src/EFCore.SqlServer/Extensions/SqlServerDbFunctionsExtensions.cs

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -29,23 +29,19 @@ public class SqlServerFullTextSearchFunctionsTranslator : IMethodCallTranslator

private static readonly MethodInfo _freeTextMethodInfo
= typeof(SqlServerDbFunctionsExtensions).GetRequiredRuntimeMethod(
nameof(SqlServerDbFunctionsExtensions.FreeText),
new[] { typeof(DbFunctions), typeof(string), typeof(string) });
nameof(SqlServerDbFunctionsExtensions.FreeText), typeof(DbFunctions), typeof(object), typeof(string));

private static readonly MethodInfo _freeTextMethodInfoWithLanguage
= typeof(SqlServerDbFunctionsExtensions).GetRequiredRuntimeMethod(
nameof(SqlServerDbFunctionsExtensions.FreeText),
new[] { typeof(DbFunctions), typeof(string), typeof(string), typeof(int) });
nameof(SqlServerDbFunctionsExtensions.FreeText), typeof(DbFunctions), typeof(object), typeof(string), typeof(int));

private static readonly MethodInfo _containsMethodInfo
= typeof(SqlServerDbFunctionsExtensions).GetRequiredRuntimeMethod(
nameof(SqlServerDbFunctionsExtensions.Contains),
new[] { typeof(DbFunctions), typeof(string), typeof(string) });
nameof(SqlServerDbFunctionsExtensions.Contains), typeof(DbFunctions), typeof(object), typeof(string));

private static readonly MethodInfo _containsMethodInfoWithLanguage
= typeof(SqlServerDbFunctionsExtensions).GetRequiredRuntimeMethod(
nameof(SqlServerDbFunctionsExtensions.Contains),
new[] { typeof(DbFunctions), typeof(string), typeof(string), typeof(int) });
nameof(SqlServerDbFunctionsExtensions.Contains), typeof(DbFunctions), typeof(object), typeof(string), typeof(int));

private static readonly IDictionary<MethodInfo, string> _functionMapping
= new Dictionary<MethodInfo, string>
Expand Down Expand Up @@ -94,7 +90,9 @@ public SqlServerFullTextSearchFunctionsTranslator([NotNull] ISqlExpressionFactor
}

var typeMapping = propertyReference.TypeMapping;
var freeText = _sqlExpressionFactory.ApplyTypeMapping(arguments[2], typeMapping);
var freeText = propertyReference.Type == arguments[2].Type
? _sqlExpressionFactory.ApplyTypeMapping(arguments[2], typeMapping)
: _sqlExpressionFactory.ApplyDefaultTypeMapping(arguments[2]);

var functionArguments = new List<SqlExpression> { propertyReference, freeText };

Expand Down
33 changes: 15 additions & 18 deletions src/EFCore.Sqlite.Core/Extensions/SqliteDbFunctionsExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,43 +5,42 @@
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore.Diagnostics;

// ReSharper disable once CheckNamespace
namespace Microsoft.EntityFrameworkCore
{
/// <summary>
/// Contains extension methods on <see cref="DbFunctions"/> for the Micosoft.EntityFrameworkCore.Sqlite provider.
/// Contains extension methods on <see cref="DbFunctions"/> for the Microsoft.EntityFrameworkCore.Sqlite provider.
/// </summary>
public static class SqliteDbFunctionsExtensions
{
/// <summary>
/// Maps to the SQLite glob function which is similar to
/// Maps to the SQLite <c>glob</c> function which is similar to
/// <see cref="DbFunctionsExtensions.Like(DbFunctions, string, string)"/> but uses the file system globbing
/// syntax instead.
/// </summary>
/// <param name="_">The DbFunctions instance.</param>
/// <param name="_">The <see cref="DbFunctions" /> instance.</param>
/// <param name="matchExpression">The string that is to be matched.</param>
/// <param name="pattern">The pattern which may involve wildcards *,?,[,^,-,].</param>
/// <param name="pattern">The pattern which may involve wildcards <c>*,?,[,^,-,]</c>.</param>
/// <returns><see langword="true" /> if there is a match.</returns>
public static bool Glob([CanBeNull] this DbFunctions _, [CanBeNull] string matchExpression, [CanBeNull] string pattern)
=> throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(Glob)));

/// <summary>
/// Maps to the SQLite hex function which returns a hexidecimal string representing the specified value.
/// Maps to the SQLite <c>hex</c> function which returns a hexadecimal string representing the specified value.
/// </summary>
/// <param name="_">The DbFunctions instance.</param>
/// <param name="_">The <see cref="DbFunctions" /> instance.</param>
/// <param name="bytes">The binary value.</param>
/// <returns>A hexadecimal string.</returns>
public static string Hex([CanBeNull] this DbFunctions _, [CanBeNull] byte[] bytes)
=> throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(Hex)));

/// <summary>
/// Maps to the SQLite substr function which returns a subarray of the specified value. The subarray starts
/// at the specified index and continues to the end of the value.
/// Maps to the SQLite <c>substr</c> function which returns a subarray of the specified value. The subarray starts
/// at <paramref name="startIndex" /> and continues to the end of the value.
/// </summary>
/// <param name="_">The DbFunctions instance.</param>
/// <param name="_">The <see cref="DbFunctions" /> instance.</param>
/// <param name="bytes">The binary value.</param>
/// <param name="startIndex">
/// The 1-based starting index. If negative, the index is relative to the end of the value.
/// </param>
/// <param name="startIndex"> The 1-based starting index. If negative, the index is relative to the end of the value. </param>
/// <returns>The subarray.</returns>
/// <remarks>
/// Use <see cref="string.Substring(int)"/> for string values.
Expand All @@ -51,15 +50,13 @@ public static byte[] Substr([CanBeNull] this DbFunctions _, [CanBeNull] byte[] b

/// <summary>
/// Maps to the SQLite substr function which returns a subarray of the specified value. The subarray starts
/// at the specified index and has the specified length.
/// at <paramref name="startIndex" /> and has the specified <paramref name="length"/>.
/// </summary>
/// <param name="_">The DbFunctions instance.</param>
/// <param name="_">The <see cref="DbFunctions" /> instance.</param>
/// <param name="bytes">The binary value.</param>
/// <param name="startIndex">
/// The 1-based starting index. If negative, the index is relative to the end of the value.
/// </param>
/// <param name="startIndex"> The 1-based starting index. If negative, the index is relative to the end of the value. </param>
/// <param name="length">
/// The length of the subarray. If negative, bytes preceding the start index are returned.
/// The length of the subarray. If negative, bytes preceding <paramref name="startIndex" /> are returned.
/// </param>
/// <returns>The subarray.</returns>
/// <remarks>
Expand Down
39 changes: 22 additions & 17 deletions src/EFCore/DbFunctionsExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,19 +16,21 @@ public static class DbFunctionsExtensions
{
/// <summary>
/// <para>
/// An implementation of the SQL LIKE operation. On relational databases this is usually directly
/// An implementation of the SQL <c>LIKE</c> operation. On relational databases this is usually directly
/// translated to SQL.
/// </para>
/// <para>
/// Note that if this function is translated into SQL, then the semantics of the comparison will
/// depend on the database configuration. In particular, it may be either case-sensitive or
/// case-insensitive. If this function is evaluated on the client, then it will always use
/// a case-insensitive comparison.
/// Note that the semantics of the comparison will depend on the database configuration.
/// In particular, it may be either case-sensitive or case-insensitive.
/// </para>
/// </summary>
/// <param name="_">The DbFunctions instance.</param>
/// <remarks>
/// This DbFunction method has no in-memory implementation and will throw if the query switches to client-evaluation.
/// This can happen if the query contains one or more expressions that could not be translated to the store.
/// </remarks>
/// <param name="_">The <see cref="DbFunctions" /> instance.</param>
/// <param name="matchExpression">The string that is to be matched.</param>
/// <param name="pattern">The pattern which may involve wildcards %,_,[,],^.</param>
/// <param name="pattern">The pattern which may involve wildcards <c>%,_,[,],^</c>.</param>
/// <returns><see langword="true" /> if there is a match.</returns>
public static bool Like(
[CanBeNull] this DbFunctions _,
Expand All @@ -42,13 +44,15 @@ public static bool Like(
/// translated to SQL.
/// </para>
/// <para>
/// Note that if this function is translated into SQL, then the semantics of the comparison will
/// depend on the database configuration. In particular, it may be either case-sensitive or
/// case-insensitive. If this function is evaluated on the client, then it will always use
/// a case-insensitive comparison.
/// Note that the semantics of the comparison will depend on the database configuration.
/// In particular, it may be either case-sensitive or case-insensitive.
/// </para>
/// </summary>
/// <param name="_">The DbFunctions instance.</param>
/// <remarks>
/// This DbFunction method has no in-memory implementation and will throw if the query switches to client-evaluation.
/// This can happen if the query contains one or more expressions that could not be translated to the store.
/// </remarks>
/// <param name="_">The <see cref="DbFunctions" /> instance.</param>
/// <param name="matchExpression">The string that is to be matched.</param>
/// <param name="pattern">The pattern which may involve wildcards %,_,[,],^.</param>
/// <param name="escapeCharacter">
Expand All @@ -64,12 +68,13 @@ public static bool Like(
=> throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(Like)));

/// <summary>
/// <para>
/// A random double number generator which generates a number between 0 and 1, exclusive.
/// This is usually directly translated to server.
/// </para>
/// A random double number generator which generates a number between 0 and 1, exclusive.
/// </summary>
/// <param name="_"> The DbFunctions instance. </param>
/// <remarks>
/// This DbFunction method has no in-memory implementation and will throw if the query switches to client-evaluation.
/// This can happen if the query contains one or more expressions that could not be translated to the store.
/// </remarks>
/// <param name="_"> The <see cref="DbFunctions" /> instance. </param>
/// <returns> A random double number between 0 and 1, exclusive. </returns>
public static double Random([CanBeNull] this DbFunctions _)
=> throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(Random)));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,47 @@ protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext con
base.OnModelCreating(modelBuilder, context);

modelBuilder.Entity<City>().Property(g => g.Location).HasColumnType("varchar(100)");

// Full-text binary search
modelBuilder.Entity<Mission>()
.Property<byte[]>("BriefingDocument");

modelBuilder.Entity<Mission>()
.Property<string>("BriefingDocumentFileExtension")
.HasColumnType("nvarchar(16)");
}

protected override void Seed(GearsOfWarContext context)
{
base.Seed(context);

// Set up full-text search and add some full-text binary data
context.Database.ExecuteSqlRaw(
@"
UPDATE [Missions]
SET
[BriefingDocumentFileExtension] = '.html',
[BriefingDocument] = CONVERT(varbinary(max), '<h1>Deploy the Lightmass Bomb to destroy the Locust Horde</h1>')
WHERE [Id] = 1;

UPDATE [Missions]
SET
[BriefingDocumentFileExtension] = '.html',
[BriefingDocument] = CONVERT(varbinary(max), '<h1>Two-day long military counterattack to kill the remaining Locust</h1>')
WHERE [Id] = 2;

IF (FULLTEXTSERVICEPROPERTY('IsFullTextInstalled') = 1)
BEGIN
IF EXISTS (SELECT 1 FROM sys.fulltext_catalogs WHERE [name] = 'GearsOfWar_FTC')
BEGIN
DROP FULLTEXT CATALOG GearsOfWar_FTC;
END

CREATE FULLTEXT CATALOG GearsOfWar_FTC AS DEFAULT;
CREATE FULLTEXT INDEX ON Missions (BriefingDocument TYPE COLUMN BriefingDocumentFileExtension) KEY INDEX PK_Missions;

WAITFOR DELAY '00:00:03';
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. Unfortunately there is some asynchronous process here, so if we run the tests too quickly after creating the full text catalog/index, we get back empty resullts (not an error, which is what you get when the catalog/index aren't created at all). 😡 😡 😡

END");
}
}
}
Loading