"PhanUndeclaredVariableAssignOp",
// approximate error count: 55
"PhanUndeclaredVariableDim",
- // approximate error count: 3
- "PhanUnextractableAnnotationElementName",
- // approximate error count: 4
- "PhanUnextractableAnnotationSuffix",
] );
$cfg['ignore_undeclared_variables_in_global_scope'] = true;
<exclude-pattern>*/includes/diff/DairikiDiff\.php</exclude-pattern>
<exclude-pattern>*/includes/Feed\.php</exclude-pattern>
<exclude-pattern>*/includes/filerepo/file/LocalFile\.php</exclude-pattern>
- <exclude-pattern>*/includes/gallery/PackedOverlayImageGallery\.php</exclude-pattern>
<exclude-pattern>*/includes/htmlform/HTMLFormElement\.php</exclude-pattern>
<exclude-pattern>*/includes/libs/filebackend/FileBackendStore\.php</exclude-pattern>
<exclude-pattern>*/includes/libs/filebackend/FSFileBackend\.php</exclude-pattern>
'PPNode_Hash_Tree' => __DIR__ . '/includes/parser/Preprocessor_Hash.php',
'PPTemplateFrame_DOM' => __DIR__ . '/includes/parser/Preprocessor_DOM.php',
'PPTemplateFrame_Hash' => __DIR__ . '/includes/parser/Preprocessor_Hash.php',
- 'PackedHoverImageGallery' => __DIR__ . '/includes/gallery/PackedOverlayImageGallery.php',
+ 'PackedHoverImageGallery' => __DIR__ . '/includes/gallery/PackedHoverImageGallery.php',
'PackedImageGallery' => __DIR__ . '/includes/gallery/PackedImageGallery.php',
'PackedOverlayImageGallery' => __DIR__ . '/includes/gallery/PackedOverlayImageGallery.php',
'Page' => __DIR__ . '/includes/page/Page.php',
'Wikimedia\\Rdbms\\DBQueryError' => __DIR__ . '/includes/libs/rdbms/exception/DBQueryError.php',
'Wikimedia\\Rdbms\\DBQueryTimeoutError' => __DIR__ . '/includes/libs/rdbms/exception/DBQueryTimeoutError.php',
'Wikimedia\\Rdbms\\DBReadOnlyError' => __DIR__ . '/includes/libs/rdbms/exception/DBReadOnlyError.php',
+ 'Wikimedia\\Rdbms\\DBReadOnlyRoleError' => __DIR__ . '/includes/libs/rdbms/exception/DBReadOnlyRoleError.php',
'Wikimedia\\Rdbms\\DBReplicationWaitError' => __DIR__ . '/includes/libs/rdbms/exception/DBReplicationWaitError.php',
'Wikimedia\\Rdbms\\DBTransactionError' => __DIR__ . '/includes/libs/rdbms/exception/DBTransactionError.php',
'Wikimedia\\Rdbms\\DBTransactionSizeError' => __DIR__ . '/includes/libs/rdbms/exception/DBTransactionSizeError.php',
* Can also be used to revert after a DB failure.
*
* @private
- * @param Title Old location to move the file from.
- * @param Title New location to move the file to.
+ * @param Title $oldTitle Old location to move the file from.
+ * @param Title $newTitle New location to move the file to.
* @return Status
*/
private function moveFile( $oldTitle, $newTitle ) {
* Parse wikitext and return the HTML (internal implementation helper)
*
* @param string $text
- * @param Title The title to use
+ * @param Title $title The title to use
* @param bool $linestart Is this the start of a line?
* @param bool $tidy Whether the output should be tidied
* @param bool $interface Use interface language (instead of content language) while parsing
--- /dev/null
+<?php
+/**
+ * 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
+ */
+
+/**
+ * Same as Packed except different CSS is applied to make the
+ * caption only show up on hover. If a touch screen is detected,
+ * falls back to PackedHoverGallery. Degrades gracefully for
+ * screen readers.
+ */
+class PackedHoverImageGallery extends PackedOverlayImageGallery {
+}
. "\n\t\t\t</div></div>";
}
}
-
-/**
- * Same as Packed except different CSS is applied to make the
- * caption only show up on hover. If a touch screen is detected,
- * falls back to PackedHoverGallery. Degrades gracefully for
- * screen readers.
- */
-class PackedHoverImageGallery extends PackedOverlayImageGallery {
-}
/**
* @param WebRequest $request
*
- * @return array("<overall message>","<select value>","<text field value>")
+ * @return array ["<overall message>","<select value>","<text field value>"]
*/
public function loadDataFromRequest( $request ) {
if ( $request->getCheck( $this->mName ) ) {
* to an integer network and a number of bits
*
* @param string $range IP with CIDR prefix
- * @return array(int or string, int)
+ * @return array [int or string, int]
*/
public static function parseCIDR( $range ) {
if ( self::isIPv6( $range ) ) {
*
* @param string $range
*
- * @return array(string, int)
+ * @return array [string, int]
*/
private static function parseCIDR6( $range ) {
# Explode into <expanded IP,range>
*
* @param string $range
*
- * @return array(string, string)
+ * @return array [string, string]
*/
private static function parseRange6( $range ) {
# Expand any IPv6 IP
* 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 explicitly to avoid interface errors
* @ingroup Database
* @since 1.22
*/
private $conn;
/** @var array|null N-tuple of (server index, group, DatabaseDomain|string) */
private $params;
+ /** @var int One of DB_MASTER/DB_REPLICA */
+ private $role;
const FLD_INDEX = 0;
const FLD_GROUP = 1;
/**
* @param ILoadBalancer $lb Connection manager for $conn
- * @param Database|array $conn Database handle or (server index, query groups, domain, flags)
+ * @param Database|array $conn Database or (server index, query groups, domain, flags)
+ * @param int $role The type of connection asked for; one of DB_MASTER/DB_REPLICA
+ * @internal This method should not be called outside of LoadBalancer
*/
- public function __construct( ILoadBalancer $lb, $conn ) {
+ public function __construct( ILoadBalancer $lb, $conn, $role ) {
$this->lb = $lb;
+ $this->role = $role;
if ( $conn instanceof Database ) {
$this->conn = $conn; // live handle
} elseif ( is_array( $conn ) && count( $conn ) >= 4 && $conn[self::FLD_DOMAIN] !== false ) {
return $this->conn->$name( ...$arguments );
}
+ /**
+ * @return int DB_MASTER when this *requires* the master DB, otherwise DB_REPLICA
+ * @since 1.33
+ */
+ public function getReferenceRole() {
+ return $this->role;
+ }
+
public function getServerInfo() {
return $this->__call( __FUNCTION__, func_get_args() );
}
}
public function query( $sql, $fname = __METHOD__, $flags = 0 ) {
- return $this->__call( __FUNCTION__, func_get_args() );
+ if ( $this->role !== ILoadBalancer::DB_MASTER ) {
+ $flags |= IDatabase::QUERY_REPLICA_ROLE;
+ }
+
+ return $this->__call( __FUNCTION__, [ $sql, $fname, $flags ] );
}
public function freeResult( $res ) {
public function lockForUpdate(
$table, $conds = '', $fname = __METHOD__, $options = [], $join_conds = []
) {
+ $this->assertRoleAllowsWrites();
+
return $this->__call( __FUNCTION__, func_get_args() );
}
}
public function insert( $table, $a, $fname = __METHOD__, $options = [] ) {
+ $this->assertRoleAllowsWrites();
+
return $this->__call( __FUNCTION__, func_get_args() );
}
public function update( $table, $values, $conds, $fname = __METHOD__, $options = [] ) {
+ $this->assertRoleAllowsWrites();
+
return $this->__call( __FUNCTION__, func_get_args() );
}
}
public function nextSequenceValue( $seqName ) {
+ $this->assertRoleAllowsWrites();
+
return $this->__call( __FUNCTION__, func_get_args() );
}
public function replace( $table, $uniqueIndexes, $rows, $fname = __METHOD__ ) {
+ $this->assertRoleAllowsWrites();
+
return $this->__call( __FUNCTION__, func_get_args() );
}
public function upsert(
$table, array $rows, $uniqueIndexes, array $set, $fname = __METHOD__
) {
+ $this->assertRoleAllowsWrites();
+
return $this->__call( __FUNCTION__, func_get_args() );
}
public function deleteJoin(
$delTable, $joinTable, $delVar, $joinVar, $conds, $fname = __METHOD__
) {
+ $this->assertRoleAllowsWrites();
+
return $this->__call( __FUNCTION__, func_get_args() );
}
public function delete( $table, $conds, $fname = __METHOD__ ) {
+ $this->assertRoleAllowsWrites();
+
return $this->__call( __FUNCTION__, func_get_args() );
}
$destTable, $srcTable, $varMap, $conds,
$fname = __METHOD__, $insertOptions = [], $selectOptions = [], $selectJoinConds = []
) {
+ $this->assertRoleAllowsWrites();
+
return $this->__call( __FUNCTION__, func_get_args() );
}
}
public function onTransactionResolution( callable $callback, $fname = __METHOD__ ) {
+ // DB_REPLICA role: caller might want to refresh cache after a REPEATABLE-READ snapshot
return $this->__call( __FUNCTION__, func_get_args() );
}
public function onTransactionCommitOrIdle( callable $callback, $fname = __METHOD__ ) {
+ // DB_REPLICA role: caller might want to refresh cache after a REPEATABLE-READ snapshot
return $this->__call( __FUNCTION__, func_get_args() );
}
public function onTransactionIdle( callable $callback, $fname = __METHOD__ ) {
- return $this->__call( __FUNCTION__, func_get_args() );
+ return $this->onTransactionCommitOrIdle( $callback, $fname );
}
public function onTransactionPreCommitOrIdle( callable $callback, $fname = __METHOD__ ) {
+ // DB_REPLICA role: caller might want to refresh cache after a cache mutex is released
return $this->__call( __FUNCTION__, func_get_args() );
}
public function startAtomic(
$fname = __METHOD__, $cancelable = IDatabase::ATOMIC_NOT_CANCELABLE
) {
+ // Don't call assertRoleAllowsWrites(); caller might want a REPEATABLE-READ snapshot
return $this->__call( __FUNCTION__, func_get_args() );
}
public function endAtomic( $fname = __METHOD__ ) {
+ // Don't call assertRoleAllowsWrites(); caller might want a REPEATABLE-READ snapshot
return $this->__call( __FUNCTION__, func_get_args() );
}
public function cancelAtomic( $fname = __METHOD__, AtomicSectionIdentifier $sectionId = null ) {
+ // Don't call assertRoleAllowsWrites(); caller might want a REPEATABLE-READ snapshot
return $this->__call( __FUNCTION__, func_get_args() );
}
public function doAtomicSection(
$fname, callable $callback, $cancelable = self::ATOMIC_NOT_CANCELABLE
) {
+ // Don't call assertRoleAllowsWrites(); caller might want a REPEATABLE-READ snapshot
return $this->__call( __FUNCTION__, func_get_args() );
}
}
public function lockIsFree( $lockName, $method ) {
+ $this->assertRoleAllowsWrites();
+
return $this->__call( __FUNCTION__, func_get_args() );
}
public function lock( $lockName, $method, $timeout = 5 ) {
+ $this->assertRoleAllowsWrites();
+
return $this->__call( __FUNCTION__, func_get_args() );
}
public function unlock( $lockName, $method ) {
+ $this->assertRoleAllowsWrites();
+
return $this->__call( __FUNCTION__, func_get_args() );
}
public function getScopedLockAndFlush( $lockKey, $fname, $timeout ) {
+ $this->assertRoleAllowsWrites();
+
return $this->__call( __FUNCTION__, func_get_args() );
}
return $this->__call( __FUNCTION__, func_get_args() );
}
+ /**
+ * Error out if the role is not DB_MASTER
+ *
+ * Note that the underlying connection may or may not itself be read-only.
+ * It could even be to a writable master (both server-side and to the application).
+ * This error is meant for the case when a DB_REPLICA handle was requested but a
+ * a write was attempted on that handle regardless.
+ *
+ * In configurations where the master DB has some generic read load or is the only server,
+ * DB_MASTER/DB_REPLICA will sometimes (or always) use the same connection to the master DB.
+ * This does not effect the role of DBConnRef instances.
+ * @throws DBReadOnlyRoleError
+ */
+ protected function assertRoleAllowsWrites() {
+ // DB_MASTER is "prima facie" writable
+ if ( $this->role !== ILoadBalancer::DB_MASTER ) {
+ throw new DBReadOnlyRoleError( $this->conn, "Cannot write with role DB_REPLICA" );
+ }
+ }
+
/**
* Clean up the connection when out of scope
*/
*/
protected function assertIsWritableMaster() {
if ( $this->getLBInfo( 'replica' ) === true ) {
- throw new DBUnexpectedError(
+ throw new DBReadOnlyRoleError(
$this,
'Write operations are not allowed on replica database connections.'
);
$flags = (int)$flags; // b/c; this field used to be a bool
$ignoreErrors = $this->hasFlags( $flags, self::QUERY_SILENCE_ERRORS );
- $pseudoPermanent = $this->hasFlags( $flags, self::QUERY_PSEUDO_PERMANENT );
$priorTransaction = $this->trxLevel;
$priorWritesPending = $this->writesOrCallbacksPending();
$this->assertIsWritableMaster();
# Do not treat temporary table writes as "meaningful writes" that need committing.
# Profile them as reads. Integration tests can override this behavior via $flags.
+ $pseudoPermanent = $this->hasFlags( $flags, self::QUERY_PSEUDO_PERMANENT );
$tableType = $this->registerTempTableWrite( $sql, $pseudoPermanent );
$isEffectiveWrite = ( $tableType !== self::TEMP_NORMAL );
+ # DBConnRef uses QUERY_REPLICA_ROLE to enforce the replica role for raw SQL queries
+ if ( $isEffectiveWrite && $this->hasFlags( $flags, self::QUERY_REPLICA_ROLE ) ) {
+ throw new DBReadOnlyRoleError( $this, "Cannot write; target role is DB_REPLICA" );
+ }
} else {
$isEffectiveWrite = false;
}
* permanent as far as write tracking is concerned. This is useful for testing.
*/
const QUERY_PSEUDO_PERMANENT = 2;
+ /** @var int Enforce that a query does not make effective writes */
+ const QUERY_REPLICA_ROLE = 4;
/** @var bool Parameter to unionQueries() for UNION ALL */
const UNION_ALL = true;
$fname = false,
callable $inputCallback = null
) {
+ $this->assertRoleAllowsWrites();
+
return $this->__call( __FUNCTION__, func_get_args() );
}
$fname = __METHOD__,
callable $inputCallback = null
) {
+ $this->assertRoleAllowsWrites();
+
return $this->__call( __FUNCTION__, func_get_args() );
}
public function dropTable( $tableName, $fName = __METHOD__ ) {
+ $this->assertRoleAllowsWrites();
+
return $this->__call( __FUNCTION__, func_get_args() );
}
public function deadlockLoop() {
+ $this->assertRoleAllowsWrites();
+
return $this->__call( __FUNCTION__, func_get_args() );
}
public function duplicateTableStructure(
$oldName, $newName, $temporary = false, $fname = __METHOD__
) {
+ $this->assertRoleAllowsWrites();
+
return $this->__call( __FUNCTION__, func_get_args() );
}
}
public function lockTables( array $read, array $write, $method ) {
+ $this->assertRoleAllowsWrites();
+
return $this->__call( __FUNCTION__, func_get_args() );
}
public function unlockTables( $method ) {
+ $this->assertRoleAllowsWrites();
+
return $this->__call( __FUNCTION__, func_get_args() );
}
--- /dev/null
+<?php
+/**
+ * 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
+ */
+
+namespace Wikimedia\Rdbms;
+
+/**
+ * Exception class for attempted DB write access to a DBConnRef with the DB_REPLICA role
+ *
+ * @ingroup Database
+ */
+class DBReadOnlyRoleError extends DBUnexpectedError {
+}
}
}
- public function getConnectionRef( $db, $groups = [], $domain = false, $flags = 0 ) {
+ public function getConnectionRef( $i, $groups = [], $domain = false, $flags = 0 ) {
$domain = $this->resolveDomainID( $domain );
+ $role = $this->getRoleFromIndex( $i );
- return new DBConnRef( $this, $this->getConnection( $db, $groups, $domain, $flags ) );
+ return new DBConnRef( $this, $this->getConnection( $i, $groups, $domain, $flags ), $role );
}
- public function getLazyConnectionRef( $db, $groups = [], $domain = false, $flags = 0 ) {
+ public function getLazyConnectionRef( $i, $groups = [], $domain = false, $flags = 0 ) {
$domain = $this->resolveDomainID( $domain );
+ $role = $this->getRoleFromIndex( $i );
- return new DBConnRef( $this, [ $db, $groups, $domain, $flags ] );
+ return new DBConnRef( $this, [ $i, $groups, $domain, $flags ], $role );
}
- public function getMaintenanceConnectionRef( $db, $groups = [], $domain = false, $flags = 0 ) {
+ public function getMaintenanceConnectionRef( $i, $groups = [], $domain = false, $flags = 0 ) {
$domain = $this->resolveDomainID( $domain );
+ $role = $this->getRoleFromIndex( $i );
return new MaintainableDBConnRef(
- $this, $this->getConnection( $db, $groups, $domain, $flags ) );
+ $this, $this->getConnection( $i, $groups, $domain, $flags ), $role );
+ }
+
+ /**
+ * @param int $i Server index or DB_MASTER/DB_REPLICA
+ * @return int One of DB_MASTER/DB_REPLICA
+ */
+ private function getRoleFromIndex( $i ) {
+ return ( $i === self::DB_MASTER || $i === $this->getWriterIndex() )
+ ? self::DB_MASTER
+ : self::DB_REPLICA;
}
public function openConnection( $i, $domain = false, $flags = 0 ) {
if ( $this->packageFiles !== null ) {
$packageFiles = $this->getPackageFiles( $context );
if ( $deprecationScript ) {
- $mainFile =& $packageFiles['files'][ $packageFiles['main'] ];
+ $mainFile =& $packageFiles['files'][$packageFiles['main']];
$mainFile['content'] = $deprecationScript . $mainFile['content'];
}
return $packageFiles;
}
// Remove 'deprecated' key
if ( is_array( $this->descriptor ) ) {
- unset( $this->descriptor[ 'deprecated' ] );
+ unset( $this->descriptor['deprecated'] );
}
// Ensure that all files have common extension.
use ResourceLoaderOOUIModule;
public function __construct( $options = [] ) {
- if ( isset( $options[ 'themeScripts' ] ) ) {
- $skinScripts = $this->getSkinSpecific( $options[ 'themeScripts' ], 'scripts' );
+ if ( isset( $options['themeScripts'] ) ) {
+ $skinScripts = $this->getSkinSpecific( $options['themeScripts'], 'scripts' );
if ( !isset( $options['skinScripts'] ) ) {
$options['skinScripts'] = [];
}
$this->extendSkinSpecific( $options['skinScripts'], $skinScripts );
}
- if ( isset( $options[ 'themeStyles' ] ) ) {
- $skinStyles = $this->getSkinSpecific( $options[ 'themeStyles' ], 'styles' );
+ if ( isset( $options['themeStyles'] ) ) {
+ $skinStyles = $this->getSkinSpecific( $options['themeStyles'], 'styles' );
if ( !isset( $options['skinStyles'] ) ) {
$options['skinStyles'] = [];
}
*/
protected function getThemePath( $theme, $kind, $module ) {
$paths = self::getThemePaths();
- $path = $paths[ $theme ][ $kind ];
+ $path = $paths[$theme][$kind];
$path = str_replace( '{module}', $module, $path );
return $path;
}
if ( !is_array( $logo ) ) {
// No media queries required if we only have one variant
- $preloadLinks[ $logo ] = [ 'as' => 'image' ];
+ $preloadLinks[$logo] = [ 'as' => 'image' ];
return $preloadLinks;
}
if ( isset( $logo['svg'] ) ) {
// No media queries required if we only have a 1x and svg variant
// because all preload-capable browsers support SVGs
- $preloadLinks [ $logo['svg'] ] = [ 'as' => 'image' ];
+ $preloadLinks[$logo['svg']] = [ 'as' => 'image' ];
return $preloadLinks;
}
} );
foreach ( $logosPerDppx as $dppx => $src ) {
- $logos[] = [ 'dppx' => $dppx, 'src' => $src ];
+ $logos[] = [
+ 'dppx' => $dppx,
+ 'src' => $src
+ ];
}
$logosCount = count( $logos );
// Smallest dppx
// min-resolution is ">=" (larger than or equal to)
// "not min-resolution" is essentially "<"
- $media_query = 'not all and (min-resolution: ' . $logos[ 1 ]['dppx'] . 'dppx)';
+ $media_query = 'not all and (min-resolution: ' . $logos[1]['dppx'] . 'dppx)';
} elseif ( $i !== $logosCount - 1 ) {
// In between
// Media query expressions can only apply "not" to the entire expression
// (e.g. can't express ">= 1.5 and not >= 2).
// Workaround: Use <= 1.9999 in place of < 2.
- $upper_bound = floatval( $logos[ $i + 1 ]['dppx'] ) - 0.000001;
- $media_query = '(min-resolution: ' . $logos[ $i ]['dppx'] .
+ $upper_bound = floatval( $logos[$i + 1]['dppx'] ) - 0.000001;
+ $media_query = '(min-resolution: ' . $logos[$i]['dppx'] .
'dppx) and (max-resolution: ' . $upper_bound . 'dppx)';
} else {
// Largest dppx
- $media_query = '(min-resolution: ' . $logos[ $i ]['dppx'] . 'dppx)';
+ $media_query = '(min-resolution: ' . $logos[$i]['dppx'] . 'dppx)';
}
- $preloadLinks[ $logos[$i]['src'] ] = [ 'as' => 'image', 'media' => $media_query ];
+ $preloadLinks[$logos[$i]['src']] = [
+ 'as' => 'image',
+ 'media' => $media_query
+ ];
}
return $preloadLinks;
// Avoid including ids or timestamps of revision/page tables so
// that versions are not wasted
$title = new TitleValue( (int)$row->page_namespace, $row->page_title );
- $titleInfo[ self::makeTitleKey( $title ) ] = [
+ $titleInfo[self::makeTitleKey( $title )] = [
'page_len' => $row->page_len,
'page_latest' => $row->page_latest,
'page_touched' => $row->page_touched,
'Listgrants' => [ 'ListGrants' ],
'Listredirects' => [ 'ListRedirects' ],
'ListDuplicatedFiles' => [ 'ListDuplicatedFiles', 'ListFileDuplicates' ],
- 'Listusers' => [ 'ListUsers', 'UserList' ],
+ 'Listusers' => [ 'ListUsers', 'UserList', 'Users' ],
'Lockdb' => [ 'LockDB' ],
'Log' => [ 'Log', 'Logs' ],
'Lonelypages' => [ 'LonelyPages', 'OrphanedPages' ],
$i = $lb->getWriterIndex();
$this->assertEquals( null, $lb->getAnyOpenConnection( $i ) );
+
$conn1 = $lb->getConnection( $i );
$this->assertNotEquals( null, $conn1 );
$this->assertEquals( $conn1, $lb->getAnyOpenConnection( $i ) );
+ $this->assertFalse( $conn1->getFlag( DBO_TRX ) );
+
$conn2 = $lb->getConnection( $i, [], false, $lb::CONN_TRX_AUTOCOMMIT );
$this->assertNotEquals( null, $conn2 );
+ $this->assertFalse( $conn2->getFlag( DBO_TRX ) );
+
if ( $lb->getServerAttributes( $i )[Database::ATTR_DB_LEVEL_LOCKING] ) {
$this->assertEquals( null,
$lb->getAnyOpenConnection( $i, $lb::CONN_TRX_AUTOCOMMIT ) );
'type' => $wgDBtype,
'dbDirectory' => $wgSQLiteDataDir,
'load' => 0,
- 'flags' => DBO_TRX // REPEATABLE-READ for consistency
+ 'flags' => DBO_TRX // simulate a web request with DBO_TRX
],
];
$conn1->close();
$conn2->close();
}
+
+ public function testDBConnRefReadsMasterAndReplicaRoles() {
+ $lb = $this->newSingleServerLocalLoadBalancer();
+
+ $rConn = $lb->getConnectionRef( DB_REPLICA );
+ $wConn = $lb->getConnectionRef( DB_MASTER );
+ $wConn2 = $lb->getConnectionRef( 0 );
+
+ $v = [ 'value' => '1', '1' ];
+ $sql = 'SELECT MAX(1) AS value';
+ foreach ( [ $rConn, $wConn, $wConn2 ] as $conn ) {
+ $conn->clearFlag( $conn::DBO_TRX );
+
+ $res = $conn->query( $sql, __METHOD__ );
+ $this->assertEquals( $v, $conn->fetchRow( $res ) );
+
+ $res = $conn->query( $sql, __METHOD__, $conn::QUERY_REPLICA_ROLE );
+ $this->assertEquals( $v, $conn->fetchRow( $res ) );
+ }
+
+ $wConn->getScopedLockAndFlush( 'key', __METHOD__, 1 );
+ $wConn2->getScopedLockAndFlush( 'key2', __METHOD__, 1 );
+ }
+
+ /**
+ * @expectedException \Wikimedia\Rdbms\DBReadOnlyRoleError
+ */
+ public function testDBConnRefWritesReplicaRole() {
+ $lb = $this->newSingleServerLocalLoadBalancer();
+
+ $rConn = $lb->getConnectionRef( DB_REPLICA );
+
+ $rConn->query( 'DELETE FROM sometesttable WHERE 1=0' );
+ }
+
+ /**
+ * @expectedException \Wikimedia\Rdbms\DBReadOnlyRoleError
+ */
+ public function testDBConnRefWritesReplicaRoleIndex() {
+ $lb = $this->newMultiServerLocalLoadBalancer();
+
+ $rConn = $lb->getConnectionRef( 1 );
+
+ $rConn->query( 'DELETE FROM sometesttable WHERE 1=0' );
+ }
+
+ /**
+ * @expectedException \Wikimedia\Rdbms\DBReadOnlyRoleError
+ */
+ public function testDBConnRefWritesReplicaRoleInsert() {
+ $lb = $this->newMultiServerLocalLoadBalancer();
+
+ $rConn = $lb->getConnectionRef( DB_REPLICA );
+
+ $rConn->insert( 'test', [ 't' => 1 ], __METHOD__ );
+ }
}
*/
private function getDBConnRef( ILoadBalancer $lb = null ) {
$lb = $lb ?: $this->getLoadBalancerMock();
- return new DBConnRef( $lb, $this->getDatabaseMock() );
+ return new DBConnRef( $lb, $this->getDatabaseMock(), DB_MASTER );
}
public function testConstruct() {
$lb = $this->getLoadBalancerMock();
- $ref = new DBConnRef( $lb, $this->getDatabaseMock() );
+ $ref = new DBConnRef( $lb, $this->getDatabaseMock(), DB_MASTER );
$this->assertInstanceOf( ResultWrapper::class, $ref->select( 'whatever', '*' ) );
}
$ref = new DBConnRef(
$lb,
- [ DB_MASTER, [ 'test' ], 'dummy', ILoadBalancer::CONN_TRX_AUTOCOMMIT ]
+ [ DB_MASTER, [ 'test' ], 'dummy', ILoadBalancer::CONN_TRX_AUTOCOMMIT ],
+ DB_MASTER
);
$this->assertInstanceOf( ResultWrapper::class, $ref->select( 'whatever', '*' ) );
+ $this->assertEquals( DB_MASTER, $ref->getReferenceRole() );
+
+ $ref2 = new DBConnRef(
+ $lb,
+ [ DB_MASTER, [ 'test' ], 'dummy', ILoadBalancer::CONN_TRX_AUTOCOMMIT ],
+ DB_REPLICA
+ );
+ $this->assertEquals( DB_REPLICA, $ref2->getReferenceRole() );
}
public function testDestruct() {
$this->setExpectedException( InvalidArgumentException::class, '' );
$lb = $this->getLoadBalancerMock();
- new DBConnRef( $lb, 17 ); // bad constructor argument
+ new DBConnRef( $lb, 17, DB_REPLICA ); // bad constructor argument
}
/**
$lb->expects( $this->never() )
->method( 'getConnection' );
- $ref = new DBConnRef( $lb, [ DB_REPLICA, [], 'dummy', 0 ] );
+ $ref = new DBConnRef( $lb, [ DB_REPLICA, [], 'dummy', 0 ], DB_REPLICA );
$this->assertSame( 'dummy', $ref->getDomainID() );
}
$this->assertInternalType( 'string', $ref->__toString() );
$lb = $this->getLoadBalancerMock();
- $ref = new DBConnRef( $lb, [ DB_MASTER, [], 'test', 0 ] );
+ $ref = new DBConnRef( $lb, [ DB_MASTER, [], 'test', 0 ], DB_MASTER );
$this->assertInternalType( 'string', $ref->__toString() );
}
*/
public function testClose() {
$lb = $this->getLoadBalancerMock();
- $ref = new DBConnRef( $lb, [ DB_REPLICA, [], 'dummy', 0 ] );
+ $ref = new DBConnRef( $lb, [ DB_REPLICA, [], 'dummy', 0 ], DB_MASTER );
$ref->close();
}
+
+ /**
+ * @covers Wikimedia\Rdbms\DBConnRef::getReferenceRole
+ */
+ public function testGetReferenceRole() {
+ $lb = $this->getLoadBalancerMock();
+ $ref = new DBConnRef( $lb, [ DB_REPLICA, [], 'dummy', 0 ], DB_REPLICA );
+ $this->assertSame( DB_REPLICA, $ref->getReferenceRole() );
+
+ $ref = new DBConnRef( $lb, [ DB_MASTER, [], 'dummy', 0 ], DB_MASTER );
+ $this->assertSame( DB_MASTER, $ref->getReferenceRole() );
+
+ $ref = new DBConnRef( $lb, [ 1, [], 'dummy', 0 ], DB_REPLICA );
+ $this->assertSame( DB_REPLICA, $ref->getReferenceRole() );
+
+ $ref = new DBConnRef( $lb, [ 0, [], 'dummy', 0 ], DB_MASTER );
+ $this->assertSame( DB_MASTER, $ref->getReferenceRole() );
+ }
+
+ /**
+ * @covers Wikimedia\Rdbms\DBConnRef::getReferenceRole
+ * @expectedException Wikimedia\Rdbms\DBReadOnlyRoleError
+ * @dataProvider provideRoleExceptions
+ */
+ public function testRoleExceptions( $method, $args ) {
+ $lb = $this->getLoadBalancerMock();
+ $ref = new DBConnRef( $lb, [ DB_REPLICA, [], 'dummy', 0 ], DB_REPLICA );
+ $ref->$method( ...$args );
+ }
+
+ function provideRoleExceptions() {
+ return [
+ [ 'insert', [ 'table', [ 'a' => 1 ] ] ],
+ [ 'update', [ 'table', [ 'a' => 1 ], [ 'a' => 2 ] ] ],
+ [ 'delete', [ 'table', [ 'a' => 1 ] ] ],
+ [ 'replace', [ 'table', [ 'a' ], [ 'a' => 1 ] ] ],
+ [ 'upsert', [ 'table', [ 'a' => 1 ], [ 'a' ], [ 'a = a + 1' ] ] ],
+ [ 'lock', [ 'k', 'method' ] ],
+ [ 'unlock', [ 'k', 'method' ] ],
+ [ 'getScopedLockAndFlush', [ 'k', 'method', 1 ] ]
+ ];
+ }
}