diff --git a/.github/workflows/03-phpunit.yml b/.github/workflows/03-phpunit.yml index 960e51e..a97bafc 100644 --- a/.github/workflows/03-phpunit.yml +++ b/.github/workflows/03-phpunit.yml @@ -166,3 +166,29 @@ jobs: - name: Run PHPUnit Tests run: ./vendor/bin/phpunit --testsuite=mysql + meilisearch-storage: + runs-on: ubuntu-latest + services: + meilisearch: + image: getmeili/meilisearch:latest + options: >- + --health-cmd "curl -s http://localhost:7700/ -o /dev/null" + --health-interval 10s + --health-timeout 5s + --health-retries 5 + ports: + - "7700:7700" + steps: + - name: Checkout Code + uses: actions/checkout@v4 + + - name: Set up PHP + uses: shivammathur/setup-php@v2 + with: + php-version: '8.2' + + - name: Install Composer Dependencies + run: composer install + + - name: Run PHPUnit Tests + run: ./vendor/bin/phpunit --testsuite=meilisearch diff --git a/composer.json b/composer.json index d1ab941..b7f7b67 100644 --- a/composer.json +++ b/composer.json @@ -31,12 +31,21 @@ "opensearch-project/opensearch-php": "^2.0", "shyim/opensearch-php-dsl": "^1.0.1", "async-aws/dynamo-db": "^2.0", - "mongodb/mongodb": "^1.16" + "mongodb/mongodb": "^1.16", + "meilisearch/meilisearch-php": "^1.5", + "symfony/http-client": "^6.4", + "nyholm/psr7": "^1.0" }, "require-dev": { "phpunit/phpunit": "^10.5", "symfony/var-dumper": "^6.3", "phpstan/phpstan": "^1.10", - "friendsofphp/php-cs-fixer": "^3.35" + "friendsofphp/php-cs-fixer": "^3.35", + "kubawerlos/php-cs-fixer-custom-fixers": "~v3.14.0" + }, + "config": { + "allow-plugins": { + "php-http/discovery": true + } } } diff --git a/devenv.lock b/devenv.lock index 566c143..d34a272 100644 --- a/devenv.lock +++ b/devenv.lock @@ -3,11 +3,11 @@ "devenv": { "locked": { "dir": "src/modules", - "lastModified": 1696344641, - "narHash": "sha256-cfGsdtDvzYaFA7oGWSgcd1yST6LFwvjMcHvtVj56VcU=", + "lastModified": 1704835383, + "narHash": "sha256-SoC0rYR9iHW0dVOEmxNEfa8vk9dTK86P5iXTgHafmwM=", "owner": "cachix", "repo": "devenv", - "rev": "05e26941f34486bff6ebeb4b9c169b6f637f1758", + "rev": "18ef9849d1ecac7a9a7920eb4f2e4adcf67a8c3a", "type": "github" }, "original": { @@ -74,11 +74,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1696419054, - "narHash": "sha256-EdR+dIKCfqL3voZUDYwcvgRDOektQB9KbhBVcE0/3Mo=", + "lastModified": 1704842529, + "narHash": "sha256-OTeQA+F8d/Evad33JMfuXC89VMetQbsU4qcaePchGr4=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "7131f3c223a2d799568e4b278380cd9dac2b8579", + "rev": "eabe8d3eface69f5bb16c18f8662a702f50c20d5", "type": "github" }, "original": { @@ -115,11 +115,11 @@ "nixpkgs-stable": "nixpkgs-stable" }, "locked": { - "lastModified": 1696158581, - "narHash": "sha256-h0vY4E7Lx95lpYQbG2w4QH4yG5wCYOvPJzK93wVQbT0=", + "lastModified": 1705056064, + "narHash": "sha256-pi9UtBFD5/U48Jrc6uvA8ZCmW4xnceUDp2QysBEkZCw=", "owner": "cachix", "repo": "pre-commit-hooks.nix", - "rev": "033453f85064ccac434dfd957f95d8457901ecd6", + "rev": "54d60d191aa8ba0629f662d8873a892cbe3d65ad", "type": "github" }, "original": { diff --git a/devenv.nix b/devenv.nix index d2b4326..fd239ee 100644 --- a/devenv.nix +++ b/devenv.nix @@ -10,6 +10,7 @@ services.redis.enable = lib.mkDefault true; services.adminer.enable = lib.mkDefault true; services.adminer.listen = lib.mkDefault "127.0.0.1:9080"; + services.meilisearch.enable = lib.mkDefault true; languages.php = { enable = lib.mkDefault true; diff --git a/phpunit.xml.dist b/phpunit.xml.dist index d607e92..76a6958 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -11,6 +11,7 @@ tests/Common + tests/Array @@ -31,6 +32,10 @@ tests/Opensearch + + tests/Meilisearch + + tests/Redis diff --git a/src/Meilisearch/MeilisearchStorage.php b/src/Meilisearch/MeilisearchStorage.php new file mode 100644 index 0000000..2d36420 --- /dev/null +++ b/src/Meilisearch/MeilisearchStorage.php @@ -0,0 +1,312 @@ +paging instanceof Page) { + $params['page'] = $criteria->paging->page; + $params['hitsPerPage'] = $criteria->limit; + } elseif ($criteria->limit !== null) { + $params['limit'] = $criteria->limit; + } + + $filters = $criteria->filters; + if ($criteria->keys !== null) { + $filters[] = new Any(field: 'key', value: $criteria->keys); + } + + if (!empty($filters)) { + $params['filter'] = $this->parse($filters, $context); + } + + $result = $this->index()->search( + query: null, + searchParams: $params + ); + + $documents = []; + foreach ($result->getHits() as $hit) { + $key = $hit['key']; + unset($hit['key']); + + $documents[] = new Document(key: $key, data: $hit); + } + + return new FilterResult( + elements: $documents + ); + } + + public function remove(array $keys): void + { + $this->index()->deleteDocuments($keys); + } + + public function store(Documents $documents): void + { + $data = []; + foreach ($documents as $document) { + $record = $document->data; + $record['key'] = $document->key; + $data[] = $record; + } + + $this->index()->addDocuments($data); + } + + public function setup(): void {} + + public function index(): Indexes + { + return $this->client->index($this->schema->source); + } + + /** + * @param array $filters + */ + private function parse(array $filters, StorageContext $context): string + { + $parsed = []; + + foreach ($filters as $filter) { + if ($filter instanceof Operator) { + $parsed[] = $this->parseOperator($filter, $context); + } + + if ($filter instanceof Filter) { + $parsed[] = $this->parseFilter($filter, $context); + } + } + + if (count($parsed) === 1) { + return $parsed[0]; + } + + return self::and($parsed); + } + + private static function cast(mixed $value): mixed + { + if (is_string($value)) { + return '"' . $value . '"'; + } + + if (is_bool($value)) { + return $value ? 'true' : 'false'; + } + + if (!is_array($value)) { + return $value; + } + + return array_map(fn($v) => self::cast($v), $value); + } + + private function parseFilter(Filter $filter, StorageContext $context): string + { + $property = SchemaUtil::property($filter->field); + + $translated = SchemaUtil::translated(schema: $this->schema, accessor: $property); + + $value = SchemaUtil::cast($this->schema, $filter->field, $filter->value); + + $value = self::cast($value); + + $factory = function (\Closure $closure) use ($filter, $value) { + return $closure($filter->field, $value); + }; + + if ($translated) { + $factory = function (\Closure $closure) use ($filter, $context, $value) { + return $this->translatedQuery($closure, $filter, $value, $context); + }; + } + + if (is_string($filter->value) && ($filter instanceof Lt || $filter instanceof Gt || $filter instanceof Lte || $filter instanceof Gte)) { + throw new NotSupportedByEngine('34', 'Meilisearch does not support string comparison.'); + } + if ($filter instanceof Contains) { + throw new NotSupportedByEngine('33', ' Meilisearch contains/prefix/suffix filter are not supported.'); + } + if ($filter instanceof Prefix) { + throw new NotSupportedByEngine('33', ' Meilisearch contains/prefix/suffix filter are not supported.'); + } + if ($filter instanceof Suffix) { + throw new NotSupportedByEngine('33', ' Meilisearch contains/prefix/suffix filter are not supported.'); + } + + if ($filter->value === null) { + if ($filter instanceof Equals) { + return $factory(fn($field, $value) => $field . ' IS NULL'); + } + + if ($filter instanceof Not) { + return $factory(fn($field, $value) => $field . ' IS NOT NULL'); + } + + throw new \RuntimeException('Null filter values are only supported for Equals and Not filters'); + } + + if ($filter instanceof Equals) { + return $factory(fn($field, $value) => $field . ' = ' . $value); + } + + if ($filter instanceof Not) { + return $factory(fn($field, $value) => $field . ' != ' . $value); + } + + if ($filter instanceof Any) { + return $factory(function (string $field, mixed $value) { + return $field . ' IN [' . implode(',', $value) . ']'; + }); + } + + if ($filter instanceof Neither) { + + return $factory(function (string $field, mixed $value) { + return $field . ' NOT IN [' . implode(',', $value) . ']'; + }); + } + + if ($filter instanceof Lt) { + return $factory(fn($field, $value) => $field . ' < ' . $value); + } + + if ($filter instanceof Gt) { + return $factory(fn($field, $value) => $field . ' > ' . $value); + } + + if ($filter instanceof Lte) { + return $factory(fn($field, $value) => $field . ' <= ' . $value); + } + + if ($filter instanceof Gte) { + return $factory(fn($field, $value) => $field . ' >= ' . $value); + } + + throw new \RuntimeException('Unknown filter: ' . get_class($filter)); + } + + private function parseOperator(Operator $operator, StorageContext $context): string + { + $parsed = []; + foreach ($operator->filters as $filter) { + $parsed[] = $this->parse([$filter], $context); + } + + if ($operator instanceof AndOperator) { + return self::and($parsed); + } + + if ($operator instanceof OrOperator) { + return self::or($parsed); + } + + if ($operator instanceof NandOperator) { + return 'NOT ' . self::and($parsed); + } + + if ($operator instanceof NorOperator) { + return 'NOT ' . self::or($parsed); + } + + throw new \RuntimeException('Unknown operator: ' . get_class($operator)); + } + + private function translatedQuery(\Closure $closure, Filter $filter, mixed $value, StorageContext $context): string + { + $queries = []; + + $before = []; + + foreach ($context->languages as $index => $languageId) { + if (array_key_first($context->languages) === $index) { + $queries[] = self::and([ + $filter->field . '.' . $languageId . ' EXISTS', + $filter->field . '.' . $languageId . ' IS NOT NULL', + $closure($filter->field . '.' . $languageId, $value), + ]); + + $before[] = $languageId; + + continue; + } + + $nested = []; + foreach ($before as $id) { + $nested[] = self::or([ + $filter->field . '.' . $id . ' NOT EXISTS', + $filter->field . '.' . $id . ' IS NULL' + ]); + } + + $nested[] = self::and([ + $filter->field . '.' . $languageId . ' EXISTS', + $filter->field . '.' . $languageId . ' IS NOT NULL', + $closure($filter->field . '.' . $languageId, $value), + ]); + + $before[] = $languageId; + + $queries[] = self::and($nested); + } + + return self::or($queries); + } + + /** + * @param array $elements + */ + private static function or(array $elements): string + { + return '(' . implode(' OR ', $elements) . ')'; + } + + /** + * @param array $elements + */ + private static function and(array $elements): string + { + return '(' . implode(' AND ', $elements) . ')'; + } +} diff --git a/src/Meilisearch/composer.json b/src/Meilisearch/composer.json new file mode 100644 index 0000000..0b06738 --- /dev/null +++ b/src/Meilisearch/composer.json @@ -0,0 +1,3 @@ +{ + "name": "shopware/storage-meilisearch" +} \ No newline at end of file diff --git a/tests/Meilisearch/LiveMeilisearchStorage.php b/tests/Meilisearch/LiveMeilisearchStorage.php new file mode 100644 index 0000000..6343510 --- /dev/null +++ b/tests/Meilisearch/LiveMeilisearchStorage.php @@ -0,0 +1,57 @@ +storage->filter($criteria, $context); + } + + public function remove(array $keys): void + { + $this->storage->remove($keys); + + $this->wait(); + } + + public function store(Documents $documents): void + { + $this->storage->store($documents); + + $this->wait(); + } + + public function setup(): void {} + + private function wait(): void + { + $tasks = new TasksQuery(); + $tasks->setStatuses(['enqueued', 'processing']); + + $tasks = $this->client->getTasks($tasks); + + $ids = array_map(fn($task) => $task['uid'], $tasks->getResults()); + + if (count($ids) === 0) { + return; + } + + $this->client->waitForTasks($ids); + } +} diff --git a/tests/Meilisearch/MeilisearchStorageTest.php b/tests/Meilisearch/MeilisearchStorageTest.php new file mode 100644 index 0000000..fe4715a --- /dev/null +++ b/tests/Meilisearch/MeilisearchStorageTest.php @@ -0,0 +1,2350 @@ +getClient()->getIndex($this->getSchema()->source); + } catch (ApiException) { + return false; + } + + return true; + } + + protected function setUp(): void + { + parent::setUp(); + + if ($this->exists()) { + $this->index()->deleteAllDocuments(); + + $this->wait(); + + return; + } + + $this->getClient()->deleteIndex($this->getSchema()->source); + + $this->wait(); + + $this->getClient()->createIndex( + uid: $this->getSchema()->source, + options: ['primaryKey' => 'key'] + ); + + $fields = array_map(fn($field) => $field->name, $this->getSchema()->fields); + + $fields[] = 'key'; + + $fields = array_values(array_filter($fields)); + + $this->index() + ->updateFilterableAttributes($fields); + + $this->index() + ->updateSortableAttributes($fields); + + $this->wait(); + } + + #[DataProvider('debugProvider')] + public function testDebug( + Documents $input, + FilterCriteria $criteria, + FilterResult $expected + ): void { + $storage = $this->getStorage(); + + $storage->store($input); + + try { + $loaded = $storage->filter($criteria, new StorageContext(languages: ['en', 'de'])); + } catch (NotSupportedByEngine $e) { + static::markTestIncomplete($e->getMessage()); + } + + static::assertEquals($expected, $loaded); + } + + public static function debugProvider(): \Generator + { + yield 'Test object field equals filter' => [ + 'input' => new Documents([ + self::document(key: 'key1', objectField: ['foo' => 'bar']), + self::document(key: 'key2', objectField: ['foo' => 'baz']), + self::document(key: 'key3', objectField: ['foo' => 'qux']), + ]), + 'criteria' => new FilterCriteria( + filters: [ + new Equals(field: 'objectField.foo', value: 'baz') + ] + ), + 'expected' => new FilterResult([ + self::document(key: 'key2', objectField: ['foo' => 'baz']), + ]) + ]; + yield 'Test object field equals any filter' => [ + 'input' => new Documents([ + self::document(key: 'key1', objectField: ['foo' => 'bar']), + self::document(key: 'key2', objectField: ['foo' => 'baz']), + self::document(key: 'key3', objectField: ['foo' => 'qux']), + ]), + 'criteria' => new FilterCriteria( + filters: [ + new Any(field: 'objectField.foo', value: ['baz', 'qux']) + ] + ), + 'expected' => new FilterResult([ + self::document(key: 'key2', objectField: ['foo' => 'baz']), + self::document(key: 'key3', objectField: ['foo' => 'qux']), + ]) + ]; + yield 'Test object field not filter' => [ + 'input' => new Documents([ + self::document(key: 'key1', objectField: ['foo' => 'bar']), + self::document(key: 'key2', objectField: ['foo' => 'baz']), + self::document(key: 'key3', objectField: ['foo' => 'qux']), + ]), + 'criteria' => new FilterCriteria( + filters: [ + new Not(field: 'objectField.foo', value: 'baz') + ] + ), + 'expected' => new FilterResult([ + self::document(key: 'key1', objectField: ['foo' => 'bar']), + self::document(key: 'key3', objectField: ['foo' => 'qux']), + ]) + ]; + yield 'Test object field not any filter' => [ + 'input' => new Documents([ + self::document(key: 'key1', objectField: ['foo' => 'bar']), + self::document(key: 'key2', objectField: ['foo' => 'baz']), + self::document(key: 'key3', objectField: ['foo' => 'qux']), + ]), + 'criteria' => new FilterCriteria( + filters: [ + new Neither(field: 'objectField.foo', value: ['baz', 'qux']) + ] + ), + 'expected' => new FilterResult([ + self::document(key: 'key1', objectField: ['foo' => 'bar']), + ]) + ]; + yield 'Test object field contains filter' => [ + 'input' => new Documents([ + self::document(key: 'key1', objectField: ['foo' => 'bar']), + self::document(key: 'key2', objectField: ['foo' => 'baz']), + self::document(key: 'key3', objectField: ['foo' => 'qux']), + ]), + 'criteria' => new FilterCriteria( + filters: [ + new Contains(field: 'objectField.foo', value: 'ba') + ] + ), + 'expected' => new FilterResult([ + self::document(key: 'key1', objectField: ['foo' => 'bar']), + self::document(key: 'key2', objectField: ['foo' => 'baz']), + ]) + ]; + yield 'Test object field gte filter' => [ + 'input' => new Documents([ + self::document(key: 'key1', objectField: ['foo' => 'bar']), + self::document(key: 'key2', objectField: ['foo' => 'baz']), + self::document(key: 'key3', objectField: ['foo' => 'qux']), + ]), + 'criteria' => new FilterCriteria( + filters: [ + new Gte(field: 'objectField.foo', value: 'baz') + ] + ), + 'expected' => new FilterResult([ + self::document(key: 'key2', objectField: ['foo' => 'baz']), + self::document(key: 'key3', objectField: ['foo' => 'qux']), + ]) + ]; + yield 'Test object field lte filter' => [ + 'input' => new Documents([ + self::document(key: 'key1', objectField: ['foo' => 'bar']), + self::document(key: 'key2', objectField: ['foo' => 'baz']), + self::document(key: 'key3', objectField: ['foo' => 'qux']), + ]), + 'criteria' => new FilterCriteria( + filters: [ + new Lte(field: 'objectField.foo', value: 'baz') + ] + ), + 'expected' => new FilterResult([ + self::document(key: 'key1', objectField: ['foo' => 'bar']), + self::document(key: 'key2', objectField: ['foo' => 'baz']), + ]) + ]; + yield 'Test object field gt filter' => [ + 'input' => new Documents([ + self::document(key: 'key1', objectField: ['foo' => 'bar']), + self::document(key: 'key2', objectField: ['foo' => 'baz']), + self::document(key: 'key3', objectField: ['foo' => 'qux']), + ]), + 'criteria' => new FilterCriteria( + filters: [ + new Gt(field: 'objectField.foo', value: 'baz') + ] + ), + 'expected' => new FilterResult([ + self::document(key: 'key3', objectField: ['foo' => 'qux']), + ]) + ]; + yield 'Test object field lt filter' => [ + 'input' => new Documents([ + self::document(key: 'key1', objectField: ['foo' => 'bar']), + self::document(key: 'key2', objectField: ['foo' => 'baz']), + self::document(key: 'key3', objectField: ['foo' => 'qux']), + ]), + 'criteria' => new FilterCriteria( + filters: [ + new Lt(field: 'objectField.foo', value: 'baz') + ] + ), + 'expected' => new FilterResult([ + self::document(key: 'key1', objectField: ['foo' => 'bar']), + ]) + ]; + + yield 'Test object field equals filter and int value' => [ + 'input' => new Documents([ + self::document(key: 'key1', objectField: ['fooInt' => 1]), + self::document(key: 'key2', objectField: ['fooInt' => 2]), + self::document(key: 'key3', objectField: ['fooInt' => 3]), + ]), + 'criteria' => new FilterCriteria( + filters: [ + new Equals(field: 'objectField.fooInt', value: 2) + ] + ), + 'expected' => new FilterResult([ + self::document(key: 'key2', objectField: ['fooInt' => 2]), + ]) + ]; + yield 'Test object field equals any filter and int values' => [ + 'input' => new Documents([ + self::document(key: 'key1', objectField: ['fooInt' => 1]), + self::document(key: 'key2', objectField: ['fooInt' => 2]), + self::document(key: 'key3', objectField: ['fooInt' => 3]), + ]), + 'criteria' => new FilterCriteria( + filters: [ + new Any(field: 'objectField.fooInt', value: [1, 2]) + ] + ), + 'expected' => new FilterResult([ + self::document(key: 'key1', objectField: ['fooInt' => 1]), + self::document(key: 'key2', objectField: ['fooInt' => 2]), + ]) + ]; + yield 'Test object field not filter and int value' => [ + 'input' => new Documents([ + self::document(key: 'key1', objectField: ['fooInt' => 1]), + self::document(key: 'key2', objectField: ['fooInt' => 2]), + self::document(key: 'key3', objectField: ['fooInt' => 3]), + ]), + 'criteria' => new FilterCriteria( + filters: [ + new Not(field: 'objectField.fooInt', value: 2) + ] + ), + 'expected' => new FilterResult([ + self::document(key: 'key1', objectField: ['fooInt' => 1]), + self::document(key: 'key3', objectField: ['fooInt' => 3]), + ]) + ]; + yield 'Test object field not any filter and int values' => [ + 'input' => new Documents([ + self::document(key: 'key1', objectField: ['fooInt' => 1]), + self::document(key: 'key2', objectField: ['fooInt' => 2]), + self::document(key: 'key3', objectField: ['fooInt' => 3]), + ]), + 'criteria' => new FilterCriteria( + filters: [ + new Neither(field: 'objectField.fooInt', value: [1, 2]) + ] + ), + 'expected' => new FilterResult([ + self::document(key: 'key3', objectField: ['fooInt' => 3]), + ]) + ]; + yield 'Test object field gte filter and int value' => [ + 'input' => new Documents([ + self::document(key: 'key1', objectField: ['fooInt' => 1]), + self::document(key: 'key2', objectField: ['fooInt' => 2]), + self::document(key: 'key3', objectField: ['fooInt' => 3]), + ]), + 'criteria' => new FilterCriteria( + filters: [ + new Gte(field: 'objectField.fooInt', value: 2) + ] + ), + 'expected' => new FilterResult([ + self::document(key: 'key2', objectField: ['fooInt' => 2]), + self::document(key: 'key3', objectField: ['fooInt' => 3]), + ]) + ]; + yield 'Test object field lte filter and int value' => [ + 'input' => new Documents([ + self::document(key: 'key1', objectField: ['fooInt' => 1]), + self::document(key: 'key2', objectField: ['fooInt' => 2]), + self::document(key: 'key3', objectField: ['fooInt' => 3]), + ]), + 'criteria' => new FilterCriteria( + filters: [ + new Lte(field: 'objectField.fooInt', value: 2) + ] + ), + 'expected' => new FilterResult([ + self::document(key: 'key1', objectField: ['fooInt' => 1]), + self::document(key: 'key2', objectField: ['fooInt' => 2]), + ]) + ]; + yield 'Test object field gt filter and int value' => [ + 'input' => new Documents([ + self::document(key: 'key1', objectField: ['fooInt' => 1]), + self::document(key: 'key2', objectField: ['fooInt' => 2]), + self::document(key: 'key3', objectField: ['fooInt' => 3]), + ]), + 'criteria' => new FilterCriteria( + filters: [ + new Gt(field: 'objectField.fooInt', value: 2) + ] + ), + 'expected' => new FilterResult([ + self::document(key: 'key3', objectField: ['fooInt' => 3]), + ]) + ]; + yield 'Test object field lt filter and int value' => [ + 'input' => new Documents([ + self::document(key: 'key1', objectField: ['fooInt' => 1]), + self::document(key: 'key2', objectField: ['fooInt' => 2]), + self::document(key: 'key3', objectField: ['fooInt' => 3]), + ]), + 'criteria' => new FilterCriteria( + filters: [ + new Lt(field: 'objectField.fooInt', value: 2) + ] + ), + 'expected' => new FilterResult([ + self::document(key: 'key1', objectField: ['fooInt' => 1]), + ]) + ]; + yield 'Test object field gte and lte filter and int values' => [ + 'input' => new Documents([ + self::document(key: 'key1', objectField: ['fooInt' => 1]), + self::document(key: 'key2', objectField: ['fooInt' => 2]), + self::document(key: 'key3', objectField: ['fooInt' => 3]), + self::document(key: 'key4', objectField: ['fooInt' => 4]), + ]), + 'criteria' => new FilterCriteria( + filters: [ + new Gte(field: 'objectField.fooInt', value: 2), + new Lte(field: 'objectField.fooInt', value: 3), + ] + ), + 'expected' => new FilterResult([ + self::document(key: 'key2', objectField: ['fooInt' => 2]), + self::document(key: 'key3', objectField: ['fooInt' => 3]), + ]) + ]; + + yield 'Test object field equals filter and float values' => [ + 'input' => new Documents([ + self::document(key: 'key1', objectField: ['fooFloat' => 1.1]), + self::document(key: 'key2', objectField: ['fooFloat' => 2.2]), + self::document(key: 'key3', objectField: ['fooFloat' => 3.3]), + ]), + 'criteria' => new FilterCriteria( + filters: [ + new Equals(field: 'objectField.fooFloat', value: 2.2) + ] + ), + 'expected' => new FilterResult([ + self::document(key: 'key2', objectField: ['fooFloat' => 2.2]), + ]) + ]; + yield 'Test object field equals any filter and float values' => [ + 'input' => new Documents([ + self::document(key: 'key1', objectField: ['fooFloat' => 1.1]), + self::document(key: 'key2', objectField: ['fooFloat' => 2.2]), + self::document(key: 'key3', objectField: ['fooFloat' => 3.3]), + ]), + 'criteria' => new FilterCriteria( + filters: [ + new Any(field: 'objectField.fooFloat', value: [1.1, 2.2]) + ] + ), + 'expected' => new FilterResult([ + self::document(key: 'key1', objectField: ['fooFloat' => 1.1]), + self::document(key: 'key2', objectField: ['fooFloat' => 2.2]), + ]) + ]; + yield 'Test object field not filter and float values' => [ + 'input' => new Documents([ + self::document(key: 'key1', objectField: ['fooFloat' => 1.1]), + self::document(key: 'key2', objectField: ['fooFloat' => 2.2]), + self::document(key: 'key3', objectField: ['fooFloat' => 3.3]), + ]), + 'criteria' => new FilterCriteria( + filters: [ + new Not(field: 'objectField.fooFloat', value: 2.2) + ] + ), + 'expected' => new FilterResult([ + self::document(key: 'key1', objectField: ['fooFloat' => 1.1]), + self::document(key: 'key3', objectField: ['fooFloat' => 3.3]), + ]) + ]; + yield 'Test object field not any filter and float values' => [ + 'input' => new Documents([ + self::document(key: 'key1', objectField: ['fooFloat' => 1.1]), + self::document(key: 'key2', objectField: ['fooFloat' => 2.2]), + self::document(key: 'key3', objectField: ['fooFloat' => 3.3]), + ]), + 'criteria' => new FilterCriteria( + filters: [ + new Neither(field: 'objectField.fooFloat', value: [1.1, 2.2]) + ] + ), + 'expected' => new FilterResult([ + self::document(key: 'key3', objectField: ['fooFloat' => 3.3]), + ]) + ]; + yield 'Test object field gte filter and float values' => [ + 'input' => new Documents([ + self::document(key: 'key1', objectField: ['fooFloat' => 1.1]), + self::document(key: 'key2', objectField: ['fooFloat' => 2.2]), + self::document(key: 'key3', objectField: ['fooFloat' => 3.3]), + ]), + 'criteria' => new FilterCriteria( + filters: [ + new Gte(field: 'objectField.fooFloat', value: 2.2) + ] + ), + 'expected' => new FilterResult([ + self::document(key: 'key2', objectField: ['fooFloat' => 2.2]), + self::document(key: 'key3', objectField: ['fooFloat' => 3.3]), + ]) + ]; + yield 'Test object field lte filter and float values' => [ + 'input' => new Documents([ + self::document(key: 'key1', objectField: ['fooFloat' => 1.1]), + self::document(key: 'key2', objectField: ['fooFloat' => 2.2]), + self::document(key: 'key3', objectField: ['fooFloat' => 3.3]), + ]), + 'criteria' => new FilterCriteria( + filters: [ + new Lte(field: 'objectField.fooFloat', value: 2.2) + ] + ), + 'expected' => new FilterResult([ + self::document(key: 'key1', objectField: ['fooFloat' => 1.1]), + self::document(key: 'key2', objectField: ['fooFloat' => 2.2]), + ]) + ]; + yield 'Test object field gt filter and float values' => [ + 'input' => new Documents([ + self::document(key: 'key1', objectField: ['fooFloat' => 1.1]), + self::document(key: 'key2', objectField: ['fooFloat' => 2.2]), + self::document(key: 'key3', objectField: ['fooFloat' => 3.3]), + ]), + 'criteria' => new FilterCriteria( + filters: [ + new Gt(field: 'objectField.fooFloat', value: 2.2) + ] + ), + 'expected' => new FilterResult([ + self::document(key: 'key3', objectField: ['fooFloat' => 3.3]), + ]) + ]; + yield 'Test object field lt filter and float values' => [ + 'input' => new Documents([ + self::document(key: 'key1', objectField: ['fooFloat' => 1.1]), + self::document(key: 'key2', objectField: ['fooFloat' => 2.2]), + self::document(key: 'key3', objectField: ['fooFloat' => 3.3]), + ]), + 'criteria' => new FilterCriteria( + filters: [ + new Lt(field: 'objectField.fooFloat', value: 2.2) + ] + ), + 'expected' => new FilterResult([ + self::document(key: 'key1', objectField: ['fooFloat' => 1.1]), + ]) + ]; + yield 'Test object field gte and lte filter and float values' => [ + 'input' => new Documents([ + self::document(key: 'key1', objectField: ['fooFloat' => 1.1]), + self::document(key: 'key2', objectField: ['fooFloat' => 2.2]), + self::document(key: 'key3', objectField: ['fooFloat' => 3.3]), + self::document(key: 'key4', objectField: ['fooFloat' => 4.4]), + ]), + 'criteria' => new FilterCriteria( + filters: [ + new Gte(field: 'objectField.fooFloat', value: 2.2), + new Lte(field: 'objectField.fooFloat', value: 3.3), + ] + ), + 'expected' => new FilterResult([ + self::document(key: 'key2', objectField: ['fooFloat' => 2.2]), + self::document(key: 'key3', objectField: ['fooFloat' => 3.3]), + ]) + ]; + + yield 'Test object field equals filter and date values' => [ + 'input' => new Documents([ + self::document(key: 'key1', objectField: ['fooDate' => '2021-01-01 00:00:00.000']), + self::document(key: 'key2', objectField: ['fooDate' => '2021-01-02 00:00:00.000']), + self::document(key: 'key3', objectField: ['fooDate' => '2021-01-03 00:00:00.000']), + ]), + 'criteria' => new FilterCriteria( + filters: [ + new Equals(field: 'objectField.fooDate', value: '2021-01-02') + ] + ), + 'expected' => new FilterResult([ + self::document(key: 'key2', objectField: ['fooDate' => '2021-01-02 00:00:00.000']), + ]) + ]; + yield 'Test object field equals any filter and date values' => [ + 'input' => new Documents([ + self::document(key: 'key1', objectField: ['fooDate' => '2021-01-01 00:00:00.000']), + self::document(key: 'key2', objectField: ['fooDate' => '2021-01-02 00:00:00.000']), + self::document(key: 'key3', objectField: ['fooDate' => '2021-01-03 00:00:00.000']), + ]), + 'criteria' => new FilterCriteria( + filters: [ + new Any(field: 'objectField.fooDate', value: ['2021-01-02', '2021-01-03']) + ] + ), + 'expected' => new FilterResult([ + self::document(key: 'key2', objectField: ['fooDate' => '2021-01-02 00:00:00.000']), + self::document(key: 'key3', objectField: ['fooDate' => '2021-01-03 00:00:00.000']), + ]) + ]; + yield 'Test object field not filter and date values' => [ + 'input' => new Documents([ + self::document(key: 'key1', objectField: ['fooDate' => '2021-01-01 00:00:00.000']), + self::document(key: 'key2', objectField: ['fooDate' => '2021-01-02 00:00:00.000']), + self::document(key: 'key3', objectField: ['fooDate' => '2021-01-03 00:00:00.000']), + ]), + 'criteria' => new FilterCriteria( + filters: [ + new Not(field: 'objectField.fooDate', value: '2021-01-02') + ] + ), + 'expected' => new FilterResult([ + self::document(key: 'key1', objectField: ['fooDate' => '2021-01-01 00:00:00.000']), + self::document(key: 'key3', objectField: ['fooDate' => '2021-01-03 00:00:00.000']), + ]) + ]; + yield 'Test object field not any filter and date values' => [ + 'input' => new Documents([ + self::document(key: 'key1', objectField: ['fooDate' => '2021-01-01 00:00:00.000']), + self::document(key: 'key2', objectField: ['fooDate' => '2021-01-02 00:00:00.000']), + self::document(key: 'key3', objectField: ['fooDate' => '2021-01-03 00:00:00.000']), + ]), + 'criteria' => new FilterCriteria( + filters: [ + new Neither(field: 'objectField.fooDate', value: ['2021-01-02', '2021-01-03']) + ] + ), + 'expected' => new FilterResult([ + self::document(key: 'key1', objectField: ['fooDate' => '2021-01-01 00:00:00.000']), + ]) + ]; + yield 'Test object field gte filter and date values' => [ + 'input' => new Documents([ + self::document(key: 'key1', objectField: ['fooDate' => '2021-01-01 00:00:00.000']), + self::document(key: 'key2', objectField: ['fooDate' => '2021-01-02 00:00:00.000']), + self::document(key: 'key3', objectField: ['fooDate' => '2021-01-03 00:00:00.000']), + ]), + 'criteria' => new FilterCriteria( + filters: [ + new Gte(field: 'objectField.fooDate', value: '2021-01-02') + ] + ), + 'expected' => new FilterResult([ + self::document(key: 'key2', objectField: ['fooDate' => '2021-01-02 00:00:00.000']), + self::document(key: 'key3', objectField: ['fooDate' => '2021-01-03 00:00:00.000']), + ]) + ]; + yield 'Test object field lte filter and date values' => [ + 'input' => new Documents([ + self::document(key: 'key1', objectField: ['fooDate' => '2021-01-01 00:00:00.000']), + self::document(key: 'key2', objectField: ['fooDate' => '2021-01-02 00:00:00.000']), + self::document(key: 'key3', objectField: ['fooDate' => '2021-01-03 00:00:00.000']), + ]), + 'criteria' => new FilterCriteria( + filters: [ + new Lte(field: 'objectField.fooDate', value: '2021-01-02') + ] + ), + 'expected' => new FilterResult([ + self::document(key: 'key1', objectField: ['fooDate' => '2021-01-01 00:00:00.000']), + self::document(key: 'key2', objectField: ['fooDate' => '2021-01-02 00:00:00.000']), + ]) + ]; + yield 'Test object field gt filter and date values' => [ + 'input' => new Documents([ + self::document(key: 'key1', objectField: ['fooDate' => '2021-01-01 00:00:00.000']), + self::document(key: 'key2', objectField: ['fooDate' => '2021-01-02 00:00:00.000']), + self::document(key: 'key3', objectField: ['fooDate' => '2021-01-03 00:00:00.000']), + ]), + 'criteria' => new FilterCriteria( + filters: [ + new Gt(field: 'objectField.fooDate', value: '2021-01-02') + ] + ), + 'expected' => new FilterResult([ + self::document(key: 'key3', objectField: ['fooDate' => '2021-01-03 00:00:00.000']), + ]) + ]; + yield 'Test object field lt filter and date values' => [ + 'input' => new Documents([ + self::document(key: 'key1', objectField: ['fooDate' => '2021-01-01 00:00:00.000']), + self::document(key: 'key2', objectField: ['fooDate' => '2021-01-02 00:00:00.000']), + self::document(key: 'key3', objectField: ['fooDate' => '2021-01-03 00:00:00.000']), + ]), + 'criteria' => new FilterCriteria( + filters: [ + new Lt(field: 'objectField.fooDate', value: '2021-01-02') + ] + ), + 'expected' => new FilterResult([ + self::document(key: 'key1', objectField: ['fooDate' => '2021-01-01 00:00:00.000']), + ]) + ]; + yield 'Test object field gte and lte filter and date values' => [ + 'input' => new Documents([ + self::document(key: 'key1', objectField: ['fooDate' => '2021-01-01 00:00:00.000']), + self::document(key: 'key2', objectField: ['fooDate' => '2021-01-02 00:00:00.000']), + self::document(key: 'key3', objectField: ['fooDate' => '2021-01-03 00:00:00.000']), + self::document(key: 'key4', objectField: ['fooDate' => '2021-01-04 00:00:00.000']), + ]), + 'criteria' => new FilterCriteria( + filters: [ + new Gte(field: 'objectField.fooDate', value: '2021-01-02'), + new Lte(field: 'objectField.fooDate', value: '2021-01-03'), + ] + ), + 'expected' => new FilterResult([ + self::document(key: 'key2', objectField: ['fooDate' => '2021-01-02 00:00:00.000']), + self::document(key: 'key3', objectField: ['fooDate' => '2021-01-03 00:00:00.000']), + ]) + ]; + + yield 'Test list field equals filter' => [ + 'input' => new Documents([ + self::document(key: 'key1', listField: ['foo', 'bar']), + self::document(key: 'key2', listField: ['foo', 'baz']), + self::document(key: 'key3', listField: ['foo', 'qux']), + ]), + 'criteria' => new FilterCriteria( + filters: [ + new Equals(field: 'listField', value: 'baz') + ] + ), + 'expected' => new FilterResult([ + self::document(key: 'key2', listField: ['foo', 'baz']), + ]) + ]; + yield 'Test list field equals any filter' => [ + 'input' => new Documents([ + self::document(key: 'key1', listField: ['foo', 'bar']), + self::document(key: 'key2', listField: ['foo', 'baz']), + self::document(key: 'key3', listField: ['foo', 'qux']), + ]), + 'criteria' => new FilterCriteria( + filters: [ + new Any(field: 'listField', value: ['baz', 'qux']) + ] + ), + 'expected' => new FilterResult([ + self::document(key: 'key2', listField: ['foo', 'baz']), + self::document(key: 'key3', listField: ['foo', 'qux']), + ]) + ]; + yield 'Test list field not filter' => [ + 'input' => new Documents([ + self::document(key: 'key1', listField: ['foo', 'bar']), + self::document(key: 'key2', listField: ['foo', 'baz']), + self::document(key: 'key3', listField: ['foo', 'qux']), + ]), + 'criteria' => new FilterCriteria( + filters: [ + new Not(field: 'listField', value: 'baz') + ] + ), + 'expected' => new FilterResult([ + self::document(key: 'key1', listField: ['foo', 'bar']), + self::document(key: 'key3', listField: ['foo', 'qux']), + ]) + ]; + yield 'Test list field not any filter' => [ + 'input' => new Documents([ + self::document(key: 'key1', listField: ['foo', 'bar']), + self::document(key: 'key2', listField: ['foo', 'baz']), + self::document(key: 'key3', listField: ['foo', 'qux']), + ]), + 'criteria' => new FilterCriteria( + filters: [ + new Neither(field: 'listField', value: ['baz', 'qux']) + ] + ), + 'expected' => new FilterResult([ + self::document(key: 'key1', listField: ['foo', 'bar']), + ]) + ]; + yield 'Test list field contains filter' => [ + 'input' => new Documents([ + self::document(key: 'key1', listField: ['foo', 'bar']), + self::document(key: 'key2', listField: ['foo', 'baz']), + self::document(key: 'key3', listField: ['foo', 'qux']), + ]), + 'criteria' => new FilterCriteria( + filters: [ + new Contains(field: 'listField', value: 'ba') + ] + ), + 'expected' => new FilterResult([ + self::document(key: 'key1', listField: ['foo', 'bar']), + self::document(key: 'key2', listField: ['foo', 'baz']), + ]) + ]; + yield 'Test list field null value equals filter' => [ + 'input' => new Documents([ + self::document(key: 'key1', listField: [1, 2]), + self::document(key: 'key2', listField: [1, 3]), + self::document(key: 'key3', listField: null), + ]), + 'criteria' => new FilterCriteria( + filters: [ + new Equals(field: 'listField', value: null) + ] + ), + 'expected' => new FilterResult([ + self::document(key: 'key3', listField: null) + ]) + ]; + yield 'Test list field null value not filter' => [ + 'input' => new Documents([ + self::document(key: 'key1', listField: [1, 2]), + self::document(key: 'key2', listField: [1, 3]), + self::document(key: 'key3', listField: null), + ]), + 'criteria' => new FilterCriteria( + filters: [ + new Not(field: 'listField', value: null) + ] + ), + 'expected' => new FilterResult([ + self::document('key1', listField: [1, 2]), + self::document('key2', listField: [1, 3]) + ]) + ]; + + yield 'Test list field equals filter and int values' => [ + 'input' => new Documents([ + self::document(key: 'key1', listField: [1, 2]), + self::document(key: 'key2', listField: [1, 3]), + self::document(key: 'key3', listField: [1, 4]), + ]), + 'criteria' => new FilterCriteria( + filters: [ + new Equals(field: 'listField', value: 3) + ] + ), + 'expected' => new FilterResult([ + self::document(key: 'key2', listField: [1, 3]), + ]) + ]; + yield 'Test list field equals any filter and int values' => [ + 'input' => new Documents([ + self::document(key: 'key1', listField: [1, 2]), + self::document(key: 'key2', listField: [1, 3]), + self::document(key: 'key3', listField: [1, 4]), + ]), + 'criteria' => new FilterCriteria( + filters: [ + new Any(field: 'listField', value: [3, 4]) + ] + ), + 'expected' => new FilterResult([ + self::document(key: 'key2', listField: [1, 3]), + self::document(key: 'key3', listField: [1, 4]), + ]) + ]; + yield 'Test list field not filter and int values' => [ + 'input' => new Documents([ + self::document(key: 'key1', listField: [1, 2]), + self::document(key: 'key2', listField: [1, 3]), + self::document(key: 'key3', listField: [1, 4]), + ]), + 'criteria' => new FilterCriteria( + filters: [ + new Not(field: 'listField', value: 3) + ] + ), + 'expected' => new FilterResult([ + self::document(key: 'key1', listField: [1, 2]), + self::document(key: 'key3', listField: [1, 4]), + ]) + ]; + yield 'Test list field not any filter and int values' => [ + 'input' => new Documents([ + self::document(key: 'key1', listField: [1, 2]), + self::document(key: 'key2', listField: [1, 3]), + self::document(key: 'key3', listField: [1, 4]), + ]), + 'criteria' => new FilterCriteria( + filters: [ + new Neither(field: 'listField', value: [3, 4]) + ] + ), + 'expected' => new FilterResult([ + self::document(key: 'key1', listField: [1, 2]), + ]) + ]; + yield 'Test list field equals filter and float values' => [ + 'input' => new Documents([ + self::document(key: 'key1', listField: [1.1, 2.2]), + self::document(key: 'key2', listField: [1.1, 3.3]), + self::document(key: 'key3', listField: [1.1, 4.4]), + ]), + 'criteria' => new FilterCriteria( + filters: [ + new Equals(field: 'listField', value: 3.3) + ] + ), + 'expected' => new FilterResult([ + self::document(key: 'key2', listField: [1.1, 3.3]), + ]) + ]; + yield 'Test list field equals any filter and float values' => [ + 'input' => new Documents([ + self::document(key: 'key1', listField: [1.1, 2.2]), + self::document(key: 'key2', listField: [1.1, 3.3]), + self::document(key: 'key3', listField: [1.1, 4.4]), + ]), + 'criteria' => new FilterCriteria( + filters: [ + new Any(field: 'listField', value: [3.3, 4.4]) + ] + ), + 'expected' => new FilterResult([ + self::document(key: 'key2', listField: [1.1, 3.3]), + self::document(key: 'key3', listField: [1.1, 4.4]), + ]) + ]; + yield 'Test list field not filter and float values' => [ + 'input' => new Documents([ + self::document(key: 'key1', listField: [1.1, 2.2]), + self::document(key: 'key2', listField: [1.1, 3.3]), + self::document(key: 'key3', listField: [1.1, 4.4]), + ]), + 'criteria' => new FilterCriteria( + filters: [ + new Not(field: 'listField', value: 3.3) + ] + ), + 'expected' => new FilterResult([ + self::document(key: 'key1', listField: [1.1, 2.2]), + self::document(key: 'key3', listField: [1.1, 4.4]), + ]) + ]; + yield 'Test list field not any filter and float values' => [ + 'input' => new Documents([ + self::document(key: 'key1', listField: [1.1, 2.2]), + self::document(key: 'key2', listField: [1.1, 3.3]), + self::document(key: 'key3', listField: [1.1, 4.4]), + ]), + 'criteria' => new FilterCriteria( + filters: [ + new Neither(field: 'listField', value: [3.3, 4.4]) + ] + ), + 'expected' => new FilterResult([ + self::document(key: 'key1', listField: [1.1, 2.2]), + ]) + ]; + yield 'Test list field equals filter and date values' => [ + 'input' => new Documents([ + self::document(key: 'key1', listField: ['2021-01-01', '2021-01-02']), + self::document(key: 'key2', listField: ['2021-01-01', '2021-01-03']), + self::document(key: 'key3', listField: ['2021-01-01', '2021-01-04']), + ]), + 'criteria' => new FilterCriteria( + filters: [ + new Equals(field: 'listField', value: '2021-01-03') + ] + ), + 'expected' => new FilterResult([ + self::document(key: 'key2', listField: ['2021-01-01', '2021-01-03']), + ]) + ]; + yield 'Test list field equals any filter and date values' => [ + 'input' => new Documents([ + self::document(key: 'key1', listField: ['2021-01-01', '2021-01-02']), + self::document(key: 'key2', listField: ['2021-01-01', '2021-01-03']), + self::document(key: 'key3', listField: ['2021-01-01', '2021-01-04']), + ]), + 'criteria' => new FilterCriteria( + filters: [ + new Any(field: 'listField', value: ['2021-01-03', '2021-01-04']) + ] + ), + 'expected' => new FilterResult([ + self::document(key: 'key2', listField: ['2021-01-01', '2021-01-03']), + self::document(key: 'key3', listField: ['2021-01-01', '2021-01-04']), + ]) + ]; + yield 'Test list field not filter and date values' => [ + 'input' => new Documents([ + self::document(key: 'key1', listField: ['2021-01-01', '2021-01-02']), + self::document(key: 'key2', listField: ['2021-01-01', '2021-01-03']), + self::document(key: 'key3', listField: ['2021-01-01', '2021-01-04']), + ]), + 'criteria' => new FilterCriteria( + filters: [ + new Not(field: 'listField', value: '2021-01-03') + ] + ), + 'expected' => new FilterResult([ + self::document(key: 'key1', listField: ['2021-01-01', '2021-01-02']), + self::document(key: 'key3', listField: ['2021-01-01', '2021-01-04']), + ]) + ]; + yield 'Test list field not any filter and date values' => [ + 'input' => new Documents([ + self::document(key: 'key1', listField: ['2021-01-01', '2021-01-02']), + self::document(key: 'key2', listField: ['2021-01-01', '2021-01-03']), + self::document(key: 'key3', listField: ['2021-01-01', '2021-01-04']), + ]), + 'criteria' => new FilterCriteria( + filters: [ + new Neither(field: 'listField', value: ['2021-01-03', '2021-01-04']) + ] + ), + 'expected' => new FilterResult([ + self::document(key: 'key1', listField: ['2021-01-01', '2021-01-02']), + ]) + ]; + yield 'Test list field contains filter and date values' => [ + 'input' => new Documents([ + self::document(key: 'key1', listField: ['2021-01-01', '2021-01-02']), + self::document(key: 'key2', listField: ['2021-01-01', '2021-01-03']), + self::document(key: 'key3', listField: ['2021-01-01', '2021-01-04']), + ]), + 'criteria' => new FilterCriteria( + filters: [ + new Contains(field: 'listField', value: '2021-01-02') + ] + ), + 'expected' => new FilterResult([ + self::document(key: 'key1', listField: ['2021-01-01', '2021-01-02']), + ]) + ]; + + yield 'Test list object field equals filter and string value' => [ + 'input' => new Documents([ + self::document(key: 'key1', objectListField: [['foo' => 'bar'], ['foo' => 'bar-2']]), + self::document(key: 'key2', objectListField: [['foo' => 'baz'], ['foo' => 'baz-2']]), + self::document(key: 'key3', objectListField: [['foo' => 'qux'], ['foo' => 'qux-2'], ['foo' => 'baz-2']]), + ]), + 'criteria' => new FilterCriteria( + filters: [ + new Equals(field: 'objectListField.foo', value: 'baz-2') + ] + ), + 'expected' => new FilterResult([ + self::document(key: 'key2', objectListField: [['foo' => 'baz'], ['foo' => 'baz-2']]), + self::document(key: 'key3', objectListField: [['foo' => 'qux'], ['foo' => 'qux-2'], ['foo' => 'baz-2']]), + ]) + ]; + yield 'Test list object field equals any filter and string value' => [ + 'input' => new Documents([ + self::document(key: 'key1', objectListField: [['foo' => 'bar'], ['foo' => 'bar-2']]), + self::document(key: 'key2', objectListField: [['foo' => 'baz'], ['foo' => 'baz-2']]), + self::document(key: 'key3', objectListField: [['foo' => 'qux'], ['foo' => 'qux-2'], ['foo' => 'baz-2']]), + ]), + 'criteria' => new FilterCriteria( + filters: [ + new Any(field: 'objectListField.foo', value: ['bar-2', 'qux-2']) + ] + ), + 'expected' => new FilterResult([ + self::document(key: 'key1', objectListField: [['foo' => 'bar'], ['foo' => 'bar-2']]), + self::document(key: 'key3', objectListField: [['foo' => 'qux'], ['foo' => 'qux-2'], ['foo' => 'baz-2']]), + ]) + ]; + yield 'Test list object field contains filter and string value' => [ + 'input' => new Documents([ + self::document(key: 'key1', objectListField: [['foo' => 'bar'], ['foo' => 'bar-2']]), + self::document(key: 'key2', objectListField: [['foo' => 'baz'], ['foo' => 'baz-2']]), + self::document(key: 'key3', objectListField: [['foo' => 'qux'], ['foo' => 'qux-2'], ['foo' => 'baz-2']]), + ]), + 'criteria' => new FilterCriteria( + filters: [ + new Contains(field: 'objectListField.foo', value: 'baz') + ] + ), + 'expected' => new FilterResult([ + self::document(key: 'key2', objectListField: [['foo' => 'baz'], ['foo' => 'baz-2']]), + self::document(key: 'key3', objectListField: [['foo' => 'qux'], ['foo' => 'qux-2'], ['foo' => 'baz-2']]), + ]) + ]; + yield 'Test list object field starts-with filter and string value' => [ + 'input' => new Documents([ + self::document(key: 'key1', objectListField: [['foo' => 'bar'], ['foo' => 'bar-2']]), + self::document(key: 'key2', objectListField: [['foo' => 'baz'], ['foo' => 'baz-2']]), + self::document(key: 'key3', objectListField: [['foo' => 'qux'], ['foo' => 'qux-2'], ['foo' => 'baz-2']]), + ]), + 'criteria' => new FilterCriteria( + filters: [ + new Prefix(field: 'objectListField.foo', value: 'qu') + ] + ), + 'expected' => new FilterResult([ + self::document(key: 'key3', objectListField: [['foo' => 'qux'], ['foo' => 'qux-2'], ['foo' => 'baz-2']]), + ]) + ]; + yield 'Test list object field ends-with filter and string value' => [ + 'input' => new Documents([ + self::document(key: 'key1', objectListField: [['foo' => 'bar'], ['foo' => 'bar-2']]), + self::document(key: 'key2', objectListField: [['foo' => 'baz'], ['foo' => 'baz-2']]), + self::document(key: 'key3', objectListField: [['foo' => 'qux'], ['foo' => 'qux-2'], ['foo' => 'baz-2']]), + ]), + 'criteria' => new FilterCriteria( + filters: [ + new Suffix(field: 'objectListField.foo', value: 'z-2') + ] + ), + 'expected' => new FilterResult([ + self::document(key: 'key2', objectListField: [['foo' => 'baz'], ['foo' => 'baz-2']]), + self::document(key: 'key3', objectListField: [['foo' => 'qux'], ['foo' => 'qux-2'], ['foo' => 'baz-2']]), + ]) + ]; + + yield 'Test list object field equals filter and int value' => [ + 'input' => new Documents([ + self::document(key: 'key1', objectListField: [['fooInt' => 1], ['fooInt' => 2]]), + self::document(key: 'key2', objectListField: [['fooInt' => 10], ['fooInt' => 2]]), + self::document(key: 'key3', objectListField: [['fooInt' => 20], ['fooInt' => 22], ['fooInt' => 24]]), + ]), + 'criteria' => new FilterCriteria( + filters: [ + new Equals(field: 'objectListField.fooInt', value: 2) + ] + ), + 'expected' => new FilterResult([ + self::document(key: 'key1', objectListField: [['fooInt' => 1], ['fooInt' => 2]]), + self::document(key: 'key2', objectListField: [['fooInt' => 10], ['fooInt' => 2]]), + ]) + ]; + yield 'Test list object field equals any filter and int value' => [ + 'input' => new Documents([ + self::document(key: 'key1', objectListField: [['fooInt' => 1], ['fooInt' => 2]]), + self::document(key: 'key2', objectListField: [['fooInt' => 10], ['fooInt' => 2]]), + self::document(key: 'key3', objectListField: [['fooInt' => 20], ['fooInt' => 22], ['fooInt' => 24]]), + ]), + 'criteria' => new FilterCriteria( + filters: [ + new Any(field: 'objectListField.fooInt', value: [10, 22]) + ] + ), + 'expected' => new FilterResult([ + self::document(key: 'key2', objectListField: [['fooInt' => 10], ['fooInt' => 2]]), + self::document(key: 'key3', objectListField: [['fooInt' => 20], ['fooInt' => 22], ['fooInt' => 24]]), + ]) + ]; + yield 'Test list object field gte filter and int value' => [ + 'input' => new Documents([ + self::document(key: 'key1', objectListField: [['fooInt' => 1], ['fooInt' => 2]]), + self::document(key: 'key2', objectListField: [['fooInt' => 10], ['fooInt' => 2]]), + self::document(key: 'key3', objectListField: [['fooInt' => 20], ['fooInt' => 22], ['fooInt' => 24]]), + ]), + 'criteria' => new FilterCriteria( + filters: [ + new Gte(field: 'objectListField.fooInt', value: 22) + ] + ), + 'expected' => new FilterResult([ + self::document(key: 'key3', objectListField: [['fooInt' => 20], ['fooInt' => 22], ['fooInt' => 24]]), + ]) + ]; + yield 'Test list object field lte filter and int value' => [ + 'input' => new Documents([ + self::document(key: 'key1', objectListField: [['fooInt' => 1], ['fooInt' => 2]]), + self::document(key: 'key2', objectListField: [['fooInt' => 10], ['fooInt' => 2]]), + self::document(key: 'key3', objectListField: [['fooInt' => 20], ['fooInt' => 22], ['fooInt' => 24]]), + ]), + 'criteria' => new FilterCriteria( + filters: [ + new Lte(field: 'objectListField.fooInt', value: 2) + ] + ), + 'expected' => new FilterResult([ + self::document(key: 'key1', objectListField: [['fooInt' => 1], ['fooInt' => 2]]), + self::document(key: 'key2', objectListField: [['fooInt' => 10], ['fooInt' => 2]]), + ]) + ]; + yield 'Test list object field gt filter and int value' => [ + 'input' => new Documents([ + self::document(key: 'key1', objectListField: [['fooInt' => 1], ['fooInt' => 2]]), + self::document(key: 'key2', objectListField: [['fooInt' => 10], ['fooInt' => 2]]), + self::document(key: 'key3', objectListField: [['fooInt' => 20], ['fooInt' => 22], ['fooInt' => 24]]), + ]), + 'criteria' => new FilterCriteria( + filters: [ + new Gt(field: 'objectListField.fooInt', value: 2) + ] + ), + 'expected' => new FilterResult([ + self::document(key: 'key2', objectListField: [['fooInt' => 10], ['fooInt' => 2]]), + self::document(key: 'key3', objectListField: [['fooInt' => 20], ['fooInt' => 22], ['fooInt' => 24]]), + ]) + ]; + yield 'Test list object field lt filter and int value' => [ + 'input' => new Documents([ + self::document(key: 'key1', objectListField: [['fooInt' => 1], ['fooInt' => 2]]), + self::document(key: 'key2', objectListField: [['fooInt' => 10], ['fooInt' => 2]]), + self::document(key: 'key3', objectListField: [['fooInt' => 20], ['fooInt' => 22], ['fooInt' => 24]]), + ]), + 'criteria' => new FilterCriteria( + filters: [ + new Lt(field: 'objectListField.fooInt', value: 20) + ] + ), + 'expected' => new FilterResult([ + self::document(key: 'key1', objectListField: [['fooInt' => 1], ['fooInt' => 2]]), + self::document(key: 'key2', objectListField: [['fooInt' => 10], ['fooInt' => 2]]), + ]) + ]; + + yield 'Test list object field equals filter and float value' => [ + 'input' => new Documents([ + self::document(key: 'key1', objectListField: [['fooFloat' => 1.1], ['fooFloat' => 2.2]]), + self::document(key: 'key2', objectListField: [['fooFloat' => 10.1], ['fooFloat' => 2.2]]), + self::document(key: 'key3', objectListField: [['fooFloat' => 20.1], ['fooFloat' => 22.2], ['fooFloat' => 24.2]]), + ]), + 'criteria' => new FilterCriteria( + filters: [ + new Equals(field: 'objectListField.fooFloat', value: 2.2) + ] + ), + 'expected' => new FilterResult([ + self::document(key: 'key1', objectListField: [['fooFloat' => 1.1], ['fooFloat' => 2.2]]), + self::document(key: 'key2', objectListField: [['fooFloat' => 10.1], ['fooFloat' => 2.2]]), + ]) + ]; + yield 'Test list object field equals any filter and float value' => [ + 'input' => new Documents([ + self::document(key: 'key1', objectListField: [['fooFloat' => 1.1], ['fooFloat' => 2.2]]), + self::document(key: 'key2', objectListField: [['fooFloat' => 10.1], ['fooFloat' => 2.2]]), + self::document(key: 'key3', objectListField: [['fooFloat' => 20.1], ['fooFloat' => 22.2], ['fooFloat' => 24.2]]), + ]), + + 'criteria' => new FilterCriteria( + filters: [ + new Any(field: 'objectListField.fooFloat', value: [10.1, 22.2]) + ] + ), + 'expected' => new FilterResult([ + self::document(key: 'key2', objectListField: [['fooFloat' => 10.1], ['fooFloat' => 2.2]]), + self::document(key: 'key3', objectListField: [['fooFloat' => 20.1], ['fooFloat' => 22.2], ['fooFloat' => 24.2]]), + ]) + ]; + yield 'Test list object field gte filter and float value' => [ + 'input' => new Documents([ + self::document(key: 'key1', objectListField: [['fooFloat' => 1.1], ['fooFloat' => 2.2]]), + self::document(key: 'key2', objectListField: [['fooFloat' => 10.1], ['fooFloat' => 2.2]]), + self::document(key: 'key3', objectListField: [['fooFloat' => 20.1], ['fooFloat' => 22.2], ['fooFloat' => 24.2]]), + ]), + 'criteria' => new FilterCriteria( + filters: [ + new Gte(field: 'objectListField.fooFloat', value: 22.2) + ] + ), + 'expected' => new FilterResult([ + self::document(key: 'key3', objectListField: [['fooFloat' => 20.1], ['fooFloat' => 22.2], ['fooFloat' => 24.2]]), + ]) + ]; + + yield 'Test list object field lte filter and float value' => [ + 'input' => new Documents([ + self::document(key: 'key1', objectListField: [['fooFloat' => 1.1], ['fooFloat' => 2.2]]), + self::document(key: 'key2', objectListField: [['fooFloat' => 10.1], ['fooFloat' => 2.2]]), + self::document(key: 'key3', objectListField: [['fooFloat' => 20.1], ['fooFloat' => 22.2], ['fooFloat' => 24.2]]), + ]), + 'criteria' => new FilterCriteria( + filters: [ + new Lte(field: 'objectListField.fooFloat', value: 2.2) + ] + ), + 'expected' => new FilterResult([ + self::document(key: 'key1', objectListField: [['fooFloat' => 1.1], ['fooFloat' => 2.2]]), + self::document(key: 'key2', objectListField: [['fooFloat' => 10.1], ['fooFloat' => 2.2]]), + ]) + ]; + yield 'Test list object field gt filter and float value' => [ + 'input' => new Documents([ + self::document(key: 'key1', objectListField: [['fooFloat' => 1.1], ['fooFloat' => 2.2]]), + self::document(key: 'key2', objectListField: [['fooFloat' => 10.1], ['fooFloat' => 2.2]]), + self::document(key: 'key3', objectListField: [['fooFloat' => 20.1], ['fooFloat' => 22.2], ['fooFloat' => 24.2]]), + ]), + 'criteria' => new FilterCriteria( + filters: [ + new Gt(field: 'objectListField.fooFloat', value: 2.2) + ] + ), + 'expected' => new FilterResult([ + self::document(key: 'key2', objectListField: [['fooFloat' => 10.1], ['fooFloat' => 2.2]]), + self::document(key: 'key3', objectListField: [['fooFloat' => 20.1], ['fooFloat' => 22.2], ['fooFloat' => 24.2]]), + ]) + ]; + yield 'Test list object field lt filter and float value' => [ + 'input' => new Documents([ + self::document(key: 'key1', objectListField: [['fooFloat' => 1.1], ['fooFloat' => 2.2]]), + self::document(key: 'key2', objectListField: [['fooFloat' => 10.1], ['fooFloat' => 2.2]]), + self::document(key: 'key3', objectListField: [['fooFloat' => 20.1], ['fooFloat' => 22.2], ['fooFloat' => 24.2]]), + ]), + 'criteria' => new FilterCriteria( + filters: [ + new Lt(field: 'objectListField.fooFloat', value: 20.1) + ] + ), + 'expected' => new FilterResult([ + self::document(key: 'key1', objectListField: [['fooFloat' => 1.1], ['fooFloat' => 2.2]]), + self::document(key: 'key2', objectListField: [['fooFloat' => 10.1], ['fooFloat' => 2.2]]), + ]) + ]; + + yield 'Test list object field equals filter and date value' => [ + 'input' => new Documents([ + self::document(key: 'key1', objectListField: [['fooDate' => '2021-01-01 00:00:00.000'], ['fooDate' => '2021-01-02 00:00:00.000']]), + self::document(key: 'key2', objectListField: [['fooDate' => '2021-01-10 00:00:00.000'], ['fooDate' => '2021-01-02 00:00:00.000']]), + self::document(key: 'key3', objectListField: [['fooDate' => '2021-01-20 00:00:00.000'], ['fooDate' => '2021-01-22 00:00:00.000'], ['fooDate' => '2021-01-24 00:00:00.000']]), + ]), + 'criteria' => new FilterCriteria( + filters: [ + new Equals(field: 'objectListField.fooDate', value: '2021-01-02 00:00:00.000') + ] + ), + 'expected' => new FilterResult([ + self::document(key: 'key1', objectListField: [['fooDate' => '2021-01-01 00:00:00.000'], ['fooDate' => '2021-01-02 00:00:00.000']]), + self::document(key: 'key2', objectListField: [['fooDate' => '2021-01-10 00:00:00.000'], ['fooDate' => '2021-01-02 00:00:00.000']]), + ]) + ]; + yield 'Test list object field equals any filter and date value' => [ + 'input' => new Documents([ + self::document(key: 'key1', objectListField: [['fooDate' => '2021-01-01 00:00:00.000'], ['fooDate' => '2021-01-02 00:00:00.000']]), + self::document(key: 'key2', objectListField: [['fooDate' => '2021-01-10 00:00:00.000'], ['fooDate' => '2021-01-02 00:00:00.000']]), + self::document(key: 'key3', objectListField: [['fooDate' => '2021-01-20 00:00:00.000'], ['fooDate' => '2021-01-22 00:00:00.000'], ['fooDate' => '2021-01-24 00:00:00.000']]), + ]), + 'criteria' => new FilterCriteria( + filters: [ + new Any(field: 'objectListField.fooDate', value: ['2021-01-10 00:00:00.000', '2021-01-22 00:00:00.000']) + ] + ), + 'expected' => new FilterResult([ + self::document(key: 'key2', objectListField: [['fooDate' => '2021-01-10 00:00:00.000'], ['fooDate' => '2021-01-02 00:00:00.000']]), + self::document(key: 'key3', objectListField: [['fooDate' => '2021-01-20 00:00:00.000'], ['fooDate' => '2021-01-22 00:00:00.000'], ['fooDate' => '2021-01-24 00:00:00.000']]), + ]) + ]; + + yield 'Test list object field gte filter and date value' => [ + 'input' => new Documents([ + self::document(key: 'key1', objectListField: [['fooDate' => '2021-01-01 00:00:00.000'], ['fooDate' => '2021-01-02 00:00:00.000']]), + self::document(key: 'key2', objectListField: [['fooDate' => '2021-01-10 00:00:00.000'], ['fooDate' => '2021-01-02 00:00:00.000']]), + self::document(key: 'key3', objectListField: [['fooDate' => '2021-01-20 00:00:00.000'], ['fooDate' => '2021-01-22 00:00:00.000'], ['fooDate' => '2021-01-24 00:00:00.000']]), + ]), + 'criteria' => new FilterCriteria( + filters: [ + new Gte(field: 'objectListField.fooDate', value: '2021-01-22 00:00:00.000') + ] + ), + 'expected' => new FilterResult([ + self::document(key: 'key3', objectListField: [['fooDate' => '2021-01-20 00:00:00.000'], ['fooDate' => '2021-01-22 00:00:00.000'], ['fooDate' => '2021-01-24 00:00:00.000']]), + ]) + ]; + yield 'Test list object field lte filter and date value' => [ + 'input' => new Documents([ + self::document(key: 'key1', objectListField: [['fooDate' => '2021-01-01 00:00:00.000'], ['fooDate' => '2021-01-02 00:00:00.000']]), + self::document(key: 'key2', objectListField: [['fooDate' => '2021-01-10 00:00:00.000'], ['fooDate' => '2021-01-02 00:00:00.000']]), + self::document(key: 'key3', objectListField: [['fooDate' => '2021-01-20 00:00:00.000'], ['fooDate' => '2021-01-22 00:00:00.000'], ['fooDate' => '2021-01-24 00:00:00.000']]), + ]), + 'criteria' => new FilterCriteria( + filters: [ + new Lte(field: 'objectListField.fooDate', value: '2021-01-02 00:00:00.000') + ] + ), + 'expected' => new FilterResult([ + self::document(key: 'key1', objectListField: [['fooDate' => '2021-01-01 00:00:00.000'], ['fooDate' => '2021-01-02 00:00:00.000']]), + self::document(key: 'key2', objectListField: [['fooDate' => '2021-01-10 00:00:00.000'], ['fooDate' => '2021-01-02 00:00:00.000']]), + ]) + ]; + yield 'Test list object field gt filter and date value' => [ + 'input' => new Documents([ + self::document(key: 'key1', objectListField: [['fooDate' => '2021-01-01 00:00:00.000'], ['fooDate' => '2021-01-02 00:00:00.000']]), + self::document(key: 'key2', objectListField: [['fooDate' => '2021-01-10 00:00:00.000'], ['fooDate' => '2021-01-02 00:00:00.000']]), + self::document(key: 'key3', objectListField: [['fooDate' => '2021-01-20 00:00:00.000'], ['fooDate' => '2021-01-22 00:00:00.000'], ['fooDate' => '2021-01-24 00:00:00.000']]), + ]), + 'criteria' => new FilterCriteria( + filters: [ + new Gt(field: 'objectListField.fooDate', value: '2021-01-02 00:00:00.000') + ] + ), + 'expected' => new FilterResult([ + self::document(key: 'key2', objectListField: [['fooDate' => '2021-01-10 00:00:00.000'], ['fooDate' => '2021-01-02 00:00:00.000']]), + self::document(key: 'key3', objectListField: [['fooDate' => '2021-01-20 00:00:00.000'], ['fooDate' => '2021-01-22 00:00:00.000'], ['fooDate' => '2021-01-24 00:00:00.000']]), + ]) + ]; + yield 'Test list object field lt filter and date value' => [ + 'input' => new Documents([ + self::document(key: 'key1', objectListField: [['fooDate' => '2021-01-01 00:00:00.000'], ['fooDate' => '2021-01-02 00:00:00.000']]), + self::document(key: 'key2', objectListField: [['fooDate' => '2021-01-10 00:00:00.000'], ['fooDate' => '2021-01-02 00:00:00.000']]), + self::document(key: 'key3', objectListField: [['fooDate' => '2021-01-20 00:00:00.000'], ['fooDate' => '2021-01-22 00:00:00.000'], ['fooDate' => '2021-01-24 00:00:00.000']]), + ]), + 'criteria' => new FilterCriteria( + filters: [ + new Lt(field: 'objectListField.fooDate', value: '2021-01-20 00:00:00.000') + ] + ), + 'expected' => new FilterResult([ + self::document(key: 'key1', objectListField: [['fooDate' => '2021-01-01 00:00:00.000'], ['fooDate' => '2021-01-02 00:00:00.000']]), + self::document(key: 'key2', objectListField: [['fooDate' => '2021-01-10 00:00:00.000'], ['fooDate' => '2021-01-02 00:00:00.000']]), + ]) + ]; + + yield 'Test nested object' => [ + 'input' => new Documents([ + self::document(key: 'key1', objectField: ['fooObj' => ['bar' => 'baz']]), + self::document(key: 'key2', objectField: ['fooObj' => ['bar' => 'qux']]), + self::document(key: 'key3', objectField: ['fooObj' => ['bar' => 'quux']]), + ]), + 'criteria' => new FilterCriteria( + filters: [ + new Equals(field: 'objectField.fooObj.bar', value: 'qux') + ] + ), + 'expected' => new FilterResult([ + self::document(key: 'key2', objectField: ['fooObj' => ['bar' => 'qux']]), + ]) + ]; + + yield 'Test translated string field equals filter' => [ + 'input' => new Documents([ + self::document(key: 'key1', translatedString: ['en' => 'bar', 'de' => 'foo']), + self::document(key: 'key2', translatedString: ['en' => 'foo']), + self::document(key: 'key3', translatedString: ['en' => null, 'de' => 'foo']), + self::document(key: 'key4', translatedString: ['de' => 'foo']), + ]), + 'criteria' => new FilterCriteria( + filters: [ + new Equals(field: 'translatedString', value: 'foo') + ] + ), + 'expected' => new FilterResult([ + self::document(key: 'key2', translatedString: ['en' => 'foo']), + self::document(key: 'key3', translatedString: ['en' => null, 'de' => 'foo']), + self::document(key: 'key4', translatedString: ['de' => 'foo']), + ]) + ]; + yield 'Test translated string field equals-any filter' => [ + 'input' => new Documents([ + self::document(key: 'key1', translatedString: ['en' => 'bar', 'de' => 'foo']), + self::document(key: 'key2', translatedString: ['en' => 'foo']), + self::document(key: 'key3', translatedString: ['en' => null, 'de' => 'foo']), + self::document(key: 'key4', translatedString: ['en' => 'baz', 'de' => 'foo']), + ]), + 'criteria' => new FilterCriteria( + filters: [ + new Any(field: 'translatedString', value: ['foo', 'bar']) + ] + ), + 'expected' => new FilterResult([ + self::document(key: 'key1', translatedString: ['en' => 'bar', 'de' => 'foo']), + self::document(key: 'key2', translatedString: ['en' => 'foo']), + self::document(key: 'key3', translatedString: ['en' => null, 'de' => 'foo']), + ]) + ]; + yield 'Test translated string field not filter' => [ + 'input' => new Documents([ + self::document(key: 'key1', translatedString: ['en' => 'bar', 'de' => 'foo']), + self::document(key: 'key2', translatedString: ['en' => 'foo']), + self::document(key: 'key3', translatedString: ['en' => null, 'de' => 'foo']), + self::document(key: 'key4', translatedString: ['en' => 'baz', 'de' => 'foo']), + ]), + 'criteria' => new FilterCriteria( + filters: [ + new Not(field: 'translatedString', value: 'foo') + ] + ), + 'expected' => new FilterResult([ + self::document(key: 'key1', translatedString: ['en' => 'bar', 'de' => 'foo']), + self::document(key: 'key4', translatedString: ['en' => 'baz', 'de' => 'foo']), + ]) + ]; + yield 'Test translated string field not any filter' => [ + 'input' => new Documents([ + self::document(key: 'key1', translatedString: ['en' => 'bar', 'de' => 'foo']), + self::document(key: 'key2', translatedString: ['en' => 'foo']), + self::document(key: 'key3', translatedString: ['en' => null, 'de' => 'foo']), + self::document(key: 'key4', translatedString: ['en' => 'baz', 'de' => 'foo']), + ]), + 'criteria' => new FilterCriteria( + filters: [ + new Neither(field: 'translatedString', value: ['foo', 'bar']) + ] + ), + 'expected' => new FilterResult([ + self::document(key: 'key4', translatedString: ['en' => 'baz', 'de' => 'foo']), + ]) + ]; + yield 'Test translated string field contains filter' => [ + 'input' => new Documents([ + self::document(key: 'key1', translatedString: ['en' => 'bar', 'de' => 'foo']), + self::document(key: 'key2', translatedString: ['en' => 'boo']), + self::document(key: 'key3', translatedString: ['en' => null, 'de' => 'foo']), + self::document(key: 'key4', translatedString: ['en' => 'foo', 'de' => 'bar']), + ]), + 'criteria' => new FilterCriteria( + filters: [ + new Contains(field: 'translatedString', value: 'oo') + ] + ), + 'expected' => new FilterResult([ + self::document(key: 'key2', translatedString: ['en' => 'boo']), + self::document(key: 'key3', translatedString: ['en' => null, 'de' => 'foo']), + self::document(key: 'key4', translatedString: ['en' => 'foo', 'de' => 'bar']), + ]) + ]; + yield 'Test translated string field starts-with filter' => [ + 'input' => new Documents([ + self::document(key: 'key1', translatedString: ['en' => 'bar', 'de' => 'foo']), + self::document(key: 'key2', translatedString: ['en' => 'foo']), + self::document(key: 'key3', translatedString: ['en' => null, 'de' => 'foo']), + self::document(key: 'key4', translatedString: ['en' => 'baz', 'de' => 'foo']), + ]), + 'criteria' => new FilterCriteria( + filters: [ + new Prefix(field: 'translatedString', value: 'foo') + ] + ), + 'expected' => new FilterResult([ + self::document(key: 'key2', translatedString: ['en' => 'foo']), + self::document(key: 'key3', translatedString: ['en' => null, 'de' => 'foo']), + ]) + ]; + yield 'Test translated string field ends-with filter' => [ + 'input' => new Documents([ + self::document(key: 'key1', translatedString: ['en' => 'bar', 'de' => 'foo']), + self::document(key: 'key2', translatedString: ['en' => 'foo']), + self::document(key: 'key3', translatedString: ['en' => null, 'de' => 'foo']), + self::document(key: 'key4', translatedString: ['en' => 'ob', 'de' => 'foo']), + ]), + 'criteria' => new FilterCriteria( + filters: [ + new Suffix(field: 'translatedString', value: 'o') + ] + ), + 'expected' => new FilterResult([ + self::document(key: 'key2', translatedString: ['en' => 'foo']), + self::document(key: 'key3', translatedString: ['en' => null, 'de' => 'foo']), + ]) + ]; + yield 'Test translated string field gte filter' => [ + 'input' => new Documents([ + self::document(key: 'key1', translatedString: ['en' => 'a', 'de' => 'b']), + self::document(key: 'key2', translatedString: ['en' => 'c']), + self::document(key: 'key3', translatedString: ['en' => null, 'de' => 'b']), + self::document(key: 'key4', translatedString: ['en' => 'b', 'de' => 'a']), + ]), + 'criteria' => new FilterCriteria( + filters: [ + new Gte(field: 'translatedString', value: 'b') + ] + ), + 'expected' => new FilterResult([ + self::document(key: 'key2', translatedString: ['en' => 'c']), + self::document(key: 'key3', translatedString: ['en' => null, 'de' => 'b']), + self::document(key: 'key4', translatedString: ['en' => 'b', 'de' => 'a']), + ]) + ]; + yield 'Test translated string field gt filter' => [ + 'input' => new Documents([ + self::document(key: 'key1', translatedString: ['en' => 'a', 'de' => 'b']), + self::document(key: 'key2', translatedString: ['en' => 'c']), + self::document(key: 'key3', translatedString: ['en' => null, 'de' => 'b']), + self::document(key: 'key4', translatedString: ['en' => 'b', 'de' => 'a']), + ]), + 'criteria' => new FilterCriteria( + filters: [ + new Gt(field: 'translatedString', value: 'b') + ] + ), + 'expected' => new FilterResult([ + self::document(key: 'key2', translatedString: ['en' => 'c']), + ]) + ]; + yield 'Test translated string field lte filter' => [ + 'input' => new Documents([ + self::document(key: 'key1', translatedString: ['en' => 'a', 'de' => 'b']), + self::document(key: 'key2', translatedString: ['en' => 'c']), + self::document(key: 'key3', translatedString: ['en' => null, 'de' => 'b']), + self::document(key: 'key4', translatedString: ['en' => 'b', 'de' => 'a']), + ]), + 'criteria' => new FilterCriteria( + filters: [ + new Lte(field: 'translatedString', value: 'b') + ] + ), + 'expected' => new FilterResult([ + self::document(key: 'key1', translatedString: ['en' => 'a', 'de' => 'b']), + self::document(key: 'key3', translatedString: ['en' => null, 'de' => 'b']), + self::document(key: 'key4', translatedString: ['en' => 'b', 'de' => 'a']), + ]) + ]; + yield 'Test translated string field lt filter' => [ + 'input' => new Documents([ + self::document(key: 'key1', translatedString: ['en' => 'a', 'de' => 'b']), + self::document(key: 'key2', translatedString: ['en' => 'c']), + self::document(key: 'key3', translatedString: ['en' => null, 'de' => 'b']), + self::document(key: 'key4', translatedString: ['en' => 'b', 'de' => 'a']), + ]), + 'criteria' => new FilterCriteria( + filters: [ + new Lt(field: 'translatedString', value: 'b') + ] + ), + 'expected' => new FilterResult([ + self::document(key: 'key1', translatedString: ['en' => 'a', 'de' => 'b']), + ]) + ]; + yield 'Test translated string field equals filter and empty string' => [ + 'input' => new Documents([ + self::document(key: 'key1', translatedString: ['en' => 'bar', 'de' => 'foo']), + self::document(key: 'key2', translatedString: ['en' => 'foo']), + self::document(key: 'key3', translatedString: ['en' => '', 'de' => 'foo']), + self::document(key: 'key4', translatedString: ['de' => 'foo']), + ]), + 'criteria' => new FilterCriteria( + filters: [ + new Equals(field: 'translatedString', value: 'foo') + ] + ), + 'expected' => new FilterResult([ + self::document(key: 'key2', translatedString: ['en' => 'foo']), + self::document(key: 'key4', translatedString: ['de' => 'foo']), + ]) + ]; + + yield 'Test translated int field equals filter' => [ + 'input' => new Documents([ + self::document(key: 'key1', translatedInt: ['en' => 1, 'de' => 2]), + self::document(key: 'key2', translatedInt: ['en' => 2]), + self::document(key: 'key3', translatedInt: ['en' => null, 'de' => 2]), + self::document(key: 'key4', translatedInt: ['de' => 2]), + ]), + 'criteria' => new FilterCriteria( + filters: [ + new Equals(field: 'translatedInt', value: 2) + ] + ), + 'expected' => new FilterResult([ + self::document(key: 'key2', translatedInt: ['en' => 2]), + self::document(key: 'key3', translatedInt: ['en' => null, 'de' => 2]), + self::document(key: 'key4', translatedInt: ['de' => 2]), + ]) + ]; + yield 'Test translated int field equals-any filter' => [ + 'input' => new Documents([ + self::document(key: 'key1', translatedInt: ['en' => 1, 'de' => 2]), + self::document(key: 'key2', translatedInt: ['en' => 2]), + self::document(key: 'key3', translatedInt: ['en' => null, 'de' => 3]), + self::document(key: 'key4', translatedInt: ['de' => 4]), + ]), + 'criteria' => new FilterCriteria( + filters: [ + new Any(field: 'translatedInt', value: [2, 3, 4]) + ] + ), + 'expected' => new FilterResult([ + self::document(key: 'key2', translatedInt: ['en' => 2]), + self::document(key: 'key3', translatedInt: ['en' => null, 'de' => 3]), + self::document(key: 'key4', translatedInt: ['de' => 4]), + ]) + ]; + yield 'Test translated int field not filter' => [ + 'input' => new Documents([ + self::document(key: 'key1', translatedInt: ['en' => 1, 'de' => 2]), + self::document(key: 'key2', translatedInt: ['en' => 2]), + self::document(key: 'key3', translatedInt: ['en' => null, 'de' => 2]), + self::document(key: 'key4', translatedInt: ['de' => 2]), + ]), + 'criteria' => new FilterCriteria( + filters: [ + new Not(field: 'translatedInt', value: 2) + ] + ), + 'expected' => new FilterResult([ + self::document(key: 'key1', translatedInt: ['en' => 1, 'de' => 2]), + ]) + ]; + yield 'Test translated int field not-any filter' => [ + 'input' => new Documents([ + self::document(key: 'key1', translatedInt: ['en' => 1, 'de' => 2]), + self::document(key: 'key2', translatedInt: ['en' => 2]), + self::document(key: 'key3', translatedInt: ['en' => null, 'de' => 2]), + self::document(key: 'key4', translatedInt: ['de' => 2]), + ]), + 'criteria' => new FilterCriteria( + filters: [ + new Neither(field: 'translatedInt', value: [1, 2]) + ] + ), + 'expected' => new FilterResult([]) + ]; + yield 'Test translated int field gte filter' => [ + 'input' => new Documents([ + self::document(key: 'key1', translatedInt: ['en' => 1, 'de' => 2]), + self::document(key: 'key2', translatedInt: ['en' => 3]), + self::document(key: 'key3', translatedInt: ['en' => null, 'de' => 2]), + self::document(key: 'key4', translatedInt: ['de' => 1]), + ]), + 'criteria' => new FilterCriteria( + filters: [ + new Gte(field: 'translatedInt', value: 2) + ] + ), + 'expected' => new FilterResult([ + self::document(key: 'key2', translatedInt: ['en' => 3]), + self::document(key: 'key3', translatedInt: ['en' => null, 'de' => 2]), + ]) + ]; + yield 'Test translated int field gt filter' => [ + 'input' => new Documents([ + self::document(key: 'key1', translatedInt: ['en' => 1, 'de' => 2]), + self::document(key: 'key2', translatedInt: ['en' => 3]), + self::document(key: 'key3', translatedInt: ['en' => null, 'de' => 2]), + self::document(key: 'key4', translatedInt: ['de' => 1]), + ]), + 'criteria' => new FilterCriteria( + filters: [ + new Gt(field: 'translatedInt', value: 2) + ] + ), + 'expected' => new FilterResult([ + self::document(key: 'key2', translatedInt: ['en' => 3]), + ]) + ]; + yield 'Test translated int field lte filter' => [ + 'input' => new Documents([ + self::document(key: 'key1', translatedInt: ['en' => 1, 'de' => 2]), + self::document(key: 'key2', translatedInt: ['en' => 3]), + self::document(key: 'key3', translatedInt: ['en' => null, 'de' => 2]), + self::document(key: 'key4', translatedInt: ['de' => 1]), + ]), + 'criteria' => new FilterCriteria( + filters: [ + new Lte(field: 'translatedInt', value: 2) + ] + ), + 'expected' => new FilterResult([ + self::document(key: 'key1', translatedInt: ['en' => 1, 'de' => 2]), + self::document(key: 'key3', translatedInt: ['en' => null, 'de' => 2]), + self::document(key: 'key4', translatedInt: ['de' => 1]), + ]) + ]; + yield 'Test translated int field lt filter' => [ + 'input' => new Documents([ + self::document(key: 'key1', translatedInt: ['en' => 1, 'de' => 2]), + self::document(key: 'key2', translatedInt: ['en' => 3]), + self::document(key: 'key3', translatedInt: ['en' => null, 'de' => 2]), + self::document(key: 'key4', translatedInt: ['de' => 1]), + ]), + 'criteria' => new FilterCriteria( + filters: [ + new Lt(field: 'translatedInt', value: 2) + ] + ), + 'expected' => new FilterResult([ + self::document(key: 'key1', translatedInt: ['en' => 1, 'de' => 2]), + self::document(key: 'key4', translatedInt: ['de' => 1]), + ]) + ]; + + yield 'Test translated float field equals filter' => [ + 'input' => new Documents([ + self::document(key: 'key1', translatedFloat: ['en' => 1.1, 'de' => 2.2]), + self::document(key: 'key2', translatedFloat: ['en' => 2.2]), + self::document(key: 'key3', translatedFloat: ['en' => null, 'de' => 2.2]), + self::document(key: 'key4', translatedFloat: ['de' => 2.2]), + ]), + 'criteria' => new FilterCriteria( + filters: [ + new Equals(field: 'translatedFloat', value: 2.2) + ] + ), + 'expected' => new FilterResult([ + self::document(key: 'key2', translatedFloat: ['en' => 2.2]), + self::document(key: 'key3', translatedFloat: ['en' => null, 'de' => 2.2]), + self::document(key: 'key4', translatedFloat: ['de' => 2.2]), + ]) + ]; + yield 'Test translated float field equals-any filter' => [ + 'input' => new Documents([ + self::document(key: 'key1', translatedFloat: ['en' => 1.1, 'de' => 2.2]), + self::document(key: 'key2', translatedFloat: ['en' => 2.2]), + self::document(key: 'key3', translatedFloat: ['en' => null, 'de' => 3.3]), + self::document(key: 'key4', translatedFloat: ['de' => 4.4]), + ]), + 'criteria' => new FilterCriteria( + filters: [ + new Any(field: 'translatedFloat', value: [2.2, 3.3, 4.4]) + ] + ), + 'expected' => new FilterResult([ + self::document(key: 'key2', translatedFloat: ['en' => 2.2]), + self::document(key: 'key3', translatedFloat: ['en' => null, 'de' => 3.3]), + self::document(key: 'key4', translatedFloat: ['de' => 4.4]), + ]) + ]; + yield 'Test translated float field not filter' => [ + 'input' => new Documents([ + self::document(key: 'key1', translatedFloat: ['en' => 1.1, 'de' => 2.2]), + self::document(key: 'key2', translatedFloat: ['en' => 2.2]), + self::document(key: 'key3', translatedFloat: ['en' => null, 'de' => 2.2]), + self::document(key: 'key4', translatedFloat: ['de' => 2.2]), + ]), + 'criteria' => new FilterCriteria( + filters: [ + new Not(field: 'translatedFloat', value: 2.2) + ] + ), + 'expected' => new FilterResult([ + self::document(key: 'key1', translatedFloat: ['en' => 1.1, 'de' => 2.2]), + ]) + ]; + yield 'Test translated float field not-any filter' => [ + 'input' => new Documents([ + self::document(key: 'key1', translatedFloat: ['en' => 1.1, 'de' => 2.2]), + self::document(key: 'key2', translatedFloat: ['en' => 2.2]), + self::document(key: 'key3', translatedFloat: ['en' => null, 'de' => 2.2]), + self::document(key: 'key4', translatedFloat: ['de' => 2.2]), + ]), + 'criteria' => new FilterCriteria( + filters: [ + new Neither(field: 'translatedFloat', value: [1.1, 2.2]) + ] + ), + 'expected' => new FilterResult([]) + ]; + yield 'Test translated float field gte filter' => [ + 'input' => new Documents([ + self::document(key: 'key1', translatedFloat: ['en' => 1.1, 'de' => 2.2]), + self::document(key: 'key2', translatedFloat: ['en' => 3.3]), + self::document(key: 'key3', translatedFloat: ['en' => null, 'de' => 2.2]), + self::document(key: 'key4', translatedFloat: ['de' => 1.1]), + ]), + 'criteria' => new FilterCriteria( + filters: [ + new Gte(field: 'translatedFloat', value: 2.2) + ] + ), + 'expected' => new FilterResult([ + self::document(key: 'key2', translatedFloat: ['en' => 3.3]), + self::document(key: 'key3', translatedFloat: ['en' => null, 'de' => 2.2]), + ]) + ]; + yield 'Test translated float field gt filter' => [ + 'input' => new Documents([ + self::document(key: 'key1', translatedFloat: ['en' => 1.1, 'de' => 2.2]), + self::document(key: 'key2', translatedFloat: ['en' => 3.3]), + self::document(key: 'key3', translatedFloat: ['en' => null, 'de' => 2.2]), + self::document(key: 'key4', translatedFloat: ['de' => 1.1]), + ]), + 'criteria' => new FilterCriteria( + filters: [ + new Gt(field: 'translatedFloat', value: 2.2) + ] + ), + 'expected' => new FilterResult([ + self::document(key: 'key2', translatedFloat: ['en' => 3.3]), + ]) + ]; + yield 'Test translated float field lte filter' => [ + 'input' => new Documents([ + self::document(key: 'key1', translatedFloat: ['en' => 1.1, 'de' => 2.2]), + self::document(key: 'key2', translatedFloat: ['en' => 3.3]), + self::document(key: 'key3', translatedFloat: ['en' => null, 'de' => 2.2]), + self::document(key: 'key4', translatedFloat: ['de' => 1.1]), + ]), + 'criteria' => new FilterCriteria( + filters: [ + new Lte(field: 'translatedFloat', value: 2.2) + ] + ), + 'expected' => new FilterResult([ + self::document(key: 'key1', translatedFloat: ['en' => 1.1, 'de' => 2.2]), + self::document(key: 'key3', translatedFloat: ['en' => null, 'de' => 2.2]), + self::document(key: 'key4', translatedFloat: ['de' => 1.1]), + ]) + ]; + yield 'Test translated float field lt filter' => [ + 'input' => new Documents([ + self::document(key: 'key1', translatedFloat: ['en' => 1.1, 'de' => 2.2]), + self::document(key: 'key2', translatedFloat: ['en' => 3.3]), + self::document(key: 'key3', translatedFloat: ['en' => null, 'de' => 2.2]), + self::document(key: 'key4', translatedFloat: ['de' => 1.1]), + ]), + 'criteria' => new FilterCriteria( + filters: [ + new Lt(field: 'translatedFloat', value: 2.2) + ] + ), + 'expected' => new FilterResult([ + self::document(key: 'key1', translatedFloat: ['en' => 1.1, 'de' => 2.2]), + self::document(key: 'key4', translatedFloat: ['de' => 1.1]), + ]) + ]; + + yield 'Test translated bool field equals filter' => [ + 'input' => new Documents([ + self::document(key: 'key1', translatedBool: ['en' => true, 'de' => false]), + self::document(key: 'key2', translatedBool: ['en' => false]), + self::document(key: 'key3', translatedBool: ['en' => null, 'de' => false]), + self::document(key: 'key4', translatedBool: ['de' => false]), + ]), + 'criteria' => new FilterCriteria( + filters: [ + new Equals(field: 'translatedBool', value: false) + ] + ), + 'expected' => new FilterResult([ + self::document(key: 'key2', translatedBool: ['en' => false]), + self::document(key: 'key3', translatedBool: ['en' => null, 'de' => false]), + self::document(key: 'key4', translatedBool: ['de' => false]), + ]) + ]; + yield 'Test translated bool field not filter' => [ + 'input' => new Documents([ + self::document(key: 'key1', translatedBool: ['en' => true, 'de' => false]), + self::document(key: 'key2', translatedBool: ['en' => false]), + self::document(key: 'key3', translatedBool: ['en' => null, 'de' => false]), + self::document(key: 'key4', translatedBool: ['de' => false]), + ]), + 'criteria' => new FilterCriteria( + filters: [ + new Not(field: 'translatedBool', value: false) + ] + ), + 'expected' => new FilterResult([ + self::document(key: 'key1', translatedBool: ['en' => true, 'de' => false]), + ]) + ]; + + yield 'Test translated date field equals filter' => [ + 'input' => new Documents([ + self::document(key: 'key1', translatedDate: ['en' => '2021-01-01 00:00:00.000', 'de' => '2021-01-02 00:00:00.000']), + self::document(key: 'key2', translatedDate: ['en' => '2021-01-02 00:00:00.000']), + self::document(key: 'key3', translatedDate: ['en' => null, 'de' => '2021-01-02 00:00:00.000']), + self::document(key: 'key4', translatedDate: ['de' => '2021-01-02 00:00:00.000']), + ]), + 'criteria' => new FilterCriteria( + filters: [ + new Equals(field: 'translatedDate', value: '2021-01-02 00:00:00.000') + ] + ), + 'expected' => new FilterResult([ + self::document(key: 'key2', translatedDate: ['en' => '2021-01-02 00:00:00.000']), + self::document(key: 'key3', translatedDate: ['en' => null, 'de' => '2021-01-02 00:00:00.000']), + self::document(key: 'key4', translatedDate: ['de' => '2021-01-02 00:00:00.000']), + ]) + ]; + yield 'Test translated date field equals-any filter' => [ + 'input' => new Documents([ + self::document(key: 'key1', translatedDate: ['en' => '2021-01-01 00:00:00.000', 'de' => '2021-01-02 00:00:00.000']), + self::document(key: 'key2', translatedDate: ['en' => '2021-01-02 00:00:00.000']), + self::document(key: 'key3', translatedDate: ['en' => null, 'de' => '2021-01-03 00:00:00.000']), + self::document(key: 'key4', translatedDate: ['de' => '2021-01-02 00:00:00.000']), + ]), + 'criteria' => new FilterCriteria( + filters: [ + new Any(field: 'translatedDate', value: ['2021-01-02 00:00:00.000', '2021-01-03 00:00:00.000']) + ] + ), + 'expected' => new FilterResult([ + self::document(key: 'key2', translatedDate: ['en' => '2021-01-02 00:00:00.000']), + self::document(key: 'key3', translatedDate: ['en' => null, 'de' => '2021-01-03 00:00:00.000']), + self::document(key: 'key4', translatedDate: ['de' => '2021-01-02 00:00:00.000']), + ]) + ]; + yield 'Test translated date field not filter' => [ + 'input' => new Documents([ + self::document(key: 'key1', translatedDate: ['en' => '2021-01-01 00:00:00.000', 'de' => '2021-01-02 00:00:00.000']), + self::document(key: 'key2', translatedDate: ['en' => '2021-01-02 00:00:00.000']), + self::document(key: 'key3', translatedDate: ['en' => null, 'de' => '2021-01-02 00:00:00.000']), + self::document(key: 'key4', translatedDate: ['de' => '2021-01-02 00:00:00.000']), + ]), + 'criteria' => new FilterCriteria( + filters: [ + new Not(field: 'translatedDate', value: '2021-01-02 00:00:00.000') + ] + ), + 'expected' => new FilterResult([ + self::document(key: 'key1', translatedDate: ['en' => '2021-01-01 00:00:00.000', 'de' => '2021-01-02 00:00:00.000']), + ]) + ]; + yield 'Test translated date field not-any filter' => [ + 'input' => new Documents([ + self::document(key: 'key1', translatedDate: ['en' => '2021-01-01 00:00:00.000', 'de' => '2021-01-02 00:00:00.000']), + self::document(key: 'key2', translatedDate: ['en' => '2021-01-02 00:00:00.000']), + self::document(key: 'key3', translatedDate: ['en' => null, 'de' => '2021-01-02 00:00:00.000']), + self::document(key: 'key4', translatedDate: ['de' => '2021-01-02 00:00:00.000']), + ]), + 'criteria' => new FilterCriteria( + filters: [ + new Neither(field: 'translatedDate', value: ['2021-01-01 00:00:00.000', '2021-01-02 00:00:00.000']) + ] + ), + 'expected' => new FilterResult([]) + ]; + yield 'Test translated date field gte filter' => [ + 'input' => new Documents([ + self::document(key: 'key1', translatedDate: ['en' => '2021-01-01 00:00:00.000', 'de' => '2021-01-02 00:00:00.000']), + self::document(key: 'key2', translatedDate: ['en' => '2021-01-03 00:00:00.000']), + self::document(key: 'key3', translatedDate: ['en' => null, 'de' => '2021-01-02 00:00:00.000']), + self::document(key: 'key4', translatedDate: ['de' => '2021-01-01 00:00:00.000']), + ]), + 'criteria' => new FilterCriteria( + filters: [ + new Gte(field: 'translatedDate', value: '2021-01-02 00:00:00.000') + ] + ), + 'expected' => new FilterResult([ + self::document(key: 'key2', translatedDate: ['en' => '2021-01-03 00:00:00.000']), + self::document(key: 'key3', translatedDate: ['en' => null, 'de' => '2021-01-02 00:00:00.000']), + ]) + ]; + yield 'Test translated date field gt filter' => [ + 'input' => new Documents([ + self::document(key: 'key1', translatedDate: ['en' => '2021-01-01 00:00:00.000', 'de' => '2021-01-02 00:00:00.000']), + self::document(key: 'key2', translatedDate: ['en' => '2021-01-03 00:00:00.000']), + self::document(key: 'key3', translatedDate: ['en' => null, 'de' => '2021-01-02 00:00:00.000']), + self::document(key: 'key4', translatedDate: ['de' => '2021-01-01 00:00:00.000']), + ]), + 'criteria' => new FilterCriteria( + filters: [ + new Gt(field: 'translatedDate', value: '2021-01-02 00:00:00.000') + ] + ), + 'expected' => new FilterResult([ + self::document(key: 'key2', translatedDate: ['en' => '2021-01-03 00:00:00.000']), + ]) + ]; + yield 'Test translated date field lte filter' => [ + 'input' => new Documents([ + self::document(key: 'key1', translatedDate: ['en' => '2021-01-01 00:00:00.000', 'de' => '2021-01-02 00:00:00.000']), + self::document(key: 'key2', translatedDate: ['en' => '2021-01-03 00:00:00.000']), + self::document(key: 'key3', translatedDate: ['en' => null, 'de' => '2021-01-02 00:00:00.000']), + self::document(key: 'key4', translatedDate: ['de' => '2021-01-01 00:00:00.000']), + ]), + 'criteria' => new FilterCriteria( + filters: [ + new Lte(field: 'translatedDate', value: '2021-01-02 00:00:00.000') + ] + ), + 'expected' => new FilterResult([ + self::document(key: 'key1', translatedDate: ['en' => '2021-01-01 00:00:00.000', 'de' => '2021-01-02 00:00:00.000']), + self::document(key: 'key3', translatedDate: ['en' => null, 'de' => '2021-01-02 00:00:00.000']), + self::document(key: 'key4', translatedDate: ['de' => '2021-01-01 00:00:00.000']), + ]) + ]; + yield 'Test translated date field lt filter' => [ + 'input' => new Documents([ + self::document(key: 'key1', translatedDate: ['en' => '2021-01-01 00:00:00.000', 'de' => '2021-01-02 00:00:00.000']), + self::document(key: 'key2', translatedDate: ['en' => '2021-01-03 00:00:00.000']), + self::document(key: 'key3', translatedDate: ['en' => null, 'de' => '2021-01-02 00:00:00.000']), + self::document(key: 'key4', translatedDate: ['de' => '2021-01-01 00:00:00.000']), + ]), + 'criteria' => new FilterCriteria( + filters: [ + new Lt(field: 'translatedDate', value: '2021-01-02 00:00:00.000') + ] + ), + 'expected' => new FilterResult([ + self::document(key: 'key1', translatedDate: ['en' => '2021-01-01 00:00:00.000', 'de' => '2021-01-02 00:00:00.000']), + self::document(key: 'key4', translatedDate: ['de' => '2021-01-01 00:00:00.000']), + ]) + ]; + + yield 'Test translated list field equals filter and string values' => [ + 'input' => new Documents([ + self::document(key: 'key1', translatedString: ['en' => 'foo', 'de' => 'bar']), + self::document(key: 'key2', translatedString: ['en' => 'bar']), + self::document(key: 'key3', translatedString: ['en' => null, 'de' => 'bar']), + self::document(key: 'key4', translatedString: ['de' => 'bar']), + ]), + 'criteria' => new FilterCriteria( + filters: [ + new Equals(field: 'translatedString', value: 'bar') + ] + ), + 'expected' => new FilterResult([ + self::document(key: 'key2', translatedString: ['en' => 'bar']), + self::document(key: 'key3', translatedString: ['en' => null, 'de' => 'bar']), + self::document(key: 'key4', translatedString: ['de' => 'bar']), + ]) + ]; + yield 'Test translated list field equals-any filter and string values' => [ + 'input' => new Documents([ + self::document(key: 'key1', translatedString: ['en' => 'foo', 'de' => 'bar']), + self::document(key: 'key2', translatedString: ['en' => 'bar']), + self::document(key: 'key3', translatedString: ['en' => null, 'de' => 'baz']), + self::document(key: 'key4', translatedString: ['de' => 'bar']), + ]), + 'criteria' => new FilterCriteria( + filters: [ + new Any(field: 'translatedString', value: ['bar', 'baz']) + ] + ), + 'expected' => new FilterResult([ + self::document(key: 'key2', translatedString: ['en' => 'bar']), + self::document(key: 'key3', translatedString: ['en' => null, 'de' => 'baz']), + self::document(key: 'key4', translatedString: ['de' => 'bar']), + ]) + ]; + yield 'Test translated list field not filter and string values' => [ + 'input' => new Documents([ + self::document(key: 'key1', translatedString: ['en' => 'foo', 'de' => 'bar']), + self::document(key: 'key2', translatedString: ['en' => 'bar']), + self::document(key: 'key3', translatedString: ['en' => null, 'de' => 'bar']), + self::document(key: 'key4', translatedString: ['de' => 'bar']), + ]), + 'criteria' => new FilterCriteria( + filters: [ + new Not(field: 'translatedString', value: 'bar') + ] + ), + 'expected' => new FilterResult([ + self::document(key: 'key1', translatedString: ['en' => 'foo', 'de' => 'bar']), + ]) + ]; + yield 'Test translated list field not-any filter and string values' => [ + 'input' => new Documents([ + self::document(key: 'key1', translatedString: ['en' => 'foo', 'de' => 'bar']), + self::document(key: 'key2', translatedString: ['en' => 'bar']), + self::document(key: 'key3', translatedString: ['en' => null, 'de' => 'bar']), + self::document(key: 'key4', translatedString: ['de' => 'bar']), + ]), + 'criteria' => new FilterCriteria( + filters: [ + new Neither(field: 'translatedString', value: ['foo', 'bar']) + ] + ), + 'expected' => new FilterResult([]) + ]; + yield 'Test translated list field contains filter and string values' => [ + 'input' => new Documents([ + self::document(key: 'key1', translatedString: ['en' => 'foo', 'de' => 'bar']), + self::document(key: 'key2', translatedString: ['en' => 'bar']), + self::document(key: 'key3', translatedString: ['en' => null, 'de' => 'bar']), + self::document(key: 'key4', translatedString: ['de' => 'bar']), + ]), + 'criteria' => new FilterCriteria( + filters: [ + new Contains(field: 'translatedString', value: 'ba') + ] + ), + 'expected' => new FilterResult([ + self::document(key: 'key2', translatedString: ['en' => 'bar']), + self::document(key: 'key3', translatedString: ['en' => null, 'de' => 'bar']), + self::document(key: 'key4', translatedString: ['de' => 'bar']), + ]) + ]; + + yield 'Test translated list field equals filter and int values' => [ + 'input' => new Documents([ + self::document(key: 'key1', translatedInt: ['en' => 1, 'de' => 2]), + self::document(key: 'key2', translatedInt: ['en' => 2]), + self::document(key: 'key3', translatedInt: ['en' => null, 'de' => 2]), + self::document(key: 'key4', translatedInt: ['de' => 2]), + ]), + 'criteria' => new FilterCriteria( + filters: [ + new Equals(field: 'translatedInt', value: 2) + ] + ), + 'expected' => new FilterResult([ + self::document(key: 'key2', translatedInt: ['en' => 2]), + self::document(key: 'key3', translatedInt: ['en' => null, 'de' => 2]), + self::document(key: 'key4', translatedInt: ['de' => 2]), + ]) + ]; + yield 'Test translated list field equals-any filter and int values' => [ + 'input' => new Documents([ + self::document(key: 'key1', translatedInt: ['en' => 1, 'de' => 2]), + self::document(key: 'key2', translatedInt: ['en' => 2]), + self::document(key: 'key3', translatedInt: ['en' => null, 'de' => 3]), + self::document(key: 'key4', translatedInt: ['de' => 2]), + ]), + 'criteria' => new FilterCriteria( + filters: [ + new Any(field: 'translatedInt', value: [2, 3]) + ] + ), + 'expected' => new FilterResult([ + self::document(key: 'key2', translatedInt: ['en' => 2]), + self::document(key: 'key3', translatedInt: ['en' => null, 'de' => 3]), + self::document(key: 'key4', translatedInt: ['de' => 2]), + ]) + ]; + yield 'Test translated list field not filter and int values' => [ + 'input' => new Documents([ + self::document(key: 'key1', translatedInt: ['en' => 1, 'de' => 2]), + self::document(key: 'key2', translatedInt: ['en' => 2]), + self::document(key: 'key3', translatedInt: ['en' => null, 'de' => 2]), + self::document(key: 'key4', translatedInt: ['de' => 2]), + ]), + 'criteria' => new FilterCriteria( + filters: [ + new Not(field: 'translatedInt', value: 2) + ] + ), + 'expected' => new FilterResult([ + self::document(key: 'key1', translatedInt: ['en' => 1, 'de' => 2]), + ]) + ]; + yield 'Test translated list field not-any filter and int values' => [ + 'input' => new Documents([ + self::document(key: 'key1', translatedInt: ['en' => 1, 'de' => 2]), + self::document(key: 'key2', translatedInt: ['en' => 2]), + self::document(key: 'key3', translatedInt: ['en' => null, 'de' => 2]), + self::document(key: 'key4', translatedInt: ['de' => 2]), + ]), + 'criteria' => new FilterCriteria( + filters: [ + new Neither(field: 'translatedInt', value: [1, 2]) + ] + ), + 'expected' => new FilterResult([]) + ]; + + yield 'Test translated list field equals filter and float values' => [ + 'input' => new Documents([ + self::document(key: 'key1', translatedFloat: ['en' => 1.1, 'de' => 2.2]), + self::document(key: 'key2', translatedFloat: ['en' => 2.2]), + self::document(key: 'key3', translatedFloat: ['en' => null, 'de' => 2.2]), + self::document(key: 'key4', translatedFloat: ['de' => 2.2]), + ]), + 'criteria' => new FilterCriteria( + filters: [ + new Equals(field: 'translatedFloat', value: 2.2) + ] + ), + 'expected' => new FilterResult([ + self::document(key: 'key2', translatedFloat: ['en' => 2.2]), + self::document(key: 'key3', translatedFloat: ['en' => null, 'de' => 2.2]), + self::document(key: 'key4', translatedFloat: ['de' => 2.2]), + ]) + ]; + yield 'Test translated list field equals-any filter and float values' => [ + 'input' => new Documents([ + self::document(key: 'key1', translatedFloat: ['en' => 1.1, 'de' => 2.2]), + self::document(key: 'key2', translatedFloat: ['en' => 2.2]), + self::document(key: 'key3', translatedFloat: ['en' => null, 'de' => 3.3]), + self::document(key: 'key4', translatedFloat: ['de' => 2.2]), + ]), + 'criteria' => new FilterCriteria( + filters: [ + new Any(field: 'translatedFloat', value: [2.2, 3.3]) + ] + ), + 'expected' => new FilterResult([ + self::document(key: 'key2', translatedFloat: ['en' => 2.2]), + self::document(key: 'key3', translatedFloat: ['en' => null, 'de' => 3.3]), + self::document(key: 'key4', translatedFloat: ['de' => 2.2]), + ]) + ]; + yield 'Test translated list field not filter and float values' => [ + 'input' => new Documents([ + self::document(key: 'key1', translatedFloat: ['en' => 1.1, 'de' => 2.2]), + self::document(key: 'key2', translatedFloat: ['en' => 2.2]), + self::document(key: 'key3', translatedFloat: ['en' => null, 'de' => 2.2]), + self::document(key: 'key4', translatedFloat: ['de' => 2.2]), + ]), + 'criteria' => new FilterCriteria( + filters: [ + new Not(field: 'translatedFloat', value: 2.2) + ] + ), + 'expected' => new FilterResult([ + self::document(key: 'key1', translatedFloat: ['en' => 1.1, 'de' => 2.2]), + ]) + ]; + yield 'Test translated list field not-any filter and float values' => [ + 'input' => new Documents([ + self::document(key: 'key1', translatedFloat: ['en' => 1.1, 'de' => 2.2]), + self::document(key: 'key2', translatedFloat: ['en' => 2.2]), + self::document(key: 'key3', translatedFloat: ['en' => null, 'de' => 2.2]), + self::document(key: 'key4', translatedFloat: ['de' => 2.2]), + ]), + 'criteria' => new FilterCriteria( + filters: [ + new Neither(field: 'translatedFloat', value: [1.1, 2.2]) + ] + ), + 'expected' => new FilterResult([]) + ]; + + yield 'Test translated list field equals filter and bool values' => [ + 'input' => new Documents([ + self::document(key: 'key1', translatedBool: ['en' => true, 'de' => false]), + self::document(key: 'key2', translatedBool: ['en' => false]), + self::document(key: 'key3', translatedBool: ['en' => null, 'de' => false]), + self::document(key: 'key4', translatedBool: ['de' => false]), + ]), + 'criteria' => new FilterCriteria( + filters: [ + new Equals(field: 'translatedBool', value: false) + ] + ), + 'expected' => new FilterResult([ + self::document(key: 'key2', translatedBool: ['en' => false]), + self::document(key: 'key3', translatedBool: ['en' => null, 'de' => false]), + self::document(key: 'key4', translatedBool: ['de' => false]), + ]) + ]; + yield 'Test translated list field equals-any filter and bool values' => [ + 'input' => new Documents([ + self::document(key: 'key1', translatedBool: ['en' => true, 'de' => false]), + self::document(key: 'key2', translatedBool: ['en' => false]), + self::document(key: 'key3', translatedBool: ['en' => null, 'de' => true]), + self::document(key: 'key4', translatedBool: ['de' => false]), + ]), + 'criteria' => new FilterCriteria( + filters: [ + new Any(field: 'translatedBool', value: [false, true]) + ] + ), + 'expected' => new FilterResult([ + self::document(key: 'key1', translatedBool: ['en' => true, 'de' => false]), + self::document(key: 'key2', translatedBool: ['en' => false]), + self::document(key: 'key3', translatedBool: ['en' => null, 'de' => true]), + self::document(key: 'key4', translatedBool: ['de' => false]), + ]) + ]; + yield 'Test translated list field not filter and bool values' => [ + 'input' => new Documents([ + self::document(key: 'key1', translatedBool: ['en' => true, 'de' => false]), + self::document(key: 'key2', translatedBool: ['en' => false]), + self::document(key: 'key3', translatedBool: ['en' => null, 'de' => false]), + self::document(key: 'key4', translatedBool: ['de' => false]), + ]), + 'criteria' => new FilterCriteria( + filters: [ + new Not(field: 'translatedBool', value: false) + ] + ), + 'expected' => new FilterResult([ + self::document(key: 'key1', translatedBool: ['en' => true, 'de' => false]), + ]) + ]; + yield 'Test translated list field not-any filter and bool values' => [ + 'input' => new Documents([ + self::document(key: 'key1', translatedBool: ['en' => true, 'de' => false]), + self::document(key: 'key2', translatedBool: ['en' => false]), + self::document(key: 'key3', translatedBool: ['en' => null, 'de' => false]), + self::document(key: 'key4', translatedBool: ['de' => false]), + ]), + 'criteria' => new FilterCriteria( + filters: [ + new Neither(field: 'translatedBool', value: [true, false]) + ] + ), + 'expected' => new FilterResult([]) + ]; + } + + private function getClient(): Client + { + if ($this->client === null) { + $this->client = new Client( + url: 'http://localhost:7700', + apiKey: 'UTbXxcv5T5Hq-nCYAjgPJ5lsBxf7PdhgiNexmoTByJk' + ); + } + + return $this->client; + } + + public function getStorage(): FilterStorage + { + return new LiveMeilisearchStorage( + storage: new MeilisearchStorage( + client: $this->getClient(), + schema: $this->getSchema() + ), + client: $this->getClient(), + ); + } + + private function index(): Indexes + { + return $this->getClient()->index($this->getSchema()->source); + } + + private function wait(): void + { + $tasks = new TasksQuery(); + $tasks->setStatuses(['enqueued', 'processing']); + + $tasks = $this->getClient()->getTasks($tasks); + + $ids = array_map(fn($task) => $task['uid'], $tasks->getResults()); + + if (count($ids) === 0) { + return; + } + + $this->getClient()->waitForTasks($ids); + } +}