Skip to content

Commit

Permalink
Translate List.Count inside json (#2376)
Browse files Browse the repository at this point in the history
Fixes #2374

(cherry picked from commit e946af8)
  • Loading branch information
roji committed May 19, 2022
1 parent b53c32d commit d0841f7
Show file tree
Hide file tree
Showing 4 changed files with 70 additions and 23 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -143,8 +143,7 @@ public NpgsqlArrayTranslator(
// The array/list CLR type may be mapped to a non-array database type (e.g. byte[] to bytea, or just
// value converters) - we don't want to translate for those cases.
static bool IsMappedToNonArray(SqlExpression arrayOrList)
=> arrayOrList.TypeMapping is RelationalTypeMapping typeMapping &&
typeMapping is not (NpgsqlArrayTypeMapping or NpgsqlJsonTypeMapping);
=> arrayOrList.TypeMapping is RelationalTypeMapping and not (NpgsqlArrayTypeMapping or NpgsqlJsonTypeMapping);

SqlExpression? TranslateCommon(SqlExpression arrayOrList, IReadOnlyList<SqlExpression> arguments)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,26 +33,37 @@ public NpgsqlJsonPocoTranslator(
_stringTypeMapping = typeMappingSource.FindMapping(typeof(string), model)!;
}

public virtual SqlExpression? Translate(SqlExpression? instance,
public virtual SqlExpression? Translate(
SqlExpression? instance,
MemberInfo member,
Type returnType,
IDiagnosticsLogger<DbLoggerCategory.Query> logger)
=> instance?.TypeMapping is NpgsqlJsonTypeMapping || instance is PostgresJsonTraversalExpression
? TranslateMemberAccess(
instance,
_sqlExpressionFactory.Constant(
member.GetCustomAttribute<JsonPropertyNameAttribute>()?.Name ?? member.Name),
returnType)
: null;
{
if (instance?.TypeMapping is not NpgsqlJsonTypeMapping && instance is not PostgresJsonTraversalExpression)
{
return null;
}

if (member.Name == nameof(List<object>.Count)
&& member.DeclaringType?.IsGenericType == true
&& member.DeclaringType.GetGenericTypeDefinition() == typeof(List<>))
{
return TranslateArrayLength(instance);
}

return TranslateMemberAccess(
instance,
_sqlExpressionFactory.Constant(member.GetCustomAttribute<JsonPropertyNameAttribute>()?.Name ?? member.Name),
returnType);
}

public virtual SqlExpression? TranslateMemberAccess(
SqlExpression instance, SqlExpression member, Type returnType)
{
// The first time we see a JSON traversal it's on a column - create a JsonTraversalExpression.
// Traversals on top of that get appended into the same expression.

if (instance is ColumnExpression columnExpression &&
columnExpression.TypeMapping is NpgsqlJsonTypeMapping)
if (instance is ColumnExpression { TypeMapping: NpgsqlJsonTypeMapping } columnExpression)
{
return ConvertFromText(
_sqlExpressionFactory.JsonTraversal(
Expand All @@ -76,8 +87,7 @@ public NpgsqlJsonPocoTranslator(

public virtual SqlExpression? TranslateArrayLength(SqlExpression expression)
{
if (expression is ColumnExpression columnExpression &&
columnExpression.TypeMapping is NpgsqlJsonTypeMapping mapping)
if (expression is ColumnExpression { TypeMapping: NpgsqlJsonTypeMapping mapping })
{
return _sqlExpressionFactory.Function(
mapping.IsJsonb ? "jsonb_array_length" : "json_array_length",
Expand Down
8 changes: 5 additions & 3 deletions test/EFCore.PG.FunctionalTests/Query/JsonDomQueryTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ public void Parameter_element()
WHERE j.""Id"" = @__p_0
LIMIT 1",
//
@"@__expected_0='{""ID"": ""00000000-0000-0000-0000-000000000000"", ""Age"": 25, ""Name"": ""Joe"", ""IsVip"": false, ""Orders"": [{""Price"": 99.5, ""ShippingAddress"": ""Some address 1""}, {""Price"": 23, ""ShippingAddress"": ""Some address 2""}], ""Statistics"": {""Nested"": {""IntArray"": [3, 4], ""SomeProperty"": 10, ""SomeNullableInt"": 20, ""SomeNullableGuid"": ""d5f2685d-e5c4-47e5-97aa-d0266154eb2d""}, ""Visits"": 4, ""Purchases"": 3}, ""VariousTypes"": {""Bool"": ""false"", ""Int16"": 8, ""Int32"": 8, ""Int64"": 8, ""String"": ""foo"", ""Decimal"": 10, ""DateTime"": ""2020-01-01T10:30:45"", ""DateTimeOffset"": ""2020-01-01T10:30:45+02:00""}}' (DbType = Object)
@"@__expected_0='{""ID"": ""00000000-0000-0000-0000-000000000000"", ""Age"": 25, ""Name"": ""Joe"", ""IsVip"": false, ""Orders"": [{""Price"": 99.5, ""ShippingAddress"": ""Some address 1""}, {""Price"": 23, ""ShippingAddress"": ""Some address 2""}], ""Statistics"": {""Nested"": {""IntList"": [3, 4], ""IntArray"": [3, 4], ""SomeProperty"": 10, ""SomeNullableInt"": 20, ""SomeNullableGuid"": ""d5f2685d-e5c4-47e5-97aa-d0266154eb2d""}, ""Visits"": 4, ""Purchases"": 3}, ""VariousTypes"": {""Bool"": ""false"", ""Int16"": 8, ""Int32"": 8, ""Int64"": 8, ""String"": ""foo"", ""Decimal"": 10, ""DateTime"": ""2020-01-01T10:30:45"", ""DateTimeOffset"": ""2020-01-01T10:30:45+02:00""}}' (DbType = Object)
SELECT j.""Id"", j.""CustomerDocument"", j.""CustomerElement""
FROM ""JsonbEntities"" AS j
Expand Down Expand Up @@ -522,7 +522,8 @@ static JsonDocument CreateCustomer1() => JsonDocument.Parse(@"
""SomeProperty"": 10,
""SomeNullableInt"": 20,
""SomeNullableGuid"": ""d5f2685d-e5c4-47e5-97aa-d0266154eb2d"",
""IntArray"": [3, 4]
""IntArray"": [3, 4],
""IntList"": [3, 4]
}
},
""Orders"":
Expand Down Expand Up @@ -564,7 +565,8 @@ static JsonDocument CreateCustomer2() => JsonDocument.Parse(@"
""SomeProperty"": 20,
""SomeNullableInt"": null,
""SomeNullableGuid"": null,
""IntArray"": [5, 6]
""IntArray"": [5, 6, 7],
""IntArray"": [5, 6, 7]
}
},
""Orders"":
Expand Down
48 changes: 42 additions & 6 deletions test/EFCore.PG.FunctionalTests/Query/JsonPocoQueryTest.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using System.Text.Json;
Expand Down Expand Up @@ -309,27 +310,59 @@ WHERE CAST(j.""Customer""#>>ARRAY['Statistics','Nested','IntArray',@__i_0]::TEXT
public void Array_Length()
{
using var ctx = CreateContext();
var x = ctx.JsonbEntities.Single(e => e.Customer.Orders.Length == 2);
var x = ctx.JsonbEntities.Single(e => e.Customer.Statistics.Nested.IntArray.Length == 2);

Assert.Equal("Joe", x.Customer.Name);
AssertSql(
@"SELECT j.""Id"", j.""Customer"", j.""ToplevelArray""
FROM ""JsonbEntities"" AS j
WHERE jsonb_array_length(j.""Customer""->'Orders') = 2
WHERE jsonb_array_length(j.""Customer""#>'{Statistics,Nested,IntArray}') = 2
LIMIT 2");
}

[Fact]
public void Array_Length_json()
{
using var ctx = CreateContext();
var x = ctx.JsonEntities.Single(e => e.Customer.Orders.Length == 2);
var x = ctx.JsonEntities.Single(e => e.Customer.Statistics.Nested.IntArray.Length == 2);

Assert.Equal("Joe", x.Customer.Name);
AssertSql(
@"SELECT j.""Id"", j.""Customer"", j.""ToplevelArray""
FROM ""JsonEntities"" AS j
WHERE json_array_length(j.""Customer""->'Orders') = 2
WHERE json_array_length(j.""Customer""#>'{Statistics,Nested,IntArray}') = 2
LIMIT 2");
}

[Fact]
public void List_Count()
{
using var ctx = CreateContext();

var x = ctx.JsonbEntities.Single(e => e.Customer.Statistics.Nested.IntList.Count == 2);

Assert.Equal("Joe", x.Customer.Name);

AssertSql(
@"SELECT j.""Id"", j.""Customer"", j.""ToplevelArray""
FROM ""JsonbEntities"" AS j
WHERE jsonb_array_length(j.""Customer""#>'{Statistics,Nested,IntList}') = 2
LIMIT 2");
}

[Fact]
public void List_Count_json()
{
using var ctx = CreateContext();

var x = ctx.JsonEntities.Single(e => e.Customer.Statistics.Nested.IntList.Count == 2);

Assert.Equal("Joe", x.Customer.Name);

AssertSql(
@"SELECT j.""Id"", j.""Customer"", j.""ToplevelArray""
FROM ""JsonEntities"" AS j
WHERE json_array_length(j.""Customer""#>'{Statistics,Nested,IntList}') = 2
LIMIT 2");
}

Expand Down Expand Up @@ -585,7 +618,8 @@ public static void Seed(JsonPocoQueryContext context)
SomeProperty = 10,
SomeNullableInt = 20,
SomeNullableGuid = Guid.Parse("d5f2685d-e5c4-47e5-97aa-d0266154eb2d"),
IntArray = new[] { 3, 4 }
IntArray = new[] { 3, 4 },
IntList = new() { 3, 4 }
}
},
Orders = new[]
Expand Down Expand Up @@ -629,7 +663,8 @@ public static void Seed(JsonPocoQueryContext context)
SomeProperty = 20,
SomeNullableInt = null,
SomeNullableGuid = null,
IntArray = new[] { 5, 6 }
IntArray = new[] { 5, 6, 7 },
IntList = new() { 5, 6, 7 }
}
},
Orders = new[]
Expand Down Expand Up @@ -709,6 +744,7 @@ public class NestedStatistics
public int SomeProperty { get; set; }
public int? SomeNullableInt { get; set; }
public int[] IntArray { get; set; }
public List<int> IntList { get; set; }
public Guid? SomeNullableGuid { get; set; }
}

Expand Down

0 comments on commit d0841f7

Please sign in to comment.