如何根据旧值自动更新 RethinkDB 中的文档?

How to atomically update a document in RethinkDB based on old value?

假设我有如下文档架构:

{
  users: [{userid: 123, username: "foo"}, {userid: 234, username: "bar"}]
}

我想向 users 添加一个项目,其用户名等于给定用户名的 "uniquified" 版本。例如,如果我尝试将 {userid: 456, username: "baz"} 添加到上面的用户列表,它应该会成功,但如果我尝试将 {userid: 456, username: "foo"} 添加到上面,则应该添加 {userid: 456, username: "foo (1)"}

有没有办法在 RethinkDB 中使用原子更新来做到这一点?这是一个确定性的操作,所以理论上它应该是可能的吧?如果没有,是否有某种方法至少可以在插入期间检测到用户名冲突并简单地拒绝更新?

我知道我可以使用子查询,但结果似乎不是原子操作?我在文档中看到的所有子查询示例都在单独的 table.

上显示子查询

根据 consistency guarantees, you can combine get (not getAll or filter though) with update and have an atomic chain. Then, inside update, it's described,如果您使用子查询或某些不确定的东西,那么您就不是原子的,必须显式声明 nonAtomic 标志。

查询中最冗长的部分变成了增加计数的方式,因为您不想以多个 bar (1).

结尾

假设您已经提供以下内容,则应自动执行:

  • 文档的id,这里did = '3a297bc8-9fda-4c57-8bcf-510f51158f7f'
  • 用户名,此处uname = 'bar'
  • userid,这里uid = 345
var uname = 'baz';
var did = "3a297bc8-9fda-4c57-8bcf-510f51158f7f";
var uid = 345;
// not sure readMode is necessary here, it's described in consistency guarantees
//  but not shown in the example with get/update/branch
r.db('db').table('table', { readMode: 'majority' })
// use only get to ensure atomicity between get and update
.get(did)
// update will throw if you're not deterministic, i.e. it can't behave atomically
//  here we never address anything but the current document so it's OK
.update(function(doc) {
  // this retrieves the highest index in parentheses if any
  //  put in a var because 1/ we use the result twice 2/ it's kind of verbose...
  var matched = doc('users').map(function(user) {
    // regex is /^bar(?: \(([0-9]+)\))?$/
    //  with the only capturing group on the index itself
    return user('username').match(r.expr('').add('^', uname, '(?: \(([0-9]+)\))?$'))
  }).filter(function(match) {
    // remove all user items that didn't match (i.e. they're null)
    return match.typeOf().ne('NULL');
  }).map(function(match) {
    // check whether we are processing 'bar' or 'bar (N)'
    //  (no captured group = no '(N)' = pure 'bar' = set index at zero)
    return r.branch(
      match('groups').filter(function(group) {
        return group.typeOf().ne('NULL');
      }).count().gt(0),
      // wrap in { index } for the following orderBy
      { index: match('groups').nth(0)('str').coerceTo('number') },
      { index: 0 }
    );
  })
  // ensure the first item in the list is the highest index
  .orderBy(r.desc('index'));
  // now we can decide on what to add
  return r.branch(
    // if there were some matches, 'bar' exists already
    //  and we now have the highest index in the list
    matched.count().gt(0),
    // add 'bar' appended with ' (N)', having N = highest index + 1
    {
      users: doc('users').add([{
        userid: uid,
        username: r.expr(uname).add(
          ' (',
          matched.nth(0)('index').add(1).coerceTo('string'),
          ')'
        )
      }])
    },
    // else, just add the user as is
    { users: doc('users').add([{ userid: uid, username: uname }]) }
  );
});

希望对您有所帮助!