4

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.

mikemaccana
  • 110,530
  • 99
  • 389
  • 494

1 Answers1

5

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.

Etienne Laurin
  • 6,731
  • 2
  • 27
  • 31
  • Thanks! Do you mind if I ask you a couple of questions? Should conditional upserts always be done with insert & the upsert option? – mikemaccana Dec 11 '13 at 17:41
  • No, like you noticed, replace can be used too. A point replace is a replace following a get. – Etienne Laurin Dec 11 '13 at 18:59
  • Thanks! Is there any way to to an atomic conditional upsert? Eg, if any document matches, update the first match, if not, create the document? – mikemaccana Dec 12 '13 at 10:58
  • Also: is insert or replace preferable for this purpose? Sorry for all the questions but I couldn't find any pointers in the docs. – mikemaccana Dec 12 '13 at 12:07
  • No. Atomic operations in RethinkDB can only depend on and modify single documents. – Etienne Laurin Dec 12 '13 at 15:33
  • Sorry if I was unclear, I meant with a single document. Ignore anything else that matches (or err), just the first result. – mikemaccana Dec 12 '13 at 15:50