Skip to content

PCBC-965 Support DocNotLockedException and core update #142

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Dec 18, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions Couchbase/Exception/DocumentNotLockedException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?php

/**
* Copyright 2014-Present Couchbase, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

declare(strict_types=1);

namespace Couchbase\Exception;

/**
* Thrown when the server reports that the document is not locked when an unlocking
* operation is being performed.
*/
class DocumentNotLockedException extends CouchbaseException
{
}
1,562 changes: 65 additions & 1,497 deletions package.xml

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
cmake_minimum_required(VERSION 3.17)
cmake_minimum_required(VERSION 3.19)
project(couchbase C CXX)

set(CMAKE_CXX_STANDARD 17)
Expand Down Expand Up @@ -74,7 +74,7 @@ file(GLOB SOURCE_FILES ${PROJECT_SOURCE_DIR}/wrapper/*.cxx)
add_library(couchbase_php_wrapper SHARED ${SOURCE_FILES})
target_include_directories(couchbase_php_wrapper PRIVATE ${PROJECT_BINARY_DIR}/generated)
target_include_directories(couchbase_php_wrapper PRIVATE ${PHP_INCLUDE_DIRS})
target_link_libraries(couchbase_php_wrapper PRIVATE project_options project_warnings couchbase_cxx_client)
target_link_libraries(couchbase_php_wrapper PRIVATE project_options project_warnings couchbase_cxx_client taocpp::json fmt::fmt asio Microsoft.GSL::GSL)
if(APPLE)
target_link_libraries(couchbase_php_wrapper PRIVATE -Wl,-undefined,dynamic_lookup)
endif()
Expand Down
2 changes: 1 addition & 1 deletion src/deps/couchbase-cxx-client
5 changes: 5 additions & 0 deletions src/wrapper/common.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ zend_class_entry* document_exists_exception_ce;
zend_class_entry* document_irretrievable_exception_ce;
zend_class_entry* document_locked_exception_ce;
zend_class_entry* document_not_found_exception_ce;
zend_class_entry* document_not_locked_exception_ce;
zend_class_entry* document_not_json_exception_ce;
zend_class_entry* durability_ambiguous_exception_ce;
zend_class_entry* durability_impossible_exception_ce;
Expand Down Expand Up @@ -170,6 +171,8 @@ initialize_exceptions(const zend_function_entry* exception_functions)
document_locked_exception_ce = zend_register_internal_class_ex(&ce, couchbase_exception_ce);
INIT_NS_CLASS_ENTRY(ce, "Couchbase\\Exception", "DocumentNotFoundException", nullptr);
document_not_found_exception_ce = zend_register_internal_class_ex(&ce, couchbase_exception_ce);
INIT_NS_CLASS_ENTRY(ce, "Couchbase\\Exception", "DocumentNotLockedException", nullptr);
document_not_locked_exception_ce = zend_register_internal_class_ex(&ce, couchbase_exception_ce);
INIT_NS_CLASS_ENTRY(ce, "Couchbase\\Exception", "DocumentNotJsonException", nullptr);
document_not_json_exception_ce = zend_register_internal_class_ex(&ce, couchbase_exception_ce);
INIT_NS_CLASS_ENTRY(ce, "Couchbase\\Exception", "DurabilityAmbiguousException", nullptr);
Expand Down Expand Up @@ -345,6 +348,8 @@ map_error_to_exception(const core_error_info& info)
switch (static_cast<couchbase::errc::key_value>(info.ec.value())) {
case couchbase::errc::key_value::document_not_found:
return document_not_found_exception_ce;
case couchbase::errc::key_value::document_not_locked:
return document_not_locked_exception_ce;
case couchbase::errc::key_value::document_irretrievable:
return document_irretrievable_exception_ce;
case couchbase::errc::key_value::document_locked:
Expand Down
25 changes: 16 additions & 9 deletions src/wrapper/connection_handle.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -25,21 +25,27 @@
#include "version.hxx"

#include <core/cluster.hxx>
#include <core/error_context/analytics.hxx>
#include <core/error_context/search.hxx>
#include <core/error_context/view.hxx>
#include <core/management/bucket_settings.hxx>
#include <core/operations.hxx>
#include <core/operations/management/bucket.hxx>
#include <core/operations/management/cluster_describe.hxx>
#include <core/operations/management/collections.hxx>
#include <core/operations/management/query.hxx>
#include <core/operations/management/search.hxx>
#include <core/operations/management/user.hxx>
#include <core/operations/management/view.hxx>
#include <core/utils/connection_string.hxx>

#include <couchbase/cluster.hxx>
#include <couchbase/collection.hxx>
#include <couchbase/mutation_token.hxx>
#include <couchbase/retry_reason.hxx>

#include <fmt/core.h>
#include <openssl/crypto.h>

#include <array>
#include <thread>
Expand Down Expand Up @@ -542,11 +548,14 @@ class connection_handle::impl : public std::enable_shared_from_this<connection_h
std::optional<std::string> bucket_name,
std::set<core::service_type> services)
{
std::optional<std::chrono::milliseconds> timeout{}; // not exposing timeout atm

auto barrier = std::make_shared<std::promise<core::diag::ping_result>>();
auto f = barrier->get_future();
cluster_->ping(std::move(report_id), std::move(bucket_name), std::move(services), [barrier](core::diag::ping_result&& resp) {
barrier->set_value(std::move(resp));
});
cluster_->ping(
std::move(report_id), std::move(bucket_name), std::move(services), timeout, [barrier](core::diag::ping_result&& resp) {
barrier->set_value(std::move(resp));
});
auto resp = f.get();
return { {}, std::move(resp) };
}
Expand All @@ -562,12 +571,12 @@ class connection_handle::impl : public std::enable_shared_from_this<connection_h

couchbase::collection collection(std::string_view bucket, std::string_view scope, std::string_view collection)
{
return couchbase::cluster(cluster_).bucket(bucket).scope(scope).collection(collection);
return couchbase::cluster(*cluster_).bucket(bucket).scope(scope).collection(collection);
}

private:
asio::io_context ctx_{};
std::shared_ptr<couchbase::core::cluster> cluster_{ couchbase::core::cluster::create(ctx_) };
std::shared_ptr<couchbase::core::cluster> cluster_{ std::make_shared<couchbase::core::cluster>(ctx_) };
std::thread worker;
core::origin origin_;
};
Expand Down Expand Up @@ -787,8 +796,7 @@ connection_handle::document_insert(zval* return_value,

auto [ctx, resp] =
impl_->collection(cb_string_new(bucket), cb_string_new(scope), cb_string_new(collection))
.insert<couchbase::php::passthrough_transcoder>(
cb_string_new(id), couchbase::codec::encoded_value{ cb_binary_new(value), static_cast<std::uint32_t>(flags) }, opts)
.insert(cb_string_new(id), couchbase::codec::encoded_value{ cb_binary_new(value), static_cast<std::uint32_t>(flags) }, opts)
.get();
if (ctx.ec()) {
return { ctx.ec(), ERROR_LOCATION, "unable to execute insert", build_error_context(ctx) };
Expand Down Expand Up @@ -835,8 +843,7 @@ connection_handle::document_replace(zval* return_value,

auto [ctx, resp] =
impl_->collection(cb_string_new(bucket), cb_string_new(scope), cb_string_new(collection))
.replace<couchbase::php::passthrough_transcoder>(
cb_string_new(id), couchbase::codec::encoded_value{ cb_binary_new(value), static_cast<std::uint32_t>(flags) }, opts)
.replace(cb_string_new(id), couchbase::codec::encoded_value{ cb_binary_new(value), static_cast<std::uint32_t>(flags) }, opts)
.get();
if (ctx.ec()) {
return { ctx.ec(), ERROR_LOCATION, "unable to execute replace", build_error_context(ctx) };
Expand Down
2 changes: 2 additions & 0 deletions src/wrapper/conversion_utilities.hxx
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@

#include <chrono>

#include <fmt/format.h>

namespace couchbase::transactions
{
class transaction_query_options;
Expand Down
2 changes: 1 addition & 1 deletion src/wrapper/scan_result_resource.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,7 @@ create_scan_result_resource(connection_handle* connection,
// Get operation agent
auto clust = connection->cluster();

auto agent_group = couchbase::core::agent_group(clust->io_context(), couchbase::core::agent_group_config{ { clust } });
auto agent_group = couchbase::core::agent_group(clust->io_context(), couchbase::core::agent_group_config{ { *clust } });
agent_group.open_bucket(bucket_name);
auto agent = agent_group.get_agent(bucket_name);
if (!agent.has_value()) {
Expand Down
3 changes: 1 addition & 2 deletions src/wrapper/transaction_context_resource.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -800,8 +800,7 @@ apply_options(transactions::transaction_options& config, zval* options)

ZEND_HASH_FOREACH_STR_KEY_VAL(Z_ARRVAL_P(options), key, value)
{
ASSIGN_DURATION_OPTION("timeout", config.expiration_time, key, value);
ASSIGN_DURATION_OPTION("keyValueTimeout", config.kv_timeout, key, value);
ASSIGN_DURATION_OPTION("timeout", config.timeout, key, value);
if (zend_binary_strcmp(ZSTR_VAL(key), ZSTR_LEN(key), ZEND_STRL("durabilityLevel")) == 0) {
if (value == nullptr || Z_TYPE_P(value) == IS_NULL) {
continue;
Expand Down
5 changes: 2 additions & 3 deletions src/wrapper/transactions_resource.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ class transactions_resource::impl : public std::enable_shared_from_this<transact
public:
impl(connection_handle* connection, const couchbase::transactions::transactions_config& config)
: cluster_{ connection->cluster() }
, transactions_(cluster_, config)
, transactions_(*cluster_, config)
{
}

Expand Down Expand Up @@ -153,8 +153,7 @@ apply_options(couchbase::transactions::transactions_config& config, zval* option

ZEND_HASH_FOREACH_STR_KEY_VAL(Z_ARRVAL_P(options), key, value)
{
ASSIGN_DURATION_OPTION("timeout", config.expiration_time, key, value);
ASSIGN_DURATION_OPTION("keyValueTimeout", config.kv_timeout, key, value);
ASSIGN_DURATION_OPTION("timeout", config.timeout, key, value);
if (zend_binary_strcmp(ZSTR_VAL(key), ZSTR_LEN(key), ZEND_STRL("durabilityLevel")) == 0) {
if (value == nullptr || Z_TYPE_P(value) == IS_NULL) {
continue;
Expand Down
5 changes: 5 additions & 0 deletions tests/Helpers/ServerVersion.php
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,11 @@ public function supportsUpdateCollectionMaxExpiry(): bool
return ($this->major == 7 && $this->minor >= 5) || $this->major > 7;
}

public function supportsDocNotLockedException(): bool
{
return ($this->major == 7 && $this->minor >= 6) || $this->major > 7;
}

/**
* @return int
*/
Expand Down
15 changes: 15 additions & 0 deletions tests/KeyValueLockingTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

declare(strict_types=1);

use Couchbase\Exception\DocumentNotLockedException;
use Couchbase\LookupGetFullSpec;
use Couchbase\LookupInOptions;
use Couchbase\UpsertOptions;
Expand Down Expand Up @@ -49,4 +50,18 @@ public function testPessimisticLockingWorkflow()
$res = $collection->get($id);
$this->assertEquals($lockedCas, $res->cas());
}

public function testUnlockingUnlockedDocumentThrowsDocNotLocked()
{
$this->skipIfProtostellar();
$this->skipIfUnsupported($this->version()->supportsDocNotLockedException());
$id = $this->uniqueId("foo");
$collection = $this->defaultCollection();

$collection->upsert($id, ["foo" => "bar"]);
$cas = $collection->get($id)->cas();

$this->expectException(DocumentNotLockedException::class);
$collection->unlock($id, $cas);
}
}