Как я могу обновить документ в rethinkdb (reql) по произвольному свойству?

У меня есть .itemId, полученный от третьей стороны, созданный не мной.

Мне нужно найти его в БД и обновить или вставить, если он не существует.

Я попытался использовать этот пример из поваренной книги: https://www.rethinkdb.com/docs/cookbook/javascript/#manipulating-documents

  const res = await this.r.table('products').filter({itemId: item.itemId})
    .limit(1)
    .replace(doc => {
      return this.r.branch(
        doc.eq(null),
        this.r.expr(item).merge({created_at: this.r.now()}),
        doc.merge(item).merge({updated_at: this.r.now()})
      )
    }, {
      returnChanges: true
    }).run(this.conn);


  if (res.replaced) {
    return res.changes[0].new_val;
  } else {
    return item; // doc was never inserted as everything in `res` is `0`.
  }

res.changes не определено, если документ не существует и если я ищу идентификатор после того, как его нет в базе данных. Он так и не был вставлен.

Есть ли способ упростить команду upsert() с учетом произвольного свойства объекта?


person chovy    schedule 02.04.2017    source источник


Ответы (1)


В предложении «else» вы должны выполнить запрос на вставку, а предложение ветки в вашем коде бесполезно (запрос никогда не вернет «null», поэтому элемент не будет «создан»)

Есть несколько способов сделать это: Лучший подход — использовать itemId (или r.uuid(itemId)) в качестве первичного ключа и выполнить вставку с условием конфликта.

Если вы не можете. Один из подходов - попытаться заменить, и если он ничего не заменил, вставьте:

this.r.table('products').filter({itemId: item.itemId})
.limit(1)
.replace(
    doc => doc.merge(item).merge({updated_at: this.r.now()}), 
    { returnChanges: true }
)
.do(res => res('replaced').eq(1).branch(
    res,
    r.table('products').insert(
        { ...item, created_at: this.r.now()}, 
        { returnChanges: true }
    )
))
.run()

Другой подход состоит в том, чтобы попытаться проверить, существует ли он, и использовать индекс для повышения:

this.r.table('products').filter({itemId: item.itemId})
.nth(0)
.default(null)
.do(res => 
    r.table('products').insert(
        { 
          ...item, 
          id: res('id').default(r.uuid()), 
          created_at: this.r.now()
        }, 
        { 
            returnChanges: true,
            conflict: (id, old, new) => 
                old.merge(item).merge({updated_at: this.r.now()})
        }
    )
))
.run()

Также, если вам это нужно, я рекомендую создать вторичный индекс для itemId и использовать «getAll» вместо «filter».

Эти подходы не помогут, если у вас есть высокая вероятность одновременного получения нескольких элементов с одним и тем же itemId, чтобы исправить это, вам нужно создать другую уникальную таблицу:

r.table('products_itemId')
 .insert(
    {itemId: item.itemId, id: r.uuid()},
    { returnChanges: true, conflict: (id, old, new) => old }
 )
 .do(res => 
     r.table('products').insert(
        { 
          ...item, 
          id: res('new_val')('id'),
          created_at: this.r.now()
        }, 
        { 
            returnChanges: true,
            conflict: (id, old, new) => 
                old.merge(item).merge({updated_at: this.r.now()})
        }
    )
))
.run()

Обратите внимание, что вам придется поддерживать удаления и обновления в поле itemId вручную.

person RonZ    schedule 02.04.2017
comment
ни один пример не работает. Я изменил спред на Object.assign(), теперь он выдает ошибку об отсутствующем операторе возврата для обоих. ReqlDriverCompileError: Anonymous function returned undefined. Did you forget a return? - person chovy; 04.04.2017
comment
Исправлено, я думаю, что оператор распространения изначально поддерживается на узле 6+. В любом случае замена его на Object.assign() является допустимым изменением. Часть обновления — это та, что находится в предложении конфликта. Я забыл вернуть (поскольку это только одно выражение, ключевое слово return не обязательно, но тогда вы не должны использовать фигурные скобки...), исправил это сейчас. - person RonZ; 04.04.2017