couchbase 3.8.1 → 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.
- checksums.yaml +4 -4
- data/README.md +2 -2
- data/ext/CMakeLists.txt +16 -1
- data/ext/cache/cpm/{CPM_0.42.0.cmake → CPM_0.43.1.cmake} +18 -2
- data/ext/cache/extconf_include.rb +6 -6
- data/ext/cache/mozilla-ca-bundle.crt +214 -155
- data/ext/cache/mozilla-ca-bundle.sha256 +1 -1
- data/ext/couchbase/CMakeLists.txt +130 -21
- data/ext/couchbase/cmake/APKBUILD.in +25 -4
- data/ext/couchbase/cmake/Bundler.cmake +143 -34
- data/ext/couchbase/cmake/CPM.cmake +126 -12
- data/ext/couchbase/cmake/Cache.cmake +7 -1
- data/ext/couchbase/cmake/CompilerOptions.cmake +1 -1
- data/ext/couchbase/cmake/CompilerWarnings.cmake +17 -6
- data/ext/couchbase/cmake/GrpcProtobuf.cmake +481 -0
- data/ext/couchbase/cmake/OpenSSL.cmake +10 -3
- data/ext/couchbase/cmake/Packaging.cmake +519 -65
- data/ext/couchbase/cmake/Protostellar.cmake +250 -0
- data/ext/couchbase/cmake/Sanitizers.cmake +61 -50
- data/ext/couchbase/cmake/StaticAnalyzers.cmake +16 -5
- data/ext/couchbase/cmake/TestFramework.cmake +283 -0
- data/ext/couchbase/cmake/TestFrameworkAddTests.cmake +51 -0
- data/ext/couchbase/cmake/Testing.cmake +27 -9
- data/ext/couchbase/cmake/ThirdPartyDependencies.cmake +141 -8
- data/ext/couchbase/cmake/VersionInfo.cmake +50 -0
- data/ext/couchbase/cmake/build_config.hxx.in +2 -1
- data/ext/couchbase/cmake/couchbase-cxx-client.spec.in +101 -23
- data/ext/couchbase/cmake/couchbase_cxx_client_static-config.cmake.in +7 -0
- data/ext/couchbase/cmake/couchbase_cxx_client_static.pc.in +2 -0
- data/ext/couchbase/cmake/gRPC_Generate.cmake +58 -0
- data/ext/couchbase/cmake/protobuf_Generate.cmake +69 -0
- data/ext/couchbase/cmake/require-git-checkout.cmake +25 -0
- data/ext/couchbase/cmake/tarball_glob.txt +49 -0
- data/ext/couchbase/core/agent_group.cxx +0 -8
- data/ext/couchbase/core/agent_group.hxx +0 -4
- data/ext/couchbase/core/analytics_stream.cxx +314 -0
- data/ext/couchbase/core/analytics_stream.hxx +129 -0
- data/ext/couchbase/core/analytics_stream_component.cxx +245 -0
- data/ext/couchbase/core/analytics_stream_component.hxx +67 -0
- data/ext/couchbase/core/app_telemetry_meter.cxx +25 -3
- data/ext/couchbase/core/app_telemetry_meter.hxx +77 -0
- data/ext/couchbase/core/app_telemetry_reporter.cxx +7 -6
- data/ext/couchbase/core/bucket.cxx +381 -87
- data/ext/couchbase/core/bucket.hxx +105 -25
- data/ext/couchbase/core/bucket_unit_test_api.hxx +57 -0
- data/ext/couchbase/core/capella.hxx +91 -0
- data/ext/couchbase/core/cluster.cxx +1554 -398
- data/ext/couchbase/core/cluster.hxx +39 -8
- data/ext/couchbase/core/cluster_agent.cxx +0 -8
- data/ext/couchbase/core/cluster_agent.hxx +0 -4
- data/ext/couchbase/core/cluster_options.hxx +26 -3
- data/ext/couchbase/core/collections_component.cxx +39 -15
- data/ext/couchbase/core/config_profiles.cxx +1 -1
- data/ext/couchbase/core/crud_component.cxx +2 -2
- data/ext/couchbase/core/crud_options.hxx +34 -0
- data/ext/couchbase/core/crypto/cbcrypto.cc +18 -13
- data/ext/couchbase/core/document_id_redaction.hxx +81 -0
- data/ext/couchbase/core/error_context/base_error_context.hxx +7 -6
- data/ext/couchbase/core/error_context/key_value.cxx +25 -2
- data/ext/couchbase/core/error_context/key_value.hxx +60 -33
- data/ext/couchbase/core/error_context/key_value_error_context.hxx +16 -15
- data/ext/couchbase/core/error_context/search.hxx +5 -0
- data/ext/couchbase/core/error_context/search_json.hxx +3 -0
- data/ext/couchbase/core/error_context/subdocument_error_context.hxx +19 -18
- data/ext/couchbase/core/error_context/view.hxx +5 -0
- data/ext/couchbase/core/free_form_http_request.cxx +237 -4
- data/ext/couchbase/core/free_form_http_request.hxx +41 -8
- data/ext/couchbase/core/http_component.cxx +300 -284
- data/ext/couchbase/core/http_component.hxx +0 -4
- data/ext/couchbase/core/impl/analytics.cxx +38 -5
- data/ext/couchbase/core/impl/analytics.hxx +34 -0
- data/ext/couchbase/core/impl/bootstrap_error.hxx +0 -3
- data/ext/couchbase/core/impl/collection.cxx +215 -202
- data/ext/couchbase/core/impl/collection_manager.cxx +1 -1
- data/ext/couchbase/core/impl/dns_srv_tracker.cxx +45 -18
- data/ext/couchbase/core/impl/error.cxx +22 -2
- data/ext/couchbase/core/impl/internal_analytics_stream_result.hxx +111 -0
- data/ext/couchbase/core/impl/internal_query_stream_result.hxx +110 -0
- data/ext/couchbase/core/impl/invoke_with_node_id.hxx +15 -7
- data/ext/couchbase/core/impl/key_value_error_category.cxx +6 -0
- data/ext/couchbase/core/impl/lookup_in_replica.cxx +10 -10
- data/ext/couchbase/core/impl/lookup_in_replica.hxx +4 -4
- data/ext/couchbase/core/impl/node_id.cxx +49 -13
- data/ext/couchbase/core/impl/observability_recorder.cxx +12 -2
- data/ext/couchbase/core/impl/observe_poll.cxx +63 -23
- data/ext/couchbase/core/impl/public_analytics_stream_result.cxx +372 -0
- data/ext/couchbase/core/impl/public_bucket.cxx +48 -2
- data/ext/couchbase/core/impl/public_cluster.cxx +325 -25
- data/ext/couchbase/core/impl/public_query_stream_result.cxx +380 -0
- data/ext/couchbase/core/impl/public_transaction_get_multi_replicas_from_preferred_server_group_result.cxx +42 -0
- data/ext/couchbase/core/impl/public_transaction_get_multi_result.cxx +42 -0
- data/ext/couchbase/core/impl/query.cxx +76 -7
- data/ext/couchbase/core/impl/query.hxx +36 -0
- data/ext/couchbase/core/impl/query_index_manager.cxx +54 -1
- data/ext/couchbase/core/impl/replica_utils.cxx +101 -0
- data/ext/couchbase/core/impl/replica_utils.hxx +64 -0
- data/ext/couchbase/core/impl/resolve_node_id.hxx +125 -0
- data/ext/couchbase/core/impl/scope.cxx +70 -0
- data/ext/couchbase/core/impl/search.cxx +31 -1
- data/ext/couchbase/core/impl/transactions.cxx +130 -0
- data/ext/couchbase/core/impl/wait_until_ready.cxx +439 -0
- data/ext/couchbase/core/impl/wait_until_ready.hxx +110 -0
- data/ext/couchbase/core/impl/with_bucket_config_or_timeout.hxx +180 -0
- data/ext/couchbase/core/impl/with_cancellation.hxx +11 -3
- data/ext/couchbase/core/io/configuration_belongs_to_session.hxx +66 -0
- data/ext/couchbase/core/io/dns_client.cxx +122 -45
- data/ext/couchbase/core/io/dns_codec.hxx +21 -21
- data/ext/couchbase/core/io/dns_config.cxx +21 -6
- data/ext/couchbase/core/io/http_command.hxx +149 -121
- data/ext/couchbase/core/io/http_error.hxx +107 -0
- data/ext/couchbase/core/io/http_parser.cxx +4 -0
- data/ext/couchbase/core/io/http_session.cxx +606 -154
- data/ext/couchbase/core/io/http_session.hxx +147 -30
- data/ext/couchbase/core/io/http_session_manager.hxx +329 -382
- data/ext/couchbase/core/io/http_streaming_parser.cxx +4 -0
- data/ext/couchbase/core/io/http_streaming_response.cxx +328 -43
- data/ext/couchbase/core/io/http_streaming_response.hxx +37 -3
- data/ext/couchbase/core/io/mcbp_buffer_pool.hxx +122 -0
- data/ext/couchbase/core/io/mcbp_command.hxx +150 -51
- data/ext/couchbase/core/io/mcbp_command_id.hxx +45 -0
- data/ext/couchbase/core/io/mcbp_message.cxx +9 -1
- data/ext/couchbase/core/io/mcbp_message.hxx +19 -0
- data/ext/couchbase/core/io/mcbp_output_queue.hxx +150 -0
- data/ext/couchbase/core/io/mcbp_parser.cxx +24 -7
- data/ext/couchbase/core/io/mcbp_session.cxx +316 -317
- data/ext/couchbase/core/io/mcbp_session.hxx +27 -18
- data/ext/couchbase/core/io/opaque_ring_table.hxx +145 -0
- data/ext/couchbase/core/io/retry_orchestrator.hxx +6 -5
- data/ext/couchbase/core/io/stream_deadline.hxx +50 -0
- data/ext/couchbase/core/io/streams.cxx +275 -14
- data/ext/couchbase/core/io/streams.hxx +53 -0
- data/ext/couchbase/core/logger/logger.cxx +36 -0
- data/ext/couchbase/core/logger/logger.hxx +46 -6
- data/ext/couchbase/core/logger/redaction.hxx +503 -0
- data/ext/couchbase/core/management/eventing_status.hxx +10 -0
- data/ext/couchbase/core/management/eventing_status_json.hxx +4 -0
- data/ext/couchbase/core/mcbp/big_endian.cxx +1 -1
- data/ext/couchbase/core/mcbp/codec.cxx +27 -18
- data/ext/couchbase/core/mcbp/operation_queue.cxx +2 -2
- data/ext/couchbase/core/mcbp/packet.hxx +2 -2
- data/ext/couchbase/core/mcbp/queue_request.cxx +23 -6
- data/ext/couchbase/core/mcbp/queue_request.hxx +21 -0
- data/ext/couchbase/core/meta/features.hxx +13 -0
- data/ext/couchbase/core/meta/version.cxx +6 -3
- data/ext/couchbase/core/metrics/logging_meter.cxx +5 -10
- data/ext/couchbase/core/metrics/meter_wrapper.cxx +63 -33
- data/ext/couchbase/core/metrics/meter_wrapper.hxx +55 -3
- data/ext/couchbase/core/operations/analytics_response_parsing.cxx +177 -0
- data/ext/couchbase/core/operations/analytics_response_parsing.hxx +55 -0
- data/ext/couchbase/core/operations/document_analytics.cxx +48 -130
- data/ext/couchbase/core/operations/document_analytics.hxx +14 -2
- data/ext/couchbase/core/operations/document_get_all_replicas.hxx +15 -7
- data/ext/couchbase/core/operations/document_get_any_replica.hxx +19 -8
- data/ext/couchbase/core/operations/document_get_projected.cxx +1 -1
- data/ext/couchbase/core/{impl/get_replica.cxx → operations/document_get_replica.cxx} +21 -3
- data/ext/couchbase/core/{impl/get_replica.hxx → operations/document_get_replica.hxx} +31 -4
- data/ext/couchbase/core/operations/document_lookup_in.cxx +10 -10
- data/ext/couchbase/core/operations/document_lookup_in_all_replicas.hxx +228 -198
- data/ext/couchbase/core/operations/document_lookup_in_any_replica.hxx +215 -217
- data/ext/couchbase/core/operations/document_query.cxx +108 -221
- data/ext/couchbase/core/operations/document_query.hxx +18 -4
- data/ext/couchbase/core/operations/document_search.cxx +52 -8
- data/ext/couchbase/core/operations/document_search.hxx +3 -0
- data/ext/couchbase/core/operations/document_view.cxx +3 -3
- data/ext/couchbase/core/operations/management/analytics_dataset_create.cxx +15 -8
- data/ext/couchbase/core/operations/management/analytics_dataset_create.hxx +4 -0
- data/ext/couchbase/core/operations/management/analytics_dataset_drop.cxx +11 -6
- data/ext/couchbase/core/operations/management/analytics_dataset_get_all.cxx +3 -2
- data/ext/couchbase/core/operations/management/analytics_dataverse_create.cxx +10 -5
- data/ext/couchbase/core/operations/management/analytics_dataverse_drop.cxx +10 -5
- data/ext/couchbase/core/operations/management/analytics_get_pending_mutations.cxx +2 -1
- data/ext/couchbase/core/operations/management/analytics_index_create.cxx +33 -8
- data/ext/couchbase/core/operations/management/analytics_index_drop.cxx +12 -7
- data/ext/couchbase/core/operations/management/analytics_index_get_all.cxx +3 -2
- data/ext/couchbase/core/operations/management/analytics_link_connect.cxx +11 -6
- data/ext/couchbase/core/operations/management/analytics_link_create.cxx +3 -3
- data/ext/couchbase/core/operations/management/analytics_link_disconnect.cxx +10 -4
- data/ext/couchbase/core/operations/management/analytics_link_drop.cxx +6 -4
- data/ext/couchbase/core/operations/management/analytics_link_get_all.cxx +4 -3
- data/ext/couchbase/core/operations/management/analytics_link_replace.cxx +3 -3
- data/ext/couchbase/core/operations/management/analytics_link_utils.hxx +1 -1
- data/ext/couchbase/core/operations/management/bucket_create.cxx +37 -38
- data/ext/couchbase/core/operations/management/bucket_update.cxx +26 -28
- data/ext/couchbase/core/operations/management/change_password.cxx +2 -0
- data/ext/couchbase/core/operations/management/collection_drop.cxx +5 -1
- data/ext/couchbase/core/operations/management/error_utils.cxx +59 -5
- data/ext/couchbase/core/operations/management/error_utils.hxx +29 -1
- data/ext/couchbase/core/operations/management/eventing_deploy_function.cxx +9 -3
- data/ext/couchbase/core/operations/management/eventing_drop_function.cxx +8 -3
- data/ext/couchbase/core/operations/management/eventing_get_all_functions.cxx +4 -2
- data/ext/couchbase/core/operations/management/eventing_get_function.cxx +5 -3
- data/ext/couchbase/core/operations/management/eventing_get_status.cxx +4 -2
- data/ext/couchbase/core/operations/management/eventing_pause_function.cxx +9 -3
- data/ext/couchbase/core/operations/management/eventing_resume_function.cxx +9 -3
- data/ext/couchbase/core/operations/management/eventing_undeploy_function.cxx +9 -3
- data/ext/couchbase/core/operations/management/eventing_upsert_function.cxx +9 -3
- data/ext/couchbase/core/operations/management/group_drop.cxx +3 -1
- data/ext/couchbase/core/operations/management/group_get.cxx +3 -1
- data/ext/couchbase/core/operations/management/group_upsert.cxx +11 -8
- data/ext/couchbase/core/operations/management/query_index_build.cxx +5 -2
- data/ext/couchbase/core/operations/management/query_index_create.cxx +9 -3
- data/ext/couchbase/core/operations/management/query_index_drop.cxx +11 -4
- data/ext/couchbase/core/operations/management/query_index_get_all.cxx +1 -1
- data/ext/couchbase/core/operations/management/query_index_get_all_deferred.cxx +1 -1
- data/ext/couchbase/core/operations/management/scope_drop.cxx +5 -1
- data/ext/couchbase/core/operations/management/scope_get_all.cxx +6 -1
- data/ext/couchbase/core/operations/management/search_get_stats.cxx +10 -0
- data/ext/couchbase/core/operations/management/search_index_analyze_document.cxx +3 -2
- data/ext/couchbase/core/operations/management/search_index_control_ingest.cxx +4 -3
- data/ext/couchbase/core/operations/management/search_index_control_plan_freeze.cxx +4 -3
- data/ext/couchbase/core/operations/management/search_index_control_query.cxx +4 -3
- data/ext/couchbase/core/operations/management/search_index_drop.cxx +2 -2
- data/ext/couchbase/core/operations/management/search_index_get.cxx +2 -2
- data/ext/couchbase/core/operations/management/search_index_get_documents_count.cxx +3 -2
- data/ext/couchbase/core/operations/management/search_index_get_stats.cxx +3 -1
- data/ext/couchbase/core/operations/management/search_index_upsert.cxx +2 -2
- data/ext/couchbase/core/operations/management/user_drop.cxx +3 -1
- data/ext/couchbase/core/operations/management/user_get.cxx +3 -1
- data/ext/couchbase/core/operations/management/user_upsert.cxx +12 -9
- data/ext/couchbase/core/operations/management/view_index_drop.cxx +1 -1
- data/ext/couchbase/core/operations/management/view_index_get.cxx +1 -1
- data/ext/couchbase/core/operations/management/view_index_upsert.cxx +1 -1
- data/ext/couchbase/core/operations/operation_traits.hxx +18 -0
- data/ext/couchbase/core/operations/query_response_parsing.cxx +211 -0
- data/ext/couchbase/core/operations/query_response_parsing.hxx +60 -0
- data/ext/couchbase/core/operations_fwd.hxx +3 -3
- data/ext/couchbase/core/origin.cxx +54 -26
- data/ext/couchbase/core/origin.hxx +12 -0
- data/ext/couchbase/core/orphan_reporter.cxx +2 -1
- data/ext/couchbase/core/platform/random.cc +7 -7
- data/ext/couchbase/core/protocol/client_request.cxx +2 -2
- data/ext/couchbase/core/protocol/client_request.hxx +38 -21
- data/ext/couchbase/core/protocol/client_response.cxx +11 -3
- data/ext/couchbase/core/protocol/client_response.hxx +27 -6
- data/ext/couchbase/core/protocol/cmd_append.cxx +2 -2
- data/ext/couchbase/core/protocol/cmd_cluster_map_change_notification.cxx +2 -2
- data/ext/couchbase/core/protocol/cmd_decrement.cxx +6 -6
- data/ext/couchbase/core/protocol/cmd_get.cxx +1 -1
- data/ext/couchbase/core/protocol/cmd_get_and_lock.cxx +2 -2
- data/ext/couchbase/core/protocol/cmd_get_and_touch.cxx +2 -2
- data/ext/couchbase/core/protocol/cmd_get_cluster_config.cxx +6 -4
- data/ext/couchbase/core/protocol/cmd_get_collection_id.cxx +2 -2
- data/ext/couchbase/core/protocol/cmd_get_error_map.cxx +1 -1
- data/ext/couchbase/core/protocol/cmd_get_meta.cxx +4 -4
- data/ext/couchbase/core/protocol/cmd_get_replica.cxx +1 -1
- data/ext/couchbase/core/protocol/cmd_hello.cxx +1 -1
- data/ext/couchbase/core/protocol/cmd_increment.cxx +6 -6
- data/ext/couchbase/core/protocol/cmd_insert.cxx +4 -4
- data/ext/couchbase/core/protocol/cmd_lookup_in.cxx +4 -3
- data/ext/couchbase/core/protocol/cmd_lookup_in_replica.cxx +4 -3
- data/ext/couchbase/core/protocol/cmd_mutate_in.cxx +10 -9
- data/ext/couchbase/core/protocol/cmd_mutate_in.hxx +10 -10
- data/ext/couchbase/core/protocol/cmd_observe_seqno.cxx +7 -7
- data/ext/couchbase/core/protocol/cmd_prepend.cxx +2 -2
- data/ext/couchbase/core/protocol/cmd_remove.cxx +2 -2
- data/ext/couchbase/core/protocol/cmd_replace.cxx +4 -4
- data/ext/couchbase/core/protocol/cmd_touch.cxx +1 -1
- data/ext/couchbase/core/protocol/cmd_upsert.cxx +4 -4
- data/ext/couchbase/core/protocol/datatype.hxx +118 -1
- data/ext/couchbase/core/protocol/frame_info_utils.cxx +1 -1
- data/ext/couchbase/core/protocol/magic.hxx +4 -0
- data/ext/couchbase/core/protocol/magic_fmt.hxx +3 -0
- data/ext/couchbase/core/protocol/server_request.hxx +3 -1
- data/ext/couchbase/core/protocol/status.cxx +1 -1
- data/ext/couchbase/core/protostellar/analytics_converter.hxx +171 -0
- data/ext/couchbase/core/protostellar/bucket_admin_converter.hxx +301 -0
- data/ext/couchbase/core/protostellar/collection_admin_converter.hxx +92 -0
- data/ext/couchbase/core/protostellar/component.cxx +2445 -0
- data/ext/couchbase/core/protostellar/component.hxx +391 -0
- data/ext/couchbase/core/{columnar/bootstrap_notification_subscriber.hxx → protostellar/compression_settings.hxx} +11 -10
- data/ext/couchbase/core/protostellar/credentials.cxx +233 -0
- data/ext/couchbase/core/protostellar/credentials.hxx +88 -0
- data/ext/couchbase/core/protostellar/dispatcher.cxx +57 -0
- data/ext/couchbase/core/protostellar/dispatcher.hxx +482 -0
- data/ext/couchbase/core/protostellar/error_utils.cxx +293 -0
- data/ext/couchbase/core/protostellar/error_utils.hxx +97 -0
- data/ext/couchbase/core/protostellar/json_payload.hxx +49 -0
- data/ext/couchbase/core/protostellar/kv_converter.cxx +70 -0
- data/ext/couchbase/core/protostellar/kv_converter.hxx +600 -0
- data/ext/couchbase/core/protostellar/query_converter.hxx +265 -0
- data/ext/couchbase/core/protostellar/query_index_admin_converter.hxx +291 -0
- data/ext/couchbase/core/protostellar/query_proto.hxx +73 -0
- data/ext/couchbase/core/protostellar/search_converter.hxx +244 -0
- data/ext/couchbase/core/protostellar/search_index_admin_converter.hxx +156 -0
- data/ext/couchbase/core/protostellar/view_converter.hxx +158 -0
- data/ext/couchbase/core/query_stream.cxx +447 -0
- data/ext/couchbase/core/query_stream.hxx +141 -0
- data/ext/couchbase/core/query_stream_component.cxx +243 -0
- data/ext/couchbase/core/query_stream_component.hxx +68 -0
- data/ext/couchbase/core/range_scan_load_balancer.cxx +15 -8
- data/ext/couchbase/core/range_scan_orchestrator.cxx +15 -3
- data/ext/couchbase/core/response_handler.hxx +41 -4
- data/ext/couchbase/core/row_streamer.cxx +332 -46
- data/ext/couchbase/core/row_streamer.hxx +31 -1
- data/ext/couchbase/core/row_streamer_options.hxx +55 -0
- data/ext/couchbase/core/sasl/client.h +3 -0
- data/ext/couchbase/core/search_scoring.hxx +66 -0
- data/ext/couchbase/core/stream_error_details.hxx +69 -0
- data/ext/couchbase/core/timeout_defaults.hxx +0 -1
- data/ext/couchbase/core/topology/capabilities.hxx +6 -0
- data/ext/couchbase/core/topology/capabilities_fmt.hxx +3 -0
- data/ext/couchbase/core/topology/configuration.cxx +21 -6
- data/ext/couchbase/core/topology/configuration.hxx +29 -8
- data/ext/couchbase/core/topology/configuration_json.hxx +3 -0
- data/ext/couchbase/core/topology/error_map_json.hxx +3 -1
- data/ext/couchbase/core/tracing/threshold_logging_tracer.cxx +70 -4
- data/ext/couchbase/core/tracing/threshold_logging_tracer.hxx +10 -0
- data/ext/couchbase/core/transactions/active_transaction_record.cxx +0 -34
- data/ext/couchbase/core/transactions/attempt_context_impl.cxx +362 -241
- data/ext/couchbase/core/transactions/attempt_context_impl.hxx +24 -3
- data/ext/couchbase/core/transactions/error_list.hxx +2 -2
- data/ext/couchbase/core/transactions/get_multi_fetch.hxx +160 -0
- data/ext/couchbase/core/transactions/get_multi_orchestrator.cxx +326 -125
- data/ext/couchbase/core/transactions/get_multi_transaction_id.hxx +62 -0
- data/ext/couchbase/core/transactions/internal/atr_entry.hxx +22 -2
- data/ext/couchbase/core/transactions/internal/transaction_context.hxx +2 -2
- data/ext/couchbase/core/transactions/internal/transactions_cleanup.hxx +23 -1
- data/ext/couchbase/core/transactions/internal/utils.hxx +50 -14
- data/ext/couchbase/core/transactions/staged_mutation.cxx +23 -18
- data/ext/couchbase/core/transactions/staged_mutation.hxx +15 -4
- data/ext/couchbase/core/transactions/transaction_context.cxx +55 -40
- data/ext/couchbase/core/transactions/transaction_get_result.hxx +16 -0
- data/ext/couchbase/core/transactions/transactions.cxx +3 -55
- data/ext/couchbase/core/transactions/transactions_cleanup.cxx +194 -66
- data/ext/couchbase/core/transactions/transactions_config.cxx +0 -11
- data/ext/couchbase/core/transactions/utils.cxx +10 -0
- data/ext/couchbase/core/transactions/waitable_op_list.hxx +15 -4
- data/ext/couchbase/core/transactions.hxx +9 -9
- data/ext/couchbase/core/utils/byteswap.hxx +166 -9
- data/ext/couchbase/core/utils/connection_string.cxx +50 -85
- data/ext/couchbase/core/utils/connection_string.hxx +15 -0
- data/ext/couchbase/core/{columnar/backoff_calculator.hxx → utils/deadline.hxx} +13 -13
- data/ext/couchbase/core/utils/duration_parser.cxx +1 -1
- data/ext/couchbase/core/utils/json_streaming_lexer.cxx +48 -5
- data/ext/couchbase/core/utils/json_streaming_lexer.hxx +4 -1
- data/ext/couchbase/core/utils/movable_function.hxx +301 -52
- data/ext/couchbase/core/utils/name_codec.hxx +154 -11
- data/ext/couchbase/core/{columnar/retry_info.hxx → utils/process_id.hxx} +18 -13
- data/ext/couchbase/core/utils/url_codec.cxx +12 -51
- data/ext/couchbase/core/websocket_codec.cxx +3 -29
- data/ext/couchbase/couchbase/analytics_options.hxx +21 -10
- data/ext/couchbase/couchbase/analytics_row.hxx +105 -0
- data/ext/couchbase/couchbase/analytics_stream_result.hxx +266 -0
- data/ext/couchbase/couchbase/boolean_query.hxx +3 -3
- data/ext/couchbase/couchbase/bucket.hxx +57 -0
- data/ext/couchbase/couchbase/cluster.hxx +216 -5
- data/ext/couchbase/{core/columnar/security_options.hxx → couchbase/cluster_state.hxx} +18 -14
- data/ext/couchbase/couchbase/codec/lenient_json_transcoder.hxx +69 -0
- data/ext/couchbase/couchbase/collection.hxx +234 -14
- data/ext/couchbase/couchbase/common_durability_options.hxx +18 -3
- data/ext/couchbase/couchbase/conjunction_query.hxx +1 -1
- data/ext/couchbase/couchbase/create_dataset_analytics_options.hxx +6 -1
- data/ext/couchbase/couchbase/date_range_query.hxx +2 -2
- data/ext/couchbase/couchbase/decrement_options.hxx +1 -1
- data/ext/couchbase/couchbase/disjunction_query.hxx +1 -1
- data/ext/couchbase/couchbase/error.hxx +32 -0
- data/ext/couchbase/couchbase/error_codes.hxx +32 -0
- data/ext/couchbase/couchbase/geo_bounding_box_query.hxx +1 -1
- data/ext/couchbase/couchbase/geo_distance_query.hxx +1 -1
- data/ext/couchbase/couchbase/geo_polygon_query.hxx +1 -1
- data/ext/couchbase/couchbase/get_replica_options.hxx +68 -0
- data/ext/couchbase/couchbase/get_replica_result.hxx +5 -3
- data/ext/couchbase/couchbase/get_replica_strategy.hxx +161 -0
- data/ext/couchbase/couchbase/get_result.hxx +1 -0
- data/ext/couchbase/couchbase/increment_options.hxx +1 -1
- data/ext/couchbase/couchbase/insert_options.hxx +1 -1
- data/ext/couchbase/couchbase/match_phrase_query.hxx +1 -1
- data/ext/couchbase/couchbase/match_query.hxx +1 -1
- data/ext/couchbase/couchbase/mutate_in_options.hxx +3 -3
- data/ext/couchbase/couchbase/network_options.hxx +1 -1
- data/ext/couchbase/couchbase/node_id.hxx +42 -14
- data/ext/couchbase/couchbase/node_id_for_options.hxx +7 -1
- data/ext/couchbase/couchbase/node_ids_options.hxx +8 -2
- data/ext/couchbase/couchbase/numeric_range_query.hxx +1 -1
- data/ext/couchbase/couchbase/phrase_query.hxx +1 -1
- data/ext/couchbase/couchbase/prefix_query.hxx +1 -1
- data/ext/couchbase/couchbase/query_options.hxx +23 -12
- data/ext/couchbase/couchbase/query_row.hxx +111 -0
- data/ext/couchbase/couchbase/query_stream_result.hxx +302 -0
- data/ext/couchbase/couchbase/query_string_query.hxx +3 -3
- data/ext/couchbase/couchbase/regexp_query.hxx +1 -1
- data/ext/couchbase/couchbase/replace_options.hxx +3 -3
- data/ext/couchbase/couchbase/result.hxx +14 -3
- data/ext/couchbase/couchbase/scan_result_item.hxx +1 -0
- data/ext/couchbase/couchbase/scope.hxx +85 -2
- data/ext/couchbase/couchbase/search_options.hxx +58 -8
- data/ext/couchbase/couchbase/search_scoring.hxx +94 -0
- data/ext/couchbase/couchbase/search_scoring_none.hxx +51 -0
- data/ext/couchbase/couchbase/search_scoring_reciprocal_rank_fusion.hxx +88 -0
- data/ext/couchbase/couchbase/search_scoring_relative_score_fusion.hxx +70 -0
- data/ext/couchbase/couchbase/term_range_query.hxx +1 -1
- data/ext/couchbase/couchbase/tracing/request_span.hxx +46 -0
- data/ext/couchbase/couchbase/transactions/transaction_get_multi_replicas_from_preferred_server_group_result.hxx +23 -21
- data/ext/couchbase/couchbase/transactions/transaction_get_multi_result.hxx +24 -19
- data/ext/couchbase/couchbase/transactions/transaction_get_result.hxx +17 -3
- data/ext/couchbase/couchbase/transactions/transactions_config.hxx +4 -2
- data/ext/couchbase/couchbase/transactions.hxx +27 -18
- data/ext/couchbase/couchbase/upsert_options.hxx +3 -3
- data/ext/couchbase/couchbase/vector_query.hxx +1 -1
- data/ext/couchbase/couchbase/wait_until_ready_options.hxx +111 -0
- data/ext/couchbase/couchbase/wildcard_query.hxx +1 -1
- data/ext/extconf.rb +15 -10
- data/ext/rcb_analytics.cxx +82 -102
- data/ext/rcb_backend.cxx +42 -36
- data/ext/rcb_backend.hxx +2 -0
- data/ext/rcb_buckets.cxx +47 -59
- data/ext/rcb_collections.cxx +42 -52
- data/ext/rcb_crud.cxx +345 -224
- data/ext/rcb_diagnostics.cxx +117 -27
- data/ext/rcb_exceptions.cxx +152 -99
- data/ext/rcb_exceptions.hxx +25 -0
- data/ext/rcb_extras.cxx +18 -19
- data/ext/rcb_hdr_histogram.cxx +19 -4
- data/ext/rcb_logger.cxx +202 -27
- data/ext/rcb_logger.hxx +12 -0
- data/ext/rcb_multi.cxx +40 -40
- data/ext/rcb_observability.cxx +7 -5
- data/ext/rcb_query.cxx +99 -142
- data/ext/rcb_range_scan.cxx +41 -35
- data/ext/rcb_search.cxx +98 -99
- data/ext/rcb_users.cxx +36 -56
- data/ext/rcb_utils.cxx +100 -37
- data/ext/rcb_utils.hxx +287 -33
- data/ext/rcb_version.cxx +1 -1
- data/ext/rcb_views.cxx +55 -51
- data/lib/active_support/cache/couchbase_store.rb +77 -12
- data/lib/couchbase/bucket.rb +20 -0
- data/lib/couchbase/cluster.rb +33 -14
- data/lib/couchbase/collection.rb +36 -1
- data/lib/couchbase/errors.rb +18 -0
- data/lib/couchbase/fork_hooks.rb +16 -7
- data/lib/couchbase/get_replica_strategy.rb +89 -0
- data/lib/couchbase/logger.rb +1 -1
- data/lib/couchbase/management/analytics_index_manager.rb +15 -15
- data/lib/couchbase/management/bucket_manager.rb +6 -6
- data/lib/couchbase/management/collection_manager.rb +6 -6
- data/lib/couchbase/management/query_index_manager.rb +7 -7
- data/lib/couchbase/management/search_index_manager.rb +13 -13
- data/lib/couchbase/management/user_manager.rb +10 -10
- data/lib/couchbase/management/view_index_manager.rb +5 -5
- data/lib/couchbase/options.rb +181 -44
- data/lib/couchbase/protostellar/bucket.rb +4 -0
- data/lib/couchbase/protostellar/cluster.rb +4 -0
- data/lib/couchbase/protostellar/collection.rb +4 -0
- data/lib/couchbase/protostellar/error_handling.rb +2 -1
- data/lib/couchbase/protostellar/request.rb +1 -0
- data/lib/couchbase/protostellar/request_generator/kv.rb +16 -2
- data/lib/couchbase/protostellar/request_generator/search.rb +17 -1
- data/lib/couchbase/protostellar/retry/orchestrator.rb +3 -2
- data/lib/couchbase/protostellar/retry/strategies/fail_fast.rb +33 -0
- data/lib/couchbase/protostellar/retry/strategies.rb +1 -0
- data/lib/couchbase/search_options.rb +95 -6
- data/lib/couchbase/version.rb +1 -1
- metadata +92 -33
- data/ext/0001-fix-build-for-mingw-w64-ucrt-x86_64-toolchain.patch +0 -40
- data/ext/couchbase/core/columnar/agent.hxx +0 -71
- data/ext/couchbase/core/columnar/agent_config.hxx +0 -59
- data/ext/couchbase/core/columnar/background_bootstrap_listener.hxx +0 -42
- data/ext/couchbase/core/columnar/backoff_calculator.cxx +0 -62
- data/ext/couchbase/core/columnar/columnar_agent.cxx +0 -147
- data/ext/couchbase/core/columnar/columnar_agent_config.cxx +0 -48
- data/ext/couchbase/core/columnar/columnar_error.cxx +0 -51
- data/ext/couchbase/core/columnar/database_management_options.hxx +0 -56
- data/ext/couchbase/core/columnar/error.hxx +0 -51
- data/ext/couchbase/core/columnar/error_codes.cxx +0 -107
- data/ext/couchbase/core/columnar/error_codes.hxx +0 -75
- data/ext/couchbase/core/columnar/management_component.cxx +0 -300
- data/ext/couchbase/core/columnar/management_component.hxx +0 -56
- data/ext/couchbase/core/columnar/query_component.cxx +0 -523
- data/ext/couchbase/core/columnar/query_component.hxx +0 -57
- data/ext/couchbase/core/columnar/query_options.hxx +0 -59
- data/ext/couchbase/core/columnar/query_result.cxx +0 -130
- data/ext/couchbase/core/columnar/query_result.hxx +0 -78
- data/ext/couchbase/core/io/config_tracker.cxx +0 -687
- data/ext/couchbase/core/io/config_tracker.hxx +0 -123
|
@@ -26,9 +26,14 @@
|
|
|
26
26
|
#include "utils/json_streaming_lexer.hxx"
|
|
27
27
|
#include "utils/movable_function.hxx"
|
|
28
28
|
|
|
29
|
+
#include <asio/error.hpp>
|
|
29
30
|
#include <asio/experimental/channel_error.hpp>
|
|
30
31
|
#include <asio/experimental/concurrent_channel.hpp>
|
|
31
32
|
#include <asio/io_context.hpp>
|
|
33
|
+
#include <asio/post.hpp>
|
|
34
|
+
|
|
35
|
+
#include <asio/steady_timer.hpp>
|
|
36
|
+
#include <chrono>
|
|
32
37
|
|
|
33
38
|
#include <atomic>
|
|
34
39
|
#include <memory>
|
|
@@ -48,50 +53,75 @@ struct row_stream_end_signal {
|
|
|
48
53
|
class row_streamer_impl : public std::enable_shared_from_this<row_streamer_impl>
|
|
49
54
|
{
|
|
50
55
|
public:
|
|
51
|
-
static constexpr std::size_t ROW_BUFFER_SIZE{ 100 };
|
|
52
|
-
static constexpr std::size_t ROW_BUFFER_FEED_THRESHOLD{ ROW_BUFFER_SIZE * 3 / 4 };
|
|
53
|
-
static constexpr std::uint32_t LEXER_DEPTH{ 4 };
|
|
54
|
-
|
|
55
56
|
row_streamer_impl(asio::io_context& io,
|
|
56
57
|
http_response_body body,
|
|
57
|
-
const std::string& pointer_expression
|
|
58
|
+
const std::string& pointer_expression,
|
|
59
|
+
row_streamer_options options)
|
|
58
60
|
: io_{ io }
|
|
59
61
|
, body_{ std::move(body) }
|
|
60
|
-
|
|
61
|
-
,
|
|
62
|
+
// rows_ initializes before options_, so read the capacity from the ctor parameter.
|
|
63
|
+
, rows_{ io_, options.row_buffer_size }
|
|
64
|
+
, options_{ options }
|
|
65
|
+
, idle_timer_{ io_ }
|
|
66
|
+
, lexer_{ pointer_expression, options_.lexer_depth, options_.max_row_bytes }
|
|
62
67
|
{
|
|
63
68
|
}
|
|
64
69
|
|
|
65
70
|
void start(utils::movable_function<void(std::string, std::error_code)>&& handler)
|
|
66
71
|
{
|
|
72
|
+
metadata_header_handler_ = std::move(handler);
|
|
67
73
|
lexer_.on_metadata_header_complete(
|
|
68
|
-
[
|
|
74
|
+
[weak = weak_from_this()](auto ec, auto meta_header) mutable {
|
|
75
|
+
auto self = weak.lock();
|
|
76
|
+
if (!self) {
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
69
79
|
// Trim any whitespace from the end
|
|
70
|
-
meta_header.
|
|
80
|
+
auto last_non_ws = meta_header.find_last_not_of(" \t\f\v\n\r");
|
|
81
|
+
if (last_non_ws != std::string::npos) {
|
|
82
|
+
meta_header.erase(last_non_ws + 1);
|
|
83
|
+
}
|
|
71
84
|
|
|
72
85
|
// The metadata header can end with an open `[` (opening bracket for pointer expression
|
|
73
86
|
// array). If that's the case, close the array and the response object
|
|
74
|
-
if (meta_header[meta_header.length() - 1] == '[') {
|
|
87
|
+
if (!meta_header.empty() && meta_header[meta_header.length() - 1] == '[') {
|
|
75
88
|
meta_header.append("]}");
|
|
76
89
|
}
|
|
77
|
-
|
|
90
|
+
self->deliver_metadata_header(ec, std::move(meta_header));
|
|
78
91
|
});
|
|
79
|
-
lexer_.on_row([
|
|
80
|
-
self
|
|
81
|
-
|
|
92
|
+
lexer_.on_row([weak = weak_from_this()](std::string&& row) -> utils::json::stream_control {
|
|
93
|
+
auto self = weak.lock();
|
|
94
|
+
if (!self) {
|
|
95
|
+
return utils::json::stream_control::stop;
|
|
96
|
+
}
|
|
97
|
+
auto row_len = row.size();
|
|
98
|
+
// Account the row against the back-pressure budget *synchronously*, as it is handed to the
|
|
99
|
+
// channel. A concurrent_channel completes async_send immediately while it has spare capacity
|
|
100
|
+
// and otherwise queues the send until a receiver drains a slot; if we only counted bytes in
|
|
101
|
+
// the completion handler, a full channel would leave buffered_bytes_ pinned low and let
|
|
102
|
+
// maybe_feed_lexer() keep pulling from the socket, piling up unbounded pending sends (each
|
|
103
|
+
// owning a row). Counting here makes the high-water mark observe the true backlog.
|
|
104
|
+
self->buffered_bytes_.fetch_add(row_len, std::memory_order_relaxed);
|
|
105
|
+
self->rows_.async_send({}, std::move(row), [self, row_len](auto ec) {
|
|
82
106
|
if (ec) {
|
|
83
107
|
if (ec != asio::experimental::error::channel_closed &&
|
|
84
108
|
ec != asio::experimental::error::channel_cancelled) {
|
|
85
109
|
CB_LOG_WARNING(
|
|
86
110
|
"unexpected error while sending to row channel: {} ({})", ec.value(), ec.message());
|
|
87
111
|
}
|
|
88
|
-
|
|
112
|
+
// The send never reached a receiver (channel closed/cancelled), so release its budget.
|
|
113
|
+
self->release_buffered_bytes(row_len);
|
|
89
114
|
}
|
|
90
115
|
});
|
|
91
116
|
return utils::json::stream_control::next_row;
|
|
92
117
|
});
|
|
93
|
-
lexer_.on_complete([
|
|
118
|
+
lexer_.on_complete([weak = weak_from_this()](
|
|
94
119
|
std::error_code ec, std::size_t /*number_of_rows*/, std::string&& meta) {
|
|
120
|
+
auto self = weak.lock();
|
|
121
|
+
if (!self) {
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
self->lexer_completed_ = true;
|
|
95
125
|
row_stream_end_signal signal{ ec, std::move(meta) };
|
|
96
126
|
self->rows_.async_send({}, std::move(signal), [self](auto ec) {
|
|
97
127
|
if (ec) {
|
|
@@ -115,7 +145,6 @@ public:
|
|
|
115
145
|
}
|
|
116
146
|
rows_.async_receive(
|
|
117
147
|
[self = shared_from_this(), handler = std::move(handler)](auto ec, auto row) mutable {
|
|
118
|
-
self->buffered_row_count_--;
|
|
119
148
|
if (ec) {
|
|
120
149
|
if (ec == asio::experimental::error::channel_closed ||
|
|
121
150
|
ec == asio::experimental::error::channel_cancelled) {
|
|
@@ -124,66 +153,288 @@ public:
|
|
|
124
153
|
return handler({}, ec);
|
|
125
154
|
}
|
|
126
155
|
if (std::holds_alternative<row_stream_end_signal>(row)) {
|
|
127
|
-
auto signal = std::get<row_stream_end_signal>(row);
|
|
156
|
+
auto& signal = std::get<row_stream_end_signal>(row);
|
|
128
157
|
if (!signal.metadata.empty()) {
|
|
129
|
-
std::
|
|
158
|
+
const std::scoped_lock<std::mutex> lock{ self->metadata_mutex_ };
|
|
130
159
|
self->metadata_ = std::move(signal.metadata);
|
|
131
160
|
}
|
|
132
161
|
return handler({}, signal.ec);
|
|
133
162
|
}
|
|
134
|
-
auto row_content = std::get<std::string>(row);
|
|
163
|
+
auto row_content = std::move(std::get<std::string>(row));
|
|
164
|
+
const auto row_bytes = row_content.size();
|
|
135
165
|
handler(std::move(row_content), {});
|
|
136
166
|
|
|
137
|
-
//
|
|
138
|
-
self->
|
|
167
|
+
// Release the row's budget and resume feeding at or below the low-water mark.
|
|
168
|
+
self->release_buffered_bytes(row_bytes);
|
|
169
|
+
if (self->should_resume()) {
|
|
170
|
+
self->maybe_feed_lexer();
|
|
171
|
+
}
|
|
139
172
|
return;
|
|
140
173
|
});
|
|
141
174
|
}
|
|
142
175
|
|
|
176
|
+
auto set_deadline(std::chrono::time_point<std::chrono::steady_clock> deadline_tp)
|
|
177
|
+
-> io::deadline_state
|
|
178
|
+
{
|
|
179
|
+
// Held across the check and the arm. Testing the flag and then arming as two steps leaves a
|
|
180
|
+
// window in which cancel() can run to completion in between, so this thread would install a
|
|
181
|
+
// deadline on a stream already cancelled and an expiry could report a timeout after it.
|
|
182
|
+
// cancel() takes the same lock around its own state change and its cancel_deadline(), so the
|
|
183
|
+
// two orderings are the only ones: arm-then-cancel, which the cancel supersedes, or
|
|
184
|
+
// cancel-then-arm, which this refuses.
|
|
185
|
+
const std::scoped_lock<std::mutex> lock{ cancel_mutex_ };
|
|
186
|
+
if (cancelled_) {
|
|
187
|
+
return io::deadline_state::body_already_ended;
|
|
188
|
+
}
|
|
189
|
+
// Arms on the calling thread: the body serialises arming, cancelling and the terminal
|
|
190
|
+
// transition under one lock and supersedes an expiry already queued. Not posted, which would
|
|
191
|
+
// leave the previous deadline installed until the post ran.
|
|
192
|
+
const auto on_expiry =
|
|
193
|
+
options_.is_read_only ? io::deadline_terminal::unambiguous : io::deadline_terminal::ambiguous;
|
|
194
|
+
return body_.set_deadline(deadline_tp, on_expiry);
|
|
195
|
+
}
|
|
196
|
+
|
|
143
197
|
void cancel()
|
|
144
198
|
{
|
|
145
|
-
|
|
199
|
+
{
|
|
200
|
+
// The state change and the supersession are one step, against set_deadline() holding the
|
|
201
|
+
// same lock across its check and its arm. Recorded on this thread rather than in the posted
|
|
202
|
+
// teardown: the flag refuses later arms, and cancel_deadline() supersedes one already armed,
|
|
203
|
+
// which would otherwise stay live until the post ran and could take the body's terminal --
|
|
204
|
+
// reporting a timeout for a stream the caller had already cancelled.
|
|
205
|
+
const std::scoped_lock<std::mutex> lock{ cancel_mutex_ };
|
|
206
|
+
cancelled_ = true;
|
|
207
|
+
body_.cancel_deadline();
|
|
208
|
+
}
|
|
209
|
+
// An explicit cancel must win over a racing inter-read idle timer, so it is disarmed here
|
|
210
|
+
// rather than in the posted teardown: a timer completion running before that post would set
|
|
211
|
+
// timed_out_ and the read abort would be reported as a timeout. The completion holds this lock
|
|
212
|
+
// across its generation test and its store, so it either sees the bump and returns, or its
|
|
213
|
+
// store precedes the clear. idle_disarmed_ refuses a timer armed by a read completing before
|
|
214
|
+
// the teardown.
|
|
215
|
+
{
|
|
216
|
+
const std::scoped_lock<std::mutex> lock{ idle_timer_mutex_ };
|
|
217
|
+
idle_disarmed_ = true;
|
|
218
|
+
idle_timer_.cancel();
|
|
219
|
+
idle_generation_.fetch_add(1, std::memory_order_relaxed);
|
|
220
|
+
timed_out_ = false;
|
|
221
|
+
}
|
|
222
|
+
// Tear the body down from the io_context rather than the caller's thread, which may never run
|
|
223
|
+
// it. The channel is thread-safe, so cancel/close it synchronously to unblock a waiting
|
|
224
|
+
// consumer immediately. No post once the read-ahead has ended: the body is closed or drained,
|
|
225
|
+
// and its cancel() does nothing. After cluster close the io_context runs out of work and
|
|
226
|
+
// stops. A post queued then never runs, and its capture holds this streamer and the body until
|
|
227
|
+
// the io_context is destroyed.
|
|
228
|
+
if (!received_all_data_) {
|
|
229
|
+
asio::post(io_, [self = shared_from_this()]() {
|
|
230
|
+
self->body_.cancel();
|
|
231
|
+
});
|
|
232
|
+
}
|
|
146
233
|
rows_.cancel();
|
|
147
234
|
rows_.close();
|
|
148
235
|
}
|
|
149
236
|
|
|
150
237
|
auto metadata() -> std::optional<std::string>
|
|
151
238
|
{
|
|
152
|
-
std::
|
|
239
|
+
const std::scoped_lock<std::mutex> lock{ metadata_mutex_ };
|
|
153
240
|
return metadata_;
|
|
154
241
|
}
|
|
155
242
|
|
|
243
|
+
[[nodiscard]] auto buffered_bytes() const -> std::size_t
|
|
244
|
+
{
|
|
245
|
+
return buffered_bytes_.load(std::memory_order_relaxed);
|
|
246
|
+
}
|
|
247
|
+
|
|
156
248
|
private:
|
|
249
|
+
// Resolve start()'s preamble handler exactly once. The lexer delivers it on a well-formed
|
|
250
|
+
// response (rows-array open or clean root pop); the terminal paths in maybe_feed_lexer call this
|
|
251
|
+
// with an error so a response that never produces a metadata header (empty body, or a valid but
|
|
252
|
+
// non-object JSON root) still resolves the handler instead of hanging the caller forever.
|
|
253
|
+
void deliver_metadata_header(std::error_code ec, std::string&& meta_header)
|
|
254
|
+
{
|
|
255
|
+
if (metadata_header_delivered_.exchange(true)) {
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
// Move the handler out before invoking so it is destroyed right after. The handler owns a
|
|
259
|
+
// strong reference to the owning query/analytics stream (which in turn owns this row_streamer),
|
|
260
|
+
// so keeping it in a member past delivery would form a reference cycle and leak the whole
|
|
261
|
+
// stream.
|
|
262
|
+
auto handler = std::move(metadata_header_handler_);
|
|
263
|
+
if (handler) {
|
|
264
|
+
handler(std::move(meta_header), ec);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// At or below the mark, not strictly below it: buffered_bytes_ is unsigned, so a strict
|
|
269
|
+
// comparison against a zero low-water mark can never hold and the consumer would never ask the
|
|
270
|
+
// producer to resume. Reading would stop for good at the first pause. An empty buffer resumes
|
|
271
|
+
// reading whatever the mark is set to.
|
|
272
|
+
[[nodiscard]] auto should_resume() const -> bool
|
|
273
|
+
{
|
|
274
|
+
return buffered_bytes_.load(std::memory_order_relaxed) <= options_.low_water_bytes;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// Release a row's byte budget, clamping at zero. Uses an atomic compare-exchange loop so the
|
|
278
|
+
// subtract is a single read-modify-write: even if the io_context is driven by more than one
|
|
279
|
+
// thread, a concurrent update to buffered_bytes_ cannot be lost (as it could with a separate
|
|
280
|
+
// load + store).
|
|
281
|
+
//
|
|
282
|
+
// Sequentially consistent, as is the gating load in maybe_feed_lexer(). The handshake needs
|
|
283
|
+
// visibility from consumer to producer, the opposite direction from the channel handoff, which
|
|
284
|
+
// therefore supplies nothing here. A consumer that drains below the low-water mark and then
|
|
285
|
+
// loses the feeding_ compare-exchange leaves the resume to the producer's gate; a gate reading a
|
|
286
|
+
// pre-drain value declines it too, and the stream stalls with no read outstanding and the
|
|
287
|
+
// consumer parked in next_row. Relaxed on both ends leaves that store-buffering shape open, and
|
|
288
|
+
// release/acquire does not forbid it either. Only a total order covering all four accesses --
|
|
289
|
+
// these two and the feeding_ gate's -- rules the stall out.
|
|
290
|
+
void release_buffered_bytes(std::size_t n)
|
|
291
|
+
{
|
|
292
|
+
auto current = buffered_bytes_.load(std::memory_order_relaxed);
|
|
293
|
+
while (!buffered_bytes_.compare_exchange_weak(current,
|
|
294
|
+
current >= n ? current - n : 0,
|
|
295
|
+
std::memory_order_seq_cst,
|
|
296
|
+
std::memory_order_relaxed)) {
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// Arm the inter-read idle timer, if configured. It is armed only while a socket read is in
|
|
301
|
+
// flight (see maybe_feed_lexer) so a legitimately slow consumer — which produces no socket
|
|
302
|
+
// traffic — is never timed out; a fire means the server stalled mid-body.
|
|
303
|
+
void arm_idle_timer()
|
|
304
|
+
{
|
|
305
|
+
if (options_.idle_timeout.count() <= 0) {
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
308
|
+
const std::scoped_lock<std::mutex> lock{ idle_timer_mutex_ };
|
|
309
|
+
if (idle_disarmed_) {
|
|
310
|
+
return;
|
|
311
|
+
}
|
|
312
|
+
const auto generation = idle_generation_.fetch_add(1, std::memory_order_relaxed) + 1;
|
|
313
|
+
idle_timer_.expires_after(options_.idle_timeout);
|
|
314
|
+
idle_timer_.async_wait([weak = weak_from_this(), generation](std::error_code ec) {
|
|
315
|
+
if (ec == asio::error::operation_aborted) {
|
|
316
|
+
return;
|
|
317
|
+
}
|
|
318
|
+
auto self = weak.lock();
|
|
319
|
+
if (!self) {
|
|
320
|
+
return;
|
|
321
|
+
}
|
|
322
|
+
// Ignore a completion from a timer that has already been superseded: when a read completes it
|
|
323
|
+
// cancels and bumps the generation, but a timer that fired at almost the same instant may
|
|
324
|
+
// already be queued. Acting on such a stale fire would abort the healthy next read and
|
|
325
|
+
// misreport it as a timeout.
|
|
326
|
+
{
|
|
327
|
+
// Under the lock cancel_idle_timer() bumps the generation in: on another thread, cancel()
|
|
328
|
+
// could otherwise bump and clear timed_out_ between this test and the store, and the
|
|
329
|
+
// cancelled read would be reported as a timeout.
|
|
330
|
+
const std::scoped_lock<std::mutex> lock{ self->idle_timer_mutex_ };
|
|
331
|
+
if (self->idle_generation_.load(std::memory_order_relaxed) != generation) {
|
|
332
|
+
return;
|
|
333
|
+
}
|
|
334
|
+
// Server stalled while a read was in flight: abort it; the read completion delivers the
|
|
335
|
+
// terminal with a timeout error (steered by timed_out_).
|
|
336
|
+
self->timed_out_ = true;
|
|
337
|
+
}
|
|
338
|
+
self->body_.cancel();
|
|
339
|
+
});
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
// Cancels the idle timer and supersedes its generation, so a completion already queued is
|
|
343
|
+
// ignored.
|
|
344
|
+
void cancel_idle_timer()
|
|
345
|
+
{
|
|
346
|
+
const std::scoped_lock<std::mutex> lock{ idle_timer_mutex_ };
|
|
347
|
+
idle_timer_.cancel();
|
|
348
|
+
idle_generation_.fetch_add(1, std::memory_order_relaxed);
|
|
349
|
+
}
|
|
350
|
+
|
|
157
351
|
void maybe_feed_lexer()
|
|
158
352
|
{
|
|
159
|
-
|
|
353
|
+
// Sequentially consistent, as is the releasing read-modify-write in release_buffered_bytes().
|
|
354
|
+
// A consumer that has drained below the low-water mark and lost the gate below leaves the
|
|
355
|
+
// resume to this load, which has to observe its release.
|
|
356
|
+
if (received_all_data_ ||
|
|
357
|
+
buffered_bytes_.load(std::memory_order_seq_cst) > options_.high_water_bytes) {
|
|
160
358
|
return;
|
|
161
359
|
}
|
|
162
360
|
|
|
163
|
-
|
|
361
|
+
// Atomically claim the feeding gate. With a multi-threaded io_context two resume paths (a
|
|
362
|
+
// next_row completion draining a slot and the feed chain re-entering) can reach here at once;
|
|
363
|
+
// only one may hold an outstanding body_.next().
|
|
364
|
+
bool expected = false;
|
|
365
|
+
if (!feeding_.compare_exchange_strong(expected, true)) {
|
|
366
|
+
return;
|
|
367
|
+
}
|
|
368
|
+
arm_idle_timer();
|
|
164
369
|
|
|
165
|
-
body_.next([self = shared_from_this()](const auto& data, auto ec) mutable {
|
|
370
|
+
body_.next([self = shared_from_this()](const auto& data, bool has_more, auto ec) mutable {
|
|
371
|
+
// A read completed (or was aborted); the idle timer only guards an in-flight read. Cancel it
|
|
372
|
+
// and bump the generation so a timer that fired at the same instant is treated as stale.
|
|
373
|
+
self->cancel_idle_timer();
|
|
166
374
|
if (ec) {
|
|
167
375
|
self->received_all_data_ = true;
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
376
|
+
// If the idle timer aborted the read, report a timeout rather than the raw cancel error.
|
|
377
|
+
// Classify it the way the buffered path (http_command) does: a read-only request is
|
|
378
|
+
// idempotent, so its timeout is unambiguous (definitely not applied); a mutating request
|
|
379
|
+
// must be ambiguous, because a retry layer that reads unambiguous would consider it safe to
|
|
380
|
+
// replay and could double-apply the mutation.
|
|
381
|
+
auto terminal_ec = ec;
|
|
382
|
+
if (self->timed_out_) {
|
|
383
|
+
terminal_ec = self->options_.is_read_only
|
|
384
|
+
? std::error_code{ errc::common::unambiguous_timeout }
|
|
385
|
+
: std::error_code{ errc::common::ambiguous_timeout };
|
|
386
|
+
}
|
|
387
|
+
// A read error before any metadata header (e.g. the connection is reset right after the
|
|
388
|
+
// response headers) must still resolve start()'s handler rather than hang the caller.
|
|
389
|
+
self->deliver_metadata_header(terminal_ec, {});
|
|
390
|
+
// Only enqueue a terminal if the lexer has not already delivered one: on a keep-alive
|
|
391
|
+
// socket a read can error after the JSON document already completed, which would otherwise
|
|
392
|
+
// enqueue a second, never-consumed terminal.
|
|
393
|
+
if (!self->lexer_completed_) {
|
|
394
|
+
auto signal = row_stream_end_signal{ terminal_ec };
|
|
395
|
+
return self->rows_.async_send({}, std::move(signal), [self](auto ec) {
|
|
396
|
+
if (!ec || ec == asio::experimental::error::channel_cancelled ||
|
|
397
|
+
ec == asio::experimental::error::channel_closed) {
|
|
398
|
+
return;
|
|
399
|
+
}
|
|
400
|
+
CB_LOG_WARNING("unexpected error while sending row stream end signal: {} ({})",
|
|
401
|
+
ec.value(),
|
|
402
|
+
ec.message());
|
|
403
|
+
});
|
|
404
|
+
}
|
|
181
405
|
return;
|
|
182
406
|
}
|
|
183
|
-
{
|
|
184
|
-
|
|
407
|
+
if (!data.empty()) {
|
|
408
|
+
// The chunk may be empty even mid-stream (a socket read consumed entirely by HTTP chunk
|
|
409
|
+
// framing); only feed the lexer when there are actual body bytes. End-of-stream is keyed
|
|
410
|
+
// off has_more, never off emptiness, so a framing-only read never looks like completion.
|
|
411
|
+
const std::scoped_lock<std::mutex> lock{ self->data_feed_mutex_ };
|
|
185
412
|
self->lexer_.feed(data);
|
|
186
413
|
}
|
|
414
|
+
if (!has_more) {
|
|
415
|
+
// Transport reached the end of the body. For a well-formed response the lexer's
|
|
416
|
+
// on_complete already delivered the terminal row_stream_end_signal; if the body ended
|
|
417
|
+
// before the JSON document completed (truncated/malformed response), no terminal would
|
|
418
|
+
// otherwise reach the consumer and it would block forever — surface a parsing failure.
|
|
419
|
+
self->received_all_data_ = true;
|
|
420
|
+
if (!self->lexer_completed_) {
|
|
421
|
+
// The body ended without the lexer completing the document: a truncated/malformed
|
|
422
|
+
// response, an empty body, or a valid-JSON-but-not-an-object root that the lexer rejects
|
|
423
|
+
// without ever emitting a metadata header. Resolve start()'s handler and enqueue a
|
|
424
|
+
// terminal so a consumer parked in start() or next_row() is not stuck forever.
|
|
425
|
+
self->deliver_metadata_header(errc::common::parsing_failure, {});
|
|
426
|
+
auto signal = row_stream_end_signal{ errc::common::parsing_failure };
|
|
427
|
+
self->rows_.async_send({}, std::move(signal), [self](auto ec) {
|
|
428
|
+
if (ec && ec != asio::experimental::error::channel_cancelled &&
|
|
429
|
+
ec != asio::experimental::error::channel_closed) {
|
|
430
|
+
CB_LOG_WARNING("unexpected error while sending row stream end signal: {} ({})",
|
|
431
|
+
ec.value(),
|
|
432
|
+
ec.message());
|
|
433
|
+
}
|
|
434
|
+
});
|
|
435
|
+
}
|
|
436
|
+
return;
|
|
437
|
+
}
|
|
187
438
|
|
|
188
439
|
self->feeding_ = false;
|
|
189
440
|
|
|
@@ -196,10 +447,31 @@ private:
|
|
|
196
447
|
asio::experimental::concurrent_channel<void(std::error_code,
|
|
197
448
|
std::variant<std::string, row_stream_end_signal>)>
|
|
198
449
|
rows_;
|
|
199
|
-
|
|
450
|
+
row_streamer_options options_;
|
|
451
|
+
asio::steady_timer idle_timer_;
|
|
452
|
+
// Armed from the read path and cancelled from the read completion and from cancel() on its
|
|
453
|
+
// caller's thread, which differ once more than one thread runs the io_context or cancel() is
|
|
454
|
+
// called off it. Also held across the timer completion's generation test and its timed_out_
|
|
455
|
+
// store.
|
|
456
|
+
std::mutex idle_timer_mutex_{};
|
|
457
|
+
// Set by cancel(); guarded by idle_timer_mutex_.
|
|
458
|
+
bool idle_disarmed_{ false };
|
|
459
|
+
std::atomic_size_t buffered_bytes_{ 0 };
|
|
200
460
|
std::atomic_bool received_all_data_{ false };
|
|
201
461
|
std::atomic_bool feeding_{ false };
|
|
202
|
-
std::
|
|
462
|
+
std::atomic_bool lexer_completed_{ false };
|
|
463
|
+
std::atomic_bool timed_out_{ false };
|
|
464
|
+
// Guards the pair below against set_deadline(): the check and the arm, and the state change and
|
|
465
|
+
// the supersession, each have to be one step or an arm can land on a cancelled stream.
|
|
466
|
+
std::mutex cancel_mutex_{};
|
|
467
|
+
bool cancelled_{ false };
|
|
468
|
+
// Guards the one-shot delivery of the preamble handler (see deliver_metadata_header): the stream
|
|
469
|
+
// must resolve start()'s handler on every terminal, even when the lexer never fires its metadata
|
|
470
|
+
// callback (empty body, or a valid-JSON-but-not-an-object root).
|
|
471
|
+
std::atomic_bool metadata_header_delivered_{ false };
|
|
472
|
+
utils::movable_function<void(std::string, std::error_code)> metadata_header_handler_{};
|
|
473
|
+
// Bumped on every arm/cancel of the idle timer so a superseded timer completion is ignored.
|
|
474
|
+
std::atomic_uint64_t idle_generation_{ 0 };
|
|
203
475
|
std::optional<std::string> metadata_;
|
|
204
476
|
utils::json::streaming_lexer lexer_;
|
|
205
477
|
std::mutex data_feed_mutex_{};
|
|
@@ -208,8 +480,9 @@ private:
|
|
|
208
480
|
|
|
209
481
|
row_streamer::row_streamer(asio::io_context& io,
|
|
210
482
|
couchbase::core::http_response_body body,
|
|
211
|
-
const std::string& pointer_expression
|
|
212
|
-
|
|
483
|
+
const std::string& pointer_expression,
|
|
484
|
+
row_streamer_options options)
|
|
485
|
+
: impl_{ std::make_shared<row_streamer_impl>(io, std::move(body), pointer_expression, options) }
|
|
213
486
|
{
|
|
214
487
|
}
|
|
215
488
|
|
|
@@ -225,6 +498,13 @@ row_streamer::next_row(utils::movable_function<void(std::string, std::error_code
|
|
|
225
498
|
impl_->next_row(std::move(handler));
|
|
226
499
|
}
|
|
227
500
|
|
|
501
|
+
auto
|
|
502
|
+
row_streamer::set_deadline(std::chrono::time_point<std::chrono::steady_clock> deadline_tp)
|
|
503
|
+
-> io::deadline_state
|
|
504
|
+
{
|
|
505
|
+
return impl_->set_deadline(deadline_tp);
|
|
506
|
+
}
|
|
507
|
+
|
|
228
508
|
void
|
|
229
509
|
row_streamer::cancel()
|
|
230
510
|
{
|
|
@@ -236,4 +516,10 @@ row_streamer::metadata() -> std::optional<std::string>
|
|
|
236
516
|
{
|
|
237
517
|
return impl_->metadata();
|
|
238
518
|
}
|
|
519
|
+
|
|
520
|
+
auto
|
|
521
|
+
row_streamer::buffered_bytes() const -> std::size_t
|
|
522
|
+
{
|
|
523
|
+
return impl_->buffered_bytes();
|
|
524
|
+
}
|
|
239
525
|
} // namespace couchbase::core
|
|
@@ -17,8 +17,11 @@
|
|
|
17
17
|
|
|
18
18
|
#pragma once
|
|
19
19
|
|
|
20
|
+
#include "core/io/stream_deadline.hxx"
|
|
21
|
+
#include "row_streamer_options.hxx"
|
|
20
22
|
#include "utils/movable_function.hxx"
|
|
21
23
|
|
|
24
|
+
#include <chrono>
|
|
22
25
|
#include <memory>
|
|
23
26
|
#include <optional>
|
|
24
27
|
#include <string>
|
|
@@ -39,7 +42,8 @@ class row_streamer
|
|
|
39
42
|
public:
|
|
40
43
|
row_streamer(asio::io_context& io,
|
|
41
44
|
http_response_body body,
|
|
42
|
-
const std::string& pointer_expression
|
|
45
|
+
const std::string& pointer_expression,
|
|
46
|
+
row_streamer_options options = {});
|
|
43
47
|
|
|
44
48
|
/**
|
|
45
49
|
* Starts the row stream and returns all the metadata preceding the first row. This typically
|
|
@@ -52,6 +56,26 @@ public:
|
|
|
52
56
|
*/
|
|
53
57
|
void next_row(utils::movable_function<void(std::string, std::error_code)>&& handler);
|
|
54
58
|
|
|
59
|
+
/**
|
|
60
|
+
* Closes the HTTP body at `deadline_tp`, whether or not a row is being pulled, releasing the
|
|
61
|
+
* connection where one is still held. A response received in full holds none: once its bytes
|
|
62
|
+
* have also been parsed the call arms nothing and reports body_already_ended, and while bytes
|
|
63
|
+
* remain unparsed it ends the body, dropping those and reporting the timeout. Rows already
|
|
64
|
+
* parsed stay consumable either way. The inter-read idle timer is armed only while a socket
|
|
65
|
+
* read is in flight, and a consumer stopped above the high-water mark has none.
|
|
66
|
+
*
|
|
67
|
+
* The terminal reaches the consumer through a pull. A pull outstanding at expiry completes
|
|
68
|
+
* with the timeout. A read that completes the response is the exception: the whole response
|
|
69
|
+
* arrived, so those bytes are delivered as a clean end. Otherwise the next pull reports the
|
|
70
|
+
* terminal, after any rows already buffered.
|
|
71
|
+
*
|
|
72
|
+
* Optional. The terminal is unambiguous_timeout for a read-only request, ambiguous_timeout
|
|
73
|
+
* otherwise. Re-arming before expiry replaces the deadline. Once the stream has ended a
|
|
74
|
+
* further call arms nothing and reports body_already_ended. Callable from any thread.
|
|
75
|
+
*/
|
|
76
|
+
[[nodiscard]] auto set_deadline(std::chrono::time_point<std::chrono::steady_clock> deadline_tp)
|
|
77
|
+
-> io::deadline_state;
|
|
78
|
+
|
|
55
79
|
/**
|
|
56
80
|
* Cancels the row stream & closes the HTTP connection
|
|
57
81
|
*/
|
|
@@ -62,6 +86,12 @@ public:
|
|
|
62
86
|
*/
|
|
63
87
|
auto metadata() -> std::optional<std::string>;
|
|
64
88
|
|
|
89
|
+
/**
|
|
90
|
+
* Number of row bytes currently buffered (delivered to the channel but not yet consumed).
|
|
91
|
+
* Exposed for back-pressure observability in tests and diagnostics.
|
|
92
|
+
*/
|
|
93
|
+
[[nodiscard]] auto buffered_bytes() const -> std::size_t;
|
|
94
|
+
|
|
65
95
|
private:
|
|
66
96
|
std::shared_ptr<row_streamer_impl> impl_;
|
|
67
97
|
};
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/* -*- Mode: C++; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
|
|
2
|
+
/*
|
|
3
|
+
* Copyright 2026. Couchbase, Inc.
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
* you may not use this file except in compliance with the License.
|
|
7
|
+
* You may obtain a copy of the License at
|
|
8
|
+
*
|
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
*
|
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
* See the License for the specific language governing permissions and
|
|
15
|
+
* limitations under the License.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
#pragma once
|
|
19
|
+
|
|
20
|
+
#include <chrono>
|
|
21
|
+
#include <cstddef>
|
|
22
|
+
#include <cstdint>
|
|
23
|
+
|
|
24
|
+
namespace couchbase::core
|
|
25
|
+
{
|
|
26
|
+
/**
|
|
27
|
+
* Tuning knobs for the streaming row engine (row_streamer). These are internal to core — there is
|
|
28
|
+
* no public API to set them yet; sensible static defaults apply unless a core caller (e.g. via
|
|
29
|
+
* cluster_options::streaming) overrides them.
|
|
30
|
+
*/
|
|
31
|
+
struct row_streamer_options {
|
|
32
|
+
// JSONPointer nesting depth the lexer descends before treating a value as a row.
|
|
33
|
+
std::uint32_t lexer_depth{ 4 };
|
|
34
|
+
// Buffered row bytes above which socket reads pause, and at or below which they resume
|
|
35
|
+
// (hysteresis). A zero low-water mark is honoured: reading resumes once the buffer is empty.
|
|
36
|
+
std::size_t high_water_bytes{ std::size_t{ 2 } * 1024 * 1024 };
|
|
37
|
+
std::size_t low_water_bytes{ std::size_t{ 512 } * 1024 };
|
|
38
|
+
// Hard ceiling on a single row (or the trailing metadata) before a synthetic error is raised.
|
|
39
|
+
std::size_t max_row_bytes{ std::size_t{ 64 } * 1024 * 1024 };
|
|
40
|
+
// Capacity of the row hand-off channel, in rows. A secondary bound; the byte watermarks above
|
|
41
|
+
// are the primary back-pressure limit.
|
|
42
|
+
std::size_t row_buffer_size{ 100 };
|
|
43
|
+
// Maximum time to wait for the server between socket reads while a read is in flight (an idle
|
|
44
|
+
// timer armed only while pulling, so a legitimately slow consumer is never timed out). Zero
|
|
45
|
+
// disables the timer.
|
|
46
|
+
std::chrono::milliseconds idle_timeout{ 0 };
|
|
47
|
+
// Whether the request being streamed is read-only (idempotent). Mirrors the buffered path's
|
|
48
|
+
// http_command branch. It classifies both timeout terminals, the inter-read idle timeout and
|
|
49
|
+
// the whole-stream deadline: unambiguous_timeout for a read-only request (definitely not
|
|
50
|
+
// applied, safe to retry) and ambiguous_timeout otherwise (a mutating query such as
|
|
51
|
+
// UPDATE ... RETURNING may have partially executed, so a retry layer must not treat it as safe
|
|
52
|
+
// to replay).
|
|
53
|
+
bool is_read_only{ false };
|
|
54
|
+
};
|
|
55
|
+
} // namespace couchbase::core
|