Problem/Motivation

There are third party database drivers (at least mssql, oracle and mongodb_dbtng) and Drupal hardcodes the database drivers inside 'core'. Given that the motivation behind 'core' was to allow for deletion of the whole dir on upgrade, it seems rather necessary to allow database drivers outside of core.

Proposed resolution

Allow database drivers in a directory (proposed name drivers/lib/Drupal/drivers/Database) from the Drupal root (much like modules and themes). Do not pre-create this because there will be so few people needing this.

Remaining tasks

A followup is necessary for the original issue: update_status should recognize these projects.

User interface changes

Absolutely none.

API changes

Existing installs need a new custom => TRUE key-value pair in their $database array in settings.php. Given the extremely small number and very high experience level of sites running any of these I do not see the need for an actual update function.

Original report by Damien Tournoud

One of the promises of the new database layer was that third-party database drivers could be shipped as contrib modules. This is not the case anymore, because the new database autoloader hardcodes 'includes/database/[driver_name]' as the path.

One of the major implications of this is that we will not be able to track the usage of those third-party db drivers.

Comments

damien tournoud’s picture

One way of doing that could be to have a path in the $databases array, like this:

$databases = array(
  'default' => array(
    'default' => array(
      'driver' => 'thirdparty',
      'driver path' => 'sites/all/modules/thirdparty',
      'database' => 'drupal',
      'username' => 'drupal',
      'password' => 'drupal',
      'host' => 'localhost',
      'port' => '',
    ),
  ),
);

'driver path' could probably be detected during installation.

moshe weitzman’s picture

Priority: Normal » Critical

Critical IMO. We can't force oracle and mssql to require core patches.

moshe weitzman’s picture

Well, its not really a patch, but it is requiring modules to put their code inside of /includes which is just as bad as a core patch.

Crell’s picture

Priority: Critical » Normal
Status: Active » Closed (works as designed)

I don't know what Damien is talking about by us promising that new drivers could be modules. We said they could be added without hacking core, which is already true.

Moshe, please don't mark new issues critical without verifying that what you're saying is even true. It's not "as bad as a core patch". It's no worse than what we have to do for install profiles. Drivers are not modules; we just lack a "DB Driver" project type in project.module.

This is also a duplicate anyway; We closed a previous issue on this subject months ago.

moshe weitzman’s picture

Install profiles don't require you to copy code into a core directory since they are already packaged that way. So its a bit different IMO. I'm not going argue about priority. Normal is fine. I do think we should endeavour to fix this. I'll see if I can find that original discussion.

hass’s picture

#778310: Update status for db drivers is missing has been marked as a duplicate by Damien, but isn't really.

chx’s picture

Title: Third party db drivers cannot be modules » Allow db drivers outside of 'core'
Version: 7.x-dev » 8.x-dev
Assigned: Unassigned » chx
Category: bug » task
Status: Closed (works as designed) » Active
StatusFileSize
new41.78 KB
new2.55 KB

This is a work in progress but something is happening. I came up with a directory name 'db_drivers' to be put alongside modules and themes in the Drupal root, this is not something I plan to pre-create unlike modules and themes. The screenshot is from an earlier version where i called it 'database', nonetheless, it shows the install screen working.

chx’s picture

Assigned: chx » Crell
Status: Active » Needs review
StatusFileSize
new4.39 KB

This is it. You can put a db_driver in a db_drivers directory in Drupal root. It needs to implement a function isCore() { return FALSE; } in the Install\Tasks.php file and the rest is taken care of: the $database array gains a db_driver key which is taken into consideration in openConnection and getDriverClass.

I took great pains to make sure the effects are extremely small, the only file operations added are in install.

The only question is, should I rename db_driver to drupal_driver?

Assigning to Crell for a review.

Ps. getDriverClass is broken 'cos if $driver_class doesn't exist it will try to instantiate a namespace-less class. Larry, should we declare the base classes abstract to mandate their overriding and simplify this function or fix this function? I know it's a separate issue but I didn't want to push two issues to you :)

chx’s picture

StatusFileSize
new3.42 KB
new4.48 KB

Let's do this instead. This uses function isCustom { return TRUE;} and writes accordingly into settings.php. It's a lot cleaner.

chx’s picture

Issue summary: View changes

Updated issue summary.

chx’s picture

StatusFileSize
new5.95 KB

To test, you can move the mysql driver from the core directory under db_drivers/test/lib/Drupal/test/ and apply patch. As you can see, aside from the namespace changes, only Connection::driver and the Tasks needed changes.

chx’s picture

Issue summary: View changes

Updated issue summary.

chx’s picture

StatusFileSize
new1.37 KB
new3.76 KB

Here's one that peeks at the Tasks classname during install to figure out whether it's a core driver and gets rid of the Tasks method. Less code.

chx’s picture

StatusFileSize
new530 bytes
new3.76 KB

dawehner says, strpos would better. And I have changed the string to test to Drupal\Core\Database just in the case some enterprising developer names a driver "Core" (although that would be a particularly stupid idea).

dawehner’s picture

+++ b/core/includes/install.incundefined
@@ -139,6 +139,16 @@ function drupal_get_database_types() {
+  if (is_dir(DRUPAL_ROOT . '/db_drivers')) {

I think it's okay to not document here how to create a custom db driver, as that's an advance topic, which could be maybe written somewhere else.

All these code lines looks fine, but yeah in a perfect world you would have a fully decoupled system, where you don't need to distinct between core and custom drivers, but this patch is already a huge improvement.

chx’s picture

Documentation wise, we (do not) have a page at http://drupal.org/node/310087 which I will amend.

Also, Damien and me I already are on the issue and I already emailed Andrea Gariboldi (and David Strauss for good measure). Also, I will contact the DB2 and the Firebird people should this go in.

chx’s picture

Issue summary: View changes

Updated issue summary.

Crell’s picture

+++ b/core/lib/Drupal/Core/Database/Database.php
@@ -374,7 +374,13 @@ public static function addConnectionInfo($key, $target, $info) {
+      drupal_classloader_register($driver, "db_drivers/$driver");
+      $driver_class = "Drupal\\{$driver}\\Connection";

This looks mostly reasonable, aside from this line as we cannot hard code Drupal functions into the Database core classes.

Instead, I wonder if we couldn't specify a class prefix (namespaces) in the database definition in settings.php, and then just rely on the autoloader on its own? If you're setting up a 3rd party driver, it should be expected that you need to take care of its autoloading. Consider, eg, if 3rd party drivers were brought in via composer. Then all you need to define is the name prefix the classes, use, which don't even have to be in the Drupal namespace technically.

chx’s picture

> as we cannot hard code Drupal functions into the Database core classes.

And what part of that is not possible? Are you still chasing the "DBTNG as an independent component' dream when the Drupal namespace is hardwired into HEAD and indeed without this patch, the Drupal Core namespace is?

Or we can't call functions from a class? Why not? Did someone from a burning bush hand you a stone table with this etched into it?

Here's a compromise: let's add a @todo that once the Database is moved into the DIC , we inject the classloader. We can't really do that now (or if we did it'd be superb ugly).

> Consider, eg, if 3rd party drivers were brought in via composer

If they are to work with Drupal they need to be in a certain namespace which we hardwire right in this patch -- before it was hardwired to Drupal core. So, if the namespace is already hardwired, why (and how?) would they take of autoloading? Manually edit settings.php? What for? That's the biggest virtue of this patch: you dont need to edit settings.php, instead install.php works.

Please reconsider your veto on this.

Crell’s picture

Injecting the classloader is ugly, too. The database manager shouldn't have to think about autoloading. That's not its job.

What if we put the /db_drivers/ check in where we setup the classloader? If it exists, register it as a possible source for \Drupal\Core\Database\Driver classes. That's one file_exists() check, but in the grand scheme of things there are far bigger performance fish to fry. Then the DB manager can just use the name of the class as normal (keeping the magic naming just as we do now), and the autoloader can do what the autoloader is good at: Load classes.

Also, Re #8, I'd be fine with the base classes being abstract if the language would let us. But I don't think you can make a class abstract unless it has at least one unimplemented method, which we have no need for as most driver-provided classes don't need to override anything. So, meh. If $driver_class doesn't exist, that's a broken state anyway. We either let it fail hard, or we catch it and throw an exception. I'm fine either way.

chx’s picture

StatusFileSize
new1.89 KB
new5.05 KB

> The database manager shouldn't have to think about autoloading. That's not its job.

In theory perhaps. Practice shows otherwise. It shouldn't hardwire namespace either, for that matter.

> What if we put the /db_drivers/ check in where we setup the classloader?

So the database manager can't know about the classloader (while it at least acknowledges its existence -- in D7 Database was loading classes) but the classloader should know about database? Isn't this completely backwards?

> What if we put the /db_drivers/ check in where we setup the classloader?

Absolutely not. We are not adding file checks to the critical path. Unlike this-is-not-nice-in-theory, imagine a network filesystem with apc.stat 0. You just killed the site's performance.

So, I do not really like this solution but your stubborn theorist stance does not let me come up with anything nicer. I would much appreciate you accepting #12 instead. Considering that Database practically uses globals (even if it's just class statics, it's not really different) I am amazed by your resistance.

chx’s picture

Assigned: Crell » dries
Status: Needs review » Reviewed & tested by the community

Well, enough of this. I know that dawehner only didn't RTBC this because we wanted Larry to see this and he said "This looks mostly reasonable, aside from this line".

Dries, I ask you to decide whether Larry is is right and preferably commit #12 (or #18 if you really must). While #12 indeed adds a call outside of the DBTNG directory this should be no problem -- just do not add a custom => TRUE to your config, done. If the time comes to separate DBTNG out, adding a function_exists there won't be a problem. There is, or should be, a reason of why we have a Drupal\Core and a Drupal\Component directory and as of right now Database is still in Core.

As I stated in the past: I find the double measure of Drupal 8 absolutely unfair where people can freely add any hundreds or thousands of code that does not fit Drupal in any way or form (and, ironically then I work hard to make it fit somehow). and I can't fix anything if it does not fit their agenda. Well, I try really hard not to complain any more for the sake of progress. Can we cut a one line slack for me -- once again, in the name of progress?

catch’s picture

Assuming this is the bit in question.

-      $driver_class = "Drupal\\Core\\Database\\Driver\\{$driver}\\{$class}";
+      if (!empty($this->connectionOptions['custom'])) {

Why can't the driver be referenced by the full namespace (either in settings.php or wherever before it gets here), then there's no hard coding of Drupal\Core anyway?

chx’s picture

Title: Allow db drivers outside of 'core' » Allow db and other drivers outside of core
Assigned: dries » chx
Status: Reviewed & tested by the community » Needs review
StatusFileSize
new4.76 KB

This is the result of an extremely fruitful conversation with catch. Small, pretty and extremely powerful. You can now put memcache and whatnot in 'drivers/lib'.

chx’s picture

Issue summary: View changes

Updated issue summary.

chx’s picture

StatusFileSize
new2.96 KB
new4.93 KB

So the top dir wants to be 'drivers' as in moduleS, themeS, siteS but the lib directory is Component not ComponentS so there.

dries’s picture

Title: Allow db and other drivers outside of core » Allow db drivers outside of 'core'
Assigned: chx » dries
Status: Needs review » Reviewed & tested by the community

I'm ok in principle but it seems like the code could be cleaned up a bit.

Specifically, I'm ok with adding a 'drivers' directory -- not sure I'm fond of 'db_drivers'. We've kept the Drupal root free of underscores, which is kinda nice.

+++ b/core/includes/install.core.incundefined
@@ -1018,6 +1018,10 @@ function install_settings_form($form, &$form_state, &$install_state) {
+  $drivers = drupal_get_database_types();
+  if (strpos(get_class($drivers[$driver]), 'Drupal\\Core\\Database') !== 0) {
+    $database['custom'] = TRUE;

Should we call this function 'drupal_get_database_drivers()' instead? Seems weird to request 'types' and to call the result 'drivers'.

+++ b/core/includes/install.core.incundefined
@@ -1090,8 +1094,13 @@ function install_settings_form_submit($form, &$form_state) {
+  $extras = '';
+  if (!empty($form_state['storage']['database']['custom'])) {
+    $driver = $form_state['storage']['database']['driver'];
+    $extras = "drupal_classloader_register('$driver', \"db_drivers/$driver\");\n";
+  }
 
-  drupal_rewrite_settings($settings);

Wouldn't this make more sense to register the classloader where we already register other classloaders?

+++ b/core/includes/install.incundefined
@@ -139,6 +139,16 @@ function drupal_get_database_types() {
+  if (is_dir(DRUPAL_ROOT . '/db_drivers')) {
+    $files = file_scan_directory(DRUPAL_ROOT . '/db_drivers', '/^[a-z_]+$/i', array('recurse' => FALSE));
+    foreach ($files as $uri => $file) {
+      $file->uri .= "/lib/Drupal/$file->filename";
+      if (file_exists($file->uri . '/Install/Tasks.php')) {
+        $drivers[$file->filename] = $file->uri;
+        drupal_classloader_register($file->filename, "db_drivers/$file->filename");
+      }
+    }

I'm not sure I like this. Could you add some code comments?

effulgentsia’s picture

Assigned: dries » effulgentsia
Status: Reviewed & tested by the community » Needs work

I'd like to take a stab at a cleanup.

damien tournoud’s picture

Assigned: effulgentsia » dries
Status: Needs work » Reviewed & tested by the community

Well, if we have to do this anyway:

+  $files = file_scan_directory(DRUPAL_ROOT . '/core/lib/Drupal/Core/Database/Driver', $mask, array('recurse' => FALSE));
+  if (is_dir(DRUPAL_ROOT . '/drivers/lib/Drupal/Driver/Database')) {
+    $files += file_scan_directory(DRUPAL_ROOT . '/drivers/lib/Drupal/Driver/Database/', $mask, array('recurse' => FALSE));
+  }

... why don't we allow modules to ship database drivers?

damien tournoud’s picture

Assigned: dries » effulgentsia
Status: Reviewed & tested by the community » Needs work
dawehner’s picture

Title: Allow db drivers outside of 'core' » Allow db and other drivers outside of core
Assigned: effulgentsia » chx
Status: Needs work » Reviewed & tested by the community
+++ b/core/includes/bootstrap.incundefined
@@ -3166,7 +3166,7 @@
+      'Drupal\Driver' => DRUPAL_ROOT . '/drivers/lib',

It's good to be consistent here with both the namespace and the folder!

+++ b/core/includes/install.incundefined
@@ -134,9 +134,11 @@
+  $mask = '/^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*$/';

Maybe it would be just be helpful to put http://www.php.net/manual/en/language.variables.basics.php as reference.

+++ b/core/lib/Drupal/Core/Database/Connection.phpundefined
@@ -624,6 +624,7 @@
+        // Fallback for Drupal 7 settings.php.

+++ b/core/lib/Drupal/Core/Database/Database.phpundefined
@@ -378,6 +378,7 @@
+      // Fallback for Drupal 7 settings.php.

Thanks for adding the comment.

dawehner’s picture

Assigned: chx » effulgentsia
Status: Reviewed & tested by the community » Needs work

sorry this wasn't indented.

chx’s picture

Title: Allow db and other drivers outside of core » Allow db drivers outside of 'core'
Assigned: effulgentsia » chx
Status: Needs work » Needs review

I think Dries reviewed some old patch cos #22 already addressed everything in #23 :D

effulgentsia’s picture

Assigned: chx » dries

#22 does pretty much what I was thinking. I might want to review this again (I'm not sure about the reflection class), but no need for it to be assigned to me.

effulgentsia’s picture

Assigned: dries » chx

ack. too much xposting.

chx’s picture

Hold it for a minute, talking to Damien, will post a new one in a minute.

chx’s picture

StatusFileSize
new608 bytes
new5 KB

OK we discussed at length and agreed on actually not changing anything. This just adds a comment to link to the PHP docs. Yes there are warts: we do "drivers/lib/Drupal/Driver/Database/*" while elsewhere we would have "drivers/*/lib/Drupal/Driver/Database" but resolving that would be a rather complicated process -- we would need to register the drivers/* roots for this which would require another settings.php global or even worse drupal_classloader_register calls in settings.php. Neither is a good idea for such an edge case.

damien tournoud’s picture

Status: Needs review » Reviewed & tested by the community

This is the best we have right now, so let's roll with it.

Crell’s picture

#33 looks good by me.

effulgentsia’s picture

Status: Reviewed & tested by the community » Fixed
chx’s picture

Title: Allow db drivers outside of 'core' » Change notice for Allow db drivers outside of 'core'
Priority: Normal » Critical
Status: Fixed » Active
chx’s picture

Title: Change notice for Allow db drivers outside of 'core' » Allow db drivers outside of 'core'
Priority: Critical » Normal
Status: Active » Fixed

Change notice is at http://drupal.org/node/1918778

chx’s picture

Issue summary: View changes

Updated issue summary.

Status: Fixed » Closed (fixed)

Automatically closed -- issue fixed for 2 weeks with no activity.

Anonymous’s picture

Issue summary: View changes

Updated issue summary.

andypost’s picture