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,2445 @@
1
+ /* -*- Mode: C++; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2
+ /*
3
+ * Copyright 2026. Couchbase, Inc.
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+
18
+ #include "core/protostellar/component.hxx"
19
+
20
+ #include "core/error_context/analytics.hxx"
21
+ #include "core/error_context/key_value.hxx"
22
+ #include "core/error_context/query.hxx"
23
+ #include "core/error_context/search.hxx"
24
+ #include "core/error_context/view.hxx"
25
+ #include "core/operations/analytics_response_parsing.hxx"
26
+ #include "core/operations/query_response_parsing.hxx"
27
+ #include "core/protostellar/analytics_converter.hxx"
28
+ #include "core/protostellar/bucket_admin_converter.hxx"
29
+ #include "core/protostellar/collection_admin_converter.hxx"
30
+ #include "core/protostellar/credentials.hxx"
31
+ #include "core/protostellar/error_utils.hxx"
32
+ #include "core/protostellar/kv_converter.hxx"
33
+ #include "core/protostellar/query_converter.hxx"
34
+ #include "core/protostellar/query_index_admin_converter.hxx"
35
+ #include "core/protostellar/search_converter.hxx"
36
+ #include "core/protostellar/search_index_admin_converter.hxx"
37
+ #include "core/protostellar/view_converter.hxx"
38
+ #include "core/utils/json_stream_control.hxx"
39
+
40
+ #include <couchbase/error_codes.hxx>
41
+
42
+ #include "core/protostellar/query_proto.hxx"
43
+ #include <couchbase/analytics/v1/analytics.grpc.pb.h>
44
+
45
+ #include <couchbase/admin/bucket/v1/bucket.grpc.pb.h>
46
+ #include <couchbase/admin/collection/v1/collection.grpc.pb.h>
47
+ #include <couchbase/admin/query/v1/query.grpc.pb.h>
48
+ #include <couchbase/admin/search/v1/search.grpc.pb.h>
49
+ #include <couchbase/kv/v1/kv.grpc.pb.h>
50
+ #include <couchbase/search/v1/search.grpc.pb.h>
51
+ #include <couchbase/view/v1/view.grpc.pb.h>
52
+
53
+ #include <asio/post.hpp>
54
+
55
+ #include <chrono>
56
+ #include <cstdint>
57
+ #include <limits>
58
+ #include <memory>
59
+ #include <string>
60
+ #include <system_error>
61
+ #include <utility>
62
+ #include <vector>
63
+
64
+ namespace couchbase::core::protostellar
65
+ {
66
+ namespace v1 = ::couchbase::kv::v1;
67
+ namespace query_v1 = ::couchbase::query::v1;
68
+ namespace analytics_v1 = ::couchbase::analytics::v1;
69
+ namespace search_v1 = ::couchbase::search::v1;
70
+ namespace view_v1 = ::couchbase::view::v1;
71
+ namespace bucket_admin_v1 = ::couchbase::admin::bucket::v1;
72
+ namespace collection_admin_v1 = ::couchbase::admin::collection::v1;
73
+ namespace query_admin_v1 = ::couchbase::admin::query::v1;
74
+ namespace search_admin_v1 = ::couchbase::admin::search::v1;
75
+
76
+ // Defined here rather than in the header so the generated gRPC types stay out of every consumer of
77
+ // component.hxx.
78
+ struct component::stubs {
79
+ std::unique_ptr<v1::KvService::Stub> kv;
80
+ std::unique_ptr<query_v1::QueryService::Stub> query;
81
+ std::unique_ptr<analytics_v1::AnalyticsService::Stub> analytics;
82
+ std::unique_ptr<search_v1::SearchService::Stub> search;
83
+ std::unique_ptr<view_v1::ViewService::Stub> view;
84
+ std::unique_ptr<bucket_admin_v1::BucketAdminService::Stub> bucket_admin;
85
+ std::unique_ptr<collection_admin_v1::CollectionAdminService::Stub> collection_admin;
86
+ std::unique_ptr<query_admin_v1::QueryAdminService::Stub> query_admin;
87
+ std::unique_ptr<search_admin_v1::SearchAdminService::Stub> search_admin;
88
+ };
89
+
90
+ namespace
91
+ {
92
+ // A request whose time budget is already spent must not be dispatched. dispatcher::unary only sets
93
+ // a deadline when the timeout is positive, so handing it a non-positive one produces a call with no
94
+ // deadline at all -- an RPC that runs until the channel breaks, and that cluster::close() then
95
+ // blocks on. Turning "no time left" into "no limit" is the wrong direction for the mistake to go.
96
+ //
97
+ // This attempt sends nothing, but that does not make the operation unsent: the retry loop reaches
98
+ // here to end an operation whose earlier attempts were dispatched. The ambiguity ambiguous_timeout
99
+ // warns about is whether the server applied a write, so it is decided by whether anything was ever
100
+ // sent, not by whether this attempt was -- the same rule the MCBP path applies in
101
+ // mcbp_command::cancel(). An idempotent operation is safe to replay either way, and a first attempt
102
+ // (no retries recorded) provably reached nobody.
103
+ template<typename Request, typename Handler>
104
+ auto
105
+ fail_expired(asio::io_context& io, const Request& request, Handler& handler) -> pending_call
106
+ {
107
+ const auto ec =
108
+ (request.retries.idempotent() || request.retries.retry_attempts() == 0)
109
+ ? errc::common::unambiguous_timeout // safe to retry, or never sent to the server
110
+ : errc::common::ambiguous_timeout; // non-idempotent and dispatched at least once
111
+ auto response = request.make_response(
112
+ make_key_value_error_context(
113
+ ec, request.id, request.retries.retry_attempts(), request.retries.retry_reasons()),
114
+ typename Request::encoded_response_type{});
115
+ // Delivered through the io_context, not inline on the caller's thread: "completions arrive on the
116
+ // SDK's execution context" is the contract every other path here honours, and a synchronous
117
+ // callback out of execute() would also re-enter the caller before it has its pending_call back.
118
+ asio::post(io, [handler = std::move(handler), response = std::move(response)]() mutable {
119
+ handler(std::move(response));
120
+ });
121
+ return {};
122
+ }
123
+
124
+ // Shared state for a streaming query/analytics/search/view response consumed row by row
125
+ // (CXXCBC-910). `callback` is empty for the buffered path. `received_any` drives the CXXCBC-909
126
+ // rule: once the gateway has sent anything the statement has begun executing, so a later error must
127
+ // fail as request_canceled and never retry. It latches on arrival rather than on delivery to a
128
+ // consumer, because a buffered request (no row_callback) is just as unsafe to replay -- a
129
+ // `DELETE FROM ... WHERE ...` whose connection drops after the first result message would otherwise
130
+ // map to a retryable error and apply the mutation twice. `stopped` latches when the consumer
131
+ // returns stream_control::stop -- we keep reading to capture the terminal metadata (matching the
132
+ // HTTP row_callback contract) but stop invoking it.
133
+ struct row_stream_state {
134
+ std::function<utils::json::stream_control(std::string)> callback{};
135
+ bool stopped{ false };
136
+ bool received_any{ false };
137
+
138
+ // Feeds one row to the consumer. Returns true when the caller should buffer the row instead
139
+ // (no row_callback wired); in that case `row` is left untouched.
140
+ auto consume(std::string&& row) -> bool
141
+ {
142
+ received_any = true;
143
+ if (!callback) {
144
+ return true;
145
+ }
146
+ if (!stopped) {
147
+ if (callback(std::move(row)) == utils::json::stream_control::stop) {
148
+ stopped = true;
149
+ }
150
+ }
151
+ return false;
152
+ }
153
+ };
154
+
155
+ // Deliver a prepared response carrying ec without sending anything. Posted rather than invoked:
156
+ // completions arrive on the io context, never inline out of execute(), which would re-enter the
157
+ // caller before it holds its pending_call.
158
+ template<typename Response, typename Handler>
159
+ auto
160
+ fail_ctx(asio::io_context& io, Handler& handler, Response response, std::error_code ec)
161
+ -> pending_call
162
+ {
163
+ response.ctx.ec = ec;
164
+ asio::post(io, [handler = std::move(handler), response = std::move(response)]() mutable {
165
+ handler(std::move(response));
166
+ });
167
+ return {};
168
+ }
169
+
170
+ // The same rule for the services whose responses carry their own error context rather than a
171
+ // key_value one: they resolve a timeout exactly as the KV overloads do, so a non-positive one
172
+ // would reach the dispatcher and produce a call with no deadline there too. The response the
173
+ // overload would have built is stamped and delivered without anything being sent.
174
+ template<typename Response, typename Handler>
175
+ auto
176
+ fail_expired_ctx(asio::io_context& io, Handler& handler, Response response) -> pending_call
177
+ {
178
+ return fail_ctx(io, handler, std::move(response), errc::common::unambiguous_timeout);
179
+ }
180
+
181
+ // A management response carrying the caller's client_context_id, which is what correlates it with
182
+ // the request that produced it when several are in flight. The http error context has the field and
183
+ // the classic path fills it, so leaving it empty here would lose the identifier on the couchbase2
184
+ // path alone -- most visibly on the paths that send nothing, where there is no server response to
185
+ // correlate by either.
186
+ //
187
+ // Only what the caller supplied is echoed. The classic path substitutes a generated uuid for an
188
+ // absent one because it travels with the HTTP request and comes back in the server's own logs;
189
+ // couchbase2 sends no such field, so an id the caller never chose would identify nothing.
190
+ template<typename Request>
191
+ [[nodiscard]] auto
192
+ stamped_management(const Request& request) -> typename Request::response_type
193
+ {
194
+ typename Request::response_type response;
195
+ response.ctx.client_context_id = request.client_context_id.value_or(std::string{});
196
+ return response;
197
+ }
198
+
199
+ // FTS answers each of the search index management endpoints with {"status":"ok"} on success, and
200
+ // the classic path copies that string into the response, where core-API consumers read it.
201
+ // admin.search.v1 replies with empty messages, so a successful RPC is the same signal and carries
202
+ // the same meaning. A failed one leaves the field empty rather than inventing a status the server
203
+ // never reported; the error code is what describes it.
204
+ template<typename Response>
205
+ void
206
+ set_ok_status(Response& response)
207
+ {
208
+ if (!response.ctx.ec) {
209
+ response.status = "ok";
210
+ }
211
+ }
212
+ } // namespace
213
+
214
+ component::component(asio::io_context& io, component_config config)
215
+ : io_{ io }
216
+ , stubs_{ std::make_unique<stubs>(
217
+ stubs{ v1::KvService::NewStub(config.channel),
218
+ query_v1::QueryService::NewStub(config.channel),
219
+ analytics_v1::AnalyticsService::NewStub(config.channel),
220
+ search_v1::SearchService::NewStub(config.channel),
221
+ view_v1::ViewService::NewStub(config.channel),
222
+ bucket_admin_v1::BucketAdminService::NewStub(config.channel),
223
+ collection_admin_v1::CollectionAdminService::NewStub(config.channel),
224
+ query_admin_v1::QueryAdminService::NewStub(config.channel),
225
+ search_admin_v1::SearchAdminService::NewStub(config.channel) }) }
226
+ , authorization_{ authorization_header(config.credentials) }
227
+ , timeouts_{ config.timeouts }
228
+ , compression_{ config.compression }
229
+ // Initialised last (see the declaration order in the header), so the channel can be moved in.
230
+ , dispatcher_{ io, std::move(config.channel) }
231
+ {
232
+ }
233
+
234
+ component::~component() = default;
235
+
236
+ auto
237
+ component::channel() const -> const std::shared_ptr<grpc::Channel>&
238
+ {
239
+ return dispatcher_.channel();
240
+ }
241
+
242
+ auto
243
+ component::execute(const operations::get_request& request,
244
+ utils::movable_function<void(operations::get_response)>&& handler)
245
+ -> pending_call
246
+ {
247
+ const auto timeout = request.timeout.value_or(timeouts_.key_value);
248
+ if (timeout <= std::chrono::milliseconds::zero()) {
249
+ return fail_expired(io_, request, handler);
250
+ }
251
+ auto proto = std::make_shared<v1::GetRequest>(kv::encode(request, compression_));
252
+ const auto id = request.id;
253
+ const auto retry_attempts = request.retries.retry_attempts();
254
+ const auto retry_reasons = request.retries.retry_reasons();
255
+ const auto auth = authorization_;
256
+ auto* stub = stubs_->kv.get();
257
+ return dispatcher_.unary<v1::GetResponse>(
258
+ timeout,
259
+ [stub, proto, auth](
260
+ grpc::ClientContext& ctx, v1::GetResponse& resp, std::function<void(grpc::Status)> cb) {
261
+ if (!auth.empty()) {
262
+ ctx.AddMetadata("authorization", auth);
263
+ }
264
+ stub->async()->Get(&ctx, proto.get(), &resp, std::move(cb));
265
+ },
266
+ // `proto` is captured here too so the request outlives the in-flight call.
267
+ [handler = std::move(handler), id, proto, retry_attempts, retry_reasons](
268
+ grpc::Status status, v1::GetResponse resp) mutable {
269
+ (void)proto; // kept only to keep the request alive for the call
270
+ // A get reads and nothing more, so a deadline here is unambiguous: the document cannot have
271
+ // changed. kv::decode owns the compressed-content rule (it reports the decode failure rather
272
+ // than handing back an empty value with a success status).
273
+ auto ctx =
274
+ make_error_context(status, id, operation_kind::read_only, retry_attempts, retry_reasons);
275
+ handler(kv::decode(resp, std::move(ctx)));
276
+ });
277
+ }
278
+
279
+ auto
280
+ component::execute(const operations::get_projected_request& request,
281
+ utils::movable_function<void(operations::get_projected_response)>&& handler)
282
+ -> pending_call
283
+ {
284
+ auto proto = std::make_shared<v1::GetRequest>(kv::encode(request, compression_));
285
+ const auto id = request.id;
286
+ const auto retry_attempts = request.retries.retry_attempts();
287
+ const auto retry_reasons = request.retries.retry_reasons();
288
+ const auto timeout = request.timeout.value_or(timeouts_.key_value);
289
+ if (timeout <= std::chrono::milliseconds::zero()) {
290
+ return fail_expired(io_, request, handler);
291
+ }
292
+ const auto auth = authorization_;
293
+ auto* stub = stubs_->kv.get();
294
+ return dispatcher_.unary<v1::GetResponse>(
295
+ timeout,
296
+ [stub, proto, auth](
297
+ grpc::ClientContext& ctx, v1::GetResponse& resp, std::function<void(grpc::Status)> cb) {
298
+ if (!auth.empty()) {
299
+ ctx.AddMetadata("authorization", auth);
300
+ }
301
+ stub->async()->Get(&ctx, proto.get(), &resp, std::move(cb));
302
+ },
303
+ [handler = std::move(handler), id, proto, request, retry_attempts, retry_reasons](
304
+ grpc::Status status, v1::GetResponse resp) mutable {
305
+ (void)proto; // kept only to keep the request alive for the call
306
+ auto ctx =
307
+ make_error_context(status, id, operation_kind::read_only, retry_attempts, retry_reasons);
308
+ handler(kv::decode(resp, std::move(ctx), request));
309
+ });
310
+ }
311
+
312
+ auto
313
+ component::execute(const operations::upsert_request& request,
314
+ utils::movable_function<void(operations::upsert_response)>&& handler)
315
+ -> pending_call
316
+ {
317
+ const auto timeout = resolve_kv_timeout(request.timeout, request.durability_level, timeouts_);
318
+ if (timeout <= std::chrono::milliseconds::zero()) {
319
+ return fail_expired(io_, request, handler);
320
+ }
321
+ auto proto = std::make_shared<v1::UpsertRequest>(kv::encode(request, compression_));
322
+ const auto id = request.id;
323
+ const auto retry_attempts = request.retries.retry_attempts();
324
+ const auto retry_reasons = request.retries.retry_reasons();
325
+ const auto auth = authorization_;
326
+ auto* stub = stubs_->kv.get();
327
+ return dispatcher_.unary<v1::UpsertResponse>(
328
+ timeout,
329
+ [stub, proto, auth](
330
+ grpc::ClientContext& ctx, v1::UpsertResponse& resp, std::function<void(grpc::Status)> cb) {
331
+ if (!auth.empty()) {
332
+ ctx.AddMetadata("authorization", auth);
333
+ }
334
+ stub->async()->Upsert(&ctx, proto.get(), &resp, std::move(cb));
335
+ },
336
+ [handler = std::move(handler), id, proto, retry_attempts, retry_reasons](
337
+ grpc::Status status, v1::UpsertResponse resp) mutable {
338
+ (void)proto; // kept only to keep the request alive for the call
339
+ auto ctx =
340
+ make_error_context(status, id, operation_kind::mutating, retry_attempts, retry_reasons);
341
+ handler(kv::decode(resp, std::move(ctx)));
342
+ });
343
+ }
344
+
345
+ auto
346
+ component::execute(const operations::insert_request& request,
347
+ utils::movable_function<void(operations::insert_response)>&& handler)
348
+ -> pending_call
349
+ {
350
+ const auto timeout = resolve_kv_timeout(request.timeout, request.durability_level, timeouts_);
351
+ if (timeout <= std::chrono::milliseconds::zero()) {
352
+ return fail_expired(io_, request, handler);
353
+ }
354
+ auto proto = std::make_shared<v1::InsertRequest>(kv::encode(request, compression_));
355
+ const auto id = request.id;
356
+ const auto retry_attempts = request.retries.retry_attempts();
357
+ const auto retry_reasons = request.retries.retry_reasons();
358
+ const auto auth = authorization_;
359
+ auto* stub = stubs_->kv.get();
360
+ return dispatcher_.unary<v1::InsertResponse>(
361
+ timeout,
362
+ [stub, proto, auth](
363
+ grpc::ClientContext& ctx, v1::InsertResponse& resp, std::function<void(grpc::Status)> cb) {
364
+ if (!auth.empty()) {
365
+ ctx.AddMetadata("authorization", auth);
366
+ }
367
+ stub->async()->Insert(&ctx, proto.get(), &resp, std::move(cb));
368
+ },
369
+ [handler = std::move(handler), id, proto, retry_attempts, retry_reasons](
370
+ grpc::Status status, v1::InsertResponse resp) mutable {
371
+ (void)proto; // kept only to keep the request alive for the call
372
+ auto ctx =
373
+ make_error_context(status, id, operation_kind::mutating, retry_attempts, retry_reasons);
374
+ handler(kv::decode(resp, std::move(ctx)));
375
+ });
376
+ }
377
+
378
+ auto
379
+ component::execute(const operations::replace_request& request,
380
+ utils::movable_function<void(operations::replace_response)>&& handler)
381
+ -> pending_call
382
+ {
383
+ const auto timeout = resolve_kv_timeout(request.timeout, request.durability_level, timeouts_);
384
+ if (timeout <= std::chrono::milliseconds::zero()) {
385
+ return fail_expired(io_, request, handler);
386
+ }
387
+ auto proto = std::make_shared<v1::ReplaceRequest>(kv::encode(request, compression_));
388
+ const auto id = request.id;
389
+ const auto retry_attempts = request.retries.retry_attempts();
390
+ const auto retry_reasons = request.retries.retry_reasons();
391
+ const auto auth = authorization_;
392
+ auto* stub = stubs_->kv.get();
393
+ return dispatcher_.unary<v1::ReplaceResponse>(
394
+ timeout,
395
+ [stub, proto, auth](
396
+ grpc::ClientContext& ctx, v1::ReplaceResponse& resp, std::function<void(grpc::Status)> cb) {
397
+ if (!auth.empty()) {
398
+ ctx.AddMetadata("authorization", auth);
399
+ }
400
+ stub->async()->Replace(&ctx, proto.get(), &resp, std::move(cb));
401
+ },
402
+ [handler = std::move(handler), id, proto, retry_attempts, retry_reasons](
403
+ grpc::Status status, v1::ReplaceResponse resp) mutable {
404
+ (void)proto; // kept only to keep the request alive for the call
405
+ auto ctx =
406
+ make_error_context(status, id, operation_kind::mutating, retry_attempts, retry_reasons);
407
+ handler(kv::decode(resp, std::move(ctx)));
408
+ });
409
+ }
410
+
411
+ auto
412
+ component::execute(const operations::remove_request& request,
413
+ utils::movable_function<void(operations::remove_response)>&& handler)
414
+ -> pending_call
415
+ {
416
+ const auto timeout = resolve_kv_timeout(request.timeout, request.durability_level, timeouts_);
417
+ if (timeout <= std::chrono::milliseconds::zero()) {
418
+ return fail_expired(io_, request, handler);
419
+ }
420
+ auto proto = std::make_shared<v1::RemoveRequest>(kv::encode(request));
421
+ const auto id = request.id;
422
+ const auto retry_attempts = request.retries.retry_attempts();
423
+ const auto retry_reasons = request.retries.retry_reasons();
424
+ const auto auth = authorization_;
425
+ auto* stub = stubs_->kv.get();
426
+ return dispatcher_.unary<v1::RemoveResponse>(
427
+ timeout,
428
+ [stub, proto, auth](
429
+ grpc::ClientContext& ctx, v1::RemoveResponse& resp, std::function<void(grpc::Status)> cb) {
430
+ if (!auth.empty()) {
431
+ ctx.AddMetadata("authorization", auth);
432
+ }
433
+ stub->async()->Remove(&ctx, proto.get(), &resp, std::move(cb));
434
+ },
435
+ [handler = std::move(handler), id, proto, retry_attempts, retry_reasons](
436
+ grpc::Status status, v1::RemoveResponse resp) mutable {
437
+ (void)proto; // kept only to keep the request alive for the call
438
+ auto ctx =
439
+ make_error_context(status, id, operation_kind::mutating, retry_attempts, retry_reasons);
440
+ handler(kv::decode(resp, std::move(ctx)));
441
+ });
442
+ }
443
+
444
+ auto
445
+ component::execute(const operations::touch_request& request,
446
+ utils::movable_function<void(operations::touch_response)>&& handler)
447
+ -> pending_call
448
+ {
449
+ auto proto = std::make_shared<v1::TouchRequest>(kv::encode(request));
450
+ const auto id = request.id;
451
+ const auto retry_attempts = request.retries.retry_attempts();
452
+ const auto retry_reasons = request.retries.retry_reasons();
453
+ const auto timeout = request.timeout.value_or(timeouts_.key_value);
454
+ if (timeout <= std::chrono::milliseconds::zero()) {
455
+ return fail_expired(io_, request, handler);
456
+ }
457
+ const auto auth = authorization_;
458
+ auto* stub = stubs_->kv.get();
459
+ return dispatcher_.unary<v1::TouchResponse>(
460
+ timeout,
461
+ [stub, proto, auth](
462
+ grpc::ClientContext& ctx, v1::TouchResponse& resp, std::function<void(grpc::Status)> cb) {
463
+ if (!auth.empty()) {
464
+ ctx.AddMetadata("authorization", auth);
465
+ }
466
+ stub->async()->Touch(&ctx, proto.get(), &resp, std::move(cb));
467
+ },
468
+ [handler = std::move(handler), id, proto, retry_attempts, retry_reasons](
469
+ grpc::Status status, v1::TouchResponse resp) mutable {
470
+ (void)proto; // kept only to keep the request alive for the call
471
+ auto ctx =
472
+ make_error_context(status, id, operation_kind::mutating, retry_attempts, retry_reasons);
473
+ handler(kv::decode(resp, std::move(ctx)));
474
+ });
475
+ }
476
+
477
+ auto
478
+ component::execute(const operations::exists_request& request,
479
+ utils::movable_function<void(operations::exists_response)>&& handler)
480
+ -> pending_call
481
+ {
482
+ auto proto = std::make_shared<v1::ExistsRequest>(kv::encode(request));
483
+ const auto id = request.id;
484
+ const auto retry_attempts = request.retries.retry_attempts();
485
+ const auto retry_reasons = request.retries.retry_reasons();
486
+ const auto timeout = request.timeout.value_or(timeouts_.key_value);
487
+ if (timeout <= std::chrono::milliseconds::zero()) {
488
+ return fail_expired(io_, request, handler);
489
+ }
490
+ const auto auth = authorization_;
491
+ auto* stub = stubs_->kv.get();
492
+ return dispatcher_.unary<v1::ExistsResponse>(
493
+ timeout,
494
+ [stub, proto, auth](
495
+ grpc::ClientContext& ctx, v1::ExistsResponse& resp, std::function<void(grpc::Status)> cb) {
496
+ if (!auth.empty()) {
497
+ ctx.AddMetadata("authorization", auth);
498
+ }
499
+ stub->async()->Exists(&ctx, proto.get(), &resp, std::move(cb));
500
+ },
501
+ [handler = std::move(handler), id, proto, retry_attempts, retry_reasons](
502
+ grpc::Status status, v1::ExistsResponse resp) mutable {
503
+ (void)proto; // kept only to keep the request alive for the call
504
+ auto ctx =
505
+ make_error_context(status, id, operation_kind::read_only, retry_attempts, retry_reasons);
506
+ handler(kv::decode(resp, std::move(ctx)));
507
+ });
508
+ }
509
+
510
+ auto
511
+ component::execute(const operations::get_and_lock_request& request,
512
+ utils::movable_function<void(operations::get_and_lock_response)>&& handler)
513
+ -> pending_call
514
+ {
515
+ auto proto = std::make_shared<v1::GetAndLockRequest>(kv::encode(request, compression_));
516
+ const auto id = request.id;
517
+ const auto retry_attempts = request.retries.retry_attempts();
518
+ const auto retry_reasons = request.retries.retry_reasons();
519
+ const auto timeout = request.timeout.value_or(timeouts_.key_value);
520
+ if (timeout <= std::chrono::milliseconds::zero()) {
521
+ return fail_expired(io_, request, handler);
522
+ }
523
+ const auto auth = authorization_;
524
+ auto* stub = stubs_->kv.get();
525
+ return dispatcher_.unary<v1::GetAndLockResponse>(
526
+ timeout,
527
+ [stub, proto, auth](grpc::ClientContext& ctx,
528
+ v1::GetAndLockResponse& resp,
529
+ std::function<void(grpc::Status)> cb) {
530
+ if (!auth.empty()) {
531
+ ctx.AddMetadata("authorization", auth);
532
+ }
533
+ stub->async()->GetAndLock(&ctx, proto.get(), &resp, std::move(cb));
534
+ },
535
+ [handler = std::move(handler), id, proto, retry_attempts, retry_reasons](
536
+ grpc::Status status, v1::GetAndLockResponse resp) mutable {
537
+ (void)proto; // kept only to keep the request alive for the call
538
+ auto ctx =
539
+ make_error_context(status, id, operation_kind::mutating, retry_attempts, retry_reasons);
540
+ handler(kv::decode(resp, std::move(ctx)));
541
+ });
542
+ }
543
+
544
+ auto
545
+ component::execute(const operations::unlock_request& request,
546
+ utils::movable_function<void(operations::unlock_response)>&& handler)
547
+ -> pending_call
548
+ {
549
+ auto proto = std::make_shared<v1::UnlockRequest>(kv::encode(request));
550
+ const auto id = request.id;
551
+ const auto retry_attempts = request.retries.retry_attempts();
552
+ const auto retry_reasons = request.retries.retry_reasons();
553
+ const auto timeout = request.timeout.value_or(timeouts_.key_value);
554
+ if (timeout <= std::chrono::milliseconds::zero()) {
555
+ return fail_expired(io_, request, handler);
556
+ }
557
+ const auto auth = authorization_;
558
+ auto* stub = stubs_->kv.get();
559
+ return dispatcher_.unary<v1::UnlockResponse>(
560
+ timeout,
561
+ [stub, proto, auth](
562
+ grpc::ClientContext& ctx, v1::UnlockResponse& resp, std::function<void(grpc::Status)> cb) {
563
+ if (!auth.empty()) {
564
+ ctx.AddMetadata("authorization", auth);
565
+ }
566
+ stub->async()->Unlock(&ctx, proto.get(), &resp, std::move(cb));
567
+ },
568
+ [handler = std::move(handler), id, proto, retry_attempts, retry_reasons](
569
+ grpc::Status status, v1::UnlockResponse resp) mutable {
570
+ (void)proto; // kept only to keep the request alive for the call
571
+ auto ctx =
572
+ make_error_context(status, id, operation_kind::mutating, retry_attempts, retry_reasons);
573
+ handler(kv::decode(resp, std::move(ctx)));
574
+ });
575
+ }
576
+
577
+ auto
578
+ component::execute(const operations::get_and_touch_request& request,
579
+ utils::movable_function<void(operations::get_and_touch_response)>&& handler)
580
+ -> pending_call
581
+ {
582
+ auto proto = std::make_shared<v1::GetAndTouchRequest>(kv::encode(request, compression_));
583
+ const auto id = request.id;
584
+ const auto retry_attempts = request.retries.retry_attempts();
585
+ const auto retry_reasons = request.retries.retry_reasons();
586
+ const auto timeout = request.timeout.value_or(timeouts_.key_value);
587
+ if (timeout <= std::chrono::milliseconds::zero()) {
588
+ return fail_expired(io_, request, handler);
589
+ }
590
+ const auto auth = authorization_;
591
+ auto* stub = stubs_->kv.get();
592
+ return dispatcher_.unary<v1::GetAndTouchResponse>(
593
+ timeout,
594
+ [stub, proto, auth](grpc::ClientContext& ctx,
595
+ v1::GetAndTouchResponse& resp,
596
+ std::function<void(grpc::Status)> cb) {
597
+ if (!auth.empty()) {
598
+ ctx.AddMetadata("authorization", auth);
599
+ }
600
+ stub->async()->GetAndTouch(&ctx, proto.get(), &resp, std::move(cb));
601
+ },
602
+ [handler = std::move(handler), id, proto, retry_attempts, retry_reasons](
603
+ grpc::Status status, v1::GetAndTouchResponse resp) mutable {
604
+ (void)proto; // kept only to keep the request alive for the call
605
+ auto ctx =
606
+ make_error_context(status, id, operation_kind::mutating, retry_attempts, retry_reasons);
607
+ handler(kv::decode(resp, std::move(ctx)));
608
+ });
609
+ }
610
+
611
+ namespace
612
+ {
613
+ // initial_value is uint64 while the proto's `initial` field is int64, so anything above INT64_MAX
614
+ // would reach the gateway as a negative seed for the counter. Refused here rather than in the
615
+ // converter: kv::encode() returns the request message by value and has no error channel, so the
616
+ // narrowing has to be caught before anything is encoded or sent. The classic path carries the same
617
+ // representation mismatch, so this is not a couchbase2 regression -- this boundary is simply the
618
+ // first place that can reject it instead of silently wrapping.
619
+ template<typename Request>
620
+ [[nodiscard]] auto
621
+ counter_initial_value_out_of_range(const Request& request) -> bool
622
+ {
623
+ return request.initial_value.has_value() &&
624
+ request.initial_value.value() >
625
+ static_cast<std::uint64_t>(std::numeric_limits<std::int64_t>::max());
626
+ }
627
+
628
+ // Same delivery contract as fail_expired: nothing is sent, and the completion still arrives on the
629
+ // io_context rather than inline on the caller's thread.
630
+ template<typename Request, typename Handler>
631
+ auto
632
+ fail_invalid_argument(asio::io_context& io, const Request& request, Handler& handler)
633
+ -> pending_call
634
+ {
635
+ auto response =
636
+ request.make_response(make_key_value_error_context(errc::common::invalid_argument, request.id),
637
+ typename Request::encoded_response_type{});
638
+ asio::post(io, [handler = std::move(handler), response = std::move(response)]() mutable {
639
+ handler(std::move(response));
640
+ });
641
+ return {};
642
+ }
643
+ } // namespace
644
+
645
+ auto
646
+ component::execute(const operations::increment_request& request,
647
+ utils::movable_function<void(operations::increment_response)>&& handler)
648
+ -> pending_call
649
+ {
650
+ if (counter_initial_value_out_of_range(request)) {
651
+ return fail_invalid_argument(io_, request, handler);
652
+ }
653
+ auto proto = std::make_shared<v1::IncrementRequest>(kv::encode(request));
654
+ const auto id = request.id;
655
+ const auto retry_attempts = request.retries.retry_attempts();
656
+ const auto retry_reasons = request.retries.retry_reasons();
657
+ const auto timeout = resolve_kv_timeout(request.timeout, request.durability_level, timeouts_);
658
+ if (timeout <= std::chrono::milliseconds::zero()) {
659
+ return fail_expired(io_, request, handler);
660
+ }
661
+ const auto auth = authorization_;
662
+ auto* stub = stubs_->kv.get();
663
+ return dispatcher_.unary<v1::IncrementResponse>(
664
+ timeout,
665
+ [stub, proto, auth](
666
+ grpc::ClientContext& ctx, v1::IncrementResponse& resp, std::function<void(grpc::Status)> cb) {
667
+ if (!auth.empty()) {
668
+ ctx.AddMetadata("authorization", auth);
669
+ }
670
+ stub->async()->Increment(&ctx, proto.get(), &resp, std::move(cb));
671
+ },
672
+ [handler = std::move(handler), id, proto, retry_attempts, retry_reasons](
673
+ grpc::Status status, v1::IncrementResponse resp) mutable {
674
+ (void)proto; // kept only to keep the request alive for the call
675
+ auto ctx =
676
+ make_error_context(status, id, operation_kind::mutating, retry_attempts, retry_reasons);
677
+ handler(kv::decode(resp, std::move(ctx)));
678
+ });
679
+ }
680
+
681
+ auto
682
+ component::execute(const operations::decrement_request& request,
683
+ utils::movable_function<void(operations::decrement_response)>&& handler)
684
+ -> pending_call
685
+ {
686
+ if (counter_initial_value_out_of_range(request)) {
687
+ return fail_invalid_argument(io_, request, handler);
688
+ }
689
+ auto proto = std::make_shared<v1::DecrementRequest>(kv::encode(request));
690
+ const auto id = request.id;
691
+ const auto retry_attempts = request.retries.retry_attempts();
692
+ const auto retry_reasons = request.retries.retry_reasons();
693
+ const auto timeout = resolve_kv_timeout(request.timeout, request.durability_level, timeouts_);
694
+ if (timeout <= std::chrono::milliseconds::zero()) {
695
+ return fail_expired(io_, request, handler);
696
+ }
697
+ const auto auth = authorization_;
698
+ auto* stub = stubs_->kv.get();
699
+ return dispatcher_.unary<v1::DecrementResponse>(
700
+ timeout,
701
+ [stub, proto, auth](
702
+ grpc::ClientContext& ctx, v1::DecrementResponse& resp, std::function<void(grpc::Status)> cb) {
703
+ if (!auth.empty()) {
704
+ ctx.AddMetadata("authorization", auth);
705
+ }
706
+ stub->async()->Decrement(&ctx, proto.get(), &resp, std::move(cb));
707
+ },
708
+ [handler = std::move(handler), id, proto, retry_attempts, retry_reasons](
709
+ grpc::Status status, v1::DecrementResponse resp) mutable {
710
+ (void)proto; // kept only to keep the request alive for the call
711
+ auto ctx =
712
+ make_error_context(status, id, operation_kind::mutating, retry_attempts, retry_reasons);
713
+ handler(kv::decode(resp, std::move(ctx)));
714
+ });
715
+ }
716
+
717
+ auto
718
+ component::execute(const operations::append_request& request,
719
+ utils::movable_function<void(operations::append_response)>&& handler)
720
+ -> pending_call
721
+ {
722
+ auto proto = std::make_shared<v1::AppendRequest>(kv::encode(request));
723
+ const auto id = request.id;
724
+ const auto retry_attempts = request.retries.retry_attempts();
725
+ const auto retry_reasons = request.retries.retry_reasons();
726
+ const auto timeout = resolve_kv_timeout(request.timeout, request.durability_level, timeouts_);
727
+ if (timeout <= std::chrono::milliseconds::zero()) {
728
+ return fail_expired(io_, request, handler);
729
+ }
730
+ const auto auth = authorization_;
731
+ auto* stub = stubs_->kv.get();
732
+ return dispatcher_.unary<v1::AppendResponse>(
733
+ timeout,
734
+ [stub, proto, auth](
735
+ grpc::ClientContext& ctx, v1::AppendResponse& resp, std::function<void(grpc::Status)> cb) {
736
+ if (!auth.empty()) {
737
+ ctx.AddMetadata("authorization", auth);
738
+ }
739
+ stub->async()->Append(&ctx, proto.get(), &resp, std::move(cb));
740
+ },
741
+ [handler = std::move(handler), id, proto, retry_attempts, retry_reasons](
742
+ grpc::Status status, v1::AppendResponse resp) mutable {
743
+ (void)proto; // kept only to keep the request alive for the call
744
+ auto ctx =
745
+ make_error_context(status, id, operation_kind::mutating, retry_attempts, retry_reasons);
746
+ handler(kv::decode(resp, std::move(ctx)));
747
+ });
748
+ }
749
+
750
+ auto
751
+ component::execute(const operations::prepend_request& request,
752
+ utils::movable_function<void(operations::prepend_response)>&& handler)
753
+ -> pending_call
754
+ {
755
+ auto proto = std::make_shared<v1::PrependRequest>(kv::encode(request));
756
+ const auto id = request.id;
757
+ const auto retry_attempts = request.retries.retry_attempts();
758
+ const auto retry_reasons = request.retries.retry_reasons();
759
+ const auto timeout = resolve_kv_timeout(request.timeout, request.durability_level, timeouts_);
760
+ if (timeout <= std::chrono::milliseconds::zero()) {
761
+ return fail_expired(io_, request, handler);
762
+ }
763
+ const auto auth = authorization_;
764
+ auto* stub = stubs_->kv.get();
765
+ return dispatcher_.unary<v1::PrependResponse>(
766
+ timeout,
767
+ [stub, proto, auth](
768
+ grpc::ClientContext& ctx, v1::PrependResponse& resp, std::function<void(grpc::Status)> cb) {
769
+ if (!auth.empty()) {
770
+ ctx.AddMetadata("authorization", auth);
771
+ }
772
+ stub->async()->Prepend(&ctx, proto.get(), &resp, std::move(cb));
773
+ },
774
+ [handler = std::move(handler), id, proto, retry_attempts, retry_reasons](
775
+ grpc::Status status, v1::PrependResponse resp) mutable {
776
+ (void)proto; // kept only to keep the request alive for the call
777
+ auto ctx =
778
+ make_error_context(status, id, operation_kind::mutating, retry_attempts, retry_reasons);
779
+ handler(kv::decode(resp, std::move(ctx)));
780
+ });
781
+ }
782
+
783
+ auto
784
+ component::execute(const operations::query_request& request,
785
+ utils::movable_function<void(operations::query_response)>&& handler)
786
+ -> pending_call
787
+ {
788
+ auto statement = request.statement;
789
+ auto client_context_id = request.client_context_id.value_or(std::string{});
790
+ // Stamped even on the paths that send nothing: the error context identifies which query failed,
791
+ // and a caller correlating by statement or client_context_id has no other handle on it.
792
+ const auto stamped = [&statement, &client_context_id]() {
793
+ operations::query_response response;
794
+ response.ctx.statement = statement;
795
+ response.ctx.client_context_id = client_context_id;
796
+ return response;
797
+ };
798
+
799
+ const auto timeout = request.timeout.value_or(timeouts_.query);
800
+ if (timeout <= std::chrono::milliseconds::zero()) {
801
+ return fail_expired_ctx(io_, handler, stamped());
802
+ }
803
+
804
+ // Features with no couchbase2 equivalent (raw passthrough, use_replica, node targeting,
805
+ // out-of-range tuning values): fail cleanly rather than silently dropping the caller's intent.
806
+ // The streaming row_callback is wired below and is deliberately not among them.
807
+ if (!query::can_encode(request)) {
808
+ auto response = stamped();
809
+ response.ctx.ec = errc::common::feature_not_available;
810
+ // Posted rather than invoked here, for the reason fail_expired above gives: completions arrive
811
+ // on the SDK's execution context, and calling back inline would re-enter the caller before it
812
+ // has its pending_call.
813
+ asio::post(io_, [handler = std::move(handler), response = std::move(response)]() mutable {
814
+ handler(std::move(response));
815
+ });
816
+ return {};
817
+ }
818
+
819
+ auto proto = std::make_shared<query_v1::QueryRequest>(query::encode(request));
820
+ const auto auth = authorization_;
821
+ // N1QL is the one service where read-only-ness is a property of the statement rather than of
822
+ // the operation, and the request already carries the flag, so a timed-out read-only query is
823
+ // reported as unambiguous exactly like a KV read.
824
+ const auto kind = request.readonly ? operation_kind::read_only : operation_kind::mutating;
825
+ auto* stub = stubs_->query.get();
826
+
827
+ // Accumulated across streamed messages; shared between the row and completion callbacks. With a
828
+ // row_callback wired, rows are delivered to the consumer as they arrive and `rows` stays empty.
829
+ auto rows = std::make_shared<std::vector<std::string>>();
830
+ auto meta = std::make_shared<operations::query_response::query_meta_data>();
831
+ // Whether a MetaData message arrived at all, which is not the same as what it said. The gateway
832
+ // leaves MetaData nil when its own result.MetaData() fails and still ends the stream OK, and an
833
+ // absent status must not be classified as a failed one.
834
+ auto meta_received = std::make_shared<bool>(false);
835
+ auto stream = std::make_shared<row_stream_state>();
836
+ if (request.row_callback) {
837
+ stream->callback = std::move(*request.row_callback);
838
+ }
839
+
840
+ return dispatcher_.server_stream<query_v1::QueryResponse>(
841
+ timeout,
842
+ [stub, proto, auth](grpc::ClientContext& ctx,
843
+ grpc::ClientReadReactor<query_v1::QueryResponse>* reactor) {
844
+ if (!auth.empty()) {
845
+ ctx.AddMetadata("authorization", auth);
846
+ }
847
+ stub->async()->Query(&ctx, proto.get(), reactor);
848
+ },
849
+ [rows, meta, meta_received, stream](query_v1::QueryResponse message) {
850
+ stream->received_any = true;
851
+ for (auto& row : *message.mutable_rows()) {
852
+ if (stream->consume(std::move(row))) {
853
+ rows->push_back(std::move(row));
854
+ }
855
+ }
856
+ if (message.has_meta_data()) {
857
+ *meta_received = true;
858
+ query::decode_meta_data(message.meta_data(), *meta);
859
+ }
860
+ },
861
+ // `proto` is captured so the request outlives the in-flight stream.
862
+ [handler = std::move(handler),
863
+ rows,
864
+ meta,
865
+ meta_received,
866
+ stream,
867
+ proto,
868
+ statement,
869
+ client_context_id,
870
+ kind](grpc::Status status) mutable {
871
+ (void)proto; // kept only to keep the request alive for the call
872
+ operations::query_response response;
873
+ response.ctx.ec = map_status(status, kind);
874
+ // CXXCBC-909: a mid-stream error after the gateway sent anything cannot be retried.
875
+ if (stream->received_any && retry_reason_for(response.ctx.ec).has_value()) {
876
+ response.ctx.ec = errc::common::request_canceled;
877
+ }
878
+ response.ctx.statement = std::move(statement);
879
+ response.ctx.client_context_id =
880
+ meta->client_context_id.empty() ? std::move(client_context_id) : meta->client_context_id;
881
+ if (!status.ok()) {
882
+ response.ctx.first_error_message = error_message(status);
883
+ }
884
+ response.meta = std::move(*meta);
885
+ response.rows = std::move(*rows);
886
+ // A terminal status other than "success" arriving with an OK trailer would otherwise be
887
+ // reported as a successful query whose own metadata says it failed. map_query_error is the
888
+ // classifier the classic path uses, so the two transports agree on the mapping; the proto
889
+ // carries no errors list, and for that case it yields internal_server_failure -- the same
890
+ // fallback document_query.cxx applies when the payload has no error code to classify.
891
+ //
892
+ // A transport-level error already in ctx.ec is more specific, so it wins.
893
+ if (!response.ctx.ec && *meta_received) {
894
+ response.ctx.ec = operations::map_query_error(response.meta);
895
+ }
896
+ handler(std::move(response));
897
+ });
898
+ }
899
+
900
+ auto
901
+ component::execute(const operations::analytics_request& request,
902
+ utils::movable_function<void(operations::analytics_response)>&& handler)
903
+ -> pending_call
904
+ {
905
+ auto statement = request.statement;
906
+ auto client_context_id = request.client_context_id.value_or(std::string{});
907
+ // Stamped even on the paths that send nothing: the error context identifies which statement
908
+ // failed, and a caller correlating by statement or client_context_id has no other handle on it.
909
+ const auto stamped = [&statement, &client_context_id]() {
910
+ operations::analytics_response response;
911
+ response.ctx.statement = statement;
912
+ response.ctx.client_context_id = client_context_id;
913
+ return response;
914
+ };
915
+
916
+ const auto timeout = request.timeout.value_or(timeouts_.analytics);
917
+ if (timeout <= std::chrono::milliseconds::zero()) {
918
+ return fail_expired_ctx(io_, handler, stamped());
919
+ }
920
+
921
+ // Scoped analytics and raw passthrough have no couchbase2 mapping: fail cleanly rather than
922
+ // dropping the caller's intent. The streaming row_callback is wired below and is not among them.
923
+ if (!analytics::can_encode(request)) {
924
+ auto response = stamped();
925
+ response.ctx.ec = errc::common::feature_not_available;
926
+ // Posted rather than invoked here, for the reason fail_expired above gives: completions arrive
927
+ // on the SDK's execution context, and calling back inline would re-enter the caller before it
928
+ // has its pending_call.
929
+ asio::post(io_, [handler = std::move(handler), response = std::move(response)]() mutable {
930
+ handler(std::move(response));
931
+ });
932
+ return {};
933
+ }
934
+
935
+ auto proto = std::make_shared<analytics_v1::AnalyticsQueryRequest>(analytics::encode(request));
936
+ const auto auth = authorization_;
937
+ // Analytics carries the same readonly flag as N1QL, so the same RFC 77 rule applies.
938
+ const auto kind = request.readonly ? operation_kind::read_only : operation_kind::mutating;
939
+ auto* stub = stubs_->analytics.get();
940
+
941
+ auto rows = std::make_shared<std::vector<std::string>>();
942
+ auto meta = std::make_shared<operations::analytics_response::analytics_meta_data>();
943
+ // Whether a MetaData message arrived, which the status alone cannot tell us: analytics_status's
944
+ // first enumerator is `running`, so a default-constructed meta is indistinguishable from one the
945
+ // gateway reported as running. Classifying without this would turn a stream that ended OK with no
946
+ // metadata into an error.
947
+ auto meta_received = std::make_shared<bool>(false);
948
+ auto stream = std::make_shared<row_stream_state>();
949
+ if (request.row_callback) {
950
+ stream->callback = std::move(*request.row_callback);
951
+ }
952
+
953
+ return dispatcher_.server_stream<analytics_v1::AnalyticsQueryResponse>(
954
+ timeout,
955
+ [stub, proto, auth](grpc::ClientContext& ctx,
956
+ grpc::ClientReadReactor<analytics_v1::AnalyticsQueryResponse>* reactor) {
957
+ if (!auth.empty()) {
958
+ ctx.AddMetadata("authorization", auth);
959
+ }
960
+ stub->async()->AnalyticsQuery(&ctx, proto.get(), reactor);
961
+ },
962
+ [rows, meta, meta_received, stream](analytics_v1::AnalyticsQueryResponse message) {
963
+ stream->received_any = true;
964
+ for (auto& row : *message.mutable_rows()) {
965
+ if (stream->consume(std::move(row))) {
966
+ rows->push_back(std::move(row));
967
+ }
968
+ }
969
+ if (message.has_meta_data()) {
970
+ *meta_received = true;
971
+ analytics::decode_meta_data(message.meta_data(), *meta);
972
+ }
973
+ },
974
+ // `proto` is captured so the request outlives the in-flight stream.
975
+ [handler = std::move(handler),
976
+ rows,
977
+ meta,
978
+ meta_received,
979
+ stream,
980
+ proto,
981
+ statement,
982
+ client_context_id,
983
+ kind](grpc::Status status) mutable {
984
+ (void)proto; // kept only to keep the request alive for the call
985
+ operations::analytics_response response;
986
+ response.ctx.ec = map_status(status, kind);
987
+ // CXXCBC-909: a mid-stream error after the gateway sent anything cannot be retried.
988
+ if (stream->received_any && retry_reason_for(response.ctx.ec).has_value()) {
989
+ response.ctx.ec = errc::common::request_canceled;
990
+ }
991
+ response.ctx.statement = std::move(statement);
992
+ response.ctx.client_context_id =
993
+ meta->client_context_id.empty() ? std::move(client_context_id) : meta->client_context_id;
994
+ if (!status.ok()) {
995
+ response.ctx.first_error_message = error_message(status);
996
+ }
997
+ response.meta = std::move(*meta);
998
+ response.rows = std::move(*rows);
999
+ // A terminal status other than success arriving with an OK trailer would otherwise be
1000
+ // reported as a successful query whose own metadata says it failed. map_analytics_error is
1001
+ // the classifier the classic path uses, so both transports agree; the proto carries no errors
1002
+ // list, and for that case it yields internal_server_failure -- the fallback its own comment
1003
+ // documents for exactly this caller.
1004
+ //
1005
+ // A transport-level error already in ctx.ec is more specific, so it wins.
1006
+ if (!response.ctx.ec && *meta_received) {
1007
+ response.ctx.ec = operations::map_analytics_error(response.meta);
1008
+ }
1009
+ handler(std::move(response));
1010
+ });
1011
+ }
1012
+
1013
+ auto
1014
+ component::execute(const operations::search_request& request,
1015
+ utils::movable_function<void(operations::search_response)>&& handler)
1016
+ -> pending_call
1017
+ {
1018
+ auto index_name = request.index_name;
1019
+ auto query_str = request.query.str();
1020
+ auto client_context_id = request.client_context_id.value_or(std::string{});
1021
+ // Stamped even on the paths that send nothing, as the query overload does: the error context
1022
+ // identifies which search failed, and a caller correlating by index or client_context_id has no
1023
+ // other handle on it.
1024
+ const auto stamped = [&index_name, &query_str, &client_context_id]() {
1025
+ operations::search_response response;
1026
+ response.ctx.index_name = index_name;
1027
+ response.ctx.query = query_str;
1028
+ response.ctx.client_context_id = client_context_id;
1029
+ return response;
1030
+ };
1031
+
1032
+ const auto timeout = request.timeout.value_or(timeouts_.search);
1033
+ if (timeout <= std::chrono::milliseconds::zero()) {
1034
+ return fail_expired_ctx(io_, handler, stamped());
1035
+ }
1036
+
1037
+ // Unmappable query shape or gated feature (facets, sort specs, vector search, mutation tokens,
1038
+ // raw): fail cleanly rather than dropping the caller's intent. A query that is not JSON at all is
1039
+ // the caller's own error and is reported as invalid_argument, so a malformed query is not
1040
+ // mistaken for a gap in couchbase2 search support.
1041
+ auto encoded = search::encode(request);
1042
+ if (!encoded.has_value()) {
1043
+ auto response = stamped();
1044
+ response.ctx.ec = search::query_is_malformed(request) ? errc::common::invalid_argument
1045
+ : errc::common::feature_not_available;
1046
+ // Posted rather than invoked here, for the reason fail_expired above gives: completions arrive
1047
+ // on the io context, never inline out of execute().
1048
+ asio::post(io_, [handler = std::move(handler), response = std::move(response)]() mutable {
1049
+ handler(std::move(response));
1050
+ });
1051
+ return {};
1052
+ }
1053
+
1054
+ auto proto = std::make_shared<search_v1::SearchQueryRequest>(std::move(*encoded));
1055
+ const auto auth = authorization_;
1056
+ auto* stub = stubs_->search.get();
1057
+ auto response = std::make_shared<operations::search_response>();
1058
+
1059
+ return dispatcher_.server_stream<search_v1::SearchQueryResponse>(
1060
+ timeout,
1061
+ [stub, proto, auth](grpc::ClientContext& ctx,
1062
+ grpc::ClientReadReactor<search_v1::SearchQueryResponse>* reactor) {
1063
+ if (!auth.empty()) {
1064
+ ctx.AddMetadata("authorization", auth);
1065
+ }
1066
+ stub->async()->SearchQuery(&ctx, proto.get(), reactor);
1067
+ },
1068
+ [response](search_v1::SearchQueryResponse message) {
1069
+ search::decode(message, *response);
1070
+ },
1071
+ [handler = std::move(handler), response, proto, index_name, query_str, client_context_id](
1072
+ grpc::Status status) mutable {
1073
+ (void)proto; // kept only to keep the request alive for the call
1074
+ response->ctx.ec = map_status(status, operation_kind::read_only);
1075
+ response->ctx.first_error_message = error_message(status);
1076
+ response->ctx.index_name = std::move(index_name);
1077
+ response->ctx.query = std::move(query_str);
1078
+ response->ctx.client_context_id = std::move(client_context_id);
1079
+ handler(std::move(*response));
1080
+ });
1081
+ }
1082
+
1083
+ auto
1084
+ component::execute(const operations::document_view_request& request,
1085
+ utils::movable_function<void(operations::document_view_response)>&& handler)
1086
+ -> pending_call
1087
+ {
1088
+ auto design_document = request.document_name;
1089
+ auto view_name = request.view_name;
1090
+ auto client_context_id = request.client_context_id.value_or(std::string{});
1091
+ // Stamped on every path for the reason the query and search overloads give: a response naming no
1092
+ // view leaves the caller nothing to correlate the failure with.
1093
+ const auto stamped = [&design_document, &view_name, &client_context_id]() {
1094
+ operations::document_view_response response;
1095
+ response.ctx.design_document_name = design_document;
1096
+ response.ctx.view_name = view_name;
1097
+ response.ctx.client_context_id = client_context_id;
1098
+ return response;
1099
+ };
1100
+
1101
+ const auto timeout = request.timeout.value_or(timeouts_.view);
1102
+ if (timeout <= std::chrono::milliseconds::zero()) {
1103
+ return fail_expired_ctx(io_, handler, stamped());
1104
+ }
1105
+
1106
+ // raw / query_string / full_set have no couchbase2 mapping: fail cleanly.
1107
+ if (!view::can_encode(request)) {
1108
+ auto response = stamped();
1109
+ response.ctx.ec = errc::common::feature_not_available;
1110
+ // Posted rather than invoked here, for the reason fail_expired above gives: completions arrive
1111
+ // on the io context, never inline out of execute().
1112
+ asio::post(io_, [handler = std::move(handler), response = std::move(response)]() mutable {
1113
+ handler(std::move(response));
1114
+ });
1115
+ return {};
1116
+ }
1117
+
1118
+ auto proto = std::make_shared<view_v1::ViewQueryRequest>(view::encode(request));
1119
+ const auto auth = authorization_;
1120
+ auto* stub = stubs_->view.get();
1121
+ auto response = std::make_shared<operations::document_view_response>();
1122
+
1123
+ return dispatcher_.server_stream<view_v1::ViewQueryResponse>(
1124
+ timeout,
1125
+ [stub, proto, auth](grpc::ClientContext& ctx,
1126
+ grpc::ClientReadReactor<view_v1::ViewQueryResponse>* reactor) {
1127
+ if (!auth.empty()) {
1128
+ ctx.AddMetadata("authorization", auth);
1129
+ }
1130
+ stub->async()->ViewQuery(&ctx, proto.get(), reactor);
1131
+ },
1132
+ [response](view_v1::ViewQueryResponse message) {
1133
+ view::decode_rows(message, *response);
1134
+ },
1135
+ [handler = std::move(handler), response, proto, design_document, view_name, client_context_id](
1136
+ grpc::Status status) mutable {
1137
+ (void)proto; // kept only to keep the request alive for the call
1138
+ response->ctx.ec = map_status(status, operation_kind::read_only);
1139
+ response->ctx.first_error_message = error_message(status);
1140
+ response->ctx.design_document_name = std::move(design_document);
1141
+ response->ctx.view_name = std::move(view_name);
1142
+ response->ctx.client_context_id = std::move(client_context_id);
1143
+ handler(std::move(*response));
1144
+ });
1145
+ }
1146
+
1147
+ auto
1148
+ component::execute(
1149
+ const operations::management::bucket_get_all_request& request,
1150
+ utils::movable_function<void(operations::management::bucket_get_all_response)>&& handler)
1151
+ -> pending_call
1152
+ {
1153
+ const auto client_context_id = request.client_context_id.value_or(std::string{});
1154
+ auto proto = std::make_shared<bucket_admin_v1::ListBucketsRequest>();
1155
+ const auto timeout = request.timeout.value_or(timeouts_.management);
1156
+ if (timeout <= std::chrono::milliseconds::zero()) {
1157
+ return fail_expired_ctx(io_, handler, stamped_management(request));
1158
+ }
1159
+ const auto auth = authorization_;
1160
+ auto* stub = stubs_->bucket_admin.get();
1161
+ return dispatcher_.unary<bucket_admin_v1::ListBucketsResponse>(
1162
+ timeout,
1163
+ [stub, proto, auth](grpc::ClientContext& ctx,
1164
+ bucket_admin_v1::ListBucketsResponse& resp,
1165
+ std::function<void(grpc::Status)> cb) {
1166
+ if (!auth.empty()) {
1167
+ ctx.AddMetadata("authorization", auth);
1168
+ }
1169
+ stub->async()->ListBuckets(&ctx, proto.get(), &resp, std::move(cb));
1170
+ },
1171
+ [handler = std::move(handler), proto, client_context_id](
1172
+ grpc::Status status, bucket_admin_v1::ListBucketsResponse resp) mutable {
1173
+ (void)proto; // kept only to keep the request alive for the call
1174
+ operations::management::bucket_get_all_response response;
1175
+ response.ctx.client_context_id = client_context_id;
1176
+ response.ctx.ec = map_status(status, operation_kind::read_only);
1177
+ for (const auto& bucket : resp.buckets()) {
1178
+ response.buckets.push_back(bucket_admin::decode_bucket(bucket));
1179
+ }
1180
+ handler(std::move(response));
1181
+ });
1182
+ }
1183
+
1184
+ auto
1185
+ component::execute(
1186
+ const operations::management::bucket_get_request& request,
1187
+ utils::movable_function<void(operations::management::bucket_get_response)>&& handler)
1188
+ -> pending_call
1189
+ {
1190
+ const auto client_context_id = request.client_context_id.value_or(std::string{});
1191
+ // admin.bucket.v1 has no GetBucket RPC; filter the bucket list by name.
1192
+ auto proto = std::make_shared<bucket_admin_v1::ListBucketsRequest>();
1193
+ const auto timeout = request.timeout.value_or(timeouts_.management);
1194
+ if (timeout <= std::chrono::milliseconds::zero()) {
1195
+ return fail_expired_ctx(io_, handler, stamped_management(request));
1196
+ }
1197
+ const auto auth = authorization_;
1198
+ const auto name = request.name;
1199
+ auto* stub = stubs_->bucket_admin.get();
1200
+ return dispatcher_.unary<bucket_admin_v1::ListBucketsResponse>(
1201
+ timeout,
1202
+ [stub, proto, auth](grpc::ClientContext& ctx,
1203
+ bucket_admin_v1::ListBucketsResponse& resp,
1204
+ std::function<void(grpc::Status)> cb) {
1205
+ if (!auth.empty()) {
1206
+ ctx.AddMetadata("authorization", auth);
1207
+ }
1208
+ stub->async()->ListBuckets(&ctx, proto.get(), &resp, std::move(cb));
1209
+ },
1210
+ [handler = std::move(handler), proto, client_context_id, name](
1211
+ grpc::Status status, bucket_admin_v1::ListBucketsResponse resp) mutable {
1212
+ (void)proto; // kept only to keep the request alive for the call
1213
+ operations::management::bucket_get_response response;
1214
+ response.ctx.client_context_id = client_context_id;
1215
+ response.ctx.ec = map_status(status, operation_kind::read_only);
1216
+ if (!response.ctx.ec) {
1217
+ bool found = false;
1218
+ for (const auto& bucket : resp.buckets()) {
1219
+ if (bucket.bucket_name() == name) {
1220
+ response.bucket = bucket_admin::decode_bucket(bucket);
1221
+ found = true;
1222
+ break;
1223
+ }
1224
+ }
1225
+ if (!found) {
1226
+ response.ctx.ec = errc::common::bucket_not_found;
1227
+ }
1228
+ }
1229
+ handler(std::move(response));
1230
+ });
1231
+ }
1232
+
1233
+ auto
1234
+ component::execute(
1235
+ const operations::management::bucket_create_request& request,
1236
+ utils::movable_function<void(operations::management::bucket_create_response)>&& handler)
1237
+ -> pending_call
1238
+ {
1239
+ const auto client_context_id = request.client_context_id.value_or(std::string{});
1240
+ auto proto = std::make_shared<bucket_admin_v1::CreateBucketRequest>();
1241
+ bucket_admin::apply_settings(request.bucket, *proto);
1242
+ if (!bucket_admin::apply_create_only_settings(request.bucket, *proto)) {
1243
+ operations::management::bucket_create_response response;
1244
+ response.ctx.client_context_id = client_context_id;
1245
+ response.ctx.ec = errc::common::feature_not_available;
1246
+ // Posted rather than invoked here, for the reason fail_expired gives: completions arrive on the
1247
+ // io context, never inline out of execute().
1248
+ asio::post(io_, [handler = std::move(handler), response = std::move(response)]() mutable {
1249
+ handler(std::move(response));
1250
+ });
1251
+ return {};
1252
+ }
1253
+ const auto timeout = request.timeout.value_or(timeouts_.management);
1254
+ if (timeout <= std::chrono::milliseconds::zero()) {
1255
+ return fail_expired_ctx(io_, handler, stamped_management(request));
1256
+ }
1257
+ const auto auth = authorization_;
1258
+ auto* stub = stubs_->bucket_admin.get();
1259
+ return dispatcher_.unary<bucket_admin_v1::CreateBucketResponse>(
1260
+ timeout,
1261
+ [stub, proto, auth](grpc::ClientContext& ctx,
1262
+ bucket_admin_v1::CreateBucketResponse& resp,
1263
+ std::function<void(grpc::Status)> cb) {
1264
+ if (!auth.empty()) {
1265
+ ctx.AddMetadata("authorization", auth);
1266
+ }
1267
+ stub->async()->CreateBucket(&ctx, proto.get(), &resp, std::move(cb));
1268
+ },
1269
+ [handler = std::move(handler), proto, client_context_id](
1270
+ grpc::Status status, bucket_admin_v1::CreateBucketResponse /* resp */) mutable {
1271
+ (void)proto; // kept only to keep the request alive for the call
1272
+ operations::management::bucket_create_response response;
1273
+ response.ctx.client_context_id = client_context_id;
1274
+ response.ctx.ec = map_status(status, operation_kind::mutating);
1275
+ if (response.ctx.ec) {
1276
+ response.error_message = error_message(status);
1277
+ }
1278
+ handler(std::move(response));
1279
+ });
1280
+ }
1281
+
1282
+ auto
1283
+ component::execute(
1284
+ const operations::management::bucket_update_request& request,
1285
+ utils::movable_function<void(operations::management::bucket_update_response)>&& handler)
1286
+ -> pending_call
1287
+ {
1288
+ const auto client_context_id = request.client_context_id.value_or(std::string{});
1289
+ auto proto = std::make_shared<bucket_admin_v1::UpdateBucketRequest>();
1290
+ bucket_admin::apply_settings(request.bucket, *proto);
1291
+ const auto timeout = request.timeout.value_or(timeouts_.management);
1292
+ if (timeout <= std::chrono::milliseconds::zero()) {
1293
+ return fail_expired_ctx(io_, handler, stamped_management(request));
1294
+ }
1295
+ const auto auth = authorization_;
1296
+ auto* stub = stubs_->bucket_admin.get();
1297
+ return dispatcher_.unary<bucket_admin_v1::UpdateBucketResponse>(
1298
+ timeout,
1299
+ [stub, proto, auth](grpc::ClientContext& ctx,
1300
+ bucket_admin_v1::UpdateBucketResponse& resp,
1301
+ std::function<void(grpc::Status)> cb) {
1302
+ if (!auth.empty()) {
1303
+ ctx.AddMetadata("authorization", auth);
1304
+ }
1305
+ stub->async()->UpdateBucket(&ctx, proto.get(), &resp, std::move(cb));
1306
+ },
1307
+ [handler = std::move(handler), proto, client_context_id](
1308
+ grpc::Status status, bucket_admin_v1::UpdateBucketResponse /* resp */) mutable {
1309
+ (void)proto; // kept only to keep the request alive for the call
1310
+ operations::management::bucket_update_response response;
1311
+ response.ctx.client_context_id = client_context_id;
1312
+ response.ctx.ec = map_status(status, operation_kind::mutating);
1313
+ if (response.ctx.ec) {
1314
+ response.error_message = error_message(status);
1315
+ }
1316
+ handler(std::move(response));
1317
+ });
1318
+ }
1319
+
1320
+ auto
1321
+ component::execute(
1322
+ const operations::management::bucket_drop_request& request,
1323
+ utils::movable_function<void(operations::management::bucket_drop_response)>&& handler)
1324
+ -> pending_call
1325
+ {
1326
+ const auto client_context_id = request.client_context_id.value_or(std::string{});
1327
+ auto proto = std::make_shared<bucket_admin_v1::DeleteBucketRequest>();
1328
+ proto->set_bucket_name(request.name);
1329
+ const auto timeout = request.timeout.value_or(timeouts_.management);
1330
+ if (timeout <= std::chrono::milliseconds::zero()) {
1331
+ return fail_expired_ctx(io_, handler, stamped_management(request));
1332
+ }
1333
+ const auto auth = authorization_;
1334
+ auto* stub = stubs_->bucket_admin.get();
1335
+ return dispatcher_.unary<bucket_admin_v1::DeleteBucketResponse>(
1336
+ timeout,
1337
+ [stub, proto, auth](grpc::ClientContext& ctx,
1338
+ bucket_admin_v1::DeleteBucketResponse& resp,
1339
+ std::function<void(grpc::Status)> cb) {
1340
+ if (!auth.empty()) {
1341
+ ctx.AddMetadata("authorization", auth);
1342
+ }
1343
+ stub->async()->DeleteBucket(&ctx, proto.get(), &resp, std::move(cb));
1344
+ },
1345
+ [handler = std::move(handler), proto, client_context_id](
1346
+ grpc::Status status, bucket_admin_v1::DeleteBucketResponse /* resp */) mutable {
1347
+ (void)proto; // kept only to keep the request alive for the call
1348
+ operations::management::bucket_drop_response response;
1349
+ response.ctx.client_context_id = client_context_id;
1350
+ response.ctx.ec = map_status(status, operation_kind::mutating);
1351
+ handler(std::move(response));
1352
+ });
1353
+ }
1354
+
1355
+ auto
1356
+ component::execute(
1357
+ const operations::management::bucket_flush_request& request,
1358
+ utils::movable_function<void(operations::management::bucket_flush_response)>&& handler)
1359
+ -> pending_call
1360
+ {
1361
+ const auto client_context_id = request.client_context_id.value_or(std::string{});
1362
+ auto proto = std::make_shared<bucket_admin_v1::FlushBucketRequest>();
1363
+ proto->set_bucket_name(request.name);
1364
+ const auto timeout = request.timeout.value_or(timeouts_.management);
1365
+ if (timeout <= std::chrono::milliseconds::zero()) {
1366
+ return fail_expired_ctx(io_, handler, stamped_management(request));
1367
+ }
1368
+ const auto auth = authorization_;
1369
+ auto* stub = stubs_->bucket_admin.get();
1370
+ return dispatcher_.unary<bucket_admin_v1::FlushBucketResponse>(
1371
+ timeout,
1372
+ [stub, proto, auth](grpc::ClientContext& ctx,
1373
+ bucket_admin_v1::FlushBucketResponse& resp,
1374
+ std::function<void(grpc::Status)> cb) {
1375
+ if (!auth.empty()) {
1376
+ ctx.AddMetadata("authorization", auth);
1377
+ }
1378
+ stub->async()->FlushBucket(&ctx, proto.get(), &resp, std::move(cb));
1379
+ },
1380
+ [handler = std::move(handler), proto, client_context_id](
1381
+ grpc::Status status, bucket_admin_v1::FlushBucketResponse /* resp */) mutable {
1382
+ (void)proto; // kept only to keep the request alive for the call
1383
+ operations::management::bucket_flush_response response;
1384
+ response.ctx.client_context_id = client_context_id;
1385
+ response.ctx.ec = map_status(status, operation_kind::mutating);
1386
+ handler(std::move(response));
1387
+ });
1388
+ }
1389
+
1390
+ auto
1391
+ component::execute(
1392
+ const operations::management::scope_get_all_request& request,
1393
+ utils::movable_function<void(operations::management::scope_get_all_response)>&& handler)
1394
+ -> pending_call
1395
+ {
1396
+ const auto client_context_id = request.client_context_id.value_or(std::string{});
1397
+ auto proto = std::make_shared<collection_admin_v1::ListCollectionsRequest>();
1398
+ proto->set_bucket_name(request.bucket_name);
1399
+ const auto timeout = request.timeout.value_or(timeouts_.management);
1400
+ if (timeout <= std::chrono::milliseconds::zero()) {
1401
+ return fail_expired_ctx(io_, handler, stamped_management(request));
1402
+ }
1403
+ const auto auth = authorization_;
1404
+ auto* stub = stubs_->collection_admin.get();
1405
+ return dispatcher_.unary<collection_admin_v1::ListCollectionsResponse>(
1406
+ timeout,
1407
+ [stub, proto, auth](grpc::ClientContext& ctx,
1408
+ collection_admin_v1::ListCollectionsResponse& resp,
1409
+ std::function<void(grpc::Status)> cb) {
1410
+ if (!auth.empty()) {
1411
+ ctx.AddMetadata("authorization", auth);
1412
+ }
1413
+ stub->async()->ListCollections(&ctx, proto.get(), &resp, std::move(cb));
1414
+ },
1415
+ [handler = std::move(handler), proto, client_context_id](
1416
+ grpc::Status status, collection_admin_v1::ListCollectionsResponse resp) mutable {
1417
+ (void)proto; // kept only to keep the request alive for the call
1418
+ operations::management::scope_get_all_response response;
1419
+ response.ctx.client_context_id = client_context_id;
1420
+ response.ctx.ec = map_status(status, operation_kind::read_only);
1421
+ if (!response.ctx.ec) {
1422
+ response.manifest = collection_admin::decode_manifest(resp);
1423
+ }
1424
+ handler(std::move(response));
1425
+ });
1426
+ }
1427
+
1428
+ auto
1429
+ component::execute(
1430
+ const operations::management::scope_create_request& request,
1431
+ utils::movable_function<void(operations::management::scope_create_response)>&& handler)
1432
+ -> pending_call
1433
+ {
1434
+ const auto client_context_id = request.client_context_id.value_or(std::string{});
1435
+ auto proto = std::make_shared<collection_admin_v1::CreateScopeRequest>();
1436
+ proto->set_bucket_name(request.bucket_name);
1437
+ proto->set_scope_name(request.scope_name);
1438
+ const auto timeout = request.timeout.value_or(timeouts_.management);
1439
+ if (timeout <= std::chrono::milliseconds::zero()) {
1440
+ return fail_expired_ctx(io_, handler, stamped_management(request));
1441
+ }
1442
+ const auto auth = authorization_;
1443
+ auto* stub = stubs_->collection_admin.get();
1444
+ return dispatcher_.unary<collection_admin_v1::CreateScopeResponse>(
1445
+ timeout,
1446
+ [stub, proto, auth](grpc::ClientContext& ctx,
1447
+ collection_admin_v1::CreateScopeResponse& resp,
1448
+ std::function<void(grpc::Status)> cb) {
1449
+ if (!auth.empty()) {
1450
+ ctx.AddMetadata("authorization", auth);
1451
+ }
1452
+ stub->async()->CreateScope(&ctx, proto.get(), &resp, std::move(cb));
1453
+ },
1454
+ [handler = std::move(handler), proto, client_context_id](
1455
+ grpc::Status status, collection_admin_v1::CreateScopeResponse resp) mutable {
1456
+ (void)proto; // kept only to keep the request alive for the call
1457
+ operations::management::scope_create_response response;
1458
+ response.ctx.client_context_id = client_context_id;
1459
+ response.ctx.ec = map_status(status, operation_kind::mutating);
1460
+ response.uid = resp.manifest_uid();
1461
+ handler(std::move(response));
1462
+ });
1463
+ }
1464
+
1465
+ auto
1466
+ component::execute(
1467
+ const operations::management::scope_drop_request& request,
1468
+ utils::movable_function<void(operations::management::scope_drop_response)>&& handler)
1469
+ -> pending_call
1470
+ {
1471
+ const auto client_context_id = request.client_context_id.value_or(std::string{});
1472
+ auto proto = std::make_shared<collection_admin_v1::DeleteScopeRequest>();
1473
+ proto->set_bucket_name(request.bucket_name);
1474
+ proto->set_scope_name(request.scope_name);
1475
+ const auto timeout = request.timeout.value_or(timeouts_.management);
1476
+ if (timeout <= std::chrono::milliseconds::zero()) {
1477
+ return fail_expired_ctx(io_, handler, stamped_management(request));
1478
+ }
1479
+ const auto auth = authorization_;
1480
+ auto* stub = stubs_->collection_admin.get();
1481
+ return dispatcher_.unary<collection_admin_v1::DeleteScopeResponse>(
1482
+ timeout,
1483
+ [stub, proto, auth](grpc::ClientContext& ctx,
1484
+ collection_admin_v1::DeleteScopeResponse& resp,
1485
+ std::function<void(grpc::Status)> cb) {
1486
+ if (!auth.empty()) {
1487
+ ctx.AddMetadata("authorization", auth);
1488
+ }
1489
+ stub->async()->DeleteScope(&ctx, proto.get(), &resp, std::move(cb));
1490
+ },
1491
+ [handler = std::move(handler), proto, client_context_id](
1492
+ grpc::Status status, collection_admin_v1::DeleteScopeResponse resp) mutable {
1493
+ (void)proto; // kept only to keep the request alive for the call
1494
+ operations::management::scope_drop_response response;
1495
+ response.ctx.client_context_id = client_context_id;
1496
+ response.ctx.ec = map_status(status, operation_kind::mutating);
1497
+ response.uid = resp.manifest_uid();
1498
+ handler(std::move(response));
1499
+ });
1500
+ }
1501
+
1502
+ auto
1503
+ component::execute(
1504
+ const operations::management::collection_create_request& request,
1505
+ utils::movable_function<void(operations::management::collection_create_response)>&& handler)
1506
+ -> pending_call
1507
+ {
1508
+ const auto client_context_id = request.client_context_id.value_or(std::string{});
1509
+ // -1 is the lowest value the sign carries a meaning for, and the classic path refuses anything
1510
+ // below it before encoding (collection_create.cxx:39-44). encode_max_expiry maps every negative
1511
+ // onto the no-expiry wire form, so without this the same request would be invalid over
1512
+ // couchbase:// and create a never-expiring collection over couchbase2://.
1513
+ if (request.max_expiry.value_or(0) < -1) {
1514
+ auto response = stamped_management(request);
1515
+ response.ctx.ec = errc::common::invalid_argument;
1516
+ asio::post(io_, [handler = std::move(handler), response = std::move(response)]() mutable {
1517
+ handler(std::move(response));
1518
+ });
1519
+ return {};
1520
+ }
1521
+
1522
+ auto proto = std::make_shared<collection_admin_v1::CreateCollectionRequest>();
1523
+ proto->set_bucket_name(request.bucket_name);
1524
+ proto->set_scope_name(request.scope_name);
1525
+ proto->set_collection_name(request.collection_name);
1526
+ // A created collection inherits the bucket default from an unset max_expiry_secs, which is what
1527
+ // a core max_expiry of 0 asks for, so encode_max_expiry declining to produce a value is already
1528
+ // the right wire form here.
1529
+ if (request.max_expiry.has_value()) {
1530
+ if (const auto secs = collection_admin::encode_max_expiry(*request.max_expiry);
1531
+ secs.has_value()) {
1532
+ proto->set_max_expiry_secs(secs.value());
1533
+ }
1534
+ }
1535
+ if (request.history.has_value()) {
1536
+ proto->set_history_retention_enabled(*request.history);
1537
+ }
1538
+ const auto timeout = request.timeout.value_or(timeouts_.management);
1539
+ if (timeout <= std::chrono::milliseconds::zero()) {
1540
+ return fail_expired_ctx(io_, handler, stamped_management(request));
1541
+ }
1542
+ const auto auth = authorization_;
1543
+ auto* stub = stubs_->collection_admin.get();
1544
+ return dispatcher_.unary<collection_admin_v1::CreateCollectionResponse>(
1545
+ timeout,
1546
+ [stub, proto, auth](grpc::ClientContext& ctx,
1547
+ collection_admin_v1::CreateCollectionResponse& resp,
1548
+ std::function<void(grpc::Status)> cb) {
1549
+ if (!auth.empty()) {
1550
+ ctx.AddMetadata("authorization", auth);
1551
+ }
1552
+ stub->async()->CreateCollection(&ctx, proto.get(), &resp, std::move(cb));
1553
+ },
1554
+ [handler = std::move(handler), proto, client_context_id](
1555
+ grpc::Status status, collection_admin_v1::CreateCollectionResponse resp) mutable {
1556
+ (void)proto; // kept only to keep the request alive for the call
1557
+ operations::management::collection_create_response response;
1558
+ response.ctx.client_context_id = client_context_id;
1559
+ response.ctx.ec = map_status(status, operation_kind::mutating);
1560
+ response.uid = resp.manifest_uid();
1561
+ handler(std::move(response));
1562
+ });
1563
+ }
1564
+
1565
+ auto
1566
+ component::execute(
1567
+ const operations::management::collection_update_request& request,
1568
+ utils::movable_function<void(operations::management::collection_update_response)>&& handler)
1569
+ -> pending_call
1570
+ {
1571
+ const auto client_context_id = request.client_context_id.value_or(std::string{});
1572
+ // The sentinel translation create does, minus the one value update cannot express, and with the
1573
+ // same lower bound. Below -1 the classic path refuses the request (collection_update.cxx:39-45).
1574
+ // An explicit 0 is the reset to the bucket's own expiry that the classic path sends as maxTTL=0,
1575
+ // and there is no wire form left for it: an explicit 0 already means "no expiry", and an unset
1576
+ // field means "leave the collection as it is" rather than "inherit the bucket default". Refuse
1577
+ // it, because reporting success for a setting the gateway was never asked to apply would leave
1578
+ // the caller's documents expiring on the old policy.
1579
+ if (request.max_expiry.has_value()) {
1580
+ std::error_code refusal{};
1581
+ if (*request.max_expiry < -1) {
1582
+ refusal = errc::common::invalid_argument;
1583
+ } else if (*request.max_expiry == 0) {
1584
+ refusal = errc::common::feature_not_available;
1585
+ }
1586
+ if (refusal) {
1587
+ auto response = stamped_management(request);
1588
+ response.ctx.ec = refusal;
1589
+ // Posted rather than invoked inline, so the completion arrives on the SDK's execution context
1590
+ // after the caller holds its pending_call.
1591
+ asio::post(io_, [handler = std::move(handler), response = std::move(response)]() mutable {
1592
+ handler(std::move(response));
1593
+ });
1594
+ return {};
1595
+ }
1596
+ }
1597
+
1598
+ auto proto = std::make_shared<collection_admin_v1::UpdateCollectionRequest>();
1599
+ proto->set_bucket_name(request.bucket_name);
1600
+ proto->set_scope_name(request.scope_name);
1601
+ proto->set_collection_name(request.collection_name);
1602
+ // Zero is refused above, so what reaches here is a no-expiry sentinel or a positive TTL, and
1603
+ // both of those encode to a value.
1604
+ if (request.max_expiry.has_value()) {
1605
+ if (const auto secs = collection_admin::encode_max_expiry(*request.max_expiry);
1606
+ secs.has_value()) {
1607
+ proto->set_max_expiry_secs(secs.value());
1608
+ }
1609
+ }
1610
+ if (request.history.has_value()) {
1611
+ proto->set_history_retention_enabled(*request.history);
1612
+ }
1613
+ const auto timeout = request.timeout.value_or(timeouts_.management);
1614
+ if (timeout <= std::chrono::milliseconds::zero()) {
1615
+ return fail_expired_ctx(io_, handler, stamped_management(request));
1616
+ }
1617
+ const auto auth = authorization_;
1618
+ auto* stub = stubs_->collection_admin.get();
1619
+ return dispatcher_.unary<collection_admin_v1::UpdateCollectionResponse>(
1620
+ timeout,
1621
+ [stub, proto, auth](grpc::ClientContext& ctx,
1622
+ collection_admin_v1::UpdateCollectionResponse& resp,
1623
+ std::function<void(grpc::Status)> cb) {
1624
+ if (!auth.empty()) {
1625
+ ctx.AddMetadata("authorization", auth);
1626
+ }
1627
+ stub->async()->UpdateCollection(&ctx, proto.get(), &resp, std::move(cb));
1628
+ },
1629
+ [handler = std::move(handler), proto, client_context_id](
1630
+ grpc::Status status, collection_admin_v1::UpdateCollectionResponse resp) mutable {
1631
+ (void)proto; // kept only to keep the request alive for the call
1632
+ operations::management::collection_update_response response;
1633
+ response.ctx.client_context_id = client_context_id;
1634
+ response.ctx.ec = map_status(status, operation_kind::mutating);
1635
+ response.uid = resp.manifest_uid();
1636
+ handler(std::move(response));
1637
+ });
1638
+ }
1639
+
1640
+ auto
1641
+ component::execute(
1642
+ const operations::management::collection_drop_request& request,
1643
+ utils::movable_function<void(operations::management::collection_drop_response)>&& handler)
1644
+ -> pending_call
1645
+ {
1646
+ const auto client_context_id = request.client_context_id.value_or(std::string{});
1647
+ auto proto = std::make_shared<collection_admin_v1::DeleteCollectionRequest>();
1648
+ proto->set_bucket_name(request.bucket_name);
1649
+ proto->set_scope_name(request.scope_name);
1650
+ proto->set_collection_name(request.collection_name);
1651
+ const auto timeout = request.timeout.value_or(timeouts_.management);
1652
+ if (timeout <= std::chrono::milliseconds::zero()) {
1653
+ return fail_expired_ctx(io_, handler, stamped_management(request));
1654
+ }
1655
+ const auto auth = authorization_;
1656
+ auto* stub = stubs_->collection_admin.get();
1657
+ return dispatcher_.unary<collection_admin_v1::DeleteCollectionResponse>(
1658
+ timeout,
1659
+ [stub, proto, auth](grpc::ClientContext& ctx,
1660
+ collection_admin_v1::DeleteCollectionResponse& resp,
1661
+ std::function<void(grpc::Status)> cb) {
1662
+ if (!auth.empty()) {
1663
+ ctx.AddMetadata("authorization", auth);
1664
+ }
1665
+ stub->async()->DeleteCollection(&ctx, proto.get(), &resp, std::move(cb));
1666
+ },
1667
+ [handler = std::move(handler), proto, client_context_id](
1668
+ grpc::Status status, collection_admin_v1::DeleteCollectionResponse resp) mutable {
1669
+ (void)proto; // kept only to keep the request alive for the call
1670
+ operations::management::collection_drop_response response;
1671
+ response.ctx.client_context_id = client_context_id;
1672
+ response.ctx.ec = map_status(status, operation_kind::mutating);
1673
+ response.uid = resp.manifest_uid();
1674
+ handler(std::move(response));
1675
+ });
1676
+ }
1677
+
1678
+ auto
1679
+ component::execute(
1680
+ const operations::management::query_index_get_all_request& request,
1681
+ utils::movable_function<void(operations::management::query_index_get_all_response)>&& handler)
1682
+ -> pending_call
1683
+ {
1684
+ const auto client_context_id = request.client_context_id.value_or(std::string{});
1685
+ auto proto = std::make_shared<query_admin_v1::GetAllIndexesRequest>(
1686
+ query_index_admin::encode_get_all(request));
1687
+ const auto timeout = request.timeout.value_or(timeouts_.management);
1688
+ if (timeout <= std::chrono::milliseconds::zero()) {
1689
+ return fail_expired_ctx(io_, handler, stamped_management(request));
1690
+ }
1691
+ const auto auth = authorization_;
1692
+ auto* stub = stubs_->query_admin.get();
1693
+ return dispatcher_.unary<query_admin_v1::GetAllIndexesResponse>(
1694
+ timeout,
1695
+ [stub, proto, auth](grpc::ClientContext& ctx,
1696
+ query_admin_v1::GetAllIndexesResponse& resp,
1697
+ std::function<void(grpc::Status)> cb) {
1698
+ if (!auth.empty()) {
1699
+ ctx.AddMetadata("authorization", auth);
1700
+ }
1701
+ stub->async()->GetAllIndexes(&ctx, proto.get(), &resp, std::move(cb));
1702
+ },
1703
+ [handler = std::move(handler), proto, client_context_id](
1704
+ grpc::Status status, query_admin_v1::GetAllIndexesResponse resp) mutable {
1705
+ (void)proto; // kept only to keep the request alive for the call
1706
+ operations::management::query_index_get_all_response response;
1707
+ response.ctx.client_context_id = client_context_id;
1708
+ response.ctx.ec = map_status(status, operation_kind::read_only);
1709
+ for (const auto& index : resp.indexes()) {
1710
+ response.indexes.push_back(query_index_admin::decode_index(index));
1711
+ }
1712
+ handler(std::move(response));
1713
+ });
1714
+ }
1715
+
1716
+ auto
1717
+ component::execute(
1718
+ const operations::management::query_index_create_request& request,
1719
+ utils::movable_function<void(operations::management::query_index_create_response)>&& handler)
1720
+ -> pending_call
1721
+ {
1722
+ const auto client_context_id = request.client_context_id.value_or(std::string{});
1723
+ if (!query_index_admin::can_encode(request)) {
1724
+ auto response = stamped_management(request);
1725
+ response.ctx.ec = errc::common::feature_not_available;
1726
+ // Posted rather than invoked here, for the reason fail_expired above gives: completions arrive
1727
+ // on the io context, never inline out of execute().
1728
+ asio::post(io_, [handler = std::move(handler), response = std::move(response)]() mutable {
1729
+ handler(std::move(response));
1730
+ });
1731
+ return {};
1732
+ }
1733
+ const auto timeout = request.timeout.value_or(timeouts_.management);
1734
+ if (timeout <= std::chrono::milliseconds::zero()) {
1735
+ return fail_expired_ctx(io_, handler, stamped_management(request));
1736
+ }
1737
+ const auto auth = authorization_;
1738
+ auto* stub = stubs_->query_admin.get();
1739
+
1740
+ if (request.is_primary) {
1741
+ auto proto = std::make_shared<query_admin_v1::CreatePrimaryIndexRequest>(
1742
+ query_index_admin::encode_create_primary(request));
1743
+ return dispatcher_.unary<query_admin_v1::CreatePrimaryIndexResponse>(
1744
+ timeout,
1745
+ [stub, proto, auth](grpc::ClientContext& ctx,
1746
+ query_admin_v1::CreatePrimaryIndexResponse& resp,
1747
+ std::function<void(grpc::Status)> cb) {
1748
+ if (!auth.empty()) {
1749
+ ctx.AddMetadata("authorization", auth);
1750
+ }
1751
+ stub->async()->CreatePrimaryIndex(&ctx, proto.get(), &resp, std::move(cb));
1752
+ },
1753
+ [handler = std::move(handler), proto, client_context_id](
1754
+ grpc::Status status, query_admin_v1::CreatePrimaryIndexResponse /* resp */) mutable {
1755
+ (void)proto; // kept only to keep the request alive for the call
1756
+ operations::management::query_index_create_response response;
1757
+ response.ctx.client_context_id = client_context_id;
1758
+ response.ctx.ec = map_status(status, operation_kind::mutating);
1759
+ handler(std::move(response));
1760
+ });
1761
+ }
1762
+
1763
+ auto proto =
1764
+ std::make_shared<query_admin_v1::CreateIndexRequest>(query_index_admin::encode_create(request));
1765
+ return dispatcher_.unary<query_admin_v1::CreateIndexResponse>(
1766
+ timeout,
1767
+ [stub, proto, auth](grpc::ClientContext& ctx,
1768
+ query_admin_v1::CreateIndexResponse& resp,
1769
+ std::function<void(grpc::Status)> cb) {
1770
+ if (!auth.empty()) {
1771
+ ctx.AddMetadata("authorization", auth);
1772
+ }
1773
+ stub->async()->CreateIndex(&ctx, proto.get(), &resp, std::move(cb));
1774
+ },
1775
+ [handler = std::move(handler), proto, client_context_id](
1776
+ grpc::Status status, query_admin_v1::CreateIndexResponse /* resp */) mutable {
1777
+ (void)proto; // kept only to keep the request alive for the call
1778
+ operations::management::query_index_create_response response;
1779
+ response.ctx.client_context_id = client_context_id;
1780
+ response.ctx.ec = map_status(status, operation_kind::mutating);
1781
+ handler(std::move(response));
1782
+ });
1783
+ }
1784
+
1785
+ auto
1786
+ component::execute(
1787
+ const operations::management::query_index_drop_request& request,
1788
+ utils::movable_function<void(operations::management::query_index_drop_response)>&& handler)
1789
+ -> pending_call
1790
+ {
1791
+ const auto client_context_id = request.client_context_id.value_or(std::string{});
1792
+ const auto timeout = request.timeout.value_or(timeouts_.management);
1793
+ if (timeout <= std::chrono::milliseconds::zero()) {
1794
+ return fail_expired_ctx(io_, handler, stamped_management(request));
1795
+ }
1796
+ const auto auth = authorization_;
1797
+ auto* stub = stubs_->query_admin.get();
1798
+
1799
+ if (request.is_primary) {
1800
+ auto proto = std::make_shared<query_admin_v1::DropPrimaryIndexRequest>(
1801
+ query_index_admin::encode_drop_primary(request));
1802
+ return dispatcher_.unary<query_admin_v1::DropPrimaryIndexResponse>(
1803
+ timeout,
1804
+ [stub, proto, auth](grpc::ClientContext& ctx,
1805
+ query_admin_v1::DropPrimaryIndexResponse& resp,
1806
+ std::function<void(grpc::Status)> cb) {
1807
+ if (!auth.empty()) {
1808
+ ctx.AddMetadata("authorization", auth);
1809
+ }
1810
+ stub->async()->DropPrimaryIndex(&ctx, proto.get(), &resp, std::move(cb));
1811
+ },
1812
+ [handler = std::move(handler), proto, client_context_id](
1813
+ grpc::Status status, query_admin_v1::DropPrimaryIndexResponse /* resp */) mutable {
1814
+ (void)proto; // kept only to keep the request alive for the call
1815
+ operations::management::query_index_drop_response response;
1816
+ response.ctx.client_context_id = client_context_id;
1817
+ response.ctx.ec = map_status(status, operation_kind::mutating);
1818
+ handler(std::move(response));
1819
+ });
1820
+ }
1821
+
1822
+ auto proto =
1823
+ std::make_shared<query_admin_v1::DropIndexRequest>(query_index_admin::encode_drop(request));
1824
+ return dispatcher_.unary<query_admin_v1::DropIndexResponse>(
1825
+ timeout,
1826
+ [stub, proto, auth](grpc::ClientContext& ctx,
1827
+ query_admin_v1::DropIndexResponse& resp,
1828
+ std::function<void(grpc::Status)> cb) {
1829
+ if (!auth.empty()) {
1830
+ ctx.AddMetadata("authorization", auth);
1831
+ }
1832
+ stub->async()->DropIndex(&ctx, proto.get(), &resp, std::move(cb));
1833
+ },
1834
+ [handler = std::move(handler), proto, client_context_id](
1835
+ grpc::Status status, query_admin_v1::DropIndexResponse /* resp */) mutable {
1836
+ (void)proto; // kept only to keep the request alive for the call
1837
+ operations::management::query_index_drop_response response;
1838
+ response.ctx.client_context_id = client_context_id;
1839
+ response.ctx.ec = map_status(status, operation_kind::mutating);
1840
+ handler(std::move(response));
1841
+ });
1842
+ }
1843
+
1844
+ auto
1845
+ component::execute(
1846
+ const operations::management::query_index_build_deferred_request& request,
1847
+ utils::movable_function<void(operations::management::query_index_build_deferred_response)>&&
1848
+ handler) -> pending_call
1849
+ {
1850
+ const auto client_context_id = request.client_context_id.value_or(std::string{});
1851
+ auto proto = std::make_shared<query_admin_v1::BuildDeferredIndexesRequest>(
1852
+ query_index_admin::encode_build_deferred(request));
1853
+ const auto timeout = request.timeout.value_or(timeouts_.management);
1854
+ if (timeout <= std::chrono::milliseconds::zero()) {
1855
+ return fail_expired_ctx(io_, handler, stamped_management(request));
1856
+ }
1857
+ const auto auth = authorization_;
1858
+ auto* stub = stubs_->query_admin.get();
1859
+ return dispatcher_.unary<query_admin_v1::BuildDeferredIndexesResponse>(
1860
+ timeout,
1861
+ [stub, proto, auth](grpc::ClientContext& ctx,
1862
+ query_admin_v1::BuildDeferredIndexesResponse& resp,
1863
+ std::function<void(grpc::Status)> cb) {
1864
+ if (!auth.empty()) {
1865
+ ctx.AddMetadata("authorization", auth);
1866
+ }
1867
+ stub->async()->BuildDeferredIndexes(&ctx, proto.get(), &resp, std::move(cb));
1868
+ },
1869
+ [handler = std::move(handler), proto, client_context_id](
1870
+ grpc::Status status, query_admin_v1::BuildDeferredIndexesResponse /* resp */) mutable {
1871
+ (void)proto; // kept only to keep the request alive for the call
1872
+ operations::management::query_index_build_deferred_response response;
1873
+ response.ctx.client_context_id = client_context_id;
1874
+ response.ctx.ec = map_status(status, operation_kind::mutating);
1875
+ handler(std::move(response));
1876
+ });
1877
+ }
1878
+
1879
+ auto
1880
+ component::execute(
1881
+ const operations::management::search_index_upsert_request& request,
1882
+ utils::movable_function<void(operations::management::search_index_upsert_response)>&& handler)
1883
+ -> pending_call
1884
+ {
1885
+ const auto client_context_id = request.client_context_id.value_or(std::string{});
1886
+ if (request.index.name.empty()) {
1887
+ return fail_ctx(io_, handler, stamped_management(request), errc::common::invalid_argument);
1888
+ }
1889
+ const auto timeout = request.timeout.value_or(timeouts_.management);
1890
+ if (timeout <= std::chrono::milliseconds::zero()) {
1891
+ return fail_expired_ctx(io_, handler, stamped_management(request));
1892
+ }
1893
+ const auto auth = authorization_;
1894
+ const auto name = request.index.name;
1895
+ auto* stub = stubs_->search_admin.get();
1896
+
1897
+ // A uuid identifies a definition that already exists, so an upsert carrying one is an update and
1898
+ // an upsert without one is a create. That is the same rule the classic path applies -- it sends
1899
+ // the uuid as the body's "uuid" member, which FTS reads as prevIndexUUID -- and the two RPCs
1900
+ // divide along the same line: UpdateIndex refuses a request with no uuid, and CreateIndex refuses
1901
+ // a name that already exists.
1902
+ //
1903
+ // The proto is built before the handler is moved into the completion, so a definition the
1904
+ // converter refuses is still reported through the handler the caller passed.
1905
+ std::shared_ptr<search_admin_v1::UpdateIndexRequest> update_proto;
1906
+ std::shared_ptr<search_admin_v1::CreateIndexRequest> create_proto;
1907
+ if (!request.index.uuid.empty()) {
1908
+ update_proto = std::make_shared<search_admin_v1::UpdateIndexRequest>();
1909
+ if (!search_index_admin::apply_index(request.index, *update_proto->mutable_index())) {
1910
+ return fail_ctx(io_, handler, stamped_management(request), errc::common::invalid_argument);
1911
+ }
1912
+ if (request.bucket_name.has_value()) {
1913
+ update_proto->set_bucket_name(*request.bucket_name);
1914
+ }
1915
+ if (request.scope_name.has_value()) {
1916
+ update_proto->set_scope_name(*request.scope_name);
1917
+ }
1918
+ } else {
1919
+ create_proto = std::make_shared<search_admin_v1::CreateIndexRequest>();
1920
+ if (!search_index_admin::apply_index(request.index, *create_proto)) {
1921
+ return fail_ctx(io_, handler, stamped_management(request), errc::common::invalid_argument);
1922
+ }
1923
+ if (request.bucket_name.has_value()) {
1924
+ create_proto->set_bucket_name(*request.bucket_name);
1925
+ }
1926
+ if (request.scope_name.has_value()) {
1927
+ create_proto->set_scope_name(*request.scope_name);
1928
+ }
1929
+ }
1930
+
1931
+ auto invoke =
1932
+ [handler = std::move(handler), client_context_id, name](grpc::Status status) mutable {
1933
+ operations::management::search_index_upsert_response response;
1934
+ response.ctx.client_context_id = client_context_id;
1935
+ response.ctx.ec = map_status(status, operation_kind::mutating);
1936
+ response.name = name;
1937
+ set_ok_status(response);
1938
+ handler(std::move(response));
1939
+ };
1940
+
1941
+ if (update_proto) {
1942
+ return dispatcher_.unary<search_admin_v1::UpdateIndexResponse>(
1943
+ timeout,
1944
+ [stub, proto = update_proto, auth](grpc::ClientContext& ctx,
1945
+ search_admin_v1::UpdateIndexResponse& resp,
1946
+ std::function<void(grpc::Status)> cb) {
1947
+ if (!auth.empty()) {
1948
+ ctx.AddMetadata("authorization", auth);
1949
+ }
1950
+ stub->async()->UpdateIndex(&ctx, proto.get(), &resp, std::move(cb));
1951
+ },
1952
+ [invoke = std::move(invoke), proto = update_proto](
1953
+ grpc::Status status, search_admin_v1::UpdateIndexResponse /* r */) mutable {
1954
+ invoke(std::move(status));
1955
+ });
1956
+ }
1957
+ return dispatcher_.unary<search_admin_v1::CreateIndexResponse>(
1958
+ timeout,
1959
+ [stub, proto = create_proto, auth](grpc::ClientContext& ctx,
1960
+ search_admin_v1::CreateIndexResponse& resp,
1961
+ std::function<void(grpc::Status)> cb) {
1962
+ if (!auth.empty()) {
1963
+ ctx.AddMetadata("authorization", auth);
1964
+ }
1965
+ stub->async()->CreateIndex(&ctx, proto.get(), &resp, std::move(cb));
1966
+ },
1967
+ [invoke = std::move(invoke), proto = create_proto](
1968
+ grpc::Status status, search_admin_v1::CreateIndexResponse /* r */) mutable {
1969
+ invoke(std::move(status));
1970
+ });
1971
+ }
1972
+
1973
+ auto
1974
+ component::execute(
1975
+ const operations::management::search_index_get_request& request,
1976
+ utils::movable_function<void(operations::management::search_index_get_response)>&& handler)
1977
+ -> pending_call
1978
+ {
1979
+ const auto client_context_id = request.client_context_id.value_or(std::string{});
1980
+ if (request.index_name.empty()) {
1981
+ return fail_ctx(io_, handler, stamped_management(request), errc::common::invalid_argument);
1982
+ }
1983
+ auto proto = std::make_shared<search_admin_v1::GetIndexRequest>();
1984
+ proto->set_name(request.index_name);
1985
+ if (request.bucket_name.has_value()) {
1986
+ proto->set_bucket_name(*request.bucket_name);
1987
+ }
1988
+ if (request.scope_name.has_value()) {
1989
+ proto->set_scope_name(*request.scope_name);
1990
+ }
1991
+ const auto timeout = request.timeout.value_or(timeouts_.management);
1992
+ if (timeout <= std::chrono::milliseconds::zero()) {
1993
+ return fail_expired_ctx(io_, handler, stamped_management(request));
1994
+ }
1995
+ const auto auth = authorization_;
1996
+ auto* stub = stubs_->search_admin.get();
1997
+ return dispatcher_.unary<search_admin_v1::GetIndexResponse>(
1998
+ timeout,
1999
+ [stub, proto, auth](grpc::ClientContext& ctx,
2000
+ search_admin_v1::GetIndexResponse& resp,
2001
+ std::function<void(grpc::Status)> cb) {
2002
+ if (!auth.empty()) {
2003
+ ctx.AddMetadata("authorization", auth);
2004
+ }
2005
+ stub->async()->GetIndex(&ctx, proto.get(), &resp, std::move(cb));
2006
+ },
2007
+ [handler = std::move(handler), proto, client_context_id](
2008
+ grpc::Status status, search_admin_v1::GetIndexResponse resp) mutable {
2009
+ (void)proto; // kept only to keep the request alive for the call
2010
+ operations::management::search_index_get_response response;
2011
+ response.ctx.client_context_id = client_context_id;
2012
+ response.ctx.ec = map_status(status, operation_kind::read_only);
2013
+ if (!response.ctx.ec) {
2014
+ // A success carrying no index describes no index. Reporting it as one -- the response
2015
+ // would hold a default-constructed definition with an empty name -- is worse than saying
2016
+ // the index is not there.
2017
+ if (!resp.has_index()) {
2018
+ response.ctx.ec = errc::common::index_not_found;
2019
+ } else if (auto index = search_index_admin::decode_index(resp.index()); index.has_value()) {
2020
+ response.index = std::move(*index);
2021
+ } else {
2022
+ response.ctx.ec = errc::common::parsing_failure;
2023
+ }
2024
+ }
2025
+ set_ok_status(response);
2026
+ handler(std::move(response));
2027
+ });
2028
+ }
2029
+
2030
+ auto
2031
+ component::execute(
2032
+ const operations::management::search_index_get_all_request& request,
2033
+ utils::movable_function<void(operations::management::search_index_get_all_response)>&& handler)
2034
+ -> pending_call
2035
+ {
2036
+ const auto client_context_id = request.client_context_id.value_or(std::string{});
2037
+ auto proto = std::make_shared<search_admin_v1::ListIndexesRequest>();
2038
+ if (request.bucket_name.has_value()) {
2039
+ proto->set_bucket_name(*request.bucket_name);
2040
+ }
2041
+ if (request.scope_name.has_value()) {
2042
+ proto->set_scope_name(*request.scope_name);
2043
+ }
2044
+ const auto timeout = request.timeout.value_or(timeouts_.management);
2045
+ if (timeout <= std::chrono::milliseconds::zero()) {
2046
+ return fail_expired_ctx(io_, handler, stamped_management(request));
2047
+ }
2048
+ const auto auth = authorization_;
2049
+ auto* stub = stubs_->search_admin.get();
2050
+ return dispatcher_.unary<search_admin_v1::ListIndexesResponse>(
2051
+ timeout,
2052
+ [stub, proto, auth](grpc::ClientContext& ctx,
2053
+ search_admin_v1::ListIndexesResponse& resp,
2054
+ std::function<void(grpc::Status)> cb) {
2055
+ if (!auth.empty()) {
2056
+ ctx.AddMetadata("authorization", auth);
2057
+ }
2058
+ stub->async()->ListIndexes(&ctx, proto.get(), &resp, std::move(cb));
2059
+ },
2060
+ [handler = std::move(handler), proto, client_context_id](
2061
+ grpc::Status status, search_admin_v1::ListIndexesResponse resp) mutable {
2062
+ (void)proto; // kept only to keep the request alive for the call
2063
+ operations::management::search_index_get_all_response response;
2064
+ response.ctx.client_context_id = client_context_id;
2065
+ response.ctx.ec = map_status(status, operation_kind::read_only);
2066
+ for (const auto& index : resp.indexes()) {
2067
+ auto decoded = search_index_admin::decode_index(index);
2068
+ if (!decoded.has_value()) {
2069
+ // One definition that cannot be represented fails the listing. Skipping it would return
2070
+ // a list shorter than the server's, which reads as "that index does not exist".
2071
+ response.ctx.ec = errc::common::parsing_failure;
2072
+ response.indexes.clear();
2073
+ break;
2074
+ }
2075
+ response.indexes.push_back(std::move(*decoded));
2076
+ }
2077
+ set_ok_status(response);
2078
+ handler(std::move(response));
2079
+ });
2080
+ }
2081
+
2082
+ auto
2083
+ component::execute(
2084
+ const operations::management::search_index_drop_request& request,
2085
+ utils::movable_function<void(operations::management::search_index_drop_response)>&& handler)
2086
+ -> pending_call
2087
+ {
2088
+ const auto client_context_id = request.client_context_id.value_or(std::string{});
2089
+ if (request.index_name.empty()) {
2090
+ return fail_ctx(io_, handler, stamped_management(request), errc::common::invalid_argument);
2091
+ }
2092
+ auto proto = std::make_shared<search_admin_v1::DeleteIndexRequest>();
2093
+ proto->set_name(request.index_name);
2094
+ if (request.bucket_name.has_value()) {
2095
+ proto->set_bucket_name(*request.bucket_name);
2096
+ }
2097
+ if (request.scope_name.has_value()) {
2098
+ proto->set_scope_name(*request.scope_name);
2099
+ }
2100
+ const auto timeout = request.timeout.value_or(timeouts_.management);
2101
+ if (timeout <= std::chrono::milliseconds::zero()) {
2102
+ return fail_expired_ctx(io_, handler, stamped_management(request));
2103
+ }
2104
+ const auto auth = authorization_;
2105
+ auto* stub = stubs_->search_admin.get();
2106
+ return dispatcher_.unary<search_admin_v1::DeleteIndexResponse>(
2107
+ timeout,
2108
+ [stub, proto, auth](grpc::ClientContext& ctx,
2109
+ search_admin_v1::DeleteIndexResponse& resp,
2110
+ std::function<void(grpc::Status)> cb) {
2111
+ if (!auth.empty()) {
2112
+ ctx.AddMetadata("authorization", auth);
2113
+ }
2114
+ stub->async()->DeleteIndex(&ctx, proto.get(), &resp, std::move(cb));
2115
+ },
2116
+ [handler = std::move(handler), proto, client_context_id](
2117
+ grpc::Status status, search_admin_v1::DeleteIndexResponse /* r */) mutable {
2118
+ (void)proto; // kept only to keep the request alive for the call
2119
+ operations::management::search_index_drop_response response;
2120
+ response.ctx.client_context_id = client_context_id;
2121
+ response.ctx.ec = map_status(status, operation_kind::mutating);
2122
+ set_ok_status(response);
2123
+ handler(std::move(response));
2124
+ });
2125
+ }
2126
+
2127
+ auto
2128
+ component::execute(
2129
+ const operations::management::search_index_analyze_document_request& request,
2130
+ utils::movable_function<void(operations::management::search_index_analyze_document_response)>&&
2131
+ handler) -> pending_call
2132
+ {
2133
+ const auto client_context_id = request.client_context_id.value_or(std::string{});
2134
+ if (request.index_name.empty()) {
2135
+ return fail_ctx(io_, handler, stamped_management(request), errc::common::invalid_argument);
2136
+ }
2137
+ auto proto = std::make_shared<search_admin_v1::AnalyzeDocumentRequest>();
2138
+ proto->set_name(request.index_name);
2139
+ proto->set_doc(request.encoded_document);
2140
+ if (request.bucket_name.has_value()) {
2141
+ proto->set_bucket_name(*request.bucket_name);
2142
+ }
2143
+ if (request.scope_name.has_value()) {
2144
+ proto->set_scope_name(*request.scope_name);
2145
+ }
2146
+ const auto timeout = request.timeout.value_or(timeouts_.management);
2147
+ if (timeout <= std::chrono::milliseconds::zero()) {
2148
+ return fail_expired_ctx(io_, handler, stamped_management(request));
2149
+ }
2150
+ const auto auth = authorization_;
2151
+ auto* stub = stubs_->search_admin.get();
2152
+ return dispatcher_.unary<search_admin_v1::AnalyzeDocumentResponse>(
2153
+ timeout,
2154
+ [stub, proto, auth](grpc::ClientContext& ctx,
2155
+ search_admin_v1::AnalyzeDocumentResponse& resp,
2156
+ std::function<void(grpc::Status)> cb) {
2157
+ if (!auth.empty()) {
2158
+ ctx.AddMetadata("authorization", auth);
2159
+ }
2160
+ stub->async()->AnalyzeDocument(&ctx, proto.get(), &resp, std::move(cb));
2161
+ },
2162
+ [handler = std::move(handler), proto, client_context_id](
2163
+ grpc::Status status, search_admin_v1::AnalyzeDocumentResponse resp) mutable {
2164
+ (void)proto; // kept only to keep the request alive for the call
2165
+ operations::management::search_index_analyze_document_response response;
2166
+ response.ctx.client_context_id = client_context_id;
2167
+ response.ctx.ec = map_status(status, operation_kind::read_only);
2168
+ response.analysis = resp.analyzed();
2169
+ // The only response in this service that relays FTS's own status string; the rest infer it
2170
+ // from the RPC. Prefer what the server said, and fall back only when it said nothing.
2171
+ if (!response.ctx.ec && !resp.status().empty()) {
2172
+ response.status = resp.status();
2173
+ } else {
2174
+ set_ok_status(response);
2175
+ }
2176
+ handler(std::move(response));
2177
+ });
2178
+ }
2179
+
2180
+ auto
2181
+ component::execute(
2182
+ const operations::management::search_index_get_documents_count_request& request,
2183
+ utils::movable_function<void(operations::management::search_index_get_documents_count_response)>&&
2184
+ handler) -> pending_call
2185
+ {
2186
+ const auto client_context_id = request.client_context_id.value_or(std::string{});
2187
+ auto proto = std::make_shared<search_admin_v1::GetIndexedDocumentsCountRequest>();
2188
+ proto->set_name(request.index_name);
2189
+ if (request.bucket_name.has_value()) {
2190
+ proto->set_bucket_name(*request.bucket_name);
2191
+ }
2192
+ if (request.scope_name.has_value()) {
2193
+ proto->set_scope_name(*request.scope_name);
2194
+ }
2195
+ const auto timeout = request.timeout.value_or(timeouts_.management);
2196
+ if (timeout <= std::chrono::milliseconds::zero()) {
2197
+ return fail_expired_ctx(io_, handler, stamped_management(request));
2198
+ }
2199
+ const auto auth = authorization_;
2200
+ auto* stub = stubs_->search_admin.get();
2201
+ return dispatcher_.unary<search_admin_v1::GetIndexedDocumentsCountResponse>(
2202
+ timeout,
2203
+ [stub, proto, auth](grpc::ClientContext& ctx,
2204
+ search_admin_v1::GetIndexedDocumentsCountResponse& resp,
2205
+ std::function<void(grpc::Status)> cb) {
2206
+ if (!auth.empty()) {
2207
+ ctx.AddMetadata("authorization", auth);
2208
+ }
2209
+ stub->async()->GetIndexedDocumentsCount(&ctx, proto.get(), &resp, std::move(cb));
2210
+ },
2211
+ [handler = std::move(handler), proto, client_context_id](
2212
+ grpc::Status status, search_admin_v1::GetIndexedDocumentsCountResponse resp) mutable {
2213
+ (void)proto; // kept only to keep the request alive for the call
2214
+ operations::management::search_index_get_documents_count_response response;
2215
+ response.ctx.client_context_id = client_context_id;
2216
+ response.ctx.ec = map_status(status, operation_kind::read_only);
2217
+ response.count = resp.count();
2218
+ set_ok_status(response);
2219
+ handler(std::move(response));
2220
+ });
2221
+ }
2222
+
2223
+ auto
2224
+ component::execute(
2225
+ const operations::management::search_index_control_ingest_request& request,
2226
+ utils::movable_function<void(operations::management::search_index_control_ingest_response)>&&
2227
+ handler) -> pending_call
2228
+ {
2229
+ const auto client_context_id = request.client_context_id.value_or(std::string{});
2230
+ if (request.index_name.empty()) {
2231
+ return fail_ctx(io_, handler, stamped_management(request), errc::common::invalid_argument);
2232
+ }
2233
+ const auto timeout = request.timeout.value_or(timeouts_.management);
2234
+ if (timeout <= std::chrono::milliseconds::zero()) {
2235
+ return fail_expired_ctx(io_, handler, stamped_management(request));
2236
+ }
2237
+ const auto auth = authorization_;
2238
+ const auto name = request.index_name;
2239
+ const auto bucket = request.bucket_name;
2240
+ const auto scope = request.scope_name;
2241
+ auto* stub = stubs_->search_admin.get();
2242
+ auto invoke = [handler = std::move(handler), client_context_id](grpc::Status status) mutable {
2243
+ operations::management::search_index_control_ingest_response response;
2244
+ response.ctx.client_context_id = client_context_id;
2245
+ response.ctx.ec = map_status(status, operation_kind::mutating);
2246
+ set_ok_status(response);
2247
+ handler(std::move(response));
2248
+ };
2249
+ if (request.pause) {
2250
+ auto proto = std::make_shared<search_admin_v1::PauseIndexIngestRequest>();
2251
+ proto->set_name(name);
2252
+ if (bucket.has_value()) {
2253
+ proto->set_bucket_name(*bucket);
2254
+ }
2255
+ if (scope.has_value()) {
2256
+ proto->set_scope_name(*scope);
2257
+ }
2258
+ return dispatcher_.unary<search_admin_v1::PauseIndexIngestResponse>(
2259
+ timeout,
2260
+ [stub, proto, auth](grpc::ClientContext& ctx,
2261
+ search_admin_v1::PauseIndexIngestResponse& resp,
2262
+ std::function<void(grpc::Status)> cb) {
2263
+ if (!auth.empty()) {
2264
+ ctx.AddMetadata("authorization", auth);
2265
+ }
2266
+ stub->async()->PauseIndexIngest(&ctx, proto.get(), &resp, std::move(cb));
2267
+ },
2268
+ [invoke = std::move(invoke),
2269
+ proto](grpc::Status status, search_admin_v1::PauseIndexIngestResponse /* r */) mutable {
2270
+ invoke(std::move(status));
2271
+ });
2272
+ }
2273
+ auto proto = std::make_shared<search_admin_v1::ResumeIndexIngestRequest>();
2274
+ proto->set_name(name);
2275
+ if (bucket.has_value()) {
2276
+ proto->set_bucket_name(*bucket);
2277
+ }
2278
+ if (scope.has_value()) {
2279
+ proto->set_scope_name(*scope);
2280
+ }
2281
+ return dispatcher_.unary<search_admin_v1::ResumeIndexIngestResponse>(
2282
+ timeout,
2283
+ [stub, proto, auth](grpc::ClientContext& ctx,
2284
+ search_admin_v1::ResumeIndexIngestResponse& resp,
2285
+ std::function<void(grpc::Status)> cb) {
2286
+ if (!auth.empty()) {
2287
+ ctx.AddMetadata("authorization", auth);
2288
+ }
2289
+ stub->async()->ResumeIndexIngest(&ctx, proto.get(), &resp, std::move(cb));
2290
+ },
2291
+ [invoke = std::move(invoke),
2292
+ proto](grpc::Status status, search_admin_v1::ResumeIndexIngestResponse /* r */) mutable {
2293
+ invoke(std::move(status));
2294
+ });
2295
+ }
2296
+
2297
+ auto
2298
+ component::execute(
2299
+ const operations::management::search_index_control_query_request& request,
2300
+ utils::movable_function<void(operations::management::search_index_control_query_response)>&&
2301
+ handler) -> pending_call
2302
+ {
2303
+ const auto client_context_id = request.client_context_id.value_or(std::string{});
2304
+ if (request.index_name.empty()) {
2305
+ return fail_ctx(io_, handler, stamped_management(request), errc::common::invalid_argument);
2306
+ }
2307
+ const auto timeout = request.timeout.value_or(timeouts_.management);
2308
+ if (timeout <= std::chrono::milliseconds::zero()) {
2309
+ return fail_expired_ctx(io_, handler, stamped_management(request));
2310
+ }
2311
+ const auto auth = authorization_;
2312
+ const auto name = request.index_name;
2313
+ const auto bucket = request.bucket_name;
2314
+ const auto scope = request.scope_name;
2315
+ auto* stub = stubs_->search_admin.get();
2316
+ auto invoke = [handler = std::move(handler), client_context_id](grpc::Status status) mutable {
2317
+ operations::management::search_index_control_query_response response;
2318
+ response.ctx.client_context_id = client_context_id;
2319
+ response.ctx.ec = map_status(status, operation_kind::mutating);
2320
+ set_ok_status(response);
2321
+ handler(std::move(response));
2322
+ };
2323
+ if (request.allow) {
2324
+ auto proto = std::make_shared<search_admin_v1::AllowIndexQueryingRequest>();
2325
+ proto->set_name(name);
2326
+ if (bucket.has_value()) {
2327
+ proto->set_bucket_name(*bucket);
2328
+ }
2329
+ if (scope.has_value()) {
2330
+ proto->set_scope_name(*scope);
2331
+ }
2332
+ return dispatcher_.unary<search_admin_v1::AllowIndexQueryingResponse>(
2333
+ timeout,
2334
+ [stub, proto, auth](grpc::ClientContext& ctx,
2335
+ search_admin_v1::AllowIndexQueryingResponse& resp,
2336
+ std::function<void(grpc::Status)> cb) {
2337
+ if (!auth.empty()) {
2338
+ ctx.AddMetadata("authorization", auth);
2339
+ }
2340
+ stub->async()->AllowIndexQuerying(&ctx, proto.get(), &resp, std::move(cb));
2341
+ },
2342
+ [invoke = std::move(invoke),
2343
+ proto](grpc::Status status, search_admin_v1::AllowIndexQueryingResponse /* r */) mutable {
2344
+ invoke(std::move(status));
2345
+ });
2346
+ }
2347
+ auto proto = std::make_shared<search_admin_v1::DisallowIndexQueryingRequest>();
2348
+ proto->set_name(name);
2349
+ if (bucket.has_value()) {
2350
+ proto->set_bucket_name(*bucket);
2351
+ }
2352
+ if (scope.has_value()) {
2353
+ proto->set_scope_name(*scope);
2354
+ }
2355
+ return dispatcher_.unary<search_admin_v1::DisallowIndexQueryingResponse>(
2356
+ timeout,
2357
+ [stub, proto, auth](grpc::ClientContext& ctx,
2358
+ search_admin_v1::DisallowIndexQueryingResponse& resp,
2359
+ std::function<void(grpc::Status)> cb) {
2360
+ if (!auth.empty()) {
2361
+ ctx.AddMetadata("authorization", auth);
2362
+ }
2363
+ stub->async()->DisallowIndexQuerying(&ctx, proto.get(), &resp, std::move(cb));
2364
+ },
2365
+ [invoke = std::move(invoke),
2366
+ proto](grpc::Status status, search_admin_v1::DisallowIndexQueryingResponse /* r */) mutable {
2367
+ invoke(std::move(status));
2368
+ });
2369
+ }
2370
+
2371
+ auto
2372
+ component::execute(
2373
+ const operations::management::search_index_control_plan_freeze_request& request,
2374
+ utils::movable_function<void(operations::management::search_index_control_plan_freeze_response)>&&
2375
+ handler) -> pending_call
2376
+ {
2377
+ const auto client_context_id = request.client_context_id.value_or(std::string{});
2378
+ if (request.index_name.empty()) {
2379
+ return fail_ctx(io_, handler, stamped_management(request), errc::common::invalid_argument);
2380
+ }
2381
+ const auto timeout = request.timeout.value_or(timeouts_.management);
2382
+ if (timeout <= std::chrono::milliseconds::zero()) {
2383
+ return fail_expired_ctx(io_, handler, stamped_management(request));
2384
+ }
2385
+ const auto auth = authorization_;
2386
+ const auto name = request.index_name;
2387
+ const auto bucket = request.bucket_name;
2388
+ const auto scope = request.scope_name;
2389
+ auto* stub = stubs_->search_admin.get();
2390
+ auto invoke = [handler = std::move(handler), client_context_id](grpc::Status status) mutable {
2391
+ operations::management::search_index_control_plan_freeze_response response;
2392
+ response.ctx.client_context_id = client_context_id;
2393
+ response.ctx.ec = map_status(status, operation_kind::mutating);
2394
+ set_ok_status(response);
2395
+ handler(std::move(response));
2396
+ };
2397
+ if (request.freeze) {
2398
+ auto proto = std::make_shared<search_admin_v1::FreezeIndexPlanRequest>();
2399
+ proto->set_name(name);
2400
+ if (bucket.has_value()) {
2401
+ proto->set_bucket_name(*bucket);
2402
+ }
2403
+ if (scope.has_value()) {
2404
+ proto->set_scope_name(*scope);
2405
+ }
2406
+ return dispatcher_.unary<search_admin_v1::FreezeIndexPlanResponse>(
2407
+ timeout,
2408
+ [stub, proto, auth](grpc::ClientContext& ctx,
2409
+ search_admin_v1::FreezeIndexPlanResponse& resp,
2410
+ std::function<void(grpc::Status)> cb) {
2411
+ if (!auth.empty()) {
2412
+ ctx.AddMetadata("authorization", auth);
2413
+ }
2414
+ stub->async()->FreezeIndexPlan(&ctx, proto.get(), &resp, std::move(cb));
2415
+ },
2416
+ [invoke = std::move(invoke),
2417
+ proto](grpc::Status status, search_admin_v1::FreezeIndexPlanResponse /* r */) mutable {
2418
+ invoke(std::move(status));
2419
+ });
2420
+ }
2421
+ auto proto = std::make_shared<search_admin_v1::UnfreezeIndexPlanRequest>();
2422
+ proto->set_name(name);
2423
+ if (bucket.has_value()) {
2424
+ proto->set_bucket_name(*bucket);
2425
+ }
2426
+ if (scope.has_value()) {
2427
+ proto->set_scope_name(*scope);
2428
+ }
2429
+ return dispatcher_.unary<search_admin_v1::UnfreezeIndexPlanResponse>(
2430
+ timeout,
2431
+ [stub, proto, auth](grpc::ClientContext& ctx,
2432
+ search_admin_v1::UnfreezeIndexPlanResponse& resp,
2433
+ std::function<void(grpc::Status)> cb) {
2434
+ if (!auth.empty()) {
2435
+ ctx.AddMetadata("authorization", auth);
2436
+ }
2437
+ stub->async()->UnfreezeIndexPlan(&ctx, proto.get(), &resp, std::move(cb));
2438
+ },
2439
+ [invoke = std::move(invoke),
2440
+ proto](grpc::Status status, search_admin_v1::UnfreezeIndexPlanResponse /* r */) mutable {
2441
+ invoke(std::move(status));
2442
+ });
2443
+ }
2444
+
2445
+ } // namespace couchbase::core::protostellar