Skip to content

Commit

Permalink
Add support for code generation for List<X> types
Browse files Browse the repository at this point in the history
Fixes #2401
  • Loading branch information
yinzara committed Jun 10, 2022
1 parent d9a236c commit 4b29d6c
Show file tree
Hide file tree
Showing 5 changed files with 160 additions and 2 deletions.
4 changes: 2 additions & 2 deletions src/EFCore.PG/Internal/EnumerableMethods.cs
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ internal static class EnumerableMethods
//public static MethodInfo ToHashSet { get; }
//public static MethodInfo ToHashSetWithComparer { get; }

// public static MethodInfo ToList { get; }
public static MethodInfo ToList { get; }

//public static MethodInfo ToLookupWithKeySelector { get; }
//public static MethodInfo ToLookupWithKeySelectorAndComparer { get; }
Expand Down Expand Up @@ -553,7 +553,7 @@ static EnumerableMethods()

// ToArray = GetMethod(nameof(Enumerable.ToArray), 1, types => new[] { typeof(IEnumerable<>).MakeGenericType(types[0]) });

// ToList = GetMethod(nameof(Enumerable.ToList), 1, types => new[] { typeof(IEnumerable<>).MakeGenericType(types[0]) });
ToList = GetMethod(nameof(Enumerable.ToList), 1, types => new[] { typeof(IEnumerable<>).MakeGenericType(types[0]) });

// Take = GetMethod(nameof(Enumerable.Take), 1,
// types => new[]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,16 @@ public override NpgsqlArrayTypeMapping MakeNonNullable()
protected override RelationalTypeMapping Clone(RelationalTypeMappingParameters parameters, RelationalTypeMapping elementMapping)
=> new NpgsqlArrayListTypeMapping(parameters, elementMapping);

#region Code Generation

public override Expression GenerateCodeLiteral(object value)
{
var arrayExpr = base.GenerateCodeLiteral(value);
return Expression.Call(PostgreSQL.Internal.EnumerableMethods.ToList.MakeGenericMethod(ElementMapping.ClrType), arrayExpr);
}

#endregion

#region Value Comparison

// Note that the value comparison code is largely duplicated from NpgsqlArrayTypeMapping.
Expand Down
26 changes: 26 additions & 0 deletions src/EFCore.PG/Storage/Internal/Mapping/NpgsqlArrayTypeMapping.cs
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,32 @@ protected override void ConfigureParameter(DbParameter parameter)
}
}

#region Code Generation
public override Expression GenerateCodeLiteral(object value)
{
var values = (IList)value;
List<Expression> elements = new(values.Count);
var generated = true;
foreach (var element in values)
{
if (generated)
{
try
{
elements.Add(ElementMapping.GenerateCodeLiteral(element)); // attempt to convert if required
continue;
}
catch (NotSupportedException)
{
generated = false; // if we can't generate one element, we probably can't generate any
}
}
elements.Add(Expression.Constant(element));
}
return Expression.NewArrayInit(ElementMapping.ClrType, elements);
}
#endregion

// isElementNullable is provided for reference-type properties by decoding NRT information from the property, since that's not
// available on the CLR type. Note, however, that because of value conversion we may get a discrepancy between the model property's
// nullability and the provider types' (e.g. array of nullable reference property value-converted to array of non-nullable value
Expand Down
103 changes: 103 additions & 0 deletions test/EFCore.PG.FunctionalTests/Migrations/MigrationsNpgsqlTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -297,6 +297,50 @@ await Test(
);");
}

[Fact]
public virtual async Task Create_table_with_string_list_column()
{
await Test(
_ => { },
builder => builder.Entity(
"People", e =>
{
e.Property<int>("Id");
e.HasKey("Id");
e.Property<List<string>>("Values");
}),
asserter: null); // We don't scaffold unlogged

AssertSql(
@"CREATE TABLE ""People"" (
""Id"" integer GENERATED BY DEFAULT AS IDENTITY,
""Values"" text[] NULL,
CONSTRAINT ""PK_People"" PRIMARY KEY (""Id"")
);");
}

[Fact]
public virtual async Task Create_table_with_required_int_array_column()
{
await Test(
_ => { },
builder => builder.Entity(
"People", e =>
{
e.Property<int>("Id");
e.HasKey("Id");
e.Property<int[]>("Values").IsRequired();
}),
asserter: null); // We don't scaffold unlogged

AssertSql(
@"CREATE TABLE ""People"" (
""Id"" integer GENERATED BY DEFAULT AS IDENTITY,
""Values"" integer[] NOT NULL,
CONSTRAINT ""PK_People"" PRIMARY KEY (""Id"")
);");
}

public override async Task Drop_table()
{
await base.Drop_table();
Expand Down Expand Up @@ -683,6 +727,28 @@ await Test(
}
#pragma warning restore CS0618

[Fact]
public virtual async Task Add_string_list_column_with_default()
{
await Test(
builder => builder.Entity(
"People", e =>
{
e.Property<int>("Id");
e.HasKey("Id");
}),
_ => { },
builder => builder.Entity(
"People", e =>
{
e.Property<List<string>>("Values").HasDefaultValue(new[] { "1", "2" }.ToList());
}),
asserter: null); // We don't scaffold unlogged

AssertSql(
@"ALTER TABLE ""People"" ADD ""Values"" text[] NULL DEFAULT ARRAY['1','2']::text[];");
}

public override async Task Add_column_shared()
{
await base.Add_column_shared();
Expand Down Expand Up @@ -2553,6 +2619,43 @@ SELECT setval(
false);");
}

[ConditionalFact]
public virtual async Task InsertDataOperation_StringList()
{
await Test(
builder =>
{
builder.Entity(
"Person", e =>
{
e.Property<int>("Id");
e.Property<List<string>>("Values");
e.HasKey("Id");
});
},
_ => { },
builder =>
{
builder.Entity("Person").HasData(
new { Id = 1, Values = new List<string> { "1", "2" }},
new { Id = 2, Values = new List<string> { "2", "3" }});
},
_ => { });

AssertSql(
@"INSERT INTO ""Person"" (""Id"", ""Values"")
VALUES (1, ARRAY['1','2']::text[]);
INSERT INTO ""Person"" (""Id"", ""Values"")
VALUES (2, ARRAY['2','3']::text[]);",
//
@"SELECT setval(
pg_get_serial_sequence('""Person""', 'Id'),
GREATEST(
(SELECT MAX(""Id"") FROM ""Person"") + 1,
nextval(pg_get_serial_sequence('""Person""', 'Id'))),
false);");
}

#endregion

#region PostgreSQL extensions
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,25 @@ public void GenerateCodeLiteral_returns_tstzrange_Interval_literal()
CodeLiteral(new Interval(new LocalDateTime(2020, 01, 01, 12, 0, 0).InUtc().ToInstant(), null)));
}

[Fact]
public void GenerateCodeLiteral_returns_string_array_literal()
{
Assert.Equal(
@"new[] { ""1"", ""2"", ""3"" }",
CodeLiteral(new[] { "1", "2", "3"})
);
}

[Fact]
public void GenerateCodeLiteral_returns_int_list_literal()
{
Assert.Equal(
@"System.Linq.Enumerable.ToList(new int[] { 1, 2, 3 })",
CodeLiteral(new List<int> { 1, 2, 3})
);
}


[Fact]
public void Interval_array_is_properly_mapped()
{
Expand Down

0 comments on commit 4b29d6c

Please sign in to comment.