return $values;
}
- public function setMulti( array $data, $expiry = 0 ) {
+ public function setMulti( array $data, $expiry = 0, $flags = 0 ) {
+ return $this->insertMulti( $data, $expiry, $flags, true );
+ }
+
+ private function insertMulti( array $data, $expiry, $flags, $replace ) {
$keysByTable = [];
foreach ( $data as $key => $value ) {
list( $serverIndex, $tableName ) = $this->getTableByKey( $key );
if ( $exptime == 0 ) {
$encExpiry = $this->getMaxDateTime( $db );
} else {
- $exptime = $this->convertExpiry( $exptime );
+ $exptime = $this->convertToExpiry( $exptime );
$encExpiry = $db->timestamp( $exptime );
}
foreach ( $serverKeys as $tableName => $tableKeys ) {
}
try {
- $db->replace(
- $tableName,
- [ 'keyname' ],
- $rows,
- __METHOD__
- );
+ if ( $replace ) {
+ $db->replace( $tableName, [ 'keyname' ], $rows, __METHOD__ );
+ } else {
+ $db->insert( $tableName, $rows, __METHOD__, [ 'IGNORE' ] );
+ $result = ( $db->affectedRows() > 0 && $result );
+ }
} catch ( DBError $e ) {
$this->handleWriteError( $e, $db, $serverIndex );
$result = false;
}
}
+ }
+ if ( ( $flags & self::WRITE_SYNC ) == self::WRITE_SYNC ) {
+ $result = $this->waitForReplication() && $result;
}
return $result;
public function set( $key, $value, $exptime = 0, $flags = 0 ) {
$ok = $this->setMulti( [ $key => $value ], $exptime );
- if ( ( $flags & self::WRITE_SYNC ) == self::WRITE_SYNC ) {
- $ok = $this->waitForReplication() && $ok;
- }
return $ok;
}
- protected function cas( $casToken, $key, $value, $exptime = 0 ) {
+ public function add( $key, $value, $exptime = 0, $flags = 0 ) {
+ $added = $this->insertMulti( [ $key => $value ], $exptime, $flags, false );
+
+ return $added;
+ }
+
+ protected function cas( $casToken, $key, $value, $exptime = 0, $flags = 0 ) {
list( $serverIndex, $tableName ) = $this->getTableByKey( $key );
$db = null;
$silenceScope = $this->silenceTransactionProfiler();
if ( $exptime == 0 ) {
$encExpiry = $this->getMaxDateTime( $db );
} else {
- $exptime = $this->convertExpiry( $exptime );
+ $exptime = $this->convertToExpiry( $exptime );
$encExpiry = $db->timestamp( $exptime );
}
// (T26425) use a replace if the db supports it instead of
return (bool)$db->affectedRows();
}
- public function delete( $key ) {
- list( $serverIndex, $tableName ) = $this->getTableByKey( $key );
- $db = null;
+ public function deleteMulti( array $keys, $flags = 0 ) {
+ $keysByTable = [];
+ foreach ( $keys as $key ) {
+ list( $serverIndex, $tableName ) = $this->getTableByKey( $key );
+ $keysByTable[$serverIndex][$tableName][] = $key;
+ }
+
+ $result = true;
$silenceScope = $this->silenceTransactionProfiler();
- try {
- $db = $this->getDB( $serverIndex );
- $db->delete(
- $tableName,
- [ 'keyname' => $key ],
- __METHOD__ );
- } catch ( DBError $e ) {
- $this->handleWriteError( $e, $db, $serverIndex );
- return false;
+ foreach ( $keysByTable as $serverIndex => $serverKeys ) {
+ $db = null;
+ try {
+ $db = $this->getDB( $serverIndex );
+ } catch ( DBError $e ) {
+ $this->handleWriteError( $e, $db, $serverIndex );
+ $result = false;
+ continue;
+ }
+
+ foreach ( $serverKeys as $tableName => $tableKeys ) {
+ try {
+ $db->delete( $tableName, [ 'keyname' => $tableKeys ], __METHOD__ );
+ } catch ( DBError $e ) {
+ $this->handleWriteError( $e, $db, $serverIndex );
+ $result = false;
+ }
+
+ }
}
- return true;
+ if ( ( $flags & self::WRITE_SYNC ) == self::WRITE_SYNC ) {
+ $result = $this->waitForReplication() && $result;
+ }
+
+ return $result;
+ }
+
+ public function delete( $key, $flags = 0 ) {
+ $ok = $this->deleteMulti( [ $key ], $flags );
+
+ return $ok;
}
public function incr( $key, $step = 1 ) {
[ 'value', 'exptime' ],
[ 'keyname' => $key ],
__METHOD__,
- [ 'FOR UPDATE' ] );
+ [ 'FOR UPDATE' ]
+ );
if ( $row === false ) {
// Missing
-
- return null;
+ return false;
}
$db->delete( $tableName, [ 'keyname' => $key ], __METHOD__ );
if ( $this->isExpired( $db, $row->exptime ) ) {
// Expired, do not reinsert
-
- return null;
+ return false;
}
$oldValue = intval( $this->unserialize( $db->decodeBlob( $row->value ) ) );
$newValue = $oldValue + $step;
- $db->insert( $tableName,
+ $db->insert(
+ $tableName,
[
'keyname' => $key,
'value' => $db->encodeBlob( $this->serialize( $newValue ) ),
'exptime' => $row->exptime
- ], __METHOD__, 'IGNORE' );
+ ],
+ __METHOD__,
+ 'IGNORE'
+ );
if ( $db->affectedRows() == 0 ) {
// Race condition. See T30611
- $newValue = null;
+ $newValue = false;
}
} catch ( DBError $e ) {
$this->handleWriteError( $e, $db, $serverIndex );
}
public function merge( $key, callable $callback, $exptime = 0, $attempts = 10, $flags = 0 ) {
- $ok = $this->mergeViaCas( $key, $callback, $exptime, $attempts );
+ $ok = $this->mergeViaCas( $key, $callback, $exptime, $attempts, $flags );
if ( ( $flags & self::WRITE_SYNC ) == self::WRITE_SYNC ) {
$ok = $this->waitForReplication() && $ok;
}
return $ok;
}
- public function changeTTL( $key, $expiry = 0 ) {
+ public function changeTTL( $key, $expiry = 0, $flags = 0 ) {
list( $serverIndex, $tableName ) = $this->getTableByKey( $key );
$db = null;
$silenceScope = $this->silenceTransactionProfiler();
$db = $this->getDB( $serverIndex );
$db->update(
$tableName,
- [ 'exptime' => $db->timestamp( $this->convertExpiry( $expiry ) ) ],
+ [ 'exptime' => $db->timestamp( $this->convertToExpiry( $expiry ) ) ],
[ 'keyname' => $key, 'exptime > ' . $db->addQuotes( $db->timestamp( time() ) ) ],
__METHOD__
);
if ( $exception instanceof DBConnectionError ) {
$this->markServerDown( $exception, $serverIndex );
}
- $this->logger->error( "DBError: {$exception->getMessage()}" );
- if ( $exception instanceof DBConnectionError ) {
- $this->setLastError( BagOStuff::ERR_UNREACHABLE );
- $this->logger->debug( __METHOD__ . ": ignoring connection error" );
- } else {
- $this->setLastError( BagOStuff::ERR_UNEXPECTED );
- $this->logger->debug( __METHOD__ . ": ignoring query error" );
- }
+
+ $this->setAndLogDBError( $exception );
}
/**
protected function handleWriteError( DBError $exception, IDatabase $db = null, $serverIndex ) {
if ( !$db ) {
$this->markServerDown( $exception, $serverIndex );
- } elseif ( $db->wasReadOnlyError() ) {
- if ( $db->trxLevel() && $this->usesMainDB() ) {
- // Errors like deadlocks and connection drops already cause rollback.
- // For consistency, we have no choice but to throw an error and trigger
- // complete rollback if the main DB is also being used as the cache DB.
- throw $exception;
- }
}
+ $this->setAndLogDBError( $exception );
+ }
+
+ /**
+ * @param DBError $exception
+ */
+ private function setAndLogDBError( DBError $exception ) {
$this->logger->error( "DBError: {$exception->getMessage()}" );
if ( $exception instanceof DBConnectionError ) {
$this->setLastError( BagOStuff::ERR_UNREACHABLE );
}
// Main LB is used; wait for any replica DBs to catch up
- $masterPos = $lb->getMasterPos();
- if ( !$masterPos ) {
- return true; // not applicable
- }
+ try {
+ $masterPos = $lb->getMasterPos();
+ if ( !$masterPos ) {
+ return true; // not applicable
+ }
- $loop = new WaitConditionLoop(
- function () use ( $lb, $masterPos ) {
- return $lb->waitForAll( $masterPos, 1 );
- },
- $this->syncTimeout,
- $this->busyCallbacks
- );
+ $loop = new WaitConditionLoop(
+ function () use ( $lb, $masterPos ) {
+ return $lb->waitForAll( $masterPos, 1 );
+ },
+ $this->syncTimeout,
+ $this->busyCallbacks
+ );
+
+ return ( $loop->invoke() === $loop::CONDITION_REACHED );
+ } catch ( DBError $e ) {
+ $this->setAndLogDBError( $e );
- return ( $loop->invoke() === $loop::CONDITION_REACHED );
+ return false;
+ }
}
/**