aws_advanced_ruby_driver_wrapper 1.0.1 → 1.1.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/CHANGELOG.md +32 -2
- data/README.md +115 -6
- data/THIRD-PARTY-LICENSES +70 -11
- data/aws_advanced_ruby_driver_wrapper.gemspec +9 -0
- data/lib/aws_advanced_ruby_driver_wrapper/active_record/aws_postgresql_adapter.rb +17 -3
- data/lib/aws_advanced_ruby_driver_wrapper/active_record/aws_trilogy_adapter.rb +80 -0
- data/lib/aws_advanced_ruby_driver_wrapper/active_record/type_adapter_alias.rb +1 -1
- data/lib/aws_advanced_ruby_driver_wrapper/driver_dialects/driver_dialect.rb +43 -0
- data/lib/aws_advanced_ruby_driver_wrapper/driver_dialects/driver_dialect_manager.rb +11 -4
- data/lib/aws_advanced_ruby_driver_wrapper/driver_dialects/mysql2_driver_dialect.rb +129 -0
- data/lib/aws_advanced_ruby_driver_wrapper/driver_dialects/mysql_driver_dialect.rb +9 -91
- data/lib/aws_advanced_ruby_driver_wrapper/driver_dialects/pg_driver_dialect.rb +18 -0
- data/lib/aws_advanced_ruby_driver_wrapper/driver_dialects/trilogy_driver_dialect.rb +118 -0
- data/lib/aws_advanced_ruby_driver_wrapper/errors/error_handler.rb +16 -0
- data/lib/aws_advanced_ruby_driver_wrapper/errors/mysql_error_handler.rb +10 -5
- data/lib/aws_advanced_ruby_driver_wrapper/errors/trilogy_error_handler.rb +127 -0
- data/lib/aws_advanced_ruby_driver_wrapper/errors.rb +1 -0
- data/lib/aws_advanced_ruby_driver_wrapper/monitoring/cluster_topology_monitor.rb +1 -0
- data/lib/aws_advanced_ruby_driver_wrapper/mysql.rb +27 -9
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/blue_green_plugin.rb +115 -7
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/drop_blue_connections_marker.rb +84 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/host_mapper.rb +3 -1
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/routing/base_routing.rb +7 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/routing/substitute_connect_routing.rb +68 -41
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/routing/suspend_connect_routing.rb +4 -2
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/routing/suspend_execute_routing.rb +5 -3
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/routing/suspend_until_corresponding_host_found_connect_routing.rb +7 -1
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/status_builder.rb +1 -1
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/status_monitor.rb +6 -13
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/status_provider.rb +90 -3
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/custom_endpoint/custom_endpoint_monitor.rb +11 -1
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/custom_endpoint/custom_endpoint_plugin.rb +4 -1
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/default_plugin.rb +14 -3
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/failover_plugin.rb +68 -3
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/federated_auth/base_saml_auth_plugin.rb +266 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/federated_auth/federated_auth_plugin.rb +134 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/federated_auth/okta_auth_plugin.rb +113 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/gdb/gdb_failover_plugin.rb +30 -9
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/iam_auth_plugin.rb +16 -2
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/initial_connection_strategy_plugin.rb +4 -1
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/key_management_utility.rb +6 -3
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/key_manager.rb +13 -4
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/kms_encryption_plugin.rb +29 -6
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/sql/trigger_mysql.sql +108 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/sql/trigger_pg.sql +54 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/secrets_manager_plugin.rb +14 -0
- data/lib/aws_advanced_ruby_driver_wrapper/postgresql.rb +41 -23
- data/lib/aws_advanced_ruby_driver_wrapper/property_definition.rb +63 -1
- data/lib/aws_advanced_ruby_driver_wrapper/ruby_method.rb +3 -0
- data/lib/aws_advanced_ruby_driver_wrapper/services/dialect_service.rb +20 -7
- data/lib/aws_advanced_ruby_driver_wrapper/services/host_service.rb +34 -8
- data/lib/aws_advanced_ruby_driver_wrapper/services/monitor_service.rb +1 -1
- data/lib/aws_advanced_ruby_driver_wrapper/services/plugin_manager.rb +80 -29
- data/lib/aws_advanced_ruby_driver_wrapper/services/service_container.rb +10 -2
- data/lib/aws_advanced_ruby_driver_wrapper/services/service_utility.rb +4 -1
- data/lib/aws_advanced_ruby_driver_wrapper/trilogy.rb +167 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/connection_config.rb +1 -1
- data/lib/aws_advanced_ruby_driver_wrapper/utils/connection_config_parser.rb +25 -15
- data/lib/aws_advanced_ruby_driver_wrapper/utils/global_aurora_topology_utils.rb +2 -2
- data/lib/aws_advanced_ruby_driver_wrapper/utils/iam_auth_utils.rb +13 -4
- data/lib/aws_advanced_ruby_driver_wrapper/utils/parser/encryption_annotation_parser.rb +15 -31
- data/lib/aws_advanced_ruby_driver_wrapper/utils/parser/mysql_statement_analyzer.rb +55 -76
- data/lib/aws_advanced_ruby_driver_wrapper/utils/parser/mysql_text.rb +215 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/parser/pg_statement_analyzer.rb +11 -20
- data/lib/aws_advanced_ruby_driver_wrapper/utils/parser/query_analysis.rb +2 -3
- data/lib/aws_advanced_ruby_driver_wrapper/utils/parser/sql_parser.rb +21 -14
- data/lib/aws_advanced_ruby_driver_wrapper/utils/retry_util.rb +2 -1
- data/lib/aws_advanced_ruby_driver_wrapper/utils/saml_utils.rb +133 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/storage/storage_service.rb +9 -5
- data/lib/aws_advanced_ruby_driver_wrapper/utils/telemetry/null_telemetry.rb +89 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/telemetry/open_telemetry_context.rb +124 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/telemetry/open_telemetry_counter.rb +39 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/telemetry/open_telemetry_factory.rb +122 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/telemetry/open_telemetry_gauge.rb +36 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/telemetry/telemetry_const.rb +50 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/telemetry/telemetry_factory.rb +143 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/topology_utils.rb +9 -2
- data/lib/aws_advanced_ruby_driver_wrapper/version.rb +1 -1
- data/lib/aws_advanced_ruby_driver_wrapper.rb +9 -2
- metadata +54 -2
|
@@ -14,6 +14,8 @@
|
|
|
14
14
|
# See the License for the specific language governing permissions and
|
|
15
15
|
# limitations under the License.
|
|
16
16
|
|
|
17
|
+
require_relative '../utils/telemetry/null_telemetry'
|
|
18
|
+
|
|
17
19
|
module AwsAdvancedRubyDriverWrapper
|
|
18
20
|
module Services
|
|
19
21
|
ServiceContainer = Struct.new(
|
|
@@ -24,7 +26,13 @@ module AwsAdvancedRubyDriverWrapper
|
|
|
24
26
|
:plugin_manager,
|
|
25
27
|
:session_state_service,
|
|
26
28
|
:storage_service,
|
|
27
|
-
:monitor_service
|
|
28
|
-
|
|
29
|
+
:monitor_service,
|
|
30
|
+
:telemetry_factory
|
|
31
|
+
) do
|
|
32
|
+
# A container that was given no telemetry factory records no telemetry.
|
|
33
|
+
def telemetry_factory
|
|
34
|
+
self[:telemetry_factory] || Utils::Telemetry::NullTelemetryFactory::INSTANCE
|
|
35
|
+
end
|
|
36
|
+
end
|
|
29
37
|
end
|
|
30
38
|
end
|
|
@@ -23,6 +23,7 @@ require_relative 'monitor_service'
|
|
|
23
23
|
require_relative 'session_state_service'
|
|
24
24
|
require_relative '../utils/storage/storage_service'
|
|
25
25
|
require_relative '../utils/events/batching_event_publisher'
|
|
26
|
+
require_relative '../utils/telemetry/telemetry_factory'
|
|
26
27
|
|
|
27
28
|
module AwsAdvancedRubyDriverWrapper
|
|
28
29
|
module Services
|
|
@@ -50,9 +51,10 @@ module AwsAdvancedRubyDriverWrapper
|
|
|
50
51
|
end
|
|
51
52
|
|
|
52
53
|
module ServiceUtility
|
|
53
|
-
def self.create_standard_container(config)
|
|
54
|
+
def self.create_standard_container(config, telemetry_factory: nil)
|
|
54
55
|
container = ServiceContainer.new
|
|
55
56
|
container.event_publisher = CoreServices.event_publisher
|
|
57
|
+
container.telemetry_factory = telemetry_factory || Utils::Telemetry::TelemetryFactory.new(config.wrapper_props)
|
|
56
58
|
container.connection_service = ConnectionService.new(container, config)
|
|
57
59
|
container.dialect_service = DialectService.new(container.connection_service, config.driver_name)
|
|
58
60
|
container.host_service = HostService.new(container)
|
|
@@ -71,6 +73,7 @@ module AwsAdvancedRubyDriverWrapper
|
|
|
71
73
|
container.event_publisher = parent_container.event_publisher
|
|
72
74
|
container.dialect_service = parent_container.dialect_service
|
|
73
75
|
container.host_service = parent_container.host_service
|
|
76
|
+
container.telemetry_factory = parent_container.telemetry_factory
|
|
74
77
|
container
|
|
75
78
|
end
|
|
76
79
|
end
|
|
@@ -0,0 +1,167 @@
|
|
|
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 '../aws_advanced_ruby_driver_wrapper'
|
|
18
|
+
require_relative 'utils/connection_config_parser'
|
|
19
|
+
require_relative 'utils/sql_encoding'
|
|
20
|
+
require_relative 'services/service_utility'
|
|
21
|
+
require_relative 'ruby_method'
|
|
22
|
+
require_relative 'errors'
|
|
23
|
+
|
|
24
|
+
module AwsAdvancedRubyDriverWrapper
|
|
25
|
+
# A drop-in for a Trilogy connection. Trilogy has no prepared statements and no asynchronous or
|
|
26
|
+
# streaming reads, and it reads a result off the wire in full before returning it, so the only calls
|
|
27
|
+
# that reach the server are made on the connection itself, and results are handed back unwrapped.
|
|
28
|
+
class WrapperTrilogyClient
|
|
29
|
+
# Trilogy.new takes its options as a single hash, so this does too, and keyword arguments as well.
|
|
30
|
+
def self.new(options = {}, **)
|
|
31
|
+
instance = allocate
|
|
32
|
+
instance.send(:initialize, options, **)
|
|
33
|
+
instance
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def initialize(options = {}, **)
|
|
37
|
+
ensure_trilogy!
|
|
38
|
+
config = Utils::ConnectionConfigParser.parse(:trilogy, **options, **)
|
|
39
|
+
@service_container = Services::ServiceUtility.create_standard_container(config)
|
|
40
|
+
@service_container.host_service.refresh_host_list
|
|
41
|
+
@query_conn = nil
|
|
42
|
+
@last_sql = nil
|
|
43
|
+
conn_service = @service_container.connection_service
|
|
44
|
+
@service_container.plugin_manager.connect(conn_service.initial_host_info, conn_service.driver_props, true)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Explicitly define critical methods (bypass method_missing to avoid method_missing overhead)
|
|
48
|
+
|
|
49
|
+
# The connection a statement was sent on is remembered, and so is its SQL, because the further
|
|
50
|
+
# result sets a statement leaves are read by calls of their own (next_result, abandon_results!),
|
|
51
|
+
# which carry neither and can only be made on that connection.
|
|
52
|
+
#
|
|
53
|
+
# What is remembered is the copy of the SQL that plugins inspect. The driver is sent the SQL as
|
|
54
|
+
# the application wrote it.
|
|
55
|
+
def query(sql)
|
|
56
|
+
ensure_connected!
|
|
57
|
+
inspected = Utils::SqlEncoding.inspectable(sql)
|
|
58
|
+
result = pm.execute(RubyMethod::CONNECTION_QUERY, current_conn, ->(*a) { current_conn.query(*a) }, sql,
|
|
59
|
+
sql: inspected)
|
|
60
|
+
@query_conn = current_conn
|
|
61
|
+
@last_sql = inspected
|
|
62
|
+
result
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def ping
|
|
66
|
+
ensure_connected!
|
|
67
|
+
pm.execute(RubyMethod::CONNECTION_PING, current_conn, -> { current_conn.ping })
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def close
|
|
71
|
+
return if current_conn.nil?
|
|
72
|
+
|
|
73
|
+
pm.execute(RubyMethod::CONNECTION_CLOSE, current_conn, -> { current_conn.close })
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def next_result
|
|
77
|
+
pm.execute(RubyMethod::CONNECTION_NEXT_RESULT, current_conn, -> { current_conn.next_result },
|
|
78
|
+
bounded_conn: @query_conn, sql: @last_sql)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Trilogy's own query_with_flags calls query on the driver connection directly, so it is defined
|
|
82
|
+
# here to send the statement through the plugin pipeline instead.
|
|
83
|
+
def query_with_flags(sql, flags)
|
|
84
|
+
ensure_connected!
|
|
85
|
+
old_flags = current_conn.query_flags
|
|
86
|
+
current_conn.query_flags = flags
|
|
87
|
+
query(sql)
|
|
88
|
+
ensure
|
|
89
|
+
current_conn.query_flags = old_flags if old_flags
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# -- method_missing: covers non-network calls and rarely used network calls --
|
|
93
|
+
|
|
94
|
+
# The network calls that are rare enough not to be worth a method of their own. They are entered
|
|
95
|
+
# into the pipeline under the name the pipeline knows them by rather than as a bare string, so
|
|
96
|
+
# that the connection each is bound to is checked.
|
|
97
|
+
DYNAMIC_METHODS = {
|
|
98
|
+
abandon_results!: RubyMethod::CONNECTION_ABANDON_RESULTS,
|
|
99
|
+
change_db: RubyMethod::CONNECTION_CHANGE_DB,
|
|
100
|
+
set_server_option: RubyMethod::CONNECTION_SET_SERVER_OPTION
|
|
101
|
+
}.freeze
|
|
102
|
+
|
|
103
|
+
# Draining what is left of a statement can only be done on the connection it was sent on.
|
|
104
|
+
BOUNDED_TO_QUERY = Set[:abandon_results!].freeze
|
|
105
|
+
|
|
106
|
+
def method_missing(method_name, *, **, &)
|
|
107
|
+
conn = ensure_connected!
|
|
108
|
+
raise NoMethodError, "undefined method `#{method_name}' for #{self.class}" unless conn.respond_to?(method_name)
|
|
109
|
+
|
|
110
|
+
method_key = "connection.#{method_name}"
|
|
111
|
+
return conn.send(method_name, *, **, &) unless network_bound_methods.include?(method_key)
|
|
112
|
+
|
|
113
|
+
bounded_conn = BOUNDED_TO_QUERY.include?(method_name) ? @query_conn : nil
|
|
114
|
+
pm.execute(
|
|
115
|
+
DYNAMIC_METHODS[method_name] || method_key, current_conn,
|
|
116
|
+
->(*a, **opts, &b) { current_conn.send(method_name, *a, **opts, &b) },
|
|
117
|
+
*, **, bounded_conn: bounded_conn, &
|
|
118
|
+
)
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def respond_to_missing?(method, include_private = false)
|
|
122
|
+
current_conn.respond_to?(method, include_private) || super
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
# A concise representation that never exposes the connection config (which carries
|
|
126
|
+
# credentials) or the cached SQL text this instance holds. Defined so a default
|
|
127
|
+
# dump - via logging, interpolation, `pp`, or a backtrace - cannot leak either.
|
|
128
|
+
def inspect
|
|
129
|
+
format('#<%<class>s:0x%<addr>016x>', class: self.class.name, addr: object_id << 1)
|
|
130
|
+
end
|
|
131
|
+
alias to_s inspect
|
|
132
|
+
|
|
133
|
+
def pretty_print(pp)
|
|
134
|
+
pp.text(inspect)
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
private
|
|
138
|
+
|
|
139
|
+
def ensure_trilogy!
|
|
140
|
+
require 'trilogy'
|
|
141
|
+
rescue LoadError
|
|
142
|
+
raise LoadError, "WrapperTrilogyClient requires 'trilogy'. Add it to your Gemfile: gem 'trilogy'"
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
def current_conn
|
|
146
|
+
@service_container.connection_service.current_connection
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
# Guards the entry points that reach for the current connection directly (the query paths and
|
|
150
|
+
# method_missing). Without a live connection AR expects the driver's own "not connected" error,
|
|
151
|
+
# which it translates to a connection error; a bare NoMethodError on nil would not be recognized.
|
|
152
|
+
def ensure_connected!
|
|
153
|
+
conn = current_conn
|
|
154
|
+
raise Trilogy::BaseConnectionError, 'MySQL client is not connected' if conn.nil?
|
|
155
|
+
|
|
156
|
+
conn
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
def pm
|
|
160
|
+
@service_container.plugin_manager
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
def network_bound_methods
|
|
164
|
+
@network_bound_methods ||= @service_container.dialect_service.driver_dialect.network_bound_methods
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
end
|
|
@@ -49,7 +49,7 @@ module AwsAdvancedRubyDriverWrapper
|
|
|
49
49
|
end
|
|
50
50
|
|
|
51
51
|
def cluster_id
|
|
52
|
-
PropertyDefinition::CLUSTER_ID.
|
|
52
|
+
PropertyDefinition::CLUSTER_ID.get_string(wrapper_props)
|
|
53
53
|
end
|
|
54
54
|
|
|
55
55
|
# Redact sensitive property values (passwords, IAM tokens, Secrets Manager
|
|
@@ -32,9 +32,10 @@ module AwsAdvancedRubyDriverWrapper
|
|
|
32
32
|
module_function
|
|
33
33
|
|
|
34
34
|
# Main entry point. The driver_name is provided by the wrapper class
|
|
35
|
-
# (e.g. :postgresql from WrapperPgConnection, :mysql2 from WrapperMysql2Client
|
|
35
|
+
# (e.g. :postgresql from WrapperPgConnection, :mysql2 from WrapperMysql2Client, :trilogy from
|
|
36
|
+
# WrapperTrilogyClient).
|
|
36
37
|
#
|
|
37
|
-
# @param driver_name [Symbol] :postgresql or :
|
|
38
|
+
# @param driver_name [Symbol] :postgresql, :mysql2 or :trilogy
|
|
38
39
|
def parse(driver_name, *args, **kwargs)
|
|
39
40
|
if driver_name == :mysql2 && !args.empty? && !args.first.is_a?(Hash)
|
|
40
41
|
raise ArgumentError,
|
|
@@ -192,7 +193,9 @@ module AwsAdvancedRubyDriverWrapper
|
|
|
192
193
|
# Validates that values in a config hash match the expected type of their corresponding WrapperProperty.
|
|
193
194
|
# Raises TypeError if a value is present but incompatible. Strings that represent valid integers
|
|
194
195
|
# are accepted for Integer-typed properties. Float-typed properties also accept Integers and
|
|
195
|
-
# strings that represent a valid integer or decimal number.
|
|
196
|
+
# strings that represent a valid integer or decimal number. String-typed properties also accept
|
|
197
|
+
# numbers and Symbol-typed properties also accept strings, since YAML (e.g. database.yml) parses
|
|
198
|
+
# unquoted values like `cluster_id: 1` as numbers and never produces symbols.
|
|
196
199
|
def validate_props!(config)
|
|
197
200
|
config.each do |key, value|
|
|
198
201
|
prop = PropertyDefinition::KNOWN_PROPERTIES[key]
|
|
@@ -200,23 +203,30 @@ module AwsAdvancedRubyDriverWrapper
|
|
|
200
203
|
next if value.nil?
|
|
201
204
|
next if prop.type.nil?
|
|
202
205
|
|
|
203
|
-
|
|
204
|
-
value.is_a?(TrueClass) || value.is_a?(FalseClass) ||
|
|
205
|
-
(value.is_a?(String) && BOOLEAN_STRINGS.include?(value.downcase))
|
|
206
|
-
elsif prop.type == Float
|
|
207
|
-
value.is_a?(Float) || value.is_a?(Integer) ||
|
|
208
|
-
(value.is_a?(String) && value.match?(/\A-?\d+(\.\d+)?\z/))
|
|
209
|
-
else
|
|
210
|
-
value.is_a?(prop.type) ||
|
|
211
|
-
(prop.type == Integer && value.is_a?(String) && value.match?(/\A-?\d+\z/))
|
|
212
|
-
end
|
|
213
|
-
|
|
214
|
-
raise TypeError, "#{key}: expected #{prop.type}, got #{value.class}" unless valid
|
|
206
|
+
raise TypeError, "#{key}: expected #{prop.type}, got #{value.class}" unless valid_type?(prop.type, value)
|
|
215
207
|
|
|
216
208
|
prop.validate!(value)
|
|
217
209
|
end
|
|
218
210
|
end
|
|
219
211
|
|
|
212
|
+
def valid_type?(type, value)
|
|
213
|
+
if type == :boolean
|
|
214
|
+
value.is_a?(TrueClass) || value.is_a?(FalseClass) ||
|
|
215
|
+
(value.is_a?(String) && BOOLEAN_STRINGS.include?(value.downcase))
|
|
216
|
+
elsif type == Float
|
|
217
|
+
value.is_a?(Float) || value.is_a?(Integer) ||
|
|
218
|
+
(value.is_a?(String) && value.match?(/\A-?\d+(\.\d+)?\z/))
|
|
219
|
+
elsif type == Integer
|
|
220
|
+
value.is_a?(Integer) || (value.is_a?(String) && value.match?(/\A-?\d+\z/))
|
|
221
|
+
elsif type == String
|
|
222
|
+
value.is_a?(String) || value.is_a?(Numeric)
|
|
223
|
+
elsif type == Symbol
|
|
224
|
+
value.is_a?(Symbol) || value.is_a?(String)
|
|
225
|
+
else
|
|
226
|
+
value.is_a?(type)
|
|
227
|
+
end
|
|
228
|
+
end
|
|
229
|
+
|
|
220
230
|
# Forms a HostInfo object from a URI host section string.
|
|
221
231
|
# Each host entry may or may not have a port; missing ports are represented as -1.
|
|
222
232
|
# The resulting port is a comma-delimited string of per-host ports.
|
|
@@ -35,9 +35,9 @@ module AwsAdvancedRubyDriverWrapper
|
|
|
35
35
|
def query_global_topology(conn, initial_host_info, instance_templates_by_region)
|
|
36
36
|
results = @dialect.execute(conn, @dialect.topology_query)
|
|
37
37
|
|
|
38
|
-
if results
|
|
38
|
+
if topology_result_empty?(results)
|
|
39
39
|
# We expect at least 4 columns. Note that the server may return 0 columns if failover has occurred.
|
|
40
|
-
logger.debug('The topology query returned a result with
|
|
40
|
+
logger.debug('The topology query returned a result with no rows or rows with no columns. ' \
|
|
41
41
|
'This may occur if the topology query is executed when the server is failing over.')
|
|
42
42
|
return nil
|
|
43
43
|
end
|
|
@@ -19,10 +19,14 @@ require_relative 'rds_utils'
|
|
|
19
19
|
require_relative 'rds_url_type'
|
|
20
20
|
require_relative '../property_definition'
|
|
21
21
|
require_relative '../logging'
|
|
22
|
+
require_relative 'telemetry/null_telemetry'
|
|
23
|
+
require_relative 'telemetry/telemetry_const'
|
|
22
24
|
|
|
23
25
|
module AwsAdvancedRubyDriverWrapper
|
|
24
26
|
module Utils
|
|
25
27
|
module IamAuthUtils
|
|
28
|
+
TELEMETRY_FETCH_TOKEN = 'fetch authentication token'
|
|
29
|
+
|
|
26
30
|
module_function
|
|
27
31
|
|
|
28
32
|
TokenEntry = Data.define(:token, :expires_at) do
|
|
@@ -83,10 +87,15 @@ module AwsAdvancedRubyDriverWrapper
|
|
|
83
87
|
arn&.match(/\Aarn:aws(?:-[a-z]+)*:rds:(?<region>[^:]+)/)&.[](:region)
|
|
84
88
|
end
|
|
85
89
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
+
# @param telemetry_factory [Telemetry::TelemetryFactory, Telemetry::NullTelemetryFactory] records
|
|
91
|
+
# the token generation as a span
|
|
92
|
+
def generate_token(region:, hostname:, port:, user:, credentials_provider:,
|
|
93
|
+
telemetry_factory: Telemetry::NullTelemetryFactory::INSTANCE)
|
|
94
|
+
telemetry_factory.in_context(TELEMETRY_FETCH_TOKEN, Telemetry::TraceLevel::NESTED, record_failure: true) do
|
|
95
|
+
Aws::RDS::AuthTokenGenerator
|
|
96
|
+
.new(credentials: credentials_provider)
|
|
97
|
+
.auth_token(region:, endpoint: "#{hostname}:#{port}", user_name: user)
|
|
98
|
+
end
|
|
90
99
|
end
|
|
91
100
|
|
|
92
101
|
def resolve_host(iam_host, host_info)
|
|
@@ -14,6 +14,8 @@
|
|
|
14
14
|
# See the License for the specific language governing permissions and
|
|
15
15
|
# limitations under the License.
|
|
16
16
|
|
|
17
|
+
require_relative 'mysql_text'
|
|
18
|
+
|
|
17
19
|
module AwsAdvancedRubyDriverWrapper
|
|
18
20
|
module Utils
|
|
19
21
|
module Parser
|
|
@@ -21,21 +23,23 @@ module AwsAdvancedRubyDriverWrapper
|
|
|
21
23
|
# Both placeholder styles are accepted, so that the same annotation works for mysql2's
|
|
22
24
|
# positional +?+ and pg's numbered +$1+.
|
|
23
25
|
ANNOTATION_PATTERN = %r{/\*@encrypt:([\w.]+)\*/\s*(?:\?|\$(\d+))}
|
|
24
|
-
|
|
25
|
-
#
|
|
26
|
-
|
|
27
|
-
QUOTES = ["'", '"', '`'].freeze
|
|
26
|
+
# Whitespace after an annotation is taken with it, but not a line break, which may be what ends
|
|
27
|
+
# a line comment the annotation sits in.
|
|
28
|
+
STRIP_PATTERN = %r{/\*@encrypt:[\w.]+\*/[^\S\n]*}
|
|
28
29
|
|
|
29
30
|
module_function
|
|
30
31
|
|
|
31
32
|
# Returns a 1-based map of parameter index => "table.column" for each
|
|
32
33
|
# /*@encrypt:table.column*/ ? or /*@encrypt:table.column*/ $n placeholder.
|
|
33
34
|
# @param sql [String]
|
|
35
|
+
# @param server_version [Integer, nil] the MySQL server's version, which decides whether a
|
|
36
|
+
# versioned +/*!+ comment is part of the statement (see MysqlText.each_segment)
|
|
34
37
|
# @return [Hash{Integer => String}]
|
|
35
|
-
def parse_annotations(sql)
|
|
36
|
-
|
|
38
|
+
def parse_annotations(sql, server_version: nil)
|
|
39
|
+
# Most statements carry no annotation, so they are not scanned for placeholders at all.
|
|
40
|
+
return {} unless annotations?(sql)
|
|
37
41
|
|
|
38
|
-
question_marks =
|
|
42
|
+
question_marks = placeholder_positions(sql, server_version)
|
|
39
43
|
|
|
40
44
|
sql.to_enum(:scan, ANNOTATION_PATTERN)
|
|
41
45
|
.each_with_object({}) do |_, result|
|
|
@@ -66,31 +70,11 @@ module AwsAdvancedRubyDriverWrapper
|
|
|
66
70
|
end
|
|
67
71
|
|
|
68
72
|
# The character positions of every +?+ that is a bind placeholder, skipping any inside a quoted
|
|
69
|
-
# literal
|
|
70
|
-
# points at.
|
|
71
|
-
|
|
72
|
-
def unquoted_question_mark_positions(sql)
|
|
73
|
+
# literal or a comment so that it does not shift the count of the placeholder an annotation
|
|
74
|
+
# points at. Only mysql2 binds with +?+, so the text is read by MySQL's rules.
|
|
75
|
+
def placeholder_positions(sql, server_version)
|
|
73
76
|
positions = []
|
|
74
|
-
|
|
75
|
-
index = 0
|
|
76
|
-
|
|
77
|
-
while index < sql.length
|
|
78
|
-
char = sql[index]
|
|
79
|
-
if quote
|
|
80
|
-
if char == '\\' && quote != '`'
|
|
81
|
-
index += 2
|
|
82
|
-
next
|
|
83
|
-
end
|
|
84
|
-
|
|
85
|
-
quote = nil if char == quote
|
|
86
|
-
elsif QUOTES.include?(char)
|
|
87
|
-
quote = char
|
|
88
|
-
elsif char == '?'
|
|
89
|
-
positions << index
|
|
90
|
-
end
|
|
91
|
-
index += 1
|
|
92
|
-
end
|
|
93
|
-
|
|
77
|
+
MysqlText.each_code_char(sql, server_version: server_version) { |char, index| positions << index if char == '?' }
|
|
94
78
|
positions
|
|
95
79
|
end
|
|
96
80
|
end
|