Problem/Motivation
Followup from #2333113-45: Add an EntityDefinitionUpdateManager so that entity handlers can respond (e.g., by updating db schema) to code updates in a controlled way (e.g., from update.php).18.
Blocks #2278017: When a content entity type providing module is uninstalled, the entities are not fully deleted, leaving broken reference.
Beta phase evaluation
Issue priority | Critical because this blocks #2278017: When a content entity type providing module is uninstalled, the entities are not fully deleted, leaving broken reference, which is a critical upgrade path issue. |
---|---|
Disruption | Zero disruption. |
Proposed resolution
- Add
::hasData()
to the entity storage interface and create implementations for it in the NULL handler and the default SQL handler - Change
SqlContentEntityStorageSchema::requiresEntityDataMigration()
to use::hasData
to check if a migration is required.
Remaining tasks
Task | Novice task? | Contributor instructions | Complete? |
---|---|---|---|
Review patch to ensure that it fixes the issue, stays within scope, is properly documented, and follows coding standards | Instructions | ||
When this is fixed, unpostpone #2278017: When a content entity type providing module is uninstalled, the entities are not fully deleted, leaving broken reference |
User interface changes
None
API changes
Adding ::hasData
to the entity storage interface
Original report by @effulgentsia
Comment | File | Size | Author |
---|---|---|---|
#68 | 2335879-2.68.patch | 21 KB | alexpott |
#68 | 66-68-interdiff.txt | 1.17 KB | alexpott |
#66 | 2335879-2.66.patch | 21 KB | alexpott |
#66 | 64-66-interdiff.txt | 449 bytes | alexpott |
#64 | 2335879-2.64.patch | 21.43 KB | alexpott |
Comments
Comment #1
dixon_I ran into this bug while building Multiversion. Here's some provisional code that at least got it working. But a few things still needs work:
$this->isTableEmpty()
to check if it has data. Best thing is probably to addhasData()
to the storage hander, as suggested in #2333113-45: Add an EntityDefinitionUpdateManager so that entity handlers can respond (e.g., by updating db schema) to code updates in a controlled way (e.g., from update.php)Comment #2
dixon_I would actually call this a bug. Because right now it's simply not possible to swap storage controller for an entity type.
Comment #3
dixon_Added
hasData()
to the storage handler.Comment #5
dixon_This should do it. We can't obviously ask the new storage handler before it's created...
Comment #6
mauzeh CreditAttribution: mauzeh commentedFieldableEntityStorageInterface was refactored into DynamicallyFieldableEntityStorageInterface. I will do a reroll.
Comment #7
mauzeh CreditAttribution: mauzeh commentedRerolled the patch.
Comment #8
mauzeh CreditAttribution: mauzeh commentedWhoops patch file was empty... Here it is again!
Comment #9
ygerasimov CreditAttribution: ygerasimov commentedPatch work good. I believe only missing part is having test.
Comment #10
ygerasimov CreditAttribution: ygerasimov commentedPatch needs comments.
Comment #11
jeqqAdded comments.
Comment #12
gumanist CreditAttribution: gumanist commentedLooks good and working fine.
Comment #13
plachThis will break if the old class is no longer available in the system. We should assume TRUE in this case as there is no way to access existing data.
Comment #14
ygerasimov CreditAttribution: ygerasimov commented@plach thank you for the review. Here is updated patch.
Comment #15
dixon_Re-rolled patch and added coverage comment to the test that seems to have got lost.
Also bumping the priority of this issue since without this patch it's impossible to switch storage controller on an already installed site (even if there's no content for the particular entity type you're trying to change for). The only way to walk around this is to create a installation profile that includes your handler from scratch. A major DX fail in my opinion. We can do better than that :)
Comment #16
dixon_This bug is blocking a stable release of the Multiversion contrib module.
Comment #17
dixon_Comment #18
dixon_Patch does not need tests anymore. Removing tag.
Comment #19
plachI'm really unsure about this, but I cannot think about a better solution, so passing the ball to a committer. Maybe they will have a suggestion. Moving back to RTBC.
Comment #20
alexpottI think we should be unit testing the conditions in
public function requiresEntityDataMigration()
. It does not look like we have test coverage here.The patch attached delegates responsibility for creating handler instances to the EntityManager.
Whilst reviewing this patch I found myself wondering if we should have an abstract base class for
SqlContentEntityStorageSchema
since it looks unlikely that a non SQL version of this class would need to have a different method forrequiresEntityDataMigration()
orrequiresFieldDataMigration()
Comment #21
plachThis looks way better to me, if we can get a new patch I am happy to RTBC it again once Alex's request for additional test coverage is addressed.
+1 on the abstract base class for the storage schema.
Comment #22
alexpottLet's do this in another issue - not necessary for this one :)
And forgot to upload a patch :)
Comment #23
yched CreditAttribution: yched commentedWell, a ContentEntityStorageSchema is an SQL-only concept anyway ?
(which I guess means +1 on the abstract class ?)
Comment #24
Wim LeersAs of #2278017-79: When a content entity type providing module is uninstalled, the entities are not fully deleted, leaving broken reference, this is now a blocker for that issue. Since that's a critical, bumping this to critical too.
Added beta evaluation.
Tests were added in #22.
That brings it down to nitpicks:
s/this/that/, on a first read I thought the 'this' was referring to *this* method, but it's referring to the *other* method, hence 'that' would be better.
mixed
typehint?Missing trailing period.
80 col rule.
$select_count_query
would be more consistent with the other variable names.Comment #25
Wim LeersThis was RTBC at #19. It was only un-RTBC'd for test coverage. The test coverage was added in #22. I reviewed the test coverage, and only found nitpicks. Fixing them myself to accelerate this issue, and hence unblock #2278017: When a content entity type providing module is uninstalled, the entities are not fully deleted, leaving broken reference.
The only remaining remark is that
hasData()
could be implemented in an abstract base class, but that's out of scope here and wouldn't be an API change or addition.Hence: back to RTBC!
Comment #26
alexpottActually tests weren't added in #22... we need to add test coverage of the
requiresEntityDataMigration
method.Comment #27
alexpottThe interdiff for #22 was in #20... just keeping everyone on their toes :)
Comment #28
Wim LeersOh :( Pre-existing missing test coverage, blargh!
Sorry!
Comment #29
YesCT CreditAttribution: YesCT commentedadding blocker tag, since this blocks #2278017: When a content entity type providing module is uninstalled, the entities are not fully deleted, leaving broken reference, so that the d8 blockers is accurate. Remove the blocker tag when this issue is fixed, and unpostpone 2278017.
added the issue summary template to the summary, but it still needs someone familiar with the issue to update the remaining tasks. And put an actual problem description and proposed resolution in the summary.
Comment #30
Wim LeersIn figuring out how to write those tests, looked at #2333113: Add an EntityDefinitionUpdateManager so that entity handlers can respond (e.g., by updating db schema) to code updates in a controlled way (e.g., from update.php), where
requiresEntityDataMigration()
was introduced. At the time, it apparently wasn't possible to write tests until some other issues had landed.This is completely over my head, but I tried to push it forward.
Comment #31
Wim LeersOops!
Well, no matter, because this will likely need a reroll anyway to improve the test coverage I wrote.
Comment #32
effulgentsia CreditAttribution: effulgentsia commentedThanks for starting on the test. For more background to #30, #2333113: Add an EntityDefinitionUpdateManager so that entity handlers can respond (e.g., by updating db schema) to code updates in a controlled way (e.g., from update.php) introduced
requiresEntityDataMigration()
and added an integration test (EntityDefinitionUpdateTest
) for what uses it (SqlContentEntityStorageSchema::onEntityTypeUpdate()
), but did not add a dedicated unit test for the new method itself. That was just an oversight. Glad that's being addressed here.The test mostly looks good, but what's confusing about it is that it's dealing with a single mocked
$this->storage
object that in the case of the storage classes being different is still serving as the mock of each one. Therefore, we're not testing that hasData() is being invoked on the correct one (i.e., the one for the original class if it's different). So I think we want to expand cases 4 and 5 into 2 versions each (i.e., change the hasData parameter into 2 parameters: one for the new storage and one for the old).However, unless someone fixes that quickly, I wonder if it makes sense to commit either #25 or #30 without waiting on that, in order to unblock #2278017: When a content entity type providing module is uninstalled, the entities are not fully deleted, leaving broken reference, and then continue the test refinements in parallel.
Comment #33
dixon_Here's a re-rolled patch fixing all issues raised in #31 and #32.
I also updated issue summary to reflect the current state and added a draft change record since we're making additions to the entity storage interface: https://www.drupal.org/node/2387757
Comment #34
dixon_Comment #35
yched CreditAttribution: yched commentedMaybe the phpdoc for EMI:: createHandlerInstance() should point that getHandler() is the preferred way to get the "official" handler for an entity type ?
Comment #36
Wim Leers#33: That all looks splendid!
#35: That's already there:
AFAICT this is now RTBC-worthy :)
Comment #37
yched CreditAttribution: yched commentedGah, sorry, patch reviewing on phone, don't mind me :-/
Comment #38
Wim LeersAucun problème! :)
P.S.: If you find a good website or native app for reading/reviewing patches on phones/tablets, let me know. I tweeted about that a few months ago, no good suggestions. I guess we're just weird like that…
Comment #39
catch->range(0,1) would be better with large data sets.
Comment #40
alexpottSure.
Comment #41
alexpottDiscovered #2388765: Improve performance of SqlContentEntityStorage::countFieldData() for large datasets when getting the result as a boolean whilst doing #40
Comment #43
alexpottFixed the unit test.
Comment #44
chx CreditAttribution: chx commentedWhy don't we add an entity query to ContentEntityStorageBase ?
Comment #45
alexpott@chx good point. Easily fixed.
Comment #46
chx CreditAttribution: chx commentedOh we have
getQuery
? That's handy.Comment #48
alexpottSo we should always be using the original storage definition to work out if we have data if we can.
Comment #49
chx CreditAttribution: chx commentedIs the first hunk intended? We discussed it but is it necessary? I somewhat doubt.
Comment #50
alexpottYep it is 100% necessary - we need to use the original definition or we get the error seen in #45
Comment #51
yched CreditAttribution: yched commentedComments nitpick on the last interdiff :
If something non-trivial is happening here (cf discussion #49 / #50), does it warrant a comment ?
Same here, we lose some comment ? Isn't this what this all issue here is about ?
Comment #52
alexpott@yched sure no problem.
Comment #53
alexpott@chx gave some feedback in IRC.
Comment #54
chx CreditAttribution: chx commentedWhoever is coding the removal this change here makes it easier to understand what's going on.
Comment #56
Wim LeersSo #40 + #43 was to change from a COUNT query to a (0, 1) range query. This is fine because we don't care how many entities there are, only if there are any entities at all.
#45 only moves the existing code to a different class (a base class).
#48 is the only significant change. Instead of either checking the original storage or the new storage, we now only check the original storage. Thinking about this, it makes sense to me, because how could the new storage class already contain any data, if what we're doing is updating the entity type definition (i.e. schema or storage class changes)? But I wonder how we missed this so far, and why we didn't notice it sooner?
I don't understand what's going on in #53/#54, but perhaps that's because I'm tired.
This test is now effectively dead, because
$this->entityType
is a mock object, not the string'entity_test'
. This should have been$this->entityType->id()
. Yet the test still passes.(Verified by debugging locally.)
NW for this.
This sounds a bit like dark magic to me, but perhaps that's because I don't know this well enough.
Comment #57
alexpottre #56
Comment #58
dawehner... Mh, so it should not be overridden, but why? For example the webprofiler module extends a hell lot of services you might never have thought to be overridden.
-1e5 ... that makes mocking in unit tests impossible.
HAHA, even this patch will fail, because of the final change.
Comment #59
alexpottre #58
Comment #60
alexpottCorrect interdiff for #59
Comment #61
effulgentsia CreditAttribution: effulgentsia commentedThe pre-#48 patches checked the new storage only if it was the same class as the original storage. So under most circumstances that's fine. However, it is possible that something else about the entity type's storage is being changed without changing the class, so I agree that the post-#48 patches are better in that regard. And the assertion that the new storage's hasData() isn't called is sufficient test coverage of that, IMO.
Comment #63
BerdirI did not read any of the existing discussion, only have some documentation suggestions/nitpicks, ignore if it was already discussed. Implementation looks good to me.
Nitpick, but afaik we always need a description for @return except when it is $this. But I could be wrong.
Wondering if we can use the new method also in #2350503: Add route generation handlers for entities ?
Would @return object be better here?
Hm. I don't think we care if it is overridden or not, as long as the implementation doesn't change?
As @dawehner mentioned, use cases like wrapping the original call to do statistics or similar things would be perfectly valid?
So why not just document something like "Any implementation of this service must call getQuery() of the corresponding entity storage." ?
Not sure if there is a reason that those comments are appended like this, doesn't really follow the coding standard :)
Comment #64
alexpottgetQueryServiceName
which makes it much harder to create an implementation of QueryFactory that does not call the getQuery / getAggregateQuery on the entity storagesComment #65
BerdirGreat, RTBC if green I'd say.
The only thing you didn't update and I'm not sure if/how is the comment in core.services.yml...
Comment #66
alexpott@Berdir right you are
Comment #67
tstoecklerThis should be getQueryServiceName().
Comment #68
alexpott@tstoeckler - right you are.
Comment #69
catchLooked through this a couple of times and couldn't find anything to complain about. Committed/pushed to 8.0.x, thanks!