Skip to content

Commit 1793ff4

Browse files
authored
MONGOCRYPT-588 support bulkWrite (#745)
Support processing `bulkWrite`: * add `MONGOCRYPT_CTX_NEED_MONGO_COLLINFO_WITH_DB` state to support obtaining remote schema on a target database differing from the command database * add `mongocrypt_setopt_use_need_mongo_collinfo_with_db_state` Improve test runner: * identify missing key in error message * propagate error message when matching BSON arrays * retain dots in keys when matching * fix test data for `fle2-explain`
1 parent 0f9a2ec commit 1793ff4

30 files changed

+1373
-109
lines changed

integrating.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,23 @@ A result from a listCollections cursor.
137137

138138
auto encrypt
139139

140+
#### State: `MONGOCRYPT_CTX_NEED_MONGO_COLLINFO_WITH_DB` ####
141+
142+
**libmongocrypt needs**...
143+
144+
Results from a listCollections cursor from a specified database.
145+
146+
**Driver needs to...**
147+
148+
1. Run listCollections on the encrypted MongoClient with the filter
149+
provided by `mongocrypt_ctx_mongo_op` on the database provided by `mongocrypt_ctx_mongo_db`.
150+
2. Return the first result (if any) with `mongocrypt_ctx_mongo_feed` or proceed to the next step if nothing was returned.
151+
3. Call `mongocrypt_ctx_mongo_done`
152+
153+
**Applies to...**
154+
155+
A context initialized with `mongocrypt_ctx_encrypt_init` for automatic encryption. This state is only entered when `mongocrypt_setopt_use_need_mongo_collinfo_with_db_state` is called to opt-in.
156+
140157
#### State: `MONGOCRYPT_CTX_NEED_MONGO_MARKINGS` ####
141158

142159
**libmongocrypt needs**...

src/mongocrypt-ctx-encrypt.c

Lines changed: 358 additions & 80 deletions
Large diffs are not rendered by default.

src/mongocrypt-ctx-private.h

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ typedef struct __mongocrypt_ctx_opts_t {
8989

9090
/* All derived contexts may override these methods. */
9191
typedef struct {
92+
const char *(*mongo_db_collinfo)(mongocrypt_ctx_t *ctx);
9293
bool (*mongo_op_collinfo)(mongocrypt_ctx_t *ctx, mongocrypt_binary_t *out);
9394
bool (*mongo_feed_collinfo)(mongocrypt_ctx_t *ctx, mongocrypt_binary_t *in);
9495
bool (*mongo_done_collinfo)(mongocrypt_ctx_t *ctx);
@@ -134,9 +135,22 @@ bool _mongocrypt_ctx_fail_w_msg(mongocrypt_ctx_t *ctx, const char *msg);
134135
typedef struct {
135136
mongocrypt_ctx_t parent;
136137
bool explicit;
137-
char *coll_name;
138-
char *db_name;
139-
char *ns;
138+
139+
// `cmd_db` is the command database (appended as `$db`).
140+
char *cmd_db;
141+
142+
// `target_ns` is the target namespace "<target_db>.<target_coll>" for the operation. May be associated with
143+
// jsonSchema (CSFLE) or encryptedFields (QE). For `bulkWrite`, the target namespace database may differ from
144+
// `cmd_db`.
145+
char *target_ns;
146+
147+
// `target_db` is the target database for the operation. For `bulkWrite`, the target namespace database may differ
148+
// from `cmd_db`. If `target_db` is NULL, the target namespace database is the same as `cmd_db`.
149+
char *target_db;
150+
151+
// `target_coll` is the target namespace collection name.
152+
char *target_coll;
153+
140154
_mongocrypt_buffer_t list_collections_filter;
141155
_mongocrypt_buffer_t schema;
142156
/* TODO CDRIVER-3150: audit + rename these buffers.
@@ -161,13 +175,19 @@ typedef struct {
161175
* schema, and there were siblings. */
162176
bool collinfo_has_siblings;
163177
/* encrypted_field_config is set when:
164-
* 1. <db_name>.<coll_name> is present in an encrypted_field_config_map.
178+
* 1. `target_ns` is present in an encrypted_field_config_map.
165179
* 2. (TODO MONGOCRYPT-414) The collection has encryptedFields in the
166180
* response to listCollections. encrypted_field_config is true if and only if
167181
* encryption is using FLE 2.0.
182+
* 3. The `bulkWrite` command is processed and needs an empty encryptedFields to be processed by query analysis.
183+
* (`bulkWrite` does not support empty JSON schema).
168184
*/
169185
_mongocrypt_buffer_t encrypted_field_config;
170186
mc_EncryptedFieldConfig_t efc;
187+
// `used_empty_encryptedFields` is true if the collection has no JSON schema or encryptedFields,
188+
// yet an empty encryptedFields was constructed to support query analysis.
189+
// When true, an empty encryptedFields is sent to query analysis, but not appended to the final command.
190+
bool used_empty_encryptedFields;
171191
/* bypass_query_analysis is set to true to skip the
172192
* MONGOCRYPT_CTX_NEED_MONGO_MARKINGS state. */
173193
bool bypass_query_analysis;

src/mongocrypt-ctx.c

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -386,6 +386,7 @@ bool mongocrypt_ctx_mongo_op(mongocrypt_ctx_t *ctx, mongocrypt_binary_t *out) {
386386
}
387387

388388
switch (ctx->state) {
389+
case MONGOCRYPT_CTX_NEED_MONGO_COLLINFO_WITH_DB:
389390
case MONGOCRYPT_CTX_NEED_MONGO_COLLINFO: CHECK_AND_CALL(mongo_op_collinfo, ctx, out);
390391
case MONGOCRYPT_CTX_NEED_MONGO_MARKINGS: CHECK_AND_CALL(mongo_op_markings, ctx, out);
391392
case MONGOCRYPT_CTX_NEED_MONGO_KEYS: CHECK_AND_CALL(mongo_op_keys, ctx, out);
@@ -398,6 +399,38 @@ bool mongocrypt_ctx_mongo_op(mongocrypt_ctx_t *ctx, mongocrypt_binary_t *out) {
398399
}
399400
}
400401

402+
const char *mongocrypt_ctx_mongo_db(mongocrypt_ctx_t *ctx) {
403+
if (!ctx) {
404+
return NULL;
405+
}
406+
if (!ctx->initialized) {
407+
_mongocrypt_ctx_fail_w_msg(ctx, "ctx NULL or uninitialized");
408+
return NULL;
409+
}
410+
411+
switch (ctx->state) {
412+
case MONGOCRYPT_CTX_NEED_MONGO_COLLINFO_WITH_DB: {
413+
if (!ctx->vtable.mongo_db_collinfo) {
414+
_mongocrypt_ctx_fail_w_msg(ctx, "not applicable to context");
415+
return NULL;
416+
}
417+
return ctx->vtable.mongo_db_collinfo(ctx);
418+
}
419+
case MONGOCRYPT_CTX_ERROR: return false;
420+
case MONGOCRYPT_CTX_NEED_MONGO_COLLINFO:
421+
case MONGOCRYPT_CTX_NEED_MONGO_MARKINGS:
422+
case MONGOCRYPT_CTX_NEED_MONGO_KEYS:
423+
case MONGOCRYPT_CTX_DONE:
424+
case MONGOCRYPT_CTX_NEED_KMS_CREDENTIALS:
425+
case MONGOCRYPT_CTX_NEED_KMS:
426+
case MONGOCRYPT_CTX_READY:
427+
default: {
428+
_mongocrypt_ctx_fail_w_msg(ctx, "wrong state");
429+
return NULL;
430+
}
431+
}
432+
}
433+
401434
bool mongocrypt_ctx_mongo_feed(mongocrypt_ctx_t *ctx, mongocrypt_binary_t *in) {
402435
if (!ctx) {
403436
return false;
@@ -419,6 +452,7 @@ bool mongocrypt_ctx_mongo_feed(mongocrypt_ctx_t *ctx, mongocrypt_binary_t *in) {
419452
}
420453

421454
switch (ctx->state) {
455+
case MONGOCRYPT_CTX_NEED_MONGO_COLLINFO_WITH_DB:
422456
case MONGOCRYPT_CTX_NEED_MONGO_COLLINFO: CHECK_AND_CALL(mongo_feed_collinfo, ctx, in);
423457
case MONGOCRYPT_CTX_NEED_MONGO_MARKINGS: CHECK_AND_CALL(mongo_feed_markings, ctx, in);
424458
case MONGOCRYPT_CTX_NEED_MONGO_KEYS: CHECK_AND_CALL(mongo_feed_keys, ctx, in);
@@ -440,6 +474,7 @@ bool mongocrypt_ctx_mongo_done(mongocrypt_ctx_t *ctx) {
440474
}
441475

442476
switch (ctx->state) {
477+
case MONGOCRYPT_CTX_NEED_MONGO_COLLINFO_WITH_DB:
443478
case MONGOCRYPT_CTX_NEED_MONGO_COLLINFO: CHECK_AND_CALL(mongo_done_collinfo, ctx);
444479
case MONGOCRYPT_CTX_NEED_MONGO_MARKINGS: CHECK_AND_CALL(mongo_done_markings, ctx);
445480
case MONGOCRYPT_CTX_NEED_MONGO_KEYS: CHECK_AND_CALL(mongo_done_keys, ctx);
@@ -483,6 +518,7 @@ mongocrypt_kms_ctx_t *mongocrypt_ctx_next_kms_ctx(mongocrypt_ctx_t *ctx) {
483518
case MONGOCRYPT_CTX_ERROR: return NULL;
484519
case MONGOCRYPT_CTX_DONE:
485520
case MONGOCRYPT_CTX_NEED_KMS_CREDENTIALS:
521+
case MONGOCRYPT_CTX_NEED_MONGO_COLLINFO_WITH_DB:
486522
case MONGOCRYPT_CTX_NEED_MONGO_COLLINFO:
487523
case MONGOCRYPT_CTX_NEED_MONGO_KEYS:
488524
case MONGOCRYPT_CTX_NEED_MONGO_MARKINGS:
@@ -554,6 +590,7 @@ bool mongocrypt_ctx_kms_done(mongocrypt_ctx_t *ctx) {
554590
case MONGOCRYPT_CTX_ERROR: return false;
555591
case MONGOCRYPT_CTX_DONE:
556592
case MONGOCRYPT_CTX_NEED_KMS_CREDENTIALS:
593+
case MONGOCRYPT_CTX_NEED_MONGO_COLLINFO_WITH_DB:
557594
case MONGOCRYPT_CTX_NEED_MONGO_COLLINFO:
558595
case MONGOCRYPT_CTX_NEED_MONGO_KEYS:
559596
case MONGOCRYPT_CTX_NEED_MONGO_MARKINGS:
@@ -584,6 +621,7 @@ bool mongocrypt_ctx_finalize(mongocrypt_ctx_t *ctx, mongocrypt_binary_t *out) {
584621
case MONGOCRYPT_CTX_DONE:
585622
case MONGOCRYPT_CTX_NEED_KMS_CREDENTIALS:
586623
case MONGOCRYPT_CTX_NEED_KMS:
624+
case MONGOCRYPT_CTX_NEED_MONGO_COLLINFO_WITH_DB:
587625
case MONGOCRYPT_CTX_NEED_MONGO_COLLINFO:
588626
case MONGOCRYPT_CTX_NEED_MONGO_KEYS:
589627
case MONGOCRYPT_CTX_NEED_MONGO_MARKINGS:

src/mongocrypt-opts-private.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,7 @@ typedef struct {
115115
mstr crypt_shared_lib_override_path;
116116

117117
bool use_need_kms_credentials_state;
118+
bool use_need_mongo_collinfo_with_db_state;
118119
bool bypass_query_analysis;
119120

120121
// When creating new encrypted payloads,

src/mongocrypt.c

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1143,6 +1143,12 @@ void mongocrypt_setopt_use_need_kms_credentials_state(mongocrypt_t *crypt) {
11431143
crypt->opts.use_need_kms_credentials_state = true;
11441144
}
11451145

1146+
void mongocrypt_setopt_use_need_mongo_collinfo_with_db_state(mongocrypt_t *crypt) {
1147+
BSON_ASSERT_PARAM(crypt);
1148+
1149+
crypt->opts.use_need_mongo_collinfo_with_db_state = true;
1150+
}
1151+
11461152
void mongocrypt_setopt_set_crypt_shared_lib_path_override(mongocrypt_t *crypt, const char *path) {
11471153
BSON_ASSERT_PARAM(crypt);
11481154
BSON_ASSERT_PARAM(path);

src/mongocrypt.h

Lines changed: 34 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -473,6 +473,18 @@ void mongocrypt_setopt_set_crypt_shared_lib_path_override(mongocrypt_t *crypt, c
473473
MONGOCRYPT_EXPORT
474474
void mongocrypt_setopt_use_need_kms_credentials_state(mongocrypt_t *crypt);
475475

476+
/**
477+
* @brief Opt-into handling the MONGOCRYPT_CTX_NEED_MONGO_COLLINFO_WITH_DB state.
478+
*
479+
* A context enters the MONGOCRYPT_CTX_NEED_MONGO_COLLINFO_WITH_DB state when
480+
* processing a `bulkWrite` command. The target database of the `bulkWrite` may differ from the command database
481+
* ("admin").
482+
*
483+
* @param[in] crypt The @ref mongocrypt_t object to update
484+
*/
485+
MONGOCRYPT_EXPORT
486+
void mongocrypt_setopt_use_need_mongo_collinfo_with_db_state(mongocrypt_t *crypt);
487+
476488
/**
477489
* Initialize new @ref mongocrypt_t object.
478490
*
@@ -962,9 +974,10 @@ bool mongocrypt_ctx_rewrap_many_datakey_init(mongocrypt_ctx_t *ctx, mongocrypt_b
962974
*/
963975
typedef enum {
964976
MONGOCRYPT_CTX_ERROR = 0,
965-
MONGOCRYPT_CTX_NEED_MONGO_COLLINFO = 1, /* run on main MongoClient */
966-
MONGOCRYPT_CTX_NEED_MONGO_MARKINGS = 2, /* run on mongocryptd. */
967-
MONGOCRYPT_CTX_NEED_MONGO_KEYS = 3, /* run on key vault */
977+
MONGOCRYPT_CTX_NEED_MONGO_COLLINFO = 1, /* run on main MongoClient */
978+
MONGOCRYPT_CTX_NEED_MONGO_COLLINFO_WITH_DB = 8, /* run on main MongoClient */
979+
MONGOCRYPT_CTX_NEED_MONGO_MARKINGS = 2, /* run on mongocryptd. */
980+
MONGOCRYPT_CTX_NEED_MONGO_KEYS = 3, /* run on key vault */
968981
MONGOCRYPT_CTX_NEED_KMS = 4,
969982
MONGOCRYPT_CTX_NEED_KMS_CREDENTIALS = 7, /* fetch/renew KMS credentials */
970983
MONGOCRYPT_CTX_READY = 5, /* ready for encryption/decryption */
@@ -985,7 +998,7 @@ mongocrypt_ctx_state_t mongocrypt_ctx_state(mongocrypt_ctx_t *ctx);
985998
* is in MONGOCRYPT_CTX_NEED_MONGO_* states.
986999
*
9871000
* @p op_bson is a BSON document to be used for the operation.
988-
* - For MONGOCRYPT_CTX_NEED_MONGO_COLLINFO it is a listCollections filter.
1001+
* - For MONGOCRYPT_CTX_NEED_MONGO_COLLINFO(_WITH_DB) it is a listCollections filter.
9891002
* - For MONGOCRYPT_CTX_NEED_MONGO_KEYS it is a find filter.
9901003
* - For MONGOCRYPT_CTX_NEED_MONGO_MARKINGS it is a command to send to
9911004
* mongocryptd.
@@ -1003,13 +1016,29 @@ mongocrypt_ctx_state_t mongocrypt_ctx_state(mongocrypt_ctx_t *ctx);
10031016
MONGOCRYPT_EXPORT
10041017
bool mongocrypt_ctx_mongo_op(mongocrypt_ctx_t *ctx, mongocrypt_binary_t *op_bson);
10051018

1019+
/**
1020+
* Get the database to run the mongo operation.
1021+
*
1022+
* Only applies when mongocrypt_ctx_t is in the state:
1023+
* MONGOCRYPT_CTX_NEED_MONGO_COLLINFO_WITH_DB.
1024+
*
1025+
* The lifetime of the returned string is tied to the lifetime of @p ctx. It is
1026+
* valid until @ref mongocrypt_ctx_destroy is called.
1027+
*
1028+
* @param[in] ctx The @ref mongocrypt_ctx_t object.
1029+
* @returns A string or NULL. If NULL, an error status is set. Retrieve it with
1030+
* @ref mongocrypt_ctx_status
1031+
*/
1032+
MONGOCRYPT_EXPORT
1033+
const char *mongocrypt_ctx_mongo_db(mongocrypt_ctx_t *ctx);
1034+
10061035
/**
10071036
* Feed a BSON reply or result when mongocrypt_ctx_t is in
10081037
* MONGOCRYPT_CTX_NEED_MONGO_* states. This may be called multiple times
10091038
* depending on the operation.
10101039
*
10111040
* reply is a BSON document result being fed back for this operation.
1012-
* - For MONGOCRYPT_CTX_NEED_MONGO_COLLINFO it is a doc from a listCollections
1041+
* - For MONGOCRYPT_CTX_NEED_MONGO_COLLINFO(_WITH_DB) it is a doc from a listCollections
10131042
* cursor. (Note, if listCollections returned no result, do not call this
10141043
* function.)
10151044
* - For MONGOCRYPT_CTX_NEED_MONGO_KEYS it is a doc from a find cursor.
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
{
2+
"bulkWrite": {
3+
"$numberInt": "1"
4+
},
5+
"ops": [
6+
{
7+
"insert": {
8+
"$numberInt": "0"
9+
},
10+
"document": {
11+
"plainText": "sample",
12+
"encrypted": {
13+
"$numberInt": "123"
14+
}
15+
}
16+
}
17+
],
18+
"$db": "admin",
19+
"nsInfo": [
20+
{
21+
"ns": "db.test",
22+
"encryptionInformation": {
23+
"type": {
24+
"$numberInt": "1"
25+
},
26+
"schema": {
27+
"db.test": {
28+
"escCollection": "enxcol_.test.esc",
29+
"ecocCollection": "enxcol_.test.ecoc",
30+
"fields": [
31+
{
32+
"keyId": {
33+
"$binary": {
34+
"base64": "YWFhYWFhYWFhYWFhYWFhYQ==",
35+
"subType": "04"
36+
}
37+
},
38+
"path": "encrypted",
39+
"bsonType": "int",
40+
"queries": {
41+
"queryType": "equality",
42+
"contention": {
43+
"$numberLong": "0"
44+
}
45+
}
46+
}
47+
]
48+
}
49+
}
50+
}
51+
}
52+
]
53+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
{
2+
"bulkWrite": {
3+
"$numberInt": "1"
4+
},
5+
"ops": [
6+
{
7+
"insert": 0,
8+
"document": {
9+
"plainText": "sample",
10+
"encrypted": {
11+
"$numberInt": "123"
12+
}
13+
}
14+
}
15+
],
16+
"nsInfo": [
17+
{
18+
"ns": "db.test"
19+
}
20+
],
21+
"jsonSchema": {},
22+
"isRemoteSchema": false
23+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
{
2+
"bulkWrite": 1,
3+
"ops": [
4+
{
5+
"insert": 0,
6+
"document": {
7+
"plainText": "sample",
8+
"encrypted": {
9+
"$numberInt": "123"
10+
}
11+
}
12+
}
13+
],
14+
"nsInfo": [
15+
{
16+
"ns": "db.test"
17+
}
18+
],
19+
"$db": "admin"
20+
}

0 commit comments

Comments
 (0)