Loading...
Searching...
No Matches
A full-blown application

As the next step of our tutorial, we will write a full blown application.

While it does not solve any real-life problems, and what it does is entirely pointless, it nevertheless is a good example to showcase certain patterns one is likely to run into while developing with libmongo-client.

#include <mongo.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>

Our first task is to add a handful of items to our test collection. We'll have two static keys, and one that's different for each key.

static void
do_inserts (mongo_sync_connection *conn)
{
bson *base;
gint i;

First, we'll build a base BSON object:

base = bson_build
(BSON_TYPE_STRING, "tutorial-program", "tut_hl_client.c", -1,
BSON_TYPE_INT32, "the answer to life, the universe and everything", 42,
BSON_TYPE_NONE);
bson_finish (base);
bson_finish
gboolean bson_finish(bson *b)
Finish a BSON object.
Definition bson.c:521
bson_build
bson * bson_build(bson_type type, const gchar *name,...)
Build a BSON object in one go.
Definition bson.c:448
BSON_TYPE_STRING
@ BSON_TYPE_STRING
4byte length + NULL terminated string
Definition bson.h:67
BSON_TYPE_NONE
@ BSON_TYPE_NONE
Only used for errors.
Definition bson.h:65
BSON_TYPE_INT32
@ BSON_TYPE_INT32
4byte integer
Definition bson.h:84

Then, we create a copy, append a counter element to the object, insert it, and do this a thousand times over.

for (i = 0; i < 1000; i++)
{
bson *n;
n = bson_new_from_data (bson_data (base), bson_size (base) - 1);
bson_append_int32 (n, "counter", i);
bson_finish (n);
if (!mongo_sync_cmd_insert (conn, "lmc.tutorial", n, NULL))
{
fprintf (stderr, "Error inserting document %d: %s\n", i,
strerror (errno));
exit (1);
}
bson_free (n);
bson_append_int32
gboolean bson_append_int32(bson *b, const gchar *name, gint32 i)
Append a 32-bit integer to a BSON object.
Definition bson.c:761
bson_free
void bson_free(bson *b)
Free the memory associated with a BSON object.
Definition bson.c:579
bson_new_from_data
bson * bson_new_from_data(const guint8 *data, gint32 size)
Create a BSON object from existing data.
Definition bson.c:269
bson_size
gint32 bson_size(const bson *b)
Return the size of a finished BSON object.
Definition bson.c:542
bson_data
const guint8 * bson_data(const bson *b)
Return the raw bytestream form of the BSON object.
Definition bson.c:554
mongo_sync_cmd_insert
gboolean mongo_sync_cmd_insert(mongo_sync_connection *conn, const gchar *ns,...)
Send an insert command to MongoDB.
Definition mongo-sync.c:943
}

This was pretty simple, wasn't it? And we even have error handling! Lets finish this function up, and move on.

bson_free (base);
}

Next up comes the interesting part: doing queries. We will use the cursor API to iterate over all our results, hiding the gory details of database access behind its convenience layer.

static void
do_query (mongo_sync_connection *conn)
{

We'll need a couple of things: a cursor, a query, and a string to store error messages in, if any.

mongo_sync_cursor *c;
bson *query;
gchar *error = NULL;

Before we can query the database, we must build a query object:

query = bson_build
(BSON_TYPE_STRING, "tutorial-program", "tut_hl_client.c", -1,
BSON_TYPE_NONE);
bson_finish (query);

Once that is done, we create a cursor, cleverly embedding the mongo_sync_cmd_query() call into the constructor:

c = mongo_sync_cursor_new (conn, "lmc.tutorial",
mongo_sync_cmd_query (conn, "lmc.tutorial", 0,
0, 10, query, NULL));
if (!c)
{
fprintf (stderr, "Error creating the query cursor: %s\n",
strerror (errno));
exit (1);
}
bson_free (query);
mongo_sync_cursor_new
mongo_sync_cursor * mongo_sync_cursor_new(mongo_sync_connection *conn, const gchar *ns, mongo_packet *packet)
Create a new MongoDB Cursor.
Definition mongo-sync-cursor.c:28
mongo_sync_cmd_query
mongo_packet * mongo_sync_cmd_query(mongo_sync_connection *conn, const gchar *ns, gint32 flags, gint32 skip, gint32 ret, const bson *query, const bson *sel)
Send a query command to MongoDB.
Definition mongo-sync.c:986

Again, we properly handle errors. It is very important to not just blindly assume things will work. While the library tries its best to handle invalid data gracefully, it's easy to get lost between the layers when one forgets to handle error cases at the appropriate level.

But I digress, lets get back to our program!

We have a nice little query cursor, it's time to loop through the database, extract the counter from the current BSON object, and move on:

while (mongo_sync_cursor_next (c))
{
bson *b = mongo_sync_cursor_get_data (c);
bson_cursor *bc;
gint32 cnt;
if (!b)
{
int e = errno;
mongo_sync_cmd_get_last_error (conn, "lmc", &error);
fprintf (stderr, "Error retrieving cursor data: %s\n",
(error) ? error : strerror (e));
exit (1);
}
mongo_sync_cursor_next
gboolean mongo_sync_cursor_next(mongo_sync_cursor *cursor)
Iterate a MongoDB cursor.
Definition mongo-sync-cursor.c:56
mongo_sync_cursor_get_data
bson * mongo_sync_cursor_get_data(mongo_sync_cursor *cursor)
Retrieve the BSON document at the cursor's position.
Definition mongo-sync-cursor.c:99
mongo_sync_cmd_get_last_error
gboolean mongo_sync_cmd_get_last_error(mongo_sync_connection *conn, const gchar *db, gchar **error)
Get the last error from MongoDB.
Definition mongo-sync.c:1399

At this point, we have the current document in the b variable, handled the error case, and as such, we're ready to dig deep into the BSON object!

bc = bson_find (b, "counter");
bson_cursor_get_int32 (bc, &cnt);
printf ("\rCounter: %d", cnt);
bson_cursor_get_int32
gboolean bson_cursor_get_int32(const bson_cursor *c, gint32 *dest)
Get the value stored at the cursor, as a 32-bit integer.
Definition bson.c:1213
bson_find
bson_cursor * bson_find(const bson *b, const gchar *name)
Create a new cursor positioned at a given key.
Definition bson.c:956

And once we're done working with the BSON object, we free the cursor, and the object, and continue the loop.

bson_cursor_free (bc);
bson_free (b);
}
bson_cursor_free
void bson_cursor_free(bson_cursor *c)
Delete a cursor, and free up all resources used by it.
Definition bson.c:800

And in the end, we emit a newline, and free the cursor to wrap up our query routine.

printf ("\n");
mongo_sync_cursor_free (c);
}
mongo_sync_cursor_free
void mongo_sync_cursor_free(mongo_sync_cursor *cursor)
Free a MongoDB cursor.
Definition mongo-sync-cursor.c:83

All that is left now, is the glue that holds this together, and connects to MongoDB:

int
main (void)
{
mongo_sync_connection *conn;
conn = mongo_sync_connect ("localhost", 27017, FALSE);
if (!conn)
{
fprintf (stderr, "Connection failed: %s\n", strerror (errno));
return 1;
}
mongo_sync_connect
mongo_sync_connection * mongo_sync_connect(const gchar *address, gint port, gboolean slaveok)
Synchronously connect to a MongoDB server.
Definition mongo-sync.c:201
do_inserts (conn);
do_query (conn);
mongo_sync_disconnect (conn);
return 0;
}
mongo_sync_disconnect
void mongo_sync_disconnect(mongo_sync_connection *conn)
Close and free a synchronous MongoDB connection.
Definition mongo-sync.c:396

I believe that does not need any further explanation.

As an exercise, one can add another feature: dropping the temporary collection on error. Or perhaps, count the number of documents returned, and see if and how the count changes between subsequent runs of the test program.