/** @var int[] Map of (ATTR_* class constant => QOS_* class constant) */
protected $attrMap = [];
- /** Bitfield constants for get()/getMulti() */
- const READ_LATEST = 1; // use latest data for replicated stores
- const READ_VERIFIED = 2; // promise that caller can tell when keys are stale
- /** Bitfield constants for set()/merge() */
- const WRITE_SYNC = 4; // synchronously write to all locations for replicated stores
- const WRITE_CACHE_ONLY = 8; // Only change state of the in-memory cache
- const WRITE_ALLOW_SEGMENTS = 16; // Allow partitioning of the value if it is large
- const WRITE_PRUNE_SEGMENTS = 32; // Delete all partition segments of the value
+ /** Bitfield constants for get()/getMulti(); these are only advisory */
+ const READ_LATEST = 1; // if supported, avoid reading stale data due to replication
+ const READ_VERIFIED = 2; // promise that the caller handles detection of staleness
+ /** Bitfield constants for set()/merge(); these are only advisory */
+ const WRITE_SYNC = 4; // if supported, block until the write is fully replicated
+ const WRITE_CACHE_ONLY = 8; // only change state of the in-memory cache
+ const WRITE_ALLOW_SEGMENTS = 16; // allow partitioning of the value if it is large
+ const WRITE_PRUNE_SEGMENTS = 32; // delete all the segments if the value is partitioned
+ const WRITE_BACKGROUND = 64; // if supported,
/** @var string Component to use for key construction of blob segment keys */
const SEGMENT_COMPONENT = 'segment';
*/
public function set( $key, $value, $exptime = 0, $flags = 0 ) {
if (
+ is_int( $value ) || // avoid breaking incr()/decr()
( $flags & self::WRITE_ALLOW_SEGMENTS ) != self::WRITE_ALLOW_SEGMENTS ||
is_infinite( $this->segmentationSize )
) {
$segmentHashes[] = $hash;
}
+ $flags &= ~self::WRITE_ALLOW_SEGMENTS; // sanity
$ok = $this->setMulti( $chunksByKey, $exptime, $flags );
if ( $ok ) {
// Only when all segments are stored should the main key be changed
* @param int $flags Bitfield of BagOStuff::WRITE_* constants
* @return bool Success
*/
- protected function mergeViaCas( $key, $callback, $exptime = 0, $attempts = 10, $flags = 0 ) {
+ final protected function mergeViaCas( $key, callable $callback, $exptime, $attempts, $flags ) {
do {
$casToken = null; // passed by reference
// Get the old value and CAS token from cache
* @since 1.28
*/
public function changeTTL( $key, $exptime = 0, $flags = 0 ) {
+ return $this->doChangeTTL( $key, $exptime, $flags );
+ }
+
+ /**
+ * @param string $key
+ * @param int $exptime
+ * @param int $flags
+ * @return bool
+ */
+ protected function doChangeTTL( $key, $exptime, $flags ) {
$expiry = $this->convertToExpiry( $exptime );
$delete = ( $expiry != 0 && $expiry < $this->getCurrentTime() );
/**
* Delete all objects expiring before a certain date.
- * @param string $date The reference date in MW format
- * @param callable|bool $progressCallback Optional, a function which will be called
+ * @param string|int $timestamp The reference date in MW or TS_UNIX format
+ * @param callable|null $progress Optional, a function which will be called
* regularly during long-running operations with the percentage progress
* as the first parameter. [optional]
* @param int $limit Maximum number of keys to delete [default: INF]
*
- * @return bool Success, false if unimplemented
+ * @return bool Success; false if unimplemented
*/
- public function deleteObjectsExpiringBefore( $date, $progressCallback = false, $limit = INF ) {
- // stub
+ public function deleteObjectsExpiringBefore(
+ $timestamp,
+ callable $progress = null,
+ $limit = INF
+ ) {
return false;
}
/**
* Get an associative array containing the item for each of the keys that have items.
- * @param string[] $keys List of keys
+ * @param string[] $keys List of keys; can be a map of (unused => key) for convenience
* @param int $flags Bitfield; supports READ_LATEST [optional]
- * @return array
+ * @return mixed[] Map of (key => value) for existing keys; preserves the order of $keys
*/
public function getMulti( array $keys, $flags = 0 ) {
- $valuesBykey = $this->doGetMulti( $keys, $flags );
- foreach ( $valuesBykey as $key => $value ) {
+ $foundByKey = $this->doGetMulti( $keys, $flags );
+
+ $res = [];
+ foreach ( $keys as $key ) {
// Resolve one blob at a time (avoids too much I/O at once)
- $valuesBykey[$key] = $this->resolveSegments( $key, $value );
+ if ( array_key_exists( $key, $foundByKey ) ) {
+ // A value should not appear in the key if a segment is missing
+ $value = $this->resolveSegments( $key, $foundByKey[$key] );
+ if ( $value !== false ) {
+ $res[$key] = $value;
+ }
+ }
}
- return $valuesBykey;
+ return $res;
}
/**
* Get an associative array containing the item for each of the keys that have items.
* @param string[] $keys List of keys
* @param int $flags Bitfield; supports READ_LATEST [optional]
- * @return array
+ * @return mixed[] Map of (key => value) for existing keys
*/
protected function doGetMulti( array $keys, $flags = 0 ) {
$res = [];
*
* This does not support WRITE_ALLOW_SEGMENTS to avoid excessive read I/O
*
+ * WRITE_BACKGROUND can be used for bulk insertion where the response is not vital
+ *
* @param mixed[] $data Map of (key => value)
* @param int $exptime Either an interval in seconds or a unix timestamp for expiry
* @param int $flags Bitfield of BagOStuff::WRITE_* constants (since 1.33)
if ( ( $flags & self::WRITE_ALLOW_SEGMENTS ) === self::WRITE_ALLOW_SEGMENTS ) {
throw new InvalidArgumentException( __METHOD__ . ' got WRITE_ALLOW_SEGMENTS' );
}
+ return $this->doSetMulti( $data, $exptime, $flags );
+ }
+ /**
+ * @param mixed[] $data Map of (key => value)
+ * @param int $exptime Either an interval in seconds or a unix timestamp for expiry
+ * @param int $flags Bitfield of BagOStuff::WRITE_* constants
+ * @return bool Success
+ */
+ protected function doSetMulti( array $data, $exptime = 0, $flags = 0 ) {
$res = true;
foreach ( $data as $key => $value ) {
$res = $this->doSet( $key, $value, $exptime, $flags ) && $res;
}
-
return $res;
}
*
* This does not support WRITE_ALLOW_SEGMENTS to avoid excessive read I/O
*
+ * WRITE_BACKGROUND can be used for bulk deletion where the response is not vital
+ *
* @param string[] $keys List of keys
* @param int $flags Bitfield of BagOStuff::WRITE_* constants
* @return bool Success
* @since 1.33
*/
public function deleteMulti( array $keys, $flags = 0 ) {
+ if ( ( $flags & self::WRITE_ALLOW_SEGMENTS ) === self::WRITE_ALLOW_SEGMENTS ) {
+ throw new InvalidArgumentException( __METHOD__ . ' got WRITE_ALLOW_SEGMENTS' );
+ }
+ return $this->doDeleteMulti( $keys, $flags );
+ }
+
+ /**
+ * @param string[] $keys List of keys
+ * @param int $flags Bitfield of BagOStuff::WRITE_* constants
+ * @return bool Success
+ */
+ protected function doDeleteMulti( array $keys, $flags = 0 ) {
$res = true;
foreach ( $keys as $key ) {
$res = $this->doDelete( $key, $flags ) && $res;
}
+ return $res;
+ }
+
+ /**
+ * Change the expiration of multiple keys that exist
+ *
+ * @see BagOStuff::changeTTL()
+ *
+ * @param string[] $keys List of keys
+ * @param int $exptime TTL or UNIX timestamp
+ * @param int $flags Bitfield of BagOStuff::WRITE_* constants (since 1.33)
+ * @return bool Success
+ * @since 1.34
+ */
+ public function changeTTLMulti( array $keys, $exptime, $flags = 0 ) {
+ $res = true;
+ foreach ( $keys as $key ) {
+ $res = $this->doChangeTTL( $key, $exptime, $flags ) && $res;
+ }
return $res;
}
* @param mixed $mainValue
* @return string|null|bool The combined string, false if missing, null on error
*/
- protected function resolveSegments( $key, $mainValue ) {
+ final protected function resolveSegments( $key, $mainValue ) {
if ( SerializedValueContainer::isUnified( $mainValue ) ) {
return $this->unserialize( $mainValue->{SerializedValueContainer::UNIFIED_DATA} );
}
* @param callable $workCallback
* @since 1.28
*/
- public function addBusyCallback( callable $workCallback ) {
+ final public function addBusyCallback( callable $workCallback ) {
$this->busyCallbacks[] = $workCallback;
}
*/
protected function debug( $text ) {
if ( $this->debugMode ) {
- $this->logger->debug( "{class} debug: $text", [
- 'class' => static::class,
- ] );
+ $this->logger->debug( "{class} debug: $text", [ 'class' => static::class ] );
}
}
* @param int $exptime
* @return bool
*/
- protected function expiryIsRelative( $exptime ) {
+ final protected function expiryIsRelative( $exptime ) {
return ( $exptime != 0 && $exptime < ( 10 * self::TTL_YEAR ) );
}
/**
- * Convert an optionally relative time to an absolute time
- * @param int $exptime
+ * Convert an optionally relative timestamp to an absolute time
+ *
+ * The input value will be cast to an integer and interpreted as follows:
+ * - zero: no expiry; return zero (e.g. TTL_INDEFINITE)
+ * - negative: relative TTL; return UNIX timestamp offset by this value
+ * - positive (< 10 years): relative TTL; return UNIX timestamp offset by this value
+ * - positive (>= 10 years): absolute UNIX timestamp; return this value
+ *
+ * @param int $exptime Absolute TTL or 0 for indefinite
* @return int
*/
- protected function convertToExpiry( $exptime ) {
- if ( $this->expiryIsRelative( $exptime ) ) {
- return (int)$this->getCurrentTime() + $exptime;
- } else {
- return $exptime;
- }
+ final protected function convertToExpiry( $exptime ) {
+ return $this->expiryIsRelative( $exptime )
+ ? (int)$this->getCurrentTime() + $exptime
+ : $exptime;
}
/**
* @param int $exptime
* @return int
*/
- protected function convertToRelative( $exptime ) {
- if ( $exptime >= ( 10 * self::TTL_YEAR ) ) {
- $exptime -= (int)$this->getCurrentTime();
- if ( $exptime <= 0 ) {
- $exptime = 1;
- }
- return $exptime;
- } else {
- return $exptime;
- }
+ final protected function convertToRelative( $exptime ) {
+ return $this->expiryIsRelative( $exptime )
+ ? (int)$exptime
+ : max( $exptime - (int)$this->getCurrentTime(), 1 );
}
/**
* @param mixed $value
* @return bool
*/
- protected function isInteger( $value ) {
+ final protected function isInteger( $value ) {
if ( is_int( $value ) ) {
return true;
} elseif ( !is_string( $value ) ) {
* @param BagOStuff[] $bags
* @return int[] Resulting flag map (class ATTR_* constant => class QOS_* constant)
*/
- protected function mergeFlagMaps( array $bags ) {
+ final protected function mergeFlagMaps( array $bags ) {
$map = [];
foreach ( $bags as $bag ) {
foreach ( $bag->attrMap as $attr => $rank ) {