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
@@ -18,15 +18,22 @@
18
18
 
19
19
  #include "active_transaction_record.hxx"
20
20
  #include "attempt_context_impl.hxx"
21
+ #include "core/cluster.hxx"
21
22
  #include "core/document_id.hxx"
22
23
  #include "core/transactions/error_class.hxx"
23
24
  #include "core/transactions/exceptions.hxx"
24
25
  #include "forward_compat.hxx"
26
+ #include "get_multi_fetch.hxx"
27
+ #include "get_multi_transaction_id.hxx"
28
+ #include "internal/utils.hxx"
29
+
30
+ #include <asio/steady_timer.hpp>
25
31
 
26
- #include <algorithm>
27
32
  #include <chrono>
28
33
  #include <exception>
34
+ #include <memory>
29
35
  #include <queue>
36
+ #include <set>
30
37
  #include <vector>
31
38
 
32
39
  namespace couchbase::core::transactions
@@ -82,31 +89,6 @@ struct get_multi_spec {
82
89
  core::document_id id{};
83
90
  };
84
91
 
85
- // TODO(SA): I believe it should be accessible as an object directly
86
- struct transaction_id {
87
- std::string transaction;
88
- std::string attempt;
89
- std::string operation;
90
-
91
- auto operator==(const transaction_id& other) const -> bool
92
- {
93
- return transaction == other.transaction && attempt == other.attempt &&
94
- operation == other.operation;
95
- }
96
- auto operator<(const transaction_id& other) const -> bool
97
- {
98
- if (transaction != other.transaction) {
99
- return transaction < other.transaction;
100
- }
101
-
102
- if (attempt != other.attempt) {
103
- return attempt < other.attempt;
104
- }
105
-
106
- return operation < other.operation;
107
- }
108
- };
109
-
110
92
  struct get_multi_result {
111
93
  get_multi_spec spec;
112
94
  std::optional<transaction_get_result> get_result{};
@@ -128,17 +110,9 @@ struct get_multi_result {
128
110
  [[nodiscard]] auto extract_transaction_id() const -> std::optional<transaction_id>
129
111
  {
130
112
  if (const auto& result = get_result; result) {
131
- auto txn = result->links().staged_transaction_id();
132
- auto atmpt = result->links().staged_attempt_id();
133
- auto op = result->links().staged_operation_id();
134
-
135
- if (txn && atmpt && op) {
136
- return transaction_id{
137
- txn.value(),
138
- atmpt.value(),
139
- op.value(),
140
- };
141
- }
113
+ return make_transaction_id(result->links().staged_transaction_id(),
114
+ result->links().staged_attempt_id(),
115
+ result->links().staged_operation_id());
142
116
  }
143
117
  return {};
144
118
  }
@@ -191,22 +165,33 @@ classify_error(const std::exception_ptr& err) -> classified_error
191
165
  return classified_error{ error_class::FAIL_OTHER, external_exception::UNKNOWN };
192
166
  }
193
167
 
194
- auto
195
- contains_mutation(const std::optional<std::vector<doc_record>>& mutated_ids,
196
- const core::document_id& id) -> bool
197
- {
198
- if (!mutated_ids) {
199
- return false;
200
- }
201
- return std::any_of(mutated_ids->begin(), mutated_ids->end(), [&id](const auto& mutation) {
202
- return mutation == id;
203
- });
204
- }
205
-
168
+ /**
169
+ * Drives a single transactional get-multi operation: fetch N documents and, where there is
170
+ * evidence of read skew, resolve it to return a consistent snapshot.
171
+ *
172
+ * This implements the algorithm in the "ExtGetMulti" transactions spec
173
+ * (couchbase-transactions-specs/transactions-get-multi.md). The spec is written as a set of
174
+ * function-like sections that call one another; the methods here mirror those sections:
175
+ * - fetch_multiple_documents() -> "Fetching multiple documents"
176
+ * - fetch_individual_document() -> "Fetching an individual document"
177
+ * - disambiguate_results() -> "Document disambiguation"
178
+ * - resolve_read_skew() -> "Read skew resolution"
179
+ * The driving loop (spec "GetMulti" / "Implementation") is realised asynchronously: each fetch
180
+ * round completes into disambiguate_results(), which either finishes the operation or schedules a
181
+ * further round via retry()/reset_and_retry(). The spec's control-flow signals (Continue, Retry,
182
+ * ResetAndRetry, Completed, BoundExceeded) are expressed as those direct method calls rather than
183
+ * as returned values.
184
+ */
206
185
  class get_multi_operation : public std::enable_shared_from_this<get_multi_operation>
207
186
  {
208
187
  public:
209
188
  static constexpr std::size_t default_number_of_concurrent_requests{ 100 };
189
+ // Spec "Fetching an individual document": bound each read by min(remaining, the default 2.5s KV
190
+ // read timeout).
191
+ static constexpr std::chrono::milliseconds get_multi_key_value_read_timeout{ 2500 };
192
+ // Backstop on per-document transient retries; the read-skew bound is the primary terminator, so
193
+ // this only caps a document that keeps failing before the bound elapses.
194
+ static constexpr std::size_t get_multi_max_transient_retries{ 100 };
210
195
 
211
196
  ~get_multi_operation() = default;
212
197
  get_multi_operation(const get_multi_operation&) = delete;
@@ -263,6 +248,14 @@ public:
263
248
  --responses_left_;
264
249
  }
265
250
 
251
+ // Mark one in-flight fetch complete without touching results_[index]. Used when the read-skew
252
+ // bound is exceeded: the value already fetched for this spec (if any) must be preserved for the
253
+ // best-effort snapshot instead of being overwritten with an empty result.
254
+ void note_fetch_complete_without_result()
255
+ {
256
+ --responses_left_;
257
+ }
258
+
266
259
  auto pop_next_spec() -> std::optional<get_multi_spec>
267
260
  {
268
261
  if (to_fetch_.empty()) {
@@ -280,6 +273,10 @@ public:
280
273
  }
281
274
  }
282
275
 
276
+ /**
277
+ * Realises the spec's "Retry" signal: refetch the given subset of specs (retaining the rest of
278
+ * the operation state and the current phase), then re-run disambiguation when they complete.
279
+ */
283
280
  void retry(std::queue<get_multi_spec> specs)
284
281
  {
285
282
  to_fetch_ = std::move(specs);
@@ -287,13 +284,21 @@ public:
287
284
  fetch_multiple_documents();
288
285
  }
289
286
 
287
+ /**
288
+ * Realises the spec's "ResetAndRetry" signal: discard the fetched results and refetch every
289
+ * original spec from scratch (the state is too complex to resolve incrementally).
290
+ */
290
291
  void reset_and_retry()
291
292
  {
292
293
  std::queue<get_multi_spec> to_fetch;
293
294
  for (const auto& spec : specs_) {
294
295
  to_fetch.emplace(spec);
295
296
  }
297
+ // Restore one slot per spec before refetching: results are stored by spec index, so the
298
+ // container must stay sized to the spec count. A bare clear() would leave size() at 0 and the
299
+ // subsequent index assignments from the refetch handlers would be out of bounds.
296
300
  results_.clear();
301
+ results_.resize(specs_.size());
297
302
  if (phase_ != get_multi_phase::first_doc_fetch) {
298
303
  phase_ = get_multi_phase::subsequent_to_first_doc_fetch;
299
304
  }
@@ -305,6 +310,13 @@ public:
305
310
  invoke_callback();
306
311
  }
307
312
 
313
+ /**
314
+ * Implements the spec's "Read skew resolution" section. Reaches here only when exactly one other
315
+ * transaction T1 is involved in the fetched documents. Fetches T1's ATR entry and, based on its
316
+ * state and the current phase, either: returns the documents as-is (T1 not committed), refetches
317
+ * the documents we missed were in T1 (committed, "SubsequentToFirstDocFetch"/"DiscoveredDocsInT1"
318
+ * branches), disambiguates a missing ATR entry, or resets and retries.
319
+ */
308
320
  void resolve_read_skew()
309
321
  {
310
322
  std::string other_attempt_id;
@@ -356,82 +368,114 @@ public:
356
368
  }
357
369
  }
358
370
 
371
+ std::optional<atr_entry> attempt{};
359
372
  if (atr) {
360
- std::optional<atr_entry> attempt{};
361
373
  for (const auto& entry : atr->entries()) {
362
374
  if (entry.attempt_id() == other_attempt_id) {
363
375
  attempt = entry;
364
376
  }
365
377
  }
366
- if (attempt) {
367
- auto state = attempt->state();
368
-
369
- if (state == attempt_state::PENDING || state == attempt_state::ABORTED) {
370
- return self->completed();
371
- }
372
-
373
- if (state == attempt_state::COMMITTED) {
374
- if (self->phase_ == get_multi_phase::subsequent_to_first_doc_fetch) {
375
- std::vector<get_multi_result> were_in_t1;
376
- for (auto& result : self->results_) {
377
- if (result.has_transactional_metadata() &&
378
- (contains_mutation(attempt->inserted_ids(), result.spec.id) ||
379
- contains_mutation(attempt->replaced_ids(), result.spec.id) ||
380
- contains_mutation(attempt->removed_ids(), result.spec.id))) {
381
- were_in_t1.emplace_back(result);
382
- }
383
- }
384
-
385
- if (were_in_t1.empty()) {
386
- for (auto& result : fetched_in_t1) {
387
- result->copy_content_from_staged_operation_into_result();
388
- }
389
- return self->completed();
390
- }
378
+ }
379
+ if (attempt) {
380
+ // Reading T1's ATR entry must run the GETS_READING_ATR forward-compatibility check, as
381
+ // the normal MAV get path does. The raw get_multi fetch performs no ATR read, so this
382
+ // check would otherwise be skipped entirely.
383
+ if (auto fc_err = check_forward_compat(forward_compat_stage::GETS_READING_ATR,
384
+ attempt->forward_compat());
385
+ fc_err) {
386
+ return self->invoke_callback(std::make_exception_ptr(fc_err.value()));
387
+ }
388
+ auto state = attempt->state();
391
389
 
392
- self->phase_ = get_multi_phase::discovered_docs_in_t1;
390
+ if (state == attempt_state::PENDING || state == attempt_state::ABORTED) {
391
+ return self->completed();
392
+ }
393
393
 
394
- std::queue<get_multi_spec> to_fetch;
395
- for (const auto& result : were_in_t1) {
396
- to_fetch.emplace(result.spec);
394
+ if (state == attempt_state::COMMITTED) {
395
+ if (self->phase_ == get_multi_phase::subsequent_to_first_doc_fetch) {
396
+ // The read-skew victims are documents we fetched WITHOUT T1's metadata (e.g. read
397
+ // before T1 committed) yet which T1's ATR entry records as mutated. They must be
398
+ // re-fetched to obtain a consistent snapshot. Mirror the reference SDK: build this
399
+ // set from the docs not fetched as part of T1 and filter by T1's ATR document list
400
+ // -- not from the docs that happen to already carry T1's metadata (those are the
401
+ // ones we have already resolved, not the stale ones).
402
+ std::vector<get_multi_result> were_in_t1;
403
+ for (auto& result : self->results_) {
404
+ if (is_read_skew_victim(result.doc_exists(),
405
+ result.extract_transaction_id(),
406
+ other_attempt_id,
407
+ result.spec.id,
408
+ attempt->inserted_ids(),
409
+ attempt->replaced_ids(),
410
+ attempt->removed_ids())) {
411
+ were_in_t1.emplace_back(result);
397
412
  }
398
- return self->retry(std::move(to_fetch));
399
413
  }
400
- if (self->phase_ == get_multi_phase::discovered_docs_in_t1) {
414
+
415
+ if (were_in_t1.empty()) {
401
416
  for (auto& result : fetched_in_t1) {
402
417
  result->copy_content_from_staged_operation_into_result();
403
418
  }
404
419
  return self->completed();
405
420
  }
406
- }
407
421
 
408
- return self->reset_and_retry();
409
- }
422
+ self->phase_ = get_multi_phase::discovered_docs_in_t1;
410
423
 
411
- // T1's ATR entry is missing
412
- if (self->phase_ == get_multi_phase::resolving_t1_atr_entry_missing) {
413
- if (fetched_in_t1.empty()) {
414
- return self->reset_and_retry();
424
+ std::queue<get_multi_spec> to_fetch;
425
+ for (const auto& result : were_in_t1) {
426
+ to_fetch.emplace(result.spec);
427
+ }
428
+ return self->retry(std::move(to_fetch));
429
+ }
430
+ if (self->phase_ == get_multi_phase::discovered_docs_in_t1) {
431
+ for (auto& result : fetched_in_t1) {
432
+ result->copy_content_from_staged_operation_into_result();
433
+ }
434
+ return self->completed();
415
435
  }
416
- return self->completed();
417
436
  }
418
- self->phase_ = get_multi_phase::resolving_t1_atr_entry_missing;
419
- std::queue<get_multi_spec> to_fetch;
420
- for (const auto& result : fetched_in_t1) {
421
- to_fetch.emplace(result->spec);
437
+
438
+ return self->reset_and_retry();
439
+ }
440
+
441
+ // There is no ATR record for T1: either the ATR document holds no entry for
442
+ // T1, or the ATR document itself is absent (get_atr yields an empty record on
443
+ // document-not-found). Both mean the same thing and are handled identically
444
+ // here, per the spec's "ATR entry missing" case.
445
+ if (self->phase_ == get_multi_phase::resolving_t1_atr_entry_missing) {
446
+ if (fetched_in_t1.empty()) {
447
+ return self->reset_and_retry();
422
448
  }
423
- return self->retry(std::move(to_fetch));
449
+ return self->completed();
450
+ }
451
+ self->phase_ = get_multi_phase::resolving_t1_atr_entry_missing;
452
+ std::queue<get_multi_spec> to_fetch;
453
+ for (const auto& result : fetched_in_t1) {
454
+ to_fetch.emplace(result->spec);
424
455
  }
456
+ return self->retry(std::move(to_fetch));
425
457
  });
426
458
  }
427
459
 
460
+ /**
461
+ * Implements the spec's "Document disambiguation" section. Runs once a fetch round has completed.
462
+ * Counts how many distinct other transactions the fetched documents are involved in: zero means
463
+ * no detectable read skew (done); exactly one proceeds to read-skew resolution; two or more is
464
+ * too complex to resolve and resets. Also enforces the operation deadline (see spec "Timeouts").
465
+ */
428
466
  void disambiguate_results()
429
467
  {
430
468
  if (std::chrono::steady_clock::now() >= deadline_) {
431
- return invoke_callback(std::make_exception_ptr(
432
- transaction_operation_failed(error_class::FAIL_EXPIRY,
433
- "timeout while fetching multiple documents")
434
- .expired()));
469
+ // The read-skew resolution bound has been exceeded (spec "Global signal handling" for a
470
+ // BoundExceeded signal). Disambiguation is only entered once a fetch round has fully
471
+ // completed, so results_ holds an entry for every spec. Per the spec, when we have something
472
+ // for every document we return it as a best-effort snapshot -- which may still contain read
473
+ // skew we ran out of time to resolve -- rather than failing the operation. Any genuine
474
+ // per-document fetch error recorded in results_ is still surfaced by the completion callback.
475
+ // The bound is checked here between fetch rounds; an individual fetch still runs to the
476
+ // regular key-value timeout, so a single slow document can overshoot the bound within a
477
+ // round.
478
+ return completed();
435
479
  }
436
480
 
437
481
  std::set<transaction_id> transaction_ids{};
@@ -457,50 +501,207 @@ public:
457
501
  }
458
502
  }
459
503
 
460
- void fetch_individual_document(const get_multi_spec& spec)
504
+ /**
505
+ * Implements the spec's "Fetching an individual document" section. Performs a raw lookupIn for
506
+ * the document body and transactional metadata (explicitly not a MAV read), runs the
507
+ * forward-compatibility checks the normal get path would, and records the result. As each fetch
508
+ * completes it pulls the next queued spec, and once the round is drained it advances the deadline
509
+ * per the mode and hands off to disambiguate_results().
510
+ */
511
+ void fetch_individual_document(const get_multi_spec& spec,
512
+ std::shared_ptr<async_exp_delay> backoff = nullptr)
461
513
  {
462
- auto handler = [spec, self = shared_from_this()](const std::exception_ptr& error,
463
- std::optional<transaction_get_result> res) {
464
- if (res) {
465
- auto forward_compat_err =
466
- check_forward_compat(forward_compat_stage::GET_MULTI_GET, res->links().forward_compat());
467
- if (forward_compat_err) {
468
- self->invoke_callback(std::make_exception_ptr(forward_compat_err.value()));
514
+ // Per spec, a read-skew-resolution fetch is bounded by min(time left before the bound, the KV
515
+ // read timeout); the first-doc fetch has no bound yet, so it uses the SDK's default read
516
+ // timeout (a nullopt override).
517
+ std::optional<std::chrono::milliseconds> timeout{};
518
+ if (phase_ != get_multi_phase::first_doc_fetch) {
519
+ timeout = get_multi_fetch_timeout(deadline_ - std::chrono::steady_clock::now(),
520
+ get_multi_key_value_read_timeout);
521
+ }
522
+
523
+ // get_multi must fetch the raw document body and transactional metadata with a plain lookupIn
524
+ // -- explicitly NOT a MAV read, which would perform its own ATR lookup and resolution and rob
525
+ // the orchestrator of the metadata it needs to detect and resolve read skew itself.
526
+ attempt_->get_doc(
527
+ spec.id,
528
+ use_replicas_,
529
+ timeout,
530
+ [spec, backoff, self = shared_from_this()](
531
+ std::optional<error_class> ec,
532
+ std::optional<external_exception> cause,
533
+ std::optional<std::string> message,
534
+ std::optional<transaction_get_result> res) mutable {
535
+ if (!self->callback_) {
536
+ // The operation already completed (e.g. a sibling fetch hit a forward-compat failure and
537
+ // invoked the callback, moving results_ out). Concurrently-dispatched fetches can still
538
+ // complete afterwards; ignore them so we never index the moved-from results_ vector.
469
539
  return;
470
540
  }
471
- self->handle_individual_document_success(spec, std::move(res));
472
- } else {
473
- self->handle_individual_document_error(spec, error);
474
- }
475
- if (auto next_spec = self->pop_next_spec(); next_spec) {
476
- return self->fetch_individual_document(next_spec.value());
541
+
542
+ if (res) {
543
+ // The raw fetch does not perform MAV, so the forward-compatibility checks the normal get
544
+ // path would run must be done here. Per spec, run two checks in order: GET_MULTI_GET
545
+ // ("GM_G", so newer clients can block getMulti specifically) then GETS ("G", which older
546
+ // clients use and which anything blocking get likely needs to block getMulti too).
547
+ for (const auto stage :
548
+ { forward_compat_stage::GET_MULTI_GET, forward_compat_stage::GETS }) {
549
+ if (auto fc_err = check_forward_compat(stage, res->links().forward_compat()); fc_err) {
550
+ self->invoke_callback(std::make_exception_ptr(fc_err.value()));
551
+ return;
552
+ }
553
+ }
554
+ self->handle_individual_document_success(spec, std::move(res));
555
+ return self->advance_after_fetch();
556
+ }
557
+
558
+ // No document: classify the failure per the spec's "Fetching an individual document /
559
+ // Error handling" section (classify_get_multi_fetch_error documents the error-model
560
+ // adaptation).
561
+ const auto fetch_outcome =
562
+ classify_get_multi_fetch_error(ec, cause, self->read_skew_bound_exceeded());
563
+ switch (fetch_outcome) {
564
+ case get_multi_fetch_outcome::document_absent:
565
+ // Not a failure: record the document as absent so the round completes.
566
+ self->handle_individual_document_error(spec, nullptr);
567
+ return self->advance_after_fetch();
568
+ case get_multi_fetch_outcome::bound_exceeded:
569
+ switch (get_multi_bound_exceeded_action(self->results_[spec.index].doc_exists())) {
570
+ case bound_exceeded_action::preserve_prior_value:
571
+ // Best-effort: a value fetched for this spec in an earlier round must stay in the
572
+ // snapshot, so mark the fetch complete without overwriting results_[index].
573
+ self->note_fetch_complete_without_result();
574
+ break;
575
+ case bound_exceeded_action::fail_retryable: {
576
+ // No value held for this spec (e.g. reset_and_retry() cleared the slots and the
577
+ // re-fetch hit a transient at the already-elapsed bound). A transient is never a
578
+ // document-not-found, so record it as retryable rather than leaving the slot blank
579
+ // -- a blank would be misreported as an absent document. The retry drives the
580
+ // enclosing transaction to try again.
581
+ auto err = transaction_operation_failed(
582
+ ec.value(),
583
+ message.value_or("get_multi read-skew bound exceeded before the "
584
+ "document could be fetched"))
585
+ .retry();
586
+ if (cause) {
587
+ err.cause(cause.value());
588
+ }
589
+ self->handle_individual_document_error(spec, std::make_exception_ptr(err));
590
+ break;
591
+ }
592
+ }
593
+ return self->advance_after_fetch();
594
+ case get_multi_fetch_outcome::retry_after_backoff:
595
+ return self->retry_individual_document_after_backoff(
596
+ spec, std::move(backoff), ec, cause, message);
597
+ case get_multi_fetch_outcome::fail_expired:
598
+ case get_multi_fetch_outcome::fail_without_rollback:
599
+ case get_multi_fetch_outcome::fail_with_rollback: {
600
+ // Build the failure once; the outcome only selects the modifier applied to it.
601
+ auto err = transaction_operation_failed(
602
+ ec.value(), message.value_or("error fetching document in get_multi"));
603
+ if (cause) {
604
+ err.cause(cause.value());
605
+ }
606
+ if (fetch_outcome == get_multi_fetch_outcome::fail_expired) {
607
+ err = err.expired();
608
+ } else if (fetch_outcome == get_multi_fetch_outcome::fail_without_rollback) {
609
+ err = err.no_rollback();
610
+ }
611
+ self->handle_individual_document_error(spec, std::make_exception_ptr(err));
612
+ return self->advance_after_fetch();
613
+ }
614
+ }
615
+ });
616
+ }
617
+
618
+ // True once the read-skew resolution bound has elapsed. There is no bound during the first fetch
619
+ // round (it uses the SDK read timeout), so a transient error there always retries rather than
620
+ // being treated as bound-exceeded.
621
+ [[nodiscard]] auto read_skew_bound_exceeded() const -> bool
622
+ {
623
+ return phase_ != get_multi_phase::first_doc_fetch &&
624
+ std::chrono::steady_clock::now() >= deadline_;
625
+ }
626
+
627
+ // Spec "Fetching an individual document / Error handling": a transient failure retries this fetch
628
+ // after an exponential backoff starting from 1ms. The backoff is threaded through the retries of
629
+ // one document so the delay grows across them; a fresh document starts a new one. If it is
630
+ // exhausted the document has stayed transiently unavailable for too long, so retry the whole
631
+ // transaction.
632
+ void retry_individual_document_after_backoff(const get_multi_spec& spec,
633
+ std::shared_ptr<async_exp_delay> backoff,
634
+ std::optional<error_class> last_ec,
635
+ std::optional<external_exception> last_cause,
636
+ std::optional<std::string> last_message)
637
+ {
638
+ if (!backoff) {
639
+ backoff = std::make_shared<async_exp_delay>(
640
+ std::make_shared<asio::steady_timer>(attempt_->cluster_ref().io_context()),
641
+ std::chrono::milliseconds{ 1 },
642
+ std::chrono::milliseconds{ 100 },
643
+ get_multi_max_transient_retries);
644
+ }
645
+ (*backoff)([spec, backoff, last_ec, last_cause, last_message, self = shared_from_this()](
646
+ std::exception_ptr err) mutable {
647
+ if (!self->callback_) {
648
+ return;
477
649
  }
478
- if (self->responses_left_ == 0) {
479
- if (self->phase_ == get_multi_phase::first_doc_fetch) {
480
- self->phase_ = get_multi_phase::subsequent_to_first_doc_fetch;
650
+ if (err) {
651
+ // Backoff exhausted: the document stayed transiently unavailable for too long. Retry the
652
+ // whole transaction, preserving the last fetch's error class/cause/message so the failure
653
+ // keeps its diagnostics (timeout vs ambiguous vs transient) instead of a generic one.
654
+ auto tof = transaction_operation_failed(
655
+ last_ec.value_or(FAIL_TRANSIENT),
656
+ last_message.value_or("get_multi document fetch kept failing transiently"))
657
+ .retry();
658
+ if (last_cause) {
659
+ tof.cause(last_cause.value());
481
660
  }
661
+ self->handle_individual_document_error(spec, std::make_exception_ptr(tof));
662
+ return self->advance_after_fetch();
663
+ }
664
+ self->fetch_individual_document(spec, std::move(backoff));
665
+ });
666
+ }
482
667
 
483
- switch (self->mode_) {
668
+ // Common tail of a completed individual fetch: start the next queued spec, or, once the round is
669
+ // drained, set the read-skew bound (once, on leaving the first-doc-fetch phase) and hand off to
670
+ // disambiguation. The bound is set only on the first-doc-fetch -> subsequent transition and left
671
+ // fixed for the remaining rounds; recomputing it every round in latency mode would push the 100ms
672
+ // bound indefinitely into the future and defeat the best-effort BoundExceeded return.
673
+ void advance_after_fetch()
674
+ {
675
+ if (auto next_spec = pop_next_spec(); next_spec) {
676
+ return fetch_individual_document(next_spec.value());
677
+ }
678
+ if (responses_left_ == 0) {
679
+ if (phase_ == get_multi_phase::first_doc_fetch) {
680
+ switch (mode_) {
484
681
  case get_multi_mode::disable_read_skew_detection:
485
- return self->invoke_callback();
682
+ break;
486
683
  case get_multi_mode::prioritise_latency:
487
- self->deadline_ = std::chrono::steady_clock::now() + std::chrono::milliseconds{ 100 };
684
+ deadline_ = std::chrono::steady_clock::now() + std::chrono::milliseconds{ 100 };
488
685
  break;
489
686
  case get_multi_mode::prioritise_read_skew_detection:
490
- self->deadline_ = self->attempt_->expiry_time();
687
+ deadline_ = attempt_->expiry_time();
491
688
  break;
492
689
  }
690
+ phase_ = get_multi_phase::subsequent_to_first_doc_fetch;
691
+ }
493
692
 
494
- self->disambiguate_results();
693
+ if (mode_ == get_multi_mode::disable_read_skew_detection) {
694
+ return invoke_callback();
495
695
  }
496
- };
497
- if (use_replicas_) {
498
- attempt_->get_replica_from_preferred_server_group(spec.id, std::move(handler));
499
- } else {
500
- attempt_->get_optional(spec.id, std::move(handler));
696
+ disambiguate_results();
501
697
  }
502
698
  }
503
699
 
700
+ /**
701
+ * Implements the spec's "Fetching multiple documents" section. Schedules the queued specs to be
702
+ * fetched concurrently, with no more than default_number_of_concurrent_requests in flight at
703
+ * once; each completing fetch starts the next, so the cap is maintained without a barrier.
704
+ */
504
705
  void fetch_multiple_documents()
505
706
  {
506
707
  std::size_t requests_scheduled{ 0 };
@@ -0,0 +1,62 @@
1
+ /*
2
+ * Copyright 2021-Present Couchbase, Inc.
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+ #pragma once
17
+
18
+ #include <optional>
19
+ #include <string>
20
+
21
+ namespace couchbase::core::transactions
22
+ {
23
+ // Identifies the transaction attempt a document is staged in. Identity is the (transaction,
24
+ // attempt) pair only: the per-mutation operation id is deliberately NOT part of it. Read-skew
25
+ // detection groups the fetched documents by the *transaction* they belong to (spec "Document
26
+ // disambiguation"), and two documents staged by the same attempt always carry different operation
27
+ // ids -- including the operation id here would make them count as separate transactions and send
28
+ // disambiguation down the "two or more transactions, too complex" reset-and-retry path forever.
29
+ struct transaction_id {
30
+ std::string transaction;
31
+ std::string attempt;
32
+
33
+ auto operator==(const transaction_id& other) const -> bool
34
+ {
35
+ return transaction == other.transaction && attempt == other.attempt;
36
+ }
37
+ auto operator<(const transaction_id& other) const -> bool
38
+ {
39
+ if (transaction != other.transaction) {
40
+ return transaction < other.transaction;
41
+ }
42
+ return attempt < other.attempt;
43
+ }
44
+ };
45
+
46
+ // Build a transaction_id from the staged (transaction, attempt, operation) ids carried on a
47
+ // document's transactional metadata. All three must be present -- a document genuinely staged in a
48
+ // transaction always carries the full triplet, so a missing id means the document is not staged --
49
+ // but the operation id is deliberately dropped from the resulting identity (see transaction_id).
50
+ // Returns nullopt if any of the three ids is absent.
51
+ [[nodiscard]] inline auto
52
+ make_transaction_id(const std::optional<std::string>& transaction,
53
+ const std::optional<std::string>& attempt,
54
+ const std::optional<std::string>& operation) -> std::optional<transaction_id>
55
+ {
56
+ if (transaction && attempt && operation) {
57
+ return transaction_id{ transaction.value(), attempt.value() };
58
+ }
59
+ return {};
60
+ }
61
+
62
+ } // namespace couchbase::core::transactions