Search code examples
rethinkdbrethinkdb-javascript

Apply delta values on nested fields


Suppose I have record like this:

{
    id: 1,
    statistics: {
        stat1: 1,
        global: {
            stat2: 3
        },
        stat111: 99
    }
}

I want to make update on record with object:

{
    statistics: {
        stat1: 8,
        global: {
            stat2: 6
        },
        stat4: 3
    }
}

And it should be added to current record as delta. So, the result record should looks like this:

{
    id: 1,
    statistics: {
        stat1: 9,
        global: {
            stat2: 9
        },
        stat4: 3,
        stat111: 99
    }
}

Is it possible to make this with one query?


Solution

  • Do you want something generic or something specific? Specific is easy, this is the generic case:

    const updateValExpr = r.expr(updateVal);
    const updateStats = (stats, val) => val
        .keys()
        .map(key => r.branch(
            stats.hasFields(key), 
            [key, stats(key).add(val(key))], 
            [key, val(key)]
        ))
        .coerceTo('object')
    
    r.table(...)
     .update(stats => 
          updateStats(stats.without('global'), updateValExpr.without('global'))
              .merge({ global: updateStats(stats('global'), updateValExpr('global'))
     )
    

    There might be some bugs here sincce it's untested but the solution key point is the updateStats function, the fact that you can get all the keys with .keys() and that coerceTo('object') transforms this array: [['a',1],['b',2]] to this object: { a: 1, b: 2 },

    Edit: You can do it recursively, although with limited stack (since you can't send recursive stacks directly, they resolve when the query is actually built:

    function updateStats(stats, val, stack = 10) { 
        return stack === 0 
            ? {} 
            : val
               .keys()
               .map(key => r.branch(
                    stats.hasFields(key).not(), 
                    [key, val(key)],
                    stats(key).typeOf().eq('OBJECT'),
                    [key, updateStats(stats(key), val(key), stack - 1)], 
                    [key, stats(key).add(val(key))]
                )).coerceTo('object')
    }
    
    r.table(...).update(row => updateStats(row, r(updateVal)).run(conn)
    
    // test in admin panel
    updateStats(r({
        id: 1,
        statistics: {
            stat1: 1,
            global: {
                stat2: 3
            },
            stat111: 99
        }
    }), r({
        statistics: {
            stat1: 8,
            global: {
                stat2: 6
            },
            stat4: 3
        }
    }))