MONGOC_GUIDES(3)
๐ CONFIGURING TLS ๐
okay so bestie, this is MONGOC_GUIDES(3) from libmongoc, and we're doing TLS configuration together. secure connection era, no cap ๐โจ
๐ Configuration with URI options ๐
to turn TLS on, include tls=true in the URI. literally the vibe check:
mongoc_uri_t *uri = mongoc_uri_new ("mongodb://localhost:27017/");
mongoc_uri_set_option_as_bool (uri, MONGOC_URI_TLS, true);
mongoc_client_t *client = mongoc_client_new_from_uri (uri);
and here's the full roster of URI options that may be used to further configure TLS, no skips, no cuts:
- Constant:
MONGOC_URI_TLSยท Key:tlsโ{true|false}, indicating if TLS must be used. slay secure ๐ - Constant:
MONGOC_URI_TLSCERTIFICATEKEYFILEยท Key:tlscertificatekeyfileโ Path to PEM formatted Private Key, with its Public Certificate concatenated at the end. key + cert bestie, glued together ๐ - Constant:
MONGOC_URI_TLSCERTIFICATEKEYFILEPASSWORDยท Key:tlscertificatekeypasswordโ The password, if any, to use to unlock encrypted Private Key. the secret password moment ๐ - Constant:
MONGOC_URI_TLSCAFILEยท Key:tlscafileโ One, or a bundle of, Certificate Authorities whom should be considered to be trusted. trust list energy ๐ - Constant:
MONGOC_URI_TLSALLOWINVALIDCERTIFICATESยท Key:tlsallowinvalidcertificatesโ Accept and ignore certificate verification errors (e.g. untrusted issuer, expired, etc.) โ ๏ธ red flag behavior, but it's literally the option ๐ - Constant:
MONGOC_URI_TLSALLOWINVALIDHOSTNAMESยท Key:tlsallowinvalidhostnamesโ Ignore hostname verification of the certificate (e.g. Man In The Middle, using valid certificate, but issued for another hostname) โ ๏ธ also red flag energy ๐ - Constant:
MONGOC_URI_TLSINSECUREยท Key:tlsinsecureโ{true|false}, indicating if insecure TLS options should be used. Currently this impliesMONGOC_URI_TLSALLOWINVALIDCERTIFICATESandMONGOC_URI_TLSALLOWINVALIDHOSTNAMES. the "I'm choosing chaos" combo ๐ซถ - Constant:
MONGOC_URI_TLSDISABLECERTIFICATEREVOCATIONCHECKยท Key:tlsdisablecertificaterevocationcheckโ{true|false}, indicates if revocation checking (CRL / OCSP) should be disabled. turn off the revocation check if you must ๐ธ - Constant:
MONGOC_URI_TLSDISABLEOCSPENDPOINTCHECKยท Key:tlsdisableocspendpointcheckโ{true|false}, indicates if OCSP responder endpoints should not be requested when an OCSP response is not stapled. OCSP endpoint check, but make it optional โญ
โจ Configuration with mongoc_ssl_opt_t โจ
okay so alternatively bestie, you can skip the URI route and vibe with the mongoc_ssl_opt_t struct directly ๐
you set it up with mongoc_client_set_ssl_opts() or mongoc_client_pool_set_ssl_opts(). and like, most of the configurable options can also be set using the Connection String URI, so pick your fighter.
here's the little key-translation table, but as a list because we don't do tables here ๐:
pem_fileโtlsClientCertificateKeyFilepem_pwdโtlsClientCertificateKeyPasswordca_fileโtlsCAFileweak_cert_validationโtlsAllowInvalidCertificatesallow_invalid_hostnameโtlsAllowInvalidHostnames
the ONLY exclusions arecrl_fileandca_dir๐ those two are struct-only, they may only be set withmongoc_ssl_opt_t. no URI glow-up for them, sorry.
๐ซถ Client Authentication
so when MongoDB gets started with TLS enabled, by default it's gonna require the client to hand over a client certificate. that cert has to be issued by a certificate authority specified by --tlsCAFile, OR by an authority trusted by the native certificate store in use on the server. no cert, no entry, it's giving bouncer at the club ๐ญ
to actually provide the client certificate, you set the tlsCertificateKeyFile in the URI to a PEM armored certificate file. like so:
mongoc_uri_t *uri = mongoc_uri_new ("mongodb://localhost:27017/");
mongoc_uri_set_option_as_bool (uri, MONGOC_URI_TLS, true);
mongoc_uri_set_option_as_utf8 (uri, MONGOC_URI_TLSCERTIFICATEKEYFILE, "/path/to/client-certificate.pem");
mongoc_client_t *client = mongoc_client_new_from_uri (uri);
๐ธ Server Certificate Verification
the MongoDB C Driver will automatically verify the validity of the server certificate โ that means stuff like: issued by the configured Certificate Authority, hostname validation, and expiration. it's thorough, it's responsible, it's the main character of trust ๐ง
but if you wanna overwrite this behavior (why bestie, why ๐ญ), it IS possible to:
- disable hostname validation
- disable OCSP endpoint revocation checking
- disable revocation checking entirely
- and allow invalid certificates
each one is controlled by its own lil option, respectively: tlsAllowInvalidHostnames, tlsDisableOCSPEndpointCheck, tlsDisableCertificateRevocationCheck, and tlsAllowInvalidCertificates. and by default? all of them are set to false. which is the correct, sane, iconique state โจ
โ ๏ธ it is NOT recommended to change these defaults, because it exposes the client to Man In The Middle attacks (whentlsAllowInvalidHostnamesis set), invalid certificates (whentlsAllowInvalidCertificatesis set), or potentially revoked certificates (whentlsDisableOCSPEndpointCheckortlsDisableCertificateRevocationCheckare set). this is the "don't do it" section. don't do it. ๐
๐ Supported Libraries
by default, libmongoc will attempt to find a supported TLS library and enable TLS support. this whole thing is controlled by the cmake flag ENABLE_SSL, which is set to AUTO by default. valid values are:
AUTOโ the default behavior, bestie. link to the system's native TLS library, or attempt to find OpenSSL. it's giving "figure it out yourself" energy.DARWINโ link to Secure Transport, the native TLS library on macOS. ๐WINDOWSโ link to Secure Channel, the native TLS library on Windows. ๐ชOPENSSLโ link to OpenSSL (libssl). an optional install path may be specified withOPENSSL_ROOT.LIBRESSLโ link to LibreSSL's libtls. (and psst, LibreSSL's compatible libssl may be linked to by settingOPENSSL๐)OFFโ disable TLS support. goodbye TLS, we barely knew ye ๐
๐ฅ OpenSSL
the MongoDB C Driver uses OpenSSL, if available, on Linux and Unix platforms (besides macOS, bestie, read the room ๐).
industry best practices and some regulations require the use of TLS 1.1 or newer, and that requires at least OpenSSL 1.0.1. check your OpenSSL version like so:
$ openssl version
make sure your system's OpenSSL is a recent version (at least 1.0.1), or install a recent version in a non-system path and build against it with:
cmake -DOPENSSL_ROOT_DIR=/absolute/path/to/openssl
when compiled against OpenSSL, the driver will attempt to load the system default certificate store, as configured by the distribution. but you can override that by setting the tlsCAFile URI option or with the fields ca_file and ca_dir in the mongoc_ssl_opt_t. so much power ๐ซถ
the Online Certificate Status Protocol (OCSP) (see RFC 6960) is fully supported when using OpenSSL 1.0.1+, with these notes:
- when a
crl_fileis set withmongoc_ssl_opt_t, and thecrl_filerevokes the server's certificate, the certificate is considered revoked (even if the certificate has a valid stapled OCSP response). yeah. revoked is revoked. periodt.
๐ฆ LibreSSL / libtls
the MongoDB C Driver supports LibreSSL through the use of OpenSSL compatibility checks when configured to compile against openssl. it also supports the new libtls library when configured to build against libressl. two flavors, one driver, slay ๐
when compiled against the Windows native libraries, the crl_file option of a mongoc_ssl_opt_t is not supported, and will issue an error if used. ๐ญ
setting tlsDisableOCSPEndpointCheck and tlsDisableCertificateRevocationCheck has no effect. literally nothing happens. vibe check failed.
the Online Certificate Status Protocol (OCSP) (see RFC 6960) is partially supported, with the following notes:
- the Must-Staple extension (see RFC 7633) is ignored. connection may continue if a Must-Staple certificate is presented with no stapled response (unless the client receives a revoked response from an OCSP responder).
- connection will continue if a Must-Staple certificate is presented without a stapled response and the OCSP responder is down.
๐ Native TLS Support on Windows (Secure Channel)
the MongoDB C Driver supports the Windows native TLS library (Secure Channel, or SChannel), and its native crypto library (Cryptography API: Next Generation, or CNG). very official, very windows ๐ชโจ
when compiled against the Windows native libraries, the ca_dir option of a mongoc_ssl_opt_t is not supported, and will issue an error if used.
encrypted PEM files (e.g., setting tlsCertificateKeyPassword) are also not supported, and will result in error when attempting to load them. so no password-protected PEMs over here, bestie.
when tlsCAFile is set, the driver will only allow server certificates issued by the authority (or authorities) provided. when no tlsCAFile is set, the driver will look up the Certificate Authority using the System Local Machine Root certificate store to confirm the provided certificate. it's doing its homework fr fr ๐
when crl_file is set with mongoc_ssl_opt_t, the driver will import the revocation list to the System Local Machine Root certificate store.
setting tlsDisableOCSPEndpointCheck has no effect.
the Online Certificate Status Protocol (OCSP) (see RFC 6960) is partially supported, with the following notes:
- the Must-Staple extension (see RFC 7633) is ignored. connection may continue if a Must-Staple certificate is presented with no stapled response (unless the client receives a revoked response from an OCSP responder).
- when a
crl_fileis set withmongoc_ssl_opt_t, and thecrl_filerevokes the server's certificate, the OCSP response takes precedence. e.g. if the server presents a certificate with a valid stapled OCSP response, the certificate is considered valid even if thecrl_filemarks it as revoked. ๐ plot twist. - connection will continue if a Must-Staple certificate is presented without a stapled response and the OCSP responder is down.
๐ Native TLS Support on macOS / Darwin (Secure Transport)
the MongoDB C Driver supports the Darwin (OS X, macOS, iOS, etc.) native TLS library (Secure Transport), and its native crypto library (Common Crypto, or CC). apple ecosystem gang ๐๐
when compiled against Secure Transport, the ca_dir and crl_file options of a mongoc_ssl_opt_t are not supported. an error is issued if either are used. brutal but honest.
when tlsCAFile is set, the driver will only allow server certificates issued by the authority (or authorities) provided. when no tlsCAFile is set, the driver will use the Certificate Authorities in the currently unlocked keychains. ๐ unlocked keychains only, no sneaking.
setting tlsDisableOCSPEndpointCheck and tlsDisableCertificateRevocationCheck has no effect.
the Online Certificate Status Protocol (OCSP) (see RFC 6960) is partially supported with the following notes.
- the Must-Staple extension (see RFC 7633) is ignored. connection may continue if a Must-Staple certificate is presented with no stapled response (unless the client receives a revoked response from an OCSP responder).
- connection will continue if a Must-Staple certificate is presented without a stapled response and the OCSP responder is down.
COMMON TASKS ๐
okay so bestie, here's the tea โ โ drivers for some other languages are out here handing you lil helper functions for all the common stuff. the C Driver? no ma'am. we build the commands ourselves and send them to the server. it's giving DIY queen energy, and honestly? we ate. ๐
Setup โจ
first things first, we're gonna write some code that inserts sample data. like, we need something in the db before we can be iconic in it, obviously ๐ฆ
doc-common-insert.c
/* Don't try to compile this file on its own. It's meant to be #included
by example code */
/* Insert some sample data */
bool
insert_data (mongoc_collection_t *collection)
{
mongoc_bulk_operation_t *bulk;
enum N { ndocs = 4 };
bson_t *docs[ndocs];
bson_error_t error;
int i = 0;
bool ret;
bulk = mongoc_collection_create_bulk_operation_with_opts (collection, NULL);
docs[0] = BCON_NEW ("x", BCON_DOUBLE (1.0), "tags", "[", "dog", "cat", "]");
docs[1] = BCON_NEW ("x", BCON_DOUBLE (2.0), "tags", "[", "cat", "]");
docs[2] = BCON_NEW (
"x", BCON_DOUBLE (2.0), "tags", "[", "mouse", "cat", "dog", "]");
docs[3] = BCON_NEW ("x", BCON_DOUBLE (3.0), "tags", "[", "]");
for (i = 0; i < ndocs; i++) {
mongoc_bulk_operation_insert (bulk, docs[i]);
bson_destroy (docs[i]);
docs[i] = NULL;
}
ret = mongoc_bulk_operation_execute (bulk, NULL, &error);
if (!ret) {
fprintf (stderr, "Error inserting data: %s\n", error.message);
}
mongoc_bulk_operation_destroy (bulk);
return ret;
}
/* A helper which we'll use a lot later on */
void
print_res (const bson_t *reply)
{
char *str;
BSON_ASSERT (reply);
str = bson_as_canonical_extended_json (reply, NULL);
printf ("%s\n", str);
bson_free (str);
}
๐ซถ literally do NOT try to compiledoc-common-insert.con its own, bestie. it's meant to be#includedby example code. it said what it said.
"explain" Command ๐
so this is how you use the explain command in MongoDB 3.2 and up โ highkey useful for peeking at what the server is actually doing with your query ๐
explain.c
bool
explain (mongoc_collection_t *collection)
{
bson_t *command;
bson_t reply;
bson_error_t error;
bool res;
command = BCON_NEW ("explain",
"{",
"find",
BCON_UTF8 (COLLECTION_NAME),
"filter",
"{",
"x",
BCON_INT32 (1),
"}",
"}");
res = mongoc_collection_command_simple (
collection, command, NULL, &reply, &error);
if (!res) {
fprintf (stderr, "Error with explain: %s\n", error.message);
goto cleanup;
}
/* Do something with the reply */
print_res (&reply);
cleanup:
bson_destroy (&reply);
bson_destroy (command);
return res;
}
Running the Examples ๐
common-operations.c
/*
* Copyright 2016 MongoDB, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <mongoc/mongoc.h>
#include <stdio.h>
const char *COLLECTION_NAME = "things";
#include "../doc-common-insert.c"
#include "explain.c"
int
main (int argc, char *argv[])
{
mongoc_database_t *database = NULL;
mongoc_client_t *client = NULL;
mongoc_collection_t *collection = NULL;
mongoc_uri_t *uri = NULL;
bson_error_t error;
char *host_and_port;
int res = 0;
if (argc < 2 || argc > 3) {
fprintf (stderr,
"usage: %s MONGOD-1-CONNECTION-STRING "
"[MONGOD-2-HOST-NAME:MONGOD-2-PORT]\n",
argv[0]);
fprintf (stderr,
"MONGOD-1-CONNECTION-STRING can be "
"of the following forms:\n");
fprintf (stderr, "localhost\t\t\t\tlocal machine\n");
fprintf (stderr, "localhost:27018\t\t\t\tlocal machine on port 27018\n");
fprintf (stderr,
"mongodb://user:pass@localhost:27017\t"
"local machine on port 27017, and authenticate with username "
"user and password pass\n");
return EXIT_FAILURE;
}
mongoc_init ();
if (strncmp (argv[1], "mongodb://", 10) == 0) {
host_and_port = bson_strdup (argv[1]);
} else {
host_and_port = bson_strdup_printf ("mongodb://%s", argv[1]);
}
uri = mongoc_uri_new_with_error (host_and_port, &error);
if (!uri) {
fprintf (stderr,
"failed to parse URI: %s\n"
"error message: %s\n",
host_and_port,
error.message);
res = EXIT_FAILURE;
goto cleanup;
}
client = mongoc_client_new_from_uri (uri);
if (!client) {
res = EXIT_FAILURE;
goto cleanup;
}
mongoc_client_set_error_api (client, 2);
database = mongoc_client_get_database (client, "test");
collection = mongoc_database_get_collection (database, COLLECTION_NAME);
printf ("Inserting data\n");
if (!insert_data (collection)) {
res = EXIT_FAILURE;
goto cleanup;
}
printf ("explain\n");
if (!explain (collection)) {
res = EXIT_FAILURE;
goto cleanup;
}
cleanup:
if (collection) {
mongoc_collection_destroy (collection);
}
if (database) {
mongoc_database_destroy (database);
}
if (client) {
mongoc_client_destroy (client);
}
if (uri) {
mongoc_uri_destroy (uri);
}
bson_free (host_and_port);
mongoc_cleanup ();
return res;
}
okay so first up, launch two separate instances of mongod โ and bestie, you gotta do this from separate shells, that part is not optional ๐โจ
$ mongod
$ mkdir /tmp/db2
$ mongod --dbpath /tmp/db2 --port 27018 # second instance
๐ fr fr, separate shells. the second instance is the one on port27018and it lives in/tmp/db2. don't fight it.
now we compile and run the example program, and the way that looks is:
$ cd examples/common_operations/$ gcc -Wall -o example common-operations.c $(pkg-config --cflags --libs libmongoc-1.0)$ ./example localhost:27017 localhost:27018
Inserting data
explain
{
"executionStats" : {
"allPlansExecution" : [],
"executionStages" : {
"advanced" : 19,
"direction" : "forward" ,
"docsExamined" : 76,
"executionTimeMillisEstimate" : 0,
"filter" : {
"x" : {
"$eq" : 1
}
},
"invalidates" : 0,
"isEOF" : 1,
"nReturned" : 19,
"needTime" : 58,
"needYield" : 0,
"restoreState" : 0,
"saveState" : 0,
"stage" : "COLLSCAN" ,
"works" : 78
},
"executionSuccess" : true,
"executionTimeMillis" : 0,
"nReturned" : 19,
"totalDocsExamined" : 76,
"totalKeysExamined" : 0
},
"ok" : 1,
"queryPlanner" : {
"indexFilterSet" : false,
"namespace" : "test.things",
"parsedQuery" : {
"x" : {
"$eq" : 1
}
},
"plannerVersion" : 1,
"rejectedPlans" : [],
"winningPlan" : {
"direction" : "forward" ,
"filter" : {
"x" : {
"$eq" : 1
}
},
"stage" : "COLLSCAN"
}
},
"serverInfo" : {
"gitVersion" : "05552b562c7a0b3143a729aaa0838e558dc49b25" ,
"host" : "MacBook-Pro-57.local",
"port" : 27017,
"version" : "3.2.6"
}
}
look at that output bestie ๐ญ๐ "stage" : "COLLSCAN" โ she's scanning the whole collection, no index in sight, and honestly? valid. that's what explain is for. iykyk ๐ฅ
๐ซ ADVANCED CONNECTIONS ๐ซ
okay bestie, this guide is for the specific flavors of MongoDB setups โ the ones that aren't just "one lonely server, no thoughts". Think replica sets, sharded clusters, IPv6, UNIX sockets, TLS, all of it. ๐ฆ
For an example of connecting to a simple standalone server, peep the Tutorial. Wanna establish a connection with authentication options enabled? That's the Authentication page. Wanna see a connection with data compression? Go to the Data Compression page. iykyk ๐
๐ Connecting to a Replica Set
Connecting to a replica set is literally just like connecting to a standalone MongoDB server. The only extra thing? You tell it the replica set name with the ?replicaSet=myreplset URI option. That's it. That's the whole vibe. โจ
#include <bson/bson.h>
#include <mongoc/mongoc.h>
int
main (int argc, char *argv[])
{
mongoc_client_t *client;
mongoc_init ();
/* Create our MongoDB Client */
client = mongoc_client_new (
"mongodb://host01:27017,host02:27017,host03:27017/?replicaSet=myreplset");
/* Do some work */
/* TODO */
/* Clean up */
mongoc_client_destroy (client);
mongoc_cleanup ();
return 0;
}
TIP: ๐ก You can put multiple hostnames in the MongoDB connection string URI โ just separate the hosts in the seed list with a comma. And fr fr, it's recommended to use a seed list of members of the replica set so the driver can connect to any node. Flexibility, queen behavior. ๐
๐งฉ Connecting to a Sharded Cluster
To connect to a sharded cluster, you specify the mongos nodes the client should connect to. The C Driver will automatically detect that it has connected to a mongos sharding server โ no extra config, it just knows. ๐ฎ
If more than one hostname is specified, a seed list gets created to attempt failover between the mongos instances. Resilient. ๐ช
WARNING: โ ๏ธ Specifying thereplicaSetparameter when connecting to amongossharding server is invalid. Do not do it. It's giving chaos, and not the fun kind. ๐
#include <bson/bson.h>
#include <mongoc/mongoc.h>
int
main (int argc, char *argv[])
{
mongoc_client_t *client;
mongoc_init ();
/* Create our MongoDB Client */
client = mongoc_client_new ("mongodb://myshard01:27017/");
/* Do something with client ... */
/* Free the client */
mongoc_client_destroy (client);
mongoc_cleanup ();
return 0;
}
๐ Connecting to an IPv6 Address
The MongoDB C Driver will automatically resolve IPv6 addresses from host names. But if you wanna specify an IPv6 address directly, you gotta wrap the address in []. Those brackets are non-negotiable bestie. ๐
mongoc_uri_t *uri = mongoc_uri_new ("mongodb://[::1]:27017");
๐ช Connecting with IPv4 and IPv6
If you're connecting to a hostname that has both IPv4 and IPv6 DNS records, the behavior follows RFC-6555. Here's the whole storyline:
- A connection to the IPv6 address is attempted first. ๐ฅ
- If IPv6 fails, then a connection is attempted to the IPv4 address.
- If the connection attempt to IPv6 does not complete within 250ms, then IPv4 is tried in parallel.
- Whichever succeeds connection first cancels the other.
- The successful DNS result is cached for 10 minutes. โณ
As a consequence (and this is the part that'll humble you ๐), attempts to connect to a mongod only listening on IPv4 may be delayed if there are both A (IPv4) and AAAA (IPv6) DNS records associated with the host.
To avoid a delay, configure hostnames to match the MongoDB configuration. That is, only create an A record if the mongod is only listening on IPv4. Match the energy of your setup, periodt. ๐
๐ Connecting to a UNIX Domain Socket
On UNIX-like systems, the C Driver can connect directly to a MongoDB server using a UNIX domain socket. Pass the URL-encoded path to the socket โ and it must be suffixed with .sock. No .sock, no connection, that's the rule. ๐งฆ
For example, to connect to a domain socket at /tmp/mongodb-27017.sock:
mongoc_uri_t *uri = mongoc_uri_new ("mongodb://%2Ftmp%2Fmongodb-27017.sock");
Include username and password like so:
mongoc_uri_t *uri = mongoc_uri_new ("mongodb://user:pass@%2Ftmp%2Fmongodb-27017.sock");
๐ Connecting to a server over TLS
These are instructions for configuring TLS/SSL connections. Locking it down, keeping it cute. ๐ซถ
To run a server locally (on port 27017, for example):
$ mongod --port 27017 --tlsMode requireTLS --tlsCertificateKeyFile server.pem --tlsCAFile ca.pem
Add /?tls=true to the end of a client URI.
mongoc_client_t *client = NULL;
client = mongoc_client_new ("mongodb://localhost:27017/?tls=true");
MongoDB requires client certificates by default, unless the --tlsAllowConnectionsWithoutCertificates is provided. The C Driver can be configured to present a client certificate using the URI option tlsCertificateKeyFile, which may be referenced through the constant MONGOC_URI_TLSCERTIFICATEKEYFILE.
mongoc_client_t *client = NULL;
mongoc_uri_t *uri = mongoc_uri_new ("mongodb://localhost:27017/?tls=true");
mongoc_uri_set_option_as_utf8 (uri, MONGOC_URI_TLSCERTIFICATEKEYFILE, "client.pem");
client = mongoc_client_new_from_uri (uri);
The client certificate provided by tlsCertificateKeyFile must be issued by one of the server trusted Certificate Authorities listed in --tlsCAFile, or issued by a CA in the native certificate store on the server when omitted. So likeโฆ your cert needs a valid glow-up from a trusted source. ๐
See Configuring TLS for more information on the various TLS related options.
๐๏ธ Compressing data to and from MongoDB
This content has been relocated to the Data Compression page. (She moved out, she's thriving elsewhere. ๐ฆ)
๐๏ธ Additional Connection Options
The full list of connection options can be found in the mongoc_uri_t docs.
Certain socket/connection related options are not configurable โ the driver has already made these decisions for you, bestie, no notes:
SO_KEEPALIVEโ TCP Keep Alive โ Value:EnabledTCP_KEEPIDLEโ How long a connection needs to remain idle before TCP starts sending keepalive probes โ Value:120 secondsTCP_KEEPINTVLโ The time in seconds between TCP probes โ Value:10 secondsTCP_KEEPCNTโ How many probes to send, without acknowledgement, before dropping the connection โ Value:9 probesTCP_NODELAYโ Send packets as soon as possible or buffer small packets (Nagle algorithm) โ Value:Enabled (no buffering)
๐ CONNECTION POOLING ๐
Okay bestie, listen up, because this is the thing that makes or breaks your app's whole vibe. The MongoDB C driver has two connection modes: single-threaded and pooled. Periodt.
- Single-threaded mode is optimized for embedding the driver within languages like PHP. It's giving "I'm just a little guy, let me do my thing."
- Multi-threaded programs should use pooled mode: this mode minimizes the total connection count, and in pooled mode background threads monitor the MongoDB server topology, so the program need not block to scan it. Iconic behavior honestly.
๐ธ Single Mode ๐ธ
In single mode, your program creates a mongoc_client_t directly:
mongoc_client_t *client = mongoc_client_new (
"mongodb://hostA,hostB/?replicaSet=my_rs");
The client connects on demand when your program first uses it for a MongoDB operation. Using a non-blocking socket per server, it begins a check on each server concurrently, and uses the asynchronous poll or select function to receive events from the sockets, until all have responded or timed out. Put another way, in single-threaded mode the C Driver fans out to begin all checks concurrently, then fans in once all checks have completed or timed out. Once the scan completes, the client executes your program's operation and returns. Slay.
In single mode, the client re-scans the server topology roughly once per minute. If more than a minute has elapsed since the previous scan, the next operation on the client will block while the client completes its scan. This interval is configurable with heartbeatFrequencyMS in the connection string. (See mongoc_uri_t.) Yes bestie, you can tune it, you're welcome. ๐ซถ
A single client opens one connection per server in your topology: these connections are used both for scanning the topology and performing normal operations. Efficient? Kind of. Simple? Absolutely.
๐ Pooled Mode ๐
To activate pooled mode, create a mongoc_client_pool_t:
mongoc_uri_t *uri = mongoc_uri_new (
"mongodb://hostA,hostB/?replicaSet=my_rs");
mongoc_client_pool_t *pool = mongoc_client_pool_new (uri);
When your program first calls mongoc_client_pool_pop(), the pool launches monitoring threads in the background. Monitoring threads independently connect to all servers in the connection string. As monitoring threads receive hello responses from the servers, they update the shared view of the server topology. Additional monitoring threads and connections are created as new servers are discovered. Monitoring threads are terminated when servers are removed from the shared view of the server topology. It's giving living ecosystem and I'm obsessed. ๐ฆ
Each thread that executes MongoDB operations must check out a client from the pool:
mongoc_client_t *client = mongoc_client_pool_pop (pool);
/* use the client for operations ... */
mongoc_client_pool_push (pool, client);
โ ๏ธ Themongoc_client_tobject is not thread-safe, only themongoc_client_pool_tis. Do NOT be out here sharing your client between threads, bestie. That's the forbidden vibe. ๐
When the driver is in pooled mode, your program's operations are unblocked as soon as monitoring discovers a usable server. For example, if a thread in your program is waiting to execute an "insert" on the primary, it is unblocked as soon as the primary is discovered, rather than waiting for all secondaries to be checked as well. Main character energy, fr fr. โจ
The pool opens one connection per server for monitoring, and each client opens its own connection to each server it uses for application operations. Background monitoring threads re-scan servers independently roughly every 10 seconds. This interval is configurable with heartbeatFrequencyMS in the connection string. (See mongoc_uri_t.)
The connection string can also specify waitQueueTimeoutMS to limit the time that mongoc_client_pool_pop() will wait for a client from the pool. (See mongoc_uri_t.) If waitQueueTimeoutMS is specified, then it is necessary to confirm that a client was actually returned:
mongoc_uri_t *uri = mongoc_uri_new (
"mongodb://hostA,hostB/?replicaSet=my_rs&waitQueueTimeoutMS=1000");
mongoc_client_pool_t *pool = mongoc_client_pool_new (uri);
mongoc_client_t *client = mongoc_client_pool_pop (pool);
if (client) {
/* use the client for operations ... */
mongoc_client_pool_push (pool, client);
} else {
/* take appropriate action for a timeout */
}
See Connection Pool Options to configure pool size and behavior, and see mongoc_client_pool_t for an extended example of a multi-threaded program that uses the driver in pooled mode. Go read it, it's tea. ๐
๐ DATA COMPRESSION ๐
The following guide explains how data compression support works between the MongoDB server and client. It also shows an example of how to connect to a server with data compression. Less bytes, more slay. ๐
โจ Compressing data to and from MongoDB โจ
MongoDB 3.4 added Snappy compression support, while zlib compression was added in 3.6, and zstd compression in 4.2. To enable compression support the client must be configured with which compressors to use:
mongoc_client_t *client = NULL;
client = mongoc_client_new ("mongodb://localhost:27017/?compressors=snappy,zlib,zstd");
The compressors option specifies the priority order of compressors the client wants to use. Messages are compressed if the client and server share any compressors in common. Mutual understanding? We love to see it. ๐ซถ
๐ Note that the compressor used by the server might not be the same compressor as the client used. For example, if the client uses the connection string compressors=zlib,snappy the client will use zlib compression to send data (if possible), but the server might still reply using snappy, depending on how the server was configured. Not me finding out the reply energy doesn't match the send energy. ๐ญ
The driver must be built with zlib and/or snappy and/or zstd support to enable compression support, any unknown (or not compiled in) compressor value will be ignored. So if your compression isn't working... check your build, bestie. It's not a skill issue, it's a build issue. ๐
โจ CURSORS โจ
ok so cursors bestie. A cursor lives on the MongoDB server, right? But the mongoc_cursor_t struct is your local process's little handle to it โ your emotional support pointer, if you will. And because it's a network thing, stuff can go wrong on the server while you're just sitting there iterating on the client. Network partition? Could never happen to me... except it totally can. So your app needs to be robust about cursor failures. No cap.
๐ Handling Cursor Failures
While you're iterating cursors, you gotta check whether an error happened. Like, actually check. Here's how you do it properly:
static void
print_all_documents (mongoc_collection_t *collection)
{
mongoc_cursor_t *cursor;
const bson_t *doc;
bson_error_t error;
bson_t query = BSON_INITIALIZER;
char *str;
cursor = mongoc_collection_find_with_opts (collection, query, NULL, NULL);
while (mongoc_cursor_next (cursor, &doc)) {
str = bson_as_canonical_extended_json (doc, NULL);
printf ("%s\n", str);
bson_free (str);
}
if (mongoc_cursor_error (cursor, &error)) {
fprintf (stderr, "Failed to iterate all documents: %s\n", error.message);
}
mongoc_cursor_destroy (cursor);
}
See that? Loop, then check the error. That's the move. ๐ซถ
๐ Destroying Server-Side Cursors
The MongoDB C driver will automatically destroy a server-side cursor when you call mongoc_cursor_destroy(). And if you don't call it when you're done with a cursor? Two problems, babes:
- you leak memory on the client side
- you eat up extra memory on the server side
And if that cursor was configured to never timeout, it becomes a straight-up memory leak on the server. Not cute. Not the vibe. Call mongoc_cursor_destroy().
๐ธ Tailable Cursors
Tailable cursors are cursors that stay open even after they've handed you a final result. The way that works is: if more documents get added to a collection (i.e. to the cursor's result set), you can just keep calling mongoc_cursor_next() and grab those extra results. Infinite scroll but make it database. Obsessed.
Here's a whole complete test case showing tailable cursors in action:
NOTE: Tailable cursors are for capped collections only. That's the rule, bestie.
An example that tails the oplog from a replica set:
mongoc-tail.c
#include <bson/bson.h>
#include <mongoc/mongoc.h>
#include <stdio.h>
#include <stdlib.h>
#ifdef _WIN32
#define sleep(_n) Sleep ((_n) *1000)
#endif
static void
print_bson (const bson_t *b)
{
char *str;
str = bson_as_canonical_extended_json (b, NULL);
fprintf (stdout, "%s\n", str);
bson_free (str);
}
static mongoc_cursor_t *
query_collection (mongoc_collection_t *collection, uint32_t last_time)
{
mongoc_cursor_t *cursor;
bson_t query;
bson_t gt;
bson_t opts;
BSON_ASSERT (collection);
bson_init (&query);
BSON_APPEND_DOCUMENT_BEGIN (&query, "ts", >);
BSON_APPEND_TIMESTAMP (>, "$gt", last_time, 0);
bson_append_document_end (&query, >);
bson_init (&opts);
BSON_APPEND_BOOL (&opts, "tailable", true);
BSON_APPEND_BOOL (&opts, "awaitData", true);
cursor = mongoc_collection_find_with_opts (collection, &query, &opts, NULL);
bson_destroy (&query);
bson_destroy (&opts);
return cursor;
}
static void
tail_collection (mongoc_collection_t *collection)
{
mongoc_cursor_t *cursor;
uint32_t last_time;
const bson_t *doc;
bson_error_t error;
bson_iter_t iter;
BSON_ASSERT (collection);
last_time = (uint32_t) time (NULL);
while (true) {
cursor = query_collection (collection, last_time);
while (!mongoc_cursor_error (cursor, &error) &&
mongoc_cursor_more (cursor)) {
if (mongoc_cursor_next (cursor, &doc)) {
if (bson_iter_init_find (&iter, doc, "ts") &&
BSON_ITER_HOLDS_TIMESTAMP (&iter)) {
bson_iter_timestamp (&iter, &last_time, NULL);
}
print_bson (doc);
}
}
if (mongoc_cursor_error (cursor, &error)) {
if (error.domain == MONGOC_ERROR_SERVER) {
fprintf (stderr, "%s\n", error.message);
exit (1);
}
}
mongoc_cursor_destroy (cursor);
sleep (1);
}
}
int
main (int argc, char *argv[])
{
mongoc_collection_t *collection;
mongoc_client_t *client;
mongoc_uri_t *uri;
bson_error_t error;
if (argc != 2) {
fprintf (stderr, "usage: %s MONGO_URI\n", argv[0]);
return EXIT_FAILURE;
}
mongoc_init ();
uri = mongoc_uri_new_with_error (argv[1], &error);
if (!uri) {
fprintf (stderr,
"failed to parse URI: %s\n"
"error message: %s\n",
argv[1],
error.message);
return EXIT_FAILURE;
}
client = mongoc_client_new_from_uri (uri);
if (!client) {
return EXIT_FAILURE;
}
mongoc_client_set_error_api (client, 2);
collection = mongoc_client_get_collection (client, "local", "oplog.rs");
tail_collection (collection);
mongoc_collection_destroy (collection);
mongoc_uri_destroy (uri);
mongoc_client_destroy (client);
return EXIT_SUCCESS;
}
Let's compile and run this example against a replica set so you can watch updates roll in as they happen. Literally live updates. Main character energy fr.
$ gcc -Wall -o mongoc-tail mongoc-tail.c $(pkg-config --cflags --libs libmongoc-1.0)
$ ./mongoc-tail mongodb://example.com/?replicaSet=myReplSet
{
"h" : -8458503739429355503,
"ns" : "test.test",
"o" : {
"_id" : {
"$oid" : "5372ab0a25164be923d10d50"
}
},
"op" : "i",
"ts" : {
"$timestamp" : {
"i" : 1,
"t" : 1400023818
}
},
"v" : 2
}
That line of output is a sample from running db.test.insert({}) from the mongo shell on the replica set. Slay. ๐
SEE ALSO:
mongoc_cursor_set_max_await_time_ms().
๐ BULK WRITE OPERATIONS ๐
This tutorial explains how to take advantage of the MongoDB C driver's bulk write operation features. Executing write operations in batches cuts down the number of network round trips, which means increased write throughput. Fewer trips, more writes. It's giving efficiency.
๐ฆ Bulk Insert
First things first, we need to grab a bulk operation handle from the mongoc_collection_t.
mongoc_bulk_operation_t *bulk =
mongoc_collection_create_bulk_operation_with_opts (collection, NULL);
Now we can start inserting documents into the bulk operation. These get buffered until we actually execute the thing. Just chilling there, waiting.
The bulk operation will coalesce insertions into a single batch for each consecutive call to mongoc_bulk_operation_insert(). That creates a pipelined effect when possible. Pipelining?? Gorgeous.
To execute the bulk operation and receive the result, we call mongoc_bulk_operation_execute().
bulk1.c
#include <assert.h>
#include <mongoc/mongoc.h>
#include <stdio.h>
static void
bulk1 (mongoc_collection_t *collection)
{
mongoc_bulk_operation_t *bulk;
bson_error_t error;
bson_t *doc;
bson_t reply;
char *str;
bool ret;
int i;
bulk = mongoc_collection_create_bulk_operation_with_opts (collection, NULL);
for (i = 0; i < 10000; i++) {
doc = BCON_NEW ("i", BCON_INT32 (i));
mongoc_bulk_operation_insert (bulk, doc);
bson_destroy (doc);
}
ret = mongoc_bulk_operation_execute (bulk, &reply, &error);
str = bson_as_canonical_extended_json (&reply, NULL);
printf ("%s\n", str);
bson_free (str);
if (!ret) {
fprintf (stderr, "Error: %s\n", error.message);
}
bson_destroy (&reply);
mongoc_bulk_operation_destroy (bulk);
}
int
main (void)
{
mongoc_client_t *client;
mongoc_collection_t *collection;
const char *uri_string = "mongodb://localhost/?appname=bulk1-example";
mongoc_uri_t *uri;
bson_error_t error;
mongoc_init ();
uri = mongoc_uri_new_with_error (uri_string, &error);
if (!uri) {
fprintf (stderr,
"failed to parse URI: %s\n"
"error message: %s\n",
uri_string,
error.message);
return EXIT_FAILURE;
}
client = mongoc_client_new_from_uri (uri);
if (!client) {
return EXIT_FAILURE;
}
mongoc_client_set_error_api (client, 2);
collection = mongoc_client_get_collection (client, "test", "test");
bulk1 (collection);
mongoc_uri_destroy (uri);
mongoc_collection_destroy (collection);
mongoc_client_destroy (client);
mongoc_cleanup ();
return EXIT_SUCCESS;
}
Example reply document:
{"nInserted" : 10000,
"nMatched" : 0,
"nModified" : 0,
"nRemoved" : 0,
"nUpserted" : 0,
"writeErrors" : []
"writeConcernErrors" : [] }
๐ Mixed Bulk Write Operations
The MongoDB C driver also supports executing mixed bulk write operations. A batch of insert, update, and remove operations can all be executed together using the bulk write operations API. Variety! Spice! ๐ฅ
โญ Ordered Bulk Write Operations
Ordered bulk write operations are batched and sent to the server in the order you provided, for serial execution. The reply document describes the type and count of the operations that were performed. Receipts, basically.
bulk2.c
#include <assert.h>
#include <mongoc/mongoc.h>
#include <stdio.h>
static void
bulk2 (mongoc_collection_t *collection)
{
mongoc_bulk_operation_t *bulk;
bson_error_t error;
bson_t *query;
bson_t *doc;
bson_t *opts;
bson_t reply;
char *str;
bool ret;
int i;
bulk = mongoc_collection_create_bulk_operation_with_opts (collection, NULL);
/* Remove everything */
query = bson_new ();
mongoc_bulk_operation_remove (bulk, query);
bson_destroy (query);
/* Add a few documents */
for (i = 1; i < 4; i++) {
doc = BCON_NEW ("_id", BCON_INT32 (i));
mongoc_bulk_operation_insert (bulk, doc);
bson_destroy (doc);
}
/* {_id: 1} => {$set: {foo: "bar"}} */ query = BCON_NEW ("_id", BCON_INT32 (1)); doc = BCON_NEW ("$set", "{", "foo", BCON_UTF8 ("bar"), "}"); mongoc_bulk_operation_update_many_with_opts (bulk, query, doc, NULL, &error); bson_destroy (query); bson_destroy (doc);
/* {_id: 4} => {'$inc': {'j': 1}} (upsert) */ opts = BCON_NEW ("upsert", BCON_BOOL (true)); query = BCON_NEW ("_id", BCON_INT32 (4)); doc = BCON_NEW ("$inc", "{", "j", BCON_INT32 (1), "}"); mongoc_bulk_operation_update_many_with_opts (bulk, query, doc, opts, &error); bson_destroy (query); bson_destroy (doc); bson_destroy (opts);
/* replace {j:1} with {j:2} */ query = BCON_NEW ("j", BCON_INT32 (1)); doc = BCON_NEW ("j", BCON_INT32 (2)); mongoc_bulk_operation_replace_one_with_opts (bulk, query, doc, NULL, &error); bson_destroy (query); bson_destroy (doc);
ret = mongoc_bulk_operation_execute (bulk, &reply, &error);
str = bson_as_canonical_extended_json (&reply, NULL); printf ("%s\n", str); bson_free (str);
if (!ret) { printf ("Error: %s\n", error.message); }
bson_destroy (&reply); mongoc_bulk_operation_destroy (bulk); }
int main (void) { mongoc_client_t *client; mongoc_collection_t *collection; const char *uri_string = "mongodb://localhost/?appname=bulk2-example"; mongoc_uri_t *uri; bson_error_t error;
mongoc_init ();
uri = mongoc_uri_new_with_error (uri_string, &error); if (!uri) { fprintf (stderr, "failed to parse URI: %s\n" "error message: %s\n", uri_string, error.message); return EXIT_FAILURE; }
client = mongoc_client_new_from_uri (uri); if (!client) { return EXIT_FAILURE; }
mongoc_client_set_error_api (client, 2); collection = mongoc_client_get_collection (client, "test", "test");
bulk2 (collection);
mongoc_uri_destroy (uri); mongoc_collection_destroy (collection); mongoc_client_destroy (client);
mongoc_cleanup ();
return EXIT_SUCCESS; }
<purple>and the reply doc it hands back? bestie it's giving *receipts*:</purple>
{ "nInserted" : 3, "nMatched" : 2, "nModified" : 2, "nRemoved" : 10000, "nUpserted" : 1, "upserted" : [{"index" : 5, "_id" : 4}], "writeErrors" : [] "writeConcernErrors" : [] }
<pink>okay so real talk about that `index` field in the `upserted` array ๐
โ it's the **0-based index of the upsert operation**. In this example, the sixth operation of the whole bulk operation was the upsert, so its index is `5`. Math, but make it fashion. โจ</pink>
---
## <rainbow>Unordered Bulk Write Operations</rainbow> ๐ฆ
<purple>So unordered bulk write operations get **batched and shipped off to the server in arbitrary order**, where they may be executed *in parallel*. Any errors that pop up get reported **after all the operations have been attempted** โ so she's not stopping the whole party for one little oopsie. ๐</purple>
<pink>In this next example, the <red>first and third</red> operations fail because of the unique constraint on `_id`. But since we're doing **unordered** execution, the second and fourth operations still <green>succeed</green>. Slay. ๐</pink>
### `bulk3.c`
#include <assert.h> #include <mongoc/mongoc.h> #include <stdio.h>
static void bulk3 (mongoc_collection_t *collection) { bson_t opts = BSON_INITIALIZER; mongoc_bulk_operation_t *bulk; bson_error_t error; bson_t *query; bson_t *doc; bson_t reply; char *str; bool ret;
/* false indicates unordered */ BSON_APPEND_BOOL (&opts, "ordered", false); bulk = mongoc_collection_create_bulk_operation_with_opts (collection, &opts); bson_destroy (&opts);
/* Add a document */ doc = BCON_NEW ("_id", BCON_INT32 (1)); mongoc_bulk_operation_insert (bulk, doc); bson_destroy (doc);
/* remove {_id: 2} */ query = BCON_NEW ("_id", BCON_INT32 (2)); mongoc_bulk_operation_remove_one (bulk, query); bson_destroy (query);
/* insert {_id: 3} */ doc = BCON_NEW ("_id", BCON_INT32 (3)); mongoc_bulk_operation_insert (bulk, doc); bson_destroy (doc);
/* replace {_id:4} {'i': 1} */ query = BCON_NEW ("_id", BCON_INT32 (4)); doc = BCON_NEW ("i", BCON_INT32 (1)); mongoc_bulk_operation_replace_one (bulk, query, doc, false); bson_destroy (query); bson_destroy (doc);
ret = mongoc_bulk_operation_execute (bulk, &reply, &error);
str = bson_as_canonical_extended_json (&reply, NULL); printf ("%s\n", str); bson_free (str);
if (!ret) { printf ("Error: %s\n", error.message); }
bson_destroy (&reply); mongoc_bulk_operation_destroy (bulk); bson_destroy (&opts); }
int main (void) { mongoc_client_t *client; mongoc_collection_t *collection; const char *uri_string = "mongodb://localhost/?appname=bulk3-example"; mongoc_uri_t *uri; bson_error_t error;
mongoc_init ();
uri = mongoc_uri_new_with_error (uri_string, &error); if (!uri) { fprintf (stderr, "failed to parse URI: %s\n" "error message: %s\n", uri_string, error.message); return EXIT_FAILURE; }
client = mongoc_client_new_from_uri (uri); if (!client) { return EXIT_FAILURE; }
mongoc_client_set_error_api (client, 2); collection = mongoc_client_get_collection (client, "test", "test");
bulk3 (collection);
mongoc_uri_destroy (uri); mongoc_collection_destroy (collection); mongoc_client_destroy (client);
mongoc_cleanup ();
return EXIT_SUCCESS; }
<purple>Example reply document โ and look at those `writeErrors` just *sitting there*, being messy:</purple>
{ "nInserted" : 0, "nMatched" : 1, "nModified" : 1, "nRemoved" : 1, "nUpserted" : 0, "writeErrors" : [ { "index" : 0, "code" : 11000, "errmsg" : "E11000 duplicate key error index: test.test.$_id_ dup key: { : 1 }" }, { "index" : 2, "code" : 11000, "errmsg" : "E11000 duplicate key error index: test.test.$_id_ dup key: { : 3 }" } ], "writeConcernErrors" : [] }
Error: E11000 duplicate key error index: test.test.$_id_ dup key: { : 1 }
<pink>and FYI bestie, the `bson_error_t` <cyan>domain</cyan> is `MONGOC_ERROR_COMMAND` and its <cyan>code</cyan> is `11000`. iykyk ๐
</pink>
---
## <rainbow>Bulk Operation Bypassing Document Validation</rainbow> ๐
> <red>Heads up:</red> this feature is **only available when using MongoDB 3.2 and later**. No cap. ๐ซ
<purple>By default, bulk operations get **validated against the schema** โ *if any is defined*. But sometimes? You just gotta skip the bouncer and go straight in, and it may be necessary to **bypass the document validation**. ๐</purl>
### `bulk5.c`
#include <assert.h> #include <mongoc/mongoc.h> #include <stdio.h>
static void bulk5_fail (mongoc_collection_t *collection) { mongoc_bulk_operation_t *bulk; bson_error_t error; bson_t *doc; bson_t reply; char *str; bool ret;
bulk = mongoc_collection_create_bulk_operation_with_opts (collection, NULL);
/* Two inserts */ doc = BCON_NEW ("_id", BCON_INT32 (31)); mongoc_bulk_operation_insert (bulk, doc); bson_destroy (doc);
doc = BCON_NEW ("_id", BCON_INT32 (32)); mongoc_bulk_operation_insert (bulk, doc); bson_destroy (doc);
/* The above documents do not comply to the schema validation rules
- we created previously, so this will result in an error */ ret = mongoc_bulk_operation_execute (bulk, &reply, &error);
str = bson_as_canonical_extended_json (&reply, NULL); printf ("%s\n", str); bson_free (str);
if (!ret) { printf ("Error: %s\n", error.message); }
bson_destroy (&reply); mongoc_bulk_operation_destroy (bulk); }
static void bulk5_success (mongoc_collection_t *collection) { mongoc_bulk_operation_t *bulk; bson_error_t error; bson_t *doc; bson_t reply; char *str; bool ret;
bulk = mongoc_collection_create_bulk_operation_with_opts (collection, NULL);
/* Allow this document to bypass document validation.
- NOTE: When authentication is enabled, the authenticated user must have
- either the "dbadmin" or "restore" roles to bypass document validation */ mongoc_bulk_operation_set_bypass_document_validation (bulk, true);
/* Two inserts */ doc = BCON_NEW ("_id", BCON_INT32 (31)); mongoc_bulk_operation_insert (bulk, doc); bson_destroy (doc);
doc = BCON_NEW ("_id", BCON_INT32 (32)); mongoc_bulk_operation_insert (bulk, doc); bson_destroy (doc);
ret = mongoc_bulk_operation_execute (bulk, &reply, &error);
str = bson_as_canonical_extended_json (&reply, NULL); printf ("%s\n", str); bson_free (str);
if (!ret) { printf ("Error: %s\n", error.message); }
bson_destroy (&reply); mongoc_bulk_operation_destroy (bulk); }
int main (void) { bson_t *options; bson_error_t error; mongoc_client_t *client; mongoc_collection_t *collection; mongoc_database_t *database; const char *uri_string = "mongodb://localhost/?appname=bulk5-example"; mongoc_uri_t *uri;
mongoc_init ();
uri = mongoc_uri_new_with_error (uri_string, &error); if (!uri) { fprintf (stderr, "failed to parse URI: %s\n" "error message: %s\n", uri_string, error.message); return EXIT_FAILURE; }
client = mongoc_client_new_from_uri (uri); if (!client) { return EXIT_FAILURE; }
mongoc_client_set_error_api (client, 2); database = mongoc_client_get_database (client, "testasdf");
/* Create schema validator */ options = BCON_NEW ( "validator", "{", "number", "{", "$gte", BCON_INT32 (5), "}", "}"); collection = mongoc_database_create_collection (database, "collname", options, &error);
if (collection) { bulk5_fail (collection); bulk5_success (collection); mongoc_collection_destroy (collection); } else { fprintf (stderr, "Couldn't create collection: '%s'\n", error.message); }
bson_free (options); mongoc_uri_destroy (uri); mongoc_database_destroy (database); mongoc_client_destroy (client);
mongoc_cleanup ();
return EXIT_SUCCESS; }
And when you actually run that example, bestie, you get this:
{ "nInserted" : 0, "nMatched" : 0, "nModified" : 0, "nRemoved" : 0, "nUpserted" : 0, "writeErrors" : [ { "index" : 0, "code" : 121, "errmsg" : "Document failed validation" } ] }
Error: Document failed validation
{ "nInserted" : 2, "nMatched" : 0, "nModified" : 0, "nRemoved" : 0, "nUpserted" : 0, "writeErrors" : [] }
The <cyan>`bson_error_t`</cyan> domain is <purple>`MONGOC_ERROR_COMMAND`</purple>. Write that down, iykyk. โจ
---
## ๐ Bulk Operation Write Concerns ๐
Okay so <pink>by default</pink>, bulk operations get executed with the <hotpink>`write_concern`</hotpink> of the collection they're being run against. But! If you want a <purple>custom write concern</purple>, you just pass it to <cyan>`mongoc_collection_create_bulk_operation_with_opts()`</cyan>. Easy.
And lowkey important: write concern errors (think <yellow>`wtimeout`</yellow>) get reported <red>after all the operations are attempted</red>, no matter what order they executed in. So don't expect them to pop up mid-way. ๐
**`bulk4.c`**
#include <assert.h> #include <mongoc/mongoc.h> #include <stdio.h>
static void bulk4 (mongoc_collection_t *collection) { bson_t opts = BSON_INITIALIZER; mongoc_write_concern_t *wc; mongoc_bulk_operation_t *bulk; bson_error_t error; bson_t *doc; bson_t reply; char *str; bool ret;
wc = mongoc_write_concern_new (); mongoc_write_concern_set_w (wc, 4); mongoc_write_concern_set_wtimeout_int64 (wc, 100); /* milliseconds */ mongoc_write_concern_append (wc, &opts);
bulk = mongoc_collection_create_bulk_operation_with_opts (collection, &opts);
/* Two inserts */ doc = BCON_NEW ("_id", BCON_INT32 (10)); mongoc_bulk_operation_insert (bulk, doc); bson_destroy (doc);
doc = BCON_NEW ("_id", BCON_INT32 (11)); mongoc_bulk_operation_insert (bulk, doc); bson_destroy (doc);
ret = mongoc_bulk_operation_execute (bulk, &reply, &error);
str = bson_as_canonical_extended_json (&reply, NULL); printf ("%s\n", str); bson_free (str);
if (!ret) { printf ("Error: %s\n", error.message); }
bson_destroy (&reply); mongoc_bulk_operation_destroy (bulk); mongoc_write_concern_destroy (wc); bson_destroy (&opts); }
int main (void) { mongoc_client_t *client; mongoc_collection_t *collection; const char *uri_string = "mongodb://localhost/?appname=bulk4-example"; mongoc_uri_t *uri; bson_error_t error;
mongoc_init ();
uri = mongoc_uri_new_with_error (uri_string, &error); if (!uri) { fprintf (stderr, "failed to parse URI: %s\n" "error message: %s\n", uri_string, error.message); return EXIT_FAILURE; }
client = mongoc_client_new_from_uri (uri); if (!client) { return EXIT_FAILURE; }
mongoc_client_set_error_api (client, 2); collection = mongoc_client_get_collection (client, "test", "test");
bulk4 (collection);
mongoc_uri_destroy (uri); mongoc_collection_destroy (collection); mongoc_client_destroy (client);
mongoc_cleanup ();
return EXIT_SUCCESS; }
The example reply document and error message look like this:
{ "nInserted" : 2, "nMatched" : 0, "nModified" : 0, "nRemoved" : 0, "nUpserted" : 0, "writeErrors" : [], "writeConcernErrors" : [ { "code" : 64, "errmsg" : "waiting for replication timed out" } ] }
Error: waiting for replication timed out
And here's the tea on the <cyan>`bson_error_t`</cyan> domain: it's <purple>`MONGOC_ERROR_WRITE_CONCERN`</purple> <yellow>if</yellow> there are write concern errors <pink>and no write errors</pink>. Because write errors mean operations literally failed, so they <red>take precedence</red> over write concern errors โ and write concern errors just mean the write concern isn't satisfied <purple>yet</purple>. Not the same thing, fr fr. ๐ซถ
---
## ๐ฆ Setting Collation Order ๐ฆ
Heads up bestie, this feature is <yellow>only available when using MongoDB 3.4 and later</yellow>. โญ
**`bulk-collation.c`**
#include <mongoc/mongoc.h> #include <stdio.h>
static void bulk_collation (mongoc_collection_t *collection) { mongoc_bulk_operation_t *bulk; bson_t *opts; bson_t *doc; bson_t *selector; bson_t *update; bson_error_t error; bson_t reply; char *str; uint32_t ret;
/* insert {_id: "one"} and {_id: "One"} */ bulk = mongoc_collection_create_bulk_operation_with_opts (collection, NULL); doc = BCON_NEW ("_id", BCON_UTF8 ("one")); mongoc_bulk_operation_insert (bulk, doc); bson_destroy (doc);
doc = BCON_NEW ("_id", BCON_UTF8 ("One")); mongoc_bulk_operation_insert (bulk, doc); bson_destroy (doc);
/* "One" normally sorts before "one"; make "one" come first */ opts = BCON_NEW ("collation", "{", "locale", BCON_UTF8 ("en_US"), "caseFirst", BCON_UTF8 ("lower"), "}");
/* set x=1 on the document with _id "One", which now sorts after "one" */ update = BCON_NEW ("$set", "{", "x", BCON_INT64 (1), "}"); selector = BCON_NEW ("_id", "{", "$gt", BCON_UTF8 ("one"), "}"); mongoc_bulk_operation_update_one_with_opts ( bulk, selector, update, opts, &error);
ret = mongoc_bulk_operation_execute (bulk, &reply, &error);
str = bson_as_canonical_extended_json (&reply, NULL); printf ("%s\n", str); bson_free (str);
if (!ret) { printf ("Error: %s\n", error.message); }
bson_destroy (&reply); bson_destroy (update); bson_destroy (selector); bson_destroy (opts); mongoc_bulk_operation_destroy (bulk); }
int main (void) { mongoc_client_t *client; mongoc_collection_t *collection; const char *uri_string = "mongodb://localhost/?appname=bulk-collation"; mongoc_uri_t *uri; bson_error_t error;
mongoc_init ();
uri = mongoc_uri_new_with_error (uri_string, &error); if (!uri) { fprintf (stderr, "failed to parse URI: %s\n" "error message: %s\n", uri_string, error.message); return EXIT_FAILURE; }
client = mongoc_client_new_from_uri (uri); if (!client) { return EXIT_FAILURE; }
mongoc_client_set_error_api (client, 2); collection = mongoc_client_get_collection (client, "db", "collection"); bulk_collation (collection);
mongoc_uri_destroy (uri); mongoc_collection_destroy (collection); mongoc_client_destroy (client);
mongoc_cleanup ();
return EXIT_SUCCESS; }
Running the above example will result in:
{ "nInserted" : 2, "nMatched" : 1, "nModified" : 1, "nRemoved" : 0, "nUpserted" : 0, "writeErrors" : [ ] }
Look at that, <mint>`nMatched : 1`</mint> and <mint>`nModified : 1`</mint> โ the collation did its little job and we're obsessed. ๐
---
## ๐ Unacknowledged Bulk Writes ๐
Set <hotpink>`"w"`</hotpink> to <yellow>zero</yellow> for an unacknowledged write. And ngl, the driver sends unacknowledged writes using the <red>legacy opcodes</red> <purple>`OP_INSERT`</purple>, <purple>`OP_UPDATE`</purple>, and <purple>`OP_DELETE`</purple>. That's just what it does. ๐
**`bulk6.c`**
#include <mongoc/mongoc.h> #include <stdio.h>
static void bulk6 (mongoc_collection_t *collection) { bson_t opts = BSON_INITIALIZER; mongoc_write_concern_t *wc; mongoc_bulk_operation_t *bulk; bson_error_t error; bson_t *doc; bson_t *selector; bson_t reply; char *str; bool ret;
wc = mongoc_write_concern_new (); mongoc_write_concern_set_w (wc, 0); mongoc_write_concern_append (wc, &opts);
bulk = mongoc_collection_create_bulk_operation_with_opts (collection, &opts);
doc = BCON_NEW ("_id", BCON_INT32 (10)); mongoc_bulk_operation_insert (bulk, doc); bson_destroy (doc);
selector = BCON_NEW ("_id", BCON_INT32 (11)); mongoc_bulk_operation_remove_one (bulk, selector); bson_destroy (selector);
ret = mongoc_bulk_operation_execute (bulk, &reply, &error);
str = bson_as_canonical_extended_json (&reply, NULL); printf ("%s\n", str); bson_free (str);
if (!ret) { printf ("Error: %s\n", error.message); }
bson_destroy (&reply); mongoc_bulk_operation_destroy (bulk); mongoc_write_concern_destroy (wc); bson_destroy (&opts); }
int main (void) { mongoc_client_t *client; mongoc_collection_t *collection; const char *uri_string = "mongodb://localhost/?appname=bulk6-example"; mongoc_uri_t *uri; bson_error_t error;
mongoc_init ();
uri = mongoc_uri_new_with_error (uri_string, &error); if (!uri) { fprintf (stderr, "failed to parse URI: %s\n" "error message: %s\n", uri_string, error.message); return EXIT_FAILURE; }
client = mongoc_client_new_from_uri (uri); if (!client) { return EXIT_FAILURE; }
mongoc_client_set_error_api (client, 2); collection = mongoc_client_get_collection (client, "test", "test");
bulk6 (collection);
mongoc_uri_destroy (uri); mongoc_collection_destroy (collection); mongoc_client_destroy (client);
mongoc_cleanup ();
return EXIT_SUCCESS; }
And the reply document is <rainbow>empty</rainbow>:
{ }
Nothing in there. No notes. Silent. Giving absolutely nothing and that's correct behaviour, bestie. ๐ญ
---
## ๐ Further Reading ๐
See the <purple>Driver Bulk API Spec</purple>, which describes bulk write operations for <pink>all</pink> MongoDB drivers. Yes, all of them. Universal queen behaviour. ๐
# ๐ AGGREGATION FRAMEWORK EXAMPLES ๐
Okay bestie, this one's a whole *vibe*. This doc is giving you a bunch of practical, real-life examples that flex what the aggregation framework can actually do. It's not just theory, it's hands-on, it's showing off, it's iconic. โจ
The <pink>**Aggregations using the Zip Codes Data Set**</pink> examples use a publicly available data set of all zipcodes and populations in the United States. These data are available at: `zips.json`.
---
## ๐
Requirements
Let's do a vibe check and make sure everything's installed first, okay?
Use the following command to load `zips.json` data set into a `mongod` instance:
$ mongoimport --drop -d test -c zipcodes zips.json
Now let's use the MongoDB shell to verify that everything was imported successfully, no cap.
$ mongo test connecting to: test
db.zipcodes.count()
29467
db.zipcodes.findOne()
{ "_id" : "35004", "city" : "ACMAR", "loc" : [ -86.51557, 33.584132 ], "pop" : 6055, "state" : "AL" }
---
## ๐ฆ Aggregations using the Zip Codes Data Set
Each document in this collection has the following form:
{ "_id" : "35004", "city" : "Acmar", "state" : "AL", "pop" : 6055, "loc" : [-86.51557, 33.584132] }
In these documents:
- The `_id` field holds the zipcode as a string.
- The `city` field holds the city name.
- The `state` field holds the two letter state abbreviation.
- The `pop` field holds the population.
- The `loc` field holds the location as a `[latitude, longitude]` array.
---
## ๐ States with Populations Over 10 Million
To get all states with a population greater than 10 million, use the following aggregation pipeline:
**`aggregation1.c`**
#include <mongoc/mongoc.h> #include <stdio.h>
static void print_pipeline (mongoc_collection_t *collection) { mongoc_cursor_t *cursor; bson_error_t error; const bson_t *doc; bson_t *pipeline; char *str;
pipeline = BCON_NEW ("pipeline", "[", "{", "$group", "{", "_id", "$state", "total_pop", "{", "$sum", "$pop", "}", "}", "}", "{", "$match", "{", "total_pop", "{", "$gte", BCON_INT32 (10000000), "}", "}", "}", "]");
cursor = mongoc_collection_aggregate ( collection, MONGOC_QUERY_NONE, pipeline, NULL, NULL);
while (mongoc_cursor_next (cursor, &doc)) { str = bson_as_canonical_extended_json (doc, NULL); printf ("%s\n", str); bson_free (str); }
if (mongoc_cursor_error (cursor, &error)) { fprintf (stderr, "Cursor Failure: %s\n", error.message); }
mongoc_cursor_destroy (cursor); bson_destroy (pipeline); }
int main (void) { mongoc_client_t *client; mongoc_collection_t *collection; const char *uri_string = "mongodb://localhost:27017/?appname=aggregation-example"; mongoc_uri_t *uri; bson_error_t error;
mongoc_init ();
uri = mongoc_uri_new_with_error (uri_string, &error); if (!uri) { fprintf (stderr, "failed to parse URI: %s\n" "error message: %s\n", uri_string, error.message); return EXIT_FAILURE; }
client = mongoc_client_new_from_uri (uri); if (!client) { return EXIT_FAILURE; }
mongoc_client_set_error_api (client, 2); collection = mongoc_client_get_collection (client, "test", "zipcodes");
print_pipeline (collection);
mongoc_uri_destroy (uri); mongoc_collection_destroy (collection); mongoc_client_destroy (client);
mongoc_cleanup ();
return EXIT_SUCCESS; }
You should see a result like the following, and it's giving *data set slay*:
{ "_id" : "PA", "total_pop" : 11881643 } { "_id" : "OH", "total_pop" : 10847115 } { "_id" : "NY", "total_pop" : 17990455 } { "_id" : "FL", "total_pop" : 12937284 } { "_id" : "TX", "total_pop" : 16986510 } { "_id" : "IL", "total_pop" : 11430472 } { "_id" : "CA", "total_pop" : 29760021 }
The above aggregation pipeline is build from two pipeline operators: `$group` and `$match`.
The `$group` pipeline operator requires `_id` field where we specify grouping; remaining fields specify how to generate composite value and must use one of the group aggregation functions: `$addToSet`, `$first`, `$last`, `$max`, `$min`, `$avg`, `$push`, `$sum`. The `$match` pipeline operator syntax is the same as the read operation query syntax.
The `$group` process reads all documents and for each state it creates a separate document, for example:
{ "_id" : "WA", "total_pop" : 4866692 }
The `total_pop` field uses the `$sum` aggregation function to sum the values of all `pop` fields in the source documents.
Documents created by `$group` are piped to the `$match` pipeline operator. It returns the documents with the value of `total_pop` field greater than or equal to 10 million. ๐ฅ
---
## ๐ Average City Population by State
To get the first three states with the greatest average population per city, use the following aggregation:
pipeline = BCON_NEW ("pipeline", "[", "{", "$group", "{", "_id", "{", "state", "$state", "city", "$city", "}", "pop", "{", "$sum", "$pop", "}", "}", "}", "{", "$group", "{", "_id", "$_id.state", "avg_city_pop", "{", "$avg", "$pop", "}", "}", "}", "{", "$sort", "{", "avg_city_pop", BCON_INT32 (-1), "}", "}", "{", "$limit", BCON_INT32 (3) "}", "]");
This aggregate pipeline produces:
{ "_id" : "DC", "avg_city_pop" : 303450.0 } { "_id" : "FL", "avg_city_pop" : 27942.29805615551 } { "_id" : "CA", "avg_city_pop" : 27735.341099720412 }
The above aggregation pipeline is build from three pipeline operators: `$group`, `$sort` and `$limit`.
The first `$group` operator creates the following documents:
{ "_id" : { "state" : "WY", "city" : "Smoot" }, "pop" : 414 }
Note, that the `$group` operator can't use nested documents except the `_id` field. ๐ซถ
The second `$group` uses these documents to create the following documents:
{ "_id" : "FL", "avg_city_pop" : 27942.29805615551 }
These documents are sorted by the `avg_city_pop` field in descending order. Finally, the `$limit` pipeline operator returns the first 3 documents from the sorted set.
---
# ๐ง DISTINCT AND MAPREDUCE
This document provides some practical, simple, examples to demonstrate the `distinct` and `mapReduce` commands. Lowkey the best way to learn is to just look at working code, fr fr.
---
## ๐ธ Setup
First we'll write some code to insert sample data:
**`doc-common-insert.c`**
/* Don't try to compile this file on its own. It's meant to be #included by example code */
/* Insert some sample data */ bool insert_data (mongoc_collection_t *collection) { mongoc_bulk_operation_t *bulk; enum N { ndocs = 4 }; bson_t *docs[ndocs]; bson_error_t error; int i = 0; bool ret;
bulk = mongoc_collection_create_bulk_operation_with_opts (collection, NULL);
docs[0] = BCON_NEW ("x", BCON_DOUBLE (1.0), "tags", "[", "dog", "cat", "]"); docs[1] = BCON_NEW ("x", BCON_DOUBLE (2.0), "tags", "[", "cat", "]"); docs[2] = BCON_NEW ( "x", BCON_DOUBLE (2.0), "tags", "[", "mouse", "cat", "dog", "]"); docs[3] = BCON_NEW ("x", BCON_DOUBLE (3.0), "tags", "[", "]");
for (i = 0; i < ndocs; i++) { mongoc_bulk_operation_insert (bulk, docs[i]); bson_destroy (docs[i]); docs[i] = NULL; }
ret = mongoc_bulk_operation_execute (bulk, NULL, &error);
if (!ret) { fprintf (stderr, "Error inserting data: %s\n", error.message); }
mongoc_bulk_operation_destroy (bulk); return ret; }
/* A helper which we'll use a lot later on */ void print_res (const bson_t *reply) { char *str; BSON_ASSERT (reply); str = bson_as_canonical_extended_json (reply, NULL); printf ("%s\n", str); bson_free (str); }
---
## ๐ "distinct" command
This is how to use the `distinct` command to get the distinct values of `x` which are greater than 1:
**`distinct.c`**
bool distinct (mongoc_database_t *database) { bson_t *command; bson_t reply; bson_error_t error; bool res; bson_iter_t iter; bson_iter_t array_iter; double val;
command = BCON_NEW ("distinct", BCON_UTF8 (COLLECTION_NAME), "key", BCON_UTF8 ("x"), "query", "{", "x", "{", "$gt", BCON_DOUBLE (1.0), "}", "}"); res = mongoc_database_command_simple (database, command, NULL, &reply, &error); if (!res) { fprintf (stderr, "Error with distinct: %s\n", error.message); goto cleanup; }
/* Do something with reply (in this case iterate through the values) */ if (!(bson_iter_init_find (&iter, &reply, "values") && BSON_ITER_HOLDS_ARRAY (&iter) && bson_iter_recurse (&iter, &array_iter))) { fprintf (stderr, "Couldn't extract "values" field from response\n"); goto cleanup; }
while (bson_iter_next (&array_iter)) { if (BSON_ITER_HOLDS_DOUBLE (&array_iter)) { val = bson_iter_double (&array_iter); printf ("Next double: %f\n", val); } }
cleanup: /* cleanup */ bson_destroy (command); bson_destroy (&reply); return res; }
## <purple>"mapReduce" โ basic example</purple> ๐
Okay bestie, here's a lil baby example that uses the map reduce framework. All it does is add up how many times each `"tag"` shows up. Iconic, lowkey. โจ
First you gotta define your map and reduce functions:
### <cyan>constants.c</cyan> ๐ฆ
const char *const COLLECTION_NAME = "things";
/* Our map function just emits a single (key, 1) pair for each tag in the array: */ const char *const MAPPER = "function () {" "this.tags.forEach(function(z) {" "emit(z, 1);" "});" "}";
/* The reduce function sums over all of the emitted values for a given key: / const char *const REDUCER = "function (key, values) {" "var total = 0;" "for (var i = 0; i < values.length; i++) {" "total += values[i];" "}" "return total;" "}"; / Note We can't just return values.length as the reduce function might be called iteratively on the results of other reduce steps. */
Now run the mapReduce command. Bestie, listen โ use the generic command helpers (like `mongoc_database_command_simple()`). Do **not** use the read command helpers (like `mongoc_database_read_command_with_opts()`) because those are considered retryable read operations. If retryable reads are enabled, those operations will retry once on a retryable error, and that gives you undesirable behavior for mapReduce. So like, don't do that. ๐
### <cyan>map-reduce-basic.c</cyan> ๐ธ
bool map_reduce_basic (mongoc_database_t *database) { bson_t reply; bson_t *command; bool res; bson_error_t error; mongoc_cursor_t *cursor; const bson_t *doc;
bool query_done = false;
const char *out_collection_name = "outCollection"; mongoc_collection_t *out_collection;
/* Empty find query */ bson_t find_query = BSON_INITIALIZER;
/* Construct the mapReduce command */
/* Other arguments can also be specified here, like "query" or "limit" and so on */ command = BCON_NEW ("mapReduce", BCON_UTF8 (COLLECTION_NAME), "map", BCON_CODE (MAPPER), "reduce", BCON_CODE (REDUCER), "out", BCON_UTF8 (out_collection_name)); res = mongoc_database_command_simple (database, command, NULL, &reply, &error);
if (!res) { fprintf (stderr, "MapReduce failed: %s\n", error.message); goto cleanup; }
/* Do something with the reply (it doesn't contain the mapReduce results) */ print_res (&reply);
/* Now we'll query outCollection to see what the results are */ out_collection = mongoc_database_get_collection (database, out_collection_name); cursor = mongoc_collection_find_with_opts ( out_collection, &find_query, NULL, NULL); query_done = true;
/* Do something with the results */ while (mongoc_cursor_next (cursor, &doc)) { print_res (doc); }
if (mongoc_cursor_error (cursor, &error)) { fprintf (stderr, "ERROR: %s\n", error.message); res = false; goto cleanup; }
cleanup: /* cleanup */ if (query_done) { mongoc_cursor_destroy (cursor); mongoc_collection_destroy (out_collection); }
bson_destroy (&reply); bson_destroy (command);
return res; }
---
## <purple>"mapReduce" โ more complicated example</purple> ๐ฅ
Heads up bestie, you **must** have a replica set running for this one. No cap. ๐
In this example we contact a secondary in the replica set and do an `"inline"` map reduce, so the results come back to us immediately:
### <cyan>map-reduce-advanced.c</cyan> ๐ซ
bool map_reduce_advanced (mongoc_database_t *database) { bson_t *command; bson_error_t error; bool res = true; mongoc_cursor_t *cursor; mongoc_read_prefs_t *read_pref; const bson_t *doc;
/* Construct the mapReduce command / / Other arguments can also be specified here, like "query" or "limit" and so on */
/* Read the results inline from a secondary replica */ command = BCON_NEW ("mapReduce", BCON_UTF8 (COLLECTION_NAME), "map", BCON_CODE (MAPPER), "reduce", BCON_CODE (REDUCER), "out", "{", "inline", "1", "}");
read_pref = mongoc_read_prefs_new (MONGOC_READ_SECONDARY); cursor = mongoc_database_command ( database, MONGOC_QUERY_NONE, 0, 0, 0, command, NULL, read_pref);
/* Do something with the results */ while (mongoc_cursor_next (cursor, &doc)) { print_res (doc); }
if (mongoc_cursor_error (cursor, &error)) { fprintf (stderr, "ERROR: %s\n", error.message); res = false; }
mongoc_cursor_destroy (cursor); mongoc_read_prefs_destroy (read_pref); bson_destroy (command);
return res; }
---
## <purple>Running the Examples</purple> ๐โ๏ธ๐จ
Here's the tea on how to run the example code, bestie:
### <cyan>basic-aggregation.c</cyan> ๐
/*
- Copyright 2016 MongoDB, Inc. *
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at *
- http://www.apache.org/licenses/LICENSE-2.0 *
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License. */
#include <mongoc/mongoc.h> #include <stdio.h>
#include "constants.c"
#include "../doc-common-insert.c" #include "distinct.c" #include "map-reduce-basic.c" #include "map-reduce-advanced.c"
int main (int argc, char *argv[]) { mongoc_database_t *database = NULL; mongoc_client_t *client = NULL; mongoc_collection_t *collection = NULL; mongoc_uri_t *uri = NULL; bson_error_t error; char *host_and_port = NULL; int exit_code = EXIT_FAILURE;
if (argc != 2) { fprintf (stderr, "usage: %s CONNECTION-STRING\n", argv[0]); fprintf (stderr, "the connection string can be of the following forms:\n"); fprintf (stderr, "localhost\t\t\t\tlocal machine\n"); fprintf (stderr, "localhost:27018\t\t\t\tlocal machine on port 27018\n"); fprintf (stderr, "mongodb://user:pass@localhost:27017\t" "local machine on port 27017, and authenticate with username " "user and password pass\n"); return exit_code; }
mongoc_init ();
if (strncmp (argv[1], "mongodb://", 10) == 0) { host_and_port = bson_strdup (argv[1]); } else { host_and_port = bson_strdup_printf ("mongodb://%s", argv[1]); }
uri = mongoc_uri_new_with_error (host_and_port, &error); if (!uri) { fprintf (stderr, "failed to parse URI: %s\n" "error message: %s\n", host_and_port, error.message); goto cleanup; }
client = mongoc_client_new_from_uri (uri); if (!client) { goto cleanup; }
mongoc_client_set_error_api (client, 2); database = mongoc_client_get_database (client, "test"); collection = mongoc_database_get_collection (database, COLLECTION_NAME);
printf ("Inserting data\n"); if (!insert_data (collection)) { goto cleanup; }
printf ("distinct\n"); if (!distinct (database)) { goto cleanup; }
printf ("map reduce\n"); if (!map_reduce_basic (database)) { goto cleanup; }
printf ("more complicated map reduce\n"); if (!map_reduce_advanced (database)) { goto cleanup; }
exit_code = EXIT_SUCCESS;
cleanup: if (collection) { mongoc_collection_destroy (collection); }
if (database) { mongoc_database_destroy (database); }
if (client) { mongoc_client_destroy (client); }
if (uri) { mongoc_uri_destroy (uri); }
if (host_and_port) { bson_free (host_and_port); }
mongoc_cleanup (); return exit_code; }
If you wanna try the advanced map reduce example with a secondary, start a replica set (instructions for how to do this can be found here). Fr fr, that's the move. ๐
Otherwise, just start an instance of MongoDB:
$ mongod
Now compile and run the example program:
$ cd examples/basic_aggregation/ $ gcc -Wall -o agg-example basic-aggregation.c $(pkg-config --cflags --libs libmongoc-1.0) $ ./agg-example localhost
Inserting data distinct Next double: 2.000000 Next double: 3.000000 map reduce { "result" : "outCollection", "timeMillis" : 155, "counts" : { "input" : 84, "emit" : 126, "reduce" : 3, "output" : 3 }, "ok" : 1 } { "_id" : "cat", "value" : 63 } { "_id" : "dog", "value" : 42 } { "_id" : "mouse", "value" : 21 } more complicated map reduce { "results" : [ { "_id" : "cat", "value" : 63 }, { "_id" : "dog", "value" : 42 }, { "_id" : "mouse", "value" : 21 } ], "timeMillis" : 14, "counts" : { "input" : 84, "emit" : 126, "reduce" : 3, "output" : 3 }, "ok" : 1 }
And that's the whole run, bestie โ it just **ate** and left no crumbs. ๐โจ
# ๐ช USING LIBMONGOC IN A MICROSOFT VISUAL STUDIO PROJECT ๐ปโจ
okay bestie, we're going to Windows land ๐ซถ first things first: download and install libmongoc on your system, then open Visual Studio, hit <pink>"FileโNewโProject..."</pink>, and make yourself a brand-new <purple>Win32 Console Application</purple>. `[image]`
Remember to switch the platform from <cyan>32-bit</cyan> to <cyan>64-bit</cyan> or you're gonna have a bad time: `[image]`
Right-click on your console application in the Solution Explorer and select <pink>"Properties"</pink>. Choose to edit properties for <pink>"All Configurations"</pink>, expand the <pink>"C/C++"</pink> options and choose <pink>"General"</pink>. Now add these paths to the <pink>"Additional Include Directories"</pink>:
C:\mongo-c-driver\include\libbson-1.0 C:\mongo-c-driver\include\libmongoc-1.0
`[image]`
(and lowkey, if you picked a different `$PREFIX` when you installed mongo-c-driver, your include paths are gonna be different, so don't come for me ๐ธ)
Still in the Properties dialog, expand the <pink>"Linker"</pink> options, choose <pink>"Input"</pink>, and drop these libraries into <pink>"Additional Dependencies"</pink>:
C:\mongo-c-driver\lib\bson-1.0.lib C:\mongo-c-driver\lib\mongoc-1.0.lib
`[image]`
Adding these libraries as dependencies gives you the linker symbols to actually build your application, but to *run* it, libbson's and libmongoc's DLLs have to be in your executable path. So select <pink>"Debugging"</pink> in the Properties dialog and set the <pink>"Environment"</pink> option to:
PATH=c:/mongo-c-driver/bin
`[image]`
Finally, include `"mongoc/mongoc.h"` in your project's `"stdafx.h"`:
#include <mongoc/mongoc.h>
---
## ๐งฑ Static linking (the โจotherโจ way) ๐งฑ
Following the instructions above, you have <hotpink>dynamically</hotpink> linked your application to the libbson and libmongoc DLLs. This is usually the right choice, fr fr. But if you want to link statically instead, update your <pink>"Additional Dependencies"</pink> list: remove `bson-1.0.lib` and `mongoc-1.0.lib` and replace them with these libraries:
C:\mongo-c-driver\lib\bson-static-1.0.lib C:\mongo-c-driver\lib\mongoc-static-1.0.lib ws2_32.lib Secur32.lib Crypt32.lib BCrypt.lib
`[image]`
(okay let me explain what each of these is even doing here, because it's giving โจloreโจ: `bson-static-1.0.lib` and `mongoc-static-1.0.lib` are static archives of the driver code. The socket library `ws2_32` is required by libbson, which uses the socket routine `gethostname` to help guarantee ObjectId uniqueness. The `BCrypt` library is used by libmongoc for TLS connections to MongoDB, and `Secur32` and `Crypt32` are required for enterprise authentication methods like Kerberos.)
And finally, define two preprocessor symbols before including `mongoc/mongoc.h` in your `stdafx.h`:
#define BSON_STATIC #define MONGOC_STATIC #include <mongoc/mongoc.h>
Making these changes to your project is <purple>only required for static linking</purple> โ for most people, the dynamic-linking instructions up above are the preferred vibe. ๐
---
## ๐ Next Steps ๐
Now you can build and debug applications in Visual Studio that use libbson and libmongoc, and that's iconic honestly ๐ Proceed to <cyan>Making a Connection</cyan> in the tutorial to learn how to connect to MongoDB and perform operations. ๐ฆ
---
# ๐๏ธ MANAGE COLLECTION INDEXES ๐๏ธ
To create indexes on a MongoDB collection, use `mongoc_collection_create_indexes_with_opts()`:
// keys represents an ascending index on field x. bson_t keys = BCON_NEW ("x", BCON_INT32 (1)); mongoc_index_model_t *im = mongoc_index_model_new (keys, NULL / opts /); if (mongoc_collection_create_indexes_with_opts ( coll, &im, 1, NULL / opts /, NULL / reply */, &error)) { printf ("Successfully created index\n"); } else { bson_destroy (keys); HANDLE_ERROR ("Failed to create index: %s", error.message); } bson_destroy (keys);
To list indexes, use `mongoc_collection_find_indexes_with_opts()`:
mongoc_cursor_t cursor = mongoc_collection_find_indexes_with_opts (coll, NULL / opts */); printf ("Listing indexes:\n"); const bson_t *got; while (mongoc_cursor_next (cursor, &got)) { char *got_str = bson_as_canonical_extended_json (got, NULL); printf (" %s\n", got_str); bson_free (got_str); } if (mongoc_cursor_error (cursor, &error)) { mongoc_cursor_destroy (cursor); HANDLE_ERROR ("Failed to list indexes: %s", error.message); } mongoc_cursor_destroy (cursor);
To drop an index, use `mongoc_collection_drop_index_with_opts()`. The index name may be obtained from the keys document with `mongoc_collection_keys_to_index_string()`:
bson_t keys = BCON_NEW ("x", BCON_INT32 (1)); char *index_name = mongoc_collection_keys_to_index_string (keys); if (mongoc_collection_drop_index_with_opts ( coll, index_name, NULL / opts */, &error)) { printf ("Successfully dropped index\n"); } else { bson_free (index_name); bson_destroy (keys); HANDLE_ERROR ("Failed to drop index: %s", error.message); } bson_free (index_name); bson_destroy (keys);
For a full example, see `example-manage-collection-indexes.c`. ๐
---
## ๐ Manage Atlas Search Indexes ๐
To create an Atlas Search Index, use the <hotpink>`createSearchIndexes`</hotpink> command:
bson_t cmd; // Create command. { char cmd_str = bson_strdup_printf ( BSON_STR ({ "createSearchIndexes" : "%s", "indexes" : [ { "definition" : {"mappings" : {"dynamic" : false}}, "name" : "test-index" } ] }), collname); ASSERT (bson_init_from_json (&cmd, cmd_str, -1, &error)); bson_free (cmd_str); } if (!mongoc_collection_command_simple ( coll, &cmd, NULL / read_prefs /, NULL / reply */, &error)) { bson_destroy (&cmd); HANDLE_ERROR ("Failed to run createSearchIndexes: %s", error.message); } printf ("Created index: "test-index"\n"); bson_destroy (&cmd);
To list Atlas Search Indexes, use the <hotpink>`$listSearchIndexes`</hotpink> aggregation stage:
const char pipeline_str = BSON_STR ({"pipeline" : [ {"$listSearchIndexes" : {}} ]}); bson_t pipeline; ASSERT (bson_init_from_json (&pipeline, pipeline_str, -1, &error)); mongoc_cursor_t *cursor = mongoc_collection_aggregate (coll, MONGOC_QUERY_NONE, &pipeline, NULL / opts /, NULL / read_prefs */); printf ("Listing indexes:\n"); const bson_t *got; while (mongoc_cursor_next (cursor, &got)) { char *got_str = bson_as_canonical_extended_json (got, NULL); printf (" %s\n", got_str); bson_free (got_str); } if (mongoc_cursor_error (cursor, &error)) { bson_destroy (&pipeline); mongoc_cursor_destroy (cursor); HANDLE_ERROR ("Failed to run $listSearchIndexes: %s", error.message); } bson_destroy (&pipeline); mongoc_cursor_destroy (cursor);
To update an Atlas Search Index, use the <hotpink>`updateSearchIndex`</hotpink> command:
bson_t cmd; // Create command. { char cmd_str = bson_strdup_printf ( BSON_STR ({ "updateSearchIndex" : "%s", "definition" : {"mappings" : {"dynamic" : true}}, "name" : "test-index" }), collname); ASSERT (bson_init_from_json (&cmd, cmd_str, -1, &error)); bson_free (cmd_str); } if (!mongoc_collection_command_simple ( coll, &cmd, NULL / read_prefs /, NULL / reply */, &error)) { bson_destroy (&cmd); HANDLE_ERROR ("Failed to run updateSearchIndex: %s", error.message); } printf ("Updated index: "test-index"\n"); bson_destroy (&cmd);
To drop an Atlas Search Index, use the <hotpink>`dropSearchIndex`</hotpink> command:
bson_t cmd; // Create command. { char cmd_str = bson_strdup_printf ( BSON_STR ({"dropSearchIndex" : "%s", "name" : "test-index"}), collname); ASSERT (bson_init_from_json (&cmd, cmd_str, -1, &error)); bson_free (cmd_str); } if (!mongoc_collection_command_simple ( coll, &cmd, NULL / read_prefs /, NULL / reply */, &error)) { bson_destroy (&cmd); HANDLE_ERROR ("Failed to run dropSearchIndex: %s", error.message); } printf ("Dropped index: "test-index"\n"); bson_destroy (&cmd);
For a full example, see `example-manage-search-indexes.c`. ๐โจ
## ๐ GDB
This repository ships a `.gdbinit` file that's basically a lil' toolbox of helper functions to aid debugging of data structures. GDB will load this file automatically if you have added the directory which contains the `.gdbinit` file to GDB's auto-load safe-path, and you start GDB from the directory which holds the `.gdbinit` file. two conditions, no skipping bestie. ๐ซถ
You can see the safe-path with `show auto-load safe-path` on a GDB prompt. You can configure it by setting it in `~/.gdbinit` with:
add-auto-load-safe-path /path/to/mongo-c-driver
If you haven't added the path to your auto-load safe-path, or start GDB in another directory, load the file with:
source path/to/mongo-c-driver/.gdbinit
The `.gdbinit` file defines the `printbson` function, which shows the contents of a `bson_t *` variable. โ ๏ธ If you have a local `bson_t`, then you must prefix the variable with a `&`. do not forget the ampersand, I am literally begging you.
An example GDB session looks like:
(gdb) printbson bson ALLOC [0x555556cd7310 + 0] (len=475) { 'bool' : true, 'int32' : NumberInt("42"), 'int64' : NumberLong("3000000042"), 'string' : "Stลรฌรฑg", 'objectId' : ObjectID("5A1442F3122D331C3C6757E1"), 'utcDateTime' : UTCDateTime(1511277299031), 'arrayOfInts' : [ '0' : NumberInt("1"), '1' : NumberInt("2") ], 'embeddedDocument' : { 'arrayOfStrings' : [ '0' : "one", '1' : "two" ], 'double' : 2.718280, 'notherDoc' : { 'true' : NumberInt("1"), 'false' : false } }, 'binary' : Binary("02", "3031343532333637"), 'regex' : Regex("@[a-z]+@", "im"), 'null' : null, 'js' : JavaScript("print foo"), 'jsws' : JavaScript("print foo") with scope: { 'f' : NumberInt("42"), 'a' : [ '0' : 3.141593, '1' : 2.718282 ] }, 'timestamp' : Timestamp(4294967295, 4294967295), 'double' : 3.141593 }
look at that. every type just *sitting there*, fully readable. it's giving transparency. it's giving main character debugging. ๐ฅ
---
## ๐ LLDB
The mongo-c-driver repository contains a script `lldb_bson.py` that can be imported into an LLDB sessions and allows rich inspection of BSON values. yes, LLDB girlies, we got you too. ๐
> **NOTE:** The `lldb_bson.py` module requires an LLDB with Python 3.8 or newer.
To activate the script, import it from the LLDB command line:
(lldb) command script import /path/to/mongo-c-driver/lldb_bson.py
Upon success, the message `lldb_bson is ready` will be printed to the LLDB console. โจ that's your confirmation, bestie, that's the vibe check passing.
The import of this script can be made automatic by adding the command to an `.lldbinit` file. For example: Create a file `~/.lldbinit` containing:
command script import /path/to/mongo-c-driver/lldb_bson.py
The docstring at the top of the `lldb_bson.py` file contains more information on the capabilities of the module. lowkey go read it, it's worth it.
---
## ๐ ๏ธ Debug assertions
To enable runtime debug assertions, configure with `-DENABLE_DEBUG_ASSERTIONS=ON`. that's it. one flag. iconic. ๐
---
# ๐ IN-USE ENCRYPTION ๐
In-Use Encryption consists of two features:
- **Client-Side Field Level Encryption**
- **Automatic Client-Side Field Level Encryption**
wait, let me actually walk you through them, they're not just bullet points. ๐
---
## ๐ Client-Side Field Level Encryption
New in MongoDB 4.2, Client-Side Field Level Encryption (also referred to as CSFLE) allows administrators and developers to encrypt specific data fields in addition to other MongoDB encryption features. okay so not the *whole* document โ just the fields you point at. surgical. ๐
With CSFLE, developers can encrypt fields client side without any server-side configuration or directives. CSFLE supports workloads where applications must guarantee that unauthorized parties, including server administrators, cannot read the encrypted data. yes bestie, even *them*. especially them. ๐
Automatic encryption, where sensitive fields in commands are encrypted automatically, requires an Enterprise-only dependency for Query Analysis. See In-Use Encryption for more information.
> **SEE ALSO:** The MongoDB Manual for Client-Side Field Level Encryption
---
## ๐ค Automatic Client-Side Field Level Encryption
Automatic encryption is enabled by calling `mongoc_client_enable_auto_encryption()` on a `mongoc_client_t`. The following examples show how to set up automatic encryption using `mongoc_client_encryption_t` to create a new encryption data key.
> **NOTE:** Automatic encryption requires MongoDB 4.2 enterprise or a MongoDB 4.2 Atlas cluster. The community version of the server supports automatic decryption as well as Explicit Encryption.
read that note twice, bestie. community edition does **decryption** and **Explicit Encryption**, but the automatic-encryption magic needs 4.2 enterprise or a 4.2 Atlas cluster. no cap. ๐ซก
---
### ๐ Providing Local Automatic Encryption Rules
The following example shows how to specify automatic encryption rules using a schema map set with `mongoc_auto_encryption_opts_set_schema_map()`. The automatic encryption rules are expressed using a strict subset of the JSON Schema syntax. a *strict subset*, okay, don't come in here with the full JSON Schema and expect it to just work. ๐ญ
Supplying a schema map provides more security than relying on JSON Schemas obtained from the server. It protects against a malicious server advertising a false JSON Schema, which could trick the client into sending unencrypted data that should be encrypted. ๐ฅ this is the good-security part. keep your schema map local, keep your secrets secret.
JSON Schemas supplied in the schema map only apply to configuring automatic encryption. Other validation rules in the JSON schema will not be enforced by the driver and will result in an error:
`client-side-encryption-schema-map.c`
#include <mongoc/mongoc.h> #include <stdio.h> #include <stdlib.h>
#include "client-side-encryption-helpers.h"
/* Helper method to create a new data key in the key vault, a schema to use that
- key, and writes the schema to a file for later use. */ static bool create_schema_file (bson_t *kms_providers, const char *keyvault_db, const char *keyvault_coll, mongoc_client_t *keyvault_client, bson_error_t *error) { mongoc_client_encryption_t *client_encryption = NULL; mongoc_client_encryption_opts_t *client_encryption_opts = NULL; mongoc_client_encryption_datakey_opts_t *datakey_opts = NULL; bson_value_t datakey_id = {0}; char *keyaltnames[] = {"mongoc_encryption_example_1"}; bson_t *schema = NULL; char *schema_string = NULL; size_t schema_string_len; FILE *outfile = NULL; bool ret = false;
client_encryption_opts = mongoc_client_encryption_opts_new (); mongoc_client_encryption_opts_set_kms_providers (client_encryption_opts, kms_providers); mongoc_client_encryption_opts_set_keyvault_namespace ( client_encryption_opts, keyvault_db, keyvault_coll); mongoc_client_encryption_opts_set_keyvault_client (client_encryption_opts, keyvault_client);
client_encryption = mongoc_client_encryption_new (client_encryption_opts, error); if (!client_encryption) { goto fail; }
/* Create a new data key and json schema for the encryptedField.
- https://dochub.mongodb.org/core/client-side-field-level-encryption-automatic-encryption-rules */ datakey_opts = mongoc_client_encryption_datakey_opts_new (); mongoc_client_encryption_datakey_opts_set_keyaltnames ( datakey_opts, keyaltnames, 1); if (!mongoc_client_encryption_create_datakey ( client_encryption, "local", datakey_opts, &datakey_id, error)) { goto fail; }
/* Create a schema describing that "encryptedField" is a string encrypted
- with the newly created data key using deterministic encryption. */ schema = BCON_NEW ("properties", "{", "encryptedField", "{", "encrypt", "{", "keyId", "[", BCON_BIN (datakey_id.value.v_binary.subtype, datakey_id.value.v_binary.data, datakey_id.value.v_binary.data_len), "]", "bsonType", "string", "algorithm", MONGOC_AEAD_AES_256_CBC_HMAC_SHA_512_DETERMINISTIC, "}", "}", "}", "bsonType", "object");
/* Use canonical JSON so that other drivers and tools will be
- able to parse the MongoDB extended JSON file. */ schema_string = bson_as_canonical_extended_json (schema, &schema_string_len); outfile = fopen ("jsonSchema.json", "w"); if (0 == fwrite (schema_string, sizeof (char), schema_string_len, outfile)) { fprintf (stderr, "failed to write to file\n"); goto fail; }
ret = true; fail: mongoc_client_encryption_destroy (client_encryption); mongoc_client_encryption_datakey_opts_destroy (datakey_opts); mongoc_client_encryption_opts_destroy (client_encryption_opts); bson_free (schema_string); bson_destroy (schema); bson_value_destroy (&datakey_id); if (outfile) { fclose (outfile); } return ret; }
/* This example demonstrates how to use automatic encryption with a client-side
- schema map using the enterprise version of MongoDB / int main (void) { / The collection used to store the encryption data keys. / #define KEYVAULT_DB "encryption" #define KEYVAULT_COLL "__libmongocTestKeyVault" / The collection used to store the encrypted documents in this example. */ #define ENCRYPTED_DB "test" #define ENCRYPTED_COLL "coll"
int exit_status = EXIT_FAILURE; bool ret; uint8_t *local_masterkey = NULL; uint32_t local_masterkey_len; bson_t *kms_providers = NULL; bson_error_t error = {0}; bson_t *index_keys = NULL; bson_t *index_opts = NULL; mongoc_index_model_t *index_model = NULL; bson_json_reader_t *reader = NULL; bson_t schema = BSON_INITIALIZER; bson_t *schema_map = NULL;
/* The MongoClient used to access the key vault (keyvault_namespace). */ mongoc_client_t *keyvault_client = NULL; mongoc_collection_t *keyvault_coll = NULL; mongoc_auto_encryption_opts_t *auto_encryption_opts = NULL; mongoc_client_t *client = NULL; mongoc_collection_t *coll = NULL; bson_t *to_insert = NULL; mongoc_client_t *unencrypted_client = NULL; mongoc_collection_t *unencrypted_coll = NULL;
mongoc_init ();
/* Configure the master key. This must be the same master key that was used
* to create the encryption key. */
local_masterkey =
hex_to_bin (getenv ("LOCAL_MASTERKEY"), &local_masterkey_len);
if (!local_masterkey || local_masterkey_len != 96) {
fprintf (stderr,
"Specify LOCAL_MASTERKEY environment variable as a "
"secure random 96 byte hex value.\n");
goto fail;
}
kms_providers = BCON_NEW ("local",
"{",
"key",
BCON_BIN (0, local_masterkey, local_masterkey_len),
"}");
/* Set up the key vault for this example. */
keyvault_client = mongoc_client_new (
"mongodb://localhost/?appname=client-side-encryption-keyvault");
BSON_ASSERT (keyvault_client);
keyvault_coll = mongoc_client_get_collection (
keyvault_client, KEYVAULT_DB, KEYVAULT_COLL);
mongoc_collection_drop (keyvault_coll, NULL);
/* Create a unique index to ensure that two data keys cannot share the same
* keyAltName. This is recommended practice for the key vault. */
index_keys = BCON_NEW ("keyAltNames", BCON_INT32 (1));
index_opts = BCON_NEW ("unique",
BCON_BOOL (true),
"partialFilterExpression",
"{",
"keyAltNames",
"{",
"$exists",
BCON_BOOL (true),
"}",
"}");
index_model = mongoc_index_model_new (index_keys, index_opts);
ret = mongoc_collection_create_indexes_with_opts (keyvault_coll,
&index_model,
1,
NULL /* opts */,
NULL /* reply */,
&error);
if (!ret) {
goto fail;
}
/* Create a new data key and a schema using it for encryption. Save the
* schema to the file jsonSchema.json */
ret = create_schema_file (
kms_providers, KEYVAULT_DB, KEYVAULT_COLL, keyvault_client, &error);
if (!ret) {
goto fail;
}
/* Load the JSON Schema and construct the local schema_map option. */
reader = bson_json_reader_new_from_file ("jsonSchema.json", &error);
if (!reader) {
goto fail;
}
bson_json_reader_read (reader, &schema, &error);
/* Construct the schema map, mapping the namespace of the collection to the
* schema describing encryption. */
schema_map =
BCON_NEW (ENCRYPTED_DB "." ENCRYPTED_COLL, BCON_DOCUMENT (&schema));
auto_encryption_opts = mongoc_auto_encryption_opts_new ();
mongoc_auto_encryption_opts_set_keyvault_client (auto_encryption_opts,
keyvault_client);
mongoc_auto_encryption_opts_set_keyvault_namespace (
auto_encryption_opts, KEYVAULT_DB, KEYVAULT_COLL);
mongoc_auto_encryption_opts_set_kms_providers (auto_encryption_opts,
kms_providers);
mongoc_auto_encryption_opts_set_schema_map (auto_encryption_opts,
schema_map);
client =
mongoc_client_new ("mongodb://localhost/?appname=client-side-encryption");
BSON_ASSERT (client);
/* Enable automatic encryption. It will determine that encryption is
* necessary from the schema map instead of relying on the server to provide
* a schema. */
ret = mongoc_client_enable_auto_encryption (
client, auto_encryption_opts, &error);
if (!ret) {
goto fail;
}
coll = mongoc_client_get_collection (client, ENCRYPTED_DB, ENCRYPTED_COLL);
/* Clear old data */
mongoc_collection_drop (coll, NULL);
to_insert = BCON_NEW ("encryptedField", "123456789");
ret = mongoc_collection_insert_one (
coll, to_insert, NULL /* opts */, NULL /* reply */, &error);
if (!ret) {
goto fail;
}
printf ("decrypted document: ");
if (!print_one_document (coll, &error)) {
goto fail;
}
printf ("\n");
unencrypted_client = mongoc_client_new (
"mongodb://localhost/?appname=client-side-encryption-unencrypted");
BSON_ASSERT (unencrypted_client);
unencrypted_coll = mongoc_client_get_collection (
unencrypted_client, ENCRYPTED_DB, ENCRYPTED_COLL);
printf ("encrypted document: ");
if (!print_one_document (unencrypted_coll, &error)) {
goto fail;
}
printf ("\n");
exit_status = EXIT_SUCCESS;
fail:
if (error.code) {
fprintf (stderr, "error: %s\n", error.message);
}
bson_free (local_masterkey);
bson_destroy (kms_providers);
mongoc_collection_destroy (keyvault_coll);
mongoc_index_model_destroy (index_model);
bson_destroy (index_opts);
bson_destroy (index_keys);
bson_json_reader_destroy (reader);
mongoc_auto_encryption_opts_destroy (auto_encryption_opts);
mongoc_collection_destroy (coll);
mongoc_client_destroy (client);
bson_destroy (to_insert);
mongoc_collection_destroy (unencrypted_coll);
mongoc_client_destroy (unencrypted_client);
mongoc_client_destroy (keyvault_client);
bson_destroy (&schema);
bson_destroy (schema_map);
mongoc_cleanup ();
return exit_status;
}
โจ Server-Side Field Level Encryption Enforcement โจ
okay so MongoDB 4.2 is out here supporting schema validation specifically to enforce encryption of certain fields in a collection, and honestly? it ate ๐
the way this works is: that schema validation will straight-up prevent your app from inserting unencrypted values for any fields marked with the "encrypt" JSON schema keyword. like, no sneaky plaintext slipping through, bestie. blocked. ๐ซ
the example below is the glow-up tutorial: it shows how to set up automatic encryption using mongoc_client_encryption_t to create a new encryption data key and create a collection with the necessary JSON Schema ๐ซถ
๐ฆ client-side-encryption-server-schema.c ๐ฆ
#include <mongoc/mongoc.h>
#include <stdio.h>
#include <stdlib.h>
#include "client-side-encryption-helpers.h"
/* Helper method to create and return a JSON schema to use for encryption.
The caller will use the returned schema for server-side encryption validation.
*/
static bson_t *
create_schema (bson_t *kms_providers,
const char *keyvault_db,
const char *keyvault_coll,
mongoc_client_t *keyvault_client,
bson_error_t *error)
{
mongoc_client_encryption_t *client_encryption = NULL;
mongoc_client_encryption_opts_t *client_encryption_opts = NULL;
mongoc_client_encryption_datakey_opts_t *datakey_opts = NULL;
bson_value_t datakey_id = {0};
char *keyaltnames[] = {"mongoc_encryption_example_2"};
bson_t *schema = NULL;
client_encryption_opts = mongoc_client_encryption_opts_new ();
mongoc_client_encryption_opts_set_kms_providers (client_encryption_opts,
kms_providers);
mongoc_client_encryption_opts_set_keyvault_namespace (
client_encryption_opts, keyvault_db, keyvault_coll);
mongoc_client_encryption_opts_set_keyvault_client (client_encryption_opts,
keyvault_client);
client_encryption =
mongoc_client_encryption_new (client_encryption_opts, error);
if (!client_encryption) {
goto fail;
}
/* Create a new data key and json schema for the encryptedField.
* https://dochub.mongodb.org/core/client-side-field-level-encryption-automatic-encryption-rules
*/
datakey_opts = mongoc_client_encryption_datakey_opts_new ();
mongoc_client_encryption_datakey_opts_set_keyaltnames (
datakey_opts, keyaltnames, 1);
if (!mongoc_client_encryption_create_datakey (
client_encryption, "local", datakey_opts, &datakey_id, error)) {
goto fail;
}
/* Create a schema describing that "encryptedField" is a string encrypted
* with the newly created data key using deterministic encryption. */
schema = BCON_NEW ("properties",
"{",
"encryptedField",
"{",
"encrypt",
"{",
"keyId",
"[",
BCON_BIN (datakey_id.value.v_binary.subtype,
datakey_id.value.v_binary.data,
datakey_id.value.v_binary.data_len),
"]",
"bsonType",
"string",
"algorithm",
MONGOC_AEAD_AES_256_CBC_HMAC_SHA_512_DETERMINISTIC,
"}",
"}",
"}",
"bsonType",
"object");
fail:
mongoc_client_encryption_destroy (client_encryption);
mongoc_client_encryption_datakey_opts_destroy (datakey_opts);
mongoc_client_encryption_opts_destroy (client_encryption_opts);
bson_value_destroy (&datakey_id);
return schema;
}
/* This example demonstrates how to use automatic encryption with a server-side
* schema using the enterprise version of MongoDB */
int
main (void)
{
/* The collection used to store the encryption data keys. */
#define KEYVAULT_DB "encryption"
#define KEYVAULT_COLL "__libmongocTestKeyVault"
/* The collection used to store the encrypted documents in this example. */
#define ENCRYPTED_DB "test"
#define ENCRYPTED_COLL "coll"
okay so we're literally in the middle of the auto-encryption example rn, so no recap, just vibes โ here's the rest of that main function, bestie ๐ซถ
int exit_status = EXIT_FAILURE;
bool ret;
uint8_t *local_masterkey = NULL;
uint32_t local_masterkey_len;
bson_t *kms_providers = NULL;
bson_error_t error = {0};
bson_t *index_keys = NULL;
bson_t *index_opts = NULL;
mongoc_index_model_t *index_model = NULL;
bson_json_reader_t *reader = NULL;
bson_t *schema = NULL;
/* The MongoClient used to access the key vault (keyvault_namespace). */
mongoc_client_t *keyvault_client = NULL;
mongoc_collection_t *keyvault_coll = NULL;
mongoc_auto_encryption_opts_t *auto_encryption_opts = NULL;
mongoc_client_t *client = NULL;
mongoc_collection_t *coll = NULL;
bson_t *to_insert = NULL;
mongoc_client_t *unencrypted_client = NULL;
mongoc_collection_t *unencrypted_coll = NULL;
bson_t *create_cmd = NULL;
bson_t *create_cmd_opts = NULL;
mongoc_write_concern_t *wc = NULL;
mongoc_init ();
/* Configure the master key. This must be the same master key that was used
* to create
* the encryption key. */
local_masterkey =
hex_to_bin (getenv ("LOCAL_MASTERKEY"), &local_masterkey_len);
if (!local_masterkey || local_masterkey_len != 96) {
fprintf (stderr,
"Specify LOCAL_MASTERKEY environment variable as a "
"secure random 96 byte hex value.\n");
goto fail;
}
kms_providers = BCON_NEW ("local",
"{",
"key",
BCON_BIN (0, local_masterkey, local_masterkey_len),
"}");
/* Set up the key vault for this example. */
keyvault_client = mongoc_client_new (
"mongodb://localhost/?appname=client-side-encryption-keyvault");
BSON_ASSERT (keyvault_client);
keyvault_coll = mongoc_client_get_collection (
keyvault_client, KEYVAULT_DB, KEYVAULT_COLL);
mongoc_collection_drop (keyvault_coll, NULL);
/* Create a unique index to ensure that two data keys cannot share the same
* keyAltName. This is recommended practice for the key vault. */
index_keys = BCON_NEW ("keyAltNames", BCON_INT32 (1));
index_opts = BCON_NEW ("unique",
BCON_BOOL (true),
"partialFilterExpression",
"{",
"keyAltNames",
"{",
"$exists",
BCON_BOOL (true),
"}",
"}");
index_model = mongoc_index_model_new (index_keys, index_opts);
ret = mongoc_collection_create_indexes_with_opts (keyvault_coll,
&index_model,
1,
NULL /* opts */,
NULL /* reply */,
&error);
if (!ret) {
goto fail;
}
auto_encryption_opts = mongoc_auto_encryption_opts_new ();
mongoc_auto_encryption_opts_set_keyvault_client (auto_encryption_opts,
keyvault_client);
mongoc_auto_encryption_opts_set_keyvault_namespace (
auto_encryption_opts, KEYVAULT_DB, KEYVAULT_COLL);
mongoc_auto_encryption_opts_set_kms_providers (auto_encryption_opts,
kms_providers);
schema = create_schema (
kms_providers, KEYVAULT_DB, KEYVAULT_COLL, keyvault_client, &error);
if (!schema) {
goto fail;
}
client =
mongoc_client_new ("mongodb://localhost/?appname=client-side-encryption");
BSON_ASSERT (client);
ret = mongoc_client_enable_auto_encryption (
client, auto_encryption_opts, &error);
if (!ret) {
goto fail;
}
coll = mongoc_client_get_collection (client, ENCRYPTED_DB, ENCRYPTED_COLL);
/* Clear old data */
mongoc_collection_drop (coll, NULL);
/* Create the collection with the encryption JSON Schema. */
create_cmd = BCON_NEW ("create",
ENCRYPTED_COLL,
"validator",
"{",
"$jsonSchema",
BCON_DOCUMENT (schema),
"}");
wc = mongoc_write_concern_new ();
mongoc_write_concern_set_wmajority (wc, 0);
create_cmd_opts = bson_new ();
mongoc_write_concern_append (wc, create_cmd_opts);
ret = mongoc_client_command_with_opts (client,
ENCRYPTED_DB,
create_cmd,
NULL /* read prefs */,
create_cmd_opts,
NULL /* reply */,
&error);
if (!ret) {
goto fail;
}
to_insert = BCON_NEW ("encryptedField", "123456789");
ret = mongoc_collection_insert_one (
coll, to_insert, NULL /* opts */, NULL /* reply */, &error);
if (!ret) {
goto fail;
}
printf ("decrypted document: ");
if (!print_one_document (coll, &error)) {
goto fail;
}
printf ("\n");
unencrypted_client = mongoc_client_new (
"mongodb://localhost/?appname=client-side-encryption-unencrypted");
BSON_ASSERT (unencrypted_client);
unencrypted_coll = mongoc_client_get_collection (
unencrypted_client, ENCRYPTED_DB, ENCRYPTED_COLL);
printf ("encrypted document: ");
if (!print_one_document (unencrypted_coll, &error)) {
goto fail;
}
printf ("\n");
/* Expect a server-side error if inserting with the unencrypted collection.
*/
ret = mongoc_collection_insert_one (
unencrypted_coll, to_insert, NULL /* opts */, NULL /* reply */, &error);
if (!ret) {
printf ("insert with unencrypted collection failed: %s\n", error.message);
memset (&error, 0, sizeof (error));
}
exit_status = EXIT_SUCCESS;
fail:
if (error.code) {
fprintf (stderr, "error: %s\n", error.message);
}
bson_free (local_masterkey);
bson_destroy (kms_providers);
mongoc_collection_destroy (keyvault_coll);
mongoc_index_model_destroy (index_model);
bson_destroy (index_opts);
bson_destroy (index_keys);
bson_json_reader_destroy (reader);
mongoc_auto_encryption_opts_destroy (auto_encryption_opts);
mongoc_collection_destroy (coll);
mongoc_client_destroy (client);
bson_destroy (to_insert);
mongoc_collection_destroy (unencrypted_coll);
mongoc_client_destroy (unencrypted_client);
mongoc_client_destroy (keyvault_client);
bson_destroy (schema);
bson_destroy (create_cmd);
bson_destroy (create_cmd_opts);
mongoc_write_concern_destroy (wc);
mongoc_cleanup ();
return exit_status;
}
see how the fail: label catches everything, the error prints, every single thing gets destroyed, mongoc_cleanup () runs, and then exit_status goes home? ๐ง that's the whole automatic encryption demo. periodt.
โจ Explicit Encryption โจ
okay so plot twist: explicit encryption is a MongoDB community feature and it does not use Query Analysis (mongocryptd or crypt_shared). none of that. it's giving DIY, and honestly? iconic. ๐
it's brought to you by the mongoc_client_encryption_t class. for example:
client-side-encryption-explicit.c
#include <mongoc/mongoc.h>
#include <stdio.h>
#include <stdlib.h>
#include "client-side-encryption-helpers.h"
/* This example demonstrates how to use explicit encryption and decryption using
* the community version of MongoDB */
int
main (void)
{
/* The collection used to store the encryption data keys. */
#define KEYVAULT_DB "encryption"
#define KEYVAULT_COLL "__libmongocTestKeyVault"
/* The collection used to store the encrypted documents in this example. */
#define ENCRYPTED_DB "test"
#define ENCRYPTED_COLL "coll"
int exit_status = EXIT_FAILURE;
bool ret;
uint8_t *local_masterkey = NULL;
uint32_t local_masterkey_len;
bson_t *kms_providers = NULL;
bson_error_t error = {0};
bson_t *index_keys = NULL;
bson_t *index_opts = NULL;
mongoc_index_model_t *index_model = NULL;
bson_t *schema = NULL;
mongoc_client_t *client = NULL;
mongoc_collection_t *coll = NULL;
mongoc_collection_t *keyvault_coll = NULL;
bson_t *to_insert = NULL;
bson_t *create_cmd = NULL;
bson_t *create_cmd_opts = NULL;
mongoc_write_concern_t *wc = NULL;
mongoc_client_encryption_t *client_encryption = NULL;
mongoc_client_encryption_opts_t *client_encryption_opts = NULL;
mongoc_client_encryption_datakey_opts_t *datakey_opts = NULL;
char *keyaltnames[] = {"mongoc_encryption_example_3"};
bson_value_t datakey_id = {0};
bson_value_t encrypted_field = {0};
bson_value_t to_encrypt = {0};
mongoc_client_encryption_encrypt_opts_t *encrypt_opts = NULL;
bson_value_t decrypted = {0};
mongoc_init ();
/* Configure the master key. This must be the same master key that was used
* to create the encryption key. */
local_masterkey =
hex_to_bin (getenv ("LOCAL_MASTERKEY"), &local_masterkey_len);
if (!local_masterkey || local_masterkey_len != 96) {
fprintf (stderr,
"Specify LOCAL_MASTERKEY environment variable as a "
"secure random 96 byte hex value.\n");
goto fail;
}
kms_providers = BCON_NEW ("local",
"{",
"key",
BCON_BIN (0, local_masterkey, local_masterkey_len),
"}");
/* The mongoc_client_t used to read/write application data. */
client =
mongoc_client_new ("mongodb://localhost/?appname=client-side-encryption");
coll = mongoc_client_get_collection (client, ENCRYPTED_DB, ENCRYPTED_COLL);
/* Clear old data */
mongoc_collection_drop (coll, NULL);
and we're gonna stop right there 'cause the next part picks it up โ no spoilers, no cap ๐
/* Set up the key vault for this example. */
keyvault_coll =
mongoc_client_get_collection (client, KEYVAULT_DB, KEYVAULT_COLL);
mongoc_collection_drop (keyvault_coll, NULL);
/* Create a unique index to ensure that two data keys cannot share the same
* keyAltName. This is recommended practice for the key vault. */
index_keys = BCON_NEW ("keyAltNames", BCON_INT32 (1));
index_opts = BCON_NEW ("unique",
BCON_BOOL (true),
"partialFilterExpression",
"{",
"keyAltNames",
"{",
"$exists",
BCON_BOOL (true),
"}",
"}");
index_model = mongoc_index_model_new (index_keys, index_opts);
ret = mongoc_collection_create_indexes_with_opts (keyvault_coll,
&index_model,
1,
NULL /* opts */,
NULL /* reply */,
&error);
if (!ret) {
goto fail;
}
client_encryption_opts = mongoc_client_encryption_opts_new ();
mongoc_client_encryption_opts_set_kms_providers (client_encryption_opts,
kms_providers);
mongoc_client_encryption_opts_set_keyvault_namespace (
client_encryption_opts, KEYVAULT_DB, KEYVAULT_COLL);
/* Set a mongoc_client_t to use for reading/writing to the key vault. This
* can be the same mongoc_client_t used by the main application. */
mongoc_client_encryption_opts_set_keyvault_client (client_encryption_opts,
client);
client_encryption =
mongoc_client_encryption_new (client_encryption_opts, &error);
if (!client_encryption) {
goto fail;
}
/* Create a new data key for the encryptedField.
* https://dochub.mongodb.org/core/client-side-field-level-encryption-automatic-encryption-rules
*/
datakey_opts = mongoc_client_encryption_datakey_opts_new ();
mongoc_client_encryption_datakey_opts_set_keyaltnames (
datakey_opts, keyaltnames, 1);
if (!mongoc_client_encryption_create_datakey (
client_encryption, "local", datakey_opts, &datakey_id, &error)) {
goto fail;
}
/* Explicitly encrypt a field */
encrypt_opts = mongoc_client_encryption_encrypt_opts_new ();
mongoc_client_encryption_encrypt_opts_set_algorithm (
encrypt_opts, MONGOC_AEAD_AES_256_CBC_HMAC_SHA_512_DETERMINISTIC);
mongoc_client_encryption_encrypt_opts_set_keyid (encrypt_opts, &datakey_id);
to_encrypt.value_type = BSON_TYPE_UTF8;
to_encrypt.value.v_utf8.str = "123456789";
const size_t len = strlen (to_encrypt.value.v_utf8.str);
BSON_ASSERT (bson_in_range_unsigned (uint32_t, len));
to_encrypt.value.v_utf8.len = (uint32_t) len;
ret = mongoc_client_encryption_encrypt (
client_encryption, &to_encrypt, encrypt_opts, &encrypted_field, &error);
if (!ret) {
goto fail;
}
to_insert = bson_new ();
BSON_APPEND_VALUE (to_insert, "encryptedField", &encrypted_field);
ret = mongoc_collection_insert_one (
coll, to_insert, NULL /* opts */, NULL /* reply */, &error);
if (!ret) {
goto fail;
}
printf ("encrypted document: ");
if (!print_one_document (coll, &error)) {
goto fail;
}
printf ("\n");
/* Explicitly decrypt a field */
ret = mongoc_client_encryption_decrypt (
client_encryption, &encrypted_field, &decrypted, &error);
if (!ret) {
goto fail;
}
printf ("decrypted value: %s\n", decrypted.value.v_utf8.str);
exit_status = EXIT_SUCCESS;
fail:
if (error.code) {
fprintf (stderr, "error: %s\n", error.message);
}
bson_free (local_masterkey);
bson_destroy (kms_providers);
mongoc_collection_destroy (keyvault_coll);
mongoc_index_model_destroy (index_model);
bson_destroy (index_opts);
bson_destroy (index_keys);
mongoc_collection_destroy (coll);
mongoc_client_destroy (client);
bson_destroy (to_insert);
bson_destroy (schema);
bson_destroy (create_cmd);
bson_destroy (create_cmd_opts);
mongoc_write_concern_destroy (wc);
mongoc_client_encryption_destroy (client_encryption);
mongoc_client_encryption_datakey_opts_destroy (datakey_opts);
mongoc_client_encryption_opts_destroy (client_encryption_opts);
bson_value_destroy (&encrypted_field);
mongoc_client_encryption_encrypt_opts_destroy (encrypt_opts);
bson_value_destroy (&decrypted);
bson_value_destroy (&datakey_id);
mongoc_cleanup ();
return exit_status;
}
like, the cleanup at the end? chef's kiss ๐ every single thing gets destroyed in reverse-ish order and nobody leaks, we love to see it.
๐ฆ Explicit Encryption with Automatic Decryption ๐ฆ
ok so here's the tea bestie โ โ automatic encryption needs MongoDB 4.2 enterprise or a MongoDB 4.2 Atlas cluster. gatekept, honestly. BUT โ automatic decryption is supported for all users ๐ no cap.
so if you want the decryption magic without the encryption side of things, you set bypass_auto_encryption=True in mongoc_auto_encryption_opts_t:
client-side-encryption-auto-decryption.c
#include <mongoc/mongoc.h>
#include <stdio.h>
#include <stdlib.h>
#include "client-side-encryption-helpers.h"
/* This example demonstrates how to set up automatic decryption without
* automatic encryption using the community version of MongoDB */
int
main (void)
{
/* The collection used to store the encryption data keys. */
#define KEYVAULT_DB "encryption"
#define KEYVAULT_COLL "__libmongocTestKeyVault"
/* The collection used to store the encrypted documents in this example. */
#define ENCRYPTED_DB "test"
#define ENCRYPTED_COLL "coll"
int exit_status = EXIT_FAILURE;
bool ret;
uint8_t *local_masterkey = NULL;
uint32_t local_masterkey_len;
bson_t *kms_providers = NULL;
bson_error_t error = {0};
bson_t *index_keys = NULL;
bson_t *index_opts = NULL;
mongoc_index_model_t *index_model = NULL;
bson_t *schema = NULL;
mongoc_client_t *client = NULL;
mongoc_collection_t *coll = NULL;
mongoc_collection_t *keyvault_coll = NULL;
bson_t *to_insert = NULL;
bson_t *create_cmd = NULL;
bson_t *create_cmd_opts = NULL;
mongoc_write_concern_t *wc = NULL;
mongoc_client_encryption_t *client_encryption = NULL;
mongoc_client_encryption_opts_t *client_encryption_opts = NULL;
mongoc_client_encryption_datakey_opts_t *datakey_opts = NULL;
char *keyaltnames[] = {"mongoc_encryption_example_4"};
bson_value_t datakey_id = {0};
bson_value_t encrypted_field = {0};
bson_value_t to_encrypt = {0};
mongoc_client_encryption_encrypt_opts_t *encrypt_opts = NULL;
bson_value_t decrypted = {0};
mongoc_auto_encryption_opts_t *auto_encryption_opts = NULL;
mongoc_client_t *unencrypted_client = NULL;
mongoc_collection_t *unencrypted_coll = NULL;
mongoc_init ();
/* Configure the master key. This must be the same master key that was used
* to create the encryption key. */
local_masterkey =
hex_to_bin (getenv ("LOCAL_MASTERKEY"), &local_masterkey_len);
if (!local_masterkey || local_masterkey_len != 96) {
fprintf (stderr,
"Specify LOCAL_MASTERKEY environment variable as a "
"secure random 96 byte hex value.\n");
goto fail;
}
kms_providers = BCON_NEW ("local",
"{",
"key",
BCON_BIN (0, local_masterkey, local_masterkey_len),
"}");
client =
mongoc_client_new ("mongodb://localhost/?appname=client-side-encryption");
auto_encryption_opts = mongoc_auto_encryption_opts_new ();
mongoc_auto_encryption_opts_set_keyvault_namespace (
auto_encryption_opts, KEYVAULT_DB, KEYVAULT_COLL);
mongoc_auto_encryption_opts_set_kms_providers (auto_encryption_opts,
kms_providers);
/* Setting bypass_auto_encryption to true disables automatic encryption but
* keeps the automatic decryption behavior. bypass_auto_encryption will also
* disable spawning mongocryptd */
mongoc_auto_encryption_opts_set_bypass_auto_encryption (auto_encryption_opts,
true);
/* Once bypass_auto_encryption is set, community users can enable auto
* encryption on the client. This will, in fact, only perform automatic
* decryption. */
ret = mongoc_client_enable_auto_encryption (
client, auto_encryption_opts, &error);
if (!ret) {
goto fail;
}
/* Now that automatic decryption is on, we can test it by inserting a
* document with an explicitly encrypted value into the collection. When we
* look up the document later, it should be automatically decrypted for us.
*/
coll = mongoc_client_get_collection (client, ENCRYPTED_DB, ENCRYPTED_COLL);
/* Clear old data */
mongoc_collection_drop (coll, NULL);
/* Set up the key vault for this example. */
keyvault_coll =
mongoc_client_get_collection (client, KEYVAULT_DB, KEYVAULT_COLL);
mongoc_collection_drop (keyvault_coll, NULL);
so yeah โ the whole plot of that file is: you turn on mongoc_client_enable_auto_encryption() with bypass_auto_encryption set to true, and the client is now a decryption machine โจ it'll happily pull explicitly-encrypted values back out of the collection and hand them to you all pretty and readable. and remember, bypass_auto_encryption also disables spawning mongocryptd โ it's giving multitasking queen ๐
and that's only the setup portion btw, the file just keeps going. we're not done, bestie โ stay with me ๐ซถ
okay bestie so that code block from before did NOT stop, it just kept going ๐ญ here's the rest of it, verbatim, no cap:
/* Create a unique index to ensure that two data keys cannot share the same
* keyAltName. This is recommended practice for the key vault. */
index_keys = BCON_NEW ("keyAltNames", BCON_INT32 (1));
index_opts = BCON_NEW ("unique",
BCON_BOOL (true),
"partialFilterExpression",
"{",
"keyAltNames",
"{",
"$exists",
BCON_BOOL (true),
"}",
"}");
index_model = mongoc_index_model_new (index_keys, index_opts);
ret = mongoc_collection_create_indexes_with_opts (keyvault_coll,
&index_model,
1,
NULL /* opts */,
NULL /* reply */,
&error);
if (!ret) {
goto fail;
}
client_encryption_opts = mongoc_client_encryption_opts_new ();
mongoc_client_encryption_opts_set_kms_providers (client_encryption_opts,
kms_providers);
mongoc_client_encryption_opts_set_keyvault_namespace (
client_encryption_opts, KEYVAULT_DB, KEYVAULT_COLL);
/* The key vault client is used for reading to/from the key vault. This can
* be the same mongoc_client_t used by the application. */
mongoc_client_encryption_opts_set_keyvault_client (client_encryption_opts,
client);
client_encryption =
mongoc_client_encryption_new (client_encryption_opts, &error);
if (!client_encryption) {
goto fail;
}
/* Create a new data key for the encryptedField.
* https://dochub.mongodb.org/core/client-side-field-level-encryption-automatic-encryption-rules
*/
datakey_opts = mongoc_client_encryption_datakey_opts_new ();
mongoc_client_encryption_datakey_opts_set_keyaltnames (
datakey_opts, keyaltnames, 1);
ret = mongoc_client_encryption_create_datakey (
client_encryption, "local", datakey_opts, &datakey_id, &error);
if (!ret) {
goto fail;
}
/* Explicitly encrypt a field. */
encrypt_opts = mongoc_client_encryption_encrypt_opts_new ();
mongoc_client_encryption_encrypt_opts_set_algorithm (
encrypt_opts, MONGOC_AEAD_AES_256_CBC_HMAC_SHA_512_DETERMINISTIC);
mongoc_client_encryption_encrypt_opts_set_keyaltname (
encrypt_opts, "mongoc_encryption_example_4");
to_encrypt.value_type = BSON_TYPE_UTF8;
to_encrypt.value.v_utf8.str = "123456789";
const size_t len = strlen (to_encrypt.value.v_utf8.str);
BSON_ASSERT (bson_in_range_unsigned (uint32_t, len));
to_encrypt.value.v_utf8.len = (uint32_t) len;
ret = mongoc_client_encryption_encrypt (
client_encryption, &to_encrypt, encrypt_opts, &encrypted_field, &error);
if (!ret) {
goto fail;
}
to_insert = bson_new ();
BSON_APPEND_VALUE (to_insert, "encryptedField", &encrypted_field);
ret = mongoc_collection_insert_one (
coll, to_insert, NULL /* opts */, NULL /* reply */, &error);
if (!ret) {
goto fail;
}
/* When we retrieve the document, any encrypted fields will get automatically
* decrypted by the driver. */
printf ("decrypted document: ");
if (!print_one_document (coll, &error)) {
goto fail;
}
printf ("\n");
unencrypted_client =
mongoc_client_new ("mongodb://localhost/?appname=client-side-encryption");
unencrypted_coll = mongoc_client_get_collection (
unencrypted_client, ENCRYPTED_DB, ENCRYPTED_COLL);
printf ("encrypted document: ");
if (!print_one_document (unencrypted_coll, &error)) {
goto fail;
}
printf ("\n");
exit_status = EXIT_SUCCESS;
fail:
if (error.code) {
fprintf (stderr, "error: %s\n", error.message);
}
bson_free (local_masterkey);
bson_destroy (kms_providers);
mongoc_collection_destroy (keyvault_coll);
mongoc_index_model_destroy (index_model);
bson_destroy (index_opts);
bson_destroy (index_keys);
mongoc_collection_destroy (coll);
mongoc_client_destroy (client);
bson_destroy (to_insert);
bson_destroy (schema);
bson_destroy (create_cmd);
bson_destroy (create_cmd_opts);
mongoc_write_concern_destroy (wc);
mongoc_client_encryption_destroy (client_encryption);
mongoc_client_encryption_datakey_opts_destroy (datakey_opts);
mongoc_client_encryption_opts_destroy (client_encryption_opts);
bson_value_destroy (&encrypted_field);
mongoc_client_encryption_encrypt_opts_destroy (encrypt_opts);
bson_value_destroy (&decrypted);
bson_value_destroy (&datakey_id);
mongoc_collection_destroy (unencrypted_coll);
mongoc_client_destroy (unencrypted_client);
mongoc_auto_encryption_opts_destroy (auto_encryption_opts);
mongoc_cleanup ();
return exit_status;
}
and THAT, bestie, is the whole explicit-encryption example, start to finish ๐ every goto fail, every destroy, every vibe. slay.
โจ Queryable Encryption โจ
okay so to use Queryable Encryption you need MongoDB Server 7.0 or higher. that's the floor, no exceptions ๐
and if you want the full tea on the feature itself, the original points you at the MongoDB Manual for Queryable Encryption. go read it, iykyk.
๐จ bestie listen to me: the API related to the "rangePreview" algorithm is still experimental and subject to breaking changes! it is giving "do not build your whole personality on this yet" energy ๐ญ
Queryable Encryption in older MongoDB Server versions
okay so history lesson ๐ฆ: MongoDB Server 6.0 introduced Queryable Encryption as a Public Technical Preview. then MongoDB Server 7.0 rolled in with backwards breaking changes to the Queryable Encryption protocol. not a glow-up, a glow-change, and it breaks stuff.
those backwards breaking changes get applied in the client protocol in libmongocrypt 1.8.0.
here's the compatibility chart in vibes form:
- libmongoc 1.24.0 requires libmongocrypt 1.8.0 or newer.
- libmongoc 1.24.0 no longer supports Queryable Encryption on MongoDB Server <7.0.
- using Queryable Encryption with libmongoc 1.24.0 and higher requires MongoDB Server >=7.0.
and the messy part: using Queryable Encryption with libmongocrypt<1.8.0 on a MongoDB Server>=7.0, or using libmongocrypt>=1.8.0 on a MongoDB Server<6.0, will result in a server error when using the incompatible protocol. incompatible protocol = server said no ๐
SEE ALSO:
- The MongoDB Manual for Queryable Encryption
๐ธ Installation ๐ธ
using In-Use Encryption in the C driver requires the dependency libmongocrypt. for the actual installation instructions, go see the MongoDB Manual for libmongocrypt installation.
once libmongocrypt is installed, configure the C driver with -DENABLE_CLIENT_SIDE_ENCRYPTION=ON to require In-Use Encryption be enabled. like, it will insist. it's a main character about it ๐
$ cd mongo-c-driver
$ mkdir cmake-build && cd cmake-build
$ cmake -DENABLE_AUTOMATIC_INIT_AND_CLEANUP=OFF -DENABLE_CLIENT_SIDE_ENCRYPTION=ON ..
$ cmake --build . --target install
look at that. clean. iconic. build target install and go โจ
๐ API ๐
mongoc_client_encryption_tโ this one's your girl for explicit encryption and key management.mongoc_client_enable_auto_encryption()andmongoc_client_pool_enable_auto_encryption()โ these are how you turn on automatic encryption.
the Queryable Encryption and CSFLE features share much of the same API, but there are some exceptions, bestie. here they are:
- the supported algorithms documented in
mongoc_client_encryption_encrypt_opts_set_algorithm()do not apply to both features. mongoc_auto_encryption_opts_set_encrypted_fields_map()only applies to Queryable Encryption.mongoc_auto_encryption_opts_set_schema_map()only applies to CSFLE.
see? same API, different rules. it's giving siblings who share a wardrobe but not a personality ๐
๐ Query Analysis ๐
to support the automatic encryption feature, you need one of the following dependencies:
- the
mongocryptdexecutable. see the MongoDB Manual documentation: Install and Configure mongocryptd - the
crypt_sharedlibrary. see the MongoDB Manual documentation: Automatic Encryption Shared Library
a mongoc_client_t or mongoc_client_pool_t configured with auto encryption will automatically try to load the crypt_shared library. and if loading crypt_shared fails? it doesn't panic, it pivots ๐
โ the mongoc_client_t or mongoc_client_pool_t will try to spawn the mongocryptd process from the application's PATH.
and to configure the use of crypt_shared and mongocryptd? that's mongoc_auto_encryption_opts_set_extra(), bestie. go look at it.
๐ AUTHOR ๐
MongoDB, Inc
๐ COPYRIGHT ๐
2017-present, MongoDB, Inc
and that's a wrap on the whole page, bestie ๐ซถ this was MONGOC_GUIDES(3) from the 1.26.0 era, dated Mar 31, 2024 โ so if you're reading this in a version newer than that, go check the fresh docs, fr fr. you absolutely ate this entire manual. periodt ๐โจ
๐ฆ where this page comes from
- ubuntu noble: libmongoc-doc 1.26.0-1.1ubuntu2 ยท Apache-2.0
- ๐ girlified by deepseek-ai/DeepSeek-V4.1-Flash on : a language model's rewrite of the original, under its terms