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.
Files changed (150) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +23 -0
  3. data/LICENSE +175 -0
  4. data/NOTICE +1 -0
  5. data/README.md +168 -0
  6. data/THIRD-PARTY-LICENSES +473 -0
  7. data/aws_advanced_ruby_driver_wrapper.gemspec +73 -0
  8. data/lib/aws_advanced_ruby_driver_wrapper/active_record/aws_mysql2_adapter.rb +73 -0
  9. data/lib/aws_advanced_ruby_driver_wrapper/active_record/aws_postgresql_adapter.rb +95 -0
  10. data/lib/aws_advanced_ruby_driver_wrapper/custom_configuration.rb +58 -0
  11. data/lib/aws_advanced_ruby_driver_wrapper/db_dialects/aurora_mysql_dialect.rb +103 -0
  12. data/lib/aws_advanced_ruby_driver_wrapper/db_dialects/aurora_pg_dialect.rb +124 -0
  13. data/lib/aws_advanced_ruby_driver_wrapper/db_dialects/dialect_codes.rb +38 -0
  14. data/lib/aws_advanced_ruby_driver_wrapper/db_dialects/global_mysql_dialect.rb +91 -0
  15. data/lib/aws_advanced_ruby_driver_wrapper/db_dialects/global_pg_dialect.rb +92 -0
  16. data/lib/aws_advanced_ruby_driver_wrapper/db_dialects/multi_az_cluster_mysql_dialect.rb +95 -0
  17. data/lib/aws_advanced_ruby_driver_wrapper/db_dialects/multi_az_cluster_pg_dialect.rb +86 -0
  18. data/lib/aws_advanced_ruby_driver_wrapper/db_dialects/mysql_dialect.rb +98 -0
  19. data/lib/aws_advanced_ruby_driver_wrapper/db_dialects/pg_dialect.rb +95 -0
  20. data/lib/aws_advanced_ruby_driver_wrapper/db_dialects/rds_mysql_dialect.rb +88 -0
  21. data/lib/aws_advanced_ruby_driver_wrapper/db_dialects/rds_pg_dialect.rb +86 -0
  22. data/lib/aws_advanced_ruby_driver_wrapper/db_dialects/unknown_dialect.rb +72 -0
  23. data/lib/aws_advanced_ruby_driver_wrapper/db_dialects/utils/dialect_utils.rb +71 -0
  24. data/lib/aws_advanced_ruby_driver_wrapper/driver_dialects/driver_dialect.rb +154 -0
  25. data/lib/aws_advanced_ruby_driver_wrapper/driver_dialects/driver_dialect_manager.rb +55 -0
  26. data/lib/aws_advanced_ruby_driver_wrapper/driver_dialects/mysql_driver_dialect.rb +165 -0
  27. data/lib/aws_advanced_ruby_driver_wrapper/driver_dialects/pg_driver_dialect.rb +201 -0
  28. data/lib/aws_advanced_ruby_driver_wrapper/errors/error_handler.rb +62 -0
  29. data/lib/aws_advanced_ruby_driver_wrapper/errors/mysql_error_handler.rb +80 -0
  30. data/lib/aws_advanced_ruby_driver_wrapper/errors/pg_error_handler.rb +126 -0
  31. data/lib/aws_advanced_ruby_driver_wrapper/errors.rb +59 -0
  32. data/lib/aws_advanced_ruby_driver_wrapper/host/connection_string_host_list_provider.rb +95 -0
  33. data/lib/aws_advanced_ruby_driver_wrapper/host/global_aurora_host_list_provider.rb +65 -0
  34. data/lib/aws_advanced_ruby_driver_wrapper/host/host_availability.rb +24 -0
  35. data/lib/aws_advanced_ruby_driver_wrapper/host/host_availability_strategy.rb +27 -0
  36. data/lib/aws_advanced_ruby_driver_wrapper/host/host_info.rb +137 -0
  37. data/lib/aws_advanced_ruby_driver_wrapper/host/host_role.rb +25 -0
  38. data/lib/aws_advanced_ruby_driver_wrapper/host/random_host_selector.rb +40 -0
  39. data/lib/aws_advanced_ruby_driver_wrapper/host/rds_host_list_provider.rb +206 -0
  40. data/lib/aws_advanced_ruby_driver_wrapper/logging.rb +110 -0
  41. data/lib/aws_advanced_ruby_driver_wrapper/monitoring/cluster_topology_monitor.rb +709 -0
  42. data/lib/aws_advanced_ruby_driver_wrapper/monitoring/global_cluster_topology_monitor.rb +72 -0
  43. data/lib/aws_advanced_ruby_driver_wrapper/monitoring/monitor.rb +99 -0
  44. data/lib/aws_advanced_ruby_driver_wrapper/monitoring/monitor_connection.rb +57 -0
  45. data/lib/aws_advanced_ruby_driver_wrapper/monitoring/monitor_state.rb +25 -0
  46. data/lib/aws_advanced_ruby_driver_wrapper/mysql.rb +429 -0
  47. data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/blue_green_plugin.rb +205 -0
  48. data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/host_mapper.rb +132 -0
  49. data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/iam_host_tracker.rb +84 -0
  50. data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/interim_status.rb +92 -0
  51. data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/interval_rate.rb +27 -0
  52. data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/phase.rb +69 -0
  53. data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/phase_event_log.rb +85 -0
  54. data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/phase_time_info.rb +25 -0
  55. data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/role.rb +38 -0
  56. data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/routing/base_routing.rb +83 -0
  57. data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/routing/reject_connect_routing.rb +40 -0
  58. data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/routing/substitute_connect_routing.rb +136 -0
  59. data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/routing/suspend_connect_routing.rb +53 -0
  60. data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/routing/suspend_execute_routing.rb +52 -0
  61. data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/routing/suspend_until_corresponding_host_found_connect_routing.rb +83 -0
  62. data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/status.rb +68 -0
  63. data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/status_builder.rb +244 -0
  64. data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/status_info.rb +30 -0
  65. data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/status_monitor.rb +564 -0
  66. data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/status_provider.rb +414 -0
  67. data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/switchover_state.rb +98 -0
  68. data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/switchover_timer.rb +46 -0
  69. data/lib/aws_advanced_ruby_driver_wrapper/plugins/custom_endpoint/custom_endpoint_monitor.rb +266 -0
  70. data/lib/aws_advanced_ruby_driver_wrapper/plugins/custom_endpoint/custom_endpoint_plugin.rb +158 -0
  71. data/lib/aws_advanced_ruby_driver_wrapper/plugins/custom_endpoint/info.rb +111 -0
  72. data/lib/aws_advanced_ruby_driver_wrapper/plugins/custom_endpoint/member_list_type.rb +31 -0
  73. data/lib/aws_advanced_ruby_driver_wrapper/plugins/custom_endpoint/role.rb +45 -0
  74. data/lib/aws_advanced_ruby_driver_wrapper/plugins/default_plugin.rb +108 -0
  75. data/lib/aws_advanced_ruby_driver_wrapper/plugins/failover_mode.rb +43 -0
  76. data/lib/aws_advanced_ruby_driver_wrapper/plugins/failover_plugin.rb +467 -0
  77. data/lib/aws_advanced_ruby_driver_wrapper/plugins/gdb/gdb_failover_mode.rb +68 -0
  78. data/lib/aws_advanced_ruby_driver_wrapper/plugins/gdb/gdb_failover_plugin.rb +403 -0
  79. data/lib/aws_advanced_ruby_driver_wrapper/plugins/iam_auth_plugin.rb +159 -0
  80. data/lib/aws_advanced_ruby_driver_wrapper/plugins/initial_connection_strategy_plugin.rb +485 -0
  81. data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/audit_logger.rb +157 -0
  82. data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/column_cipher.rb +159 -0
  83. data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/column_encryption_config.rb +61 -0
  84. data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/connection_source.rb +91 -0
  85. data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/data_key_cache.rb +220 -0
  86. data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/encryption_algorithm.rb +75 -0
  87. data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/encryption_config.rb +146 -0
  88. data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/encryption_service.rb +391 -0
  89. data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/error_context.rb +198 -0
  90. data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/errors.rb +259 -0
  91. data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/key_management_utility.rb +435 -0
  92. data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/key_manager.rb +378 -0
  93. data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/key_metadata.rb +86 -0
  94. data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/kms_encryption_plugin.rb +890 -0
  95. data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/kms_encryption_utility.rb +281 -0
  96. data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/metadata_manager.rb +332 -0
  97. data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/sanitizer.rb +147 -0
  98. data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/schema_name.rb +70 -0
  99. data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/schema_validator.rb +211 -0
  100. data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/sql_runner.rb +147 -0
  101. data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/type_marker.rb +109 -0
  102. data/lib/aws_advanced_ruby_driver_wrapper/plugins/secrets_manager_plugin.rb +358 -0
  103. data/lib/aws_advanced_ruby_driver_wrapper/postgresql.rb +659 -0
  104. data/lib/aws_advanced_ruby_driver_wrapper/property_definition.rb +409 -0
  105. data/lib/aws_advanced_ruby_driver_wrapper/ruby_method.rb +122 -0
  106. data/lib/aws_advanced_ruby_driver_wrapper/services/connection_service.rb +143 -0
  107. data/lib/aws_advanced_ruby_driver_wrapper/services/dialect_service.rb +267 -0
  108. data/lib/aws_advanced_ruby_driver_wrapper/services/host_service.rb +199 -0
  109. data/lib/aws_advanced_ruby_driver_wrapper/services/monitor_service.rb +186 -0
  110. data/lib/aws_advanced_ruby_driver_wrapper/services/plugin_call_context.rb +63 -0
  111. data/lib/aws_advanced_ruby_driver_wrapper/services/plugin_manager.rb +273 -0
  112. data/lib/aws_advanced_ruby_driver_wrapper/services/service_container.rb +30 -0
  113. data/lib/aws_advanced_ruby_driver_wrapper/services/service_utility.rb +78 -0
  114. data/lib/aws_advanced_ruby_driver_wrapper/services/session_state_service.rb +56 -0
  115. data/lib/aws_advanced_ruby_driver_wrapper/utils/accessible_regions.rb +52 -0
  116. data/lib/aws_advanced_ruby_driver_wrapper/utils/ar_constants.rb +25 -0
  117. data/lib/aws_advanced_ruby_driver_wrapper/utils/aurora_topology_utils.rb +99 -0
  118. data/lib/aws_advanced_ruby_driver_wrapper/utils/aws_credentials_utils.rb +62 -0
  119. data/lib/aws_advanced_ruby_driver_wrapper/utils/connection_config.rb +91 -0
  120. data/lib/aws_advanced_ruby_driver_wrapper/utils/connection_config_parser.rb +368 -0
  121. data/lib/aws_advanced_ruby_driver_wrapper/utils/conversion_utils.rb +51 -0
  122. data/lib/aws_advanced_ruby_driver_wrapper/utils/events/batching_event_publisher.rb +119 -0
  123. data/lib/aws_advanced_ruby_driver_wrapper/utils/events/data_access_event.rb +26 -0
  124. data/lib/aws_advanced_ruby_driver_wrapper/utils/events/monitor_reset_event.rb +26 -0
  125. data/lib/aws_advanced_ruby_driver_wrapper/utils/global_aurora_topology_utils.rb +185 -0
  126. data/lib/aws_advanced_ruby_driver_wrapper/utils/host_list_utils.rb +27 -0
  127. data/lib/aws_advanced_ruby_driver_wrapper/utils/iam_auth_utils.rb +112 -0
  128. data/lib/aws_advanced_ruby_driver_wrapper/utils/multi_az_topology_utils.rb +117 -0
  129. data/lib/aws_advanced_ruby_driver_wrapper/utils/parser/encryption_annotation_parser.rb +99 -0
  130. data/lib/aws_advanced_ruby_driver_wrapper/utils/parser/mysql_statement_analyzer.rb +641 -0
  131. data/lib/aws_advanced_ruby_driver_wrapper/utils/parser/pg_statement_analyzer.rb +502 -0
  132. data/lib/aws_advanced_ruby_driver_wrapper/utils/parser/query_analysis.rb +63 -0
  133. data/lib/aws_advanced_ruby_driver_wrapper/utils/parser/query_type.rb +35 -0
  134. data/lib/aws_advanced_ruby_driver_wrapper/utils/parser/routing_hint.rb +27 -0
  135. data/lib/aws_advanced_ruby_driver_wrapper/utils/parser/routing_hint_parser.rb +50 -0
  136. data/lib/aws_advanced_ruby_driver_wrapper/utils/parser/sql_parser.rb +139 -0
  137. data/lib/aws_advanced_ruby_driver_wrapper/utils/rds_url_type.rb +71 -0
  138. data/lib/aws_advanced_ruby_driver_wrapper/utils/rds_utils.rb +575 -0
  139. data/lib/aws_advanced_ruby_driver_wrapper/utils/retry_util.rb +153 -0
  140. data/lib/aws_advanced_ruby_driver_wrapper/utils/sql_encoding.rb +56 -0
  141. data/lib/aws_advanced_ruby_driver_wrapper/utils/sql_method_analyzer.rb +195 -0
  142. data/lib/aws_advanced_ruby_driver_wrapper/utils/storage/cache_entry.rb +56 -0
  143. data/lib/aws_advanced_ruby_driver_wrapper/utils/storage/expiration_cache.rb +108 -0
  144. data/lib/aws_advanced_ruby_driver_wrapper/utils/storage/sliding_expiration_cache.rb +137 -0
  145. data/lib/aws_advanced_ruby_driver_wrapper/utils/storage/storage_service.rb +172 -0
  146. data/lib/aws_advanced_ruby_driver_wrapper/utils/topology_utils.rb +127 -0
  147. data/lib/aws_advanced_ruby_driver_wrapper/version.rb +19 -0
  148. data/lib/aws_advanced_ruby_driver_wrapper/wrapper_property.rb +64 -0
  149. data/lib/aws_advanced_ruby_driver_wrapper.rb +116 -0
  150. metadata +227 -0
@@ -0,0 +1,63 @@
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 Services
19
+ # What a plugin can learn about, and change about, the call it is currently handling.
20
+ #
21
+ # A plugin's +execute+ already receives the arguments and the block of the call, but only as
22
+ # copies: the pipeline reads what it hands to the next plugin from here, so a plugin that needs
23
+ # to change them has to replace {#args} or {#block} rather than modify what it was given.
24
+ # Likewise {#sql} is not always among the arguments, since a result method has no SQL of its
25
+ # own and a prepared statement only carries the name it was prepared under.
26
+ #
27
+ # This context belongs to a single call on a single thread, and is reached through
28
+ # {PluginManager#current_call_context}.
29
+ class PluginCallContext
30
+ # @return [String, nil] the SQL the call originated from, nil when the caller could not say
31
+ attr_reader :sql
32
+
33
+ # @return [Array] the arguments the target driver method will be called with
34
+ attr_accessor :args
35
+
36
+ # @return [Proc, nil] the block the target driver method will be called with
37
+ attr_accessor :block
38
+
39
+ # @param sql [String, nil]
40
+ # @param args [Array]
41
+ # @param block [Proc, nil]
42
+ # @param field_names [Array<String>, Proc, nil] the result's column names in order, or a
43
+ # callable that produces them; see {#field_names}
44
+ def initialize(sql, args, block = nil, field_names = nil)
45
+ @sql = sql
46
+ @args = args
47
+ @block = block
48
+ @field_names = field_names
49
+ end
50
+
51
+ # The result's column names, in order, for a plugin that reads rows as bare arrays of values
52
+ # and has to match each position back to a column. Only result methods supply it, and only
53
+ # when a plugin might need it, so it is resolved lazily and remembered: a call that never asks
54
+ # pays nothing, and one that asks more than once pays once.
55
+ #
56
+ # @return [Array<String>, nil] nil when the call carries no column names
57
+ def field_names
58
+ @field_names = @field_names.call if @field_names.respond_to?(:call)
59
+ @field_names
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,273 @@
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 'logger'
18
+ require_relative '../errors'
19
+ require_relative '../property_definition'
20
+ require_relative '../ruby_method'
21
+ require_relative '../utils/sql_encoding'
22
+ require_relative '../plugins/default_plugin'
23
+ require_relative '../plugins/failover_plugin'
24
+ require_relative '../plugins/gdb/gdb_failover_plugin'
25
+ require_relative '../plugins/iam_auth_plugin'
26
+ require_relative '../plugins/initial_connection_strategy_plugin'
27
+ require_relative '../plugins/kms_encryption/kms_encryption_plugin'
28
+ require_relative '../plugins/secrets_manager_plugin'
29
+ require_relative '../plugins/blue_green/blue_green_plugin'
30
+ require_relative '../plugins/custom_endpoint/custom_endpoint_plugin'
31
+ require_relative 'plugin_call_context'
32
+
33
+ module AwsAdvancedRubyDriverWrapper
34
+ module Services
35
+ class PluginManager
36
+ WEIGHT_RELATIVE_TO_PRIOR_PLUGIN = -1
37
+ NOOP_CALLABLE = -> {}.freeze
38
+ CURRENT_CALL_CONTEXT_KEY = :aws_ruby_wrapper_plugin_call_context
39
+ private_constant :WEIGHT_RELATIVE_TO_PRIOR_PLUGIN, :NOOP_CALLABLE, :CURRENT_CALL_CONTEXT_KEY
40
+
41
+ @plugin_classes = {
42
+ 'bg' => Plugins::BlueGreen::BlueGreenPlugin,
43
+ 'custom_endpoint' => Plugins::CustomEndpoint::CustomEndpointPlugin,
44
+ 'failover' => Plugins::FailoverPlugin,
45
+ 'gdb_failover' => Plugins::Gdb::GdbFailoverPlugin,
46
+ 'iam' => Plugins::IamAuthPlugin,
47
+ 'initial_connection' => Plugins::InitialConnectionStrategyPlugin,
48
+ 'kms_encryption' => Plugins::KmsEncryptionPlugin,
49
+ 'secrets_manager' => Plugins::SecretsManagerPlugin
50
+ }
51
+
52
+ # The final list of plugins will be sorted by weight, starting from the lowest values up to
53
+ # the highest values. The first plugin of the list will have the lowest weight, and the
54
+ # last one will have the highest weight.
55
+ @plugin_weights = {
56
+ Plugins::BlueGreen::BlueGreenPlugin => 200,
57
+ Plugins::CustomEndpoint::CustomEndpointPlugin => 250,
58
+ Plugins::InitialConnectionStrategyPlugin => 300,
59
+ Plugins::FailoverPlugin => 400,
60
+ Plugins::Gdb::GdbFailoverPlugin => 500,
61
+ Plugins::IamAuthPlugin => 1800,
62
+ Plugins::SecretsManagerPlugin => 1900,
63
+ Plugins::KmsEncryptionPlugin => 2050
64
+ }
65
+
66
+ class << self
67
+ def register_plugin(plugin_code, plugin_class, weight: WEIGHT_RELATIVE_TO_PRIOR_PLUGIN)
68
+ @plugin_classes[plugin_code] = plugin_class
69
+ @plugin_weights[plugin_class] = weight
70
+ end
71
+
72
+ attr_reader :plugin_classes, :plugin_weights
73
+ end
74
+
75
+ def initialize(service_container)
76
+ @plugins = load_plugins(service_container)
77
+ @pipeline_cache = {}
78
+ end
79
+
80
+ def connect(host_info, driver_props, is_initial_connection, plugin_to_skip: nil)
81
+ execute_with_subscribed_plugins(
82
+ 'connect',
83
+ lambda do |plugin, next_plugin_callable|
84
+ plugin.connect(host_info, driver_props, is_initial_connection, next_plugin_callable)
85
+ end,
86
+ NOOP_CALLABLE,
87
+ plugin_to_skip:
88
+ )
89
+ end
90
+
91
+ def internal_connect(host_info, driver_props, wrapper_props, is_initial_connection, plugin_to_skip: nil)
92
+ execute_with_subscribed_plugins(
93
+ 'internal_connect',
94
+ lambda do |plugin, next_plugin_callable|
95
+ plugin.internal_connect(host_info, driver_props, wrapper_props, is_initial_connection, next_plugin_callable)
96
+ end,
97
+ NOOP_CALLABLE,
98
+ plugin_to_skip:
99
+ )
100
+ end
101
+
102
+ # The context of the call currently being executed, for plugins that need to know more about
103
+ # it than their own arguments say, or that need to change the arguments the target driver
104
+ # method is called with. See {PluginCallContext}.
105
+ #
106
+ # The context belongs to the calling thread and is restored when the call returns, so nested
107
+ # calls cannot see one another's.
108
+ #
109
+ # @return [PluginCallContext, nil] nil outside of a call
110
+ def current_call_context
111
+ Thread.current[CURRENT_CALL_CONTEXT_KEY]
112
+ end
113
+
114
+ # @return [String, nil] the SQL the call currently being executed originated from
115
+ def current_sql
116
+ current_call_context&.sql
117
+ end
118
+
119
+ # @param sql [String, nil] the SQL the call originated from, for plugins that inspect
120
+ # statements; it is published as valid UTF-8 (see {Utils::SqlEncoding.inspectable}), and is
121
+ # consumed here and never forwarded to the target driver method. Since invalid bytes in the
122
+ # published copy are replaced, a plugin that rewrites the SQL builds the new statement from
123
+ # the call's arguments rather than from this copy
124
+ # @param field_names [Array<String>, Proc, nil] the result's column names in order, for a
125
+ # plugin that reads rows as arrays; like +sql+, it is consumed here rather than forwarded
126
+ def execute(ruby_method, current_conn, target_callable, *args, bounded_conn: nil, sql: nil, field_names: nil, **kwargs, &block)
127
+ if ruby_method.is_a?(MethodInfo)
128
+ method_name = ruby_method.name
129
+
130
+ if ruby_method.check_bounded_connection && !bounded_conn.nil? && !current_conn.nil? && (bounded_conn != current_conn)
131
+ raise Errors::AwsError, "Method invoked against old connection: #{bounded_conn}"
132
+ end
133
+ else
134
+ # Fallback for dynamic method names (method_missing with string)
135
+ method_name = ruby_method.to_s
136
+ end
137
+
138
+ context = PluginCallContext.new(Utils::SqlEncoding.inspectable(sql), args, block, field_names)
139
+ previous_context = Thread.current[CURRENT_CALL_CONTEXT_KEY]
140
+ Thread.current[CURRENT_CALL_CONTEXT_KEY] = context
141
+
142
+ begin
143
+ execute_with_subscribed_plugins(
144
+ method_name,
145
+ lambda do |plugin, next_plugin_callable|
146
+ # Read from the context rather than from args and block, so that a plugin which
147
+ # replaced either is honoured by the plugins after it and by the target method.
148
+ plugin.execute(method_name, next_plugin_callable, *context.args, **kwargs, &context.block)
149
+ end,
150
+ target_callable
151
+ )
152
+ ensure
153
+ Thread.current[CURRENT_CALL_CONTEXT_KEY] = previous_context
154
+ end
155
+ end
156
+
157
+ def num_plugins
158
+ @plugins&.length || 0
159
+ end
160
+
161
+ def plugin_in_use?(plugin_class)
162
+ return false if @plugins.nil? || @plugins.empty?
163
+
164
+ @plugins.any?(plugin_class)
165
+ end
166
+
167
+ private
168
+
169
+ def load_plugins(service_container)
170
+ wrapper_props = service_container.connection_service.wrapper_props
171
+ plugin_codes = PropertyDefinition::PLUGINS.get(wrapper_props)
172
+ codes_list = plugin_codes.split(',').map(&:strip)
173
+ ensure_single_auth_plugin(codes_list)
174
+ ensure_single_failover_plugin(codes_list)
175
+ raise Errors::AwsError, 'Duplicate plugins detected' if codes_list.length != codes_list.uniq.length
176
+
177
+ plugin_classes = plugin_codes.empty? ? [] : get_plugin_classes(codes_list, wrapper_props)
178
+
179
+ plugins = plugin_classes.map do |plugin_class|
180
+ plugin_class.new(service_container, wrapper_props)
181
+ end
182
+
183
+ plugins << Plugins::DefaultPlugin.new(service_container, wrapper_props)
184
+ plugins
185
+ end
186
+
187
+ def ensure_single_auth_plugin(plugin_code_list)
188
+ auth_plugins_used = plugin_code_list & %w[iam secrets_manager].freeze
189
+
190
+ return unless auth_plugins_used.length > 1
191
+
192
+ raise Errors::PluginConflictError,
193
+ "Only one authentication plugin may be used at a time. Found: #{auth_plugins_used.join(', ')}"
194
+ end
195
+
196
+ def ensure_single_failover_plugin(plugin_code_list)
197
+ failover_plugins_used = plugin_code_list & %w[failover gdb_failover].freeze
198
+
199
+ return unless failover_plugins_used.length > 1
200
+
201
+ raise Errors::PluginConflictError,
202
+ "Only one failover plugin may be used at a time. Found: #{failover_plugins_used.join(', ')}"
203
+ end
204
+
205
+ def get_plugin_classes(plugin_code_list, _wrapper_props)
206
+ plugin_classes = plugin_code_list.map do |plugin_code|
207
+ plugin_class = self.class.plugin_classes[plugin_code]
208
+ raise Errors::AwsError, "Invalid plugin: #{plugin_code}" if plugin_class.nil?
209
+
210
+ plugin_class
211
+ end
212
+
213
+ return [] if plugin_classes.empty?
214
+
215
+ weights = plugin_weights_for(plugin_classes)
216
+ plugin_classes.sort_by! { |ft| weights[ft] }
217
+
218
+ plugin_classes
219
+ end
220
+
221
+ def plugin_weights_for(plugin_classes)
222
+ last_weight = 0
223
+ plugin_classes.each_with_object({}) do |plugin_class, weights|
224
+ weight = self.class.plugin_weights[plugin_class]
225
+
226
+ if weight.nil? || weight == WEIGHT_RELATIVE_TO_PRIOR_PLUGIN
227
+ last_weight += 1
228
+ weights[plugin_class] = last_weight
229
+ else
230
+ weights[plugin_class] = weight
231
+ last_weight = weight
232
+ end
233
+ end
234
+ end
235
+
236
+ def execute_with_subscribed_plugins(
237
+ target_method_name,
238
+ plugin_callable,
239
+ target_driver_callable,
240
+ plugin_to_skip: nil
241
+ )
242
+ pipeline_callable = @pipeline_cache[target_method_name] ||= make_pipeline(target_method_name)
243
+ pipeline_callable.call(plugin_callable, target_driver_callable, target_method_name, plugin_to_skip)
244
+ end
245
+
246
+ # Builds the plugin pipeline function chain. The pipeline allows plugins to perform logic both before and after
247
+ # the target driver function is called.
248
+ def make_pipeline(target_method_name)
249
+ subscribed_plugins = @plugins.select do |p|
250
+ p.subscribed_methods.include?('*') || p.subscribed_methods.include?(target_method_name)
251
+ end
252
+ raise Errors::AwsError, 'Plugin pipeline is nil' if subscribed_plugins.empty?
253
+
254
+ base_plugin = subscribed_plugins.last
255
+ base = lambda do |plugin_callable, target_driver_callable, *, **|
256
+ plugin_callable.call(base_plugin, target_driver_callable)
257
+ end
258
+
259
+ subscribed_plugins[0...-1].reverse.reduce(base) do |next_plugin_callable, plugin|
260
+ lambda do |plugin_callable, target_driver_callable, method_name, plugin_to_skip|
261
+ if plugin_to_skip == plugin
262
+ next_plugin_callable.call(plugin_callable, target_driver_callable, method_name, plugin_to_skip)
263
+ else
264
+ plugin_callable.call(plugin, lambda do
265
+ next_plugin_callable.call(plugin_callable, target_driver_callable, method_name, plugin_to_skip)
266
+ end)
267
+ end
268
+ end
269
+ end
270
+ end
271
+ end
272
+ end
273
+ end
@@ -0,0 +1,30 @@
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 Services
19
+ ServiceContainer = Struct.new(
20
+ :connection_service,
21
+ :dialect_service,
22
+ :event_publisher,
23
+ :host_service,
24
+ :plugin_manager,
25
+ :session_state_service,
26
+ :storage_service,
27
+ :monitor_service
28
+ )
29
+ end
30
+ end
@@ -0,0 +1,78 @@
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 'service_container'
18
+ require_relative 'connection_service'
19
+ require_relative 'dialect_service'
20
+ require_relative 'host_service'
21
+ require_relative 'plugin_manager'
22
+ require_relative 'monitor_service'
23
+ require_relative 'session_state_service'
24
+ require_relative '../utils/storage/storage_service'
25
+ require_relative '../utils/events/batching_event_publisher'
26
+
27
+ module AwsAdvancedRubyDriverWrapper
28
+ module Services
29
+ # Manages shared singleton services with correct dependency order.
30
+ # Initialized eagerly at require-time to avoid thread-safety races.
31
+ module CoreServices
32
+ @event_publisher = Utils::Events::BatchingEventPublisher.new
33
+ @storage_service = Utils::Storage::StorageService.new(event_publisher: @event_publisher)
34
+ @monitor_service = MonitorService.new(event_publisher: @event_publisher)
35
+
36
+ class << self
37
+ attr_reader :event_publisher, :storage_service, :monitor_service
38
+ end
39
+
40
+ # Resets all shared instances. For testing only.
41
+ # @api private
42
+ def self.reset!
43
+ @monitor_service.shutdown(grace_period: 2)
44
+ @storage_service.shutdown
45
+ @event_publisher.release_resources
46
+ @event_publisher = Utils::Events::BatchingEventPublisher.new
47
+ @storage_service = Utils::Storage::StorageService.new(event_publisher: @event_publisher)
48
+ @monitor_service = MonitorService.new(event_publisher: @event_publisher)
49
+ end
50
+ end
51
+
52
+ module ServiceUtility
53
+ def self.create_standard_container(config)
54
+ container = ServiceContainer.new
55
+ container.event_publisher = CoreServices.event_publisher
56
+ container.connection_service = ConnectionService.new(container, config)
57
+ container.dialect_service = DialectService.new(container.connection_service, config.driver_name)
58
+ container.host_service = HostService.new(container)
59
+ container.session_state_service = SessionStateService.new
60
+ container.storage_service = CoreServices.storage_service
61
+ container.monitor_service = CoreServices.monitor_service
62
+ # PluginManager must be assigned last: plugins are instantiated during its constructor
63
+ # and must not eagerly access container.plugin_manager (it will be nil at that point).
64
+ container.plugin_manager = PluginManager.new(container)
65
+ container.dialect_service.setup_initial_provider(container)
66
+ container
67
+ end
68
+
69
+ def self.create_monitor_container(parent_container)
70
+ container = ServiceContainer.new
71
+ container.event_publisher = parent_container.event_publisher
72
+ container.dialect_service = parent_container.dialect_service
73
+ container.host_service = parent_container.host_service
74
+ container
75
+ end
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,56 @@
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 '../utils/sql_method_analyzer'
18
+
19
+ module AwsAdvancedRubyDriverWrapper
20
+ module Services
21
+ class SessionStateService
22
+ attr_accessor :in_transaction, :autocommit
23
+ alias in_transaction? in_transaction
24
+ alias autocommit? autocommit
25
+
26
+ def initialize
27
+ reset
28
+ end
29
+
30
+ def reset
31
+ @in_transaction = false
32
+ @autocommit = true
33
+ end
34
+
35
+ def update_transaction_state(method_name, args, autocommit_before, dialect, connection, succeeded: true)
36
+ connection_in_transaction = dialect.reported_in_transaction(connection)
37
+ unless connection_in_transaction.nil?
38
+ self.in_transaction = connection_in_transaction
39
+ return
40
+ end
41
+ # When the statement failed, SQL inference can't tell what took effect, so keep the current state.
42
+ return unless succeeded
43
+
44
+ effect = Utils::SqlMethodAnalyzer.transaction_effect(
45
+ method_name, args, autocommit: autocommit?, autocommit_before: autocommit_before
46
+ )
47
+ if effect.opens_transaction
48
+ self.in_transaction = true
49
+ elsif effect.closes_transaction
50
+ self.in_transaction = false
51
+ end
52
+ self.autocommit = effect.autocommit_value unless effect.autocommit_value.nil?
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,52 @@
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 '../errors'
18
+ require_relative '../property_definition'
19
+ require_relative 'rds_utils'
20
+
21
+ module AwsAdvancedRubyDriverWrapper
22
+ module Utils
23
+ module AccessibleRegions
24
+ module_function
25
+
26
+ def parse(props)
27
+ value = PropertyDefinition::ACCESSIBLE_REGIONS.get(props)
28
+ return nil if value.nil? || value.strip.empty?
29
+
30
+ regions = value.split(',').map { |r| r.strip.downcase }.reject(&:empty?)
31
+
32
+ invalid = regions.reject { |r| RdsUtils.valid_region?(r) }
33
+ unless invalid.empty?
34
+ raise Errors::AwsError,
35
+ "#{PropertyDefinition::ACCESSIBLE_REGIONS.name} contains unknown or misspelled AWS " \
36
+ "region(s): #{invalid.join(', ')}"
37
+ end
38
+
39
+ regions.empty? ? nil : regions.to_set
40
+ end
41
+
42
+ def filter_by_region(hosts, accessible_regions)
43
+ return hosts if accessible_regions.nil?
44
+
45
+ hosts.select do |host|
46
+ region = RdsUtils.rds_region(host.host)
47
+ region && accessible_regions.include?(region.downcase)
48
+ end
49
+ end
50
+ end
51
+ end
52
+ 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 Utils
19
+ # ActiveRecord-only keys that should not be passed to the wrapper or native driver.
20
+ AR_COMMON_KEYS = %i[
21
+ adapter pool checkout_timeout idle_timeout reaping_frequency
22
+ variables prepared_statements encoding reconnect socket flags
23
+ ].freeze
24
+ end
25
+ 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 'topology_utils'
18
+ require_relative 'conversion_utils'
19
+
20
+ module AwsAdvancedRubyDriverWrapper
21
+ module Utils
22
+ # Topology utilities specific to Aurora database clusters.
23
+ # Processes topology query results that return instance ID, writer flag, CPU utilization, and instance lag columns.
24
+ class AuroraTopologyUtils
25
+ include TopologyUtils
26
+ include ConversionUtils
27
+
28
+ def initialize(dialect:)
29
+ raise ArgumentError, 'dialect cannot be nil' if dialect.nil?
30
+
31
+ @dialect = dialect
32
+ end
33
+
34
+ # Evaluate whether the given connection is to a writer instance.
35
+ #
36
+ # @param conn [Object] the connection to evaluate.
37
+ # @return [Boolean] true if the connection is to a writer instance, false otherwise.
38
+ def writer_instance?(conn)
39
+ results = @dialect.execute(conn, @dialect.writer_id_query)
40
+ return false if results.nil? || results.none?
41
+
42
+ row = results.first
43
+ !row.nil? && !row.values.first.nil? && !row.values.first.to_s.empty?
44
+ end
45
+
46
+ # Process Aurora topology query results into a list of HostInfo objects.
47
+ # Data in the result set is ordered by last update time, so the latest records are last.
48
+ # Newer records replace older ones if there are duplicate hosts.
49
+ #
50
+ # @param results [Object] the query result set (enumerable of row hashes).
51
+ # @param initial_host_info [AwsAdvancedRubyDriverWrapper::Host::HostInfo] the initial host info.
52
+ # @param instance_template [AwsAdvancedRubyDriverWrapper::Host::HostInfo] the template for building hosts.
53
+ # @return [Array<AwsAdvancedRubyDriverWrapper::Host::HostInfo>, nil] the parsed hosts or nil on failure.
54
+ def build_hosts(_conn, results, initial_host_info, instance_template)
55
+ hosts_map = {}
56
+
57
+ results.each do |row|
58
+ host = build_host_from_row(row, initial_host_info, instance_template)
59
+
60
+ # Ensure newer records replace the older ones if there are duplicate keys.
61
+ existing = hosts_map[host.host]
62
+ if existing.nil? || (existing.last_update_time && host.last_update_time &&
63
+ existing.last_update_time < host.last_update_time)
64
+ hosts_map[host.host] = host
65
+ end
66
+ rescue StandardError => e
67
+ logger.debug("Error processing topology query results: #{e.message}")
68
+ return nil
69
+ end
70
+
71
+ hosts_map.values
72
+ end
73
+
74
+ private
75
+
76
+ # Creates a HostInfo from a single Aurora topology query result row.
77
+ #
78
+ # According to the topology query, the result set should contain columns:
79
+ # host_id, is_writer, cpu_utilization, instance_lag, last_update_time.
80
+ #
81
+ # @param row [Hash] a single row from the topology query result.
82
+ # @param initial_host_info [AwsAdvancedRubyDriverWrapper::Host::HostInfo] the initial host info.
83
+ # @param instance_template [AwsAdvancedRubyDriverWrapper::Host::HostInfo] the template for building hosts.
84
+ # @return [AwsAdvancedRubyDriverWrapper::Host::HostInfo] the constructed host info.
85
+ def build_host_from_row(row, initial_host_info, instance_template)
86
+ instance_id = row_value(row, 'instance_id')
87
+ is_writer = to_boolean(row_value(row, 'is_writer'))
88
+ cpu_utilization = to_float(row_value(row, 'cpu_utilization'))
89
+ instance_lag = to_float(row_value(row, 'instance_lag'))
90
+ last_update_time = to_time(row_value(row, 'last_update_time'))
91
+
92
+ # Calculate weight based on instance lag and CPU utilization.
93
+ weight = (instance_lag.round * 100) + cpu_utilization.round
94
+
95
+ build_host(instance_id, instance_id, is_writer, weight, last_update_time, initial_host_info, instance_template)
96
+ end
97
+ end
98
+ end
99
+ end