couchbase 3.0.2 → 3.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +2 -2
- data/ext/CMakeLists.txt +3 -0
- data/ext/build_version.hxx.in +1 -1
- data/ext/cmake/CompilerWarnings.cmake +1 -0
- data/ext/cmake/PreventInSourceBuilds.cmake +4 -1
- data/ext/couchbase/bucket.hxx +28 -2
- data/ext/couchbase/cluster.hxx +8 -2
- data/ext/couchbase/couchbase.cxx +955 -511
- data/ext/couchbase/error_context/analytics.hxx +46 -0
- data/ext/couchbase/error_context/http.hxx +44 -0
- data/ext/couchbase/error_context/key_value.hxx +47 -0
- data/ext/couchbase/error_context/query.hxx +46 -0
- data/ext/couchbase/error_context/search.hxx +47 -0
- data/ext/couchbase/error_context/view.hxx +47 -0
- data/ext/couchbase/io/dns_codec.hxx +1 -2
- data/ext/couchbase/io/http_command.hxx +16 -3
- data/ext/couchbase/io/http_context.hxx +1 -1
- data/ext/couchbase/io/http_session.hxx +12 -6
- data/ext/couchbase/io/http_session_manager.hxx +25 -24
- data/ext/couchbase/io/mcbp_session.hxx +8 -2
- data/ext/couchbase/io/retry_context.hxx +1 -1
- data/ext/couchbase/operations/analytics_dataset_create.hxx +19 -12
- data/ext/couchbase/operations/analytics_dataset_drop.hxx +18 -10
- data/ext/couchbase/operations/analytics_dataset_get_all.hxx +16 -10
- data/ext/couchbase/operations/analytics_dataverse_create.hxx +18 -11
- data/ext/couchbase/operations/analytics_dataverse_drop.hxx +17 -11
- data/ext/couchbase/operations/analytics_get_pending_mutations.hxx +17 -10
- data/ext/couchbase/operations/analytics_index_create.hxx +17 -11
- data/ext/couchbase/operations/analytics_index_drop.hxx +16 -10
- data/ext/couchbase/operations/analytics_index_get_all.hxx +14 -10
- data/ext/couchbase/operations/analytics_link_connect.hxx +15 -9
- data/ext/couchbase/operations/analytics_link_disconnect.hxx +16 -10
- data/ext/couchbase/operations/bucket_create.hxx +33 -10
- data/ext/couchbase/operations/bucket_drop.hxx +9 -8
- data/ext/couchbase/operations/bucket_flush.hxx +8 -8
- data/ext/couchbase/operations/bucket_get.hxx +15 -10
- data/ext/couchbase/operations/bucket_get_all.hxx +14 -7
- data/ext/couchbase/operations/bucket_settings.hxx +16 -0
- data/ext/couchbase/operations/bucket_update.hxx +32 -10
- data/ext/couchbase/operations/cluster_developer_preview_enable.hxx +6 -6
- data/ext/couchbase/operations/collection_create.hxx +19 -13
- data/ext/couchbase/operations/collection_drop.hxx +18 -12
- data/ext/couchbase/operations/collections_manifest_get.hxx +5 -10
- data/ext/couchbase/operations/document_analytics.hxx +39 -17
- data/ext/couchbase/operations/document_append.hxx +5 -10
- data/ext/couchbase/operations/document_decrement.hxx +5 -10
- data/ext/couchbase/operations/document_exists.hxx +4 -6
- data/ext/couchbase/operations/document_get.hxx +6 -10
- data/ext/couchbase/operations/document_get_and_lock.hxx +4 -9
- data/ext/couchbase/operations/document_get_and_touch.hxx +4 -9
- data/ext/couchbase/operations/document_get_projected.hxx +21 -14
- data/ext/couchbase/operations/document_increment.hxx +5 -10
- data/ext/couchbase/operations/document_insert.hxx +5 -10
- data/ext/couchbase/operations/document_lookup_in.hxx +4 -9
- data/ext/couchbase/operations/document_mutate_in.hxx +7 -12
- data/ext/couchbase/operations/document_prepend.hxx +5 -10
- data/ext/couchbase/operations/document_query.hxx +45 -28
- data/ext/couchbase/operations/document_remove.hxx +5 -10
- data/ext/couchbase/operations/document_replace.hxx +5 -10
- data/ext/couchbase/operations/document_search.hxx +37 -16
- data/ext/couchbase/operations/document_touch.hxx +4 -9
- data/ext/couchbase/operations/document_unlock.hxx +4 -9
- data/ext/couchbase/operations/document_upsert.hxx +5 -10
- data/ext/couchbase/operations/document_view.hxx +29 -13
- data/ext/couchbase/operations/group_drop.hxx +7 -7
- data/ext/couchbase/operations/group_get.hxx +14 -10
- data/ext/couchbase/operations/group_get_all.hxx +14 -8
- data/ext/couchbase/operations/group_upsert.hxx +15 -9
- data/ext/couchbase/operations/http_noop.hxx +5 -5
- data/ext/couchbase/operations/mcbp_noop.hxx +3 -9
- data/ext/couchbase/operations/query_index_build_deferred.hxx +15 -9
- data/ext/couchbase/operations/query_index_create.hxx +16 -10
- data/ext/couchbase/operations/query_index_drop.hxx +16 -10
- data/ext/couchbase/operations/query_index_get_all.hxx +13 -7
- data/ext/couchbase/operations/role_get_all.hxx +14 -8
- data/ext/couchbase/operations/scope_create.hxx +19 -13
- data/ext/couchbase/operations/scope_drop.hxx +17 -11
- data/ext/couchbase/operations/scope_get_all.hxx +15 -10
- data/ext/couchbase/operations/search_get_stats.hxx +5 -5
- data/ext/couchbase/operations/search_index_analyze_document.hxx +25 -13
- data/ext/couchbase/operations/search_index_control_ingest.hxx +23 -11
- data/ext/couchbase/operations/search_index_control_plan_freeze.hxx +23 -11
- data/ext/couchbase/operations/search_index_control_query.hxx +23 -11
- data/ext/couchbase/operations/search_index_drop.hxx +22 -10
- data/ext/couchbase/operations/search_index_get.hxx +22 -10
- data/ext/couchbase/operations/search_index_get_all.hxx +13 -7
- data/ext/couchbase/operations/search_index_get_documents_count.hxx +24 -13
- data/ext/couchbase/operations/search_index_get_stats.hxx +16 -10
- data/ext/couchbase/operations/search_index_upsert.hxx +23 -11
- data/ext/couchbase/operations/user_drop.hxx +8 -8
- data/ext/couchbase/operations/user_get.hxx +14 -10
- data/ext/couchbase/operations/user_get_all.hxx +14 -8
- data/ext/couchbase/operations/user_upsert.hxx +15 -9
- data/ext/couchbase/operations/view_index_drop.hxx +7 -7
- data/ext/couchbase/operations/view_index_get.hxx +15 -9
- data/ext/couchbase/operations/view_index_get_all.hxx +15 -9
- data/ext/couchbase/operations/view_index_upsert.hxx +8 -8
- data/ext/couchbase/origin.hxx +1 -0
- data/ext/couchbase/platform/terminate_handler.cc +12 -8
- data/ext/couchbase/protocol/client_request.hxx +2 -1
- data/ext/couchbase/protocol/client_response.hxx +18 -15
- data/ext/couchbase/protocol/cmd_exists.hxx +1 -0
- data/ext/couchbase/protocol/cmd_get.hxx +1 -1
- data/ext/couchbase/protocol/cmd_mutate_in.hxx +3 -4
- data/ext/couchbase/protocol/enhanced_error_info.hxx +28 -0
- data/ext/couchbase/utils/connection_string.hxx +1 -1
- data/ext/couchbase/version.hxx +1 -1
- data/ext/extconf.rb +1 -1
- data/ext/test/test_native_binary_operations.cxx +18 -18
- data/ext/test/test_native_diagnostics.cxx +2 -2
- data/ext/test/test_native_trivial_crud.cxx +2 -2
- data/ext/third_party/json/include/tao/json/external/pegtl/internal/file_reader.hpp +1 -5
- data/lib/active_support/cache/couchbase_store.rb +362 -0
- data/lib/couchbase.rb +2 -0
- data/lib/couchbase/authenticator.rb +26 -0
- data/lib/couchbase/binary_collection.rb +1 -0
- data/lib/couchbase/bucket.rb +1 -0
- data/lib/couchbase/cluster.rb +51 -27
- data/lib/couchbase/collection.rb +19 -4
- data/lib/couchbase/collection_options.rb +10 -0
- data/lib/couchbase/configuration.rb +57 -0
- data/lib/couchbase/datastructures.rb +6 -0
- data/lib/couchbase/errors.rb +111 -3
- data/lib/couchbase/management.rb +27 -0
- data/lib/couchbase/management/bucket_manager.rb +9 -2
- data/lib/couchbase/management/collection_manager.rb +1 -1
- data/lib/couchbase/management/user_manager.rb +18 -2
- data/lib/couchbase/options.rb +33 -23
- data/lib/couchbase/railtie.rb +45 -0
- data/lib/couchbase/scope.rb +44 -3
- data/lib/couchbase/utils.rb +21 -0
- data/lib/couchbase/utils/time.rb +52 -0
- data/lib/couchbase/version.rb +1 -1
- data/lib/rails/generators/couchbase/config/config_generator.rb +27 -0
- metadata +19 -5
|
@@ -27,6 +27,7 @@
|
|
|
27
27
|
#include <protocol/client_opcode.hxx>
|
|
28
28
|
#include <protocol/magic.hxx>
|
|
29
29
|
#include <protocol/client_response.hxx>
|
|
30
|
+
#include <utils/byteswap.hxx>
|
|
30
31
|
|
|
31
32
|
namespace couchbase::protocol
|
|
32
33
|
{
|
|
@@ -61,7 +62,7 @@ class client_request
|
|
|
61
62
|
|
|
62
63
|
void cas(std::uint64_t val)
|
|
63
64
|
{
|
|
64
|
-
cas_ = val;
|
|
65
|
+
cas_ = utils::byte_swap_64(val);
|
|
65
66
|
}
|
|
66
67
|
|
|
67
68
|
std::uint32_t opaque()
|
|
@@ -27,15 +27,12 @@
|
|
|
27
27
|
#include <protocol/datatype.hxx>
|
|
28
28
|
#include <protocol/cmd_info.hxx>
|
|
29
29
|
#include <protocol/frame_info_id.hxx>
|
|
30
|
+
#include <protocol/enhanced_error_info.hxx>
|
|
31
|
+
#include <utils/byteswap.hxx>
|
|
30
32
|
|
|
31
33
|
namespace couchbase::protocol
|
|
32
34
|
{
|
|
33
35
|
|
|
34
|
-
struct enhanced_error {
|
|
35
|
-
std::string context;
|
|
36
|
-
std::string ref;
|
|
37
|
-
};
|
|
38
|
-
|
|
39
36
|
template<typename Body>
|
|
40
37
|
class client_response
|
|
41
38
|
{
|
|
@@ -51,7 +48,7 @@ class client_response
|
|
|
51
48
|
std::uint8_t extras_size_{ 0 };
|
|
52
49
|
std::size_t body_size_{ 0 };
|
|
53
50
|
protocol::status status_{};
|
|
54
|
-
std::optional<
|
|
51
|
+
std::optional<enhanced_error_info> error_;
|
|
55
52
|
std::uint32_t opaque_{};
|
|
56
53
|
std::uint64_t cas_{};
|
|
57
54
|
cmd_info info_{};
|
|
@@ -138,9 +135,15 @@ class client_response
|
|
|
138
135
|
memcpy(&opaque_, header_.data() + 12, sizeof(opaque_));
|
|
139
136
|
|
|
140
137
|
memcpy(&cas_, header_.data() + 16, sizeof(cas_));
|
|
138
|
+
cas_ = utils::byte_swap_64(cas_);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
[[nodiscard]] std::optional<enhanced_error_info> error_info()
|
|
142
|
+
{
|
|
143
|
+
return error_;
|
|
141
144
|
}
|
|
142
145
|
|
|
143
|
-
std::string error_message()
|
|
146
|
+
[[nodiscard]] std::string error_message()
|
|
144
147
|
{
|
|
145
148
|
if (error_) {
|
|
146
149
|
return fmt::format(R"(magic={}, opcode={}, status={}, error={})", magic_, opcode_, status_, *error_);
|
|
@@ -157,10 +160,10 @@ class client_response
|
|
|
157
160
|
if (error.is_object()) {
|
|
158
161
|
auto& err_obj = error["error"];
|
|
159
162
|
if (err_obj.is_object()) {
|
|
160
|
-
|
|
163
|
+
enhanced_error_info err{};
|
|
161
164
|
auto& ref = err_obj["ref"];
|
|
162
165
|
if (ref.is_string()) {
|
|
163
|
-
err.
|
|
166
|
+
err.reference = ref.get_string();
|
|
164
167
|
}
|
|
165
168
|
auto& ctx = err_obj["context"];
|
|
166
169
|
if (ctx.is_string()) {
|
|
@@ -202,14 +205,14 @@ class client_response
|
|
|
202
205
|
} // namespace couchbase::protocol
|
|
203
206
|
|
|
204
207
|
template<>
|
|
205
|
-
struct fmt::formatter<couchbase::protocol::
|
|
208
|
+
struct fmt::formatter<couchbase::protocol::enhanced_error_info> : formatter<std::string> {
|
|
206
209
|
template<typename FormatContext>
|
|
207
|
-
auto format(const couchbase::protocol::
|
|
210
|
+
auto format(const couchbase::protocol::enhanced_error_info& error, FormatContext& ctx)
|
|
208
211
|
{
|
|
209
|
-
if (!error.
|
|
210
|
-
format_to(ctx.out(), R"((ref: "{}", ctx: "{}"))", error.
|
|
211
|
-
} else if (!error.
|
|
212
|
-
format_to(ctx.out(), R"((ref: "{}"))", error.
|
|
212
|
+
if (!error.reference.empty() && !error.context.empty()) {
|
|
213
|
+
format_to(ctx.out(), R"((ref: "{}", ctx: "{}"))", error.reference, error.context);
|
|
214
|
+
} else if (!error.reference.empty()) {
|
|
215
|
+
format_to(ctx.out(), R"((ref: "{}"))", error.reference);
|
|
213
216
|
} else if (!error.context.empty()) {
|
|
214
217
|
format_to(ctx.out(), R"((ctx: "{}"))", error.context);
|
|
215
218
|
}
|
|
@@ -31,9 +31,9 @@ class mutate_in_response_body
|
|
|
31
31
|
static const inline client_opcode opcode = client_opcode::subdoc_multi_mutation;
|
|
32
32
|
|
|
33
33
|
struct mutate_in_field {
|
|
34
|
-
std::uint8_t index;
|
|
35
|
-
protocol::status status;
|
|
36
|
-
std::string value;
|
|
34
|
+
std::uint8_t index{};
|
|
35
|
+
protocol::status status{};
|
|
36
|
+
std::string value{};
|
|
37
37
|
};
|
|
38
38
|
|
|
39
39
|
private:
|
|
@@ -80,7 +80,6 @@ class mutate_in_response_body
|
|
|
80
80
|
mutate_in_field field;
|
|
81
81
|
|
|
82
82
|
field.index = body[static_cast<std::size_t>(offset)];
|
|
83
|
-
Expects(field.index < 16);
|
|
84
83
|
offset++;
|
|
85
84
|
|
|
86
85
|
std::uint16_t entry_status = 0;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/* -*- Mode: C++; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
|
|
2
|
+
/*
|
|
3
|
+
* Copyright 2020 Couchbase, Inc.
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
* you may not use this file except in compliance with the License.
|
|
7
|
+
* You may obtain a copy of the License at
|
|
8
|
+
*
|
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
*
|
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
* See the License for the specific language governing permissions and
|
|
15
|
+
* limitations under the License.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
#pragma once
|
|
19
|
+
|
|
20
|
+
namespace couchbase::protocol
|
|
21
|
+
{
|
|
22
|
+
|
|
23
|
+
struct enhanced_error_info {
|
|
24
|
+
std::string reference;
|
|
25
|
+
std::string context;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
} // namespace couchbase::protocol
|
|
@@ -86,7 +86,7 @@ using opt_bucket_name = opt_must<one<'/'>, bucket_name>;
|
|
|
86
86
|
using opt_params = opt_must<one<'?'>, list_must<param, one<'&'>>>;
|
|
87
87
|
using opt_nodes = seq<list_must<node, one<',', ';'>>, opt_bucket_name>;
|
|
88
88
|
|
|
89
|
-
using grammar = must<seq<uri::scheme, one<':'>, uri::dslash, opt_nodes, opt_params, eof>>;
|
|
89
|
+
using grammar = must<seq<uri::scheme, one<':'>, uri::dslash, opt_nodes, opt_params, tao::json::pegtl::eof>>;
|
|
90
90
|
|
|
91
91
|
template<typename Rule>
|
|
92
92
|
struct action {
|
data/ext/couchbase/version.hxx
CHANGED
data/ext/extconf.rb
CHANGED
|
@@ -64,7 +64,7 @@ FileUtils.rm_rf(build_dir, verbose: true) unless ENV['CB_PRESERVE_BUILD_DIR']
|
|
|
64
64
|
FileUtils.mkdir_p(build_dir, verbose: true)
|
|
65
65
|
Dir.chdir(build_dir) do
|
|
66
66
|
puts "-- build #{build_type} extension #{SDK_VERSION} for ruby #{RUBY_VERSION}-#{RUBY_PATCHLEVEL}-#{RUBY_PLATFORM}"
|
|
67
|
-
sys(cmake, *cmake_flags, project_path)
|
|
67
|
+
sys(cmake, *cmake_flags, "-B#{build_dir}", "-S#{project_path}")
|
|
68
68
|
number_of_jobs = (ENV["CB_NUMBER_OF_JOBS"] || 4).to_s
|
|
69
69
|
sys(cmake, "--build", build_dir, "--parallel", number_of_jobs, "--verbose")
|
|
70
70
|
end
|
|
@@ -59,10 +59,10 @@ TEST_CASE("native: append", "[native]")
|
|
|
59
59
|
couchbase::operations::upsert_request req{ id, "world" };
|
|
60
60
|
auto barrier = std::make_shared<std::promise<couchbase::operations::upsert_response>>();
|
|
61
61
|
auto f = barrier->get_future();
|
|
62
|
-
cluster.execute(req, [barrier](couchbase::operations::upsert_response resp) mutable { barrier->set_value(resp); });
|
|
62
|
+
cluster.execute(req, [barrier](couchbase::operations::upsert_response&& resp) mutable { barrier->set_value(resp); });
|
|
63
63
|
auto resp = f.get();
|
|
64
|
-
INFO(resp.ec.message());
|
|
65
|
-
REQUIRE_FALSE(resp.ec);
|
|
64
|
+
INFO(resp.ctx.ec.message());
|
|
65
|
+
REQUIRE_FALSE(resp.ctx.ec);
|
|
66
66
|
INFO("rc=" << resp.cas);
|
|
67
67
|
REQUIRE(resp.cas != 0);
|
|
68
68
|
INFO("seqno=" << resp.token.sequence_number);
|
|
@@ -72,10 +72,10 @@ TEST_CASE("native: append", "[native]")
|
|
|
72
72
|
couchbase::operations::append_request req{ id, "!" };
|
|
73
73
|
auto barrier = std::make_shared<std::promise<couchbase::operations::append_response>>();
|
|
74
74
|
auto f = barrier->get_future();
|
|
75
|
-
cluster.execute(req, [barrier](couchbase::operations::append_response resp) mutable { barrier->set_value(resp); });
|
|
75
|
+
cluster.execute(req, [barrier](couchbase::operations::append_response&& resp) mutable { barrier->set_value(resp); });
|
|
76
76
|
auto resp = f.get();
|
|
77
|
-
INFO(resp.ec.message());
|
|
78
|
-
REQUIRE_FALSE(resp.ec);
|
|
77
|
+
INFO(resp.ctx.ec.message());
|
|
78
|
+
REQUIRE_FALSE(resp.ctx.ec);
|
|
79
79
|
INFO("rc=" << resp.cas);
|
|
80
80
|
REQUIRE(resp.cas != 0);
|
|
81
81
|
INFO("seqno=" << resp.token.sequence_number);
|
|
@@ -85,10 +85,10 @@ TEST_CASE("native: append", "[native]")
|
|
|
85
85
|
couchbase::operations::get_request req{ id };
|
|
86
86
|
auto barrier = std::make_shared<std::promise<couchbase::operations::get_response>>();
|
|
87
87
|
auto f = barrier->get_future();
|
|
88
|
-
cluster.execute(req, [barrier](couchbase::operations::get_response resp) mutable { barrier->set_value(resp); });
|
|
88
|
+
cluster.execute(req, [barrier](couchbase::operations::get_response&& resp) mutable { barrier->set_value(resp); });
|
|
89
89
|
auto resp = f.get();
|
|
90
|
-
INFO(resp.ec.message());
|
|
91
|
-
REQUIRE_FALSE(resp.ec);
|
|
90
|
+
INFO(resp.ctx.ec.message());
|
|
91
|
+
REQUIRE_FALSE(resp.ctx.ec);
|
|
92
92
|
INFO("rc=" << resp.cas);
|
|
93
93
|
REQUIRE(resp.cas != 0);
|
|
94
94
|
INFO("value=" << resp.value);
|
|
@@ -140,10 +140,10 @@ TEST_CASE("native: prepend", "[native]")
|
|
|
140
140
|
couchbase::operations::upsert_request req{ id, "world" };
|
|
141
141
|
auto barrier = std::make_shared<std::promise<couchbase::operations::upsert_response>>();
|
|
142
142
|
auto f = barrier->get_future();
|
|
143
|
-
cluster.execute(req, [barrier](couchbase::operations::upsert_response resp) mutable { barrier->set_value(resp); });
|
|
143
|
+
cluster.execute(req, [barrier](couchbase::operations::upsert_response&& resp) mutable { barrier->set_value(resp); });
|
|
144
144
|
auto resp = f.get();
|
|
145
|
-
INFO(resp.ec.message());
|
|
146
|
-
REQUIRE_FALSE(resp.ec);
|
|
145
|
+
INFO(resp.ctx.ec.message());
|
|
146
|
+
REQUIRE_FALSE(resp.ctx.ec);
|
|
147
147
|
INFO("rc=" << resp.cas);
|
|
148
148
|
REQUIRE(resp.cas != 0);
|
|
149
149
|
INFO("seqno=" << resp.token.sequence_number);
|
|
@@ -153,10 +153,10 @@ TEST_CASE("native: prepend", "[native]")
|
|
|
153
153
|
couchbase::operations::prepend_request req{ id, "Hello, " };
|
|
154
154
|
auto barrier = std::make_shared<std::promise<couchbase::operations::prepend_response>>();
|
|
155
155
|
auto f = barrier->get_future();
|
|
156
|
-
cluster.execute(req, [barrier](couchbase::operations::prepend_response resp) mutable { barrier->set_value(resp); });
|
|
156
|
+
cluster.execute(req, [barrier](couchbase::operations::prepend_response&& resp) mutable { barrier->set_value(resp); });
|
|
157
157
|
auto resp = f.get();
|
|
158
|
-
INFO(resp.ec.message());
|
|
159
|
-
REQUIRE_FALSE(resp.ec);
|
|
158
|
+
INFO(resp.ctx.ec.message());
|
|
159
|
+
REQUIRE_FALSE(resp.ctx.ec);
|
|
160
160
|
INFO("rc=" << resp.cas);
|
|
161
161
|
REQUIRE(resp.cas != 0);
|
|
162
162
|
INFO("seqno=" << resp.token.sequence_number);
|
|
@@ -166,10 +166,10 @@ TEST_CASE("native: prepend", "[native]")
|
|
|
166
166
|
couchbase::operations::get_request req{ id };
|
|
167
167
|
auto barrier = std::make_shared<std::promise<couchbase::operations::get_response>>();
|
|
168
168
|
auto f = barrier->get_future();
|
|
169
|
-
cluster.execute(req, [barrier](couchbase::operations::get_response resp) mutable { barrier->set_value(resp); });
|
|
169
|
+
cluster.execute(req, [barrier](couchbase::operations::get_response&& resp) mutable { barrier->set_value(resp); });
|
|
170
170
|
auto resp = f.get();
|
|
171
|
-
INFO(resp.ec.message());
|
|
172
|
-
REQUIRE_FALSE(resp.ec);
|
|
171
|
+
INFO(resp.ctx.ec.message());
|
|
172
|
+
REQUIRE_FALSE(resp.ctx.ec);
|
|
173
173
|
INFO("rc=" << resp.cas);
|
|
174
174
|
REQUIRE(resp.cas != 0);
|
|
175
175
|
INFO("value=" << resp.value);
|
|
@@ -355,8 +355,8 @@ TEST_CASE("native: fetch diagnostics after N1QL query", "[native]")
|
|
|
355
355
|
auto f = barrier->get_future();
|
|
356
356
|
cluster.execute_http(req, [barrier](couchbase::operations::query_response&& resp) mutable { barrier->set_value(resp); });
|
|
357
357
|
auto resp = f.get();
|
|
358
|
-
INFO(resp.ec.message());
|
|
359
|
-
REQUIRE_FALSE(resp.ec);
|
|
358
|
+
INFO(resp.ctx.ec.message());
|
|
359
|
+
REQUIRE_FALSE(resp.ctx.ec);
|
|
360
360
|
INFO("rows.size() =" << resp.payload.rows.size());
|
|
361
361
|
REQUIRE(resp.payload.rows.size() == 1);
|
|
362
362
|
INFO("row=" << resp.payload.rows[0]);
|
|
@@ -65,8 +65,8 @@ TEST_CASE("native: upsert document into default collection", "[native]")
|
|
|
65
65
|
auto f = barrier->get_future();
|
|
66
66
|
cluster.execute(req, [barrier](couchbase::operations::upsert_response resp) mutable { barrier->set_value(resp); });
|
|
67
67
|
auto resp = f.get();
|
|
68
|
-
INFO(resp.ec.message());
|
|
69
|
-
REQUIRE_FALSE(resp.ec);
|
|
68
|
+
INFO(resp.ctx.ec.message());
|
|
69
|
+
REQUIRE_FALSE(resp.ctx.ec);
|
|
70
70
|
INFO("rc=" << resp.cas);
|
|
71
71
|
REQUIRE(resp.cas != 0);
|
|
72
72
|
INFO("seqno=" << resp.token.sequence_number);
|
|
@@ -17,19 +17,15 @@ namespace TAO_JSON_PEGTL_NAMESPACE::internal
|
|
|
17
17
|
[[nodiscard]] inline std::FILE* file_open( const std::filesystem::path& path )
|
|
18
18
|
{
|
|
19
19
|
errno = 0;
|
|
20
|
-
#if defined( _MSC_VER )
|
|
20
|
+
#if defined( _MSC_VER ) || defined( __MINGW32__ )
|
|
21
21
|
std::FILE* file;
|
|
22
22
|
if( ::_wfopen_s( &file, path.c_str(), L"rb" ) == 0 ) {
|
|
23
23
|
return file;
|
|
24
24
|
}
|
|
25
25
|
const std::error_code ec( errno, std::system_category() );
|
|
26
26
|
throw std::filesystem::filesystem_error( "_wfopen_s() failed", path, ec );
|
|
27
|
-
#else
|
|
28
|
-
#if defined( __MINGW32__ )
|
|
29
|
-
if( auto* file = std::fopen( path.c_str(), "rb" ) )
|
|
30
27
|
#else
|
|
31
28
|
if( auto* file = std::fopen( path.c_str(), "rbe" ) )
|
|
32
|
-
#endif
|
|
33
29
|
{
|
|
34
30
|
return file;
|
|
35
31
|
}
|
|
@@ -0,0 +1,362 @@
|
|
|
1
|
+
# Copyright 2020 Couchbase, Inc.
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
require "couchbase"
|
|
16
|
+
|
|
17
|
+
module ActiveSupport
|
|
18
|
+
module Cache
|
|
19
|
+
# A cache store implementation which stores data in Couchbase: https://couchbase.com
|
|
20
|
+
#
|
|
21
|
+
# * Local cache. Hot in-memory primary cache within block/middleware scope.
|
|
22
|
+
# * +read_multi+ and +write_multi+ support.
|
|
23
|
+
# * +delete_matched+ support using N1QL queries.
|
|
24
|
+
# * +clear+ for erasing whole collection (optionally can flush the bucket).
|
|
25
|
+
#
|
|
26
|
+
# To use this store, add the select it in application config
|
|
27
|
+
#
|
|
28
|
+
# config.cache_store = :couchbase_store, {
|
|
29
|
+
# connection_string: "couchbase://localhost",
|
|
30
|
+
# username: "app_cache_user",
|
|
31
|
+
# password: "s3cret",
|
|
32
|
+
# bucket: "app_cache"
|
|
33
|
+
# }
|
|
34
|
+
#
|
|
35
|
+
# @see https://guides.rubyonrails.org/caching_with_rails.html#cache-stores
|
|
36
|
+
class CouchbaseStore < Store
|
|
37
|
+
MAX_KEY_BYTESIZE = 250
|
|
38
|
+
DEFAULT_ERROR_HANDLER = lambda do |method:, returning:, exception:, logger: CouchbaseStore.logger|
|
|
39
|
+
logger&.error { "CouchbaseStore: #{method} failed, returned #{returning.inspect}: #{exception.class}: #{exception.message}" }
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Advertise cache versioning support.
|
|
43
|
+
def self.supports_cache_versioning?
|
|
44
|
+
true
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
module LocalCacheWithRaw # :nodoc:
|
|
48
|
+
private
|
|
49
|
+
|
|
50
|
+
def write_entry(key, entry, **options)
|
|
51
|
+
if options[:raw] && local_cache
|
|
52
|
+
raw_entry = Entry.new(entry.value.to_s)
|
|
53
|
+
raw_entry.expires_at = entry.expires_at
|
|
54
|
+
super(key, raw_entry, **options)
|
|
55
|
+
else
|
|
56
|
+
super
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def write_multi_entries(entries, **options)
|
|
61
|
+
if options[:raw] && local_cache
|
|
62
|
+
raw_entries = entries.map do |_key, entry|
|
|
63
|
+
raw_entry = Entry.new(serialize_entry(entry, raw: true))
|
|
64
|
+
raw_entry.expires_at = entry.expires_at
|
|
65
|
+
end.to_h
|
|
66
|
+
|
|
67
|
+
super(raw_entries, **options)
|
|
68
|
+
else
|
|
69
|
+
super
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
prepend Strategy::LocalCache
|
|
75
|
+
prepend LocalCacheWithRaw
|
|
76
|
+
|
|
77
|
+
def initialize(options = nil)
|
|
78
|
+
super
|
|
79
|
+
@error_handler = options.delete(:error_handler) { DEFAULT_ERROR_HANDLER }
|
|
80
|
+
@couchbase_options = {}
|
|
81
|
+
@couchbase_options[:connection_string] =
|
|
82
|
+
@options.delete(:connection_string) do
|
|
83
|
+
raise ArgumentError, "Missing connection string for Couchbase cache store. Use :connection_string in the store options"
|
|
84
|
+
end
|
|
85
|
+
@couchbase_options[:username] =
|
|
86
|
+
@options.delete(:username) do
|
|
87
|
+
raise ArgumentError, "Missing username for Couchbase cache store. Use :username in the store options"
|
|
88
|
+
end
|
|
89
|
+
@couchbase_options[:password] =
|
|
90
|
+
@options.delete(:password) do
|
|
91
|
+
raise ArgumentError, "Missing password for Couchbase cache store. Use :password in the store options"
|
|
92
|
+
end
|
|
93
|
+
@couchbase_options[:bucket] =
|
|
94
|
+
@options.delete(:bucket) { raise ArgumentError, "Missing bucket for Couchbase cache store. Use :bucket in the store options" }
|
|
95
|
+
@couchbase_options[:scope] = @options.delete(:scope) if @options.key?(:scope)
|
|
96
|
+
@couchbase_options[:collection] = @options.delete(:collection) if @options.key?(:collection)
|
|
97
|
+
@last_mutation_token = nil
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def collection
|
|
101
|
+
@collection ||= build_collection
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def cluster
|
|
105
|
+
@cluster ||= build_cluster
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def inspect
|
|
109
|
+
"#<#{self.class} options=#{options.inspect} collection=#{@collection.inspect}>"
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# Deletes all entries with keys matching the regular expression.
|
|
113
|
+
#
|
|
114
|
+
# The +matcher+ must be valid pattern for N1QL +REGEXP_MATCHES+ function. More info at
|
|
115
|
+
# https://docs.couchbase.com/server/current/n1ql/n1ql-language-reference/patternmatchingfun.html#section_regex_matches
|
|
116
|
+
#
|
|
117
|
+
# Because the operation performed on query engine, and it might take time to propagate changes
|
|
118
|
+
# from key/value engine to the indexer. Therefore the keys, that were created a moment ago
|
|
119
|
+
# might not be deleted.
|
|
120
|
+
#
|
|
121
|
+
# Also this method assumes, that primary index created on the target bucket
|
|
122
|
+
def delete_matched(matcher, _options = nil)
|
|
123
|
+
pattern =
|
|
124
|
+
case matcher
|
|
125
|
+
when Regexp
|
|
126
|
+
matcher.inspect[1..-2]
|
|
127
|
+
when String
|
|
128
|
+
matcher.tr("?", ".").gsub("*", ".*")
|
|
129
|
+
else
|
|
130
|
+
raise NotImplementedError, "Unable to convert #{matcher.inspect} to Regexp pattern"
|
|
131
|
+
end
|
|
132
|
+
operation_options = ::Couchbase::Options::Query(named_parameters: {"pattern" => pattern})
|
|
133
|
+
operation_options.consistent_with(::Couchbase::MutationState.new(@last_mutation_token)) if @last_mutation_token
|
|
134
|
+
begin
|
|
135
|
+
cluster.query("DELETE FROM #{scope_qualifier} cache WHERE REGEXP_MATCHES(META(cache).id, $pattern)", operation_options)
|
|
136
|
+
rescue ::Couchbase::Error::ParsingFailure, ::Couchbase::Error::ServiceNotAvailable
|
|
137
|
+
raise NotImplementedError, "The server does not support delete_matched operation"
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
# Increments an integer value in the cache.
|
|
142
|
+
#
|
|
143
|
+
# Note that it uses binary collection interface, therefore will fail if the value is not
|
|
144
|
+
# represented as ASCII-encoded number using +:raw+.
|
|
145
|
+
def increment(name, amount = 1, expires_in: nil, initial: nil, **options)
|
|
146
|
+
instrument :increment, name, amount: amount do
|
|
147
|
+
failsafe :increment do
|
|
148
|
+
key = normalize_key(name, options)
|
|
149
|
+
res = collection.binary.increment(
|
|
150
|
+
key, ::Couchbase::Options::Increment(delta: amount, expiry: expires_in, initial: initial)
|
|
151
|
+
)
|
|
152
|
+
@last_mutation_token = res.mutation_token
|
|
153
|
+
res.content
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
# Decrements an integer value in the cache.
|
|
159
|
+
#
|
|
160
|
+
# Note that it uses binary collection interface, therefore will fail if the value is not
|
|
161
|
+
# represented as ASCII-encoded number using +:raw+.
|
|
162
|
+
#
|
|
163
|
+
# Note that the counter represented by non-negative number on the server, and it will not
|
|
164
|
+
# cycle to maximum integer when decrementing zero.
|
|
165
|
+
def decrement(name, amount = 1, expires_in: nil, initial: nil, **_options)
|
|
166
|
+
instrument :decrement, name, amount: amount do
|
|
167
|
+
failsafe :decrement do
|
|
168
|
+
key = normalize_key(name, options)
|
|
169
|
+
res = collection.binary.decrement(
|
|
170
|
+
key, ::Couchbase::Options::Decrement(delta: amount, expiry: expires_in, initial: initial)
|
|
171
|
+
)
|
|
172
|
+
@last_mutation_token = res.mutation_token
|
|
173
|
+
res.content
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
# Clears the entire cache. Be careful with this method since it could
|
|
179
|
+
# affect other processes if shared cache is being used.
|
|
180
|
+
#
|
|
181
|
+
# When +use_flush+ option set to +true+ it will flush the bucket. Otherwise, it uses N1QL query
|
|
182
|
+
# and relies on default index.
|
|
183
|
+
def clear(use_flush: false, **_options)
|
|
184
|
+
failsafe(:clear) do
|
|
185
|
+
if use_flush
|
|
186
|
+
cluster.buckets.flush_bucket(@couchbase_options[:bucket_name])
|
|
187
|
+
else
|
|
188
|
+
operation_options = ::Couchbase::Options::Query.new
|
|
189
|
+
operation_options.consistent_with(::Couchbase::MutationState.new(@last_mutation_token)) if @last_mutation_token
|
|
190
|
+
cluster.query("DELETE FROM #{scope_qualifier}", operation_options)
|
|
191
|
+
end
|
|
192
|
+
end
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
private
|
|
196
|
+
|
|
197
|
+
def deserialize_entry(payload, raw:)
|
|
198
|
+
if payload && raw
|
|
199
|
+
Entry.new(payload, compress: false)
|
|
200
|
+
else
|
|
201
|
+
super(payload)
|
|
202
|
+
end
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
def serialize_entry(entry, raw: false)
|
|
206
|
+
if raw
|
|
207
|
+
entry.value.to_s
|
|
208
|
+
else
|
|
209
|
+
super(entry)
|
|
210
|
+
end
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
# Reads an entry from the cache
|
|
214
|
+
def read_entry(key, **options)
|
|
215
|
+
failsafe(:read_entry, returning: nil) do
|
|
216
|
+
result = collection.get(key, ::Couchbase::Options::Get(transcoder: nil))
|
|
217
|
+
deserialize_entry(result.content, raw: options&.fetch(:raw, false))
|
|
218
|
+
end
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
# Reads multiple entries from the cache implementation. Subclasses MAY
|
|
222
|
+
# implement this method.
|
|
223
|
+
def read_multi_entries(names, **options)
|
|
224
|
+
return {} if names.empty?
|
|
225
|
+
|
|
226
|
+
keys = names.map { |name| normalize_key(name, options) }
|
|
227
|
+
return_value = {}
|
|
228
|
+
failsafe(:read_multi_entries, returning: return_value) do
|
|
229
|
+
results = collection.get_multi(keys, ::Couchbase::Options::GetMulti(transcoder: nil))
|
|
230
|
+
results.each_with_index do |result, index|
|
|
231
|
+
next unless result.success?
|
|
232
|
+
|
|
233
|
+
entry = deserialize_entry(result.content, raw: options[:raw])
|
|
234
|
+
unless entry.nil? || entry.expired? || entry.mismatched?(normalize_version(names[index], options))
|
|
235
|
+
return_value[names[index]] = entry.value
|
|
236
|
+
end
|
|
237
|
+
end
|
|
238
|
+
return_value
|
|
239
|
+
end
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
# Writes an entry to the cache
|
|
243
|
+
def write_entry(key, entry, raw: false, expires_in: nil, race_condition_ttl: nil, **_options)
|
|
244
|
+
if race_condition_ttl && expires_in && expires_in.positive? && !raw
|
|
245
|
+
# Add few minutes to expiry in the future to support race condition TTLs on read
|
|
246
|
+
expires_in += 5.minutes
|
|
247
|
+
end
|
|
248
|
+
failsafe(:write_entry, returning: false) do
|
|
249
|
+
res = collection.upsert(key, serialize_entry(entry, raw: raw),
|
|
250
|
+
::Couchbase::Options::Upsert(transcoder: nil, expiry: expires_in))
|
|
251
|
+
@last_mutation_token = res.mutation_token
|
|
252
|
+
true
|
|
253
|
+
end
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
def write_multi_entries(hash, **options)
|
|
257
|
+
return 0 if hash.empty?
|
|
258
|
+
|
|
259
|
+
return super if local_cache
|
|
260
|
+
|
|
261
|
+
expires_in = options[:expires_in]
|
|
262
|
+
if options[:race_condition_ttl] && expires_in && expires_in.positive?
|
|
263
|
+
# Add few minutes to expiry in the future to support race condition TTLs on read
|
|
264
|
+
expires_in += 5.minutes
|
|
265
|
+
end
|
|
266
|
+
operation_options = ::Couchbase::Options::UpsertMulti(transcoder: nil, expiry: expires_in)
|
|
267
|
+
|
|
268
|
+
pairs = hash.map do |key, entry|
|
|
269
|
+
[key, options[:raw] ? entry.value.to_s : serialize_entry(entry)]
|
|
270
|
+
end
|
|
271
|
+
failsafe(:write_multi_entries, returning: nil) do
|
|
272
|
+
successful = collection.upsert_multi(pairs, operation_options).select(&:success?)
|
|
273
|
+
return 0 if successful.empty?
|
|
274
|
+
|
|
275
|
+
@last_mutation_token = successful.max_by { |r| r.mutation_token.sequence_number }
|
|
276
|
+
successful.count
|
|
277
|
+
end
|
|
278
|
+
end
|
|
279
|
+
|
|
280
|
+
# Deletes an entry from the cache implementation. Subclasses must
|
|
281
|
+
# implement this method.
|
|
282
|
+
def delete_entry(key, **_options)
|
|
283
|
+
failsafe(:delete_entry, returning: false) do
|
|
284
|
+
res = collection.remove(key)
|
|
285
|
+
@last_mutation_token = res.mutation_token
|
|
286
|
+
true
|
|
287
|
+
end
|
|
288
|
+
end
|
|
289
|
+
|
|
290
|
+
# Deletes multiple entries in the cache. Returns the number of entries deleted.
|
|
291
|
+
def delete_multi_entries(entries, **_options)
|
|
292
|
+
return if entries.empty?
|
|
293
|
+
|
|
294
|
+
failsafe(:delete_multi_entries, returning: nil) do
|
|
295
|
+
successful = collection.remove_multi(entries).select(&:success?)
|
|
296
|
+
return 0 if successful.empty?
|
|
297
|
+
|
|
298
|
+
@last_mutation_token = successful.max_by { |r| r.mutation_token.sequence_number }
|
|
299
|
+
successful.count
|
|
300
|
+
end
|
|
301
|
+
end
|
|
302
|
+
|
|
303
|
+
def failsafe(method, returning: nil)
|
|
304
|
+
yield
|
|
305
|
+
rescue ::Couchbase::Error::CouchbaseError => e
|
|
306
|
+
handle_exception(exception: e, method: method, returning: returning)
|
|
307
|
+
returning
|
|
308
|
+
end
|
|
309
|
+
|
|
310
|
+
def handle_exception(exception:, method:, returning:)
|
|
311
|
+
return unless @error_handler
|
|
312
|
+
|
|
313
|
+
@error_handler.call(method: method, exception: exception, returning: returning)
|
|
314
|
+
rescue StandardError => e
|
|
315
|
+
warn "CouchbaseStore ignored exception in handle_exception: #{e.class}: #{e.message}\n #{e.backtrace.join("\n ")}"
|
|
316
|
+
end
|
|
317
|
+
|
|
318
|
+
# Truncate keys that exceed 250 characters
|
|
319
|
+
def normalize_key(key, options)
|
|
320
|
+
truncate_key super&.b
|
|
321
|
+
end
|
|
322
|
+
|
|
323
|
+
def truncate_key(key)
|
|
324
|
+
if key && key.bytesize > MAX_KEY_BYTESIZE
|
|
325
|
+
suffix = ":sha2:#{::Digest::SHA2.hexdigest(key)}"
|
|
326
|
+
truncate_at = MAX_KEY_BYTESIZE - suffix.bytesize
|
|
327
|
+
"#{key.byteslice(0, truncate_at)}#{suffix}"
|
|
328
|
+
else
|
|
329
|
+
key
|
|
330
|
+
end
|
|
331
|
+
end
|
|
332
|
+
|
|
333
|
+
# Connects to the Couchbase cluster
|
|
334
|
+
def build_cluster
|
|
335
|
+
::Couchbase::Cluster.connect(
|
|
336
|
+
@couchbase_options[:connection_string],
|
|
337
|
+
::Couchbase::Options::Cluster(authenticator: ::Couchbase::PasswordAuthenticator.new(
|
|
338
|
+
@couchbase_options[:username], @couchbase_options[:password]
|
|
339
|
+
))
|
|
340
|
+
)
|
|
341
|
+
end
|
|
342
|
+
|
|
343
|
+
# Connects to the Couchbase cluster, opens specified bucket and returns collection object.
|
|
344
|
+
def build_collection
|
|
345
|
+
bucket = cluster.bucket(@couchbase_options[:bucket])
|
|
346
|
+
if @couchbase_options[:scope] && @couchbase_options[:collection]
|
|
347
|
+
bucket.scope(@couchbase_options[:scope]).collection(@couchbase_options[:collection])
|
|
348
|
+
else
|
|
349
|
+
bucket.default_collection
|
|
350
|
+
end
|
|
351
|
+
end
|
|
352
|
+
|
|
353
|
+
def scope_qualifier
|
|
354
|
+
if @couchbase_options[:scope] && @couchbase_options[:collection]
|
|
355
|
+
"`#{@couchbase_options[:bucket]}`.#{@couchbase_options[:scope]}.#{@couchbase_options[:collection]}"
|
|
356
|
+
else
|
|
357
|
+
"`#{@couchbase_options[:bucket]}`"
|
|
358
|
+
end
|
|
359
|
+
end
|
|
360
|
+
end
|
|
361
|
+
end
|
|
362
|
+
end
|