mongocの使い方

業務で使うことがあり、英語の情報しかなくて苦戦したためメモ。
mongoDBのCのドライバーの使い方です。

大体生成と破棄と対になっているメソッドが用意されています。

大まかな流れは以下の感じです。

--------

初期化(メインスレッド)
↓
コネクションプール生成
↓
コネクション取得
↓
コマンド発行　　　 ←
↓　　　　　　　　　↑ （必要なだけ繰り返し）
(コマンド破棄)　→　↑
↓
コネクション破棄
↓
コネクションプール破棄
↓
終了(メインスレッド)

--------


以下に主に使うメソッドを記載します。
★印がついたコメントが章のような感じです。

```c
// ★インクルードファイル
#include <mongoc/mongoc.h>


// ★初期化(メインスレッドで行う)
mongoc_init();


// ★クライアント初期化
// 　変数urlには、DBへのurl文字列(char*)を代入する。
bson_error_t         error;
mongoc_uri_t* uri = mongoc_uri_new_with_error(url, &error);
// 失敗時の判定
if (NULL == uri) {
    fprintf(stderr,
            "failed to parse URI: %s\n"
            "error message:       %s\n",
            url,
            error.message
    );
}

// ★コネクションプールの作成
mongoc_client_pool_t* connection_pool; // ※実際のソースではstaticグローバル変数で保持していました。     
connection_pool = mongoc_client_pool_new(uri);

// ★作成したuriの破棄
mongoc_uri_destroy(uri);

// ★エラー発生時のエラーコードの返し方を設定
mongoc_client_pool_set_error_api(connection_pool, MONGOC_ERROR_API_VERSION_2);

// ★コネクションプールからコネクションの取得
mongoc_client_t*    client    = NULL;
client = mongoc_client_pool_pop(connection_pool);

// ※マルチスレッドを考慮しない場合は以下のようにuriから直接コネクションを作る方法でもOK
// client = mongoc_client_new_from_uri(uri);
// この場合の切断+破棄方法
// mongoc_client_destroy(client);

// ★使用するデータベースの取得
// 　db_nameにはデータベース名を示す文字列(char*)を代入する。
mongoc_database_t* database = mongoc_client_get_database(client, db_name);

// ★コレクションの作成(オプションなし、この場合データベースオブジェクトも必要なし。)
// 　collection_nameにはコレクション名を示す文字列(char*)を代入する。
mongoc_collection_t* collection = mongoc_client_get_collection(client, db_name, collection_name);


// ★データベースオブジェクトがコレクションを持っているかチェック(重複による作成失敗を避けるための処理等に用いる。)
bool ret = mongoc_database_has_collection(database, collection_name, &error);
// ret == true -> コレクションがある、 ret == false コレクションがない。
// エラーメッセージ取得
if (0 != strlen(error.message)) {
    fprintf(stderr, "%s\n", error.message);
}


// ★cappedオプションやindexをつけてコレクションを生成する場合
int64_t size = 16; // 用途にあわせてサイズを変える
int64_t capMax = 16*16*1024;
bson_t capped_options;
bson_t indexes;

bson_t reply;
bool r;
// bsonの初期化
bson_init(&capped_options);
bson_init(&indexes);

// cappedオプション作成のためのbson設定
// bsonにキーと値を設定。
BSON_APPEND_BOOL(&capped_options, "capped", true);
BSON_APPEND_INT64(&capped_options, "size", size);
BSON_APPEND_INT64(&capped_options, "max",  capMax);

// index作成のためのbson作成
BSON_APPEND_UTF8(&indexes, "createIndexes", collection_name);
bson_t indexarray;
BSON_APPEND_ARRAY_BEGIN(&indexes, "indexes", &indexarray);

size_t loop_index;
size_t loop_keyvalue;
size_t index_num = 3;
char* index_key[] = {"_id", "val1", "val2"}; // 存在するキーを設定
int index_val[] = {1, -1, 1}; // 昇順・降順を設定
for (loop_index = 0; loop_index < index_num; loop_index++) {
    bson_t indexdoc;
    bson_t keys;
    bson_init(&indexdoc);
    bson_init(&keys);
    
    BSON_APPEND_INT32(&keys, index_key[loop_index], index_val[loop_index ]);
    // indexの文字列に変換
    char* index_name = mongoc_collection_keys_to_index_string(&keys);
    if (NULL == index_name) {
        // 異常系。 必要に応じてメモリ解放などを行う。
    }

    const char* arraykey;
    char buf[32];
    bson_uint32_to_string(loop_index, &arraykey, buf, sizeof(buf));

    BSON_APPEND_DOCUMENT(&indexdoc, "key", &keys);
    BSON_APPEND_UTF8(&indexdoc, "name", index_name);
    BSON_APPEND_BOOL(&indexdoc, "unique", true); // 一意制約。いらない場合はfalse

    // 作成した文字列を解放
    bson_free(index_name);

    BSON_APPEND_DOCUMENT(&indexarray, arraykey, &indexdoc);
}
bson_append_array_end(&indexes, &indexarray);

// コレクションの作成(capped オプション付き)
mongoc_collection_t* collection = mongoc_database_create_collection(database, collection_name, &capped_options, &error);

// インデックス設定(データ投入前にすること。)
r = mongoc_database_write_command_with_opts(database, &indexes, NULL, &reply, &error);
// 異常チェック
if (!r) {
    fprintf(stderr, "%s\n", error.message);
　　// 異常系、必要に応じてメモリ破棄等を行う
}

// 作成したbsonのうち、破棄が必要なものを破棄
// indexesは配列を作成したので解放が必要なようです。replyは恐らくmongoc_database_write_command_with_optsの関数仕様？
bson_destroy(&indexes);
bson_destroy(&reply);


// ★バルクインサートの例
// oid作成(解放不用のようです)
bson_oid_t oid;
bson_oid_init(&oid, NULL);
// ドキュメント作成(BCON_NEWで作成した場合は解放がいるようです)
bson_t* doc;
doc = BCON_NEW("_id",
               BCON_OID(&oid),
               "val1",
               BCON_INT32(30),
               "val2",
               BCON_INT32(50));

// bulkオブジェクトの作成
bson_t opts = BSON_INITIALIZER;
BSON_APPEND_BOOL (&opts, "ordered", false); // 順序通りにしない場合。 順序通りにしない方が高速だったはず。
mongoc_bulk_operation_t* bulk = mongoc_collection_create_bulk_operation_with_opts(collection, &opts);
// insertのデータ登録
mongoc_bulk_operation_insert(bulk, doc);

// ここからもう一回別のdocを作成し、mongoc_bulk_operation_insert()関数を呼ぶことで
// データをためることができる。

// バルクインサート実行
bson_t reply;
bson_error_t error;
boot ret = mongoc_bulk_operation_execute(bulk, &reply, &error);
// エラー発生時、ログに出力
if (!ret) {
    char* str = bson_as_canonical_extended_json(&reply, NULL);
    fprintf(stderr,
            "Error: %s\n%s\n",
            error.message, str
    );
    bson_free(str);
}
bson_destroy(&reply);

// 一回mongoc_bulk_operation_execute()
// を実行したらbulkオブジェクトを解放する
mongoc_bulk_operation_destroy(bulk);

// bson破棄
bson_destroy(doc);


// ★コレクションオブジェクトの破棄
mongoc_collection_destroy(collection);

// ★データベースオブジェクトの破棄
mongoc_database_destroy(database);

// ★コネクションプールへの返却と切断
mongoc_client_pool_push(connection_pool, client);


// ★コネクションプール破棄
mongoc_client_pool_destroy(connection_pool);

// ★終了(メインスレッドで行う)
mongoc_cleanup();

```

一番苦戦したのはcappedオプションやindexまわり。
BSONを思いっきり駆使しないといけないので、そのあたりの調査に時間かかりました。
BSON周りの処理はソースコードを検索して見つけたマクロが多いです。
なお、破棄類の処理を忘れると恐らくメモリリークになると思われますので
ポインタが返ってくるような処理には気を付けて。



以上