Merge branch 'v2.x' into vv-added-missing-types

This commit is contained in:
Vladyslav Vildanov
2025-03-20 14:45:42 +02:00
committed by GitHub
6 changed files with 1226 additions and 17 deletions
+40
View File
@@ -0,0 +1,40 @@
<?php
/*
* This file is part of the Predis package.
*
* (c) 2009-2020 Daniele Alessandri
* (c) 2021-2025 Till Krüss
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Predis\Cluster;
/**
* Represents the gap between slot ranges.
*/
class NullSlotRange extends SlotRange
{
public function __construct(int $start, int $end)
{
parent::__construct($start, $end, '');
}
/**
* {@inheritDoc}
*/
public function toArray(): array
{
return [];
}
/**
* {@inheritDoc}
*/
public function count(): int
{
return 0;
}
}
+209
View File
@@ -0,0 +1,209 @@
<?php
/*
* This file is part of the Predis package.
*
* (c) 2009-2020 Daniele Alessandri
* (c) 2021-2025 Till Krüss
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Predis\Cluster;
use ArrayAccess;
use ArrayIterator;
use Countable;
use IteratorAggregate;
use OutOfBoundsException;
use Predis\Connection\NodeConnectionInterface;
use ReturnTypeWillChange;
use Traversable;
/**
* Slot map for redis-cluster.
*/
class SimpleSlotMap implements ArrayAccess, IteratorAggregate, Countable
{
private $slots = [];
/**
* Checks if the given slot is valid.
*
* @param int $slot Slot index.
*
* @return bool
*/
public static function isValid($slot)
{
return $slot >= 0x0000 && $slot <= 0x3FFF;
}
/**
* Checks if the given slot range is valid.
*
* @param int $first Initial slot of the range.
* @param int $last Last slot of the range.
*
* @return bool
*/
public static function isValidRange($first, $last)
{
return $first >= 0x0000 && $first <= 0x3FFF && $last >= 0x0000 && $last <= 0x3FFF && $first <= $last;
}
/**
* Resets the slot map.
*/
public function reset()
{
$this->slots = [];
}
/**
* Checks if the slot map is empty.
*
* @return bool
*/
public function isEmpty()
{
return empty($this->slots);
}
/**
* Returns the current slot map as a dictionary of $slot => $node.
*
* The order of the slots in the dictionary is not guaranteed.
*
* @return array
*/
public function toArray()
{
return $this->slots;
}
/**
* Returns the list of unique nodes in the slot map.
*
* @return array
*/
public function getNodes()
{
return array_keys(array_flip($this->slots));
}
/**
* Assigns the specified slot range to a node.
*
* @param int $first Initial slot of the range.
* @param int $last Last slot of the range.
* @param NodeConnectionInterface|string $connection ID or connection instance.
*
* @throws OutOfBoundsException
*/
public function setSlots($first, $last, $connection)
{
if (!static::isValidRange($first, $last)) {
throw new OutOfBoundsException("Invalid slot range $first-$last for `$connection`");
}
$this->slots += array_fill($first, $last - $first + 1, (string) $connection);
}
/**
* Returns the specified slot range.
*
* @param int $first Initial slot of the range.
* @param int $last Last slot of the range.
*
* @return array
*/
public function getSlots($first, $last)
{
if (!static::isValidRange($first, $last)) {
throw new OutOfBoundsException("Invalid slot range $first-$last");
}
return array_intersect_key($this->slots, array_fill($first, $last - $first + 1, null));
}
/**
* Checks if the specified slot is assigned.
*
* @param int $slot Slot index.
*
* @return bool
*/
#[ReturnTypeWillChange]
public function offsetExists($slot)
{
return isset($this->slots[$slot]);
}
/**
* Returns the node assigned to the specified slot.
*
* @param int $slot Slot index.
*
* @return string|null
*/
#[ReturnTypeWillChange]
public function offsetGet($slot)
{
return $this->slots[$slot] ?? null;
}
/**
* Assigns the specified slot to a node.
*
* @param int $slot Slot index.
* @param NodeConnectionInterface|string $connection ID or connection instance.
*
* @return void
*/
#[ReturnTypeWillChange]
public function offsetSet($slot, $connection)
{
if (!static::isValid($slot)) {
throw new OutOfBoundsException("Invalid slot $slot for `$connection`");
}
$this->slots[(int) $slot] = (string) $connection;
}
/**
* Returns the node assigned to the specified slot.
*
* @param int $slot Slot index.
*
* @return void
*/
#[ReturnTypeWillChange]
public function offsetUnset($slot)
{
unset($this->slots[$slot]);
}
/**
* Returns the current number of assigned slots.
*
* @return int
*/
#[ReturnTypeWillChange]
public function count()
{
return count($this->slots);
}
/**
* Returns an iterator over the slot map.
*
* @return Traversable<int, string>
*/
#[ReturnTypeWillChange]
public function getIterator()
{
return new ArrayIterator($this->slots);
}
}
+225 -17
View File
@@ -22,11 +22,16 @@ use ReturnTypeWillChange;
use Traversable;
/**
* Slot map for redis-cluster.
* Compact slot map for redis-cluster.
*/
class SlotMap implements ArrayAccess, IteratorAggregate, Countable
{
private $slots = [];
/**
* Slot ranges list.
*
* @var SlotRange[]
*/
private $slotRanges = [];
/**
* Checks if the given slot is valid.
@@ -37,7 +42,7 @@ class SlotMap implements ArrayAccess, IteratorAggregate, Countable
*/
public static function isValid($slot)
{
return $slot >= 0x0000 && $slot <= 0x3FFF;
return $slot >= 0 && $slot <= SlotRange::MAX_SLOTS;
}
/**
@@ -50,7 +55,7 @@ class SlotMap implements ArrayAccess, IteratorAggregate, Countable
*/
public static function isValidRange($first, $last)
{
return $first >= 0x0000 && $first <= 0x3FFF && $last >= 0x0000 && $last <= 0x3FFF && $first <= $last;
return SlotRange::isValidRange($first, $last);
}
/**
@@ -58,7 +63,7 @@ class SlotMap implements ArrayAccess, IteratorAggregate, Countable
*/
public function reset()
{
$this->slots = [];
$this->slotRanges = [];
}
/**
@@ -68,7 +73,7 @@ class SlotMap implements ArrayAccess, IteratorAggregate, Countable
*/
public function isEmpty()
{
return empty($this->slots);
return empty($this->slotRanges);
}
/**
@@ -80,7 +85,13 @@ class SlotMap implements ArrayAccess, IteratorAggregate, Countable
*/
public function toArray()
{
return $this->slots;
return array_reduce(
$this->slotRanges,
function ($carry, $slotRange) {
return $carry + $slotRange->toArray();
},
[]
);
}
/**
@@ -90,7 +101,22 @@ class SlotMap implements ArrayAccess, IteratorAggregate, Countable
*/
public function getNodes()
{
return array_keys(array_flip($this->slots));
return array_unique(array_map(
function ($slotRange) {
return $slotRange->getConnection();
},
$this->slotRanges
));
}
/**
* Returns the list of slot ranges.
*
* @return SlotRange[]
*/
public function getSlotRanges()
{
return $this->slotRanges;
}
/**
@@ -108,7 +134,31 @@ class SlotMap implements ArrayAccess, IteratorAggregate, Countable
throw new OutOfBoundsException("Invalid slot range $first-$last for `$connection`");
}
$this->slots += array_fill($first, $last - $first + 1, (string) $connection);
$targetSlotRange = new SlotRange($first, $last, (string) $connection);
// Get gaps of slot ranges list.
$gaps = $this->getGaps($this->slotRanges);
$results = $this->slotRanges;
foreach ($gaps as $gap) {
if (!$gap->hasIntersectionWith($targetSlotRange)) {
continue;
}
// Get intersection of the gap and target slot range.
$results[] = new SlotRange(
max($gap->getStart(), $targetSlotRange->getStart()),
min($gap->getEnd(), $targetSlotRange->getEnd()),
$targetSlotRange->getConnection()
);
}
$this->sortSlotRanges($results);
$results = $this->compactSlotRanges($results);
$this->slotRanges = $results;
}
/**
@@ -117,7 +167,7 @@ class SlotMap implements ArrayAccess, IteratorAggregate, Countable
* @param int $first Initial slot of the range.
* @param int $last Last slot of the range.
*
* @return array
* @return array<int, string>
*/
public function getSlots($first, $last)
{
@@ -125,7 +175,28 @@ class SlotMap implements ArrayAccess, IteratorAggregate, Countable
throw new OutOfBoundsException("Invalid slot range $first-$last");
}
return array_intersect_key($this->slots, array_fill($first, $last - $first + 1, null));
$placeHolder = new NullSlotRange($first, $last);
$intersections = [];
foreach ($this->slotRanges as $slotRange) {
if (!$placeHolder->hasIntersectionWith($slotRange)) {
continue;
}
$intersections[] = new SlotRange(
max($placeHolder->getStart(), $slotRange->getStart()),
min($placeHolder->getEnd(), $slotRange->getEnd()),
$slotRange->getConnection()
);
}
return array_reduce(
$intersections,
function ($carry, $slotRange) {
return $carry + $slotRange->toArray();
},
[]
);
}
/**
@@ -138,7 +209,7 @@ class SlotMap implements ArrayAccess, IteratorAggregate, Countable
#[ReturnTypeWillChange]
public function offsetExists($slot)
{
return isset($this->slots[$slot]);
return $this->findRangeBySlot($slot) !== false;
}
/**
@@ -151,7 +222,9 @@ class SlotMap implements ArrayAccess, IteratorAggregate, Countable
#[ReturnTypeWillChange]
public function offsetGet($slot)
{
return $this->slots[$slot] ?? null;
$found = $this->findRangeBySlot($slot);
return $found ? $found->getConnection() : null;
}
/**
@@ -169,7 +242,8 @@ class SlotMap implements ArrayAccess, IteratorAggregate, Countable
throw new OutOfBoundsException("Invalid slot $slot for `$connection`");
}
$this->slots[(int) $slot] = (string) $connection;
$this->offsetUnset($slot);
$this->setSlots($slot, $slot, $connection);
}
/**
@@ -182,7 +256,26 @@ class SlotMap implements ArrayAccess, IteratorAggregate, Countable
#[ReturnTypeWillChange]
public function offsetUnset($slot)
{
unset($this->slots[$slot]);
if (!static::isValid($slot)) {
throw new OutOfBoundsException("Invalid slot $slot");
}
$results = [];
foreach ($this->slotRanges as $slotRange) {
if (!$slotRange->hasSlot($slot)) {
$results[] = $slotRange;
}
if (static::isValidRange($slotRange->getStart(), $slot - 1)) {
$results[] = new SlotRange($slotRange->getStart(), $slot - 1, $slotRange->getConnection());
}
if (static::isValidRange($slot + 1, $slotRange->getEnd())) {
$results[] = new SlotRange($slot + 1, $slotRange->getEnd(), $slotRange->getConnection());
}
}
$this->slotRanges = $results;
}
/**
@@ -193,7 +286,12 @@ class SlotMap implements ArrayAccess, IteratorAggregate, Countable
#[ReturnTypeWillChange]
public function count()
{
return count($this->slots);
return array_sum(array_map(
function ($slotRange) {
return $slotRange->count();
},
$this->slotRanges
));
}
/**
@@ -204,6 +302,116 @@ class SlotMap implements ArrayAccess, IteratorAggregate, Countable
#[ReturnTypeWillChange]
public function getIterator()
{
return new ArrayIterator($this->slots);
return new ArrayIterator($this->toArray());
}
/**
* Find the slot range which contains the specific slot index.
*
* @param int $slot Slot index.
*
* @return SlotRange|false The slot range object or false if not found.
*/
protected function findRangeBySlot(int $slot)
{
foreach ($this->slotRanges as $slotRange) {
if ($slotRange->hasSlot($slot)) {
return $slotRange;
}
}
return false;
}
/**
* Get gaps between sorted slot ranges with NullSlotRange object.
*
* @param SlotRange[] $slotRanges
*
* @return SlotRange[]
*/
protected function getGaps(array $slotRanges)
{
if (empty($slotRanges)) {
return [
new NullSlotRange(0, SlotRange::MAX_SLOTS),
];
}
$gaps = [];
$count = count($slotRanges);
$i = 0;
foreach ($slotRanges as $key => $slotRange) {
$start = $slotRange->getStart();
$end = $slotRange->getEnd();
if (static::isValidRange($i, $start - 1)) {
$gaps[] = new NullSlotRange($i, $start - 1);
}
$i = $end + 1;
if ($key === $count - 1) {
if (static::isValidRange($i, SlotRange::MAX_SLOTS)) {
$gaps[] = new NullSlotRange($i, SlotRange::MAX_SLOTS);
}
}
}
return $gaps;
}
/**
* Sort slot ranges by start index.
*
* @param SlotRange[] $slotRanges
*
* @return void
*/
protected function sortSlotRanges(array &$slotRanges)
{
usort(
$slotRanges,
function (SlotRange $a, SlotRange $b) {
if ($a->getStart() == $b->getStart()) {
return 0;
}
return $a->getStart() < $b->getStart() ? -1 : 1;
}
);
}
/**
* Compact adjacent slot ranges with the same connection.
*
* @param SlotRange[] $slotRanges
*
* @return SlotRange[]
*/
protected function compactSlotRanges(array $slotRanges)
{
if (empty($slotRanges)) {
return [];
}
$compacted = [];
$count = count($slotRanges);
$i = 0;
$carry = $slotRanges[0];
while ($i < $count) {
$next = $slotRanges[$i + 1] ?? null;
if (
!is_null($next)
&& ($carry->getEnd() + 1) === $next->getStart()
&& $carry->getConnection() === $next->getConnection()
) {
$carry = new SlotRange($carry->getStart(), $next->getEnd(), $carry->getConnection());
} else {
$compacted[] = $carry;
$carry = $next;
}
$i++;
}
return array_values($compacted);
}
}
+145
View File
@@ -0,0 +1,145 @@
<?php
/*
* This file is part of the Predis package.
*
* (c) 2009-2020 Daniele Alessandri
* (c) 2021-2025 Till Krüss
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Predis\Cluster;
use Countable;
use OutOfBoundsException;
/**
* Represents a range of slots in a Redis cluster.
*/
class SlotRange implements Countable
{
/**
* Maximum number of slots in a Redis cluster is 16384.
*/
public const MAX_SLOTS = 0x3FFF;
/**
* Starting slot of the range.
*
* @var int
*/
protected $start;
/**
* Ending slot of the range.
*
* @var int
*/
protected $end;
/**
* Connection to the server hosting this slot range.
*
* @var string
*/
protected $connection;
public function __construct(int $start, int $end, string $connection)
{
if (!static::isValidRange($start, $end)) {
throw new OutOfBoundsException("Invalid slot range $start-$end for `$connection`");
}
$this->start = $start;
$this->end = $end;
$this->connection = $connection;
}
/**
* Checks if a slot range is valid.
*
* @param int $first
* @param int $last
*
* @return bool
*/
public static function isValidRange($first, $last)
{
return $first >= 0 && $first <= self::MAX_SLOTS && $last >= 0x0000 && $last <= self::MAX_SLOTS && $first <= $last;
}
/**
* Returns the start slot index of this range.
*
* @return int
*/
public function getStart()
{
return $this->start;
}
/**
* Returns the end slot index of this range.
*
* @return int
*/
public function getEnd()
{
return $this->end;
}
/**
* Returns the connection to the server hosting this slot range.
*
* @return string
*/
public function getConnection()
{
return $this->connection;
}
/**
* Checks if the specific slot is contained in this range.
*
* @param int $slot
*
* @return bool
*/
public function hasSlot(int $slot)
{
return $this->start <= $slot && $this->end >= $slot;
}
/**
* Returns an array of connection strings for each slot in this range.
*
* @return string[]
*/
public function toArray(): array
{
return array_fill($this->start, $this->end - $this->start + 1, $this->connection);
}
/**
* Returns the number of slots in this range.
*
* @return int
*/
public function count(): int
{
return $this->end - $this->start + 1;
}
/**
* Checks if this range has an intersection with the given slot range.
*
* @param SlotRange $slotRange
*
* @return bool
*/
public function hasIntersectionWith(SlotRange $slotRange): bool
{
return $this->start <= $slotRange->getEnd() && $this->end >= $slotRange->getStart();
}
}
+473
View File
@@ -0,0 +1,473 @@
<?php
/*
* This file is part of the Predis package.
*
* (c) 2009-2020 Daniele Alessandri
* (c) 2021-2025 Till Krüss
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Predis\Cluster;
use Predis\Cluster\SimpleSlotMap as SlotMap;
use PredisTestCase;
class SimpleSlotMapTest extends PredisTestCase
{
/**
* @group disconnected
*/
public function testIsValidReturnsTrueOnValidSlot(): void
{
$this->assertTrue(SlotMap::isValid(0));
$this->assertTrue(SlotMap::isValid(16383));
$this->assertTrue(SlotMap::isValid(5000));
$this->assertTrue(SlotMap::isValid('5000'));
}
/**
* @group disconnected
*/
public function testIsValidReturnsFalseOnInvalidSlot(): void
{
$this->assertFalse(SlotMap::isValid(-1));
$this->assertFalse(SlotMap::isValid(16384));
}
/**
* @group disconnected
*/
public function testIsValidRangeReturnsTrueOnValidSlotRange(): void
{
$this->assertTrue(SlotMap::isValidRange(0, 16383));
$this->assertTrue(SlotMap::isValidRange(2000, 2999));
$this->assertTrue(SlotMap::isValidRange(3000, 3000));
}
/**
* @group disconnected
*/
public function testIsValidRangeReturnsFalseOnInvalidSlotRange(): void
{
$this->assertFalse(SlotMap::isValidRange(0, 16384));
$this->assertFalse(SlotMap::isValidRange(-1, 16383));
$this->assertFalse(SlotMap::isValidRange(-1, 16384));
$this->assertFalse(SlotMap::isValidRange(2999, 2000));
}
/**
* @group disconnected
*/
public function testToArrayReturnsEmptyArrayOnEmptySlotMap(): void
{
$slotmap = new SlotMap();
$this->assertEmpty($slotmap->toArray());
}
/**
* @group disconnected
*/
public function testSetSlotsAssignsSpecifiedNodeToSlotRange(): void
{
$slotmap = new SlotMap();
$slotmap->setSlots(0, 5460, '127.0.0.1:6379');
$slotmap->setSlots(5461, 10922, '127.0.0.1:6380');
$slotmap->setSlots(10923, 16383, '127.0.0.1:6381');
$expectedMap = array_merge(
array_fill(0, 5461, '127.0.0.1:6379'),
array_fill(5461, 5462, '127.0.0.1:6380'),
array_fill(10923, 5461, '127.0.0.1:6381')
);
$this->assertSame($expectedMap, $slotmap->toArray());
}
/**
* @group disconnected
*/
public function testSetSlotsOverwritesSlotRange(): void
{
$slotmap = new SlotMap();
$slotmap->setSlots(0, 5460, '127.0.0.1:6379');
$slotmap->setSlots(1000, 2000, '127.0.0.1:6380');
$expectedMap =
array_fill(0, 5461, '127.0.0.1:6379') +
array_fill(1000, 2000, '127.0.0.1:6380');
$this->assertSame($expectedMap, $slotmap->toArray());
}
/**
* @group disconnected
*/
public function testSetSlotsAssignsSingleSlotWhenFirstAndLastSlotMatch(): void
{
$slotmap = new SlotMap();
$slotmap->setSlots(10, 10, '127.0.0.1:6379');
$this->assertSame([10 => '127.0.0.1:6379'], $slotmap->toArray());
}
/**
* @group disconnected
*/
public function testSetSlotsCastsValueToString(): void
{
$slotmap = new SlotMap();
$connection = $this->getMockConnection();
$connection
->expects($this->once())
->method('__toString')
->willReturn('127.0.0.1:6379');
$slotmap->setSlots(10, 10, $connection);
$this->assertSame([10 => '127.0.0.1:6379'], $slotmap->toArray());
}
/**
* @group disconnected
*/
public function testSetSlotsThrowsExceptionOnInvalidSlotRange(): void
{
$this->expectException('OutOfBoundsException');
$this->expectExceptionMessage('Invalid slot range 0-16384 for `127.0.0.1:6379`');
$slotmap = new SlotMap();
$slotmap->setSlots(0, 16384, '127.0.0.1:6379');
}
/**
* @group disconnected
*/
public function testGetSlotsReturnsEmptyArrayOnEmptySlotMap(): void
{
$slotmap = new SlotMap();
$this->assertEmpty($slotmap->getSlots(3, 11));
}
/**
* @group disconnected
*/
public function testGetSlotsReturnsDictionaryOfSlotsWithAssignedNodes(): void
{
$slotmap = new SlotMap();
$slotmap->setSlots(0, 5, '127.0.0.1:6379');
$slotmap->setSlots(10, 13, '127.0.0.1:6380');
$expectedMap = [
3 => '127.0.0.1:6379',
4 => '127.0.0.1:6379',
5 => '127.0.0.1:6379',
10 => '127.0.0.1:6380',
11 => '127.0.0.1:6380',
];
$this->assertSame($expectedMap, $slotmap->getSlots(3, 11));
}
/**
* @group disconnected
*/
public function testGetSlotsReturnsEmptyArrayOnEmptySlotRange(): void
{
$slotmap = new SlotMap();
$slotmap->setSlots(0, 5, '127.0.0.1:6379');
$slotmap->setSlots(10, 13, '127.0.0.1:6380');
$this->assertEmpty($slotmap->getSlots(100, 200));
}
/**
* @group disconnected
*/
public function testGetSlotsThrowsExceptionOnInvalidSlotRange(): void
{
$this->expectException('OutOfBoundsException');
$this->expectExceptionMessage('Invalid slot range 0-16384');
$slotmap = new SlotMap();
$slotmap->getSlots(0, 16384);
}
/**
* @group disconnected
*/
public function testIsEmptyReturnsTrueOnEmptySlotMap(): void
{
$slotmap = new SlotMap();
$this->assertTrue($slotmap->isEmpty());
}
/**
* @group disconnected
*/
public function testIsEmptyReturnsFalseOnNonEmptySlotMap(): void
{
$slotmap = new SlotMap();
$slotmap->setSlots(0, 5460, '127.0.0.1:6379');
$this->assertFalse($slotmap->isEmpty());
}
/**
* @group disconnected
*/
public function testCountReturnsZeroOnEmptySlotMap(): void
{
$slotmap = new SlotMap();
$this->assertCount(0, $slotmap);
}
/**
* @group disconnected
*/
public function testCountReturnsAssignedSlotsInSlotMap(): void
{
$slotmap = new SlotMap();
$slotmap->setSlots(0, 5460, '127.0.0.1:6379');
$this->assertCount(5461, $slotmap);
$slotmap->setSlots(5461, 10922, '127.0.0.1:6380');
$this->assertCount(10923, $slotmap);
$slotmap->setSlots(10923, 16383, '127.0.0.1:6381');
$this->assertCount(16384, $slotmap);
}
/**
* @group disconnected
*/
public function testResetEmptiesSlotMap(): void
{
$slotmap = new SlotMap();
$slotmap->setSlots(0, 5460, '127.0.0.1:6379');
$slotmap->setSlots(5461, 10922, '127.0.0.1:6380');
$slotmap->setSlots(10923, 16383, '127.0.0.1:6381');
$this->assertFalse($slotmap->isEmpty());
$slotmap->reset();
$this->assertTrue($slotmap->isEmpty());
}
/**
* @group disconnected
*/
public function testGetNodesReturnsEmptyArrayOnEmptySlotMap(): void
{
$slotmap = new SlotMap();
$this->assertEmpty($slotmap->getNodes());
}
/**
* @group disconnected
*/
public function testGetNodesReturnsArrayOfNodesInSlotMap(): void
{
$slotmap = new SlotMap();
$slotmap->setSlots(0, 5460, '127.0.0.1:6379');
$slotmap->setSlots(5461, 10922, '127.0.0.1:6380');
$slotmap->setSlots(10923, 16383, '127.0.0.1:6381');
$this->assertSame(['127.0.0.1:6379', '127.0.0.1:6380', '127.0.0.1:6381'], $slotmap->getNodes());
}
/**
* @group disconnected
*/
public function testOffsetExistsReturnsTrueOnAssignedSlot(): void
{
$slotmap = new SlotMap();
$slotmap->setSlots(0, 5460, '127.0.0.1:6379');
$this->assertTrue(isset($slotmap[0]));
$this->assertTrue(isset($slotmap[2000]));
}
/**
* @group disconnected
*/
public function testOffsetExistsReturnsFalseOnAssignedSlot(): void
{
$slotmap = new SlotMap();
$slotmap->setSlots(0, 5460, '127.0.0.1:6379');
$this->assertFalse(isset($slotmap[6000]));
}
/**
* @group disconnected
*/
public function testOffsetExistsReturnsFalseOnInvalidSlot(): void
{
$slotmap = new SlotMap();
$slotmap->setSlots(0, 5460, '127.0.0.1:6379');
$this->assertFalse(isset($slotmap[-100]));
$this->assertFalse(isset($slotmap[16384]));
}
/**
* @group disconnected
*/
public function testOffsetGetReturnsNodeOfAssignedSlot(): void
{
$slotmap = new SlotMap();
$slotmap->setSlots(0, 5460, '127.0.0.1:6379');
$slotmap->setSlots(5461, 10922, '127.0.0.1:6380');
$slotmap->setSlots(10923, 16383, '127.0.0.1:6381');
$this->assertSame('127.0.0.1:6379', $slotmap[0]);
$this->assertSame('127.0.0.1:6380', $slotmap[5461]);
$this->assertSame('127.0.0.1:6381', $slotmap[10923]);
}
/**
* @group disconnected
*/
public function testOffsetGetReturnsNullOnUnassignedSlot(): void
{
$slotmap = new SlotMap();
$slotmap->setSlots(0, 5460, '127.0.0.1:6379');
$this->assertNull($slotmap[5461]);
}
/**
* @group disconnected
*/
public function testOffsetGetReturnsNullOnInvalidSlot(): void
{
$slotmap = new SlotMap();
$slotmap->setSlots(0, 5460, '127.0.0.1:6379');
$this->assertNull($slotmap[-100]);
$this->assertNull($slotmap[16384]);
}
/**
* @group disconnected
*/
public function testOffsetUnsetRemovesSlotAssignment(): void
{
$slotmap = new SlotMap();
$slotmap->setSlots(0, 5460, '127.0.0.1:6379');
$this->assertTrue(isset($slotmap[100]));
unset($slotmap[100]);
$this->assertFalse(isset($slotmap[100]));
}
/**
* @group disconnected
*/
public function testOffsetUnsetDoesNotDoAnythingOnUnassignedSlot(): void
{
$slotmap = new SlotMap();
$slotmap->setSlots(0, 5460, '127.0.0.1:6379');
$this->assertFalse(isset($slotmap[5461]));
unset($slotmap[5461]);
$this->assertFalse(isset($slotmap[5461]));
}
/**
* @group disconnected
*/
public function testOffsetSetAssignsNodeToSlot(): void
{
$slotmap = new SlotMap();
$slotmap->setSlots(0, 5460, '127.0.0.1:6379');
$this->assertSame('127.0.0.1:6380', $slotmap[100] = '127.0.0.1:6380');
$this->assertSame('127.0.0.1:6380', $slotmap[100]);
$this->assertNull($slotmap[5461]);
$this->assertSame('127.0.0.1:6380', $slotmap[5461] = '127.0.0.1:6380');
$this->assertSame('127.0.0.1:6380', $slotmap[5461]);
}
/**
* @group disconnected
*/
public function testOffsetSetCastsValueToString(): void
{
$slotmap = new SlotMap();
$connection = $this->getMockConnection();
$connection
->expects($this->once())
->method('__toString')
->willReturn('127.0.0.1:6379');
$this->assertSame($connection, $slotmap[0] = $connection);
$this->assertSame('127.0.0.1:6379', $slotmap[0]);
}
/**
* @group disconnected
*/
public function testOffsetSetThrowsExceptionOnInvalidSlot(): void
{
$this->expectException('OutOfBoundsException');
$this->expectExceptionMessage('Invalid slot 16384 for `127.0.0.1:6379`');
$slotmap = new SlotMap();
$slotmap[16384] = '127.0.0.1:6379';
}
/**
* @group disconnected
*/
public function testGetIteratorReturnsIteratorOverSlotMap(): void
{
$slotmap = new SlotMap();
$slotmap->setSlots(0, 5460, '127.0.0.1:6379');
$slotmap->setSlots(5461, 10922, '127.0.0.1:6380');
$slotmap->setSlots(10923, 16383, '127.0.0.1:6381');
$expectedMap = array_merge(
array_fill(0, 5461, '127.0.0.1:6379'),
array_fill(5461, 5462, '127.0.0.1:6380'),
array_fill(10923, 5461, '127.0.0.1:6381')
);
$this->assertSame($expectedMap, iterator_to_array($slotmap));
}
}
+134
View File
@@ -0,0 +1,134 @@
<?php
/*
* This file is part of the Predis package.
*
* (c) 2009-2020 Daniele Alessandri
* (c) 2021-2025 Till Krüss
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Predis\Cluster;
use PredisTestCase;
class SlotRangeTest extends PredisTestCase
{
/**
* @group disconnected
*/
public function testMaxSlotsEquals16383()
{
$this->assertEquals(SlotRange::MAX_SLOTS, 16383);
}
/**
* @group disconnected
*/
public function testConstructorThrowExceptionOnInvalidSlotRange(): void
{
$this->expectException('OutOfBoundsException');
$this->expectExceptionMessage('Invalid slot range 600-300 for `c1`');
new SlotRange(600, 300, 'c1');
}
/**
* @group disconnected
*/
public function testIsValidRangeReturnsTrueOnValidSlotRange()
{
$this->assertTrue(SlotRange::isValidRange(0, SlotRange::MAX_SLOTS));
$this->assertTrue(SlotRange::isValidRange(2000, 2999));
$this->assertTrue(SlotRange::isValidRange(3000, 3000));
}
/**
* @group disconnected
*/
public function testIsValidRangeReturnsFalseOnInvalidSlotRange()
{
$this->assertFalse(SlotRange::isValidRange(0, 16384));
$this->assertFalse(SlotRange::isValidRange(-1, 16383));
$this->assertFalse(SlotRange::isValidRange(-1, 16384));
$this->assertFalse(SlotRange::isValidRange(2999, 2000));
}
/**
* @group disconnected
*/
public function testPropertySetters()
{
$range = new SlotRange(2000, 5000, 'c1');
$this->assertEquals(2000, $range->getStart());
$this->assertEquals(5000, $range->getEnd());
$this->assertEquals('c1', $range->getConnection());
}
/**
* @group disconnected
*/
public function testHasSlot()
{
$range = new SlotRange(2000, 4000, 'c1');
$this->assertTrue($range->hasSlot(2000));
$this->assertTrue($range->hasSlot(4000));
$this->assertTrue($range->hasSlot(3000));
$this->assertFalse($range->hasSlot(1000));
$this->assertFalse($range->hasSlot(5000));
}
/**
* @group disconnected
*/
public function testToArray()
{
$range = new SlotRange(2000, 2005, 'c1');
$array = $range->toArray();
$this->assertSame([
2000 => 'c1',
2001 => 'c1',
2002 => 'c1',
2003 => 'c1',
2004 => 'c1',
2005 => 'c1',
], $array);
}
/**
* @group disconnected
*/
public function testCount()
{
$range = new SlotRange(2000, 3000, 'c1');
$this->assertEquals(1001, $range->count());
$this->assertEquals(1001, count($range));
}
/**
* @group disconnected
*/
public function testHasIntersectionWith()
{
$original = new SlotRange(2000, 3000, 'c1');
$this->assertFalse($original->hasIntersectionWith(new SlotRange(1000, 1500, 'c1')));
$this->assertFalse($original->hasIntersectionWith(new SlotRange(3001, 5000, 'c1')));
$this->assertFalse($original->hasIntersectionWith(new SlotRange(1999, 1999, 'c1')));
$this->assertFalse($original->hasIntersectionWith(new SlotRange(3001, 3001, 'c1')));
$this->assertTrue($original->hasIntersectionWith(new SlotRange(1500, 6000, 'c1')));
$this->assertTrue($original->hasIntersectionWith(new SlotRange(2500, 2999, 'c1')));
$this->assertTrue($original->hasIntersectionWith(new SlotRange(2000, 2999, 'c1')));
$this->assertTrue($original->hasIntersectionWith(new SlotRange(2500, 3000, 'c1')));
$this->assertTrue($original->hasIntersectionWith(new SlotRange(1500, 2000, 'c1')));
$this->assertTrue($original->hasIntersectionWith(new SlotRange(3000, 3500, 'c1')));
$this->assertTrue($original->hasIntersectionWith(new SlotRange(2000, 2000, 'c1')));
$this->assertTrue($original->hasIntersectionWith(new SlotRange(3000, 3000, 'c1')));
}
}