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

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 (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