Writing Data#

This tutorial will cover the basics of writing data into a MongoDB server asynchronously. This page will assume that you have read the content of the Connecting to a MongoDB Server tutorial. It will assume that you have a program that includes the amongoc headers and know how to connect to a server.

Declaring Application State Type#

This program will require that certain objects outlive the sub-operations, so we will need to persist them outside the event loop and pass them to our continuation. We declare a simple aggregate type to hold these objects:

Application State Struct#
 5// Application state
 6typedef struct {
 7    // The client object
 8    amongoc_client* client;
 9    // The collection object which we will write into
10    amongoc_collection* coll;
11} app_state;

Starting with a Connection#

We’ll start with the basics of setting up an event loop and creating a connection emitter:

Setting up an event loop#
15int main() {
16    amongoc_loop   loop;
17    amongoc_status status = amongoc_default_loop_init(&loop);
18    amongoc_if_error (status, msg) {
19        fprintf(stderr, "Failed to initialize event loop: %s\n", msg);
20        return 1;
21    }
22    // Connect
23    amongoc_emitter em = amongoc_client_new(&loop, "mongodb://localhost:27017");

Declare the Application State Object#

Declare an instance of app_state, which will be passed through the program by address. We zero-initialize it so that the pointer members are null, making deletion a no-op in case they are never initialized later.

Application state object#
25    // Application state object
26    app_state state = {0};

Create the First Continuation#

We have the pending connection object and the application state, so now we can set the first continuation:

The first continuation#
28    // Set the continuation for the connection:
29    em = amongoc_let(em,                            // [1]
30                     amongoc_async_forward_errors,  // [2]
31                     amongoc_box_pointer(&state),   // [3]
32                     on_connect);                   // [4]

The arguments are as follows:

  1. Passing the emitter we got from amongoc_client_new(), and replacing it with the new operation emitter returned by amongoc_let().

  2. amongoc_async_forward_errors is a behavior control flag for amongoc_let() that tells the operation to skip our continuation if the input operation generates an error.

  3. We pass the address of the state object by wrapping it with amongoc_box_pointer(), passing it as the userdata parameter of amongoc_let().

  4. on_connect is the name of the function that will handle the continuation.

Define the Continuation#

Now we can look at the definition of on_connect:

Continuation signature#
53// Continuation after connection completes
54static amongoc_emitter on_connect(amongoc_box state_, amongoc_status status, amongoc_box client) {
55    // We don't use the status here
56    (void)status;

The state_ parameter is the userdata box that was given to amongoc_let() in the previous section. The client parameter is a box that contains the amongoc_client pointer from the connection operation. The status parameter here is the status of the connection operation, but we don’t care about this here since we used amongoc_async_forward_errors to ask amongoc_let() to skip this function if the status would otherwise indicate an error (i.e. on_connect will only be called if the connection actually succeeds).

Update the Application State#

on_connect is passed the pointer to the application state as well as a box containing a pointer to the amongoc_client. We can extract the box value and store the client pointer within our application state struct:

Update the Application State#
57    // Store the client from the connect operation
58    app_state* const state = amongoc_box_cast(app_state*, state_);
59    amongoc_box_take(state->client, client);

We store it in the application state object so that we can later delete the client handle when the program completes.

Create a Collection Handle#

Now that we have a valid client, we can create a handle to a collection on the server:

Set up the Collection#
61    // Create a new collection handle
62    state->coll = amongoc_collection_new(state->client, "write-test-db", "main");
63    if (!state->coll) {
64        return amongoc_alloc_failure();
65    }

amongoc_collection_new() does not actually communicate with the server: It only creates a client-side handle that can be used to perform operations related to that collection. The server-side collection will be created automatically when we begin writing data into it.

We store the returned collection handle in the state struct so that we can later delete it.

Create Some Data to Insert#

To insert data, we first need data to be inserted. We can create that with the BSON APIs:

Create a Document#
    // Create a document to be inserted
    // Data: { "foo": "bar", "answer": 42 }
    bson_doc doc = bson_new();
    if (!bson_data(doc)) {
        return amongoc_alloc_failure();
    }
    bson_mut mut = bson_mutate(&doc);
    bson_insert(&mut, "foo", "bar");
    bson_insert(&mut, "answer", 42);

See also

BSON Handling for tutorials on the BSON APIs.

Create the Insert Operation#

Inserting a single document can be done with amongoc_insert_one():

Insert the Data#
76    // Insert the single document:
77    amongoc_emitter insert_em = amongoc_insert_one(state->coll, doc);
78    // Delete our copy of the doc
79    bson_delete(doc);
80    // Tell the runtime to continue into the next operation:
81    return insert_em;

amongoc_insert_one() will take a copy of the data, so we can delete it immediately. We then return the resulting insert emitter from on_connect to tell amongoc_let() to continue the composed operation.

The Full Continuation#

Here is the full on_connect function:

The on_connect function#
53// Continuation after connection completes
54static amongoc_emitter on_connect(amongoc_box state_, amongoc_status status, amongoc_box client) {
55    // We don't use the status here
56    (void)status;
57    // Store the client from the connect operation
58    app_state* const state = amongoc_box_cast(app_state*, state_);
59    amongoc_box_take(state->client, client);
60
61    // Create a new collection handle
62    state->coll = amongoc_collection_new(state->client, "write-test-db", "main");
63    if (!state->coll) {
64        return amongoc_alloc_failure();
65    }
66
67    // Create a document to be inserted
68    // Data: { "foo": "bar", "answer": 42 }
69    bson_doc doc = bson_new();
70    if (!bson_data(doc)) {
71        return amongoc_alloc_failure();
72    }
73    bson_mut mut = bson_mutate(&doc);
74    bson_insert(&mut, "foo", "bar");
75    bson_insert(&mut, "answer", 42);
76    // Insert the single document:
77    amongoc_emitter insert_em = amongoc_insert_one(state->coll, doc);
78    // Delete our copy of the doc
79    bson_delete(doc);
80    // Tell the runtime to continue into the next operation:
81    return insert_em;
82}

Tie, Start, and Run the Operation#

Going back to main, we can now tie the operation, start it, and run the event loop:

Run the Program#
34    amongoc_operation op = amongoc_tie(em, &status);
35    amongoc_start(&op);
36    amongoc_default_loop_run(&loop);
37
38    // Clean up:
39    amongoc_operation_delete(op);
40    amongoc_collection_delete(state.coll);
41    amongoc_client_delete(state.client);
42    amongoc_default_loop_destroy(&loop);

amongoc_tie() tells the emitter to store its final status in the given pointer destination, and returns an operation that can be initiated with amongoc_start(). We then give control to the event loop with amongoc_default_loop_run(). After this returns, the operation object is finished, so we delete it with amongoc_operation_delete().

We are also finished with the collection handle and the client, so we delete those here as well. Those struct members will have been filled in by on_connect.

Error Checking#

The status object will have been filled during amongoc_default_loop_run() due to the call to amongoc_tie(). We can check it for errors and print them out now:

Check the Final Status#
44    // Print the final result
45    amongoc_if_error (status, msg) {
46        fprintf(stderr, "An error occurred: %s\n", msg);
47        return 1;
48    }
49    printf("okay\n");
50    return 0;

The Full Program#

Here is the full sample program, in its entirety:

write.example.c#
 1#include <amongoc/amongoc.h>
 2
 3#include <stdio.h>
 4
 5// Application state
 6typedef struct {
 7    // The client object
 8    amongoc_client* client;
 9    // The collection object which we will write into
10    amongoc_collection* coll;
11} app_state;
12
13static amongoc_emitter on_connect(amongoc_box state, amongoc_status status, amongoc_box client);
14
15int main() {
16    amongoc_loop   loop;
17    amongoc_status status = amongoc_default_loop_init(&loop);
18    amongoc_if_error (status, msg) {
19        fprintf(stderr, "Failed to initialize event loop: %s\n", msg);
20        return 1;
21    }
22    // Connect
23    amongoc_emitter em = amongoc_client_new(&loop, "mongodb://localhost:27017");
24
25    // Application state object
26    app_state state = {0};
27
28    // Set the continuation for the connection:
29    em = amongoc_let(em,                            // [1]
30                     amongoc_async_forward_errors,  // [2]
31                     amongoc_box_pointer(&state),   // [3]
32                     on_connect);                   // [4]
33
34    amongoc_operation op = amongoc_tie(em, &status);
35    amongoc_start(&op);
36    amongoc_default_loop_run(&loop);
37
38    // Clean up:
39    amongoc_operation_delete(op);
40    amongoc_collection_delete(state.coll);
41    amongoc_client_delete(state.client);
42    amongoc_default_loop_destroy(&loop);
43
44    // Print the final result
45    amongoc_if_error (status, msg) {
46        fprintf(stderr, "An error occurred: %s\n", msg);
47        return 1;
48    }
49    printf("okay\n");
50    return 0;
51}
52
53// Continuation after connection completes
54static amongoc_emitter on_connect(amongoc_box state_, amongoc_status status, amongoc_box client) {
55    // We don't use the status here
56    (void)status;
57    // Store the client from the connect operation
58    app_state* const state = amongoc_box_cast(app_state*, state_);
59    amongoc_box_take(state->client, client);
60
61    // Create a new collection handle
62    state->coll = amongoc_collection_new(state->client, "write-test-db", "main");
63    if (!state->coll) {
64        return amongoc_alloc_failure();
65    }
66
67    // Create a document to be inserted
68    // Data: { "foo": "bar", "answer": 42 }
69    bson_doc doc = bson_new();
70    if (!bson_data(doc)) {
71        return amongoc_alloc_failure();
72    }
73    bson_mut mut = bson_mutate(&doc);
74    bson_insert(&mut, "foo", "bar");
75    bson_insert(&mut, "answer", 42);
76    // Insert the single document:
77    amongoc_emitter insert_em = amongoc_insert_one(state->coll, doc);
78    // Delete our copy of the doc
79    bson_delete(doc);
80    // Tell the runtime to continue into the next operation:
81    return insert_em;
82}
83// end:on_connect