if ( $this->conn ) {
// Resolve any dangling transaction first
if ( $this->trxLevel ) {
- // Meaningful transactions should ideally have been resolved by now
- if ( $this->writesOrCallbacksPending() ) {
- $this->queryLogger->warning(
- __METHOD__ . ": writes or callbacks still pending.",
- [ 'trace' => ( new RuntimeException() )->getTraceAsString() ]
- );
+ if ( $this->trxAtomicLevels ) {
// Cannot let incomplete atomic sections be committed
- if ( $this->trxAtomicLevels ) {
- $levels = $this->flatAtomicSectionList();
- $exception = new DBUnexpectedError(
- $this,
- __METHOD__ . ": atomic sections $levels are still open."
- );
- // Check if it is possible to properly commit and trigger callbacks
- } elseif ( $this->trxEndCallbacksSuppressed ) {
+ $levels = $this->flatAtomicSectionList();
+ $exception = new DBUnexpectedError(
+ $this,
+ __METHOD__ . ": atomic sections $levels are still open."
+ );
+ } elseif ( $this->trxAutomatic ) {
+ // Only the connection manager can commit non-empty DBO_TRX transactions
+ if ( $this->writesOrCallbacksPending() ) {
$exception = new DBUnexpectedError(
$this,
- __METHOD__ . ': callbacks are suppressed; cannot properly commit.'
+ __METHOD__ .
+ ": mass commit/rollback of peer transaction required (DBO_TRX set)."
);
}
+ } elseif ( $this->trxLevel ) {
+ // Commit explicit transactions as if this was commit()
+ $this->queryLogger->warning(
+ __METHOD__ . ": writes or callbacks still pending.",
+ [ 'trace' => ( new RuntimeException() )->getTraceAsString() ]
+ );
+ }
+
+ if ( $this->trxEndCallbacksSuppressed ) {
+ $exception = $exception ?: new DBUnexpectedError(
+ $this,
+ __METHOD__ . ': callbacks are suppressed; cannot properly commit.'
+ );
}
+
// Commit or rollback the changes and run any callbacks as needed
if ( $this->trxStatus === self::STATUS_TRX_OK && !$exception ) {
- $this->commit( __METHOD__, self::TRANSACTION_INTERNAL );
+ $this->commit(
+ __METHOD__,
+ $this->trxAutomatic ? self::FLUSHING_INTERNAL : self::FLUSHING_ONE
+ );
} else {
- $this->rollback( __METHOD__, self::TRANSACTION_INTERNAL );
+ $this->rollback( __METHOD__, self::FLUSHING_INTERNAL );
}
}
+
// Close the actual connection in the binding handle
$closed = $this->closeConnection();
$this->conn = false;
}
final public function begin( $fname = __METHOD__, $mode = self::TRANSACTION_EXPLICIT ) {
+ static $modes = [ self::TRANSACTION_EXPLICIT, self::TRANSACTION_INTERNAL ];
+ if ( !in_array( $mode, $modes, true ) ) {
+ throw new DBUnexpectedError( $this, "$fname: invalid mode parameter '$mode'." );
+ }
+
// Protect against mismatched atomic section, transaction nesting, and snapshot loss
if ( $this->trxLevel ) {
if ( $this->trxAtomicLevels ) {
$this->trxLevel = 1;
}
- final public function commit( $fname = __METHOD__, $flush = '' ) {
+ final public function commit( $fname = __METHOD__, $flush = self::FLUSHING_ONE ) {
+ static $modes = [ self::FLUSHING_ONE, self::FLUSHING_ALL_PEERS, self::FLUSHING_INTERNAL ];
+ if ( !in_array( $flush, $modes, true ) ) {
+ throw new DBUnexpectedError( $this, "$fname: invalid flush parameter '$flush'." );
+ }
+
if ( $this->trxLevel && $this->trxAtomicLevels ) {
- // There are still atomic sections open. This cannot be ignored
+ // There are still atomic sections open; this cannot be ignored
$levels = $this->flatAtomicSectionList();
throw new DBUnexpectedError(
$this,
/** @var string Atomic section is cancelable */
const ATOMIC_CANCELABLE = 'cancelable';
- /** @var string Transaction operation comes from service managing all DBs */
+ /** @var string Commit/rollback is from outside the IDatabase handle and connection manager */
+ const FLUSHING_ONE = '';
+ /** @var string Commit/rollback is from the connection manager for the IDatabase handle */
const FLUSHING_ALL_PEERS = 'flush';
- /** @var string Transaction operation comes from the database class internally */
+ /** @var string Commit/rollback is from the IDatabase handle internally */
const FLUSHING_INTERNAL = 'flush';
/** @var string Do not remember the prior flags */
* Returns the normalized form of the given page title, using the
* normalization rules of the given site. If the given title is a redirect,
* the redirect will be resolved and the redirect target is returned.
+ * Only titles of existing pages will be returned.
*
* @note This actually makes an API request to the remote site, so beware
* that this function is slow and depends on an external service.
* @param string $pageName
* @param string $apiUrl
*
- * @return string|false
+ * @return string|false The normalized form of the title,
+ * or false to indicate an invalid title, a missing page,
+ * or some other kind of error.
* @throws \MWException
*/
public function normalizePageName( $pageName, $apiUrl ) {
* Returns the normalized form of the given page title, using the
* normalization rules of the given site. If the given title is a redirect,
* the redirect will be resolved and the redirect target is returned.
+ * Only titles of existing pages will be returned.
*
* @note This actually makes an API request to the remote site, so beware
* that this function is slow and depends on an external service.
*
* @param string $pageName
*
- * @return string|false
+ * @return string|false The normalized form of the title,
+ * or false to indicate an invalid title, a missing page,
+ * or some other kind of error.
* @throws MWException
*/
public function normalizePageName( $pageName ) {
}
/**
- * Returns $pageName without changes.
- * Subclasses may override this to apply some kind of normalization.
+ * Attempt to normalize the page name in some fashion.
+ * May return false to indicate various kinds of failure.
+ *
+ * This implementation returns $pageName without changes.
*
* @see Site::normalizePageName
*
REPO_DIR=$(cd "$(dirname $0)/../.."; pwd) # Root dir of the git repo working tree
TARGET_DIR="resources/lib/oojs-ui" # Destination relative to the root of the repo
-NPM_DIR=$(mktemp -d 2>/dev/null || mktemp -d -t 'update-oojs-ui') # e.g. /tmp/update-oojs-ui.rI0I5Vir
+NPM_DIR=$(mktemp -d 2>/dev/null || mktemp -d -t 'update-ooui') # e.g. /tmp/update-ooui.rI0I5Vir
# Prepare working tree
cd "$REPO_DIR"
cp ./node_modules/oojs-ui/src/themes/wikimediaui/*.json "$REPO_DIR/$TARGET_DIR/themes/wikimediaui"
# Apex theme icons, indicators, and textures
-mkdir -p "$REPO_DIR/$TARGET_DIR/themes/apex/images/icons"
-cp ./node_modules/oojs-ui/dist/themes/apex/images/icons/*.{svg,png} "$REPO_DIR/$TARGET_DIR/themes/apex/images/icons"
-mkdir -p "$REPO_DIR/$TARGET_DIR/themes/apex/images/indicators"
-cp ./node_modules/oojs-ui/dist/themes/apex/images/indicators/*.{svg,png} "$REPO_DIR/$TARGET_DIR/themes/apex/images/indicators"
-mkdir -p "$REPO_DIR/$TARGET_DIR/themes/apex/images/textures"
-cp ./node_modules/oojs-ui/dist/themes/apex/images/textures/*.{gif,svg} "$REPO_DIR/$TARGET_DIR/themes/apex/images/textures"
-
cp ./node_modules/oojs-ui/src/themes/apex/*.json "$REPO_DIR/$TARGET_DIR/themes/apex"
# WikimediaUI LESS variables for sharing
$this->assertLastSql( 'BEGIN; DELETE FROM x WHERE field = \'3\'; ROLLBACK' );
$this->assertEquals( 0, $this->database->trxLevel() );
}
+
+ /**
+ * @covers \Wikimedia\Rdbms\Database::close
+ */
+ public function testPrematureClose3() {
+ try {
+ $this->database->setFlag( IDatabase::DBO_TRX );
+ $this->database->delete( 'x', [ 'field' => 3 ], __METHOD__ );
+ $this->assertEquals( 1, $this->database->trxLevel() );
+ $this->database->close();
+ $this->fail( 'Expected exception not thrown' );
+ } catch ( DBUnexpectedError $ex ) {
+ $this->assertSame(
+ 'Wikimedia\Rdbms\Database::close: ' .
+ 'mass commit/rollback of peer transaction required (DBO_TRX set).',
+ $ex->getMessage()
+ );
+ }
+
+ $this->assertFalse( $this->database->isOpen() );
+ $this->assertLastSql( 'BEGIN; DELETE FROM x WHERE field = \'3\'; ROLLBACK' );
+ $this->assertEquals( 0, $this->database->trxLevel() );
+ }
+
+ /**
+ * @covers \Wikimedia\Rdbms\Database::close
+ */
+ public function testPrematureClose4() {
+ $this->database->setFlag( IDatabase::DBO_TRX );
+ $this->database->query( 'SELECT 1', __METHOD__ );
+ $this->assertEquals( 1, $this->database->trxLevel() );
+ $this->database->close();
+ $this->database->clearFlag( IDatabase::DBO_TRX );
+
+ $this->assertFalse( $this->database->isOpen() );
+ $this->assertLastSql( 'BEGIN; SELECT 1; COMMIT' );
+ $this->assertEquals( 0, $this->database->trxLevel() );
+ }
}