diff --git a/.gitattributes b/.gitattributes index 75618ae42..7309afd20 100644 --- a/.gitattributes +++ b/.gitattributes @@ -22,5 +22,6 @@ rector.php export-ignore /src/Builder/Accumulator/*.php linguist-generated=true /src/Builder/Expression/*.php linguist-generated=true /src/Builder/Query/*.php linguist-generated=true +/src/Builder/Search/*.php linguist-generated=true /src/Builder/Stage/*.php linguist-generated=true /tests/Builder/*/Pipelines.php linguist-generated=true diff --git a/generator/README.md b/generator/README.md index 3e18976f9..02a3be4ec 100644 --- a/generator/README.md +++ b/generator/README.md @@ -42,6 +42,8 @@ Each operator can contain a `tests` section with a list if pipelines. To represe | Int64 | `!bson_int64 '123456789'` | | Decimal128 | `!bson_decimal128 '0.9'` | | UTCDateTime | `!bson_utcdatetime 0` | +| ObjectId | `!bson_ObjectId '5a9427648b0beebeb69589a1` | | Binary | `!bson_binary 'IA=='` | +| Binary UUID | `!bson_uuid 'fac32260-b511-4c69-8485-a2be5b7dda9e'` | To add new test cases to operators, you can get inspiration from the official MongoDB documentation and use the `generator/js2yaml.html` web page to manually convert a pipeline array from JS to Yaml. 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 8e2f5f320..c92e6ddb7 100644 --- a/generator/config/schema.json +++ b/generator/config/schema.json @@ -46,7 +46,8 @@ "resolvesToInt", "resolvesToTimestamp", "resolvesToLong", - "resolvesToDecimal" + "resolvesToDecimal", + "searchOperator" ] } }, @@ -60,7 +61,8 @@ "enum": [ "array", "object", - "single" + "single", + "search" ] }, "description": { @@ -135,7 +137,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..a984b9a39 --- /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 # any|sequential + - + 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..7a1d9f419 --- /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/compound/' +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..19c804625 --- /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/embedded-document/' +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..062e8ba59 --- /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/exists/' +type: + - searchOperator +encode: object +description: | + The exists operator tests if a path to a specified indexed field name exists in a document. +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..53dc8cba9 --- /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 # map 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..4da121e45 --- /dev/null +++ b/generator/config/search/geoShape.yaml @@ -0,0 +1,123 @@ +# $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..1739f1997 --- /dev/null +++ b/generator/config/search/geoWithin.yaml @@ -0,0 +1,103 @@ +# $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..8202771c9 --- /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 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..d17fb4803 --- /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 diff --git a/generator/config/stage/search.yaml b/generator/config/stage/search.yaml index 2531e75f6..34bf57a34 100644 --- a/generator/config/stage/search.yaml +++ b/generator/config/stage/search.yaml @@ -3,16 +3,93 @@ name: $search link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/search/' type: - stage -encode: single +encode: object 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: search + name: operator + mergeObject: true + type: + - searchOperator + description: | + Operator to search with. You can provide a specific operator or use + the compound operator to run a compound query with multiple operators. + - + name: index + optional: true + type: + - string + description: | + Name of the Atlas Search index to use. If omitted, defaults to "default". + - + name: highlight + optional: true + type: + # @todo support "highlight" type object + # https://www.mongodb.com/docs/atlas/atlas-search/highlighting/ + - object + description: | + Specifies the highlighting options for displaying search terms in their original context. + - + name: concurrent + optional: true + type: + - bool + description: | + Parallelize search across segments on dedicated search nodes. + If you don't have separate search nodes on your cluster, + Atlas Search ignores this flag. If omitted, defaults to false. + - + name: count + optional: true + type: + - string + description: | + Document that specifies the count options for retrieving a count of the results. + - + name: searchAfter + optional: true + type: + - string + description: | + Reference point for retrieving results. searchAfter returns documents starting immediately following the specified reference point. + - + name: searchBefore + optional: true + type: + - string + description: | + Reference point for retrieving results. searchBefore returns documents starting immediately before the specified reference point. + - + name: scoreDetails + optional: true + type: + - bool + description: | + Flag that specifies whether to retrieve a detailed breakdown of the score for the documents in the results. If omitted, defaults to false. + - + name: sort + optional: true + type: + - object + description: | + Document that specifies the fields to sort the Atlas Search results by in ascending or descending order. + - + name: returnStoredSource + optional: true + type: + - bool + description: | + Flag that specifies whether to perform a full document lookup on the backend database or return only stored source fields directly from Atlas Search. + - + name: tracking + optional: true type: - object - + description: | + Document that specifies the tracking option to retrieve analytics information on the search terms. tests: - name: 'Example' diff --git a/generator/config/stage/searchMeta.yaml b/generator/config/stage/searchMeta.yaml index 322d048eb..a7d92c272 100644 --- a/generator/config/stage/searchMeta.yaml +++ b/generator/config/stage/searchMeta.yaml @@ -3,16 +3,34 @@ name: $searchMeta link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/searchMeta/' type: - stage -encode: single +encode: object 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: meta + name: operator + mergeObject: true type: - - object + - searchOperator + description: | + Operator to search with. You can provide a specific operator or use + the compound operator to run a compound query with multiple operators. + - + name: index + optional: true + type: + - string + description: | + Name of the Atlas Search index to use. If omitted, defaults to default. + - + name: count + optional: true + type: + - object + description: | + Document that specifies the count options for retrieving a count of the results. tests: - name: 'Example' @@ -26,3 +44,89 @@ tests: lt: 1999 count: type: 'total' + + - + name: 'Year Facet' + link: 'https://www.mongodb.com/docs/atlas/atlas-search/facet/#example-1' + pipeline: + - $searchMeta: + facet: + operator: + range: + path: 'year' + gte: 1980 + lte: 2000 + facets: + yearFacet: + type: 'number' + path: 'year' + boundaries: + - 1980 + - 1990 + - 2000 + default: 'other' + + - + name: 'Date Facet' + link: 'https://www.mongodb.com/docs/atlas/atlas-search/facet/#example-2' + pipeline: + - + $searchMeta: + facet: + operator: + range: + path: 'released' + gte: !bson_utcdatetime '2000-01-01T00:00:00.000Z' + lte: !bson_utcdatetime '2015-01-31T00:00:00.000Z' + facets: + yearFacet: + type: 'date' + path: 'released' + boundaries: + - !bson_utcdatetime '2000-01-01' + - !bson_utcdatetime '2005-01-01' + - !bson_utcdatetime '2010-01-01' + - !bson_utcdatetime '2015-01-01' + default: 'other' + + - + name: 'Metadata Results' + link: 'https://www.mongodb.com/docs/atlas/atlas-search/facet/#examples' + pipeline: + - + $searchMeta: + facet: + operator: + range: + path: 'released' + gte: !bson_utcdatetime '2000-01-01T00:00:00.000Z' + lte: !bson_utcdatetime '2015-01-31T00:00:00.000Z' + facets: + directorsFacet: + type: 'string' + path: 'directors' + numBuckets: 7 + yearFacet: + type: 'number' + path: 'year' + boundaries: + - 2000 + - 2005 + - 2010 + - 2015 + + - + name: 'Autocomplete Bucket Results through Facet Queries' + link: 'https://www.mongodb.com/docs/atlas/atlas-search/autocomplete/#bucket-results-through-facet-queries' + pipeline: + - + $searchMeta: + facet: + operator: + autocomplete: + path: 'title' + query: 'Gravity' + facets: + titleFacet: + type: 'string' + path: 'title' diff --git a/generator/config/stage/vectorSearch.yaml b/generator/config/stage/vectorSearch.yaml new file mode 100644 index 000000000..ec4b4ff4b --- /dev/null +++ b/generator/config/stage/vectorSearch.yaml @@ -0,0 +1,119 @@ +# $schema: ../schema.json +name: $vectorSearch +link: 'https://www.mongodb.com/docs/atlas/atlas-vector-search/vector-search-stage/' +type: + - stage +encode: object +description: | + The $vectorSearch stage performs an ANN or ENN search on a vector in the specified field. +arguments: + - + name: index + type: + - string + description: | + Name of the Atlas Vector Search index to use. + - + name: limit + type: + - int + description: | + Number of documents to return in the results. This value can't exceed the value of numCandidates if you specify numCandidates. + - + name: path + type: + - searchPath + description: | + Indexed vector type field to search. + - + name: queryVector + type: + - array # of numbers + description: | + Array of numbers that represent the query vector. The number type must match the indexed field value type. + - + name: exact + optional: true + type: + - bool + description: | + This is required if numCandidates is omitted. false to run ANN search. true to run ENN search. + - + name: filter + optional: true + type: + - query + description: | + Any match query that compares an indexed field with a boolean, date, objectId, number (not decimals), string, or UUID to use as a pre-filter. + - + name: numCandidates + optional: true + type: + - int + description: | + This field is required if exact is false or omitted. + Number of nearest neighbors to use during the search. Value must be less than or equal to (<=) 10000. You can't specify a number less than the number of documents to return (limit). + +tests: + - + name: 'ANN Basic' + link: 'https://www.mongodb.com/docs/atlas/atlas-vector-search/vector-search-stage/#ann-examples' + pipeline: + - + $vectorSearch: + index: 'vector_index' + path: 'plot_embedding' + queryVector: [-0.0016261312, -0.028070757, -0.011342932] # skip other numbers, not relevant to the test + numCandidates: 150 + limit: 10 + - + $project: + _id: 0 + plot: 1 + title: 1 + score: + $meta: 'vectorSearchScore' + + - + name: 'ANN Filter' + link: 'https://www.mongodb.com/docs/atlas/atlas-vector-search/vector-search-stage/#ann-examples' + pipeline: + - + $vectorSearch: + index: 'vector_index' + path: 'plot_embedding' + filter: + $and: + - + year: + $lt: 1975 + queryVector: [0.02421053, -0.022372592, -0.006231137] # skip other numbers, not relevant to the test + numCandidates: 150 + limit: 10 + - + $project: + _id: 0 + title: 1 + plot: 1 + year: 1 + score: + $meta: 'vectorSearchScore' + + - + name: 'ENN' + link: 'https://www.mongodb.com/docs/atlas/atlas-vector-search/vector-search-stage/#enn-examples' + pipeline: + - + $vectorSearch: + index: 'vector_index' + path: 'plot_embedding' + queryVector: [-0.006954097, -0.009932499, -0.001311474] # skip other numbers, not relevant to the test + exact: true + limit: 10 + - + $project: + _id: 0 + plot: 1 + title: 1 + score: + $meta: 'vectorSearchScore' diff --git a/generator/js2yaml.html b/generator/js2yaml.html index a0f8654af..31241adb1 100644 --- a/generator/js2yaml.html +++ b/generator/js2yaml.html @@ -28,6 +28,9 @@
+
+
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',
+ 'query' => 'query',
+ 'tokenOrder' => 'tokenOrder',
+ 'fuzzy' => 'fuzzy',
+ 'score' => 'score',
+ ];
+
+ /** @var array|string $path */
+ public readonly array|string $path;
+
+ /** @var string $query */
+ public readonly string $query;
+
+ /** @var Optional|string $tokenOrder */
+ public readonly Optional|string $tokenOrder;
+
+ /** @var Optional|Document|Serializable|array|stdClass $fuzzy */
+ public readonly Optional|Document|Serializable|stdClass|array $fuzzy;
+
+ /** @var Optional|Document|Serializable|array|stdClass $score */
+ public readonly Optional|Document|Serializable|stdClass|array $score;
+
+ /**
+ * @param array|string $path
+ * @param string $query
+ * @param Optional|string $tokenOrder
+ * @param Optional|Document|Serializable|array|stdClass $fuzzy
+ * @param Optional|Document|Serializable|array|stdClass $score
+ */
+ public function __construct(
+ array|string $path,
+ string $query,
+ Optional|string $tokenOrder = Optional::Undefined,
+ Optional|Document|Serializable|stdClass|array $fuzzy = Optional::Undefined,
+ Optional|Document|Serializable|stdClass|array $score = Optional::Undefined,
+ ) {
+ $this->path = $path;
+ $this->query = $query;
+ $this->tokenOrder = $tokenOrder;
+ $this->fuzzy = $fuzzy;
+ $this->score = $score;
+ }
+}
diff --git a/src/Builder/Search/CompoundOperator.php b/src/Builder/Search/CompoundOperator.php
new file mode 100644
index 000000000..27b8c2aeb
--- /dev/null
+++ b/src/Builder/Search/CompoundOperator.php
@@ -0,0 +1,84 @@
+ 'must',
+ 'mustNot' => 'mustNot',
+ 'should' => 'should',
+ 'filter' => 'filter',
+ 'minimumShouldMatch' => 'minimumShouldMatch',
+ 'score' => 'score',
+ ];
+
+ /** @var Optional|BSONArray|Document|PackedArray|SearchOperatorInterface|Serializable|array|stdClass $must */
+ public readonly Optional|Document|PackedArray|Serializable|SearchOperatorInterface|BSONArray|stdClass|array $must;
+
+ /** @var Optional|BSONArray|Document|PackedArray|SearchOperatorInterface|Serializable|array|stdClass $mustNot */
+ public readonly Optional|Document|PackedArray|Serializable|SearchOperatorInterface|BSONArray|stdClass|array $mustNot;
+
+ /** @var Optional|BSONArray|Document|PackedArray|SearchOperatorInterface|Serializable|array|stdClass $should */
+ public readonly Optional|Document|PackedArray|Serializable|SearchOperatorInterface|BSONArray|stdClass|array $should;
+
+ /** @var Optional|BSONArray|Document|PackedArray|SearchOperatorInterface|Serializable|array|stdClass $filter */
+ public readonly Optional|Document|PackedArray|Serializable|SearchOperatorInterface|BSONArray|stdClass|array $filter;
+
+ /** @var Optional|int $minimumShouldMatch */
+ public readonly Optional|int $minimumShouldMatch;
+
+ /** @var Optional|Document|Serializable|array|stdClass $score */
+ public readonly Optional|Document|Serializable|stdClass|array $score;
+
+ /**
+ * @param Optional|BSONArray|Document|PackedArray|SearchOperatorInterface|Serializable|array|stdClass $must
+ * @param Optional|BSONArray|Document|PackedArray|SearchOperatorInterface|Serializable|array|stdClass $mustNot
+ * @param Optional|BSONArray|Document|PackedArray|SearchOperatorInterface|Serializable|array|stdClass $should
+ * @param Optional|BSONArray|Document|PackedArray|SearchOperatorInterface|Serializable|array|stdClass $filter
+ * @param Optional|int $minimumShouldMatch
+ * @param Optional|Document|Serializable|array|stdClass $score
+ */
+ public function __construct(
+ Optional|Document|PackedArray|Serializable|SearchOperatorInterface|BSONArray|stdClass|array $must = Optional::Undefined,
+ Optional|Document|PackedArray|Serializable|SearchOperatorInterface|BSONArray|stdClass|array $mustNot = Optional::Undefined,
+ Optional|Document|PackedArray|Serializable|SearchOperatorInterface|BSONArray|stdClass|array $should = Optional::Undefined,
+ Optional|Document|PackedArray|Serializable|SearchOperatorInterface|BSONArray|stdClass|array $filter = Optional::Undefined,
+ Optional|int $minimumShouldMatch = Optional::Undefined,
+ Optional|Document|Serializable|stdClass|array $score = Optional::Undefined,
+ ) {
+ $this->must = $must;
+ $this->mustNot = $mustNot;
+ $this->should = $should;
+ $this->filter = $filter;
+ $this->minimumShouldMatch = $minimumShouldMatch;
+ $this->score = $score;
+ }
+}
diff --git a/src/Builder/Search/EmbeddedDocumentOperator.php b/src/Builder/Search/EmbeddedDocumentOperator.php
new file mode 100644
index 000000000..91f6c1f96
--- /dev/null
+++ b/src/Builder/Search/EmbeddedDocumentOperator.php
@@ -0,0 +1,57 @@
+ 'path', 'operator' => 'operator', 'score' => 'score'];
+
+ /** @var array|string $path */
+ public readonly array|string $path;
+
+ /** @var Document|SearchOperatorInterface|Serializable|array|stdClass $operator */
+ public readonly Document|Serializable|SearchOperatorInterface|stdClass|array $operator;
+
+ /** @var Optional|Document|Serializable|array|stdClass $score */
+ public readonly Optional|Document|Serializable|stdClass|array $score;
+
+ /**
+ * @param array|string $path
+ * @param Document|SearchOperatorInterface|Serializable|array|stdClass $operator
+ * @param Optional|Document|Serializable|array|stdClass $score
+ */
+ public function __construct(
+ array|string $path,
+ Document|Serializable|SearchOperatorInterface|stdClass|array $operator,
+ Optional|Document|Serializable|stdClass|array $score = Optional::Undefined,
+ ) {
+ $this->path = $path;
+ $this->operator = $operator;
+ $this->score = $score;
+ }
+}
diff --git a/src/Builder/Search/EqualsOperator.php b/src/Builder/Search/EqualsOperator.php
new file mode 100644
index 000000000..bbfe75b8e
--- /dev/null
+++ b/src/Builder/Search/EqualsOperator.php
@@ -0,0 +1,59 @@
+ 'path', 'value' => 'value', 'score' => 'score'];
+
+ /** @var array|string $path */
+ public readonly array|string $path;
+
+ /** @var Binary|Decimal128|Int64|ObjectId|UTCDateTime|bool|float|int|null|string $value */
+ public readonly Binary|Decimal128|Int64|ObjectId|UTCDateTime|bool|float|int|null|string $value;
+
+ /** @var Optional|Document|Serializable|array|stdClass $score */
+ public readonly Optional|Document|Serializable|stdClass|array $score;
+
+ /**
+ * @param array|string $path
+ * @param Binary|Decimal128|Int64|ObjectId|UTCDateTime|bool|float|int|null|string $value
+ * @param Optional|Document|Serializable|array|stdClass $score
+ */
+ public function __construct(
+ array|string $path,
+ Binary|Decimal128|Int64|ObjectId|UTCDateTime|bool|float|int|null|string $value,
+ Optional|Document|Serializable|stdClass|array $score = Optional::Undefined,
+ ) {
+ $this->path = $path;
+ $this->value = $value;
+ $this->score = $score;
+ }
+}
diff --git a/src/Builder/Search/ExistsOperator.php b/src/Builder/Search/ExistsOperator.php
new file mode 100644
index 000000000..4330f130f
--- /dev/null
+++ b/src/Builder/Search/ExistsOperator.php
@@ -0,0 +1,48 @@
+ 'path', 'score' => 'score'];
+
+ /** @var array|string $path */
+ public readonly array|string $path;
+
+ /** @var Optional|Document|Serializable|array|stdClass $score */
+ public readonly Optional|Document|Serializable|stdClass|array $score;
+
+ /**
+ * @param array|string $path
+ * @param Optional|Document|Serializable|array|stdClass $score
+ */
+ public function __construct(
+ array|string $path,
+ Optional|Document|Serializable|stdClass|array $score = Optional::Undefined,
+ ) {
+ $this->path = $path;
+ $this->score = $score;
+ }
+}
diff --git a/src/Builder/Search/FacetOperator.php b/src/Builder/Search/FacetOperator.php
new file mode 100644
index 000000000..15fc10830
--- /dev/null
+++ b/src/Builder/Search/FacetOperator.php
@@ -0,0 +1,49 @@
+ 'facets', 'operator' => 'operator'];
+
+ /** @var Document|Serializable|array|stdClass $facets */
+ public readonly Document|Serializable|stdClass|array $facets;
+
+ /** @var Optional|Document|SearchOperatorInterface|Serializable|array|stdClass $operator */
+ public readonly Optional|Document|Serializable|SearchOperatorInterface|stdClass|array $operator;
+
+ /**
+ * @param Document|Serializable|array|stdClass $facets
+ * @param Optional|Document|SearchOperatorInterface|Serializable|array|stdClass $operator
+ */
+ public function __construct(
+ Document|Serializable|stdClass|array $facets,
+ Optional|Document|Serializable|SearchOperatorInterface|stdClass|array $operator = Optional::Undefined,
+ ) {
+ $this->facets = $facets;
+ $this->operator = $operator;
+ }
+}
diff --git a/src/Builder/Search/FactoryTrait.php b/src/Builder/Search/FactoryTrait.php
new file mode 100644
index 000000000..3a7762521
--- /dev/null
+++ b/src/Builder/Search/FactoryTrait.php
@@ -0,0 +1,345 @@
+ 'path', 'relation' => 'relation', 'geometry' => 'geometry', 'score' => 'score'];
+
+ /** @var array|string $path */
+ public readonly array|string $path;
+
+ /** @var string $relation */
+ public readonly string $relation;
+
+ /** @var Document|GeometryInterface|Serializable|array|stdClass $geometry */
+ public readonly Document|Serializable|GeometryInterface|stdClass|array $geometry;
+
+ /** @var Optional|Document|Serializable|array|stdClass $score */
+ public readonly Optional|Document|Serializable|stdClass|array $score;
+
+ /**
+ * @param array|string $path
+ * @param string $relation
+ * @param Document|GeometryInterface|Serializable|array|stdClass $geometry
+ * @param Optional|Document|Serializable|array|stdClass $score
+ */
+ public function __construct(
+ array|string $path,
+ string $relation,
+ Document|Serializable|GeometryInterface|stdClass|array $geometry,
+ Optional|Document|Serializable|stdClass|array $score = Optional::Undefined,
+ ) {
+ $this->path = $path;
+ $this->relation = $relation;
+ $this->geometry = $geometry;
+ $this->score = $score;
+ }
+}
diff --git a/src/Builder/Search/GeoWithinOperator.php b/src/Builder/Search/GeoWithinOperator.php
new file mode 100644
index 000000000..dcf605697
--- /dev/null
+++ b/src/Builder/Search/GeoWithinOperator.php
@@ -0,0 +1,76 @@
+ 'path',
+ 'box' => 'box',
+ 'circle' => 'circle',
+ 'geometry' => 'geometry',
+ 'score' => 'score',
+ ];
+
+ /** @var array|string $path */
+ public readonly array|string $path;
+
+ /** @var Optional|Document|Serializable|array|stdClass $box */
+ public readonly Optional|Document|Serializable|stdClass|array $box;
+
+ /** @var Optional|Document|Serializable|array|stdClass $circle */
+ public readonly Optional|Document|Serializable|stdClass|array $circle;
+
+ /** @var Optional|Document|GeometryInterface|Serializable|array|stdClass $geometry */
+ public readonly Optional|Document|Serializable|GeometryInterface|stdClass|array $geometry;
+
+ /** @var Optional|Document|Serializable|array|stdClass $score */
+ public readonly Optional|Document|Serializable|stdClass|array $score;
+
+ /**
+ * @param array|string $path
+ * @param Optional|Document|Serializable|array|stdClass $box
+ * @param Optional|Document|Serializable|array|stdClass $circle
+ * @param Optional|Document|GeometryInterface|Serializable|array|stdClass $geometry
+ * @param Optional|Document|Serializable|array|stdClass $score
+ */
+ public function __construct(
+ array|string $path,
+ Optional|Document|Serializable|stdClass|array $box = Optional::Undefined,
+ Optional|Document|Serializable|stdClass|array $circle = Optional::Undefined,
+ Optional|Document|Serializable|GeometryInterface|stdClass|array $geometry = Optional::Undefined,
+ Optional|Document|Serializable|stdClass|array $score = Optional::Undefined,
+ ) {
+ $this->path = $path;
+ $this->box = $box;
+ $this->circle = $circle;
+ $this->geometry = $geometry;
+ $this->score = $score;
+ }
+}
diff --git a/src/Builder/Search/InOperator.php b/src/Builder/Search/InOperator.php
new file mode 100644
index 000000000..f050a1679
--- /dev/null
+++ b/src/Builder/Search/InOperator.php
@@ -0,0 +1,65 @@
+ 'path', 'value' => 'value', 'score' => 'score'];
+
+ /** @var array|string $path */
+ public readonly array|string $path;
+
+ /** @var BSONArray|PackedArray|Type|array|bool|float|int|null|stdClass|string $value */
+ public readonly PackedArray|Type|BSONArray|stdClass|array|bool|float|int|null|string $value;
+
+ /** @var Optional|Document|Serializable|array|stdClass $score */
+ public readonly Optional|Document|Serializable|stdClass|array $score;
+
+ /**
+ * @param array|string $path
+ * @param BSONArray|PackedArray|Type|array|bool|float|int|null|stdClass|string $value
+ * @param Optional|Document|Serializable|array|stdClass $score
+ */
+ public function __construct(
+ array|string $path,
+ PackedArray|Type|BSONArray|stdClass|array|bool|float|int|null|string $value,
+ Optional|Document|Serializable|stdClass|array $score = Optional::Undefined,
+ ) {
+ $this->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;
+ }
+}
diff --git a/src/Builder/Search/MoreLikeThisOperator.php b/src/Builder/Search/MoreLikeThisOperator.php
new file mode 100644
index 000000000..689508d53
--- /dev/null
+++ b/src/Builder/Search/MoreLikeThisOperator.php
@@ -0,0 +1,52 @@
+ 'like', 'score' => 'score'];
+
+ /** @var BSONArray|Document|PackedArray|Serializable|array|stdClass $like */
+ public readonly Document|PackedArray|Serializable|BSONArray|stdClass|array $like;
+
+ /** @var Optional|Document|Serializable|array|stdClass $score */
+ public readonly Optional|Document|Serializable|stdClass|array $score;
+
+ /**
+ * @param BSONArray|Document|PackedArray|Serializable|array|stdClass $like
+ * @param Optional|Document|Serializable|array|stdClass $score
+ */
+ public function __construct(
+ Document|PackedArray|Serializable|BSONArray|stdClass|array $like,
+ Optional|Document|Serializable|stdClass|array $score = Optional::Undefined,
+ ) {
+ $this->like = $like;
+ $this->score = $score;
+ }
+}
diff --git a/src/Builder/Search/NearOperator.php b/src/Builder/Search/NearOperator.php
new file mode 100644
index 000000000..ab1b9b0c1
--- /dev/null
+++ b/src/Builder/Search/NearOperator.php
@@ -0,0 +1,64 @@
+ 'path', 'origin' => 'origin', 'pivot' => 'pivot', 'score' => 'score'];
+
+ /** @var array|string $path */
+ public readonly array|string $path;
+
+ /** @var Decimal128|Document|GeometryInterface|Int64|Serializable|UTCDateTime|array|float|int|stdClass $origin */
+ public readonly Decimal128|Document|Int64|Serializable|UTCDateTime|GeometryInterface|stdClass|array|float|int $origin;
+
+ /** @var Decimal128|Int64|float|int $pivot */
+ public readonly Decimal128|Int64|float|int $pivot;
+
+ /** @var Optional|Document|Serializable|array|stdClass $score */
+ public readonly Optional|Document|Serializable|stdClass|array $score;
+
+ /**
+ * @param array|string $path
+ * @param Decimal128|Document|GeometryInterface|Int64|Serializable|UTCDateTime|array|float|int|stdClass $origin
+ * @param Decimal128|Int64|float|int $pivot
+ * @param Optional|Document|Serializable|array|stdClass $score
+ */
+ public function __construct(
+ array|string $path,
+ Decimal128|Document|Int64|Serializable|UTCDateTime|GeometryInterface|stdClass|array|float|int $origin,
+ Decimal128|Int64|float|int $pivot,
+ Optional|Document|Serializable|stdClass|array $score = Optional::Undefined,
+ ) {
+ $this->path = $path;
+ $this->origin = $origin;
+ $this->pivot = $pivot;
+ $this->score = $score;
+ }
+}
diff --git a/src/Builder/Search/PhraseOperator.php b/src/Builder/Search/PhraseOperator.php
new file mode 100644
index 000000000..2bbb707a9
--- /dev/null
+++ b/src/Builder/Search/PhraseOperator.php
@@ -0,0 +1,83 @@
+ 'path',
+ 'query' => 'query',
+ 'slop' => 'slop',
+ 'synonyms' => 'synonyms',
+ 'score' => 'score',
+ ];
+
+ /** @var array|string $path */
+ public readonly array|string $path;
+
+ /** @var BSONArray|PackedArray|array|string $query */
+ public readonly PackedArray|BSONArray|array|string $query;
+
+ /** @var Optional|int $slop */
+ public readonly Optional|int $slop;
+
+ /** @var Optional|string $synonyms */
+ public readonly Optional|string $synonyms;
+
+ /** @var Optional|Document|Serializable|array|stdClass $score */
+ public readonly Optional|Document|Serializable|stdClass|array $score;
+
+ /**
+ * @param array|string $path
+ * @param BSONArray|PackedArray|array|string $query
+ * @param Optional|int $slop
+ * @param Optional|string $synonyms
+ * @param Optional|Document|Serializable|array|stdClass $score
+ */
+ public function __construct(
+ array|string $path,
+ PackedArray|BSONArray|array|string $query,
+ Optional|int $slop = Optional::Undefined,
+ Optional|string $synonyms = Optional::Undefined,
+ Optional|Document|Serializable|stdClass|array $score = Optional::Undefined,
+ ) {
+ $this->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;
+ }
+}
diff --git a/src/Builder/Search/QueryStringOperator.php b/src/Builder/Search/QueryStringOperator.php
new file mode 100644
index 000000000..edc4d3471
--- /dev/null
+++ b/src/Builder/Search/QueryStringOperator.php
@@ -0,0 +1,40 @@
+ 'defaultPath', 'query' => 'query'];
+
+ /** @var array|string $defaultPath */
+ public readonly array|string $defaultPath;
+
+ /** @var string $query */
+ public readonly string $query;
+
+ /**
+ * @param array|string $defaultPath
+ * @param string $query
+ */
+ public function __construct(array|string $defaultPath, string $query)
+ {
+ $this->defaultPath = $defaultPath;
+ $this->query = $query;
+ }
+}
diff --git a/src/Builder/Search/RangeOperator.php b/src/Builder/Search/RangeOperator.php
new file mode 100644
index 000000000..a728d7477
--- /dev/null
+++ b/src/Builder/Search/RangeOperator.php
@@ -0,0 +1,85 @@
+ 'path',
+ 'gt' => 'gt',
+ 'gte' => 'gte',
+ 'lt' => 'lt',
+ 'lte' => 'lte',
+ 'score' => 'score',
+ ];
+
+ /** @var array|string $path */
+ public readonly array|string $path;
+
+ /** @var Optional|Decimal128|Int64|ObjectId|UTCDateTime|float|int|string $gt */
+ public readonly Optional|Decimal128|Int64|ObjectId|UTCDateTime|float|int|string $gt;
+
+ /** @var Optional|Decimal128|Int64|ObjectId|UTCDateTime|float|int|string $gte */
+ public readonly Optional|Decimal128|Int64|ObjectId|UTCDateTime|float|int|string $gte;
+
+ /** @var Optional|Decimal128|Int64|ObjectId|UTCDateTime|float|int|string $lt */
+ public readonly Optional|Decimal128|Int64|ObjectId|UTCDateTime|float|int|string $lt;
+
+ /** @var Optional|Decimal128|Int64|ObjectId|UTCDateTime|float|int|string $lte */
+ public readonly Optional|Decimal128|Int64|ObjectId|UTCDateTime|float|int|string $lte;
+
+ /** @var Optional|Document|Serializable|array|stdClass $score */
+ public readonly Optional|Document|Serializable|stdClass|array $score;
+
+ /**
+ * @param array|string $path
+ * @param Optional|Decimal128|Int64|ObjectId|UTCDateTime|float|int|string $gt
+ * @param Optional|Decimal128|Int64|ObjectId|UTCDateTime|float|int|string $gte
+ * @param Optional|Decimal128|Int64|ObjectId|UTCDateTime|float|int|string $lt
+ * @param Optional|Decimal128|Int64|ObjectId|UTCDateTime|float|int|string $lte
+ * @param Optional|Document|Serializable|array|stdClass $score
+ */
+ public function __construct(
+ array|string $path,
+ Optional|Decimal128|Int64|ObjectId|UTCDateTime|float|int|string $gt = Optional::Undefined,
+ Optional|Decimal128|Int64|ObjectId|UTCDateTime|float|int|string $gte = Optional::Undefined,
+ Optional|Decimal128|Int64|ObjectId|UTCDateTime|float|int|string $lt = Optional::Undefined,
+ Optional|Decimal128|Int64|ObjectId|UTCDateTime|float|int|string $lte = Optional::Undefined,
+ Optional|Document|Serializable|stdClass|array $score = Optional::Undefined,
+ ) {
+ $this->path = $path;
+ $this->gt = $gt;
+ $this->gte = $gte;
+ $this->lt = $lt;
+ $this->lte = $lte;
+ $this->score = $score;
+ }
+}
diff --git a/src/Builder/Search/RegexOperator.php b/src/Builder/Search/RegexOperator.php
new file mode 100644
index 000000000..6b2c2d63a
--- /dev/null
+++ b/src/Builder/Search/RegexOperator.php
@@ -0,0 +1,67 @@
+ 'path',
+ 'query' => 'query',
+ 'allowAnalyzedField' => 'allowAnalyzedField',
+ 'score' => 'score',
+ ];
+
+ /** @var array|string $path */
+ public readonly array|string $path;
+
+ /** @var string $query */
+ public readonly string $query;
+
+ /** @var Optional|bool $allowAnalyzedField */
+ public readonly Optional|bool $allowAnalyzedField;
+
+ /** @var Optional|Document|Serializable|array|stdClass $score */
+ public readonly Optional|Document|Serializable|stdClass|array $score;
+
+ /**
+ * @param array|string $path
+ * @param string $query
+ * @param Optional|bool $allowAnalyzedField
+ * @param Optional|Document|Serializable|array|stdClass $score
+ */
+ public function __construct(
+ array|string $path,
+ string $query,
+ Optional|bool $allowAnalyzedField = Optional::Undefined,
+ Optional|Document|Serializable|stdClass|array $score = Optional::Undefined,
+ ) {
+ $this->path = $path;
+ $this->query = $query;
+ $this->allowAnalyzedField = $allowAnalyzedField;
+ $this->score = $score;
+ }
+}
diff --git a/src/Builder/Search/TextOperator.php b/src/Builder/Search/TextOperator.php
new file mode 100644
index 000000000..4ade6b434
--- /dev/null
+++ b/src/Builder/Search/TextOperator.php
@@ -0,0 +1,81 @@
+ 'path',
+ 'query' => 'query',
+ 'fuzzy' => 'fuzzy',
+ 'matchCriteria' => 'matchCriteria',
+ 'synonyms' => 'synonyms',
+ 'score' => 'score',
+ ];
+
+ /** @var array|string $path */
+ public readonly array|string $path;
+
+ /** @var string $query */
+ public readonly string $query;
+
+ /** @var Optional|Document|Serializable|array|stdClass $fuzzy */
+ public readonly Optional|Document|Serializable|stdClass|array $fuzzy;
+
+ /** @var Optional|string $matchCriteria */
+ public readonly Optional|string $matchCriteria;
+
+ /** @var Optional|string $synonyms */
+ public readonly Optional|string $synonyms;
+
+ /** @var Optional|Document|Serializable|array|stdClass $score */
+ public readonly Optional|Document|Serializable|stdClass|array $score;
+
+ /**
+ * @param array|string $path
+ * @param string $query
+ * @param Optional|Document|Serializable|array|stdClass $fuzzy
+ * @param Optional|string $matchCriteria
+ * @param Optional|string $synonyms
+ * @param Optional|Document|Serializable|array|stdClass $score
+ */
+ public function __construct(
+ array|string $path,
+ string $query,
+ Optional|Document|Serializable|stdClass|array $fuzzy = Optional::Undefined,
+ Optional|string $matchCriteria = Optional::Undefined,
+ Optional|string $synonyms = Optional::Undefined,
+ Optional|Document|Serializable|stdClass|array $score = Optional::Undefined,
+ ) {
+ $this->path = $path;
+ $this->query = $query;
+ $this->fuzzy = $fuzzy;
+ $this->matchCriteria = $matchCriteria;
+ $this->synonyms = $synonyms;
+ $this->score = $score;
+ }
+}
diff --git a/src/Builder/Search/WildcardOperator.php b/src/Builder/Search/WildcardOperator.php
new file mode 100644
index 000000000..ee481910e
--- /dev/null
+++ b/src/Builder/Search/WildcardOperator.php
@@ -0,0 +1,66 @@
+ 'path',
+ 'query' => 'query',
+ 'allowAnalyzedField' => 'allowAnalyzedField',
+ 'score' => 'score',
+ ];
+
+ /** @var array|string $path */
+ public readonly array|string $path;
+
+ /** @var string $query */
+ public readonly string $query;
+
+ /** @var Optional|bool $allowAnalyzedField */
+ public readonly Optional|bool $allowAnalyzedField;
+
+ /** @var Optional|Document|Serializable|array|stdClass $score */
+ public readonly Optional|Document|Serializable|stdClass|array $score;
+
+ /**
+ * @param array|string $path
+ * @param string $query
+ * @param Optional|bool $allowAnalyzedField
+ * @param Optional|Document|Serializable|array|stdClass $score
+ */
+ public function __construct(
+ array|string $path,
+ string $query,
+ Optional|bool $allowAnalyzedField = Optional::Undefined,
+ Optional|Document|Serializable|stdClass|array $score = Optional::Undefined,
+ ) {
+ $this->path = $path;
+ $this->query = $query;
+ $this->allowAnalyzedField = $allowAnalyzedField;
+ $this->score = $score;
+ }
+}
diff --git a/src/Builder/Stage/FactoryTrait.php b/src/Builder/Stage/FactoryTrait.php
index 199cf64ef..95b0e6e37 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,11 +538,35 @@ 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|Serializable|array|stdClass $search
- */
- public static function search(Document|Serializable|stdClass|array $search): SearchStage
- {
- return new SearchStage($search);
+ * @param Document|SearchOperatorInterface|Serializable|array|stdClass $operator Operator to search with. You can provide a specific operator or use
+ * the compound operator to run a compound query with multiple operators.
+ * @param Optional|string $index Name of the Atlas Search index to use. If omitted, defaults to "default".
+ * @param Optional|Document|Serializable|array|stdClass $highlight Specifies the highlighting options for displaying search terms in their original context.
+ * @param Optional|bool $concurrent Parallelize search across segments on dedicated search nodes.
+ * If you don't have separate search nodes on your cluster,
+ * Atlas Search ignores this flag. If omitted, defaults to false.
+ * @param Optional|string $count Document that specifies the count options for retrieving a count of the results.
+ * @param Optional|string $searchAfter Reference point for retrieving results. searchAfter returns documents starting immediately following the specified reference point.
+ * @param Optional|string $searchBefore Reference point for retrieving results. searchBefore returns documents starting immediately before the specified reference point.
+ * @param Optional|bool $scoreDetails Flag that specifies whether to retrieve a detailed breakdown of the score for the documents in the results. If omitted, defaults to false.
+ * @param Optional|Document|Serializable|array|stdClass $sort Document that specifies the fields to sort the Atlas Search results by in ascending or descending order.
+ * @param Optional|bool $returnStoredSource Flag that specifies whether to perform a full document lookup on the backend database or return only stored source fields directly from Atlas Search.
+ * @param Optional|Document|Serializable|array|stdClass $tracking Document that specifies the tracking option to retrieve analytics information on the search terms.
+ */
+ 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,
+ Optional|string $count = Optional::Undefined,
+ Optional|string $searchAfter = Optional::Undefined,
+ Optional|string $searchBefore = Optional::Undefined,
+ Optional|bool $scoreDetails = Optional::Undefined,
+ Optional|Document|Serializable|stdClass|array $sort = Optional::Undefined,
+ Optional|bool $returnStoredSource = Optional::Undefined,
+ Optional|Document|Serializable|stdClass|array $tracking = Optional::Undefined,
+ ): SearchStage {
+ return new SearchStage($operator, $index, $highlight, $concurrent, $count, $searchAfter, $searchBefore, $scoreDetails, $sort, $returnStoredSource, $tracking);
}
/**
@@ -549,11 +574,17 @@ public static function search(Document|Serializable|stdClass|array $search): Sea
* 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|Serializable|array|stdClass $meta
- */
- public static function searchMeta(Document|Serializable|stdClass|array $meta): SearchMetaStage
- {
- return new SearchMetaStage($meta);
+ * @param Document|SearchOperatorInterface|Serializable|array|stdClass $operator Operator to search with. You can provide a specific operator or use
+ * the compound operator to run a compound query with multiple operators.
+ * @param Optional|string $index Name of the Atlas Search index to use. If omitted, defaults to default.
+ * @param Optional|Document|Serializable|array|stdClass $count Document that specifies the count options for retrieving a count of the results.
+ */
+ public static function searchMeta(
+ Document|Serializable|SearchOperatorInterface|stdClass|array $operator,
+ Optional|string $index = Optional::Undefined,
+ Optional|Document|Serializable|stdClass|array $count = Optional::Undefined,
+ ): SearchMetaStage {
+ return new SearchMetaStage($operator, $index, $count);
}
/**
@@ -678,4 +709,29 @@ public static function unwind(
): UnwindStage {
return new UnwindStage($path, $includeArrayIndex, $preserveNullAndEmptyArrays);
}
+
+ /**
+ * The $vectorSearch stage performs an ANN or ENN search on a vector in the specified field.
+ *
+ * @see https://www.mongodb.com/docs/atlas/atlas-vector-search/vector-search-stage/
+ * @param string $index Name of the Atlas Vector Search index to use.
+ * @param int $limit Number of documents to return in the results. This value can't exceed the value of numCandidates if you specify numCandidates.
+ * @param array|string $path Indexed vector type field to search.
+ * @param BSONArray|PackedArray|array $queryVector Array of numbers that represent the query vector. The number type must match the indexed field value type.
+ * @param Optional|bool $exact This is required if numCandidates is omitted. false to run ANN search. true to run ENN search.
+ * @param Optional|QueryInterface|array $filter Any match query that compares an indexed field with a boolean, date, objectId, number (not decimals), string, or UUID to use as a pre-filter.
+ * @param Optional|int $numCandidates This field is required if exact is false or omitted.
+ * Number of nearest neighbors to use during the search. Value must be less than or equal to (<=) 10000. You can't specify a number less than the number of documents to return (limit).
+ */
+ public static function vectorSearch(
+ string $index,
+ int $limit,
+ array|string $path,
+ PackedArray|BSONArray|array $queryVector,
+ Optional|bool $exact = Optional::Undefined,
+ Optional|QueryInterface|array $filter = Optional::Undefined,
+ Optional|int $numCandidates = Optional::Undefined,
+ ): VectorSearchStage {
+ return new VectorSearchStage($index, $limit, $path, $queryVector, $exact, $filter, $numCandidates);
+ }
}
diff --git a/src/Builder/Stage/FluentFactoryTrait.php b/src/Builder/Stage/FluentFactoryTrait.php
index 6f468322d..757a09a0b 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,11 +606,35 @@ 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|Serializable|array|stdClass $search
- */
- public function search(Document|Serializable|stdClass|array $search): static
- {
- $this->pipeline[] = Stage::search($search);
+ * @param Document|SearchOperatorInterface|Serializable|array|stdClass $operator Operator to search with. You can provide a specific operator or use
+ * the compound operator to run a compound query with multiple operators.
+ * @param Optional|string $index Name of the Atlas Search index to use. If omitted, defaults to "default".
+ * @param Optional|Document|Serializable|array|stdClass $highlight Specifies the highlighting options for displaying search terms in their original context.
+ * @param Optional|bool $concurrent Parallelize search across segments on dedicated search nodes.
+ * If you don't have separate search nodes on your cluster,
+ * Atlas Search ignores this flag. If omitted, defaults to false.
+ * @param Optional|string $count Document that specifies the count options for retrieving a count of the results.
+ * @param Optional|string $searchAfter Reference point for retrieving results. searchAfter returns documents starting immediately following the specified reference point.
+ * @param Optional|string $searchBefore Reference point for retrieving results. searchBefore returns documents starting immediately before the specified reference point.
+ * @param Optional|bool $scoreDetails Flag that specifies whether to retrieve a detailed breakdown of the score for the documents in the results. If omitted, defaults to false.
+ * @param Optional|Document|Serializable|array|stdClass $sort Document that specifies the fields to sort the Atlas Search results by in ascending or descending order.
+ * @param Optional|bool $returnStoredSource Flag that specifies whether to perform a full document lookup on the backend database or return only stored source fields directly from Atlas Search.
+ * @param Optional|Document|Serializable|array|stdClass $tracking Document that specifies the tracking option to retrieve analytics information on the search terms.
+ */
+ 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,
+ Optional|string $count = Optional::Undefined,
+ Optional|string $searchAfter = Optional::Undefined,
+ Optional|string $searchBefore = Optional::Undefined,
+ Optional|bool $scoreDetails = Optional::Undefined,
+ Optional|Document|Serializable|stdClass|array $sort = Optional::Undefined,
+ Optional|bool $returnStoredSource = Optional::Undefined,
+ Optional|Document|Serializable|stdClass|array $tracking = Optional::Undefined,
+ ): static {
+ $this->pipeline[] = Stage::search($operator, $index, $highlight, $concurrent, $count, $searchAfter, $searchBefore, $scoreDetails, $sort, $returnStoredSource, $tracking);
return $this;
}
@@ -619,11 +644,17 @@ public function search(Document|Serializable|stdClass|array $search): static
* 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|Serializable|array|stdClass $meta
- */
- public function searchMeta(Document|Serializable|stdClass|array $meta): static
- {
- $this->pipeline[] = Stage::searchMeta($meta);
+ * @param Document|SearchOperatorInterface|Serializable|array|stdClass $operator Operator to search with. You can provide a specific operator or use
+ * the compound operator to run a compound query with multiple operators.
+ * @param Optional|string $index Name of the Atlas Search index to use. If omitted, defaults to default.
+ * @param Optional|Document|Serializable|array|stdClass $count Document that specifies the count options for retrieving a count of the results.
+ */
+ public function searchMeta(
+ Document|Serializable|SearchOperatorInterface|stdClass|array $operator,
+ Optional|string $index = Optional::Undefined,
+ Optional|Document|Serializable|stdClass|array $count = Optional::Undefined,
+ ): static {
+ $this->pipeline[] = Stage::searchMeta($operator, $index, $count);
return $this;
}
@@ -767,4 +798,31 @@ public function unwind(
return $this;
}
+
+ /**
+ * The $vectorSearch stage performs an ANN or ENN search on a vector in the specified field.
+ *
+ * @see https://www.mongodb.com/docs/atlas/atlas-vector-search/vector-search-stage/
+ * @param string $index Name of the Atlas Vector Search index to use.
+ * @param int $limit Number of documents to return in the results. This value can't exceed the value of numCandidates if you specify numCandidates.
+ * @param array|string $path Indexed vector type field to search.
+ * @param BSONArray|PackedArray|array $queryVector Array of numbers that represent the query vector. The number type must match the indexed field value type.
+ * @param Optional|bool $exact This is required if numCandidates is omitted. false to run ANN search. true to run ENN search.
+ * @param Optional|QueryInterface|array $filter Any match query that compares an indexed field with a boolean, date, objectId, number (not decimals), string, or UUID to use as a pre-filter.
+ * @param Optional|int $numCandidates This field is required if exact is false or omitted.
+ * Number of nearest neighbors to use during the search. Value must be less than or equal to (<=) 10000. You can't specify a number less than the number of documents to return (limit).
+ */
+ public function vectorSearch(
+ string $index,
+ int $limit,
+ array|string $path,
+ PackedArray|BSONArray|array $queryVector,
+ Optional|bool $exact = Optional::Undefined,
+ Optional|QueryInterface|array $filter = Optional::Undefined,
+ Optional|int $numCandidates = Optional::Undefined,
+ ): static {
+ $this->pipeline[] = Stage::vectorSearch($index, $limit, $path, $queryVector, $exact, $filter, $numCandidates);
+
+ return $this;
+ }
}
diff --git a/src/Builder/Stage/SearchMetaStage.php b/src/Builder/Stage/SearchMetaStage.php
index 88f05bec1..14687161b 100644
--- a/src/Builder/Stage/SearchMetaStage.php
+++ b/src/Builder/Stage/SearchMetaStage.php
@@ -12,6 +12,8 @@
use MongoDB\BSON\Serializable;
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,18 +26,35 @@
*/
final class SearchMetaStage implements StageInterface, OperatorInterface
{
- public const ENCODE = Encode::Single;
+ public const ENCODE = Encode::Object;
public const NAME = '$searchMeta';
- public const PROPERTIES = ['meta' => 'meta'];
+ public const PROPERTIES = ['operator' => null, 'index' => 'index', 'count' => 'count'];
- /** @var Document|Serializable|array|stdClass $meta */
- public readonly Document|Serializable|stdClass|array $meta;
+ /**
+ * @var Document|SearchOperatorInterface|Serializable|array|stdClass $operator Operator to search with. You can provide a specific operator or use
+ * the compound operator to run a compound query with multiple operators.
+ */
+ public readonly Document|Serializable|SearchOperatorInterface|stdClass|array $operator;
+
+ /** @var Optional|string $index Name of the Atlas Search index to use. If omitted, defaults to default. */
+ public readonly Optional|string $index;
+
+ /** @var Optional|Document|Serializable|array|stdClass $count Document that specifies the count options for retrieving a count of the results. */
+ public readonly Optional|Document|Serializable|stdClass|array $count;
/**
- * @param Document|Serializable|array|stdClass $meta
+ * @param Document|SearchOperatorInterface|Serializable|array|stdClass $operator Operator to search with. You can provide a specific operator or use
+ * the compound operator to run a compound query with multiple operators.
+ * @param Optional|string $index Name of the Atlas Search index to use. If omitted, defaults to default.
+ * @param Optional|Document|Serializable|array|stdClass $count Document that specifies the count options for retrieving a count of the results.
*/
- public function __construct(Document|Serializable|stdClass|array $meta)
- {
- $this->meta = $meta;
+ public function __construct(
+ Document|Serializable|SearchOperatorInterface|stdClass|array $operator,
+ Optional|string $index = Optional::Undefined,
+ Optional|Document|Serializable|stdClass|array $count = Optional::Undefined,
+ ) {
+ $this->operator = $operator;
+ $this->index = $index;
+ $this->count = $count;
}
}
diff --git a/src/Builder/Stage/SearchStage.php b/src/Builder/Stage/SearchStage.php
index 188a2ece4..bd224dba9 100644
--- a/src/Builder/Stage/SearchStage.php
+++ b/src/Builder/Stage/SearchStage.php
@@ -12,6 +12,8 @@
use MongoDB\BSON\Serializable;
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,18 +26,102 @@
*/
final class SearchStage implements StageInterface, OperatorInterface
{
- public const ENCODE = Encode::Single;
+ public const ENCODE = Encode::Object;
public const NAME = '$search';
- public const PROPERTIES = ['search' => 'search'];
- /** @var Document|Serializable|array|stdClass $search */
- public readonly Document|Serializable|stdClass|array $search;
+ public const PROPERTIES = [
+ 'operator' => null,
+ 'index' => 'index',
+ 'highlight' => 'highlight',
+ 'concurrent' => 'concurrent',
+ 'count' => 'count',
+ 'searchAfter' => 'searchAfter',
+ 'searchBefore' => 'searchBefore',
+ 'scoreDetails' => 'scoreDetails',
+ 'sort' => 'sort',
+ 'returnStoredSource' => 'returnStoredSource',
+ 'tracking' => 'tracking',
+ ];
/**
- * @param Document|Serializable|array|stdClass $search
+ * @var Document|SearchOperatorInterface|Serializable|array|stdClass $operator Operator to search with. You can provide a specific operator or use
+ * the compound operator to run a compound query with multiple operators.
*/
- public function __construct(Document|Serializable|stdClass|array $search)
- {
- $this->search = $search;
+ public readonly Document|Serializable|SearchOperatorInterface|stdClass|array $operator;
+
+ /** @var Optional|string $index Name of the Atlas Search index to use. If omitted, defaults to "default". */
+ public readonly Optional|string $index;
+
+ /** @var Optional|Document|Serializable|array|stdClass $highlight Specifies the highlighting options for displaying search terms in their original context. */
+ public readonly Optional|Document|Serializable|stdClass|array $highlight;
+
+ /**
+ * @var Optional|bool $concurrent Parallelize search across segments on dedicated search nodes.
+ * If you don't have separate search nodes on your cluster,
+ * Atlas Search ignores this flag. If omitted, defaults to false.
+ */
+ public readonly Optional|bool $concurrent;
+
+ /** @var Optional|string $count Document that specifies the count options for retrieving a count of the results. */
+ public readonly Optional|string $count;
+
+ /** @var Optional|string $searchAfter Reference point for retrieving results. searchAfter returns documents starting immediately following the specified reference point. */
+ public readonly Optional|string $searchAfter;
+
+ /** @var Optional|string $searchBefore Reference point for retrieving results. searchBefore returns documents starting immediately before the specified reference point. */
+ public readonly Optional|string $searchBefore;
+
+ /** @var Optional|bool $scoreDetails Flag that specifies whether to retrieve a detailed breakdown of the score for the documents in the results. If omitted, defaults to false. */
+ public readonly Optional|bool $scoreDetails;
+
+ /** @var Optional|Document|Serializable|array|stdClass $sort Document that specifies the fields to sort the Atlas Search results by in ascending or descending order. */
+ public readonly Optional|Document|Serializable|stdClass|array $sort;
+
+ /** @var Optional|bool $returnStoredSource Flag that specifies whether to perform a full document lookup on the backend database or return only stored source fields directly from Atlas Search. */
+ public readonly Optional|bool $returnStoredSource;
+
+ /** @var Optional|Document|Serializable|array|stdClass $tracking Document that specifies the tracking option to retrieve analytics information on the search terms. */
+ public readonly Optional|Document|Serializable|stdClass|array $tracking;
+
+ /**
+ * @param Document|SearchOperatorInterface|Serializable|array|stdClass $operator Operator to search with. You can provide a specific operator or use
+ * the compound operator to run a compound query with multiple operators.
+ * @param Optional|string $index Name of the Atlas Search index to use. If omitted, defaults to "default".
+ * @param Optional|Document|Serializable|array|stdClass $highlight Specifies the highlighting options for displaying search terms in their original context.
+ * @param Optional|bool $concurrent Parallelize search across segments on dedicated search nodes.
+ * If you don't have separate search nodes on your cluster,
+ * Atlas Search ignores this flag. If omitted, defaults to false.
+ * @param Optional|string $count Document that specifies the count options for retrieving a count of the results.
+ * @param Optional|string $searchAfter Reference point for retrieving results. searchAfter returns documents starting immediately following the specified reference point.
+ * @param Optional|string $searchBefore Reference point for retrieving results. searchBefore returns documents starting immediately before the specified reference point.
+ * @param Optional|bool $scoreDetails Flag that specifies whether to retrieve a detailed breakdown of the score for the documents in the results. If omitted, defaults to false.
+ * @param Optional|Document|Serializable|array|stdClass $sort Document that specifies the fields to sort the Atlas Search results by in ascending or descending order.
+ * @param Optional|bool $returnStoredSource Flag that specifies whether to perform a full document lookup on the backend database or return only stored source fields directly from Atlas Search.
+ * @param Optional|Document|Serializable|array|stdClass $tracking Document that specifies the tracking option to retrieve analytics information on the search terms.
+ */
+ 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,
+ Optional|string $count = Optional::Undefined,
+ Optional|string $searchAfter = Optional::Undefined,
+ Optional|string $searchBefore = Optional::Undefined,
+ Optional|bool $scoreDetails = Optional::Undefined,
+ Optional|Document|Serializable|stdClass|array $sort = Optional::Undefined,
+ Optional|bool $returnStoredSource = Optional::Undefined,
+ Optional|Document|Serializable|stdClass|array $tracking = Optional::Undefined,
+ ) {
+ $this->operator = $operator;
+ $this->index = $index;
+ $this->highlight = $highlight;
+ $this->concurrent = $concurrent;
+ $this->count = $count;
+ $this->searchAfter = $searchAfter;
+ $this->searchBefore = $searchBefore;
+ $this->scoreDetails = $scoreDetails;
+ $this->sort = $sort;
+ $this->returnStoredSource = $returnStoredSource;
+ $this->tracking = $tracking;
}
}
diff --git a/src/Builder/Stage/VectorSearchStage.php b/src/Builder/Stage/VectorSearchStage.php
new file mode 100644
index 000000000..e8f7738cc
--- /dev/null
+++ b/src/Builder/Stage/VectorSearchStage.php
@@ -0,0 +1,104 @@
+ 'index',
+ 'limit' => 'limit',
+ 'path' => 'path',
+ 'queryVector' => 'queryVector',
+ 'exact' => 'exact',
+ 'filter' => 'filter',
+ 'numCandidates' => 'numCandidates',
+ ];
+
+ /** @var string $index Name of the Atlas Vector Search index to use. */
+ public readonly string $index;
+
+ /** @var int $limit Number of documents to return in the results. This value can't exceed the value of numCandidates if you specify numCandidates. */
+ public readonly int $limit;
+
+ /** @var array|string $path Indexed vector type field to search. */
+ public readonly array|string $path;
+
+ /** @var BSONArray|PackedArray|array $queryVector Array of numbers that represent the query vector. The number type must match the indexed field value type. */
+ public readonly PackedArray|BSONArray|array $queryVector;
+
+ /** @var Optional|bool $exact This is required if numCandidates is omitted. false to run ANN search. true to run ENN search. */
+ public readonly Optional|bool $exact;
+
+ /** @var Optional|QueryInterface|array $filter Any match query that compares an indexed field with a boolean, date, objectId, number (not decimals), string, or UUID to use as a pre-filter. */
+ public readonly Optional|QueryInterface|array $filter;
+
+ /**
+ * @var Optional|int $numCandidates This field is required if exact is false or omitted.
+ * Number of nearest neighbors to use during the search. Value must be less than or equal to (<=) 10000. You can't specify a number less than the number of documents to return (limit).
+ */
+ public readonly Optional|int $numCandidates;
+
+ /**
+ * @param string $index Name of the Atlas Vector Search index to use.
+ * @param int $limit Number of documents to return in the results. This value can't exceed the value of numCandidates if you specify numCandidates.
+ * @param array|string $path Indexed vector type field to search.
+ * @param BSONArray|PackedArray|array $queryVector Array of numbers that represent the query vector. The number type must match the indexed field value type.
+ * @param Optional|bool $exact This is required if numCandidates is omitted. false to run ANN search. true to run ENN search.
+ * @param Optional|QueryInterface|array $filter Any match query that compares an indexed field with a boolean, date, objectId, number (not decimals), string, or UUID to use as a pre-filter.
+ * @param Optional|int $numCandidates This field is required if exact is false or omitted.
+ * Number of nearest neighbors to use during the search. Value must be less than or equal to (<=) 10000. You can't specify a number less than the number of documents to return (limit).
+ */
+ public function __construct(
+ string $index,
+ int $limit,
+ array|string $path,
+ PackedArray|BSONArray|array $queryVector,
+ Optional|bool $exact = Optional::Undefined,
+ Optional|QueryInterface|array $filter = Optional::Undefined,
+ Optional|int $numCandidates = Optional::Undefined,
+ ) {
+ $this->index = $index;
+ $this->limit = $limit;
+ $this->path = $path;
+ if (is_array($queryVector) && ! array_is_list($queryVector)) {
+ throw new InvalidArgumentException('Expected $queryVector argument to be a list, got an associative array.');
+ }
+
+ $this->queryVector = $queryVector;
+ $this->exact = $exact;
+ if (is_array($filter)) {
+ $filter = QueryObject::create($filter);
+ }
+
+ $this->filter = $filter;
+ $this->numCandidates = $numCandidates;
+ }
+}
diff --git a/src/Builder/Type/Encode.php b/src/Builder/Type/Encode.php
index d03491623..120c2ee46 100644
--- a/src/Builder/Type/Encode.php
+++ b/src/Builder/Type/Encode.php
@@ -29,6 +29,11 @@ enum Encode
*/
case Single;
+ /**
+ * Specific for $group stage
+ */
+ case Group;
+
/**
* 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..c144d4ebe
--- /dev/null
+++ b/src/Builder/Type/SearchOperatorInterface.php
@@ -0,0 +1,7 @@
+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..6cd8ae9e1
--- /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(hex2bin('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..fa94f8c9a
--- /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 8b76342ba..850013c3e 100644
--- a/tests/Builder/Stage/Pipelines.php
+++ b/tests/Builder/Stage/Pipelines.php
@@ -2638,6 +2638,197 @@ enum Pipelines: string
]
JSON;
+ /**
+ * Year Facet
+ *
+ * @see https://www.mongodb.com/docs/atlas/atlas-search/facet/#example-1
+ */
+ case SearchMetaYearFacet = <<<'JSON'
+ [
+ {
+ "$searchMeta": {
+ "facet": {
+ "operator": {
+ "range": {
+ "path": "year",
+ "gte": {
+ "$numberInt": "1980"
+ },
+ "lte": {
+ "$numberInt": "2000"
+ }
+ }
+ },
+ "facets": {
+ "yearFacet": {
+ "type": "number",
+ "path": "year",
+ "boundaries": [
+ {
+ "$numberInt": "1980"
+ },
+ {
+ "$numberInt": "1990"
+ },
+ {
+ "$numberInt": "2000"
+ }
+ ],
+ "default": "other"
+ }
+ }
+ }
+ }
+ }
+ ]
+ JSON;
+
+ /**
+ * Date Facet
+ *
+ * @see https://www.mongodb.com/docs/atlas/atlas-search/facet/#example-2
+ */
+ case SearchMetaDateFacet = <<<'JSON'
+ [
+ {
+ "$searchMeta": {
+ "facet": {
+ "operator": {
+ "range": {
+ "path": "released",
+ "gte": {
+ "$date": {
+ "$numberLong": "946684800000"
+ }
+ },
+ "lte": {
+ "$date": {
+ "$numberLong": "1422662400000"
+ }
+ }
+ }
+ },
+ "facets": {
+ "yearFacet": {
+ "type": "date",
+ "path": "released",
+ "boundaries": [
+ {
+ "$date": {
+ "$numberLong": "946684800000"
+ }
+ },
+ {
+ "$date": {
+ "$numberLong": "1104537600000"
+ }
+ },
+ {
+ "$date": {
+ "$numberLong": "1262304000000"
+ }
+ },
+ {
+ "$date": {
+ "$numberLong": "1420070400000"
+ }
+ }
+ ],
+ "default": "other"
+ }
+ }
+ }
+ }
+ }
+ ]
+ JSON;
+
+ /**
+ * Metadata Results
+ *
+ * @see https://www.mongodb.com/docs/atlas/atlas-search/facet/#examples
+ */
+ case SearchMetaMetadataResults = <<<'JSON'
+ [
+ {
+ "$searchMeta": {
+ "facet": {
+ "operator": {
+ "range": {
+ "path": "released",
+ "gte": {
+ "$date": {
+ "$numberLong": "946684800000"
+ }
+ },
+ "lte": {
+ "$date": {
+ "$numberLong": "1422662400000"
+ }
+ }
+ }
+ },
+ "facets": {
+ "directorsFacet": {
+ "type": "string",
+ "path": "directors",
+ "numBuckets": {
+ "$numberInt": "7"
+ }
+ },
+ "yearFacet": {
+ "type": "number",
+ "path": "year",
+ "boundaries": [
+ {
+ "$numberInt": "2000"
+ },
+ {
+ "$numberInt": "2005"
+ },
+ {
+ "$numberInt": "2010"
+ },
+ {
+ "$numberInt": "2015"
+ }
+ ]
+ }
+ }
+ }
+ }
+ }
+ ]
+ JSON;
+
+ /**
+ * Autocomplete Bucket Results through Facet Queries
+ *
+ * @see https://www.mongodb.com/docs/atlas/atlas-search/autocomplete/#bucket-results-through-facet-queries
+ */
+ case SearchMetaAutocompleteBucketResultsThroughFacetQueries = <<<'JSON'
+ [
+ {
+ "$searchMeta": {
+ "facet": {
+ "operator": {
+ "autocomplete": {
+ "path": "title",
+ "query": "Gravity"
+ }
+ },
+ "facets": {
+ "titleFacet": {
+ "type": "string",
+ "path": "title"
+ }
+ }
+ }
+ }
+ }
+ ]
+ JSON;
+
/**
* Using Two $set Stages
*
@@ -3362,4 +3553,163 @@ enum Pipelines: string
}
]
JSON;
+
+ /**
+ * ANN Basic
+ *
+ * @see https://www.mongodb.com/docs/atlas/atlas-vector-search/vector-search-stage/#ann-examples
+ */
+ case VectorSearchANNBasic = <<<'JSON'
+ [
+ {
+ "$vectorSearch": {
+ "index": "vector_index",
+ "path": "plot_embedding",
+ "queryVector": [
+ {
+ "$numberDouble": "-0.0016261311999999999121"
+ },
+ {
+ "$numberDouble": "-0.028070756999999998266"
+ },
+ {
+ "$numberDouble": "-0.011342932000000000015"
+ }
+ ],
+ "numCandidates": {
+ "$numberInt": "150"
+ },
+ "limit": {
+ "$numberInt": "10"
+ }
+ }
+ },
+ {
+ "$project": {
+ "_id": {
+ "$numberInt": "0"
+ },
+ "plot": {
+ "$numberInt": "1"
+ },
+ "title": {
+ "$numberInt": "1"
+ },
+ "score": {
+ "$meta": "vectorSearchScore"
+ }
+ }
+ }
+ ]
+ JSON;
+
+ /**
+ * ANN Filter
+ *
+ * @see https://www.mongodb.com/docs/atlas/atlas-vector-search/vector-search-stage/#ann-examples
+ */
+ case VectorSearchANNFilter = <<<'JSON'
+ [
+ {
+ "$vectorSearch": {
+ "index": "vector_index",
+ "path": "plot_embedding",
+ "filter": {
+ "$and": [
+ {
+ "year": {
+ "$lt": {
+ "$numberInt": "1975"
+ }
+ }
+ }
+ ]
+ },
+ "queryVector": [
+ {
+ "$numberDouble": "0.024210530000000000939"
+ },
+ {
+ "$numberDouble": "-0.022372592000000000173"
+ },
+ {
+ "$numberDouble": "-0.0062311370000000003075"
+ }
+ ],
+ "numCandidates": {
+ "$numberInt": "150"
+ },
+ "limit": {
+ "$numberInt": "10"
+ }
+ }
+ },
+ {
+ "$project": {
+ "_id": {
+ "$numberInt": "0"
+ },
+ "title": {
+ "$numberInt": "1"
+ },
+ "plot": {
+ "$numberInt": "1"
+ },
+ "year": {
+ "$numberInt": "1"
+ },
+ "score": {
+ "$meta": "vectorSearchScore"
+ }
+ }
+ }
+ ]
+ JSON;
+
+ /**
+ * ENN
+ *
+ * @see https://www.mongodb.com/docs/atlas/atlas-vector-search/vector-search-stage/#enn-examples
+ */
+ case VectorSearchENN = <<<'JSON'
+ [
+ {
+ "$vectorSearch": {
+ "index": "vector_index",
+ "path": "plot_embedding",
+ "queryVector": [
+ {
+ "$numberDouble": "-0.0069540970000000002296"
+ },
+ {
+ "$numberDouble": "-0.009932498999999999148"
+ },
+ {
+ "$numberDouble": "-0.0013114739999999999731"
+ }
+ ],
+ "exact": true,
+ "limit": {
+ "$numberInt": "10"
+ }
+ }
+ },
+ {
+ "$project": {
+ "_id": {
+ "$numberInt": "0"
+ },
+ "plot": {
+ "$numberInt": "1"
+ },
+ "title": {
+ "$numberInt": "1"
+ },
+ "score": {
+ "$meta": "vectorSearchScore"
+ }
+ }
+ }
+ ]
+ JSON;
}
diff --git a/tests/Builder/Stage/SearchMetaStageTest.php b/tests/Builder/Stage/SearchMetaStageTest.php
index 3b8283424..6ded31935 100644
--- a/tests/Builder/Stage/SearchMetaStageTest.php
+++ b/tests/Builder/Stage/SearchMetaStageTest.php
@@ -4,7 +4,10 @@
namespace MongoDB\Tests\Builder\Stage;
+use DateTimeImmutable;
+use MongoDB\BSON\UTCDateTime;
use MongoDB\Builder\Pipeline;
+use MongoDB\Builder\Search;
use MongoDB\Builder\Stage;
use MongoDB\Tests\Builder\PipelineTestCase;
@@ -15,19 +18,134 @@
*/
class SearchMetaStageTest extends PipelineTestCase
{
+ public function testAutocompleteBucketResultsThroughFacetQueries(): void
+ {
+ $pipeline = new Pipeline(
+ Stage::searchMeta(
+ Search::facet(
+ facets: object(
+ titleFacet: object(
+ type: 'string',
+ path: 'title',
+ ),
+ ),
+ operator: Search::autocomplete(
+ path: 'title',
+ query: 'Gravity',
+ ),
+ ),
+ ),
+ );
+
+ $this->assertSamePipeline(Pipelines::SearchMetaAutocompleteBucketResultsThroughFacetQueries, $pipeline);
+ }
+
+ public function testDateFacet(): void
+ {
+ $pipeline = new Pipeline(
+ Stage::searchMeta(
+ 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(
+ type: 'date',
+ path: 'released',
+ boundaries: [
+ new UTCDateTime(new DateTimeImmutable('2000-01-01')),
+ new UTCDateTime(new DateTimeImmutable('2005-01-01')),
+ new UTCDateTime(new DateTimeImmutable('2010-01-01')),
+ new UTCDateTime(new DateTimeImmutable('2015-01-01')),
+ ],
+ default: 'other',
+ ),
+ ),
+ ),
+ ),
+ );
+
+ $this->assertSamePipeline(Pipelines::SearchMetaDateFacet, $pipeline);
+ }
+
public function testExample(): void
{
$pipeline = new Pipeline(
- Stage::searchMeta(object(
- range: object(
+ Stage::searchMeta(
+ Search::range(
path: 'year',
gte: 1998,
lt: 1999,
),
count: object(type: 'total'),
- )),
+ ),
);
$this->assertSamePipeline(Pipelines::SearchMetaExample, $pipeline);
}
+
+ public function testMetadataResults(): void
+ {
+ $pipeline = new Pipeline(
+ Stage::searchMeta(
+ 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(
+ type: 'string',
+ path: 'directors',
+ numBuckets: 7,
+ ),
+ yearFacet: object(
+ type: 'number',
+ path: 'year',
+ boundaries: [
+ 2000,
+ 2005,
+ 2010,
+ 2015,
+ ],
+ ),
+ ),
+ ),
+ ),
+ );
+
+ $this->assertSamePipeline(Pipelines::SearchMetaMetadataResults, $pipeline);
+ }
+
+ public function testYearFacet(): void
+ {
+ $pipeline = new Pipeline(
+ Stage::searchMeta(
+ Search::facet(
+ operator: Search::range(
+ path: 'year',
+ gte: 1980,
+ lte: 2000,
+ ),
+ facets: object(
+ yearFacet: object(
+ type: 'number',
+ path: 'year',
+ boundaries: [
+ 1980,
+ 1990,
+ 2000,
+ ],
+ default: 'other',
+ ),
+ ),
+ ),
+ ),
+ );
+
+ $this->assertSamePipeline(Pipelines::SearchMetaYearFacet, $pipeline);
+ }
}
diff --git a/tests/Builder/Stage/SearchStageTest.php b/tests/Builder/Stage/SearchStageTest.php
index 656b9b9ee..faae2ab11 100644
--- a/tests/Builder/Stage/SearchStageTest.php
+++ b/tests/Builder/Stage/SearchStageTest.php
@@ -8,11 +8,10 @@
use MongoDB\BSON\UTCDateTime;
use MongoDB\Builder\Expression;
use MongoDB\Builder\Pipeline;
+use MongoDB\Builder\Search;
use MongoDB\Builder\Stage;
use MongoDB\Tests\Builder\PipelineTestCase;
-use function MongoDB\object;
-
/**
* Test $search stage
*/
@@ -21,13 +20,13 @@ class SearchStageTest extends PipelineTestCase
public function testExample(): void
{
$pipeline = new Pipeline(
- Stage::search(object(
- near: object(
+ Stage::search(
+ Search::near(
path: 'released',
origin: new UTCDateTime(new DateTime('2011-09-01T00:00:00.000+00:00')),
pivot: 7776000000,
),
- )),
+ ),
Stage::project(_id: 0, title: 1, released: 1),
Stage::limit(5),
Stage::facet(
diff --git a/tests/Builder/Stage/VectorSearchStageTest.php b/tests/Builder/Stage/VectorSearchStageTest.php
new file mode 100644
index 000000000..f6259510a
--- /dev/null
+++ b/tests/Builder/Stage/VectorSearchStageTest.php
@@ -0,0 +1,85 @@
+ 'vectorSearchScore'],
+ ),
+ );
+
+ $this->assertSamePipeline(Pipelines::VectorSearchANNBasic, $pipeline);
+ }
+
+ public function testANNFilter(): void
+ {
+ $pipeline = new Pipeline(
+ Stage::vectorSearch(
+ index: 'vector_index',
+ limit: 10,
+ path: 'plot_embedding',
+ queryVector: [0.02421053, -0.022372592, -0.006231137],
+ filter: Query::and(
+ Query::query(
+ year: Query::lt(1975),
+ ),
+ ),
+ numCandidates: 150,
+ ),
+ Stage::project(
+ _id: 0,
+ title: 1,
+ plot: 1,
+ year: 1,
+ score: ['$meta' => 'vectorSearchScore'],
+ ),
+ );
+
+ $this->assertSamePipeline(Pipelines::VectorSearchANNFilter, $pipeline);
+ }
+
+ public function testENN(): void
+ {
+ $pipeline = new Pipeline(
+ Stage::vectorSearch(
+ index: 'vector_index',
+ limit: 10,
+ path: 'plot_embedding',
+ queryVector: [-0.006954097, -0.009932499, -0.001311474],
+ exact: true,
+ ),
+ Stage::project(
+ _id: 0,
+ title: 1,
+ plot: 1,
+ score: ['$meta' => 'vectorSearchScore'],
+ ),
+ );
+
+ $this->assertSamePipeline(Pipelines::VectorSearchENN, $pipeline);
+ }
+}