arfi 0.5.0 → 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.
- checksums.yaml +4 -4
- data/.env.sample +2 -0
- data/.rspec +1 -0
- data/.rubocop.yml +8 -1
- data/.rubocop_todo.yml +12 -0
- data/CHANGELOG.md +94 -0
- data/CODE_OF_CONDUCT.md +5 -1
- data/CONTRIBUTING.md +26 -0
- data/README.md +327 -118
- data/SECURITY.md +17 -0
- data/Steepfile +1 -29
- data/compose.yml +33 -0
- data/docscribe.yml +92 -0
- data/gemfiles/rails_6_0.gemfile +11 -0
- data/gemfiles/rails_6_1.gemfile +11 -0
- data/gemfiles/rails_7_0.gemfile +9 -0
- data/gemfiles/rails_7_1.gemfile +10 -0
- data/gemfiles/rails_7_2.gemfile +10 -0
- data/gemfiles/rails_8_0.gemfile +10 -0
- data/gemfiles/rails_8_1.gemfile +10 -0
- data/lib/arfi/cli.rb +24 -9
- data/lib/arfi/commands/f_idx.rb +5 -230
- data/lib/arfi/commands/functions.rb +128 -0
- data/lib/arfi/commands/functions_candidates.rb +133 -0
- data/lib/arfi/commands/functions_creation.rb +157 -0
- data/lib/arfi/commands/functions_helpers.rb +181 -0
- data/lib/arfi/commands/functions_paths.rb +131 -0
- data/lib/arfi/commands/functions_rendering.rb +137 -0
- data/lib/arfi/commands/init.rb +88 -0
- data/lib/arfi/commands/project.rb +5 -51
- data/lib/arfi/errors.rb +15 -3
- data/lib/arfi/extensions/active_record/base.rb +33 -23
- data/lib/arfi/extensions/active_record/connection_adapters/postgresql/database_statements.rb +159 -24
- data/lib/arfi/sql_function_loader.rb +291 -88
- data/lib/arfi/tasks/db.rake +60 -18
- data/lib/arfi/version.rb +1 -1
- data/lib/arfi.rb +2 -0
- data/rbs_collection.lock.yaml +93 -61
- data/sig/compat/active_record_base_compat.rbs +17 -0
- data/sig/compat/thor_dsl.rbs +8 -0
- data/sig/lib/arfi/commands/f_idx.rbs +5 -103
- data/sig/lib/arfi/commands/functions.rbs +99 -0
- data/sig/lib/arfi/commands/functions_candidates.rbs +25 -0
- data/sig/lib/arfi/commands/functions_creation.rbs +29 -0
- data/sig/lib/arfi/commands/functions_helpers.rbs +41 -0
- data/sig/lib/arfi/commands/functions_paths.rbs +25 -0
- data/sig/lib/arfi/commands/functions_rendering.rbs +25 -0
- data/sig/lib/arfi/commands/init.rbs +22 -0
- data/sig/lib/arfi/commands/project.rbs +5 -24
- data/sig/lib/arfi/extensions/active_record/base.rbs +5 -10
- data/sig/lib/arfi/extensions/active_record/connection_adapters/postgresql/database_statements.rbs +28 -0
- data/sig/lib/arfi/sql_function_loader.rbs +45 -87
- data/sig/lib/arfi/tasks/db.rbs +3 -0
- data/sig/lib/arfi/version.rbs +1 -1
- metadata +125 -14
|
@@ -1,152 +1,355 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Arfi
|
|
4
|
-
#
|
|
4
|
+
# Loads user-defined SQL functions into the database by executing SQL files located under `db/functions`.
|
|
5
|
+
#
|
|
6
|
+
# Supported directory layout (Option B: explicit public):
|
|
7
|
+
#
|
|
8
|
+
# db/functions/public/*.sql (generic public)
|
|
9
|
+
# db/functions/postgresql/public/*.sql (postgres public)
|
|
10
|
+
# db/functions/postgresql/<schema>/*.sql (postgres schema)
|
|
11
|
+
# db/functions/mysql/public/*.sql (mysql/trilogy public)
|
|
12
|
+
#
|
|
13
|
+
# Backward-compatible legacy aliases:
|
|
14
|
+
#
|
|
15
|
+
# db/functions/*.sql (generic public legacy)
|
|
16
|
+
# db/functions/postgresql/*.sql (postgres public legacy)
|
|
17
|
+
# db/functions/mysql/*.sql (mysql public legacy)
|
|
18
|
+
#
|
|
19
|
+
# Rules:
|
|
20
|
+
# - underscore-prefixed files are ignored (_shared.sql)
|
|
21
|
+
# - adapter-specific overrides generic when schema+filename matches
|
|
22
|
+
#
|
|
23
|
+
# @api public
|
|
5
24
|
class SqlFunctionLoader
|
|
6
25
|
class << self
|
|
7
|
-
#
|
|
26
|
+
# Load all SQL function files into the database.
|
|
8
27
|
#
|
|
9
|
-
#
|
|
28
|
+
# Handles both single-DB and multi-DB setups. Uses the given connection or infers it.
|
|
10
29
|
#
|
|
11
|
-
# @param
|
|
12
|
-
# @
|
|
13
|
-
# @
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
30
|
+
# @param [String?] task_name Optional task name for logging (e.g. 'db:migrate')
|
|
31
|
+
# @param [ActiveRecord::ConnectionAdapters::AbstractAdapter?] connection Specific connection to load into
|
|
32
|
+
# @param [Boolean] clear_active_connections Whether to clear connections after loading
|
|
33
|
+
# @param [Boolean] verbose Whether to log each loaded file
|
|
34
|
+
# @return [void]
|
|
35
|
+
def load!(task_name: nil, connection: nil, clear_active_connections: true, verbose: true)
|
|
36
|
+
task_short = task_name ? task_name[/([^:]+$)/] : nil
|
|
37
|
+
conn = connection || default_connection
|
|
38
|
+
|
|
39
|
+
raise_unless_supported_adapter(conn)
|
|
17
40
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
41
|
+
if connection.nil? && multi_db? && task_name.nil?
|
|
42
|
+
populate_multiple_db(verbose: verbose, task_name: task_short)
|
|
43
|
+
else
|
|
44
|
+
populate_db(conn, verbose: verbose, task_name: task_short)
|
|
45
|
+
end
|
|
46
|
+
ensure
|
|
47
|
+
clear_active_connections_if_needed(clear_active_connections)
|
|
21
48
|
end
|
|
22
49
|
|
|
23
50
|
private
|
|
24
51
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
# +Arfi::SqlFunctionLoader#raise_unless_supported_adapter+ -> void
|
|
28
|
-
#
|
|
29
|
-
# Checks if the database adapter is supported.
|
|
52
|
+
# Raise unless the connection adapter is one of the supported types.
|
|
30
53
|
#
|
|
31
|
-
# @!visibility private
|
|
32
54
|
# @private
|
|
55
|
+
# @param [ActiveRecord::ConnectionAdapters::AbstractAdapter] conn Database connection
|
|
56
|
+
# @raise [Arfi::Errors::AdapterNotSupported] If adapter is not PostgreSQL, Mysql2, or Trilogy
|
|
33
57
|
# @return [void]
|
|
34
|
-
#
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
58
|
+
def raise_unless_supported_adapter(conn) # rubocop:disable SortedMethodsByCall/Waterfall
|
|
59
|
+
allowed = %w[
|
|
60
|
+
ActiveRecord::ConnectionAdapters::PostgreSQLAdapter
|
|
61
|
+
ActiveRecord::ConnectionAdapters::Mysql2Adapter
|
|
62
|
+
ActiveRecord::ConnectionAdapters::TrilogyAdapter
|
|
63
|
+
].freeze
|
|
39
64
|
|
|
40
|
-
raise Arfi::Errors::AdapterNotSupported
|
|
65
|
+
raise Arfi::Errors::AdapterNotSupported unless allowed.include?(conn.class.name)
|
|
41
66
|
end
|
|
42
67
|
|
|
43
|
-
#
|
|
68
|
+
# Check whether the Rails app has multiple database configurations for the current environment.
|
|
44
69
|
#
|
|
45
|
-
# Loads user defined SQL functions into database. This conditional branch was written this way because if we
|
|
46
|
-
# call db:migrate:db_name, then task_name will not be nil, but it will be zero if we call db:migrate. Then we
|
|
47
|
-
# check that the application has been configured to work with multiple databases in order to populate all
|
|
48
|
-
# databases, and only after this check can we populate the database in case the db:migrate (or any other) task
|
|
49
|
-
# has been called for configuration with a single database. Go to `lib/arfi/tasks/db.rake` for additional info.
|
|
50
|
-
#
|
|
51
|
-
# @!visibility private
|
|
52
70
|
# @private
|
|
53
|
-
# @return [
|
|
54
|
-
def handle_db_population
|
|
55
|
-
if task_name || (task_name && multi_db?) || task_name.nil?
|
|
56
|
-
populate_db
|
|
57
|
-
elsif multi_db?
|
|
58
|
-
populate_multiple_db
|
|
59
|
-
end
|
|
60
|
-
end
|
|
61
|
-
|
|
62
|
-
# +Arfi::SqlFunctionLoader#multi_db?+ -> Boolean
|
|
63
|
-
#
|
|
64
|
-
# Checks if the application has been configured to work with multiple databases.
|
|
65
|
-
#
|
|
66
|
-
# @return [Boolean]
|
|
71
|
+
# @return [Boolean] Whether multi-DB is configured
|
|
67
72
|
def multi_db?
|
|
68
73
|
ActiveRecord::Base.configurations.configurations.count { _1.env_name == Rails.env } > 1 # steep:ignore NoMethod
|
|
69
74
|
end
|
|
70
75
|
|
|
71
|
-
#
|
|
76
|
+
# Load functions into all databases in a multi-DB setup.
|
|
72
77
|
#
|
|
73
|
-
#
|
|
78
|
+
# Saves the original connection and restores it after iterating all configs,
|
|
79
|
+
# matching the pattern used in `run_with_connection_switch` (db.rake).
|
|
74
80
|
#
|
|
75
|
-
# @!visibility private
|
|
76
81
|
# @private
|
|
82
|
+
# @param [Boolean] verbose Whether to log each loaded file
|
|
83
|
+
# @param [String?] task_name Optional task name for logging
|
|
77
84
|
# @return [void]
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
def populate_multiple_db
|
|
85
|
+
def populate_multiple_db(verbose:, task_name: nil)
|
|
86
|
+
original = current_db_config
|
|
81
87
|
# steep:ignore:start
|
|
82
88
|
ActiveRecord::Base.configurations.configurations.select { _1.env_name == Rails.env }.each do |config|
|
|
83
89
|
ActiveRecord::Base.establish_connection(config)
|
|
84
|
-
populate_db
|
|
90
|
+
populate_db(default_connection, verbose: verbose, task_name: task_name)
|
|
85
91
|
end
|
|
86
92
|
# steep:ignore:end
|
|
93
|
+
ensure
|
|
94
|
+
ActiveRecord::Base.establish_connection(original) if original
|
|
87
95
|
end
|
|
88
96
|
|
|
89
|
-
#
|
|
97
|
+
# Get the default database connection, handling Rails version differences.
|
|
90
98
|
#
|
|
91
|
-
#
|
|
99
|
+
# @private
|
|
100
|
+
# @return [ActiveRecord::ConnectionAdapters::AbstractAdapter] Default connection
|
|
101
|
+
def default_connection
|
|
102
|
+
if Rails::VERSION::MAJOR < 7 || (Rails::VERSION::MAJOR == 7 && Rails::VERSION::MINOR < 2)
|
|
103
|
+
ActiveRecord::Base.connection
|
|
104
|
+
else
|
|
105
|
+
ActiveRecord::Base.lease_connection
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# Get the current database config, or nil if no connection is established.
|
|
110
|
+
#
|
|
111
|
+
# @private
|
|
112
|
+
# @raise [StandardError]
|
|
113
|
+
# @return [ActiveRecord::DatabaseConfig, nil] Current database config, or nil if no connection is established
|
|
114
|
+
def current_db_config
|
|
115
|
+
ActiveRecord::Base.connection_db_config # steep:ignore NoMethod
|
|
116
|
+
rescue StandardError
|
|
117
|
+
nil
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# Load SQL function files into a single database connection.
|
|
92
121
|
#
|
|
93
|
-
# @!visibility private
|
|
94
122
|
# @private
|
|
123
|
+
# @param [ActiveRecord::ConnectionAdapters::AbstractAdapter] conn Database connection
|
|
124
|
+
# @param [Boolean] verbose Whether to log each loaded file
|
|
125
|
+
# @param [String?] task_name Optional task name for logging
|
|
95
126
|
# @return [void]
|
|
96
|
-
def populate_db
|
|
97
|
-
|
|
98
|
-
|
|
127
|
+
def populate_db(conn, verbose:, task_name:)
|
|
128
|
+
files = sql_files(conn)
|
|
129
|
+
if files.empty?
|
|
130
|
+
log(conn, "No SQL files found for adapter #{conn.class}. Skipping db population with ARFI")
|
|
131
|
+
return
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
files.each { |file| load_sql_file(conn, file, verbose, task_name) }
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
# Clear all active database connections if requested, handling Rails version differences.
|
|
138
|
+
#
|
|
139
|
+
# @private
|
|
140
|
+
# @param [Boolean] clear Whether to clear connections
|
|
141
|
+
# @return [void]
|
|
142
|
+
def clear_active_connections_if_needed(clear)
|
|
143
|
+
return unless clear && defined?(ActiveRecord::Base)
|
|
144
|
+
|
|
145
|
+
if ActiveRecord::Base.respond_to?(:connection_handler) &&
|
|
146
|
+
ActiveRecord::Base.connection_handler.respond_to?(:clear_active_connections!)
|
|
147
|
+
ActiveRecord::Base.connection_handler.clear_active_connections!
|
|
148
|
+
elsif ActiveRecord::Base.respond_to?(:clear_active_connections!)
|
|
149
|
+
ActiveRecord::Base.clear_active_connections!
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
# Load a single SQL file into the database, wrapping errors with file context.
|
|
154
|
+
#
|
|
155
|
+
# @private
|
|
156
|
+
# @param [ActiveRecord::ConnectionAdapters::AbstractAdapter] conn Database connection
|
|
157
|
+
# @param [Pathname, String] file Path to the SQL file
|
|
158
|
+
# @param [Boolean] verbose Whether to log the loaded file
|
|
159
|
+
# @param [String?] task_name Optional task name for logging
|
|
160
|
+
# @raise [StandardError] If the SQL execution fails (re-raised with file context)
|
|
161
|
+
# @return [void]
|
|
162
|
+
def load_sql_file(conn, file, verbose, task_name)
|
|
163
|
+
sql = File.read(file.to_s).strip
|
|
164
|
+
return if sql.empty?
|
|
165
|
+
|
|
166
|
+
begin
|
|
99
167
|
conn.execute(sql)
|
|
100
|
-
|
|
168
|
+
rescue StandardError => e
|
|
169
|
+
raise e.class, "#{e.message}\n[ARFI] while loading #{file}", e.backtrace
|
|
101
170
|
end
|
|
171
|
+
return unless verbose
|
|
172
|
+
|
|
173
|
+
log_sql_load(conn, file, task_name)
|
|
102
174
|
end
|
|
103
175
|
|
|
104
|
-
#
|
|
176
|
+
# Log that a SQL file was successfully loaded into the database.
|
|
105
177
|
#
|
|
106
|
-
#
|
|
178
|
+
# @private
|
|
179
|
+
# @param [ActiveRecord::ConnectionAdapters::AbstractAdapter] conn Database connection
|
|
180
|
+
# @param [Pathname, String] file Path to the loaded SQL file
|
|
181
|
+
# @param [String?] task_name Optional task name for logging
|
|
182
|
+
# @return [void]
|
|
183
|
+
def log_sql_load(conn, file, task_name)
|
|
184
|
+
label = "[ARFI] Loaded: #{File.basename(file)} into #{safe_db_env(conn)} #{safe_db_name(conn)}"
|
|
185
|
+
label += " (#{task_name})" if task_name
|
|
186
|
+
log(conn, label)
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
# Get the database environment name safely, returning empty string on error.
|
|
107
190
|
#
|
|
108
|
-
# @!visibility private
|
|
109
191
|
# @private
|
|
110
|
-
# @
|
|
111
|
-
# @
|
|
112
|
-
# @
|
|
113
|
-
# @
|
|
114
|
-
def
|
|
115
|
-
|
|
116
|
-
|
|
192
|
+
# @param [ActiveRecord::ConnectionAdapters::AbstractAdapter] conn Database connection
|
|
193
|
+
# @raise [StandardError]
|
|
194
|
+
# @return [String] Environment name, or empty string on error
|
|
195
|
+
# @return [String] if StandardError
|
|
196
|
+
def safe_db_env(conn)
|
|
197
|
+
conn.pool&.db_config&.env_name.to_s
|
|
198
|
+
rescue StandardError
|
|
199
|
+
''
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
# Get the database name safely, returning empty string on error.
|
|
203
|
+
#
|
|
204
|
+
# @private
|
|
205
|
+
# @param [ActiveRecord::ConnectionAdapters::AbstractAdapter] conn Database connection
|
|
206
|
+
# @raise [StandardError]
|
|
207
|
+
# @return [String] Database name, or empty string on error
|
|
208
|
+
# @return [String] if StandardError
|
|
209
|
+
def safe_db_name(conn)
|
|
210
|
+
conn.pool&.db_config&.name.to_s
|
|
211
|
+
rescue StandardError
|
|
212
|
+
''
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
# Log a message via Rails.logger or stdout.
|
|
216
|
+
#
|
|
217
|
+
# @private
|
|
218
|
+
# @param [ActiveRecord::ConnectionAdapters::AbstractAdapter] _conn Database connection (unused)
|
|
219
|
+
# @param [String] msg Message to log
|
|
220
|
+
# @return [void]
|
|
221
|
+
def log(_conn, msg)
|
|
222
|
+
if defined?(Rails) && Rails.respond_to?(:logger) && Rails.logger
|
|
223
|
+
Rails.logger.info(msg)
|
|
117
224
|
else
|
|
118
|
-
|
|
225
|
+
$stdout.puts(msg)
|
|
119
226
|
end
|
|
120
227
|
end
|
|
121
228
|
|
|
122
|
-
#
|
|
229
|
+
# Collect all SQL files to load for the given connection, applying override resolution.
|
|
123
230
|
#
|
|
124
|
-
#
|
|
231
|
+
# @private
|
|
232
|
+
# @param [ActiveRecord::ConnectionAdapters::AbstractAdapter] conn Database connection
|
|
233
|
+
# @return [Array<String>] Ordered list of SQL file paths
|
|
234
|
+
def sql_files(conn)
|
|
235
|
+
root = Rails.root.join('db', 'functions')
|
|
236
|
+
return [] unless root.directory?
|
|
237
|
+
|
|
238
|
+
generic = [
|
|
239
|
+
collect_sql(glob: root.join('*.sql'), schema: 'public', priority: 1),
|
|
240
|
+
collect_sql(glob: root.join('public', '*.sql'), schema: 'public', priority: 2)
|
|
241
|
+
].flatten
|
|
242
|
+
|
|
243
|
+
adapter_root = adapter_root_for(conn, root)
|
|
244
|
+
return finalize_items(generic) unless adapter_root&.directory?
|
|
245
|
+
|
|
246
|
+
finalize_items(generic + collect_adapter_sql_files(conn, adapter_root))
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
# Collect adapter-specific SQL files, dispatching to the correct strategy based on adapter type.
|
|
125
250
|
#
|
|
126
|
-
# @!visibility private
|
|
127
251
|
# @private
|
|
128
|
-
# @
|
|
129
|
-
# @
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
when ActiveRecord::ConnectionAdapters::
|
|
135
|
-
|
|
252
|
+
# @param [ActiveRecord::ConnectionAdapters::AbstractAdapter] conn Database connection
|
|
253
|
+
# @param [Pathname] adapter_root Adapter root directory (e.g. db/functions/postgresql)
|
|
254
|
+
# @raise [Arfi::Errors::AdapterNotSupported] If adapter is not supported
|
|
255
|
+
# @return [Array<Arfi::sql_file_item>] List of SQL file items
|
|
256
|
+
def collect_adapter_sql_files(conn, adapter_root)
|
|
257
|
+
case conn.class.name
|
|
258
|
+
when 'ActiveRecord::ConnectionAdapters::PostgreSQLAdapter'
|
|
259
|
+
collect_postgresql_sql_files(adapter_root)
|
|
260
|
+
when 'ActiveRecord::ConnectionAdapters::Mysql2Adapter', 'ActiveRecord::ConnectionAdapters::TrilogyAdapter'
|
|
261
|
+
collect_adapter_public_sql_files(adapter_root)
|
|
136
262
|
else
|
|
137
263
|
raise Arfi::Errors::AdapterNotSupported
|
|
138
264
|
end
|
|
139
265
|
end
|
|
140
266
|
|
|
141
|
-
#
|
|
267
|
+
# Collect SQL files for PostgreSQL, including schema subdirectories.
|
|
268
|
+
#
|
|
269
|
+
# @private
|
|
270
|
+
# @param [Pathname] adapter_root PostgreSQL adapter root directory
|
|
271
|
+
# @return [Array<Arfi::sql_file_item>] List of SQL file items
|
|
272
|
+
def collect_postgresql_sql_files(adapter_root)
|
|
273
|
+
items = collect_adapter_public_sql_files(adapter_root)
|
|
274
|
+
|
|
275
|
+
Dir.children(adapter_root).sort.each do |child|
|
|
276
|
+
next if child.start_with?('_')
|
|
277
|
+
next if child == 'public'
|
|
278
|
+
|
|
279
|
+
dir = adapter_root.join(child)
|
|
280
|
+
next unless dir.directory?
|
|
281
|
+
|
|
282
|
+
items.concat collect_sql(glob: dir.join('*.sql'), schema: child, priority: 10)
|
|
283
|
+
end
|
|
284
|
+
|
|
285
|
+
items
|
|
286
|
+
end
|
|
287
|
+
|
|
288
|
+
# Collect SQL files from the adapter's public directory (legacy + explicit).
|
|
289
|
+
#
|
|
290
|
+
# @private
|
|
291
|
+
# @param [Pathname] adapter_root Adapter root directory
|
|
292
|
+
# @return [Array<Arfi::sql_file_item>] List of SQL file items
|
|
293
|
+
def collect_adapter_public_sql_files(adapter_root)
|
|
294
|
+
items = [] # steep:ignore
|
|
295
|
+
items.concat collect_sql(glob: adapter_root.join('*.sql'), schema: 'public', priority: 8)
|
|
296
|
+
items.concat collect_sql(glob: adapter_root.join('public', '*.sql'), schema: 'public', priority: 9)
|
|
297
|
+
items
|
|
298
|
+
end
|
|
299
|
+
|
|
300
|
+
# Collect SQL files matching a glob pattern, skipping underscore-prefixed files.
|
|
142
301
|
#
|
|
143
|
-
#
|
|
302
|
+
# @private
|
|
303
|
+
# @param [Pathname, String] glob Glob pattern to match SQL files
|
|
304
|
+
# @param [String] schema Schema name to assign to matched files
|
|
305
|
+
# @param [Integer] priority Priority for override resolution (higher = preferred)
|
|
306
|
+
# @return [Array<Arfi::sql_file_item>] List of SQL file items
|
|
307
|
+
def collect_sql(glob:, schema:, priority:)
|
|
308
|
+
Dir.glob(glob.to_s).filter_map do |path|
|
|
309
|
+
base = File.basename(path)
|
|
310
|
+
next if base.start_with?('_')
|
|
311
|
+
|
|
312
|
+
{ schema: schema, base: base, path: path, priority: priority }
|
|
313
|
+
end
|
|
314
|
+
end
|
|
315
|
+
|
|
316
|
+
# Finalize the file list by selecting the highest-priority item per schema/filename key.
|
|
144
317
|
#
|
|
145
|
-
# @!visibility private
|
|
146
318
|
# @private
|
|
147
|
-
# @
|
|
148
|
-
|
|
149
|
-
|
|
319
|
+
# @param [Array<Arfi::sql_file_item>] items All collected SQL file items
|
|
320
|
+
# @return [Array<String>] Ordered list of chosen file paths
|
|
321
|
+
def finalize_items(items)
|
|
322
|
+
# @type var chosen: Hash[String, { schema: String, base: String, path: String, priority: Integer }]
|
|
323
|
+
chosen = {}
|
|
324
|
+
|
|
325
|
+
items.each do |item|
|
|
326
|
+
key = "#{item[:schema]}/#{item[:base]}"
|
|
327
|
+
prev = chosen[key]
|
|
328
|
+
chosen[key] = item if prev.nil? || item[:priority] > prev[:priority]
|
|
329
|
+
end
|
|
330
|
+
|
|
331
|
+
chosen.values
|
|
332
|
+
.sort_by { |item| [item[:schema], item[:base]] }
|
|
333
|
+
.map { |item| item[:path] }
|
|
334
|
+
end
|
|
335
|
+
|
|
336
|
+
# Resolve the adapter-specific root directory for the given connection.
|
|
337
|
+
#
|
|
338
|
+
# @private
|
|
339
|
+
# @param [ActiveRecord::ConnectionAdapters::AbstractAdapter] conn Database connection
|
|
340
|
+
# @param [Pathname] root Project root (db/functions)
|
|
341
|
+
# @raise [Arfi::Errors::AdapterNotSupported] If adapter is not supported
|
|
342
|
+
# @return [Pathname] Adapter root directory
|
|
343
|
+
def adapter_root_for(conn, root)
|
|
344
|
+
case conn.class.name
|
|
345
|
+
when 'ActiveRecord::ConnectionAdapters::PostgreSQLAdapter'
|
|
346
|
+
root.join('postgresql')
|
|
347
|
+
when 'ActiveRecord::ConnectionAdapters::Mysql2Adapter',
|
|
348
|
+
'ActiveRecord::ConnectionAdapters::TrilogyAdapter'
|
|
349
|
+
root.join('mysql')
|
|
350
|
+
else
|
|
351
|
+
raise Arfi::Errors::AdapterNotSupported
|
|
352
|
+
end
|
|
150
353
|
end
|
|
151
354
|
end
|
|
152
355
|
end
|
data/lib/arfi/tasks/db.rake
CHANGED
|
@@ -1,38 +1,80 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require 'rake'
|
|
4
|
+
require 'active_record'
|
|
4
5
|
require 'arfi/sql_function_loader'
|
|
5
6
|
|
|
6
7
|
namespace :_db do
|
|
7
8
|
task :arfi_enhance do
|
|
8
|
-
|
|
9
|
+
# For non-suffixed tasks (db:migrate / db:prepare etc),
|
|
10
|
+
# loader will populate all DBs when multi-db and task_name is nil.
|
|
11
|
+
Arfi::SqlFunctionLoader.load!(verbose: true)
|
|
9
12
|
end
|
|
10
13
|
end
|
|
11
14
|
|
|
12
15
|
Rake::Task.define_task(:environment) unless Rake::Task.task_defined?(:environment)
|
|
13
16
|
|
|
14
|
-
#
|
|
15
|
-
%w[
|
|
17
|
+
# Enhance common single-db tasks (and multi-db "default" tasks)
|
|
18
|
+
%w[
|
|
19
|
+
db:migrate
|
|
20
|
+
db:schema:load
|
|
21
|
+
db:setup
|
|
22
|
+
db:prepare
|
|
23
|
+
db:test:prepare
|
|
24
|
+
].each do |task|
|
|
16
25
|
Rake::Task[task].enhance(['_db:arfi_enhance']) if Rake::Task.task_defined?(task)
|
|
17
26
|
end
|
|
18
27
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
task.
|
|
28
|
+
def define_arfi_enhance_for(task, db_name)
|
|
29
|
+
Rake::Task.define_task("_db:arfi_enhance:#{task.name}") do
|
|
30
|
+
run_arfi_loader_for(task, arfi_db_config_for(db_name))
|
|
31
|
+
end
|
|
32
|
+
task.enhance(["_db:arfi_enhance:#{task.name}"])
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def run_arfi_loader_for(task, config_hash)
|
|
36
|
+
if config_hash
|
|
37
|
+
run_with_connection_switch(task, config_hash)
|
|
38
|
+
else
|
|
39
|
+
run_arfi_loader(task)
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def run_with_connection_switch(task, config_hash)
|
|
44
|
+
original = current_db_config
|
|
45
|
+
ActiveRecord::Base.establish_connection(config_hash)
|
|
46
|
+
run_arfi_loader(task)
|
|
47
|
+
ensure
|
|
48
|
+
ActiveRecord::Base.establish_connection(original) if original
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def current_db_config
|
|
52
|
+
ActiveRecord::Base.connection_db_config
|
|
53
|
+
rescue StandardError
|
|
54
|
+
nil
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def run_arfi_loader(task)
|
|
58
|
+
Arfi::SqlFunctionLoader.load!(
|
|
59
|
+
task_name: task.name,
|
|
60
|
+
connection: ActiveRecord::Base.connection,
|
|
61
|
+
verbose: true
|
|
62
|
+
)
|
|
24
63
|
end
|
|
25
|
-
|
|
26
|
-
|
|
64
|
+
|
|
65
|
+
def arfi_db_config_for(name)
|
|
66
|
+
return unless ActiveRecord::Base.respond_to?(:configurations) && ActiveRecord::Base.configurations
|
|
67
|
+
|
|
68
|
+
list = ActiveRecord::Base.configurations.configs_for(env_name: Rails.env, name: name)
|
|
69
|
+
cfg = Array(list).first
|
|
70
|
+
cfg&.configuration_hash
|
|
27
71
|
end
|
|
28
72
|
|
|
29
|
-
#
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
73
|
+
# Enhance suffixed tasks: db:migrate:<db>, db:schema:load:<db>, etc.
|
|
74
|
+
pattern = /^(db:migrate:|db:schema:load:|db:setup:|db:prepare:|db:test:prepare:)([^:]+)$/
|
|
75
|
+
possible_tasks = Rake::Task.tasks.select { |t| t.name.match?(pattern) }
|
|
76
|
+
|
|
33
77
|
possible_tasks.each do |task|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
end
|
|
37
|
-
task.enhance(["_db:arfi_enhance:#{task.name}"])
|
|
78
|
+
db_name = task.name.match(pattern)[2]
|
|
79
|
+
define_arfi_enhance_for(task, db_name)
|
|
38
80
|
end
|
data/lib/arfi/version.rb
CHANGED