Extended RediSearch support by implementing FT.SUGGET command (#1185)

* Added support for new arguments for BITPOS, BITCOUNT commands (#1045)

* Added support for new arguments for EXPIRE, EXPIREAT commands (#1046)

* Extended core support by implementing SORT_RO command (#1044)

* Added support for SORT_RO command

* Codestyle fixes

* Added command description

---------

Co-authored-by: Vladyslav Vildanov <vladyslavvildanov@Vladyslav-Vildanov-MacBook-Pro.local>

* fix deprecated call

* Added support for container commands (#1049)

* Added support for container commands FUNCTION LOAD, FUNCTION DELETE and FCALL

* Changed ContainerInterface and AbstractContainer

* Re-implement logic of abstract methods

---------

Co-authored-by: Vladyslav Vildanov <vladyslavvildanov@Vladyslav-Vildanov-MacBook-Pro.local>

* Added stream commands to KeyPrefixProcessor (#1051)

Co-authored-by: Vladyslav Vildanov <vladyslavvildanov@Vladyslav-Vildanov-MacBook-Pro.local>

* Fix return type of ReplicationInterface::getSlaves (#1111)

* Codestyle fixes

* Changed return annotation

* Added support for FT.SUGGET command, added examples for suggestion API usage

* Include SUGGET command files to codespell ignore

* Added file extension

---------

Co-authored-by: Vladyslav Vildanov <vladyslavvildanov@Vladyslav-Vildanov-MacBook-Pro.local>
Co-authored-by: Till Krüss <till@kruss.io>
Co-authored-by: Stephan <glaubinix@users.noreply.github.com>
This commit is contained in:
Vladyslav Vildanov
2023-03-01 12:57:01 +02:00
committed by GitHub
parent 4b63d39c55
commit 5a9f62ef78
12 changed files with 383 additions and 45 deletions
+1 -1
View File
@@ -1,5 +1,5 @@
[codespell]
skip=./.git
skip=./.git,./src/Command/Redis/Search/FTSUGGET.php,./examples/Commands/Search/ft_sug_add_get_del.php
check-hidden=
check-filenames=
builtin=clear,rare,informal,usage,code,names
@@ -0,0 +1,37 @@
<?php
/*
* This file is part of the Predis package.
*
* (c) 2009-2020 Daniele Alessandri
* (c) 2021-2023 Till Krüss
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
use Predis\Client;
use Predis\Command\Argument\Search\SugAddArguments;
use Predis\Command\Argument\Search\SugGetArguments;
require __DIR__ . '/../../shared.php';
// Example of FT.SUGADD, FT.SUGGET, FT.SUGDEL commands usage:
// 1. Add suggestion to key with payload
$client = new Client();
$client->ftsugadd('key', 'hello', 2, (new SugAddArguments())->payload('payload'));
// 2. Perform fuzzy search by prefix to get previous suggestion with payload
$response = $client->ftsugget('key', 'hellp', (new SugGetArguments())->fuzzy()->withPayloads());
echo 'Suggestion for "hellp" prefix:' . "\n";
print_r($response);
// 3. Removes previous suggestion from key
$client->ftsugdel('key', 'hello');
$response = $client->ftsugget('key', 'hello');
echo 'Suggestions, after removing "hello" suggestion:' . "\n";
print_r($response);
+2
View File
@@ -21,6 +21,7 @@ use Predis\Command\Argument\Search\ProfileArguments;
use Predis\Command\Argument\Search\Schema;
use Predis\Command\Argument\Search\SearchArguments;
use Predis\Command\Argument\Search\SugAddArguments;
use Predis\Command\Argument\Search\SugGetArguments;
use Predis\Command\Argument\Search\SynUpdateArguments;
use Predis\Command\Argument\Server\LimitOffsetCount;
use Predis\Command\Argument\Server\To;
@@ -109,6 +110,7 @@ use Predis\Command\Container\Search\FTCONFIG;
* @method $this ftspellcheck(string $index, string $query, ?SearchArguments $arguments = null)
* @method $this ftsugadd(string $key, string $string, float $score, ?SugAddArguments $arguments = null)
* @method $this ftsugdel(string $key, string $string)
* @method $this ftsugget(string $key, string $prefix, ?SugGetArguments $arguments = null)
* @method $this ftsyndump(string $index)
* @method $this ftsynupdate(string $index, string $synonymGroupId, ?SynUpdateArguments $arguments = null, string ...$terms)
* @method $this fttagvals(string $index, string $fieldName)
+2
View File
@@ -21,6 +21,7 @@ use Predis\Command\Argument\Search\ProfileArguments;
use Predis\Command\Argument\Search\Schema;
use Predis\Command\Argument\Search\SearchArguments;
use Predis\Command\Argument\Search\SugAddArguments;
use Predis\Command\Argument\Search\SugGetArguments;
use Predis\Command\Argument\Search\SynUpdateArguments;
use Predis\Command\Argument\Server\LimitOffsetCount;
use Predis\Command\Argument\Server\To;
@@ -118,6 +119,7 @@ use Predis\Response\Status;
* @method array ftspellcheck(string $index, string $query, ?SearchArguments $arguments = null)
* @method int ftsugadd(string $key, string $string, float $score, ?SugAddArguments $arguments = null)
* @method int ftsugdel(string $key, string $string)
* @method array ftsugget(string $key, string $prefix, ?SugGetArguments $arguments = null)
* @method array ftsyndump(string $index)
* @method Status ftsynupdate(string $index, string $synonymGroupId, ?SynUpdateArguments $arguments = null, string ...$terms)
* @method array fttagvals(string $index, string $fieldName)
@@ -77,6 +77,30 @@ class CommonArguments implements ArrayableArgument
return $this;
}
/**
* Also returns the relative internal score of each document.
*
* @return $this
*/
public function withScores(): self
{
$this->arguments[] = 'WITHSCORES';
return $this;
}
/**
* Retrieves optional document payloads.
*
* @return $this
*/
public function withPayloads(): self
{
$this->arguments[] = 'WITHPAYLOADS';
return $this;
}
/**
* {@inheritDoc}
*/
@@ -48,30 +48,6 @@ class SearchArguments extends CommonArguments
return $this;
}
/**
* Also returns the relative internal score of each document.
*
* @return $this
*/
public function withScores(): self
{
$this->arguments[] = 'WITHSCORES';
return $this;
}
/**
* Retrieves optional document payloads.
*
* @return $this
*/
public function withPayloads(): self
{
$this->arguments[] = 'WITHPAYLOADS';
return $this;
}
/**
* Returns the value of the sorting key, right after the id and score and/or payload, if requested.
*
@@ -0,0 +1,41 @@
<?php
/*
* This file is part of the Predis package.
*
* (c) 2009-2020 Daniele Alessandri
* (c) 2021-2023 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;
class SugGetArguments extends CommonArguments
{
/**
* Performs a fuzzy prefix search, including prefixes at Levenshtein distance of 1 from the prefix sent.
*
* @return $this
*/
public function fuzzy(): self
{
$this->arguments[] = 'FUZZY';
return $this;
}
/**
* Limits the results to a maximum of num (default: 5).
*
* @param int $num
* @return $this
*/
public function max(int $num): self
{
array_push($this->arguments, 'MAX', $num);
return $this;
}
}
+39
View File
@@ -0,0 +1,39 @@
<?php
/*
* This file is part of the Predis package.
*
* (c) 2009-2020 Daniele Alessandri
* (c) 2021-2023 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\Command as RedisCommand;
/**
* @see https://redis.io/commands/ft.sugget/
*
* Get completion suggestions for a prefix.
*/
class FTSUGGET extends RedisCommand
{
public function getId()
{
return 'FT.SUGGET';
}
public function setArguments(array $arguments)
{
[$key, $prefix] = $arguments;
$commandArguments = (!empty($arguments[2])) ? $arguments[2]->toArray() : [];
parent::setArguments(array_merge(
[$key, $prefix],
$commandArguments
));
}
}
@@ -65,4 +65,24 @@ class CommonArgumentsTest extends TestCase
$this->assertSame(['SKIPINITIALSCAN'], $this->arguments->toArray());
}
/**
* @return void
*/
public function testCreatesArgumentsWithWithScoresModifier(): void
{
$this->arguments->withScores();
$this->assertSame(['WITHSCORES'], $this->arguments->toArray());
}
/**
* @return void
*/
public function testCreatesArgumentsWithWithPayloadsModifier(): void
{
$this->arguments->withPayloads();
$this->assertSame(['WITHPAYLOADS'], $this->arguments->toArray());
}
}
@@ -47,26 +47,6 @@ class SearchArgumentsTest extends TestCase
$this->assertSame(['VERBATIM'], $this->arguments->toArray());
}
/**
* @return void
*/
public function testCreatesArgumentsWithWithScoresModifier(): void
{
$this->arguments->withScores();
$this->assertSame(['WITHSCORES'], $this->arguments->toArray());
}
/**
* @return void
*/
public function testCreatesArgumentsWithWithPayloadsModifier(): void
{
$this->arguments->withPayloads();
$this->assertSame(['WITHPAYLOADS'], $this->arguments->toArray());
}
/**
* @return void
*/
@@ -0,0 +1,48 @@
<?php
/*
* This file is part of the Predis package.
*
* (c) 2009-2020 Daniele Alessandri
* (c) 2021-2023 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;
use PHPUnit\Framework\TestCase;
class SugGetArgumentsTest extends TestCase
{
/**
* @var SugGetArguments
*/
private $arguments;
protected function setUp(): void
{
$this->arguments = new SugGetArguments();
}
/**
* @return void
*/
public function testCreatesArgumentsWithFuzzyModifier(): void
{
$this->arguments->fuzzy();
$this->assertSame(['FUZZY'], $this->arguments->toArray());
}
/**
* @return void
*/
public function testCreatesArgumentsWithMaxModifier(): void
{
$this->arguments->max(5);
$this->assertSame(['MAX', 5], $this->arguments->toArray());
}
}
@@ -0,0 +1,169 @@
<?php
/*
* This file is part of the Predis package.
*
* (c) 2009-2020 Daniele Alessandri
* (c) 2021-2023 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\Argument\Search\SugAddArguments;
use Predis\Command\Argument\Search\SugGetArguments;
use Predis\Command\Redis\PredisCommandTestCase;
class FTSUGGET_Test extends PredisCommandTestCase
{
/**
* {@inheritDoc}
*/
protected function getExpectedCommand(): string
{
return FTSUGGET::class;
}
/**
* {@inheritDoc}
*/
protected function getExpectedId(): string
{
return 'FTSUGGET';
}
/**
* @group disconnected
* @dataProvider argumentsProvider
*/
public function testFilterArguments(array $actualArguments, array $expectedArguments): void
{
$command = $this->getCommand();
$command->setArguments($actualArguments);
$this->assertSameValues($expectedArguments, $command->getArguments());
}
/**
* @group disconnected
*/
public function testParseResponse(): void
{
$this->assertSame(1, $this->getCommand()->parseResponse(1));
}
/**
* @group connected
* @dataProvider suggestionProvider
* @param array $addArguments
* @param array $getArguments
* @param array $expectedResponse
* @return void
* @requiresRediSearchVersion >= 1.0.0
*/
public function testGetSuggestionsForGivenPrefix(
array $addArguments,
array $getArguments,
array $expectedResponse
): void {
$redis = $this->getClient();
$redis->ftsugadd(...$addArguments);
$actualResponse = $redis->ftsugget(...$getArguments);
$this->assertSame($expectedResponse, $actualResponse);
}
/**
* @group connected
* @return void
* @requiresRediSearchVersion >= 1.0.0
*/
public function testGetReturnsLimitedResultsWithMaxModifier(): void
{
$redis = $this->getClient();
$redis->ftsugadd('key', 'hello', 2);
$redis->ftsugadd('key', 'hell', 2);
$actualResponse = $redis->ftsugget('key', 'hel', (new SugGetArguments())->max(1));
$this->assertSame(['hell'], $actualResponse);
}
/**
* @group connected
* @return void
* @requiresRediSearchVersion >= 1.0.0
*/
public function testGetReturnsNullOnNonExistingKey(): void
{
$redis = $this->getClient();
$this->assertNull($redis->ftsugget('key', 'hel'));
}
public function argumentsProvider(): array
{
return [
'with default arguments' => [
['key', 'prefix'],
['key', 'prefix'],
],
'with FUZZY modifier' => [
['key', 'prefix', (new SugGetArguments())->fuzzy()],
['key', 'prefix', 'FUZZY'],
],
'with WITHSCORES modifier' => [
['key', 'prefix', (new SugGetArguments())->withScores()],
['key', 'prefix', 'WITHSCORES'],
],
'with WITHPAYLOADS modifier' => [
['key', 'prefix', (new SugGetArguments())->withPayloads()],
['key', 'prefix', 'WITHPAYLOADS'],
],
'with MAX modifier' => [
['key', 'prefix', (new SugGetArguments())->max(5)],
['key', 'prefix', 'MAX', 5],
],
'with all arguments' => [
['key', 'prefix', (new SugGetArguments())->fuzzy()->withScores()->withPayloads()->max(5)],
['key', 'prefix', 'FUZZY', 'WITHSCORES', 'WITHPAYLOADS', 'MAX', 5],
],
];
}
public function suggestionProvider(): array
{
return [
'with default arguments' => [
['key', 'hello', 2],
['key', 'hell'],
['hello'],
],
'with FUZZY search' => [
['key', 'hello', 2],
['key', 'help', (new SugGetArguments())->fuzzy()],
['hello'],
],
'with WITHSCORES modifier' => [
['key', 'hello', 2],
['key', 'hell', (new SugGetArguments())->withScores()],
['hello', '1.4142135381698608'],
],
'with WITHPAYLOADS modifier' => [
['key', 'hello', 2, (new SugAddArguments())->payload('payload')],
['key', 'hell', (new SugGetArguments())->withPayloads()],
['hello', 'payload'],
],
'with all modifiers' => [
['key', 'hello', 2, (new SugAddArguments())->payload('payload')],
['key', 'hellp', (new SugGetArguments())->fuzzy()->withScores()->withPayloads()],
['hello', '290630304', 'payload'],
],
];
}
}