diff --git a/core/impl/query.cxx b/core/impl/query.cxx index 7bfc30f1b..7d7013a32 100644 --- a/core/impl/query.cxx +++ b/core/impl/query.cxx @@ -191,7 +191,7 @@ build_query_request(query_options::built options) return request; } -couchbase::transactions::transaction_query_result +std::pair build_transaction_query_result(operations::query_response resp, std::error_code txn_ec /*defaults to 0*/) { if (resp.ctx.ec) { @@ -204,17 +204,19 @@ build_transaction_query_result(operations::query_response resp, std::error_code txn_ec = errc::transaction_op::not_set; } } - return { query_meta_data{ - std::move(resp.meta.request_id), - std::move(resp.meta.client_context_id), - map_status(resp.meta.status), - map_warnings(resp), - map_metrics(resp), - map_signature(resp), - map_profile(resp), - }, - map_rows(resp), - { txn_ec, build_context(resp) } }; + return { + { txn_ec, build_context(resp) }, + { query_meta_data{ + std::move(resp.meta.request_id), + std::move(resp.meta.client_context_id), + map_status(resp.meta.status), + map_warnings(resp), + map_metrics(resp), + map_signature(resp), + map_profile(resp), + }, + map_rows(resp) }, + }; } core::operations::query_request diff --git a/core/transactions.hxx b/core/transactions.hxx index 7bee41600..7923f79a7 100644 --- a/core/transactions.hxx +++ b/core/transactions.hxx @@ -157,7 +157,7 @@ class transactions : public couchbase::transactions::transactions couchbase::transactions::transaction_result run(const couchbase::transactions::transaction_options& config, logic&& code); - couchbase::transactions::transaction_result run( + std::pair run( ::couchbase::transactions::txn_logic&& code, const couchbase::transactions::transaction_options& cfg = couchbase::transactions::transaction_options()) override; diff --git a/core/transactions/attempt_context_impl.cxx b/core/transactions/attempt_context_impl.cxx index 6e551f61f..e3b17cc55 100644 --- a/core/transactions/attempt_context_impl.cxx +++ b/core/transactions/attempt_context_impl.cxx @@ -847,15 +847,15 @@ attempt_context_impl::handle_query_error(const core::operations::query_response& if (!resp.ctx.ec && !resp.meta.errors) { return {}; } - auto tx_resp = couchbase::core::impl::build_transaction_query_result(resp); + auto [tx_err, query_result] = couchbase::core::impl::build_transaction_query_result(resp); // TODO: look at ambiguous and unambiguous timeout errors vs the codes, etc... CB_ATTEMPT_CTX_LOG_TRACE( this, "handling query error {}, {} errors in meta_data", resp.ctx.ec.message(), resp.meta.errors ? "has" : "no"); if (resp.ctx.ec == couchbase::errc::common::ambiguous_timeout || resp.ctx.ec == couchbase::errc::common::unambiguous_timeout) { - return std::make_exception_ptr(query_attempt_expired(tx_resp.ctx())); + return std::make_exception_ptr(query_attempt_expired(tx_err)); } if (resp.ctx.ec == couchbase::errc::common::parsing_failure) { - return std::make_exception_ptr(query_parsing_failure(tx_resp.ctx())); + return std::make_exception_ptr(query_parsing_failure(tx_err)); } if (!resp.meta.errors) { // can't choose an error, map using the ec... @@ -883,16 +883,16 @@ attempt_context_impl::handle_query_error(const core::operations::query_response& transaction_operation_failed(FAIL_OTHER, "This couchbase server requires all queries use a scope.") .cause(FEATURE_NOT_AVAILABLE_EXCEPTION)); case 17004: - return std::make_exception_ptr(query_attempt_not_found(tx_resp.ctx())); + return std::make_exception_ptr(query_attempt_not_found(tx_err)); case 1080: case 17010: return std::make_exception_ptr(transaction_operation_failed(FAIL_EXPIRY, "transaction expired").expired()); case 17012: - return std::make_exception_ptr(document_exists(tx_resp.ctx())); + return std::make_exception_ptr(document_exists(tx_err)); case 17014: - return std::make_exception_ptr(document_not_found(tx_resp.ctx())); + return std::make_exception_ptr(document_not_found(tx_err)); case 17015: - return std::make_exception_ptr(query_cas_mismatch(tx_resp.ctx())); + return std::make_exception_ptr(query_cas_mismatch(tx_err)); } // For these errors, we will create a transaction_operation_failed from the info in it. @@ -922,7 +922,7 @@ attempt_context_impl::handle_query_error(const core::operations::query_response& } } - return { std::make_exception_ptr(op_exception(tx_resp.ctx())) }; + return { std::make_exception_ptr(op_exception(tx_err)) }; } void @@ -1071,22 +1071,22 @@ attempt_context_impl::do_core_query(const std::string& statement, return f.get(); } -couchbase::transactions::transaction_query_result_ptr +std::pair attempt_context_impl::do_public_query(const std::string& statement, const couchbase::transactions::transaction_query_options& opts, std::optional query_context) { try { auto result = do_core_query(statement, opts, query_context); - return std::make_shared(core::impl::build_transaction_query_result(result)); + return core::impl::build_transaction_query_result(result); } catch (const transaction_operation_failed& e) { - return std::make_shared(e.get_error_ctx()); + return { e.get_error_ctx(), {} }; } catch (const op_exception& qe) { - return std::make_shared(qe.ctx()); + return { qe.ctx(), {} }; } catch (...) { // should not be necessary, but just in case... transaction_op_error_context ctx(couchbase::errc::transaction_op::unknown); - return std::make_shared(ctx); + return { ctx, {} }; } } diff --git a/core/transactions/attempt_context_impl.hxx b/core/transactions/attempt_context_impl.hxx index 6ed9aac88..26eb29088 100644 --- a/core/transactions/attempt_context_impl.hxx +++ b/core/transactions/attempt_context_impl.hxx @@ -44,7 +44,7 @@ namespace couchbase::core::impl { -couchbase::transactions::transaction_query_result +std::pair build_transaction_query_result(operations::query_response resp, std::error_code ec = {}); core::operations::query_request @@ -87,14 +87,14 @@ class attempt_context_impl // transaction_context needs access to the two functions below friend class transaction_context; - couchbase::transactions::transaction_get_result_ptr insert_raw(const couchbase::collection& coll, - const std::string& id, - std::vector content) override + std::pair + insert_raw(const couchbase::collection& coll, const std::string& id, std::vector content) override { return wrap_call_for_public_api([this, coll, &id, &content]() -> transaction_get_result { return insert_raw({ coll.bucket_name(), coll.scope_name(), coll.name(), id }, content); }); } + transaction_get_result insert_raw(const core::document_id& id, const std::vector& content) override; void insert_raw(const collection& coll, std::string id, @@ -110,17 +110,20 @@ class attempt_context_impl void insert_raw(const core::document_id& id, const std::vector& content, Callback&& cb) override; transaction_get_result replace_raw(const transaction_get_result& document, const std::vector& content) override; - couchbase::transactions::transaction_get_result_ptr replace_raw(const couchbase::transactions::transaction_get_result_ptr doc, - std::vector content) override + + std::pair replace_raw( + const couchbase::transactions::transaction_get_result& doc, + std::vector content) override { return wrap_call_for_public_api( - [this, doc, &content]() -> transaction_get_result { return replace_raw(dynamic_cast(*doc), content); }); + [this, doc, &content]() -> transaction_get_result { return replace_raw(transaction_get_result(doc), content); }); } - void replace_raw(couchbase::transactions::transaction_get_result_ptr doc, + + void replace_raw(couchbase::transactions::transaction_get_result doc, std::vector content, couchbase::transactions::async_result_handler&& handler) override { - replace_raw(dynamic_cast(*doc), + replace_raw(core::transactions::transaction_get_result(doc), content, [this, handler = std::move(handler)](std::exception_ptr err, std::optional res) mutable { wrap_callback_for_async_public_api(err, res, std::move(handler)); @@ -323,23 +326,28 @@ class attempt_context_impl ~attempt_context_impl() override; transaction_get_result get(const core::document_id& id) override; - couchbase::transactions::transaction_get_result_ptr get(const couchbase::collection& coll, const std::string& id) override + std::pair get( + const couchbase::collection& coll, + const std::string& id) override { - return wrap_call_for_public_api([this, coll, id]() mutable -> transaction_get_result { + auto [ctx, res] = wrap_call_for_public_api([this, coll, id]() mutable -> transaction_get_result { auto ret = get_optional({ coll.bucket_name(), coll.scope_name(), coll.name(), id }); if (ret) { - return *ret; + return ret.value(); } - return { transaction_op_error_context{ errc::transaction_op::document_not_found_exception } }; + return {}; }); + if (!ctx.ec() && res.cas().empty()) { + return { transaction_op_error_context{ errc::transaction_op::document_not_found_exception }, res }; + } + return { ctx, res }; } void get(const couchbase::collection& coll, std::string id, couchbase::transactions::async_result_handler&& handler) override { get_optional({ coll.bucket_name(), coll.scope_name(), coll.name(), std::move(id) }, [this, handler = std::move(handler)](std::exception_ptr err, std::optional res) mutable { if (!res) { - return handler(std::make_shared( - transaction_op_error_context{ errc::transaction_op::document_not_found_exception })); + return handler(transaction_op_error_context{ errc::transaction_op::document_not_found_exception }, {}); } return wrap_callback_for_async_public_api(err, res, std::move(handler)); }); @@ -350,14 +358,14 @@ class attempt_context_impl void get_optional(const core::document_id& id, Callback&& cb) override; void remove(const transaction_get_result& document) override; - couchbase::transaction_op_error_context remove(couchbase::transactions::transaction_get_result_ptr doc) override + couchbase::transaction_op_error_context remove(const couchbase::transactions::transaction_get_result& doc) override { - return wrap_void_call_for_public_api([this, doc]() { remove(dynamic_cast(*doc)); }); + return wrap_void_call_for_public_api([this, doc]() { remove(transaction_get_result(doc)); }); } void remove(const transaction_get_result& document, VoidCallback&& cb) override; - void remove(couchbase::transactions::transaction_get_result_ptr doc, couchbase::transactions::async_err_handler&& handler) override + void remove(couchbase::transactions::transaction_get_result doc, couchbase::transactions::async_err_handler&& handler) override { - remove(dynamic_cast(*doc), [this, handler = std::move(handler)](std::exception_ptr e) mutable { + remove(transaction_get_result(doc), [this, handler = std::move(handler)](std::exception_ptr e) mutable { wrap_err_callback_for_async_api(e, std::move(handler)); }); }; @@ -366,9 +374,10 @@ class attempt_context_impl const couchbase::transactions::transaction_query_options& options, std::optional query_context) override; - couchbase::transactions::transaction_query_result_ptr do_public_query(const std::string& statement, - const couchbase::transactions::transaction_query_options& opts, - std::optional query_context) override; + std::pair do_public_query( + const std::string& statement, + const couchbase::transactions::transaction_query_options& opts, + std::optional query_context) override; void query(const std::string& statement, const couchbase::transactions::transaction_query_options& options, @@ -389,17 +398,16 @@ class attempt_context_impl try { std::rethrow_exception(err); } catch (const transaction_operation_failed& e) { - return handler(std::make_shared(e.get_error_ctx())); + return handler(e.get_error_ctx(), {}); } catch (const op_exception& ex) { - return handler(std::make_shared(ex.ctx())); + return handler(ex.ctx(), {}); } catch (...) { // just in case... - return handler(std::make_shared( - transaction_op_error_context(couchbase::errc::transaction_op::unknown))); + return handler(transaction_op_error_context(couchbase::errc::transaction_op::unknown), {}); } } - handler( - std::make_shared(core::impl::build_transaction_query_result(*resp))); + auto [ctx, res] = core::impl::build_transaction_query_result(*resp); + handler(ctx, res); }); } @@ -527,17 +535,18 @@ class attempt_context_impl error_class ec, const std::string& message); - couchbase::transactions::transaction_get_result_ptr wrap_call_for_public_api(std::function&& handler) + std::pair wrap_call_for_public_api( + std::function&& handler) { try { - return std::make_shared(handler()); + return { {}, handler().to_public_result() }; } catch (const transaction_operation_failed& e) { - return std::make_shared(e.get_error_ctx()); + return { e.get_error_ctx(), {} }; } catch (const op_exception& ex) { - return std::make_shared(ex.ctx()); + return { ex.ctx(), {} }; } catch (...) { // the handler should catch everything else, but just in case... - return std::make_shared(transaction_op_error_context(errc::transaction_op::unknown)); + return { transaction_op_error_context(errc::transaction_op::unknown), {} }; } } @@ -554,25 +563,26 @@ class attempt_context_impl } } - void wrap_callback_for_async_public_api(std::exception_ptr err, - std::optional res, - std::function&& cb) + void wrap_callback_for_async_public_api( + std::exception_ptr err, + std::optional res, + std::function&& cb) { if (res) { - return cb(std::make_shared(*res)); + return cb({}, res->to_public_result()); } if (err) { try { std::rethrow_exception(err); } catch (const op_exception& e) { - return cb(std::make_shared(e.ctx())); + return cb(e.ctx(), {}); } catch (const transaction_operation_failed& e) { - return cb(std::make_shared(e.get_error_ctx())); + return cb(e.get_error_ctx(), {}); } catch (...) { - return cb(std::make_shared(transaction_op_error_context(errc::transaction_op::unknown))); + return cb(transaction_op_error_context(errc::transaction_op::unknown), {}); } } - return cb(std::make_shared(transaction_op_error_context(errc::transaction_op::unknown))); + return cb(transaction_op_error_context(errc::transaction_op::unknown), {}); } void wrap_err_callback_for_async_api(std::exception_ptr err, std::function&& cb) diff --git a/core/transactions/exceptions.cxx b/core/transactions/exceptions.cxx index 0a32d5de3..634020e3a 100644 --- a/core/transactions/exceptions.cxx +++ b/core/transactions/exceptions.cxx @@ -103,7 +103,6 @@ transaction_exception::transaction_exception(const std::runtime_error& cause, co if (nullptr != txn_op) { cause_ = txn_op->cause(); } - result_.ctx = error_context(); } errc::transaction_op diff --git a/core/transactions/exceptions.hxx b/core/transactions/exceptions.hxx index 4cd4dc752..30b7ada0b 100644 --- a/core/transactions/exceptions.hxx +++ b/core/transactions/exceptions.hxx @@ -88,9 +88,9 @@ class transaction_exception : public std::runtime_error * * @returns Internal state of transaction. */ - ::couchbase::transactions::transaction_result get_transaction_result() const + std::pair get_transaction_result() const { - return { result_.transaction_id, result_.unstaging_complete, error_context() }; + return { error_context(), { result_.transaction_id, result_.unstaging_complete } }; } /** diff --git a/core/transactions/internal/transaction_context.hxx b/core/transactions/internal/transaction_context.hxx index 06be1d617..9a6d2c13a 100644 --- a/core/transactions/internal/transaction_context.hxx +++ b/core/transactions/internal/transaction_context.hxx @@ -119,7 +119,7 @@ class transaction_context [[nodiscard]] ::couchbase::transactions::transaction_result get_transaction_result() const { - return couchbase::transactions::transaction_result{ transaction_id(), current_attempt().state == attempt_state::COMPLETED, {} }; + return couchbase::transactions::transaction_result{ transaction_id(), current_attempt().state == attempt_state::COMPLETED }; } void new_attempt_context() { diff --git a/core/transactions/transaction_get_result.hxx b/core/transactions/transaction_get_result.hxx index 1f48d3ac4..b396b0989 100644 --- a/core/transactions/transaction_get_result.hxx +++ b/core/transactions/transaction_get_result.hxx @@ -33,12 +33,13 @@ struct result; * @brief Encapsulates results of an individual transaction operation * */ -class transaction_get_result : public couchbase::transactions::transaction_get_result +class transaction_get_result { private: couchbase::cas cas_{}; core::document_id document_id_{}; transaction_links links_{}; + std::vector content_; /** This is needed for provide {BACKUP-FIELDS}. It is only needed from the get to the staged mutation, hence Optional. */ std::optional metadata_{}; @@ -55,11 +56,11 @@ class transaction_get_result : public couchbase::transactions::transaction_get_r transaction_get_result(const transaction_get_result& doc) = default; transaction_get_result(transaction_get_result&& doc) = default; - /** @internal */ + /* transaction_get_result(const transaction_op_error_context& ctx) : couchbase::transactions::transaction_get_result(ctx) { - } + }*/ /** @internal */ template @@ -68,18 +69,28 @@ class transaction_get_result : public couchbase::transactions::transaction_get_r std::uint64_t cas, transaction_links links, std::optional metadata) - : couchbase::transactions::transaction_get_result(content) - , cas_(cas) + : cas_(cas) , document_id_(id) , links_(std::move(links)) + , content_(std::move(content)) , metadata_(std::move(metadata)) { } - /** @internal */ + transaction_get_result(const couchbase::transactions::transaction_get_result& res) + : cas_(res.cas()) + , document_id_(res.bucket(), res.scope(), res.collection(), res.key()) + , content_(res.content()) + { + } + + couchbase::transactions::transaction_get_result to_public_result() + { + return { document_id_.bucket(), document_id_.scope(), document_id_.collection(), document_id_.key(), cas_, std::move(content_) }; + } + transaction_get_result(core::document_id id, const tao::json::value& json) - : couchbase::transactions::transaction_get_result() - , document_id_(id) + : document_id_(id) , links_(json) , metadata_(json.optional("scas").value_or("")) { @@ -90,7 +101,7 @@ class transaction_get_result : public couchbase::transactions::transaction_get_r cas_ = couchbase::cas(stoull(cas->as())); } if (const auto* doc = json.find("doc"); doc != nullptr) { - value_ = core::utils::json::generate_binary(doc->get_object()); + content_ = core::utils::json::generate_binary(doc->get_object()); } } @@ -98,7 +109,7 @@ class transaction_get_result : public couchbase::transactions::transaction_get_r { if (this != &o) { document_id_ = o.document_id_; - value_ = o.value_; + content_ = o.content_; cas_ = o.cas_; links_ = o.links_; } @@ -139,7 +150,7 @@ class transaction_get_result : public couchbase::transactions::transaction_get_r { if (this != &other) { document_id_ = other.document_id_; - value_ = other.value_; + content_ = other.content_; cas_ = other.cas_; links_ = other.links_; } @@ -156,22 +167,22 @@ class transaction_get_result : public couchbase::transactions::transaction_get_r return document_id_; } - [[nodiscard]] const std::string& bucket() const override + [[nodiscard]] const std::string& bucket() const { return document_id_.bucket(); } - [[nodiscard]] const std::string& key() const override + [[nodiscard]] const std::string& key() const { return document_id_.key(); } - [[nodiscard]] const std::string& scope() const override + [[nodiscard]] const std::string& scope() const { return document_id_.scope(); } - [[nodiscard]] const std::string& collection() const override + [[nodiscard]] const std::string& collection() const { return document_id_.collection(); } @@ -207,16 +218,11 @@ class transaction_get_result : public couchbase::transactions::transaction_get_r * * @return the CAS for this document. */ - [[nodiscard]] couchbase::cas cas() const override + [[nodiscard]] couchbase::cas cas() const { return cas_; } - void ctx(transaction_op_error_context ctx) - { - ctx_ = std::move(ctx); - } - /** @internal */ template friend OStream& operator<<(OStream& os, const transaction_get_result document) @@ -225,6 +231,44 @@ class transaction_get_result : public couchbase::transactions::transaction_get_r << "}"; return os; } + + /** + * Content of the document. + * + * @return content of the document. + */ + template + [[nodiscard]] Content content() const + { + return codec::tao_json_serializer::deserialize(content_); + } + + /** + * Content of the document as raw byte vector + * + * @return content + */ + [[nodiscard]] const std::vector& content() const + { + return content_; + } + /** + * Copy content into document + * @param content + */ + void content(std::vector content) + { + content_ = std::move(content); + } + /** + * Move content into document + * + * @param content + */ + void content(std::vector&& content) + { + content_ = std::move(content); + } }; } // namespace couchbase::core::transactions diff --git a/core/transactions/transactions.cxx b/core/transactions/transactions.cxx index eb3c8b354..5f971d158 100644 --- a/core/transactions/transactions.cxx +++ b/core/transactions/transactions.cxx @@ -128,11 +128,11 @@ transactions::run(const couchbase::transactions::transaction_options& config, lo return wrap_run(*this, config, max_attempts_, std::move(code)); } -couchbase::transactions::transaction_result +std::pair transactions::run(couchbase::transactions::txn_logic&& code, const couchbase::transactions::transaction_options& config) { try { - return wrap_run(*this, config, max_attempts_, std::move(code)); + return { {}, wrap_run(*this, config, max_attempts_, std::move(code)) }; } catch (const transaction_exception& e) { // get transaction_error_context from e and return it in the transaction_result return e.get_transaction_result(); @@ -159,9 +159,10 @@ transactions::run(couchbase::transactions::async_txn_logic&& code, std::thread([this, config, code = std::move(code), cb = std::move(cb)]() { try { auto result = wrap_run(*this, config, max_attempts_, std::move(code)); - return cb(result); + return cb({}, result); } catch (const transaction_exception& e) { - return cb(e.get_transaction_result()); + auto [ctx, res] = e.get_transaction_result(); + return cb(ctx, res); } }).detach(); } diff --git a/couchbase/transactions.hxx b/couchbase/transactions.hxx index 4a97e3a37..7acf5f9f4 100644 --- a/couchbase/transactions.hxx +++ b/couchbase/transactions.hxx @@ -28,13 +28,14 @@ namespace couchbase::transactions { using txn_logic = std::function; using async_txn_logic = std::function; -using async_txn_complete_logic = std::function; +using async_txn_complete_logic = std::function; class transactions { public: virtual ~transactions() = default; - virtual transaction_result run(txn_logic&& logic, const transaction_options& cfg = transaction_options()) = 0; + virtual std::pair run(txn_logic&& logic, + const transaction_options& cfg = transaction_options()) = 0; virtual void run(async_txn_logic&& logic, async_txn_complete_logic&& complete_callback, const transaction_options& cfg = transaction_options()) = 0; diff --git a/couchbase/transactions/async_attempt_context.hxx b/couchbase/transactions/async_attempt_context.hxx index 7b7b4e8a6..8bf439e50 100644 --- a/couchbase/transactions/async_attempt_context.hxx +++ b/couchbase/transactions/async_attempt_context.hxx @@ -21,33 +21,33 @@ namespace couchbase::transactions { -using async_result_handler = std::function; -using async_query_handler = std::function; +using async_result_handler = std::function; +using async_query_handler = std::function; using async_err_handler = std::function; class async_attempt_context { public: virtual void get(const collection& coll, std::string id, async_result_handler&& handler) = 0; - virtual void remove(transaction_get_result_ptr doc, async_err_handler&& handler) = 0; + virtual void remove(transaction_get_result doc, async_err_handler&& handler) = 0; template void insert(const collection& coll, std::string id, Content&& content, async_result_handler&& handler) { if constexpr (std::is_same_v>) { - return insert_raw(content, id, content, std::move(handler)); + return insert_raw(std::forward>(content), std::move(id), content, std::move(handler)); } else { - return insert_raw(coll, id, codec::tao_json_serializer::serialize(content), std::move(handler)); + return insert_raw(coll, std::move(id), codec::tao_json_serializer::serialize(content), std::move(handler)); } } template - void replace(transaction_get_result_ptr doc, Content&& content, async_result_handler&& handler) + void replace(transaction_get_result doc, Content&& content, async_result_handler&& handler) { if constexpr (std::is_same_v>) { - return replace_raw(doc, content, std::move(handler)); + return replace_raw(std::move(doc), std::forward>(content), std::move(handler)); } else { - return replace_raw(doc, codec::tao_json_serializer::serialize(content), std::move(handler)); + return replace_raw(std::move(doc), codec::tao_json_serializer::serialize(content), std::move(handler)); } } @@ -63,13 +63,13 @@ class async_attempt_context void query(std::string statement, async_query_handler&& handler) { - return query(statement, {}, {}, std::move(handler)); + return query(std::move(statement), {}, std::move(handler)); } virtual ~async_attempt_context() = default; protected: virtual void insert_raw(const collection& coll, std::string id, std::vector content, async_result_handler&& handler) = 0; - virtual void replace_raw(transaction_get_result_ptr doc, std::vector content, async_result_handler&& handler) = 0; + virtual void replace_raw(transaction_get_result doc, std::vector content, async_result_handler&& handler) = 0; virtual void query(std::string statement, transaction_query_options opts, std::optional query_context, diff --git a/couchbase/transactions/attempt_context.hxx b/couchbase/transactions/attempt_context.hxx index 41b9a4480..c8d34cc5c 100644 --- a/couchbase/transactions/attempt_context.hxx +++ b/couchbase/transactions/attempt_context.hxx @@ -24,10 +24,13 @@ namespace couchbase::transactions class attempt_context { public: - virtual transaction_get_result_ptr get(const couchbase::collection& coll, const std::string& id) = 0; + virtual std::pair get(const couchbase::collection& coll, + const std::string& id) = 0; template - transaction_get_result_ptr insert(const couchbase::collection& coll, const std::string& id, const Content& content) + std::pair insert(const couchbase::collection& coll, + const std::string& id, + const Content& content) { if constexpr (std::is_same_v>) { return insert_raw(content, id, content); @@ -37,7 +40,7 @@ class attempt_context } template - transaction_get_result_ptr replace(const transaction_get_result_ptr doc, const Content& content) + std::pair replace(const transaction_get_result& doc, const Content& content) { if constexpr (std::is_same_v>) { return replace_raw(doc, content); @@ -46,28 +49,31 @@ class attempt_context } } - virtual transaction_op_error_context remove(transaction_get_result_ptr doc) = 0; + virtual transaction_op_error_context remove(const transaction_get_result& doc) = 0; - transaction_query_result_ptr query(const std::string& statement, const transaction_query_options& options = {}) + std::pair query(const std::string& statement, + const transaction_query_options& options = {}) { return do_public_query(statement, options, {}); } - transaction_query_result_ptr query(const scope& scope, std::string& statement, const transaction_query_options& opts = {}) + std::pair query(const scope& scope, + const std::string& statement, + const transaction_query_options& opts = {}) { - auto new_opts = opts; return do_public_query(statement, opts, fmt::format("{}.{}", scope.bucket_name(), scope.name())); } virtual ~attempt_context() = default; protected: - virtual transaction_get_result_ptr replace_raw(const transaction_get_result_ptr doc, std::vector content) = 0; - virtual transaction_get_result_ptr insert_raw(const couchbase::collection& coll, - const std::string& id, - std::vector content) = 0; - virtual transaction_query_result_ptr do_public_query(const std::string& statement, - const transaction_query_options& options, - std::optional query_context) = 0; + virtual std::pair replace_raw(const transaction_get_result& doc, + std::vector content) = 0; + virtual std::pair insert_raw(const couchbase::collection& coll, + const std::string& id, + std::vector content) = 0; + virtual std::pair do_public_query(const std::string& statement, + const transaction_query_options& options, + std::optional query_context) = 0; }; } // namespace couchbase::transactions diff --git a/couchbase/transactions/transaction_get_result.hxx b/couchbase/transactions/transaction_get_result.hxx index 749bd436c..5ea7f5e89 100644 --- a/couchbase/transactions/transaction_get_result.hxx +++ b/couchbase/transactions/transaction_get_result.hxx @@ -21,31 +21,39 @@ #include #include #include +#include #include namespace couchbase::transactions { -class transaction_get_result +class transaction_get_result : public result { protected: - std::vector value_{}; - transaction_op_error_context ctx_{}; + std::string bucket_{}; + std::string scope_{}; + std::string collection_{}; + std::string key_{}; + std::vector content_{}; + public: transaction_get_result() = default; - transaction_get_result(std::vector content) - : value_(content) - { - } - transaction_get_result(const transaction_op_error_context& ctx) - : ctx_(ctx) + transaction_get_result(std::string bucket, + std::string scope, + std::string collection, + std::string key, + couchbase::cas cas, + std::vector content) + : result(cas) + , bucket_(std::move(bucket)) + , scope_(std::move(scope)) + , collection_(std::move(collection)) + , key_(std::move(key)) + , content_(std::move(content)) { } - virtual ~transaction_get_result() = default; - - public: /** * Content of the document. * @@ -54,7 +62,7 @@ class transaction_get_result template [[nodiscard]] Content content() const { - return codec::tao_json_serializer::deserialize(value_); + return codec::tao_json_serializer::deserialize(content_); } /** @@ -64,37 +72,62 @@ class transaction_get_result */ [[nodiscard]] const std::vector& content() const { - return value_; + return content_; } - - // TODO: move to core - void content(const std::vector& content) + /** + * Copy content into document + * @param content + */ + void content(std::vector content) { - value_ = content; + content_ = std::move(content); } - - [[nodiscard]] const transaction_op_error_context& ctx() const + /** + * Move content into document + * + * @param content + */ + void content(std::vector&& content) { - return ctx_; + content_ = std::move(content); } /** - * @brief Get document id. + * Get document id. * * @return the id of this document. */ - [[nodiscard]] virtual const std::string& key() const = 0; + [[nodiscard]] const std::string key() const + { + return key_; + } /** - * @brief Get document CAS. + * Get the name of the bucket this document is in. * - * @return the CAS for this document. + * @return name of the bucket which contains the document. */ - [[nodiscard]] virtual couchbase::cas cas() const = 0; - - [[nodiscard]] virtual const std::string& bucket() const = 0; - [[nodiscard]] virtual const std::string& scope() const = 0; - [[nodiscard]] virtual const std::string& collection() const = 0; + [[nodiscard]] const std::string bucket() const + { + return bucket_; + } + /** + * Get the name of the scope this document is in. + * + * @return name of the scope which contains the document. + */ + [[nodiscard]] const std::string scope() const + { + return scope_; + } + /** + * Get the name of the collection this document is in. + * + * @return name of the collection which contains the document. + */ + [[nodiscard]] const std::string collection() const + { + return collection_; + } }; -using transaction_get_result_ptr = std::shared_ptr; } // namespace couchbase::transactions diff --git a/couchbase/transactions/transaction_query_result.hxx b/couchbase/transactions/transaction_query_result.hxx index 8800a9944..e678826ef 100644 --- a/couchbase/transactions/transaction_query_result.hxx +++ b/couchbase/transactions/transaction_query_result.hxx @@ -23,33 +23,14 @@ namespace couchbase::transactions class transaction_query_result : public query_result { public: - transaction_query_result(query_meta_data meta_data, std::vector rows, transaction_op_error_context ctx) + transaction_query_result(query_meta_data meta_data, std::vector rows) : query_result(std::move(meta_data), std::move(rows)) - , ctx_(std::move(ctx)) { } - transaction_query_result(query_meta_data meta_data, std::vector rows, query_error_context ctx) - : query_result(std::move(meta_data), std::move(rows)) - , ctx_({}, std::move(ctx)) - { - } - - explicit transaction_query_result(transaction_op_error_context ctx) + transaction_query_result() : query_result() - , ctx_(std::move(ctx)) { } - - [[nodiscard]] const transaction_op_error_context& ctx() const - { - return ctx_; - } - - private: - transaction_op_error_context ctx_{}; }; - -// convenient to have a shared ptr for results -using transaction_query_result_ptr = std::shared_ptr; } // namespace couchbase::transactions diff --git a/couchbase/transactions/transaction_result.hxx b/couchbase/transactions/transaction_result.hxx index a812aba1e..8917f7c98 100644 --- a/couchbase/transactions/transaction_result.hxx +++ b/couchbase/transactions/transaction_result.hxx @@ -30,6 +30,5 @@ namespace couchbase::transactions struct transaction_result { std::string transaction_id; bool unstaging_complete; - transaction_error_context ctx; }; } // namespace couchbase::transactions diff --git a/examples/async_game_server.cxx b/examples/async_game_server.cxx index eb32c7df6..c613f8b3a 100644 --- a/examples/async_game_server.cxx +++ b/examples/async_game_server.cxx @@ -18,8 +18,6 @@ #include #include -#include - #include #include @@ -136,28 +134,27 @@ class GameServer return experience / 100; } - std::future player_hits_monster(int damage, - const couchbase::collection& collection, - const std::string& player_id, - const std::string& monster_id, - std::atomic& exists) + std::future> player_hits_monster( + int damage, + const couchbase::collection& collection, + const std::string& player_id, + const std::string& monster_id, + std::atomic& exists) { - auto barrier = std::make_shared>(); + auto barrier = std::make_shared>>(); auto f = barrier->get_future(); transactions_->run( [this, damage, collection, player_id, monster_id, &exists](async_attempt_context& ctx) { ctx.get( collection, monster_id, - [&ctx, this, collection, monster_id, player_id, &exists, damage = std::move(damage)](transaction_get_result_ptr monster) { - if (monster->ctx().ec()) { - if (monster->ctx().ec() == couchbase::errc::transaction_op::document_not_found_exception) { - std::cout << "monster no longer exists" << std::endl; - exists = false; - return; - } + [&ctx, this, collection, monster_id, player_id, &exists, damage = std::move(damage)](auto e, auto monster) { + if (e.ec() == couchbase::errc::transaction_op::document_not_found_exception) { + std::cout << "monster no longer exists" << std::endl; + exists = false; + return; } - auto monster_body = monster->content(); + auto monster_body = monster.template content(); auto monster_hitpoints = monster_body.hitpoints; auto monster_new_hitpoints = monster_hitpoints - damage; @@ -166,46 +163,45 @@ class GameServer if (monster_new_hitpoints <= 0) { // Monster is killed. The remove is just for demoing, and a more realistic examples would set a "dead" flag or // similar. - ctx.remove(monster, [](couchbase::transaction_op_error_context e) { + ctx.remove(monster, [](auto e) { if (e.ec()) { std::cout << "error removing monster: " << e.ec().message() << std::endl; } }); // also, in parallel, get/update player - ctx.get( - collection, player_id, [&ctx, player_id, monster_id, monster_body, this](transaction_get_result_ptr player) { - if (player->ctx().ec()) { - std::cout << "error getting player: " << player->ctx().ec().message() << std::endl; - return; - } - const Player& player_body = player->content(); - - // the player earns experience for killing the monster - int experience_for_killing_monster = monster_body.experience_when_killed; - int player_experience = player_body.experience; - int player_new_experience = player_experience + experience_for_killing_monster; - int player_new_level = calculate_level_for_experience(player_new_experience); - - std::cout << "Monster " << monster_id << " was killed. Player " << player_id << " gains " - << experience_for_killing_monster << " experience, now has level " << player_new_level << std::endl; - - Player player_new_body = player_body; - player_new_body.experience = player_new_experience; - player_new_body.level = player_new_level; - ctx.replace(player, player_new_body, [](transaction_get_result_ptr res) { - if (res->ctx().ec()) { - std::cout << "Error updating player :" << res->ctx().ec().message() << std::endl; - } - }); - }); + ctx.get(collection, player_id, [&ctx, player_id, monster_id, monster_body, this](auto e, auto player) { + if (e.ec()) { + std::cout << "error getting player: " << e.ec().message() << std::endl; + return; + } + const Player& player_body = player.template content(); + + // the player earns experience for killing the monster + int experience_for_killing_monster = monster_body.experience_when_killed; + int player_experience = player_body.experience; + int player_new_experience = player_experience + experience_for_killing_monster; + int player_new_level = calculate_level_for_experience(player_new_experience); + + std::cout << "Monster " << monster_id << " was killed. Player " << player_id << " gains " + << experience_for_killing_monster << " experience, now has level " << player_new_level << std::endl; + + Player player_new_body = player_body; + player_new_body.experience = player_new_experience; + player_new_body.level = player_new_level; + ctx.replace(player, player_new_body, [](auto e, auto) { + if (e.ec()) { + std::cout << "Error updating player :" << e.ec().message() << std::endl; + } + }); + }); } else { std::cout << "Monster " << monster_id << " is damaged but alive" << std::endl; Monster monster_new_body = monster_body; monster_new_body.hitpoints = monster_new_hitpoints; - ctx.replace(monster, monster_new_body, [monster_new_body](transaction_get_result_ptr res) { - if (res->ctx().ec()) { - std::cout << "Error updating monster :" << res->ctx().ec().message() << std::endl; + ctx.replace(monster, monster_new_body, [monster_new_body](auto e, auto res) { + if (e.ec()) { + std::cout << "Error updating monster :" << e.ec().message() << std::endl; } else { auto body = couchbase::codec::tao_json_serializer::serialize(monster_new_body); std::cout << "Monster body updated to :" << std::string(reinterpret_cast(&body.front()), body.size()) @@ -215,7 +211,9 @@ class GameServer } }); }, - [barrier](transaction_result res) { barrier->set_value(res); }); + [barrier](auto err, auto res) { + barrier->set_value({ err, res }); + }); return f; } }; @@ -283,12 +281,17 @@ main() for (int i = 0; i < NUM_THREADS; i++) { threads.emplace_back([&rand, player_id, collection, monster_id, &monster_exists, &game_server]() { while (monster_exists.load()) { - std::cout << "[thread " << std::this_thread::get_id() << "]Monster exists -- lets hit it!" << std::endl; - auto res = game_server.player_hits_monster(rand() % 80, collection, player_id, monster_id, monster_exists).get(); - if (res.unstaging_complete) { - std::cout << "[thread " << std::this_thread::get_id() << "] success" << std::endl; - } else { - std::cout << "[thread " << std::this_thread::get_id() << "] " << res.ctx.ec().message() << std::endl; + try { + std::cout << "[thread " << std::this_thread::get_id() << "]Monster exists -- lets hit it!" << std::endl; + + auto [err, res] = game_server.player_hits_monster(rand() % 80, collection, player_id, monster_id, monster_exists).get(); + if (!err.ec()) { + std::cout << "[thread " << std::this_thread::get_id() << "] success" << std::endl; + } else { + std::cout << "[thread " << std::this_thread::get_id() << "] " << err.ec().message() << std::endl; + } + } catch (const std::exception& e) { + std::cout << "[thread " << std::this_thread::get_id() << "] got exception " << e.what() << std::endl; } } }); @@ -300,7 +303,6 @@ main() t.join(); } } - // close the cluster... cluster.close(); diff --git a/examples/game_server.cxx b/examples/game_server.cxx index ea51c23e9..7e85d2622 100644 --- a/examples/game_server.cxx +++ b/examples/game_server.cxx @@ -141,14 +141,14 @@ class GameServer const std::string& monster_id, std::atomic& exists) { - auto result = transactions_->run([&](attempt_context& ctx) { - auto monster = ctx.get(collection, monster_id); - if (monster->ctx().ec() == couchbase::errc::transaction_op::document_not_found_exception) { + auto [err, result] = transactions_->run([&](attempt_context& ctx) { + auto [e, monster] = ctx.get(collection, monster_id); + if (e.ec() == couchbase::errc::transaction_op::document_not_found_exception) { std::cout << "monster no longer exists" << std::endl; exists = false; return; } - const Monster& monster_body = monster->content(); + const Monster& monster_body = monster.content(); int monster_hitpoints = monster_body.hitpoints; int monster_new_hitpoints = monster_hitpoints - damage_; @@ -156,8 +156,8 @@ class GameServer std::cout << "Monster " << monster_id << " had " << monster_hitpoints << " hitpoints, took " << damage_ << " damage, now has " << monster_new_hitpoints << " hitpoints" << std::endl; - auto player = ctx.get(collection, player_id); - if (player->ctx().ec()) { + auto [e2, player] = ctx.get(collection, player_id); + if (e2.ec()) { // rollback throw std::runtime_error(fmt::format("error getting player {}", player_id)); } @@ -166,7 +166,7 @@ class GameServer // Monster is killed. The remove is just for demoing, and a more realistic examples would set a "dead" flag or similar. ctx.remove(monster); - const Player& player_body = player->content(); + const Player& player_body = player.content(); // the player earns experience for killing the monster int experience_for_killing_monster = monster_body.experience_when_killed; @@ -189,9 +189,8 @@ class GameServer ctx.replace(monster, monster_new_body); } }); - if (result.ctx.ec()) { - std::cout << "txn error during player_hits_monster: " << result.ctx.ec().message() << ", " << result.ctx.cause().message() - << std::endl; + if (err.ec()) { + std::cout << "txn error during player_hits_monster: " << err.ec().message() << ", " << err.cause().message() << std::endl; } } }; diff --git a/test/test_transaction_transaction_public_async_api.cxx b/test/test_transaction_transaction_public_async_api.cxx index 39946f577..02095eaf4 100644 --- a/test/test_transaction_transaction_public_async_api.cxx +++ b/test/test_transaction_transaction_public_async_api.cxx @@ -46,14 +46,14 @@ TEST_CASE("can async get", "[transactions]") auto f = barrier->get_future(); c.transactions()->run( [id, coll](couchbase::transactions::async_attempt_context& ctx) { - ctx.get(coll, id, [id](couchbase::transactions::transaction_get_result_ptr res) { - CHECK_FALSE(res->ctx().ec()); - CHECK(res->key() == id); - CHECK(res->content() == async_content); + ctx.get(coll, id, [id](auto e, auto res) { + CHECK_FALSE(e.ec()); + CHECK(res.key() == id); + CHECK(res.template content() == async_content); }); }, - [barrier](couchbase::transactions::transaction_result res) { - CHECK_FALSE(res.ctx.ec()); + [barrier](auto e, auto res) { + CHECK_FALSE(e.ec()); CHECK_FALSE(res.transaction_id.empty()); CHECK_FALSE(res.unstaging_complete); barrier->set_value(); @@ -74,13 +74,10 @@ TEST_CASE("can get fail as expected", "[transactions]") auto f = barrier->get_future(); c.transactions()->run( [id, coll](couchbase::transactions::async_attempt_context& ctx) { - ctx.get(coll, id, [id](couchbase::transactions::transaction_get_result_ptr res) { - CHECK(res->ctx().ec()); - CHECK(res->ctx().ec() == couchbase::errc::transaction_op::document_not_found_exception); - }); + ctx.get(coll, id, [id](auto e, auto) { CHECK(e.ec() == couchbase::errc::transaction_op::document_not_found_exception); }); }, - [barrier](couchbase::transactions::transaction_result res) { - CHECK_FALSE(res.ctx.ec()); + [barrier](auto e, auto res) { + CHECK_FALSE(e.ec()); CHECK_FALSE(res.transaction_id.empty()); CHECK_FALSE(res.unstaging_complete); barrier->set_value(); @@ -102,15 +99,15 @@ TEST_CASE("can async remove", "[transactions]") auto f = barrier->get_future(); c.transactions()->run( [coll, id](couchbase::transactions::async_attempt_context& ctx) { - ctx.get(coll, id, [&ctx](couchbase::transactions::transaction_get_result_ptr res) { - CHECK_FALSE(res->ctx().ec()); - ctx.remove(res, [](couchbase::transaction_op_error_context remove_err) { CHECK_FALSE(remove_err.ec()); }); + ctx.get(coll, id, [&ctx](auto e, auto res) { + CHECK_FALSE(e.ec()); + ctx.remove(res, [](auto remove_err) { CHECK_FALSE(remove_err.ec()); }); }); }, - [barrier](couchbase::transactions::transaction_result res) { + [barrier](auto e, auto res) { CHECK_FALSE(res.transaction_id.empty()); CHECK(res.unstaging_complete); - CHECK_FALSE(res.ctx.ec()); + CHECK_FALSE(e.ec()); barrier->set_value(); }, async_options()); @@ -131,15 +128,17 @@ TEST_CASE("async remove with bad cas fails as expected", "[transactions]") auto f = barrier->get_future(); c.transactions()->run( [coll, id](couchbase::transactions::async_attempt_context& ctx) { - ctx.get(coll, id, [&ctx](couchbase::transactions::transaction_get_result_ptr res) { - reinterpret_cast(*res).cas(100); - ctx.remove(res, [](couchbase::transaction_op_error_context remove_err) { CHECK(remove_err.ec()); }); + ctx.get(coll, id, [&ctx](auto, auto res) { + // all this to change the cas... + couchbase::core::transactions::transaction_get_result temp_doc(res); + temp_doc.cas(100); + ctx.remove(temp_doc.to_public_result(), [](auto remove_err) { CHECK(remove_err.ec()); }); }); }, - [barrier](couchbase::transactions::transaction_result res) { + [barrier](auto e, auto res) { CHECK_FALSE(res.transaction_id.empty()); CHECK_FALSE(res.unstaging_complete); - CHECK(res.ctx.ec()); // sometimes, it is a FAIL, as it expires in rollback, other times an expiry + CHECK(e.ec()); // sometimes, it is a FAIL, as it expires in rollback, other times an expiry barrier->set_value(); }, async_options()); @@ -157,13 +156,12 @@ TEST_CASE("can async insert", "[transactions]") auto f = barrier->get_future(); c.transactions()->run( [id, coll](couchbase::transactions::async_attempt_context& ctx) { - ctx.insert( - coll, id, async_content, [coll, id](couchbase::transactions::transaction_get_result_ptr res) { CHECK_FALSE(res->ctx().ec()); }); + ctx.insert(coll, id, async_content, [coll, id](auto e, auto) { CHECK_FALSE(e.ec()); }); }, - [barrier](couchbase::transactions::transaction_result res) { + [barrier](auto e, auto res) { CHECK_FALSE(res.transaction_id.empty()); CHECK(res.unstaging_complete); - CHECK_FALSE(res.ctx.ec()); + CHECK_FALSE(e.ec()); barrier->set_value(); }, async_options()); @@ -184,14 +182,14 @@ TEST_CASE("async insert fails when doc already exists, but doesn't rollback", "[ auto f = barrier->get_future(); c.transactions()->run( [id, coll](couchbase::transactions::async_attempt_context& ctx) { - ctx.insert(coll, id, async_content, [coll, id](couchbase::transactions::transaction_get_result_ptr res) { - CHECK(res->ctx().ec() == couchbase::errc::transaction_op::document_exists_exception); + ctx.insert(coll, id, async_content, [coll, id](auto e, auto) { + CHECK(e.ec() == couchbase::errc::transaction_op::document_exists_exception); }); }, - [barrier](couchbase::transactions::transaction_result res) { + [barrier](auto e, auto res) { CHECK_FALSE(res.transaction_id.empty()); CHECK(res.unstaging_complete); - CHECK_FALSE(res.ctx.ec()); + CHECK_FALSE(e.ec()); barrier->set_value(); }, async_options()); @@ -213,17 +211,17 @@ TEST_CASE("can async replace", "[transactions]") tao::json::value new_content = { { "Iam", "new content" } }; c.transactions()->run( [id, coll, new_content](couchbase::transactions::async_attempt_context& ctx) { - ctx.get(coll, id, [new_content, &ctx](couchbase::transactions::transaction_get_result_ptr res) { - CHECK_FALSE(res->ctx().ec()); - ctx.replace(res, new_content, [](couchbase::transactions::transaction_get_result_ptr replace_res) { - CHECK_FALSE(replace_res->ctx().ec()); + ctx.get(coll, id, [new_content, &ctx](auto, auto res) { + ctx.replace(res, new_content, [](auto replace_e, auto replace_result) { + CHECK(!replace_result.cas().empty()); + CHECK_FALSE(replace_e.ec()); }); }); }, - [barrier](couchbase::transactions::transaction_result tx_result) { + [barrier](auto e, auto tx_result) { CHECK_FALSE(tx_result.transaction_id.empty()); CHECK(tx_result.unstaging_complete); - CHECK_FALSE(tx_result.ctx.ec()); + CHECK_FALSE(e.ec()); barrier->set_value(); }, async_options()); @@ -244,16 +242,17 @@ TEST_CASE("async replace fails as expected with bad cas", "[transactions]") tao::json::value new_content = { { "Iam", "new content" } }; c.transactions()->run( [id, coll, new_content](couchbase::transactions::async_attempt_context& ctx) { - ctx.get(coll, id, [new_content, &ctx](couchbase::transactions::transaction_get_result_ptr res) { - reinterpret_cast(*res).cas(100); - ctx.replace( - res, new_content, [](couchbase::transactions::transaction_get_result_ptr replace_res) { CHECK(replace_res->ctx().ec()); }); + ctx.get(coll, id, [new_content, &ctx](auto, auto res) { + // all this to change the cas... + couchbase::core::transactions::transaction_get_result temp_doc(res); + temp_doc.cas(100); + ctx.replace(temp_doc.to_public_result(), new_content, [](auto replace_e, auto) { CHECK(replace_e.ec()); }); }); }, - [barrier](couchbase::transactions::transaction_result tx_result) { + [barrier](auto e, auto tx_result) { CHECK_FALSE(tx_result.transaction_id.empty()); CHECK_FALSE(tx_result.unstaging_complete); - CHECK(tx_result.ctx.ec()); + CHECK(e.ec()); barrier->set_value(); }, async_options()); @@ -275,16 +274,16 @@ TEST_CASE("uncaught exception will rollback", "[transactions]") tao::json::value new_content = { { "Iam", "new content" } }; c.transactions()->run( [id, coll, new_content](couchbase::transactions::async_attempt_context& ctx) { - ctx.get(coll, id, [new_content, &ctx](couchbase::transactions::transaction_get_result_ptr res) { - CHECK_FALSE(res->ctx().ec()); - ctx.replace(res, new_content, [](couchbase::transactions::transaction_get_result_ptr res) { - CHECK_FALSE(res->ctx().ec()); + ctx.get(coll, id, [new_content, &ctx](auto e, auto res) { + CHECK_FALSE(e.ec()); + ctx.replace(res, new_content, [](auto replace_e, auto) { + CHECK_FALSE(replace_e.ec()); throw std::runtime_error("I wanna rollback"); }); }); }, - [barrier](couchbase::transactions::transaction_result res) { - CHECK(res.ctx.ec() == couchbase::errc::transaction::failed); + [barrier](auto e, auto res) { + CHECK(e.ec() == couchbase::errc::transaction::failed); CHECK_FALSE(res.unstaging_complete); CHECK_FALSE(res.transaction_id.empty()); barrier->set_value(); @@ -310,12 +309,14 @@ TEST_CASE("can set transaction options", "[transactions]") auto f = barrier->get_future(); c.transactions()->run( [id, coll](couchbase::transactions::async_attempt_context& ctx) { - ctx.get(coll, id, [&ctx](couchbase::transactions::transaction_get_result_ptr doc) { - reinterpret_cast(*doc).cas(100); - ctx.remove(doc, [](couchbase::transaction_op_error_context remove_err) { CHECK(remove_err.ec()); }); + ctx.get(coll, id, [&ctx](auto, auto doc) { + // all this to change the cas... + couchbase::core::transactions::transaction_get_result temp_doc(doc); + temp_doc.cas(100); + ctx.remove(temp_doc.to_public_result(), [](couchbase::transaction_op_error_context remove_err) { CHECK(remove_err.ec()); }); }); }, - [&begin, &cfg, barrier](couchbase::transactions::transaction_result res) { + [&begin, &cfg, barrier](auto e, auto res) { auto end = std::chrono::steady_clock::now(); auto elapsed = std::chrono::duration_cast(end - begin); // should be greater than the expiration time @@ -325,7 +326,7 @@ TEST_CASE("can set transaction options", "[transactions]") // and of course the txn should have expired CHECK_FALSE(res.transaction_id.empty()); CHECK_FALSE(res.unstaging_complete); - CHECK(res.ctx.ec()); // can be fail or expired, as we get a fail if expiring in rollback. + CHECK(e.ec()); // can be fail or expired, as we get a fail if expiring in rollback. barrier->set_value(); }, cfg); @@ -347,10 +348,10 @@ TEST_CASE("can do mutating query", "[transactions]") c.transactions()->run( [id, test_ctx = integration.ctx](couchbase::transactions::async_attempt_context& ctx) { ctx.query(fmt::format(R"(INSERT INTO `{}` (KEY, VALUE) VALUES("{}", {}))", test_ctx.bucket, id, async_content_json), - [](couchbase::transactions::transaction_query_result_ptr res) { CHECK_FALSE(res->ctx().ec()); }); + [](auto e, auto) { CHECK_FALSE(e.ec()); }); }, - [barrier](couchbase::transactions::transaction_result res) { - CHECK_FALSE(res.ctx.ec()); + [barrier](auto e, auto res) { + CHECK_FALSE(e.ec()); CHECK_FALSE(res.transaction_id.empty()); CHECK(res.unstaging_complete); barrier->set_value(); @@ -375,14 +376,14 @@ TEST_CASE("some query errors rollback", "[transactions]") c.transactions()->run( [id, id2, test_ctx = integration.ctx](couchbase::transactions::async_attempt_context& ctx) { ctx.query(fmt::format(R"(INSERT INTO `{}` (KEY, VALUE) VALUES("{}", {}))", test_ctx.bucket, id2, async_content_json), - [id, &ctx, &test_ctx](couchbase::transactions::transaction_query_result_ptr res) { - CHECK_FALSE(res->ctx().ec()); + [id, &ctx, &test_ctx](auto e, auto) { + CHECK_FALSE(e.ec()); ctx.query(fmt::format(R"(INSERT INTO `{}` (KEY, VALUE) VALUES("{}", {}))", test_ctx.bucket, id, async_content_json), - [](couchbase::transactions::transaction_query_result_ptr) {}); + [](auto, auto) {}); }); }, - [barrier](couchbase::transactions::transaction_result res) { - CHECK(res.ctx.ec() == couchbase::errc::transaction::failed); + [barrier](auto e, auto res) { + CHECK(e.ec() == couchbase::errc::transaction::failed); CHECK_FALSE(res.transaction_id.empty()); CHECK_FALSE(res.unstaging_complete); barrier->set_value(); diff --git a/test/test_transaction_transaction_public_blocking_api.cxx b/test/test_transaction_transaction_public_blocking_api.cxx index 8538f1c69..115bbd4ed 100644 --- a/test/test_transaction_transaction_public_blocking_api.cxx +++ b/test/test_transaction_transaction_public_blocking_api.cxx @@ -105,17 +105,17 @@ TEST_CASE("can get", "[transactions]") REQUIRE_SUCCESS(err.ec()); REQUIRE_FALSE(upsert_res.cas().empty()); - auto result = c.transactions()->run( + auto [tx_err, result] = c.transactions()->run( [id, &coll](couchbase::transactions::attempt_context& ctx) { - auto doc = ctx.get(coll, id); - CHECK_FALSE(doc->ctx().ec()); - CHECK(doc->key() == id); - CHECK_FALSE(doc->cas().empty()); - CHECK(doc->content() == content); + auto [e, doc] = ctx.get(coll, id); + CHECK_FALSE(e.ec()); + CHECK(doc.key() == id); + CHECK_FALSE(doc.cas().empty()); + CHECK(doc.content() == content); }, txn_opts()); CHECK_FALSE(result.transaction_id.empty()); - CHECK_FALSE(result.ctx.ec()); + CHECK_FALSE(tx_err.ec()); } TEST_CASE("get returns error if doc doesn't exist", "[transactions]") @@ -126,16 +126,15 @@ TEST_CASE("get returns error if doc doesn't exist", "[transactions]") couchbase::cluster c(integration.cluster); auto coll = c.bucket(integration.ctx.bucket).default_collection(); - auto result = c.transactions()->run( + auto [tx_err, result] = c.transactions()->run( [id, coll](couchbase::transactions::attempt_context& ctx) { - auto doc = ctx.get(coll, id); - CHECK(doc->ctx().ec()); - CHECK(doc->ctx().ec() == couchbase::errc::transaction_op::document_not_found_exception); + auto [e, doc] = ctx.get(coll, id); + CHECK(e.ec() == couchbase::errc::transaction_op::document_not_found_exception); }, txn_opts()); CHECK_FALSE(result.transaction_id.empty()); CHECK_FALSE(result.unstaging_complete); - CHECK_FALSE(result.ctx.ec()); + CHECK_FALSE(tx_err.ec()); } TEST_CASE("can insert", "[transactions]") @@ -146,18 +145,20 @@ TEST_CASE("can insert", "[transactions]") couchbase::cluster c(integration.cluster); auto coll = c.bucket(integration.ctx.bucket).default_collection(); - auto result = c.transactions()->run( + auto [tx_err, result] = c.transactions()->run( [id, coll](couchbase::transactions::attempt_context& ctx) { - auto doc = ctx.insert(coll, id, content); - CHECK_FALSE(doc->ctx().ec()); - CHECK(doc->key() == id); - CHECK_FALSE(doc->cas().empty()); - CHECK(doc->content() == content); + auto [e, doc] = ctx.insert(coll, id, content); + CHECK_FALSE(e.ec()); + CHECK(doc.key() == id); + CHECK_FALSE(doc.cas().empty()); + auto [e2, inserted_doc] = ctx.get(coll, id); + CHECK_FALSE(e2.ec()); + CHECK(inserted_doc.content() == content); }, txn_opts()); REQUIRE_FALSE(result.transaction_id.empty()); REQUIRE(result.unstaging_complete); - REQUIRE_FALSE(result.ctx.ec()); + REQUIRE_FALSE(tx_err.ec()); // check that it is really there now auto [err, final_doc] = coll.get(id, {}).get(); REQUIRE_SUCCESS(err.ec()); @@ -176,16 +177,16 @@ TEST_CASE("insert has error as expected when doc already exists", "[transactions REQUIRE_SUCCESS(err.ec()); tao::json::value new_content{ { "something", "else" } }; - auto result = c.transactions()->run( + auto [tx_err, result] = c.transactions()->run( [id, coll, new_content](couchbase::transactions::attempt_context& ctx) { - auto doc = ctx.insert(coll, id, new_content); - CHECK(doc->ctx().ec() == couchbase::errc::transaction_op::document_exists_exception); + auto [e, doc] = ctx.insert(coll, id, new_content); + CHECK(e.ec() == couchbase::errc::transaction_op::document_exists_exception); }, txn_opts()); CHECK_FALSE(result.transaction_id.empty()); // but the txn is successful CHECK(result.unstaging_complete); - CHECK_FALSE(result.ctx.ec()); + CHECK_FALSE(tx_err.ec()); // check that it is really unchanged too. auto [final_err, final_doc] = coll.get(id, {}).get(); REQUIRE_SUCCESS(final_err.ec()); @@ -203,23 +204,25 @@ TEST_CASE("can replace", "[transactions]") REQUIRE_SUCCESS(err.ec()); tao::json::value new_content = { { "some_other_number", 3 } }; - auto result = c.transactions()->run( + auto [tx_err, result] = c.transactions()->run( [id, coll, new_content](couchbase::transactions::attempt_context& ctx) { - auto doc = ctx.get(coll, id); - auto replaced_doc = ctx.replace(doc, new_content); - CHECK(doc->key() == replaced_doc->key()); - CHECK(doc->cas() != replaced_doc->cas()); - CHECK(doc->content() == content); - CHECK(replaced_doc->content() == new_content); + auto [_, doc] = ctx.get(coll, id); + auto [e, replaced_doc] = ctx.replace(doc, new_content); + CHECK_FALSE(e.ec()); + CHECK(doc.key() == replaced_doc.key()); + CHECK(doc.cas() != replaced_doc.cas()); + CHECK(doc.content() == content); + CHECK(replaced_doc.content() == new_content); }, txn_opts()); CHECK_FALSE(result.transaction_id.empty()); CHECK(result.unstaging_complete); - CHECK_FALSE(result.ctx.ec()); + CHECK_FALSE(tx_err.ec()); // check that it is really replaced auto [final_err, final_doc] = coll.get(id, {}).get(); REQUIRE_SUCCESS(final_err.ec()); - REQUIRE(final_doc.content_as() == new_content); + REQUIRE(couchbase::core::utils::json::generate_binary(final_doc.content_as()) == + couchbase::core::utils::json::generate_binary(new_content)); } TEST_CASE("replace fails as expected with bad cas", "[transactions]") @@ -234,17 +237,18 @@ TEST_CASE("replace fails as expected with bad cas", "[transactions]") REQUIRE_SUCCESS(err.ec()); tao::json::value new_content = { { "some_other_number", 3 } }; - auto result = c.transactions()->run( + auto [tx_err, result] = c.transactions()->run( [id, coll, new_content](couchbase::transactions::attempt_context& ctx) { - auto doc = ctx.get(coll, id); - reinterpret_cast(*doc).cas(100); - auto replaced_doc = ctx.replace(doc, new_content); + auto [_, doc] = ctx.get(coll, id); + // all this to change the cas... + couchbase::core::transactions::transaction_get_result temp_doc(doc); + temp_doc.cas(100); + auto replaced_doc = ctx.replace(temp_doc.to_public_result(), new_content); }, txn_opts()); CHECK_FALSE(result.transaction_id.empty()); CHECK_FALSE(result.unstaging_complete); - CHECK(result.ctx.ec()); - CHECK(result.ctx.ec()); + CHECK(tx_err.ec()); // check that it is unchanged auto [final_err, final_doc] = coll.get(id, {}).get(); REQUIRE_SUCCESS(final_err.ec()); @@ -261,14 +265,15 @@ TEST_CASE("can remove", "[transactions]") auto [err, upsert_res] = coll.upsert(id, content, {}).get(); REQUIRE_SUCCESS(err.ec()); - auto result = c.transactions()->run( + auto [tx_err, result] = c.transactions()->run( [id, coll](couchbase::transactions::attempt_context& ctx) { - auto doc = ctx.get(coll, id); + auto [_, doc] = ctx.get(coll, id); auto removed_doc = ctx.remove(doc); }, txn_opts()); CHECK_FALSE(result.transaction_id.empty()); CHECK(result.unstaging_complete); + CHECK_FALSE(tx_err.ec()); // make sure it is really gone... auto [final_err, final_doc] = coll.get(id, {}).get(); @@ -286,18 +291,20 @@ TEST_CASE("remove fails as expected with bad cas", "[transactions]") auto [err, upsert_res] = coll.upsert(id, content, {}).get(); REQUIRE_SUCCESS(err.ec()); - auto result = c.transactions()->run( + auto [tx_err, result] = c.transactions()->run( [id, coll](couchbase::transactions::attempt_context& ctx) { - auto doc = ctx.get(coll, id); + auto [e, doc] = ctx.get(coll, id); // change cas, so remove will fail and retry - reinterpret_cast(*doc).cas(100); - auto remove_err = ctx.remove(doc); + // all this to change the cas... + couchbase::core::transactions::transaction_get_result temp_doc(doc); + temp_doc.cas(100); + auto remove_err = ctx.remove(temp_doc.to_public_result()); CHECK(remove_err.ec()); }, txn_opts()); CHECK_FALSE(result.transaction_id.empty()); CHECK_FALSE(result.unstaging_complete); - CHECK(result.ctx.ec()); + CHECK(tx_err.ec()); } TEST_CASE("remove fails as expected with missing doc", "[transactions]") @@ -308,10 +315,10 @@ TEST_CASE("remove fails as expected with missing doc", "[transactions]") couchbase::cluster c(integration.cluster); auto coll = c.bucket(integration.ctx.bucket).default_collection(); - auto result = c.transactions()->run( + auto [tx_err, result] = c.transactions()->run( [id, coll](couchbase::transactions::attempt_context& ctx) { - auto doc = ctx.get(coll, id); - CHECK(doc->ctx().ec() == couchbase::errc::transaction_op::document_not_found_exception); + auto [e, doc] = ctx.get(coll, id); + CHECK(e.ec() == couchbase::errc::transaction_op::document_not_found_exception); // the doc is 'blank', so trying to use it results in failure auto err = ctx.remove(doc); CHECK(err.ec()); @@ -320,9 +327,8 @@ TEST_CASE("remove fails as expected with missing doc", "[transactions]") txn_opts()); CHECK_FALSE(result.transaction_id.empty()); CHECK_FALSE(result.unstaging_complete); - CHECK(result.ctx.ec()); - CHECK(result.ctx.ec() == couchbase::errc::transaction::failed); - CHECK(result.ctx.cause() == couchbase::errc::transaction_op::unknown); + CHECK(tx_err.ec() == couchbase::errc::transaction::failed); + CHECK(tx_err.cause() == couchbase::errc::transaction_op::unknown); } TEST_CASE("uncaught exception in lambda will rollback without retry", "[transactions]") @@ -333,18 +339,17 @@ TEST_CASE("uncaught exception in lambda will rollback without retry", "[transact couchbase::cluster c(integration.cluster); auto coll = c.bucket(integration.ctx.bucket).default_collection(); - auto result = c.transactions()->run( + auto [tx_err, result] = c.transactions()->run( [id, coll](couchbase::transactions::attempt_context& ctx) { - auto doc = ctx.insert(coll, id, content); - CHECK_FALSE(doc->ctx().ec()); + auto [e, doc] = ctx.insert(coll, id, content); + CHECK_FALSE(e.ec()); throw std::runtime_error("some exception"); }, txn_opts()); CHECK_FALSE(result.transaction_id.empty()); CHECK_FALSE(result.unstaging_complete); - CHECK(result.ctx.ec()); - CHECK(result.ctx.ec() == couchbase::errc::transaction::failed); - CHECK(result.ctx.cause() == couchbase::errc::transaction_op::unknown); + CHECK(tx_err.ec() == couchbase::errc::transaction::failed); + CHECK(tx_err.cause() == couchbase::errc::transaction_op::unknown); } TEST_CASE("can pass per-transaction configs", "[transactions]") @@ -359,11 +364,13 @@ TEST_CASE("can pass per-transaction configs", "[transactions]") auto opts = couchbase::transactions::transaction_options().expiration_time(std::chrono::seconds(2)); auto begin = std::chrono::steady_clock::now(); - auto result = c.transactions()->run( + auto [tx_err, result] = c.transactions()->run( [id, coll](couchbase::transactions::attempt_context& ctx) { - auto doc = ctx.get(coll, id); - reinterpret_cast(*doc).cas(100); - auto remove_err = ctx.remove(doc); + auto [e, doc] = ctx.get(coll, id); + // all this to change the cas... + couchbase::core::transactions::transaction_get_result temp_doc(doc); + temp_doc.cas(100); + auto remove_err = ctx.remove(temp_doc.to_public_result()); CHECK(remove_err.ec()); }, opts); @@ -376,7 +383,7 @@ TEST_CASE("can pass per-transaction configs", "[transactions]") CHECK_FALSE(result.transaction_id.empty()); CHECK_FALSE(result.unstaging_complete); // could have failed in rollback, which returns fail rather than expired - CHECK(result.ctx.ec()); + CHECK(tx_err.ec()); } TEST_CASE("can do simple query", "[transactions]") @@ -388,14 +395,14 @@ TEST_CASE("can do simple query", "[transactions]") auto coll = c.bucket(integration.ctx.bucket).default_collection(); auto [err, upsert_res] = coll.upsert(id, content, {}).get(); REQUIRE_SUCCESS(err.ec()); - auto result = c.transactions()->run( + auto [tx_err, result] = c.transactions()->run( [id, coll, test_ctx = integration.ctx](couchbase::transactions::attempt_context& ctx) { - auto res = ctx.query(fmt::format("SELECT * FROM `{}` USE KEYS '{}'", test_ctx.bucket, id)); - CHECK_FALSE(res->ctx().ec()); - CHECK(content == res->rows_as_json().front()["default"]); + auto [e, res] = ctx.query(fmt::format("SELECT * FROM `{}` USE KEYS '{}'", test_ctx.bucket, id)); + CHECK_FALSE(e.ec()); + CHECK(content == res.rows_as_json().front()["default"]); }, couchbase::transactions::transaction_options().expiration_time(std::chrono::seconds(10))); - CHECK_FALSE(result.ctx.ec()); + CHECK_FALSE(tx_err.ec()); CHECK(result.unstaging_complete); CHECK_FALSE(result.transaction_id.empty()); } @@ -410,13 +417,13 @@ TEST_CASE("can do simple mutating query", "[transactions]") auto [err, upsert_res] = coll.upsert(id, content, {}).get(); REQUIRE_SUCCESS(err.ec()); - auto result = c.transactions()->run( + auto [tx_err, result] = c.transactions()->run( [id, coll, test_ctx = integration.ctx](couchbase::transactions::attempt_context& ctx) { - auto res = ctx.query(fmt::format("UPDATE `{}` USE KEYS '{}' SET `some_number` = 10", test_ctx.bucket, id)); - CHECK_FALSE(res->ctx().ec()); + auto [e, res] = ctx.query(fmt::format("UPDATE `{}` USE KEYS '{}' SET `some_number` = 10", test_ctx.bucket, id)); + CHECK_FALSE(e.ec()); }, couchbase::transactions::transaction_options().expiration_time(std::chrono::seconds(10))); - CHECK_FALSE(result.ctx.ec()); + CHECK_FALSE(tx_err.ec()); CHECK(result.unstaging_complete); CHECK_FALSE(result.transaction_id.empty()); auto [final_err, final_doc] = coll.get(id, {}).get(); @@ -431,16 +438,17 @@ TEST_CASE("some query errors don't force rollback", "[transactions]") couchbase::cluster c(integration.cluster); auto coll = c.bucket(integration.ctx.bucket).default_collection(); - auto result = c.transactions()->run( + auto [tx_err, result] = c.transactions()->run( [id, coll, test_ctx = integration.ctx](couchbase::transactions::attempt_context& ctx) { - auto get_res = ctx.query(fmt::format("SELECT * FROM `{}` USE KEYS '{}'", test_ctx.bucket, id)); - CHECK_FALSE(get_res->ctx().ec()); - CHECK(get_res->rows_as_json().size() == 0); - auto insert_res = ctx.query(fmt::format(R"(INSERT INTO `{}` (KEY, VALUE) VALUES ("{}", {}))", test_ctx.bucket, id, content_json)); - CHECK_FALSE(insert_res->ctx().ec()); + auto [get_err, get_res] = ctx.query(fmt::format("SELECT * FROM `{}` USE KEYS '{}'", test_ctx.bucket, id)); + CHECK_FALSE(get_err.ec()); + CHECK(get_res.rows_as_json().size() == 0); + auto [insert_err, _] = + ctx.query(fmt::format(R"(INSERT INTO `{}` (KEY, VALUE) VALUES ("{}", {}))", test_ctx.bucket, id, content_json)); + CHECK_FALSE(insert_err.ec()); }, couchbase::transactions::transaction_options().expiration_time(std::chrono::seconds(10))); - CHECK_FALSE(result.ctx.ec()); + CHECK_FALSE(tx_err.ec()); CHECK(result.unstaging_complete); CHECK_FALSE(result.transaction_id.empty()); auto [final_err, final_doc] = coll.get(id, {}).get(); @@ -458,15 +466,17 @@ TEST_CASE("some query errors do rollback", "[transactions]") auto [err, upsert_res] = coll.upsert(id, content, {}).get(); REQUIRE_SUCCESS(err.ec()); - auto result = c.transactions()->run( + auto [tx_err, result] = c.transactions()->run( [id, id2, coll, test_ctx = integration.ctx](couchbase::transactions::attempt_context& ctx) { // this one works. - ctx.query(fmt::format(R"(INSERT INTO `{}` (KEY, VALUE) VALUES ("{}", {}))", test_ctx.bucket, id2, content_json)); + auto [e, _] = ctx.query(fmt::format(R"(INSERT INTO `{}` (KEY, VALUE) VALUES ("{}", {}))", test_ctx.bucket, id2, content_json)); + CHECK_FALSE(e.ec()); // but not this one. But the query server doesn't notice until commit, so this _appears_ to succeed - ctx.query(fmt::format(R"(INSERT INTO `{}` (KEY, VALUE) VALUES ("{}", {}))", test_ctx.bucket, id, content_json)); + auto [e2, __] = ctx.query(fmt::format(R"(INSERT INTO `{}` (KEY, VALUE) VALUES ("{}", {}))", test_ctx.bucket, id, content_json)); + CHECK_FALSE(e2.ec()); }, couchbase::transactions::transaction_options().expiration_time(std::chrono::seconds(10))); - CHECK(result.ctx.ec() == couchbase::errc::transaction::failed); + CHECK(tx_err.ec() == couchbase::errc::transaction::failed); // id2 should not exist, since the txn should have rolled back. auto [doc2_err, doc2] = coll.get(id2, {}).get(); @@ -481,14 +491,14 @@ TEST_CASE("some query errors are seen immediately", "[transactions]") couchbase::cluster c(integration.cluster); auto coll = c.bucket(integration.ctx.bucket).default_collection(); - auto result = c.transactions()->run( + auto [tx_err, result] = c.transactions()->run( [](couchbase::transactions::attempt_context& ctx) { - auto res = ctx.query("I am not a valid n1ql query"); - CHECK(res->ctx().ec()); - CHECK(std::holds_alternative(res->ctx().cause())); + auto [e, res] = ctx.query("I am not a valid n1ql query"); + CHECK(e.ec()); + CHECK(std::holds_alternative(e.cause())); }, couchbase::transactions::transaction_options().expiration_time(std::chrono::seconds(10))); - CHECK_FALSE(result.ctx.ec()); + CHECK_FALSE(tx_err.ec()); CHECK_FALSE(result.transaction_id.empty()); CHECK(result.unstaging_complete); } @@ -509,15 +519,15 @@ TEST_CASE("can query from a scope", "[transactions]") REQUIRE_SUCCESS(err.ec()); auto statement = fmt::format("SELECT * FROM `{}` USE KEYS '{}'", new_coll_name, id); - auto result = c.transactions()->run( + auto [tx_err, result] = c.transactions()->run( [&](couchbase::transactions::attempt_context& ctx) { - auto res = ctx.query(new_scope, statement); - CHECK_FALSE(res->ctx().ec()); - CHECK(res->rows_as_json().size() > 0); - CHECK(res->rows_as_json().front()[new_coll_name] == content); + auto [e, res] = ctx.query(new_scope, statement); + CHECK_FALSE(e.ec()); + CHECK(res.rows_as_json().size() > 0); + CHECK(res.rows_as_json().front()[new_coll_name] == content); }, txn_opts()); - CHECK_FALSE(result.ctx.ec()); + CHECK_FALSE(tx_err.ec()); CHECK_FALSE(result.transaction_id.empty()); } @@ -536,14 +546,14 @@ TEST_CASE("can get doc from bucket not yet opened", "[transactions]") with_new_guard([&](test::utils::integration_test_guard& integration) { couchbase::cluster c(integration.cluster); auto coll = c.bucket(integration.ctx.bucket).default_collection(); - auto result = c.transactions()->run( + auto [tx_err, result] = c.transactions()->run( [&id, &coll](couchbase::transactions::attempt_context& ctx) { - auto doc = ctx.get(coll, id); - CHECK_FALSE(doc->ctx().ec()); - CHECK(doc->content() == content); + auto [e, doc] = ctx.get(coll, id); + CHECK_FALSE(e.ec()); + CHECK(doc.content() == content); }, txn_opts()); - CHECK_FALSE(result.ctx.ec()); + CHECK_FALSE(tx_err.ec()); CHECK_FALSE(result.transaction_id.empty()); CHECK_FALSE(result.unstaging_complete); // no mutations = no unstaging }); @@ -559,14 +569,14 @@ TEST_CASE("can insert doc into bucket not yet opened", "[transactions]") couchbase::cluster c(guard.cluster); auto coll = c.bucket(integration.ctx.bucket).default_collection(); - auto result = c.transactions()->run( + auto [tx_err, result] = c.transactions()->run( [&id, &coll](couchbase::transactions::attempt_context& ctx) { - auto doc = ctx.insert(coll, id, content); - CHECK_FALSE(doc->ctx().ec()); - CHECK_FALSE(doc->cas().empty()); + auto [e, doc] = ctx.insert(coll, id, content); + CHECK_FALSE(e.ec()); + CHECK_FALSE(doc.cas().empty()); }, txn_opts()); - CHECK_FALSE(result.ctx.ec()); + CHECK_FALSE(tx_err.ec()); CHECK_FALSE(result.transaction_id.empty()); CHECK(result.unstaging_complete); auto [err, get_res] = coll.get(id, {}).get(); @@ -592,16 +602,16 @@ TEST_CASE("can replace doc in bucket not yet opened", "[transactions]") auto coll = c.bucket(guard.ctx.bucket).default_collection(); tao::json::value new_content = { { "some", "new content" } }; - auto result = c.transactions()->run( + auto [tx_err, result] = c.transactions()->run( [&id, &coll, new_content](couchbase::transactions::attempt_context& ctx) { - auto get_doc = ctx.get(coll, id); - CHECK_FALSE(get_doc->ctx().ec()); - auto doc = ctx.replace(get_doc, new_content); - CHECK_FALSE(doc->ctx().ec()); - CHECK_FALSE(doc->cas().empty()); + auto [get_err, get_doc] = ctx.get(coll, id); + CHECK_FALSE(get_err.ec()); + auto [e, doc] = ctx.replace(get_doc, new_content); + CHECK_FALSE(e.ec()); + CHECK_FALSE(doc.cas().empty()); }, txn_opts()); - CHECK_FALSE(result.ctx.ec()); + CHECK_FALSE(tx_err.ec()); CHECK_FALSE(result.transaction_id.empty()); CHECK(result.unstaging_complete); auto [err, get_res] = coll.get(id, {}).get(); @@ -626,15 +636,15 @@ TEST_CASE("can remove doc in bucket not yet opened", "[transactions]") couchbase::cluster c(guard.cluster); auto coll = c.bucket(guard.ctx.bucket).default_collection(); tao::json::value new_content = { { "some", "new content" } }; - auto result = c.transactions()->run( + auto [tx_err, result] = c.transactions()->run( [&id, &coll, new_content](couchbase::transactions::attempt_context& ctx) { - auto get_doc = ctx.get(coll, id); - CHECK_FALSE(get_doc->ctx().ec()); + auto [e, get_doc] = ctx.get(coll, id); + CHECK_FALSE(e.ec()); auto res = ctx.remove(get_doc); CHECK_FALSE(res.ec()); }, txn_opts()); - CHECK_FALSE(result.ctx.ec()); + CHECK_FALSE(tx_err.ec()); CHECK_FALSE(result.transaction_id.empty()); CHECK(result.unstaging_complete); auto [get_err, get_res] = coll.get(id, {}).get(); diff --git a/test/test_unit_transaction_utils.cxx b/test/test_unit_transaction_utils.cxx index 87a5960c8..4d0ce32c4 100644 --- a/test/test_unit_transaction_utils.cxx +++ b/test/test_unit_transaction_utils.cxx @@ -19,6 +19,9 @@ #include "core/transactions/internal/exceptions_internal.hxx" #include "core/transactions/internal/utils.hxx" +#include +#include + #include #include #include @@ -212,3 +215,47 @@ TEST_CASE("retryable op: can have constant delay", "[unit]") REQUIRE(state.timings.size() == 10); } } + +TEST_CASE("transaction_get_result: can convert core transaction_get_result to public, and visa-versa", "[unit]") +{ + const tao::json::value content{ { "some_number", 0 } }; + const auto binary_content = couchbase::core::utils::json::generate_binary(content); + const std::string bucket = "bucket"; + const std::string scope = "scope"; + const std::string collection = "collection"; + const std::string key = "key"; + const couchbase::cas cas(100ULL); + + SECTION("public to core") + { + couchbase::transactions::transaction_get_result public_result(bucket, scope, collection, key, cas, binary_content); + couchbase::core::transactions::transaction_get_result core_result(public_result); + REQUIRE(core_result.collection() == collection); + REQUIRE(core_result.bucket() == bucket); + REQUIRE(core_result.scope() == scope); + REQUIRE(core_result.cas() == cas); + REQUIRE(core_result.content() == binary_content); + REQUIRE(public_result.collection() == collection); + REQUIRE(public_result.bucket() == bucket); + REQUIRE(public_result.scope() == scope); + REQUIRE(public_result.cas() == cas); + REQUIRE(public_result.content() == binary_content); + } + SECTION("core to public") + { + couchbase::core::transactions::transaction_get_result core_result( + { bucket, scope, collection, key }, binary_content, cas.value(), {}, {}); + auto public_result = core_result.to_public_result(); + REQUIRE(public_result.collection() == collection); + REQUIRE(public_result.bucket() == bucket); + REQUIRE(public_result.scope() == scope); + REQUIRE(public_result.cas() == cas); + REQUIRE(public_result.content() == binary_content); + REQUIRE(core_result.collection() == collection); + REQUIRE(core_result.bucket() == bucket); + REQUIRE(core_result.scope() == scope); + REQUIRE(core_result.cas() == cas); + // the content is _moved_ when creating the public_result + REQUIRE(core_result.content().empty()); + } +} \ No newline at end of file