Сохранять один или несколько документов, но обновлять только в том случае, если существующий документ соответствует условию - PullRequest
0 голосов
/ 06 октября 2018

Прочитав кучу документации и примеров драйверов C ++ (например, 1 , 2 ), я не могу собрать способ достижения своей цели с помощью драйвера C ++.

У меня есть коллекция документов со следующей структурой:

{
   _id : int64_t // Supplied by me manually
   url : string
   status : int
   date : int
}

Я хочу вставить новый документ.Однако, если документ с таким же _id уже существует (что означает, что его url такой же, потому что мой _id является хешем url), я хочу обновить его следующим образом.Пусть existing_doc будет документом с тем же _id, уже имеющимся в БД, и new_doc будет документом, который я отправляю в MongoDB:

  1. Обновите поле date поля existing_doc только если existing_doc[status] было x (некоторая целочисленная константа).
  2. Обновлять поле status поля existing_doc, только если new_doc[status] было y (некоторая другая константа).

Бонусные баллы, если можно выполнить массовую операцию (кучу разных new_doc с), но любые советы о том, как добиться этой логики, будут оценены.

1 Ответ

0 голосов
/ 26 марта 2019

Я не смог найти простой способ, используя операторы обновления полей MongoDB или в других ответах .

Но одна возможность - выполнить bulk_write с двумя операциями:

  1. Одна операция обновления для обновления existing_doc[date], когда existing_doc[status] равен x или для вставки новых документов (условие 1)
  2. Одна операция обновления для обновления existing_doc[status] выполняется, когда new_doc[status] равно y (условие 2)

Для условие 1 мы можем выполнить один upsert operation запрос к id и status=x:

  1. При получении документа обновляется date (оператор $set), поскольку это означает, что existing_doc[status] было x
  2. Если документ не получен, выполняется попытка вставки (оператор $setOnInsert) и могут произойти две вещи:
    • Документ вставлен правильно -> это был новый документ
    • Ошибка дублированного ключаброшен -> документ с таким id уже существовал, но его status не был x, так что нечегообновление

Условие 2 проще, поскольку входные данные уже сообщают нам, нужно ли нам выполнить обновление или мы можем его пропустить.

Следующий код использует bulk_write для выполнения вышеуказанных операций:

#include <iostream>

#include <bsoncxx/builder/stream/document.hpp>
#include <bsoncxx/json.hpp>

#include <mongocxx/client.hpp>
#include <mongocxx/exception/bulk_write_exception.hpp>
#include <mongocxx/instance.hpp>
#include <mongocxx/uri.hpp>

using bsoncxx::builder::basic::kvp;
using bsoncxx::builder::basic::make_document;
using bsoncxx::builder::stream::document;
using bsoncxx::builder::stream::finalize;

// Status constants
int kStatus_X = 1234;
int kStatus_Y = 6789;

// Helper method to retrieve the document (in json format) if exists
std::string retrieveJsonDocById(mongocxx::collection& coll, const std::string& id)
{
    bsoncxx::stdx::optional<bsoncxx::document::value> maybe_result =
        coll.find_one(document{} << "_id" << id << finalize);

    if (maybe_result)   { return bsoncxx::to_json(*maybe_result); }
    else                { return "Nothing retrieved for id: " + id; }
}


// Inserts a new document {id,url,status,date}, or updates the existing one
void upsertUrl(mongocxx::collection& coll,
               std::string id, std::string url, int status, int date)
{
    std::cout << ">> Before insert/update: " << retrieveJsonDocById(coll, id) << std::endl;

    // Bulk write ordered=false to force performing all operations
    mongocxx::options::bulk_write bulkWriteOption;
    bulkWriteOption.ordered(false);
    auto bulk = coll.create_bulk_write(bulkWriteOption);

    // If document exists and has status='x', update the date field
    // If document exists but status!='x', nothing will be inserted (duplicate key thrown)
    // If document is new, perform insert
    mongocxx::model::update_one upsert_op{
        make_document(kvp("_id", id), kvp("status", kStatus_X)),
        make_document(
                kvp("$set", make_document(kvp("date", date))),
                kvp("$setOnInsert", make_document(kvp("status", status), kvp("url", url))))
    };
    upsert_op.upsert(true);
    bulk.append(upsert_op);

    // If new_doc[status] is 'y', attempt to perform status update
    if (status == kStatus_Y) {
        mongocxx::model::update_one update_op{
            make_document(kvp("_id", id)),
            make_document(kvp("$set", make_document(kvp("status", status))))
        };
        bulk.append(update_op);
    }

    try {
        auto result = bulk.execute();
    }
    catch (const mongocxx::bulk_write_exception& e) {
        if (e.code().value() == 11000) {
            std::cout << "Duplicate key error expected when id exists but the status!=x: ";
            std::cout << std::endl << e.what() << std::endl;
        }
    }
    std::cout << ">> After insert/update:  " << retrieveJsonDocById(coll, id) << std::endl << std::endl;
}

, который в этих тестовых сценариях:

int main(int, char**) {

    std::cout << "Starting program, x=" << kStatus_X << ", y=" << kStatus_Y << std::endl;

    mongocxx::instance instance{};
    mongocxx::client client{ mongocxx::uri{} };

    mongocxx::database db = client["stack"];
    mongocxx::collection coll = db["urls"];

    std::cout << "Inserting Doc #1 (status=x):" << std::endl;
    upsertUrl(coll, "1", "1_url.com", kStatus_X, 101010);
    std::cout << "Inserting Doc #2 (status=x):" << std::endl;
    upsertUrl(coll, "2", "2_url.com", kStatus_X, 202020);
    std::cout << "Inserting Doc #3 (status!=x):" << std::endl;
    upsertUrl(coll, "3", "3_url.com", 3, 303030);
    std::cout << "Inserting Doc #4 (status!=x):" << std::endl;
    upsertUrl(coll, "4", "4_url.com", 4, 404040);

    std::cout << "Inserting again Doc #1 (existing.status=x, new.status=y) -> should update the date and status:" << std::endl;
    upsertUrl(coll, "1", "1_url.com", kStatus_Y, 505050);
    std::cout << "Inserting again Doc #2 (existing.status=x, new.status!=y) -> should update date:" << std::endl;
    upsertUrl(coll, "2", "2_url.com", 6, 606060);
    std::cout << "Inserting again Doc #3 (existing.status!=x, new.status=y) -> should update status:" << std::endl;
    upsertUrl(coll, "3", "3_url.com", kStatus_Y, 707070);
    std::cout << "Inserting again Doc #4 (existing.status!=x, new.status!=y) -> should update nothing:" << std::endl;
    upsertUrl(coll, "4", "4_url.com", 8, 808080);

    std::cout << "End program" << std::endl;
}

генерирует следующий вывод:

Starting program, x=1234, y=6789
Inserting Doc #1 (status=x):
>> Before insert/update: Nothing retrieved for id: 1
>> After insert/update:  { "_id" : "1", "status" : 1234, "date" : 101010, "url" : "1_url.com" }

Inserting Doc #2 (status=x):
>> Before insert/update: Nothing retrieved for id: 2
>> After insert/update:  { "_id" : "2", "status" : 1234, "date" : 202020, "url" : "2_url.com" }

Inserting Doc #3 (status!=x):
>> Before insert/update: Nothing retrieved for id: 3
>> After insert/update:  { "_id" : "3", "status" : 3, "date" : 303030, "url" : "3_url.com" }

Inserting Doc #4 (status!=x):
>> Before insert/update: Nothing retrieved for id: 4
>> After insert/update:  { "_id" : "4", "status" : 4, "date" : 404040, "url" : "4_url.com" }

Inserting again Doc #1 (existing.status=x, new.status=y) -> should update the date and status:
>> Before insert/update: { "_id" : "1", "status" : 1234, "date" : 101010, "url" : "1_url.com" }
>> After insert/update:  { "_id" : "1", "status" : 6789, "date" : 505050, "url" : "1_url.com" }

Inserting again Doc #2 (existing.status=x, new.status!=y) -> should update date:
>> Before insert/update: { "_id" : "2", "status" : 1234, "date" : 202020, "url" : "2_url.com" }
>> After insert/update:  { "_id" : "2", "status" : 1234, "date" : 606060, "url" : "2_url.com" }

Inserting again Doc #3 (existing.status!=x, new.status=y) -> should update status:
>> Before insert/update: { "_id" : "3", "status" : 3, "date" : 303030, "url" : "3_url.com" }
Duplicate key error expected when id exists but the status!=x:
E11000 duplicate key error collection: stack.urls index: _id_ dup key: { : "3" }: generic server error
>> After insert/update:  { "_id" : "3", "status" : 6789, "date" : 303030, "url" : "3_url.com" }

Inserting again Doc #4 (existing.status!=x, new.status!=y) -> should update nothing:
>> Before insert/update: { "_id" : "4", "status" : 4, "date" : 404040, "url" : "4_url.com" }
Duplicate key error expected when id exists but the status!=x:
E11000 duplicate key error collection: stack.urls index: _id_ dup key: { : "4" }: generic server error
>> After insert/update:  { "_id" : "4", "status" : 4, "date" : 404040, "url" : "4_url.com" }

End program
...