pghero_fork 2.7.3

Sign up to get free protection for your applications and to get access to all the features.
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