pghero_fork 2.7.3

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 (75) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +391 -0
  3. data/CONTRIBUTING.md +42 -0
  4. data/LICENSE.txt +22 -0
  5. data/README.md +3 -0
  6. data/app/assets/images/pghero/favicon.png +0 -0
  7. data/app/assets/javascripts/pghero/Chart.bundle.js +20755 -0
  8. data/app/assets/javascripts/pghero/application.js +158 -0
  9. data/app/assets/javascripts/pghero/chartkick.js +2436 -0
  10. data/app/assets/javascripts/pghero/highlight.pack.js +2 -0
  11. data/app/assets/javascripts/pghero/jquery.js +10872 -0
  12. data/app/assets/javascripts/pghero/nouislider.js +2672 -0
  13. data/app/assets/stylesheets/pghero/application.css +514 -0
  14. data/app/assets/stylesheets/pghero/arduino-light.css +86 -0
  15. data/app/assets/stylesheets/pghero/nouislider.css +310 -0
  16. data/app/controllers/pg_hero/home_controller.rb +449 -0
  17. data/app/helpers/pg_hero/home_helper.rb +30 -0
  18. data/app/views/layouts/pg_hero/application.html.erb +68 -0
  19. data/app/views/pg_hero/home/_connections_table.html.erb +16 -0
  20. data/app/views/pg_hero/home/_live_queries_table.html.erb +51 -0
  21. data/app/views/pg_hero/home/_queries_table.html.erb +72 -0
  22. data/app/views/pg_hero/home/_query_stats_slider.html.erb +16 -0
  23. data/app/views/pg_hero/home/_suggested_index.html.erb +18 -0
  24. data/app/views/pg_hero/home/connections.html.erb +32 -0
  25. data/app/views/pg_hero/home/explain.html.erb +27 -0
  26. data/app/views/pg_hero/home/index.html.erb +518 -0
  27. data/app/views/pg_hero/home/index_bloat.html.erb +72 -0
  28. data/app/views/pg_hero/home/live_queries.html.erb +11 -0
  29. data/app/views/pg_hero/home/maintenance.html.erb +55 -0
  30. data/app/views/pg_hero/home/queries.html.erb +33 -0
  31. data/app/views/pg_hero/home/relation_space.html.erb +14 -0
  32. data/app/views/pg_hero/home/show_query.html.erb +106 -0
  33. data/app/views/pg_hero/home/space.html.erb +83 -0
  34. data/app/views/pg_hero/home/system.html.erb +34 -0
  35. data/app/views/pg_hero/home/tune.html.erb +53 -0
  36. data/config/routes.rb +32 -0
  37. data/lib/generators/pghero/config_generator.rb +13 -0
  38. data/lib/generators/pghero/query_stats_generator.rb +18 -0
  39. data/lib/generators/pghero/space_stats_generator.rb +18 -0
  40. data/lib/generators/pghero/templates/config.yml.tt +46 -0
  41. data/lib/generators/pghero/templates/query_stats.rb.tt +15 -0
  42. data/lib/generators/pghero/templates/space_stats.rb.tt +13 -0
  43. data/lib/pghero.rb +246 -0
  44. data/lib/pghero/connection.rb +5 -0
  45. data/lib/pghero/database.rb +175 -0
  46. data/lib/pghero/engine.rb +16 -0
  47. data/lib/pghero/methods/basic.rb +160 -0
  48. data/lib/pghero/methods/connections.rb +77 -0
  49. data/lib/pghero/methods/constraints.rb +30 -0
  50. data/lib/pghero/methods/explain.rb +29 -0
  51. data/lib/pghero/methods/indexes.rb +332 -0
  52. data/lib/pghero/methods/kill.rb +28 -0
  53. data/lib/pghero/methods/maintenance.rb +93 -0
  54. data/lib/pghero/methods/queries.rb +75 -0
  55. data/lib/pghero/methods/query_stats.rb +349 -0
  56. data/lib/pghero/methods/replication.rb +74 -0
  57. data/lib/pghero/methods/sequences.rb +124 -0
  58. data/lib/pghero/methods/settings.rb +37 -0
  59. data/lib/pghero/methods/space.rb +141 -0
  60. data/lib/pghero/methods/suggested_indexes.rb +329 -0
  61. data/lib/pghero/methods/system.rb +287 -0
  62. data/lib/pghero/methods/tables.rb +68 -0
  63. data/lib/pghero/methods/users.rb +87 -0
  64. data/lib/pghero/query_stats.rb +5 -0
  65. data/lib/pghero/space_stats.rb +5 -0
  66. data/lib/pghero/stats.rb +6 -0
  67. data/lib/pghero/version.rb +3 -0
  68. data/lib/tasks/pghero.rake +27 -0
  69. data/licenses/LICENSE-chart.js.txt +9 -0
  70. data/licenses/LICENSE-chartkick.js.txt +22 -0
  71. data/licenses/LICENSE-highlight.js.txt +29 -0
  72. data/licenses/LICENSE-jquery.txt +20 -0
  73. data/licenses/LICENSE-moment.txt +22 -0
  74. data/licenses/LICENSE-nouislider.txt +21 -0
  75. metadata +130 -0
@@ -0,0 +1,32 @@
1
+ PgHero::Engine.routes.draw do
2
+ scope "(:database)", constraints: proc { |req| (PgHero.config["databases"].keys + [nil]).include?(req.params[:database]) } do
3
+ get "space", to: "home#space"
4
+ get "space/:relation", to: "home#relation_space", as: :relation_space
5
+ get "index_bloat", to: "home#index_bloat"
6
+ get "live_queries", to: "home#live_queries"
7
+ get "queries", to: "home#queries"
8
+ get "queries/:query_hash", to: "home#show_query", as: :show_query
9
+ get "system", to: "home#system"
10
+ get "cpu_usage", to: "home#cpu_usage"
11
+ get "connection_stats", to: "home#connection_stats"
12
+ get "replication_lag_stats", to: "home#replication_lag_stats"
13
+ get "load_stats", to: "home#load_stats"
14
+ get "free_space_stats", to: "home#free_space_stats"
15
+ get "explain", to: "home#explain"
16
+ get "tune", to: "home#tune"
17
+ get "connections", to: "home#connections"
18
+ get "maintenance", to: "home#maintenance"
19
+ post "kill", to: "home#kill"
20
+ post "kill_long_running_queries", to: "home#kill_long_running_queries"
21
+ post "kill_all", to: "home#kill_all"
22
+ post "enable_query_stats", to: "home#enable_query_stats"
23
+ post "explain", to: "home#explain"
24
+ post "reset_query_stats", to: "home#reset_query_stats"
25
+
26
+ # legacy routes
27
+ get "system_stats" => redirect("system")
28
+ get "query_stats" => redirect("queries")
29
+
30
+ root to: "home#index"
31
+ end
32
+ end
@@ -0,0 +1,13 @@
1
+ require "rails/generators"
2
+
3
+ module Pghero
4
+ module Generators
5
+ class ConfigGenerator < Rails::Generators::Base
6
+ source_root File.join(__dir__, "templates")
7
+
8
+ def create_initializer
9
+ template "config.yml", "config/pghero.yml"
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,18 @@
1
+ require "rails/generators/active_record"
2
+
3
+ module Pghero
4
+ module Generators
5
+ class QueryStatsGenerator < Rails::Generators::Base
6
+ include ActiveRecord::Generators::Migration
7
+ source_root File.join(__dir__, "templates")
8
+
9
+ def copy_migration
10
+ migration_template "query_stats.rb", "db/migrate/create_pghero_query_stats.rb", migration_version: migration_version
11
+ end
12
+
13
+ def migration_version
14
+ "[#{ActiveRecord::VERSION::MAJOR}.#{ActiveRecord::VERSION::MINOR}]"
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,18 @@
1
+ require "rails/generators/active_record"
2
+
3
+ module Pghero
4
+ module Generators
5
+ class SpaceStatsGenerator < Rails::Generators::Base
6
+ include ActiveRecord::Generators::Migration
7
+ source_root File.join(__dir__, "templates")
8
+
9
+ def copy_migration
10
+ migration_template "space_stats.rb", "db/migrate/create_pghero_space_stats.rb", migration_version: migration_version
11
+ end
12
+
13
+ def migration_version
14
+ "[#{ActiveRecord::VERSION::MAJOR}.#{ActiveRecord::VERSION::MINOR}]"
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,46 @@
1
+ databases:
2
+ primary:
3
+ # Database URL (defaults to app database)
4
+ # url: <%%= ENV["DATABASE_URL"] %>
5
+
6
+ # System stats
7
+ # aws_db_instance_identifier: my-instance
8
+ # gcp_database_id: my-project:my-instance
9
+ # azure_resource_id: my-resource-id
10
+
11
+ # Add more databases
12
+ # other:
13
+ # url: <%%= ENV["OTHER_DATABASE_URL"] %>
14
+
15
+ # Minimum time for long running queries
16
+ # long_running_query_sec: 60
17
+
18
+ # Minimum average time for slow queries
19
+ # slow_query_ms: 20
20
+
21
+ # Minimum calls for slow queries
22
+ # slow_query_calls: 100
23
+
24
+ # Minimum connections for high connections warning
25
+ # total_connections_threshold: 500
26
+
27
+ # Statement timeout for explain
28
+ # explain_timeout_sec: 10
29
+
30
+ # Time zone (defaults to app time zone)
31
+ # time_zone: "Pacific Time (US & Canada)"
32
+
33
+ # Basic authentication
34
+ # username: admin
35
+ # password: <%%= ENV["PGHERO_PASSWORD"] %>
36
+
37
+ # Stats database URL (defaults to app database)
38
+ # stats_database_url: <%%= ENV["PGHERO_STATS_DATABASE_URL"] %>
39
+
40
+ # AWS configuration (defaults to app AWS config)
41
+ # aws_access_key_id: <%%= ENV["AWS_ACCESS_KEY_ID"] %>
42
+ # aws_secret_access_key: <%%= ENV["AWS_SECRET_ACCESS_KEY"] %>
43
+ # aws_region: us-east-1
44
+
45
+ # Filter data from queries (experimental)
46
+ # filter_data: true
@@ -0,0 +1,15 @@
1
+ class <%= migration_class_name %> < ActiveRecord::Migration<%= migration_version %>
2
+ def change
3
+ create_table :pghero_query_stats do |t|
4
+ t.text :database
5
+ t.text :user
6
+ t.text :query
7
+ t.integer :query_hash, limit: 8
8
+ t.float :total_time
9
+ t.integer :calls, limit: 8
10
+ t.timestamp :captured_at
11
+ end
12
+
13
+ add_index :pghero_query_stats, [:database, :captured_at]
14
+ end
15
+ end
@@ -0,0 +1,13 @@
1
+ class <%= migration_class_name %> < ActiveRecord::Migration<%= migration_version %>
2
+ def change
3
+ create_table :pghero_space_stats do |t|
4
+ t.text :database
5
+ t.text :schema
6
+ t.text :relation
7
+ t.integer :size, limit: 8
8
+ t.timestamp :captured_at
9
+ end
10
+
11
+ add_index :pghero_space_stats, [:database, :captured_at]
12
+ end
13
+ end
@@ -0,0 +1,246 @@
1
+ # dependencies
2
+ require "active_support"
3
+ require "forwardable"
4
+
5
+ # methods
6
+ require "pghero/methods/basic"
7
+ require "pghero/methods/connections"
8
+ require "pghero/methods/constraints"
9
+ require "pghero/methods/explain"
10
+ require "pghero/methods/indexes"
11
+ require "pghero/methods/kill"
12
+ require "pghero/methods/maintenance"
13
+ require "pghero/methods/queries"
14
+ require "pghero/methods/query_stats"
15
+ require "pghero/methods/replication"
16
+ require "pghero/methods/sequences"
17
+ require "pghero/methods/settings"
18
+ require "pghero/methods/space"
19
+ require "pghero/methods/suggested_indexes"
20
+ require "pghero/methods/system"
21
+ require "pghero/methods/tables"
22
+ require "pghero/methods/users"
23
+
24
+ require "pghero/database"
25
+ require "pghero/engine" if defined?(Rails)
26
+ require "pghero/version"
27
+
28
+ module PgHero
29
+ autoload :Connection, "pghero/connection"
30
+ autoload :Stats, "pghero/stats"
31
+ autoload :QueryStats, "pghero/query_stats"
32
+ autoload :SpaceStats, "pghero/space_stats"
33
+
34
+ class Error < StandardError; end
35
+ class NotEnabled < Error; end
36
+
37
+ MUTEX = Mutex.new
38
+
39
+ # settings
40
+ class << self
41
+ attr_accessor :long_running_query_sec, :slow_query_ms, :slow_query_calls, :explain_timeout_sec, :total_connections_threshold, :cache_hit_rate_threshold, :env, :show_migrations, :config_path, :filter_data
42
+ end
43
+ self.long_running_query_sec = (ENV["PGHERO_LONG_RUNNING_QUERY_SEC"] || 60).to_i
44
+ self.slow_query_ms = (ENV["PGHERO_SLOW_QUERY_MS"] || 20).to_i
45
+ self.slow_query_calls = (ENV["PGHERO_SLOW_QUERY_CALLS"] || 100).to_i
46
+ self.explain_timeout_sec = (ENV["PGHERO_EXPLAIN_TIMEOUT_SEC"] || 10).to_f
47
+ self.total_connections_threshold = (ENV["PGHERO_TOTAL_CONNECTIONS_THRESHOLD"] || 500).to_i
48
+ self.cache_hit_rate_threshold = 99
49
+ self.env = ENV["RAILS_ENV"] || ENV["RACK_ENV"] || "development"
50
+ self.show_migrations = true
51
+ self.config_path = ENV["PGHERO_CONFIG_PATH"] || "config/pghero.yml"
52
+ self.filter_data = ENV["PGHERO_FILTER_DATA"].to_s.size > 0
53
+
54
+ class << self
55
+ extend Forwardable
56
+ def_delegators :primary_database, :access_key_id, :analyze, :analyze_tables, :autoindex, :autovacuum_danger,
57
+ :best_index, :blocked_queries, :connections, :connection_sources, :connection_states, :connection_stats,
58
+ :cpu_usage, :create_user, :database_size, :db_instance_identifier, :disable_query_stats, :drop_user,
59
+ :duplicate_indexes, :enable_query_stats, :explain, :historical_query_stats_enabled?, :index_caching,
60
+ :index_hit_rate, :index_usage, :indexes, :invalid_constraints, :invalid_indexes, :kill, :kill_all, :kill_long_running_queries,
61
+ :last_stats_reset_time, :long_running_queries, :maintenance_info, :missing_indexes, :query_stats,
62
+ :query_stats_available?, :query_stats_enabled?, :query_stats_extension_enabled?, :query_stats_readable?,
63
+ :rds_stats, :read_iops_stats, :region, :relation_sizes, :replica?, :replication_lag, :replication_lag_stats,
64
+ :reset_query_stats, :reset_stats, :running_queries, :secret_access_key, :sequence_danger, :sequences, :settings,
65
+ :slow_queries, :space_growth, :ssl_used?, :stats_connection, :suggested_indexes, :suggested_indexes_by_query,
66
+ :suggested_indexes_enabled?, :system_stats_enabled?, :table_caching, :table_hit_rate, :table_stats,
67
+ :total_connections, :transaction_id_danger, :unused_indexes, :unused_tables, :write_iops_stats
68
+
69
+ def time_zone=(time_zone)
70
+ @time_zone = time_zone.is_a?(ActiveSupport::TimeZone) ? time_zone : ActiveSupport::TimeZone[time_zone.to_s]
71
+ end
72
+
73
+ def time_zone
74
+ @time_zone || Time.zone
75
+ end
76
+
77
+ # use method instead of attr_accessor to ensure
78
+ # this works if variable set after PgHero is loaded
79
+ def username
80
+ @username ||= config["username"] || ENV["PGHERO_USERNAME"]
81
+ end
82
+
83
+ # use method instead of attr_accessor to ensure
84
+ # this works if variable set after PgHero is loaded
85
+ def password
86
+ @password ||= config["password"] || ENV["PGHERO_PASSWORD"]
87
+ end
88
+
89
+ def stats_database_url
90
+ @stats_database_url ||= config["stats_database_url"] || ENV["PGHERO_STATS_DATABASE_URL"]
91
+ end
92
+
93
+ def config
94
+ @config ||= begin
95
+ require "erb"
96
+ require "yaml"
97
+
98
+ path = config_path
99
+
100
+ config_file_exists = File.exist?(path)
101
+
102
+ config = YAML.load(ERB.new(File.read(path)).result) if config_file_exists
103
+ config ||= {}
104
+
105
+ if config[env]
106
+ config[env]
107
+ elsif config["databases"] # preferred format
108
+ config
109
+ elsif config_file_exists
110
+ raise "Invalid config file"
111
+ else
112
+ databases = {}
113
+
114
+ if !ENV["PGHERO_DATABASE_URL"] && spec_supported?
115
+ ActiveRecord::Base.configurations.configs_for(env_name: env, include_replicas: true).each do |db|
116
+ databases[db.send(spec_name_key)] = {"spec" => db.send(spec_name_key)}
117
+ end
118
+ end
119
+
120
+ if databases.empty?
121
+ databases["primary"] = {
122
+ "url" => ENV["PGHERO_DATABASE_URL"] || connection_config(ActiveRecord::Base)
123
+ }
124
+ end
125
+
126
+ if databases.size == 1
127
+ databases.values.first.merge!(
128
+ "db_instance_identifier" => ENV["PGHERO_DB_INSTANCE_IDENTIFIER"],
129
+ "gcp_database_id" => ENV["PGHERO_GCP_DATABASE_ID"],
130
+ "azure_resource_id" => ENV["PGHERO_AZURE_RESOURCE_ID"]
131
+ )
132
+ end
133
+
134
+ {
135
+ "databases" => databases
136
+ }
137
+ end
138
+ end
139
+ end
140
+
141
+ # ensure we only have one copy of databases
142
+ # so there's only one connection pool per database
143
+ def databases
144
+ unless defined?(@databases)
145
+ # only use mutex on initialization
146
+ MUTEX.synchronize do
147
+ # return if another process initialized while we were waiting
148
+ return @databases if defined?(@databases)
149
+
150
+ @databases = config["databases"].map { |id, c| [id.to_sym, Database.new(id, c)] }.to_h
151
+ end
152
+ end
153
+
154
+ @databases
155
+ end
156
+
157
+ def primary_database
158
+ databases.values.first
159
+ end
160
+
161
+ def capture_query_stats(verbose: false)
162
+ each_database do |database|
163
+ next unless database.capture_query_stats?
164
+ puts "Capturing query stats for #{database.id}..." if verbose
165
+ database.capture_query_stats(raise_errors: true)
166
+ end
167
+ end
168
+
169
+ def capture_space_stats(verbose: false)
170
+ each_database do |database|
171
+ puts "Capturing space stats for #{database.id}..." if verbose
172
+ database.capture_space_stats
173
+ end
174
+ end
175
+
176
+ def analyze_all(**options)
177
+ each_database do |database|
178
+ next if database.replica?
179
+ database.analyze_tables(**options)
180
+ end
181
+ end
182
+
183
+ def autoindex_all(create: false, verbose: true)
184
+ each_database do |database|
185
+ puts "Autoindexing #{database.id}..." if verbose
186
+ database.autoindex(create: create)
187
+ end
188
+ end
189
+
190
+ def pretty_size(value)
191
+ ActiveSupport::NumberHelper.number_to_human_size(value, precision: 3)
192
+ end
193
+
194
+ # delete previous stats
195
+ # go database by database to use an index
196
+ # stats for old databases are not cleaned up since we can't use an index
197
+ def clean_query_stats
198
+ each_database do |database|
199
+ database.clean_query_stats
200
+ end
201
+ end
202
+
203
+ def clean_space_stats
204
+ each_database do |database|
205
+ database.clean_space_stats
206
+ end
207
+ end
208
+
209
+ # private
210
+ def spec_supported?
211
+ ActiveRecord::VERSION::MAJOR >= 6
212
+ end
213
+
214
+ # private
215
+ def connection_config(model)
216
+ ActiveRecord::VERSION::STRING.to_f >= 6.1 ? model.connection_db_config.configuration_hash : model.connection_config
217
+ end
218
+
219
+ # private
220
+ # Rails 6.1 deprecate `spec_name` and use `name` for configurations
221
+ # https://github.com/rails/rails/pull/38536
222
+ def spec_name_key
223
+ ActiveRecord::VERSION::STRING.to_f >= 6.1 ? :name : :spec_name
224
+ end
225
+
226
+ private
227
+
228
+ def each_database
229
+ first_error = nil
230
+
231
+ databases.each do |_, database|
232
+ begin
233
+ yield database
234
+ rescue => e
235
+ puts "#{e.class.name}: #{e.message}"
236
+ puts
237
+ first_error ||= e
238
+ end
239
+ end
240
+
241
+ raise first_error if first_error
242
+
243
+ true
244
+ end
245
+ end
246
+ end
@@ -0,0 +1,5 @@
1
+ module PgHero
2
+ class Connection < ActiveRecord::Base
3
+ self.abstract_class = true
4
+ end
5
+ end
@@ -0,0 +1,175 @@
1
+ module PgHero
2
+ class Database
3
+ include Methods::Basic
4
+ include Methods::Connections
5
+ include Methods::Constraints
6
+ include Methods::Explain
7
+ include Methods::Indexes
8
+ include Methods::Kill
9
+ include Methods::Maintenance
10
+ include Methods::Queries
11
+ include Methods::QueryStats
12
+ include Methods::Replication
13
+ include Methods::Sequences
14
+ include Methods::Settings
15
+ include Methods::Space
16
+ include Methods::SuggestedIndexes
17
+ include Methods::System
18
+ include Methods::Tables
19
+ include Methods::Users
20
+
21
+ attr_reader :id, :config
22
+
23
+ def initialize(id, config)
24
+ @id = id
25
+ @config = config || {}
26
+
27
+ # preload model to ensure only one connection pool
28
+ # this doesn't actually start any connections
29
+ @adapter_checked = false
30
+ @connection_model = build_connection_model
31
+ end
32
+
33
+ def name
34
+ @name ||= @config["name"] || id.titleize
35
+ end
36
+
37
+ def capture_query_stats?
38
+ config["capture_query_stats"] != false
39
+ end
40
+
41
+ def cache_hit_rate_threshold
42
+ (config["cache_hit_rate_threshold"] || PgHero.config["cache_hit_rate_threshold"] || PgHero.cache_hit_rate_threshold).to_i
43
+ end
44
+
45
+ def total_connections_threshold
46
+ (config["total_connections_threshold"] || PgHero.config["total_connections_threshold"] || PgHero.total_connections_threshold).to_i
47
+ end
48
+
49
+ def slow_query_ms
50
+ (config["slow_query_ms"] || PgHero.config["slow_query_ms"] || PgHero.slow_query_ms).to_i
51
+ end
52
+
53
+ def slow_query_calls
54
+ (config["slow_query_calls"] || PgHero.config["slow_query_calls"] || PgHero.slow_query_calls).to_i
55
+ end
56
+
57
+ def explain_timeout_sec
58
+ (config["explain_timeout_sec"] || PgHero.config["explain_timeout_sec"] || PgHero.explain_timeout_sec).to_f
59
+ end
60
+
61
+ def long_running_query_sec
62
+ (config["long_running_query_sec"] || PgHero.config["long_running_query_sec"] || PgHero.long_running_query_sec).to_i
63
+ end
64
+
65
+ # defaults to 100 megabytes
66
+ def index_bloat_bytes
67
+ (config["index_bloat_bytes"] || PgHero.config["index_bloat_bytes"] || 104857600).to_i
68
+ end
69
+
70
+ def aws_access_key_id
71
+ config["aws_access_key_id"] || PgHero.config["aws_access_key_id"] || ENV["PGHERO_ACCESS_KEY_ID"] || ENV["AWS_ACCESS_KEY_ID"]
72
+ end
73
+
74
+ def aws_secret_access_key
75
+ config["aws_secret_access_key"] || PgHero.config["aws_secret_access_key"] || ENV["PGHERO_SECRET_ACCESS_KEY"] || ENV["AWS_SECRET_ACCESS_KEY"]
76
+ end
77
+
78
+ def aws_region
79
+ config["aws_region"] || PgHero.config["aws_region"] || ENV["PGHERO_REGION"] || ENV["AWS_REGION"] || (defined?(Aws) && Aws.config[:region]) || "us-east-1"
80
+ end
81
+
82
+ # environment variable is only used if no config file
83
+ def aws_db_instance_identifier
84
+ @aws_db_instance_identifier ||= config["aws_db_instance_identifier"] || config["db_instance_identifier"]
85
+ end
86
+
87
+ # environment variable is only used if no config file
88
+ def gcp_database_id
89
+ @gcp_database_id ||= config["gcp_database_id"]
90
+ end
91
+
92
+ # environment variable is only used if no config file
93
+ def azure_resource_id
94
+ @azure_resource_id ||= config["azure_resource_id"]
95
+ end
96
+
97
+ # must check keys for booleans
98
+ def filter_data
99
+ unless defined?(@filter_data)
100
+ @filter_data =
101
+ if config.key?("filter_data")
102
+ config["filter_data"]
103
+ elsif PgHero.config.key?("filter_data")
104
+ PgHero.config.key?("filter_data")
105
+ else
106
+ PgHero.filter_data
107
+ end
108
+
109
+ if @filter_data
110
+ begin
111
+ require "pg_query"
112
+ rescue LoadError
113
+ raise Error, "pg_query required for filter_data"
114
+ end
115
+ end
116
+ end
117
+
118
+ @filter_data
119
+ end
120
+
121
+ # TODO remove in next major version
122
+ alias_method :access_key_id, :aws_access_key_id
123
+ alias_method :secret_access_key, :aws_secret_access_key
124
+ alias_method :region, :aws_region
125
+ alias_method :db_instance_identifier, :aws_db_instance_identifier
126
+
127
+ private
128
+
129
+ # check adapter lazily
130
+ def connection_model
131
+ unless @adapter_checked
132
+ # rough check for Postgres adapter
133
+ # keep this message generic so it's useful
134
+ # when empty url set in Docker image pghero.yml
135
+ unless @connection_model.connection.adapter_name =~ /postg/i
136
+ raise Error, "Invalid connection URL"
137
+ end
138
+ @adapter_checked = true
139
+ end
140
+
141
+ @connection_model
142
+ end
143
+
144
+ # just return the model
145
+ # do not start a connection
146
+ def build_connection_model
147
+ url = config["url"]
148
+
149
+ # resolve spec
150
+ if !url && config["spec"]
151
+ raise Error, "Spec requires Rails 6+" unless PgHero.spec_supported?
152
+ config_options = {env_name: PgHero.env, PgHero.spec_name_key => config["spec"], include_replicas: true}
153
+ resolved = ActiveRecord::Base.configurations.configs_for(**config_options)
154
+ raise Error, "Spec not found: #{config["spec"]}" unless resolved
155
+ url = ActiveRecord::VERSION::STRING.to_f >= 6.1 ? resolved.configuration_hash : resolved.config
156
+ end
157
+
158
+ url = url.dup
159
+
160
+ Class.new(PgHero::Connection) do
161
+ def self.name
162
+ "PgHero::Connection::Database#{object_id}"
163
+ end
164
+
165
+ case url
166
+ when String
167
+ url = "#{url}#{url.include?("?") ? "&" : "?"}connect_timeout=5" unless url.include?("connect_timeout=")
168
+ when Hash
169
+ url[:connect_timeout] ||= 5
170
+ end
171
+ establish_connection url if url
172
+ end
173
+ end
174
+ end
175
+ end