Skip to content

Commit

Permalink
Fix LTree type mapping (#1930)
Browse files Browse the repository at this point in the history
Allow mapping string to PG ltree, not just the .NET LTree type (which
is supported via value conversion, as the ADO layer only supports
string). This also resolves issues in seeding.

Fixes #1929
  • Loading branch information
roji authored Jul 27, 2021
1 parent b05db2f commit dc03707
Show file tree
Hide file tree
Showing 4 changed files with 79 additions and 43 deletions.
29 changes: 0 additions & 29 deletions src/EFCore.PG/Storage/Internal/Mapping/LTreeTypeMapping.cs

This file was deleted.

36 changes: 36 additions & 0 deletions src/EFCore.PG/Storage/Internal/Mapping/NpgsqlLTreeTypeMapping.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
using System.Linq.Expressions;
using System.Reflection;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Storage;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using NpgsqlTypes;

namespace Npgsql.EntityFrameworkCore.PostgreSQL.Storage.Internal.Mapping
{
public class NpgsqlLTreeTypeMapping : NpgsqlStringTypeMapping
{
private static readonly ConstructorInfo Constructor = typeof(LTree).GetConstructor(new[] { typeof(string) })!;

public NpgsqlLTreeTypeMapping()
: base(
new RelationalTypeMappingParameters(
new CoreTypeMappingParameters(
typeof(LTree),
new ValueConverter<LTree, string>(l => l, s => new(s))),
"ltree"),
NpgsqlDbType.LTree)
{
}

protected NpgsqlLTreeTypeMapping(RelationalTypeMappingParameters parameters)
: base(parameters, NpgsqlDbType.LTree)
{
}

protected override RelationalTypeMapping Clone(RelationalTypeMappingParameters parameters)
=> new NpgsqlLTreeTypeMapping(parameters);

public override Expression GenerateCodeLiteral(object value)
=> Expression.New(Constructor, Expression.Constant((string)(LTree)value, typeof(string)));
}
}
5 changes: 3 additions & 2 deletions src/EFCore.PG/Storage/Internal/NpgsqlTypeMappingSource.cs
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,8 @@ public class NpgsqlTypeMappingSource : RelationalTypeMappingSource
private readonly NpgsqlHstoreTypeMapping _immutableHstore = new(typeof(ImmutableDictionary<string, string>));
private readonly NpgsqlTidTypeMapping _tid = new();

private readonly LTreeTypeMapping _ltree = new();
private readonly NpgsqlLTreeTypeMapping _ltree = new();
private readonly NpgsqlStringTypeMapping _ltreeString = new("ltree", NpgsqlDbType.LTree);
private readonly NpgsqlStringTypeMapping _lquery = new("lquery", NpgsqlDbType.LQuery);
private readonly NpgsqlStringTypeMapping _ltxtquery = new("ltxtquery", NpgsqlDbType.LTxtQuery);

Expand Down Expand Up @@ -251,7 +252,7 @@ public NpgsqlTypeMappingSource(TypeMappingSourceDependencies dependencies,
{ "tsvector", new[] { _tsvector } },
{ "regconfig", new[] { _regconfig } },

{ "ltree", new[] { _ltree } },
{ "ltree", new[] { _ltree, _ltreeString } },
{ "lquery", new[] { _lquery } },
{ "ltxtquery", new[] { _ltxtquery } },

Expand Down
52 changes: 40 additions & 12 deletions test/EFCore.PG.FunctionalTests/Query/LTreeQueryTest.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.TestUtilities;
Expand Down Expand Up @@ -27,6 +28,7 @@ public void Load()
using var ctx = CreateContext();
var entity = ctx.LTreeEntities.Single(l => l.Id == 5);
Assert.Equal("Top.Science.Astronomy.Cosmology", entity.Path);
Assert.Equal("Top.Science.Astronomy.Cosmology", entity.PathAsString);
}

[ConditionalFact]
Expand Down Expand Up @@ -58,6 +60,19 @@ SELECT COUNT(*)::INT
WHERE l.""Path"" = @__p_0");
}

[ConditionalFact]
public void Compare_string_to_string_literal()
{
using var ctx = CreateContext();
var count = ctx.LTreeEntities.Count(l => l.PathAsString == "Top.Science");

Assert.Equal(1, count);
AssertSql(
@"SELECT COUNT(*)::INT
FROM ""LTreeEntities"" AS l
WHERE l.""PathAsString"" = 'Top.Science'");
}

[ConditionalFact]
public void LTree_cast_to_string()
{
Expand Down Expand Up @@ -105,7 +120,7 @@ public void LTree_matches_LQuery()

Assert.Equal(4, entity.Id);
AssertSql(
@"SELECT l.""Id"", l.""Path""
@"SELECT l.""Id"", l.""Path"", l.""PathAsString""
FROM ""LTreeEntities"" AS l
WHERE l.""Path"" ~ '*.Astrophysics'
LIMIT 2");
Expand All @@ -122,7 +137,7 @@ public void LTree_matches_any_LQuery()
AssertSql(
@"@__lqueries_0={ '*.Astrophysics', '*.Geology' } (DbType = Object)
SELECT l.""Id"", l.""Path""
SELECT l.""Id"", l.""Path"", l.""PathAsString""
FROM ""LTreeEntities"" AS l
WHERE l.""Path"" ? @__lqueries_0
LIMIT 2");
Expand All @@ -149,7 +164,7 @@ public void LTree_concat()

Assert.Equal(2, entity.Id);
AssertSql(
@"SELECT l.""Id"", l.""Path""
@"SELECT l.""Id"", l.""Path"", l.""PathAsString""
FROM ""LTreeEntities"" AS l
WHERE (CAST(l.""Path"" AS text) || '.Astronomy') = 'Top.Science.Astronomy'
LIMIT 2");
Expand Down Expand Up @@ -342,7 +357,7 @@ public void Subpath2()

Assert.Equal(4, result.Id);
AssertSql(
@"SELECT l.""Id"", l.""Path""
@"SELECT l.""Id"", l.""Path"", l.""PathAsString""
FROM ""LTreeEntities"" AS l
WHERE (nlevel(l.""Path"") > 2) AND (subpath(l.""Path"", 2) = 'Astronomy.Astrophysics')
LIMIT 2");
Expand Down Expand Up @@ -422,14 +437,23 @@ protected override void OnModelCreating(ModelBuilder modelBuilder)

public static void Seed(LTreeQueryContext context)
{
context.LTreeEntities.AddRange(
new LTreeEntity { Id = 1, Path = "Top" },
new LTreeEntity { Id = 2, Path = "Top.Science" },
new LTreeEntity { Id = 3, Path = "Top.Science.Astronomy" },
new LTreeEntity { Id = 4, Path = "Top.Science.Astronomy.Astrophysics" },
new LTreeEntity { Id = 5, Path = "Top.Science.Astronomy.Cosmology" },
new LTreeEntity { Id = 6, Path = "Top.Hobbies" },
new LTreeEntity { Id = 7, Path = "Top.Hobbies.Amateurs_Astronomy" });
var ltreeEntities = new LTreeEntity[]
{
new() { Id = 1, Path = "Top" },
new() { Id = 2, Path = "Top.Science" },
new() { Id = 3, Path = "Top.Science.Astronomy" },
new() { Id = 4, Path = "Top.Science.Astronomy.Astrophysics" },
new() { Id = 5, Path = "Top.Science.Astronomy.Cosmology" },
new() { Id = 6, Path = "Top.Hobbies" },
new() { Id = 7, Path = "Top.Hobbies.Amateurs_Astronomy" }
};

foreach (var ltreeEntity in ltreeEntities)
{
ltreeEntity.PathAsString = ltreeEntity.Path;
}

context.LTreeEntities.AddRange(ltreeEntities);
context.SaveChanges();
}
}
Expand All @@ -440,6 +464,10 @@ public class LTreeEntity

[Required]
public LTree Path { get; set; }

[Required]
[Column(TypeName = "ltree")]
public string PathAsString { get; set; }
}

public class LTreeQueryFixture : SharedStoreFixtureBase<LTreeQueryContext>
Expand Down

0 comments on commit dc03707

Please sign in to comment.