aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAaron Schulz <aschulz@wikimedia.org>2016-09-14 03:11:41 -0700
committerAaron Schulz <aschulz@wikimedia.org>2016-09-14 20:40:12 -0700
commit0bbba6e0d4a99a63c020acb402b6e57baa4863b0 (patch)
tree7ed49580c8a337a9238acce07304fc4058b35edc
parentc1c68c92f27969fd763a63300142e1aba4b0f164 (diff)
downloadmediawikicore-0bbba6e0d4a99a63c020acb402b6e57baa4863b0.tar.gz
mediawikicore-0bbba6e0d4a99a63c020acb402b6e57baa4863b0.zip
Move various DB helper classes to /libs/rdbms
Change-Id: I0724f1acce4f6c43b1f0983fa119e628e7c53ba5
-rw-r--r--autoload.php30
-rw-r--r--includes/db/DatabaseMssql.php145
-rw-r--r--includes/db/DatabaseMysqlBase.php240
-rw-r--r--includes/db/DatabaseOracle.php54
-rw-r--r--includes/db/DatabasePostgres.php3
-rw-r--r--includes/db/DatabaseSqlite.php42
-rw-r--r--includes/db/DatabaseUtility.php347
-rw-r--r--includes/libs/rdbms/database/DBConnRef.php (renamed from includes/libs/rdbms/database/RBConnRef.php)0
-rw-r--r--includes/libs/rdbms/database/position/DBMasterPos.php33
-rw-r--r--includes/libs/rdbms/database/position/MySQLMasterPos.php132
-rw-r--r--includes/libs/rdbms/database/resultwrapper/FakeResultWrapper.php81
-rw-r--r--includes/libs/rdbms/database/resultwrapper/MssqlResultWrapper.php70
-rw-r--r--includes/libs/rdbms/database/resultwrapper/ResultWrapper.php134
-rw-r--r--includes/libs/rdbms/encasing/Blob.php19
-rw-r--r--includes/libs/rdbms/encasing/LikeMatch.php28
-rw-r--r--includes/libs/rdbms/encasing/MssqlBlob.php33
-rw-r--r--includes/libs/rdbms/encasing/PostgresBlob.php4
-rw-r--r--includes/libs/rdbms/field/Field.php30
-rw-r--r--includes/libs/rdbms/field/MssqlField.php38
-rw-r--r--includes/libs/rdbms/field/MySQLField.php106
-rw-r--r--includes/libs/rdbms/field/ORAField.php50
-rw-r--r--includes/libs/rdbms/field/SQLiteField.php39
22 files changed, 812 insertions, 846 deletions
diff --git a/autoload.php b/autoload.php
index 759c715f2a4d..1ebf728ca275 100644
--- a/autoload.php
+++ b/autoload.php
@@ -189,7 +189,7 @@ $wgAutoloadLocalClasses = [
'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',
@@ -299,13 +299,13 @@ $wgAutoloadLocalClasses = [
'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',
@@ -440,7 +440,7 @@ $wgAutoloadLocalClasses = [
'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',
@@ -448,7 +448,7 @@ $wgAutoloadLocalClasses = [
'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',
@@ -715,7 +715,7 @@ $wgAutoloadLocalClasses = [
'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',
@@ -943,10 +943,10 @@ $wgAutoloadLocalClasses = [
'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',
@@ -954,8 +954,8 @@ $wgAutoloadLocalClasses = [
'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',
@@ -980,7 +980,7 @@ $wgAutoloadLocalClasses = [
'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',
@@ -1065,7 +1065,7 @@ $wgAutoloadLocalClasses = [
'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',
@@ -1182,7 +1182,7 @@ $wgAutoloadLocalClasses = [
'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',
@@ -1214,7 +1214,7 @@ $wgAutoloadLocalClasses = [
'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',
diff --git a/includes/db/DatabaseMssql.php b/includes/db/DatabaseMssql.php
index 2439c600735a..1d7adc6856ee 100644
--- a/includes/db/DatabaseMssql.php
+++ b/includes/db/DatabaseMssql.php
@@ -1410,148 +1410,3 @@ class DatabaseMssql extends Database {
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;
- }
-}
diff --git a/includes/db/DatabaseMysqlBase.php b/includes/db/DatabaseMysqlBase.php
index af80d24aef51..f8737a84dfe4 100644
--- a/includes/db/DatabaseMysqlBase.php
+++ b/includes/db/DatabaseMysqlBase.php
@@ -1351,243 +1351,3 @@ abstract class DatabaseMysqlBase extends Database {
}
}
-/**
- * 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;
- }
-}
diff --git a/includes/db/DatabaseOracle.php b/includes/db/DatabaseOracle.php
index 5d0ff447bae0..df311aafac2d 100644
--- a/includes/db/DatabaseOracle.php
+++ b/includes/db/DatabaseOracle.php
@@ -129,60 +129,6 @@ class ORAResult {
}
/**
- * 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
*/
class DatabaseOracle extends Database {
diff --git a/includes/db/DatabasePostgres.php b/includes/db/DatabasePostgres.php
index b1cc96bc7c67..590e1f4931db 100644
--- a/includes/db/DatabasePostgres.php
+++ b/includes/db/DatabasePostgres.php
@@ -1636,6 +1636,3 @@ SQL;
return Wikimedia\base_convert( substr( sha1( $lockName ), 0, 15 ), 16, 10 );
}
} // end DatabasePostgres class
-
-class PostgresBlob extends Blob {
-}
diff --git a/includes/db/DatabaseSqlite.php b/includes/db/DatabaseSqlite.php
index 6bf48e23653f..fa4e453b6509 100644
--- a/includes/db/DatabaseSqlite.php
+++ b/includes/db/DatabaseSqlite.php
@@ -1044,45 +1044,3 @@ class DatabaseSqlite extends Database {
}
} // 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
diff --git a/includes/db/DatabaseUtility.php b/includes/db/DatabaseUtility.php
deleted file mode 100644
index 2b8c8c6d4b7f..000000000000
--- a/includes/db/DatabaseUtility.php
+++ /dev/null
@@ -1,347 +0,0 @@
-<?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();
-}
diff --git a/includes/libs/rdbms/database/RBConnRef.php b/includes/libs/rdbms/database/DBConnRef.php
index 9035bbd5c94b..9035bbd5c94b 100644
--- a/includes/libs/rdbms/database/RBConnRef.php
+++ b/includes/libs/rdbms/database/DBConnRef.php
diff --git a/includes/libs/rdbms/database/position/DBMasterPos.php b/includes/libs/rdbms/database/position/DBMasterPos.php
new file mode 100644
index 000000000000..eda0ff32569c
--- /dev/null
+++ b/includes/libs/rdbms/database/position/DBMasterPos.php
@@ -0,0 +1,33 @@
+<?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();
+}
diff --git a/includes/libs/rdbms/database/position/MySQLMasterPos.php b/includes/libs/rdbms/database/position/MySQLMasterPos.php
new file mode 100644
index 000000000000..71fbe7e5875e
--- /dev/null
+++ b/includes/libs/rdbms/database/position/MySQLMasterPos.php
@@ -0,0 +1,132 @@
+<?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;
+ }
+}
diff --git a/includes/libs/rdbms/database/resultwrapper/FakeResultWrapper.php b/includes/libs/rdbms/database/resultwrapper/FakeResultWrapper.php
new file mode 100644
index 000000000000..774def8086b8
--- /dev/null
+++ b/includes/libs/rdbms/database/resultwrapper/FakeResultWrapper.php
@@ -0,0 +1,81 @@
+<?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();
+ }
+}
diff --git a/includes/libs/rdbms/database/resultwrapper/MssqlResultWrapper.php b/includes/libs/rdbms/database/resultwrapper/MssqlResultWrapper.php
new file mode 100644
index 000000000000..cccb8f17d2ac
--- /dev/null
+++ b/includes/libs/rdbms/database/resultwrapper/MssqlResultWrapper.php
@@ -0,0 +1,70 @@
+<?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;
+ }
+}
diff --git a/includes/libs/rdbms/database/resultwrapper/ResultWrapper.php b/includes/libs/rdbms/database/resultwrapper/ResultWrapper.php
new file mode 100644
index 000000000000..252f4f7e79ca
--- /dev/null
+++ b/includes/libs/rdbms/database/resultwrapper/ResultWrapper.php
@@ -0,0 +1,134 @@
+<?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;
+ }
+}
diff --git a/includes/libs/rdbms/encasing/Blob.php b/includes/libs/rdbms/encasing/Blob.php
new file mode 100644
index 000000000000..bd9033057789
--- /dev/null
+++ b/includes/libs/rdbms/encasing/Blob.php
@@ -0,0 +1,19 @@
+<?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;
+ }
+}
diff --git a/includes/libs/rdbms/encasing/LikeMatch.php b/includes/libs/rdbms/encasing/LikeMatch.php
new file mode 100644
index 000000000000..5dee884f2822
--- /dev/null
+++ b/includes/libs/rdbms/encasing/LikeMatch.php
@@ -0,0 +1,28 @@
+<?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;
+ }
+}
diff --git a/includes/libs/rdbms/encasing/MssqlBlob.php b/includes/libs/rdbms/encasing/MssqlBlob.php
new file mode 100644
index 000000000000..35be65c26e7c
--- /dev/null
+++ b/includes/libs/rdbms/encasing/MssqlBlob.php
@@ -0,0 +1,33 @@
+<?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;
+ }
+}
diff --git a/includes/libs/rdbms/encasing/PostgresBlob.php b/includes/libs/rdbms/encasing/PostgresBlob.php
new file mode 100644
index 000000000000..cc52336c299a
--- /dev/null
+++ b/includes/libs/rdbms/encasing/PostgresBlob.php
@@ -0,0 +1,4 @@
+<?php
+class PostgresBlob extends Blob {
+
+}
diff --git a/includes/libs/rdbms/field/Field.php b/includes/libs/rdbms/field/Field.php
new file mode 100644
index 000000000000..ed102f40c17c
--- /dev/null
+++ b/includes/libs/rdbms/field/Field.php
@@ -0,0 +1,30 @@
+<?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();
+}
diff --git a/includes/libs/rdbms/field/MssqlField.php b/includes/libs/rdbms/field/MssqlField.php
new file mode 100644
index 000000000000..80e19245bf40
--- /dev/null
+++ b/includes/libs/rdbms/field/MssqlField.php
@@ -0,0 +1,38 @@
+<?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;
+ }
+}
+
diff --git a/includes/libs/rdbms/field/MySQLField.php b/includes/libs/rdbms/field/MySQLField.php
new file mode 100644
index 000000000000..8cf964cc3d9a
--- /dev/null
+++ b/includes/libs/rdbms/field/MySQLField.php
@@ -0,0 +1,106 @@
+<?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;
+ }
+}
+
diff --git a/includes/libs/rdbms/field/ORAField.php b/includes/libs/rdbms/field/ORAField.php
new file mode 100644
index 000000000000..e48310ddc3e4
--- /dev/null
+++ b/includes/libs/rdbms/field/ORAField.php
@@ -0,0 +1,50 @@
+<?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;
+ }
+}
diff --git a/includes/libs/rdbms/field/SQLiteField.php b/includes/libs/rdbms/field/SQLiteField.php
new file mode 100644
index 000000000000..0a2389bfb0a7
--- /dev/null
+++ b/includes/libs/rdbms/field/SQLiteField.php
@@ -0,0 +1,39 @@
+<?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;
+ }
+}