From df9dacabe69df99dbe52ae3add0464f1294ee387 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Tamarelle?= Date: Tue, 26 Nov 2024 14:36:57 +0100 Subject: [PATCH] Create classes for Search operators --- generator/composer.json | 2 +- generator/config/definitions.php | 12 + generator/config/expressions.php | 12 + generator/config/schema.json | 9 +- generator/config/search/autocomplete.yaml | 152 + generator/config/search/compound.yaml | 156 + generator/config/search/embeddedDocument.yaml | 155 + generator/config/search/equals.yaml | 104 + generator/config/search/exists.yaml | 56 + generator/config/search/facet.yaml | 56 + generator/config/search/geoShape.yaml | 171 + generator/config/search/geoWithin.yaml | 117 + generator/config/search/in.yaml | 89 + generator/config/search/moreLikeThis.yaml | 99 + generator/config/search/near.yaml | 124 + generator/config/search/phrase.yaml | 109 + generator/config/search/queryString.yaml | 35 + generator/config/search/range.yaml | 139 + generator/config/search/regex.yaml | 42 + generator/config/search/text.yaml | 194 ++ generator/config/search/wildcard.yaml | 60 + generator/config/stage/search.yaml | 1451 +-------- generator/config/stage/searchMeta.yaml | 92 +- .../src/Definition/OperatorDefinition.php | 1 + phpcs.xml.dist | 2 +- src/Builder/Encoder/OperatorEncoder.php | 29 + src/Builder/Search.php | 10 + src/Builder/Search/AutocompleteOperator.php | 71 + src/Builder/Search/CompoundOperator.php | 78 + .../Search/EmbeddedDocumentOperator.php | 59 + src/Builder/Search/EqualsOperator.php | 61 + src/Builder/Search/ExistsOperator.php | 50 + src/Builder/Search/FacetOperator.php | 51 + src/Builder/Search/FactoryTrait.php | 345 ++ src/Builder/Search/GeoShapeOperator.php | 64 + src/Builder/Search/GeoWithinOperator.php | 71 + src/Builder/Search/InOperator.php | 67 + src/Builder/Search/MoreLikeThisOperator.php | 54 + src/Builder/Search/NearOperator.php | 66 + src/Builder/Search/PhraseOperator.php | 78 + src/Builder/Search/QueryStringOperator.php | 42 + src/Builder/Search/RangeOperator.php | 79 + src/Builder/Search/RegexOperator.php | 63 + src/Builder/Search/TextOperator.php | 75 + src/Builder/Search/WildcardOperator.php | 62 + src/Builder/Stage/FactoryTrait.php | 77 +- src/Builder/Stage/FluentFactoryTrait.php | 77 +- src/Builder/Stage/SearchMetaStage.php | 111 +- src/Builder/Stage/SearchStage.php | 111 +- src/Builder/Type/Encode.php | 5 + src/Builder/Type/SearchOperatorInterface.php | 8 + .../Search/AutocompleteOperatorTest.php | 138 + tests/Builder/Search/CompoundOperatorTest.php | 157 + .../Search/EmbeddedDocumentOperatorTest.php | 167 + tests/Builder/Search/EqualsOperatorTest.php | 125 + tests/Builder/Search/ExistsOperatorTest.php | 67 + tests/Builder/Search/FacetOperatorTest.php | 61 + tests/Builder/Search/GeoShapeOperatorTest.php | 204 ++ .../Builder/Search/GeoWithinOperatorTest.php | 124 + tests/Builder/Search/InOperatorTest.php | 101 + .../Search/MoreLikeThisOperatorTest.php | 115 + tests/Builder/Search/NearOperatorTest.php | 128 + tests/Builder/Search/PhraseOperatorTest.php | 102 + tests/Builder/Search/Pipelines.php | 2814 +++++++++++++++++ .../Search/QueryStringOperatorTest.php | 31 + tests/Builder/Search/RangeOperatorTest.php | 122 + tests/Builder/Search/RegexOperatorTest.php | 34 + tests/Builder/Search/TextOperatorTest.php | 194 ++ tests/Builder/Search/WildcardOperatorTest.php | 54 + tests/Builder/Stage/Pipelines.php | 2802 ---------------- tests/Builder/Stage/SearchMetaStageTest.php | 51 +- tests/Builder/Stage/SearchStageTest.php | 1708 +--------- 72 files changed, 8181 insertions(+), 6421 deletions(-) create mode 100644 generator/config/search/autocomplete.yaml create mode 100644 generator/config/search/compound.yaml create mode 100644 generator/config/search/embeddedDocument.yaml create mode 100644 generator/config/search/equals.yaml create mode 100644 generator/config/search/exists.yaml create mode 100644 generator/config/search/facet.yaml create mode 100644 generator/config/search/geoShape.yaml create mode 100644 generator/config/search/geoWithin.yaml create mode 100644 generator/config/search/in.yaml create mode 100644 generator/config/search/moreLikeThis.yaml create mode 100644 generator/config/search/near.yaml create mode 100644 generator/config/search/phrase.yaml create mode 100644 generator/config/search/queryString.yaml create mode 100644 generator/config/search/range.yaml create mode 100644 generator/config/search/regex.yaml create mode 100644 generator/config/search/text.yaml create mode 100644 generator/config/search/wildcard.yaml create mode 100644 src/Builder/Search.php create mode 100644 src/Builder/Search/AutocompleteOperator.php create mode 100644 src/Builder/Search/CompoundOperator.php create mode 100644 src/Builder/Search/EmbeddedDocumentOperator.php create mode 100644 src/Builder/Search/EqualsOperator.php create mode 100644 src/Builder/Search/ExistsOperator.php create mode 100644 src/Builder/Search/FacetOperator.php create mode 100644 src/Builder/Search/FactoryTrait.php create mode 100644 src/Builder/Search/GeoShapeOperator.php create mode 100644 src/Builder/Search/GeoWithinOperator.php create mode 100644 src/Builder/Search/InOperator.php create mode 100644 src/Builder/Search/MoreLikeThisOperator.php create mode 100644 src/Builder/Search/NearOperator.php create mode 100644 src/Builder/Search/PhraseOperator.php create mode 100644 src/Builder/Search/QueryStringOperator.php create mode 100644 src/Builder/Search/RangeOperator.php create mode 100644 src/Builder/Search/RegexOperator.php create mode 100644 src/Builder/Search/TextOperator.php create mode 100644 src/Builder/Search/WildcardOperator.php create mode 100644 src/Builder/Type/SearchOperatorInterface.php create mode 100644 tests/Builder/Search/AutocompleteOperatorTest.php create mode 100644 tests/Builder/Search/CompoundOperatorTest.php create mode 100644 tests/Builder/Search/EmbeddedDocumentOperatorTest.php create mode 100644 tests/Builder/Search/EqualsOperatorTest.php create mode 100644 tests/Builder/Search/ExistsOperatorTest.php create mode 100644 tests/Builder/Search/FacetOperatorTest.php create mode 100644 tests/Builder/Search/GeoShapeOperatorTest.php create mode 100644 tests/Builder/Search/GeoWithinOperatorTest.php create mode 100644 tests/Builder/Search/InOperatorTest.php create mode 100644 tests/Builder/Search/MoreLikeThisOperatorTest.php create mode 100644 tests/Builder/Search/NearOperatorTest.php create mode 100644 tests/Builder/Search/PhraseOperatorTest.php create mode 100644 tests/Builder/Search/Pipelines.php create mode 100644 tests/Builder/Search/QueryStringOperatorTest.php create mode 100644 tests/Builder/Search/RangeOperatorTest.php create mode 100644 tests/Builder/Search/RegexOperatorTest.php create mode 100644 tests/Builder/Search/TextOperatorTest.php create mode 100644 tests/Builder/Search/WildcardOperatorTest.php diff --git a/generator/composer.json b/generator/composer.json index 2deb4ae95..c4dedcfb7 100644 --- a/generator/composer.json +++ b/generator/composer.json @@ -4,7 +4,7 @@ "repositories": [ { "type": "path", - "url": "../", + "url": "..", "symlink": true } ], diff --git a/generator/config/definitions.php b/generator/config/definitions.php index a4f952d39..06d9b44ed 100644 --- a/generator/config/definitions.php +++ b/generator/config/definitions.php @@ -58,4 +58,16 @@ OperatorTestGenerator::class, ], ], + + // Search Operators + [ + 'configFiles' => __DIR__ . '/search', + 'namespace' => 'MongoDB\\Builder\\Search', + 'classNameSuffix' => 'Operator', + 'generators' => [ + OperatorClassGenerator::class, + OperatorFactoryGenerator::class, + OperatorTestGenerator::class, + ], + ], ]; diff --git a/generator/config/expressions.php b/generator/config/expressions.php index f731234b2..a2c649e8e 100644 --- a/generator/config/expressions.php +++ b/generator/config/expressions.php @@ -109,6 +109,10 @@ 'implements' => [ResolvesToAny::class], 'acceptedTypes' => ['string'], ], + 'searchOperator' => [ + 'returnType' => Type\SearchOperatorInterface::class, + 'acceptedTypes' => [Type\SearchOperatorInterface::class, ...$bsonTypes['object']], + ], 'geometry' => [ 'returnType' => Type\GeometryInterface::class, 'acceptedTypes' => [Type\GeometryInterface::class, ...$bsonTypes['object']], @@ -168,4 +172,12 @@ 'GeoPoint' => [ 'acceptedTypes' => [...$bsonTypes['object']], ], + + // Search + 'searchPath' => [ + 'acceptedTypes' => ['string', 'array'], + ], + 'searchScore' => [ + 'acceptedTypes' => [...$bsonTypes['object']], + ], ]; diff --git a/generator/config/schema.json b/generator/config/schema.json index a68564e8e..a1909bb92 100644 --- a/generator/config/schema.json +++ b/generator/config/schema.json @@ -46,7 +46,8 @@ "resolvesToInt", "resolvesToTimestamp", "resolvesToLong", - "resolvesToDecimal" + "resolvesToDecimal", + "searchOperator" ] } }, @@ -63,7 +64,8 @@ "flat_object", "dollar_object", "single", - "group" + "group", + "search" ] }, "description": { @@ -133,7 +135,8 @@ "resolvesToInt", "intFieldPath", "int", "resolvesToTimestamp", "timestampFieldPath", "timestamp", "resolvesToLong", "longFieldPath", "long", - "resolvesToDecimal", "decimalFieldPath", "decimal" + "resolvesToDecimal", "decimalFieldPath", "decimal", + "searchPath", "searchScore", "searchOperator" ] } }, diff --git a/generator/config/search/autocomplete.yaml b/generator/config/search/autocomplete.yaml new file mode 100644 index 000000000..ec76ead26 --- /dev/null +++ b/generator/config/search/autocomplete.yaml @@ -0,0 +1,152 @@ +# $schema: ../schema.json +name: autocomplete +link: 'https://www.mongodb.com/docs/atlas/atlas-search/autocomplete/' +type: + - searchOperator +encode: object +description: | + The autocomplete operator performs a search for a word or phrase that + contains a sequence of characters from an incomplete input string. The + fields that you intend to query with the autocomplete operator must be + indexed with the autocomplete data type in the collection's index definition. +arguments: + - + name: path + type: + - searchPath + - + name: query + type: + - string + - + name: tokenOrder + optional: true + type: + - string #tokenOrder + - + name: fuzzy + optional: true + type: + - object + - + name: score + optional: true + type: + - searchScore +tests: + - + name: 'Basic' + link: 'https://www.mongodb.com/docs/atlas/atlas-search/autocomplete/#basic-example' + pipeline: + - + $search: + autocomplete: + query: 'off' + path: 'title' + - + $limit: 10 + - + $project: + _id: 0 + title: 1 + + - + name: 'Fuzzy' + link: 'https://www.mongodb.com/docs/atlas/atlas-search/autocomplete/#fuzzy-example' + pipeline: + - + $search: + autocomplete: + query: 'pre' + path: 'title' + fuzzy: + maxEdits: 1 + prefixLength: 1 + maxExpansions: 256 + - + $limit: 10 + - + $project: + _id: 0 + title: 1 + + - + name: 'Token Order any' + link: 'https://www.mongodb.com/docs/atlas/atlas-search/autocomplete/#simple-any-example' + pipeline: + - + $search: + autocomplete: + query: 'men with' + path: 'title' + tokenOrder: 'any' + - + $limit: 4 + - + $project: + _id: 0 + title: 1 + + - + name: 'Token Order sequential' + link: 'https://www.mongodb.com/docs/atlas/atlas-search/autocomplete/#simple-sequential-example' + pipeline: + - + $search: + autocomplete: + query: 'men with' + path: 'title' + tokenOrder: 'sequential' + - + $limit: 4 + - + $project: + _id: 0 + title: 1 + + - + name: 'Highlighting' + link: 'https://www.mongodb.com/docs/atlas/atlas-search/autocomplete/#highlighting-example' + pipeline: + - + $search: + autocomplete: + query: 'ger' + path: 'title' + highlight: + path: 'title' + - + $limit: 5 + - + $project: + score: + $meta: 'searchScore' + _id: 0 + title: 1 + highlights: + $meta: 'searchHighlights' + + - + name: 'Across Multiple Fields' + link: 'https://www.mongodb.com/docs/atlas/atlas-search/autocomplete/#search-across-multiple-fields' + pipeline: + - + $search: + compound: + should: + - + autocomplete: + query: 'inter' + path: 'title' + - + autocomplete: + query: 'inter' + path: 'plot' + minimumShouldMatch: 1 + - + $limit: 10 + - + $project: + _id: 0 + title: 1 + plot: 1 diff --git a/generator/config/search/compound.yaml b/generator/config/search/compound.yaml new file mode 100644 index 000000000..6dbb77abf --- /dev/null +++ b/generator/config/search/compound.yaml @@ -0,0 +1,156 @@ +# $schema: ../schema.json +name: compound +link: 'https://www.mongodb.com/docs/atlas/atlas-search/equals/' +type: + - searchOperator +encode: object +description: | + The compound operator combines two or more operators into a single query. + Each element of a compound query is called a clause, and each clause + consists of one or more sub-queries. +arguments: + - + name: must + optional: true + type: + - searchOperator + - array # of searchOperator + - + name: mustNot + optional: true + type: + - searchOperator + - array # of searchOperator + - + name: should + optional: true + type: + - searchOperator + - array # of searchOperator + - + name: filter + optional: true + type: + - searchOperator + - array # of searchOperator + - + name: minimumShouldMatch + optional: true + type: + - int + - + name: score + optional: true + type: + - searchScore +tests: + - + name: 'must and mustNot' + link: 'https://www.mongodb.com/docs/atlas/atlas-search/compound/#must-and-mustnot-example' + pipeline: + - + $search: + compound: + must: + - + text: + query: 'varieties' + path: 'description' + mustNot: + - + text: + query: 'apples' + path: 'description' + + - + name: 'must and should' + link: 'https://www.mongodb.com/docs/atlas/atlas-search/compound/#must-and-should-example' + pipeline: + - + $search: + compound: + must: + - + text: + query: 'varieties' + path: 'description' + should: + - + text: + query: 'Fuji' + path: 'description' + - + $project: + score: + $meta: 'searchScore' + + - + name: 'minimumShouldMatch' + link: 'https://www.mongodb.com/docs/atlas/atlas-search/compound/#minimumshouldmatch-example' + pipeline: + - + $search: + compound: + must: + - + text: + query: 'varieties' + path: 'description' + should: + - + text: + query: 'Fuji' + path: 'description' + - + text: + query: 'Golden Delicious' + path: 'description' + minimumShouldMatch: 1 + + - + name: 'Filter' + link: 'https://www.mongodb.com/docs/atlas/atlas-search/compound/#filter-examples' + pipeline: + - + $search: + compound: + must: + - + text: + query: 'varieties' + path: 'description' + should: + - + text: + query: 'banana' + path: 'description' + filter: + - + text: + query: 'granny' + path: 'description' + + - + name: 'Nested' + link: 'https://www.mongodb.com/docs/atlas/atlas-search/compound/#nested-example' + pipeline: + - + $search: + compound: + should: + - + text: + query: 'apple' + path: 'type' + - + compound: + must: + - + text: + query: 'organic' + path: 'category' + - + equals: + value: true + path: 'in_stock' + minimumShouldMatch: 1 diff --git a/generator/config/search/embeddedDocument.yaml b/generator/config/search/embeddedDocument.yaml new file mode 100644 index 000000000..2f7578cdf --- /dev/null +++ b/generator/config/search/embeddedDocument.yaml @@ -0,0 +1,155 @@ +# $schema: ../schema.json +name: embeddedDocument +link: 'https://www.mongodb.com/docs/atlas/atlas-search/embeddedDocument/' +type: + - searchOperator +encode: object +description: | + The embeddedDocument operator is similar to $elemMatch operator. + It constrains multiple query predicates to be satisfied from a single + element of an array of embedded documents. embeddedDocument can be used only + for queries over fields of the embeddedDocuments +arguments: + - + name: path + type: + - searchPath + - + name: operator + type: + - searchOperator + - + name: score + optional: true + type: + - searchScore +tests: + - + name: 'Basic' + link: 'https://www.mongodb.com/docs/atlas/atlas-search/embedded-document/#index-definition' + pipeline: + - + $search: + embeddedDocument: + path: 'items' + operator: + compound: + must: + - + text: + path: 'items.tags' + query: 'school' + should: + - + text: + path: 'items.name' + query: 'backpack' + score: + embedded: + aggregate: 'mean' + - + $limit: 5 + - + $project: + _id: 0 + items.name: 1 + items.tags: 1 + score: + $meta: 'searchScore' + + - + name: 'Facet' + link: 'https://www.mongodb.com/docs/atlas/atlas-search/embedded-document/#facet-query' + pipeline: + - + $searchMeta: + facet: + operator: + embeddedDocument: + path: 'items' + operator: + compound: + must: + - + text: + path: 'items.tags' + query: 'school' + should: + - + text: + path: 'items.name' + query: 'backpack' + facets: + purchaseMethodFacet: + type: 'string' + path: 'purchaseMethod' + + - + name: 'Query and Sort' + link: 'https://www.mongodb.com/docs/atlas/atlas-search/embedded-document/#query-and-sort' + pipeline: + - + $search: + embeddedDocument: + path: 'items' + operator: + text: + path: 'items.name' + query: 'laptop' + sort: + items.tags: 1 + - + $limit: 5 + - + $project: + _id: 0 + items.name: 1 + items.tags: 1 + score: + $meta: 'searchScore' + + - + name: 'Query for Matching Embedded Documents Only' + link: 'https://www.mongodb.com/docs/atlas/atlas-search/embedded-document/#query-for-matching-embedded-documents-only' + pipeline: + - + $search: + embeddedDocument: + path: 'items' + operator: + compound: + must: + - + range: + path: 'items.quantity' + gt: 2 + - + exists: + path: 'items.price' + - + text: + path: 'items.tags' + query: 'school' + - + $limit: 2 + - + $project: + _id: 0 + storeLocation: 1 + items: + $filter: + input: '$items' + cond: + $and: + - + $ifNull: + - '$$this.price' + - 'false' + - + $gt: + - '$$this.quantity' + - 2 + - + $in: + - 'office' + - '$$this.tags' diff --git a/generator/config/search/equals.yaml b/generator/config/search/equals.yaml new file mode 100644 index 000000000..b3e50c641 --- /dev/null +++ b/generator/config/search/equals.yaml @@ -0,0 +1,104 @@ +# $schema: ../schema.json +name: equals +link: 'https://www.mongodb.com/docs/atlas/atlas-search/equals/' +type: + - searchOperator +encode: object +description: | + The equals operator checks whether a field matches a value you specify. +arguments: + - + name: path + type: + - searchPath + - + name: value + type: + - binData + - bool + - date + - objectId + - 'null' + - number + - string + - + name: score + optional: true + type: + - searchScore +tests: + - + name: 'Boolean' + link: 'https://www.mongodb.com/docs/atlas/atlas-search/equals/#boolean-examples' + pipeline: + - + $search: + equals: + path: 'verified_user' + value: true + - + $project: + name: 1 + _id: 0 + score: + $meta: 'searchScore' + + - + name: 'ObjectId' + link: 'https://www.mongodb.com/docs/atlas/atlas-search/equals/#objectid-example' + pipeline: + - + $search: + equals: + path: 'teammates' + value: !bson_objectId '5a9427648b0beebeb69589a1' + + - + name: 'Date' + link: 'https://www.mongodb.com/docs/atlas/atlas-search/equals/#date-example' + pipeline: + - + $search: + equals: + path: 'account_created' + value: !bson_utcdatetime '2022-05-04T05:01:08.000+00:00' + + - + name: 'Number' + link: 'https://www.mongodb.com/docs/atlas/atlas-search/equals/#number-example' + pipeline: + - + $search: + equals: + path: 'employee_number' + value: 259 + + - + name: 'String' + link: 'https://www.mongodb.com/docs/atlas/atlas-search/equals/#string-example' + pipeline: + - + $search: + equals: + path: 'name' + value: 'jim hall' + + - + name: 'UUID' + link: 'https://www.mongodb.com/docs/atlas/atlas-search/equals/#uuid-example' + pipeline: + - + $search: + equals: + path: 'uuid' + value: !bson_uuid 'fac32260-b511-4c69-8485-a2be5b7dda9e' + + - + name: 'Null' + link: 'https://www.mongodb.com/docs/atlas/atlas-search/equals/#null-example' + pipeline: + - + $search: + equals: + path: 'job_title' + value: ~ diff --git a/generator/config/search/exists.yaml b/generator/config/search/exists.yaml new file mode 100644 index 000000000..fed96a609 --- /dev/null +++ b/generator/config/search/exists.yaml @@ -0,0 +1,56 @@ +# $schema: ../schema.json +name: exists +link: 'https://www.mongodb.com/docs/atlas/atlas-search/equals/' +type: + - searchOperator +encode: object +description: | + The equals operator checks whether a field matches a value you specify. +arguments: + - + name: path + type: + - searchPath + - + name: score + optional: true + type: + - searchScore +tests: + - + name: 'Basic' + link: 'https://www.mongodb.com/docs/atlas/atlas-search/exists/#basic-example' + pipeline: + - + $search: + exists: + path: 'type' + + - + name: 'Embedded' + link: 'https://www.mongodb.com/docs/atlas/atlas-search/exists/#embedded-example' + pipeline: + - + $search: + exists: + path: 'quantities.lemons' + + - + name: 'Compound' + link: 'https://www.mongodb.com/docs/atlas/atlas-search/exists/#compound-example' + pipeline: + - + $search: + compound: + must: + - + exists: + path: 'type' + - + text: + query: 'apple' + path: 'type' + should: + text: + query: 'fuji' + path: 'description' diff --git a/generator/config/search/facet.yaml b/generator/config/search/facet.yaml new file mode 100644 index 000000000..696867cf5 --- /dev/null +++ b/generator/config/search/facet.yaml @@ -0,0 +1,56 @@ +# $schema: ../schema.json +name: facet +link: 'https://www.mongodb.com/docs/atlas/atlas-search/facet/' +type: + - searchOperator # should be searchCollector +encode: object +description: | + The facet collector groups results by values or ranges in the specified + faceted fields and returns the count for each of those groups. +arguments: + - + name: facets + type: + - object # of facetDefinition + - + name: operator + optional: true + type: + - searchOperator +tests: + - + name: 'Facet' + link: 'https://www.mongodb.com/docs/atlas/atlas-search/facet/#examples' + pipeline: + - + $search: + facet: + operator: + near: + path: 'released' + origin: !bson_utcdatetime '1999-07-01T00:00:00.000+00:00' + pivot: 7776000000 + facets: + genresFacet: + type: 'string' + path: 'genres' + - + $limit: 2 + - + $facet: + docs: + - + $project: + title: 1 + released: 1 + meta: + - + $replaceWith: '$$SEARCH_META' + - + $limit: 1 + - + $set: + meta: + $arrayElemAt: + - '$meta' + - 0 diff --git a/generator/config/search/geoShape.yaml b/generator/config/search/geoShape.yaml new file mode 100644 index 000000000..1c9ae27ff --- /dev/null +++ b/generator/config/search/geoShape.yaml @@ -0,0 +1,171 @@ +# $schema: ../schema.json +name: geoShape +link: 'https://www.mongodb.com/docs/atlas/atlas-search/geoShape/' +type: + - searchOperator +encode: object +description: | + The geoShape operator supports querying shapes with a relation to a given + geometry if indexShapes is set to true in the index definition. +arguments: + - + name: path + type: + - searchPath + - + name: relation + type: + - string # contains | disjoint | intersects | within + - + name: geometry + type: + - geometry + - + name: score + optional: true + type: + - searchScore +tests: + - + name: 'Disjoint' + link: 'https://www.mongodb.com/docs/atlas/atlas-search/geoShape/#disjoint-example' + pipeline: + - + $search: + geoShape: + relation: 'disjoint' + geometry: + type: 'Polygon' + coordinates: + - + - + - -161.323242 + - 22.512557 + - + - -152.446289 + - 22.065278 + - + - -156.09375 + - 17.811456 + - + - -161.323242 + - 22.512557 + path: 'address.location' + - + $limit: 3 + - + $project: + _id: 0 + name: 1 + address: 1 + score: + $meta: 'searchScore' + + - + name: 'Intersect' + link: 'https://www.mongodb.com/docs/atlas/atlas-search/geoShape/#intersects-example' + pipeline: + - + $search: + geoShape: + relation: 'intersects' + geometry: + type: 'MultiPolygon' + coordinates: + - + - + - + - 2.16942 + - 41.40082 + - + - 2.17963 + - 41.40087 + - + - 2.18146 + - 41.39716 + - + - 2.15533 + - 41.40686 + - + - 2.14596 + - 41.38475 + - + - 2.17519 + - 41.41035 + - + - 2.16942 + - 41.40082 + - + - + - + - 2.16365 + - 41.39416 + - + - 2.16963 + - 41.39726 + - + - 2.15395 + - 41.38005 + - + - 2.17935 + - 41.43038 + - + - 2.16365 + - 41.39416 + path: 'address.location' + - + $limit: 3 + - + $project: + _id: 0 + name: 1 + address: 1 + score: + $meta: 'searchScore' + + - + name: 'Within' + link: 'https://www.mongodb.com/docs/atlas/atlas-search/geoShape/#within-example' + pipeline: + - + $search: + geoShape: + relation: 'within' + geometry: + type: 'Polygon' + coordinates: + - + - + - -74.3994140625 + - 40.5305017757 + - + - -74.7290039063 + - 40.5805846641 + - + - -74.7729492188 + - 40.9467136651 + - + - -74.0698242188 + - 41.1290213475 + - + - -73.65234375 + - 40.9964840144 + - + - -72.6416015625 + - 40.9467136651 + - + - -72.3559570313 + - 40.7971774152 + - + - -74.3994140625 + - 40.5305017757 + path: 'address.location' + - + $limit: 3 + - + $project: + _id: 0 + name: 1 + address: 1 + score: + $meta: 'searchScore' diff --git a/generator/config/search/geoWithin.yaml b/generator/config/search/geoWithin.yaml new file mode 100644 index 000000000..d330b4d64 --- /dev/null +++ b/generator/config/search/geoWithin.yaml @@ -0,0 +1,117 @@ +# $schema: ../schema.json +name: geoWithin +link: 'https://www.mongodb.com/docs/atlas/atlas-search/geoWithin/' +type: + - searchOperator +encode: object +description: | + The geoWithin operator supports querying geographic points within a given + geometry. Only points are returned, even if indexShapes value is true in + the index definition. +arguments: + - + name: path + type: + - searchPath + - + name: box + optional: true + type: + - object + - + name: circle + optional: true + type: + - object + - + name: geometry + optional: true + type: + - geometry + - + name: score + optional: true + type: + - searchScore +tests: + - + name: 'box' + link: 'https://www.mongodb.com/docs/atlas/atlas-search/geoWithin/#box-example' + pipeline: + - + $search: + geoWithin: + path: 'address.location' + box: + bottomLeft: + type: 'Point' + coordinates: + - 112.467 + - -55.05 + topRight: + type: 'Point' + coordinates: + - 168 + - -9.133 + - + $limit: 3 + - + $project: + _id: 0 + name: 1 + address: 1 + + - + name: 'circle' + link: 'https://www.mongodb.com/docs/atlas/atlas-search/geoWithin/#circle-example' + pipeline: + - + $search: + geoWithin: + circle: + center: + type: 'Point' + coordinates: + - -73.54 + - 45.54 + radius: 1600 + path: 'address.location' + - + $limit: 3 + - + $project: + _id: 0 + name: 1 + address: 1 + + - + name: 'geometry' + link: 'https://www.mongodb.com/docs/atlas/atlas-search/geoWithin/#geometry-examples' + pipeline: + - + $search: + geoWithin: + geometry: + type: 'Polygon' + coordinates: + - + - + - -161.323242 + - 22.512557 + - + - -152.446289 + - 22.065278 + - + - -156.09375 + - 17.811456 + - + - -161.323242 + - 22.512557 + path: 'address.location' + - + $limit: 3 + - + $project: + _id: 0 + name: 1 + address: 1 diff --git a/generator/config/search/in.yaml b/generator/config/search/in.yaml new file mode 100644 index 000000000..cc1aa6c33 --- /dev/null +++ b/generator/config/search/in.yaml @@ -0,0 +1,89 @@ +# $schema: ../schema.json +name: in +link: 'https://www.mongodb.com/docs/atlas/atlas-search/in/' +type: + - searchOperator +encode: object +description: | + The in operator performs a search for an array of BSON values in a field. +arguments: + - + name: path + type: + - searchPath + - + name: value + type: + - any + - array # of any + - + name: score + optional: true + type: + - searchScore +tests: + - + name: 'Single Value Field Match' + link: 'https://www.mongodb.com/docs/atlas/atlas-search/in/#examples' + pipeline: + - + $search: + in: + path: 'birthdate' + value: + - !bson_utcdatetime '1977-03-02T02:20:31.000+00:00' + - !bson_utcdatetime '1977-03-01T00:00:00.000+00:00' + - !bson_utcdatetime '1977-05-06T21:57:35.000+00:00' + - + $project: + _id: 0 + name: 1 + birthdate: 1 + + - + name: 'Array Value Field Match' + link: 'https://www.mongodb.com/docs/atlas/atlas-search/in/#examples' + pipeline: + - + $search: + in: + path: 'accounts' + value: + - 371138 + - 371139 + - 371140 + - + $project: + _id: 0 + name: 1 + accounts: 1 + + - + name: 'Compound Query Match' + link: 'https://www.mongodb.com/docs/atlas/atlas-search/in/#examples' + pipeline: + - + $search: + compound: + must: + - + in: + path: 'name' + value: + - 'james sanchez' + - 'jennifer lawrence' + should: + - + in: + path: '_id' + value: + - !bson_objectId '5ca4bbcea2dd94ee58162a72' + - !bson_objectId '5ca4bbcea2dd94ee58162a91' + - + $limit: 5 + - + $project: + _id: 1 + name: 1 + score: + $meta: 'searchScore' diff --git a/generator/config/search/moreLikeThis.yaml b/generator/config/search/moreLikeThis.yaml new file mode 100644 index 000000000..8c4803bdd --- /dev/null +++ b/generator/config/search/moreLikeThis.yaml @@ -0,0 +1,99 @@ +# $schema: ../schema.json +name: moreLikeThis +link: 'https://www.mongodb.com/docs/atlas/atlas-search/moreLikeThis/' +type: + - searchOperator +encode: object +description: | + The moreLikeThis operator returns documents similar to input documents. + The moreLikeThis operator allows you to build features for your applications + that display similar or alternative results based on one or more given documents. +arguments: + - + name: like + type: + - object + - array # of object + - + name: score + optional: true + type: + - searchScore +tests: + - + name: 'Single Document with Multiple Fields' + link: 'https://www.mongodb.com/docs/atlas/atlas-search/morelikethis/#example-1--single-document-with-multiple-fields' + pipeline: + - + $search: + moreLikeThis: + like: + title: 'The Godfather' + genres: 'action' + - + $limit: 5 + - + $project: + _id: 0 + title: 1 + released: 1 + genres: 1 + + - + name: 'Input Document Excluded in Results' + link: 'https://www.mongodb.com/docs/atlas/atlas-search/morelikethis/#example-2--input-document-excluded-in-results' + pipeline: + - + $search: + compound: + must: + - + moreLikeThis: + like: + _id: !bson_objectId '573a1396f29313caabce4a9a' + genres: + - 'Crime' + - 'Drama' + title: 'The Godfather' + mustNot: + - + equals: + path: '_id' + value: !bson_objectId '573a1396f29313caabce4a9a' + - + $limit: 5 + - + $project: + _id: 1 + title: 1 + released: 1 + genres: 1 + + - + name: 'Multiple Analyzers' + link: 'https://www.mongodb.com/docs/atlas/atlas-search/morelikethis/#example-3--multiple-analyzers' + pipeline: + - + $search: + compound: + should: + - + moreLikeThis: + like: + _id: !bson_objectId '573a1396f29313caabce4a9a' + genres: + - 'Crime' + - 'Drama' + title: 'The Godfather' + mustNot: + - + equals: + path: '_id' + value: !bson_objectId '573a1394f29313caabcde9ef' + - + $limit: 10 + - + $project: + title: 1 + genres: 1 + _id: 1 diff --git a/generator/config/search/near.yaml b/generator/config/search/near.yaml new file mode 100644 index 000000000..bd4119cf9 --- /dev/null +++ b/generator/config/search/near.yaml @@ -0,0 +1,124 @@ +# $schema: ../schema.json +name: near +link: 'https://www.mongodb.com/docs/atlas/atlas-search/near/' +type: + - searchOperator +encode: object +description: | + The near operator supports querying and scoring numeric, date, and GeoJSON point values. +arguments: + - + name: path + type: + - searchPath + - + name: origin + type: + - date + - number + - geometry + - + name: pivot + type: + - number + - + name: score + optional: true + type: + - searchScore +tests: + - + name: 'Number' + link: 'https://www.mongodb.com/docs/atlas/atlas-search/near/#number-example' + pipeline: + - + $search: + index: 'runtimes' + near: + path: 'runtime' + origin: 279 + pivot: 2 + - + $limit: 7 + - + $project: + _id: 0 + title: 1 + runtime: 1 + score: + $meta: 'searchScore' + + - + name: 'Date' + link: 'https://www.mongodb.com/docs/atlas/atlas-search/near/#date-example' + pipeline: + - + $search: + index: 'releaseddate' + near: + path: 'released' + origin: !bson_utcdatetime '1915-09-13T00:00:00.000+00:00' + pivot: 7776000000 + - + $limit: 3 + - + $project: + _id: 0 + title: 1 + released: 1 + score: + $meta: 'searchScore' + + - + name: 'GeoJSON Point' + link: 'https://www.mongodb.com/docs/atlas/atlas-search/near/#geojson-point-examples' + pipeline: + - + $search: + near: + origin: + type: 'Point' + coordinates: + - -8.61308 + - 41.1413 + pivot: 1000 + path: 'address.location' + - + $limit: 3 + - + $project: + _id: 0 + name: 1 + address: 1 + score: + $meta: 'searchScore' + + - + name: 'Compound' + link: 'https://www.mongodb.com/docs/atlas/atlas-search/near/#compound-example' + pipeline: + - + $search: + compound: + must: + text: + query: 'Apartment' + path: 'property_type' + should: + near: + origin: + type: 'Point' + coordinates: + - 114.15027 + - 22.28158 + pivot: 1000 + path: 'address.location' + - + $limit: 3 + - + $project: + _id: 0 + property_type: 1 + address: 1 + score: + $meta: 'searchScore' diff --git a/generator/config/search/phrase.yaml b/generator/config/search/phrase.yaml new file mode 100644 index 000000000..4d9b75c4e --- /dev/null +++ b/generator/config/search/phrase.yaml @@ -0,0 +1,109 @@ +# $schema: ../schema.json +name: phrase +link: 'https://www.mongodb.com/docs/atlas/atlas-search/phrase/' +type: + - searchOperator +encode: object +description: | + The phrase operator performs search for documents containing an ordered sequence of terms using the analyzer specified in the index configuration. +arguments: + - + name: path + type: + - searchPath + - + name: query + type: + - string + - array # of string + - + name: slop + optional: true + type: + - int + - + name: synonyms + optional: true + type: + - string + - + name: score + optional: true + type: + - searchScore +tests: + - + name: 'Single Phrase' + link: 'https://www.mongodb.com/docs/atlas/atlas-search/phrase/#single-phrase-example' + pipeline: + - + $search: + phrase: + path: 'title' + query: 'new york' + - + $limit: 10 + - + $project: + _id: 0 + title: 1 + score: + $meta: 'searchScore' + + - + name: 'Multiple Phrase' + link: 'https://www.mongodb.com/docs/atlas/atlas-search/phrase/#multiple-phrases-example' + pipeline: + - + $search: + phrase: + path: 'title' + query: + - 'the man' + - 'the moon' + - + $limit: 10 + - + $project: + _id: 0 + title: 1 + score: + $meta: 'searchScore' + + - + name: 'Phrase Slop' + link: 'https://www.mongodb.com/docs/atlas/atlas-search/phrase/#slop-example' + pipeline: + - + $search: + phrase: + path: 'title' + query: 'men women' + slop: 5 + - + $project: + _id: 0 + title: 1 + score: + $meta: 'searchScore' + + - + name: 'Phrase Synonyms' + link: 'https://www.mongodb.com/docs/atlas/atlas-search/phrase/#synonyms-example' + pipeline: + - + $search: + phrase: + path: 'plot' + query: 'automobile race' + slop: 5 + synonyms: 'my_synonyms' + - + $limit: 5 + - + $project: + _id: 0 + plot: 1 + title: 1 + score: + $meta: 'searchScore' diff --git a/generator/config/search/queryString.yaml b/generator/config/search/queryString.yaml new file mode 100644 index 000000000..0bebdf45a --- /dev/null +++ b/generator/config/search/queryString.yaml @@ -0,0 +1,35 @@ +# $schema: ../schema.json +name: queryString +link: 'https://www.mongodb.com/docs/atlas/atlas-search/queryString/' +type: + - searchOperator +encode: object +description: | + +arguments: + - + name: defaultPath + type: + - searchPath + - + name: query + type: + - string + +# The various example from the doc are variations of the "query" parameter +# this is not pertinent for testing the aggregation builder, unless we create +# a queryString builder. +tests: + - + name: 'Boolean Operator Queries' + link: 'https://www.mongodb.com/docs/atlas/atlas-search/queryString/#boolean-operator-queries' + pipeline: + - + $search: + queryString: + defaultPath: 'title' + query: 'Rocky AND (IV OR 4 OR Four)' + - + $project: + _id: 0 + title: 1 \ No newline at end of file diff --git a/generator/config/search/range.yaml b/generator/config/search/range.yaml new file mode 100644 index 000000000..f42c69176 --- /dev/null +++ b/generator/config/search/range.yaml @@ -0,0 +1,139 @@ +# $schema: ../schema.json +name: range +link: 'https://www.mongodb.com/docs/atlas/atlas-search/range/' +type: + - searchOperator +encode: object +description: | + The range operator supports querying and scoring numeric, date, and string values. + You can use this operator to find results that are within a given numeric, date, objectId, or letter (from the English alphabet) range. +arguments: + - + name: path + type: + - searchPath + - + name: gt + optional: true + type: + - date + - number + - string + - objectId + - + name: gte + optional: true + type: + - date + - number + - string + - objectId + - + name: lt + optional: true + type: + - date + - number + - string + - objectId + - + name: lte + optional: true + type: + - date + - number + - string + - objectId + - + name: score + optional: true + type: + - searchScore +tests: + - + name: 'Number gte lte' + link: 'https://www.mongodb.com/docs/atlas/atlas-search/range/#number-example' + pipeline: + - + $search: + range: + path: 'runtime' + gte: 2 + lte: 3 + - + $limit: 5 + - + $project: + _id: 0 + title: 1 + runtime: 1 + + - + name: 'Number lte' + link: 'https://www.mongodb.com/docs/atlas/atlas-search/range/#number-example' + pipeline: + - + $search: + range: + path: 'runtime' + lte: 2 + - + $limit: 5 + - + $project: + _id: 0 + title: 1 + runtime: 1 + score: + $meta: 'searchScore' + + - + name: 'Date' + link: 'https://www.mongodb.com/docs/atlas/atlas-search/range/#date-example' + pipeline: + - + $search: + range: + path: 'released' + gt: !bson_utcdatetime '2010-01-01T00:00:00.000Z' + lt: !bson_utcdatetime '2015-01-01T00:00:00.000Z' + - + $limit: 5 + - + $project: + _id: 0 + title: 1 + released: 1 + + - + name: 'ObjectId' + link: 'https://www.mongodb.com/docs/atlas/atlas-search/range/#objectid-example' + pipeline: + - + $search: + range: + path: '_id' + gte: !bson_objectId '573a1396f29313caabce4a9a' + lte: !bson_objectId '573a1396f29313caabce4ae7' + - + $project: + _id: 1 + title: 1 + released: 1 + + - + name: 'String' + link: 'https://www.mongodb.com/docs/atlas/atlas-search/range/#string-example' + pipeline: + - + $search: + range: + path: 'title' + gt: 'city' + lt: 'country' + - + $limit: 5 + - + $project: + _id: 0 + title: 1 diff --git a/generator/config/search/regex.yaml b/generator/config/search/regex.yaml new file mode 100644 index 000000000..869ffabde --- /dev/null +++ b/generator/config/search/regex.yaml @@ -0,0 +1,42 @@ +# $schema: ../schema.json +name: regex +link: 'https://www.mongodb.com/docs/atlas/atlas-search/regex/' +type: + - searchOperator +encode: object +description: | + regex interprets the query field as a regular expression. + regex is a term-level operator, meaning that the query field isn't analyzed. +arguments: + - + name: path + type: + - searchPath + - + name: query + type: + - string + - + name: allowAnalyzedField + optional: true + type: + - bool + - + name: score + optional: true + type: + - searchScore +tests: + - + name: 'Regex' + link: 'https://www.mongodb.com/docs/atlas/atlas-search/regex/#examples' + pipeline: + - + $search: + regex: + path: 'title' + query: '[0-9]{2} (.){4}s' + - + $project: + _id: 0 + title: 1 diff --git a/generator/config/search/text.yaml b/generator/config/search/text.yaml new file mode 100644 index 000000000..dbd48cdd0 --- /dev/null +++ b/generator/config/search/text.yaml @@ -0,0 +1,194 @@ +# $schema: ../schema.json +name: text +link: 'https://www.mongodb.com/docs/atlas/atlas-search/text/' +type: + - searchOperator +encode: object +description: | + The text operator performs a full-text search using the analyzer that you specify in the index configuration. + If you omit an analyzer, the text operator uses the default standard analyzer. +arguments: + - + name: path + type: + - searchPath + - + name: query + type: + - string + - + name: fuzzy + optional: true + type: + - object + - + name: matchCriteria + optional: true + type: + - string # "any" | "all" + - + name: synonyms + optional: true + type: + - string + - + name: score + optional: true + type: + - searchScore +tests: + - + name: 'Basic' + link: 'https://www.mongodb.com/docs/atlas/atlas-search/text/#basic-example' + pipeline: + - + $search: + text: + path: 'title' + query: 'surfer' + - + $project: + _id: 0 + title: 1 + score: + $meta: 'searchScore' + - + name: 'Fuzzy Default' + link: 'https://www.mongodb.com/docs/atlas/atlas-search/text/#fuzzy-examples' + pipeline: + - + $search: + text: + path: 'title' + query: 'naw yark' + fuzzy: {} + - + $limit: 10 + - + $project: + _id: 0 + title: 1 + score: + $meta: 'searchScore' + + - + name: 'Fuzzy maxExpansions' + link: 'https://www.mongodb.com/docs/atlas/atlas-search/text/#fuzzy-examples' + pipeline: + - + $search: + text: + path: 'title' + query: 'naw yark' + fuzzy: + maxEdits: 1 + maxExpansions: 100 + - + $limit: 10 + - + $project: + _id: 0 + title: 1 + score: + $meta: 'searchScore' + + - + name: 'Fuzzy prefixLength' + link: 'https://www.mongodb.com/docs/atlas/atlas-search/text/#fuzzy-examples' + pipeline: + - + $search: + text: + path: 'title' + query: 'naw yark' + fuzzy: + maxEdits: 1 + prefixLength: 2 + - + $limit: 8 + - + $project: + _id: 1 + title: 1 + score: + $meta: 'searchScore' + + - + name: 'Match any Using equivalent Mapping' + link: 'https://www.mongodb.com/docs/atlas/atlas-search/text/#match-any-using-equivalent-mapping' + pipeline: + - + $search: + text: + path: 'plot' + query: 'attire' + synonyms: 'my_synonyms' + matchCriteria: 'any' + - + $limit: 5 + - + $project: + _id: 0 + plot: 1 + title: 1 + score: + $meta: 'searchScore' + + - + name: 'Match any Using explicit Mapping' + link: 'https://www.mongodb.com/docs/atlas/atlas-search/text/#match-any-using-explicit-mapping' + pipeline: + - + $search: + text: + path: 'plot' + query: 'boat race' + synonyms: 'my_synonyms' + matchCriteria: 'any' + - + $limit: 10 + - + $project: + _id: 0 + plot: 1 + title: 1 + score: + $meta: 'searchScore' + + - + name: 'Match all Using Synonyms' + link: 'https://www.mongodb.com/docs/atlas/atlas-search/text/#match-all-using-synonyms' + pipeline: + - + $search: + text: + path: 'plot' + query: 'automobile race' + matchCriteria: 'all' + synonyms: 'my_synonyms' + - + $limit: 20 + - + $project: + _id: 0 + plot: 1 + title: 1 + score: + $meta: 'searchScore' + + - + name: 'Wildcard Path' + link: 'https://www.mongodb.com/docs/atlas/atlas-search/text/' + pipeline: + - + $search: + text: + path: + wildcard: '*' + query: 'surfer' + - + $project: + _id: 0 + title: 1 + score: + $meta: 'searchScore' diff --git a/generator/config/search/wildcard.yaml b/generator/config/search/wildcard.yaml new file mode 100644 index 000000000..1dd256e1a --- /dev/null +++ b/generator/config/search/wildcard.yaml @@ -0,0 +1,60 @@ +# $schema: ../schema.json +name: wildcard +link: 'https://www.mongodb.com/docs/atlas/atlas-search/wildcard/' +type: + - searchOperator +encode: object +description: | + The wildcard operator enables queries which use special characters in the search string that can match any character. +arguments: + - + name: path + type: + - searchPath + - + name: query + type: + - string + - + name: allowAnalyzedField + optional: true + type: + - bool + - + name: score + optional: true + type: + - searchScore +tests: + - + name: 'Wildcard Path' + link: 'https://www.mongodb.com/docs/atlas/atlas-search/wildcard/#index-definition' + pipeline: + - + $search: + wildcard: + query: 'Wom?n *' + path: + wildcard: '*' + - + $limit: 5 + - + $project: + _id: 0 + title: 1 + + - + name: 'Escape Character Example' + link: 'https://www.mongodb.com/docs/atlas/atlas-search/wildcard/#escape-character-example' + pipeline: + - + $search: + wildcard: + query: '*\?' + path: 'title' + - + $limit: 5 + - + $project: + _id: 0 + title: 1 \ No newline at end of file diff --git a/generator/config/stage/search.yaml b/generator/config/stage/search.yaml index 8de703b53..c0b1c5c8b 100644 --- a/generator/config/stage/search.yaml +++ b/generator/config/stage/search.yaml @@ -3,11 +3,15 @@ name: $search link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/search/' type: - stage -encode: object +encode: search description: | Performs a full-text search of the field or fields in an Atlas collection. NOTE: $search is only available for MongoDB Atlas clusters, and is not available for self-managed deployments. arguments: + - + name: operator + type: + - searchOperator - name: index optional: true @@ -60,92 +64,6 @@ arguments: optional: true type: - object - # List of exclusive operators - - - name: autocomplete - optional: true - type: - - object - - - name: compound - optional: true - type: - - object - - - name: embeddedDocument - optional: true - type: - - object - - - name: equals - optional: true - type: - - object - - - name: exists - optional: true - type: - - object - - - name: geoShape - optional: true - type: - - object - - - name: geoWithin - optional: true - type: - - object - - - name: in - optional: true - type: - - object - - - name: moreLikeThis - optional: true - type: - - object - - - name: near - optional: true - type: - - object - - - name: phrase - optional: true - type: - - object - - - name: queryString - optional: true - type: - - object - - - name: range - optional: true - type: - - object - - - name: regex - optional: true - type: - - object - - - name: text - optional: true - type: - - object - - - name: wildcard - optional: true - type: - - object - - - name: facet - optional: true - type: - - object tests: - name: 'Example' @@ -172,1362 +90,3 @@ tests: $replaceWith: '$$SEARCH_META' - $limit: 1 - - - - name: 'Autocomplete Basic' - link: 'https://www.mongodb.com/docs/atlas/atlas-search/autocomplete/#basic-example' - pipeline: - - - $search: - autocomplete: - query: 'off' - path: 'title' - - - $limit: 10 - - - $project: - _id: 0 - title: 1 - - - - name: 'Autocomplete Fuzzy' - link: 'https://www.mongodb.com/docs/atlas/atlas-search/autocomplete/#fuzzy-example' - pipeline: - - - $search: - autocomplete: - query: 'pre' - path: 'title' - fuzzy: - maxEdits: 1 - prefixLength: 1 - maxExpansions: 256 - - - $limit: 10 - - - $project: - _id: 0 - title: 1 - - - - name: 'Autocomplete Token Order any' - link: 'https://www.mongodb.com/docs/atlas/atlas-search/autocomplete/#simple-any-example' - pipeline: - - - $search: - autocomplete: - query: 'men with' - path: 'title' - tokenOrder: 'any' - - - $limit: 4 - - - $project: - _id: 0 - title: 1 - - - - name: 'Autocomplete Token Order sequential' - link: 'https://www.mongodb.com/docs/atlas/atlas-search/autocomplete/#simple-sequential-example' - pipeline: - - - $search: - autocomplete: - query: 'men with' - path: 'title' - tokenOrder: 'sequential' - - - $limit: 4 - - - $project: - _id: 0 - title: 1 - - - - name: 'Autocomplete Highlighting' - link: 'https://www.mongodb.com/docs/atlas/atlas-search/autocomplete/#highlighting-example' - pipeline: - - - $search: - autocomplete: - query: 'ger' - path: 'title' - highlight: - path: 'title' - - - $limit: 5 - - - $project: - score: - $meta: 'searchScore' - _id: 0 - title: 1 - highlights: - $meta: 'searchHighlights' - - - - name: 'Autocomplete Across Multiple Fields' - link: 'https://www.mongodb.com/docs/atlas/atlas-search/autocomplete/#search-across-multiple-fields' - pipeline: - - - $search: - compound: - should: - - - autocomplete: - query: 'inter' - path: 'title' - - - autocomplete: - query: 'inter' - path: 'plot' - minimumShouldMatch: 1 - - - $limit: 10 - - - $project: - _id: 0 - title: 1 - plot: 1 - - - - name: 'Compound must and mustNot' - link: 'https://www.mongodb.com/docs/atlas/atlas-search/compound/#must-and-mustnot-example' - pipeline: - - - $search: - compound: - must: - - - text: - query: 'varieties' - path: 'description' - mustNot: - - - text: - query: 'apples' - path: 'description' - - - - name: 'Compound must and should' - link: 'https://www.mongodb.com/docs/atlas/atlas-search/compound/#must-and-should-example' - pipeline: - - - $search: - compound: - must: - - - text: - query: 'varieties' - path: 'description' - should: - - - text: - query: 'Fuji' - path: 'description' - - - $project: - score: - $meta: 'searchScore' - - - - name: 'Compound minimumShouldMatch' - link: 'https://www.mongodb.com/docs/atlas/atlas-search/compound/#minimumshouldmatch-example' - pipeline: - - - $search: - compound: - must: - - - text: - query: 'varieties' - path: 'description' - should: - - - text: - query: 'Fuji' - path: 'description' - - - text: - query: 'Golden Delicious' - path: 'description' - minimumShouldMatch: 1 - - - - name: 'Compound Filter' - link: 'https://www.mongodb.com/docs/atlas/atlas-search/compound/#filter-examples' - pipeline: - - - $search: - compound: - must: - - - text: - query: 'varieties' - path: 'description' - should: - - - text: - query: 'banana' - path: 'description' - filter: - - - text: - query: 'granny' - path: 'description' - - - - name: 'Compound Nested' - link: 'https://www.mongodb.com/docs/atlas/atlas-search/compound/#nested-example' - pipeline: - - - $search: - compound: - should: - - - text: - query: 'apple' - path: 'type' - - - compound: - must: - - - text: - query: 'organic' - path: 'category' - - - equals: - value: true - path: 'in_stock' - minimumShouldMatch: 1 - - - - name: 'EmbeddedDocument Basic' - link: 'https://www.mongodb.com/docs/atlas/atlas-search/embedded-document/#index-definition' - pipeline: - - - $search: - embeddedDocument: - path: 'items' - operator: - compound: - must: - - - text: - path: 'items.tags' - query: 'school' - should: - - - text: - path: 'items.name' - query: 'backpack' - score: - embedded: - aggregate: 'mean' - - - $limit: 5 - - - $project: - _id: 0 - items.name: 1 - items.tags: 1 - score: - $meta: 'searchScore' - - - - name: 'EmbeddedDocument Facet' - link: 'https://www.mongodb.com/docs/atlas/atlas-search/embedded-document/#facet-query' - pipeline: - - - $searchMeta: - facet: - operator: - embeddedDocument: - path: 'items' - operator: - compound: - must: - - - text: - path: 'items.tags' - query: 'school' - should: - - - text: - path: 'items.name' - query: 'backpack' - facets: - purchaseMethodFacet: - type: 'string' - path: 'purchaseMethod' - - - - name: 'EmbeddedDocument Query and Sort' - link: 'https://www.mongodb.com/docs/atlas/atlas-search/embedded-document/#query-and-sort' - pipeline: - - - $search: - embeddedDocument: - path: 'items' - operator: - text: - path: 'items.name' - query: 'laptop' - sort: - items.tags: 1 - - - $limit: 5 - - - $project: - _id: 0 - items.name: 1 - items.tags: 1 - score: - $meta: 'searchScore' - - - - name: 'Query for Matching Embedded Documents Only' - link: 'https://www.mongodb.com/docs/atlas/atlas-search/embedded-document/#query-for-matching-embedded-documents-only' - pipeline: - - - $search: - embeddedDocument: - path: 'items' - operator: - compound: - must: - - - range: - path: 'items.quantity' - gt: 2 - - - exists: - path: 'items.price' - - - text: - path: 'items.tags' - query: 'school' - - - $limit: 2 - - - $project: - _id: 0 - storeLocation: 1 - items: - $filter: - input: '$items' - cond: - $and: - - - $ifNull: - - '$$this.price' - - 'false' - - - $gt: - - '$$this.quantity' - - 2 - - - $in: - - 'office' - - '$$this.tags' - - - - name: 'Equals Boolean' - link: 'https://www.mongodb.com/docs/atlas/atlas-search/equals/#boolean-examples' - pipeline: - - - $search: - equals: - path: 'verified_user' - value: true - - - $project: - name: 1 - _id: 0 - score: - $meta: 'searchScore' - - - - name: 'Equals ObjectId' - link: 'https://www.mongodb.com/docs/atlas/atlas-search/equals/#objectid-example' - pipeline: - - - $search: - equals: - path: 'teammates' - value: !bson_objectId '5a9427648b0beebeb69589a1' - - - - name: 'Equals Date' - link: 'https://www.mongodb.com/docs/atlas/atlas-search/equals/#date-example' - pipeline: - - - $search: - equals: - path: 'account_created' - value: !bson_utcdatetime '2022-05-04T05:01:08.000+00:00' - - - - name: 'Equals Number' - link: 'https://www.mongodb.com/docs/atlas/atlas-search/equals/#number-example' - pipeline: - - - $search: - equals: - path: 'employee_number' - value: 259 - - - - name: 'Equals String' - link: 'https://www.mongodb.com/docs/atlas/atlas-search/equals/#string-example' - pipeline: - - - $search: - equals: - path: 'name' - value: 'jim hall' - - - - name: 'Equals UUID' - link: 'https://www.mongodb.com/docs/atlas/atlas-search/equals/#uuid-example' - pipeline: - - - $search: - equals: - path: 'uuid' - value: !bson_uuid 'fac32260-b511-4c69-8485-a2be5b7dda9e' - - - - name: 'Equals Null' - link: 'https://www.mongodb.com/docs/atlas/atlas-search/equals/#null-example' - pipeline: - - - $search: - equals: - path: 'job_title' - value: ~ - - - - name: 'Exists Basic' - link: 'https://www.mongodb.com/docs/atlas/atlas-search/exists/#basic-example' - pipeline: - - - $search: - exists: - path: 'type' - - - - name: 'Exists Embedded' - link: 'https://www.mongodb.com/docs/atlas/atlas-search/exists/#embedded-example' - pipeline: - - - $search: - exists: - path: 'quantities.lemons' - - - - name: 'Exists Compound' - link: 'https://www.mongodb.com/docs/atlas/atlas-search/exists/#compound-example' - pipeline: - - - $search: - compound: - must: - - - exists: - path: 'type' - - - text: - query: 'apple' - path: 'type' - should: - text: - query: 'fuji' - path: 'description' - - - - name: 'Facet' - link: 'https://www.mongodb.com/docs/atlas/atlas-search/facet/#examples' - pipeline: - - - $search: - facet: - operator: - near: - path: 'released' - origin: !bson_utcdatetime '1999-07-01T00:00:00.000+00:00' - pivot: 7776000000 - facets: - genresFacet: - type: 'string' - path: 'genres' - - - $limit: 2 - - - $facet: - docs: - - - $project: - title: 1 - released: 1 - meta: - - - $replaceWith: '$$SEARCH_META' - - - $limit: 1 - - - $set: - meta: - $arrayElemAt: - - '$meta' - - 0 - - - - name: 'GeoShape Disjoint' - link: 'https://www.mongodb.com/docs/atlas/atlas-search/geoShape/#disjoint-example' - pipeline: - - - $search: - geoShape: - relation: 'disjoint' - geometry: - type: 'Polygon' - coordinates: - - - - - - -161.323242 - - 22.512557 - - - - -152.446289 - - 22.065278 - - - - -156.09375 - - 17.811456 - - - - -161.323242 - - 22.512557 - path: 'address.location' - - - $limit: 3 - - - $project: - _id: 0 - name: 1 - address: 1 - score: - $meta: 'searchScore' - - - - name: 'GeoShape Intersect' - link: 'https://www.mongodb.com/docs/atlas/atlas-search/geoShape/#intersects-example' - pipeline: - - - $search: - geoShape: - relation: 'intersects' - geometry: - type: 'MultiPolygon' - coordinates: - - - - - - - - 2.16942 - - 41.40082 - - - - 2.17963 - - 41.40087 - - - - 2.18146 - - 41.39716 - - - - 2.15533 - - 41.40686 - - - - 2.14596 - - 41.38475 - - - - 2.17519 - - 41.41035 - - - - 2.16942 - - 41.40082 - - - - - - - - 2.16365 - - 41.39416 - - - - 2.16963 - - 41.39726 - - - - 2.15395 - - 41.38005 - - - - 2.17935 - - 41.43038 - - - - 2.16365 - - 41.39416 - path: 'address.location' - - - $limit: 3 - - - $project: - _id: 0 - name: 1 - address: 1 - score: - $meta: 'searchScore' - - - - name: 'GeoShape Within' - link: 'https://www.mongodb.com/docs/atlas/atlas-search/geoShape/#within-example' - pipeline: - - - $search: - geoShape: - relation: 'within' - geometry: - type: 'Polygon' - coordinates: - - - - - - -74.3994140625 - - 40.5305017757 - - - - -74.7290039063 - - 40.5805846641 - - - - -74.7729492188 - - 40.9467136651 - - - - -74.0698242188 - - 41.1290213475 - - - - -73.65234375 - - 40.9964840144 - - - - -72.6416015625 - - 40.9467136651 - - - - -72.3559570313 - - 40.7971774152 - - - - -74.3994140625 - - 40.5305017757 - path: 'address.location' - - - $limit: 3 - - - $project: - _id: 0 - name: 1 - address: 1 - score: - $meta: 'searchScore' - - - - name: 'GeoWithin box' - link: 'https://www.mongodb.com/docs/atlas/atlas-search/geoWithin/#box-example' - pipeline: - - - $search: - geoWithin: - path: 'address.location' - box: - bottomLeft: - type: 'Point' - coordinates: - - 112.467 - - -55.05 - topRight: - type: 'Point' - coordinates: - - 168 - - -9.133 - - - $limit: 3 - - - $project: - _id: 0 - name: 1 - address: 1 - - - - name: 'GeoWithin circle' - link: 'https://www.mongodb.com/docs/atlas/atlas-search/geoWithin/#circle-example' - pipeline: - - - $search: - geoWithin: - circle: - center: - type: 'Point' - coordinates: - - -73.54 - - 45.54 - radius: 1600 - path: 'address.location' - - - $limit: 3 - - - $project: - _id: 0 - name: 1 - address: 1 - - - - name: 'GeoWithin geometry' - link: 'https://www.mongodb.com/docs/atlas/atlas-search/geoWithin/#geometry-examples' - pipeline: - - - $search: - geoWithin: - geometry: - type: 'Polygon' - coordinates: - - - - - - -161.323242 - - 22.512557 - - - - -152.446289 - - 22.065278 - - - - -156.09375 - - 17.811456 - - - - -161.323242 - - 22.512557 - path: 'address.location' - - - $limit: 3 - - - $project: - _id: 0 - name: 1 - address: 1 - - - - name: 'In Single Value Field Match' - link: 'https://www.mongodb.com/docs/atlas/atlas-search/in/#examples' - pipeline: - - - $search: - in: - path: 'birthdate' - value: - - !bson_utcdatetime '1977-03-02T02:20:31.000+00:00' - - !bson_utcdatetime '1977-03-01T00:00:00.000+00:00' - - !bson_utcdatetime '1977-05-06T21:57:35.000+00:00' - - - $project: - _id: 0 - name: 1 - birthdate: 1 - - - - name: 'In Array Value Field Match' - link: 'https://www.mongodb.com/docs/atlas/atlas-search/in/#examples' - pipeline: - - - $search: - in: - path: 'accounts' - value: - - 371138 - - 371139 - - 371140 - - - $project: - _id: 0 - name: 1 - accounts: 1 - - - - name: 'In Compound Query Match' - link: 'https://www.mongodb.com/docs/atlas/atlas-search/in/#examples' - pipeline: - - - $search: - compound: - must: - - - in: - path: 'name' - value: - - 'james sanchez' - - 'jennifer lawrence' - should: - - - in: - path: '_id' - value: - - !bson_objectId '5ca4bbcea2dd94ee58162a72' - - !bson_objectId '5ca4bbcea2dd94ee58162a91' - - - $limit: 5 - - - $project: - _id: 1 - name: 1 - score: - $meta: 'searchScore' - - - - name: 'MoreLikeThis Single Document with Multiple Fields' - link: 'https://www.mongodb.com/docs/atlas/atlas-search/morelikethis/#example-1--single-document-with-multiple-fields' - pipeline: - - - $search: - moreLikeThis: - like: - title: 'The Godfather' - genres: 'action' - - - $limit: 5 - - - $project: - _id: 0 - title: 1 - released: 1 - genres: 1 - - - - name: 'MoreLikeThis Input Document Excluded in Results' - link: 'https://www.mongodb.com/docs/atlas/atlas-search/morelikethis/#example-2--input-document-excluded-in-results' - pipeline: - - - $search: - compound: - must: - - - moreLikeThis: - like: - _id: !bson_objectId '573a1396f29313caabce4a9a' - genres: - - 'Crime' - - 'Drama' - title: 'The Godfather' - mustNot: - - - equals: - path: '_id' - value: !bson_objectId '573a1396f29313caabce4a9a' - - - $limit: 5 - - - $project: - _id: 1 - title: 1 - released: 1 - genres: 1 - - - - name: 'MoreLikeThis Multiple Analyzers' - link: 'https://www.mongodb.com/docs/atlas/atlas-search/morelikethis/#example-3--multiple-analyzers' - pipeline: - - - $search: - compound: - should: - - - moreLikeThis: - like: - _id: !bson_objectId '573a1396f29313caabce4a9a' - genres: - - 'Crime' - - 'Drama' - title: 'The Godfather' - mustNot: - - - equals: - path: '_id' - value: !bson_objectId '573a1394f29313caabcde9ef' - - - $limit: 10 - - - $project: - title: 1 - genres: 1 - _id: 1 - - - - name: 'Near Number' - link: 'https://www.mongodb.com/docs/atlas/atlas-search/near/#number-example' - pipeline: - - - $search: - index: 'runtimes' - near: - path: 'runtime' - origin: 279 - pivot: 2 - - - $limit: 7 - - - $project: - _id: 0 - title: 1 - runtime: 1 - score: - $meta: 'searchScore' - - - - name: 'Near Date' - link: 'https://www.mongodb.com/docs/atlas/atlas-search/near/#date-example' - pipeline: - - - $search: - index: 'releaseddate' - near: - path: 'released' - origin: !bson_utcdatetime '1915-09-13T00:00:00.000+00:00' - pivot: 7776000000 - - - $limit: 3 - - - $project: - _id: 0 - title: 1 - released: 1 - score: - $meta: 'searchScore' - - - - name: 'Near GeoJSON Point' - link: 'https://www.mongodb.com/docs/atlas/atlas-search/near/#geojson-point-examples' - pipeline: - - - $search: - near: - origin: - type: 'Point' - coordinates: - - -8.61308 - - 41.1413 - pivot: 1000 - path: 'address.location' - - - $limit: 3 - - - $project: - _id: 0 - name: 1 - address: 1 - score: - $meta: 'searchScore' - - - - name: 'Near Compound' - link: 'https://www.mongodb.com/docs/atlas/atlas-search/near/#compound-example' - pipeline: - - - $search: - compound: - must: - text: - query: 'Apartment' - path: 'property_type' - should: - near: - origin: - type: 'Point' - coordinates: - - 114.15027 - - 22.28158 - pivot: 1000 - path: 'address.location' - - - $limit: 3 - - - $project: - _id: 0 - property_type: 1 - address: 1 - score: - $meta: 'searchScore' - - - - name: 'Single Phrase' - link: 'https://www.mongodb.com/docs/atlas/atlas-search/phrase/#single-phrase-example' - pipeline: - - - $search: - phrase: - path: 'title' - query: 'new york' - - - $limit: 10 - - - $project: - _id: 0 - title: 1 - score: - $meta: 'searchScore' - - - - name: 'Multiple Phrase' - link: 'https://www.mongodb.com/docs/atlas/atlas-search/phrase/#multiple-phrases-example' - pipeline: - - - $search: - phrase: - path: 'title' - query: - - 'the man' - - 'the moon' - - - $limit: 10 - - - $project: - _id: 0 - title: 1 - score: - $meta: 'searchScore' - - - - name: 'Phrase Slop' - link: 'https://www.mongodb.com/docs/atlas/atlas-search/phrase/#slop-example' - pipeline: - - - $search: - phrase: - path: 'title' - query: 'men women' - slop: 5 - - - $project: - _id: 0 - title: 1 - score: - $meta: 'searchScore' - - - - name: 'Phrase Synonyms' - link: 'https://www.mongodb.com/docs/atlas/atlas-search/phrase/#synonyms-example' - pipeline: - - - $search: - phrase: - path: 'plot' - query: 'automobile race' - slop: 5 - synonyms: 'my_synonyms' - - - $limit: 5 - - - $project: - _id: 0 - plot: 1 - title: 1 - score: - $meta: 'searchScore' - - # The various example from the doc are variations of the "query" parameter - # this is not pertinent for testing the aggregation builder, unless we create - # a queryString builder. - - - name: 'Boolean Operator Queries' - link: 'https://www.mongodb.com/docs/atlas/atlas-search/queryString/#boolean-operator-queries' - pipeline: - - - $search: - queryString: - defaultPath: 'title' - query: 'Rocky AND (IV OR 4 OR Four)' - - - $project: - _id: 0 - title: 1 - - - - name: 'Range Number gte lte' - link: 'https://www.mongodb.com/docs/atlas/atlas-search/range/#number-example' - pipeline: - - - $search: - range: - path: 'runtime' - gte: 2 - lte: 3 - - - $limit: 5 - - - $project: - _id: 0 - title: 1 - runtime: 1 - - - - name: 'Range Number lte' - link: 'https://www.mongodb.com/docs/atlas/atlas-search/range/#number-example' - pipeline: - - - $search: - range: - path: 'runtime' - lte: 2 - - - $limit: 5 - - - $project: - _id: 0 - title: 1 - runtime: 1 - score: - $meta: 'searchScore' - - - - name: 'Range Date' - link: 'https://www.mongodb.com/docs/atlas/atlas-search/range/#date-example' - pipeline: - - - $search: - range: - path: 'released' - gt: !bson_utcdatetime '2010-01-01T00:00:00.000Z' - lt: !bson_utcdatetime '2015-01-01T00:00:00.000Z' - - - $limit: 5 - - - $project: - _id: 0 - title: 1 - released: 1 - - - - name: 'Range ObjectId' - link: 'https://www.mongodb.com/docs/atlas/atlas-search/range/#objectid-example' - pipeline: - - - $search: - range: - path: '_id' - gte: !bson_objectId '573a1396f29313caabce4a9a' - lte: !bson_objectId '573a1396f29313caabce4ae7' - - - $project: - _id: 1 - title: 1 - released: 1 - - - - name: 'Range String' - link: 'https://www.mongodb.com/docs/atlas/atlas-search/range/#string-example' - pipeline: - - - $search: - range: - path: 'title' - gt: 'city' - lt: 'country' - - - $limit: 5 - - - $project: - _id: 0 - title: 1 - - - - name: 'Regex' - link: 'https://www.mongodb.com/docs/atlas/atlas-search/regex/#examples' - pipeline: - - - $search: - regex: - path: 'title' - query: '[0-9]{2} (.){4}s' - - - $project: - _id: 0 - title: 1 - - - - name: 'Text Wildcard Path' - link: 'https://www.mongodb.com/docs/atlas/atlas-search/text/' - pipeline: - - - $search: - text: - path: - wildcard: '*' - query: 'surfer' - - - $project: - _id: 0 - title: 1 - score: - $meta: 'searchScore' - - - - name: 'Text Basic' - link: 'https://www.mongodb.com/docs/atlas/atlas-search/text/#basic-example' - pipeline: - - - $search: - text: - path: 'title' - query: 'surfer' - - - $project: - _id: 0 - title: 1 - score: - $meta: 'searchScore' - - - name: 'Text Fuzzy Default' - link: 'https://www.mongodb.com/docs/atlas/atlas-search/text/#fuzzy-examples' - pipeline: - - - $search: - text: - path: 'title' - query: 'naw yark' - fuzzy: {} - - - $limit: 10 - - - $project: - _id: 0 - title: 1 - score: - $meta: 'searchScore' - - - - name: 'Text Fuzzy maxExpansions' - link: 'https://www.mongodb.com/docs/atlas/atlas-search/text/#fuzzy-examples' - pipeline: - - - $search: - text: - path: 'title' - query: 'naw yark' - fuzzy: - maxEdits: 1 - maxExpansions: 100 - - - $limit: 10 - - - $project: - _id: 0 - title: 1 - score: - $meta: 'searchScore' - - - - name: 'Text Fuzzy prefixLength' - link: 'https://www.mongodb.com/docs/atlas/atlas-search/text/#fuzzy-examples' - pipeline: - - - $search: - text: - path: 'title' - query: 'naw yark' - fuzzy: - maxEdits: 1 - prefixLength: 2 - - - $limit: 8 - - - $project: - _id: 1 - title: 1 - score: - $meta: 'searchScore' - - - - name: 'Text Match any Using equivalent Mapping' - link: 'https://www.mongodb.com/docs/atlas/atlas-search/text/#match-any-using-equivalent-mapping' - pipeline: - - - $search: - text: - path: 'plot' - query: 'attire' - synonyms: 'my_synonyms' - matchCriteria: 'any' - - - $limit: 5 - - - $project: - _id: 0 - plot: 1 - title: 1 - score: - $meta: 'searchScore' - - - - name: 'Text Match any Using explicit Mapping' - link: 'https://www.mongodb.com/docs/atlas/atlas-search/text/#match-any-using-explicit-mapping' - pipeline: - - - $search: - text: - path: 'plot' - query: 'boat race' - synonyms: 'my_synonyms' - matchCriteria: 'any' - - - $limit: 10 - - - $project: - _id: 0 - plot: 1 - title: 1 - score: - $meta: 'searchScore' - - - - name: 'Text Match all Using Synonyms' - link: 'https://www.mongodb.com/docs/atlas/atlas-search/text/#match-all-using-synonyms' - pipeline: - - - $search: - text: - path: 'plot' - query: 'automobile race' - matchCriteria: 'all' - synonyms: 'my_synonyms' - - - $limit: 20 - - - $project: - _id: 0 - plot: 1 - title: 1 - score: - $meta: 'searchScore' - - - - name: 'Wildcard' - link: 'https://www.mongodb.com/docs/atlas/atlas-search/wildcard/#index-definition' - pipeline: - - - $search: - wildcard: - query: 'Wom?n *' - path: - wildcard: '*' - - - $limit: 5 - - - $project: - _id: 0 - title: 1 - - - - name: 'Wildcard Escape Character Example' - link: 'https://www.mongodb.com/docs/atlas/atlas-search/wildcard/#escape-character-example' - pipeline: - - - $search: - wildcard: - query: '*\?' - path: 'title' - - - $limit: 5 - - - $project: - _id: 0 - title: 1 diff --git a/generator/config/stage/searchMeta.yaml b/generator/config/stage/searchMeta.yaml index 78aa3f236..7cdd43aa0 100644 --- a/generator/config/stage/searchMeta.yaml +++ b/generator/config/stage/searchMeta.yaml @@ -3,11 +3,15 @@ name: $searchMeta link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/searchMeta/' type: - stage -encode: object +encode: search description: | Returns different types of metadata result documents for the Atlas Search query against an Atlas collection. NOTE: $searchMeta is only available for MongoDB Atlas clusters running MongoDB v4.4.9 or higher, and is not available for self-managed deployments. arguments: + - + name: operator + type: + - searchOperator - name: index optional: true @@ -18,92 +22,6 @@ arguments: optional: true type: - object - # List of exclusive operators - - - name: autocomplete - optional: true - type: - - object - - - name: compound - optional: true - type: - - object - - - name: embeddedDocument - optional: true - type: - - object - - - name: equals - optional: true - type: - - object - - - name: exists - optional: true - type: - - object - - - name: geoShape - optional: true - type: - - object - - - name: geoWithin - optional: true - type: - - object - - - name: in - optional: true - type: - - object - - - name: moreLikeThis - optional: true - type: - - object - - - name: near - optional: true - type: - - object - - - name: phrase - optional: true - type: - - object - - - name: queryString - optional: true - type: - - object - - - name: range - optional: true - type: - - object - - - name: regex - optional: true - type: - - object - - - name: text - optional: true - type: - - object - - - name: wildcard - optional: true - type: - - object - - - name: facet - optional: true - type: - - object tests: - name: 'Example' diff --git a/generator/src/Definition/OperatorDefinition.php b/generator/src/Definition/OperatorDefinition.php index ce39fe4d7..48282f734 100644 --- a/generator/src/Definition/OperatorDefinition.php +++ b/generator/src/Definition/OperatorDefinition.php @@ -42,6 +42,7 @@ public function __construct( 'flat_object' => Encode::FlatObject, 'dollar_object' => Encode::DollarObject, 'group' => Encode::Group, + 'search' => Encode::Search, default => throw new UnexpectedValueException(sprintf('Unexpected "encode" value for operator "%s". Got "%s"', $name, $encode)), }; diff --git a/phpcs.xml.dist b/phpcs.xml.dist index de542ac64..da7f9ff8f 100644 --- a/phpcs.xml.dist +++ b/phpcs.xml.dist @@ -19,7 +19,7 @@ rector.php - src/Builder/(Accumulator|Expression|Query|Projection|Stage)/*\.php + src/Builder/(Accumulator|Expression|Query|Projection|Search|Stage)/*\.php diff --git a/src/Builder/Encoder/OperatorEncoder.php b/src/Builder/Encoder/OperatorEncoder.php index 69ef27c24..163660400 100644 --- a/src/Builder/Encoder/OperatorEncoder.php +++ b/src/Builder/Encoder/OperatorEncoder.php @@ -6,6 +6,8 @@ use LogicException; use MongoDB\Builder\Stage\GroupStage; +use MongoDB\Builder\Stage\SearchMetaStage; +use MongoDB\Builder\Stage\SearchStage; use MongoDB\Builder\Type\Encode; use MongoDB\Builder\Type\OperatorInterface; use MongoDB\Builder\Type\Optional; @@ -44,6 +46,7 @@ public function encode(mixed $value): stdClass Encode::Object, Encode::FlatObject => $this->encodeAsObject($value), Encode::DollarObject => $this->encodeAsDollarObject($value), Encode::Group => $this->encodeAsGroup($value), + Encode::Search => $this->encodeAsSearch($value), default => throw new LogicException(sprintf('Class "%s" does not have a valid ENCODE constant.', $value::class)), }; } @@ -112,6 +115,32 @@ private function encodeAsGroup(OperatorInterface $value): stdClass return $this->wrap($value, $result); } + private function encodeAsSearch(OperatorInterface $value): stdClass + { + assert($value instanceof SearchStage || $value instanceof SearchMetaStage); + + $result = new stdClass(); + foreach (get_object_vars($value) as $key => $val) { + // Skip optional arguments. If they have a default value, it is resolved by the server. + if ($val === Optional::Undefined) { + continue; + } + + // Merge the operator properties into the result object + if ($key === 'operator') { + foreach (get_object_vars($this->recursiveEncode($val)) as $subkey => $subval) { + $result->{$subkey} = $subval; + } + + continue; + } + + $result->{$key} = $this->recursiveEncode($val); + } + + return $this->wrap($value, $result); + } + private function encodeAsObject(OperatorInterface $value): stdClass { $result = new stdClass(); diff --git a/src/Builder/Search.php b/src/Builder/Search.php new file mode 100644 index 000000000..7cab84641 --- /dev/null +++ b/src/Builder/Search.php @@ -0,0 +1,10 @@ +path = $path; + $this->query = $query; + $this->tokenOrder = $tokenOrder; + $this->fuzzy = $fuzzy; + $this->score = $score; + } + + public function getOperator(): string + { + return 'autocomplete'; + } +} diff --git a/src/Builder/Search/CompoundOperator.php b/src/Builder/Search/CompoundOperator.php new file mode 100644 index 000000000..99a69ea07 --- /dev/null +++ b/src/Builder/Search/CompoundOperator.php @@ -0,0 +1,78 @@ +must = $must; + $this->mustNot = $mustNot; + $this->should = $should; + $this->filter = $filter; + $this->minimumShouldMatch = $minimumShouldMatch; + $this->score = $score; + } + + public function getOperator(): string + { + return 'compound'; + } +} diff --git a/src/Builder/Search/EmbeddedDocumentOperator.php b/src/Builder/Search/EmbeddedDocumentOperator.php new file mode 100644 index 000000000..ab04b3286 --- /dev/null +++ b/src/Builder/Search/EmbeddedDocumentOperator.php @@ -0,0 +1,59 @@ +path = $path; + $this->operator = $operator; + $this->score = $score; + } + + public function getOperator(): string + { + return 'embeddedDocument'; + } +} diff --git a/src/Builder/Search/EqualsOperator.php b/src/Builder/Search/EqualsOperator.php new file mode 100644 index 000000000..1618cde56 --- /dev/null +++ b/src/Builder/Search/EqualsOperator.php @@ -0,0 +1,61 @@ +path = $path; + $this->value = $value; + $this->score = $score; + } + + public function getOperator(): string + { + return 'equals'; + } +} diff --git a/src/Builder/Search/ExistsOperator.php b/src/Builder/Search/ExistsOperator.php new file mode 100644 index 000000000..2b14e8361 --- /dev/null +++ b/src/Builder/Search/ExistsOperator.php @@ -0,0 +1,50 @@ +path = $path; + $this->score = $score; + } + + public function getOperator(): string + { + return 'exists'; + } +} diff --git a/src/Builder/Search/FacetOperator.php b/src/Builder/Search/FacetOperator.php new file mode 100644 index 000000000..2d58c8bc0 --- /dev/null +++ b/src/Builder/Search/FacetOperator.php @@ -0,0 +1,51 @@ +facets = $facets; + $this->operator = $operator; + } + + public function getOperator(): string + { + return 'facet'; + } +} diff --git a/src/Builder/Search/FactoryTrait.php b/src/Builder/Search/FactoryTrait.php new file mode 100644 index 000000000..b3e2e5747 --- /dev/null +++ b/src/Builder/Search/FactoryTrait.php @@ -0,0 +1,345 @@ +path = $path; + $this->relation = $relation; + $this->geometry = $geometry; + $this->score = $score; + } + + public function getOperator(): string + { + return 'geoShape'; + } +} diff --git a/src/Builder/Search/GeoWithinOperator.php b/src/Builder/Search/GeoWithinOperator.php new file mode 100644 index 000000000..4782a26bc --- /dev/null +++ b/src/Builder/Search/GeoWithinOperator.php @@ -0,0 +1,71 @@ +path = $path; + $this->box = $box; + $this->circle = $circle; + $this->geometry = $geometry; + $this->score = $score; + } + + public function getOperator(): string + { + return 'geoWithin'; + } +} diff --git a/src/Builder/Search/InOperator.php b/src/Builder/Search/InOperator.php new file mode 100644 index 000000000..2729472c8 --- /dev/null +++ b/src/Builder/Search/InOperator.php @@ -0,0 +1,67 @@ +path = $path; + if (is_array($value) && ! array_is_list($value)) { + throw new InvalidArgumentException('Expected $value argument to be a list, got an associative array.'); + } + + $this->value = $value; + $this->score = $score; + } + + public function getOperator(): string + { + return 'in'; + } +} diff --git a/src/Builder/Search/MoreLikeThisOperator.php b/src/Builder/Search/MoreLikeThisOperator.php new file mode 100644 index 000000000..aadf2895e --- /dev/null +++ b/src/Builder/Search/MoreLikeThisOperator.php @@ -0,0 +1,54 @@ +like = $like; + $this->score = $score; + } + + public function getOperator(): string + { + return 'moreLikeThis'; + } +} diff --git a/src/Builder/Search/NearOperator.php b/src/Builder/Search/NearOperator.php new file mode 100644 index 000000000..8af334878 --- /dev/null +++ b/src/Builder/Search/NearOperator.php @@ -0,0 +1,66 @@ +path = $path; + $this->origin = $origin; + $this->pivot = $pivot; + $this->score = $score; + } + + public function getOperator(): string + { + return 'near'; + } +} diff --git a/src/Builder/Search/PhraseOperator.php b/src/Builder/Search/PhraseOperator.php new file mode 100644 index 000000000..99854e458 --- /dev/null +++ b/src/Builder/Search/PhraseOperator.php @@ -0,0 +1,78 @@ +path = $path; + if (is_array($query) && ! array_is_list($query)) { + throw new InvalidArgumentException('Expected $query argument to be a list, got an associative array.'); + } + + $this->query = $query; + $this->slop = $slop; + $this->synonyms = $synonyms; + $this->score = $score; + } + + public function getOperator(): string + { + return 'phrase'; + } +} diff --git a/src/Builder/Search/QueryStringOperator.php b/src/Builder/Search/QueryStringOperator.php new file mode 100644 index 000000000..e7f60f900 --- /dev/null +++ b/src/Builder/Search/QueryStringOperator.php @@ -0,0 +1,42 @@ +defaultPath = $defaultPath; + $this->query = $query; + } + + public function getOperator(): string + { + return 'queryString'; + } +} diff --git a/src/Builder/Search/RangeOperator.php b/src/Builder/Search/RangeOperator.php new file mode 100644 index 000000000..23c7ba94d --- /dev/null +++ b/src/Builder/Search/RangeOperator.php @@ -0,0 +1,79 @@ +path = $path; + $this->gt = $gt; + $this->gte = $gte; + $this->lt = $lt; + $this->lte = $lte; + $this->score = $score; + } + + public function getOperator(): string + { + return 'range'; + } +} diff --git a/src/Builder/Search/RegexOperator.php b/src/Builder/Search/RegexOperator.php new file mode 100644 index 000000000..97a4df5e2 --- /dev/null +++ b/src/Builder/Search/RegexOperator.php @@ -0,0 +1,63 @@ +path = $path; + $this->query = $query; + $this->allowAnalyzedField = $allowAnalyzedField; + $this->score = $score; + } + + public function getOperator(): string + { + return 'regex'; + } +} diff --git a/src/Builder/Search/TextOperator.php b/src/Builder/Search/TextOperator.php new file mode 100644 index 000000000..afb8a673a --- /dev/null +++ b/src/Builder/Search/TextOperator.php @@ -0,0 +1,75 @@ +path = $path; + $this->query = $query; + $this->fuzzy = $fuzzy; + $this->matchCriteria = $matchCriteria; + $this->synonyms = $synonyms; + $this->score = $score; + } + + public function getOperator(): string + { + return 'text'; + } +} diff --git a/src/Builder/Search/WildcardOperator.php b/src/Builder/Search/WildcardOperator.php new file mode 100644 index 000000000..57f3934c2 --- /dev/null +++ b/src/Builder/Search/WildcardOperator.php @@ -0,0 +1,62 @@ +path = $path; + $this->query = $query; + $this->allowAnalyzedField = $allowAnalyzedField; + $this->score = $score; + } + + public function getOperator(): string + { + return 'wildcard'; + } +} diff --git a/src/Builder/Stage/FactoryTrait.php b/src/Builder/Stage/FactoryTrait.php index d1d83d82e..abf8f10a0 100644 --- a/src/Builder/Stage/FactoryTrait.php +++ b/src/Builder/Stage/FactoryTrait.php @@ -24,6 +24,7 @@ use MongoDB\Builder\Type\ExpressionInterface; use MongoDB\Builder\Type\Optional; use MongoDB\Builder\Type\QueryInterface; +use MongoDB\Builder\Type\SearchOperatorInterface; use MongoDB\Builder\Type\Sort; use MongoDB\Model\BSONArray; use stdClass; @@ -537,6 +538,7 @@ public static function sample(int $size): SampleStage * NOTE: $search is only available for MongoDB Atlas clusters, and is not available for self-managed deployments. * * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/search/ + * @param Document|SearchOperatorInterface|Serializable|array|stdClass $operator * @param Optional|string $index * @param Optional|Document|Serializable|array|stdClass $highlight * @param Optional|bool $concurrent @@ -547,25 +549,9 @@ public static function sample(int $size): SampleStage * @param Optional|Document|Serializable|array|stdClass $sort * @param Optional|bool $returnStoredSource * @param Optional|Document|Serializable|array|stdClass $tracking - * @param Optional|Document|Serializable|array|stdClass $autocomplete - * @param Optional|Document|Serializable|array|stdClass $compound - * @param Optional|Document|Serializable|array|stdClass $embeddedDocument - * @param Optional|Document|Serializable|array|stdClass $equals - * @param Optional|Document|Serializable|array|stdClass $exists - * @param Optional|Document|Serializable|array|stdClass $geoShape - * @param Optional|Document|Serializable|array|stdClass $geoWithin - * @param Optional|Document|Serializable|array|stdClass $in - * @param Optional|Document|Serializable|array|stdClass $moreLikeThis - * @param Optional|Document|Serializable|array|stdClass $near - * @param Optional|Document|Serializable|array|stdClass $phrase - * @param Optional|Document|Serializable|array|stdClass $queryString - * @param Optional|Document|Serializable|array|stdClass $range - * @param Optional|Document|Serializable|array|stdClass $regex - * @param Optional|Document|Serializable|array|stdClass $text - * @param Optional|Document|Serializable|array|stdClass $wildcard - * @param Optional|Document|Serializable|array|stdClass $facet */ public static function search( + Document|Serializable|SearchOperatorInterface|stdClass|array $operator, Optional|string $index = Optional::Undefined, Optional|Document|Serializable|stdClass|array $highlight = Optional::Undefined, Optional|bool $concurrent = Optional::Undefined, @@ -576,25 +562,8 @@ public static function search( Optional|Document|Serializable|stdClass|array $sort = Optional::Undefined, Optional|bool $returnStoredSource = Optional::Undefined, Optional|Document|Serializable|stdClass|array $tracking = Optional::Undefined, - Optional|Document|Serializable|stdClass|array $autocomplete = Optional::Undefined, - Optional|Document|Serializable|stdClass|array $compound = Optional::Undefined, - Optional|Document|Serializable|stdClass|array $embeddedDocument = Optional::Undefined, - Optional|Document|Serializable|stdClass|array $equals = Optional::Undefined, - Optional|Document|Serializable|stdClass|array $exists = Optional::Undefined, - Optional|Document|Serializable|stdClass|array $geoShape = Optional::Undefined, - Optional|Document|Serializable|stdClass|array $geoWithin = Optional::Undefined, - Optional|Document|Serializable|stdClass|array $in = Optional::Undefined, - Optional|Document|Serializable|stdClass|array $moreLikeThis = Optional::Undefined, - Optional|Document|Serializable|stdClass|array $near = Optional::Undefined, - Optional|Document|Serializable|stdClass|array $phrase = Optional::Undefined, - Optional|Document|Serializable|stdClass|array $queryString = Optional::Undefined, - Optional|Document|Serializable|stdClass|array $range = Optional::Undefined, - Optional|Document|Serializable|stdClass|array $regex = Optional::Undefined, - Optional|Document|Serializable|stdClass|array $text = Optional::Undefined, - Optional|Document|Serializable|stdClass|array $wildcard = Optional::Undefined, - Optional|Document|Serializable|stdClass|array $facet = Optional::Undefined, ): SearchStage { - return new SearchStage($index, $highlight, $concurrent, $count, $searchAfter, $searchBefore, $scoreDetails, $sort, $returnStoredSource, $tracking, $autocomplete, $compound, $embeddedDocument, $equals, $exists, $geoShape, $geoWithin, $in, $moreLikeThis, $near, $phrase, $queryString, $range, $regex, $text, $wildcard, $facet); + return new SearchStage($operator, $index, $highlight, $concurrent, $count, $searchAfter, $searchBefore, $scoreDetails, $sort, $returnStoredSource, $tracking); } /** @@ -602,48 +571,16 @@ public static function search( * NOTE: $searchMeta is only available for MongoDB Atlas clusters running MongoDB v4.4.9 or higher, and is not available for self-managed deployments. * * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/searchMeta/ + * @param Document|SearchOperatorInterface|Serializable|array|stdClass $operator * @param Optional|string $index * @param Optional|Document|Serializable|array|stdClass $count - * @param Optional|Document|Serializable|array|stdClass $autocomplete - * @param Optional|Document|Serializable|array|stdClass $compound - * @param Optional|Document|Serializable|array|stdClass $embeddedDocument - * @param Optional|Document|Serializable|array|stdClass $equals - * @param Optional|Document|Serializable|array|stdClass $exists - * @param Optional|Document|Serializable|array|stdClass $geoShape - * @param Optional|Document|Serializable|array|stdClass $geoWithin - * @param Optional|Document|Serializable|array|stdClass $in - * @param Optional|Document|Serializable|array|stdClass $moreLikeThis - * @param Optional|Document|Serializable|array|stdClass $near - * @param Optional|Document|Serializable|array|stdClass $phrase - * @param Optional|Document|Serializable|array|stdClass $queryString - * @param Optional|Document|Serializable|array|stdClass $range - * @param Optional|Document|Serializable|array|stdClass $regex - * @param Optional|Document|Serializable|array|stdClass $text - * @param Optional|Document|Serializable|array|stdClass $wildcard - * @param Optional|Document|Serializable|array|stdClass $facet */ public static function searchMeta( + Document|Serializable|SearchOperatorInterface|stdClass|array $operator, Optional|string $index = Optional::Undefined, Optional|Document|Serializable|stdClass|array $count = Optional::Undefined, - Optional|Document|Serializable|stdClass|array $autocomplete = Optional::Undefined, - Optional|Document|Serializable|stdClass|array $compound = Optional::Undefined, - Optional|Document|Serializable|stdClass|array $embeddedDocument = Optional::Undefined, - Optional|Document|Serializable|stdClass|array $equals = Optional::Undefined, - Optional|Document|Serializable|stdClass|array $exists = Optional::Undefined, - Optional|Document|Serializable|stdClass|array $geoShape = Optional::Undefined, - Optional|Document|Serializable|stdClass|array $geoWithin = Optional::Undefined, - Optional|Document|Serializable|stdClass|array $in = Optional::Undefined, - Optional|Document|Serializable|stdClass|array $moreLikeThis = Optional::Undefined, - Optional|Document|Serializable|stdClass|array $near = Optional::Undefined, - Optional|Document|Serializable|stdClass|array $phrase = Optional::Undefined, - Optional|Document|Serializable|stdClass|array $queryString = Optional::Undefined, - Optional|Document|Serializable|stdClass|array $range = Optional::Undefined, - Optional|Document|Serializable|stdClass|array $regex = Optional::Undefined, - Optional|Document|Serializable|stdClass|array $text = Optional::Undefined, - Optional|Document|Serializable|stdClass|array $wildcard = Optional::Undefined, - Optional|Document|Serializable|stdClass|array $facet = Optional::Undefined, ): SearchMetaStage { - return new SearchMetaStage($index, $count, $autocomplete, $compound, $embeddedDocument, $equals, $exists, $geoShape, $geoWithin, $in, $moreLikeThis, $near, $phrase, $queryString, $range, $regex, $text, $wildcard, $facet); + return new SearchMetaStage($operator, $index, $count); } /** diff --git a/src/Builder/Stage/FluentFactoryTrait.php b/src/Builder/Stage/FluentFactoryTrait.php index 101b259f3..ab0793f53 100644 --- a/src/Builder/Stage/FluentFactoryTrait.php +++ b/src/Builder/Stage/FluentFactoryTrait.php @@ -26,6 +26,7 @@ use MongoDB\Builder\Type\FieldQueryInterface; use MongoDB\Builder\Type\Optional; use MongoDB\Builder\Type\QueryInterface; +use MongoDB\Builder\Type\SearchOperatorInterface; use MongoDB\Builder\Type\Sort; use MongoDB\Builder\Type\StageInterface; use MongoDB\Model\BSONArray; @@ -605,6 +606,7 @@ public function sample(int $size): static * NOTE: $search is only available for MongoDB Atlas clusters, and is not available for self-managed deployments. * * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/search/ + * @param Document|SearchOperatorInterface|Serializable|array|stdClass $operator * @param Optional|string $index * @param Optional|Document|Serializable|array|stdClass $highlight * @param Optional|bool $concurrent @@ -615,25 +617,9 @@ public function sample(int $size): static * @param Optional|Document|Serializable|array|stdClass $sort * @param Optional|bool $returnStoredSource * @param Optional|Document|Serializable|array|stdClass $tracking - * @param Optional|Document|Serializable|array|stdClass $autocomplete - * @param Optional|Document|Serializable|array|stdClass $compound - * @param Optional|Document|Serializable|array|stdClass $embeddedDocument - * @param Optional|Document|Serializable|array|stdClass $equals - * @param Optional|Document|Serializable|array|stdClass $exists - * @param Optional|Document|Serializable|array|stdClass $geoShape - * @param Optional|Document|Serializable|array|stdClass $geoWithin - * @param Optional|Document|Serializable|array|stdClass $in - * @param Optional|Document|Serializable|array|stdClass $moreLikeThis - * @param Optional|Document|Serializable|array|stdClass $near - * @param Optional|Document|Serializable|array|stdClass $phrase - * @param Optional|Document|Serializable|array|stdClass $queryString - * @param Optional|Document|Serializable|array|stdClass $range - * @param Optional|Document|Serializable|array|stdClass $regex - * @param Optional|Document|Serializable|array|stdClass $text - * @param Optional|Document|Serializable|array|stdClass $wildcard - * @param Optional|Document|Serializable|array|stdClass $facet */ public function search( + Document|Serializable|SearchOperatorInterface|stdClass|array $operator, Optional|string $index = Optional::Undefined, Optional|Document|Serializable|stdClass|array $highlight = Optional::Undefined, Optional|bool $concurrent = Optional::Undefined, @@ -644,25 +630,8 @@ public function search( Optional|Document|Serializable|stdClass|array $sort = Optional::Undefined, Optional|bool $returnStoredSource = Optional::Undefined, Optional|Document|Serializable|stdClass|array $tracking = Optional::Undefined, - Optional|Document|Serializable|stdClass|array $autocomplete = Optional::Undefined, - Optional|Document|Serializable|stdClass|array $compound = Optional::Undefined, - Optional|Document|Serializable|stdClass|array $embeddedDocument = Optional::Undefined, - Optional|Document|Serializable|stdClass|array $equals = Optional::Undefined, - Optional|Document|Serializable|stdClass|array $exists = Optional::Undefined, - Optional|Document|Serializable|stdClass|array $geoShape = Optional::Undefined, - Optional|Document|Serializable|stdClass|array $geoWithin = Optional::Undefined, - Optional|Document|Serializable|stdClass|array $in = Optional::Undefined, - Optional|Document|Serializable|stdClass|array $moreLikeThis = Optional::Undefined, - Optional|Document|Serializable|stdClass|array $near = Optional::Undefined, - Optional|Document|Serializable|stdClass|array $phrase = Optional::Undefined, - Optional|Document|Serializable|stdClass|array $queryString = Optional::Undefined, - Optional|Document|Serializable|stdClass|array $range = Optional::Undefined, - Optional|Document|Serializable|stdClass|array $regex = Optional::Undefined, - Optional|Document|Serializable|stdClass|array $text = Optional::Undefined, - Optional|Document|Serializable|stdClass|array $wildcard = Optional::Undefined, - Optional|Document|Serializable|stdClass|array $facet = Optional::Undefined, ): static { - $this->pipeline[] = Stage::search($index, $highlight, $concurrent, $count, $searchAfter, $searchBefore, $scoreDetails, $sort, $returnStoredSource, $tracking, $autocomplete, $compound, $embeddedDocument, $equals, $exists, $geoShape, $geoWithin, $in, $moreLikeThis, $near, $phrase, $queryString, $range, $regex, $text, $wildcard, $facet); + $this->pipeline[] = Stage::search($operator, $index, $highlight, $concurrent, $count, $searchAfter, $searchBefore, $scoreDetails, $sort, $returnStoredSource, $tracking); return $this; } @@ -672,48 +641,16 @@ public function search( * NOTE: $searchMeta is only available for MongoDB Atlas clusters running MongoDB v4.4.9 or higher, and is not available for self-managed deployments. * * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/searchMeta/ + * @param Document|SearchOperatorInterface|Serializable|array|stdClass $operator * @param Optional|string $index * @param Optional|Document|Serializable|array|stdClass $count - * @param Optional|Document|Serializable|array|stdClass $autocomplete - * @param Optional|Document|Serializable|array|stdClass $compound - * @param Optional|Document|Serializable|array|stdClass $embeddedDocument - * @param Optional|Document|Serializable|array|stdClass $equals - * @param Optional|Document|Serializable|array|stdClass $exists - * @param Optional|Document|Serializable|array|stdClass $geoShape - * @param Optional|Document|Serializable|array|stdClass $geoWithin - * @param Optional|Document|Serializable|array|stdClass $in - * @param Optional|Document|Serializable|array|stdClass $moreLikeThis - * @param Optional|Document|Serializable|array|stdClass $near - * @param Optional|Document|Serializable|array|stdClass $phrase - * @param Optional|Document|Serializable|array|stdClass $queryString - * @param Optional|Document|Serializable|array|stdClass $range - * @param Optional|Document|Serializable|array|stdClass $regex - * @param Optional|Document|Serializable|array|stdClass $text - * @param Optional|Document|Serializable|array|stdClass $wildcard - * @param Optional|Document|Serializable|array|stdClass $facet */ public function searchMeta( + Document|Serializable|SearchOperatorInterface|stdClass|array $operator, Optional|string $index = Optional::Undefined, Optional|Document|Serializable|stdClass|array $count = Optional::Undefined, - Optional|Document|Serializable|stdClass|array $autocomplete = Optional::Undefined, - Optional|Document|Serializable|stdClass|array $compound = Optional::Undefined, - Optional|Document|Serializable|stdClass|array $embeddedDocument = Optional::Undefined, - Optional|Document|Serializable|stdClass|array $equals = Optional::Undefined, - Optional|Document|Serializable|stdClass|array $exists = Optional::Undefined, - Optional|Document|Serializable|stdClass|array $geoShape = Optional::Undefined, - Optional|Document|Serializable|stdClass|array $geoWithin = Optional::Undefined, - Optional|Document|Serializable|stdClass|array $in = Optional::Undefined, - Optional|Document|Serializable|stdClass|array $moreLikeThis = Optional::Undefined, - Optional|Document|Serializable|stdClass|array $near = Optional::Undefined, - Optional|Document|Serializable|stdClass|array $phrase = Optional::Undefined, - Optional|Document|Serializable|stdClass|array $queryString = Optional::Undefined, - Optional|Document|Serializable|stdClass|array $range = Optional::Undefined, - Optional|Document|Serializable|stdClass|array $regex = Optional::Undefined, - Optional|Document|Serializable|stdClass|array $text = Optional::Undefined, - Optional|Document|Serializable|stdClass|array $wildcard = Optional::Undefined, - Optional|Document|Serializable|stdClass|array $facet = Optional::Undefined, ): static { - $this->pipeline[] = Stage::searchMeta($index, $count, $autocomplete, $compound, $embeddedDocument, $equals, $exists, $geoShape, $geoWithin, $in, $moreLikeThis, $near, $phrase, $queryString, $range, $regex, $text, $wildcard, $facet); + $this->pipeline[] = Stage::searchMeta($operator, $index, $count); return $this; } diff --git a/src/Builder/Stage/SearchMetaStage.php b/src/Builder/Stage/SearchMetaStage.php index 750840487..f0a76503c 100644 --- a/src/Builder/Stage/SearchMetaStage.php +++ b/src/Builder/Stage/SearchMetaStage.php @@ -13,6 +13,7 @@ use MongoDB\Builder\Type\Encode; use MongoDB\Builder\Type\OperatorInterface; use MongoDB\Builder\Type\Optional; +use MongoDB\Builder\Type\SearchOperatorInterface; use MongoDB\Builder\Type\StageInterface; use stdClass; @@ -24,7 +25,10 @@ */ class SearchMetaStage implements StageInterface, OperatorInterface { - public const ENCODE = Encode::Object; + public const ENCODE = Encode::Search; + + /** @var Document|SearchOperatorInterface|Serializable|array|stdClass $operator */ + public readonly Document|Serializable|SearchOperatorInterface|stdClass|array $operator; /** @var Optional|string $index */ public readonly Optional|string $index; @@ -32,118 +36,19 @@ class SearchMetaStage implements StageInterface, OperatorInterface /** @var Optional|Document|Serializable|array|stdClass $count */ public readonly Optional|Document|Serializable|stdClass|array $count; - /** @var Optional|Document|Serializable|array|stdClass $autocomplete */ - public readonly Optional|Document|Serializable|stdClass|array $autocomplete; - - /** @var Optional|Document|Serializable|array|stdClass $compound */ - public readonly Optional|Document|Serializable|stdClass|array $compound; - - /** @var Optional|Document|Serializable|array|stdClass $embeddedDocument */ - public readonly Optional|Document|Serializable|stdClass|array $embeddedDocument; - - /** @var Optional|Document|Serializable|array|stdClass $equals */ - public readonly Optional|Document|Serializable|stdClass|array $equals; - - /** @var Optional|Document|Serializable|array|stdClass $exists */ - public readonly Optional|Document|Serializable|stdClass|array $exists; - - /** @var Optional|Document|Serializable|array|stdClass $geoShape */ - public readonly Optional|Document|Serializable|stdClass|array $geoShape; - - /** @var Optional|Document|Serializable|array|stdClass $geoWithin */ - public readonly Optional|Document|Serializable|stdClass|array $geoWithin; - - /** @var Optional|Document|Serializable|array|stdClass $in */ - public readonly Optional|Document|Serializable|stdClass|array $in; - - /** @var Optional|Document|Serializable|array|stdClass $moreLikeThis */ - public readonly Optional|Document|Serializable|stdClass|array $moreLikeThis; - - /** @var Optional|Document|Serializable|array|stdClass $near */ - public readonly Optional|Document|Serializable|stdClass|array $near; - - /** @var Optional|Document|Serializable|array|stdClass $phrase */ - public readonly Optional|Document|Serializable|stdClass|array $phrase; - - /** @var Optional|Document|Serializable|array|stdClass $queryString */ - public readonly Optional|Document|Serializable|stdClass|array $queryString; - - /** @var Optional|Document|Serializable|array|stdClass $range */ - public readonly Optional|Document|Serializable|stdClass|array $range; - - /** @var Optional|Document|Serializable|array|stdClass $regex */ - public readonly Optional|Document|Serializable|stdClass|array $regex; - - /** @var Optional|Document|Serializable|array|stdClass $text */ - public readonly Optional|Document|Serializable|stdClass|array $text; - - /** @var Optional|Document|Serializable|array|stdClass $wildcard */ - public readonly Optional|Document|Serializable|stdClass|array $wildcard; - - /** @var Optional|Document|Serializable|array|stdClass $facet */ - public readonly Optional|Document|Serializable|stdClass|array $facet; - /** + * @param Document|SearchOperatorInterface|Serializable|array|stdClass $operator * @param Optional|string $index * @param Optional|Document|Serializable|array|stdClass $count - * @param Optional|Document|Serializable|array|stdClass $autocomplete - * @param Optional|Document|Serializable|array|stdClass $compound - * @param Optional|Document|Serializable|array|stdClass $embeddedDocument - * @param Optional|Document|Serializable|array|stdClass $equals - * @param Optional|Document|Serializable|array|stdClass $exists - * @param Optional|Document|Serializable|array|stdClass $geoShape - * @param Optional|Document|Serializable|array|stdClass $geoWithin - * @param Optional|Document|Serializable|array|stdClass $in - * @param Optional|Document|Serializable|array|stdClass $moreLikeThis - * @param Optional|Document|Serializable|array|stdClass $near - * @param Optional|Document|Serializable|array|stdClass $phrase - * @param Optional|Document|Serializable|array|stdClass $queryString - * @param Optional|Document|Serializable|array|stdClass $range - * @param Optional|Document|Serializable|array|stdClass $regex - * @param Optional|Document|Serializable|array|stdClass $text - * @param Optional|Document|Serializable|array|stdClass $wildcard - * @param Optional|Document|Serializable|array|stdClass $facet */ public function __construct( + Document|Serializable|SearchOperatorInterface|stdClass|array $operator, Optional|string $index = Optional::Undefined, Optional|Document|Serializable|stdClass|array $count = Optional::Undefined, - Optional|Document|Serializable|stdClass|array $autocomplete = Optional::Undefined, - Optional|Document|Serializable|stdClass|array $compound = Optional::Undefined, - Optional|Document|Serializable|stdClass|array $embeddedDocument = Optional::Undefined, - Optional|Document|Serializable|stdClass|array $equals = Optional::Undefined, - Optional|Document|Serializable|stdClass|array $exists = Optional::Undefined, - Optional|Document|Serializable|stdClass|array $geoShape = Optional::Undefined, - Optional|Document|Serializable|stdClass|array $geoWithin = Optional::Undefined, - Optional|Document|Serializable|stdClass|array $in = Optional::Undefined, - Optional|Document|Serializable|stdClass|array $moreLikeThis = Optional::Undefined, - Optional|Document|Serializable|stdClass|array $near = Optional::Undefined, - Optional|Document|Serializable|stdClass|array $phrase = Optional::Undefined, - Optional|Document|Serializable|stdClass|array $queryString = Optional::Undefined, - Optional|Document|Serializable|stdClass|array $range = Optional::Undefined, - Optional|Document|Serializable|stdClass|array $regex = Optional::Undefined, - Optional|Document|Serializable|stdClass|array $text = Optional::Undefined, - Optional|Document|Serializable|stdClass|array $wildcard = Optional::Undefined, - Optional|Document|Serializable|stdClass|array $facet = Optional::Undefined, ) { + $this->operator = $operator; $this->index = $index; $this->count = $count; - $this->autocomplete = $autocomplete; - $this->compound = $compound; - $this->embeddedDocument = $embeddedDocument; - $this->equals = $equals; - $this->exists = $exists; - $this->geoShape = $geoShape; - $this->geoWithin = $geoWithin; - $this->in = $in; - $this->moreLikeThis = $moreLikeThis; - $this->near = $near; - $this->phrase = $phrase; - $this->queryString = $queryString; - $this->range = $range; - $this->regex = $regex; - $this->text = $text; - $this->wildcard = $wildcard; - $this->facet = $facet; } public function getOperator(): string diff --git a/src/Builder/Stage/SearchStage.php b/src/Builder/Stage/SearchStage.php index 974861436..a0b8ba3ef 100644 --- a/src/Builder/Stage/SearchStage.php +++ b/src/Builder/Stage/SearchStage.php @@ -13,6 +13,7 @@ use MongoDB\Builder\Type\Encode; use MongoDB\Builder\Type\OperatorInterface; use MongoDB\Builder\Type\Optional; +use MongoDB\Builder\Type\SearchOperatorInterface; use MongoDB\Builder\Type\StageInterface; use stdClass; @@ -24,7 +25,10 @@ */ class SearchStage implements StageInterface, OperatorInterface { - public const ENCODE = Encode::Object; + public const ENCODE = Encode::Search; + + /** @var Document|SearchOperatorInterface|Serializable|array|stdClass $operator */ + public readonly Document|Serializable|SearchOperatorInterface|stdClass|array $operator; /** @var Optional|string $index */ public readonly Optional|string $index; @@ -56,58 +60,8 @@ class SearchStage implements StageInterface, OperatorInterface /** @var Optional|Document|Serializable|array|stdClass $tracking */ public readonly Optional|Document|Serializable|stdClass|array $tracking; - /** @var Optional|Document|Serializable|array|stdClass $autocomplete */ - public readonly Optional|Document|Serializable|stdClass|array $autocomplete; - - /** @var Optional|Document|Serializable|array|stdClass $compound */ - public readonly Optional|Document|Serializable|stdClass|array $compound; - - /** @var Optional|Document|Serializable|array|stdClass $embeddedDocument */ - public readonly Optional|Document|Serializable|stdClass|array $embeddedDocument; - - /** @var Optional|Document|Serializable|array|stdClass $equals */ - public readonly Optional|Document|Serializable|stdClass|array $equals; - - /** @var Optional|Document|Serializable|array|stdClass $exists */ - public readonly Optional|Document|Serializable|stdClass|array $exists; - - /** @var Optional|Document|Serializable|array|stdClass $geoShape */ - public readonly Optional|Document|Serializable|stdClass|array $geoShape; - - /** @var Optional|Document|Serializable|array|stdClass $geoWithin */ - public readonly Optional|Document|Serializable|stdClass|array $geoWithin; - - /** @var Optional|Document|Serializable|array|stdClass $in */ - public readonly Optional|Document|Serializable|stdClass|array $in; - - /** @var Optional|Document|Serializable|array|stdClass $moreLikeThis */ - public readonly Optional|Document|Serializable|stdClass|array $moreLikeThis; - - /** @var Optional|Document|Serializable|array|stdClass $near */ - public readonly Optional|Document|Serializable|stdClass|array $near; - - /** @var Optional|Document|Serializable|array|stdClass $phrase */ - public readonly Optional|Document|Serializable|stdClass|array $phrase; - - /** @var Optional|Document|Serializable|array|stdClass $queryString */ - public readonly Optional|Document|Serializable|stdClass|array $queryString; - - /** @var Optional|Document|Serializable|array|stdClass $range */ - public readonly Optional|Document|Serializable|stdClass|array $range; - - /** @var Optional|Document|Serializable|array|stdClass $regex */ - public readonly Optional|Document|Serializable|stdClass|array $regex; - - /** @var Optional|Document|Serializable|array|stdClass $text */ - public readonly Optional|Document|Serializable|stdClass|array $text; - - /** @var Optional|Document|Serializable|array|stdClass $wildcard */ - public readonly Optional|Document|Serializable|stdClass|array $wildcard; - - /** @var Optional|Document|Serializable|array|stdClass $facet */ - public readonly Optional|Document|Serializable|stdClass|array $facet; - /** + * @param Document|SearchOperatorInterface|Serializable|array|stdClass $operator * @param Optional|string $index * @param Optional|Document|Serializable|array|stdClass $highlight * @param Optional|bool $concurrent @@ -118,25 +72,9 @@ class SearchStage implements StageInterface, OperatorInterface * @param Optional|Document|Serializable|array|stdClass $sort * @param Optional|bool $returnStoredSource * @param Optional|Document|Serializable|array|stdClass $tracking - * @param Optional|Document|Serializable|array|stdClass $autocomplete - * @param Optional|Document|Serializable|array|stdClass $compound - * @param Optional|Document|Serializable|array|stdClass $embeddedDocument - * @param Optional|Document|Serializable|array|stdClass $equals - * @param Optional|Document|Serializable|array|stdClass $exists - * @param Optional|Document|Serializable|array|stdClass $geoShape - * @param Optional|Document|Serializable|array|stdClass $geoWithin - * @param Optional|Document|Serializable|array|stdClass $in - * @param Optional|Document|Serializable|array|stdClass $moreLikeThis - * @param Optional|Document|Serializable|array|stdClass $near - * @param Optional|Document|Serializable|array|stdClass $phrase - * @param Optional|Document|Serializable|array|stdClass $queryString - * @param Optional|Document|Serializable|array|stdClass $range - * @param Optional|Document|Serializable|array|stdClass $regex - * @param Optional|Document|Serializable|array|stdClass $text - * @param Optional|Document|Serializable|array|stdClass $wildcard - * @param Optional|Document|Serializable|array|stdClass $facet */ public function __construct( + Document|Serializable|SearchOperatorInterface|stdClass|array $operator, Optional|string $index = Optional::Undefined, Optional|Document|Serializable|stdClass|array $highlight = Optional::Undefined, Optional|bool $concurrent = Optional::Undefined, @@ -147,24 +85,8 @@ public function __construct( Optional|Document|Serializable|stdClass|array $sort = Optional::Undefined, Optional|bool $returnStoredSource = Optional::Undefined, Optional|Document|Serializable|stdClass|array $tracking = Optional::Undefined, - Optional|Document|Serializable|stdClass|array $autocomplete = Optional::Undefined, - Optional|Document|Serializable|stdClass|array $compound = Optional::Undefined, - Optional|Document|Serializable|stdClass|array $embeddedDocument = Optional::Undefined, - Optional|Document|Serializable|stdClass|array $equals = Optional::Undefined, - Optional|Document|Serializable|stdClass|array $exists = Optional::Undefined, - Optional|Document|Serializable|stdClass|array $geoShape = Optional::Undefined, - Optional|Document|Serializable|stdClass|array $geoWithin = Optional::Undefined, - Optional|Document|Serializable|stdClass|array $in = Optional::Undefined, - Optional|Document|Serializable|stdClass|array $moreLikeThis = Optional::Undefined, - Optional|Document|Serializable|stdClass|array $near = Optional::Undefined, - Optional|Document|Serializable|stdClass|array $phrase = Optional::Undefined, - Optional|Document|Serializable|stdClass|array $queryString = Optional::Undefined, - Optional|Document|Serializable|stdClass|array $range = Optional::Undefined, - Optional|Document|Serializable|stdClass|array $regex = Optional::Undefined, - Optional|Document|Serializable|stdClass|array $text = Optional::Undefined, - Optional|Document|Serializable|stdClass|array $wildcard = Optional::Undefined, - Optional|Document|Serializable|stdClass|array $facet = Optional::Undefined, ) { + $this->operator = $operator; $this->index = $index; $this->highlight = $highlight; $this->concurrent = $concurrent; @@ -175,23 +97,6 @@ public function __construct( $this->sort = $sort; $this->returnStoredSource = $returnStoredSource; $this->tracking = $tracking; - $this->autocomplete = $autocomplete; - $this->compound = $compound; - $this->embeddedDocument = $embeddedDocument; - $this->equals = $equals; - $this->exists = $exists; - $this->geoShape = $geoShape; - $this->geoWithin = $geoWithin; - $this->in = $in; - $this->moreLikeThis = $moreLikeThis; - $this->near = $near; - $this->phrase = $phrase; - $this->queryString = $queryString; - $this->range = $range; - $this->regex = $regex; - $this->text = $text; - $this->wildcard = $wildcard; - $this->facet = $facet; } public function getOperator(): string diff --git a/src/Builder/Type/Encode.php b/src/Builder/Type/Encode.php index 85a5eaf66..4d654044a 100644 --- a/src/Builder/Type/Encode.php +++ b/src/Builder/Type/Encode.php @@ -43,6 +43,11 @@ enum Encode */ case Group; + /** + * Specific for $group stage + */ + case Search; + /** * Default case used in the interface; implementing classes are expected to override this value */ diff --git a/src/Builder/Type/SearchOperatorInterface.php b/src/Builder/Type/SearchOperatorInterface.php new file mode 100644 index 000000000..b8a05706e --- /dev/null +++ b/src/Builder/Type/SearchOperatorInterface.php @@ -0,0 +1,8 @@ +assertSamePipeline(Pipelines::AutocompleteAcrossMultipleFields, $pipeline); + } + + public function testBasic(): void + { + $pipeline = new Pipeline( + Stage::search( + Search::autocomplete( + query: 'off', + path: 'title', + ), + ), + Stage::limit(10), + Stage::project(_id: 0, title: 1), + ); + + $this->assertSamePipeline(Pipelines::AutocompleteBasic, $pipeline); + } + + public function testFuzzy(): void + { + $pipeline = new Pipeline( + Stage::search( + Search::autocomplete( + query: 'pre', + path: 'title', + fuzzy: object( + maxEdits: 1, + prefixLength: 1, + maxExpansions: 256, + ), + ), + ), + Stage::limit(10), + Stage::project(_id: 0, title: 1), + ); + + $this->assertSamePipeline(Pipelines::AutocompleteFuzzy, $pipeline); + } + + public function testHighlighting(): void + { + $pipeline = new Pipeline( + Stage::search( + Search::autocomplete( + query: 'ger', + path: 'title', + ), + highlight: object( + path: 'title', + ), + ), + Stage::limit(5), + Stage::project( + score: ['$meta' => 'searchScore'], + _id: 0, + title: 1, + highlights: ['$meta' => 'searchHighlights'], + ), + ); + + $this->assertSamePipeline(Pipelines::AutocompleteHighlighting, $pipeline); + } + + public function testTokenOrderAny(): void + { + $pipeline = new Pipeline( + Stage::search( + Search::autocomplete( + query: 'men with', + path: 'title', + tokenOrder: 'any', + ), + ), + Stage::limit(4), + Stage::project(_id: 0, title: 1), + ); + + $this->assertSamePipeline(Pipelines::AutocompleteTokenOrderAny, $pipeline); + } + + public function testTokenOrderSequential(): void + { + $pipeline = new Pipeline( + Stage::search( + Search::autocomplete( + query: 'men with', + path: 'title', + tokenOrder: 'sequential', + ), + ), + Stage::limit(4), + Stage::project(_id: 0, title: 1), + ); + + $this->assertSamePipeline(Pipelines::AutocompleteTokenOrderSequential, $pipeline); + } +} diff --git a/tests/Builder/Search/CompoundOperatorTest.php b/tests/Builder/Search/CompoundOperatorTest.php new file mode 100644 index 000000000..8d5d69aed --- /dev/null +++ b/tests/Builder/Search/CompoundOperatorTest.php @@ -0,0 +1,157 @@ +assertSamePipeline(Pipelines::CompoundFilter, $pipeline); + } + + public function testMinimumShouldMatch(): void + { + $pipeline = new Pipeline( + Stage::search( + Search::compound( + must: [ + Search::text( + path: 'description', + query: 'varieties', + ), + ], + should: [ + Search::text( + path: 'description', + query: 'Fuji', + ), + Search::text( + path: 'description', + query: 'Golden Delicious', + ), + ], + minimumShouldMatch: 1, + ), + ), + ); + + $this->assertSamePipeline(Pipelines::CompoundMinimumShouldMatch, $pipeline); + } + + public function testMustAndMustNot(): void + { + $pipeline = new Pipeline( + Stage::search( + Search::compound( + must: [ + Search::text( + path: 'description', + query: 'varieties', + ), + ], + mustNot: [ + Search::text( + path: 'description', + query: 'apples', + ), + ], + ), + ), + ); + + $this->assertSamePipeline(Pipelines::CompoundMustAndMustNot, $pipeline); + } + + public function testMustAndShould(): void + { + $pipeline = new Pipeline( + Stage::search( + Search::compound( + must: [ + Search::text( + path: 'description', + query: 'varieties', + ), + ], + should: [ + Search::text( + path: 'description', + query: 'Fuji', + ), + ], + ), + ), + Stage::project( + score: ['$meta' => 'searchScore'], + ), + ); + + $this->assertSamePipeline(Pipelines::CompoundMustAndShould, $pipeline); + } + + public function testNested(): void + { + $pipeline = new Pipeline( + Stage::search( + Search::compound( + should: [ + Search::text( + path: 'type', + query: 'apple', + ), + Search::compound( + must: [ + Search::text( + path: 'category', + query: 'organic', + ), + Search::equals( + path: 'in_stock', + value: true, + ), + ], + ), + ], + minimumShouldMatch: 1, + ), + ), + ); + + $this->assertSamePipeline(Pipelines::CompoundNested, $pipeline); + } +} diff --git a/tests/Builder/Search/EmbeddedDocumentOperatorTest.php b/tests/Builder/Search/EmbeddedDocumentOperatorTest.php new file mode 100644 index 000000000..b83790c83 --- /dev/null +++ b/tests/Builder/Search/EmbeddedDocumentOperatorTest.php @@ -0,0 +1,167 @@ + 1, + 'items.tags' => 1, + ], + _id: 0, + score: ['$meta' => 'searchScore'], + ), + ); + + $this->assertSamePipeline(Pipelines::EmbeddedDocumentBasic, $pipeline); + } + + public function testFacet(): void + { + $pipeline = new Pipeline( + Stage::searchMeta( + Search::facet( + facets: object( + purchaseMethodFacet: object( + type: 'string', + path: 'purchaseMethod', + ), + ), + operator: Search::embeddedDocument( + path: 'items', + operator: Search::compound( + must: [ + Search::text( + path: 'items.tags', + query: 'school', + ), + ], + should: [ + Search::text( + path: 'items.name', + query: 'backpack', + ), + ], + ), + ), + ), + ), + ); + + $this->assertSamePipeline(Pipelines::EmbeddedDocumentFacet, $pipeline); + } + + public function testQueryAndSort(): void + { + $pipeline = new Pipeline( + Stage::search( + Search::embeddedDocument( + path: 'items', + operator: + Search::text( + path: 'items.name', + query: 'laptop', + ), + ), + sort: ['items.tags' => Sort::Asc], + ), + Stage::limit(5), + Stage::project( + ...[ + 'items.name' => 1, + 'items.tags' => 1, + ], + _id: 0, + score: ['$meta' => 'searchScore'], + ), + ); + + $this->assertSamePipeline(Pipelines::EmbeddedDocumentQueryAndSort, $pipeline); + } + + public function testQueryForMatchingEmbeddedDocumentsOnly(): void + { + $pipeline = new Pipeline( + Stage::search( + Search::embeddedDocument( + path: 'items', + operator: + Search::compound( + must: [ + Search::range( + path: 'items.quantity', + gt: 2, + ), + Search::exists( + path: 'items.price', + ), + Search::text( + path: 'items.tags', + query: 'school', + ), + ], + ), + ), + ), + Stage::limit(2), + Stage::project( + _id: 0, + storeLocation: 1, + items: Expression::filter( + input: Expression::arrayFieldPath('items'), + cond: Expression::and( + Expression::ifNull('$$this.price', 'false'), + Expression::gt(Expression::variable('this.quantity'), 2), + Expression::in('office', Expression::variable('this.tags')), + ), + ), + ), + ); + + $this->assertSamePipeline(Pipelines::EmbeddedDocumentQueryForMatchingEmbeddedDocumentsOnly, $pipeline); + } +} diff --git a/tests/Builder/Search/EqualsOperatorTest.php b/tests/Builder/Search/EqualsOperatorTest.php new file mode 100644 index 000000000..1020e5509 --- /dev/null +++ b/tests/Builder/Search/EqualsOperatorTest.php @@ -0,0 +1,125 @@ + 'searchScore'], + ), + ); + + $this->assertSamePipeline(Pipelines::EqualsBoolean, $pipeline); + } + + public function testDate(): void + { + $pipeline = new Pipeline( + Stage::search( + Search::equals( + path: 'account_created', + value: new UTCDateTime(new DateTimeImmutable('2022-05-04T05:01:08')), + ), + ), + ); + + $this->assertSamePipeline(Pipelines::EqualsDate, $pipeline); + } + + public function testNull(): void + { + $pipeline = new Pipeline( + Stage::search( + Search::equals( + path: 'job_title', + value: null, + ), + ), + ); + + $this->assertSamePipeline(Pipelines::EqualsNull, $pipeline); + } + + public function testNumber(): void + { + $pipeline = new Pipeline( + Stage::search( + Search::equals( + path: 'employee_number', + value: 259, + ), + ), + ); + + $this->assertSamePipeline(Pipelines::EqualsNumber, $pipeline); + } + + public function testObjectId(): void + { + $pipeline = new Pipeline( + Stage::search( + Search::equals( + path: 'teammates', + value: new ObjectId('5a9427648b0beebeb69589a1'), + ), + ), + ); + + $this->assertSamePipeline(Pipelines::EqualsObjectId, $pipeline); + } + + public function testString(): void + { + $pipeline = new Pipeline( + Stage::search( + Search::equals( + path: 'name', + value: 'jim hall', + ), + ), + ); + + $this->assertSamePipeline(Pipelines::EqualsString, $pipeline); + } + + public function testUUID(): void + { + $pipeline = new Pipeline( + Stage::search( + Search::equals( + path: 'uuid', + value: new Binary(pack('h*', 'fac32260b5114c698485a2be5b7dda9e'), Binary::TYPE_UUID), + ), + ), + ); + + $this->assertSamePipeline(Pipelines::EqualsUUID, $pipeline); + } +} diff --git a/tests/Builder/Search/ExistsOperatorTest.php b/tests/Builder/Search/ExistsOperatorTest.php new file mode 100644 index 000000000..5666a1099 --- /dev/null +++ b/tests/Builder/Search/ExistsOperatorTest.php @@ -0,0 +1,67 @@ +assertSamePipeline(Pipelines::ExistsBasic, $pipeline); + } + + public function testCompound(): void + { + $pipeline = new Pipeline( + Stage::search( + Search::compound( + must: [ + Search::exists( + path: 'type', + ), + Search::text( + path: 'type', + query: 'apple', + ), + ], + should: Search::text( + path: 'description', + query: 'fuji', + ), + ), + ), + ); + + $this->assertSamePipeline(Pipelines::ExistsCompound, $pipeline); + } + + public function testEmbedded(): void + { + $pipeline = new Pipeline( + Stage::search( + Search::exists( + path: 'quantities.lemons', + ), + ), + ); + + $this->assertSamePipeline(Pipelines::ExistsEmbedded, $pipeline); + } +} diff --git a/tests/Builder/Search/FacetOperatorTest.php b/tests/Builder/Search/FacetOperatorTest.php new file mode 100644 index 000000000..3be124bec --- /dev/null +++ b/tests/Builder/Search/FacetOperatorTest.php @@ -0,0 +1,61 @@ +assertSamePipeline(Pipelines::FacetFacet, $pipeline); + } +} diff --git a/tests/Builder/Search/GeoShapeOperatorTest.php b/tests/Builder/Search/GeoShapeOperatorTest.php new file mode 100644 index 000000000..b5e748660 --- /dev/null +++ b/tests/Builder/Search/GeoShapeOperatorTest.php @@ -0,0 +1,204 @@ + 'searchScore'], + ), + ); + + $this->assertSamePipeline(Pipelines::GeoShapeDisjoint, $pipeline); + } + + public function testIntersect(): void + { + $pipeline = new Pipeline( + Stage::search( + Search::geoShape( + relation: 'intersects', + geometry: object( + type: 'MultiPolygon', + coordinates: [ + [ + [ + [ + 2.16942, + 41.40082, + ], + [ + 2.17963, + 41.40087, + ], + [ + 2.18146, + 41.39716, + ], + [ + 2.15533, + 41.40686, + ], + [ + 2.14596, + 41.38475, + ], + [ + 2.17519, + 41.41035, + ], + [ + 2.16942, + 41.40082, + ], + ], + ], + [ + [ + [ + 2.16365, + 41.39416, + ], + [ + 2.16963, + 41.39726, + ], + [ + 2.15395, + 41.38005, + ], + [ + 2.17935, + 41.43038, + ], + [ + 2.16365, + 41.39416, + ], + ], + ], + ], + ), + path: 'address.location', + ), + ), + Stage::limit(3), + Stage::project( + _id: 0, + name: 1, + address: 1, + score: ['$meta' => 'searchScore'], + ), + ); + + $this->assertSamePipeline(Pipelines::GeoShapeIntersect, $pipeline); + } + + public function testWithin(): void + { + $pipeline = new Pipeline( + Stage::search( + Search::geoShape( + relation: 'within', + geometry: object( + type: 'Polygon', + coordinates: [ + [ + [ + -74.3994140625, + 40.5305017757, + ], + [ + -74.7290039063, + 40.5805846641, + ], + [ + -74.7729492188, + 40.9467136651, + ], + [ + -74.0698242188, + 41.1290213475, + ], + [ + -73.65234375, + 40.9964840144, + ], + [ + -72.6416015625, + 40.9467136651, + ], + [ + -72.3559570313, + 40.7971774152, + ], + [ + -74.3994140625, + 40.5305017757, + ], + ], + ], + ), + path: 'address.location', + ), + ), + Stage::limit(3), + Stage::project( + _id: 0, + name: 1, + address: 1, + score: ['$meta' => 'searchScore'], + ), + ); + + $this->assertSamePipeline(Pipelines::GeoShapeWithin, $pipeline); + } +} diff --git a/tests/Builder/Search/GeoWithinOperatorTest.php b/tests/Builder/Search/GeoWithinOperatorTest.php new file mode 100644 index 000000000..3a6a6b0af --- /dev/null +++ b/tests/Builder/Search/GeoWithinOperatorTest.php @@ -0,0 +1,124 @@ +assertSamePipeline(Pipelines::GeoWithinBox, $pipeline); + } + + public function testCircle(): void + { + $pipeline = new Pipeline( + Stage::search( + Search::geoWithin( + path: 'address.location', + circle: object( + center: object( + type: 'Point', + coordinates: [ + -73.54, + 45.54, + ], + ), + radius: 1600, + ), + ), + ), + Stage::limit(3), + Stage::project( + _id: 0, + name: 1, + address: 1, + ), + ); + + $this->assertSamePipeline(Pipelines::GeoWithinCircle, $pipeline); + } + + public function testGeometry(): void + { + $pipeline = new Pipeline( + Stage::search( + Search::geoWithin( + path: 'address.location', + geometry: object( + type: 'Polygon', + coordinates: [ + [ + [ + -161.323242, + 22.512557, + ], + [ + -152.446289, + 22.065278, + ], + [ + -156.09375, + 17.811456, + ], + [ + -161.323242, + 22.512557, + ], + ], + ], + ), + ), + ), + Stage::limit(3), + Stage::project( + _id: 0, + name: 1, + address: 1, + ), + ); + + $this->assertSamePipeline(Pipelines::GeoWithinGeometry, $pipeline); + } +} diff --git a/tests/Builder/Search/InOperatorTest.php b/tests/Builder/Search/InOperatorTest.php new file mode 100644 index 000000000..e6d1af57c --- /dev/null +++ b/tests/Builder/Search/InOperatorTest.php @@ -0,0 +1,101 @@ +assertSamePipeline(Pipelines::InArrayValueFieldMatch, $pipeline); + } + + public function testCompoundQueryMatch(): void + { + $pipeline = new Pipeline( + Stage::search( + Search::compound( + must: [ + Search::in( + path: 'name', + value: [ + 'james sanchez', + 'jennifer lawrence', + ], + ), + ], + should: [ + Search::in( + path: '_id', + value: [ + new ObjectId('5ca4bbcea2dd94ee58162a72'), + new ObjectId('5ca4bbcea2dd94ee58162a91'), + ], + ), + ], + ), + ), + Stage::limit(5), + Stage::project( + _id: 1, + name: 1, + score: ['$meta' => 'searchScore'], + ), + ); + + $this->assertSamePipeline(Pipelines::InCompoundQueryMatch, $pipeline); + } + + public function testSingleValueFieldMatch(): void + { + $pipeline = new Pipeline( + Stage::search( + Search::in( + path: 'birthdate', + value: [ + new UTCDateTime(new DateTimeImmutable('1977-03-02T02:20:31')), + new UTCDateTime(new DateTimeImmutable('1977-03-01T00:00:00')), + new UTCDateTime(new DateTimeImmutable('1977-05-06T21:57:35')), + ], + ), + ), + Stage::project( + _id: 0, + name: 1, + birthdate: 1, + ), + ); + + $this->assertSamePipeline(Pipelines::InSingleValueFieldMatch, $pipeline); + } +} diff --git a/tests/Builder/Search/MoreLikeThisOperatorTest.php b/tests/Builder/Search/MoreLikeThisOperatorTest.php new file mode 100644 index 000000000..a83c03559 --- /dev/null +++ b/tests/Builder/Search/MoreLikeThisOperatorTest.php @@ -0,0 +1,115 @@ +assertSamePipeline(Pipelines::MoreLikeThisInputDocumentExcludedInResults, $pipeline); + } + + public function testMultipleAnalyzers(): void + { + $pipeline = new Pipeline( + Stage::search( + Search::compound( + mustNot: [ + Search::equals( + path: '_id', + value: new ObjectId('573a1394f29313caabcde9ef'), + ), + ], + should: [ + Search::moreLikeThis( + like: object( + _id: new ObjectId('573a1396f29313caabce4a9a'), + genres: [ + 'Crime', + 'Drama', + ], + title: 'The Godfather', + ), + ), + ], + ), + ), + Stage::limit(10), + Stage::project( + title: 1, + genres: 1, + _id: 1, + ), + ); + + $this->assertSamePipeline(Pipelines::MoreLikeThisMultipleAnalyzers, $pipeline); + } + + public function testSingleDocumentWithMultipleFields(): void + { + $pipeline = new Pipeline( + Stage::search( + Search::moreLikeThis( + like: object( + title: 'The Godfather', + genres: 'action', + ), + ), + ), + Stage::limit(5), + Stage::project( + _id: 0, + title: 1, + released: 1, + genres: 1, + ), + ); + + $this->assertSamePipeline(Pipelines::MoreLikeThisSingleDocumentWithMultipleFields, $pipeline); + } +} diff --git a/tests/Builder/Search/NearOperatorTest.php b/tests/Builder/Search/NearOperatorTest.php new file mode 100644 index 000000000..45a48e0d5 --- /dev/null +++ b/tests/Builder/Search/NearOperatorTest.php @@ -0,0 +1,128 @@ + 'searchScore'], + ), + ); + + $this->assertSamePipeline(Pipelines::NearCompound, $pipeline); + } + + public function testDate(): void + { + $pipeline = new Pipeline( + Stage::search( + Search::near( + path: 'released', + origin: new UTCDateTime(new DateTimeImmutable('1915-09-13T00:00:00.000+00:00')), + pivot: 7776000000, + ), + index: 'releaseddate', + ), + Stage::limit(3), + Stage::project( + _id: 0, + title: 1, + released: 1, + score: ['$meta' => 'searchScore'], + ), + ); + + $this->assertSamePipeline(Pipelines::NearDate, $pipeline); + } + + public function testGeoJSONPoint(): void + { + $pipeline = new Pipeline( + Stage::search( + Search::near( + path: 'address.location', + origin: object( + type: 'Point', + coordinates: [ + -8.61308, + 41.1413, + ], + ), + pivot: 1000, + ), + ), + Stage::limit(3), + Stage::project( + _id: 0, + name: 1, + address: 1, + score: ['$meta' => 'searchScore'], + ), + ); + + $this->assertSamePipeline(Pipelines::NearGeoJSONPoint, $pipeline); + } + + public function testNumber(): void + { + $pipeline = new Pipeline( + Stage::search( + Search::near( + path: 'runtime', + origin: 279, + pivot: 2, + ), + index: 'runtimes', + ), + Stage::limit(7), + Stage::project( + _id: 0, + title: 1, + runtime: 1, + score: ['$meta' => 'searchScore'], + ), + ); + + $this->assertSamePipeline(Pipelines::NearNumber, $pipeline); + } +} diff --git a/tests/Builder/Search/PhraseOperatorTest.php b/tests/Builder/Search/PhraseOperatorTest.php new file mode 100644 index 000000000..a045e9c12 --- /dev/null +++ b/tests/Builder/Search/PhraseOperatorTest.php @@ -0,0 +1,102 @@ + 'searchScore'], + ), + ); + + $this->assertSamePipeline(Pipelines::PhraseMultiplePhrase, $pipeline); + } + + public function testPhraseSlop(): void + { + $pipeline = new Pipeline( + Stage::search( + Search::phrase( + path: 'title', + query: 'men women', + slop: 5, + ), + ), + Stage::project( + _id: 0, + title: 1, + score: ['$meta' => 'searchScore'], + ), + ); + + $this->assertSamePipeline(Pipelines::PhrasePhraseSlop, $pipeline); + } + + public function testPhraseSynonyms(): void + { + $pipeline = new Pipeline( + Stage::search( + Search::phrase( + path: 'plot', + query: 'automobile race', + slop: 5, + synonyms: 'my_synonyms', + ), + ), + Stage::limit(5), + Stage::project( + _id: 0, + plot: 1, + title: 1, + score: ['$meta' => 'searchScore'], + ), + ); + + $this->assertSamePipeline(Pipelines::PhrasePhraseSynonyms, $pipeline); + } + + public function testSinglePhrase(): void + { + $pipeline = new Pipeline( + Stage::search( + Search::phrase( + path: 'title', + query: 'new york', + ), + ), + Stage::limit(10), + Stage::project( + _id: 0, + title: 1, + score: ['$meta' => 'searchScore'], + ), + ); + + $this->assertSamePipeline(Pipelines::PhraseSinglePhrase, $pipeline); + } +} diff --git a/tests/Builder/Search/Pipelines.php b/tests/Builder/Search/Pipelines.php new file mode 100644 index 000000000..214b7a072 --- /dev/null +++ b/tests/Builder/Search/Pipelines.php @@ -0,0 +1,2814 @@ +assertSamePipeline(Pipelines::QueryStringBooleanOperatorQueries, $pipeline); + } +} diff --git a/tests/Builder/Search/RangeOperatorTest.php b/tests/Builder/Search/RangeOperatorTest.php new file mode 100644 index 000000000..9606014ed --- /dev/null +++ b/tests/Builder/Search/RangeOperatorTest.php @@ -0,0 +1,122 @@ +assertSamePipeline(Pipelines::RangeDate, $pipeline); + } + + public function testNumberGteLte(): void + { + $pipeline = new Pipeline( + Stage::search( + Search::range( + path: 'runtime', + gte: 2, + lte: 3, + ), + ), + Stage::limit(5), + Stage::project( + _id: 0, + title: 1, + runtime: 1, + ), + ); + + $this->assertSamePipeline(Pipelines::RangeNumberGteLte, $pipeline); + } + + public function testNumberLte(): void + { + $pipeline = new Pipeline( + Stage::search( + Search::range( + path: 'runtime', + lte: 2, + ), + ), + Stage::limit(5), + Stage::project( + _id: 0, + title: 1, + runtime: 1, + score: ['$meta' => 'searchScore'], + ), + ); + + $this->assertSamePipeline(Pipelines::RangeNumberLte, $pipeline); + } + + public function testObjectId(): void + { + $pipeline = new Pipeline( + Stage::search( + Search::range( + path: '_id', + gte: new ObjectId('573a1396f29313caabce4a9a'), + lte: new ObjectId('573a1396f29313caabce4ae7'), + ), + ), + Stage::project( + _id: 1, + title: 1, + released: 1, + ), + ); + + $this->assertSamePipeline(Pipelines::RangeObjectId, $pipeline); + } + + public function testString(): void + { + $pipeline = new Pipeline( + Stage::search( + Search::range( + path: 'title', + gt: 'city', + lt: 'country', + ), + ), + Stage::limit(5), + Stage::project( + _id: 0, + title: 1, + ), + ); + + $this->assertSamePipeline(Pipelines::RangeString, $pipeline); + } +} diff --git a/tests/Builder/Search/RegexOperatorTest.php b/tests/Builder/Search/RegexOperatorTest.php new file mode 100644 index 000000000..b95f518a0 --- /dev/null +++ b/tests/Builder/Search/RegexOperatorTest.php @@ -0,0 +1,34 @@ +assertSamePipeline(Pipelines::RegexRegex, $pipeline); + } +} diff --git a/tests/Builder/Search/TextOperatorTest.php b/tests/Builder/Search/TextOperatorTest.php new file mode 100644 index 000000000..12709740e --- /dev/null +++ b/tests/Builder/Search/TextOperatorTest.php @@ -0,0 +1,194 @@ + 'searchScore'], + ), + ); + + $this->assertSamePipeline(Pipelines::TextBasic, $pipeline); + } + + public function testFuzzyDefault(): void + { + $pipeline = new Pipeline( + Stage::search( + Search::text( + path: 'title', + query: 'naw yark', + fuzzy: object(), + ), + ), + Stage::limit(10), + Stage::project( + _id: 0, + title: 1, + score: ['$meta' => 'searchScore'], + ), + ); + + $this->assertSamePipeline(Pipelines::TextFuzzyDefault, $pipeline); + } + + public function testFuzzyMaxExpansions(): void + { + $pipeline = new Pipeline( + Stage::search( + Search::text( + path: 'title', + query: 'naw yark', + fuzzy: object( + maxEdits: 1, + maxExpansions: 100, + ), + ), + ), + Stage::limit(10), + Stage::project( + _id: 0, + title: 1, + score: ['$meta' => 'searchScore'], + ), + ); + + $this->assertSamePipeline(Pipelines::TextFuzzyMaxExpansions, $pipeline); + } + + public function testFuzzyPrefixLength(): void + { + $pipeline = new Pipeline( + Stage::search( + Search::text( + path: 'title', + query: 'naw yark', + fuzzy: object( + maxEdits: 1, + prefixLength: 2, + ), + ), + ), + Stage::limit(8), + Stage::project( + _id: 1, + title: 1, + score: ['$meta' => 'searchScore'], + ), + ); + + $this->assertSamePipeline(Pipelines::TextFuzzyPrefixLength, $pipeline); + } + + public function testMatchAllUsingSynonyms(): void + { + $pipeline = new Pipeline( + Stage::search( + Search::text( + path: 'plot', + query: 'automobile race', + matchCriteria: 'all', + synonyms: 'my_synonyms', + ), + ), + Stage::limit(20), + Stage::project( + _id: 0, + plot: 1, + title: 1, + score: ['$meta' => 'searchScore'], + ), + ); + + $this->assertSamePipeline(Pipelines::TextMatchAllUsingSynonyms, $pipeline); + } + + public function testMatchAnyUsingEquivalentMapping(): void + { + $pipeline = new Pipeline( + Stage::search( + Search::text( + path: 'plot', + query: 'attire', + synonyms: 'my_synonyms', + matchCriteria: 'any', + ), + ), + Stage::limit(5), + Stage::project( + _id: 0, + plot: 1, + title: 1, + score: ['$meta' => 'searchScore'], + ), + ); + + $this->assertSamePipeline(Pipelines::TextMatchAnyUsingEquivalentMapping, $pipeline); + } + + public function testMatchAnyUsingExplicitMapping(): void + { + $pipeline = new Pipeline( + Stage::search( + Search::text( + path: 'plot', + query: 'boat race', + synonyms: 'my_synonyms', + matchCriteria: 'any', + ), + ), + Stage::limit(10), + Stage::project( + _id: 0, + plot: 1, + title: 1, + score: ['$meta' => 'searchScore'], + ), + ); + + $this->assertSamePipeline(Pipelines::TextMatchAnyUsingExplicitMapping, $pipeline); + } + + public function testWildcardPath(): void + { + $pipeline = new Pipeline( + Stage::search( + Search::text( + path: ['wildcard' => '*'], + query: 'surfer', + ), + ), + Stage::project( + _id: 0, + title: 1, + score: ['$meta' => 'searchScore'], + ), + ); + + $this->assertSamePipeline(Pipelines::TextWildcardPath, $pipeline); + } +} diff --git a/tests/Builder/Search/WildcardOperatorTest.php b/tests/Builder/Search/WildcardOperatorTest.php new file mode 100644 index 000000000..18103b75d --- /dev/null +++ b/tests/Builder/Search/WildcardOperatorTest.php @@ -0,0 +1,54 @@ +assertSamePipeline(Pipelines::WildcardEscapeCharacterExample, $pipeline); + } + + public function testWildcardPath(): void + { + $pipeline = new Pipeline( + Stage::search( + Search::wildcard( + query: 'Wom?n *', + path: ['wildcard' => '*'], + ), + ), + Stage::limit(5), + Stage::project( + _id: 0, + title: 1, + ), + ); + + $this->assertSamePipeline(Pipelines::WildcardWildcardPath, $pipeline); + } +} diff --git a/tests/Builder/Stage/Pipelines.php b/tests/Builder/Stage/Pipelines.php index 9e3eead19..eefa4e714 100644 --- a/tests/Builder/Stage/Pipelines.php +++ b/tests/Builder/Stage/Pipelines.php @@ -2612,2808 +2612,6 @@ enum Pipelines: string ] JSON; - /** - * Autocomplete Basic - * - * @see https://www.mongodb.com/docs/atlas/atlas-search/autocomplete/#basic-example - */ - case SearchAutocompleteBasic = <<<'JSON' - [ - { - "$search": { - "autocomplete": { - "query": "off", - "path": "title" - } - } - }, - { - "$limit": { - "$numberInt": "10" - } - }, - { - "$project": { - "_id": { - "$numberInt": "0" - }, - "title": { - "$numberInt": "1" - } - } - } - ] - JSON; - - /** - * Autocomplete Fuzzy - * - * @see https://www.mongodb.com/docs/atlas/atlas-search/autocomplete/#fuzzy-example - */ - case SearchAutocompleteFuzzy = <<<'JSON' - [ - { - "$search": { - "autocomplete": { - "query": "pre", - "path": "title", - "fuzzy": { - "maxEdits": { - "$numberInt": "1" - }, - "prefixLength": { - "$numberInt": "1" - }, - "maxExpansions": { - "$numberInt": "256" - } - } - } - } - }, - { - "$limit": { - "$numberInt": "10" - } - }, - { - "$project": { - "_id": { - "$numberInt": "0" - }, - "title": { - "$numberInt": "1" - } - } - } - ] - JSON; - - /** - * Autocomplete Token Order any - * - * @see https://www.mongodb.com/docs/atlas/atlas-search/autocomplete/#simple-any-example - */ - case SearchAutocompleteTokenOrderAny = <<<'JSON' - [ - { - "$search": { - "autocomplete": { - "query": "men with", - "path": "title", - "tokenOrder": "any" - } - } - }, - { - "$limit": { - "$numberInt": "4" - } - }, - { - "$project": { - "_id": { - "$numberInt": "0" - }, - "title": { - "$numberInt": "1" - } - } - } - ] - JSON; - - /** - * Autocomplete Token Order sequential - * - * @see https://www.mongodb.com/docs/atlas/atlas-search/autocomplete/#simple-sequential-example - */ - case SearchAutocompleteTokenOrderSequential = <<<'JSON' - [ - { - "$search": { - "autocomplete": { - "query": "men with", - "path": "title", - "tokenOrder": "sequential" - } - } - }, - { - "$limit": { - "$numberInt": "4" - } - }, - { - "$project": { - "_id": { - "$numberInt": "0" - }, - "title": { - "$numberInt": "1" - } - } - } - ] - JSON; - - /** - * Autocomplete Highlighting - * - * @see https://www.mongodb.com/docs/atlas/atlas-search/autocomplete/#highlighting-example - */ - case SearchAutocompleteHighlighting = <<<'JSON' - [ - { - "$search": { - "autocomplete": { - "query": "ger", - "path": "title" - }, - "highlight": { - "path": "title" - } - } - }, - { - "$limit": { - "$numberInt": "5" - } - }, - { - "$project": { - "score": { - "$meta": "searchScore" - }, - "_id": { - "$numberInt": "0" - }, - "title": { - "$numberInt": "1" - }, - "highlights": { - "$meta": "searchHighlights" - } - } - } - ] - JSON; - - /** - * Autocomplete Across Multiple Fields - * - * @see https://www.mongodb.com/docs/atlas/atlas-search/autocomplete/#search-across-multiple-fields - */ - case SearchAutocompleteAcrossMultipleFields = <<<'JSON' - [ - { - "$search": { - "compound": { - "should": [ - { - "autocomplete": { - "query": "inter", - "path": "title" - } - }, - { - "autocomplete": { - "query": "inter", - "path": "plot" - } - } - ], - "minimumShouldMatch": { - "$numberInt": "1" - } - } - } - }, - { - "$limit": { - "$numberInt": "10" - } - }, - { - "$project": { - "_id": { - "$numberInt": "0" - }, - "title": { - "$numberInt": "1" - }, - "plot": { - "$numberInt": "1" - } - } - } - ] - JSON; - - /** - * Compound must and mustNot - * - * @see https://www.mongodb.com/docs/atlas/atlas-search/compound/#must-and-mustnot-example - */ - case SearchCompoundMustAndMustNot = <<<'JSON' - [ - { - "$search": { - "compound": { - "must": [ - { - "text": { - "query": "varieties", - "path": "description" - } - } - ], - "mustNot": [ - { - "text": { - "query": "apples", - "path": "description" - } - } - ] - } - } - } - ] - JSON; - - /** - * Compound must and should - * - * @see https://www.mongodb.com/docs/atlas/atlas-search/compound/#must-and-should-example - */ - case SearchCompoundMustAndShould = <<<'JSON' - [ - { - "$search": { - "compound": { - "must": [ - { - "text": { - "query": "varieties", - "path": "description" - } - } - ], - "should": [ - { - "text": { - "query": "Fuji", - "path": "description" - } - } - ] - } - } - }, - { - "$project": { - "score": { - "$meta": "searchScore" - } - } - } - ] - JSON; - - /** - * Compound minimumShouldMatch - * - * @see https://www.mongodb.com/docs/atlas/atlas-search/compound/#minimumshouldmatch-example - */ - case SearchCompoundMinimumShouldMatch = <<<'JSON' - [ - { - "$search": { - "compound": { - "must": [ - { - "text": { - "query": "varieties", - "path": "description" - } - } - ], - "should": [ - { - "text": { - "query": "Fuji", - "path": "description" - } - }, - { - "text": { - "query": "Golden Delicious", - "path": "description" - } - } - ], - "minimumShouldMatch": { - "$numberInt": "1" - } - } - } - } - ] - JSON; - - /** - * Compound Filter - * - * @see https://www.mongodb.com/docs/atlas/atlas-search/compound/#filter-examples - */ - case SearchCompoundFilter = <<<'JSON' - [ - { - "$search": { - "compound": { - "must": [ - { - "text": { - "query": "varieties", - "path": "description" - } - } - ], - "should": [ - { - "text": { - "query": "banana", - "path": "description" - } - } - ], - "filter": [ - { - "text": { - "query": "granny", - "path": "description" - } - } - ] - } - } - } - ] - JSON; - - /** - * Compound Nested - * - * @see https://www.mongodb.com/docs/atlas/atlas-search/compound/#nested-example - */ - case SearchCompoundNested = <<<'JSON' - [ - { - "$search": { - "compound": { - "should": [ - { - "text": { - "query": "apple", - "path": "type" - } - }, - { - "compound": { - "must": [ - { - "text": { - "query": "organic", - "path": "category" - } - }, - { - "equals": { - "value": true, - "path": "in_stock" - } - } - ] - } - } - ], - "minimumShouldMatch": { - "$numberInt": "1" - } - } - } - } - ] - JSON; - - /** - * EmbeddedDocument Basic - * - * @see https://www.mongodb.com/docs/atlas/atlas-search/embedded-document/#index-definition - */ - case SearchEmbeddedDocumentBasic = <<<'JSON' - [ - { - "$search": { - "embeddedDocument": { - "path": "items", - "operator": { - "compound": { - "must": [ - { - "text": { - "path": "items.tags", - "query": "school" - } - } - ], - "should": [ - { - "text": { - "path": "items.name", - "query": "backpack" - } - } - ] - } - }, - "score": { - "embedded": { - "aggregate": "mean" - } - } - } - } - }, - { - "$limit": { - "$numberInt": "5" - } - }, - { - "$project": { - "_id": { - "$numberInt": "0" - }, - "items.name": { - "$numberInt": "1" - }, - "items.tags": { - "$numberInt": "1" - }, - "score": { - "$meta": "searchScore" - } - } - } - ] - JSON; - - /** - * EmbeddedDocument Facet - * - * @see https://www.mongodb.com/docs/atlas/atlas-search/embedded-document/#facet-query - */ - case SearchEmbeddedDocumentFacet = <<<'JSON' - [ - { - "$searchMeta": { - "facet": { - "operator": { - "embeddedDocument": { - "path": "items", - "operator": { - "compound": { - "must": [ - { - "text": { - "path": "items.tags", - "query": "school" - } - } - ], - "should": [ - { - "text": { - "path": "items.name", - "query": "backpack" - } - } - ] - } - } - } - }, - "facets": { - "purchaseMethodFacet": { - "type": "string", - "path": "purchaseMethod" - } - } - } - } - } - ] - JSON; - - /** - * EmbeddedDocument Query and Sort - * - * @see https://www.mongodb.com/docs/atlas/atlas-search/embedded-document/#query-and-sort - */ - case SearchEmbeddedDocumentQueryAndSort = <<<'JSON' - [ - { - "$search": { - "embeddedDocument": { - "path": "items", - "operator": { - "text": { - "path": "items.name", - "query": "laptop" - } - } - }, - "sort": { - "items.tags": { - "$numberInt": "1" - } - } - } - }, - { - "$limit": { - "$numberInt": "5" - } - }, - { - "$project": { - "_id": { - "$numberInt": "0" - }, - "items.name": { - "$numberInt": "1" - }, - "items.tags": { - "$numberInt": "1" - }, - "score": { - "$meta": "searchScore" - } - } - } - ] - JSON; - - /** - * Query for Matching Embedded Documents Only - * - * @see https://www.mongodb.com/docs/atlas/atlas-search/embedded-document/#query-for-matching-embedded-documents-only - */ - case SearchQueryForMatchingEmbeddedDocumentsOnly = <<<'JSON' - [ - { - "$search": { - "embeddedDocument": { - "path": "items", - "operator": { - "compound": { - "must": [ - { - "range": { - "path": "items.quantity", - "gt": { - "$numberInt": "2" - } - } - }, - { - "exists": { - "path": "items.price" - } - }, - { - "text": { - "path": "items.tags", - "query": "school" - } - } - ] - } - } - } - } - }, - { - "$limit": { - "$numberInt": "2" - } - }, - { - "$project": { - "_id": { - "$numberInt": "0" - }, - "storeLocation": { - "$numberInt": "1" - }, - "items": { - "$filter": { - "input": "$items", - "cond": { - "$and": [ - { - "$ifNull": [ - "$$this.price", - "false" - ] - }, - { - "$gt": [ - "$$this.quantity", - { - "$numberInt": "2" - } - ] - }, - { - "$in": [ - "office", - "$$this.tags" - ] - } - ] - } - } - } - } - } - ] - JSON; - - /** - * Equals Boolean - * - * @see https://www.mongodb.com/docs/atlas/atlas-search/equals/#boolean-examples - */ - case SearchEqualsBoolean = <<<'JSON' - [ - { - "$search": { - "equals": { - "path": "verified_user", - "value": true - } - } - }, - { - "$project": { - "name": { - "$numberInt": "1" - }, - "_id": { - "$numberInt": "0" - }, - "score": { - "$meta": "searchScore" - } - } - } - ] - JSON; - - /** - * Equals ObjectId - * - * @see https://www.mongodb.com/docs/atlas/atlas-search/equals/#objectid-example - */ - case SearchEqualsObjectId = <<<'JSON' - [ - { - "$search": { - "equals": { - "path": "teammates", - "value": { - "$oid": "5a9427648b0beebeb69589a1" - } - } - } - } - ] - JSON; - - /** - * Equals Date - * - * @see https://www.mongodb.com/docs/atlas/atlas-search/equals/#date-example - */ - case SearchEqualsDate = <<<'JSON' - [ - { - "$search": { - "equals": { - "path": "account_created", - "value": { - "$date": { - "$numberLong": "1651640468000" - } - } - } - } - } - ] - JSON; - - /** - * Equals Number - * - * @see https://www.mongodb.com/docs/atlas/atlas-search/equals/#number-example - */ - case SearchEqualsNumber = <<<'JSON' - [ - { - "$search": { - "equals": { - "path": "employee_number", - "value": { - "$numberInt": "259" - } - } - } - } - ] - JSON; - - /** - * Equals String - * - * @see https://www.mongodb.com/docs/atlas/atlas-search/equals/#string-example - */ - case SearchEqualsString = <<<'JSON' - [ - { - "$search": { - "equals": { - "path": "name", - "value": "jim hall" - } - } - } - ] - JSON; - - /** - * Equals UUID - * - * @see https://www.mongodb.com/docs/atlas/atlas-search/equals/#uuid-example - */ - case SearchEqualsUUID = <<<'JSON' - [ - { - "$search": { - "equals": { - "path": "uuid", - "value": { - "$binary": { - "base64": "rzwiBlsRxJZIWCrrtdet6Q==", - "subType": "04" - } - } - } - } - } - ] - JSON; - - /** - * Equals Null - * - * @see https://www.mongodb.com/docs/atlas/atlas-search/equals/#null-example - */ - case SearchEqualsNull = <<<'JSON' - [ - { - "$search": { - "equals": { - "path": "job_title", - "value": null - } - } - } - ] - JSON; - - /** - * Exists Basic - * - * @see https://www.mongodb.com/docs/atlas/atlas-search/exists/#basic-example - */ - case SearchExistsBasic = <<<'JSON' - [ - { - "$search": { - "exists": { - "path": "type" - } - } - } - ] - JSON; - - /** - * Exists Embedded - * - * @see https://www.mongodb.com/docs/atlas/atlas-search/exists/#embedded-example - */ - case SearchExistsEmbedded = <<<'JSON' - [ - { - "$search": { - "exists": { - "path": "quantities.lemons" - } - } - } - ] - JSON; - - /** - * Exists Compound - * - * @see https://www.mongodb.com/docs/atlas/atlas-search/exists/#compound-example - */ - case SearchExistsCompound = <<<'JSON' - [ - { - "$search": { - "compound": { - "must": [ - { - "exists": { - "path": "type" - } - }, - { - "text": { - "query": "apple", - "path": "type" - } - } - ], - "should": { - "text": { - "query": "fuji", - "path": "description" - } - } - } - } - } - ] - JSON; - - /** - * Facet - * - * @see https://www.mongodb.com/docs/atlas/atlas-search/facet/#examples - */ - case SearchFacet = <<<'JSON' - [ - { - "$search": { - "facet": { - "operator": { - "near": { - "path": "released", - "origin": { - "$date": { - "$numberLong": "930787200000" - } - }, - "pivot": { - "$numberLong": "7776000000" - } - } - }, - "facets": { - "genresFacet": { - "type": "string", - "path": "genres" - } - } - } - } - }, - { - "$limit": { - "$numberInt": "2" - } - }, - { - "$facet": { - "docs": [ - { - "$project": { - "title": { - "$numberInt": "1" - }, - "released": { - "$numberInt": "1" - } - } - } - ], - "meta": [ - { - "$replaceWith": "$$SEARCH_META" - }, - { - "$limit": { - "$numberInt": "1" - } - } - ] - } - }, - { - "$set": { - "meta": { - "$arrayElemAt": [ - "$meta", - { - "$numberInt": "0" - } - ] - } - } - } - ] - JSON; - - /** - * GeoShape Disjoint - * - * @see https://www.mongodb.com/docs/atlas/atlas-search/geoShape/#disjoint-example - */ - case SearchGeoShapeDisjoint = <<<'JSON' - [ - { - "$search": { - "geoShape": { - "relation": "disjoint", - "geometry": { - "type": "Polygon", - "coordinates": [ - [ - [ - { - "$numberDouble": "-161.32324199999999337" - }, - { - "$numberDouble": "22.51255700000000104" - } - ], - [ - { - "$numberDouble": "-152.44628900000000726" - }, - { - "$numberDouble": "22.065277999999999281" - } - ], - [ - { - "$numberDouble": "-156.09375" - }, - { - "$numberDouble": "17.811455999999999733" - } - ], - [ - { - "$numberDouble": "-161.32324199999999337" - }, - { - "$numberDouble": "22.51255700000000104" - } - ] - ] - ] - }, - "path": "address.location" - } - } - }, - { - "$limit": { - "$numberInt": "3" - } - }, - { - "$project": { - "_id": { - "$numberInt": "0" - }, - "name": { - "$numberInt": "1" - }, - "address": { - "$numberInt": "1" - }, - "score": { - "$meta": "searchScore" - } - } - } - ] - JSON; - - /** - * GeoShape Intersect - * - * @see https://www.mongodb.com/docs/atlas/atlas-search/geoShape/#intersects-example - */ - case SearchGeoShapeIntersect = <<<'JSON' - [ - { - "$search": { - "geoShape": { - "relation": "intersects", - "geometry": { - "type": "MultiPolygon", - "coordinates": [ - [ - [ - [ - { - "$numberDouble": "2.1694200000000001261" - }, - { - "$numberDouble": "41.400820000000003063" - } - ], - [ - { - "$numberDouble": "2.1796299999999999564" - }, - { - "$numberDouble": "41.400869999999997617" - } - ], - [ - { - "$numberDouble": "2.1814599999999999547" - }, - { - "$numberDouble": "41.397159999999999513" - } - ], - [ - { - "$numberDouble": "2.1553300000000001901" - }, - { - "$numberDouble": "41.406860000000001776" - } - ], - [ - { - "$numberDouble": "2.1459600000000000897" - }, - { - "$numberDouble": "41.384749999999996817" - } - ], - [ - { - "$numberDouble": "2.1751900000000001789" - }, - { - "$numberDouble": "41.410350000000001103" - } - ], - [ - { - "$numberDouble": "2.1694200000000001261" - }, - { - "$numberDouble": "41.400820000000003063" - } - ] - ] - ], - [ - [ - [ - { - "$numberDouble": "2.1636500000000000732" - }, - { - "$numberDouble": "41.3941599999999994" - } - ], - [ - { - "$numberDouble": "2.1696300000000001695" - }, - { - "$numberDouble": "41.397260000000002833" - } - ], - [ - { - "$numberDouble": "2.1539500000000000313" - }, - { - "$numberDouble": "41.380049999999997112" - } - ], - [ - { - "$numberDouble": "2.1793499999999998984" - }, - { - "$numberDouble": "41.430379999999999541" - } - ], - [ - { - "$numberDouble": "2.1636500000000000732" - }, - { - "$numberDouble": "41.3941599999999994" - } - ] - ] - ] - ] - }, - "path": "address.location" - } - } - }, - { - "$limit": { - "$numberInt": "3" - } - }, - { - "$project": { - "_id": { - "$numberInt": "0" - }, - "name": { - "$numberInt": "1" - }, - "address": { - "$numberInt": "1" - }, - "score": { - "$meta": "searchScore" - } - } - } - ] - JSON; - - /** - * GeoShape Within - * - * @see https://www.mongodb.com/docs/atlas/atlas-search/geoShape/#within-example - */ - case SearchGeoShapeWithin = <<<'JSON' - [ - { - "$search": { - "geoShape": { - "relation": "within", - "geometry": { - "type": "Polygon", - "coordinates": [ - [ - [ - { - "$numberDouble": "-74.3994140625" - }, - { - "$numberDouble": "40.530501775700003009" - } - ], - [ - { - "$numberDouble": "-74.729003906299993787" - }, - { - "$numberDouble": "40.580584664100001646" - } - ], - [ - { - "$numberDouble": "-74.772949218799993787" - }, - { - "$numberDouble": "40.946713665099998991" - } - ], - [ - { - "$numberDouble": "-74.069824218799993787" - }, - { - "$numberDouble": "41.129021347500000161" - } - ], - [ - { - "$numberDouble": "-73.65234375" - }, - { - "$numberDouble": "40.996484014399996454" - } - ], - [ - { - "$numberDouble": "-72.6416015625" - }, - { - "$numberDouble": "40.946713665099998991" - } - ], - [ - { - "$numberDouble": "-72.355957031299993787" - }, - { - "$numberDouble": "40.797177415199996631" - } - ], - [ - { - "$numberDouble": "-74.3994140625" - }, - { - "$numberDouble": "40.530501775700003009" - } - ] - ] - ] - }, - "path": "address.location" - } - } - }, - { - "$limit": { - "$numberInt": "3" - } - }, - { - "$project": { - "_id": { - "$numberInt": "0" - }, - "name": { - "$numberInt": "1" - }, - "address": { - "$numberInt": "1" - }, - "score": { - "$meta": "searchScore" - } - } - } - ] - JSON; - - /** - * GeoWithin box - * - * @see https://www.mongodb.com/docs/atlas/atlas-search/geoWithin/#box-example - */ - case SearchGeoWithinBox = <<<'JSON' - [ - { - "$search": { - "geoWithin": { - "path": "address.location", - "box": { - "bottomLeft": { - "type": "Point", - "coordinates": [ - { - "$numberDouble": "112.46699999999999875" - }, - { - "$numberDouble": "-55.049999999999997158" - } - ] - }, - "topRight": { - "type": "Point", - "coordinates": [ - { - "$numberInt": "168" - }, - { - "$numberDouble": "-9.1329999999999991189" - } - ] - } - } - } - } - }, - { - "$limit": { - "$numberInt": "3" - } - }, - { - "$project": { - "_id": { - "$numberInt": "0" - }, - "name": { - "$numberInt": "1" - }, - "address": { - "$numberInt": "1" - } - } - } - ] - JSON; - - /** - * GeoWithin circle - * - * @see https://www.mongodb.com/docs/atlas/atlas-search/geoWithin/#circle-example - */ - case SearchGeoWithinCircle = <<<'JSON' - [ - { - "$search": { - "geoWithin": { - "circle": { - "center": { - "type": "Point", - "coordinates": [ - { - "$numberDouble": "-73.540000000000006253" - }, - { - "$numberDouble": "45.539999999999999147" - } - ] - }, - "radius": { - "$numberInt": "1600" - } - }, - "path": "address.location" - } - } - }, - { - "$limit": { - "$numberInt": "3" - } - }, - { - "$project": { - "_id": { - "$numberInt": "0" - }, - "name": { - "$numberInt": "1" - }, - "address": { - "$numberInt": "1" - } - } - } - ] - JSON; - - /** - * GeoWithin geometry - * - * @see https://www.mongodb.com/docs/atlas/atlas-search/geoWithin/#geometry-examples - */ - case SearchGeoWithinGeometry = <<<'JSON' - [ - { - "$search": { - "geoWithin": { - "geometry": { - "type": "Polygon", - "coordinates": [ - [ - [ - { - "$numberDouble": "-161.32324199999999337" - }, - { - "$numberDouble": "22.51255700000000104" - } - ], - [ - { - "$numberDouble": "-152.44628900000000726" - }, - { - "$numberDouble": "22.065277999999999281" - } - ], - [ - { - "$numberDouble": "-156.09375" - }, - { - "$numberDouble": "17.811455999999999733" - } - ], - [ - { - "$numberDouble": "-161.32324199999999337" - }, - { - "$numberDouble": "22.51255700000000104" - } - ] - ] - ] - }, - "path": "address.location" - } - } - }, - { - "$limit": { - "$numberInt": "3" - } - }, - { - "$project": { - "_id": { - "$numberInt": "0" - }, - "name": { - "$numberInt": "1" - }, - "address": { - "$numberInt": "1" - } - } - } - ] - JSON; - - /** - * In Single Value Field Match - * - * @see https://www.mongodb.com/docs/atlas/atlas-search/in/#examples - */ - case SearchInSingleValueFieldMatch = <<<'JSON' - [ - { - "$search": { - "in": { - "path": "birthdate", - "value": [ - { - "$date": { - "$numberLong": "226117231000" - } - }, - { - "$date": { - "$numberLong": "226022400000" - } - }, - { - "$date": { - "$numberLong": "231803855000" - } - } - ] - } - } - }, - { - "$project": { - "_id": { - "$numberInt": "0" - }, - "name": { - "$numberInt": "1" - }, - "birthdate": { - "$numberInt": "1" - } - } - } - ] - JSON; - - /** - * In Array Value Field Match - * - * @see https://www.mongodb.com/docs/atlas/atlas-search/in/#examples - */ - case SearchInArrayValueFieldMatch = <<<'JSON' - [ - { - "$search": { - "in": { - "path": "accounts", - "value": [ - { - "$numberInt": "371138" - }, - { - "$numberInt": "371139" - }, - { - "$numberInt": "371140" - } - ] - } - } - }, - { - "$project": { - "_id": { - "$numberInt": "0" - }, - "name": { - "$numberInt": "1" - }, - "accounts": { - "$numberInt": "1" - } - } - } - ] - JSON; - - /** - * In Compound Query Match - * - * @see https://www.mongodb.com/docs/atlas/atlas-search/in/#examples - */ - case SearchInCompoundQueryMatch = <<<'JSON' - [ - { - "$search": { - "compound": { - "must": [ - { - "in": { - "path": "name", - "value": [ - "james sanchez", - "jennifer lawrence" - ] - } - } - ], - "should": [ - { - "in": { - "path": "_id", - "value": [ - { - "$oid": "5ca4bbcea2dd94ee58162a72" - }, - { - "$oid": "5ca4bbcea2dd94ee58162a91" - } - ] - } - } - ] - } - } - }, - { - "$limit": { - "$numberInt": "5" - } - }, - { - "$project": { - "_id": { - "$numberInt": "1" - }, - "name": { - "$numberInt": "1" - }, - "score": { - "$meta": "searchScore" - } - } - } - ] - JSON; - - /** - * MoreLikeThis Single Document with Multiple Fields - * - * @see https://www.mongodb.com/docs/atlas/atlas-search/morelikethis/#example-1--single-document-with-multiple-fields - */ - case SearchMoreLikeThisSingleDocumentWithMultipleFields = <<<'JSON' - [ - { - "$search": { - "moreLikeThis": { - "like": { - "title": "The Godfather", - "genres": "action" - } - } - } - }, - { - "$limit": { - "$numberInt": "5" - } - }, - { - "$project": { - "_id": { - "$numberInt": "0" - }, - "title": { - "$numberInt": "1" - }, - "released": { - "$numberInt": "1" - }, - "genres": { - "$numberInt": "1" - } - } - } - ] - JSON; - - /** - * MoreLikeThis Input Document Excluded in Results - * - * @see https://www.mongodb.com/docs/atlas/atlas-search/morelikethis/#example-2--input-document-excluded-in-results - */ - case SearchMoreLikeThisInputDocumentExcludedInResults = <<<'JSON' - [ - { - "$search": { - "compound": { - "must": [ - { - "moreLikeThis": { - "like": { - "_id": { - "$oid": "573a1396f29313caabce4a9a" - }, - "genres": [ - "Crime", - "Drama" - ], - "title": "The Godfather" - } - } - } - ], - "mustNot": [ - { - "equals": { - "path": "_id", - "value": { - "$oid": "573a1396f29313caabce4a9a" - } - } - } - ] - } - } - }, - { - "$limit": { - "$numberInt": "5" - } - }, - { - "$project": { - "_id": { - "$numberInt": "1" - }, - "title": { - "$numberInt": "1" - }, - "released": { - "$numberInt": "1" - }, - "genres": { - "$numberInt": "1" - } - } - } - ] - JSON; - - /** - * MoreLikeThis Multiple Analyzers - * - * @see https://www.mongodb.com/docs/atlas/atlas-search/morelikethis/#example-3--multiple-analyzers - */ - case SearchMoreLikeThisMultipleAnalyzers = <<<'JSON' - [ - { - "$search": { - "compound": { - "should": [ - { - "moreLikeThis": { - "like": { - "_id": { - "$oid": "573a1396f29313caabce4a9a" - }, - "genres": [ - "Crime", - "Drama" - ], - "title": "The Godfather" - } - } - } - ], - "mustNot": [ - { - "equals": { - "path": "_id", - "value": { - "$oid": "573a1394f29313caabcde9ef" - } - } - } - ] - } - } - }, - { - "$limit": { - "$numberInt": "10" - } - }, - { - "$project": { - "title": { - "$numberInt": "1" - }, - "genres": { - "$numberInt": "1" - }, - "_id": { - "$numberInt": "1" - } - } - } - ] - JSON; - - /** - * Near Number - * - * @see https://www.mongodb.com/docs/atlas/atlas-search/near/#number-example - */ - case SearchNearNumber = <<<'JSON' - [ - { - "$search": { - "index": "runtimes", - "near": { - "path": "runtime", - "origin": { - "$numberInt": "279" - }, - "pivot": { - "$numberInt": "2" - } - } - } - }, - { - "$limit": { - "$numberInt": "7" - } - }, - { - "$project": { - "_id": { - "$numberInt": "0" - }, - "title": { - "$numberInt": "1" - }, - "runtime": { - "$numberInt": "1" - }, - "score": { - "$meta": "searchScore" - } - } - } - ] - JSON; - - /** - * Near Date - * - * @see https://www.mongodb.com/docs/atlas/atlas-search/near/#date-example - */ - case SearchNearDate = <<<'JSON' - [ - { - "$search": { - "index": "releaseddate", - "near": { - "path": "released", - "origin": { - "$date": { - "$numberLong": "-1713657600000" - } - }, - "pivot": { - "$numberLong": "7776000000" - } - } - } - }, - { - "$limit": { - "$numberInt": "3" - } - }, - { - "$project": { - "_id": { - "$numberInt": "0" - }, - "title": { - "$numberInt": "1" - }, - "released": { - "$numberInt": "1" - }, - "score": { - "$meta": "searchScore" - } - } - } - ] - JSON; - - /** - * Near GeoJSON Point - * - * @see https://www.mongodb.com/docs/atlas/atlas-search/near/#geojson-point-examples - */ - case SearchNearGeoJSONPoint = <<<'JSON' - [ - { - "$search": { - "near": { - "origin": { - "type": "Point", - "coordinates": [ - { - "$numberDouble": "-8.6130800000000000693" - }, - { - "$numberDouble": "41.141300000000001091" - } - ] - }, - "pivot": { - "$numberInt": "1000" - }, - "path": "address.location" - } - } - }, - { - "$limit": { - "$numberInt": "3" - } - }, - { - "$project": { - "_id": { - "$numberInt": "0" - }, - "name": { - "$numberInt": "1" - }, - "address": { - "$numberInt": "1" - }, - "score": { - "$meta": "searchScore" - } - } - } - ] - JSON; - - /** - * Near Compound - * - * @see https://www.mongodb.com/docs/atlas/atlas-search/near/#compound-example - */ - case SearchNearCompound = <<<'JSON' - [ - { - "$search": { - "compound": { - "must": { - "text": { - "query": "Apartment", - "path": "property_type" - } - }, - "should": { - "near": { - "origin": { - "type": "Point", - "coordinates": [ - { - "$numberDouble": "114.15027000000000612" - }, - { - "$numberDouble": "22.281580000000001718" - } - ] - }, - "pivot": { - "$numberInt": "1000" - }, - "path": "address.location" - } - } - } - } - }, - { - "$limit": { - "$numberInt": "3" - } - }, - { - "$project": { - "_id": { - "$numberInt": "0" - }, - "property_type": { - "$numberInt": "1" - }, - "address": { - "$numberInt": "1" - }, - "score": { - "$meta": "searchScore" - } - } - } - ] - JSON; - - /** - * Single Phrase - * - * @see https://www.mongodb.com/docs/atlas/atlas-search/phrase/#single-phrase-example - */ - case SearchSinglePhrase = <<<'JSON' - [ - { - "$search": { - "phrase": { - "path": "title", - "query": "new york" - } - } - }, - { - "$limit": { - "$numberInt": "10" - } - }, - { - "$project": { - "_id": { - "$numberInt": "0" - }, - "title": { - "$numberInt": "1" - }, - "score": { - "$meta": "searchScore" - } - } - } - ] - JSON; - - /** - * Multiple Phrase - * - * @see https://www.mongodb.com/docs/atlas/atlas-search/phrase/#multiple-phrases-example - */ - case SearchMultiplePhrase = <<<'JSON' - [ - { - "$search": { - "phrase": { - "path": "title", - "query": [ - "the man", - "the moon" - ] - } - } - }, - { - "$limit": { - "$numberInt": "10" - } - }, - { - "$project": { - "_id": { - "$numberInt": "0" - }, - "title": { - "$numberInt": "1" - }, - "score": { - "$meta": "searchScore" - } - } - } - ] - JSON; - - /** - * Phrase Slop - * - * @see https://www.mongodb.com/docs/atlas/atlas-search/phrase/#slop-example - */ - case SearchPhraseSlop = <<<'JSON' - [ - { - "$search": { - "phrase": { - "path": "title", - "query": "men women", - "slop": { - "$numberInt": "5" - } - } - } - }, - { - "$project": { - "_id": { - "$numberInt": "0" - }, - "title": { - "$numberInt": "1" - }, - "score": { - "$meta": "searchScore" - } - } - } - ] - JSON; - - /** - * Phrase Synonyms - * - * @see https://www.mongodb.com/docs/atlas/atlas-search/phrase/#synonyms-example - */ - case SearchPhraseSynonyms = <<<'JSON' - [ - { - "$search": { - "phrase": { - "path": "plot", - "query": "automobile race", - "slop": { - "$numberInt": "5" - }, - "synonyms": "my_synonyms" - } - } - }, - { - "$limit": { - "$numberInt": "5" - } - }, - { - "$project": { - "_id": { - "$numberInt": "0" - }, - "plot": { - "$numberInt": "1" - }, - "title": { - "$numberInt": "1" - }, - "score": { - "$meta": "searchScore" - } - } - } - ] - JSON; - - /** - * Boolean Operator Queries - * - * @see https://www.mongodb.com/docs/atlas/atlas-search/queryString/#boolean-operator-queries - */ - case SearchBooleanOperatorQueries = <<<'JSON' - [ - { - "$search": { - "queryString": { - "defaultPath": "title", - "query": "Rocky AND (IV OR 4 OR Four)" - } - } - }, - { - "$project": { - "_id": { - "$numberInt": "0" - }, - "title": { - "$numberInt": "1" - } - } - } - ] - JSON; - - /** - * Range Number gte lte - * - * @see https://www.mongodb.com/docs/atlas/atlas-search/range/#number-example - */ - case SearchRangeNumberGteLte = <<<'JSON' - [ - { - "$search": { - "range": { - "path": "runtime", - "gte": { - "$numberInt": "2" - }, - "lte": { - "$numberInt": "3" - } - } - } - }, - { - "$limit": { - "$numberInt": "5" - } - }, - { - "$project": { - "_id": { - "$numberInt": "0" - }, - "title": { - "$numberInt": "1" - }, - "runtime": { - "$numberInt": "1" - } - } - } - ] - JSON; - - /** - * Range Number lte - * - * @see https://www.mongodb.com/docs/atlas/atlas-search/range/#number-example - */ - case SearchRangeNumberLte = <<<'JSON' - [ - { - "$search": { - "range": { - "path": "runtime", - "lte": { - "$numberInt": "2" - } - } - } - }, - { - "$limit": { - "$numberInt": "5" - } - }, - { - "$project": { - "_id": { - "$numberInt": "0" - }, - "title": { - "$numberInt": "1" - }, - "runtime": { - "$numberInt": "1" - }, - "score": { - "$meta": "searchScore" - } - } - } - ] - JSON; - - /** - * Range Date - * - * @see https://www.mongodb.com/docs/atlas/atlas-search/range/#date-example - */ - case SearchRangeDate = <<<'JSON' - [ - { - "$search": { - "range": { - "path": "released", - "gt": { - "$date": { - "$numberLong": "1262304000000" - } - }, - "lt": { - "$date": { - "$numberLong": "1420070400000" - } - } - } - } - }, - { - "$limit": { - "$numberInt": "5" - } - }, - { - "$project": { - "_id": { - "$numberInt": "0" - }, - "title": { - "$numberInt": "1" - }, - "released": { - "$numberInt": "1" - } - } - } - ] - JSON; - - /** - * Range ObjectId - * - * @see https://www.mongodb.com/docs/atlas/atlas-search/range/#objectid-example - */ - case SearchRangeObjectId = <<<'JSON' - [ - { - "$search": { - "range": { - "path": "_id", - "gte": { - "$oid": "573a1396f29313caabce4a9a" - }, - "lte": { - "$oid": "573a1396f29313caabce4ae7" - } - } - } - }, - { - "$project": { - "_id": { - "$numberInt": "1" - }, - "title": { - "$numberInt": "1" - }, - "released": { - "$numberInt": "1" - } - } - } - ] - JSON; - - /** - * Range String - * - * @see https://www.mongodb.com/docs/atlas/atlas-search/range/#string-example - */ - case SearchRangeString = <<<'JSON' - [ - { - "$search": { - "range": { - "path": "title", - "gt": "city", - "lt": "country" - } - } - }, - { - "$limit": { - "$numberInt": "5" - } - }, - { - "$project": { - "_id": { - "$numberInt": "0" - }, - "title": { - "$numberInt": "1" - } - } - } - ] - JSON; - - /** - * Regex - * - * @see https://www.mongodb.com/docs/atlas/atlas-search/regex/#examples - */ - case SearchRegex = <<<'JSON' - [ - { - "$search": { - "regex": { - "path": "title", - "query": "[0-9]{2} (.){4}s" - } - } - }, - { - "$project": { - "_id": { - "$numberInt": "0" - }, - "title": { - "$numberInt": "1" - } - } - } - ] - JSON; - - /** - * Text Wildcard Path - * - * @see https://www.mongodb.com/docs/atlas/atlas-search/text/ - */ - case SearchTextWildcardPath = <<<'JSON' - [ - { - "$search": { - "text": { - "path": { - "wildcard": "*" - }, - "query": "surfer" - } - } - }, - { - "$project": { - "_id": { - "$numberInt": "0" - }, - "title": { - "$numberInt": "1" - }, - "score": { - "$meta": "searchScore" - } - } - } - ] - JSON; - - /** - * Text Basic - * - * @see https://www.mongodb.com/docs/atlas/atlas-search/text/#basic-example - */ - case SearchTextBasic = <<<'JSON' - [ - { - "$search": { - "text": { - "path": "title", - "query": "surfer" - } - } - }, - { - "$project": { - "_id": { - "$numberInt": "0" - }, - "title": { - "$numberInt": "1" - }, - "score": { - "$meta": "searchScore" - } - } - } - ] - JSON; - - /** - * Text Fuzzy Default - * - * @see https://www.mongodb.com/docs/atlas/atlas-search/text/#fuzzy-examples - */ - case SearchTextFuzzyDefault = <<<'JSON' - [ - { - "$search": { - "text": { - "path": "title", - "query": "naw yark", - "fuzzy": {} - } - } - }, - { - "$limit": { - "$numberInt": "10" - } - }, - { - "$project": { - "_id": { - "$numberInt": "0" - }, - "title": { - "$numberInt": "1" - }, - "score": { - "$meta": "searchScore" - } - } - } - ] - JSON; - - /** - * Text Fuzzy maxExpansions - * - * @see https://www.mongodb.com/docs/atlas/atlas-search/text/#fuzzy-examples - */ - case SearchTextFuzzyMaxExpansions = <<<'JSON' - [ - { - "$search": { - "text": { - "path": "title", - "query": "naw yark", - "fuzzy": { - "maxEdits": { - "$numberInt": "1" - }, - "maxExpansions": { - "$numberInt": "100" - } - } - } - } - }, - { - "$limit": { - "$numberInt": "10" - } - }, - { - "$project": { - "_id": { - "$numberInt": "0" - }, - "title": { - "$numberInt": "1" - }, - "score": { - "$meta": "searchScore" - } - } - } - ] - JSON; - - /** - * Text Fuzzy prefixLength - * - * @see https://www.mongodb.com/docs/atlas/atlas-search/text/#fuzzy-examples - */ - case SearchTextFuzzyPrefixLength = <<<'JSON' - [ - { - "$search": { - "text": { - "path": "title", - "query": "naw yark", - "fuzzy": { - "maxEdits": { - "$numberInt": "1" - }, - "prefixLength": { - "$numberInt": "2" - } - } - } - } - }, - { - "$limit": { - "$numberInt": "8" - } - }, - { - "$project": { - "_id": { - "$numberInt": "1" - }, - "title": { - "$numberInt": "1" - }, - "score": { - "$meta": "searchScore" - } - } - } - ] - JSON; - - /** - * Text Match any Using equivalent Mapping - * - * @see https://www.mongodb.com/docs/atlas/atlas-search/text/#match-any-using-equivalent-mapping - */ - case SearchTextMatchAnyUsingEquivalentMapping = <<<'JSON' - [ - { - "$search": { - "text": { - "path": "plot", - "query": "attire", - "synonyms": "my_synonyms", - "matchCriteria": "any" - } - } - }, - { - "$limit": { - "$numberInt": "5" - } - }, - { - "$project": { - "_id": { - "$numberInt": "0" - }, - "plot": { - "$numberInt": "1" - }, - "title": { - "$numberInt": "1" - }, - "score": { - "$meta": "searchScore" - } - } - } - ] - JSON; - - /** - * Text Match any Using explicit Mapping - * - * @see https://www.mongodb.com/docs/atlas/atlas-search/text/#match-any-using-explicit-mapping - */ - case SearchTextMatchAnyUsingExplicitMapping = <<<'JSON' - [ - { - "$search": { - "text": { - "path": "plot", - "query": "boat race", - "synonyms": "my_synonyms", - "matchCriteria": "any" - } - } - }, - { - "$limit": { - "$numberInt": "10" - } - }, - { - "$project": { - "_id": { - "$numberInt": "0" - }, - "plot": { - "$numberInt": "1" - }, - "title": { - "$numberInt": "1" - }, - "score": { - "$meta": "searchScore" - } - } - } - ] - JSON; - - /** - * Text Match all Using Synonyms - * - * @see https://www.mongodb.com/docs/atlas/atlas-search/text/#match-all-using-synonyms - */ - case SearchTextMatchAllUsingSynonyms = <<<'JSON' - [ - { - "$search": { - "text": { - "path": "plot", - "query": "automobile race", - "matchCriteria": "all", - "synonyms": "my_synonyms" - } - } - }, - { - "$limit": { - "$numberInt": "20" - } - }, - { - "$project": { - "_id": { - "$numberInt": "0" - }, - "plot": { - "$numberInt": "1" - }, - "title": { - "$numberInt": "1" - }, - "score": { - "$meta": "searchScore" - } - } - } - ] - JSON; - - /** - * Wildcard - * - * @see https://www.mongodb.com/docs/atlas/atlas-search/wildcard/#index-definition - */ - case SearchWildcard = <<<'JSON' - [ - { - "$search": { - "wildcard": { - "query": "Wom?n *", - "path": { - "wildcard": "*" - } - } - } - }, - { - "$limit": { - "$numberInt": "5" - } - }, - { - "$project": { - "_id": { - "$numberInt": "0" - }, - "title": { - "$numberInt": "1" - } - } - } - ] - JSON; - - /** - * Wildcard Escape Character Example - * - * @see https://www.mongodb.com/docs/atlas/atlas-search/wildcard/#escape-character-example - */ - case SearchWildcardEscapeCharacterExample = <<<'JSON' - [ - { - "$search": { - "wildcard": { - "query": "*\\?", - "path": "title" - } - } - }, - { - "$limit": { - "$numberInt": "5" - } - }, - { - "$project": { - "_id": { - "$numberInt": "0" - }, - "title": { - "$numberInt": "1" - } - } - } - ] - JSON; - /** * Example * diff --git a/tests/Builder/Stage/SearchMetaStageTest.php b/tests/Builder/Stage/SearchMetaStageTest.php index 60160ba33..6ded31935 100644 --- a/tests/Builder/Stage/SearchMetaStageTest.php +++ b/tests/Builder/Stage/SearchMetaStageTest.php @@ -7,6 +7,7 @@ use DateTimeImmutable; use MongoDB\BSON\UTCDateTime; use MongoDB\Builder\Pipeline; +use MongoDB\Builder\Search; use MongoDB\Builder\Stage; use MongoDB\Tests\Builder\PipelineTestCase; @@ -21,19 +22,17 @@ public function testAutocompleteBucketResultsThroughFacetQueries(): void { $pipeline = new Pipeline( Stage::searchMeta( - facet: object( - operator: object( - autocomplete: object( - path: 'title', - query: 'Gravity', - ), - ), + Search::facet( facets: object( titleFacet: object( type: 'string', path: 'title', ), ), + operator: Search::autocomplete( + path: 'title', + query: 'Gravity', + ), ), ), ); @@ -45,13 +44,11 @@ public function testDateFacet(): void { $pipeline = new Pipeline( Stage::searchMeta( - facet: object( - operator: object( - range: object( - path: 'released', - gte: new UTCDateTime(new DateTimeImmutable('2000-01-01')), - lte: new UTCDateTime(new DateTimeImmutable('2015-01-31')), - ), + Search::facet( + operator: Search::range( + path: 'released', + gte: new UTCDateTime(new DateTimeImmutable('2000-01-01')), + lte: new UTCDateTime(new DateTimeImmutable('2015-01-31')), ), facets: object( yearFacet: object( @@ -77,7 +74,7 @@ public function testExample(): void { $pipeline = new Pipeline( Stage::searchMeta( - range: object( + Search::range( path: 'year', gte: 1998, lt: 1999, @@ -93,13 +90,11 @@ public function testMetadataResults(): void { $pipeline = new Pipeline( Stage::searchMeta( - facet: object( - operator: object( - range: object( - path: 'released', - gte: new UTCDateTime(new DateTimeImmutable('2000-01-01')), - lte: new UTCDateTime(new DateTimeImmutable('2015-01-31')), - ), + Search::facet( + operator: Search::range( + path: 'released', + gte: new UTCDateTime(new DateTimeImmutable('2000-01-01')), + lte: new UTCDateTime(new DateTimeImmutable('2015-01-31')), ), facets: object( directorsFacet: object( @@ -129,13 +124,11 @@ public function testYearFacet(): void { $pipeline = new Pipeline( Stage::searchMeta( - facet: object( - operator: object( - range: object( - path: 'year', - gte: 1980, - lte: 2000, - ), + Search::facet( + operator: Search::range( + path: 'year', + gte: 1980, + lte: 2000, ), facets: object( yearFacet: object( diff --git a/tests/Builder/Stage/SearchStageTest.php b/tests/Builder/Stage/SearchStageTest.php index b5d583906..faae2ab11 100644 --- a/tests/Builder/Stage/SearchStageTest.php +++ b/tests/Builder/Stage/SearchStageTest.php @@ -5,560 +5,23 @@ namespace MongoDB\Tests\Builder\Stage; use DateTime; -use DateTimeImmutable; -use MongoDB\BSON\Binary; -use MongoDB\BSON\ObjectId; use MongoDB\BSON\UTCDateTime; use MongoDB\Builder\Expression; use MongoDB\Builder\Pipeline; +use MongoDB\Builder\Search; use MongoDB\Builder\Stage; -use MongoDB\Builder\Type\Sort; -use MongoDB\Builder\Variable; use MongoDB\Tests\Builder\PipelineTestCase; -use function MongoDB\object; -use function pack; - /** * Test $search stage */ class SearchStageTest extends PipelineTestCase { - public function testAutocompleteAcrossMultipleFields(): void - { - $pipeline = new Pipeline( - Stage::search( - compound: object( - should: [ - object( - autocomplete: object( - query: 'inter', - path: 'title', - ), - ), - object( - autocomplete: object( - query: 'inter', - path: 'plot', - ), - ), - ], - minimumShouldMatch: 1, - ), - ), - Stage::limit(10), - Stage::project(_id: 0, title: 1, plot: 1), - ); - - $this->assertSamePipeline(Pipelines::SearchAutocompleteAcrossMultipleFields, $pipeline); - } - - public function testAutocompleteBasic(): void - { - $pipeline = new Pipeline( - Stage::search( - autocomplete: object( - query: 'off', - path: 'title', - ), - ), - Stage::limit(10), - Stage::project(_id: 0, title: 1), - ); - - $this->assertSamePipeline(Pipelines::SearchAutocompleteBasic, $pipeline); - } - - public function testAutocompleteFuzzy(): void - { - $pipeline = new Pipeline( - Stage::search( - autocomplete: object( - query: 'pre', - path: 'title', - fuzzy: object( - maxEdits: 1, - prefixLength: 1, - maxExpansions: 256, - ), - ), - ), - Stage::limit(10), - Stage::project(_id: 0, title: 1), - ); - - $this->assertSamePipeline(Pipelines::SearchAutocompleteFuzzy, $pipeline); - } - - public function testAutocompleteHighlighting(): void - { - $pipeline = new Pipeline( - Stage::search( - autocomplete: object( - query: 'ger', - path: 'title', - ), - highlight: object( - path: 'title', - ), - ), - Stage::limit(5), - Stage::project( - score: ['$meta' => 'searchScore'], - _id: 0, - title: 1, - highlights: ['$meta' => 'searchHighlights'], - ), - ); - - $this->assertSamePipeline(Pipelines::SearchAutocompleteHighlighting, $pipeline); - } - - public function testAutocompleteTokenOrderAny(): void - { - $pipeline = new Pipeline( - Stage::search( - autocomplete: object( - query: 'men with', - path: 'title', - tokenOrder: 'any', - ), - ), - Stage::limit(4), - Stage::project(_id: 0, title: 1), - ); - - $this->assertSamePipeline(Pipelines::SearchAutocompleteTokenOrderAny, $pipeline); - } - - public function testAutocompleteTokenOrderSequential(): void - { - $pipeline = new Pipeline( - Stage::search( - autocomplete: object( - query: 'men with', - path: 'title', - tokenOrder: 'sequential', - ), - ), - Stage::limit(4), - Stage::project(_id: 0, title: 1), - ); - - $this->assertSamePipeline(Pipelines::SearchAutocompleteTokenOrderSequential, $pipeline); - } - - public function testBooleanOperatorQueries(): void - { - $pipeline = new Pipeline( - Stage::search( - queryString: object( - defaultPath: 'title', - query: 'Rocky AND (IV OR 4 OR Four)', - ), - ), - Stage::project(_id: 0, title: 1), - ); - - $this->assertSamePipeline(Pipelines::SearchBooleanOperatorQueries, $pipeline); - } - - public function testCompoundFilter(): void - { - $pipeline = new Pipeline( - Stage::search( - compound: object( - must: [ - object( - text: object( - query: 'varieties', - path: 'description', - ), - ), - ], - should: [ - object( - text: object( - query: 'banana', - path: 'description', - ), - ), - ], - filter: [ - object( - text: object( - query: 'granny', - path: 'description', - ), - ), - ], - ), - ), - ); - - $this->assertSamePipeline(Pipelines::SearchCompoundFilter, $pipeline); - } - - public function testCompoundMinimumShouldMatch(): void - { - $pipeline = new Pipeline( - Stage::search( - compound: object( - must: [ - object( - text: object( - query: 'varieties', - path: 'description', - ), - ), - ], - should: [ - object( - text: object( - query: 'Fuji', - path: 'description', - ), - ), - object( - text: object( - query: 'Golden Delicious', - path: 'description', - ), - ), - ], - minimumShouldMatch: 1, - ), - ), - ); - - $this->assertSamePipeline(Pipelines::SearchCompoundMinimumShouldMatch, $pipeline); - } - - public function testCompoundMustAndMustNot(): void - { - $pipeline = new Pipeline( - Stage::search( - compound: object( - must: [ - object( - text: object( - query: 'varieties', - path: 'description', - ), - ), - ], - mustNot: [ - object( - text: object( - query: 'apples', - path: 'description', - ), - ), - ], - ), - ), - ); - - $this->assertSamePipeline(Pipelines::SearchCompoundMustAndMustNot, $pipeline); - } - - public function testCompoundMustAndShould(): void - { - $pipeline = new Pipeline( - Stage::search( - compound: object( - must: [ - object( - text: object( - query: 'varieties', - path: 'description', - ), - ), - ], - should: [ - object( - text: object( - query: 'Fuji', - path: 'description', - ), - ), - ], - ), - ), - Stage::project( - score: ['$meta' => 'searchScore'], - ), - ); - - $this->assertSamePipeline(Pipelines::SearchCompoundMustAndShould, $pipeline); - } - - public function testCompoundNested(): void - { - $pipeline = new Pipeline( - Stage::search( - compound: object( - should: [ - object( - text: object( - query: 'apple', - path: 'type', - ), - ), - object( - compound: object( - must: [ - object( - text: object( - query: 'organic', - path: 'category', - ), - ), - object( - equals: object( - value: true, - path: 'in_stock', - ), - ), - ], - ), - ), - ], - minimumShouldMatch: 1, - ), - ), - ); - - $this->assertSamePipeline(Pipelines::SearchCompoundNested, $pipeline); - } - - public function testEmbeddedDocumentBasic(): void - { - $pipeline = new Pipeline( - Stage::search( - embeddedDocument: object( - path: 'items', - operator: object( - compound: object( - must: [ - object( - text: object( - path: 'items.tags', - query: 'school', - ), - ), - ], - should: [ - object( - text: object( - path: 'items.name', - query: 'backpack', - ), - ), - ], - ), - ), - score: object( - embedded: object( - aggregate: 'mean', - ), - ), - ), - ), - Stage::limit(5), - Stage::project( - ...[ - 'items.name' => 1, - 'items.tags' => 1, - ], - _id: 0, - score: ['$meta' => 'searchScore'], - ), - ); - - $this->assertSamePipeline(Pipelines::SearchEmbeddedDocumentBasic, $pipeline); - } - - public function testEmbeddedDocumentFacet(): void - { - $pipeline = new Pipeline( - Stage::searchMeta( - facet: object( - operator: object( - embeddedDocument: object( - path: 'items', - operator: object( - compound: object( - must: [ - object( - text: object( - path: 'items.tags', - query: 'school', - ), - ), - ], - should: [ - object( - text: object( - path: 'items.name', - query: 'backpack', - ), - ), - ], - ), - ), - ), - ), - facets: object( - purchaseMethodFacet: object( - type: 'string', - path: 'purchaseMethod', - ), - ), - ), - ), - ); - - $this->assertSamePipeline(Pipelines::SearchEmbeddedDocumentFacet, $pipeline); - } - - public function testEmbeddedDocumentQueryAndSort(): void - { - $pipeline = new Pipeline( - Stage::search( - embeddedDocument: object( - path: 'items', - operator: object( - text: object( - path: 'items.name', - query: 'laptop', - ), - ), - ), - sort: ['items.tags' => Sort::Asc], - ), - Stage::limit(5), - Stage::project( - ...[ - 'items.name' => 1, - 'items.tags' => 1, - ], - _id: 0, - score: ['$meta' => 'searchScore'], - ), - ); - - $this->assertSamePipeline(Pipelines::SearchEmbeddedDocumentQueryAndSort, $pipeline); - } - - public function testEqualsBoolean(): void - { - $pipeline = new Pipeline( - Stage::search( - equals: object( - path: 'verified_user', - value: true, - ), - ), - Stage::project( - name: 1, - _id: 0, - score: ['$meta' => 'searchScore'], - ), - ); - - $this->assertSamePipeline(Pipelines::SearchEqualsBoolean, $pipeline); - } - - public function testEqualsDate(): void - { - $pipeline = new Pipeline( - Stage::search( - equals: object( - path: 'account_created', - value: new UTCDateTime(new DateTimeImmutable('2022-05-04T05:01:08')), - ), - ), - ); - - $this->assertSamePipeline(Pipelines::SearchEqualsDate, $pipeline); - } - - public function testEqualsNull(): void - { - $pipeline = new Pipeline( - Stage::search( - equals: object( - path: 'job_title', - value: null, - ), - ), - ); - - $this->assertSamePipeline(Pipelines::SearchEqualsNull, $pipeline); - } - - public function testEqualsNumber(): void - { - $pipeline = new Pipeline( - Stage::search( - equals: object( - path: 'employee_number', - value: 259, - ), - ), - ); - - $this->assertSamePipeline(Pipelines::SearchEqualsNumber, $pipeline); - } - - public function testEqualsObjectId(): void - { - $pipeline = new Pipeline( - Stage::search( - equals: object( - path: 'teammates', - value: new ObjectId('5a9427648b0beebeb69589a1'), - ), - ), - ); - - $this->assertSamePipeline(Pipelines::SearchEqualsObjectId, $pipeline); - } - - public function testEqualsString(): void - { - $pipeline = new Pipeline( - Stage::search( - equals: object( - path: 'name', - value: 'jim hall', - ), - ), - ); - - $this->assertSamePipeline(Pipelines::SearchEqualsString, $pipeline); - } - - public function testEqualsUUID(): void - { - $pipeline = new Pipeline( - Stage::search( - equals: object( - path: 'uuid', - value: new Binary(pack('h*', 'fac32260b5114c698485a2be5b7dda9e'), Binary::TYPE_UUID), - ), - ), - ); - - $this->assertSamePipeline(Pipelines::SearchEqualsUUID, $pipeline); - } - public function testExample(): void { $pipeline = new Pipeline( Stage::search( - near: object( + Search::near( path: 'released', origin: new UTCDateTime(new DateTime('2011-09-01T00:00:00.000+00:00')), pivot: 7776000000, @@ -577,1171 +40,4 @@ public function testExample(): void $this->assertSamePipeline(Pipelines::SearchExample, $pipeline); } - - public function testExistsBasic(): void - { - $pipeline = new Pipeline( - Stage::search( - exists: object( - path: 'type', - ), - ), - ); - - $this->assertSamePipeline(Pipelines::SearchExistsBasic, $pipeline); - } - - public function testExistsCompound(): void - { - $pipeline = new Pipeline( - Stage::search( - compound: object( - must: [ - object( - exists: object( - path: 'type', - ), - ), - object( - text: object( - query: 'apple', - path: 'type', - ), - ), - ], - should: object( - text: object( - query: 'fuji', - path: 'description', - ), - ), - ), - ), - ); - - $this->assertSamePipeline(Pipelines::SearchExistsCompound, $pipeline); - } - - public function testExistsEmbedded(): void - { - $pipeline = new Pipeline( - Stage::search( - exists: object( - path: 'quantities.lemons', - ), - ), - ); - - $this->assertSamePipeline(Pipelines::SearchExistsEmbedded, $pipeline); - } - - public function testFacet(): void - { - $pipeline = new Pipeline( - Stage::search( - facet: object( - operator: object( - near: object( - path: 'released', - origin: new UTCDateTime(new DateTimeImmutable('1999-07-01T00:00:00')), - pivot: 7776000000, - ), - ), - facets: object( - genresFacet: object( - type: 'string', - path: 'genres', - ), - ), - ), - ), - Stage::limit(2), - Stage::facet( - docs: [ - Stage::project( - title: 1, - released: 1, - ), - ], - meta: [ - Stage::replaceWith(Variable::variable('SEARCH_META')), - Stage::limit(1), - ], - ), - Stage::set( - meta: Expression::arrayElemAt(Expression::arrayFieldPath('meta'), 0), - ), - ); - - $this->assertSamePipeline(Pipelines::SearchFacet, $pipeline); - } - - public function testGeoShapeDisjoint(): void - { - $pipeline = new Pipeline( - Stage::search( - geoShape: object( - relation: 'disjoint', - geometry: object( - type: 'Polygon', - coordinates: [ - [ - [ - -161.323242, - 22.512557, - ], - [ - -152.446289, - 22.065278, - ], - [ - -156.09375, - 17.811456, - ], - [ - -161.323242, - 22.512557, - ], - ], - ], - ), - path: 'address.location', - ), - ), - Stage::limit(3), - Stage::project( - _id: 0, - name: 1, - address: 1, - score: ['$meta' => 'searchScore'], - ), - ); - - $this->assertSamePipeline(Pipelines::SearchGeoShapeDisjoint, $pipeline); - } - - public function testGeoShapeIntersect(): void - { - $pipeline = new Pipeline( - Stage::search( - geoShape: object( - relation: 'intersects', - geometry: object( - type: 'MultiPolygon', - coordinates: [ - [ - [ - [ - 2.16942, - 41.40082, - ], - [ - 2.17963, - 41.40087, - ], - [ - 2.18146, - 41.39716, - ], - [ - 2.15533, - 41.40686, - ], - [ - 2.14596, - 41.38475, - ], - [ - 2.17519, - 41.41035, - ], - [ - 2.16942, - 41.40082, - ], - ], - ], - [ - [ - [ - 2.16365, - 41.39416, - ], - [ - 2.16963, - 41.39726, - ], - [ - 2.15395, - 41.38005, - ], - [ - 2.17935, - 41.43038, - ], - [ - 2.16365, - 41.39416, - ], - ], - ], - ], - ), - path: 'address.location', - ), - ), - Stage::limit(3), - Stage::project( - _id: 0, - name: 1, - address: 1, - score: ['$meta' => 'searchScore'], - ), - ); - - $this->assertSamePipeline(Pipelines::SearchGeoShapeIntersect, $pipeline); - } - - public function testGeoShapeWithin(): void - { - $pipeline = new Pipeline( - Stage::search( - geoShape: object( - relation: 'within', - geometry: object( - type: 'Polygon', - coordinates: [ - [ - [ - -74.3994140625, - 40.5305017757, - ], - [ - -74.7290039063, - 40.5805846641, - ], - [ - -74.7729492188, - 40.9467136651, - ], - [ - -74.0698242188, - 41.1290213475, - ], - [ - -73.65234375, - 40.9964840144, - ], - [ - -72.6416015625, - 40.9467136651, - ], - [ - -72.3559570313, - 40.7971774152, - ], - [ - -74.3994140625, - 40.5305017757, - ], - ], - ], - ), - path: 'address.location', - ), - ), - Stage::limit(3), - Stage::project( - _id: 0, - name: 1, - address: 1, - score: ['$meta' => 'searchScore'], - ), - ); - - $this->assertSamePipeline(Pipelines::SearchGeoShapeWithin, $pipeline); - } - - public function testGeoWithinBox(): void - { - $pipeline = new Pipeline( - Stage::search( - geoWithin: object( - path: 'address.location', - box: object( - bottomLeft: object( - type: 'Point', - coordinates: [ - 112.467, - -55.05, - ], - ), - topRight: object( - type: 'Point', - coordinates: [ - 168, - -9.133, - ], - ), - ), - ), - ), - Stage::limit(3), - Stage::project( - _id: 0, - name: 1, - address: 1, - ), - ); - - $this->assertSamePipeline(Pipelines::SearchGeoWithinBox, $pipeline); - } - - public function testGeoWithinCircle(): void - { - $pipeline = new Pipeline( - Stage::search( - geoWithin: object( - circle: object( - center: object( - type: 'Point', - coordinates: [ - -73.54, - 45.54, - ], - ), - radius: 1600, - ), - path: 'address.location', - ), - ), - Stage::limit(3), - Stage::project( - _id: 0, - name: 1, - address: 1, - ), - ); - - $this->assertSamePipeline(Pipelines::SearchGeoWithinCircle, $pipeline); - } - - public function testGeoWithinGeometry(): void - { - $pipeline = new Pipeline( - Stage::search( - geoWithin: object( - geometry: object( - type: 'Polygon', - coordinates: [ - [ - [ - -161.323242, - 22.512557, - ], - [ - -152.446289, - 22.065278, - ], - [ - -156.09375, - 17.811456, - ], - [ - -161.323242, - 22.512557, - ], - ], - ], - ), - path: 'address.location', - ), - ), - Stage::limit(3), - Stage::project( - _id: 0, - name: 1, - address: 1, - ), - ); - - $this->assertSamePipeline(Pipelines::SearchGeoWithinGeometry, $pipeline); - } - - public function testInArrayValueFieldMatch(): void - { - $pipeline = new Pipeline( - Stage::search( - in: object( - path: 'accounts', - value: [ - 371138, - 371139, - 371140, - ], - ), - ), - Stage::project( - _id: 0, - name: 1, - accounts: 1, - ), - ); - - $this->assertSamePipeline(Pipelines::SearchInArrayValueFieldMatch, $pipeline); - } - - public function testInCompoundQueryMatch(): void - { - $pipeline = new Pipeline( - Stage::search( - compound: object( - must: [ - object( - in: object( - path: 'name', - value: [ - 'james sanchez', - 'jennifer lawrence', - ], - ), - ), - ], - should: [ - object( - in: object( - path: '_id', - value: [ - new ObjectId('5ca4bbcea2dd94ee58162a72'), - new ObjectId('5ca4bbcea2dd94ee58162a91'), - ], - ), - ), - ], - ), - ), - Stage::limit(5), - Stage::project( - _id: 1, - name: 1, - score: ['$meta' => 'searchScore'], - ), - ); - - $this->assertSamePipeline(Pipelines::SearchInCompoundQueryMatch, $pipeline); - } - - public function testInSingleValueFieldMatch(): void - { - $pipeline = new Pipeline( - Stage::search( - in: object( - path: 'birthdate', - value: [ - new UTCDateTime(new DateTimeImmutable('1977-03-02T02:20:31')), - new UTCDateTime(new DateTimeImmutable('1977-03-01T00:00:00')), - new UTCDateTime(new DateTimeImmutable('1977-05-06T21:57:35')), - ], - ), - ), - Stage::project( - _id: 0, - name: 1, - birthdate: 1, - ), - ); - - $this->assertSamePipeline(Pipelines::SearchInSingleValueFieldMatch, $pipeline); - } - - public function testMoreLikeThisInputDocumentExcludedInResults(): void - { - $pipeline = new Pipeline( - Stage::search( - compound: object( - must: [ - object( - moreLikeThis: object( - like: object( - _id: new ObjectId('573a1396f29313caabce4a9a'), - genres: [ - 'Crime', - 'Drama', - ], - title: 'The Godfather', - ), - ), - ), - ], - mustNot: [ - object( - equals: object( - path: '_id', - value: new ObjectId('573a1396f29313caabce4a9a'), - ), - ), - ], - ), - ), - Stage::limit(5), - Stage::project( - _id: 1, - title: 1, - released: 1, - genres: 1, - ), - ); - - $this->assertSamePipeline(Pipelines::SearchMoreLikeThisInputDocumentExcludedInResults, $pipeline); - } - - public function testMoreLikeThisMultipleAnalyzers(): void - { - $pipeline = new Pipeline( - Stage::search( - compound: object( - should: [ - object( - moreLikeThis: object( - like: object( - _id: new ObjectId('573a1396f29313caabce4a9a'), - genres: [ - 'Crime', - 'Drama', - ], - title: 'The Godfather', - ), - ), - ), - ], - mustNot: [ - object( - equals: object( - path: '_id', - value: new ObjectId('573a1394f29313caabcde9ef'), - ), - ), - ], - ), - ), - Stage::limit(10), - Stage::project( - title: 1, - genres: 1, - _id: 1, - ), - ); - - $this->assertSamePipeline(Pipelines::SearchMoreLikeThisMultipleAnalyzers, $pipeline); - } - - public function testMoreLikeThisSingleDocumentWithMultipleFields(): void - { - $pipeline = new Pipeline( - Stage::search( - moreLikeThis: object( - like: object( - title: 'The Godfather', - genres: 'action', - ), - ), - ), - Stage::limit(5), - Stage::project( - _id: 0, - title: 1, - released: 1, - genres: 1, - ), - ); - - $this->assertSamePipeline(Pipelines::SearchMoreLikeThisSingleDocumentWithMultipleFields, $pipeline); - } - - public function testMultiplePhrase(): void - { - $pipeline = new Pipeline( - Stage::search( - phrase: object( - path: 'title', - query: [ - 'the man', - 'the moon', - ], - ), - ), - Stage::limit(10), - Stage::project( - _id: 0, - title: 1, - score: ['$meta' => 'searchScore'], - ), - ); - - $this->assertSamePipeline(Pipelines::SearchMultiplePhrase, $pipeline); - } - - public function testNearCompound(): void - { - $pipeline = new Pipeline( - Stage::search( - compound: object( - must: object( - text: object( - query: 'Apartment', - path: 'property_type', - ), - ), - should: object( - near: object( - origin: object( - type: 'Point', - coordinates: [ - 114.15027, - 22.28158, - ], - ), - pivot: 1000, - path: 'address.location', - ), - ), - ), - ), - Stage::limit(3), - Stage::project( - _id: 0, - property_type: 1, - address: 1, - score: ['$meta' => 'searchScore'], - ), - ); - - $this->assertSamePipeline(Pipelines::SearchNearCompound, $pipeline); - } - - public function testNearDate(): void - { - $pipeline = new Pipeline( - Stage::search( - index: 'releaseddate', - near: object( - path: 'released', - origin: new UTCDateTime(new DateTimeImmutable('1915-09-13T00:00:00.000+00:00')), - pivot: 7776000000, - ), - ), - Stage::limit(3), - Stage::project( - _id: 0, - title: 1, - released: 1, - score: ['$meta' => 'searchScore'], - ), - ); - - $this->assertSamePipeline(Pipelines::SearchNearDate, $pipeline); - } - - public function testNearGeoJSONPoint(): void - { - $pipeline = new Pipeline( - Stage::search( - near: object( - origin: object( - type: 'Point', - coordinates: [ - -8.61308, - 41.1413, - ], - ), - pivot: 1000, - path: 'address.location', - ), - ), - Stage::limit(3), - Stage::project( - _id: 0, - name: 1, - address: 1, - score: ['$meta' => 'searchScore'], - ), - ); - - $this->assertSamePipeline(Pipelines::SearchNearGeoJSONPoint, $pipeline); - } - - public function testNearNumber(): void - { - $pipeline = new Pipeline( - Stage::search( - index: 'runtimes', - near: object( - path: 'runtime', - origin: 279, - pivot: 2, - ), - ), - Stage::limit(7), - Stage::project( - _id: 0, - title: 1, - runtime: 1, - score: ['$meta' => 'searchScore'], - ), - ); - - $this->assertSamePipeline(Pipelines::SearchNearNumber, $pipeline); - } - - public function testPhraseSlop(): void - { - $pipeline = new Pipeline( - Stage::search( - phrase: object( - path: 'title', - query: 'men women', - slop: 5, - ), - ), - Stage::project( - _id: 0, - title: 1, - score: ['$meta' => 'searchScore'], - ), - ); - - $this->assertSamePipeline(Pipelines::SearchPhraseSlop, $pipeline); - } - - public function testPhraseSynonyms(): void - { - $pipeline = new Pipeline( - Stage::search( - phrase: object( - path: 'plot', - query: 'automobile race', - slop: 5, - synonyms: 'my_synonyms', - ), - ), - Stage::limit(5), - Stage::project( - _id: 0, - plot: 1, - title: 1, - score: ['$meta' => 'searchScore'], - ), - ); - - $this->assertSamePipeline(Pipelines::SearchPhraseSynonyms, $pipeline); - } - - public function testQueryForMatchingEmbeddedDocumentsOnly(): void - { - $pipeline = new Pipeline( - Stage::search( - embeddedDocument: object( - path: 'items', - operator: object( - compound: object( - must: [ - object( - range: object( - path: 'items.quantity', - gt: 2, - ), - ), - object( - exists: object( - path: 'items.price', - ), - ), - object( - text: object( - path: 'items.tags', - query: 'school', - ), - ), - ], - ), - ), - ), - ), - Stage::limit(2), - Stage::project( - _id: 0, - storeLocation: 1, - items: Expression::filter( - input: Expression::arrayFieldPath('items'), - cond: Expression::and( - Expression::ifNull('$$this.price', 'false'), - Expression::gt(Expression::variable('this.quantity'), 2), - Expression::in('office', Expression::variable('this.tags')), - ), - ), - ), - ); - - $this->assertSamePipeline(Pipelines::SearchQueryForMatchingEmbeddedDocumentsOnly, $pipeline); - } - - public function testRangeDate(): void - { - $pipeline = new Pipeline( - Stage::search( - range: object( - path: 'released', - gt: new UTCDateTime(new DateTimeImmutable('2010-01-01T00:00:00.000Z')), - lt: new UTCDateTime(new DateTimeImmutable('2015-01-01T00:00:00.000Z')), - ), - ), - Stage::limit(5), - Stage::project( - _id: 0, - title: 1, - released: 1, - ), - ); - - $this->assertSamePipeline(Pipelines::SearchRangeDate, $pipeline); - } - - public function testRangeNumberGteLte(): void - { - $pipeline = new Pipeline( - Stage::search( - range: object( - path: 'runtime', - gte: 2, - lte: 3, - ), - ), - Stage::limit(5), - Stage::project( - _id: 0, - title: 1, - runtime: 1, - ), - ); - - $this->assertSamePipeline(Pipelines::SearchRangeNumberGteLte, $pipeline); - } - - public function testRangeNumberLte(): void - { - $pipeline = new Pipeline( - Stage::search( - range: object( - path: 'runtime', - lte: 2, - ), - ), - Stage::limit(5), - Stage::project( - _id: 0, - title: 1, - runtime: 1, - score: ['$meta' => 'searchScore'], - ), - ); - - $this->assertSamePipeline(Pipelines::SearchRangeNumberLte, $pipeline); - } - - public function testRangeObjectId(): void - { - $pipeline = new Pipeline( - Stage::search( - range: object( - path: '_id', - gte: new ObjectId('573a1396f29313caabce4a9a'), - lte: new ObjectId('573a1396f29313caabce4ae7'), - ), - ), - Stage::project( - _id: 1, - title: 1, - released: 1, - ), - ); - - $this->assertSamePipeline(Pipelines::SearchRangeObjectId, $pipeline); - } - - public function testRangeString(): void - { - $pipeline = new Pipeline( - Stage::search( - range: object( - path: 'title', - gt: 'city', - lt: 'country', - ), - ), - Stage::limit(5), - Stage::project( - _id: 0, - title: 1, - ), - ); - - $this->assertSamePipeline(Pipelines::SearchRangeString, $pipeline); - } - - public function testRegex(): void - { - $pipeline = new Pipeline( - Stage::search( - regex: object( - path: 'title', - query: '[0-9]{2} (.){4}s', - ), - ), - Stage::project( - _id: 0, - title: 1, - ), - ); - - $this->assertSamePipeline(Pipelines::SearchRegex, $pipeline); - } - - public function testSinglePhrase(): void - { - $pipeline = new Pipeline( - Stage::search( - phrase: object( - path: 'title', - query: 'new york', - ), - ), - Stage::limit(10), - Stage::project( - _id: 0, - title: 1, - score: ['$meta' => 'searchScore'], - ), - ); - - $this->assertSamePipeline(Pipelines::SearchSinglePhrase, $pipeline); - } - - public function testTextBasic(): void - { - $pipeline = new Pipeline( - Stage::search( - text: object( - path: 'title', - query: 'surfer', - ), - ), - Stage::project( - _id: 0, - title: 1, - score: ['$meta' => 'searchScore'], - ), - ); - - $this->assertSamePipeline(Pipelines::SearchTextBasic, $pipeline); - } - - public function testTextFuzzyDefault(): void - { - $pipeline = new Pipeline( - Stage::search( - text: object( - path: 'title', - query: 'naw yark', - fuzzy: object( - ), - ), - ), - Stage::limit(10), - Stage::project( - _id: 0, - title: 1, - score: ['$meta' => 'searchScore'], - ), - ); - - $this->assertSamePipeline(Pipelines::SearchTextFuzzyDefault, $pipeline); - } - - public function testTextFuzzyMaxExpansions(): void - { - $pipeline = new Pipeline( - Stage::search( - text: object( - path: 'title', - query: 'naw yark', - fuzzy: object( - maxEdits: 1, - maxExpansions: 100, - ), - ), - ), - Stage::limit(10), - Stage::project( - _id: 0, - title: 1, - score: ['$meta' => 'searchScore'], - ), - ); - - $this->assertSamePipeline(Pipelines::SearchTextFuzzyMaxExpansions, $pipeline); - } - - public function testTextFuzzyPrefixLength(): void - { - $pipeline = new Pipeline( - Stage::search( - text: object( - path: 'title', - query: 'naw yark', - fuzzy: object( - maxEdits: 1, - prefixLength: 2, - ), - ), - ), - Stage::limit(8), - Stage::project( - _id: 1, - title: 1, - score: ['$meta' => 'searchScore'], - ), - ); - - $this->assertSamePipeline(Pipelines::SearchTextFuzzyPrefixLength, $pipeline); - } - - public function testTextMatchAllUsingSynonyms(): void - { - $pipeline = new Pipeline( - Stage::search( - text: object( - path: 'plot', - query: 'automobile race', - matchCriteria: 'all', - synonyms: 'my_synonyms', - ), - ), - Stage::limit(20), - Stage::project( - _id: 0, - plot: 1, - title: 1, - score: ['$meta' => 'searchScore'], - ), - ); - - $this->assertSamePipeline(Pipelines::SearchTextMatchAllUsingSynonyms, $pipeline); - } - - public function testTextMatchAnyUsingEquivalentMapping(): void - { - $pipeline = new Pipeline( - Stage::search( - text: object( - path: 'plot', - query: 'attire', - synonyms: 'my_synonyms', - matchCriteria: 'any', - ), - ), - Stage::limit(5), - Stage::project( - _id: 0, - plot: 1, - title: 1, - score: ['$meta' => 'searchScore'], - ), - ); - - $this->assertSamePipeline(Pipelines::SearchTextMatchAnyUsingEquivalentMapping, $pipeline); - } - - public function testTextMatchAnyUsingExplicitMapping(): void - { - $pipeline = new Pipeline( - Stage::search( - text: object( - path: 'plot', - query: 'boat race', - synonyms: 'my_synonyms', - matchCriteria: 'any', - ), - ), - Stage::limit(10), - Stage::project( - _id: 0, - plot: 1, - title: 1, - score: ['$meta' => 'searchScore'], - ), - ); - - $this->assertSamePipeline(Pipelines::SearchTextMatchAnyUsingExplicitMapping, $pipeline); - } - - public function testTextWildcardPath(): void - { - $pipeline = new Pipeline( - Stage::search( - text: object( - path: object( - wildcard: '*', - ), - query: 'surfer', - ), - ), - Stage::project( - _id: 0, - title: 1, - score: ['$meta' => 'searchScore'], - ), - ); - - $this->assertSamePipeline(Pipelines::SearchTextWildcardPath, $pipeline); - } - - public function testWildcard(): void - { - $pipeline = new Pipeline( - Stage::search( - wildcard: object( - query: 'Wom?n *', - path: object( - wildcard: '*', - ), - ), - ), - Stage::limit(5), - Stage::project( - _id: 0, - title: 1, - ), - ); - - $this->assertSamePipeline(Pipelines::SearchWildcard, $pipeline); - } - - public function testWildcardEscapeCharacterExample(): void - { - $pipeline = new Pipeline( - Stage::search( - wildcard: object( - query: '*\\?', - path: 'title', - ), - ), - Stage::limit(5), - Stage::project( - _id: 0, - title: 1, - ), - ); - - $this->assertSamePipeline(Pipelines::SearchWildcardEscapeCharacterExample, $pipeline); - } }