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
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,13 @@ namespace Microsoft.EntityFrameworkCore
[SuppressMessage("ReSharper", "UnusedParameter.Global")]
public static class NpgsqlFullTextSearchDbFunctionsExtensions
{
/// <summary>
/// Convert <paramref name="lexemes" /> to a tsvector.
/// https://www.postgresql.org/docs/current/static/functions-textsearch.html
/// </summary>
public static NpgsqlTsVector ArrayToTsVector(this DbFunctions _, string[] lexemes) =>
throw new NotSupportedException();

/// <summary>
/// Reduce <paramref name="document" /> to tsvector.
/// http://www.postgresql.org/docs/current/static/textsearch-controls.html#TEXTSEARCH-PARSING-DOCUMENTS
Expand Down
30 changes: 30 additions & 0 deletions src/EFCore.PG/FullTextSearch/NpgsqlFullTextSearchLinqExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,15 @@ public static NpgsqlTsQuery ToPhrase(this NpgsqlTsQuery query1, NpgsqlTsQuery qu
public static bool Matches(this NpgsqlTsVector vector, NpgsqlTsQuery query) =>
throw new NotSupportedException();

/// <summary>
/// Returns a vector which combines the lexemes and positional information of <paramref name="vector1" />
/// and <paramref name="vector2"/> using the || tsvector operator. Positions and weight labels are retained
/// during the concatenation.
/// https://www.postgresql.org/docs/10/static/textsearch-features.html#TEXTSEARCH-MANIPULATE-TSVECTOR
/// </summary>
public static NpgsqlTsVector Concat(this NpgsqlTsVector vector1, NpgsqlTsVector vector2) =>
throw new NotSupportedException();

/// <summary>
/// Assign weight to each element of <paramref name="vector" /> and return a new
/// weighted tsvector.
Expand Down Expand Up @@ -179,6 +188,27 @@ public static NpgsqlTsVector SetWeight(this NpgsqlTsVector vector, char weight)
public static NpgsqlTsVector SetWeight(this NpgsqlTsVector vector, char weight, string[] lexemes) =>
throw new NotSupportedException();

/// <summary>
/// Return a new vector with <paramref name="lexeme" /> removed from <paramref name="vector" />
/// https://www.postgresql.org/docs/current/static/functions-textsearch.html
/// </summary>
public static NpgsqlTsVector Delete(this NpgsqlTsVector vector, string lexeme) =>
throw new NotSupportedException();

/// <summary>
/// Return a new vector with <paramref name="lexemes" /> removed from <paramref name="vector" />
/// https://www.postgresql.org/docs/current/static/functions-textsearch.html
/// </summary>
public static NpgsqlTsVector Delete(this NpgsqlTsVector vector, string[] lexemes) =>
throw new NotSupportedException();

/// <summary>
/// Returns a new vector with only lexemes having weights specified in <paramref name="weights" />.
/// https://www.postgresql.org/docs/current/static/functions-textsearch.html
/// </summary>
public static NpgsqlTsVector Filter(this NpgsqlTsVector vector, char[] weights) =>
throw new NotSupportedException();

/// <summary>
/// Returns the number of lexemes in <paramref name="vector" />.
/// http://www.postgresql.org/docs/current/static/textsearch-features.html#TEXTSEARCH-MANIPULATE-TSVECTOR
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ public class NpgsqlFullTextSearchMethodTranslator : IMethodCallTranslator
static readonly IReadOnlyDictionary<string, string> _sqlNameByMethodName =
new Dictionary<string, string>
{
[nameof(NpgsqlFullTextSearchDbFunctionsExtensions.ArrayToTsVector)] = "array_to_tsvector",
[nameof(NpgsqlFullTextSearchDbFunctionsExtensions.ToTsVector)] = "to_tsvector",
[nameof(NpgsqlFullTextSearchDbFunctionsExtensions.PlainToTsQuery)] = "plainto_tsquery",
[nameof(NpgsqlFullTextSearchDbFunctionsExtensions.PhraseToTsQuery)] = "phraseto_tsquery",
Expand Down Expand Up @@ -105,6 +106,11 @@ static Expression TryTranslateOperator(MethodCallExpression methodCallExpression
methodCallExpression.Arguments[0],
methodCallExpression.Arguments[1]);

case nameof(NpgsqlFullTextSearchLinqExtensions.Concat):
return FullTextSearchExpression.TsVectorConcat(
methodCallExpression.Arguments[0],
methodCallExpression.Arguments[1]);

default:
return null;
}
Expand Down Expand Up @@ -196,6 +202,22 @@ static Expression TryTranslateFunction(MethodCallExpression methodCallExpression
methodCallExpression.Method.ReturnType,
arguments);

case nameof(NpgsqlFullTextSearchLinqExtensions.Delete):
return new SqlFunctionExpression(
"ts_delete",
methodCallExpression.Method.ReturnType,
methodCallExpression.Arguments);

case nameof(NpgsqlFullTextSearchLinqExtensions.Filter):
return new SqlFunctionExpression(
"ts_filter",
methodCallExpression.Method.ReturnType,
new[]
{
methodCallExpression.Arguments[0],
new ExplicitStoreTypeCastExpression(methodCallExpression.Arguments[1], typeof(char[]), "\"char\"[]")
});

case nameof(NpgsqlFullTextSearchLinqExtensions.GetLength):
return new SqlFunctionExpression(
"length",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,12 @@ public static FullTextSearchExpression TsVectorMatches([NotNull] Expression left
return new FullTextSearchExpression("@@", left, right, typeof(bool));
}

public static FullTextSearchExpression TsVectorConcat([NotNull] Expression left, [NotNull] Expression right)
{
ValidateArguments(left, typeof(NpgsqlTsVector), right, typeof(NpgsqlTsVector));
return new FullTextSearchExpression("||", left, right, typeof(NpgsqlTsVector));
}

private static void ValidateArguments(
[NotNull] Expression left,
[NotNull] Type validLeftType,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Linq;
using System;
using System.Linq;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.TestModels.Northwind;
using Microsoft.EntityFrameworkCore.TestUtilities;
Expand Down Expand Up @@ -32,6 +33,34 @@ public void TsVectorParse_converted_to_cast()
LIMIT 1");
}

[Fact]
public void ArrayToTsVector()
{
using (var context = CreateContext())
{
var tsvector = context.Customers.Select(c => EF.Functions.ArrayToTsVector(new[] { "b", "c", "d" }))
.First();
Assert.Equal(NpgsqlTsVector.Parse("b c d").ToString(), tsvector.ToString());
}

AssertSql(
@"SELECT array_to_tsvector(ARRAY['b','c','d'])
FROM ""Customers"" AS c
LIMIT 1");
}

[Fact]
public void ArrayToTsVector_From_Columns_Throws_NotSupportedException()
{
using (var context = CreateContext())
{
Assert.Throws<NotSupportedException>(
() => context.Customers
.Select(c => EF.Functions.ArrayToTsVector(new[] { c.CompanyName, c.Address }))
.First());
}
}

[Fact]
public void ToTsVector()
{
Expand Down Expand Up @@ -429,6 +458,23 @@ public void Matches_With_Tsquery()
LIMIT 1");
}

[Fact]
public void TsVectorConcat()
{
using (var context = CreateContext())
{
var tsVector = context.Customers
.Select(c => EF.Functions.ToTsVector("b").Concat(EF.Functions.ToTsVector("c")))
.First();
Assert.Equal(NpgsqlTsVector.Parse("b:1 c:2").ToString(), tsVector.ToString());
}

AssertSql(
@"SELECT (to_tsvector('b') || to_tsvector('c'))
FROM ""Customers"" AS c
LIMIT 1");
}

[Fact]
public void Setweight_With_Enum()
{
Expand Down Expand Up @@ -497,6 +543,57 @@ public void Setweight_With_Char_And_Lexemes()
LIMIT 1");
}

[Fact]
public void Delete_With_Single_Lexeme()
{
using (var context = CreateContext())
{
var tsVector = context.Customers
.Select(c => EF.Functions.ToTsVector("b c").Delete("c"))
.First();
Assert.Equal(NpgsqlTsVector.Parse("b:1").ToString(), tsVector.ToString());
}

AssertSql(
@"SELECT ts_delete(to_tsvector('b c'), 'c')
FROM ""Customers"" AS c
LIMIT 1");
}

[Fact]
public void Delete_With_Multiple_Lexemes()
{
using (var context = CreateContext())
{
var tsVector = context.Customers
.Select(c => EF.Functions.ToTsVector("b c d").Delete(new[] { "c", "d" }))
.First();
Assert.Equal(NpgsqlTsVector.Parse("b:1").ToString(), tsVector.ToString());
}

AssertSql(
@"SELECT ts_delete(to_tsvector('b c d'), ARRAY['c','d'])
FROM ""Customers"" AS c
LIMIT 1");
}

[Fact]
public void Filter()
{
using (var context = CreateContext())
{
var tsVector = context.Customers
.Select(c => NpgsqlTsVector.Parse("b:1A c:2B d:3C").Filter(new[] { 'B', 'C' }))
.First();
Assert.Equal(NpgsqlTsVector.Parse("c:2B d:3C").ToString(), tsVector.ToString());
}

AssertSql(
@"SELECT ts_filter(CAST('b:1A c:2B d:3C' AS tsvector), CAST(ARRAY['B','C'] AS ""char""[]))
FROM ""Customers"" AS c
LIMIT 1");
}

[Fact]
public void GetLength()
{
Expand Down