Lesson 17-C Language and MongoDB Database Operations

Operating MongoDB databases in C language primarily relies on the official libmongoc driver library (MongoDB C Driver). It provides a complete API supporting core operations such as connection, CRUD, aggregation, and more. The following systematically explains the interaction between C language and MongoDB, from environment setup and basic operations to advanced applications.

Environment Preparation: Installing libmongoc

Installing Dependencies

libmongoc depends on the following libraries (using Ubuntu as an example):

sudo apt-get install -y build-essential pkg-config libssl-dev libsasl2-dev

Installing libmongoc

Install via source compilation (recommended to use the latest stable version, such as 1.24.x):

# Download source code
wget https://github.com/mongodb/mongo-c-driver/releases/download/1.24.3/mongo-c-driver-1.24.3.tar.gz
tar xzf mongo-c-driver-1.24.3.tar.gz
cd mongo-c-driver-1.24.3

# Configure, compile, and install
./configure --enable-tests=no --enable-examples=no
make
sudo make install

# Refresh dynamic library cache
sudo ldconfig

Verifying Installation

Check if the installation is successful using pkg-config:

pkg-config --cflags --libs libmongoc-1.0
# Output similar to: -I/usr/local/include/libmongoc-1.0 -I/usr/local/include/bson-1.0 -lmongoc-1.0 -lbson-1.0

Core Process of Operating MongoDB in C Language

Connecting to MongoDB

Use mongoc_client_new() to establish a connection, specifying the MongoDB URI (e.g., mongodb://localhost:27017).

#include <mongoc/mongoc.h>
#include <bson/bson.h>

int main() {
    mongoc_client_t *client = NULL;
    mongoc_collection_t *collection = NULL;
    bson_error_t error;

    // 1. Initialize libmongoc (only once globally)
    mongoc_init();

    // 2. Create MongoDB client (connect to local port 27017)
    client = mongoc_client_new("mongodb://localhost:27017/");
    if (!client) {
        fprintf(stderr, "Failed to create client\n");
        return 1;
    }

    // 3. Get database (assuming database name is "test_db")
    mongoc_database_t *db = mongoc_client_get_database(client, "test_db");
    if (!db) {
        fprintf(stderr, "Failed to get database\n");
        goto cleanup;
    }

    // 4. Get collection (assuming collection name is "users")
    collection = mongoc_database_get_collection(db, "users");
    if (!collection) {
        fprintf(stderr, "Failed to get collection\n");
        goto cleanup;
    }

    // ... subsequent operations (CRUD)

cleanup:
    // 5. Release resources (in reverse order)
    if (collection) mongoc_collection_destroy(collection);
    if (db) mongoc_database_destroy(db);
    if (client) mongoc_client_destroy(client);
    mongoc_cleanup();
    return 0;
}

Core Operations: CRUD (Create, Read, Update, Delete)

Inserting Documents (Create)

Use mongoc_collection_insert_one() to insert a single document, requiring construction of a bson_t type document.

// Example of inserting a document
void insert_user(mongoc_collection_t *collection) {
    bson_t *doc = bson_new(); // Create empty BSON document
    bson_error_t error;

    // Construct document fields (key-value pairs)
    BSON_APPEND_UTF8(doc, "name", "Alice");       // String
    BSON_APPEND_INT32(doc, "age", 30);            // Integer
    BSON_APPEND_BOOL(doc, "is_student", false);   // Boolean
    BSON_APPEND_DOUBLE(doc, "score", 92.5);       // Double
    BSON_APPEND_OID(doc, "_id", &oid);            // Optional: explicitly specify _id (otherwise MongoDB auto-generates)

    // Insert document into collection
    bool ret = mongoc_collection_insert_one(
        collection,
        doc,
        NULL,          // Insert options (e.g., write concern, optional)
        NULL,          // Output result (optional)
        &error         // Error information
    );

    if (!ret) {
        fprintf(stderr, "Insert failed: %s\n", error.message);
    } else {
        printf("Inserted document ID: %s\n", bson_oid_to_string(&oid));
    }

    bson_destroy(doc); // Destroy BSON document (free memory)
}

Querying Documents (Read)

Use mongoc_collection_find_with_opts() to query data, construct query conditions with bson_t, and iterate through the result set.

// Example of querying documents (query users with age >= 25)
void query_users(mongoc_collection_t *collection) {
    bson_t *query = BCON_NEW(
        "age", BCON_INT32, 25  // Condition: age >= 25 (BCON macro simplifies BSON construction)
    );
    mongoc_cursor_t *cursor = mongoc_collection_find_with_opts(
        collection,
        query,
        NULL,          // Query options (e.g., sort, pagination, optional)
        NULL           // Read preference (optional)
    );
    bson_error_t error;

    // Iterate through result set
    const bson_t *doc;
    while (mongoc_cursor_next(cursor, &doc)) {
        // Parse document fields
        const char *name;
        int age;
        bool is_student;

        if (bson_lookup_utf8(doc, "name", &name) &&
            bson_lookup_int32(doc, "age", &age) &&
            bson_lookup_bool(doc, "is_student", &is_student)) {
            printf("Name: %s, Age: %d, Student: %s\n",
                   name, age, is_student ? "Yes" : "No");
        }
    }

    // Check for errors during iteration
    if (mongoc_cursor_error(cursor, &error)) {
        fprintf(stderr, "Query failed: %s\n", error.message);
    }

    // Clean up resources
    mongoc_cursor_destroy(cursor);
    bson_destroy(query);
}

Updating Documents (Update)

Use mongoc_collection_update_one() to update a single document, supporting update operators such as $set, $inc.

// Example of updating a document (change Alice's age to 31)
void update_user(mongoc_collection_t *collection) {
    bson_t *filter = BCON_NEW("name", BCON_UTF8, "Alice"); // Query condition
    bson_t *update = BCON_NEW(
        "$set", BCON_DOCUMENT(&(
            BCON_NEW("age", BCON_INT32, 31)  // Update operation: set age=31
        ))
    );
    bson_error_t error;
    bool ret;

    // Execute update (only update the first matching document)
    ret = mongoc_collection_update_one(
        collection,
        filter,
        update,
        NULL,          // Update options (e.g., upsert, optional)
        NULL,          // Output result (optional)
        &error
    );

    if (!ret) {
        fprintf(stderr, "Update failed: %s\n", error.message);
    } else {
        printf("Matched %d document(s), modified %d document(s)\n",
               update->updated, update->modified_count); // Need to check output result
    }

    bson_destroy(filter);
    bson_destroy(update);
}

Deleting Documents (Delete)

Use mongoc_collection_delete_one() to delete a single document, specifying the deletion condition with filter.

// Example of deleting a document (delete users with age >= 30)
void delete_user(mongoc_collection_t *collection) {
    bson_t *filter = BCON_NEW("age", BCON_INT32, 30); // Query condition
    bson_error_t error;
    bool ret;

    // Execute deletion (only delete the first matching document)
    ret = mongoc_collection_delete_one(
        collection,
        filter,
        NULL,          // Delete options (optional)
        NULL,          // Output result (optional)
        &error
    );

    if (!ret) {
        fprintf(stderr, "Delete failed: %s\n", error.message);
    } else {
        printf("Deleted %d document(s)\n", delete->deleted_count); // Need to check output result
    }

    bson_destroy(filter);
}

Advanced Operations: Aggregation and Indexing

Aggregation Framework

MongoDB’s aggregation framework supports complex data processing (such as grouping, summing, filtering), implemented via mongoc_collection_aggregate().

// Aggregation example: count users by age
void aggregate_users(mongoc_collection_t *collection) {
    // Construct aggregation pipeline ($group stage)
    bson_t *pipeline = bson_new();
    bson_t *stage_group = BCON_NEW(
        "$group", BCON_DOCUMENT(&(
            BCON_NEW("_id", BCON_INT32, "$age"),  // Group by age
            BCON_NEW("count", BCON_INT32, 0)     // Count quantity
        ))
    );
    bson_array_append_document(pipeline, stage_group); // Add stage to pipeline

    // Execute aggregation
    mongoc_cursor_t *cursor = mongoc_collection_aggregate(
        collection,
        pipeline,
        NULL,          // Aggregation options (optional)
        NULL           // Read preference (optional)
    );
    bson_error_t error;

    // Iterate through aggregation results
    const bson_t *doc;
    while (mongoc_cursor_next(cursor, &doc)) {
        int age;
        int count;
        if (bson_lookup_int32(doc, "_id", &age) &&
            bson_lookup_int32(doc, "count", &count)) {
            printf("Age %d: %d users\n", age, count);
        }
    }

    if (mongoc_cursor_error(cursor, &error)) {
        fprintf(stderr, "Aggregation failed: %s\n", error.message);
    }

    // Clean up resources
    mongoc_cursor_destroy(cursor);
    bson_destroy(pipeline);
}

Creating Indexes

Create indexes for frequently queried fields to improve performance using mongoc_collection_create_index().

// Example of creating an index (create ascending index on age field)
void create_index(mongoc_collection_t *collection) {
    bson_t *keys = BCON_NEW("age", BCON_INT32, 1); // 1 means ascending, -1 means descending
    mongoc_index_opt_t opt = MONGOC_INDEX_OPT_INIT; // Index options (e.g., uniqueness)
    bson_error_t error;
    bson_t *index_name; // Store generated index name (optional)

    // Create index (asynchronously in background)
    bool ret = mongoc_collection_create_index(
        collection,
        keys,
        &opt,
        &index_name,   // Output index name (e.g., "age_1")
        &error
    );

    if (ret) {
        printf("Index created: %s\n", bson_str(index_name));
        bson_destroy(index_name);
    } else {
        fprintf(stderr, "Create index failed: %s\n", error.message);
    }

    bson_destroy(keys);
}

Compiling and Running

Compile Command

When compiling with gcc, link libmongoc and bson libraries via pkg-config:

gcc -o mongo_demo mongo_demo.c $(pkg-config --cflags --libs libmongoc-1.0)

Running the Test

Ensure the MongoDB service is started (default local port 27017), then execute the compiled program:

./mongo_demo

Notes

  1. Error Handling: All MongoDB C API operations may return errors (via bson_error_t), and error information must always be checked.
  2. Resource Release: Objects such as bson_t, mongoc_cursor_t, mongoc_collection_t must be manually released using bson_destroy(), mongoc_cursor_destroy(), etc., to avoid memory leaks.
  3. Thread Safety: The libmongoc client (mongoc_client_t) is thread-safe, but collections (mongoc_collection_t) and cursors (mongoc_cursor_t) are not; avoid sharing across threads.
  4. Production Environment Configuration:
    • Connection URI can include authentication (e.g., mongodb://user:pass@host:port/db).
    • Configure write concern and read preference to improve data reliability.
    • Use connection pooling (mongoc_client_pool_t) to manage client instances and optimize performance in high-concurrency scenarios.

Summary

Through the libmongoc driver, C language can efficiently interact with MongoDB, implementing complete CRUD operations and advanced features (such as aggregation and indexing). The key steps include:

  1. Install libmongoc and initialize;
  2. Establish connection and get collection;
  3. Use BSON to construct query/update conditions;
  4. Call API to execute operations and handle results;
  5. Strictly release resources and handle errors.

In actual development, refer to the MongoDB C Driver official documentation for more details and advanced usage.

Share your love