Skip to content

Commit

Permalink
Merge branch 'release-15.37.0'
Browse files Browse the repository at this point in the history
  • Loading branch information
github-actions committed Jul 9, 2024
2 parents b7462ff + c516ab4 commit e9cd24f
Show file tree
Hide file tree
Showing 2 changed files with 217 additions and 18 deletions.
82 changes: 64 additions & 18 deletions common/persistence/class.PhpRedisDriver.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ class common_persistence_PhpRedisDriver implements common_persistence_AdvKvDrive
public const DEFAULT_TIMEOUT = 5; // in seconds
public const RETRY_DELAY = 500000; // Eq to 0.5s

private const DEFAULT_PREFIX_SEPARATOR = ':';

/**
* @var Redis
*/
Expand Down Expand Up @@ -155,75 +157,75 @@ public function set($key, $value, $ttl = null, $nx = false)
if ($nx) {
$options[] = 'nx';
}
return $this->callWithRetry('set', [$key, $value, $options]);
return $this->callWithRetry('set', [$this->prefixKey($key), $value, $options]);
}

public function get($key)
{
return $this->callWithRetry('get', [$key]);
return $this->callWithRetry('get', [$this->prefixKey($key)]);
}

public function exists($key)
{
return (bool)$this->callWithRetry('exists', [$key]);
return (bool)$this->callWithRetry('exists', [$this->prefixKey($key)]);
}

public function del($key)
{
return $this->callWithRetry('del', [$key]);
return $this->callWithRetry('del', [$this->prefixKey($key)]);
}

//O(N) where N is the number of fields being set.
public function hmSet($key, $fields)
{
return $this->callWithRetry('hmSet', [$key, $fields]);
return $this->callWithRetry('hmSet', [$this->prefixKey($key), $fields]);
}

//Time complexity: O(1)
public function hExists($key, $field)
{
return (bool)$this->callWithRetry('hExists', [$key, $field]);
return (bool)$this->callWithRetry('hExists', [$this->prefixKey($key), $field]);
}

//Time complexity: O(1)
public function hSet($key, $field, $value)
{
return $this->callWithRetry('hSet', [$key, $field, $value]);
return $this->callWithRetry('hSet', [$this->prefixKey($key), $field, $value]);
}

//Time complexity: O(1)
public function hGet($key, $field)
{
return $this->callWithRetry('hGet', [$key, $field]);
return $this->callWithRetry('hGet', [$this->prefixKey($key), $field]);
}

public function hDel($key, $field): bool
{
return (bool)$this->callWithRetry('hDel', [$key, $field]);
return (bool)$this->callWithRetry('hDel', [$this->prefixKey($key), $field]);
}

//Time complexity: O(N) where N is the size of the hash.
public function hGetAll($key)
{
return $this->callWithRetry('hGetAll', [$key]);
return $this->callWithRetry('hGetAll', [$this->prefixKey($key)]);
}

//Time complexity: O(N)
public function keys($pattern)
{
return $this->callWithRetry('keys', [$pattern]);
return $this->callWithRetry('keys', [$this->prefixKey($pattern)]);
}

//Time complexity: O(1)
public function incr($key)
{
return $this->callWithRetry('incr', [$key]);
return $this->callWithRetry('incr', [$this->prefixKey($key)]);
}

//Time complexity: O(1)
public function decr($key)
{
return $this->callWithRetry('decr', [$key]);
return $this->callWithRetry('decr', [$this->prefixKey($key)]);
}

/**
Expand All @@ -237,9 +239,9 @@ public function scan(int &$iterator = null, string $pattern = null, int $count =

while ($attempt <= $retry) {
try {
return $this->connection->scan($iterator, $pattern, $count);
return $this->connection->scan($iterator, $this->prefixKey($pattern), $count);
} catch (Exception $exception) {
$this->reconnectOnException($exception, $method, $attempt, $retry);
$this->reconnectOnException($exception, 'scan', $attempt, $retry);
}

$attempt++;
Expand All @@ -257,23 +259,23 @@ public function scan(int &$iterator = null, string $pattern = null, int $count =
*/
public function mGet(array $keys)
{
return $this->callWithRetry('mGet', [$keys]);
return $this->callWithRetry('mGet', [$this->prefixKeys($keys)]);
}

/**
* @return bool|mixed
*/
public function mDel(array $keys)
{
return $this->callWithRetry('del', [$keys]);
return $this->callWithRetry('del', [$this->prefixKeys($keys)]);
}

/**
* @return bool|mixed
*/
public function mSet(array $keyValues)
{
return $this->callWithRetry('mSet', [$keyValues]);
return $this->callWithRetry('mSet', [$this->prefixKeys($keyValues, true)]);
}

/**
Expand All @@ -284,6 +286,50 @@ public function getConnection()
return $this->connection;
}

protected function getPrefix(array $params): ?string
{
$prefix = null;

if (!empty($this->params['prefix'])) {
$prefix = $this->params['prefix'];
}

return $prefix;
}

/**
* @param string|int|null $key
* @return string|int|null
*/
private function prefixKey($key)
{
$prefix = $this->getPrefix($this->params);

if ($prefix === null) {
return $key;
}

return $prefix . ($this->params['prefixSeparator'] ?? self::DEFAULT_PREFIX_SEPARATOR) . $key;
}

private function prefixKeys(array $keys, bool $keyValueMode = false): array
{
if ($this->getPrefix($this->params) !== null) {
$prefixedKeys = [];
foreach (array_values($keys) as $i => $element) {
if ($keyValueMode) {
$prefixedKeys[] = $i % 2 == 0 ? $this->prefixKey($element) : $element;
} else {
$prefixedKeys[] = $this->prefixKey($element);
}
}

return $prefixedKeys;
}

return $keys;
}

/**
* @return void
* @throws RedisException
Expand Down
153 changes: 153 additions & 0 deletions test/unit/common/persistence/PhpRedisDriverTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
<?php

/**
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; under version 2
* of the License (non-upgradable).
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* Copyright (c) 2024 (original work) Open Assessment Technologies SA (under the project TAO-PRODUCT);
*/

namespace oat\generis\test\unit\common\persistence;

use common_persistence_PhpRedisDriver;
use PHPUnit\Framework\TestCase;

class PhpRedisDriverTest extends TestCase
{
private common_persistence_PhpRedisDriver $driver;

private object $connection;

public function setUp(): void
{
$driver = new common_persistence_PhpRedisDriver();

$this->connection = new class
{
public array $calls = [];

public function __call($name, $arguments)
{
$this->calls[$name] = $arguments;
if ($name === 'scan') {
return [];
}

return 'data';
}
};

$this->setDriverProperty($driver, 'connection', $this->connection);

$this->driver = $driver;
}

public function testKeyPrefixIsNotAddedIfPrefixIsNotSet()
{
$this->setDriverProperty($this->driver, 'params', ['attempt' => 2]);

$this->driver->set('key', 'value');

$this->assertEquals('key', $this->getLastCallKey('set'));
}

public function testKeyPrefixIsAddedIfPrefixIsSet()
{
$this->setDriverProperty($this->driver, 'params', ['attempt' => 2, 'prefix' => '25']);

$this->driver->set('key1', 'value');

$this->assertEquals('25:key1', $this->getLastCallKey('set'));
}

public function testKeyPrefixHasNotDefaultSeparatorIfSeparatorIsSet()
{
$this->setDriverProperty($this->driver, 'params', [
'attempt' => 2,
'prefix' => '25',
'prefixSeparator' => '-'
]);

$this->driver->set('key2', 'value');

$this->assertEquals('25-key2', $this->getLastCallKey('set'));
}

public function testKeysHavePrefixWithoutKeyValueMode()
{
$this->setDriverProperty($this->driver, 'params', ['attempt' => 2, 'prefix' => '26']);

$this->driver->mGet(['key1', 'key2']);

$this->assertEquals(['26:key1', '26:key2'], $this->getLastCallKey('mGet'));
}

public function testKeysHavePrefixWithKeyValueMode()
{
$this->setDriverProperty($this->driver, 'params', ['attempt' => 2, 'prefix' => '25']);

$this->driver->mSet(['key1', 'value1', 'key2', 'value2']);

$this->assertEquals(['25:key1', 'value1', '25:key2', 'value2'], $this->getLastCallKey('mSet'));
}

public function testKeyPrefixIsAddedToAllMethods()
{
$this->setDriverProperty($this->driver, 'params', ['attempt' => 2, 'prefix' => 25]);

$methods = [
'set' => ['key', 'value'],
'get' => ['key'],
'exists' => ['key'],
'del' => ['key'],
'hmSet' => ['key', 'field'],
'hExists' => ['key', 'field'],
'hSet' => ['key', 'field', 'value'],
'hGet' => ['key', 'field'],
'hDel' => ['key', 'field'],
'keys' => ['key'],
'incr' => ['key'],
'decr' => ['key'],
];

foreach ($methods as $method => $methodParams) {
$this->driver->$method(...$methodParams);
$this->assertEquals('25:key', $this->getLastCallKey($method));
}
}

public function testKeyPrefixIsAddedForScanMethod()
{
$this->setDriverProperty($this->driver, 'params', ['attempt' => 2, 'prefix' => 'pref']);

$iterator = null;
$this->driver->scan($iterator, '*pattern*');

$this->assertEquals('pref:*pattern*', $this->driver->getConnection()->calls['scan'][1]);
}

private function getLastCallKey(string $method)
{
return $this->driver->getConnection()->calls[$method][0];
}

private function setDriverProperty($driver, string $propertyName, $propertyValue): void
{
$reflection = new \ReflectionClass($driver);

$property = $reflection->getProperty($propertyName);
$property->setAccessible(true);
$property->setValue($driver, $propertyValue);
}
}

0 comments on commit e9cd24f

Please sign in to comment.