Support for Drupal 7 is ending on 5 January 2025—it’s time to migrate to Drupal 10! Learn about the many benefits of Drupal 10 and find migration tools in our resource center.
Problem/Motivation
SchemaTest::testSchema fails if a driver implements Sqlite support but is not the Drupal core driver, 'cause
case 'sqlite':
// For SQLite we need access to the protected
// \Drupal\Core\Database\Driver\sqlite\Schema::introspectSchema() method
// because we have no other way of getting the table prefixes needed for
// running a straight PRAGMA query.
$schema_object = Database::getConnection()->schema();
$reflection = new \ReflectionMethod($schema_object, 'introspectSchema');
$reflection->setAccessible(TRUE);
assumes an introspectSchema method exists, but it may not be the case.
Also, per parent issue #2877583: [Meta] Remove database specific logic from core it is desirable not to have database specific conditionals in core code.
Proposed resolution
Add a Schema::findPrimaryKeyColumns in the abstract class and its driver implementations, and call that method from the relevant test.
Remaining tasks
Review.
User interface changes
None
API changes
An additional abstract method in the abstract Schema class + driver level implementations.
Data model changes
None
Comment | File | Size | Author |
---|---|---|---|
#57 | 2881522-57.patch | 17.16 KB | tstoeckler |
#57 | 2881522-41-57-interdiff.txt | 11.5 KB | tstoeckler |
Comments
Comment #2
mondrakePatch.
Comment #4
daffie CreditAttribution: daffie commentedI like the idea for this issue. We should do the same with unique and non-unique indexes.
The patch looks good to me.
I am missing testing for the table does not exists and testing for a table without a primary key.
Comment #6
tstoecklerPatch looks great! @mondrake can you add the tests requested by #4, then I could RTBC ;-)
I think if we want to do the same for unique keys it should be a new issue.
Comment #7
mondrakeI'll work on that. I also suggest to use a different method name e.g.
findPrimaryKeyColumns
instead ofgetPrimaryKeyColumns
to reflect the fact that this is introspecting actual status from the db - a similar method to introspect the existing tables is namedSchema::findTables
.Comment #8
mondrakeRenamed the method and added tests.
Comment #9
tstoecklerAwesome, looks great to me! Thanks.
Comment #10
mondrakeI thought this may need a CR so I drafted one: https://www.drupal.org/node/2974956
Comment #11
amateescu CreditAttribution: amateescu for Pfizer, Inc. commentedThe patch looks good to me as well, I just found two tiny nitpcks :)
Missing the 'string' type hint.
We could change this to
@covers \Drupal\Core\Database\Schema::findPrimaryKeyColumns
.Comment #12
mondrake#11:
1. Correct, but out of scope of this patch. Actually most of the methods are missing the typehint in this test class.
2. Done, albeit a bit differently.
Comment #13
daffie CreditAttribution: daffie commentedCould we add testing with more complicated scenario's. Like:
The patch looks good to me. Great work!
Comment #14
tstoecklerRe #13:
Let's leave this out for now as this is currently broken on MariaDB and also inconsistent between MySQL/SQLite on the on hand and PostgreSQL on the other hand. See #2974722: Fix dropping of columns in a composite primary key on MariaDB >=10.2.8 and make it consistent across database drivers and the latest comments in #2938951: The primary key is not correctly re-created when updating the storage definition of an identifier field for more information. I think we should expand the former issue to be "Fix dropping of columns in a composite primary key on MariaDB and make it consistent across database drivers" and then that one can include that coverage (and be blocked on this one).
Comment #15
mondrakeI agree, let's have #13 in follow ups, this issue is already kind of blocker for the ones mentioned in #14.
Actually #13.1 is already covered in the testSchema method.
Comment #16
mondrakeOK, actually added some more tests for #13.1 and #13.2; #13.3 let's leave for follow up as suggested by @tstoeckler.
Comment #17
amateescu CreditAttribution: amateescu for Pfizer, Inc. commentedRe #12:
That's not quite true, it's very much in the scope of this patch :) Since it is adding new code, it should conform to the coding standards regardless of the surrounding code.
Comment #18
mondrake#17 you're right, sorry I was looking at the wrong place...
Comment #19
mondrakeIt looks like additional tests are now spotting an issue in SQLite and Postgres... :(
If the sequence of columns composing the PK is different than the flow of columns on the table left-to-right, the queries do not take that into account.
I can investigate the SQLite one, but have no clue on pgsql... anybody can help?
Comment #20
mondrakeThis should fix the SQLite one.
Comment #21
tstoecklerHehe, I kind of noticed that already in #2881522: Add a Schema::findPrimaryKeyColumns method to remove database specific logic from test but didn't realize I did. I just added a bunch of
sort()
s in the respective assertions and was done with it. You are right, though, that we should fix this properly ;-) Thanks! And thanks @daffie for insisting on the added tests! Will try to look at Postgres one later (unless someone beats me to it)Comment #22
daffie CreditAttribution: daffie commented@tstoeckler: The honour should go to @wimleers. He is the one with his api-first initiative that we should not only test the "easy path". So we should do the same here. :)
Comment #23
mondrakeComment #24
tstoecklerAlright, this should pass in all three database engines now.
For SQLite, #20 was pretty much on the right track, except you fixed the issue for normal indexes not for the primary key. So I adapted your approach for the primary key and it worked. I had to use an explicit
ksort()
in PHP there, as we cannot sort in SQL directly because we do not want to alter the order of the fields and the primary key information is attached to them directly. I also reverted the interdiff from #20. I think it's correct to retrieve the indexes in the specified order, but if we make that change here we should also add test coverage so I'd rather leave that for a separate issue.For PostgreSQL, the only place I could find where the order of the primary key is stored is in the
i.indkey
array column in the order of the array itself. Which thearray_position()
function we can then extract the correct key for sorting. Since we needed that anyway, I altered the query a bit so that the return value is keyed by the position directly so that we don't need the explicitarray_keys()
in PHP. I removed thedata_type
field, but we weren't using that anyway, that was just copied from https://wiki.postgresql.org/wiki/Retrieve_primary_key_columns I guess (but due to the ordering we diverge from that query anyway...)I just noticed that this is marked against 8.6.x, but this now blocks a number of bugs, so is there a chance we can backport this to 8.5.x?
Comment #25
mondrakeExcellent @tstoeckler, very clean - I was getting into complicated stuff but your solution is simple and elegant. Also fully agree on reverting #20 here, it was just my mistake. RTBC++ if it comes back green, but I cannot set it myself.
Comment #26
mondrakeCustom/contrib db drivers would fail in that case because of the new abstract method in Schema that has to be implemented - so I think this only for a minor.
Comment #27
mondrakeStill issues with Postgres
Comment #28
tstoecklerApparently
array_position()
is only PostgreSQL >=9.5 :-(So I went ahead and re-implemented the ordering in PHP. Since the order is only available (as far as I can tell) in array form, which gets into PHP as a concatenated array, we have to do some annoying string futzing for that. Please feel free to suggest a succincter or more readable version if you can come up with one ;-)
Comment #29
daffie CreditAttribution: daffie commentedIn the drivers for PostgreSQL and SQLite are sorting methods added. I have added a test with a more complicated primary key to see if the one test with a multiple primary key is not a lucky pass. I also added some comments to the tests.
Comment #30
daffie CreditAttribution: daffie commentedUsed the same name twice for creating a table in the test.
Comment #31
tstoecklerThanks @daffie! The test also looks more readable now. Assuming the tests come back green, maybe we can get an RTBC here? ;-)
Comment #33
daffie CreditAttribution: daffie commentedTaking this as given my tests addition as good enough for RTBC.
The patch looks good.
There is enough testing for the problem.
It is RTBC for me.
There is a follow-up: #2974722: Fix dropping of columns in a composite primary key on MariaDB >=10.2.8 and make it consistent across database drivers.
Comment #34
alexpott$row->pk - 1
needs a comment. Since this looks pretty odd. I think we need to document that$row->pk
contains a number that reflects the primary key order. The number starts from 1 (i guess) so we subtract 1 to make it a zero indexed array. Given that we're doinglater, I think we should do what we do for postgres and do
And then we don't have to do -1 subtracting.
It feels awkward to be adding API for tests-only but having #2974722: Fix dropping of columns in a composite primary key on MariaDB >=10.2.8 and make it consistent across database drivers following is good because that will introduce a usage in non-test code.
Comment #35
tstoecklerYup, that's exactly right. Will fix according to your suggestion.
Comment #36
tstoecklerHere we go.
Comment #37
amateescu CreditAttribution: amateescu for Pfizer, Inc. commented@alexpott's review has been addressed, back to RBTC :)
Comment #38
daffie CreditAttribution: daffie commentedHe, I wanted to do that! ;-)
+1 for RTBC.
Comment #39
alexpottSorry, I've had another look at the patch and I have a BC concern.
This is going to break contributed database drivers. I think we need to think about this a bit more carefully for BC purposes.
Comment #40
mondrakeOne possibility on top of my head would be to change this abstract into a base method that throws an exception - then core drivers would all override it with their implementations. At that stage it would be OK also for contrib drivers that may or may not implement the override, nothing will fatal out because of the missing implementation. That's a common pattern e.g. in Doctrine DBAL.
The problem will arise when this method will start to be used in non-test core code - calls to the method should be enveloped in try/catch blocks and logic be developed to manage the case of missing implementation.
Comment #41
mondrake#40, as a patch.
Comment #42
mondrake#41: SQLite is currently failing in HEAD, too.
Comment #43
daffie CreditAttribution: daffie commentedI think this is the best solution to minimise the BC break. Back to RTBC.
Comment #44
mondrakeI do not know of ways to test the change in #41 in DrupalCI. But if anyone interested, here's the test run on TravisCI for a non-core db driver in the scenario where
findPrimaryKeysColumn
override implementation is missing. If you click on any of the tests with a red X, you will see the RuntimeException being thrown. https://travis-ci.org/mondrake/drudbal/builds/386647348Comment #45
daffie CreditAttribution: daffie commentedYou could write a Unittest that mocks Connection::driver() to a not by Drupal core supported driver. Calling Schema::findPrimaryKeyColumns() should then result in your RuntimeException being thrown.
Comment #47
alexpottDiscussed with @catch in #contribute. We agreed that throwing the exception is the right way to go but...
We need to document that all non-test usages of this method MUST catch this exception and do the most sensible thing if this not available including continuing with old buggy behaviour because in Drupal 8 we can not guarantee this functionality is available.
Also in reviewing the code we can do the FALSE return when the table does not exist in this implementation.
Comment #48
tstoecklerCan you elaborate on this? Even though practically speaking the current lack of this functionality is really an oversight, technically speaking we are adding new functionality here. The chain of issues makes very clear that there really is no way around adding this, but still there will be no current code that is using this function. So I'm not sure what you mean by "continuing with old buggy behavior"? While wrapping each usage of this method in a try-catch is anything from elegant, I'm not actually talking about elegance here. I just really don't understand what to do in such a case. At the end of the day this would mean that even with this patch we are not providing a proper API to fetch the primary key because you still need to somehow deal with the case where you don't know what the primary key is.
That is fine with me, I think in actual real life use-cases - similar to the code in Entity API in the related issues - what you actually want to do is make sure the primary key has a certain state, so instead of asking for the current primary key, it's easy enough to just recreate it to the desired state. I just thought up to this point that the scope of the issue was a different one and that we were actually adding an API function here.
But I guess that means we should mark the function as
@internal
?Nice catch, good idea!
Comment #49
alexpott@tstoeckler if the calling code does not catch the exception and do something sensible - which might be continuing with the buggy present behaviour - then this is a breaking change. Code that didn't halt execution now will and given this exception will occur during low level entity schema operations that might be even worse than the current behaviour.
Comment #50
tstoecklerSorry, I'm still not getting it.
What behavior specifically? As I stated above the method cannot be called right now, so I'm not sure what you mean by that.
Comment #51
alexpottSo when #2974722: Fix dropping of columns in a composite primary key on MariaDB >=10.2.8 and make it consistent across database drivers and #2976493: Creating a table with an explicitly empty primary key is broken on PostgreSQL are fixed and use this new method they have to catch the exception rather than just allowing the exception to surface to the user.
Comment #52
tstoecklerSo #2976493: Creating a table with an explicitly empty primary key is broken on PostgreSQL doesn't actually use the new method outside of the tests.
#2974722: Fix dropping of columns in a composite primary key on MariaDB >=10.2.8 and make it consistent across database drivers does use it, but it's a special case: it's using it inside of the database drivers themselves. So there's no need to add a try-catch as the drivers themselves can be sure that it will never be thrown.
What I thought what we were talking about is code outside of tests and outside of the drivers.
Comment #53
alexpottWell #52 really makes me question the wisdom of this public API addition then. Since there is no requirement for the addition to the public API. If we decide we want to add this then we do need to add a disclaimer on
Schema::findPrimaryKeyColumns()
that if this used outside of tests or the drivers themselves then the exception MUST be caught because we cannot guarantee that it is implemented by contrib/custom database drivers in Drupal 8.Comment #54
tstoecklerSo do you think it would be sufficient to declare it
@internal
? I think that matches pretty well with only using it in tests and in the drivers themselves (and we can also document it, in this case).The question is then just whether remove the abstract base method entirely and consider it an "implementation detail" of each of the three database drivers in core or whether to keep throwing the exception. I would prefer the latter, but more than anything I would love for this to land, so I'll pretty much comply with anything you and @catch can agree on ;-)
Comment #55
daffie CreditAttribution: daffie commentedIf we are going to make the method
Schema::findPrimaryKeyColumns()
@internal, are we then going to make it a protected method instead of public?Comment #56
alexpottI'd be +1 on making it protected and using reflection in tests to get access to it and having a default implementation that throws an exception. That way:
Comment #57
tstoecklerSo, in other words, this?! ;-)
Comment #58
daffie CreditAttribution: daffie commentedIt all looks good to me. This is what @alexpott would like to happen.
I have only 1 minor documentation point, that can be solved on submit:
Add between those 2 lines of code the following documentation:
Back to RTBC.
Comment #59
alexpott@daffie that's not necessary - \Drupal\Core\Database\Connection::schema() already provides the typehint.
In fact the typehint here is not necessary.
Comment #60
alexpottCommitted 3edde1f and pushed to 8.6.x. Thanks!
Fixed on commit.
Comment #62
daffie CreditAttribution: daffie commented@alexpott: Thank you for the explanation.