Problem/Motivation
If a config entity is being renamed through \Drupal\Core\Config\ConfigFactoryInterface::rename() then it becomes impossible to load the entity by its uuid through \Drupal\Core\Entity\EntityRepositoryInterface::loadEntityByUuid().
Proposed resolution
When config is saved or deleted we listen to the corresponding events in \Drupal\Core\Config\Entity\Query\QueryFactory and update the config key store for fast lookups so that a config entity could be loaded by its lookup keys.
In order to cover renaming config entities through the config factory we should additionally listen to config renaming events and update the config key store for fast lookups accordingly. Additionally we should remove delete the old values from the config key store for fast lookups.
Remaining tasks
Review
Commit
User interface changes
API changes
Data model changes
| Comment | File | Size | Author |
|---|---|---|---|
| #82 | reroll_diff_77-82.txt | 1.28 KB | tanuj. |
| #82 | 2960643-82.patch | 9.77 KB | tanuj. |
| #77 | interdiff_69-77.txt | 2.3 KB | sourabhjain |
| #77 | 2960643-77.patch | 9.74 KB | sourabhjain |
| #71 | 2960643-71.patch | 8.85 KB | amanshukla6158 |
Comments
Comment #2
hchonovComment #3
hchonovTest + fix. I am not sure if we need an update path to update the config key store for all config entities, as if there is a system where a config entity has been renamed then that entity will not be loadable by its entity UUID.
Comment #4
hchonovAnd here is the update for re-saving all entities that cannot be loaded by uuid.
Comment #5
hchonovComment #6
amateescu commentedWe should use the new
config.entity_updaterservice instead of manually re-saving all config entities :) See https://www.drupal.org/node/2949630Comment #7
hchonovI wasn't sure if that service could be used for more entity types at once. Lets give it a try :).
Do we need an update test?
Oups I've used the wrong comment number for the patch .. sorry about this.
Comment #8
hchonovOh wait the CR contains the wrong guide. There is no service for the config entity updater like stated in the CR, but the class resolver should be used instead.
Comment #9
hchonovComment #11
hchonovAfter looking further into the issue I've found out, that it is not enough to simply re-save the entities, as by doing only this the new name will be added to the values in the corresponding record in the key value store for fast lookups. This solves the problem, but the previous name remains there as well, which should be removed. We could automate this for records containing only one entry as then we know that this is the entry written by us and contains only the previous name. If the record however contains multiple entires, then we should not automatically just remove it, but instead inform that manual work is needed.
Comment #12
tstoecklerWow, absolutely crazy find and really nice fix. The new code fits really neatly in with the existing code - very unfortunate that we forgot to put it there in the first place.
Some notes:
So if we only clean-up the UUID ones I think we can simplify the update path and simpy query the key-value store directly for items in the
"config.entity.key_store.$entity_type_id"collection with the name/key"uuid:$entity_uuid".I am wondering, though, why we do are not updating the other keys, as well? All that is stored is the ID, so regardless of the actual keys, we know what we have to update, right?
Having written that, it seems to me that - even if we do update all possible keys - we should be able to perform the update without using reflection, by first building up a map of renames using the
loadEntityByUuid()check just like you are doing now and then in a second step iterating over all values in"config.entity.key_store.$entity_type_id"key-value collection and replacing any IDs that match our rename map.What do you think?
I think this test could be added to
::testLookupKeys()instead, no? We already test the save and delete event there, so I think that would make sense.Comment #13
tstoeckleruuid -> UUID
Also, instead of having the longer description in the docblock, let's remove it there and add it as in inline comment in the function. The docblock will be parsed and displayed when running the updates via
update.phpand updates with multi-line descriptions always look pretty weird there.Comment #14
hchonovSure, we could do that. I just felt more comfortable using the code that we already have.
You mean if the config entity has additional look-up keys beside the uuid key? I think you are right and we should cover them as well. Good catch!
How do you build a rename map with only one known value? The original ID is already lost and there is no track of it.
Yes, this makes more sense.
Comment #16
tstoecklerNeeds a re-roll for System post-updates added in the meantime.
Comment #17
anya_m commentedReroll for #11 patch for 8.7.x
Comment #18
tstoecklerOK, this needed an update for #2624770: Use more specific entity.manager services in core.services.yml .
Comment #20
tstoecklerAhh sorry, I accidentally included #3005137: Register the node/add path as link template "add-page" in #18. Reverted that and also fixed the coding style violations.
Comment #23
geaseSome updates to the post_update hook implementation. As is noticed in #14, what stands for uuid, stands for other keys as well (in core, I can see just uuid and theme). So I removed uuid condition. Entity updater should care to re-save unloadable entities, and it saves those for which updater callback returns TRUE, so we need to negate $loadable.
Comment #25
geaseExtended test with checks that renaming config object through Entity API works correctly (including removing obsolete entries from key-value store) and added upgrade path test reproducing renaming config object both through Entity API and Config API.
The test failure in #23 still needs to be taken care of.
Comment #26
geaseErroneously uploaded previous patch before.
Comment #27
berdirYou can still add a type hint while keeping it optional. Not sure if not passing it should be deprecated so that we can eventually make it required (has to be in D10 now)
Because this doesn't document a NULL return value, so it is kind of expected to be there, and we can't provide BC for it.
Maybe we should just require it? The classname is hardcoded, this isn't a service, there is no reason for it to be subclassed.
not sure if the second paragraph is needed and afaik both drush and the UI don't display that properly.
I don't think this foreach loop works like you think it should.
The trick with the config entity update and $sandbox is that is going to do updates in batch and is going to be called many times. But your loop is going to start from the start every time.
We likely can't use that service here but need to duplicate that logic, it does too much on its own.
I think it is likely much easier to implement if you instead loop over *all* config and don't use entity API here.
That means we need to use $config_factory->listAll() and then probably use array_chunk to split them into chunks of e.g. 100, put that in sandbox (unfortunately makes it quite big as there can be thousands and then each call needs to process one chunk, remove it and return. And then #finished = 1.
And for each config, you use similar logic as the rename event implementation and interact with the key value service directly.
Comment #28
hchonovRe #27.1-3:
Agree with everything.
Re #27.4:
Hm, to be honest I am confused :).
Please take a look at
text_post_update_add_required_summary_flag():it has at the end
Which is basically also like a foreach isn't it?
Comment #29
berdirYeah, I'm not 100% sure, I see that there is a $sandbox_key and it's done inside of that, but the global #finished is then set based on the current key. So honestly, I don't know what is going to happen exactly? Probably the last call wins, so it would process $batch_size of each entity type, and then if the last one has less than $batch_size, it would be done. And if not, then would continue until the last one is done.
So, it *kinda* works I suppose as long has the last entity type has the highest entity count? which is obviously not something that you can rely on.
Comment #30
hchonovYes, the last one will always win.
So we just found a bug when the config entity updater is being used for multiple entity types. In this case the #finished should be computed based on all sandbox keys. This also means that
text_post_update_add_required_summary_flag()will not update allentity_form_displayentities if there are lessfield_configentities.Comment #31
berdir> In this case the #finished should be computed based on all sandbox keys
Which is easier said than done because update() has no knowledge about which keys even exist and it doesn't know in advance how many that there are.
Maybe we could add a key inside that's basically a
config_entity_updater_was_hereflag, then it can loop over all sandbox keys with that key, and then each run stores its own progress and calculates the total progress of all that did run so far. As it also doesn't know if it's the last. Plus a check that just skips if the current sandbox_key is already complete (except updating total). Still quite a bit of overhead and complexity.Which is why I think that a custom batch loop that just loops over all config directly would be easier here.
Comment #32
hchonovIt can be a lot easier - just put the sandbox keys into a dedicated section :).
So instead of
$sandbox[$sandbox_key]use$sandbox['sandbox_keys'][$sandbox_key]. Then we know that we simply have to iterate through everything that is inside$sandbox['sandbox_keys'].I think that we would need a dedicated issue for that, where we will have to rerun the updates updating multiple config entity types.
Comment #33
hchonovI've created an issue for this - #3092714: Config entity updater misbehaves when updating multiple entity types. Let's continue the discussion about this over there and focus on #27.1-3 here.
Comment #34
berdirGoing to have a look at this.
Comment #35
berdirSo, here's my proposal for an update function that is much simpler and IMHO sufficient here as in at least as good as the other, faster and doesn't have any problematic assumptions:
* The entity type fail is I think because that entity type hasn't been installed yet, we can't rely on that being the case. My approach doesn't have this problem because the only thing we need from a config is its entity type id, that's always going to be available if there's config for it.
* I honestly didn't fully understand the logic around having one or multiple entries and that warning log message. One one side, we IMHO do not need to support any third party messing with the data, this is private. So UUID must always be exactly one key. However, at the same time, it is perfectly valid for other lookup keys to have many values, one example is blocks with the theme lookup key.
So my update function relies on the expectation that UUID must be exactly one config name, and if it doesn't match that, we delete that and resave it. That's enough to fix both conditions in the test.
What it would *not* clean up is exactly that example with block themes, so doing an entity query on a block theme would still return stale keys, but that's less of an issue as they are expected to be passed to loadMultiple() which would then just ignore non-existing keys. Might be a small overhead, but the cached config storage explicitly supports caching non-existing lookups as well, so that's very minimal.
If we'd decide to have to fix that too, then we should probably switch the the test from image styles to blocks.
There's two things that I'm not quite sure about in the update test:
* Many comments explain what "we" do. I think that's not really how we comment things, should be more neutral and just explains how things are. Too tired to try and rewrite that.
* We're using the entity API before running updates. It's "just" config entities, so less tricky, but still, that's not really supported We've had problems with that before, if we'd add some kind of new lookup/check relying on things that aren't there yet... We're only testing our own mock data, so I'm not sure how important is.
Comment #37
berdirComment #38
geaseUpdated database dump from 8.4 to 8.8.
Comment #39
hchonovLet's keep only one empty line and also document this. Maybe something like:
Add an UUID lookup record containing the previous and the new name of a config entity, which simulates what used to happen prior to Drupal 8.9.0 after renaming a config entity through the config factory.Let's describe what we are testing and add a see to the update method.
not needed empty line.
Not needed empty line.
Refactor comment to use the empty space.
don't need the "we".
"here we" is also unnecessary.
...entity
canbe loaded..Comment #40
geaseAdded test checks for correctness of value in lookup table and extensively reworked comments to make clear there are 2 separate use cases which are tested on separate entities and fixtures.
Comment #41
geaseFurther updated fixtures and comments for the sake of clarity and consistency.
Comment #43
johnwebdev commentedRerolled.
Comment #44
johnwebdev commentedComment #45
larowlanDo we need to allow this to be null for BC (And trigger a deprecation if it is not provided)?
It is possible that someone in contrib/custom code is firing their own config events right?
If so we'd also need a deprecation test
Comment #46
berdirSee #27 for why I think BC for this is pointless. The event must provide the old config or we can't fix the bug. Only mentions of this class are on the event subscribers: http://grep.xnddx.ru/search?text=ConfigRenameEvent
Comment #47
larowlanFair enough - thanks
Comment #49
quietone commentedNeeds reroll and looks suitable for a novice.
Comment #50
anushrikumari commentedComment #51
anushrikumari commentedRerolled patch for 9.2.x
Comment #52
anushrikumari commentedComment #54
nikitagupta commentedComment #55
nikitagupta commentedComment #56
kapilv commentedComment #57
amateescu commentedThe entity API uses the term "original" when referring to the object that holds the previous values. Is there any reason to go with "old" here?
I see we use "old name" when referring to the previous name, but I'm not sure being consistent with that is worth it in the long run..
Comment #58
renatog commentedOn this case we're using $this_model
$old_config = $this->get($old_name);And here we're using $thisModel
protected $oldConfig;Both are correct but we can use one or another in the same file to be consistent
More information here:
https://www.drupal.org/docs/develop/standards/coding-standards#naming
Comment #59
megha_kundar commentedConverted $old_config to $oldConfig as per coding standards.
Comment #60
berdir> Both are correct but we can use one or another in the same file to be consistent
Actually, the not-mixing part refers to local variables only. properties must camelCase. local variables can be either but should be consistent across the whole file. And since there are existing variables that use snake_case, #58/#59 isn't correct.
#57: Hm. the existence of $old_name is exactly why old_config made sense to me, but I have no strong feelings about that. Interestingly, https://api.drupal.org/api/drupal/core%21lib%21Drupal%21Core%21Config%21... already does exist, that does make we wonder if we could rely on that instead, then we wouldn't even need the extra argument?
Comment #64
ranjith_kumar_k_u commentedRerolled #59 for 9.5
Comment #66
gaurav-mathur commentedPatch #64 applied successfully on drupal version 9.5.x and working fine but this patch does not applied on drupal version 10.1.x please reroll the patch for drupal 10.1.x
Thank you
Comment #67
_utsavsharma commentedRerolled for 10.1.x.
But i could not understand how to make changes in the file (core/modules/system/system.post_update.php).
Please review.
Comment #68
catchThis needs the post update adding back from #64. You should be able to just copy the code over from the patch as a last resort given it's all new.
Comment #69
_pratik_Add changes in system.post_update.php also.
thanks
Comment #70
renatog commentedWhat do you think if we do the opposite using early-return?
Ex:
It'll reduce one level of indentation, you know?
Comment #71
amanshukla6158 commentedmade changes as per #70
Comment #72
mstrelan commented#71 needs work for phpcs errors
Comment #73
akram khanadded updated patch fixed CCF #71
Comment #74
_pratik_Comment #75
smustgrave commented#71 seems to be removing the code of the post_update hook
#73 seems to be adding additional changes from #71
#74 seems to be the same as #73 and adding additional changes
So patch #69 should be starting point but #70 needs to be addressed.
Comment #76
sourabhjainLet me work on #75.
Comment #77
sourabhjainI have tried to fixed the issue mentioned in #75. Please review.
Comment #78
smustgrave commented#77 does seem to address #70.
Comment #80
sahil.goyal commentedComment #81
larowlanNo longer applies
Comment #82
tanuj. commentedas patch #77 does not applies
adding a reroll for #77, please review.
Comment #83
tanuj. commentedComment #84
smustgrave commentedReroll seems good
Comment #85
larowlanI don't see any exploration of the ::getOriginal method highlighted in #60, we've just been re-rolling the previous patch without seeing if that means we don't even need the new arguments.
Can we explore that first please? If it works the patch will be dramatically simplified.
Comment #88
ekes commented