couchbase 3.8.1 → 3.9.0

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 (475) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +2 -2
  3. data/ext/CMakeLists.txt +16 -1
  4. data/ext/cache/cpm/{CPM_0.42.0.cmake → CPM_0.43.1.cmake} +18 -2
  5. data/ext/cache/extconf_include.rb +6 -6
  6. data/ext/cache/mozilla-ca-bundle.crt +214 -155
  7. data/ext/cache/mozilla-ca-bundle.sha256 +1 -1
  8. data/ext/couchbase/CMakeLists.txt +130 -21
  9. data/ext/couchbase/cmake/APKBUILD.in +25 -4
  10. data/ext/couchbase/cmake/Bundler.cmake +143 -34
  11. data/ext/couchbase/cmake/CPM.cmake +126 -12
  12. data/ext/couchbase/cmake/Cache.cmake +7 -1
  13. data/ext/couchbase/cmake/CompilerOptions.cmake +1 -1
  14. data/ext/couchbase/cmake/CompilerWarnings.cmake +17 -6
  15. data/ext/couchbase/cmake/GrpcProtobuf.cmake +481 -0
  16. data/ext/couchbase/cmake/OpenSSL.cmake +10 -3
  17. data/ext/couchbase/cmake/Packaging.cmake +519 -65
  18. data/ext/couchbase/cmake/Protostellar.cmake +250 -0
  19. data/ext/couchbase/cmake/Sanitizers.cmake +61 -50
  20. data/ext/couchbase/cmake/StaticAnalyzers.cmake +16 -5
  21. data/ext/couchbase/cmake/TestFramework.cmake +283 -0
  22. data/ext/couchbase/cmake/TestFrameworkAddTests.cmake +51 -0
  23. data/ext/couchbase/cmake/Testing.cmake +27 -9
  24. data/ext/couchbase/cmake/ThirdPartyDependencies.cmake +141 -8
  25. data/ext/couchbase/cmake/VersionInfo.cmake +50 -0
  26. data/ext/couchbase/cmake/build_config.hxx.in +2 -1
  27. data/ext/couchbase/cmake/couchbase-cxx-client.spec.in +101 -23
  28. data/ext/couchbase/cmake/couchbase_cxx_client_static-config.cmake.in +7 -0
  29. data/ext/couchbase/cmake/couchbase_cxx_client_static.pc.in +2 -0
  30. data/ext/couchbase/cmake/gRPC_Generate.cmake +58 -0
  31. data/ext/couchbase/cmake/protobuf_Generate.cmake +69 -0
  32. data/ext/couchbase/cmake/require-git-checkout.cmake +25 -0
  33. data/ext/couchbase/cmake/tarball_glob.txt +49 -0
  34. data/ext/couchbase/core/agent_group.cxx +0 -8
  35. data/ext/couchbase/core/agent_group.hxx +0 -4
  36. data/ext/couchbase/core/analytics_stream.cxx +314 -0
  37. data/ext/couchbase/core/analytics_stream.hxx +129 -0
  38. data/ext/couchbase/core/analytics_stream_component.cxx +245 -0
  39. data/ext/couchbase/core/analytics_stream_component.hxx +67 -0
  40. data/ext/couchbase/core/app_telemetry_meter.cxx +25 -3
  41. data/ext/couchbase/core/app_telemetry_meter.hxx +77 -0
  42. data/ext/couchbase/core/app_telemetry_reporter.cxx +7 -6
  43. data/ext/couchbase/core/bucket.cxx +381 -87
  44. data/ext/couchbase/core/bucket.hxx +105 -25
  45. data/ext/couchbase/core/bucket_unit_test_api.hxx +57 -0
  46. data/ext/couchbase/core/capella.hxx +91 -0
  47. data/ext/couchbase/core/cluster.cxx +1554 -398
  48. data/ext/couchbase/core/cluster.hxx +39 -8
  49. data/ext/couchbase/core/cluster_agent.cxx +0 -8
  50. data/ext/couchbase/core/cluster_agent.hxx +0 -4
  51. data/ext/couchbase/core/cluster_options.hxx +26 -3
  52. data/ext/couchbase/core/collections_component.cxx +39 -15
  53. data/ext/couchbase/core/config_profiles.cxx +1 -1
  54. data/ext/couchbase/core/crud_component.cxx +2 -2
  55. data/ext/couchbase/core/crud_options.hxx +34 -0
  56. data/ext/couchbase/core/crypto/cbcrypto.cc +18 -13
  57. data/ext/couchbase/core/document_id_redaction.hxx +81 -0
  58. data/ext/couchbase/core/error_context/base_error_context.hxx +7 -6
  59. data/ext/couchbase/core/error_context/key_value.cxx +25 -2
  60. data/ext/couchbase/core/error_context/key_value.hxx +60 -33
  61. data/ext/couchbase/core/error_context/key_value_error_context.hxx +16 -15
  62. data/ext/couchbase/core/error_context/search.hxx +5 -0
  63. data/ext/couchbase/core/error_context/search_json.hxx +3 -0
  64. data/ext/couchbase/core/error_context/subdocument_error_context.hxx +19 -18
  65. data/ext/couchbase/core/error_context/view.hxx +5 -0
  66. data/ext/couchbase/core/free_form_http_request.cxx +237 -4
  67. data/ext/couchbase/core/free_form_http_request.hxx +41 -8
  68. data/ext/couchbase/core/http_component.cxx +300 -284
  69. data/ext/couchbase/core/http_component.hxx +0 -4
  70. data/ext/couchbase/core/impl/analytics.cxx +38 -5
  71. data/ext/couchbase/core/impl/analytics.hxx +34 -0
  72. data/ext/couchbase/core/impl/bootstrap_error.hxx +0 -3
  73. data/ext/couchbase/core/impl/collection.cxx +215 -202
  74. data/ext/couchbase/core/impl/collection_manager.cxx +1 -1
  75. data/ext/couchbase/core/impl/dns_srv_tracker.cxx +45 -18
  76. data/ext/couchbase/core/impl/error.cxx +22 -2
  77. data/ext/couchbase/core/impl/internal_analytics_stream_result.hxx +111 -0
  78. data/ext/couchbase/core/impl/internal_query_stream_result.hxx +110 -0
  79. data/ext/couchbase/core/impl/invoke_with_node_id.hxx +15 -7
  80. data/ext/couchbase/core/impl/key_value_error_category.cxx +6 -0
  81. data/ext/couchbase/core/impl/lookup_in_replica.cxx +10 -10
  82. data/ext/couchbase/core/impl/lookup_in_replica.hxx +4 -4
  83. data/ext/couchbase/core/impl/node_id.cxx +49 -13
  84. data/ext/couchbase/core/impl/observability_recorder.cxx +12 -2
  85. data/ext/couchbase/core/impl/observe_poll.cxx +63 -23
  86. data/ext/couchbase/core/impl/public_analytics_stream_result.cxx +372 -0
  87. data/ext/couchbase/core/impl/public_bucket.cxx +48 -2
  88. data/ext/couchbase/core/impl/public_cluster.cxx +325 -25
  89. data/ext/couchbase/core/impl/public_query_stream_result.cxx +380 -0
  90. data/ext/couchbase/core/impl/public_transaction_get_multi_replicas_from_preferred_server_group_result.cxx +42 -0
  91. data/ext/couchbase/core/impl/public_transaction_get_multi_result.cxx +42 -0
  92. data/ext/couchbase/core/impl/query.cxx +76 -7
  93. data/ext/couchbase/core/impl/query.hxx +36 -0
  94. data/ext/couchbase/core/impl/query_index_manager.cxx +54 -1
  95. data/ext/couchbase/core/impl/replica_utils.cxx +101 -0
  96. data/ext/couchbase/core/impl/replica_utils.hxx +64 -0
  97. data/ext/couchbase/core/impl/resolve_node_id.hxx +125 -0
  98. data/ext/couchbase/core/impl/scope.cxx +70 -0
  99. data/ext/couchbase/core/impl/search.cxx +31 -1
  100. data/ext/couchbase/core/impl/transactions.cxx +130 -0
  101. data/ext/couchbase/core/impl/wait_until_ready.cxx +439 -0
  102. data/ext/couchbase/core/impl/wait_until_ready.hxx +110 -0
  103. data/ext/couchbase/core/impl/with_bucket_config_or_timeout.hxx +180 -0
  104. data/ext/couchbase/core/impl/with_cancellation.hxx +11 -3
  105. data/ext/couchbase/core/io/configuration_belongs_to_session.hxx +66 -0
  106. data/ext/couchbase/core/io/dns_client.cxx +122 -45
  107. data/ext/couchbase/core/io/dns_codec.hxx +21 -21
  108. data/ext/couchbase/core/io/dns_config.cxx +21 -6
  109. data/ext/couchbase/core/io/http_command.hxx +149 -121
  110. data/ext/couchbase/core/io/http_error.hxx +107 -0
  111. data/ext/couchbase/core/io/http_parser.cxx +4 -0
  112. data/ext/couchbase/core/io/http_session.cxx +606 -154
  113. data/ext/couchbase/core/io/http_session.hxx +147 -30
  114. data/ext/couchbase/core/io/http_session_manager.hxx +329 -382
  115. data/ext/couchbase/core/io/http_streaming_parser.cxx +4 -0
  116. data/ext/couchbase/core/io/http_streaming_response.cxx +328 -43
  117. data/ext/couchbase/core/io/http_streaming_response.hxx +37 -3
  118. data/ext/couchbase/core/io/mcbp_buffer_pool.hxx +122 -0
  119. data/ext/couchbase/core/io/mcbp_command.hxx +150 -51
  120. data/ext/couchbase/core/io/mcbp_command_id.hxx +45 -0
  121. data/ext/couchbase/core/io/mcbp_message.cxx +9 -1
  122. data/ext/couchbase/core/io/mcbp_message.hxx +19 -0
  123. data/ext/couchbase/core/io/mcbp_output_queue.hxx +150 -0
  124. data/ext/couchbase/core/io/mcbp_parser.cxx +24 -7
  125. data/ext/couchbase/core/io/mcbp_session.cxx +316 -317
  126. data/ext/couchbase/core/io/mcbp_session.hxx +27 -18
  127. data/ext/couchbase/core/io/opaque_ring_table.hxx +145 -0
  128. data/ext/couchbase/core/io/retry_orchestrator.hxx +6 -5
  129. data/ext/couchbase/core/io/stream_deadline.hxx +50 -0
  130. data/ext/couchbase/core/io/streams.cxx +275 -14
  131. data/ext/couchbase/core/io/streams.hxx +53 -0
  132. data/ext/couchbase/core/logger/logger.cxx +36 -0
  133. data/ext/couchbase/core/logger/logger.hxx +46 -6
  134. data/ext/couchbase/core/logger/redaction.hxx +503 -0
  135. data/ext/couchbase/core/management/eventing_status.hxx +10 -0
  136. data/ext/couchbase/core/management/eventing_status_json.hxx +4 -0
  137. data/ext/couchbase/core/mcbp/big_endian.cxx +1 -1
  138. data/ext/couchbase/core/mcbp/codec.cxx +27 -18
  139. data/ext/couchbase/core/mcbp/operation_queue.cxx +2 -2
  140. data/ext/couchbase/core/mcbp/packet.hxx +2 -2
  141. data/ext/couchbase/core/mcbp/queue_request.cxx +23 -6
  142. data/ext/couchbase/core/mcbp/queue_request.hxx +21 -0
  143. data/ext/couchbase/core/meta/features.hxx +13 -0
  144. data/ext/couchbase/core/meta/version.cxx +6 -3
  145. data/ext/couchbase/core/metrics/logging_meter.cxx +5 -10
  146. data/ext/couchbase/core/metrics/meter_wrapper.cxx +63 -33
  147. data/ext/couchbase/core/metrics/meter_wrapper.hxx +55 -3
  148. data/ext/couchbase/core/operations/analytics_response_parsing.cxx +177 -0
  149. data/ext/couchbase/core/operations/analytics_response_parsing.hxx +55 -0
  150. data/ext/couchbase/core/operations/document_analytics.cxx +48 -130
  151. data/ext/couchbase/core/operations/document_analytics.hxx +14 -2
  152. data/ext/couchbase/core/operations/document_get_all_replicas.hxx +15 -7
  153. data/ext/couchbase/core/operations/document_get_any_replica.hxx +19 -8
  154. data/ext/couchbase/core/operations/document_get_projected.cxx +1 -1
  155. data/ext/couchbase/core/{impl/get_replica.cxx → operations/document_get_replica.cxx} +21 -3
  156. data/ext/couchbase/core/{impl/get_replica.hxx → operations/document_get_replica.hxx} +31 -4
  157. data/ext/couchbase/core/operations/document_lookup_in.cxx +10 -10
  158. data/ext/couchbase/core/operations/document_lookup_in_all_replicas.hxx +228 -198
  159. data/ext/couchbase/core/operations/document_lookup_in_any_replica.hxx +215 -217
  160. data/ext/couchbase/core/operations/document_query.cxx +108 -221
  161. data/ext/couchbase/core/operations/document_query.hxx +18 -4
  162. data/ext/couchbase/core/operations/document_search.cxx +52 -8
  163. data/ext/couchbase/core/operations/document_search.hxx +3 -0
  164. data/ext/couchbase/core/operations/document_view.cxx +3 -3
  165. data/ext/couchbase/core/operations/management/analytics_dataset_create.cxx +15 -8
  166. data/ext/couchbase/core/operations/management/analytics_dataset_create.hxx +4 -0
  167. data/ext/couchbase/core/operations/management/analytics_dataset_drop.cxx +11 -6
  168. data/ext/couchbase/core/operations/management/analytics_dataset_get_all.cxx +3 -2
  169. data/ext/couchbase/core/operations/management/analytics_dataverse_create.cxx +10 -5
  170. data/ext/couchbase/core/operations/management/analytics_dataverse_drop.cxx +10 -5
  171. data/ext/couchbase/core/operations/management/analytics_get_pending_mutations.cxx +2 -1
  172. data/ext/couchbase/core/operations/management/analytics_index_create.cxx +33 -8
  173. data/ext/couchbase/core/operations/management/analytics_index_drop.cxx +12 -7
  174. data/ext/couchbase/core/operations/management/analytics_index_get_all.cxx +3 -2
  175. data/ext/couchbase/core/operations/management/analytics_link_connect.cxx +11 -6
  176. data/ext/couchbase/core/operations/management/analytics_link_create.cxx +3 -3
  177. data/ext/couchbase/core/operations/management/analytics_link_disconnect.cxx +10 -4
  178. data/ext/couchbase/core/operations/management/analytics_link_drop.cxx +6 -4
  179. data/ext/couchbase/core/operations/management/analytics_link_get_all.cxx +4 -3
  180. data/ext/couchbase/core/operations/management/analytics_link_replace.cxx +3 -3
  181. data/ext/couchbase/core/operations/management/analytics_link_utils.hxx +1 -1
  182. data/ext/couchbase/core/operations/management/bucket_create.cxx +37 -38
  183. data/ext/couchbase/core/operations/management/bucket_update.cxx +26 -28
  184. data/ext/couchbase/core/operations/management/change_password.cxx +2 -0
  185. data/ext/couchbase/core/operations/management/collection_drop.cxx +5 -1
  186. data/ext/couchbase/core/operations/management/error_utils.cxx +59 -5
  187. data/ext/couchbase/core/operations/management/error_utils.hxx +29 -1
  188. data/ext/couchbase/core/operations/management/eventing_deploy_function.cxx +9 -3
  189. data/ext/couchbase/core/operations/management/eventing_drop_function.cxx +8 -3
  190. data/ext/couchbase/core/operations/management/eventing_get_all_functions.cxx +4 -2
  191. data/ext/couchbase/core/operations/management/eventing_get_function.cxx +5 -3
  192. data/ext/couchbase/core/operations/management/eventing_get_status.cxx +4 -2
  193. data/ext/couchbase/core/operations/management/eventing_pause_function.cxx +9 -3
  194. data/ext/couchbase/core/operations/management/eventing_resume_function.cxx +9 -3
  195. data/ext/couchbase/core/operations/management/eventing_undeploy_function.cxx +9 -3
  196. data/ext/couchbase/core/operations/management/eventing_upsert_function.cxx +9 -3
  197. data/ext/couchbase/core/operations/management/group_drop.cxx +3 -1
  198. data/ext/couchbase/core/operations/management/group_get.cxx +3 -1
  199. data/ext/couchbase/core/operations/management/group_upsert.cxx +11 -8
  200. data/ext/couchbase/core/operations/management/query_index_build.cxx +5 -2
  201. data/ext/couchbase/core/operations/management/query_index_create.cxx +9 -3
  202. data/ext/couchbase/core/operations/management/query_index_drop.cxx +11 -4
  203. data/ext/couchbase/core/operations/management/query_index_get_all.cxx +1 -1
  204. data/ext/couchbase/core/operations/management/query_index_get_all_deferred.cxx +1 -1
  205. data/ext/couchbase/core/operations/management/scope_drop.cxx +5 -1
  206. data/ext/couchbase/core/operations/management/scope_get_all.cxx +6 -1
  207. data/ext/couchbase/core/operations/management/search_get_stats.cxx +10 -0
  208. data/ext/couchbase/core/operations/management/search_index_analyze_document.cxx +3 -2
  209. data/ext/couchbase/core/operations/management/search_index_control_ingest.cxx +4 -3
  210. data/ext/couchbase/core/operations/management/search_index_control_plan_freeze.cxx +4 -3
  211. data/ext/couchbase/core/operations/management/search_index_control_query.cxx +4 -3
  212. data/ext/couchbase/core/operations/management/search_index_drop.cxx +2 -2
  213. data/ext/couchbase/core/operations/management/search_index_get.cxx +2 -2
  214. data/ext/couchbase/core/operations/management/search_index_get_documents_count.cxx +3 -2
  215. data/ext/couchbase/core/operations/management/search_index_get_stats.cxx +3 -1
  216. data/ext/couchbase/core/operations/management/search_index_upsert.cxx +2 -2
  217. data/ext/couchbase/core/operations/management/user_drop.cxx +3 -1
  218. data/ext/couchbase/core/operations/management/user_get.cxx +3 -1
  219. data/ext/couchbase/core/operations/management/user_upsert.cxx +12 -9
  220. data/ext/couchbase/core/operations/management/view_index_drop.cxx +1 -1
  221. data/ext/couchbase/core/operations/management/view_index_get.cxx +1 -1
  222. data/ext/couchbase/core/operations/management/view_index_upsert.cxx +1 -1
  223. data/ext/couchbase/core/operations/operation_traits.hxx +18 -0
  224. data/ext/couchbase/core/operations/query_response_parsing.cxx +211 -0
  225. data/ext/couchbase/core/operations/query_response_parsing.hxx +60 -0
  226. data/ext/couchbase/core/operations_fwd.hxx +3 -3
  227. data/ext/couchbase/core/origin.cxx +54 -26
  228. data/ext/couchbase/core/origin.hxx +12 -0
  229. data/ext/couchbase/core/orphan_reporter.cxx +2 -1
  230. data/ext/couchbase/core/platform/random.cc +7 -7
  231. data/ext/couchbase/core/protocol/client_request.cxx +2 -2
  232. data/ext/couchbase/core/protocol/client_request.hxx +38 -21
  233. data/ext/couchbase/core/protocol/client_response.cxx +11 -3
  234. data/ext/couchbase/core/protocol/client_response.hxx +27 -6
  235. data/ext/couchbase/core/protocol/cmd_append.cxx +2 -2
  236. data/ext/couchbase/core/protocol/cmd_cluster_map_change_notification.cxx +2 -2
  237. data/ext/couchbase/core/protocol/cmd_decrement.cxx +6 -6
  238. data/ext/couchbase/core/protocol/cmd_get.cxx +1 -1
  239. data/ext/couchbase/core/protocol/cmd_get_and_lock.cxx +2 -2
  240. data/ext/couchbase/core/protocol/cmd_get_and_touch.cxx +2 -2
  241. data/ext/couchbase/core/protocol/cmd_get_cluster_config.cxx +6 -4
  242. data/ext/couchbase/core/protocol/cmd_get_collection_id.cxx +2 -2
  243. data/ext/couchbase/core/protocol/cmd_get_error_map.cxx +1 -1
  244. data/ext/couchbase/core/protocol/cmd_get_meta.cxx +4 -4
  245. data/ext/couchbase/core/protocol/cmd_get_replica.cxx +1 -1
  246. data/ext/couchbase/core/protocol/cmd_hello.cxx +1 -1
  247. data/ext/couchbase/core/protocol/cmd_increment.cxx +6 -6
  248. data/ext/couchbase/core/protocol/cmd_insert.cxx +4 -4
  249. data/ext/couchbase/core/protocol/cmd_lookup_in.cxx +4 -3
  250. data/ext/couchbase/core/protocol/cmd_lookup_in_replica.cxx +4 -3
  251. data/ext/couchbase/core/protocol/cmd_mutate_in.cxx +10 -9
  252. data/ext/couchbase/core/protocol/cmd_mutate_in.hxx +10 -10
  253. data/ext/couchbase/core/protocol/cmd_observe_seqno.cxx +7 -7
  254. data/ext/couchbase/core/protocol/cmd_prepend.cxx +2 -2
  255. data/ext/couchbase/core/protocol/cmd_remove.cxx +2 -2
  256. data/ext/couchbase/core/protocol/cmd_replace.cxx +4 -4
  257. data/ext/couchbase/core/protocol/cmd_touch.cxx +1 -1
  258. data/ext/couchbase/core/protocol/cmd_upsert.cxx +4 -4
  259. data/ext/couchbase/core/protocol/datatype.hxx +118 -1
  260. data/ext/couchbase/core/protocol/frame_info_utils.cxx +1 -1
  261. data/ext/couchbase/core/protocol/magic.hxx +4 -0
  262. data/ext/couchbase/core/protocol/magic_fmt.hxx +3 -0
  263. data/ext/couchbase/core/protocol/server_request.hxx +3 -1
  264. data/ext/couchbase/core/protocol/status.cxx +1 -1
  265. data/ext/couchbase/core/protostellar/analytics_converter.hxx +171 -0
  266. data/ext/couchbase/core/protostellar/bucket_admin_converter.hxx +301 -0
  267. data/ext/couchbase/core/protostellar/collection_admin_converter.hxx +92 -0
  268. data/ext/couchbase/core/protostellar/component.cxx +2445 -0
  269. data/ext/couchbase/core/protostellar/component.hxx +391 -0
  270. data/ext/couchbase/core/{columnar/bootstrap_notification_subscriber.hxx → protostellar/compression_settings.hxx} +11 -10
  271. data/ext/couchbase/core/protostellar/credentials.cxx +233 -0
  272. data/ext/couchbase/core/protostellar/credentials.hxx +88 -0
  273. data/ext/couchbase/core/protostellar/dispatcher.cxx +57 -0
  274. data/ext/couchbase/core/protostellar/dispatcher.hxx +482 -0
  275. data/ext/couchbase/core/protostellar/error_utils.cxx +293 -0
  276. data/ext/couchbase/core/protostellar/error_utils.hxx +97 -0
  277. data/ext/couchbase/core/protostellar/json_payload.hxx +49 -0
  278. data/ext/couchbase/core/protostellar/kv_converter.cxx +70 -0
  279. data/ext/couchbase/core/protostellar/kv_converter.hxx +600 -0
  280. data/ext/couchbase/core/protostellar/query_converter.hxx +265 -0
  281. data/ext/couchbase/core/protostellar/query_index_admin_converter.hxx +291 -0
  282. data/ext/couchbase/core/protostellar/query_proto.hxx +73 -0
  283. data/ext/couchbase/core/protostellar/search_converter.hxx +244 -0
  284. data/ext/couchbase/core/protostellar/search_index_admin_converter.hxx +156 -0
  285. data/ext/couchbase/core/protostellar/view_converter.hxx +158 -0
  286. data/ext/couchbase/core/query_stream.cxx +447 -0
  287. data/ext/couchbase/core/query_stream.hxx +141 -0
  288. data/ext/couchbase/core/query_stream_component.cxx +243 -0
  289. data/ext/couchbase/core/query_stream_component.hxx +68 -0
  290. data/ext/couchbase/core/range_scan_load_balancer.cxx +15 -8
  291. data/ext/couchbase/core/range_scan_orchestrator.cxx +15 -3
  292. data/ext/couchbase/core/response_handler.hxx +41 -4
  293. data/ext/couchbase/core/row_streamer.cxx +332 -46
  294. data/ext/couchbase/core/row_streamer.hxx +31 -1
  295. data/ext/couchbase/core/row_streamer_options.hxx +55 -0
  296. data/ext/couchbase/core/sasl/client.h +3 -0
  297. data/ext/couchbase/core/search_scoring.hxx +66 -0
  298. data/ext/couchbase/core/stream_error_details.hxx +69 -0
  299. data/ext/couchbase/core/timeout_defaults.hxx +0 -1
  300. data/ext/couchbase/core/topology/capabilities.hxx +6 -0
  301. data/ext/couchbase/core/topology/capabilities_fmt.hxx +3 -0
  302. data/ext/couchbase/core/topology/configuration.cxx +21 -6
  303. data/ext/couchbase/core/topology/configuration.hxx +29 -8
  304. data/ext/couchbase/core/topology/configuration_json.hxx +3 -0
  305. data/ext/couchbase/core/topology/error_map_json.hxx +3 -1
  306. data/ext/couchbase/core/tracing/threshold_logging_tracer.cxx +70 -4
  307. data/ext/couchbase/core/tracing/threshold_logging_tracer.hxx +10 -0
  308. data/ext/couchbase/core/transactions/active_transaction_record.cxx +0 -34
  309. data/ext/couchbase/core/transactions/attempt_context_impl.cxx +362 -241
  310. data/ext/couchbase/core/transactions/attempt_context_impl.hxx +24 -3
  311. data/ext/couchbase/core/transactions/error_list.hxx +2 -2
  312. data/ext/couchbase/core/transactions/get_multi_fetch.hxx +160 -0
  313. data/ext/couchbase/core/transactions/get_multi_orchestrator.cxx +326 -125
  314. data/ext/couchbase/core/transactions/get_multi_transaction_id.hxx +62 -0
  315. data/ext/couchbase/core/transactions/internal/atr_entry.hxx +22 -2
  316. data/ext/couchbase/core/transactions/internal/transaction_context.hxx +2 -2
  317. data/ext/couchbase/core/transactions/internal/transactions_cleanup.hxx +23 -1
  318. data/ext/couchbase/core/transactions/internal/utils.hxx +50 -14
  319. data/ext/couchbase/core/transactions/staged_mutation.cxx +23 -18
  320. data/ext/couchbase/core/transactions/staged_mutation.hxx +15 -4
  321. data/ext/couchbase/core/transactions/transaction_context.cxx +55 -40
  322. data/ext/couchbase/core/transactions/transaction_get_result.hxx +16 -0
  323. data/ext/couchbase/core/transactions/transactions.cxx +3 -55
  324. data/ext/couchbase/core/transactions/transactions_cleanup.cxx +194 -66
  325. data/ext/couchbase/core/transactions/transactions_config.cxx +0 -11
  326. data/ext/couchbase/core/transactions/utils.cxx +10 -0
  327. data/ext/couchbase/core/transactions/waitable_op_list.hxx +15 -4
  328. data/ext/couchbase/core/transactions.hxx +9 -9
  329. data/ext/couchbase/core/utils/byteswap.hxx +166 -9
  330. data/ext/couchbase/core/utils/connection_string.cxx +50 -85
  331. data/ext/couchbase/core/utils/connection_string.hxx +15 -0
  332. data/ext/couchbase/core/{columnar/backoff_calculator.hxx → utils/deadline.hxx} +13 -13
  333. data/ext/couchbase/core/utils/duration_parser.cxx +1 -1
  334. data/ext/couchbase/core/utils/json_streaming_lexer.cxx +48 -5
  335. data/ext/couchbase/core/utils/json_streaming_lexer.hxx +4 -1
  336. data/ext/couchbase/core/utils/movable_function.hxx +301 -52
  337. data/ext/couchbase/core/utils/name_codec.hxx +154 -11
  338. data/ext/couchbase/core/{columnar/retry_info.hxx → utils/process_id.hxx} +18 -13
  339. data/ext/couchbase/core/utils/url_codec.cxx +12 -51
  340. data/ext/couchbase/core/websocket_codec.cxx +3 -29
  341. data/ext/couchbase/couchbase/analytics_options.hxx +21 -10
  342. data/ext/couchbase/couchbase/analytics_row.hxx +105 -0
  343. data/ext/couchbase/couchbase/analytics_stream_result.hxx +266 -0
  344. data/ext/couchbase/couchbase/boolean_query.hxx +3 -3
  345. data/ext/couchbase/couchbase/bucket.hxx +57 -0
  346. data/ext/couchbase/couchbase/cluster.hxx +216 -5
  347. data/ext/couchbase/{core/columnar/security_options.hxx → couchbase/cluster_state.hxx} +18 -14
  348. data/ext/couchbase/couchbase/codec/lenient_json_transcoder.hxx +69 -0
  349. data/ext/couchbase/couchbase/collection.hxx +234 -14
  350. data/ext/couchbase/couchbase/common_durability_options.hxx +18 -3
  351. data/ext/couchbase/couchbase/conjunction_query.hxx +1 -1
  352. data/ext/couchbase/couchbase/create_dataset_analytics_options.hxx +6 -1
  353. data/ext/couchbase/couchbase/date_range_query.hxx +2 -2
  354. data/ext/couchbase/couchbase/decrement_options.hxx +1 -1
  355. data/ext/couchbase/couchbase/disjunction_query.hxx +1 -1
  356. data/ext/couchbase/couchbase/error.hxx +32 -0
  357. data/ext/couchbase/couchbase/error_codes.hxx +32 -0
  358. data/ext/couchbase/couchbase/geo_bounding_box_query.hxx +1 -1
  359. data/ext/couchbase/couchbase/geo_distance_query.hxx +1 -1
  360. data/ext/couchbase/couchbase/geo_polygon_query.hxx +1 -1
  361. data/ext/couchbase/couchbase/get_replica_options.hxx +68 -0
  362. data/ext/couchbase/couchbase/get_replica_result.hxx +5 -3
  363. data/ext/couchbase/couchbase/get_replica_strategy.hxx +161 -0
  364. data/ext/couchbase/couchbase/get_result.hxx +1 -0
  365. data/ext/couchbase/couchbase/increment_options.hxx +1 -1
  366. data/ext/couchbase/couchbase/insert_options.hxx +1 -1
  367. data/ext/couchbase/couchbase/match_phrase_query.hxx +1 -1
  368. data/ext/couchbase/couchbase/match_query.hxx +1 -1
  369. data/ext/couchbase/couchbase/mutate_in_options.hxx +3 -3
  370. data/ext/couchbase/couchbase/network_options.hxx +1 -1
  371. data/ext/couchbase/couchbase/node_id.hxx +42 -14
  372. data/ext/couchbase/couchbase/node_id_for_options.hxx +7 -1
  373. data/ext/couchbase/couchbase/node_ids_options.hxx +8 -2
  374. data/ext/couchbase/couchbase/numeric_range_query.hxx +1 -1
  375. data/ext/couchbase/couchbase/phrase_query.hxx +1 -1
  376. data/ext/couchbase/couchbase/prefix_query.hxx +1 -1
  377. data/ext/couchbase/couchbase/query_options.hxx +23 -12
  378. data/ext/couchbase/couchbase/query_row.hxx +111 -0
  379. data/ext/couchbase/couchbase/query_stream_result.hxx +302 -0
  380. data/ext/couchbase/couchbase/query_string_query.hxx +3 -3
  381. data/ext/couchbase/couchbase/regexp_query.hxx +1 -1
  382. data/ext/couchbase/couchbase/replace_options.hxx +3 -3
  383. data/ext/couchbase/couchbase/result.hxx +14 -3
  384. data/ext/couchbase/couchbase/scan_result_item.hxx +1 -0
  385. data/ext/couchbase/couchbase/scope.hxx +85 -2
  386. data/ext/couchbase/couchbase/search_options.hxx +58 -8
  387. data/ext/couchbase/couchbase/search_scoring.hxx +94 -0
  388. data/ext/couchbase/couchbase/search_scoring_none.hxx +51 -0
  389. data/ext/couchbase/couchbase/search_scoring_reciprocal_rank_fusion.hxx +88 -0
  390. data/ext/couchbase/couchbase/search_scoring_relative_score_fusion.hxx +70 -0
  391. data/ext/couchbase/couchbase/term_range_query.hxx +1 -1
  392. data/ext/couchbase/couchbase/tracing/request_span.hxx +46 -0
  393. data/ext/couchbase/couchbase/transactions/transaction_get_multi_replicas_from_preferred_server_group_result.hxx +23 -21
  394. data/ext/couchbase/couchbase/transactions/transaction_get_multi_result.hxx +24 -19
  395. data/ext/couchbase/couchbase/transactions/transaction_get_result.hxx +17 -3
  396. data/ext/couchbase/couchbase/transactions/transactions_config.hxx +4 -2
  397. data/ext/couchbase/couchbase/transactions.hxx +27 -18
  398. data/ext/couchbase/couchbase/upsert_options.hxx +3 -3
  399. data/ext/couchbase/couchbase/vector_query.hxx +1 -1
  400. data/ext/couchbase/couchbase/wait_until_ready_options.hxx +111 -0
  401. data/ext/couchbase/couchbase/wildcard_query.hxx +1 -1
  402. data/ext/extconf.rb +15 -10
  403. data/ext/rcb_analytics.cxx +82 -102
  404. data/ext/rcb_backend.cxx +42 -36
  405. data/ext/rcb_backend.hxx +2 -0
  406. data/ext/rcb_buckets.cxx +47 -59
  407. data/ext/rcb_collections.cxx +42 -52
  408. data/ext/rcb_crud.cxx +345 -224
  409. data/ext/rcb_diagnostics.cxx +117 -27
  410. data/ext/rcb_exceptions.cxx +152 -99
  411. data/ext/rcb_exceptions.hxx +25 -0
  412. data/ext/rcb_extras.cxx +18 -19
  413. data/ext/rcb_hdr_histogram.cxx +19 -4
  414. data/ext/rcb_logger.cxx +202 -27
  415. data/ext/rcb_logger.hxx +12 -0
  416. data/ext/rcb_multi.cxx +40 -40
  417. data/ext/rcb_observability.cxx +7 -5
  418. data/ext/rcb_query.cxx +99 -142
  419. data/ext/rcb_range_scan.cxx +41 -35
  420. data/ext/rcb_search.cxx +98 -99
  421. data/ext/rcb_users.cxx +36 -56
  422. data/ext/rcb_utils.cxx +100 -37
  423. data/ext/rcb_utils.hxx +287 -33
  424. data/ext/rcb_version.cxx +1 -1
  425. data/ext/rcb_views.cxx +55 -51
  426. data/lib/active_support/cache/couchbase_store.rb +77 -12
  427. data/lib/couchbase/bucket.rb +20 -0
  428. data/lib/couchbase/cluster.rb +33 -14
  429. data/lib/couchbase/collection.rb +36 -1
  430. data/lib/couchbase/errors.rb +18 -0
  431. data/lib/couchbase/fork_hooks.rb +16 -7
  432. data/lib/couchbase/get_replica_strategy.rb +89 -0
  433. data/lib/couchbase/logger.rb +1 -1
  434. data/lib/couchbase/management/analytics_index_manager.rb +15 -15
  435. data/lib/couchbase/management/bucket_manager.rb +6 -6
  436. data/lib/couchbase/management/collection_manager.rb +6 -6
  437. data/lib/couchbase/management/query_index_manager.rb +7 -7
  438. data/lib/couchbase/management/search_index_manager.rb +13 -13
  439. data/lib/couchbase/management/user_manager.rb +10 -10
  440. data/lib/couchbase/management/view_index_manager.rb +5 -5
  441. data/lib/couchbase/options.rb +181 -44
  442. data/lib/couchbase/protostellar/bucket.rb +4 -0
  443. data/lib/couchbase/protostellar/cluster.rb +4 -0
  444. data/lib/couchbase/protostellar/collection.rb +4 -0
  445. data/lib/couchbase/protostellar/error_handling.rb +2 -1
  446. data/lib/couchbase/protostellar/request.rb +1 -0
  447. data/lib/couchbase/protostellar/request_generator/kv.rb +16 -2
  448. data/lib/couchbase/protostellar/request_generator/search.rb +17 -1
  449. data/lib/couchbase/protostellar/retry/orchestrator.rb +3 -2
  450. data/lib/couchbase/protostellar/retry/strategies/fail_fast.rb +33 -0
  451. data/lib/couchbase/protostellar/retry/strategies.rb +1 -0
  452. data/lib/couchbase/search_options.rb +95 -6
  453. data/lib/couchbase/version.rb +1 -1
  454. metadata +92 -33
  455. data/ext/0001-fix-build-for-mingw-w64-ucrt-x86_64-toolchain.patch +0 -40
  456. data/ext/couchbase/core/columnar/agent.hxx +0 -71
  457. data/ext/couchbase/core/columnar/agent_config.hxx +0 -59
  458. data/ext/couchbase/core/columnar/background_bootstrap_listener.hxx +0 -42
  459. data/ext/couchbase/core/columnar/backoff_calculator.cxx +0 -62
  460. data/ext/couchbase/core/columnar/columnar_agent.cxx +0 -147
  461. data/ext/couchbase/core/columnar/columnar_agent_config.cxx +0 -48
  462. data/ext/couchbase/core/columnar/columnar_error.cxx +0 -51
  463. data/ext/couchbase/core/columnar/database_management_options.hxx +0 -56
  464. data/ext/couchbase/core/columnar/error.hxx +0 -51
  465. data/ext/couchbase/core/columnar/error_codes.cxx +0 -107
  466. data/ext/couchbase/core/columnar/error_codes.hxx +0 -75
  467. data/ext/couchbase/core/columnar/management_component.cxx +0 -300
  468. data/ext/couchbase/core/columnar/management_component.hxx +0 -56
  469. data/ext/couchbase/core/columnar/query_component.cxx +0 -523
  470. data/ext/couchbase/core/columnar/query_component.hxx +0 -57
  471. data/ext/couchbase/core/columnar/query_options.hxx +0 -59
  472. data/ext/couchbase/core/columnar/query_result.cxx +0 -130
  473. data/ext/couchbase/core/columnar/query_result.hxx +0 -78
  474. data/ext/couchbase/core/io/config_tracker.cxx +0 -687
  475. data/ext/couchbase/core/io/config_tracker.hxx +0 -123
@@ -0,0 +1,439 @@
1
+ /* -*- Mode: C++; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2
+ /*
3
+ * Copyright 2026-Present 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
+ #include "wait_until_ready.hxx"
19
+
20
+ #include "core/diagnostics.hxx"
21
+ #include "core/logger/logger.hxx"
22
+ #include "core/logger/redaction.hxx"
23
+ #include "core/topology/configuration.hxx"
24
+ #include "with_bucket_config_or_timeout.hxx"
25
+
26
+ #include <couchbase/error.hxx>
27
+ #include <couchbase/error_codes.hxx>
28
+
29
+ #include <asio/post.hpp>
30
+ #include <asio/steady_timer.hpp>
31
+
32
+ #include <algorithm>
33
+ #include <atomic>
34
+ #include <cstdint>
35
+ #include <memory>
36
+ #include <string_view>
37
+ #include <utility>
38
+
39
+ namespace couchbase::core::impl
40
+ {
41
+ namespace
42
+ {
43
+ // The SDK refreshes the cluster map on its own interval (2.5s by default), so a freshly created
44
+ // bucket's vbucket map will not change faster than that regardless; a short poll just keeps latency
45
+ // low once it does.
46
+ constexpr std::chrono::milliseconds poll_interval{ 100 };
47
+
48
+ auto
49
+ data_node_count(const topology::configuration& config) -> std::uint32_t
50
+ {
51
+ return static_cast<std::uint32_t>(
52
+ std::count_if(config.nodes.begin(), config.nodes.end(), [](const auto& node) {
53
+ return node.services_plain.key_value.has_value() || node.services_tls.key_value.has_value();
54
+ }));
55
+ }
56
+ } // namespace
57
+
58
+ // online : every requested service has at least one endpoint and ALL of them are ok.
59
+ // degraded : every requested service has at least one endpoint that is ok.
60
+ // When no services were requested we consider whatever the report contains; an empty report
61
+ // (nothing pinged back yet) is treated as not-ready.
62
+ auto
63
+ ping_predicate_satisfied(const diag::ping_result& report,
64
+ couchbase::cluster_state state,
65
+ const std::set<service_type>& requested) -> bool
66
+ {
67
+ const auto& endpoints = report.services;
68
+
69
+ std::set<service_type> services = requested;
70
+ if (services.empty()) {
71
+ for (const auto& [service, reports] : endpoints) {
72
+ services.insert(service);
73
+ }
74
+ if (services.empty()) {
75
+ return false;
76
+ }
77
+ }
78
+
79
+ for (const auto service : services) {
80
+ const auto it = endpoints.find(service);
81
+ if (it == endpoints.end() || it->second.empty()) {
82
+ return false;
83
+ }
84
+ const auto& reports = it->second;
85
+ const auto is_ok = [](const diag::endpoint_ping_info& r) {
86
+ return r.state == diag::ping_state::ok;
87
+ };
88
+ const bool ok = (state == couchbase::cluster_state::online)
89
+ ? std::all_of(reports.begin(), reports.end(), is_ok)
90
+ : std::any_of(reports.begin(), reports.end(), is_ok);
91
+ if (!ok) {
92
+ return false;
93
+ }
94
+ }
95
+ return true;
96
+ }
97
+
98
+ // One replica per data node besides the one holding the active, up to num_replicas. The server
99
+ // leaves a replica slot at -1 for as long as there is no node to place it on. A configuration
100
+ // listing no data node is not capped.
101
+ auto
102
+ placeable_replicas(const topology::configuration& config) -> std::uint32_t
103
+ {
104
+ const auto data_nodes = data_node_count(config);
105
+ if (data_nodes == 0) {
106
+ return config.num_replicas.value_or(0); // no node list to cap against
107
+ }
108
+ return std::min(config.num_replicas.value_or(0), data_nodes - 1);
109
+ }
110
+
111
+ auto
112
+ unplaceable_replicas(const topology::configuration& config) -> std::uint32_t
113
+ {
114
+ return config.num_replicas.value_or(0) - placeable_replicas(config);
115
+ }
116
+
117
+ void
118
+ warn_if_replicas_unplaceable(const topology::configuration& config,
119
+ std::string_view bucket_name,
120
+ std::atomic_bool& logged)
121
+ {
122
+ if (unplaceable_replicas(config) == 0 || logged.exchange(true)) {
123
+ return;
124
+ }
125
+ CB_LOG_WARNING("bucket \"{}\" is configured with {} replica(s), but {} data node(s) can hold "
126
+ "only {} replica(s)",
127
+ logger::metadata(bucket_name),
128
+ config.num_replicas.value_or(0),
129
+ data_node_count(config),
130
+ placeable_replicas(config));
131
+ }
132
+
133
+ // True once every vbucket has its active and the placeable replica copies assigned to a node. A
134
+ // freshly created bucket reports an empty/partial vbucket map (replica slots set to -1) until the
135
+ // server finishes placing replicas; durable (MAJORITY) writes are ambiguous until then.
136
+ auto
137
+ vbucket_map_ready(const topology::configuration& config) -> bool
138
+ {
139
+ if (!config.vbmap.has_value()) {
140
+ return false;
141
+ }
142
+ const auto& vbmap = config.vbmap.value();
143
+ if (vbmap.empty()) {
144
+ return false;
145
+ }
146
+ // active + replicas
147
+ const auto copies = static_cast<std::size_t>(placeable_replicas(config)) + 1;
148
+ for (const auto& chain : vbmap) {
149
+ if (chain.size() < copies) {
150
+ return false;
151
+ }
152
+ for (std::size_t i = 0; i < copies; ++i) {
153
+ if (chain[i] < 0) { // -1 => copy not yet assigned to a node
154
+ return false;
155
+ }
156
+ }
157
+ }
158
+ return true;
159
+ }
160
+
161
+ namespace
162
+ {
163
+ // Self-owning async poll loop. Exactly one continuation (config fetch, ping, or the poll timer) is
164
+ // in flight at a time, each holding a shared_ptr to keep the operation alive; every continuation
165
+ // returns to poll()/schedule_retry(), which re-check the deadline, so the whole operation is
166
+ // bounded by `timeout` without a separate deadline timer.
167
+ class wait_until_ready_operation : public std::enable_shared_from_this<wait_until_ready_operation>
168
+ {
169
+ public:
170
+ wait_until_ready_operation(core::cluster core,
171
+ std::optional<std::string> bucket_name,
172
+ std::chrono::milliseconds timeout,
173
+ couchbase::cluster_state desired_state,
174
+ std::set<service_type> services,
175
+ utils::movable_function<void(std::error_code)> handler)
176
+ : core_{ std::move(core) }
177
+ , bucket_name_{ std::move(bucket_name) }
178
+ , deadline_{ std::chrono::steady_clock::now() + timeout }
179
+ , desired_state_{ desired_state }
180
+ , services_{ std::move(services) }
181
+ , handler_{ std::move(handler) }
182
+ , timer_{ core_.io_context() }
183
+ {
184
+ }
185
+
186
+ wait_until_ready_operation(const wait_until_ready_operation&) = delete;
187
+ wait_until_ready_operation(wait_until_ready_operation&&) = delete;
188
+ auto operator=(const wait_until_ready_operation&) -> wait_until_ready_operation& = delete;
189
+ auto operator=(wait_until_ready_operation&&) -> wait_until_ready_operation& = delete;
190
+
191
+ ~wait_until_ready_operation()
192
+ {
193
+ // Fail closed. If the io_context is torn down while the wait is in flight, it drops every
194
+ // pending continuation (the poll timer, the config wait, the ping) without invoking their
195
+ // handlers; the last shared_ptr then expires here with the operation never completed. Deliver
196
+ // an error so a std::future caller never observes broken_promise and a callback caller never
197
+ // hangs. By the time the destructor runs the last reference is gone, so no other thread can be
198
+ // in complete() concurrently. The handler is user code, and a destructor must not let an
199
+ // exception escape.
200
+ if (!completed_) {
201
+ try {
202
+ handler_(errc::network::cluster_closed);
203
+ } catch (...) { // NOLINT(bugprone-empty-catch)
204
+ }
205
+ }
206
+ }
207
+
208
+ void run()
209
+ {
210
+ // close() completes a wait still in flight: the public cluster stops its io_context once
211
+ // close()'s handler runs, so no timer or posted continuation of this wait would run after
212
+ // that. A cluster already closed completes here, inline, as open_bucket() does.
213
+ const std::weak_ptr<wait_until_ready_operation> weak_for_close = shared_from_this();
214
+ if (!core_.add_close_hook(weak_for_close, [weak_for_close]() {
215
+ if (auto self = weak_for_close.lock(); self) {
216
+ self->complete(errc::network::cluster_closed);
217
+ }
218
+ })) {
219
+ return complete(errc::network::cluster_closed);
220
+ }
221
+ if (desired_state_ == couchbase::cluster_state::offline) {
222
+ // Reject on the io_context, not inline, so the handler runs on the io thread as on the poll
223
+ // path. A closed cluster is answered inline above, because its io_context may be stopped.
224
+ auto self = shared_from_this();
225
+ return asio::post(core_.io_context(), [self]() {
226
+ self->complete(errc::common::invalid_argument);
227
+ });
228
+ }
229
+ if (core_.is_protostellar()) {
230
+ // couchbase2 reaches every service through the gateway's gRPC channel, so a READY channel is
231
+ // both the online and the degraded state, whatever the bucket and services. There is no
232
+ // bucket to open and no session to ping. Posted for the same reason as the rejection above.
233
+ auto self = shared_from_this();
234
+ return asio::post(core_.io_context(), [self]() {
235
+ self->poll();
236
+ });
237
+ }
238
+ if (bucket_name_) {
239
+ // Opening the bucket is what makes its configuration (and therefore the vbucket map we poll)
240
+ // available on the core cluster. It is idempotent for an already-open bucket.
241
+ //
242
+ // Capture a weak_ptr, not self: open_bucket parks the callback in the core's deferred queue
243
+ // until the bucket opens, and the operation holds a strong core_ member -- a strong capture
244
+ // would form an op<->core reference cycle that survives cluster close, so the operation would
245
+ // never be destroyed and its handler never fires. anchor() keeps the operation alive on the
246
+ // io_context while the open is outstanding instead.
247
+ const auto& bucket_name = *bucket_name_;
248
+ anchor();
249
+ const std::weak_ptr<wait_until_ready_operation> weak = shared_from_this();
250
+ core_.open_bucket(bucket_name, [weak](std::error_code ec) {
251
+ auto self = weak.lock();
252
+ if (!self) {
253
+ return;
254
+ }
255
+ self->timer_.cancel();
256
+ // A closed cluster will never open the bucket; fail fast with the accurate error instead of
257
+ // polling until the deadline (ping() returns an empty report once stopped, so the poll loop
258
+ // would otherwise surface unambiguous_timeout). Other open errors (e.g. a missing bucket)
259
+ // are transient from the caller's point of view and still resolve through the poll loop.
260
+ if (ec == errc::network::cluster_closed) {
261
+ return self->complete(ec);
262
+ }
263
+ self->poll();
264
+ });
265
+ } else {
266
+ poll();
267
+ }
268
+ }
269
+
270
+ private:
271
+ auto remaining() const -> std::chrono::milliseconds
272
+ {
273
+ const auto left = std::chrono::duration_cast<std::chrono::milliseconds>(
274
+ deadline_ - std::chrono::steady_clock::now());
275
+ return std::max(left, std::chrono::milliseconds::zero());
276
+ }
277
+
278
+ // The close hook can run on another io thread than a continuation of this wait.
279
+ void complete(std::error_code ec)
280
+ {
281
+ if (completed_.exchange(true)) {
282
+ return;
283
+ }
284
+ // Moved out, so that the handler's captures are released when it returns: a completed wait can
285
+ // stay referenced by a continuation queued on a stopped io_context.
286
+ auto handler = std::move(handler_);
287
+ handler(ec);
288
+ }
289
+
290
+ void schedule_retry()
291
+ {
292
+ if (std::chrono::steady_clock::now() >= deadline_) {
293
+ return complete(errc::common::unambiguous_timeout);
294
+ }
295
+ // Never sleep past the deadline: when less than a full interval remains, a fixed poll_interval
296
+ // wait would overshoot the caller's timeout by up to one interval before poll() notices.
297
+ timer_.expires_after(std::min(poll_interval, remaining()));
298
+ auto self = shared_from_this();
299
+ timer_.async_wait([self](std::error_code timer_ec) {
300
+ if (timer_ec) {
301
+ return; // cancelled / io_context shutting down
302
+ }
303
+ self->poll();
304
+ });
305
+ }
306
+
307
+ // Keep the operation alive on the io_context (via timer_, holding a strong self) while a core
308
+ // call that captures only a weak_ptr is outstanding -- open_bucket and ping park their callbacks
309
+ // in the core's queues, so anchoring here rather than through those callbacks is what avoids the
310
+ // op<->core reference cycle. The anchor doubles as the deadline guard for the outstanding call:
311
+ // its continuation cancels the anchor, and if the deadline is reached first the wait completes
312
+ // with a timeout instead of stalling until the call happens to return.
313
+ void anchor()
314
+ {
315
+ timer_.expires_after(remaining());
316
+ timer_.async_wait([self = shared_from_this()](std::error_code timer_ec) {
317
+ if (timer_ec) {
318
+ return; // cancelled by the core call's continuation, or io_context shutting down
319
+ }
320
+ self->complete(errc::common::unambiguous_timeout);
321
+ });
322
+ }
323
+
324
+ void poll()
325
+ {
326
+ if (completed_) {
327
+ return;
328
+ }
329
+ if (remaining() == std::chrono::milliseconds::zero()) {
330
+ return complete(errc::common::unambiguous_timeout);
331
+ }
332
+ if (core_.is_protostellar()) {
333
+ const auto [ec, ready] = core_.protostellar_channel_ready();
334
+ if (ec) {
335
+ return complete(ec);
336
+ }
337
+ if (ready) {
338
+ return complete({});
339
+ }
340
+ return schedule_retry();
341
+ }
342
+ if (bucket_name_ && desired_state_ == couchbase::cluster_state::online) {
343
+ // KV readiness (replica placement) first -- it is the condition durable writes need and the
344
+ // one a fresh bucket fails; then confirm service health with a ping. Only gate on it for the
345
+ // `online` target: `degraded` deliberately tolerates partial availability, so requiring full
346
+ // replica placement there would contradict the state's meaning and could never be satisfied
347
+ // by a genuinely degraded bucket.
348
+ const auto& bucket_name = *bucket_name_;
349
+ auto self = shared_from_this();
350
+ // Bound each waiter to one poll interval, not the whole remaining budget: when a config never
351
+ // arrives, a per-poll waiter armed for `remaining()` would leave one pending config callback
352
+ // and steady_timer stacked up for every 100ms tick until the deadline. A short inner timeout
353
+ // clears each waiter before the next poll (a cached config still fires its callback promptly,
354
+ // so this adds no latency to the common ready path). The overall deadline is still enforced
355
+ // by remaining() at the top of poll()/schedule_retry().
356
+ with_bucket_config_or_timeout<bool>(
357
+ core_,
358
+ bucket_name,
359
+ std::min(remaining(), poll_interval),
360
+ [self](couchbase::error err, bool ready) {
361
+ // A closed cluster is terminal: complete with the accurate error rather than retrying
362
+ // until the deadline turns it into unambiguous_timeout. Any other error (including the
363
+ // helper's own inner unambiguous_timeout when no config has arrived yet) just means "not
364
+ // ready yet" and is retried.
365
+ if (err.ec() == errc::network::cluster_closed) {
366
+ return self->complete(err.ec());
367
+ }
368
+ if (err.ec() || !ready) {
369
+ return self->schedule_retry();
370
+ }
371
+ self->do_ping();
372
+ },
373
+ // Weak: the helper parks this resolver in the bucket's deferred queue, which must not pin
374
+ // the operation and its handler.
375
+ [weak = weak_from_this()](const std::shared_ptr<topology::configuration>& config)
376
+ -> std::pair<std::error_code, bool> {
377
+ auto self = weak.lock();
378
+ if (!self || !config) {
379
+ return { {}, false };
380
+ }
381
+ warn_if_replicas_unplaceable(
382
+ *config, self->bucket_name_.value_or(""), self->unplaceable_replicas_logged_);
383
+ return { {}, vbucket_map_ready(*config) };
384
+ });
385
+ } else {
386
+ do_ping();
387
+ }
388
+ }
389
+
390
+ void do_ping()
391
+ {
392
+ // Weak capture + anchor, for the same reason as open_bucket: core_.ping parks the callback in
393
+ // the core, so a strong self-capture would cycle with the core_ member and leak on close.
394
+ anchor();
395
+ const std::weak_ptr<wait_until_ready_operation> weak = shared_from_this();
396
+ core_.ping(
397
+ std::nullopt, bucket_name_, services_, remaining(), [weak](const diag::ping_result& report) {
398
+ auto self = weak.lock();
399
+ if (!self) {
400
+ return;
401
+ }
402
+ self->timer_.cancel();
403
+ if (ping_predicate_satisfied(report, self->desired_state_, self->services_)) {
404
+ return self->complete({});
405
+ }
406
+ self->schedule_retry();
407
+ });
408
+ }
409
+
410
+ core::cluster core_;
411
+ std::optional<std::string> bucket_name_;
412
+ std::chrono::steady_clock::time_point deadline_;
413
+ couchbase::cluster_state desired_state_;
414
+ std::set<service_type> services_;
415
+ utils::movable_function<void(std::error_code)> handler_;
416
+ asio::steady_timer timer_;
417
+ std::atomic_bool completed_{ false };
418
+ // Every poll sees the same configuration until a node is added, so the warning is logged once.
419
+ std::atomic_bool unplaceable_replicas_logged_{ false };
420
+ };
421
+ } // namespace
422
+
423
+ void
424
+ wait_until_ready(core::cluster core,
425
+ std::optional<std::string> bucket_name,
426
+ std::chrono::milliseconds timeout,
427
+ couchbase::cluster_state desired_state,
428
+ std::set<service_type> services,
429
+ utils::movable_function<void(std::error_code)> handler)
430
+ {
431
+ auto op = std::make_shared<wait_until_ready_operation>(std::move(core),
432
+ std::move(bucket_name),
433
+ timeout,
434
+ desired_state,
435
+ std::move(services),
436
+ std::move(handler));
437
+ op->run();
438
+ }
439
+ } // namespace couchbase::core::impl
@@ -0,0 +1,110 @@
1
+ /* -*- Mode: C++; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2
+ /*
3
+ * Copyright 2026-Present 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 "core/cluster.hxx"
21
+ #include "core/diagnostics.hxx"
22
+ #include "core/service_type.hxx"
23
+ #include "core/topology/configuration.hxx"
24
+ #include "core/utils/movable_function.hxx"
25
+
26
+ #include <couchbase/cluster_state.hxx>
27
+
28
+ #include <atomic>
29
+ #include <chrono>
30
+ #include <cstdint>
31
+ #include <optional>
32
+ #include <set>
33
+ #include <string>
34
+ #include <string_view>
35
+ #include <system_error>
36
+
37
+ namespace couchbase::core::impl
38
+ {
39
+ /**
40
+ * Returns whether @p report satisfies @p state for the @p requested services.
41
+ *
42
+ * online : every requested service has at least one endpoint and ALL of them are ok.
43
+ * degraded : every requested service has at least one endpoint that is ok.
44
+ *
45
+ * When @p requested is empty, whichever services the report contains are considered; an empty
46
+ * report (nothing pinged back yet) is treated as not-ready. Exposed for unit testing.
47
+ */
48
+ auto
49
+ ping_predicate_satisfied(const diag::ping_result& report,
50
+ couchbase::cluster_state state,
51
+ const std::set<service_type>& requested) -> bool;
52
+
53
+ /**
54
+ * Returns the number of replica copies of each vbucket the data nodes can hold: num_replicas,
55
+ * capped at one per data node besides the one holding the active. The server leaves the remaining
56
+ * replica slots at -1 for as long as no node is added. A configuration listing no data node is not
57
+ * capped. Exposed for unit testing.
58
+ */
59
+ auto
60
+ placeable_replicas(const topology::configuration& config) -> std::uint32_t;
61
+
62
+ /**
63
+ * Returns how many of the configured replicas the data nodes cannot hold. Exposed for unit testing.
64
+ */
65
+ auto
66
+ unplaceable_replicas(const topology::configuration& config) -> std::uint32_t;
67
+
68
+ /**
69
+ * Logs a warning naming @p bucket_name when unplaceable_replicas() is non-zero and @p logged is
70
+ * still false, then sets @p logged. Exposed for unit testing.
71
+ */
72
+ void
73
+ warn_if_replicas_unplaceable(const topology::configuration& config,
74
+ std::string_view bucket_name,
75
+ std::atomic_bool& logged);
76
+
77
+ /**
78
+ * Returns true once every vbucket has its active and placeable_replicas() replica copies assigned
79
+ * to a node. A freshly created bucket reports an empty/partial vbucket map (replica slots set to
80
+ * -1) until the server finishes placing replicas; durable (MAJORITY) writes are ambiguous until
81
+ * then. Exposed for unit testing.
82
+ */
83
+ auto
84
+ vbucket_map_ready(const topology::configuration& config) -> bool;
85
+
86
+ /**
87
+ * Polls the cluster (or, when @p bucket_name is set, a bucket) until it reaches @p desired_state or
88
+ * @p timeout elapses, then invokes @p handler exactly once with the resulting error code (empty on
89
+ * success, @c errc::common::unambiguous_timeout on timeout, @c errc::common::invalid_argument when
90
+ * @p desired_state is @c cluster_state::offline, @c errc::network::cluster_closed when the
91
+ * io_context is torn down while the wait is in flight).
92
+ *
93
+ * Readiness is assessed with @c cluster::ping(). When @p desired_state is @c cluster_state::online
94
+ * the bucket variant additionally waits until every vbucket has its active and
95
+ * placeable_replicas() replica copies assigned -- the readiness that durable writes require and
96
+ * that a freshly created bucket does not satisfy immediately. Replicas beyond what the data nodes
97
+ * can hold are not awaited, and warn_if_replicas_unplaceable() logs them once per wait. The @c
98
+ * degraded target is ping-only, since it deliberately tolerates partial availability.
99
+ *
100
+ * The operation is fully asynchronous: it owns itself for its lifetime and drives the poll cadence
101
+ * with an asio timer on the cluster's io_context; it does not block a thread.
102
+ */
103
+ void
104
+ wait_until_ready(core::cluster core,
105
+ std::optional<std::string> bucket_name,
106
+ std::chrono::milliseconds timeout,
107
+ couchbase::cluster_state desired_state,
108
+ std::set<service_type> services,
109
+ utils::movable_function<void(std::error_code)> handler);
110
+ } // namespace couchbase::core::impl