couchbase 3.8.2 → 3.9.1

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 (473) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  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 +121 -55
  7. data/ext/cache/mozilla-ca-bundle.sha256 +1 -1
  8. data/ext/couchbase/CMakeLists.txt +136 -23
  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 +82 -7
  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 +1551 -397
  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 +229 -42
  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 +1 -2
  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 +309 -301
  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 +151 -21
  131. data/ext/couchbase/core/io/streams.hxx +48 -1
  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/change_password.cxx +2 -0
  183. data/ext/couchbase/core/operations/management/collection_drop.cxx +5 -1
  184. data/ext/couchbase/core/operations/management/error_utils.cxx +60 -6
  185. data/ext/couchbase/core/operations/management/error_utils.hxx +29 -1
  186. data/ext/couchbase/core/operations/management/eventing_deploy_function.cxx +9 -3
  187. data/ext/couchbase/core/operations/management/eventing_drop_function.cxx +8 -3
  188. data/ext/couchbase/core/operations/management/eventing_get_all_functions.cxx +4 -2
  189. data/ext/couchbase/core/operations/management/eventing_get_function.cxx +5 -3
  190. data/ext/couchbase/core/operations/management/eventing_get_status.cxx +4 -2
  191. data/ext/couchbase/core/operations/management/eventing_pause_function.cxx +9 -3
  192. data/ext/couchbase/core/operations/management/eventing_resume_function.cxx +9 -3
  193. data/ext/couchbase/core/operations/management/eventing_undeploy_function.cxx +9 -3
  194. data/ext/couchbase/core/operations/management/eventing_upsert_function.cxx +9 -3
  195. data/ext/couchbase/core/operations/management/group_drop.cxx +3 -1
  196. data/ext/couchbase/core/operations/management/group_get.cxx +3 -1
  197. data/ext/couchbase/core/operations/management/group_upsert.cxx +11 -8
  198. data/ext/couchbase/core/operations/management/query_index_build.cxx +5 -2
  199. data/ext/couchbase/core/operations/management/query_index_create.cxx +9 -3
  200. data/ext/couchbase/core/operations/management/query_index_drop.cxx +11 -4
  201. data/ext/couchbase/core/operations/management/query_index_get_all.cxx +1 -1
  202. data/ext/couchbase/core/operations/management/query_index_get_all_deferred.cxx +1 -1
  203. data/ext/couchbase/core/operations/management/scope_drop.cxx +5 -1
  204. data/ext/couchbase/core/operations/management/scope_get_all.cxx +6 -1
  205. data/ext/couchbase/core/operations/management/search_get_stats.cxx +10 -0
  206. data/ext/couchbase/core/operations/management/search_index_analyze_document.cxx +3 -2
  207. data/ext/couchbase/core/operations/management/search_index_control_ingest.cxx +4 -3
  208. data/ext/couchbase/core/operations/management/search_index_control_plan_freeze.cxx +4 -3
  209. data/ext/couchbase/core/operations/management/search_index_control_query.cxx +4 -3
  210. data/ext/couchbase/core/operations/management/search_index_drop.cxx +2 -2
  211. data/ext/couchbase/core/operations/management/search_index_get.cxx +2 -2
  212. data/ext/couchbase/core/operations/management/search_index_get_documents_count.cxx +3 -2
  213. data/ext/couchbase/core/operations/management/search_index_get_stats.cxx +3 -1
  214. data/ext/couchbase/core/operations/management/search_index_upsert.cxx +2 -2
  215. data/ext/couchbase/core/operations/management/user_drop.cxx +3 -1
  216. data/ext/couchbase/core/operations/management/user_get.cxx +3 -1
  217. data/ext/couchbase/core/operations/management/user_upsert.cxx +12 -9
  218. data/ext/couchbase/core/operations/management/view_index_drop.cxx +1 -1
  219. data/ext/couchbase/core/operations/management/view_index_get.cxx +1 -1
  220. data/ext/couchbase/core/operations/management/view_index_upsert.cxx +1 -1
  221. data/ext/couchbase/core/operations/operation_traits.hxx +18 -0
  222. data/ext/couchbase/core/operations/query_response_parsing.cxx +211 -0
  223. data/ext/couchbase/core/operations/query_response_parsing.hxx +60 -0
  224. data/ext/couchbase/core/operations_fwd.hxx +3 -3
  225. data/ext/couchbase/core/origin.cxx +54 -26
  226. data/ext/couchbase/core/origin.hxx +12 -0
  227. data/ext/couchbase/core/orphan_reporter.cxx +2 -1
  228. data/ext/couchbase/core/platform/random.cc +7 -7
  229. data/ext/couchbase/core/protocol/client_request.cxx +2 -2
  230. data/ext/couchbase/core/protocol/client_request.hxx +38 -21
  231. data/ext/couchbase/core/protocol/client_response.cxx +11 -3
  232. data/ext/couchbase/core/protocol/client_response.hxx +27 -6
  233. data/ext/couchbase/core/protocol/cmd_append.cxx +2 -2
  234. data/ext/couchbase/core/protocol/cmd_cluster_map_change_notification.cxx +2 -2
  235. data/ext/couchbase/core/protocol/cmd_decrement.cxx +6 -6
  236. data/ext/couchbase/core/protocol/cmd_get.cxx +1 -1
  237. data/ext/couchbase/core/protocol/cmd_get_and_lock.cxx +2 -2
  238. data/ext/couchbase/core/protocol/cmd_get_and_touch.cxx +2 -2
  239. data/ext/couchbase/core/protocol/cmd_get_cluster_config.cxx +6 -4
  240. data/ext/couchbase/core/protocol/cmd_get_collection_id.cxx +2 -2
  241. data/ext/couchbase/core/protocol/cmd_get_error_map.cxx +1 -1
  242. data/ext/couchbase/core/protocol/cmd_get_meta.cxx +4 -4
  243. data/ext/couchbase/core/protocol/cmd_get_replica.cxx +1 -1
  244. data/ext/couchbase/core/protocol/cmd_hello.cxx +1 -1
  245. data/ext/couchbase/core/protocol/cmd_increment.cxx +6 -6
  246. data/ext/couchbase/core/protocol/cmd_insert.cxx +4 -4
  247. data/ext/couchbase/core/protocol/cmd_lookup_in.cxx +4 -3
  248. data/ext/couchbase/core/protocol/cmd_lookup_in_replica.cxx +4 -3
  249. data/ext/couchbase/core/protocol/cmd_mutate_in.cxx +10 -9
  250. data/ext/couchbase/core/protocol/cmd_mutate_in.hxx +10 -10
  251. data/ext/couchbase/core/protocol/cmd_observe_seqno.cxx +7 -7
  252. data/ext/couchbase/core/protocol/cmd_prepend.cxx +2 -2
  253. data/ext/couchbase/core/protocol/cmd_remove.cxx +2 -2
  254. data/ext/couchbase/core/protocol/cmd_replace.cxx +4 -4
  255. data/ext/couchbase/core/protocol/cmd_touch.cxx +1 -1
  256. data/ext/couchbase/core/protocol/cmd_upsert.cxx +4 -4
  257. data/ext/couchbase/core/protocol/datatype.hxx +118 -1
  258. data/ext/couchbase/core/protocol/frame_info_utils.cxx +1 -1
  259. data/ext/couchbase/core/protocol/magic.hxx +4 -0
  260. data/ext/couchbase/core/protocol/magic_fmt.hxx +3 -0
  261. data/ext/couchbase/core/protocol/server_request.hxx +3 -1
  262. data/ext/couchbase/core/protocol/status.cxx +1 -1
  263. data/ext/couchbase/core/protostellar/analytics_converter.hxx +171 -0
  264. data/ext/couchbase/core/protostellar/bucket_admin_converter.hxx +301 -0
  265. data/ext/couchbase/core/protostellar/collection_admin_converter.hxx +92 -0
  266. data/ext/couchbase/core/protostellar/component.cxx +2445 -0
  267. data/ext/couchbase/core/protostellar/component.hxx +391 -0
  268. data/ext/couchbase/core/{columnar/bootstrap_notification_subscriber.hxx → protostellar/compression_settings.hxx} +11 -10
  269. data/ext/couchbase/core/protostellar/credentials.cxx +233 -0
  270. data/ext/couchbase/core/protostellar/credentials.hxx +88 -0
  271. data/ext/couchbase/core/protostellar/dispatcher.cxx +57 -0
  272. data/ext/couchbase/core/protostellar/dispatcher.hxx +482 -0
  273. data/ext/couchbase/core/protostellar/error_utils.cxx +293 -0
  274. data/ext/couchbase/core/protostellar/error_utils.hxx +97 -0
  275. data/ext/couchbase/core/protostellar/json_payload.hxx +49 -0
  276. data/ext/couchbase/core/protostellar/kv_converter.cxx +70 -0
  277. data/ext/couchbase/core/protostellar/kv_converter.hxx +600 -0
  278. data/ext/couchbase/core/protostellar/query_converter.hxx +265 -0
  279. data/ext/couchbase/core/protostellar/query_index_admin_converter.hxx +291 -0
  280. data/ext/couchbase/core/protostellar/query_proto.hxx +73 -0
  281. data/ext/couchbase/core/protostellar/search_converter.hxx +244 -0
  282. data/ext/couchbase/core/protostellar/search_index_admin_converter.hxx +156 -0
  283. data/ext/couchbase/core/protostellar/view_converter.hxx +158 -0
  284. data/ext/couchbase/core/query_stream.cxx +447 -0
  285. data/ext/couchbase/core/query_stream.hxx +141 -0
  286. data/ext/couchbase/core/query_stream_component.cxx +243 -0
  287. data/ext/couchbase/core/query_stream_component.hxx +68 -0
  288. data/ext/couchbase/core/range_scan_load_balancer.cxx +15 -8
  289. data/ext/couchbase/core/range_scan_orchestrator.cxx +15 -3
  290. data/ext/couchbase/core/response_handler.hxx +41 -4
  291. data/ext/couchbase/core/row_streamer.cxx +332 -46
  292. data/ext/couchbase/core/row_streamer.hxx +31 -1
  293. data/ext/couchbase/core/row_streamer_options.hxx +55 -0
  294. data/ext/couchbase/core/sasl/client.h +3 -0
  295. data/ext/couchbase/core/search_scoring.hxx +66 -0
  296. data/ext/couchbase/core/stream_error_details.hxx +69 -0
  297. data/ext/couchbase/core/timeout_defaults.hxx +0 -1
  298. data/ext/couchbase/core/topology/capabilities.hxx +6 -0
  299. data/ext/couchbase/core/topology/capabilities_fmt.hxx +3 -0
  300. data/ext/couchbase/core/topology/configuration.cxx +21 -6
  301. data/ext/couchbase/core/topology/configuration.hxx +29 -8
  302. data/ext/couchbase/core/topology/configuration_json.hxx +3 -0
  303. data/ext/couchbase/core/topology/error_map_json.hxx +3 -1
  304. data/ext/couchbase/core/tracing/threshold_logging_tracer.cxx +70 -4
  305. data/ext/couchbase/core/tracing/threshold_logging_tracer.hxx +10 -0
  306. data/ext/couchbase/core/transactions/active_transaction_record.cxx +0 -34
  307. data/ext/couchbase/core/transactions/attempt_context_impl.cxx +108 -63
  308. data/ext/couchbase/core/transactions/attempt_context_impl.hxx +19 -2
  309. data/ext/couchbase/core/transactions/error_list.hxx +2 -2
  310. data/ext/couchbase/core/transactions/get_multi_fetch.hxx +160 -0
  311. data/ext/couchbase/core/transactions/get_multi_orchestrator.cxx +326 -125
  312. data/ext/couchbase/core/transactions/get_multi_transaction_id.hxx +62 -0
  313. data/ext/couchbase/core/transactions/internal/atr_entry.hxx +22 -2
  314. data/ext/couchbase/core/transactions/internal/transaction_context.hxx +2 -2
  315. data/ext/couchbase/core/transactions/internal/transactions_cleanup.hxx +23 -1
  316. data/ext/couchbase/core/transactions/internal/utils.hxx +50 -14
  317. data/ext/couchbase/core/transactions/staged_mutation.cxx +23 -18
  318. data/ext/couchbase/core/transactions/staged_mutation.hxx +15 -4
  319. data/ext/couchbase/core/transactions/transaction_context.cxx +55 -40
  320. data/ext/couchbase/core/transactions/transaction_get_result.hxx +16 -0
  321. data/ext/couchbase/core/transactions/transactions.cxx +3 -55
  322. data/ext/couchbase/core/transactions/transactions_cleanup.cxx +165 -72
  323. data/ext/couchbase/core/transactions/transactions_config.cxx +0 -11
  324. data/ext/couchbase/core/transactions/utils.cxx +10 -0
  325. data/ext/couchbase/core/transactions/waitable_op_list.hxx +2 -2
  326. data/ext/couchbase/core/transactions.hxx +9 -9
  327. data/ext/couchbase/core/utils/byteswap.hxx +166 -9
  328. data/ext/couchbase/core/utils/connection_string.cxx +50 -85
  329. data/ext/couchbase/core/utils/connection_string.hxx +15 -0
  330. data/ext/couchbase/core/{columnar/backoff_calculator.hxx → utils/deadline.hxx} +13 -13
  331. data/ext/couchbase/core/utils/duration_parser.cxx +1 -1
  332. data/ext/couchbase/core/utils/json_streaming_lexer.cxx +48 -5
  333. data/ext/couchbase/core/utils/json_streaming_lexer.hxx +4 -1
  334. data/ext/couchbase/core/utils/movable_function.hxx +301 -52
  335. data/ext/couchbase/core/utils/name_codec.hxx +154 -11
  336. data/ext/couchbase/core/{columnar/retry_info.hxx → utils/process_id.hxx} +18 -13
  337. data/ext/couchbase/core/utils/url_codec.cxx +12 -51
  338. data/ext/couchbase/core/websocket_codec.cxx +13 -37
  339. data/ext/couchbase/couchbase/analytics_options.hxx +21 -10
  340. data/ext/couchbase/couchbase/analytics_row.hxx +105 -0
  341. data/ext/couchbase/couchbase/analytics_stream_result.hxx +266 -0
  342. data/ext/couchbase/couchbase/boolean_query.hxx +3 -3
  343. data/ext/couchbase/couchbase/bucket.hxx +57 -0
  344. data/ext/couchbase/couchbase/cluster.hxx +183 -5
  345. data/ext/couchbase/{core/columnar/security_options.hxx → couchbase/cluster_state.hxx} +18 -14
  346. data/ext/couchbase/couchbase/codec/lenient_json_transcoder.hxx +69 -0
  347. data/ext/couchbase/couchbase/collection.hxx +234 -14
  348. data/ext/couchbase/couchbase/common_durability_options.hxx +18 -3
  349. data/ext/couchbase/couchbase/conjunction_query.hxx +1 -1
  350. data/ext/couchbase/couchbase/create_dataset_analytics_options.hxx +6 -1
  351. data/ext/couchbase/couchbase/date_range_query.hxx +2 -2
  352. data/ext/couchbase/couchbase/decrement_options.hxx +1 -1
  353. data/ext/couchbase/couchbase/disjunction_query.hxx +1 -1
  354. data/ext/couchbase/couchbase/error.hxx +32 -0
  355. data/ext/couchbase/couchbase/error_codes.hxx +33 -1
  356. data/ext/couchbase/couchbase/geo_bounding_box_query.hxx +1 -1
  357. data/ext/couchbase/couchbase/geo_distance_query.hxx +1 -1
  358. data/ext/couchbase/couchbase/geo_polygon_query.hxx +1 -1
  359. data/ext/couchbase/couchbase/get_replica_options.hxx +68 -0
  360. data/ext/couchbase/couchbase/get_replica_result.hxx +5 -3
  361. data/ext/couchbase/couchbase/get_replica_strategy.hxx +161 -0
  362. data/ext/couchbase/couchbase/get_result.hxx +1 -0
  363. data/ext/couchbase/couchbase/increment_options.hxx +1 -1
  364. data/ext/couchbase/couchbase/insert_options.hxx +1 -1
  365. data/ext/couchbase/couchbase/match_phrase_query.hxx +1 -1
  366. data/ext/couchbase/couchbase/match_query.hxx +1 -1
  367. data/ext/couchbase/couchbase/mutate_in_options.hxx +3 -3
  368. data/ext/couchbase/couchbase/network_options.hxx +1 -1
  369. data/ext/couchbase/couchbase/node_id.hxx +42 -14
  370. data/ext/couchbase/couchbase/node_id_for_options.hxx +7 -1
  371. data/ext/couchbase/couchbase/node_ids_options.hxx +8 -2
  372. data/ext/couchbase/couchbase/numeric_range_query.hxx +1 -1
  373. data/ext/couchbase/couchbase/phrase_query.hxx +1 -1
  374. data/ext/couchbase/couchbase/prefix_query.hxx +1 -1
  375. data/ext/couchbase/couchbase/query_options.hxx +23 -12
  376. data/ext/couchbase/couchbase/query_row.hxx +111 -0
  377. data/ext/couchbase/couchbase/query_stream_result.hxx +302 -0
  378. data/ext/couchbase/couchbase/query_string_query.hxx +3 -3
  379. data/ext/couchbase/couchbase/regexp_query.hxx +1 -1
  380. data/ext/couchbase/couchbase/replace_options.hxx +3 -3
  381. data/ext/couchbase/couchbase/result.hxx +14 -3
  382. data/ext/couchbase/couchbase/scan_result_item.hxx +1 -0
  383. data/ext/couchbase/couchbase/scope.hxx +85 -2
  384. data/ext/couchbase/couchbase/search_options.hxx +58 -8
  385. data/ext/couchbase/couchbase/search_scoring.hxx +94 -0
  386. data/ext/couchbase/couchbase/search_scoring_none.hxx +51 -0
  387. data/ext/couchbase/couchbase/search_scoring_reciprocal_rank_fusion.hxx +88 -0
  388. data/ext/couchbase/couchbase/search_scoring_relative_score_fusion.hxx +70 -0
  389. data/ext/couchbase/couchbase/term_range_query.hxx +1 -1
  390. data/ext/couchbase/couchbase/tracing/request_span.hxx +46 -0
  391. data/ext/couchbase/couchbase/transactions/transaction_get_multi_replicas_from_preferred_server_group_result.hxx +23 -21
  392. data/ext/couchbase/couchbase/transactions/transaction_get_multi_result.hxx +24 -19
  393. data/ext/couchbase/couchbase/transactions/transaction_get_result.hxx +17 -3
  394. data/ext/couchbase/couchbase/transactions/transactions_config.hxx +4 -2
  395. data/ext/couchbase/couchbase/transactions.hxx +27 -18
  396. data/ext/couchbase/couchbase/upsert_options.hxx +3 -3
  397. data/ext/couchbase/couchbase/vector_query.hxx +1 -1
  398. data/ext/couchbase/couchbase/wait_until_ready_options.hxx +111 -0
  399. data/ext/couchbase/couchbase/wildcard_query.hxx +1 -1
  400. data/ext/extconf.rb +15 -10
  401. data/ext/rcb_analytics.cxx +82 -102
  402. data/ext/rcb_backend.cxx +42 -36
  403. data/ext/rcb_backend.hxx +2 -0
  404. data/ext/rcb_buckets.cxx +47 -59
  405. data/ext/rcb_collections.cxx +42 -52
  406. data/ext/rcb_crud.cxx +345 -224
  407. data/ext/rcb_diagnostics.cxx +117 -27
  408. data/ext/rcb_exceptions.cxx +152 -99
  409. data/ext/rcb_exceptions.hxx +25 -0
  410. data/ext/rcb_extras.cxx +18 -19
  411. data/ext/rcb_hdr_histogram.cxx +19 -4
  412. data/ext/rcb_logger.cxx +202 -27
  413. data/ext/rcb_logger.hxx +12 -0
  414. data/ext/rcb_multi.cxx +40 -40
  415. data/ext/rcb_observability.cxx +7 -5
  416. data/ext/rcb_query.cxx +99 -142
  417. data/ext/rcb_range_scan.cxx +41 -35
  418. data/ext/rcb_search.cxx +98 -99
  419. data/ext/rcb_users.cxx +36 -56
  420. data/ext/rcb_utils.cxx +100 -37
  421. data/ext/rcb_utils.hxx +287 -33
  422. data/ext/rcb_version.cxx +1 -1
  423. data/ext/rcb_views.cxx +55 -51
  424. data/lib/active_support/cache/couchbase_store.rb +77 -12
  425. data/lib/couchbase/bucket.rb +20 -0
  426. data/lib/couchbase/cluster.rb +17 -0
  427. data/lib/couchbase/collection.rb +36 -1
  428. data/lib/couchbase/errors.rb +18 -0
  429. data/lib/couchbase/fork_hooks.rb +16 -7
  430. data/lib/couchbase/get_replica_strategy.rb +89 -0
  431. data/lib/couchbase/logger.rb +1 -1
  432. data/lib/couchbase/management/analytics_index_manager.rb +15 -15
  433. data/lib/couchbase/management/bucket_manager.rb +6 -6
  434. data/lib/couchbase/management/collection_manager.rb +6 -6
  435. data/lib/couchbase/management/query_index_manager.rb +7 -7
  436. data/lib/couchbase/management/search_index_manager.rb +13 -13
  437. data/lib/couchbase/management/user_manager.rb +10 -10
  438. data/lib/couchbase/management/view_index_manager.rb +5 -5
  439. data/lib/couchbase/options.rb +174 -42
  440. data/lib/couchbase/protostellar/bucket.rb +4 -0
  441. data/lib/couchbase/protostellar/cluster.rb +4 -0
  442. data/lib/couchbase/protostellar/collection.rb +4 -0
  443. data/lib/couchbase/protostellar/error_handling.rb +2 -1
  444. data/lib/couchbase/protostellar/request.rb +1 -0
  445. data/lib/couchbase/protostellar/request_generator/kv.rb +16 -2
  446. data/lib/couchbase/protostellar/request_generator/search.rb +17 -1
  447. data/lib/couchbase/protostellar/retry/orchestrator.rb +3 -2
  448. data/lib/couchbase/protostellar/retry/strategies/fail_fast.rb +33 -0
  449. data/lib/couchbase/protostellar/retry/strategies.rb +1 -0
  450. data/lib/couchbase/search_options.rb +95 -6
  451. data/lib/couchbase/version.rb +1 -1
  452. metadata +91 -33
  453. data/ext/0001-fix-build-for-mingw-w64-ucrt-x86_64-toolchain.patch +0 -40
  454. data/ext/couchbase/core/columnar/agent.hxx +0 -71
  455. data/ext/couchbase/core/columnar/agent_config.hxx +0 -59
  456. data/ext/couchbase/core/columnar/background_bootstrap_listener.hxx +0 -42
  457. data/ext/couchbase/core/columnar/backoff_calculator.cxx +0 -62
  458. data/ext/couchbase/core/columnar/columnar_agent.cxx +0 -147
  459. data/ext/couchbase/core/columnar/columnar_agent_config.cxx +0 -48
  460. data/ext/couchbase/core/columnar/columnar_error.cxx +0 -51
  461. data/ext/couchbase/core/columnar/database_management_options.hxx +0 -56
  462. data/ext/couchbase/core/columnar/error.hxx +0 -51
  463. data/ext/couchbase/core/columnar/error_codes.cxx +0 -107
  464. data/ext/couchbase/core/columnar/error_codes.hxx +0 -75
  465. data/ext/couchbase/core/columnar/management_component.cxx +0 -300
  466. data/ext/couchbase/core/columnar/management_component.hxx +0 -56
  467. data/ext/couchbase/core/columnar/query_component.cxx +0 -523
  468. data/ext/couchbase/core/columnar/query_component.hxx +0 -57
  469. data/ext/couchbase/core/columnar/query_options.hxx +0 -59
  470. data/ext/couchbase/core/columnar/query_result.cxx +0 -130
  471. data/ext/couchbase/core/columnar/query_result.hxx +0 -78
  472. data/ext/couchbase/core/io/config_tracker.cxx +0 -687
  473. data/ext/couchbase/core/io/config_tracker.hxx +0 -123
@@ -0,0 +1,88 @@
1
+ /* -*- Mode: C++; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2
+ /*
3
+ * Copyright 2026. 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 <grpcpp/grpcpp.h>
21
+
22
+ #include <memory>
23
+ #include <string>
24
+
25
+ namespace couchbase::core
26
+ {
27
+ struct cluster_options;
28
+ struct cluster_credentials;
29
+ } // namespace couchbase::core
30
+
31
+ namespace couchbase::core::protostellar
32
+ {
33
+
34
+ // Translate the SDK's TLS options and credentials into gRPC's SSL material: root certs come from an
35
+ // inline PEM value, else a CA file, else the SDK's own default set -- the Capella root plus the
36
+ // bundled Mozilla roots unless those are disabled. The client chain/key are set together for
37
+ // certificate (mTLS) authentication.
38
+ //
39
+ // The default set matches what MCBP's configure_tls_context() contributes from material this SDK
40
+ // ships, as RFC 77 (Bootstrapping -> Security) requires. It does not additionally fold in the
41
+ // platform trust store that MCBP reaches through set_default_verify_paths(): gRPC takes roots only
42
+ // as a single PEM that replaces its store outright, and under OpenSSL/BoringSSL the platform set is
43
+ // not a defined collection but whatever the host ships, re-pointable at run time via
44
+ // SSL_CERT_FILE/SSL_CERT_DIR. See credentials.cxx for the full reasoning and how the other SDKs
45
+ // handle it.
46
+ [[nodiscard]] auto
47
+ build_ssl_options(const cluster_options& options, const cluster_credentials& credentials)
48
+ -> grpc::SslCredentialsOptions;
49
+
50
+ // "Authorization" header values: Basic base64(user:password) and Bearer <jwt>.
51
+ [[nodiscard]] auto
52
+ basic_auth_value(const std::string& username, const std::string& password) -> std::string;
53
+ [[nodiscard]] auto
54
+ bearer_auth_value(const std::string& jwt_token) -> std::string;
55
+
56
+ // The per-RPC authorization header the credentials imply: Bearer for a JWT, Basic for
57
+ // username/password, empty when neither applies (e.g. certificate auth, where identity is the
58
+ // client cert). couchbase2 attaches this as call metadata rather than as gRPC CallCredentials,
59
+ // so it works on both secure and insecure channels.
60
+ [[nodiscard]] auto
61
+ authorization_header(const cluster_credentials& credentials) -> std::string;
62
+
63
+ // Whether these options select a channel that encrypts but does NOT verify the server certificate
64
+ // or hostname. make_channel_credentials() branches on exactly this predicate, so pinning it in a
65
+ // test pins the real decision: grpc::ChannelCredentials is opaque, and nothing about the object it
66
+ // returns reveals which branch produced it.
67
+ //
68
+ // Only tls_verify=none on a TLS-enabled channel reaches that branch. A plaintext channel is a
69
+ // different thing, not a weaker one -- it presents no certificate at all, and
70
+ // make_channel_credentials refuses to put credentials on it.
71
+ [[nodiscard]] auto
72
+ tls_peer_verification_disabled(const cluster_options& options) -> bool;
73
+
74
+ // Channel credentials for a couchbase2 endpoint: TLS (from build_ssl_options) when TLS is
75
+ // enabled, otherwise insecure. When tls_peer_verification_disabled() holds, the channel is
76
+ // encrypted but unverified, and taking that branch is logged at warning level.
77
+ [[nodiscard]] auto
78
+ make_channel_credentials(const cluster_options& options, const cluster_credentials& credentials)
79
+ -> std::shared_ptr<grpc::ChannelCredentials>;
80
+
81
+ // Build a channel to a couchbase2 endpoint honouring the TLS options, with the default channel
82
+ // arguments (keepalive).
83
+ [[nodiscard]] auto
84
+ make_channel(const std::string& endpoint,
85
+ const cluster_options& options,
86
+ const cluster_credentials& credentials) -> std::shared_ptr<grpc::Channel>;
87
+
88
+ } // namespace couchbase::core::protostellar
@@ -0,0 +1,57 @@
1
+ /* -*- Mode: C++; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2
+ /*
3
+ * Copyright 2026. 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 "core/protostellar/dispatcher.hxx"
19
+
20
+ #include <grpcpp/create_channel.h>
21
+
22
+ #include <utility>
23
+
24
+ namespace couchbase::core::protostellar
25
+ {
26
+ auto
27
+ default_channel_arguments() -> grpc::ChannelArguments
28
+ {
29
+ grpc::ChannelArguments args;
30
+ args.SetInt(GRPC_ARG_KEEPALIVE_TIME_MS, 30'000);
31
+ args.SetInt(GRPC_ARG_KEEPALIVE_TIMEOUT_MS, 5'000);
32
+ args.SetInt(GRPC_ARG_KEEPALIVE_PERMIT_WITHOUT_CALLS, 1);
33
+ // gRPC's 4 MB default is too small for the largest values Couchbase serves.
34
+ args.SetMaxReceiveMessageSize(static_cast<int>(max_receive_message_size));
35
+ return args;
36
+ }
37
+
38
+ auto
39
+ make_insecure_channel(const std::string& endpoint) -> std::shared_ptr<grpc::Channel>
40
+ {
41
+ return grpc::CreateCustomChannel(
42
+ endpoint, grpc::InsecureChannelCredentials(), default_channel_arguments());
43
+ }
44
+
45
+ dispatcher::dispatcher(asio::io_context& io, std::shared_ptr<grpc::Channel> channel)
46
+ : io_{ io }
47
+ , channel_{ std::move(channel) }
48
+ {
49
+ }
50
+
51
+ auto
52
+ dispatcher::channel() const -> const std::shared_ptr<grpc::Channel>&
53
+ {
54
+ return channel_;
55
+ }
56
+
57
+ } // namespace couchbase::core::protostellar
@@ -0,0 +1,482 @@
1
+ /* -*- Mode: C++; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2
+ /*
3
+ * Copyright 2026. 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/utils/movable_function.hxx"
21
+
22
+ #include <asio/io_context.hpp>
23
+ #include <asio/post.hpp>
24
+
25
+ #include <grpcpp/grpcpp.h>
26
+ #include <grpcpp/support/client_callback.h>
27
+
28
+ #include <algorithm>
29
+ #include <chrono>
30
+ #include <condition_variable>
31
+ #include <cstddef>
32
+ #include <functional>
33
+ #include <memory>
34
+ #include <mutex>
35
+ #include <string>
36
+ #include <type_traits>
37
+ #include <unordered_map>
38
+ #include <utility>
39
+ #include <vector>
40
+
41
+ namespace couchbase::core::protostellar
42
+ {
43
+
44
+ // RFC 77 (Bootstrapping -> Maximum Message Size): couchbase2 clients raise the receive limit to
45
+ // 25 MiB, so nothing the transport hands to a converter can legitimately exceed it -- including a
46
+ // value a compressed payload claims to inflate to.
47
+ constexpr std::size_t max_receive_message_size{ std::size_t{ 25 } * 1024 * 1024 };
48
+
49
+ // Registry of in-flight gRPC calls so the transport can cancel and drain them at shutdown. Without
50
+ // it, teardown could destroy the io_context (and channel) while a gRPC callback is still pending on
51
+ // gRPC's threadpool -- the callback would then post onto a freed io_context (use-after-free). Each
52
+ // completion posts its handler and *then* removes its context; cancel_and_drain() TryCancels
53
+ // whatever is still outstanding and blocks until every callback has done both. That order is the
54
+ // contract, not an incidental detail: remove() is what releases the drain, so deregistering first
55
+ // would let the drain return while a gRPC thread was still posting onto the io_context -- exactly
56
+ // the use-after-free above. Held via shared_ptr so a late callback can safely touch it even while
57
+ // the owning dispatcher is being destroyed.
58
+ class call_tracker
59
+ {
60
+ public:
61
+ // The tracker OWNS a shared_ptr to each context so a snapshot can keep it alive while cancelling
62
+ // even if a concurrent completion removes and releases the caller's own reference.
63
+ void add(std::shared_ptr<grpc::ClientContext> context)
64
+ {
65
+ auto* key = context.get();
66
+ const std::scoped_lock lock{ mutex_ };
67
+ outstanding_.emplace(key, entry{ std::move(context), {}, {} });
68
+ }
69
+
70
+ // As above, for a call that cancellation alone does not drive to completion. `on_cancel` runs
71
+ // straight after TryCancel; `owner` keeps the call's own object alive for as long as it stays
72
+ // registered, so that hook cannot run against freed storage.
73
+ void add(std::shared_ptr<grpc::ClientContext> context,
74
+ std::function<void()> on_cancel,
75
+ std::shared_ptr<void> owner)
76
+ {
77
+ auto* key = context.get();
78
+ const std::scoped_lock lock{ mutex_ };
79
+ outstanding_.emplace(key, entry{ std::move(context), std::move(on_cancel), std::move(owner) });
80
+ }
81
+
82
+ void remove(grpc::ClientContext* context)
83
+ {
84
+ const std::scoped_lock lock{ mutex_ };
85
+ outstanding_.erase(context);
86
+ if (outstanding_.empty()) {
87
+ idle_.notify_all();
88
+ }
89
+ }
90
+
91
+ // Cancel every in-flight call and block until all of their gRPC callbacks have run. Runs on
92
+ // whichever thread destroys the dispatcher: the io_context thread, another thread while the io
93
+ // thread keeps running, or a thread that never runs the io_context at all. Nothing here may
94
+ // therefore depend on io_context work making progress. gRPC's own threads deliver the
95
+ // cancellations and the completions post back onto the io_context, where they stay queued until
96
+ // the drain returns, so blocking here cannot deadlock against them.
97
+ void cancel_and_drain()
98
+ {
99
+ // Snapshot the entries under the lock, then release it before calling TryCancel or any cancel
100
+ // hook so no external gRPC call runs while mutex_ is held (a completion delivered on a gRPC
101
+ // thread takes that same lock in remove(), and releasing a read hold can deliver OnDone
102
+ // inline). The snapshot keeps every context and owner alive across the unlocked cancel, so a
103
+ // concurrent completion that removes and releases its own reference cannot leave a dangling
104
+ // pointer here.
105
+ std::vector<entry> pending;
106
+ {
107
+ const std::scoped_lock lock{ mutex_ };
108
+ pending.reserve(outstanding_.size());
109
+ for (const auto& [ptr, item] : outstanding_) {
110
+ pending.push_back(item);
111
+ }
112
+ }
113
+ for (const auto& item : pending) {
114
+ item.context->TryCancel();
115
+ if (item.on_cancel) {
116
+ item.on_cancel();
117
+ }
118
+ }
119
+ std::unique_lock lock{ mutex_ };
120
+ idle_.wait(lock, [this] {
121
+ return outstanding_.empty();
122
+ });
123
+ }
124
+
125
+ private:
126
+ struct entry {
127
+ std::shared_ptr<grpc::ClientContext> context;
128
+ // Run straight after TryCancel for calls that cancellation alone does not finish. A streaming
129
+ // call registers one that releases its read hold: gRPC defers OnDone while a hold is active,
130
+ // and the hold is otherwise released only from an io_context continuation -- which cannot run
131
+ // when the drain is itself blocking the io thread. Empty for unary calls, which take no holds.
132
+ std::function<void()> on_cancel;
133
+ // Keeps a self-owning call object (the streaming reactor) alive for as long as it is
134
+ // registered, so on_cancel cannot run against freed storage.
135
+ std::shared_ptr<void> owner;
136
+ };
137
+
138
+ std::mutex mutex_{};
139
+ std::condition_variable idle_{};
140
+ std::unordered_map<grpc::ClientContext*, entry> outstanding_{};
141
+ };
142
+
143
+ // Handle to an in-flight unary call. cancel() maps to gRPC's client-side TryCancel; the call still
144
+ // completes afterwards (with a CANCELLED status) and its handler is posted as usual, so callers
145
+ // never leak a pending operation. As with any completion, the handler runs when the io_context runs
146
+ // it -- cancelling does not make it run inline.
147
+ class pending_call
148
+ {
149
+ public:
150
+ pending_call() = default;
151
+ explicit pending_call(std::shared_ptr<grpc::ClientContext> context)
152
+ : context_{ std::move(context) }
153
+ {
154
+ }
155
+
156
+ void cancel() const
157
+ {
158
+ if (context_) {
159
+ context_->TryCancel();
160
+ }
161
+ }
162
+
163
+ private:
164
+ std::shared_ptr<grpc::ClientContext> context_{};
165
+ };
166
+
167
+ // Every call gets a deadline, including one whose budget is already spent.
168
+ //
169
+ // gRPC has no "expire immediately" input: leaving the deadline unset means the call runs until the
170
+ // channel breaks, so guarding the set_deadline with `timeout > 0` turns "no time left" into "no
171
+ // limit" -- the wrong direction for the mistake to go, and one that leaves cluster::close() waiting
172
+ // on a call with no reason to finish. Clamping to the smallest positive deadline expires the call
173
+ // instead, which is what an exhausted budget should do.
174
+ //
175
+ // This is the second line of defence, not the only one: every caller in component.cxx rejects a
176
+ // non-positive timeout up front (see fail_expired there), because failing before anything is sent
177
+ // carries information this layer cannot reconstruct -- that the operation provably never reached
178
+ // the server, hence unambiguous_timeout even for a mutation. The clamp exists so a caller that
179
+ // forgets, or a retry loop passing on what is left of a budget, cannot produce an unbounded call.
180
+ [[nodiscard]] inline auto
181
+ call_deadline(std::chrono::milliseconds timeout) -> std::chrono::system_clock::time_point
182
+ {
183
+ return std::chrono::system_clock::now() + std::max(timeout, std::chrono::milliseconds{ 1 });
184
+ }
185
+
186
+ // Default channel arguments: TCP keepalive so an idle channel detects a dead gateway.
187
+ [[nodiscard]] auto
188
+ default_channel_arguments() -> grpc::ChannelArguments;
189
+
190
+ // Build an insecure (plaintext) channel for the non-TLS couchbase2 path. TLS and per-call
191
+ // credentials live on the secure-channel path (see credentials.hxx/.cxx).
192
+ [[nodiscard]] auto
193
+ make_insecure_channel(const std::string& endpoint) -> std::shared_ptr<grpc::Channel>;
194
+
195
+ // Reads a gRPC server stream via the callback API and hands each message, plus the terminal
196
+ // status, to the io_context. Assumes a single-threaded io_context (the SDK contract), so the FIFO
197
+ // post order guarantees every row is delivered before the completion runs.
198
+ //
199
+ // Held by shared_ptr, with every posted continuation carrying a strong reference. That covers both
200
+ // endings: the last reference is dropped either by the io_context running the completion or by the
201
+ // io_context being destroyed with the completion still queued. It also lets the read hold below be
202
+ // released from another thread without racing the reactor's own destruction.
203
+ template<typename Response>
204
+ class streaming_reactor final
205
+ : public grpc::ClientReadReactor<Response>
206
+ , public std::enable_shared_from_this<streaming_reactor<Response>>
207
+ {
208
+ public:
209
+ streaming_reactor(asio::io_context& io,
210
+ std::shared_ptr<call_tracker> tracker,
211
+ std::shared_ptr<grpc::ClientContext> context,
212
+ utils::movable_function<void(Response)> on_row,
213
+ utils::movable_function<void(grpc::Status)> on_done)
214
+ : io_{ io }
215
+ , tracker_{ std::move(tracker) }
216
+ , context_{ std::move(context) }
217
+ , on_row_{ std::move(on_row) }
218
+ , on_done_{ std::move(on_done) }
219
+ {
220
+ }
221
+
222
+ void begin()
223
+ {
224
+ // Because backpressure re-issues StartRead from the io_context (outside the reaction, see
225
+ // OnReadDone), a hold keeps the call alive between a completed read and the next StartRead --
226
+ // otherwise gRPC could finalize the call (fire OnDone, free the reader) while a queued read
227
+ // task still holds a pointer to it, and the stray StartRead would hit a destroyed call. This is
228
+ // gRPC's documented requirement for issuing an operation from outside a reaction, not a local
229
+ // precaution: see the AddHold contract in grpcpp/impl/codegen/client_callback.h.
230
+ //
231
+ // The hold is released in OnReadDone once a read comes back not-ok, or by release_hold() when
232
+ // the call is cancelled while no read is outstanding. That second caller is not the read loop's
233
+ // owner and cannot know whether a read task is still queued, so the hold alone does not close
234
+ // the window -- release_hold() and the queued StartRead are ordered against each other instead.
235
+ //
236
+ // This runs under the same lock, because server_stream() registers the cancel hook before the
237
+ // invoker binds the reactor to a call: a drain in that window would otherwise reach RemoveHold
238
+ // with no reader bound and no hold taken. Under the lock the two orders are both well defined
239
+ // -- either the hold is taken and the drain releases it, or the drain gets there first and no
240
+ // hold is taken at all. The call is still started in that second case, so it runs to OnDone and
241
+ // releases the drain rather than stranding it.
242
+ const std::scoped_lock lock{ mutex_ };
243
+ if (!hold_released_) {
244
+ this->AddHold();
245
+ hold_taken_ = true;
246
+ }
247
+ this->StartRead(&current_);
248
+ this->StartCall();
249
+ }
250
+
251
+ // Releases begin()'s hold, at most once however many times this is called -- RemoveHold has to
252
+ // balance AddHold exactly, and two callers race for it: OnReadDone when reads stop, and
253
+ // cancel_and_drain() through the hook registered in server_stream().
254
+ //
255
+ // The drain needs that hook because a hold defers OnDone. Between a completed read and the next
256
+ // StartRead no read is outstanding for TryCancel to fail, so nothing would drive the call to
257
+ // completion, and the drain -- which may be running on the io_context thread itself -- would wait
258
+ // for an OnDone that cannot fire until a continuation that cannot run has run.
259
+ //
260
+ // The lock orders this release against the queued StartRead in OnReadDone; it is not there to
261
+ // make the flag thread-safe. Removing the last hold destroys the reader synchronously, inside
262
+ // RemoveHold, so the two must not overlap: whichever side acquires the lock first, the other sees
263
+ // a decision already made rather than a reader mid-destruction. Releasing while a read is
264
+ // outstanding is harmless -- the outstanding read keeps gRPC's own count above zero, so it
265
+ // finalizes only once that read completes.
266
+ void release_hold()
267
+ {
268
+ const std::scoped_lock lock{ mutex_ };
269
+ if (!hold_released_) {
270
+ hold_released_ = true;
271
+ // Nothing to balance if begin() has not run yet: the reactor is reachable from the tracker
272
+ // before the invoker binds it to a call, and RemoveHold on an unbound reactor dereferences a
273
+ // reader that does not exist. Setting the flag is enough -- begin() reads it and skips the
274
+ // hold.
275
+ if (hold_taken_) {
276
+ this->RemoveHold();
277
+ }
278
+ }
279
+ }
280
+
281
+ void OnReadDone(bool ok) override
282
+ {
283
+ if (!ok) {
284
+ release_hold(); // no more reads; let the call finalize and OnDone fire
285
+ return;
286
+ }
287
+ auto message = std::make_shared<Response>(std::move(current_));
288
+ // Backpressure: issue the next read from *inside* the io_context continuation, only after the
289
+ // consumer has processed this row. On a single-threaded io_context that caps the amount gRPC
290
+ // reads ahead of a slow consumer at one message, instead of draining the whole stream into the
291
+ // asio post queue. current_ is moved-from here but no read is outstanding until StartRead runs.
292
+ // The continuation holds a strong reference so it cannot outlive its own reactor.
293
+ //
294
+ // The consumer runs before the lock is taken, deliberately: it is caller code, and holding the
295
+ // reactor's lock across it would let a slow or blocking consumer stall a concurrent drain.
296
+ asio::post(io_, [self = this->shared_from_this(), message]() {
297
+ self->on_row_(std::move(*message));
298
+ const std::scoped_lock lock{ self->mutex_ };
299
+ if (self->hold_released_) {
300
+ // The hold is gone, so gRPC may already have destroyed the reader -- it does so inside
301
+ // RemoveHold -- and a StartRead would dispatch through it. Nothing is lost by stopping
302
+ // here: the hold was released either because reads had finished or because the call was
303
+ // cancelled, and either way it is already on its way to OnDone.
304
+ return;
305
+ }
306
+ self->StartRead(&self->current_);
307
+ });
308
+ }
309
+
310
+ void OnDone(const grpc::Status& status) override
311
+ {
312
+ // Post before deregistering, for the same reason as unary(): remove() releases
313
+ // cancel_and_drain(), so deregistering first would let ~dispatcher() return while this thread
314
+ // is still inside asio::post(). The handle and the context pointer are cached in locals first
315
+ // because the posted task owns this reactor and may destroy it on the io thread as soon as it
316
+ // is enqueued -- reading tracker_ or context_ after the post could touch freed storage. The
317
+ // tracker owns a shared_ptr to the context, so the cached pointer stays valid as a key until
318
+ // remove() erases it, and remove() never dereferences it.
319
+ auto tracker = tracker_;
320
+ auto* context = context_.get();
321
+ // Copy the status into the closure -- it is a reference parameter, so the posted task must own
322
+ // its own copy rather than capture a reference that dangles once OnDone returns.
323
+ //
324
+ // The strong reference is what destroys the reactor: dropped when this task runs, or when the
325
+ // task is destroyed unrun because the io_context went away first. A shared_ptr rather than the
326
+ // unique_ptr this held before backpressure, because the cancel hook needs a weak reference to
327
+ // the same object.
328
+ asio::post(io_, [self = this->shared_from_this(), status_copy = status]() mutable {
329
+ // Moved rather than copied: the task owns this status and does not touch it again, and
330
+ // grpc::Status carries two std::strings.
331
+ self->on_done_(std::move(status_copy));
332
+ });
333
+ tracker->remove(context);
334
+ }
335
+
336
+ private:
337
+ // Guards the hold flags and, with them, the ordering between releasing the hold and issuing a
338
+ // read -- both the next read in OnReadDone and the first one in begin(). Uncontended in steady
339
+ // state: only the io_context thread takes it, and the cancel hook once at teardown.
340
+ std::mutex mutex_{};
341
+ bool hold_taken_{ false };
342
+ bool hold_released_{ false };
343
+ asio::io_context& io_;
344
+ std::shared_ptr<call_tracker> tracker_;
345
+ Response current_{};
346
+ std::shared_ptr<grpc::ClientContext> context_;
347
+ utils::movable_function<void(Response)> on_row_;
348
+ utils::movable_function<void(grpc::Status)> on_done_;
349
+ };
350
+
351
+ // Bridges gRPC's callback API onto an asio::io_context. A unary RPC is launched on gRPC's own
352
+ // threadpool; its completion is posted back onto the io_context, so callers observe results on
353
+ // the SDK's execution context — the same threading contract as the MCBP path.
354
+ class dispatcher
355
+ {
356
+ public:
357
+ dispatcher(asio::io_context& io, std::shared_ptr<grpc::Channel> channel);
358
+ dispatcher(const dispatcher&) = delete;
359
+ dispatcher(dispatcher&&) = delete;
360
+ auto operator=(const dispatcher&) -> dispatcher& = delete;
361
+ auto operator=(dispatcher&&) -> dispatcher& = delete;
362
+
363
+ // Cancels and drains every in-flight call before the io_context/channel go away, so no gRPC
364
+ // callback can fire against freed state after the owning component is destroyed.
365
+ ~dispatcher()
366
+ {
367
+ tracker_->cancel_and_drain();
368
+ }
369
+
370
+ [[nodiscard]] auto channel() const -> const std::shared_ptr<grpc::Channel>&;
371
+
372
+ // Issue a unary RPC.
373
+ // invoker(grpc::ClientContext&, Response&, callback) launches the async call, e.g.
374
+ // stub->async()->Get(&ctx, &request, &response, std::move(callback));
375
+ // handler(grpc::Status, Response&&) runs once, on the io_context, when the call completes.
376
+ // The request must outlive the call (the caller owns it). Returns a pending_call for
377
+ // cancellation.
378
+ template<typename Response, typename Invoker, typename Handler>
379
+ auto unary(std::chrono::milliseconds timeout, Invoker&& invoker, Handler&& handler)
380
+ -> pending_call
381
+ {
382
+ struct call_state {
383
+ std::shared_ptr<grpc::ClientContext> context{ std::make_shared<grpc::ClientContext>() };
384
+ Response response{};
385
+ };
386
+ auto state = std::make_shared<call_state>();
387
+ state->context->set_deadline(call_deadline(timeout));
388
+
389
+ // Register the call so shutdown can cancel and drain it. The context is kept alive both by
390
+ // `state` (captured by the callback below) and by the tracker's own reference, so the raw
391
+ // pointer the tracker keys on is valid for the TryCancel in cancel_and_drain().
392
+ auto* context = state->context.get();
393
+ tracker_->add(state->context);
394
+
395
+ // Hold the handler in a shared_ptr so the gRPC-thread callback can hand it to the posted
396
+ // task. Capture the io_context by pointer (never a reference to a local) so nothing dangles
397
+ // once unary() returns; the dispatcher (and its io_context) must outlive the call. Capture the
398
+ // tracker (shared) so deregistration is safe even as the dispatcher is being destroyed.
399
+ auto handler_holder = std::make_shared<std::decay_t<Handler>>(std::forward<Handler>(handler));
400
+ auto* io = &io_;
401
+ auto tracker = tracker_;
402
+
403
+ // If the invoker throws before it launches the async call, the completion callback below never
404
+ // runs, so deregister here to keep cancel_and_drain() from waiting on a call that will never
405
+ // complete.
406
+ try {
407
+ std::forward<Invoker>(invoker)(
408
+ *state->context,
409
+ state->response,
410
+ [state, handler_holder, io, tracker, context](grpc::Status status) {
411
+ // Post before deregistering. remove() is what releases cancel_and_drain(), so
412
+ // deregistering first would let ~dispatcher() return -- and its owner tear the
413
+ // io_context down -- while this thread is still inside asio::post(*io, ...), which is
414
+ // the use-after-free the tracker exists to prevent. Ordered this way, the drain cannot
415
+ // return until the post has been issued. The captures keep state and the tracker alive,
416
+ // so nothing here depends on the dispatcher still existing.
417
+ asio::post(*io, [state, handler_holder, status = std::move(status)]() mutable {
418
+ (*handler_holder)(std::move(status), std::move(state->response));
419
+ });
420
+ tracker->remove(context);
421
+ });
422
+ } catch (...) {
423
+ tracker_->remove(context);
424
+ throw;
425
+ }
426
+
427
+ return pending_call{ state->context };
428
+ }
429
+
430
+ // Issue a server-streaming RPC.
431
+ // invoker(grpc::ClientContext&, grpc::ClientReadReactor<Response>*) binds the reactor to the
432
+ // call, e.g. stub->async()->Query(&ctx, &request, reactor).
433
+ // on_row(Response) runs on the io_context for each streamed message; on_done(grpc::Status)
434
+ // runs once when the stream ends. The request must outlive the call (capture it in on_done).
435
+ template<typename Response, typename Invoker>
436
+ auto server_stream(std::chrono::milliseconds timeout,
437
+ Invoker&& invoker,
438
+ utils::movable_function<void(Response)> on_row,
439
+ utils::movable_function<void(grpc::Status)> on_done) -> pending_call
440
+ {
441
+ auto context = std::make_shared<grpc::ClientContext>();
442
+ context->set_deadline(call_deadline(timeout));
443
+ // gRPC only borrows the reactor for the duration of the call; ownership stays with this
444
+ // shared_ptr and the continuations the reactor posts.
445
+ auto reactor = std::make_shared<streaming_reactor<Response>>(
446
+ io_, tracker_, context, std::move(on_row), std::move(on_done));
447
+ // Register before launch so shutdown can cancel it; the reactor deregisters in OnDone. The hook
448
+ // releases the reactor's read hold, without which a cancelled-but-parked stream never reaches
449
+ // OnDone and the drain blocks forever; the reactor doubles as the owner so the hook always has
450
+ // a live object to call.
451
+ tracker_->add(
452
+ context,
453
+ [weak = std::weak_ptr<streaming_reactor<Response>>{ reactor }]() {
454
+ if (const auto locked = weak.lock()) {
455
+ locked->release_hold();
456
+ }
457
+ },
458
+ reactor);
459
+ // If the invoker fails to bind the reactor to a call, OnDone never fires, so deregister here
460
+ // rather than block cancel_and_drain() forever at shutdown. Dropping the tracker's reference
461
+ // leaves this scope holding the last one, so the reactor is destroyed as the exception unwinds.
462
+ bool bound{ false };
463
+ try {
464
+ std::forward<Invoker>(invoker)(*context, reactor.get());
465
+ bound = true;
466
+ reactor->begin();
467
+ } catch (...) {
468
+ if (!bound) {
469
+ tracker_->remove(context.get());
470
+ }
471
+ throw;
472
+ }
473
+ return pending_call{ context };
474
+ }
475
+
476
+ private:
477
+ asio::io_context& io_;
478
+ std::shared_ptr<grpc::Channel> channel_;
479
+ std::shared_ptr<call_tracker> tracker_{ std::make_shared<call_tracker>() };
480
+ };
481
+
482
+ } // namespace couchbase::core::protostellar