Getting and writing data

Put, Delete, Get

Kodein-DB does not know the difference between Put and Update. If you put a document that already exists in the database, it will be overwritten (you can, however, manually check the existence and state of an overwritten model).

To put a document inside the database, simply use the put method:

Putting a model
val key = db.put(user) (1)
1 The put method returns the Key that refers to the model.

You can as easily delete a document:

Deleting a document
db.delete(key)

To get a model, simply use the get method:

Getting a model
val user = db[key]

Handling Keys

Definition

A Key is the unique identifier that identifies a document (and the associated model) inside the database. It contains the following information:

  • The type of the document (which is also the type of the model, unless you’re using polymorphism).

  • The Id of the document.

You can think of Kodein-DB as a file cabinet:

  • The document type defines the drawer inside the cabinet.

  • The document ID is the label that defines the document inside the drawer.

A key is specific to a Kodein-DB instance: a key refers to a model relative to a specific Kodein-DB. Do not share a Key: the same model with the same ID may give 2 different keys on two different databases.

Creating a Key

From a model

You can always ask Kodein-DB to provide you with the key of a specific model:

Creating a key
val key = db.newKey(user)
You can create a key from any model, whether it exists in database or not. Using newKeyFrom does not ensure you that the model exists in database, nor does it put it.

From ID values

You can easily create a key from its ID value(s):

Creating a key
val key = db.newKey<User>("01234567-89ab-cdef-0123-456789abcdef")

If you are using composite IDs, you need to provide all values of the ID.
In the case of the earlier example, you would need to create the key as such:

Creating a key with a composite ID
val key = db.newKey<User>("Doe", "John", "01234567-89ab-cdef-0123-456789abcdef")

From a Base64 representation

Once you have a Key (either from put or newKey), you can serialize it to Base64:

serializing a key to Base64
val b64 = key.toBase64()

Then, you can re-create a key from a Base64 string:

deserializing a key from Base64
val key = db.newKeyFromB64(b64)
Even when serialized in Base64, a Key is only valid on the Kodein-DB that created it.

Iteration

Getting a cursor

To iterate over a collection or a subset of a collection, you need to create cursor.

Cursors are Closeable! You need to either close them after use, or to call use on them.

You can iterate:

  • Over all models of a collection, ordered by ID:

    val cursor = db.find<User>().all()
  • Over all models of a collection whose composite ID begins with a specific value, ordered by id:

    val cursor = db.find<User>().byId("Doe")
  • Over all models of a collection, ordered by an index:

    val cursor = db.find<User>().byIndex("lastName")
  • Over all models of a collection with the specific index (composite or not) value, ordered by that index:

    val cursor = db.find<User>().byIndex("lastName", "Doe")
  • Over all models of the database:

    val cursor = db.findAll()
    When using findAll() on multiplatform projects, you must define a type table that defines all model classes!
Cursors are working on a snapshot of the database. This means that once an iterator is created, subsequent writes to the database won’t be reflected in the result of the iteration.

Using a cursor

As is

By default, a cursor points to the first value of the matching query.

  • You can get the key and model with key() and model().

  • You can move the cursor one item with next() and previous(), or directly to the start or end of the matching items with seekToFirst or seekToLast.

  • Each time the cursor moves, you need to check whether it is still valid with isValid().

Iterating with a cursor
db.find<User>().byIndex("lastName", "Doe").use { cursor -> (1)
    while (cursor.isValid()) {
        val model = cursor.model()
        println(model)
        cursor.next()
    }
}
1 use ensures to close the cursor when iteration ends.

As a sequence

You can easily transform a Cursor to a Sequence. Note that when you do, the cursor will be automatically closed when the sequence ends.

You can use either models() that creates a squence of models, or entries, that create a sequence of model and key pairs.

Iterating with a sequence
db.find<User>().byIndex("lastName", "Doe").models().forEach {
    println(it)
}

The sequence will only close when the last item has been collected!

The following code will not close the associated cursor and create a resource leak!

Creating a cursor leak
val first3 = db.find<User>().all()
    .models()
    .take(3) (1)
    .toList()
1 Because we take only the first 3 items, the last item of the cursor is never collected.

The correct way to do this is as such:

Getting the first 3 items without resource leak:
val first3 = db.find<User>().all().use { (1)
    it.models()
        .take(3)
        .toList()
}
1 use ensures to close the cursor when iteration ends.