diff --git a/src/Command/Argument/Search/SchemaFields/AbstractField.php b/src/Command/Argument/Search/SchemaFields/AbstractField.php index eb49f099..70b1edbe 100644 --- a/src/Command/Argument/Search/SchemaFields/AbstractField.php +++ b/src/Command/Argument/Search/SchemaFields/AbstractField.php @@ -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'; + } } /** diff --git a/src/Command/Argument/Search/SchemaFields/GeoField.php b/src/Command/Argument/Search/SchemaFields/GeoField.php index 23e7cc78..78d485a2 100644 --- a/src/Command/Argument/Search/SchemaFields/GeoField.php +++ b/src/Command/Argument/Search/SchemaFields/GeoField.php @@ -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); } } diff --git a/src/Command/Argument/Search/SchemaFields/NumericField.php b/src/Command/Argument/Search/SchemaFields/NumericField.php index 758b4e9c..fa50e912 100644 --- a/src/Command/Argument/Search/SchemaFields/NumericField.php +++ b/src/Command/Argument/Search/SchemaFields/NumericField.php @@ -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); } } diff --git a/src/Command/Argument/Search/SchemaFields/TagField.php b/src/Command/Argument/Search/SchemaFields/TagField.php index 358b3090..27180232 100644 --- a/src/Command/Argument/Search/SchemaFields/TagField.php +++ b/src/Command/Argument/Search/SchemaFields/TagField.php @@ -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'; + } } } diff --git a/src/Command/Argument/Search/SchemaFields/TextField.php b/src/Command/Argument/Search/SchemaFields/TextField.php index d72c6238..1b5e44c8 100644 --- a/src/Command/Argument/Search/SchemaFields/TextField.php +++ b/src/Command/Argument/Search/SchemaFields/TextField.php @@ -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'; + } } } diff --git a/tests/Predis/Command/Argument/Search/SchemaFields/GeoFieldTest.php b/tests/Predis/Command/Argument/Search/SchemaFields/GeoFieldTest.php index 155c77e5..789b9fb1 100644 --- a/tests/Predis/Command/Argument/Search/SchemaFields/GeoFieldTest.php +++ b/tests/Predis/Command/Argument/Search/SchemaFields/GeoFieldTest.php @@ -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'], + ], ]; } } diff --git a/tests/Predis/Command/Argument/Search/SchemaFields/NumericFieldTest.php b/tests/Predis/Command/Argument/Search/SchemaFields/NumericFieldTest.php index 14078e1f..245ab4e6 100644 --- a/tests/Predis/Command/Argument/Search/SchemaFields/NumericFieldTest.php +++ b/tests/Predis/Command/Argument/Search/SchemaFields/NumericFieldTest.php @@ -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'], + ], ]; } } diff --git a/tests/Predis/Command/Argument/Search/SchemaFields/TagFieldTest.php b/tests/Predis/Command/Argument/Search/SchemaFields/TagFieldTest.php index 34420882..1f4bade0 100644 --- a/tests/Predis/Command/Argument/Search/SchemaFields/TagFieldTest.php +++ b/tests/Predis/Command/Argument/Search/SchemaFields/TagFieldTest.php @@ -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'], + ], ]; } } diff --git a/tests/Predis/Command/Argument/Search/SchemaFields/TextFieldTest.php b/tests/Predis/Command/Argument/Search/SchemaFields/TextFieldTest.php index 03b9a468..9f5274c5 100644 --- a/tests/Predis/Command/Argument/Search/SchemaFields/TextFieldTest.php +++ b/tests/Predis/Command/Argument/Search/SchemaFields/TextFieldTest.php @@ -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'], + ], ]; } } diff --git a/tests/Predis/Command/Redis/Search/FTCREATE_Test.php b/tests/Predis/Command/Redis/Search/FTCREATE_Test.php index 5285d492..b2478730 100644 --- a/tests/Predis/Command/Redis/Search/FTCREATE_Test.php +++ b/tests/Predis/Command/Redis/Search/FTCREATE_Test.php @@ -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 [ diff --git a/tests/Predis/Command/Redis/Search/FTSEARCH_Test.php b/tests/Predis/Command/Redis/Search/FTSEARCH_Test.php index 06c86000..98a8f3a4 100644 --- a/tests/Predis/Command/Redis/Search/FTSEARCH_Test.php +++ b/tests/Predis/Command/Redis/Search/FTSEARCH_Test.php @@ -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 {