mirror of
https://github.com/predis/predis.git
synced 2026-08-19 00:13:30 +00:00
Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 153097374b | |||
| 6ef87493ca | |||
| 80430b4cde | |||
| d0f37f0bac | |||
| f11e855047 | |||
| ce853ecec2 | |||
| f4dcf6ce57 | |||
| 72a95b3d75 | |||
| 62f4ebcc37 | |||
| f26a13c8f4 |
@@ -40,7 +40,7 @@ jobs:
|
||||
run: |
|
||||
# Mapping of original redis versions to client test containers
|
||||
declare -A redis_clients_version_mapping=(
|
||||
["8.4"]="8.4-M01-pre"
|
||||
["8.4"]="8.4.0"
|
||||
["8.2"]="8.2.2-pre"
|
||||
["8.0"]="8.0.2"
|
||||
["7.4"]="7.4.2"
|
||||
|
||||
+6
-5
@@ -1,16 +1,17 @@
|
||||
## Changelog
|
||||
|
||||
## Unreleased
|
||||
## v3.3.0 (2025-11-24)
|
||||
### Added
|
||||
- Added cluster support for `XADD`, `XDEL` and `XRANGE` (#1587)
|
||||
- Added prefixable interface for `HEXPIRE` and `HEXPIRETIME` (#1592)
|
||||
- Added new experimental CAS/CAD functionality (#1609)
|
||||
- Added temporary `XREADGROUP_CLAIM` command (#1608)
|
||||
- Added support for `MSET` command (#1610)
|
||||
- Added experimental support for `FT.HYBRID` (#1607)
|
||||
|
||||
### Changed
|
||||
- Refactor pipeline data writing depends on connection type (#1586)
|
||||
- Improved compatiblity with Relay (#1597)
|
||||
|
||||
### Maintenance
|
||||
- Added testing with 8.4-M01 (#1593)
|
||||
- Refactor pipeline data writing depends on connection type (#1586)
|
||||
|
||||
## v3.2.0 (2025-08-05)
|
||||
### Added
|
||||
|
||||
+1
-1
@@ -54,7 +54,7 @@ use Traversable;
|
||||
*/
|
||||
class Client implements ClientInterface, IteratorAggregate
|
||||
{
|
||||
public const VERSION = '3.2.0';
|
||||
public const VERSION = '3.3.0';
|
||||
|
||||
/** @var OptionsInterface */
|
||||
private $options;
|
||||
|
||||
@@ -19,6 +19,7 @@ use Predis\Command\Argument\Search\AlterArguments;
|
||||
use Predis\Command\Argument\Search\CreateArguments;
|
||||
use Predis\Command\Argument\Search\DropArguments;
|
||||
use Predis\Command\Argument\Search\ExplainArguments;
|
||||
use Predis\Command\Argument\Search\HybridSearch\HybridSearchQuery;
|
||||
use Predis\Command\Argument\Search\ProfileArguments;
|
||||
use Predis\Command\Argument\Search\SchemaFields\FieldInterface;
|
||||
use Predis\Command\Argument\Search\SearchArguments;
|
||||
@@ -52,6 +53,8 @@ use Predis\Command\Redis\VADD;
|
||||
*
|
||||
* @method $this copy(string $source, string $destination, int $db = -1, bool $replace = false)
|
||||
* @method $this del(array|string $keys)
|
||||
* @method $this delex(string $key, string $flag, $flagValue)
|
||||
* @method $this digest(string $key)
|
||||
* @method $this dump($key)
|
||||
* @method $this exists($key)
|
||||
* @method $this expire($key, $seconds, string $expireOption = '')
|
||||
@@ -126,6 +129,7 @@ use Predis\Command\Redis\VADD;
|
||||
* @method $this ftdictdump(string $dict)
|
||||
* @method $this ftdropindex(string $index, ?DropArguments $arguments = null)
|
||||
* @method $this ftexplain(string $index, string $query, ?ExplainArguments $arguments = null)
|
||||
* @method $this fthybrid(string $index, HybridSearchQuery $query)
|
||||
* @method $this ftinfo(string $index)
|
||||
* @method $this ftprofile(string $index, ProfileArguments $arguments)
|
||||
* @method $this ftsearch(string $index, string $query, ?SearchArguments $arguments = null)
|
||||
@@ -148,9 +152,10 @@ use Predis\Command\Redis\VADD;
|
||||
* @method $this incrbyfloat($key, $increment)
|
||||
* @method $this mget(array $keys)
|
||||
* @method $this mset(array $dictionary)
|
||||
* @method $this msetex(array $dictionary, ?string $existModifier = null, ?string $expireResolution = null, ?int $expireTTL = null)
|
||||
* @method $this msetnx(array $dictionary)
|
||||
* @method $this psetex($key, $milliseconds, $value)
|
||||
* @method $this set($key, $value, $expireResolution = null, $expireTTL = null, $flag = null)
|
||||
* @method $this set($key, $value, $expireResolution = null, $expireTTL = null, $flag = null, $flagValue = null)
|
||||
* @method $this setbit($key, $offset, $value)
|
||||
* @method $this setex($key, $seconds, $value)
|
||||
* @method $this setnx($key, $value)
|
||||
@@ -297,6 +302,7 @@ use Predis\Command\Redis\VADD;
|
||||
* @method $this xrange(string $key, string $start, string $end, ?int $count = null)
|
||||
* @method $this xread(int $count = null, int $block = null, array $streams = null, string ...$id)
|
||||
* @method $this xreadgroup(string $group, string $consumer, ?int $count = null, ?int $blockMs = null, bool $noAck = false, string ...$keyOrId)
|
||||
* @method $this xreadgroup_claim(string $group, string $consumer, array $keyIdDict, ?int $count = null, ?int $blockMs = null, bool $noAck = false, ?int $claim = null)
|
||||
* @method $this xsetid(string $key, string $lastId, ?int $entriesAdded = null, ?string $maxDeleteId = null)
|
||||
* @method $this xtrim(string $key, array|string $strategy, string $threshold, array $options = null)
|
||||
* @method $this zadd($key, array $membersAndScoresDictionary)
|
||||
|
||||
@@ -19,6 +19,7 @@ use Predis\Command\Argument\Search\AlterArguments;
|
||||
use Predis\Command\Argument\Search\CreateArguments;
|
||||
use Predis\Command\Argument\Search\DropArguments;
|
||||
use Predis\Command\Argument\Search\ExplainArguments;
|
||||
use Predis\Command\Argument\Search\HybridSearch\HybridSearchQuery;
|
||||
use Predis\Command\Argument\Search\ProfileArguments;
|
||||
use Predis\Command\Argument\Search\SchemaFields\FieldInterface;
|
||||
use Predis\Command\Argument\Search\SearchArguments;
|
||||
@@ -62,6 +63,8 @@ use Predis\Response\Status;
|
||||
*
|
||||
* @method int copy(string $source, string $destination, int $db = -1, bool $replace = false)
|
||||
* @method int del(string[]|string $keyOrKeys, string ...$keys = null)
|
||||
* @method int delex(string $key, string $flag, $flagValue)
|
||||
* @method string digest(string $key)
|
||||
* @method string|null dump(string $key)
|
||||
* @method int exists(string $key)
|
||||
* @method int expire(string $key, int $seconds, string $expireOption = '')
|
||||
@@ -136,6 +139,7 @@ use Predis\Response\Status;
|
||||
* @method array ftdictdump(string $dict)
|
||||
* @method Status ftdropindex(string $index, ?DropArguments $arguments = null)
|
||||
* @method string ftexplain(string $index, string $query, ?ExplainArguments $arguments = null)
|
||||
* @method array fthybrid(string $index, HybridSearchQuery $query)
|
||||
* @method array ftinfo(string $index)
|
||||
* @method array ftprofile(string $index, ProfileArguments $arguments)
|
||||
* @method array ftsearch(string $index, string $query, ?SearchArguments $arguments = null)
|
||||
@@ -158,9 +162,10 @@ use Predis\Response\Status;
|
||||
* @method string incrbyfloat(string $key, int|float $increment)
|
||||
* @method array mget(string[]|string $keyOrKeys, string ...$keys = null)
|
||||
* @method mixed mset(array $dictionary)
|
||||
* @method array msetex(array $dictionary, ?string $existModifier = null, ?string $expireResolution = null, ?int $expireTTL = null)
|
||||
* @method int msetnx(array $dictionary)
|
||||
* @method Status psetex(string $key, $milliseconds, $value)
|
||||
* @method Status|null set(string $key, $value, $expireResolution = null, $expireTTL = null, $flag = null)
|
||||
* @method Status|null set(string $key, $value, $expireResolution = null, $expireTTL = null, $flag = null, $flagValue = null)
|
||||
* @method int setbit(string $key, $offset, $value)
|
||||
* @method Status setex(string $key, $seconds, $value)
|
||||
* @method int setnx(string $key, $value)
|
||||
@@ -308,6 +313,7 @@ use Predis\Response\Status;
|
||||
* @method array xrange(string $key, string $start, string $end, ?int $count = null)
|
||||
* @method array|null xread(int $count = null, int $block = null, array $streams = null, string ...$id)
|
||||
* @method array xreadgroup(string $group, string $consumer, ?int $count = null, ?int $blockMs = null, bool $noAck = false, string ...$keyOrId)
|
||||
* @method array xreadgroup_claim(string $group, string $consumer, array $keyIdDict, ?int $count = null, ?int $blockMs = null, bool $noAck = false, ?int $claim = null)
|
||||
* @method Status xsetid(string $key, string $lastId, ?int $entriesAdded = null, ?string $maxDeleteId = null)
|
||||
* @method string xtrim(string $key, array|string $strategy, string $threshold, array $options = null)
|
||||
* @method int zadd(string $key, array $membersAndScoresDictionary)
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
<?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\Command\Argument\Search\HybridSearch\Combine;
|
||||
|
||||
use Predis\Command\Argument\ArrayableArgument;
|
||||
|
||||
abstract class BaseCombine implements ArrayableArgument
|
||||
{
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $arguments = ['COMBINE'];
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $as = [];
|
||||
|
||||
/**
|
||||
* @param string $alias
|
||||
* @return $this
|
||||
*/
|
||||
public function as(string $alias): self
|
||||
{
|
||||
array_push($this->as, 'YIELD_SCORE_AS', $alias);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
abstract public function toArray(): array;
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
<?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\Command\Argument\Search\HybridSearch\Combine;
|
||||
|
||||
class LinearCombineConfig extends BaseCombine
|
||||
{
|
||||
/**
|
||||
* @var float
|
||||
*/
|
||||
protected $alpha;
|
||||
|
||||
/**
|
||||
* @var float
|
||||
*/
|
||||
protected $beta;
|
||||
|
||||
/**
|
||||
* The weight for the text score (a value between 0 and 1).
|
||||
*
|
||||
* @param float $alpha
|
||||
* @return $this
|
||||
*/
|
||||
public function alpha(float $alpha): self
|
||||
{
|
||||
$this->alpha = $alpha;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* The weight for the vector score (a value between 0 and 1).
|
||||
*
|
||||
* @param float $beta
|
||||
* @return $this
|
||||
*/
|
||||
public function beta(float $beta): self
|
||||
{
|
||||
$this->beta = $beta;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function toArray(): array
|
||||
{
|
||||
$this->arguments[] = 'LINEAR';
|
||||
$tokens = [];
|
||||
|
||||
if ($this->alpha !== null) {
|
||||
array_push($tokens, 'ALPHA', $this->alpha);
|
||||
}
|
||||
|
||||
if ($this->beta !== null) {
|
||||
array_push($tokens, 'BETA', $this->beta);
|
||||
}
|
||||
|
||||
if ($this->as) {
|
||||
array_push($tokens, ...$this->as);
|
||||
}
|
||||
|
||||
if (!empty($tokens)) {
|
||||
array_push($this->arguments, count($tokens), ...$tokens);
|
||||
}
|
||||
|
||||
return $this->arguments;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
<?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\Command\Argument\Search\HybridSearch\Combine;
|
||||
|
||||
class RRFCombineConfig extends BaseCombine
|
||||
{
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
protected $window;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
protected $rrfConstant;
|
||||
|
||||
/**
|
||||
* The number of top results from each search type to consider for fusion. Defaults to 50.
|
||||
*
|
||||
* @param int $window
|
||||
* @return $this
|
||||
*/
|
||||
public function window(int $window): self
|
||||
{
|
||||
$this->window = $window;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* The RRF ranking constant. A smaller value gives more weight to top-ranked items. Defaults to 60.
|
||||
*
|
||||
* @param int $constant
|
||||
* @return $this
|
||||
*/
|
||||
public function rrfConstant(int $constant): self
|
||||
{
|
||||
$this->rrfConstant = $constant;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function toArray(): array
|
||||
{
|
||||
$this->arguments[] = 'RRF';
|
||||
$tokens = [];
|
||||
|
||||
if ($this->window !== null) {
|
||||
array_push($tokens, 'WINDOW', $this->window);
|
||||
}
|
||||
|
||||
if ($this->rrfConstant !== null) {
|
||||
array_push($tokens, 'CONSTANT', $this->rrfConstant);
|
||||
}
|
||||
|
||||
if ($this->as) {
|
||||
array_push($tokens, ...$this->as);
|
||||
}
|
||||
|
||||
if (!empty($tokens)) {
|
||||
array_push($this->arguments, count($tokens), ...$tokens);
|
||||
}
|
||||
|
||||
return $this->arguments;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,352 @@
|
||||
<?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\Command\Argument\Search\HybridSearch;
|
||||
|
||||
use Predis\Command\Argument\ArrayableArgument;
|
||||
use Predis\Command\Argument\Search\HybridSearch\Combine\LinearCombineConfig;
|
||||
use Predis\Command\Argument\Search\HybridSearch\Combine\RRFCombineConfig;
|
||||
use Predis\Command\Argument\Search\HybridSearch\VectorSearch\KNNVectorSearchConfig;
|
||||
use Predis\Command\Argument\Search\HybridSearch\VectorSearch\RangeVectorSearchConfig;
|
||||
use Predis\Command\Redis\Utils\CommandUtility;
|
||||
use ValueError;
|
||||
|
||||
class HybridSearchQuery implements ArrayableArgument
|
||||
{
|
||||
public const SORT_ASC = 'ASC';
|
||||
public const SORT_DESC = 'DESC';
|
||||
|
||||
/**
|
||||
* @var SearchConfig
|
||||
*/
|
||||
protected $searchConfig;
|
||||
|
||||
/**
|
||||
* The vector search portion of the query.
|
||||
*
|
||||
* @var KNNVectorSearchConfig|RangeVectorSearchConfig
|
||||
*/
|
||||
protected $vectorSearchConfig;
|
||||
|
||||
/**
|
||||
* Configuration for the score fusion method (optional).
|
||||
* If not provided, Reciprocal Rank Fusion (RRF) is used with server-side default parameters.
|
||||
*
|
||||
* @var RRFCombineConfig|LinearCombineConfig
|
||||
*/
|
||||
protected $combineConfig;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $load = [];
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $groupBy = [];
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $apply = [];
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $sortBy = [];
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $filter;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $limit = [];
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $params = [];
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
protected $explainScore = false;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
protected $timeout = false;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $withCursor = [];
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $arguments = [];
|
||||
|
||||
/**
|
||||
* @param string $vectorSearchMethod Class type of desired vector search method
|
||||
* @param string $combineMethod Class type of desired combine method
|
||||
*/
|
||||
public function __construct(
|
||||
string $vectorSearchMethod = KNNVectorSearchConfig::class,
|
||||
string $combineMethod = RRFCombineConfig::class
|
||||
) {
|
||||
$this->searchConfig = new SearchConfig();
|
||||
$this->vectorSearchConfig = new $vectorSearchMethod();
|
||||
$this->combineConfig = new $combineMethod();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param callable(SearchConfig): void $callable
|
||||
* @return $this
|
||||
*/
|
||||
public function buildSearchConfig(callable $callable): self
|
||||
{
|
||||
$callable($this->searchConfig);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param callable(KNNVectorSearchConfig|RangeVectorSearchConfig): void $callable
|
||||
* @return $this
|
||||
*/
|
||||
public function buildVectorSearchConfig(callable $callable): self
|
||||
{
|
||||
$callable($this->vectorSearchConfig);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param callable(RRFCombineConfig|LinearCombineConfig): void $callable
|
||||
* @return $this
|
||||
*/
|
||||
public function buildCombineConfig(callable $callable): self
|
||||
{
|
||||
$callable($this->combineConfig);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* The list of fields to return in the results.
|
||||
*
|
||||
* @param array $fields
|
||||
* @return $this
|
||||
*/
|
||||
public function load(array $fields): self
|
||||
{
|
||||
array_push($this->load, 'LOAD', count($fields), ...$fields);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $fields
|
||||
* @param Reducer[] $reducers
|
||||
* @return $this
|
||||
*/
|
||||
public function groupBy(array $fields, array $reducers): self
|
||||
{
|
||||
array_push($this->groupBy, 'GROUPBY', count($fields), ...$fields);
|
||||
|
||||
foreach ($reducers as $reducer) {
|
||||
array_push($this->groupBy, 'REDUCE', ...$reducer->toArray());
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $expressionFieldDict field => function dictionary
|
||||
* @return $this
|
||||
*/
|
||||
public function apply(array $expressionFieldDict): self
|
||||
{
|
||||
foreach ($expressionFieldDict as $field => $function) {
|
||||
array_push($this->apply, 'APPLY', $function, 'AS', $field);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sorts the final results by a specific field.
|
||||
*
|
||||
* @param array<string, string> $fields Dictionary with fields and sort direction. Check class constants.
|
||||
* @return $this
|
||||
*/
|
||||
public function sortBy(array $fields): self
|
||||
{
|
||||
$fieldsArray = [];
|
||||
foreach ($fields as $field => $direction) {
|
||||
if (!in_array(strtoupper($direction), [self::SORT_ASC, self::SORT_DESC])) {
|
||||
throw new ValueError('Sort direction must be one of "ASC" or "DESC".');
|
||||
}
|
||||
|
||||
array_push($fieldsArray, $field, $direction);
|
||||
}
|
||||
|
||||
array_push($this->sortBy, 'SORTBY', count($fieldsArray), ...$fieldsArray);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Final result filtering.
|
||||
*
|
||||
* @param string $expression
|
||||
* @return $this
|
||||
*/
|
||||
public function filter(string $expression): self
|
||||
{
|
||||
$this->filter = $expression;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $offset
|
||||
* @param int $num
|
||||
* @return $this
|
||||
*/
|
||||
public function limit(int $offset, int $num): self
|
||||
{
|
||||
array_push($this->limit, 'LIMIT', $offset, $num);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Binds values to named parameters in the query string.
|
||||
*
|
||||
* @param array $params
|
||||
* @return $this
|
||||
*/
|
||||
public function params(array $params): self
|
||||
{
|
||||
$arrayParams = CommandUtility::dictionaryToArray($params);
|
||||
array_push($this->params, 'PARAMS', count($arrayParams), ...$arrayParams);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return $this
|
||||
*/
|
||||
public function explainScore(): self
|
||||
{
|
||||
$this->explainScore = true;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return $this
|
||||
*/
|
||||
public function timeout(): self
|
||||
{
|
||||
$this->timeout = true;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int|null $readSize
|
||||
* @param int|null $idleTime
|
||||
* @return $this
|
||||
*/
|
||||
public function withCursor(?int $readSize = null, ?int $idleTime = null): self
|
||||
{
|
||||
$this->withCursor[] = 'WITHCURSOR';
|
||||
|
||||
if ($readSize) {
|
||||
array_push($this->withCursor, 'COUNT', $readSize);
|
||||
}
|
||||
|
||||
if ($idleTime) {
|
||||
array_push($this->withCursor, 'MAXIDLE', $idleTime);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function toArray(): array
|
||||
{
|
||||
$this->arguments = array_merge(
|
||||
$this->arguments,
|
||||
$this->searchConfig->toArray(),
|
||||
$this->vectorSearchConfig->toArray()
|
||||
);
|
||||
|
||||
$combineConfig = $this->combineConfig->toArray();
|
||||
|
||||
// Only add if any configuration was applied
|
||||
if (count($combineConfig) > 2) {
|
||||
$this->arguments = array_merge($this->arguments, $combineConfig);
|
||||
}
|
||||
|
||||
if ($this->load) {
|
||||
$this->arguments = array_merge($this->arguments, $this->load);
|
||||
}
|
||||
|
||||
if ($this->groupBy) {
|
||||
$this->arguments = array_merge($this->arguments, $this->groupBy);
|
||||
}
|
||||
|
||||
if ($this->apply) {
|
||||
$this->arguments = array_merge($this->arguments, $this->apply);
|
||||
}
|
||||
|
||||
if ($this->sortBy) {
|
||||
$this->arguments = array_merge($this->arguments, $this->sortBy);
|
||||
}
|
||||
|
||||
if ($this->filter) {
|
||||
array_push($this->arguments, 'FILTER', $this->filter);
|
||||
}
|
||||
|
||||
if ($this->limit) {
|
||||
$this->arguments = array_merge($this->arguments, $this->limit);
|
||||
}
|
||||
|
||||
if ($this->params) {
|
||||
$this->arguments = array_merge($this->arguments, $this->params);
|
||||
}
|
||||
|
||||
if ($this->explainScore) {
|
||||
$this->arguments[] = 'EXPLAINSCORE';
|
||||
}
|
||||
|
||||
if ($this->timeout) {
|
||||
$this->arguments[] = 'TIMEOUT';
|
||||
}
|
||||
|
||||
if ($this->withCursor) {
|
||||
$this->arguments = array_merge($this->arguments, $this->withCursor);
|
||||
}
|
||||
|
||||
return $this->arguments;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
<?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\Command\Argument\Search\HybridSearch;
|
||||
|
||||
use Predis\Command\Argument\ArrayableArgument;
|
||||
|
||||
class Reducer implements ArrayableArgument
|
||||
{
|
||||
public const REDUCE_COUNT = 'COUNT';
|
||||
public const REDUCE_COUNT_DISTINCT = 'COUNT_DISTINCT';
|
||||
public const REDUCE_COUNT_DISTINCTISH = 'COUNT_DISTINCTISH';
|
||||
public const REDUCE_SUM = 'SUM';
|
||||
public const REDUCE_MIN = 'MIN';
|
||||
public const REDUCE_MAX = 'MAX';
|
||||
public const REDUCE_AVG = 'AVG';
|
||||
public const REDUCE_STDDEV = 'STDDEV';
|
||||
public const REDUCE_QUANTILE = 'QUANTILE';
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $arguments = [];
|
||||
|
||||
/**
|
||||
* @param string $function One of the available functions. Check class constants.
|
||||
* @param array $arguments List of properties
|
||||
*/
|
||||
public function __construct(string $function = self::REDUCE_COUNT, array $arguments = [], ?string $alias = null)
|
||||
{
|
||||
array_push($this->arguments, $function, count($arguments), ...$arguments);
|
||||
|
||||
if ($alias) {
|
||||
array_push($this->arguments, 'AS', $alias);
|
||||
}
|
||||
}
|
||||
|
||||
public function toArray(): array
|
||||
{
|
||||
return $this->arguments;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
<?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\Command\Argument\Search\HybridSearch;
|
||||
|
||||
use Predis\Command\Argument\ArrayableArgument;
|
||||
|
||||
class ScorerConfig implements ArrayableArgument
|
||||
{
|
||||
public const TYPE_BM25 = 'BM25';
|
||||
public const TYPE_TFIDF = 'TFIDF';
|
||||
public const TYPE_DISMAX = 'DISMAX';
|
||||
public const TYPE_DOCSCORE = 'DOCSCORE';
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $arguments = [];
|
||||
|
||||
/**
|
||||
* The text scoring algorithm. Defaults to BM25.
|
||||
*
|
||||
* @param string $type
|
||||
* @return $this
|
||||
*/
|
||||
public function type(string $type = self::TYPE_BM25): self
|
||||
{
|
||||
$this->arguments[] = $type;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* An alias for the text score field in the results.
|
||||
* The aliased field will be included in the `value` object of each returned document.
|
||||
*
|
||||
* @param string $alias
|
||||
* @return $this
|
||||
*/
|
||||
public function as(string $alias): self
|
||||
{
|
||||
array_push($this->arguments, 'YIELD_SCORE_AS', $alias);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function toArray(): array
|
||||
{
|
||||
return $this->arguments;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
<?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\Command\Argument\Search\HybridSearch;
|
||||
|
||||
use Predis\Command\Argument\ArrayableArgument;
|
||||
|
||||
class SearchConfig implements ArrayableArgument
|
||||
{
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $arguments = ['SEARCH'];
|
||||
|
||||
/**
|
||||
* @var ScorerConfig
|
||||
*/
|
||||
protected $scorerConfig;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->scorerConfig = new ScorerConfig();
|
||||
}
|
||||
|
||||
/**
|
||||
* Search query.
|
||||
*
|
||||
* @param string $query
|
||||
* @return $this
|
||||
*/
|
||||
public function query(string $query): self
|
||||
{
|
||||
$this->arguments[] = $query;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $alias
|
||||
* @return $this
|
||||
*/
|
||||
public function as(string $alias): self
|
||||
{
|
||||
array_push($this->arguments, 'YIELD_SCORE_AS', $alias);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param callable(ScorerConfig): void $callable
|
||||
* @return $this
|
||||
*/
|
||||
public function buildScorerConfig(callable $callable): self
|
||||
{
|
||||
$callable($this->scorerConfig);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function toArray(): array
|
||||
{
|
||||
$scorerConfig = $this->scorerConfig->toArray();
|
||||
|
||||
if (!empty($scorerConfig)) {
|
||||
$this->arguments[] = 'SCORER';
|
||||
$this->arguments = array_merge($this->arguments, $scorerConfig);
|
||||
}
|
||||
|
||||
return $this->arguments;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
<?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\Command\Argument\Search\HybridSearch\VectorSearch;
|
||||
|
||||
use Predis\Command\Argument\ArrayableArgument;
|
||||
use Predis\Command\Redis\Utils\VectorUtility;
|
||||
|
||||
abstract class BaseVectorSearchConfig implements ArrayableArgument
|
||||
{
|
||||
public const POLICY_ADHOC = 'ADHOC';
|
||||
public const POLICY_BATCHES = 'BATCHES';
|
||||
public const POLICY_ACORN = 'ACORN';
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $vector = [];
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $filter = [];
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $as = [];
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $arguments = ['VSIM'];
|
||||
|
||||
/**
|
||||
* Vector to perform search against.
|
||||
*
|
||||
* @param string $field The vector field name to search against. Must start with "@".
|
||||
* @param string|float[] $value Binary vector representation or array of floats as vector.
|
||||
* @return self
|
||||
*/
|
||||
public function vector(string $field, $value): self
|
||||
{
|
||||
if (is_array($value)) {
|
||||
array_push($this->vector, $field, VectorUtility::toBlob($value));
|
||||
} else {
|
||||
array_push($this->vector, $field, $value);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $expression
|
||||
* @return $this
|
||||
*/
|
||||
public function filter(string $expression): self
|
||||
{
|
||||
array_push($this->filter, 'FILTER', $expression);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $alias
|
||||
* @return $this
|
||||
*/
|
||||
public function as(string $alias): self
|
||||
{
|
||||
array_push($this->as, 'YIELD_SCORE_AS', $alias);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
abstract public function toArray(): array;
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
<?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\Command\Argument\Search\HybridSearch\VectorSearch;
|
||||
|
||||
use ValueError;
|
||||
|
||||
class KNNVectorSearchConfig extends BaseVectorSearchConfig
|
||||
{
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
protected $k;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
protected $ef;
|
||||
|
||||
/**
|
||||
* The number of nearest neighbors to find. Defaults to 10 on server side.
|
||||
*
|
||||
* @param int $k
|
||||
* @return self
|
||||
*/
|
||||
public function k(int $k): self
|
||||
{
|
||||
$this->k = $k;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* The HNSW `ef_runtime` parameter for tuning the accuracy/speed trade-off.
|
||||
*
|
||||
* @param int $ef
|
||||
* @return $this
|
||||
*/
|
||||
public function ef(int $ef): self
|
||||
{
|
||||
$this->ef = $ef;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function toArray(): array
|
||||
{
|
||||
if (!$this->vector) {
|
||||
throw new ValueError('Vector configuration not specified.');
|
||||
}
|
||||
|
||||
$this->arguments = array_merge($this->arguments, $this->vector);
|
||||
|
||||
if ($this->k || $this->ef) {
|
||||
$this->arguments[] = 'KNN';
|
||||
}
|
||||
|
||||
$tokens = [];
|
||||
|
||||
if ($this->k !== null) {
|
||||
array_push($tokens, 'K', $this->k);
|
||||
}
|
||||
|
||||
if ($this->ef !== null) {
|
||||
array_push($tokens, 'EF_RUNTIME', $this->ef);
|
||||
}
|
||||
|
||||
if (!empty($tokens)) {
|
||||
array_push($this->arguments, count($tokens), ...$tokens);
|
||||
}
|
||||
|
||||
if ($this->filter) {
|
||||
$this->arguments = array_merge($this->arguments, $this->filter);
|
||||
}
|
||||
|
||||
if ($this->as) {
|
||||
array_push($this->arguments, ...$this->as);
|
||||
}
|
||||
|
||||
return $this->arguments;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
<?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\Command\Argument\Search\HybridSearch\VectorSearch;
|
||||
|
||||
use ValueError;
|
||||
|
||||
class RangeVectorSearchConfig extends BaseVectorSearchConfig
|
||||
{
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
protected $radius;
|
||||
|
||||
/**
|
||||
* @var float
|
||||
*/
|
||||
protected $epsilon;
|
||||
|
||||
/**
|
||||
* The search radius/threshold. Finds all vectors within this distance.
|
||||
*
|
||||
* @param int $radius
|
||||
* @return $this
|
||||
*/
|
||||
public function radius(int $radius): self
|
||||
{
|
||||
$this->radius = $radius;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param float $epsilon
|
||||
* @return $this
|
||||
*/
|
||||
public function epsilon(float $epsilon): self
|
||||
{
|
||||
$this->epsilon = $epsilon;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function toArray(): array
|
||||
{
|
||||
if (!$this->vector) {
|
||||
throw new ValueError('Vector configuration not specified.');
|
||||
}
|
||||
|
||||
$this->arguments = array_merge($this->arguments, $this->vector);
|
||||
|
||||
if ($this->radius || $this->epsilon) {
|
||||
$this->arguments[] = 'RANGE';
|
||||
}
|
||||
|
||||
$tokens = [];
|
||||
|
||||
if ($this->radius !== null) {
|
||||
array_push($tokens, 'RADIUS', $this->radius);
|
||||
}
|
||||
|
||||
if ($this->epsilon !== null) {
|
||||
array_push($tokens, 'EPSILON', $this->epsilon);
|
||||
}
|
||||
|
||||
if (!empty($tokens)) {
|
||||
array_push($this->arguments, count($tokens), ...$tokens);
|
||||
}
|
||||
|
||||
if ($this->filter) {
|
||||
$this->arguments = array_merge($this->arguments, $this->filter);
|
||||
}
|
||||
|
||||
if ($this->as) {
|
||||
array_push($this->arguments, ...$this->as);
|
||||
}
|
||||
|
||||
return $this->arguments;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
<?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\Command\Redis;
|
||||
|
||||
use Predis\Command\PrefixableCommand;
|
||||
|
||||
class DELEX extends PrefixableCommand
|
||||
{
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function getId()
|
||||
{
|
||||
return 'DELEX';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function prefixKeys($prefix)
|
||||
{
|
||||
$this->applyPrefixForFirstArgument($prefix);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
<?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\Command\Redis;
|
||||
|
||||
use Predis\Command\PrefixableCommand;
|
||||
|
||||
class DIGEST extends PrefixableCommand
|
||||
{
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function getId()
|
||||
{
|
||||
return 'DIGEST';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function prefixKeys($prefix)
|
||||
{
|
||||
$this->applyPrefixForFirstArgument($prefix);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
<?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\Command\Redis;
|
||||
|
||||
use Predis\Command\PrefixableCommand;
|
||||
use ValueError;
|
||||
|
||||
class MSETEX extends PrefixableCommand
|
||||
{
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function getId()
|
||||
{
|
||||
return 'MSETEX';
|
||||
}
|
||||
|
||||
public function setArguments(array $arguments)
|
||||
{
|
||||
$processedArguments = [count(array_keys($arguments[0]))];
|
||||
|
||||
array_walk($arguments[0], function ($value, $key) use (&$processedArguments) {
|
||||
array_push($processedArguments, $key, $value);
|
||||
});
|
||||
|
||||
if (isset($arguments[1])) {
|
||||
if (!in_array(strtoupper($arguments[1]), ['NX', 'XX'])) {
|
||||
throw new ValueError('Incorrect exist modifier. Should be one of: NX, XX.');
|
||||
}
|
||||
|
||||
$processedArguments[] = strtoupper($arguments[1]);
|
||||
}
|
||||
|
||||
if (isset($arguments[2])) {
|
||||
if (!isset($arguments[3]) && strtoupper($arguments[2]) !== 'KEEPTTL') {
|
||||
throw new ValueError('TTL should be specified along with expire resolution parameter');
|
||||
}
|
||||
|
||||
if (!in_array(strtoupper($arguments[2]), ['EX', 'PX', 'EXAT', 'PXAT', 'KEEPTTL'])) {
|
||||
throw new ValueError('Incorrect expire modifier. Should be one of: EX, PX, EXAT, PXAT, KEEPTTL');
|
||||
}
|
||||
|
||||
if (strtoupper($arguments[2]) !== 'KEEPTTL') {
|
||||
array_push($processedArguments, strtoupper($arguments[2]), $arguments[3]);
|
||||
} else {
|
||||
$processedArguments[] = strtoupper($arguments[2]);
|
||||
}
|
||||
}
|
||||
|
||||
parent::setArguments($processedArguments);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function prefixKeys($prefix)
|
||||
{
|
||||
$arguments = $this->getArguments();
|
||||
$keysCount = $arguments[0];
|
||||
$currentKeyIndex = 1;
|
||||
|
||||
while ($keysCount > 0) {
|
||||
$arguments[$currentKeyIndex] = $prefix . $arguments[$currentKeyIndex];
|
||||
$keysCount--;
|
||||
$currentKeyIndex += 2;
|
||||
}
|
||||
|
||||
parent::setRawArguments($arguments);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
<?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\Command\Redis\Search;
|
||||
|
||||
use Predis\Command\PrefixableCommand as RedisCommand;
|
||||
use Predis\Command\Redis\Utils\CommandUtility;
|
||||
|
||||
class FTHYBRID extends RedisCommand
|
||||
{
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getId()
|
||||
{
|
||||
return 'FT.HYBRID';
|
||||
}
|
||||
|
||||
public function setArguments(array $arguments)
|
||||
{
|
||||
[$index, $query] = $arguments;
|
||||
|
||||
parent::setArguments(array_merge(
|
||||
[$index],
|
||||
$query->toArray()
|
||||
));
|
||||
}
|
||||
|
||||
public function parseResponse($data)
|
||||
{
|
||||
$response = CommandUtility::arrayToDictionary($data, null, false);
|
||||
|
||||
foreach ($response['results'] as $key => $result) {
|
||||
$response['results'][$key] = CommandUtility::arrayToDictionary($result);
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
public function prefixKeys($prefix)
|
||||
{
|
||||
$this->applyPrefixForFirstArgument($prefix);
|
||||
}
|
||||
}
|
||||
@@ -12,6 +12,7 @@
|
||||
|
||||
namespace Predis\Command\Redis\Utils;
|
||||
|
||||
use RuntimeException;
|
||||
use UnexpectedValueException;
|
||||
|
||||
class CommandUtility
|
||||
@@ -53,4 +54,36 @@ class CommandUtility
|
||||
|
||||
return $dict;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a value into XXH3 hash.
|
||||
*
|
||||
* @param $value
|
||||
* @return string
|
||||
*/
|
||||
public static function xxh3Hash($value): string
|
||||
{
|
||||
if (!in_array('xxh3', hash_algos(), true)) {
|
||||
throw new RuntimeException('XXH3 algorithm is not supported. Please install PECL xxhash extension.');
|
||||
}
|
||||
|
||||
return hash('xxh3', $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts associative array into flatten array (key1, value1...keyN, valueN).
|
||||
*
|
||||
* @param array $dict
|
||||
* @return array
|
||||
*/
|
||||
public static function dictionaryToArray(array $dict): array
|
||||
{
|
||||
$array = [];
|
||||
|
||||
array_walk($dict, function ($value, $key) use (&$array) {
|
||||
array_push($array, $key, $value);
|
||||
});
|
||||
|
||||
return $array;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,9 +57,13 @@ class VLINKS extends RedisCommand
|
||||
if (!is_null($data)) {
|
||||
if ($this->withScores) {
|
||||
foreach ($data as $key => $value) {
|
||||
$data[$key] = CommandUtility::arrayToDictionary($value, function ($key, $value) {
|
||||
return [$key, (float) $value];
|
||||
});
|
||||
if ($value === array_values($value)) {
|
||||
$data[$key] = CommandUtility::arrayToDictionary($value, function ($key, $value) {
|
||||
return [$key, (float) $value];
|
||||
});
|
||||
} else {
|
||||
$data[$key] = $value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
|
||||
namespace Predis\Command\Redis;
|
||||
|
||||
use Predis\Command\Command as RedisCommand;
|
||||
use Predis\Command\PrefixableCommand as RedisCommand;
|
||||
|
||||
/**
|
||||
* @see https://redis.io/commands/?name=xgroup
|
||||
@@ -78,4 +78,11 @@ class XGROUP extends RedisCommand
|
||||
|
||||
parent::setArguments($processedArguments);
|
||||
}
|
||||
|
||||
public function prefixKeys($prefix)
|
||||
{
|
||||
$arguments = $this->getArguments();
|
||||
$arguments[1] = $prefix . $arguments[1];
|
||||
$this->setRawArguments($arguments);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,8 +12,12 @@
|
||||
|
||||
namespace Predis\Command\Redis;
|
||||
|
||||
use Predis\Command\Command as RedisCommand;
|
||||
use Predis\Command\PrefixableCommand as RedisCommand;
|
||||
|
||||
/**
|
||||
* @deprecated Public API will be changed in the next major version.
|
||||
* XREADGROUP_CLAIM API will be used instead.
|
||||
*/
|
||||
class XREADGROUP extends RedisCommand
|
||||
{
|
||||
public function getId()
|
||||
@@ -58,4 +62,18 @@ class XREADGROUP extends RedisCommand
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function prefixKeys($prefix)
|
||||
{
|
||||
$arguments = $this->getArguments();
|
||||
$keyIdsStartingIndex = array_search('STREAMS', $arguments) + 1;
|
||||
$keysAndIdsCount = count($arguments) - $keyIdsStartingIndex;
|
||||
$keysCount = $keysAndIdsCount / 2;
|
||||
|
||||
for ($i = $keyIdsStartingIndex; $i < $keyIdsStartingIndex + $keysCount; $i++) {
|
||||
$arguments[$i] = $prefix . $arguments[$i];
|
||||
}
|
||||
|
||||
parent::setRawArguments($arguments);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
<?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\Command\Redis;
|
||||
|
||||
use Predis\Command\PrefixableCommand as RedisCommand;
|
||||
|
||||
/**
|
||||
* This is a transitional command. In the next major version this command will replace XREADGROUP.
|
||||
*/
|
||||
class XREADGROUP_CLAIM extends RedisCommand
|
||||
{
|
||||
public function getId()
|
||||
{
|
||||
return 'XREADGROUP';
|
||||
}
|
||||
|
||||
public function setArguments(array $arguments)
|
||||
{
|
||||
$processedArguments = ['GROUP', $arguments[0], $arguments[1]];
|
||||
|
||||
if (count($arguments) >= 4 && null !== $arguments[3]) {
|
||||
array_push($processedArguments, 'COUNT', $arguments[3]);
|
||||
}
|
||||
|
||||
if (count($arguments) >= 5 && null !== $arguments[4]) {
|
||||
array_push($processedArguments, 'BLOCK', $arguments[4]);
|
||||
}
|
||||
|
||||
if (count($arguments) >= 6 && false !== $arguments[5]) {
|
||||
$processedArguments[] = 'NOACK';
|
||||
}
|
||||
|
||||
if (count($arguments) >= 7 && false !== $arguments[6]) {
|
||||
array_push($processedArguments, 'CLAIM', $arguments[6]);
|
||||
}
|
||||
|
||||
array_push($processedArguments, 'STREAMS', ...array_keys($arguments[2]), ...array_values($arguments[2]));
|
||||
|
||||
parent::setArguments($processedArguments);
|
||||
}
|
||||
|
||||
public function parseResponse($data)
|
||||
{
|
||||
if (!is_array($data) || $data === array_values($data)) {
|
||||
return $data;
|
||||
}
|
||||
|
||||
// Relay
|
||||
$result = [];
|
||||
foreach ($data as $key => $value) {
|
||||
$group = [$key, $value];
|
||||
$result[] = $group;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function prefixKeys($prefix)
|
||||
{
|
||||
$arguments = $this->getArguments();
|
||||
$keyIdsStartingIndex = array_search('STREAMS', $arguments) + 1;
|
||||
$keysAndIdsCount = count($arguments) - $keyIdsStartingIndex;
|
||||
$keysCount = $keysAndIdsCount / 2;
|
||||
|
||||
for ($i = $keyIdsStartingIndex; $i < $keyIdsStartingIndex + $keysCount; $i++) {
|
||||
$arguments[$i] = $prefix . $arguments[$i];
|
||||
}
|
||||
|
||||
parent::setRawArguments($arguments);
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,7 @@ use PHPUnit\OneOfConstraint;
|
||||
use PHPUnit\Util\Test as TestUtil;
|
||||
use Predis\Client;
|
||||
use Predis\Command;
|
||||
use Predis\Command\Processor\KeyPrefixProcessor;
|
||||
use Predis\Connection;
|
||||
|
||||
/**
|
||||
@@ -262,8 +263,14 @@ abstract class PredisTestCase extends PHPUnit\Framework\TestCase
|
||||
$parameters ?: []
|
||||
);
|
||||
|
||||
$commandsFactory = $this->getCommandFactory();
|
||||
|
||||
if (null !== $options && array_key_exists('prefix', $options)) {
|
||||
$commandsFactory->setProcessor(new KeyPrefixProcessor($options['prefix']));
|
||||
}
|
||||
|
||||
$options = array_merge(
|
||||
['commands' => $this->getCommandFactory()],
|
||||
['commands' => $commandsFactory],
|
||||
$options ?: [],
|
||||
getenv('USE_RELAY') ? ['connections' => 'relay'] : []
|
||||
);
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
<?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\Command\Argument\Search\HybridSearch\Combine;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class LinearCombineConfigTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* @dataProvider argumentsProvider
|
||||
* @return void
|
||||
*/
|
||||
public function testToArray(
|
||||
array $expectedReturn,
|
||||
?float $alpha = null,
|
||||
?float $beta = null,
|
||||
?string $as = null
|
||||
) {
|
||||
$config = new LinearCombineConfig();
|
||||
|
||||
if ($alpha) {
|
||||
$this->assertEquals($config, $config->alpha($alpha));
|
||||
}
|
||||
|
||||
if ($beta) {
|
||||
$this->assertEquals($config, $config->beta($beta));
|
||||
}
|
||||
|
||||
if ($as) {
|
||||
$this->assertEquals($config, $config->as($as));
|
||||
}
|
||||
|
||||
$this->assertSame($expectedReturn, $config->toArray());
|
||||
}
|
||||
|
||||
public function argumentsProvider(): array
|
||||
{
|
||||
return [
|
||||
'with ALPHA' => [['COMBINE', 'LINEAR', 2, 'ALPHA', 0.2], 0.2, null, null],
|
||||
'with BETA' => [['COMBINE', 'LINEAR', 2, 'BETA', 0.2], null, 0.2, null],
|
||||
'with all arguments' => [
|
||||
['COMBINE', 'LINEAR', 6, 'ALPHA', 0.3, 'BETA', 0.2, 'YIELD_SCORE_AS', 'alias'],
|
||||
0.3, 0.2, 'alias',
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
<?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\Command\Argument\Search\HybridSearch\Combine;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class RRFCombineConfigTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* @dataProvider argumentsProvider
|
||||
* @return void
|
||||
*/
|
||||
public function testToArray(
|
||||
array $expectedReturn,
|
||||
?int $window = null,
|
||||
?int $rrfConstant = null,
|
||||
?string $as = null
|
||||
) {
|
||||
$config = new RRFCombineConfig();
|
||||
|
||||
if ($window) {
|
||||
$this->assertEquals($config, $config->window($window));
|
||||
}
|
||||
|
||||
if ($rrfConstant) {
|
||||
$this->assertEquals($config, $config->rrfConstant($rrfConstant));
|
||||
}
|
||||
|
||||
if ($as) {
|
||||
$this->assertEquals($config, $config->as($as));
|
||||
}
|
||||
|
||||
$this->assertSame($expectedReturn, $config->toArray());
|
||||
}
|
||||
|
||||
public function argumentsProvider(): array
|
||||
{
|
||||
return [
|
||||
'with WINDOW' => [['COMBINE', 'RRF', 2, 'WINDOW', 5], 5, null, null],
|
||||
'with CONSTANT' => [['COMBINE', 'RRF', 2, 'CONSTANT', 5], null, 5, null],
|
||||
'with all' => [['COMBINE', 'RRF', 6, 'WINDOW', 10, 'CONSTANT', 5, 'YIELD_SCORE_AS', 'alias'], 10, 5, 'alias'],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,288 @@
|
||||
<?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\Command\Argument\Search\HybridSearch;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Predis\Command\Argument\Search\HybridSearch\Combine\LinearCombineConfig;
|
||||
use Predis\Command\Argument\Search\HybridSearch\Combine\RRFCombineConfig;
|
||||
use Predis\Command\Argument\Search\HybridSearch\VectorSearch\KNNVectorSearchConfig;
|
||||
use Predis\Command\Argument\Search\HybridSearch\VectorSearch\RangeVectorSearchConfig;
|
||||
use Predis\Command\Redis\Utils\VectorUtility;
|
||||
|
||||
class HybridSearchQueryTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* @dataProvider argumentsProvider
|
||||
* @return void
|
||||
*/
|
||||
public function testToArray(HybridSearchQuery $config, array $expectedReturn)
|
||||
{
|
||||
$this->assertSame($expectedReturn, $config->toArray());
|
||||
}
|
||||
|
||||
public function argumentsProvider(): array
|
||||
{
|
||||
return [
|
||||
'with default configs' => [
|
||||
(new HybridSearchQuery())
|
||||
->buildSearchConfig(function (SearchConfig $config) {
|
||||
$config->buildScorerConfig(function (ScorerConfig $scorerConfig) {
|
||||
$scorerConfig->type(ScorerConfig::TYPE_DISMAX);
|
||||
})
|
||||
->query('*');
|
||||
})
|
||||
->buildVectorSearchConfig(function (KNNVectorSearchConfig $config) {
|
||||
$config
|
||||
->vector('vector', [0.1, 0.2, 0.3])
|
||||
->k(5)
|
||||
->ef(10);
|
||||
}),
|
||||
['SEARCH', '*', 'SCORER', ScorerConfig::TYPE_DISMAX, 'VSIM', 'vector', VectorUtility::toBlob([0.1, 0.2, 0.3]), 'KNN', 4, 'K', 5, 'EF_RUNTIME', 10],
|
||||
],
|
||||
'with RANGE vector search' => [
|
||||
(new HybridSearchQuery(RangeVectorSearchConfig::class))
|
||||
->buildSearchConfig(function (SearchConfig $config) {
|
||||
$config->buildScorerConfig(function (ScorerConfig $scorerConfig) {
|
||||
$scorerConfig->type(ScorerConfig::TYPE_DISMAX);
|
||||
})
|
||||
->query('*');
|
||||
})
|
||||
->buildVectorSearchConfig(function (RangeVectorSearchConfig $config) {
|
||||
$config
|
||||
->vector('vector', [0.1, 0.2, 0.3])
|
||||
->radius(5)
|
||||
->epsilon(0.2);
|
||||
}),
|
||||
['SEARCH', '*', 'SCORER', ScorerConfig::TYPE_DISMAX, 'VSIM', 'vector', VectorUtility::toBlob([0.1, 0.2, 0.3]), 'RANGE', 4, 'RADIUS', 5, 'EPSILON', 0.2],
|
||||
],
|
||||
'with COMBINE config - RRF' => [
|
||||
(new HybridSearchQuery())
|
||||
->buildSearchConfig(function (SearchConfig $config) {
|
||||
$config->buildScorerConfig(function (ScorerConfig $scorerConfig) {
|
||||
$scorerConfig->type(ScorerConfig::TYPE_DISMAX);
|
||||
})
|
||||
->query('*');
|
||||
})
|
||||
->buildVectorSearchConfig(function (KNNVectorSearchConfig $config) {
|
||||
$config
|
||||
->vector('vector', [0.1, 0.2, 0.3])
|
||||
->k(5)
|
||||
->ef(10);
|
||||
})
|
||||
->buildCombineConfig(function (RRFCombineConfig $config) {
|
||||
$config
|
||||
->window(5)
|
||||
->rrfConstant(10);
|
||||
}),
|
||||
['SEARCH', '*', 'SCORER', ScorerConfig::TYPE_DISMAX, 'VSIM', 'vector', VectorUtility::toBlob([0.1, 0.2, 0.3]), 'KNN', 4, 'K', 5, 'EF_RUNTIME', 10, 'COMBINE', 'RRF', 4, 'WINDOW', 5, 'CONSTANT', 10],
|
||||
],
|
||||
'with COMBINE config - LINEAR' => [
|
||||
(new HybridSearchQuery(KNNVectorSearchConfig::class, LinearCombineConfig::class))
|
||||
->buildSearchConfig(function (SearchConfig $config) {
|
||||
$config->buildScorerConfig(function (ScorerConfig $scorerConfig) {
|
||||
$scorerConfig->type(ScorerConfig::TYPE_DISMAX);
|
||||
})
|
||||
->query('*');
|
||||
})
|
||||
->buildVectorSearchConfig(function (KNNVectorSearchConfig $config) {
|
||||
$config
|
||||
->vector('vector', [0.1, 0.2, 0.3])
|
||||
->k(5)
|
||||
->ef(10);
|
||||
})
|
||||
->buildCombineConfig(function (LinearCombineConfig $config) {
|
||||
$config
|
||||
->alpha(0.2)
|
||||
->beta(0.3);
|
||||
}),
|
||||
['SEARCH', '*', 'SCORER', ScorerConfig::TYPE_DISMAX, 'VSIM', 'vector', VectorUtility::toBlob([0.1, 0.2, 0.3]), 'KNN', 4, 'K', 5, 'EF_RUNTIME', 10, 'COMBINE', 'LINEAR', 4, 'ALPHA', 0.2, 'BETA', 0.3],
|
||||
],
|
||||
'with LOAD' => [
|
||||
(new HybridSearchQuery())
|
||||
->buildSearchConfig(function (SearchConfig $config) {
|
||||
$config->buildScorerConfig(function (ScorerConfig $scorerConfig) {
|
||||
$scorerConfig->type(ScorerConfig::TYPE_DISMAX);
|
||||
})
|
||||
->query('*');
|
||||
})
|
||||
->buildVectorSearchConfig(function (KNNVectorSearchConfig $config) {
|
||||
$config
|
||||
->vector('vector', [0.1, 0.2, 0.3])
|
||||
->k(5)
|
||||
->ef(10);
|
||||
})
|
||||
->load(['field1', 'field2']),
|
||||
['SEARCH', '*', 'SCORER', ScorerConfig::TYPE_DISMAX, 'VSIM', 'vector', VectorUtility::toBlob([0.1, 0.2, 0.3]), 'KNN', 4, 'K', 5, 'EF_RUNTIME', 10, 'LOAD', 2, 'field1', 'field2'],
|
||||
],
|
||||
'with GROUPBY' => [
|
||||
(new HybridSearchQuery())
|
||||
->buildSearchConfig(function (SearchConfig $config) {
|
||||
$config->buildScorerConfig(function (ScorerConfig $scorerConfig) {
|
||||
$scorerConfig->type(ScorerConfig::TYPE_DISMAX);
|
||||
})
|
||||
->query('*');
|
||||
})
|
||||
->buildVectorSearchConfig(function (KNNVectorSearchConfig $config) {
|
||||
$config
|
||||
->vector('vector', [0.1, 0.2, 0.3])
|
||||
->k(5)
|
||||
->ef(10);
|
||||
})
|
||||
->groupBy(
|
||||
['field1', 'field2'],
|
||||
[
|
||||
new Reducer(Reducer::REDUCE_COUNT, ['prop1', 'prop2'], 'alias'),
|
||||
new Reducer(Reducer::REDUCE_MAX, ['prop1', 'prop2']),
|
||||
]
|
||||
),
|
||||
['SEARCH', '*', 'SCORER', ScorerConfig::TYPE_DISMAX, 'VSIM', 'vector', VectorUtility::toBlob([0.1, 0.2, 0.3]), 'KNN', 4, 'K', 5, 'EF_RUNTIME', 10, 'GROUPBY', 2, 'field1', 'field2', 'REDUCE', 'COUNT', 2, 'prop1', 'prop2', 'AS', 'alias', 'REDUCE', 'MAX', 2, 'prop1', 'prop2'],
|
||||
],
|
||||
'with APPLY' => [
|
||||
(new HybridSearchQuery())
|
||||
->buildSearchConfig(function (SearchConfig $config) {
|
||||
$config->buildScorerConfig(function (ScorerConfig $scorerConfig) {
|
||||
$scorerConfig->type(ScorerConfig::TYPE_DISMAX);
|
||||
})
|
||||
->query('*');
|
||||
})
|
||||
->buildVectorSearchConfig(function (KNNVectorSearchConfig $config) {
|
||||
$config
|
||||
->vector('vector', [0.1, 0.2, 0.3])
|
||||
->k(5)
|
||||
->ef(10);
|
||||
})
|
||||
->apply(['field' => 'expr']),
|
||||
['SEARCH', '*', 'SCORER', ScorerConfig::TYPE_DISMAX, 'VSIM', 'vector', VectorUtility::toBlob([0.1, 0.2, 0.3]), 'KNN', 4, 'K', 5, 'EF_RUNTIME', 10, 'APPLY', 'expr', 'AS', 'field'],
|
||||
],
|
||||
'with SORTBY' => [
|
||||
(new HybridSearchQuery())
|
||||
->buildSearchConfig(function (SearchConfig $config) {
|
||||
$config->buildScorerConfig(function (ScorerConfig $scorerConfig) {
|
||||
$scorerConfig->type(ScorerConfig::TYPE_DISMAX);
|
||||
})
|
||||
->query('*');
|
||||
})
|
||||
->buildVectorSearchConfig(function (KNNVectorSearchConfig $config) {
|
||||
$config
|
||||
->vector('vector', [0.1, 0.2, 0.3])
|
||||
->k(5)
|
||||
->ef(10);
|
||||
})
|
||||
->sortBy(['field' => 'DESC']),
|
||||
['SEARCH', '*', 'SCORER', ScorerConfig::TYPE_DISMAX, 'VSIM', 'vector', VectorUtility::toBlob([0.1, 0.2, 0.3]), 'KNN', 4, 'K', 5, 'EF_RUNTIME', 10, 'SORTBY', 2, 'field', 'DESC'],
|
||||
],
|
||||
'with FILTER' => [
|
||||
(new HybridSearchQuery())
|
||||
->buildSearchConfig(function (SearchConfig $config) {
|
||||
$config->buildScorerConfig(function (ScorerConfig $scorerConfig) {
|
||||
$scorerConfig->type(ScorerConfig::TYPE_DISMAX);
|
||||
})
|
||||
->query('*');
|
||||
})
|
||||
->buildVectorSearchConfig(function (KNNVectorSearchConfig $config) {
|
||||
$config
|
||||
->vector('vector', [0.1, 0.2, 0.3])
|
||||
->k(5)
|
||||
->ef(10);
|
||||
})
|
||||
->filter('expr'),
|
||||
['SEARCH', '*', 'SCORER', ScorerConfig::TYPE_DISMAX, 'VSIM', 'vector', VectorUtility::toBlob([0.1, 0.2, 0.3]), 'KNN', 4, 'K', 5, 'EF_RUNTIME', 10, 'FILTER', 'expr'],
|
||||
],
|
||||
'with LIMIT' => [
|
||||
(new HybridSearchQuery())
|
||||
->buildSearchConfig(function (SearchConfig $config) {
|
||||
$config->buildScorerConfig(function (ScorerConfig $scorerConfig) {
|
||||
$scorerConfig->type(ScorerConfig::TYPE_DISMAX);
|
||||
})
|
||||
->query('*');
|
||||
})
|
||||
->buildVectorSearchConfig(function (KNNVectorSearchConfig $config) {
|
||||
$config
|
||||
->vector('vector', [0.1, 0.2, 0.3])
|
||||
->k(5)
|
||||
->ef(10);
|
||||
})
|
||||
->limit(0, 10),
|
||||
['SEARCH', '*', 'SCORER', ScorerConfig::TYPE_DISMAX, 'VSIM', 'vector', VectorUtility::toBlob([0.1, 0.2, 0.3]), 'KNN', 4, 'K', 5, 'EF_RUNTIME', 10, 'LIMIT', 0, 10],
|
||||
],
|
||||
'with PARAMS' => [
|
||||
(new HybridSearchQuery())
|
||||
->buildSearchConfig(function (SearchConfig $config) {
|
||||
$config->buildScorerConfig(function (ScorerConfig $scorerConfig) {
|
||||
$scorerConfig->type(ScorerConfig::TYPE_DISMAX);
|
||||
})
|
||||
->query('*');
|
||||
})
|
||||
->buildVectorSearchConfig(function (KNNVectorSearchConfig $config) {
|
||||
$config
|
||||
->vector('vector', [0.1, 0.2, 0.3])
|
||||
->k(5)
|
||||
->ef(10);
|
||||
})
|
||||
->params(['param1' => 'value1', 'param2' => 'value2']),
|
||||
['SEARCH', '*', 'SCORER', ScorerConfig::TYPE_DISMAX, 'VSIM', 'vector', VectorUtility::toBlob([0.1, 0.2, 0.3]), 'KNN', 4, 'K', 5, 'EF_RUNTIME', 10, 'PARAMS', 4, 'param1', 'value1', 'param2', 'value2'],
|
||||
],
|
||||
'with EXPLAINSCORE' => [
|
||||
(new HybridSearchQuery())
|
||||
->buildSearchConfig(function (SearchConfig $config) {
|
||||
$config->buildScorerConfig(function (ScorerConfig $scorerConfig) {
|
||||
$scorerConfig->type(ScorerConfig::TYPE_DISMAX);
|
||||
})
|
||||
->query('*');
|
||||
})
|
||||
->buildVectorSearchConfig(function (KNNVectorSearchConfig $config) {
|
||||
$config
|
||||
->vector('vector', [0.1, 0.2, 0.3])
|
||||
->k(5)
|
||||
->ef(10);
|
||||
})
|
||||
->explainScore(),
|
||||
['SEARCH', '*', 'SCORER', ScorerConfig::TYPE_DISMAX, 'VSIM', 'vector', VectorUtility::toBlob([0.1, 0.2, 0.3]), 'KNN', 4, 'K', 5, 'EF_RUNTIME', 10, 'EXPLAINSCORE'],
|
||||
],
|
||||
'with TIMEOUT' => [
|
||||
(new HybridSearchQuery())
|
||||
->buildSearchConfig(function (SearchConfig $config) {
|
||||
$config->buildScorerConfig(function (ScorerConfig $scorerConfig) {
|
||||
$scorerConfig->type(ScorerConfig::TYPE_DISMAX);
|
||||
})
|
||||
->query('*');
|
||||
})
|
||||
->buildVectorSearchConfig(function (KNNVectorSearchConfig $config) {
|
||||
$config
|
||||
->vector('vector', [0.1, 0.2, 0.3])
|
||||
->k(5)
|
||||
->ef(10);
|
||||
})
|
||||
->timeout(),
|
||||
['SEARCH', '*', 'SCORER', ScorerConfig::TYPE_DISMAX, 'VSIM', 'vector', VectorUtility::toBlob([0.1, 0.2, 0.3]), 'KNN', 4, 'K', 5, 'EF_RUNTIME', 10, 'TIMEOUT'],
|
||||
],
|
||||
'with WITHCURSOR' => [
|
||||
(new HybridSearchQuery())
|
||||
->buildSearchConfig(function (SearchConfig $config) {
|
||||
$config->buildScorerConfig(function (ScorerConfig $scorerConfig) {
|
||||
$scorerConfig->type(ScorerConfig::TYPE_DISMAX);
|
||||
})
|
||||
->query('*');
|
||||
})
|
||||
->buildVectorSearchConfig(function (KNNVectorSearchConfig $config) {
|
||||
$config
|
||||
->vector('vector', [0.1, 0.2, 0.3])
|
||||
->k(5)
|
||||
->ef(10);
|
||||
})
|
||||
->withCursor(10, 10),
|
||||
['SEARCH', '*', 'SCORER', ScorerConfig::TYPE_DISMAX, 'VSIM', 'vector', VectorUtility::toBlob([0.1, 0.2, 0.3]), 'KNN', 4, 'K', 5, 'EF_RUNTIME', 10, 'WITHCURSOR', 'COUNT', 10, 'MAXIDLE', 10],
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
<?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\Command\Argument\Search\HybridSearch;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class ReducerTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function testToArray(): void
|
||||
{
|
||||
$reducer = new Reducer(Reducer::REDUCE_MAX, ['prop1', 'prop2'], 'alias');
|
||||
$this->assertSame([Reducer::REDUCE_MAX, 2, 'prop1', 'prop2', 'AS', 'alias'], $reducer->toArray());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
<?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\Command\Argument\Search\HybridSearch;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class ScorerConfigTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* @dataProvider argumentsProvider
|
||||
* @return void
|
||||
*/
|
||||
public function testToArray(
|
||||
array $expectedReturn,
|
||||
?string $type = null,
|
||||
?string $as = null
|
||||
) {
|
||||
$config = new ScorerConfig();
|
||||
|
||||
if ($type) {
|
||||
$this->assertEquals($config, $config->type($type));
|
||||
}
|
||||
|
||||
if ($as) {
|
||||
$this->assertEquals($config, $config->as($as));
|
||||
}
|
||||
|
||||
$this->assertSame($expectedReturn, $config->toArray());
|
||||
}
|
||||
|
||||
public function argumentsProvider(): array
|
||||
{
|
||||
return [
|
||||
'with TYPE' => [[ScorerConfig::TYPE_BM25], ScorerConfig::TYPE_BM25],
|
||||
'with YIELD_SCORE_AS' => [['YIELD_SCORE_AS', 'alias'], null, 'alias'],
|
||||
'with all' => [
|
||||
[ScorerConfig::TYPE_DISMAX, 'YIELD_SCORE_AS', 'alias'],
|
||||
ScorerConfig::TYPE_DISMAX, 'alias',
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
<?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\Command\Argument\Search\HybridSearch;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class SearchConfigTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* @dataProvider argumentsProvider
|
||||
* @return void
|
||||
*/
|
||||
public function testToArray(
|
||||
array $expectedReturn,
|
||||
?string $query = null,
|
||||
?string $type = null,
|
||||
?string $as = null
|
||||
) {
|
||||
$config = new SearchConfig();
|
||||
|
||||
if ($query) {
|
||||
$this->assertEquals($config, $config->query($query));
|
||||
}
|
||||
|
||||
if ($type) {
|
||||
$this->assertEquals($config, $config->buildScorerConfig(function (ScorerConfig $scorerConfig) use ($type) {
|
||||
$scorerConfig->type($type);
|
||||
}));
|
||||
}
|
||||
|
||||
if ($as) {
|
||||
$this->assertEquals($config, $config->as($as));
|
||||
}
|
||||
|
||||
$this->assertSame($expectedReturn, $config->toArray());
|
||||
}
|
||||
|
||||
public function argumentsProvider(): array
|
||||
{
|
||||
return [
|
||||
'with query' => [['SEARCH', '*'], '*', null, null],
|
||||
'with AS' => [['SEARCH', '*', 'YIELD_SCORE_AS', 'alias'], '*', null, 'alias'],
|
||||
'with SCORER' => [
|
||||
['SEARCH', '*', 'SCORER', ScorerConfig::TYPE_DOCSCORE],
|
||||
'*', ScorerConfig::TYPE_DOCSCORE, null,
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
+76
@@ -0,0 +1,76 @@
|
||||
<?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\Command\Argument\Search\HybridSearch\VectorSearch;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Predis\Command\Redis\Utils\VectorUtility;
|
||||
use ValueError;
|
||||
|
||||
class KNNVectorSearchConfigTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* @dataProvider argumentsProvider
|
||||
* @return void
|
||||
*/
|
||||
public function testToArray(KNNVectorSearchConfig $config, array $expectedReturn)
|
||||
{
|
||||
$this->assertSame($expectedReturn, $config->toArray());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function testToArrayThrowsExceptionOnMissingProperty(): void
|
||||
{
|
||||
$config = new KNNVectorSearchConfig();
|
||||
|
||||
$this->expectException(ValueError::class);
|
||||
$this->expectExceptionMessage('Vector configuration not specified.');
|
||||
|
||||
$config
|
||||
->filter('filter')
|
||||
->as('alias')
|
||||
->toArray();
|
||||
}
|
||||
|
||||
public function argumentsProvider(): array
|
||||
{
|
||||
return [
|
||||
'with vector and K' => [
|
||||
(new KNNVectorSearchConfig())
|
||||
->vector('vector', [0.1, 0.2, 0.3])
|
||||
->k(5),
|
||||
['VSIM', 'vector', VectorUtility::toBlob([0.1, 0.2, 0.3]), 'KNN', 2, 'K', 5]],
|
||||
'with vector, K and EF_RUNTIME' => [
|
||||
(new KNNVectorSearchConfig())
|
||||
->vector('vector', [0.1, 0.2, 0.3])
|
||||
->k(5)
|
||||
->ef(5),
|
||||
['VSIM', 'vector', VectorUtility::toBlob([0.1, 0.2, 0.3]), 'KNN', 4, 'K', 5, 'EF_RUNTIME', 5]],
|
||||
'with vector, K and FILTER' => [
|
||||
(new KNNVectorSearchConfig())
|
||||
->vector('vector', [0.1, 0.2, 0.3])
|
||||
->k(5)
|
||||
->filter('*'),
|
||||
['VSIM', 'vector', VectorUtility::toBlob([0.1, 0.2, 0.3]), 'KNN', 2, 'K', 5, 'FILTER', '*']],
|
||||
'with all' => [
|
||||
(new KNNVectorSearchConfig())
|
||||
->vector('vector', VectorUtility::toBlob([0.1, 0.2, 0.3]))
|
||||
->k(5)
|
||||
->ef(5)
|
||||
->filter('*')
|
||||
->as('alias'),
|
||||
['VSIM', 'vector', VectorUtility::toBlob([0.1, 0.2, 0.3]), 'KNN', 4, 'K', 5, 'EF_RUNTIME', 5, 'FILTER', '*', 'YIELD_SCORE_AS', 'alias']],
|
||||
];
|
||||
}
|
||||
}
|
||||
+86
@@ -0,0 +1,86 @@
|
||||
<?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\Command\Argument\Search\HybridSearch\VectorSearch;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Predis\Command\Redis\Utils\VectorUtility;
|
||||
use ValueError;
|
||||
|
||||
class RangeVectorSearchConfigTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* @dataProvider argumentsProvider
|
||||
* @return void
|
||||
*/
|
||||
public function testToArray(RangeVectorSearchConfig $config, array $expectedReturn)
|
||||
{
|
||||
$this->assertSame($expectedReturn, $config->toArray());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function testAs()
|
||||
{
|
||||
$config = new RangeVectorSearchConfig();
|
||||
|
||||
$this->assertEquals($config, $config->as('alias'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function testToArrayThrowsExceptionOnMissingProperty(): void
|
||||
{
|
||||
$config = new RangeVectorSearchConfig();
|
||||
|
||||
$this->expectException(ValueError::class);
|
||||
$this->expectExceptionMessage('Vector configuration not specified.');
|
||||
|
||||
$config
|
||||
->filter('filter')
|
||||
->as('alias')
|
||||
->toArray();
|
||||
}
|
||||
|
||||
public function argumentsProvider(): array
|
||||
{
|
||||
return [
|
||||
'with vector and RADIUS' => [
|
||||
(new RangeVectorSearchConfig())
|
||||
->vector('vector', [0.1, 0.2, 0.3])
|
||||
->radius(5),
|
||||
['VSIM', 'vector', VectorUtility::toBlob([0.1, 0.2, 0.3]), 'RANGE', 2, 'RADIUS', 5]],
|
||||
'with vector, RADIUS and EPSILON' => [
|
||||
(new RangeVectorSearchConfig())
|
||||
->vector('vector', [0.1, 0.2, 0.3])
|
||||
->radius(5)
|
||||
->epsilon(0.2),
|
||||
['VSIM', 'vector', VectorUtility::toBlob([0.1, 0.2, 0.3]), 'RANGE', 4, 'RADIUS', 5, 'EPSILON', 0.2]],
|
||||
'with vector, RADIUS and FILTER' => [
|
||||
(new RangeVectorSearchConfig())
|
||||
->vector('vector', [0.1, 0.2, 0.3])
|
||||
->radius(5)
|
||||
->filter('*'),
|
||||
['VSIM', 'vector', VectorUtility::toBlob([0.1, 0.2, 0.3]), 'RANGE', 2, 'RADIUS', 5, 'FILTER', '*']],
|
||||
'with all arguments' => [
|
||||
(new RangeVectorSearchConfig())
|
||||
->vector('vector', VectorUtility::toBlob([0.1, 0.2, 0.3]))
|
||||
->radius(5)
|
||||
->epsilon(0.2)
|
||||
->as('alias')
|
||||
->filter('*'),
|
||||
['VSIM', 'vector', VectorUtility::toBlob([0.1, 0.2, 0.3]), 'RANGE', 4, 'RADIUS', 5, 'EPSILON', 0.2, 'FILTER', '*', 'YIELD_SCORE_AS', 'alias']],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -241,6 +241,14 @@ class KeyPrefixProcessorTest extends PredisTestCase
|
||||
['key1', 'key2', 'key3'],
|
||||
['prefix:key1', 'prefix:key2', 'prefix:key3'],
|
||||
],
|
||||
['DELEX',
|
||||
['key1', 'IFEQ', 'value'],
|
||||
['prefix:key1', 'IFEQ', 'value'],
|
||||
],
|
||||
['DIGEST',
|
||||
['key1'],
|
||||
['prefix:key1'],
|
||||
],
|
||||
['TYPE',
|
||||
['key'],
|
||||
['prefix:key'],
|
||||
@@ -301,6 +309,10 @@ class KeyPrefixProcessorTest extends PredisTestCase
|
||||
['foo', 'bar', 'hoge', 'piyo'],
|
||||
['prefix:foo', 'bar', 'prefix:hoge', 'piyo'],
|
||||
],
|
||||
['MSETEX',
|
||||
[['key' => 'value', 'key1' => 'value', 'key2' => 'value'], 'NX', 'EX', 10],
|
||||
[3, 'prefix:key', 'value', 'prefix:key1', 'value', 'prefix:key2', 'value', 'NX', 'EX', 10],
|
||||
],
|
||||
['MSETNX',
|
||||
['foo', 'bar', 'hoge', 'piyo'],
|
||||
['prefix:foo', 'bar', 'prefix:hoge', 'piyo'],
|
||||
@@ -804,6 +816,18 @@ class KeyPrefixProcessorTest extends PredisTestCase
|
||||
['key', 'MAXLEN', 100],
|
||||
['prefix:key', 'MAXLEN', 100],
|
||||
],
|
||||
['XGROUP',
|
||||
['CREATE', 'key', 'group', '$'],
|
||||
['CREATE', 'prefix:key', 'group', '$'],
|
||||
],
|
||||
['XREADGROUP',
|
||||
['group', 'consumer', 10, 10, true, 'stream', 'stream1', '0-0', '0-0'],
|
||||
['GROUP', 'group', 'consumer', 'COUNT', 10, 'BLOCK', 10, 'NOACK', 'STREAMS', 'prefix:stream', 'prefix:stream1', '0-0', '0-0'],
|
||||
],
|
||||
['XREADGROUP_CLAIM',
|
||||
['group', 'consumer', ['stream' => '0-0', 'stream1' => '0-0'], 10, 10, true, 10],
|
||||
['GROUP', 'group', 'consumer', 'COUNT', 10, 'BLOCK', 10, 'NOACK', 'CLAIM', 10, 'STREAMS', 'prefix:stream', 'prefix:stream1', '0-0', '0-0'],
|
||||
],
|
||||
['ZPOPMIN',
|
||||
['key'],
|
||||
['prefix:key'],
|
||||
|
||||
@@ -0,0 +1,115 @@
|
||||
<?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\Command\Redis;
|
||||
|
||||
use Predis\Command\Redis\Utils\CommandUtility;
|
||||
|
||||
class DELEX_Test extends PredisCommandTestCase
|
||||
{
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
protected function getExpectedCommand(): string
|
||||
{
|
||||
return DELEX::class;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
protected function getExpectedId(): string
|
||||
{
|
||||
return 'DELEX';
|
||||
}
|
||||
|
||||
/**
|
||||
* @group disconnected
|
||||
*/
|
||||
public function testFilterArguments(): void
|
||||
{
|
||||
$arguments = ['key1'];
|
||||
$expected = ['key1'];
|
||||
|
||||
$command = $this->getCommand();
|
||||
$command->setArguments($arguments);
|
||||
$this->assertSame($expected, $command->getArguments());
|
||||
}
|
||||
|
||||
/**
|
||||
* @group connected
|
||||
* @requires PHP >= 8.1
|
||||
* @requiresRedisVersion >= 8.3.224
|
||||
*/
|
||||
public function testDeleteValueWithDifferentModifiers(): void
|
||||
{
|
||||
$redis = $this->getClient();
|
||||
$this->assertEquals(
|
||||
'OK',
|
||||
$redis->set('foo', 'bar')
|
||||
);
|
||||
|
||||
$value = $redis->get('foo');
|
||||
$this->assertEquals(
|
||||
1,
|
||||
$redis->delex('foo', 'IFEQ', $value)
|
||||
);
|
||||
$this->assertEquals(
|
||||
'OK',
|
||||
$redis->set('foo', 'bar')
|
||||
);
|
||||
|
||||
$this->assertEquals(
|
||||
0,
|
||||
$redis->delex('foo', 'IFEQ', 'wrong')
|
||||
);
|
||||
$this->assertEquals(
|
||||
1,
|
||||
$redis->delex('foo', 'IFNE', 'not equal')
|
||||
);
|
||||
$this->assertEquals(
|
||||
'OK',
|
||||
$redis->set('foo', 'bar')
|
||||
);
|
||||
|
||||
$this->assertEquals(
|
||||
0,
|
||||
$redis->delex('foo', 'IFNE', $value)
|
||||
);
|
||||
$this->assertEquals(
|
||||
1,
|
||||
$redis->delex('foo', 'IFDEQ', CommandUtility::xxh3Hash($value))
|
||||
);
|
||||
$this->assertEquals(
|
||||
'OK',
|
||||
$redis->set('foo', 'bar')
|
||||
);
|
||||
|
||||
$this->assertEquals(
|
||||
0,
|
||||
$redis->delex('foo', 'IFDEQ', CommandUtility::xxh3Hash('wrong'))
|
||||
);
|
||||
$this->assertEquals(
|
||||
1,
|
||||
$redis->delex('foo', 'IFDNE', CommandUtility::xxh3Hash('not equal'))
|
||||
);
|
||||
$this->assertEquals(
|
||||
'OK',
|
||||
$redis->set('foo', 'bar')
|
||||
);
|
||||
|
||||
$this->assertEquals(
|
||||
0,
|
||||
$redis->delex('foo', 'IFDNE', CommandUtility::xxh3Hash($value))
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
<?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\Command\Redis;
|
||||
|
||||
use Predis\Command\Redis\Utils\CommandUtility;
|
||||
|
||||
class DIGEST_Test extends PredisCommandTestCase
|
||||
{
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
protected function getExpectedCommand(): string
|
||||
{
|
||||
return DIGEST::class;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
protected function getExpectedId(): string
|
||||
{
|
||||
return 'DIGEST';
|
||||
}
|
||||
|
||||
/**
|
||||
* @group disconnected
|
||||
*/
|
||||
public function testFilterArguments(): void
|
||||
{
|
||||
$arguments = ['key1'];
|
||||
$expected = ['key1'];
|
||||
|
||||
$command = $this->getCommand();
|
||||
$command->setArguments($arguments);
|
||||
$this->assertSame($expected, $command->getArguments());
|
||||
}
|
||||
|
||||
/**
|
||||
* @group connected
|
||||
* @requires PHP >= 8.1
|
||||
* @requiresRedisVersion >= 8.3.224
|
||||
*/
|
||||
public function testReturnsDigestOfGivenValue(): void
|
||||
{
|
||||
$redis = $this->getClient();
|
||||
|
||||
$this->assertEquals('OK', $redis->set('foo', 'bar'));
|
||||
$this->assertEquals(CommandUtility::xxh3Hash('bar'), $redis->digest('foo'));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
<?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\Command\Redis;
|
||||
|
||||
use ValueError;
|
||||
|
||||
class MSETEX_Test extends PredisCommandTestCase
|
||||
{
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
protected function getExpectedCommand(): string
|
||||
{
|
||||
return MSETEX::class;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
protected function getExpectedId(): string
|
||||
{
|
||||
return 'MSETEX';
|
||||
}
|
||||
|
||||
/**
|
||||
* @group disconnected
|
||||
* @dataProvider argumentsProvider
|
||||
*/
|
||||
public function testFilterArguments(array $actualArguments, array $expectedArguments): void
|
||||
{
|
||||
$command = $this->getCommand();
|
||||
$command->setArguments($actualArguments);
|
||||
|
||||
$this->assertSame($expectedArguments, $command->getArguments());
|
||||
}
|
||||
|
||||
/**
|
||||
* @group connected
|
||||
* @return void
|
||||
* @requiresRedisVersion >= 8.3.224
|
||||
*/
|
||||
public function testSetWithModifiers()
|
||||
{
|
||||
$redis = $this->getClient();
|
||||
|
||||
$this->assertEquals(1, $redis->msetex(['foo' => 'bar', 'bar' => 'baz'], 'nx'));
|
||||
$this->assertEquals(0, $redis->msetex(['foo' => 'bar', 'bar' => 'baz'], 'nx'));
|
||||
$this->assertEquals(1, $redis->msetex(['foo' => 'baz', 'bar' => 'bar'], 'xx'));
|
||||
$this->assertEquals(0, $redis->msetex(['foo' => 'baz', 'baz' => 'bar'], 'xx'));
|
||||
|
||||
$this->assertEquals(1, $redis->msetex(['foo' => 'baz', 'bar' => 'bar'], null, 'ex', 10));
|
||||
$this->assertEquals(1, $redis->msetex(['foo' => 'baz', 'bar' => 'bar'], null, 'px', 1000));
|
||||
$this->assertEquals(1, $redis->msetex(['foo' => 'baz', 'bar' => 'bar'], null, 'exat', time() + 10));
|
||||
$this->assertEquals(1, $redis->msetex(['foo' => 'baz', 'bar' => 'bar'], null, 'pxat', (time() * 1000) + 1000));
|
||||
$this->assertEquals(1, $redis->msetex(['foo' => 'baz', 'bar' => 'bar'], null, 'keepttl'));
|
||||
|
||||
$this->assertGreaterThan(0, $redis->expiretime('foo'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @group disconnected
|
||||
* @return void
|
||||
*/
|
||||
public function testThrowsExceptionOnInvalidArguments(): void
|
||||
{
|
||||
$command = $this->getCommand();
|
||||
|
||||
$this->expectException(ValueError::class);
|
||||
$this->expectExceptionMessage('Incorrect exist modifier. Should be one of: NX, XX.');
|
||||
|
||||
$command->setArguments([['key' => 'value'], 'wrong']);
|
||||
|
||||
$this->expectException(ValueError::class);
|
||||
$this->expectExceptionMessage('TTL should be specified along with expire resolution parameter');
|
||||
|
||||
$command->setArguments([['key' => 'value'], null, 'EX']);
|
||||
|
||||
$this->expectException(ValueError::class);
|
||||
$this->expectExceptionMessage('Incorrect expire modifier. Should be one of: EX, PX, EXAT, PXAT, KEEPTTL');
|
||||
|
||||
$command->setArguments([['key' => 'value'], null, 'wrong', 10]);
|
||||
}
|
||||
|
||||
public function argumentsProvider(): array
|
||||
{
|
||||
return [
|
||||
'with default arguments' => [
|
||||
[['field1' => 'value1', 'field2' => 'value2']],
|
||||
[2, 'field1', 'value1', 'field2', 'value2'],
|
||||
],
|
||||
'with exist modifier - NX' => [
|
||||
[['field1' => 'value1', 'field2' => 'value2'], 'nx'],
|
||||
[2, 'field1', 'value1', 'field2', 'value2', 'NX'],
|
||||
],
|
||||
'with exist modifier - XX' => [
|
||||
[['field1' => 'value1', 'field2' => 'value2'], 'xx'],
|
||||
[2, 'field1', 'value1', 'field2', 'value2', 'XX'],
|
||||
],
|
||||
'with expire modifier - EX' => [
|
||||
[['field1' => 'value1', 'field2' => 'value2'], null, 'EX', 10],
|
||||
[2, 'field1', 'value1', 'field2', 'value2', 'EX', 10],
|
||||
],
|
||||
'with expire modifier - PX' => [
|
||||
[['field1' => 'value1', 'field2' => 'value2'], null, 'PX', 10],
|
||||
[2, 'field1', 'value1', 'field2', 'value2', 'PX', 10],
|
||||
],
|
||||
'with expire modifier - EXAT' => [
|
||||
[['field1' => 'value1', 'field2' => 'value2'], null, 'EXAT', 10],
|
||||
[2, 'field1', 'value1', 'field2', 'value2', 'EXAT', 10],
|
||||
],
|
||||
'with expire modifier - PXAT' => [
|
||||
[['field1' => 'value1', 'field2' => 'value2'], null, 'PXAT', 10],
|
||||
[2, 'field1', 'value1', 'field2', 'value2', 'PXAT', 10],
|
||||
],
|
||||
'with expire modifier - KEETTL' => [
|
||||
[['field1' => 'value1', 'field2' => 'value2'], null, 'KEEPTTL'],
|
||||
[2, 'field1', 'value1', 'field2', 'value2', 'KEEPTTL'],
|
||||
],
|
||||
'with all modifiers' => [
|
||||
[['field1' => 'value1', 'field2' => 'value2'], 'xx', 'KEEPTTL'],
|
||||
[2, 'field1', 'value1', 'field2', 'value2', 'XX', 'KEEPTTL'],
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -13,6 +13,7 @@
|
||||
namespace Predis\Command\Redis;
|
||||
|
||||
use Predis\Command\PrefixableCommand;
|
||||
use Predis\Command\Redis\Utils\CommandUtility;
|
||||
|
||||
/**
|
||||
* @group commands
|
||||
@@ -256,6 +257,50 @@ class SET_Test extends PredisCommandTestCase
|
||||
$this->testSetStringValueWithModifierXX();
|
||||
}
|
||||
|
||||
/**
|
||||
* @group connected
|
||||
* @requires PHP >= 8.1
|
||||
* @requiresRedisVersion >= 8.3.224
|
||||
*/
|
||||
public function testSetStringValueWithNewModifiers(): void
|
||||
{
|
||||
$redis = $this->getClient();
|
||||
$this->assertEquals(
|
||||
'OK',
|
||||
$redis->set('foo', 'bar')
|
||||
);
|
||||
|
||||
$value = $redis->get('foo');
|
||||
$this->assertEquals(
|
||||
'OK',
|
||||
$redis->set('foo', $value, null, null, 'IFEQ', $value)
|
||||
);
|
||||
$this->assertNull(
|
||||
$redis->set('foo', $value, null, null, 'IFEQ', 'wrong')
|
||||
);
|
||||
$this->assertEquals(
|
||||
'OK',
|
||||
$redis->set('foo', $value, null, null, 'IFNE', 'not equal')
|
||||
);
|
||||
$this->assertNull(
|
||||
$redis->set('foo', $value, null, null, 'IFNE', $value)
|
||||
);
|
||||
$this->assertEquals(
|
||||
'OK',
|
||||
$redis->set('foo', $value, null, null, 'IFDEQ', CommandUtility::xxh3Hash($value))
|
||||
);
|
||||
$this->assertNull(
|
||||
$redis->set('foo', $value, null, null, 'IFDEQ', CommandUtility::xxh3Hash('wrong'))
|
||||
);
|
||||
$this->assertEquals(
|
||||
'OK',
|
||||
$redis->set('foo', $value, null, null, 'IFDNE', CommandUtility::xxh3Hash('not equal'))
|
||||
);
|
||||
$this->assertNull(
|
||||
$redis->set('foo', $value, null, null, 'IFDNE', CommandUtility::xxh3Hash($value))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @group connected
|
||||
* @group cluster
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -66,8 +66,6 @@ class VEMB_Test extends PredisCommandTestCase
|
||||
/**
|
||||
* @dataProvider quantisationProvider
|
||||
* @group connected
|
||||
* @group relay-incompatible
|
||||
* @group relay-fixme
|
||||
* @return void
|
||||
* @requiresRedisVersion >= 8.0.0
|
||||
*/
|
||||
@@ -100,8 +98,6 @@ class VEMB_Test extends PredisCommandTestCase
|
||||
/**
|
||||
* @dataProvider quantisationProvider
|
||||
* @group connected
|
||||
* @group relay-incompatible
|
||||
* @group relay-fixme
|
||||
* @return void
|
||||
* @requiresRedisVersion >= 8.0.0
|
||||
*/
|
||||
|
||||
@@ -62,8 +62,6 @@ class VGETATTR_Test extends PredisCommandTestCase
|
||||
|
||||
/**
|
||||
* @group connected
|
||||
* @group relay-incompatible
|
||||
* @group relay-fixme
|
||||
* @return void
|
||||
* @requiresRedisVersion >= 8.0.0
|
||||
*/
|
||||
@@ -88,8 +86,6 @@ class VGETATTR_Test extends PredisCommandTestCase
|
||||
|
||||
/**
|
||||
* @group connected
|
||||
* @group relay-incompatible
|
||||
* @group relay-fixme
|
||||
* @return void
|
||||
* @requiresRedisVersion >= 8.0.0
|
||||
*/
|
||||
|
||||
@@ -62,8 +62,6 @@ class VLINKS_Test extends PredisCommandTestCase
|
||||
|
||||
/**
|
||||
* @group connected
|
||||
* @group relay-incompatible
|
||||
* @group relay-fixme
|
||||
* @return void
|
||||
* @requiresRedisVersion >= 8.0.0
|
||||
*/
|
||||
@@ -96,8 +94,6 @@ class VLINKS_Test extends PredisCommandTestCase
|
||||
|
||||
/**
|
||||
* @group connected
|
||||
* @group relay-incompatible
|
||||
* @group relay-fixme
|
||||
* @return void
|
||||
* @requiresRedisVersion >= 8.0.0
|
||||
*/
|
||||
|
||||
@@ -54,8 +54,6 @@ class VREM_Test extends PredisCommandTestCase
|
||||
|
||||
/**
|
||||
* @group connected
|
||||
* @group relay-incompatible
|
||||
* @group relay-fixme
|
||||
* @return void
|
||||
* @requiresRedisVersion >= 8.0.0
|
||||
*/
|
||||
@@ -80,8 +78,6 @@ class VREM_Test extends PredisCommandTestCase
|
||||
|
||||
/**
|
||||
* @group connected
|
||||
* @group relay-incompatible
|
||||
* @group relay-fixme
|
||||
* @return void
|
||||
* @requiresRedisVersion >= 8.0.0
|
||||
*/
|
||||
|
||||
@@ -59,8 +59,6 @@ class VSETATTR_Test extends PredisCommandTestCase
|
||||
|
||||
/**
|
||||
* @group connected
|
||||
* @group relay-incompatible
|
||||
* @group relay-fixme
|
||||
* @return void
|
||||
* @requiresRedisVersion >= 8.0.0
|
||||
*/
|
||||
@@ -80,8 +78,6 @@ class VSETATTR_Test extends PredisCommandTestCase
|
||||
|
||||
/**
|
||||
* @group connected
|
||||
* @group relay-incompatible
|
||||
* @group relay-fixme
|
||||
* @return void
|
||||
* @requiresRedisVersion >= 8.0.0
|
||||
*/
|
||||
|
||||
@@ -134,8 +134,6 @@ class VSIM_Test extends PredisCommandTestCase
|
||||
|
||||
/**
|
||||
* @group connected
|
||||
* @group relay-incompatible
|
||||
* @group relay-fixme
|
||||
* @return void
|
||||
* @requiresRedisVersion >= 8.0.0
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,291 @@
|
||||
<?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\Command\Redis;
|
||||
|
||||
class XREADGROUP_CLAIM_Test extends PredisCommandTestCase
|
||||
{
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
protected function getExpectedCommand(): string
|
||||
{
|
||||
return XREADGROUP_CLAIM::class;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
protected function getExpectedId(): string
|
||||
{
|
||||
return 'XREADGROUP';
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider argumentsProvider
|
||||
* @group disconnected
|
||||
*/
|
||||
public function testFilterArguments(array $actualArguments, array $expectedArguments): void
|
||||
{
|
||||
$command = $this->getCommand();
|
||||
$command->setArguments($actualArguments);
|
||||
|
||||
$this->assertSame($expectedArguments, $command->getArguments());
|
||||
}
|
||||
|
||||
/**
|
||||
* @group connected
|
||||
* @return void
|
||||
* @requiresRedisVersion >= 5.0.0
|
||||
*/
|
||||
public function testReadsFromGivenConsumerGroup(): void
|
||||
{
|
||||
$redis = $this->getClient();
|
||||
|
||||
$streamInitId = $redis->xadd('stream', ['field' => 'value']);
|
||||
$this->assertEquals('OK', $redis->xgroup->create('stream', 'group', $streamInitId));
|
||||
|
||||
$nextId = $redis->xadd('stream', ['newField' => 'newValue']);
|
||||
$expectedResponse = [
|
||||
[
|
||||
'stream',
|
||||
[
|
||||
[$nextId, ['newField', 'newValue']],
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
$this->assertSame(
|
||||
$expectedResponse,
|
||||
$redis->xreadgroup_claim(
|
||||
'group',
|
||||
'consumer',
|
||||
['stream' => '>']
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @group connected
|
||||
* @return void
|
||||
* @requiresRedisVersion >= 5.0.0
|
||||
*/
|
||||
public function testReadsFromConsumerGroupFromMultipleStreams(): void
|
||||
{
|
||||
$redis = $this->getClient();
|
||||
|
||||
$streamInitId = $redis->xadd('stream', ['field' => 'value']);
|
||||
$this->assertEquals('OK', $redis->xgroup->create('stream', 'group', $streamInitId));
|
||||
|
||||
$anotherStreamInitId = $redis->xadd('another_stream', ['field' => 'value']);
|
||||
$this->assertEquals('OK', $redis->xgroup->create('another_stream', 'group', $anotherStreamInitId));
|
||||
|
||||
$nextId = $redis->xadd('stream', ['newField' => 'newValue']);
|
||||
$anotherNextId = $redis->xadd('another_stream', ['newField' => 'newValue']);
|
||||
|
||||
$expectedResponse = [
|
||||
[
|
||||
'stream',
|
||||
[
|
||||
[$nextId, ['newField', 'newValue']],
|
||||
],
|
||||
],
|
||||
[
|
||||
'another_stream',
|
||||
[
|
||||
[$anotherNextId, ['newField', 'newValue']],
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
$this->assertSame(
|
||||
$expectedResponse,
|
||||
$redis->xreadgroup_claim(
|
||||
'group',
|
||||
'consumer',
|
||||
['stream' => '>', 'another_stream' => '>']
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @group connected
|
||||
* @return void
|
||||
* @requiresRedisVersion >= 5.0.0
|
||||
*/
|
||||
public function testPrefixStreamKeys(): void
|
||||
{
|
||||
$redis = $this->createClient(null, ['prefix' => 'prefix:']);
|
||||
|
||||
$streamInitId = $redis->xadd('stream', ['field' => 'value']);
|
||||
$this->assertEquals('OK', $redis->xgroup->create('stream', 'group', $streamInitId));
|
||||
|
||||
$anotherStreamInitId = $redis->xadd('another_stream', ['field' => 'value']);
|
||||
$this->assertEquals('OK', $redis->xgroup->create('another_stream', 'group', $anotherStreamInitId));
|
||||
|
||||
$nextId = $redis->xadd('stream', ['newField' => 'newValue']);
|
||||
$anotherNextId = $redis->xadd('another_stream', ['newField' => 'newValue']);
|
||||
|
||||
$expectedResponse = [
|
||||
[
|
||||
'prefix:stream',
|
||||
[
|
||||
[$nextId, ['newField', 'newValue']],
|
||||
],
|
||||
],
|
||||
[
|
||||
'prefix:another_stream',
|
||||
[
|
||||
[$anotherNextId, ['newField', 'newValue']],
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
$this->assertSame(
|
||||
$expectedResponse,
|
||||
$redis->xreadgroup_claim(
|
||||
'group',
|
||||
'consumer',
|
||||
['stream' => '>', 'another_stream' => '>']
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @group connected
|
||||
* @return void
|
||||
* @requiresRedisVersion >= 8.3.224
|
||||
*/
|
||||
public function testReadsAndClaimFromConsumerGroupFromSingleStream(): void
|
||||
{
|
||||
$redis = $this->getClient();
|
||||
|
||||
$streamInitId = $redis->xadd('stream', ['field' => 'value']);
|
||||
$this->assertEquals('OK', $redis->xgroup->create('stream', 'group', $streamInitId));
|
||||
|
||||
$nextId = $redis->xadd('stream', ['newField' => 'newValue']);
|
||||
|
||||
$expectedResponse = [
|
||||
[
|
||||
'stream',
|
||||
[
|
||||
[$nextId, ['newField', 'newValue'], '0', '0'],
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
$this->assertEmpty(
|
||||
$redis->xpending('stream', 'group', null, '-', '+', 5)
|
||||
);
|
||||
|
||||
$this->assertEquals(
|
||||
$expectedResponse,
|
||||
$redis->xreadgroup_claim(
|
||||
'group',
|
||||
'consumer',
|
||||
['stream' => '>'], null, null, false, 10)
|
||||
);
|
||||
|
||||
$this->assertCount(
|
||||
4,
|
||||
$redis->xpending('stream', 'group', null, '-', '+', 5)[0]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @group connected
|
||||
* @return void
|
||||
* @requiresRedisVersion >= 8.3.224
|
||||
*/
|
||||
public function testReadsAndClaimFromConsumerGroupFromMultipleStreams(): void
|
||||
{
|
||||
$redis = $this->getClient();
|
||||
|
||||
$streamInitId = $redis->xadd('stream', ['field' => 'value']);
|
||||
$this->assertEquals('OK', $redis->xgroup->create('stream', 'group', $streamInitId));
|
||||
|
||||
$anotherStreamInitId = $redis->xadd('another_stream', ['field' => 'value']);
|
||||
$this->assertEquals('OK', $redis->xgroup->create('another_stream', 'group', $anotherStreamInitId));
|
||||
|
||||
$nextId = $redis->xadd('stream', ['newField' => 'newValue']);
|
||||
$anotherNextId = $redis->xadd('another_stream', ['newField' => 'newValue']);
|
||||
|
||||
$expectedResponse = [
|
||||
[
|
||||
'stream',
|
||||
[
|
||||
[$nextId, ['newField', 'newValue'], '0', '0'],
|
||||
],
|
||||
],
|
||||
[
|
||||
'another_stream',
|
||||
[
|
||||
[$anotherNextId, ['newField', 'newValue'], '0', '0'],
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
$this->assertEmpty(
|
||||
$redis->xpending('stream', 'group', null, '-', '+', 5)
|
||||
);
|
||||
$this->assertEmpty(
|
||||
$redis->xpending('another_stream', 'group', null, '-', '+', 5)
|
||||
);
|
||||
|
||||
$this->assertEquals(
|
||||
$expectedResponse,
|
||||
$redis->xreadgroup_claim(
|
||||
'group',
|
||||
'consumer',
|
||||
['stream' => '>', 'another_stream' => '>'], null, null, false, 10)
|
||||
);
|
||||
|
||||
$this->assertCount(
|
||||
4,
|
||||
$redis->xpending('stream', 'group', null, '-', '+', 5)[0]
|
||||
);
|
||||
$this->assertCount(
|
||||
4,
|
||||
$redis->xpending('another_stream', 'group', null, '-', '+', 5)[0]
|
||||
);
|
||||
}
|
||||
|
||||
public function argumentsProvider(): array
|
||||
{
|
||||
return [
|
||||
'with default arguments' => [
|
||||
['group', 'consumer', ['stream' => '0-0']],
|
||||
['GROUP', 'group', 'consumer', 'STREAMS', 'stream', '0-0'],
|
||||
],
|
||||
'with COUNT modifier' => [
|
||||
['group', 'consumer', ['stream' => '0-0'], 10],
|
||||
['GROUP', 'group', 'consumer', 'COUNT', 10, 'STREAMS', 'stream', '0-0'],
|
||||
],
|
||||
'with BLOCK modifier' => [
|
||||
['group', 'consumer', ['stream' => '0-0'], null, 10],
|
||||
['GROUP', 'group', 'consumer', 'BLOCK', 10, 'STREAMS', 'stream', '0-0'],
|
||||
],
|
||||
'with NOACK modifier' => [
|
||||
['group', 'consumer', ['stream' => '0-0'], null, null, true],
|
||||
['GROUP', 'group', 'consumer', 'NOACK', 'STREAMS', 'stream', '0-0'],
|
||||
],
|
||||
'with CLAIM modifier' => [
|
||||
['group', 'consumer', ['stream' => '0-0'], null, null, false, 10],
|
||||
['GROUP', 'group', 'consumer', 'CLAIM', 10, 'STREAMS', 'stream', '0-0'],
|
||||
],
|
||||
'with all arguments' => [
|
||||
['group', 'consumer', ['stream' => '0-0', 'stream1' => '0-0'], 10, 10, true, 20],
|
||||
['GROUP', 'group', 'consumer', 'COUNT', 10, 'BLOCK', 10, 'NOACK', 'CLAIM', 20, 'STREAMS', 'stream', 'stream1', '0-0', '0-0'],
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -54,8 +54,6 @@ class XREADGROUP_Test extends PredisCommandTestCase
|
||||
|
||||
/**
|
||||
* @group connected
|
||||
* @group relay-incompatible
|
||||
* @group relay-fixme
|
||||
* @return void
|
||||
* @requiresRedisVersion >= 5.0.0
|
||||
*/
|
||||
@@ -138,6 +136,55 @@ class XREADGROUP_Test extends PredisCommandTestCase
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @group connected
|
||||
* @return void
|
||||
* @requiresRedisVersion >= 5.0.0
|
||||
*/
|
||||
public function testPrefixStreamKeys(): void
|
||||
{
|
||||
$redis = $this->createClient(null, ['prefix' => 'prefix:']);
|
||||
|
||||
$streamInitId = $redis->xadd('stream', ['field' => 'value']);
|
||||
$this->assertEquals('OK', $redis->xgroup->create('stream', 'group', $streamInitId));
|
||||
|
||||
$anotherStreamInitId = $redis->xadd('another_stream', ['field' => 'value']);
|
||||
$this->assertEquals('OK', $redis->xgroup->create('another_stream', 'group', $anotherStreamInitId));
|
||||
|
||||
$nextId = $redis->xadd('stream', ['newField' => 'newValue']);
|
||||
$anotherNextId = $redis->xadd('another_stream', ['newField' => 'newValue']);
|
||||
|
||||
$expectedResponse = [
|
||||
[
|
||||
'prefix:stream',
|
||||
[
|
||||
[$nextId, ['newField', 'newValue']],
|
||||
],
|
||||
],
|
||||
[
|
||||
'prefix:another_stream',
|
||||
[
|
||||
[$anotherNextId, ['newField', 'newValue']],
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
$this->assertSame(
|
||||
$expectedResponse,
|
||||
$redis->xreadgroup(
|
||||
'group',
|
||||
'consumer',
|
||||
null,
|
||||
null,
|
||||
false,
|
||||
'stream',
|
||||
'another_stream',
|
||||
'>',
|
||||
'>'
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @group connected
|
||||
* @return void
|
||||
|
||||
@@ -14,11 +14,13 @@ namespace Predis\Command\Utils;
|
||||
|
||||
use Predis\Command\Redis\Utils\CommandUtility;
|
||||
use PredisTestCase;
|
||||
use RuntimeException;
|
||||
use UnexpectedValueException;
|
||||
|
||||
class CommandUtilityTest extends PredisTestCase
|
||||
{
|
||||
/**
|
||||
* @group disconnected
|
||||
* @dataProvider arrayProvider
|
||||
* @param array $actual
|
||||
* @param array $expected
|
||||
@@ -32,6 +34,7 @@ class CommandUtilityTest extends PredisTestCase
|
||||
}
|
||||
|
||||
/**
|
||||
* @group disconnected
|
||||
* @return void
|
||||
*/
|
||||
public function testArrayToDictionaryThrowsExceptionOnOddNumberOfElements()
|
||||
@@ -42,6 +45,49 @@ class CommandUtilityTest extends PredisTestCase
|
||||
CommandUtility::arrayToDictionary(['key1', 'value1', 'key1']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @group disconnected
|
||||
* @requires PHP >= 8.1
|
||||
* @return void
|
||||
*/
|
||||
public function testXXH3Hash()
|
||||
{
|
||||
$expectedHash = '87d57e269b9df0f0';
|
||||
|
||||
$this->assertEquals($expectedHash, CommandUtility::xxh3Hash('value'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @group disconnected
|
||||
* @requires PHP < 8.1
|
||||
* @return void
|
||||
*/
|
||||
public function testXXH3HashRaiseExceptionOnPHPLowerThan81()
|
||||
{
|
||||
$this->expectException(RuntimeException::class);
|
||||
$this->expectExceptionMessage('XXH3 algorithm is not supported. Please install PECL xxhash extension.');
|
||||
|
||||
CommandUtility::xxh3Hash('value');
|
||||
}
|
||||
|
||||
/**
|
||||
* @group disconnected
|
||||
* @return void
|
||||
*/
|
||||
public function testDictionaryToArray(): void
|
||||
{
|
||||
$dict = [
|
||||
'key1' => 'value1',
|
||||
'key2' => 'value2',
|
||||
'key3' => 'value3',
|
||||
];
|
||||
|
||||
$this->assertSame(
|
||||
['key1', 'value1', 'key2', 'value2', 'key3', 'value3'],
|
||||
CommandUtility::dictionaryToArray($dict)
|
||||
);
|
||||
}
|
||||
|
||||
public function arrayProvider(): array
|
||||
{
|
||||
return [
|
||||
|
||||
@@ -472,7 +472,9 @@ class RelayConnectionTest extends PredisTestCase
|
||||
*/
|
||||
public function testExecutesCommand(): void
|
||||
{
|
||||
$connection = new RelayConnection(new Parameters(), new Relay());
|
||||
$parameters = $this->getParameters();
|
||||
$connection = new RelayConnection($parameters, new Relay());
|
||||
$connection->executeCommand(new RawCommand('AUTH', [$parameters->password]));
|
||||
|
||||
$this->assertEquals(
|
||||
'OK',
|
||||
@@ -491,7 +493,9 @@ class RelayConnectionTest extends PredisTestCase
|
||||
*/
|
||||
public function testConnectWithOnConnectionCommands(): void
|
||||
{
|
||||
$connection = new RelayConnection(new Parameters(), new Relay());
|
||||
$parameters = $this->getParameters();
|
||||
$connection = new RelayConnection($parameters, new Relay());
|
||||
$connection->addConnectCommand(new RawCommand('AUTH', [$parameters->password]));
|
||||
$connection->addConnectCommand(new RawCommand('CLIENT', ['SETNAME', 'predis']));
|
||||
|
||||
$connection->connect();
|
||||
|
||||
Reference in New Issue
Block a user