couchbase 3.8.2 → 3.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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 +130 -21
  9. data/ext/couchbase/cmake/APKBUILD.in +25 -4
  10. data/ext/couchbase/cmake/Bundler.cmake +143 -34
  11. data/ext/couchbase/cmake/CPM.cmake +126 -12
  12. data/ext/couchbase/cmake/Cache.cmake +7 -1
  13. data/ext/couchbase/cmake/CompilerOptions.cmake +1 -1
  14. data/ext/couchbase/cmake/CompilerWarnings.cmake +17 -6
  15. data/ext/couchbase/cmake/GrpcProtobuf.cmake +481 -0
  16. data/ext/couchbase/cmake/OpenSSL.cmake +10 -3
  17. data/ext/couchbase/cmake/Packaging.cmake +519 -65
  18. data/ext/couchbase/cmake/Protostellar.cmake +250 -0
  19. data/ext/couchbase/cmake/Sanitizers.cmake +61 -50
  20. data/ext/couchbase/cmake/StaticAnalyzers.cmake +16 -5
  21. data/ext/couchbase/cmake/TestFramework.cmake +283 -0
  22. data/ext/couchbase/cmake/TestFrameworkAddTests.cmake +51 -0
  23. data/ext/couchbase/cmake/Testing.cmake +27 -9
  24. data/ext/couchbase/cmake/ThirdPartyDependencies.cmake +141 -8
  25. data/ext/couchbase/cmake/VersionInfo.cmake +50 -0
  26. data/ext/couchbase/cmake/build_config.hxx.in +2 -1
  27. data/ext/couchbase/cmake/couchbase-cxx-client.spec.in +101 -23
  28. data/ext/couchbase/cmake/couchbase_cxx_client_static-config.cmake.in +7 -0
  29. data/ext/couchbase/cmake/couchbase_cxx_client_static.pc.in +2 -0
  30. data/ext/couchbase/cmake/gRPC_Generate.cmake +58 -0
  31. data/ext/couchbase/cmake/protobuf_Generate.cmake +69 -0
  32. data/ext/couchbase/cmake/require-git-checkout.cmake +25 -0
  33. data/ext/couchbase/cmake/tarball_glob.txt +49 -0
  34. data/ext/couchbase/core/agent_group.cxx +0 -8
  35. data/ext/couchbase/core/agent_group.hxx +0 -4
  36. data/ext/couchbase/core/analytics_stream.cxx +314 -0
  37. data/ext/couchbase/core/analytics_stream.hxx +129 -0
  38. data/ext/couchbase/core/analytics_stream_component.cxx +245 -0
  39. data/ext/couchbase/core/analytics_stream_component.hxx +67 -0
  40. data/ext/couchbase/core/app_telemetry_meter.cxx +25 -3
  41. data/ext/couchbase/core/app_telemetry_meter.hxx +77 -0
  42. data/ext/couchbase/core/app_telemetry_reporter.cxx +7 -6
  43. data/ext/couchbase/core/bucket.cxx +381 -87
  44. data/ext/couchbase/core/bucket.hxx +105 -25
  45. data/ext/couchbase/core/bucket_unit_test_api.hxx +57 -0
  46. data/ext/couchbase/core/capella.hxx +91 -0
  47. data/ext/couchbase/core/cluster.cxx +1554 -398
  48. data/ext/couchbase/core/cluster.hxx +39 -8
  49. data/ext/couchbase/core/cluster_agent.cxx +0 -8
  50. data/ext/couchbase/core/cluster_agent.hxx +0 -4
  51. data/ext/couchbase/core/cluster_options.hxx +26 -3
  52. data/ext/couchbase/core/collections_component.cxx +39 -15
  53. data/ext/couchbase/core/config_profiles.cxx +1 -1
  54. data/ext/couchbase/core/crud_component.cxx +2 -2
  55. data/ext/couchbase/core/crud_options.hxx +34 -0
  56. data/ext/couchbase/core/crypto/cbcrypto.cc +18 -13
  57. data/ext/couchbase/core/document_id_redaction.hxx +81 -0
  58. data/ext/couchbase/core/error_context/base_error_context.hxx +7 -6
  59. data/ext/couchbase/core/error_context/key_value.cxx +25 -2
  60. data/ext/couchbase/core/error_context/key_value.hxx +60 -33
  61. data/ext/couchbase/core/error_context/key_value_error_context.hxx +16 -15
  62. data/ext/couchbase/core/error_context/search.hxx +5 -0
  63. data/ext/couchbase/core/error_context/search_json.hxx +3 -0
  64. data/ext/couchbase/core/error_context/subdocument_error_context.hxx +19 -18
  65. data/ext/couchbase/core/error_context/view.hxx +5 -0
  66. data/ext/couchbase/core/free_form_http_request.cxx +237 -4
  67. data/ext/couchbase/core/free_form_http_request.hxx +41 -8
  68. data/ext/couchbase/core/http_component.cxx +300 -284
  69. data/ext/couchbase/core/http_component.hxx +0 -4
  70. data/ext/couchbase/core/impl/analytics.cxx +38 -5
  71. data/ext/couchbase/core/impl/analytics.hxx +34 -0
  72. data/ext/couchbase/core/impl/bootstrap_error.hxx +0 -3
  73. data/ext/couchbase/core/impl/collection.cxx +215 -202
  74. data/ext/couchbase/core/impl/collection_manager.cxx +1 -1
  75. data/ext/couchbase/core/impl/dns_srv_tracker.cxx +45 -18
  76. data/ext/couchbase/core/impl/error.cxx +22 -2
  77. data/ext/couchbase/core/impl/internal_analytics_stream_result.hxx +111 -0
  78. data/ext/couchbase/core/impl/internal_query_stream_result.hxx +110 -0
  79. data/ext/couchbase/core/impl/invoke_with_node_id.hxx +15 -7
  80. data/ext/couchbase/core/impl/key_value_error_category.cxx +6 -0
  81. data/ext/couchbase/core/impl/lookup_in_replica.cxx +10 -10
  82. data/ext/couchbase/core/impl/lookup_in_replica.hxx +4 -4
  83. data/ext/couchbase/core/impl/node_id.cxx +49 -13
  84. data/ext/couchbase/core/impl/observability_recorder.cxx +12 -2
  85. data/ext/couchbase/core/impl/observe_poll.cxx +63 -23
  86. data/ext/couchbase/core/impl/public_analytics_stream_result.cxx +372 -0
  87. data/ext/couchbase/core/impl/public_bucket.cxx +48 -2
  88. data/ext/couchbase/core/impl/public_cluster.cxx +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 +59 -5
  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 +3 -29
  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 +32 -0
  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,314 @@
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 "analytics_stream.hxx"
19
+
20
+ #include <couchbase/error_codes.hxx>
21
+
22
+ #include "core/io/http_error.hxx"
23
+ #include "core/operations/analytics_response_parsing.hxx"
24
+ #include "free_form_http_request.hxx"
25
+ #include "logger/logger.hxx"
26
+ #include "row_streamer.hxx"
27
+ #include "utils/json.hxx"
28
+ #include "utils/movable_function.hxx"
29
+
30
+ #include <tao/json/value.hpp>
31
+
32
+ #include <chrono>
33
+ #include <memory>
34
+ #include <mutex>
35
+ #include <optional>
36
+ #include <string>
37
+ #include <system_error>
38
+ #include <utility>
39
+
40
+ namespace couchbase::core
41
+ {
42
+ namespace
43
+ {
44
+ constexpr auto ANALYTICS_RESULTS_POINTER = "/results/^";
45
+
46
+ // Normalize a bounded-lexer failure (malformed / oversized JSON) to errc::common::parsing_failure,
47
+ // matching the buffered analytics_query() path and row_streamer's own truncated-body terminal, so
48
+ // the public streaming error contract is consistent.
49
+ auto
50
+ normalize_stream_error(std::error_code ec) -> std::error_code
51
+ {
52
+ if (ec.category() == couchbase::core::impl::streaming_json_lexer_category()) {
53
+ return errc::common::parsing_failure;
54
+ }
55
+ return ec;
56
+ }
57
+ } // namespace
58
+
59
+ namespace
60
+ {
61
+ // Lift the service-reported diagnostics out of a parsed preamble/trailer. Called for both sections,
62
+ // so whichever one carried the failure is what the error context ends up describing.
63
+ auto
64
+ extract_error_details(const operations::analytics_response::analytics_meta_data& meta,
65
+ std::string raw) -> stream_error_details
66
+ {
67
+ stream_error_details details{};
68
+ details.client_context_id = meta.client_context_id;
69
+ // Retained whether or not the section reports an error: it becomes error_context::http_body,
70
+ // which the buffered path fills for every response, and even an error-free envelope names the
71
+ // request the service logged.
72
+ details.raw = std::move(raw);
73
+ if (!meta.errors.empty()) {
74
+ details.first_error_code = meta.errors.front().code;
75
+ details.first_error_message = meta.errors.front().message;
76
+ }
77
+ return details;
78
+ }
79
+ } // namespace
80
+
81
+ class analytics_stream_impl : public std::enable_shared_from_this<analytics_stream_impl>
82
+ {
83
+ public:
84
+ analytics_stream_impl(asio::io_context& io,
85
+ http_response_body body,
86
+ row_streamer_options options,
87
+ std::uint32_t http_status)
88
+ : streamer_{ io, std::move(body), ANALYTICS_RESULTS_POINTER, options }
89
+ , http_status_{ http_status }
90
+ {
91
+ }
92
+ analytics_stream_impl(const analytics_stream_impl&) = delete;
93
+ analytics_stream_impl(analytics_stream_impl&&) = delete;
94
+ auto operator=(const analytics_stream_impl&) -> analytics_stream_impl& = delete;
95
+ auto operator=(analytics_stream_impl&&) -> analytics_stream_impl& = delete;
96
+
97
+ // Runs with no handle left, no pull in flight and the preamble delivered: each pull, and start()
98
+ // until the preamble, holds this object.
99
+ //
100
+ // Cancels unless a pull delivered the terminal of a completed JSON document. Without the cancel,
101
+ // the read-ahead keeps the body and its session alive:
102
+ // - through a read in flight, until the idle timer fires or the cluster closes;
103
+ // - through a row send queued on a full channel, indefinitely.
104
+ // The cancel stops the connection only while the body is still being read. A body read to its
105
+ // end has already checked the connection in.
106
+ //
107
+ // After a completed document the read-ahead reads the trailing bytes and the connection is
108
+ // checked in, or the idle timer ends the body.
109
+ ~analytics_stream_impl()
110
+ {
111
+ if (!document_completed_) {
112
+ streamer_.cancel();
113
+ }
114
+ }
115
+
116
+ void start(utils::movable_function<void(std::error_code)>&& on_ready)
117
+ {
118
+ streamer_.start([self = shared_from_this(), on_ready = std::move(on_ready)](
119
+ std::string preamble, std::error_code ec) mutable {
120
+ if (ec) {
121
+ return on_ready(normalize_stream_error(
122
+ io::invalid_argument_for_undecodable_400(ec, self->http_status_, {})));
123
+ }
124
+ operations::analytics_response::analytics_meta_data meta{};
125
+ try {
126
+ meta = operations::parse_analytics_meta(utils::json::parse(preamble));
127
+ } catch (const std::exception&) {
128
+ {
129
+ // Keep the unparseable preamble: it is the only diagnostic there is for a malformed
130
+ // response, and the buffered path reports the raw body in the same situation.
131
+ const std::scoped_lock lock{ self->mutex_ };
132
+ self->error_details_.raw = std::move(preamble);
133
+ }
134
+ return on_ready(io::invalid_argument_for_undecodable_400(
135
+ errc::common::parsing_failure, self->http_status_, {}));
136
+ }
137
+ {
138
+ const std::scoped_lock lock{ self->mutex_ };
139
+ self->signature_ = meta.signature;
140
+ self->error_details_ = extract_error_details(meta, std::move(preamble));
141
+ }
142
+ // An upfront error is rare for the streaming path (status/errors usually arrive in the
143
+ // trailer). The preamble status is typically "running", so only surface an early error when
144
+ // the preamble already carries error entries.
145
+ std::error_code early_error{};
146
+ if (!meta.errors.empty()) {
147
+ early_error = operations::map_analytics_error(meta, self->http_status_);
148
+ }
149
+ on_ready(early_error);
150
+ });
151
+ }
152
+
153
+ void next_row(
154
+ utils::movable_function<void(std::optional<std::string>, std::error_code)>&& handler)
155
+ {
156
+ // Terminal-sticky: once the stream has ended, re-deliver the stored terminal on every
157
+ // subsequent pull instead of issuing another receive that would park forever on the
158
+ // drained-but-open row channel. The public handle guards this too, but a core::-direct consumer
159
+ // (e.g. a wrapper) relies on this being safe.
160
+ std::optional<std::error_code> sticky_terminal;
161
+ {
162
+ const std::scoped_lock lock{ mutex_ };
163
+ if (terminal_reached_) {
164
+ sticky_terminal = terminal_ec_;
165
+ }
166
+ }
167
+ if (sticky_terminal) {
168
+ // Deliver outside the lock: the handler typically issues the next pull, which re-enters
169
+ // next_row and would re-lock the non-recursive mutex_ and deadlock if invoked while held.
170
+ return handler(std::nullopt, *sticky_terminal);
171
+ }
172
+ streamer_.next_row([self = shared_from_this(),
173
+ handler = std::move(handler)](std::string row, std::error_code ec) mutable {
174
+ if (!row.empty()) {
175
+ return handler(std::move(row), {});
176
+ }
177
+ // Empty row marks the end of the stream. Classify the terminal, remember it (so later pulls
178
+ // are served the sticky terminal above), then deliver.
179
+ std::error_code terminal_ec{};
180
+ if (ec) {
181
+ // Normalize a mid-document lexer failure to parsing_failure, matching the buffered path.
182
+ terminal_ec = normalize_stream_error(
183
+ io::invalid_argument_for_undecodable_400(ec, self->http_status_, {}));
184
+ } else if (auto raw_meta = self->streamer_.metadata(); !raw_meta.has_value()) {
185
+ // A clean end without reconstructed metadata violates the row_streamer invariant; surface
186
+ // it rather than silently reporting success with no metadata.
187
+ CB_LOG_WARNING("analytics_stream: end reached but row_streamer returned no metadata");
188
+ terminal_ec = errc::common::internal_server_failure;
189
+ } else {
190
+ try {
191
+ auto meta = operations::parse_analytics_meta(utils::json::parse(raw_meta.value()));
192
+ terminal_ec = operations::map_analytics_error(meta, self->http_status_);
193
+ const std::scoped_lock lock{ self->mutex_ };
194
+ // The trailer is where a request that failed part-way reports itself, so its diagnostics
195
+ // supersede the preamble's for the terminal error.
196
+ self->error_details_ = extract_error_details(meta, std::move(raw_meta.value()));
197
+ self->meta_data_ = std::move(meta);
198
+ } catch (const std::exception&) {
199
+ terminal_ec = io::invalid_argument_for_undecodable_400(
200
+ errc::common::parsing_failure, self->http_status_, {});
201
+ const std::scoped_lock lock{ self->mutex_ };
202
+ self->error_details_.raw = std::move(raw_meta.value());
203
+ }
204
+ }
205
+ {
206
+ const std::scoped_lock lock{ self->mutex_ };
207
+ self->terminal_reached_ = true;
208
+ self->terminal_ec_ = terminal_ec;
209
+ self->document_completed_ = !ec;
210
+ }
211
+ handler(std::nullopt, terminal_ec);
212
+ });
213
+ }
214
+
215
+ [[nodiscard]] auto signature() const -> std::optional<std::string>
216
+ {
217
+ const std::scoped_lock lock{ mutex_ };
218
+ return signature_;
219
+ }
220
+
221
+ [[nodiscard]] auto meta_data() const
222
+ -> std::optional<operations::analytics_response::analytics_meta_data>
223
+ {
224
+ const std::scoped_lock lock{ mutex_ };
225
+ return meta_data_;
226
+ }
227
+
228
+ [[nodiscard]] auto error_details() const -> stream_error_details
229
+ {
230
+ const std::scoped_lock lock{ mutex_ };
231
+ return error_details_;
232
+ }
233
+
234
+ auto set_deadline(std::chrono::time_point<std::chrono::steady_clock> deadline_tp)
235
+ -> io::deadline_state
236
+ {
237
+ return streamer_.set_deadline(deadline_tp);
238
+ }
239
+
240
+ void cancel()
241
+ {
242
+ streamer_.cancel();
243
+ }
244
+
245
+ private:
246
+ row_streamer streamer_;
247
+ std::uint32_t http_status_{};
248
+ mutable std::mutex mutex_{};
249
+ std::optional<std::string> signature_{};
250
+ std::optional<operations::analytics_response::analytics_meta_data> meta_data_{};
251
+ stream_error_details error_details_{};
252
+ // Terminal-sticky state (guarded by mutex_): once next_row reports the terminal, it is remembered
253
+ // so every later pull re-delivers it instead of parking on the drained channel.
254
+ bool terminal_reached_{ false };
255
+ std::error_code terminal_ec_{};
256
+ // Set when a pull delivers a terminal with no streamer error: the lexer completed the JSON
257
+ // document. Written under mutex_. The destructor reads it unlocked: the release of the last
258
+ // shared_ptr orders that read after every write.
259
+ bool document_completed_{ false };
260
+ };
261
+
262
+ analytics_stream::analytics_stream(asio::io_context& io,
263
+ http_response_body body,
264
+ row_streamer_options options,
265
+ std::uint32_t http_status)
266
+ : impl_{ std::make_shared<analytics_stream_impl>(io, std::move(body), options, http_status) }
267
+ {
268
+ }
269
+
270
+ void
271
+ analytics_stream::start(utils::movable_function<void(std::error_code)>&& on_ready)
272
+ {
273
+ impl_->start(std::move(on_ready));
274
+ }
275
+
276
+ void
277
+ analytics_stream::next_row(
278
+ utils::movable_function<void(std::optional<std::string>, std::error_code)>&& handler)
279
+ {
280
+ impl_->next_row(std::move(handler));
281
+ }
282
+
283
+ auto
284
+ analytics_stream::signature() const -> std::optional<std::string>
285
+ {
286
+ return impl_->signature();
287
+ }
288
+
289
+ auto
290
+ analytics_stream::error_details() const -> stream_error_details
291
+ {
292
+ return impl_->error_details();
293
+ }
294
+
295
+ auto
296
+ analytics_stream::meta_data() const
297
+ -> std::optional<operations::analytics_response::analytics_meta_data>
298
+ {
299
+ return impl_->meta_data();
300
+ }
301
+
302
+ auto
303
+ analytics_stream::set_deadline(std::chrono::time_point<std::chrono::steady_clock> deadline_tp)
304
+ -> io::deadline_state
305
+ {
306
+ return impl_->set_deadline(deadline_tp);
307
+ }
308
+
309
+ void
310
+ analytics_stream::cancel()
311
+ {
312
+ impl_->cancel();
313
+ }
314
+ } // namespace couchbase::core
@@ -0,0 +1,129 @@
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/io/stream_deadline.hxx"
21
+ #include "core/operations/document_analytics.hxx"
22
+ #include "row_streamer.hxx"
23
+ #include "stream_error_details.hxx"
24
+ #include "utils/movable_function.hxx"
25
+
26
+ #include <chrono>
27
+ #include <cstdint>
28
+ #include <memory>
29
+ #include <optional>
30
+ #include <string>
31
+ #include <system_error>
32
+
33
+ namespace asio
34
+ {
35
+ class io_context;
36
+ } // namespace asio
37
+
38
+ namespace couchbase::core
39
+ {
40
+ class analytics_stream_impl;
41
+ class http_response_body;
42
+
43
+ /**
44
+ * L3 streaming state machine for analytics responses.
45
+ *
46
+ * Mirrors query_stream but with analytics-specific semantics: it wraps a row_streamer (pointed at
47
+ * "/results/^", the same pointer the buffered analytics path uses) and adds preamble parsing
48
+ * (signature + upfront error detection), late trailer metadata, and terminal error classification
49
+ * via map_analytics_error.
50
+ */
51
+ class analytics_stream
52
+ {
53
+ public:
54
+ /**
55
+ * Constructs an empty handle (no underlying stream). Used to carry the "no stream" outcome on
56
+ * the error path of the dispatch; none of the streaming methods may be called on it.
57
+ */
58
+ analytics_stream() = default;
59
+
60
+ /**
61
+ * http_status is the status of the response the body belongs to; an HTTP 400 that matches no
62
+ * specific error is reported as invalid_argument.
63
+ */
64
+ analytics_stream(asio::io_context& io,
65
+ http_response_body body,
66
+ row_streamer_options options = {},
67
+ std::uint32_t http_status = 0);
68
+
69
+ /**
70
+ * Starts the stream. Resolves once the preamble has been parsed. The early_error is set when
71
+ * the response carried an upfront error (or the preamble failed to parse).
72
+ */
73
+ void start(utils::movable_function<void(std::error_code early_error)>&& on_ready);
74
+
75
+ /**
76
+ * Service-reported diagnostics from the most recently parsed JSON section — the preamble after
77
+ * start(), the trailer once the terminal has been reached. The dispatch layer stamps these into
78
+ * the error context so a streaming failure reports the same detail as a buffered one.
79
+ */
80
+ [[nodiscard]] auto error_details() const -> stream_error_details;
81
+
82
+ /**
83
+ * Retrieves the next row. A populated row is delivered with a falsy error_code. An empty row
84
+ * (std::nullopt) signals the end: a falsy error_code means a clean success end, a truthy
85
+ * error_code means a trailing analytics/transport error.
86
+ */
87
+ void next_row(
88
+ utils::movable_function<void(std::optional<std::string> row, std::error_code)>&& handler);
89
+
90
+ /**
91
+ * Signature captured from the preamble (available after start resolves).
92
+ */
93
+ [[nodiscard]] auto signature() const -> std::optional<std::string>;
94
+
95
+ /**
96
+ * Trailer metadata. Valid only after the stream has reached its end.
97
+ */
98
+ [[nodiscard]] auto meta_data() const
99
+ -> std::optional<operations::analytics_response::analytics_meta_data>;
100
+
101
+ /**
102
+ * Closes the HTTP body at `deadline_tp`, whether or not a row is being pulled, releasing the
103
+ * connection where one is still held. A response received in full holds none: once its bytes
104
+ * have also been parsed the call arms nothing and reports body_already_ended, and while bytes
105
+ * remain unparsed it ends the body, dropping those and reporting the timeout. Rows already
106
+ * parsed stay consumable either way. The inter-read idle timer is armed only while a socket
107
+ * read is in flight, and a consumer stopped above the high-water mark has none.
108
+ *
109
+ * The terminal reaches the consumer through a pull. A pull outstanding at expiry completes
110
+ * with the timeout. A read that completes the response is the exception: the whole response
111
+ * arrived, so those bytes are delivered as a clean end. Otherwise the next pull reports the
112
+ * terminal, after any rows already buffered.
113
+ *
114
+ * Optional. The terminal is unambiguous_timeout for a read-only request, ambiguous_timeout
115
+ * otherwise. Re-arming before expiry replaces the deadline. Once the stream has ended a
116
+ * further call arms nothing and reports body_already_ended. Callable from any thread.
117
+ */
118
+ [[nodiscard]] auto set_deadline(std::chrono::time_point<std::chrono::steady_clock> deadline_tp)
119
+ -> io::deadline_state;
120
+
121
+ /**
122
+ * Cancels the stream & closes the HTTP connection.
123
+ */
124
+ void cancel();
125
+
126
+ private:
127
+ std::shared_ptr<analytics_stream_impl> impl_;
128
+ };
129
+ } // namespace couchbase::core
@@ -0,0 +1,245 @@
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 "analytics_stream_component.hxx"
19
+
20
+ #include "free_form_http_request.hxx"
21
+ #include "http_component.hxx"
22
+ #include "impl/bootstrap_error.hxx"
23
+ #include "operations/document_analytics.hxx"
24
+ #include "platform/uuid.h"
25
+ #include "service_type.hxx"
26
+ #include "utils/json.hxx"
27
+
28
+ #include <couchbase/error_codes.hxx>
29
+
30
+ #include <asio/io_context.hpp>
31
+ #include <spdlog/fmt/bundled/core.h>
32
+ #include <tao/json/value.hpp>
33
+
34
+ #include <chrono>
35
+ #include <memory>
36
+ #include <mutex>
37
+ #include <string>
38
+ #include <system_error>
39
+ #include <utility>
40
+
41
+ namespace couchbase::core
42
+ {
43
+ namespace
44
+ {
45
+ auto
46
+ to_error_code(std::error_code err) -> std::error_code
47
+ {
48
+ return err;
49
+ }
50
+
51
+ auto
52
+ build_streaming_analytics_body(const operations::analytics_request& request,
53
+ const std::string& client_context_id,
54
+ std::chrono::milliseconds timeout) -> std::string
55
+ {
56
+ tao::json::value body{};
57
+
58
+ // The remaining options are identical to the buffered analytics path; share one encoder so the
59
+ // two stay byte-for-byte consistent. This runs first so the control fields set below always win
60
+ // over any same-named `raw` entry a caller may have supplied.
61
+ operations::encode_analytics_options(body, request);
62
+
63
+ body["client_context_id"] = client_context_id;
64
+ body["statement"] = request.statement;
65
+ // The server-side timeout is sent unmodified here — unlike the query path, which shaves 500ms so
66
+ // it observes the engine's timeout before its own. This mirrors the buffered analytics path
67
+ // (document_analytics), which likewise does not tighten the engine deadline; keep them
68
+ // consistent.
69
+ body["timeout"] = fmt::format("{}ms", timeout.count());
70
+
71
+ return utils::json::generate(body);
72
+ }
73
+ } // namespace
74
+
75
+ class analytics_stream_component_impl
76
+ : public std::enable_shared_from_this<analytics_stream_component_impl>
77
+ {
78
+ public:
79
+ analytics_stream_component_impl(asio::io_context& io,
80
+ http_component http,
81
+ std::chrono::milliseconds default_timeout,
82
+ row_streamer_options streaming_options)
83
+ : io_{ io }
84
+ , http_{ std::move(http) }
85
+ , default_timeout_{ default_timeout }
86
+ , streaming_options_{ streaming_options }
87
+ {
88
+ }
89
+
90
+ void execute(operations::analytics_request request,
91
+ analytics_stream_component::handler_type&& handler)
92
+ {
93
+ const std::string client_context_id =
94
+ request.client_context_id.value_or(uuid::to_string(uuid::random()));
95
+ const std::chrono::milliseconds timeout = request.timeout.value_or(default_timeout_);
96
+
97
+ http_request http_req{ service_type::analytics, "POST" };
98
+ http_req.path = "/query/service";
99
+
100
+ // Everything about the request that an error context reports, known before dispatch. The
101
+ // response-side fields (HTTP status, the service's first error, the endpoint) are filled in as
102
+ // they become available, so a failure at any stage carries as much detail as the buffered path.
103
+ // Held behind a shared_ptr, not by value: it carries the encoded request body in
104
+ // `parameters`, and both the dispatch callback and the synchronous dispatch-failure branch
105
+ // below need it, so sharing avoids deep-copying that body on every streaming request.
106
+ auto base_ctx = std::make_shared<error_context::analytics>();
107
+ base_ctx->client_context_id = client_context_id;
108
+ base_ctx->statement = request.statement;
109
+ base_ctx->method = http_req.method;
110
+ base_ctx->path = http_req.path;
111
+
112
+ try {
113
+ http_req.body = build_streaming_analytics_body(request, client_context_id, timeout);
114
+ } catch (const std::exception&) {
115
+ // A caller-supplied raw/parameter value that is not valid JSON fails encoding; surface it as
116
+ // invalid_argument rather than letting the exception escape the public API.
117
+ base_ctx->ec = errc::common::invalid_argument;
118
+ return handler({}, std::move(*base_ctx));
119
+ }
120
+ base_ctx->parameters = http_req.body;
121
+ http_req.timeout = timeout;
122
+ http_req.client_context_id = client_context_id;
123
+ http_req.is_read_only = request.readonly;
124
+ http_req.headers["connection"] = "keep-alive";
125
+ http_req.headers["content-type"] = "application/json";
126
+ if (request.priority) {
127
+ http_req.headers["analytics-priority"] = "-1";
128
+ }
129
+ if (request.parent_span) {
130
+ http_req.parent_span = request.parent_span;
131
+ }
132
+
133
+ auto callback_state = std::make_shared<callback_state_type>(std::move(handler));
134
+
135
+ using dispatch_error = std::error_code;
136
+
137
+ const bool read_only = request.readonly;
138
+ auto op = http_.do_http_request(
139
+ http_req,
140
+ [self = shared_from_this(), callback_state, timeout, read_only, base_ctx](
141
+ http_response resp, dispatch_error err) mutable {
142
+ if (auto ec = to_error_code(err)) {
143
+ base_ctx->ec = ec;
144
+ self->invoke(callback_state, {}, std::move(*base_ctx));
145
+ return;
146
+ }
147
+ // The response is in hand, so the context can now name the endpoint and HTTP status.
148
+ base_ctx->http_status = resp.status_code();
149
+ const auto dispatch_info = resp.dispatch_info();
150
+ base_ctx->hostname = dispatch_info.hostname;
151
+ base_ctx->port = dispatch_info.port;
152
+ if (!dispatch_info.remote_address.empty()) {
153
+ base_ctx->last_dispatched_to = dispatch_info.remote_address;
154
+ }
155
+ if (!dispatch_info.local_address.empty()) {
156
+ base_ctx->last_dispatched_from = dispatch_info.local_address;
157
+ }
158
+ // Use the cluster's streaming tuning, but bound the server between reads with this
159
+ // request's timeout: if it stalls mid-body the stream times out rather than hanging next()
160
+ // forever (the request timeout is otherwise released once headers arrive).
161
+ auto options = self->streaming_options_;
162
+ options.idle_timeout = timeout;
163
+ options.is_read_only = read_only;
164
+ auto stream =
165
+ std::make_shared<analytics_stream>(self->io_, resp.body(), options, resp.status_code());
166
+ stream->start(
167
+ [self, callback_state, stream, base_ctx](std::error_code early_error) mutable {
168
+ if (early_error) {
169
+ // The preamble is what carried the failure, so stamp its diagnostics before tearing
170
+ // the stream down.
171
+ base_ctx->ec = early_error;
172
+ apply_error_details(*base_ctx, stream->error_details());
173
+ // No consumer will ever take ownership of this stream, so tear the underlying body
174
+ // (socket + timers) down now rather than when the last handle drops.
175
+ stream->cancel();
176
+ self->invoke(callback_state, {}, std::move(*base_ctx));
177
+ return;
178
+ }
179
+ // Hand the base context to the consumer alongside the stream: a terminal error reached
180
+ // later is stamped onto it, so a mid-stream failure reports the same detail as this
181
+ // one.
182
+ self->invoke(callback_state, std::move(*stream), std::move(*base_ctx));
183
+ });
184
+ });
185
+
186
+ if (!op.has_value()) {
187
+ base_ctx->ec = to_error_code(op.error());
188
+ invoke(callback_state, {}, std::move(*base_ctx));
189
+ }
190
+ }
191
+
192
+ private:
193
+ struct callback_state_type {
194
+ explicit callback_state_type(analytics_stream_component::handler_type h)
195
+ : handler{ std::move(h) }
196
+ {
197
+ }
198
+
199
+ analytics_stream_component::handler_type handler;
200
+ std::mutex mutex{};
201
+ bool invoked{ false };
202
+ };
203
+
204
+ void invoke(const std::shared_ptr<callback_state_type>& state,
205
+ analytics_stream stream,
206
+ error_context::analytics ctx)
207
+ {
208
+ analytics_stream_component::handler_type handler;
209
+ {
210
+ const std::scoped_lock lock{ state->mutex };
211
+ if (state->invoked) {
212
+ return;
213
+ }
214
+ state->invoked = true;
215
+ handler = std::move(state->handler);
216
+ }
217
+ if (handler) {
218
+ handler(std::move(stream), std::move(ctx));
219
+ }
220
+ }
221
+
222
+ asio::io_context& io_;
223
+ http_component http_;
224
+ std::chrono::milliseconds default_timeout_;
225
+ row_streamer_options streaming_options_;
226
+ };
227
+
228
+ analytics_stream_component::analytics_stream_component(asio::io_context& io,
229
+ http_component http,
230
+ std::chrono::milliseconds default_timeout,
231
+ row_streamer_options streaming_options)
232
+ : impl_{ std::make_shared<analytics_stream_component_impl>(io,
233
+ std::move(http),
234
+ default_timeout,
235
+ streaming_options) }
236
+ {
237
+ }
238
+
239
+ void
240
+ analytics_stream_component::execute(operations::analytics_request request,
241
+ handler_type&& handler) const
242
+ {
243
+ impl_->execute(std::move(request), std::move(handler));
244
+ }
245
+ } // namespace couchbase::core