Extensions Overview

This overview presents the extensions for the synchronous driver.

Equivalent methods are available for async drivers.

Insert, Update & Delete

Insert

Note that by default, KMongo serializes null values during insertion of instances with the Jackson mapping engine. This is not the case with the "native" POJO engine.

class TFighter(val version: String, val pilot: Pilot?)

database.getCollection<TFighter>().insertOne(TFighter("v1", null))
val col = database.getCollection("tfighter")
//collection of org.bson.Document is returned when no generic used

println(col.findOne()) //print: Document{{_id=(...), version=v1, pilot=null}}

The query shell format is supported:

database.getCollection<TFighter>().insertOne("{'version':'v1'}")

Save

KMongo provides a convenient MongoCollection<T>.save(target: T) method. If the instance passed as argument has no id, or a null id, it is inserted (and the id generated on server side or client side, respectively). Otherwise, a replaceOneById with upsert=true is performed.

Update

KMongo provides various MongoCollection<T>.updateOne methods.

  • you can perform an update using shell query format:
col.updateOne("{name:'Paul'}", "{$set:{name:'John'}}")
  • or with typed queries:
col.updateOne(Friend::name eq "Paul", set(Friend::name, "John"))
//or with annotation processor ->
col.updateOne(Name eq "Paul", set(Name, "John"))
  • You can also update an instance by its _id (all properties of the instance are updated):
//explicitly
col.updateOneById(friend._id, newFriend)
//or implicitly
//note that if newFriend does not contains an _id value, the operation fails
col.updateOne(newFriend)

updateMany is also supported.

Replace

The replaceOne and replaceOneById methods remove automatically the _id of the replacement when using shell query format, and looks like update methods:

//ObjectId of friend replacement does not matter 
col.replaceOne("{name:'Peter'}}", Friend(ObjectId(), "John"))
//explicit id filter ->
col.replaceOneById(friend._id, Friend(ObjectId(), "John"))
//implicit id filter ->
col.replaceOne(Friend(friend._id, "John"))

But for typed queries you need to use replaceOneWithFilter as the default java driver method does not remove the _id:

//ObjectId of friend replacement is removed from the replace query 
//before it is sent to mongo 
col.replaceOneWithFilter(Name eq "Peter", Friend(ObjectId(), "John"))
// if the mongo java driver method is used, it will fail at runtime
// as the _id is updated ->
col.replaceOne(Name eq "Peter", Friend(ObjectId(), "John"))

Delete

deleteOne and deleteOneById work as expected:

col.deleteOne("{name:'John'}")
col.deleteOne(Friend::name eq "John")

As does deleteMany.

FindOneAnd...

The java driver variants are also supported:

Bulk Write

If you want to do many operations with only one query (for performance reasons), bulkWrite is supported.

With query shell format :

val friend = Friend("John", "22 Wall Street Avenue")
val result = col.bulkWrite(
   """ [
      { insertOne : { "document" : ${friend.json} } },
      { updateOne : {
                      "filter" : {name:"Fred"},
                      "update" : {$set:{address:"221B Baker Street"}},
                      "upsert" : true
                    }
      },
      { updateMany : {
                          "filter" : {},
                          "update" : {$set:{address:"nowhere"}}
                      }
      },
      { replaceOne : {
                          "filter" : {name:"Max"},
                          "replacement" : {name:"Joe"},
                          "upsert" : true
                      }
      },
      { deleteOne :  { "filter" : {name:"Joe"} }},
      { deleteMany :  { "filter" : {} } }
      ] """
 )

 assertEquals(1, result.insertedCount)
 assertEquals(2, result.matchedCount)
 assertEquals(3, result.deletedCount)
 assertEquals(2, result.modifiedCount)
 assertEquals(2, result.upserts.size)
 assertEquals(0, col.count())

or typed queries :

with(friend) {
        col.bulkWrite(
            insertOne(friend),
            updateOne(
                ::name eq "Fred",
                set(::address, "221B Baker Street"),
                upsert()
            ),
            updateMany(
                EMPTY_BSON,
                set(::address, "nowhere")
            ),
            replaceOne(
                ::name eq "Max",
                Friend("Joe"),
                upsert()
            ),
            deleteOne(::name eq "Max"),
            deleteMany(EMPTY_BSON)
        )

Retrieve Data

find

findOne, findOneById and find works as expected.

With the shell query format:

col.findOne("{name:'John'}")

or the type-safe query format:

col.findOne(Friend::name eq "John")
//implicit $and is used if more than one criterion is set ->
col.findOne(Name eq "John", Address eq "22 Wall Street Avenue")

There are also extensions for FindIterable. The most used is usually the toList extension on MongoIterable:

val list : List<Friend> = col.find(Friend::name eq "John").toList()

count

count is supported:

col.count("{name:{$exists:true}}")

distinct

As is distinct:

With shell query format:

col.distinct<String>("address")

Or type-safe queries:

col.distinct(Friend::address)

Map-Reduce

mapReduce and equivalent method (to avoid name conflict with java driver) mapReduceWith are available:

data class KeyValue(val _id:String, val value:Long)

col.mapReduce<KeyValue>(
            """
                function() {
                       emit("name", this.name.length);
                   };
            """,
            """
                 function(name, l) {
                          return Array.sum(l);
                      };
            """
        ).first()

Aggregate

The aggregation framework is supported.

It works with shell query format, with several limitations:

col.aggregate<Article>("[{$match:{tags:'virus'}},{$limit:1}]").toList()

But this is an area or type-safe style shines:

val r = col.aggregate<Result>(
            match(
                Article::tags contains "virus"
            ),
            project(
                Article::title from Article::title,
                Article::ok from cond(Article::ok, 1, 0),
                Result::averageYear from year(Article::date)
            ),
            group(
                Article::title,
                Result::count sum Article::ok,
                Result::averageYear avg Result::averageYear
            ),
            sort(
                ascending(
                    Result::title
                )
            )
        )
        .toList()             

Indexes

KMongo provides several methods to manage indexes.

ensureIndex

ensureIndex and ensureUniqueIndex ensure that index is created even if it's structure has changed.

You can create an index with shell query format:

col.ensureUniqueIndex("{'id':1,'type':1}")

or with type-safe style:

col.ensureUniqueIndex(Id, Type)

dropIndex

With dropIndexOfKeys, you can drop index, based on their keys:

col.dropIndexOfKeys("{'id':1,'type':1}")

MongoIterable

The MongoIterable interface of the java synchronous driver has a major flaw: it extends Iterable with this declaration:

MongoCursor<TResult> iterator();

The problem is that you need to close each MongoCursor you create from a MongoIterable, or you get a potential memory leak. This is really error prone.

for(r in col.find()) println(r)
//you have a memory leak - with java or kotlin!

This is especially problematic for Kotlin, as it is a common practise to use the Kotlin Iterable extensions:

// without KMongo, a memory leak at each line!
col.find().firstOrNull()
col.find().mapIndexed{ ... }
col.find().toList()
col.find().map {it.a to it.b}.toMap()
col.find().forEach{println(it)}

KMongo does not fix the "for" issue, but does solve automatically memory leaks for all other patterns by providing MongoIterable extensions.

You have nothing to change in your code - just compile it with the KMongo dependency in the classpath!

withKMongo

The recommended method, in order to setup KMongo, is to use KMongo.createClient() to get a MongoClient instance.

However you can also get a MongoClient instance directly from the java driver and then call MongoDatabase#withKMongo or MongoCollection#withKMongo extensions to enable KMongo object mapping support.

This is especially useful if you have already a java project and you want to migrate progressively to Kotlin.

KDoc

Please consult KDoc for an exhaustive list of the KMongo extensions.