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:
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:
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.
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:
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:
Passing the emitter we got from
amongoc_client_new()
, and replacing it with the new operation emitter returned byamongoc_let()
.amongoc_async_forward_errors
is a behavior control flag foramongoc_let()
that tells the operation to skip our continuation if the input operation generates an error.We pass the address of the
state
object by wrapping it withamongoc_box_pointer()
, passing it as theuserdata
parameter ofamongoc_let()
.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
:
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:
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:
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 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()
:
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:
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:
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:
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