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
@@ -26,6 +26,9 @@
26
26
 
27
27
  namespace
28
28
  {
29
+ // NOLINTBEGIN(misc-const-correctness): these are llhttp callbacks whose signatures must match
30
+ // llhttp_data_cb / llhttp_cb exactly (int(llhttp_t*, ...)); the parser pointer cannot be const or
31
+ // the assignment to the llhttp_settings_t members below would be ill-formed.
29
32
  inline auto
30
33
  static_on_status(llhttp_t* parser, const char* at, std::size_t length) -> int
31
34
  {
@@ -80,6 +83,7 @@ static_on_message_complete(llhttp_t* parser) -> int
80
83
  wrapper->complete = true;
81
84
  return 0;
82
85
  }
86
+ // NOLINTEND(misc-const-correctness)
83
87
  } // namespace
84
88
 
85
89
  namespace couchbase::core::io
@@ -17,20 +17,38 @@
17
17
 
18
18
  #include "http_streaming_response.hxx"
19
19
 
20
+ #include "core/logger/logger.hxx"
21
+ #include "core/logger/redaction.hxx"
20
22
  #include "core/utils/movable_function.hxx"
21
23
  #include "http_session.hxx"
22
24
 
23
25
  #include <couchbase/error_codes.hxx>
24
26
 
27
+ #include <asio/post.hpp>
28
+
25
29
  #include <cstdint>
26
30
  #include <map>
27
31
  #include <memory>
32
+ #include <mutex>
33
+ #include <optional>
28
34
  #include <string>
29
35
  #include <system_error>
30
36
  #include <utility>
31
37
 
32
38
  namespace couchbase::core::io
33
39
  {
40
+ auto
41
+ terminal_error_code(deadline_terminal terminal) -> std::error_code
42
+ {
43
+ switch (terminal) {
44
+ case deadline_terminal::unambiguous:
45
+ return errc::common::unambiguous_timeout;
46
+ case deadline_terminal::ambiguous:
47
+ break;
48
+ }
49
+ return errc::common::ambiguous_timeout;
50
+ }
51
+
34
52
  class http_streaming_response_body_impl
35
53
  : public std::enable_shared_from_this<http_streaming_response_body_impl>
36
54
  {
@@ -38,85 +56,318 @@ public:
38
56
  http_streaming_response_body_impl(asio::io_context& io,
39
57
  std::shared_ptr<http_session> session,
40
58
  std::string cached_data,
41
- bool reading_complete)
59
+ bool reading_complete,
60
+ std::size_t cached_chunk_size)
42
61
  : session_{ std::move(session) }
62
+ , endpoint_{ session_ ? session_->hostname() + ":" + session_->port() : std::string{} }
43
63
  , cached_data_{ std::move(cached_data) }
44
64
  , deadline_{ io }
45
65
  , reading_complete_{ reading_complete }
66
+ , cached_chunk_size_{ cached_chunk_size }
67
+ {
68
+ // A response complete before a single read_some is constructed with the session still held.
69
+ // http_session checks that connection in once the response handler returns, and nothing
70
+ // further belongs to this message, so a read from that session would park on a connection
71
+ // serving someone else. Release it here, which also establishes the invariant next(),
72
+ // close_impl() and set_deadline() each assume: reading_complete_ means no live session.
73
+ if (reading_complete_) {
74
+ session_ = nullptr;
75
+ }
76
+ }
77
+
78
+ // A pending read holds this body, so it is destroyed only with no pull in flight. An armed
79
+ // deadline does not hold it. A body dropped before its response was fully read leaves its
80
+ // connection mid-response, which otherwise only cluster close ends. Closing the body posts a stop
81
+ // of its session to the session strand.
82
+ ~http_streaming_response_body_impl()
83
+ {
84
+ close_impl(errc::common::request_canceled, std::nullopt);
85
+ }
86
+
87
+ void close(std::error_code ec)
46
88
  {
89
+ close_impl(ec, std::nullopt);
47
90
  }
48
91
 
49
- auto close(std::error_code ec)
92
+ // Closes only if `generation` is still current. The check and the terminal transition share one
93
+ // lock, so a re-arm or a clean completion cannot land between them.
94
+ void close_at_deadline(deadline_terminal on_expiry, std::uint64_t generation)
50
95
  {
51
- if (session_) {
52
- session_->stop();
96
+ const auto ec = terminal_error_code(on_expiry);
97
+ if (close_impl(ec, generation)) {
98
+ CB_LOG_DEBUG("streaming response deadline expired, closing the body: terminal={}, "
99
+ "endpoint=\"{}\"",
100
+ ec.message(),
101
+ logger::system_data(endpoint_));
53
102
  }
54
- session_ = nullptr;
55
- final_ec_ = ec;
56
103
  }
57
104
 
58
- void next(utils::movable_function<void(std::string, std::error_code)>&& callback)
105
+ // Returns whether this call closed the body; false when it was already closed, drained, or the
106
+ // generation was superseded.
107
+ auto close_impl(std::error_code ec, std::optional<std::uint64_t> expect_generation) -> bool
59
108
  {
60
- if (!cached_data_.empty()) {
61
- // Return the cached data & clear the cache
62
- std::string data;
63
- std::swap(data, cached_data_);
64
- callback(std::move(data), {});
65
- return;
109
+ // session_ and final_ec_ are written here and on the session's read completion, which runs on
110
+ // the session strand. close() is reached off that strand: from row_streamer::cancel()'s post,
111
+ // the idle-timer completion and the deadline completion, and from the destructor on whatever
112
+ // thread drops the body. A mutex guards the shared teardown state. close() is
113
+ // idempotent, so a second teardown (a cancel racing a transport error) neither stops the
114
+ // session twice nor overwrites the first terminal error.
115
+ std::shared_ptr<http_session> to_stop;
116
+ {
117
+ const std::scoped_lock lock{ mutex_ };
118
+ if (expect_generation.has_value() && *expect_generation != deadline_generation_) {
119
+ return false;
120
+ }
121
+ if (closed_ || (reading_complete_ && cached_data_.empty())) {
122
+ // Drained is terminal too, the state set_deadline() also refuses. A clean end leaves
123
+ // closed_ false, so without this a later close() overwrites final_ec_ and a pull after
124
+ // end-of-stream reports request_canceled. Both clean-end paths disarm the timer, so
125
+ // returning here leaves none armed.
126
+ return false;
127
+ }
128
+ closed_ = true;
129
+ // Only stop the session when the response was abandoned mid-body: such a connection is left
130
+ // at an arbitrary offset and cannot be reused. A response that was already fully received
131
+ // (reading_complete_) leaves the connection at a message boundary, and http_session checks
132
+ // it back into the keep-alive pool via its stream-end handler
133
+ // (http_session_manager::check_in) — stopping it here would evict a live pooled connection
134
+ // and make the *next* unrelated request on it fail with request_canceled.
135
+ //
136
+ // reading_complete_ is what separates a connection this body may stop from one the pool
137
+ // owns. http_session::read_some runs the stream-end handler only after invoking this body's
138
+ // completion, so on one thread the flag is set by the time the connection is published.
139
+ // Across threads a clean end can still land between the claim below and the stop that
140
+ // executes it, which is what mark_stopping() covers.
141
+ //
142
+ // Hand the session out to be stopped below, then drop our reference. Written as two
143
+ // statements rather than std::exchange because cppcheck's flow analysis mis-models the
144
+ // std::exchange return value and wrongly reports the `if (to_stop)` guard as always-false.
145
+ //
146
+ // A session already stopped is skipped: the stop() call that set stopped_ runs the whole
147
+ // teardown, and a stop posted after the cluster closed would hold the session until the
148
+ // io_context is destroyed.
149
+ if (!reading_complete_ && session_ && !session_->is_stopped()) {
150
+ to_stop = session_;
151
+ // Claim the stop while this lock is held. The clean-end branch of the read completion
152
+ // clears closed_ and hands the connection to check_in, and it can run between the claim
153
+ // and the stop that executes it; the claim is what check_in tests, so the pool refuses a
154
+ // connection this body is about to stop.
155
+ to_stop->mark_stopping();
156
+ }
157
+ session_ = nullptr;
158
+ final_ec_ = ec;
159
+ // Disarm. cancel() does not stop a completion already queued, hence the generation bump.
160
+ deadline_.cancel();
161
+ ++deadline_generation_;
162
+ // Reached on error, cancel or destruction (a clean end sets reading_complete_ instead), so
163
+ // any bytes still buffered from the initial parse are not handed out as data. Dropping them
164
+ // makes next() surface the terminal error, not stale body bytes.
165
+ cached_data_.clear();
66
166
  }
67
- if (reading_complete_) {
68
- callback({}, {});
69
- return;
167
+ // stop() runs inline -- it closes the stream, cancels the timers, cancels the current
168
+ // response and invokes on_stop_handler_ -- so it has to run on the session strand, alongside
169
+ // the read and write handlers it tears down. close() is reached off that strand from a
170
+ // deadline expiry, an idle-timer completion, row_streamer::cancel()'s post and the destructor,
171
+ // so the stop is posted there.
172
+ //
173
+ // The session is already claimed above, so the gap between deciding the stop and running it
174
+ // stays invisible to the pool.
175
+ if (to_stop) {
176
+ asio::post(to_stop->get_executor(), [to_stop]() {
177
+ to_stop->stop();
178
+ });
70
179
  }
71
- if (session_) {
72
- session_->read_some([self = shared_from_this(), cb = std::move(callback)](
73
- std::string data, bool has_more, std::error_code ec) mutable {
74
- if (!has_more || ec) {
75
- self->close({});
76
- if (!ec) {
77
- self->reading_complete_ = true;
78
- }
180
+ return true;
181
+ }
182
+
183
+ void next(utils::movable_function<void(std::string, bool, std::error_code)>&& callback)
184
+ {
185
+ // Decide what to do under the lock (the state it reads is mutated by close() and by the read
186
+ // completion below), then invoke the callback / start the read outside the lock so neither can
187
+ // re-enter next() while the mutex is held. deadline_ is touched under the lock: an asio timer
188
+ // is not thread-safe.
189
+ std::string data;
190
+ bool has_more = false;
191
+ std::error_code deliver_ec;
192
+ bool deliver_now = false;
193
+ std::shared_ptr<http_session> to_read;
194
+ {
195
+ const std::scoped_lock lock{ mutex_ };
196
+ if (closed_) {
197
+ // The body was closed on error/cancel: surface the recorded terminal error and stop,
198
+ // ahead of the cached-data branch, so a next() after close never delivers residual body
199
+ // bytes (with a falsy ec) that would make a cancelled/failed stream look like it is still
200
+ // producing rows.
201
+ deliver_ec = final_ec_;
202
+ deliver_now = true;
203
+ } else if (!cached_data_.empty()) {
204
+ // Hand back the data buffered during the initial parse. When a cached chunk size is set (a
205
+ // test seam that simulates a socket that dribbles the body out), deliver at most that many
206
+ // bytes per pull; otherwise hand back everything at once. There is more to come while
207
+ // cached data remains or the response was not already fully read (reading_complete_).
208
+ if (cached_chunk_size_ == 0 || cached_data_.size() <= cached_chunk_size_) {
209
+ std::swap(data, cached_data_);
210
+ } else {
211
+ data = cached_data_.substr(0, cached_chunk_size_);
212
+ cached_data_.erase(0, cached_chunk_size_);
79
213
  }
80
- cb(std::move(data), ec);
81
- });
214
+ has_more = !reading_complete_ || !cached_data_.empty();
215
+ deliver_now = true;
216
+ if (!has_more) {
217
+ deadline_.cancel();
218
+ ++deadline_generation_;
219
+ }
220
+ } else if (session_) {
221
+ // A read is needed. session_ is non-null here: it is cleared alongside closed_ and
222
+ // alongside reading_complete_, so a live session means the body is still streaming.
223
+ to_read = session_;
224
+ } else {
225
+ // No cached data and no live session, and not closed: the body drained cleanly
226
+ // (reading_complete_), so report end-of-stream with a falsy error.
227
+ deliver_now = true;
228
+ }
229
+ }
230
+ if (deliver_now) {
231
+ callback(std::move(data), has_more, deliver_ec);
82
232
  return;
83
233
  }
84
- callback({}, final_ec_);
234
+ to_read->read_some([self = shared_from_this(), cb = std::move(callback)](
235
+ std::string data, bool has_more, std::error_code ec) mutable {
236
+ if (ec) {
237
+ // Error or cancellation: the connection is left mid-response and is not reusable, so stop
238
+ // it. close() records the terminal in final_ec_, which this callback and any later
239
+ // next() both report.
240
+ self->close(ec);
241
+ } else {
242
+ const std::scoped_lock lock{ self->mutex_ };
243
+ if (!has_more) {
244
+ // Clean end-of-stream. http_session::read_some hands the session back to the keep-alive
245
+ // pool via its stream-end handler (http_session_manager::check_in) once this completion
246
+ // returns, and that handler stops the connection only when it is not reusable
247
+ // (Connection: close, node gone, etc.). Calling stop() here would evict an
248
+ // otherwise-reusable connection and defeat
249
+ // keep-alive, adding avoidable connection churn, so just release our reference and mark
250
+ // the body drained; subsequent next() calls report end-of-stream via reading_complete_.
251
+ //
252
+ // Tested ahead of closed_, so a response whose last read succeeded is delivered even
253
+ // when a terminal was recorded while that read was in flight. This is the rule
254
+ // close_impl already applies, refusing a close once the body is drained; the read
255
+ // completion is where drained is established, so the two orders disagree only over the
256
+ // instant that establishes it. The bytes are the whole remainder of a response the
257
+ // server completed, and reporting a timeout for one held in hand tells a retry layer
258
+ // the request may not have applied when it did.
259
+ self->reading_complete_ = true;
260
+ self->session_ = nullptr;
261
+ self->deadline_.cancel();
262
+ ++self->deadline_generation_;
263
+ // Drop the terminal a close recorded while this read was in flight. Delivering the read
264
+ // as a clean end and leaving that terminal in place would contradict it on the next
265
+ // pull, which tests closed_ first and would report the timeout the body has just
266
+ // decided against. The body is drained either way -- reading_complete_ with no cached
267
+ // bytes is the state close_impl and set_deadline already refuse -- so clearing this
268
+ // reopens nothing.
269
+ self->closed_ = false;
270
+ self->final_ec_ = {};
271
+ } else if (self->closed_) {
272
+ // Closed while a read that has more to come was completing. Report the recorded terminal
273
+ // and drop the bytes: they are a fragment of a response that has already ended, and
274
+ // handing them over with a falsy error_code would report a closed body as still
275
+ // producing data. row_streamer::cancel closes the row channel itself, so it is a
276
+ // deadline expiry that reaches here with a consumer still able to receive. The close
277
+ // has to land between the read completing and this lock, which needs a second thread
278
+ // running the io_context.
279
+ ec = self->final_ec_;
280
+ data.clear();
281
+ has_more = false;
282
+ }
283
+ }
284
+ if (ec) {
285
+ // A failed read reports why the body closed, not what the read returned: an abort reports
286
+ // request_canceled whatever ended the body, and a transport error arriving after a close
287
+ // would otherwise mask the terminal.
288
+ ec = self->terminal_error();
289
+ }
290
+ cb(std::move(data), has_more, ec);
291
+ });
292
+ }
293
+
294
+ // Supersedes any armed deadline without ending the body. Taken under the same lock as arming
295
+ // and the terminal transition, so an expiry already queued when the lock is taken is discarded
296
+ // by the generation check rather than delivered. Lets a caller whose own teardown must be
297
+ // posted stop the deadline on the calling thread first, so a cancel cannot lose to an expiry
298
+ // landing in the gap.
299
+ void cancel_deadline()
300
+ {
301
+ const std::scoped_lock lock{ mutex_ };
302
+ deadline_.cancel();
303
+ ++deadline_generation_;
85
304
  }
86
305
 
87
- void set_deadline(std::chrono::time_point<std::chrono::steady_clock> deadline_tp)
306
+ auto set_deadline(std::chrono::time_point<std::chrono::steady_clock> deadline_tp,
307
+ deadline_terminal on_expiry) -> deadline_state
88
308
  {
309
+ const std::scoped_lock lock{ mutex_ };
310
+ if (closed_ || (reading_complete_ && cached_data_.empty())) {
311
+ // Drained: nothing left to reclaim. reading_complete_ with cached_data_ still buffered is
312
+ // not drained, and does take a deadline.
313
+ return deadline_state::body_already_ended;
314
+ }
315
+ const auto generation = ++deadline_generation_;
89
316
  deadline_.expires_at(deadline_tp);
90
- deadline_.async_wait([self = shared_from_this()](auto ec) {
317
+ // Weak: an armed deadline does not keep a dropped body, and through it the session, alive
318
+ // until expiry. The destructor closes a body dropped before its deadline.
319
+ deadline_.async_wait([weak = weak_from_this(), on_expiry, generation](auto ec) {
91
320
  if (ec == asio::error::operation_aborted) {
92
321
  return;
93
322
  }
94
- self->close(errc::common::ambiguous_timeout);
323
+ if (const auto self = weak.lock(); self) {
324
+ self->close_at_deadline(on_expiry, generation);
325
+ }
95
326
  });
327
+ return deadline_state::armed;
328
+ }
329
+
330
+ [[nodiscard]] auto terminal_error() -> std::error_code
331
+ {
332
+ const std::scoped_lock lock{ mutex_ };
333
+ return final_ec_;
96
334
  }
97
335
 
98
336
  private:
337
+ std::mutex mutex_{};
338
+ // Guarded by mutex_: written from the read completion (session strand) and from close()
339
+ // (off-strand). cached_data_/cached_chunk_size_ are logically single-consumer but are read under
340
+ // the same lock for uniformity. deadline_ is guarded because an asio timer may not be armed and
341
+ // cancelled concurrently; deadline_generation_ supersedes a queued completion.
99
342
  std::shared_ptr<http_session> session_;
343
+ // hostname:port of the session the body was built with, kept for logging after session_ is
344
+ // released.
345
+ std::string endpoint_;
100
346
  std::string cached_data_;
101
347
  std::error_code final_ec_;
102
348
  asio::steady_timer deadline_;
103
- std::atomic_bool reading_complete_{ false };
349
+ std::uint64_t deadline_generation_{ 0 };
350
+ bool reading_complete_{ false };
351
+ bool closed_{ false };
352
+ std::size_t cached_chunk_size_{ 0 };
104
353
  };
105
354
 
106
355
  http_streaming_response_body::http_streaming_response_body(asio::io_context& io,
107
356
  std::shared_ptr<http_session> session,
108
357
  std::string cached_data,
109
- bool reading_complete)
358
+ bool reading_complete,
359
+ std::size_t cached_chunk_size)
110
360
  : impl_{ std::make_shared<http_streaming_response_body_impl>(io,
111
361
  std::move(session),
112
362
  std::move(cached_data),
113
- reading_complete) }
363
+ reading_complete,
364
+ cached_chunk_size) }
114
365
  {
115
366
  }
116
367
 
117
368
  void
118
369
  http_streaming_response_body::next(
119
- utils::movable_function<void(std::string, std::error_code)>&& callback)
370
+ utils::movable_function<void(std::string, bool, std::error_code)>&& callback)
120
371
  {
121
372
  impl_->next(std::move(callback));
122
373
  }
@@ -127,11 +378,18 @@ http_streaming_response_body::close(std::error_code ec)
127
378
  impl_->close(ec);
128
379
  }
129
380
 
130
- void
381
+ auto
131
382
  http_streaming_response_body::set_deadline(
132
- std::chrono::time_point<std::chrono::steady_clock> deadline_tp)
383
+ std::chrono::time_point<std::chrono::steady_clock> deadline_tp,
384
+ deadline_terminal on_expiry) -> deadline_state
133
385
  {
134
- impl_->set_deadline(deadline_tp);
386
+ return impl_->set_deadline(deadline_tp, on_expiry);
387
+ }
388
+
389
+ void
390
+ http_streaming_response_body::cancel_deadline()
391
+ {
392
+ return impl_->cancel_deadline();
135
393
  }
136
394
 
137
395
  class http_streaming_response_impl
@@ -140,11 +398,13 @@ public:
140
398
  http_streaming_response_impl(std::uint32_t status_code,
141
399
  std::string status_message,
142
400
  std::map<std::string, std::string> headers,
143
- http_streaming_response_body body)
401
+ http_streaming_response_body body,
402
+ http_dispatch_info dispatch_info)
144
403
  : status_code_{ status_code }
145
404
  , status_message_{ std::move(status_message) }
146
405
  , headers_{ std::move(headers) }
147
406
  , body_{ std::move(body) }
407
+ , dispatch_info_{ std::move(dispatch_info) }
148
408
  {
149
409
  }
150
410
 
@@ -176,23 +436,42 @@ public:
176
436
  return false;
177
437
  }
178
438
 
439
+ [[nodiscard]] auto dispatch_info() const -> const http_dispatch_info&
440
+ {
441
+ return dispatch_info_;
442
+ }
443
+
179
444
  private:
180
445
  std::uint32_t status_code_;
181
446
  std::string status_message_;
182
447
  std::map<std::string, std::string> headers_;
183
448
  http_streaming_response_body body_;
449
+ http_dispatch_info dispatch_info_;
184
450
  };
185
451
 
186
452
  http_streaming_response::http_streaming_response(
187
453
  asio::io_context& io,
188
454
  const couchbase::core::io::http_streaming_parser& parser,
189
455
  std::shared_ptr<http_session> session)
190
- : impl_{ std::make_shared<http_streaming_response_impl>(
191
- parser.status_code,
192
- parser.status_message,
193
- parser.headers,
194
- http_streaming_response_body{ io, std::move(session), parser.body_chunk, parser.complete }) }
456
+ : impl_{ nullptr }
195
457
  {
458
+ http_dispatch_info dispatch_info{};
459
+ if (session) {
460
+ // Taken from the session's http_context, the same source io::http_command stamps onto a
461
+ // buffered response, so both paths report the identical endpoint. The port is already a
462
+ // std::uint16_t there; http_session::port() is that same value rendered as a string for
463
+ // resolving and for the Host header, so parsing it back would only reintroduce a conversion.
464
+ dispatch_info.hostname = session->http_context().hostname;
465
+ dispatch_info.port = session->http_context().port;
466
+ dispatch_info.remote_address = session->remote_address();
467
+ dispatch_info.local_address = session->local_address();
468
+ }
469
+ impl_ = std::make_shared<http_streaming_response_impl>(
470
+ parser.status_code,
471
+ parser.status_message,
472
+ parser.headers,
473
+ http_streaming_response_body{ io, std::move(session), parser.body_chunk, parser.complete },
474
+ std::move(dispatch_info));
196
475
  }
197
476
 
198
477
  auto
@@ -224,4 +503,10 @@ http_streaming_response::must_close_connection() const -> bool
224
503
  {
225
504
  return impl_->must_close_connection();
226
505
  }
506
+
507
+ auto
508
+ http_streaming_response::dispatch_info() const -> const http_dispatch_info&
509
+ {
510
+ return impl_->dispatch_info();
511
+ }
227
512
  } // namespace couchbase::core::io
@@ -18,10 +18,12 @@
18
18
 
19
19
  #include <couchbase/error_codes.hxx>
20
20
 
21
+ #include "core/io/stream_deadline.hxx"
21
22
  #include "core/utils/movable_function.hxx"
22
23
 
23
24
  #include <asio/steady_timer.hpp>
24
25
 
26
+ #include <cstddef>
25
27
  #include <cstdint>
26
28
  #include <map>
27
29
  #include <memory>
@@ -30,6 +32,7 @@
30
32
 
31
33
  namespace couchbase::core::io
32
34
  {
35
+
33
36
  class http_session;
34
37
  struct http_streaming_parser;
35
38
  class http_streaming_response_body_impl;
@@ -41,10 +44,30 @@ public:
41
44
  http_streaming_response_body(asio::io_context& io,
42
45
  std::shared_ptr<http_session> session,
43
46
  std::string cached_data = {},
44
- bool reading_complete = false);
47
+ bool reading_complete = false,
48
+ std::size_t cached_chunk_size = 0);
45
49
 
46
- void set_deadline(std::chrono::time_point<std::chrono::steady_clock> deadline_tp);
47
- void next(utils::movable_function<void(std::string, std::error_code)>&& callback);
50
+ // Closes the body with `on_expiry` at `deadline_tp`, whether or not a read is in flight. A
51
+ // deadline already in the past fires as soon as the io_context runs it. Re-arming before expiry
52
+ // replaces the previous deadline. Once the body has ended, by expiry or otherwise, a further
53
+ // call arms nothing and reports body_already_ended.
54
+ //
55
+ // Callable from any thread. Arming, cancelling and the terminal transition share one lock. An
56
+ // expiry already queued when that lock is taken is superseded, not delivered.
57
+ [[nodiscard]] auto set_deadline(std::chrono::time_point<std::chrono::steady_clock> deadline_tp,
58
+ deadline_terminal on_expiry) -> deadline_state;
59
+ // Supersedes an armed deadline without ending the body, under the same lock as set_deadline.
60
+ // For a caller that must post its own teardown: stopping the deadline here first keeps a cancel
61
+ // from losing to an expiry that lands before the posted work runs.
62
+ void cancel_deadline();
63
+ // Delivers the next body chunk. The bool argument is `has_more`: true while the response is
64
+ // still in progress (an empty chunk with has_more==true simply carried no body bytes, e.g. a
65
+ // read consumed by HTTP chunk framing), false once the stream has ended. Consumers must key
66
+ // end-of-stream off this flag, never off an empty data string.
67
+ //
68
+ // The error_code next() reports is the reason the body was closed, not the error the read
69
+ // returned: read_some reports request_canceled for every aborted read.
70
+ void next(utils::movable_function<void(std::string, bool, std::error_code)>&& callback);
48
71
  void close(std::error_code ec = couchbase::errc::common::request_canceled);
49
72
 
50
73
  private:
@@ -53,6 +76,16 @@ private:
53
76
 
54
77
  class http_streaming_response_impl;
55
78
 
79
+ // Where a streaming response was dispatched, captured at construction rather than held as a session
80
+ // reference: an error context is built after the stream has been torn down, by which point the
81
+ // session may already be back in the keep-alive pool or gone.
82
+ struct http_dispatch_info {
83
+ std::string hostname{};
84
+ std::uint16_t port{};
85
+ std::string remote_address{};
86
+ std::string local_address{};
87
+ };
88
+
56
89
  class http_streaming_response
57
90
  {
58
91
  public:
@@ -66,6 +99,7 @@ public:
66
99
  [[nodiscard]] auto headers() const -> const std::map<std::string, std::string>&;
67
100
  [[nodiscard]] auto body() -> http_streaming_response_body&;
68
101
  [[nodiscard]] auto must_close_connection() const -> bool;
102
+ [[nodiscard]] auto dispatch_info() const -> const http_dispatch_info&;
69
103
 
70
104
  private:
71
105
  std::shared_ptr<http_streaming_response_impl> impl_;