Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -77,15 +77,15 @@ default T elementAt(final int i) {

BooleanExpression contains(T contains);

ArrayExpression<T> concat(ArrayExpression<T> array);
ArrayExpression<T> concat(ArrayExpression<? extends T> array);

ArrayExpression<T> slice(IntegerExpression start, IntegerExpression length);

default ArrayExpression<T> slice(final int start, final int length) {
return this.slice(of(start), of(length));
}

ArrayExpression<T> union(ArrayExpression<T> set);
ArrayExpression<T> union(ArrayExpression<? extends T> set);

ArrayExpression<T> distinct();
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ public interface DateExpression extends Expression {
IntegerExpression week(StringExpression timezone);
IntegerExpression millisecond(StringExpression timezone);

StringExpression dateToString();
StringExpression dateToString(StringExpression timezone, StringExpression format);
StringExpression asString(StringExpression timezone, StringExpression format);

}
Original file line number Diff line number Diff line change
Expand Up @@ -97,4 +97,17 @@ public interface Expression {
* @return true if less than or equal to, false otherwise
*/
BooleanExpression lte(Expression lte);

/**
* also checks for nulls
* @param or
* @return
*/
BooleanExpression isBooleanOr(BooleanExpression or);
NumberExpression isNumberOr(NumberExpression or);
StringExpression isStringOr(StringExpression or);
DateExpression isDateOr(DateExpression or);
ArrayExpression<Expression> isArrayOr(ArrayExpression<? extends Expression> or);
<T extends DocumentExpression> T isDocumentOr(T or);
StringExpression asString();
}
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,7 @@ public static <T extends Expression> ArrayExpression<T> ofArray(final T... array
});
}

public static DocumentExpression ofDocument(final Bson document) {
public static DocumentExpression of(final Bson document) {
Assertions.notNull("document", document);
// All documents are wrapped in a $literal. If we don't wrap, we need to
// check for empty documents and documents that are actually expressions
Expand All @@ -193,7 +193,9 @@ public static DocumentExpression ofDocument(final Bson document) {
document.toBsonDocument(BsonDocument.class, cr))));
}

public static <R extends Expression> R ofNull() {
public static Expression ofNull() {
// There is no specific expression type corresponding to Null,
// and Null is not a value in any other expression type.
return new MqlExpression<>((cr) -> new AstPlaceholder(new BsonNull()))
.assertImplementsAllExpressions();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@
import java.util.function.BinaryOperator;
import java.util.function.Function;

import static com.mongodb.client.model.expressions.Expressions.of;
import static com.mongodb.client.model.expressions.Expressions.ofStringArray;

final class MqlExpression<T extends Expression>
implements Expression, BooleanExpression, IntegerExpression, NumberExpression,
StringExpression, DateExpression, DocumentExpression, ArrayExpression<T> {
Expand Down Expand Up @@ -61,6 +64,12 @@ private Function<CodecRegistry, AstPlaceholder> ast(final String name) {
return (cr) -> new AstPlaceholder(new BsonDocument(name, this.toBsonValue(cr)));
}

// in cases where we must wrap the first argument in an array
private Function<CodecRegistry, AstPlaceholder> astWrapped(final String name) {
return (cr) -> new AstPlaceholder(new BsonDocument(name,
new BsonArray(Collections.singletonList(this.toBsonValue(cr)))));
}

private Function<CodecRegistry, AstPlaceholder> ast(final String name, final Expression param1) {
return (cr) -> {
BsonArray value = new BsonArray();
Expand Down Expand Up @@ -161,6 +170,80 @@ public BooleanExpression lte(final Expression lte) {
return new MqlExpression<>(ast("$lte", lte));
}

public BooleanExpression isBoolean() {
return new MqlExpression<>(ast("$type")).eq(of("bool"));
}

@Override
public BooleanExpression isBooleanOr(final BooleanExpression or) {
return this.isBoolean().cond(this, or);
}

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

@Override
public NumberExpression isNumberOr(final NumberExpression or) {
return this.isNumber().cond(this, or);
}

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

@Override
public StringExpression isStringOr(final StringExpression or) {
return this.isString().cond(this, or);
}

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

@Override
public DateExpression isDateOr(final DateExpression or) {
return this.isDate().cond(this, or);
}

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

@SuppressWarnings("unchecked") // TODO
@Override
public ArrayExpression<Expression> isArrayOr(final ArrayExpression<? extends Expression> or) {
// TODO it seems that ArrEx<T> does not make sense here
return (ArrayExpression<Expression>) this.isArray().cond(this.assertImplementsAllExpressions(), or);
}

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

@Override
public <R extends DocumentExpression> R isDocumentOr(final R or) {
return this.isDocument().cond(this.assertImplementsAllExpressions(), or);
}

@Override
public StringExpression asString() {
return new MqlExpression<>(astWrapped("$toString"));
}

private Function<CodecRegistry, AstPlaceholder> convertInternal(final String to, final Expression orElse) {
return (cr) -> astDoc("$convert", new BsonDocument()
.append("input", this.fn.apply(cr).bsonValue)
.append("onError", extractBsonValue(cr, orElse))
.append("to", new BsonString(to)));
}

@Override
public IntegerExpression parseInteger() {
Expression asLong = new MqlExpression<>(ast("$toLong"));
return new MqlExpression<>(convertInternal("int", asLong));
}

/** @see ArrayExpression */

@Override
Expand Down Expand Up @@ -191,10 +274,7 @@ public T reduce(final T initialValue, final BinaryOperator<T> in) {

@Override
public IntegerExpression size() {
return new MqlExpression<>(
(cr) -> new AstPlaceholder(new BsonDocument("$size",
// must wrap the first argument in a list
new BsonArray(Collections.singletonList(this.toBsonValue(cr))))));
return new MqlExpression<>(astWrapped("$size"));
}

@Override
Expand All @@ -205,19 +285,13 @@ public T elementAt(final IntegerExpression at) {

@Override
public T first() {
return new MqlExpression<>(
(cr) -> new AstPlaceholder(new BsonDocument("$first",
// must wrap the first argument in a list
new BsonArray(Collections.singletonList(this.toBsonValue(cr))))))
return new MqlExpression<>(astWrapped("$first"))
.assertImplementsAllExpressions();
}

@Override
public T last() {
return new MqlExpression<>(
(cr) -> new AstPlaceholder(new BsonDocument("$last",
// must wrap the first argument in a list
new BsonArray(Collections.singletonList(this.toBsonValue(cr))))))
return new MqlExpression<>(astWrapped("$last"))
.assertImplementsAllExpressions();
}

Expand All @@ -233,7 +307,7 @@ public BooleanExpression contains(final T item) {
}

@Override
public ArrayExpression<T> concat(final ArrayExpression<T> array) {
public ArrayExpression<T> concat(final ArrayExpression<? extends T> array) {
return new MqlExpression<>(ast("$concatArrays", array))
.assertImplementsAllExpressions();
}
Expand All @@ -245,17 +319,14 @@ public ArrayExpression<T> slice(final IntegerExpression start, final IntegerExpr
}

@Override
public ArrayExpression<T> union(final ArrayExpression<T> set) {
public ArrayExpression<T> union(final ArrayExpression<? extends T> set) {
return new MqlExpression<>(ast("$setUnion", set))
.assertImplementsAllExpressions();
}

@Override
public ArrayExpression<T> distinct() {
return new MqlExpression<>(
(cr) -> new AstPlaceholder(new BsonDocument("$setUnion",
// must wrap the first argument in a list
new BsonArray(Collections.singletonList(this.toBsonValue(cr))))));
return new MqlExpression<>(astWrapped("$setUnion"));
}


Expand Down Expand Up @@ -307,6 +378,11 @@ public IntegerExpression abs() {
return newMqlExpression(ast("$abs"));
}

@Override
public DateExpression millisecondsToDate() {
return newMqlExpression(ast("$toDate"));
}

@Override
public NumberExpression subtract(final NumberExpression n) {
return new MqlExpression<>(ast("$subtract", n));
Expand Down Expand Up @@ -391,19 +467,34 @@ public IntegerExpression millisecond(final StringExpression timezone) {
}

@Override
public StringExpression dateToString() {
public StringExpression asString(final StringExpression timezone, final StringExpression format) {
return newMqlExpression((cr) -> astDoc("$dateToString", new BsonDocument()
.append("date", this.toBsonValue(cr))));
.append("date", this.toBsonValue(cr))
.append("format", extractBsonValue(cr, format))
.append("timezone", extractBsonValue(cr, timezone))));
}

@Override
public StringExpression dateToString(final StringExpression timezone, final StringExpression format) {
return newMqlExpression((cr) -> astDoc("$dateToString", new BsonDocument()
.append("date", this.toBsonValue(cr))
public DateExpression parseDate(final StringExpression timezone, final StringExpression format) {
return newMqlExpression((cr) -> astDoc("$dateFromString", new BsonDocument()
.append("dateString", this.toBsonValue(cr))
.append("format", extractBsonValue(cr, format))
.append("timezone", extractBsonValue(cr, timezone))));
}

@Override
public DateExpression parseDate(final StringExpression format) {
return newMqlExpression((cr) -> astDoc("$dateFromString", new BsonDocument()
.append("dateString", this.toBsonValue(cr))
.append("format", extractBsonValue(cr, format))));
}

@Override
public DateExpression parseDate() {
return newMqlExpression((cr) -> astDoc("$dateFromString", new BsonDocument()
.append("dateString", this.toBsonValue(cr))));
}

/** @see StringExpression */

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,4 +54,6 @@ default NumberExpression subtract(final Number subtract) {
NumberExpression round(IntegerExpression place);

NumberExpression abs();

DateExpression millisecondsToDate();
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,21 @@ public interface StringExpression extends Expression {

StringExpression substr(IntegerExpression start, IntegerExpression length);

default StringExpression substr(int start, int length) {
default StringExpression substr(final int start, final int length) {
return this.substr(of(start), of(length));
}

StringExpression substrBytes(IntegerExpression start, IntegerExpression length);

default StringExpression substrBytes(int start, int length) {
default StringExpression substrBytes(final int start, final int length) {
return this.substrBytes(of(start), of(length));
}

IntegerExpression parseInteger();

DateExpression parseDate();

DateExpression parseDate(StringExpression format);

DateExpression parseDate(StringExpression timezone, StringExpression format);
}
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,11 @@

public abstract class AbstractExpressionsFunctionalTest extends OperationTest {

/**
* Java stand-in for the "missing" value.
*/
public static final Object MISSING = new Object();

@BeforeEach
public void setUp() {
getCollectionHelper().drop();
Expand All @@ -54,7 +59,7 @@ public void tearDown() {
getCollectionHelper().drop();
}

protected void assertExpression(final Object expected, final Expression expression) {
protected void assertExpression(@Nullable final Object expected, final Expression expression) {
assertExpression(expected, expression, null);
}

Expand All @@ -74,6 +79,10 @@ protected void assertExpression(@Nullable final Object expected, final Expressio

private void assertEval(@Nullable final Object expected, final Expression toEvaluate) {
BsonValue evaluated = evaluate(toEvaluate);
if (expected == MISSING && evaluated == null) {
// if the "val" field was removed by "missing", then evaluated is null
return;
}
BsonValue expected1 = toBsonValue(expected);
assertEquals(expected1, evaluated);
}
Expand All @@ -85,6 +94,7 @@ protected BsonValue toBsonValue(@Nullable final Object value) {
return new Document("val", value).toBsonDocument().get("val");
}

@Nullable
protected BsonValue evaluate(final Expression toEvaluate) {
Bson addFieldsStage = addFields(new Field<>("val", toEvaluate));
List<Bson> stages = new ArrayList<>();
Expand Down
Loading