Added support for INDEXMISSING and INDEXEMPTY arguments (#1464)

* Added support for INDEXMISSING and INDEXEMPTY arguments

* Added version restriction for test

* Codestyle fixes

* Updated test case to remove DIALECT 5

* Updated another test case

* Added correct versions restriction

---------

Co-authored-by: Till Krüss <tillkruss@users.noreply.github.com>
This commit is contained in:
Vladyslav Vildanov
2024-11-21 20:19:51 +02:00
committed by GitHub
parent 52bdcf0c1f
commit aaec13d074
11 changed files with 190 additions and 10 deletions
@@ -29,6 +29,7 @@ abstract class AbstractField implements FieldInterface
* @param string $alias
* @param bool|string $sortable
* @param bool $noIndex
* @param bool $allowsMissing
* @return void
*/
protected function setCommonOptions(
@@ -36,7 +37,8 @@ abstract class AbstractField implements FieldInterface
string $identifier,
string $alias = '',
$sortable = self::NOT_SORTABLE,
bool $noIndex = false
bool $noIndex = false,
bool $allowsMissing = false
): void {
$this->fieldArguments[] = $identifier;
@@ -57,6 +59,10 @@ abstract class AbstractField implements FieldInterface
if ($noIndex) {
$this->fieldArguments[] = 'NOINDEX';
}
if ($allowsMissing) {
$this->fieldArguments[] = 'INDEXMISSING';
}
}
/**
@@ -19,13 +19,15 @@ class GeoField extends AbstractField
* @param string $alias
* @param bool|string $sortable
* @param bool $noIndex
* @param bool $allowsMissing
*/
public function __construct(
string $identifier,
string $alias = '',
$sortable = self::NOT_SORTABLE,
bool $noIndex = false
bool $noIndex = false,
bool $allowsMissing = false
) {
$this->setCommonOptions('GEO', $identifier, $alias, $sortable, $noIndex);
$this->setCommonOptions('GEO', $identifier, $alias, $sortable, $noIndex, $allowsMissing);
}
}
@@ -19,13 +19,15 @@ class NumericField extends AbstractField
* @param string $alias
* @param bool|string $sortable
* @param bool $noIndex
* @param bool $allowsMissing
*/
public function __construct(
string $identifier,
string $alias = '',
$sortable = self::NOT_SORTABLE,
bool $noIndex = false
bool $noIndex = false,
bool $allowsMissing = false
) {
$this->setCommonOptions('NUMERIC', $identifier, $alias, $sortable, $noIndex);
$this->setCommonOptions('NUMERIC', $identifier, $alias, $sortable, $noIndex, $allowsMissing);
}
}
@@ -21,6 +21,7 @@ class TagField extends AbstractField
* @param bool $noIndex
* @param string $separator
* @param bool $caseSensitive
* @param bool $allowsEmpty
*/
public function __construct(
string $identifier,
@@ -28,9 +29,11 @@ class TagField extends AbstractField
$sortable = self::NOT_SORTABLE,
bool $noIndex = false,
string $separator = ',',
bool $caseSensitive = false
bool $caseSensitive = false,
bool $allowsEmpty = false,
bool $allowsMissing = false
) {
$this->setCommonOptions('TAG', $identifier, $alias, $sortable, $noIndex);
$this->setCommonOptions('TAG', $identifier, $alias, $sortable, $noIndex, $allowsMissing);
if ($separator !== ',') {
$this->fieldArguments[] = 'SEPARATOR';
@@ -40,5 +43,9 @@ class TagField extends AbstractField
if ($caseSensitive) {
$this->fieldArguments[] = 'CASESENSITIVE';
}
if ($allowsEmpty) {
$this->fieldArguments[] = 'INDEXEMPTY';
}
}
}
@@ -23,6 +23,8 @@ class TextField extends AbstractField
* @param string $phonetic
* @param int $weight
* @param bool $withSuffixTrie
* @param bool $allowsEmpty
* @param bool $allowsMissing
*/
public function __construct(
string $identifier,
@@ -32,9 +34,11 @@ class TextField extends AbstractField
bool $noStem = false,
string $phonetic = '',
int $weight = 1,
bool $withSuffixTrie = false
bool $withSuffixTrie = false,
bool $allowsEmpty = false,
bool $allowsMissing = false
) {
$this->setCommonOptions('TEXT', $identifier, $alias, $sortable, $noIndex);
$this->setCommonOptions('TEXT', $identifier, $alias, $sortable, $noIndex, $allowsMissing);
if ($noStem) {
$this->fieldArguments[] = 'NOSTEM';
@@ -53,5 +57,9 @@ class TextField extends AbstractField
if ($withSuffixTrie) {
$this->fieldArguments[] = 'WITHSUFFIXTRIE';
}
if ($allowsEmpty) {
$this->fieldArguments[] = 'INDEXEMPTY';
}
}
}
@@ -52,6 +52,10 @@ class GeoFieldTest extends TestCase
['field_name', '', AbstractField::NOT_SORTABLE, true],
['field_name', 'GEO', 'NOINDEX'],
],
'with INDEXMISSING modifier' => [
['field_name', '', AbstractField::NOT_SORTABLE, false, true],
['field_name', 'GEO', 'INDEXMISSING'],
],
];
}
}
@@ -52,6 +52,10 @@ class NumericFieldTest extends TestCase
['field_name', '', AbstractField::NOT_SORTABLE, true],
['field_name', 'NUMERIC', 'NOINDEX'],
],
'with INDEXMISSING modifier' => [
['field_name', '', AbstractField::NOT_SORTABLE, false, true],
['field_name', 'NUMERIC', 'INDEXMISSING'],
],
];
}
}
@@ -52,6 +52,14 @@ class TagFieldTest extends TestCase
['field_name', '', AbstractField::NOT_SORTABLE, true],
['field_name', 'TAG', 'NOINDEX'],
],
'with INDEXEMPTY modifier' => [
['field_name', '', AbstractField::NOT_SORTABLE, false, ',', false, true],
['field_name', 'TAG', 'INDEXEMPTY'],
],
'with INDEXMISSING modifier' => [
['field_name', '', AbstractField::NOT_SORTABLE, false, ',', false, false, true],
['field_name', 'TAG', 'INDEXMISSING'],
],
];
}
}
@@ -52,6 +52,14 @@ class TextFieldTest extends TestCase
['field_name', '', AbstractField::NOT_SORTABLE, true],
['field_name', 'TEXT', 'NOINDEX'],
],
'with INDEXEMPTY modifier' => [
['field_name', '', AbstractField::NOT_SORTABLE, false, false, '', 1, false, true],
['field_name', 'TEXT', 'INDEXEMPTY'],
],
'with INDEXMISSING modifier' => [
['field_name', '', AbstractField::NOT_SORTABLE, false, false, '', 1, false, false, true],
['field_name', 'TEXT', 'INDEXMISSING'],
],
];
}
}
@@ -13,6 +13,7 @@
namespace Predis\Command\Redis\Search;
use Predis\Command\Argument\Search\CreateArguments;
use Predis\Command\Argument\Search\SchemaFields\GeoField;
use Predis\Command\Argument\Search\SchemaFields\NumericField;
use Predis\Command\Argument\Search\SchemaFields\TagField;
use Predis\Command\Argument\Search\SchemaFields\TextField;
@@ -113,6 +114,43 @@ class FTCREATE_Test extends PredisCommandTestCase
$this->assertEquals('OK', $actualResponse);
}
/**
* @group connected
* @group relay-resp3
* @return void
* @requiresRediSearchVersion >= 2.09.00
*/
public function testCreatesSearchIndexWithMissingAndEmptyFields(): void
{
$redis = $this->getClient();
$schema = [
new TextField(
'text_empty',
'',
false, false, false, '', 1, false, true
),
new TagField('tag_empty',
'', false, false, ',', false, true
),
new NumericField('num_missing', '', false, false, true),
new GeoField('geo_missing', '', false, false, true),
new TextField(
'text_empty_missing',
'',
false,
false, false, '', 1, false, true, true
),
new TagField('tag_empty_missing',
'', false, false, ',', false, true, true
),
];
$actualResponse = $redis->ftcreate('index', $schema);
$this->assertEquals('OK', $actualResponse);
}
public function argumentsProvider(): array
{
return [
@@ -20,6 +20,7 @@ use Predis\Command\Argument\Search\SchemaFields\TagField;
use Predis\Command\Argument\Search\SchemaFields\TextField;
use Predis\Command\Argument\Search\SearchArguments;
use Predis\Command\Redis\PredisCommandTestCase;
use Predis\Response\ServerException;
/**
* @group commands
@@ -134,6 +135,98 @@ class FTSEARCH_Test extends PredisCommandTestCase
* @return void
* @requiresRediSearchVersion >= 2.09.00
*/
public function testSearchHashEmptyValues(): void
{
$redis = $this->getClient();
$hashResponse = $redis->hmset('test:1', ['text_empty' => '']);
$this->assertEquals('OK', $hashResponse);
$schema = [
new TextField(
'text_empty',
'',
false, false, false, '', 1, false, true
),
new TextField(
'text_not_empty',
'',
false, false, false, '', 1, false, false
),
];
$createArgs = new CreateArguments();
$createArgs->prefix(['test:']);
$ftCreateResponse = $redis->ftcreate('idx', $schema, $createArgs);
$this->assertEquals('OK', $ftCreateResponse);
// Timeout to make sure that index created before search performed.
usleep(10000);
$searchArgs = new SearchArguments();
$searchArgs->dialect(4);
$this->assertSame(
[1, 'test:1', ['text_empty', '']],
$redis->ftsearch('idx', '@text_empty:("")', $searchArgs)
);
$this->expectException(ServerException::class);
$redis->ftsearch('idx', '@text_not_empty:("")', $searchArgs);
}
/**
* @group connected
* @group relay-resp3
* @return void
* @requiresRediSearchVersion >= 2.09.00
* @requiresRedisJsonVersion >= 1.0.0
*/
public function testSearchJsonEmptyValues(): void
{
$redis = $this->getClient();
$hashResponse = $redis->jsonset('test:1', '$', '{"text_empty":""}');
$this->assertEquals('OK', $hashResponse);
$schema = [
new TextField(
'$.text_empty',
'text_empty',
false, false, false, '', 1, false, true
),
new TextField(
'$.text_not_empty',
'text_not_empty',
false, false, false, '', 1, false, false
),
];
$createArgs = new CreateArguments();
$createArgs->on('JSON');
$createArgs->prefix(['test:']);
$ftCreateResponse = $redis->ftcreate('idx', $schema, $createArgs);
$this->assertEquals('OK', $ftCreateResponse);
// Timeout to make sure that index created before search performed.
usleep(10000);
$searchArgs = new SearchArguments();
$searchArgs->dialect(4);
$this->assertSame(
[1, 'test:1', ['$', '[{"text_empty":""}]']],
$redis->ftsearch('idx', '@text_empty:("")', $searchArgs)
);
$this->expectException(ServerException::class);
$redis->ftsearch('idx', '@text_not_empty:("")', $searchArgs);
}
public function testSearchWithEnhancedMatchingCapabilities(): void
{
$redis = $this->getClient();
@@ -243,7 +336,7 @@ class FTSEARCH_Test extends PredisCommandTestCase
* @group connected
* @group relay-resp3
* @return void
* @requiresRediSearchVersion >= 2.9.0
* @requiresRediSearchVersion >= 2.09.00
*/
public function testGeoSearchQueriesContainsAndWithin(): void
{