Convert your existing KMongo queries to KtMongo's DSL¶
This page is about converting queries written with the deprecated library KMongo to use KtMongo's DSL instead.
- To learn why you should migrate, read the dedicated article.
- To configure KtMongo in your existing KMongo project, see our dedicated guide.
- If you are using the official MongoDB driver for Java or for Kotlin, see our dedicated guide.
- If you're unsure in which situation you are, see our dedicated guide.
Access the KtMongo DSL¶
The KMongo library offers extension functions directly on top of the official Java driver. This leads to overload pollution: you may confuse methods from the official Java driver with those from KMongo, and vice versa.
Instead, KtMongo has its own collection type. KtMongo methods are only available on that type. Therefore, it's never ambiguous which library you're trying to call.
To convert from a KMongo collection to a KtMongo collection, you can use the MongoCollection.asKtMongo() extension function.
To learn more, see the dedicated guide.
Main differences¶
Vararg vs DSL¶
The main difference between KMongo operators and the KtMongo DSL is that KMongo accepts parameters as a vararg, whereas KtMongo uses a DSL:
Notice how:
- the parentheses become braces,
- the trailing commas are gone.
The KMongo operators are top-level functions that return an opaque type-unsafe Bson type, whereas KtMongo operators directly attach themselves into the current operation and have no return value.
As a consequence, KtMongo is more type-safe:
- Operators will not compile when called in the wrong context (e.g.
$eqin an update). - Operators will correctly disambiguate between different contexts, even if they have the exact same Kotlin syntax (e.g.
$eqin find and$eqin an aggregation).
Dynamic queries¶
This allows us to more easily create complex queries. Compare the following KMongo query:
collection.findOne(
and(
listOfNotNull(
(User::name eq criteria.name).takeUnless { criteria.name == null },
(User::age eq criteria.age).takeUnless { criteria.age == null },
)
)
)
When these kinds of requests grow, they become harder to understand and easier to get wrong. Also, with these way of writing them, the criteria is instantiated even if it isn't used later.
With KtMongo, everything is co-located and the intermediate list is eliminated:
collection.findOne {
if (criteria.name != null)
User::name eq criteria.name
if (criteria.age != null)
User::age eq criteria.age
}
Note that you also don't need the root and() operator with KtMongo, because KtMongo automatically detects that a findOne with multiple criteria must be using an $and operator.
For the specific case of optional criteria, KtMongo provides a dedicated syntax that is even more concise.
Extracting into functions¶
To make parts of complex queries reusable, it is common to extract them as independent functions.
In KMongo, this is done by creating a function that returns a Bson instance:
fun filter(criteria: UserFilterCriteria): Bson = and(
User::name eq criteria.name,
User::age gte criteria.minAge,
)
KtMongo needs to know the type of the document for type-safety reasons, and operators automatically bind themselves to the current DSL. Each DSL provides its own scope:
fun FilterQuery<User>.filter(criteria: UserFilterCriteria) = and {
User::name eq criteria.name
User::age gte criteria.minAge
}
Options¶
Options are passed as their own DSL and are usually written first. For example, the following query:
users.countDocuments(
filter = User::name eq "Patrick",
options = CountOptions().limit(10)
)
Nested fields¶
Nested documents¶
Both KMongo and KtMongo use the syntax User::profile / Profile::name.
Danger
Although both libraries use the same syntax, they are implemented differently and do not recognize each other. Do not use the KMongo / operator in a KtMongo query, as it will result in an incorrect query. Using the KtMongo operator in a KMongo query will not compile.
The KMongo / operator needs to be explicitly imported. The KtMongo / operator is only available within a KtMongo command's DSL and doesn't need an import.
You can follow the progress of lifting this restriction here.
Unsafe nested documents¶
To unsafely access a nested field (without type-safety), KMongo adds the % operator: User::profile % Car::name.
Instead, KtMongo provides the unsafe extension function: User::profile unsafe Car::name.
You can also unsafely cast a field to another type and then use any type-safe operator: User::profile.unsafeCast<Car>() / Car::name.
Nested arrays¶
| MongoDB syntax | KMongo | KtMongo | Meaning |
|---|---|---|---|
"friends.2" |
User::friends.pos(2) |
User::friends[2] |
The friend at index 2. |
"friends.$" |
User::friends.posOp |
User::friends.selectedAvailable only in updates. |
The first friend selected by the update's filter. |
"friends.$[]" |
User::friends.allPosOp |
User::friends.allAvailable only in updates. |
All friends. |
"friends.$[<id>]" |
User::friends.filteredPosOp("id") |
User::friends.filter {}Available only in updates. |
Creates an array filter. |
Find¶
Use find() (returns a cursor) and findOne() (returns a single element).
As explained above, the vararg becomes a DSL.
Filter operations with multiple criteria imply a root $and:
Complex query building can be replaced by simple if and for directly within the DSL.
Update (simple)¶
KtMongo provides:
updateMany: updates all documents that match a filter.updateOne: updates a single document that matches a filter.upsertOne: upserts a single document that matches a filter.
They each follow the same pattern as the find() methods.
collection.updateMany(
filter = and(
User::name.exists(),
User::age gt 18,
),
set(
User::isLegal setTo true,
)
)
collection.updateMany(
filter = {
User::name.exists()
User::age gt 18
},
update = {
User::isLegal set true
}
)
Unlike in KMongo, there is no need to combine multiple operators yourself.
collection.updateMany(
filter = …,
set(
User::name setTo "foo",
User::isLegal setTo true,
),
inc(
User::age setTo 1
)
)
collection.updateMany(
filter = { … },
update = {
User::name set "foo"
User::isLegal set true
User::age inc 1
}
)
Update (aggregation pipeline)¶
MongoDB provides two syntaxes for updates: one with regular query operators, and one with aggregation operators.
In KMongo, they are differentiated by the overload of updateOne (or similar): the overload that takes a vararg Bson uses the query syntax, but the overload that takes a List<Bson> uses the aggregation syntax. This is confusing, especially because KMongo doesn't provide the aggregation operators out of the box.
KtMongo uses a suffix to use the aggregation pipeline syntax:
updateManyWithPipeline: updates all documents that match a filter.updateOneWithPipeline: updates a single document that matches a filter.upsertOneWithPipeline: upserts a single document that matches a filter.