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,137 @@
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 'cache_entry'
18
+
19
+ module AwsAdvancedRubyDriverWrapper
20
+ module Utils
21
+ module Storage
22
+ # A thread-safe cache with sliding TTL expiration. Accessing an existing entry
23
+ # via {#compute_if_absent} renews its expiration, and {#extend_expiration} allows
24
+ # manual renewal. Provides conditional removal via {#remove_if} and
25
+ # {#remove_if_expired} for entries that require careful lifecycle management.
26
+ # Disposal of removed items should be handled by the caller.
27
+ #
28
+ # For simple data that does not need expiration renewal or controlled removal,
29
+ # see {ExpirationCache}.
30
+ class SlidingExpirationCache
31
+ DEFAULT_TTL = 900 # 15 minutes in seconds
32
+
33
+ # @param ttl [Numeric] time-to-live for cache entries in seconds.
34
+ def initialize(ttl: DEFAULT_TTL)
35
+ @cache = {}
36
+ @ttl = ttl
37
+ @lock = Mutex.new
38
+ end
39
+
40
+ # Retrieves the value at the given key. Returns nil if absent or expired.
41
+ # Extends the expiration if the entry exists and is not expired.
42
+ # @param key [Object] the cache key.
43
+ # @return [Object, nil] the cached value, or nil.
44
+ def get(key)
45
+ @lock.synchronize do
46
+ entry = @cache[key]
47
+ return nil if entry.nil? || entry.expired?
48
+
49
+ entry.extend_expiration(@ttl)
50
+ entry.value
51
+ end
52
+ end
53
+
54
+ # Computes and stores a value if the key is absent.
55
+ # If the key exists, extends its expiration and returns the existing value, even if it is expired.
56
+ # @param key [Object] the cache key.
57
+ # @yield [key] block to compute the value if absent.
58
+ # @return [Object] the current (existing or computed) value.
59
+ def compute_if_absent(key)
60
+ @lock.synchronize do
61
+ entry = @cache[key]
62
+
63
+ if entry.nil?
64
+ value = yield(key)
65
+ @cache[key] = new_cache_entry(value)
66
+ return value
67
+ end
68
+
69
+ entry.extend_expiration(@ttl)
70
+ entry.value
71
+ end
72
+ end
73
+
74
+ # Extends the expiration of the entry at the given key, if it exists.
75
+ # @param key [Object] the cache key.
76
+ def extend_expiration(key)
77
+ @lock.synchronize do
78
+ entry = @cache[key]
79
+ entry&.extend_expiration(@ttl)
80
+ end
81
+ end
82
+
83
+ # Removes the value at the given key.
84
+ # @param key [Object] the cache key.
85
+ # @return [Object, nil] the removed value, or nil.
86
+ def remove(key)
87
+ @lock.synchronize do
88
+ entry = @cache.delete(key)
89
+ entry&.value
90
+ end
91
+ end
92
+
93
+ # Removes the value at the given key only if the predicate returns true.
94
+ # @param key [Object] the cache key.
95
+ # @yield [value] block that returns true if the value should be removed.
96
+ # @return [Object, nil] the removed value, or nil.
97
+ def remove_if(key)
98
+ @lock.synchronize do
99
+ entry = @cache[key]
100
+ return nil if entry.nil?
101
+ return nil unless yield(entry.value)
102
+
103
+ @cache.delete(key)
104
+ entry.value
105
+ end
106
+ end
107
+
108
+ # Removes the value at the given key only if it is expired.
109
+ # @param key [Object] the cache key.
110
+ # @return [Object, nil] the removed value, or nil.
111
+ def remove_if_expired(key)
112
+ @lock.synchronize do
113
+ entry = @cache[key]
114
+ return nil if entry.nil? || !entry.expired?
115
+
116
+ @cache.delete(key)
117
+ entry.value
118
+ end
119
+ end
120
+
121
+ # Returns a hash copy of all entries (including expired ones).
122
+ # @return [Hash]
123
+ def entries
124
+ @lock.synchronize do
125
+ @cache.transform_values(&:value)
126
+ end
127
+ end
128
+
129
+ private
130
+
131
+ def new_cache_entry(value)
132
+ CacheEntry.new(value, Process.clock_gettime(Process::CLOCK_MONOTONIC) + @ttl)
133
+ end
134
+ end
135
+ end
136
+ end
137
+ end
@@ -0,0 +1,172 @@
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 'expiration_cache'
18
+ require_relative '../../logging'
19
+ require_relative '../events/data_access_event'
20
+
21
+ module AwsAdvancedRubyDriverWrapper
22
+ module Utils
23
+ module Storage
24
+ # A centralized, shared cache registry with per-type TTL and background cleanup.
25
+ # Each named cache partition stores items independently with its own expiration policy.
26
+ class StorageService
27
+ include Logging
28
+
29
+ DEFAULT_CLEANUP_INTERVAL = 300 # 5 minutes in seconds
30
+
31
+ # @param event_publisher [#publish] the event publisher for data access events.
32
+ # @param cleanup_interval [Numeric] seconds between cleanup runs.
33
+ def initialize(event_publisher:, cleanup_interval: DEFAULT_CLEANUP_INTERVAL)
34
+ @caches = {}
35
+ @event_publisher = event_publisher
36
+ @lock = Mutex.new
37
+ @running = true
38
+ @cleanup_thread = start_cleanup_thread(cleanup_interval)
39
+ end
40
+
41
+ # Registers a named cache. No-op if already registered.
42
+ #
43
+ # @param name [Symbol] cache partition name.
44
+ # @param ttl [Numeric] time-to-live in seconds.
45
+ def register(name, ttl:)
46
+ @lock.synchronize do
47
+ return if @caches.key?(name)
48
+
49
+ @caches[name] = ExpirationCache.new(ttl:)
50
+ end
51
+ end
52
+
53
+ # Stores an item.
54
+ # @param name [Symbol] registered cache name.
55
+ # @param key [Object] item key.
56
+ # @param value [Object] item to store.
57
+ def set(name, key, value)
58
+ fetch_cache!(name).put(key, value)
59
+ end
60
+
61
+ # Retrieves an item. Returns nil if absent or expired.
62
+ # @param name [Symbol] registered cache name.
63
+ # @param key [Object] item key.
64
+ # @param register_access [Boolean] whether to publish a DataAccessEvent.
65
+ # @return [Object, nil]
66
+ def get(name, key, register_access: true)
67
+ value = fetch_cache!(name).get(key)
68
+ return nil unless value
69
+
70
+ @event_publisher.publish(Events::DataAccessEvent.new(data_type: name, key:)) if register_access && @event_publisher
71
+
72
+ value
73
+ end
74
+
75
+ # Retrieves an item without raising if the named cache has not been registered.
76
+ # Returns nil when the cache partition is absent, or when the item is absent or expired.
77
+ # Use this for reads of optional/foreign caches that may not exist in the current
78
+ # plugin configuration (e.g. a plugin reading a cache owned by another, optional plugin).
79
+ # @param name [Symbol] cache name (need not be registered).
80
+ # @param key [Object] item key.
81
+ # @param register_access [Boolean] whether to publish a DataAccessEvent.
82
+ # @return [Object, nil]
83
+ def get_if_registered(name, key, register_access: true)
84
+ cache = @caches[name]
85
+ return nil if cache.nil?
86
+
87
+ value = cache.get(key)
88
+ return nil unless value
89
+
90
+ @event_publisher.publish(Events::DataAccessEvent.new(data_type: name, key:)) if register_access && @event_publisher
91
+
92
+ value
93
+ end
94
+
95
+ # Returns true if a non-expired item exists at the given name + key.
96
+ # @param name [Symbol] registered cache name.
97
+ # @param key [Object] item key.
98
+ # @return [Boolean]
99
+ def exists?(name, key)
100
+ !get(name, key, register_access: false).nil?
101
+ end
102
+
103
+ # Removes an item.
104
+ # @param name [Symbol] registered cache name.
105
+ # @param key [Object] item key.
106
+ def remove(name, key)
107
+ fetch_cache!(name).remove(key)
108
+ end
109
+
110
+ # Clears all items for a given name.
111
+ # @param name [Symbol] registered cache name.
112
+ def clear(name)
113
+ fetch_cache!(name).clear
114
+ end
115
+
116
+ # Clears all items from all registered caches.
117
+ def clear_all
118
+ @lock.synchronize { @caches.keys }.each { |name| clear(name) }
119
+ end
120
+
121
+ # Returns the number of items (including expired) for a given name.
122
+ # @param name [Symbol] registered cache name.
123
+ # @return [Integer]
124
+ def size(name)
125
+ fetch_cache!(name).size
126
+ end
127
+
128
+ # Stops the cleanup thread.
129
+ def shutdown
130
+ @running = false
131
+ begin
132
+ @cleanup_thread&.wakeup
133
+ rescue ThreadError
134
+ nil
135
+ end
136
+ @cleanup_thread&.join(5)
137
+ end
138
+
139
+ private
140
+
141
+ def fetch_cache!(name)
142
+ cache = @caches[name]
143
+ raise ArgumentError, "Cache not registered: #{name.inspect}" unless cache
144
+
145
+ cache
146
+ end
147
+
148
+ def start_cleanup_thread(interval)
149
+ thread = Thread.new do
150
+ while @running
151
+ sleep(interval)
152
+ remove_expired_items
153
+ end
154
+ end
155
+ thread.name = 'storage-service-cleanup'
156
+ thread
157
+ end
158
+
159
+ def remove_expired_items
160
+ @lock.synchronize { @caches.keys }.each do |name|
161
+ cache = @caches[name]
162
+ next unless cache
163
+
164
+ cache.remove_expired_entries
165
+ rescue StandardError => e
166
+ logger.debug("StorageService cleanup failed for #{name}: #{e.message}")
167
+ end
168
+ end
169
+ end
170
+ end
171
+ end
172
+ end
@@ -0,0 +1,127 @@
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 '../host/host_info'
19
+ require_relative '../host/host_role'
20
+ require_relative '../host/host_availability'
21
+ require_relative '../logging'
22
+
23
+ module AwsAdvancedRubyDriverWrapper
24
+ module Utils
25
+ # A mixin providing shared utility methods for retrieving and processing single-cluster topology information.
26
+ # Classes that include this module must implement:
27
+ # - #build_hosts(conn, results, initial_host_info, instance_template) => Array<HostInfo> or nil
28
+ module TopologyUtils
29
+ include Logging
30
+
31
+ attr_reader :dialect
32
+
33
+ # Builds a HostInfo from the given topology information.
34
+ #
35
+ # @param instance_id [String, nil] the database instance identifier, e.g. "mydb-instance-1" (Aurora) or "123456789" (Multi-AZ).
36
+ # @param instance_name [String, nil] the database instance name, e.g. "mydb-instance-1" (Aurora and Multi-AZ).
37
+ # @param is_writer [Boolean] true if this is a writer instance.
38
+ # @param weight [Integer] the instance weight for load balancing.
39
+ # @param last_update_time [Time] the timestamp of the last update to this instance's information.
40
+ # @param initial_host_info [AwsAdvancedRubyDriverWrapper::Host::HostInfo] the original host info used for connecting.
41
+ # @param instance_template [AwsAdvancedRubyDriverWrapper::Host::HostInfo] the template used to construct the new HostInfo.
42
+ # @return [AwsAdvancedRubyDriverWrapper::Host::HostInfo] a HostInfo representing the given information.
43
+ def build_host(instance_id, instance_name, is_writer, weight, last_update_time, initial_host_info, instance_template)
44
+ instance_name = '?' if instance_name.nil?
45
+ endpoint = instance_template.host.sub('?', instance_name)
46
+ port = resolve_port(instance_template, initial_host_info)
47
+ role = is_writer ? Host::HostRole::WRITER : Host::HostRole::READER
48
+
49
+ Host::HostInfo.new(
50
+ host: endpoint,
51
+ port:,
52
+ role:,
53
+ availability: Host::HostAvailability::AVAILABLE,
54
+ weight:,
55
+ id: instance_id,
56
+ last_update_time:
57
+ )
58
+ end
59
+
60
+ # Retrieves a value from a row hash, trying both string and symbol keys.
61
+ def row_value(row, key)
62
+ row[key] || row[key.to_sym]
63
+ end
64
+
65
+ # Query the database for information for each instance in the database topology.
66
+ #
67
+ # @param conn [Object] the connection to use to query the database.
68
+ # @param initial_host_info [AwsAdvancedRubyDriverWrapper::Host::HostInfo] the HostInfo used to initially connect.
69
+ # @param instance_template [AwsAdvancedRubyDriverWrapper::Host::HostInfo] the template HostInfo to use when
70
+ # constructing new HostInfo objects from the data returned by the topology query.
71
+ # @return [Array<AwsAdvancedRubyDriverWrapper::Host::HostInfo>, nil] a list of HostInfo objects representing
72
+ # the results of the topology query, or nil if the query returned unexpected results.
73
+ def query_topology(conn, initial_host_info, instance_template)
74
+ results = @dialect.execute(conn, @dialect.topology_query)
75
+ # We expect at least 4 columns. Note that the server may return 0 columns if failover has occurred.
76
+ if results.fields.empty?
77
+ logger.debug('The topology query returned a result with 0 columns. ' \
78
+ 'This may occur if the topology query is executed when the server is failing over.')
79
+ return nil
80
+ end
81
+
82
+ verify_writer(build_hosts(conn, results, initial_host_info, instance_template))
83
+ end
84
+
85
+ private
86
+
87
+ # Verifies the writer in the host list. If multiple writers exist, the one with the most recent
88
+ # last_update_time is used as the current writer.
89
+ #
90
+ # @param all_hosts [Array<AwsAdvancedRubyDriverWrapper::Host::HostInfo>, nil] the list of all hosts.
91
+ # @return [Array<AwsAdvancedRubyDriverWrapper::Host::HostInfo>, nil] the verified host list, or nil if no writer found.
92
+ def verify_writer(all_hosts)
93
+ return nil if all_hosts.nil?
94
+
95
+ hosts = []
96
+ writers = []
97
+
98
+ all_hosts.each do |host|
99
+ if host.role == Host::HostRole::WRITER
100
+ writers << host
101
+ else
102
+ hosts << host
103
+ end
104
+ end
105
+
106
+ return nil if writers.empty?
107
+
108
+ if writers.size == 1
109
+ hosts << writers.first
110
+ else
111
+ # Assume the latest updated writer instance is the current writer.
112
+ sorted_writers = writers.sort_by { |w| w.last_update_time || Time.at(0) }.reverse
113
+ hosts << sorted_writers.first
114
+ end
115
+
116
+ hosts
117
+ end
118
+
119
+ def resolve_port(instance_template, initial_host_info)
120
+ return instance_template.port if instance_template.port_specified?
121
+ return initial_host_info.port if initial_host_info
122
+
123
+ Host::HostInfo::NO_PORT
124
+ end
125
+ end
126
+ end
127
+ end
@@ -0,0 +1,19 @@
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
+ VERSION = '1.0.0'
19
+ end
@@ -0,0 +1,64 @@
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
+ class WrapperProperty
19
+ attr_reader :name, :default_value, :description, :type
20
+
21
+ def initialize(name, description, default_value: nil, type: nil, validator: nil)
22
+ @name = name.to_sym
23
+ @description = description
24
+ @default_value = default_value
25
+ @type = type
26
+ @validator = validator
27
+ end
28
+
29
+ # @return [String, Boolean, Integer, nil] the value from props, or the property's default
30
+ def get(props, override = nil)
31
+ override&.key?(@name) ? override.fetch(@name, @default_value) : props.fetch(@name, @default_value)
32
+ end
33
+
34
+ def get_bool(props, override = nil)
35
+ val = get(props, override)
36
+ return val if val.is_a?(TrueClass) || val.is_a?(FalseClass)
37
+
38
+ val.to_s.downcase == 'true'
39
+ end
40
+
41
+ def get_int(props, override = nil)
42
+ val = get(props, override)
43
+ val.is_a?(Integer) ? val : val.to_i
44
+ end
45
+
46
+ def get_float(props, override = nil)
47
+ val = get(props, override)
48
+ val.is_a?(Float) ? val : val.to_f
49
+ end
50
+
51
+ def get_string(props, override = nil)
52
+ val = get(props, override)
53
+ val&.to_s
54
+ end
55
+
56
+ def validate!(value)
57
+ @validator&.call(value, @name)
58
+ end
59
+
60
+ def set(props, value)
61
+ props.put(@name, value)
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,116 @@
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/version'
18
+ require_relative 'aws_advanced_ruby_driver_wrapper/custom_configuration'
19
+ require_relative 'aws_advanced_ruby_driver_wrapper/property_definition'
20
+ require_relative 'aws_advanced_ruby_driver_wrapper/logging'
21
+ # Error classes are loaded up front so they can be named before any connection is opened, for
22
+ # example in a Rails `rescue_from` or ActiveJob `retry_on`, which are evaluated at class load.
23
+ require_relative 'aws_advanced_ruby_driver_wrapper/errors'
24
+ require_relative 'aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/errors'
25
+ require_relative 'aws_advanced_ruby_driver_wrapper/utils/rds_utils'
26
+ require_relative 'aws_advanced_ruby_driver_wrapper/utils/connection_config'
27
+ require_relative 'aws_advanced_ruby_driver_wrapper/utils/connection_config_parser'
28
+ require_relative 'aws_advanced_ruby_driver_wrapper/monitoring/monitor_state'
29
+ require_relative 'aws_advanced_ruby_driver_wrapper/monitoring/monitor'
30
+ require_relative 'aws_advanced_ruby_driver_wrapper/plugins/blue_green/blue_green_plugin'
31
+
32
+ module AwsAdvancedRubyDriverWrapper
33
+ @config = Configuration.new
34
+
35
+ # Direct-driver connection classes are autoloaded so requiring this file alone is enough to use them:
36
+ # referencing either constant loads its driver-specific file on demand. Loading stays lazy - a client
37
+ # for one driver never loads the other driver's code, and the underlying 'pg'/'mysql2' gem is only
38
+ # required when a connection is actually opened.
39
+ autoload :WrapperPgConnection, 'aws_advanced_ruby_driver_wrapper/postgresql'
40
+ autoload :WrapperMysql2Client, 'aws_advanced_ruby_driver_wrapper/mysql'
41
+
42
+ # Other public entry points are autoloaded for the same reason, so they work from code that runs
43
+ # before any connection is opened, such as a Rails initializer or a setup script.
44
+ module Services
45
+ autoload :HostService, 'aws_advanced_ruby_driver_wrapper/services/host_service'
46
+ autoload :PluginManager, 'aws_advanced_ruby_driver_wrapper/services/plugin_manager'
47
+ end
48
+
49
+ module Plugins
50
+ module Encryption
51
+ autoload :EncryptionConfig, 'aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/encryption_config'
52
+ autoload :KeyManagementUtility, 'aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/key_management_utility'
53
+ end
54
+ end
55
+
56
+ class << self
57
+ attr_reader :config
58
+ end
59
+
60
+ # Gracefully tears down all background resources. Invoked by the at_exit hook (and thus
61
+ # indirectly by the TERM/INT signal traps, which just exit). Safe to call more than once.
62
+ def self.shutdown(grace_period_sec: 10)
63
+ Plugins::BlueGreen::BlueGreenPlugin.clean_up_providers
64
+ release_resources(grace_period_sec: grace_period_sec)
65
+ end
66
+
67
+ def self.clear_caches
68
+ require_relative 'aws_advanced_ruby_driver_wrapper/services/service_utility'
69
+ require_relative 'aws_advanced_ruby_driver_wrapper/services/host_service'
70
+ Services::CoreServices.storage_service.clear_all
71
+ Utils::RdsUtils.clear_cache
72
+ Services::DialectService.known_endpoint_dialects.clear
73
+ Services::HostService.clear_id_cache
74
+ end
75
+
76
+ def self.release_resources(grace_period_sec: 5)
77
+ require_relative 'aws_advanced_ruby_driver_wrapper/services/service_utility'
78
+ Services::CoreServices.monitor_service.shutdown(grace_period: grace_period_sec)
79
+ Services::CoreServices.event_publisher.release_resources
80
+ clear_caches
81
+ end
82
+ end
83
+
84
+ # Register signal traps and at_exit hook for graceful shutdown. The traps only exit — the real
85
+ # teardown runs in the at_exit hook, since acquiring locks / joining threads inside a signal-trap
86
+ # context is unsafe in Ruby and can deadlock.
87
+ %w[TERM INT].each { |signal| trap(signal) { exit(0) } }
88
+
89
+ at_exit { AwsAdvancedRubyDriverWrapper.shutdown }
90
+
91
+ # Register adapters with ActiveRecord via a lazy-load hook so the require order between this file and
92
+ # ActiveRecord does not matter. The block runs the first time ActiveRecord::Base is referenced, whether
93
+ # ActiveRecord is loaded before or after this file. The register call is itself lazy — each adapter file
94
+ # is only loaded when a connection using that adapter is first established, so users of a single adapter
95
+ # never load the other.
96
+ begin
97
+ require 'active_support/lazy_load_hooks'
98
+ rescue LoadError
99
+ # ActiveSupport is absent (raw-driver-only usage with no ActiveRecord); there is nothing to register.
100
+ end
101
+
102
+ if defined?(ActiveSupport) && ActiveSupport.respond_to?(:on_load)
103
+ ActiveSupport.on_load(:active_record) do
104
+ ActiveRecord::ConnectionAdapters.register(
105
+ 'aws_postgresql',
106
+ 'ActiveRecord::ConnectionAdapters::AwsPostgreSQLAdapter',
107
+ 'aws_advanced_ruby_driver_wrapper/active_record/aws_postgresql_adapter'
108
+ )
109
+
110
+ ActiveRecord::ConnectionAdapters.register(
111
+ 'aws_mysql2',
112
+ 'ActiveRecord::ConnectionAdapters::AwsMysql2Adapter',
113
+ 'aws_advanced_ruby_driver_wrapper/active_record/aws_mysql2_adapter'
114
+ )
115
+ end
116
+ end