Skip to content

Commit 3505eb5

Browse files
committed
Implement document expressions (#1052)
JAVA-4782
1 parent 57c2416 commit 3505eb5

File tree

7 files changed

+488
-28
lines changed

7 files changed

+488
-28
lines changed

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

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,83 @@
1616

1717
package com.mongodb.client.model.expressions;
1818

19+
import org.bson.conversions.Bson;
20+
import org.bson.types.Decimal128;
21+
22+
import java.time.Instant;
23+
24+
import static com.mongodb.client.model.expressions.Expressions.of;
25+
1926
/**
2027
* Expresses a document value. A document is an ordered set of fields, where the
2128
* key is a string value, mapping to a value of any other expression type.
2229
*/
2330
public interface DocumentExpression extends Expression {
2431

32+
DocumentExpression setField(String fieldName, Expression exp);
33+
34+
DocumentExpression unsetField(String fieldName);
35+
36+
Expression getField(String fieldName);
37+
38+
BooleanExpression getBoolean(String fieldName);
39+
40+
BooleanExpression getBoolean(String fieldName, BooleanExpression other);
41+
42+
default BooleanExpression getBoolean(final String fieldName, final boolean other) {
43+
return getBoolean(fieldName, of(other));
44+
}
45+
46+
NumberExpression getNumber(String fieldName);
47+
48+
NumberExpression getNumber(String fieldName, NumberExpression other);
49+
50+
default NumberExpression getNumber(final String fieldName, final double other) {
51+
return getNumber(fieldName, of(other));
52+
}
53+
54+
default NumberExpression getNumber(final String fieldName, final Decimal128 other) {
55+
return getNumber(fieldName, of(other));
56+
}
57+
58+
IntegerExpression getInteger(String fieldName);
59+
60+
IntegerExpression getInteger(String fieldName, IntegerExpression other);
61+
62+
default IntegerExpression getInteger(final String fieldName, final int other) {
63+
return getInteger(fieldName, of(other));
64+
}
65+
66+
default IntegerExpression getInteger(final String fieldName, final long other) {
67+
return getInteger(fieldName, of(other));
68+
}
69+
70+
71+
StringExpression getString(String fieldName);
72+
73+
StringExpression getString(String fieldName, StringExpression other);
74+
75+
default StringExpression getString(final String fieldName, final String other) {
76+
return getString(fieldName, of(other));
77+
}
78+
79+
DateExpression getDate(String fieldName);
80+
DateExpression getDate(String fieldName, DateExpression other);
81+
82+
default DateExpression getDate(final String fieldName, final Instant other) {
83+
return getDate(fieldName, of(other));
84+
}
85+
86+
DocumentExpression getDocument(String fieldName);
87+
DocumentExpression getDocument(String fieldName, DocumentExpression other);
88+
89+
default DocumentExpression getDocument(final String fieldName, final Bson other) {
90+
return getDocument(fieldName, of(other));
91+
}
92+
93+
<T extends Expression> ArrayExpression<T> getArray(String fieldName);
94+
95+
<T extends Expression> ArrayExpression<T> getArray(String fieldName, ArrayExpression<? extends T> other);
96+
97+
DocumentExpression merge(DocumentExpression other);
2598
}

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

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -100,14 +100,16 @@ public interface Expression {
100100

101101
/**
102102
* also checks for nulls
103-
* @param or
103+
* @param other
104104
* @return
105105
*/
106-
BooleanExpression isBooleanOr(BooleanExpression or);
107-
NumberExpression isNumberOr(NumberExpression or);
108-
StringExpression isStringOr(StringExpression or);
109-
DateExpression isDateOr(DateExpression or);
110-
ArrayExpression<Expression> isArrayOr(ArrayExpression<? extends Expression> or);
111-
<T extends DocumentExpression> T isDocumentOr(T or);
106+
BooleanExpression isBooleanOr(BooleanExpression other);
107+
NumberExpression isNumberOr(NumberExpression other);
108+
IntegerExpression isIntegerOr(IntegerExpression other);
109+
StringExpression isStringOr(StringExpression other);
110+
DateExpression isDateOr(DateExpression other);
111+
<T extends Expression> ArrayExpression<T> isArrayOr(ArrayExpression<? extends T> other);
112+
<T extends DocumentExpression> T isDocumentOr(T other);
113+
112114
StringExpression asString();
113115
}

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

Lines changed: 137 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,113 @@ public <R extends Expression> R cond(final R left, final R right) {
137137
return newMqlExpression(ast("$cond", left, right));
138138
}
139139

140+
/** @see DocumentExpression */
141+
142+
private Function<CodecRegistry, AstPlaceholder> getFieldInternal(final String fieldName) {
143+
return (cr) -> {
144+
BsonValue value = fieldName.startsWith("$")
145+
? new BsonDocument("$literal", new BsonString(fieldName))
146+
: new BsonString(fieldName);
147+
return astDoc("$getField", new BsonDocument()
148+
.append("input", this.fn.apply(cr).bsonValue)
149+
.append("field", value));
150+
};
151+
}
152+
153+
@Override
154+
public Expression getField(final String fieldName) {
155+
return new MqlExpression<>(getFieldInternal(fieldName));
156+
}
157+
158+
@Override
159+
public BooleanExpression getBoolean(final String fieldName) {
160+
return new MqlExpression<>(getFieldInternal(fieldName));
161+
}
162+
163+
@Override
164+
public BooleanExpression getBoolean(final String fieldName, final BooleanExpression other) {
165+
return getBoolean(fieldName).isBooleanOr(other);
166+
}
167+
168+
@Override
169+
public NumberExpression getNumber(final String fieldName) {
170+
return new MqlExpression<>(getFieldInternal(fieldName));
171+
}
172+
173+
@Override
174+
public NumberExpression getNumber(final String fieldName, final NumberExpression other) {
175+
return getNumber(fieldName).isNumberOr(other);
176+
}
177+
178+
@Override
179+
public IntegerExpression getInteger(final String fieldName) {
180+
return new MqlExpression<>(getFieldInternal(fieldName));
181+
}
182+
183+
@Override
184+
public IntegerExpression getInteger(final String fieldName, final IntegerExpression other) {
185+
return getInteger(fieldName).isIntegerOr(other);
186+
}
187+
188+
@Override
189+
public StringExpression getString(final String fieldName) {
190+
return new MqlExpression<>(getFieldInternal(fieldName));
191+
}
192+
193+
@Override
194+
public StringExpression getString(final String fieldName, final StringExpression other) {
195+
return getString(fieldName).isStringOr(other);
196+
}
197+
198+
@Override
199+
public DateExpression getDate(final String fieldName) {
200+
return new MqlExpression<>(getFieldInternal(fieldName));
201+
}
202+
203+
@Override
204+
public DateExpression getDate(final String fieldName, final DateExpression other) {
205+
return getDate(fieldName).isDateOr(other);
206+
}
207+
208+
@Override
209+
public DocumentExpression getDocument(final String fieldName) {
210+
return new MqlExpression<>(getFieldInternal(fieldName));
211+
}
212+
213+
@Override
214+
public DocumentExpression getDocument(final String fieldName, final DocumentExpression other) {
215+
return getDocument(fieldName).isDocumentOr(other);
216+
}
217+
218+
@Override
219+
public <R extends Expression> ArrayExpression<R> getArray(final String fieldName) {
220+
return new MqlExpression<>(getFieldInternal(fieldName));
221+
}
222+
223+
@Override
224+
public <R extends Expression> ArrayExpression<R> getArray(final String fieldName, final ArrayExpression<? extends R> other) {
225+
return getArray(fieldName).isArrayOr(other);
226+
}
227+
228+
@Override
229+
public DocumentExpression merge(final DocumentExpression other) {
230+
return new MqlExpression<>(ast("$mergeObjects", other));
231+
}
232+
233+
@Override
234+
public DocumentExpression setField(final String fieldName, final Expression exp) {
235+
return newMqlExpression((cr) -> astDoc("$setField", new BsonDocument()
236+
.append("field", new BsonString(fieldName))
237+
.append("input", this.toBsonValue(cr))
238+
.append("value", extractBsonValue(cr, exp))));
239+
}
240+
241+
@Override
242+
public DocumentExpression unsetField(final String fieldName) {
243+
return newMqlExpression((cr) -> astDoc("$unsetField", new BsonDocument()
244+
.append("field", new BsonString(fieldName))
245+
.append("input", this.toBsonValue(cr))));
246+
}
140247

141248
/** @see Expression */
142249

@@ -175,55 +282,71 @@ public BooleanExpression isBoolean() {
175282
}
176283

177284
@Override
178-
public BooleanExpression isBooleanOr(final BooleanExpression or) {
179-
return this.isBoolean().cond(this, or);
285+
public BooleanExpression isBooleanOr(final BooleanExpression other) {
286+
return this.isBoolean().cond(this, other);
180287
}
181288

182289
public BooleanExpression isNumber() {
183290
return new MqlExpression<>(astWrapped("$isNumber"));
184291
}
185292

186293
@Override
187-
public NumberExpression isNumberOr(final NumberExpression or) {
188-
return this.isNumber().cond(this, or);
294+
public NumberExpression isNumberOr(final NumberExpression other) {
295+
return this.isNumber().cond(this, other);
296+
}
297+
298+
public BooleanExpression isInteger() {
299+
return this.isNumber().cond(this.eq(this.round()), of(false));
300+
}
301+
302+
@Override
303+
public IntegerExpression isIntegerOr(final IntegerExpression other) {
304+
return this.isInteger().cond(this, other);
189305
}
190306

191307
public BooleanExpression isString() {
192308
return new MqlExpression<>(ast("$type")).eq(of("string"));
193309
}
194310

195311
@Override
196-
public StringExpression isStringOr(final StringExpression or) {
197-
return this.isString().cond(this, or);
312+
public StringExpression isStringOr(final StringExpression other) {
313+
return this.isString().cond(this, other);
198314
}
199315

200316
public BooleanExpression isDate() {
201317
return ofStringArray("date").contains(new MqlExpression<>(ast("$type")));
202318
}
203319

204320
@Override
205-
public DateExpression isDateOr(final DateExpression or) {
206-
return this.isDate().cond(this, or);
321+
public DateExpression isDateOr(final DateExpression other) {
322+
return this.isDate().cond(this, other);
207323
}
208324

209325
public BooleanExpression isArray() {
210326
return new MqlExpression<>(astWrapped("$isArray"));
211327
}
212328

213-
@SuppressWarnings("unchecked") // TODO
329+
/**
330+
* checks if array (but cannot check type)
331+
* user asserts array is of type R
332+
*
333+
* @param other
334+
* @return
335+
* @param <R>
336+
*/
337+
@SuppressWarnings("unchecked")
214338
@Override
215-
public ArrayExpression<Expression> isArrayOr(final ArrayExpression<? extends Expression> or) {
216-
// TODO it seems that ArrEx<T> does not make sense here
217-
return (ArrayExpression<Expression>) this.isArray().cond(this.assertImplementsAllExpressions(), or);
339+
public <R extends Expression> ArrayExpression<R> isArrayOr(final ArrayExpression<? extends R> other) {
340+
return (ArrayExpression<R>) this.isArray().cond(this.assertImplementsAllExpressions(), other);
218341
}
219342

220343
public BooleanExpression isDocument() {
221344
return new MqlExpression<>(ast("$type")).eq(of("object"));
222345
}
223346

224347
@Override
225-
public <R extends DocumentExpression> R isDocumentOr(final R or) {
226-
return this.isDocument().cond(this.assertImplementsAllExpressions(), or);
348+
public <R extends DocumentExpression> R isDocumentOr(final R other) {
349+
return this.isDocument().cond(this.assertImplementsAllExpressions(), other);
227350
}
228351

229352
@Override

driver-core/src/test/functional/com/mongodb/client/model/expressions/AbstractExpressionsFunctionalTest.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
import static com.mongodb.ClusterFixture.serverVersionAtLeast;
4141
import static com.mongodb.client.model.Aggregates.addFields;
4242
import static org.bson.codecs.configuration.CodecRegistries.fromProviders;
43+
import static org.bson.conversions.Bson.DEFAULT_CODEC_REGISTRY;
4344
import static org.junit.jupiter.api.Assertions.assertEquals;
4445

4546
public abstract class AbstractExpressionsFunctionalTest extends OperationTest {
@@ -70,7 +71,8 @@ protected void assertExpression(@Nullable final Object expected, final Expressio
7071
return;
7172
}
7273

73-
BsonValue expressionValue = ((MqlExpression<?>) expression).toBsonValue(fromProviders(new BsonValueCodecProvider()));
74+
BsonValue expressionValue = ((MqlExpression<?>) expression).toBsonValue(
75+
fromProviders(new BsonValueCodecProvider(), DEFAULT_CODEC_REGISTRY));
7476
BsonValue bsonValue = new BsonDocumentFragmentCodec().readValue(
7577
new JsonReader(expectedMql),
7678
DecoderContext.builder().build());

driver-core/src/test/functional/com/mongodb/client/model/expressions/ArrayExpressionsFunctionalTest.java

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
package com.mongodb.client.model.expressions;
1818

1919
import com.mongodb.MongoCommandException;
20+
import org.bson.Document;
2021
import org.bson.types.Decimal128;
2122
import org.junit.jupiter.api.Test;
2223

@@ -79,13 +80,21 @@ public void literalsTest() {
7980
Arrays.asList(Instant.parse("2007-12-03T10:15:30.00Z")),
8081
ofDateArray(Instant.parse("2007-12-03T10:15:30.00Z")),
8182
"[{'$date': '2007-12-03T10:15:30.00Z'}]");
83+
8284
// Document
83-
// ...
85+
ArrayExpression<DocumentExpression> documentArray = ofArray(
86+
of(Document.parse("{a: 1}")),
87+
of(Document.parse("{b: 2}")));
88+
assertExpression(
89+
Arrays.asList(Document.parse("{a: 1}"), Document.parse("{b: 2}")),
90+
documentArray,
91+
"[{'$literal': {'a': 1}}, {'$literal': {'b': 2}}]");
8492

8593
// Array
86-
ArrayExpression<ArrayExpression<Expression>> arrays = ofArray(ofArray(), ofArray());
94+
ArrayExpression<ArrayExpression<Expression>> arrayArray = ofArray(ofArray(), ofArray());
8795
assertExpression(
88-
Arrays.asList(Collections.emptyList(), Collections.emptyList()), arrays,
96+
Arrays.asList(Collections.emptyList(), Collections.emptyList()),
97+
arrayArray,
8998
"[[], []]");
9099

91100
// Mixed
@@ -176,9 +185,10 @@ public void elementAtTest() {
176185
assertExpression(
177186
Arrays.asList(1, 2, 3).get(0),
178187
// 0.0 is a valid integer value
179-
array123.elementAt((IntegerExpression) of(0.0)),
188+
array123.elementAt(of(0.0).isIntegerOr(of(-1))),
180189
// MQL:
181-
"{'$arrayElemAt': [[1, 2, 3], 0.0]}");
190+
"{'$arrayElemAt': [[1, 2, 3], {'$cond': [{'$cond': "
191+
+ "[{'$isNumber': [0.0]}, {'$eq': [0.0, {'$round': 0.0}]}, false]}, 0.0, -1]}]}");
182192
// negatives
183193
assertExpression(
184194
Arrays.asList(1, 2, 3).get(3 - 1),

driver-core/src/test/functional/com/mongodb/client/model/expressions/ComparisonExpressionsFunctionalTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@
3636
class ComparisonExpressionsFunctionalTest extends AbstractExpressionsFunctionalTest {
3737
// https://www.mongodb.com/docs/manual/reference/operator/aggregation/#comparison-expression-operators
3838
// (Complete as of 6.0)
39-
// Comparison expressions are part of the the generic Expression class.
39+
// Comparison expressions are part of the generic Expression class.
4040

4141
// https://www.mongodb.com/docs/manual/reference/bson-type-comparison-order/#std-label-bson-types-comparison-order
4242
private final List<Expression> sampleValues = Arrays.asList(

0 commit comments

Comments
 (0)