提交 e5997c05 编写于 作者: J Jean-Philippe Leconte 提交者: Shay Rojansky

Added pg_trgm module plugin (#1085)

Closes #897
上级 d7863b8a
Microsoft Visual Studio Solution File, Format Version 12.00
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
VisualStudioVersion = 15.0.27130.2026
MinimumVisualStudioVersion = 10.0.40219.1
......@@ -23,6 +23,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EFCore.PG.NodaTime", "src\E
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EFCore.PG.NTS", "src\EFCore.PG.NTS\EFCore.PG.NTS.csproj", "{D7106D61-C7CA-4005-B31F-43281BB397AD}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EFCore.PG.Trigrams", "src\EFCore.PG.Trigrams\EFCore.PG.Trigrams.csproj", "{4DEDE46C-FABB-4AD3-A7DF-BF4A3AF00B06}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
......@@ -93,6 +95,18 @@ Global
{D7106D61-C7CA-4005-B31F-43281BB397AD}.Release|x64.Build.0 = Release|Any CPU
{D7106D61-C7CA-4005-B31F-43281BB397AD}.Release|x86.ActiveCfg = Release|Any CPU
{D7106D61-C7CA-4005-B31F-43281BB397AD}.Release|x86.Build.0 = Release|Any CPU
{4DEDE46C-FABB-4AD3-A7DF-BF4A3AF00B06}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{4DEDE46C-FABB-4AD3-A7DF-BF4A3AF00B06}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4DEDE46C-FABB-4AD3-A7DF-BF4A3AF00B06}.Debug|x64.ActiveCfg = Debug|Any CPU
{4DEDE46C-FABB-4AD3-A7DF-BF4A3AF00B06}.Debug|x64.Build.0 = Debug|Any CPU
{4DEDE46C-FABB-4AD3-A7DF-BF4A3AF00B06}.Debug|x86.ActiveCfg = Debug|Any CPU
{4DEDE46C-FABB-4AD3-A7DF-BF4A3AF00B06}.Debug|x86.Build.0 = Debug|Any CPU
{4DEDE46C-FABB-4AD3-A7DF-BF4A3AF00B06}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4DEDE46C-FABB-4AD3-A7DF-BF4A3AF00B06}.Release|Any CPU.Build.0 = Release|Any CPU
{4DEDE46C-FABB-4AD3-A7DF-BF4A3AF00B06}.Release|x64.ActiveCfg = Release|Any CPU
{4DEDE46C-FABB-4AD3-A7DF-BF4A3AF00B06}.Release|x64.Build.0 = Release|Any CPU
{4DEDE46C-FABB-4AD3-A7DF-BF4A3AF00B06}.Release|x86.ActiveCfg = Release|Any CPU
{4DEDE46C-FABB-4AD3-A7DF-BF4A3AF00B06}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
......@@ -104,6 +118,7 @@ Global
{B78A7825-BE72-4509-B0AD-01EEC67A9624} = {ED612DB1-AB32-4603-95E7-891BACA71C39}
{77F0608F-6D0C-481C-9108-D5176E2EAD69} = {8537E50E-CF7F-49CB-B4EF-3E2A1B11F050}
{D7106D61-C7CA-4005-B31F-43281BB397AD} = {8537E50E-CF7F-49CB-B4EF-3E2A1B11F050}
{4DEDE46C-FABB-4AD3-A7DF-BF4A3AF00B06} = {8537E50E-CF7F-49CB-B4EF-3E2A1B11F050}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {F4EAAE6D-758C-4184-9D8C-7113384B61A8}
......
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<Authors>Jean-Philippe Leconte</Authors>
<Description>pg_trgm module support plugin for PostgreSQL/Npgsql Entity Framework Core provider.</Description>
<PackageTags>npgsql;postgresql;postgres;Entity Framework Core;entity-framework-core;ef;efcore;orm;sql;pg_trgm;trgm;trigram</PackageTags>
<AssemblyName>Npgsql.EntityFrameworkCore.PostgreSQL.Trigrams</AssemblyName>
<RootNamespace>Npgsql.EntityFrameworkCore.PostgreSQL.Trigrams</RootNamespace>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\EFCore.PG\EFCore.PG.csproj" />
</ItemGroup>
<ItemGroup>
<Compile Include="..\Shared\*.cs" />
<None Include="build\**\*">
<Pack>True</Pack>
<PackagePath>build</PackagePath>
</None>
</ItemGroup>
</Project>
using Microsoft.EntityFrameworkCore.Infrastructure;
using Npgsql.EntityFrameworkCore.PostgreSQL.Infrastructure;
using Npgsql.EntityFrameworkCore.PostgreSQL.Infrastructure.Internal;
namespace Microsoft.EntityFrameworkCore
{
/// <summary>
/// pg_trgm module specific extension methods for <see cref="NpgsqlDbContextOptionsBuilder"/>.
/// </summary>
public static class NpgsqlTrigramsDbContextOptionsBuilderExtensions
{
/// <summary>
/// Enable pg_trgm module methods and operators.
/// </summary>
/// <param name="optionsBuilder">The build being used to configure Postgres.</param>
/// <returns>The options builder so that further configuration can be chained.</returns>
public static NpgsqlDbContextOptionsBuilder UseTrigrams(
this NpgsqlDbContextOptionsBuilder optionsBuilder)
{
var coreOptionsBuilder = ((IRelationalDbContextOptionsBuilderInfrastructure)optionsBuilder).OptionsBuilder;
var extension = coreOptionsBuilder.Options.FindExtension<NpgsqlTrigramsOptionsExtension>()
?? new NpgsqlTrigramsOptionsExtension();
((IDbContextOptionsBuilderInfrastructure)coreOptionsBuilder).AddOrUpdateExtension(extension);
return optionsBuilder;
}
}
}
using System;
namespace Microsoft.EntityFrameworkCore
{
public static class NpgsqlTrigramsDbFunctionsExtensions
{
/// <summary>
/// Returns an array of all the trigrams in the given <paramref name="text" />.
/// (In practice this is seldom useful except for debugging.)
/// </summary>
/// <remarks>
/// The method call is translated to <c>show_trgm(text)</c>.
///
/// See https://www.postgresql.org/docs/current/pgtrgm.html.
/// </remarks>
public static string[] TrigramsShow(this DbFunctions _, string text) =>
throw new NotSupportedException();
/// <summary>
/// Returns a number that indicates how similar the two arguments are.
/// The range of the result is zero (indicating that the two strings are
/// completely dissimilar) to one (indicating that the two strings are identical).
/// </summary>
/// <remarks>
/// The method call is translated to <c>similarity(source, target)</c>.
///
/// See https://www.postgresql.org/docs/current/pgtrgm.html.
/// </remarks>
public static double TrigramsSimilarity(this DbFunctions _, string source, string target) =>
throw new NotSupportedException();
/// <summary>
/// Returns a number that indicates the greatest similarity between the set of trigrams
/// in the first string and any continuous extent of an ordered set of trigrams
/// in the second string.
/// </summary>
/// <remarks>
/// The method call is translated to <c>word_similarity(source, target)</c>.
///
/// See https://www.postgresql.org/docs/current/pgtrgm.html.
/// </remarks>
public static double TrigramsWordSimilarity(this DbFunctions _, string source, string target) =>
throw new NotSupportedException();
/// <summary>
/// Same as word_similarity(text, text), but forces extent boundaries to match word boundaries.
/// Since we don't have cross-word trigrams, this function actually returns greatest similarity
/// between first string and any continuous extent of words of the second string.
/// </summary>
/// <remarks>
/// The method call is translated to <c>strict_word_similarity(source, target)</c>.
///
/// See https://www.postgresql.org/docs/current/pgtrgm.html.
/// </remarks>
public static double TrigramsStrictWordSimilarity(this DbFunctions _, string source, string target) =>
throw new NotSupportedException();
/// <summary>
/// Returns true if its arguments have a similarity that is greater than the current similarity
/// threshold set by pg_trgm.similarity_threshold.
/// </summary>
/// <remarks>
/// The method call is translated to <c>source % target</c>.
///
/// See https://www.postgresql.org/docs/current/pgtrgm.html.
/// </remarks>
public static bool TrigramsAreSimilar(this DbFunctions _, string source, string target) =>
throw new NotSupportedException();
/// <summary>
/// Returns true if the similarity between the trigram set in the first argument and a continuous
/// extent of an ordered trigram set in the second argument is greater than the current word similarity
/// threshold set by pg_trgm.word_similarity_threshold parameter.
/// </summary>
/// <remarks>
/// The method call is translated to <c>source &lt;% target</c>.
///
/// See https://www.postgresql.org/docs/current/pgtrgm.html.
/// </remarks>
public static bool TrigramsAreWordSimilar(this DbFunctions _, string source, string target) =>
throw new NotSupportedException();
/// <summary>
/// Commutator of the &lt;% operator.
/// </summary>
/// <remarks>
/// The method call is translated to <c>source %&gt; target</c>.
///
/// See https://www.postgresql.org/docs/current/pgtrgm.html.
/// </remarks>
public static bool TrigramsAreNotWordSimilar(this DbFunctions _, string source, string target) =>
throw new NotSupportedException();
/// <summary>
/// Returns true if its second argument has a continuous extent of an ordered trigram set that
/// matches word boundaries, and its similarity to the trigram set of the first argument is greater
/// than the current strict word similarity threshold set by the pg_trgm.strict_word_similarity_threshold
/// parameter.
/// </summary>
/// <remarks>
/// The method call is translated to <c>source &lt;&lt;% target</c>.
///
/// See https://www.postgresql.org/docs/current/pgtrgm.html.
/// </remarks>
public static bool TrigramsAreStrictWordSimilar(this DbFunctions _, string source, string target) =>
throw new NotSupportedException();
/// <summary>
/// Commutator of the &lt;&lt;% operator.
/// </summary>
/// <remarks>
/// The method call is translated to <c>source %&gt;&gt; target</c>.
///
/// See https://www.postgresql.org/docs/current/pgtrgm.html.
/// </remarks>
public static bool TrigramsAreNotStrictWordSimilar(this DbFunctions _, string source, string target) =>
throw new NotSupportedException();
/// <summary>
/// Returns the "distance" between the arguments, that is one minus the similarity() value.
/// </summary>
/// <remarks>
/// The method call is translated to <c>source &lt;-&gt; target</c>.
///
/// See https://www.postgresql.org/docs/current/pgtrgm.html.
/// </remarks>
public static double TrigramsSimilarityDistance(this DbFunctions _, string source, string target) =>
throw new NotSupportedException();
/// <summary>
/// Returns the "distance" between the arguments, that is one minus the word_similarity() value.
/// </summary>
/// <remarks>
/// The method call is translated to <c>source &lt;&lt;-&gt; target</c>.
///
/// See https://www.postgresql.org/docs/current/pgtrgm.html.
/// </remarks>
public static double TrigramsWordSimilarityDistance(this DbFunctions _, string source, string target) =>
throw new NotSupportedException();
/// <summary>
/// Commutator of the &lt;&lt;-&gt; operator.
/// </summary>
/// <remarks>
/// The method call is translated to <c>source &lt;-&gt;&gt; target</c>.
///
/// See https://www.postgresql.org/docs/current/pgtrgm.html.
/// </remarks>
public static double TrigramsWordSimilarityDistanceInverted(this DbFunctions _, string source, string target) =>
throw new NotSupportedException();
/// <summary>
/// Returns the "distance" between the arguments, that is one minus the strict_word_similarity() value.
/// </summary>
/// <remarks>
/// The method call is translated to <c>source &lt;&lt;&lt;-&gt; target</c>.
///
/// See https://www.postgresql.org/docs/current/pgtrgm.html.
/// </remarks>
public static double TrigramsStrictWordSimilarityDistance(this DbFunctions _, string source, string target) =>
throw new NotSupportedException();
/// <summary>
/// Commutator of the &lt;&lt;&lt;-&gt; operator.
/// </summary>
/// <remarks>
/// The method call is translated to <c>source &lt;-&gt;&gt;&gt; target</c>.
///
/// See https://www.postgresql.org/docs/current/pgtrgm.html.
/// </remarks>
public static double TrigramsStrictWordSimilarityDistanceInverted(this DbFunctions _, string source, string target) =>
throw new NotSupportedException();
}
}
\ No newline at end of file
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Query;
using Npgsql.EntityFrameworkCore.PostgreSQL.Query.ExpressionTranslators.Internal;
namespace Microsoft.Extensions.DependencyInjection
{
/// <summary>
/// pg_trgm module extension methods for <see cref="IServiceCollection"/>.
/// </summary>
public static class NpgsqlTrigramsServiceCollectionExtensions
{
/// <summary>
/// Adds the services required for pg_trgm support in the Npgsql provider for Entity Framework.
/// </summary>
/// <param name="serviceCollection">The <see cref="IServiceCollection"/> to add services to.</param>
/// <returns>The same service collection so that multiple calls can be chained.</returns>
public static IServiceCollection AddEntityFrameworkNpgsqlTrigrams(
this IServiceCollection serviceCollection)
{
new EntityFrameworkRelationalServicesBuilder(serviceCollection)
.TryAddProviderSpecificServices(
x => x.TryAddSingletonEnumerable<IMethodCallTranslatorPlugin, NpgsqlTrigramsMethodCallTranslatorPlugin>());
return serviceCollection;
}
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Query;
using Microsoft.Extensions.DependencyInjection;
using Npgsql.EntityFrameworkCore.PostgreSQL.Query.ExpressionTranslators.Internal;
namespace Npgsql.EntityFrameworkCore.PostgreSQL.Infrastructure.Internal
{
public class NpgsqlTrigramsOptionsExtension : IDbContextOptionsExtension
{
public DbContextOptionsExtensionInfo Info => new ExtensionInfo(this);
public virtual void ApplyServices(IServiceCollection services) => services.AddEntityFrameworkNpgsqlTrigrams();
public virtual void Validate(IDbContextOptions options)
{
var internalServiceProvider = options.FindExtension<CoreOptionsExtension>()?.InternalServiceProvider;
if (internalServiceProvider != null)
{
using var scope = internalServiceProvider.CreateScope();
var plugins = scope.ServiceProvider.GetService<IEnumerable<IMethodCallTranslatorPlugin>>();
if (plugins is null || !plugins.Any(s => s is NpgsqlTrigramsMethodCallTranslatorPlugin))
{
throw new InvalidOperationException($"{nameof(NpgsqlTrigramsDbContextOptionsBuilderExtensions.UseTrigrams)} requires {nameof(NpgsqlTrigramsServiceCollectionExtensions.AddEntityFrameworkNpgsqlTrigrams)} to be called on the internal service provider used.");
}
}
}
sealed class ExtensionInfo : DbContextOptionsExtensionInfo
{
public ExtensionInfo(IDbContextOptionsExtension extension)
: base(extension)
{
}
public override bool IsDatabaseProvider => false;
public override long GetServiceProviderHashCode() => 0;
public override void PopulateDebugInfo(IDictionary<string, string> debugInfo)
=> debugInfo["Npgsql:" + nameof(NpgsqlTrigramsDbContextOptionsBuilderExtensions.UseTrigrams)] = "1";
public override string LogFragment => "using Trigrams ";
}
}
}
using System.Collections.Generic;
using Microsoft.EntityFrameworkCore.Query;
using Microsoft.EntityFrameworkCore.Storage;
using Npgsql.EntityFrameworkCore.PostgreSQL.Query.Internal;
namespace Npgsql.EntityFrameworkCore.PostgreSQL.Query.ExpressionTranslators.Internal
{
public class NpgsqlTrigramsMethodCallTranslatorPlugin : IMethodCallTranslatorPlugin
{
public NpgsqlTrigramsMethodCallTranslatorPlugin(
IRelationalTypeMappingSource typeMappingSource,
ISqlExpressionFactory sqlExpressionFactory)
=> Translators = new IMethodCallTranslator[]
{
new NpgsqlTrigramsMethodTranslator((NpgsqlSqlExpressionFactory) sqlExpressionFactory, typeMappingSource),
};
public virtual IEnumerable<IMethodCallTranslator> Translators { get; }
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Query;
using Microsoft.EntityFrameworkCore.Query.SqlExpressions;
using Microsoft.EntityFrameworkCore.Storage;
using Npgsql.EntityFrameworkCore.PostgreSQL.Query.Expressions.Internal;
using Npgsql.EntityFrameworkCore.PostgreSQL.Query.Internal;
namespace Npgsql.EntityFrameworkCore.PostgreSQL.Query.ExpressionTranslators.Internal
{
public class NpgsqlTrigramsMethodTranslator : IMethodCallTranslator
{
static readonly Dictionary<MethodInfo, string> Functions = new Dictionary<MethodInfo, string>
{
[GetRuntimeMethod(nameof(NpgsqlTrigramsDbFunctionsExtensions.TrigramsShow), new[] { typeof(DbFunctions), typeof(string) })] = "show_trgm",
[GetRuntimeMethod(nameof(NpgsqlTrigramsDbFunctionsExtensions.TrigramsSimilarity), new[] { typeof(DbFunctions), typeof(string), typeof(string) })] = "similarity",
[GetRuntimeMethod(nameof(NpgsqlTrigramsDbFunctionsExtensions.TrigramsWordSimilarity), new[] { typeof(DbFunctions), typeof(string), typeof(string) })] = "word_similarity",
[GetRuntimeMethod(nameof(NpgsqlTrigramsDbFunctionsExtensions.TrigramsStrictWordSimilarity), new[] { typeof(DbFunctions), typeof(string), typeof(string) })] = "strict_word_similarity"
};
static readonly Dictionary<MethodInfo, string> BoolReturningOperators = new Dictionary<MethodInfo, string>
{
[GetRuntimeMethod(nameof(NpgsqlTrigramsDbFunctionsExtensions.TrigramsAreSimilar), new[] { typeof(DbFunctions), typeof(string), typeof(string) })] = "%",
[GetRuntimeMethod(nameof(NpgsqlTrigramsDbFunctionsExtensions.TrigramsAreWordSimilar), new[] { typeof(DbFunctions), typeof(string), typeof(string) })] = "<%",
[GetRuntimeMethod(nameof(NpgsqlTrigramsDbFunctionsExtensions.TrigramsAreNotWordSimilar), new[] { typeof(DbFunctions), typeof(string), typeof(string) })] = "%>",
[GetRuntimeMethod(nameof(NpgsqlTrigramsDbFunctionsExtensions.TrigramsAreStrictWordSimilar), new[] { typeof(DbFunctions), typeof(string), typeof(string) })] = "<<%",
[GetRuntimeMethod(nameof(NpgsqlTrigramsDbFunctionsExtensions.TrigramsAreNotStrictWordSimilar), new[] { typeof(DbFunctions), typeof(string), typeof(string) })] = "%>>"
};
static readonly Dictionary<MethodInfo, string> FloatReturningOperators = new Dictionary<MethodInfo, string>
{
[GetRuntimeMethod(nameof(NpgsqlTrigramsDbFunctionsExtensions.TrigramsSimilarityDistance), new[] { typeof(DbFunctions), typeof(string), typeof(string) })] = "<->",
[GetRuntimeMethod(nameof(NpgsqlTrigramsDbFunctionsExtensions.TrigramsWordSimilarityDistance), new[] { typeof(DbFunctions), typeof(string), typeof(string) })] = "<<->",
[GetRuntimeMethod(nameof(NpgsqlTrigramsDbFunctionsExtensions.TrigramsWordSimilarityDistanceInverted), new[] { typeof(DbFunctions), typeof(string), typeof(string) })] = "<->>",
[GetRuntimeMethod(nameof(NpgsqlTrigramsDbFunctionsExtensions.TrigramsStrictWordSimilarityDistance), new[] { typeof(DbFunctions), typeof(string), typeof(string) })] = "<<<->",
[GetRuntimeMethod(nameof(NpgsqlTrigramsDbFunctionsExtensions.TrigramsStrictWordSimilarityDistanceInverted), new[] { typeof(DbFunctions), typeof(string), typeof(string) })] = "<->>>"
};
static MethodInfo GetRuntimeMethod(string name, params Type[] parameters)
=> typeof(NpgsqlTrigramsDbFunctionsExtensions).GetRuntimeMethod(name, parameters);
readonly NpgsqlSqlExpressionFactory _sqlExpressionFactory;
readonly RelationalTypeMapping _boolMapping;
readonly RelationalTypeMapping _floatMapping;
public NpgsqlTrigramsMethodTranslator(NpgsqlSqlExpressionFactory sqlExpressionFactory, IRelationalTypeMappingSource typeMappingSource)
{
_sqlExpressionFactory = sqlExpressionFactory;
_boolMapping = typeMappingSource.FindMapping(typeof(bool));
_floatMapping = typeMappingSource.FindMapping(typeof(float));
}
#pragma warning disable EF1001
/// <inheritdoc />
public SqlExpression Translate(SqlExpression instance, MethodInfo method, IReadOnlyList<SqlExpression> arguments)
{
if (Functions.TryGetValue(method, out var function))
return _sqlExpressionFactory.Function(function, arguments.Skip(1), method.ReturnType);
if (BoolReturningOperators.TryGetValue(method, out var boolOperator))
return new SqlCustomBinaryExpression(
_sqlExpressionFactory.ApplyDefaultTypeMapping(arguments[1]),
_sqlExpressionFactory.ApplyDefaultTypeMapping(arguments[2]),
boolOperator,
_boolMapping.ClrType,
_boolMapping);
if (FloatReturningOperators.TryGetValue(method, out var floatOperator))
return new SqlCustomBinaryExpression(
_sqlExpressionFactory.ApplyDefaultTypeMapping(arguments[1]),
_sqlExpressionFactory.ApplyDefaultTypeMapping(arguments[2]),
floatOperator,
_floatMapping.ClrType,
_floatMapping);
return null;
}
#pragma warning restore EF1001
}
}
......@@ -8,6 +8,7 @@
<ItemGroup>
<ProjectReference Include="..\..\src\EFCore.PG\EFCore.PG.csproj" />
<ProjectReference Include="..\..\src\EFCore.PG.NTS\EFCore.PG.NTS.csproj" />
<ProjectReference Include="..\..\src\EFCore.PG.Trigrams\EFCore.PG.Trigrams.csproj" />
</ItemGroup>
<ItemGroup>
......
using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.TestUtilities;
using Microsoft.Extensions.DependencyInjection;
using Npgsql.EntityFrameworkCore.PostgreSQL.Infrastructure;
using Npgsql.EntityFrameworkCore.PostgreSQL.TestUtilities;
using Npgsql.EntityFrameworkCore.PostgreSQL.TestUtilities.Xunit;
using Xunit;
using Xunit.Abstractions;
// ReSharper disable ConvertToConstant.Local
namespace Npgsql.EntityFrameworkCore.PostgreSQL.Query
{
/// <summary>
/// Provides unit tests for the pg_trgm module operator and function translations.
/// </summary>
/// <remarks>
/// See: https://www.postgresql.org/docs/current/pgtrgm.html
/// </remarks>
public class TrigramsQueryNpgsqlTest : IClassFixture<TrigramsQueryNpgsqlTest.TrigramsQueryNpgsqlFixture>
{
#region Setup
TrigramsQueryNpgsqlFixture Fixture { get; }
public TrigramsQueryNpgsqlTest(TrigramsQueryNpgsqlFixture fixture, ITestOutputHelper testOutputHelper)
{
Fixture = fixture;
Fixture.TestSqlLoggerFactory.Clear();
//Fixture.TestSqlLoggerFactory.SetTestOutputHelper(testOutputHelper);
}
#endregion
#region FunctionTests
[Fact]
public void TrigramsShow()
{
using (var context = Fixture.CreateContext())
{
var _ =
context.TrigramsTestEntities
.Select(x => EF.Functions.TrigramsShow(x.Text))
.ToArray();
AssertContainsSql(@"show_trgm(t.""Text"")");
}
}
[Fact]
public void TrigramsSimilarity()
{
using (var context = Fixture.CreateContext())
{
var _ =
context.TrigramsTestEntities
.Select(x => EF.Functions.TrigramsSimilarity(x.Text, "target"))
.ToArray();
AssertContainsSql(@"similarity(t.""Text"", 'target')");
}
}
[Fact]
public void TrigramsWordSimilarity()
{
using (var context = Fixture.CreateContext())
{
var _ =
context.TrigramsTestEntities
.Select(x => EF.Functions.TrigramsWordSimilarity(x.Text, "target"))
.ToArray();
AssertContainsSql(@"word_similarity(t.""Text"", 'target')");
}
}
[Fact]
public void TrigramsStrictWordSimilarity()
{
using (var context = Fixture.CreateContext())
{
var _ =
context.TrigramsTestEntities
.Select(x => EF.Functions.TrigramsStrictWordSimilarity(x.Text, "target"))
.ToArray();
AssertContainsSql(@"strict_word_similarity(t.""Text"", 'target')");
}
}
[Fact]
public void TrigramsAreSimilar()
{
using (var context = Fixture.CreateContext())
{
var _ =
context.TrigramsTestEntities
.Select(x => EF.Functions.TrigramsAreSimilar(x.Text, "target"))
.ToArray();
AssertContainsSql(@"t.""Text"" % 'target'");
}
}
[Fact]
public void TrigramsAreWordSimilar()
{
using (var context = Fixture.CreateContext())
{
var _ =
context.TrigramsTestEntities
.Select(x => EF.Functions.TrigramsAreWordSimilar(x.Text, "target"))
.ToArray();
AssertContainsSql(@"t.""Text"" <% 'target'");
}
}
[Fact]
public void TrigramsAreNotWordSimilar()
{
using (var context = Fixture.CreateContext())
{
var _ =
context.TrigramsTestEntities
.Select(x => EF.Functions.TrigramsAreNotWordSimilar(x.Text, "target"))
.ToArray();
AssertContainsSql(@"t.""Text"" %> 'target'");
}
}
[Fact]
public void TrigramsAreStrictWordSimilar()
{
using (var context = Fixture.CreateContext())
{
var _ =
context.TrigramsTestEntities
.Select(x => EF.Functions.TrigramsAreStrictWordSimilar(x.Text, "target"))
.ToArray();
AssertContainsSql(@"t.""Text"" <<% 'target'");
}
}
[Fact]
public void TrigramsAreNotStrictWordSimilar()
{
using (var context = Fixture.CreateContext())
{
var _ =
context.TrigramsTestEntities
.Select(x => EF.Functions.TrigramsAreNotStrictWordSimilar(x.Text, "target"))
.ToArray();
AssertContainsSql(@"t.""Text"" %>> 'target'");
}
}
[Fact]
public void TrigramsSimilarityDistance()
{
using (var context = Fixture.CreateContext())
{
var _ =
context.TrigramsTestEntities
.Select(x => EF.Functions.TrigramsSimilarityDistance(x.Text, "target"))
.ToArray();
AssertContainsSql(@"t.""Text"" <-> 'target'");
}
}
[Fact]
public void TrigramsWordSimilarityDistance()
{
using (var context = Fixture.CreateContext())
{
var _ =
context.TrigramsTestEntities
.Select(x => EF.Functions.TrigramsWordSimilarityDistance(x.Text, "target"))
.ToArray();
AssertContainsSql(@"t.""Text"" <<-> 'target'");
}
}
[Fact]
public void TrigramsWordSimilarityDistanceInverted()
{
using (var context = Fixture.CreateContext())
{
var _ =
context.TrigramsTestEntities
.Select(x => EF.Functions.TrigramsWordSimilarityDistanceInverted(x.Text, "target"))
.ToArray();
AssertContainsSql(@"t.""Text"" <->> 'target'");
}
}
[Fact]
public void TrigramsStrictWordSimilarityDistance()
{
using (var context = Fixture.CreateContext())
{
var _ =
context.TrigramsTestEntities
.Select(x => EF.Functions.TrigramsStrictWordSimilarityDistance(x.Text, "target"))
.ToArray();
AssertContainsSql(@"t.""Text"" <<<-> 'target'");
}
}
[Fact]
public void TrigramsStrictWordSimilarityDistanceInverted()
{
using (var context = Fixture.CreateContext())
{
var _ =
context.TrigramsTestEntities
.Select(x => EF.Functions.TrigramsStrictWordSimilarityDistanceInverted(x.Text, "target"))
.ToArray();
AssertContainsSql(@"t.""Text"" <->>> 'target'");
}
}
#endregion
#region Fixtures
/// <summary>
/// Represents a fixture suitable for testing trigrams operators.
/// </summary>
public class TrigramsQueryNpgsqlFixture : SharedStoreFixtureBase<TrigramsContext>
{
protected override string StoreName => "TrigramsQueryTest";
protected override IServiceCollection AddServices(IServiceCollection serviceCollection)
=> base.AddServices(serviceCollection).AddEntityFrameworkNpgsqlTrigrams();
public override DbContextOptionsBuilder AddOptions(DbContextOptionsBuilder builder)
{
var optionsBuilder = base.AddOptions(builder);
new NpgsqlDbContextOptionsBuilder(optionsBuilder).UseTrigrams();
return optionsBuilder;
}
protected override ITestStoreFactory TestStoreFactory => NpgsqlTestStoreFactory.Instance;
public TestSqlLoggerFactory TestSqlLoggerFactory => (TestSqlLoggerFactory)ListLoggerFactory;
protected override void Seed(TrigramsContext context) => TrigramsContext.Seed(context);
}
/// <summary>
/// Represents an entity suitable for testing trigrams operators.
/// </summary>
public class TrigramsTestEntity
{
// ReSharper disable once UnusedMember.Global
/// <summary>
/// The primary key.
/// </summary>
[Key]
public int Id { get; set; }
/// <summary>
/// Some text.
/// </summary>
public string Text { get; set; }
}
/// <summary>
/// Represents a database suitable for testing trigrams operators.
/// </summary>
public class TrigramsContext : PoolableDbContext
{
/// <summary>
/// Represents a set of entities with <see cref="System.String"/> properties.
/// </summary>
public DbSet<TrigramsTestEntity> TrigramsTestEntities { get; set; }
/// <summary>
/// Initializes a <see cref="TrigramsContext"/>.
/// </summary>
/// <param name="options">
/// The options to be used for configuration.
/// </param>
public TrigramsContext(DbContextOptions options) : base(options) {}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.HasPostgresExtension("pg_trgm");
base.OnModelCreating(modelBuilder);
}
public static void Seed(TrigramsContext context)
{
for (var i = 1; i <= 9; i++)
{
var text = "Some text " + i;
context.TrigramsTestEntities.Add(
new TrigramsTestEntity
{
Id = i,
Text = text
});
}
context.SaveChanges();
}
}
#endregion
#region Helpers
void AssertSql(params string[] expected) => Fixture.TestSqlLoggerFactory.AssertBaseline(expected);
/// <summary>
/// Asserts that the SQL fragment appears in the logs.
/// </summary>
/// <param name="sql">The SQL statement or fragment to search for in the logs.</param>
void AssertContainsSql(string sql) => Assert.Contains(sql, Fixture.TestSqlLoggerFactory.Sql);
#endregion
}
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册