static bool
txn_abort (mongoc_client_session_t *session, bson_t *reply, bson_error_t *error)
{
   bson_t cmd = BSON_INITIALIZER;
   bson_t opts = BSON_INITIALIZER;
   bson_error_t err_local;
   bson_error_t *err_ptr = error ? error : &err_local;
   bson_t reply_local = BSON_INITIALIZER;
   mongoc_write_err_type_t error_type;
   bool r = false;

   _mongoc_bson_init_if_set (reply);

   if (!mongoc_client_session_append (session, &opts, err_ptr)) {
      GOTO (done);
   }

   if (session->txn.opts.write_concern) {
      if (!mongoc_write_concern_append (session->txn.opts.write_concern,
                                        &opts)) {
         bson_set_error (err_ptr,
                         MONGOC_ERROR_TRANSACTION,
                         MONGOC_ERROR_TRANSACTION_INVALID_STATE,
                         "Invalid transaction write concern");
         GOTO (done);
      }
   }

   BSON_APPEND_INT32 (&cmd, "abortTransaction", 1);

   /* will be reinitialized by mongoc_client_write_command_with_opts */
   bson_destroy (&reply_local);
   r = mongoc_client_write_command_with_opts (
      session->client, "admin", &cmd, &opts, &reply_local, err_ptr);

   /* Transactions Spec: "Drivers MUST retry the commitTransaction command once
    * after it fails with a retryable error", same for abort */
   error_type = _mongoc_write_error_get_type (r, err_ptr, &reply_local);
   if (error_type == MONGOC_WRITE_ERR_RETRY) {
      bson_destroy (&reply_local);
      r = mongoc_client_write_command_with_opts (
         session->client, "admin", &cmd, &opts, &reply_local, err_ptr);
   }

   if (!r) {
      /* we won't return an error from abortTransaction, so warn */
      MONGOC_WARNING ("Error in abortTransaction: %s", err_ptr->message);
   }

done:
   bson_destroy (&reply_local);
   bson_destroy (&cmd);
   bson_destroy (&opts);

   return r;
}
static void
test_write_concern_append (void)
{
   mongoc_write_concern_t *wc;
   bson_t *cmd;

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

   /* cannot append invalid writeConcern */
   wc = NULL;
   assert (!mongoc_write_concern_append (wc, cmd));

   /* append valid writeConcern */
   wc = mongoc_write_concern_new ();
   mongoc_write_concern_set_w (wc, 1);
   assert (mongoc_write_concern_append (wc, cmd));

   ASSERT (match_bson (cmd,
                       tmp_bson ("{'foo': 1, 'writeConcern': {'w': 1}}"),
                       true));

   mongoc_write_concern_destroy (wc);
}
예제 #3
0
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;
}
int
main ()
{
   bson_t empty = BSON_INITIALIZER;
   const bson_t *doc;
   bson_t *to_insert = BCON_NEW ("x", BCON_INT32 (1));
   const bson_t *err_doc;
   bson_error_t error;
   const char *uri_string;
   mongoc_uri_t *uri;
   mongoc_client_t *client;
   mongoc_collection_t *coll;
   mongoc_change_stream_t *stream;
   mongoc_write_concern_t *wc = mongoc_write_concern_new ();
   bson_t opts = BSON_INITIALIZER;
   bool r;

   mongoc_init ();

   uri_string = "mongodb://"
                "localhost:27017,localhost:27018,localhost:"
                "27019/db?replicaSet=rs0";

   uri = mongoc_uri_new_with_error (uri_string, &error);
   if (!uri) {
      fprintf (stderr,
               "failed to parse URI: %s\n"
               "error message:       %s\n",
               uri_string,
               error.message);
      return EXIT_FAILURE;
   }

   client = mongoc_client_new_from_uri (uri);
   if (!client) {
      return EXIT_FAILURE;
   }

   coll = mongoc_client_get_collection (client, "db", "coll");
   stream = mongoc_collection_watch (coll, &empty, NULL);

   mongoc_write_concern_set_wmajority (wc, 10000);
   mongoc_write_concern_append (wc, &opts);
   r = mongoc_collection_insert_one (coll, to_insert, &opts, NULL, &error);
   if (!r) {
      fprintf (stderr, "Error: %s\n", error.message);
      return EXIT_FAILURE;
   }

   while (mongoc_change_stream_next (stream, &doc)) {
      char *as_json = bson_as_relaxed_extended_json (doc, NULL);
      fprintf (stderr, "Got document: %s\n", as_json);
      bson_free (as_json);
   }

   if (mongoc_change_stream_error_document (stream, &error, &err_doc)) {
      if (!bson_empty (err_doc)) {
         fprintf (stderr,
                  "Server Error: %s\n",
                  bson_as_relaxed_extended_json (err_doc, NULL));
      } else {
         fprintf (stderr, "Client Error: %s\n", error.message);
      }
      return EXIT_FAILURE;
   }

   bson_destroy (to_insert);
   mongoc_write_concern_destroy (wc);
   bson_destroy (&opts);
   mongoc_change_stream_destroy (stream);
   mongoc_collection_destroy (coll);
   mongoc_uri_destroy (uri);
   mongoc_client_destroy (client);
   mongoc_cleanup ();

   return EXIT_SUCCESS;
}
static bool
txn_finish (mongoc_client_session_t *session,
            mongoc_txn_intent_t intent,
            bson_t *reply,
            bson_error_t *error)
{
   const char *cmd_name;
   bson_t cmd = BSON_INITIALIZER;
   bson_t opts = BSON_INITIALIZER;
   bson_error_t err_local;
   bson_error_t *err_ptr = error ? error : &err_local;
   bson_t reply_local = BSON_INITIALIZER;
   mongoc_write_err_type_t error_type;
   bool r = false;

   _mongoc_bson_init_if_set (reply);

   cmd_name = (intent == TXN_COMMIT ? "commitTransaction" : "abortTransaction");

   if (!mongoc_client_session_append (session, &opts, err_ptr)) {
      GOTO (done);
   }

   if (session->txn.opts.write_concern) {
      if (!mongoc_write_concern_append (session->txn.opts.write_concern,
                                        &opts)) {
         bson_set_error (err_ptr,
                         MONGOC_ERROR_TRANSACTION,
                         MONGOC_ERROR_TRANSACTION_INVALID_STATE,
                         "Invalid transaction write concern");
         GOTO (done);
      }
   }

   BSON_APPEND_INT32 (&cmd, cmd_name, 1);

   /* will be reinitialized by mongoc_client_write_command_with_opts */
   bson_destroy (&reply_local);
   r = mongoc_client_write_command_with_opts (
      session->client, "admin", &cmd, &opts, &reply_local, err_ptr);

   /* Transactions Spec: "Drivers MUST retry the commitTransaction command once
    * after it fails with a retryable error", same for abort */
   error_type = _mongoc_write_error_get_type (r, err_ptr, &reply_local);
   if (error_type == MONGOC_WRITE_ERR_RETRY) {
      bson_destroy (&reply_local);
      r = mongoc_client_write_command_with_opts (
         session->client, "admin", &cmd, &opts, &reply_local, err_ptr);

      error_type = _mongoc_write_error_get_type (r, err_ptr, &reply_local);
   }

   /* Transactions Spec: "add the UnknownTransactionCommitResult error label
    * when commitTransaction fails with a network error, server selection
    * error, or write concern failed / timeout." */
   if (intent == TXN_COMMIT && reply) {
      if ((!r && err_ptr->domain == MONGOC_ERROR_SERVER_SELECTION) ||
          error_type == MONGOC_WRITE_ERR_RETRY ||
          error_type == MONGOC_WRITE_ERR_WRITE_CONCERN) {
         bson_copy_to_excluding_noinit (
            &reply_local, reply, "errorLabels", NULL);
         copy_labels_plus_unknown_commit_result (&reply_local, reply);
      } else {
         /* maintain invariants: reply & reply_local are valid until the end */
         bson_destroy (reply);
         bson_steal (reply, &reply_local);
         bson_init (&reply_local);
      }

   } else if (intent == TXN_ABORT && !r) {
      /* we won't return an error from abortTransaction, so warn */
      MONGOC_WARNING ("Error in %s: %s", cmd_name, err_ptr->message);
   }

done:
   bson_destroy (&reply_local);
   bson_destroy (&cmd);
   bson_destroy (&opts);
   return r;
}
static bool
txn_commit (mongoc_client_session_t *session,
            bool explicitly_retrying,
            bson_t *reply,
            bson_error_t *error)
{
   bson_t cmd = BSON_INITIALIZER;
   bson_t opts = BSON_INITIALIZER;
   bson_error_t err_local;
   bson_error_t *err_ptr = error ? error : &err_local;
   bson_t reply_local = BSON_INITIALIZER;
   mongoc_write_err_type_t error_type;
   bool r = false;
   bool retrying_after_error = false;
   mongoc_write_concern_t *retry_wc = NULL;

   _mongoc_bson_init_if_set (reply);

   BSON_APPEND_INT32 (&cmd, "commitTransaction", 1);

retry:
   if (!mongoc_client_session_append (session, &opts, err_ptr)) {
      GOTO (done);
   }

   /* Transactions Spec: "When commitTransaction is retried, either by the
    * driver's internal retry-once logic or explicitly by the user calling
    * commitTransaction again, drivers MUST apply w:majority to the write
    * concern of the commitTransaction command." */
   if (!retry_wc && (retrying_after_error || explicitly_retrying)) {
      retry_wc = create_commit_retry_wc (session->txn.opts.write_concern
                                            ? session->txn.opts.write_concern
                                            : session->client->write_concern);
   }

   if (retry_wc || session->txn.opts.write_concern) {
      if (!mongoc_write_concern_append (
             retry_wc ? retry_wc : session->txn.opts.write_concern, &opts)) {
         bson_set_error (err_ptr,
                         MONGOC_ERROR_TRANSACTION,
                         MONGOC_ERROR_TRANSACTION_INVALID_STATE,
                         "Invalid transaction write concern");
         GOTO (done);
      }
   }

   /* will be reinitialized by mongoc_client_write_command_with_opts */
   bson_destroy (&reply_local);
   r = mongoc_client_write_command_with_opts (
      session->client, "admin", &cmd, &opts, &reply_local, err_ptr);

   /* Transactions Spec: "Drivers MUST retry the commitTransaction command once
    * after it fails with a retryable error", same for abort */
   error_type = _mongoc_write_error_get_type (r, err_ptr, &reply_local);
   if (!retrying_after_error && error_type == MONGOC_WRITE_ERR_RETRY) {
      retrying_after_error = true; /* retry after error only once */
      bson_reinit (&opts);
      GOTO (retry);
   }

   /* Transactions Spec: "add the UnknownTransactionCommitResult error label
    * when commitTransaction fails with a network error, server selection
    * error, or write concern failed / timeout." */
   if (reply) {
      if ((!r && err_ptr->domain == MONGOC_ERROR_SERVER_SELECTION) ||
          error_type == MONGOC_WRITE_ERR_RETRY ||
          error_type == MONGOC_WRITE_ERR_WRITE_CONCERN) {
         bson_copy_to_excluding_noinit (
            &reply_local, reply, "errorLabels", NULL);
         copy_labels_plus_unknown_commit_result (&reply_local, reply);
      } else {
         /* maintain invariants: reply & reply_local are valid until the end */
         bson_destroy (reply);
         bson_steal (reply, &reply_local);
         bson_init (&reply_local);
      }
   }

done:
   bson_destroy (&reply_local);
   bson_destroy (&cmd);
   bson_destroy (&opts);

   if (retry_wc) {
      mongoc_write_concern_destroy (retry_wc);
   }

   return r;
}