static void
_test_read_concern_wire_version (bool allow, bool explicit)
{
   mongoc_read_concern_t *rc;
   bson_t opts = BSON_INITIALIZER;
   mock_server_t *server;
   mongoc_client_t *client;
   mongoc_collection_t *collection;
   mongoc_cursor_t *cursor;
   const bson_t *doc;
   future_t *future;
   request_t *request;
   bson_error_t error;

   rc = mongoc_read_concern_new ();
   mongoc_read_concern_set_level (rc, "foo");

   server = mock_server_with_autoismaster (
      allow ? WIRE_VERSION_READ_CONCERN : WIRE_VERSION_READ_CONCERN - 1);
   mock_server_run (server);
   client = mongoc_client_new_from_uri (mock_server_get_uri (server));
   collection = mongoc_client_get_collection (client, "db", "collection");

   if (explicit) {
      mongoc_read_concern_append (rc, &opts);
   } else {
static void
test_read_concern_append (void)
{
   mongoc_read_concern_t *rc;
   bson_t *cmd;

   cmd = tmp_bson ("{'foo': 1}");

   /* append default readConcern */
   rc = mongoc_read_concern_new ();
   ASSERT (mongoc_read_concern_is_default (rc));
   ASSERT_MATCH (cmd, "{'foo': 1, 'readConcern': {'$exists': false}}");

   /* append readConcern with level */
   mongoc_read_concern_set_level (rc, MONGOC_READ_CONCERN_LEVEL_LOCAL);
   ASSERT (mongoc_read_concern_append (rc, cmd));

   ASSERT_MATCH (cmd, "{'foo': 1, 'readConcern': {'level': 'local'}}");

   mongoc_read_concern_destroy (rc);
}
static void
set_func_opt (bson_t *opts,
              mongoc_read_prefs_t **prefs_ptr,
              opt_type_t opt_type)
{
   SET_OPT_PREAMBLE (function);

   switch (opt_type) {
   case OPT_READ_CONCERN:
      BSON_ASSERT (mongoc_read_concern_append (rc, opts));
      break;
   case OPT_WRITE_CONCERN:
      BSON_ASSERT (mongoc_write_concern_append (wc, opts));
      break;
   case OPT_READ_PREFS:
      *prefs_ptr = mongoc_read_prefs_copy (prefs);
      break;
   default:
      abort ();
   }

   SET_OPT_CLEANUP;
}
/*---------------------------------------------------------------------------
 *
 * _make_cursor --
 *
 *       Construct and send the aggregate command and create the resulting
 *       cursor. On error, stream->cursor remains NULL, otherwise it is
 *       created and must be destroyed.
 *
 * Return:
 *       False on error and sets stream->err.
 *
 *--------------------------------------------------------------------------
 */
static bool
_make_cursor (mongoc_change_stream_t *stream)
{
   mongoc_client_session_t *cs = NULL;
   bson_t command_opts;
   bson_t command; /* { aggregate: "coll", pipeline: [], ... } */
   bson_t reply;
   bson_t getmore_opts = BSON_INITIALIZER;
   bson_iter_t iter;
   mongoc_server_description_t *sd;
   uint32_t server_id;
   int32_t max_wire_version = -1;

   BSON_ASSERT (stream);
   BSON_ASSERT (!stream->cursor);
   _make_command (stream, &command);
   bson_copy_to (&(stream->opts.extra), &command_opts);
   sd = mongoc_client_select_server (
      stream->client, false /* for_writes */, stream->read_prefs, &stream->err);
   if (!sd) {
      goto cleanup;
   }
   server_id = mongoc_server_description_id (sd);
   bson_append_int32 (&command_opts, "serverId", 8, server_id);
   bson_append_int32 (&getmore_opts, "serverId", 8, server_id);
   max_wire_version = sd->max_wire_version;
   mongoc_server_description_destroy (sd);

   if (bson_iter_init_find (&iter, &command_opts, "sessionId")) {
      if (!_mongoc_client_session_from_iter (
             stream->client, &iter, &cs, &stream->err)) {
         goto cleanup;
      }
   } else if (stream->implicit_session) {
      /* If an implicit session was created before, and this cursor is now
       * being recreated after resuming, then use the same session as before. */
      cs = stream->implicit_session;
      if (!mongoc_client_session_append (cs, &command_opts, &stream->err)) {
         goto cleanup;
      }
   } else {
      /* Create an implicit session. This session lsid must be the same for the
       * agg command and the subsequent getMores. Thus, this implicit session is
       * passed as if it were an explicit session to
       * collection_read_command_with_opts and cursor_new_from_reply, but it is
       * still implicit and its lifetime is owned by this change_stream_t. */
      mongoc_session_opt_t *session_opts;
      session_opts = mongoc_session_opts_new ();
      mongoc_session_opts_set_causal_consistency (session_opts, false);
      /* returns NULL if sessions aren't supported. ignore errors. */
      cs = mongoc_client_start_session (stream->client, session_opts, NULL);
      stream->implicit_session = cs;
      mongoc_session_opts_destroy (session_opts);
      if (cs &&
          !mongoc_client_session_append (cs, &command_opts, &stream->err)) {
         goto cleanup;
      }
   }

   if (cs && !mongoc_client_session_append (cs, &getmore_opts, &stream->err)) {
      goto cleanup;
   }

   if (stream->read_concern && !bson_has_field (&command_opts, "readConcern")) {
      mongoc_read_concern_append (stream->read_concern, &command_opts);
   }

   /* even though serverId has already been set, still pass the read prefs.
    * they are necessary for OP_MSG if sending to a secondary. */
   if (!mongoc_client_read_command_with_opts (stream->client,
                                              stream->db,
                                              &command,
                                              stream->read_prefs,
                                              &command_opts,
                                              &reply,
                                              &stream->err)) {
      bson_destroy (&stream->err_doc);
      bson_copy_to (&reply, &stream->err_doc);
      bson_destroy (&reply);
      goto cleanup;
   }

   bson_append_bool (
      &getmore_opts, MONGOC_CURSOR_TAILABLE, MONGOC_CURSOR_TAILABLE_LEN, true);
   bson_append_bool (&getmore_opts,
                     MONGOC_CURSOR_AWAIT_DATA,
                     MONGOC_CURSOR_AWAIT_DATA_LEN,
                     true);

   /* maxTimeMS is only appended to getMores if these are set in cursor opts. */
   if (stream->max_await_time_ms > 0) {
      bson_append_int64 (&getmore_opts,
                         MONGOC_CURSOR_MAX_AWAIT_TIME_MS,
                         MONGOC_CURSOR_MAX_AWAIT_TIME_MS_LEN,
                         stream->max_await_time_ms);
   }

   if (stream->batch_size > 0) {
      bson_append_int32 (&getmore_opts,
                         MONGOC_CURSOR_BATCH_SIZE,
                         MONGOC_CURSOR_BATCH_SIZE_LEN,
                         stream->batch_size);
   }

   /* Change streams spec: "If neither startAtOperationTime nor resumeAfter are
    * specified, and the max wire version is >= 7, and the initial aggregate
    * command does not return a resumeToken (indicating no results), the
    * ChangeStream MUST save the operationTime from the initial aggregate
    * command when it returns." */
   if (bson_empty (&stream->resume_token) &&
       _mongoc_timestamp_empty (&stream->operation_time) &&
       max_wire_version >= 7 &&
       bson_iter_init_find (&iter, &reply, "operationTime")) {
      _mongoc_timestamp_set_from_bson (&stream->operation_time, &iter);
   }
   /* steals reply. */
   stream->cursor = mongoc_cursor_new_from_command_reply_with_opts (
      stream->client, &reply, &getmore_opts);

cleanup:
   bson_destroy (&command);
   bson_destroy (&command_opts);
   bson_destroy (&getmore_opts);
   return stream->err.code == 0;
}