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,62 @@
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 'digest'
18
+
19
+ module AwsAdvancedRubyDriverWrapper
20
+ module Utils
21
+ module AwsCredentialsUtils
22
+ module_function
23
+
24
+ # Stands in for the identity when a provider resolves to no credentials.
25
+ NO_CREDENTIALS = 'none'
26
+ IDENTITY_LENGTH = 16
27
+
28
+ # The credentials a provider resolves to right now, copied into a fixed Aws::Credentials, or
29
+ # nil when it resolves to none. Callers that cache what they obtain use one snapshot both to
30
+ # build the cache key ({identity}) and to make the request, so the key always describes the
31
+ # credentials the cached value was obtained with, even if the provider refreshes in between.
32
+ # Requires the AWS SDK to be loaded.
33
+ #
34
+ # @param credentials_provider [#credentials, Aws::Credentials, nil]
35
+ # @return [Aws::Credentials, nil]
36
+ def snapshot(credentials_provider)
37
+ credentials = credentials_provider.respond_to?(:credentials) ? credentials_provider.credentials : credentials_provider
38
+ access_key_id = credentials.respond_to?(:access_key_id) ? credentials.access_key_id : nil
39
+ return nil if access_key_id.nil? || access_key_id.empty?
40
+
41
+ session_token = credentials.respond_to?(:session_token) ? credentials.session_token : nil
42
+ Aws::Credentials.new(access_key_id, credentials.secret_access_key, session_token)
43
+ end
44
+
45
+ # A short, stable identifier for the AWS credentials a provider currently resolves to, for
46
+ # use in cache keys: two providers that resolve to the same access key share it, and a
47
+ # provider that refreshes to a new access key (a new assumed-role session, for example) gets
48
+ # a new one. It is derived from the access key id alone, which is hashed so that not even
49
+ # that appears in a cache key. The secret key is never read.
50
+ #
51
+ # @param credentials_provider [#credentials, Aws::Credentials, nil]
52
+ # @return [String]
53
+ def identity(credentials_provider)
54
+ credentials = credentials_provider.respond_to?(:credentials) ? credentials_provider.credentials : credentials_provider
55
+ access_key_id = credentials.respond_to?(:access_key_id) ? credentials.access_key_id : nil
56
+ return NO_CREDENTIALS if access_key_id.nil? || access_key_id.empty?
57
+
58
+ Digest::SHA256.hexdigest(access_key_id)[0, IDENTITY_LENGTH]
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,91 @@
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 'concurrent'
18
+ require_relative '../property_definition'
19
+ require_relative '../logging'
20
+
21
+ module AwsAdvancedRubyDriverWrapper
22
+ module Utils
23
+ class ConnectionConfig
24
+ attr_accessor :wrapper_props, :driver_props, :prefixed_wrapper_config, :prefixed_driver_config, :initial_host_info, :driver_name
25
+ attr_reader :original_host, :original_port
26
+
27
+ def initialize(wrapper_props: ::Concurrent::Map.new,
28
+ driver_props: ::Concurrent::Map.new,
29
+ prefixed_wrapper_config: ::Concurrent::Map.new,
30
+ prefixed_driver_config: ::Concurrent::Map.new,
31
+ initial_host_info: nil,
32
+ driver_name: nil,
33
+ original_host: nil,
34
+ original_port: nil,
35
+ multi_host_url: false)
36
+ @wrapper_props = wrapper_props
37
+ @driver_props = driver_props
38
+ @prefixed_wrapper_config = prefixed_wrapper_config
39
+ @prefixed_driver_config = prefixed_driver_config
40
+ @initial_host_info = initial_host_info
41
+ @driver_name = driver_name
42
+ @original_host = original_host
43
+ @original_port = original_port
44
+ @multi_host_url = multi_host_url
45
+ end
46
+
47
+ def multi_host_url?
48
+ @multi_host_url
49
+ end
50
+
51
+ def cluster_id
52
+ PropertyDefinition::CLUSTER_ID.get(wrapper_props)
53
+ end
54
+
55
+ # Redact sensitive property values (passwords, IAM tokens, Secrets Manager
56
+ # credentials) so they are never exposed if a config is logged, interpolated,
57
+ # or rendered in a backtrace. The property maps are the only fields that carry
58
+ # secrets; the rest are host/driver metadata that is safe to show.
59
+ #
60
+ # Most secrets are recognized by name. The IAM token is passed under a property
61
+ # whose name is configurable, so whatever name is configured is redacted too.
62
+ def inspect
63
+ "#<#{self.class.name} " \
64
+ "wrapper_props=#{masked(wrapper_props)}, " \
65
+ "driver_props=#{masked(driver_props)}, " \
66
+ "prefixed_wrapper_config=#{masked(prefixed_wrapper_config)}, " \
67
+ "prefixed_driver_config=#{masked(prefixed_driver_config)}, " \
68
+ "initial_host_info=#{initial_host_info.inspect}, driver_name=#{driver_name.inspect}, " \
69
+ "original_host=#{original_host.inspect}, original_port=#{original_port.inspect}, " \
70
+ "multi_host_url=#{@multi_host_url.inspect}>"
71
+ end
72
+ alias to_s inspect
73
+
74
+ # `pp` / PrettyPrint does not call #inspect; route them through the redacted
75
+ # representation so `pp config` cannot leak credentials.
76
+ def pretty_print(pp)
77
+ pp.text(inspect)
78
+ end
79
+
80
+ private
81
+
82
+ def masked(props)
83
+ AwsAdvancedRubyDriverWrapper.mask_properties(props, [iam_token_property_name])
84
+ end
85
+
86
+ def iam_token_property_name
87
+ wrapper_props && PropertyDefinition::IAM_ACCESS_TOKEN_PROPERTY_NAME.get(wrapper_props)
88
+ end
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,368 @@
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 'uri'
18
+ require 'concurrent'
19
+ require_relative 'connection_config'
20
+ require_relative '../property_definition'
21
+ require_relative '../host/host_availability'
22
+ require_relative '../host/host_availability_strategy'
23
+ require_relative '../host/host_info'
24
+ require_relative '../host/host_role'
25
+
26
+ module AwsAdvancedRubyDriverWrapper
27
+ module Utils
28
+ module ConnectionConfigParser
29
+ CONNINFO_PATTERN = /(\w+)=(?:'([^']*)'|([^\s]++))/
30
+ BOOLEAN_STRINGS = %w[true false].freeze
31
+
32
+ module_function
33
+
34
+ # Main entry point. The driver_name is provided by the wrapper class
35
+ # (e.g. :postgresql from WrapperPgConnection, :mysql2 from WrapperMysql2Client).
36
+ #
37
+ # @param driver_name [Symbol] :postgresql or :mysql2
38
+ def parse(driver_name, *args, **kwargs)
39
+ if driver_name == :mysql2 && !args.empty? && !args.first.is_a?(Hash)
40
+ raise ArgumentError,
41
+ "WrapperMysql2Client only accepts keyword arguments (e.g. host: 'x', port: 3306). " \
42
+ 'URI strings and positional arguments are not supported.'
43
+ end
44
+
45
+ if args.length == 1 && args.first.is_a?(String)
46
+ str = args.first
47
+ return parse_uri(driver_name, str, **kwargs) if str.include?('://')
48
+
49
+ return parse_conninfo(driver_name, str, **kwargs)
50
+ end
51
+
52
+ return parse_hash(driver_name, args.first.merge(kwargs)) if args.length == 1 && args.first.is_a?(Hash)
53
+ return parse_hash(driver_name, kwargs) if args.empty? && !kwargs.empty?
54
+
55
+ parse_positional(driver_name, args, kwargs)
56
+ end
57
+
58
+ # Parses a URI connection string, e.g.
59
+ # "postgresql://user:pass@host1,host2:5432/mydb?sslmode=require"
60
+ def parse_uri(driver_name, uri_string, **overrides)
61
+ # eg ["postgresql", "user:pass@host1,host2:5432/mydb?sslmode=require"]
62
+ scheme_rest = uri_string.split('://', 2)
63
+ # eg "user:pass@host1,host2:5432/mydb?sslmode=require"
64
+ authority_and_rest = scheme_rest[1] || ''
65
+ # eg "host1,host2:5432"
66
+ host_section = extract_host_section(authority_and_rest)
67
+
68
+ # eg "user", "pass", "/mydb", "sslmode=require"
69
+ user, password, path, query_string = parse_uri_parts(uri_string, authority_and_rest)
70
+ # eg {"sslmode" => "require"}
71
+ query_params = query_string ? URI.decode_www_form(query_string).to_h : {}
72
+ # eg {host: "host1,host2", user: "user", password: "pass", dbname: "mydb", sslmode: "require"}
73
+ driver_config = uri_to_config(user, password, path, driver_name)
74
+ # eg {sslmode: "require"}
75
+ all_props = query_params.each_with_object({}) { |(k, v), h| h[k.to_sym] = v }
76
+ overrides.transform_keys(&:to_sym).each { |k, v| all_props[k] = v }
77
+
78
+ # eg {}, {sslmode: "require"}, {}
79
+ wrapper_config, extra_driver, prefixed_wrapper_config, prefixed_driver_config = split_props(all_props)
80
+ extra_driver.each { |k, v| driver_config[k] = v }
81
+
82
+ initial_host_info = string_to_host_info(host_section)
83
+ host, port = host_port_from_uri(host_section)
84
+ driver_config[:host] = host
85
+ driver_config[:port] = port unless port.to_s.tr(',', '').empty?
86
+
87
+ ConnectionConfig.new(
88
+ wrapper_props: wrapper_config,
89
+ driver_props: driver_config,
90
+ prefixed_wrapper_config: prefixed_wrapper_config,
91
+ prefixed_driver_config: prefixed_driver_config,
92
+ initial_host_info:,
93
+ original_host: host,
94
+ original_port: port,
95
+ multi_host_url: host.include?(','),
96
+ driver_name:
97
+ )
98
+ end
99
+
100
+ # Parses libpq key=value conninfo strings, e.g. "host=localhost port=5432 dbname=mydb"
101
+ def parse_conninfo(driver_name, conninfo, **overrides)
102
+ params = conninfo.scan(CONNINFO_PATTERN).each_with_object({}) do |(k, v1, v2), h|
103
+ h[k.to_sym] = v1 || v2
104
+ end
105
+ params.merge!(overrides)
106
+ parse_hash(driver_name, params)
107
+ end
108
+
109
+ # Parses a flat keyword hash, e.g.
110
+ # parse_hash(:postgresql, { host: "myhost", port: 5432, dbname: "mydb", wrapper_plugins: "failover" })
111
+ def parse_hash(driver_name, params)
112
+ params = params.transform_keys(&:to_sym)
113
+ wrapper_config, driver_config, prefixed_wrapper_config, prefixed_driver_config = split_props(params)
114
+ driver_config[:port] = driver_config[:port].to_s if driver_config.key?(:port)
115
+ initial_host_info = hash_to_host_info(driver_config)
116
+
117
+ original_host = driver_config[:host] || driver_config[:hostname]
118
+ original_port = driver_config[:port]
119
+
120
+ ConnectionConfig.new(
121
+ wrapper_props: wrapper_config,
122
+ driver_props: driver_config,
123
+ prefixed_wrapper_config: prefixed_wrapper_config,
124
+ prefixed_driver_config: prefixed_driver_config,
125
+ initial_host_info:,
126
+ original_host:,
127
+ original_port:,
128
+ multi_host_url: original_host.to_s.include?(','),
129
+ driver_name:
130
+ )
131
+ end
132
+
133
+ # Parses PG-style positional arguments, e.g.
134
+ # parse_positional(:postgresql, ["myhost", 5432, nil, nil, "mydb", "user", "pass"], { cluster_id: "test" })
135
+ def parse_positional(driver_name, args, kwargs)
136
+ keys = %i[host port options tty dbname user password]
137
+ positional = keys.zip(args).to_h.compact
138
+ all_props = positional.merge(kwargs.transform_keys(&:to_sym))
139
+
140
+ wrapper_config, driver_config, prefixed_wrapper_config, prefixed_driver_config = split_props(all_props)
141
+ driver_config[:port] = driver_config[:port].to_s if driver_config.key?(:port)
142
+ initial_host_info = hash_to_host_info(driver_config)
143
+
144
+ original_host = driver_config[:host]
145
+ original_port = driver_config[:port]
146
+
147
+ ConnectionConfig.new(
148
+ wrapper_props: wrapper_config,
149
+ driver_props: driver_config,
150
+ prefixed_wrapper_config: prefixed_wrapper_config,
151
+ prefixed_driver_config: prefixed_driver_config,
152
+ initial_host_info:,
153
+ original_host:,
154
+ original_port:,
155
+ multi_host_url: original_host.to_s.include?(','),
156
+ driver_name:
157
+ )
158
+ end
159
+
160
+ # Splits a flat hash into wrapper_config, driver_config, and prefixed_config.
161
+ # Keys matching a known prefix are stripped and grouped by prefix in prefixed_config.
162
+ # Known wrapper properties go to wrapper_config. Everything else goes to driver_config.
163
+ def split_props(props)
164
+ wrapper_config = ::Concurrent::Map.new
165
+ driver_config = ::Concurrent::Map.new
166
+ prefixed_wrapper_config = ::Concurrent::Map.new
167
+ prefixed_driver_config = ::Concurrent::Map.new
168
+
169
+ props.each do |key, value|
170
+ key_s = key.to_s
171
+ prefix = PropertyDefinition::KNOWN_PREFIXES.find { |p| key_s.start_with?(p) }
172
+ if prefix
173
+ prefix_key_sym = key_s.delete_prefix(prefix).to_sym
174
+ if PropertyDefinition.wrapper_property?(prefix_key_sym)
175
+ (prefixed_wrapper_config[prefix] ||= ::Concurrent::Map.new)[prefix_key_sym] = value
176
+ else
177
+ (prefixed_driver_config[prefix] ||= ::Concurrent::Map.new)[prefix_key_sym] = value
178
+ end
179
+ elsif PropertyDefinition.wrapper_property?(key)
180
+ wrapper_config[key.to_sym] = value
181
+ else
182
+ driver_config[key.to_sym] = value
183
+ end
184
+ end
185
+
186
+ validate_props!(wrapper_config)
187
+ prefixed_wrapper_config.each_value { |prefixed| validate_props!(prefixed) }
188
+
189
+ [wrapper_config, driver_config, prefixed_wrapper_config, prefixed_driver_config]
190
+ end
191
+
192
+ # Validates that values in a config hash match the expected type of their corresponding WrapperProperty.
193
+ # Raises TypeError if a value is present but incompatible. Strings that represent valid integers
194
+ # are accepted for Integer-typed properties. Float-typed properties also accept Integers and
195
+ # strings that represent a valid integer or decimal number.
196
+ def validate_props!(config)
197
+ config.each do |key, value|
198
+ prop = PropertyDefinition::KNOWN_PROPERTIES[key]
199
+ next unless prop
200
+ next if value.nil?
201
+ next if prop.type.nil?
202
+
203
+ valid = if prop.type == :boolean
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
215
+
216
+ prop.validate!(value)
217
+ end
218
+ end
219
+
220
+ # Forms a HostInfo object from a URI host section string.
221
+ # Each host entry may or may not have a port; missing ports are represented as -1.
222
+ # The resulting port is a comma-delimited string of per-host ports.
223
+ def string_to_host_info(host)
224
+ host_str = host&.strip
225
+ return Host::HostInfo.new if host_str.nil? || host_str.empty?
226
+
227
+ entries = host_str.split(',').map(&:strip)
228
+ hosts = []
229
+ ports = []
230
+ entries.each do |entry|
231
+ if entry.include?(':')
232
+ h, p = entry.split(':', 2)
233
+ hosts << h
234
+ ports << p.to_s
235
+ else
236
+ hosts << entry
237
+ ports << Host::HostInfo::NO_PORT
238
+ end
239
+ end
240
+
241
+ port_str = ports.uniq.length == 1 ? ports.first : ports.join(',')
242
+ Host::HostInfo.new(host: hosts.join(','), port: port_str)
243
+ end
244
+
245
+ # Forms a HostInfo object from hash-style input.
246
+ # If a single port is given (even with multiple hosts), the port is kept as-is.
247
+ # The resulting port is always a string.
248
+ def hash_to_host_info(hash)
249
+ host_str = hash[:host]&.to_s&.strip || hash[:hostname]&.to_s&.strip
250
+ port_str = hash[:port]&.to_s&.strip
251
+
252
+ Host::HostInfo.new(
253
+ host: host_str || Host::HostInfo::NO_HOST,
254
+ port: port_str || Host::HostInfo::NO_PORT
255
+ )
256
+ end
257
+
258
+ def extract_host_section(authority_and_rest)
259
+ without_userinfo = if authority_and_rest.include?('@')
260
+ authority_and_rest.split('@', 2).last
261
+ else
262
+ authority_and_rest
263
+ end
264
+ without_userinfo.split(%r{[/?#]}, 2).first || ''
265
+ end
266
+
267
+ # Extracts original_host and original_port from a URI host section.
268
+ # Missing ports are represented as empty strings in the comma-delimited port string.
269
+ # E.g. "host1,host2:5433" => ["host1,host2", ",5433"]
270
+ # "host1:5432,host2:5433" => ["host1,host2", "5432,5433"]
271
+ # "host1,host2" => ["host1,host2", ","]
272
+ def host_port_from_uri(host_section)
273
+ entries = host_section.split(',').map(&:strip)
274
+ hosts = []
275
+ ports = []
276
+ entries.each do |entry|
277
+ if entry.include?(':')
278
+ h, p = entry.split(':', 2)
279
+ hosts << h
280
+ ports << p
281
+ else
282
+ hosts << entry
283
+ ports << ''
284
+ end
285
+ end
286
+
287
+ original_host = hosts.join(',')
288
+ original_port = ports.join(',')
289
+ [original_host, original_port]
290
+ end
291
+
292
+ # Parses user, password, path, and query from a PostgreSQL-style URI.
293
+ # Falls back to manual parsing when URI.parse fails (e.g. multi-host with per-host ports).
294
+ def parse_uri_parts(uri_string, authority_and_rest)
295
+ begin
296
+ parsed = URI.parse(uri_string)
297
+ user = parsed.user ? URI.decode_www_form_component(parsed.user) : nil
298
+ password = parsed.password ? URI.decode_www_form_component(parsed.password) : nil
299
+ path = parsed.path
300
+ query_string = parsed.query
301
+ rescue URI::InvalidURIError
302
+ # Manual parsing for multi-host URIs that break standard URI parsing
303
+ user, password = extract_userinfo(authority_and_rest)
304
+ path, query_string = extract_path_and_query(authority_and_rest)
305
+ end
306
+ [user, password, path, query_string]
307
+ end
308
+
309
+ # Extracts user:password from the authority section before the @ sign.
310
+ def extract_userinfo(authority_and_rest)
311
+ return [nil, nil] unless authority_and_rest.include?('@')
312
+
313
+ userinfo = authority_and_rest.split('@', 2).first
314
+ if userinfo.include?(':')
315
+ user, pass = userinfo.split(':', 2)
316
+ [URI.decode_www_form_component(user), URI.decode_www_form_component(pass)]
317
+ else
318
+ [URI.decode_www_form_component(userinfo), nil]
319
+ end
320
+ end
321
+
322
+ # Extracts path and query string from the authority-and-rest section.
323
+ def extract_path_and_query(authority_and_rest)
324
+ without_userinfo = if authority_and_rest.include?('@')
325
+ authority_and_rest.split('@', 2).last
326
+ else
327
+ authority_and_rest
328
+ end
329
+ # Remove host section
330
+ remainder = without_userinfo.split(%r{[/?#]}, 2)[1] || ''
331
+
332
+ # Determine if we split on / or ?
333
+ first_delim_idx = without_userinfo.index(%r{[/?#]})
334
+ if first_delim_idx.nil?
335
+ ['', nil]
336
+ else
337
+ delim = without_userinfo[first_delim_idx]
338
+ if delim == '/'
339
+ path_and_query = "/#{remainder}"
340
+ if path_and_query.include?('?')
341
+ path, query = path_and_query.split('?', 2)
342
+ [path, query]
343
+ else
344
+ [path_and_query, nil]
345
+ end
346
+ elsif delim == '?'
347
+ ['', remainder]
348
+ else
349
+ ['', nil]
350
+ end
351
+ end
352
+ end
353
+
354
+ def uri_to_config(user, password, path, protocol)
355
+ config = {}
356
+ config[:user] = user if user
357
+ config[:password] = password if password
358
+
359
+ db = path&.sub(%r{^/}, '')
360
+ unless db.to_s.empty?
361
+ config[protocol == :postgresql ? :dbname : :database] = db
362
+ end
363
+
364
+ config
365
+ end
366
+ end
367
+ end
368
+ end
@@ -0,0 +1,51 @@
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
+ # Utility methods for converting values from database query results into Ruby types.
20
+ module ConversionUtils
21
+ require 'time'
22
+ def to_boolean(value)
23
+ case value
24
+ when true, 1, '1', 'true', 't', 'TRUE', 'T'
25
+ true
26
+ else
27
+ false
28
+ end
29
+ end
30
+
31
+ def to_float(value)
32
+ Float(value || 0)
33
+ rescue ArgumentError, TypeError
34
+ 0.0
35
+ end
36
+
37
+ def to_time(value)
38
+ case value
39
+ when Time
40
+ value
41
+ when String
42
+ Time.parse(value)
43
+ else
44
+ Time.now
45
+ end
46
+ rescue ArgumentError
47
+ Time.now
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,119 @@
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 '../../logging'
18
+
19
+ module AwsAdvancedRubyDriverWrapper
20
+ module Utils
21
+ module Events
22
+ # Publishes batched events periodically and immediate events synchronously.
23
+ # Batches deduplicate events via Set semantics (eql?/hash).
24
+ #
25
+ # Public API:
26
+ # subscribe(subscriber, event_classes) — register for event types
27
+ # unsubscribe(subscriber, event_classes) — deregister
28
+ # publish(event) — deliver immediate or queue batched
29
+ # release_resources — stop background thread
30
+ class BatchingEventPublisher
31
+ include Logging
32
+
33
+ DEFAULT_MESSAGE_INTERVAL_SEC = 30
34
+
35
+ def initialize(message_interval_sec: DEFAULT_MESSAGE_INTERVAL_SEC)
36
+ @message_interval_sec = message_interval_sec
37
+ @subscribers = {}
38
+ @event_queue = Set.new
39
+ @lock = Mutex.new
40
+ @running = true
41
+ @thread = start_publishing_thread
42
+ end
43
+
44
+ def subscribe(subscriber, event_classes)
45
+ @lock.synchronize do
46
+ event_classes.each do |event_class|
47
+ (@subscribers[event_class] ||= Set.new).add(subscriber)
48
+ end
49
+ end
50
+ end
51
+
52
+ def unsubscribe(subscriber, event_classes)
53
+ @lock.synchronize do
54
+ event_classes.each do |event_class|
55
+ set = @subscribers[event_class]
56
+ next unless set
57
+
58
+ set.delete(subscriber)
59
+ @subscribers.delete(event_class) if set.empty?
60
+ end
61
+ end
62
+ end
63
+
64
+ def publish(event)
65
+ if event.immediate_delivery?
66
+ deliver_event(event)
67
+ else
68
+ @lock.synchronize { @event_queue.add(event) }
69
+ end
70
+ end
71
+
72
+ def release_resources
73
+ @lock.synchronize { @running = false }
74
+ begin
75
+ @thread&.wakeup
76
+ rescue ThreadError
77
+ nil
78
+ end
79
+ @thread&.join(@message_interval_sec)
80
+ end
81
+
82
+ private
83
+
84
+ def start_publishing_thread
85
+ thread = Thread.new do
86
+ while @lock.synchronize { @running }
87
+ sleep(@message_interval_sec)
88
+ send_messages
89
+ end
90
+ send_messages
91
+ end
92
+ thread.name = 'batching-event-publisher'
93
+ thread
94
+ end
95
+
96
+ def send_messages
97
+ events = @lock.synchronize do
98
+ batch = @event_queue.to_a
99
+ @event_queue.clear
100
+ batch
101
+ end
102
+
103
+ events.each { |event| deliver_event(event) }
104
+ end
105
+
106
+ def deliver_event(event)
107
+ subscribers = @lock.synchronize { @subscribers[event.class]&.to_a }
108
+ return unless subscribers
109
+
110
+ subscribers.each do |subscriber|
111
+ subscriber.process_event(event)
112
+ rescue StandardError => e
113
+ logger.error("Error delivering event to subscriber: #{e.message}")
114
+ end
115
+ end
116
+ end
117
+ end
118
+ end
119
+ end