Merge "rdbms: Add $join_conds to IDatabase::estimateRowCount()"
authorjenkins-bot <jenkins-bot@gerrit.wikimedia.org>
Wed, 14 Mar 2018 02:06:55 +0000 (02:06 +0000)
committerGerrit Code Review <gerrit@wikimedia.org>
Wed, 14 Mar 2018 02:06:55 +0000 (02:06 +0000)
1  2 
includes/libs/rdbms/database/DBConnRef.php
includes/libs/rdbms/database/Database.php
includes/libs/rdbms/database/DatabaseMysqlBase.php
includes/libs/rdbms/database/IDatabase.php

@@@ -281,7 -281,7 +281,7 @@@ class DBConnRef implements IDatabase 
        }
  
        public function estimateRowCount(
-               $table, $vars = '*', $conds = '', $fname = __METHOD__, $options = []
+               $table, $vars = '*', $conds = '', $fname = __METHOD__, $options = [], $join_conds = []
        ) {
                return $this->__call( __FUNCTION__, func_get_args() );
        }
                return $this->__call( __FUNCTION__, func_get_args() );
        }
  
 +      public function setIndexAliases( array $aliases ) {
 +              return $this->__call( __FUNCTION__, func_get_args() );
 +      }
 +
        /**
         * Clean up the connection when out of scope
         */
@@@ -76,10 -76,8 +76,10 @@@ abstract class Database implements IDat
        protected $password;
        /** @var string */
        protected $dbName;
 -      /** @var array[] $aliases Map of (table => (dbname, schema, prefix) map) */
 +      /** @var array[] Map of (table => (dbname, schema, prefix) map) */
        protected $tableAliases = [];
 +      /** @var string[] Map of (index alias => index) */
 +      protected $indexAliases = [];
        /** @var bool Whether this PHP instance is for a CLI script */
        protected $cliMode;
        /** @var string Agent name for query profiling */
  
        public function writesOrCallbacksPending() {
                return $this->trxLevel && (
 -                      $this->trxDoneWrites || $this->trxIdleCallbacks || $this->trxPreCommitCallbacks
 +                      $this->trxDoneWrites ||
 +                      $this->trxIdleCallbacks ||
 +                      $this->trxPreCommitCallbacks ||
 +                      $this->trxEndCallbacks
                );
        }
  
  
        public function close() {
                if ( $this->conn ) {
 +                      // Resolve any dangling transaction first
                        if ( $this->trxLevel() ) {
 +                              // Meaningful transactions should ideally have been resolved by now
 +                              if ( $this->writesOrCallbacksPending() ) {
 +                                      $this->queryLogger->warning(
 +                                              __METHOD__ . ": writes or callbacks still pending.",
 +                                              [ 'trace' => ( new RuntimeException() )->getTraceAsString() ]
 +                                      );
 +                              }
 +                              // Check if it is possible to properly commit and trigger callbacks
 +                              if ( $this->trxEndCallbacksSuppressed ) {
 +                                      throw new DBUnexpectedError(
 +                                              $this,
 +                                              __METHOD__ . ': callbacks are suppressed; cannot properly commit.'
 +                                      );
 +                              }
 +                              // Commit the changes and run any callbacks as needed
                                $this->commit( __METHOD__, self::FLUSHING_INTERNAL );
                        }
 -
 +                      // Close the actual connection in the binding handle
                        $closed = $this->closeConnection();
                        $this->conn = false;
 -              } elseif (
 -                      $this->trxIdleCallbacks ||
 -                      $this->trxPreCommitCallbacks ||
 -                      $this->trxEndCallbacks
 -              ) { // sanity
 -                      throw new RuntimeException( "Transaction callbacks still pending." );
 +                      // Sanity check that no callbacks are dangling
 +                      if (
 +                              $this->trxIdleCallbacks || $this->trxPreCommitCallbacks || $this->trxEndCallbacks
 +                      ) {
 +                              throw new RuntimeException( "Transaction callbacks still pending." );
 +                      }
                } else {
 -                      $closed = true;
 +                      $closed = true; // already closed; nothing to do
                }
 +
                $this->opened = false;
  
                return $closed;
        }
  
        public function estimateRowCount(
-               $table, $vars = '*', $conds = '', $fname = __METHOD__, $options = []
+               $table, $vars = '*', $conds = '', $fname = __METHOD__, $options = [], $join_conds = []
        ) {
                $rows = 0;
-               $res = $this->select( $table, [ 'rowcount' => 'COUNT(*)' ], $conds, $fname, $options );
+               $res = $this->select(
+                       $table, [ 'rowcount' => 'COUNT(*)' ], $conds, $fname, $options, $join_conds
+               );
  
                if ( $res ) {
                        $row = $this->fetchRow( $res );
         * @return string
         */
        protected function indexName( $index ) {
 -              return $index;
 +              return isset( $this->indexAliases[$index] )
 +                      ? $this->indexAliases[$index]
 +                      : $index;
        }
  
        public function addQuotes( $s ) {
                $this->tableAliases = $aliases;
        }
  
 +      public function setIndexAliases( array $aliases ) {
 +              $this->indexAliases = $aliases;
 +      }
 +
        /**
         * @return bool Whether a DB user is required to access the DB
         * @since 1.28
@@@ -562,13 -562,14 +562,14 @@@ abstract class DatabaseMysqlBase extend
         * @param string|array $conds
         * @param string $fname
         * @param string|array $options
+        * @param array $join_conds
         * @return bool|int
         */
        public function estimateRowCount( $table, $vars = '*', $conds = '',
-               $fname = __METHOD__, $options = []
+               $fname = __METHOD__, $options = [], $join_conds = []
        ) {
                $options['EXPLAIN'] = true;
-               $res = $this->select( $table, $vars, $conds, $fname, $options );
+               $res = $this->select( $table, $vars, $conds, $fname, $options, $join_conds );
                if ( $res === false ) {
                        return false;
                }
                return in_array( $name, $this->listViews( $prefix ) );
        }
  
 -      /**
 -       * Allows for index remapping in queries where this is not consistent across DBMS
 -       *
 -       * @param string $index
 -       * @return string
 -       */
 -      protected function indexName( $index ) {
 -              /**
 -               * When SQLite indexes were introduced in r45764, it was noted that
 -               * SQLite requires index names to be unique within the whole database,
 -               * not just within a schema. As discussed in CR r45819, to avoid the
 -               * need for a schema change on existing installations, the indexes
 -               * were implicitly mapped from the new names to the old names.
 -               *
 -               * This mapping can be removed if DB patches are introduced to alter
 -               * the relevant tables in existing installations. Note that because
 -               * this index mapping applies to table creation, even new installations
 -               * of MySQL have the old names (except for installations created during
 -               * a period where this mapping was inappropriately removed, see
 -               * T154872).
 -               */
 -              $renamed = [
 -                      'ar_usertext_timestamp' => 'usertext_timestamp',
 -                      'un_user_id' => 'user_id',
 -                      'un_user_ip' => 'user_ip',
 -              ];
 -
 -              if ( isset( $renamed[$index] ) ) {
 -                      return $renamed[$index];
 -              } else {
 -                      return $index;
 -              }
 -      }
 -
        protected function isTransactableQuery( $sql ) {
                return parent::isTransactableQuery( $sql ) &&
                        !preg_match( '/^SELECT\s+(GET|RELEASE|IS_FREE)_LOCK\(/', $sql );
@@@ -357,7 -357,7 +357,7 @@@ interface IDatabase 
        public function getType();
  
        /**
 -       * Open a connection to the database. Usually aborts on failure
 +       * Open a new connection to the database (closing any existing one)
         *
         * @param string $server Database server host
         * @param string $user Database user name
        public function getServerVersion();
  
        /**
 -       * Closes a database connection.
 -       * if it is open : commits any open transactions
 +       * Close the database connection
 +       *
 +       * This should only be called after any transactions have been resolved,
 +       * aside from read-only transactions (assuming no callbacks are registered).
 +       * If a transaction is still open anyway, it will be committed if possible.
         *
         * @throws DBError
         * @return bool Operation success. true if already closed.
         * @param array|string $conds Filters on the table
         * @param string $fname Function name for profiling
         * @param array $options Options for select
+        * @param array|string $join_conds Join conditions
         * @return int Row count
         * @throws DBError
         */
        public function estimateRowCount(
-               $table, $vars = '*', $conds = '', $fname = __METHOD__, $options = []
+               $table, $vars = '*', $conds = '', $fname = __METHOD__, $options = [], $join_conds = []
        );
  
        /**
         * @since 1.28
         */
        public function setTableAliases( array $aliases );
 +
 +      /**
 +       * Convert certain index names to alternative names before querying the DB
 +       *
 +       * Note that this applies to indexes regardless of the table they belong to.
 +       *
 +       * This can be employed when an index was renamed X => Y in code, but the new Y-named
 +       * indexes were not yet built on all DBs. After all the Y-named ones are added by the DBA,
 +       * the aliases can be removed, and then the old X-named indexes dropped.
 +       *
 +       * @param string[] $aliases
 +       * @return mixed
 +       * @since 1.31
 +       */
 +      public function setIndexAliases( array $aliases );
  }
  
  class_alias( IDatabase::class, 'IDatabase' );