Search code examples
couchbasesql++spring-data-couchbase

Not able to deploy couchbase event function


I have a document "student" in bucket student-records where id is the documentd.

{
  "id":"101",
  "fname": "abc",
  "lname": "xyz",
  "rank": "1",
  "scholarShip": "",
  "grade": ""
}

My job is to find all student with rank 1 and then update "scholarship" and "grade" in respective docs.

I have created an event function in Couchbase as below

function OnUpdate(doc, meta) {
    log('docId', meta.id);
       try {
        var rankValue = SELECT rank FROM `student-records` USE KEYS ["id"];
        for (var rv of rankValue) {
            if (rv==1) {  
               UPDATE `student-records` USE KEYS ["id"] set scholarShip="100%", grade="A";
        }
     }
    } catch(e) { log(e); } 
}

while deploying this I am getting an error:

Deployment failed: Syntax error (7, 16) - Can not execute DML query on bucket "student-records"

while creating function I have declared:

source bucket => student-records

metadata bucket=> student-records-metadata


Solution

  • Let's walk through your question - intuitively I know the answer (writing to the source bucket (issue-A) and incorrect use of keys (issue-B)) - but we can improve your code a bit with a walk through to highlight both best practices and explain to you what you need. First let's assume you will have different document beside a student record, so add I added a "type" field. Below I show one sample record that I could put into your 'student-records' (of type=student, this is my added field).

    {  "id":"101", "fname": "abc", "lname": "xyz", 
    "rank": "1", "scholarShip": "", "grade": "", 
    "type": "student" }
    

    Next since couchbase works better limiting the number of buckets to about 10 buckets (and 30 buckets for version 6.5 which is in beta). We don't really want a whole bunch of different 'metadata' buckets for individual eventing functions so I typically make a common bucket called 'meta' for all my eventing functions. Along the same lines if you think about it, by adding a type field you can store many different types of data in a bucket so why don't we also rename student-records to a general purpose school bucket. So the bucket school can hold multiple types type=students type=teachers, type=classroom, type=schedule, etc. etc..

    So I create two buckets 1) school and 2 meta then I poked in a test record via the QUERY editor of the UI.

    INSERT INTO `school` ( KEY, VALUE ) VALUES
    (
       "student101",
       {  "id":"101", "fname": "abc", "lname": "xyz", 
          "rank": "1", "scholarShip": "", "grade": "", "type": "student" }
    )
    

    To help/allow our queries on specific types let's build a N1QL index in the QUERY editor of the UI

    CREATE INDEX adv_type ON `school`(`type`);
    

    Now let's look at our test data in the QUERY editor of the UI

    SELECT * FROM `school` WHERE type = "student";
    

    The expected JSON data is returned

    [
      {
        "school": {
          "fname": "abc",
          "grade": "",
          "id": "101",
          "lname": "xyz",
          "rank": "1",
          "scholarShip": "",
          "type": "student"
        }
      }
    ]
    

    It's always good practice to test a N1QL before putting it in Eventing so let's do a dry run in the UI's QUERY editor. Please look at the KEY and how it is constructed as a concatenation of both the 'type' and the 'id' so we have an existing KEY - this relates to issue-B where you used the string "id" in your original Eventing function.

    UPDATE `school` USE KEYS ["student101"] 
    set scholarShip="100%", grade="A" WHERE type="student";
    

    Let's look at the result again

    SELECT * FROM `school` WHERE type = "student";
    

    The expected JSON data is returned

    [
      {
        "school": {
          "fname": "abc",
          "grade": "A",
          "id": "101",
          "lname": "xyz",
          "rank": "1",
          "scholarShip": "100%",
          "type": "student"
        }
      }
    ]
    

    Now let's put the data back to the way it was (I own't show the results)

    UPDATE `school` USE KEYS ["student101"] 
    set scholarShip="", grade="" WHERE type="student";
    

    Note, since we're using KEYS I did not need to use the WHERE type="student" clause in the prior UPDATE statements but it highlights how to discriminate when you have multiple types in the same bucket.

    Okay so now it's time to make an Eventing function but at this point we have to understand some aspects about Eventing.

    • For versions of Couchbase though 6.0.X you can not write back to the source bucket via Eventing.
    • For 6.5 (which is a beta preview) you can write back to the source bucket via the alias'd KV map (but not via N1QL).

    The reason for this limitation is that you can create cyclic dependancies triggering endless recursive Eventing operations and it a very hard to detect such occurrences in N1QL as opposed to a understanding a direct operation from an Eventing function on an aliased KV map.

    So moving forward, implementing your actual Eventing function, you have two choices here to make your actual Eventing function use 6.5 query with N1QL but poke the bits back via KV or create a destination bucket. I'll assume you are using 6.5-beta in this case when we define our function we want a) source bucket of 'school', b) meta data bucket of 'meta', and c) a bucket alias of 'school' for bucket 'school' set to 'read & write' as follows:

    setup screen

    Note when making aliases do not use a '-' character as it is illegal javascript variable name and will complain when you try to deploy your Eventing function.

    And the eventing code here we don't even need to use N1QL we use the exposed Javascript KV map (I used the alias 'school' for the bucket 'school' this alias is a Javascript map that exposes the bucket by its KEYS.

    function OnUpdate(doc, meta) {
        log('docId', meta.id);
        if (doc.type != "student") return;
        if (doc.rank == 1) {
            try {
                doc.grade = "A";
                doc.scholarShip = "100%";
                school[meta.id] = doc;
            } catch(e) { 
                log(e); 
            } 
        }
    }
    

    Now if you deploy the function (for Everything) you will see that the only record you have is automatically updated by Eventing since the record had a rank of 1.

    Run the select query and see for yourself e.g.

    SELECT * FROM `school` WHERE type = "student";
    

    If Eventing is working you can put the data back via our prior UPDATE but since that generates a mutation Eventing will immediate alter it back to the processed state (rest assured the update actually works but since rank=1 it is reprocessed because Eventing is running and deployed and it picked up the mutation you created in the QUERY UI):

    UPDATE `school` USE KEYS ["student101"] 
    set scholarShip="", grade="" WHERE type="student";
    

    Of course each time eventing runs is will write to a log (accessible via the UI's Eventing tab for the your Function) information like the following:

    2019-12-12T15:30:18.153-07:00 [INFO] "docId" "student101"
    

    If you want help with N1QL or implementing a pre-6.5-beta solution, feel free to contact me directly.