From de1965ddfb9d5b643d5934c3ccade461d50a3023 Mon Sep 17 00:00:00 2001 From: rustagir Date: Wed, 7 Aug 2024 14:16:50 -0400 Subject: [PATCH 01/11] DOCSP-41146: agg exp operations --- source/aggregation/agg-exp-ops.txt | 1176 +++++++++++++++++ source/includes/aggregation/aggExpressions.kt | 529 ++++++++ 2 files changed, 1705 insertions(+) create mode 100644 source/aggregation/agg-exp-ops.txt create mode 100644 source/includes/aggregation/aggExpressions.kt diff --git a/source/aggregation/agg-exp-ops.txt b/source/aggregation/agg-exp-ops.txt new file mode 100644 index 00000000..04435062 --- /dev/null +++ b/source/aggregation/agg-exp-ops.txt @@ -0,0 +1,1176 @@ +.. _kotlin-sync-sync-aggregation-expression-operations: + +================================= +Aggregation Expression Operations +================================= + +.. contents:: On this page + :local: + :backlinks: none + :depth: 2 + :class: singlecol + +Overview +-------- + +In this guide, you can learn how to use the {+driver-short+} to construct +expressions for use in aggregation pipelines. You can perform +expression operations with discoverable, typesafe {+language+} methods rather +than by using BSON documents. Because these methods follow the fluent interface +pattern, you can chain aggregation operations together to create code +that is compact and naturally readable. + +The operations in this guide use methods from the +`com.mongodb.client.model.mql <{+core-api+}/com/mongodb/client/model/mql/package-summary.html>`__ package. +These methods provide an idiomatic way to use the Query API, +the mechanism by which the driver interacts with a MongoDB deployment. To learn more +about the Query API, see the :manual:`Server manual documentation `. + +How to Use Operations +--------------------- + +The examples in this guide assume that you include the following imports +in your code: + +.. code-block:: kotlin + :copyable: true + + import com.mongodb.client.model.Aggregates + import com.mongodb.client.model.Accumulators + import com.mongodb.client.model.Projections + import com.mongodb.client.model.Filters + import com.mongodb.client.model.mql.MqlValues + +To access document fields in an expression, you need to reference the +current document being processed by the aggregation pipeline. Use the +``current()`` method to refer to this document. To access the value of a +field, you must use the appropriately typed method, such as +``getString()`` or ``getDate()``. When you specify the type for a field, +you ensure that the driver provides only those methods which are +compatible with that type. The following code shows how to reference a +string field called ``name``: + +.. code-block:: kotlin + :copyable: true + + current().getString("name") + +To specify a value in an operation, pass it to the ``of()`` constructor method to +convert it to a valid type. The following code shows how to reference a +value of ``1.0``: + +.. code-block:: kotlin + :copyable: true + + of(1.0) + +To create an operation, chain a method to your field or value reference. +You can build more complex operations by chaining additional methods. + +The following example creates an operation to find patients in New +Mexico who have visited the doctor’s office at least once. The operation +performs the following actions: + +- Checks if the size of the ``visitDates`` array is greater than ``0`` + by using the ``gt()`` method +- Checks if the ``state`` field value is “New Mexico” by using the + ``eq()`` method + +The ``and()`` method links these operations so that the pipeline stage +matches only documents that meet both criteria. + +.. code-block:: kotlin + :copyable: true + + current() + .getArray("visitDates") + .size() + .gt(of(0)) + .and(current() + .getString("state") + .eq(of("New Mexico"))) + +While some aggregation stages, such as ``group()``, accept operations +directly, other stages expect that you first include your operation in a +method such as ``computed()`` or ``expr()``. These methods, which take +values of type ``TExpression``, allow you to use your expressions in +certain aggregations. + +To complete your aggregation pipeline stage, include your expression +in an aggregates builder method. The following list provides examples of +how to include your expression in common aggregates builder methods: + +- ``match(expr())`` +- ``project(fields(computed("", )))`` +- ``group()`` + +.. TODO To learn more about these methods, see the +.. :ref:`kotlin-sync-sync-aggregation`. + +The examples use the ``listOf()`` method to create a list of +aggregation stages. Then, the examples pass the pipeline to the +``aggregate()`` method of ``MongoCollection``. + +Constructor Methods +------------------- + +You can use these constructor methods to define values for use in {+language+} aggregation +expressions. + +.. list-table:: + :header-rows: 1 + :widths: 40 60 + + * - Method + - Description + + * - `current() <{+core-api+}/com/mongodb/client/model/mql/MqlValues.html#current()>`__ + - References the current document being processed by the aggregation pipeline. + + * - `currentAsMap() <{+core-api+}/com/mongodb/client/model/mql/MqlValues.html#currentAsMap()>`__ + - References the current document being processed by the aggregation pipeline as a map value. + + * - | `of() for MqlBoolean <{+core-api+}/com/mongodb/client/model/mql/MqlValues.html#of(boolean)>`__ + | `of() for MqlNumber (double) <{+core-api+}/com/mongodb/client/model/mql/MqlValues.html#of(double)>`__ + | `of() for MqlNumber (Decimal128) <{+core-api+}/com/mongodb/client/model/mql/MqlValues.html#of(org.bson.types.Decimal128)>`__ + | `of() for MqlInteger (int) <{+core-api+}/com/mongodb/client/model/mql/MqlValues.html#of(int)>`__ + | `of() for MqlInteger (long) <{+core-api+}/com/mongodb/client/model/mql/MqlValues.html#of(long)>`__ + | `of() for MqlString <{+core-api+}/com/mongodb/client/model/mql/MqlValues.html#of(java.lang.String)>`__ + | `of() for MqlDate <{+core-api+}/com/mongodb/client/model/mql/MqlValues.html#of(java.time.Instant)>`__ + | `of() for MqlDocument <{+core-api+}/com/mongodb/client/model/mql/MqlValues.html#of(org.bson.conversions.Bson)>`__ + + - Returns an ``MqlValue`` type corresponding to the provided primitive. + + * - `ofArray() <{+core-api+}/com/mongodb/client/model/mql/MqlValues.html#ofArray(T...)>`__ + + | **Typed Variants**: + | `ofBooleanArray() <{+core-api+}/com/mongodb/client/model/mql/MqlValues.html#ofBooleanArray(boolean...)>`__ + | `ofDateArray() <{+core-api+}/com/mongodb/client/model/mql/MqlValues.html#ofDateArray(java.time.Instant...)>`__ + | `ofIntegerArray() <{+core-api+}/com/mongodb/client/model/mql/MqlValues.html#ofIntegerArray(int...)>`__ + | `ofNumberArray() <{+core-api+}/com/mongodb/client/model/mql/MqlValues.html#ofNumberArray(double...)>`__ + | `ofStringArray() <{+core-api+}/com/mongodb/client/model/mql/MqlValues.html#ofStringArray(java.lang.String...)>`__ + + - Returns an array of ``MqlValue`` types corresponding to the provided array of primitives. + + * - `ofEntry() <{+core-api+}/com/mongodb/client/model/mql/MqlValues.html#ofEntry(com.mongodb.client.model.mql.MqlString,T)>`__ + - Returns an entry value. + + * - `ofMap() <{+core-api+}/com/mongodb/client/model/mql/MqlValues.html#ofMap()>`__ + - Returns an empty map value. + + * - `ofNull() <{+core-api+}/com/mongodb/client/model/mql/MqlValues.html#ofNull()>`__ + - Returns the null value as exists in the Query API. + +.. important:: + + When you provide a value to one of these methods, the driver treats + it literally. For example, ``of("$x")`` represents the string value + ``"$x"``, rather than a field named ``x``. + +Refer to any of the sections under :ref:`Operations +` for examples using these +methods. + +.. _kotlin-sync-aggregation-expression-ops-section: + +Operations +---------- + +The following sections provide information and examples for +aggregation expression operations available in the driver. +The operations are categorized by purpose and functionality. + +Each section has a table that describes aggregation methods +available in the driver and corresponding expression operators in the +Query API. The method names link to API documentation and the +aggregation pipeline operator names link to descriptions and examples in +the Server manual documentation. While each method is effectively +equivalent to the corresponding Query API expression, they may differ in +expected parameters and implementation. + +.. note:: + + The driver generates a Query API expression that may be different + from the Query API expression provided in each example. However, + both expressions will produce the same aggregation result. + +.. important:: + + The driver does not provide methods for all aggregation pipeline operators in + the Query API. If you need to use an unsupported operation in an + aggregation, you must define the entire expression using the BSON ``Document`` + type. + +.. TODO add to note To learn more about the ``Document`` type, see :ref:``. + +Arithmetic Operations +~~~~~~~~~~~~~~~~~~~~~ + +You can perform an arithmetic operation on a value of type ``MqlInteger`` or +``MqlNumber`` using the methods described in this section. + +.. list-table:: + :header-rows: 1 + :widths: 50 50 + + * - Method + - Aggregation Pipeline Operator + + * - | `abs() for MqlInteger <{+core-api+}/com/mongodb/client/model/mql/MqlInteger.html#abs()>`__ + | `abs() for MqlNumber <{+core-api+}/com/mongodb/client/model/mql/MqlNumber.html#abs()>`__ + + - :manual:`$abs ` + + * - | `add() for MqlInteger <{+core-api+}/com/mongodb/client/model/mql/MqlInteger.html#add(int)>`__ + | `add() for MqlNumber <{+core-api+}/com/mongodb/client/model/mql/MqlNumber.html#add(com.mongodb.client.model.mql.MqlNumber)>`__ + + - :manual:`$add ` + + * - `divide() <{+core-api+}/com/mongodb/client/model/mql/MqlNumber.html#divide(com.mongodb.client.model.mql.MqlNumber)>`__ + - :manual:`$divide ` + + * - | `multiply() for MqlInteger <{+core-api+}/com/mongodb/client/model/mql/MqlInteger.html#multiply(int)>`__ + | `multiply() for MqlNumber <{+core-api+}/com/mongodb/client/model/mql/MqlNumber.html#multiply(com.mongodb.client.model.mql.MqlNumber)>`__ + + - :manual:`$multiply ` + + * - `round() <{+core-api+}/com/mongodb/client/model/mql/MqlNumber.html#round()>`__ + - :manual:`$round ` + + * - | `subtract() for MqlInteger <{+core-api+}/com/mongodb/client/model/mql/MqlInteger.html#subtract(int)>`__ + | `subtract() for MqlNumber <{+core-api+}/com/mongodb/client/model/mql/MqlNumber.html#subtract(com.mongodb.client.model.mql.MqlNumber)>`__ + + - :manual:`$subtract ` + +Suppose you have weather data for a specific year that includes the +precipitation measurement (in inches) for each day. You want find the average +precipitation, in millimeters, for each month. + +The ``multiply()`` operator multiplies the ``precipitation`` field by +``25.4`` to convert the value to millimeters. The ``avg()`` accumulator method +returns the average as the ``avgPrecipMM`` field. The ``group()`` method +groups the values by month given in each document's ``date`` field. + +The following code shows the pipeline for this aggregation: + +.. literalinclude:: /includes/aggregation/aggExpressions.kt + :start-after: start-arithmetic-aggregation + :end-before: end-arithmetic-aggregation + :language: kotlin + :copyable: + :dedent: + +The following code provides an equivalent aggregation pipeline in the +Query API: + +.. code-block:: javascript + :copyable: true + + [ { $group: { + _id: { $month: "$date" }, + avgPrecipMM: { + $avg: { $multiply: ["$precipitation", 25.4] } } + } } ] + +Array Operations +~~~~~~~~~~~~~~~~ + +You can perform an array operation on a value of type ``MqlArray`` +using the methods described in this section. + +.. list-table:: + :header-rows: 1 + :widths: 50 50 + + * - Method + - Aggregation Pipeline Operator + + * - `all() <{+core-api+}/com/mongodb/client/model/mql/MqlArray.html#all(java.util.function.Function)>`__ + - :manual:`$allElementsTrue ` + + * - `any() <{+core-api+}/com/mongodb/client/model/mql/MqlArray.html#any(java.util.function.Function)>`__ + - :manual:`$anyElementTrue ` + + * - `concat() <{+core-api+}/com/mongodb/client/model/mql/MqlArray.html#concat(com.mongodb.client.model.mql.MqlArray)>`__ + - :manual:`$concatArrays ` + + * - `concatArrays() <{+core-api+}/com/mongodb/client/model/mql/MqlArray.html#concatArrays(java.util.function.Function)>`__ + - :manual:`$concatArrays ` + + * - `contains() <{+core-api+}/com/mongodb/client/model/mql/MqlArray.html#contains(T)>`__ + - :manual:`$in ` + + * - `distinct() <{+core-api+}/com/mongodb/client/model/mql/MqlArray.html#distinct()>`__ + - :manual:`$setUnion ` + + * - `elementAt() <{+core-api+}/com/mongodb/client/model/mql/MqlArray.html#elementAt(int)>`__ + - :manual:`$arrayElemAt ` + + * - `filter() <{+core-api+}/com/mongodb/client/model/mql/MqlArray.html#filter(java.util.function.Function)>`__ + - :manual:`$filter ` + + * - `first() <{+core-api+}/com/mongodb/client/model/mql/MqlArray.html#first()>`__ + - :manual:`$first ` + + * - `joinStrings() <{+core-api+}/com/mongodb/client/model/mql/MqlArray.html#joinStrings(java.util.function.Function)>`__ + - :manual:`$concat ` + + * - `last() <{+core-api+}/com/mongodb/client/model/mql/MqlArray.html#last()>`__ + - :manual:`$last ` + + * - `map() <{+core-api+}/com/mongodb/client/model/mql/MqlArray.html#map(java.util.function.Function)>`__ + - :manual:`$map ` + + * - `max() <{+core-api+}/com/mongodb/client/model/mql/MqlArray.html#max(T)>`__ + - :manual:`$max ` + + * - `maxN() <{+core-api+}/com/mongodb/client/model/mql/MqlArray.html#maxN(com.mongodb.client.model.mql.MqlInteger)>`__ + - :manual:`$maxN ` + + * - `min() <{+core-api+}/com/mongodb/client/model/mql/MqlArray.html#min(T)>`__ + - :manual:`$min ` + + * - `minN() <{+core-api+}/com/mongodb/client/model/mql/MqlArray.html#minN(com.mongodb.client.model.mql.MqlInteger)>`__ + - :manual:`$minN ` + + * - `multiply() <{+core-api+}/com/mongodb/client/model/mql/MqlArray.html#multiply(java.util.function.Function)>`__ + - :manual:`$multiply ` + + * - `size() <{+core-api+}/com/mongodb/client/model/mql/MqlArray.html#size()>`__ + - :manual:`$size ` + + * - `slice() <{+core-api+}/com/mongodb/client/model/mql/MqlArray.html#slice(int,int)>`__ + - :manual:`$slice ` + + * - `sum() <{+core-api+}/com/mongodb/client/model/mql/MqlArray.html#sum(java.util.function.Function)>`__ + - :manual:`$sum ` + + * - `union() <{+core-api+}/com/mongodb/client/model/mql/MqlArray.html#union(com.mongodb.client.model.mql.MqlArray)>`__ + - :manual:`$setUnion ` + + * - `unionArrays() <{+core-api+}/com/mongodb/client/model/mql/MqlArray.html#unionArrays(java.util.function.Function)>`__ + - :manual:`$setUnion ` + +Suppose you have a collection of movies, each of which contains an array +of nested documents for upcoming showtimes. Each nested document +contains an array that represents the total number of seats in the +theater, where the first array entry is the number of premium seats and +the second entry is the number of regular seats. Each nested document +also contains the number of tickets that have already been bought for +the showtime. A document in this collection might resemble the +following: + +.. code-block:: json + :copyable: false + + { + "_id": ..., + "movie": "Hamlet", + "showtimes": [ + { + "date": "May 14, 2023, 12:00 PM", + "seats": [ 20, 80 ], + "ticketsBought": 100 + }, + { + "date": "May 20, 2023, 08:00 PM", + "seats": [ 10, 40 ], + "ticketsBought": 34 + }] + } + +The ``filter()`` method displays only the results matching the provided +predicate. In this case, the predicate uses ``sum()`` to calculate the +total number of seats and compares that value to the number of ``ticketsBought`` +with ``lt()``. The ``project()`` method stores these filtered results as a new +``availableShowtimes`` array. + +.. tip:: + + You must specify the type of the array that you retrieve with the + ``getArray()`` method if you need to work with the values of the + array as their specific type. + + In this example, we specify that the ``seats`` array contains values + of type ``MqlDocument`` so that we can extract nested fields from + each array entry. + +The following code shows the pipeline for this aggregation: + +.. literalinclude:: /includes/aggregation/aggExpressions.kt + :start-after: start-array-aggregation + :end-before: end-array-aggregation + :language: kotlin + :copyable: + :dedent: + +.. note:: + + To improve readability, the previous example assigns intermediary values to + the ``totalSeats`` and ``isAvailable`` variables. If you don't pull + out these intermediary values into variables, the code still produces + equivalent results. + +The following code provides an equivalent aggregation pipeline in +the Query API: + +.. code-block:: javascript + :copyable: true + + [ { $project: { + availableShowtimes: { + $filter: { + input: "$showtimes", + as: "showtime", + cond: { $lt: [ "$$showtime.ticketsBought", { $sum: "$$showtime.seats" } ] } + } } + } } ] + +Boolean Operations +~~~~~~~~~~~~~~~~~~ + +You can perform a boolean operation on a value of type ``MqlBoolean`` +using the methods described in this section. + +.. list-table:: + :header-rows: 1 + :widths: 50 50 + + * - Method + - Aggregation Pipeline Operator + + * - `and() <{+core-api+}/com/mongodb/client/model/mql/MqlBoolean.html#and(com.mongodb.client.model.mql.MqlBoolean)>`__ + - :manual:`$and ` + + * - `not() <{+core-api+}/com/mongodb/client/model/mql/MqlBoolean.html#not()>`__ + - :manual:`$not ` + + * - `or() <{+core-api+}/com/mongodb/client/model/mql/MqlBoolean.html#or(com.mongodb.client.model.mql.MqlBoolean)>`__ + - :manual:`$or ` + +Suppose you want to classify very low or high weather temperature +readings (in degrees Fahrenheit) as extreme. + +The ``or()`` operator checks to see if temperatures are extreme by comparing +the ``temperature`` field to predefined values with ``lt()`` and ``gt()``. +The ``project()`` method records this result in the ``extremeTemp`` field. + +The following code shows the pipeline for this aggregation: + +.. literalinclude:: /includes/aggregation/aggExpressions.kt + :start-after: start-boolean-aggregation + :end-before: end-boolean-aggregation + :language: kotlin + :copyable: + :dedent: + +The following code provides an equivalent aggregation pipeline in +the Query API: + +.. code-block:: javascript + :copyable: true + + [ { $project: { + extremeTemp: { $or: [ { $lt: ["$temperature", 10] }, + { $gt: ["$temperature", 95] } ] } + } } ] + +Comparison Operations +~~~~~~~~~~~~~~~~~~~~~ + +You can perform a comparison operation on a value of type ``MqlValue`` +using the methods described in this section. + +.. tip:: + + The ``cond()`` method is similar to the ternary operator in Java and you + should use it for simple branches based on a boolean value. You should use + the ``switchOn()`` methods for more complex comparisons such as performing + pattern matching on the value type or other arbitrary checks on the value. + +.. list-table:: + :header-rows: 1 + :widths: 50 50 + + * - Method + - Aggregation Pipeline Operator + + * - `eq() <{+core-api+}/com/mongodb/client/model/mql/MqlValue.html#eq(com.mongodb.client.model.mql.MqlValue)>`__ + - :manual:`$eq ` + + * - `gt() <{+core-api+}/com/mongodb/client/model/mql/MqlValue.html#gt(com.mongodb.client.model.mql.MqlValue)>`__ + - :manual:`$gt ` + + * - `gte() <{+core-api+}/com/mongodb/client/model/mql/MqlValue.html#gte(com.mongodb.client.model.mql.MqlValue)>`__ + - :manual:`$gte ` + + * - `lt() <{+core-api+}/com/mongodb/client/model/mql/MqlValue.html#lt(com.mongodb.client.model.mql.MqlValue)>`__ + - :manual:`$lt ` + + * - `lte() <{+core-api+}/com/mongodb/client/model/mql/MqlValue.html#lte(com.mongodb.client.model.mql.MqlValue)>`__ + - :manual:`$lte ` + + * - | `max() for MqlInteger <{+core-api+}/com/mongodb/client/model/mql/MqlInteger.html#max(com.mongodb.client.model.mql.MqlInteger)>`__ + | `max() for MqlNumber <{+core-api+}/com/mongodb/client/model/mql/MqlNumber.html#max(com.mongodb.client.model.mql.MqlNumber)>`__ + + - :manual:`$max ` + + * - | `min() for MqlInteger <{+core-api+}/com/mongodb/client/model/mql/MqlInteger.html#min(com.mongodb.client.model.mql.MqlInteger)>`__ + | `min() for MqlNumber <{+core-api+}/com/mongodb/client/model/mql/MqlNumber.html#min(com.mongodb.client.model.mql.MqlNumber)>`__ + + - :manual:`$min ` + + * - `ne() <{+core-api+}/com/mongodb/client/model/mql/MqlValue.html#ne(com.mongodb.client.model.mql.MqlValue)>`__ + - :manual:`$ne ` + +The following example shows a pipeline that matches all the documents +where the ``location`` field has the value ``"California"``: + +.. literalinclude:: /includes/aggregation/aggExpressions.kt + :start-after: start-comparison-aggregation + :end-before: end-comparison-aggregation + :language: kotlin + :copyable: + :dedent: + +The following code provides an equivalent aggregation pipeline in +the Query API: + +.. code-block:: javascript + :copyable: true + + [ { $match: { location: { $eq: "California" } } } ] + +Conditional Operations +~~~~~~~~~~~~~~~~~~~~~~ + +You can perform a conditional operation using the methods described in +this section. + +.. list-table:: + :header-rows: 1 + :widths: 50 50 + + * - Method + - Aggregation Pipeline Operator + + * - `cond() <{+core-api+}/com/mongodb/client/model/mql/MqlBoolean.html#cond(T,T)>`__ + - :manual:`$cond ` + + * - `switchOn() <{+core-api+}/com/mongodb/client/model/mql/MqlValue.html#switchOn(java.util.function.Function)>`__ + + | **Typed Variants**: + | `switchArrayOn() <{+core-api+}/com/mongodb/client/model/mql/MqlArray.html#switchArrayOn(java.util.function.Function)>`__ + | `switchBooleanOn() <{+core-api+}/com/mongodb/client/model/mql/MqlBoolean.html#switchBooleanOn(java.util.function.Function)>`__ + | `switchDateOn() <{+core-api+}/com/mongodb/client/model/mql/MqlDate.html#switchDateOn(java.util.function.Function)>`__ + | `switchDocumentOn() <{+core-api+}/com/mongodb/client/model/mql/MqlDocument.html#switchDocumentOn(java.util.function.Function)>`__ + | `switchIntegerOn() <{+core-api+}/com/mongodb/client/model/mql/MqlInteger.html#switchIntegerOn(java.util.function.Function)>`__ + | `switchMapOn() <{+core-api+}/com/mongodb/client/model/mql/MqlMap.html#switchMapOn(java.util.function.Function)>`__ + | `switchNumberOn() <{+core-api+}/com/mongodb/client/model/mql/MqlNumber.html#switchNumberOn(java.util.function.Function)>`__ + | `switchStringOn() <{+core-api+}/com/mongodb/client/model/mql/MqlString.html#switchStringOn(java.util.function.Function)>`__ + + - :manual:`$switch ` + +Suppose you have a collection of customers with their membership information. +Originally, customers were either members or not. Over time, membership levels +were introduced and used the same field. The information stored in this field +can be one of a few different types, and you want to create a standardized value +indicating their membership level. + +The ``switchOn()`` method checks each clause in order. If the value matches the +type indicated by the clause, that clause determines the string value +corresponding to the membership level. If the original value is a string, it +represents the membership level and that value is used. If the data type is a +boolean, it returns either ``Gold`` or ``Guest`` for the membership level. If +the data type is an array, it returns the most recent string in the array which +matches the most recent membership level. If the ``member`` field is an +unknown type, the ``switchOn()`` method provides a default value of ``Guest``. + +The following code shows the pipeline for this aggregation: + +.. literalinclude:: /includes/aggregation/aggExpressions.kt + :start-after: start-conditional-aggregation + :end-before: end-conditional-aggregation + :language: kotlin + :copyable: + :dedent: + +The following code provides an equivalent aggregation pipeline in +the Query API: + +.. code-block:: javascript + :copyable: true + + [ { $project: { + membershipLevel: { + $switch: { + branches: [ + { case: { $eq: [ { $type: "$member" }, "string" ] }, then: "$member" }, + { case: { $eq: [ { $type: "$member" }, "bool" ] }, then: { $cond: { + if: "$member", + then: "Gold", + else: "Guest" } } }, + { case: { $eq: [ { $type: "$member" }, "array" ] }, then: { $last: "$member" } } + ], + default: "Guest" } } + } } ] + +Convenience Operations +~~~~~~~~~~~~~~~~~~~~~~ + +You can apply custom functions to values of type +``MqlValue`` using the methods described in this section. + +To improve readability and allow for code reuse, you can move redundant +code into static methods. However, it is not possible to directly chain +static methods in {+language+}. The ``passTo()`` method lets you chain values +into custom static methods. + +.. list-table:: + :header-rows: 1 + :widths: 50 50 + + * - Method + - Aggregation Pipeline Operator + + * - `passTo() <{+core-api+}/com/mongodb/client/model/mql/MqlValue.html#passTo(java.util.function.Function)>`__ + + | **Typed Variants**: + | `passArrayTo() <{+core-api+}/com/mongodb/client/model/mql/MqlArray.html#passArrayTo(java.util.function.Function)>`__ + | `passBooleanTo() <{+core-api+}/com/mongodb/client/model/mql/MqlBoolean.html#passBooleanTo(java.util.function.Function)>`__ + | `passDateTo() <{+core-api+}/com/mongodb/client/model/mql/MqlDate.html#passDateTo(java.util.function.Function)>`__ + | `passDocumentTo() <{+core-api+}/com/mongodb/client/model/mql/MqlDocument.html#passDocumentTo(java.util.function.Function)>`__ + | `passIntegerTo() <{+core-api+}/com/mongodb/client/model/mql/MqlInteger.html#passIntegerTo(java.util.function.Function)>`__ + | `passMapTo() <{+core-api+}/com/mongodb/client/model/mql/MqlMap.html#passMapTo(java.util.function.Function)>`__ + | `passNumberTo() <{+core-api+}/com/mongodb/client/model/mql/MqlNumber.html#passNumberTo(java.util.function.Function)>`__ + | `passStringTo() <{+core-api+}/com/mongodb/client/model/mql/MqlString.html#passStringTo(java.util.function.Function)>`__ + + - *No corresponding operator* + +Suppose you need to determine how a class is performing against some +benchmarks. You want to find the average final grade for each class and +compare it against the benchmark values. + +The following custom method ``gradeAverage()`` takes an array of documents and +the name of an integer field shared across those documents. It calculates the +average of that field across all the documents in the provided array and +determines the average of that field across all the elements in +the provided array. The ``evaluate()`` method compares a provided value to +two provided range limits and generates a response string based on +how the values compare: + +.. literalinclude:: /includes/aggregation/aggExpressions.kt + :start-after: start-convenience-aggregation-methods + :end-before: end-convenience-aggregation-methods + :language: kotlin + :copyable: + :dedent: + +.. tip:: + + One advantage of using the ``passTo()`` method is that you can reuse + your custom methods for other aggregations. You could + use the ``gradeAverage()`` method to find the average of grades for + groups of students filtered by, for example, entry year or district, not just their + class. You could use the ``evaluate()`` method to evaluate, for + example, an individual student's performance, or an entire school's or + district's performance. + +The ``passArrayTo()`` method takes all of the students and calculates the +average score by using the ``gradeAverage()`` method. Then, the +``passNumberTo()`` method uses the ``evaluate()`` method to determine how the +classes are performing. This example stores the result as the ``evaluation`` +field using the ``project()`` method. + +The following code shows the pipeline for this aggregation: + +.. literalinclude:: /includes/aggregation/aggExpressions.kt + :start-after: start-convenience-aggregation + :end-before: end-convenience-aggregation + :language: kotlin + :copyable: + :dedent: + +The following code provides an equivalent aggregation pipeline in +the Query API: + +.. code-block:: javascript + :copyable: true + + [ { $project: { + evaluation: { $switch: { + branches: [ + { case: { $lte: [ { $avg: "$students.finalGrade" }, 70 ] }, + then: "Needs improvement" + }, + { case: { $lte: [ { $avg: "$students.finalGrade" }, 85 ] }, + then: "Meets expectations" + } + ], + default: "Exceeds expectations" } } + } } ] + +Conversion Operations +~~~~~~~~~~~~~~~~~~~~~ + +You can perform a conversion operation to convert between certain ``MqlValue`` +types using the methods described in this section. + +.. list-table:: + :header-rows: 1 + :widths: 50 50 + + * - Method + - Aggregation Pipeline Operator + + * - `asDocument() <{+core-api+}/com/mongodb/client/model/mql/MqlMap.html#asDocument()>`__ + - *No corresponding operator* + + * - `asMap() <{+core-api+}/com/mongodb/client/model/mql/MqlDocument.html#asMap()>`__ + - *No corresponding operator* + + * - `asString() for MqlDate <{+core-api+}/com/mongodb/client/model/mql/MqlDate.html#asString(com.mongodb.client.model.mql.MqlString,com.mongodb.client.model.mql.MqlString)>`__ + - :manual:`$dateToString ` + + * - `asString() for MqlValue <{+core-api+}/com/mongodb/client/model/mql/MqlValue.html#asString()>`__ + - :manual:`$toString ` + + * - `millisecondsAsDate() <{+core-api+}/com/mongodb/client/model/mql/MqlInteger.html#millisecondsAsDate()>`__ + - :manual:`$toDate ` + + * - `parseDate() <{+core-api+}/com/mongodb/client/model/mql/MqlString.html#parseDate()>`__ + - :manual:`$dateFromString ` + + * - `parseInteger() <{+core-api+}/com/mongodb/client/model/mql/MqlString.html#parseInteger()>`__ + - :manual:`$toInt ` + +Suppose you want to have a collection of student data that includes +their graduation years, which are stored as strings. You want to +calculate the year of their five-year reunion and store this value in a +new field. + +The ``parseInteger()`` method converts the ``graduationYear`` to an integer +so that ``add()`` can calculate the reunion year. The ``addFields()`` method +stores this result as a new ``reunionYear`` field. + +The following code shows the pipeline for this aggregation: + +.. literalinclude:: /includes/aggregation/aggExpressions.kt + :start-after: start-convenience-aggregation + :end-before: end-convenience-aggregation + :language: kotlin + :copyable: + :dedent: + +The following code provides an equivalent aggregation pipeline in +the Query API: + +.. code-block:: javascript + :copyable: true + + [ { $addFields: { + reunionYear: { + $add: [ { $toInt: "$graduationYear" }, 5 ] } + } } ] + +Date Operations +~~~~~~~~~~~~~~~ + +You can perform a date operation on a value of type ``MqlDate`` +using the methods described in this section. + +.. list-table:: + :header-rows: 1 + :widths: 50 50 + + * - Method + - Aggregation Pipeline Operator + + * - `dayOfMonth() <{+core-api+}/com/mongodb/client/model/mql/MqlDate.html#dayOfMonth(com.mongodb.client.model.mql.MqlString)>`__ + - :manual:`$dayOfMonth ` + + * - `dayOfWeek() <{+core-api+}/com/mongodb/client/model/mql/MqlDate.html#dayOfWeek(com.mongodb.client.model.mql.MqlString)>`__ + - :manual:`$dayOfWeek ` + + * - `dayOfYear() <{+core-api+}/com/mongodb/client/model/mql/MqlDate.html#dayOfYear(com.mongodb.client.model.mql.MqlString)>`__ + - :manual:`$dayOfYear ` + + * - `hour() <{+core-api+}/com/mongodb/client/model/mql/MqlDate.html#hour(com.mongodb.client.model.mql.MqlString)>`__ + - :manual:`$hour ` + + * - `millisecond() <{+core-api+}/com/mongodb/client/model/mql/MqlDate.html#millisecond(com.mongodb.client.model.mql.MqlString)>`__ + - :manual:`$millisecond ` + + * - `minute() <{+core-api+}/com/mongodb/client/model/mql/MqlDate.html#minute(com.mongodb.client.model.mql.MqlString)>`__ + - :manual:`$minute ` + + * - `month() <{+core-api+}/com/mongodb/client/model/mql/MqlDate.html#month(com.mongodb.client.model.mql.MqlString)>`__ + - :manual:`$month ` + + * - `second() <{+core-api+}/com/mongodb/client/model/mql/MqlDate.html#second(com.mongodb.client.model.mql.MqlString)>`__ + - :manual:`$second ` + + * - `week() <{+core-api+}/com/mongodb/client/model/mql/MqlDate.html#week(com.mongodb.client.model.mql.MqlString)>`__ + - :manual:`$week ` + + * - `year() <{+core-api+}/com/mongodb/client/model/mql/MqlDate.html#year(com.mongodb.client.model.mql.MqlString)>`__ + - :manual:`$year ` + +Suppose you have data about package deliveries and need to match +deliveries that occurred on any Monday in the ``"America/New_York"`` time +zone. + +If the ``deliveryDate`` field contains any string values representing +valid dates, such as ``"2018-01-15T16:00:00Z"`` or ``"Jan 15, 2018, 12:00 +PM EST"``, you can use the ``parseDate()`` method to convert the strings +into date types. + +The ``dayOfWeek()`` method determines which day of the week it is and converts +it to a number based on which day is a Monday according to the +``"America/New_York"`` parameter. The ``eq()`` method compares this value to +``2``, which corresponds to Monday based on the provided timezone parameter. + +The following code shows the pipeline for this aggregation: + +.. literalinclude:: /includes/aggregation/aggExpressions.kt + :start-after: start-date-aggregation + :end-before: end-date-aggregation + :language: kotlin + :copyable: + :dedent: + +The following code provides an equivalent aggregation pipeline in +the Query API: + +.. code-block:: javascript + :copyable: true + + [ { $match: { + $expr: { + $eq: [ { + $dayOfWeek: { + date: { $dateFromString: { dateString: "$deliveryDate" } }, + timezone: "America/New_York" }}, + 2 + ] } + } } ] + +Document Operations +~~~~~~~~~~~~~~~~~~~ + +You can perform a document operation on a value of type ``MqlDocument`` +using the methods described in this section. + +.. list-table:: + :header-rows: 1 + :widths: 50 50 + + * - Method + - Aggregation Pipeline Operator + + * - | `getArray() <{+core-api+}/com/mongodb/client/model/mql/MqlDocument.html#getArray(java.lang.String)>`__ + | `getBoolean() <{+core-api+}/com/mongodb/client/model/mql/MqlDocument.html#getBoolean(java.lang.String)>`__ + | `getDate() <{+core-api+}/com/mongodb/client/model/mql/MqlDocument.html#getDate(java.lang.String)>`__ + | `getDocument() <{+core-api+}/com/mongodb/client/model/mql/MqlDocument.html#getDocument(java.lang.String)>`__ + | `getField() <{+core-api+}/com/mongodb/client/model/mql/MqlDocument.html#getField(java.lang.String)>`__ + | `getInteger() <{+core-api+}/com/mongodb/client/model/mql/MqlDocument.html#getInteger(java.lang.String)>`__ + | `getMap() <{+core-api+}/com/mongodb/client/model/mql/MqlDocument.html#getMap(java.lang.String)>`__ + | `getNumber() <{+core-api+}/com/mongodb/client/model/mql/MqlDocument.html#getNumber(java.lang.String)>`__ + | `getString() <{+core-api+}/com/mongodb/client/model/mql/MqlDocument.html#getString(java.lang.String)>`__ + - :manual:`$getField ` + + * - `hasField() <{+core-api+}/com/mongodb/client/model/mql/MqlDocument.html#hasField(java.lang.String)>`__ + - *No corresponding operator* + + * - `merge() <{+core-api+}/com/mongodb/client/model/mql/MqlDocument.html#merge(com.mongodb.client.model.mql.MqlDocument)>`__ + - :manual:`$mergeObjects ` + + * - `setField() <{+core-api+}/com/mongodb/client/model/mql/MqlDocument.html#setField(java.lang.String,com.mongodb.client.model.mql.MqlValue)>`__ + - :manual:`$setField ` + + * - `unsetField() <{+core-api+}/com/mongodb/client/model/mql/MqlDocument.html#unsetField(java.lang.String)>`__ + - :manual:`$unsetField ` + +Suppose you have a collection of legacy customer data which includes +addresses as child documents under the ``mailing.address`` field. You want +to find all the customers who currently live in Washington state. A +document in this collection might resemble the following: + +.. code-block:: json + :copyable: false + + { + "_id": ..., + "customer.name": "Mary Kenneth Keller", + "mailing.address": + { + "street": "601 Mongo Drive", + "city": "Vasqueztown", + "state": "CO", + "zip": 27017 + } + } + +The ``getDocument()`` method retrieves the ``mailing.address`` field as a +document so the nested ``state`` field can be retrieved with the +``getString()`` method. The ``eq()`` method checks if the value of the +``state`` field is ``"WA"``. + +The following code shows the pipeline for this aggregation: + +.. literalinclude:: /includes/aggregation/aggExpressions.kt + :start-after: start-document-aggregation + :end-before: end-document-aggregation + :language: kotlin + :copyable: + :dedent: + +The following code provides an equivalent aggregation pipeline in +the Query API: + +.. code-block:: javascript + :copyable: true + + [ + { $match: { + $expr: { + $eq: [{ + $getField: { + input: { $getField: { input: "$$CURRENT", field: "mailing.address"}}, + field: "state" }}, + "WA" ] + }}}] + +Map Operations +~~~~~~~~~~~~~~ + +You can perform a map operation on a value of either type ``MqlMap`` or +``MqlEntry`` using the methods described in this section. + +.. tip:: + + You should represent data as a map if the data maps + arbitrary keys such as dates or item IDs to values. + +.. list-table:: + :header-rows: 1 + :widths: 50 50 + + * - Method + - Aggregation Pipeline Operator + + * - `entries() <{+core-api+}/com/mongodb/client/model/mql/MqlMap.html#entries()>`__ + - :manual:`$objectToArray ` + + * - `get() <{+core-api+}/com/mongodb/client/model/mql/MqlMap.html#get(com.mongodb.client.model.mql.MqlString)>`__ + - *No corresponding operator* + + * - `getKey() <{+core-api+}/com/mongodb/client/model/mql/MqlEntry.html#getKey()>`__ + - *No corresponding operator* + + * - `getValue() <{+core-api+}/com/mongodb/client/model/mql/MqlEntry.html#getValue()>`__ + - *No corresponding operator* + + * - `has() <{+core-api+}/com/mongodb/client/model/mql/MqlMap.html#has(com.mongodb.client.model.mql.MqlString)>`__ + - *No corresponding operator* + + * - `merge() <{+core-api+}/com/mongodb/client/model/mql/MqlMap.html#merge(com.mongodb.client.model.mql.MqlMap)>`__ + - *No corresponding operator* + + * - `set() <{+core-api+}/com/mongodb/client/model/mql/MqlMap.html#set(com.mongodb.client.model.mql.MqlString,T)>`__ + - *No corresponding operator* + + * - `setKey() <{+core-api+}/com/mongodb/client/model/mql/MqlEntry.html#setKey(com.mongodb.client.model.mql.MqlString)>`__ + - *No corresponding operator* + + * - `setValue() <{+core-api+}/com/mongodb/client/model/mql/MqlEntry.html#setValue(T)>`__ + - *No corresponding operator* + + * - `unset() <{+core-api+}/com/mongodb/client/model/mql/MqlMap.html#unset(com.mongodb.client.model.mql.MqlString)>`__ + - *No corresponding operator* + +Suppose you have a collection of inventory data where each document represents +an individual item you're responsible for supplying. Each document contains a +field that is a map of all your warehouses and how many copies they currently +have in their inventory of the item. You want to determine the total number of +copies of items you have across all of your warehouses. A document in this +collection might resemble the following: + +.. code-block:: json + :copyable: false + + { + "_id": ..., + "item": "notebook" + "warehouses": [ + { "Atlanta", 50 }, + { "Chicago", 0 }, + { "Portland", 120 }, + { "Dallas", 6 } + ] + } + +The ``entries()`` method returns the map entries in the ``warehouses`` +field as an array. The ``sum()`` method calculates the total value of items +based on the values in the array retrieved with the ``getValue()`` method. +This example stores the result as the new ``totalInventory`` field using the +``project()`` method. + +The following code shows the pipeline for this aggregation: + +.. literalinclude:: /includes/aggregation/aggExpressions.kt + :start-after: start-map-aggregation + :end-before: end-map-aggregation + :language: kotlin + :copyable: + :dedent: + +The following code provides an equivalent aggregation pipeline in +the Query API: + +.. code-block:: javascript + :copyable: true + + [ { $project: { + totalInventory: { + $sum: { + $getField: { $objectToArray: "$warehouses" }, + } } + } } ] + +String Operations +~~~~~~~~~~~~~~~~~ + +You can perform a string operation on a value of type ``MqlString`` +using the methods described in this section. + +.. list-table:: + :header-rows: 1 + :widths: 50 50 + + * - Method + - Aggregation Pipeline Operator + + * - `append() <{+core-api+}/com/mongodb/client/model/mql/MqlString.html#append(com.mongodb.client.model.mql.MqlString)>`__ + - :manual:`$concat ` + + * - `length() <{+core-api+}/com/mongodb/client/model/mql/MqlString.html#length()>`__ + - :manual:`$strLenCP ` + + * - `lengthBytes() <{+core-api+}/com/mongodb/client/model/mql/MqlString.html#lengthBytes()>`__ + - :manual:`$strLenBytes ` + + * - `substr() <{+core-api+}/com/mongodb/client/model/mql/MqlString.html#substr(int,int)>`__ + - :manual:`$substrCP ` + + * - `substrBytes() <{+core-api+}/com/mongodb/client/model/mql/MqlString.html#substrBytes(int,int)>`__ + - :manual:`$substrBytes ` + + * - `toLower() <{+core-api+}/com/mongodb/client/model/mql/MqlString.html#toLower()>`__ + - :manual:`$toLower ` + + * - `toUpper() <{+core-api+}/com/mongodb/client/model/mql/MqlString.html#toUpper()>`__ + - :manual:`$toUpper ` + +Suppose you need to generate lowercase usernames for employees of a +company from the employees' last names and employee IDs. + +The ``append()`` method combines the ``lastName`` and ``employeeID`` fields into +a single username, while the ``toLower()`` method makes the entire username +lowercase. This example stores the result as a new ``username`` field using +the ``project()`` method. + +The following code shows the pipeline for this aggregation: + +.. literalinclude:: /includes/aggregation/aggExpressions.kt + :start-after: start-string-aggregation + :end-before: end-string-aggregation + :language: kotlin + :copyable: + :dedent: + +The following code provides an equivalent aggregation pipeline in +the Query API: + +.. code-block:: javascript + :copyable: true + + [ { $project: { + username: { + $toLower: { $concat: ["$lastName", "$employeeID"] } } + } } ] + +Type-Checking Operations +~~~~~~~~~~~~~~~~~~~~~~~~ + +You can perform a type-check operation on a value of type ``MqlValue`` +using the methods described in this section. + +These methods do not return boolean values. Instead, you provide a default value +that matches the type specified by the method. If the checked value +matches the method type, the checked value is returned. Otherwise, the supplied +default value is returned. If you want to program branching logic based on the +data type, see ``switchOn()``. + +.. list-table:: + :header-rows: 1 + :widths: 50 50 + + * - Method + - Aggregation Pipeline Operator + + * - `isArrayOr() <{+core-api+}/com/mongodb/client/model/mql/MqlValue.html#isArrayOr(com.mongodb.client.model.mql.MqlArray)>`__ + - *No corresponding operator* + + * - `isBooleanOr() <{+core-api+}/com/mongodb/client/model/mql/MqlValue.html#isBooleanOr(com.mongodb.client.model.mql.MqlBoolean)>`__ + - *No corresponding operator* + + * - `isDateOr() <{+core-api+}/com/mongodb/client/model/mql/MqlValue.html#isDateOr(com.mongodb.client.model.mql.MqlDate)>`__ + - *No corresponding operator* + + * - `isDocumentOr() <{+core-api+}/com/mongodb/client/model/mql/MqlValue.html#isDocumentOr(T)>`__ + - *No corresponding operator* + + * - `isIntegerOr() <{+core-api+}/com/mongodb/client/model/mql/MqlValue.html#isIntegerOr(com.mongodb.client.model.mql.MqlInteger)>`__ + - *No corresponding operator* + + * - `isMapOr() <{+core-api+}/com/mongodb/client/model/mql/MqlValue.html#isMapOr(com.mongodb.client.model.mql.MqlMap)>`__ + - *No corresponding operator* + + * - `isNumberOr() <{+core-api+}/com/mongodb/client/model/mql/MqlValue.html#isNumberOr(com.mongodb.client.model.mql.MqlNumber)>`__ + - *No corresponding operator* + + * - `isStringOr() <{+core-api+}/com/mongodb/client/model/mql/MqlValue.html#isStringOr(com.mongodb.client.model.mql.MqlString)>`__ + - *No corresponding operator* + +Suppose you have a collection of rating data. An early version of the review +schema allowed users to submit negative reviews without a star rating. You want +convert any of these negative reviews without a star rating to have the minimum +value of 1 star. + +The ``isNumberOr()`` method returns either the value of ``rating``, or +a value of ``1`` if ``rating`` is not a number or is null. The +``project()`` method returns this value as a new ``numericalRating`` field. + +The following code shows the pipeline for this aggregation: + +.. literalinclude:: /includes/aggregation/aggExpressions.kt + :start-after: start-type-aggregation + :end-before: end-type-aggregation + :language: kotlin + :copyable: + :dedent: + +The following code provides an equivalent aggregation pipeline in +the Query API: + +.. code-block:: javascript + :copyable: true + + [ { $project: { + numericalRating: { + $cond: { if: { $isNumber: "$rating" }, + then: "$rating", + else: 1 + } } + } } ] diff --git a/source/includes/aggregation/aggExpressions.kt b/source/includes/aggregation/aggExpressions.kt new file mode 100644 index 00000000..053640e1 --- /dev/null +++ b/source/includes/aggregation/aggExpressions.kt @@ -0,0 +1,529 @@ +import com.mongodb.client.model.* +import com.mongodb.client.model.mql.* +import com.mongodb.client.model.mql.MqlValues.current +import com.mongodb.client.model.mql.MqlValues.of +import com.mongodb.kotlin.client.MongoClient +import org.bson.Document +import org.junit.jupiter.api.AfterAll +import org.junit.jupiter.api.Assertions.* +import org.junit.jupiter.api.Test +import java.time.LocalDate + +class AggExpressionsTest { + companion object { + val uri = "" + val mongoClient = MongoClient.create(uri) + private val database = mongoClient.getDatabase("aggExpressions") + + @AfterAll + @JvmStatic + fun afterAll() { + mongoClient.close() + } + } + + @Test + fun arithmeticOpsTest() { + + data class Entry(val date: LocalDate, val precipitation: Double) + + val collection = database.getCollection("weatherData") + + val entries = listOf( + Entry(LocalDate.of(2021, 6, 24), 5.0), + Entry(LocalDate.of(2021, 6, 25), 1.4) + ) + + collection.insertMany(entries) + + // start-arithmetic-aggregation + val month = current().getDate("date").month(of("UTC")) + val precip = current().getInteger("precipitation") + + val results = collection.aggregate( // :remove: + listOf( + Aggregates.group( + month, + Accumulators.avg("avgPrecipMM", precip.multiply(25.4)) + ) + ) + ) + // end-arithmetic-aggregation + + val result = results.toList() + assertEquals(1, result.size) + assertEquals(6, result[0].getInteger("_id")) + assertEquals(81.28, result[0].getDouble("avgPrecipMM")) + + collection.drop() + } + + @Test + fun arrayOpsTest() { + + data class Showtime( + val date: String, + val seats: List, + val ticketsBought: Int + ) + + data class Movie( + val movie: String, + val showtimes: List, + ) + + val collection = database.getCollection("movies") + + val entries = listOf( + Movie( + "Hamlet", + listOf( + Showtime("May 14, 2023, 12:00 PM", listOf(20, 80), 100), + Showtime("May 20, 2023, 08:00 PM", listOf(10, 40), 34) + ) + ) + ) + + collection.insertMany(entries) + + // start-array-aggregation + val showtimes = current().getArray("showtimes") + + val results = collection.aggregate( // :remove: + listOf( + Aggregates.project( + Projections.fields( + Projections.computed("availableShowtimes", showtimes + .filter { showtime -> + val seats = showtime.getArray("seats") + val totalSeats = seats.sum { n -> n } + val ticketsBought = showtime.getInteger("ticketsBought") + val isAvailable = ticketsBought.lt(totalSeats) + isAvailable + }) + ) + ) + ) + ) + // end-array-aggregation + + val result = results.toList() + assertEquals(1, result.size) + + collection.drop() + } + + @Test + fun booleanOpsTest() { + + data class Entry(val location: String, val temperature: Int) + + val collection = database.getCollection("tempData") + + val entries = listOf( + Entry("Randolph, NJ", 100), + Entry("Seward, AK", 1), + Entry("Lincoln, NE", 45) + ) + + collection.insertMany(entries) + + // start-boolean-aggregation + val temperature = current().getInteger("temperature") + + val results = collection.aggregate( // :remove: + listOf( + Aggregates.project( + Projections.fields( + Projections.computed( + "extremeTemp", temperature + .lt(of(10)) + .or(temperature.gt(of(95))) + ) + ) + ) + ) + ) + // end-boolean-aggregation + + val result = results.toList() + assertEquals(3, result.size) + assertEquals(true, result[0].getBoolean("extremeTemp")) + assertEquals(true, result[1].getBoolean("extremeTemp")) + assertEquals(false, result[2].getBoolean("extremeTemp")) + + collection.drop() + } + + @Test + fun comparisonOpsTest() { + + data class Place(val location: String) + + val collection = database.getCollection("placesData") + + val entries = listOf( + Place("Delaware"), + Place("California") + ) + + collection.insertMany(entries) + + // start-comparison-aggregation + val location = current().getString("location") + + val results = collection.aggregate( // :remove: + listOf( + Aggregates.match( + Filters.expr(location.eq(of("California"))) + ) + ) + ) + // end-comparison-aggregation + + val result = results.toList() + assertEquals(1, result.size) + assertEquals("California", result[0].getString("location")) + + collection.drop() + } + + @Test + fun conditionalOpsTest() { + + val collection = database.getCollection("memberData") + + val entries = listOf( + Document("name", "Sandra K").append("member", "Gold"), + Document("name", "Darren I").append("member", true), + Document("name", "Corey P").append("member", false), + Document("name", "Francine D").append("member", 7), + Document("name", "Lily A").append("member", listOf("None", "Gold", "Premium")) + ) + + collection.insertMany(entries) + + // start-conditional-aggregation + val member = current().getField("member") + + val results = collection.aggregate( // :remove: + listOf( + Aggregates.project( + Projections.fields( + Projections.computed("membershipLevel", + member.switchOn { field -> + field + .isString { s -> s } + .isBoolean { b -> b.cond(of("Gold"), of("Guest")) } + .isArray { a -> a.last() } + .defaults { d -> of("Guest") } + }) + ) + ) + ) + ) + // end-conditional-aggregation + + val result = results.toList() + assertEquals(5, result.size) + assertEquals("Gold", result[0].getString("membershipLevel")) + assertEquals("Gold", result[1].getString("membershipLevel")) + assertEquals("Guest", result[2].getString("membershipLevel")) + assertEquals("Guest", result[3].getString("membershipLevel")) + assertEquals("Premium", result[4].getString("membershipLevel")) + + collection.drop() + } + + @Test + fun convenienceOpsTest() { + + data class Student(val name: String, val finalGrade: Int) + data class Class(val title: String, val students: List) + + val collection = database.getCollection("classData") + + val entries = listOf( + Class("History", listOf(Student("Kaley", 99), Student("Kevin", 80))), + Class("Math", listOf(Student("Dan", 68), Student("Shelley", 45))), + Class("Language Arts", listOf(Student("Kaley", 83), Student("Shelley", 86))) + ) + + collection.insertMany(entries) + + // start-convenience-aggregation-methods + fun gradeAverage(students: MqlArray, fieldName: String): MqlNumber { + val sum = students.sum { student -> student.getInteger(fieldName) } + val avg = sum.divide(students.size()) + return avg + } + + fun evaluate(grade: MqlNumber, cutoff1: MqlNumber, cutoff2: MqlNumber): MqlString { + val message = grade.switchOn { on -> + on + .lte(cutoff1) { g -> of("Needs improvement") } + .lte(cutoff2) { g -> of("Meets expectations") } + .defaults { g -> of("Exceeds expectations") } + } + return message + } + // end-convenience-aggregation-methods + + // start-convenience-aggregation + val students = current().getArray("students") + + val results = collection.aggregate( // :remove: + listOf( + Aggregates.project( + Projections.fields( + Projections.computed("evaluation", students + .passArrayTo { s -> gradeAverage(s, "finalGrade") } + .passNumberTo { grade -> evaluate(grade, of(70), of(85)) }) + ) + ) + ) + ) + // end-convenience-aggregation + + val result = results.toList() + assertEquals(3, result.size) + assertEquals("Exceeds expectations", result[0].getString("evaluation")) + assertEquals("Needs improvement", result[1].getString("evaluation")) + assertEquals("Meets expectations", result[2].getString("evaluation")) + + collection.drop() + } + + @Test + fun conversionOpsTest() { + + data class Alumnus(val name: String, val graduationYear: String) + + val collection = database.getCollection("alumniData") + + val entries = listOf( + Alumnus("Shelley E", "2009"), + Alumnus("Courtney S", "1994") + ) + + collection.insertMany(entries) + + // start-conversion-aggregation + val graduationYear = current().getString("graduationYear") + + val results = collection.aggregate( // :remove: + listOf( + Aggregates.addFields( + Field( + "reunionYear", + graduationYear + .parseInteger() + .add(5) + ) + ) + ) + ) + // end-conversion-aggregation + + val result = results.toList() + assertEquals(2, result.size) + assertEquals(2014, result[0].getInteger("reunionYear")) + assertEquals(1999, result[1].getInteger("reunionYear")) + + collection.drop() + } + + @Test + fun dateOpsTest() { + + data class Delivery(val desc: String, val deliveryDate: String) + + val collection = database.getCollection("deliveryData") + + val entries = listOf( + Delivery("Order #1234", "2018-01-15T16:00:00Z"), + Delivery("Order #4397", "Jan 15, 2018, 12:00 PM EST"), + Delivery("Order #4397", "Jun 8, 2023, 12:00 PM EST") + ) + + collection.insertMany(entries) + + // start-date-aggregation + val deliveryDate = current().getString("deliveryDate") + + val results = collection.aggregate( // :remove: + listOf( + Aggregates.match( + Filters.expr( + deliveryDate + .parseDate() + .dayOfWeek(of("America/New_York")) + .eq(of(2)) + ) + ) + ) + ) + // end-date-aggregation + + val result = results.toList() + assertEquals(2, result.size) + assertEquals("Order #1234", result[0].getString("desc")) + assertEquals("Order #4397", result[1].getString("desc")) + + collection.drop() + } + + @Test + fun documentOpsTest() { + + val collection = database.getCollection("addressData") + + val address1 = Document("street", "601 Mongo Drive").append("city", "Vasqueztown").append("state", "CO") + .append("zip", 27017) + val address2 = + Document("street", "533 Maple Ave").append("city", "Bellevue").append("state", "WA").append("zip", 98004) + + val entries = listOf( + Document("customer.name", "Mary Kenneth Keller").append("mailing.address", address1), + Document("customer.name", "Surathi Raj").append("mailing.address", address2) + ) + + collection.insertMany(entries) + + // start-document-aggregation + val address = current().getDocument("mailing.address") + + val results = collection.aggregate( // :remove: + listOf( + Aggregates.match( + Filters.expr( + address + .getString("state") + .eq(of("WA")) + ) + ) + ) + ) + // end-document-aggregation + + val result = results.toList() + assertEquals(1, result.size) + assertEquals("Surathi Raj", result[0].getString("customer.name")) + + collection.drop() + } + + @Test + fun mapOpsTest() { + + data class Item(val item: String, val warehouses: Map) + + val collection = database.getCollection("stockData") + + val doc = Item("notebook", mapOf("Atlanta" to 50, "Chicago" to 0, "Portland" to 120, "Dallas" to 6)) + + collection.insertOne(doc) + + // start-map-aggregation + val warehouses = current().getMap("warehouses") + + val results = collection.aggregate( // :remove: + listOf( + Aggregates.project( + Projections.fields( + Projections.computed("totalInventory", warehouses + .entries() + .sum { v -> v.getValue() }) + ) + ) + ) + ) + // end-map-aggregation + + val result = results.toList() + assertEquals(1, result.size) + assertEquals(176, result[0].getInteger("totalInventory")) + + collection.drop() + } + + @Test + fun stringOpsTest() { + + data class Employee(val lastName: String, val employeeID: String) + + val collection = database.getCollection("employeeData") + + val entries = listOf( + Employee("Carter", "12w2"), + Employee("Derry", "32rj") + ) + + collection.insertMany(entries) + + // start-string-aggregation + val lastName = current().getString("lastName") + val employeeID = current().getString("employeeID") + + val results = collection.aggregate( // :remove: + listOf( + Aggregates.project( + Projections.fields( + Projections.computed( + "username", lastName + .append(employeeID) + .toLower() + ) + ) + ) + ) + ) + // end-string-aggregation + + val result = results.toList() + assertEquals(2, result.size) + assertEquals("carter12w2", result[0].getString("username")) + assertEquals("derry32rj", result[1].getString("username")) + + collection.drop() + } + + @Test + fun typeOpsTest() { + + val collection = database.getCollection("movieRatingData") + + val entries = listOf( + Document("movie", "Narnia").append("rating", 4), + Document("movie", "Jaws").append("rating", null), + Document("movie", "Phantom Thread").append("rating", "hate!!") + ) + + collection.insertMany(entries) + + // start-type-aggregation + val rating = current().getField("rating") + + val results = collection.aggregate( // :remove: + listOf( + Aggregates.project( + Projections.fields( + Projections.computed( + "numericalRating", rating + .isNumberOr(of(1)) + ) + ) + ) + ) + ) + // end-type-aggregation + + val result = results.toList() + assertEquals(3, result.size) + assertEquals(4, result[0].getInteger("numericalRating")) + assertEquals(1, result[1].getInteger("numericalRating")) + assertEquals(1, result[2].getInteger("numericalRating")) + + collection.drop() + } +} \ No newline at end of file From 6ef8793f53fd74a3a1b2996da52b1e4d68f55fbb Mon Sep 17 00:00:00 2001 From: rustagir Date: Wed, 7 Aug 2024 14:19:30 -0400 Subject: [PATCH 02/11] wip --- source/aggregation/agg-exp-ops.txt | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/source/aggregation/agg-exp-ops.txt b/source/aggregation/agg-exp-ops.txt index 04435062..96013e70 100644 --- a/source/aggregation/agg-exp-ops.txt +++ b/source/aggregation/agg-exp-ops.txt @@ -10,6 +10,13 @@ Aggregation Expression Operations :depth: 2 :class: singlecol +.. facet:: + :name: genre + :values: reference + +.. meta:: + :keywords: transform data, fluent interface + Overview -------- From bf5fbac2c481a673b20a4e274fc906e89b421505 Mon Sep 17 00:00:00 2001 From: rustagir Date: Wed, 7 Aug 2024 14:27:20 -0400 Subject: [PATCH 03/11] vale --- source/aggregation/agg-exp-ops.txt | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/source/aggregation/agg-exp-ops.txt b/source/aggregation/agg-exp-ops.txt index 96013e70..b603a736 100644 --- a/source/aggregation/agg-exp-ops.txt +++ b/source/aggregation/agg-exp-ops.txt @@ -48,9 +48,9 @@ in your code: import com.mongodb.client.model.Filters import com.mongodb.client.model.mql.MqlValues -To access document fields in an expression, you need to reference the -current document being processed by the aggregation pipeline. Use the -``current()`` method to refer to this document. To access the value of a +To access document fields in an expression, you must reference the +current document being processed by the aggregation pipeline by using the +``current()`` method. To access the value of a field, you must use the appropriately typed method, such as ``getString()`` or ``getDate()``. When you specify the type for a field, you ensure that the driver provides only those methods which are @@ -72,7 +72,7 @@ value of ``1.0``: of(1.0) To create an operation, chain a method to your field or value reference. -You can build more complex operations by chaining additional methods. +You can build more complex operations by chaining other methods. The following example creates an operation to find patients in New Mexico who have visited the doctor’s office at least once. The operation @@ -204,7 +204,7 @@ expected parameters and implementation. .. important:: The driver does not provide methods for all aggregation pipeline operators in - the Query API. If you need to use an unsupported operation in an + the Query API. To use an unsupported operation in an aggregation, you must define the entire expression using the BSON ``Document`` type. @@ -395,7 +395,7 @@ with ``lt()``. The ``project()`` method stores these filtered results as a new .. tip:: You must specify the type of the array that you retrieve with the - ``getArray()`` method if you need to work with the values of the + ``getArray()`` method if you work with the values of the array as their specific type. In this example, we specify that the ``seats`` array contains values @@ -629,7 +629,7 @@ You can apply custom functions to values of type ``MqlValue`` using the methods described in this section. To improve readability and allow for code reuse, you can move redundant -code into static methods. However, it is not possible to directly chain +code into static methods. However, you cannot directly chain static methods in {+language+}. The ``passTo()`` method lets you chain values into custom static methods. @@ -654,7 +654,7 @@ into custom static methods. - *No corresponding operator* -Suppose you need to determine how a class is performing against some +Suppose you want to determine how a class is performing against some benchmarks. You want to find the average final grade for each class and compare it against the benchmark values. From 9d33de13435736e3d15703ade9154f3148fa3ee6 Mon Sep 17 00:00:00 2001 From: rustagir Date: Wed, 7 Aug 2024 14:32:26 -0400 Subject: [PATCH 04/11] vale --- source/aggregation/agg-exp-ops.txt | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/source/aggregation/agg-exp-ops.txt b/source/aggregation/agg-exp-ops.txt index b603a736..f342b995 100644 --- a/source/aggregation/agg-exp-ops.txt +++ b/source/aggregation/agg-exp-ops.txt @@ -490,8 +490,8 @@ using the methods described in this section. .. tip:: - The ``cond()`` method is similar to the ternary operator in Java and you - should use it for simple branches based on a boolean value. You should use + The ``cond()`` method is similar to the ternary operator in {+language+} and you + can use it for simple branches based on boolean values. Use the ``switchOn()`` methods for more complex comparisons such as performing pattern matching on the value type or other arbitrary checks on the value. @@ -585,7 +585,7 @@ can be one of a few different types, and you want to create a standardized value indicating their membership level. The ``switchOn()`` method checks each clause in order. If the value matches the -type indicated by the clause, that clause determines the string value +type indicated by the clause, then the clause determines the string value corresponding to the membership level. If the original value is a string, it represents the membership level and that value is used. If the data type is a boolean, it returns either ``Gold`` or ``Guest`` for the membership level. If @@ -678,12 +678,12 @@ how the values compare: One advantage of using the ``passTo()`` method is that you can reuse your custom methods for other aggregations. You could use the ``gradeAverage()`` method to find the average of grades for - groups of students filtered by, for example, entry year or district, not just their - class. You could use the ``evaluate()`` method to evaluate, for + groups of students filtered by entry year or district, not just their + class, for example. You could use the ``evaluate()`` method to evaluate, for example, an individual student's performance, or an entire school's or district's performance. -The ``passArrayTo()`` method takes all of the students and calculates the +The ``passArrayTo()`` method takes an array of all students and calculates the average score by using the ``gradeAverage()`` method. Then, the ``passNumberTo()`` method uses the ``evaluate()`` method to determine how the classes are performing. This example stores the result as the ``evaluation`` @@ -823,7 +823,7 @@ using the methods described in this section. * - `year() <{+core-api+}/com/mongodb/client/model/mql/MqlDate.html#year(com.mongodb.client.model.mql.MqlString)>`__ - :manual:`$year ` -Suppose you have data about package deliveries and need to match +Suppose you have data about package deliveries and want to match deliveries that occurred on any Monday in the ``"America/New_York"`` time zone. @@ -900,7 +900,7 @@ using the methods described in this section. Suppose you have a collection of legacy customer data which includes addresses as child documents under the ``mailing.address`` field. You want -to find all the customers who currently live in Washington state. A +to find all the customers who live in Washington state. A document in this collection might resemble the following: .. code-block:: json @@ -956,7 +956,7 @@ You can perform a map operation on a value of either type ``MqlMap`` or .. tip:: - You should represent data as a map if the data maps + Represent data as a map if the data maps arbitrary keys such as dates or item IDs to values. .. list-table:: @@ -998,7 +998,7 @@ You can perform a map operation on a value of either type ``MqlMap`` or Suppose you have a collection of inventory data where each document represents an individual item you're responsible for supplying. Each document contains a -field that is a map of all your warehouses and how many copies they currently +field that is a map of all your warehouses and how many copies they have in their inventory of the item. You want to determine the total number of copies of items you have across all of your warehouses. A document in this collection might resemble the following: @@ -1079,7 +1079,7 @@ using the methods described in this section. * - `toUpper() <{+core-api+}/com/mongodb/client/model/mql/MqlString.html#toUpper()>`__ - :manual:`$toUpper ` -Suppose you need to generate lowercase usernames for employees of a +Suppose you want to generate lowercase usernames for employees of a company from the employees' last names and employee IDs. The ``append()`` method combines the ``lastName`` and ``employeeID`` fields into From c88382daf5850d339318e1a81794db88eb8fadcd Mon Sep 17 00:00:00 2001 From: rustagir Date: Wed, 7 Aug 2024 14:34:19 -0400 Subject: [PATCH 05/11] vale --- source/aggregation/agg-exp-ops.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/aggregation/agg-exp-ops.txt b/source/aggregation/agg-exp-ops.txt index f342b995..3e0787ae 100644 --- a/source/aggregation/agg-exp-ops.txt +++ b/source/aggregation/agg-exp-ops.txt @@ -1000,7 +1000,7 @@ Suppose you have a collection of inventory data where each document represents an individual item you're responsible for supplying. Each document contains a field that is a map of all your warehouses and how many copies they have in their inventory of the item. You want to determine the total number of -copies of items you have across all of your warehouses. A document in this +copies of items you have across all warehouses. A document in this collection might resemble the following: .. code-block:: json From 44b00b41613667b0fb705f84944e48f4562f6bcc Mon Sep 17 00:00:00 2001 From: rustagir Date: Thu, 8 Aug 2024 14:42:49 -0400 Subject: [PATCH 06/11] MM PR fixes 1 --- source/aggregation/agg-exp-ops.txt | 64 ++++++++++--------- source/includes/aggregation/aggExpressions.kt | 24 +++---- 2 files changed, 45 insertions(+), 43 deletions(-) diff --git a/source/aggregation/agg-exp-ops.txt b/source/aggregation/agg-exp-ops.txt index 3e0787ae..6d274afe 100644 --- a/source/aggregation/agg-exp-ops.txt +++ b/source/aggregation/agg-exp-ops.txt @@ -1,4 +1,4 @@ -.. _kotlin-sync-sync-aggregation-expression-operations: +.. _kotlin-sync-aggregation-expression-operations: ================================= Aggregation Expression Operations @@ -72,13 +72,13 @@ value of ``1.0``: of(1.0) To create an operation, chain a method to your field or value reference. -You can build more complex operations by chaining other methods. +You can build more complex operations by chaining multiple methods. The following example creates an operation to find patients in New Mexico who have visited the doctor’s office at least once. The operation performs the following actions: -- Checks if the size of the ``visitDates`` array is greater than ``0`` +- Checks if the size of the ``visitDates`` array value is greater than ``0`` by using the ``gt()`` method - Checks if the ``state`` field value is “New Mexico” by using the ``eq()`` method @@ -112,11 +112,7 @@ how to include your expression in common aggregates builder methods: - ``group()`` .. TODO To learn more about these methods, see the -.. :ref:`kotlin-sync-sync-aggregation`. - -The examples use the ``listOf()`` method to create a list of -aggregation stages. Then, the examples pass the pipeline to the -``aggregate()`` method of ``MongoCollection``. +.. :ref:`kotlin-sync-aggregation`. Constructor Methods ------------------- @@ -195,6 +191,10 @@ the Server manual documentation. While each method is effectively equivalent to the corresponding Query API expression, they may differ in expected parameters and implementation. +The example in each section uses the ``listOf()`` method to create a +pipeline from the aggregation stage. Then, each example passes the +pipeline to the ``aggregate()`` method of ``MongoCollection``. + .. note:: The driver generates a Query API expression that may be different @@ -250,8 +250,8 @@ You can perform an arithmetic operation on a value of type ``MqlInteger`` or - :manual:`$subtract ` Suppose you have weather data for a specific year that includes the -precipitation measurement (in inches) for each day. You want find the average -precipitation, in millimeters, for each month. +precipitation measurement (in inches) for each day. You want to find the +average precipitation, in millimeters, for each month. The ``multiply()`` operator multiplies the ``precipitation`` field by ``25.4`` to convert the value to millimeters. The ``avg()`` accumulator method @@ -389,18 +389,20 @@ following: The ``filter()`` method displays only the results matching the provided predicate. In this case, the predicate uses ``sum()`` to calculate the total number of seats and compares that value to the number of ``ticketsBought`` -with ``lt()``. The ``project()`` method stores these filtered results as a new -``availableShowtimes`` array. +by using the ``lt()`` method. The ``project()`` method stores these +filtered results as a new ``availableShowtimes`` array field. .. tip:: - You must specify the type of the array that you retrieve with the - ``getArray()`` method if you work with the values of the - array as their specific type. + You must specify the type of values that an array contains when using + the ``getArray()`` method to work with the values as any specific + type. For example, you must specify that an array contains integers + if you want to perform calculations with those integers elsewhere in + your application. - In this example, we specify that the ``seats`` array contains values - of type ``MqlDocument`` so that we can extract nested fields from - each array entry. + The example in this section specifies that the ``seats`` array + contains values of type ``MqlDocument`` so that it can extract nested + fields from each array entry. The following code shows the pipeline for this aggregation: @@ -414,8 +416,8 @@ The following code shows the pipeline for this aggregation: .. note:: To improve readability, the previous example assigns intermediary values to - the ``totalSeats`` and ``isAvailable`` variables. If you don't pull - out these intermediary values into variables, the code still produces + the ``totalSeats`` and ``isAvailable`` variables. If you don't assign + these intermediary values to variables, the code still produces equivalent results. The following code provides an equivalent aggregation pipeline in @@ -459,8 +461,9 @@ Suppose you want to classify very low or high weather temperature readings (in degrees Fahrenheit) as extreme. The ``or()`` operator checks to see if temperatures are extreme by comparing -the ``temperature`` field to predefined values with ``lt()`` and ``gt()``. -The ``project()`` method records this result in the ``extremeTemp`` field. +the ``temperature`` field to predefined values by using the ``lt()`` and +``gt()`` methods. The ``project()`` method records this result in the +``extremeTemp`` field. The following code shows the pipeline for this aggregation: @@ -675,13 +678,12 @@ how the values compare: .. tip:: - One advantage of using the ``passTo()`` method is that you can reuse - your custom methods for other aggregations. You could + Using the ``passTo()`` method allows you to reuse + your custom methods for other aggregations. For example, you can use the ``gradeAverage()`` method to find the average of grades for groups of students filtered by entry year or district, not just their - class, for example. You could use the ``evaluate()`` method to evaluate, for - example, an individual student's performance, or an entire school's or - district's performance. + class. Similarly, you could use the ``evaluate()`` method to evaluate + an individual student's performance or an entire school's performance. The ``passArrayTo()`` method takes an array of all students and calculates the average score by using the ``gradeAverage()`` method. Then, the @@ -832,10 +834,10 @@ valid dates, such as ``"2018-01-15T16:00:00Z"`` or ``"Jan 15, 2018, 12:00 PM EST"``, you can use the ``parseDate()`` method to convert the strings into date types. -The ``dayOfWeek()`` method determines which day of the week it is and converts -it to a number based on which day is a Monday according to the -``"America/New_York"`` parameter. The ``eq()`` method compares this value to -``2``, which corresponds to Monday based on the provided timezone parameter. +The ``dayOfWeek()`` method determines which day of the week that a date +is, then converts it to a number. The number assignment uses ``0`` to mean +Sunday when using the ``"America/New_York"`` timezone. The ``eq()`` +method compares this value to ``2``, or Monday. The following code shows the pipeline for this aggregation: diff --git a/source/includes/aggregation/aggExpressions.kt b/source/includes/aggregation/aggExpressions.kt index 053640e1..b58a2b36 100644 --- a/source/includes/aggregation/aggExpressions.kt +++ b/source/includes/aggregation/aggExpressions.kt @@ -40,7 +40,7 @@ class AggExpressionsTest { val month = current().getDate("date").month(of("UTC")) val precip = current().getInteger("precipitation") - val results = collection.aggregate( // :remove: + val results = collection.aggregate( listOf( Aggregates.group( month, @@ -89,7 +89,7 @@ class AggExpressionsTest { // start-array-aggregation val showtimes = current().getArray("showtimes") - val results = collection.aggregate( // :remove: + val results = collection.aggregate( listOf( Aggregates.project( Projections.fields( @@ -131,7 +131,7 @@ class AggExpressionsTest { // start-boolean-aggregation val temperature = current().getInteger("temperature") - val results = collection.aggregate( // :remove: + val results = collection.aggregate( listOf( Aggregates.project( Projections.fields( @@ -172,7 +172,7 @@ class AggExpressionsTest { // start-comparison-aggregation val location = current().getString("location") - val results = collection.aggregate( // :remove: + val results = collection.aggregate( listOf( Aggregates.match( Filters.expr(location.eq(of("California"))) @@ -206,7 +206,7 @@ class AggExpressionsTest { // start-conditional-aggregation val member = current().getField("member") - val results = collection.aggregate( // :remove: + val results = collection.aggregate( listOf( Aggregates.project( Projections.fields( @@ -272,7 +272,7 @@ class AggExpressionsTest { // start-convenience-aggregation val students = current().getArray("students") - val results = collection.aggregate( // :remove: + val results = collection.aggregate( listOf( Aggregates.project( Projections.fields( @@ -311,7 +311,7 @@ class AggExpressionsTest { // start-conversion-aggregation val graduationYear = current().getString("graduationYear") - val results = collection.aggregate( // :remove: + val results = collection.aggregate( listOf( Aggregates.addFields( Field( @@ -351,7 +351,7 @@ class AggExpressionsTest { // start-date-aggregation val deliveryDate = current().getString("deliveryDate") - val results = collection.aggregate( // :remove: + val results = collection.aggregate( listOf( Aggregates.match( Filters.expr( @@ -393,7 +393,7 @@ class AggExpressionsTest { // start-document-aggregation val address = current().getDocument("mailing.address") - val results = collection.aggregate( // :remove: + val results = collection.aggregate( listOf( Aggregates.match( Filters.expr( @@ -427,7 +427,7 @@ class AggExpressionsTest { // start-map-aggregation val warehouses = current().getMap("warehouses") - val results = collection.aggregate( // :remove: + val results = collection.aggregate( listOf( Aggregates.project( Projections.fields( @@ -465,7 +465,7 @@ class AggExpressionsTest { val lastName = current().getString("lastName") val employeeID = current().getString("employeeID") - val results = collection.aggregate( // :remove: + val results = collection.aggregate( listOf( Aggregates.project( Projections.fields( @@ -504,7 +504,7 @@ class AggExpressionsTest { // start-type-aggregation val rating = current().getField("rating") - val results = collection.aggregate( // :remove: + val results = collection.aggregate( listOf( Aggregates.project( Projections.fields( From dfa3763e84d82ce2ff8c1d0b8cc3cbaf818d3641 Mon Sep 17 00:00:00 2001 From: rustagir Date: Thu, 8 Aug 2024 14:45:16 -0400 Subject: [PATCH 07/11] vale --- source/aggregation/agg-exp-ops.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/source/aggregation/agg-exp-ops.txt b/source/aggregation/agg-exp-ops.txt index 6d274afe..33d59e3e 100644 --- a/source/aggregation/agg-exp-ops.txt +++ b/source/aggregation/agg-exp-ops.txt @@ -397,7 +397,7 @@ filtered results as a new ``availableShowtimes`` array field. You must specify the type of values that an array contains when using the ``getArray()`` method to work with the values as any specific type. For example, you must specify that an array contains integers - if you want to perform calculations with those integers elsewhere in + to perform calculations with those integers elsewhere in your application. The example in this section specifies that the ``seats`` array @@ -1118,7 +1118,7 @@ using the methods described in this section. These methods do not return boolean values. Instead, you provide a default value that matches the type specified by the method. If the checked value matches the method type, the checked value is returned. Otherwise, the supplied -default value is returned. If you want to program branching logic based on the +default value is returned. To program branching logic based on the data type, see ``switchOn()``. .. list-table:: From 78b6ed4bf78b62e51d6d36856332966cd695d9db Mon Sep 17 00:00:00 2001 From: rustagir Date: Thu, 8 Aug 2024 14:46:24 -0400 Subject: [PATCH 08/11] indentation fix --- source/aggregation/agg-exp-ops.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/aggregation/agg-exp-ops.txt b/source/aggregation/agg-exp-ops.txt index 33d59e3e..d7197ca4 100644 --- a/source/aggregation/agg-exp-ops.txt +++ b/source/aggregation/agg-exp-ops.txt @@ -417,7 +417,7 @@ The following code shows the pipeline for this aggregation: To improve readability, the previous example assigns intermediary values to the ``totalSeats`` and ``isAvailable`` variables. If you don't assign - these intermediary values to variables, the code still produces + these intermediary values to variables, the code still produces equivalent results. The following code provides an equivalent aggregation pipeline in From 075e349569cf62a773b8fa5fc276bd269c4f3d6c Mon Sep 17 00:00:00 2001 From: rustagir Date: Tue, 13 Aug 2024 10:58:13 -0400 Subject: [PATCH 09/11] MM PR fixes 2 --- source/aggregation/agg-exp-ops.txt | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/source/aggregation/agg-exp-ops.txt b/source/aggregation/agg-exp-ops.txt index d7197ca4..ed87ef0d 100644 --- a/source/aggregation/agg-exp-ops.txt +++ b/source/aggregation/agg-exp-ops.txt @@ -188,7 +188,7 @@ available in the driver and corresponding expression operators in the Query API. The method names link to API documentation and the aggregation pipeline operator names link to descriptions and examples in the Server manual documentation. While each method is effectively -equivalent to the corresponding Query API expression, they may differ in +equivalent to the corresponding aggregation operator, they may differ in expected parameters and implementation. The example in each section uses the ``listOf()`` method to create a @@ -254,7 +254,7 @@ precipitation measurement (in inches) for each day. You want to find the average precipitation, in millimeters, for each month. The ``multiply()`` operator multiplies the ``precipitation`` field by -``25.4`` to convert the value to millimeters. The ``avg()`` accumulator method +``25.4`` to convert the field value to millimeters. The ``avg()`` accumulator method returns the average as the ``avgPrecipMM`` field. The ``group()`` method groups the values by month given in each document's ``date`` field. @@ -956,11 +956,6 @@ Map Operations You can perform a map operation on a value of either type ``MqlMap`` or ``MqlEntry`` using the methods described in this section. -.. tip:: - - Represent data as a map if the data maps - arbitrary keys such as dates or item IDs to values. - .. list-table:: :header-rows: 1 :widths: 50 50 From 6e142fdf62865c5ffde7d109b351492177835356 Mon Sep 17 00:00:00 2001 From: rustagir Date: Tue, 13 Aug 2024 11:03:20 -0400 Subject: [PATCH 10/11] entry to index --- source/{aggregation => }/agg-exp-ops.txt | 0 source/index.txt | 4 ++++ 2 files changed, 4 insertions(+) rename source/{aggregation => }/agg-exp-ops.txt (100%) diff --git a/source/aggregation/agg-exp-ops.txt b/source/agg-exp-ops.txt similarity index 100% rename from source/aggregation/agg-exp-ops.txt rename to source/agg-exp-ops.txt diff --git a/source/index.txt b/source/index.txt index 8ecf273d..970a08c8 100644 --- a/source/index.txt +++ b/source/index.txt @@ -19,6 +19,7 @@ /read /indexes /aggregation + /agg-exp-ops /data-formats /faq /connection-troubleshooting @@ -85,6 +86,9 @@ Transform Your Data with Aggregation Learn how to use the {+driver-short+} to perform aggregation operations in the :ref:`kotlin-sync-aggregation` section. +Learn how to use aggregation expression operations to build +aggregation stages in the :ref:`kotlin-sync-aggregation-expression-operations` section. + Specialized Data Formats ------------------------ From 398bcbe559845f2795ae294648b59adb04682adf Mon Sep 17 00:00:00 2001 From: rustagir Date: Tue, 13 Aug 2024 11:04:37 -0400 Subject: [PATCH 11/11] wip --- source/index.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/index.txt b/source/index.txt index 970a08c8..eba9c5d7 100644 --- a/source/index.txt +++ b/source/index.txt @@ -19,7 +19,7 @@ /read /indexes /aggregation - /agg-exp-ops + Use Aggregation Expression Operations /data-formats /faq /connection-troubleshooting