// First, add JitPack to your repositories
repositories {
//...
maven { url "https://jitpack.io" }
}
// main kmongodsl package: dependens only from org.mongodb:bson
compile 'com.github.beyondeye:kmongodsl:0.1.0'
MongoDB aggregation pipeline is very powerful. But writing complex multistage aggregations is error-prone. Also the extensive use of the "$" sign in MongoDB command syntax create many problems in Kotlin where the same symbol is used for triggering string interpolation. KMongo DSL solve this problem and has also several other advantages:
- Auto completion of name of aggregation stages, operators and operator parameters
- auto indenting and code folding like regular Kotlin code
- Directly output BSON without the need to parse a JSON string.
The library
Let's see some example of increasing complexity
in json
{
"$project": {
"fieldToInclude": 1
}
}
with kmongodsl
val stage= mongoAggregateStage
{
project {
+"fieldToInclude"
}
}
in json
{
"$project": {
"fieldToExclude": 0
}
}
with kmongodsl
val stage= mongoAggregateStage
{
project {
-"fieldToExclude"
}
}
in json
{
"$project": {
"fieldNewName": "fieldToInclude"
}
}
with kmongodsl
val stage= mongoAggregateStage
{
project {
"fieldNewName".."fieldToInclude"
}
}
in json
{
"$project": {
"the_sliced_field": {
"$slice": ["$fieldtoslice", 1, 10]
}
}
}
with kmongodsl
val stage= mongoAggregateStage {
project {
"the_sliced_field"..{
slice("fieldtoslice", 1, 10)
}
}
}
In the previous example we saw a short form definition for the slice
operator. Not all operators
support this. Now we will see the most general form for specifying arguments for
operators that take an array of arguments as input
in json
{
"$project": {
"the_sliced_field": {
"$slice": [
{"$sum": ["$a", "$b"]},
1,
10
]
}
}
}
with kmongodsl
val stage4= mongoAggregateStage {
project {
"the_sliced_field"..{
slice{
marg..{ sum { mfld.."a";mfld.."b" } }
mval..1
mval..10
}
}
}
}
In the code above we see some inportant conventions used in the DSL
- marg..[expression] is used to specify a general expression as argument
- fld..[fieldName] is used to specify a field in the source document as argument
- mval..[constant] is used to specify a literal constant as argument
- mvar..[variableName] is used to specify a mongodb variable as argument (not in the example)
Also another important thing to note is that in the DSL is possible to specify more than one argument per line by separating them with a semicolumn
in json
{
"$project": {
"the_computed_field": {
"$let": {
"vars": {
"var1": "$field1",
"var2": "$field2"
},
"in": {
"$multiply": [
"$$var1",
"$$var2"
]
}
}
}
}
}
with kmongodsl
val stage1= mongoAggregateStage {
project {
"the_computed_field"..{
let{
vars {
"var1"..(mfld.."field1")
"var2"..(mfld.."field2")
}
in_ { multiply { mvar.."var1";mvar.."var2" } }
}
}
}
}
Notice that in
was renamed in_
because this is a reserved keyword in Kotlin
in json
{
"$project": {
"the_computed_field": {
"$map": {
"input": "$quizzes",
"as": "grade",
"in": {
"$sum": ["$$grade",2 ]
}
}
}
}
}
with kmongodsl
val stage1= mongoAggregateStage {
project {
"the_computed_field"..{
map{
input(mfld.."quizzes") //beware of the pitfall of writing input {mfld.."quizzes"}
as_("grade")
in_ { sum { mvar.."grade";mval..2 } }
}
}
}
}
in json
{
"$project": {
"the_computed_field": {
"$let": {
"vars": {
"var1": {
"$sum": ["$field1", "$field2"]
},
"var2": "$field2"
},
"in": {
"$multiply": ["$$var1", "$$var2"]
}
}
}
}
}
with kmongodsl
val stage1= mongoAggregateStage {
project {
"the_computed_field"..{
let{
vars {
"var1"..{
sum { mfld.."field1"; mfld.."field2" }
}
"var2"..(mfld.."field2")
}
in_ { multiply { mvar.."var1";mvar.."var2" } }
}
}
}
}
in json
{
"$project": {
"the_computed_field": {
"$zip": {
"inputs": [
{"$arrayElementAt": ["$matrix", 0]},
{"$arrayElementAt": ["$matrix", 1]},
{"$arrayElementAt": ["$matrix", 2]}
],
"useLongestLength": true,
"defaults": "$$defaults"
}
}
}
}
with kmongodsl
val stage1= mongoAggregateStage {
project {
"the_computed_field"..{
zip{
inputs {
marg..{arrayElementAt { mfld.."matrix"; mval..0 }}
marg..{arrayElementAt { mfld.."matrix"; mval..1 }}
marg..{arrayElementAt { mfld.."matrix"; mval..2 }}
}
useLongestLength(true)
defaults(mvar.."defaults")
}
}
}
}
A very important thing to notice here is that for input fields that are actually
an array of expressions (like inputs
in the example above), the syntax is to put one expression per line with the syntax marg..<expression>
(or also on the same line separated by a semicolumn).
This is also true if the expression in the array is not a complex expression, but instead a field in the source document. Also
in this case you need to use the marg..<>
syntax
This is not very intuitive.
This is even more confusing because for simple operators (operators that takes a
simple array of expressions (like $arrayElementAt
, $multiply
, etc..) and not an object containing a field that is
an array of expressions (like $let
,$map
,$reduce
, etc..) , the syntax is much simpler (as shown in the examples above
This complex syntax using marg..
will probably change in the future.
Anyway the autocompletion features of the DSL will help a lot in such cases for remembering the correct syntax
Notice that as
was also renamed as as_
because it is a reserved keyword in Kotlin
in json
{
"$project": {
"the_computed_field": {
"$reduce": {
"input": "$discounts",
"initialValue": "$price",
"in": {
"$multiply": ["$$value", {"$subtract": [1, "$$this"]}
]
}
}
}
}
}
with kmongodsl
val stage1= mongoAggregateStage {
project {
"the_computed_field"..{
reduce{
input(mfld.."discounts")
initialValue(mfld.."price")
in_expr {
multiply {
mvar.."value"
marg..{ subtract { mval..1;mvar.."this" }
}
}
}
}
}
}
}
The code is still in development and incomplete (not all mongodb operators and aggregation stages are implemented) and the syntax is not yet final
Copyright 2017 Dario Elyasy
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.