Problem/Motivation
In #3109534: Raise the minimum MySQL version to 5.7.8 and MariaDB version to 10.2.7 in Drupal 9, we raised the minimum MySQL version to 5.7 for Drupal 9.0. However, MySQL 5.6 isn't EOL until Feb. 2021. We should therefore make it possible for people to have a custom driver that allows MySQL 5.6, at least for a little while. At some point, possibly as early as Drupal 9.1, we might start using MySQL 5.7 features, such as JSON, that'll be hard for a 5.6 driver to implement. But even if a MySQL 5.6 custom driver is only functional for Drupal 9.0 (and not 9.1), Drupal 9.0 will be supported until after Feb. 2021, so even a trivial custom driver can get people past MySQL 5.6's EOL.
To a large extent, core already allows custom drivers to extend the core ones. Within settings.php, you can have:
$databases['default']['default'] = array (
'namespace' => 'Drupal\\Driver\\Database\\mysql',
'driver' => 'mysql',
...
);
which works as expected.
However, there are two places where the above namespace from the above connection info array is not checked:
- In
db_installer_object(), used by the installer before settings.php has been written. - In
Database::convertDbUrlToConnectionInfo(), which can be called from places that only have a db URL and not a connection info array. In core, that's just tests and console commands.
In these two places, HEAD currently checks the core namespace first, and only checks the custom namespace for drivers that aren't in core. This would prevent, for example, a custom MySQL driver from being used during installation and tests.
Proposed resolution
Change the above two places to check for a driver in the custom namespace first, and then fall back to the core namespace.
Remaining tasks
User interface changes
API changes
Data model changes
Release notes snippet
| Comment | File | Size | Author |
|---|---|---|---|
| #16 | increment.txt | 6.64 KB | effulgentsia |
| #16 | db-prioritize-custom-driver-3118832-16.patch | 9.48 KB | effulgentsia |
Comments
Comment #2
effulgentsia commentedHere's 3 patches...
MYSQL_MINIMUM_VERSIONto 5.6. It should pass tests on MySQL 5.6, except the problems identified in the issue summary prevent that.Comment #4
effulgentsia commentedSuccess! The 1 failure in the
db-prioritize-custom-driver-3118832-with-example-driverpatch is the test that explicitly checks that the mysql driver resolves to the core namespace, and that is the exact behavior that we want to change in this issue. However, it's a behavior change that's only surfaced when there's a driver with the same name in the custom namespace. So I don't think that we need to change the test, since we're not planning to commit the custom driver to core's codebase. The fact that there are no other failures on 5.6 is what matters here.Meanwhile,
db-prioritize-custom-driver-3118832.patchitself passes tests (on 5.7 only, since that's what's required by the core driver).Here's an added unit test case for the URL conversion test to ensure that the custom namespace takes precedence.
We might want to also add a unit test for
db_installer_object()since that's being modified by this patch, but I'm not sure how best to do that yet.Comment #5
xjmWe need some test coverage here.
Comment #6
xjmComment #7
larowlanis it worth abstracting this logic to a helper method somewhere?
Other than that +1 on the idea, and +1 for tests
Comment #8
effulgentsia commented#4 already had tests for the change to
convertDbUrlToConnectionInfo(). This now also adds tests fordb_installer_object(). I considered moving that code into a class, so that the new unit test would test a class rather than a global function, but decided against it because that would bring us to...Yes, but let's do that in a followup, because we'll need to pick a name for that helper method. The obvious choice would be
Database::getDriverClass()except that might cause confusion withDatabase::getConnection()->getDriverClass(), so worth some discussion whether that parity would be good or bad.Comment #9
effulgentsia commentedRemoving a copy/paste leftover.
Comment #12
effulgentsia commentedYay, the new test added in #3118477: RegistryTest, RegistryLegacyTest both define the same class, use mock instead did its job. This fixes that test failure, and also the coding standards violations.
Comment #13
alexpottThis will affect all the other unit tests that run afterwards. Well not on DrupalCI because that runs all tests in their own process but as this is very much futzing with classloading it might be a good idea to always run this test in s separate process - there's an annotation for this -
@runTestsInSeparateProcesses- see UrlConversionTest for an example.If we hard coded the class name I don't think we wouldn't need the ugly require_once in the classes.
Comment #14
effulgentsia commentedThis addresses #13.1. For #13.2, hard coding the class names wouldn't remove the need for require_once, because that's for when PHPUnit performs its --list-tests scanning, where it loads the fixture classes apart from running the test itself. What would prevent the need for require_once is if there's a way to tell PHPUnit to not scan for tests inside of fixture directories.
Comment #15
alexpott@effulgentsia ah I hadn't spotted that. That's why core/tests/fixtures/ exists.
Comment #16
effulgentsia commentedNice! This moves the fixtures into there.
Because a bunch of file moves are involved, I'm attaching a git increment instead of an interdiff.
Comment #17
daffie commentedCan we all stop for a moment and take a look at the bigger picture, regarding the workflow of contrib database drivers.
Before I start with that, I would like to thank everybody who is taking an interest in improving contrib database drivers very very much. It is really appreciated! :)
Now for that bigger picture
There are 2 kinds of contrib database drivers:
- the first has only a driver directory. Lets call them type A;
- the second one has a driver directory and a module part. Lets call those type B.
I am working on creating a fallback driver for PostgreSQL and lets take that as an example. At the moment it only has a driver directory and therefor its a type A. When the by core supported driver for PostgreSQL starts using the pg_trgm extension, the contrib driver will need to undo that and therefor needs a module part and becomes a contrib driver of type B.
The workflow of installing a contrib database driver
Installing the contrib driver is as follows:
1. Download/require the contrib driver;
2. Copy the contents of the "drivers" directory from the module to the root of the Drupal project/installation.
3. If the contrib driver has a module part, the module must be enabled.
I am not sure when working with composer if this can be automated from the contrib driver or does the site builder/owner has to use scaffolding in their composer.json to copy the "drivers" directory them self. This is how it works now. Is this how we want it to be?
The workflow of updating a contrib database driver
Updating the contrib driver is as follows:
1. Downloading the updated contrib driver;
2. Copy the contents of the "drivers" directory from the module to the root of the Drupal project/installation and replacing the existing one.
3. Running the site updates script.
Site builders/owner are not used to coping files after they have run the command composer update. If they forget they will still be working with the old version of the driver directory. If they have a type B contrib driver they will be using 2 different versions. One for the driver directory and one for the module part of the contrib driver. If a site builder/owner runs the update script before copying the update to the "drivers" directory, a lot can go wrong. Is this how we want it to be?
The workflow of moving from using a contrib driver to using a by core supported driver
Moving to using the by core supported driver (with the current patch):
1. Running the update process that will be provided by the contrib driver (creating the pg_trgm indexes);
2. Deleting the contents of the "drivers" directory in the root of the Drupal project/installation;
3. Disabling the contrib module (if type B).
Testing the transition is for a site builder/owner not very easy to do.
If somebody wants to know if a site is using a contrib driver, the only way to check for that is looking if the "drivers" directory in the root of the Drupal project/installation is used or not. The driver setting in the settings.php will be the same for contrib and the by core supported drivers.
Moving to using the by core supported driver (without the current patch):
1. Running the update process that will be provided by the contrib driver (creating the pg_trgm indexes);
2. Change the driver setting from "pgsql-fallback" to "pgsql" in the settings.php file.
Can we take a look at the big picture of how we want the workflow to be for contrib database drivers before we start implementing it. Again a very big thank you to everybody who is taking an interest in contrib database drivers!
Comment #18
alexpott@daffie +1 (and a bit more). The whole process of registering and moving code about for contrib db drivers is extremely convoluted. It's driven by the use of magic driver namespaces - ie. Drupal\\Driver\\Database\\{$driver}\\Connection and you're 100% right that the whole process is very clunky and fraught with possible ways to break. For example the update process in #17 will is likely fail if the contrib driver removes a file.
I think we need to come up with a method that doesn't require anyone to move code about anywhere. #3112476: Always set $info['namespace'] on database connection info is going to set a namespace on all connections including core and think this is the start of this. Once we do that we need away of providing the namespace in the URL instead of the driver. And simple instructions for how to add the contrib db driver to the autoloader in settings.php.
Comment #19
alexpottThat said I don't think that #17 and #18 block this issue. Is only that looking at this issue makes us aware of just how painful all of this is.
I think the changes here are okay and will help us provide BC drivers in the short term but we should also continue to work on making things much easier for contrib drivers.
Comment #20
effulgentsia commentedThanks for the detail in #17!
First of all, I have no objection to some contrib drivers that extend core ones from using a different name. There might be great reasons for
pgsql-fallbackto be named that.But I want to make sure that it's also ok for contrib drivers to choose to use the same name. I created https://www.drupal.org/project/mysql56 and that's what I'm doing so far there. I named the d.o. project
mysql56for clarity on d.o., but the driver name is stillmysql.Except that's not all you'd need to do. If you installed
pgsql-fallbackto begin with, then the installer added:into your settings.php. So for step 2, you'd also need to remove that line. Which means you're changing two values in settings.php:
'driver'and'namespace'.Whereas if the driver is named the same (e.g., 'mysql'), then you only need to change/remove the 'namespace' value.
But that's not the only benefit of having the same name. Another one is that on the installer screen you're presented with a single choice for "MySQL" instead of having to choose between two different MySQL options, one of which (the core one) will give you an error screen if you choose it (assuming that if you added the mysql56 project to your codebase, it's because you're on MySQL 5.6).
This issue doesn't change any behavior in regards to how
$databases['default']['default']from settings.php is interpreted, which means it doesn't change the runtime behavior of Drupal requests that use that for connecting to the database. With or without this patch, you can toggle'namespace'between'Drupal\Driver\Database\mysql'and'Drupal\Core\Database\Driver\mysql', and the driver from the corresponding namespace will get used for that request.What this patch does is just give the custom one precedence during installation (prior to settings.php existing) and during console commands and tests where a
db_urlis provided instead of a settings.php connection info array.I think the rest of #17 is pointing out how cumbersome it is to have to place custom drivers in the particular directory that they need to go in.
It can. That's what composer/installers is for. But we need to register a new type in that project, which I opened a PR for, and we need to register the path for that type in our Composer project templates, which I opened #3119853: Add database driver installer path to Composer project templates for. With that,
composer require drupal/mysql56will put that driver into the correct directory,composer updatewill update it in that directory with whatever the latest code is, andcomposer remove drupal/mysql56will remove it from that directory. Basically all workflow is then the same as for modules, just that ones whose composer.json specifies "type": "drupal-database-driver" go into the correct directory for drivers instead of the directory for modules.These won't be able to use
"type": "drupal-database-driver"alone, since now you have code that needs to go into two different places:driversandmodules. There's a couple ways this could be handled: one is you can make two different projects:pgsql_fallbackandpgsql_fallback_module. The first can specify the second as a dependency (or vice versa) in its composer.json. Or if you really want to keep them in the same project, then another way is you can create a custom installer.And a big thank you to you as well for that!
Comment #21
alexpott@effulgentsia there’s no need for another composer installers Drupal type. I scoped out a plan with @daffie a couple of days ago and he’s going to put it in a new issue as I don’t think any of this needs to block us here.
Comment #22
effulgentsia commentedGreat! I look forward to seeing it.
Great! Given that, I created a draft CR: https://www.drupal.org/node/3119933.
Comment #23
daffie commentedAll the code changes look good to me.
For all code changes there is testing.
All the comment changes also look in order.
The moving of the fixture is to me also a good idea.
The change record is there.
For me it is RTBC.
Comment #24
alexpottRe #7 I don't think so. @daffie and I are working on a new way to autoload DB drivers that will mean that custom and contrib drivers no longer need to move code and it's likely this code will be refactored further so let's not preempt that work. @daffie hasn't yet (as far as I know created an issue on d.o for this work but that will happen in time).
Committed and pushed c4f0d96291 to 9.0.x and c3b1069774 to 8.9.x. Thanks!
Comment #27
daffie commented@alexpott: I have created that issue. See: #3120096: Support contrib database driver directories in a fixed location in a module.