diff --git a/config/checkstyle/suppressions.xml b/config/checkstyle/suppressions.xml index 2f6bbaa20a8..370a169aa28 100644 --- a/config/checkstyle/suppressions.xml +++ b/config/checkstyle/suppressions.xml @@ -156,4 +156,6 @@ + + diff --git a/driver-core/src/main/com/mongodb/MongoClientSettings.java b/driver-core/src/main/com/mongodb/MongoClientSettings.java index 9010e1cb35a..1cd4792304b 100644 --- a/driver-core/src/main/com/mongodb/MongoClientSettings.java +++ b/driver-core/src/main/com/mongodb/MongoClientSettings.java @@ -19,6 +19,7 @@ import com.mongodb.annotations.Immutable; import com.mongodb.annotations.NotThreadSafe; import com.mongodb.client.gridfs.codecs.GridFSFileCodecProvider; +import com.mongodb.client.model.expressions.ExpressionCodecProvider; import com.mongodb.client.model.geojson.codecs.GeoJsonCodecProvider; import com.mongodb.connection.ClusterSettings; import com.mongodb.connection.ConnectionPoolSettings; @@ -74,6 +75,7 @@ public final class MongoClientSettings { new JsonObjectCodecProvider(), new BsonCodecProvider(), new EnumCodecProvider(), + new ExpressionCodecProvider(), new Jep395RecordCodecProvider())); private final ReadPreference readPreference; @@ -120,6 +122,7 @@ public final class MongoClientSettings { *
  • {@link org.bson.codecs.JsonObjectCodecProvider}
  • *
  • {@link org.bson.codecs.BsonCodecProvider}
  • *
  • {@link org.bson.codecs.EnumCodecProvider}
  • + *
  • {@link ExpressionCodecProvider}
  • *
  • {@link com.mongodb.Jep395RecordCodecProvider}
  • * * diff --git a/driver-core/src/main/com/mongodb/client/model/expressions/ArrayExpression.java b/driver-core/src/main/com/mongodb/client/model/expressions/ArrayExpression.java new file mode 100644 index 00000000000..27acf24c7eb --- /dev/null +++ b/driver-core/src/main/com/mongodb/client/model/expressions/ArrayExpression.java @@ -0,0 +1,27 @@ +/* + * 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; + +/** + * Expresses an array value. An array value is a finite, ordered collection of + * elements of a certain type. + * + * @param the type of the elements in the array + */ +public interface ArrayExpression extends Expression { + +} diff --git a/driver-core/src/main/com/mongodb/client/model/expressions/BooleanExpression.java b/driver-core/src/main/com/mongodb/client/model/expressions/BooleanExpression.java new file mode 100644 index 00000000000..fcf0eca939e --- /dev/null +++ b/driver-core/src/main/com/mongodb/client/model/expressions/BooleanExpression.java @@ -0,0 +1,61 @@ +/* + * 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; + +/** + * Expresses a boolean value. + */ +public interface BooleanExpression extends Expression { + + /** + * Returns logical true if this expression evaluates to logical false. + * Returns logical false if this expression evaluates to logical true. + * + * @return True if false; false if true. + */ + BooleanExpression not(); + + /** + * Returns logical true if this or the other expression evaluates to logical + * true. Returns logical false if both evaluate to logical false. + * + * @param or the other boolean expression. + * @return True if either true, false if both false. + */ + BooleanExpression or(BooleanExpression or); + + /** + * Returns logical true if both this and the other expression evaluate to + * logical true. Returns logical false if either evaluate to logical false. + * + * @param and the other boolean expression. + * @return true if both true, false if either false. + */ + BooleanExpression and(BooleanExpression and); + + /** + * If this expression evaluates to logical true, returns the result of the + * evaluated left branch expression. If this evaluates to logical false, + * returns the result of the evaluated right branch expression. + * + * @param left the left branch expression + * @param right the right branch expression + * @return left if true, right if false. + * @param The type of the resulting expression. + */ + T cond(T left, T right); +} diff --git a/driver-core/src/main/com/mongodb/client/model/expressions/DateExpression.java b/driver-core/src/main/com/mongodb/client/model/expressions/DateExpression.java new file mode 100644 index 00000000000..98e8f005769 --- /dev/null +++ b/driver-core/src/main/com/mongodb/client/model/expressions/DateExpression.java @@ -0,0 +1,24 @@ +/* + * 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; + +/** + * Expresses a date value. + */ +public interface DateExpression extends Expression { + +} diff --git a/driver-core/src/main/com/mongodb/client/model/expressions/DocumentExpression.java b/driver-core/src/main/com/mongodb/client/model/expressions/DocumentExpression.java new file mode 100644 index 00000000000..cf1522d1623 --- /dev/null +++ b/driver-core/src/main/com/mongodb/client/model/expressions/DocumentExpression.java @@ -0,0 +1,25 @@ +/* + * 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; + +/** + * Expresses a document value. A document is an ordered set of fields, where the + * key is a string value, mapping to a value of any other expression type. + */ +public interface DocumentExpression extends Expression { + +} diff --git a/driver-core/src/main/com/mongodb/client/model/expressions/Expression.java b/driver-core/src/main/com/mongodb/client/model/expressions/Expression.java new file mode 100644 index 00000000000..987ccdd4bfa --- /dev/null +++ b/driver-core/src/main/com/mongodb/client/model/expressions/Expression.java @@ -0,0 +1,47 @@ +/* + * 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 com.mongodb.annotations.Evolving; + +/** + * Expressions express values that may be represented in (or computations that + * may be performed within) a MongoDB server. Each expression evaluates to some + * value, much like any Java expression evaluates to some value. Expressions may + * be thought of as boxed values. Evaluation of an expression will usually occur + * on a MongoDB server. + * + *

    Users should treat these interfaces as sealed, and must not implement any + * expression interfaces. + * + *

    Expressions are typed. It is possible to execute expressions against data + * that is of the wrong type, such as by applying the "not" boolean expression + * to a document field that is an integer, null, or missing. This API does not + * define the output in such cases (though the output may be defined within the + * execution context - the server - where the expression is evaluated). Users of + * this API must mitigate any risk of applying an expression to some type where + * resulting behaviour is not defined by this API (for example, by checking for + * null, by ensuring that field types are correctly specified). Likewise, unless + * otherwise specified, this API does not define the order of evaluation for all + * arguments, and whether all arguments to some expression will be evaluated. + * + * @see Expressions + */ +@Evolving +public interface Expression { + +} diff --git a/driver-core/src/main/com/mongodb/client/model/expressions/ExpressionCodecProvider.java b/driver-core/src/main/com/mongodb/client/model/expressions/ExpressionCodecProvider.java new file mode 100644 index 00000000000..60afc3a6874 --- /dev/null +++ b/driver-core/src/main/com/mongodb/client/model/expressions/ExpressionCodecProvider.java @@ -0,0 +1,53 @@ +/* + * 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 com.mongodb.annotations.Beta; +import com.mongodb.annotations.Immutable; +import com.mongodb.lang.Nullable; +import org.bson.codecs.Codec; +import org.bson.codecs.configuration.CodecProvider; +import org.bson.codecs.configuration.CodecRegistry; + +/** + * Provides Codec instances for MQL expressions. + * + *

    Responsible for converting values and computations expressed using the + * driver's implementation of the {@link Expression} API into the corresponding + * values and computations expressed in MQL BSON. Booleans are converted to BSON + * booleans, documents to BSON documents, and so on. The specific structure + * representing numbers is preserved where possible (that is, number literals + * specified as Java longs are converted into BSON int64, and so on). + * + *

    This API is marked Beta because it may be replaced with a generalized + * mechanism for converting expressions. This would only affect users who use + * MqlExpressionCodecProvider directly in custom codec providers. This Beta + * annotation does not imply that the Expressions API in general is Beta. + */ +@Beta(Beta.Reason.CLIENT) +@Immutable +public final class ExpressionCodecProvider implements CodecProvider { + @Override + @SuppressWarnings("unchecked") + @Nullable + public Codec get(final Class clazz, final CodecRegistry registry) { + if (MqlExpression.class.equals(clazz)) { + return (Codec) new MqlExpressionCodec(registry); + } + return null; + } +} diff --git a/driver-core/src/main/com/mongodb/client/model/expressions/Expressions.java b/driver-core/src/main/com/mongodb/client/model/expressions/Expressions.java new file mode 100644 index 00000000000..de96a8f7994 --- /dev/null +++ b/driver-core/src/main/com/mongodb/client/model/expressions/Expressions.java @@ -0,0 +1,63 @@ +/* + * 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.BsonBoolean; +import org.bson.BsonInt32; +import org.bson.BsonString; + +/** + * Convenience methods related to {@link Expression}. + */ +public final class Expressions { + + private Expressions() {} + + /** + * Returns an expression having the same boolean value as the provided + * boolean primitive. + * + * @param of the boolean primitive + * @return the boolean expression + */ + public static BooleanExpression of(final boolean of) { + // we intentionally disallow ofBoolean(null) + return new MqlExpression<>((codecRegistry) -> new BsonBoolean(of)); + } + + /** + * Returns an expression having the same integer value as the provided + * int primitive. + * + * @param of the int primitive + * @return the integer expression + */ + public static IntegerExpression of(final int of) { + return new MqlExpression<>((codecRegistry) -> new BsonInt32(of)); + } + + /** + * Returns an expression having the same string value as the provided + * string. + * + * @param of the string + * @return the string expression + */ + public static StringExpression of(final String of) { + return new MqlExpression<>((codecRegistry) -> new BsonString(of)); + } +} diff --git a/driver-core/src/main/com/mongodb/client/model/expressions/IntegerExpression.java b/driver-core/src/main/com/mongodb/client/model/expressions/IntegerExpression.java new file mode 100644 index 00000000000..6b53b9d62b8 --- /dev/null +++ b/driver-core/src/main/com/mongodb/client/model/expressions/IntegerExpression.java @@ -0,0 +1,24 @@ +/* + * 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; + +/** + * Expresses an integer value. + */ +public interface IntegerExpression extends NumberExpression { + +} diff --git a/driver-core/src/main/com/mongodb/client/model/expressions/MqlExpression.java b/driver-core/src/main/com/mongodb/client/model/expressions/MqlExpression.java new file mode 100644 index 00000000000..d52926338e5 --- /dev/null +++ b/driver-core/src/main/com/mongodb/client/model/expressions/MqlExpression.java @@ -0,0 +1,121 @@ +/* + * 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.BsonArray; +import org.bson.BsonDocument; +import org.bson.BsonString; +import org.bson.BsonValue; +import org.bson.codecs.configuration.CodecRegistry; + +import java.util.function.Function; + +final class MqlExpression + implements Expression, BooleanExpression, IntegerExpression, NumberExpression, + StringExpression, DateExpression, DocumentExpression, ArrayExpression { + + private final Function fn; + + MqlExpression(final Function fn) { + this.fn = fn; + } + + /** + * Exposes the evaluated BsonValue so that expressions may be used in + * aggregations. Non-public, as it is intended to be used only by the + * {@link MqlExpressionCodec}. + */ + BsonValue toBsonValue(final CodecRegistry codecRegistry) { + return fn.apply(codecRegistry); + } + + private Function astDoc(final String name, final BsonDocument value) { + return (cr) -> new BsonDocument(name, value); + } + + private Function ast(final String name) { + return (cr) -> new BsonDocument(name, this.toBsonValue(cr)); + } + + private Function ast(final String name, final Expression param1) { + return (cr) -> { + BsonArray value = new BsonArray(); + value.add(this.toBsonValue(cr)); + value.add(extractBsonValue(cr, param1)); + return new BsonDocument(name, value); + }; + } + + private Function ast(final String name, final Expression param1, final Expression param2) { + return (cr) -> { + BsonArray value = new BsonArray(); + value.add(this.toBsonValue(cr)); + value.add(extractBsonValue(cr, param1)); + value.add(extractBsonValue(cr, param2)); + return new BsonDocument(name, value); + }; + } + + /** + * Takes an expression and converts it to a BsonValue. MqlExpression will be + * the only implementation of Expression and all subclasses, so this will + * not mis-cast an expression as anything else. + */ + private static BsonValue extractBsonValue(final CodecRegistry cr, final Expression expression) { + return ((MqlExpression) expression).toBsonValue(cr); + } + + /** + * Converts an MqlExpression to any subtype of Expression. Users must not + * extend Expression or its subtypes, so MqlExpression will implement any R. + */ + @SuppressWarnings("unchecked") + private R assertImplementsAllExpressions() { + return (R) this; + } + + private static R newMqlExpression(final Function ast) { + return new MqlExpression<>(ast).assertImplementsAllExpressions(); + } + + private R variable(final String variable) { + return newMqlExpression((cr) -> new BsonString(variable)); + } + + /** @see BooleanExpression */ + + @Override + public BooleanExpression not() { + return new MqlExpression<>(ast("$not")); + } + + @Override + public BooleanExpression or(final BooleanExpression or) { + return new MqlExpression<>(ast("$or", or)); + } + + @Override + public BooleanExpression and(final BooleanExpression and) { + return new MqlExpression<>(ast("$and", and)); + } + + @Override + public R cond(final R left, final R right) { + return newMqlExpression(ast("$cond", left, right)); + } + +} diff --git a/driver-core/src/main/com/mongodb/client/model/expressions/MqlExpressionCodec.java b/driver-core/src/main/com/mongodb/client/model/expressions/MqlExpressionCodec.java new file mode 100644 index 00000000000..877d9c8b26b --- /dev/null +++ b/driver-core/src/main/com/mongodb/client/model/expressions/MqlExpressionCodec.java @@ -0,0 +1,52 @@ +/* + * 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.BsonReader; +import org.bson.BsonValue; +import org.bson.BsonWriter; +import org.bson.codecs.Codec; +import org.bson.codecs.DecoderContext; +import org.bson.codecs.EncoderContext; +import org.bson.codecs.configuration.CodecRegistry; + +@SuppressWarnings("rawtypes") +final class MqlExpressionCodec implements Codec { + private final CodecRegistry codecRegistry; + + MqlExpressionCodec(final CodecRegistry codecRegistry) { + this.codecRegistry = codecRegistry; + } + + @Override + public MqlExpression decode(final BsonReader reader, final DecoderContext decoderContext) { + throw new UnsupportedOperationException("Decoding to an expression is not supported"); + } + + @Override + @SuppressWarnings({"unchecked"}) + public void encode(final BsonWriter writer, final MqlExpression value, final EncoderContext encoderContext) { + BsonValue bsonValue = value.toBsonValue(codecRegistry); + Codec codec = codecRegistry.get(bsonValue.getClass()); + codec.encode(writer, bsonValue, encoderContext); + } + + @Override + public Class getEncoderClass() { + return MqlExpression.class; + } +} diff --git a/driver-core/src/main/com/mongodb/client/model/expressions/NumberExpression.java b/driver-core/src/main/com/mongodb/client/model/expressions/NumberExpression.java new file mode 100644 index 00000000000..25084473853 --- /dev/null +++ b/driver-core/src/main/com/mongodb/client/model/expressions/NumberExpression.java @@ -0,0 +1,24 @@ +/* + * 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; + +/** + * Expresses a numeric value. + */ +public interface NumberExpression extends Expression { + +} diff --git a/driver-core/src/main/com/mongodb/client/model/expressions/StringExpression.java b/driver-core/src/main/com/mongodb/client/model/expressions/StringExpression.java new file mode 100644 index 00000000000..de1f5878b96 --- /dev/null +++ b/driver-core/src/main/com/mongodb/client/model/expressions/StringExpression.java @@ -0,0 +1,24 @@ +/* + * 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; + +/** + * Expresses a string value. + */ +public interface StringExpression extends Expression { + +} diff --git a/driver-core/src/main/com/mongodb/client/model/expressions/package-info.java b/driver-core/src/main/com/mongodb/client/model/expressions/package-info.java new file mode 100644 index 00000000000..596d642e654 --- /dev/null +++ b/driver-core/src/main/com/mongodb/client/model/expressions/package-info.java @@ -0,0 +1,24 @@ +/* + * 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. + */ + +/** + * API for MQL expressions. + * + * @see com.mongodb.client.model.expressions.Expression + */ +@NonNullApi +package com.mongodb.client.model.expressions; +import com.mongodb.lang.NonNullApi; diff --git a/driver-core/src/test/functional/com/mongodb/client/model/AggregatesTest.java b/driver-core/src/test/functional/com/mongodb/client/model/AggregatesTest.java index 0afa1a5488a..ae29109a0c6 100644 --- a/driver-core/src/test/functional/com/mongodb/client/model/AggregatesTest.java +++ b/driver-core/src/test/functional/com/mongodb/client/model/AggregatesTest.java @@ -20,36 +20,21 @@ import com.mongodb.client.model.geojson.Position; import org.bson.BsonDocument; import org.bson.Document; - -import java.util.Collections; -import java.util.List; - import org.bson.conversions.Bson; import org.junit.jupiter.api.Test; +import java.util.List; + import static com.mongodb.ClusterFixture.serverVersionAtLeast; -import static com.mongodb.MongoClientSettings.getDefaultCodecRegistry; -import static com.mongodb.client.model.GeoNearOptions.geoNearOptions; import static com.mongodb.client.model.Aggregates.geoNear; import static com.mongodb.client.model.Aggregates.unset; +import static com.mongodb.client.model.GeoNearOptions.geoNearOptions; import static java.util.Arrays.asList; -import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assumptions.assumeTrue; public class AggregatesTest extends OperationTest { - private List assertPipeline(final String stageAsString, final Bson stage) { - BsonDocument expectedStage = BsonDocument.parse(stageAsString); - List pipeline = Collections.singletonList(stage); - assertEquals(expectedStage, pipeline.get(0).toBsonDocument(BsonDocument.class, getDefaultCodecRegistry())); - return pipeline; - } - private void assertResults(final List pipeline, final String s) { - List expectedResults = parseToList(s); - List results = getCollectionHelper().aggregate(pipeline); - assertEquals(expectedResults, results); - } @Test public void testUnset() { diff --git a/driver-core/src/test/functional/com/mongodb/client/model/OperationTest.java b/driver-core/src/test/functional/com/mongodb/client/model/OperationTest.java index ac0558f3c34..885b95aaea5 100644 --- a/driver-core/src/test/functional/com/mongodb/client/model/OperationTest.java +++ b/driver-core/src/test/functional/com/mongodb/client/model/OperationTest.java @@ -22,12 +22,13 @@ import com.mongodb.internal.connection.ServerHelper; import org.bson.BsonArray; import org.bson.BsonDocument; -import org.bson.Document; +import org.bson.codecs.BsonDocumentCodec; import org.bson.codecs.DecoderContext; -import org.bson.codecs.DocumentCodec; +import org.bson.conversions.Bson; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; +import java.util.Collections; import java.util.List; import java.util.stream.Collectors; @@ -36,6 +37,7 @@ import static com.mongodb.ClusterFixture.getBinding; import static com.mongodb.ClusterFixture.getPrimary; import static com.mongodb.MongoClientSettings.getDefaultCodecRegistry; +import static org.junit.jupiter.api.Assertions.assertEquals; public abstract class OperationTest { @@ -53,12 +55,12 @@ public void afterEach() { ServerHelper.checkPool(getPrimary()); } - CollectionHelper getCollectionHelper() { + protected CollectionHelper getCollectionHelper() { return getCollectionHelper(getNamespace()); } - private CollectionHelper getCollectionHelper(final MongoNamespace namespace) { - return new CollectionHelper<>(new DocumentCodec(), namespace); + private CollectionHelper getCollectionHelper(final MongoNamespace namespace) { + return new CollectionHelper<>(new BsonDocumentCodec(), namespace); } private String getDatabaseName() { @@ -73,11 +75,29 @@ MongoNamespace getNamespace() { return new MongoNamespace(getDatabaseName(), getCollectionName()); } - public static List parseToList(final String s) { - return BsonArray.parse(s).stream().map(v -> toDocument(v.asDocument())).collect(Collectors.toList()); + private static List parseToList(final String s) { + return BsonArray.parse(s).stream().map(v -> toBsonDocument(v.asDocument())).collect(Collectors.toList()); } - public static Document toDocument(final BsonDocument bsonDocument) { - return getDefaultCodecRegistry().get(Document.class).decode(bsonDocument.asBsonReader(), DecoderContext.builder().build()); + public static BsonDocument toBsonDocument(final BsonDocument bsonDocument) { + return getDefaultCodecRegistry().get(BsonDocument.class).decode(bsonDocument.asBsonReader(), DecoderContext.builder().build()); + } + + + protected List assertPipeline(final String stageAsString, final Bson stage) { + List pipeline = Collections.singletonList(stage); + return assertPipeline(stageAsString, pipeline); + } + + protected List assertPipeline(final String stageAsString, final List pipeline) { + BsonDocument expectedStage = BsonDocument.parse(stageAsString); + assertEquals(expectedStage, pipeline.get(0).toBsonDocument(BsonDocument.class, getDefaultCodecRegistry())); + return pipeline; + } + + protected void assertResults(final List pipeline, final String expectedResultsAsString) { + List expectedResults = parseToList(expectedResultsAsString); + List results = getCollectionHelper().aggregate(pipeline); + assertEquals(expectedResults, results); } } diff --git a/driver-core/src/test/functional/com/mongodb/client/model/expressions/AbstractExpressionsFunctionalTest.java b/driver-core/src/test/functional/com/mongodb/client/model/expressions/AbstractExpressionsFunctionalTest.java new file mode 100644 index 00000000000..59171c6f0b2 --- /dev/null +++ b/driver-core/src/test/functional/com/mongodb/client/model/expressions/AbstractExpressionsFunctionalTest.java @@ -0,0 +1,101 @@ +/* + * 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 com.mongodb.client.model.Field; +import com.mongodb.client.model.OperationTest; +import org.bson.BsonArray; +import org.bson.BsonDocument; +import org.bson.BsonReader; +import org.bson.BsonString; +import org.bson.BsonValue; +import org.bson.Document; +import org.bson.codecs.BsonDocumentCodec; +import org.bson.codecs.BsonValueCodecProvider; +import org.bson.codecs.DecoderContext; +import org.bson.conversions.Bson; +import org.bson.json.JsonReader; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import static com.mongodb.ClusterFixture.serverVersionAtLeast; +import static com.mongodb.client.model.Aggregates.addFields; +import static org.bson.codecs.configuration.CodecRegistries.fromProviders; +import static org.junit.jupiter.api.Assertions.assertEquals; + +public abstract class AbstractExpressionsFunctionalTest extends OperationTest { + + @BeforeEach + public void setUp() { + getCollectionHelper().drop(); + } + + @AfterEach + public void tearDown() { + getCollectionHelper().drop(); + } + + protected void assertExpression(final Object expectedResult, final Expression expression, final String expectedMql) { + assertEval(expectedResult, expression); + + BsonValue expressionValue = ((MqlExpression) expression).toBsonValue(fromProviders(new BsonValueCodecProvider())); + BsonValue bsonValue = new BsonDocumentFragmentCodec().readValue( + new JsonReader(expectedMql), + DecoderContext.builder().build()); + assertEquals(bsonValue, expressionValue, expressionValue.toString().replace("\"", "'")); + } + + private void assertEval(final Object expected, final Expression toEvaluate) { + BsonValue evaluated = evaluate(toEvaluate); + assertEquals(new Document("val", expected).toBsonDocument().get("val"), evaluated); + } + + protected BsonValue evaluate(final Expression toEvaluate) { + Bson addFieldsStage = addFields(new Field<>("val", toEvaluate)); + List stages = new ArrayList<>(); + stages.add(addFieldsStage); + List results; + if (getCollectionHelper().count() == 0) { + BsonDocument document = new BsonDocument("val", new BsonString("#invalid string#")); + if (serverVersionAtLeast(5, 1)) { + Bson documentsStage = new BsonDocument("$documents", new BsonArray(Arrays.asList(document))); + stages.add(0, documentsStage); + results = getCollectionHelper().aggregateDb(stages); + } else { + getCollectionHelper().insertDocuments(document); + results = getCollectionHelper().aggregate(stages); + getCollectionHelper().drop(); + } + } else { + results = getCollectionHelper().aggregate(stages); + } + BsonValue evaluated = results.get(0).get("val"); + return evaluated; + } + + private static class BsonDocumentFragmentCodec extends BsonDocumentCodec { + public BsonValue readValue(final BsonReader reader, final DecoderContext decoderContext) { + reader.readBsonType(); + return super.readValue(reader, decoderContext); + } + } +} + diff --git a/driver-core/src/test/functional/com/mongodb/client/model/expressions/BooleanExpressionsFunctionalTest.java b/driver-core/src/test/functional/com/mongodb/client/model/expressions/BooleanExpressionsFunctionalTest.java new file mode 100644 index 00000000000..af0ebdbff37 --- /dev/null +++ b/driver-core/src/test/functional/com/mongodb/client/model/expressions/BooleanExpressionsFunctionalTest.java @@ -0,0 +1,75 @@ +/* + * 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.junit.jupiter.api.Test; + +@SuppressWarnings({"PointlessBooleanExpression", "ConstantConditions", "ConstantConditionalExpression"}) +class BooleanExpressionsFunctionalTest extends AbstractExpressionsFunctionalTest { + // https://www.mongodb.com/docs/manual/reference/operator/aggregation/#boolean-expression-operators + // (Complete as of 6.0) + + private final BooleanExpression tru = Expressions.of(true); + private final BooleanExpression fal = Expressions.of(false); + + @Test + public void literalsTest() { + assertExpression(true, tru, "true"); + assertExpression(false, fal, "false"); + } + + @Test + public void orTest() { + // https://www.mongodb.com/docs/manual/reference/operator/aggregation/or/ + assertExpression(true || false, tru.or(fal), "{'$or': [true, false]}"); + assertExpression(false || true, fal.or(tru), "{'$or': [false, true]}"); + } + + @Test + public void andTest() { + // https://www.mongodb.com/docs/manual/reference/operator/aggregation/and/ + assertExpression(true && false, tru.and(fal), "{'$and': [true, false]}"); + assertExpression(false && true, fal.and(tru), "{'$and': [false, true]}"); + } + + @Test + public void notTest() { + // https://www.mongodb.com/docs/manual/reference/operator/aggregation/not/ + assertExpression(!true, tru.not(), "{'$not': true}"); + assertExpression(!false, fal.not(), "{'$not': false}"); + } + + @Test + public void condTest() { + // https://www.mongodb.com/docs/manual/reference/operator/aggregation/cond/ + StringExpression abc = Expressions.of("abc"); + StringExpression xyz = Expressions.of("xyz"); + NumberExpression nnn = Expressions.of(123); + assertExpression( + true && false ? "abc" : "xyz", + tru.and(fal).cond(abc, xyz), + "{'$cond': [{'$and': [true, false]}, 'abc', 'xyz']}"); + assertExpression( + true || false ? "abc" : "xyz", + tru.or(fal).cond(abc, xyz), + "{'$cond': [{'$or': [true, false]}, 'abc', 'xyz']}"); + assertExpression( + false ? "abc" : 123, + fal.cond(abc, nnn), + "{'$cond': [false, 'abc', 123]}"); + } +} diff --git a/driver-core/src/test/functional/com/mongodb/client/test/CollectionHelper.java b/driver-core/src/test/functional/com/mongodb/client/test/CollectionHelper.java index 178c5cbe792..c8478d308b0 100644 --- a/driver-core/src/test/functional/com/mongodb/client/test/CollectionHelper.java +++ b/driver-core/src/test/functional/com/mongodb/client/test/CollectionHelper.java @@ -33,6 +33,7 @@ import com.mongodb.internal.bulk.InsertRequest; import com.mongodb.internal.bulk.UpdateRequest; import com.mongodb.internal.bulk.WriteRequest; +import com.mongodb.internal.client.model.AggregationLevel; import com.mongodb.internal.operation.AggregateOperation; import com.mongodb.internal.operation.BatchCursor; import com.mongodb.internal.operation.CommandReadOperation; @@ -282,12 +283,20 @@ public List aggregate(final List pipeline) { } public List aggregate(final List pipeline, final Decoder decoder) { + return aggregate(pipeline, decoder, AggregationLevel.COLLECTION); + } + + public List aggregateDb(final List pipeline) { + return aggregate(pipeline, codec, AggregationLevel.DATABASE); + } + + private List aggregate(final List pipeline, final Decoder decoder, final AggregationLevel level) { List bsonDocumentPipeline = new ArrayList(); for (Bson cur : pipeline) { bsonDocumentPipeline.add(cur.toBsonDocument(Document.class, registry)); } - BatchCursor cursor = new AggregateOperation(namespace, bsonDocumentPipeline, decoder) - .execute(getBinding()); + BatchCursor cursor = new AggregateOperation(namespace, bsonDocumentPipeline, decoder, level) + .execute(getBinding()); List results = new ArrayList(); while (cursor.hasNext()) { results.addAll(cursor.next());