Skip to content

Commit ac15365

Browse files
authored
Add query building API for the $densify aggregation pipeline stage (#961)
JAVA-4350
1 parent b68b580 commit ac15365

File tree

20 files changed

+1079
-16
lines changed

20 files changed

+1079
-16
lines changed

driver-core/src/main/com/mongodb/client/model/Aggregates.java

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
package com.mongodb.client.model;
1818

1919
import com.mongodb.MongoNamespace;
20+
import com.mongodb.client.model.densify.DensifyOptions;
21+
import com.mongodb.client.model.densify.DensifyRange;
2022
import com.mongodb.client.model.fill.FillComputation;
2123
import com.mongodb.client.model.fill.FillOptions;
2224
import com.mongodb.client.model.search.SearchOperator;
@@ -38,6 +40,7 @@
3840
import java.util.Objects;
3941

4042
import static com.mongodb.assertions.Assertions.assertTrue;
43+
import static com.mongodb.client.model.densify.DensifyOptions.densifyOptions;
4144
import static com.mongodb.assertions.Assertions.isTrueArgument;
4245
import static com.mongodb.assertions.Assertions.notNull;
4346
import static com.mongodb.client.model.search.SearchOptions.searchOptions;
@@ -661,6 +664,60 @@ public static <TExpression> Bson setWindowFields(@Nullable final TExpression par
661664
return new SetWindowFieldsStage<>(partitionBy, sortBy, output);
662665
}
663666

667+
/**
668+
* Creates a {@code $densify} pipeline stage, which adds documents to a sequence of documents
669+
* where certain values in the {@code field} are missing.
670+
*
671+
* @param field The field to densify.
672+
* @param range The range.
673+
* @return The requested pipeline stage.
674+
* @mongodb.driver.manual reference/operator/aggregation/densify/ $densify
675+
* @mongodb.driver.manual core/document/#dot-notation Dot notation
676+
* @mongodb.server.release 5.1
677+
* @since 4.7
678+
*/
679+
public static Bson densify(final String field, final DensifyRange range) {
680+
return densify(notNull("field", field), notNull("range", range), densifyOptions());
681+
}
682+
683+
/**
684+
* Creates a {@code $densify} pipeline stage, which adds documents to a sequence of documents
685+
* where certain values in the {@code field} are missing.
686+
*
687+
* @param field The field to densify.
688+
* @param range The range.
689+
* @param options The densify options.
690+
* Specifying {@link DensifyOptions#densifyOptions()} is equivalent to calling {@link #densify(String, DensifyRange)}.
691+
* @return The requested pipeline stage.
692+
* @mongodb.driver.manual reference/operator/aggregation/densify/ $densify
693+
* @mongodb.driver.manual core/document/#dot-notation Dot notation
694+
* @mongodb.server.release 5.1
695+
* @since 4.7
696+
*/
697+
public static Bson densify(final String field, final DensifyRange range, final DensifyOptions options) {
698+
notNull("field", field);
699+
notNull("range", range);
700+
notNull("options", options);
701+
return new Bson() {
702+
@Override
703+
public <TDocument> BsonDocument toBsonDocument(final Class<TDocument> documentClass, final CodecRegistry codecRegistry) {
704+
BsonDocument densifySpecificationDoc = new BsonDocument("field", new BsonString(field));
705+
densifySpecificationDoc.append("range", range.toBsonDocument(documentClass, codecRegistry));
706+
densifySpecificationDoc.putAll(options.toBsonDocument(documentClass, codecRegistry));
707+
return new BsonDocument("$densify", densifySpecificationDoc);
708+
}
709+
710+
@Override
711+
public String toString() {
712+
return "Stage{name='$densify'"
713+
+ ", field=" + field
714+
+ ", range=" + range
715+
+ ", options=" + options
716+
+ '}';
717+
}
718+
};
719+
}
720+
664721
/**
665722
* Creates a {@code $fill} pipeline stage, which assigns values to fields when they are {@link BsonType#NULL Null} or missing.
666723
*

driver-core/src/main/com/mongodb/client/model/MongoTimeUnit.java

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -15,48 +15,53 @@
1515
*/
1616
package com.mongodb.client.model;
1717

18+
import org.bson.conversions.Bson;
19+
import com.mongodb.client.model.densify.DensifyRange;
20+
1821
/**
19-
* Units for specifying time-based bounds for {@linkplain Window windows} and output units for some time-based
20-
* {@linkplain WindowedComputation windowed computations}.
22+
* Units for specifying time-based values.
2123
*
24+
* @see Windows
25+
* @see WindowedComputations
26+
* @see DensifyRange
2227
* @mongodb.server.release 5.0
2328
* @since 4.3
2429
*/
2530
public enum MongoTimeUnit {
2631
/**
27-
* YEAR
32+
* {@linkplain #value() "year"}
2833
*/
2934
YEAR("year", false),
3035
/**
31-
* QUARTER
36+
* {@linkplain #value() "quarter"}
3237
*/
3338
QUARTER("quarter", false),
3439
/**
35-
* MONTH
40+
* {@linkplain #value() "month"}
3641
*/
3742
MONTH("month", false),
3843
/**
39-
* WEEK
44+
* {@linkplain #value() "week"}
4045
*/
4146
WEEK("week", true),
4247
/**
43-
* DAY
48+
* {@linkplain #value() "day"}
4449
*/
4550
DAY("day", true),
4651
/**
47-
* HOUR
52+
* {@linkplain #value() "hour"}
4853
*/
4954
HOUR("hour", true),
5055
/**
51-
* MINUTE
56+
* {@linkplain #value() "minute"}
5257
*/
5358
MINUTE("minute", true),
5459
/**
55-
* SECOND
60+
* {@linkplain #value() "second"}
5661
*/
5762
SECOND("second", true),
5863
/**
59-
* MILLISECOND
64+
* {@linkplain #value() "millisecond"}
6065
*/
6166
MILLISECOND("millisecond", true);
6267

@@ -68,7 +73,13 @@ public enum MongoTimeUnit {
6873
this.fixed = fixed;
6974
}
7075

71-
String value() {
76+
/**
77+
* Returns a {@link String} representation of the unit, which may be useful when using methods like
78+
* {@link Windows#of(Bson)}, {@link DensifyRange#of(Bson)}.
79+
*
80+
* @return A {@link String} representation of the unit.
81+
*/
82+
public String value() {
7283
return value;
7384
}
7485

driver-core/src/main/com/mongodb/client/model/Windows.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ public final class Windows {
9393
* Window pastWeek1 = Windows.timeRange(-1, MongoTimeUnit.WEEK, Windows.Bound.CURRENT);
9494
* Window pastWeek2 = Windows.of(
9595
* new Document("range", Arrays.asList(-1, "current"))
96-
* .append("unit", "week"));
96+
* .append("unit", MongoTimeUnit.WEEK.value()));
9797
* }</pre>
9898
*
9999
* @param window A {@link Bson} representing the required window.
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/*
2+
* Copyright 2008-present MongoDB, Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUNumber WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package com.mongodb.client.model.densify;
17+
18+
import com.mongodb.annotations.Evolving;
19+
import com.mongodb.client.model.MongoTimeUnit;
20+
21+
import java.time.Instant;
22+
23+
/**
24+
* @see DensifyRange#fullRangeWithStep(long, MongoTimeUnit)
25+
* @see DensifyRange#partitionRangeWithStep(long, MongoTimeUnit)
26+
* @see DensifyRange#rangeWithStep(Instant, Instant, long, MongoTimeUnit)
27+
* @mongodb.server.release 5.1
28+
* @since 4.7
29+
*/
30+
@Evolving
31+
public interface DateDensifyRange extends DensifyRange {
32+
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
/*
2+
* Copyright 2008-present MongoDB, Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package com.mongodb.client.model.densify;
17+
18+
import com.mongodb.internal.client.model.AbstractConstructibleBson;
19+
import org.bson.Document;
20+
import org.bson.conversions.Bson;
21+
22+
import static com.mongodb.assertions.Assertions.notNull;
23+
import static com.mongodb.internal.client.model.Util.sizeAtLeast;
24+
25+
final class DensifyConstructibleBson extends AbstractConstructibleBson<DensifyConstructibleBson> implements
26+
NumberDensifyRange, DateDensifyRange,
27+
DensifyOptions {
28+
static final DensifyConstructibleBson EMPTY_IMMUTABLE = new DensifyConstructibleBson(AbstractConstructibleBson.EMPTY_IMMUTABLE);
29+
30+
DensifyConstructibleBson(final Bson base) {
31+
super(base);
32+
}
33+
34+
private DensifyConstructibleBson(final Bson base, final Document appended) {
35+
super(base, appended);
36+
}
37+
38+
@Override
39+
protected DensifyConstructibleBson newSelf(final Bson base, final Document appended) {
40+
return new DensifyConstructibleBson(base, appended);
41+
}
42+
43+
@Override
44+
public DensifyOptions partitionByFields(final Iterable<String> fields) {
45+
notNull("partitionByFields", fields);
46+
return newMutated(doc -> {
47+
if (sizeAtLeast(fields, 1)) {
48+
doc.append("partitionByFields", fields);
49+
} else {
50+
doc.remove("partitionByFields");
51+
}
52+
});
53+
}
54+
55+
@Override
56+
public DensifyOptions option(final String name, final Object value) {
57+
return newAppended(notNull("name", name), notNull("value", value));
58+
}
59+
}
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
/*
2+
* Copyright 2008-present MongoDB, Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package com.mongodb.client.model.densify;
17+
18+
import com.mongodb.annotations.Evolving;
19+
import com.mongodb.client.model.Aggregates;
20+
import com.mongodb.lang.Nullable;
21+
import org.bson.conversions.Bson;
22+
23+
import static java.util.Arrays.asList;
24+
import static java.util.Collections.emptyList;
25+
26+
/**
27+
* Represents optional fields of the {@code $densify} pipeline stage of an aggregation pipeline.
28+
*
29+
* @see Aggregates#densify(String, DensifyRange, DensifyOptions)
30+
* @see Aggregates#densify(String, DensifyRange)
31+
* @mongodb.server.release 5.1
32+
* @since 4.7
33+
*/
34+
@Evolving
35+
public interface DensifyOptions extends Bson {
36+
/**
37+
* Creates a new {@link DensifyOptions} with the specified {@code fields} to partition by.
38+
*
39+
* @param fields The fields to partition by.
40+
* If no fields are specified, then the whole sequence is considered to be a single partition.
41+
* @return A new {@link DensifyOptions}.
42+
* @mongodb.driver.manual core/document/#dot-notation Dot notation
43+
*/
44+
default DensifyOptions partitionByFields(@Nullable final String... fields) {
45+
return partitionByFields(fields == null ? emptyList() : asList(fields));
46+
}
47+
48+
/**
49+
* Creates a new {@link DensifyOptions} with the specified {@code fields} to partition by.
50+
*
51+
* @param fields The fields to partition by.
52+
* If no fields are specified, then the whole sequence is considered to be a single partition.
53+
* @return A new {@link DensifyOptions}.
54+
* @mongodb.driver.manual core/document/#dot-notation Dot notation
55+
*/
56+
DensifyOptions partitionByFields(Iterable<String> fields);
57+
58+
/**
59+
* Creates a new {@link DensifyOptions} with the specified option in situations when there is no builder method
60+
* that better satisfies your needs.
61+
* This method cannot be used to validate the syntax.
62+
* <p>
63+
* <i>Example</i><br>
64+
* The following code creates two functionally equivalent {@link DensifyOptions} objects,
65+
* though they may not be {@linkplain Object#equals(Object) equal}.
66+
* <pre>{@code
67+
* DensifyOptions options1 = DensifyOptions.densifyOptions()
68+
* .partitionByFields("fieldName");
69+
* DensifyOptions options2 = DensifyOptions.densifyOptions()
70+
* .option("partitionByFields", Collections.singleton("fieldName"));
71+
* }</pre>
72+
*
73+
* @param name The option name.
74+
* @param value The option value.
75+
* @return A new {@link DensifyOptions}.
76+
*/
77+
DensifyOptions option(String name, Object value);
78+
79+
/**
80+
* Returns {@link DensifyOptions} that represents server defaults.
81+
*
82+
* @return {@link DensifyOptions} that represents server defaults.
83+
*/
84+
static DensifyOptions densifyOptions() {
85+
return DensifyConstructibleBson.EMPTY_IMMUTABLE;
86+
}
87+
}

0 commit comments

Comments
 (0)