// Lock the `category` row before locking `categorylinks` rows to try
// to avoid deadlocks with LinksDeletionUpdate (T195397)
- $dbw->selectField(
- 'category',
- 1,
- [ 'cat_title' => $this->mName ],
- __METHOD__,
- [ 'FOR UPDATE' ]
- );
+ $dbw->lockForUpdate( 'category', [ 'cat_title' => $this->mName ], __METHOD__ );
// Lock all the `categorylinks` records and gaps for this category;
// this is a separate query due to postgres/oracle limitations
return $permCheck;
}
+ $this->dbw->startAtomic( __METHOD__ );
+
$this->dbw->update(
'revision',
[ 'rev_page' => $this->dest->getArticleID() ],
// Check if this did anything
$this->revisionsMerged = $this->dbw->affectedRows();
if ( $this->revisionsMerged < 1 ) {
+ $this->dbw->endAtomic( __METHOD__ );
$status->fatal( 'mergehistory-fail-no-change' );
+
return $status;
}
// Make the source page a redirect if no revisions are left
- $haveRevisions = $this->dbw->selectField(
+ $haveRevisions = $this->dbw->lockForUpdate(
'revision',
- 'rev_timestamp',
[ 'rev_page' => $this->source->getArticleID() ],
- __METHOD__,
- [ 'FOR UPDATE' ]
+ __METHOD__
);
if ( !$haveRevisions ) {
if ( $reason ) {
Hooks::run( 'ArticleMergeComplete', [ $this->source, $this->dest ] );
+ $this->dbw->endAtomic( __METHOD__ );
+
return $status;
}
}
$status = $repo->newGood();
$dbw = $this->db;
- $hasCurrent = $dbw->selectField(
+ $hasCurrent = $dbw->lockForUpdate(
'image',
- '1',
[ 'img_name' => $this->oldName ],
- __METHOD__,
- [ 'FOR UPDATE' ]
+ __METHOD__
);
- $oldRowCount = $dbw->selectRowCount(
+ $oldRowCount = $dbw->lockForUpdate(
'oldimage',
- '*',
[ 'oi_name' => $this->oldName ],
- __METHOD__,
- [ 'FOR UPDATE' ]
+ __METHOD__
);
if ( $hasCurrent ) {
return $this->__call( __FUNCTION__, func_get_args() );
}
+ public function lockForUpdate(
+ $table, $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() );
}
return $column;
}
+ public function lockForUpdate(
+ $table, $conds = '', $fname = __METHOD__, $options = [], $join_conds = []
+ ) {
+ if ( !$this->trxLevel && !$this->getFlag( self::DBO_TRX ) ) {
+ throw new DBUnexpectedError(
+ $this,
+ __METHOD__ . ': no transaction is active nor is DBO_TRX set'
+ );
+ }
+
+ $options = (array)$options;
+ $options[] = 'FOR UPDATE';
+
+ return $this->selectRowCount( $table, '*', $conds, $fname, $options, $join_conds );
+ }
+
/**
* Removes most variables from an SQL query and replaces them with X or N for numbers.
* It's only slightly flawed. Don't use for anything important.
$tables, $var = '*', $conds = '', $fname = __METHOD__, $options = [], $join_conds = []
);
+ /**
+ * Lock all rows meeting the given conditions/options FOR UPDATE
+ *
+ * @param array|string $table Table names
+ * @param array|string $conds Filters on the table
+ * @param string $fname Function name for profiling
+ * @param array $options Options for select ("FOR UPDATE" is added automatically)
+ * @param array $join_conds Join conditions
+ * @return int Number of matching rows found (and locked)
+ * @since 1.32
+ */
+ public function lockForUpdate(
+ $table, $conds = '', $fname = __METHOD__, $options = [], $join_conds = []
+ );
+
/**
* Determines whether a field exists in a table
*
// Note array_intersect() preserves keys from the first arg, and we're
// assuming $revQuery has `revision` primary and isn't using subtables
// for anything we care about.
- $res = $dbw->select(
+ $dbw->lockForUpdate(
array_intersect(
$revQuery['tables'],
[ 'revision', 'revision_comment_temp', 'revision_actor_temp' ]
),
- '1',
[ 'rev_page' => $id ],
__METHOD__,
- 'FOR UPDATE',
+ [],
$revQuery['joins']
);
- foreach ( $res as $row ) {
- // Fetch all rows in case the DB needs that to properly lock them.
- }
// Get all of the page revisions
$res = $dbw->select(
];
}
+ /**
+ * @dataProvider provideLockForUpdate
+ * @covers Wikimedia\Rdbms\Database::lockForUpdate
+ */
+ public function testLockForUpdate( $sql, $sqlText ) {
+ $this->database->startAtomic( __METHOD__ );
+ $this->database->lockForUpdate(
+ $sql['tables'],
+ $sql['conds'] ?? [],
+ __METHOD__,
+ $sql['options'] ?? [],
+ $sql['join_conds'] ?? []
+ );
+ $this->database->endAtomic( __METHOD__ );
+
+ $this->assertLastSql( "BEGIN; $sqlText; COMMIT" );
+ }
+
+ public static function provideLockForUpdate() {
+ return [
+ [
+ [
+ 'tables' => [ 'table' ],
+ 'conds' => [ 'field' => [ 1, 2, 3, 4 ] ],
+ ],
+ "SELECT COUNT(*) AS rowcount FROM " .
+ "(SELECT 1 FROM table WHERE field IN ('1','2','3','4') " .
+ "FOR UPDATE) tmp_count"
+ ],
+ [
+ [
+ 'tables' => [ 'table', 't2' => 'table2' ],
+ 'conds' => [ 'field' => 'text' ],
+ 'options' => [ 'LIMIT' => 1, 'ORDER BY' => 'field' ],
+ 'join_conds' => [ 't2' => [
+ 'LEFT JOIN', 'tid = t2.id'
+ ] ],
+ ],
+ "SELECT COUNT(*) AS rowcount FROM " .
+ "(SELECT 1 FROM table LEFT JOIN table2 t2 ON ((tid = t2.id)) " .
+ "WHERE field = 'text' ORDER BY field LIMIT 1 FOR UPDATE) tmp_count"
+ ],
+ [
+ [
+ 'tables' => 'table',
+ ],
+ "SELECT COUNT(*) AS rowcount FROM " .
+ "(SELECT 1 FROM table FOR UPDATE) tmp_count"
+ ],
+ ];
+ }
+
/**
* @covers Wikimedia\Rdbms\Subquery
* @dataProvider provideSelectRowCount