Question

I know rethinkDB's insert() has an upsert option. Eg, from the docs:

rethinkdb.table('marvel').insert(
  { superhero: 'Iron Man', superpower: 'Arc Reactor' },
  {upsert: true, return_vals: true}
).run(conn, callback)

But say there might be existing record for {superhero: 'Iron Man'} with an incorrect or out of date superpower, and I wanted to either insert { superhero: 'Iron Man', superpower: 'Arc Reactor' } or update any existing {superhero: 'Iron Man'} record?

In short: how can I upsert so that existing records are updated only if particular fields are matched?

Edit: Seems like this is done with .replace():

Here's the important bit from the replace() docs:

You can have new documents inserted if you do a point-replace on a key that isn't in the table

I'm not sure what 'point-replace' means - but I assume it just means replace.

var latestDoc = { superhero: 'Iron Man', superpower: 'Arc Reactor' }
var mergeOrCreate = function(existingDoc) {
  if ( existingDoc ) {
    return existingDoc.merge(latestDoc)
  } else {
    return latestDoc
  }
}
rethinkdb.table('marvel')
  .filter({ superhero: 'Iron Man'})
  .replace(mergeOrCreate),
).run(conn, callback)

However in testing, the 'mergeOrCreate' function is being given another function, rather than an actual doc or a cursor, as an argument.

Was it helpful?

Solution

Your first code block is the answer to your first question. However, it assumes that superhero is the primary key for your table.

You can set the primary key of a table when creating it:

r.tableCreate('marvel', {primaryKey: 'superhero'}).run(conn, cb)

Your second code block has two mistakes.

First, the mergeOrCreate function does not work because if statements are evaluated client-side. You must use r.branch to create the equivalent of an if statements:

var mergeOrCreate = function(existingDoc) {
  return r.branch(existingDoc,    /* if */
    existingDoc.merge(latestDoc), /* then */
    latestDoc)                    /* else */
}

Second, get does not support the same syntax as filter. Its argument should be the value of the primary key. If the primary key is superhero, then:

r.table('marvel')
  .get('Iron Man')

Finally, if superhero is not the primary key, you have to write a different kind of query. There could be zero, one or many documents with the same superhero. Here is one way to write it:

r.table('marvel')
  .insert(
   r.table('marvel')
     .filter({ superhero: 'Iron Man' })
     .coerceTo('array')
     .do(function(docs){
        return r.branch(
          docs,
          docs.merge(latestDoc),
          latestDoc)
        }),
   { upsert: true })
  .run(conn, cb);

Note that because this query depends on more than one document, it is not atomic.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top