Skip to content

Commit

Permalink
Add translation of string.Join overload used with List<string> parame…
Browse files Browse the repository at this point in the history
…ter (#3106)

Fixes #3105

(cherry picked from commit 707c89e)
  • Loading branch information
georg-jung authored and roji committed Feb 24, 2024
1 parent 1e6c725 commit f280bb7
Show file tree
Hide file tree
Showing 8 changed files with 229 additions and 131 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using Npgsql.EntityFrameworkCore.PostgreSQL.Query.Expressions.Internal;
using Npgsql.EntityFrameworkCore.PostgreSQL.Storage.Internal;
using Npgsql.EntityFrameworkCore.PostgreSQL.Storage.Internal.Mapping;
using static Npgsql.EntityFrameworkCore.PostgreSQL.Utilities.Statics;
using ExpressionExtensions = Microsoft.EntityFrameworkCore.Query.ExpressionExtensions;

Expand Down Expand Up @@ -100,6 +101,9 @@ private static readonly MethodInfo LastOrDefaultMethodInfoWithoutArgs
private static readonly MethodInfo String_Join4 =
typeof(string).GetMethod(nameof(string.Join), new[] { typeof(char), typeof(string[]) })!;

private static readonly MethodInfo String_Join5 =
typeof(string).GetMethod(nameof(string.Join), [typeof(string), typeof(IEnumerable<string>)])!;

private static readonly MethodInfo String_Join_generic1 =
typeof(string).GetTypeInfo().GetMethods(BindingFlags.Public | BindingFlags.Static | BindingFlags.DeclaredOnly)
.Single(
Expand Down Expand Up @@ -334,8 +338,10 @@ public NpgsqlStringMethodTranslator(NpgsqlTypeMappingSource typeMappingSource, I
|| method == String_Join2
|| method == String_Join3
|| method == String_Join4
|| method == String_Join5
|| method.IsClosedFormOf(String_Join_generic1)
|| method.IsClosedFormOf(String_Join_generic2)))
|| method.IsClosedFormOf(String_Join_generic2))
&& arguments[1].TypeMapping is NpgsqlArrayTypeMapping)
{
// If the array of strings to be joined is a constant (NewArrayExpression), we translate to concat_ws.
// Otherwise we translate to array_to_string, which also supports array columns and parameters.
Expand Down
143 changes: 86 additions & 57 deletions test/EFCore.PG.FunctionalTests/Query/ArrayArrayQueryTest.cs

Large diffs are not rendered by default.

153 changes: 91 additions & 62 deletions test/EFCore.PG.FunctionalTests/Query/ArrayListQueryTest.cs

Large diffs are not rendered by default.

6 changes: 4 additions & 2 deletions test/EFCore.PG.FunctionalTests/Query/ArrayQueryFixture.cs
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,10 @@ public IReadOnlyDictionary<Type, object> EntityAsserters
Assert.Equal(ee.NullableText, ee.NullableText);
Assert.Equal(ee.NonNullableText, ee.NonNullableText);
Assert.Equal(ee.EnumConvertedToInt, ee.EnumConvertedToInt);
Assert.Equal(ee.ValueConvertedArray, ee.ValueConvertedArray);
Assert.Equal(ee.ValueConvertedList, ee.ValueConvertedList);
Assert.Equal(ee.ArrayOfStringConvertedToDelimitedString, ee.ArrayOfStringConvertedToDelimitedString);
Assert.Equal(ee.ListOfStringConvertedToDelimitedString, ee.ListOfStringConvertedToDelimitedString);
Assert.Equal(ee.ValueConvertedArrayOfEnum, ee.ValueConvertedArrayOfEnum);
Assert.Equal(ee.ValueConvertedListOfEnum, ee.ValueConvertedListOfEnum);
Assert.Equal(ee.IList, ee.IList);
Assert.Equal(ee.Byte, ee.Byte);
}
Expand Down
10 changes: 9 additions & 1 deletion test/EFCore.PG.FunctionalTests/Query/ArrayQueryTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -504,12 +504,20 @@ public virtual Task Concat(bool async)
// Note: see NorthwindFunctionsQueryNpgsqlTest.String_Join_non_aggregate for regular use without an array column/parameter
[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public virtual Task String_Join_with_array_parameter(bool async)
public virtual Task String_Join_with_array_of_int_column(bool async)
=> AssertQuery(
async,
ss => ss.Set<ArrayEntity>()
.Where(e => string.Join(", ", e.IntArray) == "3, 4"));

[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public abstract Task String_Join_with_array_of_string_column(bool async);

[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public abstract Task String_Join_disallow_non_array_type_mapped_parameter(bool async);

#endregion Other translations

#region Support
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,10 @@ public class ArrayEntity
public SomeEnum EnumConvertedToString { get; set; }
public SomeEnum? NullableEnumConvertedToString { get; set; }
public SomeEnum? NullableEnumConvertedToStringWithNonNullableLambda { get; set; }
public SomeEnum[] ValueConvertedArray { get; set; } = null!;
public List<SomeEnum> ValueConvertedList { get; set; } = null!;
public SomeEnum[] ValueConvertedArrayOfEnum { get; set; } = null!;
public List<SomeEnum> ValueConvertedListOfEnum { get; set; } = null!;
public string[] ArrayOfStringConvertedToDelimitedString { get; set; } = null!;
public List<string> ListOfStringConvertedToDelimitedString { get; set; } = null!;
public IList<int> IList { get; set; } = null!;
public byte Byte { get; set; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,28 @@ protected override void OnModelCreating(ModelBuilder modelBuilder)
e.Property(ae => ae.NullableEnumConvertedToStringWithNonNullableLambda)
.HasConversion(new ValueConverter<SomeEnum, string>(w => w.ToString(), v => Enum.Parse<SomeEnum>(v)));

e.PrimitiveCollection(ae => ae.ValueConvertedArray)
e.Property(ae => ae.ListOfStringConvertedToDelimitedString)
.HasConversion(
v => string.Join(",", v),
v => v.Split(',', StringSplitOptions.None).ToList(),
new ValueComparer<List<string>>(
(c1, c2) => c1.SequenceEqual(c2),
c => c.Aggregate(0, (a, v) => HashCode.Combine(a, v.GetHashCode())),
c => c.ToList()));

e.Property(ae => ae.ArrayOfStringConvertedToDelimitedString)
.HasConversion(
v => string.Join(",", v),
v => v.Split(',', StringSplitOptions.None).ToArray(),
new ValueComparer<string[]>(
(c1, c2) => c1.SequenceEqual(c2),
c => c.Aggregate(0, (a, v) => HashCode.Combine(a, v.GetHashCode())),
c => c.ToArray()));

e.PrimitiveCollection(ae => ae.ValueConvertedArrayOfEnum)
.ElementType(eb => eb.HasConversion(typeof(EnumToStringConverter<SomeEnum>)));

e.PrimitiveCollection(ae => ae.ValueConvertedList)
e.PrimitiveCollection(ae => ae.ValueConvertedListOfEnum)
.ElementType(eb => eb.HasConversion(typeof(EnumToStringConverter<SomeEnum>)));

e.HasIndex(ae => ae.NonNullableText);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,10 @@ public static IReadOnlyList<ArrayEntity> CreateArrayEntities()
EnumConvertedToString = SomeEnum.One,
NullableEnumConvertedToString = SomeEnum.One,
NullableEnumConvertedToStringWithNonNullableLambda = SomeEnum.One,
ValueConvertedArray = new[] { SomeEnum.Eight, SomeEnum.Nine },
ValueConvertedList = new List<SomeEnum> { SomeEnum.Eight, SomeEnum.Nine },
ValueConvertedArrayOfEnum = new[] { SomeEnum.Eight, SomeEnum.Nine },
ValueConvertedListOfEnum = new List<SomeEnum> { SomeEnum.Eight, SomeEnum.Nine },
ArrayOfStringConvertedToDelimitedString = ["3", "4"],
ListOfStringConvertedToDelimitedString = ["3", "4"],
IList = new[] { 8, 9 },
Byte = 10
},
Expand Down Expand Up @@ -110,8 +112,10 @@ public static IReadOnlyList<ArrayEntity> CreateArrayEntities()
EnumConvertedToString = SomeEnum.Two,
NullableEnumConvertedToString = SomeEnum.Two,
NullableEnumConvertedToStringWithNonNullableLambda = SomeEnum.Two,
ValueConvertedArray = new[] { SomeEnum.Nine, SomeEnum.Ten },
ValueConvertedList = new List<SomeEnum> { SomeEnum.Nine, SomeEnum.Ten },
ValueConvertedArrayOfEnum = new[] { SomeEnum.Nine, SomeEnum.Ten },
ValueConvertedListOfEnum = new List<SomeEnum> { SomeEnum.Nine, SomeEnum.Ten },
ArrayOfStringConvertedToDelimitedString = ["5", "6", "7", "8"],
ListOfStringConvertedToDelimitedString = ["5", "6", "7", "8"],
IList = new[] { 9, 10 },
Byte = 20
}
Expand Down

0 comments on commit f280bb7

Please sign in to comment.