couchbase 3.0.0.alpha.1-universal-darwin-19 → 3.0.0.alpha.2-universal-darwin-19

Sign up to get free protection for your applications and to get access to all the features.
Files changed (176) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/tests-6.0.3.yml +49 -0
  3. data/.github/workflows/tests.yml +47 -0
  4. data/.gitmodules +3 -0
  5. data/.idea/dictionaries/gem_terms.xml +5 -0
  6. data/.idea/inspectionProfiles/Project_Default.xml +1 -0
  7. data/.idea/vcs.xml +1 -0
  8. data/Gemfile +1 -0
  9. data/README.md +55 -2
  10. data/Rakefile +18 -0
  11. data/bin/init-cluster +62 -0
  12. data/bin/setup +1 -0
  13. data/couchbase.gemspec +3 -2
  14. data/examples/crud.rb +1 -2
  15. data/examples/managing_buckets.rb +47 -0
  16. data/examples/managing_collections.rb +58 -0
  17. data/examples/managing_query_indexes.rb +63 -0
  18. data/examples/query.rb +3 -2
  19. data/examples/query_with_consistency.rb +76 -0
  20. data/examples/subdocument.rb +23 -1
  21. data/ext/.clang-format +1 -1
  22. data/ext/.idea/dictionaries/couchbase_terms.xml +2 -0
  23. data/ext/.idea/vcs.xml +1 -0
  24. data/ext/CMakeLists.txt +30 -12
  25. data/ext/build_version.hxx.in +26 -0
  26. data/ext/couchbase/bucket.hxx +69 -8
  27. data/ext/couchbase/cluster.hxx +70 -54
  28. data/ext/couchbase/collections_manifest.hxx +3 -3
  29. data/ext/couchbase/configuration.hxx +14 -0
  30. data/ext/couchbase/couchbase.cxx +2044 -383
  31. data/ext/couchbase/{operations/document_id.hxx → document_id.hxx} +5 -4
  32. data/ext/couchbase/io/http_message.hxx +5 -1
  33. data/ext/couchbase/io/http_parser.hxx +2 -1
  34. data/ext/couchbase/io/http_session.hxx +6 -3
  35. data/ext/couchbase/io/{binary_message.hxx → mcbp_message.hxx} +15 -12
  36. data/ext/couchbase/io/mcbp_parser.hxx +99 -0
  37. data/ext/couchbase/io/{key_value_session.hxx → mcbp_session.hxx} +200 -95
  38. data/ext/couchbase/io/session_manager.hxx +37 -22
  39. data/ext/couchbase/mutation_token.hxx +2 -1
  40. data/ext/couchbase/operations.hxx +38 -8
  41. data/ext/couchbase/operations/bucket_create.hxx +138 -0
  42. data/ext/couchbase/operations/bucket_drop.hxx +65 -0
  43. data/ext/couchbase/operations/bucket_flush.hxx +65 -0
  44. data/ext/couchbase/operations/bucket_get.hxx +69 -0
  45. data/ext/couchbase/operations/bucket_get_all.hxx +62 -0
  46. data/ext/couchbase/operations/bucket_settings.hxx +111 -0
  47. data/ext/couchbase/operations/bucket_update.hxx +115 -0
  48. data/ext/couchbase/operations/cluster_developer_preview_enable.hxx +60 -0
  49. data/ext/couchbase/operations/collection_create.hxx +86 -0
  50. data/ext/couchbase/operations/collection_drop.hxx +82 -0
  51. data/ext/couchbase/operations/command.hxx +10 -10
  52. data/ext/couchbase/operations/document_decrement.hxx +80 -0
  53. data/ext/couchbase/operations/document_exists.hxx +80 -0
  54. data/ext/couchbase/operations/{get.hxx → document_get.hxx} +4 -2
  55. data/ext/couchbase/operations/document_get_and_lock.hxx +64 -0
  56. data/ext/couchbase/operations/document_get_and_touch.hxx +64 -0
  57. data/ext/couchbase/operations/document_increment.hxx +80 -0
  58. data/ext/couchbase/operations/document_insert.hxx +74 -0
  59. data/ext/couchbase/operations/{lookup_in.hxx → document_lookup_in.hxx} +2 -2
  60. data/ext/couchbase/operations/{mutate_in.hxx → document_mutate_in.hxx} +11 -2
  61. data/ext/couchbase/operations/{query.hxx → document_query.hxx} +101 -6
  62. data/ext/couchbase/operations/document_remove.hxx +67 -0
  63. data/ext/couchbase/operations/document_replace.hxx +76 -0
  64. data/ext/couchbase/operations/{upsert.hxx → document_touch.hxx} +14 -14
  65. data/ext/couchbase/operations/{remove.hxx → document_unlock.hxx} +12 -10
  66. data/ext/couchbase/operations/document_upsert.hxx +74 -0
  67. data/ext/couchbase/operations/query_index_build_deferred.hxx +85 -0
  68. data/ext/couchbase/operations/query_index_create.hxx +134 -0
  69. data/ext/couchbase/operations/query_index_drop.hxx +108 -0
  70. data/ext/couchbase/operations/query_index_get_all.hxx +106 -0
  71. data/ext/couchbase/operations/scope_create.hxx +81 -0
  72. data/ext/couchbase/operations/scope_drop.hxx +79 -0
  73. data/ext/couchbase/operations/scope_get_all.hxx +72 -0
  74. data/ext/couchbase/protocol/client_opcode.hxx +35 -0
  75. data/ext/couchbase/protocol/client_request.hxx +56 -9
  76. data/ext/couchbase/protocol/client_response.hxx +52 -15
  77. data/ext/couchbase/protocol/cmd_cluster_map_change_notification.hxx +81 -0
  78. data/ext/couchbase/protocol/cmd_decrement.hxx +187 -0
  79. data/ext/couchbase/protocol/cmd_exists.hxx +171 -0
  80. data/ext/couchbase/protocol/cmd_get.hxx +31 -8
  81. data/ext/couchbase/protocol/cmd_get_and_lock.hxx +142 -0
  82. data/ext/couchbase/protocol/cmd_get_and_touch.hxx +142 -0
  83. data/ext/couchbase/protocol/cmd_get_cluster_config.hxx +16 -3
  84. data/ext/couchbase/protocol/cmd_get_collections_manifest.hxx +16 -3
  85. data/ext/couchbase/protocol/cmd_get_error_map.hxx +16 -3
  86. data/ext/couchbase/protocol/cmd_hello.hxx +24 -8
  87. data/ext/couchbase/protocol/cmd_increment.hxx +187 -0
  88. data/ext/couchbase/protocol/cmd_info.hxx +1 -0
  89. data/ext/couchbase/protocol/cmd_insert.hxx +172 -0
  90. data/ext/couchbase/protocol/cmd_lookup_in.hxx +28 -13
  91. data/ext/couchbase/protocol/cmd_mutate_in.hxx +65 -13
  92. data/ext/couchbase/protocol/cmd_remove.hxx +59 -4
  93. data/ext/couchbase/protocol/cmd_replace.hxx +172 -0
  94. data/ext/couchbase/protocol/cmd_sasl_auth.hxx +15 -3
  95. data/ext/couchbase/protocol/cmd_sasl_list_mechs.hxx +15 -3
  96. data/ext/couchbase/protocol/cmd_sasl_step.hxx +15 -3
  97. data/ext/couchbase/protocol/cmd_select_bucket.hxx +14 -2
  98. data/ext/couchbase/protocol/cmd_touch.hxx +102 -0
  99. data/ext/couchbase/protocol/cmd_unlock.hxx +95 -0
  100. data/ext/couchbase/protocol/cmd_upsert.hxx +50 -14
  101. data/ext/couchbase/protocol/durability_level.hxx +67 -0
  102. data/ext/couchbase/protocol/frame_info_id.hxx +187 -0
  103. data/ext/couchbase/protocol/hello_feature.hxx +137 -0
  104. data/ext/couchbase/protocol/server_opcode.hxx +57 -0
  105. data/ext/couchbase/protocol/server_request.hxx +122 -0
  106. data/ext/couchbase/protocol/unsigned_leb128.h +15 -15
  107. data/ext/couchbase/utils/byteswap.hxx +1 -2
  108. data/ext/couchbase/utils/url_codec.hxx +225 -0
  109. data/ext/couchbase/version.hxx +3 -1
  110. data/ext/extconf.rb +4 -1
  111. data/ext/test/main.cxx +37 -113
  112. data/ext/third_party/snappy/.appveyor.yml +36 -0
  113. data/ext/third_party/snappy/.gitignore +8 -0
  114. data/ext/third_party/snappy/.travis.yml +98 -0
  115. data/ext/third_party/snappy/AUTHORS +1 -0
  116. data/ext/third_party/snappy/CMakeLists.txt +345 -0
  117. data/ext/third_party/snappy/CONTRIBUTING.md +26 -0
  118. data/ext/third_party/snappy/COPYING +54 -0
  119. data/ext/third_party/snappy/NEWS +188 -0
  120. data/ext/third_party/snappy/README.md +148 -0
  121. data/ext/third_party/snappy/cmake/SnappyConfig.cmake.in +33 -0
  122. data/ext/third_party/snappy/cmake/config.h.in +59 -0
  123. data/ext/third_party/snappy/docs/README.md +72 -0
  124. data/ext/third_party/snappy/format_description.txt +110 -0
  125. data/ext/third_party/snappy/framing_format.txt +135 -0
  126. data/ext/third_party/snappy/snappy-c.cc +90 -0
  127. data/ext/third_party/snappy/snappy-c.h +138 -0
  128. data/ext/third_party/snappy/snappy-internal.h +315 -0
  129. data/ext/third_party/snappy/snappy-sinksource.cc +121 -0
  130. data/ext/third_party/snappy/snappy-sinksource.h +182 -0
  131. data/ext/third_party/snappy/snappy-stubs-internal.cc +42 -0
  132. data/ext/third_party/snappy/snappy-stubs-internal.h +493 -0
  133. data/ext/third_party/snappy/snappy-stubs-public.h.in +63 -0
  134. data/ext/third_party/snappy/snappy-test.cc +613 -0
  135. data/ext/third_party/snappy/snappy-test.h +526 -0
  136. data/ext/third_party/snappy/snappy.cc +1770 -0
  137. data/ext/third_party/snappy/snappy.h +209 -0
  138. data/ext/third_party/snappy/snappy_compress_fuzzer.cc +60 -0
  139. data/ext/third_party/snappy/snappy_uncompress_fuzzer.cc +58 -0
  140. data/ext/third_party/snappy/snappy_unittest.cc +1512 -0
  141. data/ext/third_party/snappy/testdata/alice29.txt +3609 -0
  142. data/ext/third_party/snappy/testdata/asyoulik.txt +4122 -0
  143. data/ext/third_party/snappy/testdata/baddata1.snappy +0 -0
  144. data/ext/third_party/snappy/testdata/baddata2.snappy +0 -0
  145. data/ext/third_party/snappy/testdata/baddata3.snappy +0 -0
  146. data/ext/third_party/snappy/testdata/fireworks.jpeg +0 -0
  147. data/ext/third_party/snappy/testdata/geo.protodata +0 -0
  148. data/ext/third_party/snappy/testdata/html +1 -0
  149. data/ext/third_party/snappy/testdata/html_x_4 +1 -0
  150. data/ext/third_party/snappy/testdata/kppkn.gtb +0 -0
  151. data/ext/third_party/snappy/testdata/lcet10.txt +7519 -0
  152. data/ext/third_party/snappy/testdata/paper-100k.pdf +600 -2
  153. data/ext/third_party/snappy/testdata/plrabn12.txt +10699 -0
  154. data/ext/third_party/snappy/testdata/urls.10K +10000 -0
  155. data/lib/couchbase/binary_collection.rb +33 -76
  156. data/lib/couchbase/binary_collection_options.rb +94 -0
  157. data/lib/couchbase/bucket.rb +9 -3
  158. data/lib/couchbase/cluster.rb +161 -23
  159. data/lib/couchbase/collection.rb +108 -191
  160. data/lib/couchbase/collection_options.rb +430 -0
  161. data/lib/couchbase/errors.rb +136 -134
  162. data/lib/couchbase/json_transcoder.rb +32 -0
  163. data/lib/couchbase/management/analytics_index_manager.rb +185 -9
  164. data/lib/couchbase/management/bucket_manager.rb +84 -33
  165. data/lib/couchbase/management/collection_manager.rb +166 -1
  166. data/lib/couchbase/management/query_index_manager.rb +261 -0
  167. data/lib/couchbase/management/search_index_manager.rb +291 -0
  168. data/lib/couchbase/management/user_manager.rb +12 -10
  169. data/lib/couchbase/management/view_index_manager.rb +151 -1
  170. data/lib/couchbase/mutation_state.rb +11 -1
  171. data/lib/couchbase/scope.rb +4 -4
  172. data/lib/couchbase/version.rb +1 -1
  173. metadata +113 -18
  174. data/.travis.yml +0 -7
  175. data/ext/couchbase/io/binary_parser.hxx +0 -64
  176. data/lib/couchbase/results.rb +0 -307
@@ -17,19 +17,20 @@
17
17
 
18
18
  #pragma once
19
19
 
20
- namespace couchbase::operations
20
+ namespace couchbase
21
21
  {
22
22
  struct document_id {
23
23
  std::string bucket;
24
24
  std::string collection;
25
25
  std::string key;
26
+ std::optional<std::uint32_t> collection_uid; // filled with resolved UID during request lifetime
26
27
  };
27
- } // namespace couchbase::operations
28
+ } // namespace couchbase
28
29
 
29
30
  template<>
30
- struct fmt::formatter<couchbase::operations::document_id> : formatter<std::string> {
31
+ struct fmt::formatter<couchbase::document_id> : formatter<std::string> {
31
32
  template<typename FormatContext>
32
- auto format(const couchbase::operations::document_id& id, FormatContext& ctx)
33
+ auto format(const couchbase::document_id& id, FormatContext& ctx)
33
34
  {
34
35
  format_to(ctx.out(), "{}/{}/{}", id.bucket, id.collection, id.key);
35
36
  return formatter<std::string>::format("", ctx);
@@ -17,9 +17,12 @@
17
17
 
18
18
  #pragma once
19
19
 
20
+ #include <service_type.hxx>
21
+
20
22
  namespace couchbase::io
21
23
  {
22
24
  struct http_request {
25
+ service_type type;
23
26
  std::string method;
24
27
  std::string path;
25
28
  std::map<std::string, std::string> headers;
@@ -27,7 +30,8 @@ struct http_request {
27
30
  };
28
31
 
29
32
  struct http_response {
30
- std::string status;
33
+ uint32_t status_code;
34
+ std::string status_message;
31
35
  std::map<std::string, std::string> headers;
32
36
  std::string body;
33
37
  };
@@ -98,7 +98,8 @@ struct http_parser {
98
98
 
99
99
  int on_status(const char* at, std::size_t length)
100
100
  {
101
- response.status.assign(at, length);
101
+ response.status_message.assign(at, length);
102
+ response.status_code = parser_.status_code;
102
103
  return 0;
103
104
  }
104
105
 
@@ -148,7 +148,7 @@ class http_session : public std::enable_shared_from_this
148
148
  }
149
149
  endpoints_ = endpoints;
150
150
  do_connect(endpoints_.begin());
151
- deadline_timer_.async_wait(std::bind(&http_session::check_deadline, this));
151
+ deadline_timer_.async_wait(std::bind(&http_session::check_deadline, this, std::placeholders::_1));
152
152
  }
153
153
 
154
154
  void do_connect(asio::ip::tcp::resolver::results_type::iterator it)
@@ -180,8 +180,11 @@ class http_session : public std::enable_shared_from_this
180
180
  }
181
181
  }
182
182
 
183
- void check_deadline()
183
+ void check_deadline(std::error_code ec)
184
184
  {
185
+ if (ec == asio::error::operation_aborted) {
186
+ return;
187
+ }
185
188
  if (stopped_) {
186
189
  return;
187
190
  }
@@ -189,7 +192,7 @@ class http_session : public std::enable_shared_from_this
189
192
  socket_.close();
190
193
  deadline_timer_.expires_at(asio::steady_timer::time_point::max());
191
194
  }
192
- deadline_timer_.async_wait(std::bind(&http_session::check_deadline, this));
195
+ deadline_timer_.async_wait(std::bind(&http_session::check_deadline, this, std::placeholders::_1));
193
196
  }
194
197
 
195
198
  void do_read()
@@ -17,22 +17,25 @@
17
17
 
18
18
  #pragma once
19
19
 
20
+ #include <cstdint>
21
+ #include <vector>
22
+
20
23
  namespace couchbase::io
21
24
  {
22
25
  struct binary_header {
23
- uint8_t magic;
24
- uint8_t opcode;
25
- uint16_t keylen;
26
- uint8_t extlen;
27
- uint8_t datatype;
28
- uint16_t specific;
29
- uint32_t bodylen;
30
- uint32_t opaque;
31
- uint64_t cas;
26
+ std::uint8_t magic;
27
+ std::uint8_t opcode;
28
+ std::uint16_t keylen;
29
+ std::uint8_t extlen;
30
+ std::uint8_t datatype;
31
+ std::uint16_t specific;
32
+ std::uint32_t bodylen;
33
+ std::uint32_t opaque;
34
+ std::uint64_t cas;
32
35
  };
33
36
 
34
- struct binary_message {
37
+ struct mcbp_message {
35
38
  binary_header header;
36
- std::vector<uint8_t> body;
39
+ std::vector<std::uint8_t> body;
37
40
  };
38
- } // namespace couchbase::io
41
+ } // namespace couchbase::io
@@ -0,0 +1,99 @@
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
+ #include <snappy.h>
21
+
22
+ #include <gsl/gsl_assert>
23
+ #include <protocol/magic.hxx>
24
+ #include <protocol/datatype.hxx>
25
+
26
+ #include <spdlog/fmt/bin_to_hex.h>
27
+
28
+ namespace couchbase::io
29
+ {
30
+ struct mcbp_parser {
31
+ enum result { ok, need_data, failure };
32
+
33
+ template<typename Iterator>
34
+ void feed(Iterator begin, Iterator end)
35
+ {
36
+ buf.reserve(buf.size() + static_cast<size_t>(std::distance(begin, end)));
37
+ std::copy(begin, end, std::back_inserter(buf));
38
+ }
39
+
40
+ void reset()
41
+ {
42
+ buf.clear();
43
+ }
44
+
45
+ result next(mcbp_message& msg)
46
+ {
47
+ static const size_t header_size = 24;
48
+ if (buf.size() < header_size) {
49
+ return need_data;
50
+ }
51
+ std::memcpy(&msg.header, buf.data(), header_size);
52
+ uint32_t body_size = ntohl(msg.header.bodylen);
53
+ if (body_size > 0 && buf.size() - header_size < body_size) {
54
+ return need_data;
55
+ }
56
+ msg.body.clear();
57
+ msg.body.reserve(body_size);
58
+ uint32_t key_size = ntohs(msg.header.keylen);
59
+ uint32_t prefix_size = uint32_t(msg.header.extlen) + key_size;
60
+ if (msg.header.magic == static_cast<uint8_t>(protocol::magic::alt_client_response)) {
61
+ uint8_t framing_extras_size = msg.header.keylen & 0xfU;
62
+ key_size = (msg.header.keylen & 0xf0U) >> 8U;
63
+ prefix_size = uint32_t(framing_extras_size) + uint32_t(msg.header.extlen) + key_size;
64
+ }
65
+ std::copy(buf.begin() + header_size, buf.begin() + header_size + prefix_size, std::back_inserter(msg.body));
66
+
67
+ bool is_compressed = (msg.header.datatype & static_cast<uint8_t>(protocol::datatype::snappy)) != 0;
68
+ bool use_raw_value = true;
69
+ if (is_compressed) {
70
+ std::string uncompressed;
71
+ size_t offset = header_size + prefix_size;
72
+ bool success = snappy::Uncompress(reinterpret_cast<const char*>(buf.data() + offset), body_size - prefix_size, &uncompressed);
73
+ if (success) {
74
+ std::copy(uncompressed.begin(), uncompressed.end(), std::back_inserter(msg.body));
75
+ use_raw_value = false;
76
+ }
77
+ }
78
+ if (use_raw_value) {
79
+ std::copy(buf.begin() + header_size + prefix_size, buf.begin() + header_size + body_size, std::back_inserter(msg.body));
80
+ }
81
+ buf.erase(buf.begin(), buf.begin() + header_size + body_size);
82
+ if (!protocol::is_valid_magic(buf[0])) {
83
+ spdlog::warn("parsed frame for magic={:x}, opcode={:x}, opaque={}, body_len={}. Invalid magic of the next frame: {:x}, {} "
84
+ "bytes to parse{}",
85
+ msg.header.magic,
86
+ msg.header.opcode,
87
+ msg.header.opaque,
88
+ body_size,
89
+ buf[0],
90
+ buf.size(),
91
+ spdlog::to_hex(buf));
92
+ reset();
93
+ }
94
+ return ok;
95
+ }
96
+
97
+ std::vector<std::uint8_t> buf;
98
+ };
99
+ } // namespace couchbase::io
@@ -25,12 +25,13 @@
25
25
 
26
26
  #include <platform/uuid.h>
27
27
 
28
- #include <io/binary_message.hxx>
29
- #include <io/binary_parser.hxx>
28
+ #include <io/mcbp_message.hxx>
29
+ #include <io/mcbp_parser.hxx>
30
30
 
31
31
  #include <protocol/hello_feature.hxx>
32
32
  #include <protocol/client_request.hxx>
33
33
  #include <protocol/client_response.hxx>
34
+ #include <protocol/server_request.hxx>
34
35
  #include <protocol/cmd_hello.hxx>
35
36
  #include <protocol/cmd_sasl_list_mechs.hxx>
36
37
  #include <protocol/cmd_sasl_auth.hxx>
@@ -40,6 +41,7 @@
40
41
  #include <protocol/cmd_get_error_map.hxx>
41
42
  #include <protocol/cmd_get_collections_manifest.hxx>
42
43
  #include <protocol/cmd_get.hxx>
44
+ #include <protocol/cmd_cluster_map_change_notification.hxx>
43
45
 
44
46
  #include <cbsasl/client.h>
45
47
 
@@ -51,12 +53,12 @@
51
53
  namespace couchbase::io
52
54
  {
53
55
 
54
- class key_value_session : public std::enable_shared_from_this<key_value_session>
56
+ class mcbp_session : public std::enable_shared_from_this<mcbp_session>
55
57
  {
56
58
  class message_handler
57
59
  {
58
60
  public:
59
- virtual void handle(binary_message&& msg) = 0;
61
+ virtual void handle(mcbp_message&& msg) = 0;
60
62
 
61
63
  virtual ~message_handler() = default;
62
64
 
@@ -68,14 +70,23 @@ class key_value_session : public std::enable_shared_from_this
68
70
  class bootstrap_handler : public message_handler
69
71
  {
70
72
  private:
71
- std::shared_ptr<key_value_session> session_;
73
+ std::shared_ptr<mcbp_session> session_;
72
74
  sasl::ClientContext sasl_;
75
+ std::atomic_bool stopped_{ false };
73
76
 
74
77
  public:
75
- bootstrap_handler(bootstrap_handler&&) = default;
76
- ~bootstrap_handler() = default;
78
+ ~bootstrap_handler() override = default;
77
79
 
78
- explicit bootstrap_handler(std::shared_ptr<key_value_session> session)
80
+ void stop() override
81
+ {
82
+ if (stopped_) {
83
+ return;
84
+ }
85
+ stopped_ = true;
86
+ session_.reset();
87
+ }
88
+
89
+ explicit bootstrap_handler(std::shared_ptr<mcbp_session> session)
79
90
  : session_(session)
80
91
  , sasl_([this]() -> std::string { return session_->username_; },
81
92
  [this]() -> std::string { return session_->password_; },
@@ -116,7 +127,6 @@ class key_value_session : public std::enable_shared_from_this
116
127
 
117
128
  void auth_success()
118
129
  {
119
- spdlog::debug("{} authentication successful", sasl_.get_name());
120
130
  session_->authenticated_ = true;
121
131
  if (session_->supports_feature(protocol::hello_feature::xerror)) {
122
132
  protocol::client_request<protocol::get_error_map_request_body> errmap_req;
@@ -139,8 +149,11 @@ class key_value_session : public std::enable_shared_from_this
139
149
  session_->flush();
140
150
  }
141
151
 
142
- void handle(binary_message&& msg) override
152
+ void handle(mcbp_message&& msg) override
143
153
  {
154
+ if (stopped_ || !session_) {
155
+ return;
156
+ }
144
157
  Expects(protocol::is_valid_client_opcode(msg.header.opcode));
145
158
  auto opcode = static_cast<protocol::client_opcode>(msg.header.opcode);
146
159
  switch (opcode) {
@@ -183,7 +196,7 @@ class key_value_session : public std::enable_shared_from_this
183
196
  return complete(std::make_error_code(error::common_errc::authentication_failure));
184
197
  }
185
198
  } else {
186
- spdlog::warn("unexpected message status during bootstrap: {}", resp.error_message());
199
+ spdlog::warn("unexpected message status during bootstrap: {} (opcode={})", resp.error_message(), opcode);
187
200
  return complete(std::make_error_code(error::common_errc::authentication_failure));
188
201
  }
189
202
  } break;
@@ -199,7 +212,7 @@ class key_value_session : public std::enable_shared_from_this
199
212
  if (resp.status() == protocol::status::success) {
200
213
  session_->errmap_.emplace(resp.body().errmap());
201
214
  } else {
202
- spdlog::warn("unexpected message status during bootstrap: {}", resp.error_message());
215
+ spdlog::warn("unexpected message status during bootstrap: {} (opcode={})", resp.error_message(), opcode);
203
216
  return complete(std::make_error_code(error::network_errc::protocol_error));
204
217
  }
205
218
  } break;
@@ -214,7 +227,7 @@ class key_value_session : public std::enable_shared_from_this
214
227
  session_->bucket_name_.value_or(""),
215
228
  resp.error_message());
216
229
  } else {
217
- spdlog::warn("unexpected message status during bootstrap: {}", resp.error_message());
230
+ spdlog::warn("unexpected message status during bootstrap: {} (opcode={})", resp.error_message(), opcode);
218
231
  return complete(std::make_error_code(error::network_errc::protocol_error));
219
232
  }
220
233
  } break;
@@ -237,15 +250,21 @@ class key_value_session : public std::enable_shared_from_this
237
250
  protocol::client_response<protocol::get_cluster_config_response_body> resp(msg);
238
251
  if (resp.status() == protocol::status::success) {
239
252
  session_->update_configuration(resp.body().config());
240
- spdlog::trace("received new configuration: {}", session_->config_.value());
253
+ complete({});
254
+ } else if (resp.status() == protocol::status::no_bucket && !session_->bucket_name_) {
255
+ // bucket-less session, but the server wants bucket
256
+ session_->supports_gcccp_ = false;
257
+ spdlog::warn("this server does not support GCCCP, open bucket before making any cluster-level command");
258
+ session_->update_configuration(
259
+ make_blank_configuration(session_->endpoint_.address().to_string(), session_->endpoint_.port(), 0));
241
260
  complete({});
242
261
  } else {
243
- spdlog::warn("unexpected message status during bootstrap: {}", resp.error_message());
262
+ spdlog::warn("unexpected message status during bootstrap: {} (opcode={})", resp.error_message(), opcode);
244
263
  return complete(std::make_error_code(error::network_errc::protocol_error));
245
264
  }
246
265
  } break;
247
266
  default:
248
- spdlog::trace("unexpected message during bootstrap: {}", opcode);
267
+ spdlog::warn("unexpected message during bootstrap: {}", opcode);
249
268
  return complete(std::make_error_code(error::network_errc::protocol_error));
250
269
  }
251
270
  }
@@ -254,75 +273,124 @@ class key_value_session : public std::enable_shared_from_this
254
273
  class normal_handler : public message_handler
255
274
  {
256
275
  private:
257
- std::shared_ptr<key_value_session> session_;
276
+ std::shared_ptr<mcbp_session> session_;
258
277
  asio::steady_timer heartbeat_timer_;
278
+ std::atomic_bool stopped_{ false };
259
279
 
260
280
  public:
261
- normal_handler(normal_handler&&) = default;
262
- ~normal_handler() = default;
281
+ ~normal_handler() override = default;
263
282
 
264
- explicit normal_handler(std::shared_ptr<key_value_session> session)
283
+ explicit normal_handler(std::shared_ptr<mcbp_session> session)
265
284
  : session_(session)
266
285
  , heartbeat_timer_(session_->ctx_)
267
286
  {
268
- fetch_config();
287
+ if (session_->supports_gcccp_) {
288
+ fetch_config({});
289
+ }
269
290
  }
270
291
 
271
292
  void stop() override
272
293
  {
294
+ if (stopped_) {
295
+ return;
296
+ }
297
+ stopped_ = true;
273
298
  heartbeat_timer_.cancel();
299
+ session_.reset();
274
300
  }
275
301
 
276
- void handle(binary_message&& msg) override
302
+ void handle(mcbp_message&& msg) override
277
303
  {
278
- if (session_->stopped_) {
304
+ if (stopped_ || !session_) {
279
305
  return;
280
306
  }
281
- Expects(protocol::is_valid_client_opcode(msg.header.opcode));
282
- switch (auto opcode = static_cast<protocol::client_opcode>(msg.header.opcode)) {
283
- case protocol::client_opcode::get_cluster_config: {
284
- protocol::client_response<protocol::get_cluster_config_response_body> resp(msg);
285
- if (resp.status() == protocol::status::success) {
286
- session_->update_configuration(resp.body().config());
287
- } else {
288
- spdlog::warn("unexpected message status: {}", resp.error_message());
307
+ Expects(protocol::is_valid_magic(msg.header.magic));
308
+ switch (auto magic = static_cast<protocol::magic>(msg.header.magic)) {
309
+ case protocol::magic::client_response:
310
+ case protocol::magic::alt_client_response:
311
+ Expects(protocol::is_valid_client_opcode(msg.header.opcode));
312
+ switch (auto opcode = static_cast<protocol::client_opcode>(msg.header.opcode)) {
313
+ case protocol::client_opcode::get_cluster_config: {
314
+ protocol::client_response<protocol::get_cluster_config_response_body> resp(msg);
315
+ if (resp.status() == protocol::status::success) {
316
+ if (session_) {
317
+ session_->update_configuration(resp.body().config());
318
+ }
319
+ } else {
320
+ spdlog::warn("unexpected message status: {}", resp.error_message());
321
+ }
322
+ } break;
323
+ case protocol::client_opcode::get:
324
+ case protocol::client_opcode::get_and_lock:
325
+ case protocol::client_opcode::get_and_touch:
326
+ case protocol::client_opcode::touch:
327
+ case protocol::client_opcode::insert:
328
+ case protocol::client_opcode::replace:
329
+ case protocol::client_opcode::upsert:
330
+ case protocol::client_opcode::remove:
331
+ case protocol::client_opcode::observe:
332
+ case protocol::client_opcode::unlock:
333
+ case protocol::client_opcode::increment:
334
+ case protocol::client_opcode::decrement:
335
+ case protocol::client_opcode::subdoc_multi_lookup:
336
+ case protocol::client_opcode::subdoc_multi_mutation: {
337
+ std::uint32_t opaque = msg.header.opaque;
338
+ std::uint16_t status = ntohs(msg.header.specific);
339
+ auto handler = session_->command_handlers_.find(opaque);
340
+ if (handler != session_->command_handlers_.end()) {
341
+ handler->second(session_->map_status_code(opcode, status), std::move(msg));
342
+ session_->command_handlers_.erase(handler);
343
+ } else {
344
+ spdlog::debug("unexpected orphan response opcode={}, opaque={}", msg.header.opcode, msg.header.opaque);
345
+ }
346
+ } break;
347
+ default:
348
+ spdlog::warn("unexpected client response: {}", opcode);
289
349
  }
290
- } break;
291
- case protocol::client_opcode::get:
292
- case protocol::client_opcode::upsert:
293
- case protocol::client_opcode::remove:
294
- case protocol::client_opcode::subdoc_multi_lookup:
295
- case protocol::client_opcode::subdoc_multi_mutation: {
296
- std::uint32_t opaque = msg.header.opaque;
297
- std::uint16_t status = ntohs(msg.header.specific);
298
- auto handler = session_->command_handlers_.find(opaque);
299
- if (handler != session_->command_handlers_.end()) {
300
- handler->second(session_->map_status_code(opcode, status), std::move(msg));
301
- session_->command_handlers_.erase(handler);
302
- } else {
303
- spdlog::trace("unexpected orphan response opcode={}, opaque={}", msg.header.opcode, msg.header.opaque);
350
+ break;
351
+ case protocol::magic::server_request:
352
+ Expects(protocol::is_valid_server_request_opcode(msg.header.opcode));
353
+ switch (auto opcode = static_cast<protocol::server_opcode>(msg.header.opcode)) {
354
+ case protocol::server_opcode::cluster_map_change_notification: {
355
+ protocol::server_request<protocol::cluster_map_change_notification_request_body> req(msg);
356
+ if (session_) {
357
+ if ((!req.body().config().bucket.has_value() && req.body().bucket().empty()) ||
358
+ (session_->bucket_name_.has_value() && !req.body().bucket().empty() &&
359
+ session_->bucket_name_.value() == req.body().bucket())) {
360
+ session_->update_configuration(req.body().config());
361
+ }
362
+ }
363
+ } break;
364
+ default:
365
+ spdlog::warn("unexpected server request: {}", opcode);
304
366
  }
305
- } break;
306
- default:
307
- spdlog::trace("unexpected message: {}", opcode);
367
+ break;
368
+ case protocol::magic::client_request:
369
+ case protocol::magic::alt_client_request:
370
+ case protocol::magic::server_response:
371
+ spdlog::warn("unexpected magic: {}, opcode={}, opaque={}", magic, msg.header.opcode, msg.header.opaque);
372
+ break;
308
373
  }
309
374
  }
310
375
 
311
- void fetch_config()
376
+ void fetch_config(std::error_code ec)
312
377
  {
313
- if (session_->stopped_) {
378
+ if (ec == asio::error::operation_aborted) {
379
+ return;
380
+ }
381
+ if (stopped_ || !session_) {
314
382
  return;
315
383
  }
316
384
  protocol::client_request<protocol::get_cluster_config_request_body> req;
317
385
  req.opaque(session_->next_opaque());
318
386
  session_->write_and_flush(req.data());
319
387
  heartbeat_timer_.expires_after(std::chrono::milliseconds(2500));
320
- heartbeat_timer_.async_wait(std::bind(&normal_handler::fetch_config, this));
388
+ heartbeat_timer_.async_wait(std::bind(&normal_handler::fetch_config, this, std::placeholders::_1));
321
389
  }
322
390
  };
323
391
 
324
392
  public:
325
- key_value_session(uuid::uuid_t client_id, asio::io_context& ctx, std::optional<std::string> bucket_name = {})
393
+ mcbp_session(uuid::uuid_t client_id, asio::io_context& ctx, std::optional<std::string> bucket_name = {})
326
394
  : client_id_(client_id)
327
395
  , id_(uuid::random())
328
396
  , ctx_(ctx)
@@ -334,7 +402,7 @@ class key_value_session : public std::enable_shared_from_this
334
402
  {
335
403
  }
336
404
 
337
- ~key_value_session()
405
+ ~mcbp_session()
338
406
  {
339
407
  stop();
340
408
  }
@@ -343,13 +411,13 @@ class key_value_session : public std::enable_shared_from_this
343
411
  const std::string& service,
344
412
  const std::string& username,
345
413
  const std::string& password,
346
- std::function<void(std::error_code)>&& handler)
414
+ std::function<void(std::error_code, configuration)>&& handler)
347
415
  {
348
416
  username_ = username;
349
417
  password_ = password;
350
418
  bootstrap_handler_ = std::move(handler);
351
419
  resolver_.async_resolve(
352
- hostname, service, std::bind(&key_value_session::on_resolve, this, std::placeholders::_1, std::placeholders::_2));
420
+ hostname, service, std::bind(&mcbp_session::on_resolve, this, std::placeholders::_1, std::placeholders::_2));
353
421
  }
354
422
 
355
423
  [[nodiscard]] std::string id()
@@ -359,19 +427,18 @@ class key_value_session : public std::enable_shared_from_this
359
427
 
360
428
  void stop()
361
429
  {
362
- stopped_ = true;
363
- if (handler_) {
364
- handler_->stop();
430
+ if (stopped_) {
431
+ return;
365
432
  }
433
+ stopped_ = true;
434
+ deadline_timer_.cancel();
435
+ resolver_.cancel();
366
436
  if (socket_.is_open()) {
367
437
  socket_.close();
368
438
  }
369
- deadline_timer_.cancel();
370
- }
371
-
372
- bool is_stopped()
373
- {
374
- return stopped_;
439
+ if (handler_) {
440
+ handler_->stop();
441
+ }
375
442
  }
376
443
 
377
444
  void write(const std::vector<uint8_t>& buf)
@@ -401,13 +468,22 @@ class key_value_session : public std::enable_shared_from_this
401
468
 
402
469
  void write_and_subscribe(uint32_t opaque,
403
470
  std::vector<std::uint8_t>& data,
404
- std::function<void(std::error_code, io::binary_message&&)> handler)
471
+ std::function<void(std::error_code, io::mcbp_message&&)> handler)
405
472
  {
406
473
  if (stopped_) {
407
474
  return;
408
475
  }
409
476
  command_handlers_.emplace(opaque, std::move(handler));
410
- write_and_flush(data);
477
+ if (bootstrapped_ && socket_.is_open()) {
478
+ write_and_flush(data);
479
+ } else {
480
+ pending_buffer_.push_back(data);
481
+ }
482
+ }
483
+
484
+ [[nodiscard]] std::optional<collections_manifest> manifest()
485
+ {
486
+ return manifest_;
411
487
  }
412
488
 
413
489
  bool supports_feature(protocol::hello_feature feature)
@@ -415,6 +491,11 @@ class key_value_session : public std::enable_shared_from_this
415
491
  return std::find(supported_features_.begin(), supported_features_.end(), feature) != supported_features_.end();
416
492
  }
417
493
 
494
+ bool supports_gcccp()
495
+ {
496
+ return supports_gcccp_;
497
+ }
498
+
418
499
  [[nodiscard]] bool has_config() const
419
500
  {
420
501
  return config_.has_value();
@@ -570,23 +651,15 @@ class key_value_session : public std::enable_shared_from_this
570
651
  break;
571
652
  }
572
653
  // FIXME: use error map here
654
+ spdlog::warn("unknown status code: {} (opcode={})", status, opcode);
573
655
  return std::make_error_code(error::network_errc::protocol_error);
574
656
  }
575
657
 
576
- template<typename Handler>
577
- void subscribe_to_configuration_updates(Handler&& handler)
578
- {
579
- configuration_listeners_.emplace_back(std::forward<Handler>(handler));
580
- }
581
-
582
658
  void update_configuration(configuration&& config)
583
659
  {
584
660
  if (!config_ || config.rev > config_->rev) {
585
661
  config_.emplace(config);
586
662
  spdlog::trace("received new configuration: {}", config_.value());
587
- for (auto& listener : configuration_listeners_) {
588
- listener(config_.value());
589
- }
590
663
  }
591
664
  }
592
665
 
@@ -594,32 +667,45 @@ class key_value_session : public std::enable_shared_from_this
594
667
  void invoke_bootstrap_handler(std::error_code ec)
595
668
  {
596
669
  if (!bootstrapped_ && bootstrap_handler_) {
597
- bootstrap_handler_(ec);
670
+ bootstrap_handler_(ec, config_.value_or(configuration{}));
598
671
  bootstrap_handler_ = nullptr;
599
672
  }
600
673
  bootstrapped_ = true;
601
674
  if (ec) {
602
675
  stop();
603
676
  }
677
+ if (!pending_buffer_.empty()) {
678
+ for (const auto& buf : pending_buffer_) {
679
+ write(buf);
680
+ }
681
+ pending_buffer_.clear();
682
+ flush();
683
+ }
604
684
  }
605
685
 
606
686
  void on_resolve(std::error_code ec, const asio::ip::tcp::resolver::results_type& endpoints)
607
687
  {
688
+ if (stopped_) {
689
+ return;
690
+ }
608
691
  if (ec) {
609
692
  spdlog::error("error on resolve: {}", ec.message());
610
693
  return invoke_bootstrap_handler(std::make_error_code(error::network_errc::resolve_failure));
611
694
  }
612
695
  endpoints_ = endpoints;
613
696
  do_connect(endpoints_.begin());
614
- deadline_timer_.async_wait(std::bind(&key_value_session::check_deadline, this));
697
+ deadline_timer_.async_wait(std::bind(&mcbp_session::check_deadline, this, std::placeholders::_1));
615
698
  }
616
699
 
617
700
  void do_connect(asio::ip::tcp::resolver::results_type::iterator it)
618
701
  {
702
+ if (stopped_) {
703
+ return;
704
+ }
619
705
  if (it != endpoints_.end()) {
620
706
  spdlog::trace("connecting to {}:{}", it->endpoint().address().to_string(), it->endpoint().port());
621
707
  deadline_timer_.expires_after(std::chrono::seconds(10));
622
- socket_.async_connect(it->endpoint(), std::bind(&key_value_session::on_connect, this, std::placeholders::_1, it));
708
+ socket_.async_connect(it->endpoint(), std::bind(&mcbp_session::on_connect, this, std::placeholders::_1, it));
623
709
  } else {
624
710
  spdlog::error("no more endpoints left to connect");
625
711
  invoke_bootstrap_handler(std::make_error_code(error::network_errc::no_endpoints_left));
@@ -635,6 +721,8 @@ class key_value_session : public std::enable_shared_from_this
635
721
  if (!socket_.is_open() || ec) {
636
722
  do_connect(++it);
637
723
  } else {
724
+ socket_.set_option(asio::ip::tcp::no_delay{ true });
725
+ socket_.set_option(asio::socket_base::keep_alive{ true });
638
726
  endpoint_ = it->endpoint();
639
727
  spdlog::trace("connected to {}:{}", endpoint_.address().to_string(), it->endpoint().port());
640
728
  handler_ = std::make_unique<bootstrap_handler>(shared_from_this());
@@ -643,8 +731,11 @@ class key_value_session : public std::enable_shared_from_this
643
731
  }
644
732
  }
645
733
 
646
- void check_deadline()
734
+ void check_deadline(std::error_code ec)
647
735
  {
736
+ if (ec == asio::error::operation_aborted) {
737
+ return;
738
+ }
648
739
  if (stopped_) {
649
740
  return;
650
741
  }
@@ -652,7 +743,7 @@ class key_value_session : public std::enable_shared_from_this
652
743
  socket_.close();
653
744
  deadline_timer_.expires_at(asio::steady_timer::time_point::max());
654
745
  }
655
- deadline_timer_.async_wait(std::bind(&key_value_session::check_deadline, this));
746
+ deadline_timer_.async_wait(std::bind(&mcbp_session::check_deadline, this, std::placeholders::_1));
656
747
  }
657
748
 
658
749
  void do_read()
@@ -660,27 +751,35 @@ class key_value_session : public std::enable_shared_from_this
660
751
  if (stopped_) {
661
752
  return;
662
753
  }
754
+ if (reading_) {
755
+ return;
756
+ }
757
+ reading_ = true;
663
758
  socket_.async_read_some(asio::buffer(input_buffer_),
664
759
  [self = shared_from_this()](std::error_code ec, std::size_t bytes_transferred) {
665
- if (self->stopped_) {
760
+ if (ec == asio::error::operation_aborted || self->stopped_) {
666
761
  return;
667
762
  }
668
- if (ec && ec != asio::error::operation_aborted) {
669
- spdlog::error("IO error while reading from the socket: {}", ec.message());
763
+ if (ec) {
764
+ spdlog::error("[{}] [{}] IO error while reading from the socket: {}",
765
+ uuid::to_string(self->id_),
766
+ self->endpoint_.address().to_string(),
767
+ ec.message());
670
768
  return self->stop();
671
769
  }
672
770
  self->parser_.feed(self->input_buffer_.data(), self->input_buffer_.data() + ssize_t(bytes_transferred));
673
771
 
674
772
  for (;;) {
675
- binary_message msg{};
773
+ mcbp_message msg{};
676
774
  switch (self->parser_.next(msg)) {
677
- case binary_parser::ok:
775
+ case mcbp_parser::ok:
678
776
  self->handler_->handle(std::move(msg));
679
777
  break;
680
- case binary_parser::need_data:
778
+ case mcbp_parser::need_data:
779
+ self->reading_ = false;
681
780
  return self->do_read();
682
- case binary_parser::failure:
683
- ec = std::make_error_code(std::errc::protocol_error);
781
+ case mcbp_parser::failure:
782
+ ec = std::make_error_code(error::common_errc::parsing_failure);
684
783
  return self->stop();
685
784
  }
686
785
  }
@@ -706,7 +805,10 @@ class key_value_session : public std::enable_shared_from_this
706
805
  return;
707
806
  }
708
807
  if (ec) {
709
- spdlog::error("IO error while writing to the socket: {}", ec.message());
808
+ spdlog::error("[{}] [{}] IO error while writing to the socket: {}",
809
+ uuid::to_string(self->id_),
810
+ self->endpoint_.address().to_string(),
811
+ ec.message());
710
812
  return self->stop();
711
813
  }
712
814
  self->writing_buffer_.clear();
@@ -725,24 +827,25 @@ class key_value_session : public std::enable_shared_from_this
725
827
  asio::ip::tcp::socket socket_;
726
828
  asio::steady_timer deadline_timer_;
727
829
  std::optional<std::string> bucket_name_;
728
- binary_parser parser_;
830
+ mcbp_parser parser_;
729
831
  std::unique_ptr<message_handler> handler_;
730
- std::function<void(std::error_code)> bootstrap_handler_;
731
- std::map<uint32_t, std::function<void(std::error_code, io::binary_message&&)>> command_handlers_{};
832
+ std::function<void(std::error_code, configuration)> bootstrap_handler_;
833
+ std::map<uint32_t, std::function<void(std::error_code, io::mcbp_message&&)>> command_handlers_{};
732
834
 
733
835
  bool bootstrapped_{ false };
734
- bool stopped_{ false };
836
+ std::atomic_bool stopped_{ false };
735
837
  bool authenticated_{ false };
736
838
  bool bucket_selected_{ false };
839
+ bool supports_gcccp_{ true };
737
840
 
738
841
  std::atomic<std::uint32_t> opaque_{ 0 };
739
842
 
740
843
  std::string username_;
741
844
  std::string password_;
742
845
 
743
- std::list<std::function<void(const configuration&)>> configuration_listeners_;
744
846
  std::array<std::uint8_t, 16384> input_buffer_{};
745
847
  std::vector<std::vector<std::uint8_t>> output_buffer_{};
848
+ std::vector<std::vector<std::uint8_t>> pending_buffer_{};
746
849
  std::vector<std::vector<std::uint8_t>> writing_buffer_{};
747
850
  asio::ip::tcp::endpoint endpoint_{}; // connected endpoint
748
851
  asio::ip::tcp::resolver::results_type endpoints_;
@@ -750,5 +853,7 @@ class key_value_session : public std::enable_shared_from_this
750
853
  std::optional<configuration> config_;
751
854
  std::optional<error_map> errmap_;
752
855
  std::optional<collections_manifest> manifest_;
856
+
857
+ std::atomic_bool reading_{ false };
753
858
  };
754
- } // namespace couchbase::io
859
+ } // namespace couchbase::io