diff --git a/driver-core/src/main/com/mongodb/client/model/search/SearchCollector.java b/driver-core/src/main/com/mongodb/client/model/search/SearchCollector.java index 6f2c45b4961..571bff38044 100644 --- a/driver-core/src/main/com/mongodb/client/model/search/SearchCollector.java +++ b/driver-core/src/main/com/mongodb/client/model/search/SearchCollector.java @@ -54,6 +54,23 @@ static FacetSearchCollector facet(final SearchOperator operator, final Iterable< .append("facets", combineToBson(facets))); } + /** + * Returns a {@link SearchCollector} that groups results by values or ranges in the specified faceted fields and returns the count + * for each of those groups, faceting over the entire collection. + *

+ * Unlike {@link #facet(SearchOperator, Iterable)}, this method omits the search operator, so the facets are computed + * across all documents in the collection.

+ * + * @param facets The non-empty facet definitions. + * @return The requested {@link SearchCollector}. + * @mongodb.atlas.manual atlas-search/facet/ facet collector + */ + @Beta({Reason.CLIENT, Reason.SERVER}) + static FacetSearchCollector facet(final Iterable facets) { + notNull("facets", facets); + return new SearchConstructibleBsonElement("facet", new Document("facets", combineToBson(facets))); + } + /** * Creates a {@link SearchCollector} from a {@link Bson} in situations when there is no builder method that better satisfies your needs. * This method cannot be used to validate the syntax. diff --git a/driver-core/src/test/unit/com/mongodb/client/model/search/SearchCollectorTest.java b/driver-core/src/test/unit/com/mongodb/client/model/search/SearchCollectorTest.java index f8826554990..3cf9b86507a 100644 --- a/driver-core/src/test/unit/com/mongodb/client/model/search/SearchCollectorTest.java +++ b/driver-core/src/test/unit/com/mongodb/client/model/search/SearchCollectorTest.java @@ -62,6 +62,27 @@ void facet() { ); } + @Test + void facetWithoutOperator() { + assertAll( + () -> assertThrows(CodecConfigurationException.class, () -> + // facet names must be unique; `BsonCodec` wraps our `IllegalStateException` into `CodecConfigurationException` + SearchCollector.facet( + asList( + stringFacet("duplicateFacetName", fieldPath("stringFieldName")), + numberFacet("duplicateFacetName", fieldPath("numberFieldName"), asList(10, 20, 30)))) + // we have to render into `BsonDocument` in order to trigger the lazy check + .toBsonDocument() + ), + () -> assertEquals( + documentWithoutOperator() + .toBsonDocument(), + searchCollectorWithoutOperator() + .toBsonDocument() + ) + ); + } + private static SearchCollector docExamplePredefined() { return SearchCollector.facet( exists( @@ -89,4 +110,28 @@ private static Document docExampleCustom() { fieldPath("numberFieldName"), asList(10, 20, 30)))))); } + + private static SearchCollector searchCollectorWithoutOperator() { + return SearchCollector.facet( + asList( + stringFacet( + "stringFacetName", + fieldPath("stringFieldName")), + numberFacet( + "numberFacetName", + fieldPath("numberFieldName"), + asList(10, 20, 30)))); + } + + private static Document documentWithoutOperator() { + return new Document("facet", + new Document("facets", combineToBson(asList( + stringFacet( + "stringFacetName", + fieldPath("stringFieldName")), + numberFacet( + "numberFacetName", + fieldPath("numberFieldName"), + asList(10, 20, 30)))))); + } }