Updated: Comment #111
The status quo
Taken over from Drupal 7's hook_field_schema(), almost all implementations of \Drupal\Core\Field\FieldItemInterface::schema() specify 'not null' => TRUE for some of their columns.
Although this is not enforced in any way, the columns that are NOT NULL are those that control whether the field item is empty or not. Therefore when field data is stored in dedicated tables - which is how it is done for configurable fields - the NOT NULL does not cause problems: If a certain field of an entity is empty, no field item will be saved in the dedicated table at all.
When the field data is stored in a shared table - which is how it is done for base fields - the NOT NULL would cause problems for fields that are not required: If no data for a field is specified there are no values for the columns that are marked NOT NULL. This currently does not surface anywhere as we specify default values for all fields that are marked as NOT NULL which then get saved into the database.
Furthermore, when new field columns get added to shared tables as part of an entity type update this breaks when the schema specifies NOT NULL but no default value.
The constraints
Creating schemas with NOT NULL is not only more semantic, but it also leads to increased performance for indexes on the respective columns. Therefore - although it would be possible - it is not desirable to drop NOT NULL from schemas entirely.
For the reasons mentioned above we can only add NOT NULL for fields that are marked as required. Thus, we can no longer hardcode 'not null' (neither TRUE nor FALSE) in FieldItemInterface::schema(). Instead it needs to become per-field-definition information.
A field can have multiple columns and any combination of those can be "essential" for the field type, and should be (conditionally) marked as NOT NULL. For example, for a required image field, the target_id column should be marked as NOT NULL, while the alt and title columns shoult not. (This is related to the return value of FieldItemInterface::isEmpty() but it is not - or not necessarily - identical, so that is a separate (albeit related) problem.) If a field item is not required, no columns should be marked as NOT NULL.
The decision of which columns should be "essential" must be done per-field-type. Since the schema column names must correspond to the names of a field's property definitions anyway, DataDefinitionInterface::isRequired() on the property definitions can be used to make this differentiation
As the storage schema should have complete control over the entity schema the logic that adds 'not null' should be contained in the storage schema.
Proposed resolution
- Remove
'not null'from all implementations ofFieldItemInterface::schema() - Document that
'not null'should not be used inFieldItemInterface::schema() - Make field items use
setRequired(TRUE)on property definitions of "essential" columns. - Document on
FieldItemInterface::propertyDefinitions()thatsetRequired(TRUE)should be utilized for "essential" property definitions. - Add code to
SqlContentEntityStorageSchema::getDedicatedTableSchema()to set those columns to'not null'for which property definition is required.
User interface changes
None.
API changes
- FieldItem::schema() does not support 'not null' statements, they are discarded if present.
- FieldItem::propertyDefinitions() should use $property->setRequired(TRUE) to denote "essential" properties (those that are required to constitue a non-empty Item).
For those required properties, the SchemaHandler *can* decide to assign a 'not null' = TRUE schema statement, depending on the storage layout
| Comment | File | Size | Author |
|---|---|---|---|
| #161 | 2232477-not_null-161.patch | 90.05 KB | plach |
| #105 | 2232477-not_null-105-fail.patch | 8.28 KB | plach |
Comments
Comment #1
tstoecklerHere we go. I went with the alternative solution for now. We have $field_definition at hand so - as can be seen from the patch - it's not really a lot of work. Let's see how badly this fails. :-)
Comment #2
tstoeckler1: 2232477-1-field-schema-not-null.patch queued for re-testing.
Comment #3
sunCross-posting from #2183231-316: Make ContentEntityDatabaseStorage generate static database schemas for content entities:
Re: To NULL or to NOT NULL:
As long as the columns are not part of an index, the change doesn't make a difference. However, for columns that are part of an index, allowing NULL
values should be used wisely, because affected indexes require an additional internal flag to track emptiness. That makes the index larger, and a larger index negatively affects filter/group/sort performance.
This is part of the one-pager about most basic database performance optimization rules, which everyone should know inside-out and follow by heart:
http://dev.mysql.com/doc/refman/5.6/en/data-size.html
Comment #4
plachWe discussed this a bit in IRC, this is the plan that came out:
Comment #5
tstoecklerRe #4: I understood that agreement to be the temporary solution for #2183231: Make ContentEntityDatabaseStorage generate static database schemas for content entities so that one doesn't incur any performance penalties. Except that I thought we would just add the
NOT NULLmanually, instead if automatically (for now). I don't see any reason to implement #4 as our long-term solution if we can just as well always addNOT NULLas long as fields are required. I.e. just what patch #1 does.With patch #1 the performance problem mentioned in #3 is no longer given - as long as entity type authors (and site builders, to a lesser extent) remember to make the appropriate fields required. But if the fields are not required we cannot safely mark the
NOT NULLanyway, so as far as I'm aware, #1 implements the best of all worlds.I'd like to hear some more thoughts on this, though.
Comment #6
tstoeckler1: 2232477-1-field-schema-not-null.patch queued for re-testing.
Comment #8
tstoecklerRerolled.
Because we now pass
FieldStorageInterfaceintoFieldItemInterface::schema(),isRequired()now needs to be onFieldStorageInterfaceand not onFieldDefinitionInterface. Hence, the interdiff.Checked that this still covers all core field types.
Comment #9
tstoecklerComment #11
fagoSo we add not null when a field is required plus fix #2274597: Not all required fields are marked as required. Or any problems I miss?
Comment #12
tstoecklerExactly. Now we just need to make the patch pass :-)
Comment #13
yched commentedHmm, 'required' is a per-instance property on confugurable fields, we can't put it in FieldStorageDefinition nor use it to build the schema ?
Comment #14
tstoecklerOh right, I forgot about that. That's a problem.
Just like the
form element descriptions === field definition descriptions === schema descriptionsproblem our unification is biting us in the tail where the storage backend and the user interface have different needs but are both served from the same middle-man: The field definitions. And the fact that there no longer is any clean way to differentiate between things likenode.idandnode.body. Not really sure how to address this.We want both:
A. To be able to declare certain fields as
NOT NULLin the schema not only for theoretical purity but also for index performanceB. To be able to configure whether or not a certain field is marked as required in the user interface (or also for RESTful requests) per field instance.
So some thoughts on this:
FieldInstanceConfigthan forFieldConfig. I.e. aFieldInstanceConfigcan be required while the underlyingFieldConfigis not required.FieldDefinitionInterfacethan forFieldStorageDefinitionInterface.isRequired()both onFieldStorageDefinitionInterfaceandFieldDefinitionInterfacewhere the two could vary.FieldDefinitionInterfaceextendsFieldStorageInterface, so it is impossible for both to have a method with the same name but different semantics.FieldStorageDefinitionInterfacethat clarifies its different meaning and is not confusing alongsideisRequired(), i.e.isStorageRequired().This is just my train of thought and I am not 100% happy with it myself, so would love to hear some thoughts and hopefully better suggestions! :-)
Comment #15
berdirThis came up again today/yesterday in some discussions, assigning to @yched to get some feedback from him :)
Comment #16
tstoecklerSo I thought about this recently and since we now have separated
FieldDefinitionInterfacefromFieldStorageDefinitionInterfacethis should actually be cleanly doable now by providing anisRequired()methods on both interfaces with different semantics:-
FieldStorageDefinitionInterface::isRequired()<->NOT NULLin schema-
FieldDefinitionInterface::isRequired<-> Form field requiredWe then need to enforce in
FieldDefinitionthat if its storage is required it will always be required also.Thoughts?
Comment #17
catchThis issue is causing the exceptions plach found in #2347301: "Data truncated for column" exceptions thrown when adding fields to the user entity type. We set not null true but don't set a default at all.
Bumping priority and tagging.
Comment #18
tstoecklerWill work on this now. Feedback from @yched is of course still very much appreciated.
Comment #19
tstoecklerHere we go.
Started from scratch, so no interdiff.
Comment #21
tstoecklerDiscussed this with @yched and @catch. This is a difficult problem space, so instead of writing down everything in this comment, I've updated the issue summary, so that it's easier to find and iterate on. Keep in mind, though, that I might not have grasped the problem space including potential solutions in its entirety so especially @yched, @plach, @catch et al please do be bold in improving the issue summary further.
Comment #22
plachThe issue summary looks good to me :)
Comment #23
yched commented@tstoeckler: Thanks ! The issue summary looks pretty much inline with our discussion.
I think at some point in the discussion was the notion that 'not null = TRUE' was never ok in base tables ?
Actually, it's a little more complex than that :-/
- 'not null' columns are ok in the initial table creation, or in columns added later as long as the table is empty.
- they fatal when adding new columns the moment the table has rows
So :
1) either we never say 'not null TRUE' for columns in base tables,
Drawback: we lose index optimization in base tables - a bit unfortunate, base tables are the ones where fast indexes are most useful...
2) or we add 'not null's to columns created while the table is empty, and stop doing that the moment the table has data
Drawback: the resulting schema depends on "history", that sounds like a recipe for hairpulling "why is the schema on prod different from the one on my dev machine".
3) or we somehow find a way to support a notion of 'initial' value on 'not null' columns - but as discussed previously, I have no clue how to support that. $base_field->setInitialValue($property, $value) ?
That only makes sense for "base fields that the table mapping will decide to place in the base table, and that are added after some entities were created" (and table mappings are supposed to be swappable, right ?), so it's a bit difficult to reason on it as a generic feature...
Comment #24
yched commentedI'd think 2) is not on the table.
We can punt and go with 1) for now, and think of ways to do 3) in a followup ?
Comment #25
plach#24 sounds like a plan to me.
3) is #2346019: Handle initial values when creating a new field storage definition, I guess.
Comment #26
yched commented@plach: indeed.
Meaning :
- this (critical) issue here would fix the curent (critical) bug of "Fatal when adding new fields in a base table that contains existing entities" by doing 1) ( = "no 'not null's at all in base tables, too bad for index optimization")
- and then #2346019: Handle initial values when creating a new field storage definition (wich we should demote to non-critical) would try to add back the possiblity of 'not null' in base fields , by somehow adding some support for 'initial'
Does that sound like a viable plan ?
Comment #27
plachYep :)
Comment #28
catchBoth the issue summary and splitting the issue in two look right to me.
Comment #29
plach@tstoeckler:
Are you still planning to work on this? I can take it over otherwise.
Comment #30
tstoecklerPlanning, yes. But the assignment is a lie at this point. Sorry, had overlooked that. In other words: Knock yourself out! :-)
Comment #31
xjmSounds like we need a summary update for #23 - #24?
Comment #32
amateescu commentedWe also need a patch :)
Comment #34
amateescu commentedJust a bit greedy in the previous patch.
Comment #35
xjmDiscussed with @alexpott, @effulgentsia, and @dawehner. Retitling to the critical bug this will solve.
Comment #36
chx commentedThis will also solve a serious migrate issue where things with parents (think comments, menu links, taxonomy terms) currently tend to fatal when we stub their parents. #2372837: menu link stubbing fails because fields are NOT NULL
Comment #37
amateescu commented@xjm, the issue summary was updated in #34 :)
Comment #38
xjmOops. :)
Comment #39
plachHere is a test-only version of #34 to check whether the bug is exposed...
Comment #41
plachLooks good to me, thanks!
(Patch to commit in #34)
Comment #42
plachUpdated issue summary.
Comment #43
tstoecklerDon't want to hold this up any longer as this fixes a real problem but kind of sad to see all the previous work on this issue lost...
Comment #44
plach@tstoeckler:
I thought the plan was using most of it in #2346019: Handle initial values when creating a new field storage definition...
Comment #45
alexpottThis issue addresses a critical bug and is allowed per https://www.drupal.org/core/beta-changes. Committed 08d3ebb and pushed to 8.0.x. Thanks!
Let's proceed with #2346019: Handle initial values when creating a new field storage definition and move on with this to unblock other patches.
Comment #47
tstoeckler@plach: Yeah, let's do that. I just think it would have been fine to at least include all the
'not null' => TRUEremovals from the core field types so we have that out of the way. But yeah, whatever :-)progress++
Comment #48
yched commentedSorry that I didn't react earlier on #34, but yes, I thought the plan discussed in Amsterdam (steps 1 to 5 in the issue summary) was still valid :
- remove all 'not null = TRUE' in FieldItem::schema()
- use setRequired() on property definitions instead, to carry that same information (non-required = "non essential column, might be empty even when the field value itself is not empty because other properties have values")
- adapt SqlContentEntityStorageSchema to assign 'not null = TRUE / FALSE' automatically depending on property->isRequired()
I missed the part where we ditched that plan :-)
The discussion in #23 / #24 only pointed that, until we have support for 'initial', base tables have to force 'not null = FALSE' for all properties, independently of the isRequired() state - something like :
$not_null = ($base_table) ? FALSE : $property->isRequired();
Moving from "field types specify 'not null' in their schema()" to "field types specify isRequired() in their propertyDefinitions()" is not related to a (still hypothetical) support for "initial", so has nothing to do with #2346019: Handle initial values when creating a new field storage definition ?
Comment #49
plachSorry, I should have read again previous comments before reviewing the patch, given that in Amsterdam I was pretty sick and obviously I didn't retain the details of our discussion. Should we reopen this (maybe demoting it)?
Comment #50
amateescu commentedOops, sorry as well. Since I didn't participate in this particular discussion in Amsterdam, I took #23, #26 and #28 as the way forward because nothing else was documented in the issue.
Comment #51
catchWe shouldn't re-use the issue unless we roll back the patch that's already gone in - makes it much harder to track history etc. (i.e. if we demote this to major it'll drop out of fixed criticals stats etc.). A new issue to continue this is fine. I don't think a rollback is required but given it's a small-ish patch that went in I'd also be fine doing that if we really want to continue here?
Comment #52
yched commented@amateescu :
@tstoeckler put it in the issue summary in #21 :-)
Anyway - then yeah, I guess new issue now. Although the original plan (see bottom of the IS or #48) would now be an API change that doesn't fix a critical anymore...
The idea behind that plan was to avoid the situation where the field type schema() says one thing but the final DB schema might turn out different. It was cleaning the fact that the information currently provided by FieldItem::schema() 'not_null' is actually not about the schema, it's about the property - "is this property essential/required to constitute a field value or not". Also makes it clearer for field type authors to figure out how to fill that information ("why should I put not_null = TRUE or FALSE ?" is quite obscure at the moment).
Not sure whether that move to better API conceptual consistency is still an acceptable change now that the critical symptom has been fixed in a simpler way, though. @catch ? ;-)
Comment #53
catch@yched I remember that discussion (or some of it) from Amsterdam and it also bothers me a bit about the patch (but I didn't review the final committed patch before it was committed).
I actually think we should roll back and continue the discussion here. If we get stuck, we can always re-commit #39 but schema definition not matching the schema feels like a normal bug at least and this is a real continuation of the issue, not just a follow-up.
Moving back to active for now - @alexpott what do you think?
Please no patches on this issue until after a revert decision though.
Comment #54
plachMy bad, this was not my end goal too, obviously. I thought we would just fix the critical issue to unblock the stuff depending on it and go on with the plan in #2346019: Handle initial values when creating a new field storage definition. Again, sorry for the confusion.
Comment #55
alexpottI'm happy with going either way here - I see the merits of rolling back - but I also see the merits of doing the simple thing to unblock and handling the complexity in #2346019: Handle initial values when creating a new field storage definition.
Comment #57
catchOK I went ahead and rolled this back.
@Alex #2346019: Handle initial values when creating a new field storage definition would allow for not null field schemas.
This issue is essentially preventing not null field schemas, but without updating the field definitions, and I think that's more or less all we need to do here. We could possibly throw an exception rather than forcing the definition change once core is converted, so it's clear that's not possible at the moment too.
Comment #58
yched commentedHere's a patch for the approach that was discussed in AMS.
- uses $property->setRequired() in FieldItem::propertyDefinitions() to denote required properties
- uses that to determine 'not null' in tabe schemas in SqlContentEntityStorageSchema
- removes all 'not null's in FieldItem::schema() - a lot of them were moot / inconsistent.
Let's see what breaks...
Side notes:
- DataDefinitionInterface contains isRequired() but not setRequired() - well, it doesn't contain any setter, for that matter...
- 'path' field type (PathItem) : WTF ? no schema ? nothing is ever stored ?
Comment #60
yched commentedA lot of those fails seem to be around NULL format for text fields. Current code doesn't seem to treat that property as "required", let's see how many tests this fixes.
Comment #61
yched commentedAlso - damn, setting entity_ref's target_id property to "required" breaks autocreate, of course : the target_id receives an actual ID when the "autocreate" entity is saved, during the parent entity's preSave(), which means after validation. That's a problem.
Leaving it out for now, just to see.
Comment #64
plachJust a reroll and a small fix (probably not the right one).
Comment #66
effulgentsia commentedCan we do this instead by either requiring code that creates a key field to call
setRequired(TRUE), or else doing that in an appropriate central place, but not special-casing a difference between required and 'not null' here?Comment #67
effulgentsia commentedIgnore #66. I understand now why that's being punted to the @todo. So I think "all" that's needed here is fixing the test failures.
Comment #68
yched commentedInterdiff #64 :
Testbot seems to like that. Would be interesting to determine what triggers that, though - a column name that doesn't match a property looks like something is wrong with some field type.
The column names defined in schema() should be a subset of the properties defined in propertyDefinitions() - even through we don't strictly check and enforce that atm.
Comment #69
fagoI created #2390495: Support marking field storage definitions as required per our discussion. It's not needed to resolve this critical.
Comment #70
plachI'm working on this.
Comment #71
plachThis should be a better version of #64, which should give us passing PHP Unit tests. Let's see whether I broke anything in the process.
Comment #72
plachStill on this :)
Comment #74
plachtagging
Comment #75
plachLet's clean up things a bit.
Comment #76
plachThis should fix most remaining failures
Comment #77
plachMmh, not sure why #71 was lost, restoring it + reapplying
EntityReferenceItemchanges.Comment #81
plachDiscussed with @yched and @amateescu a fix for entity references having the autocreate option enabled. This is a start but it's working well while manual testing, let's see how many failures are left.
Comment #83
plachSlightly different approach. I'm not completely satisfied about it yet but let's see whether it's working better.
Interdiff is with #77.
Comment #85
amateescu commentedThe number of failures seems to indicate that we're on a better path :)
Comment #86
plachThis should be green, now I'm going to clean-up
EntityReferenceItemto centralize the new logic and add more test coverage.Comment #88
plachreroll
Comment #89
effulgentsia commentedYay! Very happy to see this green. "Needs work" per #86.
Comment #90
fagoIt would help simplifying things if we could get #2137309: Typed data does not handle set() and onChange() consistently in first, but it's not required to block this blocking issue on it.
Comment #91
plachThis cleans-up ERI taking inspiration from #2137309: Typed data does not handle set() and onChange() consistently, let's see whether it's still green.
Comment #92
tstoecklerWrong issue? :-)
Comment #93
plachoops, wrong patch, thanks :)
Comment #95
swentel commentedSome doc nitpicks
constitute ?
Comments of the params need indentation
Comment #96
plachAdded explicit test coverage for the new code and fixed #95. We should be done here.
Comment #97
plachSmall clean-up
Comment #98
amateescu commentedI looked over the patch and the parts in EntityReferenceItem look good to me. I will have to leave the RTBC to @fago or @yched though..
Comment #99
plachTrying to unblock the patch
Comment #100
plachJust a reroll
Comment #101
effulgentsia commentedThe whole patch looks great to me. Very minor nits:
Cute. But really? Can we use 0 or -1 instead?
With the changes in EntityReferenceItem that keep target_id and entity in sync, can we remove these 'target_id' lines entirely?
There's also a 'not null' in this class that needs to be removed. Also some in ShapeItem.
Comment #102
effulgentsia commentedAlso, this will need a change record instructing contrib field types to:
- Remove their 'not null' lines.
- Add a setRequired() where needed, which for a single-property field should always include that property.
Comment #103
jibranNW as per #101
Why are we not using
$this->set('target_id', $this->entity->id(), FALSE);here? AFAIK save is always called on parent i.e. entity then why set$notifyTRUEhere?Comment #104
plachThis should address the reviews above. It also expands test coverage to actually cover the main bug here, because that was not the case previously.
Working on the CR right now.
Comment #105
plachHere you can find a failing patch.
Comment #107
plachSpoke with @fago about #103 and we agreed there are cases where the parent needs to be notified the target id column has changed, e.g. to invalidate caches. Reverting that change.
Comment #108
plachDraft CR at https://www.drupal.org/node/2392807
Comment #109
plachComment #110
effulgentsia commentedThanks!
Comment #111
yched commentedReverted the issue summary to the actual approach we use for the fix ($property->setRequired(TRUE)).
Comment #112
yched commentedOther than that, I'm just a little worried that the changes in ERItem::setValue() / set() / onChange() clash with or anticipate the cleanup being done in that exact area in
#2386559: ERItem::setValue(array('entity' => $entity) produces broken Items
and
#2137309: Typed data does not handle set() and onChange() consistently
(the former is actually waiting for the latter being committed)
It might be better to get those in first ?
Comment #113
plachComment #114
plachThe proposed solution was outdated.
@yched:
This is a prioritized critical, so I don't think it should wait for those: if the changes here are good the other issues just need a reroll. This was written with an eye to #2137309: Typed data does not handle set() and onChange() consistently, btw.
Comment #115
yched commentedDiscussed with @fago, he agreed it would be best to get #2137309: Typed data does not handle set() and onChange() consistently and #2386559: ERItem::setValue(array('entity' => $entity) produces broken Items in first. This area (TypedData notifications & sync between properties in an ER item) is quite confused at the moment, and untangling it is a bit hairpulling.
The 1st one is RTBC already, I'll bug @alexpott to try to get it committed asap.
@fago said he'd update the 2nd one today with a patch rolled on top of the 1st one, for a quicker RTBC.
Comment #116
gábor hojtsySo this makes #1916790: Convert translation metadata into regular entity fields postponed not only this one issue but now three issues. Considering this issue as an endpoint of things would be incorrect. See #1916790-98: Convert translation metadata into regular entity fields. Is that intended?
Comment #117
plachSpoke with @yched and he's ok with unpostponing this now that #2137309: Typed data does not handle set() and onChange() consistently landed, #2386559: ERItem::setValue(array('entity' => $entity) produces broken Items will be rolled on top of this instead.
The reroll was a bit tough so I'm providing a diff of
EntityReferenceItem(the only part that was involved in the merge) for reviewer's convenience.Comment #118
fagoAgreed, it should be easy to merge #2386559: ERItem::setValue(array('entity' => $entity) produces broken Items in later on.
Comment #119
plachReviewing the patch with @yched we found a small issue.
Comment #121
yched commentedSo my worry is :
The first hunk largely reverts what was done in #2137309: Typed data does not handle set() and onChange() consistently (keep the main logic in Map::setValue() and have child implementations be mere wrappers around that).
The second hunk directly contradicts the approach for bringing sanity to the 'target_id' / 'entity' sync dance that @fago defined in #2386559: ERItem::setValue(array('entity' => $entity) produces broken Items.
It seems the unique point of those changes is to ensure that "wehenever 'entity' received to an unsaved entity, 'taget_id' needs to be set to NEW_ENTITY_MARKER".
Given that :
- Such syncing is currently over complicated, I'm honestly lost in it myself.
- #2386559: ERItem::setValue(array('entity' => $entity) produces broken Items has a plan to make those "sync" simpler
- Meanwhile, this patch here needs convoluted code to acount for its own, new syncing needs,
I'd be way more confortable with having #2386559: ERItem::setValue(array('entity' => $entity) produces broken Items clarify things first (it's green), and then use it here to express our new sync behavior using simple code", rather than committing the current convoluted code here and have to undo it later.
But well, I'm officially lost in that code, so I'll leave it to @fago to decide.
Comment #122
plachFair enough, unpostponed #2386559: ERItem::setValue(array('entity' => $entity) produces broken Items.
Comment #124
plachI think I got it right this time. I was not able to succesfully reroll this exactly because #2386559: ERItem::setValue(array('entity' => $entity) produces broken Items. The attached patch is rolled upon it so it should be an easy reroll once that lands.
Setting NR for the bot, but still actually postponed.
Comment #125
jibranWhy can't we use $this->get('entity')->getTargetIdentifier() here?
Comment #126
plachNot sure what you mean: the ternary operator is needed because in the case of a new entity the ID is
NULLor a value different fromstatic::$NEW_ENTITY_MARKER. If the entity is new we need to use the marker value to populatetarget_id, so that it will pass not null validation even if the entity actual ID isNULL.Comment #127
jibranYeah, now I see you are right please ignore my comment and thanks for the explanation.
Comment #131
plachThis should be green again.
Comment #132
jibranThis comment needs update cuz it doesn't make sense with current condition.
Comment #133
plachRight, thanks :)
Comment #134
plachd.o.--
Comment #135
jibranPatch looks good to me. The only questionable thing after #121 is addressed so RTBC.
Comment #136
alexpottShouldn't we ensuring this for all identifiers not only those of type int (that we convert to serial)?
From FieldDefinitionInterface - is this statement true?
This is also ringing alarm bells.
Comment #137
jibranCan we please add a quick test of
($values['target_id'] != static::$NEW_ENTITY_MARKER || !$this->entity->isNew())part of the condition inEntityReferenceItemTest?Comment #138
yched commentedNitpick : any way we could split that on several lines using temp vars ?
Nitpick : IMO we can simplify the comment to "Save the entity if it has not already been saved by some other code".
Awesome, much simpler than in the previous iterations !
Although : when assigning '2' to the 'entity' property, this code here will now trigger an entity load ? Any way we could avoid that ?
That's a perf impact, since "assign '2' to the 'entity' property" is what happens on $node->field_ref = 2 / $node->field_ref->setValue(2) - see the comment at the beginning of ERItem::setValue().
@fago, do you think that is problematic ?
Comment #139
plachThis should address #136 and #137 (plus a couple of other cases I came up with meanwhile).
@alexpott:
Yep :)
Actually that line is redundant in the default implementation because the same
'not null'clause is set in::getSharedTableFieldSchema(). Anyway subclasses may make find this useful, so better making it work correctly for all possible cases.Comment #140
plach"at least"
Not sending the patch to the testbot as there is a big queue already: this is green if the previous one is (only a PHP doc a change).
Comment #141
jibranAwesome tests thanks @plach
Can we replace -1 with proper variable here?
Comment #142
plachD'oh, missed #138.
Nope, it's protected :)
Please, tell me this is RTBC now :)
Comment #143
yched commentedre: @alexpott #136.2 and the interdiff in #139 :
I'm not sure I understand that new comment, and IMO it unnecessarily muddies the water. We are in the code that deals with per-field tables, for which *field* requiredness doesn't matter.
It matters upstream at the entity validation level, but storage-wise, it is orthogonal to the reasoning about the structure of a per-field table.
@alexpott : if the comment here said :
"A dedicated table only contain rows for actual field values, and no rows for entities where the field is empty. Thus, we can safely enforce 'not null' on the columns for the field's required properties.",
would that adress your concerns from #136.2 ?
Comment #144
yched commentedHmmm - that new text contains a couple inaccuracies. We could work to adjust them, but I don't get why we bother adding that in this issue here, since, as mentioned in the previous comment, this is not the issue where we do anything with *field*-requiredness ?
Field-requiredness is going to matter for allowing 'not null' = TRUE in shared/base tables, and that's #2346019: Handle initial values when creating a new field storage definition.
Comment #145
yched commented@plach #142 : awesome, thanks !
I'd feel more confortable to have @fago validate that interdiff (the hasTarget() part), but that does adress my concerns in #138.3
Comment #146
alexpott@yched yes the suggestion in #143 sounds better. I'm just concerned that the meaning of requiredness is different and there is a lack of documentation explaining this.
Comment #147
plach@yched:
Can we just fix the new docs? They are describing the current status, we can always update them after adding support for required storage definitions. If you tell me what's wrong with them it should be a quick fix...
Comment #148
fagoThe second clause would be easier to parse if we apply the de-morgan rule once and make it:
if ($entity_id != $values['target_id'] && !($values['target_id'] == static::$NEW_ENTITY_MARKER && $this->entity->isNew())) {That generally loads the reference - imo the default should be to check the target identifier so it avoids loading it unncessarily.
Given the marker, there should be no need any more to load entities when an entity reference is set by id. However, this loads it. As discussed, I'd suggest adding a isNew() method to the EntityReference property class, which can assume an entity is not new if there is no object but only an id given.
Comment #149
plachNope,
::hasTarget()just checks a plain object property value, just to be sure there was no magic getters involved I debugged this with the following code:The last line fetches the user entity from the DB, hence it was not loaded while setting the value in the previous line, otherwise it would be in the static cache.
Checking whether the ID is defined does not help here, we need to know whether the target is actually loaded.
Nope, this code is written exactly with the goal of not loading the entity object. This is the place where the marker is set, hence we cannot rely on it, we just need to know whether the entity is new.
I don't agree: if in the
EntityReferenceobject we have only the id set we cannot automatically assume it's a new entity. That's precisely the use case tested in the code above. It's in the parent object that we can safely make this assumption because the entity property cannot be set to a new entity without providing the related object.Sorry, I misread the sentence above. I'm fine with the
::isNew()method, it can be implemented exactly as::hasTarget(), although it provides less information (not sure whether that's good or bad).Once the documentation issue is sorted out I'll provide a new patch.
Comment #150
yched commentedI'll let @plach & @fago disccus #148 / #149.
Meanwhile, here are some proposed doc adjustments for my remarks in #143 / #144.
Patch needs a reroll, so I just post them as an interdiff.
Comment #151
plachThis includes #150 (thanks) and should fix #148. I tried to implement
::isTargetNew()(I think the name is more consistent this way) as:but that is not enough, because we can populate the item property with an already saved entity. The current code is basically the same of the previous patch, just wrapped in the
::isTargetNew()method. Performance concerns should be still addressed as the entity will be accessed only if already populated, so no load operation is ever triggered.Comment #152
plachJust a reroll
@fago:
Any feedback on the interdiff posted in #151?
Comment #153
xjmComment #154
plachJust keeping this fresh...
Comment #155
effulgentsia commentedI re-reviewed the entire patch, and it looks great to me, but assigning to fago for RTBC to confirm his concerns in #148 have been addressed.
Some non-RTBC-blocking nits:
'not null' isn't in the ShapeRequired field's schema(). Should we change the comment to "Add a base field with a required property..."? Or were you actually wanting to test the situation of someone adding a 'not null' explicitly in the schema() method?
There can be other reasons for the exception to be thrown, so to make this test more robust, how about also doing an insert with the same data and the new_bundle_field_shape column and make sure that passes?
Additionally, should something like #149 be added as an automated test, so we know if some future patch creates an unnecessary entity load, since it seems like it's not so easy to tell just by looking at the code?
Comment #156
plachAddressed #155. This should prove that @fago's concerns were addressed too.
Comment #157
plachWrong patch...
Comment #159
yched commentedThanks @plach. A couple nitpicks on the last interdiff :
Nice - however, this tests Core stuff, right ?
Drupal\Core\Entity\Plugin\DataType\EntityReference and Drupal\Core\Field\Plugin\Field\FieldType\EntityReferenceItem ?
So it isn't really for modules/entity_reference/src/Tests ?
Could use a word of comment ?
OK, but could use a couple comments too ?
Could be more interesting to have a test storage that extends SqlStorage but logs actual loads (from storage / from cache) in an internal property that can be fetched in tests.
Arguably outside the scope of this patch here, though. Just sayin, feel free to ignore :-)
Comment #160
fagoThanks, plach. Looks like I mixed up the both entity reference classes - sry. However, having test coverage for that is best anyway. (Looks like this would have been a good fit for a phpunit test and mocking, but nevermind.)
That sentence was a bit a hard to parse, maybe rephrase to something like: "If only an ID is given, the reference cannot be a new entity."
Agreed this is ready, should we address the few nitpicks and move the test to core before commit?
Comment #161
plach@yched:
This should address #159 except for 4, which I shamelessly ignored :)
@fago:
Yep, initially I wanted to provide a PHP unit test, but then it turned out there is no such test for field items so this would be a bit inconsistent.
Comment #162
yched commentedYay :-)
Comment #163
alexpottThis issue addresses a critical bug and is allowed per https://www.drupal.org/core/beta-changes. Committed 057b0ca and pushed to 8.0.x. Thanks!
Comment #164
plachY A H O O O O !
Comment #166
jibranFinally. Thanks Entity API team for fixing it and @alexpott for committing it.
Comment #167
yched commentedAwesome ! @plach++++++
Comment #168
yched commentedNow we can do this : #2401221: FieldItem::isEmpty() should rely on $property->isRequired()
Comment #169
larowlanTwo follow ups from this
#2403793: EntityReferenceItem uses a static, but it was most likely supposed to be a constant - the static can't be accessed elsewhere and can be changed
and
#2403873: FileFormatterBase does not retain unsaved entities (files) - which I think was just missed
Comment #170
jibranFinally published the change record.
Comment #171
larowlanAnother follow up
#2404331: Can't serialise an entity with an entity reference to an unsaved entity (remove EntityReferenceItem::$NEW_MARKER in favour of a validation constraint)