Sub-doc API - upsert auto-create document?

Hi, I’m just starting out with Couchbase and have a couple of questions about java sub-doc api, hope someone can help me.

Lets say I want to upsert a document with key “user:123” and JSON body
{
“age”: 42,
“tags”: [“tag1”, “tag2”]
}
If that document did not exist, I want it to be created.
But if it did exist, I want to set property “age” (override if it exists) to specified value, and add tags (add them to existing tag list or just create new list if they didn’t exist).

What is recommended way to do this via async API?

I like the idea of subdoc API, and I tried to create mutateIn() with upsert(“age”, 42, true) and arrayAppendAll(“tags”, [“tag1”, “tag2”]. But it only works if document exists, otherwise it throws exception.
So how do I upsert the document? Is it even possible to upsert doc and upsert attributes at the same time?
If it is not possible, do I have to lookup/get document before mutating it? it seems to defeat the purpose of “optimal traffic” in terms of data transfer during operations…

And another question - I want to store tags in user and want to be able to add new tags to user via sub doc api, but I want to keep this list filled with unique tags. So I tried to use subdoc api arrayAddUnique, but it throws exception if I add already existing tag - and what should I do with it? lookup in advance? just more operations… I guess it would be much better to just add item to array if it didn’t exist and do nothing if it did (no exception). I don’t want to use arrayAppend, because if its called multiple times for the same tag, it keeps adding it and duplicates will be created. I guess it would be great to have some “hashSetAdd” which would handle my case. But maybe there is some other way to do that?

Thanks in advance!

Hey @chotenvan, you mention that if you try to perform the subdoc operation and the doc doesn’t exist then an exception is thrown. Perhaps your application could perform the subdoc upsert and then if it encounters the exception then just perform a full doc insert() instead. This means that in the worst case you perform 2 separate operations and in the general case just 1, without the need to pre-fetch the document.

Regarding your second question about unique tags, your current approach does sound sensible but rather than looking up the document in advance you could just catch the exception it throws for existing elements and ignore it within your application code.

Hope this helps!

Currently MattC’s suggestion (attempt the subdoc upsert, retry with normal ADD on failure) is the best current approach.

In the next release (5.0) there will be a new MAKE_DOC flag you can pass to sub-document commands which will create the document if it doesn’t already exist.

@matt.carabine, @drigby, thanks a lot for your help!

Catching exception on new doc upsert sounds good, however its somewhat tricky when I do batch of such upsert operations via Observables in async API… Maybe I have to study Observables more, at the moment I don’t know how to chain different try-retry operations without creating some temp variables to track failed items. Is there any example in documentation for such batch chain?
Anyway, great to hear that 5.0 simplifies this process!

Here is example - how do I do retry?

 Observable.just("user:99999999991", "user:99999999992")
                .flatMap(key -> bucket.async().mutateIn(key).upsert("age", 42, true).execute())
                .onErrorResumeNext(error -> {
                    System.out.println(error);
                    // I would like to do code below - but I don't have document key here...
                    // nor it is possible to extract key from DocumentDoesNotExistException
                    return null;
                    //JsonObject json = JsonObject.empty();
                    //json.put("age", 42);
                    //return bucket.async().upsert(JsonDocument.create(key, json));
                })
                .toBlocking()
                .subscribe();

As I’ve mentioned in the code comments, there is no way to get key of failed document.
And even documentation https://developer.couchbase.com/documentation/server/current/sdk/java/async-programming.html in Error Handling section onErrorResumeNext sample uses hardcoded “id” in onErrorResumeNext… however this makes such function not usable for flatMap.
I guess, proper way would be to extract failed document id from exception, but I didn’t find how to do that.

As for tags, the problem with retry is that I add several tags in one operation - and if even one of them already exists, it will throw exception for arrayAddUnique but I can’t ignore it, because there are other tags in array which I planned to add… if there was “hashsetAdd” method, it would solve this problem )

Thank you!

Hi!

I’d like to know if the MAKE_DOC flag you mentioned has been implemented already.

I’m currently querying CB with an UPDATE N1QL query, which perform sub-doc operations. Has there been some changes with CB 5.0, which could help us perform an UPSERT with sub-doc operations?

Thank you!

It has been implemented in 5.0 - you’ll also need a version of the Couchbase Java SDK which supports 5.0 features.

Thank you, much appreciating it all! :smiley: