From: jenkins-bot Date: Thu, 30 Mar 2017 02:32:53 +0000 (+0000) Subject: Merge "Revert "Remove old remapping hacks from Database::indexName()"" X-Git-Tag: 1.31.0-rc.0~3662 X-Git-Url: https://git.cyclocoop.org/%7B%24admin_url%7Dcompta/operations/modifier.php?a=commitdiff_plain;h=2c8f7978df47f338ee6e245e3efba6175ba425e9;hp=-c;p=lhc%2Fweb%2Fwiklou.git Merge "Revert "Remove old remapping hacks from Database::indexName()"" --- 2c8f7978df47f338ee6e245e3efba6175ba425e9 diff --combined includes/libs/rdbms/database/Database.php index beb38bc0d7,7e80221128..fbfd899e6d --- a/includes/libs/rdbms/database/Database.php +++ b/includes/libs/rdbms/database/Database.php @@@ -25,24 -25,12 +25,24 @@@ */ use Psr\Log\LoggerAwareInterface; use Psr\Log\LoggerInterface; +use Wikimedia\ScopedCallback; +use Wikimedia\Rdbms\TransactionProfiler; +use Wikimedia\Rdbms\LikeMatch; +use Wikimedia\Rdbms\DatabaseDomain; +use Wikimedia\Rdbms\ResultWrapper; +use Wikimedia\Rdbms\DBMasterPos; +use Wikimedia\Rdbms\Blob; +use Wikimedia\Timestamp\ConvertibleTimestamp; +use Wikimedia\Rdbms\IDatabase; +use Wikimedia\Rdbms\IMaintainableDatabase; /** - * Database abstraction object + * Relational database abstraction object + * * @ingroup Database + * @since 1.28 */ -abstract class Database implements IDatabase, LoggerAwareInterface { +abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAwareInterface { /** Number of times to re-try an operation in case of deadlock */ const DEADLOCK_TRIES = 4; /** Minimum time to wait before retry, in microseconds */ @@@ -60,8 -48,8 +60,8 @@@ /** @var string SQL query */ protected $mLastQuery = ''; - /** @var bool */ - protected $mDoneWrites = false; + /** @var float|bool UNIX timestamp of last write query */ + protected $mLastWriteTime = false; /** @var string|bool */ protected $mPHPError = false; /** @var string */ @@@ -88,7 -76,7 +88,7 @@@ /** @var callback Error logging callback */ protected $errorLogger; - /** @var resource Database connection */ + /** @var resource|null Database connection */ protected $mConn = null; /** @var bool */ protected $mOpened = false; @@@ -138,7 -126,7 +138,7 @@@ * Either a short hexidecimal string if a transaction is active or "" * * @var string - * @see DatabaseBase::mTrxLevel + * @see Database::mTrxLevel */ protected $mTrxShortId = ''; /** @@@ -147,7 -135,7 +147,7 @@@ * point (possibly more up-to-date since the first SELECT defines the snapshot). * * @var float|null - * @see DatabaseBase::mTrxLevel + * @see Database::mTrxLevel */ private $mTrxTimestamp = null; /** @var float Lag estimate at the time of BEGIN */ @@@ -157,21 -145,21 +157,21 @@@ * Used to provide additional context for error reporting. * * @var string - * @see DatabaseBase::mTrxLevel + * @see Database::mTrxLevel */ private $mTrxFname = null; /** * Record if possible write queries were done in the last transaction started * * @var bool - * @see DatabaseBase::mTrxLevel + * @see Database::mTrxLevel */ private $mTrxDoneWrites = false; /** * Record if the current transaction was started implicitly due to DBO_TRX being set. * * @var bool - * @see DatabaseBase::mTrxLevel + * @see Database::mTrxLevel */ private $mTrxAutomatic = false; /** @@@ -181,7 -169,7 +181,7 @@@ */ private $mTrxAtomicLevels = []; /** - * Record if the current transaction was started implicitly by DatabaseBase::startAtomic + * Record if the current transaction was started implicitly by Database::startAtomic * * @var bool */ @@@ -216,11 -204,23 +216,11 @@@ /** @var array Map of (name => 1) for locks obtained via lock() */ private $mNamedLocksHeld = []; /** @var array Map of (table name => 1) for TEMPORARY tables */ - private $mSessionTempTables = []; + protected $mSessionTempTables = []; /** @var IDatabase|null Lazy handle to the master DB this server replicates from */ private $lazyMasterHandle; - /** - * @since 1.21 - * @var resource File handle for upgrade - */ - protected $fileHandle = null; - - /** - * @since 1.22 - * @var string[] Process cache of VIEWs names in the database - */ - protected $allViews = null; - /** @var float UNIX timestamp */ protected $lastPing = 0.0; @@@ -254,11 -254,11 +254,11 @@@ $this->agent = str_replace( '/', '-', $params['agent'] ); $this->mFlags = $params['flags']; - if ( $this->mFlags & DBO_DEFAULT ) { + if ( $this->mFlags & self::DBO_DEFAULT ) { if ( $this->cliMode ) { - $this->mFlags &= ~DBO_TRX; + $this->mFlags &= ~self::DBO_TRX; } else { - $this->mFlags |= DBO_TRX; + $this->mFlags |= self::DBO_TRX; } } @@@ -272,7 -272,6 +272,7 @@@ $this->trxProfiler = $params['trxProfiler']; $this->connLogger = $params['connLogger']; $this->queryLogger = $params['queryLogger']; + $this->errorLogger = $params['errorLogger']; // Set initial dummy domain until open() sets the final DB/prefix $this->currentDomain = DatabaseDomain::newUnspecified(); @@@ -362,13 -361,13 +362,13 @@@ } else { $driver = $dbType; } - if ( $driver === false ) { + if ( $driver === false || $driver === '' ) { throw new InvalidArgumentException( __METHOD__ . " no viable database extension found for type '$dbType'" ); } $class = 'Database' . ucfirst( $driver ); - if ( class_exists( $class ) && is_subclass_of( $class, 'IDatabase' ) ) { + if ( class_exists( $class ) && is_subclass_of( $class, IDatabase::class ) ) { // Resolve some defaults for b/c $p['host'] = isset( $p['host'] ) ? $p['host'] : false; $p['user'] = isset( $p['user'] ) ? $p['user'] : false; @@@ -392,7 -391,7 +392,7 @@@ } if ( !isset( $p['errorLogger'] ) ) { $p['errorLogger'] = function ( Exception $e ) { - trigger_error( get_class( $e ) . ': ' . $e->getMessage(), E_WARNING ); + trigger_error( get_class( $e ) . ': ' . $e->getMessage(), E_USER_WARNING ); }; } @@@ -413,11 -412,9 +413,11 @@@ } public function bufferResults( $buffer = null ) { - $res = !$this->getFlag( DBO_NOBUFFER ); + $res = !$this->getFlag( self::DBO_NOBUFFER ); if ( $buffer !== null ) { - $buffer ? $this->clearFlag( DBO_NOBUFFER ) : $this->setFlag( DBO_NOBUFFER ); + $buffer + ? $this->clearFlag( self::DBO_NOBUFFER ) + : $this->setFlag( self::DBO_NOBUFFER ); } return $res; @@@ -436,11 -433,9 +436,11 @@@ * @return bool The previous value of the flag. */ protected function ignoreErrors( $ignoreErrors = null ) { - $res = $this->getFlag( DBO_IGNORE ); + $res = $this->getFlag( self::DBO_IGNORE ); if ( $ignoreErrors !== null ) { - $ignoreErrors ? $this->setFlag( DBO_IGNORE ) : $this->clearFlag( DBO_IGNORE ); + $ignoreErrors + ? $this->setFlag( self::DBO_IGNORE ) + : $this->clearFlag( self::DBO_IGNORE ); } return $res; @@@ -475,6 -470,15 +475,6 @@@ return $old; } - /** - * Set the filehandle to copy write statements to. - * - * @param resource $fh File handle - */ - public function setFileHandle( $fh ) { - $this->fileHandle = $fh; - } - public function getLBInfo( $name = null ) { if ( is_null( $name ) ) { return $this->mLBInfo; @@@ -504,7 -508,7 +504,7 @@@ * @see setLazyMasterHandle() * @since 1.27 */ - public function getLazyMasterHandle() { + protected function getLazyMasterHandle() { return $this->lazyMasterHandle; } @@@ -521,11 -525,11 +521,11 @@@ } public function doneWrites() { - return (bool)$this->mDoneWrites; + return (bool)$this->mLastWriteTime; } public function lastDoneWrites() { - return $this->mDoneWrites ?: false; + return $this->mLastWriteTime ?: false; } public function writesPending() { @@@ -618,11 -622,6 +618,11 @@@ return !!( $this->mFlags & $flag ); } + /** + * @param string $name Class field name + * @return mixed + * @deprecated Since 1.28 + */ public function getProperty( $name ) { return $this->$name; } @@@ -655,7 -654,7 +655,7 @@@ protected function installErrorHandler() { $this->mPHPError = false; $this->htmlErrors = ini_set( 'html_errors', '0' ); - set_error_handler( [ $this, 'connectionerrorLogger' ] ); + set_error_handler( [ $this, 'connectionErrorLogger' ] ); } /** @@@ -666,31 -665,21 +666,31 @@@ if ( $this->htmlErrors !== false ) { ini_set( 'html_errors', $this->htmlErrors ); } + + return $this->getLastPHPError(); + } + + /** + * @return string|bool Last PHP error for this DB (typically connection errors) + */ + protected function getLastPHPError() { if ( $this->mPHPError ) { $error = preg_replace( '!\[\]!', '', $this->mPHPError ); $error = preg_replace( '!^.*?:\s?(.*)$!', '$1', $error ); return $error; - } else { - return false; } + + return false; } /** + * This method should not be used outside of Database classes + * * @param int $errno * @param string $errstr */ - public function connectionerrorLogger( $errno, $errstr ) { + public function connectionErrorLogger( $errno, $errstr ) { $this->mPHPError = $errstr; } @@@ -747,7 -736,7 +747,7 @@@ */ abstract protected function closeConnection(); - function reportConnectionError( $error = 'Unknown error' ) { + public function reportConnectionError( $error = 'Unknown error' ) { $myError = $this->lastError(); if ( $myError ) { $error = $myError; @@@ -796,11 -785,8 +796,11 @@@ * @return bool */ protected function isTransactableQuery( $sql ) { - $verb = $this->getQueryVerb( $sql ); - return !in_array( $verb, [ 'BEGIN', 'COMMIT', 'ROLLBACK', 'SHOW', 'SET' ], true ); + return !in_array( + $this->getQueryVerb( $sql ), + [ 'BEGIN', 'COMMIT', 'ROLLBACK', 'SHOW', 'SET', 'CREATE', 'ALTER' ], + true + ); } /** @@@ -809,19 -795,16 +809,19 @@@ */ protected function registerTempTableOperation( $sql ) { if ( preg_match( - '/^(CREATE|DROP)\s+TEMPORARY\s+TABLE\s+(?:IF\s+NOT\s+EXISTS\s+)?[`"\']?(\w+)[`"\']?/i', + '/^CREATE\s+TEMPORARY\s+TABLE\s+(?:IF\s+NOT\s+EXISTS\s+)?[`"\']?(\w+)[`"\']?/i', $sql, $matches ) ) { - list( , $verb, $table ) = $matches; - if ( $verb === 'CREATE' ) { - $this->mSessionTempTables[$table] = 1; - } else { - unset( $this->mSessionTempTables[$table] ); - } + $this->mSessionTempTables[$matches[1]] = 1; + + return true; + } elseif ( preg_match( + '/^DROP\s+(?:TEMPORARY\s+)?TABLE\s+(?:IF\s+EXISTS\s+)?[`"\']?(\w+)[`"\']?/i', + $sql, + $matches + ) ) { + unset( $this->mSessionTempTables[$matches[1]] ); return true; } elseif ( preg_match( @@@ -846,15 -829,15 +846,15 @@@ throw new DBReadOnlyError( $this, "Database is read-only: $reason" ); } # Set a flag indicating that writes have been done - $this->mDoneWrites = microtime( true ); + $this->mLastWriteTime = microtime( true ); } // Add trace comment to the begin of the sql string, right after the operator. - // Or, for one-word queries (like "BEGIN" or COMMIT") add it to the end (bug 42598) + // Or, for one-word queries (like "BEGIN" or COMMIT") add it to the end (T44598) $commentedSql = preg_replace( '/\s|$/', " /* $fname {$this->agent} */ ", $sql, 1 ); # Start implicit transactions that wrap the request if DBO_TRX is enabled - if ( !$this->mTrxLevel && $this->getFlag( DBO_TRX ) + if ( !$this->mTrxLevel && $this->getFlag( self::DBO_TRX ) && $this->isTransactableQuery( $sql ) ) { $this->begin( __METHOD__ . " ($fname)", self::TRANSACTION_INTERNAL ); @@@ -868,7 -851,7 +868,7 @@@ $this->mServer, $this->mDBname, $this->mTrxShortId ); } - if ( $this->getFlag( DBO_DEBUG ) ) { + if ( $this->getFlag( self::DBO_DEBUG ) ) { $this->queryLogger->debug( "{$this->mDBname} {$commentedSql}" ); } @@@ -907,7 -890,7 +907,7 @@@ if ( false === $ret ) { # Deadlocks cause the entire transaction to abort, not just the statement. - # http://dev.mysql.com/doc/refman/5.7/en/innodb-error-handling.html + # https://dev.mysql.com/doc/refman/5.7/en/innodb-error-handling.html # https://www.postgresql.org/docs/9.1/static/explicit-locking.html if ( $this->wasDeadlock() ) { if ( $this->explicitTrxActive() || $priorWritesPending ) { @@@ -1029,8 -1012,8 +1029,8 @@@ private function handleSessionLoss() { $this->mTrxLevel = 0; - $this->mTrxIdleCallbacks = []; // bug 65263 - $this->mTrxPreCommitCallbacks = []; // bug 65263 + $this->mTrxIdleCallbacks = []; // T67263 + $this->mTrxPreCommitCallbacks = []; // T67263 $this->mSessionTempTables = []; $this->mNamedLocksHeld = []; try { @@@ -1127,9 -1110,9 +1127,9 @@@ * @param array $options Associative array of options to be turned into * an SQL query, valid keys are listed in the function. * @return array - * @see DatabaseBase::select() + * @see Database::select() */ - public function makeSelectOptions( $options ) { + protected function makeSelectOptions( $options ) { $preLimitTail = $postLimitTail = ''; $startOpts = ''; @@@ -1145,6 -1128,12 +1145,6 @@@ $preLimitTail .= $this->makeOrderBy( $options ); - // if (isset($options['LIMIT'])) { - // $tailOpts .= $this->limitResult('', $options['LIMIT'], - // isset($options['OFFSET']) ? $options['OFFSET'] - // : false); - // } - if ( isset( $noKeyOptions['FOR UPDATE'] ) ) { $postLimitTail .= ' FOR UPDATE'; } @@@ -1209,10 -1198,10 +1209,10 @@@ * * @param array $options Associative array of options * @return string - * @see DatabaseBase::select() + * @see Database::select() * @since 1.21 */ - public function makeGroupByWithHaving( $options ) { + protected function makeGroupByWithHaving( $options ) { $sql = ''; if ( isset( $options['GROUP BY'] ) ) { $gb = is_array( $options['GROUP BY'] ) @@@ -1235,10 -1224,10 +1235,10 @@@ * * @param array $options Associative array of options * @return string - * @see DatabaseBase::select() + * @see Database::select() * @since 1.21 */ - public function makeOrderBy( $options ) { + protected function makeOrderBy( $options ) { if ( isset( $options['ORDER BY'] ) ) { $ob = is_array( $options['ORDER BY'] ) ? implode( ',', $options['ORDER BY'] ) @@@ -1250,6 -1239,7 +1250,6 @@@ return ''; } - // See IDatabase::select for the docs for this function public function select( $table, $vars, $conds = '', $fname = __METHOD__, $options = [], $join_conds = [] ) { $sql = $this->selectSQLText( $table, $vars, $conds, $fname, $options, $join_conds ); @@@ -1268,24 -1258,19 +1268,24 @@@ $useIndexes = ( isset( $options['USE INDEX'] ) && is_array( $options['USE INDEX'] ) ) ? $options['USE INDEX'] : []; - $ignoreIndexes = ( isset( $options['IGNORE INDEX'] ) && is_array( $options['IGNORE INDEX'] ) ) + $ignoreIndexes = ( + isset( $options['IGNORE INDEX'] ) && + is_array( $options['IGNORE INDEX'] ) + ) ? $options['IGNORE INDEX'] : []; if ( is_array( $table ) ) { $from = ' FROM ' . - $this->tableNamesWithIndexClauseOrJOIN( $table, $useIndexes, $ignoreIndexes, $join_conds ); + $this->tableNamesWithIndexClauseOrJOIN( + $table, $useIndexes, $ignoreIndexes, $join_conds ); } elseif ( $table != '' ) { if ( $table[0] == ' ' ) { $from = ' FROM ' . $table; } else { $from = ' FROM ' . - $this->tableNamesWithIndexClauseOrJOIN( [ $table ], $useIndexes, $ignoreIndexes, [] ); + $this->tableNamesWithIndexClauseOrJOIN( + [ $table ], $useIndexes, $ignoreIndexes, [] ); } } else { $from = ''; @@@ -1298,8 -1283,7 +1298,8 @@@ if ( is_array( $conds ) ) { $conds = $this->makeList( $conds, self::LIST_AND ); } - $sql = "SELECT $startOpts $vars $from $useIndex $ignoreIndex WHERE $conds $preLimitTail"; + $sql = "SELECT $startOpts $vars $from $useIndex $ignoreIndex " . + "WHERE $conds $preLimitTail"; } else { $sql = "SELECT $startOpts $vars $from $useIndex $ignoreIndex $preLimitTail"; } @@@ -1356,10 -1340,7 +1356,10 @@@ ) { $rows = 0; $sql = $this->selectSQLText( $tables, '1', $conds, $fname, $options, $join_conds ); - $res = $this->query( "SELECT COUNT(*) AS rowcount FROM ($sql) tmp_count", $fname ); + // The identifier quotes is primarily for MSSQL. + $rowCountCol = $this->addIdentifierQuotes( "rowcount" ); + $tableName = $this->addIdentifierQuotes( "tmp_count" ); + $res = $this->query( "SELECT COUNT(*) AS $rowCountCol FROM ($sql) $tableName", $fname ); if ( $res ) { $row = $this->fetchRow( $res ); @@@ -1419,14 -1400,10 +1419,14 @@@ } public function tableExists( $table, $fname = __METHOD__ ) { + $tableRaw = $this->tableName( $table, 'raw' ); + if ( isset( $this->mSessionTempTables[$tableRaw] ) ) { + return true; // already known to exist + } + $table = $this->tableName( $table ); - $old = $this->ignoreErrors( true ); - $res = $this->query( "SELECT 1 FROM $table LIMIT 1", $fname ); - $this->ignoreErrors( $old ); + $ignoreErrors = true; + $res = $this->query( "SELECT 1 FROM $table LIMIT 1", $fname, $ignoreErrors ); return (bool)$res; } @@@ -1442,7 -1419,7 +1442,7 @@@ } /** - * Helper for DatabaseBase::insert(). + * Helper for Database::insert(). * * @param array $options * @return string @@@ -1504,7 -1481,7 +1504,7 @@@ } /** - * Make UPDATE options array for DatabaseBase::makeUpdateOptions + * Make UPDATE options array for Database::makeUpdateOptions * * @param array $options * @return array @@@ -1524,9 -1501,9 +1524,9 @@@ } /** - * Make UPDATE options for the DatabaseBase::update function + * Make UPDATE options for the Database::update function * - * @param array $options The options passed to DatabaseBase::update + * @param array $options The options passed to Database::update * @return string */ protected function makeUpdateOptions( $options ) { @@@ -1535,7 -1512,7 +1535,7 @@@ return implode( ' ', $opts ); } - function update( $table, $values, $conds, $fname = __METHOD__, $options = [] ) { + public function update( $table, $values, $conds, $fname = __METHOD__, $options = [] ) { $table = $this->tableName( $table ); $opts = $this->makeUpdateOptions( $options ); $sql = "UPDATE $opts $table SET " . $this->makeList( $values, self::LIST_SET ); @@@ -1544,7 -1521,7 +1544,7 @@@ $sql .= " WHERE " . $this->makeList( $conds, self::LIST_AND ); } - return $this->query( $sql, $fname ); + return (bool)$this->query( $sql, $fname ); } public function makeList( $a, $mode = self::LIST_COMMA ) { @@@ -1646,6 -1623,14 +1646,6 @@@ } } - /** - * Return aggregated value alias - * - * @param array $valuedata - * @param string $valuename - * - * @return string - */ public function aggregateValue( $valuedata, $valuename = 'value' ) { return $valuename; } @@@ -1674,6 -1659,11 +1674,6 @@@ return '(' . $this->selectSQLText( $table, $fld, $conds, null, [], $join_conds ) . ')'; } - /** - * @param string $field Field or column to cast - * @return string - * @since 1.28 - */ public function buildStringCast( $field ) { return $field; } @@@ -1695,6 -1685,25 +1695,6 @@@ return $this->mServer; } - /** - * Format a table name ready for use in constructing an SQL query - * - * This does two important things: it quotes the table names to clean them up, - * and it adds a table prefix if only given a table name with no quotes. - * - * All functions of this object which require a table name call this function - * themselves. Pass the canonical name to such functions. This is only needed - * when calling query() directly. - * - * @note This function does not sanitize user input. It is not safe to use - * this function to escape user input. - * @param string $name Database table name - * @param string $format One of: - * quoted - Automatically pass the table name through addIdentifierQuotes() - * so that it can be used in a query. - * raw - Do not add identifier quotes to the table name - * @return string Full database name - */ public function tableName( $name, $format = 'quoted' ) { # 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 @@@ -1726,9 -1735,9 +1726,9 @@@ } elseif ( count( $dbDetails ) == 2 ) { list( $database, $table ) = $dbDetails; # We don't want any prefix added in this case + $prefix = ''; # In dbs that support it, $database may actually be the schema # but that doesn't affect any of the functionality here - $prefix = ''; $schema = ''; } else { list( $table ) = $dbDetails; @@@ -1750,37 -1759,42 +1750,37 @@@ # 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 ); } - # Quote $schema and merge it with the table name if needed - if ( strlen( $schema ) ) { - if ( $format == 'quoted' && !$this->isQuotedIdentifier( $schema ) ) { - $schema = $this->addIdentifierQuotes( $schema ); - } - $tableName = $schema . '.' . $tableName; - } - - # Quote $database and merge it with the table name if needed - if ( $database !== '' ) { - if ( $format == 'quoted' && !$this->isQuotedIdentifier( $database ) ) { - $database = $this->addIdentifierQuotes( $database ); - } - $tableName = $database . '.' . $tableName; - } + # Quote $schema and $database and merge them with the table name if needed + $tableName = $this->prependDatabaseOrSchema( $schema, $tableName, $format ); + $tableName = $this->prependDatabaseOrSchema( $database, $tableName, $format ); return $tableName; } /** - * Fetch a number of table names into an array - * This is handy when you need to construct SQL for joins - * - * Example: - * extract( $dbr->tableNames( 'user', 'watchlist' ) ); - * $sql = "SELECT wl_namespace,wl_title FROM $watchlist,$user - * WHERE wl_user=user_id AND wl_user=$nameWithQuotes"; - * - * @return array + * @param string|null $namespace Database or schema + * @param string $relation Name of table, view, sequence, etc... + * @param string $format One of (raw, quoted) + * @return string Relation name with quoted and merged $namespace as needed */ + private function prependDatabaseOrSchema( $namespace, $relation, $format ) { + if ( strlen( $namespace ) ) { + if ( $format === 'quoted' && !$this->isQuotedIdentifier( $namespace ) ) { + $namespace = $this->addIdentifierQuotes( $namespace ); + } + $relation = $namespace . '.' . $relation; + } + + return $relation; + } + public function tableNames() { $inArray = func_get_args(); $retVal = []; @@@ -1792,6 -1806,17 +1792,6 @@@ return $retVal; } - /** - * Fetch a number of table names into an zero-indexed numerical array - * This is handy when you need to construct SQL for joins - * - * Example: - * list( $user, $watchlist ) = $dbr->tableNamesN( 'user', 'watchlist' ); - * $sql = "SELECT wl_namespace,wl_title FROM $watchlist,$user - * WHERE wl_user=user_id AND wl_user=$nameWithQuotes"; - * - * @return array - */ public function tableNamesN() { $inArray = func_get_args(); $retVal = []; @@@ -1811,7 -1836,7 +1811,7 @@@ * @param string|bool $alias Alias (optional) * @return string SQL name for aliased table. Will not alias a table to its own name */ - public function tableNameWithAlias( $name, $alias = false ) { + protected function tableNameWithAlias( $name, $alias = false ) { if ( !$alias || $alias == $name ) { return $this->tableName( $name ); } else { @@@ -1825,7 -1850,7 +1825,7 @@@ * @param array $tables [ [alias] => table ] * @return string[] See tableNameWithAlias() */ - public function tableNamesWithAlias( $tables ) { + protected function tableNamesWithAlias( $tables ) { $retval = []; foreach ( $tables as $alias => $table ) { if ( is_numeric( $alias ) ) { @@@ -1845,7 -1870,7 +1845,7 @@@ * @param string|bool $alias Alias (optional) * @return string SQL name for aliased field. Will not alias a field to its own name */ - public function fieldNameWithAlias( $name, $alias = false ) { + protected function fieldNameWithAlias( $name, $alias = false ) { if ( !$alias || (string)$alias === (string)$name ) { return $name; } else { @@@ -1859,7 -1884,7 +1859,7 @@@ * @param array $fields [ [alias] => field ] * @return string[] See fieldNameWithAlias() */ - public function fieldNamesWithAlias( $fields ) { + protected function fieldNamesWithAlias( $fields ) { $retval = []; foreach ( $fields as $alias => $field ) { if ( is_numeric( $alias ) ) { @@@ -1907,8 -1932,7 +1907,8 @@@ } } if ( isset( $ignore_index[$alias] ) ) { // has IGNORE INDEX? - $ignore = $this->ignoreIndexClause( implode( ',', (array)$ignore_index[$alias] ) ); + $ignore = $this->ignoreIndexClause( + implode( ',', (array)$ignore_index[$alias] ) ); if ( $ignore != '' ) { $tableClause .= ' ' . $ignore; } @@@ -1957,7 -1981,18 +1957,18 @@@ * @return string */ protected function indexName( $index ) { - return $index; + // Backwards-compatibility hack + $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; + } } public function addQuotes( $s ) { @@@ -2225,6 -2260,13 +2236,6 @@@ $this->query( $sql, $fname ); } - /** - * Returns the size of a text field, or -1 for "unlimited" - * - * @param string $table - * @param string $field - * @return int - */ public function textFieldSize( $table, $field ) { $table = $this->tableName( $table ); $sql = "SHOW COLUMNS FROM $table LIKE \"$field\";"; @@@ -2299,7 -2341,7 +2310,7 @@@ return $this->insert( $destTable, $rows, $fname, $insertOptions ); } - public function nativeInsertSelect( $destTable, $srcTable, $varMap, $conds, + protected function nativeInsertSelect( $destTable, $srcTable, $varMap, $conds, $fname = __METHOD__, $insertOptions = [], $selectOptions = [] ) { @@@ -2319,13 -2361,12 +2330,13 @@@ $selectOptions ); if ( is_array( $srcTable ) ) { - $srcTable = implode( ',', array_map( [ &$this, 'tableName' ], $srcTable ) ); + $srcTable = implode( ',', array_map( [ $this, 'tableName' ], $srcTable ) ); } else { $srcTable = $this->tableName( $srcTable ); } - $sql = "INSERT $insertOptions INTO $destTable (" . implode( ',', array_keys( $varMap ) ) . ')' . + $sql = "INSERT $insertOptions" . + " INTO $destTable (" . implode( ',', array_keys( $varMap ) ) . ')' . " SELECT $startOpts " . implode( ',', $varMap ) . " FROM $srcTable $useIndex $ignoreIndex "; @@@ -2362,8 -2403,7 +2373,8 @@@ */ public function limitResult( $sql, $limit, $offset = false ) { if ( !is_numeric( $limit ) ) { - throw new DBUnexpectedError( $this, "Invalid non-numeric limit passed to limitResult()\n" ); + throw new DBUnexpectedError( $this, + "Invalid non-numeric limit passed to limitResult()\n" ); } return "$sql LIMIT " @@@ -2414,15 -2454,40 +2425,15 @@@ } /** - * Determines if the given query error was a connection drop - * STUB + * Do not use this method outside of Database/DBError classes * * @param integer|string $errno - * @return bool + * @return bool Whether the given query error was a connection drop */ public function wasConnectionError( $errno ) { return false; } - /** - * Perform a deadlock-prone transaction. - * - * This function invokes a callback function to perform a set of write - * queries. If a deadlock occurs during the processing, the transaction - * will be rolled back and the callback function will be called again. - * - * Avoid using this method outside of Job or Maintenance classes. - * - * Usage: - * $dbw->deadlockLoop( callback, ... ); - * - * Extra arguments are passed through to the specified callback function. - * This method requires that no transactions are already active to avoid - * causing premature commits or exceptions. - * - * Returns whatever the callback function returned on its successful, - * iteration, or false on error, for example if the retry limit was - * reached. - * - * @return mixed - * @throws DBUnexpectedError - * @throws Exception - */ public function deadlockLoop() { $args = func_get_args(); $function = array_shift( $args ); @@@ -2464,7 -2529,7 +2475,7 @@@ return 0; } - public function getSlavePos() { + public function getReplicaPos() { # Stub return false; } @@@ -2542,7 -2607,7 +2553,7 @@@ return; } - $autoTrx = $this->getFlag( DBO_TRX ); // automatic begin() enabled? + $autoTrx = $this->getFlag( self::DBO_TRX ); // automatic begin() enabled? /** @var Exception $e */ $e = null; // first exception do { // callbacks may add callbacks :) @@@ -2555,12 -2620,12 +2566,12 @@@ foreach ( $callbacks as $callback ) { try { list( $phpCallback ) = $callback; - $this->clearFlag( DBO_TRX ); // make each query its own transaction + $this->clearFlag( self::DBO_TRX ); // make each query its own transaction call_user_func_array( $phpCallback, [ $trigger ] ); if ( $autoTrx ) { - $this->setFlag( DBO_TRX ); // restore automatic begin() + $this->setFlag( self::DBO_TRX ); // restore automatic begin() } else { - $this->clearFlag( DBO_TRX ); // restore auto-commit + $this->clearFlag( self::DBO_TRX ); // restore auto-commit } } catch ( Exception $ex ) { call_user_func( $this->errorLogger, $ex ); @@@ -2644,7 -2709,7 +2655,7 @@@ $this->begin( $fname, self::TRANSACTION_INTERNAL ); // If DBO_TRX is set, a series of startAtomic/endAtomic pairs will result // in all changes being in one transaction to keep requests transactional. - if ( !$this->getFlag( DBO_TRX ) ) { + if ( !$this->getFlag( self::DBO_TRX ) ) { $this->mTrxAutomaticAtomic = true; } } @@@ -2696,7 -2761,7 +2707,7 @@@ $this->queryLogger->error( $msg ); return; // join the main transaction set } - } elseif ( $this->getFlag( DBO_TRX ) && $mode !== self::TRANSACTION_INTERNAL ) { + } elseif ( $this->getFlag( self::DBO_TRX ) && $mode !== self::TRANSACTION_INTERNAL ) { // @TODO: make this an exception at some point $msg = "$fname: Implicit transaction expected (DBO_TRX set)."; $this->queryLogger->error( $msg ); @@@ -2710,6 -2775,7 +2721,6 @@@ $this->mTrxTimestamp = microtime( true ); $this->mTrxFname = $fname; $this->mTrxDoneWrites = false; - $this->mTrxAutomatic = ( $mode === self::TRANSACTION_INTERNAL ); $this->mTrxAutomaticAtomic = false; $this->mTrxAtomicLevels = []; $this->mTrxShortId = sprintf( '%06x', mt_rand( 0, 0xffffff ) ); @@@ -2723,16 -2789,12 +2734,16 @@@ // as lag itself just to be safe $status = $this->getApproximateLagStatus(); $this->mTrxReplicaLag = $status['lag'] + ( microtime( true ) - $status['since'] ); + // T147697: make explicitTrxActive() return true until begin() finishes. This way, no + // caller will think its OK to muck around with the transaction just because startAtomic() + // has not yet completed (e.g. setting mTrxAtomicLevels). + $this->mTrxAutomatic = ( $mode === self::TRANSACTION_INTERNAL ); } /** * Issues the BEGIN command to the database server. * - * @see DatabaseBase::begin() + * @see Database::begin() * @param string $fname */ protected function doBegin( $fname ) { @@@ -2761,8 -2823,7 +2772,8 @@@ } } else { if ( !$this->mTrxLevel ) { - $this->queryLogger->error( "$fname: No transaction to commit, something got out of sync." ); + $this->queryLogger->error( + "$fname: No transaction to commit, something got out of sync." ); return; // nothing to do } elseif ( $this->mTrxAutomatic ) { // @TODO: make this an exception at some point @@@ -2779,7 -2840,7 +2790,7 @@@ $writeTime = $this->pendingWriteQueryDuration( self::ESTIMATE_DB_APPLY ); $this->doCommit( $fname ); if ( $this->mTrxDoneWrites ) { - $this->mDoneWrites = microtime( true ); + $this->mLastWriteTime = microtime( true ); $this->trxProfiler->transactionWritingOut( $this->mServer, $this->mDBname, $this->mTrxShortId, $writeTime ); } @@@ -2791,7 -2852,7 +2802,7 @@@ /** * Issues the COMMIT command to the database server. * - * @see DatabaseBase::commit() + * @see Database::commit() * @param string $fname */ protected function doCommit( $fname ) { @@@ -2811,7 -2872,7 +2822,7 @@@ $this->queryLogger->error( "$fname: No transaction to rollback, something got out of sync." ); return; // nothing to do - } elseif ( $this->getFlag( DBO_TRX ) ) { + } elseif ( $this->getFlag( self::DBO_TRX ) ) { throw new DBUnexpectedError( $this, "$fname: Expected mass rollback of all peer databases (DBO_TRX set)." @@@ -2838,7 -2899,7 +2849,7 @@@ /** * Issues the ROLLBACK command to the database server. * - * @see DatabaseBase::rollback() + * @see Database::rollback() * @param string $fname */ protected function doRollback( $fname ) { @@@ -2856,7 -2917,7 +2867,7 @@@ $fnames = implode( ', ', $this->pendingWriteAndCallbackCallers() ); throw new DBUnexpectedError( $this, - "$fname: Cannot COMMIT to clear snapshot because writes are pending ($fnames)." + "$fname: Cannot flush snapshot because writes are pending ($fnames)." ); } @@@ -2867,22 -2928,69 +2878,22 @@@ return $this->mTrxLevel && ( $this->mTrxAtomicLevels || !$this->mTrxAutomatic ); } - /** - * Creates a new table with structure copied from existing table - * Note that unlike most database abstraction functions, this function does not - * automatically append database prefix, because it works at a lower - * abstraction level. - * The table names passed to this function shall not be quoted (this - * function calls addIdentifierQuotes when needed). - * - * @param string $oldName Name of table whose structure should be copied - * @param string $newName Name of table to be created - * @param bool $temporary Whether the new table should be temporary - * @param string $fname Calling function name - * @throws RuntimeException - * @return bool True if operation was successful - */ - public function duplicateTableStructure( $oldName, $newName, $temporary = false, - $fname = __METHOD__ + public function duplicateTableStructure( + $oldName, $newName, $temporary = false, $fname = __METHOD__ ) { throw new RuntimeException( __METHOD__ . ' is not implemented in descendant class' ); } - function listTables( $prefix = null, $fname = __METHOD__ ) { + public function listTables( $prefix = null, $fname = __METHOD__ ) { throw new RuntimeException( __METHOD__ . ' is not implemented in descendant class' ); } - /** - * Reset the views process cache set by listViews() - * @since 1.22 - */ - final public function clearViewsCache() { - $this->allViews = null; - } - - /** - * Lists all the VIEWs in the database - * - * For caching purposes the list of all views should be stored in - * $this->allViews. The process cache can be cleared with clearViewsCache() - * - * @param string $prefix Only show VIEWs with this prefix, eg. unit_test_ - * @param string $fname Name of calling function - * @throws RuntimeException - * @return array - * @since 1.22 - */ public function listViews( $prefix = null, $fname = __METHOD__ ) { throw new RuntimeException( __METHOD__ . ' is not implemented in descendant class' ); } - /** - * Differentiates between a TABLE and a VIEW - * - * @param string $name Name of the database-structure to test. - * @throws RuntimeException - * @return bool - * @since 1.22 - */ - public function isView( $name ) { - throw new RuntimeException( __METHOD__ . ' is not implemented in descendant class' ); - } - public function timestamp( $ts = 0 ) { - $t = new ConvertableTimestamp( $ts ); + $t = new ConvertibleTimestamp( $ts ); // Let errors bubble up to avoid putting garbage in the DB return $t->getTimestamp( TS_MW ); } @@@ -2900,7 -3008,7 +2911,7 @@@ * necessary. Boolean values are passed through as is, to indicate success * of write queries or failure. * - * Once upon a time, DatabaseBase::query() returned a bare MySQL result + * Once upon a time, Database::query() returned a bare MySQL result * resource, and it was necessary to call this function to convert it to * a wrapper. Nowadays, raw database objects are never exposed to external * callers, so this is unnecessary in external code. @@@ -2931,7 -3039,7 +2942,7 @@@ } // This will reconnect if possible or return false if not - $this->clearFlag( DBO_TRX, self::REMEMBER_PRIOR ); + $this->clearFlag( self::DBO_TRX, self::REMEMBER_PRIOR ); $ok = ( $this->query( self::PING_QUERY, __METHOD__, true ) !== false ); $this->restoreFlags( self::RESTORE_PRIOR ); @@@ -2975,7 -3083,7 +2986,7 @@@ * @return array|null ('lag': seconds or false on error, 'since': UNIX timestamp of BEGIN) * @since 1.27 */ - public function getTransactionLagStatus() { + protected function getTransactionLagStatus() { return $this->mTrxLevel ? [ 'lag' => $this->mTrxReplicaLag, 'since' => $this->trxTimestamp() ] : null; @@@ -2987,7 -3095,7 +2998,7 @@@ * @return array ('lag': seconds or false on error, 'since': UNIX timestamp of estimate) * @since 1.27 */ - public function getApproximateLagStatus() { + protected function getApproximateLagStatus() { return [ 'lag' => $this->getLBInfo( 'replica' ) ? $this->getLag() : 0, 'since' => microtime( true ) @@@ -3033,7 -3141,7 +3044,7 @@@ return 0; } - function maxListLen() { + public function maxListLen() { return 0; } @@@ -3051,12 -3159,24 +3062,12 @@@ public function setSessionOptions( array $options ) { } - /** - * Read and execute SQL commands from a file. - * - * Returns true on success, error string or exception on failure (depending - * on object's error ignore settings). - * - * @param string $filename File name to open - * @param bool|callable $lineCallback Optional function called before reading each line - * @param bool|callable $resultCallback Optional function called for each MySQL result - * @param bool|string $fname Calling function name or false if name should be - * generated dynamically using $filename - * @param bool|callable $inputCallback Optional function called for each - * complete line sent - * @return bool|string - * @throws Exception - */ public function sourceFile( - $filename, $lineCallback = false, $resultCallback = false, $fname = false, $inputCallback = false + $filename, + callable $lineCallback = null, + callable $resultCallback = null, + $fname = false, + callable $inputCallback = null ) { MediaWiki\suppressWarnings(); $fp = fopen( $filename, 'r' ); @@@ -3071,8 -3191,7 +3082,8 @@@ } try { - $error = $this->sourceStream( $fp, $lineCallback, $resultCallback, $fname, $inputCallback ); + $error = $this->sourceStream( + $fp, $lineCallback, $resultCallback, $fname, $inputCallback ); } catch ( Exception $e ) { fclose( $fp ); throw $e; @@@ -3087,12 -3206,21 +3098,12 @@@ $this->mSchemaVars = $vars; } - /** - * Read and execute commands from an open file handle. - * - * Returns true on success, error string or exception on failure (depending - * on object's error ignore settings). - * - * @param resource $fp File handle - * @param bool|callable $lineCallback Optional function called before reading each query - * @param bool|callable $resultCallback Optional function called for each MySQL result - * @param string $fname Calling function name - * @param bool|callable $inputCallback Optional function called for each complete query sent - * @return bool|string - */ - public function sourceStream( $fp, $lineCallback = false, $resultCallback = false, - $fname = __METHOD__, $inputCallback = false + public function sourceStream( + $fp, + callable $lineCallback = null, + callable $resultCallback = null, + $fname = __METHOD__, + callable $inputCallback = null ) { $cmd = ''; @@@ -3122,7 -3250,7 +3133,7 @@@ if ( $done || feof( $fp ) ) { $cmd = $this->replaceVars( $cmd ); - if ( ( $inputCallback && call_user_func( $inputCallback, $cmd ) ) || !$inputCallback ) { + if ( !$inputCallback || call_user_func( $inputCallback, $cmd ) ) { $res = $this->query( $cmd, $fname ); if ( $resultCallback ) { @@@ -3145,15 -3273,14 +3156,15 @@@ /** * Called by sourceStream() to check if we've reached a statement end * - * @param string $sql SQL assembled so far - * @param string $newLine New line about to be added to $sql + * @param string &$sql SQL assembled so far + * @param string &$newLine New line about to be added to $sql * @return bool Whether $newLine contains end of the statement */ public function streamStatementEnd( &$sql, &$newLine ) { if ( $this->delimiter ) { $prev = $newLine; - $newLine = preg_replace( '/' . preg_quote( $this->delimiter, '/' ) . '$/', '', $newLine ); + $newLine = preg_replace( + '/' . preg_quote( $this->delimiter, '/' ) . '$/', '', $newLine ); if ( $newLine != $prev ) { return true; } @@@ -3262,7 -3389,7 +3273,7 @@@ $fnames = implode( ', ', $this->pendingWriteAndCallbackCallers() ); throw new DBUnexpectedError( $this, - "$fname: Cannot COMMIT to clear snapshot because writes are pending ($fnames)." + "$fname: Cannot flush pre-lock snapshot because writes are pending ($fnames)." ); } @@@ -3349,7 -3476,13 +3360,7 @@@ return 'infinity'; } - try { - $t = new ConvertableTimestamp( $expiry ); - - return $t->getTimestamp( $format ); - } catch ( TimestampException $e ) { - return false; - } + return ConvertibleTimestamp::convert( $format, $expiry ); } public function setBigSelects( $value = true ) { @@@ -3381,28 -3514,6 +3392,28 @@@ return true; } + /** + * Get the underlying binding handle, mConn + * + * Makes sure that mConn is set (disconnects and ping() failure can unset it). + * This catches broken callers than catch and ignore disconnection exceptions. + * Unlike checking isOpen(), this is safe to call inside of open(). + * + * @return resource|object + * @throws DBUnexpectedError + * @since 1.26 + */ + protected function getBindingHandle() { + if ( !$this->mConn ) { + throw new DBUnexpectedError( + $this, + 'DB connection was already closed or the connection dropped.' + ); + } + + return $this->mConn; + } + /** * @since 1.19 * @return string @@@ -3417,7 -3528,7 +3428,7 @@@ */ public function __clone() { $this->connLogger->warning( - "Cloning " . get_class( $this ) . " is not recomended; forking connection:\n" . + "Cloning " . static::class . " is not recomended; forking connection:\n" . ( new RuntimeException() )->getTraceAsString() ); @@@ -3425,8 -3536,7 +3436,8 @@@ // Open a new connection resource without messing with the old one $this->mOpened = false; $this->mConn = false; - $this->mTrxLevel = 0; // no trx anymore + $this->mTrxEndCallbacks = []; // don't copy + $this->handleSessionLoss(); // no trx or locks anymore $this->open( $this->mServer, $this->mUser, $this->mPassword, $this->mDBname ); $this->lastPing = microtime( true ); } @@@ -3457,15 -3567,10 +3468,15 @@@ } if ( $this->mConn ) { - // Avoid connection leaks for sanity + // Avoid connection leaks for sanity. Normally, resources close at script completion. + // The connection might already be closed in zend/hhvm by now, so suppress warnings. + \MediaWiki\suppressWarnings(); $this->closeConnection(); + \MediaWiki\restoreWarnings(); $this->mConn = false; $this->mOpened = false; } } } + +class_alias( Database::class, 'DatabaseBase' ); diff --combined includes/libs/rdbms/database/DatabaseSqlite.php index 090ce8eeae,2281f23ec8..2a5deb63f9 --- a/includes/libs/rdbms/database/DatabaseSqlite.php +++ b/includes/libs/rdbms/database/DatabaseSqlite.php @@@ -21,26 -21,26 +21,26 @@@ * @file * @ingroup Database */ +use Wikimedia\Rdbms\Blob; +use Wikimedia\Rdbms\SQLiteField; +use Wikimedia\Rdbms\ResultWrapper; /** * @ingroup Database */ -class DatabaseSqlite extends DatabaseBase { +class DatabaseSqlite extends Database { /** @var bool Whether full text is enabled */ private static $fulltextEnabled = null; /** @var string Directory */ protected $dbDir; - /** @var string File name for SQLite database file */ protected $dbPath; - /** @var string Transaction mode */ protected $trxMode; /** @var int The number of rows affected as an integer */ protected $mAffectedRows; - /** @var resource */ protected $mLastResult; @@@ -117,7 -117,7 +117,7 @@@ $p['schema'] = false; $p['tablePrefix'] = ''; - return DatabaseBase::factory( 'sqlite', $p ); + return Database::factory( 'sqlite', $p ); } /** @@@ -145,7 -145,7 +145,7 @@@ * @param string $dbName * * @throws DBConnectionError - * @return PDO + * @return bool */ function open( $server, $user, $pass, $dbName ) { $this->close(); @@@ -156,7 -156,7 +156,7 @@@ } $this->openFile( $fileName ); - return $this->mConn; + return (bool)$this->mConn; } /** @@@ -171,7 -171,7 +171,7 @@@ $this->dbPath = $fileName; try { - if ( $this->mFlags & DBO_PERSISTENT ) { + if ( $this->mFlags & self::DBO_PERSISTENT ) { $this->mConn = new PDO( "sqlite:$fileName", '', '', [ PDO::ATTR_PERSISTENT => true ] ); } else { @@@ -199,10 -199,6 +199,10 @@@ return false; } + public function selectDB( $db ) { + return false; // doesn't make sense + } + /** * @return string SQLite DB file path * @since 1.25 @@@ -271,7 -267,7 +271,7 @@@ } /** - * Attaches external database to our connection, see http://sqlite.org/lang_attach.html + * Attaches external database to our connection, see https://sqlite.org/lang_attach.html * for details. * * @param string $name Database name to be used in queries like @@@ -430,6 -426,16 +430,16 @@@ return str_replace( '"', '', parent::tableName( $name, $format ) ); } + /** + * Index names have DB scope + * + * @param string $index + * @return string + */ + protected function indexName( $index ) { + return $index; + } + /** * This must be called after nextSequenceVal * @@@ -498,12 -504,15 +508,12 @@@ * @param string $table * @param string $index * @param string $fname - * @return array + * @return array|false */ function indexInfo( $table, $index, $fname = __METHOD__ ) { $sql = 'PRAGMA index_info(' . $this->addQuotes( $this->indexName( $index ) ) . ')'; $res = $this->query( $sql, $fname ); - if ( !$res ) { - return null; - } - if ( $res->numRows() == 0 ) { + if ( !$res || $res->numRows() == 0 ) { return false; } $info = []; @@@ -668,7 -677,7 +678,7 @@@ } /** - * @param string $sqls + * @param string[] $sqls * @param bool $all Whether to "UNION ALL" or not * @return string */