Skip to content

Implement eq, ne, gt, gte, lt, lte #1033

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Nov 10, 2022
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 @@ -44,4 +44,57 @@
@Evolving
public interface Expression {

/**
* Returns logical true if the value of this expression is equal to the
* value of the other expression. Otherwise, false.
*
* @param eq the other expression
* @return true if equal, false if not equal
*/
BooleanExpression eq(Expression eq);

/**
* Returns logical true if the value of this expression is not equal to the
* value of the other expression. Otherwise, false.
*
* @param ne the other expression
* @return true if equal, false otherwise
*/
BooleanExpression ne(Expression ne);

/**
* Returns logical true if the value of this expression is greater than the
* value of the other expression. Otherwise, false.
*
* @param gt the other expression
* @return true if greater than, false otherwise
*/
BooleanExpression gt(Expression gt);

/**
* Returns logical true if the value of this expression is greater than or
* equal to the value of the other expression. Otherwise, false.
*
* @param gte the other expression
* @return true if greater than or equal to, false otherwise
*/
BooleanExpression gte(Expression gte);

/**
* Returns logical true if the value of this expression is less than the
* value of the other expression. Otherwise, false.
*
* @param lt the other expression
* @return true if less than, false otherwise
*/
BooleanExpression lt(Expression lt);

/**
* Returns logical true if the value of this expression is less than or
* equal to the value of the other expression. Otherwise, false.
*
* @param lte the other expression
* @return true if less than or equal to, false otherwise
*/
BooleanExpression lte(Expression lte);
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,26 @@

package com.mongodb.client.model.expressions;

import com.mongodb.assertions.Assertions;
import org.bson.BsonArray;
import org.bson.BsonBoolean;
import org.bson.BsonDateTime;
import org.bson.BsonDocument;
import org.bson.BsonDouble;
import org.bson.BsonInt32;
import org.bson.BsonInt64;
import org.bson.BsonNull;
import org.bson.BsonString;
import org.bson.BsonValue;
import org.bson.conversions.Bson;

import java.time.Instant;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.stream.Collectors;


/**
* Convenience methods related to {@link Expression}.
Expand Down Expand Up @@ -54,6 +66,15 @@ public static BooleanExpression of(final boolean of) {
public static IntegerExpression of(final int of) {
return new MqlExpression<>((codecRegistry) -> new BsonInt32(of));
}
public static IntegerExpression of(final long of) {
return new MqlExpression<>((codecRegistry) -> new BsonInt64(of));
}
public static NumberExpression of(final double of) {
return new MqlExpression<>((codecRegistry) -> new BsonDouble(of));
}
public static DateExpression of(final Instant of) {
return new MqlExpression<>((codecRegistry) -> new BsonDateTime(of.toEpochMilli()));
}

/**
* Returns an expression having the same string value as the provided
Expand Down Expand Up @@ -81,4 +102,24 @@ public static ArrayExpression<BooleanExpression> ofBooleanArray(final boolean...
return new MqlExpression<>((cr) -> new BsonArray(result));
}


public static ArrayExpression<IntegerExpression> ofIntegerArray(final int... ofIntegerArray) {
List<BsonValue> array = Arrays.stream(ofIntegerArray)
.mapToObj(BsonInt32::new)
.collect(Collectors.toList());
return new MqlExpression<>((cr) -> new BsonArray(array));
}

public static DocumentExpression ofDocument(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
// (and need to be wrapped in $literal anyway). This would be brittle.
return new MqlExpression<>((cr) -> new BsonDocument("$literal",
document.toBsonDocument(BsonDocument.class, cr)));
}

public static <R extends Expression> R ofNull() {
return new MqlExpression<>((cr) -> new BsonNull()).assertImplementsAllExpressions();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ private static BsonValue extractBsonValue(final CodecRegistry cr, final Expressi
* extend Expression or its subtypes, so MqlExpression will implement any R.
*/
@SuppressWarnings("unchecked")
private <R extends Expression> R assertImplementsAllExpressions() {
<R extends Expression> R assertImplementsAllExpressions() {
return (R) this;
}

Expand Down Expand Up @@ -120,6 +120,38 @@ public <R extends Expression> R cond(final R left, final R right) {
}


/** @see Expression */

@Override
public BooleanExpression eq(final Expression eq) {
return new MqlExpression<>(ast("$eq", eq));
}

@Override
public BooleanExpression ne(final Expression ne) {
return new MqlExpression<>(ast("$ne", ne));
}

@Override
public BooleanExpression gt(final Expression gt) {
return new MqlExpression<>(ast("$gt", gt));
}

@Override
public BooleanExpression gte(final Expression gte) {
return new MqlExpression<>(ast("$gte", gte));
}

@Override
public BooleanExpression lt(final Expression lt) {
return new MqlExpression<>(ast("$lt", lt));
}

@Override
public BooleanExpression lte(final Expression lte) {
return new MqlExpression<>(ast("$lte", lte));
}

/** @see ArrayExpression */

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
/*
* Copyright 2008-present MongoDB, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.mongodb.client.model.expressions;

import org.bson.BsonDocument;
import org.bson.BsonString;
import org.bson.BsonValue;
import org.bson.codecs.BsonValueCodecProvider;
import org.junit.jupiter.api.Test;

import java.time.Instant;
import java.util.Arrays;
import java.util.Date;
import java.util.List;

import static com.mongodb.client.model.expressions.Expressions.of;
import static com.mongodb.client.model.expressions.Expressions.ofBooleanArray;
import static com.mongodb.client.model.expressions.Expressions.ofDocument;
import static com.mongodb.client.model.expressions.Expressions.ofIntegerArray;
import static com.mongodb.client.model.expressions.Expressions.ofNull;
import static org.bson.codecs.configuration.CodecRegistries.fromProviders;
import static org.junit.jupiter.api.Assertions.fail;

@SuppressWarnings({"ConstantConditions"})
class ComparisonExpressionsFunctionalTest extends AbstractExpressionsFunctionalTest {
// https://www.mongodb.com/docs/manual/reference/operator/aggregation/#comparison-expression-operators
// (Complete as of 6.0)
// Comparison expressions are part of the the generic Expression class.

static <R extends Expression> R ofRem() {
// $$REMOVE is intentionally not exposed to users
return new MqlExpression<>((cr) -> new BsonString("$$REMOVE")).assertImplementsAllExpressions();
}

// https://www.mongodb.com/docs/manual/reference/bson-type-comparison-order/#std-label-bson-types-comparison-order
private final List<Expression> sampleValues = Arrays.asList(
ofRem(),
ofNull(),
of(0),
of(1),
of(""),
of("str"),
ofDocument(BsonDocument.parse("{}")),
ofDocument(BsonDocument.parse("{a: 1}")),
ofDocument(BsonDocument.parse("{a: 2}")),
ofDocument(BsonDocument.parse("{a: 2, b: 1}")),
ofDocument(BsonDocument.parse("{b: 1, a: 2}")),
ofDocument(BsonDocument.parse("{'':''}")),
ofIntegerArray(0),
ofIntegerArray(1),
ofBooleanArray(true),
of(false),
of(true),
of(Instant.now())
);

@Test
public void eqTest() {
// https://www.mongodb.com/docs/manual/reference/operator/aggregation/eq/
assertExpression(
1 == 2,
of(1).eq(of(2)),
"{'$eq': [1, 2]}");
assertExpression(
false,
ofDocument(BsonDocument.parse("{}")).eq(ofIntegerArray()),
"{'$eq': [{'$literal': {}}, []]}");

// numbers are equal, even though of different types
assertExpression(
1 == 1.0,
of(1).eq(of(1.0)),
"{'$eq': [1, 1.0]}");
assertExpression(
1 == 1L,
of(1).eq(of(1L)),
"{'$eq': [1, { '$numberLong': '1' }]}");

// ensure that no two samples are equal to each other
for (int i = 0; i < sampleValues.size(); i++) {
for (int j = 0; j < sampleValues.size(); j++) {
if (i == j) {
continue;
}
Expression first = sampleValues.get(i);
Expression second = sampleValues.get(j);
BsonValue evaluate = evaluate(first.eq(second));
if (evaluate.asBoolean().getValue()) {
BsonValue v1 = ((MqlExpression<?>) first).toBsonValue(fromProviders(new BsonValueCodecProvider()));
BsonValue v2 = ((MqlExpression<?>) second).toBsonValue(fromProviders(new BsonValueCodecProvider()));
fail(i + "," + j + " --" + v1 + " and " + v2 + " should not equal");
}
}
}
}

@Test
public void neTest() {
// https://www.mongodb.com/docs/manual/reference/operator/aggregation/ne/
assertExpression(
1 != 2,
of(1).ne(of(2)),
"{'$ne': [1, 2]}");
}

@Test
public void ltTest() {
// https://www.mongodb.com/docs/manual/reference/operator/aggregation/lt/
assertExpression(
-1 < 1,
of(-1).lt(of(1)),
"{'$lt': [-1, 1]}");
assertExpression(
0 < 0,
of(0).lt(of(0)),
"{'$lt': [0, 0]}");

assertExpression(
true,
ofNull().lt(of(0)),
"{'$lt': [null, 0]}");

for (int i = 0; i < sampleValues.size() - 1; i++) {
for (int j = i + 1; j < sampleValues.size(); j++) {
Expression first = sampleValues.get(i);
Expression second = sampleValues.get(j);
BsonValue evaluate = evaluate(first.lt(second));
if (!evaluate.asBoolean().getValue()) {
BsonValue v1 = ((MqlExpression<?>) first).toBsonValue(fromProviders(new BsonValueCodecProvider()));
BsonValue v2 = ((MqlExpression<?>) second).toBsonValue(fromProviders(new BsonValueCodecProvider()));
fail(i + "," + j + " --" + v1 + " < " + v2 + " should be true");
}
}
}
}

@Test
public void lteTest() {
// https://www.mongodb.com/docs/manual/reference/operator/aggregation/lte/
assertExpression(
-1 <= 1,
of(-1).lte(of(1)),
"{'$lte': [-1, 1]}");
assertExpression(
0 <= 0,
of(0).lte(of(0)),
"{'$lte': [0, 0]}");
}

@Test
public void gtTest() {
// https://www.mongodb.com/docs/manual/reference/operator/aggregation/gt/
assertExpression(
-1 > 1,
of(-1).gt(of(1)),
"{'$gt': [-1, 1]}");
assertExpression(
0 > 0,
of(0).gt(of(0)),
"{'$gt': [0, 0]}");
}

@Test
public void gteTest() {
// https://www.mongodb.com/docs/manual/reference/operator/aggregation/gte/
assertExpression(
-1 >= 1,
of(-1).gte(of(1)),
"{'$gte': [-1, 1]}");
assertExpression(
0 >= 0,
of(0).gte(of(0)),
"{'$gte': [0, 0]}");
}

}