X-Git-Url: https://git.heureux-cyclage.org/?p=lhc%2Fweb%2Fwiklou.git;a=blobdiff_plain;f=includes%2Flibs%2Frdbms%2Fdatabase%2FDatabase.php;h=f3877fbe1e739b6a0b250e863c017d914b8286ed;hp=323c1478245f44216048b963914315fe6aff29dc;hb=689c847a32e7fe8a0b3a559a88a627a252c5018e;hpb=9a6b2a4fffb82840d0bf780eb4ecb873ad64fa54 diff --git a/includes/libs/rdbms/database/Database.php b/includes/libs/rdbms/database/Database.php index 323c147824..f3877fbe1e 100644 --- a/includes/libs/rdbms/database/Database.php +++ b/includes/libs/rdbms/database/Database.php @@ -126,6 +126,8 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware protected $delimiter = ';'; /** @var DatabaseDomain */ protected $currentDomain; + /** @var integer|null Rows affected by the last query to query() or its CRUD wrappers */ + protected $affectedRowCount; /** * Either 1 if a transaction is active or 0 otherwise. @@ -336,53 +338,51 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware * @since 1.18 */ final public static function factory( $dbType, $p = [] ) { - static $canonicalDBTypes = [ - 'mysql' => [ 'mysqli' ], - 'postgres' => [], - 'sqlite' => [], - 'oracle' => [], - 'mssql' => [], - ]; - static $classAliases = [ - 'DatabaseMssql' => DatabaseMssql::class, - 'DatabaseMysqli' => DatabaseMysqli::class, - 'DatabaseSqlite' => DatabaseSqlite::class, - 'DatabasePostgres' => DatabasePostgres::class + // For database types with built-in support, the below maps type to IDatabase + // implementations. For types with multipe driver implementations (PHP extensions), + // an array can be used, keyed by extension name. In case of an array, the + // optional 'driver' parameter can be used to force a specific driver. Otherwise, + // we auto-detect the first available driver. For types without built-in support, + // an class named "Database" us used, eg. DatabaseFoo for type 'foo'. + static $builtinTypes = [ + 'mssql' => DatabaseMssql::class, + 'mysql' => [ 'mysqli' => DatabaseMysqli::class ], + 'sqlite' => DatabaseSqlite::class, + 'postgres' => DatabasePostgres::class, ]; - $driver = false; $dbType = strtolower( $dbType ); - if ( isset( $canonicalDBTypes[$dbType] ) && $canonicalDBTypes[$dbType] ) { - $possibleDrivers = $canonicalDBTypes[$dbType]; - if ( !empty( $p['driver'] ) ) { - if ( in_array( $p['driver'], $possibleDrivers ) ) { - $driver = $p['driver']; - } else { - throw new InvalidArgumentException( __METHOD__ . - " type '$dbType' does not support driver '{$p['driver']}'" ); - } + $class = false; + if ( isset( $builtinTypes[$dbType] ) ) { + $possibleDrivers = $builtinTypes[$dbType]; + if ( is_string( $possibleDrivers ) ) { + $class = $possibleDrivers; } else { - foreach ( $possibleDrivers as $posDriver ) { - if ( extension_loaded( $posDriver ) ) { - $driver = $posDriver; - break; + if ( !empty( $p['driver'] ) ) { + if ( !isset( $possibleDrivers[$p['driver']] ) ) { + throw new InvalidArgumentException( __METHOD__ . + " type '$dbType' does not support driver '{$p['driver']}'" ); + } else { + $class = $possibleDrivers[$p['driver']]; + } + } else { + foreach ( $possibleDrivers as $posDriver => $possibleClass ) { + if ( extension_loaded( $posDriver ) ) { + $class = $possibleClass; + break; + } } } } } else { - $driver = $dbType; + $class = 'Database' . ucfirst( $dbType ); } - if ( $driver === false || $driver === '' ) { + if ( $class === false ) { throw new InvalidArgumentException( __METHOD__ . " no viable database extension found for type '$dbType'" ); } - $class = 'Database' . ucfirst( $driver ); - if ( isset( $classAliases[$class] ) ) { - $class = $classAliases[$class]; - } - if ( class_exists( $class ) && is_subclass_of( $class, IDatabase::class ) ) { // Resolve some defaults for b/c $p['host'] = isset( $p['host'] ) ? $p['host'] : false; @@ -1004,8 +1004,7 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware } /** - * Helper method for query() that handles profiling and logging and sends - * the query to doQuery() + * Wrapper for query() that also handles profiling, logging, and affected row count updates * * @param string $sql Original SQL query * @param string $commentedSql SQL query with debugging/trace comment @@ -1031,7 +1030,9 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware if ( $this->profiler ) { call_user_func( [ $this->profiler, 'profileIn' ], $queryProf ); } + $this->affectedRowCount = null; $ret = $this->doQuery( $commentedSql ); + $this->affectedRowCount = $this->affectedRows(); if ( $this->profiler ) { call_user_func( [ $this->profiler, 'profileOut' ], $queryProf ); } @@ -2240,51 +2241,49 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware } public function replace( $table, $uniqueIndexes, $rows, $fname = __METHOD__ ) { - $quotedTable = $this->tableName( $table ); - if ( count( $rows ) == 0 ) { return; } - # Single row case + // Single row case if ( !is_array( reset( $rows ) ) ) { $rows = [ $rows ]; } - // @FXIME: this is not atomic, but a trx would break affectedRows() + $affectedRowCount = 0; foreach ( $rows as $row ) { - # Delete rows which collide - if ( $uniqueIndexes ) { - $sql = "DELETE FROM $quotedTable WHERE "; - $first = true; - foreach ( $uniqueIndexes as $index ) { - if ( $first ) { - $first = false; - $sql .= '( '; - } else { - $sql .= ' ) OR ( '; - } - if ( is_array( $index ) ) { - $first2 = true; - foreach ( $index as $col ) { - if ( $first2 ) { - $first2 = false; - } else { - $sql .= ' AND '; - } - $sql .= $col . '=' . $this->addQuotes( $row[$col] ); - } - } else { - $sql .= $index . '=' . $this->addQuotes( $row[$index] ); - } + // Delete rows which collide with this one + $indexWhereClauses = []; + foreach ( $uniqueIndexes as $index ) { + $indexColumns = (array)$index; + $indexRowValues = array_intersect_key( $row, array_flip( $indexColumns ) ); + if ( count( $indexRowValues ) != count( $indexColumns ) ) { + throw new DBUnexpectedError( + $this, + 'New record does not provide all values for unique key (' . + implode( ', ', $indexColumns ) . ')' + ); + } elseif ( in_array( null, $indexRowValues, true ) ) { + throw new DBUnexpectedError( + $this, + 'New record has a null value for unique key (' . + implode( ', ', $indexColumns ) . ')' + ); } - $sql .= ' )'; - $this->query( $sql, $fname ); + $indexWhereClauses[] = $this->makeList( $indexRowValues, LIST_AND ); + } + + if ( $indexWhereClauses ) { + $this->delete( $table, $this->makeList( $indexWhereClauses, LIST_OR ), $fname ); + $affectedRowCount += $this->affectedRows(); } - # Now insert the row + // Now insert the row $this->insert( $table, $row, $fname ); + $affectedRowCount += $this->affectedRows(); } + + $this->affectedRowCount = $affectedRowCount; } /** @@ -2349,6 +2348,7 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware $where = false; } + $affectedRowCount = 0; $useTrx = !$this->mTrxLevel; if ( $useTrx ) { $this->begin( $fname, self::TRANSACTION_INTERNAL ); @@ -2357,11 +2357,13 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware # Update any existing conflicting row(s) if ( $where !== false ) { $ok = $this->update( $table, $set, $where, $fname ); + $affectedRowCount += $this->affectedRows(); } else { $ok = true; } # Now insert any non-conflicting row(s) $ok = $this->insert( $table, $rows, $fname, [ 'IGNORE' ] ) && $ok; + $affectedRowCount += $this->affectedRows(); } catch ( Exception $e ) { if ( $useTrx ) { $this->rollback( $fname, self::FLUSHING_INTERNAL ); @@ -2371,6 +2373,7 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware if ( $useTrx ) { $this->commit( $fname, self::FLUSHING_INTERNAL ); } + $this->affectedRowCount = $affectedRowCount; return $ok; } @@ -3189,6 +3192,17 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware } } + public function affectedRows() { + return ( $this->affectedRowCount === null ) + ? $this->fetchAffectedRowCount() // default to driver value + : $this->affectedRowCount; + } + + /** + * @return int Number of retrieved rows according to the driver + */ + abstract protected function fetchAffectedRowCount(); + /** * Take the result from a query, and wrap it in a ResultWrapper if * necessary. Boolean values are passed through as is, to indicate success