diff --git a/src/Command/Redis/Search/FTAGGREGATE.php b/src/Command/Redis/Search/FTAGGREGATE.php index 948455ed..35c21d28 100644 --- a/src/Command/Redis/Search/FTAGGREGATE.php +++ b/src/Command/Redis/Search/FTAGGREGATE.php @@ -29,8 +29,21 @@ class FTAGGREGATE extends RedisCommand public function setArguments(array $arguments) { + // If command already deserialized, bypass logic. + if (in_array('DIALECT', $arguments)) { + parent::setArguments($arguments); + + return; + } + [$index, $query] = $arguments; - $commandArguments = (!empty($arguments[2])) ? $arguments[2]->toArray() : []; + + if (!empty($arguments[2]) && !in_array('DIALECT', $arguments[2]->toArray())) { + // Default dialect is 2 + $arguments[2]->dialect(2); + } + + $commandArguments = (!empty($arguments[2])) ? $arguments[2]->toArray() : ['DIALECT', 2]; parent::setArguments(array_merge( [$index, $query], diff --git a/src/Command/Redis/Search/FTEXPLAIN.php b/src/Command/Redis/Search/FTEXPLAIN.php index e2aa35d4..5bbea312 100644 --- a/src/Command/Redis/Search/FTEXPLAIN.php +++ b/src/Command/Redis/Search/FTEXPLAIN.php @@ -28,8 +28,21 @@ class FTEXPLAIN extends RedisCommand public function setArguments(array $arguments) { + // If command already deserialized, bypass logic. + if (in_array('DIALECT', $arguments)) { + parent::setArguments($arguments); + + return; + } + [$index, $query] = $arguments; - $commandArguments = []; + + if (!empty($arguments[2]) && !in_array('DIALECT', $arguments[2]->toArray())) { + // Default dialect is 2 + $arguments[2]->dialect(2); + } + + $commandArguments = ['DIALECT', 2]; if (!empty($arguments[2])) { $commandArguments = $arguments[2]->toArray(); diff --git a/src/Command/Redis/Search/FTSEARCH.php b/src/Command/Redis/Search/FTSEARCH.php index 9fbe8a56..5e3cf1bb 100644 --- a/src/Command/Redis/Search/FTSEARCH.php +++ b/src/Command/Redis/Search/FTSEARCH.php @@ -28,8 +28,21 @@ class FTSEARCH extends RedisCommand public function setArguments(array $arguments) { + // If command already deserialized, bypass logic. + if (in_array('DIALECT', $arguments)) { + parent::setArguments($arguments); + + return; + } + [$index, $query] = $arguments; - $commandArguments = (!empty($arguments[2])) ? $arguments[2]->toArray() : []; + + if (!empty($arguments[2]) && !in_array('DIALECT', $arguments[2]->toArray())) { + // Default dialect is 2 + $arguments[2]->dialect(2); + } + + $commandArguments = (!empty($arguments[2])) ? $arguments[2]->toArray() : ['DIALECT', 2]; parent::setArguments(array_merge( [$index, $query], diff --git a/src/Command/Redis/Search/FTSPELLCHECK.php b/src/Command/Redis/Search/FTSPELLCHECK.php index ac0232bb..3bdf4aab 100644 --- a/src/Command/Redis/Search/FTSPELLCHECK.php +++ b/src/Command/Redis/Search/FTSPELLCHECK.php @@ -23,8 +23,21 @@ class FTSPELLCHECK extends RedisCommand public function setArguments(array $arguments) { + // If command already deserialized, bypass logic. + if (in_array('DIALECT', $arguments)) { + parent::setArguments($arguments); + + return; + } + [$index, $query] = $arguments; - $commandArguments = []; + + if (!empty($arguments[2]) && !in_array('DIALECT', $arguments[2]->toArray())) { + // Default dialect is 2 + $arguments[2]->dialect(2); + } + + $commandArguments = ['DIALECT', 2]; if (!empty($arguments[2])) { $commandArguments = $arguments[2]->toArray(); diff --git a/tests/Predis/Command/CommandTest.php b/tests/Predis/Command/CommandTest.php index e58dcf4d..6ac2a94a 100644 --- a/tests/Predis/Command/CommandTest.php +++ b/tests/Predis/Command/CommandTest.php @@ -18,7 +18,10 @@ use Predis\Command\Redis\CuckooFilter\CFADD; use Predis\Command\Redis\GET; use Predis\Command\Redis\Json\JSONSET; use Predis\Command\Redis\MGET; +use Predis\Command\Redis\Search\FTAGGREGATE; +use Predis\Command\Redis\Search\FTEXPLAIN; use Predis\Command\Redis\Search\FTSEARCH; +use Predis\Command\Redis\Search\FTSPELLCHECK; use Predis\Command\Redis\TDigest\TDIGESTADD; use Predis\Command\Redis\TimeSeries\TSGET; use Predis\Command\Redis\TopK\TOPKQUERY; @@ -204,7 +207,7 @@ class CommandTest extends PredisTestCase $deserializedCommand = Command::deserializeCommand($command->serializeCommand()); $this->assertInstanceOf($class, $deserializedCommand); - $this->assertSame($command->getArguments(), $deserializedCommand->getArguments()); + $this->assertEquals($command->getArguments(), $deserializedCommand->getArguments()); } /** @@ -252,7 +255,19 @@ class CommandTest extends PredisTestCase ], 'FTSEARCH' => [ FTSEARCH::class, - ['key', 'value'], + ['key', 'value', 'DIALECT', '2'], + ], + 'FTAGGREGATE' => [ + FTAGGREGATE::class, + ['key', 'value', 'DIALECT', '2'], + ], + 'FTSPELLCHECK' => [ + FTSPELLCHECK::class, + ['key', 'value', 'DIALECT', '2'], + ], + 'FTEXPLAIN' => [ + FTEXPLAIN::class, + ['key', 'value', 'DIALECT', '2'], ], 'TDIGESTADD' => [ TDIGESTADD::class, diff --git a/tests/Predis/Command/RawCommandTest.php b/tests/Predis/Command/RawCommandTest.php index ee0b5a02..fffd7bc1 100644 --- a/tests/Predis/Command/RawCommandTest.php +++ b/tests/Predis/Command/RawCommandTest.php @@ -176,7 +176,7 @@ class RawCommandTest extends PredisTestCase $deserializedCommand = RawCommand::deserializeCommand($command->serializeCommand()); $this->assertInstanceOf($class, $deserializedCommand); - $this->assertSame($command->getArguments(), $deserializedCommand->getArguments()); + $this->assertEquals($command->getArguments(), $deserializedCommand->getArguments()); } /** diff --git a/tests/Predis/Command/Redis/Search/FTAGGREGATE_Test.php b/tests/Predis/Command/Redis/Search/FTAGGREGATE_Test.php index 69ac23a8..f472a051 100644 --- a/tests/Predis/Command/Redis/Search/FTAGGREGATE_Test.php +++ b/tests/Predis/Command/Redis/Search/FTAGGREGATE_Test.php @@ -45,13 +45,14 @@ class FTAGGREGATE_Test extends PredisCommandTestCase /** * @group disconnected * @dataProvider argumentsProvider + * @requires PHP > 7.3 */ public function testFilterArguments(array $actualArguments, array $expectedArguments): void { $command = $this->getCommand(); $command->setArguments($actualArguments); - $this->assertSameValues($expectedArguments, $command->getArguments()); + $this->assertEquals($expectedArguments, $command->getArguments()); } /** @@ -176,55 +177,55 @@ class FTAGGREGATE_Test extends PredisCommandTestCase return [ 'with default arguments' => [ ['index', 'query'], - ['index', 'query'], + ['index', 'query', 'DIALECT', 2], ], 'with VERBATIM modifier' => [ ['index', 'query', (new AggregateArguments())->verbatim()], - ['index', 'query', 'VERBATIM'], + ['index', 'query', 'VERBATIM', 'DIALECT', 2], ], 'with LOAD modifier - specified fields' => [ ['index', 'query', (new AggregateArguments())->load('field1', 'field2')], - ['index', 'query', 'LOAD', 2, 'field1', 'field2'], + ['index', 'query', 'LOAD', 2, 'field1', 'field2', 'DIALECT', 2], ], 'with LOAD modifier - all fields' => [ ['index', 'query', (new AggregateArguments())->load('*')], - ['index', 'query', 'LOAD', '*'], + ['index', 'query', 'LOAD', '*', 'DIALECT', 2], ], 'with TIMEOUT modifier' => [ ['index', 'query', (new AggregateArguments())->timeout(2)], - ['index', 'query', 'TIMEOUT', 2], + ['index', 'query', 'TIMEOUT', 2, 'DIALECT', 2], ], 'with GROUPBY modifier' => [ ['index', 'query', (new AggregateArguments())->groupBy('property1', 'property2')], - ['index', 'query', 'GROUPBY', 2, 'property1', 'property2'], + ['index', 'query', 'GROUPBY', 2, 'property1', 'property2', 'DIALECT', 2], ], 'with REDUCE modifier' => [ ['index', 'query', (new AggregateArguments())->reduce('function', 'arg1', true, 'alias1', 'arg2')], - ['index', 'query', 'REDUCE', 'function', 2, 'arg1', 'AS', 'alias1', 'arg2'], + ['index', 'query', 'REDUCE', 'function', 2, 'arg1', 'AS', 'alias1', 'arg2', 'DIALECT', 2], ], 'with SORTBY modifier' => [ ['index', 'query', (new AggregateArguments())->sortBy(2, 'property1', 'ASC', 'property2', 'DESC')], - ['index', 'query', 'SORTBY', 2, 'property1', 'ASC', 'property2', 'DESC', 'MAX', 2], + ['index', 'query', 'SORTBY', 4, 'property1', 'ASC', 'property2', 'DESC', 'MAX', 2, 'DIALECT', 2], ], 'with APPLY modifier' => [ ['index', 'query', (new AggregateArguments())->apply('expression', 'name')], - ['index', 'query', 'APPLY', 'expression', 'AS', 'name'], + ['index', 'query', 'APPLY', 'expression', 'AS', 'name', 'DIALECT', 2], ], 'with LIMIT modifier' => [ ['index', 'query', (new AggregateArguments())->limit(2, 3)], - ['index', 'query', 'LIMIT', 2, 3], + ['index', 'query', 'LIMIT', 2, 3, 'DIALECT', 2], ], 'with FILTER modifier' => [ ['index', 'query', (new AggregateArguments())->filter('filter')], - ['index', 'query', 'FILTER', 'filter'], + ['index', 'query', 'FILTER', 'filter', 'DIALECT', 2], ], 'with WITHCURSOR modifier' => [ ['index', 'query', (new AggregateArguments())->withCursor(10, 20)], - ['index', 'query', 'WITHCURSOR', 'COUNT', 10, 'MAXIDLE', 20], + ['index', 'query', 'WITHCURSOR', 'COUNT', 10, 'MAXIDLE', 20, 'DIALECT', 2], ], 'with PARAMS modifier' => [ ['index', 'query', (new AggregateArguments())->params(['name1', 'value1', 'name2', 'value2'])], - ['index', 'query', 'PARAMS', 4, 'name1', 'value1', 'name2', 'value2'], + ['index', 'query', 'PARAMS', 4, 'name1', 'value1', 'name2', 'value2', 'DIALECT', 2], ], 'with DIALECT modifier' => [ ['index', 'query', (new AggregateArguments())->dialect('dialect')], @@ -242,7 +243,7 @@ class FTAGGREGATE_Test extends PredisCommandTestCase ], [ 'index', '@name: "test"', 'APPLY', 'year(@dob)', 'AS', 'birth', 'GROUPBY', 2, '@birth', '@country', - 'REDUCE', 'COUNT', 0, 'AS', 'num_visits', 'SORTBY', 1, '@day', + 'REDUCE', 'COUNT', 0, 'AS', 'num_visits', 'SORTBY', 1, '@day', 'DIALECT', 2, ], ], ]; diff --git a/tests/Predis/Command/Redis/Search/FTEXPLAIN_Test.php b/tests/Predis/Command/Redis/Search/FTEXPLAIN_Test.php index 6cf5e9b3..81966143 100644 --- a/tests/Predis/Command/Redis/Search/FTEXPLAIN_Test.php +++ b/tests/Predis/Command/Redis/Search/FTEXPLAIN_Test.php @@ -105,7 +105,10 @@ EOT; $this->assertEquals('OK', $redis->ftcreate('index', $schema)); $this->assertEquals( $expectedResponse, - $redis->ftexplain('index', '(foo bar)|(hello world) @date:[100 200]|@date:[500 +inf]') + $redis->ftexplain( + 'index', '(foo bar)|(hello world) @date:[100 200]|@date:[500 +inf]', + (new ExplainArguments())->dialect(1) + ) ); } @@ -113,6 +116,7 @@ EOT; * @group connected * @return void * @requiresRediSearchVersion >= 2.8.0 + * @requiresRedisVersion >= 7.5.0 */ public function testExplainReturnsExecutionPlanForGivenQueryResp3(): void { @@ -179,7 +183,7 @@ EOT; return [ 'with default arguments' => [ ['index', 'query', null], - ['index', 'query'], + ['index', 'query', 'DIALECT', 2], ], 'with DIALECT' => [ ['index', 'query', (new ExplainArguments())->dialect('dialect')], diff --git a/tests/Predis/Command/Redis/Search/FTSEARCH_Test.php b/tests/Predis/Command/Redis/Search/FTSEARCH_Test.php index ea6867ad..be9be9fe 100644 --- a/tests/Predis/Command/Redis/Search/FTSEARCH_Test.php +++ b/tests/Predis/Command/Redis/Search/FTSEARCH_Test.php @@ -15,6 +15,7 @@ namespace Predis\Command\Redis\Search; use Predis\Command\Argument\Search\CreateArguments; use Predis\Command\Argument\Search\SchemaFields\NumericField; use Predis\Command\Argument\Search\SchemaFields\TextField; +use Predis\Command\Argument\Search\SchemaFields\VectorField; use Predis\Command\Argument\Search\SearchArguments; use Predis\Command\Redis\PredisCommandTestCase; @@ -158,96 +159,173 @@ class FTSEARCH_Test extends PredisCommandTestCase $this->assertNotEmpty($actualResponse); } + /** + * @group connected + * @group relay-resp3 + * @return void + * @requiresRedisVersion >= 7.9.0 + */ + public function testVectorSearchWithDefaultDialect(): void + { + $redis = $this->getClient(); + + $this->assertEquals('OK', $redis->ftcreate('test', [ + new VectorField( + 'v', 'HNSW', + ['TYPE', 'FLOAT32', 'DIM', 2, 'DISTANCE_METRIC', 'L2'] + ), + ])); + + $this->sleep(0.1); + + $redis->hset('a', 'v', 'aaaaaaaa'); + $redis->hset('b', 'v', 'aaaabaaa'); + $redis->hset('c', 'v', 'aaaaabaa'); + + $searchArguments = new SearchArguments(); + $searchArguments->params(['vec', 'aaaaaaaa']); + + $ftSearch = new FTSEARCH(); + $ftSearch->setArguments(['test', '*=>[KNN 2 @v $vec]', $searchArguments]); + + $this->assertContains('DIALECT', $ftSearch->getArguments()); + $this->assertContains(2, $ftSearch->getArguments()); + + // Check if dialect applied via constructing custom command object. + $this->assertEquals(2, $redis->executeCommand($ftSearch)[0]); + + // Check if it works via usual client interface. + $this->assertEquals( + 2, + $redis->ftsearch('test', '*=>[KNN 2 @v $vec]', $searchArguments)[0] + ); + } + + /** + * @group connected + * @group relay-resp3 + * @return void + * @requiresRedisVersion >= 7.9.0 + */ + public function testSearchQueryWithDifferentDialects(): void + { + $redis = $this->getClient(); + $createArguments = new CreateArguments(); + $createArguments->prefix(['test:']); + + $this->assertEquals('OK', $redis->ftcreate('test', [ + new TextField('name'), + new TextField('lastname'), + ], $createArguments)); + + $this->sleep(0.1); + + $redis->hset('test:1', 'name', 'James'); + $redis->hset('test:1', 'lastname', 'Brown'); + + // Query with default DIALECT 2 + $this->assertEquals(1, $redis->ftsearch('test', '@name: James Brown')[0]); + + $searchArguments = new SearchArguments(); + $searchArguments->dialect(1); + + // Query with explicit DIALECT 1 + $this->assertEquals( + 0, + $redis->ftsearch('test', '@name: James Brown', $searchArguments)[0] + ); + } + public function argumentsProvider(): array { return [ 'with NOCONTENT modifier' => [ ['index', '*', (new SearchArguments())->noContent()], - ['index', '*', 'NOCONTENT'], + ['index', '*', 'NOCONTENT', 'DIALECT', 2], ], 'with VERBATIM modifier' => [ ['index', '*', (new SearchArguments())->verbatim()], - ['index', '*', 'VERBATIM'], + ['index', '*', 'VERBATIM', 'DIALECT', 2], ], 'with WITHSCORES modifier' => [ ['index', '*', (new SearchArguments())->withScores()], - ['index', '*', 'WITHSCORES'], + ['index', '*', 'WITHSCORES', 'DIALECT', 2], ], 'with WITHPAYLOADS modifier' => [ ['index', '*', (new SearchArguments())->withPayloads()], - ['index', '*', 'WITHPAYLOADS'], + ['index', '*', 'WITHPAYLOADS', 'DIALECT', 2], ], 'with WITHSORTKEYS modifier' => [ ['index', '*', (new SearchArguments())->withSortKeys()], - ['index', '*', 'WITHSORTKEYS'], + ['index', '*', 'WITHSORTKEYS', 'DIALECT', 2], ], 'with FILTER modifier' => [ ['index', '*', (new SearchArguments())->searchFilter(['numeric_field', 1, 10])], - ['index', '*', 'FILTER', 'numeric_field', 1, 10], + ['index', '*', 'FILTER', 'numeric_field', 1, 10, 'DIALECT', 2], ], 'with GEOFILTER modifier' => [ ['index', '*', (new SearchArguments())->geoFilter(['geo_field', 12.213, 14.212, 300, 'km'])], - ['index', '*', 'GEOFILTER', 'geo_field', 12.213, 14.212, 300, 'km'], + ['index', '*', 'GEOFILTER', 'geo_field', 12.213, 14.212, 300, 'km', 'DIALECT', 2], ], 'with INKEYS modifier' => [ ['index', '*', (new SearchArguments())->inKeys(['key1', 'key2'])], - ['index', '*', 'INKEYS', 2, 'key1', 'key2'], + ['index', '*', 'INKEYS', 2, 'key1', 'key2', 'DIALECT', 2], ], 'with INFIELDS modifier' => [ ['index', '*', (new SearchArguments())->inFields(['field1', 'field2'])], - ['index', '*', 'INFIELDS', 2, 'field1', 'field2'], + ['index', '*', 'INFIELDS', 2, 'field1', 'field2', 'DIALECT', 2], ], 'with RETURN modifier' => [ ['index', '*', (new SearchArguments())->addReturn(2, 'identifier', true, 'property')], - ['index', '*', 'RETURN', 2, 'identifier', 'AS', 'property'], + ['index', '*', 'RETURN', 2, 'identifier', 'AS', 'property', 'DIALECT', 2], ], 'with SUMMARIZE modifier' => [ ['index', '*', (new SearchArguments())->summarize(['field1', 'field2'], 2, 2, ',')], - ['index', '*', 'SUMMARIZE', 'FIELDS', 2, 'field1', 'field2', 'FRAGS', 2, 'LEN', 2, 'SEPARATOR', ','], + ['index', '*', 'SUMMARIZE', 'FIELDS', 2, 'field1', 'field2', 'FRAGS', 2, 'LEN', 2, 'SEPARATOR', ',', 'DIALECT', 2], ], 'with HIGHLIGHT modifier' => [ ['index', '*', (new SearchArguments())->highlight(['field1', 'field2'], 'openTag', 'closeTag')], - ['index', '*', 'HIGHLIGHT', 'FIELDS', 2, 'field1', 'field2', 'TAGS', 'openTag', 'closeTag'], + ['index', '*', 'HIGHLIGHT', 'FIELDS', 2, 'field1', 'field2', 'TAGS', 'openTag', 'closeTag', 'DIALECT', 2], ], 'with SLOP modifier' => [ ['index', '*', (new SearchArguments())->slop(2)], - ['index', '*', 'SLOP', 2], + ['index', '*', 'SLOP', 2, 'DIALECT', 2], ], 'with TIMEOUT modifier' => [ ['index', '*', (new SearchArguments())->timeout(2)], - ['index', '*', 'TIMEOUT', 2], + ['index', '*', 'TIMEOUT', 2, 'DIALECT', 2], ], 'with INORDER modifier' => [ ['index', '*', (new SearchArguments())->inOrder()], - ['index', '*', 'INORDER'], + ['index', '*', 'INORDER', 'DIALECT', 2], ], 'with EXPANDER modifier' => [ ['index', '*', (new SearchArguments())->expander('expander')], - ['index', '*', 'EXPANDER', 'expander'], + ['index', '*', 'EXPANDER', 'expander', 'DIALECT', 2], ], 'with SCORER modifier' => [ ['index', '*', (new SearchArguments())->scorer('scorer')], - ['index', '*', 'SCORER', 'scorer'], + ['index', '*', 'SCORER', 'scorer', 'DIALECT', 2], ], 'with EXPLAINSCORE modifier' => [ ['index', '*', (new SearchArguments())->explainScore()], - ['index', '*', 'EXPLAINSCORE'], + ['index', '*', 'EXPLAINSCORE', 'DIALECT', 2], ], 'with PAYLOAD modifier' => [ ['index', '*', (new SearchArguments())->payload('payload')], - ['index', '*', 'PAYLOAD', 'payload'], + ['index', '*', 'PAYLOAD', 'payload', 'DIALECT', 2], ], 'with SORTBY modifier' => [ ['index', '*', (new SearchArguments())->sortBy('sort_attribute', 'desc')], - ['index', '*', 'SORTBY', 'sort_attribute', 'DESC'], + ['index', '*', 'SORTBY', 'sort_attribute', 'DESC', 'DIALECT', 2], ], 'with LIMIT modifier' => [ ['index', '*', (new SearchArguments())->limit(2, 2)], - ['index', '*', 'LIMIT', 2, 2], + ['index', '*', 'LIMIT', 2, 2, 'DIALECT', 2], ], 'with PARAMS modifier' => [ ['index', '*', (new SearchArguments())->params(['name1', 'value2', 'name2', 'value2'])], - ['index', '*', 'PARAMS', 4, 'name1', 'value2', 'name2', 'value2'], + ['index', '*', 'PARAMS', 4, 'name1', 'value2', 'name2', 'value2', 'DIALECT', 2], ], 'with DIALECT modifier' => [ ['index', '*', (new SearchArguments())->dialect('dialect')], @@ -255,7 +333,7 @@ class FTSEARCH_Test extends PredisCommandTestCase ], 'with chain of arguments' => [ ['index', '*', (new SearchArguments())->withScores()->withPayloads()->searchFilter(['numeric_field', 1, 10])->addReturn(2, 'identifier', true, 'property')], - ['index', '*', 'WITHSCORES', 'WITHPAYLOADS', 'FILTER', 'numeric_field', 1, 10, 'RETURN', 2, 'identifier', 'AS', 'property'], + ['index', '*', 'WITHSCORES', 'WITHPAYLOADS', 'FILTER', 'numeric_field', 1, 10, 'RETURN', 2, 'identifier', 'AS', 'property', 'DIALECT', 2], ], ]; } diff --git a/tests/Predis/Command/Redis/Search/FTSPELLCHECK_Test.php b/tests/Predis/Command/Redis/Search/FTSPELLCHECK_Test.php index e9915a6c..fa2a14e0 100644 --- a/tests/Predis/Command/Redis/Search/FTSPELLCHECK_Test.php +++ b/tests/Predis/Command/Redis/Search/FTSPELLCHECK_Test.php @@ -148,7 +148,6 @@ class FTSPELLCHECK_Test extends PredisCommandTestCase $redis = $this->getClient(); $this->expectException(ServerException::class); - $this->expectExceptionMessage('Unknown Index name'); $redis->ftspellcheck( 'index', @@ -162,19 +161,19 @@ class FTSPELLCHECK_Test extends PredisCommandTestCase return [ 'with default arguments' => [ ['index', 'query'], - ['index', 'query'], + ['index', 'query', 'DIALECT', 2], ], 'with DISTANCE modifier' => [ ['index', 'query', (new SpellcheckArguments())->distance(2)], - ['index', 'query', 'DISTANCE', 2], + ['index', 'query', 'DISTANCE', 2, 'DIALECT', 2], ], 'with TERMS modifier - INCLUDE' => [ ['index', 'query', (new SpellcheckArguments())->terms('dict', 'INCLUDE', 'term')], - ['index', 'query', 'TERMS', 'INCLUDE', 'dict', 'term'], + ['index', 'query', 'TERMS', 'INCLUDE', 'dict', 'term', 'DIALECT', 2], ], 'with TERMS modifier - EXCLUDE' => [ ['index', 'query', (new SpellcheckArguments())->terms('dict', 'EXCLUDE', 'term')], - ['index', 'query', 'TERMS', 'EXCLUDE', 'dict', 'term'], + ['index', 'query', 'TERMS', 'EXCLUDE', 'dict', 'term', 'DIALECT', 2], ], 'with DIALECT modifier' => [ ['index', 'query', (new SpellcheckArguments())->dialect('dialect')],