'BitmapHandler' => __DIR__ . '/includes/media/Bitmap.php',
'BitmapHandler_ClientOnly' => __DIR__ . '/includes/media/Bitmap_ClientOnly.php',
'BitmapMetadataHandler' => __DIR__ . '/includes/media/BitmapMetadataHandler.php',
- 'Blob' => __DIR__ . '/includes/db/DatabaseUtility.php',
+ 'Blob' => __DIR__ . '/includes/libs/rdbms/encasing/Blob.php',
'Block' => __DIR__ . '/includes/Block.php',
'BlockLevelPass' => __DIR__ . '/includes/parser/BlockLevelPass.php',
'BlockListPager' => __DIR__ . '/includes/specials/pagers/BlockListPager.php',
'DBAccessBase' => __DIR__ . '/includes/dao/DBAccessBase.php',
'DBAccessError' => __DIR__ . '/includes/libs/rdbms/exception/DBError.php',
'DBAccessObjectUtils' => __DIR__ . '/includes/dao/DBAccessObjectUtils.php',
- 'DBConnRef' => __DIR__ . '/includes/libs/rdbms/database/RBConnRef.php',
+ 'DBConnRef' => __DIR__ . '/includes/libs/rdbms/database/DBConnRef.php',
'DBConnectionError' => __DIR__ . '/includes/libs/rdbms/exception/DBError.php',
'DBError' => __DIR__ . '/includes/libs/rdbms/exception/DBError.php',
'DBExpectedError' => __DIR__ . '/includes/libs/rdbms/exception/DBError.php',
'DBFileJournal' => __DIR__ . '/includes/filebackend/filejournal/DBFileJournal.php',
'DBLockManager' => __DIR__ . '/includes/filebackend/lockmanager/DBLockManager.php',
- 'DBMasterPos' => __DIR__ . '/includes/db/DatabaseUtility.php',
+ 'DBMasterPos' => __DIR__ . '/includes/libs/rdbms/database/position/DBMasterPos.php',
'DBQueryError' => __DIR__ . '/includes/libs/rdbms/exception/DBError.php',
'DBReadOnlyError' => __DIR__ . '/includes/libs/rdbms/exception/DBError.php',
'DBReplicationWaitError' => __DIR__ . '/includes/libs/rdbms/exception/DBError.php',
'FakeAuthTemplate' => __DIR__ . '/includes/specialpage/LoginSignupSpecialPage.php',
'FakeConverter' => __DIR__ . '/languages/FakeConverter.php',
'FakeMaintenance' => __DIR__ . '/maintenance/Maintenance.php',
- 'FakeResultWrapper' => __DIR__ . '/includes/db/DatabaseUtility.php',
+ 'FakeResultWrapper' => __DIR__ . '/includes/libs/rdbms/database/resultwrapper/FakeResultWrapper.php',
'FatalError' => __DIR__ . '/includes/exception/FatalError.php',
'FauxRequest' => __DIR__ . '/includes/FauxRequest.php',
'FauxResponse' => __DIR__ . '/includes/WebResponse.php',
'FeedUtils' => __DIR__ . '/includes/FeedUtils.php',
'FetchText' => __DIR__ . '/maintenance/fetchText.php',
'FewestrevisionsPage' => __DIR__ . '/includes/specials/SpecialFewestrevisions.php',
- 'Field' => __DIR__ . '/includes/db/DatabaseUtility.php',
+ 'Field' => __DIR__ . '/includes/libs/rdbms/field/Field.php',
'File' => __DIR__ . '/includes/filerepo/file/File.php',
'FileAwareNodeVisitor' => __DIR__ . '/maintenance/findDeprecated.php',
'FileBackend' => __DIR__ . '/includes/filebackend/FileBackend.php',
'LegacyLogFormatter' => __DIR__ . '/includes/logging/LogFormatter.php',
'License' => __DIR__ . '/includes/Licenses.php',
'Licenses' => __DIR__ . '/includes/Licenses.php',
- 'LikeMatch' => __DIR__ . '/includes/db/DatabaseUtility.php',
+ 'LikeMatch' => __DIR__ . '/includes/libs/rdbms/encasing/LikeMatch.php',
'LinkBatch' => __DIR__ . '/includes/cache/LinkBatch.php',
'LinkCache' => __DIR__ . '/includes/cache/LinkCache.php',
'LinkFilter' => __DIR__ . '/includes/LinkFilter.php',
'MoveLogFormatter' => __DIR__ . '/includes/logging/MoveLogFormatter.php',
'MovePage' => __DIR__ . '/includes/MovePage.php',
'MovePageForm' => __DIR__ . '/includes/specials/SpecialMovepage.php',
- 'MssqlBlob' => __DIR__ . '/includes/db/DatabaseMssql.php',
- 'MssqlField' => __DIR__ . '/includes/db/DatabaseMssql.php',
+ 'MssqlBlob' => __DIR__ . '/includes/libs/rdbms/encasing/MssqlBlob.php',
+ 'MssqlField' => __DIR__ . '/includes/libs/rdbms/field/MssqlField.php',
'MssqlInstaller' => __DIR__ . '/includes/installer/MssqlInstaller.php',
- 'MssqlResultWrapper' => __DIR__ . '/includes/db/DatabaseMssql.php',
+ 'MssqlResultWrapper' => __DIR__ . '/includes/libs/rdbms/database/resultwrapper/MssqlResultWrapper.php',
'MssqlUpdater' => __DIR__ . '/includes/installer/MssqlUpdater.php',
'MultiConfig' => __DIR__ . '/includes/config/MultiConfig.php',
'MultiHttpClient' => __DIR__ . '/includes/libs/MultiHttpClient.php',
'MutableConfig' => __DIR__ . '/includes/config/MutableConfig.php',
'MutableContext' => __DIR__ . '/includes/context/MutableContext.php',
'MwSql' => __DIR__ . '/maintenance/sql.php',
- 'MySQLField' => __DIR__ . '/includes/db/DatabaseMysqlBase.php',
- 'MySQLMasterPos' => __DIR__ . '/includes/db/DatabaseMysqlBase.php',
+ 'MySQLField' => __DIR__ . '/includes/libs/rdbms/field/MySQLField.php',
+ 'MySQLMasterPos' => __DIR__ . '/includes/libs/rdbms/database/position/MySQLMasterPos.php',
'MySqlLockManager' => __DIR__ . '/includes/filebackend/lockmanager/MySqlLockManager.php',
'MysqlInstaller' => __DIR__ . '/includes/installer/MysqlInstaller.php',
'MysqlUpdater' => __DIR__ . '/includes/installer/MysqlUpdater.php',
'NullStatsdDataFactory' => __DIR__ . '/includes/libs/stats/NullStatsdDataFactory.php',
'NumericUppercaseCollation' => __DIR__ . '/includes/collation/NumericUppercaseCollation.php',
'OOUIHTMLForm' => __DIR__ . '/includes/htmlform/OOUIHTMLForm.php',
- 'ORAField' => __DIR__ . '/includes/db/DatabaseOracle.php',
+ 'ORAField' => __DIR__ . '/includes/libs/rdbms/field/ORAField.php',
'ORAResult' => __DIR__ . '/includes/db/DatabaseOracle.php',
'ObjectCache' => __DIR__ . '/includes/objectcache/ObjectCache.php',
'ObjectFactory' => __DIR__ . '/includes/libs/ObjectFactory.php',
'PopulateRevisionLength' => __DIR__ . '/maintenance/populateRevisionLength.php',
'PopulateRevisionSha1' => __DIR__ . '/maintenance/populateRevisionSha1.php',
'PostgreSqlLockManager' => __DIR__ . '/includes/filebackend/lockmanager/PostgreSqlLockManager.php',
- 'PostgresBlob' => __DIR__ . '/includes/db/DatabasePostgres.php',
+ 'PostgresBlob' => __DIR__ . '/includes/libs/rdbms/encasing/PostgresBlob.php',
'PostgresField' => __DIR__ . '/includes/db/DatabasePostgres.php',
'PostgresInstaller' => __DIR__ . '/includes/installer/PostgresInstaller.php',
'PostgresUpdater' => __DIR__ . '/includes/installer/PostgresUpdater.php',
'ResourceLoaderUserTokensModule' => __DIR__ . '/includes/resourceloader/ResourceLoaderUserTokensModule.php',
'ResourceLoaderWikiModule' => __DIR__ . '/includes/resourceloader/ResourceLoaderWikiModule.php',
'RestbaseVirtualRESTService' => __DIR__ . '/includes/libs/virtualrest/RestbaseVirtualRESTService.php',
- 'ResultWrapper' => __DIR__ . '/includes/db/DatabaseUtility.php',
+ 'ResultWrapper' => __DIR__ . '/includes/libs/rdbms/database/resultwrapper/ResultWrapper.php',
'RevDelArchiveItem' => __DIR__ . '/includes/revisiondelete/RevDelArchiveItem.php',
'RevDelArchiveList' => __DIR__ . '/includes/revisiondelete/RevDelArchiveList.php',
'RevDelArchivedFileItem' => __DIR__ . '/includes/revisiondelete/RevDelArchivedFileItem.php',
'RowUpdateGenerator' => __DIR__ . '/includes/utils/RowUpdateGenerator.php',
'RunJobs' => __DIR__ . '/maintenance/runJobs.php',
'RunningStat' => __DIR__ . '/includes/compat/RunningStatCompat.php',
- 'SQLiteField' => __DIR__ . '/includes/db/DatabaseSqlite.php',
+ 'SQLiteField' => __DIR__ . '/includes/libs/rdbms/field/SQLiteField.php',
'SVGMetadataExtractor' => __DIR__ . '/includes/media/SVGMetadataExtractor.php',
'SVGReader' => __DIR__ . '/includes/media/SVGMetadataExtractor.php',
'SamplingStatsdClient' => __DIR__ . '/includes/libs/SamplingStatsdClient.php',
return wfSetVar( $this->mIgnoreErrors, $value );
}
} // end DatabaseMssql class
-
-/**
- * Utility class.
- *
- * @ingroup Database
- */
-class MssqlField implements Field {
- private $name, $tableName, $default, $max_length, $nullable, $type;
-
- function __construct( $info ) {
- $this->name = $info['COLUMN_NAME'];
- $this->tableName = $info['TABLE_NAME'];
- $this->default = $info['COLUMN_DEFAULT'];
- $this->max_length = $info['CHARACTER_MAXIMUM_LENGTH'];
- $this->nullable = !( strtolower( $info['IS_NULLABLE'] ) == 'no' );
- $this->type = $info['DATA_TYPE'];
- }
-
- function name() {
- return $this->name;
- }
-
- function tableName() {
- return $this->tableName;
- }
-
- function defaultValue() {
- return $this->default;
- }
-
- function maxLength() {
- return $this->max_length;
- }
-
- function isNullable() {
- return $this->nullable;
- }
-
- function type() {
- return $this->type;
- }
-}
-
-class MssqlBlob extends Blob {
- public function __construct( $data ) {
- if ( $data instanceof MssqlBlob ) {
- return $data;
- } elseif ( $data instanceof Blob ) {
- $this->mData = $data->fetch();
- } elseif ( is_array( $data ) && is_object( $data ) ) {
- $this->mData = serialize( $data );
- } else {
- $this->mData = $data;
- }
- }
-
- /**
- * Returns an unquoted hex representation of a binary string
- * for insertion into varbinary-type fields
- * @return string
- */
- public function fetch() {
- if ( $this->mData === null ) {
- return 'null';
- }
-
- $ret = '0x';
- $dataLength = strlen( $this->mData );
- for ( $i = 0; $i < $dataLength; $i++ ) {
- $ret .= bin2hex( pack( 'C', ord( $this->mData[$i] ) ) );
- }
-
- return $ret;
- }
-}
-
-class MssqlResultWrapper extends ResultWrapper {
- private $mSeekTo = null;
-
- /**
- * @return stdClass|bool
- */
- public function fetchObject() {
- $res = $this->result;
-
- if ( $this->mSeekTo !== null ) {
- $result = sqlsrv_fetch_object( $res, 'stdClass', [],
- SQLSRV_SCROLL_ABSOLUTE, $this->mSeekTo );
- $this->mSeekTo = null;
- } else {
- $result = sqlsrv_fetch_object( $res );
- }
-
- // MediaWiki expects us to return boolean false when there are no more rows instead of null
- if ( $result === null ) {
- return false;
- }
-
- return $result;
- }
-
- /**
- * @return array|bool
- */
- public function fetchRow() {
- $res = $this->result;
-
- if ( $this->mSeekTo !== null ) {
- $result = sqlsrv_fetch_array( $res, SQLSRV_FETCH_BOTH,
- SQLSRV_SCROLL_ABSOLUTE, $this->mSeekTo );
- $this->mSeekTo = null;
- } else {
- $result = sqlsrv_fetch_array( $res );
- }
-
- // MediaWiki expects us to return boolean false when there are no more rows instead of null
- if ( $result === null ) {
- return false;
- }
-
- return $result;
- }
-
- /**
- * @param int $row
- * @return bool
- */
- public function seek( $row ) {
- $res = $this->result;
-
- // check bounds
- $numRows = $this->db->numRows( $res );
- $row = intval( $row );
-
- if ( $numRows === 0 ) {
- return false;
- } elseif ( $row < 0 || $row > $numRows - 1 ) {
- return false;
- }
-
- // Unlike MySQL, the seek actually happens on the next access
- $this->mSeekTo = $row;
- return true;
- }
-}
}
}
-/**
- * Utility class.
- * @ingroup Database
- */
-class MySQLField implements Field {
- private $name, $tablename, $default, $max_length, $nullable,
- $is_pk, $is_unique, $is_multiple, $is_key, $type, $binary,
- $is_numeric, $is_blob, $is_unsigned, $is_zerofill;
-
- function __construct( $info ) {
- $this->name = $info->name;
- $this->tablename = $info->table;
- $this->default = $info->def;
- $this->max_length = $info->max_length;
- $this->nullable = !$info->not_null;
- $this->is_pk = $info->primary_key;
- $this->is_unique = $info->unique_key;
- $this->is_multiple = $info->multiple_key;
- $this->is_key = ( $this->is_pk || $this->is_unique || $this->is_multiple );
- $this->type = $info->type;
- $this->binary = isset( $info->binary ) ? $info->binary : false;
- $this->is_numeric = isset( $info->numeric ) ? $info->numeric : false;
- $this->is_blob = isset( $info->blob ) ? $info->blob : false;
- $this->is_unsigned = isset( $info->unsigned ) ? $info->unsigned : false;
- $this->is_zerofill = isset( $info->zerofill ) ? $info->zerofill : false;
- }
-
- /**
- * @return string
- */
- function name() {
- return $this->name;
- }
-
- /**
- * @return string
- */
- function tableName() {
- return $this->tablename;
- }
-
- /**
- * @return string
- */
- function type() {
- return $this->type;
- }
-
- /**
- * @return bool
- */
- function isNullable() {
- return $this->nullable;
- }
-
- function defaultValue() {
- return $this->default;
- }
-
- /**
- * @return bool
- */
- function isKey() {
- return $this->is_key;
- }
-
- /**
- * @return bool
- */
- function isMultipleKey() {
- return $this->is_multiple;
- }
-
- /**
- * @return bool
- */
- function isBinary() {
- return $this->binary;
- }
-
- /**
- * @return bool
- */
- function isNumeric() {
- return $this->is_numeric;
- }
-
- /**
- * @return bool
- */
- function isBlob() {
- return $this->is_blob;
- }
-
- /**
- * @return bool
- */
- function isUnsigned() {
- return $this->is_unsigned;
- }
-
- /**
- * @return bool
- */
- function isZerofill() {
- return $this->is_zerofill;
- }
-}
-
-/**
- * DBMasterPos class for MySQL/MariaDB
- *
- * Note that master positions and sync logic here make some assumptions:
- * - Binlog-based usage assumes single-source replication and non-hierarchical replication.
- * - GTID-based usage allows getting/syncing with multi-source replication. It is assumed
- * that GTID sets are complete (e.g. include all domains on the server).
- */
-class MySQLMasterPos implements DBMasterPos {
- /** @var string Binlog file */
- public $file;
- /** @var int Binglog file position */
- public $pos;
- /** @var string[] GTID list */
- public $gtids = [];
- /** @var float UNIX timestamp */
- public $asOfTime = 0.0;
-
- /**
- * @param string $file Binlog file name
- * @param integer $pos Binlog position
- * @param string $gtid Comma separated GTID set [optional]
- */
- function __construct( $file, $pos, $gtid = '' ) {
- $this->file = $file;
- $this->pos = $pos;
- $this->gtids = array_map( 'trim', explode( ',', $gtid ) );
- $this->asOfTime = microtime( true );
- }
-
- /**
- * @return string <binlog file>/<position>, e.g db1034-bin.000976/843431247
- */
- function __toString() {
- return "{$this->file}/{$this->pos}";
- }
-
- function asOfTime() {
- return $this->asOfTime;
- }
-
- function hasReached( DBMasterPos $pos ) {
- if ( !( $pos instanceof self ) ) {
- throw new InvalidArgumentException( "Position not an instance of " . __CLASS__ );
- }
-
- // Prefer GTID comparisons, which work with multi-tier replication
- $thisPosByDomain = $this->getGtidCoordinates();
- $thatPosByDomain = $pos->getGtidCoordinates();
- if ( $thisPosByDomain && $thatPosByDomain ) {
- $reached = true;
- // Check that this has positions GTE all of those in $pos for all domains in $pos
- foreach ( $thatPosByDomain as $domain => $thatPos ) {
- $thisPos = isset( $thisPosByDomain[$domain] ) ? $thisPosByDomain[$domain] : -1;
- $reached = $reached && ( $thatPos <= $thisPos );
- }
-
- return $reached;
- }
-
- // Fallback to the binlog file comparisons
- $thisBinPos = $this->getBinlogCoordinates();
- $thatBinPos = $pos->getBinlogCoordinates();
- if ( $thisBinPos && $thatBinPos && $thisBinPos['binlog'] === $thatBinPos['binlog'] ) {
- return ( $thisBinPos['pos'] >= $thatBinPos['pos'] );
- }
-
- // Comparing totally different binlogs does not make sense
- return false;
- }
-
- function channelsMatch( DBMasterPos $pos ) {
- if ( !( $pos instanceof self ) ) {
- throw new InvalidArgumentException( "Position not an instance of " . __CLASS__ );
- }
-
- // Prefer GTID comparisons, which work with multi-tier replication
- $thisPosDomains = array_keys( $this->getGtidCoordinates() );
- $thatPosDomains = array_keys( $pos->getGtidCoordinates() );
- if ( $thisPosDomains && $thatPosDomains ) {
- // Check that this has GTIDs for all domains in $pos
- return !array_diff( $thatPosDomains, $thisPosDomains );
- }
-
- // Fallback to the binlog file comparisons
- $thisBinPos = $this->getBinlogCoordinates();
- $thatBinPos = $pos->getBinlogCoordinates();
-
- return ( $thisBinPos && $thatBinPos && $thisBinPos['binlog'] === $thatBinPos['binlog'] );
- }
-
- /**
- * @note: this returns false for multi-source replication GTID sets
- * @see https://mariadb.com/kb/en/mariadb/gtid
- * @see https://dev.mysql.com/doc/refman/5.6/en/replication-gtids-concepts.html
- * @return array Map of (domain => integer position) or false
- */
- protected function getGtidCoordinates() {
- $gtidInfos = [];
- foreach ( $this->gtids as $gtid ) {
- $m = [];
- // MariaDB style: <domain>-<server id>-<sequence number>
- if ( preg_match( '!^(\d+)-\d+-(\d+)$!', $gtid, $m ) ) {
- $gtidInfos[(int)$m[1]] = (int)$m[2];
- // MySQL style: <UUID domain>:<sequence number>
- } elseif ( preg_match( '!^(\w{8}-\w{4}-\w{4}-\w{4}-\w{12}):(\d+)$!', $gtid, $m ) ) {
- $gtidInfos[$m[1]] = (int)$m[2];
- } else {
- $gtidInfos = [];
- break; // unrecognized GTID
- }
-
- }
-
- return $gtidInfos;
- }
-
- /**
- * @see http://dev.mysql.com/doc/refman/5.7/en/show-master-status.html
- * @see http://dev.mysql.com/doc/refman/5.7/en/show-slave-status.html
- * @return array|bool (binlog, (integer file number, integer position)) or false
- */
- protected function getBinlogCoordinates() {
- $m = [];
- if ( preg_match( '!^(.+)\.(\d+)/(\d+)$!', (string)$this, $m ) ) {
- return [ 'binlog' => $m[1], 'pos' => [ (int)$m[2], (int)$m[3] ] ];
- }
-
- return false;
- }
-}
}
}
-/**
- * Utility class.
- * @ingroup Database
- */
-class ORAField implements Field {
- private $name, $tablename, $default, $max_length, $nullable,
- $is_pk, $is_unique, $is_multiple, $is_key, $type;
-
- function __construct( $info ) {
- $this->name = $info['column_name'];
- $this->tablename = $info['table_name'];
- $this->default = $info['data_default'];
- $this->max_length = $info['data_length'];
- $this->nullable = $info['not_null'];
- $this->is_pk = isset( $info['prim'] ) && $info['prim'] == 1 ? 1 : 0;
- $this->is_unique = isset( $info['uniq'] ) && $info['uniq'] == 1 ? 1 : 0;
- $this->is_multiple = isset( $info['nonuniq'] ) && $info['nonuniq'] == 1 ? 1 : 0;
- $this->is_key = ( $this->is_pk || $this->is_unique || $this->is_multiple );
- $this->type = $info['data_type'];
- }
-
- function name() {
- return $this->name;
- }
-
- function tableName() {
- return $this->tablename;
- }
-
- function defaultValue() {
- return $this->default;
- }
-
- function maxLength() {
- return $this->max_length;
- }
-
- function isNullable() {
- return $this->nullable;
- }
-
- function isKey() {
- return $this->is_key;
- }
-
- function isMultipleKey() {
- return $this->is_multiple;
- }
-
- function type() {
- return $this->type;
- }
-}
-
/**
* @ingroup Database
*/
return Wikimedia\base_convert( substr( sha1( $lockName ), 0, 15 ), 16, 10 );
}
} // end DatabasePostgres class
-
-class PostgresBlob extends Blob {
-}
}
} // end DatabaseSqlite class
-
-/**
- * @ingroup Database
- */
-class SQLiteField implements Field {
- private $info, $tableName;
-
- function __construct( $info, $tableName ) {
- $this->info = $info;
- $this->tableName = $tableName;
- }
-
- function name() {
- return $this->info->name;
- }
-
- function tableName() {
- return $this->tableName;
- }
-
- function defaultValue() {
- if ( is_string( $this->info->dflt_value ) ) {
- // Typically quoted
- if ( preg_match( '/^\'(.*)\'$', $this->info->dflt_value ) ) {
- return str_replace( "''", "'", $this->info->dflt_value );
- }
- }
-
- return $this->info->dflt_value;
- }
-
- /**
- * @return bool
- */
- function isNullable() {
- return !$this->info->notnull;
- }
-
- function type() {
- return $this->info->type;
- }
-} // end SQLiteField
+++ /dev/null
-<?php
-/**
- * This file contains database-related utility classes.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- * @ingroup Database
- */
-
-/**
- * Utility class
- * @ingroup Database
- *
- * This allows us to distinguish a blob from a normal string and an array of strings
- */
-class Blob {
- /** @var string */
- protected $mData;
-
- function __construct( $data ) {
- $this->mData = $data;
- }
-
- function fetch() {
- return $this->mData;
- }
-}
-
-/**
- * Base for all database-specific classes representing information about database fields
- * @ingroup Database
- */
-interface Field {
- /**
- * Field name
- * @return string
- */
- function name();
-
- /**
- * Name of table this field belongs to
- * @return string
- */
- function tableName();
-
- /**
- * Database type
- * @return string
- */
- function type();
-
- /**
- * Whether this field can store NULL values
- * @return bool
- */
- function isNullable();
-}
-
-/**
- * Result wrapper for grabbing data queried by someone else
- * @ingroup Database
- */
-class ResultWrapper implements Iterator {
- /** @var resource */
- public $result;
-
- /** @var IDatabase */
- protected $db;
-
- /** @var int */
- protected $pos = 0;
-
- /** @var object|null */
- protected $currentRow = null;
-
- /**
- * Create a new result object from a result resource and a Database object
- *
- * @param IDatabase $database
- * @param resource|ResultWrapper $result
- */
- function __construct( $database, $result ) {
- $this->db = $database;
-
- if ( $result instanceof ResultWrapper ) {
- $this->result = $result->result;
- } else {
- $this->result = $result;
- }
- }
-
- /**
- * Get the number of rows in a result object
- *
- * @return int
- */
- function numRows() {
- return $this->db->numRows( $this );
- }
-
- /**
- * Fetch the next row from the given result object, in object form. Fields can be retrieved with
- * $row->fieldname, with fields acting like member variables. If no more rows are available,
- * false is returned.
- *
- * @return stdClass|bool
- * @throws DBUnexpectedError Thrown if the database returns an error
- */
- function fetchObject() {
- return $this->db->fetchObject( $this );
- }
-
- /**
- * Fetch the next row from the given result object, in associative array form. Fields are
- * retrieved with $row['fieldname']. If no more rows are available, false is returned.
- *
- * @return array|bool
- * @throws DBUnexpectedError Thrown if the database returns an error
- */
- function fetchRow() {
- return $this->db->fetchRow( $this );
- }
-
- /**
- * Free a result object
- */
- function free() {
- $this->db->freeResult( $this );
- unset( $this->result );
- unset( $this->db );
- }
-
- /**
- * Change the position of the cursor in a result object.
- * See mysql_data_seek()
- *
- * @param int $row
- */
- function seek( $row ) {
- $this->db->dataSeek( $this, $row );
- }
-
- /*
- * ======= Iterator functions =======
- * Note that using these in combination with the non-iterator functions
- * above may cause rows to be skipped or repeated.
- */
-
- function rewind() {
- if ( $this->numRows() ) {
- $this->db->dataSeek( $this, 0 );
- }
- $this->pos = 0;
- $this->currentRow = null;
- }
-
- /**
- * @return stdClass|array|bool
- */
- function current() {
- if ( is_null( $this->currentRow ) ) {
- $this->next();
- }
-
- return $this->currentRow;
- }
-
- /**
- * @return int
- */
- function key() {
- return $this->pos;
- }
-
- /**
- * @return stdClass
- */
- function next() {
- $this->pos++;
- $this->currentRow = $this->fetchObject();
-
- return $this->currentRow;
- }
-
- /**
- * @return bool
- */
- function valid() {
- return $this->current() !== false;
- }
-}
-
-/**
- * Overloads the relevant methods of the real ResultsWrapper so it
- * doesn't go anywhere near an actual database.
- */
-class FakeResultWrapper extends ResultWrapper {
- /** @var array */
- public $result = [];
-
- /** @var null And it's going to stay that way :D */
- protected $db = null;
-
- /** @var int */
- protected $pos = 0;
-
- /** @var array|stdClass|bool */
- protected $currentRow = null;
-
- /**
- * @param array $array
- */
- function __construct( $array ) {
- $this->result = $array;
- }
-
- /**
- * @return int
- */
- function numRows() {
- return count( $this->result );
- }
-
- /**
- * @return array|bool
- */
- function fetchRow() {
- if ( $this->pos < count( $this->result ) ) {
- $this->currentRow = $this->result[$this->pos];
- } else {
- $this->currentRow = false;
- }
- $this->pos++;
- if ( is_object( $this->currentRow ) ) {
- return get_object_vars( $this->currentRow );
- } else {
- return $this->currentRow;
- }
- }
-
- function seek( $row ) {
- $this->pos = $row;
- }
-
- function free() {
- }
-
- /**
- * Callers want to be able to access fields with $this->fieldName
- * @return bool|stdClass
- */
- function fetchObject() {
- $this->fetchRow();
- if ( $this->currentRow ) {
- return (object)$this->currentRow;
- } else {
- return false;
- }
- }
-
- function rewind() {
- $this->pos = 0;
- $this->currentRow = null;
- }
-
- /**
- * @return bool|stdClass
- */
- function next() {
- return $this->fetchObject();
- }
-}
-
-/**
- * Used by DatabaseBase::buildLike() to represent characters that have special
- * meaning in SQL LIKE clauses and thus need no escaping. Don't instantiate it
- * manually, use DatabaseBase::anyChar() and anyString() instead.
- */
-class LikeMatch {
- /** @var string */
- private $str;
-
- /**
- * Store a string into a LikeMatch marker object.
- *
- * @param string $s
- */
- public function __construct( $s ) {
- $this->str = $s;
- }
-
- /**
- * Return the original stored string.
- *
- * @return string
- */
- public function toString() {
- return $this->str;
- }
-}
-
-/**
- * An object representing a master or replica DB position in a replicated setup.
- *
- * The implementation details of this opaque type are up to the database subclass.
- */
-interface DBMasterPos {
- /**
- * @return float UNIX timestamp
- * @since 1.25
- */
- public function asOfTime();
-
- /**
- * @param DBMasterPos $pos
- * @return bool Whether this position is at or higher than $pos
- * @since 1.27
- */
- public function hasReached( DBMasterPos $pos );
-
- /**
- * @param DBMasterPos $pos
- * @return bool Whether this position appears to be for the same channel as another
- * @since 1.27
- */
- public function channelsMatch( DBMasterPos $pos );
-
- /**
- * @return string
- * @since 1.27
- */
- public function __toString();
-}
--- /dev/null
+<?php
+/**
+ * Helper class to handle automatically marking connections as reusable (via RAII pattern)
+ * as well handling deferring the actual network connection until the handle is used
+ *
+ * @note: proxy methods are defined explicity to avoid interface errors
+ * @ingroup Database
+ * @since 1.22
+ */
+class DBConnRef implements IDatabase {
+ /** @var ILoadBalancer */
+ private $lb;
+
+ /** @var IDatabase|null Live connection handle */
+ private $conn;
+
+ /** @var array|null */
+ private $params;
+
+ const FLD_INDEX = 0;
+ const FLD_GROUP = 1;
+ const FLD_WIKI = 2;
+
+ /**
+ * @param ILoadBalancer $lb
+ * @param IDatabase|array $conn Connection or (server index, group, wiki ID)
+ */
+ public function __construct( ILoadBalancer $lb, $conn ) {
+ $this->lb = $lb;
+ if ( $conn instanceof IDatabase ) {
+ $this->conn = $conn; // live handle
+ } elseif ( count( $conn ) >= 3 && $conn[self::FLD_WIKI] !== false ) {
+ $this->params = $conn;
+ } else {
+ throw new InvalidArgumentException( "Missing lazy connection arguments." );
+ }
+ }
+
+ function __call( $name, array $arguments ) {
+ if ( $this->conn === null ) {
+ list( $db, $groups, $wiki ) = $this->params;
+ $this->conn = $this->lb->getConnection( $db, $groups, $wiki );
+ }
+
+ return call_user_func_array( [ $this->conn, $name ], $arguments );
+ }
+
+ public function getServerInfo() {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function bufferResults( $buffer = null ) {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function trxLevel() {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function trxTimestamp() {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function explicitTrxActive() {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function tablePrefix( $prefix = null ) {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function dbSchema( $schema = null ) {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function getLBInfo( $name = null ) {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function setLBInfo( $name, $value = null ) {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function setLazyMasterHandle( IDatabase $conn ) {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function implicitGroupby() {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function implicitOrderby() {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function lastQuery() {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function doneWrites() {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function lastDoneWrites() {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function writesPending() {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function writesOrCallbacksPending() {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function pendingWriteQueryDuration( $type = self::ESTIMATE_TOTAL ) {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function pendingWriteCallers() {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function isOpen() {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function setFlag( $flag, $remember = self::REMEMBER_NOTHING ) {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function clearFlag( $flag, $remember = self::REMEMBER_NOTHING ) {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function restoreFlags( $state = self::RESTORE_PRIOR ) {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function getFlag( $flag ) {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function getProperty( $name ) {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function getWikiID() {
+ if ( $this->conn === null ) {
+ // Avoid triggering a connection
+ return $this->params[self::FLD_WIKI];
+ }
+
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function getType() {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function open( $server, $user, $password, $dbName ) {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function fetchObject( $res ) {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function fetchRow( $res ) {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function numRows( $res ) {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function numFields( $res ) {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function fieldName( $res, $n ) {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function insertId() {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function dataSeek( $res, $row ) {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function lastErrno() {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function lastError() {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function fieldInfo( $table, $field ) {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function affectedRows() {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function getSoftwareLink() {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function getServerVersion() {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function close() {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function reportConnectionError( $error = 'Unknown error' ) {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function query( $sql, $fname = __METHOD__, $tempIgnore = false ) {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function reportQueryError( $error, $errno, $sql, $fname, $tempIgnore = false ) {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function freeResult( $res ) {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function selectField(
+ $table, $var, $cond = '', $fname = __METHOD__, $options = []
+ ) {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function selectFieldValues(
+ $table, $var, $cond = '', $fname = __METHOD__, $options = []
+ ) {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function select(
+ $table, $vars, $conds = '', $fname = __METHOD__,
+ $options = [], $join_conds = []
+ ) {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function selectSQLText(
+ $table, $vars, $conds = '', $fname = __METHOD__,
+ $options = [], $join_conds = []
+ ) {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function selectRow(
+ $table, $vars, $conds, $fname = __METHOD__,
+ $options = [], $join_conds = []
+ ) {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function estimateRowCount(
+ $table, $vars = '*', $conds = '', $fname = __METHOD__, $options = []
+ ) {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function selectRowCount(
+ $tables, $vars = '*', $conds = '', $fname = __METHOD__, $options = [], $join_conds = []
+ ) {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function fieldExists( $table, $field, $fname = __METHOD__ ) {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function indexExists( $table, $index, $fname = __METHOD__ ) {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function tableExists( $table, $fname = __METHOD__ ) {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function indexUnique( $table, $index ) {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function insert( $table, $a, $fname = __METHOD__, $options = [] ) {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function update( $table, $values, $conds, $fname = __METHOD__, $options = [] ) {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function makeList( $a, $mode = LIST_COMMA ) {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function makeWhereFrom2d( $data, $baseKey, $subKey ) {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function bitNot( $field ) {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function bitAnd( $fieldLeft, $fieldRight ) {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function bitOr( $fieldLeft, $fieldRight ) {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function buildConcat( $stringList ) {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function buildGroupConcatField(
+ $delim, $table, $field, $conds = '', $join_conds = []
+ ) {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function selectDB( $db ) {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function getDBname() {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function getServer() {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function addQuotes( $s ) {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function buildLike() {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function anyChar() {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function anyString() {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function nextSequenceValue( $seqName ) {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function replace( $table, $uniqueIndexes, $rows, $fname = __METHOD__ ) {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function upsert(
+ $table, array $rows, array $uniqueIndexes, array $set, $fname = __METHOD__
+ ) {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function deleteJoin(
+ $delTable, $joinTable, $delVar, $joinVar, $conds, $fname = __METHOD__
+ ) {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function delete( $table, $conds, $fname = __METHOD__ ) {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function insertSelect(
+ $destTable, $srcTable, $varMap, $conds,
+ $fname = __METHOD__, $insertOptions = [], $selectOptions = []
+ ) {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function unionSupportsOrderAndLimit() {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function unionQueries( $sqls, $all ) {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function conditional( $cond, $trueVal, $falseVal ) {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function strreplace( $orig, $old, $new ) {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function getServerUptime() {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function wasDeadlock() {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function wasLockTimeout() {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function wasErrorReissuable() {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function wasReadOnlyError() {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function masterPosWait( DBMasterPos $pos, $timeout ) {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function getSlavePos() {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function getMasterPos() {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function serverIsReadOnly() {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function onTransactionResolution( callable $callback ) {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function onTransactionIdle( callable $callback ) {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function onTransactionPreCommitOrIdle( callable $callback ) {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function setTransactionListener( $name, callable $callback = null ) {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function startAtomic( $fname = __METHOD__ ) {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function endAtomic( $fname = __METHOD__ ) {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function doAtomicSection( $fname, callable $callback ) {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function begin( $fname = __METHOD__, $mode = IDatabase::TRANSACTION_EXPLICIT ) {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function commit( $fname = __METHOD__, $flush = '' ) {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function rollback( $fname = __METHOD__, $flush = '' ) {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function flushSnapshot( $fname = __METHOD__ ) {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function listTables( $prefix = null, $fname = __METHOD__ ) {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function timestamp( $ts = 0 ) {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function timestampOrNull( $ts = null ) {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function ping( &$rtt = null ) {
+ return func_num_args()
+ ? $this->__call( __FUNCTION__, [ &$rtt ] )
+ : $this->__call( __FUNCTION__, [] ); // method cares about null vs missing
+ }
+
+ public function getLag() {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function getSessionLagStatus() {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function maxListLen() {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function encodeBlob( $b ) {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function decodeBlob( $b ) {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function setSessionOptions( array $options ) {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function setSchemaVars( $vars ) {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function lockIsFree( $lockName, $method ) {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function lock( $lockName, $method, $timeout = 5 ) {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function unlock( $lockName, $method ) {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function getScopedLockAndFlush( $lockKey, $fname, $timeout ) {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function namedLocksEnqueue() {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function getInfinity() {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function encodeExpiry( $expiry ) {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function decodeExpiry( $expiry, $format = TS_MW ) {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function setBigSelects( $value = true ) {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function isReadOnly() {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ /**
+ * Clean up the connection when out of scope
+ */
+ function __destruct() {
+ if ( $this->conn !== null ) {
+ $this->lb->reuseConnection( $this->conn );
+ }
+ }
+}
+++ /dev/null
-<?php
-/**
- * Helper class to handle automatically marking connections as reusable (via RAII pattern)
- * as well handling deferring the actual network connection until the handle is used
- *
- * @note: proxy methods are defined explicity to avoid interface errors
- * @ingroup Database
- * @since 1.22
- */
-class DBConnRef implements IDatabase {
- /** @var ILoadBalancer */
- private $lb;
-
- /** @var IDatabase|null Live connection handle */
- private $conn;
-
- /** @var array|null */
- private $params;
-
- const FLD_INDEX = 0;
- const FLD_GROUP = 1;
- const FLD_WIKI = 2;
-
- /**
- * @param ILoadBalancer $lb
- * @param IDatabase|array $conn Connection or (server index, group, wiki ID)
- */
- public function __construct( ILoadBalancer $lb, $conn ) {
- $this->lb = $lb;
- if ( $conn instanceof IDatabase ) {
- $this->conn = $conn; // live handle
- } elseif ( count( $conn ) >= 3 && $conn[self::FLD_WIKI] !== false ) {
- $this->params = $conn;
- } else {
- throw new InvalidArgumentException( "Missing lazy connection arguments." );
- }
- }
-
- function __call( $name, array $arguments ) {
- if ( $this->conn === null ) {
- list( $db, $groups, $wiki ) = $this->params;
- $this->conn = $this->lb->getConnection( $db, $groups, $wiki );
- }
-
- return call_user_func_array( [ $this->conn, $name ], $arguments );
- }
-
- public function getServerInfo() {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function bufferResults( $buffer = null ) {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function trxLevel() {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function trxTimestamp() {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function explicitTrxActive() {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function tablePrefix( $prefix = null ) {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function dbSchema( $schema = null ) {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function getLBInfo( $name = null ) {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function setLBInfo( $name, $value = null ) {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function setLazyMasterHandle( IDatabase $conn ) {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function implicitGroupby() {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function implicitOrderby() {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function lastQuery() {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function doneWrites() {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function lastDoneWrites() {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function writesPending() {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function writesOrCallbacksPending() {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function pendingWriteQueryDuration( $type = self::ESTIMATE_TOTAL ) {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function pendingWriteCallers() {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function isOpen() {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function setFlag( $flag, $remember = self::REMEMBER_NOTHING ) {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function clearFlag( $flag, $remember = self::REMEMBER_NOTHING ) {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function restoreFlags( $state = self::RESTORE_PRIOR ) {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function getFlag( $flag ) {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function getProperty( $name ) {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function getWikiID() {
- if ( $this->conn === null ) {
- // Avoid triggering a connection
- return $this->params[self::FLD_WIKI];
- }
-
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function getType() {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function open( $server, $user, $password, $dbName ) {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function fetchObject( $res ) {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function fetchRow( $res ) {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function numRows( $res ) {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function numFields( $res ) {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function fieldName( $res, $n ) {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function insertId() {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function dataSeek( $res, $row ) {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function lastErrno() {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function lastError() {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function fieldInfo( $table, $field ) {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function affectedRows() {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function getSoftwareLink() {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function getServerVersion() {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function close() {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function reportConnectionError( $error = 'Unknown error' ) {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function query( $sql, $fname = __METHOD__, $tempIgnore = false ) {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function reportQueryError( $error, $errno, $sql, $fname, $tempIgnore = false ) {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function freeResult( $res ) {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function selectField(
- $table, $var, $cond = '', $fname = __METHOD__, $options = []
- ) {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function selectFieldValues(
- $table, $var, $cond = '', $fname = __METHOD__, $options = []
- ) {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function select(
- $table, $vars, $conds = '', $fname = __METHOD__,
- $options = [], $join_conds = []
- ) {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function selectSQLText(
- $table, $vars, $conds = '', $fname = __METHOD__,
- $options = [], $join_conds = []
- ) {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function selectRow(
- $table, $vars, $conds, $fname = __METHOD__,
- $options = [], $join_conds = []
- ) {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function estimateRowCount(
- $table, $vars = '*', $conds = '', $fname = __METHOD__, $options = []
- ) {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function selectRowCount(
- $tables, $vars = '*', $conds = '', $fname = __METHOD__, $options = [], $join_conds = []
- ) {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function fieldExists( $table, $field, $fname = __METHOD__ ) {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function indexExists( $table, $index, $fname = __METHOD__ ) {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function tableExists( $table, $fname = __METHOD__ ) {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function indexUnique( $table, $index ) {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function insert( $table, $a, $fname = __METHOD__, $options = [] ) {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function update( $table, $values, $conds, $fname = __METHOD__, $options = [] ) {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function makeList( $a, $mode = LIST_COMMA ) {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function makeWhereFrom2d( $data, $baseKey, $subKey ) {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function bitNot( $field ) {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function bitAnd( $fieldLeft, $fieldRight ) {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function bitOr( $fieldLeft, $fieldRight ) {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function buildConcat( $stringList ) {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function buildGroupConcatField(
- $delim, $table, $field, $conds = '', $join_conds = []
- ) {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function selectDB( $db ) {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function getDBname() {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function getServer() {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function addQuotes( $s ) {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function buildLike() {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function anyChar() {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function anyString() {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function nextSequenceValue( $seqName ) {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function replace( $table, $uniqueIndexes, $rows, $fname = __METHOD__ ) {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function upsert(
- $table, array $rows, array $uniqueIndexes, array $set, $fname = __METHOD__
- ) {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function deleteJoin(
- $delTable, $joinTable, $delVar, $joinVar, $conds, $fname = __METHOD__
- ) {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function delete( $table, $conds, $fname = __METHOD__ ) {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function insertSelect(
- $destTable, $srcTable, $varMap, $conds,
- $fname = __METHOD__, $insertOptions = [], $selectOptions = []
- ) {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function unionSupportsOrderAndLimit() {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function unionQueries( $sqls, $all ) {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function conditional( $cond, $trueVal, $falseVal ) {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function strreplace( $orig, $old, $new ) {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function getServerUptime() {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function wasDeadlock() {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function wasLockTimeout() {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function wasErrorReissuable() {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function wasReadOnlyError() {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function masterPosWait( DBMasterPos $pos, $timeout ) {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function getSlavePos() {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function getMasterPos() {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function serverIsReadOnly() {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function onTransactionResolution( callable $callback ) {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function onTransactionIdle( callable $callback ) {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function onTransactionPreCommitOrIdle( callable $callback ) {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function setTransactionListener( $name, callable $callback = null ) {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function startAtomic( $fname = __METHOD__ ) {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function endAtomic( $fname = __METHOD__ ) {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function doAtomicSection( $fname, callable $callback ) {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function begin( $fname = __METHOD__, $mode = IDatabase::TRANSACTION_EXPLICIT ) {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function commit( $fname = __METHOD__, $flush = '' ) {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function rollback( $fname = __METHOD__, $flush = '' ) {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function flushSnapshot( $fname = __METHOD__ ) {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function listTables( $prefix = null, $fname = __METHOD__ ) {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function timestamp( $ts = 0 ) {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function timestampOrNull( $ts = null ) {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function ping( &$rtt = null ) {
- return func_num_args()
- ? $this->__call( __FUNCTION__, [ &$rtt ] )
- : $this->__call( __FUNCTION__, [] ); // method cares about null vs missing
- }
-
- public function getLag() {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function getSessionLagStatus() {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function maxListLen() {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function encodeBlob( $b ) {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function decodeBlob( $b ) {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function setSessionOptions( array $options ) {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function setSchemaVars( $vars ) {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function lockIsFree( $lockName, $method ) {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function lock( $lockName, $method, $timeout = 5 ) {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function unlock( $lockName, $method ) {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function getScopedLockAndFlush( $lockKey, $fname, $timeout ) {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function namedLocksEnqueue() {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function getInfinity() {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function encodeExpiry( $expiry ) {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function decodeExpiry( $expiry, $format = TS_MW ) {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function setBigSelects( $value = true ) {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function isReadOnly() {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- /**
- * Clean up the connection when out of scope
- */
- function __destruct() {
- if ( $this->conn !== null ) {
- $this->lb->reuseConnection( $this->conn );
- }
- }
-}
--- /dev/null
+<?php
+/**
+ * An object representing a master or replica DB position in a replicated setup.
+ *
+ * The implementation details of this opaque type are up to the database subclass.
+ */
+interface DBMasterPos {
+ /**
+ * @return float UNIX timestamp
+ * @since 1.25
+ */
+ public function asOfTime();
+
+ /**
+ * @param DBMasterPos $pos
+ * @return bool Whether this position is at or higher than $pos
+ * @since 1.27
+ */
+ public function hasReached( DBMasterPos $pos );
+
+ /**
+ * @param DBMasterPos $pos
+ * @return bool Whether this position appears to be for the same channel as another
+ * @since 1.27
+ */
+ public function channelsMatch( DBMasterPos $pos );
+
+ /**
+ * @return string
+ * @since 1.27
+ */
+ public function __toString();
+}
--- /dev/null
+<?php
+/**
+ * DBMasterPos class for MySQL/MariaDB
+ *
+ * Note that master positions and sync logic here make some assumptions:
+ * - Binlog-based usage assumes single-source replication and non-hierarchical replication.
+ * - GTID-based usage allows getting/syncing with multi-source replication. It is assumed
+ * that GTID sets are complete (e.g. include all domains on the server).
+ */
+class MySQLMasterPos implements DBMasterPos {
+ /** @var string Binlog file */
+ public $file;
+ /** @var int Binglog file position */
+ public $pos;
+ /** @var string[] GTID list */
+ public $gtids = [];
+ /** @var float UNIX timestamp */
+ public $asOfTime = 0.0;
+
+ /**
+ * @param string $file Binlog file name
+ * @param integer $pos Binlog position
+ * @param string $gtid Comma separated GTID set [optional]
+ */
+ function __construct( $file, $pos, $gtid = '' ) {
+ $this->file = $file;
+ $this->pos = $pos;
+ $this->gtids = array_map( 'trim', explode( ',', $gtid ) );
+ $this->asOfTime = microtime( true );
+ }
+
+ /**
+ * @return string <binlog file>/<position>, e.g db1034-bin.000976/843431247
+ */
+ function __toString() {
+ return "{$this->file}/{$this->pos}";
+ }
+
+ function asOfTime() {
+ return $this->asOfTime;
+ }
+
+ function hasReached( DBMasterPos $pos ) {
+ if ( !( $pos instanceof self ) ) {
+ throw new InvalidArgumentException( "Position not an instance of " . __CLASS__ );
+ }
+
+ // Prefer GTID comparisons, which work with multi-tier replication
+ $thisPosByDomain = $this->getGtidCoordinates();
+ $thatPosByDomain = $pos->getGtidCoordinates();
+ if ( $thisPosByDomain && $thatPosByDomain ) {
+ $reached = true;
+ // Check that this has positions GTE all of those in $pos for all domains in $pos
+ foreach ( $thatPosByDomain as $domain => $thatPos ) {
+ $thisPos = isset( $thisPosByDomain[$domain] ) ? $thisPosByDomain[$domain] : -1;
+ $reached = $reached && ( $thatPos <= $thisPos );
+ }
+
+ return $reached;
+ }
+
+ // Fallback to the binlog file comparisons
+ $thisBinPos = $this->getBinlogCoordinates();
+ $thatBinPos = $pos->getBinlogCoordinates();
+ if ( $thisBinPos && $thatBinPos && $thisBinPos['binlog'] === $thatBinPos['binlog'] ) {
+ return ( $thisBinPos['pos'] >= $thatBinPos['pos'] );
+ }
+
+ // Comparing totally different binlogs does not make sense
+ return false;
+ }
+
+ function channelsMatch( DBMasterPos $pos ) {
+ if ( !( $pos instanceof self ) ) {
+ throw new InvalidArgumentException( "Position not an instance of " . __CLASS__ );
+ }
+
+ // Prefer GTID comparisons, which work with multi-tier replication
+ $thisPosDomains = array_keys( $this->getGtidCoordinates() );
+ $thatPosDomains = array_keys( $pos->getGtidCoordinates() );
+ if ( $thisPosDomains && $thatPosDomains ) {
+ // Check that this has GTIDs for all domains in $pos
+ return !array_diff( $thatPosDomains, $thisPosDomains );
+ }
+
+ // Fallback to the binlog file comparisons
+ $thisBinPos = $this->getBinlogCoordinates();
+ $thatBinPos = $pos->getBinlogCoordinates();
+
+ return ( $thisBinPos && $thatBinPos && $thisBinPos['binlog'] === $thatBinPos['binlog'] );
+ }
+
+ /**
+ * @note: this returns false for multi-source replication GTID sets
+ * @see https://mariadb.com/kb/en/mariadb/gtid
+ * @see https://dev.mysql.com/doc/refman/5.6/en/replication-gtids-concepts.html
+ * @return array Map of (domain => integer position) or false
+ */
+ protected function getGtidCoordinates() {
+ $gtidInfos = [];
+ foreach ( $this->gtids as $gtid ) {
+ $m = [];
+ // MariaDB style: <domain>-<server id>-<sequence number>
+ if ( preg_match( '!^(\d+)-\d+-(\d+)$!', $gtid, $m ) ) {
+ $gtidInfos[(int)$m[1]] = (int)$m[2];
+ // MySQL style: <UUID domain>:<sequence number>
+ } elseif ( preg_match( '!^(\w{8}-\w{4}-\w{4}-\w{4}-\w{12}):(\d+)$!', $gtid, $m ) ) {
+ $gtidInfos[$m[1]] = (int)$m[2];
+ } else {
+ $gtidInfos = [];
+ break; // unrecognized GTID
+ }
+
+ }
+
+ return $gtidInfos;
+ }
+
+ /**
+ * @see http://dev.mysql.com/doc/refman/5.7/en/show-master-status.html
+ * @see http://dev.mysql.com/doc/refman/5.7/en/show-slave-status.html
+ * @return array|bool (binlog, (integer file number, integer position)) or false
+ */
+ protected function getBinlogCoordinates() {
+ $m = [];
+ if ( preg_match( '!^(.+)\.(\d+)/(\d+)$!', (string)$this, $m ) ) {
+ return [ 'binlog' => $m[1], 'pos' => [ (int)$m[2], (int)$m[3] ] ];
+ }
+
+ return false;
+ }
+}
--- /dev/null
+<?php
+/**
+ * Overloads the relevant methods of the real ResultsWrapper so it
+ * doesn't go anywhere near an actual database.
+ */
+class FakeResultWrapper extends ResultWrapper {
+ /** @var array */
+ public $result = [];
+
+ /** @var null And it's going to stay that way :D */
+ protected $db = null;
+
+ /** @var int */
+ protected $pos = 0;
+
+ /** @var array|stdClass|bool */
+ protected $currentRow = null;
+
+ /**
+ * @param array $array
+ */
+ function __construct( $array ) {
+ $this->result = $array;
+ }
+
+ /**
+ * @return int
+ */
+ function numRows() {
+ return count( $this->result );
+ }
+
+ /**
+ * @return array|bool
+ */
+ function fetchRow() {
+ if ( $this->pos < count( $this->result ) ) {
+ $this->currentRow = $this->result[$this->pos];
+ } else {
+ $this->currentRow = false;
+ }
+ $this->pos++;
+ if ( is_object( $this->currentRow ) ) {
+ return get_object_vars( $this->currentRow );
+ } else {
+ return $this->currentRow;
+ }
+ }
+
+ function seek( $row ) {
+ $this->pos = $row;
+ }
+
+ function free() {
+ }
+
+ /**
+ * Callers want to be able to access fields with $this->fieldName
+ * @return bool|stdClass
+ */
+ function fetchObject() {
+ $this->fetchRow();
+ if ( $this->currentRow ) {
+ return (object)$this->currentRow;
+ } else {
+ return false;
+ }
+ }
+
+ function rewind() {
+ $this->pos = 0;
+ $this->currentRow = null;
+ }
+
+ /**
+ * @return bool|stdClass
+ */
+ function next() {
+ return $this->fetchObject();
+ }
+}
--- /dev/null
+<?php
+class MssqlResultWrapper extends ResultWrapper {
+ private $mSeekTo = null;
+
+ /**
+ * @return stdClass|bool
+ */
+ public function fetchObject() {
+ $res = $this->result;
+
+ if ( $this->mSeekTo !== null ) {
+ $result = sqlsrv_fetch_object( $res, 'stdClass', [],
+ SQLSRV_SCROLL_ABSOLUTE, $this->mSeekTo );
+ $this->mSeekTo = null;
+ } else {
+ $result = sqlsrv_fetch_object( $res );
+ }
+
+ // MediaWiki expects us to return boolean false when there are no more rows instead of null
+ if ( $result === null ) {
+ return false;
+ }
+
+ return $result;
+ }
+
+ /**
+ * @return array|bool
+ */
+ public function fetchRow() {
+ $res = $this->result;
+
+ if ( $this->mSeekTo !== null ) {
+ $result = sqlsrv_fetch_array( $res, SQLSRV_FETCH_BOTH,
+ SQLSRV_SCROLL_ABSOLUTE, $this->mSeekTo );
+ $this->mSeekTo = null;
+ } else {
+ $result = sqlsrv_fetch_array( $res );
+ }
+
+ // MediaWiki expects us to return boolean false when there are no more rows instead of null
+ if ( $result === null ) {
+ return false;
+ }
+
+ return $result;
+ }
+
+ /**
+ * @param int $row
+ * @return bool
+ */
+ public function seek( $row ) {
+ $res = $this->result;
+
+ // check bounds
+ $numRows = $this->db->numRows( $res );
+ $row = intval( $row );
+
+ if ( $numRows === 0 ) {
+ return false;
+ } elseif ( $row < 0 || $row > $numRows - 1 ) {
+ return false;
+ }
+
+ // Unlike MySQL, the seek actually happens on the next access
+ $this->mSeekTo = $row;
+ return true;
+ }
+}
--- /dev/null
+<?php
+/**
+ * Result wrapper for grabbing data queried by someone else
+ * @ingroup Database
+ */
+class ResultWrapper implements Iterator {
+ /** @var resource */
+ public $result;
+
+ /** @var DatabaseBase */
+ protected $db;
+
+ /** @var int */
+ protected $pos = 0;
+
+ /** @var object|null */
+ protected $currentRow = null;
+
+ /**
+ * Create a new result object from a result resource and a Database object
+ *
+ * @param DatabaseBase $database
+ * @param resource|ResultWrapper $result
+ */
+ function __construct( $database, $result ) {
+ $this->db = $database;
+
+ if ( $result instanceof ResultWrapper ) {
+ $this->result = $result->result;
+ } else {
+ $this->result = $result;
+ }
+ }
+
+ /**
+ * Get the number of rows in a result object
+ *
+ * @return int
+ */
+ function numRows() {
+ return $this->db->numRows( $this );
+ }
+
+ /**
+ * Fetch the next row from the given result object, in object form. Fields can be retrieved with
+ * $row->fieldname, with fields acting like member variables. If no more rows are available,
+ * false is returned.
+ *
+ * @return stdClass|bool
+ * @throws DBUnexpectedError Thrown if the database returns an error
+ */
+ function fetchObject() {
+ return $this->db->fetchObject( $this );
+ }
+
+ /**
+ * Fetch the next row from the given result object, in associative array form. Fields are
+ * retrieved with $row['fieldname']. If no more rows are available, false is returned.
+ *
+ * @return array|bool
+ * @throws DBUnexpectedError Thrown if the database returns an error
+ */
+ function fetchRow() {
+ return $this->db->fetchRow( $this );
+ }
+
+ /**
+ * Free a result object
+ */
+ function free() {
+ $this->db->freeResult( $this );
+ unset( $this->result );
+ unset( $this->db );
+ }
+
+ /**
+ * Change the position of the cursor in a result object.
+ * See mysql_data_seek()
+ *
+ * @param int $row
+ */
+ function seek( $row ) {
+ $this->db->dataSeek( $this, $row );
+ }
+
+ /*
+ * ======= Iterator functions =======
+ * Note that using these in combination with the non-iterator functions
+ * above may cause rows to be skipped or repeated.
+ */
+
+ function rewind() {
+ if ( $this->numRows() ) {
+ $this->db->dataSeek( $this, 0 );
+ }
+ $this->pos = 0;
+ $this->currentRow = null;
+ }
+
+ /**
+ * @return stdClass|array|bool
+ */
+ function current() {
+ if ( is_null( $this->currentRow ) ) {
+ $this->next();
+ }
+
+ return $this->currentRow;
+ }
+
+ /**
+ * @return int
+ */
+ function key() {
+ return $this->pos;
+ }
+
+ /**
+ * @return stdClass
+ */
+ function next() {
+ $this->pos++;
+ $this->currentRow = $this->fetchObject();
+
+ return $this->currentRow;
+ }
+
+ /**
+ * @return bool
+ */
+ function valid() {
+ return $this->current() !== false;
+ }
+}
--- /dev/null
+<?php
+/**
+ * Utility class
+ * @ingroup Database
+ *
+ * This allows us to distinguish a blob from a normal string and an array of strings
+ */
+class Blob {
+ /** @var string */
+ protected $mData;
+
+ function __construct( $data ) {
+ $this->mData = $data;
+ }
+
+ function fetch() {
+ return $this->mData;
+ }
+}
--- /dev/null
+<?php
+/**
+ * Used by DatabaseBase::buildLike() to represent characters that have special
+ * meaning in SQL LIKE clauses and thus need no escaping. Don't instantiate it
+ * manually, use DatabaseBase::anyChar() and anyString() instead.
+ */
+class LikeMatch {
+ /** @var string */
+ private $str;
+
+ /**
+ * Store a string into a LikeMatch marker object.
+ *
+ * @param string $s
+ */
+ public function __construct( $s ) {
+ $this->str = $s;
+ }
+
+ /**
+ * Return the original stored string.
+ *
+ * @return string
+ */
+ public function toString() {
+ return $this->str;
+ }
+}
--- /dev/null
+<?php
+class MssqlBlob extends Blob {
+ public function __construct( $data ) {
+ if ( $data instanceof MssqlBlob ) {
+ return $data;
+ } elseif ( $data instanceof Blob ) {
+ $this->mData = $data->fetch();
+ } elseif ( is_array( $data ) && is_object( $data ) ) {
+ $this->mData = serialize( $data );
+ } else {
+ $this->mData = $data;
+ }
+ }
+
+ /**
+ * Returns an unquoted hex representation of a binary string
+ * for insertion into varbinary-type fields
+ * @return string
+ */
+ public function fetch() {
+ if ( $this->mData === null ) {
+ return 'null';
+ }
+
+ $ret = '0x';
+ $dataLength = strlen( $this->mData );
+ for ( $i = 0; $i < $dataLength; $i++ ) {
+ $ret .= bin2hex( pack( 'C', ord( $this->mData[$i] ) ) );
+ }
+
+ return $ret;
+ }
+}
--- /dev/null
+<?php
+class PostgresBlob extends Blob {
+
+}
--- /dev/null
+<?php
+/**
+ * Base for all database-specific classes representing information about database fields
+ * @ingroup Database
+ */
+interface Field {
+ /**
+ * Field name
+ * @return string
+ */
+ function name();
+
+ /**
+ * Name of table this field belongs to
+ * @return string
+ */
+ function tableName();
+
+ /**
+ * Database type
+ * @return string
+ */
+ function type();
+
+ /**
+ * Whether this field can store NULL values
+ * @return bool
+ */
+ function isNullable();
+}
--- /dev/null
+<?php
+class MssqlField implements Field {
+ private $name, $tableName, $default, $max_length, $nullable, $type;
+
+ function __construct( $info ) {
+ $this->name = $info['COLUMN_NAME'];
+ $this->tableName = $info['TABLE_NAME'];
+ $this->default = $info['COLUMN_DEFAULT'];
+ $this->max_length = $info['CHARACTER_MAXIMUM_LENGTH'];
+ $this->nullable = !( strtolower( $info['IS_NULLABLE'] ) == 'no' );
+ $this->type = $info['DATA_TYPE'];
+ }
+
+ function name() {
+ return $this->name;
+ }
+
+ function tableName() {
+ return $this->tableName;
+ }
+
+ function defaultValue() {
+ return $this->default;
+ }
+
+ function maxLength() {
+ return $this->max_length;
+ }
+
+ function isNullable() {
+ return $this->nullable;
+ }
+
+ function type() {
+ return $this->type;
+ }
+}
+
--- /dev/null
+<?php
+class MySQLField implements Field {
+ private $name, $tablename, $default, $max_length, $nullable,
+ $is_pk, $is_unique, $is_multiple, $is_key, $type, $binary,
+ $is_numeric, $is_blob, $is_unsigned, $is_zerofill;
+
+ function __construct( $info ) {
+ $this->name = $info->name;
+ $this->tablename = $info->table;
+ $this->default = $info->def;
+ $this->max_length = $info->max_length;
+ $this->nullable = !$info->not_null;
+ $this->is_pk = $info->primary_key;
+ $this->is_unique = $info->unique_key;
+ $this->is_multiple = $info->multiple_key;
+ $this->is_key = ( $this->is_pk || $this->is_unique || $this->is_multiple );
+ $this->type = $info->type;
+ $this->binary = isset( $info->binary ) ? $info->binary : false;
+ $this->is_numeric = isset( $info->numeric ) ? $info->numeric : false;
+ $this->is_blob = isset( $info->blob ) ? $info->blob : false;
+ $this->is_unsigned = isset( $info->unsigned ) ? $info->unsigned : false;
+ $this->is_zerofill = isset( $info->zerofill ) ? $info->zerofill : false;
+ }
+
+ /**
+ * @return string
+ */
+ function name() {
+ return $this->name;
+ }
+
+ /**
+ * @return string
+ */
+ function tableName() {
+ return $this->tablename;
+ }
+
+ /**
+ * @return string
+ */
+ function type() {
+ return $this->type;
+ }
+
+ /**
+ * @return bool
+ */
+ function isNullable() {
+ return $this->nullable;
+ }
+
+ function defaultValue() {
+ return $this->default;
+ }
+
+ /**
+ * @return bool
+ */
+ function isKey() {
+ return $this->is_key;
+ }
+
+ /**
+ * @return bool
+ */
+ function isMultipleKey() {
+ return $this->is_multiple;
+ }
+
+ /**
+ * @return bool
+ */
+ function isBinary() {
+ return $this->binary;
+ }
+
+ /**
+ * @return bool
+ */
+ function isNumeric() {
+ return $this->is_numeric;
+ }
+
+ /**
+ * @return bool
+ */
+ function isBlob() {
+ return $this->is_blob;
+ }
+
+ /**
+ * @return bool
+ */
+ function isUnsigned() {
+ return $this->is_unsigned;
+ }
+
+ /**
+ * @return bool
+ */
+ function isZerofill() {
+ return $this->is_zerofill;
+ }
+}
+
--- /dev/null
+<?php
+class ORAField implements Field {
+ private $name, $tablename, $default, $max_length, $nullable,
+ $is_pk, $is_unique, $is_multiple, $is_key, $type;
+
+ function __construct( $info ) {
+ $this->name = $info['column_name'];
+ $this->tablename = $info['table_name'];
+ $this->default = $info['data_default'];
+ $this->max_length = $info['data_length'];
+ $this->nullable = $info['not_null'];
+ $this->is_pk = isset( $info['prim'] ) && $info['prim'] == 1 ? 1 : 0;
+ $this->is_unique = isset( $info['uniq'] ) && $info['uniq'] == 1 ? 1 : 0;
+ $this->is_multiple = isset( $info['nonuniq'] ) && $info['nonuniq'] == 1 ? 1 : 0;
+ $this->is_key = ( $this->is_pk || $this->is_unique || $this->is_multiple );
+ $this->type = $info['data_type'];
+ }
+
+ function name() {
+ return $this->name;
+ }
+
+ function tableName() {
+ return $this->tablename;
+ }
+
+ function defaultValue() {
+ return $this->default;
+ }
+
+ function maxLength() {
+ return $this->max_length;
+ }
+
+ function isNullable() {
+ return $this->nullable;
+ }
+
+ function isKey() {
+ return $this->is_key;
+ }
+
+ function isMultipleKey() {
+ return $this->is_multiple;
+ }
+
+ function type() {
+ return $this->type;
+ }
+}
--- /dev/null
+<?php
+class SQLiteField implements Field {
+ private $info, $tableName;
+
+ function __construct( $info, $tableName ) {
+ $this->info = $info;
+ $this->tableName = $tableName;
+ }
+
+ function name() {
+ return $this->info->name;
+ }
+
+ function tableName() {
+ return $this->tableName;
+ }
+
+ function defaultValue() {
+ if ( is_string( $this->info->dflt_value ) ) {
+ // Typically quoted
+ if ( preg_match( '/^\'(.*)\'$', $this->info->dflt_value ) ) {
+ return str_replace( "''", "'", $this->info->dflt_value );
+ }
+ }
+
+ return $this->info->dflt_value;
+ }
+
+ /**
+ * @return bool
+ */
+ function isNullable() {
+ return !$this->info->notnull;
+ }
+
+ function type() {
+ return $this->info->type;
+ }
+}