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.
Files changed (136) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +2 -2
  3. data/ext/CMakeLists.txt +3 -0
  4. data/ext/build_version.hxx.in +1 -1
  5. data/ext/cmake/CompilerWarnings.cmake +1 -0
  6. data/ext/cmake/PreventInSourceBuilds.cmake +4 -1
  7. data/ext/couchbase/bucket.hxx +28 -2
  8. data/ext/couchbase/cluster.hxx +8 -2
  9. data/ext/couchbase/couchbase.cxx +955 -511
  10. data/ext/couchbase/error_context/analytics.hxx +46 -0
  11. data/ext/couchbase/error_context/http.hxx +44 -0
  12. data/ext/couchbase/error_context/key_value.hxx +47 -0
  13. data/ext/couchbase/error_context/query.hxx +46 -0
  14. data/ext/couchbase/error_context/search.hxx +47 -0
  15. data/ext/couchbase/error_context/view.hxx +47 -0
  16. data/ext/couchbase/io/dns_codec.hxx +1 -2
  17. data/ext/couchbase/io/http_command.hxx +16 -3
  18. data/ext/couchbase/io/http_context.hxx +1 -1
  19. data/ext/couchbase/io/http_session.hxx +12 -6
  20. data/ext/couchbase/io/http_session_manager.hxx +25 -24
  21. data/ext/couchbase/io/mcbp_session.hxx +8 -2
  22. data/ext/couchbase/io/retry_context.hxx +1 -1
  23. data/ext/couchbase/operations/analytics_dataset_create.hxx +19 -12
  24. data/ext/couchbase/operations/analytics_dataset_drop.hxx +18 -10
  25. data/ext/couchbase/operations/analytics_dataset_get_all.hxx +16 -10
  26. data/ext/couchbase/operations/analytics_dataverse_create.hxx +18 -11
  27. data/ext/couchbase/operations/analytics_dataverse_drop.hxx +17 -11
  28. data/ext/couchbase/operations/analytics_get_pending_mutations.hxx +17 -10
  29. data/ext/couchbase/operations/analytics_index_create.hxx +17 -11
  30. data/ext/couchbase/operations/analytics_index_drop.hxx +16 -10
  31. data/ext/couchbase/operations/analytics_index_get_all.hxx +14 -10
  32. data/ext/couchbase/operations/analytics_link_connect.hxx +15 -9
  33. data/ext/couchbase/operations/analytics_link_disconnect.hxx +16 -10
  34. data/ext/couchbase/operations/bucket_create.hxx +33 -10
  35. data/ext/couchbase/operations/bucket_drop.hxx +9 -8
  36. data/ext/couchbase/operations/bucket_flush.hxx +8 -8
  37. data/ext/couchbase/operations/bucket_get.hxx +15 -10
  38. data/ext/couchbase/operations/bucket_get_all.hxx +14 -7
  39. data/ext/couchbase/operations/bucket_settings.hxx +16 -0
  40. data/ext/couchbase/operations/bucket_update.hxx +32 -10
  41. data/ext/couchbase/operations/cluster_developer_preview_enable.hxx +6 -6
  42. data/ext/couchbase/operations/collection_create.hxx +19 -13
  43. data/ext/couchbase/operations/collection_drop.hxx +18 -12
  44. data/ext/couchbase/operations/collections_manifest_get.hxx +5 -10
  45. data/ext/couchbase/operations/document_analytics.hxx +39 -17
  46. data/ext/couchbase/operations/document_append.hxx +5 -10
  47. data/ext/couchbase/operations/document_decrement.hxx +5 -10
  48. data/ext/couchbase/operations/document_exists.hxx +4 -6
  49. data/ext/couchbase/operations/document_get.hxx +6 -10
  50. data/ext/couchbase/operations/document_get_and_lock.hxx +4 -9
  51. data/ext/couchbase/operations/document_get_and_touch.hxx +4 -9
  52. data/ext/couchbase/operations/document_get_projected.hxx +21 -14
  53. data/ext/couchbase/operations/document_increment.hxx +5 -10
  54. data/ext/couchbase/operations/document_insert.hxx +5 -10
  55. data/ext/couchbase/operations/document_lookup_in.hxx +4 -9
  56. data/ext/couchbase/operations/document_mutate_in.hxx +7 -12
  57. data/ext/couchbase/operations/document_prepend.hxx +5 -10
  58. data/ext/couchbase/operations/document_query.hxx +45 -28
  59. data/ext/couchbase/operations/document_remove.hxx +5 -10
  60. data/ext/couchbase/operations/document_replace.hxx +5 -10
  61. data/ext/couchbase/operations/document_search.hxx +37 -16
  62. data/ext/couchbase/operations/document_touch.hxx +4 -9
  63. data/ext/couchbase/operations/document_unlock.hxx +4 -9
  64. data/ext/couchbase/operations/document_upsert.hxx +5 -10
  65. data/ext/couchbase/operations/document_view.hxx +29 -13
  66. data/ext/couchbase/operations/group_drop.hxx +7 -7
  67. data/ext/couchbase/operations/group_get.hxx +14 -10
  68. data/ext/couchbase/operations/group_get_all.hxx +14 -8
  69. data/ext/couchbase/operations/group_upsert.hxx +15 -9
  70. data/ext/couchbase/operations/http_noop.hxx +5 -5
  71. data/ext/couchbase/operations/mcbp_noop.hxx +3 -9
  72. data/ext/couchbase/operations/query_index_build_deferred.hxx +15 -9
  73. data/ext/couchbase/operations/query_index_create.hxx +16 -10
  74. data/ext/couchbase/operations/query_index_drop.hxx +16 -10
  75. data/ext/couchbase/operations/query_index_get_all.hxx +13 -7
  76. data/ext/couchbase/operations/role_get_all.hxx +14 -8
  77. data/ext/couchbase/operations/scope_create.hxx +19 -13
  78. data/ext/couchbase/operations/scope_drop.hxx +17 -11
  79. data/ext/couchbase/operations/scope_get_all.hxx +15 -10
  80. data/ext/couchbase/operations/search_get_stats.hxx +5 -5
  81. data/ext/couchbase/operations/search_index_analyze_document.hxx +25 -13
  82. data/ext/couchbase/operations/search_index_control_ingest.hxx +23 -11
  83. data/ext/couchbase/operations/search_index_control_plan_freeze.hxx +23 -11
  84. data/ext/couchbase/operations/search_index_control_query.hxx +23 -11
  85. data/ext/couchbase/operations/search_index_drop.hxx +22 -10
  86. data/ext/couchbase/operations/search_index_get.hxx +22 -10
  87. data/ext/couchbase/operations/search_index_get_all.hxx +13 -7
  88. data/ext/couchbase/operations/search_index_get_documents_count.hxx +24 -13
  89. data/ext/couchbase/operations/search_index_get_stats.hxx +16 -10
  90. data/ext/couchbase/operations/search_index_upsert.hxx +23 -11
  91. data/ext/couchbase/operations/user_drop.hxx +8 -8
  92. data/ext/couchbase/operations/user_get.hxx +14 -10
  93. data/ext/couchbase/operations/user_get_all.hxx +14 -8
  94. data/ext/couchbase/operations/user_upsert.hxx +15 -9
  95. data/ext/couchbase/operations/view_index_drop.hxx +7 -7
  96. data/ext/couchbase/operations/view_index_get.hxx +15 -9
  97. data/ext/couchbase/operations/view_index_get_all.hxx +15 -9
  98. data/ext/couchbase/operations/view_index_upsert.hxx +8 -8
  99. data/ext/couchbase/origin.hxx +1 -0
  100. data/ext/couchbase/platform/terminate_handler.cc +12 -8
  101. data/ext/couchbase/protocol/client_request.hxx +2 -1
  102. data/ext/couchbase/protocol/client_response.hxx +18 -15
  103. data/ext/couchbase/protocol/cmd_exists.hxx +1 -0
  104. data/ext/couchbase/protocol/cmd_get.hxx +1 -1
  105. data/ext/couchbase/protocol/cmd_mutate_in.hxx +3 -4
  106. data/ext/couchbase/protocol/enhanced_error_info.hxx +28 -0
  107. data/ext/couchbase/utils/connection_string.hxx +1 -1
  108. data/ext/couchbase/version.hxx +1 -1
  109. data/ext/extconf.rb +1 -1
  110. data/ext/test/test_native_binary_operations.cxx +18 -18
  111. data/ext/test/test_native_diagnostics.cxx +2 -2
  112. data/ext/test/test_native_trivial_crud.cxx +2 -2
  113. data/ext/third_party/json/include/tao/json/external/pegtl/internal/file_reader.hpp +1 -5
  114. data/lib/active_support/cache/couchbase_store.rb +362 -0
  115. data/lib/couchbase.rb +2 -0
  116. data/lib/couchbase/authenticator.rb +26 -0
  117. data/lib/couchbase/binary_collection.rb +1 -0
  118. data/lib/couchbase/bucket.rb +1 -0
  119. data/lib/couchbase/cluster.rb +51 -27
  120. data/lib/couchbase/collection.rb +19 -4
  121. data/lib/couchbase/collection_options.rb +10 -0
  122. data/lib/couchbase/configuration.rb +57 -0
  123. data/lib/couchbase/datastructures.rb +6 -0
  124. data/lib/couchbase/errors.rb +111 -3
  125. data/lib/couchbase/management.rb +27 -0
  126. data/lib/couchbase/management/bucket_manager.rb +9 -2
  127. data/lib/couchbase/management/collection_manager.rb +1 -1
  128. data/lib/couchbase/management/user_manager.rb +18 -2
  129. data/lib/couchbase/options.rb +33 -23
  130. data/lib/couchbase/railtie.rb +45 -0
  131. data/lib/couchbase/scope.rb +44 -3
  132. data/lib/couchbase/utils.rb +21 -0
  133. data/lib/couchbase/utils/time.rb +52 -0
  134. data/lib/couchbase/version.rb +1 -1
  135. data/lib/rails/generators/couchbase/config/config_generator.rb +27 -0
  136. 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<enhanced_error> error_;
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
- enhanced_error err{};
163
+ enhanced_error_info err{};
161
164
  auto& ref = err_obj["ref"];
162
165
  if (ref.is_string()) {
163
- err.ref = ref.get_string();
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::enhanced_error> : formatter<std::string> {
208
+ struct fmt::formatter<couchbase::protocol::enhanced_error_info> : formatter<std::string> {
206
209
  template<typename FormatContext>
207
- auto format(const couchbase::protocol::enhanced_error& error, FormatContext& ctx)
210
+ auto format(const couchbase::protocol::enhanced_error_info& error, FormatContext& ctx)
208
211
  {
209
- if (!error.ref.empty() && !error.context.empty()) {
210
- format_to(ctx.out(), R"((ref: "{}", ctx: "{}"))", error.ref, error.context);
211
- } else if (!error.ref.empty()) {
212
- format_to(ctx.out(), R"((ref: "{}"))", error.ref);
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
  }
@@ -87,6 +87,7 @@ class exists_response_body
87
87
  offset++;
88
88
 
89
89
  memcpy(&cas_, body.data() + offset, sizeof(cas_));
90
+ cas_ = utils::byte_swap_64(cas_);
90
91
  }
91
92
  return false;
92
93
  }
@@ -19,8 +19,8 @@
19
19
 
20
20
  #include <protocol/unsigned_leb128.h>
21
21
 
22
- #include <protocol/client_opcode.hxx>
23
22
  #include <document_id.hxx>
23
+ #include <protocol/client_opcode.hxx>
24
24
 
25
25
  namespace couchbase::protocol
26
26
  {
@@ -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 {
@@ -24,7 +24,7 @@
24
24
  namespace couchbase
25
25
  {
26
26
  constexpr auto BACKEND_VERSION_MAJOR = 1;
27
- constexpr auto BACKEND_VERSION_MINOR = 2;
27
+ constexpr auto BACKEND_VERSION_MINOR = 3;
28
28
  constexpr auto BACKEND_VERSION_PATCH = 0;
29
29
 
30
30
  inline const std::string&
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