-
Notifications
You must be signed in to change notification settings - Fork 225
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Work in progress on composite support #708
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Only had time for a partial review. I'll add another one with more coverage later today/tomorrow.
@austindrenski have pushed a commit addressing your comments thanks. Although this seems like it's too early for nits and other minor comments - I'm looking more for general comments on the approach, possible suggestions on how to handle the remaining issues, and opinions on whether this is viable with the limitations listed above (and for 2.2). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Another couple of questions. I'm still looking over the changes to NpgsqlTypeMappingSource
, but generally, I think this looks viable with the limitations.
Whether it's viable for 2.2 depends on how long we have until the release... I'm not expecting anything until after the Thanksgiving holiday, which means we could have at least another week to work on this.
In the meantime, some additional tests would be helpful to get a better picture of what is and is not working (e.g. a couple of Assert.Throws
would be illustrative).
@@ -659,6 +659,7 @@ public virtual void Generate(NpgsqlDropDatabaseOperation operation, [CanBeNull] | |||
Check.NotNull(builder, nameof(builder)); | |||
|
|||
GenerateEnumStatements(operation, model, builder); | |||
GenerateCompositeStatements(operation, model, builder); | |||
GenerateRangeStatements(operation, model, builder); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we create user-defined ranges first, so that they exist for use by composites?
// TODO: Schema support for the field type | ||
.Append(string.Join(", ", compositeType.Fields.Select( | ||
f => $"{Dependencies.SqlGenerationHelper.DelimitIdentifier(f.Name)} {Dependencies.SqlGenerationHelper.DelimitIdentifier(f.StoreType)}"))) | ||
.AppendLine(");"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a blocking issue for schema support here, or was it just simpler to skip it for now?
@@ -62,7 +77,10 @@ public class NpgsqlSqlTranslatingExpressionVisitor : SqlTranslatingExpressionVis | |||
[CanBeNull] Expression topLevelPredicate = null, | |||
bool inProjection = false) | |||
: base(dependencies, queryModelVisitor, targetSelectExpression, topLevelPredicate, inProjection) | |||
=> _queryModelVisitor = queryModelVisitor; | |||
{ | |||
_typeMappingSource = (NpgsqlTypeMappingSource)dependencies.TypeMappingSource; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this need to be NpgsqlTypeMappingSource
rather than TypeMappingSource
?
memberExpression.Expression, _queryModelVisitor.QueryCompilationContext, out _); | ||
if (properties.Count == 0) | ||
return null; | ||
var lastPropertyType = properties[properties.Count - 1].ClrType; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I know I've seen this elsewhere in the provider/upstream, but it always looks fragile... Could we move it into a helper and clarify what exactly this access pattern is doing?
{ | ||
public class NpgsqlCompositeTypeMapping : RelationalTypeMapping | ||
{ | ||
[NotNull] static readonly NpgsqlSqlGenerationHelper SqlGenerationHelper = |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this class needs to be updated along the lines of #626.
if (mappingInfo.ClrType?.IsEnum != true) | ||
return null; // ClrType was given and is not an enum | ||
|
||
return null; // NotImplemented |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure I follow what's happening here... Shouldn't this have the previous enum logic?
FYI there's a good chance we'll end up punting this for 3.0... @divega suggested an idea for working around the current lack of information in NpgsqlCompositeMapping, but there's a lot going on at the moment and 3.0 may change the query pipeline internals quite a bit in ways that may impact this. I'll still go over your review though, @austindrenski. |
It's a really large change, so I can't review it right now because of the presentation I mentioned. But definitely will do it in the nearest future. Could you share @divega's suggestion with us? |
Sure, sorry about that, was in a bit of a hurry... Basically the EF Core way of doing things is to manage as much as possible without a connection to the database. This is true when generating migrations, type mappings etc. Now, when first doing the support for enums, my idea was to pull all enum mappings from the ADO layer, since in any case the user has to set up enum mappings there ( Composite types are a bit different - the global mapping set up at the ADO level isn't enough for us; although we can get the fields from the CLR type (just like the enum) and even use the translator to generate their database counterparts, we're missing their order, since the CLR property order will not necessarily correspond to the PostgreSQL composite type's field order. And the literal representation of a composite in PostgreSQL is positional - you need to specify values in the order of the composite fields (without names) - so you must know the order. At the ADO layer, the order is only known when the composite type is actually loaded from the database, when the first connection is instantiated and the types are loaded from pg_type and related tables - but the EF Core type mapper has no access to any specific connection. So one way to solve this would be to somehow lazily communicate the database composite definition (with the order) to the EF Core type mapper/mapping on the first physical connection. That's complicated, and in theory you could have two databases with two different definitions being accessed with the same type mapper. @divega's suggestion was simply to make sure the user communicates all the necessary information at the EF Core level (i.e. context options) rather than attempting to pull in information from the ADO layer. This is more in line with who EF Core works and should work fine, but has its own difficulties - the user would need to duplicate the information already mapped at the ADO layer (store type, CLR type, name translator). But it's definitely doable. Then, for generating code literals (for data seeding) there are other challenges... To generate an instantiation of an arbitrary user CLR type we'd probably need to look for an appropriate constructor on the type (matching parameter names and types) and use that - not trivial but doable. As an alternative we could initialize the individual properties after the constructor ( |
Looks good from query perspective. Nested would be difficult due to MemberAccessBinding GetPropertyPath method. That method has deviated too far for initial use case and we certainly need better way of binding. I have that on my radar for 3.0 |
This is work in progress on composite support for EFCore.PG 2.2, allowing you to map a CLR type to a PostgreSQL column defined as a composite type. This leverages composite support at the Npgsql level which is already there.
It's definitely not complete, but I wanted to get some feedback before going any deeper. It's probably a bit ambitious to push this for 2.2, but we may have enough value to deliver something.
What works:
ForNpgsqlHasComposite()
can be used to generate composite types in the database (much likeForNpgsqlHasEnum()
. Currently there's no overload the infers the definition from the CLR type (like for enums).CREATE TYPE ... AS (...)
).WHERE my_entity.my_composite.some_field > 100
).What doesn't work:
MemberAccessBindingExpressionVisitor.GetPropertyPath
does not traverse the outer composite. I think it's OK to move forward while documenting this limitation.ForNpgsqlHasComposite()
, but it will be up to the user to actually create the relevant CLR type and entity properties. This partial support is currently what we do for enums, maybe in the future we can actually generate the CLR types as well.@ajcvickers @divega @smitpatel you may be interested in this work which seems to push the limits a little.