From 7be39bace89d19894661875f3af4caac7058bdfe Mon Sep 17 00:00:00 2001 From: Maxim Katcharov Date: Wed, 2 Nov 2022 08:58:29 -0600 Subject: [PATCH 01/27] Implement boolean expressions (#1025) JAVA-4779 --- config/checkstyle/suppressions.xml | 2 + .../main/com/mongodb/MongoClientSettings.java | 3 + .../model/expressions/ArrayExpression.java | 27 ++++ .../model/expressions/BooleanExpression.java | 61 +++++++++ .../model/expressions/DateExpression.java | 24 ++++ .../model/expressions/DocumentExpression.java | 25 ++++ .../client/model/expressions/Expression.java | 47 +++++++ .../expressions/ExpressionCodecProvider.java | 53 ++++++++ .../client/model/expressions/Expressions.java | 63 +++++++++ .../model/expressions/IntegerExpression.java | 24 ++++ .../model/expressions/MqlExpression.java | 121 ++++++++++++++++++ .../model/expressions/MqlExpressionCodec.java | 52 ++++++++ .../model/expressions/NumberExpression.java | 24 ++++ .../model/expressions/StringExpression.java | 24 ++++ .../model/expressions/package-info.java | 24 ++++ .../mongodb/client/model/AggregatesTest.java | 15 +-- .../mongodb/client/model/OperationTest.java | 38 ++++-- .../AbstractExpressionsFunctionalTest.java | 101 +++++++++++++++ .../BooleanExpressionsFunctionalTest.java | 75 +++++++++++ 19 files changed, 780 insertions(+), 23 deletions(-) create mode 100644 driver-core/src/main/com/mongodb/client/model/expressions/ArrayExpression.java create mode 100644 driver-core/src/main/com/mongodb/client/model/expressions/BooleanExpression.java create mode 100644 driver-core/src/main/com/mongodb/client/model/expressions/DateExpression.java create mode 100644 driver-core/src/main/com/mongodb/client/model/expressions/DocumentExpression.java create mode 100644 driver-core/src/main/com/mongodb/client/model/expressions/Expression.java create mode 100644 driver-core/src/main/com/mongodb/client/model/expressions/ExpressionCodecProvider.java create mode 100644 driver-core/src/main/com/mongodb/client/model/expressions/Expressions.java create mode 100644 driver-core/src/main/com/mongodb/client/model/expressions/IntegerExpression.java create mode 100644 driver-core/src/main/com/mongodb/client/model/expressions/MqlExpression.java create mode 100644 driver-core/src/main/com/mongodb/client/model/expressions/MqlExpressionCodec.java create mode 100644 driver-core/src/main/com/mongodb/client/model/expressions/NumberExpression.java create mode 100644 driver-core/src/main/com/mongodb/client/model/expressions/StringExpression.java create mode 100644 driver-core/src/main/com/mongodb/client/model/expressions/package-info.java create mode 100644 driver-core/src/test/functional/com/mongodb/client/model/expressions/AbstractExpressionsFunctionalTest.java create mode 100644 driver-core/src/test/functional/com/mongodb/client/model/expressions/BooleanExpressionsFunctionalTest.java diff --git a/config/checkstyle/suppressions.xml b/config/checkstyle/suppressions.xml index a5ff7ca9304..49f3fb18e9e 100644 --- a/config/checkstyle/suppressions.xml +++ b/config/checkstyle/suppressions.xml @@ -157,4 +157,6 @@ + + diff --git a/driver-core/src/main/com/mongodb/MongoClientSettings.java b/driver-core/src/main/com/mongodb/MongoClientSettings.java index f81b6a75b1f..97a5b9322aa 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; @@ -76,6 +77,7 @@ public final class MongoClientSettings { new JsonObjectCodecProvider(), new BsonCodecProvider(), new EnumCodecProvider(), + new ExpressionCodecProvider(), new Jep395RecordCodecProvider())); private final ReadPreference readPreference; @@ -123,6 +125,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 0db3482dfe5..02ea44a25a5 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 @@ -22,35 +22,22 @@ import org.bson.Document; import java.util.Arrays; -import java.util.Collections; import java.util.List; import org.bson.conversions.Bson; import org.junit.jupiter.api.Test; 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..8fb89a90ece 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()); + 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]}"); + } +} From 2f33777a793e24ec573bab62f81207238097c308 Mon Sep 17 00:00:00 2001 From: Maxim Katcharov Date: Wed, 9 Nov 2022 08:39:25 -0700 Subject: [PATCH 02/27] Implement filter, map, reduce (#1031) JAVA-4781 --- .../model/expressions/ArrayExpression.java | 37 ++++++++ .../client/model/expressions/Expressions.java | 21 +++++ .../model/expressions/MqlExpression.java | 30 ++++++ .../ArrayExpressionsFunctionalTest.java | 91 +++++++++++++++++++ 4 files changed, 179 insertions(+) create mode 100644 driver-core/src/test/functional/com/mongodb/client/model/expressions/ArrayExpressionsFunctionalTest.java 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 index 27acf24c7eb..77266e17bd7 100644 --- a/driver-core/src/main/com/mongodb/client/model/expressions/ArrayExpression.java +++ b/driver-core/src/main/com/mongodb/client/model/expressions/ArrayExpression.java @@ -16,6 +16,9 @@ package com.mongodb.client.model.expressions; +import java.util.function.BinaryOperator; +import java.util.function.Function; + /** * Expresses an array value. An array value is a finite, ordered collection of * elements of a certain type. @@ -24,4 +27,38 @@ */ public interface ArrayExpression extends Expression { + /** + * Returns an array consisting of those elements in this array that match + * the given predicate condition. Evaluates each expression in this array + * according to the cond function. If cond evaluates to logical true, then + * the element is preserved; if cond evaluates to logical false, the element + * is omitted. + * + * @param cond the function to apply to each element + * @return the new array + */ + ArrayExpression filter(Function cond); + + /** + * Returns an array consisting of the results of applying the given function + * to the elements of this array. + * + * @param in the function to apply to each element + * @return the new array + * @param the type contained in the resulting array + */ + ArrayExpression map(Function in); + + /** + * Performs a reduction on the elements of this array, using the provided + * identity value and an associative reducing function, and returns + * the reduced value. The initial value must be the identity value for the + * reducing function. + * + * @param initialValue the identity for the reducing function + * @param in the associative reducing function + * @return the reduced value + */ + T reduce(T initialValue, BinaryOperator in); + } 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 index de96a8f7994..fdde0751e46 100644 --- a/driver-core/src/main/com/mongodb/client/model/expressions/Expressions.java +++ b/driver-core/src/main/com/mongodb/client/model/expressions/Expressions.java @@ -16,9 +16,14 @@ package com.mongodb.client.model.expressions; +import org.bson.BsonArray; import org.bson.BsonBoolean; import org.bson.BsonInt32; import org.bson.BsonString; +import org.bson.BsonValue; + +import java.util.ArrayList; +import java.util.List; /** * Convenience methods related to {@link Expression}. @@ -60,4 +65,20 @@ public static IntegerExpression of(final int of) { public static StringExpression of(final String of) { return new MqlExpression<>((codecRegistry) -> new BsonString(of)); } + + /** + * Returns an array expression containing the same boolean values as the + * provided array of booleans. + * + * @param array the array of booleans + * @return the boolean array expression + */ + public static ArrayExpression ofBooleanArray(final boolean... array) { + List result = new ArrayList<>(); + for (boolean b : array) { + result.add(new BsonBoolean(b)); + } + return new MqlExpression<>((cr) -> new BsonArray(result)); + } + } 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 index d52926338e5..14f26cbb233 100644 --- a/driver-core/src/main/com/mongodb/client/model/expressions/MqlExpression.java +++ b/driver-core/src/main/com/mongodb/client/model/expressions/MqlExpression.java @@ -22,6 +22,7 @@ import org.bson.BsonValue; import org.bson.codecs.configuration.CodecRegistry; +import java.util.function.BinaryOperator; import java.util.function.Function; final class MqlExpression @@ -118,4 +119,33 @@ public R cond(final R left, final R right) { return newMqlExpression(ast("$cond", left, right)); } + + /** @see ArrayExpression */ + + @Override + public ArrayExpression map(final Function in) { + T varThis = variable("$$this"); + return new MqlExpression<>((cr) -> astDoc("$map", new BsonDocument() + .append("input", this.toBsonValue(cr)) + .append("in", extractBsonValue(cr, in.apply(varThis)))).apply(cr)); + } + + @Override + public ArrayExpression filter(final Function cond) { + T varThis = variable("$$this"); + return new MqlExpression((cr) -> astDoc("$filter", new BsonDocument() + .append("input", this.toBsonValue(cr)) + .append("cond", extractBsonValue(cr, cond.apply(varThis)))).apply(cr)); + } + + @Override + public T reduce(final T initialValue, final BinaryOperator in) { + T varThis = variable("$$this"); + T varValue = variable("$$value"); + return newMqlExpression((cr) -> astDoc("$reduce", new BsonDocument() + .append("input", this.toBsonValue(cr)) + .append("initialValue", extractBsonValue(cr, initialValue)) + .append("in", extractBsonValue(cr, in.apply(varThis, varValue)))).apply(cr)); + } + } diff --git a/driver-core/src/test/functional/com/mongodb/client/model/expressions/ArrayExpressionsFunctionalTest.java b/driver-core/src/test/functional/com/mongodb/client/model/expressions/ArrayExpressionsFunctionalTest.java new file mode 100644 index 00000000000..acee30a0628 --- /dev/null +++ b/driver-core/src/test/functional/com/mongodb/client/model/expressions/ArrayExpressionsFunctionalTest.java @@ -0,0 +1,91 @@ +/* + * 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; + +import java.util.Arrays; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import static com.mongodb.client.model.expressions.Expressions.of; +import static com.mongodb.client.model.expressions.Expressions.ofBooleanArray; + +@SuppressWarnings({"PointlessBooleanExpression", "ConstantConditions", "Convert2MethodRef"}) +class ArrayExpressionsFunctionalTest extends AbstractExpressionsFunctionalTest { + // https://www.mongodb.com/docs/manual/reference/operator/aggregation/#array-expression-operators + // (Incomplete) + + private final ArrayExpression arrayTTF = ofBooleanArray(true, true, false); + + @Test + public void literalsTest() { + assertExpression(Arrays.asList(true, true, false), arrayTTF, "[true, true, false]"); + } + + @Test + public void filterTest() { + // https://www.mongodb.com/docs/manual/reference/operator/aggregation/filter/ + assertExpression( + Stream.of(true, true, false) + .filter(v -> v).collect(Collectors.toList()), + arrayTTF.filter(v -> v), + // MQL: + "{'$filter': {'input': [true, true, false], 'cond': '$$this'}}"); + } + + @Test + public void mapTest() { + // https://www.mongodb.com/docs/manual/reference/operator/aggregation/map/ + assertExpression( + Stream.of(true, true, false) + .map(v -> !v).collect(Collectors.toList()), + arrayTTF.map(v -> v.not()), + // MQL: + "{'$map': {'input': [true, true, false], 'in': {'$not': '$$this'}}}"); + } + + @Test + public void reduceTest() { + // https://www.mongodb.com/docs/manual/reference/operator/aggregation/reduce/ + assertExpression( + Stream.of(true, true, false) + .reduce(false, (a, b) -> a || b), + arrayTTF.reduce(of(false), (a, b) -> a.or(b)), + // MQL: + "{'$reduce': {'input': [true, true, false], 'initialValue': false, 'in': {'$or': ['$$this', '$$value']}}}"); + assertExpression( + Stream.of(true, true, false) + .reduce(true, (a, b) -> a && b), + arrayTTF.reduce(of(true), (a, b) -> a.and(b)), + // MQL: + "{'$reduce': {'input': [true, true, false], 'initialValue': true, 'in': {'$and': ['$$this', '$$value']}}}"); + // empty array + assertExpression( + Stream.empty().reduce(true, (a, b) -> a && b), + ofBooleanArray().reduce(of(true), (a, b) -> a.and(b)), + // MQL: + "{'$reduce': {'input': [], 'initialValue': true, 'in': {'$and': ['$$this', '$$value']}}}"); + // constant result + assertExpression( + Stream.of(true, true, false) + .reduce(true, (a, b) -> true), + arrayTTF.reduce(of(true), (a, b) -> of(true)), + // MQL: + "{'$reduce': {'input': [true, true, false], 'initialValue': true, 'in': true}}"); + } +} From 6acb79840e5b2562c4998a33d797201d118211e9 Mon Sep 17 00:00:00 2001 From: Maxim Katcharov Date: Thu, 10 Nov 2022 12:18:44 -0700 Subject: [PATCH 03/27] Implement eq, ne, gt, gte, lt, lte (#1033) JAVA-4784 --- .../client/model/expressions/Expression.java | 53 +++++ .../client/model/expressions/Expressions.java | 41 ++++ .../model/expressions/MqlExpression.java | 34 +++- .../ComparisonExpressionsFunctionalTest.java | 190 ++++++++++++++++++ 4 files changed, 317 insertions(+), 1 deletion(-) create mode 100644 driver-core/src/test/functional/com/mongodb/client/model/expressions/ComparisonExpressionsFunctionalTest.java 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 index 987ccdd4bfa..ac0b9dcbbef 100644 --- a/driver-core/src/main/com/mongodb/client/model/expressions/Expression.java +++ b/driver-core/src/main/com/mongodb/client/model/expressions/Expression.java @@ -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); } 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 index fdde0751e46..48d42001af8 100644 --- a/driver-core/src/main/com/mongodb/client/model/expressions/Expressions.java +++ b/driver-core/src/main/com/mongodb/client/model/expressions/Expressions.java @@ -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}. @@ -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 @@ -81,4 +102,24 @@ public static ArrayExpression ofBooleanArray(final boolean... return new MqlExpression<>((cr) -> new BsonArray(result)); } + + public static ArrayExpression ofIntegerArray(final int... ofIntegerArray) { + List 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 ofNull() { + return new MqlExpression<>((cr) -> new BsonNull()).assertImplementsAllExpressions(); + } } 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 index 14f26cbb233..5fee6ce59a8 100644 --- a/driver-core/src/main/com/mongodb/client/model/expressions/MqlExpression.java +++ b/driver-core/src/main/com/mongodb/client/model/expressions/MqlExpression.java @@ -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 assertImplementsAllExpressions() { + R assertImplementsAllExpressions() { return (R) this; } @@ -120,6 +120,38 @@ public 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 diff --git a/driver-core/src/test/functional/com/mongodb/client/model/expressions/ComparisonExpressionsFunctionalTest.java b/driver-core/src/test/functional/com/mongodb/client/model/expressions/ComparisonExpressionsFunctionalTest.java new file mode 100644 index 00000000000..e4eea574583 --- /dev/null +++ b/driver-core/src/test/functional/com/mongodb/client/model/expressions/ComparisonExpressionsFunctionalTest.java @@ -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 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 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]}"); + } + +} From 2d7c03cef7c73d90882eab31cc67762a7baf9054 Mon Sep 17 00:00:00 2001 From: Maxim Katcharov Date: Tue, 15 Nov 2022 09:44:29 -0700 Subject: [PATCH 04/27] Implement string expressions (#1036) JAVA-4801 --- .../client/model/expressions/Expressions.java | 2 +- .../model/expressions/MqlExpression.java | 37 ++++ .../model/expressions/StringExpression.java | 23 +++ .../AbstractExpressionsFunctionalTest.java | 28 ++- .../ComparisonExpressionsFunctionalTest.java | 1 - .../StringExpressionsFunctionalTest.java | 168 ++++++++++++++++++ 6 files changed, 251 insertions(+), 8 deletions(-) create mode 100644 driver-core/src/test/functional/com/mongodb/client/model/expressions/StringExpressionsFunctionalTest.java 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 index 48d42001af8..6a2f4442219 100644 --- a/driver-core/src/main/com/mongodb/client/model/expressions/Expressions.java +++ b/driver-core/src/main/com/mongodb/client/model/expressions/Expressions.java @@ -32,7 +32,6 @@ 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; @@ -84,6 +83,7 @@ public static DateExpression of(final Instant of) { * @return the string expression */ public static StringExpression of(final String of) { + Assertions.notNull("String", of); return new MqlExpression<>((codecRegistry) -> new BsonString(of)); } 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 index 5fee6ce59a8..70c075ab87d 100644 --- a/driver-core/src/main/com/mongodb/client/model/expressions/MqlExpression.java +++ b/driver-core/src/main/com/mongodb/client/model/expressions/MqlExpression.java @@ -180,4 +180,41 @@ public T reduce(final T initialValue, final BinaryOperator in) { .append("in", extractBsonValue(cr, in.apply(varThis, varValue)))).apply(cr)); } + + /** @see StringExpression */ + + @Override + public StringExpression toLower() { + return new MqlExpression<>(ast("$toLower")); + } + + @Override + public StringExpression toUpper() { + return new MqlExpression<>(ast("$toUpper")); + } + + @Override + public StringExpression concat(final StringExpression concat) { + return new MqlExpression<>(ast("$concat", concat)); + } + + @Override + public IntegerExpression strLen() { + return new MqlExpression<>(ast("$strLenCP")); + } + + @Override + public IntegerExpression strLenBytes() { + return new MqlExpression<>(ast("$strLenBytes")); + } + + @Override + public StringExpression substr(final IntegerExpression start, final IntegerExpression length) { + return new MqlExpression<>(ast("$substrCP", start, length)); + } + + @Override + public StringExpression substrBytes(final IntegerExpression start, final IntegerExpression length) { + return new MqlExpression<>(ast("$substrBytes", start, length)); + } } 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 index de1f5878b96..a0812878137 100644 --- a/driver-core/src/main/com/mongodb/client/model/expressions/StringExpression.java +++ b/driver-core/src/main/com/mongodb/client/model/expressions/StringExpression.java @@ -16,9 +16,32 @@ package com.mongodb.client.model.expressions; +import static com.mongodb.client.model.expressions.Expressions.of; + /** * Expresses a string value. */ public interface StringExpression extends Expression { + StringExpression toLower(); + + StringExpression toUpper(); + + StringExpression concat(StringExpression concat); + + IntegerExpression strLen(); + + IntegerExpression strLenBytes(); + + StringExpression substr(IntegerExpression start, IntegerExpression length); + + default StringExpression substr(int start, int length) { + return this.substr(of(start), of(length)); + } + + StringExpression substrBytes(IntegerExpression start, IntegerExpression length); + + default StringExpression substrBytes(int start, int length) { + return this.substrBytes(of(start), of(length)); + } } 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 index 59171c6f0b2..fc35aed7c3e 100644 --- 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 @@ -18,6 +18,7 @@ import com.mongodb.client.model.Field; import com.mongodb.client.model.OperationTest; +import com.mongodb.lang.Nullable; import org.bson.BsonArray; import org.bson.BsonDocument; import org.bson.BsonReader; @@ -53,8 +54,16 @@ public void tearDown() { getCollectionHelper().drop(); } - protected void assertExpression(final Object expectedResult, final Expression expression, final String expectedMql) { - assertEval(expectedResult, expression); + protected void assertExpression(final Object expected, final Expression expression) { + assertExpression(expected, expression, null); + } + + protected void assertExpression(@Nullable final Object expected, final Expression expression, @Nullable final String expectedMql) { + assertEval(expected, expression); + + if (expectedMql == null) { + return; + } BsonValue expressionValue = ((MqlExpression) expression).toBsonValue(fromProviders(new BsonValueCodecProvider())); BsonValue bsonValue = new BsonDocumentFragmentCodec().readValue( @@ -63,9 +72,17 @@ protected void assertExpression(final Object expectedResult, final Expression ex assertEquals(bsonValue, expressionValue, expressionValue.toString().replace("\"", "'")); } - private void assertEval(final Object expected, final Expression toEvaluate) { + private void assertEval(@Nullable final Object expected, final Expression toEvaluate) { BsonValue evaluated = evaluate(toEvaluate); - assertEquals(new Document("val", expected).toBsonDocument().get("val"), evaluated); + BsonValue expected1 = toBsonValue(expected); + assertEquals(expected1, evaluated); + } + + protected BsonValue toBsonValue(@Nullable final Object value) { + if (value instanceof BsonValue) { + return (BsonValue) value; + } + return new Document("val", value).toBsonDocument().get("val"); } protected BsonValue evaluate(final Expression toEvaluate) { @@ -87,8 +104,7 @@ protected BsonValue evaluate(final Expression toEvaluate) { } else { results = getCollectionHelper().aggregate(stages); } - BsonValue evaluated = results.get(0).get("val"); - return evaluated; + return results.get(0).get("val"); } private static class BsonDocumentFragmentCodec extends BsonDocumentCodec { diff --git a/driver-core/src/test/functional/com/mongodb/client/model/expressions/ComparisonExpressionsFunctionalTest.java b/driver-core/src/test/functional/com/mongodb/client/model/expressions/ComparisonExpressionsFunctionalTest.java index e4eea574583..b2d32c352b0 100644 --- a/driver-core/src/test/functional/com/mongodb/client/model/expressions/ComparisonExpressionsFunctionalTest.java +++ b/driver-core/src/test/functional/com/mongodb/client/model/expressions/ComparisonExpressionsFunctionalTest.java @@ -24,7 +24,6 @@ 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; diff --git a/driver-core/src/test/functional/com/mongodb/client/model/expressions/StringExpressionsFunctionalTest.java b/driver-core/src/test/functional/com/mongodb/client/model/expressions/StringExpressionsFunctionalTest.java new file mode 100644 index 00000000000..b4092963c9c --- /dev/null +++ b/driver-core/src/test/functional/com/mongodb/client/model/expressions/StringExpressionsFunctionalTest.java @@ -0,0 +1,168 @@ +/* + * 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; + +import java.nio.charset.StandardCharsets; +import java.util.Arrays; + +import static com.mongodb.client.model.expressions.Expressions.of; +import static org.junit.jupiter.api.Assertions.assertThrows; + +@SuppressWarnings({"ConstantConditions"}) +class StringExpressionsFunctionalTest extends AbstractExpressionsFunctionalTest { + // https://www.mongodb.com/docs/manual/reference/operator/aggregation/#string-expression-operators + + private final String jalapeno = "jalape\u00F1o"; + private final String sushi = "\u5BFF\u53F8"; + private final String fish = "\uD83D\uDC1F"; + + @Test + public void literalsTest() { + assertExpression("", of(""), "''"); + assertExpression("abc", of("abc"), "'abc'"); + assertThrows(IllegalArgumentException.class, () -> of((String) null)); + assertExpression(fish, of(fish), "'" + fish + "'"); + } + + @Test + public void concatTest() { + // https://www.mongodb.com/docs/manual/reference/operator/aggregation/concat/ + assertExpression( + "abc".concat("de"), + of("abc").concat(of("de")), + "{'$concat': ['abc', 'de']}"); + } + + @Test + public void toLowerTest() { + // https://www.mongodb.com/docs/manual/reference/operator/aggregation/toLower/ + assertExpression( + "ABC".toLowerCase(), + of("ABC").toLower(), + "{'$toLower': 'ABC'}"); + } + + @Test + public void toUpperTest() { + // https://www.mongodb.com/docs/manual/reference/operator/aggregation/toUpper/ (?) + assertExpression( + "abc".toUpperCase(), + of("abc").toUpper(), + "{'$toUpper': 'abc'}"); + } + + @Test + public void strLenTest() { + // https://www.mongodb.com/docs/manual/reference/operator/aggregation/strLenCP/ (?) + assertExpression( + "abc".codePointCount(0, 3), + of("abc").strLen(), + "{'$strLenCP': 'abc'}"); + + // unicode + assertExpression( + jalapeno.codePointCount(0, jalapeno.length()), + of(jalapeno).strLen(), + "{'$strLenCP': '" + jalapeno + "'}"); + assertExpression( + sushi.codePointCount(0, sushi.length()), + of(sushi).strLen(), + "{'$strLenCP': '" + sushi + "'}"); + assertExpression( + fish.codePointCount(0, fish.length()), + of(fish).strLen(), + "{'$strLenCP': '" + fish + "'}"); + } + + @Test + public void strLenBytesTest() { + // https://www.mongodb.com/docs/manual/reference/operator/aggregation/strLenBytes/ (?) + assertExpression( + "abc".getBytes(StandardCharsets.UTF_8).length, + of("abc").strLenBytes(), + "{'$strLenBytes': 'abc'}"); + + // unicode + assertExpression( + jalapeno.getBytes(StandardCharsets.UTF_8).length, + of(jalapeno).strLenBytes(), + "{'$strLenBytes': '" + jalapeno + "'}"); + assertExpression( + sushi.getBytes(StandardCharsets.UTF_8).length, + of(sushi).strLenBytes(), + "{'$strLenBytes': '" + sushi + "'}"); + assertExpression( + fish.getBytes(StandardCharsets.UTF_8).length, + of(fish).strLenBytes(), + "{'$strLenBytes': '" + fish + "'}"); + + // comparison + assertExpression(8, of(jalapeno).strLen()); + assertExpression(9, of(jalapeno).strLenBytes()); + assertExpression(2, of(sushi).strLen()); + assertExpression(6, of(sushi).strLenBytes()); + assertExpression(1, of(fish).strLen()); + assertExpression(4, of(fish).strLenBytes()); + } + + @Test + public void substrTest() { + // https://www.mongodb.com/docs/manual/reference/operator/aggregation/substr/ + // https://www.mongodb.com/docs/manual/reference/operator/aggregation/substrCP/ (?) + // substr is deprecated, an alias for bytes + assertExpression( + "abc".substring(1, 1 + 1), + of("abc").substr(of(1), of(1)), + "{'$substrCP': ['abc', 1, 1]}"); + + // unicode + assertExpression( + jalapeno.substring(5, 5 + 3), + of(jalapeno).substr(of(5), of(3)), + "{'$substrCP': ['" + jalapeno + "', 5, 3]}"); + assertExpression( + "e\u00F1o", + of(jalapeno).substr(of(5), of(3))); + + // bounds; convenience + assertExpression("abc", of("abc").substr(0, 99)); + assertExpression("ab", of("abc").substr(0, 2)); + assertExpression("b", of("abc").substr(1, 1)); + assertExpression("", of("abc").substr(1, 0)); + } + + @Test + public void substrBytesTest() { + // https://www.mongodb.com/docs/manual/reference/operator/aggregation/substrBytes/ (?) + assertExpression( + "b", + of("abc").substrBytes(of(1), of(1)), + "{'$substrBytes': ['abc', 1, 1]}"); + + // unicode + byte[] bytes = Arrays.copyOfRange(sushi.getBytes(StandardCharsets.UTF_8), 0, 3); + String expected = new String(bytes, StandardCharsets.UTF_8); + assertExpression(expected, + of(sushi).substrBytes(of(0), of(3))); + // server returns "starting index is a UTF-8 continuation byte" error when substrBytes(1, 1) + + // convenience + assertExpression("b", of("abc").substrBytes(1, 1)); + } +} From 6768db0bfe28887787177d933e8537afda3ebd40 Mon Sep 17 00:00:00 2001 From: Maxim Katcharov Date: Tue, 29 Nov 2022 08:30:58 -0700 Subject: [PATCH 05/27] Implement arithmetic expressions (#1037) Implement arithmetic expressions (from top 50, and others) JAVA-4803 --- .../client/model/expressions/Expressions.java | 45 +++- .../model/expressions/IntegerExpression.java | 21 ++ .../model/expressions/MqlExpression.java | 114 ++++++-- .../model/expressions/NumberExpression.java | 33 +++ .../ArithmeticExpressionsFunctionalTest.java | 243 ++++++++++++++++++ .../ArrayExpressionsFunctionalTest.java | 2 +- .../ComparisonExpressionsFunctionalTest.java | 3 +- 7 files changed, 432 insertions(+), 29 deletions(-) create mode 100644 driver-core/src/test/functional/com/mongodb/client/model/expressions/ArithmeticExpressionsFunctionalTest.java 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 index 6a2f4442219..a11cef3dfa4 100644 --- a/driver-core/src/main/com/mongodb/client/model/expressions/Expressions.java +++ b/driver-core/src/main/com/mongodb/client/model/expressions/Expressions.java @@ -20,6 +20,7 @@ import org.bson.BsonArray; import org.bson.BsonBoolean; import org.bson.BsonDateTime; +import org.bson.BsonDecimal128; import org.bson.BsonDocument; import org.bson.BsonDouble; import org.bson.BsonInt32; @@ -28,6 +29,7 @@ import org.bson.BsonString; import org.bson.BsonValue; import org.bson.conversions.Bson; +import org.bson.types.Decimal128; import java.time.Instant; import java.util.ArrayList; @@ -35,6 +37,7 @@ import java.util.List; import java.util.stream.Collectors; +import static com.mongodb.client.model.expressions.MqlExpression.AstPlaceholder; /** * Convenience methods related to {@link Expression}. @@ -52,7 +55,7 @@ private Expressions() {} */ public static BooleanExpression of(final boolean of) { // we intentionally disallow ofBoolean(null) - return new MqlExpression<>((codecRegistry) -> new BsonBoolean(of)); + return new MqlExpression<>((codecRegistry) -> new AstPlaceholder(new BsonBoolean(of))); } /** @@ -63,16 +66,21 @@ public static BooleanExpression of(final boolean of) { * @return the integer expression */ public static IntegerExpression of(final int of) { - return new MqlExpression<>((codecRegistry) -> new BsonInt32(of)); + return new MqlExpression<>((codecRegistry) -> new AstPlaceholder(new BsonInt32(of))); } public static IntegerExpression of(final long of) { - return new MqlExpression<>((codecRegistry) -> new BsonInt64(of)); + return new MqlExpression<>((codecRegistry) -> new AstPlaceholder(new BsonInt64(of))); } public static NumberExpression of(final double of) { - return new MqlExpression<>((codecRegistry) -> new BsonDouble(of)); + return new MqlExpression<>((codecRegistry) -> new AstPlaceholder(new BsonDouble(of))); + } + public static NumberExpression of(final Decimal128 of) { + Assertions.notNull("Decimal128", of); + return new MqlExpression<>((codecRegistry) -> new AstPlaceholder(new BsonDecimal128(of))); } public static DateExpression of(final Instant of) { - return new MqlExpression<>((codecRegistry) -> new BsonDateTime(of.toEpochMilli())); + Assertions.notNull("Instant", of); + return new MqlExpression<>((codecRegistry) -> new AstPlaceholder(new BsonDateTime(of.toEpochMilli()))); } /** @@ -84,7 +92,7 @@ public static DateExpression of(final Instant of) { */ public static StringExpression of(final String of) { Assertions.notNull("String", of); - return new MqlExpression<>((codecRegistry) -> new BsonString(of)); + return new MqlExpression<>((codecRegistry) -> new AstPlaceholder(new BsonString(of))); } /** @@ -99,7 +107,7 @@ public static ArrayExpression ofBooleanArray(final boolean... for (boolean b : array) { result.add(new BsonBoolean(b)); } - return new MqlExpression<>((cr) -> new BsonArray(result)); + return new MqlExpression<>((cr) -> new AstPlaceholder(new BsonArray(result))); } @@ -107,7 +115,7 @@ public static ArrayExpression ofIntegerArray(final int... ofI List array = Arrays.stream(ofIntegerArray) .mapToObj(BsonInt32::new) .collect(Collectors.toList()); - return new MqlExpression<>((cr) -> new BsonArray(array)); + return new MqlExpression<>((cr) -> new AstPlaceholder(new BsonArray(array))); } public static DocumentExpression ofDocument(final Bson document) { @@ -115,11 +123,26 @@ public static DocumentExpression ofDocument(final Bson 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))); + return new MqlExpression<>((cr) -> new AstPlaceholder(new BsonDocument("$literal", + document.toBsonDocument(BsonDocument.class, cr)))); } public static R ofNull() { - return new MqlExpression<>((cr) -> new BsonNull()).assertImplementsAllExpressions(); + return new MqlExpression<>((cr) -> new AstPlaceholder(new BsonNull())) + .assertImplementsAllExpressions(); + } + + static NumberExpression numberToExpression(final Number number) { + if (number instanceof Integer) { + return of((int) number); + } else if (number instanceof Long) { + return of((long) number); + } else if (number instanceof Double) { + return of((double) number); + } else if (number instanceof Decimal128) { + return of((Decimal128) number); + } else { + throw new IllegalArgumentException("Number must be one of: Integer, Long, Double, Decimal128"); + } } } 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 index 6b53b9d62b8..eab541c0474 100644 --- a/driver-core/src/main/com/mongodb/client/model/expressions/IntegerExpression.java +++ b/driver-core/src/main/com/mongodb/client/model/expressions/IntegerExpression.java @@ -20,5 +20,26 @@ * Expresses an integer value. */ public interface IntegerExpression extends NumberExpression { + IntegerExpression multiply(IntegerExpression i); + default IntegerExpression multiply(final int multiply) { + return this.multiply(Expressions.of(multiply)); + } + + IntegerExpression add(IntegerExpression i); + + default IntegerExpression add(final int add) { + return this.add(Expressions.of(add)); + } + + IntegerExpression subtract(IntegerExpression i); + + default IntegerExpression subtract(final int subtract) { + return this.subtract(Expressions.of(subtract)); + } + + IntegerExpression max(IntegerExpression i); + IntegerExpression min(IntegerExpression i); + + IntegerExpression abs(); } 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 index 70c075ab87d..302dd7e0610 100644 --- a/driver-core/src/main/com/mongodb/client/model/expressions/MqlExpression.java +++ b/driver-core/src/main/com/mongodb/client/model/expressions/MqlExpression.java @@ -29,9 +29,9 @@ final class MqlExpression implements Expression, BooleanExpression, IntegerExpression, NumberExpression, StringExpression, DateExpression, DocumentExpression, ArrayExpression { - private final Function fn; + private final Function fn; - MqlExpression(final Function fn) { + MqlExpression(final Function fn) { this.fn = fn; } @@ -41,33 +41,41 @@ final class MqlExpression * {@link MqlExpressionCodec}. */ BsonValue toBsonValue(final CodecRegistry codecRegistry) { - return fn.apply(codecRegistry); + return fn.apply(codecRegistry).bsonValue; } - private Function astDoc(final String name, final BsonDocument value) { - return (cr) -> new BsonDocument(name, value); + private AstPlaceholder astDoc(final String name, final BsonDocument value) { + return new AstPlaceholder(new BsonDocument(name, value)); } - private Function ast(final String name) { - return (cr) -> new BsonDocument(name, this.toBsonValue(cr)); + static final class AstPlaceholder { + private final BsonValue bsonValue; + + AstPlaceholder(final BsonValue bsonValue) { + this.bsonValue = bsonValue; + } + } + + private Function ast(final String name) { + return (cr) -> new AstPlaceholder(new BsonDocument(name, this.toBsonValue(cr))); } - private Function ast(final String name, final Expression param1) { + 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); + return new AstPlaceholder(new BsonDocument(name, value)); }; } - private Function ast(final String name, final Expression param1, final Expression param2) { + 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); + return new AstPlaceholder(new BsonDocument(name, value)); }; } @@ -89,12 +97,12 @@ R assertImplementsAllExpressions() { return (R) this; } - private static R newMqlExpression(final Function ast) { + private static R newMqlExpression(final Function ast) { return new MqlExpression<>(ast).assertImplementsAllExpressions(); } private R variable(final String variable) { - return newMqlExpression((cr) -> new BsonString(variable)); + return newMqlExpression((cr) -> new AstPlaceholder(new BsonString(variable))); } /** @see BooleanExpression */ @@ -159,7 +167,7 @@ public ArrayExpression map(final Function((cr) -> astDoc("$map", new BsonDocument() .append("input", this.toBsonValue(cr)) - .append("in", extractBsonValue(cr, in.apply(varThis)))).apply(cr)); + .append("in", extractBsonValue(cr, in.apply(varThis))))); } @Override @@ -167,7 +175,7 @@ public ArrayExpression filter(final Function((cr) -> astDoc("$filter", new BsonDocument() .append("input", this.toBsonValue(cr)) - .append("cond", extractBsonValue(cr, cond.apply(varThis)))).apply(cr)); + .append("cond", extractBsonValue(cr, cond.apply(varThis))))); } @Override @@ -177,7 +185,81 @@ public T reduce(final T initialValue, final BinaryOperator in) { return newMqlExpression((cr) -> astDoc("$reduce", new BsonDocument() .append("input", this.toBsonValue(cr)) .append("initialValue", extractBsonValue(cr, initialValue)) - .append("in", extractBsonValue(cr, in.apply(varThis, varValue)))).apply(cr)); + .append("in", extractBsonValue(cr, in.apply(varThis, varValue))))); + } + + + /** @see IntegerExpression + * @see NumberExpression */ + + @Override + public IntegerExpression multiply(final NumberExpression n) { + return newMqlExpression(ast("$multiply", n)); + } + + @Override + public NumberExpression add(final NumberExpression n) { + return new MqlExpression<>(ast("$add", n)); + } + + @Override + public NumberExpression divide(final NumberExpression n) { + return new MqlExpression<>(ast("$divide", n)); + } + + @Override + public NumberExpression max(final NumberExpression n) { + return new MqlExpression<>(ast("$max", n)); + } + + @Override + public NumberExpression min(final NumberExpression n) { + return new MqlExpression<>(ast("$min", n)); + } + + @Override + public IntegerExpression round() { + return new MqlExpression<>(ast("$round")); + } + + @Override + public NumberExpression round(final IntegerExpression place) { + return new MqlExpression<>(ast("$round", place)); + } + + @Override + public IntegerExpression multiply(final IntegerExpression i) { + return new MqlExpression<>(ast("$multiply", i)); + } + + @Override + public IntegerExpression abs() { + return newMqlExpression(ast("$abs")); + } + + @Override + public NumberExpression subtract(final NumberExpression n) { + return new MqlExpression<>(ast("$subtract", n)); + } + + @Override + public IntegerExpression add(final IntegerExpression i) { + return new MqlExpression<>(ast("$add", i)); + } + + @Override + public IntegerExpression subtract(final IntegerExpression i) { + return new MqlExpression<>(ast("$subtract", i)); + } + + @Override + public IntegerExpression max(final IntegerExpression i) { + return new MqlExpression<>(ast("$max", i)); + } + + @Override + public IntegerExpression min(final IntegerExpression i) { + return new MqlExpression<>(ast("$min", i)); } 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 index 25084473853..bbef9f5768f 100644 --- a/driver-core/src/main/com/mongodb/client/model/expressions/NumberExpression.java +++ b/driver-core/src/main/com/mongodb/client/model/expressions/NumberExpression.java @@ -21,4 +21,37 @@ */ public interface NumberExpression extends Expression { + NumberExpression multiply(NumberExpression n); + + default NumberExpression multiply(final Number multiply) { + return this.multiply(Expressions.numberToExpression(multiply)); + } + + NumberExpression divide(NumberExpression n); + + default NumberExpression divide(final Number divide) { + return this.divide(Expressions.numberToExpression(divide)); + } + + NumberExpression add(NumberExpression n); + + default NumberExpression add(final Number add) { + return this.add(Expressions.numberToExpression(add)); + } + + NumberExpression subtract(NumberExpression n); + + default NumberExpression subtract(final Number subtract) { + return this.subtract(Expressions.numberToExpression(subtract)); + } + + NumberExpression max(NumberExpression n); + + NumberExpression min(NumberExpression n); + + IntegerExpression round(); + + NumberExpression round(IntegerExpression place); + + NumberExpression abs(); } diff --git a/driver-core/src/test/functional/com/mongodb/client/model/expressions/ArithmeticExpressionsFunctionalTest.java b/driver-core/src/test/functional/com/mongodb/client/model/expressions/ArithmeticExpressionsFunctionalTest.java new file mode 100644 index 00000000000..4e156c86516 --- /dev/null +++ b/driver-core/src/test/functional/com/mongodb/client/model/expressions/ArithmeticExpressionsFunctionalTest.java @@ -0,0 +1,243 @@ +/* + * 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.types.Decimal128; +import org.junit.jupiter.api.Test; + +import java.math.BigDecimal; +import java.math.RoundingMode; + +import static com.mongodb.client.model.expressions.Expressions.numberToExpression; +import static com.mongodb.client.model.expressions.Expressions.of; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +@SuppressWarnings("ConstantConditions") +class ArithmeticExpressionsFunctionalTest extends AbstractExpressionsFunctionalTest { + // https://www.mongodb.com/docs/manual/reference/operator/aggregation/#arithmetic-expression-operators + + @Test + public void literalsTest() { + assertExpression(1, of(1), "1"); + assertExpression(1L, of(1L)); + assertExpression(1.0, of(1.0)); + assertExpression(Decimal128.parse("1.0"), of(Decimal128.parse("1.0"))); + assertThrows(IllegalArgumentException.class, () -> of((Decimal128) null)); + + // expression equality differs from bson equality + assertExpression(true, of(1L).eq(of(1.0))); + assertExpression(true, of(1L).eq(of(1))); + + // bson equality; underlying type is preserved + // this behaviour is not defined by the API, but tested for clarity + assertEquals(toBsonValue(1), evaluate(of(1))); + assertEquals(toBsonValue(1L), evaluate(of(1L))); + assertEquals(toBsonValue(1.0), evaluate(of(1.0))); + assertNotEquals(toBsonValue(1), evaluate(of(1L))); + assertNotEquals(toBsonValue(1.0), evaluate(of(1L))); + + // Number conversions; used internally + assertExpression(1, numberToExpression(1)); + assertExpression(1L, numberToExpression(1L)); + assertExpression(1.0, numberToExpression(1.0)); + assertExpression(Decimal128.parse("1.0"), numberToExpression(Decimal128.parse("1.0"))); + assertThrows(IllegalArgumentException.class, + () -> assertExpression("n/a", numberToExpression(BigDecimal.valueOf(1)))); + } + + @Test + public void multiplyTest() { + // https://www.mongodb.com/docs/manual/reference/operator/aggregation/multiply/ + assertExpression( + 2.0 * 2, + of(2.0).multiply(of(2)), + "{'$multiply': [2.0, 2]}"); + + // mixing integers and numbers + IntegerExpression oneInt = of(1); + NumberExpression oneNum = of(1.0); + IntegerExpression resultInt = oneInt.multiply(oneInt); + NumberExpression resultNum = oneNum.multiply(oneNum); + // compile time error if these were IntegerExpressions: + NumberExpression r2 = oneNum.multiply(oneInt); + NumberExpression r3 = oneInt.multiply(oneNum); + assertExpression(1, resultInt); + // 1 is also a valid expected value in our API + assertExpression(1.0, resultNum); + assertExpression(1.0, r2); + assertExpression(1.0, r3); + + // convenience + assertExpression(2.0, of(1.0).multiply(2.0)); + assertExpression(2L, of(1).multiply(2L)); + assertExpression(2, of(1).multiply(2)); + } + + @Test + public void divideTest() { + // https://www.mongodb.com/docs/manual/reference/operator/aggregation/divide/ + assertExpression( + 1.0 / 2.0, + of(1.0).divide(of(2.0)), + "{'$divide': [1.0, 2.0]}"); + // unlike Java's 1/2==0, dividing any type of numbers always yields an + // equal result, in this case represented using a double. + assertExpression( + 0.5, + of(1).divide(of(2)), + "{'$divide': [1, 2]}"); + + // however, there are differences in evaluation between numbers + // represented using Decimal128 and double: + assertExpression( + 2.5242187499999997, + of(3.231).divide(of(1.28))); + assertExpression( + Decimal128.parse("2.52421875"), + of(Decimal128.parse("3.231")).divide(of(Decimal128.parse("1.28")))); + assertExpression( + Decimal128.parse("2.52421875"), + of(Decimal128.parse("3.231")).divide(of(1.28))); + assertExpression( + Decimal128.parse("2.524218750000"), + of(3.231).divide(of(Decimal128.parse("1.28")))); + + // convenience + assertExpression(0.5, of(1.0).divide(2.0)); + assertExpression(0.5, of(1).divide(2.0)); + assertExpression(0.5, of(1).divide(2L)); + assertExpression(0.5, of(1).divide(2)); + + // divide always returns a Number, so the method is not on IntegerExpression + } + + @Test + public void addTest() { + // https://www.mongodb.com/docs/manual/reference/operator/aggregation/add/ + IntegerExpression actual = of(2).add(of(2)); + assertExpression( + 2 + 2, actual, + "{'$add': [2, 2]}"); + assertExpression( + 2.0 + 2, + of(2.0).add(of(2)), + "{'$add': [2.0, 2]}"); + + // convenience + assertExpression(3.0, of(1.0).add(2.0)); + assertExpression(3L, of(1).add(2L)); + assertExpression(3, of(1).add(2)); + + // https://www.mongodb.com/docs/manual/reference/operator/aggregation/sum/ + // sum's alternative behaviour exists for purposes of reduction, but is + // inconsistent with multiply, and potentially confusing. Unimplemented. + } + + @Test + public void subtractTest() { + // https://www.mongodb.com/docs/manual/reference/operator/aggregation/subtract/ + IntegerExpression actual = of(2).subtract(of(2)); + assertExpression( + 0, + actual, + "{'$subtract': [2, 2]} "); + assertExpression( + 2.0 - 2, + of(2.0).subtract(of(2)), + "{'$subtract': [2.0, 2]} "); + + // convenience + assertExpression(-1.0, of(1.0).subtract(2.0)); + assertExpression(-1, of(1).subtract(2)); + } + + @Test + public void maxTest() { + // https://www.mongodb.com/docs/manual/reference/operator/aggregation/max/ + IntegerExpression actual = of(-2).max(of(2)); + assertExpression( + Math.max(-2, 2), + actual, + "{'$max': [-2, 2]}"); + assertExpression( + Math.max(-2.0, 2.0), + of(-2.0).max(of(2.0)), + "{'$max': [-2.0, 2.0]}"); + } + + @Test + public void minTest() { + // https://www.mongodb.com/docs/manual/reference/operator/aggregation/min/ (63) + IntegerExpression actual = of(-2).min(of(2)); + assertExpression( + Math.min(-2, 2), + actual, + "{'$min': [-2, 2]}"); + assertExpression( + Math.min(-2.0, 2.0), + of(-2.0).min(of(2.0)), + "{'$min': [-2.0, 2.0]}"); + } + + @Test + public void roundTest() { + // https://www.mongodb.com/docs/manual/reference/operator/aggregation/round/ + IntegerExpression actual = of(5.5).round(); + assertExpression( + 6.0, + actual, + "{'$round': 5.5} "); + NumberExpression actualNum = of(5.5).round(of(0)); + assertExpression( + new BigDecimal("5.5").setScale(0, RoundingMode.HALF_EVEN).doubleValue(), + actualNum, + "{'$round': [5.5, 0]} "); + // unlike Java, uses banker's rounding (half_even) + assertExpression( + 2.0, + of(2.5).round(), + "{'$round': 2.5} "); + assertExpression( + new BigDecimal("-5.5").setScale(0, RoundingMode.HALF_EVEN).doubleValue(), + of(-5.5).round()); + // to place + assertExpression( + 555.55, + of(555.555).round(of(2)), + "{'$round': [555.555, 2]} "); + assertExpression( + 600.0, + of(555.555).round(of(-2)), + "{'$round': [555.555, -2]} "); + } + + @Test + public void absTest() { + // https://www.mongodb.com/docs/manual/reference/operator/aggregation/round/ (?) + assertExpression( + Math.abs(-2.0), + of(-2.0).abs(), + "{'$abs': -2.0}"); + // integer + IntegerExpression abs = of(-2).abs(); + assertExpression( + Math.abs(-2), abs, + "{'$abs': -2}"); + } +} diff --git a/driver-core/src/test/functional/com/mongodb/client/model/expressions/ArrayExpressionsFunctionalTest.java b/driver-core/src/test/functional/com/mongodb/client/model/expressions/ArrayExpressionsFunctionalTest.java index acee30a0628..b056a9fb8d0 100644 --- a/driver-core/src/test/functional/com/mongodb/client/model/expressions/ArrayExpressionsFunctionalTest.java +++ b/driver-core/src/test/functional/com/mongodb/client/model/expressions/ArrayExpressionsFunctionalTest.java @@ -25,7 +25,7 @@ import static com.mongodb.client.model.expressions.Expressions.of; import static com.mongodb.client.model.expressions.Expressions.ofBooleanArray; -@SuppressWarnings({"PointlessBooleanExpression", "ConstantConditions", "Convert2MethodRef"}) +@SuppressWarnings({"ConstantConditions", "Convert2MethodRef"}) class ArrayExpressionsFunctionalTest extends AbstractExpressionsFunctionalTest { // https://www.mongodb.com/docs/manual/reference/operator/aggregation/#array-expression-operators // (Incomplete) diff --git a/driver-core/src/test/functional/com/mongodb/client/model/expressions/ComparisonExpressionsFunctionalTest.java b/driver-core/src/test/functional/com/mongodb/client/model/expressions/ComparisonExpressionsFunctionalTest.java index b2d32c352b0..984119adaa8 100644 --- a/driver-core/src/test/functional/com/mongodb/client/model/expressions/ComparisonExpressionsFunctionalTest.java +++ b/driver-core/src/test/functional/com/mongodb/client/model/expressions/ComparisonExpressionsFunctionalTest.java @@ -42,7 +42,8 @@ class ComparisonExpressionsFunctionalTest extends AbstractExpressionsFunctionalT static R ofRem() { // $$REMOVE is intentionally not exposed to users - return new MqlExpression<>((cr) -> new BsonString("$$REMOVE")).assertImplementsAllExpressions(); + return new MqlExpression<>((cr) -> new MqlExpression.AstPlaceholder(new BsonString("$$REMOVE"))) + .assertImplementsAllExpressions(); } // https://www.mongodb.com/docs/manual/reference/bson-type-comparison-order/#std-label-bson-types-comparison-order From 7ac3ccd1f73c98185fd4d10947b1fd654d36d088 Mon Sep 17 00:00:00 2001 From: Maxim Katcharov Date: Tue, 29 Nov 2022 08:32:55 -0700 Subject: [PATCH 06/27] Implement array expressions (#1043) JAVA-4805 --- .../model/expressions/ArrayExpression.java | 27 +++ .../client/model/expressions/Expressions.java | 101 +++++++-- .../model/expressions/MqlExpression.java | 74 ++++++- .../AbstractExpressionsFunctionalTest.java | 7 + .../ArrayExpressionsFunctionalTest.java | 192 +++++++++++++++++- .../ComparisonExpressionsFunctionalTest.java | 7 - 6 files changed, 379 insertions(+), 29 deletions(-) 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 index 77266e17bd7..99f7da587fa 100644 --- a/driver-core/src/main/com/mongodb/client/model/expressions/ArrayExpression.java +++ b/driver-core/src/main/com/mongodb/client/model/expressions/ArrayExpression.java @@ -19,6 +19,8 @@ import java.util.function.BinaryOperator; import java.util.function.Function; +import static com.mongodb.client.model.expressions.Expressions.of; + /** * Expresses an array value. An array value is a finite, ordered collection of * elements of a certain type. @@ -61,4 +63,29 @@ public interface ArrayExpression extends Expression { */ T reduce(T initialValue, BinaryOperator in); + IntegerExpression size(); + + T elementAt(IntegerExpression i); + + default T elementAt(final int i) { + return this.elementAt(of(i)); + } + + T first(); + + T last(); + + BooleanExpression contains(T contains); + + ArrayExpression concat(ArrayExpression array); + + ArrayExpression slice(IntegerExpression start, IntegerExpression length); + + default ArrayExpression slice(final int start, final int length) { + return this.slice(of(start), of(length)); + } + + ArrayExpression union(ArrayExpression set); + + ArrayExpression distinct(); } 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 index a11cef3dfa4..1bb631015f1 100644 --- a/driver-core/src/main/com/mongodb/client/model/expressions/Expressions.java +++ b/driver-core/src/main/com/mongodb/client/model/expressions/Expressions.java @@ -58,6 +58,22 @@ public static BooleanExpression of(final boolean of) { return new MqlExpression<>((codecRegistry) -> new AstPlaceholder(new BsonBoolean(of))); } + /** + * Returns an array expression containing the same boolean values as the + * provided array of booleans. + * + * @param array the array of booleans + * @return the boolean array expression + */ + public static ArrayExpression ofBooleanArray(final boolean... array) { + Assertions.notNull("array", array); + List list = new ArrayList<>(); + for (boolean b : array) { + list.add(new BsonBoolean(b)); + } + return new MqlExpression<>((cr) -> new AstPlaceholder(new BsonArray(list))); + } + /** * Returns an expression having the same integer value as the provided * int primitive. @@ -68,21 +84,72 @@ public static BooleanExpression of(final boolean of) { public static IntegerExpression of(final int of) { return new MqlExpression<>((codecRegistry) -> new AstPlaceholder(new BsonInt32(of))); } + + public static ArrayExpression ofIntegerArray(final int... array) { + Assertions.notNull("array", array); + List list = new ArrayList<>(); + for (int i : array) { + list.add(new BsonInt32(i)); + } + return new MqlExpression<>((cr) -> new AstPlaceholder(new BsonArray(list))); + } + public static IntegerExpression of(final long of) { return new MqlExpression<>((codecRegistry) -> new AstPlaceholder(new BsonInt64(of))); } + + public static ArrayExpression ofIntegerArray(final long... array) { + Assertions.notNull("array", array); + List list = new ArrayList<>(); + for (long i : array) { + list.add(new BsonInt64(i)); + } + return new MqlExpression<>((cr) -> new AstPlaceholder(new BsonArray(list))); + } + public static NumberExpression of(final double of) { return new MqlExpression<>((codecRegistry) -> new AstPlaceholder(new BsonDouble(of))); } + + public static ArrayExpression ofNumberArray(final double... array) { + Assertions.notNull("array", array); + List list = new ArrayList<>(); + for (double n : array) { + list.add(new BsonDouble(n)); + } + return new MqlExpression<>((cr) -> new AstPlaceholder(new BsonArray(list))); + } + public static NumberExpression of(final Decimal128 of) { Assertions.notNull("Decimal128", of); return new MqlExpression<>((codecRegistry) -> new AstPlaceholder(new BsonDecimal128(of))); } + + public static ArrayExpression ofNumberArray(final Decimal128... array) { + Assertions.notNull("array", array); + List result = new ArrayList<>(); + for (Decimal128 e : array) { + Assertions.notNull("elements of array", e); + result.add(new BsonDecimal128(e)); + } + return new MqlExpression<>((cr) -> new AstPlaceholder(new BsonArray(result))); + } + public static DateExpression of(final Instant of) { Assertions.notNull("Instant", of); return new MqlExpression<>((codecRegistry) -> new AstPlaceholder(new BsonDateTime(of.toEpochMilli()))); } + public static ArrayExpression ofDateArray(final Instant... array) { + Assertions.notNull("array", array); + List result = new ArrayList<>(); + for (Instant e : array) { + Assertions.notNull("elements of array", e); + result.add(new BsonDateTime(e.toEpochMilli())); + } + return new MqlExpression<>((cr) -> new AstPlaceholder(new BsonArray(result))); + } + /** * Returns an expression having the same string value as the provided * string. @@ -95,27 +162,28 @@ public static StringExpression of(final String of) { return new MqlExpression<>((codecRegistry) -> new AstPlaceholder(new BsonString(of))); } - /** - * Returns an array expression containing the same boolean values as the - * provided array of booleans. - * - * @param array the array of booleans - * @return the boolean array expression - */ - public static ArrayExpression ofBooleanArray(final boolean... array) { + + public static ArrayExpression ofStringArray(final String... array) { + Assertions.notNull("array", array); List result = new ArrayList<>(); - for (boolean b : array) { - result.add(new BsonBoolean(b)); + for (String e : array) { + Assertions.notNull("elements of array", e); + result.add(new BsonString(e)); } return new MqlExpression<>((cr) -> new AstPlaceholder(new BsonArray(result))); } - - public static ArrayExpression ofIntegerArray(final int... ofIntegerArray) { - List array = Arrays.stream(ofIntegerArray) - .mapToObj(BsonInt32::new) - .collect(Collectors.toList()); - return new MqlExpression<>((cr) -> new AstPlaceholder(new BsonArray(array))); + @SafeVarargs // nothing is stored in the array + public static ArrayExpression ofArray(final T... array) { + Assertions.notNull("array", array); + return new MqlExpression<>((cr) -> { + List list = new ArrayList<>(); + for (T v : array) { + Assertions.notNull("elements of array", v); + list.add(((MqlExpression) v).toBsonValue(cr)); + } + return new AstPlaceholder(new BsonArray(list)); + }); } public static DocumentExpression ofDocument(final Bson document) { @@ -133,6 +201,7 @@ public static R ofNull() { } static NumberExpression numberToExpression(final Number number) { + Assertions.notNull("number", number); if (number instanceof Integer) { return of((int) number); } else if (number instanceof Long) { 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 index 302dd7e0610..218a396b0bc 100644 --- a/driver-core/src/main/com/mongodb/client/model/expressions/MqlExpression.java +++ b/driver-core/src/main/com/mongodb/client/model/expressions/MqlExpression.java @@ -22,6 +22,7 @@ import org.bson.BsonValue; import org.bson.codecs.configuration.CodecRegistry; +import java.util.Collections; import java.util.function.BinaryOperator; import java.util.function.Function; @@ -173,7 +174,7 @@ public ArrayExpression map(final Function filter(final Function cond) { T varThis = variable("$$this"); - return new MqlExpression((cr) -> astDoc("$filter", new BsonDocument() + return new MqlExpression<>((cr) -> astDoc("$filter", new BsonDocument() .append("input", this.toBsonValue(cr)) .append("cond", extractBsonValue(cr, cond.apply(varThis))))); } @@ -185,7 +186,76 @@ public T reduce(final T initialValue, final BinaryOperator in) { return newMqlExpression((cr) -> astDoc("$reduce", new BsonDocument() .append("input", this.toBsonValue(cr)) .append("initialValue", extractBsonValue(cr, initialValue)) - .append("in", extractBsonValue(cr, in.apply(varThis, varValue))))); + .append("in", extractBsonValue(cr, in.apply(varValue, varThis))))); + } + + @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)))))); + } + + @Override + public T elementAt(final IntegerExpression at) { + return new MqlExpression<>(ast("$arrayElemAt", at)) + .assertImplementsAllExpressions(); + } + + @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)))))) + .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)))))) + .assertImplementsAllExpressions(); + } + + @Override + public BooleanExpression contains(final T item) { + String name = "$in"; + return new MqlExpression<>((cr) -> { + BsonArray value = new BsonArray(); + value.add(extractBsonValue(cr, item)); + value.add(this.toBsonValue(cr)); + return new AstPlaceholder(new BsonDocument(name, value)); + }).assertImplementsAllExpressions(); + } + + @Override + public ArrayExpression concat(final ArrayExpression array) { + return new MqlExpression<>(ast("$concatArrays", array)) + .assertImplementsAllExpressions(); + } + + @Override + public ArrayExpression slice(final IntegerExpression start, final IntegerExpression length) { + return new MqlExpression<>(ast("$slice", start, length)) + .assertImplementsAllExpressions(); + } + + @Override + public ArrayExpression union(final ArrayExpression set) { + return new MqlExpression<>(ast("$setUnion", set)) + .assertImplementsAllExpressions(); + } + + @Override + public ArrayExpression 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)))))); } 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 index fc35aed7c3e..4b966294027 100644 --- 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 @@ -113,5 +113,12 @@ public BsonValue readValue(final BsonReader reader, final DecoderContext decoder return super.readValue(reader, decoderContext); } } + + + static R ofRem() { + // $$REMOVE is intentionally not exposed to users + return new MqlExpression<>((cr) -> new MqlExpression.AstPlaceholder(new BsonString("$$REMOVE"))) + .assertImplementsAllExpressions(); + } } diff --git a/driver-core/src/test/functional/com/mongodb/client/model/expressions/ArrayExpressionsFunctionalTest.java b/driver-core/src/test/functional/com/mongodb/client/model/expressions/ArrayExpressionsFunctionalTest.java index b056a9fb8d0..e38058551e7 100644 --- a/driver-core/src/test/functional/com/mongodb/client/model/expressions/ArrayExpressionsFunctionalTest.java +++ b/driver-core/src/test/functional/com/mongodb/client/model/expressions/ArrayExpressionsFunctionalTest.java @@ -16,25 +16,82 @@ package com.mongodb.client.model.expressions; +import org.bson.types.Decimal128; import org.junit.jupiter.api.Test; +import java.time.Instant; import java.util.Arrays; +import java.util.Collections; +import java.util.LinkedList; import java.util.stream.Collectors; import java.util.stream.Stream; import static com.mongodb.client.model.expressions.Expressions.of; +import static com.mongodb.client.model.expressions.Expressions.ofArray; import static com.mongodb.client.model.expressions.Expressions.ofBooleanArray; +import static com.mongodb.client.model.expressions.Expressions.ofDateArray; +import static com.mongodb.client.model.expressions.Expressions.ofIntegerArray; +import static com.mongodb.client.model.expressions.Expressions.ofNumberArray; +import static com.mongodb.client.model.expressions.Expressions.ofStringArray; @SuppressWarnings({"ConstantConditions", "Convert2MethodRef"}) class ArrayExpressionsFunctionalTest extends AbstractExpressionsFunctionalTest { // https://www.mongodb.com/docs/manual/reference/operator/aggregation/#array-expression-operators // (Incomplete) + private final ArrayExpression array123 = ofIntegerArray(1, 2, 3); private final ArrayExpression arrayTTF = ofBooleanArray(true, true, false); @Test public void literalsTest() { - assertExpression(Arrays.asList(true, true, false), arrayTTF, "[true, true, false]"); + // Boolean + assertExpression( + Arrays.asList(true, true, false), + arrayTTF, + "[true, true, false]"); + // Integer + assertExpression( + Arrays.asList(1, 2, 3), + array123, + "[1, 2, 3]"); + assertExpression( + Arrays.asList(1L, 2L, 3L), + ofIntegerArray(1L, 2L, 3L), + "[{'$numberLong': '1'}, {'$numberLong': '2'}, {'$numberLong': '3'}]"); + // Number + assertExpression( + Arrays.asList(1.0, 2.0, 3.0), + ofNumberArray(1.0, 2.0, 3.0), + "[1.0, 2.0, 3.0]"); + assertExpression( + Arrays.asList(Decimal128.parse("1.0")), + ofNumberArray(Decimal128.parse("1.0")), + "[{'$numberDecimal': '1.0'}]"); + // String + assertExpression( + Arrays.asList("a", "b", "c"), + ofStringArray("a", "b", "c"), + "['a', 'b', 'c']"); + // Date + assertExpression( + Arrays.asList(Instant.parse("2007-12-03T10:15:30.00Z")), + ofDateArray(Instant.parse("2007-12-03T10:15:30.00Z")), + "[{'$date': '2007-12-03T10:15:30.00Z'}]"); + // Document + // ... + + // Array + ArrayExpression> arrays = ofArray(ofArray(), ofArray()); + assertExpression( + Arrays.asList(Collections.emptyList(), Collections.emptyList()), arrays, + "[[], []]"); + + // Mixed + ArrayExpression expression = ofArray(of(1), of(true), ofArray(of(1.0), of(1))); + assertExpression( + Arrays.asList(1, true, Arrays.asList(1.0, 1)), + expression, + "[1, true, [1.0, 1]]"); } @Test @@ -67,19 +124,19 @@ public void reduceTest() { .reduce(false, (a, b) -> a || b), arrayTTF.reduce(of(false), (a, b) -> a.or(b)), // MQL: - "{'$reduce': {'input': [true, true, false], 'initialValue': false, 'in': {'$or': ['$$this', '$$value']}}}"); + "{'$reduce': {'input': [true, true, false], 'initialValue': false, 'in': {'$or': ['$$value', '$$this']}}}"); assertExpression( Stream.of(true, true, false) .reduce(true, (a, b) -> a && b), arrayTTF.reduce(of(true), (a, b) -> a.and(b)), // MQL: - "{'$reduce': {'input': [true, true, false], 'initialValue': true, 'in': {'$and': ['$$this', '$$value']}}}"); + "{'$reduce': {'input': [true, true, false], 'initialValue': true, 'in': {'$and': ['$$value', '$$this']}}}"); // empty array assertExpression( Stream.empty().reduce(true, (a, b) -> a && b), ofBooleanArray().reduce(of(true), (a, b) -> a.and(b)), // MQL: - "{'$reduce': {'input': [], 'initialValue': true, 'in': {'$and': ['$$this', '$$value']}}}"); + "{'$reduce': {'input': [], 'initialValue': true, 'in': {'$and': ['$$value', '$$this']}}}"); // constant result assertExpression( Stream.of(true, true, false) @@ -87,5 +144,132 @@ public void reduceTest() { arrayTTF.reduce(of(true), (a, b) -> of(true)), // MQL: "{'$reduce': {'input': [true, true, false], 'initialValue': true, 'in': true}}"); + // non-commutative + assertExpression( + "abc", + ofStringArray("a", "b", "c").reduce(of(""), (a, b) -> a.concat(b)), + // MQL: + "{'$reduce': {'input': ['a', 'b', 'c'], 'initialValue': '', 'in': {'$concat': ['$$value', '$$this']}}}"); + + } + + @Test + public void sizeTest() { + // https://www.mongodb.com/docs/manual/reference/operator/aggregation/size/ + assertExpression( + Arrays.asList(1, 2, 3).size(), + array123.size(), + // MQL: + "{'$size': [[1, 2, 3]]}"); + assertExpression( + 0, + ofIntegerArray().size(), + // MQL: + "{'$size': [[]]}"); + } + + @Test + public void elementAtTest() { + // https://www.mongodb.com/docs/manual/reference/operator/aggregation/arrayElemAt/ + assertExpression( + Arrays.asList(1, 2, 3).get(0), + // 0.0 is a valid integer value + array123.elementAt((IntegerExpression) of(0.0)), + // MQL: + "{'$arrayElemAt': [[1, 2, 3], 0.0]}"); + + assertExpression( + Arrays.asList(1, 2, 3).get(3 - 1), + array123.elementAt(-1)); + + assertExpression( + true, + ofRem().eq(array123.elementAt(99))); + assertExpression( + true, + ofRem().eq(array123.elementAt(-99))); + } + + @Test + public void firstTest() { + // https://www.mongodb.com/docs/manual/reference/operator/aggregation/first/ + // https://www.mongodb.com/docs/manual/reference/operator/aggregation/first-array-element/ + assertExpression( + new LinkedList<>(Arrays.asList(1, 2, 3)).getFirst(), + array123.first(), + // MQL: + "{'$first': [[1, 2, 3]]}"); + } + + @Test + public void lastTest() { + // https://www.mongodb.com/docs/manual/reference/operator/aggregation/last-array-element/ + assertExpression( + new LinkedList<>(Arrays.asList(1, 2, 3)).getLast(), + array123.last(), + // MQL: + "{'$last': [[1, 2, 3]]}"); + } + + @Test + public void containsTest() { + // https://www.mongodb.com/docs/manual/reference/operator/aggregation/in/ + // The parameters of this expression are flipped + assertExpression( + Arrays.asList(1, 2, 3).contains(2), + array123.contains(of(2)), + // MQL: + "{'$in': [2, [1, 2, 3]]}"); + } + + @Test + public void concatTest() { + // https://www.mongodb.com/docs/manual/reference/operator/aggregation/concatArrays/ + assertExpression( + Stream.concat(Stream.of(1, 2, 3), Stream.of(1, 2, 3)) + .collect(Collectors.toList()), + array123.concat(array123), + // MQL: + "{'$concatArrays': [[1, 2, 3], [1, 2, 3]]}"); } + + @Test + public void sliceTest() { + // https://www.mongodb.com/docs/manual/reference/operator/aggregation/slice/ + assertExpression( + Arrays.asList(1, 2, 3).subList(1, 3), + array123.slice(1, 10), + // MQL: + "{'$slice': [[1, 2, 3], 1, 10]}"); + + ArrayExpression array12345 = ofIntegerArray(1, 2, 3, 4, 5); + // sub-array: skipFirstN + firstN + assertExpression( + Arrays.asList(2, 3), + array12345.slice(1, 2)); + // lastN + firstN + assertExpression( + Arrays.asList(5), + array12345.slice(-1, 100)); + assertExpression( + Arrays.asList(1, 2, 3, 4, 5), + array12345.slice(-100, 100)); + } + + @Test + public void setUnionTest() { + // https://www.mongodb.com/docs/manual/reference/operator/aggregation/setUnion/ (40) + assertExpression( + Arrays.asList(1, 2, 3), + array123.union(array123), + // MQL: + "{'$setUnion': [[1, 2, 3], [1, 2, 3]]}"); + // convenience + assertExpression( + Arrays.asList(1, 2, 3), + ofIntegerArray(1, 2, 1, 3, 3).distinct(), + // MQL: + "{'$setUnion': [[1, 2, 1, 3, 3]]}"); + } + } diff --git a/driver-core/src/test/functional/com/mongodb/client/model/expressions/ComparisonExpressionsFunctionalTest.java b/driver-core/src/test/functional/com/mongodb/client/model/expressions/ComparisonExpressionsFunctionalTest.java index 984119adaa8..ad1ca67a723 100644 --- a/driver-core/src/test/functional/com/mongodb/client/model/expressions/ComparisonExpressionsFunctionalTest.java +++ b/driver-core/src/test/functional/com/mongodb/client/model/expressions/ComparisonExpressionsFunctionalTest.java @@ -17,7 +17,6 @@ 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; @@ -40,12 +39,6 @@ class ComparisonExpressionsFunctionalTest extends AbstractExpressionsFunctionalT // (Complete as of 6.0) // Comparison expressions are part of the the generic Expression class. - static R ofRem() { - // $$REMOVE is intentionally not exposed to users - return new MqlExpression<>((cr) -> new MqlExpression.AstPlaceholder(new BsonString("$$REMOVE"))) - .assertImplementsAllExpressions(); - } - // https://www.mongodb.com/docs/manual/reference/bson-type-comparison-order/#std-label-bson-types-comparison-order private final List sampleValues = Arrays.asList( ofRem(), From 8b69f188e8d9c9cb306f99235bb62e2e2a34d21b Mon Sep 17 00:00:00 2001 From: Maxim Katcharov Date: Tue, 29 Nov 2022 08:48:08 -0700 Subject: [PATCH 07/27] Implement date expressions (#1045) JAVA-4804 --- .../model/expressions/DateExpression.java | 13 ++ .../client/model/expressions/Expressions.java | 2 - .../model/expressions/MqlExpression.java | 71 ++++++++ .../DateExpressionsFunctionalTest.java | 157 ++++++++++++++++++ 4 files changed, 241 insertions(+), 2 deletions(-) create mode 100644 driver-core/src/test/functional/com/mongodb/client/model/expressions/DateExpressionsFunctionalTest.java 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 index 98e8f005769..dd1b2ae5324 100644 --- a/driver-core/src/main/com/mongodb/client/model/expressions/DateExpression.java +++ b/driver-core/src/main/com/mongodb/client/model/expressions/DateExpression.java @@ -20,5 +20,18 @@ * Expresses a date value. */ public interface DateExpression extends Expression { + IntegerExpression year(StringExpression timezone); + IntegerExpression month(StringExpression timezone); + IntegerExpression dayOfMonth(StringExpression timezone); + IntegerExpression dayOfWeek(StringExpression timezone); + IntegerExpression dayOfYear(StringExpression timezone); + IntegerExpression hour(StringExpression timezone); + IntegerExpression minute(StringExpression timezone); + IntegerExpression second(StringExpression timezone); + IntegerExpression week(StringExpression timezone); + IntegerExpression millisecond(StringExpression timezone); + + StringExpression dateToString(); + StringExpression dateToString(StringExpression timezone, StringExpression format); } 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 index 1bb631015f1..28987a8dc01 100644 --- a/driver-core/src/main/com/mongodb/client/model/expressions/Expressions.java +++ b/driver-core/src/main/com/mongodb/client/model/expressions/Expressions.java @@ -33,9 +33,7 @@ import java.time.Instant; import java.util.ArrayList; -import java.util.Arrays; import java.util.List; -import java.util.stream.Collectors; import static com.mongodb.client.model.expressions.MqlExpression.AstPlaceholder; 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 index 218a396b0bc..370f3ce7040 100644 --- a/driver-core/src/main/com/mongodb/client/model/expressions/MqlExpression.java +++ b/driver-core/src/main/com/mongodb/client/model/expressions/MqlExpression.java @@ -332,6 +332,77 @@ public IntegerExpression min(final IntegerExpression i) { return new MqlExpression<>(ast("$min", i)); } + /** @see DateExpression */ + + private MqlExpression usingTimezone(final String name, final StringExpression timezone) { + return new MqlExpression<>((cr) -> astDoc(name, new BsonDocument() + .append("date", this.toBsonValue(cr)) + .append("timezone", extractBsonValue(cr, timezone)))); + } + + @Override + public IntegerExpression year(final StringExpression timezone) { + return usingTimezone("$year", timezone); + } + + @Override + public IntegerExpression month(final StringExpression timezone) { + return usingTimezone("$month", timezone); + } + + @Override + public IntegerExpression dayOfMonth(final StringExpression timezone) { + return usingTimezone("$dayOfMonth", timezone); + } + + @Override + public IntegerExpression dayOfWeek(final StringExpression timezone) { + return usingTimezone("$dayOfWeek", timezone); + } + + @Override + public IntegerExpression dayOfYear(final StringExpression timezone) { + return usingTimezone("$dayOfYear", timezone); + } + + @Override + public IntegerExpression hour(final StringExpression timezone) { + return usingTimezone("$hour", timezone); + } + + @Override + public IntegerExpression minute(final StringExpression timezone) { + return usingTimezone("$minute", timezone); + } + + @Override + public IntegerExpression second(final StringExpression timezone) { + return usingTimezone("$second", timezone); + } + + @Override + public IntegerExpression week(final StringExpression timezone) { + return usingTimezone("$week", timezone); + } + + @Override + public IntegerExpression millisecond(final StringExpression timezone) { + return usingTimezone("$millisecond", timezone); + } + + @Override + public StringExpression dateToString() { + return newMqlExpression((cr) -> astDoc("$dateToString", new BsonDocument() + .append("date", this.toBsonValue(cr)))); + } + + @Override + public StringExpression dateToString(final StringExpression timezone, final StringExpression format) { + return newMqlExpression((cr) -> astDoc("$dateToString", new BsonDocument() + .append("date", this.toBsonValue(cr)) + .append("format", extractBsonValue(cr, format)) + .append("timezone", extractBsonValue(cr, timezone)))); + } /** @see StringExpression */ diff --git a/driver-core/src/test/functional/com/mongodb/client/model/expressions/DateExpressionsFunctionalTest.java b/driver-core/src/test/functional/com/mongodb/client/model/expressions/DateExpressionsFunctionalTest.java new file mode 100644 index 00000000000..73cec9c5198 --- /dev/null +++ b/driver-core/src/test/functional/com/mongodb/client/model/expressions/DateExpressionsFunctionalTest.java @@ -0,0 +1,157 @@ +/* + * 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; + +import java.time.Instant; +import java.time.ZoneId; +import java.time.ZoneOffset; +import java.time.ZonedDateTime; +import java.time.temporal.ChronoField; + +import static com.mongodb.client.model.expressions.Expressions.of; +import static java.time.format.DateTimeFormatter.ISO_LOCAL_DATE_TIME; + +class DateExpressionsFunctionalTest extends AbstractExpressionsFunctionalTest { + // https://www.mongodb.com/docs/manual/reference/operator/aggregation/#date-expression-operators + + private final Instant instant = Instant.parse("2007-12-03T10:15:30.005Z"); + private final DateExpression date = of(instant); + private final ZonedDateTime utcDateTime = ZonedDateTime.ofInstant(instant, ZoneId.of(ZoneOffset.UTC.getId())); + private final StringExpression utc = of("UTC"); + + @Test + public void literalsTest() { + assertExpression( + instant, + date, + "{'$date': '2007-12-03T10:15:30.005Z'}"); + } + + @Test + public void yearTest() { + // https://www.mongodb.com/docs/manual/reference/operator/aggregation/year/ + assertExpression( + utcDateTime.get(ChronoField.YEAR), + date.year(utc), + "{'$year': {'date': {'$date': '2007-12-03T10:15:30.005Z'}, 'timezone': 'UTC'}}"); + } + + @Test + public void monthTest() { + // https://www.mongodb.com/docs/manual/reference/operator/aggregation/month/ + assertExpression( + utcDateTime.get(ChronoField.MONTH_OF_YEAR), + date.month(utc), + "{'$month': {'date': {'$date': '2007-12-03T10:15:30.005Z'}, 'timezone': 'UTC'}}"); + } + + @Test + public void dayOfMonthTest() { + // https://www.mongodb.com/docs/manual/reference/operator/aggregation/dayOfMonth/ + assertExpression( + utcDateTime.get(ChronoField.DAY_OF_MONTH), + date.dayOfMonth(utc), + "{'$dayOfMonth': {'date': {'$date': '2007-12-03T10:15:30.005Z'}, 'timezone': 'UTC'}}"); + } + + @Test + public void dayOfWeekTest() { + // https://www.mongodb.com/docs/manual/reference/operator/aggregation/dayOfWeek/ + assertExpression( + utcDateTime.get(ChronoField.DAY_OF_WEEK) + 1, + date.dayOfWeek(utc), + "{'$dayOfWeek': {'date': {'$date': '2007-12-03T10:15:30.005Z'}, 'timezone': 'UTC'}}"); + } + + @Test + public void dayOfYearTest() { + // https://www.mongodb.com/docs/manual/reference/operator/aggregation/dayOfYear/ + assertExpression( + utcDateTime.get(ChronoField.DAY_OF_YEAR), + date.dayOfYear(utc), + "{'$dayOfYear': {'date': {'$date': '2007-12-03T10:15:30.005Z'}, 'timezone': 'UTC'}}"); + } + + @Test + public void hourTest() { + // https://www.mongodb.com/docs/manual/reference/operator/aggregation/hour/ + assertExpression( + utcDateTime.get(ChronoField.HOUR_OF_DAY), + date.hour(utc), + "{'$hour': {'date': {'$date': '2007-12-03T10:15:30.005Z'}, 'timezone': 'UTC'}}"); + } + + @Test + public void minuteTest() { + // https://www.mongodb.com/docs/manual/reference/operator/aggregation/minute/ + assertExpression( + utcDateTime.get(ChronoField.MINUTE_OF_HOUR), + date.minute(utc), + "{'$minute': {'date': {'$date': '2007-12-03T10:15:30.005Z'}, 'timezone': 'UTC'}}"); + } + + @Test + public void secondTest() { + // https://www.mongodb.com/docs/manual/reference/operator/aggregation/second/ + assertExpression( + utcDateTime.get(ChronoField.SECOND_OF_MINUTE), + date.second(utc), + "{'$second': {'date': {'$date': '2007-12-03T10:15:30.005Z'}, 'timezone': 'UTC'}}"); + } + + @Test + public void weekTest() { + // https://www.mongodb.com/docs/manual/reference/operator/aggregation/week/ + assertExpression( + 48, + date.week(utc), + "{'$week': {'date': {'$date': '2007-12-03T10:15:30.005Z'}, 'timezone': 'UTC'}}"); + } + + @Test + public void millisecondTest() { + // https://www.mongodb.com/docs/manual/reference/operator/aggregation/millisecond/ + assertExpression( + utcDateTime.get(ChronoField.MILLI_OF_SECOND), + date.millisecond(utc), + "{'$millisecond': {'date': {'$date': '2007-12-03T10:15:30.005Z'}, 'timezone': 'UTC'}}"); + } + + @Test + public void dateToStringTest() { + // https://www.mongodb.com/docs/manual/reference/operator/aggregation/dateToString/ + assertExpression( + instant.toString(), + date.dateToString(), + "{'$dateToString': {'date': {'$date': '2007-12-03T10:15:30.005Z'}}}"); + // with parameters + assertExpression( + utcDateTime.withZoneSameInstant(ZoneId.of("America/New_York")).format(ISO_LOCAL_DATE_TIME), + date.dateToString(of("America/New_York"), of("%Y-%m-%dT%H:%M:%S.%L")), + "{'$dateToString': {'date': {'$date': '2007-12-03T10:15:30.005Z'}, " + + "'format': '%Y-%m-%dT%H:%M:%S.%L', " + + "'timezone': 'America/New_York'}}"); + assertExpression( + utcDateTime.withZoneSameInstant(ZoneId.of("+04:30")).format(ISO_LOCAL_DATE_TIME), + date.dateToString(of("+04:30"), of("%Y-%m-%dT%H:%M:%S.%L")), + "{'$dateToString': {'date': {'$date': '2007-12-03T10:15:30.005Z'}, " + + "'format': '%Y-%m-%dT%H:%M:%S.%L', " + + "'timezone': '+04:30'}}"); + } +} From 47efc90222dba6315950de884a6975d9e809ac68 Mon Sep 17 00:00:00 2001 From: Maxim Katcharov Date: Tue, 13 Dec 2022 14:42:27 -0700 Subject: [PATCH 08/27] Implement conversion/type expressions (#1050) JAVA-4802 --- .../model/expressions/ArrayExpression.java | 4 +- .../model/expressions/DateExpression.java | 3 +- .../client/model/expressions/Expression.java | 13 + .../client/model/expressions/Expressions.java | 6 +- .../model/expressions/MqlExpression.java | 137 ++++++++-- .../model/expressions/NumberExpression.java | 2 + .../model/expressions/StringExpression.java | 12 +- .../AbstractExpressionsFunctionalTest.java | 12 +- .../ArithmeticExpressionsFunctionalTest.java | 46 +++- .../ArrayExpressionsFunctionalTest.java | 42 +++- .../ComparisonExpressionsFunctionalTest.java | 15 +- .../DateExpressionsFunctionalTest.java | 25 +- .../StringExpressionsFunctionalTest.java | 10 +- .../TypeExpressionsFunctionalTest.java | 234 ++++++++++++++++++ 14 files changed, 483 insertions(+), 78 deletions(-) create mode 100644 driver-core/src/test/functional/com/mongodb/client/model/expressions/TypeExpressionsFunctionalTest.java 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 index 99f7da587fa..342c13e91c4 100644 --- a/driver-core/src/main/com/mongodb/client/model/expressions/ArrayExpression.java +++ b/driver-core/src/main/com/mongodb/client/model/expressions/ArrayExpression.java @@ -77,7 +77,7 @@ default T elementAt(final int i) { BooleanExpression contains(T contains); - ArrayExpression concat(ArrayExpression array); + ArrayExpression concat(ArrayExpression array); ArrayExpression slice(IntegerExpression start, IntegerExpression length); @@ -85,7 +85,7 @@ default ArrayExpression slice(final int start, final int length) { return this.slice(of(start), of(length)); } - ArrayExpression union(ArrayExpression set); + ArrayExpression union(ArrayExpression set); ArrayExpression distinct(); } 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 index dd1b2ae5324..04eb6b00ea5 100644 --- a/driver-core/src/main/com/mongodb/client/model/expressions/DateExpression.java +++ b/driver-core/src/main/com/mongodb/client/model/expressions/DateExpression.java @@ -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); } 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 index ac0b9dcbbef..1ef5e6460ef 100644 --- a/driver-core/src/main/com/mongodb/client/model/expressions/Expression.java +++ b/driver-core/src/main/com/mongodb/client/model/expressions/Expression.java @@ -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 isArrayOr(ArrayExpression or); + T isDocumentOr(T or); + StringExpression asString(); } 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 index 28987a8dc01..511984ec6c3 100644 --- a/driver-core/src/main/com/mongodb/client/model/expressions/Expressions.java +++ b/driver-core/src/main/com/mongodb/client/model/expressions/Expressions.java @@ -184,7 +184,7 @@ public static ArrayExpression 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 @@ -193,7 +193,9 @@ public static DocumentExpression ofDocument(final Bson document) { document.toBsonDocument(BsonDocument.class, cr)))); } - public static 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(); } 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 index 370f3ce7040..e67044a7315 100644 --- a/driver-core/src/main/com/mongodb/client/model/expressions/MqlExpression.java +++ b/driver-core/src/main/com/mongodb/client/model/expressions/MqlExpression.java @@ -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 implements Expression, BooleanExpression, IntegerExpression, NumberExpression, StringExpression, DateExpression, DocumentExpression, ArrayExpression { @@ -61,6 +64,12 @@ private Function 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 astWrapped(final String name) { + return (cr) -> new AstPlaceholder(new BsonDocument(name, + new BsonArray(Collections.singletonList(this.toBsonValue(cr))))); + } + private Function ast(final String name, final Expression param1) { return (cr) -> { BsonArray value = new BsonArray(); @@ -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 isArrayOr(final ArrayExpression or) { + // TODO it seems that ArrEx does not make sense here + return (ArrayExpression) this.isArray().cond(this.assertImplementsAllExpressions(), or); + } + + public BooleanExpression isDocument() { + return new MqlExpression<>(ast("$type")).eq(of("object")); + } + + @Override + public R isDocumentOr(final R or) { + return this.isDocument().cond(this.assertImplementsAllExpressions(), or); + } + + @Override + public StringExpression asString() { + return new MqlExpression<>(astWrapped("$toString")); + } + + private Function 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 @@ -191,10 +274,7 @@ public T reduce(final T initialValue, final BinaryOperator 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 @@ -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(); } @@ -233,7 +307,7 @@ public BooleanExpression contains(final T item) { } @Override - public ArrayExpression concat(final ArrayExpression array) { + public ArrayExpression concat(final ArrayExpression array) { return new MqlExpression<>(ast("$concatArrays", array)) .assertImplementsAllExpressions(); } @@ -245,17 +319,14 @@ public ArrayExpression slice(final IntegerExpression start, final IntegerExpr } @Override - public ArrayExpression union(final ArrayExpression set) { + public ArrayExpression union(final ArrayExpression set) { return new MqlExpression<>(ast("$setUnion", set)) .assertImplementsAllExpressions(); } @Override public ArrayExpression 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")); } @@ -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)); @@ -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 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 index bbef9f5768f..f08b9a57d00 100644 --- a/driver-core/src/main/com/mongodb/client/model/expressions/NumberExpression.java +++ b/driver-core/src/main/com/mongodb/client/model/expressions/NumberExpression.java @@ -54,4 +54,6 @@ default NumberExpression subtract(final Number subtract) { NumberExpression round(IntegerExpression place); NumberExpression abs(); + + DateExpression millisecondsToDate(); } 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 index a0812878137..c5cc7a8eb72 100644 --- a/driver-core/src/main/com/mongodb/client/model/expressions/StringExpression.java +++ b/driver-core/src/main/com/mongodb/client/model/expressions/StringExpression.java @@ -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); } 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 index 4b966294027..9a4899a0f76 100644 --- 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 @@ -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(); @@ -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); } @@ -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); } @@ -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 stages = new ArrayList<>(); diff --git a/driver-core/src/test/functional/com/mongodb/client/model/expressions/ArithmeticExpressionsFunctionalTest.java b/driver-core/src/test/functional/com/mongodb/client/model/expressions/ArithmeticExpressionsFunctionalTest.java index 4e156c86516..a1e289270d8 100644 --- a/driver-core/src/test/functional/com/mongodb/client/model/expressions/ArithmeticExpressionsFunctionalTest.java +++ b/driver-core/src/test/functional/com/mongodb/client/model/expressions/ArithmeticExpressionsFunctionalTest.java @@ -89,15 +89,27 @@ public void multiplyTest() { assertExpression(2, of(1).multiply(2)); } + @SuppressWarnings("PointlessArithmeticExpression") @Test public void divideTest() { // https://www.mongodb.com/docs/manual/reference/operator/aggregation/divide/ + assertExpression( + 2.0 / 1.0, + of(2.0).divide(of(1.0)), + "{'$divide': [2.0, 1.0]}"); + + // division always converts to a double: + assertExpression( + 2.0, // not: 2 / 1 + of(2).divide(of(1)), + "{'$divide': [2, 1]}"); + + // this means that unlike Java's 1/2==0, dividing any underlying + // BSON number type always yields an equal result: assertExpression( 1.0 / 2.0, of(1.0).divide(of(2.0)), "{'$divide': [1.0, 2.0]}"); - // unlike Java's 1/2==0, dividing any type of numbers always yields an - // equal result, in this case represented using a double. assertExpression( 0.5, of(1).divide(of(2)), @@ -117,6 +129,11 @@ public void divideTest() { assertExpression( Decimal128.parse("2.524218750000"), of(3.231).divide(of(Decimal128.parse("1.28")))); + // this is not simply because the Java literal used has no corresponding + // double value - it is the same value as-written: + assertEquals("3.231", "" + 3.231); + assertEquals("1.28", "" + 1.28); + // convenience assertExpression(0.5, of(1.0).divide(2.0)); @@ -139,6 +156,17 @@ public void addTest() { of(2.0).add(of(2)), "{'$add': [2.0, 2]}"); + // overflows into a supported underlying type + assertExpression( + Integer.MAX_VALUE + 2L, + of(Integer.MAX_VALUE).add(of(2))); + assertExpression( + Long.MAX_VALUE + 2.0, + of(Long.MAX_VALUE).add(of(2))); + assertExpression( + Double.POSITIVE_INFINITY, + of(Double.MAX_VALUE).add(of(Double.MAX_VALUE))); + // convenience assertExpression(3.0, of(1.0).add(2.0)); assertExpression(3L, of(1).add(2L)); @@ -183,7 +211,7 @@ public void maxTest() { @Test public void minTest() { - // https://www.mongodb.com/docs/manual/reference/operator/aggregation/min/ (63) + // https://www.mongodb.com/docs/manual/reference/operator/aggregation/min/ IntegerExpression actual = of(-2).min(of(2)); assertExpression( Math.min(-2, 2), @@ -225,11 +253,21 @@ public void roundTest() { 600.0, of(555.555).round(of(-2)), "{'$round': [555.555, -2]} "); + // underlying type rounds to same underlying type + assertExpression( + 5L, + of(5L).round()); + assertExpression( + 5.0, + of(5.0).round()); + assertExpression( + Decimal128.parse("1234"), + of(Decimal128.parse("1234.2")).round()); } @Test public void absTest() { - // https://www.mongodb.com/docs/manual/reference/operator/aggregation/round/ (?) + // https://www.mongodb.com/docs/manual/reference/operator/aggregation/round/ assertExpression( Math.abs(-2.0), of(-2.0).abs(), diff --git a/driver-core/src/test/functional/com/mongodb/client/model/expressions/ArrayExpressionsFunctionalTest.java b/driver-core/src/test/functional/com/mongodb/client/model/expressions/ArrayExpressionsFunctionalTest.java index e38058551e7..24145443c23 100644 --- a/driver-core/src/test/functional/com/mongodb/client/model/expressions/ArrayExpressionsFunctionalTest.java +++ b/driver-core/src/test/functional/com/mongodb/client/model/expressions/ArrayExpressionsFunctionalTest.java @@ -16,6 +16,7 @@ package com.mongodb.client.model.expressions; +import com.mongodb.MongoCommandException; import org.bson.types.Decimal128; import org.junit.jupiter.api.Test; @@ -33,6 +34,7 @@ import static com.mongodb.client.model.expressions.Expressions.ofIntegerArray; import static com.mongodb.client.model.expressions.Expressions.ofNumberArray; import static com.mongodb.client.model.expressions.Expressions.ofStringArray; +import static org.junit.jupiter.api.Assertions.assertThrows; @SuppressWarnings({"ConstantConditions", "Convert2MethodRef"}) class ArrayExpressionsFunctionalTest extends AbstractExpressionsFunctionalTest { @@ -177,17 +179,27 @@ public void elementAtTest() { array123.elementAt((IntegerExpression) of(0.0)), // MQL: "{'$arrayElemAt': [[1, 2, 3], 0.0]}"); - + // negatives assertExpression( Arrays.asList(1, 2, 3).get(3 - 1), array123.elementAt(-1)); + // underlying long + assertExpression( + 2, + array123.elementAt(of(1L))); assertExpression( - true, - ofRem().eq(array123.elementAt(99))); + MISSING, + array123.elementAt(99)); + assertExpression( - true, - ofRem().eq(array123.elementAt(-99))); + MISSING, + array123.elementAt(-99)); + + // long values are considered entirely out of bounds; server error + assertThrows(MongoCommandException.class, () -> assertExpression( + MISSING, + array123.elementAt(of(Long.MAX_VALUE)))); } @Test @@ -209,6 +221,12 @@ public void lastTest() { array123.last(), // MQL: "{'$last': [[1, 2, 3]]}"); + + assertExpression( + MISSING, + ofIntegerArray().last(), + // MQL: + "{'$last': [[]]}"); } @Test @@ -228,9 +246,13 @@ public void concatTest() { assertExpression( Stream.concat(Stream.of(1, 2, 3), Stream.of(1, 2, 3)) .collect(Collectors.toList()), - array123.concat(array123), + ofIntegerArray(1, 2, 3).concat(ofIntegerArray(1, 2, 3)), // MQL: "{'$concatArrays': [[1, 2, 3], [1, 2, 3]]}"); + // mixed types: + assertExpression( + Arrays.asList(1.0, 1, 2, 3), + ofNumberArray(1.0).concat(ofIntegerArray(1, 2, 3))); } @Test @@ -258,12 +280,18 @@ public void sliceTest() { @Test public void setUnionTest() { - // https://www.mongodb.com/docs/manual/reference/operator/aggregation/setUnion/ (40) + // https://www.mongodb.com/docs/manual/reference/operator/aggregation/setUnion/ assertExpression( Arrays.asList(1, 2, 3), array123.union(array123), // MQL: "{'$setUnion': [[1, 2, 3], [1, 2, 3]]}"); + + // mixed types: + assertExpression( + Arrays.asList(1, 2.0, 3), + // above is a set; in case of flakiness, below should `sort` (not implemented at time of test creation) + ofNumberArray(2.0).union(ofIntegerArray(1, 2, 3))); // convenience assertExpression( Arrays.asList(1, 2, 3), diff --git a/driver-core/src/test/functional/com/mongodb/client/model/expressions/ComparisonExpressionsFunctionalTest.java b/driver-core/src/test/functional/com/mongodb/client/model/expressions/ComparisonExpressionsFunctionalTest.java index ad1ca67a723..0587a6d7871 100644 --- a/driver-core/src/test/functional/com/mongodb/client/model/expressions/ComparisonExpressionsFunctionalTest.java +++ b/driver-core/src/test/functional/com/mongodb/client/model/expressions/ComparisonExpressionsFunctionalTest.java @@ -27,7 +27,6 @@ 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; @@ -47,12 +46,12 @@ class ComparisonExpressionsFunctionalTest extends AbstractExpressionsFunctionalT 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("{'':''}")), + of(BsonDocument.parse("{}")), + of(BsonDocument.parse("{a: 1}")), + of(BsonDocument.parse("{a: 2}")), + of(BsonDocument.parse("{a: 2, b: 1}")), + of(BsonDocument.parse("{b: 1, a: 2}")), + of(BsonDocument.parse("{'':''}")), ofIntegerArray(0), ofIntegerArray(1), ofBooleanArray(true), @@ -70,7 +69,7 @@ public void eqTest() { "{'$eq': [1, 2]}"); assertExpression( false, - ofDocument(BsonDocument.parse("{}")).eq(ofIntegerArray()), + of(BsonDocument.parse("{}")).eq(ofIntegerArray()), "{'$eq': [{'$literal': {}}, []]}"); // numbers are equal, even though of different types diff --git a/driver-core/src/test/functional/com/mongodb/client/model/expressions/DateExpressionsFunctionalTest.java b/driver-core/src/test/functional/com/mongodb/client/model/expressions/DateExpressionsFunctionalTest.java index 73cec9c5198..d801297bbbb 100644 --- a/driver-core/src/test/functional/com/mongodb/client/model/expressions/DateExpressionsFunctionalTest.java +++ b/driver-core/src/test/functional/com/mongodb/client/model/expressions/DateExpressionsFunctionalTest.java @@ -25,8 +25,9 @@ import java.time.temporal.ChronoField; import static com.mongodb.client.model.expressions.Expressions.of; -import static java.time.format.DateTimeFormatter.ISO_LOCAL_DATE_TIME; +import static org.junit.jupiter.api.Assertions.assertThrows; +@SuppressWarnings("ConstantConditions") class DateExpressionsFunctionalTest extends AbstractExpressionsFunctionalTest { // https://www.mongodb.com/docs/manual/reference/operator/aggregation/#date-expression-operators @@ -41,6 +42,7 @@ public void literalsTest() { instant, date, "{'$date': '2007-12-03T10:15:30.005Z'}"); + assertThrows(IllegalArgumentException.class, () -> of((Instant) null)); } @Test @@ -133,25 +135,4 @@ public void millisecondTest() { "{'$millisecond': {'date': {'$date': '2007-12-03T10:15:30.005Z'}, 'timezone': 'UTC'}}"); } - @Test - public void dateToStringTest() { - // https://www.mongodb.com/docs/manual/reference/operator/aggregation/dateToString/ - assertExpression( - instant.toString(), - date.dateToString(), - "{'$dateToString': {'date': {'$date': '2007-12-03T10:15:30.005Z'}}}"); - // with parameters - assertExpression( - utcDateTime.withZoneSameInstant(ZoneId.of("America/New_York")).format(ISO_LOCAL_DATE_TIME), - date.dateToString(of("America/New_York"), of("%Y-%m-%dT%H:%M:%S.%L")), - "{'$dateToString': {'date': {'$date': '2007-12-03T10:15:30.005Z'}, " - + "'format': '%Y-%m-%dT%H:%M:%S.%L', " - + "'timezone': 'America/New_York'}}"); - assertExpression( - utcDateTime.withZoneSameInstant(ZoneId.of("+04:30")).format(ISO_LOCAL_DATE_TIME), - date.dateToString(of("+04:30"), of("%Y-%m-%dT%H:%M:%S.%L")), - "{'$dateToString': {'date': {'$date': '2007-12-03T10:15:30.005Z'}, " - + "'format': '%Y-%m-%dT%H:%M:%S.%L', " - + "'timezone': '+04:30'}}"); - } } diff --git a/driver-core/src/test/functional/com/mongodb/client/model/expressions/StringExpressionsFunctionalTest.java b/driver-core/src/test/functional/com/mongodb/client/model/expressions/StringExpressionsFunctionalTest.java index b4092963c9c..5ba68f3b8b2 100644 --- a/driver-core/src/test/functional/com/mongodb/client/model/expressions/StringExpressionsFunctionalTest.java +++ b/driver-core/src/test/functional/com/mongodb/client/model/expressions/StringExpressionsFunctionalTest.java @@ -60,7 +60,7 @@ public void toLowerTest() { @Test public void toUpperTest() { - // https://www.mongodb.com/docs/manual/reference/operator/aggregation/toUpper/ (?) + // https://www.mongodb.com/docs/manual/reference/operator/aggregation/toUpper/ assertExpression( "abc".toUpperCase(), of("abc").toUpper(), @@ -69,7 +69,7 @@ public void toUpperTest() { @Test public void strLenTest() { - // https://www.mongodb.com/docs/manual/reference/operator/aggregation/strLenCP/ (?) + // https://www.mongodb.com/docs/manual/reference/operator/aggregation/strLenCP/ assertExpression( "abc".codePointCount(0, 3), of("abc").strLen(), @@ -92,7 +92,7 @@ public void strLenTest() { @Test public void strLenBytesTest() { - // https://www.mongodb.com/docs/manual/reference/operator/aggregation/strLenBytes/ (?) + // https://www.mongodb.com/docs/manual/reference/operator/aggregation/strLenBytes/ assertExpression( "abc".getBytes(StandardCharsets.UTF_8).length, of("abc").strLenBytes(), @@ -124,7 +124,7 @@ public void strLenBytesTest() { @Test public void substrTest() { // https://www.mongodb.com/docs/manual/reference/operator/aggregation/substr/ - // https://www.mongodb.com/docs/manual/reference/operator/aggregation/substrCP/ (?) + // https://www.mongodb.com/docs/manual/reference/operator/aggregation/substrCP/ // substr is deprecated, an alias for bytes assertExpression( "abc".substring(1, 1 + 1), @@ -149,7 +149,7 @@ public void substrTest() { @Test public void substrBytesTest() { - // https://www.mongodb.com/docs/manual/reference/operator/aggregation/substrBytes/ (?) + // https://www.mongodb.com/docs/manual/reference/operator/aggregation/substrBytes/ assertExpression( "b", of("abc").substrBytes(of(1), of(1)), diff --git a/driver-core/src/test/functional/com/mongodb/client/model/expressions/TypeExpressionsFunctionalTest.java b/driver-core/src/test/functional/com/mongodb/client/model/expressions/TypeExpressionsFunctionalTest.java new file mode 100644 index 00000000000..1f9daf28a3a --- /dev/null +++ b/driver-core/src/test/functional/com/mongodb/client/model/expressions/TypeExpressionsFunctionalTest.java @@ -0,0 +1,234 @@ +/* + * 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.MongoCommandException; +import org.bson.BsonDocument; +import org.bson.Document; +import org.bson.types.Decimal128; +import org.junit.jupiter.api.Test; + +import java.time.Instant; +import java.time.ZoneId; +import java.time.ZoneOffset; +import java.time.ZonedDateTime; +import java.util.Arrays; + +import static com.mongodb.client.model.expressions.Expressions.of; +import static com.mongodb.client.model.expressions.Expressions.ofIntegerArray; +import static com.mongodb.client.model.expressions.Expressions.ofNull; +import static java.time.format.DateTimeFormatter.ISO_LOCAL_DATE_TIME; +import static org.junit.jupiter.api.Assertions.assertThrows; + +class TypeExpressionsFunctionalTest extends AbstractExpressionsFunctionalTest { + // https://www.mongodb.com/docs/manual/reference/operator/aggregation/#type-expression-operators + + // https://www.mongodb.com/docs/manual/reference/operator/aggregation/type/ + // type is not implemented directly; instead, similar checks done via switch + + @Test + public void isBooleanOrTest() { + assertExpression( + true, + of(true).isBooleanOr(of(false)), + "{'$cond': [{'$eq': [{'$type': true}, 'bool']}, true, false]}"); + // non-boolean: + assertExpression(false, ofIntegerArray(1).isBooleanOr(of(false))); + assertExpression(false, ofNull().isBooleanOr(of(false))); + } + + @Test + public void isNumberOrTest() { + // https://www.mongodb.com/docs/manual/reference/operator/aggregation/isNumber/ + assertExpression(1, of(1).isNumberOr(of(99)), "{'$cond': [{'$isNumber': [1]}, 1, 99]}"); + // other numeric values: + assertExpression(1L, of(1L).isNumberOr(of(99))); + assertExpression(1.0, of(1.0).isNumberOr(of(99))); + assertExpression(Decimal128.parse("1"), of(Decimal128.parse("1")).isNumberOr(of(99))); + // non-numeric: + assertExpression(99, ofIntegerArray(1).isNumberOr(of(99))); + assertExpression(99, ofNull().isNumberOr(of(99))); + } + + @Test + public void isStringOrTest() { + assertExpression( + "abc", + of("abc").isStringOr(of("or")), + "{'$cond': [{'$eq': [{'$type': 'abc'}, 'string']}, 'abc', 'or']}"); + // non-string: + assertExpression("or", ofIntegerArray(1).isStringOr(of("or"))); + assertExpression("or", ofNull().isStringOr(of("or"))); + } + + @Test + public void isDateOrTest() { + Instant date = Instant.parse("2007-12-03T10:15:30.005Z"); + assertExpression( + date, + of(date).isDateOr(of(date.plusMillis(10))), + "{'$cond': [{'$in': [{'$type': {'$date': '2007-12-03T10:15:30.005Z'}}, ['date']]}, " + + "{'$date': '2007-12-03T10:15:30.005Z'}, {'$date': '2007-12-03T10:15:30.015Z'}]}"); + // non-date: + assertExpression(date, ofIntegerArray(1).isDateOr(of(date))); + assertExpression(date, ofNull().isDateOr(of(date))); + } + + @Test + public void isArrayOrTest() { + // https://www.mongodb.com/docs/manual/reference/operator/aggregation/isArray/ + assertExpression( + Arrays.asList(1, 2), + ofIntegerArray(1, 2).isArrayOr(ofIntegerArray(99)), + "{'$cond': [{'$isArray': [[1, 2]]}, [1, 2], [99]]}"); + // non-array: + assertExpression(Arrays.asList(1, 2), of(true).isArrayOr(ofIntegerArray(1, 2))); + assertExpression(Arrays.asList(1, 2), ofNull().isArrayOr(ofIntegerArray(1, 2))); + } + + @Test + public void isDocumentOrTest() { + BsonDocument doc = BsonDocument.parse("{a: 1}"); + assertExpression(doc, + of(doc).isDocumentOr(of(BsonDocument.parse("{b: 2}"))), + "{'$cond': [{'$eq': [{'$type': {'$literal': {'a': 1}}}, 'object']}, " + + "{'$literal': {'a': 1}}, {'$literal': {'b': 2}}]}"); + // non-document: + assertExpression(doc, ofIntegerArray(1).isDocumentOr(of(doc))); + assertExpression(doc, ofNull().isDocumentOr(of(doc))); + } + + // conversions + // https://www.mongodb.com/docs/manual/reference/operator/aggregation/convert/ + // Convert is not implemented: too dynamic, conversions should be explicit. + + @Test + public void asStringTest() { + // https://www.mongodb.com/docs/manual/reference/operator/aggregation/toString/ + // asString, since toString conflicts + assertExpression("false", of(false).asString(), "{'$toString': [false]}"); + + assertExpression("1", of(1).asString()); + assertExpression("1", of(1L).asString()); + assertExpression("1", of(1.0).asString()); + assertExpression("1.0", of(Decimal128.parse("1.0")).asString()); + + assertExpression("abc", of("abc").asString()); + + // this is equivalent to $dateToString + assertExpression("1970-01-01T00:00:00.123Z", of(Instant.ofEpochMilli(123)).asString()); + + // Arrays and documents are not (yet) supported: + assertThrows(MongoCommandException.class, () -> + assertExpression("[]", ofIntegerArray(1, 2).asString())); + assertThrows(MongoCommandException.class, () -> + assertExpression("[1, 2]", ofIntegerArray(1, 2).asString())); + assertThrows(MongoCommandException.class, () -> + assertExpression("{a: 1}", of(Document.parse("{a: 1}")).asString())); + } + + @Test + public void dateAsStringTest() { + // https://www.mongodb.com/docs/manual/reference/operator/aggregation/dateToString/ + final Instant instant = Instant.parse("2007-12-03T10:15:30.005Z"); + DateExpression date = of(instant); + ZonedDateTime utcDateTime = ZonedDateTime.ofInstant(instant, ZoneId.of(ZoneOffset.UTC.getId())); + assertExpression( + "2007-12-03T10:15:30.005Z", + of(instant).asString(), + "{'$toString': [{'$date': '2007-12-03T10:15:30.005Z'}]}"); + // with parameters + assertExpression( + utcDateTime.withZoneSameInstant(ZoneId.of("America/New_York")).format(ISO_LOCAL_DATE_TIME), + date.asString(of("America/New_York"), of("%Y-%m-%dT%H:%M:%S.%L")), + "{'$dateToString': {'date': {'$date': '2007-12-03T10:15:30.005Z'}, " + + "'format': '%Y-%m-%dT%H:%M:%S.%L', " + + "'timezone': 'America/New_York'}}"); + assertExpression( + utcDateTime.withZoneSameInstant(ZoneId.of("+04:30")).format(ISO_LOCAL_DATE_TIME), + date.asString(of("+04:30"), of("%Y-%m-%dT%H:%M:%S.%L")), + "{'$dateToString': {'date': {'$date': '2007-12-03T10:15:30.005Z'}, " + + "'format': '%Y-%m-%dT%H:%M:%S.%L', " + + "'timezone': '+04:30'}}"); + // Olson Timezone Identifier is changed to UTC offset: + assertExpression( + "2007-12-03T05:15:30.005-0500", + of(instant).asString(of("America/New_York"), of("%Y-%m-%dT%H:%M:%S.%L%z"))); + } + + // parse string + + @Test + public void parseDateTest() { + // https://www.mongodb.com/docs/manual/reference/operator/aggregation/dateToString/ + String dateString = "2007-12-03T10:15:30.005Z"; + assertExpression( + Instant.parse(dateString), + of(dateString).parseDate(), + "{'$dateFromString': {'dateString': '2007-12-03T10:15:30.005Z'}}"); + + + // throws: "cannot pass in a date/time string with GMT offset together with a timezone argument" + assertThrows(MongoCommandException.class, () -> + assertExpression(1, of("2007-12-03T10:15:30.005+01:00") + .parseDate(of("+01:00"), of("%Y-%m-%dT%H:%M:%S.%L%z")) + .asString())); + // therefore, to parse date strings containing UTC offsets, we need: + assertExpression( + Instant.parse("2007-12-03T09:15:30.005Z"), + of("2007-12-03T10:15:30.005+01:00") + .parseDate(of("%Y-%m-%dT%H:%M:%S.%L%z")), + "{'$dateFromString': {'dateString': '2007-12-03T10:15:30.005+01:00', " + + "'format': '%Y-%m-%dT%H:%M:%S.%L%z'}}"); + } + + @Test + public void parseIntegerTest() { + // https://www.mongodb.com/docs/manual/reference/operator/aggregation/toInt/ + // https://www.mongodb.com/docs/manual/reference/operator/aggregation/toLong/ + assertExpression( + 1234, + of("1234").parseInteger(), + "{'$convert': {'input': '1234', 'onError': {'$toLong': '1234'}, 'to': 'int'}}"); + + int intVal = 2_000_000_000; + long longVal = 4_000_000_000L; + assertExpression( + intVal, + of(intVal + "").parseInteger()); + assertExpression( + longVal, + of(longVal + "").parseInteger()); + } + + // non-string + + @Test + public void millisecondsToDateTest() { + // https://www.mongodb.com/docs/manual/reference/operator/aggregation/toDate/ + assertExpression( + Instant.ofEpochMilli(1234), + of(1234L).millisecondsToDate(), + "{'$toDate': {'$numberLong': '1234'}}"); + // This does not accept plain integers: + assertThrows(MongoCommandException.class, () -> + assertExpression( + Instant.parse("2007-12-03T10:15:30.005Z"), + of(1234).millisecondsToDate(), + "{'$toDate': 1234}")); + } +} From 3dcd2ffea62ee82778e77db993b169c2a08c7ce2 Mon Sep 17 00:00:00 2001 From: Maxim Katcharov Date: Tue, 13 Dec 2022 14:50:19 -0700 Subject: [PATCH 09/27] Implement document expressions (#1052) JAVA-4782 --- .../model/expressions/DocumentExpression.java | 73 +++++ .../client/model/expressions/Expression.java | 16 +- .../model/expressions/MqlExpression.java | 151 ++++++++++- .../AbstractExpressionsFunctionalTest.java | 4 +- .../ArrayExpressionsFunctionalTest.java | 20 +- .../ComparisonExpressionsFunctionalTest.java | 2 +- .../DocumentExpressionsFunctionalTest.java | 250 ++++++++++++++++++ 7 files changed, 488 insertions(+), 28 deletions(-) create mode 100644 driver-core/src/test/functional/com/mongodb/client/model/expressions/DocumentExpressionsFunctionalTest.java 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 index cf1522d1623..833d9546040 100644 --- a/driver-core/src/main/com/mongodb/client/model/expressions/DocumentExpression.java +++ b/driver-core/src/main/com/mongodb/client/model/expressions/DocumentExpression.java @@ -16,10 +16,83 @@ package com.mongodb.client.model.expressions; +import org.bson.conversions.Bson; +import org.bson.types.Decimal128; + +import java.time.Instant; + +import static com.mongodb.client.model.expressions.Expressions.of; + /** * 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 { + DocumentExpression setField(String fieldName, Expression exp); + + DocumentExpression unsetField(String fieldName); + + Expression getField(String fieldName); + + BooleanExpression getBoolean(String fieldName); + + BooleanExpression getBoolean(String fieldName, BooleanExpression other); + + default BooleanExpression getBoolean(final String fieldName, final boolean other) { + return getBoolean(fieldName, of(other)); + } + + NumberExpression getNumber(String fieldName); + + NumberExpression getNumber(String fieldName, NumberExpression other); + + default NumberExpression getNumber(final String fieldName, final double other) { + return getNumber(fieldName, of(other)); + } + + default NumberExpression getNumber(final String fieldName, final Decimal128 other) { + return getNumber(fieldName, of(other)); + } + + IntegerExpression getInteger(String fieldName); + + IntegerExpression getInteger(String fieldName, IntegerExpression other); + + default IntegerExpression getInteger(final String fieldName, final int other) { + return getInteger(fieldName, of(other)); + } + + default IntegerExpression getInteger(final String fieldName, final long other) { + return getInteger(fieldName, of(other)); + } + + + StringExpression getString(String fieldName); + + StringExpression getString(String fieldName, StringExpression other); + + default StringExpression getString(final String fieldName, final String other) { + return getString(fieldName, of(other)); + } + + DateExpression getDate(String fieldName); + DateExpression getDate(String fieldName, DateExpression other); + + default DateExpression getDate(final String fieldName, final Instant other) { + return getDate(fieldName, of(other)); + } + + DocumentExpression getDocument(String fieldName); + DocumentExpression getDocument(String fieldName, DocumentExpression other); + + default DocumentExpression getDocument(final String fieldName, final Bson other) { + return getDocument(fieldName, of(other)); + } + + ArrayExpression getArray(String fieldName); + + ArrayExpression getArray(String fieldName, ArrayExpression other); + + DocumentExpression merge(DocumentExpression other); } 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 index 1ef5e6460ef..f203175c60b 100644 --- a/driver-core/src/main/com/mongodb/client/model/expressions/Expression.java +++ b/driver-core/src/main/com/mongodb/client/model/expressions/Expression.java @@ -100,14 +100,16 @@ public interface Expression { /** * also checks for nulls - * @param or + * @param other * @return */ - BooleanExpression isBooleanOr(BooleanExpression or); - NumberExpression isNumberOr(NumberExpression or); - StringExpression isStringOr(StringExpression or); - DateExpression isDateOr(DateExpression or); - ArrayExpression isArrayOr(ArrayExpression or); - T isDocumentOr(T or); + BooleanExpression isBooleanOr(BooleanExpression other); + NumberExpression isNumberOr(NumberExpression other); + IntegerExpression isIntegerOr(IntegerExpression other); + StringExpression isStringOr(StringExpression other); + DateExpression isDateOr(DateExpression other); + ArrayExpression isArrayOr(ArrayExpression other); + T isDocumentOr(T other); + StringExpression asString(); } 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 index e67044a7315..7629def2c15 100644 --- a/driver-core/src/main/com/mongodb/client/model/expressions/MqlExpression.java +++ b/driver-core/src/main/com/mongodb/client/model/expressions/MqlExpression.java @@ -137,6 +137,113 @@ public R cond(final R left, final R right) { return newMqlExpression(ast("$cond", left, right)); } + /** @see DocumentExpression */ + + private Function getFieldInternal(final String fieldName) { + return (cr) -> { + BsonValue value = fieldName.startsWith("$") + ? new BsonDocument("$literal", new BsonString(fieldName)) + : new BsonString(fieldName); + return astDoc("$getField", new BsonDocument() + .append("input", this.fn.apply(cr).bsonValue) + .append("field", value)); + }; + } + + @Override + public Expression getField(final String fieldName) { + return new MqlExpression<>(getFieldInternal(fieldName)); + } + + @Override + public BooleanExpression getBoolean(final String fieldName) { + return new MqlExpression<>(getFieldInternal(fieldName)); + } + + @Override + public BooleanExpression getBoolean(final String fieldName, final BooleanExpression other) { + return getBoolean(fieldName).isBooleanOr(other); + } + + @Override + public NumberExpression getNumber(final String fieldName) { + return new MqlExpression<>(getFieldInternal(fieldName)); + } + + @Override + public NumberExpression getNumber(final String fieldName, final NumberExpression other) { + return getNumber(fieldName).isNumberOr(other); + } + + @Override + public IntegerExpression getInteger(final String fieldName) { + return new MqlExpression<>(getFieldInternal(fieldName)); + } + + @Override + public IntegerExpression getInteger(final String fieldName, final IntegerExpression other) { + return getInteger(fieldName).isIntegerOr(other); + } + + @Override + public StringExpression getString(final String fieldName) { + return new MqlExpression<>(getFieldInternal(fieldName)); + } + + @Override + public StringExpression getString(final String fieldName, final StringExpression other) { + return getString(fieldName).isStringOr(other); + } + + @Override + public DateExpression getDate(final String fieldName) { + return new MqlExpression<>(getFieldInternal(fieldName)); + } + + @Override + public DateExpression getDate(final String fieldName, final DateExpression other) { + return getDate(fieldName).isDateOr(other); + } + + @Override + public DocumentExpression getDocument(final String fieldName) { + return new MqlExpression<>(getFieldInternal(fieldName)); + } + + @Override + public DocumentExpression getDocument(final String fieldName, final DocumentExpression other) { + return getDocument(fieldName).isDocumentOr(other); + } + + @Override + public ArrayExpression getArray(final String fieldName) { + return new MqlExpression<>(getFieldInternal(fieldName)); + } + + @Override + public ArrayExpression getArray(final String fieldName, final ArrayExpression other) { + return getArray(fieldName).isArrayOr(other); + } + + @Override + public DocumentExpression merge(final DocumentExpression other) { + return new MqlExpression<>(ast("$mergeObjects", other)); + } + + @Override + public DocumentExpression setField(final String fieldName, final Expression exp) { + return newMqlExpression((cr) -> astDoc("$setField", new BsonDocument() + .append("field", new BsonString(fieldName)) + .append("input", this.toBsonValue(cr)) + .append("value", extractBsonValue(cr, exp)))); + } + + @Override + public DocumentExpression unsetField(final String fieldName) { + return newMqlExpression((cr) -> astDoc("$unsetField", new BsonDocument() + .append("field", new BsonString(fieldName)) + .append("input", this.toBsonValue(cr)))); + } /** @see Expression */ @@ -175,8 +282,8 @@ public BooleanExpression isBoolean() { } @Override - public BooleanExpression isBooleanOr(final BooleanExpression or) { - return this.isBoolean().cond(this, or); + public BooleanExpression isBooleanOr(final BooleanExpression other) { + return this.isBoolean().cond(this, other); } public BooleanExpression isNumber() { @@ -184,8 +291,17 @@ public BooleanExpression isNumber() { } @Override - public NumberExpression isNumberOr(final NumberExpression or) { - return this.isNumber().cond(this, or); + public NumberExpression isNumberOr(final NumberExpression other) { + return this.isNumber().cond(this, other); + } + + public BooleanExpression isInteger() { + return this.isNumber().cond(this.eq(this.round()), of(false)); + } + + @Override + public IntegerExpression isIntegerOr(final IntegerExpression other) { + return this.isInteger().cond(this, other); } public BooleanExpression isString() { @@ -193,8 +309,8 @@ public BooleanExpression isString() { } @Override - public StringExpression isStringOr(final StringExpression or) { - return this.isString().cond(this, or); + public StringExpression isStringOr(final StringExpression other) { + return this.isString().cond(this, other); } public BooleanExpression isDate() { @@ -202,19 +318,26 @@ public BooleanExpression isDate() { } @Override - public DateExpression isDateOr(final DateExpression or) { - return this.isDate().cond(this, or); + public DateExpression isDateOr(final DateExpression other) { + return this.isDate().cond(this, other); } public BooleanExpression isArray() { return new MqlExpression<>(astWrapped("$isArray")); } - @SuppressWarnings("unchecked") // TODO + /** + * checks if array (but cannot check type) + * user asserts array is of type R + * + * @param other + * @return + * @param + */ + @SuppressWarnings("unchecked") @Override - public ArrayExpression isArrayOr(final ArrayExpression or) { - // TODO it seems that ArrEx does not make sense here - return (ArrayExpression) this.isArray().cond(this.assertImplementsAllExpressions(), or); + public ArrayExpression isArrayOr(final ArrayExpression other) { + return (ArrayExpression) this.isArray().cond(this.assertImplementsAllExpressions(), other); } public BooleanExpression isDocument() { @@ -222,8 +345,8 @@ public BooleanExpression isDocument() { } @Override - public R isDocumentOr(final R or) { - return this.isDocument().cond(this.assertImplementsAllExpressions(), or); + public R isDocumentOr(final R other) { + return this.isDocument().cond(this.assertImplementsAllExpressions(), other); } @Override 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 index 9a4899a0f76..a167120f041 100644 --- 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 @@ -40,6 +40,7 @@ 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.bson.conversions.Bson.DEFAULT_CODEC_REGISTRY; import static org.junit.jupiter.api.Assertions.assertEquals; public abstract class AbstractExpressionsFunctionalTest extends OperationTest { @@ -70,7 +71,8 @@ protected void assertExpression(@Nullable final Object expected, final Expressio return; } - BsonValue expressionValue = ((MqlExpression) expression).toBsonValue(fromProviders(new BsonValueCodecProvider())); + BsonValue expressionValue = ((MqlExpression) expression).toBsonValue( + fromProviders(new BsonValueCodecProvider(), DEFAULT_CODEC_REGISTRY)); BsonValue bsonValue = new BsonDocumentFragmentCodec().readValue( new JsonReader(expectedMql), DecoderContext.builder().build()); diff --git a/driver-core/src/test/functional/com/mongodb/client/model/expressions/ArrayExpressionsFunctionalTest.java b/driver-core/src/test/functional/com/mongodb/client/model/expressions/ArrayExpressionsFunctionalTest.java index 24145443c23..69f3399da02 100644 --- a/driver-core/src/test/functional/com/mongodb/client/model/expressions/ArrayExpressionsFunctionalTest.java +++ b/driver-core/src/test/functional/com/mongodb/client/model/expressions/ArrayExpressionsFunctionalTest.java @@ -17,6 +17,7 @@ package com.mongodb.client.model.expressions; import com.mongodb.MongoCommandException; +import org.bson.Document; import org.bson.types.Decimal128; import org.junit.jupiter.api.Test; @@ -79,13 +80,21 @@ public void literalsTest() { Arrays.asList(Instant.parse("2007-12-03T10:15:30.00Z")), ofDateArray(Instant.parse("2007-12-03T10:15:30.00Z")), "[{'$date': '2007-12-03T10:15:30.00Z'}]"); + // Document - // ... + ArrayExpression documentArray = ofArray( + of(Document.parse("{a: 1}")), + of(Document.parse("{b: 2}"))); + assertExpression( + Arrays.asList(Document.parse("{a: 1}"), Document.parse("{b: 2}")), + documentArray, + "[{'$literal': {'a': 1}}, {'$literal': {'b': 2}}]"); // Array - ArrayExpression> arrays = ofArray(ofArray(), ofArray()); + ArrayExpression> arrayArray = ofArray(ofArray(), ofArray()); assertExpression( - Arrays.asList(Collections.emptyList(), Collections.emptyList()), arrays, + Arrays.asList(Collections.emptyList(), Collections.emptyList()), + arrayArray, "[[], []]"); // Mixed @@ -176,9 +185,10 @@ public void elementAtTest() { assertExpression( Arrays.asList(1, 2, 3).get(0), // 0.0 is a valid integer value - array123.elementAt((IntegerExpression) of(0.0)), + array123.elementAt(of(0.0).isIntegerOr(of(-1))), // MQL: - "{'$arrayElemAt': [[1, 2, 3], 0.0]}"); + "{'$arrayElemAt': [[1, 2, 3], {'$cond': [{'$cond': " + + "[{'$isNumber': [0.0]}, {'$eq': [0.0, {'$round': 0.0}]}, false]}, 0.0, -1]}]}"); // negatives assertExpression( Arrays.asList(1, 2, 3).get(3 - 1), diff --git a/driver-core/src/test/functional/com/mongodb/client/model/expressions/ComparisonExpressionsFunctionalTest.java b/driver-core/src/test/functional/com/mongodb/client/model/expressions/ComparisonExpressionsFunctionalTest.java index 0587a6d7871..ef68da2d394 100644 --- a/driver-core/src/test/functional/com/mongodb/client/model/expressions/ComparisonExpressionsFunctionalTest.java +++ b/driver-core/src/test/functional/com/mongodb/client/model/expressions/ComparisonExpressionsFunctionalTest.java @@ -36,7 +36,7 @@ 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. + // Comparison expressions are part of the generic Expression class. // https://www.mongodb.com/docs/manual/reference/bson-type-comparison-order/#std-label-bson-types-comparison-order private final List sampleValues = Arrays.asList( diff --git a/driver-core/src/test/functional/com/mongodb/client/model/expressions/DocumentExpressionsFunctionalTest.java b/driver-core/src/test/functional/com/mongodb/client/model/expressions/DocumentExpressionsFunctionalTest.java new file mode 100644 index 00000000000..a00a16f5d27 --- /dev/null +++ b/driver-core/src/test/functional/com/mongodb/client/model/expressions/DocumentExpressionsFunctionalTest.java @@ -0,0 +1,250 @@ +/* + * 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.Document; +import org.bson.conversions.Bson; +import org.bson.types.Decimal128; +import org.junit.jupiter.api.Test; + +import java.time.Instant; +import java.util.Arrays; + +import static com.mongodb.client.model.expressions.Expressions.of; +import static com.mongodb.client.model.expressions.Expressions.ofIntegerArray; +import static org.junit.jupiter.api.Assertions.assertThrows; + +@SuppressWarnings("ConstantConditions") +class DocumentExpressionsFunctionalTest extends AbstractExpressionsFunctionalTest { + // https://www.mongodb.com/docs/manual/reference/operator/aggregation/#object-expression-operators + // (Complete as of 6.0) + + private static DocumentExpression ofDoc(final String ofDoc) { + return of(BsonDocument.parse(ofDoc)); + } + + private final DocumentExpression a1 = ofDoc("{a: 1}"); + private final DocumentExpression ax1ay2 = ofDoc("{a: {x: 1, y: 2}}"); + + @Test + public void literalsTest() { + assertExpression( + BsonDocument.parse("{'a': 1}"), + ofDoc("{a: 1}"), + "{'$literal': {'a': 1}}"); + assertThrows(IllegalArgumentException.class, () -> of((Bson) null)); + // doc inside doc + assertExpression( + BsonDocument.parse("{'a': {'x': 1, 'y': 2}}"), + ofDoc("{a: {x: 1, y: 2}}"), + "{'$literal': {'a': {'x': 1, 'y': 2}}}"); + // empty + assertExpression( + BsonDocument.parse("{}"), + ofDoc("{}"), + "{'$literal': {}}"); + // ensure is literal + assertExpression(BsonDocument.parse( + "{'lit': {'$not': true}}"), + of(BsonDocument.parse("{lit: {'$not': true} }")), + "{'$literal': {'lit': {'$not': true}}}"); + } + + @Test + public void getFieldTest() { + // https://www.mongodb.com/docs/manual/reference/operator/aggregation/getField/ (100) + // these count as assertions by the user that the value is of the correct type + + assertExpression(1, + a1.getField("a"), + "{'$getField': {'input': {'$literal': {'a': 1}}, 'field': 'a'}}"); + assertExpression(2, + a1.getInteger("a").multiply(2), + "{'$multiply': [{'$getField': {'input': {'$literal': {'a': 1}}, 'field': 'a'}}, 2]}"); + + // different types + String getFieldMql = "{'$getField': {'input': {'$literal': {'a': 1}}, 'field': 'a'}}"; + assertExpression(1, a1.getNumber("a"), getFieldMql); + // these are all violations, since they assert the wrong type, but we are testing the generated Mql: + assertExpression(1, a1.getBoolean("a"), getFieldMql); + assertExpression(1, a1.getInteger("a"), getFieldMql); + assertExpression(1, a1.getString("a"), getFieldMql); + assertExpression(1, a1.getDate("a"), getFieldMql); + assertExpression(1, a1.getArray("a"), getFieldMql); + assertExpression(1, a1.getDocument("a"), getFieldMql); + // usage with other expressions + assertExpression(false, ofDoc("{a: true}").getBoolean("a").not()); + assertExpression(0.5, ofDoc("{a: 1.0}").getNumber("a").divide(2)); + assertExpression(8, ofIntegerArray(9, 8, 7).elementAt(ofDoc("{a: 1.0}").getInteger("a"))); + assertExpression("a", ofDoc("{a: 'A'}").getString("a").toLower()); + assertExpression(12, ofDoc("{a: {'$date': '2007-12-03T10:15:30.005Z'}}") + .getDate("a").month(of("UTC"))); + assertExpression(3, ofDoc("{a: [3, 2]}").getArray("a").first()); + assertExpression(2, ofDoc("{a: {b: 2}}").getDocument("a").getInteger("b")); + + // field names, not paths + DocumentExpression doc = ofDoc("{a: {b: 2}, 'a.b': 3, 'a$b': 4, '$a.b': 5}"); + assertExpression(2, doc.getDocument("a").getInteger("b")); + assertExpression(3, doc.getInteger("a.b")); + assertExpression(4, doc.getInteger("a$b")); + assertExpression(5, + doc.getInteger("$a.b"), + "{'$getField': {'input': {'$literal': {'a': {'b': 2}, 'a.b': 3, 'a$b': 4, '$a.b': 5}}, " + + "'field': {'$literal': '$a.b'}}}"); + } + + @Test + public void getFieldOrTest() { + // convenience + assertExpression(true, ofDoc("{a: true}").getBoolean("a", false)); + assertExpression(1.0, ofDoc("{a: 1.0}").getNumber("a", 99)); + assertExpression(1.0, ofDoc("{a: 1.0}").getNumber("a", Decimal128.parse("99"))); + assertExpression("A", ofDoc("{a: 'A'}").getString("a", "Z")); + assertExpression(2007, ofDoc("{a: {'$date': '2007-12-03T10:15:30.005Z'}}") + .getDate("a", Instant.EPOCH).year(of("UTC"))); + // no convenience for arrays + assertExpression(Document.parse("{b: 2}"), ofDoc("{a: {b: 2}}") + .getDocument("a", Document.parse("{z: 99}"))); + + // normal + assertExpression(true, ofDoc("{a: true}").getBoolean("a", of(false))); + assertExpression(1.0, ofDoc("{a: 1.0}").getNumber("a", of(99))); + assertExpression(1.0, ofDoc("{a: 1.0}").getInteger("a", of(99))); + assertExpression("A", ofDoc("{a: 'A'}").getString("a", of("Z"))); + assertExpression(2007, ofDoc("{a: {'$date': '2007-12-03T10:15:30.005Z'}}") + .getDate("a", of(Instant.EPOCH)).year(of("UTC"))); + assertExpression(Arrays.asList(3, 2), ofDoc("{a: [3, 2]}").getArray("a", ofIntegerArray(99, 88))); + assertExpression(Document.parse("{b: 2}"), ofDoc("{a: {b: 2}}") + .getDocument("a", of(Document.parse("{z: 99}")))); + + // right branch (missing field) + assertExpression(false, ofDoc("{}").getBoolean("a", false)); + assertExpression(99, ofDoc("{}").getInteger("a", 99)); + assertExpression(99, ofDoc("{}").getNumber("a", 99)); + assertExpression(99L, ofDoc("{}").getNumber("a", 99L)); + assertExpression(99.0, ofDoc("{}").getNumber("a", 99.0)); + assertExpression(Decimal128.parse("99"), ofDoc("{}").getNumber("a", Decimal128.parse("99"))); + assertExpression("Z", ofDoc("{}").getString("a", "Z")); + assertExpression(1970, ofDoc("{}") + .getDate("a", Instant.EPOCH).year(of("UTC"))); + assertExpression(Arrays.asList(99, 88), ofDoc("{}").getArray("a", ofIntegerArray(99, 88))); + assertExpression(Document.parse("{z: 99}"), ofDoc("{}") + .getDocument("a", Document.parse("{z: 99}"))); + + // int vs num + assertExpression(99, ofDoc("{a: 1.1}").getInteger("a", of(99))); + } + + @Test + public void getFieldMissingTest() { + // missing fields + assertExpression( + BsonDocument.parse("{'a': 1}"), + a1.setField("z", a1.getBoolean("missing"))); + assertExpression( + BsonDocument.parse("{'a': 1}"), + a1.setField("z", a1.getDocument("missing").getDocument("also_missing"))); + assertExpression( + BsonDocument.parse("{'a': 1, 'z': ''}"), + a1.setField("z", a1.getString("missing").toLower())); + /* + The behaviour of missing fields appears to be as follows, and equivalent to $$REMOVE: + propagates -- getField, cond branches... + false -- not, or, cond check... + 0 -- sum... + "" -- toLower... + null -- multiply, add, subtract, year, filter, reduce, map, result within map... + */ + } + + @Test + public void setFieldTest() { + // https://www.mongodb.com/docs/manual/reference/operator/aggregation/setField/ + // Placing a field based on a literal: + assertExpression( + BsonDocument.parse("{a: 1, r: 2}"), // map.put("r", 2) + a1.setField("r", of(2)), + // MQL: + "{'$setField': {'field': 'r', 'input': {'$literal': {'a': 1}}, 'value': 2}}"); + + // Placing a null value: + assertExpression( + BsonDocument.parse("{a: 1, r: null}"), // map.put("r", null) + a1.setField("r", Expressions.ofNull()), + // MQL: + "{'$setField': {'field': 'r', 'input': {'$literal': {'a': 1}}, 'value': null}}"); + + // Replacing a field based on its prior value: + assertExpression( + BsonDocument.parse("{a: 3}"), // map.put("a", map.get("a") * 3) + a1.setField("a", a1.getInteger("a").multiply(3)), + // MQL: + "{'$setField': {'field': 'a', 'input': {'$literal': {'a': 1}}, 'value': " + + "{'$multiply': [{'$getField': {'input': {'$literal': {'a': 1}}, 'field': 'a'}}, 3]}}}"); + + // Placing a field based on a nested object: + assertExpression( + BsonDocument.parse("{'a': {'x': 1, 'y': 2}, r: 10}"), + ax1ay2.setField("r", ax1ay2.getDocument("a").getInteger("x").multiply(10)), + // MQL: + "{'$setField': {'field': 'r', 'input': {'$literal': {'a': {'x': 1, 'y': 2}}}, " + + "'value': {'$multiply': [" + + " {'$getField': {'input': {'$getField': {'input': {'$literal': {'a': {'x': 1, 'y': 2}}}, " + + " 'field': 'a'}}, 'field': 'x'}}, 10]}}}"); + + // Replacing a nested object requires two setFields, as expected: + assertExpression( + // "with" syntax: [ { a:{x:1,y:2} } ].map(d -> d.with("a", d.a.with("y", d.a.y.multiply(11)))) + BsonDocument.parse("{'a': {'x': 1, 'y': 22}}"), + ax1ay2.setField("a", ax1ay2.getDocument("a") + .setField("y", ax1ay2.getDocument("a").getInteger("y").multiply(11))), + "{'$setField': {'field': 'a', 'input': {'$literal': {'a': {'x': 1, 'y': 2}}}, " + + "'value': {'$setField': {'field': 'y', 'input': {'$getField': " + + "{'input': {'$literal': {'a': {'x': 1, 'y': 2}}}, 'field': 'a'}}, " + + "'value': {'$multiply': [{'$getField': {'input': {'$getField': " + + "{'input': {'$literal': {'a': {'x': 1, 'y': 2}}}, 'field': 'a'}}, " + + "'field': 'y'}}, 11]}}}}}"); + } + + @Test + public void unsetFieldTest() { + // https://www.mongodb.com/docs/manual/reference/operator/aggregation/unsetField/ + assertExpression( + BsonDocument.parse("{}"), // map.remove("a") + a1.unsetField("a"), + // MQL: + "{'$unsetField': {'field': 'a', 'input': {'$literal': {'a': 1}}}}"); + } + + @Test + public void mergeTest() { + // https://www.mongodb.com/docs/manual/reference/operator/aggregation/mergeObjects/ + assertExpression( + BsonDocument.parse("{a: 1, b: 2}"), + ofDoc("{a: 1}").merge(ofDoc("{b: 2}")), + "{'$mergeObjects': [{'$literal': {'a': 1}}, {'$literal': {'b': 2}}]}"); + + assertExpression( + BsonDocument.parse("{a: null}"), + ofDoc("{a: 1}").merge(ofDoc("{a: null}"))); + + assertExpression( + BsonDocument.parse("{a: 1}"), + ofDoc("{a: null}").merge(ofDoc("{a: 1}"))); + } +} From 1ce49121fd3b521427f1e65e6a6cca6735dac8e8 Mon Sep 17 00:00:00 2001 From: Maxim Katcharov Date: Wed, 18 Jan 2023 09:45:43 -0700 Subject: [PATCH 10/27] Replace reduce with individual reductions (#1053) JAVA-4814 --- .../model/expressions/ArrayExpression.java | 46 +++- .../model/expressions/DocumentExpression.java | 9 +- .../model/expressions/MqlExpression.java | 79 ++++++- .../ArrayExpressionsFunctionalTest.java | 213 +++++++++++++++--- 4 files changed, 290 insertions(+), 57 deletions(-) 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 index 342c13e91c4..280efb62439 100644 --- a/driver-core/src/main/com/mongodb/client/model/expressions/ArrayExpression.java +++ b/driver-core/src/main/com/mongodb/client/model/expressions/ArrayExpression.java @@ -16,7 +16,6 @@ package com.mongodb.client.model.expressions; -import java.util.function.BinaryOperator; import java.util.function.Function; import static com.mongodb.client.model.expressions.Expressions.of; @@ -51,28 +50,51 @@ public interface ArrayExpression extends Expression { */ ArrayExpression map(Function in); + IntegerExpression size(); + + BooleanExpression any(Function predicate); + + BooleanExpression all(Function predicate); + + NumberExpression sum(Function mapper); + + NumberExpression multiply(Function mapper); + + T max(T other); + + T min(T other); + + ArrayExpression maxN(IntegerExpression n); + ArrayExpression minN(IntegerExpression n); + + StringExpression join(Function mapper); + + ArrayExpression concat(Function> mapper); + + ArrayExpression union(Function> mapper); + /** - * Performs a reduction on the elements of this array, using the provided - * identity value and an associative reducing function, and returns - * the reduced value. The initial value must be the identity value for the - * reducing function. + * user asserts that i is in bounds for the array * - * @param initialValue the identity for the reducing function - * @param in the associative reducing function - * @return the reduced value + * @param i + * @return */ - T reduce(T initialValue, BinaryOperator in); - - IntegerExpression size(); - T elementAt(IntegerExpression i); default T elementAt(final int i) { return this.elementAt(of(i)); } + /** + * user asserts that array is not empty + * @return + */ T first(); + /** + * user asserts that array is not empty + * @return + */ T last(); BooleanExpression contains(T contains); 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 index 833d9546040..00d2fb9b3d3 100644 --- a/driver-core/src/main/com/mongodb/client/model/expressions/DocumentExpression.java +++ b/driver-core/src/main/com/mongodb/client/model/expressions/DocumentExpression.java @@ -17,7 +17,6 @@ package com.mongodb.client.model.expressions; import org.bson.conversions.Bson; -import org.bson.types.Decimal128; import java.time.Instant; @@ -47,12 +46,8 @@ default BooleanExpression getBoolean(final String fieldName, final boolean other NumberExpression getNumber(String fieldName, NumberExpression other); - default NumberExpression getNumber(final String fieldName, final double other) { - return getNumber(fieldName, of(other)); - } - - default NumberExpression getNumber(final String fieldName, final Decimal128 other) { - return getNumber(fieldName, of(other)); + default NumberExpression getNumber(final String fieldName, final Number other) { + return getNumber(fieldName, Expressions.numberToExpression(other)); } IntegerExpression getInteger(String fieldName); 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 index 7629def2c15..abb32529b62 100644 --- a/driver-core/src/main/com/mongodb/client/model/expressions/MqlExpression.java +++ b/driver-core/src/main/com/mongodb/client/model/expressions/MqlExpression.java @@ -18,6 +18,7 @@ import org.bson.BsonArray; import org.bson.BsonDocument; +import org.bson.BsonInt32; import org.bson.BsonString; import org.bson.BsonValue; import org.bson.codecs.configuration.CodecRegistry; @@ -385,7 +386,12 @@ public ArrayExpression filter(final Function sort() { + return new MqlExpression<>((cr) -> astDoc("$sortArray", new BsonDocument() + .append("input", this.toBsonValue(cr)) + .append("sortBy", new BsonInt32(1)))); + } + public T reduce(final T initialValue, final BinaryOperator in) { T varThis = variable("$$this"); T varValue = variable("$$value"); @@ -395,6 +401,77 @@ public T reduce(final T initialValue, final BinaryOperator in) { .append("in", extractBsonValue(cr, in.apply(varValue, varThis))))); } + @Override + public BooleanExpression any(final Function predicate) { + MqlExpression array = (MqlExpression) this.map(predicate); + return array.reduce(of(false), (a, b) -> a.or(b)); + } + + @Override + public BooleanExpression all(final Function predicate) { + MqlExpression array = (MqlExpression) this.map(predicate); + return array.reduce(of(true), (a, b) -> a.and(b)); + } + + @SuppressWarnings("unchecked") + @Override + public NumberExpression sum(final Function mapper) { + // no sum that returns IntegerExpression, both have same erasure + MqlExpression array = (MqlExpression) this.map(mapper); + return array.reduce(of(0), (a, b) -> a.add(b)); + } + + @SuppressWarnings("unchecked") + @Override + public NumberExpression multiply(final Function mapper) { + MqlExpression array = (MqlExpression) this.map(mapper); + return array.reduce(of(0), (NumberExpression a, NumberExpression b) -> a.multiply(b)); + } + + @Override + public T max(final T other) { + return this.size().eq(of(0)).cond(other, this.maxN(of(1)).first()); + } + + @Override + public T min(final T other) { + return this.size().eq(of(0)).cond(other, this.minN(of(1)).first()); + } + + @Override + public ArrayExpression maxN(final IntegerExpression n) { + return newMqlExpression((CodecRegistry cr) -> astDoc("$maxN", new BsonDocument() + .append("input", extractBsonValue(cr, this)) + .append("n", extractBsonValue(cr, n)))); + } + + @Override + public ArrayExpression minN(final IntegerExpression n) { + return newMqlExpression((CodecRegistry cr) -> astDoc("$minN", new BsonDocument() + .append("input", extractBsonValue(cr, this)) + .append("n", extractBsonValue(cr, n)))); + } + + @Override + public StringExpression join(final Function mapper) { + MqlExpression array = (MqlExpression) this.map(mapper); + return array.reduce(of(""), (a, b) -> a.concat(b)); + } + + @SuppressWarnings("unchecked") + @Override + public ArrayExpression concat(final Function> mapper) { + MqlExpression> array = (MqlExpression>) this.map(mapper); + return array.reduce(Expressions.ofArray(), (a, b) -> a.concat(b)); + } + + @SuppressWarnings("unchecked") + @Override + public ArrayExpression union(final Function> mapper) { + MqlExpression> array = (MqlExpression>) this.map(mapper); + return array.reduce(Expressions.ofArray(), (a, b) -> a.union(b)); + } + @Override public IntegerExpression size() { return new MqlExpression<>(astWrapped("$size")); diff --git a/driver-core/src/test/functional/com/mongodb/client/model/expressions/ArrayExpressionsFunctionalTest.java b/driver-core/src/test/functional/com/mongodb/client/model/expressions/ArrayExpressionsFunctionalTest.java index 69f3399da02..b622688b49f 100644 --- a/driver-core/src/test/functional/com/mongodb/client/model/expressions/ArrayExpressionsFunctionalTest.java +++ b/driver-core/src/test/functional/com/mongodb/client/model/expressions/ArrayExpressionsFunctionalTest.java @@ -25,6 +25,7 @@ import java.util.Arrays; import java.util.Collections; import java.util.LinkedList; +import java.util.function.Function; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -37,7 +38,7 @@ import static com.mongodb.client.model.expressions.Expressions.ofStringArray; import static org.junit.jupiter.api.Assertions.assertThrows; -@SuppressWarnings({"ConstantConditions", "Convert2MethodRef"}) +@SuppressWarnings({"Convert2MethodRef"}) class ArrayExpressionsFunctionalTest extends AbstractExpressionsFunctionalTest { // https://www.mongodb.com/docs/manual/reference/operator/aggregation/#array-expression-operators // (Incomplete) @@ -128,40 +129,173 @@ public void mapTest() { } @Test - public void reduceTest() { - // https://www.mongodb.com/docs/manual/reference/operator/aggregation/reduce/ + public void sortTest() { + // https://www.mongodb.com/docs/manual/reference/operator/aggregation/sortArray/ + ArrayExpression integerExpressionArrayExpression = ofIntegerArray(3, 1, 2); assertExpression( - Stream.of(true, true, false) - .reduce(false, (a, b) -> a || b), - arrayTTF.reduce(of(false), (a, b) -> a.or(b)), + Stream.of(3, 1, 2) + .sorted().collect(Collectors.toList()), sort(integerExpressionArrayExpression), // MQL: - "{'$reduce': {'input': [true, true, false], 'initialValue': false, 'in': {'$or': ['$$value', '$$this']}}}"); + "{'$sortArray': {'input': [3, 1, 2], 'sortBy': 1}}"); + } + + @SuppressWarnings("unchecked") + private static ArrayExpression sort(final ArrayExpression array) { + MqlExpression mqlArray = (MqlExpression) array; + return mqlArray.sort(); + } + + // https://www.mongodb.com/docs/manual/reference/operator/aggregation/reduce/ + // reduce is implemented as each individual type of reduction (monoid) + // this prevents issues related to incorrect specification of identity values + + @Test + public void reduceAnyTest() { assertExpression( - Stream.of(true, true, false) - .reduce(true, (a, b) -> a && b), - arrayTTF.reduce(of(true), (a, b) -> a.and(b)), - // MQL: - "{'$reduce': {'input': [true, true, false], 'initialValue': true, 'in': {'$and': ['$$value', '$$this']}}}"); - // empty array + true, + arrayTTF.any(a -> a), + "{'$reduce': {'input': {'$map': {'input': [true, true, false], 'in': '$$this'}}, " + + "'initialValue': false, 'in': {'$or': ['$$value', '$$this']}}}"); assertExpression( - Stream.empty().reduce(true, (a, b) -> a && b), - ofBooleanArray().reduce(of(true), (a, b) -> a.and(b)), - // MQL: - "{'$reduce': {'input': [], 'initialValue': true, 'in': {'$and': ['$$value', '$$this']}}}"); - // constant result + false, + ofBooleanArray().any(a -> a)); + assertExpression( - Stream.of(true, true, false) - .reduce(true, (a, b) -> true), - arrayTTF.reduce(of(true), (a, b) -> of(true)), - // MQL: - "{'$reduce': {'input': [true, true, false], 'initialValue': true, 'in': true}}"); - // non-commutative + true, + ofIntegerArray(1, 2, 3).any(a -> a.eq(of(3)))); + assertExpression( + false, + ofIntegerArray(1, 2, 2).any(a -> a.eq(of(9)))); + } + + @Test + public void reduceAllTest() { + assertExpression( + false, + arrayTTF.all(a -> a), + "{'$reduce': {'input': {'$map': {'input': [true, true, false], 'in': '$$this'}}, " + + "'initialValue': true, 'in': {'$and': ['$$value', '$$this']}}}"); + assertExpression( + true, + ofBooleanArray().all(a -> a)); + + assertExpression( + true, + ofIntegerArray(1, 2, 3).all(a -> a.gt(of(0)))); + assertExpression( + false, + ofIntegerArray(1, 2, 2).all(a -> a.eq(of(2)))); + } + + @Test + public void reduceSumTest() { + assertExpression( + 6, + ofIntegerArray(1, 2, 3).sum(a -> a), + "{'$reduce': {'input': {'$map': {'input': [1, 2, 3], 'in': '$$this'}}, " + + "'initialValue': 0, 'in': {'$add': ['$$value', '$$this']}}}"); + // empty array: + assertExpression( + 0, + ofIntegerArray().sum(a -> a)); + } + + @Test + public void reduceMaxTest() { + assertExpression( + 3, + ofIntegerArray(1, 2, 3).max(of(9)), + "{'$cond': [{'$eq': [{'$size': [[1, 2, 3]]}, 0]}, 9, " + + "{'$first': [{'$maxN': {'input': [1, 2, 3], 'n': 1}}]}]}"); + assertExpression( + 9, + ofIntegerArray().max(of(9))); + } + + @Test + public void reduceMinTest() { + assertExpression( + 1, + ofIntegerArray(1, 2, 3).min(of(9)), + "{'$cond': [{'$eq': [{'$size': [[1, 2, 3]]}, 0]}, 9, " + + "{'$first': [{'$minN': {'input': [1, 2, 3], 'n': 1}}]}]}"); + assertExpression( + 9, + ofIntegerArray().min(of(9))); + } + + @Test + public void reduceMaxNTest() { + assertExpression( + Arrays.asList(3, 2), + ofIntegerArray(3, 1, 2).maxN(of(2))); + assertExpression( + Arrays.asList(), + ofIntegerArray().maxN(of(2))); + // N must be non-zero + assertThrows(MongoCommandException.class, () -> assertExpression( + Arrays.asList(), + ofIntegerArray(3, 2, 1).maxN(of(0)))); + } + + @Test + public void reduceMinNTest() { + assertExpression( + Arrays.asList(1, 2), + ofIntegerArray(3, 1, 2).minN(of(2))); + assertExpression( + Arrays.asList(), + ofIntegerArray().minN(of(2))); + // N must be non-zero + assertThrows(MongoCommandException.class, () -> assertExpression( + Arrays.asList(), + ofIntegerArray(3, 2, 1).minN(of(0)))); + } + + @Test + public void reduceJoinTest() { assertExpression( "abc", - ofStringArray("a", "b", "c").reduce(of(""), (a, b) -> a.concat(b)), + ofStringArray("a", "b", "c").join(a -> a), + "{'$reduce': {'input': {'$map': {'input': ['a', 'b', 'c'], 'in': '$$this'}}, " + + "'initialValue': '', 'in': {'$concat': ['$$value', '$$this']}}}"); + assertExpression( + "", + ofStringArray().join(a -> a)); + } + + @Test + public void reduceConcatTest() { + assertExpression( + Arrays.asList(1, 2, 3, 4), + ofArray(ofIntegerArray(1, 2), ofIntegerArray(3, 4)).concat(v -> v), + "{'$reduce': {'input': {'$map': {'input': [[1, 2], [3, 4]], 'in': '$$this'}}, " + + "'initialValue': [], " + + "'in': {'$concatArrays': ['$$value', '$$this']}}} "); + // empty: + ArrayExpression> expressionArrayExpression = ofArray(); + assertExpression( + Collections.emptyList(), + expressionArrayExpression.concat(a -> a)); + } + + @Test + public void reduceUnionTest() { + // https://www.mongodb.com/docs/manual/reference/operator/aggregation/setUnion/ (40) + assertExpression( + Arrays.asList(1, 2, 3), + sort(ofArray(ofIntegerArray(1, 2), ofIntegerArray(1, 3)).union(v -> v)), // MQL: - "{'$reduce': {'input': ['a', 'b', 'c'], 'initialValue': '', 'in': {'$concat': ['$$value', '$$this']}}}"); + "{'$sortArray': {'input': {'$reduce': {'input': " + + "{'$map': {'input': [[1, 2], [1, 3]], 'in': '$$this'}}, " + + "'initialValue': [], 'in': {'$setUnion': ['$$value', '$$this']}}}, 'sortBy': 1}}"); + Function, ArrayExpression> f = a -> + a.map(v -> v.isBooleanOr(of(false)) + .cond(of(1), of(0))); + assertExpression( + Arrays.asList(0, 1), + ofArray(ofBooleanArray(true, false), ofBooleanArray(false)).union(f)); } @Test @@ -221,6 +355,12 @@ public void firstTest() { array123.first(), // MQL: "{'$first': [[1, 2, 3]]}"); + + assertExpression( + MISSING, + ofIntegerArray().first(), + // MQL: + "{'$first': [[]]}"); } @Test @@ -289,25 +429,24 @@ public void sliceTest() { } @Test - public void setUnionTest() { + public void unionTest() { // https://www.mongodb.com/docs/manual/reference/operator/aggregation/setUnion/ assertExpression( Arrays.asList(1, 2, 3), - array123.union(array123), + sort(array123.union(array123)), // MQL: - "{'$setUnion': [[1, 2, 3], [1, 2, 3]]}"); - + "{'$sortArray': {'input': {'$setUnion': [[1, 2, 3], [1, 2, 3]]}, 'sortBy': 1}}"); // mixed types: assertExpression( Arrays.asList(1, 2.0, 3), - // above is a set; in case of flakiness, below should `sort` (not implemented at time of test creation) - ofNumberArray(2.0).union(ofIntegerArray(1, 2, 3))); - // convenience + sort(ofNumberArray(2.0).union(ofIntegerArray(1, 2, 3)))); + } + + @Test + public void distinctTest() { assertExpression( Arrays.asList(1, 2, 3), - ofIntegerArray(1, 2, 1, 3, 3).distinct(), - // MQL: - "{'$setUnion': [[1, 2, 1, 3, 3]]}"); + sort(ofIntegerArray(1, 2, 1, 3, 3).distinct()), + "{'$sortArray': {'input': {'$setUnion': [[1, 2, 1, 3, 3]]}, 'sortBy': 1}}"); } - } From 0bfce8e2510f1d553fde6e21e1530a379bf0ff5a Mon Sep 17 00:00:00 2001 From: Maxim Katcharov Date: Thu, 19 Jan 2023 08:40:39 -0700 Subject: [PATCH 11/27] Implement map expressions (#1054) JAVA-4817 --- .../model/expressions/ArrayExpression.java | 2 + .../model/expressions/DocumentExpression.java | 10 + .../model/expressions/EntryExpression.java | 32 +++ .../client/model/expressions/Expression.java | 2 + .../client/model/expressions/Expressions.java | 29 +++ .../model/expressions/MapExpression.java | 60 ++++++ .../model/expressions/MqlExpression.java | 129 +++++++++++- .../AbstractExpressionsFunctionalTest.java | 6 - .../ComparisonExpressionsFunctionalTest.java | 2 +- .../DocumentExpressionsFunctionalTest.java | 14 ++ .../MapExpressionsFunctionalTest.java | 194 ++++++++++++++++++ .../TypeExpressionsFunctionalTest.java | 34 ++- 12 files changed, 500 insertions(+), 14 deletions(-) create mode 100644 driver-core/src/main/com/mongodb/client/model/expressions/EntryExpression.java create mode 100644 driver-core/src/main/com/mongodb/client/model/expressions/MapExpression.java create mode 100644 driver-core/src/test/functional/com/mongodb/client/model/expressions/MapExpressionsFunctionalTest.java 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 index 280efb62439..dc1e7b84261 100644 --- a/driver-core/src/main/com/mongodb/client/model/expressions/ArrayExpression.java +++ b/driver-core/src/main/com/mongodb/client/model/expressions/ArrayExpression.java @@ -73,6 +73,8 @@ public interface ArrayExpression extends Expression { ArrayExpression union(Function> mapper); + MapExpression asMap(Function> mapper); + /** * user asserts that i is in bounds for the array * 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 index 00d2fb9b3d3..3ce4f8946b5 100644 --- a/driver-core/src/main/com/mongodb/client/model/expressions/DocumentExpression.java +++ b/driver-core/src/main/com/mongodb/client/model/expressions/DocumentExpression.java @@ -21,6 +21,7 @@ import java.time.Instant; import static com.mongodb.client.model.expressions.Expressions.of; +import static com.mongodb.client.model.expressions.Expressions.ofMap; /** * Expresses a document value. A document is an ordered set of fields, where the @@ -85,9 +86,18 @@ default DocumentExpression getDocument(final String fieldName, final Bson other) return getDocument(fieldName, of(other)); } + MapExpression getMap(String fieldName); + MapExpression getMap(String fieldName, MapExpression other); + + default MapExpression getMap(final String fieldName, final Bson other) { + return getMap(fieldName, ofMap(other)); + } + ArrayExpression getArray(String fieldName); ArrayExpression getArray(String fieldName, ArrayExpression other); DocumentExpression merge(DocumentExpression other); + + MapExpression asMap(); } diff --git a/driver-core/src/main/com/mongodb/client/model/expressions/EntryExpression.java b/driver-core/src/main/com/mongodb/client/model/expressions/EntryExpression.java new file mode 100644 index 00000000000..6fda24588c6 --- /dev/null +++ b/driver-core/src/main/com/mongodb/client/model/expressions/EntryExpression.java @@ -0,0 +1,32 @@ +/* + * 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 static com.mongodb.client.model.expressions.Expressions.of; + +public interface EntryExpression extends Expression { + StringExpression getKey(); + + T getValue(); + + EntryExpression setValue(T val); + + EntryExpression setKey(StringExpression key); + default EntryExpression setKey(final String key) { + return setKey(of(key)); + } +} 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 index f203175c60b..e2000d24aaa 100644 --- a/driver-core/src/main/com/mongodb/client/model/expressions/Expression.java +++ b/driver-core/src/main/com/mongodb/client/model/expressions/Expression.java @@ -111,5 +111,7 @@ public interface Expression { ArrayExpression isArrayOr(ArrayExpression other); T isDocumentOr(T other); + MapExpression isMapOr(MapExpression other); + StringExpression asString(); } 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 index 511984ec6c3..26b50356877 100644 --- a/driver-core/src/main/com/mongodb/client/model/expressions/Expressions.java +++ b/driver-core/src/main/com/mongodb/client/model/expressions/Expressions.java @@ -36,6 +36,7 @@ import java.util.List; import static com.mongodb.client.model.expressions.MqlExpression.AstPlaceholder; +import static com.mongodb.client.model.expressions.MqlExpression.extractBsonValue; /** * Convenience methods related to {@link Expression}. @@ -184,6 +185,34 @@ public static ArrayExpression ofArray(final T... array }); } + public static EntryExpression ofEntry(final StringExpression k, final T v) { + Assertions.notNull("k", k); + Assertions.notNull("v", v); + return new MqlExpression<>((cr) -> { + BsonDocument document = new BsonDocument(); + document.put("k", extractBsonValue(cr, k)); + document.put("v", extractBsonValue(cr, v)); + return new AstPlaceholder(document); + }); + } + + public static MapExpression ofMap() { + return ofMap(new BsonDocument()); + } + + /** + * user asserts type of values is T + * + * @param map + * @return + * @param + */ + public static MapExpression ofMap(final Bson map) { + Assertions.notNull("map", map); + return new MqlExpression<>((cr) -> new AstPlaceholder(new BsonDocument("$literal", + map.toBsonDocument(BsonDocument.class, cr)))); + } + 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 diff --git a/driver-core/src/main/com/mongodb/client/model/expressions/MapExpression.java b/driver-core/src/main/com/mongodb/client/model/expressions/MapExpression.java new file mode 100644 index 00000000000..2271a77025d --- /dev/null +++ b/driver-core/src/main/com/mongodb/client/model/expressions/MapExpression.java @@ -0,0 +1,60 @@ +/* + * 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 static com.mongodb.client.model.expressions.Expressions.of; + +public interface MapExpression extends Expression { + + BooleanExpression has(StringExpression key); + + default BooleanExpression has(String key) { + return has(of(key)); + } + + // TODO-END doc "user asserts" + T get(StringExpression key); + + // TODO-END doc "user asserts" + default T get(final String key) { + return get(of(key)); + } + + T get(StringExpression key, T other); + + default T get(final String key, final T other) { + return get(of(key), other); + } + + MapExpression set(StringExpression key, T value); + + default MapExpression set(final String key, final T value) { + return set(of(key), value); + } + + MapExpression unset(StringExpression key); + + default MapExpression unset(final String key) { + return unset(of(key)); + } + + MapExpression merge(MapExpression map); + + ArrayExpression> entrySet(); + + R asDocument(); +} 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 index abb32529b62..e26643ddf80 100644 --- a/driver-core/src/main/com/mongodb/client/model/expressions/MqlExpression.java +++ b/driver-core/src/main/com/mongodb/client/model/expressions/MqlExpression.java @@ -32,7 +32,7 @@ final class MqlExpression implements Expression, BooleanExpression, IntegerExpression, NumberExpression, - StringExpression, DateExpression, DocumentExpression, ArrayExpression { + StringExpression, DateExpression, DocumentExpression, ArrayExpression, MapExpression, EntryExpression { private final Function fn; @@ -53,6 +53,26 @@ private AstPlaceholder astDoc(final String name, final BsonDocument value) { return new AstPlaceholder(new BsonDocument(name, value)); } + @Override + public StringExpression getKey() { + return new MqlExpression<>(getFieldInternal("k")); + } + + @Override + public T getValue() { + return newMqlExpression(getFieldInternal("v")); + } + + @Override + public EntryExpression setValue(final T value) { + return setFieldInternal("v", value); + } + + @Override + public EntryExpression setKey(final StringExpression key) { + return setFieldInternal("k", key); + } + static final class AstPlaceholder { private final BsonValue bsonValue; @@ -95,7 +115,7 @@ private Function ast(final String name, final Exp * 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) { + static BsonValue extractBsonValue(final CodecRegistry cr, final Expression expression) { return ((MqlExpression) expression).toBsonValue(cr); } @@ -211,6 +231,16 @@ public DocumentExpression getDocument(final String fieldName) { return new MqlExpression<>(getFieldInternal(fieldName)); } + @Override + public MapExpression getMap(final String field) { + return new MqlExpression<>(getFieldInternal(field)); + } + + @Override + public MapExpression getMap(final String field, final MapExpression other) { + return getMap(field).isMapOr(other); + } + @Override public DocumentExpression getDocument(final String fieldName, final DocumentExpression other) { return getDocument(fieldName).isDocumentOr(other); @@ -233,6 +263,10 @@ public DocumentExpression merge(final DocumentExpression other) { @Override public DocumentExpression setField(final String fieldName, final Expression exp) { + return setFieldInternal(fieldName, exp); + } + + private MqlExpression setFieldInternal(final String fieldName, final Expression exp) { return newMqlExpression((cr) -> astDoc("$setField", new BsonDocument() .append("field", new BsonString(fieldName)) .append("input", this.toBsonValue(cr)) @@ -327,6 +361,11 @@ public BooleanExpression isArray() { return new MqlExpression<>(astWrapped("$isArray")); } + private Expression ifNull(final Expression ifNull) { + return new MqlExpression<>(ast("$ifNull", ifNull, Expressions.ofNull())) + .assertImplementsAllExpressions(); + } + /** * checks if array (but cannot check type) * user asserts array is of type R @@ -341,13 +380,19 @@ public ArrayExpression isArrayOr(final ArrayExpression return (ArrayExpression) this.isArray().cond(this.assertImplementsAllExpressions(), other); } - public BooleanExpression isDocument() { + private BooleanExpression isDocumentOrMap() { return new MqlExpression<>(ast("$type")).eq(of("object")); } @Override public R isDocumentOr(final R other) { - return this.isDocument().cond(this.assertImplementsAllExpressions(), other); + return this.isDocumentOrMap().cond(this.assertImplementsAllExpressions(), other); + } + + @Override + public MapExpression isMapOr(final MapExpression other) { + MqlExpression isMap = (MqlExpression) this.isDocumentOrMap(); + return newMqlExpression(isMap.ast("$cond", this.assertImplementsAllExpressions(), other)); } @Override @@ -355,10 +400,10 @@ public StringExpression asString() { return new MqlExpression<>(astWrapped("$toString")); } - private Function convertInternal(final String to, final Expression orElse) { + private Function convertInternal(final String to, final Expression other) { return (cr) -> astDoc("$convert", new BsonDocument() .append("input", this.fn.apply(cr).bsonValue) - .append("onError", extractBsonValue(cr, orElse)) + .append("onError", extractBsonValue(cr, other)) .append("to", new BsonString(to))); } @@ -731,4 +776,76 @@ public StringExpression substr(final IntegerExpression start, final IntegerExpre public StringExpression substrBytes(final IntegerExpression start, final IntegerExpression length) { return new MqlExpression<>(ast("$substrBytes", start, length)); } + + @Override + public BooleanExpression has(final StringExpression key) { + return get(key).ne(ofRem()); + } + + static R ofRem() { + // $$REMOVE is intentionally not exposed to users + return new MqlExpression<>((cr) -> new MqlExpression.AstPlaceholder(new BsonString("$$REMOVE"))) + .assertImplementsAllExpressions(); + } + + /** @see MapExpression + * @see EntryExpression */ + + @Override + public T get(final StringExpression key) { + return newMqlExpression((cr) -> astDoc("$getField", new BsonDocument() + .append("input", this.fn.apply(cr).bsonValue) + .append("field", extractBsonValue(cr, key)))); + } + + @SuppressWarnings("unchecked") + @Override + public T get(final StringExpression key, final T other) { + return (T) ((MqlExpression) get(key)).ifNull(other); + } + + @Override + public MapExpression set(final StringExpression key, final T value) { + return newMqlExpression((cr) -> astDoc("$setField", new BsonDocument() + .append("field", extractBsonValue(cr, key)) + .append("input", this.toBsonValue(cr)) + .append("value", extractBsonValue(cr, value)))); + } + + @Override + public MapExpression unset(final StringExpression key) { + return newMqlExpression((cr) -> astDoc("$unsetField", new BsonDocument() + .append("field", extractBsonValue(cr, key)) + .append("input", this.toBsonValue(cr)))); + } + + @Override + public MapExpression merge(final MapExpression map) { + return new MqlExpression<>(ast("$mergeObjects", map)); + } + + @Override + public ArrayExpression> entrySet() { + return newMqlExpression(ast("$objectToArray")); + } + + @Override + public MapExpression asMap( + final Function> mapper) { + @SuppressWarnings("unchecked") + MqlExpression> array = (MqlExpression>) this.map(mapper); + return newMqlExpression(array.astWrapped("$arrayToObject")); + } + + @SuppressWarnings("unchecked") + @Override + public MapExpression asMap() { + return (MapExpression) this; + } + + @SuppressWarnings("unchecked") + @Override + public R asDocument() { + return (R) this; + } } 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 index a167120f041..45c662a99ad 100644 --- 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 @@ -126,11 +126,5 @@ public BsonValue readValue(final BsonReader reader, final DecoderContext decoder } } - - static R ofRem() { - // $$REMOVE is intentionally not exposed to users - return new MqlExpression<>((cr) -> new MqlExpression.AstPlaceholder(new BsonString("$$REMOVE"))) - .assertImplementsAllExpressions(); - } } diff --git a/driver-core/src/test/functional/com/mongodb/client/model/expressions/ComparisonExpressionsFunctionalTest.java b/driver-core/src/test/functional/com/mongodb/client/model/expressions/ComparisonExpressionsFunctionalTest.java index ef68da2d394..b044128f039 100644 --- a/driver-core/src/test/functional/com/mongodb/client/model/expressions/ComparisonExpressionsFunctionalTest.java +++ b/driver-core/src/test/functional/com/mongodb/client/model/expressions/ComparisonExpressionsFunctionalTest.java @@ -40,7 +40,7 @@ class ComparisonExpressionsFunctionalTest extends AbstractExpressionsFunctionalT // https://www.mongodb.com/docs/manual/reference/bson-type-comparison-order/#std-label-bson-types-comparison-order private final List sampleValues = Arrays.asList( - ofRem(), + MqlExpression.ofRem(), ofNull(), of(0), of(1), diff --git a/driver-core/src/test/functional/com/mongodb/client/model/expressions/DocumentExpressionsFunctionalTest.java b/driver-core/src/test/functional/com/mongodb/client/model/expressions/DocumentExpressionsFunctionalTest.java index a00a16f5d27..f0ef4880631 100644 --- a/driver-core/src/test/functional/com/mongodb/client/model/expressions/DocumentExpressionsFunctionalTest.java +++ b/driver-core/src/test/functional/com/mongodb/client/model/expressions/DocumentExpressionsFunctionalTest.java @@ -27,6 +27,8 @@ import static com.mongodb.client.model.expressions.Expressions.of; import static com.mongodb.client.model.expressions.Expressions.ofIntegerArray; +import static com.mongodb.client.model.expressions.Expressions.ofMap; +import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertThrows; @SuppressWarnings("ConstantConditions") @@ -120,6 +122,8 @@ public void getFieldOrTest() { // no convenience for arrays assertExpression(Document.parse("{b: 2}"), ofDoc("{a: {b: 2}}") .getDocument("a", Document.parse("{z: 99}"))); + assertExpression(Document.parse("{b: 2}"), ofDoc("{a: {b: 2}}") + .getMap("a", Document.parse("{z: 99}"))); // normal assertExpression(true, ofDoc("{a: true}").getBoolean("a", of(false))); @@ -131,6 +135,8 @@ public void getFieldOrTest() { assertExpression(Arrays.asList(3, 2), ofDoc("{a: [3, 2]}").getArray("a", ofIntegerArray(99, 88))); assertExpression(Document.parse("{b: 2}"), ofDoc("{a: {b: 2}}") .getDocument("a", of(Document.parse("{z: 99}")))); + assertExpression(Document.parse("{b: 2}"), ofDoc("{a: {b: 2}}") + .getMap("a", ofMap(Document.parse("{z: 99}")))); // right branch (missing field) assertExpression(false, ofDoc("{}").getBoolean("a", false)); @@ -145,6 +151,8 @@ public void getFieldOrTest() { assertExpression(Arrays.asList(99, 88), ofDoc("{}").getArray("a", ofIntegerArray(99, 88))); assertExpression(Document.parse("{z: 99}"), ofDoc("{}") .getDocument("a", Document.parse("{z: 99}"))); + assertExpression(Document.parse("{z: 99}"), ofDoc("{}") + .getMap("a", Document.parse("{z: 99}"))); // int vs num assertExpression(99, ofDoc("{a: 1.1}").getInteger("a", of(99))); @@ -247,4 +255,10 @@ public void mergeTest() { BsonDocument.parse("{a: 1}"), ofDoc("{a: null}").merge(ofDoc("{a: 1}"))); } + + @Test + public void asMapTest() { + DocumentExpression d = ofDoc("{a: 1}"); + assertSame(d, d.asMap()); + } } diff --git a/driver-core/src/test/functional/com/mongodb/client/model/expressions/MapExpressionsFunctionalTest.java b/driver-core/src/test/functional/com/mongodb/client/model/expressions/MapExpressionsFunctionalTest.java new file mode 100644 index 00000000000..ba657796a46 --- /dev/null +++ b/driver-core/src/test/functional/com/mongodb/client/model/expressions/MapExpressionsFunctionalTest.java @@ -0,0 +1,194 @@ +/* + * 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.Document; +import org.junit.jupiter.api.Test; + +import java.util.Arrays; + +import static com.mongodb.client.model.expressions.Expressions.of; +import static com.mongodb.client.model.expressions.Expressions.ofArray; +import static com.mongodb.client.model.expressions.Expressions.ofEntry; +import static com.mongodb.client.model.expressions.Expressions.ofMap; +import static com.mongodb.client.model.expressions.Expressions.ofStringArray; +import static org.junit.jupiter.api.Assertions.assertSame; + +class MapExpressionsFunctionalTest extends AbstractExpressionsFunctionalTest { + + private final MapExpression mapKey123 = Expressions.ofMap() + .set("key", of(123)); + + private final MapExpression mapA1B2 = ofMap(Document.parse("{keyA: 1, keyB: 2}")); + + @Test + public void literalsTest() { + // map + assertExpression( + Document.parse("{key: 123}"), + mapKey123, + "{'$setField': {'field': 'key', 'input': {'$literal': {}}, 'value': 123}}"); + assertExpression( + Document.parse("{keyA: 1, keyB: 2}"), + ofMap(Document.parse("{keyA: 1, keyB: 2}")), + "{'$literal': {'keyA': 1, 'keyB': 2}}"); + // entry + assertExpression( + Document.parse("{k: 'keyA', v: 1}"), + ofEntry(of("keyA"), of(1))); + } + + @Test + public void getSetMapTest() { + // get + assertExpression( + 123, + mapKey123.get("key")); + assertExpression( + 1, + mapKey123.get("missing", of(1))); + // set (map.put) + assertExpression( + BsonDocument.parse("{key: 123, b: 1}"), + mapKey123.set("b", of(1))); + // unset (delete) + assertExpression( + BsonDocument.parse("{}"), + mapKey123.unset("key")); + // "other" parameter + assertExpression( + 1, + ofMap(Document.parse("{ 'null': null }")).get("null", of(1))); + } + + @Test + public void hasMapTest() { + assertExpression( + true, + mapKey123.has(of("key")), + "{'$ne': [{'$getField': {'input': {'$setField': {'field': 'key', 'input': " + + "{'$literal': {}}, 'value': 123}}, 'field': 'key'}}, '$$REMOVE']}"); + assertExpression( + false, + mapKey123.has("not_key")); + } + + @Test + public void getSetEntryTest() { + EntryExpression entryA1 = ofEntry(of("keyA"), of(1)); + assertExpression( + Document.parse("{k: 'keyA', 'v': 33}"), + entryA1.setValue(of(33))); + assertExpression( + Document.parse("{k: 'keyB', 'v': 1}"), + entryA1.setKey(of("keyB"))); + assertExpression( + Document.parse("{k: 'keyB', 'v': 1}"), + entryA1.setKey("keyB")); + } + + @Test + public void buildMapTest() { + // https://www.mongodb.com/docs/manual/reference/operator/aggregation/arrayToObject/ (48) + assertExpression( + Document.parse("{'keyA': 1}"), + ofArray(ofEntry(of("keyA"), of(1))).asMap(v -> v), + "{'$arrayToObject': [{'$map': {'input': [{'k': 'keyA', 'v': 1}], 'in': '$$this'}}]}"); + + assertExpression( + Document.parse("{'keyA': 55}"), + ofArray(ofEntry(of("keyA"), of(1))).asMap(v -> v.setValue(of(55))), + "{'$arrayToObject': [{'$map': {'input': [{'k': 'keyA', 'v': 1}], " + + "'in': {'$setField': {'field': 'v', 'input': '$$this', 'value': 55}}}}]}"); + + // using documents + assertExpression( + Document.parse("{ 'item' : 'abc123', 'qty' : 25 }"), + ofArray( + of(Document.parse("{ 'k': 'item', 'v': 'abc123' }")), + of(Document.parse("{ 'k': 'qty', 'v': 25 }"))) + .asMap(v -> ofEntry(v.getString("k"), v.getField("v")))); + // using arrays + assertExpression( + Document.parse("{ 'item' : 'abc123', 'qty' : 25 }"), + ofArray( + ofStringArray("item", "abc123"), + ofArray(of("qty"), of(25))) + .asMap(v -> ofEntry(v.elementAt(of(0)).asString(), v.elementAt(of(1))))); + // last listed value used + assertExpression( + Document.parse("{ 'item' : 'abc123' }"), + ofArray( + Expressions.ofMap(Document.parse("{ 'k': 'item', 'v': '123abc' }")), + Expressions.ofMap(Document.parse("{ 'k': 'item', 'v': 'abc123' }"))) + .asMap(v -> ofEntry(v.get("k"), v.get("v")))); + + } + + @Test + public void entrySetTest() { + // https://www.mongodb.com/docs/manual/reference/operator/aggregation/objectToArray/ (23) + assertExpression( + Arrays.asList(Document.parse("{'k': 'k1', 'v': 1}")), + Expressions.ofMap().set("k1", of(1)).entrySet(), + "{'$objectToArray': {'$setField': " + + "{'field': 'k1', 'input': {'$literal': {}}, 'value': 1}}}"); + + // key/value usage + assertExpression( + "keyA|keyB|", + mapA1B2.entrySet().map(v -> v.getKey().concat(of("|"))).join(v -> v)); + assertExpression( + 23, + mapA1B2.entrySet().map(v -> v.getValue().add(10)).sum(v -> v)); + + // combined entrySet-buildMap usage + assertExpression( + Document.parse("{'keyA': 2, 'keyB': 3}"), + mapA1B2 + .entrySet() + .map(v -> v.setValue(v.getValue().add(1))) + .asMap(v -> v)); + + // via getMap + DocumentExpression doc = of(Document.parse("{ instock: { warehouse1: 2500, warehouse2: 500 } }")); + assertExpression( + Arrays.asList( + Document.parse("{'k': 'warehouse1', 'v': 2500}"), + Document.parse("{'k': 'warehouse2', 'v': 500}")), + doc.getMap("instock").entrySet(), + "{'$objectToArray': {'$getField': {'input': {'$literal': " + + "{'instock': {'warehouse1': 2500, 'warehouse2': 500}}}, 'field': 'instock'}}}"); + } + + @Test + public void mergeTest() { + assertExpression( + Document.parse("{'keyA': 9, 'keyB': 2, 'keyC': 3}"), + ofMap(Document.parse("{keyA: 1, keyB: 2}")) + .merge(ofMap(Document.parse("{keyA: 9, keyC: 3}"))), + "{'$mergeObjects': [{'$literal': {'keyA': 1, 'keyB': 2}}, " + + "{'$literal': {'keyA': 9, 'keyC': 3}}]}"); + } + + @Test + public void asDocumentTest() { + MapExpression d = ofMap(BsonDocument.parse("{a: 1}")); + assertSame(d, d.asDocument()); + } +} diff --git a/driver-core/src/test/functional/com/mongodb/client/model/expressions/TypeExpressionsFunctionalTest.java b/driver-core/src/test/functional/com/mongodb/client/model/expressions/TypeExpressionsFunctionalTest.java index 1f9daf28a3a..adb4a82e9ce 100644 --- a/driver-core/src/test/functional/com/mongodb/client/model/expressions/TypeExpressionsFunctionalTest.java +++ b/driver-core/src/test/functional/com/mongodb/client/model/expressions/TypeExpressionsFunctionalTest.java @@ -30,6 +30,7 @@ import static com.mongodb.client.model.expressions.Expressions.of; import static com.mongodb.client.model.expressions.Expressions.ofIntegerArray; +import static com.mongodb.client.model.expressions.Expressions.ofMap; import static com.mongodb.client.model.expressions.Expressions.ofNull; import static java.time.format.DateTimeFormatter.ISO_LOCAL_DATE_TIME; import static org.junit.jupiter.api.Assertions.assertThrows; @@ -103,13 +104,44 @@ public void isArrayOrTest() { @Test public void isDocumentOrTest() { BsonDocument doc = BsonDocument.parse("{a: 1}"); - assertExpression(doc, + assertExpression( + doc, of(doc).isDocumentOr(of(BsonDocument.parse("{b: 2}"))), "{'$cond': [{'$eq': [{'$type': {'$literal': {'a': 1}}}, 'object']}, " + "{'$literal': {'a': 1}}, {'$literal': {'b': 2}}]}"); // non-document: assertExpression(doc, ofIntegerArray(1).isDocumentOr(of(doc))); assertExpression(doc, ofNull().isDocumentOr(of(doc))); + + // maps are documents + assertExpression(doc, ofMap(doc).isDocumentOr(of(BsonDocument.parse("{x: 9}")))); + + // conversion between maps and documents + MapExpression first = ofMap(doc); + DocumentExpression second = first.isDocumentOr(of(BsonDocument.parse("{}"))); + MapExpression third = second.isMapOr(ofMap(BsonDocument.parse("{}"))); + assertExpression( + true, + first.eq(second)); + assertExpression( + true, + second.eq(third)); + } + + @Test + public void isMapOrTest() { + BsonDocument map = BsonDocument.parse("{a: 1}"); + assertExpression( + map, + ofMap(map).isMapOr(ofMap(BsonDocument.parse("{b: 2}"))), + "{'$cond': [{'$eq': [{'$type': {'$literal': {'a': 1}}}, 'object']}, " + + "{'$literal': {'a': 1}}, {'$literal': {'b': 2}}]}"); + // non-map: + assertExpression(map, ofIntegerArray(1).isMapOr(ofMap(map))); + assertExpression(map, ofNull().isMapOr(ofMap(map))); + + // documents are maps + assertExpression(map, of(map).isMapOr(ofMap(BsonDocument.parse("{x: 9}")))); } // conversions From a5245b05567162f6eff8684a7658d0ae63264582 Mon Sep 17 00:00:00 2001 From: Maxim Katcharov Date: Thu, 19 Jan 2023 08:46:54 -0700 Subject: [PATCH 12/27] Implement switch expression (#1055) JAVA-4813 --- .../model/expressions/ArrayExpression.java | 4 + .../model/expressions/BooleanExpression.java | 6 + .../client/model/expressions/Branches.java | 97 ++++++ .../expressions/BranchesIntermediary.java | 102 +++++++ .../model/expressions/BranchesTerminal.java | 47 +++ .../model/expressions/DateExpression.java | 4 + .../model/expressions/DocumentExpression.java | 4 + .../client/model/expressions/Expression.java | 7 + .../model/expressions/IntegerExpression.java | 5 + .../model/expressions/MapExpression.java | 5 + .../model/expressions/MqlExpression.java | 141 ++++++++- .../model/expressions/NumberExpression.java | 5 + .../model/expressions/StringExpression.java | 5 + .../client/model/expressions/SwitchCase.java | 35 +++ .../ArrayExpressionsFunctionalTest.java | 5 +- .../ComparisonExpressionsFunctionalTest.java | 12 + .../ControlExpressionsFunctionalTest.java | 279 ++++++++++++++++++ .../TypeExpressionsFunctionalTest.java | 26 +- 18 files changed, 776 insertions(+), 13 deletions(-) create mode 100644 driver-core/src/main/com/mongodb/client/model/expressions/Branches.java create mode 100644 driver-core/src/main/com/mongodb/client/model/expressions/BranchesIntermediary.java create mode 100644 driver-core/src/main/com/mongodb/client/model/expressions/BranchesTerminal.java create mode 100644 driver-core/src/main/com/mongodb/client/model/expressions/SwitchCase.java create mode 100644 driver-core/src/test/functional/com/mongodb/client/model/expressions/ControlExpressionsFunctionalTest.java 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 index dc1e7b84261..7b499f7ada0 100644 --- a/driver-core/src/main/com/mongodb/client/model/expressions/ArrayExpression.java +++ b/driver-core/src/main/com/mongodb/client/model/expressions/ArrayExpression.java @@ -112,4 +112,8 @@ default ArrayExpression slice(final int start, final int length) { ArrayExpression union(ArrayExpression set); ArrayExpression distinct(); + + R passArrayTo(Function, ? extends R> f); + + R switchArrayOn(Function>, ? extends BranchesTerminal, ? extends R>> on); } 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 index fcf0eca939e..7f0b43370dc 100644 --- a/driver-core/src/main/com/mongodb/client/model/expressions/BooleanExpression.java +++ b/driver-core/src/main/com/mongodb/client/model/expressions/BooleanExpression.java @@ -16,6 +16,8 @@ package com.mongodb.client.model.expressions; +import java.util.function.Function; + /** * Expresses a boolean value. */ @@ -58,4 +60,8 @@ public interface BooleanExpression extends Expression { * @param The type of the resulting expression. */ T cond(T left, T right); + + R passBooleanTo(Function f); + + R switchBooleanOn(Function, ? extends BranchesTerminal> on); } diff --git a/driver-core/src/main/com/mongodb/client/model/expressions/Branches.java b/driver-core/src/main/com/mongodb/client/model/expressions/Branches.java new file mode 100644 index 00000000000..40a726b4c9c --- /dev/null +++ b/driver-core/src/main/com/mongodb/client/model/expressions/Branches.java @@ -0,0 +1,97 @@ +/* + * 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 java.util.ArrayList; +import java.util.List; +import java.util.function.Function; + +public final class Branches { + + Branches() { + } + + private static BranchesIntermediary with(final Function> switchCase) { + List>> v = new ArrayList<>(); + v.add(switchCase); + return new BranchesIntermediary<>(v); + } + + private static MqlExpression mqlEx(final T value) { + return (MqlExpression) value; + } + + // is fn + + public BranchesIntermediary is(final Function o, final Function r) { + return with(value -> new SwitchCase<>(o.apply(value), r.apply(value))); + } + + // eq lt lte + + public BranchesIntermediary eq(final T v, final Function r) { + return is(value -> value.eq(v), r); + } + + public BranchesIntermediary lt(final T v, final Function r) { + return is(value -> value.lt(v), r); + } + + public BranchesIntermediary lte(final T v, final Function r) { + return is(value -> value.lte(v), r); + } + + // is type + + public BranchesIntermediary isBoolean(final Function r) { + return is(v -> mqlEx(v).isBoolean(), v -> r.apply((BooleanExpression) v)); + } + + public BranchesIntermediary isNumber(final Function r) { + return is(v -> mqlEx(v).isNumber(), v -> r.apply((NumberExpression) v)); + } + + public BranchesIntermediary isInteger(final Function r) { + return is(v -> mqlEx(v).isInteger(), v -> r.apply((IntegerExpression) v)); + } + + public BranchesIntermediary isString(final Function r) { + return is(v -> mqlEx(v).isString(), v -> r.apply((StringExpression) v)); + } + + public BranchesIntermediary isDate(final Function r) { + return is(v -> mqlEx(v).isDate(), v -> r.apply((DateExpression) v)); + } + + @SuppressWarnings("unchecked") + public BranchesIntermediary isArray(final Function, ? extends R> r) { + return is(v -> mqlEx(v).isArray(), v -> r.apply((ArrayExpression) v)); + } + + public BranchesIntermediary isDocument(final Function r) { + return is(v -> mqlEx(v).isDocumentOrMap(), v -> r.apply((DocumentExpression) v)); + } + + @SuppressWarnings("unchecked") + public BranchesIntermediary isMap(final Function, ? extends R> r) { + return is(v -> mqlEx(v).isDocumentOrMap(), v -> r.apply((MapExpression) v)); + } + + public BranchesIntermediary isNull(final Function r) { + return is(v -> mqlEx(v).isNull(), v -> r.apply(v)); + } +} diff --git a/driver-core/src/main/com/mongodb/client/model/expressions/BranchesIntermediary.java b/driver-core/src/main/com/mongodb/client/model/expressions/BranchesIntermediary.java new file mode 100644 index 00000000000..9ef53d4a7c7 --- /dev/null +++ b/driver-core/src/main/com/mongodb/client/model/expressions/BranchesIntermediary.java @@ -0,0 +1,102 @@ +/* + * 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 java.util.ArrayList; +import java.util.List; +import java.util.function.Function; + +public final class BranchesIntermediary extends BranchesTerminal { + BranchesIntermediary(final List>> branches) { + super(branches, null); + } + + private BranchesIntermediary with(final Function> switchCase) { + List>> v = new ArrayList<>(this.getBranches()); + v.add(switchCase); + return new BranchesIntermediary<>(v); + } + + private static MqlExpression mqlEx(final T value) { + return (MqlExpression) value; + } + + // is fn + + public BranchesIntermediary is(final Function o, final Function r) { + return this.with(value -> new SwitchCase<>(o.apply(value), r.apply(value))); + } + + // eq lt lte + + public BranchesIntermediary eq(final T v, final Function r) { + return is(value -> value.eq(v), r); + } + + public BranchesIntermediary lt(final T v, final Function r) { + return is(value -> value.lt(v), r); + } + + public BranchesIntermediary lte(final T v, final Function r) { + return is(value -> value.lte(v), r); + } + + // is type + + public BranchesIntermediary isBoolean(final Function r) { + return is(v -> mqlEx(v).isBoolean(), v -> r.apply((BooleanExpression) v)); + } + + public BranchesIntermediary isNumber(final Function r) { + return is(v -> mqlEx(v).isNumber(), v -> r.apply((NumberExpression) v)); + } + + public BranchesIntermediary isInteger(final Function r) { + return is(v -> mqlEx(v).isInteger(), v -> r.apply((IntegerExpression) v)); + } + + public BranchesIntermediary isString(final Function r) { + return is(v -> mqlEx(v).isString(), v -> r.apply((StringExpression) v)); + } + + public BranchesIntermediary isDate(final Function r) { + return is(v -> mqlEx(v).isDate(), v -> r.apply((DateExpression) v)); + } + + @SuppressWarnings("unchecked") + public BranchesIntermediary isArray(final Function, ? extends R> r) { + return is(v -> mqlEx(v).isArray(), v -> r.apply((ArrayExpression) v)); + } + + public BranchesIntermediary isDocument(final Function r) { + return is(v -> mqlEx(v).isDocumentOrMap(), v -> r.apply((DocumentExpression) v)); + } + + @SuppressWarnings("unchecked") + public BranchesIntermediary isMap(final Function, ? extends R> r) { + return is(v -> mqlEx(v).isDocumentOrMap(), v -> r.apply((MapExpression) v)); + } + + public BranchesIntermediary isNull(final Function r) { + return is(v -> mqlEx(v).isNull(), v -> r.apply(v)); + } + + public BranchesTerminal defaults(final Function r) { + return this.withDefault(value -> r.apply(value)); + } + +} diff --git a/driver-core/src/main/com/mongodb/client/model/expressions/BranchesTerminal.java b/driver-core/src/main/com/mongodb/client/model/expressions/BranchesTerminal.java new file mode 100644 index 00000000000..97d0b85f233 --- /dev/null +++ b/driver-core/src/main/com/mongodb/client/model/expressions/BranchesTerminal.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.lang.Nullable; + +import java.util.List; +import java.util.function.Function; + +public class BranchesTerminal { + + private final List>> branches; + + private final Function defaults; + + BranchesTerminal(final List>> branches, @Nullable final Function defaults) { + this.branches = branches; + this.defaults = defaults; + } + + protected BranchesTerminal withDefault(final Function defaults) { + return new BranchesTerminal<>(branches, defaults); + } + + protected List>> getBranches() { + return branches; + } + + @Nullable + protected Function getDefaults() { + return defaults; + } +} 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 index 04eb6b00ea5..cd211d1fd85 100644 --- a/driver-core/src/main/com/mongodb/client/model/expressions/DateExpression.java +++ b/driver-core/src/main/com/mongodb/client/model/expressions/DateExpression.java @@ -16,6 +16,8 @@ package com.mongodb.client.model.expressions; +import java.util.function.Function; + /** * Expresses a date value. */ @@ -33,4 +35,6 @@ public interface DateExpression extends Expression { StringExpression asString(StringExpression timezone, StringExpression format); + R passDateTo(Function f); + R switchDateOn(Function, ? extends BranchesTerminal> on); } 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 index 3ce4f8946b5..4d7d2ef678f 100644 --- a/driver-core/src/main/com/mongodb/client/model/expressions/DocumentExpression.java +++ b/driver-core/src/main/com/mongodb/client/model/expressions/DocumentExpression.java @@ -19,6 +19,7 @@ import org.bson.conversions.Bson; import java.time.Instant; +import java.util.function.Function; import static com.mongodb.client.model.expressions.Expressions.of; import static com.mongodb.client.model.expressions.Expressions.ofMap; @@ -100,4 +101,7 @@ default MapExpression getMap(final String fieldName, f DocumentExpression merge(DocumentExpression other); MapExpression asMap(); + + R passDocumentTo(Function f); + R switchDocumentOn(Function, ? extends BranchesTerminal> on); } 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 index e2000d24aaa..a803134cc4f 100644 --- a/driver-core/src/main/com/mongodb/client/model/expressions/Expression.java +++ b/driver-core/src/main/com/mongodb/client/model/expressions/Expression.java @@ -18,6 +18,8 @@ import com.mongodb.annotations.Evolving; +import java.util.function.Function; + /** * Expressions express values that may be represented in (or computations that * may be performed within) a MongoDB server. Each expression evaluates to some @@ -114,4 +116,9 @@ public interface Expression { MapExpression isMapOr(MapExpression other); StringExpression asString(); + + R passTo(Function f); + + R switchOn(Function, ? extends BranchesTerminal> on); + } 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 index eab541c0474..40cde216ce3 100644 --- a/driver-core/src/main/com/mongodb/client/model/expressions/IntegerExpression.java +++ b/driver-core/src/main/com/mongodb/client/model/expressions/IntegerExpression.java @@ -16,6 +16,8 @@ package com.mongodb.client.model.expressions; +import java.util.function.Function; + /** * Expresses an integer value. */ @@ -42,4 +44,7 @@ default IntegerExpression subtract(final int subtract) { IntegerExpression min(IntegerExpression i); IntegerExpression abs(); + + R passIntegerTo(Function f); + R switchIntegerOn(Function, ? extends BranchesTerminal> on); } diff --git a/driver-core/src/main/com/mongodb/client/model/expressions/MapExpression.java b/driver-core/src/main/com/mongodb/client/model/expressions/MapExpression.java index 2271a77025d..d5d782c7728 100644 --- a/driver-core/src/main/com/mongodb/client/model/expressions/MapExpression.java +++ b/driver-core/src/main/com/mongodb/client/model/expressions/MapExpression.java @@ -16,6 +16,8 @@ package com.mongodb.client.model.expressions; +import java.util.function.Function; + import static com.mongodb.client.model.expressions.Expressions.of; public interface MapExpression extends Expression { @@ -57,4 +59,7 @@ default MapExpression unset(final String key) { ArrayExpression> entrySet(); R asDocument(); + + R passMapTo(Function, ? extends R> f); + R switchMapOn(Function>, ? extends BranchesTerminal, ? extends R>> on); } 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 index e26643ddf80..2f62b05b11a 100644 --- a/driver-core/src/main/com/mongodb/client/model/expressions/MqlExpression.java +++ b/driver-core/src/main/com/mongodb/client/model/expressions/MqlExpression.java @@ -28,6 +28,7 @@ import java.util.function.Function; import static com.mongodb.client.model.expressions.Expressions.of; +import static com.mongodb.client.model.expressions.Expressions.ofNull; import static com.mongodb.client.model.expressions.Expressions.ofStringArray; final class MqlExpression @@ -282,6 +283,114 @@ public DocumentExpression unsetField(final String fieldName) { /** @see Expression */ + @Override + public R passTo(final Function f) { + return f.apply(this.assertImplementsAllExpressions()); + } + + @Override + public R switchOn(final Function, ? extends BranchesTerminal> switchMap) { + return switchMapInternal(this.assertImplementsAllExpressions(), switchMap.apply(new Branches<>())); + } + + @Override + public R passBooleanTo(final Function f) { + return f.apply(this.assertImplementsAllExpressions()); + } + + @Override + public R switchBooleanOn(final Function, ? extends BranchesTerminal> switchMap) { + return switchMapInternal(this.assertImplementsAllExpressions(), switchMap.apply(new Branches<>())); + } + + @Override + public R passIntegerTo(final Function f) { + return f.apply(this.assertImplementsAllExpressions()); + } + + @Override + public R switchIntegerOn(final Function, ? extends BranchesTerminal> switchMap) { + return switchMapInternal(this.assertImplementsAllExpressions(), switchMap.apply(new Branches<>())); + } + + @Override + public R passNumberTo(final Function f) { + return f.apply(this.assertImplementsAllExpressions()); + } + + @Override + public R switchNumberOn(final Function, ? extends BranchesTerminal> switchMap) { + return switchMapInternal(this.assertImplementsAllExpressions(), switchMap.apply(new Branches<>())); + } + + @Override + public R passStringTo(final Function f) { + return f.apply(this.assertImplementsAllExpressions()); + } + + @Override + public R switchStringOn(final Function, ? extends BranchesTerminal> switchMap) { + return switchMapInternal(this.assertImplementsAllExpressions(), switchMap.apply(new Branches<>())); + } + + @Override + public R passDateTo(final Function f) { + return f.apply(this.assertImplementsAllExpressions()); + } + + @Override + public R switchDateOn(final Function, ? extends BranchesTerminal> switchMap) { + return switchMapInternal(this.assertImplementsAllExpressions(), switchMap.apply(new Branches<>())); + } + + @Override + public R passArrayTo(final Function, ? extends R> f) { + return f.apply(this.assertImplementsAllExpressions()); + } + + @Override + public R switchArrayOn(final Function>, ? extends BranchesTerminal, ? extends R>> switchMap) { + return switchMapInternal(this.assertImplementsAllExpressions(), switchMap.apply(new Branches<>())); + } + + @Override + public R passMapTo(final Function, ? extends R> f) { + return f.apply(this.assertImplementsAllExpressions()); + } + + @Override + public R switchMapOn(final Function>, ? extends BranchesTerminal, ? extends R>> switchMap) { + return switchMapInternal(this.assertImplementsAllExpressions(), switchMap.apply(new Branches<>())); + } + + @Override + public R passDocumentTo(final Function f) { + return f.apply(this.assertImplementsAllExpressions()); + } + + @Override + public R switchDocumentOn(final Function, ? extends BranchesTerminal> switchMap) { + return switchMapInternal(this.assertImplementsAllExpressions(), switchMap.apply(new Branches<>())); + } + + private R0 switchMapInternal( + final T0 value, final BranchesTerminal construct) { + return newMqlExpression((cr) -> { + BsonArray branches = new BsonArray(); + for (Function> fn : construct.getBranches()) { + SwitchCase result = fn.apply(value); + branches.add(new BsonDocument() + .append("case", extractBsonValue(cr, result.getCaseValue())) + .append("then", extractBsonValue(cr, result.getThenValue()))); + } + BsonDocument switchBson = new BsonDocument().append("branches", branches); + if (construct.getDefaults() != null) { + switchBson = switchBson.append("default", extractBsonValue(cr, construct.getDefaults().apply(value))); + } + return astDoc("$switch", switchBson); + }); + } + @Override public BooleanExpression eq(final Expression eq) { return new MqlExpression<>(ast("$eq", eq)); @@ -313,7 +422,7 @@ public BooleanExpression lte(final Expression lte) { } public BooleanExpression isBoolean() { - return new MqlExpression<>(ast("$type")).eq(of("bool")); + return new MqlExpression<>(astWrapped("$type")).eq(of("bool")); } @Override @@ -331,16 +440,30 @@ public NumberExpression isNumberOr(final NumberExpression other) { } public BooleanExpression isInteger() { - return this.isNumber().cond(this.eq(this.round()), of(false)); + return switchOn(on -> on + .isNumber(v -> v.round().eq(v)) + .defaults(v -> of(false))); } @Override public IntegerExpression isIntegerOr(final IntegerExpression other) { - return this.isInteger().cond(this, other); + /* + The server does not evaluate both branches of and/or/cond unless needed. + However, the server has a pipeline optimization stage prior to + evaluation that does attempt to optimize both branches, and fails with + "Failed to optimize pipeline" when there is a problem arising from the + use of literals and typed expressions. Using "switch" avoids this, + otherwise we could just use: + this.isNumber().and(this.eq(this.round())) + */ + + return this.switchOn(on -> on + .isNumber(v -> (IntegerExpression) v.round().eq(v).cond(v, other)) + .defaults(v -> other)); } public BooleanExpression isString() { - return new MqlExpression<>(ast("$type")).eq(of("string")); + return new MqlExpression<>(astWrapped("$type")).eq(of("string")); } @Override @@ -349,7 +472,7 @@ public StringExpression isStringOr(final StringExpression other) { } public BooleanExpression isDate() { - return ofStringArray("date").contains(new MqlExpression<>(ast("$type"))); + return ofStringArray("date").contains(new MqlExpression<>(astWrapped("$type"))); } @Override @@ -362,7 +485,7 @@ public BooleanExpression isArray() { } private Expression ifNull(final Expression ifNull) { - return new MqlExpression<>(ast("$ifNull", ifNull, Expressions.ofNull())) + return new MqlExpression<>(ast("$ifNull", ifNull, ofNull())) .assertImplementsAllExpressions(); } @@ -380,7 +503,7 @@ public ArrayExpression isArrayOr(final ArrayExpression return (ArrayExpression) this.isArray().cond(this.assertImplementsAllExpressions(), other); } - private BooleanExpression isDocumentOrMap() { + BooleanExpression isDocumentOrMap() { return new MqlExpression<>(ast("$type")).eq(of("object")); } @@ -395,6 +518,10 @@ public MapExpression isMapOr(final MapExpression(astWrapped("$toString")); 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 index f08b9a57d00..91d922dd476 100644 --- a/driver-core/src/main/com/mongodb/client/model/expressions/NumberExpression.java +++ b/driver-core/src/main/com/mongodb/client/model/expressions/NumberExpression.java @@ -16,6 +16,8 @@ package com.mongodb.client.model.expressions; +import java.util.function.Function; + /** * Expresses a numeric value. */ @@ -56,4 +58,7 @@ default NumberExpression subtract(final Number subtract) { NumberExpression abs(); DateExpression millisecondsToDate(); + + R passNumberTo(Function f); + R switchNumberOn(Function, ? extends BranchesTerminal> on); } 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 index c5cc7a8eb72..f4990b54949 100644 --- a/driver-core/src/main/com/mongodb/client/model/expressions/StringExpression.java +++ b/driver-core/src/main/com/mongodb/client/model/expressions/StringExpression.java @@ -16,6 +16,8 @@ package com.mongodb.client.model.expressions; +import java.util.function.Function; + import static com.mongodb.client.model.expressions.Expressions.of; /** @@ -52,4 +54,7 @@ default StringExpression substrBytes(final int start, final int length) { DateExpression parseDate(StringExpression format); DateExpression parseDate(StringExpression timezone, StringExpression format); + + R passStringTo(Function f); + R switchStringOn(Function, ? extends BranchesTerminal> on); } diff --git a/driver-core/src/main/com/mongodb/client/model/expressions/SwitchCase.java b/driver-core/src/main/com/mongodb/client/model/expressions/SwitchCase.java new file mode 100644 index 00000000000..bbdc608391c --- /dev/null +++ b/driver-core/src/main/com/mongodb/client/model/expressions/SwitchCase.java @@ -0,0 +1,35 @@ +/* + * 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; + +final class SwitchCase { + private final BooleanExpression caseValue; + private final R thenValue; + + SwitchCase(final BooleanExpression caseValue, final R thenValue) { + this.caseValue = caseValue; + this.thenValue = thenValue; + } + + BooleanExpression getCaseValue() { + return caseValue; + } + + R getThenValue() { + return thenValue; + } +} diff --git a/driver-core/src/test/functional/com/mongodb/client/model/expressions/ArrayExpressionsFunctionalTest.java b/driver-core/src/test/functional/com/mongodb/client/model/expressions/ArrayExpressionsFunctionalTest.java index b622688b49f..a72fbcd29fc 100644 --- a/driver-core/src/test/functional/com/mongodb/client/model/expressions/ArrayExpressionsFunctionalTest.java +++ b/driver-core/src/test/functional/com/mongodb/client/model/expressions/ArrayExpressionsFunctionalTest.java @@ -321,8 +321,9 @@ public void elementAtTest() { // 0.0 is a valid integer value array123.elementAt(of(0.0).isIntegerOr(of(-1))), // MQL: - "{'$arrayElemAt': [[1, 2, 3], {'$cond': [{'$cond': " - + "[{'$isNumber': [0.0]}, {'$eq': [0.0, {'$round': 0.0}]}, false]}, 0.0, -1]}]}"); + "{'$arrayElemAt': [[1, 2, 3], {'$switch': {'branches': [{'case': " + + "{'$isNumber': [0.0]}, 'then': {'$cond': " + + "[{'$eq': [{'$round': 0.0}, 0.0]}, 0.0, -1]}}], 'default': -1}}]} "); // negatives assertExpression( Arrays.asList(1, 2, 3).get(3 - 1), diff --git a/driver-core/src/test/functional/com/mongodb/client/model/expressions/ComparisonExpressionsFunctionalTest.java b/driver-core/src/test/functional/com/mongodb/client/model/expressions/ComparisonExpressionsFunctionalTest.java index b044128f039..1dc30aea020 100644 --- a/driver-core/src/test/functional/com/mongodb/client/model/expressions/ComparisonExpressionsFunctionalTest.java +++ b/driver-core/src/test/functional/com/mongodb/client/model/expressions/ComparisonExpressionsFunctionalTest.java @@ -44,6 +44,7 @@ class ComparisonExpressionsFunctionalTest extends AbstractExpressionsFunctionalT ofNull(), of(0), of(1), + of(2.0), of(""), of("str"), of(BsonDocument.parse("{}")), @@ -60,6 +61,17 @@ class ComparisonExpressionsFunctionalTest extends AbstractExpressionsFunctionalT of(Instant.now()) ); + @Test + public void literalsTest() { + // special values + assertExpression(null, ofNull(), "null"); + // the "missing" value is obtained via getField. + // the "$$REMOVE" value is intentionally not exposed. It is used internally. + // the "undefined" value is deprecated. + // https://www.mongodb.com/docs/manual/reference/operator/aggregation/literal/ + // $literal is intentionally not exposed. It is used internally. + } + @Test public void eqTest() { // https://www.mongodb.com/docs/manual/reference/operator/aggregation/eq/ diff --git a/driver-core/src/test/functional/com/mongodb/client/model/expressions/ControlExpressionsFunctionalTest.java b/driver-core/src/test/functional/com/mongodb/client/model/expressions/ControlExpressionsFunctionalTest.java new file mode 100644 index 00000000000..77a539f418d --- /dev/null +++ b/driver-core/src/test/functional/com/mongodb/client/model/expressions/ControlExpressionsFunctionalTest.java @@ -0,0 +1,279 @@ +/* + * 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.Document; +import org.junit.jupiter.api.Test; + +import java.time.Instant; +import java.util.function.Function; + +import static com.mongodb.client.model.expressions.Expressions.of; +import static com.mongodb.client.model.expressions.Expressions.ofArray; +import static com.mongodb.client.model.expressions.Expressions.ofIntegerArray; +import static com.mongodb.client.model.expressions.Expressions.ofMap; +import static com.mongodb.client.model.expressions.Expressions.ofNull; + +class ControlExpressionsFunctionalTest extends AbstractExpressionsFunctionalTest { + + @Test + public void passToTest() { + Function intDecrement = (e) -> e.subtract(of(1)); + Function numDecrement = (e) -> e.subtract(of(1)); + + // "nested functional" function application: + assertExpression( + 2 - 1, + intDecrement.apply(of(2)), + "{'$subtract': [2, 1]}"); + // "chained" function application produces the same MQL: + assertExpression( + 2 - 1, + of(2).passIntegerTo(intDecrement), + "{'$subtract': [2, 1]}"); + + // variations + assertExpression( + 2 - 1, + of(2).passIntegerTo(numDecrement)); + assertExpression( + 2 - 1, + of(2).passNumberTo(numDecrement)); + + // all types + Function test = on -> of("A"); + assertExpression("A", of(true).passTo(test)); + assertExpression("A", of(false).passBooleanTo(test)); + assertExpression("A", of(0).passIntegerTo(test)); + assertExpression("A", of(0).passNumberTo(test)); + assertExpression("A", of("").passStringTo(test)); + assertExpression("A", of(Instant.ofEpochMilli(123)).passDateTo(test)); + assertExpression("A", ofIntegerArray(1, 2).passArrayTo(test)); + assertExpression("A", of(Document.parse("{_id: 'a'}")).passDocumentTo(test)); + assertExpression("A", ofMap(Document.parse("{_id: 'a'}")).passMapTo(test)); + } + + @Test + public void switchTest() { + // https://www.mongodb.com/docs/manual/reference/operator/aggregation/switch/ + assertExpression("a", of(0).switchOn(on -> on.is(v -> v.eq(of(0)), v -> of("a")))); + assertExpression("a", of(0).switchOn(on -> on.isNumber(v -> of("a")))); + assertExpression("a", of(0).switchOn(on -> on.eq(of(0), v -> of("a")))); + assertExpression("a", of(0).switchOn(on -> on.lte(of(9), v -> of("a")))); + + // test branches + Function isOver10 = v -> v.subtract(10).gt(of(0)); + Function s = e -> e + .switchIntegerOn(on -> on + .eq(of(0), v -> of("A")) + .lt(of(10), v -> of("B")) + .is(isOver10, v -> of("C")) + .defaults(v -> of("D"))) + .toLower(); + + assertExpression("a", of(0).passIntegerTo(s)); + assertExpression("b", of(9).passIntegerTo(s)); + assertExpression("b", of(-9).passIntegerTo(s)); + assertExpression("c", of(11).passIntegerTo(s)); + assertExpression("d", of(10).passIntegerTo(s)); + } + + @Test + public void switchInferenceTest() { + // the following must compile: + assertExpression( + "b", + of(1).switchOn(on -> on + .eq(of(0), v -> of("a")) + .eq(of(1), v -> of("b")) + )); + // the "of(0)" must not cause a type inference of T being an integer, + // since switchOn expects an Expression. + } + + @Test + public void switchTypesTest() { + Function label = expr -> expr.switchOn(on -> on + .isBoolean(v -> v.asString().concat(of(" - bool"))) + // integer should be checked before string + .isInteger(v -> v.asString().concat(of(" - integer"))) + .isNumber(v -> v.asString().concat(of(" - number"))) + .isString(v -> v.asString().concat(of(" - string"))) + .isDate(v -> v.asString().concat(of(" - date"))) + .isArray((ArrayExpression v) -> v.sum(a -> a).asString().concat(of(" - array"))) + .isDocument(v -> v.getString("_id").concat(of(" - document"))) + .isNull(v -> of("null - null")) + .defaults(v -> of("default")) + ).toLower(); + assertExpression("true - bool", of(true).passTo(label)); + assertExpression("false - bool", of(false).passBooleanTo(label)); + assertExpression("1 - integer", of(1).passIntegerTo(label)); + assertExpression("1 - integer", of(1.0).passNumberTo(label)); + assertExpression("1.01 - number", of(1.01).passNumberTo(label)); + assertExpression("abc - string", of("abc").passStringTo(label)); + assertExpression("1970-01-01t00:00:00.123z - date", of(Instant.ofEpochMilli(123)).passDateTo(label)); + assertExpression("3 - array", ofIntegerArray(1, 2).passArrayTo(label)); + assertExpression("a - document", of(Document.parse("{_id: 'a'}")).passDocumentTo(label)); + // maps are considered documents + assertExpression("a - document", ofMap(Document.parse("{_id: 'a'}")).passMapTo(label)); + assertExpression("null - null", ofNull().passTo(label)); + // maps via isMap: + assertExpression( + "12 - map", + ofMap(Document.parse("{a: '1', b: '2'}")).switchOn(on -> on + .isMap((MapExpression v) -> v.entrySet() + .join(e -> e.getValue()).concat(of(" - map"))))); + // arrays via isArray, and tests signature: + assertExpression( + "ab - array", + ofArray(of("a"), of("b")).switchOn(on -> on + .isArray((ArrayExpression v) -> v + .join(e -> e).concat(of(" - array"))))); + } + + private BranchesIntermediary branches(final Branches on) { + return on.is(v -> of(true), v -> of("A")); + } + + @Test + public void switchTestVariants() { + assertExpression("A", of(true).switchOn(this::branches)); + assertExpression("A", of(false).switchBooleanOn(this::branches)); + assertExpression("A", of(0).switchIntegerOn(this::branches)); + assertExpression("A", of(0).switchNumberOn(this::branches)); + assertExpression("A", of("").switchStringOn(this::branches)); + assertExpression("A", of(Instant.ofEpochMilli(123)).switchDateOn(this::branches)); + assertExpression("A", ofIntegerArray(1, 2).switchArrayOn(this::branches)); + assertExpression("A", of(Document.parse("{_id: 'a'}")).switchDocumentOn(this::branches)); + assertExpression("A", ofMap(Document.parse("{_id: 'a'}")).switchMapOn(this::branches)); + } + + @Test + public void switchTestInitial() { + assertExpression("A", + of(0).switchOn(on -> on.is(v -> v.gt(of(-1)), v -> of("A"))), + "{'$switch': {'branches': [{'case': {'$gt': [0, -1]}, 'then': 'A'}]}}"); + // eq lt lte + assertExpression("A", + of(0).switchOn(on -> on.eq(of(0), v -> of("A"))), + "{'$switch': {'branches': [{'case': {'$eq': [0, 0]}, 'then': 'A'}]}}"); + assertExpression("A", + of(0).switchOn(on -> on.lt(of(1), v -> of("A"))), + "{'$switch': {'branches': [{'case': {'$lt': [0, 1]}, 'then': 'A'}]}}"); + assertExpression("A", + of(0).switchOn(on -> on.lte(of(0), v -> of("A"))), + "{'$switch': {'branches': [{'case': {'$lte': [0, 0]}, 'then': 'A'}]}}"); + // is type + assertExpression("A", + of(true).switchOn(on -> on.isBoolean(v -> of("A"))), + "{'$switch': {'branches': [{'case': {'$eq': [{'$type': [true]}, 'bool']}, 'then': 'A'}]}}"); + assertExpression("A", + of(1).switchOn(on -> on.isNumber(v -> of("A"))), + "{'$switch': {'branches': [{'case': {'$isNumber': [1]}, 'then': 'A'}]}}"); + assertExpression("A", + of(1).switchOn(on -> on.isInteger(v -> of("A"))), + "{'$switch': {'branches': [{'case': {'$switch': {'branches': [{'case': {'$isNumber': [1]}," + + "'then': {'$eq': [{'$round': 1}, 1]}}], 'default': false}}, 'then': 'A'}]}}"); + assertExpression("A", + of("x").switchOn(on -> on.isString(v -> of("A"))), + "{'$switch': {'branches': [{'case': {'$eq': [{'$type': ['x']}, 'string']}, 'then': 'A'}]}}"); + assertExpression("A", + of(Instant.ofEpochMilli(123)).switchOn(on -> on.isDate(v -> of("A"))), + "{'$switch': {'branches': [{'case': {'$in': [{'$type': " + + "[{'$date': '1970-01-01T00:00:00.123Z'}]}, ['date']]}, 'then': 'A'}]}}"); + assertExpression("A", + ofIntegerArray(0).switchOn(on -> on.isArray(v -> of("A"))), + "{'$switch': {'branches': [{'case': {'$isArray': [[0]]}, 'then': 'A'}]}}"); + assertExpression("A", + of(Document.parse("{}")).switchOn(on -> on.isDocument(v -> of("A"))), + "{'$switch': {'branches': [{'case': {'$eq': [{'$type': " + + "[{'$literal': {}}]}, 'object']}, 'then': 'A'}]}}"); + assertExpression("A", + ofMap(Document.parse("{}")).switchOn(on -> on.isMap(v -> of("A"))), + "{'$switch': {'branches': [{'case': {'$eq': [{'$type': " + + "{'$literal': {}}}, 'object']}, 'then': 'A'}]}}"); + assertExpression("A", + ofNull().switchOn(on -> on.isNull(v -> of("A"))), + "{'$switch': {'branches': [{'case': {'$eq': [null, null]}, 'then': 'A'}]}}"); + } + + @Test + public void switchTestPartial() { + assertExpression("A", + of(0).switchOn(on -> on.isNull(v -> of("X")).is(v -> v.gt(of(-1)), v -> of("A"))), + "{'$switch': {'branches': [{'case': {'$eq': [0, null]}, 'then': 'X'}, " + + "{'case': {'$gt': [0, -1]}, 'then': 'A'}]}}"); + assertExpression("A", + of(0).switchOn(on -> on.isNull(v -> of("X")).defaults(v -> of("A"))), + "{'$switch': {'branches': [{'case': {'$eq': [0, null]}, 'then': 'X'}], " + + "'default': 'A'}}"); + // eq lt lte + assertExpression("A", + of(0).switchOn(on -> on.isNull(v -> of("X")).eq(of(0), v -> of("A"))), + "{'$switch': {'branches': [{'case': {'$eq': [0, null]}, 'then': 'X'}, " + + "{'case': {'$eq': [0, 0]}, 'then': 'A'}]}}"); + assertExpression("A", + of(0).switchOn(on -> on.isNull(v -> of("X")).lt(of(1), v -> of("A"))), + "{'$switch': {'branches': [{'case': {'$eq': [0, null]}, 'then': 'X'}, " + + "{'case': {'$lt': [0, 1]}, 'then': 'A'}]}}"); + assertExpression("A", + of(0).switchOn(on -> on.isNull(v -> of("X")).lte(of(0), v -> of("A"))), + "{'$switch': {'branches': [{'case': {'$eq': [0, null]}, 'then': 'X'}, " + + "{'case': {'$lte': [0, 0]}, 'then': 'A'}]}}"); + // is type + assertExpression("A", + of(true).switchOn(on -> on.isNull(v -> of("X")).isBoolean(v -> of("A"))), + "{'$switch': {'branches': [{'case': {'$eq': [true, null]}, 'then': 'X'}, " + + "{'case': {'$eq': [{'$type': [true]}, 'bool']}, 'then': 'A'}]}}"); + assertExpression("A", + of(1).switchOn(on -> on.isNull(v -> of("X")).isNumber(v -> of("A"))), + "{'$switch': {'branches': [{'case': {'$eq': [1, null]}, 'then': 'X'}, " + + "{'case': {'$isNumber': [1]}, 'then': 'A'}]}}"); + assertExpression("A", + of(1).switchOn(on -> on.isNull(v -> of("X")).isInteger(v -> of("A"))), + "{'$switch': {'branches': [{'case': {'$eq': [1, null]}, 'then': 'X'}, {'case': " + + "{'$switch': {'branches': [{'case': {'$isNumber': [1]}, " + + "'then': {'$eq': [{'$round': 1}, 1]}}], 'default': false}}, 'then': 'A'}]}}"); + assertExpression("A", + of("x").switchOn(on -> on.isNull(v -> of("X")).isString(v -> of("A"))), + "{'$switch': {'branches': [{'case': {'$eq': ['x', null]}, 'then': 'X'}, " + + "{'case': {'$eq': [{'$type': ['x']}, 'string']}, 'then': 'A'}]}}"); + assertExpression("A", + of(Instant.ofEpochMilli(123)).switchOn(on -> on.isNull(v -> of("X")).isDate(v -> of("A"))), + "{'$switch': {'branches': [" + + "{'case': {'$eq': [{'$date': '1970-01-01T00:00:00.123Z'}, null]}, 'then': 'X'}, " + + "{'case': {'$in': [{'$type': [{'$date': '1970-01-01T00:00:00.123Z'}]}, " + + "['date']]}, 'then': 'A'}]}}"); + assertExpression("A", + ofIntegerArray(0).switchOn(on -> on.isNull(v -> of("X")).isArray(v -> of("A"))), + "{'$switch': {'branches': [{'case': {'$eq': [[0], null]}, 'then': 'X'}, " + + "{'case': {'$isArray': [[0]]}, 'then': 'A'}]}}"); + assertExpression("A", + of(Document.parse("{}")).switchOn(on -> on.isNull(v -> of("X")).isDocument(v -> of("A"))), + "{'$switch': {'branches': [{'case': {'$eq': [{'$literal': {}}, null]}, 'then': 'X'}, " + + "{'case': {'$eq': [{'$type': [{'$literal': {}}]}, 'object']}, 'then': 'A'}]}}"); + assertExpression("A", + ofMap(Document.parse("{}")).switchOn(on -> on.isNull(v -> of("X")).isMap(v -> of("A"))), + " {'$switch': {'branches': [" + + "{'case': {'$eq': [{'$literal': {}}, null]}, 'then': 'X'}, " + + "{'case': {'$eq': [{'$type': {'$literal': {}}}, 'object']}, 'then': 'A'}]}}"); + assertExpression("A", + ofNull().switchOn(on -> on.isNumber(v -> of("X")).isNull(v -> of("A"))), + "{'$switch': {'branches': [{'case': {'$isNumber': [null]}, 'then': 'X'}, " + + "{'case': {'$eq': [null, null]}, 'then': 'A'}]}}"); + } +} diff --git a/driver-core/src/test/functional/com/mongodb/client/model/expressions/TypeExpressionsFunctionalTest.java b/driver-core/src/test/functional/com/mongodb/client/model/expressions/TypeExpressionsFunctionalTest.java index adb4a82e9ce..db600647ab5 100644 --- a/driver-core/src/test/functional/com/mongodb/client/model/expressions/TypeExpressionsFunctionalTest.java +++ b/driver-core/src/test/functional/com/mongodb/client/model/expressions/TypeExpressionsFunctionalTest.java @@ -46,7 +46,7 @@ public void isBooleanOrTest() { assertExpression( true, of(true).isBooleanOr(of(false)), - "{'$cond': [{'$eq': [{'$type': true}, 'bool']}, true, false]}"); + "{'$cond': [{'$eq': [{'$type': [true]}, 'bool']}, true, false]}"); // non-boolean: assertExpression(false, ofIntegerArray(1).isBooleanOr(of(false))); assertExpression(false, ofNull().isBooleanOr(of(false))); @@ -65,12 +65,30 @@ public void isNumberOrTest() { assertExpression(99, ofNull().isNumberOr(of(99))); } + @Test + public void isIntegerOr() { + assertExpression( + 1, + of(1).isIntegerOr(of(99)), + "{'$switch': {'branches': [{'case': {'$isNumber': [1]}, 'then': " + + "{'$cond': [{'$eq': [{'$round': 1}, 1]}, 1, 99]}}], 'default': 99}}" + ); + // other numeric values: + assertExpression(1L, of(1L).isIntegerOr(of(99))); + assertExpression(1.0, of(1.0).isIntegerOr(of(99))); + assertExpression(Decimal128.parse("1"), of(Decimal128.parse("1")).isIntegerOr(of(99))); + // non-numeric: + assertExpression(99, ofIntegerArray(1).isIntegerOr(of(99))); + assertExpression(99, ofNull().isIntegerOr(of(99))); + assertExpression(99, of("str").isIntegerOr(of(99))); + } + @Test public void isStringOrTest() { assertExpression( "abc", of("abc").isStringOr(of("or")), - "{'$cond': [{'$eq': [{'$type': 'abc'}, 'string']}, 'abc', 'or']}"); + "{'$cond': [{'$eq': [{'$type': ['abc']}, 'string']}, 'abc', 'or']}"); // non-string: assertExpression("or", ofIntegerArray(1).isStringOr(of("or"))); assertExpression("or", ofNull().isStringOr(of("or"))); @@ -82,7 +100,7 @@ public void isDateOrTest() { assertExpression( date, of(date).isDateOr(of(date.plusMillis(10))), - "{'$cond': [{'$in': [{'$type': {'$date': '2007-12-03T10:15:30.005Z'}}, ['date']]}, " + "{'$cond': [{'$in': [{'$type': [{'$date': '2007-12-03T10:15:30.005Z'}]}, ['date']]}, " + "{'$date': '2007-12-03T10:15:30.005Z'}, {'$date': '2007-12-03T10:15:30.015Z'}]}"); // non-date: assertExpression(date, ofIntegerArray(1).isDateOr(of(date))); @@ -107,7 +125,7 @@ public void isDocumentOrTest() { assertExpression( doc, of(doc).isDocumentOr(of(BsonDocument.parse("{b: 2}"))), - "{'$cond': [{'$eq': [{'$type': {'$literal': {'a': 1}}}, 'object']}, " + "{'$cond': [{'$eq': [{'$type': [{'$literal': {'a': 1}}]}, 'object']}, " + "{'$literal': {'a': 1}}, {'$literal': {'b': 2}}]}"); // non-document: assertExpression(doc, ofIntegerArray(1).isDocumentOr(of(doc))); From 6436f96c0833d2b5adced014a304c678e1c8e585 Mon Sep 17 00:00:00 2001 From: Maxim Katcharov Date: Thu, 19 Jan 2023 08:48:22 -0700 Subject: [PATCH 13/27] Test expressions in context (#1057) JAVA-4820 --- .../client/model/expressions/Expressions.java | 10 + .../InContextExpressionsFunctionalTest.java | 196 ++++++++++++++++++ 2 files changed, 206 insertions(+) create mode 100644 driver-sync/src/test/functional/com/mongodb/client/model/expressions/InContextExpressionsFunctionalTest.java 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 index 26b50356877..132d0fa03c1 100644 --- a/driver-core/src/main/com/mongodb/client/model/expressions/Expressions.java +++ b/driver-core/src/main/com/mongodb/client/model/expressions/Expressions.java @@ -172,6 +172,16 @@ public static ArrayExpression ofStringArray(final String... ar return new MqlExpression<>((cr) -> new AstPlaceholder(new BsonArray(result))); } + public static DocumentExpression current() { + return new MqlExpression<>((cr) -> new AstPlaceholder(new BsonString("$$CURRENT"))) + .assertImplementsAllExpressions(); + } + + public static MapExpression currentAsMap() { + return new MqlExpression<>((cr) -> new AstPlaceholder(new BsonString("$$CURRENT"))) + .assertImplementsAllExpressions(); + } + @SafeVarargs // nothing is stored in the array public static ArrayExpression ofArray(final T... array) { Assertions.notNull("array", array); diff --git a/driver-sync/src/test/functional/com/mongodb/client/model/expressions/InContextExpressionsFunctionalTest.java b/driver-sync/src/test/functional/com/mongodb/client/model/expressions/InContextExpressionsFunctionalTest.java new file mode 100644 index 00000000000..d9ca29b07d1 --- /dev/null +++ b/driver-sync/src/test/functional/com/mongodb/client/model/expressions/InContextExpressionsFunctionalTest.java @@ -0,0 +1,196 @@ +/* + * 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.MongoClientSettings; +import com.mongodb.client.AggregateIterable; +import com.mongodb.client.FindIterable; +import com.mongodb.client.MongoClient; +import com.mongodb.client.MongoClients; +import com.mongodb.client.MongoCollection; +import com.mongodb.client.model.Aggregates; +import org.bson.Document; +import org.bson.conversions.Bson; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import static com.mongodb.client.model.Accumulators.sum; +import static com.mongodb.client.model.Aggregates.match; +import static com.mongodb.client.model.Aggregates.project; +import static com.mongodb.client.model.Filters.expr; +import static com.mongodb.client.model.Projections.computed; +import static com.mongodb.client.model.Projections.excludeId; +import static com.mongodb.client.model.Projections.fields; +import static com.mongodb.client.model.Projections.include; +import static com.mongodb.client.model.Sorts.ascending; +import static com.mongodb.client.model.expressions.Expressions.current; +import static com.mongodb.client.model.expressions.Expressions.of; +import static com.mongodb.client.model.expressions.Expressions.ofArray; +import static org.junit.jupiter.api.Assertions.assertEquals; + +class InContextExpressionsFunctionalTest extends AbstractExpressionsFunctionalTest { + + private MongoClient client; + private MongoCollection col; + + @BeforeEach + public void setUp() { + client = MongoClients.create(); + col = client.getDatabase("testdb").getCollection("testcol"); + col.drop(); + } + + @AfterEach + public void tearDown() { + client.close(); + } + + private static String bsonToString(final Bson project) { + return project.toBsonDocument(Document.class, MongoClientSettings.getDefaultCodecRegistry()).toString().replaceAll("\"", "'"); + } + + private List aggregate(final Bson... stages) { + AggregateIterable result = col.aggregate(Arrays.asList(stages)); + List results = new ArrayList<>(); + result.forEach(r -> results.add(r)); + return results; + } + + @Test + public void findTest() { + col.insertMany(Arrays.asList( + Document.parse("{_id: 1, x: 0, y: 2}"), + Document.parse("{_id: 2, x: 0, y: 3}"), + Document.parse("{_id: 3, x: 1, y: 3}"))); + + FindIterable iterable = col.find(expr( + current().getInteger("x").eq(of(1)))); + List results = new ArrayList<>(); + iterable.forEach(r -> results.add(r)); + + assertEquals( + Arrays.asList(Document.parse("{_id: 3, x: 1, y: 3}")), + results); + } + + @Test + public void matchTest() { + col.insertMany(Arrays.asList( + Document.parse("{_id: 1, x: 0, y: 2}"), + Document.parse("{_id: 2, x: 0, y: 3}"), + Document.parse("{_id: 3, x: 1, y: 3}"))); + + List results = aggregate( + match(expr(current().getInteger("x").eq(of(1))))); + + assertEquals( + Arrays.asList(Document.parse("{_id: 3, x: 1, y: 3}")), + results); + } + + @Test + public void currentAsMapMatchTest() { + col.insertMany(Arrays.asList( + Document.parse("{_id: 1, x: 0, y: 2}"), + Document.parse("{_id: 2, x: 0, y: 3}"), + Document.parse("{_id: 3, x: 1, y: 3}"))); + + List results = aggregate( + match(expr(Expressions.currentAsMap() + .entrySet() + .map(e -> e.getValue()) + .sum(v -> v).eq(of(7))))); + + assertEquals( + Arrays.asList(Document.parse("{_id: 3, x: 1, y: 3}")), + results); + } + + @Test + public void projectTest() { + col.insertMany(Arrays.asList( + Document.parse("{_id: 1, x: 0, y: 2}"))); + + List expected = Arrays.asList(Document.parse("{_id: 1, x: 0, c: 2}")); + + // old, using "$y" + Bson projectOld = project(fields(include("x"), computed("c", + "$y"))); + assertEquals("{'$project': {'x': 1, 'c': '$y'}}", bsonToString(projectOld)); + assertEquals(expected, + aggregate(projectOld)); + + // new, using current() with add/subtract + Bson projectNew = project(fields(include("x"), computed("c", + current().getInteger("y").add(10).subtract(10)))); + assertEquals( + "{'$project': {'x': 1, 'c': " + + "{'$subtract': [{'$add': [{'$getField': " + + "{'input': '$$CURRENT', 'field': 'y'}}, 10]}, 10]}}}", + bsonToString(projectNew)); + assertEquals(expected, + aggregate(projectNew)); + } + + @Test + public void projectTest2() { + col.insertMany(Arrays.asList(Document.parse("{_id: 0, x: 1}"))); + + // new, nestedArray + Bson projectNestedArray = project(fields(excludeId(), computed("nestedArray", ofArray( + current().getInteger("x").max(of(4)), + current().getInteger("x"), + of(0), of(1), of(true), of(false) + )))); + assertEquals( + Arrays.asList(Document.parse("{ nestedArray: [ 4, 1, 0, 1, true, false ] }")), + aggregate(projectNestedArray)); + + // new, document + Bson projectDocument = project(fields(computed("nested", + // the below is roughly: "{ x: {$max : ['$x', 4] }}" + of(Document.parse("{x: 9}")).setField("x", current().getInteger("x").max(of(4))) + ))); + assertEquals( + Arrays.asList(Document.parse("{_id: 0, nested: { x: 4 } }")), + aggregate(projectDocument)); + } + + @Test + public void groupTest() { + col.insertMany(Arrays.asList( + Document.parse("{t: 0, a: 1}"), + Document.parse("{t: 0, a: 2}"), + Document.parse("{t: 1, a: 9}"))); + + List results = aggregate( + Aggregates.group( + current().getInteger("t").add(of(100)), + sum("sum", current().getInteger("a").add(1))), + Aggregates.sort(ascending("_id"))); + assertEquals( + Arrays.asList( + Document.parse("{_id: 100, sum: 5}"), + Document.parse("{_id: 101, sum: 10}")), + results); + } +} From 25553d166e6f511af9b2ba298c82e628d730f0ff Mon Sep 17 00:00:00 2001 From: Maxim Katcharov Date: Wed, 30 Nov 2022 11:35:59 -0700 Subject: [PATCH 14/27] Add javadoc for boolean, date, number, integer, and expression (#1059) JAVA-4799 --- .../model/expressions/BooleanExpression.java | 37 ++-- .../model/expressions/DateExpression.java | 95 ++++++++- .../client/model/expressions/Expression.java | 196 +++++++++++++----- .../model/expressions/IntegerExpression.java | 90 ++++++-- .../model/expressions/MqlExpression.java | 52 ++--- .../model/expressions/NumberExpression.java | 128 ++++++++++-- 6 files changed, 464 insertions(+), 134 deletions(-) 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 index 7f0b43370dc..00da72a208e 100644 --- a/driver-core/src/main/com/mongodb/client/model/expressions/BooleanExpression.java +++ b/driver-core/src/main/com/mongodb/client/model/expressions/BooleanExpression.java @@ -19,44 +19,41 @@ import java.util.function.Function; /** - * Expresses a boolean value. + * A logical boolean value, either true or false. */ 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. + * The logical negation of {@code this} value. * - * @return True if false; false if true. + * @return the resulting value. */ BooleanExpression not(); /** - * Returns logical true if this or the other expression evaluates to logical - * true. Returns logical false if both evaluate to logical false. + * The logical conjunction of {@code this} and the {@code other} value. * - * @param or the other boolean expression. - * @return True if either true, false if both false. + * @param other the other boolean value. + * @return the resulting value. */ - BooleanExpression or(BooleanExpression or); + BooleanExpression or(BooleanExpression other); /** - * Returns logical true if both this and the other expression evaluate to - * logical true. Returns logical false if either evaluate to logical false. + * The logical disjunction of {@code this} and the {@code other} value. * - * @param and the other boolean expression. - * @return true if both true, false if either false. + * @param other the other boolean value. + * @return the resulting value. */ - BooleanExpression and(BooleanExpression and); + BooleanExpression and(BooleanExpression other); + // TODO-END check the evaluation semantics of and/or /** - * 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. + * The {@code left} branch when {@code this} is true, + * and the {@code right} branch otherwise. * - * @param left the left branch expression - * @param right the right branch expression - * @return left if true, right if false. + * @param left the left branch. + * @param right the right branch. + * @return the resulting value. * @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 index cd211d1fd85..be2b838058b 100644 --- a/driver-core/src/main/com/mongodb/client/model/expressions/DateExpression.java +++ b/driver-core/src/main/com/mongodb/client/model/expressions/DateExpression.java @@ -19,20 +19,113 @@ import java.util.function.Function; /** - * Expresses a date value. + * An instantaneous date and time. Tracks a UTC datetime, the number of + * milliseconds since the Unix epoch. Does not track the timezone. */ public interface DateExpression extends Expression { + + /** + * The year of {@code this} date as determined by the provided + * {@code timezone}. + * + * @param timezone the UTC Offset or Olson Timezone Identifier. + * @return the resulting value. + */ IntegerExpression year(StringExpression timezone); + + /** + * The month of {@code this} date as determined by the provided + * {@code timezone}, as an integer between 1 and 12. + * + * @param timezone the UTC Offset or Olson Timezone Identifier. + * @return the resulting value. + */ IntegerExpression month(StringExpression timezone); + + /** + * The day of the month of {@code this} date as determined by the provided + * {@code timezone}, as an integer between 1 and 31. + * + * @param timezone the UTC Offset or Olson Timezone Identifier. + * @return the resulting value. + */ IntegerExpression dayOfMonth(StringExpression timezone); + + /** + * The day of the week of {@code this} date as determined by the provided + * {@code timezone}, as an integer between 1 (Sunday) and 7 (Saturday). + * + * @param timezone the UTC Offset or Olson Timezone Identifier. + * @return the resulting value. + */ IntegerExpression dayOfWeek(StringExpression timezone); + + /** + * The day of the year of {@code this} date as determined by the provided + * {@code timezone}, as an integer between 1 and 366. + * + * @param timezone the UTC Offset or Olson Timezone Identifier. + * @return the resulting value. + */ IntegerExpression dayOfYear(StringExpression timezone); + + /** + * The hour of {@code this} date as determined by the provided + * {@code timezone}, as an integer between 0 and 23. + * + * @param timezone the UTC Offset or Olson Timezone Identifier. + * @return the resulting value. + */ IntegerExpression hour(StringExpression timezone); + + /** + * The minute of {@code this} date as determined by the provided + * {@code timezone}, as an integer between 0 and 59. + * + * @param timezone the UTC Offset or Olson Timezone Identifier. + * @return the resulting value. + */ IntegerExpression minute(StringExpression timezone); + + /** + * The second of {@code this} date as determined by the provided + * {@code timezone}, as an integer between 0 and 59, and 60 in the case + * of a leap second. + * + * @param timezone the UTC Offset or Olson Timezone Identifier. + * @return the resulting value. + */ IntegerExpression second(StringExpression timezone); + + /** + * The week of the year of {@code this} date as determined by the provided + * {@code timezone}, as an integer between 0 and 53. + * + *

    Weeks begin on Sundays, and week 1 begins with the first Sunday of the + * year. Days preceding the first Sunday of the year are in week 0. + * + * @param timezone the UTC Offset or Olson Timezone Identifier. + * @return the resulting value. + */ IntegerExpression week(StringExpression timezone); + + /** + * The millisecond part of {@code this} date as determined by the provided + * {@code timezone}, as an integer between 0 and 999. + * + * @param timezone the UTC Offset or Olson Timezone Identifier. + * @return the resulting value. + */ IntegerExpression millisecond(StringExpression timezone); + /** + * The string representation of {@code this} date as determined by the + * provided {@code timezone}, and formatted according to the {@code format}. + * + * @param timezone the UTC Offset or Olson Timezone Identifier. + * @param format the format specifier. TODO-END what standard is this? + * @return the resulting value. + */ StringExpression asString(StringExpression timezone, StringExpression format); R passDateTo(Function f); 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 index a803134cc4f..fb94262796f 100644 --- a/driver-core/src/main/com/mongodb/client/model/expressions/Expression.java +++ b/driver-core/src/main/com/mongodb/client/model/expressions/Expression.java @@ -21,25 +21,11 @@ import java.util.function.Function; /** - * 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. + * sub-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. + * TODO-END: 'cause an error', 'execution context', wrong types/unsafe operations + * TODO-END: types and how missing/null are not part of any type. * * @see Expressions */ @@ -47,78 +33,184 @@ public interface Expression { /** - * Returns logical true if the value of this expression is equal to the - * value of the other expression. Otherwise, false. + * Whether {@code this} value is equal to the {@code other} value. * - * @param eq the other expression - * @return true if equal, false if not equal + * @param other the other value. + * @return the resulting value. */ - BooleanExpression eq(Expression eq); + BooleanExpression eq(Expression other); /** - * Returns logical true if the value of this expression is not equal to the - * value of the other expression. Otherwise, false. + * Whether {@code this} value is not equal to the {@code other} value. * - * @param ne the other expression - * @return true if equal, false otherwise + * @param other the other value. + * @return the resulting value. */ - BooleanExpression ne(Expression ne); + BooleanExpression ne(Expression other); /** - * Returns logical true if the value of this expression is greater than the - * value of the other expression. Otherwise, false. + * Whether {@code this} value is greater than the {@code other} value. * - * @param gt the other expression - * @return true if greater than, false otherwise + * @param other the other value. + * @return the resulting value. */ - BooleanExpression gt(Expression gt); + BooleanExpression gt(Expression other); /** - * Returns logical true if the value of this expression is greater than or - * equal to the value of the other expression. Otherwise, false. + * Whether {@code this} value is greater than or equal to the {@code other} + * value. * - * @param gte the other expression - * @return true if greater than or equal to, false otherwise + * @param other the other value. + * @return the resulting value. */ - BooleanExpression gte(Expression gte); + BooleanExpression gte(Expression other); /** - * Returns logical true if the value of this expression is less than the - * value of the other expression. Otherwise, false. + * Whether {@code this} value is less than the {@code other} value. * - * @param lt the other expression - * @return true if less than, false otherwise + * @param other the other value. + * @return the resulting value. */ - BooleanExpression lt(Expression lt); + BooleanExpression lt(Expression other); /** - * Returns logical true if the value of this expression is less than or - * equal to the value of the other expression. Otherwise, false. + * Whether {@code this} value is less than or equal to the {@code other} + * value. * - * @param lte the other expression - * @return true if less than or equal to, false otherwise + * @param other the other value. + * @return the resulting value. */ - BooleanExpression lte(Expression lte); + BooleanExpression lte(Expression other); /** - * also checks for nulls - * @param other - * @return + * {@code this} value as a {@link BooleanExpression boolean} if + * {@code this} is a boolean, or the {@code other} boolean value if + * {@code this} is null, or is missing, or is of any other non-boolean type. + * + * @param other the other value. + * @return the resulting value. */ BooleanExpression isBooleanOr(BooleanExpression other); + + /** + * {@code this} value as a {@link NumberExpression number} if + * {@code this} is a number, or the {@code other} number value if + * {@code this} is null, or is missing, or is of any other non-number type. + * + *

    Since integers are a subset of numbers, if a value is an integer, + * this does not imply that it is not a number, and vice-versa. + * + * @param other the other value. + * @return the resulting value. + */ NumberExpression isNumberOr(NumberExpression other); + + /** + * {@code this} value as an {@link IntegerExpression integer} if + * {@code this} is an integer, or the {@code other} integer value if + * {@code this} is null, or is missing, or is of any other non-integer type. + * + *

    Since integers are a subset of numbers, if a value is an integer, + * this does not imply that it is not a number, and vice-versa. + * + * @param other the other value. + * @return the resulting value. + */ IntegerExpression isIntegerOr(IntegerExpression other); + + /** + * {@code this} value as a {@link BooleanExpression boolean} if + * {@code this} is a boolean, or the {@code other} boolean value if + * {@code this} is null, or is missing, or is of any other non-boolean type. + * + * @param other the other value. + * @return the resulting value. + */ StringExpression isStringOr(StringExpression other); + + /** + * {@code this} value as a {@link StringExpression string} if + * {@code this} is a string, or the {@code other} string value if + * {@code this} is null, or is missing, or is of any other non-string type. + * + * @param other the other value. + * @return the resulting value. + */ DateExpression isDateOr(DateExpression other); + + + /** + * {@code this} value as a {@link ArrayExpression array} if + * {@code this} is an array, or the {@code other} array value if + * {@code this} is null, or is missing, or is of any other non-array type. + * + *

    Warning: this operation does not guarantee type safety. While this + * operation is guaranteed to produce an array, the type of the elements of + * that array are not guaranteed by the API. The specification of a type by + * the user is an unchecked assertion that all elements are of that type, + * and that no element is null, is missing, or is of some other type. If the + * user cannot make such an assertion, some appropriate super-type should be + * chosen, and elements should be individually type-checked. + * + * @param other the other value. + * @return the resulting value. + * @param the type of the elements of the resulting array. + */ ArrayExpression isArrayOr(ArrayExpression other); + + // TODO-END doc after Map merged, "record" and "schema objects" are decided T isDocumentOr(T other); MapExpression isMapOr(MapExpression other); + /** + * The {@link StringExpression string} value corresponding to this value. + * + *

    This may "cause an error" if the type cannot be converted to string, + * as is the case with {@link ArrayExpression arrays}, + * {@link DocumentExpression documents}, and {@link MapExpression maps}. + * TODO-END what about null/missing? + * TODO-END document vs record + * TODO-END "cause an error" above + * + * + * @see StringExpression#parseDate() + * @see StringExpression#parseInteger() + * TODO-END all the others? implement? + * @return the resulting value. + */ StringExpression asString(); + /** + * Applies the provided function to {@code this}. + * + *

    Equivalent to {@code f.apply(this)}, and allows lambdas and static, + * user-defined functions to use the chaining syntax. For example: + * {@code myInteger.apply(isEven)} (here, {@code isEven} is a function + * taking an {@link IntegerExpression integer} and yielding a + * {@link BooleanExpression boolean}). + * + * @param f the function to apply. + * @return the resulting value. + * @param the type of the resulting value. + */ R passTo(Function f); - - R switchOn(Function, ? extends BranchesTerminal> on); + /** + * The value resulting from applying the provided switch mapping to + * {@code this} value. + * + *

    Can be used to perform pattern matching on the type of {@code this} + * value, or to perform comparisons, or to perform any arbitrary check on + * {@code this} value. + * + *

    The suggested convention is to use "{@code on}" as the name of the + * {@code mapping} parameter, for example: + * {@code myValue.switchOn(on -> on.isInteger(...)...)}. + * + * @param mapping the switch mapping. + * @return the resulting value. + * @param the type of the resulting value. + */ + R switchOn(Function, ? extends BranchesTerminal> mapping); } 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 index 40cde216ce3..9e7bac51bae 100644 --- a/driver-core/src/main/com/mongodb/client/model/expressions/IntegerExpression.java +++ b/driver-core/src/main/com/mongodb/client/model/expressions/IntegerExpression.java @@ -19,32 +19,98 @@ import java.util.function.Function; /** - * Expresses an integer value. + * An integer value. Integers are a subset of {@link NumberExpression numbers}, + * and so, for example, the integer 0 and the number 0 are the same value, + * and are equal. */ public interface IntegerExpression extends NumberExpression { - IntegerExpression multiply(IntegerExpression i); - default IntegerExpression multiply(final int multiply) { - return this.multiply(Expressions.of(multiply)); + /** + * The product of multiplying {@code this} and the {@code other} value. + * + * @param other the other value. + * @return the resulting value. + */ + IntegerExpression multiply(IntegerExpression other); + + /** + * The product of multiplying {@code this} and the {@code other} value. + * + * @param other the other value. + * @return the resulting value. + */ + default IntegerExpression multiply(final int other) { + return this.multiply(Expressions.of(other)); } - IntegerExpression add(IntegerExpression i); + /** + * The sum of adding {@code this} and the {@code other} value. + * + * @param other the other value. + * @return the resulting value. + */ + IntegerExpression add(IntegerExpression other); - default IntegerExpression add(final int add) { - return this.add(Expressions.of(add)); + /** + * The sum of adding {@code this} and the {@code other} value. + * + * @param other the other value. + * @return the resulting value. + */ + default IntegerExpression add(final int other) { + return this.add(Expressions.of(other)); } - IntegerExpression subtract(IntegerExpression i); + /** + * The result of subtracting the {@code other} value from {@code this}. + * + * @param other the other value. + * @return the resulting value. + */ + IntegerExpression subtract(IntegerExpression other); - default IntegerExpression subtract(final int subtract) { - return this.subtract(Expressions.of(subtract)); + /** + * The result of subtracting the {@code other} value from {@code this}. + * + * @param other the other value. + * @return the resulting value. + */ + default IntegerExpression subtract(final int other) { + return this.subtract(Expressions.of(other)); } - IntegerExpression max(IntegerExpression i); - IntegerExpression min(IntegerExpression i); + /** + * The larger value of {@code this} and the {@code other} value. + * + * @param other the other value. + * @return the resulting value. + */ + IntegerExpression max(IntegerExpression other); + + /** + * The smaller value of {@code this} and the {@code other} value. + * + * @param other the other value. + * @return the resulting value. + */ + IntegerExpression min(IntegerExpression other); + /** + * The absolute value of {@code this} value. + * + * @return the resulting value. + */ IntegerExpression abs(); + /** + * The {@link DateExpression date} corresponding to {@code this} value + * when taken to be the number of milliseconds since the Unix epoch. + * + * @return the resulting value. + */ + DateExpression millisecondsToDate(); + // TODO-END rename integer.utcMillisecondsToDate -- date.asUtcMilliseconds + R passIntegerTo(Function f); R switchIntegerOn(Function, ? extends BranchesTerminal> on); } 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 index 2f62b05b11a..a1d923c8be2 100644 --- a/driver-core/src/main/com/mongodb/client/model/expressions/MqlExpression.java +++ b/driver-core/src/main/com/mongodb/client/model/expressions/MqlExpression.java @@ -145,13 +145,13 @@ public BooleanExpression not() { } @Override - public BooleanExpression or(final BooleanExpression or) { - return new MqlExpression<>(ast("$or", or)); + public BooleanExpression or(final BooleanExpression other) { + return new MqlExpression<>(ast("$or", other)); } @Override - public BooleanExpression and(final BooleanExpression and) { - return new MqlExpression<>(ast("$and", and)); + public BooleanExpression and(final BooleanExpression other) { + return new MqlExpression<>(ast("$and", other)); } @Override @@ -392,33 +392,33 @@ private R0 switchMapInternal( } @Override - public BooleanExpression eq(final Expression eq) { - return new MqlExpression<>(ast("$eq", eq)); + public BooleanExpression eq(final Expression other) { + return new MqlExpression<>(ast("$eq", other)); } @Override - public BooleanExpression ne(final Expression ne) { - return new MqlExpression<>(ast("$ne", ne)); + public BooleanExpression ne(final Expression other) { + return new MqlExpression<>(ast("$ne", other)); } @Override - public BooleanExpression gt(final Expression gt) { - return new MqlExpression<>(ast("$gt", gt)); + public BooleanExpression gt(final Expression other) { + return new MqlExpression<>(ast("$gt", other)); } @Override - public BooleanExpression gte(final Expression gte) { - return new MqlExpression<>(ast("$gte", gte)); + public BooleanExpression gte(final Expression other) { + return new MqlExpression<>(ast("$gte", other)); } @Override - public BooleanExpression lt(final Expression lt) { - return new MqlExpression<>(ast("$lt", lt)); + public BooleanExpression lt(final Expression other) { + return new MqlExpression<>(ast("$lt", other)); } @Override - public BooleanExpression lte(final Expression lte) { - return new MqlExpression<>(ast("$lte", lte)); + public BooleanExpression lte(final Expression other) { + return new MqlExpression<>(ast("$lte", other)); } public BooleanExpression isBoolean() { @@ -551,11 +551,11 @@ public ArrayExpression map(final Function filter(final Function cond) { + public ArrayExpression filter(final Function predicate) { T varThis = variable("$$this"); return new MqlExpression<>((cr) -> astDoc("$filter", new BsonDocument() .append("input", this.toBsonValue(cr)) - .append("cond", extractBsonValue(cr, cond.apply(varThis))))); + .append("cond", extractBsonValue(cr, predicate.apply(varThis))))); } public ArrayExpression sort() { @@ -721,13 +721,13 @@ public NumberExpression divide(final NumberExpression n) { } @Override - public NumberExpression max(final NumberExpression n) { - return new MqlExpression<>(ast("$max", n)); + public NumberExpression max(final NumberExpression other) { + return new MqlExpression<>(ast("$max", other)); } @Override - public NumberExpression min(final NumberExpression n) { - return new MqlExpression<>(ast("$min", n)); + public NumberExpression min(final NumberExpression other) { + return new MqlExpression<>(ast("$min", other)); } @Override @@ -741,8 +741,8 @@ public NumberExpression round(final IntegerExpression place) { } @Override - public IntegerExpression multiply(final IntegerExpression i) { - return new MqlExpression<>(ast("$multiply", i)); + public IntegerExpression multiply(final IntegerExpression other) { + return new MqlExpression<>(ast("$multiply", other)); } @Override @@ -761,8 +761,8 @@ public NumberExpression subtract(final NumberExpression n) { } @Override - public IntegerExpression add(final IntegerExpression i) { - return new MqlExpression<>(ast("$add", i)); + public IntegerExpression add(final IntegerExpression other) { + return new MqlExpression<>(ast("$add", other)); } @Override 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 index 91d922dd476..52f882ae9f8 100644 --- a/driver-core/src/main/com/mongodb/client/model/expressions/NumberExpression.java +++ b/driver-core/src/main/com/mongodb/client/model/expressions/NumberExpression.java @@ -19,46 +19,128 @@ import java.util.function.Function; /** - * Expresses a numeric value. + * A number value. {@link IntegerExpression Integers} are a subset of numbers, + * and so, for example, the integer 0 and the number 0 are the same value, + * and are equal. */ public interface NumberExpression extends Expression { - NumberExpression multiply(NumberExpression n); - - default NumberExpression multiply(final Number multiply) { - return this.multiply(Expressions.numberToExpression(multiply)); + /** + * The product of multiplying {@code this} and the {@code other} value. + * + * @param other the other value. + * @return the resulting value. + */ + NumberExpression multiply(NumberExpression other); + + /** + * The product of multiplying {@code this} and the {@code other} value. + * + * @param other the other value. + * @return the resulting value. + */ + default NumberExpression multiply(final Number other) { + return this.multiply(Expressions.numberToExpression(other)); } - NumberExpression divide(NumberExpression n); - - default NumberExpression divide(final Number divide) { - return this.divide(Expressions.numberToExpression(divide)); + /** + * The result of dividing {@code this} value by the {@code other} value. + * This is not integer division: dividing {@code 1} by {@code 2} will yield + * {@code 0.5}. + * + * @param other the other value. + * @return the resulting value. + */ + NumberExpression divide(NumberExpression other); + + /** + * The result of dividing {@code this} value by the {@code other} value. + * This is not integer division: dividing {@code 1} by {@code 2} will yield + * {@code 0.5}. + * + * @param other the other value. + * @return the resulting value. + */ + default NumberExpression divide(final Number other) { + return this.divide(Expressions.numberToExpression(other)); } - NumberExpression add(NumberExpression n); - - default NumberExpression add(final Number add) { - return this.add(Expressions.numberToExpression(add)); + /** + * The sum of adding {@code this} and the {@code other} value. + * + * @param other the other value. + * @return the resulting value. + */ + NumberExpression add(NumberExpression other); + + /** + * The sum of adding {@code this} and the {@code other} value. + * + * @param other the other value. + * @return the resulting value. + */ + default NumberExpression add(final Number other) { + return this.add(Expressions.numberToExpression(other)); } - NumberExpression subtract(NumberExpression n); - - default NumberExpression subtract(final Number subtract) { - return this.subtract(Expressions.numberToExpression(subtract)); + /** + * The result of subtracting the {@code other} value from {@code this}. + * + * @param other the other value. + * @return the resulting value. + */ + NumberExpression subtract(NumberExpression other); + + /** + * The result of subtracting the {@code other} value from {@code this}. + * + * @param other the other value. + * @return the resulting value. + */ + default NumberExpression subtract(final Number other) { + return this.subtract(Expressions.numberToExpression(other)); } - NumberExpression max(NumberExpression n); - - NumberExpression min(NumberExpression n); - + /** + * The larger value of {@code this} and the {@code other} value. + * + * @param other the other value. + * @return the resulting value. + */ + NumberExpression max(NumberExpression other); + + /** + * The smaller value of {@code this} and the {@code other} value. + * + * @param other the other value. + * @return the resulting value. + */ + NumberExpression min(NumberExpression other); + + /** + * The integer result of rounding {@code this} to the nearest even value. + * + * @return the resulting value. + */ IntegerExpression round(); + /** + * The result of rounding {@code this} to the nearest even {@code place}. + * + * @param place the decimal place to round to, from -20 to 100, exclusive. + * Positive values specify the place to the right of the + * decimal point, while negative values, to the left. + * @return the resulting value. + */ NumberExpression round(IntegerExpression place); + /** + * The absolute value of {@code this} value. + * + * @return the resulting value. + */ NumberExpression abs(); - DateExpression millisecondsToDate(); - R passNumberTo(Function f); R switchNumberOn(Function, ? extends BranchesTerminal> on); } From 1d9964bd5eb3542b0439949c0c5efded9a1d2d5d Mon Sep 17 00:00:00 2001 From: Maxim Katcharov Date: Mon, 5 Dec 2022 14:19:51 -0700 Subject: [PATCH 15/27] Update and add documentation (#1059) * Fix, tests JAVA-4799 --- .../model/expressions/ArrayExpression.java | 56 +++-- .../model/expressions/BooleanExpression.java | 34 ++- .../model/expressions/DateExpression.java | 30 ++- .../model/expressions/DocumentExpression.java | 21 +- .../model/expressions/EntryExpression.java | 13 ++ .../client/model/expressions/Expression.java | 206 ++++++++++++++---- .../client/model/expressions/Expressions.java | 142 +++++++++--- .../model/expressions/IntegerExpression.java | 41 +++- .../model/expressions/MapExpression.java | 21 +- .../model/expressions/MqlExpression.java | 6 +- .../model/expressions/NumberExpression.java | 53 +++-- .../model/expressions/StringExpression.java | 21 +- .../model/expressions/package-info.java | 1 + .../ControlExpressionsFunctionalTest.java | 7 +- .../TypeExpressionsFunctionalTest.java | 2 +- 15 files changed, 516 insertions(+), 138 deletions(-) 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 index 7b499f7ada0..674efb3d5a7 100644 --- a/driver-core/src/main/com/mongodb/client/model/expressions/ArrayExpression.java +++ b/driver-core/src/main/com/mongodb/client/model/expressions/ArrayExpression.java @@ -21,35 +21,39 @@ import static com.mongodb.client.model.expressions.Expressions.of; /** - * Expresses an array value. An array value is a finite, ordered collection of - * elements of a certain type. + * An array {@link Expression value} in the context of the MongoDB Query + * Language (MQL). An array is a finite, ordered collection of elements of a + * certain type. It is also known as a finite mathematical sequence. * - * @param the type of the elements in the array + * @param the type of the elements */ public interface ArrayExpression extends Expression { /** - * Returns an array consisting of those elements in this array that match - * the given predicate condition. Evaluates each expression in this array - * according to the cond function. If cond evaluates to logical true, then - * the element is preserved; if cond evaluates to logical false, the element - * is omitted. + * An array consisting of only those elements in {@code this} array that + * match the provided predicate. * - * @param cond the function to apply to each element - * @return the new array + * @param predicate the predicate to apply to each element to determine if + * it should be included. + * @return the resulting array. */ - ArrayExpression filter(Function cond); + ArrayExpression filter(Function predicate); /** - * Returns an array consisting of the results of applying the given function - * to the elements of this array. + * An array consisting of the results of applying the provided function to + * the elements of {@code this} array. * - * @param in the function to apply to each element - * @return the new array - * @param the type contained in the resulting array + * @param in the function to apply to each element. + * @return the resulting array. + * @param the type of the elements of the resulting array. */ ArrayExpression map(Function in); + /** + * The size of {@code this} array. + * + * @return the size. + */ IntegerExpression size(); BooleanExpression any(Function predicate); @@ -113,7 +117,25 @@ default ArrayExpression slice(final int start, final int length) { ArrayExpression distinct(); + /** + * The result of passing {@code this} value to the provided function. + * Equivalent to {@code f.apply(this)}, and allows lambdas and static, + * user-defined functions to use the chaining syntax. + * + * @see Expression#passTo + * @param f the function to apply. + * @return the resulting value. + * @param the type of the resulting value. + */ R passArrayTo(Function, ? extends R> f); - R switchArrayOn(Function>, ? extends BranchesTerminal, ? extends R>> on); + /** + * The result of applying the provided switch mapping to {@code this} value. + * + * @see Expression#switchOn + * @param mapping the switch mapping. + * @return the resulting value. + * @param the type of the resulting value. + */ + R switchArrayOn(Function>, ? extends BranchesTerminal, ? extends R>> mapping); } 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 index 00da72a208e..489fecc9a8d 100644 --- a/driver-core/src/main/com/mongodb/client/model/expressions/BooleanExpression.java +++ b/driver-core/src/main/com/mongodb/client/model/expressions/BooleanExpression.java @@ -19,7 +19,8 @@ import java.util.function.Function; /** - * A logical boolean value, either true or false. + * A boolean {@linkplain Expression value} in the context of the + * MongoDB Query Language (MQL). */ public interface BooleanExpression extends Expression { @@ -45,20 +46,37 @@ public interface BooleanExpression extends Expression { * @return the resulting value. */ BooleanExpression and(BooleanExpression other); - // TODO-END check the evaluation semantics of and/or /** - * The {@code left} branch when {@code this} is true, - * and the {@code right} branch otherwise. + * The {@code ifTrue} value when {@code this} is true, + * and the {@code ifFalse} value otherwise. * - * @param left the left branch. - * @param right the right branch. + * @param ifTrue the ifTrue value. + * @param ifFalse the ifFalse value. * @return the resulting value. * @param The type of the resulting expression. */ - T cond(T left, T right); + T cond(T ifTrue, T ifFalse); + /** + * The result of passing {@code this} value to the provided function. + * Equivalent to {@code f.apply(this)}, and allows lambdas and static, + * user-defined functions to use the chaining syntax. + * + * @see Expression#passTo + * @param f the function to apply. + * @return the resulting value. + * @param the type of the resulting value. + */ R passBooleanTo(Function f); - R switchBooleanOn(Function, ? extends BranchesTerminal> on); + /** + * The result of applying the provided switch mapping to {@code this} value. + * + * @see Expression#switchOn + * @param mapping the switch mapping. + * @return the resulting value. + * @param the type of the resulting value. + */ + R switchBooleanOn(Function, ? extends BranchesTerminal> mapping); } 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 index be2b838058b..3d5c0d2b634 100644 --- a/driver-core/src/main/com/mongodb/client/model/expressions/DateExpression.java +++ b/driver-core/src/main/com/mongodb/client/model/expressions/DateExpression.java @@ -19,8 +19,11 @@ import java.util.function.Function; /** - * An instantaneous date and time. Tracks a UTC datetime, the number of - * milliseconds since the Unix epoch. Does not track the timezone. + * A UTC date-time {@linkplain Expression value} in the context + * of the MongoDB Query Language (MQL). Tracks the number of + * milliseconds since the Unix epoch, and does not track the timezone. + * + * @mongodb.driver.manual reference/operator/aggregation/dateToString/ Format Specifiers, UTC Offset, and Olson Timezone Identifier */ public interface DateExpression extends Expression { @@ -123,11 +126,30 @@ public interface DateExpression extends Expression { * provided {@code timezone}, and formatted according to the {@code format}. * * @param timezone the UTC Offset or Olson Timezone Identifier. - * @param format the format specifier. TODO-END what standard is this? + * @param format the format specifier. * @return the resulting value. */ StringExpression asString(StringExpression timezone, StringExpression format); + /** + * The result of passing {@code this} value to the provided function. + * Equivalent to {@code f.apply(this)}, and allows lambdas and static, + * user-defined functions to use the chaining syntax. + * + * @see Expression#passTo + * @param f the function to apply. + * @return the resulting value. + * @param the type of the resulting value. + */ R passDateTo(Function f); - R switchDateOn(Function, ? extends BranchesTerminal> on); + + /** + * The result of applying the provided switch mapping to {@code this} value. + * + * @see Expression#switchOn + * @param mapping the switch mapping. + * @return the resulting value. + * @param the type of the resulting value. + */ + R switchDateOn(Function, ? extends BranchesTerminal> mapping); } 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 index 4d7d2ef678f..343a8e7f68d 100644 --- a/driver-core/src/main/com/mongodb/client/model/expressions/DocumentExpression.java +++ b/driver-core/src/main/com/mongodb/client/model/expressions/DocumentExpression.java @@ -102,6 +102,25 @@ default MapExpression getMap(final String fieldName, f MapExpression asMap(); + /** + * The result of passing {@code this} value to the provided function. + * Equivalent to {@code f.apply(this)}, and allows lambdas and static, + * user-defined functions to use the chaining syntax. + * + * @see Expression#passTo + * @param f the function to apply. + * @return the resulting value. + * @param the type of the resulting value. + */ R passDocumentTo(Function f); - R switchDocumentOn(Function, ? extends BranchesTerminal> on); + + /** + * The result of applying the provided switch mapping to {@code this} value. + * + * @see Expression#switchOn + * @param mapping the switch mapping. + * @return the resulting value. + * @param the type of the resulting value. + */ + R switchDocumentOn(Function, ? extends BranchesTerminal> mapping); } diff --git a/driver-core/src/main/com/mongodb/client/model/expressions/EntryExpression.java b/driver-core/src/main/com/mongodb/client/model/expressions/EntryExpression.java index 6fda24588c6..c5908c28973 100644 --- a/driver-core/src/main/com/mongodb/client/model/expressions/EntryExpression.java +++ b/driver-core/src/main/com/mongodb/client/model/expressions/EntryExpression.java @@ -18,6 +18,19 @@ import static com.mongodb.client.model.expressions.Expressions.of; +/** + * A map entry {@linkplain Expression value} in the context + * of the MongoDB Query Language (MQL). An entry has a + * {@linkplain StringExpression string} key and some + * {@linkplain Expression value}. Entries are used with + * {@linkplain MapExpression maps}. + * + * Entries are {@linkplain Expression#isDocumentOr document-like} and + * {@linkplain Expression#isMapOr map-like}, unless the method returning the + * entry specifies otherwise. + * + * @param The type of the value + */ public interface EntryExpression extends Expression { StringExpression getKey(); 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 index fb94262796f..87eb853e361 100644 --- a/driver-core/src/main/com/mongodb/client/model/expressions/Expression.java +++ b/driver-core/src/main/com/mongodb/client/model/expressions/Expression.java @@ -21,20 +21,85 @@ import java.util.function.Function; /** - *

    Users should treat these interfaces as sealed, and must not implement any - * sub-interfaces. + * A value in the context of the MongoDB Query Language (MQL). * - * TODO-END: 'cause an error', 'execution context', wrong types/unsafe operations - * TODO-END: types and how missing/null are not part of any type. + *

    The API provided by this base type and its subtypes is the Java-native + * variant of MQL. It is used to query the MongoDB server, to perform remote + * computations, to store and retrieve data, or to otherwise work with data on + * a MongoDB server or compatible execution context. Though the methods exposed + * through this API generally correspond to MQL operations, this correspondence + * is not exact. + * + *

    The following is an example of usage within an aggregation pipeline. Here, + * the current document value is obtained and its "numberArray" field is + * filtered and summed, in a style similar to that of the Java Stream API: + * + *

    {@code
    + * import static com.mongodb.client.model.expressions.Expressions.current;
    + * MongoCollection col = ...;
    + * AggregateIterable result = col.aggregate(Arrays.asList(
    + *     addFields(new Field<>("result", current()
    + *         .getArray("numberArray")
    + *         .filter(v -> v.gt(of(0)))
    + *         .sum(v -> v)))));
    + * }
    + * + *

    Values are typically initially obtained via the current document and its + * fields, or specified via statically-imported methods on the + * {@link Expressions} class. + * + *

    As with the Java Stream API's terminal operations, corresponding Java + * values are not directly available, but must be obtained indirectly via + * {@code MongoCollection.aggregate} or {@code MongoCollection.find}. + * Certain methods may cause an error, which will be produced + * through these "terminal operations". + * + *

    The null value is not part of, and cannot be used as if it were part + * of, any explicit type (except the root type {@link Expression} itself). + * See {@link Expressions#ofNull} for more details. + * + *

    This API specifies no "missing" or "undefined" value. Users may use + * {@link MapExpression#has} to check whether a value is present. + * + *

    This type hierarchy differs from the {@linkplain org.bson} types in that + * they provide computational operations, the numeric types are less granular, + * and it offers multiple abstractions of certain types (document, map, entry). + * It differs from the corresponding Java types (such as {@code int}, + * {@link String}, {@link java.util.Map}) in that the operations + * available differ, and in that an implementation of this API may be used to + * produce MQL in the form of BSON. (This API makes no guarantee regarding the + * BSON output produced by its implementation, which in any case may vary due + * to optimization or other factors.) + * + *

    Some methods within the API constitute an assertion by the user that the + * data is of a certain type. For example, {@link DocumentExpression#getArray}} + * requires that the underlying field is both an array, and an array of some + * certain type. If the field is not an array in the underlying data, behaviour + * is undefined by this API (though behaviours may be defined by the execution + * context, users are strongly discouraged from relying on behaviour that is not + * part of this API). + * + * + *

    Users should treat these interfaces as sealed, and should not create + * implementations. * * @see Expressions */ @Evolving public interface Expression { + /** + * The method {@link Expression#eq} should be used to compare values for + * equality. This method checks reference equality. + */ + @Override + boolean equals(Object other); + /** * Whether {@code this} value is equal to the {@code other} value. * + *

    The result does not correlate with {@link Expression#equals(Object)}. + * * @param other the other value. * @return the resulting value. */ @@ -43,6 +108,8 @@ public interface Expression { /** * Whether {@code this} value is not equal to the {@code other} value. * + *

    The result does not correlate with {@link Expression#equals(Object)}. + * * @param other the other value. * @return the resulting value. */ @@ -83,7 +150,7 @@ public interface Expression { BooleanExpression lte(Expression other); /** - * {@code this} value as a {@link BooleanExpression boolean} if + * {@code this} value as a {@linkplain BooleanExpression boolean} if * {@code this} is a boolean, or the {@code other} boolean value if * {@code this} is null, or is missing, or is of any other non-boolean type. * @@ -93,35 +160,29 @@ public interface Expression { BooleanExpression isBooleanOr(BooleanExpression other); /** - * {@code this} value as a {@link NumberExpression number} if + * {@code this} value as a {@linkplain NumberExpression number} if * {@code this} is a number, or the {@code other} number value if * {@code this} is null, or is missing, or is of any other non-number type. * - *

    Since integers are a subset of numbers, if a value is an integer, - * this does not imply that it is not a number, and vice-versa. - * * @param other the other value. * @return the resulting value. */ NumberExpression isNumberOr(NumberExpression other); /** - * {@code this} value as an {@link IntegerExpression integer} if + * {@code this} value as an {@linkplain IntegerExpression integer} if * {@code this} is an integer, or the {@code other} integer value if * {@code this} is null, or is missing, or is of any other non-integer type. * - *

    Since integers are a subset of numbers, if a value is an integer, - * this does not imply that it is not a number, and vice-versa. - * * @param other the other value. * @return the resulting value. */ IntegerExpression isIntegerOr(IntegerExpression other); /** - * {@code this} value as a {@link BooleanExpression boolean} if - * {@code this} is a boolean, or the {@code other} boolean value if - * {@code this} is null, or is missing, or is of any other non-boolean type. + * {@code this} value as a {@linkplain StringExpression string} if + * {@code this} is a string, or the {@code other} string value if + * {@code this} is null, or is missing, or is of any other non-string type. * * @param other the other value. * @return the resulting value. @@ -129,28 +190,26 @@ public interface Expression { StringExpression isStringOr(StringExpression other); /** - * {@code this} value as a {@link StringExpression string} if - * {@code this} is a string, or the {@code other} string value if - * {@code this} is null, or is missing, or is of any other non-string type. + * {@code this} value as a {@linkplain DateExpression boolean} if + * {@code this} is a date, or the {@code other} date value if + * {@code this} is null, or is missing, or is of any other non-date type. * * @param other the other value. * @return the resulting value. */ DateExpression isDateOr(DateExpression other); - /** - * {@code this} value as a {@link ArrayExpression array} if + * {@code this} value as a {@linkplain ArrayExpression array} if * {@code this} is an array, or the {@code other} array value if * {@code this} is null, or is missing, or is of any other non-array type. * - *

    Warning: this operation does not guarantee type safety. While this - * operation is guaranteed to produce an array, the type of the elements of - * that array are not guaranteed by the API. The specification of a type by - * the user is an unchecked assertion that all elements are of that type, - * and that no element is null, is missing, or is of some other type. If the - * user cannot make such an assertion, some appropriate super-type should be - * chosen, and elements should be individually type-checked. + *

    Warning: The type of the elements of the resulting array are not + * enforced by the API. The specification of a type by the user is an + * unchecked assertion that all elements are of that type. + * If the array contains multiple types (such as both nulls and integers) + * then a super-type encompassing all types must be chosen, and + * if necessary the elements should be individually type-checked when used. * * @param other the other value. * @return the resulting value. @@ -158,46 +217,80 @@ public interface Expression { */ ArrayExpression isArrayOr(ArrayExpression other); - // TODO-END doc after Map merged, "record" and "schema objects" are decided + /** + * {@code this} value as a {@linkplain DocumentExpression document} if + * {@code this} is a document or document-like value (see + * {@link MapExpression} and {@link EntryExpression}) + * or the {@code other} document value if {@code this} is null, + * or is missing, or is of any other non-document type. + * + * @param other the other value. + * @return the resulting value. + */ T isDocumentOr(T other); + /** + * {@code this} value as a {@linkplain MapExpression map} if + * {@code this} is a map or map-like value (see + * {@link DocumentExpression} and {@link EntryExpression}) + * or the {@code other} map value if {@code this} is null, + * or is missing, or is of any other non-map type. + * + *

    Warning: The type of the values of the resulting map are not + * enforced by the API. The specification of a type by the user is an + * unchecked assertion that all map values are of that type. + * If the map contains multiple types (such as both nulls and integers) + * then a super-type encompassing all types must be chosen, and + * if necessary the elements should be individually type-checked when used. + * + * @param other the other value. + * @return the resulting value. + * @param the type of the values of the resulting map. + */ MapExpression isMapOr(MapExpression other); /** - * The {@link StringExpression string} value corresponding to this value. - * - *

    This may "cause an error" if the type cannot be converted to string, - * as is the case with {@link ArrayExpression arrays}, - * {@link DocumentExpression documents}, and {@link MapExpression maps}. - * TODO-END what about null/missing? - * TODO-END document vs record - * TODO-END "cause an error" above + * The {@linkplain StringExpression string} representation of {@code this} value. * + *

    This may cause an error to be produced if the type cannot be converted + * to a {@linkplain StringExpression string}, as is the case with + * {@linkplain ArrayExpression arrays}, + * {@linkplain DocumentExpression documents}, + * {@linkplain MapExpression maps}, + * {@linkplain EntryExpression entries}, and the + * {@linkplain Expressions#ofNull() null value}. * * @see StringExpression#parseDate() * @see StringExpression#parseInteger() - * TODO-END all the others? implement? * @return the resulting value. */ StringExpression asString(); /** - * Applies the provided function to {@code this}. + * The result of passing {@code this} value to the provided function. + * Equivalent to {@code f.apply(this)}, and allows lambdas and static, + * user-defined functions to use the chaining syntax. * - *

    Equivalent to {@code f.apply(this)}, and allows lambdas and static, - * user-defined functions to use the chaining syntax. For example: - * {@code myInteger.apply(isEven)} (here, {@code isEven} is a function - * taking an {@link IntegerExpression integer} and yielding a - * {@link BooleanExpression boolean}). + *

    The appropriate type-based variant should be used when the type + * of {@code this} is known. + * + * @see BooleanExpression#passBooleanTo + * @see IntegerExpression#passIntegerTo + * @see NumberExpression#passNumberTo + * @see StringExpression#passStringTo + * @see DateExpression#passDateTo + * @see ArrayExpression#passArrayTo + * @see MapExpression#passMapTo + * @see DocumentExpression#passDocumentTo * * @param f the function to apply. * @return the resulting value. * @param the type of the resulting value. */ R passTo(Function f); + /** - * The value resulting from applying the provided switch mapping to - * {@code this} value. + * The result of applying the provided switch mapping to {@code this} value. * *

    Can be used to perform pattern matching on the type of {@code this} * value, or to perform comparisons, or to perform any arbitrary check on @@ -205,12 +298,29 @@ public interface Expression { * *

    The suggested convention is to use "{@code on}" as the name of the * {@code mapping} parameter, for example: - * {@code myValue.switchOn(on -> on.isInteger(...)...)}. + * + *

    {@code
    +     * myValue.switchOn(on -> on
    +     *     .isInteger(...)
    +     *     ...
    +     *     .defaults(...))
    +     * }
    + * + *

    The appropriate type-based variant should be used when the type + * of {@code this} is known. + * + * @see BooleanExpression#switchBooleanOn + * @see IntegerExpression#switchIntegerOn + * @see NumberExpression#switchNumberOn + * @see StringExpression#switchStringOn + * @see DateExpression#switchDateOn + * @see ArrayExpression#switchArrayOn + * @see MapExpression#switchMapOn + * @see DocumentExpression#switchDocumentOn * * @param mapping the switch mapping. * @return the resulting value. * @param the type of the resulting value. */ R switchOn(Function, ? extends BranchesTerminal> mapping); - } 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 index 132d0fa03c1..489d5baf904 100644 --- a/driver-core/src/main/com/mongodb/client/model/expressions/Expressions.java +++ b/driver-core/src/main/com/mongodb/client/model/expressions/Expressions.java @@ -39,31 +39,25 @@ import static com.mongodb.client.model.expressions.MqlExpression.extractBsonValue; /** - * Convenience methods related to {@link Expression}. + * Convenience methods related to {@link Expression}, used primarily to + * produce values in the context of the MongoDB Query Language (MQL). */ public final class Expressions { private Expressions() {} /** - * Returns an expression having the same boolean value as the provided - * boolean primitive. + * Returns a {@linkplain BooleanExpression boolean} value corresponding to + * the provided {@code boolean} primitive. * - * @param of the boolean primitive - * @return the boolean expression + * @param of the {@code boolean} primitive. + * @return the resulting value. */ public static BooleanExpression of(final boolean of) { // we intentionally disallow ofBoolean(null) return new MqlExpression<>((codecRegistry) -> new AstPlaceholder(new BsonBoolean(of))); } - /** - * Returns an array expression containing the same boolean values as the - * provided array of booleans. - * - * @param array the array of booleans - * @return the boolean array expression - */ public static ArrayExpression ofBooleanArray(final boolean... array) { Assertions.notNull("array", array); List list = new ArrayList<>(); @@ -74,11 +68,11 @@ public static ArrayExpression ofBooleanArray(final boolean... } /** - * Returns an expression having the same integer value as the provided - * int primitive. + * Returns an {@linkplain IntegerExpression integer} value corresponding to + * the provided {@code int} primitive. * - * @param of the int primitive - * @return the integer expression + * @param of the {@code int} primitive. + * @return the resulting value. */ public static IntegerExpression of(final int of) { return new MqlExpression<>((codecRegistry) -> new AstPlaceholder(new BsonInt32(of))); @@ -93,6 +87,13 @@ public static ArrayExpression ofIntegerArray(final int... arr return new MqlExpression<>((cr) -> new AstPlaceholder(new BsonArray(list))); } + /** + * Returns an {@linkplain IntegerExpression integer} value corresponding to + * the provided {@code long} primitive. + * + * @param of the {@code long} primitive. + * @return the resulting value. + */ public static IntegerExpression of(final long of) { return new MqlExpression<>((codecRegistry) -> new AstPlaceholder(new BsonInt64(of))); } @@ -106,6 +107,13 @@ public static ArrayExpression ofIntegerArray(final long... ar return new MqlExpression<>((cr) -> new AstPlaceholder(new BsonArray(list))); } + /** + * Returns a {@linkplain NumberExpression number} value corresponding to + * the provided {@code double} primitive. + * + * @param of the {@code double} primitive. + * @return the resulting value. + */ public static NumberExpression of(final double of) { return new MqlExpression<>((codecRegistry) -> new AstPlaceholder(new BsonDouble(of))); } @@ -119,6 +127,13 @@ public static ArrayExpression ofNumberArray(final double... ar return new MqlExpression<>((cr) -> new AstPlaceholder(new BsonArray(list))); } + /** + * Returns a {@linkplain NumberExpression number} value corresponding to + * the provided {@link Decimal128} + * + * @param of the {@link Decimal128}. + * @return the resulting value. + */ public static NumberExpression of(final Decimal128 of) { Assertions.notNull("Decimal128", of); return new MqlExpression<>((codecRegistry) -> new AstPlaceholder(new BsonDecimal128(of))); @@ -134,6 +149,14 @@ public static ArrayExpression ofNumberArray(final Decimal128.. return new MqlExpression<>((cr) -> new AstPlaceholder(new BsonArray(result))); } + + /** + * Returns a {@linkplain DateExpression date and time} value corresponding to + * the provided {@link Instant}. + * + * @param of the {@link Instant}. + * @return the resulting value. + */ public static DateExpression of(final Instant of) { Assertions.notNull("Instant", of); return new MqlExpression<>((codecRegistry) -> new AstPlaceholder(new BsonDateTime(of.toEpochMilli()))); @@ -150,18 +173,17 @@ public static ArrayExpression ofDateArray(final Instant... array } /** - * Returns an expression having the same string value as the provided - * string. + * Returns an {@linkplain StringExpression string} value corresponding to + * the provided {@link String}. * - * @param of the string - * @return the string expression + * @param of the {@link String}. + * @return the resulting value. */ public static StringExpression of(final String of) { Assertions.notNull("String", of); return new MqlExpression<>((codecRegistry) -> new AstPlaceholder(new BsonString(of))); } - public static ArrayExpression ofStringArray(final String... array) { Assertions.notNull("array", array); List result = new ArrayList<>(); @@ -172,16 +194,48 @@ public static ArrayExpression ofStringArray(final String... ar return new MqlExpression<>((cr) -> new AstPlaceholder(new BsonArray(result))); } + /** + * Returns a reference to the "current" + * {@linkplain DocumentExpression document} value. + * The "current" value is the top-level document currently being processed + * in the aggregation pipeline stage. + * + * @return a reference to the current value + */ public static DocumentExpression current() { return new MqlExpression<>((cr) -> new AstPlaceholder(new BsonString("$$CURRENT"))) .assertImplementsAllExpressions(); } + /** + * Returns a reference to the "current" + * value as a {@linkplain MapExpression map} value. + * The "current" value is the top-level document currently being processed + * in the aggregation pipeline stage. + * + *

    Warning: The type of the values of the resulting map are not + * enforced by the API. The specification of a type by the user is an + * unchecked assertion that all map values are of that type. + * If the map contains multiple types (such as both nulls and integers) + * then a super-type encompassing all types must be chosen, and + * if necessary the elements should be individually type-checked when used. + * + * @return a reference to the current value as a map. + * @param the type of the map's values. + */ public static MapExpression currentAsMap() { return new MqlExpression<>((cr) -> new AstPlaceholder(new BsonString("$$CURRENT"))) .assertImplementsAllExpressions(); } + /** + * Returns an {@linkplain DocumentExpression array} value, containing the + * {@linkplain Expression values} provided. + * + * @param array the {@linkplain Expression values}. + * @return the resulting value. + * @param the type of the array elements. + */ @SafeVarargs // nothing is stored in the array public static ArrayExpression ofArray(final T... array) { Assertions.notNull("array", array); @@ -195,6 +249,14 @@ public static ArrayExpression ofArray(final T... array }); } + /** + * Returns an {@linkplain EntryExpression entry} value. + * + * @param k the key. + * @param v the value. + * @return the resulting value. + * @param the type of the key. + */ public static EntryExpression ofEntry(final StringExpression k, final T v) { Assertions.notNull("k", k); Assertions.notNull("v", v); @@ -211,11 +273,19 @@ public static MapExpression ofMap() { } /** - * user asserts type of values is T + * Returns a {@linkplain MapExpression map} value corresponding to the + * provided {@link Bson Bson document}. * - * @param map - * @return - * @param + *

    Warning: The type of the values of the resulting map are not + * enforced by the API. The specification of a type by the user is an + * unchecked assertion that all map values are of that type. + * If the map contains multiple types (such as both nulls and integers) + * then a super-type encompassing all types must be chosen, and + * if necessary the elements should be individually type-checked when used. + * + * @param map the map as a {@link Bson Bson document}. + * @return the resulting map value. + * @param the type of the resulting map's values. */ public static MapExpression ofMap(final Bson map) { Assertions.notNull("map", map); @@ -223,6 +293,13 @@ public static MapExpression ofMap(final Bson map) { map.toBsonDocument(BsonDocument.class, cr)))); } + /** + * Returns a {@linkplain DocumentExpression document} value corresponding to the + * provided {@link Bson Bson document}. + * + * @param document the {@linkplain Bson BSON document}. + * @return the resulting value. + */ 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 @@ -232,6 +309,21 @@ public static DocumentExpression of(final Bson document) { document.toBsonDocument(BsonDocument.class, cr)))); } + /** + * The null value in the context of the MongoDB Query Language (MQL). + * + *

    The null value is not part of, and cannot be used as if it were part + * of, any explicit type (except the root type {@link Expression} itself). + * It has no explicit type of its own. + * + *

    Instead of checking that a value is null, users should generally + * check that a value is of their expected type, via methods such as + * {@link Expression#isNumberOr(NumberExpression)}. Where the null value + * must be checked explicitly, users may use {@link Branches#isNull} within + * {@link Expression#switchOn}. + * + * @return the null value + */ public static Expression ofNull() { // There is no specific expression type corresponding to Null, // and Null is not a value in any other expression type. 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 index 9e7bac51bae..4757e64eab1 100644 --- a/driver-core/src/main/com/mongodb/client/model/expressions/IntegerExpression.java +++ b/driver-core/src/main/com/mongodb/client/model/expressions/IntegerExpression.java @@ -19,9 +19,10 @@ import java.util.function.Function; /** - * An integer value. Integers are a subset of {@link NumberExpression numbers}, - * and so, for example, the integer 0 and the number 0 are the same value, - * and are equal. + * An integer {@linkplain Expression value} in the context of the MongoDB Query + * Language (MQL). Integers are a subset of {@linkplain NumberExpression numbers}, + * and so, for example, the integer 0 and the number 0 are + * {@linkplain #eq(Expression) equal}. */ public interface IntegerExpression extends NumberExpression { @@ -62,7 +63,7 @@ default IntegerExpression add(final int other) { } /** - * The result of subtracting the {@code other} value from {@code this}. + * The difference of subtracting the {@code other} value from {@code this}. * * @param other the other value. * @return the resulting value. @@ -70,7 +71,7 @@ default IntegerExpression add(final int other) { IntegerExpression subtract(IntegerExpression other); /** - * The result of subtracting the {@code other} value from {@code this}. + * The difference of subtracting the {@code other} value from {@code this}. * * @param other the other value. * @return the resulting value. @@ -80,7 +81,8 @@ default IntegerExpression subtract(final int other) { } /** - * The larger value of {@code this} and the {@code other} value. + * The {@linkplain #gt(Expression) larger} value of {@code this} + * and the {@code other} value. * * @param other the other value. * @return the resulting value. @@ -88,7 +90,8 @@ default IntegerExpression subtract(final int other) { IntegerExpression max(IntegerExpression other); /** - * The smaller value of {@code this} and the {@code other} value. + * The {@linkplain #lt(Expression) smaller} value of {@code this} + * and the {@code other} value. * * @param other the other value. * @return the resulting value. @@ -103,14 +106,32 @@ default IntegerExpression subtract(final int other) { IntegerExpression abs(); /** - * The {@link DateExpression date} corresponding to {@code this} value + * The {@linkplain DateExpression date} corresponding to {@code this} value * when taken to be the number of milliseconds since the Unix epoch. * * @return the resulting value. */ DateExpression millisecondsToDate(); - // TODO-END rename integer.utcMillisecondsToDate -- date.asUtcMilliseconds + /** + * The result of passing {@code this} value to the provided function. + * Equivalent to {@code f.apply(this)}, and allows lambdas and static, + * user-defined functions to use the chaining syntax. + * + * @see Expression#passTo + * @param f the function to apply. + * @return the resulting value. + * @param the type of the resulting value. + */ R passIntegerTo(Function f); - R switchIntegerOn(Function, ? extends BranchesTerminal> on); + + /** + * The result of applying the provided switch mapping to {@code this} value. + * + * @see Expression#switchOn + * @param mapping the switch mapping. + * @return the resulting value. + * @param the type of the resulting value. + */ + R switchIntegerOn(Function, ? extends BranchesTerminal> mapping); } diff --git a/driver-core/src/main/com/mongodb/client/model/expressions/MapExpression.java b/driver-core/src/main/com/mongodb/client/model/expressions/MapExpression.java index d5d782c7728..d33c1bcf80d 100644 --- a/driver-core/src/main/com/mongodb/client/model/expressions/MapExpression.java +++ b/driver-core/src/main/com/mongodb/client/model/expressions/MapExpression.java @@ -60,6 +60,25 @@ default MapExpression unset(final String key) { R asDocument(); + /** + * The result of passing {@code this} value to the provided function. + * Equivalent to {@code f.apply(this)}, and allows lambdas and static, + * user-defined functions to use the chaining syntax. + * + * @see Expression#passTo + * @param f the function to apply. + * @return the resulting value. + * @param the type of the resulting value. + */ R passMapTo(Function, ? extends R> f); - R switchMapOn(Function>, ? extends BranchesTerminal, ? extends R>> on); + + /** + * The result of applying the provided switch mapping to {@code this} value. + * + * @see Expression#switchOn + * @param mapping the switch mapping. + * @return the resulting value. + * @param the type of the resulting value. + */ + R switchMapOn(Function>, ? extends BranchesTerminal, ? extends R>> mapping); } 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 index a1d923c8be2..ccf53feba55 100644 --- a/driver-core/src/main/com/mongodb/client/model/expressions/MqlExpression.java +++ b/driver-core/src/main/com/mongodb/client/model/expressions/MqlExpression.java @@ -155,8 +155,8 @@ public BooleanExpression and(final BooleanExpression other) { } @Override - public R cond(final R left, final R right) { - return newMqlExpression(ast("$cond", left, right)); + public R cond(final R ifTrue, final R ifFalse) { + return newMqlExpression(ast("$cond", ifTrue, ifFalse)); } /** @see DocumentExpression */ @@ -504,7 +504,7 @@ public ArrayExpression isArrayOr(final ArrayExpression } BooleanExpression isDocumentOrMap() { - return new MqlExpression<>(ast("$type")).eq(of("object")); + return new MqlExpression<>(astWrapped("$type")).eq(of("object")); } @Override 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 index 52f882ae9f8..1756ed02318 100644 --- a/driver-core/src/main/com/mongodb/client/model/expressions/NumberExpression.java +++ b/driver-core/src/main/com/mongodb/client/model/expressions/NumberExpression.java @@ -19,9 +19,10 @@ import java.util.function.Function; /** - * A number value. {@link IntegerExpression Integers} are a subset of numbers, - * and so, for example, the integer 0 and the number 0 are the same value, - * and are equal. + * A number {@linkplain Expression value} in the context of the MongoDB Query + * Language (MQL). {@linkplain IntegerExpression Integers} are a subset of + * numbers, and so, for example, the integer 0 and the number 0 are + * {@linkplain #eq(Expression) equal}. */ public interface NumberExpression extends Expression { @@ -44,9 +45,9 @@ default NumberExpression multiply(final Number other) { } /** - * The result of dividing {@code this} value by the {@code other} value. - * This is not integer division: dividing {@code 1} by {@code 2} will yield - * {@code 0.5}. + * The quotient of dividing {@code this} value by the {@code other} value. + * This is not integer division: dividing {@code 1} by {@code 2} will + * always yield {@code 0.5}. * * @param other the other value. * @return the resulting value. @@ -54,9 +55,9 @@ default NumberExpression multiply(final Number other) { NumberExpression divide(NumberExpression other); /** - * The result of dividing {@code this} value by the {@code other} value. - * This is not integer division: dividing {@code 1} by {@code 2} will yield - * {@code 0.5}. + * The quotient of dividing {@code this} value by the {@code other} value. + * This is not integer division: dividing {@code 1} by {@code 2} will + * always yield {@code 0.5}. * * @param other the other value. * @return the resulting value. @@ -84,7 +85,7 @@ default NumberExpression add(final Number other) { } /** - * The result of subtracting the {@code other} value from {@code this}. + * The difference of subtracting the {@code other} value from {@code this}. * * @param other the other value. * @return the resulting value. @@ -92,7 +93,7 @@ default NumberExpression add(final Number other) { NumberExpression subtract(NumberExpression other); /** - * The result of subtracting the {@code other} value from {@code this}. + * The difference of subtracting the {@code other} value from {@code this}. * * @param other the other value. * @return the resulting value. @@ -102,7 +103,8 @@ default NumberExpression subtract(final Number other) { } /** - * The larger value of {@code this} and the {@code other} value. + * The {@linkplain #gt(Expression) larger} value of {@code this} + * and the {@code other} value. * * @param other the other value. * @return the resulting value. @@ -110,7 +112,8 @@ default NumberExpression subtract(final Number other) { NumberExpression max(NumberExpression other); /** - * The smaller value of {@code this} and the {@code other} value. + * The {@linkplain #lt(Expression) smaller} value of {@code this} + * and the {@code other} value. * * @param other the other value. * @return the resulting value. @@ -125,7 +128,8 @@ default NumberExpression subtract(final Number other) { IntegerExpression round(); /** - * The result of rounding {@code this} to the nearest even {@code place}. + * The result of rounding {@code this} to {@code place} decimal places + * using the "half to even" approach. * * @param place the decimal place to round to, from -20 to 100, exclusive. * Positive values specify the place to the right of the @@ -141,6 +145,25 @@ default NumberExpression subtract(final Number other) { */ NumberExpression abs(); + /** + * The result of passing {@code this} value to the provided function. + * Equivalent to {@code f.apply(this)}, and allows lambdas and static, + * user-defined functions to use the chaining syntax. + * + * @see Expression#passTo + * @param f the function to apply. + * @return the resulting value. + * @param the type of the resulting value. + */ R passNumberTo(Function f); - R switchNumberOn(Function, ? extends BranchesTerminal> on); + + /** + * The result of applying the provided switch mapping to {@code this} value. + * + * @see Expression#switchOn + * @param mapping the switch mapping. + * @return the resulting value. + * @param the type of the resulting value. + */ + R switchNumberOn(Function, ? extends BranchesTerminal> mapping); } 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 index f4990b54949..633a99baba0 100644 --- a/driver-core/src/main/com/mongodb/client/model/expressions/StringExpression.java +++ b/driver-core/src/main/com/mongodb/client/model/expressions/StringExpression.java @@ -55,6 +55,25 @@ default StringExpression substrBytes(final int start, final int length) { DateExpression parseDate(StringExpression timezone, StringExpression format); + /** + * The result of passing {@code this} value to the provided function. + * Equivalent to {@code f.apply(this)}, and allows lambdas and static, + * user-defined functions to use the chaining syntax. + * + * @see Expression#passTo + * @param f the function to apply. + * @return the resulting value. + * @param the type of the resulting value. + */ R passStringTo(Function f); - R switchStringOn(Function, ? extends BranchesTerminal> on); + + /** + * The result of applying the provided switch mapping to {@code this} value. + * + * @see Expression#switchOn + * @param mapping the switch mapping. + * @return the resulting value. + * @param the type of the resulting value. + */ + R switchStringOn(Function, ? extends BranchesTerminal> mapping); } 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 index 596d642e654..f9b52b24897 100644 --- 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 @@ -18,6 +18,7 @@ * API for MQL expressions. * * @see com.mongodb.client.model.expressions.Expression + * @see com.mongodb.client.model.expressions.Expressions */ @NonNullApi package com.mongodb.client.model.expressions; diff --git a/driver-core/src/test/functional/com/mongodb/client/model/expressions/ControlExpressionsFunctionalTest.java b/driver-core/src/test/functional/com/mongodb/client/model/expressions/ControlExpressionsFunctionalTest.java index 77a539f418d..256bb8d4cee 100644 --- a/driver-core/src/test/functional/com/mongodb/client/model/expressions/ControlExpressionsFunctionalTest.java +++ b/driver-core/src/test/functional/com/mongodb/client/model/expressions/ControlExpressionsFunctionalTest.java @@ -205,7 +205,7 @@ public void switchTestInitial() { assertExpression("A", ofMap(Document.parse("{}")).switchOn(on -> on.isMap(v -> of("A"))), "{'$switch': {'branches': [{'case': {'$eq': [{'$type': " - + "{'$literal': {}}}, 'object']}, 'then': 'A'}]}}"); + + "[{'$literal': {}}]}, 'object']}, 'then': 'A'}]}}"); assertExpression("A", ofNull().switchOn(on -> on.isNull(v -> of("A"))), "{'$switch': {'branches': [{'case': {'$eq': [null, null]}, 'then': 'A'}]}}"); @@ -268,9 +268,8 @@ public void switchTestPartial() { + "{'case': {'$eq': [{'$type': [{'$literal': {}}]}, 'object']}, 'then': 'A'}]}}"); assertExpression("A", ofMap(Document.parse("{}")).switchOn(on -> on.isNull(v -> of("X")).isMap(v -> of("A"))), - " {'$switch': {'branches': [" - + "{'case': {'$eq': [{'$literal': {}}, null]}, 'then': 'X'}, " - + "{'case': {'$eq': [{'$type': {'$literal': {}}}, 'object']}, 'then': 'A'}]}}"); + "{'$switch': {'branches': [{'case': {'$eq': [{'$literal': {}}, null]}, 'then': 'X'}, " + + "{'case': {'$eq': [{'$type': [{'$literal': {}}]}, 'object']}, 'then': 'A'}]}}"); assertExpression("A", ofNull().switchOn(on -> on.isNumber(v -> of("X")).isNull(v -> of("A"))), "{'$switch': {'branches': [{'case': {'$isNumber': [null]}, 'then': 'X'}, " diff --git a/driver-core/src/test/functional/com/mongodb/client/model/expressions/TypeExpressionsFunctionalTest.java b/driver-core/src/test/functional/com/mongodb/client/model/expressions/TypeExpressionsFunctionalTest.java index db600647ab5..3dd6ee830d8 100644 --- a/driver-core/src/test/functional/com/mongodb/client/model/expressions/TypeExpressionsFunctionalTest.java +++ b/driver-core/src/test/functional/com/mongodb/client/model/expressions/TypeExpressionsFunctionalTest.java @@ -152,7 +152,7 @@ public void isMapOrTest() { assertExpression( map, ofMap(map).isMapOr(ofMap(BsonDocument.parse("{b: 2}"))), - "{'$cond': [{'$eq': [{'$type': {'$literal': {'a': 1}}}, 'object']}, " + "{'$cond': [{'$eq': [{'$type': [{'$literal': {'a': 1}}]}, 'object']}, " + "{'$literal': {'a': 1}}, {'$literal': {'b': 2}}]}"); // non-map: assertExpression(map, ofIntegerArray(1).isMapOr(ofMap(map))); From 347ab45dfbc6aff862fbdcf2e1549a47e776ad3c Mon Sep 17 00:00:00 2001 From: Valentin Kovalenko Date: Tue, 17 Jan 2023 19:03:36 -0700 Subject: [PATCH 16/27] Add `@MqlUnchecked` and a few usage examples (#1059) JAVA-4799 --- .../model/expressions/ArrayExpression.java | 4 + .../client/model/expressions/Branches.java | 4 +- .../model/expressions/DocumentExpression.java | 4 + .../client/model/expressions/Expression.java | 4 +- .../model/expressions/MapExpression.java | 2 + .../model/expressions/MqlUnchecked.java | 75 +++++++++++++++++++ 6 files changed, 91 insertions(+), 2 deletions(-) create mode 100644 driver-core/src/main/com/mongodb/client/model/expressions/MqlUnchecked.java 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 index 674efb3d5a7..2291b1e69fe 100644 --- a/driver-core/src/main/com/mongodb/client/model/expressions/ArrayExpression.java +++ b/driver-core/src/main/com/mongodb/client/model/expressions/ArrayExpression.java @@ -19,6 +19,8 @@ import java.util.function.Function; import static com.mongodb.client.model.expressions.Expressions.of; +import static com.mongodb.client.model.expressions.MqlUnchecked.Unchecked.PRESENT; +import static com.mongodb.client.model.expressions.MqlUnchecked.Unchecked.NON_EMPTY; /** * An array {@link Expression value} in the context of the MongoDB Query @@ -85,6 +87,7 @@ public interface ArrayExpression extends Expression { * @param i * @return */ + @MqlUnchecked(PRESENT) T elementAt(IntegerExpression i); default T elementAt(final int i) { @@ -95,6 +98,7 @@ default T elementAt(final int i) { * user asserts that array is not empty * @return */ + @MqlUnchecked(NON_EMPTY) T first(); /** diff --git a/driver-core/src/main/com/mongodb/client/model/expressions/Branches.java b/driver-core/src/main/com/mongodb/client/model/expressions/Branches.java index 40a726b4c9c..fbda31c2e9e 100644 --- a/driver-core/src/main/com/mongodb/client/model/expressions/Branches.java +++ b/driver-core/src/main/com/mongodb/client/model/expressions/Branches.java @@ -20,6 +20,8 @@ import java.util.List; import java.util.function.Function; +import static com.mongodb.client.model.expressions.MqlUnchecked.Unchecked.TYPE_ARGUMENT; + public final class Branches { Branches() { @@ -78,7 +80,7 @@ public BranchesIntermediary isDate(final Function BranchesIntermediary isArray(final Function, ? extends R> r) { + public BranchesIntermediary isArray(final Function, ? extends R> r) { return is(v -> mqlEx(v).isArray(), v -> r.apply((ArrayExpression) v)); } 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 index 343a8e7f68d..42105b2f213 100644 --- a/driver-core/src/main/com/mongodb/client/model/expressions/DocumentExpression.java +++ b/driver-core/src/main/com/mongodb/client/model/expressions/DocumentExpression.java @@ -23,6 +23,8 @@ import static com.mongodb.client.model.expressions.Expressions.of; import static com.mongodb.client.model.expressions.Expressions.ofMap; +import static com.mongodb.client.model.expressions.MqlUnchecked.Unchecked.PRESENT; +import static com.mongodb.client.model.expressions.MqlUnchecked.Unchecked.TYPE; /** * Expresses a document value. A document is an ordered set of fields, where the @@ -34,8 +36,10 @@ public interface DocumentExpression extends Expression { DocumentExpression unsetField(String fieldName); + @MqlUnchecked(PRESENT) Expression getField(String fieldName); + @MqlUnchecked({PRESENT, TYPE}) BooleanExpression getBoolean(String fieldName); BooleanExpression getBoolean(String fieldName, BooleanExpression other); 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 index 87eb853e361..e17060bb772 100644 --- a/driver-core/src/main/com/mongodb/client/model/expressions/Expression.java +++ b/driver-core/src/main/com/mongodb/client/model/expressions/Expression.java @@ -20,6 +20,8 @@ import java.util.function.Function; +import static com.mongodb.client.model.expressions.MqlUnchecked.Unchecked.TYPE_ARGUMENT; + /** * A value in the context of the MongoDB Query Language (MQL). * @@ -215,7 +217,7 @@ public interface Expression { * @return the resulting value. * @param the type of the elements of the resulting array. */ - ArrayExpression isArrayOr(ArrayExpression other); + ArrayExpression<@MqlUnchecked(TYPE_ARGUMENT) T> isArrayOr(ArrayExpression other); /** * {@code this} value as a {@linkplain DocumentExpression document} if diff --git a/driver-core/src/main/com/mongodb/client/model/expressions/MapExpression.java b/driver-core/src/main/com/mongodb/client/model/expressions/MapExpression.java index d33c1bcf80d..db0d387bb02 100644 --- a/driver-core/src/main/com/mongodb/client/model/expressions/MapExpression.java +++ b/driver-core/src/main/com/mongodb/client/model/expressions/MapExpression.java @@ -19,6 +19,7 @@ import java.util.function.Function; import static com.mongodb.client.model.expressions.Expressions.of; +import static com.mongodb.client.model.expressions.MqlUnchecked.Unchecked.PRESENT; public interface MapExpression extends Expression { @@ -29,6 +30,7 @@ default BooleanExpression has(String key) { } // TODO-END doc "user asserts" + @MqlUnchecked(PRESENT) T get(StringExpression key); // TODO-END doc "user asserts" diff --git a/driver-core/src/main/com/mongodb/client/model/expressions/MqlUnchecked.java b/driver-core/src/main/com/mongodb/client/model/expressions/MqlUnchecked.java new file mode 100644 index 00000000000..561a261e976 --- /dev/null +++ b/driver-core/src/main/com/mongodb/client/model/expressions/MqlUnchecked.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 java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Documents places where the API relies on a user asserting + * something that is not checked at run-time. + * If the assertion turns out to be false, the API behavior is unspecified. + * + *

    This class is not part of the public API and may be removed or changed at any time

    + */ +@Documented +@Retention(RetentionPolicy.SOURCE) +@Target({ElementType.METHOD, ElementType.TYPE_USE}) +public @interface MqlUnchecked { + /** + * @return A hint on the user assertion the API relies on. + */ + Unchecked[] value(); + + /** + * @see MqlUnchecked#value() + */ + enum Unchecked { + /** + * The API relies on the values it encounters being of the type + * implied/specified by or inferred from the user code. + * For example, {@link com.mongodb.client.model.expressions.DocumentExpression#getBoolean(String)} + * relies on the values of the document field being of the + * {@linkplain com.mongodb.client.model.expressions.BooleanExpression boolean} type. + */ + TYPE, + /** + * The API checks the raw type, but relies on the type argument + * implied/specified by or inferred from the user code being correct. + * For example, {@link com.mongodb.client.model.expressions.Expression#isArrayOr(ArrayExpression)} + * checks that the value is of the + * {@linkplain com.mongodb.client.model.expressions.ArrayExpression array} raw type, + * but relies on the elements of the array being of the type derived from the user code. + * + *

    One may think of it as a more specific version of {@link #TYPE}.

    + */ + TYPE_ARGUMENT, + /** + * The API relies on the array being non-empty. + * For example, {@link com.mongodb.client.model.expressions.ArrayExpression#first()}. + */ + NON_EMPTY, + /** + * The API relies on the element identified by index, name, etc., being present in the + * {@linkplain com.mongodb.client.model.expressions.DocumentExpression document} involved. + * For example, {@link com.mongodb.client.model.expressions.DocumentExpression#getField(String)}. + */ + PRESENT, + } +} From 7ce9cb5f8d39aa983a24b6e46faa16f14fc3d271 Mon Sep 17 00:00:00 2001 From: Maxim Katcharov Date: Fri, 20 Jan 2023 12:18:17 -0700 Subject: [PATCH 17/27] Add has to document, add tests (#1070) JAVA-4799 --- .../model/expressions/DocumentExpression.java | 9 +++++++++ .../model/expressions/MqlExpression.java | 8 +++++++- .../ArrayExpressionsFunctionalTest.java | 13 +++++++++++++ .../DocumentExpressionsFunctionalTest.java | 13 +++++++++++++ .../MapExpressionsFunctionalTest.java | 10 +++++----- .../StringExpressionsFunctionalTest.java | 4 ++++ .../TypeExpressionsFunctionalTest.java | 18 ++++++++++++++++++ 7 files changed, 69 insertions(+), 6 deletions(-) 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 index 42105b2f213..9222defafaa 100644 --- a/driver-core/src/main/com/mongodb/client/model/expressions/DocumentExpression.java +++ b/driver-core/src/main/com/mongodb/client/model/expressions/DocumentExpression.java @@ -33,6 +33,15 @@ public interface DocumentExpression extends Expression { DocumentExpression setField(String fieldName, Expression exp); + /** + * True if {@code this} document has a field with the provided + * {@code fieldName}. + * + * @param fieldName the name of the field. + * @return the resulting value. + */ + BooleanExpression has(String fieldName); + DocumentExpression unsetField(String fieldName); 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 index ccf53feba55..298ce7c973d 100644 --- a/driver-core/src/main/com/mongodb/client/model/expressions/MqlExpression.java +++ b/driver-core/src/main/com/mongodb/client/model/expressions/MqlExpression.java @@ -597,7 +597,7 @@ public NumberExpression sum(final Function mapper) { MqlExpression array = (MqlExpression) this.map(mapper); - return array.reduce(of(0), (NumberExpression a, NumberExpression b) -> a.multiply(b)); + return array.reduce(of(1), (NumberExpression a, NumberExpression b) -> a.multiply(b)); } @Override @@ -909,6 +909,12 @@ public BooleanExpression has(final StringExpression key) { return get(key).ne(ofRem()); } + + @Override + public BooleanExpression has(final String fieldName) { + return this.has(of(fieldName)); + } + static R ofRem() { // $$REMOVE is intentionally not exposed to users return new MqlExpression<>((cr) -> new MqlExpression.AstPlaceholder(new BsonString("$$REMOVE"))) diff --git a/driver-core/src/test/functional/com/mongodb/client/model/expressions/ArrayExpressionsFunctionalTest.java b/driver-core/src/test/functional/com/mongodb/client/model/expressions/ArrayExpressionsFunctionalTest.java index a72fbcd29fc..34fe654e9d5 100644 --- a/driver-core/src/test/functional/com/mongodb/client/model/expressions/ArrayExpressionsFunctionalTest.java +++ b/driver-core/src/test/functional/com/mongodb/client/model/expressions/ArrayExpressionsFunctionalTest.java @@ -200,6 +200,19 @@ public void reduceSumTest() { ofIntegerArray().sum(a -> a)); } + @Test + public void reduceMultiplyTest() { + assertExpression( + 6, + ofIntegerArray(1, 2, 3).multiply(a -> a), + "{'$reduce': {'input': {'$map': {'input': [1, 2, 3], 'in': '$$this'}}, " + + "'initialValue': 1, 'in': {'$multiply': ['$$value', '$$this']}}}"); + // empty array: + assertExpression( + 1, + ofIntegerArray().multiply(a -> a)); + } + @Test public void reduceMaxTest() { assertExpression( diff --git a/driver-core/src/test/functional/com/mongodb/client/model/expressions/DocumentExpressionsFunctionalTest.java b/driver-core/src/test/functional/com/mongodb/client/model/expressions/DocumentExpressionsFunctionalTest.java index f0ef4880631..894d078c4df 100644 --- a/driver-core/src/test/functional/com/mongodb/client/model/expressions/DocumentExpressionsFunctionalTest.java +++ b/driver-core/src/test/functional/com/mongodb/client/model/expressions/DocumentExpressionsFunctionalTest.java @@ -261,4 +261,17 @@ public void asMapTest() { DocumentExpression d = ofDoc("{a: 1}"); assertSame(d, d.asMap()); } + + + @Test + public void hasTest() { + DocumentExpression d = ofDoc("{a: 1}"); + assertExpression( + true, + d.has("a"), + "{'$ne': [{'$getField': {'input': {'$literal': {'a': 1}}, 'field': 'a'}}, '$$REMOVE']}"); + assertExpression( + false, + d.has("not_a")); + } } diff --git a/driver-core/src/test/functional/com/mongodb/client/model/expressions/MapExpressionsFunctionalTest.java b/driver-core/src/test/functional/com/mongodb/client/model/expressions/MapExpressionsFunctionalTest.java index ba657796a46..749e0bdb229 100644 --- a/driver-core/src/test/functional/com/mongodb/client/model/expressions/MapExpressionsFunctionalTest.java +++ b/driver-core/src/test/functional/com/mongodb/client/model/expressions/MapExpressionsFunctionalTest.java @@ -77,15 +77,15 @@ public void getSetMapTest() { } @Test - public void hasMapTest() { + public void hasTest() { + MapExpression e = ofMap(BsonDocument.parse("{key: 1}")); assertExpression( true, - mapKey123.has(of("key")), - "{'$ne': [{'$getField': {'input': {'$setField': {'field': 'key', 'input': " - + "{'$literal': {}}, 'value': 123}}, 'field': 'key'}}, '$$REMOVE']}"); + e.has(of("key")), + "{'$ne': [{'$getField': {'input': {'$literal': {'key': 1}}, 'field': 'key'}}, '$$REMOVE']}"); assertExpression( false, - mapKey123.has("not_key")); + e.has("not_key")); } @Test diff --git a/driver-core/src/test/functional/com/mongodb/client/model/expressions/StringExpressionsFunctionalTest.java b/driver-core/src/test/functional/com/mongodb/client/model/expressions/StringExpressionsFunctionalTest.java index 5ba68f3b8b2..44789d7c50f 100644 --- a/driver-core/src/test/functional/com/mongodb/client/model/expressions/StringExpressionsFunctionalTest.java +++ b/driver-core/src/test/functional/com/mongodb/client/model/expressions/StringExpressionsFunctionalTest.java @@ -130,6 +130,10 @@ public void substrTest() { "abc".substring(1, 1 + 1), of("abc").substr(of(1), of(1)), "{'$substrCP': ['abc', 1, 1]}"); + assertExpression( + "bc", + of("abc").substr(of(1), of(100)), + "{'$substrCP': ['abc', 1, 100]}"); // unicode assertExpression( diff --git a/driver-core/src/test/functional/com/mongodb/client/model/expressions/TypeExpressionsFunctionalTest.java b/driver-core/src/test/functional/com/mongodb/client/model/expressions/TypeExpressionsFunctionalTest.java index 3dd6ee830d8..8a83a8ea014 100644 --- a/driver-core/src/test/functional/com/mongodb/client/model/expressions/TypeExpressionsFunctionalTest.java +++ b/driver-core/src/test/functional/com/mongodb/client/model/expressions/TypeExpressionsFunctionalTest.java @@ -263,6 +263,24 @@ public void parseIntegerTest() { assertExpression( longVal, of(longVal + "").parseInteger()); + + // failures + assertThrows(MongoCommandException.class, () -> + assertExpression( + "", + of(BsonDocument.parse("{a:'1.5'}")).getString("a").parseInteger())); + assertThrows(MongoCommandException.class, () -> + assertExpression( + "", + of(BsonDocument.parse("{a:'not an integer'}")).getString("a").parseInteger())); + assertThrows(MongoCommandException.class, () -> + assertExpression( + "", + of("1.5").parseInteger())); + assertThrows(MongoCommandException.class, () -> + assertExpression( + "", + of("not an integer").parseInteger())); } // non-string From 75cb0b7a8b187f282bce5811e61d45ef99b0c879 Mon Sep 17 00:00:00 2001 From: Maxim Katcharov Date: Fri, 20 Jan 2023 15:41:09 -0700 Subject: [PATCH 18/27] Add javadocs for remaining classes (#1070) JAVA-4799 --- .../model/expressions/ArrayExpression.java | 223 ++++++++++- .../client/model/expressions/Branches.java | 239 ++++++++--- .../expressions/BranchesIntermediary.java | 237 ++++++++--- .../model/expressions/BranchesTerminal.java | 13 +- .../model/expressions/DocumentExpression.java | 370 +++++++++++++++++- .../model/expressions/EntryExpression.java | 35 +- .../client/model/expressions/Expression.java | 4 +- .../client/model/expressions/Expressions.java | 67 +++- .../model/expressions/MapExpression.java | 125 +++++- .../model/expressions/MqlExpression.java | 8 +- .../model/expressions/MqlUnchecked.java | 33 +- .../model/expressions/StringExpression.java | 110 +++++- 12 files changed, 1320 insertions(+), 144 deletions(-) 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 index 2291b1e69fe..a4eb5493666 100644 --- a/driver-core/src/main/com/mongodb/client/model/expressions/ArrayExpression.java +++ b/driver-core/src/main/com/mongodb/client/model/expressions/ArrayExpression.java @@ -20,7 +20,6 @@ import static com.mongodb.client.model.expressions.Expressions.of; import static com.mongodb.client.model.expressions.MqlUnchecked.Unchecked.PRESENT; -import static com.mongodb.client.model.expressions.MqlUnchecked.Unchecked.NON_EMPTY; /** * An array {@link Expression value} in the context of the MongoDB Query @@ -58,67 +57,267 @@ public interface ArrayExpression extends Expression { */ IntegerExpression size(); + /** + * True if any value in {@code this} array satisfies the predicate. + * + * @param predicate the predicate. + * @return the resulting value. + */ BooleanExpression any(Function predicate); + /** + * True if all values in {@code this} array satisfy the predicate. + * + * @param predicate the predicate. + * @return the resulting value. + */ BooleanExpression all(Function predicate); + /** + * The sum of adding together all the values of {@code this} array, + * via the provided {@code mapper}. Returns 0 if the array is empty. + * + *

    The mapper may be used to transform the values of {@code this} array + * into {@linkplain NumberExpression numbers}. If no transformation is + * necessary, then the identify function {@code array.sum(v -> v)} should + * be used. + * + * @param mapper the mapper function. + * @return the resulting value. + */ NumberExpression sum(Function mapper); + /** + * The product of multiplying together all the values of {@code this} array, + * via the provided {@code mapper}. Returns 1 if the array is empty. + * + *

    The mapper may be used to transform the values of {@code this} array + * into {@linkplain NumberExpression numbers}. If no transformation is + * necessary, then the identify function {@code array.multiply(v -> v)} + * should be used. + * + * @param mapper the mapper function. + * @return the resulting value. + */ NumberExpression multiply(Function mapper); + /** + * The {@linkplain #gt(Expression) largest} value all the values of + * {@code this} array, or the {@code other} value if this array is empty. + * + * @param other the other value. + * @return the resulting value. + */ T max(T other); + /** + * The {@linkplain #lt(Expression) smallest} value all the values of + * {@code this} array, or the {@code other} value if this array is empty. + * + * @param other the other value. + * @return the resulting value. + */ T min(T other); + /** + * The {@linkplain #gt(Expression) largest} {@code n} elements of + * {@code this} array, or all elements if the array contains fewer than + * {@code n} elements. + * + * @param n the number of elements. + * @return the resulting value. + */ ArrayExpression maxN(IntegerExpression n); + + /** + * The {@linkplain #lt(Expression) smallest} {@code n} elements of + * {@code this} array, or all elements if the array contains fewer than + * {@code n} elements. + * + * @param n the number of elements. + * @return the resulting value. + */ ArrayExpression minN(IntegerExpression n); + /** + * The string-concatenation of all the values of {@code this} array, + * via the provided {@code mapper}. Returns the empty string if the array + * is empty. + * + *

    The mapper may be used to transform the values of {@code this} array + * into {@linkplain StringExpression strings}. If no transformation is + * necessary, then the identify function {@code array.join(v -> v)} should + * be used. + * + * @param mapper the mapper function. + * @return the resulting value. + */ StringExpression join(Function mapper); + /** + * The array-concatenation of all the array values of {@code this} array, + * via the provided {@code mapper}. Returns the empty array if the array + * is empty. + * + *

    The mapper may be used to transform the values of {@code this} array + * into {@linkplain ArrayExpression arrays}. If no transformation is + * necessary, then the identify function {@code array.concat(v -> v)} should + * be used. + * + * @param mapper the mapper function. + * @return the resulting value. + * @param the type of the elements of the array. + */ ArrayExpression concat(Function> mapper); + /** + * The set union of all the array values of {@code this} array, + * via the provided {@code mapper}. Returns the empty array if the array + * is empty. + * + *

    The mapper may be used to transform the values of {@code this} array + * into {@linkplain ArrayExpression arrays}. If no transformation is + * necessary, then the identify function {@code array.union(v -> v)} should + * be used. + * + * @param mapper the mapper function. + * @return the resulting value. + * @param the type of the elements of the array. + */ ArrayExpression union(Function> mapper); + /** + * The {@linkplain MapExpression map} value corresponding to the + * {@linkplain EntryExpression entry} values of {@code this} array, + * via the provided {@code mapper}. Returns the empty array if the array + * is empty. + * + *

    The mapper may be used to transform the values of {@code this} array + * into {@linkplain EntryExpression entries}. If no transformation is + * necessary, then the identify function {@code array.union(v -> v)} should + * be used. + * + * @see MapExpression#entrySet() + * @param mapper the mapper function. + * @return the resulting value. + * @param the type of the resulting map's values. + */ MapExpression asMap(Function> mapper); /** - * user asserts that i is in bounds for the array + * Returns the element at the provided index {@code i} for + * {@code this} array. * - * @param i - * @return + *

    Warning: The use of this method is an assertion that + * the index {@code i} is in bounds for the array. + * If the index is out of bounds for this array, then + * the behaviour of the API is not defined. + * + * @param i the index. + * @return the resulting value. */ @MqlUnchecked(PRESENT) T elementAt(IntegerExpression i); + /** + * Returns the element at the provided index {@code i} for + * {@code this} array. + * + *

    Warning: The use of this method is an assertion that + * the index {@code i} is in bounds for the array. + * If the index is out of bounds for this array, then + * the behaviour of the API is not defined. + * + * @param i the index. + * @return the resulting value. + */ + @MqlUnchecked(PRESENT) default T elementAt(final int i) { return this.elementAt(of(i)); } /** - * user asserts that array is not empty - * @return + * Returns the first element of {@code this} array. + * + *

    Warning: The use of this method is an assertion that + * the array is not empty. + * If the array is empty then the behaviour of the API is not defined. + * + * @return the resulting value. */ - @MqlUnchecked(NON_EMPTY) + @MqlUnchecked(PRESENT) T first(); /** - * user asserts that array is not empty - * @return + * Returns the last element of {@code this} array. + * + *

    Warning: The use of this method is an assertion that + * the array is not empty. + * If the array is empty then the behaviour of the API is not defined. + * + * @return the resulting value. */ T last(); - BooleanExpression contains(T contains); + /** + * True if {@code this} array contains a value that is + * {@linkplain #eq equal} to the provided {@code value}. + * + * @param value the value. + * @return the resulting value. + */ + BooleanExpression contains(T value); - ArrayExpression concat(ArrayExpression array); + /** + * The result of concatenating {@code this} array first with + * the {@code other} array ensuing. + * + * @param other the other array. + * @return the resulting array. + */ + ArrayExpression concat(ArrayExpression other); + /** + * The subarray of {@code this} array, from the {@code start} index + * inclusive, and continuing for the specified {@code length}, up to + * the end of the array. + * + * @param start start index + * @param length length + * @return the resulting value + */ ArrayExpression slice(IntegerExpression start, IntegerExpression length); + /** + * The subarray of {@code this} array, from the {@code start} index + * inclusive, and continuing for the specified {@code length}, or + * to the end of the array. + * + * @param start start index + * @param length length + * @return the resulting value + */ default ArrayExpression slice(final int start, final int length) { return this.slice(of(start), of(length)); } - ArrayExpression union(ArrayExpression set); + /** + * The set-union of {@code this} array and the {@code other} array ensuing, + * containing only the distinct values of both. + * No guarantee is made regarding order. + * + * @param other the other array. + * @return the resulting array. + */ + ArrayExpression union(ArrayExpression other); + + /** + * An array containing only the distinct values of {@code this} array. + * No guarantee is made regarding order. + * + * @return the resulting value + */ ArrayExpression distinct(); /** diff --git a/driver-core/src/main/com/mongodb/client/model/expressions/Branches.java b/driver-core/src/main/com/mongodb/client/model/expressions/Branches.java index fbda31c2e9e..12e5d5561e6 100644 --- a/driver-core/src/main/com/mongodb/client/model/expressions/Branches.java +++ b/driver-core/src/main/com/mongodb/client/model/expressions/Branches.java @@ -22,6 +22,16 @@ import static com.mongodb.client.model.expressions.MqlUnchecked.Unchecked.TYPE_ARGUMENT; +/** + * Branches are used in {@linkplain Expression#switchOn}, and + * define a sequence of checks that will be performed. The first check + * to succeed will produce the value that it specifies. If no check succeeds, + * then the operation + * {@linkplain BranchesIntermediary#defaults(Function) defaults} to a default + * value, or if none is specified, the operation causes an error. + * + * @param the type of the values that may be checked. + */ public final class Branches { Branches() { @@ -39,61 +49,198 @@ private static MqlExpression mqlEx(final T value) { // is fn - public BranchesIntermediary is(final Function o, final Function r) { - return with(value -> new SwitchCase<>(o.apply(value), r.apply(value))); + /** + * A successful check for the specified {@code predicate} + * produces a value specified by the {@code mapping}. + * + * @param predicate the predicate. + * @param mapping the mapping. + * @param the type of the produced value. + * @return the appended sequence of checks. + */ + public BranchesIntermediary is(final Function predicate, final Function mapping) { + return with(value -> new SwitchCase<>(predicate.apply(value), mapping.apply(value))); } // eq lt lte - public BranchesIntermediary eq(final T v, final Function r) { - return is(value -> value.eq(v), r); - } - - public BranchesIntermediary lt(final T v, final Function r) { - return is(value -> value.lt(v), r); - } - - public BranchesIntermediary lte(final T v, final Function r) { - return is(value -> value.lte(v), r); + /** + * A successful check for {@linkplain Expression#eq equality} + * produces a value specified by the {@code mapping}. + * + * @param v the value to check against. + * @param mapping the mapping. + * @param the type of the produced value. + * @return the appended sequence of checks. + */ + public BranchesIntermediary eq(final T v, final Function mapping) { + return is(value -> value.eq(v), mapping); + } + + /** + * A successful check for being + * {@linkplain Expression#lt less than} + * the provided value {@code v} + * produces a value specified by the {@code mapping}. + * + * @param v the value to check against. + * @param mapping the mapping. + * @param the type of the produced value. + * @return the appended sequence of checks. + */ + public BranchesIntermediary lt(final T v, final Function mapping) { + return is(value -> value.lt(v), mapping); + } + + /** + * A successful check for being + * {@linkplain Expression#lte less than or equal to} + * the provided value {@code v} + * produces a value specified by the {@code mapping}. + * + * @param v the value to check against. + * @param mapping the mapping. + * @param the type of the produced value. + * @return the appended sequence of checks. + */ + public BranchesIntermediary lte(final T v, final Function mapping) { + return is(value -> value.lte(v), mapping); } // is type - public BranchesIntermediary isBoolean(final Function r) { - return is(v -> mqlEx(v).isBoolean(), v -> r.apply((BooleanExpression) v)); - } - - public BranchesIntermediary isNumber(final Function r) { - return is(v -> mqlEx(v).isNumber(), v -> r.apply((NumberExpression) v)); - } - - public BranchesIntermediary isInteger(final Function r) { - return is(v -> mqlEx(v).isInteger(), v -> r.apply((IntegerExpression) v)); - } - - public BranchesIntermediary isString(final Function r) { - return is(v -> mqlEx(v).isString(), v -> r.apply((StringExpression) v)); - } - - public BranchesIntermediary isDate(final Function r) { - return is(v -> mqlEx(v).isDate(), v -> r.apply((DateExpression) v)); - } - + /** + * A successful check for + * {@linkplain Expression#isBooleanOr(BooleanExpression) being a boolean} + * produces a value specified by the {@code mapping}. + * + * @param mapping the mapping. + * @return the appended sequence of checks. + * @param the type of the produced value. + */ + public BranchesIntermediary isBoolean(final Function mapping) { + return is(v -> mqlEx(v).isBoolean(), v -> mapping.apply((BooleanExpression) v)); + } + + /** + * A successful check for + * {@linkplain Expression#isBooleanOr(BooleanExpression) being a boolean} + * produces a value specified by the {@code mapping}. + * + * @param mapping the mapping. + * @return the appended sequence of checks. + * @param the type of the produced value. + */ + public BranchesIntermediary isNumber(final Function mapping) { + return is(v -> mqlEx(v).isNumber(), v -> mapping.apply((NumberExpression) v)); + } + + /** + * A successful check for + * {@linkplain Expression#isIntegerOr(IntegerExpression) being an integer} + * produces a value specified by the {@code mapping}. + * + * @param mapping the mapping. + * @return the appended sequence of checks. + * @param the type of the produced value. + */ + public BranchesIntermediary isInteger(final Function mapping) { + return is(v -> mqlEx(v).isInteger(), v -> mapping.apply((IntegerExpression) v)); + } + + /** + * A successful check for + * {@linkplain Expression#isStringOr(StringExpression) being a string} + * produces a value specified by the {@code mapping}. + * + * @param mapping the mapping. + * @return the appended sequence of checks. + * @param the type of the produced value. + */ + public BranchesIntermediary isString(final Function mapping) { + return is(v -> mqlEx(v).isString(), v -> mapping.apply((StringExpression) v)); + } + + /** + * A successful check for + * {@linkplain Expression#isDateOr(DateExpression) being a date} + * produces a value specified by the {@code mapping}. + * + * @param mapping the mapping. + * @return the appended sequence of checks. + * @param the type of the produced value. + */ + public BranchesIntermediary isDate(final Function mapping) { + return is(v -> mqlEx(v).isDate(), v -> mapping.apply((DateExpression) v)); + } + + /** + * A successful check for + * {@linkplain Expression#isArrayOr(ArrayExpression) being an array} + * produces a value specified by the {@code mapping}. + * + *

    Warning: The type argument of the array is not + * enforced by the API. The use of this method is an + * unchecked assertion that the type argument is correct. + * + * @param mapping the mapping. + * @return the appended sequence of checks. + * @param the type of the produced value. + * @param the type of the array. + */ @SuppressWarnings("unchecked") - public BranchesIntermediary isArray(final Function, ? extends R> r) { - return is(v -> mqlEx(v).isArray(), v -> r.apply((ArrayExpression) v)); - } - - public BranchesIntermediary isDocument(final Function r) { - return is(v -> mqlEx(v).isDocumentOrMap(), v -> r.apply((DocumentExpression) v)); - } - + public BranchesIntermediary isArray(final Function, ? extends R> mapping) { + return is(v -> mqlEx(v).isArray(), v -> mapping.apply((ArrayExpression) v)); + } + + /** + * A successful check for + * {@linkplain Expression#isDocumentOr(DocumentExpression) being a document} + * produces a value specified by the {@code mapping}. + * + *

    Note: Any value considered to be a document by this API + * will also be considered a map, and vice-versa. + * + * @param mapping the mapping. + * @return the appended sequence of checks. + * @param the type of the produced value. + */ + public BranchesIntermediary isDocument(final Function mapping) { + return is(v -> mqlEx(v).isDocumentOrMap(), v -> mapping.apply((DocumentExpression) v)); + } + + /** + * A successful check for + * {@linkplain Expression#isMapOr(MapExpression) being a map} + * produces a value specified by the {@code mapping}. + * + *

    Note: Any value considered to be a map by this API + * will also be considered a document, and vice-versa. + * + *

    Warning: The type argument of the map is not + * enforced by the API. The use of this method is an + * unchecked assertion that the type argument is correct. + * + * @param mapping the mapping. + * @return the appended sequence of checks. + * @param the type of the produced value. + * @param the type of the array. + */ @SuppressWarnings("unchecked") - public BranchesIntermediary isMap(final Function, ? extends R> r) { - return is(v -> mqlEx(v).isDocumentOrMap(), v -> r.apply((MapExpression) v)); - } - - public BranchesIntermediary isNull(final Function r) { - return is(v -> mqlEx(v).isNull(), v -> r.apply(v)); + public BranchesIntermediary isMap(final Function, ? extends R> mapping) { + return is(v -> mqlEx(v).isDocumentOrMap(), v -> mapping.apply((MapExpression) v)); + } + + /** + * A successful check for + * {@linkplain Expressions#ofNull()} being the null value} + * produces a value specified by the {@code mapping}. + * + * @param mapping the mapping. + * @return the appended sequence of checks. + * @param the type of the produced value. + */ + public BranchesIntermediary isNull(final Function mapping) { + return is(v -> mqlEx(v).isNull(), v -> mapping.apply(v)); } } diff --git a/driver-core/src/main/com/mongodb/client/model/expressions/BranchesIntermediary.java b/driver-core/src/main/com/mongodb/client/model/expressions/BranchesIntermediary.java index 9ef53d4a7c7..3606aa520ff 100644 --- a/driver-core/src/main/com/mongodb/client/model/expressions/BranchesIntermediary.java +++ b/driver-core/src/main/com/mongodb/client/model/expressions/BranchesIntermediary.java @@ -20,6 +20,12 @@ import java.util.List; import java.util.function.Function; +/** + * See {@link Branches}. + * + * @param the type of the values that may be checked. + * @param the type of the value produced. + */ public final class BranchesIntermediary extends BranchesTerminal { BranchesIntermediary(final List>> branches) { super(branches, null); @@ -37,66 +43,197 @@ private static MqlExpression mqlEx(final T value) { // is fn - public BranchesIntermediary is(final Function o, final Function r) { - return this.with(value -> new SwitchCase<>(o.apply(value), r.apply(value))); + /** + * A successful check for the specified {@code predicate} + * produces a value specified by the {@code mapping}. + * + * @param predicate the predicate. + * @param mapping the mapping. + * @return the appended sequence of checks. + */ + public BranchesIntermediary is(final Function predicate, final Function mapping) { + return this.with(value -> new SwitchCase<>(predicate.apply(value), mapping.apply(value))); } // eq lt lte - public BranchesIntermediary eq(final T v, final Function r) { - return is(value -> value.eq(v), r); - } - - public BranchesIntermediary lt(final T v, final Function r) { - return is(value -> value.lt(v), r); - } - - public BranchesIntermediary lte(final T v, final Function r) { - return is(value -> value.lte(v), r); + /** + * A successful check for {@linkplain Expression#eq equality} + * produces a value specified by the {@code mapping}. + * + * @param v the value to check against. + * @param mapping the mapping. + * @return the appended sequence of checks. + */ + public BranchesIntermediary eq(final T v, final Function mapping) { + return is(value -> value.eq(v), mapping); + } + + /** + * A successful check for being + * {@linkplain Expression#lt less than} + * the provided value {@code v} + * produces a value specified by the {@code mapping}. + * + * @param v the value to check against. + * @param mapping the mapping. + * @return the appended sequence of checks. + */ + public BranchesIntermediary lt(final T v, final Function mapping) { + return is(value -> value.lt(v), mapping); + } + + /** + * A successful check for being + * {@linkplain Expression#lte less than or equal to} + * the provided value {@code v} + * produces a value specified by the {@code mapping}. + * + * @param v the value to check against. + * @param mapping the mapping. + * @return the appended sequence of checks. + */ + public BranchesIntermediary lte(final T v, final Function mapping) { + return is(value -> value.lte(v), mapping); } // is type - public BranchesIntermediary isBoolean(final Function r) { - return is(v -> mqlEx(v).isBoolean(), v -> r.apply((BooleanExpression) v)); - } - - public BranchesIntermediary isNumber(final Function r) { - return is(v -> mqlEx(v).isNumber(), v -> r.apply((NumberExpression) v)); - } - - public BranchesIntermediary isInteger(final Function r) { - return is(v -> mqlEx(v).isInteger(), v -> r.apply((IntegerExpression) v)); - } - - public BranchesIntermediary isString(final Function r) { - return is(v -> mqlEx(v).isString(), v -> r.apply((StringExpression) v)); - } - - public BranchesIntermediary isDate(final Function r) { - return is(v -> mqlEx(v).isDate(), v -> r.apply((DateExpression) v)); - } - + /** + * A successful check for + * {@linkplain Expression#isBooleanOr(BooleanExpression) being a boolean} + * produces a value specified by the {@code mapping}. + * + * @param mapping the mapping. + * @return the appended sequence of checks. + */ + public BranchesIntermediary isBoolean(final Function mapping) { + return is(v -> mqlEx(v).isBoolean(), v -> mapping.apply((BooleanExpression) v)); + } + + /** + * A successful check for + * {@linkplain Expression#isBooleanOr(BooleanExpression) being a boolean} + * produces a value specified by the {@code mapping}. + * + * @param mapping the mapping. + * @return the appended sequence of checks. + */ + public BranchesIntermediary isNumber(final Function mapping) { + return is(v -> mqlEx(v).isNumber(), v -> mapping.apply((NumberExpression) v)); + } + + /** + * A successful check for + * {@linkplain Expression#isIntegerOr(IntegerExpression) being an integer} + * produces a value specified by the {@code mapping}. + * + * @param mapping the mapping. + * @return the appended sequence of checks. + */ + public BranchesIntermediary isInteger(final Function mapping) { + return is(v -> mqlEx(v).isInteger(), v -> mapping.apply((IntegerExpression) v)); + } + + /** + * A successful check for + * {@linkplain Expression#isStringOr(StringExpression) being a string} + * produces a value specified by the {@code mapping}. + * + * @param mapping the mapping. + * @return the appended sequence of checks. + */ + public BranchesIntermediary isString(final Function mapping) { + return is(v -> mqlEx(v).isString(), v -> mapping.apply((StringExpression) v)); + } + + /** + * A successful check for + * {@linkplain Expression#isDateOr(DateExpression) being a date} + * produces a value specified by the {@code mapping}. + * + * @param mapping the mapping. + * @return the appended sequence of checks. + */ + public BranchesIntermediary isDate(final Function mapping) { + return is(v -> mqlEx(v).isDate(), v -> mapping.apply((DateExpression) v)); + } + + /** + * A successful check for + * {@linkplain Expression#isArrayOr(ArrayExpression) being an array} + * produces a value specified by the {@code mapping}. + * + *

    Warning: The type argument of the array is not + * enforced by the API. The use of this method is an + * unchecked assertion that the type argument is correct. + * + * @param mapping the mapping. + * @return the appended sequence of checks. + * @param the type of the array. + */ @SuppressWarnings("unchecked") - public BranchesIntermediary isArray(final Function, ? extends R> r) { - return is(v -> mqlEx(v).isArray(), v -> r.apply((ArrayExpression) v)); - } - - public BranchesIntermediary isDocument(final Function r) { - return is(v -> mqlEx(v).isDocumentOrMap(), v -> r.apply((DocumentExpression) v)); - } - + public BranchesIntermediary isArray(final Function, ? extends R> mapping) { + return is(v -> mqlEx(v).isArray(), v -> mapping.apply((ArrayExpression) v)); + } + + /** + * A successful check for + * {@linkplain Expression#isDocumentOr(DocumentExpression) being a document} + * produces a value specified by the {@code mapping}. + * + *

    Note: Any value considered to be a document by this API + * will also be considered a map, and vice-versa. + * + * @param mapping the mapping. + * @return the appended sequence of checks. + */ + public BranchesIntermediary isDocument(final Function mapping) { + return is(v -> mqlEx(v).isDocumentOrMap(), v -> mapping.apply((DocumentExpression) v)); + } + + /** + * A successful check for + * {@linkplain Expression#isMapOr(MapExpression) being a map} + * produces a value specified by the {@code mapping}. + * + *

    Note: Any value considered to be a map by this API + * will also be considered a document, and vice-versa. + * + *

    Warning: The type argument of the map is not + * enforced by the API. The use of this method is an + * unchecked assertion that the type argument is correct. + * + * @param mapping the mapping. + * @return the appended sequence of checks. + * @param the type of the array. + */ @SuppressWarnings("unchecked") - public BranchesIntermediary isMap(final Function, ? extends R> r) { - return is(v -> mqlEx(v).isDocumentOrMap(), v -> r.apply((MapExpression) v)); - } - - public BranchesIntermediary isNull(final Function r) { - return is(v -> mqlEx(v).isNull(), v -> r.apply(v)); - } - - public BranchesTerminal defaults(final Function r) { - return this.withDefault(value -> r.apply(value)); + public BranchesIntermediary isMap(final Function, ? extends R> mapping) { + return is(v -> mqlEx(v).isDocumentOrMap(), v -> mapping.apply((MapExpression) v)); + } + + /** + * A successful check for + * {@linkplain Expressions#ofNull()} being the null value} + * produces a value specified by the {@code mapping}. + * + * @param mapping the mapping. + * @return the appended sequence of checks. + */ + public BranchesIntermediary isNull(final Function mapping) { + return is(v -> mqlEx(v).isNull(), v -> mapping.apply(v)); + } + + /** + * If no other check succeeds, + * produces a value specified by the {@code mapping}. + * + * @param mapping the mapping. + * @return the appended sequence of checks. + */ + public BranchesTerminal defaults(final Function mapping) { + return this.withDefault(value -> mapping.apply(value)); } } diff --git a/driver-core/src/main/com/mongodb/client/model/expressions/BranchesTerminal.java b/driver-core/src/main/com/mongodb/client/model/expressions/BranchesTerminal.java index 97d0b85f233..9170be6b87b 100644 --- a/driver-core/src/main/com/mongodb/client/model/expressions/BranchesTerminal.java +++ b/driver-core/src/main/com/mongodb/client/model/expressions/BranchesTerminal.java @@ -21,6 +21,13 @@ import java.util.List; import java.util.function.Function; +/** + * See {@link Branches}. This is the terminal branch, to which no additional + * checks may be added, since the default value has been specified. + * + * @param the type of the values that may be checked. + * @param the type of the value produced. + */ public class BranchesTerminal { private final List>> branches; @@ -32,16 +39,16 @@ public class BranchesTerminal { this.defaults = defaults; } - protected BranchesTerminal withDefault(final Function defaults) { + BranchesTerminal withDefault(final Function defaults) { return new BranchesTerminal<>(branches, defaults); } - protected List>> getBranches() { + List>> getBranches() { return branches; } @Nullable - protected Function getDefaults() { + Function getDefaults() { return defaults; } } 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 index 9222defafaa..649d17548d2 100644 --- a/driver-core/src/main/com/mongodb/client/model/expressions/DocumentExpression.java +++ b/driver-core/src/main/com/mongodb/client/model/expressions/DocumentExpression.java @@ -25,6 +25,7 @@ import static com.mongodb.client.model.expressions.Expressions.ofMap; import static com.mongodb.client.model.expressions.MqlUnchecked.Unchecked.PRESENT; import static com.mongodb.client.model.expressions.MqlUnchecked.Unchecked.TYPE; +import static com.mongodb.client.model.expressions.MqlUnchecked.Unchecked.TYPE_ARGUMENT; /** * Expresses a document value. A document is an ordered set of fields, where the @@ -32,7 +33,6 @@ */ public interface DocumentExpression extends Expression { - DocumentExpression setField(String fieldName, Expression exp); /** * True if {@code this} document has a field with the provided * {@code fieldName}. @@ -42,78 +42,442 @@ public interface DocumentExpression extends Expression { */ BooleanExpression has(String fieldName); + /** + * Returns a document with the same fields as {@code this} document, but + * with the {@code fieldName} field set to the specified {@code value}. + * + *

    This does not affect the original document. + * + *

    Warning: Users should take care to assign values, such that the types + * of those values correspond to the types of ensuing {@code get...} + * invocations, since this API has no way of verifying this correspondence. + * + * @param fieldName the name of the field. + * @param value the value. + * @return the resulting document. + */ + DocumentExpression setField(String fieldName, Expression value); + + /** + * Returns a document with the same fields as {@code this} document, but + * with the {@code fieldName} field set to the specified {@code value}. + * + *

    This does not affect the original document. + * + *

    Warning: Users should take care to assign values, such that the types + * of those values correspond to the types of ensuing {@code get...} + * invocations, since this API has no way of verifying this correspondence. + * + * @param fieldName the name of the field. + * @return the resulting document. + */ DocumentExpression unsetField(String fieldName); + /** + * Returns the {@linkplain Expression} value of the field + * with the provided {@code fieldName}. + * + *

    Use of this method is an assertion that the field exists. + * + *

    Warning: The type of the values of the resulting map are not + * enforced by the API. The specification of a type by the user is an + * unchecked assertion that all map values are of that type. + * If the map contains multiple types (such as both nulls and integers) + * then a super-type encompassing all types must be chosen, and + * if necessary the elements should be individually type-checked when used. + * + * @param fieldName the name of the field. + * @return the resulting value. + */ @MqlUnchecked(PRESENT) Expression getField(String fieldName); + /** + * Returns the {@linkplain BooleanExpression boolean} value of the field + * with the provided {@code fieldName}. + * + *

    Warning: The type and presence of the resulting value is not + * enforced by the API. The use of this method is an + * unchecked assertion that the field is present and + * the field value is of the specified type. + * + * @param fieldName the name of the field. + * @return the resulting value. + */ @MqlUnchecked({PRESENT, TYPE}) BooleanExpression getBoolean(String fieldName); + /** + * Returns the {@linkplain BooleanExpression boolean} value of the field + * with the provided {@code fieldName}, + * or the {@code other} value if the field is not a boolean + * or if the document {@linkplain #has} no such field. + * + * @param fieldName the name of the field. + * @param other the other value. + * @return the resulting value. + */ BooleanExpression getBoolean(String fieldName, BooleanExpression other); + /** + * Returns the {@linkplain BooleanExpression boolean} value of the field + * with the provided {@code fieldName}, + * or the {@code other} value if the field is not a boolean + * or if the document {@linkplain #has} no such field. + * + * @param fieldName the name of the field. + * @param other the other value. + * @return the resulting value. + */ default BooleanExpression getBoolean(final String fieldName, final boolean other) { return getBoolean(fieldName, of(other)); } + /** + * Returns the {@linkplain NumberExpression number} value of the field + * with the provided {@code fieldName}. + * + *

    Warning: The type and presence of the resulting value is not + * enforced by the API. The use of this method is an + * unchecked assertion that the field is present and + * the field value is of the specified type. + * + * @param fieldName the name of the field. + * @return the resulting value. + */ + @MqlUnchecked({PRESENT, TYPE}) NumberExpression getNumber(String fieldName); + /** + * Returns the {@linkplain NumberExpression number} value of the field + * with the provided {@code fieldName}, + * or the {@code other} value if the field is not a number + * or if the document {@linkplain #has} no such field. + * + * @param fieldName the name of the field. + * @param other the other value. + * @return the resulting value. + */ NumberExpression getNumber(String fieldName, NumberExpression other); + /** + * Returns the {@linkplain NumberExpression number} value of the field + * with the provided {@code fieldName}, + * or the {@code other} value if the field is not a number + * or if the document {@linkplain #has} no such field. + * + * @param fieldName the name of the field. + * @param other the other value. + * @return the resulting value. + */ default NumberExpression getNumber(final String fieldName, final Number other) { return getNumber(fieldName, Expressions.numberToExpression(other)); } + /** + * Returns the {@linkplain IntegerExpression integer} value of the field + * with the provided {@code fieldName}. + * + *

    Warning: The type and presence of the resulting value is not + * enforced by the API. The use of this method is an + * unchecked assertion that the field is present and + * the field value is of the specified type. + * + * @param fieldName the name of the field. + * @return the resulting value. + */ + @MqlUnchecked({PRESENT, TYPE}) IntegerExpression getInteger(String fieldName); + /** + * Returns the {@linkplain IntegerExpression integer} value of the field + * with the provided {@code fieldName}, + * or the {@code other} value if the field is not an integer + * or if the document {@linkplain #has} no such field. + * + * @param fieldName the name of the field. + * @param other the other value. + * @return the resulting value. + */ IntegerExpression getInteger(String fieldName, IntegerExpression other); + /** + * Returns the {@linkplain IntegerExpression integer} value of the field + * with the provided {@code fieldName}, + * or the {@code other} value if the field is not an integer + * or if the document {@linkplain #has} no such field. + * + * @param fieldName the name of the field. + * @param other the other value. + * @return the resulting value. + */ default IntegerExpression getInteger(final String fieldName, final int other) { return getInteger(fieldName, of(other)); } + /** + * Returns the {@linkplain IntegerExpression integer} value of the field + * with the provided {@code fieldName}, + * or the {@code other} value if the field is not an integer + * or if the document {@linkplain #has} no such field. + * + * @param fieldName the name of the field. + * @param other the other value. + * @return the resulting value. + */ default IntegerExpression getInteger(final String fieldName, final long other) { return getInteger(fieldName, of(other)); } - + /** + * Returns the {@linkplain StringExpression string} value of the field + * with the provided {@code fieldName}. + * + *

    Warning: The type and presence of the resulting value is not + * enforced by the API. The use of this method is an + * unchecked assertion that the field is present and + * the field value is of the specified type. + * + * @param fieldName the name of the field. + * @return the resulting value. + */ + @MqlUnchecked({PRESENT, TYPE}) StringExpression getString(String fieldName); + /** + * Returns the {@linkplain StringExpression string} value of the field + * with the provided {@code fieldName}, + * or the {@code other} value if the field is not a string + * or if the document {@linkplain #has} no such field. + * + * @param fieldName the name of the field. + * @param other the other value. + * @return the resulting value. + */ StringExpression getString(String fieldName, StringExpression other); + /** + * Returns the {@linkplain StringExpression string} value of the field + * with the provided {@code fieldName}, + * or the {@code other} value if the field is not a string + * or if the document {@linkplain #has} no such field. + * + * @param fieldName the name of the field. + * @param other the other value. + * @return the resulting value. + */ default StringExpression getString(final String fieldName, final String other) { return getString(fieldName, of(other)); } + /** + * Returns the {@linkplain DateExpression date} value of the field + * with the provided {@code fieldName}. + * + *

    Warning: The type and presence of the resulting value is not + * enforced by the API. The use of this method is an + * unchecked assertion that the field is present and + * the field value is of the specified type. + * + * @param fieldName the name of the field. + * @return the resulting value. + */ + @MqlUnchecked({PRESENT, TYPE}) DateExpression getDate(String fieldName); + + /** + * Returns the {@linkplain DateExpression date} value of the field + * with the provided {@code fieldName}, + * or the {@code other} value if the field is not a date + * or if the document {@linkplain #has} no such field. + * + * @param fieldName the name of the field. + * @param other the other value. + * @return the resulting value. + */ DateExpression getDate(String fieldName, DateExpression other); + /** + * Returns the {@linkplain DateExpression date} value of the field + * with the provided {@code fieldName}, + * or the {@code other} value if the field is not a date + * or if the document {@linkplain #has} no such field. + * + * @param fieldName the name of the field. + * @param other the other value. + * @return the resulting value. + */ default DateExpression getDate(final String fieldName, final Instant other) { return getDate(fieldName, of(other)); } + /** + * Returns the {@linkplain DocumentExpression document} value of the field + * with the provided {@code fieldName}. + * + *

    Warning: The type and presence of the resulting value is not + * enforced by the API. The use of this method is an + * unchecked assertion that the field is present and + * the field value is of the specified type. + * + * @param fieldName the name of the field. + * @return the resulting value. + */ + @MqlUnchecked({PRESENT, TYPE}) DocumentExpression getDocument(String fieldName); + + /** + * Returns the {@linkplain DocumentExpression document} value of the field + * with the provided {@code fieldName}, + * or the {@code other} value if the field is not a (child) document + * or if the document {@linkplain #has} no such field. + * + *

    Note: Any field considered to be a document by this API + * will also be considered a map, and vice-versa. + * + * @param fieldName the name of the field. + * @param other the other value. + * @return the resulting value. + */ DocumentExpression getDocument(String fieldName, DocumentExpression other); + /** + * Returns the {@linkplain DocumentExpression document} value of the field + * with the provided {@code fieldName}, + * or the {@code other} value if the field is not a (child) document + * or if the document {@linkplain #has} no such field. + * + *

    Note: Any field considered to be a document by this API + * will also be considered a map, and vice-versa. + * + * @param fieldName the name of the field. + * @param other the other value. + * @return the resulting value. + */ default DocumentExpression getDocument(final String fieldName, final Bson other) { return getDocument(fieldName, of(other)); } + /** + * Returns the {@linkplain MapExpression map} value of the field + * with the provided {@code fieldName}. + * + *

    Warning: The type and presence of the resulting value is not + * enforced by the API. The use of this method is an + * unchecked assertion that the field is present and + * the field value is of the specified type. + * + * @param fieldName the name of the field. + * @return the resulting value. + * @param the type. + */ + @MqlUnchecked({PRESENT, TYPE, TYPE_ARGUMENT}) MapExpression getMap(String fieldName); + + /** + * Returns the {@linkplain MapExpression map} value of the field + * with the provided {@code fieldName}, + * or the {@code other} value if the field is not a map + * or if the document {@linkplain #has} no such field. + * + *

    Note: Any field considered to be a document by this API + * will also be considered a map, and vice-versa. + * + *

    Warning: The type argument of the resulting value is not + * enforced by the API. The use of this method is an + * unchecked assertion that the type argument is correct. + * + * @param fieldName the name of the field. + * @param other the other value. + * @return the resulting value. + * @param the type. + */ + @MqlUnchecked({TYPE_ARGUMENT}) MapExpression getMap(String fieldName, MapExpression other); + /** + * Returns the {@linkplain MapExpression map} value of the field + * with the provided {@code fieldName}, + * or the {@code other} value if the field is not a map + * or if the document {@linkplain #has} no such field. + * + *

    Note: Any field considered to be a document by this API + * will also be considered a map, and vice-versa. + * + *

    Warning: The type argument of the resulting value is not + * enforced by the API. The use of this method is an + * unchecked assertion that the type argument is correct. + * + * @param fieldName the name of the field. + * @param other the other value. + * @return the resulting value. + * @param the type. + */ + @MqlUnchecked({TYPE_ARGUMENT}) default MapExpression getMap(final String fieldName, final Bson other) { return getMap(fieldName, ofMap(other)); } + /** + * Returns the {@linkplain ArrayExpression array} value of the field + * with the provided {@code fieldName}. + * + *

    Warning: The type and presence of the resulting value is not + * enforced by the API. The use of this method is an + * unchecked assertion that the field is present and + * the field value is of the specified type. + * + * @param fieldName the name of the field. + * @return the resulting value. + * @param the type. + */ + @MqlUnchecked({PRESENT, TYPE, TYPE_ARGUMENT}) ArrayExpression getArray(String fieldName); + /** + * Returns the {@linkplain ArrayExpression array} value of the field + * with the provided {@code fieldName}, + * or the {@code other} value if the field is not an array + * or if the document {@linkplain #has} no such field. + * + *

    Warning: The type argument of the resulting value is not + * enforced by the API. The use of this method is an + * unchecked assertion that the type argument is correct. + * + * @param fieldName the name of the field. + * @param other the other value. + * @return the resulting value. + * @param the type. + */ + @MqlUnchecked({TYPE_ARGUMENT}) ArrayExpression getArray(String fieldName, ArrayExpression other); + /** + * Returns a document with the same fields as {@code this} document, but + * with any fields present in the {@code other} document overwritten with + * the fields of that other document. That is, fields from both this and the + * other document are merged, with the other document having priority. + * + *

    This does not affect the original document. + * + * @param other the other document. + * @return the resulting value. + */ DocumentExpression merge(DocumentExpression other); - MapExpression asMap(); + /** + * {@code this} document as a {@linkplain MapExpression map}. + * + *

    Warning: The type argument of the resulting value is not + * enforced by the API. The use of this method is an + * unchecked assertion that the type argument is correct. + * + * @return the resulting value. + * @param the type. + */ + + MapExpression asMap(); /** * The result of passing {@code this} value to the provided function. diff --git a/driver-core/src/main/com/mongodb/client/model/expressions/EntryExpression.java b/driver-core/src/main/com/mongodb/client/model/expressions/EntryExpression.java index c5908c28973..822d8329952 100644 --- a/driver-core/src/main/com/mongodb/client/model/expressions/EntryExpression.java +++ b/driver-core/src/main/com/mongodb/client/model/expressions/EntryExpression.java @@ -32,13 +32,46 @@ * @param The type of the value */ public interface EntryExpression extends Expression { + + /** + * The key of {@code this} entry. + * + * @return the key. + */ StringExpression getKey(); + /** + * The value of {@code this} entry. + * + * @return the value. + */ T getValue(); - EntryExpression setValue(T val); + /** + * An entry with the same key as {@code this} entry, and the + * specified {@code value}. + * + * @param value the value. + * @return the resulting entry. + */ + EntryExpression setValue(T value); + /** + * An entry with the same value as {@code this} entry, and the + * specified {@code key}. + * + * @param key the key. + * @return the resulting entry. + */ EntryExpression setKey(StringExpression key); + + /** + * An entry with the same value as {@code this} entry, and the + * specified {@code key}. + * + * @param key the key. + * @return the resulting entry. + */ default EntryExpression setKey(final String key) { return setKey(of(key)); } 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 index e17060bb772..2101527389f 100644 --- a/driver-core/src/main/com/mongodb/client/model/expressions/Expression.java +++ b/driver-core/src/main/com/mongodb/client/model/expressions/Expression.java @@ -81,7 +81,6 @@ * context, users are strongly discouraged from relying on behaviour that is not * part of this API). * - * *

    Users should treat these interfaces as sealed, and should not create * implementations. * @@ -228,6 +227,7 @@ public interface Expression { * * @param other the other value. * @return the resulting value. + * @param the type. */ T isDocumentOr(T other); @@ -254,7 +254,7 @@ public interface Expression { /** * The {@linkplain StringExpression string} representation of {@code this} value. * - *

    This may cause an error to be produced if the type cannot be converted + *

    This will cause an error if the type cannot be converted * to a {@linkplain StringExpression string}, as is the case with * {@linkplain ArrayExpression arrays}, * {@linkplain DocumentExpression documents}, 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 index 489d5baf904..bba99d86a47 100644 --- a/driver-core/src/main/com/mongodb/client/model/expressions/Expressions.java +++ b/driver-core/src/main/com/mongodb/client/model/expressions/Expressions.java @@ -58,6 +58,14 @@ public static BooleanExpression of(final boolean of) { return new MqlExpression<>((codecRegistry) -> new AstPlaceholder(new BsonBoolean(of))); } + /** + * Returns an {@linkplain ArrayExpression array} of + * {@linkplain BooleanExpression booleans} corresponding to + * the provided {@code boolean} primitives. + * + * @param array the array. + * @return the resulting value. + */ public static ArrayExpression ofBooleanArray(final boolean... array) { Assertions.notNull("array", array); List list = new ArrayList<>(); @@ -78,6 +86,14 @@ public static IntegerExpression of(final int of) { return new MqlExpression<>((codecRegistry) -> new AstPlaceholder(new BsonInt32(of))); } + /** + * Returns an {@linkplain ArrayExpression array} of + * {@linkplain IntegerExpression integers} corresponding to + * the provided {@code int} primitives. + * + * @param array the array. + * @return the resulting value. + */ public static ArrayExpression ofIntegerArray(final int... array) { Assertions.notNull("array", array); List list = new ArrayList<>(); @@ -98,6 +114,14 @@ public static IntegerExpression of(final long of) { return new MqlExpression<>((codecRegistry) -> new AstPlaceholder(new BsonInt64(of))); } + /** + * Returns an {@linkplain ArrayExpression array} of + * {@linkplain IntegerExpression integers} corresponding to + * the provided {@code long} primitives. + * + * @param array the array. + * @return the resulting value. + */ public static ArrayExpression ofIntegerArray(final long... array) { Assertions.notNull("array", array); List list = new ArrayList<>(); @@ -118,6 +142,14 @@ public static NumberExpression of(final double of) { return new MqlExpression<>((codecRegistry) -> new AstPlaceholder(new BsonDouble(of))); } + /** + * Returns an {@linkplain ArrayExpression array} of + * {@linkplain NumberExpression numbers} corresponding to + * the provided {@code double} primitives. + * + * @param array the array. + * @return the resulting value. + */ public static ArrayExpression ofNumberArray(final double... array) { Assertions.notNull("array", array); List list = new ArrayList<>(); @@ -129,7 +161,7 @@ public static ArrayExpression ofNumberArray(final double... ar /** * Returns a {@linkplain NumberExpression number} value corresponding to - * the provided {@link Decimal128} + * the provided {@link Decimal128}. * * @param of the {@link Decimal128}. * @return the resulting value. @@ -139,6 +171,14 @@ public static NumberExpression of(final Decimal128 of) { return new MqlExpression<>((codecRegistry) -> new AstPlaceholder(new BsonDecimal128(of))); } + /** + * Returns an {@linkplain ArrayExpression array} of + * {@linkplain NumberExpression numbers} corresponding to + * the provided {@link Decimal128 Decimal128s}. + * + * @param array the array. + * @return the resulting value. + */ public static ArrayExpression ofNumberArray(final Decimal128... array) { Assertions.notNull("array", array); List result = new ArrayList<>(); @@ -149,7 +189,6 @@ public static ArrayExpression ofNumberArray(final Decimal128.. return new MqlExpression<>((cr) -> new AstPlaceholder(new BsonArray(result))); } - /** * Returns a {@linkplain DateExpression date and time} value corresponding to * the provided {@link Instant}. @@ -162,6 +201,14 @@ public static DateExpression of(final Instant of) { return new MqlExpression<>((codecRegistry) -> new AstPlaceholder(new BsonDateTime(of.toEpochMilli()))); } + /** + * Returns an {@linkplain ArrayExpression array} of + * {@linkplain DateExpression dates} corresponding to + * the provided {@link Instant Instants}. + * + * @param array the array. + * @return the resulting value. + */ public static ArrayExpression ofDateArray(final Instant... array) { Assertions.notNull("array", array); List result = new ArrayList<>(); @@ -184,6 +231,14 @@ public static StringExpression of(final String of) { return new MqlExpression<>((codecRegistry) -> new AstPlaceholder(new BsonString(of))); } + /** + * Returns an {@linkplain ArrayExpression array} of + * {@linkplain StringExpression strings} corresponding to + * the provided {@link String Strings}. + * + * @param array the array. + * @return the resulting value. + */ public static ArrayExpression ofStringArray(final String... array) { Assertions.notNull("array", array); List result = new ArrayList<>(); @@ -268,6 +323,12 @@ public static EntryExpression ofEntry(final StringExpr }); } + /** + * Returns an empty {@linkplain MapExpression map} value. + * + * @param the type of the resulting map's values. + * @return the resulting map value. + */ public static MapExpression ofMap() { return ofMap(new BsonDocument()); } @@ -284,8 +345,8 @@ public static MapExpression ofMap() { * if necessary the elements should be individually type-checked when used. * * @param map the map as a {@link Bson Bson document}. - * @return the resulting map value. * @param the type of the resulting map's values. + * @return the resulting map value. */ public static MapExpression ofMap(final Bson map) { Assertions.notNull("map", map); diff --git a/driver-core/src/main/com/mongodb/client/model/expressions/MapExpression.java b/driver-core/src/main/com/mongodb/client/model/expressions/MapExpression.java index db0d387bb02..dccf2a567f1 100644 --- a/driver-core/src/main/com/mongodb/client/model/expressions/MapExpression.java +++ b/driver-core/src/main/com/mongodb/client/model/expressions/MapExpression.java @@ -21,45 +21,162 @@ import static com.mongodb.client.model.expressions.Expressions.of; import static com.mongodb.client.model.expressions.MqlUnchecked.Unchecked.PRESENT; +/** + * A map {@link Expression value} in the context of the MongoDB Query + * Language (MQL). An map is a finite, unordered a set of + * {@link EntryExpression entries} of a certain type, such that no entry key + * is repeated. It is a mapping from keys to values. + * + * @param the type of the elements + */ public interface MapExpression extends Expression { + /** + * True if {@code this} map has a value (including null) for + * the provided key. + * + * @param key the key. + * @return the resulting value. + */ BooleanExpression has(StringExpression key); - default BooleanExpression has(String key) { + /** + * True if {@code this} map has a value (including null) for + * the provided key. + * + * @param key the key. + * @return the resulting value. + */ + default BooleanExpression has(final String key) { return has(of(key)); } - // TODO-END doc "user asserts" + /** + * The value corresponding to the provided key. + * + *

    Warning: The use of this method is an unchecked assertion that + * the key is present (which may be confirmed via {@link #has}). See + * {@link #get(StringExpression, Expression)} for a typesafe variant. + * + * @param key the key. + * @return the value. + */ @MqlUnchecked(PRESENT) T get(StringExpression key); - // TODO-END doc "user asserts" + /** + * The value corresponding to the provided key. + * + *

    Warning: The use of this method is an unchecked assertion that + * the key is present (which may be confirmed via {@link #has}). See + * {@link #get(StringExpression, Expression)} for a typesafe variant. + * + * @param key the key. + * @return the value. + */ default T get(final String key) { return get(of(key)); } + /** + * The value corresponding to the provided {@code key}, or the + * {@code other} value if an entry for the key is not present. + * + * @param key the key. + * @param other the other value. + * @return the resulting value. + */ T get(StringExpression key, T other); + /** + * The value corresponding to the provided {@code key}, or the + * {@code other} value if an entry for the key is not present. + * + * @param key the key. + * @param other the other value. + * @return the resulting value. + */ default T get(final String key, final T other) { return get(of(key), other); } + /** + * Returns a map with the same entries as {@code this} map, but with + * the specified {@code key} set to the specified {@code value}. + * + *

    This does not affect the original map. + * + * @param key the key. + * @param value the value. + * @return the resulting value. + */ MapExpression set(StringExpression key, T value); + /** + * Returns a map with the same entries as {@code this} map, but with + * the specified {@code key} set to the specified {@code value}. + * + *

    This does not affect the original map. + * + * @param key the key. + * @param value the value. + * @return the resulting value. + */ default MapExpression set(final String key, final T value) { return set(of(key), value); } + /** + * Returns a map with the same entries as {@code this} map, but with + * the specified {@code key} absent. + * + *

    This does not affect the original map. + * + * @param key the key. + * @return the resulting value. + */ MapExpression unset(StringExpression key); + /** + * Returns a map with the same entries as {@code this} map, but with + * the specified {@code key} absent. + * + *

    This does not affect the original map. + * + * @param key the key. + * @return the resulting value. + */ default MapExpression unset(final String key) { return unset(of(key)); } - MapExpression merge(MapExpression map); + /** + * Returns a map with the same entries as {@code this} map, but with + * any keys present in the {@code other} map overwritten with the + * values of that other map. That is, entries from both this and the + * other map are merged, with the other map having priority. + * + *

    This does not affect the original map. + * + * @param other the other map. + * @return the resulting value. + */ + MapExpression merge(MapExpression other); + /** + * The {@linkplain EntryExpression entries} of this map as an array. + * + * @see ArrayExpression#asMap + * @return the resulting value. + */ ArrayExpression> entrySet(); + /** + * {@code this} map as a {@linkplain DocumentExpression document}. + * + * @return the resulting value. + * @param the resulting type. + */ R asDocument(); /** 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 index 298ce7c973d..5f5bc9daa3c 100644 --- a/driver-core/src/main/com/mongodb/client/model/expressions/MqlExpression.java +++ b/driver-core/src/main/com/mongodb/client/model/expressions/MqlExpression.java @@ -880,8 +880,8 @@ public StringExpression toUpper() { } @Override - public StringExpression concat(final StringExpression concat) { - return new MqlExpression<>(ast("$concat", concat)); + public StringExpression concat(final StringExpression other) { + return new MqlExpression<>(ast("$concat", other)); } @Override @@ -972,8 +972,8 @@ public MapExpression asMap( @SuppressWarnings("unchecked") @Override - public MapExpression asMap() { - return (MapExpression) this; + public MapExpression asMap() { + return (MapExpression) this; } @SuppressWarnings("unchecked") diff --git a/driver-core/src/main/com/mongodb/client/model/expressions/MqlUnchecked.java b/driver-core/src/main/com/mongodb/client/model/expressions/MqlUnchecked.java index 561a261e976..3841630fe1d 100644 --- a/driver-core/src/main/com/mongodb/client/model/expressions/MqlUnchecked.java +++ b/driver-core/src/main/com/mongodb/client/model/expressions/MqlUnchecked.java @@ -43,32 +43,35 @@ enum Unchecked { /** * The API relies on the values it encounters being of the type - * implied/specified by or inferred from the user code. - * For example, {@link com.mongodb.client.model.expressions.DocumentExpression#getBoolean(String)} + * implied, specified by, or inferred from the user code. + * + *

    For example, {@link DocumentExpression#getBoolean(String)} * relies on the values of the document field being of the - * {@linkplain com.mongodb.client.model.expressions.BooleanExpression boolean} type. + * {@linkplain BooleanExpression boolean} type. */ TYPE, /** * The API checks the raw type, but relies on the type argument - * implied/specified by or inferred from the user code being correct. - * For example, {@link com.mongodb.client.model.expressions.Expression#isArrayOr(ArrayExpression)} + * implied, specified by, or inferred from user code. + * + *

    For example, {@link Expression#isArrayOr(ArrayExpression)} * checks that the value is of the - * {@linkplain com.mongodb.client.model.expressions.ArrayExpression array} raw type, - * but relies on the elements of the array being of the type derived from the user code. + * {@linkplain ArrayExpression array} raw type, + * but relies on the elements of the array being of + * the type derived from the user code. * *

    One may think of it as a more specific version of {@link #TYPE}.

    */ TYPE_ARGUMENT, /** - * The API relies on the array being non-empty. - * For example, {@link com.mongodb.client.model.expressions.ArrayExpression#first()}. - */ - NON_EMPTY, - /** - * The API relies on the element identified by index, name, etc., being present in the - * {@linkplain com.mongodb.client.model.expressions.DocumentExpression document} involved. - * For example, {@link com.mongodb.client.model.expressions.DocumentExpression#getField(String)}. + * The presence of the specified value is not checked by the API. + * The use of the annotated method is an unchecked assertion that the + * specified (whether by index, name, key, position, or otherwise) + * element is present in the structure involved. + * + *

    For example, {@link DocumentExpression#getField(String)} relies + * on the field being present, and {@link ArrayExpression#first} relies + * on the array being non-empty. */ PRESENT, } 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 index 633a99baba0..29065cd92b9 100644 --- a/driver-core/src/main/com/mongodb/client/model/expressions/StringExpression.java +++ b/driver-core/src/main/com/mongodb/client/model/expressions/StringExpression.java @@ -25,34 +25,142 @@ */ public interface StringExpression extends Expression { + /** + * Converts {@code this} string to lowercase. + * + * @return the resulting value. + */ StringExpression toLower(); + /** + * Converts {@code this} string to uppercase. + * + * @return the resulting value. + */ StringExpression toUpper(); - StringExpression concat(StringExpression concat); + /** + * The concatenation of {@code this} string, followed by + * the {@code other} string. + * + * @param other the other value. + * @return the resulting value. + */ + StringExpression concat(StringExpression other); + /** + * The number of UTF-8 code points in {@code this} string. + * + * @return the resulting value. + */ IntegerExpression strLen(); + /** + * The number of UTF-8 encoded bytes in {@code this} string. + * + * @return the resulting value. + */ IntegerExpression strLenBytes(); + /** + * The substring of {@code this} string, from the {@code start} index + * inclusive, and including the specified {@code length}, up to + * the end of the string. + * + *

    Warning: the index position is in UTF-8 code points, not in + * UTF-8 encoded bytes. + * + * @param start the start index in UTF-8 code points. + * @param length the length in UTF-8 code points. + * @return the resulting value. + */ StringExpression substr(IntegerExpression start, IntegerExpression length); + /** + * The substring of {@code this} string, from the {@code start} index + * inclusive, and including the specified {@code length}, up to + * the end of the string. + * + *

    Warning: the index position is in UTF-8 code points, not in + * UTF-8 encoded bytes. + * + * @param start the start index in UTF-8 code points. + * @param length the length in UTF-8 code points. + * @return the resulting value. + */ default StringExpression substr(final int start, final int length) { return this.substr(of(start), of(length)); } + /** + * The substring of {@code this} string, from the {@code start} index + * inclusive, and including the specified {@code length}, up to + * the end of the string. + * + *

    The index position is in UTF-8 encoded bytes, not in + * UTF-8 code points. + * + * @param start the start index in UTF-8 encoded bytes. + * @param length the length in UTF-8 encoded bytes. + * @return the resulting value. + */ StringExpression substrBytes(IntegerExpression start, IntegerExpression length); + /** + * The substring of {@code this} string, from the {@code start} index + * inclusive, and including the specified {@code length}, up to + * the end of the string. + * + *

    The index position is in UTF-8 encoded bytes, not in + * UTF-8 code points. + * + * @param start the start index in UTF-8 encoded bytes. + * @param length the length in UTF-8 encoded bytes. + * @return the resulting value. + */ default StringExpression substrBytes(final int start, final int length) { return this.substrBytes(of(start), of(length)); } + /** + * Converts {@code this} string to an {@linkplain IntegerExpression integer}. + * + *

    This will cause an error if this string does not represent an integer. + * + * @return the resulting value. + */ IntegerExpression parseInteger(); + /** + * Converts {@code this} string to a {@linkplain DateExpression date}. + * + *

    This will cause an error if this string does not represent a valid + * date string (such as "2018-03-20", "2018-03-20T12:00:00Z", or + * "2018-03-20T12:00:00+0500"). + * + * @return the resulting value. + */ DateExpression parseDate(); + /** + * Converts {@code this} string to a {@linkplain DateExpression date}, + * using the specified {@code format}. + * + * @mongodb.driver.manual reference/operator/aggregation/dateToString/#std-label-format-specifiers Format Specifiers + * @param format the format. + * @return the resulting value. + */ DateExpression parseDate(StringExpression format); + /** + * Converts {@code this} string to a {@linkplain DateExpression date}, + * using the specified {@code timezone} and {@code format}. + * + * @mongodb.driver.manual reference/operator/aggregation/dateToString/#std-label-format-specifiers Format Specifiers + * @param format the format. + * @param timezone the UTC Offset or Olson Timezone Identifier. + * @return the resulting value. + */ DateExpression parseDate(StringExpression timezone, StringExpression format); /** From 8de33967c87f70d88f90459575883185fbd11141 Mon Sep 17 00:00:00 2001 From: Maxim Katcharov Date: Mon, 23 Jan 2023 15:25:12 -0700 Subject: [PATCH 19/27] 5.2 annotations (#1070) JAVA-4799 --- .../mongodb/client/model/expressions/ArrayExpression.java | 4 ++++ .../com/mongodb/client/model/expressions/Expression.java | 1 + .../model/expressions/ArrayExpressionsFunctionalTest.java | 7 +++++++ .../expressions/ControlExpressionsFunctionalTest.java | 4 ++++ .../model/expressions/TypeExpressionsFunctionalTest.java | 4 ++++ 5 files changed, 20 insertions(+) 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 index a4eb5493666..3122c090995 100644 --- a/driver-core/src/main/com/mongodb/client/model/expressions/ArrayExpression.java +++ b/driver-core/src/main/com/mongodb/client/model/expressions/ArrayExpression.java @@ -105,6 +105,7 @@ public interface ArrayExpression extends Expression { * The {@linkplain #gt(Expression) largest} value all the values of * {@code this} array, or the {@code other} value if this array is empty. * + * @mongodb.server.release 5.2 * @param other the other value. * @return the resulting value. */ @@ -114,6 +115,7 @@ public interface ArrayExpression extends Expression { * The {@linkplain #lt(Expression) smallest} value all the values of * {@code this} array, or the {@code other} value if this array is empty. * + * @mongodb.server.release 5.2 * @param other the other value. * @return the resulting value. */ @@ -124,6 +126,7 @@ public interface ArrayExpression extends Expression { * {@code this} array, or all elements if the array contains fewer than * {@code n} elements. * + * @mongodb.server.release 5.2 * @param n the number of elements. * @return the resulting value. */ @@ -134,6 +137,7 @@ public interface ArrayExpression extends Expression { * {@code this} array, or all elements if the array contains fewer than * {@code n} elements. * + * @mongodb.server.release 5.2 * @param n the number of elements. * @return the resulting value. */ 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 index 2101527389f..1141d57e243 100644 --- a/driver-core/src/main/com/mongodb/client/model/expressions/Expression.java +++ b/driver-core/src/main/com/mongodb/client/model/expressions/Expression.java @@ -175,6 +175,7 @@ public interface Expression { * {@code this} is an integer, or the {@code other} integer value if * {@code this} is null, or is missing, or is of any other non-integer type. * + * @mongodb.server.release 5.2 * @param other the other value. * @return the resulting value. */ diff --git a/driver-core/src/test/functional/com/mongodb/client/model/expressions/ArrayExpressionsFunctionalTest.java b/driver-core/src/test/functional/com/mongodb/client/model/expressions/ArrayExpressionsFunctionalTest.java index 34fe654e9d5..c5b0956b628 100644 --- a/driver-core/src/test/functional/com/mongodb/client/model/expressions/ArrayExpressionsFunctionalTest.java +++ b/driver-core/src/test/functional/com/mongodb/client/model/expressions/ArrayExpressionsFunctionalTest.java @@ -29,6 +29,7 @@ import java.util.stream.Collectors; import java.util.stream.Stream; +import static com.mongodb.ClusterFixture.serverVersionAtLeast; import static com.mongodb.client.model.expressions.Expressions.of; import static com.mongodb.client.model.expressions.Expressions.ofArray; import static com.mongodb.client.model.expressions.Expressions.ofBooleanArray; @@ -37,6 +38,7 @@ import static com.mongodb.client.model.expressions.Expressions.ofNumberArray; import static com.mongodb.client.model.expressions.Expressions.ofStringArray; import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assumptions.assumeTrue; @SuppressWarnings({"Convert2MethodRef"}) class ArrayExpressionsFunctionalTest extends AbstractExpressionsFunctionalTest { @@ -141,6 +143,7 @@ public void sortTest() { @SuppressWarnings("unchecked") private static ArrayExpression sort(final ArrayExpression array) { + assumeTrue(serverVersionAtLeast(5, 2)); // due to sort MqlExpression mqlArray = (MqlExpression) array; return mqlArray.sort(); } @@ -215,6 +218,7 @@ public void reduceMultiplyTest() { @Test public void reduceMaxTest() { + assumeTrue(serverVersionAtLeast(5, 2)); assertExpression( 3, ofIntegerArray(1, 2, 3).max(of(9)), @@ -227,6 +231,7 @@ public void reduceMaxTest() { @Test public void reduceMinTest() { + assumeTrue(serverVersionAtLeast(5, 2)); assertExpression( 1, ofIntegerArray(1, 2, 3).min(of(9)), @@ -239,6 +244,7 @@ public void reduceMinTest() { @Test public void reduceMaxNTest() { + assumeTrue(serverVersionAtLeast(5, 2)); assertExpression( Arrays.asList(3, 2), ofIntegerArray(3, 1, 2).maxN(of(2))); @@ -253,6 +259,7 @@ public void reduceMaxNTest() { @Test public void reduceMinNTest() { + assumeTrue(serverVersionAtLeast(5, 2)); assertExpression( Arrays.asList(1, 2), ofIntegerArray(3, 1, 2).minN(of(2))); diff --git a/driver-core/src/test/functional/com/mongodb/client/model/expressions/ControlExpressionsFunctionalTest.java b/driver-core/src/test/functional/com/mongodb/client/model/expressions/ControlExpressionsFunctionalTest.java index 256bb8d4cee..af0117b3b86 100644 --- a/driver-core/src/test/functional/com/mongodb/client/model/expressions/ControlExpressionsFunctionalTest.java +++ b/driver-core/src/test/functional/com/mongodb/client/model/expressions/ControlExpressionsFunctionalTest.java @@ -22,11 +22,13 @@ import java.time.Instant; import java.util.function.Function; +import static com.mongodb.ClusterFixture.serverVersionAtLeast; import static com.mongodb.client.model.expressions.Expressions.of; import static com.mongodb.client.model.expressions.Expressions.ofArray; import static com.mongodb.client.model.expressions.Expressions.ofIntegerArray; import static com.mongodb.client.model.expressions.Expressions.ofMap; import static com.mongodb.client.model.expressions.Expressions.ofNull; +import static org.junit.jupiter.api.Assumptions.assumeTrue; class ControlExpressionsFunctionalTest extends AbstractExpressionsFunctionalTest { @@ -107,6 +109,8 @@ public void switchInferenceTest() { @Test public void switchTypesTest() { + // isIntegerOr relies on switch short-circuiting, which only happens after 5.2 + assumeTrue(serverVersionAtLeast(5, 2)); Function label = expr -> expr.switchOn(on -> on .isBoolean(v -> v.asString().concat(of(" - bool"))) // integer should be checked before string diff --git a/driver-core/src/test/functional/com/mongodb/client/model/expressions/TypeExpressionsFunctionalTest.java b/driver-core/src/test/functional/com/mongodb/client/model/expressions/TypeExpressionsFunctionalTest.java index 8a83a8ea014..41ab5e6bba7 100644 --- a/driver-core/src/test/functional/com/mongodb/client/model/expressions/TypeExpressionsFunctionalTest.java +++ b/driver-core/src/test/functional/com/mongodb/client/model/expressions/TypeExpressionsFunctionalTest.java @@ -28,12 +28,14 @@ import java.time.ZonedDateTime; import java.util.Arrays; +import static com.mongodb.ClusterFixture.serverVersionAtLeast; import static com.mongodb.client.model.expressions.Expressions.of; import static com.mongodb.client.model.expressions.Expressions.ofIntegerArray; import static com.mongodb.client.model.expressions.Expressions.ofMap; import static com.mongodb.client.model.expressions.Expressions.ofNull; import static java.time.format.DateTimeFormatter.ISO_LOCAL_DATE_TIME; import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assumptions.assumeTrue; class TypeExpressionsFunctionalTest extends AbstractExpressionsFunctionalTest { // https://www.mongodb.com/docs/manual/reference/operator/aggregation/#type-expression-operators @@ -67,6 +69,8 @@ public void isNumberOrTest() { @Test public void isIntegerOr() { + // isIntegerOr relies on switch short-circuiting, which only happens after 5.2 + assumeTrue(serverVersionAtLeast(5, 2)); assertExpression( 1, of(1).isIntegerOr(of(99)), From 568010267ca018fafc45e66b6a86139cedebae12 Mon Sep 17 00:00:00 2001 From: Maxim Katcharov Date: Mon, 23 Jan 2023 15:37:18 -0700 Subject: [PATCH 20/27] 5.0 annotations (#1070) JAVA-4799 --- .../model/expressions/DocumentExpression.java | 28 +++++++++++++++++++ .../model/expressions/EntryExpression.java | 5 ++++ .../DocumentExpressionsFunctionalTest.java | 8 ++++++ .../MapExpressionsFunctionalTest.java | 22 ++++++++++----- .../InContextExpressionsFunctionalTest.java | 8 ++++++ 5 files changed, 64 insertions(+), 7 deletions(-) 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 index 649d17548d2..9aa123f6b89 100644 --- a/driver-core/src/main/com/mongodb/client/model/expressions/DocumentExpression.java +++ b/driver-core/src/main/com/mongodb/client/model/expressions/DocumentExpression.java @@ -37,6 +37,7 @@ public interface DocumentExpression extends Expression { * True if {@code this} document has a field with the provided * {@code fieldName}. * + * @mongodb.server.release 5.0 * @param fieldName the name of the field. * @return the resulting value. */ @@ -52,6 +53,7 @@ public interface DocumentExpression extends Expression { * of those values correspond to the types of ensuing {@code get...} * invocations, since this API has no way of verifying this correspondence. * + * @mongodb.server.release 5.0 * @param fieldName the name of the field. * @param value the value. * @return the resulting document. @@ -69,6 +71,7 @@ public interface DocumentExpression extends Expression { * of those values correspond to the types of ensuing {@code get...} * invocations, since this API has no way of verifying this correspondence. * + * @mongodb.server.release 5.0 * @param fieldName the name of the field. * @return the resulting document. */ @@ -87,6 +90,7 @@ public interface DocumentExpression extends Expression { * then a super-type encompassing all types must be chosen, and * if necessary the elements should be individually type-checked when used. * + * @mongodb.server.release 5.0 * @param fieldName the name of the field. * @return the resulting value. */ @@ -102,6 +106,7 @@ public interface DocumentExpression extends Expression { * unchecked assertion that the field is present and * the field value is of the specified type. * + * @mongodb.server.release 5.0 * @param fieldName the name of the field. * @return the resulting value. */ @@ -114,6 +119,7 @@ public interface DocumentExpression extends Expression { * or the {@code other} value if the field is not a boolean * or if the document {@linkplain #has} no such field. * + * @mongodb.server.release 5.0 * @param fieldName the name of the field. * @param other the other value. * @return the resulting value. @@ -126,6 +132,7 @@ public interface DocumentExpression extends Expression { * or the {@code other} value if the field is not a boolean * or if the document {@linkplain #has} no such field. * + * @mongodb.server.release 5.0 * @param fieldName the name of the field. * @param other the other value. * @return the resulting value. @@ -143,6 +150,7 @@ default BooleanExpression getBoolean(final String fieldName, final boolean other * unchecked assertion that the field is present and * the field value is of the specified type. * + * @mongodb.server.release 5.0 * @param fieldName the name of the field. * @return the resulting value. */ @@ -155,6 +163,7 @@ default BooleanExpression getBoolean(final String fieldName, final boolean other * or the {@code other} value if the field is not a number * or if the document {@linkplain #has} no such field. * + * @mongodb.server.release 5.0 * @param fieldName the name of the field. * @param other the other value. * @return the resulting value. @@ -167,6 +176,7 @@ default BooleanExpression getBoolean(final String fieldName, final boolean other * or the {@code other} value if the field is not a number * or if the document {@linkplain #has} no such field. * + * @mongodb.server.release 5.0 * @param fieldName the name of the field. * @param other the other value. * @return the resulting value. @@ -184,6 +194,7 @@ default NumberExpression getNumber(final String fieldName, final Number other) { * unchecked assertion that the field is present and * the field value is of the specified type. * + * @mongodb.server.release 5.0 * @param fieldName the name of the field. * @return the resulting value. */ @@ -196,6 +207,7 @@ default NumberExpression getNumber(final String fieldName, final Number other) { * or the {@code other} value if the field is not an integer * or if the document {@linkplain #has} no such field. * + * @mongodb.server.release 5.0 * @param fieldName the name of the field. * @param other the other value. * @return the resulting value. @@ -208,6 +220,7 @@ default NumberExpression getNumber(final String fieldName, final Number other) { * or the {@code other} value if the field is not an integer * or if the document {@linkplain #has} no such field. * + * @mongodb.server.release 5.0 * @param fieldName the name of the field. * @param other the other value. * @return the resulting value. @@ -222,6 +235,7 @@ default IntegerExpression getInteger(final String fieldName, final int other) { * or the {@code other} value if the field is not an integer * or if the document {@linkplain #has} no such field. * + * @mongodb.server.release 5.0 * @param fieldName the name of the field. * @param other the other value. * @return the resulting value. @@ -239,6 +253,7 @@ default IntegerExpression getInteger(final String fieldName, final long other) { * unchecked assertion that the field is present and * the field value is of the specified type. * + * @mongodb.server.release 5.0 * @param fieldName the name of the field. * @return the resulting value. */ @@ -251,6 +266,7 @@ default IntegerExpression getInteger(final String fieldName, final long other) { * or the {@code other} value if the field is not a string * or if the document {@linkplain #has} no such field. * + * @mongodb.server.release 5.0 * @param fieldName the name of the field. * @param other the other value. * @return the resulting value. @@ -263,6 +279,7 @@ default IntegerExpression getInteger(final String fieldName, final long other) { * or the {@code other} value if the field is not a string * or if the document {@linkplain #has} no such field. * + * @mongodb.server.release 5.0 * @param fieldName the name of the field. * @param other the other value. * @return the resulting value. @@ -280,6 +297,7 @@ default StringExpression getString(final String fieldName, final String other) { * unchecked assertion that the field is present and * the field value is of the specified type. * + * @mongodb.server.release 5.0 * @param fieldName the name of the field. * @return the resulting value. */ @@ -292,6 +310,7 @@ default StringExpression getString(final String fieldName, final String other) { * or the {@code other} value if the field is not a date * or if the document {@linkplain #has} no such field. * + * @mongodb.server.release 5.0 * @param fieldName the name of the field. * @param other the other value. * @return the resulting value. @@ -304,6 +323,7 @@ default StringExpression getString(final String fieldName, final String other) { * or the {@code other} value if the field is not a date * or if the document {@linkplain #has} no such field. * + * @mongodb.server.release 5.0 * @param fieldName the name of the field. * @param other the other value. * @return the resulting value. @@ -321,6 +341,7 @@ default DateExpression getDate(final String fieldName, final Instant other) { * unchecked assertion that the field is present and * the field value is of the specified type. * + * @mongodb.server.release 5.0 * @param fieldName the name of the field. * @return the resulting value. */ @@ -336,6 +357,7 @@ default DateExpression getDate(final String fieldName, final Instant other) { *

    Note: Any field considered to be a document by this API * will also be considered a map, and vice-versa. * + * @mongodb.server.release 5.0 * @param fieldName the name of the field. * @param other the other value. * @return the resulting value. @@ -351,6 +373,7 @@ default DateExpression getDate(final String fieldName, final Instant other) { *

    Note: Any field considered to be a document by this API * will also be considered a map, and vice-versa. * + * @mongodb.server.release 5.0 * @param fieldName the name of the field. * @param other the other value. * @return the resulting value. @@ -368,6 +391,7 @@ default DocumentExpression getDocument(final String fieldName, final Bson other) * unchecked assertion that the field is present and * the field value is of the specified type. * + * @mongodb.server.release 5.0 * @param fieldName the name of the field. * @return the resulting value. * @param the type. @@ -388,6 +412,7 @@ default DocumentExpression getDocument(final String fieldName, final Bson other) * enforced by the API. The use of this method is an * unchecked assertion that the type argument is correct. * + * @mongodb.server.release 5.0 * @param fieldName the name of the field. * @param other the other value. * @return the resulting value. @@ -409,6 +434,7 @@ default DocumentExpression getDocument(final String fieldName, final Bson other) * enforced by the API. The use of this method is an * unchecked assertion that the type argument is correct. * + * @mongodb.server.release 5.0 * @param fieldName the name of the field. * @param other the other value. * @return the resulting value. @@ -428,6 +454,7 @@ default MapExpression getMap(final String fieldName, f * unchecked assertion that the field is present and * the field value is of the specified type. * + * @mongodb.server.release 5.0 * @param fieldName the name of the field. * @return the resulting value. * @param the type. @@ -445,6 +472,7 @@ default MapExpression getMap(final String fieldName, f * enforced by the API. The use of this method is an * unchecked assertion that the type argument is correct. * + * @mongodb.server.release 5.0 * @param fieldName the name of the field. * @param other the other value. * @return the resulting value. diff --git a/driver-core/src/main/com/mongodb/client/model/expressions/EntryExpression.java b/driver-core/src/main/com/mongodb/client/model/expressions/EntryExpression.java index 822d8329952..b7b5a545bf0 100644 --- a/driver-core/src/main/com/mongodb/client/model/expressions/EntryExpression.java +++ b/driver-core/src/main/com/mongodb/client/model/expressions/EntryExpression.java @@ -36,6 +36,7 @@ public interface EntryExpression extends Expression { /** * The key of {@code this} entry. * + * @mongodb.server.release 5.0 * @return the key. */ StringExpression getKey(); @@ -43,6 +44,7 @@ public interface EntryExpression extends Expression { /** * The value of {@code this} entry. * + * @mongodb.server.release 5.0 * @return the value. */ T getValue(); @@ -51,6 +53,7 @@ public interface EntryExpression extends Expression { * An entry with the same key as {@code this} entry, and the * specified {@code value}. * + * @mongodb.server.release 5.0 * @param value the value. * @return the resulting entry. */ @@ -60,6 +63,7 @@ public interface EntryExpression extends Expression { * An entry with the same value as {@code this} entry, and the * specified {@code key}. * + * @mongodb.server.release 5.0 * @param key the key. * @return the resulting entry. */ @@ -69,6 +73,7 @@ public interface EntryExpression extends Expression { * An entry with the same value as {@code this} entry, and the * specified {@code key}. * + * @mongodb.server.release 5.0 * @param key the key. * @return the resulting entry. */ diff --git a/driver-core/src/test/functional/com/mongodb/client/model/expressions/DocumentExpressionsFunctionalTest.java b/driver-core/src/test/functional/com/mongodb/client/model/expressions/DocumentExpressionsFunctionalTest.java index 894d078c4df..ddd7823d7f8 100644 --- a/driver-core/src/test/functional/com/mongodb/client/model/expressions/DocumentExpressionsFunctionalTest.java +++ b/driver-core/src/test/functional/com/mongodb/client/model/expressions/DocumentExpressionsFunctionalTest.java @@ -25,11 +25,13 @@ import java.time.Instant; import java.util.Arrays; +import static com.mongodb.ClusterFixture.serverVersionAtLeast; import static com.mongodb.client.model.expressions.Expressions.of; import static com.mongodb.client.model.expressions.Expressions.ofIntegerArray; import static com.mongodb.client.model.expressions.Expressions.ofMap; import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assumptions.assumeTrue; @SuppressWarnings("ConstantConditions") class DocumentExpressionsFunctionalTest extends AbstractExpressionsFunctionalTest { @@ -69,6 +71,7 @@ public void literalsTest() { @Test public void getFieldTest() { + assumeTrue(serverVersionAtLeast(5, 0)); // get/setField // https://www.mongodb.com/docs/manual/reference/operator/aggregation/getField/ (100) // these count as assertions by the user that the value is of the correct type @@ -112,6 +115,7 @@ public void getFieldTest() { @Test public void getFieldOrTest() { + assumeTrue(serverVersionAtLeast(5, 0)); // get/setField // convenience assertExpression(true, ofDoc("{a: true}").getBoolean("a", false)); assertExpression(1.0, ofDoc("{a: 1.0}").getNumber("a", 99)); @@ -160,6 +164,7 @@ public void getFieldOrTest() { @Test public void getFieldMissingTest() { + assumeTrue(serverVersionAtLeast(5, 0)); // get/setField // missing fields assertExpression( BsonDocument.parse("{'a': 1}"), @@ -182,6 +187,7 @@ public void getFieldMissingTest() { @Test public void setFieldTest() { + assumeTrue(serverVersionAtLeast(5, 0)); // get/setField // https://www.mongodb.com/docs/manual/reference/operator/aggregation/setField/ // Placing a field based on a literal: assertExpression( @@ -231,6 +237,7 @@ public void setFieldTest() { @Test public void unsetFieldTest() { + assumeTrue(serverVersionAtLeast(5, 0)); // get/setField (unset) // https://www.mongodb.com/docs/manual/reference/operator/aggregation/unsetField/ assertExpression( BsonDocument.parse("{}"), // map.remove("a") @@ -265,6 +272,7 @@ public void asMapTest() { @Test public void hasTest() { + assumeTrue(serverVersionAtLeast(5, 0)); // get/setField DocumentExpression d = ofDoc("{a: 1}"); assertExpression( true, diff --git a/driver-core/src/test/functional/com/mongodb/client/model/expressions/MapExpressionsFunctionalTest.java b/driver-core/src/test/functional/com/mongodb/client/model/expressions/MapExpressionsFunctionalTest.java index 749e0bdb229..6be284cf2d8 100644 --- a/driver-core/src/test/functional/com/mongodb/client/model/expressions/MapExpressionsFunctionalTest.java +++ b/driver-core/src/test/functional/com/mongodb/client/model/expressions/MapExpressionsFunctionalTest.java @@ -22,12 +22,14 @@ import java.util.Arrays; +import static com.mongodb.ClusterFixture.serverVersionAtLeast; import static com.mongodb.client.model.expressions.Expressions.of; import static com.mongodb.client.model.expressions.Expressions.ofArray; import static com.mongodb.client.model.expressions.Expressions.ofEntry; import static com.mongodb.client.model.expressions.Expressions.ofMap; import static com.mongodb.client.model.expressions.Expressions.ofStringArray; import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assumptions.assumeTrue; class MapExpressionsFunctionalTest extends AbstractExpressionsFunctionalTest { @@ -38,23 +40,25 @@ class MapExpressionsFunctionalTest extends AbstractExpressionsFunctionalTest { @Test public void literalsTest() { - // map + // entry assertExpression( - Document.parse("{key: 123}"), - mapKey123, - "{'$setField': {'field': 'key', 'input': {'$literal': {}}, 'value': 123}}"); + Document.parse("{k: 'keyA', v: 1}"), + ofEntry(of("keyA"), of(1))); + assumeTrue(serverVersionAtLeast(5, 0)); // get/setField (unset) + // map assertExpression( Document.parse("{keyA: 1, keyB: 2}"), ofMap(Document.parse("{keyA: 1, keyB: 2}")), "{'$literal': {'keyA': 1, 'keyB': 2}}"); - // entry assertExpression( - Document.parse("{k: 'keyA', v: 1}"), - ofEntry(of("keyA"), of(1))); + Document.parse("{key: 123}"), + mapKey123, + "{'$setField': {'field': 'key', 'input': {'$literal': {}}, 'value': 123}}"); } @Test public void getSetMapTest() { + assumeTrue(serverVersionAtLeast(5, 0)); // get/setField // get assertExpression( 123, @@ -78,6 +82,7 @@ public void getSetMapTest() { @Test public void hasTest() { + assumeTrue(serverVersionAtLeast(5, 0)); // get/setField (unset) MapExpression e = ofMap(BsonDocument.parse("{key: 1}")); assertExpression( true, @@ -90,6 +95,7 @@ public void hasTest() { @Test public void getSetEntryTest() { + assumeTrue(serverVersionAtLeast(5, 0)); // get/setField EntryExpression entryA1 = ofEntry(of("keyA"), of(1)); assertExpression( Document.parse("{k: 'keyA', 'v': 33}"), @@ -110,6 +116,7 @@ public void buildMapTest() { ofArray(ofEntry(of("keyA"), of(1))).asMap(v -> v), "{'$arrayToObject': [{'$map': {'input': [{'k': 'keyA', 'v': 1}], 'in': '$$this'}}]}"); + assumeTrue(serverVersionAtLeast(5, 0)); // get/setField assertExpression( Document.parse("{'keyA': 55}"), ofArray(ofEntry(of("keyA"), of(1))).asMap(v -> v.setValue(of(55))), @@ -142,6 +149,7 @@ public void buildMapTest() { @Test public void entrySetTest() { + assumeTrue(serverVersionAtLeast(5, 0)); // get/setField // https://www.mongodb.com/docs/manual/reference/operator/aggregation/objectToArray/ (23) assertExpression( Arrays.asList(Document.parse("{'k': 'k1', 'v': 1}")), diff --git a/driver-sync/src/test/functional/com/mongodb/client/model/expressions/InContextExpressionsFunctionalTest.java b/driver-sync/src/test/functional/com/mongodb/client/model/expressions/InContextExpressionsFunctionalTest.java index d9ca29b07d1..f29e4b5307b 100644 --- a/driver-sync/src/test/functional/com/mongodb/client/model/expressions/InContextExpressionsFunctionalTest.java +++ b/driver-sync/src/test/functional/com/mongodb/client/model/expressions/InContextExpressionsFunctionalTest.java @@ -33,6 +33,7 @@ import java.util.Arrays; import java.util.List; +import static com.mongodb.ClusterFixture.serverVersionAtLeast; import static com.mongodb.client.model.Accumulators.sum; import static com.mongodb.client.model.Aggregates.match; import static com.mongodb.client.model.Aggregates.project; @@ -46,6 +47,7 @@ import static com.mongodb.client.model.expressions.Expressions.of; import static com.mongodb.client.model.expressions.Expressions.ofArray; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assumptions.assumeTrue; class InContextExpressionsFunctionalTest extends AbstractExpressionsFunctionalTest { @@ -77,6 +79,7 @@ private List aggregate(final Bson... stages) { @Test public void findTest() { + assumeTrue(serverVersionAtLeast(5, 0)); // get/setField col.insertMany(Arrays.asList( Document.parse("{_id: 1, x: 0, y: 2}"), Document.parse("{_id: 2, x: 0, y: 3}"), @@ -94,6 +97,7 @@ public void findTest() { @Test public void matchTest() { + assumeTrue(serverVersionAtLeast(5, 0)); // get/setField col.insertMany(Arrays.asList( Document.parse("{_id: 1, x: 0, y: 2}"), Document.parse("{_id: 2, x: 0, y: 3}"), @@ -109,6 +113,7 @@ public void matchTest() { @Test public void currentAsMapMatchTest() { + assumeTrue(serverVersionAtLeast(5, 0)); // get/setField col.insertMany(Arrays.asList( Document.parse("{_id: 1, x: 0, y: 2}"), Document.parse("{_id: 2, x: 0, y: 3}"), @@ -127,6 +132,7 @@ public void currentAsMapMatchTest() { @Test public void projectTest() { + assumeTrue(serverVersionAtLeast(5, 0)); // get/setField col.insertMany(Arrays.asList( Document.parse("{_id: 1, x: 0, y: 2}"))); @@ -153,6 +159,7 @@ public void projectTest() { @Test public void projectTest2() { + assumeTrue(serverVersionAtLeast(5, 0)); // get/setField col.insertMany(Arrays.asList(Document.parse("{_id: 0, x: 1}"))); // new, nestedArray @@ -177,6 +184,7 @@ public void projectTest2() { @Test public void groupTest() { + assumeTrue(serverVersionAtLeast(5, 0)); // get/setField col.insertMany(Arrays.asList( Document.parse("{t: 0, a: 1}"), Document.parse("{t: 0, a: 2}"), From b80bfcd98d0a63a2872a7462ccbc0594bef3f8ab Mon Sep 17 00:00:00 2001 From: Maxim Katcharov Date: Mon, 23 Jan 2023 16:10:53 -0700 Subject: [PATCH 21/27] 4.4 annotations (#1070) JAVA-4799 --- .../model/expressions/ArrayExpression.java | 2 + .../client/model/expressions/Branches.java | 2 + .../expressions/BranchesIntermediary.java | 2 + .../client/model/expressions/Expression.java | 1 + .../ArrayExpressionsFunctionalTest.java | 15 ++++-- .../ControlExpressionsFunctionalTest.java | 50 +++++++++++-------- .../TypeExpressionsFunctionalTest.java | 1 + 7 files changed, 48 insertions(+), 25 deletions(-) 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 index 3122c090995..1159129927e 100644 --- a/driver-core/src/main/com/mongodb/client/model/expressions/ArrayExpression.java +++ b/driver-core/src/main/com/mongodb/client/model/expressions/ArrayExpression.java @@ -247,6 +247,7 @@ default T elementAt(final int i) { * the array is not empty. * If the array is empty then the behaviour of the API is not defined. * + * @mongodb.server.release 4.4 * @return the resulting value. */ @MqlUnchecked(PRESENT) @@ -259,6 +260,7 @@ default T elementAt(final int i) { * the array is not empty. * If the array is empty then the behaviour of the API is not defined. * + * @mongodb.server.release 4.4 * @return the resulting value. */ T last(); diff --git a/driver-core/src/main/com/mongodb/client/model/expressions/Branches.java b/driver-core/src/main/com/mongodb/client/model/expressions/Branches.java index 12e5d5561e6..ef88226bc8f 100644 --- a/driver-core/src/main/com/mongodb/client/model/expressions/Branches.java +++ b/driver-core/src/main/com/mongodb/client/model/expressions/Branches.java @@ -127,6 +127,7 @@ public BranchesIntermediary isBoolean(final Functio * {@linkplain Expression#isBooleanOr(BooleanExpression) being a boolean} * produces a value specified by the {@code mapping}. * + * @mongodb.server.release 4.4 * @param mapping the mapping. * @return the appended sequence of checks. * @param the type of the produced value. @@ -140,6 +141,7 @@ public BranchesIntermediary isNumber(final Function * {@linkplain Expression#isIntegerOr(IntegerExpression) being an integer} * produces a value specified by the {@code mapping}. * + * @mongodb.server.release 4.4 * @param mapping the mapping. * @return the appended sequence of checks. * @param the type of the produced value. diff --git a/driver-core/src/main/com/mongodb/client/model/expressions/BranchesIntermediary.java b/driver-core/src/main/com/mongodb/client/model/expressions/BranchesIntermediary.java index 3606aa520ff..491afb53b5d 100644 --- a/driver-core/src/main/com/mongodb/client/model/expressions/BranchesIntermediary.java +++ b/driver-core/src/main/com/mongodb/client/model/expressions/BranchesIntermediary.java @@ -116,6 +116,7 @@ public BranchesIntermediary isBoolean(final Function isNumber(final Function assertExpression( MISSING, array123.elementAt(of(Long.MAX_VALUE)))); + + // 0.0 is a valid integer value + assumeTrue(serverVersionAtLeast(4, 4)); // isNumber + assertExpression( + Arrays.asList(1, 2, 3).get(0), + array123.elementAt(of(0.0).isIntegerOr(of(-1)))); } @Test public void firstTest() { + assumeTrue(serverVersionAtLeast(4, 4)); // https://www.mongodb.com/docs/manual/reference/operator/aggregation/first/ // https://www.mongodb.com/docs/manual/reference/operator/aggregation/first-array-element/ assertExpression( @@ -386,6 +390,7 @@ public void firstTest() { @Test public void lastTest() { + assumeTrue(serverVersionAtLeast(4, 4)); // https://www.mongodb.com/docs/manual/reference/operator/aggregation/last-array-element/ assertExpression( new LinkedList<>(Arrays.asList(1, 2, 3)).getLast(), diff --git a/driver-core/src/test/functional/com/mongodb/client/model/expressions/ControlExpressionsFunctionalTest.java b/driver-core/src/test/functional/com/mongodb/client/model/expressions/ControlExpressionsFunctionalTest.java index af0117b3b86..2e03570fb72 100644 --- a/driver-core/src/test/functional/com/mongodb/client/model/expressions/ControlExpressionsFunctionalTest.java +++ b/driver-core/src/test/functional/com/mongodb/client/model/expressions/ControlExpressionsFunctionalTest.java @@ -71,6 +71,7 @@ public void passToTest() { @Test public void switchTest() { + assumeTrue(serverVersionAtLeast(4, 4)); // isNumber // https://www.mongodb.com/docs/manual/reference/operator/aggregation/switch/ assertExpression("a", of(0).switchOn(on -> on.is(v -> v.eq(of(0)), v -> of("a")))); assertExpression("a", of(0).switchOn(on -> on.isNumber(v -> of("a")))); @@ -185,13 +186,6 @@ public void switchTestInitial() { assertExpression("A", of(true).switchOn(on -> on.isBoolean(v -> of("A"))), "{'$switch': {'branches': [{'case': {'$eq': [{'$type': [true]}, 'bool']}, 'then': 'A'}]}}"); - assertExpression("A", - of(1).switchOn(on -> on.isNumber(v -> of("A"))), - "{'$switch': {'branches': [{'case': {'$isNumber': [1]}, 'then': 'A'}]}}"); - assertExpression("A", - of(1).switchOn(on -> on.isInteger(v -> of("A"))), - "{'$switch': {'branches': [{'case': {'$switch': {'branches': [{'case': {'$isNumber': [1]}," - + "'then': {'$eq': [{'$round': 1}, 1]}}], 'default': false}}, 'then': 'A'}]}}"); assertExpression("A", of("x").switchOn(on -> on.isString(v -> of("A"))), "{'$switch': {'branches': [{'case': {'$eq': [{'$type': ['x']}, 'string']}, 'then': 'A'}]}}"); @@ -215,6 +209,35 @@ public void switchTestInitial() { "{'$switch': {'branches': [{'case': {'$eq': [null, null]}, 'then': 'A'}]}}"); } + @Test + public void switchTestInitialVersion44() { + assumeTrue(serverVersionAtLeast(4, 4)); + assertExpression("A", + of(1).switchOn(on -> on.isNumber(v -> of("A"))), + "{'$switch': {'branches': [{'case': {'$isNumber': [1]}, 'then': 'A'}]}}"); + assertExpression("A", + of(1).switchOn(on -> on.isInteger(v -> of("A"))), + "{'$switch': {'branches': [{'case': {'$switch': {'branches': [{'case': {'$isNumber': [1]}," + + "'then': {'$eq': [{'$round': 1}, 1]}}], 'default': false}}, 'then': 'A'}]}}"); + } + @Test + public void switchTestPartialVersion44() { + assumeTrue(serverVersionAtLeast(4, 4)); + assertExpression("A", + of(1).switchOn(on -> on.isNull(v -> of("X")).isNumber(v -> of("A"))), + "{'$switch': {'branches': [{'case': {'$eq': [1, null]}, 'then': 'X'}, " + + "{'case': {'$isNumber': [1]}, 'then': 'A'}]}}"); + assertExpression("A", + of(1).switchOn(on -> on.isNull(v -> of("X")).isInteger(v -> of("A"))), + "{'$switch': {'branches': [{'case': {'$eq': [1, null]}, 'then': 'X'}, {'case': " + + "{'$switch': {'branches': [{'case': {'$isNumber': [1]}, " + + "'then': {'$eq': [{'$round': 1}, 1]}}], 'default': false}}, 'then': 'A'}]}}"); + assertExpression("A", + ofNull().switchOn(on -> on.isNumber(v -> of("X")).isNull(v -> of("A"))), + "{'$switch': {'branches': [{'case': {'$isNumber': [null]}, 'then': 'X'}, " + + "{'case': {'$eq': [null, null]}, 'then': 'A'}]}}"); + } + @Test public void switchTestPartial() { assertExpression("A", @@ -243,15 +266,6 @@ public void switchTestPartial() { of(true).switchOn(on -> on.isNull(v -> of("X")).isBoolean(v -> of("A"))), "{'$switch': {'branches': [{'case': {'$eq': [true, null]}, 'then': 'X'}, " + "{'case': {'$eq': [{'$type': [true]}, 'bool']}, 'then': 'A'}]}}"); - assertExpression("A", - of(1).switchOn(on -> on.isNull(v -> of("X")).isNumber(v -> of("A"))), - "{'$switch': {'branches': [{'case': {'$eq': [1, null]}, 'then': 'X'}, " - + "{'case': {'$isNumber': [1]}, 'then': 'A'}]}}"); - assertExpression("A", - of(1).switchOn(on -> on.isNull(v -> of("X")).isInteger(v -> of("A"))), - "{'$switch': {'branches': [{'case': {'$eq': [1, null]}, 'then': 'X'}, {'case': " - + "{'$switch': {'branches': [{'case': {'$isNumber': [1]}, " - + "'then': {'$eq': [{'$round': 1}, 1]}}], 'default': false}}, 'then': 'A'}]}}"); assertExpression("A", of("x").switchOn(on -> on.isNull(v -> of("X")).isString(v -> of("A"))), "{'$switch': {'branches': [{'case': {'$eq': ['x', null]}, 'then': 'X'}, " @@ -274,9 +288,5 @@ public void switchTestPartial() { ofMap(Document.parse("{}")).switchOn(on -> on.isNull(v -> of("X")).isMap(v -> of("A"))), "{'$switch': {'branches': [{'case': {'$eq': [{'$literal': {}}, null]}, 'then': 'X'}, " + "{'case': {'$eq': [{'$type': [{'$literal': {}}]}, 'object']}, 'then': 'A'}]}}"); - assertExpression("A", - ofNull().switchOn(on -> on.isNumber(v -> of("X")).isNull(v -> of("A"))), - "{'$switch': {'branches': [{'case': {'$isNumber': [null]}, 'then': 'X'}, " - + "{'case': {'$eq': [null, null]}, 'then': 'A'}]}}"); } } diff --git a/driver-core/src/test/functional/com/mongodb/client/model/expressions/TypeExpressionsFunctionalTest.java b/driver-core/src/test/functional/com/mongodb/client/model/expressions/TypeExpressionsFunctionalTest.java index 41ab5e6bba7..7c7a36ddd39 100644 --- a/driver-core/src/test/functional/com/mongodb/client/model/expressions/TypeExpressionsFunctionalTest.java +++ b/driver-core/src/test/functional/com/mongodb/client/model/expressions/TypeExpressionsFunctionalTest.java @@ -56,6 +56,7 @@ public void isBooleanOrTest() { @Test public void isNumberOrTest() { + assumeTrue(serverVersionAtLeast(4, 4)); // https://www.mongodb.com/docs/manual/reference/operator/aggregation/isNumber/ assertExpression(1, of(1).isNumberOr(of(99)), "{'$cond': [{'$isNumber': [1]}, 1, 99]}"); // other numeric values: From 6f24bea134673f49faa277d34b9e1603cbc575e8 Mon Sep 17 00:00:00 2001 From: Maxim Katcharov Date: Mon, 23 Jan 2023 16:24:42 -0700 Subject: [PATCH 22/27] 4.2 annotations (#1070) JAVA-4799 --- .../com/mongodb/client/model/expressions/NumberExpression.java | 1 + .../model/expressions/ArithmeticExpressionsFunctionalTest.java | 3 +++ 2 files changed, 4 insertions(+) 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 index 1756ed02318..d333faf0a3f 100644 --- a/driver-core/src/main/com/mongodb/client/model/expressions/NumberExpression.java +++ b/driver-core/src/main/com/mongodb/client/model/expressions/NumberExpression.java @@ -123,6 +123,7 @@ default NumberExpression subtract(final Number other) { /** * The integer result of rounding {@code this} to the nearest even value. * + * @mongodb.server.release 4.2 * @return the resulting value. */ IntegerExpression round(); diff --git a/driver-core/src/test/functional/com/mongodb/client/model/expressions/ArithmeticExpressionsFunctionalTest.java b/driver-core/src/test/functional/com/mongodb/client/model/expressions/ArithmeticExpressionsFunctionalTest.java index a1e289270d8..f1c61ceb0fc 100644 --- a/driver-core/src/test/functional/com/mongodb/client/model/expressions/ArithmeticExpressionsFunctionalTest.java +++ b/driver-core/src/test/functional/com/mongodb/client/model/expressions/ArithmeticExpressionsFunctionalTest.java @@ -22,11 +22,13 @@ import java.math.BigDecimal; import java.math.RoundingMode; +import static com.mongodb.ClusterFixture.serverVersionAtLeast; import static com.mongodb.client.model.expressions.Expressions.numberToExpression; import static com.mongodb.client.model.expressions.Expressions.of; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assumptions.assumeTrue; @SuppressWarnings("ConstantConditions") class ArithmeticExpressionsFunctionalTest extends AbstractExpressionsFunctionalTest { @@ -225,6 +227,7 @@ public void minTest() { @Test public void roundTest() { + assumeTrue(serverVersionAtLeast(4, 2)); // https://www.mongodb.com/docs/manual/reference/operator/aggregation/round/ IntegerExpression actual = of(5.5).round(); assertExpression( From b1ae65d012b5c2f6e3f1ba44b2aaad431517ea30 Mon Sep 17 00:00:00 2001 From: Maxim Katcharov Date: Mon, 23 Jan 2023 16:34:51 -0700 Subject: [PATCH 23/27] 4.0 annotations (#1070) JAVA-4799 --- .../com/mongodb/client/model/expressions/Expression.java | 1 + .../mongodb/client/model/expressions/IntegerExpression.java | 1 + .../mongodb/client/model/expressions/StringExpression.java | 2 ++ .../model/expressions/TypeExpressionsFunctionalTest.java | 5 +++++ 4 files changed, 9 insertions(+) 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 index 20c28ea8aaa..f50799cde98 100644 --- a/driver-core/src/main/com/mongodb/client/model/expressions/Expression.java +++ b/driver-core/src/main/com/mongodb/client/model/expressions/Expression.java @@ -264,6 +264,7 @@ public interface Expression { * {@linkplain EntryExpression entries}, and the * {@linkplain Expressions#ofNull() null value}. * + * @mongodb.server.release 4.0 * @see StringExpression#parseDate() * @see StringExpression#parseInteger() * @return the resulting value. 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 index 4757e64eab1..d007b3d0dda 100644 --- a/driver-core/src/main/com/mongodb/client/model/expressions/IntegerExpression.java +++ b/driver-core/src/main/com/mongodb/client/model/expressions/IntegerExpression.java @@ -109,6 +109,7 @@ default IntegerExpression subtract(final int other) { * The {@linkplain DateExpression date} corresponding to {@code this} value * when taken to be the number of milliseconds since the Unix epoch. * + * @mongodb.server.release 4.0 * @return the resulting value. */ DateExpression millisecondsToDate(); 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 index 29065cd92b9..7fbe5773b74 100644 --- a/driver-core/src/main/com/mongodb/client/model/expressions/StringExpression.java +++ b/driver-core/src/main/com/mongodb/client/model/expressions/StringExpression.java @@ -127,6 +127,7 @@ default StringExpression substrBytes(final int start, final int length) { * *

    This will cause an error if this string does not represent an integer. * + * @mongodb.server.release 4.0 * @return the resulting value. */ IntegerExpression parseInteger(); @@ -146,6 +147,7 @@ default StringExpression substrBytes(final int start, final int length) { * Converts {@code this} string to a {@linkplain DateExpression date}, * using the specified {@code format}. * + * @mongodb.server.release 4.0 * @mongodb.driver.manual reference/operator/aggregation/dateToString/#std-label-format-specifiers Format Specifiers * @param format the format. * @return the resulting value. diff --git a/driver-core/src/test/functional/com/mongodb/client/model/expressions/TypeExpressionsFunctionalTest.java b/driver-core/src/test/functional/com/mongodb/client/model/expressions/TypeExpressionsFunctionalTest.java index 7c7a36ddd39..9281d439009 100644 --- a/driver-core/src/test/functional/com/mongodb/client/model/expressions/TypeExpressionsFunctionalTest.java +++ b/driver-core/src/test/functional/com/mongodb/client/model/expressions/TypeExpressionsFunctionalTest.java @@ -173,6 +173,7 @@ public void isMapOrTest() { @Test public void asStringTest() { + assumeTrue(serverVersionAtLeast(4, 0)); // https://www.mongodb.com/docs/manual/reference/operator/aggregation/toString/ // asString, since toString conflicts assertExpression("false", of(false).asString(), "{'$toString': [false]}"); @@ -198,6 +199,7 @@ public void asStringTest() { @Test public void dateAsStringTest() { + assumeTrue(serverVersionAtLeast(4, 0)); // https://www.mongodb.com/docs/manual/reference/operator/aggregation/dateToString/ final Instant instant = Instant.parse("2007-12-03T10:15:30.005Z"); DateExpression date = of(instant); @@ -229,6 +231,7 @@ public void dateAsStringTest() { @Test public void parseDateTest() { + assumeTrue(serverVersionAtLeast(4, 0)); // https://www.mongodb.com/docs/manual/reference/operator/aggregation/dateToString/ String dateString = "2007-12-03T10:15:30.005Z"; assertExpression( @@ -253,6 +256,7 @@ public void parseDateTest() { @Test public void parseIntegerTest() { + assumeTrue(serverVersionAtLeast(4, 0)); // https://www.mongodb.com/docs/manual/reference/operator/aggregation/toInt/ // https://www.mongodb.com/docs/manual/reference/operator/aggregation/toLong/ assertExpression( @@ -292,6 +296,7 @@ public void parseIntegerTest() { @Test public void millisecondsToDateTest() { + assumeTrue(serverVersionAtLeast(4, 0)); // https://www.mongodb.com/docs/manual/reference/operator/aggregation/toDate/ assertExpression( Instant.ofEpochMilli(1234), From 678cef87c1def542fc56a9885160a71282aa6c67 Mon Sep 17 00:00:00 2001 From: Maxim Katcharov Date: Mon, 23 Jan 2023 16:43:35 -0700 Subject: [PATCH 24/27] Update and add documentation, add tests, fix minor issues (#1070) Rename extractBsonValue Fix access modifiers Remove excess comments Update docs Fix: behaviour of get Add notNull to API, add notNullApi test Fix docs/annotations, tests Fix docs, annotations, since Fix docs Revert external Add missing MqlUnchecked Fix missing null checks Checkstyle JAVA-4799 --- .../main/com/mongodb/annotations/Sealed.java | 42 +++ .../model/expressions/ArrayExpression.java | 41 ++- .../model/expressions/BooleanExpression.java | 7 + .../client/model/expressions/Branches.java | 36 +- .../expressions/BranchesIntermediary.java | 43 ++- .../model/expressions/BranchesTerminal.java | 3 + .../model/expressions/DateExpression.java | 6 + .../model/expressions/DocumentExpression.java | 132 ++++---- .../model/expressions/EntryExpression.java | 8 +- .../client/model/expressions/Expression.java | 17 +- .../expressions/ExpressionCodecProvider.java | 9 +- .../client/model/expressions/Expressions.java | 21 +- .../model/expressions/IntegerExpression.java | 7 + .../model/expressions/MapExpression.java | 43 ++- .../model/expressions/MqlExpression.java | 312 ++++++++++++------ .../model/expressions/MqlUnchecked.java | 8 +- .../model/expressions/NumberExpression.java | 12 + .../model/expressions/StringExpression.java | 65 +++- .../model/expressions/package-info.java | 5 +- .../ArrayExpressionsFunctionalTest.java | 16 - .../DocumentExpressionsFunctionalTest.java | 12 +- .../MapExpressionsFunctionalTest.java | 12 +- .../model/expressions/NotNullApiTest.java | 162 +++++++++ .../TypeExpressionsFunctionalTest.java | 14 + 24 files changed, 767 insertions(+), 266 deletions(-) create mode 100644 driver-core/src/main/com/mongodb/annotations/Sealed.java create mode 100644 driver-core/src/test/functional/com/mongodb/client/model/expressions/NotNullApiTest.java diff --git a/driver-core/src/main/com/mongodb/annotations/Sealed.java b/driver-core/src/main/com/mongodb/annotations/Sealed.java new file mode 100644 index 00000000000..59a2b2a7473 --- /dev/null +++ b/driver-core/src/main/com/mongodb/annotations/Sealed.java @@ -0,0 +1,42 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * Copyright 2010 The Guava Authors + * Copyright 2011 The Guava Authors + * + * 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.annotations; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Signifies that the annotated class or interface should be treated as sealed: + * it must not be extended or implemented. + * + *

    Using such classes and interfaces is no different from using ordinary + * unannotated classes and interfaces. + * + *

    This annotation does not imply that the API is experimental or + * {@link Beta}, or that the quality or performance of the API is inferior. + */ +@Retention(RetentionPolicy.CLASS) +@Target(ElementType.TYPE) +@Documented +@Sealed +public @interface Sealed { +} 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 index 1159129927e..42785dcebe0 100644 --- a/driver-core/src/main/com/mongodb/client/model/expressions/ArrayExpression.java +++ b/driver-core/src/main/com/mongodb/client/model/expressions/ArrayExpression.java @@ -16,6 +16,9 @@ package com.mongodb.client.model.expressions; +import com.mongodb.annotations.Beta; +import com.mongodb.annotations.Sealed; + import java.util.function.Function; import static com.mongodb.client.model.expressions.Expressions.of; @@ -27,7 +30,10 @@ * certain type. It is also known as a finite mathematical sequence. * * @param the type of the elements + * @since 4.9.0 */ +@Sealed +@Beta(Beta.Reason.CLIENT) public interface ArrayExpression extends Expression { /** @@ -58,7 +64,7 @@ public interface ArrayExpression extends Expression { IntegerExpression size(); /** - * True if any value in {@code this} array satisfies the predicate. + * Whether any value in {@code this} array satisfies the predicate. * * @param predicate the predicate. * @return the resulting value. @@ -66,7 +72,7 @@ public interface ArrayExpression extends Expression { BooleanExpression any(Function predicate); /** - * True if all values in {@code this} array satisfy the predicate. + * Whether all values in {@code this} array satisfy the predicate. * * @param predicate the predicate. * @return the resulting value. @@ -79,7 +85,7 @@ public interface ArrayExpression extends Expression { * *

    The mapper may be used to transform the values of {@code this} array * into {@linkplain NumberExpression numbers}. If no transformation is - * necessary, then the identify function {@code array.sum(v -> v)} should + * necessary, then the identity function {@code array.sum(v -> v)} should * be used. * * @param mapper the mapper function. @@ -93,7 +99,7 @@ public interface ArrayExpression extends Expression { * *

    The mapper may be used to transform the values of {@code this} array * into {@linkplain NumberExpression numbers}. If no transformation is - * necessary, then the identify function {@code array.multiply(v -> v)} + * necessary, then the identity function {@code array.multiply(v -> v)} * should be used. * * @param mapper the mapper function. @@ -150,7 +156,7 @@ public interface ArrayExpression extends Expression { * *

    The mapper may be used to transform the values of {@code this} array * into {@linkplain StringExpression strings}. If no transformation is - * necessary, then the identify function {@code array.join(v -> v)} should + * necessary, then the identity function {@code array.join(v -> v)} should * be used. * * @param mapper the mapper function. @@ -159,13 +165,14 @@ public interface ArrayExpression extends Expression { StringExpression join(Function mapper); /** - * The array-concatenation of all the array values of {@code this} array, + * The {@linkplain #concat(ArrayExpression) array-concatenation} + * of all the array values of {@code this} array, * via the provided {@code mapper}. Returns the empty array if the array * is empty. * *

    The mapper may be used to transform the values of {@code this} array * into {@linkplain ArrayExpression arrays}. If no transformation is - * necessary, then the identify function {@code array.concat(v -> v)} should + * necessary, then the identity function {@code array.concat(v -> v)} should * be used. * * @param mapper the mapper function. @@ -175,13 +182,14 @@ public interface ArrayExpression extends Expression { ArrayExpression concat(Function> mapper); /** - * The set union of all the array values of {@code this} array, + * The {@linkplain #union(ArrayExpression) set-union} + * of all the array values of {@code this} array, * via the provided {@code mapper}. Returns the empty array if the array * is empty. * *

    The mapper may be used to transform the values of {@code this} array * into {@linkplain ArrayExpression arrays}. If no transformation is - * necessary, then the identify function {@code array.union(v -> v)} should + * necessary, then the identity function {@code array.union(v -> v)} should * be used. * * @param mapper the mapper function. @@ -193,12 +201,12 @@ public interface ArrayExpression extends Expression { /** * The {@linkplain MapExpression map} value corresponding to the * {@linkplain EntryExpression entry} values of {@code this} array, - * via the provided {@code mapper}. Returns the empty array if the array + * via the provided {@code mapper}. Returns the empty map if the array * is empty. * *

    The mapper may be used to transform the values of {@code this} array * into {@linkplain EntryExpression entries}. If no transformation is - * necessary, then the identify function {@code array.union(v -> v)} should + * necessary, then the identity function {@code array.union(v -> v)} should * be used. * * @see MapExpression#entrySet() @@ -215,7 +223,7 @@ public interface ArrayExpression extends Expression { *

    Warning: The use of this method is an assertion that * the index {@code i} is in bounds for the array. * If the index is out of bounds for this array, then - * the behaviour of the API is not defined. + * the behaviour of the API is not specified. * * @param i the index. * @return the resulting value. @@ -230,7 +238,7 @@ public interface ArrayExpression extends Expression { *

    Warning: The use of this method is an assertion that * the index {@code i} is in bounds for the array. * If the index is out of bounds for this array, then - * the behaviour of the API is not defined. + * the behaviour of the API is not specified. * * @param i the index. * @return the resulting value. @@ -245,7 +253,7 @@ default T elementAt(final int i) { * *

    Warning: The use of this method is an assertion that * the array is not empty. - * If the array is empty then the behaviour of the API is not defined. + * If the array is empty then the behaviour of the API is not specified. * * @mongodb.server.release 4.4 * @return the resulting value. @@ -258,15 +266,16 @@ default T elementAt(final int i) { * *

    Warning: The use of this method is an assertion that * the array is not empty. - * If the array is empty then the behaviour of the API is not defined. + * If the array is empty then the behaviour of the API is not specified. * * @mongodb.server.release 4.4 * @return the resulting value. */ + @MqlUnchecked(PRESENT) T last(); /** - * True if {@code this} array contains a value that is + * Whether {@code this} array contains a value that is * {@linkplain #eq equal} to the provided {@code value}. * * @param value the value. 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 index 489fecc9a8d..7c7305652a3 100644 --- a/driver-core/src/main/com/mongodb/client/model/expressions/BooleanExpression.java +++ b/driver-core/src/main/com/mongodb/client/model/expressions/BooleanExpression.java @@ -16,12 +16,19 @@ package com.mongodb.client.model.expressions; +import com.mongodb.annotations.Beta; +import com.mongodb.annotations.Sealed; + import java.util.function.Function; /** * A boolean {@linkplain Expression value} in the context of the * MongoDB Query Language (MQL). + * + * @since 4.9.0 */ +@Sealed +@Beta(Beta.Reason.CLIENT) public interface BooleanExpression extends Expression { /** diff --git a/driver-core/src/main/com/mongodb/client/model/expressions/Branches.java b/driver-core/src/main/com/mongodb/client/model/expressions/Branches.java index ef88226bc8f..c0f86572e34 100644 --- a/driver-core/src/main/com/mongodb/client/model/expressions/Branches.java +++ b/driver-core/src/main/com/mongodb/client/model/expressions/Branches.java @@ -16,6 +16,9 @@ package com.mongodb.client.model.expressions; +import com.mongodb.annotations.Beta; +import com.mongodb.assertions.Assertions; + import java.util.ArrayList; import java.util.List; import java.util.function.Function; @@ -28,10 +31,12 @@ * to succeed will produce the value that it specifies. If no check succeeds, * then the operation * {@linkplain BranchesIntermediary#defaults(Function) defaults} to a default - * value, or if none is specified, the operation causes an error. + * value, or if none is specified, the operation will cause an error. * * @param the type of the values that may be checked. + * @since 4.9.0 */ +@Beta(Beta.Reason.CLIENT) public final class Branches { Branches() { @@ -59,6 +64,8 @@ private static MqlExpression mqlEx(final T value) { * @return the appended sequence of checks. */ public BranchesIntermediary is(final Function predicate, final Function mapping) { + Assertions.notNull("predicate", predicate); + Assertions.notNull("mapping", mapping); return with(value -> new SwitchCase<>(predicate.apply(value), mapping.apply(value))); } @@ -74,6 +81,8 @@ public BranchesIntermediary is(final Function BranchesIntermediary eq(final T v, final Function mapping) { + Assertions.notNull("v", v); + Assertions.notNull("mapping", mapping); return is(value -> value.eq(v), mapping); } @@ -89,6 +98,8 @@ public BranchesIntermediary eq(final T v, final Fun * @return the appended sequence of checks. */ public BranchesIntermediary lt(final T v, final Function mapping) { + Assertions.notNull("v", v); + Assertions.notNull("mapping", mapping); return is(value -> value.lt(v), mapping); } @@ -104,6 +115,8 @@ public BranchesIntermediary lt(final T v, final Fun * @return the appended sequence of checks. */ public BranchesIntermediary lte(final T v, final Function mapping) { + Assertions.notNull("v", v); + Assertions.notNull("mapping", mapping); return is(value -> value.lte(v), mapping); } @@ -119,12 +132,13 @@ public BranchesIntermediary lte(final T v, final Fu * @param the type of the produced value. */ public BranchesIntermediary isBoolean(final Function mapping) { + Assertions.notNull("mapping", mapping); return is(v -> mqlEx(v).isBoolean(), v -> mapping.apply((BooleanExpression) v)); } /** * A successful check for - * {@linkplain Expression#isBooleanOr(BooleanExpression) being a boolean} + * {@linkplain Expression#isNumberOr(NumberExpression) being a number} * produces a value specified by the {@code mapping}. * * @mongodb.server.release 4.4 @@ -133,6 +147,7 @@ public BranchesIntermediary isBoolean(final Functio * @param the type of the produced value. */ public BranchesIntermediary isNumber(final Function mapping) { + Assertions.notNull("mapping", mapping); return is(v -> mqlEx(v).isNumber(), v -> mapping.apply((NumberExpression) v)); } @@ -147,6 +162,7 @@ public BranchesIntermediary isNumber(final Function * @param the type of the produced value. */ public BranchesIntermediary isInteger(final Function mapping) { + Assertions.notNull("mapping", mapping); return is(v -> mqlEx(v).isInteger(), v -> mapping.apply((IntegerExpression) v)); } @@ -160,6 +176,7 @@ public BranchesIntermediary isInteger(final Functio * @param the type of the produced value. */ public BranchesIntermediary isString(final Function mapping) { + Assertions.notNull("mapping", mapping); return is(v -> mqlEx(v).isString(), v -> mapping.apply((StringExpression) v)); } @@ -173,6 +190,7 @@ public BranchesIntermediary isString(final Function * @param the type of the produced value. */ public BranchesIntermediary isDate(final Function mapping) { + Assertions.notNull("mapping", mapping); return is(v -> mqlEx(v).isDate(), v -> mapping.apply((DateExpression) v)); } @@ -192,33 +210,33 @@ public BranchesIntermediary isDate(final Function BranchesIntermediary isArray(final Function, ? extends R> mapping) { + Assertions.notNull("mapping", mapping); return is(v -> mqlEx(v).isArray(), v -> mapping.apply((ArrayExpression) v)); } /** * A successful check for * {@linkplain Expression#isDocumentOr(DocumentExpression) being a document} + * (or document-like value, see + * {@link MapExpression} and {@link EntryExpression}) * produces a value specified by the {@code mapping}. * - *

    Note: Any value considered to be a document by this API - * will also be considered a map, and vice-versa. - * * @param mapping the mapping. * @return the appended sequence of checks. * @param the type of the produced value. */ public BranchesIntermediary isDocument(final Function mapping) { + Assertions.notNull("mapping", mapping); return is(v -> mqlEx(v).isDocumentOrMap(), v -> mapping.apply((DocumentExpression) v)); } /** * A successful check for * {@linkplain Expression#isMapOr(MapExpression) being a map} + * (or map-like value, see + * {@link DocumentExpression} and {@link EntryExpression}) * produces a value specified by the {@code mapping}. * - *

    Note: Any value considered to be a map by this API - * will also be considered a document, and vice-versa. - * *

    Warning: The type argument of the map is not * enforced by the API. The use of this method is an * unchecked assertion that the type argument is correct. @@ -230,6 +248,7 @@ public BranchesIntermediary isDocument(final Functi */ @SuppressWarnings("unchecked") public BranchesIntermediary isMap(final Function, ? extends R> mapping) { + Assertions.notNull("mapping", mapping); return is(v -> mqlEx(v).isDocumentOrMap(), v -> mapping.apply((MapExpression) v)); } @@ -243,6 +262,7 @@ public BranchesIntermediary i * @param the type of the produced value. */ public BranchesIntermediary isNull(final Function mapping) { + Assertions.notNull("mapping", mapping); return is(v -> mqlEx(v).isNull(), v -> mapping.apply(v)); } } diff --git a/driver-core/src/main/com/mongodb/client/model/expressions/BranchesIntermediary.java b/driver-core/src/main/com/mongodb/client/model/expressions/BranchesIntermediary.java index 491afb53b5d..7e6fcb8a76c 100644 --- a/driver-core/src/main/com/mongodb/client/model/expressions/BranchesIntermediary.java +++ b/driver-core/src/main/com/mongodb/client/model/expressions/BranchesIntermediary.java @@ -16,16 +16,23 @@ package com.mongodb.client.model.expressions; +import com.mongodb.annotations.Beta; +import com.mongodb.assertions.Assertions; + import java.util.ArrayList; import java.util.List; import java.util.function.Function; +import static com.mongodb.client.model.expressions.MqlUnchecked.Unchecked.TYPE_ARGUMENT; + /** * See {@link Branches}. * * @param the type of the values that may be checked. * @param the type of the value produced. + * @since 4.9.0 */ +@Beta(Beta.Reason.CLIENT) public final class BranchesIntermediary extends BranchesTerminal { BranchesIntermediary(final List>> branches) { super(branches, null); @@ -52,6 +59,8 @@ private static MqlExpression mqlEx(final T value) { * @return the appended sequence of checks. */ public BranchesIntermediary is(final Function predicate, final Function mapping) { + Assertions.notNull("predicate", predicate); + Assertions.notNull("mapping", mapping); return this.with(value -> new SwitchCase<>(predicate.apply(value), mapping.apply(value))); } @@ -66,6 +75,8 @@ public BranchesIntermediary is(final Function eq(final T v, final Function mapping) { + Assertions.notNull("v", v); + Assertions.notNull("mapping", mapping); return is(value -> value.eq(v), mapping); } @@ -80,6 +91,8 @@ public BranchesIntermediary eq(final T v, final Function lt(final T v, final Function mapping) { + Assertions.notNull("v", v); + Assertions.notNull("mapping", mapping); return is(value -> value.lt(v), mapping); } @@ -94,6 +107,8 @@ public BranchesIntermediary lt(final T v, final Function lte(final T v, final Function mapping) { + Assertions.notNull("v", v); + Assertions.notNull("mapping", mapping); return is(value -> value.lte(v), mapping); } @@ -108,12 +123,13 @@ public BranchesIntermediary lte(final T v, final Function isBoolean(final Function mapping) { + Assertions.notNull("mapping", mapping); return is(v -> mqlEx(v).isBoolean(), v -> mapping.apply((BooleanExpression) v)); } /** * A successful check for - * {@linkplain Expression#isBooleanOr(BooleanExpression) being a boolean} + * {@linkplain Expression#isNumberOr(NumberExpression) being a number} * produces a value specified by the {@code mapping}. * * @mongodb.server.release 4.4 @@ -121,6 +137,7 @@ public BranchesIntermediary isBoolean(final Function isNumber(final Function mapping) { + Assertions.notNull("mapping", mapping); return is(v -> mqlEx(v).isNumber(), v -> mapping.apply((NumberExpression) v)); } @@ -134,6 +151,7 @@ public BranchesIntermediary isNumber(final Function isInteger(final Function mapping) { + Assertions.notNull("mapping", mapping); return is(v -> mqlEx(v).isInteger(), v -> mapping.apply((IntegerExpression) v)); } @@ -146,6 +164,7 @@ public BranchesIntermediary isInteger(final Function isString(final Function mapping) { + Assertions.notNull("mapping", mapping); return is(v -> mqlEx(v).isString(), v -> mapping.apply((StringExpression) v)); } @@ -158,6 +177,7 @@ public BranchesIntermediary isString(final Function isDate(final Function mapping) { + Assertions.notNull("mapping", mapping); return is(v -> mqlEx(v).isDate(), v -> mapping.apply((DateExpression) v)); } @@ -172,36 +192,36 @@ public BranchesIntermediary isDate(final Function the type of the array. + * @param the type of the elements of the resulting array. */ @SuppressWarnings("unchecked") - public BranchesIntermediary isArray(final Function, ? extends R> mapping) { + public BranchesIntermediary isArray(final Function, ? extends R> mapping) { + Assertions.notNull("mapping", mapping); return is(v -> mqlEx(v).isArray(), v -> mapping.apply((ArrayExpression) v)); } /** * A successful check for * {@linkplain Expression#isDocumentOr(DocumentExpression) being a document} + * (or document-like value, see + * {@link MapExpression} and {@link EntryExpression}) * produces a value specified by the {@code mapping}. * - *

    Note: Any value considered to be a document by this API - * will also be considered a map, and vice-versa. - * * @param mapping the mapping. * @return the appended sequence of checks. */ public BranchesIntermediary isDocument(final Function mapping) { + Assertions.notNull("mapping", mapping); return is(v -> mqlEx(v).isDocumentOrMap(), v -> mapping.apply((DocumentExpression) v)); } /** * A successful check for * {@linkplain Expression#isMapOr(MapExpression) being a map} + * (or map-like value, see + * {@link DocumentExpression} and {@link EntryExpression}) * produces a value specified by the {@code mapping}. * - *

    Note: Any value considered to be a map by this API - * will also be considered a document, and vice-versa. - * *

    Warning: The type argument of the map is not * enforced by the API. The use of this method is an * unchecked assertion that the type argument is correct. @@ -211,7 +231,8 @@ public BranchesIntermediary isDocument(final Function the type of the array. */ @SuppressWarnings("unchecked") - public BranchesIntermediary isMap(final Function, ? extends R> mapping) { + public BranchesIntermediary isMap(final Function, ? extends R> mapping) { + Assertions.notNull("mapping", mapping); return is(v -> mqlEx(v).isDocumentOrMap(), v -> mapping.apply((MapExpression) v)); } @@ -224,6 +245,7 @@ public BranchesIntermediary isMap(final Function isNull(final Function mapping) { + Assertions.notNull("mapping", mapping); return is(v -> mqlEx(v).isNull(), v -> mapping.apply(v)); } @@ -235,6 +257,7 @@ public BranchesIntermediary isNull(final Function defaults(final Function mapping) { + Assertions.notNull("mapping", mapping); return this.withDefault(value -> mapping.apply(value)); } diff --git a/driver-core/src/main/com/mongodb/client/model/expressions/BranchesTerminal.java b/driver-core/src/main/com/mongodb/client/model/expressions/BranchesTerminal.java index 9170be6b87b..d561f616466 100644 --- a/driver-core/src/main/com/mongodb/client/model/expressions/BranchesTerminal.java +++ b/driver-core/src/main/com/mongodb/client/model/expressions/BranchesTerminal.java @@ -16,6 +16,7 @@ package com.mongodb.client.model.expressions; +import com.mongodb.annotations.Beta; import com.mongodb.lang.Nullable; import java.util.List; @@ -27,7 +28,9 @@ * * @param the type of the values that may be checked. * @param the type of the value produced. + * @since 4.9.0 */ +@Beta(Beta.Reason.CLIENT) public class BranchesTerminal { private final List>> branches; 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 index 3d5c0d2b634..35d8c5906be 100644 --- a/driver-core/src/main/com/mongodb/client/model/expressions/DateExpression.java +++ b/driver-core/src/main/com/mongodb/client/model/expressions/DateExpression.java @@ -16,6 +16,9 @@ package com.mongodb.client.model.expressions; +import com.mongodb.annotations.Beta; +import com.mongodb.annotations.Sealed; + import java.util.function.Function; /** @@ -24,7 +27,10 @@ * milliseconds since the Unix epoch, and does not track the timezone. * * @mongodb.driver.manual reference/operator/aggregation/dateToString/ Format Specifiers, UTC Offset, and Olson Timezone Identifier + * @since 4.9.0 */ +@Sealed +@Beta(Beta.Reason.CLIENT) 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 index 9aa123f6b89..e82db8415ba 100644 --- a/driver-core/src/main/com/mongodb/client/model/expressions/DocumentExpression.java +++ b/driver-core/src/main/com/mongodb/client/model/expressions/DocumentExpression.java @@ -16,6 +16,9 @@ package com.mongodb.client.model.expressions; +import com.mongodb.annotations.Beta; +import com.mongodb.annotations.Sealed; +import com.mongodb.assertions.Assertions; import org.bson.conversions.Bson; import java.time.Instant; @@ -28,14 +31,21 @@ import static com.mongodb.client.model.expressions.MqlUnchecked.Unchecked.TYPE_ARGUMENT; /** - * 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. + * A document {@link Expression value} in the context of the MongoDB Query + * Language (MQL). A document is a finite set of fields, where the field + * name is a string, together with a value of any other + * {@linkplain Expression type in the type hierarchy}. + * No field name is repeated. + * + * @since 4.9.0 */ +@Sealed +@Beta(Beta.Reason.CLIENT) public interface DocumentExpression extends Expression { /** - * True if {@code this} document has a field with the provided - * {@code fieldName}. + * Whether {@code this} document has a field with the provided + * {@code fieldName} (if a field is set to null, it is present). * * @mongodb.server.release 5.0 * @param fieldName the name of the field. @@ -60,17 +70,12 @@ public interface DocumentExpression extends Expression { */ DocumentExpression setField(String fieldName, Expression value); - /** * Returns a document with the same fields as {@code this} document, but - * with the {@code fieldName} field set to the specified {@code value}. + * excluding the field with the specified {@code fieldName}. * *

    This does not affect the original document. * - *

    Warning: Users should take care to assign values, such that the types - * of those values correspond to the types of ensuing {@code get...} - * invocations, since this API has no way of verifying this correspondence. - * * @mongodb.server.release 5.0 * @param fieldName the name of the field. * @return the resulting document. @@ -81,14 +86,8 @@ public interface DocumentExpression extends Expression { * Returns the {@linkplain Expression} value of the field * with the provided {@code fieldName}. * - *

    Use of this method is an assertion that the field exists. - * - *

    Warning: The type of the values of the resulting map are not - * enforced by the API. The specification of a type by the user is an - * unchecked assertion that all map values are of that type. - * If the map contains multiple types (such as both nulls and integers) - * then a super-type encompassing all types must be chosen, and - * if necessary the elements should be individually type-checked when used. + *

    Warning: Use of this method is an assertion that the document + * {@linkplain #has(String) has} the named field. * * @mongodb.server.release 5.0 * @param fieldName the name of the field. @@ -103,7 +102,8 @@ public interface DocumentExpression extends Expression { * *

    Warning: The type and presence of the resulting value is not * enforced by the API. The use of this method is an - * unchecked assertion that the field is present and + * unchecked assertion that the document + * {@linkplain #has(String) has} the named field and * the field value is of the specified type. * * @mongodb.server.release 5.0 @@ -138,6 +138,7 @@ public interface DocumentExpression extends Expression { * @return the resulting value. */ default BooleanExpression getBoolean(final String fieldName, final boolean other) { + Assertions.notNull("fieldName", fieldName); return getBoolean(fieldName, of(other)); } @@ -147,7 +148,8 @@ default BooleanExpression getBoolean(final String fieldName, final boolean other * *

    Warning: The type and presence of the resulting value is not * enforced by the API. The use of this method is an - * unchecked assertion that the field is present and + * unchecked assertion that the document + * {@linkplain #has(String) has} the named field and * the field value is of the specified type. * * @mongodb.server.release 5.0 @@ -182,6 +184,8 @@ default BooleanExpression getBoolean(final String fieldName, final boolean other * @return the resulting value. */ default NumberExpression getNumber(final String fieldName, final Number other) { + Assertions.notNull("fieldName", fieldName); + Assertions.notNull("other", other); return getNumber(fieldName, Expressions.numberToExpression(other)); } @@ -191,7 +195,8 @@ default NumberExpression getNumber(final String fieldName, final Number other) { * *

    Warning: The type and presence of the resulting value is not * enforced by the API. The use of this method is an - * unchecked assertion that the field is present and + * unchecked assertion that the document + * {@linkplain #has(String) has} the named field and * the field value is of the specified type. * * @mongodb.server.release 5.0 @@ -226,6 +231,7 @@ default NumberExpression getNumber(final String fieldName, final Number other) { * @return the resulting value. */ default IntegerExpression getInteger(final String fieldName, final int other) { + Assertions.notNull("fieldName", fieldName); return getInteger(fieldName, of(other)); } @@ -241,6 +247,7 @@ default IntegerExpression getInteger(final String fieldName, final int other) { * @return the resulting value. */ default IntegerExpression getInteger(final String fieldName, final long other) { + Assertions.notNull("fieldName", fieldName); return getInteger(fieldName, of(other)); } @@ -250,7 +257,8 @@ default IntegerExpression getInteger(final String fieldName, final long other) { * *

    Warning: The type and presence of the resulting value is not * enforced by the API. The use of this method is an - * unchecked assertion that the field is present and + * unchecked assertion that the document + * {@linkplain #has(String) has} the named field and * the field value is of the specified type. * * @mongodb.server.release 5.0 @@ -285,6 +293,8 @@ default IntegerExpression getInteger(final String fieldName, final long other) { * @return the resulting value. */ default StringExpression getString(final String fieldName, final String other) { + Assertions.notNull("fieldName", fieldName); + Assertions.notNull("other", other); return getString(fieldName, of(other)); } @@ -294,7 +304,8 @@ default StringExpression getString(final String fieldName, final String other) { * *

    Warning: The type and presence of the resulting value is not * enforced by the API. The use of this method is an - * unchecked assertion that the field is present and + * unchecked assertion that the document + * {@linkplain #has(String) has} the named field and * the field value is of the specified type. * * @mongodb.server.release 5.0 @@ -329,6 +340,8 @@ default StringExpression getString(final String fieldName, final String other) { * @return the resulting value. */ default DateExpression getDate(final String fieldName, final Instant other) { + Assertions.notNull("fieldName", fieldName); + Assertions.notNull("other", other); return getDate(fieldName, of(other)); } @@ -338,7 +351,8 @@ default DateExpression getDate(final String fieldName, final Instant other) { * *

    Warning: The type and presence of the resulting value is not * enforced by the API. The use of this method is an - * unchecked assertion that the field is present and + * unchecked assertion that the document + * {@linkplain #has(String) has} the named field and * the field value is of the specified type. * * @mongodb.server.release 5.0 @@ -351,11 +365,10 @@ default DateExpression getDate(final String fieldName, final Instant other) { /** * Returns the {@linkplain DocumentExpression document} value of the field * with the provided {@code fieldName}, - * or the {@code other} value if the field is not a (child) document - * or if the document {@linkplain #has} no such field. - * - *

    Note: Any field considered to be a document by this API - * will also be considered a map, and vice-versa. + * or the {@code other} value + * if the document {@linkplain #has} no such field, + * or if the specified field is not a (child) document + * (or other {@linkplain Expression#isDocumentOr document-like value}. * * @mongodb.server.release 5.0 * @param fieldName the name of the field. @@ -367,11 +380,10 @@ default DateExpression getDate(final String fieldName, final Instant other) { /** * Returns the {@linkplain DocumentExpression document} value of the field * with the provided {@code fieldName}, - * or the {@code other} value if the field is not a (child) document - * or if the document {@linkplain #has} no such field. - * - *

    Note: Any field considered to be a document by this API - * will also be considered a map, and vice-versa. + * or the {@code other} value + * if the document {@linkplain #has} no such field, + * or if the specified field is not a (child) document + * (or other {@linkplain Expression#isDocumentOr document-like value}. * * @mongodb.server.release 5.0 * @param fieldName the name of the field. @@ -379,6 +391,8 @@ default DateExpression getDate(final String fieldName, final Instant other) { * @return the resulting value. */ default DocumentExpression getDocument(final String fieldName, final Bson other) { + Assertions.notNull("fieldName", fieldName); + Assertions.notNull("other", other); return getDocument(fieldName, of(other)); } @@ -388,25 +402,27 @@ default DocumentExpression getDocument(final String fieldName, final Bson other) * *

    Warning: The type and presence of the resulting value is not * enforced by the API. The use of this method is an - * unchecked assertion that the field is present and - * the field value is of the specified type. + * unchecked assertion that the document + * {@linkplain #has(String) has} the named field, + * and the field value is of the specified raw type, + * and the field value's type has the specified type argument. * * @mongodb.server.release 5.0 * @param fieldName the name of the field. * @return the resulting value. * @param the type. */ - @MqlUnchecked({PRESENT, TYPE, TYPE_ARGUMENT}) - MapExpression getMap(String fieldName); + @MqlUnchecked({PRESENT, TYPE}) + MapExpression<@MqlUnchecked(TYPE_ARGUMENT) T> getMap(String fieldName); + /** * Returns the {@linkplain MapExpression map} value of the field * with the provided {@code fieldName}, - * or the {@code other} value if the field is not a map - * or if the document {@linkplain #has} no such field. - * - *

    Note: Any field considered to be a document by this API - * will also be considered a map, and vice-versa. + * or the {@code other} value + * if the document {@linkplain #has} no such field, + * or if the specified field is not a map + * (or other {@linkplain Expression#isMapOr} map-like value}). * *

    Warning: The type argument of the resulting value is not * enforced by the API. The use of this method is an @@ -418,17 +434,15 @@ default DocumentExpression getDocument(final String fieldName, final Bson other) * @return the resulting value. * @param the type. */ - @MqlUnchecked({TYPE_ARGUMENT}) - MapExpression getMap(String fieldName, MapExpression other); + MapExpression getMap(String fieldName, MapExpression<@MqlUnchecked(TYPE_ARGUMENT) ? extends T> other); /** * Returns the {@linkplain MapExpression map} value of the field * with the provided {@code fieldName}, - * or the {@code other} value if the field is not a map - * or if the document {@linkplain #has} no such field. - * - *

    Note: Any field considered to be a document by this API - * will also be considered a map, and vice-versa. + * or the {@code other} value + * if the document {@linkplain #has} no such field, + * or if the specified field is not a map + * (or other {@linkplain Expression#isMapOr} map-like value}). * *

    Warning: The type argument of the resulting value is not * enforced by the API. The use of this method is an @@ -440,8 +454,9 @@ default DocumentExpression getDocument(final String fieldName, final Bson other) * @return the resulting value. * @param the type. */ - @MqlUnchecked({TYPE_ARGUMENT}) - default MapExpression getMap(final String fieldName, final Bson other) { + default MapExpression<@MqlUnchecked(TYPE_ARGUMENT) T> getMap(final String fieldName, final Bson other) { + Assertions.notNull("fieldName", fieldName); + Assertions.notNull("other", other); return getMap(fieldName, ofMap(other)); } @@ -451,16 +466,18 @@ default MapExpression getMap(final String fieldName, f * *

    Warning: The type and presence of the resulting value is not * enforced by the API. The use of this method is an - * unchecked assertion that the field is present and - * the field value is of the specified type. + * unchecked assertion that the document + * {@linkplain #has(String) has} the named field, + * and the field value is of the specified raw type, + * and the field value's type has the specified type argument. * * @mongodb.server.release 5.0 * @param fieldName the name of the field. * @return the resulting value. * @param the type. */ - @MqlUnchecked({PRESENT, TYPE, TYPE_ARGUMENT}) - ArrayExpression getArray(String fieldName); + @MqlUnchecked({PRESENT, TYPE}) + ArrayExpression<@MqlUnchecked(TYPE_ARGUMENT) T> getArray(String fieldName); /** * Returns the {@linkplain ArrayExpression array} value of the field @@ -478,8 +495,7 @@ default MapExpression getMap(final String fieldName, f * @return the resulting value. * @param the type. */ - @MqlUnchecked({TYPE_ARGUMENT}) - ArrayExpression getArray(String fieldName, ArrayExpression other); + ArrayExpression<@MqlUnchecked(TYPE_ARGUMENT) T> getArray(String fieldName, ArrayExpression other); /** * Returns a document with the same fields as {@code this} document, but diff --git a/driver-core/src/main/com/mongodb/client/model/expressions/EntryExpression.java b/driver-core/src/main/com/mongodb/client/model/expressions/EntryExpression.java index b7b5a545bf0..924b3472221 100644 --- a/driver-core/src/main/com/mongodb/client/model/expressions/EntryExpression.java +++ b/driver-core/src/main/com/mongodb/client/model/expressions/EntryExpression.java @@ -16,6 +16,9 @@ package com.mongodb.client.model.expressions; +import com.mongodb.annotations.Beta; +import com.mongodb.annotations.Sealed; + import static com.mongodb.client.model.expressions.Expressions.of; /** @@ -25,12 +28,15 @@ * {@linkplain Expression value}. Entries are used with * {@linkplain MapExpression maps}. * - * Entries are {@linkplain Expression#isDocumentOr document-like} and + *

    Entries are {@linkplain Expression#isDocumentOr document-like} and * {@linkplain Expression#isMapOr map-like}, unless the method returning the * entry specifies otherwise. * * @param The type of the value + * @since 4.9.0 */ +@Sealed +@Beta(Beta.Reason.CLIENT) public interface EntryExpression 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 index f50799cde98..c397a4906d0 100644 --- a/driver-core/src/main/com/mongodb/client/model/expressions/Expression.java +++ b/driver-core/src/main/com/mongodb/client/model/expressions/Expression.java @@ -16,7 +16,8 @@ package com.mongodb.client.model.expressions; -import com.mongodb.annotations.Evolving; +import com.mongodb.annotations.Beta; +import com.mongodb.annotations.Sealed; import java.util.function.Function; @@ -81,12 +82,14 @@ * context, users are strongly discouraged from relying on behaviour that is not * part of this API). * - *

    Users should treat these interfaces as sealed, and should not create - * implementations. + *

    This API should be treated as sealed: + * it must not be extended or implemented (unless explicitly allowed). * * @see Expressions + * @since 4.9.0 */ -@Evolving +@Sealed +@Beta(Beta.Reason.CLIENT) public interface Expression { /** @@ -222,7 +225,7 @@ public interface Expression { /** * {@code this} value as a {@linkplain DocumentExpression document} if - * {@code this} is a document or document-like value (see + * {@code this} is a document (or document-like value, see * {@link MapExpression} and {@link EntryExpression}) * or the {@code other} document value if {@code this} is null, * or is missing, or is of any other non-document type. @@ -235,7 +238,7 @@ public interface Expression { /** * {@code this} value as a {@linkplain MapExpression map} if - * {@code this} is a map or map-like value (see + * {@code this} is a map (or map-like value, see * {@link DocumentExpression} and {@link EntryExpression}) * or the {@code other} map value if {@code this} is null, * or is missing, or is of any other non-map type. @@ -251,7 +254,7 @@ public interface Expression { * @return the resulting value. * @param the type of the values of the resulting map. */ - MapExpression isMapOr(MapExpression other); + MapExpression isMapOr(MapExpression<@MqlUnchecked(TYPE_ARGUMENT) ? extends T> other); /** * The {@linkplain StringExpression string} representation of {@code this} value. 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 index 60afc3a6874..6ac41e96759 100644 --- a/driver-core/src/main/com/mongodb/client/model/expressions/ExpressionCodecProvider.java +++ b/driver-core/src/main/com/mongodb/client/model/expressions/ExpressionCodecProvider.java @@ -24,19 +24,16 @@ import org.bson.codecs.configuration.CodecRegistry; /** - * Provides Codec instances for MQL expressions. + * Provides Codec instances for the {@link Expression MQL API}. * *

    Responsible for converting values and computations expressed using the - * driver's implementation of the {@link Expression} API into the corresponding + * driver's implementation of the {@link Expression MQL 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. + * @since 4.9.0 */ @Beta(Beta.Reason.CLIENT) @Immutable 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 index bba99d86a47..19b9c0a16ca 100644 --- a/driver-core/src/main/com/mongodb/client/model/expressions/Expressions.java +++ b/driver-core/src/main/com/mongodb/client/model/expressions/Expressions.java @@ -16,6 +16,7 @@ package com.mongodb.client.model.expressions; +import com.mongodb.annotations.Beta; import com.mongodb.assertions.Assertions; import org.bson.BsonArray; import org.bson.BsonBoolean; @@ -36,12 +37,16 @@ import java.util.List; import static com.mongodb.client.model.expressions.MqlExpression.AstPlaceholder; -import static com.mongodb.client.model.expressions.MqlExpression.extractBsonValue; +import static com.mongodb.client.model.expressions.MqlExpression.toBsonValue; +import static com.mongodb.client.model.expressions.MqlUnchecked.Unchecked.TYPE_ARGUMENT; /** * Convenience methods related to {@link Expression}, used primarily to * produce values in the context of the MongoDB Query Language (MQL). + * + * @since 4.9.0 */ +@Beta(Beta.Reason.CLIENT) public final class Expressions { private Expressions() {} @@ -174,7 +179,7 @@ public static NumberExpression of(final Decimal128 of) { /** * Returns an {@linkplain ArrayExpression array} of * {@linkplain NumberExpression numbers} corresponding to - * the provided {@link Decimal128 Decimal128s}. + * the provided {@link Decimal128}s. * * @param array the array. * @return the resulting value. @@ -204,7 +209,7 @@ public static DateExpression of(final Instant of) { /** * Returns an {@linkplain ArrayExpression array} of * {@linkplain DateExpression dates} corresponding to - * the provided {@link Instant Instants}. + * the provided {@link Instant}s. * * @param array the array. * @return the resulting value. @@ -234,7 +239,7 @@ public static StringExpression of(final String of) { /** * Returns an {@linkplain ArrayExpression array} of * {@linkplain StringExpression strings} corresponding to - * the provided {@link String Strings}. + * the provided {@link String}s. * * @param array the array. * @return the resulting value. @@ -278,7 +283,7 @@ public static DocumentExpression current() { * @return a reference to the current value as a map. * @param the type of the map's values. */ - public static MapExpression currentAsMap() { + public static MapExpression<@MqlUnchecked(TYPE_ARGUMENT) R> currentAsMap() { return new MqlExpression<>((cr) -> new AstPlaceholder(new BsonString("$$CURRENT"))) .assertImplementsAllExpressions(); } @@ -317,8 +322,8 @@ public static EntryExpression ofEntry(final StringExpr Assertions.notNull("v", v); return new MqlExpression<>((cr) -> { BsonDocument document = new BsonDocument(); - document.put("k", extractBsonValue(cr, k)); - document.put("v", extractBsonValue(cr, v)); + document.put("k", toBsonValue(cr, k)); + document.put("v", toBsonValue(cr, v)); return new AstPlaceholder(document); }); } @@ -348,7 +353,7 @@ public static MapExpression ofMap() { * @param the type of the resulting map's values. * @return the resulting map value. */ - public static MapExpression ofMap(final Bson map) { + public static MapExpression<@MqlUnchecked(TYPE_ARGUMENT) T> ofMap(final Bson map) { Assertions.notNull("map", map); return new MqlExpression<>((cr) -> new AstPlaceholder(new BsonDocument("$literal", map.toBsonDocument(BsonDocument.class, cr)))); 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 index d007b3d0dda..6aa7183b96b 100644 --- a/driver-core/src/main/com/mongodb/client/model/expressions/IntegerExpression.java +++ b/driver-core/src/main/com/mongodb/client/model/expressions/IntegerExpression.java @@ -16,6 +16,9 @@ package com.mongodb.client.model.expressions; +import com.mongodb.annotations.Beta; +import com.mongodb.annotations.Sealed; + import java.util.function.Function; /** @@ -23,7 +26,11 @@ * Language (MQL). Integers are a subset of {@linkplain NumberExpression numbers}, * and so, for example, the integer 0 and the number 0 are * {@linkplain #eq(Expression) equal}. + * + * @since 4.9.0 */ +@Sealed +@Beta(Beta.Reason.CLIENT) public interface IntegerExpression extends NumberExpression { /** diff --git a/driver-core/src/main/com/mongodb/client/model/expressions/MapExpression.java b/driver-core/src/main/com/mongodb/client/model/expressions/MapExpression.java index dccf2a567f1..2ccece44e1e 100644 --- a/driver-core/src/main/com/mongodb/client/model/expressions/MapExpression.java +++ b/driver-core/src/main/com/mongodb/client/model/expressions/MapExpression.java @@ -16,6 +16,10 @@ package com.mongodb.client.model.expressions; +import com.mongodb.annotations.Beta; +import com.mongodb.annotations.Sealed; +import com.mongodb.assertions.Assertions; + import java.util.function.Function; import static com.mongodb.client.model.expressions.Expressions.of; @@ -23,16 +27,19 @@ /** * A map {@link Expression value} in the context of the MongoDB Query - * Language (MQL). An map is a finite, unordered a set of - * {@link EntryExpression entries} of a certain type, such that no entry key - * is repeated. It is a mapping from keys to values. + * Language (MQL). A map is a finite set of + * {@link EntryExpression entries} of a certain type. + * No entry key is repeated. It is a mapping from keys to values. * - * @param the type of the elements + * @param the type of the entry values + * @since 4.9.0 */ +@Sealed +@Beta(Beta.Reason.CLIENT) public interface MapExpression extends Expression { /** - * True if {@code this} map has a value (including null) for + * Whether {@code this} map has a value (including null) for * the provided key. * * @param key the key. @@ -41,7 +48,7 @@ public interface MapExpression extends Expression { BooleanExpression has(StringExpression key); /** - * True if {@code this} map has a value (including null) for + * Whether {@code this} map has a value (including null) for * the provided key. * * @param key the key. @@ -74,13 +81,16 @@ default BooleanExpression has(final String key) { * @param key the key. * @return the value. */ + @MqlUnchecked(PRESENT) default T get(final String key) { + Assertions.notNull("key", key); return get(of(key)); } /** * The value corresponding to the provided {@code key}, or the - * {@code other} value if an entry for the key is not present. + * {@code other} value if an entry for the key is not + * {@linkplain #has(StringExpression) present}. * * @param key the key. * @param other the other value. @@ -90,13 +100,16 @@ default T get(final String key) { /** * The value corresponding to the provided {@code key}, or the - * {@code other} value if an entry for the key is not present. + * {@code other} value if an entry for the key is not + * {@linkplain #has(StringExpression) present}. * * @param key the key. * @param other the other value. * @return the resulting value. */ default T get(final String key, final T other) { + Assertions.notNull("key", key); + Assertions.notNull("other", other); return get(of(key), other); } @@ -123,12 +136,15 @@ default T get(final String key, final T other) { * @return the resulting value. */ default MapExpression set(final String key, final T value) { + Assertions.notNull("key", key); + Assertions.notNull("value", value); return set(of(key), value); } /** - * Returns a map with the same entries as {@code this} map, but with - * the specified {@code key} absent. + * Returns a map with the same entries as {@code this} map, but which + * {@linkplain #has(StringExpression) has} no entry with the specified + * {@code key}. * *

    This does not affect the original map. * @@ -138,8 +154,9 @@ default MapExpression set(final String key, final T value) { MapExpression unset(StringExpression key); /** - * Returns a map with the same entries as {@code this} map, but with - * the specified {@code key} absent. + * Returns a map with the same entries as {@code this} map, but which + * {@linkplain #has(StringExpression) has} no entry with the specified + * {@code key}. * *

    This does not affect the original map. * @@ -147,6 +164,7 @@ default MapExpression set(final String key, final T value) { * @return the resulting value. */ default MapExpression unset(final String key) { + Assertions.notNull("key", key); return unset(of(key)); } @@ -165,6 +183,7 @@ default MapExpression unset(final String key) { /** * The {@linkplain EntryExpression entries} of this map as an array. + * No guarantee is made regarding order. * * @see ArrayExpression#asMap * @return the resulting value. 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 index 5f5bc9daa3c..041b0d63a2e 100644 --- a/driver-core/src/main/com/mongodb/client/model/expressions/MqlExpression.java +++ b/driver-core/src/main/com/mongodb/client/model/expressions/MqlExpression.java @@ -16,6 +16,7 @@ package com.mongodb.client.model.expressions; +import com.mongodb.assertions.Assertions; import org.bson.BsonArray; import org.bson.BsonDocument; import org.bson.BsonInt32; @@ -96,7 +97,7 @@ private Function ast(final String name, final Exp return (cr) -> { BsonArray value = new BsonArray(); value.add(this.toBsonValue(cr)); - value.add(extractBsonValue(cr, param1)); + value.add(toBsonValue(cr, param1)); return new AstPlaceholder(new BsonDocument(name, value)); }; } @@ -105,8 +106,8 @@ private Function ast(final String name, final Exp return (cr) -> { BsonArray value = new BsonArray(); value.add(this.toBsonValue(cr)); - value.add(extractBsonValue(cr, param1)); - value.add(extractBsonValue(cr, param2)); + value.add(toBsonValue(cr, param1)); + value.add(toBsonValue(cr, param2)); return new AstPlaceholder(new BsonDocument(name, value)); }; } @@ -116,7 +117,7 @@ private Function ast(final String name, final Exp * the only implementation of Expression and all subclasses, so this will * not mis-cast an expression as anything else. */ - static BsonValue extractBsonValue(final CodecRegistry cr, final Expression expression) { + static BsonValue toBsonValue(final CodecRegistry cr, final Expression expression) { return ((MqlExpression) expression).toBsonValue(cr); } @@ -146,16 +147,20 @@ public BooleanExpression not() { @Override public BooleanExpression or(final BooleanExpression other) { + Assertions.notNull("other", other); return new MqlExpression<>(ast("$or", other)); } @Override public BooleanExpression and(final BooleanExpression other) { + Assertions.notNull("other", other); return new MqlExpression<>(ast("$and", other)); } @Override public R cond(final R ifTrue, final R ifFalse) { + Assertions.notNull("ifTrue", ifTrue); + Assertions.notNull("ifFalse", ifFalse); return newMqlExpression(ast("$cond", ifTrue, ifFalse)); } @@ -174,108 +179,138 @@ private Function getFieldInternal(final String fi @Override public Expression getField(final String fieldName) { + Assertions.notNull("fieldName", fieldName); return new MqlExpression<>(getFieldInternal(fieldName)); } @Override public BooleanExpression getBoolean(final String fieldName) { + Assertions.notNull("fieldName", fieldName); return new MqlExpression<>(getFieldInternal(fieldName)); } @Override public BooleanExpression getBoolean(final String fieldName, final BooleanExpression other) { + Assertions.notNull("fieldName", fieldName); + Assertions.notNull("other", other); return getBoolean(fieldName).isBooleanOr(other); } @Override public NumberExpression getNumber(final String fieldName) { + Assertions.notNull("fieldName", fieldName); return new MqlExpression<>(getFieldInternal(fieldName)); } @Override public NumberExpression getNumber(final String fieldName, final NumberExpression other) { + Assertions.notNull("fieldName", fieldName); + Assertions.notNull("other", other); return getNumber(fieldName).isNumberOr(other); } @Override public IntegerExpression getInteger(final String fieldName) { + Assertions.notNull("fieldName", fieldName); return new MqlExpression<>(getFieldInternal(fieldName)); } @Override public IntegerExpression getInteger(final String fieldName, final IntegerExpression other) { + Assertions.notNull("fieldName", fieldName); + Assertions.notNull("other", other); return getInteger(fieldName).isIntegerOr(other); } @Override public StringExpression getString(final String fieldName) { + Assertions.notNull("fieldName", fieldName); return new MqlExpression<>(getFieldInternal(fieldName)); } @Override public StringExpression getString(final String fieldName, final StringExpression other) { + Assertions.notNull("fieldName", fieldName); + Assertions.notNull("other", other); return getString(fieldName).isStringOr(other); } @Override public DateExpression getDate(final String fieldName) { + Assertions.notNull("fieldName", fieldName); return new MqlExpression<>(getFieldInternal(fieldName)); } @Override public DateExpression getDate(final String fieldName, final DateExpression other) { + Assertions.notNull("fieldName", fieldName); + Assertions.notNull("other", other); return getDate(fieldName).isDateOr(other); } @Override public DocumentExpression getDocument(final String fieldName) { + Assertions.notNull("fieldName", fieldName); return new MqlExpression<>(getFieldInternal(fieldName)); } @Override - public MapExpression getMap(final String field) { - return new MqlExpression<>(getFieldInternal(field)); + public MapExpression getMap(final String fieldName) { + Assertions.notNull("fieldName", fieldName); + return new MqlExpression<>(getFieldInternal(fieldName)); } @Override - public MapExpression getMap(final String field, final MapExpression other) { - return getMap(field).isMapOr(other); + public MapExpression getMap(final String fieldName, final MapExpression other) { + Assertions.notNull("fieldName", fieldName); + Assertions.notNull("other", other); + return getMap(fieldName).isMapOr(other); } @Override public DocumentExpression getDocument(final String fieldName, final DocumentExpression other) { + Assertions.notNull("fieldName", fieldName); + Assertions.notNull("other", other); return getDocument(fieldName).isDocumentOr(other); } @Override public ArrayExpression getArray(final String fieldName) { + Assertions.notNull("fieldName", fieldName); return new MqlExpression<>(getFieldInternal(fieldName)); } @Override public ArrayExpression getArray(final String fieldName, final ArrayExpression other) { + Assertions.notNull("fieldName", fieldName); + Assertions.notNull("other", other); return getArray(fieldName).isArrayOr(other); } @Override public DocumentExpression merge(final DocumentExpression other) { + Assertions.notNull("other", other); return new MqlExpression<>(ast("$mergeObjects", other)); } @Override - public DocumentExpression setField(final String fieldName, final Expression exp) { - return setFieldInternal(fieldName, exp); + public DocumentExpression setField(final String fieldName, final Expression value) { + Assertions.notNull("fieldName", fieldName); + Assertions.notNull("value", value); + return setFieldInternal(fieldName, value); } - private MqlExpression setFieldInternal(final String fieldName, final Expression exp) { + private MqlExpression setFieldInternal(final String fieldName, final Expression value) { + Assertions.notNull("fieldName", fieldName); return newMqlExpression((cr) -> astDoc("$setField", new BsonDocument() .append("field", new BsonString(fieldName)) .append("input", this.toBsonValue(cr)) - .append("value", extractBsonValue(cr, exp)))); + .append("value", toBsonValue(cr, value)))); } @Override public DocumentExpression unsetField(final String fieldName) { + Assertions.notNull("fieldName", fieldName); return newMqlExpression((cr) -> astDoc("$unsetField", new BsonDocument() .append("field", new BsonString(fieldName)) .append("input", this.toBsonValue(cr)))); @@ -285,92 +320,110 @@ public DocumentExpression unsetField(final String fieldName) { @Override public R passTo(final Function f) { + Assertions.notNull("f", f); return f.apply(this.assertImplementsAllExpressions()); } @Override - public R switchOn(final Function, ? extends BranchesTerminal> switchMap) { - return switchMapInternal(this.assertImplementsAllExpressions(), switchMap.apply(new Branches<>())); + public R switchOn(final Function, ? extends BranchesTerminal> mapping) { + Assertions.notNull("mapping", mapping); + return switchMapInternal(this.assertImplementsAllExpressions(), mapping.apply(new Branches<>())); } @Override public R passBooleanTo(final Function f) { + Assertions.notNull("f", f); return f.apply(this.assertImplementsAllExpressions()); } @Override - public R switchBooleanOn(final Function, ? extends BranchesTerminal> switchMap) { - return switchMapInternal(this.assertImplementsAllExpressions(), switchMap.apply(new Branches<>())); + public R switchBooleanOn(final Function, ? extends BranchesTerminal> mapping) { + Assertions.notNull("mapping", mapping); + return switchMapInternal(this.assertImplementsAllExpressions(), mapping.apply(new Branches<>())); } @Override public R passIntegerTo(final Function f) { + Assertions.notNull("f", f); return f.apply(this.assertImplementsAllExpressions()); } @Override - public R switchIntegerOn(final Function, ? extends BranchesTerminal> switchMap) { - return switchMapInternal(this.assertImplementsAllExpressions(), switchMap.apply(new Branches<>())); + public R switchIntegerOn(final Function, ? extends BranchesTerminal> mapping) { + Assertions.notNull("mapping", mapping); + return switchMapInternal(this.assertImplementsAllExpressions(), mapping.apply(new Branches<>())); } @Override public R passNumberTo(final Function f) { + Assertions.notNull("f", f); return f.apply(this.assertImplementsAllExpressions()); } @Override - public R switchNumberOn(final Function, ? extends BranchesTerminal> switchMap) { - return switchMapInternal(this.assertImplementsAllExpressions(), switchMap.apply(new Branches<>())); + public R switchNumberOn(final Function, ? extends BranchesTerminal> mapping) { + Assertions.notNull("mapping", mapping); + return switchMapInternal(this.assertImplementsAllExpressions(), mapping.apply(new Branches<>())); } @Override public R passStringTo(final Function f) { + Assertions.notNull("f", f); return f.apply(this.assertImplementsAllExpressions()); } @Override - public R switchStringOn(final Function, ? extends BranchesTerminal> switchMap) { - return switchMapInternal(this.assertImplementsAllExpressions(), switchMap.apply(new Branches<>())); + public R switchStringOn(final Function, ? extends BranchesTerminal> mapping) { + Assertions.notNull("mapping", mapping); + return switchMapInternal(this.assertImplementsAllExpressions(), mapping.apply(new Branches<>())); } @Override public R passDateTo(final Function f) { + Assertions.notNull("f", f); return f.apply(this.assertImplementsAllExpressions()); } @Override - public R switchDateOn(final Function, ? extends BranchesTerminal> switchMap) { - return switchMapInternal(this.assertImplementsAllExpressions(), switchMap.apply(new Branches<>())); + public R switchDateOn(final Function, ? extends BranchesTerminal> mapping) { + Assertions.notNull("mapping", mapping); + return switchMapInternal(this.assertImplementsAllExpressions(), mapping.apply(new Branches<>())); } @Override public R passArrayTo(final Function, ? extends R> f) { + Assertions.notNull("f", f); return f.apply(this.assertImplementsAllExpressions()); } @Override - public R switchArrayOn(final Function>, ? extends BranchesTerminal, ? extends R>> switchMap) { - return switchMapInternal(this.assertImplementsAllExpressions(), switchMap.apply(new Branches<>())); + public R switchArrayOn(final Function>, ? extends BranchesTerminal, ? extends R>> mapping) { + Assertions.notNull("mapping", mapping); + return switchMapInternal(this.assertImplementsAllExpressions(), mapping.apply(new Branches<>())); } @Override public R passMapTo(final Function, ? extends R> f) { + Assertions.notNull("f", f); return f.apply(this.assertImplementsAllExpressions()); } @Override - public R switchMapOn(final Function>, ? extends BranchesTerminal, ? extends R>> switchMap) { - return switchMapInternal(this.assertImplementsAllExpressions(), switchMap.apply(new Branches<>())); + public R switchMapOn(final Function>, ? extends BranchesTerminal, ? extends R>> mapping) { + Assertions.notNull("mapping", mapping); + return switchMapInternal(this.assertImplementsAllExpressions(), mapping.apply(new Branches<>())); } @Override public R passDocumentTo(final Function f) { + Assertions.notNull("f", f); return f.apply(this.assertImplementsAllExpressions()); } @Override - public R switchDocumentOn(final Function, ? extends BranchesTerminal> switchMap) { - return switchMapInternal(this.assertImplementsAllExpressions(), switchMap.apply(new Branches<>())); + public R switchDocumentOn(final Function, ? extends BranchesTerminal> mapping) { + Assertions.notNull("mapping", mapping); + return switchMapInternal(this.assertImplementsAllExpressions(), mapping.apply(new Branches<>())); } private R0 switchMapInternal( @@ -380,12 +433,12 @@ private R0 switchMapInternal( for (Function> fn : construct.getBranches()) { SwitchCase result = fn.apply(value); branches.add(new BsonDocument() - .append("case", extractBsonValue(cr, result.getCaseValue())) - .append("then", extractBsonValue(cr, result.getThenValue()))); + .append("case", toBsonValue(cr, result.getCaseValue())) + .append("then", toBsonValue(cr, result.getThenValue()))); } BsonDocument switchBson = new BsonDocument().append("branches", branches); if (construct.getDefaults() != null) { - switchBson = switchBson.append("default", extractBsonValue(cr, construct.getDefaults().apply(value))); + switchBson = switchBson.append("default", toBsonValue(cr, construct.getDefaults().apply(value))); } return astDoc("$switch", switchBson); }); @@ -393,53 +446,61 @@ private R0 switchMapInternal( @Override public BooleanExpression eq(final Expression other) { + Assertions.notNull("other", other); return new MqlExpression<>(ast("$eq", other)); } @Override public BooleanExpression ne(final Expression other) { + Assertions.notNull("other", other); return new MqlExpression<>(ast("$ne", other)); } @Override public BooleanExpression gt(final Expression other) { + Assertions.notNull("other", other); return new MqlExpression<>(ast("$gt", other)); } @Override public BooleanExpression gte(final Expression other) { + Assertions.notNull("other", other); return new MqlExpression<>(ast("$gte", other)); } @Override public BooleanExpression lt(final Expression other) { + Assertions.notNull("other", other); return new MqlExpression<>(ast("$lt", other)); } @Override public BooleanExpression lte(final Expression other) { + Assertions.notNull("other", other); return new MqlExpression<>(ast("$lte", other)); } - public BooleanExpression isBoolean() { + BooleanExpression isBoolean() { return new MqlExpression<>(astWrapped("$type")).eq(of("bool")); } @Override public BooleanExpression isBooleanOr(final BooleanExpression other) { + Assertions.notNull("other", other); return this.isBoolean().cond(this, other); } - public BooleanExpression isNumber() { + BooleanExpression isNumber() { return new MqlExpression<>(astWrapped("$isNumber")); } @Override public NumberExpression isNumberOr(final NumberExpression other) { + Assertions.notNull("other", other); return this.isNumber().cond(this, other); } - public BooleanExpression isInteger() { + BooleanExpression isInteger() { return switchOn(on -> on .isNumber(v -> v.round().eq(v)) .defaults(v -> of(false))); @@ -447,6 +508,7 @@ public BooleanExpression isInteger() { @Override public IntegerExpression isIntegerOr(final IntegerExpression other) { + Assertions.notNull("other", other); /* The server does not evaluate both branches of and/or/cond unless needed. However, the server has a pipeline optimization stage prior to @@ -456,39 +518,35 @@ public IntegerExpression isIntegerOr(final IntegerExpression other) { otherwise we could just use: this.isNumber().and(this.eq(this.round())) */ - return this.switchOn(on -> on .isNumber(v -> (IntegerExpression) v.round().eq(v).cond(v, other)) .defaults(v -> other)); } - public BooleanExpression isString() { + BooleanExpression isString() { return new MqlExpression<>(astWrapped("$type")).eq(of("string")); } @Override public StringExpression isStringOr(final StringExpression other) { + Assertions.notNull("other", other); return this.isString().cond(this, other); } - public BooleanExpression isDate() { + BooleanExpression isDate() { return ofStringArray("date").contains(new MqlExpression<>(astWrapped("$type"))); } @Override public DateExpression isDateOr(final DateExpression other) { + Assertions.notNull("other", other); return this.isDate().cond(this, other); } - public BooleanExpression isArray() { + BooleanExpression isArray() { return new MqlExpression<>(astWrapped("$isArray")); } - private Expression ifNull(final Expression ifNull) { - return new MqlExpression<>(ast("$ifNull", ifNull, ofNull())) - .assertImplementsAllExpressions(); - } - /** * checks if array (but cannot check type) * user asserts array is of type R @@ -500,6 +558,7 @@ private Expression ifNull(final Expression ifNull) { @SuppressWarnings("unchecked") @Override public ArrayExpression isArrayOr(final ArrayExpression other) { + Assertions.notNull("other", other); return (ArrayExpression) this.isArray().cond(this.assertImplementsAllExpressions(), other); } @@ -509,11 +568,13 @@ BooleanExpression isDocumentOrMap() { @Override public R isDocumentOr(final R other) { + Assertions.notNull("other", other); return this.isDocumentOrMap().cond(this.assertImplementsAllExpressions(), other); } @Override public MapExpression isMapOr(final MapExpression other) { + Assertions.notNull("other", other); MqlExpression isMap = (MqlExpression) this.isDocumentOrMap(); return newMqlExpression(isMap.ast("$cond", this.assertImplementsAllExpressions(), other)); } @@ -530,7 +591,7 @@ public StringExpression asString() { private Function convertInternal(final String to, final Expression other) { return (cr) -> astDoc("$convert", new BsonDocument() .append("input", this.fn.apply(cr).bsonValue) - .append("onError", extractBsonValue(cr, other)) + .append("onError", toBsonValue(cr, other)) .append("to", new BsonString(to))); } @@ -544,43 +605,47 @@ public IntegerExpression parseInteger() { @Override public ArrayExpression map(final Function in) { + Assertions.notNull("in", in); T varThis = variable("$$this"); return new MqlExpression<>((cr) -> astDoc("$map", new BsonDocument() .append("input", this.toBsonValue(cr)) - .append("in", extractBsonValue(cr, in.apply(varThis))))); + .append("in", toBsonValue(cr, in.apply(varThis))))); } @Override public ArrayExpression filter(final Function predicate) { + Assertions.notNull("predicate", predicate); T varThis = variable("$$this"); return new MqlExpression<>((cr) -> astDoc("$filter", new BsonDocument() .append("input", this.toBsonValue(cr)) - .append("cond", extractBsonValue(cr, predicate.apply(varThis))))); + .append("cond", toBsonValue(cr, predicate.apply(varThis))))); } - public ArrayExpression sort() { + ArrayExpression sort() { return new MqlExpression<>((cr) -> astDoc("$sortArray", new BsonDocument() .append("input", this.toBsonValue(cr)) .append("sortBy", new BsonInt32(1)))); } - public T reduce(final T initialValue, final BinaryOperator in) { + private T reduce(final T initialValue, final BinaryOperator in) { T varThis = variable("$$this"); T varValue = variable("$$value"); return newMqlExpression((cr) -> astDoc("$reduce", new BsonDocument() .append("input", this.toBsonValue(cr)) - .append("initialValue", extractBsonValue(cr, initialValue)) - .append("in", extractBsonValue(cr, in.apply(varValue, varThis))))); + .append("initialValue", toBsonValue(cr, initialValue)) + .append("in", toBsonValue(cr, in.apply(varValue, varThis))))); } @Override public BooleanExpression any(final Function predicate) { + Assertions.notNull("predicate", predicate); MqlExpression array = (MqlExpression) this.map(predicate); return array.reduce(of(false), (a, b) -> a.or(b)); } @Override public BooleanExpression all(final Function predicate) { + Assertions.notNull("predicate", predicate); MqlExpression array = (MqlExpression) this.map(predicate); return array.reduce(of(true), (a, b) -> a.and(b)); } @@ -588,6 +653,7 @@ public BooleanExpression all(final Function predic @SuppressWarnings("unchecked") @Override public NumberExpression sum(final Function mapper) { + Assertions.notNull("mapper", mapper); // no sum that returns IntegerExpression, both have same erasure MqlExpression array = (MqlExpression) this.map(mapper); return array.reduce(of(0), (a, b) -> a.add(b)); @@ -596,36 +662,42 @@ public NumberExpression sum(final Function mapper) { + Assertions.notNull("mapper", mapper); MqlExpression array = (MqlExpression) this.map(mapper); return array.reduce(of(1), (NumberExpression a, NumberExpression b) -> a.multiply(b)); } @Override public T max(final T other) { + Assertions.notNull("other", other); return this.size().eq(of(0)).cond(other, this.maxN(of(1)).first()); } @Override public T min(final T other) { + Assertions.notNull("other", other); return this.size().eq(of(0)).cond(other, this.minN(of(1)).first()); } @Override public ArrayExpression maxN(final IntegerExpression n) { + Assertions.notNull("n", n); return newMqlExpression((CodecRegistry cr) -> astDoc("$maxN", new BsonDocument() - .append("input", extractBsonValue(cr, this)) - .append("n", extractBsonValue(cr, n)))); + .append("input", toBsonValue(cr, this)) + .append("n", toBsonValue(cr, n)))); } @Override public ArrayExpression minN(final IntegerExpression n) { + Assertions.notNull("n", n); return newMqlExpression((CodecRegistry cr) -> astDoc("$minN", new BsonDocument() - .append("input", extractBsonValue(cr, this)) - .append("n", extractBsonValue(cr, n)))); + .append("input", toBsonValue(cr, this)) + .append("n", toBsonValue(cr, n)))); } @Override public StringExpression join(final Function mapper) { + Assertions.notNull("mapper", mapper); MqlExpression array = (MqlExpression) this.map(mapper); return array.reduce(of(""), (a, b) -> a.concat(b)); } @@ -633,6 +705,7 @@ public StringExpression join(final Function mapper) @SuppressWarnings("unchecked") @Override public ArrayExpression concat(final Function> mapper) { + Assertions.notNull("mapper", mapper); MqlExpression> array = (MqlExpression>) this.map(mapper); return array.reduce(Expressions.ofArray(), (a, b) -> a.concat(b)); } @@ -640,6 +713,8 @@ public ArrayExpression concat(final Function ArrayExpression union(final Function> mapper) { + Assertions.notNull("mapper", mapper); + Assertions.notNull("mapper", mapper); MqlExpression> array = (MqlExpression>) this.map(mapper); return array.reduce(Expressions.ofArray(), (a, b) -> a.union(b)); } @@ -650,8 +725,9 @@ public IntegerExpression size() { } @Override - public T elementAt(final IntegerExpression at) { - return new MqlExpression<>(ast("$arrayElemAt", at)) + public T elementAt(final IntegerExpression i) { + Assertions.notNull("i", i); + return new MqlExpression<>(ast("$arrayElemAt", i)) .assertImplementsAllExpressions(); } @@ -668,31 +744,36 @@ public T last() { } @Override - public BooleanExpression contains(final T item) { + public BooleanExpression contains(final T value) { + Assertions.notNull("value", value); String name = "$in"; return new MqlExpression<>((cr) -> { - BsonArray value = new BsonArray(); - value.add(extractBsonValue(cr, item)); - value.add(this.toBsonValue(cr)); - return new AstPlaceholder(new BsonDocument(name, value)); + BsonArray array = new BsonArray(); + array.add(toBsonValue(cr, value)); + array.add(this.toBsonValue(cr)); + return new AstPlaceholder(new BsonDocument(name, array)); }).assertImplementsAllExpressions(); } @Override - public ArrayExpression concat(final ArrayExpression array) { - return new MqlExpression<>(ast("$concatArrays", array)) + public ArrayExpression concat(final ArrayExpression other) { + Assertions.notNull("other", other); + return new MqlExpression<>(ast("$concatArrays", other)) .assertImplementsAllExpressions(); } @Override public ArrayExpression slice(final IntegerExpression start, final IntegerExpression length) { + Assertions.notNull("start", start); + Assertions.notNull("length", length); return new MqlExpression<>(ast("$slice", start, length)) .assertImplementsAllExpressions(); } @Override - public ArrayExpression union(final ArrayExpression set) { - return new MqlExpression<>(ast("$setUnion", set)) + public ArrayExpression union(final ArrayExpression other) { + Assertions.notNull("other", other); + return new MqlExpression<>(ast("$setUnion", other)) .assertImplementsAllExpressions(); } @@ -706,27 +787,32 @@ public ArrayExpression distinct() { * @see NumberExpression */ @Override - public IntegerExpression multiply(final NumberExpression n) { - return newMqlExpression(ast("$multiply", n)); + public IntegerExpression multiply(final NumberExpression other) { + Assertions.notNull("other", other); + return newMqlExpression(ast("$multiply", other)); } @Override - public NumberExpression add(final NumberExpression n) { - return new MqlExpression<>(ast("$add", n)); + public NumberExpression add(final NumberExpression other) { + Assertions.notNull("other", other); + return new MqlExpression<>(ast("$add", other)); } @Override - public NumberExpression divide(final NumberExpression n) { - return new MqlExpression<>(ast("$divide", n)); + public NumberExpression divide(final NumberExpression other) { + Assertions.notNull("other", other); + return new MqlExpression<>(ast("$divide", other)); } @Override public NumberExpression max(final NumberExpression other) { + Assertions.notNull("other", other); return new MqlExpression<>(ast("$max", other)); } @Override public NumberExpression min(final NumberExpression other) { + Assertions.notNull("other", other); return new MqlExpression<>(ast("$min", other)); } @@ -737,11 +823,13 @@ public IntegerExpression round() { @Override public NumberExpression round(final IntegerExpression place) { + Assertions.notNull("place", place); return new MqlExpression<>(ast("$round", place)); } @Override public IntegerExpression multiply(final IntegerExpression other) { + Assertions.notNull("other", other); return new MqlExpression<>(ast("$multiply", other)); } @@ -756,28 +844,33 @@ public DateExpression millisecondsToDate() { } @Override - public NumberExpression subtract(final NumberExpression n) { - return new MqlExpression<>(ast("$subtract", n)); + public NumberExpression subtract(final NumberExpression other) { + Assertions.notNull("other", other); + return new MqlExpression<>(ast("$subtract", other)); } @Override public IntegerExpression add(final IntegerExpression other) { + Assertions.notNull("other", other); return new MqlExpression<>(ast("$add", other)); } @Override - public IntegerExpression subtract(final IntegerExpression i) { - return new MqlExpression<>(ast("$subtract", i)); + public IntegerExpression subtract(final IntegerExpression other) { + Assertions.notNull("other", other); + return new MqlExpression<>(ast("$subtract", other)); } @Override - public IntegerExpression max(final IntegerExpression i) { - return new MqlExpression<>(ast("$max", i)); + public IntegerExpression max(final IntegerExpression other) { + Assertions.notNull("other", other); + return new MqlExpression<>(ast("$max", other)); } @Override - public IntegerExpression min(final IntegerExpression i) { - return new MqlExpression<>(ast("$min", i)); + public IntegerExpression min(final IntegerExpression other) { + Assertions.notNull("other", other); + return new MqlExpression<>(ast("$min", other)); } /** @see DateExpression */ @@ -785,80 +878,95 @@ public IntegerExpression min(final IntegerExpression i) { private MqlExpression usingTimezone(final String name, final StringExpression timezone) { return new MqlExpression<>((cr) -> astDoc(name, new BsonDocument() .append("date", this.toBsonValue(cr)) - .append("timezone", extractBsonValue(cr, timezone)))); + .append("timezone", toBsonValue(cr, timezone)))); } @Override public IntegerExpression year(final StringExpression timezone) { + Assertions.notNull("timezone", timezone); return usingTimezone("$year", timezone); } @Override public IntegerExpression month(final StringExpression timezone) { + Assertions.notNull("timezone", timezone); return usingTimezone("$month", timezone); } @Override public IntegerExpression dayOfMonth(final StringExpression timezone) { + Assertions.notNull("timezone", timezone); return usingTimezone("$dayOfMonth", timezone); } @Override public IntegerExpression dayOfWeek(final StringExpression timezone) { + Assertions.notNull("timezone", timezone); return usingTimezone("$dayOfWeek", timezone); } @Override public IntegerExpression dayOfYear(final StringExpression timezone) { + Assertions.notNull("timezone", timezone); return usingTimezone("$dayOfYear", timezone); } @Override public IntegerExpression hour(final StringExpression timezone) { + Assertions.notNull("timezone", timezone); return usingTimezone("$hour", timezone); } @Override public IntegerExpression minute(final StringExpression timezone) { + Assertions.notNull("timezone", timezone); return usingTimezone("$minute", timezone); } @Override public IntegerExpression second(final StringExpression timezone) { + Assertions.notNull("timezone", timezone); return usingTimezone("$second", timezone); } @Override public IntegerExpression week(final StringExpression timezone) { + Assertions.notNull("timezone", timezone); return usingTimezone("$week", timezone); } @Override public IntegerExpression millisecond(final StringExpression timezone) { + Assertions.notNull("timezone", timezone); return usingTimezone("$millisecond", timezone); } @Override public StringExpression asString(final StringExpression timezone, final StringExpression format) { + Assertions.notNull("timezone", timezone); + Assertions.notNull("format", format); return newMqlExpression((cr) -> astDoc("$dateToString", new BsonDocument() .append("date", this.toBsonValue(cr)) - .append("format", extractBsonValue(cr, format)) - .append("timezone", extractBsonValue(cr, timezone)))); + .append("format", toBsonValue(cr, format)) + .append("timezone", toBsonValue(cr, timezone)))); } @Override public DateExpression parseDate(final StringExpression timezone, final StringExpression format) { + Assertions.notNull("timezone", timezone); + Assertions.notNull("format", format); return newMqlExpression((cr) -> astDoc("$dateFromString", new BsonDocument() .append("dateString", this.toBsonValue(cr)) - .append("format", extractBsonValue(cr, format)) - .append("timezone", extractBsonValue(cr, timezone)))); + .append("format", toBsonValue(cr, format)) + .append("timezone", toBsonValue(cr, timezone)))); } @Override public DateExpression parseDate(final StringExpression format) { + Assertions.notNull("format", format); return newMqlExpression((cr) -> astDoc("$dateFromString", new BsonDocument() .append("dateString", this.toBsonValue(cr)) - .append("format", extractBsonValue(cr, format)))); + .append("format", toBsonValue(cr, format)))); } @Override @@ -881,6 +989,7 @@ public StringExpression toUpper() { @Override public StringExpression concat(final StringExpression other) { + Assertions.notNull("other", other); return new MqlExpression<>(ast("$concat", other)); } @@ -896,22 +1005,28 @@ public IntegerExpression strLenBytes() { @Override public StringExpression substr(final IntegerExpression start, final IntegerExpression length) { + Assertions.notNull("start", start); + Assertions.notNull("length", length); return new MqlExpression<>(ast("$substrCP", start, length)); } @Override public StringExpression substrBytes(final IntegerExpression start, final IntegerExpression length) { + Assertions.notNull("start", start); + Assertions.notNull("length", length); return new MqlExpression<>(ast("$substrBytes", start, length)); } @Override public BooleanExpression has(final StringExpression key) { + Assertions.notNull("key", key); return get(key).ne(ofRem()); } @Override public BooleanExpression has(final String fieldName) { + Assertions.notNull("fieldName", fieldName); return this.has(of(fieldName)); } @@ -926,35 +1041,43 @@ static R ofRem() { @Override public T get(final StringExpression key) { + Assertions.notNull("key", key); return newMqlExpression((cr) -> astDoc("$getField", new BsonDocument() .append("input", this.fn.apply(cr).bsonValue) - .append("field", extractBsonValue(cr, key)))); + .append("field", toBsonValue(cr, key)))); } @SuppressWarnings("unchecked") @Override public T get(final StringExpression key, final T other) { - return (T) ((MqlExpression) get(key)).ifNull(other); + Assertions.notNull("key", key); + Assertions.notNull("other", other); + MqlExpression mqlExpression = (MqlExpression) get(key); + return (T) mqlExpression.eq(ofRem()).cond(other, mqlExpression); } @Override public MapExpression set(final StringExpression key, final T value) { + Assertions.notNull("key", key); + Assertions.notNull("value", value); return newMqlExpression((cr) -> astDoc("$setField", new BsonDocument() - .append("field", extractBsonValue(cr, key)) + .append("field", toBsonValue(cr, key)) .append("input", this.toBsonValue(cr)) - .append("value", extractBsonValue(cr, value)))); + .append("value", toBsonValue(cr, value)))); } @Override public MapExpression unset(final StringExpression key) { + Assertions.notNull("key", key); return newMqlExpression((cr) -> astDoc("$unsetField", new BsonDocument() - .append("field", extractBsonValue(cr, key)) + .append("field", toBsonValue(cr, key)) .append("input", this.toBsonValue(cr)))); } @Override - public MapExpression merge(final MapExpression map) { - return new MqlExpression<>(ast("$mergeObjects", map)); + public MapExpression merge(final MapExpression other) { + Assertions.notNull("other", other); + return new MqlExpression<>(ast("$mergeObjects", other)); } @Override @@ -965,6 +1088,7 @@ public ArrayExpression> entrySet() { @Override public MapExpression asMap( final Function> mapper) { + Assertions.notNull("mapper", mapper); @SuppressWarnings("unchecked") MqlExpression> array = (MqlExpression>) this.map(mapper); return newMqlExpression(array.astWrapped("$arrayToObject")); diff --git a/driver-core/src/main/com/mongodb/client/model/expressions/MqlUnchecked.java b/driver-core/src/main/com/mongodb/client/model/expressions/MqlUnchecked.java index 3841630fe1d..3ed5641c533 100644 --- a/driver-core/src/main/com/mongodb/client/model/expressions/MqlUnchecked.java +++ b/driver-core/src/main/com/mongodb/client/model/expressions/MqlUnchecked.java @@ -15,6 +15,8 @@ */ package com.mongodb.client.model.expressions; +import com.mongodb.annotations.Sealed; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; @@ -31,6 +33,7 @@ @Documented @Retention(RetentionPolicy.SOURCE) @Target({ElementType.METHOD, ElementType.TYPE_USE}) +@Sealed public @interface MqlUnchecked { /** * @return A hint on the user assertion the API relies on. @@ -42,7 +45,8 @@ */ enum Unchecked { /** - * The API relies on the values it encounters being of the type + * The API relies on the values it encounters being of the + * (raw or non-parameterized) type * implied, specified by, or inferred from the user code. * *

    For example, {@link DocumentExpression#getBoolean(String)} @@ -59,8 +63,6 @@ enum Unchecked { * {@linkplain ArrayExpression array} raw type, * but relies on the elements of the array being of * the type derived from the user code. - * - *

    One may think of it as a more specific version of {@link #TYPE}.

    */ TYPE_ARGUMENT, /** 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 index d333faf0a3f..97b5972cd3b 100644 --- a/driver-core/src/main/com/mongodb/client/model/expressions/NumberExpression.java +++ b/driver-core/src/main/com/mongodb/client/model/expressions/NumberExpression.java @@ -16,6 +16,10 @@ package com.mongodb.client.model.expressions; +import com.mongodb.annotations.Beta; +import com.mongodb.annotations.Sealed; +import com.mongodb.assertions.Assertions; + import java.util.function.Function; /** @@ -23,7 +27,11 @@ * Language (MQL). {@linkplain IntegerExpression Integers} are a subset of * numbers, and so, for example, the integer 0 and the number 0 are * {@linkplain #eq(Expression) equal}. + * + * @since 4.9.0 */ +@Sealed +@Beta(Beta.Reason.CLIENT) public interface NumberExpression extends Expression { /** @@ -41,6 +49,7 @@ public interface NumberExpression extends Expression { * @return the resulting value. */ default NumberExpression multiply(final Number other) { + Assertions.notNull("other", other); return this.multiply(Expressions.numberToExpression(other)); } @@ -63,6 +72,7 @@ default NumberExpression multiply(final Number other) { * @return the resulting value. */ default NumberExpression divide(final Number other) { + Assertions.notNull("other", other); return this.divide(Expressions.numberToExpression(other)); } @@ -81,6 +91,7 @@ default NumberExpression divide(final Number other) { * @return the resulting value. */ default NumberExpression add(final Number other) { + Assertions.notNull("other", other); return this.add(Expressions.numberToExpression(other)); } @@ -99,6 +110,7 @@ default NumberExpression add(final Number other) { * @return the resulting value. */ default NumberExpression subtract(final Number other) { + Assertions.notNull("other", other); return this.subtract(Expressions.numberToExpression(other)); } 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 index 7fbe5773b74..665701ec2aa 100644 --- a/driver-core/src/main/com/mongodb/client/model/expressions/StringExpression.java +++ b/driver-core/src/main/com/mongodb/client/model/expressions/StringExpression.java @@ -16,13 +16,21 @@ package com.mongodb.client.model.expressions; +import com.mongodb.annotations.Beta; +import com.mongodb.annotations.Sealed; + import java.util.function.Function; import static com.mongodb.client.model.expressions.Expressions.of; /** - * Expresses a string value. + * A string {@linkplain Expression value} in the context of the MongoDB Query + * Language (MQL). + * + * @since 4.9.0 */ +@Sealed +@Beta(Beta.Reason.CLIENT) public interface StringExpression extends Expression { /** @@ -49,7 +57,7 @@ public interface StringExpression extends Expression { StringExpression concat(StringExpression other); /** - * The number of UTF-8 code points in {@code this} string. + * The number of Unicode code points in {@code this} string. * * @return the resulting value. */ @@ -67,11 +75,11 @@ public interface StringExpression extends Expression { * inclusive, and including the specified {@code length}, up to * the end of the string. * - *

    Warning: the index position is in UTF-8 code points, not in + *

    Warning: the index position is in Unicode code points, not in * UTF-8 encoded bytes. * - * @param start the start index in UTF-8 code points. - * @param length the length in UTF-8 code points. + * @param start the start index in Unicode code points. + * @param length the length in Unicode code points. * @return the resulting value. */ StringExpression substr(IntegerExpression start, IntegerExpression length); @@ -81,11 +89,11 @@ public interface StringExpression extends Expression { * inclusive, and including the specified {@code length}, up to * the end of the string. * - *

    Warning: the index position is in UTF-8 code points, not in + *

    Warning: the index position is in Unicode code points, not in * UTF-8 encoded bytes. * - * @param start the start index in UTF-8 code points. - * @param length the length in UTF-8 code points. + * @param start the start index in Unicode code points. + * @param length the length in Unicode code points. * @return the resulting value. */ default StringExpression substr(final int start, final int length) { @@ -98,7 +106,7 @@ default StringExpression substr(final int start, final int length) { * the end of the string. * *

    The index position is in UTF-8 encoded bytes, not in - * UTF-8 code points. + * Unicode code points. * * @param start the start index in UTF-8 encoded bytes. * @param length the length in UTF-8 encoded bytes. @@ -112,7 +120,7 @@ default StringExpression substr(final int start, final int length) { * the end of the string. * *

    The index position is in UTF-8 encoded bytes, not in - * UTF-8 code points. + * Unicode code points. * * @param start the start index in UTF-8 encoded bytes. * @param length the length in UTF-8 encoded bytes. @@ -135,20 +143,35 @@ default StringExpression substrBytes(final int start, final int length) { /** * Converts {@code this} string to a {@linkplain DateExpression date}. * - *

    This will cause an error if this string does not represent a valid + *

    This method behaves like {@link #parseDate(StringExpression)}, + * with the default format, which is {@code "%Y-%m-%dT%H:%M:%S.%LZ"}. + * + *

    Will cause an error if this string does not represent a valid * date string (such as "2018-03-20", "2018-03-20T12:00:00Z", or * "2018-03-20T12:00:00+0500"). * + * @see DateExpression#asString() + * @see DateExpression#asString(StringExpression, StringExpression) * @return the resulting value. */ DateExpression parseDate(); /** * Converts {@code this} string to a {@linkplain DateExpression date}, - * using the specified {@code format}. - * + * using the specified {@code format}. UTC is assumed if the timezone + * offset element is not specified in the format. + * + *

    Will cause an error if {@code this} string does not match the + * specified {@code format}. + * Will cause an error if an element is specified that is finer-grained + * than an element that is not specified, with year being coarsest + * (for example, minute is specified, but hour is not). + * Omitted finer-grained elements will be parsed to 0. + * + * @see DateExpression#asString() + * @see DateExpression#asString(StringExpression, StringExpression) * @mongodb.server.release 4.0 - * @mongodb.driver.manual reference/operator/aggregation/dateToString/#std-label-format-specifiers Format Specifiers + * @mongodb.driver.manual reference/operator/aggregation/dateFromString/ Format Specifiers, UTC Offset, and Olson Timezone Identifier * @param format the format. * @return the resulting value. */ @@ -158,7 +181,19 @@ default StringExpression substrBytes(final int start, final int length) { * Converts {@code this} string to a {@linkplain DateExpression date}, * using the specified {@code timezone} and {@code format}. * - * @mongodb.driver.manual reference/operator/aggregation/dateToString/#std-label-format-specifiers Format Specifiers + + *

    Will cause an error if {@code this} string does not match the + * specified {@code format}. + * Will cause an error if an element is specified that is finer-grained + * than an element that is not specified, with year being coarsest + * (for example, minute is specified, but hour is not). + * Omitted finer-grained elements will be parsed to 0. + * Will cause an error if the format includes an offset or + * timezone, even if it matches the supplied {@code timezone}. + * + * @see DateExpression#asString() + * @see DateExpression#asString(StringExpression, StringExpression) + * @mongodb.driver.manual reference/operator/aggregation/dateFromString/ Format Specifiers, UTC Offset, and Olson Timezone Identifier * @param format the format. * @param timezone the UTC Offset or Olson Timezone Identifier. * @return the resulting value. 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 index f9b52b24897..ea2d03f818e 100644 --- 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 @@ -15,11 +15,12 @@ */ /** - * API for MQL expressions. - * * @see com.mongodb.client.model.expressions.Expression * @see com.mongodb.client.model.expressions.Expressions + * @since 4.9.0 */ +@Beta(Beta.Reason.CLIENT) @NonNullApi package com.mongodb.client.model.expressions; +import com.mongodb.annotations.Beta; import com.mongodb.lang.NonNullApi; diff --git a/driver-core/src/test/functional/com/mongodb/client/model/expressions/ArrayExpressionsFunctionalTest.java b/driver-core/src/test/functional/com/mongodb/client/model/expressions/ArrayExpressionsFunctionalTest.java index 9d806cad670..306dc679865 100644 --- a/driver-core/src/test/functional/com/mongodb/client/model/expressions/ArrayExpressionsFunctionalTest.java +++ b/driver-core/src/test/functional/com/mongodb/client/model/expressions/ArrayExpressionsFunctionalTest.java @@ -43,7 +43,6 @@ @SuppressWarnings({"Convert2MethodRef"}) class ArrayExpressionsFunctionalTest extends AbstractExpressionsFunctionalTest { // https://www.mongodb.com/docs/manual/reference/operator/aggregation/#array-expression-operators - // (Incomplete) private final ArrayExpression array123 = ofIntegerArray(1, 2, 3); private final ArrayExpression arrayTTF = ofBooleanArray(true, true, false); @@ -115,7 +114,6 @@ public void filterTest() { Stream.of(true, true, false) .filter(v -> v).collect(Collectors.toList()), arrayTTF.filter(v -> v), - // MQL: "{'$filter': {'input': [true, true, false], 'cond': '$$this'}}"); } @@ -126,7 +124,6 @@ public void mapTest() { Stream.of(true, true, false) .map(v -> !v).collect(Collectors.toList()), arrayTTF.map(v -> v.not()), - // MQL: "{'$map': {'input': [true, true, false], 'in': {'$not': '$$this'}}}"); } @@ -137,7 +134,6 @@ public void sortTest() { assertExpression( Stream.of(3, 1, 2) .sorted().collect(Collectors.toList()), sort(integerExpressionArrayExpression), - // MQL: "{'$sortArray': {'input': [3, 1, 2], 'sortBy': 1}}"); } @@ -305,7 +301,6 @@ public void reduceUnionTest() { assertExpression( Arrays.asList(1, 2, 3), sort(ofArray(ofIntegerArray(1, 2), ofIntegerArray(1, 3)).union(v -> v)), - // MQL: "{'$sortArray': {'input': {'$reduce': {'input': " + "{'$map': {'input': [[1, 2], [1, 3]], 'in': '$$this'}}, " + "'initialValue': [], 'in': {'$setUnion': ['$$value', '$$this']}}}, 'sortBy': 1}}"); @@ -324,12 +319,10 @@ public void sizeTest() { assertExpression( Arrays.asList(1, 2, 3).size(), array123.size(), - // MQL: "{'$size': [[1, 2, 3]]}"); assertExpression( 0, ofIntegerArray().size(), - // MQL: "{'$size': [[]]}"); } @@ -339,7 +332,6 @@ public void elementAtTest() { assertExpression( Arrays.asList(1, 2, 3).get(0), array123.elementAt(of(0)), - // MQL: "{'$arrayElemAt': [[1, 2, 3], 0]}"); // negatives assertExpression( @@ -378,13 +370,11 @@ public void firstTest() { assertExpression( new LinkedList<>(Arrays.asList(1, 2, 3)).getFirst(), array123.first(), - // MQL: "{'$first': [[1, 2, 3]]}"); assertExpression( MISSING, ofIntegerArray().first(), - // MQL: "{'$first': [[]]}"); } @@ -395,13 +385,11 @@ public void lastTest() { assertExpression( new LinkedList<>(Arrays.asList(1, 2, 3)).getLast(), array123.last(), - // MQL: "{'$last': [[1, 2, 3]]}"); assertExpression( MISSING, ofIntegerArray().last(), - // MQL: "{'$last': [[]]}"); } @@ -412,7 +400,6 @@ public void containsTest() { assertExpression( Arrays.asList(1, 2, 3).contains(2), array123.contains(of(2)), - // MQL: "{'$in': [2, [1, 2, 3]]}"); } @@ -423,7 +410,6 @@ public void concatTest() { Stream.concat(Stream.of(1, 2, 3), Stream.of(1, 2, 3)) .collect(Collectors.toList()), ofIntegerArray(1, 2, 3).concat(ofIntegerArray(1, 2, 3)), - // MQL: "{'$concatArrays': [[1, 2, 3], [1, 2, 3]]}"); // mixed types: assertExpression( @@ -437,7 +423,6 @@ public void sliceTest() { assertExpression( Arrays.asList(1, 2, 3).subList(1, 3), array123.slice(1, 10), - // MQL: "{'$slice': [[1, 2, 3], 1, 10]}"); ArrayExpression array12345 = ofIntegerArray(1, 2, 3, 4, 5); @@ -460,7 +445,6 @@ public void unionTest() { assertExpression( Arrays.asList(1, 2, 3), sort(array123.union(array123)), - // MQL: "{'$sortArray': {'input': {'$setUnion': [[1, 2, 3], [1, 2, 3]]}, 'sortBy': 1}}"); // mixed types: assertExpression( diff --git a/driver-core/src/test/functional/com/mongodb/client/model/expressions/DocumentExpressionsFunctionalTest.java b/driver-core/src/test/functional/com/mongodb/client/model/expressions/DocumentExpressionsFunctionalTest.java index ddd7823d7f8..ed57114f45e 100644 --- a/driver-core/src/test/functional/com/mongodb/client/model/expressions/DocumentExpressionsFunctionalTest.java +++ b/driver-core/src/test/functional/com/mongodb/client/model/expressions/DocumentExpressionsFunctionalTest.java @@ -193,21 +193,18 @@ public void setFieldTest() { assertExpression( BsonDocument.parse("{a: 1, r: 2}"), // map.put("r", 2) a1.setField("r", of(2)), - // MQL: "{'$setField': {'field': 'r', 'input': {'$literal': {'a': 1}}, 'value': 2}}"); // Placing a null value: assertExpression( BsonDocument.parse("{a: 1, r: null}"), // map.put("r", null) a1.setField("r", Expressions.ofNull()), - // MQL: "{'$setField': {'field': 'r', 'input': {'$literal': {'a': 1}}, 'value': null}}"); // Replacing a field based on its prior value: assertExpression( BsonDocument.parse("{a: 3}"), // map.put("a", map.get("a") * 3) a1.setField("a", a1.getInteger("a").multiply(3)), - // MQL: "{'$setField': {'field': 'a', 'input': {'$literal': {'a': 1}}, 'value': " + "{'$multiply': [{'$getField': {'input': {'$literal': {'a': 1}}, 'field': 'a'}}, 3]}}}"); @@ -215,7 +212,6 @@ public void setFieldTest() { assertExpression( BsonDocument.parse("{'a': {'x': 1, 'y': 2}, r: 10}"), ax1ay2.setField("r", ax1ay2.getDocument("a").getInteger("x").multiply(10)), - // MQL: "{'$setField': {'field': 'r', 'input': {'$literal': {'a': {'x': 1, 'y': 2}}}, " + "'value': {'$multiply': [" + " {'$getField': {'input': {'$getField': {'input': {'$literal': {'a': {'x': 1, 'y': 2}}}, " @@ -242,7 +238,6 @@ public void unsetFieldTest() { assertExpression( BsonDocument.parse("{}"), // map.remove("a") a1.unsetField("a"), - // MQL: "{'$unsetField': {'field': 'a', 'input': {'$literal': {'a': 1}}}}"); } @@ -273,13 +268,16 @@ public void asMapTest() { @Test public void hasTest() { assumeTrue(serverVersionAtLeast(5, 0)); // get/setField - DocumentExpression d = ofDoc("{a: 1}"); + DocumentExpression d = ofDoc("{a: 1, null: null}"); assertExpression( true, d.has("a"), - "{'$ne': [{'$getField': {'input': {'$literal': {'a': 1}}, 'field': 'a'}}, '$$REMOVE']}"); + "{'$ne': [{'$getField': {'input': {'$literal': {'a': 1, 'null': null}}, 'field': 'a'}}, '$$REMOVE']}"); assertExpression( false, d.has("not_a")); + assertExpression( + true, + d.has("null")); } } diff --git a/driver-core/src/test/functional/com/mongodb/client/model/expressions/MapExpressionsFunctionalTest.java b/driver-core/src/test/functional/com/mongodb/client/model/expressions/MapExpressionsFunctionalTest.java index 6be284cf2d8..d1762d9f784 100644 --- a/driver-core/src/test/functional/com/mongodb/client/model/expressions/MapExpressionsFunctionalTest.java +++ b/driver-core/src/test/functional/com/mongodb/client/model/expressions/MapExpressionsFunctionalTest.java @@ -76,21 +76,27 @@ public void getSetMapTest() { mapKey123.unset("key")); // "other" parameter assertExpression( - 1, + null, ofMap(Document.parse("{ 'null': null }")).get("null", of(1))); } @Test public void hasTest() { assumeTrue(serverVersionAtLeast(5, 0)); // get/setField (unset) - MapExpression e = ofMap(BsonDocument.parse("{key: 1}")); + MapExpression e = ofMap(BsonDocument.parse("{key: 1, null: null}")); assertExpression( true, e.has(of("key")), - "{'$ne': [{'$getField': {'input': {'$literal': {'key': 1}}, 'field': 'key'}}, '$$REMOVE']}"); + "{'$ne': [{'$getField': {'input': {'$literal': {'key': 1, 'null': null}}, 'field': 'key'}}, '$$REMOVE']}"); assertExpression( false, e.has("not_key")); + assertExpression( + true, + e.has("null")); + // consistency: + assertExpression(true, e.has("null")); + assertExpression(null, e.get("null", of(1))); } @Test diff --git a/driver-core/src/test/functional/com/mongodb/client/model/expressions/NotNullApiTest.java b/driver-core/src/test/functional/com/mongodb/client/model/expressions/NotNullApiTest.java new file mode 100644 index 00000000000..28e8afb68fa --- /dev/null +++ b/driver-core/src/test/functional/com/mongodb/client/model/expressions/NotNullApiTest.java @@ -0,0 +1,162 @@ +/* + * 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.conversions.Bson; +import org.bson.types.Decimal128; +import org.junit.jupiter.api.Test; + +import java.lang.reflect.Array; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.time.Instant; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Function; + +import static com.mongodb.assertions.Assertions.fail; +import static com.mongodb.client.model.expressions.Expressions.of; +import static com.mongodb.client.model.expressions.Expressions.ofArray; +import static com.mongodb.client.model.expressions.Expressions.ofMap; +import static com.mongodb.client.model.expressions.Expressions.ofNull; + +class NotNullApiTest { + + @Test + public void notNullApiTest() { + Map, Object> mapping = new HashMap<>(); + Map, Object> paramMapping = new HashMap<>(); + + // to test: + mapping.put(Expressions.class, null); + mapping.put(BooleanExpression.class, of(true)); + mapping.put(IntegerExpression.class, of(1)); + mapping.put(NumberExpression.class, of(1.0)); + mapping.put(StringExpression.class, of("")); + mapping.put(DateExpression.class, of(Instant.now())); + mapping.put(DocumentExpression.class, of(BsonDocument.parse("{}"))); + mapping.put(MapExpression.class, ofMap(BsonDocument.parse("{}"))); + mapping.put(ArrayExpression.class, ofArray()); + mapping.put(Expression.class, ofNull()); + mapping.put(Branches.class, new Branches<>()); + mapping.put(BranchesIntermediary.class, new BranchesIntermediary<>(Collections.emptyList())); + mapping.put(BranchesTerminal.class, new BranchesTerminal<>(Collections.emptyList(), null)); + + // additional params from classes not tested: + paramMapping.put(String.class, ""); + paramMapping.put(Instant.class, Instant.now()); + paramMapping.put(Bson.class, BsonDocument.parse("{}")); + paramMapping.put(Function.class, Function.identity()); + paramMapping.put(Number.class, 1); + paramMapping.put(int.class, 1); + paramMapping.put(boolean.class, true); + paramMapping.put(long.class, 1L); + paramMapping.put(Object.class, new Object()); + paramMapping.put(Decimal128.class, new Decimal128(1)); + putArray(paramMapping, Expression.class); + putArray(paramMapping, boolean.class); + putArray(paramMapping, long.class); + putArray(paramMapping, int.class); + putArray(paramMapping, double.class); + putArray(paramMapping, Decimal128.class); + putArray(paramMapping, Instant.class); + putArray(paramMapping, String.class); + + checkNotNullApi(mapping, paramMapping); + } + + private void putArray(final Map, Object> paramMapping, final Class componentType) { + final Object o = Array.newInstance(componentType, 0); + paramMapping.put(o.getClass(), o); + } + + private void checkNotNullApi( + final Map, Object> mapping, + final Map, Object> paramMapping) { + Map, Object> allParams = new HashMap<>(); + allParams.putAll(mapping); + allParams.putAll(paramMapping); + List uncheckedMethods = new ArrayList<>(); + for (Map.Entry, Object> entry : mapping.entrySet()) { + Object instance = entry.getValue(); + Class clazz = entry.getKey(); + Method[] methods = clazz.getDeclaredMethods(); + for (Method method : methods) { + if (!Modifier.isPublic(method.getModifiers())) { + continue; + } + boolean failed = false; + for (int i = 0; i < method.getParameterCount(); i++) { + if (method.getParameterTypes()[i].isPrimitive()) { + continue; + } + if (method.toString().endsWith(".equals(java.lang.Object)")) { + continue; + } + Object[] args = createArgs(allParams, method); + args[i] = null; // set one parameter to null + try { + // the method needs to throw due to Assertions.notNull: + method.invoke(instance, args); + failed = true; + } catch (Exception e) { + Throwable cause = e.getCause(); + if (!(cause instanceof IllegalArgumentException)) { + failed = true; + continue; + } + StackTraceElement[] trace = cause.getStackTrace(); + if (!method.getName().equals(trace[1].getMethodName())) { + failed = true; + } + if (!"notNull".equals(trace[0].getMethodName())) { + failed = true; + } + } + } + if (failed) { + uncheckedMethods.add("> " + method); + } + } + } + if (uncheckedMethods.size() > 0) { + fail("Assertions.notNull must be called on parameter from " + + uncheckedMethods.size() + " methods:\n" + + String.join("\n", uncheckedMethods)); + } + } + + private Object[] createArgs(final Map, ?> mapping, final Method method) { + Object[] args = new Object[method.getParameterCount()]; + Class[] parameterTypes = method.getParameterTypes(); + for (int j = 0; j < parameterTypes.length; j++) { + Class p = parameterTypes[j]; + Object arg = mapping.get(p); + if (arg == null) { + throw new IllegalArgumentException("mappings did not contain parameter of type: " + + p + " for method " + method); + } + args[j] = arg; + } + return args; + } + +} diff --git a/driver-core/src/test/functional/com/mongodb/client/model/expressions/TypeExpressionsFunctionalTest.java b/driver-core/src/test/functional/com/mongodb/client/model/expressions/TypeExpressionsFunctionalTest.java index 9281d439009..bdd59776ebb 100644 --- a/driver-core/src/test/functional/com/mongodb/client/model/expressions/TypeExpressionsFunctionalTest.java +++ b/driver-core/src/test/functional/com/mongodb/client/model/expressions/TypeExpressionsFunctionalTest.java @@ -252,6 +252,20 @@ public void parseDateTest() { .parseDate(of("%Y-%m-%dT%H:%M:%S.%L%z")), "{'$dateFromString': {'dateString': '2007-12-03T10:15:30.005+01:00', " + "'format': '%Y-%m-%dT%H:%M:%S.%L%z'}}"); + + // missing items: + assertExpression( + Instant.parse("2007-12-03T10:15:00.000Z"), + of("2007-12-03T10:15").parseDate(of("%Y-%m-%dT%H:%M"))); + assertThrows(MongoCommandException.class, () -> assertExpression( + "an incomplete date/time string has been found, with elements missing", + of("-12-03T10:15").parseDate(of("-%m-%dT%H:%M")).asString())); + assertThrows(MongoCommandException.class, () -> assertExpression( + "an incomplete date/time string has been found, with elements missing", + of("2007--03T10:15").parseDate(of("%Y--%dT%H:%M")).asString())); + assertThrows(MongoCommandException.class, () -> assertExpression( + "an incomplete date/time string has been found, with elements missing", + of("").parseDate(of("")).asString())); } @Test From 441bc53ff6e5461961ab8349e370faa720f1b424 Mon Sep 17 00:00:00 2001 From: Maxim Katcharov Date: Fri, 27 Jan 2023 14:04:37 -0700 Subject: [PATCH 25/27] Rename to Mql (automated) (#1073) JAVA-3879 --- .../main/com/mongodb/MongoClientSettings.java | 2 +- .../model/{expressions => mql}/Branches.java | 82 ++-- .../BranchesIntermediary.java | 72 ++-- .../BranchesTerminal.java | 4 +- .../ExpressionCodecProvider.java | 6 +- .../MqlArray.java} | 88 ++--- .../MqlBoolean.java} | 22 +- .../DateExpression.java => mql/MqlDate.java} | 36 +- .../MqlDocument.java} | 148 +++---- .../MqlEntry.java} | 26 +- .../{expressions => mql}/MqlExpression.java | 360 +++++++++--------- .../MqlExpressionCodec.java | 2 +- .../MqlInteger.java} | 50 +-- .../MapExpression.java => mql/MqlMap.java} | 60 +-- .../MqlNumber.java} | 56 +-- .../MqlString.java} | 64 ++-- .../{expressions => mql}/MqlUnchecked.java | 14 +- .../Expression.java => mql/MqlValue.java} | 128 +++---- .../Expressions.java => mql/MqlValues.java} | 126 +++--- .../{expressions => mql}/SwitchCase.java | 10 +- .../{expressions => mql}/package-info.java | 6 +- .../AbstractMqlValuesFunctionalTest.java} | 18 +- .../ArithmeticMqlValuesFunctionalTest.java} | 34 +- .../ArrayMqlValuesFunctionalTest.java} | 40 +- .../BooleanMqlValuesFunctionalTest.java} | 14 +- .../ComparisonMqlValuesFunctionalTest.java} | 22 +- .../ControlMqlValuesFunctionalTest.java} | 34 +- .../DateMqlValuesFunctionalTest.java} | 10 +- .../DocumentMqlValuesFunctionalTest.java} | 24 +- .../MapMqlValuesFunctionalTest.java} | 32 +- .../{expressions => mql}/NotNullApiTest.java | 32 +- .../StringMqlValuesFunctionalTest.java} | 6 +- .../TypeMqlValuesFunctionalTest.java} | 20 +- .../InContextMqlValuesFunctionalTest.java} | 12 +- 34 files changed, 830 insertions(+), 830 deletions(-) rename driver-core/src/main/com/mongodb/client/model/{expressions => mql}/Branches.java (66%) rename driver-core/src/main/com/mongodb/client/model/{expressions => mql}/BranchesIntermediary.java (76%) rename driver-core/src/main/com/mongodb/client/model/{expressions => mql}/BranchesTerminal.java (93%) rename driver-core/src/main/com/mongodb/client/model/{expressions => mql}/ExpressionCodecProvider.java (89%) rename driver-core/src/main/com/mongodb/client/model/{expressions/ArrayExpression.java => mql/MqlArray.java} (75%) rename driver-core/src/main/com/mongodb/client/model/{expressions/BooleanExpression.java => mql/MqlBoolean.java} (76%) rename driver-core/src/main/com/mongodb/client/model/{expressions/DateExpression.java => mql/MqlDate.java} (80%) rename driver-core/src/main/com/mongodb/client/model/{expressions/DocumentExpression.java => mql/MqlDocument.java} (73%) rename driver-core/src/main/com/mongodb/client/model/{expressions/EntryExpression.java => mql/MqlEntry.java} (71%) rename driver-core/src/main/com/mongodb/client/model/{expressions => mql}/MqlExpression.java (67%) rename driver-core/src/main/com/mongodb/client/model/{expressions => mql}/MqlExpressionCodec.java (97%) rename driver-core/src/main/com/mongodb/client/model/{expressions/IntegerExpression.java => mql/MqlInteger.java} (66%) rename driver-core/src/main/com/mongodb/client/model/{expressions/MapExpression.java => mql/MqlMap.java} (74%) rename driver-core/src/main/com/mongodb/client/model/{expressions/NumberExpression.java => mql/MqlNumber.java} (71%) rename driver-core/src/main/com/mongodb/client/model/{expressions/StringExpression.java => mql/MqlString.java} (75%) rename driver-core/src/main/com/mongodb/client/model/{expressions => mql}/MqlUnchecked.java (84%) rename driver-core/src/main/com/mongodb/client/model/{expressions/Expression.java => mql/MqlValue.java} (72%) rename driver-core/src/main/com/mongodb/client/model/{expressions/Expressions.java => mql/MqlValues.java} (72%) rename driver-core/src/main/com/mongodb/client/model/{expressions => mql}/SwitchCase.java (76%) rename driver-core/src/main/com/mongodb/client/model/{expressions => mql}/package-info.java (82%) rename driver-core/src/test/functional/com/mongodb/client/model/{expressions/AbstractExpressionsFunctionalTest.java => mql/AbstractMqlValuesFunctionalTest.java} (89%) rename driver-core/src/test/functional/com/mongodb/client/model/{expressions/ArithmeticExpressionsFunctionalTest.java => mql/ArithmeticMqlValuesFunctionalTest.java} (90%) rename driver-core/src/test/functional/com/mongodb/client/model/{expressions/ArrayExpressionsFunctionalTest.java => mql/ArrayMqlValuesFunctionalTest.java} (90%) rename driver-core/src/test/functional/com/mongodb/client/model/{expressions/BooleanExpressionsFunctionalTest.java => mql/BooleanMqlValuesFunctionalTest.java} (85%) rename driver-core/src/test/functional/com/mongodb/client/model/{expressions/ComparisonExpressionsFunctionalTest.java => mql/ComparisonMqlValuesFunctionalTest.java} (89%) rename driver-core/src/test/functional/com/mongodb/client/model/{expressions/ControlExpressionsFunctionalTest.java => mql/ControlMqlValuesFunctionalTest.java} (91%) rename driver-core/src/test/functional/com/mongodb/client/model/{expressions/DateExpressionsFunctionalTest.java => mql/DateMqlValuesFunctionalTest.java} (94%) rename driver-core/src/test/functional/com/mongodb/client/model/{expressions/DocumentExpressionsFunctionalTest.java => mql/DocumentMqlValuesFunctionalTest.java} (94%) rename driver-core/src/test/functional/com/mongodb/client/model/{expressions/MapExpressionsFunctionalTest.java => mql/MapMqlValuesFunctionalTest.java} (84%) rename driver-core/src/test/functional/com/mongodb/client/model/{expressions => mql}/NotNullApiTest.java (85%) rename driver-core/src/test/functional/com/mongodb/client/model/{expressions/StringExpressionsFunctionalTest.java => mql/StringMqlValuesFunctionalTest.java} (96%) rename driver-core/src/test/functional/com/mongodb/client/model/{expressions/TypeExpressionsFunctionalTest.java => mql/TypeMqlValuesFunctionalTest.java} (95%) rename driver-sync/src/test/functional/com/mongodb/client/model/{expressions/InContextExpressionsFunctionalTest.java => mql/InContextMqlValuesFunctionalTest.java} (94%) diff --git a/driver-core/src/main/com/mongodb/MongoClientSettings.java b/driver-core/src/main/com/mongodb/MongoClientSettings.java index 97a5b9322aa..524f0100e4f 100644 --- a/driver-core/src/main/com/mongodb/MongoClientSettings.java +++ b/driver-core/src/main/com/mongodb/MongoClientSettings.java @@ -19,7 +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.mql.ExpressionCodecProvider; import com.mongodb.client.model.geojson.codecs.GeoJsonCodecProvider; import com.mongodb.connection.ClusterSettings; import com.mongodb.connection.ConnectionPoolSettings; diff --git a/driver-core/src/main/com/mongodb/client/model/expressions/Branches.java b/driver-core/src/main/com/mongodb/client/model/mql/Branches.java similarity index 66% rename from driver-core/src/main/com/mongodb/client/model/expressions/Branches.java rename to driver-core/src/main/com/mongodb/client/model/mql/Branches.java index c0f86572e34..1a576cfe581 100644 --- a/driver-core/src/main/com/mongodb/client/model/expressions/Branches.java +++ b/driver-core/src/main/com/mongodb/client/model/mql/Branches.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.mongodb.client.model.expressions; +package com.mongodb.client.model.mql; import com.mongodb.annotations.Beta; import com.mongodb.assertions.Assertions; @@ -23,10 +23,10 @@ import java.util.List; import java.util.function.Function; -import static com.mongodb.client.model.expressions.MqlUnchecked.Unchecked.TYPE_ARGUMENT; +import static com.mongodb.client.model.mql.MqlUnchecked.Unchecked.TYPE_ARGUMENT; /** - * Branches are used in {@linkplain Expression#switchOn}, and + * Branches are used in {@linkplain MqlValue#switchOn}, and * define a sequence of checks that will be performed. The first check * to succeed will produce the value that it specifies. If no check succeeds, * then the operation @@ -37,18 +37,18 @@ * @since 4.9.0 */ @Beta(Beta.Reason.CLIENT) -public final class Branches { +public final class Branches { Branches() { } - private static BranchesIntermediary with(final Function> switchCase) { + private static BranchesIntermediary with(final Function> switchCase) { List>> v = new ArrayList<>(); v.add(switchCase); return new BranchesIntermediary<>(v); } - private static MqlExpression mqlEx(final T value) { + private static MqlExpression mqlEx(final T value) { return (MqlExpression) value; } @@ -63,7 +63,7 @@ private static MqlExpression mqlEx(final T value) { * @param the type of the produced value. * @return the appended sequence of checks. */ - public BranchesIntermediary is(final Function predicate, final Function mapping) { + public BranchesIntermediary is(final Function predicate, final Function mapping) { Assertions.notNull("predicate", predicate); Assertions.notNull("mapping", mapping); return with(value -> new SwitchCase<>(predicate.apply(value), mapping.apply(value))); @@ -72,7 +72,7 @@ public BranchesIntermediary is(final Function BranchesIntermediary is(final Function the type of the produced value. * @return the appended sequence of checks. */ - public BranchesIntermediary eq(final T v, final Function mapping) { + public BranchesIntermediary eq(final T v, final Function mapping) { Assertions.notNull("v", v); Assertions.notNull("mapping", mapping); return is(value -> value.eq(v), mapping); @@ -88,7 +88,7 @@ public BranchesIntermediary eq(final T v, final Fun /** * A successful check for being - * {@linkplain Expression#lt less than} + * {@linkplain MqlValue#lt less than} * the provided value {@code v} * produces a value specified by the {@code mapping}. * @@ -97,7 +97,7 @@ public BranchesIntermediary eq(final T v, final Fun * @param the type of the produced value. * @return the appended sequence of checks. */ - public BranchesIntermediary lt(final T v, final Function mapping) { + public BranchesIntermediary lt(final T v, final Function mapping) { Assertions.notNull("v", v); Assertions.notNull("mapping", mapping); return is(value -> value.lt(v), mapping); @@ -105,7 +105,7 @@ public BranchesIntermediary lt(final T v, final Fun /** * A successful check for being - * {@linkplain Expression#lte less than or equal to} + * {@linkplain MqlValue#lte less than or equal to} * the provided value {@code v} * produces a value specified by the {@code mapping}. * @@ -114,7 +114,7 @@ public BranchesIntermediary lt(final T v, final Fun * @param the type of the produced value. * @return the appended sequence of checks. */ - public BranchesIntermediary lte(final T v, final Function mapping) { + public BranchesIntermediary lte(final T v, final Function mapping) { Assertions.notNull("v", v); Assertions.notNull("mapping", mapping); return is(value -> value.lte(v), mapping); @@ -124,21 +124,21 @@ public BranchesIntermediary lte(final T v, final Fu /** * A successful check for - * {@linkplain Expression#isBooleanOr(BooleanExpression) being a boolean} + * {@linkplain MqlValue#isBooleanOr(MqlBoolean) being a boolean} * produces a value specified by the {@code mapping}. * * @param mapping the mapping. * @return the appended sequence of checks. * @param the type of the produced value. */ - public BranchesIntermediary isBoolean(final Function mapping) { + public BranchesIntermediary isBoolean(final Function mapping) { Assertions.notNull("mapping", mapping); - return is(v -> mqlEx(v).isBoolean(), v -> mapping.apply((BooleanExpression) v)); + return is(v -> mqlEx(v).isBoolean(), v -> mapping.apply((MqlBoolean) v)); } /** * A successful check for - * {@linkplain Expression#isNumberOr(NumberExpression) being a number} + * {@linkplain MqlValue#isNumberOr(MqlNumber) being a number} * produces a value specified by the {@code mapping}. * * @mongodb.server.release 4.4 @@ -146,14 +146,14 @@ public BranchesIntermediary isBoolean(final Functio * @return the appended sequence of checks. * @param the type of the produced value. */ - public BranchesIntermediary isNumber(final Function mapping) { + public BranchesIntermediary isNumber(final Function mapping) { Assertions.notNull("mapping", mapping); - return is(v -> mqlEx(v).isNumber(), v -> mapping.apply((NumberExpression) v)); + return is(v -> mqlEx(v).isNumber(), v -> mapping.apply((MqlNumber) v)); } /** * A successful check for - * {@linkplain Expression#isIntegerOr(IntegerExpression) being an integer} + * {@linkplain MqlValue#isIntegerOr(MqlInteger) being an integer} * produces a value specified by the {@code mapping}. * * @mongodb.server.release 4.4 @@ -161,42 +161,42 @@ public BranchesIntermediary isNumber(final Function * @return the appended sequence of checks. * @param the type of the produced value. */ - public BranchesIntermediary isInteger(final Function mapping) { + public BranchesIntermediary isInteger(final Function mapping) { Assertions.notNull("mapping", mapping); - return is(v -> mqlEx(v).isInteger(), v -> mapping.apply((IntegerExpression) v)); + return is(v -> mqlEx(v).isInteger(), v -> mapping.apply((MqlInteger) v)); } /** * A successful check for - * {@linkplain Expression#isStringOr(StringExpression) being a string} + * {@linkplain MqlValue#isStringOr(MqlString) being a string} * produces a value specified by the {@code mapping}. * * @param mapping the mapping. * @return the appended sequence of checks. * @param the type of the produced value. */ - public BranchesIntermediary isString(final Function mapping) { + public BranchesIntermediary isString(final Function mapping) { Assertions.notNull("mapping", mapping); - return is(v -> mqlEx(v).isString(), v -> mapping.apply((StringExpression) v)); + return is(v -> mqlEx(v).isString(), v -> mapping.apply((MqlString) v)); } /** * A successful check for - * {@linkplain Expression#isDateOr(DateExpression) being a date} + * {@linkplain MqlValue#isDateOr(MqlDate) being a date} * produces a value specified by the {@code mapping}. * * @param mapping the mapping. * @return the appended sequence of checks. * @param the type of the produced value. */ - public BranchesIntermediary isDate(final Function mapping) { + public BranchesIntermediary isDate(final Function mapping) { Assertions.notNull("mapping", mapping); - return is(v -> mqlEx(v).isDate(), v -> mapping.apply((DateExpression) v)); + return is(v -> mqlEx(v).isDate(), v -> mapping.apply((MqlDate) v)); } /** * A successful check for - * {@linkplain Expression#isArrayOr(ArrayExpression) being an array} + * {@linkplain MqlValue#isArrayOr(MqlArray) being an array} * produces a value specified by the {@code mapping}. * *

    Warning: The type argument of the array is not @@ -209,32 +209,32 @@ public BranchesIntermediary isDate(final Function the type of the array. */ @SuppressWarnings("unchecked") - public BranchesIntermediary isArray(final Function, ? extends R> mapping) { + public BranchesIntermediary isArray(final Function, ? extends R> mapping) { Assertions.notNull("mapping", mapping); - return is(v -> mqlEx(v).isArray(), v -> mapping.apply((ArrayExpression) v)); + return is(v -> mqlEx(v).isArray(), v -> mapping.apply((MqlArray) v)); } /** * A successful check for - * {@linkplain Expression#isDocumentOr(DocumentExpression) being a document} + * {@linkplain MqlValue#isDocumentOr(MqlDocument) being a document} * (or document-like value, see - * {@link MapExpression} and {@link EntryExpression}) + * {@link MqlMap} and {@link MqlEntry}) * produces a value specified by the {@code mapping}. * * @param mapping the mapping. * @return the appended sequence of checks. * @param the type of the produced value. */ - public BranchesIntermediary isDocument(final Function mapping) { + public BranchesIntermediary isDocument(final Function mapping) { Assertions.notNull("mapping", mapping); - return is(v -> mqlEx(v).isDocumentOrMap(), v -> mapping.apply((DocumentExpression) v)); + return is(v -> mqlEx(v).isDocumentOrMap(), v -> mapping.apply((MqlDocument) v)); } /** * A successful check for - * {@linkplain Expression#isMapOr(MapExpression) being a map} + * {@linkplain MqlValue#isMapOr(MqlMap) being a map} * (or map-like value, see - * {@link DocumentExpression} and {@link EntryExpression}) + * {@link MqlDocument} and {@link MqlEntry}) * produces a value specified by the {@code mapping}. * *

    Warning: The type argument of the map is not @@ -247,21 +247,21 @@ public BranchesIntermediary isDocument(final Functi * @param the type of the array. */ @SuppressWarnings("unchecked") - public BranchesIntermediary isMap(final Function, ? extends R> mapping) { + public BranchesIntermediary isMap(final Function, ? extends R> mapping) { Assertions.notNull("mapping", mapping); - return is(v -> mqlEx(v).isDocumentOrMap(), v -> mapping.apply((MapExpression) v)); + return is(v -> mqlEx(v).isDocumentOrMap(), v -> mapping.apply((MqlMap) v)); } /** * A successful check for - * {@linkplain Expressions#ofNull()} being the null value} + * {@linkplain MqlValues#ofNull()} being the null value} * produces a value specified by the {@code mapping}. * * @param mapping the mapping. * @return the appended sequence of checks. * @param the type of the produced value. */ - public BranchesIntermediary isNull(final Function mapping) { + public BranchesIntermediary isNull(final Function mapping) { Assertions.notNull("mapping", mapping); return is(v -> mqlEx(v).isNull(), v -> mapping.apply(v)); } diff --git a/driver-core/src/main/com/mongodb/client/model/expressions/BranchesIntermediary.java b/driver-core/src/main/com/mongodb/client/model/mql/BranchesIntermediary.java similarity index 76% rename from driver-core/src/main/com/mongodb/client/model/expressions/BranchesIntermediary.java rename to driver-core/src/main/com/mongodb/client/model/mql/BranchesIntermediary.java index 7e6fcb8a76c..9b1b88e4467 100644 --- a/driver-core/src/main/com/mongodb/client/model/expressions/BranchesIntermediary.java +++ b/driver-core/src/main/com/mongodb/client/model/mql/BranchesIntermediary.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.mongodb.client.model.expressions; +package com.mongodb.client.model.mql; import com.mongodb.annotations.Beta; import com.mongodb.assertions.Assertions; @@ -23,7 +23,7 @@ import java.util.List; import java.util.function.Function; -import static com.mongodb.client.model.expressions.MqlUnchecked.Unchecked.TYPE_ARGUMENT; +import static com.mongodb.client.model.mql.MqlUnchecked.Unchecked.TYPE_ARGUMENT; /** * See {@link Branches}. @@ -33,7 +33,7 @@ * @since 4.9.0 */ @Beta(Beta.Reason.CLIENT) -public final class BranchesIntermediary extends BranchesTerminal { +public final class BranchesIntermediary extends BranchesTerminal { BranchesIntermediary(final List>> branches) { super(branches, null); } @@ -44,7 +44,7 @@ private BranchesIntermediary with(final Function> switchC return new BranchesIntermediary<>(v); } - private static MqlExpression mqlEx(final T value) { + private static MqlExpression mqlEx(final T value) { return (MqlExpression) value; } @@ -58,7 +58,7 @@ private static MqlExpression mqlEx(final T value) { * @param mapping the mapping. * @return the appended sequence of checks. */ - public BranchesIntermediary is(final Function predicate, final Function mapping) { + public BranchesIntermediary is(final Function predicate, final Function mapping) { Assertions.notNull("predicate", predicate); Assertions.notNull("mapping", mapping); return this.with(value -> new SwitchCase<>(predicate.apply(value), mapping.apply(value))); @@ -67,7 +67,7 @@ public BranchesIntermediary is(final Function eq(final T v, final Function lt(final T v, final Function lte(final T v, final Function isBoolean(final Function mapping) { + public BranchesIntermediary isBoolean(final Function mapping) { Assertions.notNull("mapping", mapping); - return is(v -> mqlEx(v).isBoolean(), v -> mapping.apply((BooleanExpression) v)); + return is(v -> mqlEx(v).isBoolean(), v -> mapping.apply((MqlBoolean) v)); } /** * A successful check for - * {@linkplain Expression#isNumberOr(NumberExpression) being a number} + * {@linkplain MqlValue#isNumberOr(MqlNumber) being a number} * produces a value specified by the {@code mapping}. * * @mongodb.server.release 4.4 * @param mapping the mapping. * @return the appended sequence of checks. */ - public BranchesIntermediary isNumber(final Function mapping) { + public BranchesIntermediary isNumber(final Function mapping) { Assertions.notNull("mapping", mapping); - return is(v -> mqlEx(v).isNumber(), v -> mapping.apply((NumberExpression) v)); + return is(v -> mqlEx(v).isNumber(), v -> mapping.apply((MqlNumber) v)); } /** * A successful check for - * {@linkplain Expression#isIntegerOr(IntegerExpression) being an integer} + * {@linkplain MqlValue#isIntegerOr(MqlInteger) being an integer} * produces a value specified by the {@code mapping}. * * @mongodb.server.release 4.4 * @param mapping the mapping. * @return the appended sequence of checks. */ - public BranchesIntermediary isInteger(final Function mapping) { + public BranchesIntermediary isInteger(final Function mapping) { Assertions.notNull("mapping", mapping); - return is(v -> mqlEx(v).isInteger(), v -> mapping.apply((IntegerExpression) v)); + return is(v -> mqlEx(v).isInteger(), v -> mapping.apply((MqlInteger) v)); } /** * A successful check for - * {@linkplain Expression#isStringOr(StringExpression) being a string} + * {@linkplain MqlValue#isStringOr(MqlString) being a string} * produces a value specified by the {@code mapping}. * * @param mapping the mapping. * @return the appended sequence of checks. */ - public BranchesIntermediary isString(final Function mapping) { + public BranchesIntermediary isString(final Function mapping) { Assertions.notNull("mapping", mapping); - return is(v -> mqlEx(v).isString(), v -> mapping.apply((StringExpression) v)); + return is(v -> mqlEx(v).isString(), v -> mapping.apply((MqlString) v)); } /** * A successful check for - * {@linkplain Expression#isDateOr(DateExpression) being a date} + * {@linkplain MqlValue#isDateOr(MqlDate) being a date} * produces a value specified by the {@code mapping}. * * @param mapping the mapping. * @return the appended sequence of checks. */ - public BranchesIntermediary isDate(final Function mapping) { + public BranchesIntermediary isDate(final Function mapping) { Assertions.notNull("mapping", mapping); - return is(v -> mqlEx(v).isDate(), v -> mapping.apply((DateExpression) v)); + return is(v -> mqlEx(v).isDate(), v -> mapping.apply((MqlDate) v)); } /** * A successful check for - * {@linkplain Expression#isArrayOr(ArrayExpression) being an array} + * {@linkplain MqlValue#isArrayOr(MqlArray) being an array} * produces a value specified by the {@code mapping}. * *

    Warning: The type argument of the array is not @@ -195,31 +195,31 @@ public BranchesIntermediary isDate(final Function the type of the elements of the resulting array. */ @SuppressWarnings("unchecked") - public BranchesIntermediary isArray(final Function, ? extends R> mapping) { + public BranchesIntermediary isArray(final Function, ? extends R> mapping) { Assertions.notNull("mapping", mapping); - return is(v -> mqlEx(v).isArray(), v -> mapping.apply((ArrayExpression) v)); + return is(v -> mqlEx(v).isArray(), v -> mapping.apply((MqlArray) v)); } /** * A successful check for - * {@linkplain Expression#isDocumentOr(DocumentExpression) being a document} + * {@linkplain MqlValue#isDocumentOr(MqlDocument) being a document} * (or document-like value, see - * {@link MapExpression} and {@link EntryExpression}) + * {@link MqlMap} and {@link MqlEntry}) * produces a value specified by the {@code mapping}. * * @param mapping the mapping. * @return the appended sequence of checks. */ - public BranchesIntermediary isDocument(final Function mapping) { + public BranchesIntermediary isDocument(final Function mapping) { Assertions.notNull("mapping", mapping); - return is(v -> mqlEx(v).isDocumentOrMap(), v -> mapping.apply((DocumentExpression) v)); + return is(v -> mqlEx(v).isDocumentOrMap(), v -> mapping.apply((MqlDocument) v)); } /** * A successful check for - * {@linkplain Expression#isMapOr(MapExpression) being a map} + * {@linkplain MqlValue#isMapOr(MqlMap) being a map} * (or map-like value, see - * {@link DocumentExpression} and {@link EntryExpression}) + * {@link MqlDocument} and {@link MqlEntry}) * produces a value specified by the {@code mapping}. * *

    Warning: The type argument of the map is not @@ -231,20 +231,20 @@ public BranchesIntermediary isDocument(final Function the type of the array. */ @SuppressWarnings("unchecked") - public BranchesIntermediary isMap(final Function, ? extends R> mapping) { + public BranchesIntermediary isMap(final Function, ? extends R> mapping) { Assertions.notNull("mapping", mapping); - return is(v -> mqlEx(v).isDocumentOrMap(), v -> mapping.apply((MapExpression) v)); + return is(v -> mqlEx(v).isDocumentOrMap(), v -> mapping.apply((MqlMap) v)); } /** * A successful check for - * {@linkplain Expressions#ofNull()} being the null value} + * {@linkplain MqlValues#ofNull()} being the null value} * produces a value specified by the {@code mapping}. * * @param mapping the mapping. * @return the appended sequence of checks. */ - public BranchesIntermediary isNull(final Function mapping) { + public BranchesIntermediary isNull(final Function mapping) { Assertions.notNull("mapping", mapping); return is(v -> mqlEx(v).isNull(), v -> mapping.apply(v)); } diff --git a/driver-core/src/main/com/mongodb/client/model/expressions/BranchesTerminal.java b/driver-core/src/main/com/mongodb/client/model/mql/BranchesTerminal.java similarity index 93% rename from driver-core/src/main/com/mongodb/client/model/expressions/BranchesTerminal.java rename to driver-core/src/main/com/mongodb/client/model/mql/BranchesTerminal.java index d561f616466..f72cb5cb1f4 100644 --- a/driver-core/src/main/com/mongodb/client/model/expressions/BranchesTerminal.java +++ b/driver-core/src/main/com/mongodb/client/model/mql/BranchesTerminal.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.mongodb.client.model.expressions; +package com.mongodb.client.model.mql; import com.mongodb.annotations.Beta; import com.mongodb.lang.Nullable; @@ -31,7 +31,7 @@ * @since 4.9.0 */ @Beta(Beta.Reason.CLIENT) -public class BranchesTerminal { +public class BranchesTerminal { private final List>> branches; diff --git a/driver-core/src/main/com/mongodb/client/model/expressions/ExpressionCodecProvider.java b/driver-core/src/main/com/mongodb/client/model/mql/ExpressionCodecProvider.java similarity index 89% rename from driver-core/src/main/com/mongodb/client/model/expressions/ExpressionCodecProvider.java rename to driver-core/src/main/com/mongodb/client/model/mql/ExpressionCodecProvider.java index 6ac41e96759..d4176b7205f 100644 --- a/driver-core/src/main/com/mongodb/client/model/expressions/ExpressionCodecProvider.java +++ b/driver-core/src/main/com/mongodb/client/model/mql/ExpressionCodecProvider.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.mongodb.client.model.expressions; +package com.mongodb.client.model.mql; import com.mongodb.annotations.Beta; import com.mongodb.annotations.Immutable; @@ -24,10 +24,10 @@ import org.bson.codecs.configuration.CodecRegistry; /** - * Provides Codec instances for the {@link Expression MQL API}. + * Provides Codec instances for the {@link MqlValue MQL API}. * *

    Responsible for converting values and computations expressed using the - * driver's implementation of the {@link Expression MQL API} into the corresponding + * driver's implementation of the {@link MqlValue MQL 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 diff --git a/driver-core/src/main/com/mongodb/client/model/expressions/ArrayExpression.java b/driver-core/src/main/com/mongodb/client/model/mql/MqlArray.java similarity index 75% rename from driver-core/src/main/com/mongodb/client/model/expressions/ArrayExpression.java rename to driver-core/src/main/com/mongodb/client/model/mql/MqlArray.java index 42785dcebe0..2503c5d88df 100644 --- a/driver-core/src/main/com/mongodb/client/model/expressions/ArrayExpression.java +++ b/driver-core/src/main/com/mongodb/client/model/mql/MqlArray.java @@ -14,18 +14,18 @@ * limitations under the License. */ -package com.mongodb.client.model.expressions; +package com.mongodb.client.model.mql; import com.mongodb.annotations.Beta; import com.mongodb.annotations.Sealed; import java.util.function.Function; -import static com.mongodb.client.model.expressions.Expressions.of; -import static com.mongodb.client.model.expressions.MqlUnchecked.Unchecked.PRESENT; +import static com.mongodb.client.model.mql.MqlValues.of; +import static com.mongodb.client.model.mql.MqlUnchecked.Unchecked.PRESENT; /** - * An array {@link Expression value} in the context of the MongoDB Query + * An array {@link MqlValue value} in the context of the MongoDB Query * Language (MQL). An array is a finite, ordered collection of elements of a * certain type. It is also known as a finite mathematical sequence. * @@ -34,7 +34,7 @@ */ @Sealed @Beta(Beta.Reason.CLIENT) -public interface ArrayExpression extends Expression { +public interface MqlArray extends MqlValue { /** * An array consisting of only those elements in {@code this} array that @@ -44,7 +44,7 @@ public interface ArrayExpression extends Expression { * it should be included. * @return the resulting array. */ - ArrayExpression filter(Function predicate); + MqlArray filter(Function predicate); /** * An array consisting of the results of applying the provided function to @@ -54,14 +54,14 @@ public interface ArrayExpression extends Expression { * @return the resulting array. * @param the type of the elements of the resulting array. */ - ArrayExpression map(Function in); + MqlArray map(Function in); /** * The size of {@code this} array. * * @return the size. */ - IntegerExpression size(); + MqlInteger size(); /** * Whether any value in {@code this} array satisfies the predicate. @@ -69,7 +69,7 @@ public interface ArrayExpression extends Expression { * @param predicate the predicate. * @return the resulting value. */ - BooleanExpression any(Function predicate); + MqlBoolean any(Function predicate); /** * Whether all values in {@code this} array satisfy the predicate. @@ -77,38 +77,38 @@ public interface ArrayExpression extends Expression { * @param predicate the predicate. * @return the resulting value. */ - BooleanExpression all(Function predicate); + MqlBoolean all(Function predicate); /** * The sum of adding together all the values of {@code this} array, * via the provided {@code mapper}. Returns 0 if the array is empty. * *

    The mapper may be used to transform the values of {@code this} array - * into {@linkplain NumberExpression numbers}. If no transformation is + * into {@linkplain MqlNumber numbers}. If no transformation is * necessary, then the identity function {@code array.sum(v -> v)} should * be used. * * @param mapper the mapper function. * @return the resulting value. */ - NumberExpression sum(Function mapper); + MqlNumber sum(Function mapper); /** * The product of multiplying together all the values of {@code this} array, * via the provided {@code mapper}. Returns 1 if the array is empty. * *

    The mapper may be used to transform the values of {@code this} array - * into {@linkplain NumberExpression numbers}. If no transformation is + * into {@linkplain MqlNumber numbers}. If no transformation is * necessary, then the identity function {@code array.multiply(v -> v)} * should be used. * * @param mapper the mapper function. * @return the resulting value. */ - NumberExpression multiply(Function mapper); + MqlNumber multiply(Function mapper); /** - * The {@linkplain #gt(Expression) largest} value all the values of + * The {@linkplain #gt(MqlValue) largest} value all the values of * {@code this} array, or the {@code other} value if this array is empty. * * @mongodb.server.release 5.2 @@ -118,7 +118,7 @@ public interface ArrayExpression extends Expression { T max(T other); /** - * The {@linkplain #lt(Expression) smallest} value all the values of + * The {@linkplain #lt(MqlValue) smallest} value all the values of * {@code this} array, or the {@code other} value if this array is empty. * * @mongodb.server.release 5.2 @@ -128,7 +128,7 @@ public interface ArrayExpression extends Expression { T min(T other); /** - * The {@linkplain #gt(Expression) largest} {@code n} elements of + * The {@linkplain #gt(MqlValue) largest} {@code n} elements of * {@code this} array, or all elements if the array contains fewer than * {@code n} elements. * @@ -136,10 +136,10 @@ public interface ArrayExpression extends Expression { * @param n the number of elements. * @return the resulting value. */ - ArrayExpression maxN(IntegerExpression n); + MqlArray maxN(MqlInteger n); /** - * The {@linkplain #lt(Expression) smallest} {@code n} elements of + * The {@linkplain #lt(MqlValue) smallest} {@code n} elements of * {@code this} array, or all elements if the array contains fewer than * {@code n} elements. * @@ -147,7 +147,7 @@ public interface ArrayExpression extends Expression { * @param n the number of elements. * @return the resulting value. */ - ArrayExpression minN(IntegerExpression n); + MqlArray minN(MqlInteger n); /** * The string-concatenation of all the values of {@code this} array, @@ -155,23 +155,23 @@ public interface ArrayExpression extends Expression { * is empty. * *

    The mapper may be used to transform the values of {@code this} array - * into {@linkplain StringExpression strings}. If no transformation is + * into {@linkplain MqlString strings}. If no transformation is * necessary, then the identity function {@code array.join(v -> v)} should * be used. * * @param mapper the mapper function. * @return the resulting value. */ - StringExpression join(Function mapper); + MqlString join(Function mapper); /** - * The {@linkplain #concat(ArrayExpression) array-concatenation} + * The {@linkplain #concat(MqlArray) array-concatenation} * of all the array values of {@code this} array, * via the provided {@code mapper}. Returns the empty array if the array * is empty. * *

    The mapper may be used to transform the values of {@code this} array - * into {@linkplain ArrayExpression arrays}. If no transformation is + * into {@linkplain MqlArray arrays}. If no transformation is * necessary, then the identity function {@code array.concat(v -> v)} should * be used. * @@ -179,16 +179,16 @@ public interface ArrayExpression extends Expression { * @return the resulting value. * @param the type of the elements of the array. */ - ArrayExpression concat(Function> mapper); + MqlArray concat(Function> mapper); /** - * The {@linkplain #union(ArrayExpression) set-union} + * The {@linkplain #union(MqlArray) set-union} * of all the array values of {@code this} array, * via the provided {@code mapper}. Returns the empty array if the array * is empty. * *

    The mapper may be used to transform the values of {@code this} array - * into {@linkplain ArrayExpression arrays}. If no transformation is + * into {@linkplain MqlArray arrays}. If no transformation is * necessary, then the identity function {@code array.union(v -> v)} should * be used. * @@ -196,25 +196,25 @@ public interface ArrayExpression extends Expression { * @return the resulting value. * @param the type of the elements of the array. */ - ArrayExpression union(Function> mapper); + MqlArray union(Function> mapper); /** - * The {@linkplain MapExpression map} value corresponding to the - * {@linkplain EntryExpression entry} values of {@code this} array, + * The {@linkplain MqlMap map} value corresponding to the + * {@linkplain MqlEntry entry} values of {@code this} array, * via the provided {@code mapper}. Returns the empty map if the array * is empty. * *

    The mapper may be used to transform the values of {@code this} array - * into {@linkplain EntryExpression entries}. If no transformation is + * into {@linkplain MqlEntry entries}. If no transformation is * necessary, then the identity function {@code array.union(v -> v)} should * be used. * - * @see MapExpression#entrySet() + * @see MqlMap#entrySet() * @param mapper the mapper function. * @return the resulting value. * @param the type of the resulting map's values. */ - MapExpression asMap(Function> mapper); + MqlMap asMap(Function> mapper); /** * Returns the element at the provided index {@code i} for @@ -229,7 +229,7 @@ public interface ArrayExpression extends Expression { * @return the resulting value. */ @MqlUnchecked(PRESENT) - T elementAt(IntegerExpression i); + T elementAt(MqlInteger i); /** * Returns the element at the provided index {@code i} for @@ -281,7 +281,7 @@ default T elementAt(final int i) { * @param value the value. * @return the resulting value. */ - BooleanExpression contains(T value); + MqlBoolean contains(T value); /** * The result of concatenating {@code this} array first with @@ -290,7 +290,7 @@ default T elementAt(final int i) { * @param other the other array. * @return the resulting array. */ - ArrayExpression concat(ArrayExpression other); + MqlArray concat(MqlArray other); /** * The subarray of {@code this} array, from the {@code start} index @@ -301,7 +301,7 @@ default T elementAt(final int i) { * @param length length * @return the resulting value */ - ArrayExpression slice(IntegerExpression start, IntegerExpression length); + MqlArray slice(MqlInteger start, MqlInteger length); /** * The subarray of {@code this} array, from the {@code start} index @@ -312,7 +312,7 @@ default T elementAt(final int i) { * @param length length * @return the resulting value */ - default ArrayExpression slice(final int start, final int length) { + default MqlArray slice(final int start, final int length) { return this.slice(of(start), of(length)); } @@ -324,7 +324,7 @@ default ArrayExpression slice(final int start, final int length) { * @param other the other array. * @return the resulting array. */ - ArrayExpression union(ArrayExpression other); + MqlArray union(MqlArray other); /** @@ -333,27 +333,27 @@ default ArrayExpression slice(final int start, final int length) { * * @return the resulting value */ - ArrayExpression distinct(); + MqlArray distinct(); /** * The result of passing {@code this} value to the provided function. * Equivalent to {@code f.apply(this)}, and allows lambdas and static, * user-defined functions to use the chaining syntax. * - * @see Expression#passTo + * @see MqlValue#passTo * @param f the function to apply. * @return the resulting value. * @param the type of the resulting value. */ - R passArrayTo(Function, ? extends R> f); + R passArrayTo(Function, ? extends R> f); /** * The result of applying the provided switch mapping to {@code this} value. * - * @see Expression#switchOn + * @see MqlValue#switchOn * @param mapping the switch mapping. * @return the resulting value. * @param the type of the resulting value. */ - R switchArrayOn(Function>, ? extends BranchesTerminal, ? extends R>> mapping); + R switchArrayOn(Function>, ? extends BranchesTerminal, ? extends R>> mapping); } diff --git a/driver-core/src/main/com/mongodb/client/model/expressions/BooleanExpression.java b/driver-core/src/main/com/mongodb/client/model/mql/MqlBoolean.java similarity index 76% rename from driver-core/src/main/com/mongodb/client/model/expressions/BooleanExpression.java rename to driver-core/src/main/com/mongodb/client/model/mql/MqlBoolean.java index 7c7305652a3..a752d8037e9 100644 --- a/driver-core/src/main/com/mongodb/client/model/expressions/BooleanExpression.java +++ b/driver-core/src/main/com/mongodb/client/model/mql/MqlBoolean.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.mongodb.client.model.expressions; +package com.mongodb.client.model.mql; import com.mongodb.annotations.Beta; import com.mongodb.annotations.Sealed; @@ -22,21 +22,21 @@ import java.util.function.Function; /** - * A boolean {@linkplain Expression value} in the context of the + * A boolean {@linkplain MqlValue value} in the context of the * MongoDB Query Language (MQL). * * @since 4.9.0 */ @Sealed @Beta(Beta.Reason.CLIENT) -public interface BooleanExpression extends Expression { +public interface MqlBoolean extends MqlValue { /** * The logical negation of {@code this} value. * * @return the resulting value. */ - BooleanExpression not(); + MqlBoolean not(); /** * The logical conjunction of {@code this} and the {@code other} value. @@ -44,7 +44,7 @@ public interface BooleanExpression extends Expression { * @param other the other boolean value. * @return the resulting value. */ - BooleanExpression or(BooleanExpression other); + MqlBoolean or(MqlBoolean other); /** * The logical disjunction of {@code this} and the {@code other} value. @@ -52,7 +52,7 @@ public interface BooleanExpression extends Expression { * @param other the other boolean value. * @return the resulting value. */ - BooleanExpression and(BooleanExpression other); + MqlBoolean and(MqlBoolean other); /** * The {@code ifTrue} value when {@code this} is true, @@ -63,27 +63,27 @@ public interface BooleanExpression extends Expression { * @return the resulting value. * @param The type of the resulting expression. */ - T cond(T ifTrue, T ifFalse); + T cond(T ifTrue, T ifFalse); /** * The result of passing {@code this} value to the provided function. * Equivalent to {@code f.apply(this)}, and allows lambdas and static, * user-defined functions to use the chaining syntax. * - * @see Expression#passTo + * @see MqlValue#passTo * @param f the function to apply. * @return the resulting value. * @param the type of the resulting value. */ - R passBooleanTo(Function f); + R passBooleanTo(Function f); /** * The result of applying the provided switch mapping to {@code this} value. * - * @see Expression#switchOn + * @see MqlValue#switchOn * @param mapping the switch mapping. * @return the resulting value. * @param the type of the resulting value. */ - R switchBooleanOn(Function, ? extends BranchesTerminal> mapping); + R switchBooleanOn(Function, ? extends BranchesTerminal> mapping); } diff --git a/driver-core/src/main/com/mongodb/client/model/expressions/DateExpression.java b/driver-core/src/main/com/mongodb/client/model/mql/MqlDate.java similarity index 80% rename from driver-core/src/main/com/mongodb/client/model/expressions/DateExpression.java rename to driver-core/src/main/com/mongodb/client/model/mql/MqlDate.java index 35d8c5906be..7c39057ee23 100644 --- a/driver-core/src/main/com/mongodb/client/model/expressions/DateExpression.java +++ b/driver-core/src/main/com/mongodb/client/model/mql/MqlDate.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.mongodb.client.model.expressions; +package com.mongodb.client.model.mql; import com.mongodb.annotations.Beta; import com.mongodb.annotations.Sealed; @@ -22,7 +22,7 @@ import java.util.function.Function; /** - * A UTC date-time {@linkplain Expression value} in the context + * A UTC date-time {@linkplain MqlValue value} in the context * of the MongoDB Query Language (MQL). Tracks the number of * milliseconds since the Unix epoch, and does not track the timezone. * @@ -31,7 +31,7 @@ */ @Sealed @Beta(Beta.Reason.CLIENT) -public interface DateExpression extends Expression { +public interface MqlDate extends MqlValue { /** * The year of {@code this} date as determined by the provided @@ -40,7 +40,7 @@ public interface DateExpression extends Expression { * @param timezone the UTC Offset or Olson Timezone Identifier. * @return the resulting value. */ - IntegerExpression year(StringExpression timezone); + MqlInteger year(MqlString timezone); /** * The month of {@code this} date as determined by the provided @@ -49,7 +49,7 @@ public interface DateExpression extends Expression { * @param timezone the UTC Offset or Olson Timezone Identifier. * @return the resulting value. */ - IntegerExpression month(StringExpression timezone); + MqlInteger month(MqlString timezone); /** * The day of the month of {@code this} date as determined by the provided @@ -58,7 +58,7 @@ public interface DateExpression extends Expression { * @param timezone the UTC Offset or Olson Timezone Identifier. * @return the resulting value. */ - IntegerExpression dayOfMonth(StringExpression timezone); + MqlInteger dayOfMonth(MqlString timezone); /** * The day of the week of {@code this} date as determined by the provided @@ -67,7 +67,7 @@ public interface DateExpression extends Expression { * @param timezone the UTC Offset or Olson Timezone Identifier. * @return the resulting value. */ - IntegerExpression dayOfWeek(StringExpression timezone); + MqlInteger dayOfWeek(MqlString timezone); /** * The day of the year of {@code this} date as determined by the provided @@ -76,7 +76,7 @@ public interface DateExpression extends Expression { * @param timezone the UTC Offset or Olson Timezone Identifier. * @return the resulting value. */ - IntegerExpression dayOfYear(StringExpression timezone); + MqlInteger dayOfYear(MqlString timezone); /** * The hour of {@code this} date as determined by the provided @@ -85,7 +85,7 @@ public interface DateExpression extends Expression { * @param timezone the UTC Offset or Olson Timezone Identifier. * @return the resulting value. */ - IntegerExpression hour(StringExpression timezone); + MqlInteger hour(MqlString timezone); /** * The minute of {@code this} date as determined by the provided @@ -94,7 +94,7 @@ public interface DateExpression extends Expression { * @param timezone the UTC Offset or Olson Timezone Identifier. * @return the resulting value. */ - IntegerExpression minute(StringExpression timezone); + MqlInteger minute(MqlString timezone); /** * The second of {@code this} date as determined by the provided @@ -104,7 +104,7 @@ public interface DateExpression extends Expression { * @param timezone the UTC Offset or Olson Timezone Identifier. * @return the resulting value. */ - IntegerExpression second(StringExpression timezone); + MqlInteger second(MqlString timezone); /** * The week of the year of {@code this} date as determined by the provided @@ -116,7 +116,7 @@ public interface DateExpression extends Expression { * @param timezone the UTC Offset or Olson Timezone Identifier. * @return the resulting value. */ - IntegerExpression week(StringExpression timezone); + MqlInteger week(MqlString timezone); /** * The millisecond part of {@code this} date as determined by the provided @@ -125,7 +125,7 @@ public interface DateExpression extends Expression { * @param timezone the UTC Offset or Olson Timezone Identifier. * @return the resulting value. */ - IntegerExpression millisecond(StringExpression timezone); + MqlInteger millisecond(MqlString timezone); /** * The string representation of {@code this} date as determined by the @@ -135,27 +135,27 @@ public interface DateExpression extends Expression { * @param format the format specifier. * @return the resulting value. */ - StringExpression asString(StringExpression timezone, StringExpression format); + MqlString asString(MqlString timezone, MqlString format); /** * The result of passing {@code this} value to the provided function. * Equivalent to {@code f.apply(this)}, and allows lambdas and static, * user-defined functions to use the chaining syntax. * - * @see Expression#passTo + * @see MqlValue#passTo * @param f the function to apply. * @return the resulting value. * @param the type of the resulting value. */ - R passDateTo(Function f); + R passDateTo(Function f); /** * The result of applying the provided switch mapping to {@code this} value. * - * @see Expression#switchOn + * @see MqlValue#switchOn * @param mapping the switch mapping. * @return the resulting value. * @param the type of the resulting value. */ - R switchDateOn(Function, ? extends BranchesTerminal> mapping); + R switchDateOn(Function, ? extends BranchesTerminal> mapping); } diff --git a/driver-core/src/main/com/mongodb/client/model/expressions/DocumentExpression.java b/driver-core/src/main/com/mongodb/client/model/mql/MqlDocument.java similarity index 73% rename from driver-core/src/main/com/mongodb/client/model/expressions/DocumentExpression.java rename to driver-core/src/main/com/mongodb/client/model/mql/MqlDocument.java index e82db8415ba..c930677f066 100644 --- a/driver-core/src/main/com/mongodb/client/model/expressions/DocumentExpression.java +++ b/driver-core/src/main/com/mongodb/client/model/mql/MqlDocument.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.mongodb.client.model.expressions; +package com.mongodb.client.model.mql; import com.mongodb.annotations.Beta; import com.mongodb.annotations.Sealed; @@ -24,24 +24,24 @@ import java.time.Instant; import java.util.function.Function; -import static com.mongodb.client.model.expressions.Expressions.of; -import static com.mongodb.client.model.expressions.Expressions.ofMap; -import static com.mongodb.client.model.expressions.MqlUnchecked.Unchecked.PRESENT; -import static com.mongodb.client.model.expressions.MqlUnchecked.Unchecked.TYPE; -import static com.mongodb.client.model.expressions.MqlUnchecked.Unchecked.TYPE_ARGUMENT; +import static com.mongodb.client.model.mql.MqlValues.of; +import static com.mongodb.client.model.mql.MqlValues.ofMap; +import static com.mongodb.client.model.mql.MqlUnchecked.Unchecked.PRESENT; +import static com.mongodb.client.model.mql.MqlUnchecked.Unchecked.TYPE; +import static com.mongodb.client.model.mql.MqlUnchecked.Unchecked.TYPE_ARGUMENT; /** - * A document {@link Expression value} in the context of the MongoDB Query + * A document {@link MqlValue value} in the context of the MongoDB Query * Language (MQL). A document is a finite set of fields, where the field * name is a string, together with a value of any other - * {@linkplain Expression type in the type hierarchy}. + * {@linkplain MqlValue type in the type hierarchy}. * No field name is repeated. * * @since 4.9.0 */ @Sealed @Beta(Beta.Reason.CLIENT) -public interface DocumentExpression extends Expression { +public interface MqlDocument extends MqlValue { /** * Whether {@code this} document has a field with the provided @@ -51,7 +51,7 @@ public interface DocumentExpression extends Expression { * @param fieldName the name of the field. * @return the resulting value. */ - BooleanExpression has(String fieldName); + MqlBoolean has(String fieldName); /** * Returns a document with the same fields as {@code this} document, but @@ -68,7 +68,7 @@ public interface DocumentExpression extends Expression { * @param value the value. * @return the resulting document. */ - DocumentExpression setField(String fieldName, Expression value); + MqlDocument setField(String fieldName, MqlValue value); /** * Returns a document with the same fields as {@code this} document, but @@ -80,10 +80,10 @@ public interface DocumentExpression extends Expression { * @param fieldName the name of the field. * @return the resulting document. */ - DocumentExpression unsetField(String fieldName); + MqlDocument unsetField(String fieldName); /** - * Returns the {@linkplain Expression} value of the field + * Returns the {@linkplain MqlValue} value of the field * with the provided {@code fieldName}. * *

    Warning: Use of this method is an assertion that the document @@ -94,10 +94,10 @@ public interface DocumentExpression extends Expression { * @return the resulting value. */ @MqlUnchecked(PRESENT) - Expression getField(String fieldName); + MqlValue getField(String fieldName); /** - * Returns the {@linkplain BooleanExpression boolean} value of the field + * Returns the {@linkplain MqlBoolean boolean} value of the field * with the provided {@code fieldName}. * *

    Warning: The type and presence of the resulting value is not @@ -111,10 +111,10 @@ public interface DocumentExpression extends Expression { * @return the resulting value. */ @MqlUnchecked({PRESENT, TYPE}) - BooleanExpression getBoolean(String fieldName); + MqlBoolean getBoolean(String fieldName); /** - * Returns the {@linkplain BooleanExpression boolean} value of the field + * Returns the {@linkplain MqlBoolean boolean} value of the field * with the provided {@code fieldName}, * or the {@code other} value if the field is not a boolean * or if the document {@linkplain #has} no such field. @@ -124,10 +124,10 @@ public interface DocumentExpression extends Expression { * @param other the other value. * @return the resulting value. */ - BooleanExpression getBoolean(String fieldName, BooleanExpression other); + MqlBoolean getBoolean(String fieldName, MqlBoolean other); /** - * Returns the {@linkplain BooleanExpression boolean} value of the field + * Returns the {@linkplain MqlBoolean boolean} value of the field * with the provided {@code fieldName}, * or the {@code other} value if the field is not a boolean * or if the document {@linkplain #has} no such field. @@ -137,13 +137,13 @@ public interface DocumentExpression extends Expression { * @param other the other value. * @return the resulting value. */ - default BooleanExpression getBoolean(final String fieldName, final boolean other) { + default MqlBoolean getBoolean(final String fieldName, final boolean other) { Assertions.notNull("fieldName", fieldName); return getBoolean(fieldName, of(other)); } /** - * Returns the {@linkplain NumberExpression number} value of the field + * Returns the {@linkplain MqlNumber number} value of the field * with the provided {@code fieldName}. * *

    Warning: The type and presence of the resulting value is not @@ -157,10 +157,10 @@ default BooleanExpression getBoolean(final String fieldName, final boolean other * @return the resulting value. */ @MqlUnchecked({PRESENT, TYPE}) - NumberExpression getNumber(String fieldName); + MqlNumber getNumber(String fieldName); /** - * Returns the {@linkplain NumberExpression number} value of the field + * Returns the {@linkplain MqlNumber number} value of the field * with the provided {@code fieldName}, * or the {@code other} value if the field is not a number * or if the document {@linkplain #has} no such field. @@ -170,10 +170,10 @@ default BooleanExpression getBoolean(final String fieldName, final boolean other * @param other the other value. * @return the resulting value. */ - NumberExpression getNumber(String fieldName, NumberExpression other); + MqlNumber getNumber(String fieldName, MqlNumber other); /** - * Returns the {@linkplain NumberExpression number} value of the field + * Returns the {@linkplain MqlNumber number} value of the field * with the provided {@code fieldName}, * or the {@code other} value if the field is not a number * or if the document {@linkplain #has} no such field. @@ -183,14 +183,14 @@ default BooleanExpression getBoolean(final String fieldName, final boolean other * @param other the other value. * @return the resulting value. */ - default NumberExpression getNumber(final String fieldName, final Number other) { + default MqlNumber getNumber(final String fieldName, final Number other) { Assertions.notNull("fieldName", fieldName); Assertions.notNull("other", other); - return getNumber(fieldName, Expressions.numberToExpression(other)); + return getNumber(fieldName, MqlValues.numberToExpression(other)); } /** - * Returns the {@linkplain IntegerExpression integer} value of the field + * Returns the {@linkplain MqlInteger integer} value of the field * with the provided {@code fieldName}. * *

    Warning: The type and presence of the resulting value is not @@ -204,10 +204,10 @@ default NumberExpression getNumber(final String fieldName, final Number other) { * @return the resulting value. */ @MqlUnchecked({PRESENT, TYPE}) - IntegerExpression getInteger(String fieldName); + MqlInteger getInteger(String fieldName); /** - * Returns the {@linkplain IntegerExpression integer} value of the field + * Returns the {@linkplain MqlInteger integer} value of the field * with the provided {@code fieldName}, * or the {@code other} value if the field is not an integer * or if the document {@linkplain #has} no such field. @@ -217,10 +217,10 @@ default NumberExpression getNumber(final String fieldName, final Number other) { * @param other the other value. * @return the resulting value. */ - IntegerExpression getInteger(String fieldName, IntegerExpression other); + MqlInteger getInteger(String fieldName, MqlInteger other); /** - * Returns the {@linkplain IntegerExpression integer} value of the field + * Returns the {@linkplain MqlInteger integer} value of the field * with the provided {@code fieldName}, * or the {@code other} value if the field is not an integer * or if the document {@linkplain #has} no such field. @@ -230,13 +230,13 @@ default NumberExpression getNumber(final String fieldName, final Number other) { * @param other the other value. * @return the resulting value. */ - default IntegerExpression getInteger(final String fieldName, final int other) { + default MqlInteger getInteger(final String fieldName, final int other) { Assertions.notNull("fieldName", fieldName); return getInteger(fieldName, of(other)); } /** - * Returns the {@linkplain IntegerExpression integer} value of the field + * Returns the {@linkplain MqlInteger integer} value of the field * with the provided {@code fieldName}, * or the {@code other} value if the field is not an integer * or if the document {@linkplain #has} no such field. @@ -246,13 +246,13 @@ default IntegerExpression getInteger(final String fieldName, final int other) { * @param other the other value. * @return the resulting value. */ - default IntegerExpression getInteger(final String fieldName, final long other) { + default MqlInteger getInteger(final String fieldName, final long other) { Assertions.notNull("fieldName", fieldName); return getInteger(fieldName, of(other)); } /** - * Returns the {@linkplain StringExpression string} value of the field + * Returns the {@linkplain MqlString string} value of the field * with the provided {@code fieldName}. * *

    Warning: The type and presence of the resulting value is not @@ -266,10 +266,10 @@ default IntegerExpression getInteger(final String fieldName, final long other) { * @return the resulting value. */ @MqlUnchecked({PRESENT, TYPE}) - StringExpression getString(String fieldName); + MqlString getString(String fieldName); /** - * Returns the {@linkplain StringExpression string} value of the field + * Returns the {@linkplain MqlString string} value of the field * with the provided {@code fieldName}, * or the {@code other} value if the field is not a string * or if the document {@linkplain #has} no such field. @@ -279,10 +279,10 @@ default IntegerExpression getInteger(final String fieldName, final long other) { * @param other the other value. * @return the resulting value. */ - StringExpression getString(String fieldName, StringExpression other); + MqlString getString(String fieldName, MqlString other); /** - * Returns the {@linkplain StringExpression string} value of the field + * Returns the {@linkplain MqlString string} value of the field * with the provided {@code fieldName}, * or the {@code other} value if the field is not a string * or if the document {@linkplain #has} no such field. @@ -292,14 +292,14 @@ default IntegerExpression getInteger(final String fieldName, final long other) { * @param other the other value. * @return the resulting value. */ - default StringExpression getString(final String fieldName, final String other) { + default MqlString getString(final String fieldName, final String other) { Assertions.notNull("fieldName", fieldName); Assertions.notNull("other", other); return getString(fieldName, of(other)); } /** - * Returns the {@linkplain DateExpression date} value of the field + * Returns the {@linkplain MqlDate date} value of the field * with the provided {@code fieldName}. * *

    Warning: The type and presence of the resulting value is not @@ -313,10 +313,10 @@ default StringExpression getString(final String fieldName, final String other) { * @return the resulting value. */ @MqlUnchecked({PRESENT, TYPE}) - DateExpression getDate(String fieldName); + MqlDate getDate(String fieldName); /** - * Returns the {@linkplain DateExpression date} value of the field + * Returns the {@linkplain MqlDate date} value of the field * with the provided {@code fieldName}, * or the {@code other} value if the field is not a date * or if the document {@linkplain #has} no such field. @@ -326,10 +326,10 @@ default StringExpression getString(final String fieldName, final String other) { * @param other the other value. * @return the resulting value. */ - DateExpression getDate(String fieldName, DateExpression other); + MqlDate getDate(String fieldName, MqlDate other); /** - * Returns the {@linkplain DateExpression date} value of the field + * Returns the {@linkplain MqlDate date} value of the field * with the provided {@code fieldName}, * or the {@code other} value if the field is not a date * or if the document {@linkplain #has} no such field. @@ -339,14 +339,14 @@ default StringExpression getString(final String fieldName, final String other) { * @param other the other value. * @return the resulting value. */ - default DateExpression getDate(final String fieldName, final Instant other) { + default MqlDate getDate(final String fieldName, final Instant other) { Assertions.notNull("fieldName", fieldName); Assertions.notNull("other", other); return getDate(fieldName, of(other)); } /** - * Returns the {@linkplain DocumentExpression document} value of the field + * Returns the {@linkplain MqlDocument document} value of the field * with the provided {@code fieldName}. * *

    Warning: The type and presence of the resulting value is not @@ -360,44 +360,44 @@ default DateExpression getDate(final String fieldName, final Instant other) { * @return the resulting value. */ @MqlUnchecked({PRESENT, TYPE}) - DocumentExpression getDocument(String fieldName); + MqlDocument getDocument(String fieldName); /** - * Returns the {@linkplain DocumentExpression document} value of the field + * Returns the {@linkplain MqlDocument document} value of the field * with the provided {@code fieldName}, * or the {@code other} value * if the document {@linkplain #has} no such field, * or if the specified field is not a (child) document - * (or other {@linkplain Expression#isDocumentOr document-like value}. + * (or other {@linkplain MqlValue#isDocumentOr document-like value}. * * @mongodb.server.release 5.0 * @param fieldName the name of the field. * @param other the other value. * @return the resulting value. */ - DocumentExpression getDocument(String fieldName, DocumentExpression other); + MqlDocument getDocument(String fieldName, MqlDocument other); /** - * Returns the {@linkplain DocumentExpression document} value of the field + * Returns the {@linkplain MqlDocument document} value of the field * with the provided {@code fieldName}, * or the {@code other} value * if the document {@linkplain #has} no such field, * or if the specified field is not a (child) document - * (or other {@linkplain Expression#isDocumentOr document-like value}. + * (or other {@linkplain MqlValue#isDocumentOr document-like value}. * * @mongodb.server.release 5.0 * @param fieldName the name of the field. * @param other the other value. * @return the resulting value. */ - default DocumentExpression getDocument(final String fieldName, final Bson other) { + default MqlDocument getDocument(final String fieldName, final Bson other) { Assertions.notNull("fieldName", fieldName); Assertions.notNull("other", other); return getDocument(fieldName, of(other)); } /** - * Returns the {@linkplain MapExpression map} value of the field + * Returns the {@linkplain MqlMap map} value of the field * with the provided {@code fieldName}. * *

    Warning: The type and presence of the resulting value is not @@ -413,16 +413,16 @@ default DocumentExpression getDocument(final String fieldName, final Bson other) * @param the type. */ @MqlUnchecked({PRESENT, TYPE}) - MapExpression<@MqlUnchecked(TYPE_ARGUMENT) T> getMap(String fieldName); + MqlMap<@MqlUnchecked(TYPE_ARGUMENT) T> getMap(String fieldName); /** - * Returns the {@linkplain MapExpression map} value of the field + * Returns the {@linkplain MqlMap map} value of the field * with the provided {@code fieldName}, * or the {@code other} value * if the document {@linkplain #has} no such field, * or if the specified field is not a map - * (or other {@linkplain Expression#isMapOr} map-like value}). + * (or other {@linkplain MqlValue#isMapOr} map-like value}). * *

    Warning: The type argument of the resulting value is not * enforced by the API. The use of this method is an @@ -434,15 +434,15 @@ default DocumentExpression getDocument(final String fieldName, final Bson other) * @return the resulting value. * @param the type. */ - MapExpression getMap(String fieldName, MapExpression<@MqlUnchecked(TYPE_ARGUMENT) ? extends T> other); + MqlMap getMap(String fieldName, MqlMap<@MqlUnchecked(TYPE_ARGUMENT) ? extends T> other); /** - * Returns the {@linkplain MapExpression map} value of the field + * Returns the {@linkplain MqlMap map} value of the field * with the provided {@code fieldName}, * or the {@code other} value * if the document {@linkplain #has} no such field, * or if the specified field is not a map - * (or other {@linkplain Expression#isMapOr} map-like value}). + * (or other {@linkplain MqlValue#isMapOr} map-like value}). * *

    Warning: The type argument of the resulting value is not * enforced by the API. The use of this method is an @@ -454,14 +454,14 @@ default DocumentExpression getDocument(final String fieldName, final Bson other) * @return the resulting value. * @param the type. */ - default MapExpression<@MqlUnchecked(TYPE_ARGUMENT) T> getMap(final String fieldName, final Bson other) { + default MqlMap<@MqlUnchecked(TYPE_ARGUMENT) T> getMap(final String fieldName, final Bson other) { Assertions.notNull("fieldName", fieldName); Assertions.notNull("other", other); return getMap(fieldName, ofMap(other)); } /** - * Returns the {@linkplain ArrayExpression array} value of the field + * Returns the {@linkplain MqlArray array} value of the field * with the provided {@code fieldName}. * *

    Warning: The type and presence of the resulting value is not @@ -477,10 +477,10 @@ default DocumentExpression getDocument(final String fieldName, final Bson other) * @param the type. */ @MqlUnchecked({PRESENT, TYPE}) - ArrayExpression<@MqlUnchecked(TYPE_ARGUMENT) T> getArray(String fieldName); + MqlArray<@MqlUnchecked(TYPE_ARGUMENT) T> getArray(String fieldName); /** - * Returns the {@linkplain ArrayExpression array} value of the field + * Returns the {@linkplain MqlArray array} value of the field * with the provided {@code fieldName}, * or the {@code other} value if the field is not an array * or if the document {@linkplain #has} no such field. @@ -495,7 +495,7 @@ default DocumentExpression getDocument(final String fieldName, final Bson other) * @return the resulting value. * @param the type. */ - ArrayExpression<@MqlUnchecked(TYPE_ARGUMENT) T> getArray(String fieldName, ArrayExpression other); + MqlArray<@MqlUnchecked(TYPE_ARGUMENT) T> getArray(String fieldName, MqlArray other); /** * Returns a document with the same fields as {@code this} document, but @@ -508,10 +508,10 @@ default DocumentExpression getDocument(final String fieldName, final Bson other) * @param other the other document. * @return the resulting value. */ - DocumentExpression merge(DocumentExpression other); + MqlDocument merge(MqlDocument other); /** - * {@code this} document as a {@linkplain MapExpression map}. + * {@code this} document as a {@linkplain MqlMap map}. * *

    Warning: The type argument of the resulting value is not * enforced by the API. The use of this method is an @@ -521,27 +521,27 @@ default DocumentExpression getDocument(final String fieldName, final Bson other) * @param the type. */ - MapExpression asMap(); + MqlMap asMap(); /** * The result of passing {@code this} value to the provided function. * Equivalent to {@code f.apply(this)}, and allows lambdas and static, * user-defined functions to use the chaining syntax. * - * @see Expression#passTo + * @see MqlValue#passTo * @param f the function to apply. * @return the resulting value. * @param the type of the resulting value. */ - R passDocumentTo(Function f); + R passDocumentTo(Function f); /** * The result of applying the provided switch mapping to {@code this} value. * - * @see Expression#switchOn + * @see MqlValue#switchOn * @param mapping the switch mapping. * @return the resulting value. * @param the type of the resulting value. */ - R switchDocumentOn(Function, ? extends BranchesTerminal> mapping); + R switchDocumentOn(Function, ? extends BranchesTerminal> mapping); } diff --git a/driver-core/src/main/com/mongodb/client/model/expressions/EntryExpression.java b/driver-core/src/main/com/mongodb/client/model/mql/MqlEntry.java similarity index 71% rename from driver-core/src/main/com/mongodb/client/model/expressions/EntryExpression.java rename to driver-core/src/main/com/mongodb/client/model/mql/MqlEntry.java index 924b3472221..abe6f4414cd 100644 --- a/driver-core/src/main/com/mongodb/client/model/expressions/EntryExpression.java +++ b/driver-core/src/main/com/mongodb/client/model/mql/MqlEntry.java @@ -14,22 +14,22 @@ * limitations under the License. */ -package com.mongodb.client.model.expressions; +package com.mongodb.client.model.mql; import com.mongodb.annotations.Beta; import com.mongodb.annotations.Sealed; -import static com.mongodb.client.model.expressions.Expressions.of; +import static com.mongodb.client.model.mql.MqlValues.of; /** - * A map entry {@linkplain Expression value} in the context + * A map entry {@linkplain MqlValue value} in the context * of the MongoDB Query Language (MQL). An entry has a - * {@linkplain StringExpression string} key and some - * {@linkplain Expression value}. Entries are used with - * {@linkplain MapExpression maps}. + * {@linkplain MqlString string} key and some + * {@linkplain MqlValue value}. Entries are used with + * {@linkplain MqlMap maps}. * - *

    Entries are {@linkplain Expression#isDocumentOr document-like} and - * {@linkplain Expression#isMapOr map-like}, unless the method returning the + *

    Entries are {@linkplain MqlValue#isDocumentOr document-like} and + * {@linkplain MqlValue#isMapOr map-like}, unless the method returning the * entry specifies otherwise. * * @param The type of the value @@ -37,7 +37,7 @@ */ @Sealed @Beta(Beta.Reason.CLIENT) -public interface EntryExpression extends Expression { +public interface MqlEntry extends MqlValue { /** * The key of {@code this} entry. @@ -45,7 +45,7 @@ public interface EntryExpression extends Expression { * @mongodb.server.release 5.0 * @return the key. */ - StringExpression getKey(); + MqlString getKey(); /** * The value of {@code this} entry. @@ -63,7 +63,7 @@ public interface EntryExpression extends Expression { * @param value the value. * @return the resulting entry. */ - EntryExpression setValue(T value); + MqlEntry setValue(T value); /** * An entry with the same value as {@code this} entry, and the @@ -73,7 +73,7 @@ public interface EntryExpression extends Expression { * @param key the key. * @return the resulting entry. */ - EntryExpression setKey(StringExpression key); + MqlEntry setKey(MqlString key); /** * An entry with the same value as {@code this} entry, and the @@ -83,7 +83,7 @@ public interface EntryExpression extends Expression { * @param key the key. * @return the resulting entry. */ - default EntryExpression setKey(final String key) { + default MqlEntry setKey(final String key) { return setKey(of(key)); } } diff --git a/driver-core/src/main/com/mongodb/client/model/expressions/MqlExpression.java b/driver-core/src/main/com/mongodb/client/model/mql/MqlExpression.java similarity index 67% rename from driver-core/src/main/com/mongodb/client/model/expressions/MqlExpression.java rename to driver-core/src/main/com/mongodb/client/model/mql/MqlExpression.java index 041b0d63a2e..7d743ad5726 100644 --- a/driver-core/src/main/com/mongodb/client/model/expressions/MqlExpression.java +++ b/driver-core/src/main/com/mongodb/client/model/mql/MqlExpression.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.mongodb.client.model.expressions; +package com.mongodb.client.model.mql; import com.mongodb.assertions.Assertions; import org.bson.BsonArray; @@ -28,13 +28,13 @@ 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.ofNull; -import static com.mongodb.client.model.expressions.Expressions.ofStringArray; +import static com.mongodb.client.model.mql.MqlValues.of; +import static com.mongodb.client.model.mql.MqlValues.ofNull; +import static com.mongodb.client.model.mql.MqlValues.ofStringArray; -final class MqlExpression - implements Expression, BooleanExpression, IntegerExpression, NumberExpression, - StringExpression, DateExpression, DocumentExpression, ArrayExpression, MapExpression, EntryExpression { +final class MqlExpression + implements MqlValue, MqlBoolean, MqlInteger, MqlNumber, + MqlString, MqlDate, MqlDocument, MqlArray, MqlMap, MqlEntry { private final Function fn; @@ -56,7 +56,7 @@ private AstPlaceholder astDoc(final String name, final BsonDocument value) { } @Override - public StringExpression getKey() { + public MqlString getKey() { return new MqlExpression<>(getFieldInternal("k")); } @@ -66,12 +66,12 @@ public T getValue() { } @Override - public EntryExpression setValue(final T value) { + public MqlEntry setValue(final T value) { return setFieldInternal("v", value); } @Override - public EntryExpression setKey(final StringExpression key) { + public MqlEntry setKey(final MqlString key) { return setFieldInternal("k", key); } @@ -93,7 +93,7 @@ private Function astWrapped(final String name) { new BsonArray(Collections.singletonList(this.toBsonValue(cr))))); } - private Function ast(final String name, final Expression param1) { + private Function ast(final String name, final MqlValue param1) { return (cr) -> { BsonArray value = new BsonArray(); value.add(this.toBsonValue(cr)); @@ -102,7 +102,7 @@ private Function ast(final String name, final Exp }; } - private Function ast(final String name, final Expression param1, final Expression param2) { + private Function ast(final String name, final MqlValue param1, final MqlValue param2) { return (cr) -> { BsonArray value = new BsonArray(); value.add(this.toBsonValue(cr)); @@ -117,8 +117,8 @@ private Function ast(final String name, final Exp * the only implementation of Expression and all subclasses, so this will * not mis-cast an expression as anything else. */ - static BsonValue toBsonValue(final CodecRegistry cr, final Expression expression) { - return ((MqlExpression) expression).toBsonValue(cr); + static BsonValue toBsonValue(final CodecRegistry cr, final MqlValue mqlValue) { + return ((MqlExpression) mqlValue).toBsonValue(cr); } /** @@ -126,45 +126,45 @@ static BsonValue toBsonValue(final CodecRegistry cr, final Expression expression * extend Expression or its subtypes, so MqlExpression will implement any R. */ @SuppressWarnings("unchecked") - R assertImplementsAllExpressions() { + R assertImplementsAllExpressions() { return (R) this; } - private static R newMqlExpression(final Function ast) { + private static R newMqlExpression(final Function ast) { return new MqlExpression<>(ast).assertImplementsAllExpressions(); } - private R variable(final String variable) { + private R variable(final String variable) { return newMqlExpression((cr) -> new AstPlaceholder(new BsonString(variable))); } - /** @see BooleanExpression */ + /** @see MqlBoolean */ @Override - public BooleanExpression not() { + public MqlBoolean not() { return new MqlExpression<>(ast("$not")); } @Override - public BooleanExpression or(final BooleanExpression other) { + public MqlBoolean or(final MqlBoolean other) { Assertions.notNull("other", other); return new MqlExpression<>(ast("$or", other)); } @Override - public BooleanExpression and(final BooleanExpression other) { + public MqlBoolean and(final MqlBoolean other) { Assertions.notNull("other", other); return new MqlExpression<>(ast("$and", other)); } @Override - public R cond(final R ifTrue, final R ifFalse) { + public R cond(final R ifTrue, final R ifFalse) { Assertions.notNull("ifTrue", ifTrue); Assertions.notNull("ifFalse", ifFalse); return newMqlExpression(ast("$cond", ifTrue, ifFalse)); } - /** @see DocumentExpression */ + /** @see MqlDocument */ private Function getFieldInternal(final String fieldName) { return (cr) -> { @@ -178,129 +178,129 @@ private Function getFieldInternal(final String fi } @Override - public Expression getField(final String fieldName) { + public MqlValue getField(final String fieldName) { Assertions.notNull("fieldName", fieldName); return new MqlExpression<>(getFieldInternal(fieldName)); } @Override - public BooleanExpression getBoolean(final String fieldName) { + public MqlBoolean getBoolean(final String fieldName) { Assertions.notNull("fieldName", fieldName); return new MqlExpression<>(getFieldInternal(fieldName)); } @Override - public BooleanExpression getBoolean(final String fieldName, final BooleanExpression other) { + public MqlBoolean getBoolean(final String fieldName, final MqlBoolean other) { Assertions.notNull("fieldName", fieldName); Assertions.notNull("other", other); return getBoolean(fieldName).isBooleanOr(other); } @Override - public NumberExpression getNumber(final String fieldName) { + public MqlNumber getNumber(final String fieldName) { Assertions.notNull("fieldName", fieldName); return new MqlExpression<>(getFieldInternal(fieldName)); } @Override - public NumberExpression getNumber(final String fieldName, final NumberExpression other) { + public MqlNumber getNumber(final String fieldName, final MqlNumber other) { Assertions.notNull("fieldName", fieldName); Assertions.notNull("other", other); return getNumber(fieldName).isNumberOr(other); } @Override - public IntegerExpression getInteger(final String fieldName) { + public MqlInteger getInteger(final String fieldName) { Assertions.notNull("fieldName", fieldName); return new MqlExpression<>(getFieldInternal(fieldName)); } @Override - public IntegerExpression getInteger(final String fieldName, final IntegerExpression other) { + public MqlInteger getInteger(final String fieldName, final MqlInteger other) { Assertions.notNull("fieldName", fieldName); Assertions.notNull("other", other); return getInteger(fieldName).isIntegerOr(other); } @Override - public StringExpression getString(final String fieldName) { + public MqlString getString(final String fieldName) { Assertions.notNull("fieldName", fieldName); return new MqlExpression<>(getFieldInternal(fieldName)); } @Override - public StringExpression getString(final String fieldName, final StringExpression other) { + public MqlString getString(final String fieldName, final MqlString other) { Assertions.notNull("fieldName", fieldName); Assertions.notNull("other", other); return getString(fieldName).isStringOr(other); } @Override - public DateExpression getDate(final String fieldName) { + public MqlDate getDate(final String fieldName) { Assertions.notNull("fieldName", fieldName); return new MqlExpression<>(getFieldInternal(fieldName)); } @Override - public DateExpression getDate(final String fieldName, final DateExpression other) { + public MqlDate getDate(final String fieldName, final MqlDate other) { Assertions.notNull("fieldName", fieldName); Assertions.notNull("other", other); return getDate(fieldName).isDateOr(other); } @Override - public DocumentExpression getDocument(final String fieldName) { + public MqlDocument getDocument(final String fieldName) { Assertions.notNull("fieldName", fieldName); return new MqlExpression<>(getFieldInternal(fieldName)); } @Override - public MapExpression getMap(final String fieldName) { + public MqlMap getMap(final String fieldName) { Assertions.notNull("fieldName", fieldName); return new MqlExpression<>(getFieldInternal(fieldName)); } @Override - public MapExpression getMap(final String fieldName, final MapExpression other) { + public MqlMap getMap(final String fieldName, final MqlMap other) { Assertions.notNull("fieldName", fieldName); Assertions.notNull("other", other); return getMap(fieldName).isMapOr(other); } @Override - public DocumentExpression getDocument(final String fieldName, final DocumentExpression other) { + public MqlDocument getDocument(final String fieldName, final MqlDocument other) { Assertions.notNull("fieldName", fieldName); Assertions.notNull("other", other); return getDocument(fieldName).isDocumentOr(other); } @Override - public ArrayExpression getArray(final String fieldName) { + public MqlArray getArray(final String fieldName) { Assertions.notNull("fieldName", fieldName); return new MqlExpression<>(getFieldInternal(fieldName)); } @Override - public ArrayExpression getArray(final String fieldName, final ArrayExpression other) { + public MqlArray getArray(final String fieldName, final MqlArray other) { Assertions.notNull("fieldName", fieldName); Assertions.notNull("other", other); return getArray(fieldName).isArrayOr(other); } @Override - public DocumentExpression merge(final DocumentExpression other) { + public MqlDocument merge(final MqlDocument other) { Assertions.notNull("other", other); return new MqlExpression<>(ast("$mergeObjects", other)); } @Override - public DocumentExpression setField(final String fieldName, final Expression value) { + public MqlDocument setField(final String fieldName, final MqlValue value) { Assertions.notNull("fieldName", fieldName); Assertions.notNull("value", value); return setFieldInternal(fieldName, value); } - private MqlExpression setFieldInternal(final String fieldName, final Expression value) { + private MqlExpression setFieldInternal(final String fieldName, final MqlValue value) { Assertions.notNull("fieldName", fieldName); return newMqlExpression((cr) -> astDoc("$setField", new BsonDocument() .append("field", new BsonString(fieldName)) @@ -309,124 +309,124 @@ private MqlExpression setFieldInternal(final String fieldName, final Expressi } @Override - public DocumentExpression unsetField(final String fieldName) { + public MqlDocument unsetField(final String fieldName) { Assertions.notNull("fieldName", fieldName); return newMqlExpression((cr) -> astDoc("$unsetField", new BsonDocument() .append("field", new BsonString(fieldName)) .append("input", this.toBsonValue(cr)))); } - /** @see Expression */ + /** @see MqlValue */ @Override - public R passTo(final Function f) { + public R passTo(final Function f) { Assertions.notNull("f", f); return f.apply(this.assertImplementsAllExpressions()); } @Override - public R switchOn(final Function, ? extends BranchesTerminal> mapping) { + public R switchOn(final Function, ? extends BranchesTerminal> mapping) { Assertions.notNull("mapping", mapping); return switchMapInternal(this.assertImplementsAllExpressions(), mapping.apply(new Branches<>())); } @Override - public R passBooleanTo(final Function f) { + public R passBooleanTo(final Function f) { Assertions.notNull("f", f); return f.apply(this.assertImplementsAllExpressions()); } @Override - public R switchBooleanOn(final Function, ? extends BranchesTerminal> mapping) { + public R switchBooleanOn(final Function, ? extends BranchesTerminal> mapping) { Assertions.notNull("mapping", mapping); return switchMapInternal(this.assertImplementsAllExpressions(), mapping.apply(new Branches<>())); } @Override - public R passIntegerTo(final Function f) { + public R passIntegerTo(final Function f) { Assertions.notNull("f", f); return f.apply(this.assertImplementsAllExpressions()); } @Override - public R switchIntegerOn(final Function, ? extends BranchesTerminal> mapping) { + public R switchIntegerOn(final Function, ? extends BranchesTerminal> mapping) { Assertions.notNull("mapping", mapping); return switchMapInternal(this.assertImplementsAllExpressions(), mapping.apply(new Branches<>())); } @Override - public R passNumberTo(final Function f) { + public R passNumberTo(final Function f) { Assertions.notNull("f", f); return f.apply(this.assertImplementsAllExpressions()); } @Override - public R switchNumberOn(final Function, ? extends BranchesTerminal> mapping) { + public R switchNumberOn(final Function, ? extends BranchesTerminal> mapping) { Assertions.notNull("mapping", mapping); return switchMapInternal(this.assertImplementsAllExpressions(), mapping.apply(new Branches<>())); } @Override - public R passStringTo(final Function f) { + public R passStringTo(final Function f) { Assertions.notNull("f", f); return f.apply(this.assertImplementsAllExpressions()); } @Override - public R switchStringOn(final Function, ? extends BranchesTerminal> mapping) { + public R switchStringOn(final Function, ? extends BranchesTerminal> mapping) { Assertions.notNull("mapping", mapping); return switchMapInternal(this.assertImplementsAllExpressions(), mapping.apply(new Branches<>())); } @Override - public R passDateTo(final Function f) { + public R passDateTo(final Function f) { Assertions.notNull("f", f); return f.apply(this.assertImplementsAllExpressions()); } @Override - public R switchDateOn(final Function, ? extends BranchesTerminal> mapping) { + public R switchDateOn(final Function, ? extends BranchesTerminal> mapping) { Assertions.notNull("mapping", mapping); return switchMapInternal(this.assertImplementsAllExpressions(), mapping.apply(new Branches<>())); } @Override - public R passArrayTo(final Function, ? extends R> f) { + public R passArrayTo(final Function, ? extends R> f) { Assertions.notNull("f", f); return f.apply(this.assertImplementsAllExpressions()); } @Override - public R switchArrayOn(final Function>, ? extends BranchesTerminal, ? extends R>> mapping) { + public R switchArrayOn(final Function>, ? extends BranchesTerminal, ? extends R>> mapping) { Assertions.notNull("mapping", mapping); return switchMapInternal(this.assertImplementsAllExpressions(), mapping.apply(new Branches<>())); } @Override - public R passMapTo(final Function, ? extends R> f) { + public R passMapTo(final Function, ? extends R> f) { Assertions.notNull("f", f); return f.apply(this.assertImplementsAllExpressions()); } @Override - public R switchMapOn(final Function>, ? extends BranchesTerminal, ? extends R>> mapping) { + public R switchMapOn(final Function>, ? extends BranchesTerminal, ? extends R>> mapping) { Assertions.notNull("mapping", mapping); return switchMapInternal(this.assertImplementsAllExpressions(), mapping.apply(new Branches<>())); } @Override - public R passDocumentTo(final Function f) { + public R passDocumentTo(final Function f) { Assertions.notNull("f", f); return f.apply(this.assertImplementsAllExpressions()); } @Override - public R switchDocumentOn(final Function, ? extends BranchesTerminal> mapping) { + public R switchDocumentOn(final Function, ? extends BranchesTerminal> mapping) { Assertions.notNull("mapping", mapping); return switchMapInternal(this.assertImplementsAllExpressions(), mapping.apply(new Branches<>())); } - private R0 switchMapInternal( + private R0 switchMapInternal( final T0 value, final BranchesTerminal construct) { return newMqlExpression((cr) -> { BsonArray branches = new BsonArray(); @@ -445,69 +445,69 @@ private R0 switchMapInternal( } @Override - public BooleanExpression eq(final Expression other) { + public MqlBoolean eq(final MqlValue other) { Assertions.notNull("other", other); return new MqlExpression<>(ast("$eq", other)); } @Override - public BooleanExpression ne(final Expression other) { + public MqlBoolean ne(final MqlValue other) { Assertions.notNull("other", other); return new MqlExpression<>(ast("$ne", other)); } @Override - public BooleanExpression gt(final Expression other) { + public MqlBoolean gt(final MqlValue other) { Assertions.notNull("other", other); return new MqlExpression<>(ast("$gt", other)); } @Override - public BooleanExpression gte(final Expression other) { + public MqlBoolean gte(final MqlValue other) { Assertions.notNull("other", other); return new MqlExpression<>(ast("$gte", other)); } @Override - public BooleanExpression lt(final Expression other) { + public MqlBoolean lt(final MqlValue other) { Assertions.notNull("other", other); return new MqlExpression<>(ast("$lt", other)); } @Override - public BooleanExpression lte(final Expression other) { + public MqlBoolean lte(final MqlValue other) { Assertions.notNull("other", other); return new MqlExpression<>(ast("$lte", other)); } - BooleanExpression isBoolean() { + MqlBoolean isBoolean() { return new MqlExpression<>(astWrapped("$type")).eq(of("bool")); } @Override - public BooleanExpression isBooleanOr(final BooleanExpression other) { + public MqlBoolean isBooleanOr(final MqlBoolean other) { Assertions.notNull("other", other); return this.isBoolean().cond(this, other); } - BooleanExpression isNumber() { + MqlBoolean isNumber() { return new MqlExpression<>(astWrapped("$isNumber")); } @Override - public NumberExpression isNumberOr(final NumberExpression other) { + public MqlNumber isNumberOr(final MqlNumber other) { Assertions.notNull("other", other); return this.isNumber().cond(this, other); } - BooleanExpression isInteger() { + MqlBoolean isInteger() { return switchOn(on -> on .isNumber(v -> v.round().eq(v)) .defaults(v -> of(false))); } @Override - public IntegerExpression isIntegerOr(final IntegerExpression other) { + public MqlInteger isIntegerOr(final MqlInteger other) { Assertions.notNull("other", other); /* The server does not evaluate both branches of and/or/cond unless needed. @@ -519,31 +519,31 @@ public IntegerExpression isIntegerOr(final IntegerExpression other) { this.isNumber().and(this.eq(this.round())) */ return this.switchOn(on -> on - .isNumber(v -> (IntegerExpression) v.round().eq(v).cond(v, other)) + .isNumber(v -> (MqlInteger) v.round().eq(v).cond(v, other)) .defaults(v -> other)); } - BooleanExpression isString() { + MqlBoolean isString() { return new MqlExpression<>(astWrapped("$type")).eq(of("string")); } @Override - public StringExpression isStringOr(final StringExpression other) { + public MqlString isStringOr(final MqlString other) { Assertions.notNull("other", other); return this.isString().cond(this, other); } - BooleanExpression isDate() { + MqlBoolean isDate() { return ofStringArray("date").contains(new MqlExpression<>(astWrapped("$type"))); } @Override - public DateExpression isDateOr(final DateExpression other) { + public MqlDate isDateOr(final MqlDate other) { Assertions.notNull("other", other); return this.isDate().cond(this, other); } - BooleanExpression isArray() { + MqlBoolean isArray() { return new MqlExpression<>(astWrapped("$isArray")); } @@ -557,38 +557,38 @@ BooleanExpression isArray() { */ @SuppressWarnings("unchecked") @Override - public ArrayExpression isArrayOr(final ArrayExpression other) { + public MqlArray isArrayOr(final MqlArray other) { Assertions.notNull("other", other); - return (ArrayExpression) this.isArray().cond(this.assertImplementsAllExpressions(), other); + return (MqlArray) this.isArray().cond(this.assertImplementsAllExpressions(), other); } - BooleanExpression isDocumentOrMap() { + MqlBoolean isDocumentOrMap() { return new MqlExpression<>(astWrapped("$type")).eq(of("object")); } @Override - public R isDocumentOr(final R other) { + public R isDocumentOr(final R other) { Assertions.notNull("other", other); return this.isDocumentOrMap().cond(this.assertImplementsAllExpressions(), other); } @Override - public MapExpression isMapOr(final MapExpression other) { + public MqlMap isMapOr(final MqlMap other) { Assertions.notNull("other", other); MqlExpression isMap = (MqlExpression) this.isDocumentOrMap(); return newMqlExpression(isMap.ast("$cond", this.assertImplementsAllExpressions(), other)); } - BooleanExpression isNull() { + MqlBoolean isNull() { return this.eq(ofNull()); } @Override - public StringExpression asString() { + public MqlString asString() { return new MqlExpression<>(astWrapped("$toString")); } - private Function convertInternal(final String to, final Expression other) { + private Function convertInternal(final String to, final MqlValue other) { return (cr) -> astDoc("$convert", new BsonDocument() .append("input", this.fn.apply(cr).bsonValue) .append("onError", toBsonValue(cr, other)) @@ -596,15 +596,15 @@ private Function convertInternal(final String to, } @Override - public IntegerExpression parseInteger() { - Expression asLong = new MqlExpression<>(ast("$toLong")); + public MqlInteger parseInteger() { + MqlValue asLong = new MqlExpression<>(ast("$toLong")); return new MqlExpression<>(convertInternal("int", asLong)); } - /** @see ArrayExpression */ + /** @see MqlArray */ @Override - public ArrayExpression map(final Function in) { + public MqlArray map(final Function in) { Assertions.notNull("in", in); T varThis = variable("$$this"); return new MqlExpression<>((cr) -> astDoc("$map", new BsonDocument() @@ -613,7 +613,7 @@ public ArrayExpression map(final Function filter(final Function predicate) { + public MqlArray filter(final Function predicate) { Assertions.notNull("predicate", predicate); T varThis = variable("$$this"); return new MqlExpression<>((cr) -> astDoc("$filter", new BsonDocument() @@ -621,7 +621,7 @@ public ArrayExpression filter(final Function sort() { + MqlArray sort() { return new MqlExpression<>((cr) -> astDoc("$sortArray", new BsonDocument() .append("input", this.toBsonValue(cr)) .append("sortBy", new BsonInt32(1)))); @@ -637,34 +637,34 @@ private T reduce(final T initialValue, final BinaryOperator in) { } @Override - public BooleanExpression any(final Function predicate) { + public MqlBoolean any(final Function predicate) { Assertions.notNull("predicate", predicate); - MqlExpression array = (MqlExpression) this.map(predicate); + MqlExpression array = (MqlExpression) this.map(predicate); return array.reduce(of(false), (a, b) -> a.or(b)); } @Override - public BooleanExpression all(final Function predicate) { + public MqlBoolean all(final Function predicate) { Assertions.notNull("predicate", predicate); - MqlExpression array = (MqlExpression) this.map(predicate); + MqlExpression array = (MqlExpression) this.map(predicate); return array.reduce(of(true), (a, b) -> a.and(b)); } @SuppressWarnings("unchecked") @Override - public NumberExpression sum(final Function mapper) { + public MqlNumber sum(final Function mapper) { Assertions.notNull("mapper", mapper); // no sum that returns IntegerExpression, both have same erasure - MqlExpression array = (MqlExpression) this.map(mapper); + MqlExpression array = (MqlExpression) this.map(mapper); return array.reduce(of(0), (a, b) -> a.add(b)); } @SuppressWarnings("unchecked") @Override - public NumberExpression multiply(final Function mapper) { + public MqlNumber multiply(final Function mapper) { Assertions.notNull("mapper", mapper); - MqlExpression array = (MqlExpression) this.map(mapper); - return array.reduce(of(1), (NumberExpression a, NumberExpression b) -> a.multiply(b)); + MqlExpression array = (MqlExpression) this.map(mapper); + return array.reduce(of(1), (MqlNumber a, MqlNumber b) -> a.multiply(b)); } @Override @@ -680,7 +680,7 @@ public T min(final T other) { } @Override - public ArrayExpression maxN(final IntegerExpression n) { + public MqlArray maxN(final MqlInteger n) { Assertions.notNull("n", n); return newMqlExpression((CodecRegistry cr) -> astDoc("$maxN", new BsonDocument() .append("input", toBsonValue(cr, this)) @@ -688,7 +688,7 @@ public ArrayExpression maxN(final IntegerExpression n) { } @Override - public ArrayExpression minN(final IntegerExpression n) { + public MqlArray minN(final MqlInteger n) { Assertions.notNull("n", n); return newMqlExpression((CodecRegistry cr) -> astDoc("$minN", new BsonDocument() .append("input", toBsonValue(cr, this)) @@ -696,36 +696,36 @@ public ArrayExpression minN(final IntegerExpression n) { } @Override - public StringExpression join(final Function mapper) { + public MqlString join(final Function mapper) { Assertions.notNull("mapper", mapper); - MqlExpression array = (MqlExpression) this.map(mapper); + MqlExpression array = (MqlExpression) this.map(mapper); return array.reduce(of(""), (a, b) -> a.concat(b)); } @SuppressWarnings("unchecked") @Override - public ArrayExpression concat(final Function> mapper) { + public MqlArray concat(final Function> mapper) { Assertions.notNull("mapper", mapper); - MqlExpression> array = (MqlExpression>) this.map(mapper); - return array.reduce(Expressions.ofArray(), (a, b) -> a.concat(b)); + MqlExpression> array = (MqlExpression>) this.map(mapper); + return array.reduce(MqlValues.ofArray(), (a, b) -> a.concat(b)); } @SuppressWarnings("unchecked") @Override - public ArrayExpression union(final Function> mapper) { + public MqlArray union(final Function> mapper) { Assertions.notNull("mapper", mapper); Assertions.notNull("mapper", mapper); - MqlExpression> array = (MqlExpression>) this.map(mapper); - return array.reduce(Expressions.ofArray(), (a, b) -> a.union(b)); + MqlExpression> array = (MqlExpression>) this.map(mapper); + return array.reduce(MqlValues.ofArray(), (a, b) -> a.union(b)); } @Override - public IntegerExpression size() { + public MqlInteger size() { return new MqlExpression<>(astWrapped("$size")); } @Override - public T elementAt(final IntegerExpression i) { + public T elementAt(final MqlInteger i) { Assertions.notNull("i", i); return new MqlExpression<>(ast("$arrayElemAt", i)) .assertImplementsAllExpressions(); @@ -744,7 +744,7 @@ public T last() { } @Override - public BooleanExpression contains(final T value) { + public MqlBoolean contains(final T value) { Assertions.notNull("value", value); String name = "$in"; return new MqlExpression<>((cr) -> { @@ -756,14 +756,14 @@ public BooleanExpression contains(final T value) { } @Override - public ArrayExpression concat(final ArrayExpression other) { + public MqlArray concat(final MqlArray other) { Assertions.notNull("other", other); return new MqlExpression<>(ast("$concatArrays", other)) .assertImplementsAllExpressions(); } @Override - public ArrayExpression slice(final IntegerExpression start, final IntegerExpression length) { + public MqlArray slice(final MqlInteger start, final MqlInteger length) { Assertions.notNull("start", start); Assertions.notNull("length", length); return new MqlExpression<>(ast("$slice", start, length)) @@ -771,178 +771,178 @@ public ArrayExpression slice(final IntegerExpression start, final IntegerExpr } @Override - public ArrayExpression union(final ArrayExpression other) { + public MqlArray union(final MqlArray other) { Assertions.notNull("other", other); return new MqlExpression<>(ast("$setUnion", other)) .assertImplementsAllExpressions(); } @Override - public ArrayExpression distinct() { + public MqlArray distinct() { return new MqlExpression<>(astWrapped("$setUnion")); } - /** @see IntegerExpression - * @see NumberExpression */ + /** @see MqlInteger + * @see MqlNumber */ @Override - public IntegerExpression multiply(final NumberExpression other) { + public MqlInteger multiply(final MqlNumber other) { Assertions.notNull("other", other); return newMqlExpression(ast("$multiply", other)); } @Override - public NumberExpression add(final NumberExpression other) { + public MqlNumber add(final MqlNumber other) { Assertions.notNull("other", other); return new MqlExpression<>(ast("$add", other)); } @Override - public NumberExpression divide(final NumberExpression other) { + public MqlNumber divide(final MqlNumber other) { Assertions.notNull("other", other); return new MqlExpression<>(ast("$divide", other)); } @Override - public NumberExpression max(final NumberExpression other) { + public MqlNumber max(final MqlNumber other) { Assertions.notNull("other", other); return new MqlExpression<>(ast("$max", other)); } @Override - public NumberExpression min(final NumberExpression other) { + public MqlNumber min(final MqlNumber other) { Assertions.notNull("other", other); return new MqlExpression<>(ast("$min", other)); } @Override - public IntegerExpression round() { + public MqlInteger round() { return new MqlExpression<>(ast("$round")); } @Override - public NumberExpression round(final IntegerExpression place) { + public MqlNumber round(final MqlInteger place) { Assertions.notNull("place", place); return new MqlExpression<>(ast("$round", place)); } @Override - public IntegerExpression multiply(final IntegerExpression other) { + public MqlInteger multiply(final MqlInteger other) { Assertions.notNull("other", other); return new MqlExpression<>(ast("$multiply", other)); } @Override - public IntegerExpression abs() { + public MqlInteger abs() { return newMqlExpression(ast("$abs")); } @Override - public DateExpression millisecondsToDate() { + public MqlDate millisecondsToDate() { return newMqlExpression(ast("$toDate")); } @Override - public NumberExpression subtract(final NumberExpression other) { + public MqlNumber subtract(final MqlNumber other) { Assertions.notNull("other", other); return new MqlExpression<>(ast("$subtract", other)); } @Override - public IntegerExpression add(final IntegerExpression other) { + public MqlInteger add(final MqlInteger other) { Assertions.notNull("other", other); return new MqlExpression<>(ast("$add", other)); } @Override - public IntegerExpression subtract(final IntegerExpression other) { + public MqlInteger subtract(final MqlInteger other) { Assertions.notNull("other", other); return new MqlExpression<>(ast("$subtract", other)); } @Override - public IntegerExpression max(final IntegerExpression other) { + public MqlInteger max(final MqlInteger other) { Assertions.notNull("other", other); return new MqlExpression<>(ast("$max", other)); } @Override - public IntegerExpression min(final IntegerExpression other) { + public MqlInteger min(final MqlInteger other) { Assertions.notNull("other", other); return new MqlExpression<>(ast("$min", other)); } - /** @see DateExpression */ + /** @see MqlDate */ - private MqlExpression usingTimezone(final String name, final StringExpression timezone) { + private MqlExpression usingTimezone(final String name, final MqlString timezone) { return new MqlExpression<>((cr) -> astDoc(name, new BsonDocument() .append("date", this.toBsonValue(cr)) .append("timezone", toBsonValue(cr, timezone)))); } @Override - public IntegerExpression year(final StringExpression timezone) { + public MqlInteger year(final MqlString timezone) { Assertions.notNull("timezone", timezone); return usingTimezone("$year", timezone); } @Override - public IntegerExpression month(final StringExpression timezone) { + public MqlInteger month(final MqlString timezone) { Assertions.notNull("timezone", timezone); return usingTimezone("$month", timezone); } @Override - public IntegerExpression dayOfMonth(final StringExpression timezone) { + public MqlInteger dayOfMonth(final MqlString timezone) { Assertions.notNull("timezone", timezone); return usingTimezone("$dayOfMonth", timezone); } @Override - public IntegerExpression dayOfWeek(final StringExpression timezone) { + public MqlInteger dayOfWeek(final MqlString timezone) { Assertions.notNull("timezone", timezone); return usingTimezone("$dayOfWeek", timezone); } @Override - public IntegerExpression dayOfYear(final StringExpression timezone) { + public MqlInteger dayOfYear(final MqlString timezone) { Assertions.notNull("timezone", timezone); return usingTimezone("$dayOfYear", timezone); } @Override - public IntegerExpression hour(final StringExpression timezone) { + public MqlInteger hour(final MqlString timezone) { Assertions.notNull("timezone", timezone); return usingTimezone("$hour", timezone); } @Override - public IntegerExpression minute(final StringExpression timezone) { + public MqlInteger minute(final MqlString timezone) { Assertions.notNull("timezone", timezone); return usingTimezone("$minute", timezone); } @Override - public IntegerExpression second(final StringExpression timezone) { + public MqlInteger second(final MqlString timezone) { Assertions.notNull("timezone", timezone); return usingTimezone("$second", timezone); } @Override - public IntegerExpression week(final StringExpression timezone) { + public MqlInteger week(final MqlString timezone) { Assertions.notNull("timezone", timezone); return usingTimezone("$week", timezone); } @Override - public IntegerExpression millisecond(final StringExpression timezone) { + public MqlInteger millisecond(final MqlString timezone) { Assertions.notNull("timezone", timezone); return usingTimezone("$millisecond", timezone); } @Override - public StringExpression asString(final StringExpression timezone, final StringExpression format) { + public MqlString asString(final MqlString timezone, final MqlString format) { Assertions.notNull("timezone", timezone); Assertions.notNull("format", format); return newMqlExpression((cr) -> astDoc("$dateToString", new BsonDocument() @@ -952,7 +952,7 @@ public StringExpression asString(final StringExpression timezone, final StringEx } @Override - public DateExpression parseDate(final StringExpression timezone, final StringExpression format) { + public MqlDate parseDate(final MqlString timezone, final MqlString format) { Assertions.notNull("timezone", timezone); Assertions.notNull("format", format); return newMqlExpression((cr) -> astDoc("$dateFromString", new BsonDocument() @@ -962,7 +962,7 @@ public DateExpression parseDate(final StringExpression timezone, final StringExp } @Override - public DateExpression parseDate(final StringExpression format) { + public MqlDate parseDate(final MqlString format) { Assertions.notNull("format", format); return newMqlExpression((cr) -> astDoc("$dateFromString", new BsonDocument() .append("dateString", this.toBsonValue(cr)) @@ -970,77 +970,77 @@ public DateExpression parseDate(final StringExpression format) { } @Override - public DateExpression parseDate() { + public MqlDate parseDate() { return newMqlExpression((cr) -> astDoc("$dateFromString", new BsonDocument() .append("dateString", this.toBsonValue(cr)))); } - /** @see StringExpression */ + /** @see MqlString */ @Override - public StringExpression toLower() { + public MqlString toLower() { return new MqlExpression<>(ast("$toLower")); } @Override - public StringExpression toUpper() { + public MqlString toUpper() { return new MqlExpression<>(ast("$toUpper")); } @Override - public StringExpression concat(final StringExpression other) { + public MqlString concat(final MqlString other) { Assertions.notNull("other", other); return new MqlExpression<>(ast("$concat", other)); } @Override - public IntegerExpression strLen() { + public MqlInteger strLen() { return new MqlExpression<>(ast("$strLenCP")); } @Override - public IntegerExpression strLenBytes() { + public MqlInteger strLenBytes() { return new MqlExpression<>(ast("$strLenBytes")); } @Override - public StringExpression substr(final IntegerExpression start, final IntegerExpression length) { + public MqlString substr(final MqlInteger start, final MqlInteger length) { Assertions.notNull("start", start); Assertions.notNull("length", length); return new MqlExpression<>(ast("$substrCP", start, length)); } @Override - public StringExpression substrBytes(final IntegerExpression start, final IntegerExpression length) { + public MqlString substrBytes(final MqlInteger start, final MqlInteger length) { Assertions.notNull("start", start); Assertions.notNull("length", length); return new MqlExpression<>(ast("$substrBytes", start, length)); } @Override - public BooleanExpression has(final StringExpression key) { + public MqlBoolean has(final MqlString key) { Assertions.notNull("key", key); return get(key).ne(ofRem()); } @Override - public BooleanExpression has(final String fieldName) { + public MqlBoolean has(final String fieldName) { Assertions.notNull("fieldName", fieldName); return this.has(of(fieldName)); } - static R ofRem() { + static R ofRem() { // $$REMOVE is intentionally not exposed to users return new MqlExpression<>((cr) -> new MqlExpression.AstPlaceholder(new BsonString("$$REMOVE"))) .assertImplementsAllExpressions(); } - /** @see MapExpression - * @see EntryExpression */ + /** @see MqlMap + * @see MqlEntry */ @Override - public T get(final StringExpression key) { + public T get(final MqlString key) { Assertions.notNull("key", key); return newMqlExpression((cr) -> astDoc("$getField", new BsonDocument() .append("input", this.fn.apply(cr).bsonValue) @@ -1049,7 +1049,7 @@ public T get(final StringExpression key) { @SuppressWarnings("unchecked") @Override - public T get(final StringExpression key, final T other) { + public T get(final MqlString key, final T other) { Assertions.notNull("key", key); Assertions.notNull("other", other); MqlExpression mqlExpression = (MqlExpression) get(key); @@ -1057,7 +1057,7 @@ public T get(final StringExpression key, final T other) { } @Override - public MapExpression set(final StringExpression key, final T value) { + public MqlMap set(final MqlString key, final T value) { Assertions.notNull("key", key); Assertions.notNull("value", value); return newMqlExpression((cr) -> astDoc("$setField", new BsonDocument() @@ -1067,7 +1067,7 @@ public MapExpression set(final StringExpression key, final T value) { } @Override - public MapExpression unset(final StringExpression key) { + public MqlMap unset(final MqlString key) { Assertions.notNull("key", key); return newMqlExpression((cr) -> astDoc("$unsetField", new BsonDocument() .append("field", toBsonValue(cr, key)) @@ -1075,34 +1075,34 @@ public MapExpression unset(final StringExpression key) { } @Override - public MapExpression merge(final MapExpression other) { + public MqlMap merge(final MqlMap other) { Assertions.notNull("other", other); return new MqlExpression<>(ast("$mergeObjects", other)); } @Override - public ArrayExpression> entrySet() { + public MqlArray> entrySet() { return newMqlExpression(ast("$objectToArray")); } @Override - public MapExpression asMap( - final Function> mapper) { + public MqlMap asMap( + final Function> mapper) { Assertions.notNull("mapper", mapper); @SuppressWarnings("unchecked") - MqlExpression> array = (MqlExpression>) this.map(mapper); + MqlExpression> array = (MqlExpression>) this.map(mapper); return newMqlExpression(array.astWrapped("$arrayToObject")); } @SuppressWarnings("unchecked") @Override - public MapExpression asMap() { - return (MapExpression) this; + public MqlMap asMap() { + return (MqlMap) this; } @SuppressWarnings("unchecked") @Override - public R asDocument() { + public R asDocument() { return (R) this; } } diff --git a/driver-core/src/main/com/mongodb/client/model/expressions/MqlExpressionCodec.java b/driver-core/src/main/com/mongodb/client/model/mql/MqlExpressionCodec.java similarity index 97% rename from driver-core/src/main/com/mongodb/client/model/expressions/MqlExpressionCodec.java rename to driver-core/src/main/com/mongodb/client/model/mql/MqlExpressionCodec.java index 877d9c8b26b..cc6da160345 100644 --- a/driver-core/src/main/com/mongodb/client/model/expressions/MqlExpressionCodec.java +++ b/driver-core/src/main/com/mongodb/client/model/mql/MqlExpressionCodec.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.mongodb.client.model.expressions; +package com.mongodb.client.model.mql; import org.bson.BsonReader; import org.bson.BsonValue; diff --git a/driver-core/src/main/com/mongodb/client/model/expressions/IntegerExpression.java b/driver-core/src/main/com/mongodb/client/model/mql/MqlInteger.java similarity index 66% rename from driver-core/src/main/com/mongodb/client/model/expressions/IntegerExpression.java rename to driver-core/src/main/com/mongodb/client/model/mql/MqlInteger.java index 6aa7183b96b..f9eace4bc7b 100644 --- a/driver-core/src/main/com/mongodb/client/model/expressions/IntegerExpression.java +++ b/driver-core/src/main/com/mongodb/client/model/mql/MqlInteger.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.mongodb.client.model.expressions; +package com.mongodb.client.model.mql; import com.mongodb.annotations.Beta; import com.mongodb.annotations.Sealed; @@ -22,16 +22,16 @@ import java.util.function.Function; /** - * An integer {@linkplain Expression value} in the context of the MongoDB Query - * Language (MQL). Integers are a subset of {@linkplain NumberExpression numbers}, + * An integer {@linkplain MqlValue value} in the context of the MongoDB Query + * Language (MQL). Integers are a subset of {@linkplain MqlNumber numbers}, * and so, for example, the integer 0 and the number 0 are - * {@linkplain #eq(Expression) equal}. + * {@linkplain #eq(MqlValue) equal}. * * @since 4.9.0 */ @Sealed @Beta(Beta.Reason.CLIENT) -public interface IntegerExpression extends NumberExpression { +public interface MqlInteger extends MqlNumber { /** * The product of multiplying {@code this} and the {@code other} value. @@ -39,7 +39,7 @@ public interface IntegerExpression extends NumberExpression { * @param other the other value. * @return the resulting value. */ - IntegerExpression multiply(IntegerExpression other); + MqlInteger multiply(MqlInteger other); /** * The product of multiplying {@code this} and the {@code other} value. @@ -47,8 +47,8 @@ public interface IntegerExpression extends NumberExpression { * @param other the other value. * @return the resulting value. */ - default IntegerExpression multiply(final int other) { - return this.multiply(Expressions.of(other)); + default MqlInteger multiply(final int other) { + return this.multiply(MqlValues.of(other)); } /** @@ -57,7 +57,7 @@ default IntegerExpression multiply(final int other) { * @param other the other value. * @return the resulting value. */ - IntegerExpression add(IntegerExpression other); + MqlInteger add(MqlInteger other); /** * The sum of adding {@code this} and the {@code other} value. @@ -65,8 +65,8 @@ default IntegerExpression multiply(final int other) { * @param other the other value. * @return the resulting value. */ - default IntegerExpression add(final int other) { - return this.add(Expressions.of(other)); + default MqlInteger add(final int other) { + return this.add(MqlValues.of(other)); } /** @@ -75,7 +75,7 @@ default IntegerExpression add(final int other) { * @param other the other value. * @return the resulting value. */ - IntegerExpression subtract(IntegerExpression other); + MqlInteger subtract(MqlInteger other); /** * The difference of subtracting the {@code other} value from {@code this}. @@ -83,63 +83,63 @@ default IntegerExpression add(final int other) { * @param other the other value. * @return the resulting value. */ - default IntegerExpression subtract(final int other) { - return this.subtract(Expressions.of(other)); + default MqlInteger subtract(final int other) { + return this.subtract(MqlValues.of(other)); } /** - * The {@linkplain #gt(Expression) larger} value of {@code this} + * The {@linkplain #gt(MqlValue) larger} value of {@code this} * and the {@code other} value. * * @param other the other value. * @return the resulting value. */ - IntegerExpression max(IntegerExpression other); + MqlInteger max(MqlInteger other); /** - * The {@linkplain #lt(Expression) smaller} value of {@code this} + * The {@linkplain #lt(MqlValue) smaller} value of {@code this} * and the {@code other} value. * * @param other the other value. * @return the resulting value. */ - IntegerExpression min(IntegerExpression other); + MqlInteger min(MqlInteger other); /** * The absolute value of {@code this} value. * * @return the resulting value. */ - IntegerExpression abs(); + MqlInteger abs(); /** - * The {@linkplain DateExpression date} corresponding to {@code this} value + * The {@linkplain MqlDate date} corresponding to {@code this} value * when taken to be the number of milliseconds since the Unix epoch. * * @mongodb.server.release 4.0 * @return the resulting value. */ - DateExpression millisecondsToDate(); + MqlDate millisecondsToDate(); /** * The result of passing {@code this} value to the provided function. * Equivalent to {@code f.apply(this)}, and allows lambdas and static, * user-defined functions to use the chaining syntax. * - * @see Expression#passTo + * @see MqlValue#passTo * @param f the function to apply. * @return the resulting value. * @param the type of the resulting value. */ - R passIntegerTo(Function f); + R passIntegerTo(Function f); /** * The result of applying the provided switch mapping to {@code this} value. * - * @see Expression#switchOn + * @see MqlValue#switchOn * @param mapping the switch mapping. * @return the resulting value. * @param the type of the resulting value. */ - R switchIntegerOn(Function, ? extends BranchesTerminal> mapping); + R switchIntegerOn(Function, ? extends BranchesTerminal> mapping); } diff --git a/driver-core/src/main/com/mongodb/client/model/expressions/MapExpression.java b/driver-core/src/main/com/mongodb/client/model/mql/MqlMap.java similarity index 74% rename from driver-core/src/main/com/mongodb/client/model/expressions/MapExpression.java rename to driver-core/src/main/com/mongodb/client/model/mql/MqlMap.java index 2ccece44e1e..b25af981105 100644 --- a/driver-core/src/main/com/mongodb/client/model/expressions/MapExpression.java +++ b/driver-core/src/main/com/mongodb/client/model/mql/MqlMap.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.mongodb.client.model.expressions; +package com.mongodb.client.model.mql; import com.mongodb.annotations.Beta; import com.mongodb.annotations.Sealed; @@ -22,13 +22,13 @@ import java.util.function.Function; -import static com.mongodb.client.model.expressions.Expressions.of; -import static com.mongodb.client.model.expressions.MqlUnchecked.Unchecked.PRESENT; +import static com.mongodb.client.model.mql.MqlValues.of; +import static com.mongodb.client.model.mql.MqlUnchecked.Unchecked.PRESENT; /** - * A map {@link Expression value} in the context of the MongoDB Query + * A map {@link MqlValue value} in the context of the MongoDB Query * Language (MQL). A map is a finite set of - * {@link EntryExpression entries} of a certain type. + * {@link MqlEntry entries} of a certain type. * No entry key is repeated. It is a mapping from keys to values. * * @param the type of the entry values @@ -36,7 +36,7 @@ */ @Sealed @Beta(Beta.Reason.CLIENT) -public interface MapExpression extends Expression { +public interface MqlMap extends MqlValue { /** * Whether {@code this} map has a value (including null) for @@ -45,7 +45,7 @@ public interface MapExpression extends Expression { * @param key the key. * @return the resulting value. */ - BooleanExpression has(StringExpression key); + MqlBoolean has(MqlString key); /** * Whether {@code this} map has a value (including null) for @@ -54,7 +54,7 @@ public interface MapExpression extends Expression { * @param key the key. * @return the resulting value. */ - default BooleanExpression has(final String key) { + default MqlBoolean has(final String key) { return has(of(key)); } @@ -63,20 +63,20 @@ default BooleanExpression has(final String key) { * *

    Warning: The use of this method is an unchecked assertion that * the key is present (which may be confirmed via {@link #has}). See - * {@link #get(StringExpression, Expression)} for a typesafe variant. + * {@link #get(MqlString, MqlValue)} for a typesafe variant. * * @param key the key. * @return the value. */ @MqlUnchecked(PRESENT) - T get(StringExpression key); + T get(MqlString key); /** * The value corresponding to the provided key. * *

    Warning: The use of this method is an unchecked assertion that * the key is present (which may be confirmed via {@link #has}). See - * {@link #get(StringExpression, Expression)} for a typesafe variant. + * {@link #get(MqlString, MqlValue)} for a typesafe variant. * * @param key the key. * @return the value. @@ -90,18 +90,18 @@ default T get(final String key) { /** * The value corresponding to the provided {@code key}, or the * {@code other} value if an entry for the key is not - * {@linkplain #has(StringExpression) present}. + * {@linkplain #has(MqlString) present}. * * @param key the key. * @param other the other value. * @return the resulting value. */ - T get(StringExpression key, T other); + T get(MqlString key, T other); /** * The value corresponding to the provided {@code key}, or the * {@code other} value if an entry for the key is not - * {@linkplain #has(StringExpression) present}. + * {@linkplain #has(MqlString) present}. * * @param key the key. * @param other the other value. @@ -123,7 +123,7 @@ default T get(final String key, final T other) { * @param value the value. * @return the resulting value. */ - MapExpression set(StringExpression key, T value); + MqlMap set(MqlString key, T value); /** * Returns a map with the same entries as {@code this} map, but with @@ -135,7 +135,7 @@ default T get(final String key, final T other) { * @param value the value. * @return the resulting value. */ - default MapExpression set(final String key, final T value) { + default MqlMap set(final String key, final T value) { Assertions.notNull("key", key); Assertions.notNull("value", value); return set(of(key), value); @@ -143,7 +143,7 @@ default MapExpression set(final String key, final T value) { /** * Returns a map with the same entries as {@code this} map, but which - * {@linkplain #has(StringExpression) has} no entry with the specified + * {@linkplain #has(MqlString) has} no entry with the specified * {@code key}. * *

    This does not affect the original map. @@ -151,11 +151,11 @@ default MapExpression set(final String key, final T value) { * @param key the key. * @return the resulting value. */ - MapExpression unset(StringExpression key); + MqlMap unset(MqlString key); /** * Returns a map with the same entries as {@code this} map, but which - * {@linkplain #has(StringExpression) has} no entry with the specified + * {@linkplain #has(MqlString) has} no entry with the specified * {@code key}. * *

    This does not affect the original map. @@ -163,7 +163,7 @@ default MapExpression set(final String key, final T value) { * @param key the key. * @return the resulting value. */ - default MapExpression unset(final String key) { + default MqlMap unset(final String key) { Assertions.notNull("key", key); return unset(of(key)); } @@ -179,44 +179,44 @@ default MapExpression unset(final String key) { * @param other the other map. * @return the resulting value. */ - MapExpression merge(MapExpression other); + MqlMap merge(MqlMap other); /** - * The {@linkplain EntryExpression entries} of this map as an array. + * The {@linkplain MqlEntry entries} of this map as an array. * No guarantee is made regarding order. * - * @see ArrayExpression#asMap + * @see MqlArray#asMap * @return the resulting value. */ - ArrayExpression> entrySet(); + MqlArray> entrySet(); /** - * {@code this} map as a {@linkplain DocumentExpression document}. + * {@code this} map as a {@linkplain MqlDocument document}. * * @return the resulting value. * @param the resulting type. */ - R asDocument(); + R asDocument(); /** * The result of passing {@code this} value to the provided function. * Equivalent to {@code f.apply(this)}, and allows lambdas and static, * user-defined functions to use the chaining syntax. * - * @see Expression#passTo + * @see MqlValue#passTo * @param f the function to apply. * @return the resulting value. * @param the type of the resulting value. */ - R passMapTo(Function, ? extends R> f); + R passMapTo(Function, ? extends R> f); /** * The result of applying the provided switch mapping to {@code this} value. * - * @see Expression#switchOn + * @see MqlValue#switchOn * @param mapping the switch mapping. * @return the resulting value. * @param the type of the resulting value. */ - R switchMapOn(Function>, ? extends BranchesTerminal, ? extends R>> mapping); + R switchMapOn(Function>, ? extends BranchesTerminal, ? extends R>> mapping); } diff --git a/driver-core/src/main/com/mongodb/client/model/expressions/NumberExpression.java b/driver-core/src/main/com/mongodb/client/model/mql/MqlNumber.java similarity index 71% rename from driver-core/src/main/com/mongodb/client/model/expressions/NumberExpression.java rename to driver-core/src/main/com/mongodb/client/model/mql/MqlNumber.java index 97b5972cd3b..e2dbed35393 100644 --- a/driver-core/src/main/com/mongodb/client/model/expressions/NumberExpression.java +++ b/driver-core/src/main/com/mongodb/client/model/mql/MqlNumber.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.mongodb.client.model.expressions; +package com.mongodb.client.model.mql; import com.mongodb.annotations.Beta; import com.mongodb.annotations.Sealed; @@ -23,16 +23,16 @@ import java.util.function.Function; /** - * A number {@linkplain Expression value} in the context of the MongoDB Query - * Language (MQL). {@linkplain IntegerExpression Integers} are a subset of + * A number {@linkplain MqlValue value} in the context of the MongoDB Query + * Language (MQL). {@linkplain MqlInteger Integers} are a subset of * numbers, and so, for example, the integer 0 and the number 0 are - * {@linkplain #eq(Expression) equal}. + * {@linkplain #eq(MqlValue) equal}. * * @since 4.9.0 */ @Sealed @Beta(Beta.Reason.CLIENT) -public interface NumberExpression extends Expression { +public interface MqlNumber extends MqlValue { /** * The product of multiplying {@code this} and the {@code other} value. @@ -40,7 +40,7 @@ public interface NumberExpression extends Expression { * @param other the other value. * @return the resulting value. */ - NumberExpression multiply(NumberExpression other); + MqlNumber multiply(MqlNumber other); /** * The product of multiplying {@code this} and the {@code other} value. @@ -48,9 +48,9 @@ public interface NumberExpression extends Expression { * @param other the other value. * @return the resulting value. */ - default NumberExpression multiply(final Number other) { + default MqlNumber multiply(final Number other) { Assertions.notNull("other", other); - return this.multiply(Expressions.numberToExpression(other)); + return this.multiply(MqlValues.numberToExpression(other)); } /** @@ -61,7 +61,7 @@ default NumberExpression multiply(final Number other) { * @param other the other value. * @return the resulting value. */ - NumberExpression divide(NumberExpression other); + MqlNumber divide(MqlNumber other); /** * The quotient of dividing {@code this} value by the {@code other} value. @@ -71,9 +71,9 @@ default NumberExpression multiply(final Number other) { * @param other the other value. * @return the resulting value. */ - default NumberExpression divide(final Number other) { + default MqlNumber divide(final Number other) { Assertions.notNull("other", other); - return this.divide(Expressions.numberToExpression(other)); + return this.divide(MqlValues.numberToExpression(other)); } /** @@ -82,7 +82,7 @@ default NumberExpression divide(final Number other) { * @param other the other value. * @return the resulting value. */ - NumberExpression add(NumberExpression other); + MqlNumber add(MqlNumber other); /** * The sum of adding {@code this} and the {@code other} value. @@ -90,9 +90,9 @@ default NumberExpression divide(final Number other) { * @param other the other value. * @return the resulting value. */ - default NumberExpression add(final Number other) { + default MqlNumber add(final Number other) { Assertions.notNull("other", other); - return this.add(Expressions.numberToExpression(other)); + return this.add(MqlValues.numberToExpression(other)); } /** @@ -101,7 +101,7 @@ default NumberExpression add(final Number other) { * @param other the other value. * @return the resulting value. */ - NumberExpression subtract(NumberExpression other); + MqlNumber subtract(MqlNumber other); /** * The difference of subtracting the {@code other} value from {@code this}. @@ -109,28 +109,28 @@ default NumberExpression add(final Number other) { * @param other the other value. * @return the resulting value. */ - default NumberExpression subtract(final Number other) { + default MqlNumber subtract(final Number other) { Assertions.notNull("other", other); - return this.subtract(Expressions.numberToExpression(other)); + return this.subtract(MqlValues.numberToExpression(other)); } /** - * The {@linkplain #gt(Expression) larger} value of {@code this} + * The {@linkplain #gt(MqlValue) larger} value of {@code this} * and the {@code other} value. * * @param other the other value. * @return the resulting value. */ - NumberExpression max(NumberExpression other); + MqlNumber max(MqlNumber other); /** - * The {@linkplain #lt(Expression) smaller} value of {@code this} + * The {@linkplain #lt(MqlValue) smaller} value of {@code this} * and the {@code other} value. * * @param other the other value. * @return the resulting value. */ - NumberExpression min(NumberExpression other); + MqlNumber min(MqlNumber other); /** * The integer result of rounding {@code this} to the nearest even value. @@ -138,7 +138,7 @@ default NumberExpression subtract(final Number other) { * @mongodb.server.release 4.2 * @return the resulting value. */ - IntegerExpression round(); + MqlInteger round(); /** * The result of rounding {@code this} to {@code place} decimal places @@ -149,34 +149,34 @@ default NumberExpression subtract(final Number other) { * decimal point, while negative values, to the left. * @return the resulting value. */ - NumberExpression round(IntegerExpression place); + MqlNumber round(MqlInteger place); /** * The absolute value of {@code this} value. * * @return the resulting value. */ - NumberExpression abs(); + MqlNumber abs(); /** * The result of passing {@code this} value to the provided function. * Equivalent to {@code f.apply(this)}, and allows lambdas and static, * user-defined functions to use the chaining syntax. * - * @see Expression#passTo + * @see MqlValue#passTo * @param f the function to apply. * @return the resulting value. * @param the type of the resulting value. */ - R passNumberTo(Function f); + R passNumberTo(Function f); /** * The result of applying the provided switch mapping to {@code this} value. * - * @see Expression#switchOn + * @see MqlValue#switchOn * @param mapping the switch mapping. * @return the resulting value. * @param the type of the resulting value. */ - R switchNumberOn(Function, ? extends BranchesTerminal> mapping); + R switchNumberOn(Function, ? extends BranchesTerminal> mapping); } diff --git a/driver-core/src/main/com/mongodb/client/model/expressions/StringExpression.java b/driver-core/src/main/com/mongodb/client/model/mql/MqlString.java similarity index 75% rename from driver-core/src/main/com/mongodb/client/model/expressions/StringExpression.java rename to driver-core/src/main/com/mongodb/client/model/mql/MqlString.java index 665701ec2aa..29b720add0c 100644 --- a/driver-core/src/main/com/mongodb/client/model/expressions/StringExpression.java +++ b/driver-core/src/main/com/mongodb/client/model/mql/MqlString.java @@ -14,38 +14,38 @@ * limitations under the License. */ -package com.mongodb.client.model.expressions; +package com.mongodb.client.model.mql; import com.mongodb.annotations.Beta; import com.mongodb.annotations.Sealed; import java.util.function.Function; -import static com.mongodb.client.model.expressions.Expressions.of; +import static com.mongodb.client.model.mql.MqlValues.of; /** - * A string {@linkplain Expression value} in the context of the MongoDB Query + * A string {@linkplain MqlValue value} in the context of the MongoDB Query * Language (MQL). * * @since 4.9.0 */ @Sealed @Beta(Beta.Reason.CLIENT) -public interface StringExpression extends Expression { +public interface MqlString extends MqlValue { /** * Converts {@code this} string to lowercase. * * @return the resulting value. */ - StringExpression toLower(); + MqlString toLower(); /** * Converts {@code this} string to uppercase. * * @return the resulting value. */ - StringExpression toUpper(); + MqlString toUpper(); /** * The concatenation of {@code this} string, followed by @@ -54,21 +54,21 @@ public interface StringExpression extends Expression { * @param other the other value. * @return the resulting value. */ - StringExpression concat(StringExpression other); + MqlString concat(MqlString other); /** * The number of Unicode code points in {@code this} string. * * @return the resulting value. */ - IntegerExpression strLen(); + MqlInteger strLen(); /** * The number of UTF-8 encoded bytes in {@code this} string. * * @return the resulting value. */ - IntegerExpression strLenBytes(); + MqlInteger strLenBytes(); /** * The substring of {@code this} string, from the {@code start} index @@ -82,7 +82,7 @@ public interface StringExpression extends Expression { * @param length the length in Unicode code points. * @return the resulting value. */ - StringExpression substr(IntegerExpression start, IntegerExpression length); + MqlString substr(MqlInteger start, MqlInteger length); /** * The substring of {@code this} string, from the {@code start} index @@ -96,7 +96,7 @@ public interface StringExpression extends Expression { * @param length the length in Unicode code points. * @return the resulting value. */ - default StringExpression substr(final int start, final int length) { + default MqlString substr(final int start, final int length) { return this.substr(of(start), of(length)); } @@ -112,7 +112,7 @@ default StringExpression substr(final int start, final int length) { * @param length the length in UTF-8 encoded bytes. * @return the resulting value. */ - StringExpression substrBytes(IntegerExpression start, IntegerExpression length); + MqlString substrBytes(MqlInteger start, MqlInteger length); /** * The substring of {@code this} string, from the {@code start} index @@ -126,38 +126,38 @@ default StringExpression substr(final int start, final int length) { * @param length the length in UTF-8 encoded bytes. * @return the resulting value. */ - default StringExpression substrBytes(final int start, final int length) { + default MqlString substrBytes(final int start, final int length) { return this.substrBytes(of(start), of(length)); } /** - * Converts {@code this} string to an {@linkplain IntegerExpression integer}. + * Converts {@code this} string to an {@linkplain MqlInteger integer}. * *

    This will cause an error if this string does not represent an integer. * * @mongodb.server.release 4.0 * @return the resulting value. */ - IntegerExpression parseInteger(); + MqlInteger parseInteger(); /** - * Converts {@code this} string to a {@linkplain DateExpression date}. + * Converts {@code this} string to a {@linkplain MqlDate date}. * - *

    This method behaves like {@link #parseDate(StringExpression)}, + *

    This method behaves like {@link #parseDate(MqlString)}, * with the default format, which is {@code "%Y-%m-%dT%H:%M:%S.%LZ"}. * *

    Will cause an error if this string does not represent a valid * date string (such as "2018-03-20", "2018-03-20T12:00:00Z", or * "2018-03-20T12:00:00+0500"). * - * @see DateExpression#asString() - * @see DateExpression#asString(StringExpression, StringExpression) + * @see MqlDate#asString() + * @see MqlDate#asString(MqlString, MqlString) * @return the resulting value. */ - DateExpression parseDate(); + MqlDate parseDate(); /** - * Converts {@code this} string to a {@linkplain DateExpression date}, + * Converts {@code this} string to a {@linkplain MqlDate date}, * using the specified {@code format}. UTC is assumed if the timezone * offset element is not specified in the format. * @@ -168,17 +168,17 @@ default StringExpression substrBytes(final int start, final int length) { * (for example, minute is specified, but hour is not). * Omitted finer-grained elements will be parsed to 0. * - * @see DateExpression#asString() - * @see DateExpression#asString(StringExpression, StringExpression) + * @see MqlDate#asString() + * @see MqlDate#asString(MqlString, MqlString) * @mongodb.server.release 4.0 * @mongodb.driver.manual reference/operator/aggregation/dateFromString/ Format Specifiers, UTC Offset, and Olson Timezone Identifier * @param format the format. * @return the resulting value. */ - DateExpression parseDate(StringExpression format); + MqlDate parseDate(MqlString format); /** - * Converts {@code this} string to a {@linkplain DateExpression date}, + * Converts {@code this} string to a {@linkplain MqlDate date}, * using the specified {@code timezone} and {@code format}. * @@ -191,34 +191,34 @@ default StringExpression substrBytes(final int start, final int length) { * Will cause an error if the format includes an offset or * timezone, even if it matches the supplied {@code timezone}. * - * @see DateExpression#asString() - * @see DateExpression#asString(StringExpression, StringExpression) + * @see MqlDate#asString() + * @see MqlDate#asString(MqlString, MqlString) * @mongodb.driver.manual reference/operator/aggregation/dateFromString/ Format Specifiers, UTC Offset, and Olson Timezone Identifier * @param format the format. * @param timezone the UTC Offset or Olson Timezone Identifier. * @return the resulting value. */ - DateExpression parseDate(StringExpression timezone, StringExpression format); + MqlDate parseDate(MqlString timezone, MqlString format); /** * The result of passing {@code this} value to the provided function. * Equivalent to {@code f.apply(this)}, and allows lambdas and static, * user-defined functions to use the chaining syntax. * - * @see Expression#passTo + * @see MqlValue#passTo * @param f the function to apply. * @return the resulting value. * @param the type of the resulting value. */ - R passStringTo(Function f); + R passStringTo(Function f); /** * The result of applying the provided switch mapping to {@code this} value. * - * @see Expression#switchOn + * @see MqlValue#switchOn * @param mapping the switch mapping. * @return the resulting value. * @param the type of the resulting value. */ - R switchStringOn(Function, ? extends BranchesTerminal> mapping); + R switchStringOn(Function, ? extends BranchesTerminal> mapping); } diff --git a/driver-core/src/main/com/mongodb/client/model/expressions/MqlUnchecked.java b/driver-core/src/main/com/mongodb/client/model/mql/MqlUnchecked.java similarity index 84% rename from driver-core/src/main/com/mongodb/client/model/expressions/MqlUnchecked.java rename to driver-core/src/main/com/mongodb/client/model/mql/MqlUnchecked.java index 3ed5641c533..ec53a927b4e 100644 --- a/driver-core/src/main/com/mongodb/client/model/expressions/MqlUnchecked.java +++ b/driver-core/src/main/com/mongodb/client/model/mql/MqlUnchecked.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.mongodb.client.model.expressions; +package com.mongodb.client.model.mql; import com.mongodb.annotations.Sealed; @@ -49,18 +49,18 @@ enum Unchecked { * (raw or non-parameterized) type * implied, specified by, or inferred from the user code. * - *

    For example, {@link DocumentExpression#getBoolean(String)} + *

    For example, {@link MqlDocument#getBoolean(String)} * relies on the values of the document field being of the - * {@linkplain BooleanExpression boolean} type. + * {@linkplain MqlBoolean boolean} type. */ TYPE, /** * The API checks the raw type, but relies on the type argument * implied, specified by, or inferred from user code. * - *

    For example, {@link Expression#isArrayOr(ArrayExpression)} + *

    For example, {@link MqlValue#isArrayOr(MqlArray)} * checks that the value is of the - * {@linkplain ArrayExpression array} raw type, + * {@linkplain MqlArray array} raw type, * but relies on the elements of the array being of * the type derived from the user code. */ @@ -71,8 +71,8 @@ enum Unchecked { * specified (whether by index, name, key, position, or otherwise) * element is present in the structure involved. * - *

    For example, {@link DocumentExpression#getField(String)} relies - * on the field being present, and {@link ArrayExpression#first} relies + *

    For example, {@link MqlDocument#getField(String)} relies + * on the field being present, and {@link MqlArray#first} relies * on the array being non-empty. */ PRESENT, diff --git a/driver-core/src/main/com/mongodb/client/model/expressions/Expression.java b/driver-core/src/main/com/mongodb/client/model/mql/MqlValue.java similarity index 72% rename from driver-core/src/main/com/mongodb/client/model/expressions/Expression.java rename to driver-core/src/main/com/mongodb/client/model/mql/MqlValue.java index c397a4906d0..24c7846dcc2 100644 --- a/driver-core/src/main/com/mongodb/client/model/expressions/Expression.java +++ b/driver-core/src/main/com/mongodb/client/model/mql/MqlValue.java @@ -14,14 +14,14 @@ * limitations under the License. */ -package com.mongodb.client.model.expressions; +package com.mongodb.client.model.mql; import com.mongodb.annotations.Beta; import com.mongodb.annotations.Sealed; import java.util.function.Function; -import static com.mongodb.client.model.expressions.MqlUnchecked.Unchecked.TYPE_ARGUMENT; +import static com.mongodb.client.model.mql.MqlUnchecked.Unchecked.TYPE_ARGUMENT; /** * A value in the context of the MongoDB Query Language (MQL). @@ -49,7 +49,7 @@ * *

    Values are typically initially obtained via the current document and its * fields, or specified via statically-imported methods on the - * {@link Expressions} class. + * {@link MqlValues} class. * *

    As with the Java Stream API's terminal operations, corresponding Java * values are not directly available, but must be obtained indirectly via @@ -58,11 +58,11 @@ * through these "terminal operations". * *

    The null value is not part of, and cannot be used as if it were part - * of, any explicit type (except the root type {@link Expression} itself). - * See {@link Expressions#ofNull} for more details. + * of, any explicit type (except the root type {@link MqlValue} itself). + * See {@link MqlValues#ofNull} for more details. * *

    This API specifies no "missing" or "undefined" value. Users may use - * {@link MapExpression#has} to check whether a value is present. + * {@link MqlMap#has} to check whether a value is present. * *

    This type hierarchy differs from the {@linkplain org.bson} types in that * they provide computational operations, the numeric types are less granular, @@ -75,7 +75,7 @@ * to optimization or other factors.) * *

    Some methods within the API constitute an assertion by the user that the - * data is of a certain type. For example, {@link DocumentExpression#getArray}} + * data is of a certain type. For example, {@link MqlDocument#getArray}} * requires that the underlying field is both an array, and an array of some * certain type. If the field is not an array in the underlying data, behaviour * is undefined by this API (though behaviours may be defined by the execution @@ -85,15 +85,15 @@ *

    This API should be treated as sealed: * it must not be extended or implemented (unless explicitly allowed). * - * @see Expressions + * @see MqlValues * @since 4.9.0 */ @Sealed @Beta(Beta.Reason.CLIENT) -public interface Expression { +public interface MqlValue { /** - * The method {@link Expression#eq} should be used to compare values for + * The method {@link MqlValue#eq} should be used to compare values for * equality. This method checks reference equality. */ @Override @@ -102,22 +102,22 @@ public interface Expression { /** * Whether {@code this} value is equal to the {@code other} value. * - *

    The result does not correlate with {@link Expression#equals(Object)}. + *

    The result does not correlate with {@link MqlValue#equals(Object)}. * * @param other the other value. * @return the resulting value. */ - BooleanExpression eq(Expression other); + MqlBoolean eq(MqlValue other); /** * Whether {@code this} value is not equal to the {@code other} value. * - *

    The result does not correlate with {@link Expression#equals(Object)}. + *

    The result does not correlate with {@link MqlValue#equals(Object)}. * * @param other the other value. * @return the resulting value. */ - BooleanExpression ne(Expression other); + MqlBoolean ne(MqlValue other); /** * Whether {@code this} value is greater than the {@code other} value. @@ -125,7 +125,7 @@ public interface Expression { * @param other the other value. * @return the resulting value. */ - BooleanExpression gt(Expression other); + MqlBoolean gt(MqlValue other); /** * Whether {@code this} value is greater than or equal to the {@code other} @@ -134,7 +134,7 @@ public interface Expression { * @param other the other value. * @return the resulting value. */ - BooleanExpression gte(Expression other); + MqlBoolean gte(MqlValue other); /** * Whether {@code this} value is less than the {@code other} value. @@ -142,7 +142,7 @@ public interface Expression { * @param other the other value. * @return the resulting value. */ - BooleanExpression lt(Expression other); + MqlBoolean lt(MqlValue other); /** * Whether {@code this} value is less than or equal to the {@code other} @@ -151,20 +151,20 @@ public interface Expression { * @param other the other value. * @return the resulting value. */ - BooleanExpression lte(Expression other); + MqlBoolean lte(MqlValue other); /** - * {@code this} value as a {@linkplain BooleanExpression boolean} if + * {@code this} value as a {@linkplain MqlBoolean boolean} if * {@code this} is a boolean, or the {@code other} boolean value if * {@code this} is null, or is missing, or is of any other non-boolean type. * * @param other the other value. * @return the resulting value. */ - BooleanExpression isBooleanOr(BooleanExpression other); + MqlBoolean isBooleanOr(MqlBoolean other); /** - * {@code this} value as a {@linkplain NumberExpression number} if + * {@code this} value as a {@linkplain MqlNumber number} if * {@code this} is a number, or the {@code other} number value if * {@code this} is null, or is missing, or is of any other non-number type. * @@ -172,10 +172,10 @@ public interface Expression { * @param other the other value. * @return the resulting value. */ - NumberExpression isNumberOr(NumberExpression other); + MqlNumber isNumberOr(MqlNumber other); /** - * {@code this} value as an {@linkplain IntegerExpression integer} if + * {@code this} value as an {@linkplain MqlInteger integer} if * {@code this} is an integer, or the {@code other} integer value if * {@code this} is null, or is missing, or is of any other non-integer type. * @@ -183,30 +183,30 @@ public interface Expression { * @param other the other value. * @return the resulting value. */ - IntegerExpression isIntegerOr(IntegerExpression other); + MqlInteger isIntegerOr(MqlInteger other); /** - * {@code this} value as a {@linkplain StringExpression string} if + * {@code this} value as a {@linkplain MqlString string} if * {@code this} is a string, or the {@code other} string value if * {@code this} is null, or is missing, or is of any other non-string type. * * @param other the other value. * @return the resulting value. */ - StringExpression isStringOr(StringExpression other); + MqlString isStringOr(MqlString other); /** - * {@code this} value as a {@linkplain DateExpression boolean} if + * {@code this} value as a {@linkplain MqlDate boolean} if * {@code this} is a date, or the {@code other} date value if * {@code this} is null, or is missing, or is of any other non-date type. * * @param other the other value. * @return the resulting value. */ - DateExpression isDateOr(DateExpression other); + MqlDate isDateOr(MqlDate other); /** - * {@code this} value as a {@linkplain ArrayExpression array} if + * {@code this} value as a {@linkplain MqlArray array} if * {@code this} is an array, or the {@code other} array value if * {@code this} is null, or is missing, or is of any other non-array type. * @@ -221,12 +221,12 @@ public interface Expression { * @return the resulting value. * @param the type of the elements of the resulting array. */ - ArrayExpression<@MqlUnchecked(TYPE_ARGUMENT) T> isArrayOr(ArrayExpression other); + MqlArray<@MqlUnchecked(TYPE_ARGUMENT) T> isArrayOr(MqlArray other); /** - * {@code this} value as a {@linkplain DocumentExpression document} if + * {@code this} value as a {@linkplain MqlDocument document} if * {@code this} is a document (or document-like value, see - * {@link MapExpression} and {@link EntryExpression}) + * {@link MqlMap} and {@link MqlEntry}) * or the {@code other} document value if {@code this} is null, * or is missing, or is of any other non-document type. * @@ -234,12 +234,12 @@ public interface Expression { * @return the resulting value. * @param the type. */ - T isDocumentOr(T other); + T isDocumentOr(T other); /** - * {@code this} value as a {@linkplain MapExpression map} if + * {@code this} value as a {@linkplain MqlMap map} if * {@code this} is a map (or map-like value, see - * {@link DocumentExpression} and {@link EntryExpression}) + * {@link MqlDocument} and {@link MqlEntry}) * or the {@code other} map value if {@code this} is null, * or is missing, or is of any other non-map type. * @@ -254,25 +254,25 @@ public interface Expression { * @return the resulting value. * @param the type of the values of the resulting map. */ - MapExpression isMapOr(MapExpression<@MqlUnchecked(TYPE_ARGUMENT) ? extends T> other); + MqlMap isMapOr(MqlMap<@MqlUnchecked(TYPE_ARGUMENT) ? extends T> other); /** - * The {@linkplain StringExpression string} representation of {@code this} value. + * The {@linkplain MqlString string} representation of {@code this} value. * *

    This will cause an error if the type cannot be converted - * to a {@linkplain StringExpression string}, as is the case with - * {@linkplain ArrayExpression arrays}, - * {@linkplain DocumentExpression documents}, - * {@linkplain MapExpression maps}, - * {@linkplain EntryExpression entries}, and the - * {@linkplain Expressions#ofNull() null value}. + * to a {@linkplain MqlString string}, as is the case with + * {@linkplain MqlArray arrays}, + * {@linkplain MqlDocument documents}, + * {@linkplain MqlMap maps}, + * {@linkplain MqlEntry entries}, and the + * {@linkplain MqlValues#ofNull() null value}. * * @mongodb.server.release 4.0 - * @see StringExpression#parseDate() - * @see StringExpression#parseInteger() + * @see MqlString#parseDate() + * @see MqlString#parseInteger() * @return the resulting value. */ - StringExpression asString(); + MqlString asString(); /** * The result of passing {@code this} value to the provided function. @@ -282,20 +282,20 @@ public interface Expression { *

    The appropriate type-based variant should be used when the type * of {@code this} is known. * - * @see BooleanExpression#passBooleanTo - * @see IntegerExpression#passIntegerTo - * @see NumberExpression#passNumberTo - * @see StringExpression#passStringTo - * @see DateExpression#passDateTo - * @see ArrayExpression#passArrayTo - * @see MapExpression#passMapTo - * @see DocumentExpression#passDocumentTo + * @see MqlBoolean#passBooleanTo + * @see MqlInteger#passIntegerTo + * @see MqlNumber#passNumberTo + * @see MqlString#passStringTo + * @see MqlDate#passDateTo + * @see MqlArray#passArrayTo + * @see MqlMap#passMapTo + * @see MqlDocument#passDocumentTo * * @param f the function to apply. * @return the resulting value. * @param the type of the resulting value. */ - R passTo(Function f); + R passTo(Function f); /** * The result of applying the provided switch mapping to {@code this} value. @@ -317,18 +317,18 @@ public interface Expression { *

    The appropriate type-based variant should be used when the type * of {@code this} is known. * - * @see BooleanExpression#switchBooleanOn - * @see IntegerExpression#switchIntegerOn - * @see NumberExpression#switchNumberOn - * @see StringExpression#switchStringOn - * @see DateExpression#switchDateOn - * @see ArrayExpression#switchArrayOn - * @see MapExpression#switchMapOn - * @see DocumentExpression#switchDocumentOn + * @see MqlBoolean#switchBooleanOn + * @see MqlInteger#switchIntegerOn + * @see MqlNumber#switchNumberOn + * @see MqlString#switchStringOn + * @see MqlDate#switchDateOn + * @see MqlArray#switchArrayOn + * @see MqlMap#switchMapOn + * @see MqlDocument#switchDocumentOn * * @param mapping the switch mapping. * @return the resulting value. * @param the type of the resulting value. */ - R switchOn(Function, ? extends BranchesTerminal> mapping); + R switchOn(Function, ? extends BranchesTerminal> mapping); } diff --git a/driver-core/src/main/com/mongodb/client/model/expressions/Expressions.java b/driver-core/src/main/com/mongodb/client/model/mql/MqlValues.java similarity index 72% rename from driver-core/src/main/com/mongodb/client/model/expressions/Expressions.java rename to driver-core/src/main/com/mongodb/client/model/mql/MqlValues.java index 19b9c0a16ca..cbfe80b34e6 100644 --- a/driver-core/src/main/com/mongodb/client/model/expressions/Expressions.java +++ b/driver-core/src/main/com/mongodb/client/model/mql/MqlValues.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.mongodb.client.model.expressions; +package com.mongodb.client.model.mql; import com.mongodb.annotations.Beta; import com.mongodb.assertions.Assertions; @@ -36,42 +36,42 @@ import java.util.ArrayList; import java.util.List; -import static com.mongodb.client.model.expressions.MqlExpression.AstPlaceholder; -import static com.mongodb.client.model.expressions.MqlExpression.toBsonValue; -import static com.mongodb.client.model.expressions.MqlUnchecked.Unchecked.TYPE_ARGUMENT; +import static com.mongodb.client.model.mql.MqlExpression.AstPlaceholder; +import static com.mongodb.client.model.mql.MqlExpression.toBsonValue; +import static com.mongodb.client.model.mql.MqlUnchecked.Unchecked.TYPE_ARGUMENT; /** - * Convenience methods related to {@link Expression}, used primarily to + * Convenience methods related to {@link MqlValue}, used primarily to * produce values in the context of the MongoDB Query Language (MQL). * * @since 4.9.0 */ @Beta(Beta.Reason.CLIENT) -public final class Expressions { +public final class MqlValues { - private Expressions() {} + private MqlValues() {} /** - * Returns a {@linkplain BooleanExpression boolean} value corresponding to + * Returns a {@linkplain MqlBoolean boolean} value corresponding to * the provided {@code boolean} primitive. * * @param of the {@code boolean} primitive. * @return the resulting value. */ - public static BooleanExpression of(final boolean of) { + public static MqlBoolean of(final boolean of) { // we intentionally disallow ofBoolean(null) return new MqlExpression<>((codecRegistry) -> new AstPlaceholder(new BsonBoolean(of))); } /** - * Returns an {@linkplain ArrayExpression array} of - * {@linkplain BooleanExpression booleans} corresponding to + * Returns an {@linkplain MqlArray array} of + * {@linkplain MqlBoolean booleans} corresponding to * the provided {@code boolean} primitives. * * @param array the array. * @return the resulting value. */ - public static ArrayExpression ofBooleanArray(final boolean... array) { + public static MqlArray ofBooleanArray(final boolean... array) { Assertions.notNull("array", array); List list = new ArrayList<>(); for (boolean b : array) { @@ -81,25 +81,25 @@ public static ArrayExpression ofBooleanArray(final boolean... } /** - * Returns an {@linkplain IntegerExpression integer} value corresponding to + * Returns an {@linkplain MqlInteger integer} value corresponding to * the provided {@code int} primitive. * * @param of the {@code int} primitive. * @return the resulting value. */ - public static IntegerExpression of(final int of) { + public static MqlInteger of(final int of) { return new MqlExpression<>((codecRegistry) -> new AstPlaceholder(new BsonInt32(of))); } /** - * Returns an {@linkplain ArrayExpression array} of - * {@linkplain IntegerExpression integers} corresponding to + * Returns an {@linkplain MqlArray array} of + * {@linkplain MqlInteger integers} corresponding to * the provided {@code int} primitives. * * @param array the array. * @return the resulting value. */ - public static ArrayExpression ofIntegerArray(final int... array) { + public static MqlArray ofIntegerArray(final int... array) { Assertions.notNull("array", array); List list = new ArrayList<>(); for (int i : array) { @@ -109,25 +109,25 @@ public static ArrayExpression ofIntegerArray(final int... arr } /** - * Returns an {@linkplain IntegerExpression integer} value corresponding to + * Returns an {@linkplain MqlInteger integer} value corresponding to * the provided {@code long} primitive. * * @param of the {@code long} primitive. * @return the resulting value. */ - public static IntegerExpression of(final long of) { + public static MqlInteger of(final long of) { return new MqlExpression<>((codecRegistry) -> new AstPlaceholder(new BsonInt64(of))); } /** - * Returns an {@linkplain ArrayExpression array} of - * {@linkplain IntegerExpression integers} corresponding to + * Returns an {@linkplain MqlArray array} of + * {@linkplain MqlInteger integers} corresponding to * the provided {@code long} primitives. * * @param array the array. * @return the resulting value. */ - public static ArrayExpression ofIntegerArray(final long... array) { + public static MqlArray ofIntegerArray(final long... array) { Assertions.notNull("array", array); List list = new ArrayList<>(); for (long i : array) { @@ -137,25 +137,25 @@ public static ArrayExpression ofIntegerArray(final long... ar } /** - * Returns a {@linkplain NumberExpression number} value corresponding to + * Returns a {@linkplain MqlNumber number} value corresponding to * the provided {@code double} primitive. * * @param of the {@code double} primitive. * @return the resulting value. */ - public static NumberExpression of(final double of) { + public static MqlNumber of(final double of) { return new MqlExpression<>((codecRegistry) -> new AstPlaceholder(new BsonDouble(of))); } /** - * Returns an {@linkplain ArrayExpression array} of - * {@linkplain NumberExpression numbers} corresponding to + * Returns an {@linkplain MqlArray array} of + * {@linkplain MqlNumber numbers} corresponding to * the provided {@code double} primitives. * * @param array the array. * @return the resulting value. */ - public static ArrayExpression ofNumberArray(final double... array) { + public static MqlArray ofNumberArray(final double... array) { Assertions.notNull("array", array); List list = new ArrayList<>(); for (double n : array) { @@ -165,26 +165,26 @@ public static ArrayExpression ofNumberArray(final double... ar } /** - * Returns a {@linkplain NumberExpression number} value corresponding to + * Returns a {@linkplain MqlNumber number} value corresponding to * the provided {@link Decimal128}. * * @param of the {@link Decimal128}. * @return the resulting value. */ - public static NumberExpression of(final Decimal128 of) { + public static MqlNumber of(final Decimal128 of) { Assertions.notNull("Decimal128", of); return new MqlExpression<>((codecRegistry) -> new AstPlaceholder(new BsonDecimal128(of))); } /** - * Returns an {@linkplain ArrayExpression array} of - * {@linkplain NumberExpression numbers} corresponding to + * Returns an {@linkplain MqlArray array} of + * {@linkplain MqlNumber numbers} corresponding to * the provided {@link Decimal128}s. * * @param array the array. * @return the resulting value. */ - public static ArrayExpression ofNumberArray(final Decimal128... array) { + public static MqlArray ofNumberArray(final Decimal128... array) { Assertions.notNull("array", array); List result = new ArrayList<>(); for (Decimal128 e : array) { @@ -195,26 +195,26 @@ public static ArrayExpression ofNumberArray(final Decimal128.. } /** - * Returns a {@linkplain DateExpression date and time} value corresponding to + * Returns a {@linkplain MqlDate date and time} value corresponding to * the provided {@link Instant}. * * @param of the {@link Instant}. * @return the resulting value. */ - public static DateExpression of(final Instant of) { + public static MqlDate of(final Instant of) { Assertions.notNull("Instant", of); return new MqlExpression<>((codecRegistry) -> new AstPlaceholder(new BsonDateTime(of.toEpochMilli()))); } /** - * Returns an {@linkplain ArrayExpression array} of - * {@linkplain DateExpression dates} corresponding to + * Returns an {@linkplain MqlArray array} of + * {@linkplain MqlDate dates} corresponding to * the provided {@link Instant}s. * * @param array the array. * @return the resulting value. */ - public static ArrayExpression ofDateArray(final Instant... array) { + public static MqlArray ofDateArray(final Instant... array) { Assertions.notNull("array", array); List result = new ArrayList<>(); for (Instant e : array) { @@ -225,26 +225,26 @@ public static ArrayExpression ofDateArray(final Instant... array } /** - * Returns an {@linkplain StringExpression string} value corresponding to + * Returns an {@linkplain MqlString string} value corresponding to * the provided {@link String}. * * @param of the {@link String}. * @return the resulting value. */ - public static StringExpression of(final String of) { + public static MqlString of(final String of) { Assertions.notNull("String", of); return new MqlExpression<>((codecRegistry) -> new AstPlaceholder(new BsonString(of))); } /** - * Returns an {@linkplain ArrayExpression array} of - * {@linkplain StringExpression strings} corresponding to + * Returns an {@linkplain MqlArray array} of + * {@linkplain MqlString strings} corresponding to * the provided {@link String}s. * * @param array the array. * @return the resulting value. */ - public static ArrayExpression ofStringArray(final String... array) { + public static MqlArray ofStringArray(final String... array) { Assertions.notNull("array", array); List result = new ArrayList<>(); for (String e : array) { @@ -256,20 +256,20 @@ public static ArrayExpression ofStringArray(final String... ar /** * Returns a reference to the "current" - * {@linkplain DocumentExpression document} value. + * {@linkplain MqlDocument document} value. * The "current" value is the top-level document currently being processed * in the aggregation pipeline stage. * * @return a reference to the current value */ - public static DocumentExpression current() { + public static MqlDocument current() { return new MqlExpression<>((cr) -> new AstPlaceholder(new BsonString("$$CURRENT"))) .assertImplementsAllExpressions(); } /** * Returns a reference to the "current" - * value as a {@linkplain MapExpression map} value. + * value as a {@linkplain MqlMap map} value. * The "current" value is the top-level document currently being processed * in the aggregation pipeline stage. * @@ -283,21 +283,21 @@ public static DocumentExpression current() { * @return a reference to the current value as a map. * @param the type of the map's values. */ - public static MapExpression<@MqlUnchecked(TYPE_ARGUMENT) R> currentAsMap() { + public static MqlMap<@MqlUnchecked(TYPE_ARGUMENT) R> currentAsMap() { return new MqlExpression<>((cr) -> new AstPlaceholder(new BsonString("$$CURRENT"))) .assertImplementsAllExpressions(); } /** - * Returns an {@linkplain DocumentExpression array} value, containing the - * {@linkplain Expression values} provided. + * Returns an {@linkplain MqlDocument array} value, containing the + * {@linkplain MqlValue values} provided. * - * @param array the {@linkplain Expression values}. + * @param array the {@linkplain MqlValue values}. * @return the resulting value. * @param the type of the array elements. */ @SafeVarargs // nothing is stored in the array - public static ArrayExpression ofArray(final T... array) { + public static MqlArray ofArray(final T... array) { Assertions.notNull("array", array); return new MqlExpression<>((cr) -> { List list = new ArrayList<>(); @@ -310,14 +310,14 @@ public static ArrayExpression ofArray(final T... array } /** - * Returns an {@linkplain EntryExpression entry} value. + * Returns an {@linkplain MqlEntry entry} value. * * @param k the key. * @param v the value. * @return the resulting value. * @param the type of the key. */ - public static EntryExpression ofEntry(final StringExpression k, final T v) { + public static MqlEntry ofEntry(final MqlString k, final T v) { Assertions.notNull("k", k); Assertions.notNull("v", v); return new MqlExpression<>((cr) -> { @@ -329,17 +329,17 @@ public static EntryExpression ofEntry(final StringExpr } /** - * Returns an empty {@linkplain MapExpression map} value. + * Returns an empty {@linkplain MqlMap map} value. * * @param the type of the resulting map's values. * @return the resulting map value. */ - public static MapExpression ofMap() { + public static MqlMap ofMap() { return ofMap(new BsonDocument()); } /** - * Returns a {@linkplain MapExpression map} value corresponding to the + * Returns a {@linkplain MqlMap map} value corresponding to the * provided {@link Bson Bson document}. * *

    Warning: The type of the values of the resulting map are not @@ -353,20 +353,20 @@ public static MapExpression ofMap() { * @param the type of the resulting map's values. * @return the resulting map value. */ - public static MapExpression<@MqlUnchecked(TYPE_ARGUMENT) T> ofMap(final Bson map) { + public static MqlMap<@MqlUnchecked(TYPE_ARGUMENT) T> ofMap(final Bson map) { Assertions.notNull("map", map); return new MqlExpression<>((cr) -> new AstPlaceholder(new BsonDocument("$literal", map.toBsonDocument(BsonDocument.class, cr)))); } /** - * Returns a {@linkplain DocumentExpression document} value corresponding to the + * Returns a {@linkplain MqlDocument document} value corresponding to the * provided {@link Bson Bson document}. * * @param document the {@linkplain Bson BSON document}. * @return the resulting value. */ - public static DocumentExpression of(final Bson document) { + public static MqlDocument 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 @@ -379,25 +379,25 @@ public static DocumentExpression of(final Bson document) { * The null value in the context of the MongoDB Query Language (MQL). * *

    The null value is not part of, and cannot be used as if it were part - * of, any explicit type (except the root type {@link Expression} itself). + * of, any explicit type (except the root type {@link MqlValue} itself). * It has no explicit type of its own. * *

    Instead of checking that a value is null, users should generally * check that a value is of their expected type, via methods such as - * {@link Expression#isNumberOr(NumberExpression)}. Where the null value + * {@link MqlValue#isNumberOr(MqlNumber)}. Where the null value * must be checked explicitly, users may use {@link Branches#isNull} within - * {@link Expression#switchOn}. + * {@link MqlValue#switchOn}. * * @return the null value */ - public static Expression ofNull() { + public static MqlValue 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(); } - static NumberExpression numberToExpression(final Number number) { + static MqlNumber numberToExpression(final Number number) { Assertions.notNull("number", number); if (number instanceof Integer) { return of((int) number); diff --git a/driver-core/src/main/com/mongodb/client/model/expressions/SwitchCase.java b/driver-core/src/main/com/mongodb/client/model/mql/SwitchCase.java similarity index 76% rename from driver-core/src/main/com/mongodb/client/model/expressions/SwitchCase.java rename to driver-core/src/main/com/mongodb/client/model/mql/SwitchCase.java index bbdc608391c..3210a9bfdf2 100644 --- a/driver-core/src/main/com/mongodb/client/model/expressions/SwitchCase.java +++ b/driver-core/src/main/com/mongodb/client/model/mql/SwitchCase.java @@ -14,18 +14,18 @@ * limitations under the License. */ -package com.mongodb.client.model.expressions; +package com.mongodb.client.model.mql; -final class SwitchCase { - private final BooleanExpression caseValue; +final class SwitchCase { + private final MqlBoolean caseValue; private final R thenValue; - SwitchCase(final BooleanExpression caseValue, final R thenValue) { + SwitchCase(final MqlBoolean caseValue, final R thenValue) { this.caseValue = caseValue; this.thenValue = thenValue; } - BooleanExpression getCaseValue() { + MqlBoolean getCaseValue() { return caseValue; } diff --git a/driver-core/src/main/com/mongodb/client/model/expressions/package-info.java b/driver-core/src/main/com/mongodb/client/model/mql/package-info.java similarity index 82% rename from driver-core/src/main/com/mongodb/client/model/expressions/package-info.java rename to driver-core/src/main/com/mongodb/client/model/mql/package-info.java index ea2d03f818e..08cbc6195a7 100644 --- a/driver-core/src/main/com/mongodb/client/model/expressions/package-info.java +++ b/driver-core/src/main/com/mongodb/client/model/mql/package-info.java @@ -15,12 +15,12 @@ */ /** - * @see com.mongodb.client.model.expressions.Expression - * @see com.mongodb.client.model.expressions.Expressions + * @see com.mongodb.client.model.mql.MqlValue + * @see com.mongodb.client.model.mql.MqlValues * @since 4.9.0 */ @Beta(Beta.Reason.CLIENT) @NonNullApi -package com.mongodb.client.model.expressions; +package com.mongodb.client.model.mql; import com.mongodb.annotations.Beta; import com.mongodb.lang.NonNullApi; 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/mql/AbstractMqlValuesFunctionalTest.java similarity index 89% rename from driver-core/src/test/functional/com/mongodb/client/model/expressions/AbstractExpressionsFunctionalTest.java rename to driver-core/src/test/functional/com/mongodb/client/model/mql/AbstractMqlValuesFunctionalTest.java index 45c662a99ad..31a5cecd91f 100644 --- a/driver-core/src/test/functional/com/mongodb/client/model/expressions/AbstractExpressionsFunctionalTest.java +++ b/driver-core/src/test/functional/com/mongodb/client/model/mql/AbstractMqlValuesFunctionalTest.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.mongodb.client.model.expressions; +package com.mongodb.client.model.mql; import com.mongodb.client.model.Field; import com.mongodb.client.model.OperationTest; @@ -43,7 +43,7 @@ import static org.bson.conversions.Bson.DEFAULT_CODEC_REGISTRY; import static org.junit.jupiter.api.Assertions.assertEquals; -public abstract class AbstractExpressionsFunctionalTest extends OperationTest { +public abstract class AbstractMqlValuesFunctionalTest extends OperationTest { /** * Java stand-in for the "missing" value. @@ -60,18 +60,18 @@ public void tearDown() { getCollectionHelper().drop(); } - protected void assertExpression(@Nullable final Object expected, final Expression expression) { - assertExpression(expected, expression, null); + protected void assertExpression(@Nullable final Object expected, final MqlValue mqlValue) { + assertExpression(expected, mqlValue, null); } - protected void assertExpression(@Nullable final Object expected, final Expression expression, @Nullable final String expectedMql) { - assertEval(expected, expression); + protected void assertExpression(@Nullable final Object expected, final MqlValue mqlValue, @Nullable final String expectedMql) { + assertEval(expected, mqlValue); if (expectedMql == null) { return; } - BsonValue expressionValue = ((MqlExpression) expression).toBsonValue( + BsonValue expressionValue = ((MqlExpression) mqlValue).toBsonValue( fromProviders(new BsonValueCodecProvider(), DEFAULT_CODEC_REGISTRY)); BsonValue bsonValue = new BsonDocumentFragmentCodec().readValue( new JsonReader(expectedMql), @@ -79,7 +79,7 @@ protected void assertExpression(@Nullable final Object expected, final Expressio assertEquals(bsonValue, expressionValue, expressionValue.toString().replace("\"", "'")); } - private void assertEval(@Nullable final Object expected, final Expression toEvaluate) { + private void assertEval(@Nullable final Object expected, final MqlValue toEvaluate) { BsonValue evaluated = evaluate(toEvaluate); if (expected == MISSING && evaluated == null) { // if the "val" field was removed by "missing", then evaluated is null @@ -97,7 +97,7 @@ protected BsonValue toBsonValue(@Nullable final Object value) { } @Nullable - protected BsonValue evaluate(final Expression toEvaluate) { + protected BsonValue evaluate(final MqlValue toEvaluate) { Bson addFieldsStage = addFields(new Field<>("val", toEvaluate)); List stages = new ArrayList<>(); stages.add(addFieldsStage); diff --git a/driver-core/src/test/functional/com/mongodb/client/model/expressions/ArithmeticExpressionsFunctionalTest.java b/driver-core/src/test/functional/com/mongodb/client/model/mql/ArithmeticMqlValuesFunctionalTest.java similarity index 90% rename from driver-core/src/test/functional/com/mongodb/client/model/expressions/ArithmeticExpressionsFunctionalTest.java rename to driver-core/src/test/functional/com/mongodb/client/model/mql/ArithmeticMqlValuesFunctionalTest.java index f1c61ceb0fc..5bb559d13d4 100644 --- a/driver-core/src/test/functional/com/mongodb/client/model/expressions/ArithmeticExpressionsFunctionalTest.java +++ b/driver-core/src/test/functional/com/mongodb/client/model/mql/ArithmeticMqlValuesFunctionalTest.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.mongodb.client.model.expressions; +package com.mongodb.client.model.mql; import org.bson.types.Decimal128; import org.junit.jupiter.api.Test; @@ -23,15 +23,15 @@ import java.math.RoundingMode; import static com.mongodb.ClusterFixture.serverVersionAtLeast; -import static com.mongodb.client.model.expressions.Expressions.numberToExpression; -import static com.mongodb.client.model.expressions.Expressions.of; +import static com.mongodb.client.model.mql.MqlValues.numberToExpression; +import static com.mongodb.client.model.mql.MqlValues.of; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assumptions.assumeTrue; @SuppressWarnings("ConstantConditions") -class ArithmeticExpressionsFunctionalTest extends AbstractExpressionsFunctionalTest { +class ArithmeticMqlValuesFunctionalTest extends AbstractMqlValuesFunctionalTest { // https://www.mongodb.com/docs/manual/reference/operator/aggregation/#arithmetic-expression-operators @Test @@ -72,13 +72,13 @@ public void multiplyTest() { "{'$multiply': [2.0, 2]}"); // mixing integers and numbers - IntegerExpression oneInt = of(1); - NumberExpression oneNum = of(1.0); - IntegerExpression resultInt = oneInt.multiply(oneInt); - NumberExpression resultNum = oneNum.multiply(oneNum); + MqlInteger oneInt = of(1); + MqlNumber oneNum = of(1.0); + MqlInteger resultInt = oneInt.multiply(oneInt); + MqlNumber resultNum = oneNum.multiply(oneNum); // compile time error if these were IntegerExpressions: - NumberExpression r2 = oneNum.multiply(oneInt); - NumberExpression r3 = oneInt.multiply(oneNum); + MqlNumber r2 = oneNum.multiply(oneInt); + MqlNumber r3 = oneInt.multiply(oneNum); assertExpression(1, resultInt); // 1 is also a valid expected value in our API assertExpression(1.0, resultNum); @@ -149,7 +149,7 @@ public void divideTest() { @Test public void addTest() { // https://www.mongodb.com/docs/manual/reference/operator/aggregation/add/ - IntegerExpression actual = of(2).add(of(2)); + MqlInteger actual = of(2).add(of(2)); assertExpression( 2 + 2, actual, "{'$add': [2, 2]}"); @@ -182,7 +182,7 @@ public void addTest() { @Test public void subtractTest() { // https://www.mongodb.com/docs/manual/reference/operator/aggregation/subtract/ - IntegerExpression actual = of(2).subtract(of(2)); + MqlInteger actual = of(2).subtract(of(2)); assertExpression( 0, actual, @@ -200,7 +200,7 @@ public void subtractTest() { @Test public void maxTest() { // https://www.mongodb.com/docs/manual/reference/operator/aggregation/max/ - IntegerExpression actual = of(-2).max(of(2)); + MqlInteger actual = of(-2).max(of(2)); assertExpression( Math.max(-2, 2), actual, @@ -214,7 +214,7 @@ public void maxTest() { @Test public void minTest() { // https://www.mongodb.com/docs/manual/reference/operator/aggregation/min/ - IntegerExpression actual = of(-2).min(of(2)); + MqlInteger actual = of(-2).min(of(2)); assertExpression( Math.min(-2, 2), actual, @@ -229,12 +229,12 @@ public void minTest() { public void roundTest() { assumeTrue(serverVersionAtLeast(4, 2)); // https://www.mongodb.com/docs/manual/reference/operator/aggregation/round/ - IntegerExpression actual = of(5.5).round(); + MqlInteger actual = of(5.5).round(); assertExpression( 6.0, actual, "{'$round': 5.5} "); - NumberExpression actualNum = of(5.5).round(of(0)); + MqlNumber actualNum = of(5.5).round(of(0)); assertExpression( new BigDecimal("5.5").setScale(0, RoundingMode.HALF_EVEN).doubleValue(), actualNum, @@ -276,7 +276,7 @@ public void absTest() { of(-2.0).abs(), "{'$abs': -2.0}"); // integer - IntegerExpression abs = of(-2).abs(); + MqlInteger abs = of(-2).abs(); assertExpression( Math.abs(-2), abs, "{'$abs': -2}"); diff --git a/driver-core/src/test/functional/com/mongodb/client/model/expressions/ArrayExpressionsFunctionalTest.java b/driver-core/src/test/functional/com/mongodb/client/model/mql/ArrayMqlValuesFunctionalTest.java similarity index 90% rename from driver-core/src/test/functional/com/mongodb/client/model/expressions/ArrayExpressionsFunctionalTest.java rename to driver-core/src/test/functional/com/mongodb/client/model/mql/ArrayMqlValuesFunctionalTest.java index 306dc679865..351d5687ba9 100644 --- a/driver-core/src/test/functional/com/mongodb/client/model/expressions/ArrayExpressionsFunctionalTest.java +++ b/driver-core/src/test/functional/com/mongodb/client/model/mql/ArrayMqlValuesFunctionalTest.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.mongodb.client.model.expressions; +package com.mongodb.client.model.mql; import com.mongodb.MongoCommandException; import org.bson.Document; @@ -30,22 +30,22 @@ import java.util.stream.Stream; import static com.mongodb.ClusterFixture.serverVersionAtLeast; -import static com.mongodb.client.model.expressions.Expressions.of; -import static com.mongodb.client.model.expressions.Expressions.ofArray; -import static com.mongodb.client.model.expressions.Expressions.ofBooleanArray; -import static com.mongodb.client.model.expressions.Expressions.ofDateArray; -import static com.mongodb.client.model.expressions.Expressions.ofIntegerArray; -import static com.mongodb.client.model.expressions.Expressions.ofNumberArray; -import static com.mongodb.client.model.expressions.Expressions.ofStringArray; +import static com.mongodb.client.model.mql.MqlValues.of; +import static com.mongodb.client.model.mql.MqlValues.ofArray; +import static com.mongodb.client.model.mql.MqlValues.ofBooleanArray; +import static com.mongodb.client.model.mql.MqlValues.ofDateArray; +import static com.mongodb.client.model.mql.MqlValues.ofIntegerArray; +import static com.mongodb.client.model.mql.MqlValues.ofNumberArray; +import static com.mongodb.client.model.mql.MqlValues.ofStringArray; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assumptions.assumeTrue; @SuppressWarnings({"Convert2MethodRef"}) -class ArrayExpressionsFunctionalTest extends AbstractExpressionsFunctionalTest { +class ArrayMqlValuesFunctionalTest extends AbstractMqlValuesFunctionalTest { // https://www.mongodb.com/docs/manual/reference/operator/aggregation/#array-expression-operators - private final ArrayExpression array123 = ofIntegerArray(1, 2, 3); - private final ArrayExpression arrayTTF = ofBooleanArray(true, true, false); + private final MqlArray array123 = ofIntegerArray(1, 2, 3); + private final MqlArray arrayTTF = ofBooleanArray(true, true, false); @Test public void literalsTest() { @@ -84,7 +84,7 @@ public void literalsTest() { "[{'$date': '2007-12-03T10:15:30.00Z'}]"); // Document - ArrayExpression documentArray = ofArray( + MqlArray documentArray = ofArray( of(Document.parse("{a: 1}")), of(Document.parse("{b: 2}"))); assertExpression( @@ -93,14 +93,14 @@ public void literalsTest() { "[{'$literal': {'a': 1}}, {'$literal': {'b': 2}}]"); // Array - ArrayExpression> arrayArray = ofArray(ofArray(), ofArray()); + MqlArray> arrayArray = ofArray(ofArray(), ofArray()); assertExpression( Arrays.asList(Collections.emptyList(), Collections.emptyList()), arrayArray, "[[], []]"); // Mixed - ArrayExpression expression = ofArray(of(1), of(true), ofArray(of(1.0), of(1))); + MqlArray expression = ofArray(of(1), of(true), ofArray(of(1.0), of(1))); assertExpression( Arrays.asList(1, true, Arrays.asList(1.0, 1)), expression, @@ -130,7 +130,7 @@ public void mapTest() { @Test public void sortTest() { // https://www.mongodb.com/docs/manual/reference/operator/aggregation/sortArray/ - ArrayExpression integerExpressionArrayExpression = ofIntegerArray(3, 1, 2); + MqlArray integerExpressionArrayExpression = ofIntegerArray(3, 1, 2); assertExpression( Stream.of(3, 1, 2) .sorted().collect(Collectors.toList()), sort(integerExpressionArrayExpression), @@ -138,9 +138,9 @@ public void sortTest() { } @SuppressWarnings("unchecked") - private static ArrayExpression sort(final ArrayExpression array) { + private static MqlArray sort(final MqlArray array) { assumeTrue(serverVersionAtLeast(5, 2)); // due to sort - MqlExpression mqlArray = (MqlExpression) array; + MqlExpression mqlArray = (MqlExpression) array; return mqlArray.sort(); } @@ -289,7 +289,7 @@ public void reduceConcatTest() { + "'initialValue': [], " + "'in': {'$concatArrays': ['$$value', '$$this']}}} "); // empty: - ArrayExpression> expressionArrayExpression = ofArray(); + MqlArray> expressionArrayExpression = ofArray(); assertExpression( Collections.emptyList(), expressionArrayExpression.concat(a -> a)); @@ -305,7 +305,7 @@ public void reduceUnionTest() { + "{'$map': {'input': [[1, 2], [1, 3]], 'in': '$$this'}}, " + "'initialValue': [], 'in': {'$setUnion': ['$$value', '$$this']}}}, 'sortBy': 1}}"); - Function, ArrayExpression> f = a -> + Function, MqlArray> f = a -> a.map(v -> v.isBooleanOr(of(false)) .cond(of(1), of(0))); assertExpression( @@ -425,7 +425,7 @@ public void sliceTest() { array123.slice(1, 10), "{'$slice': [[1, 2, 3], 1, 10]}"); - ArrayExpression array12345 = ofIntegerArray(1, 2, 3, 4, 5); + MqlArray array12345 = ofIntegerArray(1, 2, 3, 4, 5); // sub-array: skipFirstN + firstN assertExpression( Arrays.asList(2, 3), 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/mql/BooleanMqlValuesFunctionalTest.java similarity index 85% rename from driver-core/src/test/functional/com/mongodb/client/model/expressions/BooleanExpressionsFunctionalTest.java rename to driver-core/src/test/functional/com/mongodb/client/model/mql/BooleanMqlValuesFunctionalTest.java index af0ebdbff37..377f7d4bfbb 100644 --- a/driver-core/src/test/functional/com/mongodb/client/model/expressions/BooleanExpressionsFunctionalTest.java +++ b/driver-core/src/test/functional/com/mongodb/client/model/mql/BooleanMqlValuesFunctionalTest.java @@ -14,17 +14,17 @@ * limitations under the License. */ -package com.mongodb.client.model.expressions; +package com.mongodb.client.model.mql; import org.junit.jupiter.api.Test; @SuppressWarnings({"PointlessBooleanExpression", "ConstantConditions", "ConstantConditionalExpression"}) -class BooleanExpressionsFunctionalTest extends AbstractExpressionsFunctionalTest { +class BooleanMqlValuesFunctionalTest extends AbstractMqlValuesFunctionalTest { // 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); + private final MqlBoolean tru = MqlValues.of(true); + private final MqlBoolean fal = MqlValues.of(false); @Test public void literalsTest() { @@ -56,9 +56,9 @@ public void notTest() { @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); + MqlString abc = MqlValues.of("abc"); + MqlString xyz = MqlValues.of("xyz"); + MqlNumber nnn = MqlValues.of(123); assertExpression( true && false ? "abc" : "xyz", tru.and(fal).cond(abc, xyz), diff --git a/driver-core/src/test/functional/com/mongodb/client/model/expressions/ComparisonExpressionsFunctionalTest.java b/driver-core/src/test/functional/com/mongodb/client/model/mql/ComparisonMqlValuesFunctionalTest.java similarity index 89% rename from driver-core/src/test/functional/com/mongodb/client/model/expressions/ComparisonExpressionsFunctionalTest.java rename to driver-core/src/test/functional/com/mongodb/client/model/mql/ComparisonMqlValuesFunctionalTest.java index 1dc30aea020..f5108fe4e25 100644 --- a/driver-core/src/test/functional/com/mongodb/client/model/expressions/ComparisonExpressionsFunctionalTest.java +++ b/driver-core/src/test/functional/com/mongodb/client/model/mql/ComparisonMqlValuesFunctionalTest.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.mongodb.client.model.expressions; +package com.mongodb.client.model.mql; import org.bson.BsonDocument; import org.bson.BsonValue; @@ -25,21 +25,21 @@ import java.util.Arrays; 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.ofIntegerArray; -import static com.mongodb.client.model.expressions.Expressions.ofNull; +import static com.mongodb.client.model.mql.MqlValues.of; +import static com.mongodb.client.model.mql.MqlValues.ofBooleanArray; +import static com.mongodb.client.model.mql.MqlValues.ofIntegerArray; +import static com.mongodb.client.model.mql.MqlValues.ofNull; import static org.bson.codecs.configuration.CodecRegistries.fromProviders; import static org.junit.jupiter.api.Assertions.fail; @SuppressWarnings({"ConstantConditions"}) -class ComparisonExpressionsFunctionalTest extends AbstractExpressionsFunctionalTest { +class ComparisonMqlValuesFunctionalTest extends AbstractMqlValuesFunctionalTest { // https://www.mongodb.com/docs/manual/reference/operator/aggregation/#comparison-expression-operators // (Complete as of 6.0) // Comparison expressions are part of the generic Expression class. // https://www.mongodb.com/docs/manual/reference/bson-type-comparison-order/#std-label-bson-types-comparison-order - private final List sampleValues = Arrays.asList( + private final List sampleValues = Arrays.asList( MqlExpression.ofRem(), ofNull(), of(0), @@ -100,8 +100,8 @@ public void eqTest() { if (i == j) { continue; } - Expression first = sampleValues.get(i); - Expression second = sampleValues.get(j); + MqlValue first = sampleValues.get(i); + MqlValue second = sampleValues.get(j); BsonValue evaluate = evaluate(first.eq(second)); if (evaluate.asBoolean().getValue()) { BsonValue v1 = ((MqlExpression) first).toBsonValue(fromProviders(new BsonValueCodecProvider())); @@ -140,8 +140,8 @@ public void ltTest() { 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); + MqlValue first = sampleValues.get(i); + MqlValue second = sampleValues.get(j); BsonValue evaluate = evaluate(first.lt(second)); if (!evaluate.asBoolean().getValue()) { BsonValue v1 = ((MqlExpression) first).toBsonValue(fromProviders(new BsonValueCodecProvider())); diff --git a/driver-core/src/test/functional/com/mongodb/client/model/expressions/ControlExpressionsFunctionalTest.java b/driver-core/src/test/functional/com/mongodb/client/model/mql/ControlMqlValuesFunctionalTest.java similarity index 91% rename from driver-core/src/test/functional/com/mongodb/client/model/expressions/ControlExpressionsFunctionalTest.java rename to driver-core/src/test/functional/com/mongodb/client/model/mql/ControlMqlValuesFunctionalTest.java index 2e03570fb72..a85a6fb41d7 100644 --- a/driver-core/src/test/functional/com/mongodb/client/model/expressions/ControlExpressionsFunctionalTest.java +++ b/driver-core/src/test/functional/com/mongodb/client/model/mql/ControlMqlValuesFunctionalTest.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.mongodb.client.model.expressions; +package com.mongodb.client.model.mql; import org.bson.Document; import org.junit.jupiter.api.Test; @@ -23,19 +23,19 @@ import java.util.function.Function; import static com.mongodb.ClusterFixture.serverVersionAtLeast; -import static com.mongodb.client.model.expressions.Expressions.of; -import static com.mongodb.client.model.expressions.Expressions.ofArray; -import static com.mongodb.client.model.expressions.Expressions.ofIntegerArray; -import static com.mongodb.client.model.expressions.Expressions.ofMap; -import static com.mongodb.client.model.expressions.Expressions.ofNull; +import static com.mongodb.client.model.mql.MqlValues.of; +import static com.mongodb.client.model.mql.MqlValues.ofArray; +import static com.mongodb.client.model.mql.MqlValues.ofIntegerArray; +import static com.mongodb.client.model.mql.MqlValues.ofMap; +import static com.mongodb.client.model.mql.MqlValues.ofNull; import static org.junit.jupiter.api.Assumptions.assumeTrue; -class ControlExpressionsFunctionalTest extends AbstractExpressionsFunctionalTest { +class ControlMqlValuesFunctionalTest extends AbstractMqlValuesFunctionalTest { @Test public void passToTest() { - Function intDecrement = (e) -> e.subtract(of(1)); - Function numDecrement = (e) -> e.subtract(of(1)); + Function intDecrement = (e) -> e.subtract(of(1)); + Function numDecrement = (e) -> e.subtract(of(1)); // "nested functional" function application: assertExpression( @@ -57,7 +57,7 @@ public void passToTest() { of(2).passNumberTo(numDecrement)); // all types - Function test = on -> of("A"); + Function test = on -> of("A"); assertExpression("A", of(true).passTo(test)); assertExpression("A", of(false).passBooleanTo(test)); assertExpression("A", of(0).passIntegerTo(test)); @@ -79,8 +79,8 @@ public void switchTest() { assertExpression("a", of(0).switchOn(on -> on.lte(of(9), v -> of("a")))); // test branches - Function isOver10 = v -> v.subtract(10).gt(of(0)); - Function s = e -> e + Function isOver10 = v -> v.subtract(10).gt(of(0)); + Function s = e -> e .switchIntegerOn(on -> on .eq(of(0), v -> of("A")) .lt(of(10), v -> of("B")) @@ -112,14 +112,14 @@ public void switchInferenceTest() { public void switchTypesTest() { // isIntegerOr relies on switch short-circuiting, which only happens after 5.2 assumeTrue(serverVersionAtLeast(5, 2)); - Function label = expr -> expr.switchOn(on -> on + Function label = expr -> expr.switchOn(on -> on .isBoolean(v -> v.asString().concat(of(" - bool"))) // integer should be checked before string .isInteger(v -> v.asString().concat(of(" - integer"))) .isNumber(v -> v.asString().concat(of(" - number"))) .isString(v -> v.asString().concat(of(" - string"))) .isDate(v -> v.asString().concat(of(" - date"))) - .isArray((ArrayExpression v) -> v.sum(a -> a).asString().concat(of(" - array"))) + .isArray((MqlArray v) -> v.sum(a -> a).asString().concat(of(" - array"))) .isDocument(v -> v.getString("_id").concat(of(" - document"))) .isNull(v -> of("null - null")) .defaults(v -> of("default")) @@ -140,17 +140,17 @@ public void switchTypesTest() { assertExpression( "12 - map", ofMap(Document.parse("{a: '1', b: '2'}")).switchOn(on -> on - .isMap((MapExpression v) -> v.entrySet() + .isMap((MqlMap v) -> v.entrySet() .join(e -> e.getValue()).concat(of(" - map"))))); // arrays via isArray, and tests signature: assertExpression( "ab - array", ofArray(of("a"), of("b")).switchOn(on -> on - .isArray((ArrayExpression v) -> v + .isArray((MqlArray v) -> v .join(e -> e).concat(of(" - array"))))); } - private BranchesIntermediary branches(final Branches on) { + private BranchesIntermediary branches(final Branches on) { return on.is(v -> of(true), v -> of("A")); } diff --git a/driver-core/src/test/functional/com/mongodb/client/model/expressions/DateExpressionsFunctionalTest.java b/driver-core/src/test/functional/com/mongodb/client/model/mql/DateMqlValuesFunctionalTest.java similarity index 94% rename from driver-core/src/test/functional/com/mongodb/client/model/expressions/DateExpressionsFunctionalTest.java rename to driver-core/src/test/functional/com/mongodb/client/model/mql/DateMqlValuesFunctionalTest.java index d801297bbbb..2683b772bf2 100644 --- a/driver-core/src/test/functional/com/mongodb/client/model/expressions/DateExpressionsFunctionalTest.java +++ b/driver-core/src/test/functional/com/mongodb/client/model/mql/DateMqlValuesFunctionalTest.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.mongodb.client.model.expressions; +package com.mongodb.client.model.mql; import org.junit.jupiter.api.Test; @@ -24,17 +24,17 @@ import java.time.ZonedDateTime; import java.time.temporal.ChronoField; -import static com.mongodb.client.model.expressions.Expressions.of; +import static com.mongodb.client.model.mql.MqlValues.of; import static org.junit.jupiter.api.Assertions.assertThrows; @SuppressWarnings("ConstantConditions") -class DateExpressionsFunctionalTest extends AbstractExpressionsFunctionalTest { +class DateMqlValuesFunctionalTest extends AbstractMqlValuesFunctionalTest { // https://www.mongodb.com/docs/manual/reference/operator/aggregation/#date-expression-operators private final Instant instant = Instant.parse("2007-12-03T10:15:30.005Z"); - private final DateExpression date = of(instant); + private final MqlDate date = of(instant); private final ZonedDateTime utcDateTime = ZonedDateTime.ofInstant(instant, ZoneId.of(ZoneOffset.UTC.getId())); - private final StringExpression utc = of("UTC"); + private final MqlString utc = of("UTC"); @Test public void literalsTest() { diff --git a/driver-core/src/test/functional/com/mongodb/client/model/expressions/DocumentExpressionsFunctionalTest.java b/driver-core/src/test/functional/com/mongodb/client/model/mql/DocumentMqlValuesFunctionalTest.java similarity index 94% rename from driver-core/src/test/functional/com/mongodb/client/model/expressions/DocumentExpressionsFunctionalTest.java rename to driver-core/src/test/functional/com/mongodb/client/model/mql/DocumentMqlValuesFunctionalTest.java index ed57114f45e..320a63bbab9 100644 --- a/driver-core/src/test/functional/com/mongodb/client/model/expressions/DocumentExpressionsFunctionalTest.java +++ b/driver-core/src/test/functional/com/mongodb/client/model/mql/DocumentMqlValuesFunctionalTest.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.mongodb.client.model.expressions; +package com.mongodb.client.model.mql; import org.bson.BsonDocument; import org.bson.Document; @@ -26,24 +26,24 @@ import java.util.Arrays; import static com.mongodb.ClusterFixture.serverVersionAtLeast; -import static com.mongodb.client.model.expressions.Expressions.of; -import static com.mongodb.client.model.expressions.Expressions.ofIntegerArray; -import static com.mongodb.client.model.expressions.Expressions.ofMap; +import static com.mongodb.client.model.mql.MqlValues.of; +import static com.mongodb.client.model.mql.MqlValues.ofIntegerArray; +import static com.mongodb.client.model.mql.MqlValues.ofMap; import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assumptions.assumeTrue; @SuppressWarnings("ConstantConditions") -class DocumentExpressionsFunctionalTest extends AbstractExpressionsFunctionalTest { +class DocumentMqlValuesFunctionalTest extends AbstractMqlValuesFunctionalTest { // https://www.mongodb.com/docs/manual/reference/operator/aggregation/#object-expression-operators // (Complete as of 6.0) - private static DocumentExpression ofDoc(final String ofDoc) { + private static MqlDocument ofDoc(final String ofDoc) { return of(BsonDocument.parse(ofDoc)); } - private final DocumentExpression a1 = ofDoc("{a: 1}"); - private final DocumentExpression ax1ay2 = ofDoc("{a: {x: 1, y: 2}}"); + private final MqlDocument a1 = ofDoc("{a: 1}"); + private final MqlDocument ax1ay2 = ofDoc("{a: {x: 1, y: 2}}"); @Test public void literalsTest() { @@ -103,7 +103,7 @@ public void getFieldTest() { assertExpression(2, ofDoc("{a: {b: 2}}").getDocument("a").getInteger("b")); // field names, not paths - DocumentExpression doc = ofDoc("{a: {b: 2}, 'a.b': 3, 'a$b': 4, '$a.b': 5}"); + MqlDocument doc = ofDoc("{a: {b: 2}, 'a.b': 3, 'a$b': 4, '$a.b': 5}"); assertExpression(2, doc.getDocument("a").getInteger("b")); assertExpression(3, doc.getInteger("a.b")); assertExpression(4, doc.getInteger("a$b")); @@ -198,7 +198,7 @@ public void setFieldTest() { // Placing a null value: assertExpression( BsonDocument.parse("{a: 1, r: null}"), // map.put("r", null) - a1.setField("r", Expressions.ofNull()), + a1.setField("r", MqlValues.ofNull()), "{'$setField': {'field': 'r', 'input': {'$literal': {'a': 1}}, 'value': null}}"); // Replacing a field based on its prior value: @@ -260,7 +260,7 @@ public void mergeTest() { @Test public void asMapTest() { - DocumentExpression d = ofDoc("{a: 1}"); + MqlDocument d = ofDoc("{a: 1}"); assertSame(d, d.asMap()); } @@ -268,7 +268,7 @@ public void asMapTest() { @Test public void hasTest() { assumeTrue(serverVersionAtLeast(5, 0)); // get/setField - DocumentExpression d = ofDoc("{a: 1, null: null}"); + MqlDocument d = ofDoc("{a: 1, null: null}"); assertExpression( true, d.has("a"), diff --git a/driver-core/src/test/functional/com/mongodb/client/model/expressions/MapExpressionsFunctionalTest.java b/driver-core/src/test/functional/com/mongodb/client/model/mql/MapMqlValuesFunctionalTest.java similarity index 84% rename from driver-core/src/test/functional/com/mongodb/client/model/expressions/MapExpressionsFunctionalTest.java rename to driver-core/src/test/functional/com/mongodb/client/model/mql/MapMqlValuesFunctionalTest.java index d1762d9f784..6fc51d7b6ad 100644 --- a/driver-core/src/test/functional/com/mongodb/client/model/expressions/MapExpressionsFunctionalTest.java +++ b/driver-core/src/test/functional/com/mongodb/client/model/mql/MapMqlValuesFunctionalTest.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.mongodb.client.model.expressions; +package com.mongodb.client.model.mql; import org.bson.BsonDocument; import org.bson.Document; @@ -23,20 +23,20 @@ import java.util.Arrays; import static com.mongodb.ClusterFixture.serverVersionAtLeast; -import static com.mongodb.client.model.expressions.Expressions.of; -import static com.mongodb.client.model.expressions.Expressions.ofArray; -import static com.mongodb.client.model.expressions.Expressions.ofEntry; -import static com.mongodb.client.model.expressions.Expressions.ofMap; -import static com.mongodb.client.model.expressions.Expressions.ofStringArray; +import static com.mongodb.client.model.mql.MqlValues.of; +import static com.mongodb.client.model.mql.MqlValues.ofArray; +import static com.mongodb.client.model.mql.MqlValues.ofEntry; +import static com.mongodb.client.model.mql.MqlValues.ofMap; +import static com.mongodb.client.model.mql.MqlValues.ofStringArray; import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assumptions.assumeTrue; -class MapExpressionsFunctionalTest extends AbstractExpressionsFunctionalTest { +class MapMqlValuesFunctionalTest extends AbstractMqlValuesFunctionalTest { - private final MapExpression mapKey123 = Expressions.ofMap() + private final MqlMap mapKey123 = MqlValues.ofMap() .set("key", of(123)); - private final MapExpression mapA1B2 = ofMap(Document.parse("{keyA: 1, keyB: 2}")); + private final MqlMap mapA1B2 = ofMap(Document.parse("{keyA: 1, keyB: 2}")); @Test public void literalsTest() { @@ -83,7 +83,7 @@ public void getSetMapTest() { @Test public void hasTest() { assumeTrue(serverVersionAtLeast(5, 0)); // get/setField (unset) - MapExpression e = ofMap(BsonDocument.parse("{key: 1, null: null}")); + MqlMap e = ofMap(BsonDocument.parse("{key: 1, null: null}")); assertExpression( true, e.has(of("key")), @@ -102,7 +102,7 @@ public void hasTest() { @Test public void getSetEntryTest() { assumeTrue(serverVersionAtLeast(5, 0)); // get/setField - EntryExpression entryA1 = ofEntry(of("keyA"), of(1)); + MqlEntry entryA1 = ofEntry(of("keyA"), of(1)); assertExpression( Document.parse("{k: 'keyA', 'v': 33}"), entryA1.setValue(of(33))); @@ -147,8 +147,8 @@ public void buildMapTest() { assertExpression( Document.parse("{ 'item' : 'abc123' }"), ofArray( - Expressions.ofMap(Document.parse("{ 'k': 'item', 'v': '123abc' }")), - Expressions.ofMap(Document.parse("{ 'k': 'item', 'v': 'abc123' }"))) + MqlValues.ofMap(Document.parse("{ 'k': 'item', 'v': '123abc' }")), + MqlValues.ofMap(Document.parse("{ 'k': 'item', 'v': 'abc123' }"))) .asMap(v -> ofEntry(v.get("k"), v.get("v")))); } @@ -159,7 +159,7 @@ public void entrySetTest() { // https://www.mongodb.com/docs/manual/reference/operator/aggregation/objectToArray/ (23) assertExpression( Arrays.asList(Document.parse("{'k': 'k1', 'v': 1}")), - Expressions.ofMap().set("k1", of(1)).entrySet(), + MqlValues.ofMap().set("k1", of(1)).entrySet(), "{'$objectToArray': {'$setField': " + "{'field': 'k1', 'input': {'$literal': {}}, 'value': 1}}}"); @@ -180,7 +180,7 @@ public void entrySetTest() { .asMap(v -> v)); // via getMap - DocumentExpression doc = of(Document.parse("{ instock: { warehouse1: 2500, warehouse2: 500 } }")); + MqlDocument doc = of(Document.parse("{ instock: { warehouse1: 2500, warehouse2: 500 } }")); assertExpression( Arrays.asList( Document.parse("{'k': 'warehouse1', 'v': 2500}"), @@ -202,7 +202,7 @@ public void mergeTest() { @Test public void asDocumentTest() { - MapExpression d = ofMap(BsonDocument.parse("{a: 1}")); + MqlMap d = ofMap(BsonDocument.parse("{a: 1}")); assertSame(d, d.asDocument()); } } diff --git a/driver-core/src/test/functional/com/mongodb/client/model/expressions/NotNullApiTest.java b/driver-core/src/test/functional/com/mongodb/client/model/mql/NotNullApiTest.java similarity index 85% rename from driver-core/src/test/functional/com/mongodb/client/model/expressions/NotNullApiTest.java rename to driver-core/src/test/functional/com/mongodb/client/model/mql/NotNullApiTest.java index 28e8afb68fa..dfc39ce87c7 100644 --- a/driver-core/src/test/functional/com/mongodb/client/model/expressions/NotNullApiTest.java +++ b/driver-core/src/test/functional/com/mongodb/client/model/mql/NotNullApiTest.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.mongodb.client.model.expressions; +package com.mongodb.client.model.mql; import org.bson.BsonDocument; import org.bson.conversions.Bson; @@ -33,10 +33,10 @@ import java.util.function.Function; import static com.mongodb.assertions.Assertions.fail; -import static com.mongodb.client.model.expressions.Expressions.of; -import static com.mongodb.client.model.expressions.Expressions.ofArray; -import static com.mongodb.client.model.expressions.Expressions.ofMap; -import static com.mongodb.client.model.expressions.Expressions.ofNull; +import static com.mongodb.client.model.mql.MqlValues.of; +import static com.mongodb.client.model.mql.MqlValues.ofArray; +import static com.mongodb.client.model.mql.MqlValues.ofMap; +import static com.mongodb.client.model.mql.MqlValues.ofNull; class NotNullApiTest { @@ -46,16 +46,16 @@ public void notNullApiTest() { Map, Object> paramMapping = new HashMap<>(); // to test: - mapping.put(Expressions.class, null); - mapping.put(BooleanExpression.class, of(true)); - mapping.put(IntegerExpression.class, of(1)); - mapping.put(NumberExpression.class, of(1.0)); - mapping.put(StringExpression.class, of("")); - mapping.put(DateExpression.class, of(Instant.now())); - mapping.put(DocumentExpression.class, of(BsonDocument.parse("{}"))); - mapping.put(MapExpression.class, ofMap(BsonDocument.parse("{}"))); - mapping.put(ArrayExpression.class, ofArray()); - mapping.put(Expression.class, ofNull()); + mapping.put(MqlValues.class, null); + mapping.put(MqlBoolean.class, of(true)); + mapping.put(MqlInteger.class, of(1)); + mapping.put(MqlNumber.class, of(1.0)); + mapping.put(MqlString.class, of("")); + mapping.put(MqlDate.class, of(Instant.now())); + mapping.put(MqlDocument.class, of(BsonDocument.parse("{}"))); + mapping.put(MqlMap.class, ofMap(BsonDocument.parse("{}"))); + mapping.put(MqlArray.class, ofArray()); + mapping.put(MqlValue.class, ofNull()); mapping.put(Branches.class, new Branches<>()); mapping.put(BranchesIntermediary.class, new BranchesIntermediary<>(Collections.emptyList())); mapping.put(BranchesTerminal.class, new BranchesTerminal<>(Collections.emptyList(), null)); @@ -71,7 +71,7 @@ public void notNullApiTest() { paramMapping.put(long.class, 1L); paramMapping.put(Object.class, new Object()); paramMapping.put(Decimal128.class, new Decimal128(1)); - putArray(paramMapping, Expression.class); + putArray(paramMapping, MqlValue.class); putArray(paramMapping, boolean.class); putArray(paramMapping, long.class); putArray(paramMapping, int.class); diff --git a/driver-core/src/test/functional/com/mongodb/client/model/expressions/StringExpressionsFunctionalTest.java b/driver-core/src/test/functional/com/mongodb/client/model/mql/StringMqlValuesFunctionalTest.java similarity index 96% rename from driver-core/src/test/functional/com/mongodb/client/model/expressions/StringExpressionsFunctionalTest.java rename to driver-core/src/test/functional/com/mongodb/client/model/mql/StringMqlValuesFunctionalTest.java index 44789d7c50f..dfcdf4535e5 100644 --- a/driver-core/src/test/functional/com/mongodb/client/model/expressions/StringExpressionsFunctionalTest.java +++ b/driver-core/src/test/functional/com/mongodb/client/model/mql/StringMqlValuesFunctionalTest.java @@ -14,18 +14,18 @@ * limitations under the License. */ -package com.mongodb.client.model.expressions; +package com.mongodb.client.model.mql; import org.junit.jupiter.api.Test; import java.nio.charset.StandardCharsets; import java.util.Arrays; -import static com.mongodb.client.model.expressions.Expressions.of; +import static com.mongodb.client.model.mql.MqlValues.of; import static org.junit.jupiter.api.Assertions.assertThrows; @SuppressWarnings({"ConstantConditions"}) -class StringExpressionsFunctionalTest extends AbstractExpressionsFunctionalTest { +class StringMqlValuesFunctionalTest extends AbstractMqlValuesFunctionalTest { // https://www.mongodb.com/docs/manual/reference/operator/aggregation/#string-expression-operators private final String jalapeno = "jalape\u00F1o"; diff --git a/driver-core/src/test/functional/com/mongodb/client/model/expressions/TypeExpressionsFunctionalTest.java b/driver-core/src/test/functional/com/mongodb/client/model/mql/TypeMqlValuesFunctionalTest.java similarity index 95% rename from driver-core/src/test/functional/com/mongodb/client/model/expressions/TypeExpressionsFunctionalTest.java rename to driver-core/src/test/functional/com/mongodb/client/model/mql/TypeMqlValuesFunctionalTest.java index bdd59776ebb..56d6dce92b9 100644 --- a/driver-core/src/test/functional/com/mongodb/client/model/expressions/TypeExpressionsFunctionalTest.java +++ b/driver-core/src/test/functional/com/mongodb/client/model/mql/TypeMqlValuesFunctionalTest.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.mongodb.client.model.expressions; +package com.mongodb.client.model.mql; import com.mongodb.MongoCommandException; import org.bson.BsonDocument; @@ -29,15 +29,15 @@ import java.util.Arrays; import static com.mongodb.ClusterFixture.serverVersionAtLeast; -import static com.mongodb.client.model.expressions.Expressions.of; -import static com.mongodb.client.model.expressions.Expressions.ofIntegerArray; -import static com.mongodb.client.model.expressions.Expressions.ofMap; -import static com.mongodb.client.model.expressions.Expressions.ofNull; +import static com.mongodb.client.model.mql.MqlValues.of; +import static com.mongodb.client.model.mql.MqlValues.ofIntegerArray; +import static com.mongodb.client.model.mql.MqlValues.ofMap; +import static com.mongodb.client.model.mql.MqlValues.ofNull; import static java.time.format.DateTimeFormatter.ISO_LOCAL_DATE_TIME; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assumptions.assumeTrue; -class TypeExpressionsFunctionalTest extends AbstractExpressionsFunctionalTest { +class TypeMqlValuesFunctionalTest extends AbstractMqlValuesFunctionalTest { // https://www.mongodb.com/docs/manual/reference/operator/aggregation/#type-expression-operators // https://www.mongodb.com/docs/manual/reference/operator/aggregation/type/ @@ -140,9 +140,9 @@ public void isDocumentOrTest() { assertExpression(doc, ofMap(doc).isDocumentOr(of(BsonDocument.parse("{x: 9}")))); // conversion between maps and documents - MapExpression first = ofMap(doc); - DocumentExpression second = first.isDocumentOr(of(BsonDocument.parse("{}"))); - MapExpression third = second.isMapOr(ofMap(BsonDocument.parse("{}"))); + MqlMap first = ofMap(doc); + MqlDocument second = first.isDocumentOr(of(BsonDocument.parse("{}"))); + MqlMap third = second.isMapOr(ofMap(BsonDocument.parse("{}"))); assertExpression( true, first.eq(second)); @@ -202,7 +202,7 @@ public void dateAsStringTest() { assumeTrue(serverVersionAtLeast(4, 0)); // https://www.mongodb.com/docs/manual/reference/operator/aggregation/dateToString/ final Instant instant = Instant.parse("2007-12-03T10:15:30.005Z"); - DateExpression date = of(instant); + MqlDate date = of(instant); ZonedDateTime utcDateTime = ZonedDateTime.ofInstant(instant, ZoneId.of(ZoneOffset.UTC.getId())); assertExpression( "2007-12-03T10:15:30.005Z", diff --git a/driver-sync/src/test/functional/com/mongodb/client/model/expressions/InContextExpressionsFunctionalTest.java b/driver-sync/src/test/functional/com/mongodb/client/model/mql/InContextMqlValuesFunctionalTest.java similarity index 94% rename from driver-sync/src/test/functional/com/mongodb/client/model/expressions/InContextExpressionsFunctionalTest.java rename to driver-sync/src/test/functional/com/mongodb/client/model/mql/InContextMqlValuesFunctionalTest.java index f29e4b5307b..1cd5b0916a3 100644 --- a/driver-sync/src/test/functional/com/mongodb/client/model/expressions/InContextExpressionsFunctionalTest.java +++ b/driver-sync/src/test/functional/com/mongodb/client/model/mql/InContextMqlValuesFunctionalTest.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.mongodb.client.model.expressions; +package com.mongodb.client.model.mql; import com.mongodb.MongoClientSettings; import com.mongodb.client.AggregateIterable; @@ -43,13 +43,13 @@ import static com.mongodb.client.model.Projections.fields; import static com.mongodb.client.model.Projections.include; import static com.mongodb.client.model.Sorts.ascending; -import static com.mongodb.client.model.expressions.Expressions.current; -import static com.mongodb.client.model.expressions.Expressions.of; -import static com.mongodb.client.model.expressions.Expressions.ofArray; +import static com.mongodb.client.model.mql.MqlValues.current; +import static com.mongodb.client.model.mql.MqlValues.of; +import static com.mongodb.client.model.mql.MqlValues.ofArray; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assumptions.assumeTrue; -class InContextExpressionsFunctionalTest extends AbstractExpressionsFunctionalTest { +class InContextMqlValuesFunctionalTest extends AbstractMqlValuesFunctionalTest { private MongoClient client; private MongoCollection col; @@ -120,7 +120,7 @@ public void currentAsMapMatchTest() { Document.parse("{_id: 3, x: 1, y: 3}"))); List results = aggregate( - match(expr(Expressions.currentAsMap() + match(expr(MqlValues.currentAsMap() .entrySet() .map(e -> e.getValue()) .sum(v -> v).eq(of(7))))); From 6bbb231439ee2899874a5c8960dbabea046bd7f3 Mon Sep 17 00:00:00 2001 From: Maxim Katcharov Date: Fri, 27 Jan 2023 15:13:40 -0700 Subject: [PATCH 26/27] Rename methods (automated) (#1073) JAVA-3879 --- .../mongodb/client/model/mql/MqlArray.java | 6 +-- .../mongodb/client/model/mql/MqlDocument.java | 52 +++++++++---------- .../client/model/mql/MqlExpression.java | 18 +++---- .../mongodb/client/model/mql/MqlInteger.java | 2 +- .../mongodb/client/model/mql/MqlString.java | 6 +-- .../mql/ArrayMqlValuesFunctionalTest.java | 12 ++--- .../mql/ControlMqlValuesFunctionalTest.java | 18 +++---- .../mql/DocumentMqlValuesFunctionalTest.java | 6 +-- .../model/mql/MapMqlValuesFunctionalTest.java | 2 +- .../mql/StringMqlValuesFunctionalTest.java | 30 +++++------ .../mql/TypeMqlValuesFunctionalTest.java | 4 +- 11 files changed, 78 insertions(+), 78 deletions(-) diff --git a/driver-core/src/main/com/mongodb/client/model/mql/MqlArray.java b/driver-core/src/main/com/mongodb/client/model/mql/MqlArray.java index 2503c5d88df..d056c30aa90 100644 --- a/driver-core/src/main/com/mongodb/client/model/mql/MqlArray.java +++ b/driver-core/src/main/com/mongodb/client/model/mql/MqlArray.java @@ -162,7 +162,7 @@ public interface MqlArray extends MqlValue { * @param mapper the mapper function. * @return the resulting value. */ - MqlString join(Function mapper); + MqlString joinStrings(Function mapper); /** * The {@linkplain #concat(MqlArray) array-concatenation} @@ -179,7 +179,7 @@ public interface MqlArray extends MqlValue { * @return the resulting value. * @param the type of the elements of the array. */ - MqlArray concat(Function> mapper); + MqlArray concatArrays(Function> mapper); /** * The {@linkplain #union(MqlArray) set-union} @@ -196,7 +196,7 @@ public interface MqlArray extends MqlValue { * @return the resulting value. * @param the type of the elements of the array. */ - MqlArray union(Function> mapper); + MqlArray unionArrays(Function> mapper); /** * The {@linkplain MqlMap map} value corresponding to the diff --git a/driver-core/src/main/com/mongodb/client/model/mql/MqlDocument.java b/driver-core/src/main/com/mongodb/client/model/mql/MqlDocument.java index c930677f066..54f1e3f4d95 100644 --- a/driver-core/src/main/com/mongodb/client/model/mql/MqlDocument.java +++ b/driver-core/src/main/com/mongodb/client/model/mql/MqlDocument.java @@ -51,7 +51,7 @@ public interface MqlDocument extends MqlValue { * @param fieldName the name of the field. * @return the resulting value. */ - MqlBoolean has(String fieldName); + MqlBoolean hasField(String fieldName); /** * Returns a document with the same fields as {@code this} document, but @@ -87,7 +87,7 @@ public interface MqlDocument extends MqlValue { * with the provided {@code fieldName}. * *

    Warning: Use of this method is an assertion that the document - * {@linkplain #has(String) has} the named field. + * {@linkplain #hasField(String) has} the named field. * * @mongodb.server.release 5.0 * @param fieldName the name of the field. @@ -103,7 +103,7 @@ public interface MqlDocument extends MqlValue { *

    Warning: The type and presence of the resulting value is not * enforced by the API. The use of this method is an * unchecked assertion that the document - * {@linkplain #has(String) has} the named field and + * {@linkplain #hasField(String) has} the named field and * the field value is of the specified type. * * @mongodb.server.release 5.0 @@ -117,7 +117,7 @@ public interface MqlDocument extends MqlValue { * Returns the {@linkplain MqlBoolean boolean} value of the field * with the provided {@code fieldName}, * or the {@code other} value if the field is not a boolean - * or if the document {@linkplain #has} no such field. + * or if the document {@linkplain #hasField} no such field. * * @mongodb.server.release 5.0 * @param fieldName the name of the field. @@ -130,7 +130,7 @@ public interface MqlDocument extends MqlValue { * Returns the {@linkplain MqlBoolean boolean} value of the field * with the provided {@code fieldName}, * or the {@code other} value if the field is not a boolean - * or if the document {@linkplain #has} no such field. + * or if the document {@linkplain #hasField} no such field. * * @mongodb.server.release 5.0 * @param fieldName the name of the field. @@ -149,7 +149,7 @@ default MqlBoolean getBoolean(final String fieldName, final boolean other) { *

    Warning: The type and presence of the resulting value is not * enforced by the API. The use of this method is an * unchecked assertion that the document - * {@linkplain #has(String) has} the named field and + * {@linkplain #hasField(String) has} the named field and * the field value is of the specified type. * * @mongodb.server.release 5.0 @@ -163,7 +163,7 @@ default MqlBoolean getBoolean(final String fieldName, final boolean other) { * Returns the {@linkplain MqlNumber number} value of the field * with the provided {@code fieldName}, * or the {@code other} value if the field is not a number - * or if the document {@linkplain #has} no such field. + * or if the document {@linkplain #hasField} no such field. * * @mongodb.server.release 5.0 * @param fieldName the name of the field. @@ -176,7 +176,7 @@ default MqlBoolean getBoolean(final String fieldName, final boolean other) { * Returns the {@linkplain MqlNumber number} value of the field * with the provided {@code fieldName}, * or the {@code other} value if the field is not a number - * or if the document {@linkplain #has} no such field. + * or if the document {@linkplain #hasField} no such field. * * @mongodb.server.release 5.0 * @param fieldName the name of the field. @@ -196,7 +196,7 @@ default MqlNumber getNumber(final String fieldName, final Number other) { *

    Warning: The type and presence of the resulting value is not * enforced by the API. The use of this method is an * unchecked assertion that the document - * {@linkplain #has(String) has} the named field and + * {@linkplain #hasField(String) has} the named field and * the field value is of the specified type. * * @mongodb.server.release 5.0 @@ -210,7 +210,7 @@ default MqlNumber getNumber(final String fieldName, final Number other) { * Returns the {@linkplain MqlInteger integer} value of the field * with the provided {@code fieldName}, * or the {@code other} value if the field is not an integer - * or if the document {@linkplain #has} no such field. + * or if the document {@linkplain #hasField} no such field. * * @mongodb.server.release 5.0 * @param fieldName the name of the field. @@ -223,7 +223,7 @@ default MqlNumber getNumber(final String fieldName, final Number other) { * Returns the {@linkplain MqlInteger integer} value of the field * with the provided {@code fieldName}, * or the {@code other} value if the field is not an integer - * or if the document {@linkplain #has} no such field. + * or if the document {@linkplain #hasField} no such field. * * @mongodb.server.release 5.0 * @param fieldName the name of the field. @@ -239,7 +239,7 @@ default MqlInteger getInteger(final String fieldName, final int other) { * Returns the {@linkplain MqlInteger integer} value of the field * with the provided {@code fieldName}, * or the {@code other} value if the field is not an integer - * or if the document {@linkplain #has} no such field. + * or if the document {@linkplain #hasField} no such field. * * @mongodb.server.release 5.0 * @param fieldName the name of the field. @@ -258,7 +258,7 @@ default MqlInteger getInteger(final String fieldName, final long other) { *

    Warning: The type and presence of the resulting value is not * enforced by the API. The use of this method is an * unchecked assertion that the document - * {@linkplain #has(String) has} the named field and + * {@linkplain #hasField(String) has} the named field and * the field value is of the specified type. * * @mongodb.server.release 5.0 @@ -272,7 +272,7 @@ default MqlInteger getInteger(final String fieldName, final long other) { * Returns the {@linkplain MqlString string} value of the field * with the provided {@code fieldName}, * or the {@code other} value if the field is not a string - * or if the document {@linkplain #has} no such field. + * or if the document {@linkplain #hasField} no such field. * * @mongodb.server.release 5.0 * @param fieldName the name of the field. @@ -285,7 +285,7 @@ default MqlInteger getInteger(final String fieldName, final long other) { * Returns the {@linkplain MqlString string} value of the field * with the provided {@code fieldName}, * or the {@code other} value if the field is not a string - * or if the document {@linkplain #has} no such field. + * or if the document {@linkplain #hasField} no such field. * * @mongodb.server.release 5.0 * @param fieldName the name of the field. @@ -305,7 +305,7 @@ default MqlString getString(final String fieldName, final String other) { *

    Warning: The type and presence of the resulting value is not * enforced by the API. The use of this method is an * unchecked assertion that the document - * {@linkplain #has(String) has} the named field and + * {@linkplain #hasField(String) has} the named field and * the field value is of the specified type. * * @mongodb.server.release 5.0 @@ -319,7 +319,7 @@ default MqlString getString(final String fieldName, final String other) { * Returns the {@linkplain MqlDate date} value of the field * with the provided {@code fieldName}, * or the {@code other} value if the field is not a date - * or if the document {@linkplain #has} no such field. + * or if the document {@linkplain #hasField} no such field. * * @mongodb.server.release 5.0 * @param fieldName the name of the field. @@ -332,7 +332,7 @@ default MqlString getString(final String fieldName, final String other) { * Returns the {@linkplain MqlDate date} value of the field * with the provided {@code fieldName}, * or the {@code other} value if the field is not a date - * or if the document {@linkplain #has} no such field. + * or if the document {@linkplain #hasField} no such field. * * @mongodb.server.release 5.0 * @param fieldName the name of the field. @@ -352,7 +352,7 @@ default MqlDate getDate(final String fieldName, final Instant other) { *

    Warning: The type and presence of the resulting value is not * enforced by the API. The use of this method is an * unchecked assertion that the document - * {@linkplain #has(String) has} the named field and + * {@linkplain #hasField(String) has} the named field and * the field value is of the specified type. * * @mongodb.server.release 5.0 @@ -366,7 +366,7 @@ default MqlDate getDate(final String fieldName, final Instant other) { * Returns the {@linkplain MqlDocument document} value of the field * with the provided {@code fieldName}, * or the {@code other} value - * if the document {@linkplain #has} no such field, + * if the document {@linkplain #hasField} no such field, * or if the specified field is not a (child) document * (or other {@linkplain MqlValue#isDocumentOr document-like value}. * @@ -381,7 +381,7 @@ default MqlDate getDate(final String fieldName, final Instant other) { * Returns the {@linkplain MqlDocument document} value of the field * with the provided {@code fieldName}, * or the {@code other} value - * if the document {@linkplain #has} no such field, + * if the document {@linkplain #hasField} no such field, * or if the specified field is not a (child) document * (or other {@linkplain MqlValue#isDocumentOr document-like value}. * @@ -403,7 +403,7 @@ default MqlDocument getDocument(final String fieldName, final Bson other) { *

    Warning: The type and presence of the resulting value is not * enforced by the API. The use of this method is an * unchecked assertion that the document - * {@linkplain #has(String) has} the named field, + * {@linkplain #hasField(String) has} the named field, * and the field value is of the specified raw type, * and the field value's type has the specified type argument. * @@ -420,7 +420,7 @@ default MqlDocument getDocument(final String fieldName, final Bson other) { * Returns the {@linkplain MqlMap map} value of the field * with the provided {@code fieldName}, * or the {@code other} value - * if the document {@linkplain #has} no such field, + * if the document {@linkplain #hasField} no such field, * or if the specified field is not a map * (or other {@linkplain MqlValue#isMapOr} map-like value}). * @@ -440,7 +440,7 @@ default MqlDocument getDocument(final String fieldName, final Bson other) { * Returns the {@linkplain MqlMap map} value of the field * with the provided {@code fieldName}, * or the {@code other} value - * if the document {@linkplain #has} no such field, + * if the document {@linkplain #hasField} no such field, * or if the specified field is not a map * (or other {@linkplain MqlValue#isMapOr} map-like value}). * @@ -467,7 +467,7 @@ default MqlDocument getDocument(final String fieldName, final Bson other) { *

    Warning: The type and presence of the resulting value is not * enforced by the API. The use of this method is an * unchecked assertion that the document - * {@linkplain #has(String) has} the named field, + * {@linkplain #hasField(String) has} the named field, * and the field value is of the specified raw type, * and the field value's type has the specified type argument. * @@ -483,7 +483,7 @@ default MqlDocument getDocument(final String fieldName, final Bson other) { * Returns the {@linkplain MqlArray array} value of the field * with the provided {@code fieldName}, * or the {@code other} value if the field is not an array - * or if the document {@linkplain #has} no such field. + * or if the document {@linkplain #hasField} no such field. * *

    Warning: The type argument of the resulting value is not * enforced by the API. The use of this method is an diff --git a/driver-core/src/main/com/mongodb/client/model/mql/MqlExpression.java b/driver-core/src/main/com/mongodb/client/model/mql/MqlExpression.java index 7d743ad5726..577e48a7c02 100644 --- a/driver-core/src/main/com/mongodb/client/model/mql/MqlExpression.java +++ b/driver-core/src/main/com/mongodb/client/model/mql/MqlExpression.java @@ -696,15 +696,15 @@ public MqlArray minN(final MqlInteger n) { } @Override - public MqlString join(final Function mapper) { + public MqlString joinStrings(final Function mapper) { Assertions.notNull("mapper", mapper); MqlExpression array = (MqlExpression) this.map(mapper); - return array.reduce(of(""), (a, b) -> a.concat(b)); + return array.reduce(of(""), (a, b) -> a.append(b)); } @SuppressWarnings("unchecked") @Override - public MqlArray concat(final Function> mapper) { + public MqlArray concatArrays(final Function> mapper) { Assertions.notNull("mapper", mapper); MqlExpression> array = (MqlExpression>) this.map(mapper); return array.reduce(MqlValues.ofArray(), (a, b) -> a.concat(b)); @@ -712,7 +712,7 @@ public MqlArray concat(final Function MqlArray union(final Function> mapper) { + public MqlArray unionArrays(final Function> mapper) { Assertions.notNull("mapper", mapper); Assertions.notNull("mapper", mapper); MqlExpression> array = (MqlExpression>) this.map(mapper); @@ -839,7 +839,7 @@ public MqlInteger abs() { } @Override - public MqlDate millisecondsToDate() { + public MqlDate millisecondsAsDate() { return newMqlExpression(ast("$toDate")); } @@ -988,18 +988,18 @@ public MqlString toUpper() { } @Override - public MqlString concat(final MqlString other) { + public MqlString append(final MqlString other) { Assertions.notNull("other", other); return new MqlExpression<>(ast("$concat", other)); } @Override - public MqlInteger strLen() { + public MqlInteger length() { return new MqlExpression<>(ast("$strLenCP")); } @Override - public MqlInteger strLenBytes() { + public MqlInteger lengthBytes() { return new MqlExpression<>(ast("$strLenBytes")); } @@ -1025,7 +1025,7 @@ public MqlBoolean has(final MqlString key) { @Override - public MqlBoolean has(final String fieldName) { + public MqlBoolean hasField(final String fieldName) { Assertions.notNull("fieldName", fieldName); return this.has(of(fieldName)); } diff --git a/driver-core/src/main/com/mongodb/client/model/mql/MqlInteger.java b/driver-core/src/main/com/mongodb/client/model/mql/MqlInteger.java index f9eace4bc7b..0fe85fd88d9 100644 --- a/driver-core/src/main/com/mongodb/client/model/mql/MqlInteger.java +++ b/driver-core/src/main/com/mongodb/client/model/mql/MqlInteger.java @@ -119,7 +119,7 @@ default MqlInteger subtract(final int other) { * @mongodb.server.release 4.0 * @return the resulting value. */ - MqlDate millisecondsToDate(); + MqlDate millisecondsAsDate(); /** * The result of passing {@code this} value to the provided function. diff --git a/driver-core/src/main/com/mongodb/client/model/mql/MqlString.java b/driver-core/src/main/com/mongodb/client/model/mql/MqlString.java index 29b720add0c..399f09879cb 100644 --- a/driver-core/src/main/com/mongodb/client/model/mql/MqlString.java +++ b/driver-core/src/main/com/mongodb/client/model/mql/MqlString.java @@ -54,21 +54,21 @@ public interface MqlString extends MqlValue { * @param other the other value. * @return the resulting value. */ - MqlString concat(MqlString other); + MqlString append(MqlString other); /** * The number of Unicode code points in {@code this} string. * * @return the resulting value. */ - MqlInteger strLen(); + MqlInteger length(); /** * The number of UTF-8 encoded bytes in {@code this} string. * * @return the resulting value. */ - MqlInteger strLenBytes(); + MqlInteger lengthBytes(); /** * The substring of {@code this} string, from the {@code start} index diff --git a/driver-core/src/test/functional/com/mongodb/client/model/mql/ArrayMqlValuesFunctionalTest.java b/driver-core/src/test/functional/com/mongodb/client/model/mql/ArrayMqlValuesFunctionalTest.java index 351d5687ba9..59ca6027742 100644 --- a/driver-core/src/test/functional/com/mongodb/client/model/mql/ArrayMqlValuesFunctionalTest.java +++ b/driver-core/src/test/functional/com/mongodb/client/model/mql/ArrayMqlValuesFunctionalTest.java @@ -272,19 +272,19 @@ public void reduceMinNTest() { public void reduceJoinTest() { assertExpression( "abc", - ofStringArray("a", "b", "c").join(a -> a), + ofStringArray("a", "b", "c").joinStrings(a -> a), "{'$reduce': {'input': {'$map': {'input': ['a', 'b', 'c'], 'in': '$$this'}}, " + "'initialValue': '', 'in': {'$concat': ['$$value', '$$this']}}}"); assertExpression( "", - ofStringArray().join(a -> a)); + ofStringArray().joinStrings(a -> a)); } @Test public void reduceConcatTest() { assertExpression( Arrays.asList(1, 2, 3, 4), - ofArray(ofIntegerArray(1, 2), ofIntegerArray(3, 4)).concat(v -> v), + ofArray(ofIntegerArray(1, 2), ofIntegerArray(3, 4)).concatArrays(v -> v), "{'$reduce': {'input': {'$map': {'input': [[1, 2], [3, 4]], 'in': '$$this'}}, " + "'initialValue': [], " + "'in': {'$concatArrays': ['$$value', '$$this']}}} "); @@ -292,7 +292,7 @@ public void reduceConcatTest() { MqlArray> expressionArrayExpression = ofArray(); assertExpression( Collections.emptyList(), - expressionArrayExpression.concat(a -> a)); + expressionArrayExpression.concatArrays(a -> a)); } @Test @@ -300,7 +300,7 @@ public void reduceUnionTest() { // https://www.mongodb.com/docs/manual/reference/operator/aggregation/setUnion/ (40) assertExpression( Arrays.asList(1, 2, 3), - sort(ofArray(ofIntegerArray(1, 2), ofIntegerArray(1, 3)).union(v -> v)), + sort(ofArray(ofIntegerArray(1, 2), ofIntegerArray(1, 3)).unionArrays(v -> v)), "{'$sortArray': {'input': {'$reduce': {'input': " + "{'$map': {'input': [[1, 2], [1, 3]], 'in': '$$this'}}, " + "'initialValue': [], 'in': {'$setUnion': ['$$value', '$$this']}}}, 'sortBy': 1}}"); @@ -310,7 +310,7 @@ public void reduceUnionTest() { .cond(of(1), of(0))); assertExpression( Arrays.asList(0, 1), - ofArray(ofBooleanArray(true, false), ofBooleanArray(false)).union(f)); + ofArray(ofBooleanArray(true, false), ofBooleanArray(false)).unionArrays(f)); } @Test diff --git a/driver-core/src/test/functional/com/mongodb/client/model/mql/ControlMqlValuesFunctionalTest.java b/driver-core/src/test/functional/com/mongodb/client/model/mql/ControlMqlValuesFunctionalTest.java index a85a6fb41d7..3173ddb4521 100644 --- a/driver-core/src/test/functional/com/mongodb/client/model/mql/ControlMqlValuesFunctionalTest.java +++ b/driver-core/src/test/functional/com/mongodb/client/model/mql/ControlMqlValuesFunctionalTest.java @@ -113,14 +113,14 @@ public void switchTypesTest() { // isIntegerOr relies on switch short-circuiting, which only happens after 5.2 assumeTrue(serverVersionAtLeast(5, 2)); Function label = expr -> expr.switchOn(on -> on - .isBoolean(v -> v.asString().concat(of(" - bool"))) + .isBoolean(v -> v.asString().append(of(" - bool"))) // integer should be checked before string - .isInteger(v -> v.asString().concat(of(" - integer"))) - .isNumber(v -> v.asString().concat(of(" - number"))) - .isString(v -> v.asString().concat(of(" - string"))) - .isDate(v -> v.asString().concat(of(" - date"))) - .isArray((MqlArray v) -> v.sum(a -> a).asString().concat(of(" - array"))) - .isDocument(v -> v.getString("_id").concat(of(" - document"))) + .isInteger(v -> v.asString().append(of(" - integer"))) + .isNumber(v -> v.asString().append(of(" - number"))) + .isString(v -> v.asString().append(of(" - string"))) + .isDate(v -> v.asString().append(of(" - date"))) + .isArray((MqlArray v) -> v.sum(a -> a).asString().append(of(" - array"))) + .isDocument(v -> v.getString("_id").append(of(" - document"))) .isNull(v -> of("null - null")) .defaults(v -> of("default")) ).toLower(); @@ -141,13 +141,13 @@ public void switchTypesTest() { "12 - map", ofMap(Document.parse("{a: '1', b: '2'}")).switchOn(on -> on .isMap((MqlMap v) -> v.entrySet() - .join(e -> e.getValue()).concat(of(" - map"))))); + .joinStrings(e -> e.getValue()).append(of(" - map"))))); // arrays via isArray, and tests signature: assertExpression( "ab - array", ofArray(of("a"), of("b")).switchOn(on -> on .isArray((MqlArray v) -> v - .join(e -> e).concat(of(" - array"))))); + .joinStrings(e -> e).append(of(" - array"))))); } private BranchesIntermediary branches(final Branches on) { diff --git a/driver-core/src/test/functional/com/mongodb/client/model/mql/DocumentMqlValuesFunctionalTest.java b/driver-core/src/test/functional/com/mongodb/client/model/mql/DocumentMqlValuesFunctionalTest.java index 320a63bbab9..9afc7274953 100644 --- a/driver-core/src/test/functional/com/mongodb/client/model/mql/DocumentMqlValuesFunctionalTest.java +++ b/driver-core/src/test/functional/com/mongodb/client/model/mql/DocumentMqlValuesFunctionalTest.java @@ -271,13 +271,13 @@ public void hasTest() { MqlDocument d = ofDoc("{a: 1, null: null}"); assertExpression( true, - d.has("a"), + d.hasField("a"), "{'$ne': [{'$getField': {'input': {'$literal': {'a': 1, 'null': null}}, 'field': 'a'}}, '$$REMOVE']}"); assertExpression( false, - d.has("not_a")); + d.hasField("not_a")); assertExpression( true, - d.has("null")); + d.hasField("null")); } } diff --git a/driver-core/src/test/functional/com/mongodb/client/model/mql/MapMqlValuesFunctionalTest.java b/driver-core/src/test/functional/com/mongodb/client/model/mql/MapMqlValuesFunctionalTest.java index 6fc51d7b6ad..351ca180a9a 100644 --- a/driver-core/src/test/functional/com/mongodb/client/model/mql/MapMqlValuesFunctionalTest.java +++ b/driver-core/src/test/functional/com/mongodb/client/model/mql/MapMqlValuesFunctionalTest.java @@ -166,7 +166,7 @@ public void entrySetTest() { // key/value usage assertExpression( "keyA|keyB|", - mapA1B2.entrySet().map(v -> v.getKey().concat(of("|"))).join(v -> v)); + mapA1B2.entrySet().map(v -> v.getKey().append(of("|"))).joinStrings(v -> v)); assertExpression( 23, mapA1B2.entrySet().map(v -> v.getValue().add(10)).sum(v -> v)); diff --git a/driver-core/src/test/functional/com/mongodb/client/model/mql/StringMqlValuesFunctionalTest.java b/driver-core/src/test/functional/com/mongodb/client/model/mql/StringMqlValuesFunctionalTest.java index dfcdf4535e5..34e39b9d483 100644 --- a/driver-core/src/test/functional/com/mongodb/client/model/mql/StringMqlValuesFunctionalTest.java +++ b/driver-core/src/test/functional/com/mongodb/client/model/mql/StringMqlValuesFunctionalTest.java @@ -45,7 +45,7 @@ public void concatTest() { // https://www.mongodb.com/docs/manual/reference/operator/aggregation/concat/ assertExpression( "abc".concat("de"), - of("abc").concat(of("de")), + of("abc").append(of("de")), "{'$concat': ['abc', 'de']}"); } @@ -72,21 +72,21 @@ public void strLenTest() { // https://www.mongodb.com/docs/manual/reference/operator/aggregation/strLenCP/ assertExpression( "abc".codePointCount(0, 3), - of("abc").strLen(), + of("abc").length(), "{'$strLenCP': 'abc'}"); // unicode assertExpression( jalapeno.codePointCount(0, jalapeno.length()), - of(jalapeno).strLen(), + of(jalapeno).length(), "{'$strLenCP': '" + jalapeno + "'}"); assertExpression( sushi.codePointCount(0, sushi.length()), - of(sushi).strLen(), + of(sushi).length(), "{'$strLenCP': '" + sushi + "'}"); assertExpression( fish.codePointCount(0, fish.length()), - of(fish).strLen(), + of(fish).length(), "{'$strLenCP': '" + fish + "'}"); } @@ -95,30 +95,30 @@ public void strLenBytesTest() { // https://www.mongodb.com/docs/manual/reference/operator/aggregation/strLenBytes/ assertExpression( "abc".getBytes(StandardCharsets.UTF_8).length, - of("abc").strLenBytes(), + of("abc").lengthBytes(), "{'$strLenBytes': 'abc'}"); // unicode assertExpression( jalapeno.getBytes(StandardCharsets.UTF_8).length, - of(jalapeno).strLenBytes(), + of(jalapeno).lengthBytes(), "{'$strLenBytes': '" + jalapeno + "'}"); assertExpression( sushi.getBytes(StandardCharsets.UTF_8).length, - of(sushi).strLenBytes(), + of(sushi).lengthBytes(), "{'$strLenBytes': '" + sushi + "'}"); assertExpression( fish.getBytes(StandardCharsets.UTF_8).length, - of(fish).strLenBytes(), + of(fish).lengthBytes(), "{'$strLenBytes': '" + fish + "'}"); // comparison - assertExpression(8, of(jalapeno).strLen()); - assertExpression(9, of(jalapeno).strLenBytes()); - assertExpression(2, of(sushi).strLen()); - assertExpression(6, of(sushi).strLenBytes()); - assertExpression(1, of(fish).strLen()); - assertExpression(4, of(fish).strLenBytes()); + assertExpression(8, of(jalapeno).length()); + assertExpression(9, of(jalapeno).lengthBytes()); + assertExpression(2, of(sushi).length()); + assertExpression(6, of(sushi).lengthBytes()); + assertExpression(1, of(fish).length()); + assertExpression(4, of(fish).lengthBytes()); } @Test diff --git a/driver-core/src/test/functional/com/mongodb/client/model/mql/TypeMqlValuesFunctionalTest.java b/driver-core/src/test/functional/com/mongodb/client/model/mql/TypeMqlValuesFunctionalTest.java index 56d6dce92b9..7df9748da1f 100644 --- a/driver-core/src/test/functional/com/mongodb/client/model/mql/TypeMqlValuesFunctionalTest.java +++ b/driver-core/src/test/functional/com/mongodb/client/model/mql/TypeMqlValuesFunctionalTest.java @@ -314,13 +314,13 @@ public void millisecondsToDateTest() { // https://www.mongodb.com/docs/manual/reference/operator/aggregation/toDate/ assertExpression( Instant.ofEpochMilli(1234), - of(1234L).millisecondsToDate(), + of(1234L).millisecondsAsDate(), "{'$toDate': {'$numberLong': '1234'}}"); // This does not accept plain integers: assertThrows(MongoCommandException.class, () -> assertExpression( Instant.parse("2007-12-03T10:15:30.005Z"), - of(1234).millisecondsToDate(), + of(1234).millisecondsAsDate(), "{'$toDate': 1234}")); } } From 658840650fde08068ef34cd884fe457bb9035c91 Mon Sep 17 00:00:00 2001 From: Maxim Katcharov Date: Fri, 27 Jan 2023 14:22:56 -0700 Subject: [PATCH 27/27] Update naming, terms, and missing checks and annotations (#1073) JAVA-3879 --- .../mongodb/client/model/mql/MqlArray.java | 2 +- .../mongodb/client/model/mql/MqlBoolean.java | 2 +- .../mongodb/client/model/mql/MqlDocument.java | 4 +- .../mongodb/client/model/mql/MqlEntry.java | 14 ------ .../client/model/mql/MqlExpression.java | 8 ++-- .../client/model/mql/MqlExpressionCodec.java | 2 +- .../com/mongodb/client/model/mql/MqlMap.java | 3 +- .../mongodb/client/model/mql/MqlNumber.java | 8 ++-- .../mongodb/client/model/mql/MqlString.java | 4 +- .../mongodb/client/model/mql/MqlValue.java | 4 +- .../mongodb/client/model/mql/MqlValues.java | 10 ++-- .../ArithmeticMqlValuesFunctionalTest.java | 12 ++--- .../mql/BooleanMqlValuesFunctionalTest.java | 2 +- .../mql/ControlMqlValuesFunctionalTest.java | 2 +- .../model/mql/MapMqlValuesFunctionalTest.java | 13 ++--- .../client/model/mql/NotNullApiTest.java | 2 + .../mql/InContextMqlValuesFunctionalTest.java | 47 ++++++------------- 17 files changed, 53 insertions(+), 86 deletions(-) diff --git a/driver-core/src/main/com/mongodb/client/model/mql/MqlArray.java b/driver-core/src/main/com/mongodb/client/model/mql/MqlArray.java index d056c30aa90..047e294c8e9 100644 --- a/driver-core/src/main/com/mongodb/client/model/mql/MqlArray.java +++ b/driver-core/src/main/com/mongodb/client/model/mql/MqlArray.java @@ -209,7 +209,7 @@ public interface MqlArray extends MqlValue { * necessary, then the identity function {@code array.union(v -> v)} should * be used. * - * @see MqlMap#entrySet() + * @see MqlMap#entries() * @param mapper the mapper function. * @return the resulting value. * @param the type of the resulting map's values. diff --git a/driver-core/src/main/com/mongodb/client/model/mql/MqlBoolean.java b/driver-core/src/main/com/mongodb/client/model/mql/MqlBoolean.java index a752d8037e9..5e594a757c7 100644 --- a/driver-core/src/main/com/mongodb/client/model/mql/MqlBoolean.java +++ b/driver-core/src/main/com/mongodb/client/model/mql/MqlBoolean.java @@ -61,7 +61,7 @@ public interface MqlBoolean extends MqlValue { * @param ifTrue the ifTrue value. * @param ifFalse the ifFalse value. * @return the resulting value. - * @param The type of the resulting expression. + * @param The type of the resulting value. */ T cond(T ifTrue, T ifFalse); diff --git a/driver-core/src/main/com/mongodb/client/model/mql/MqlDocument.java b/driver-core/src/main/com/mongodb/client/model/mql/MqlDocument.java index 54f1e3f4d95..b99d5b3354b 100644 --- a/driver-core/src/main/com/mongodb/client/model/mql/MqlDocument.java +++ b/driver-core/src/main/com/mongodb/client/model/mql/MqlDocument.java @@ -186,7 +186,7 @@ default MqlBoolean getBoolean(final String fieldName, final boolean other) { default MqlNumber getNumber(final String fieldName, final Number other) { Assertions.notNull("fieldName", fieldName); Assertions.notNull("other", other); - return getNumber(fieldName, MqlValues.numberToExpression(other)); + return getNumber(fieldName, MqlValues.numberToMqlNumber(other)); } /** @@ -521,7 +521,7 @@ default MqlDocument getDocument(final String fieldName, final Bson other) { * @param the type. */ - MqlMap asMap(); + MqlMap<@MqlUnchecked(TYPE_ARGUMENT) T> asMap(); /** * The result of passing {@code this} value to the provided function. diff --git a/driver-core/src/main/com/mongodb/client/model/mql/MqlEntry.java b/driver-core/src/main/com/mongodb/client/model/mql/MqlEntry.java index abe6f4414cd..bcb1f26e251 100644 --- a/driver-core/src/main/com/mongodb/client/model/mql/MqlEntry.java +++ b/driver-core/src/main/com/mongodb/client/model/mql/MqlEntry.java @@ -19,8 +19,6 @@ import com.mongodb.annotations.Beta; import com.mongodb.annotations.Sealed; -import static com.mongodb.client.model.mql.MqlValues.of; - /** * A map entry {@linkplain MqlValue value} in the context * of the MongoDB Query Language (MQL). An entry has a @@ -74,16 +72,4 @@ public interface MqlEntry extends MqlValue { * @return the resulting entry. */ MqlEntry setKey(MqlString key); - - /** - * An entry with the same value as {@code this} entry, and the - * specified {@code key}. - * - * @mongodb.server.release 5.0 - * @param key the key. - * @return the resulting entry. - */ - default MqlEntry setKey(final String key) { - return setKey(of(key)); - } } diff --git a/driver-core/src/main/com/mongodb/client/model/mql/MqlExpression.java b/driver-core/src/main/com/mongodb/client/model/mql/MqlExpression.java index 577e48a7c02..eb7ea9a68cd 100644 --- a/driver-core/src/main/com/mongodb/client/model/mql/MqlExpression.java +++ b/driver-core/src/main/com/mongodb/client/model/mql/MqlExpression.java @@ -43,8 +43,8 @@ final class MqlExpression } /** - * Exposes the evaluated BsonValue so that expressions may be used in - * aggregations. Non-public, as it is intended to be used only by the + * Exposes the evaluated BsonValue so that this mql expression may be used + * in aggregations. Non-public, as it is intended to be used only by the * {@link MqlExpressionCodec}. */ BsonValue toBsonValue(final CodecRegistry codecRegistry) { @@ -67,11 +67,13 @@ public T getValue() { @Override public MqlEntry setValue(final T value) { + Assertions.notNull("value", value); return setFieldInternal("v", value); } @Override public MqlEntry setKey(final MqlString key) { + Assertions.notNull("key", key); return setFieldInternal("k", key); } @@ -1081,7 +1083,7 @@ public MqlMap merge(final MqlMap other) { } @Override - public MqlArray> entrySet() { + public MqlArray> entries() { return newMqlExpression(ast("$objectToArray")); } diff --git a/driver-core/src/main/com/mongodb/client/model/mql/MqlExpressionCodec.java b/driver-core/src/main/com/mongodb/client/model/mql/MqlExpressionCodec.java index cc6da160345..70f4329b6d0 100644 --- a/driver-core/src/main/com/mongodb/client/model/mql/MqlExpressionCodec.java +++ b/driver-core/src/main/com/mongodb/client/model/mql/MqlExpressionCodec.java @@ -34,7 +34,7 @@ final class MqlExpressionCodec implements Codec { @Override public MqlExpression decode(final BsonReader reader, final DecoderContext decoderContext) { - throw new UnsupportedOperationException("Decoding to an expression is not supported"); + throw new UnsupportedOperationException("Decoding to an MqlExpression is not supported"); } @Override diff --git a/driver-core/src/main/com/mongodb/client/model/mql/MqlMap.java b/driver-core/src/main/com/mongodb/client/model/mql/MqlMap.java index b25af981105..24ee3ef405b 100644 --- a/driver-core/src/main/com/mongodb/client/model/mql/MqlMap.java +++ b/driver-core/src/main/com/mongodb/client/model/mql/MqlMap.java @@ -55,6 +55,7 @@ public interface MqlMap extends MqlValue { * @return the resulting value. */ default MqlBoolean has(final String key) { + Assertions.notNull("key", key); return has(of(key)); } @@ -188,7 +189,7 @@ default MqlMap unset(final String key) { * @see MqlArray#asMap * @return the resulting value. */ - MqlArray> entrySet(); + MqlArray> entries(); /** * {@code this} map as a {@linkplain MqlDocument document}. diff --git a/driver-core/src/main/com/mongodb/client/model/mql/MqlNumber.java b/driver-core/src/main/com/mongodb/client/model/mql/MqlNumber.java index e2dbed35393..ec3099047b8 100644 --- a/driver-core/src/main/com/mongodb/client/model/mql/MqlNumber.java +++ b/driver-core/src/main/com/mongodb/client/model/mql/MqlNumber.java @@ -50,7 +50,7 @@ public interface MqlNumber extends MqlValue { */ default MqlNumber multiply(final Number other) { Assertions.notNull("other", other); - return this.multiply(MqlValues.numberToExpression(other)); + return this.multiply(MqlValues.numberToMqlNumber(other)); } /** @@ -73,7 +73,7 @@ default MqlNumber multiply(final Number other) { */ default MqlNumber divide(final Number other) { Assertions.notNull("other", other); - return this.divide(MqlValues.numberToExpression(other)); + return this.divide(MqlValues.numberToMqlNumber(other)); } /** @@ -92,7 +92,7 @@ default MqlNumber divide(final Number other) { */ default MqlNumber add(final Number other) { Assertions.notNull("other", other); - return this.add(MqlValues.numberToExpression(other)); + return this.add(MqlValues.numberToMqlNumber(other)); } /** @@ -111,7 +111,7 @@ default MqlNumber add(final Number other) { */ default MqlNumber subtract(final Number other) { Assertions.notNull("other", other); - return this.subtract(MqlValues.numberToExpression(other)); + return this.subtract(MqlValues.numberToMqlNumber(other)); } /** diff --git a/driver-core/src/main/com/mongodb/client/model/mql/MqlString.java b/driver-core/src/main/com/mongodb/client/model/mql/MqlString.java index 399f09879cb..dd24a8c94a2 100644 --- a/driver-core/src/main/com/mongodb/client/model/mql/MqlString.java +++ b/driver-core/src/main/com/mongodb/client/model/mql/MqlString.java @@ -48,8 +48,8 @@ public interface MqlString extends MqlValue { MqlString toUpper(); /** - * The concatenation of {@code this} string, followed by - * the {@code other} string. + * The result of appending the {@code other} string to the end of + * {@code this} string (strict concatenation). * * @param other the other value. * @return the resulting value. diff --git a/driver-core/src/main/com/mongodb/client/model/mql/MqlValue.java b/driver-core/src/main/com/mongodb/client/model/mql/MqlValue.java index 24c7846dcc2..9366ce77fe9 100644 --- a/driver-core/src/main/com/mongodb/client/model/mql/MqlValue.java +++ b/driver-core/src/main/com/mongodb/client/model/mql/MqlValue.java @@ -38,7 +38,7 @@ * filtered and summed, in a style similar to that of the Java Stream API: * *

    {@code
    - * import static com.mongodb.client.model.expressions.Expressions.current;
    + * import static com.mongodb.client.model.mql.MqlValues.current;
      * MongoCollection col = ...;
      * AggregateIterable result = col.aggregate(Arrays.asList(
      *     addFields(new Field<>("result", current()
    @@ -254,7 +254,7 @@ public interface MqlValue {
          * @return the resulting value.
          * @param  the type of the values of the resulting map.
          */
    -     MqlMap isMapOr(MqlMap<@MqlUnchecked(TYPE_ARGUMENT) ? extends T> other);
    +     MqlMap<@MqlUnchecked(TYPE_ARGUMENT) T> isMapOr(MqlMap other);
     
         /**
          * The {@linkplain MqlString string} representation of {@code this} value.
    diff --git a/driver-core/src/main/com/mongodb/client/model/mql/MqlValues.java b/driver-core/src/main/com/mongodb/client/model/mql/MqlValues.java
    index cbfe80b34e6..c91994872b3 100644
    --- a/driver-core/src/main/com/mongodb/client/model/mql/MqlValues.java
    +++ b/driver-core/src/main/com/mongodb/client/model/mql/MqlValues.java
    @@ -368,9 +368,7 @@ public static  MqlMap ofMap() {
          */
         public static MqlDocument 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
    -        // (and need to be wrapped in $literal anyway). This would be brittle.
    +        // All documents are wrapped in a $literal; this is the least brittle approach.
             return new MqlExpression<>((cr) -> new AstPlaceholder(new BsonDocument("$literal",
                     document.toBsonDocument(BsonDocument.class, cr))));
         }
    @@ -391,13 +389,13 @@ public static MqlDocument of(final Bson document) {
          * @return the null value
          */
         public static MqlValue ofNull() {
    -        // There is no specific expression type corresponding to Null,
    -        // and Null is not a value in any other expression type.
    +        // There is no specific mql type corresponding to Null,
    +        // and Null is not a value in any other mql type.
             return new MqlExpression<>((cr) -> new AstPlaceholder(new BsonNull()))
                     .assertImplementsAllExpressions();
         }
     
    -    static MqlNumber numberToExpression(final Number number) {
    +    static MqlNumber numberToMqlNumber(final Number number) {
             Assertions.notNull("number", number);
             if (number instanceof Integer) {
                 return of((int) number);
    diff --git a/driver-core/src/test/functional/com/mongodb/client/model/mql/ArithmeticMqlValuesFunctionalTest.java b/driver-core/src/test/functional/com/mongodb/client/model/mql/ArithmeticMqlValuesFunctionalTest.java
    index 5bb559d13d4..cbc9451ffe3 100644
    --- a/driver-core/src/test/functional/com/mongodb/client/model/mql/ArithmeticMqlValuesFunctionalTest.java
    +++ b/driver-core/src/test/functional/com/mongodb/client/model/mql/ArithmeticMqlValuesFunctionalTest.java
    @@ -23,7 +23,7 @@
     import java.math.RoundingMode;
     
     import static com.mongodb.ClusterFixture.serverVersionAtLeast;
    -import static com.mongodb.client.model.mql.MqlValues.numberToExpression;
    +import static com.mongodb.client.model.mql.MqlValues.numberToMqlNumber;
     import static com.mongodb.client.model.mql.MqlValues.of;
     import static org.junit.jupiter.api.Assertions.assertEquals;
     import static org.junit.jupiter.api.Assertions.assertNotEquals;
    @@ -55,12 +55,12 @@ public void literalsTest() {
             assertNotEquals(toBsonValue(1.0), evaluate(of(1L)));
     
             // Number conversions; used internally
    -        assertExpression(1, numberToExpression(1));
    -        assertExpression(1L, numberToExpression(1L));
    -        assertExpression(1.0, numberToExpression(1.0));
    -        assertExpression(Decimal128.parse("1.0"), numberToExpression(Decimal128.parse("1.0")));
    +        assertExpression(1, numberToMqlNumber(1));
    +        assertExpression(1L, numberToMqlNumber(1L));
    +        assertExpression(1.0, numberToMqlNumber(1.0));
    +        assertExpression(Decimal128.parse("1.0"), numberToMqlNumber(Decimal128.parse("1.0")));
             assertThrows(IllegalArgumentException.class,
    -                () -> assertExpression("n/a", numberToExpression(BigDecimal.valueOf(1))));
    +                () -> assertExpression("n/a", numberToMqlNumber(BigDecimal.valueOf(1))));
         }
     
         @Test
    diff --git a/driver-core/src/test/functional/com/mongodb/client/model/mql/BooleanMqlValuesFunctionalTest.java b/driver-core/src/test/functional/com/mongodb/client/model/mql/BooleanMqlValuesFunctionalTest.java
    index 377f7d4bfbb..0c5b8bd48ce 100644
    --- a/driver-core/src/test/functional/com/mongodb/client/model/mql/BooleanMqlValuesFunctionalTest.java
    +++ b/driver-core/src/test/functional/com/mongodb/client/model/mql/BooleanMqlValuesFunctionalTest.java
    @@ -18,7 +18,7 @@
     
     import org.junit.jupiter.api.Test;
     
    -@SuppressWarnings({"PointlessBooleanExpression", "ConstantConditions", "ConstantConditionalExpression"})
    +@SuppressWarnings({"PointlessBooleanExpression", "ConstantConditions", "ConstantConditionalExpression", "SimplifyBooleanExpression"})
     class BooleanMqlValuesFunctionalTest extends AbstractMqlValuesFunctionalTest {
         // https://www.mongodb.com/docs/manual/reference/operator/aggregation/#boolean-expression-operators
         // (Complete as of 6.0)
    diff --git a/driver-core/src/test/functional/com/mongodb/client/model/mql/ControlMqlValuesFunctionalTest.java b/driver-core/src/test/functional/com/mongodb/client/model/mql/ControlMqlValuesFunctionalTest.java
    index 3173ddb4521..706f20c2e60 100644
    --- a/driver-core/src/test/functional/com/mongodb/client/model/mql/ControlMqlValuesFunctionalTest.java
    +++ b/driver-core/src/test/functional/com/mongodb/client/model/mql/ControlMqlValuesFunctionalTest.java
    @@ -140,7 +140,7 @@ public void switchTypesTest() {
             assertExpression(
                     "12 - map",
                     ofMap(Document.parse("{a: '1', b: '2'}")).switchOn(on -> on
    -                        .isMap((MqlMap v) -> v.entrySet()
    +                        .isMap((MqlMap v) -> v.entries()
                                     .joinStrings(e -> e.getValue()).append(of(" - map")))));
             // arrays via isArray, and tests signature:
             assertExpression(
    diff --git a/driver-core/src/test/functional/com/mongodb/client/model/mql/MapMqlValuesFunctionalTest.java b/driver-core/src/test/functional/com/mongodb/client/model/mql/MapMqlValuesFunctionalTest.java
    index 351ca180a9a..b85100e4e05 100644
    --- a/driver-core/src/test/functional/com/mongodb/client/model/mql/MapMqlValuesFunctionalTest.java
    +++ b/driver-core/src/test/functional/com/mongodb/client/model/mql/MapMqlValuesFunctionalTest.java
    @@ -109,9 +109,6 @@ public void getSetEntryTest() {
             assertExpression(
                     Document.parse("{k: 'keyB', 'v': 1}"),
                     entryA1.setKey(of("keyB")));
    -        assertExpression(
    -                Document.parse("{k: 'keyB', 'v': 1}"),
    -                entryA1.setKey("keyB"));
         }
     
         @Test
    @@ -159,23 +156,23 @@ public void entrySetTest() {
             // https://www.mongodb.com/docs/manual/reference/operator/aggregation/objectToArray/ (23)
             assertExpression(
                     Arrays.asList(Document.parse("{'k': 'k1', 'v': 1}")),
    -                MqlValues.ofMap().set("k1", of(1)).entrySet(),
    +                MqlValues.ofMap().set("k1", of(1)).entries(),
                     "{'$objectToArray': {'$setField': "
                             + "{'field': 'k1', 'input': {'$literal': {}}, 'value': 1}}}");
     
             // key/value usage
             assertExpression(
                     "keyA|keyB|",
    -                mapA1B2.entrySet().map(v -> v.getKey().append(of("|"))).joinStrings(v -> v));
    +                mapA1B2.entries().map(v -> v.getKey().append(of("|"))).joinStrings(v -> v));
             assertExpression(
                     23,
    -                mapA1B2.entrySet().map(v -> v.getValue().add(10)).sum(v -> v));
    +                mapA1B2.entries().map(v -> v.getValue().add(10)).sum(v -> v));
     
             // combined entrySet-buildMap usage
             assertExpression(
                     Document.parse("{'keyA': 2, 'keyB': 3}"),
                     mapA1B2
    -                        .entrySet()
    +                        .entries()
                             .map(v -> v.setValue(v.getValue().add(1)))
                             .asMap(v -> v));
     
    @@ -185,7 +182,7 @@ public void entrySetTest() {
                     Arrays.asList(
                             Document.parse("{'k': 'warehouse1', 'v': 2500}"),
                             Document.parse("{'k': 'warehouse2', 'v': 500}")),
    -                doc.getMap("instock").entrySet(),
    +                doc.getMap("instock").entries(),
                     "{'$objectToArray': {'$getField': {'input': {'$literal': "
                             + "{'instock': {'warehouse1': 2500, 'warehouse2': 500}}}, 'field': 'instock'}}}");
         }
    diff --git a/driver-core/src/test/functional/com/mongodb/client/model/mql/NotNullApiTest.java b/driver-core/src/test/functional/com/mongodb/client/model/mql/NotNullApiTest.java
    index dfc39ce87c7..97635bbf44d 100644
    --- a/driver-core/src/test/functional/com/mongodb/client/model/mql/NotNullApiTest.java
    +++ b/driver-core/src/test/functional/com/mongodb/client/model/mql/NotNullApiTest.java
    @@ -35,6 +35,7 @@
     import static com.mongodb.assertions.Assertions.fail;
     import static com.mongodb.client.model.mql.MqlValues.of;
     import static com.mongodb.client.model.mql.MqlValues.ofArray;
    +import static com.mongodb.client.model.mql.MqlValues.ofEntry;
     import static com.mongodb.client.model.mql.MqlValues.ofMap;
     import static com.mongodb.client.model.mql.MqlValues.ofNull;
     
    @@ -56,6 +57,7 @@ public void notNullApiTest() {
             mapping.put(MqlMap.class, ofMap(BsonDocument.parse("{}")));
             mapping.put(MqlArray.class, ofArray());
             mapping.put(MqlValue.class, ofNull());
    +        mapping.put(MqlEntry.class, ofEntry(of(""), of("")));
             mapping.put(Branches.class, new Branches<>());
             mapping.put(BranchesIntermediary.class, new BranchesIntermediary<>(Collections.emptyList()));
             mapping.put(BranchesTerminal.class, new BranchesTerminal<>(Collections.emptyList(), null));
    diff --git a/driver-sync/src/test/functional/com/mongodb/client/model/mql/InContextMqlValuesFunctionalTest.java b/driver-sync/src/test/functional/com/mongodb/client/model/mql/InContextMqlValuesFunctionalTest.java
    index 1cd5b0916a3..e55d74c167a 100644
    --- a/driver-sync/src/test/functional/com/mongodb/client/model/mql/InContextMqlValuesFunctionalTest.java
    +++ b/driver-sync/src/test/functional/com/mongodb/client/model/mql/InContextMqlValuesFunctionalTest.java
    @@ -18,16 +18,12 @@
     
     import com.mongodb.MongoClientSettings;
     import com.mongodb.client.AggregateIterable;
    +import com.mongodb.client.DatabaseTestCase;
     import com.mongodb.client.FindIterable;
    -import com.mongodb.client.MongoClient;
    -import com.mongodb.client.MongoClients;
    -import com.mongodb.client.MongoCollection;
     import com.mongodb.client.model.Aggregates;
     import org.bson.Document;
     import org.bson.conversions.Bson;
    -import org.junit.jupiter.api.AfterEach;
    -import org.junit.jupiter.api.BeforeEach;
    -import org.junit.jupiter.api.Test;
    +import org.junit.Test;
     
     import java.util.ArrayList;
     import java.util.Arrays;
    @@ -46,32 +42,17 @@
     import static com.mongodb.client.model.mql.MqlValues.current;
     import static com.mongodb.client.model.mql.MqlValues.of;
     import static com.mongodb.client.model.mql.MqlValues.ofArray;
    -import static org.junit.jupiter.api.Assertions.assertEquals;
    -import static org.junit.jupiter.api.Assumptions.assumeTrue;
    +import static org.junit.Assert.assertEquals;
    +import static org.junit.Assume.assumeTrue;
     
    -class InContextMqlValuesFunctionalTest extends AbstractMqlValuesFunctionalTest {
    -
    -    private MongoClient client;
    -    private MongoCollection col;
    -
    -    @BeforeEach
    -    public void setUp() {
    -        client = MongoClients.create();
    -        col = client.getDatabase("testdb").getCollection("testcol");
    -        col.drop();
    -    }
    -
    -    @AfterEach
    -    public void tearDown() {
    -        client.close();
    -    }
    +public class InContextMqlValuesFunctionalTest extends DatabaseTestCase {
     
         private static String bsonToString(final Bson project) {
             return project.toBsonDocument(Document.class, MongoClientSettings.getDefaultCodecRegistry()).toString().replaceAll("\"", "'");
         }
     
         private List aggregate(final Bson... stages) {
    -        AggregateIterable result = col.aggregate(Arrays.asList(stages));
    +        AggregateIterable result = collection.aggregate(Arrays.asList(stages));
             List results = new ArrayList<>();
             result.forEach(r -> results.add(r));
             return results;
    @@ -80,12 +61,12 @@ private List aggregate(final Bson... stages) {
         @Test
         public void findTest() {
             assumeTrue(serverVersionAtLeast(5, 0)); // get/setField
    -        col.insertMany(Arrays.asList(
    +        collection.insertMany(Arrays.asList(
                     Document.parse("{_id: 1, x: 0, y: 2}"),
                     Document.parse("{_id: 2, x: 0, y: 3}"),
                     Document.parse("{_id: 3, x: 1, y: 3}")));
     
    -        FindIterable iterable = col.find(expr(
    +        FindIterable iterable = collection.find(expr(
                     current().getInteger("x").eq(of(1))));
             List results = new ArrayList<>();
             iterable.forEach(r -> results.add(r));
    @@ -98,7 +79,7 @@ public void findTest() {
         @Test
         public void matchTest() {
             assumeTrue(serverVersionAtLeast(5, 0)); // get/setField
    -        col.insertMany(Arrays.asList(
    +        collection.insertMany(Arrays.asList(
                     Document.parse("{_id: 1, x: 0, y: 2}"),
                     Document.parse("{_id: 2, x: 0, y: 3}"),
                     Document.parse("{_id: 3, x: 1, y: 3}")));
    @@ -114,14 +95,14 @@ public void matchTest() {
         @Test
         public void currentAsMapMatchTest() {
             assumeTrue(serverVersionAtLeast(5, 0)); // get/setField
    -        col.insertMany(Arrays.asList(
    +        collection.insertMany(Arrays.asList(
                     Document.parse("{_id: 1, x: 0, y: 2}"),
                     Document.parse("{_id: 2, x: 0, y: 3}"),
                     Document.parse("{_id: 3, x: 1, y: 3}")));
     
             List results = aggregate(
                     match(expr(MqlValues.currentAsMap()
    -                        .entrySet()
    +                        .entries()
                             .map(e -> e.getValue())
                             .sum(v -> v).eq(of(7)))));
     
    @@ -133,7 +114,7 @@ public void currentAsMapMatchTest() {
         @Test
         public void projectTest() {
             assumeTrue(serverVersionAtLeast(5, 0)); // get/setField
    -        col.insertMany(Arrays.asList(
    +        collection.insertMany(Arrays.asList(
                     Document.parse("{_id: 1, x: 0, y: 2}")));
     
             List expected = Arrays.asList(Document.parse("{_id: 1, x: 0, c: 2}"));
    @@ -160,7 +141,7 @@ public void projectTest() {
         @Test
         public void projectTest2() {
             assumeTrue(serverVersionAtLeast(5, 0)); // get/setField
    -        col.insertMany(Arrays.asList(Document.parse("{_id: 0, x: 1}")));
    +        collection.insertMany(Arrays.asList(Document.parse("{_id: 0, x: 1}")));
     
             // new, nestedArray
             Bson projectNestedArray = project(fields(excludeId(), computed("nestedArray", ofArray(
    @@ -185,7 +166,7 @@ public void projectTest2() {
         @Test
         public void groupTest() {
             assumeTrue(serverVersionAtLeast(5, 0)); // get/setField
    -        col.insertMany(Arrays.asList(
    +        collection.insertMany(Arrays.asList(
                     Document.parse("{t: 0, a: 1}"),
                     Document.parse("{t: 0, a: 2}"),
                     Document.parse("{t: 1, a: 9}")));