Merge "rdbms: make LoadBalancer::getConnection() ignore CONN_TRX_AUTO when unusable"
authorjenkins-bot <jenkins-bot@gerrit.wikimedia.org>
Fri, 2 Mar 2018 19:26:49 +0000 (19:26 +0000)
committerGerrit Code Review <gerrit@wikimedia.org>
Fri, 2 Mar 2018 19:26:49 +0000 (19:26 +0000)
1  2 
includes/libs/rdbms/database/Database.php

@@@ -59,6 -59,9 +59,9 @@@ abstract class Database implements IDat
        const SLOW_WRITE_SEC = 0.500;
        const SMALL_WRITE_ROWS = 100;
  
+       /** @var string Whether lock granularity is on the level of the entire database */
+       const ATTR_DB_LEVEL_LOCKING = 'db-level-locking';
        /** @var string SQL query */
        protected $lastQuery = '';
        /** @var float|bool UNIX timestamp of last write query */
                return $conn;
        }
  
+       /**
+        * @param string $dbType A possible DB type (sqlite, mysql, postgres,...)
+        * @param string|null $driver Optional name of a specific DB client driver
+        * @return array Map of (Database::ATTRIBUTE_* constant => value) for all such constants
+        * @throws InvalidArgumentException
+        * @since 1.31
+        */
+       final public static function attributesFromType( $dbType, $driver = null ) {
+               static $defaults = [ self::ATTR_DB_LEVEL_LOCKING => false ];
+               $class = self::getClass( $dbType, $driver );
+               return call_user_func( [ $class, 'getAttributes' ] ) + $defaults;
+       }
        /**
         * @param string $dbType A possible DB type (sqlite, mysql, postgres,...)
         * @param string|null $driver Optional name of a specific DB client driver
                return $class;
        }
  
+       /**
+        * @return array Map of (Database::ATTRIBUTE_* constant => value
+        * @since 1.31
+        */
+       protected static function getAttributes() {
+               return [];
+       }
        /**
         * Set the PSR-3 logger interface to use for query logging. (The logger
         * interfaces for connection logging and error logging can be set with the
                list( $startOpts, $useIndex, $preLimitTail, $postLimitTail, $ignoreIndex ) =
                        $this->makeSelectOptions( $options );
  
 -              if ( !empty( $conds ) ) {
 -                      if ( is_array( $conds ) ) {
 -                              $conds = $this->makeList( $conds, self::LIST_AND );
 -                      }
 +              if ( is_array( $conds ) ) {
 +                      $conds = $this->makeList( $conds, self::LIST_AND );
 +              }
 +
 +              if ( $conds === null || $conds === false ) {
 +                      $this->queryLogger->warning(
 +                              __METHOD__
 +                              . ' called from '
 +                              . $fname
 +                              . ' with incorrect parameters: $conds must be a string or an array'
 +                      );
 +                      $conds = '';
 +              }
 +
 +              if ( $conds === '' ) {
 +                      $sql = "SELECT $startOpts $vars $from $useIndex $ignoreIndex $preLimitTail";
 +              } elseif ( is_string( $conds ) ) {
                        $sql = "SELECT $startOpts $vars $from $useIndex $ignoreIndex " .
                                "WHERE $conds $preLimitTail";
                } else {
 -                      $sql = "SELECT $startOpts $vars $from $useIndex $ignoreIndex $preLimitTail";
 +                      throw new DBUnexpectedError( $this, __METHOD__ . ' called with incorrect parameters' );
                }
  
                if ( isset( $options['LIMIT'] ) ) {
                }
  
                // We can't separate explicit JOIN clauses with ',', use ' ' for those
 -              $implicitJoins = !empty( $ret ) ? implode( ',', $ret ) : "";
 -              $explicitJoins = !empty( $retJOIN ) ? implode( ' ', $retJOIN ) : "";
 +              $implicitJoins = $ret ? implode( ',', $ret ) : "";
 +              $explicitJoins = $retJOIN ? implode( ' ', $retJOIN ) : "";
  
                // Compile our final table clause
                return implode( ' ', [ $implicitJoins, $explicitJoins ] );
                        $rows = [ $rows ];
                }
  
 -              $useTrx = !$this->trxLevel;
 -              if ( $useTrx ) {
 -                      $this->begin( $fname, self::TRANSACTION_INTERNAL );
 -              }
                try {
 +                      $this->startAtomic( $fname );
                        $affectedRowCount = 0;
                        foreach ( $rows as $row ) {
                                // Delete rows which collide with this one
                                $this->insert( $table, $row, $fname );
                                $affectedRowCount += $this->affectedRows();
                        }
 +                      $this->endAtomic( $fname );
 +                      $this->affectedRowCount = $affectedRowCount;
                } catch ( Exception $e ) {
 -                      if ( $useTrx ) {
 -                              $this->rollback( $fname, self::FLUSHING_INTERNAL );
 -                      }
 +                      $this->rollback( $fname, self::FLUSHING_INTERNAL );
                        throw $e;
                }
 -              if ( $useTrx ) {
 -                      $this->commit( $fname, self::FLUSHING_INTERNAL );
 -              }
 -
 -              $this->affectedRowCount = $affectedRowCount;
        }
  
        /**
                }
  
                $affectedRowCount = 0;
 -              $useTrx = !$this->trxLevel;
 -              if ( $useTrx ) {
 -                      $this->begin( $fname, self::TRANSACTION_INTERNAL );
 -              }
                try {
 +                      $this->startAtomic( $fname );
                        # Update any existing conflicting row(s)
                        if ( $where !== false ) {
                                $ok = $this->update( $table, $set, $where, $fname );
                        # Now insert any non-conflicting row(s)
                        $ok = $this->insert( $table, $rows, $fname, [ 'IGNORE' ] ) && $ok;
                        $affectedRowCount += $this->affectedRows();
 +                      $this->endAtomic( $fname );
 +                      $this->affectedRowCount = $affectedRowCount;
                } catch ( Exception $e ) {
 -                      if ( $useTrx ) {
 -                              $this->rollback( $fname, self::FLUSHING_INTERNAL );
 -                      }
 +                      $this->rollback( $fname, self::FLUSHING_INTERNAL );
                        throw $e;
                }
 -              if ( $useTrx ) {
 -                      $this->commit( $fname, self::FLUSHING_INTERNAL );
 -              }
 -              $this->affectedRowCount = $affectedRowCount;
  
                return $ok;
        }
                return $this->query( $sql, $fname );
        }
  
 -      public function insertSelect(
 +      final public function insertSelect(
                $destTable, $srcTable, $varMap, $conds,
                $fname = __METHOD__, $insertOptions = [], $selectOptions = [], $selectJoinConds = []
        ) {
 -              if ( $this->cliMode ) {
 +              static $hints = [ 'NO_AUTO_COLUMNS' ];
 +
 +              $insertOptions = (array)$insertOptions;
 +              $selectOptions = (array)$selectOptions;
 +
 +              if ( $this->cliMode && $this->isInsertSelectSafe( $insertOptions, $selectOptions ) ) {
                        // For massive migrations with downtime, we don't want to select everything
                        // into memory and OOM, so do all this native on the server side if possible.
                        return $this->nativeInsertSelect(
                                $varMap,
                                $conds,
                                $fname,
 -                              $insertOptions,
 +                              array_diff( $insertOptions, $hints ),
                                $selectOptions,
                                $selectJoinConds
                        );
                        $varMap,
                        $conds,
                        $fname,
 -                      $insertOptions,
 +                      array_diff( $insertOptions, $hints ),
                        $selectOptions,
                        $selectJoinConds
                );
        }
  
 +      /**
 +       * @param array $insertOptions INSERT options
 +       * @param array $selectOptions SELECT options
 +       * @return bool Whether an INSERT SELECT with these options will be replication safe
 +       * @since 1.31
 +       */
 +      protected function isInsertSelectSafe( array $insertOptions, array $selectOptions ) {
 +              return true;
 +      }
 +
        /**
         * Implementation of insertSelect() based on select() and insert()
         *
                $fname = __METHOD__,
                $insertOptions = [], $selectOptions = [], $selectJoinConds = []
        ) {
 -              $insertOptions = array_diff( (array)$insertOptions, [ 'NO_AUTO_COLUMNS' ] );
 -
                // For web requests, do a locking SELECT and then INSERT. This puts the SELECT burden
                // on only the master (without needing row-based-replication). It also makes it easy to
                // know how big the INSERT is going to be.
                                $this->affectedRowCount = $affectedRowCount;
                        } else {
                                $this->rollback( $fname, self::FLUSHING_INTERNAL );
 -                              $this->affectedRowCount = 0;
                        }
                        return $ok;
                } catch ( Exception $e ) {
                        $this->rollback( $fname, self::FLUSHING_INTERNAL );
 -                      $this->affectedRowCount = 0;
                        throw $e;
                }
        }
                if ( !is_array( $insertOptions ) ) {
                        $insertOptions = [ $insertOptions ];
                }
 -              $insertOptions = array_diff( (array)$insertOptions, [ 'NO_AUTO_COLUMNS' ] );
  
                $insertOptions = $this->makeInsertOptions( $insertOptions );
  
                } catch ( Exception $e ) {
                        // already logged; let LoadBalancer move on during mass-rollback
                }
 +
 +              $this->affectedRowCount = 0; // for the sake of consistency
        }
  
        /**