Как атомарно обновить документ в RethinkDB на основе старого значения? - PullRequest
0 голосов
/ 29 апреля 2020

Допустим, у меня есть схема документа, подобная следующей:

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

Я хочу добавить элемент к users с именем пользователя, равным "неунифицированной" версии данного имени пользователя. Например, если я попытаюсь добавить {userid: 456, username: "baz"} в список пользователей выше, это будет успешным, но если я попытаюсь добавить {userid: 456, username: "foo"} к вышеупомянутому, то вместо этого следует добавить {userid: 456, username: "foo (1)"}.

Есть ли способ сделать это с помощью обновления atomi c в RethinkDB? Это детерминированная операция c, поэтому теоретически это должно быть возможно, верно? Если нет, есть ли какой-нибудь способ, которым я могу хотя бы обнаружить конфликт имени пользователя во время вставки и просто отклонить обновление?

Я знаю, что могу использовать подзапросы, но похоже, что результатом будет не операция атома c? Все примеры подзапросов, которые я видел в документации, показывают подзапросы в отдельной таблице.

1 Ответ

0 голосов
/ 29 апреля 2020

В соответствии с гарантиями согласованности , вы можете объединить get (но не getAll или filter) с update и иметь цепочку атомов c. Затем, внутри update, описано , что если вы используете подзапросы или что-то, что не является детерминированным c, то вы не Atomi c и должны явно объявить флаг nonAtomic.

Самая многословная часть в запросе становится способом увеличения счетчика, так как вы не хотите заканчиваться несколькими bar (1).

Следующее должно вести себя атомарно, предполагая Вы уже предоставили:

  • идентификатор документа, здесь did = '3a297bc8-9fda-4c57-8bcf-510f51158f7f'
  • имя пользователя, здесь uname = 'bar'
  • идентификатор пользователя, здесь 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 }]) }
  );
});

Надеюсь, это поможет!

...