Atomic operations

Describes other atomic operations like counter() or append()/prepend()

Counter

The counter() method allows you to atomically increment or decrement a document with numerical content. This is reflected by that it only accepts and returns a LongDocument. If the value is incremented or decremented depends on the delta given, and it is also possible to pass in an initial value or an expiration time.

// Increase the counter by 5 and set the initial value to 0 if it does not exist
Observable<LongDocument> doc = bucket.counter("id", 5);

The resulting document contains the new counter value. A very common use case is to implement a increasing AUTO_INCREMENT like counter, where every new user just gets a new id:

bucket
    .counter("user::id", 1, 1)
    .map(new Func1<LongDocument, String>() {
        @Override
        public String call(LongDocument counter) {
            return "user::" + counter.content();
        }
    })
    .flatMap(new Func1<String, Observable<JsonDocument>>() {
        @Override
        public Observable<JsonDocument> call(String id) {
            return bucket.insert(JsonDocument.create(id, JsonObject.empty()));
        }
    }).subscribe();

This code increases the counter by one, then maps the returned number onto a custom document ID (here the code prefixes user::). Afterwards, the insert method is executed with the generated id, and here a empty document content. Since a counter operation is atomic, the code is guaranteed to deliver different user IDs, even when called at the same time from multiple threads.

If the initial value is omittted, 0 is used. Note that the counter needs to be always greater or equal than zero, negative values are nto allowed. If you want to decrement a counter, make sure to set it to a higher positive value initially.

This means that if the document does not exist, the value will be always set to 0. If you want to set the counter to the delta initially, set both values:

// Increase the counter by 5 and set the initial value to 5 if it does not exist
Observable<LongDocument> doc = bucket.counter("id", 5, 5);

If you want to set an expiration time, you need to provide both the initial value and the expiration time. This is a constraint by the API, because just exposing the expiration time would be ambigous with the initial value (long and int).

// Increment by 5, initial 5 and 3 second expiration
Observable<LongDocument> doc = bucket.counter("id", 5, 5, 3);

Append & Prepend

Appending and prepending values to existing documents is also possible. Both the append and prepend operation are atomic, so they can be used without further synchronization.

Note: Both operations only work on binary documents, ideally strings or byte arrays. It does not work on JSON documents, because it doesn't do any further inspection. Applying one of those operations on a JSON document will render it invalid.

Also note that a Document needs to be created before values can be appended or prepended. Here is an example that creates a document and then appends a string to it:

bucket
    .insert(LegacyDocument.create("doc", "Hello, "))
    .flatMap(doc ->
        bucket.append(LegacyDocument.create("doc", "World!"))
    )
    .flatMap(bucket::get)
    .toBlocking()
    .forEach(doc -> System.err.println(doc.content()));

When executed, this prints Hello, World!.