提交 ddc63fc2 编写于 作者: S Shay Rojansky

Correct LTree type mapping logic

Fixes #2487
上级 07400f75
...@@ -57,32 +57,32 @@ public class NpgsqlLTreeTranslator : IMethodCallTranslator, IMemberTranslator ...@@ -57,32 +57,32 @@ public class NpgsqlLTreeTranslator : IMethodCallTranslator, IMemberTranslator
nameof(LTree.IsAncestorOf) nameof(LTree.IsAncestorOf)
=> new PostgresBinaryExpression( => new PostgresBinaryExpression(
PostgresExpressionType.Contains, PostgresExpressionType.Contains,
_sqlExpressionFactory.ApplyTypeMapping(instance!, _ltreeTypeMapping), ApplyTypeMappingOrConvert(instance!, _ltreeTypeMapping),
_sqlExpressionFactory.ApplyTypeMapping(arguments[0], _ltreeTypeMapping), ApplyTypeMappingOrConvert(arguments[0], _ltreeTypeMapping),
typeof(bool), typeof(bool),
_boolTypeMapping), _boolTypeMapping),
nameof(LTree.IsDescendantOf) nameof(LTree.IsDescendantOf)
=> new PostgresBinaryExpression( => new PostgresBinaryExpression(
PostgresExpressionType.ContainedBy, PostgresExpressionType.ContainedBy,
_sqlExpressionFactory.ApplyTypeMapping(instance!, _ltreeTypeMapping), ApplyTypeMappingOrConvert(instance!, _ltreeTypeMapping),
_sqlExpressionFactory.ApplyTypeMapping(arguments[0], _ltreeTypeMapping), ApplyTypeMappingOrConvert(arguments[0], _ltreeTypeMapping),
typeof(bool), typeof(bool),
_boolTypeMapping), _boolTypeMapping),
nameof(LTree.MatchesLQuery) nameof(LTree.MatchesLQuery)
=> new PostgresBinaryExpression( => new PostgresBinaryExpression(
PostgresExpressionType.LTreeMatches, PostgresExpressionType.LTreeMatches,
_sqlExpressionFactory.ApplyTypeMapping(instance!, _ltreeTypeMapping), ApplyTypeMappingOrConvert(instance!, _ltreeTypeMapping),
_sqlExpressionFactory.ApplyTypeMapping(arguments[0], _lqueryTypeMapping), ApplyTypeMappingOrConvert(arguments[0], _lqueryTypeMapping),
typeof(bool), typeof(bool),
_boolTypeMapping), _boolTypeMapping),
nameof(LTree.MatchesLTxtQuery) nameof(LTree.MatchesLTxtQuery)
=> new PostgresBinaryExpression( => new PostgresBinaryExpression(
PostgresExpressionType.LTreeMatches, PostgresExpressionType.LTreeMatches,
_sqlExpressionFactory.ApplyTypeMapping(instance!, _ltreeTypeMapping), ApplyTypeMappingOrConvert(instance!, _ltreeTypeMapping),
_sqlExpressionFactory.ApplyTypeMapping(arguments[0], _ltxtqueryTypeMapping), ApplyTypeMappingOrConvert(arguments[0], _ltxtqueryTypeMapping),
typeof(bool), typeof(bool),
_boolTypeMapping), _boolTypeMapping),
...@@ -171,7 +171,7 @@ public class NpgsqlLTreeTranslator : IMethodCallTranslator, IMemberTranslator ...@@ -171,7 +171,7 @@ public class NpgsqlLTreeTranslator : IMethodCallTranslator, IMemberTranslator
{ {
return new PostgresBinaryExpression( return new PostgresBinaryExpression(
PostgresExpressionType.LTreeMatchesAny, PostgresExpressionType.LTreeMatchesAny,
_sqlExpressionFactory.ApplyTypeMapping(Visit(predicateInstance), _ltreeTypeMapping), ApplyTypeMappingOrConvert(Visit(predicateInstance), _ltreeTypeMapping),
_sqlExpressionFactory.ApplyTypeMapping(Visit(array), _lqueryArrayTypeMapping), _sqlExpressionFactory.ApplyTypeMapping(Visit(array), _lqueryArrayTypeMapping),
typeof(bool), typeof(bool),
_boolTypeMapping); _boolTypeMapping);
...@@ -184,7 +184,7 @@ public class NpgsqlLTreeTranslator : IMethodCallTranslator, IMemberTranslator ...@@ -184,7 +184,7 @@ public class NpgsqlLTreeTranslator : IMethodCallTranslator, IMemberTranslator
return new PostgresBinaryExpression( return new PostgresBinaryExpression(
PostgresExpressionType.Contains, PostgresExpressionType.Contains,
_sqlExpressionFactory.ApplyTypeMapping(Visit(array), _ltreeArrayTypeMapping), _sqlExpressionFactory.ApplyTypeMapping(Visit(array), _ltreeArrayTypeMapping),
_sqlExpressionFactory.ApplyTypeMapping(Visit(predicateArguments[0]), _ltreeTypeMapping), ApplyTypeMappingOrConvert(Visit(predicateArguments[0]), _ltreeTypeMapping),
typeof(bool), typeof(bool),
_boolTypeMapping); _boolTypeMapping);
} }
...@@ -196,7 +196,7 @@ public class NpgsqlLTreeTranslator : IMethodCallTranslator, IMemberTranslator ...@@ -196,7 +196,7 @@ public class NpgsqlLTreeTranslator : IMethodCallTranslator, IMemberTranslator
return new PostgresBinaryExpression( return new PostgresBinaryExpression(
PostgresExpressionType.ContainedBy, PostgresExpressionType.ContainedBy,
_sqlExpressionFactory.ApplyTypeMapping(Visit(array), _ltreeArrayTypeMapping), _sqlExpressionFactory.ApplyTypeMapping(Visit(array), _ltreeArrayTypeMapping),
_sqlExpressionFactory.ApplyTypeMapping(Visit(predicateArguments[0]), _ltreeTypeMapping), ApplyTypeMappingOrConvert(Visit(predicateArguments[0]), _ltreeTypeMapping),
typeof(bool), typeof(bool),
_boolTypeMapping); _boolTypeMapping);
} }
...@@ -208,7 +208,7 @@ public class NpgsqlLTreeTranslator : IMethodCallTranslator, IMemberTranslator ...@@ -208,7 +208,7 @@ public class NpgsqlLTreeTranslator : IMethodCallTranslator, IMemberTranslator
return new PostgresBinaryExpression( return new PostgresBinaryExpression(
PostgresExpressionType.LTreeMatches, PostgresExpressionType.LTreeMatches,
_sqlExpressionFactory.ApplyTypeMapping(Visit(array), _ltreeArrayTypeMapping), _sqlExpressionFactory.ApplyTypeMapping(Visit(array), _ltreeArrayTypeMapping),
_sqlExpressionFactory.ApplyTypeMapping(Visit(predicateArguments[0]), _lqueryTypeMapping), ApplyTypeMappingOrConvert(Visit(predicateArguments[0]), _lqueryTypeMapping),
typeof(bool), typeof(bool),
_boolTypeMapping); _boolTypeMapping);
} }
...@@ -220,7 +220,7 @@ public class NpgsqlLTreeTranslator : IMethodCallTranslator, IMemberTranslator ...@@ -220,7 +220,7 @@ public class NpgsqlLTreeTranslator : IMethodCallTranslator, IMemberTranslator
return new PostgresBinaryExpression( return new PostgresBinaryExpression(
PostgresExpressionType.LTreeMatches, PostgresExpressionType.LTreeMatches,
_sqlExpressionFactory.ApplyTypeMapping(Visit(array), _ltreeArrayTypeMapping), _sqlExpressionFactory.ApplyTypeMapping(Visit(array), _ltreeArrayTypeMapping),
_sqlExpressionFactory.ApplyTypeMapping(Visit(predicateArguments[0]), _ltxtqueryTypeMapping), ApplyTypeMappingOrConvert(Visit(predicateArguments[0]), _ltxtqueryTypeMapping),
typeof(bool), typeof(bool),
_boolTypeMapping); _boolTypeMapping);
} }
...@@ -267,7 +267,7 @@ public class NpgsqlLTreeTranslator : IMethodCallTranslator, IMemberTranslator ...@@ -267,7 +267,7 @@ public class NpgsqlLTreeTranslator : IMethodCallTranslator, IMemberTranslator
return new PostgresBinaryExpression( return new PostgresBinaryExpression(
PostgresExpressionType.LTreeFirstAncestor, PostgresExpressionType.LTreeFirstAncestor,
_sqlExpressionFactory.ApplyTypeMapping(Visit(array), _ltreeArrayTypeMapping), _sqlExpressionFactory.ApplyTypeMapping(Visit(array), _ltreeArrayTypeMapping),
_sqlExpressionFactory.ApplyTypeMapping(Visit(predicateArguments[0]), _ltreeTypeMapping), ApplyTypeMappingOrConvert(Visit(predicateArguments[0]), _ltreeTypeMapping),
typeof(LTree), typeof(LTree),
_ltreeTypeMapping); _ltreeTypeMapping);
} }
...@@ -279,7 +279,7 @@ public class NpgsqlLTreeTranslator : IMethodCallTranslator, IMemberTranslator ...@@ -279,7 +279,7 @@ public class NpgsqlLTreeTranslator : IMethodCallTranslator, IMemberTranslator
return new PostgresBinaryExpression( return new PostgresBinaryExpression(
PostgresExpressionType.LTreeFirstDescendent, PostgresExpressionType.LTreeFirstDescendent,
_sqlExpressionFactory.ApplyTypeMapping(Visit(array), _ltreeArrayTypeMapping), _sqlExpressionFactory.ApplyTypeMapping(Visit(array), _ltreeArrayTypeMapping),
_sqlExpressionFactory.ApplyTypeMapping(Visit(predicateArguments[0]), _ltreeTypeMapping), ApplyTypeMappingOrConvert(Visit(predicateArguments[0]), _ltreeTypeMapping),
typeof(LTree), typeof(LTree),
_ltreeTypeMapping); _ltreeTypeMapping);
} }
...@@ -291,7 +291,7 @@ public class NpgsqlLTreeTranslator : IMethodCallTranslator, IMemberTranslator ...@@ -291,7 +291,7 @@ public class NpgsqlLTreeTranslator : IMethodCallTranslator, IMemberTranslator
return new PostgresBinaryExpression( return new PostgresBinaryExpression(
PostgresExpressionType.LTreeFirstMatches, PostgresExpressionType.LTreeFirstMatches,
_sqlExpressionFactory.ApplyTypeMapping(Visit(array), _ltreeArrayTypeMapping), _sqlExpressionFactory.ApplyTypeMapping(Visit(array), _ltreeArrayTypeMapping),
_sqlExpressionFactory.ApplyTypeMapping(Visit(predicateArguments[0]), _lqueryTypeMapping), ApplyTypeMappingOrConvert(Visit(predicateArguments[0]), _lqueryTypeMapping),
typeof(LTree), typeof(LTree),
_ltreeTypeMapping); _ltreeTypeMapping);
} }
...@@ -303,7 +303,7 @@ public class NpgsqlLTreeTranslator : IMethodCallTranslator, IMemberTranslator ...@@ -303,7 +303,7 @@ public class NpgsqlLTreeTranslator : IMethodCallTranslator, IMemberTranslator
return new PostgresBinaryExpression( return new PostgresBinaryExpression(
PostgresExpressionType.LTreeFirstMatches, PostgresExpressionType.LTreeFirstMatches,
_sqlExpressionFactory.ApplyTypeMapping(Visit(array), _ltreeArrayTypeMapping), _sqlExpressionFactory.ApplyTypeMapping(Visit(array), _ltreeArrayTypeMapping),
_sqlExpressionFactory.ApplyTypeMapping(Visit(predicateArguments[0]), _ltxtqueryTypeMapping), ApplyTypeMappingOrConvert(Visit(predicateArguments[0]), _ltxtqueryTypeMapping),
typeof(string), typeof(string),
_ltreeTypeMapping); _ltreeTypeMapping);
} }
...@@ -315,4 +315,18 @@ public class NpgsqlLTreeTranslator : IMethodCallTranslator, IMemberTranslator ...@@ -315,4 +315,18 @@ public class NpgsqlLTreeTranslator : IMethodCallTranslator, IMemberTranslator
SqlExpression Visit(Expression expression) SqlExpression Visit(Expression expression)
=> (SqlExpression)sqlTranslatingExpressionVisitor.Visit(expression); => (SqlExpression)sqlTranslatingExpressionVisitor.Visit(expression);
} }
// Applying e.g. the LQuery type mapping on a function operator is a bit tricky.
// If it's a constant, we can just apply the mapping: the constant will get rendered as an untyped string literal, and PG will
// coerce it as the function parameter.
// If it's a parameter, we can also just apply the mapping (which causes NpgsqlDbType to be set to LQuery).
// For anything else, we may need an explicit cast to LQuery, e.g. a plain text column or a concatenation between strings;
// apply the default type mapping and then apply an additional Convert node if the resulting mapping isn't what we need.
private SqlExpression ApplyTypeMappingOrConvert(SqlExpression sqlExpression, RelationalTypeMapping typeMapping)
=> sqlExpression is SqlConstantExpression or SqlParameterExpression
? _sqlExpressionFactory.ApplyTypeMapping(sqlExpression, typeMapping)
: _sqlExpressionFactory.ApplyDefaultTypeMapping(sqlExpression) is var expressionWithDefaultTypeMapping
&& expressionWithDefaultTypeMapping.TypeMapping!.StoreType == typeMapping.StoreType
? expressionWithDefaultTypeMapping
: _sqlExpressionFactory.Convert(expressionWithDefaultTypeMapping, typeMapping.ClrType, typeMapping);
} }
\ No newline at end of file
...@@ -128,13 +128,44 @@ public void LTree_matches_LQuery() ...@@ -128,13 +128,44 @@ public void LTree_matches_LQuery()
Assert.Equal(4, entity.Id); Assert.Equal(4, entity.Id);
AssertSql( AssertSql(
""" """
SELECT l."Id", l."Path", l."PathAsString" SELECT l."Id", l."Path", l."PathAsString", l."SomeString"
FROM "LTreeEntities" AS l FROM "LTreeEntities" AS l
WHERE l."Path" ~ '*.Astrophysics' WHERE l."Path" ~ '*.Astrophysics'
LIMIT 2 LIMIT 2
"""); """);
} }
[ConditionalFact] // #2487
public void LTree_matches_LQuery_with_string_column()
{
using var ctx = CreateContext();
var entity = ctx.LTreeEntities.Single(l => l.Path.MatchesLQuery(l.SomeString));
Assert.Equal(4, entity.Id);
AssertSql(
"""
SELECT l."Id", l."Path", l."PathAsString", l."SomeString"
FROM "LTreeEntities" AS l
WHERE l."Path" ~ l."SomeString"::lquery
LIMIT 2
""");
}
[ConditionalFact] // #2487
public void LTree_matches_LQuery_with_concat()
{
using var ctx = CreateContext();
var count = ctx.LTreeEntities.Count(l => l.Path.MatchesLQuery("*.Astrophysics." + l.Id));
Assert.Equal(0, count);
AssertSql(
"""
SELECT count(*)::int
FROM "LTreeEntities" AS l
WHERE l."Path" ~ CAST(('*.Astrophysics.' || l."Id"::text) AS lquery)
""");
}
[ConditionalFact] [ConditionalFact]
public void LTree_matches_any_LQuery() public void LTree_matches_any_LQuery()
{ {
...@@ -147,7 +178,7 @@ public void LTree_matches_any_LQuery() ...@@ -147,7 +178,7 @@ public void LTree_matches_any_LQuery()
""" """
@__lqueries_0={ '*.Astrophysics', '*.Geology' } (DbType = Object) @__lqueries_0={ '*.Astrophysics', '*.Geology' } (DbType = Object)
SELECT l."Id", l."Path", l."PathAsString" SELECT l."Id", l."Path", l."PathAsString", l."SomeString"
FROM "LTreeEntities" AS l FROM "LTreeEntities" AS l
WHERE l."Path" ? @__lqueries_0 WHERE l."Path" ? @__lqueries_0
LIMIT 2 LIMIT 2
...@@ -178,7 +209,7 @@ public void LTree_concat() ...@@ -178,7 +209,7 @@ public void LTree_concat()
Assert.Equal(2, entity.Id); Assert.Equal(2, entity.Id);
AssertSql( AssertSql(
""" """
SELECT l."Id", l."Path", l."PathAsString" SELECT l."Id", l."Path", l."PathAsString", l."SomeString"
FROM "LTreeEntities" AS l FROM "LTreeEntities" AS l
WHERE (l."Path"::text || '.Astronomy') = 'Top.Science.Astronomy' WHERE (l."Path"::text || '.Astronomy') = 'Top.Science.Astronomy'
LIMIT 2 LIMIT 2
...@@ -395,7 +426,7 @@ public void Subpath2() ...@@ -395,7 +426,7 @@ public void Subpath2()
Assert.Equal(4, result.Id); Assert.Equal(4, result.Id);
AssertSql( AssertSql(
""" """
SELECT l."Id", l."Path", l."PathAsString" SELECT l."Id", l."Path", l."PathAsString", l."SomeString"
FROM "LTreeEntities" AS l FROM "LTreeEntities" AS l
WHERE nlevel(l."Path") > 2 AND subpath(l."Path", 2) = 'Astronomy.Astrophysics' WHERE nlevel(l."Path") > 2 AND subpath(l."Path", 2) = 'Astronomy.Astrophysics'
LIMIT 2 LIMIT 2
...@@ -495,6 +526,7 @@ public static void Seed(LTreeQueryContext context) ...@@ -495,6 +526,7 @@ public static void Seed(LTreeQueryContext context)
foreach (var ltreeEntity in ltreeEntities) foreach (var ltreeEntity in ltreeEntities)
{ {
ltreeEntity.PathAsString = ltreeEntity.Path; ltreeEntity.PathAsString = ltreeEntity.Path;
ltreeEntity.SomeString = "*.Astrophysics";
} }
context.LTreeEntities.AddRange(ltreeEntities); context.LTreeEntities.AddRange(ltreeEntities);
...@@ -512,6 +544,9 @@ public class LTreeEntity ...@@ -512,6 +544,9 @@ public class LTreeEntity
[Required] [Required]
[Column(TypeName = "ltree")] [Column(TypeName = "ltree")]
public string PathAsString { get; set; } public string PathAsString { get; set; }
[Required]
public string SomeString { get; set; }
} }
public class LTreeQueryFixture : SharedStoreFixtureBase<LTreeQueryContext> public class LTreeQueryFixture : SharedStoreFixtureBase<LTreeQueryContext>
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册