Dependency inject $wgSharedTables into DatabaseBase
authorAaron Schulz <aschulz@wikimedia.org>
Thu, 15 Sep 2016 17:07:47 +0000 (10:07 -0700)
committerAaron Schulz <aschulz@wikimedia.org>
Thu, 15 Sep 2016 18:12:24 +0000 (18:12 +0000)
Change-Id: I3e659b56c587d2059fc3d3f922bc38dec7685df4

includes/Setup.php
includes/db/Database.php
includes/db/DatabaseSqlite.php
includes/libs/rdbms/database/DBConnRef.php
includes/libs/rdbms/database/IDatabase.php
includes/libs/rdbms/loadbalancer/LoadBalancer.php
tests/phpunit/includes/db/DatabaseTest.php

index 2f462b8..ddf5b89 100644 (file)
@@ -504,6 +504,19 @@ if ( !class_exists( 'AutoLoader' ) ) {
 // Reset the global service locator, so any services that have already been created will be
 // re-created while taking into account any custom settings and extensions.
 MediaWikiServices::resetGlobalInstance( new GlobalVarConfig(), 'quick' );
+// Apply $wgSharedDB table aliases for the local LB (all non-foreign DB connections)
+if ( $wgSharedDB && $wgSharedTables ) {
+       MediaWikiServices::getInstance()->getDBLoadBalancer()->setTableAliases(
+               array_fill_keys(
+                       $wgSharedTables,
+                       [
+                               'dbname' => $wgSharedDB,
+                               'schema' => $wgSharedSchema,
+                               'prefix' => $wgSharedPrefix
+                       ]
+               )
+       );
+}
 
 // Define a constant that indicates that the bootstrapping of the service locator
 // is complete.
index 917bc91..5182fce 100644 (file)
@@ -60,6 +60,8 @@ abstract class DatabaseBase implements IDatabase, LoggerAwareInterface {
        protected $mPassword;
        /** @var string */
        protected $mDBname;
+       /** @var array[] $aliases Map of (table => (dbname, schema, prefix) map) */
+       protected $tableAliases = [];
        /** @var bool */
        protected $cliMode;
 
@@ -94,8 +96,6 @@ abstract class DatabaseBase implements IDatabase, LoggerAwareInterface {
        protected $mSchema;
        /** @var integer */
        protected $mFlags;
-       /** @var bool */
-       protected $mForeign;
        /** @var array */
        protected $mLBInfo = [];
        /** @var bool|null */
@@ -263,8 +263,6 @@ abstract class DatabaseBase implements IDatabase, LoggerAwareInterface {
 
                $this->mSessionVars = $params['variables'];
 
-               $this->mForeign = $params['foreign'];
-
                $this->srvCache = isset( $params['srvCache'] )
                        ? $params['srvCache']
                        : new EmptyBagOStuff();
@@ -1862,7 +1860,6 @@ abstract class DatabaseBase implements IDatabase, LoggerAwareInterface {
         * @return string Full database name
         */
        public function tableName( $name, $format = 'quoted' ) {
-               global $wgSharedDB, $wgSharedPrefix, $wgSharedTables, $wgSharedSchema;
                # Skip the entire process when we have a string quoted on both ends.
                # Note that we check the end so that we will still quote any use of
                # use of `database`.table. But won't break things if someone wants
@@ -1899,14 +1896,14 @@ abstract class DatabaseBase implements IDatabase, LoggerAwareInterface {
                        $schema = null;
                } else {
                        list( $table ) = $dbDetails;
-                       if ( $wgSharedDB !== null # We have a shared database
-                               && $this->mForeign == false # We're not working on a foreign database
-                               && !$this->isQuotedIdentifier( $table ) # Prevent shared tables listing '`table`'
-                               && in_array( $table, $wgSharedTables ) # A shared table is selected
-                       ) {
-                               $database = $wgSharedDB;
-                               $schema = $wgSharedSchema === null ? $this->mSchema : $wgSharedSchema;
-                               $prefix = $wgSharedPrefix === null ? $this->mTablePrefix : $wgSharedPrefix;
+                       if ( isset( $this->tableAliases[$table] ) ) {
+                               $database = $this->tableAliases[$table]['dbname'];
+                               $schema = is_string( $this->tableAliases[$table]['schema'] )
+                                       ? $this->tableAliases[$table]['schema']
+                                       : $this->mSchema;
+                               $prefix = is_string( $this->tableAliases[$table]['prefix'] )
+                                       ? $this->tableAliases[$table]['prefix']
+                                       : $this->mTablePrefix;
                        } else {
                                $database = null;
                                $schema = $this->mSchema; # Default schema
@@ -1917,7 +1914,9 @@ abstract class DatabaseBase implements IDatabase, LoggerAwareInterface {
                # Quote $table and apply the prefix if not quoted.
                # $tableName might be empty if this is called from Database::replaceVars()
                $tableName = "{$prefix}{$table}";
-               if ( $format == 'quoted' && !$this->isQuotedIdentifier( $tableName ) && $tableName !== '' ) {
+               if ( $format == 'quoted'
+                       && !$this->isQuotedIdentifier( $tableName ) && $tableName !== ''
+               ) {
                        $tableName = $this->addIdentifierQuotes( $tableName );
                }
 
@@ -3686,6 +3685,10 @@ abstract class DatabaseBase implements IDatabase, LoggerAwareInterface {
                return is_string( $reason ) ? $reason : false;
        }
 
+       public function setTableAliases( array $aliases ) {
+               $this->tableAliases = $aliases;
+       }
+
        /**
         * @since 1.19
         * @return string
index 3c0c279..0cbb496 100644 (file)
@@ -59,7 +59,7 @@ class DatabaseSqlite extends Database {
         * @param array $p
         */
        function __construct( array $p ) {
-               global $wgSharedDB, $wgSQLiteDataDir;
+               global $wgSQLiteDataDir;
 
                $this->dbDir = isset( $p['dbDirectory'] ) ? $p['dbDirectory'] : $wgSQLiteDataDir;
 
@@ -76,8 +76,13 @@ class DatabaseSqlite extends Database {
                        // Super doesn't open when $user is false, but we can work with $dbName
                        if ( $p['dbname'] && !$this->isOpen() ) {
                                if ( $this->open( $p['host'], $p['user'], $p['password'], $p['dbname'] ) ) {
-                                       if ( $wgSharedDB ) {
-                                               $this->attachDatabase( $wgSharedDB );
+                                       $done = [];
+                                       foreach ( $this->tableAliases as $params ) {
+                                               if ( isset( $done[$params['dbname']] ) ) {
+                                                       continue;
+                                               }
+                                               $this->attachDatabase( $params['dbname'] );
+                                               $done[$params['dbname']] = 1;
                                        }
                                }
                        }
index 9035bbd..3a18fc0 100644 (file)
@@ -574,6 +574,10 @@ class DBConnRef implements IDatabase {
                return $this->__call( __FUNCTION__, func_get_args() );
        }
 
+       public function setTableAliases( array $aliases ) {
+               return $this->__call( __FUNCTION__, func_get_args() );
+       }
+
        /**
         * Clean up the connection when out of scope
         */
index 671ce99..026cbc5 100644 (file)
@@ -1719,4 +1719,19 @@ interface IDatabase {
         * @since 1.27
         */
        public function isReadOnly();
+
+       /**
+        * Make certain table names use their own database, schema, and table prefix
+        * when passed into SQL queries pre-escaped and without a qualified database name
+        *
+        * For example, "user" can be converted to "myschema.mydbname.user" for convenience.
+        * Appearances like `user`, somedb.user, somedb.someschema.user will used literally.
+        *
+        * Calling this twice will completely clear any old table aliases. Also, note that
+        * callers are responsible for making sure the schemas and databases actually exist.
+        *
+        * @param array[] $aliases Map of (table => (dbname, schema, prefix) map)
+        * @since 1.28
+        */
+       public function setTableAliases( array $aliases );
 }
index 824279d..cea7523 100644 (file)
@@ -42,6 +42,8 @@ class LoadBalancer implements ILoadBalancer {
        private $mWaitTimeout;
        /** @var string The LoadMonitor subclass name */
        private $mLoadMonitorClass;
+       /** @var array[] $aliases Map of (table => (dbname, schema, prefix) map) */
+       private $tableAliases = [];
 
        /** @var LoadMonitor */
        private $mLoadMonitor;
@@ -822,6 +824,7 @@ class LoadBalancer implements ILoadBalancer {
                $db->setLazyMasterHandle(
                        $this->getLazyConnectionRef( DB_MASTER, [], $db->getWikiID() )
                );
+               $db->setTableAliases( $this->tableAliases );
 
                if ( $server['serverIndex'] === $this->getWriterIndex() ) {
                        if ( $this->trxRoundId !== false ) {
@@ -1570,6 +1573,23 @@ class LoadBalancer implements ILoadBalancer {
                );
        }
 
+       /**
+        * Make certain table names use their own database, schema, and table prefix
+        * when passed into SQL queries pre-escaped and without a qualified database name
+        *
+        * For example, "user" can be converted to "myschema.mydbname.user" for convenience.
+        * Appearances like `user`, somedb.user, somedb.someschema.user will used literally.
+        *
+        * Calling this twice will completely clear any old table aliases. Also, note that
+        * callers are responsible for making sure the schemas and databases actually exist.
+        *
+        * @param array[] $aliases Map of (table => (dbname, schema, prefix) map)
+        * @since 1.28
+        */
+       public function setTableAliases( array $aliases ) {
+               $this->tableAliases = $aliases;
+       }
+
        /**
         * Set a new table prefix for the existing local domain ID for testing
         *
index 0f9a401..e884640 100644 (file)
@@ -68,21 +68,26 @@ class DatabaseTest extends MediaWikiTestCase {
        }
 
        private function getSharedTableName( $table, $database, $prefix, $format = 'quoted' ) {
-               global $wgSharedDB, $wgSharedTables, $wgSharedPrefix;
+               global $wgSharedDB, $wgSharedTables, $wgSharedPrefix, $wgSharedSchema;
 
-               $oldName = $wgSharedDB;
-               $oldTables = $wgSharedTables;
-               $oldPrefix = $wgSharedPrefix;
-
-               $wgSharedDB = $database;
-               $wgSharedTables = [ $table ];
-               $wgSharedPrefix = $prefix;
+               $this->db->setTableAliases( [
+                       $table => [
+                               'dbname' => $database,
+                               'schema' => null,
+                               'prefix' => $prefix
+                       ]
+               ] );
 
                $ret = $this->db->tableName( $table, $format );
 
-               $wgSharedDB = $oldName;
-               $wgSharedTables = $oldTables;
-               $wgSharedPrefix = $oldPrefix;
+               $this->db->setTableAliases( array_fill_keys(
+                       $wgSharedDB ? $wgSharedTables : [],
+                       [
+                               'dbname' => $wgSharedDB,
+                               'schema' => $wgSharedSchema,
+                               'prefix' => $wgSharedPrefix
+                       ]
+               ) );
 
                return $ret;
        }