aws_advanced_ruby_driver_wrapper 1.0.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 +7 -0
- data/CHANGELOG.md +23 -0
- data/LICENSE +175 -0
- data/NOTICE +1 -0
- data/README.md +168 -0
- data/THIRD-PARTY-LICENSES +473 -0
- data/aws_advanced_ruby_driver_wrapper.gemspec +73 -0
- data/lib/aws_advanced_ruby_driver_wrapper/active_record/aws_mysql2_adapter.rb +73 -0
- data/lib/aws_advanced_ruby_driver_wrapper/active_record/aws_postgresql_adapter.rb +95 -0
- data/lib/aws_advanced_ruby_driver_wrapper/custom_configuration.rb +58 -0
- data/lib/aws_advanced_ruby_driver_wrapper/db_dialects/aurora_mysql_dialect.rb +103 -0
- data/lib/aws_advanced_ruby_driver_wrapper/db_dialects/aurora_pg_dialect.rb +124 -0
- data/lib/aws_advanced_ruby_driver_wrapper/db_dialects/dialect_codes.rb +38 -0
- data/lib/aws_advanced_ruby_driver_wrapper/db_dialects/global_mysql_dialect.rb +91 -0
- data/lib/aws_advanced_ruby_driver_wrapper/db_dialects/global_pg_dialect.rb +92 -0
- data/lib/aws_advanced_ruby_driver_wrapper/db_dialects/multi_az_cluster_mysql_dialect.rb +95 -0
- data/lib/aws_advanced_ruby_driver_wrapper/db_dialects/multi_az_cluster_pg_dialect.rb +86 -0
- data/lib/aws_advanced_ruby_driver_wrapper/db_dialects/mysql_dialect.rb +98 -0
- data/lib/aws_advanced_ruby_driver_wrapper/db_dialects/pg_dialect.rb +95 -0
- data/lib/aws_advanced_ruby_driver_wrapper/db_dialects/rds_mysql_dialect.rb +88 -0
- data/lib/aws_advanced_ruby_driver_wrapper/db_dialects/rds_pg_dialect.rb +86 -0
- data/lib/aws_advanced_ruby_driver_wrapper/db_dialects/unknown_dialect.rb +72 -0
- data/lib/aws_advanced_ruby_driver_wrapper/db_dialects/utils/dialect_utils.rb +71 -0
- data/lib/aws_advanced_ruby_driver_wrapper/driver_dialects/driver_dialect.rb +154 -0
- data/lib/aws_advanced_ruby_driver_wrapper/driver_dialects/driver_dialect_manager.rb +55 -0
- data/lib/aws_advanced_ruby_driver_wrapper/driver_dialects/mysql_driver_dialect.rb +165 -0
- data/lib/aws_advanced_ruby_driver_wrapper/driver_dialects/pg_driver_dialect.rb +201 -0
- data/lib/aws_advanced_ruby_driver_wrapper/errors/error_handler.rb +62 -0
- data/lib/aws_advanced_ruby_driver_wrapper/errors/mysql_error_handler.rb +80 -0
- data/lib/aws_advanced_ruby_driver_wrapper/errors/pg_error_handler.rb +126 -0
- data/lib/aws_advanced_ruby_driver_wrapper/errors.rb +59 -0
- data/lib/aws_advanced_ruby_driver_wrapper/host/connection_string_host_list_provider.rb +95 -0
- data/lib/aws_advanced_ruby_driver_wrapper/host/global_aurora_host_list_provider.rb +65 -0
- data/lib/aws_advanced_ruby_driver_wrapper/host/host_availability.rb +24 -0
- data/lib/aws_advanced_ruby_driver_wrapper/host/host_availability_strategy.rb +27 -0
- data/lib/aws_advanced_ruby_driver_wrapper/host/host_info.rb +137 -0
- data/lib/aws_advanced_ruby_driver_wrapper/host/host_role.rb +25 -0
- data/lib/aws_advanced_ruby_driver_wrapper/host/random_host_selector.rb +40 -0
- data/lib/aws_advanced_ruby_driver_wrapper/host/rds_host_list_provider.rb +206 -0
- data/lib/aws_advanced_ruby_driver_wrapper/logging.rb +110 -0
- data/lib/aws_advanced_ruby_driver_wrapper/monitoring/cluster_topology_monitor.rb +709 -0
- data/lib/aws_advanced_ruby_driver_wrapper/monitoring/global_cluster_topology_monitor.rb +72 -0
- data/lib/aws_advanced_ruby_driver_wrapper/monitoring/monitor.rb +99 -0
- data/lib/aws_advanced_ruby_driver_wrapper/monitoring/monitor_connection.rb +57 -0
- data/lib/aws_advanced_ruby_driver_wrapper/monitoring/monitor_state.rb +25 -0
- data/lib/aws_advanced_ruby_driver_wrapper/mysql.rb +429 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/blue_green_plugin.rb +205 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/host_mapper.rb +132 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/iam_host_tracker.rb +84 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/interim_status.rb +92 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/interval_rate.rb +27 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/phase.rb +69 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/phase_event_log.rb +85 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/phase_time_info.rb +25 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/role.rb +38 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/routing/base_routing.rb +83 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/routing/reject_connect_routing.rb +40 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/routing/substitute_connect_routing.rb +136 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/routing/suspend_connect_routing.rb +53 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/routing/suspend_execute_routing.rb +52 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/routing/suspend_until_corresponding_host_found_connect_routing.rb +83 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/status.rb +68 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/status_builder.rb +244 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/status_info.rb +30 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/status_monitor.rb +564 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/status_provider.rb +414 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/switchover_state.rb +98 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/switchover_timer.rb +46 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/custom_endpoint/custom_endpoint_monitor.rb +266 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/custom_endpoint/custom_endpoint_plugin.rb +158 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/custom_endpoint/info.rb +111 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/custom_endpoint/member_list_type.rb +31 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/custom_endpoint/role.rb +45 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/default_plugin.rb +108 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/failover_mode.rb +43 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/failover_plugin.rb +467 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/gdb/gdb_failover_mode.rb +68 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/gdb/gdb_failover_plugin.rb +403 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/iam_auth_plugin.rb +159 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/initial_connection_strategy_plugin.rb +485 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/audit_logger.rb +157 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/column_cipher.rb +159 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/column_encryption_config.rb +61 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/connection_source.rb +91 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/data_key_cache.rb +220 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/encryption_algorithm.rb +75 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/encryption_config.rb +146 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/encryption_service.rb +391 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/error_context.rb +198 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/errors.rb +259 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/key_management_utility.rb +435 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/key_manager.rb +378 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/key_metadata.rb +86 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/kms_encryption_plugin.rb +890 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/kms_encryption_utility.rb +281 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/metadata_manager.rb +332 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/sanitizer.rb +147 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/schema_name.rb +70 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/schema_validator.rb +211 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/sql_runner.rb +147 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/type_marker.rb +109 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/secrets_manager_plugin.rb +358 -0
- data/lib/aws_advanced_ruby_driver_wrapper/postgresql.rb +659 -0
- data/lib/aws_advanced_ruby_driver_wrapper/property_definition.rb +409 -0
- data/lib/aws_advanced_ruby_driver_wrapper/ruby_method.rb +122 -0
- data/lib/aws_advanced_ruby_driver_wrapper/services/connection_service.rb +143 -0
- data/lib/aws_advanced_ruby_driver_wrapper/services/dialect_service.rb +267 -0
- data/lib/aws_advanced_ruby_driver_wrapper/services/host_service.rb +199 -0
- data/lib/aws_advanced_ruby_driver_wrapper/services/monitor_service.rb +186 -0
- data/lib/aws_advanced_ruby_driver_wrapper/services/plugin_call_context.rb +63 -0
- data/lib/aws_advanced_ruby_driver_wrapper/services/plugin_manager.rb +273 -0
- data/lib/aws_advanced_ruby_driver_wrapper/services/service_container.rb +30 -0
- data/lib/aws_advanced_ruby_driver_wrapper/services/service_utility.rb +78 -0
- data/lib/aws_advanced_ruby_driver_wrapper/services/session_state_service.rb +56 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/accessible_regions.rb +52 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/ar_constants.rb +25 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/aurora_topology_utils.rb +99 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/aws_credentials_utils.rb +62 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/connection_config.rb +91 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/connection_config_parser.rb +368 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/conversion_utils.rb +51 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/events/batching_event_publisher.rb +119 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/events/data_access_event.rb +26 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/events/monitor_reset_event.rb +26 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/global_aurora_topology_utils.rb +185 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/host_list_utils.rb +27 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/iam_auth_utils.rb +112 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/multi_az_topology_utils.rb +117 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/parser/encryption_annotation_parser.rb +99 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/parser/mysql_statement_analyzer.rb +641 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/parser/pg_statement_analyzer.rb +502 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/parser/query_analysis.rb +63 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/parser/query_type.rb +35 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/parser/routing_hint.rb +27 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/parser/routing_hint_parser.rb +50 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/parser/sql_parser.rb +139 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/rds_url_type.rb +71 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/rds_utils.rb +575 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/retry_util.rb +153 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/sql_encoding.rb +56 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/sql_method_analyzer.rb +195 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/storage/cache_entry.rb +56 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/storage/expiration_cache.rb +108 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/storage/sliding_expiration_cache.rb +137 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/storage/storage_service.rb +172 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/topology_utils.rb +127 -0
- data/lib/aws_advanced_ruby_driver_wrapper/version.rb +19 -0
- data/lib/aws_advanced_ruby_driver_wrapper/wrapper_property.rb +64 -0
- data/lib/aws_advanced_ruby_driver_wrapper.rb +116 -0
- metadata +227 -0
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
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
|
+
require_relative 'cluster_topology_monitor'
|
|
18
|
+
|
|
19
|
+
module AwsAdvancedRubyDriverWrapper
|
|
20
|
+
module Monitoring
|
|
21
|
+
# Topology monitor for Aurora Global Databases spanning multiple AWS regions.
|
|
22
|
+
# Extends ClusterTopologyMonitor by using region-aware instance templates and
|
|
23
|
+
# a global topology query that returns region information per instance.
|
|
24
|
+
class GlobalClusterTopologyMonitor < ClusterTopologyMonitor
|
|
25
|
+
# @param service_container [Services::ServiceContainer]
|
|
26
|
+
# @param cluster_id [String]
|
|
27
|
+
# @param instance_template [Host::HostInfo] default template (used for initial connection)
|
|
28
|
+
# @param instance_templates_by_region [Hash{String => Host::HostInfo}] region -> instance template
|
|
29
|
+
# @param topology_utils [Utils::GlobalAuroraTopologyUtils]
|
|
30
|
+
# @param monitoring_driver_props [Hash]
|
|
31
|
+
# @param monitoring_wrapper_props [Hash]
|
|
32
|
+
def initialize(
|
|
33
|
+
service_container:,
|
|
34
|
+
cluster_id:,
|
|
35
|
+
instance_template:,
|
|
36
|
+
instance_templates_by_region:,
|
|
37
|
+
topology_utils:,
|
|
38
|
+
monitoring_driver_props:,
|
|
39
|
+
monitoring_wrapper_props: {}
|
|
40
|
+
)
|
|
41
|
+
super(
|
|
42
|
+
service_container:,
|
|
43
|
+
cluster_id:,
|
|
44
|
+
instance_template:,
|
|
45
|
+
topology_utils:,
|
|
46
|
+
monitoring_driver_props:,
|
|
47
|
+
monitoring_wrapper_props:
|
|
48
|
+
)
|
|
49
|
+
@instance_templates_by_region = instance_templates_by_region
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
private
|
|
53
|
+
|
|
54
|
+
def query_topology(conn)
|
|
55
|
+
@topology_utils.query_global_topology(conn, initial_host_info, @instance_templates_by_region)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def resolve_instance_template(instance_id, conn)
|
|
59
|
+
region = @topology_utils.query_region(instance_id, conn)
|
|
60
|
+
return @instance_template if region.nil?
|
|
61
|
+
|
|
62
|
+
template = @instance_templates_by_region[region]
|
|
63
|
+
if template.nil?
|
|
64
|
+
logger.warn("[#{@cluster_id}] No instance template for region '#{region}'")
|
|
65
|
+
return @instance_template
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
template
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
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
|
+
require_relative 'monitor_state'
|
|
18
|
+
require_relative '../logging'
|
|
19
|
+
require 'concurrent'
|
|
20
|
+
|
|
21
|
+
module AwsAdvancedRubyDriverWrapper
|
|
22
|
+
module Monitoring
|
|
23
|
+
# Base class for all monitors. Manages thread lifecycle, state transitions,
|
|
24
|
+
# and activity tracking. Subclasses implement #monitor with their polling logic.
|
|
25
|
+
class Monitor
|
|
26
|
+
include Logging
|
|
27
|
+
|
|
28
|
+
attr_reader :last_activity_sec
|
|
29
|
+
|
|
30
|
+
def state
|
|
31
|
+
@state.value
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def initialize(termination_timeout_sec: 30.0)
|
|
35
|
+
@termination_timeout_sec = termination_timeout_sec
|
|
36
|
+
@state = Concurrent::AtomicReference.new(nil)
|
|
37
|
+
@stop_flag = Concurrent::AtomicBoolean.new(false)
|
|
38
|
+
update_activity
|
|
39
|
+
@thread = nil
|
|
40
|
+
@lock = Mutex.new
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def start
|
|
44
|
+
@lock.synchronize do
|
|
45
|
+
return if @state.value == MonitorState::RUNNING
|
|
46
|
+
|
|
47
|
+
@stop_flag.make_false
|
|
48
|
+
@state.set(MonitorState::RUNNING)
|
|
49
|
+
@thread = Thread.new { run }
|
|
50
|
+
@thread.name = "monitor-#{monitor_thread_suffix}"
|
|
51
|
+
logger.debug("Started monitoring thread: #{@thread.name}")
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def stop
|
|
56
|
+
@stop_flag.make_true
|
|
57
|
+
|
|
58
|
+
thread = @thread
|
|
59
|
+
if thread&.alive? && thread != Thread.current
|
|
60
|
+
thread.join(@termination_timeout_sec)
|
|
61
|
+
thread.kill if thread.alive?
|
|
62
|
+
end
|
|
63
|
+
@thread = nil
|
|
64
|
+
|
|
65
|
+
@state.set(MonitorState::STOPPED)
|
|
66
|
+
close
|
|
67
|
+
logger.debug("Stopped monitoring thread: monitor-#{monitor_thread_suffix}")
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def stopped?
|
|
71
|
+
@stop_flag.true?
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def close; end
|
|
75
|
+
|
|
76
|
+
private
|
|
77
|
+
|
|
78
|
+
def run
|
|
79
|
+
monitor
|
|
80
|
+
rescue StandardError => e
|
|
81
|
+
logger.error("Exception in monitoring thread monitor-#{monitor_thread_suffix}: #{e.message}")
|
|
82
|
+
@state.set(MonitorState::ERROR)
|
|
83
|
+
ensure
|
|
84
|
+
@state.compare_and_set(MonitorState::RUNNING, MonitorState::STOPPED)
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def monitor_thread_suffix
|
|
88
|
+
class_name = self.class.name
|
|
89
|
+
return 'unknown' unless class_name
|
|
90
|
+
|
|
91
|
+
class_name.split('::').last.downcase
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def update_activity
|
|
95
|
+
@last_activity_sec = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
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
|
+
module AwsAdvancedRubyDriverWrapper
|
|
18
|
+
module Monitoring
|
|
19
|
+
# Thread-safe connection wrapper with compare-and-set semantics.
|
|
20
|
+
# Automatically closes the old connection when replaced.
|
|
21
|
+
class MonitorConnection
|
|
22
|
+
# @param driver_dialect [DriverDialects::DriverDialect] the driver dialect used to safely close connections.
|
|
23
|
+
def initialize(driver_dialect)
|
|
24
|
+
@connection = Concurrent::AtomicReference.new(nil)
|
|
25
|
+
@driver_dialect = driver_dialect
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Returns the current connection, or nil.
|
|
29
|
+
def get
|
|
30
|
+
@connection.value
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Replaces the current connection. Closes the old one unless close_old is false.
|
|
34
|
+
# @param new_conn [Object, nil] the new connection.
|
|
35
|
+
# @param close_old [Boolean] whether to close the previous connection.
|
|
36
|
+
def set(new_conn, close_old: true)
|
|
37
|
+
old = @connection.get_and_set(new_conn)
|
|
38
|
+
return unless close_old && old && !old.equal?(new_conn)
|
|
39
|
+
|
|
40
|
+
@driver_dialect.close_connection(old)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Atomically sets the connection only if the current value is `expected` (identity check).
|
|
44
|
+
# @param expected [Object, nil] the expected current connection.
|
|
45
|
+
# @param new_conn [Object] the new connection to set.
|
|
46
|
+
# @return [Boolean] true if the swap succeeded.
|
|
47
|
+
def compare_and_set(expected, new_conn)
|
|
48
|
+
@connection.compare_and_set(expected, new_conn)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Closes and nils the connection.
|
|
52
|
+
def close
|
|
53
|
+
set(nil)
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
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
|
+
module AwsAdvancedRubyDriverWrapper
|
|
18
|
+
module Monitoring
|
|
19
|
+
module MonitorState
|
|
20
|
+
RUNNING = :running
|
|
21
|
+
STOPPED = :stopped
|
|
22
|
+
ERROR = :error
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,429 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
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
|
+
# Loads the gem's top-level setup (version constant, global configuration) that this client relies on,
|
|
18
|
+
# so requiring this file alone is enough to use the MySQL client.
|
|
19
|
+
require_relative '../aws_advanced_ruby_driver_wrapper'
|
|
20
|
+
require_relative 'utils/connection_config_parser'
|
|
21
|
+
require_relative 'utils/sql_encoding'
|
|
22
|
+
require_relative 'services/service_utility'
|
|
23
|
+
require_relative 'ruby_method'
|
|
24
|
+
require_relative 'errors'
|
|
25
|
+
|
|
26
|
+
module AwsAdvancedRubyDriverWrapper
|
|
27
|
+
class WrapperMysql2Client
|
|
28
|
+
def self.new(**)
|
|
29
|
+
instance = allocate
|
|
30
|
+
instance.send(:initialize, **)
|
|
31
|
+
instance
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def initialize(**)
|
|
35
|
+
ensure_mysql2!
|
|
36
|
+
config = Utils::ConnectionConfigParser.parse(:mysql2, **)
|
|
37
|
+
@service_container = Services::ServiceUtility.create_standard_container(config)
|
|
38
|
+
@service_container.host_service.refresh_host_list
|
|
39
|
+
@async_conn = nil
|
|
40
|
+
@async_sql = nil
|
|
41
|
+
@last_sql = nil
|
|
42
|
+
conn_service = @service_container.connection_service
|
|
43
|
+
@service_container.plugin_manager.connect(conn_service.initial_host_info, conn_service.driver_props, true)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Explicitly define critical methods (bypass method_missing to avoid method_missing overhead)
|
|
47
|
+
|
|
48
|
+
# This is how mysql2 sends a statement asynchronously as well: +query(sql, async: true)+ returns
|
|
49
|
+
# nothing and the result is read afterward by +async_result+. The connection the statement was
|
|
50
|
+
# sent on is remembered for that read, and so is its SQL, since the read is a call of its own and
|
|
51
|
+
# carries neither.
|
|
52
|
+
#
|
|
53
|
+
# What is remembered is the copy of the SQL that plugins inspect, made once here rather than on
|
|
54
|
+
# every call that is handed it later. The driver is sent the SQL as the application wrote it.
|
|
55
|
+
def query(sql, options = {})
|
|
56
|
+
inspected = Utils::SqlEncoding.inspectable(sql)
|
|
57
|
+
result = pm.execute(RubyMethod::CONNECTION_QUERY, current_conn, ->(*a) { current_conn.query(*a) }, sql, options,
|
|
58
|
+
sql: inspected)
|
|
59
|
+
@last_sql = inspected
|
|
60
|
+
if options[:async]
|
|
61
|
+
@async_conn = current_conn
|
|
62
|
+
@async_sql = inspected
|
|
63
|
+
end
|
|
64
|
+
wrap_mysql_result(result, inspected)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def prepare(sql)
|
|
68
|
+
inspected = Utils::SqlEncoding.inspectable(sql)
|
|
69
|
+
mysql_stmt = pm.execute(RubyMethod::CONNECTION_PREPARE, current_conn, ->(*a) { current_conn.prepare(*a) }, sql,
|
|
70
|
+
sql: inspected)
|
|
71
|
+
Mysql2WrapperStatement.new(@service_container, current_conn, mysql_stmt, inspected)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def escape(string)
|
|
75
|
+
pm.execute(RubyMethod::CONNECTION_ESCAPE, current_conn, ->(*a) { current_conn.escape(*a) }, string)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def ping
|
|
79
|
+
pm.execute(RubyMethod::CONNECTION_PING, current_conn, -> { current_conn.ping })
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def close
|
|
83
|
+
pm.execute(RubyMethod::CONNECTION_CLOSE, current_conn, -> { current_conn.close })
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# -- Async readers (check bounded to @async_conn) --
|
|
87
|
+
|
|
88
|
+
# The result of a statement that was sent with +async: true+. It is read by a call of its own, so
|
|
89
|
+
# it is handed the SQL of the statement it belongs to, and it is refused on any connection other
|
|
90
|
+
# than the one that statement was sent on.
|
|
91
|
+
def async_result
|
|
92
|
+
result = pm.execute(RubyMethod::CONNECTION_ASYNC_RESULT, current_conn, -> { current_conn.async_result },
|
|
93
|
+
bounded_conn: @async_conn, sql: @async_sql)
|
|
94
|
+
sql = @async_sql
|
|
95
|
+
@async_conn = nil
|
|
96
|
+
@async_sql = nil
|
|
97
|
+
wrap_mysql_result(result, sql)
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def store_result
|
|
101
|
+
result = pm.execute(RubyMethod::CONNECTION_STORE_RESULT, current_conn, -> { current_conn.store_result },
|
|
102
|
+
bounded_conn: @async_conn, sql: @async_sql)
|
|
103
|
+
sql = @async_sql
|
|
104
|
+
@async_conn = nil
|
|
105
|
+
@async_sql = nil
|
|
106
|
+
wrap_mysql_result(result, sql)
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def more_results?
|
|
110
|
+
pm.execute(RubyMethod::CONNECTION_MORE_RESULTS, current_conn, -> { current_conn.more_results? }, bounded_conn: @async_conn)
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# A statement that leaves more than one result set is read by moving to each in turn and storing
|
|
114
|
+
# it. Storing a result forgets the connection the statement was sent on and the SQL that was sent
|
|
115
|
+
# on it, and every one of those results belongs to that statement, so both are put back for the
|
|
116
|
+
# read that follows and dropped once there is nothing left to read.
|
|
117
|
+
def next_result
|
|
118
|
+
result = pm.execute(RubyMethod::CONNECTION_NEXT_RESULT, current_conn, -> { current_conn.next_result },
|
|
119
|
+
bounded_conn: @async_conn)
|
|
120
|
+
if result
|
|
121
|
+
@async_conn = current_conn
|
|
122
|
+
@async_sql = @last_sql
|
|
123
|
+
else
|
|
124
|
+
@async_conn = nil
|
|
125
|
+
@async_sql = nil
|
|
126
|
+
end
|
|
127
|
+
result
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
# -- method_missing: covers non-network calls and rarely used network calls --
|
|
131
|
+
|
|
132
|
+
# The network calls that are rare enough not to be worth a method of their own. They are entered
|
|
133
|
+
# into the pipeline under the name the pipeline knows them by rather than as a bare string, so
|
|
134
|
+
# that the connection each is bound to is checked. mysql2 gives none of its calls a second
|
|
135
|
+
# spelling, so unlike pg there is nothing here to translate.
|
|
136
|
+
DYNAMIC_METHODS = {
|
|
137
|
+
abandon_results!: RubyMethod::CONNECTION_ABANDON_RESULTS,
|
|
138
|
+
select_db: RubyMethod::CONNECTION_SELECT_DB,
|
|
139
|
+
set_server_option: RubyMethod::CONNECTION_SET_SERVER_OPTION
|
|
140
|
+
}.freeze
|
|
141
|
+
|
|
142
|
+
# Draining what is left of a statement can only be done on the connection it was sent on.
|
|
143
|
+
BOUNDED_TO_ASYNC = Set[:abandon_results!].freeze
|
|
144
|
+
|
|
145
|
+
def method_missing(method_name, *args, **kwargs, &)
|
|
146
|
+
conn = current_conn
|
|
147
|
+
raise NoMethodError, 'Connection not initialized' if conn.nil?
|
|
148
|
+
raise NoMethodError, "undefined method `#{method_name}' for #{self.class}" unless conn.respond_to?(method_name)
|
|
149
|
+
|
|
150
|
+
method_key = "connection.#{method_name}"
|
|
151
|
+
return conn.send(method_name, *args, **kwargs, &) unless network_bound_methods.include?(method_key)
|
|
152
|
+
|
|
153
|
+
execute_dynamic(method_name, method_key, args, kwargs, &)
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def respond_to_missing?(method, include_private = false)
|
|
157
|
+
current_conn.respond_to?(method, include_private) || super
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
# A concise representation that never exposes the connection config (which carries
|
|
161
|
+
# credentials) or the cached SQL text this instance holds. Defined so a default
|
|
162
|
+
# dump - via logging, interpolation, `pp`, or a backtrace - cannot leak either.
|
|
163
|
+
def inspect
|
|
164
|
+
format('#<%<class>s:0x%<addr>016x>', class: self.class.name, addr: object_id << 1)
|
|
165
|
+
end
|
|
166
|
+
alias to_s inspect
|
|
167
|
+
|
|
168
|
+
def pretty_print(pp)
|
|
169
|
+
pp.text(inspect)
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
private
|
|
173
|
+
|
|
174
|
+
# Runs a call that reached method_missing through the pipeline, telling the plugins the connection
|
|
175
|
+
# it is bound to, which is known here rather than from the arguments of the call.
|
|
176
|
+
def execute_dynamic(method_name, method_key, args, kwargs, &)
|
|
177
|
+
bounded_conn = BOUNDED_TO_ASYNC.include?(method_name) ? @async_conn : nil
|
|
178
|
+
result = pm.execute(
|
|
179
|
+
DYNAMIC_METHODS[method_name] || method_key, current_conn,
|
|
180
|
+
->(*a, **opts, &b) { current_conn.send(method_name, *a, **opts, &b) },
|
|
181
|
+
*args, **kwargs, bounded_conn: bounded_conn, &
|
|
182
|
+
)
|
|
183
|
+
if method_name == :abandon_results!
|
|
184
|
+
@async_conn = nil
|
|
185
|
+
@async_sql = nil
|
|
186
|
+
end
|
|
187
|
+
wrap_mysql_result(result)
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
def ensure_mysql2!
|
|
191
|
+
require 'mysql2'
|
|
192
|
+
rescue LoadError
|
|
193
|
+
raise LoadError, "WrapperMysql2Client requires 'mysql2'. Add it to your Gemfile: gem 'mysql2'"
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
def current_conn
|
|
197
|
+
@service_container.connection_service.current_connection
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
def pm
|
|
201
|
+
@service_container.plugin_manager
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
def network_bound_methods
|
|
205
|
+
@network_bound_methods ||= @service_container.dialect_service.driver_dialect.network_bound_methods
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
def wrap_mysql_result(result, sql = nil)
|
|
209
|
+
return result unless result.is_a?(Mysql2::Result)
|
|
210
|
+
|
|
211
|
+
Mysql2WrapperResult.new(result, @service_container, current_conn, sql)
|
|
212
|
+
end
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
class Mysql2WrapperStatement
|
|
216
|
+
# @param sql [String, nil] the SQL the statement was prepared with, kept so that plugins
|
|
217
|
+
# which inspect statements still see it when the statement is executed
|
|
218
|
+
def initialize(service_container, connection, mysql_stmt, sql = nil)
|
|
219
|
+
@service_container = service_container
|
|
220
|
+
@connection = connection
|
|
221
|
+
@mysql_stmt = mysql_stmt
|
|
222
|
+
@sql = sql
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
def execute(*params, **)
|
|
226
|
+
result = pm.execute(
|
|
227
|
+
RubyMethod::STATEMENT_EXECUTE, current_conn,
|
|
228
|
+
->(*p, **o) { @mysql_stmt.execute(*p, **o) },
|
|
229
|
+
*params, bounded_conn: @connection, sql: @sql, **
|
|
230
|
+
)
|
|
231
|
+
return result unless result.is_a?(Mysql2::Result)
|
|
232
|
+
|
|
233
|
+
# The statement is handed to the result so it can read column names from the statement's
|
|
234
|
+
# metadata rather than from Mysql2::Result#fields, which dereferences the result's field
|
|
235
|
+
# pointer and segfaults on a prepared statement that returned no rows.
|
|
236
|
+
Mysql2WrapperResult.new(result, @service_container, @connection, @sql, @mysql_stmt)
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
def close
|
|
240
|
+
pm.execute(RubyMethod::STATEMENT_CLOSE, current_conn, -> { @mysql_stmt.close })
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
# Delegate non-network methods directly
|
|
244
|
+
def fields
|
|
245
|
+
@mysql_stmt.fields
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
def field_count
|
|
249
|
+
@mysql_stmt.field_count
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
def param_count
|
|
253
|
+
@mysql_stmt.param_count
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
def affected_rows
|
|
257
|
+
@mysql_stmt.affected_rows
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
def last_id
|
|
261
|
+
@mysql_stmt.last_id
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
def closed?
|
|
265
|
+
@mysql_stmt.closed?
|
|
266
|
+
end
|
|
267
|
+
|
|
268
|
+
def inspect
|
|
269
|
+
format('#<%<class>s:0x%<addr>016x>', class: self.class.name, addr: object_id << 1)
|
|
270
|
+
end
|
|
271
|
+
alias to_s inspect
|
|
272
|
+
|
|
273
|
+
def pretty_print(pp)
|
|
274
|
+
pp.text(inspect)
|
|
275
|
+
end
|
|
276
|
+
|
|
277
|
+
private
|
|
278
|
+
|
|
279
|
+
def current_conn
|
|
280
|
+
@service_container.connection_service.current_connection
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
def pm
|
|
284
|
+
@service_container.plugin_manager
|
|
285
|
+
end
|
|
286
|
+
end
|
|
287
|
+
|
|
288
|
+
class Mysql2WrapperResult
|
|
289
|
+
include Enumerable
|
|
290
|
+
|
|
291
|
+
# @param sql [String, nil] the SQL that produced the result, kept so that plugins which
|
|
292
|
+
# inspect statements still see it when the rows are read
|
|
293
|
+
# @param statement [Mysql2::Statement, nil] the prepared statement the result came from, if any,
|
|
294
|
+
# used as a safe source of column names (see {#result_field_names})
|
|
295
|
+
def initialize(result, service_container, connection, sql = nil, statement = nil)
|
|
296
|
+
@result = result
|
|
297
|
+
@service_container = service_container
|
|
298
|
+
@connection = connection
|
|
299
|
+
@sql = sql
|
|
300
|
+
@statement = statement
|
|
301
|
+
end
|
|
302
|
+
|
|
303
|
+
def each(*args, &block)
|
|
304
|
+
pm.execute(RubyMethod::RESULT_EACH, current_conn, ->(&blk) { @result.each(*args, &blk) },
|
|
305
|
+
bounded_conn: @connection, sql: @sql, field_names: -> { result_field_names }, &block)
|
|
306
|
+
end
|
|
307
|
+
|
|
308
|
+
def to_a
|
|
309
|
+
pm.execute(RubyMethod::RESULT_TO_A, current_conn, -> { @result.to_a },
|
|
310
|
+
bounded_conn: @connection, sql: @sql, field_names: -> { result_field_names })
|
|
311
|
+
end
|
|
312
|
+
|
|
313
|
+
def [](index)
|
|
314
|
+
pm.execute(RubyMethod::RESULT_BRACKET, current_conn, ->(*a) { @result[*a] }, index,
|
|
315
|
+
bounded_conn: @connection, sql: @sql, field_names: -> { result_field_names })
|
|
316
|
+
end
|
|
317
|
+
|
|
318
|
+
# A buffered result is already in client memory, so letting it go is local. An unbuffered one,
|
|
319
|
+
# from +query(sql, stream: true)+, still has whatever was not read on the wire, and libmysql has
|
|
320
|
+
# to drain it before it can free the result. That makes this a call to the server, on the
|
|
321
|
+
# connection the statement was sent on, so it should go through the pipeline.
|
|
322
|
+
def free
|
|
323
|
+
pm.execute(RubyMethod::RESULT_FREE, current_conn, -> { @result.free }, bounded_conn: @connection, sql: @sql)
|
|
324
|
+
end
|
|
325
|
+
|
|
326
|
+
# Mysql2::Statement#execute reads a buffered result to the end before returning it, and when that
|
|
327
|
+
# read finds no rows mysql2 frees the result's metadata without having cached any column names.
|
|
328
|
+
# Mysql2::Result#fields then reads the freed metadata - an empty list at best, a segfault at worst
|
|
329
|
+
# (ActiveRecord calls it on every result, so any finder that matches nothing can crash). The
|
|
330
|
+
# names for that one case come from the statement's own metadata, symbolized when the result was
|
|
331
|
+
# asked for symbolize_keys just as Mysql2::Result#fields would. Every other result is read
|
|
332
|
+
# directly, so a freed streaming result still raises as the driver does.
|
|
333
|
+
def fields
|
|
334
|
+
return @result.fields unless empty_buffered_statement_result?
|
|
335
|
+
|
|
336
|
+
names = @statement.fields
|
|
337
|
+
result_query_options[:symbolize_keys] ? names.map(&:to_sym) : names
|
|
338
|
+
end
|
|
339
|
+
|
|
340
|
+
def field_types
|
|
341
|
+
@result.field_types
|
|
342
|
+
end
|
|
343
|
+
|
|
344
|
+
def count
|
|
345
|
+
@result.count
|
|
346
|
+
end
|
|
347
|
+
|
|
348
|
+
def size
|
|
349
|
+
@result.size
|
|
350
|
+
end
|
|
351
|
+
|
|
352
|
+
def server_flags
|
|
353
|
+
@result.server_flags
|
|
354
|
+
end
|
|
355
|
+
|
|
356
|
+
def query_time
|
|
357
|
+
@result.query_time
|
|
358
|
+
end
|
|
359
|
+
|
|
360
|
+
def tables
|
|
361
|
+
@result.tables
|
|
362
|
+
end
|
|
363
|
+
|
|
364
|
+
def dbs
|
|
365
|
+
@result.dbs
|
|
366
|
+
end
|
|
367
|
+
|
|
368
|
+
def method_missing(method_name, ...)
|
|
369
|
+
return @result.send(method_name, ...) if @result.respond_to?(method_name)
|
|
370
|
+
|
|
371
|
+
super
|
|
372
|
+
end
|
|
373
|
+
|
|
374
|
+
def respond_to_missing?(method_name, include_private = false)
|
|
375
|
+
@result.respond_to?(method_name, include_private) || super
|
|
376
|
+
end
|
|
377
|
+
|
|
378
|
+
def inspect
|
|
379
|
+
format('#<%<class>s:0x%<addr>016x>', class: self.class.name, addr: object_id << 1)
|
|
380
|
+
end
|
|
381
|
+
alias to_s inspect
|
|
382
|
+
|
|
383
|
+
def pretty_print(pp)
|
|
384
|
+
pp.text(inspect)
|
|
385
|
+
end
|
|
386
|
+
|
|
387
|
+
private
|
|
388
|
+
|
|
389
|
+
# Column names for the plugins that inspect a result as it is read, such as one that decrypts
|
|
390
|
+
# certain columns and has to know which position each holds. This is internal plumbing, not the
|
|
391
|
+
# driver's own #fields - so unlike #fields it must always yield the real column names, including
|
|
392
|
+
# for a prepared-statement result that fetched no rows. Mysql2::Result#fields cannot supply them
|
|
393
|
+
# there: it dereferences the result's field pointer, which mysql2 never populates until a row is
|
|
394
|
+
# fetched, so it returns an empty list or, on some client libraries, segfaults. In that one case
|
|
395
|
+
# the names come from the statement's own metadata (mysql_stmt_result_metadata), which carries
|
|
396
|
+
# them whether or not any rows came back. Once a row has been fetched the field cache is in place
|
|
397
|
+
# and the result's own fields are safe; results from +query+ carry no statement and read directly.
|
|
398
|
+
def result_field_names
|
|
399
|
+
# none? would iterate the result and consume an unbuffered one; count reads the driver's own
|
|
400
|
+
# row count without touching the rows.
|
|
401
|
+
return @statement.fields if @statement && @result.count.zero? # rubocop:disable Style/CollectionQuerying
|
|
402
|
+
|
|
403
|
+
@result.fields
|
|
404
|
+
end
|
|
405
|
+
|
|
406
|
+
# A streaming result is not read until the caller iterates it, so only a buffered one has had its
|
|
407
|
+
# metadata freed by the time it is returned. count reads the driver's own row count without
|
|
408
|
+
# touching the rows.
|
|
409
|
+
def empty_buffered_statement_result?
|
|
410
|
+
@statement && !result_query_options[:stream] && @result.count.zero? # rubocop:disable Style/CollectionQuerying
|
|
411
|
+
end
|
|
412
|
+
|
|
413
|
+
# Part of the workaround in #fields for a mysql2 bug that can segfault when an empty
|
|
414
|
+
# prepared-statement result is asked for its fields. Telling that case apart, and symbolizing the
|
|
415
|
+
# fallback names, needs the options the result was produced with; mysql2 keeps them in this ivar
|
|
416
|
+
# and exposes no reader for it.
|
|
417
|
+
def result_query_options
|
|
418
|
+
@result.instance_variable_get(:@query_options) || {}
|
|
419
|
+
end
|
|
420
|
+
|
|
421
|
+
def current_conn
|
|
422
|
+
@service_container.connection_service.current_connection
|
|
423
|
+
end
|
|
424
|
+
|
|
425
|
+
def pm
|
|
426
|
+
@service_container.plugin_manager
|
|
427
|
+
end
|
|
428
|
+
end
|
|
429
|
+
end
|