Advanced Keyspace Accessors

  • Capella Operational
    Use Advanced Keyspace Accessors to access advanced Key Value functionality.

    Advanced Keyspace Accessors use the same bucket bindings defined in the handler as Basic Keyspace Accessors, but they expose a larger set of options and operators. These operators can be used to:

    • Set or retrieve document expirations in the Data Service

    • Solve race conditions through CAS

    • Manipulate KV items under high contention using JavaScript inside Eventing Functions

    • Perform distributed atomic counter operations

    Couchbase supports the following:

    Advanced GET Operation

    result = couchbase.get(binding, meta)

    The GET operation lets you read a document with metadata from your bucket. It also allows any subsequent operations to use CAS and check or modify the expiration date of the document.

    Example

    Operation
    function OnUpdate(doc, meta) {
        log('input doc', doc);
        log('input meta', meta);
        // could be the same or different
        var new_meta = {"id": "test_adv_get::1"};
        var result = couchbase.get(src_col, new_meta);
        if (result.success) {
            log('success adv. get: result', result);
        } else {
            log('failure adv. get: id', new_meta.id, 'result',result);
        }
    }

    You can use the optional parameter { "cache": true } with the GET operation.

    Advanced INSERT Operation

    result = couchbase.insert(binding, meta, doc)

    The INSERT operation lets you create a new document in your bucket. It also lets you specify an expiration date (or TTL) for the document.

    The operation fails if the document with the key you specified already exists.

    Example

    Operation
    function OnUpdate(doc, meta) {
        log('input meta', meta);
        log('input doc', doc);
        // Can be the same or different
        var new_meta = {"id": "test_adv_insert:1"};
        // (Optional) Set an expiry time of 60 seconds in the future
        // new_meta.expiry_date = new Date(Date.now() + 60 * 1000);
        var new_doc = doc;
        new_doc.random = Math.random();
        var result = couchbase.insert(src_col, new_meta, new_doc);
        if (result.success) {
            log('success adv. insert: result', result);
        } else {
            log('failure adv. insert: id', new_meta.id, 'result', result);
        }
    }
    Results
    {
        "meta": {
            "id": "test_adv_insert:1",
            "cas": "1610041053310025728"
        },
        "success": true
    }
    
    {
        "error": {
            "code": 272,
            "name": "LCB_KEY_EEXISTS",
            "desc": "The document key already exists in the server.",
            "key_already_exists": true
        },
        "success": false
    }

    You can use the optional parameter { "self_recursion": true } with the INSERT operation.

    Advanced UPSERT Operation

    result = couchbase.upsert(binding, meta, doc)

    The UPSERT operation lets you update an existing document in your bucket. It also lets you specify an expiration date (or TTL) for the document.

    If no documents exist in your bucket, the operation creates a new document with the key you specified.

    The operation does not allow you to specify CAS.

    Example

    Operation
    function OnUpdate(doc, meta) {
        log('input meta', meta);
        log('input doc', doc);
        // Can be the same or different
        var new_meta = {"id": "test_adv_upsert:1"}; // If supplied, the CAS is ignored
        // (Optional) Set an expiry time of 60 seconds in the future
        // new_meta.expiry_date = new Date(Date.now() + 60 * 1000);
        var new_doc = doc;
        new_doc.random = Math.random();
        var result = couchbase.upsert(src_col, new_meta, new_doc);
        if (result.success) {
            log('success adv. upsert: result', result);
        } else {
            log('failure adv. upsert: id', new_meta.id, 'result', result);
        }
    }

    You can use the optional parameter { "self_recursion": true } with the UPSERT operation.

    Advanced REPLACE Operation

    result = couchbase.replace(binding, meta, doc)

    The REPLACE operation lets you replace an existing document in your bucket with a new document. It also lets you specify the following:

    • An expiration date (or TTL) for the document

    • A CAS value to be used as a pre-condition for the operation

    Example

    Operation
    function OnUpdate(doc, meta) {
        log('input meta', meta);
        log('input doc', doc);
    
        var mode = 3; // 1-> no CAS, 2-> mismatch in CAS, 3-> good CAS
    
        // Set up the operation, make sure there is a document to be replaced, ignore any errors
        couchbase.insert(src_col,{"id": "test_adv_replace:10"},{"a:": 1});
    
        var new_meta;
        if (mode === 1) {
            // If no CAS is passed, the operation succeeds
            new_meta = {"id": "test_adv_replace:10"};
            // (Optional) Set an expiry time of 60 seconds in the future
            // new_meta.expiry_date = new Date(Date.now() + 60 * 1000);
        }
        if (mode === 2) {
            // If a non-matching CAS is passed, the operation fails
            new_meta = {"id": "test_adv_replace:10", "cas": "1111111111111111111"};
        }
        if (mode === 3) {
            // If the current or matching CAS is passed, the operation succeeds
            var tmp_r = couchbase.get(src_col, {"id": "test_adv_replace:10"});
            if (tmp_r.success) {
                // Use the current CAS to read through the couchbase.get(...) operation
                new_meta = {"id": "test_adv_replace:10", "cas": tmp_r.meta.cas};
            } else {
                log('Cannot replace a non-existing key. Recreate the key and rerun the operation.', "test_adv_replace:10");
                return;
            }
        }
        var new_doc = doc;
        new_doc.random = Math.random();
        var result = couchbase.replace(src_col, new_meta, new_doc);
        if (result.success) {
            log('success adv. replace: result', result);
        } else {
            log('failure adv. replace: id', new_meta.id, 'result', result);
        }
    }
    Results
    {
        "meta": {
            "id": "test_adv_replace:10",
            "cas": "1610130177286144000"
        },
        "success": true
    }
    
    {
        "error": {
            "code": 272,
            "name": "LCB_KEY_EEXISTS",
            "desc": "The document key exists but it has a CAS value that is different from the specified value.",
            "cas_mismatch": true
        },
        "success": false
    }

    You can use the optional parameter { "self_recursion": true } with the REPLACE operation.

    Advanced DELETE Operation

    result = couchbase.delete(binding, meta)

    The DELETE operation lets you delete a document in your bucket. You can use the document key to specify the document you want to delete.

    This operation also lets you specify a CAS value to be matched as a pre-condition to proceed with the operation.

    Example

    Operation
    function OnUpdate(doc, meta) {
        log('input meta', meta);
        log('input doc', doc);
    
        var mode = 4; // 1-> no CAS, 2-> mismatch in CAS, 3-> good CAS, 4-> no CAS key
    
        // Set up the operation, make sure there is a document to be deleted, ignore any errors
        couchbase.insert(src_col,{"id": "test_adv_delete:10"},{"a:": 1});
    
        var new_meta;
        if (mode === 1) {
            // If no CAS is passed, the operation succeeds
            new_meta = {"id": "test_adv_delete:10"};
            // (Optional) Set an expiry time of 60 seconds in the future
            // new_meta.expiry_date = new Date(Date.now() + 60 * 1000);
        }
        if (mode === 2) {
            // If a non-matching CAS is passed, the operation fails
            new_meta = {"id": "test_adv_delete:10", "cas": "1111111111111111111"};
        }
        if (mode === 3) {
            // If the current or matching CAS is passed, the operation succeeds
            var tmp_r = couchbase.get(src_col,{"id": "test_adv_delete:10"});
            if (tmp_r.success) {
                // Use the current CAS to read through the couchbase.get(...) operation
                new_meta = {"id": "test_adv_delete:10", "cas": tmp_r.meta.cas};
            } else {
                log('Cannot delete a non-existing key. Recreate the key and rerun the operation.',"test_adv_delete:10");
                return;
            }
        }
        if (mode === 4) {
            // Remove so that we have: no such key
            delete src_col["test_adv_delete: 10"]
            new_meta = {"id": "test_adv_delete:10"};
        }
        var result = couchbase.delete(src_col, new_meta);
        if (result.success) {
            log('success adv. delete: result', result);
        } else {
            log('failure adv. delete: id', new_meta.id, 'result', result);
        }
    }
    Results
    {
        "meta": {
            "id": "key::10",
            "cas": "1609374065129816064"
        },
        "success": true
    }
    
    {
        "error": {
            "code": 272,
            "name": "LCB_KEY_EEXISTS",
            "desc": "The document key exists with a CAS value different than the specified value",
            "cas_mismatch": true
        },
        "success": false
    }
    
    {
        "error": {
            "code": 272,
            "name": "LCB_KEY_ENOENT",
            "desc": "The document key does not exist on the server",
            "key_not_found": true
        },
        "success": false
    }

    Advanced INCREMENT Operation

    result = couchbase.increment(binding, meta)

    The INCREMENT operation lets you increment the count field in a specific document.

    For example, the document can have the structure { count: 23 }, where 23 is the example counter value.

    If the specified counter document does not exist, the operation creates a new document with a count value of 0. If the count value is 0, the first returned value is 1.

    The INCREMENT operation cannot manipulate full document counters because of limitations in the KV engine API.

    Example

    Operation
    function OnUpdate(doc, meta) {
        log('input meta', meta);
        log('input doc', doc);
    
        // The operation creates a doc.count if it does not already exist
        var ctr_meta = {"id": "my_atomic_counter:1" };
        var result = couchbase.increment(src_col, ctr_meta);
        if (result.success) {
            log('success adv. increment: result', result);
        } else {
            log('failure adv. increment: id', ctr_meta.id, 'result', result);
        }
    }

    Advanced DECREMENT Operation

    result = couchbase.decrement(binding, meta)

    The DECREMENT operation lets you decrement the count field in a specific document.

    For example, the document can have the structure { count: 23 }, where 23 is the example counter value.

    If the specified counter document does not exist, the operation creates a new document with a count value of 0. If the count value is 0, the first returned value is -1.

    The DECREMENT operation cannot manipulate full document counters because of limitations in the KV engine API.

    Example

    Operation
    function OnUpdate(doc, meta) {
        log('input meta', meta);
        log('input doc', doc);
    
        // The operation creates a doc.count if it does not already exist
        var ctr_meta = {"id": "my_atomic_counter:1" };
        var result = couchbase.decrement(src_col, ctr_meta);
        if (result.success) {
            log('success adv. decrement: result', result);
        } else {
            log('failure adv. decrement: id', ctr_meta.id, 'result', result);
        }
    }

    Advanced TOUCH Operation

    Couchbase Server 7.6

    result = couchbase.touch(binding, meta)

    The TOUCH operation lets you modify the expiration time of a document without the need to access that document first.

    You can use this operation if your application does not need to access the cluster when handling a user session.

    Example

    Operation
    function OnUpdate(doc, meta) {
        log('input meta', meta);
        log('input doc', doc);
    
        var expiry = new Date();
        expiry.setSeconds(expiry.getSeconds() + 10);
    
        var req = {"id": "doc1", "expiry_date": expiry};
        var result = couchbase.touch(dst_bucket, req);
        if (result.success) {
            log('success adv. touch: result', result);
        } else {
            log('failure adv. touch: id', req.id, 'result', result);
        }
    }
    Results
    {
      "meta": {
        "id": "doc1",
        "cas": "1708978502129614848"
      },
      "success": true
    }
    
    {
      "error": {
        "code": 1,
        "name": "LCB_KEY_ENOENT",
        "desc": "The document key does not exist on the server",
        "key_not_found": true
      },
      "success": false
    }

    Sub-Document MUTATEIN Operation

    Couchbase Server 7.6

    result = couchbase.mutateIn(binding, meta, subdoc_operation_array, options)

    Sub-Document MUTATEIN operations let you modify only parts of a document instead of the entire document. This makes them faster and more efficient than full-document operations like REPLACE and UPSERT.

    By default, a MUTATEIN operation does not modify Extended Attributes (XATTRs). To modify a document’s XATTRs, you must:

    • Pass xattrs as the third argument of your OnUpdate function.

    • Pass { "xattrs": true } in the subdoc_operation_array argument of your OnUpdate function.

    Sub-Document array operations do not have concurrency issues and can be performed without checking CAS.

    Examples

    Operation
    function OnUpdate(doc, meta) {
        var meta = {"id": meta.id};
        couchbase.mutateIn(dst_bucket, meta, [
            couchbase.MutateInSpec.insert("testField", "insert")
        ]);
        couchbase.mutateIn(dst_bucket, meta, [
            couchbase.MutateInSpec.replace("testField", "replace")
        ]);
        couchbase.mutateIn(dst_bucket, meta, [
            couchbase.MutateInSpec.remove("testField")
        ]);
    }
    Operation
    function OnUpdate(doc, meta) {
        var meta = {"id": meta.id};
        couchbase.mutateIn(dst_bucket, meta, [
            couchbase.MutateInSpec.upsert("arrayTest", []),
            couchbase.MutateInSpec.arrayAppend("arrayTest", 2),
            couchbase.MutateInSpec.arrayPrepend("arrayTest", 1),
            couchbase.MutateInSpec.arrayInsert("arrayTest[0]", 0),
            couchbase.MutateInSpec.arrayAddUnique("arrayTest", 3)
        ]);
    };
    Operation
    function OnUpdate(doc, meta, xattrs) {
        var meta = {"id": meta.id};
    
        // INSERT XATTR
        couchbase.mutateIn(dst_bucket, meta, [
            couchbase.MutateInSpec.insert("testField", "insert", {"xattrs": true})
        ]);
    
        // UPSERT XATTR
        couchbase.mutateIn(dst_bucket, meta, [
            couchbase.MutateInSpec.upsert("testField", "upsert", {"xattrs": true})
        ]);
    
        // REPLACE XATTR
        couchbase.mutateIn(dst_bucket, meta, [
            couchbase.MutateInSpec.replace("testField", "replace", {"xattrs": true})
        ]);
    
        // REMOVE XATTR
        couchbase.mutateIn(dst_bucket, meta, [
            couchbase.MutateInSpec.remove("testField", {"xattrs": true})
        ]);
    
        // ARRAY OPERATIONS WITH XATTR
        couchbase.mutateIn(dst_bucket, meta, [
            couchbase.MutateInSpec.upsert("arrayTest", [], {"xattrs": true}),
            couchbase.MutateInSpec.arrayAppend("arrayTest", 2, {"xattrs": true}),
            couchbase.MutateInSpec.arrayPrepend("arrayTest", 1, {"xattrs": true}),
            couchbase.MutateInSpec.arrayInsert("arrayTest[0]", 0, {"xattrs": true}),
            couchbase.MutateInSpec.arrayAddUnique("arrayTest", 3, {"xattrs": true})
        ]);
    };

    Sub-Document LOOKUPIN Operation

    Couchbase Server 7.6.2

    result = couchbase.lookupIn(binding, meta, subdoc_array, options)

    Sub-Document LOOKUPIN operations let you search for a specific field in a document without having to search and retrieve the entire document.

    By default, a LOOKUPIN operation does not fetch Extended Attributes (XATTRs). To fetch a document’s XATTRs, you must:

    • Pass xattrs as the third argument of your OnUpdate function.

    • Pass { "xattrs": true } in the subdoc_array argument of your OnUpdate function.

    The subdoc_array argument contains one or more of the following couchbase.lookupIn.get specs:

    • <subdoc_path>, which is the key of the subdocument you want to fetch.

    • { "xattrs": true }, if you want to fetch a document’s XATTRs.

    • { "doc":[{ "value”: <subdoc_value_0>, ”success”: <bool> }, { "value”: <subdoc_value_1>, ”success”:<bool>} ], ”meta”: <meta>, ”success”: <overall fetch operation success bool> }, which returns results.

    • result.doc[i].value, which gives you access to the i-th value of the subdocument. If there’s an error in fetching the i-th subdoc_path, the result.doc[i].value is undefined.

    Example

    Operation
    function OnUpdate(doc, meta, xattrs) {
      var meta = {"id": meta.id};
      var result = couchbase.lookupIn(dst_bucket, meta, [
        couchbase.LookupInSpec.get("<subdoc_path_0>", {"xattrs": true}),
        couchbase.LookupInSpec.get("<subdoc_path_1>", {"xattrs": true})
      ]);
      var value_0 = result.doc[0].value;
      var value_1 = result.doc[1].value;
    }

    Eventing Functions that Listen to Multiple Collections

    You can use the wildcard * in an Eventing Function’s scope or collection to listen to multiple collections.

    If the binding used by the Advanced Keyspace Accessor also contains a wildcard * for its scope or collection, you must use the additional meta.keyspace parameter.

    The following example includes a meta.keyspace parameter that specifies the keyspace in which the INSERT operation is to take place:

    Example

    Operation
    couchbase.insert(
        src_col, {
            "id": id_str,
            "keyspace": {
                "bucket_name": "bkt01",
                "scope_name": "scp01",
                "collection_name": "col01"
            }
        },
        some_doc
    )

    See the multiCollectionEventing example for a detailed example of Eventing Functions that listen to multiple collections.

    Optional Parameters

    Optional { "cache": true } Parameter

    You can use an optional third parameter { "cache": true } to enable a bucket backed cache to hold the documents for one second. This cache exists on each Eventing node and is shared across all Eventing Functions in the same node.

    The cache has "read your own write" (RYOW) semantics. Writing and then reading the same document with { "cache": true } always retrieves the value that has just been written.

    This parameter loads near static data from the Data Service, where every mutation needs external data to drive the business logic of Eventing Functions. The performance of this operator is usually 18 to 25 times faster than reading data directly from the Data Service.

    This parameter can be used with the GET advanced operation.

    Example using the optional { "cache": true } parameter

    Operation
    function OnUpdate(doc, meta) {
        log('input doc', doc);
        log('input meta', meta);
        // Can be the same or different
        var new_meta = { "id": "test_adv_get::1" };
        var result = couchbase.get(src_col, new_meta, { "cache": true });
        if (result.success) {
            log('success adv. get: result', result);
        } else {
            log('failure adv. get: id', new_meta.id, 'result',result);
        }
    }
    Results
    {
        "doc": {
            "id": 1,
            "type": "test_adv_get"
        },
        "meta": {
            "id": "test_adv_get::1",
            "cas": "1610034762747412480",
            "datatype": "json"
        },
        "success": true
    }
    
    {
        "doc": {
            "a": 1,
            "random": 0.09799092443129842
        },
        "meta": {
            "id": "test_adv_insert:1",
            "cas": "1610140272584884224",
            "expiry_date": "2021-01-08T21:12:12.000Z",
            "datatype": "json"
        },
        "success": true
    }
    
    {
        "error": {
            "code": 272,
            "name": "LCB_KEY_ENOENT",
            "desc": "The document key does not exist on the server",
            "key_not_found": true
        },
        "success": false
    }

    Optional { "self_recursion": true } Parameter

    Couchbase Server 7.6

    You can use the optional fourth parameter { "self_recursion": true } to prevent the suppression of recursive source bucket mutations and to process the mutations that you have just created.

    If you do not add { "self_recursion": true } to your operation, all source bucket mutations are suppressed.

    This parameter can be used with the INSERT, UPSERT, and REPLACE advanced operations.

    Example using the optional parameter { "self_recursion": true }

    Operation
    function OnUpdate(doc, meta) {
        if (!doc.count) {
            doc = { "count": 1, "id": meta.id };
            meta.id = meta.id + "_test";
            couchbase.insert(src, meta, doc, { "self_recursion": true });
            return;
        }
    
        if (doc.count < 3) {
            doc.count++
            couchbase.upsert(src, meta, doc, { "self_recursion": true });
            return;
        }
    
        if (doc.count < 6) {
            doc.count++;
            couchbase.replace(src, meta, doc, { "self_recursion": true });
            return;
        }
    
        couchbase.delete(src, { "id": meta.id });
        couchbase.delete(src, { "id": doc.id });
    }

    Return Values

    Value Type Description

    binding

    string

    The name of the binding that references the target bucket.

    For the Advanced GET operation, the binding can have an access level of read or read/write.

    For all other operations, the binding must have an access level of read/write.

    meta

    Object

    The positional parameter that represents the metadata of the operation.

    meta.id

    string

    The key of the document to be used in the operation. This is a mandatory parameter that must be a JavaScript string.

    meta.keyspace

    Object

    The keyspace of the document to be used for the operation.

    Must be in the JavaScript format "keyspace": { "bucket_name": string, "scope_name": string, "collection_name": string }.

    meta.cas

    string

    (Optional) Specifies the CAS value to be used as a pre-condition for the operation.

    If the CAS value of the document does not match the CAS value specified in this field, the operation fails and sets the parameter cas_mismatch to true in the error return object.

    meta.expiry_date

    Date

    (Optional) Sets the expiry time for the document. If specified, must be in the JavaScript format Date.

    doc

    string, number, boolean, null, Object, or Array

    The document content of the operation.

    result

    Object

    Indicates the success or failure of the operation.

    If the operation is successful, it returns the data that was fetched. If the operation fails, it returns the details of the error.

    result.success

    boolean

    Indicates if the operation is successful or not. This field is always present in the return object.

    result.meta

    Object

    Contains metadata about the object that was fetched. This field is only present is the operation is successful.

    If the specified key is not present in the bucket, the operation fails and returns key_not_found in the error object.

    result.meta.id

    string

    The key of the document fetched by the operation.

    result.meta.cas

    string

    The CAS value of the document fetched by the operation.

    result.meta.expiry_date

    Date

    The expiration date of the document. This field is only present if an expiration is set on the document.

    result.meta.datatype

    string

    Indicates whether the document is json or binary.

    result.doc

    string, number, boolean, null, Object, or Array

    Returns the content of the requested document if the operation is successful.

    result.error

    Object

    Returns an error if the operation fails.

    result.error.cas_mismatch

    boolean

    If true, this field indicates that the operation failed because a CAS value was not specified or because the CAS value on the object did not match the CAS value in the request.

    result.error.key_not_found

    boolean

    If true, this field indicates that the operation failed because the specified key did not exist in the bucket.

    result.error.key_already_exists

    boolean

    If true, this field indicates that the operation failed because the specified key already exists in the bucket.

    result.error.code

    number

    Represents the SDK error code that triggered the operation to fail. Usually returns an internal numeric code.

    result.error.name

    string

    Indicates the error that the SDK triggered and that caused the operation to fail.

    result.error.desc

    string

    A description of the error. This description can be used for diagnostics and logging, and can change over time. Programming logic should not be tied to the specific contents of this field.

    exceptions

    -

    Indicates errors through the error object in the return value. Exceptions are only thrown during system failure conditions.

    See Also