/*
 * Copyright 2009-present 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 "operation.h"

#include <mongoc/mongoc-array-private.h>
#include <mongoc/mongoc-bulkwrite.h>
#include <mongoc/mongoc-util-private.h> // hex_to_bin
#include "result.h"
#include "test-diagnostics.h"
#include "test-libmongoc.h"
#include "util.h"
#include <mongoc/utlist.h>
#include <common-bson-dsl-private.h>
#include <common-cmp-private.h>

typedef struct {
   char *name;
   char *object;
   bson_t *arguments;
   bson_t *expect_error;
   bson_val_t *expect_result;
   bool *ignore_result_and_error;
   char *save_result_as_entity;
   bson_parser_t *parser;
   char *session_id;
   mongoc_client_session_t *session;
} operation_t;

static void
operation_destroy (operation_t *op)
{
   if (!op) {
      return;
   }
   bson_parser_destroy_with_parsed_fields (op->parser);
   bson_free (op->session_id);
   bson_free (op);
}

static operation_t *
operation_new (bson_t *bson, bson_error_t *error)
{
   operation_t *op = bson_malloc0 (sizeof (operation_t));
   op->parser = bson_parser_new ();
   bson_parser_utf8 (op->parser, "name", &op->name);
   bson_parser_utf8 (op->parser, "object", &op->object);
   bson_parser_doc_optional (op->parser, "arguments", &op->arguments);
   bson_parser_doc_optional (op->parser, "expectError", &op->expect_error);
   bson_parser_any_optional (op->parser, "expectResult", &op->expect_result);
   bson_parser_bool_optional (op->parser, "ignoreResultAndError", &op->ignore_result_and_error);
   bson_parser_utf8_optional (op->parser, "saveResultAsEntity", &op->save_result_as_entity);
   if (!bson_parser_parse (op->parser, bson, error)) {
      operation_destroy (op);
      return NULL;
   }
   return op;
}

static bool
operation_create_change_stream (test_t *test, operation_t *op, result_t *result, bson_error_t *error)
{
   bool ret = false;
   entity_t *entity = NULL;
   bson_parser_t *parser = NULL;
   mongoc_change_stream_t *changestream = NULL;
   bson_t *pipeline = NULL;
   const bson_t *op_reply = NULL;
   bson_error_t op_error = {0};
   bson_t *opts = NULL;

   parser = bson_parser_new ();
   /* Capture options as all extra fields, and pass them directly as change
    * stream options. */
   bson_parser_allow_extra (parser, true);
   bson_parser_array (parser, "pipeline", &pipeline);
   if (!bson_parser_parse (parser, op->arguments, error)) {
      goto done;
   }

   opts = bson_copy (bson_parser_get_extra (parser));
   if (op->session) {
      if (!mongoc_client_session_append (op->session, opts, error)) {
         goto done;
      }
   }

   entity = entity_map_get (test->entity_map, op->object, error);
   if (!entity) {
      goto done;
   }

   if (0 == strcmp (entity->type, "client")) {
      mongoc_client_t *client = (mongoc_client_t *) entity->value;
      if (!client) {
         test_set_error (error, "client '%s' is closed", entity->id);
         goto done;
      }
      changestream = mongoc_client_watch (client, pipeline, opts);
   } else if (0 == strcmp (entity->type, "database")) {
      mongoc_database_t *db = (mongoc_database_t *) entity->value;
      changestream = mongoc_database_watch (db, pipeline, opts);
   } else if (0 == strcmp (entity->type, "collection")) {
      mongoc_collection_t *coll = (mongoc_collection_t *) entity->value;
      changestream = mongoc_collection_watch (coll, pipeline, opts);
   }

   mongoc_change_stream_error_document (changestream, &op_error, &op_reply);
   result_from_val_and_reply (result, NULL, (bson_t *) op_reply, &op_error);

   if (op->save_result_as_entity) {
      if (!entity_map_add_changestream (test->entity_map, op->save_result_as_entity, changestream, error)) {
         goto done;
      } else {
         // Successfully saved the changestream
      }
   } else {
      // We're not saving the changestream
      mongoc_change_stream_destroy (changestream);
   }

   ret = true;
done:
   bson_parser_destroy_with_parsed_fields (parser);
   bson_destroy (opts);
   return ret;
}

static bool
operation_list_databases (test_t *test, operation_t *op, result_t *result, bson_error_t *error)
{
   bool ret = false;
   mongoc_client_t *client = NULL;
   mongoc_cursor_t *cursor = NULL;
   bson_t *opts = NULL;

   opts = bson_new ();
   if (op->session) {
      if (!mongoc_client_session_append (op->session, opts, error)) {
         goto done;
      }
   }
   if (op->arguments) {
      bson_concat (opts, op->arguments);
   }

   client = entity_map_get_client (test->entity_map, op->object, error);
   if (!client) {
      goto done;
   }

   cursor = mongoc_client_find_databases_with_opts (client, opts);

   result_from_cursor (result, cursor);

   ret = true;
done:
   mongoc_cursor_destroy (cursor);
   bson_destroy (opts);
   return ret;
}

static bool
operation_list_database_names (test_t *test, operation_t *op, result_t *result, bson_error_t *error)
{
   bool ret = false;
   mongoc_client_t *client = NULL;
   bson_t *opts = NULL;

   opts = bson_new ();
   if (op->session) {
      if (!mongoc_client_session_append (op->session, opts, error)) {
         goto done;
      }
   }
   if (op->arguments) {
      bson_concat (opts, op->arguments);
   }

   client = entity_map_get_client (test->entity_map, op->object, error);
   if (!client) {
      goto done;
   }

   char **names = mongoc_client_get_database_names_with_opts (client, opts, error);

   {
      bson_val_t *val = NULL;
      if (names) {
         bson_t bson = BSON_INITIALIZER;
         bson_array_builder_t *element;

         BSON_APPEND_ARRAY_BUILDER_BEGIN (&bson, "v", &element);
         for (char **names_iter = names; *names_iter != NULL; ++names_iter) {
            bson_array_builder_append_utf8 (element, *names_iter, -1);
         }
         bson_append_array_builder_end (&bson, element);

         bson_iter_t iter;
         bson_iter_init_find (&iter, &bson, "v");
         val = bson_val_from_iter (&iter);

         bson_destroy (&bson);
      }
      result_from_val_and_reply (result, val, NULL, error);
      bson_val_destroy (val);
   }

   bson_strfreev (names);

   ret = true;
done:
   bson_destroy (opts);
   return ret;
}

static bool
append_client_bulkwritemodel (mongoc_bulkwrite_t *bw, bson_t *model_wrapper, bson_error_t *error)
{
   bool ok = false;
   // Example `model_wrapper`:
   // { "insertOne": { "namespace": "db.coll", "document": { "_id": 1 } }}
   char *namespace = NULL;
   bson_t *document = NULL;
   bson_t *filter = NULL;
   bson_t *update = NULL;
   bson_t *replacement = NULL;
   bson_t *collation = NULL;
   bson_val_t *hint = NULL;
   bool *upsert = NULL;
   bson_t *arrayFilters = NULL;
   bson_t *sort = NULL;
   bson_parser_t *parser = bson_parser_new ();

   // Expect exactly one root key to identify the model (e.g. "insertOne"):
   if (bson_count_keys (model_wrapper) != 1) {
      test_set_error (error,
                      "expected exactly one key in model, got %" PRIu32 " : %s",
                      bson_count_keys (model_wrapper),
                      tmp_json (model_wrapper));
      goto done;
   }
   bson_iter_t model_wrapper_iter;
   BSON_ASSERT (bson_iter_init (&model_wrapper_iter, model_wrapper));
   BSON_ASSERT (bson_iter_next (&model_wrapper_iter));
   const char *model_name = bson_iter_key (&model_wrapper_iter);
   bson_t model_bson;
   bson_iter_bson (&model_wrapper_iter, &model_bson);

   if (0 == strcmp ("insertOne", model_name)) {
      // Parse an "insertOne".
      bson_parser_utf8 (parser, "namespace", &namespace);
      bson_parser_doc (parser, "document", &document);
      if (!bson_parser_parse (parser, &model_bson, error)) {
         goto done;
      }

      if (!mongoc_bulkwrite_append_insertone (bw, namespace, document, NULL, error)) {
         goto done;
      }
   } else if (0 == strcmp ("updateOne", model_name)) {
      // Parse an "updateOne".
      bson_parser_utf8 (parser, "namespace", &namespace);
      bson_parser_doc (parser, "filter", &filter);
      bson_parser_array_or_doc (parser, "update", &update);
      bson_parser_array_optional (parser, "arrayFilters", &arrayFilters);
      bson_parser_doc_optional (parser, "collation", &collation);
      bson_parser_any_optional (parser, "hint", &hint);
      bson_parser_bool_optional (parser, "upsert", &upsert);
      bson_parser_doc_optional (parser, "sort", &sort);
      if (!bson_parser_parse (parser, &model_bson, error)) {
         goto done;
      }

      mongoc_bulkwrite_updateoneopts_t *opts = mongoc_bulkwrite_updateoneopts_new ();
      mongoc_bulkwrite_updateoneopts_set_arrayfilters (opts, arrayFilters);
      mongoc_bulkwrite_updateoneopts_set_collation (opts, collation);
      if (hint) {
         mongoc_bulkwrite_updateoneopts_set_hint (opts, bson_val_to_value (hint));
      }
      if (upsert) {
         mongoc_bulkwrite_updateoneopts_set_upsert (opts, *upsert);
      }
      if (sort) {
         mongoc_bulkwrite_updateoneopts_set_sort (opts, sort);
      }

      if (!mongoc_bulkwrite_append_updateone (bw, namespace, filter, update, opts, error)) {
         mongoc_bulkwrite_updateoneopts_destroy (opts);
         goto done;
      }
      mongoc_bulkwrite_updateoneopts_destroy (opts);
   } else if (0 == strcmp ("updateMany", model_name)) {
      // Parse an "updateMany".
      bson_parser_utf8 (parser, "namespace", &namespace);
      bson_parser_doc (parser, "filter", &filter);
      bson_parser_array_or_doc (parser, "update", &update);
      bson_parser_array_optional (parser, "arrayFilters", &arrayFilters);
      bson_parser_doc_optional (parser, "collation", &collation);
      bson_parser_any_optional (parser, "hint", &hint);
      bson_parser_bool_optional (parser, "upsert", &upsert);
      if (!bson_parser_parse (parser, &model_bson, error)) {
         goto done;
      }

      mongoc_bulkwrite_updatemanyopts_t *opts = mongoc_bulkwrite_updatemanyopts_new ();
      mongoc_bulkwrite_updatemanyopts_set_arrayfilters (opts, arrayFilters);
      mongoc_bulkwrite_updatemanyopts_set_collation (opts, collation);
      if (hint) {
         mongoc_bulkwrite_updatemanyopts_set_hint (opts, bson_val_to_value (hint));
      }
      if (upsert) {
         mongoc_bulkwrite_updatemanyopts_set_upsert (opts, *upsert);
      }

      if (!mongoc_bulkwrite_append_updatemany (bw, namespace, filter, update, opts, error)) {
         mongoc_bulkwrite_updatemanyopts_destroy (opts);
         goto done;
      }
      mongoc_bulkwrite_updatemanyopts_destroy (opts);
   } else if (0 == strcmp ("deleteOne", model_name)) {
      // Parse a "deleteOne".
      bson_parser_utf8 (parser, "namespace", &namespace);
      bson_parser_doc (parser, "filter", &filter);
      bson_parser_doc_optional (parser, "collation", &collation);
      bson_parser_any_optional (parser, "hint", &hint);
      if (!bson_parser_parse (parser, &model_bson, error)) {
         goto done;
      }

      mongoc_bulkwrite_deleteoneopts_t *opts = mongoc_bulkwrite_deleteoneopts_new ();
      mongoc_bulkwrite_deleteoneopts_set_collation (opts, collation);
      if (hint) {
         mongoc_bulkwrite_deleteoneopts_set_hint (opts, bson_val_to_value (hint));
      }

      if (!mongoc_bulkwrite_append_deleteone (bw, namespace, filter, opts, error)) {
         mongoc_bulkwrite_deleteoneopts_destroy (opts);
         goto done;
      }
      mongoc_bulkwrite_deleteoneopts_destroy (opts);
   } else if (0 == strcmp ("deleteMany", model_name)) {
      // Parse a "deleteMany".
      bson_parser_utf8 (parser, "namespace", &namespace);
      bson_parser_doc (parser, "filter", &filter);
      bson_parser_doc_optional (parser, "collation", &collation);
      bson_parser_any_optional (parser, "hint", &hint);
      if (!bson_parser_parse (parser, &model_bson, error)) {
         goto done;
      }

      mongoc_bulkwrite_deletemanyopts_t *opts = mongoc_bulkwrite_deletemanyopts_new ();
      mongoc_bulkwrite_deletemanyopts_set_collation (opts, collation);
      if (hint) {
         mongoc_bulkwrite_deletemanyopts_set_hint (opts, bson_val_to_value (hint));
      }

      if (!mongoc_bulkwrite_append_deletemany (bw, namespace, filter, opts, error)) {
         mongoc_bulkwrite_deletemanyopts_destroy (opts);
         goto done;
      }
      mongoc_bulkwrite_deletemanyopts_destroy (opts);
   } else if (0 == strcmp ("replaceOne", model_name)) {
      // Parse a "replaceOne".
      bson_parser_utf8 (parser, "namespace", &namespace);
      bson_parser_doc (parser, "filter", &filter);
      bson_parser_doc (parser, "replacement", &replacement);
      bson_parser_doc_optional (parser, "collation", &collation);
      bson_parser_bool_optional (parser, "upsert", &upsert);
      bson_parser_any_optional (parser, "hint", &hint);
      bson_parser_doc_optional (parser, "sort", &sort);
      if (!bson_parser_parse (parser, &model_bson, error)) {
         goto done;
      }

      mongoc_bulkwrite_replaceoneopts_t *opts = mongoc_bulkwrite_replaceoneopts_new ();
      mongoc_bulkwrite_replaceoneopts_set_collation (opts, collation);
      if (hint) {
         mongoc_bulkwrite_replaceoneopts_set_hint (opts, bson_val_to_value (hint));
      }
      if (upsert) {
         mongoc_bulkwrite_replaceoneopts_set_upsert (opts, *upsert);
      }
      if (sort) {
         mongoc_bulkwrite_replaceoneopts_set_sort (opts, sort);
      }

      if (!mongoc_bulkwrite_append_replaceone (bw, namespace, filter, replacement, opts, error)) {
         mongoc_bulkwrite_replaceoneopts_destroy (opts);
         goto done;
      }
      mongoc_bulkwrite_replaceoneopts_destroy (opts);
   } else {
      test_set_error (error, "unsupported model: %s", model_name);
      goto done;
   }

   ok = true;
done:
   bson_parser_destroy_with_parsed_fields (parser);
   return ok;
}

static bool
operation_client_bulkwrite (test_t *test, operation_t *op, result_t *result, bson_error_t *error)
{
   bool ret = false;
   mongoc_client_t *client = NULL;
   mongoc_bulkwrite_t *bw = NULL;
   mongoc_bulkwriteopts_t *opts = mongoc_bulkwriteopts_new ();

   client = entity_map_get_client (test->entity_map, op->object, error);
   if (!client) {
      goto done;
   }

   // Parse arguments.
   {
      bool parse_ok = false;
      bson_t *args_models = NULL;
      bool *args_verboseResults = NULL;
      bool *args_ordered = NULL;
      bson_val_t *args_comment = NULL;
      bool *args_bypassDocumentValidation = NULL;
      bson_t *args_let = NULL;
      mongoc_write_concern_t *args_wc = NULL;
      bson_parser_t *parser = bson_parser_new ();

      bson_parser_array (parser, "models", &args_models);
      bson_parser_bool_optional (parser, "verboseResults", &args_verboseResults);
      bson_parser_bool_optional (parser, "ordered", &args_ordered);
      bson_parser_any_optional (parser, "comment", &args_comment);
      bson_parser_bool_optional (parser, "bypassDocumentValidation", &args_bypassDocumentValidation);
      bson_parser_doc_optional (parser, "let", &args_let);
      bson_parser_write_concern_optional (parser, &args_wc);
      if (!bson_parser_parse (parser, op->arguments, error)) {
         goto parse_done;
      }
      if (args_verboseResults && *args_verboseResults) {
         mongoc_bulkwriteopts_set_verboseresults (opts, true);
      }
      if (args_ordered) {
         mongoc_bulkwriteopts_set_ordered (opts, *args_ordered);
      }
      if (args_comment) {
         mongoc_bulkwriteopts_set_comment (opts, bson_val_to_value (args_comment));
      }
      if (args_bypassDocumentValidation) {
         mongoc_bulkwriteopts_set_bypassdocumentvalidation (opts, *args_bypassDocumentValidation);
      }
      if (args_let) {
         mongoc_bulkwriteopts_set_let (opts, args_let);
      }
      if (args_wc) {
         mongoc_bulkwriteopts_set_writeconcern (opts, args_wc);
      }

      // Parse models.
      bson_iter_t args_models_iter;
      BSON_ASSERT (bson_iter_init (&args_models_iter, args_models));
      bw = mongoc_client_bulkwrite_new (client);
      while (bson_iter_next (&args_models_iter)) {
         bson_t model_wrapper;
         bson_iter_bson (&args_models_iter, &model_wrapper);
         if (!append_client_bulkwritemodel (bw, &model_wrapper, error)) {
            if (error->domain != TEST_ERROR_DOMAIN) {
               // Propagate error as a test result.
               result_from_val_and_reply (result, NULL, NULL, error);
               // Return with a success (to not abort test runner) and propagate
               // the error as a result.
               ret = true;
               *error = (bson_error_t){0};
               bson_parser_destroy_with_parsed_fields (parser);
               goto done;
            }
            goto parse_done;
         }
      }

      parse_ok = true;
   parse_done:
      bson_parser_destroy_with_parsed_fields (parser);
      if (!parse_ok) {
         goto done;
      }
   }

   // Do client bulk write.
   mongoc_bulkwrite_set_session (bw, op->session);
   mongoc_bulkwritereturn_t bwr = mongoc_bulkwrite_execute (bw, opts);

   result_from_bulkwritereturn (result, bwr);
   mongoc_bulkwriteexception_destroy (bwr.exc);
   mongoc_bulkwriteresult_destroy (bwr.res);
   ret = true;
done:
   mongoc_bulkwriteopts_destroy (opts);
   mongoc_bulkwrite_destroy (bw);
   return ret;
}

static bool
operation_create_datakey (test_t *test, operation_t *op, result_t *result, bson_error_t *error)
{
   bson_parser_t *parser = bson_parser_new ();
   char *kms_provider = NULL;
   bson_t *opts;
   mongoc_client_encryption_t *ce = NULL;
   mongoc_client_encryption_datakey_opts_t *datakey_opts = NULL;
   bson_value_t key_id_value = {0};
   bool ret = false;

   bson_parser_utf8 (parser, "kmsProvider", &kms_provider);
   bson_parser_doc_optional (parser, "opts", &opts);

   if (!bson_parser_parse (parser, op->arguments, error)) {
      goto done;
   }

   if (!(ce = entity_map_get_client_encryption (test->entity_map, op->object, error))) {
      goto done;
   }

   datakey_opts = mongoc_client_encryption_datakey_opts_new ();

   if (opts) {
      bson_parser_t *opts_parser = bson_parser_new ();
      bson_t *master_key = NULL;
      bson_t *key_alt_names = NULL;
      bson_val_t *key_material_val = NULL;
      bool success = false;

      bson_parser_doc_optional (opts_parser, "masterKey", &master_key);
      bson_parser_array_optional (opts_parser, "keyAltNames", &key_alt_names);
      bson_parser_any_optional (opts_parser, "keyMaterial", &key_material_val);

      if (!bson_parser_parse (opts_parser, opts, error)) {
         goto opts_done;
      }

      if (master_key) {
         mongoc_client_encryption_datakey_opts_set_masterkey (datakey_opts, master_key);
      }

      if (key_alt_names) {
         bson_iter_t iter;
         mongoc_array_t arr;

         _mongoc_array_init (&arr, sizeof (char *));

         BSON_FOREACH (key_alt_names, iter)
         {
            const char *key_alt_name = bson_iter_utf8 (&iter, NULL);

            _mongoc_array_append_val (&arr, key_alt_name);
         }

         BSON_ASSERT (mcommon_in_range_unsigned (uint32_t, arr.len));

         mongoc_client_encryption_datakey_opts_set_keyaltnames (datakey_opts, arr.data, (uint32_t) arr.len);

         _mongoc_array_destroy (&arr);
      }

      if (key_material_val) {
         const bson_value_t *value = bson_val_to_value (key_material_val);

         BSON_ASSERT (value);

         if (value->value_type != BSON_TYPE_BINARY || value->value.v_binary.subtype != BSON_SUBTYPE_BINARY) {
            test_set_error (error, "expected field 'keyMaterial' to be binData with subtype 00");
            goto opts_done;
         }

         mongoc_client_encryption_datakey_opts_set_keymaterial (
            datakey_opts, value->value.v_binary.data, value->value.v_binary.data_len);
      }

      success = true;

   opts_done:
      bson_parser_destroy_with_parsed_fields (opts_parser);

      if (!success) {
         goto done;
      }
   }

   {
      const bool success =
         mongoc_client_encryption_create_datakey (ce, kms_provider, datakey_opts, &key_id_value, error);
      bson_val_t *val = NULL;

      if (success) {
         val = bson_val_from_value (&key_id_value);
      }

      result_from_val_and_reply (result, val, NULL, error);

      bson_val_destroy (val);
   }

   ret = true;

done:
   mongoc_client_encryption_datakey_opts_destroy (datakey_opts);
   bson_parser_destroy_with_parsed_fields (parser);
   bson_value_destroy (&key_id_value);

   return ret;
}

static bool
operation_rewrap_many_datakey (test_t *test, operation_t *op, result_t *result, bson_error_t *error)
{
   bson_parser_t *const parser = bson_parser_new ();
   mongoc_client_encryption_rewrap_many_datakey_result_t *const rmd_result =
      mongoc_client_encryption_rewrap_many_datakey_result_new ();

   bool ret = false;
   mongoc_client_encryption_t *ce = NULL;
   bson_t *filter_doc = NULL;
   bson_t *opts_doc = NULL;
   char *provider = NULL;
   bson_t *master_key = NULL;

   bson_parser_doc (parser, "filter", &filter_doc);
   bson_parser_doc_optional (parser, "opts", &opts_doc);

   if (!bson_parser_parse (parser, op->arguments, error)) {
      goto done;
   }

   if (!(ce = entity_map_get_client_encryption (test->entity_map, op->object, error))) {
      goto done;
   }

   if (opts_doc) {
      bson_parser_t *const opts_parser = bson_parser_new ();
      bool success = false;

      bson_parser_utf8 (opts_parser, "provider", &provider);
      bson_parser_doc_optional (opts_parser, "masterKey", &master_key);

      success = bson_parser_parse (opts_parser, opts_doc, error);

      bson_parser_destroy (opts_parser);

      if (!success) {
         goto done;
      }
   }

   if (mongoc_client_encryption_rewrap_many_datakey (ce, filter_doc, provider, master_key, rmd_result, error)) {
      const bson_t *const bulk_write_result =
         mongoc_client_encryption_rewrap_many_datakey_result_get_bulk_write_result (rmd_result);

      bson_t doc = BSON_INITIALIZER;

      if (bulk_write_result) {
         bson_t *const rewritten = rewrite_bulk_write_result (bulk_write_result);
         BSON_APPEND_DOCUMENT (&doc, "bulkWriteResult", rewritten);
         bson_destroy (rewritten);
      }

      {
         bson_val_t *const val = bson_val_from_bson (&doc);
         result_from_val_and_reply (result, val, NULL, error);
         bson_val_destroy (val);
      }

      bson_destroy (&doc);
   } else {
      result_from_val_and_reply (result, NULL, NULL, error);
   }

   ret = true;

done:
   bson_free (provider);
   bson_destroy (master_key);
   mongoc_client_encryption_rewrap_many_datakey_result_destroy (rmd_result);
   bson_parser_destroy_with_parsed_fields (parser);

   return ret;
}

static bool
operation_delete_key (test_t *test, operation_t *op, result_t *result, bson_error_t *error)
{
   bson_parser_t *const parser = bson_parser_new ();

   bool ret = false;
   bson_val_t *id_val = NULL;
   mongoc_client_encryption_t *ce = NULL;

   bson_parser_any (parser, "id", &id_val);

   if (!bson_parser_parse (parser, op->arguments, error)) {
      goto done;
   }

   if (!(ce = entity_map_get_client_encryption (test->entity_map, op->object, error))) {
      goto done;
   }

   {
      bson_t reply;
      const bool success = mongoc_client_encryption_delete_key (ce, bson_val_to_value (id_val), &reply, error);
      bson_val_t *const val = success ? bson_val_from_bson (&reply) : NULL;

      result_from_val_and_reply (result, val, NULL, error);

      bson_destroy (&reply);
      bson_val_destroy (val);
   }

   ret = true;

done:
   bson_parser_destroy_with_parsed_fields (parser);

   return ret;
}

static bool
operation_get_key (test_t *test, operation_t *op, result_t *result, bson_error_t *error)
{
   bson_parser_t *const parser = bson_parser_new ();

   bool ret = false;
   bson_val_t *id_val = NULL;
   mongoc_client_encryption_t *ce = NULL;

   bson_parser_any (parser, "id", &id_val);

   if (!bson_parser_parse (parser, op->arguments, error)) {
      goto done;
   }

   if (!(ce = entity_map_get_client_encryption (test->entity_map, op->object, error))) {
      goto done;
   }

   {
      bson_t key_doc;
      const bool success = mongoc_client_encryption_get_key (ce, bson_val_to_value (id_val), &key_doc, error);
      const bson_value_t value = {.value_type = BSON_TYPE_NULL};
      bson_val_t *const val =
         success ? (bson_empty (&key_doc) ? bson_val_from_value (&value) : bson_val_from_bson (&key_doc)) : NULL;

      result_from_val_and_reply (result, val, NULL, error);

      bson_val_destroy (val);
      bson_destroy (&key_doc);
   }

   ret = true;

done:
   bson_parser_destroy_with_parsed_fields (parser);

   return ret;
}

static bool
operation_get_keys (test_t *test, operation_t *op, result_t *result, bson_error_t *error)
{
   bson_parser_t *const parser = bson_parser_new ();

   bool ret = false;
   mongoc_client_encryption_t *ce = NULL;

   if (!bson_parser_parse (parser, op->arguments, error)) {
      goto done;
   }

   if (!(ce = entity_map_get_client_encryption (test->entity_map, op->object, error))) {
      goto done;
   }

   {
      mongoc_cursor_t *const cursor = mongoc_client_encryption_get_keys (ce, error);

      if (cursor) {
         result_from_cursor (result, cursor);
      } else {
         result_from_val_and_reply (result, NULL, NULL, error);
      }

      mongoc_cursor_destroy (cursor);
   }

   ret = true;

done:
   bson_parser_destroy_with_parsed_fields (parser);

   return ret;
}

static bool
operation_add_key_alt_name (test_t *test, operation_t *op, result_t *result, bson_error_t *error)
{
   bson_parser_t *const parser = bson_parser_new ();

   bool ret = false;
   bson_val_t *id_val = NULL;
   char *alt_name = NULL;
   mongoc_client_encryption_t *ce = NULL;

   bson_parser_any (parser, "id", &id_val);
   bson_parser_utf8 (parser, "keyAltName", &alt_name);

   if (!bson_parser_parse (parser, op->arguments, error)) {
      goto done;
   }

   if (!(ce = entity_map_get_client_encryption (test->entity_map, op->object, error))) {
      goto done;
   }

   {
      bson_t key_doc;
      const bool success =
         mongoc_client_encryption_add_key_alt_name (ce, bson_val_to_value (id_val), alt_name, &key_doc, error);
      const bson_value_t value = {.value_type = BSON_TYPE_NULL};
      bson_val_t *const val =
         success ? (bson_empty (&key_doc) ? bson_val_from_value (&value) : bson_val_from_bson (&key_doc)) : NULL;

      result_from_val_and_reply (result, val, NULL, error);

      bson_destroy (&key_doc);
      bson_val_destroy (val);
   }

   ret = true;

done:
   bson_parser_destroy_with_parsed_fields (parser);

   return ret;
}

static bool
operation_remove_key_alt_name (test_t *test, operation_t *op, result_t *result, bson_error_t *error)
{
   bson_parser_t *const parser = bson_parser_new ();

   bool ret = false;
   bson_val_t *id_val = NULL;
   char *alt_name = NULL;
   mongoc_client_encryption_t *ce = NULL;

   bson_parser_any (parser, "id", &id_val);
   bson_parser_utf8 (parser, "keyAltName", &alt_name);

   if (!bson_parser_parse (parser, op->arguments, error)) {
      goto done;
   }

   if (!(ce = entity_map_get_client_encryption (test->entity_map, op->object, error))) {
      goto done;
   }

   {
      bson_t key_doc;
      const bool success =
         mongoc_client_encryption_remove_key_alt_name (ce, bson_val_to_value (id_val), alt_name, &key_doc, error);
      const bson_value_t value = {.value_type = BSON_TYPE_NULL};
      bson_val_t *const val =
         success ? (bson_empty (&key_doc) ? bson_val_from_value (&value) : bson_val_from_bson (&key_doc)) : NULL;

      result_from_val_and_reply (result, val, NULL, error);

      bson_destroy (&key_doc);
      bson_val_destroy (val);
   }

   ret = true;

done:
   bson_parser_destroy_with_parsed_fields (parser);

   return ret;
}

static bool
operation_get_key_by_alt_name (test_t *test, operation_t *op, result_t *result, bson_error_t *error)
{
   bson_parser_t *const parser = bson_parser_new ();

   bool ret = false;
   char *keyaltname = NULL;
   mongoc_client_encryption_t *ce = NULL;

   bson_parser_utf8 (parser, "keyAltName", &keyaltname);

   if (!bson_parser_parse (parser, op->arguments, error)) {
      goto done;
   }

   if (!(ce = entity_map_get_client_encryption (test->entity_map, op->object, error))) {
      goto done;
   }

   {
      bson_t key_doc;
      const bool success = mongoc_client_encryption_get_key_by_alt_name (ce, keyaltname, &key_doc, error);
      const bson_value_t value = {.value_type = BSON_TYPE_NULL};
      bson_val_t *const val =
         success ? (bson_empty (&key_doc) ? bson_val_from_value (&value) : bson_val_from_bson (&key_doc)) : NULL;

      result_from_val_and_reply (result, val, NULL, error);

      bson_destroy (&key_doc);
      bson_val_destroy (val);
   }

   ret = true;

done:
   bson_parser_destroy_with_parsed_fields (parser);

   return ret;
}

static bool
operation_encrypt (test_t *test, operation_t *op, result_t *result, bson_error_t *error)
{
   bool ret = false;
   mongoc_client_encryption_t *ce = NULL;
   mongoc_client_encryption_encrypt_opts_t *eo = mongoc_client_encryption_encrypt_opts_new ();
   bson_val_t *value = NULL;
   bson_t *opts = NULL;
   char *opts_keyaltname = NULL;
   bson_val_t *opts_id = NULL;
   char *opts_algorithm = NULL;

   // Parse `value` and `opts`.
   {
      bson_parser_t *const parser = bson_parser_new ();
      bool success = false;

      bson_parser_any (parser, "value", &value);
      bson_parser_doc (parser, "opts", &opts);

      success = bson_parser_parse (parser, op->arguments, error);

      bson_parser_destroy (parser);

      if (!success) {
         goto done;
      }
   }

   // Parse fields in `opts`.
   {
      bson_parser_t *const parser = bson_parser_new ();
      bool success = false;

      bson_parser_utf8_optional (parser, "keyAltName", &opts_keyaltname);
      bson_parser_any_optional (parser, "id", &opts_id);
      bson_parser_utf8 (parser, "algorithm", &opts_algorithm);

      success = bson_parser_parse (parser, opts, error);

      bson_parser_destroy (parser);

      if (!success) {
         goto done;
      }
   }

   // Get ClientEncryption object.
   if (!(ce = entity_map_get_client_encryption (test->entity_map, op->object, error))) {
      goto done;
   }

   // Encrypt.
   {
      if (opts_id) {
         mongoc_client_encryption_encrypt_opts_set_keyid (eo, bson_val_to_value (opts_id));
      }
      if (opts_keyaltname) {
         mongoc_client_encryption_encrypt_opts_set_keyaltname (eo, opts_keyaltname);
      }
      mongoc_client_encryption_encrypt_opts_set_algorithm (eo, opts_algorithm);

      bson_value_t ciphertext;
      const bool success = mongoc_client_encryption_encrypt (ce, bson_val_to_value (value), eo, &ciphertext, error);
      bson_val_t *const val = success ? bson_val_from_value (&ciphertext) : NULL;
      result_from_val_and_reply (result, val, NULL /* reply */, error);
      bson_value_destroy (&ciphertext);
      bson_val_destroy (val);
   }

   ret = true;

done:

   bson_free (opts_algorithm);
   bson_val_destroy (opts_id);
   bson_free (opts_keyaltname);
   bson_destroy (opts);
   bson_val_destroy (value);
   mongoc_client_encryption_encrypt_opts_destroy (eo);

   return ret;
}

static bool
operation_decrypt (test_t *test, operation_t *op, result_t *result, bson_error_t *error)
{
   bool ret = false;
   mongoc_client_encryption_t *ce = NULL;
   bson_val_t *value = NULL;

   // Parse `value`.
   {
      bson_parser_t *const parser = bson_parser_new ();
      bool success = false;

      bson_parser_any (parser, "value", &value);

      success = bson_parser_parse (parser, op->arguments, error);

      bson_parser_destroy (parser);

      if (!success) {
         goto done;
      }
   }

   // Get ClientEncryption object.
   if (!(ce = entity_map_get_client_encryption (test->entity_map, op->object, error))) {
      goto done;
   }

   // Decrypt.
   {
      bson_value_t plaintext;
      const bool success = mongoc_client_encryption_decrypt (ce, bson_val_to_value (value), &plaintext, error);
      bson_val_t *const val = success ? bson_val_from_value (&plaintext) : NULL;
      result_from_val_and_reply (result, val, NULL /* reply */, error);
      bson_value_destroy (&plaintext);
      bson_val_destroy (val);
   }

   ret = true;

done:

   bson_val_destroy (value);

   return ret;
}

static bool
operation_create_collection (test_t *test, operation_t *op, result_t *result, bson_error_t *error)
{
   bool ret = false;
   bson_parser_t *parser = NULL;
   mongoc_database_t *db = NULL;
   char *collection = NULL;
   bson_error_t op_error = {0};
   bson_t *opts = NULL;
   mongoc_collection_t *coll = NULL;

   parser = bson_parser_new ();
   bson_parser_allow_extra (parser, true);
   bson_parser_utf8 (parser, "collection", &collection);
   if (!bson_parser_parse (parser, op->arguments, error)) {
      goto done;
   }

   db = entity_map_get_database (test->entity_map, op->object, error);
   if (!db) {
      goto done;
   }

   opts = bson_copy (bson_parser_get_extra (parser));
   if (op->session) {
      if (!mongoc_client_session_append (op->session, opts, error)) {
         goto done;
      }
   }

   coll = mongoc_database_create_collection (db, collection, opts, &op_error);

   result_from_val_and_reply (result, NULL, NULL, &op_error);

   ret = true;
done:
   bson_parser_destroy_with_parsed_fields (parser);
   bson_destroy (opts);
   mongoc_collection_destroy (coll);
   return ret;
}

static bool
operation_drop_collection (test_t *test, operation_t *op, result_t *result, bson_error_t *error)
{
   bool ret = false;
   bson_parser_t *parser = NULL;
   mongoc_database_t *db = NULL;
   mongoc_collection_t *coll = NULL;
   char *collection = NULL;
   bson_error_t op_error = {0};
   bson_t *opts = NULL;

   parser = bson_parser_new ();
   bson_parser_allow_extra (parser, true);
   bson_parser_utf8 (parser, "collection", &collection);
   if (!bson_parser_parse (parser, op->arguments, error)) {
      goto done;
   }

   opts = bson_new ();
   if (op->session) {
      if (!mongoc_client_session_append (op->session, opts, error)) {
         goto done;
      }
   }

   db = entity_map_get_database (test->entity_map, op->object, error);
   if (!db) {
      goto done;
   }

   coll = mongoc_database_get_collection (db, collection);
   mongoc_collection_drop_with_opts (coll, opts, &op_error);

   /* Ignore "ns not found" errors. This assumes that the client under test is
    * using MONGOC_ERROR_API_VERSION_2. */
   if (op_error.domain == MONGOC_ERROR_SERVER && op_error.code == 26) {
      memset (&op_error, 0, sizeof (bson_error_t));
   }

   result_from_val_and_reply (result, NULL, NULL, &op_error);

   ret = true;
done:
   bson_parser_destroy_with_parsed_fields (parser);
   mongoc_collection_destroy (coll);
   bson_destroy (opts);
   return ret;
}

static bool
operation_list_collections (test_t *test, operation_t *op, result_t *result, bson_error_t *error)
{
   bool ret = false;
   mongoc_database_t *db = NULL;
   mongoc_cursor_t *cursor = NULL;
   bson_t *opts = NULL;

   opts = bson_new ();
   if (op->session) {
      if (!mongoc_client_session_append (op->session, opts, error)) {
         goto done;
      }
   }
   if (op->arguments) {
      bson_concat (opts, op->arguments);
   }

   db = entity_map_get_database (test->entity_map, op->object, error);
   if (!db) {
      goto done;
   }

   cursor = mongoc_database_find_collections_with_opts (db, opts);

   result_from_cursor (result, cursor);

   ret = true;
done:
   mongoc_cursor_destroy (cursor);
   bson_destroy (opts);
   return ret;
}

static bool
operation_list_collection_names (test_t *test, operation_t *op, result_t *result, bson_error_t *error)
{
   bool ret = false;
   mongoc_database_t *db = NULL;
   mongoc_cursor_t *cursor = NULL;
   char **op_ret = NULL;
   bson_error_t op_error = {0};
   bson_t *opts = NULL;

   opts = bson_new ();
   if (op->session) {
      if (!mongoc_client_session_append (op->session, opts, error)) {
         goto done;
      }
   }
   bson_concat (opts, op->arguments);

   db = entity_map_get_database (test->entity_map, op->object, error);
   if (!db) {
      goto done;
   }

   op_ret = mongoc_database_get_collection_names_with_opts (db, opts, &op_error);

   result_from_ok (result);

   ret = true;
done:
   mongoc_cursor_destroy (cursor);
   bson_strfreev (op_ret);
   bson_destroy (opts);
   return ret;
}

static bool
operation_list_indexes (test_t *test, operation_t *op, result_t *result, bson_error_t *error)
{
   bool ret = false;
   mongoc_collection_t *coll = NULL;
   mongoc_cursor_t *cursor = NULL;
   bson_t *opts = NULL;

   opts = bson_new ();
   if (op->session) {
      if (!mongoc_client_session_append (op->session, opts, error)) {
         goto done;
      }
   }

   if (op->arguments) {
      bson_concat (opts, op->arguments);
   }

   coll = entity_map_get_collection (test->entity_map, op->object, error);
   if (!coll) {
      goto done;
   }

   cursor = mongoc_collection_find_indexes_with_opts (coll, opts);

   result_from_cursor (result, cursor);

   ret = true;
done:
   mongoc_cursor_destroy (cursor);
   bson_destroy (opts);
   return ret;
}

static bool
operation_run_command (test_t *test, operation_t *op, result_t *result, bson_error_t *error)
{
   bool ret = false;
   bson_parser_t *parser = NULL;
   bson_t *command = NULL;
   char *command_name = NULL;
   mongoc_database_t *db = NULL;
   mongoc_read_prefs_t *rp = NULL;
   mongoc_write_concern_t *wc = NULL;
   mongoc_read_concern_t *rc = NULL;
   bson_error_t op_error = {0};
   bson_t op_reply = BSON_INITIALIZER;
   bson_t *opts = NULL;

   parser = bson_parser_new ();
   bson_parser_doc (parser, "command", &command);
   bson_parser_utf8 (parser, "commandName", &command_name);
   bson_parser_read_concern_optional (parser, &rc);
   bson_parser_write_concern_optional (parser, &wc);
   bson_parser_read_prefs_optional (parser, &rp);
   if (!bson_parser_parse (parser, op->arguments, error)) {
      goto done;
   }

   opts = bson_new ();
   if (op->session) {
      if (!mongoc_client_session_append (op->session, opts, error)) {
         goto done;
      }
   }

   db = entity_map_get_database (test->entity_map, op->object, error);
   if (!db) {
      goto done;
   }

   if (rc) {
      mongoc_read_concern_append (rc, opts);
   }
   if (wc) {
      mongoc_write_concern_append (wc, opts);
   }

   mongoc_database_command_with_opts (db, command, rp, opts, &op_reply, &op_error);

   // For a generic command, the reply is also the result value
   bson_val_t *op_reply_val = bson_val_from_bson (&op_reply);
   result_from_val_and_reply (result, op_reply_val, &op_reply, &op_error);
   bson_val_destroy (op_reply_val);

   ret = true;
done:
   bson_destroy (&op_reply);
   bson_parser_destroy_with_parsed_fields (parser);
   bson_destroy (opts);
   return ret;
}

static bool
operation_modify_collection (test_t *test, operation_t *op, result_t *result, bson_error_t *error)
{
   bson_parser_t *const parser = bson_parser_new ();

   char *coll_name = NULL;
   mongoc_database_t *db = NULL;
   bson_t command = BSON_INITIALIZER;
   bool ret = false;

   bson_parser_utf8 (parser, "collection", &coll_name);
   bson_parser_allow_extra (parser, true);

   if (!bson_parser_parse (parser, op->arguments, error)) {
      goto done;
   }

   if (!(db = entity_map_get_database (test->entity_map, op->object, error))) {
      goto done;
   }

   BSON_ASSERT (BSON_APPEND_UTF8 (&command, "collMod", coll_name));

   /* Forward all arguments other than collection name as-is. */
   BSON_ASSERT (bson_concat (&command, bson_parser_get_extra (parser)));

   {
      bson_t reply;

      mongoc_database_write_command_with_opts (db, &command, NULL, &reply, error);
      result_from_val_and_reply (result, NULL, &reply, error);

      bson_destroy (&reply);
   }

   ret = true;

done:
   bson_parser_destroy_with_parsed_fields (parser);
   bson_destroy (&command);

   return ret;
}

static bool
operation_aggregate (test_t *test, operation_t *op, result_t *result, bson_error_t *error)
{
   bool ret = false;
   entity_t *entity = NULL;
   bson_t *pipeline = NULL;
   bson_parser_t *parser = NULL;
   mongoc_cursor_t *cursor = NULL;
   bson_t *opts = NULL;

   parser = bson_parser_new ();
   bson_parser_allow_extra (parser, true);
   bson_parser_array (parser, "pipeline", &pipeline);
   if (!bson_parser_parse (parser, op->arguments, error)) {
      goto done;
   }

   opts = bson_copy (bson_parser_get_extra (parser));
   if (op->session) {
      if (!mongoc_client_session_append (op->session, opts, error)) {
         goto done;
      }
   }

   entity = entity_map_get (test->entity_map, op->object, error);
   if (0 == strcmp (entity->type, "collection")) {
      mongoc_collection_t *coll = (mongoc_collection_t *) entity->value;
      cursor = mongoc_collection_aggregate (coll, 0 /* query flags */, pipeline, opts, NULL /* read prefs */);
   } else if (0 == strcmp (entity->type, "database")) {
      mongoc_database_t *db = (mongoc_database_t *) entity->value;
      cursor = mongoc_database_aggregate (db, pipeline, opts, NULL /* read prefs */);
   } else {
      goto done;
   }

   result_from_cursor (result, cursor);

   ret = true;
done:
   bson_parser_destroy_with_parsed_fields (parser);
   mongoc_cursor_destroy (cursor);
   bson_destroy (opts);
   return ret;
}

static bool
bulk_op_append (mongoc_bulk_operation_t *bulk, bson_t *request, bson_error_t *error)
{
   bool ret = false;
   bson_iter_t iter;
   const char *op_type;
   bson_parser_t *parser = NULL;
   bson_t *document = NULL, *filter = NULL, *update = NULL, *replacement = NULL;
   bson_t request_doc;

   bson_iter_init (&iter, request);
   bson_iter_next (&iter);
   if (!BSON_ITER_HOLDS_DOCUMENT (&iter)) {
      test_set_error (error, "Unexpected non-document in bulk write model: %s", bson_iter_key (&iter));
   }
   op_type = bson_iter_key (&iter);
   bson_iter_bson (&iter, &request_doc);

   parser = bson_parser_new ();
   /* Pass extra options to operation. Server errors on unrecognized options. */
   bson_parser_allow_extra (parser, true);

   if (0 == strcmp (op_type, "insertOne")) {
      bson_parser_doc (parser, "document", &document);
      if (!bson_parser_parse (parser, &request_doc, error)) {
         goto done;
      }

      mongoc_bulk_operation_insert_with_opts (bulk, document, bson_parser_get_extra (parser), error);
   } else if (0 == strcmp (op_type, "updateOne")) {
      bson_parser_doc (parser, "filter", &filter);
      bson_parser_array_or_doc (parser, "update", &update);
      if (!bson_parser_parse (parser, &request_doc, error)) {
         goto done;
      }

      mongoc_bulk_operation_update_one_with_opts (bulk, filter, update, bson_parser_get_extra (parser), error);
   } else if (0 == strcmp (op_type, "updateMany")) {
      bson_parser_doc (parser, "filter", &filter);
      bson_parser_array_or_doc (parser, "update", &update);
      if (!bson_parser_parse (parser, &request_doc, error)) {
         goto done;
      }

      mongoc_bulk_operation_update_many_with_opts (bulk, filter, update, bson_parser_get_extra (parser), error);
   } else if (0 == strcmp (op_type, "deleteOne")) {
      bson_parser_doc (parser, "filter", &filter);

      if (!bson_parser_parse (parser, &request_doc, error)) {
         goto done;
      }

      mongoc_bulk_operation_remove_one_with_opts (bulk, filter, bson_parser_get_extra (parser), error);
   } else if (0 == strcmp (op_type, "deleteMany")) {
      bson_parser_doc (parser, "filter", &filter);
      if (!bson_parser_parse (parser, &request_doc, error)) {
         goto done;
      }

      mongoc_bulk_operation_remove_many_with_opts (bulk, filter, bson_parser_get_extra (parser), error);
   } else if (0 == strcmp (op_type, "replaceOne")) {
      bson_parser_doc (parser, "filter", &filter);
      bson_parser_doc (parser, "replacement", &replacement);
      if (!bson_parser_parse (parser, &request_doc, error)) {
         goto done;
      }

      mongoc_bulk_operation_replace_one_with_opts (bulk, filter, replacement, bson_parser_get_extra (parser), error);
   }

   ret = true;
done:
   bson_parser_destroy_with_parsed_fields (parser);
   return ret;
}

static bool
operation_bulk_write (test_t *test, operation_t *op, result_t *result, bson_error_t *error)
{
   bool ret = false;
   mongoc_collection_t *coll = NULL;
   bson_parser_t *parser = NULL;
   bool *ordered = NULL;
   bson_t *requests = NULL;
   bson_t *let = NULL;
   bson_val_t *comment = NULL;
   bson_t *opts = NULL;
   bson_iter_t iter;
   mongoc_bulk_operation_t *bulk_op = NULL;
   bson_t op_reply = BSON_INITIALIZER;
   bson_error_t op_error = {0};

   parser = bson_parser_new ();
   bson_parser_array (parser, "requests", &requests);
   bson_parser_bool_optional (parser, "ordered", &ordered);
   bson_parser_doc_optional (parser, "let", &let);
   bson_parser_any_optional (parser, "comment", &comment);

   if (!bson_parser_parse (parser, op->arguments, error)) {
      goto done;
   }

   coll = entity_map_get_collection (test->entity_map, op->object, error);
   if (!coll) {
      goto done;
   }

   opts = bson_new ();
   if (ordered) {
      BSON_APPEND_BOOL (opts, "ordered", *ordered);
   }
   if (!bson_empty0 (let)) {
      BSON_APPEND_DOCUMENT (opts, "let", let);
   }
   if (comment) {
      BSON_APPEND_VALUE (opts, "comment", bson_val_to_value (comment));
   }
   if (op->session) {
      if (!mongoc_client_session_append (op->session, opts, error)) {
         goto done;
      }
   }

   bulk_op = mongoc_collection_create_bulk_operation_with_opts (coll, opts);

   BSON_FOREACH (requests, iter)
   {
      bson_t request;
      bson_iter_bson (&iter, &request);
      if (!bulk_op_append (bulk_op, &request, error)) {
         goto done;
      }
   }

   bson_destroy (&op_reply);
   mongoc_bulk_operation_execute (bulk_op, &op_reply, &op_error);
   result_from_bulk_write (result, &op_reply, &op_error);


   ret = true;
done:
   bson_parser_destroy_with_parsed_fields (parser);
   bson_destroy (&op_reply);
   bson_destroy (opts);
   mongoc_bulk_operation_destroy (bulk_op);
   return ret;
}

static bool
operation_count_documents (test_t *test, operation_t *op, result_t *result, bson_error_t *error)
{
   bool ret = false;
   mongoc_collection_t *coll = NULL;
   bson_parser_t *parser = NULL;
   bson_t *filter = NULL;
   int64_t op_ret = -1;
   bson_t op_reply = BSON_INITIALIZER;
   bson_error_t op_error = {0};
   bson_val_t *val = NULL;
   bson_t *opts = NULL;

   parser = bson_parser_new ();
   bson_parser_allow_extra (parser, true);
   bson_parser_doc (parser, "filter", &filter);
   if (!bson_parser_parse (parser, op->arguments, error)) {
      goto done;
   }

   opts = bson_copy (bson_parser_get_extra (parser));
   if (op->session) {
      if (!mongoc_client_session_append (op->session, opts, error)) {
         goto done;
      }
   }

   coll = entity_map_get_collection (test->entity_map, op->object, error);
   if (!coll) {
      goto done;
   }

   bson_destroy (&op_reply);
   op_ret = mongoc_collection_count_documents (coll, filter, opts, NULL /* read prefs */, &op_reply, &op_error);

   if (op_ret != -1) {
      val = bson_val_from_int64 (op_ret);
   }

   result_from_val_and_reply (result, val, &op_reply, &op_error);

   ret = true;
done:
   bson_parser_destroy_with_parsed_fields (parser);
   bson_destroy (&op_reply);
   bson_val_destroy (val);
   bson_destroy (opts);
   return ret;
}

static bool
operation_create_find_cursor (test_t *test, operation_t *op, result_t *result, bson_error_t *error)
{
   bool ret = false;
   mongoc_collection_t *coll = NULL;
   bson_parser_t *parser = NULL;
   mongoc_cursor_t *cursor = NULL;
   bson_t *filter = NULL;
   bson_t *opts = NULL;
   const bson_t *op_reply = NULL;
   bson_error_t op_error = {0};
   const bson_t *first_result = NULL;

   parser = bson_parser_new ();
   bson_parser_allow_extra (parser, true);
   bson_parser_doc (parser, "filter", &filter);
   if (!bson_parser_parse (parser, op->arguments, error)) {
      goto done;
   }

   coll = entity_map_get_collection (test->entity_map, op->object, error);
   if (!coll) {
      goto done;
   }

   opts = bson_copy (bson_parser_get_extra (parser));
   if (op->session) {
      if (!mongoc_client_session_append (op->session, opts, error)) {
         goto done;
      }
   }

   cursor = mongoc_collection_find_with_opts (coll, filter, opts, NULL /* read prefs */);

   mongoc_cursor_next (cursor, &first_result);

   mongoc_cursor_error_document (cursor, &op_error, &op_reply);
   result_from_val_and_reply (result, NULL, (bson_t *) op_reply, &op_error);

   ret = true;

   if (!op->save_result_as_entity) {
      mongoc_cursor_destroy (cursor);
      goto done;
   }

   if (!entity_map_add_findcursor (test->entity_map, op->save_result_as_entity, cursor, first_result, error)) {
      goto done;
   }

done:
   bson_parser_destroy_with_parsed_fields (parser);
   bson_destroy (opts);
   return ret;
}

static bool
operation_create_index (test_t *test, operation_t *op, result_t *result, bson_error_t *error)
{
   bool ret = false;
   mongoc_collection_t *coll = NULL;
   bson_parser_t *bp = NULL;
   char *name = NULL;
   bson_t *keys = NULL;
   bool *unique = NULL;
   bson_t *create_indexes = bson_new ();
   bson_t op_reply = BSON_INITIALIZER;
   bson_error_t op_error = {0};
   bson_t *opts = bson_new ();
   bson_t *index_opts = bson_new ();
   mongoc_index_model_t *im = NULL;

   coll = entity_map_get_collection (test->entity_map, op->object, error);
   if (!coll) {
      goto done;
   }

   bp = bson_parser_new ();
   bson_parser_doc (bp, "keys", &keys);
   bson_parser_utf8_optional (bp, "name", &name);
   bson_parser_bool_optional (bp, "unique", &unique);

   if (!bson_parser_parse (bp, op->arguments, error)) {
      goto done;
   }

   if (name) {
      BSON_APPEND_UTF8 (index_opts, "name", name);
   }
   if (unique) {
      BSON_APPEND_BOOL (index_opts, "unique", *unique);
   }

   if (op->session) {
      if (!mongoc_client_session_append (op->session, opts, error)) {
         goto done;
      }
   }

   im = mongoc_index_model_new (keys, index_opts);
   mongoc_collection_create_indexes_with_opts (coll, &im, 1, opts, &op_reply, &op_error);

   MONGOC_DEBUG ("running createIndexes: %s", tmp_json (create_indexes));

   printf ("got reply: %s\n", tmp_json (&op_reply));

   result_from_val_and_reply (result, NULL, &op_reply, &op_error);

   ret = true;
done:
   mongoc_index_model_destroy (im);
   bson_destroy (index_opts);
   bson_parser_destroy_with_parsed_fields (bp);
   bson_destroy (&op_reply);
   bson_destroy (opts);
   bson_destroy (create_indexes);

   return ret;
}

static bool
operation_delete_one (test_t *test, operation_t *op, result_t *result, bson_error_t *error)
{
   bool ret = false;
   mongoc_collection_t *coll = NULL;
   bson_parser_t *parser = NULL;
   bson_t *filter = NULL;
   bson_t op_reply = BSON_INITIALIZER;
   bson_error_t op_error = {0};
   bson_t *opts = NULL;

   parser = bson_parser_new ();
   bson_parser_allow_extra (parser, true);
   bson_parser_doc (parser, "filter", &filter);
   if (!bson_parser_parse (parser, op->arguments, error)) {
      goto done;
   }

   opts = bson_copy (bson_parser_get_extra (parser));
   if (op->session) {
      if (!mongoc_client_session_append (op->session, opts, error)) {
         goto done;
      }
   }

   coll = entity_map_get_collection (test->entity_map, op->object, error);
   if (!coll) {
      goto done;
   }

   bson_destroy (&op_reply);
   mongoc_collection_delete_one (coll, filter, opts, &op_reply, &op_error);

   result_from_delete (result, &op_reply, &op_error);

   ret = true;
done:
   bson_destroy (&op_reply);
   bson_parser_destroy_with_parsed_fields (parser);
   bson_destroy (opts);
   return ret;
}

static bool
operation_delete_many (test_t *test, operation_t *op, result_t *result, bson_error_t *error)
{
   bool ret = false;
   mongoc_collection_t *coll = NULL;
   bson_parser_t *parser = NULL;
   bson_t *filter = NULL;
   bson_t op_reply = BSON_INITIALIZER;
   bson_error_t op_error = {0};
   bson_t *opts = NULL;

   parser = bson_parser_new ();
   bson_parser_allow_extra (parser, true);
   bson_parser_doc (parser, "filter", &filter);
   if (!bson_parser_parse (parser, op->arguments, error)) {
      goto done;
   }

   opts = bson_copy (bson_parser_get_extra (parser));
   if (op->session) {
      if (!mongoc_client_session_append (op->session, opts, error)) {
         goto done;
      }
   }

   coll = entity_map_get_collection (test->entity_map, op->object, error);
   if (!coll) {
      goto done;
   }

   bson_destroy (&op_reply);
   mongoc_collection_delete_many (coll, filter, opts, &op_reply, &op_error);

   result_from_delete (result, &op_reply, &op_error);

   ret = true;
done:
   bson_parser_destroy_with_parsed_fields (parser);
   bson_destroy (&op_reply);
   bson_destroy (opts);
   return ret;
}

static bool
operation_distinct (test_t *test, operation_t *op, result_t *result, bson_error_t *error)
{
   bool ret = false;
   mongoc_collection_t *coll = NULL;
   bson_parser_t *parser = NULL;
   bson_t *distinct = NULL;
   char *field_name = NULL;
   bson_t *filter = NULL;
   bson_t op_reply = BSON_INITIALIZER;
   bson_error_t op_error = {0};
   bson_t *opts = NULL;

   parser = bson_parser_new ();
   bson_parser_allow_extra (parser, true);
   bson_parser_utf8 (parser, "fieldName", &field_name);
   bson_parser_doc (parser, "filter", &filter);
   if (!bson_parser_parse (parser, op->arguments, error)) {
      goto done;
   }

   opts = bson_copy (bson_parser_get_extra (parser));
   if (op->session) {
      if (!mongoc_client_session_append (op->session, opts, error)) {
         goto done;
      }
   }

   coll = entity_map_get_collection (test->entity_map, op->object, error);
   if (!coll) {
      goto done;
   }

   distinct = BCON_NEW (
      "distinct", mongoc_collection_get_name (coll), "key", BCON_UTF8 (field_name), "query", "{", &filter, "}");

   bson_destroy (&op_reply);
   mongoc_collection_read_command_with_opts (coll, distinct, NULL /* read prefs */, opts, &op_reply, &op_error);

   result_from_distinct (result, &op_reply, &op_error);

   ret = true;
done:
   bson_parser_destroy_with_parsed_fields (parser);
   bson_destroy (&op_reply);
   bson_destroy (distinct);
   bson_destroy (opts);
   return ret;
}

static bool
operation_estimated_document_count (test_t *test, operation_t *op, result_t *result, bson_error_t *error)
{
   bool ret = false;
   mongoc_collection_t *coll = NULL;
   bson_parser_t *parser = NULL;
   int64_t op_ret;
   bson_t op_reply = BSON_INITIALIZER;
   bson_error_t op_error = {0};
   bson_val_t *val = NULL;
   bson_t *opts = NULL;

   parser = bson_parser_new ();
   bson_parser_allow_extra (parser, true);
   if (!bson_parser_parse (parser, op->arguments, error)) {
      goto done;
   }

   opts = bson_copy (bson_parser_get_extra (parser));
   if (op->session) {
      if (!mongoc_client_session_append (op->session, opts, error)) {
         goto done;
      }
   }

   coll = entity_map_get_collection (test->entity_map, op->object, error);
   if (!coll) {
      goto done;
   }

   bson_destroy (&op_reply);
   op_ret = mongoc_collection_estimated_document_count (coll, opts, NULL /* read prefs */, &op_reply, &op_error);

   if (op_ret != -1) {
      val = bson_val_from_int64 (op_ret);
   }

   result_from_val_and_reply (result, val, &op_reply, &op_error);

   ret = true;
done:
   bson_parser_destroy_with_parsed_fields (parser);
   bson_destroy (&op_reply);
   bson_val_destroy (val);
   bson_destroy (opts);
   return ret;
}

static bool
operation_find (test_t *test, operation_t *op, result_t *result, bson_error_t *error)
{
   bool ret = false;
   mongoc_collection_t *coll = NULL;
   bson_parser_t *parser = NULL;
   mongoc_cursor_t *cursor = NULL;
   bson_t *filter = NULL;
   bson_t *opts = NULL;

   parser = bson_parser_new ();
   bson_parser_allow_extra (parser, true);
   bson_parser_doc (parser, "filter", &filter);
   if (!bson_parser_parse (parser, op->arguments, error)) {
      goto done;
   }

   coll = entity_map_get_collection (test->entity_map, op->object, error);
   if (!coll) {
      goto done;
   }

   opts = bson_copy (bson_parser_get_extra (parser));
   if (op->session) {
      if (!mongoc_client_session_append (op->session, opts, error)) {
         goto done;
      }
   }

   cursor = mongoc_collection_find_with_opts (coll, filter, opts, NULL /* read prefs */);

   result_from_cursor (result, cursor);

   ret = true;
done:
   bson_parser_destroy_with_parsed_fields (parser);
   bson_destroy (opts);
   mongoc_cursor_destroy (cursor);
   return ret;
}

static bool
operation_find_one_and_update (test_t *test, operation_t *op, result_t *result, bson_error_t *error)
{
   bool ret = false;
   mongoc_collection_t *coll = NULL;
   bson_parser_t *parser = NULL;
   bson_t op_reply = BSON_INITIALIZER;
   bson_error_t op_error = {0};
   bson_t *filter = NULL;
   char *return_document = NULL;
   mongoc_find_and_modify_opts_t *opts = NULL;
   mongoc_find_and_modify_flags_t flags = 0;
   bson_val_t *val = NULL;
   bson_iter_t iter;
   bson_t *session_opts = bson_new ();

   parser = bson_parser_new ();
   bson_parser_allow_extra (parser, true);
   bson_parser_doc (parser, "filter", &filter);
   bson_parser_utf8_optional (parser, "returnDocument", &return_document);
   if (!bson_parser_parse (parser, op->arguments, error)) {
      goto done;
   }

   coll = entity_map_get_collection (test->entity_map, op->object, error);
   if (!coll) {
      goto done;
   }

   opts = mongoc_find_and_modify_opts_new ();
   if (return_document && 0 == strcmp (return_document, "After")) {
      flags |= MONGOC_FIND_AND_MODIFY_RETURN_NEW;
   }
   mongoc_find_and_modify_opts_set_flags (opts, flags);
   mongoc_find_and_modify_opts_append (opts, bson_parser_get_extra (parser));
   if (op->session) {
      if (!mongoc_client_session_append (op->session, session_opts, error)) {
         goto done;
      }
   }
   mongoc_find_and_modify_opts_append (opts, session_opts);

   bson_destroy (&op_reply);
   mongoc_collection_find_and_modify_with_opts (coll, filter, opts, &op_reply, &op_error);

   if (bson_iter_init_find (&iter, &op_reply, "value")) {
      val = bson_val_from_iter (&iter);
   }
   result_from_val_and_reply (result, val, &op_reply, &op_error);

   ret = true;
done:
   bson_val_destroy (val);
   bson_parser_destroy_with_parsed_fields (parser);
   mongoc_find_and_modify_opts_destroy (opts);
   bson_destroy (&op_reply);
   bson_destroy (session_opts);
   return ret;
}

static bool
operation_find_one_and_replace (test_t *test, operation_t *op, result_t *result, bson_error_t *error)
{
   bool ret = false;
   mongoc_collection_t *coll = NULL;
   bson_parser_t *parser = NULL;
   bson_t op_reply = BSON_INITIALIZER;
   bson_error_t op_error = {0};
   bson_t *filter = NULL;
   bson_t *replacement = NULL;
   char *return_document = NULL;
   mongoc_find_and_modify_opts_t *opts = NULL;
   mongoc_find_and_modify_flags_t flags = 0;
   bson_val_t *val = NULL;
   bson_iter_t iter;
   bson_t *session_opts = bson_new ();

   parser = bson_parser_new ();
   bson_parser_allow_extra (parser, true);
   bson_parser_doc (parser, "filter", &filter);
   bson_parser_doc (parser, "replacement", &replacement);
   bson_parser_utf8_optional (parser, "returnDocument", &return_document);
   if (!bson_parser_parse (parser, op->arguments, error)) {
      goto done;
   }

   coll = entity_map_get_collection (test->entity_map, op->object, error);
   if (!coll) {
      goto done;
   }

   opts = mongoc_find_and_modify_opts_new ();
   if (return_document && 0 == strcmp (return_document, "After")) {
      flags |= MONGOC_FIND_AND_MODIFY_RETURN_NEW;
   }
   mongoc_find_and_modify_opts_set_flags (opts, flags);
   mongoc_find_and_modify_opts_append (opts, bson_parser_get_extra (parser));
   mongoc_find_and_modify_opts_set_update (opts, replacement);
   if (op->session) {
      if (!mongoc_client_session_append (op->session, session_opts, error)) {
         goto done;
      }
   }
   mongoc_find_and_modify_opts_append (opts, session_opts);

   bson_destroy (&op_reply);
   mongoc_collection_find_and_modify_with_opts (coll, filter, opts, &op_reply, &op_error);

   if (bson_iter_init_find (&iter, &op_reply, "value")) {
      val = bson_val_from_iter (&iter);
   }
   result_from_val_and_reply (result, val, &op_reply, &op_error);

   ret = true;
done:
   bson_val_destroy (val);
   bson_parser_destroy_with_parsed_fields (parser);
   mongoc_find_and_modify_opts_destroy (opts);
   bson_destroy (&op_reply);
   bson_destroy (session_opts);
   return ret;
}

static bool
operation_find_one_and_delete (test_t *test, operation_t *op, result_t *result, bson_error_t *error)
{
   bool ret = false;
   mongoc_collection_t *coll = NULL;
   bson_parser_t *parser = NULL;
   bson_t op_reply = BSON_INITIALIZER;
   bson_error_t op_error = {0};
   bson_t *filter = NULL;
   mongoc_find_and_modify_opts_t *opts = NULL;
   mongoc_find_and_modify_flags_t flags = 0;
   bson_val_t *val = NULL;
   bson_iter_t iter;
   bson_t *session_opts = bson_new ();

   parser = bson_parser_new ();
   bson_parser_allow_extra (parser, true);
   bson_parser_doc (parser, "filter", &filter);
   if (!bson_parser_parse (parser, op->arguments, error)) {
      goto done;
   }

   coll = entity_map_get_collection (test->entity_map, op->object, error);
   if (!coll) {
      goto done;
   }

   opts = mongoc_find_and_modify_opts_new ();
   flags |= MONGOC_FIND_AND_MODIFY_REMOVE;
   mongoc_find_and_modify_opts_set_flags (opts, flags);
   mongoc_find_and_modify_opts_append (opts, bson_parser_get_extra (parser));
   if (op->session) {
      if (!mongoc_client_session_append (op->session, session_opts, error)) {
         goto done;
      }
   }
   mongoc_find_and_modify_opts_append (opts, session_opts);

   bson_destroy (&op_reply);
   mongoc_collection_find_and_modify_with_opts (coll, filter, opts, &op_reply, &op_error);

   if (bson_iter_init_find (&iter, &op_reply, "value")) {
      val = bson_val_from_iter (&iter);
   }
   result_from_val_and_reply (result, val, &op_reply, &op_error);

   ret = true;
done:
   bson_val_destroy (val);
   bson_parser_destroy_with_parsed_fields (parser);
   mongoc_find_and_modify_opts_destroy (opts);
   bson_destroy (&op_reply);
   bson_destroy (session_opts);
   return ret;
}

static bool
operation_insert_many (test_t *test, operation_t *op, result_t *result, bson_error_t *error)
{
   bool ret = false;
   mongoc_collection_t *coll = NULL;
   bson_parser_t *parser = NULL;
   bson_t *documents = NULL;
   bson_t op_reply = BSON_INITIALIZER;
   bson_error_t op_error = {0};
   bson_iter_t iter;
   int n_docs = 0, i = 0;
   bson_t **docs = NULL;
   bson_t *opts = NULL;

   parser = bson_parser_new ();
   bson_parser_allow_extra (parser, true);
   bson_parser_array (parser, "documents", &documents);
   if (!bson_parser_parse (parser, op->arguments, error)) {
      goto done;
   }

   opts = bson_copy (bson_parser_get_extra (parser));
   if (op->session) {
      if (!mongoc_client_session_append (op->session, opts, error)) {
         goto done;
      }
   }

   coll = entity_map_get_collection (test->entity_map, op->object, error);
   if (!coll) {
      goto done;
   }

   BSON_FOREACH (documents, iter)
   {
      n_docs++;
   }
   docs = bson_malloc0 (n_docs * sizeof (bson_t *));

   MONGOC_DEBUG ("ndocs=%d", n_docs);
   BSON_FOREACH (documents, iter)
   {
      bson_t doc;

      bson_iter_bson (&iter, &doc);
      docs[i] = bson_copy (&doc);
      i++;
   }

   bson_destroy (&op_reply);
   mongoc_collection_insert_many (coll, (const bson_t **) docs, n_docs, opts, &op_reply, &op_error);
   result_from_insert_many (result, &op_reply, &op_error);
   ret = true;
done:
   bson_parser_destroy_with_parsed_fields (parser);
   if (docs) {
      for (i = 0; i < n_docs; i++) {
         bson_destroy (docs[i]);
      }
   }
   bson_free (docs);
   bson_destroy (&op_reply);
   bson_destroy (opts);
   return ret;
}

static bool
operation_insert_one (test_t *test, operation_t *op, result_t *result, bson_error_t *error)
{
   bool ret = false;
   mongoc_collection_t *coll = NULL;
   bson_t *document = NULL;
   bson_parser_t *parser = NULL;
   bson_error_t op_error = {0};
   bson_t op_reply = BSON_INITIALIZER;
   bson_t *opts = NULL;

   parser = bson_parser_new ();
   bson_parser_allow_extra (parser, true);
   bson_parser_doc (parser, "document", &document);
   if (!bson_parser_parse (parser, op->arguments, error)) {
      goto done;
   }

   opts = bson_copy (bson_parser_get_extra (parser));
   if (op->session) {
      if (!mongoc_client_session_append (op->session, opts, error)) {
         goto done;
      }
   }

   coll = entity_map_get_collection (test->entity_map, op->object, error);
   if (!coll) {
      goto done;
   }

   bson_destroy (&op_reply);
   mongoc_collection_insert_one (coll, document, opts, &op_reply, &op_error);
   result_from_insert_one (result, &op_reply, &op_error);


   ret = true;
done:
   bson_destroy (&op_reply);
   bson_parser_destroy_with_parsed_fields (parser);
   bson_destroy (opts);
   return ret;
}

static bool
operation_replace_one (test_t *test, operation_t *op, result_t *result, bson_error_t *error)
{
   bool ret = false;
   mongoc_collection_t *coll = NULL;
   bson_parser_t *parser = NULL;
   bson_t *filter = NULL;
   bson_t *replacement = NULL;
   bson_t op_reply = BSON_INITIALIZER;
   bson_error_t op_error = {0};
   bson_t *opts = NULL;

   parser = bson_parser_new ();
   bson_parser_allow_extra (parser, true);
   bson_parser_doc (parser, "filter", &filter);
   bson_parser_doc (parser, "replacement", &replacement);
   if (!bson_parser_parse (parser, op->arguments, error)) {
      goto done;
   }

   opts = bson_copy (bson_parser_get_extra (parser));
   if (op->session) {
      if (!mongoc_client_session_append (op->session, opts, error)) {
         goto done;
      }
   }

   coll = entity_map_get_collection (test->entity_map, op->object, error);
   if (!coll) {
      goto done;
   }

   bson_destroy (&op_reply);
   mongoc_collection_replace_one (coll, filter, replacement, opts, &op_reply, &op_error);

   result_from_update_or_replace (result, &op_reply, &op_error);

   ret = true;
done:
   bson_parser_destroy_with_parsed_fields (parser);
   bson_destroy (&op_reply);
   bson_destroy (opts);
   return ret;
}

static bool
operation_update_one (test_t *test, operation_t *op, result_t *result, bson_error_t *error)
{
   bool ret = false;
   mongoc_collection_t *coll = NULL;
   bson_parser_t *parser = NULL;
   bson_t *filter = NULL;
   bson_t *update = NULL;
   bson_t op_reply = BSON_INITIALIZER;
   bson_error_t op_error = {0};
   bson_t *opts = NULL;

   parser = bson_parser_new ();
   bson_parser_allow_extra (parser, true);
   bson_parser_doc (parser, "filter", &filter);
   bson_parser_array_or_doc (parser, "update", &update);
   if (!bson_parser_parse (parser, op->arguments, error)) {
      goto done;
   }

   opts = bson_copy (bson_parser_get_extra (parser));
   if (op->session) {
      if (!mongoc_client_session_append (op->session, opts, error)) {
         goto done;
      }
   }

   coll = entity_map_get_collection (test->entity_map, op->object, error);
   if (!coll) {
      goto done;
   }

   bson_destroy (&op_reply);
   mongoc_collection_update_one (coll, filter, update, opts, &op_reply, &op_error);

   result_from_update_or_replace (result, &op_reply, &op_error);

   ret = true;
done:
   bson_parser_destroy_with_parsed_fields (parser);
   bson_destroy (&op_reply);
   bson_destroy (opts);
   return ret;
}

static bool
operation_update_many (test_t *test, operation_t *op, result_t *result, bson_error_t *error)
{
   bool ret = false;
   mongoc_collection_t *coll = NULL;
   bson_parser_t *parser = NULL;
   bson_t *filter = NULL;
   bson_t *update = NULL;
   bson_t op_reply = BSON_INITIALIZER;
   bson_error_t op_error = {0};
   bson_t *opts = NULL;

   parser = bson_parser_new ();
   bson_parser_allow_extra (parser, true);
   bson_parser_doc (parser, "filter", &filter);
   bson_parser_array_or_doc (parser, "update", &update);
   if (!bson_parser_parse (parser, op->arguments, error)) {
      goto done;
   }

   opts = bson_copy (bson_parser_get_extra (parser));
   if (op->session) {
      if (!mongoc_client_session_append (op->session, opts, error)) {
         goto done;
      }
   }

   coll = entity_map_get_collection (test->entity_map, op->object, error);
   if (!coll) {
      goto done;
   }

   bson_destroy (&op_reply);
   mongoc_collection_update_many (coll, filter, update, opts, &op_reply, &op_error);

   result_from_update_or_replace (result, &op_reply, &op_error);

   ret = true;
done:
   bson_parser_destroy_with_parsed_fields (parser);
   bson_destroy (&op_reply);
   bson_destroy (opts);
   return ret;
}

static bool
operation_iterate_until_document_or_error (test_t *test, operation_t *op, result_t *result, bson_error_t *error)
{
   bool ret = false;
   mongoc_change_stream_t *changestream = NULL;
   entity_findcursor_t *findcursor = NULL;
   const bson_t *doc = NULL;
   const bson_t *op_reply = NULL;
   bson_error_t op_error = {0};
   bson_val_t *val = NULL;
   entity_t *entity;

   entity = entity_map_get (test->entity_map, op->object, error);
   if (!entity) {
      goto done;
   }

   if (strcmp ("changestream", entity->type) == 0) {
      changestream = entity_map_get_changestream (test->entity_map, op->object, error);
      if (!changestream) {
         goto done;
      }

      /* Loop until error or document is returned. */
      while (!mongoc_change_stream_next (changestream, &doc)) {
         if (mongoc_change_stream_error_document (changestream, &op_error, &op_reply)) {
            break;
         }
      }
   } else {
      findcursor = entity_map_get_findcursor (test->entity_map, op->object, error);
      if (!findcursor) {
         goto done;
      }

      entity_findcursor_iterate_until_document_or_error (findcursor, &doc, &op_error, &op_reply);
   }

   if (NULL != doc) {
      val = bson_val_from_bson ((bson_t *) doc);
   }

   result_from_val_and_reply (result, val, (bson_t *) op_reply, &op_error);

   ret = true;
done:
   bson_val_destroy (val);
   return ret;
}

static bool
operation_close (test_t *test, operation_t *op, result_t *result, bson_error_t *error)
{
   bool ret = false;
   entity_t *entity;
   entity = entity_map_get (test->entity_map, op->object, error);
   if (!entity) {
      goto done;
   }

   if (!entity_map_close (test->entity_map, op->object, error)) {
      goto done;
   }

   result_from_ok (result);

   ret = true;
done:
   return ret;
}

static bool
operation_drop_index (test_t *test, operation_t *op, result_t *result, bson_error_t *error)
{
   bool ret = false;
   entity_t *entity;
   bson_parser_t *parser = NULL;
   char *index = NULL;
   bson_t *opts = NULL;
   mongoc_collection_t *coll = NULL;
   bson_error_t op_error = {0};

   parser = bson_parser_new ();
   bson_parser_utf8 (parser, "name", &index);
   if (!bson_parser_parse (parser, op->arguments, error)) {
      goto done;
   }

   entity = entity_map_get (test->entity_map, op->object, error);
   if (!entity) {
      goto done;
   }

   opts = bson_new ();
   if (op->session) {
      if (!mongoc_client_session_append (op->session, opts, error)) {
         goto done;
      }
   }

   coll = entity_map_get_collection (test->entity_map, op->object, error);
   mongoc_collection_drop_index (coll, index, error);
   result_from_val_and_reply (result, NULL, NULL, &op_error);
   ret = true;

done:
   bson_parser_destroy_with_parsed_fields (parser);
   bson_destroy (opts);
   return ret;
}

static bool
operation_failpoint (test_t *test, operation_t *op, result_t *result, bson_error_t *error)
{
   bool ret = false;
   bson_parser_t *bp = NULL;
   char *client_id = NULL;
   mongoc_client_t *client = NULL;
   bson_t *failpoint = NULL;
   mongoc_read_prefs_t *rp = NULL;
   bson_t op_reply = BSON_INITIALIZER;
   bson_error_t op_error = {0};

   bp = bson_parser_new ();
   bson_parser_utf8 (bp, "client", &client_id);
   bson_parser_doc (bp, "failPoint", &failpoint);

   if (!bson_parser_parse (bp, op->arguments, error)) {
      goto done;
   }

   client = entity_map_get_client (test->entity_map, client_id, error);
   if (!client) {
      goto done;
   }

   rp = mongoc_read_prefs_new (MONGOC_READ_PRIMARY);

   bson_destroy (&op_reply);
   entity_map_log_filter_push (test->entity_map, client_id, NULL, NULL);
   mongoc_client_command_simple (client, "admin", failpoint, rp, &op_reply, &op_error);
   entity_map_log_filter_pop (test->entity_map, client_id, NULL, NULL);
   result_from_val_and_reply (result, NULL /* value */, &op_reply, &op_error);

   /* Add failpoint to list of test_t's known failpoints */
   register_failpoint (test, (char *) bson_lookup_utf8 (failpoint, "configureFailPoint"), client_id, 0);

   ret = true;
done:
   mongoc_read_prefs_destroy (rp);
   bson_parser_destroy_with_parsed_fields (bp);
   bson_destroy (&op_reply);
   return ret;
}

static bool
operation_targeted_failpoint (test_t *test, operation_t *op, result_t *result, bson_error_t *error)
{
   bool ret = false;
   bson_parser_t *bp = NULL;
   char *client_id = NULL;
   mongoc_client_t *client = NULL;
   bson_t *failpoint = NULL;
   mongoc_read_prefs_t *rp = NULL;
   bson_t op_reply = BSON_INITIALIZER;
   bson_error_t op_error = {0};
   uint32_t server_id;

   bp = bson_parser_new ();
   bson_parser_doc (bp, "failPoint", &failpoint);

   if (!bson_parser_parse (bp, op->arguments, error)) {
      goto done;
   }

   if (!op->session) {
      test_set_error (error, "%s", "session unset");
      goto done;
   }

   client_id = entity_map_get_session_client_id (test->entity_map, op->session_id, error);
   if (!client_id) {
      goto done;
   }

   client = entity_map_get_client (test->entity_map, client_id, error);
   if (!client) {
      goto done;
   }

   server_id = mongoc_client_session_get_server_id (op->session);
   if (0 == server_id) {
      test_set_error (error, "expected session %s to be pinned but was not", op->session_id);
      goto done;
   }

   rp = mongoc_read_prefs_new (MONGOC_READ_PRIMARY);

   bson_destroy (&op_reply);
   entity_map_log_filter_push (test->entity_map, client_id, NULL, NULL);
   mongoc_client_command_simple_with_server_id (client, "admin", failpoint, rp, server_id, &op_reply, &op_error);
   entity_map_log_filter_pop (test->entity_map, client_id, NULL, NULL);
   result_from_val_and_reply (result, NULL /* value */, &op_reply, &op_error);

   /* Add failpoint to list of test_t's known failpoints */
   register_failpoint (test, (char *) bson_lookup_utf8 (failpoint, "configureFailPoint"), client_id, server_id);

   ret = true;
done:
   mongoc_read_prefs_destroy (rp);
   bson_parser_destroy_with_parsed_fields (bp);
   bson_destroy (&op_reply);
   return ret;
}

static bool
operation_delete (test_t *test, operation_t *op, result_t *result, bson_error_t *error)
{
   bool ret = false;
   mongoc_gridfs_bucket_t *bucket = NULL;
   bson_parser_t *bp = NULL;
   bson_val_t *id = NULL;
   bson_error_t op_error = {0};

   bp = bson_parser_new ();
   bson_parser_any (bp, "id", &id);
   if (!bson_parser_parse (bp, op->arguments, error)) {
      goto done;
   }

   bucket = entity_map_get_bucket (test->entity_map, op->object, error);
   if (!bucket) {
      goto done;
   }

   mongoc_gridfs_bucket_delete_by_id (bucket, bson_val_to_value (id), &op_error);
   result_from_val_and_reply (result, NULL, NULL, &op_error);

   ret = true;
done:
   bson_parser_destroy_with_parsed_fields (bp);
   return ret;
}

static bool
operation_download (test_t *test, operation_t *op, result_t *result, bson_error_t *error)
{
   bool ret = false;
   mongoc_gridfs_bucket_t *bucket = NULL;
   bson_parser_t *bp = NULL;
   bson_val_t *id = NULL;
   bson_error_t op_error = {0};
   mongoc_stream_t *stream = NULL;
   uint8_t buf[256];
   ssize_t bytes_read = 0;
   mongoc_array_t all_bytes;
   bson_val_t *val = NULL;

   _mongoc_array_init (&all_bytes, sizeof (uint8_t));
   bp = bson_parser_new ();
   bson_parser_any (bp, "id", &id);
   if (!bson_parser_parse (bp, op->arguments, error)) {
      goto done;
   }

   bucket = entity_map_get_bucket (test->entity_map, op->object, error);
   if (!bucket) {
      goto done;
   }

   stream = mongoc_gridfs_bucket_open_download_stream (bucket, bson_val_to_value (id), &op_error);

   if (stream) {
      while ((bytes_read = mongoc_stream_read (stream, buf, sizeof (buf), 1, 0)) > 0) {
         ASSERT (mcommon_in_range_signed (uint32_t, bytes_read));
         _mongoc_array_append_vals (&all_bytes, buf, (uint32_t) bytes_read);
      }
      mongoc_gridfs_bucket_stream_error (stream, &op_error);
   }

   ASSERT (mcommon_in_range_unsigned (uint32_t, all_bytes.len));
   val = bson_val_from_bytes (all_bytes.data, (uint32_t) all_bytes.len);
   result_from_val_and_reply (result, val, NULL, &op_error);

   ret = true;
done:
   bson_parser_destroy_with_parsed_fields (bp);
   _mongoc_array_destroy (&all_bytes);
   bson_val_destroy (val);
   mongoc_stream_destroy (stream);
   return ret;
}

static bool
operation_upload (test_t *test, operation_t *op, result_t *result, bson_error_t *error)
{
   bool ret = false;
   mongoc_gridfs_bucket_t *bucket = NULL;
   bson_parser_t *bp = NULL;
   bson_t *source = NULL;
   char *filename = NULL;
   mongoc_stream_t *stream = NULL;
   bson_value_t file_id;
   bson_error_t op_error = {0};
   bson_val_t *val = NULL;

   bp = bson_parser_new ();
   bson_parser_allow_extra (bp, true);
   bson_parser_doc (bp, "source", &source);
   bson_parser_utf8 (bp, "filename", &filename);
   if (!bson_parser_parse (bp, op->arguments, error)) {
      goto done;
   }

   bucket = entity_map_get_bucket (test->entity_map, op->object, error);
   if (!bucket) {
      goto done;
   }

   stream = mongoc_gridfs_bucket_open_upload_stream (bucket, filename, bson_parser_get_extra (bp), &file_id, &op_error);

   if (stream) {
      size_t total_written = 0u;
      uint8_t *source_bytes;
      uint32_t source_bytes_len;
      bson_iter_t iter;

      if (!bson_iter_init_find (&iter, source, "$$hexBytes") || !BSON_ITER_HOLDS_UTF8 (&iter)) {
         test_set_error (error, "$$hexBytes not found in source data");
         goto done;
      }

      source_bytes = hex_to_bin (bson_iter_utf8 (&iter, NULL), &source_bytes_len);
      while (total_written < source_bytes_len) {
         const ssize_t bytes_written = mongoc_stream_write (stream, source_bytes, source_bytes_len - total_written, 0);
         if (bytes_written < 0) {
            break;
         }
         total_written += (size_t) bytes_written;
      }
      mongoc_gridfs_bucket_stream_error (stream, &op_error);
      bson_free (source_bytes);
   }

   val = bson_val_from_value (&file_id);
   result_from_val_and_reply (result, val, NULL, &op_error);

   ret = true;
done:
   bson_parser_destroy_with_parsed_fields (bp);
   mongoc_stream_destroy (stream);
   bson_val_destroy (val);
   return ret;
}

static bool
assert_session_dirty_helper (test_t *test, operation_t *op, result_t *result, bool check_dirty, bson_error_t *error)
{
   bool ret = false;

   BSON_UNUSED (test);

   if (!op->session) {
      test_set_error (error, "%s", "session unset");
      goto done;
   }

   if (check_dirty != mongoc_client_session_get_dirty (op->session)) {
      test_set_error (error, "expected session to%s be dirty but was not", check_dirty ? "" : " not");
      goto done;
   }

   result_from_ok (result);

   ret = true;
done:
   return ret;
}
static bool
operation_assert_session_not_dirty (test_t *test, operation_t *op, result_t *result, bson_error_t *error)
{
   return assert_session_dirty_helper (test, op, result, false, error);
}

static bool
operation_assert_session_dirty (test_t *test, operation_t *op, result_t *result, bson_error_t *error)
{
   return assert_session_dirty_helper (test, op, result, true, error);
}

static event_t *
next_started_event (event_t *iter)
{
   if (!iter) {
      return NULL;
   }

   iter = iter->next;
   while (iter && 0 != strcmp (iter->type, "commandStartedEvent")) {
      iter = iter->next;
   }
   return iter;
}

static bool
assert_lsid_on_last_two_commands (test_t *test, operation_t *op, result_t *result, bool check_same, bson_error_t *error)
{
   bool ret = false;
   bson_parser_t *bp = NULL;
   char *client_id = NULL;
   entity_t *entity = NULL;
   event_t *a, *b;
   bson_iter_t iter;
   bson_t a_lsid;
   bson_t b_lsid;

   bp = bson_parser_new ();
   bson_parser_utf8 (bp, "client", &client_id);
   if (!bson_parser_parse (bp, op->arguments, error)) {
      goto done;
   }

   entity = entity_map_get (test->entity_map, client_id, error);
   if (!entity) {
      goto done;
   }

   if (0 != strcmp (entity->type, "client")) {
      goto done;
   }

   a = NULL;
   b = entity->events;
   if (b && 0 != strcmp (b->type, "commandStartedEvent")) {
      b = next_started_event (b);
   }

   while (next_started_event (b) != NULL) {
      a = b;
      b = next_started_event (b);
   }

   if (NULL == a || NULL == b) {
      test_set_error (error, "unable to find two commandStartedEvents on client: %s", client_id);
      goto done;
   }

   if (bson_iter_init (&iter, a->serialized) && bson_iter_find_descendant (&iter, "command.lsid", &iter)) {
      bson_iter_bson (&iter, &a_lsid);
   } else {
      test_set_error (error, "unable to find lsid in second to last commandStartedEvent: %s", tmp_json (a->serialized));
      goto done;
   }

   if (bson_iter_init (&iter, b->serialized) && bson_iter_find_descendant (&iter, "command.lsid", &iter)) {
      bson_iter_bson (&iter, &b_lsid);
   } else {
      test_set_error (error, "unable to find lsid in last commandStartedEvent: %s", tmp_json (b->serialized));
      goto done;
   }

   if (check_same != bson_equal (&a_lsid, &b_lsid)) {
      test_set_error (error,
                      "expected $lsid's to be%s equal, but got: %s and %s",
                      check_same ? "" : " not",
                      tmp_json (&a_lsid),
                      tmp_json (&b_lsid));
      goto done;
   }

   result_from_ok (result);

   ret = true;
done:
   bson_parser_destroy_with_parsed_fields (bp);
   return ret;
}

static bool
operation_assert_same_lsid_on_last_two_commands (test_t *test, operation_t *op, result_t *result, bson_error_t *error)
{
   return assert_lsid_on_last_two_commands (test, op, result, true, error);
}

static bool
operation_assert_different_lsid_on_last_two_commands (test_t *test,
                                                      operation_t *op,
                                                      result_t *result,
                                                      bson_error_t *error)
{
   return assert_lsid_on_last_two_commands (test, op, result, false, error);
}

static bool
operation_end_session (test_t *test, operation_t *op, result_t *result, bson_error_t *error)
{
   bool ret = false;

   if (!entity_map_end_session (test->entity_map, op->object, error)) {
      goto done;
   }
   result_from_ok (result);

   ret = true;
done:
   return ret;
}

static bool
operation_start_transaction (test_t *test, operation_t *op, result_t *result, bson_error_t *error)
{
   bool ret = false;
   mongoc_client_session_t *session = NULL;
   bson_error_t op_error = {0};
   mongoc_transaction_opt_t *opts = NULL;
   mongoc_read_concern_t *rc = NULL;
   mongoc_write_concern_t *wc = NULL;
   mongoc_read_prefs_t *rp = NULL;
   bson_parser_t *bp = NULL;

   opts = mongoc_transaction_opts_new ();
   bp = bson_parser_new ();
   bson_parser_read_concern_optional (bp, &rc);
   bson_parser_write_concern_optional (bp, &wc);
   bson_parser_read_prefs_optional (bp, &rp);
   if (!bson_parser_parse (bp, op->arguments, error)) {
      goto done;
   }
   if (rc) {
      mongoc_transaction_opts_set_read_concern (opts, rc);
   }

   if (wc) {
      mongoc_transaction_opts_set_write_concern (opts, wc);
   }

   if (rp) {
      mongoc_transaction_opts_set_read_prefs (opts, rp);
   }

   session = entity_map_get_session (test->entity_map, op->object, error);
   if (!session) {
      goto done;
   }

   mongoc_client_session_start_transaction (session, opts, &op_error);
   result_from_val_and_reply (result, NULL, NULL, &op_error);

   ret = true;
done:
   bson_parser_destroy_with_parsed_fields (bp);
   mongoc_transaction_opts_destroy (opts);
   return ret;
}

const char *
transaction_state_to_string (mongoc_transaction_state_t tstate)
{
   switch (tstate) {
   case MONGOC_TRANSACTION_NONE:
      return "none";
   case MONGOC_TRANSACTION_STARTING:
      return "starting";
   case MONGOC_TRANSACTION_IN_PROGRESS:
      return "in_progress";
   case MONGOC_TRANSACTION_COMMITTED:
      return "committed";
   case MONGOC_TRANSACTION_ABORTED:
      return "aborted";
   default:
      return "invalid";
   }
}

static bool
operation_assert_session_transaction_state (test_t *test, operation_t *op, result_t *result, bson_error_t *error)
{
   bool ret = false;
   bson_parser_t *bp = NULL;
   char *expected = NULL;
   const char *actual;
   mongoc_transaction_state_t state;

   BSON_UNUSED (test);

   bp = bson_parser_new ();
   bson_parser_utf8 (bp, "state", &expected);
   if (!bson_parser_parse (bp, op->arguments, error)) {
      goto done;
   }

   if (!op->session) {
      test_set_error (error, "expected session");
      goto done;
   }

   state = mongoc_client_session_get_transaction_state (op->session);
   actual = transaction_state_to_string (state);
   if (0 != strcmp (expected, actual)) {
      test_set_error (error, "expected state: %s, got state: %s", expected, actual);
      goto done;
   }

   result_from_ok (result);

   ret = true;
done:
   bson_parser_destroy_with_parsed_fields (bp);
   return ret;
}

static bool
assert_collection_exists (test_t *test, operation_t *op, result_t *result, bool expect_exist, bson_error_t *error)
{
   bool ret = false;
   bson_parser_t *bp = NULL;
   char *collection_name = NULL;
   char *database_name = NULL;
   mongoc_database_t *db = NULL;
   bson_error_t op_error = {0};
   bool actual_exist = false;
   char **head = NULL, **iter = NULL;

   bp = bson_parser_new ();
   bson_parser_utf8 (bp, "collectionName", &collection_name);
   bson_parser_utf8 (bp, "databaseName", &database_name);
   if (!bson_parser_parse (bp, op->arguments, error)) {
      goto done;
   }

   db = mongoc_client_get_database (test->test_file->test_runner->internal_client, database_name);
   head = mongoc_database_get_collection_names_with_opts (db, NULL, &op_error);
   for (iter = head; *iter; iter++) {
      if (0 == strcmp (*iter, collection_name)) {
         actual_exist = true;
         break;
      }
   }

   if (expect_exist != actual_exist) {
      test_set_error (error,
                      "expected collection %s %s exist but %s",
                      collection_name,
                      expect_exist ? "to" : "to not",
                      expect_exist ? "did not" : "did");
      goto done;
   }

   result_from_val_and_reply (result, NULL, NULL, &op_error);

   ret = true;
done:
   bson_strfreev (head);
   mongoc_database_destroy (db);
   bson_parser_destroy_with_parsed_fields (bp);
   return ret;
}

static bool
operation_assert_collection_exists (test_t *test, operation_t *op, result_t *result, bson_error_t *error)
{
   return assert_collection_exists (test, op, result, true, error);
}

static bool
operation_assert_collection_not_exists (test_t *test, operation_t *op, result_t *result, bson_error_t *error)
{
   return assert_collection_exists (test, op, result, false, error);
}

static bool
operation_commit_transaction (test_t *test, operation_t *op, result_t *result, bson_error_t *error)
{
   bool ret = false;
   mongoc_client_session_t *session = NULL;
   bson_t op_reply = BSON_INITIALIZER;
   bson_error_t op_error = {0};

   session = entity_map_get_session (test->entity_map, op->object, error);
   if (!session) {
      goto done;
   }

   bson_destroy (&op_reply);
   mongoc_client_session_commit_transaction (session, &op_reply, &op_error);
   result_from_val_and_reply (result, NULL, &op_reply, &op_error);

   ret = true;
done:
   bson_destroy (&op_reply);
   return ret;
}

static bool
operation_abort_transaction (test_t *test, operation_t *op, result_t *result, bson_error_t *error)
{
   bool ret = false;
   mongoc_client_session_t *session = NULL;
   bson_error_t op_error = {0};

   session = entity_map_get_session (test->entity_map, op->object, error);
   if (!session) {
      goto done;
   }

   mongoc_client_session_abort_transaction (session, &op_error);
   result_from_val_and_reply (result, NULL, NULL, &op_error);

   ret = true;
done:
   return ret;
}

static bool
assert_index_exists (test_t *test, operation_t *op, result_t *result, bool expect_exist, bson_error_t *error)
{
   bool ret = false;
   bson_parser_t *bp = NULL;
   char *collection_name = NULL;
   char *database_name = NULL;
   char *index_name = NULL;
   mongoc_collection_t *coll = NULL;
   bson_error_t op_error = {0};
   bool actual_exist = false;
   mongoc_cursor_t *cursor = NULL;
   const bson_t *index;
   const bson_t *error_doc = NULL;

   bp = bson_parser_new ();
   bson_parser_utf8 (bp, "collectionName", &collection_name);
   bson_parser_utf8 (bp, "databaseName", &database_name);
   bson_parser_utf8 (bp, "indexName", &index_name);
   if (!bson_parser_parse (bp, op->arguments, error)) {
      goto done;
   }

   coll = mongoc_client_get_collection (test->test_file->test_runner->internal_client, database_name, collection_name);
   cursor = mongoc_collection_find_indexes_with_opts (coll, NULL);
   while (mongoc_cursor_next (cursor, &index)) {
      bson_iter_t iter;

      if (!bson_iter_init_find (&iter, index, "name")) {
         continue;
      }

      if (!BSON_ITER_HOLDS_UTF8 (&iter)) {
         continue;
      }

      if (0 != strcmp (bson_iter_utf8 (&iter, NULL), index_name)) {
         continue;
      }

      actual_exist = true;
      break;
   }

   mongoc_cursor_error_document (cursor, &op_error, &error_doc);

   if (expect_exist != actual_exist) {
      test_set_error (error,
                      "expected index %s %s exist but %s",
                      index_name,
                      expect_exist ? "to" : "to not",
                      expect_exist ? "did not" : "did");
      goto done;
   }

   result_from_val_and_reply (result, NULL, (bson_t *) error_doc, &op_error);

   ret = true;
done:
   mongoc_cursor_destroy (cursor);
   mongoc_collection_destroy (coll);
   bson_parser_destroy_with_parsed_fields (bp);
   return ret;
}

static bool
operation_assert_index_exists (test_t *test, operation_t *op, result_t *result, bson_error_t *error)
{
   return assert_index_exists (test, op, result, true, error);
}

static bool
operation_assert_index_not_exists (test_t *test, operation_t *op, result_t *result, bson_error_t *error)
{
   return assert_index_exists (test, op, result, false, error);
}

typedef struct {
   test_t *test;
   bson_t *ops;
} txn_ctx_t;

static bool
with_transaction_cb (mongoc_client_session_t *session, void *ctx, bson_t **reply, bson_error_t *error)
{
   bool ret = false;
   bson_iter_t iter;
   txn_ctx_t *tctx = NULL;

   BSON_UNUSED (session);
   BSON_UNUSED (reply);

   tctx = (txn_ctx_t *) ctx;

   BSON_FOREACH (tctx->ops, iter)
   {
      bson_t op_bson;

      bson_iter_bson (&iter, &op_bson);
      MONGOC_DEBUG ("in with_transaction_cb running: %s", tmp_json (&op_bson));
      if (!operation_run (tctx->test, &op_bson, error)) {
         goto done;
      }
   }

   ret = true;
done:
   return ret;
}

static bool
operation_with_transaction (test_t *test, operation_t *op, result_t *result, bson_error_t *error)
{
   bool ret = false;
   bson_parser_t *bp = NULL;
   mongoc_client_session_t *session = NULL;
   bson_t op_reply = BSON_INITIALIZER;
   bson_error_t op_error = {0};
   mongoc_read_concern_t *rc = NULL;
   mongoc_write_concern_t *wc = NULL;
   mongoc_read_prefs_t *rp = NULL;
   mongoc_transaction_opt_t *topts = NULL;
   txn_ctx_t tctx;

   session = entity_map_get_session (test->entity_map, op->object, error);
   if (!session) {
      goto done;
   }

   topts = mongoc_transaction_opts_new ();

   bp = bson_parser_new ();
   bson_parser_array (bp, "callback", &tctx.ops);
   bson_parser_read_concern_optional (bp, &rc);
   bson_parser_write_concern_optional (bp, &wc);
   bson_parser_read_prefs_optional (bp, &rp);
   if (!bson_parser_parse (bp, op->arguments, error)) {
      goto done;
   }
   if (rc) {
      mongoc_transaction_opts_set_read_concern (topts, rc);
   }

   if (wc) {
      mongoc_transaction_opts_set_write_concern (topts, wc);
   }

   if (rp) {
      mongoc_transaction_opts_set_read_prefs (topts, rp);
   }

   tctx.test = test;
   bson_destroy (&op_reply);
   mongoc_client_session_with_transaction (session, with_transaction_cb, topts, &tctx, &op_reply, &op_error);

   result_from_val_and_reply (result, NULL, &op_reply, &op_error);

   ret = true;
done:
   bson_parser_destroy_with_parsed_fields (bp);
   mongoc_transaction_opts_destroy (topts);
   bson_destroy (&op_reply);
   return ret;
}

static bool
assert_session_pinned (test_t *test, operation_t *op, result_t *result, bool expect_pinned, bson_error_t *error)
{
   bool ret = false;
   bool actual_pinned = false;

   BSON_UNUSED (test);

   if (!op->session) {
      test_set_error (error, "%s", "expected session to be set");
      goto done;
   }

   if (0 != mongoc_client_session_get_server_id (op->session)) {
      actual_pinned = true;
   }

   if (actual_pinned != expect_pinned) {
      test_set_error (error,
                      "expected session to be %s but got %s",
                      expect_pinned ? "pinned" : "unpinned",
                      expect_pinned ? "unpinned" : "pinnned");
      goto done;
   }

   result_from_ok (result);
   ret = true;
done:
   return ret;
}

static bool
operation_assert_session_pinned (test_t *test, operation_t *op, result_t *result, bson_error_t *error)
{
   return assert_session_pinned (test, op, result, true, error);
}

static bool
operation_assert_session_unpinned (test_t *test, operation_t *op, result_t *result, bson_error_t *error)
{
   return assert_session_pinned (test, op, result, false, error);
}

static bool
create_loop_bson_array_entity (entity_map_t *em, const char *id, bson_error_t *error)
{
   BSON_ASSERT_PARAM (em);
   BSON_OPTIONAL_PARAM (id);
   BSON_OPTIONAL_PARAM (error);

   if (!id) {
      // Nothing to do.
      return true;
   }

   const entity_t *const entity = entity_map_get (em, id, error);

   // If the entity does not exist, the test runner MUST create it with the
   // type of BSON array.
   if (!entity) {
      return entity_map_add_bson_array (em, id, error);
   }

   // If the entity exists and is of type BSON array, the test runner MUST do
   // nothing.
   else if (strcmp (entity->type, "bson_array") == 0) {
      return true;
   }

   // If the entity exists and is of a different type, the test runner MUST
   // raise an error.
   else {
      test_set_error (error, "loop entity %s exists but is not of type BSON array", id);
      return false;
   }
}

static bool
create_loop_size_t_entity (entity_map_t *em, const char *id, bson_error_t *error)
{
   BSON_ASSERT_PARAM (em);
   BSON_OPTIONAL_PARAM (id);
   BSON_OPTIONAL_PARAM (error);

   if (!id) {
      return true;
   }

   const entity_t *const entity = entity_map_get (em, id, error);

   // If the entity of the specified name already exists, the test runner MUST
   // raise an error.
   if (entity) {
      test_set_error (error, "loop entity %s already exists when it should not", id);
      return false;
   }

   return entity_map_add_size_t (em, id, bson_malloc0 (sizeof (size_t)), error);
}

static void
increment_loop_counter (entity_map_t *em, const char *id)
{
   BSON_ASSERT_PARAM (em);
   BSON_OPTIONAL_PARAM (id);

   if (id) {
      bson_error_t error = {0};
      size_t *const counter = entity_map_get_size_t (em, id, &error);

      // Entity should always be valid at this point.
      ASSERT_OR_PRINT (counter, error);

      *counter += 1u;
   }
}

static bool
append_loop_error (entity_map_t *em, const char *id, bson_error_t *op_error, bson_error_t *error)
{
   BSON_ASSERT_PARAM (em);
   BSON_OPTIONAL_PARAM (id);
   BSON_ASSERT_PARAM (op_error);
   BSON_OPTIONAL_PARAM (error);

   if (!id) {
      return true;
   }

   mongoc_array_t *array = entity_map_get_bson_array (em, id, error);

   if (!array) {
      return false;
   }

   const int64_t usecs = usecs_since_epoch ();
   const double secs = (double) usecs / 1000000.0;

   bson_t *doc = bson_new ();
   BSON_APPEND_UTF8 (doc, "error", op_error->message);
   BSON_APPEND_DOUBLE (doc, "time", secs);

   _mongoc_array_append_val (array, doc); // Transfer ownership.

   return true;
}

// Expected to be set by a SIGINT (on Linux and OS X) or CTRL_BREAK_EVENT (on
// Windows) handler when test suite is being executed by test-atlas-executor.
volatile sig_atomic_t operation_loop_terminated = false;

static bool
operation_loop (test_t *test, operation_t *op, result_t *result, bson_error_t *error)
{
   bool ret = false;

   BSON_ASSERT_PARAM (test);
   BSON_ASSERT_PARAM (op);
   BSON_ASSERT_PARAM (result);
   BSON_ASSERT_PARAM (error);

   if (test->loop_operation_executed) {
      test_set_error (error, "test should not contain more than one loop operation");
      return false;
   } else {
      test->loop_operation_executed = true;
   }

   if (!op->object || strcmp (op->object, "testRunner") != 0) {
      test_set_error (error, "loop operation object should be \"testRunner\"");
      return false;
   }

   bson_t *operations = NULL;
   char *errors_as_entity = NULL;
   char *failures_as_entity = NULL;
   char *successes_as_entity = NULL;
   char *iterations_as_entity = NULL;

   bson_parser_t *const parser = bson_parser_new ();

   bson_parser_array (parser, "operations", &operations);
   bson_parser_utf8_optional (parser, "storeErrorsAsEntity", &errors_as_entity);
   bson_parser_utf8_optional (parser, "storeFailuresAsEntity", &failures_as_entity);
   bson_parser_utf8_optional (parser, "storeSuccessesAsEntity", &successes_as_entity);
   bson_parser_utf8_optional (parser, "storeIterationsAsEntity", &iterations_as_entity);

   if (!bson_parser_parse (parser, op->arguments, error)) {
      goto done;
   }

   // If the test runner propagates an error or failure (e.g. it is not captured
   // by the loop or occurs outside of the loop), it MUST be reported by the
   // workload executor.
   const bool should_propagate = !failures_as_entity && !errors_as_entity;

   // Guarantee propagation of errors if captures are not defined by the loop.
   if (should_propagate) {
      // Ensure any errors or failures are still reported.
      errors_as_entity = bson_strdup ("errors");
   }

   // If only one of errors or failures is provided, all failures and errors
   // must be stored in the provided entity.
   if ((errors_as_entity ? 1 : 0) != (failures_as_entity ? 1 : 0)) {
      if (errors_as_entity) {
         failures_as_entity = bson_strdup (errors_as_entity);
      } else {
         errors_as_entity = bson_strdup (failures_as_entity);
      }
   }

   if (!create_loop_bson_array_entity (test->entity_map, errors_as_entity, error)) {
      goto done;
   }

   if (!create_loop_bson_array_entity (test->entity_map, failures_as_entity, error)) {
      goto done;
   }

   if (!create_loop_size_t_entity (test->entity_map, successes_as_entity, error)) {
      goto done;
   }

   if (!create_loop_size_t_entity (test->entity_map, iterations_as_entity, error)) {
      goto done;
   }

   MONGOC_DEBUG ("running loop operations...");
   // `operation_loop_terminated` may be set to true within the loop if
   // `should_propagate` is true on error/failure or by test-atlas-executor upon
   // receiving a termination request.
   while (!operation_loop_terminated) {
      // Clear any prior errors that may have occurred.
      error->code = 0;

      increment_loop_counter (test->entity_map, iterations_as_entity);

      bson_iter_t iter;
      BSON_FOREACH (operations, iter)
      {
         bson_t op_bson;
         bson_iter_bson (&iter, &op_bson);

         bson_error_t op_error = {0};

         // Execute the loop sub-operation.
         const bool res = operation_run (test, &op_bson, &op_error);

         if (res) {
            increment_loop_counter (test->entity_map, successes_as_entity);
         } else {
            // Categorize errors triggered by a sub-operation as a "failure".
            // If this operation fails, categorize as an "error" instead.
            (void) append_loop_error (test->entity_map, failures_as_entity, &op_error, error);

            // If neither storeErrorsAsEntity nor storeFailuresAsEntity are
            // specified, the loop MUST terminate and raise the error/failure
            // (i.e. the error/failure will interrupt the test).
            operation_loop_terminated = operation_loop_terminated || should_propagate;

            // If, in the course of executing sub-operations, a sub-operation
            // yields an error or failure, the test runner MUST NOT execute
            // subsequent sub-operations in the same loop iteration.
            break;
         }
      } // End of loop sub-operations, but *not* end of loop iterations.

      // Categorize errors triggered outside of sub-operation execution as
      // "errors".
      if (error->code != 0) {
         // We have a problem if we can't store errors without triggering a new
         // error. Terminate the test suite early instead of looping with an
         // irrecoverable state.
         ASSERT_OR_PRINT (append_loop_error (test->entity_map, errors_as_entity, error, error), (*error));

         // If neither storeErrorsAsEntity nor storeFailuresAsEntity are
         // specified, the loop MUST terminate and raise the error/failure
         // (i.e. the error/failure will interrupt the test).
         operation_loop_terminated = operation_loop_terminated || should_propagate;
      }
   }
   MONGOC_DEBUG ("running loop operations... done.");

   result_from_ok (result);
   ret = true;

done:
   bson_parser_destroy_with_parsed_fields (parser);

   return ret;
}

static bool
operation_assert_number_connections_checked_out (test_t *test, operation_t *op, result_t *result, bson_error_t *error)
{
   BSON_UNUSED (test);
   BSON_UNUSED (op);
   BSON_UNUSED (error);
   /* "This operation only applies to drivers that implement connection pooling
    * and should be skipped for drivers that do not."
    * TODO: (CDRIVER-3525) add this assertion when CMAP is implemented. */
   result_from_ok (result);
   return true;
}

static bool
operation_wait (test_t *test, operation_t *op, result_t *result, bson_error_t *error)
{
   BSON_UNUSED (test);
   BSON_UNUSED (error);

   bson_iter_t iter;
   bson_iter_init_find (&iter, op->arguments, "ms");
   ASSERT (BSON_ITER_HOLDS_INT (&iter));
   const int64_t sleep_msec = bson_iter_as_int64 (&iter);
   _mongoc_usleep (sleep_msec * 1000);

   result_from_ok (result);
   return true;
}

static bool
operation_rename (test_t *test, operation_t *op, result_t *result, bson_error_t *error)
{
   // First validate the arguments
   const char *object = op->object;
   bson_parser_t *bp = bson_parser_new ();
   bool ret = false;
   bool *drop_target = NULL;
   char *new_name = NULL;
   bson_parser_utf8 (bp, "to", &new_name);
   bson_parser_bool_optional (bp, "dropTarget", &drop_target);
   bool parse_ok = bson_parser_parse (bp, op->arguments, error);
   bson_parser_destroy (bp);
   if (!parse_ok) {
      goto done;
   }

   // Now get the entity
   entity_t *ent = entity_map_get (test->entity_map, object, error);
   if (!ent) {
      goto done;
   }
   // We only support collections so far
   if (0 != strcmp (ent->type, "collection")) {
      test_set_error (error,
                      "'rename' is only supported for collection objects "
                      "'%s' has type '%s'",
                      object,
                      ent->type);
      goto done;
   }

   // Rename the collection in the server,
   mongoc_collection_t *coll = ent->value;
   if (!mongoc_collection_rename (coll, NULL, new_name, drop_target ? *drop_target : false, error)) {
      goto done;
   }
   result_from_ok (result);
   ret = true;
done:
   bson_free (new_name);
   bson_free (drop_target);
   return ret;
}

static bool
operation_createSearchIndex (test_t *test, operation_t *op, result_t *result, bson_error_t *error)
{
   bool ret = false;
   bson_parser_t *bp = bson_parser_new ();
   bson_t *model = NULL;
   mongoc_collection_t *coll = NULL;
   bson_error_t op_error;
   bson_t op_reply = BSON_INITIALIZER;
   bson_t *cmd = bson_new ();

   // Parse arguments.
   bson_parser_doc (bp, "model", &model);
   if (!bson_parser_parse (bp, op->arguments, error)) {
      goto done;
   }

   coll = entity_map_get_collection (test->entity_map, op->object, error);
   if (!coll) {
      goto done;
   }

   // Build command.
   bsonBuildAppend (*cmd, kv ("createSearchIndexes", cstr (coll->collection)), kv ("indexes", array (bson (*model))));
   ASSERT (!bsonBuildError);

   mongoc_collection_command_simple (coll, cmd, NULL /* read_prefs */, NULL /* reply */, &op_error);
   result_from_val_and_reply (result, NULL, &op_reply, &op_error);
   ret = true;
done:
   bson_destroy (cmd);
   bson_parser_destroy_with_parsed_fields (bp);
   bson_destroy (&op_reply);
   return ret;
}

static bool
operation_createSearchIndexes (test_t *test, operation_t *op, result_t *result, bson_error_t *error)
{
   bool ret = false;
   bson_parser_t *bp = bson_parser_new ();
   bson_t *models = NULL;
   mongoc_collection_t *coll = NULL;
   bson_error_t op_error;
   bson_t op_reply = BSON_INITIALIZER;
   bson_t *cmd = bson_new ();

   // Parse arguments.
   bson_parser_array (bp, "models", &models);
   if (!bson_parser_parse (bp, op->arguments, error)) {
      goto done;
   }

   coll = entity_map_get_collection (test->entity_map, op->object, error);
   if (!coll) {
      goto done;
   }

   // Build command.
   bsonBuildAppend (*cmd, kv ("createSearchIndexes", cstr (coll->collection)), kv ("indexes", bsonArray (*models)));
   ASSERT (!bsonBuildError);

   mongoc_collection_command_simple (coll, cmd, NULL /* read_prefs */, NULL /* reply */, &op_error);
   result_from_val_and_reply (result, NULL, &op_reply, &op_error);
   ret = true;
done:
   bson_destroy (cmd);
   bson_parser_destroy_with_parsed_fields (bp);
   bson_destroy (&op_reply);
   return ret;
}

static bool
operation_dropSearchIndex (test_t *test, operation_t *op, result_t *result, bson_error_t *error)
{
   bool ret = false;
   bson_parser_t *bp = bson_parser_new ();
   char *name = NULL;
   mongoc_collection_t *coll = NULL;
   bson_error_t op_error;
   bson_t op_reply = BSON_INITIALIZER;
   bson_t *cmd = bson_new ();

   // Parse arguments.
   bson_parser_utf8 (bp, "name", &name);
   if (!bson_parser_parse (bp, op->arguments, error)) {
      goto done;
   }

   coll = entity_map_get_collection (test->entity_map, op->object, error);
   if (!coll) {
      goto done;
   }

   // Build command.
   bsonBuildAppend (*cmd, kv ("dropSearchIndex", cstr (coll->collection)), kv ("name", cstr (name)));
   ASSERT (!bsonBuildError);

   mongoc_collection_command_simple (coll, cmd, NULL /* read_prefs */, NULL /* reply */, &op_error);
   result_from_val_and_reply (result, NULL, &op_reply, &op_error);
   ret = true;
done:
   bson_destroy (cmd);
   bson_parser_destroy_with_parsed_fields (bp);
   bson_destroy (&op_reply);
   return ret;
}

static bool
operation_listSearchIndexes (test_t *test, operation_t *op, result_t *result, bson_error_t *error)
{
   bool ret = false;
   bson_parser_t *bp = bson_parser_new ();
   bson_t *aggregateOptions = NULL;
   char *name = NULL;
   mongoc_collection_t *coll = NULL;
   bson_t *pipeline = bson_new ();
   mongoc_cursor_t *cursor = NULL;

   // Parse arguments.
   if (op->arguments) {
      bson_parser_utf8_optional (bp, "name", &name);
      bson_parser_doc_optional (bp, "aggregationOptions", &aggregateOptions);
      if (!bson_parser_parse (bp, op->arguments, error)) {
         goto done;
      }
   }

   coll = entity_map_get_collection (test->entity_map, op->object, error);
   if (!coll) {
      goto done;
   }

   // Build command.
   bsonBuildAppend (*pipeline,
                    kv ("pipeline",
                        array (doc (kv ("$listSearchIndexes",
                                        if (name != NULL, then (doc (kv ("name", cstr (name)))), else (doc ())))))));
   ASSERT (!bsonBuildError);

   cursor = mongoc_collection_aggregate (
      coll, MONGOC_QUERY_NONE, pipeline, aggregateOptions /* opts */, NULL /* read_prefs */);

   result_from_cursor (result, cursor);
   ret = true;
done:
   mongoc_cursor_destroy (cursor);
   bson_destroy (pipeline);
   bson_parser_destroy_with_parsed_fields (bp);
   return ret;
}

static bool
operation_updateSearchIndex (test_t *test, operation_t *op, result_t *result, bson_error_t *error)
{
   bool ret = false;
   bson_parser_t *bp = bson_parser_new ();
   bson_t *definition = NULL;
   char *name = NULL;
   mongoc_collection_t *coll = NULL;
   bson_error_t op_error;
   bson_t op_reply = BSON_INITIALIZER;
   bson_t *cmd = bson_new ();

   // Parse arguments.
   bson_parser_doc (bp, "definition", &definition);
   bson_parser_utf8_optional (bp, "name", &name);
   if (!bson_parser_parse (bp, op->arguments, error)) {
      goto done;
   }

   coll = entity_map_get_collection (test->entity_map, op->object, error);
   if (!coll) {
      goto done;
   }

   // Build command.
   bsonBuildAppend (*cmd,
                    kv ("updateSearchIndex", cstr (coll->collection)),
                    kv ("definition", bson (*definition)),
                    if (name != NULL, then (kv ("name", cstr (name)))));
   ASSERT (!bsonBuildError);

   mongoc_collection_command_simple (coll, cmd, NULL /* read_prefs */, NULL /* reply */, &op_error);
   result_from_val_and_reply (result, NULL, &op_reply, &op_error);
   ret = true;
done:
   bson_destroy (cmd);
   bson_parser_destroy_with_parsed_fields (bp);
   bson_destroy (&op_reply);
   return ret;
}

static bool
operation_create_entities (test_t *test, operation_t *op, result_t *result, bson_error_t *error)
{
   bool ret = false;

   bson_parser_t *bp = bson_parser_new ();
   bson_t *entities;
   bson_parser_array_optional (bp, "entities", &entities);
   if (!bson_parser_parse (bp, op->arguments, error)) {
      goto done;
   }

   bson_iter_t entity_iter;
   BSON_FOREACH (entities, entity_iter)
   {
      bson_t entity;
      bson_iter_bson (&entity_iter, &entity);
      bool create_ret = entity_map_create (test->entity_map, &entity, error);
      bson_destroy (&entity);
      if (!create_ret) {
         goto done;
      }
   }

   result_from_ok (result);
   ret = true;
done:
   bson_parser_destroy_with_parsed_fields (bp);
   return ret;
}

#define WAIT_FOR_EVENT_TIMEOUT_MS (10 * 1000) // Specified by test runner spec
#define WAIT_FOR_EVENT_TICK_MS 10             // Same tick size as used in non-unified json test

static bool
log_filter_hide_server_selection_operation (const mongoc_structured_log_entry_t *entry, void *user_data)
{
   BSON_ASSERT_PARAM (entry);
   BSON_ASSERT_PARAM (user_data);
   const char *expected_operation = (const char *) user_data;

   if (mongoc_structured_log_entry_get_component (entry) == MONGOC_STRUCTURED_LOG_COMPONENT_SERVER_SELECTION) {
      bson_t *bson = mongoc_structured_log_entry_message_as_bson (entry);
      bson_iter_t iter;
      BSON_ASSERT (bson_iter_init_find (&iter, bson, "operation"));
      BSON_ASSERT (BSON_ITER_HOLDS_UTF8 (&iter));
      BSON_ASSERT (0 == strcmp (expected_operation, bson_iter_utf8 (&iter, NULL)));
      bson_destroy (bson);
      return false;
   }
   return true;
}

static void
_operation_hidden_wait (test_t *test, entity_t *client, const char *name)
{
   _mongoc_usleep (WAIT_FOR_EVENT_TICK_MS * 1000);

   // @todo Re-examine this once we have support for connection pools in the unified test
   //    runner. Without pooling, all events we could be waiting on would be coming
   //    from single-threaded (blocking) topology scans, or from lazily opening the topology
   //    description when it's first used. Request stream selection after blocking, to
   //    handle either of these cases.
   //
   // Structured logs can not be fully suppressed here, because we do need to emit topology
   // lifecycle events. Filter out only the server selection operation here, and ASSERT
   // that it's the waitForEvent we expect.

   entity_log_filter_push (client, log_filter_hide_server_selection_operation, (void *) name);

   mongoc_client_t *mc_client = entity_map_get_client (test->entity_map, client->id, NULL);
   if (mc_client) {
      const mongoc_ss_log_context_t ss_log_context = {.operation = name};
      mongoc_server_stream_t *stream = mongoc_cluster_stream_for_reads (&mc_client->cluster,
                                                                        &ss_log_context,
                                                                        NULL /* read_prefs */,
                                                                        NULL /* client session */,
                                                                        NULL /* deprioritized servers */,
                                                                        NULL /* reply */,
                                                                        NULL /* error */);
      if (stream) {
         mongoc_server_stream_cleanup (stream);
      }
   }

   entity_log_filter_pop (client, log_filter_hide_server_selection_operation, (void *) name);
}

static bool
operation_wait_for_event (test_t *test, operation_t *op, result_t *result, bson_error_t *error)
{
   bool ret = false;
   int64_t start_time = bson_get_monotonic_time ();

   bson_parser_t *bp = bson_parser_new ();
   char *client_id;
   bson_t *expected_event;
   int64_t *expected_count;
   bson_parser_utf8 (bp, "client", &client_id);
   bson_parser_doc (bp, "event", &expected_event);
   bson_parser_int (bp, "count", &expected_count);
   if (!bson_parser_parse (bp, op->arguments, error)) {
      goto done;
   }

   entity_t *client = entity_map_get (test->entity_map, client_id, error);
   if (!client) {
      goto done;
   }

   // @todo CDRIVER-3525 test support for CMAP events once supported by libmongoc
   bson_iter_t iter;
   bson_iter_init (&iter, expected_event);
   bson_iter_next (&iter);
   const char *expected_event_type = bson_iter_key (&iter);
   if (is_unsupported_event_type (expected_event_type)) {
      MONGOC_DEBUG ("SKIPPING waitForEvent for unsupported event type '%s'", expected_event_type);
      result_from_ok (result);
      ret = true;
      goto done;
   }

   while (true) {
      int64_t count;
      if (!test_count_matching_events_for_client (test, client, expected_event, error, &count)) {
         goto done;
      }
      if (count >= *expected_count) {
         break;
      }

      int64_t duration = bson_get_monotonic_time () - start_time;
      if (duration >= (int64_t) WAIT_FOR_EVENT_TIMEOUT_MS * 1000) {
         char *event_list_string = event_list_to_string (client->events);
         test_diagnostics_error_info ("all captured events for client:\n%s", event_list_string);
         bson_free (event_list_string);
         test_diagnostics_error_info ("checking for expected event: %s\n", tmp_json (expected_event));
         test_set_error (error,
                         "waitForEvent timed out with %" PRId64 " of %" PRId64
                         " matches needed. waited %dms (max %dms)",
                         count,
                         *expected_count,
                         (int) (duration / 1000),
                         (int) WAIT_FOR_EVENT_TIMEOUT_MS);
         goto done;
      };

      _operation_hidden_wait (test, client, "waitForEvent");
   }

   result_from_ok (result);
   ret = true;
done:
   bson_parser_destroy_with_parsed_fields (bp);
   return ret;
}

/* Test for an event match document containing an unsupported event type as defined by is_unsupported_event_type(). If
 * the match document has an unexpected format, returns false. */
static bool
is_unsupported_event_match_document (const bson_t *event_match)
{
   BSON_ASSERT_PARAM (event_match);

   if (bson_count_keys (event_match) == 1) {
      bson_iter_t iter;
      bson_iter_init (&iter, event_match);
      bson_iter_next (&iter);
      return is_unsupported_event_type (bson_iter_key (&iter));
   } else {
      return false;
   }
}

static bool
operation_assert_event_count (test_t *test, operation_t *op, result_t *result, bson_error_t *error)
{
   bool ret = false;

   bson_parser_t *bp = bson_parser_new ();
   char *client_id;
   bson_t *expected_event;
   int64_t *expected_count;
   bson_parser_utf8 (bp, "client", &client_id);
   bson_parser_doc (bp, "event", &expected_event);
   bson_parser_int (bp, "count", &expected_count);
   if (!bson_parser_parse (bp, op->arguments, error)) {
      goto done;
   }

   if (is_unsupported_event_match_document (expected_event)) {
      MONGOC_DEBUG ("SKIPPING assertEventCount for unsupported event %s", tmp_json (expected_event));
      result_from_ok (result);
      ret = true;
      goto done;
   }

   entity_t *client = entity_map_get (test->entity_map, client_id, error);
   if (!client) {
      goto done;
   }

   int64_t actual_count;
   if (!test_count_matching_events_for_client (test, client, expected_event, error, &actual_count)) {
      goto done;
   }
   if (actual_count != *expected_count) {
      char *event_list_string = event_list_to_string (client->events);
      test_diagnostics_error_info ("all captured events for client:\n%s", event_list_string);
      bson_free (event_list_string);
      test_diagnostics_error_info ("checking for expected event: %s\n", tmp_json (expected_event));
      test_set_error (error,
                      "assertEventCount found %" PRId64 " matches, required exactly %" PRId64 " matches",
                      actual_count,
                      *expected_count);
      goto done;
   }

   result_from_ok (result);
   ret = true;
done:
   bson_parser_destroy_with_parsed_fields (bp);
   return ret;
}

static const char *
tmp_topology_description_json (const mongoc_topology_description_t *td)
{
   bson_t bson = BSON_INITIALIZER;
   BSON_ASSERT (mongoc_topology_description_append_contents_to_bson (
      td,
      &bson,
      MONGOC_TOPOLOGY_DESCRIPTION_CONTENT_FLAG_TYPE | MONGOC_TOPOLOGY_DESCRIPTION_CONTENT_FLAG_SERVERS,
      MONGOC_SERVER_DESCRIPTION_CONTENT_FLAG_ADDRESS | MONGOC_SERVER_DESCRIPTION_CONTENT_FLAG_TYPE));
   const char *result = tmp_json (&bson);
   bson_destroy (&bson);
   return result;
}

static bool
operation_record_topology_description (test_t *test, operation_t *op, result_t *result, bson_error_t *error)
{
   bool ret = false;

   bson_parser_t *bp = bson_parser_new ();
   char *client_id, *td_id;
   bson_parser_utf8 (bp, "client", &client_id);
   bson_parser_utf8 (bp, "id", &td_id);
   if (!bson_parser_parse (bp, op->arguments, error)) {
      goto done;
   }

   mongoc_client_t *client = entity_map_get_client (test->entity_map, client_id, error);
   if (!client) {
      goto done;
   }

   mongoc_topology_description_t *td_copy = bson_malloc0 (sizeof *td_copy);
   mc_shared_tpld td = mc_tpld_take_ref (client->topology);
   _mongoc_topology_description_copy_to (td.ptr, td_copy);
   mc_tpld_drop_ref (&td);

   MONGOC_DEBUG ("Recording topology description '%s': %s", td_id, tmp_topology_description_json (td_copy));

   if (!entity_map_add_topology_description (test->entity_map, td_id, td_copy, error)) {
      mongoc_topology_description_destroy (td_copy);
      goto done;
   }

   result_from_ok (result);
   ret = true;
done:
   bson_parser_destroy_with_parsed_fields (bp);
   return ret;
}

static bool
operation_assert_topology_type (test_t *test, operation_t *op, result_t *result, bson_error_t *error)
{
   bool ret = false;

   bson_parser_t *bp = bson_parser_new ();
   char *td_id, *expected_topology_type;
   bson_parser_utf8 (bp, "topologyDescription", &td_id);
   bson_parser_utf8 (bp, "topologyType", &expected_topology_type);
   if (!bson_parser_parse (bp, op->arguments, error)) {
      goto done;
   }

   // Pointer borrowed from entity
   mongoc_topology_description_t *td = entity_map_get_topology_description (test->entity_map, td_id, error);
   if (!td) {
      goto done;
   }

   // Static lifetime
   const char *actual_topology_type = mongoc_topology_description_type (td);

   if (0 != strcmp (expected_topology_type, actual_topology_type)) {
      test_set_error (error,
                      "assertTopologyType failed, expected '%s' but found type '%s' in topology description: %s",
                      expected_topology_type,
                      actual_topology_type,
                      tmp_topology_description_json (td));
      goto done;
   }

   result_from_ok (result);
   ret = true;
done:
   bson_parser_destroy_with_parsed_fields (bp);
   return ret;
}

static bool
operation_wait_for_primary_change (test_t *test, operation_t *op, result_t *result, bson_error_t *error)
{
   bool ret = false;

   bson_parser_t *bp = bson_parser_new ();
   char *client_id, *prior_td_id;
   int64_t *optional_timeout_ms = NULL;
   bson_parser_utf8 (bp, "client", &client_id);
   bson_parser_utf8 (bp, "priorTopologyDescription", &prior_td_id);
   bson_parser_int_optional (bp, "timeoutMS", &optional_timeout_ms);
   if (!bson_parser_parse (bp, op->arguments, error)) {
      goto done;
   }

   int64_t timeout = INT64_C (1000) * (optional_timeout_ms ? *optional_timeout_ms : INT64_C (10000));
   int64_t start_time = bson_get_monotonic_time ();

   mongoc_client_t *mc_client = entity_map_get_client (test->entity_map, client_id, error);
   if (!mc_client) {
      goto done;
   }

   entity_t *client = entity_map_get (test->entity_map, client_id, error);
   BSON_ASSERT (client);

   // Pointer borrowed from entity
   mongoc_topology_description_t *prior_td = entity_map_get_topology_description (test->entity_map, prior_td_id, error);
   if (!prior_td) {
      goto done;
   }

   // Will be NULL when there's no primary
   const mongoc_server_description_t *prior_primary = _mongoc_topology_description_has_primary (prior_td);

   while (true) {
      mc_shared_tpld td = mc_tpld_take_ref (mc_client->topology);
      const mongoc_server_description_t *primary = _mongoc_topology_description_has_primary (td.ptr);

      if (!prior_primary && primary) {
         MONGOC_DEBUG ("waitForPrimaryChange succeeded by transitioning from none to any");
         mc_tpld_drop_ref (&td);
         break;
      }

      if (prior_primary && primary && !_mongoc_server_description_equal (prior_primary, primary)) {
         MONGOC_DEBUG ("waitForPrimaryChange succeeded by switching to a different primary server");
         mc_tpld_drop_ref (&td);
         break;
      }

      int64_t duration = bson_get_monotonic_time () - start_time;
      if (duration >= timeout) {
         test_set_error (error,
                         "waitForPrimaryChange timed out. waited %fms (max %fms). \nprior topology description: "
                         "%s\ncurrent topology description: %s",
                         duration / 1000.0,
                         timeout / 1000.0,
                         tmp_topology_description_json (prior_td),
                         tmp_topology_description_json (td.ptr));

         mc_tpld_drop_ref (&td);
         goto done;
      }
      mc_tpld_drop_ref (&td);

      _operation_hidden_wait (test, client, "waitForPrimaryChange");
   }

   result_from_ok (result);
   ret = true;
done:
   bson_parser_destroy_with_parsed_fields (bp);
   return ret;
}

typedef struct {
   const char *op;
   bool (*fn) (test_t *, operation_t *, result_t *, bson_error_t *);
} op_to_fn_t;

bool
operation_run (test_t *test, bson_t *op_bson, bson_error_t *error)
{
   operation_t *op = NULL;
   result_t *result = NULL;
   int i, num_ops;
   bool check_result = true;
   op_to_fn_t op_to_fn_map[] = {
      /* Client operations */
      {"createChangeStream", operation_create_change_stream},
      {"listDatabases", operation_list_databases},
      {"listDatabaseNames", operation_list_database_names},
      {"clientBulkWrite", operation_client_bulkwrite},

      /* ClientEncryption operations */
      {"createDataKey", operation_create_datakey},
      {"rewrapManyDataKey", operation_rewrap_many_datakey},
      {"deleteKey", operation_delete_key},
      {"getKey", operation_get_key},
      {"getKeys", operation_get_keys},
      {"addKeyAltName", operation_add_key_alt_name},
      {"removeKeyAltName", operation_remove_key_alt_name},
      {"getKeyByAltName", operation_get_key_by_alt_name},
      {"encrypt", operation_encrypt},
      {"decrypt", operation_decrypt},

      /* Database operations */
      {"createCollection", operation_create_collection},
      {"dropCollection", operation_drop_collection},
      {"listCollections", operation_list_collections},
      {"listCollectionNames", operation_list_collection_names},
      {"listIndexes", operation_list_indexes},
      {"runCommand", operation_run_command},
      {"modifyCollection", operation_modify_collection},

      /* Collection operations */
      {"aggregate", operation_aggregate},
      {"bulkWrite", operation_bulk_write},
      {"countDocuments", operation_count_documents},
      {"createFindCursor", operation_create_find_cursor},
      {"createIndex", operation_create_index},
      {"deleteOne", operation_delete_one},
      {"deleteMany", operation_delete_many},
      {"distinct", operation_distinct},
      {"estimatedDocumentCount", operation_estimated_document_count},
      {"find", operation_find},
      {"findOneAndDelete", operation_find_one_and_delete},
      {"findOneAndReplace", operation_find_one_and_replace},
      {"findOneAndUpdate", operation_find_one_and_update},
      {"insertMany", operation_insert_many},
      {"insertOne", operation_insert_one},
      {"replaceOne", operation_replace_one},
      {"updateOne", operation_update_one},
      {"updateMany", operation_update_many},
      {"rename", operation_rename},
      {"createSearchIndex", operation_createSearchIndex},
      {"createSearchIndexes", operation_createSearchIndexes},
      {"dropSearchIndex", operation_dropSearchIndex},
      {"listSearchIndexes", operation_listSearchIndexes},
      {"updateSearchIndex", operation_updateSearchIndex},

      /* Change stream and cursor operations */
      {"iterateUntilDocumentOrError", operation_iterate_until_document_or_error},
      {"close", operation_close},
      {"dropIndex", operation_drop_index},

      /* Test runner operations */
      {"failPoint", operation_failpoint},
      {"targetedFailPoint", operation_targeted_failpoint},
      {"assertSessionDirty", operation_assert_session_dirty},
      {"assertSessionNotDirty", operation_assert_session_not_dirty},
      {"assertSameLsidOnLastTwoCommands", operation_assert_same_lsid_on_last_two_commands},
      {"assertDifferentLsidOnLastTwoCommands", operation_assert_different_lsid_on_last_two_commands},
      {"assertSessionTransactionState", operation_assert_session_transaction_state},
      {"assertCollectionNotExists", operation_assert_collection_not_exists},
      {"assertCollectionExists", operation_assert_collection_exists},
      {"assertIndexNotExists", operation_assert_index_not_exists},
      {"assertIndexExists", operation_assert_index_exists},
      {"assertSessionPinned", operation_assert_session_pinned},
      {"assertSessionUnpinned", operation_assert_session_unpinned},
      {"loop", operation_loop},
      {"assertNumberConnectionsCheckedOut", operation_assert_number_connections_checked_out},
      {"wait", operation_wait},
      {"waitForEvent", operation_wait_for_event},
      {"assertEventCount", operation_assert_event_count},
      {"recordTopologyDescription", operation_record_topology_description},
      {"assertTopologyType", operation_assert_topology_type},
      {"waitForPrimaryChange", operation_wait_for_primary_change},

      /* GridFS operations */
      {"delete", operation_delete},
      {"download", operation_download},
      {"upload", operation_upload},

      /* Session operations */
      {"endSession", operation_end_session},
      {"startTransaction", operation_start_transaction},
      {"commitTransaction", operation_commit_transaction},
      {"withTransaction", operation_with_transaction},
      {"abortTransaction", operation_abort_transaction},

      /* Entity operations */
      {"createEntities", operation_create_entities},
   };
   bool ret = false;

   op = operation_new (op_bson, error);
   if (!op) {
      goto done;
   }

   /* Check for a "session" argument in all operations, it can be
    * an argument for any operation. */
   if (op->arguments && bson_has_field (op->arguments, "session")) {
      bson_t copied;
      mongoc_client_session_t *session = NULL;

      op->session_id = bson_strdup (bson_lookup_utf8 (op->arguments, "session"));
      session = entity_map_get_session (test->entity_map, op->session_id, error);

      if (!session) {
         goto done;
      }

      bson_copy_to_excluding (op->arguments, &copied, "session", NULL);
      bson_destroy (op->arguments);
      op->arguments = bson_copy (&copied);
      bson_destroy (&copied);
      op->session = session;
   }

   // Avoid spamming output with sub-operations when executing loop operation.
   if (!test->loop_operation_executed || operation_loop_terminated) {
      MONGOC_DEBUG ("running operation: %s", tmp_json (op_bson));
   }

   num_ops = sizeof (op_to_fn_map) / sizeof (op_to_fn_t);
   result = result_new ();

   if (op->ignore_result_and_error && *op->ignore_result_and_error) {
      check_result = false;
   }

   for (i = 0; i < num_ops; i++) {
      if (0 == strcmp (op->name, op_to_fn_map[i].op)) {
         if (!op_to_fn_map[i].fn (test, op, result, error)) {
            goto done;
         }
         if (check_result && !result_check (result, test->entity_map, op->expect_result, op->expect_error, error)) {
            test_diagnostics_error_info ("checking for result (%s) / error (%s)",
                                         bson_val_to_json (op->expect_result),
                                         tmp_json (op->expect_error));
            goto done;
         }
         if (result_get_val (result) != NULL && op->save_result_as_entity) {
            if (!entity_map_add_bson (test->entity_map, op->save_result_as_entity, result_get_val (result), error)) {
               goto done;
            }
         }
         break;
      }
   }

   if (i == num_ops) {
      test_set_error (error, "unrecognized operation: %s", op->name);
      goto done;
   }

   ret = true;
done:
   operation_destroy (op);
   result_destroy (result);
   return ret;
}
