after_migrate 0.1.1 → 0.2.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/.ruby-version +1 -1
- data/CHANGELOG.md +15 -0
- data/lib/after_migrate/adapters/sql.rb +7 -7
- data/lib/after_migrate/collector.rb +5 -15
- data/lib/after_migrate/executor.rb +16 -17
- data/lib/after_migrate/railtie.rb +11 -1
- data/lib/after_migrate/version.rb +1 -1
- data/lib/after_migrate.rb +26 -2
- metadata +3 -7
- data/lib/after_migrate/current.rb +0 -13
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: a13db746f7bfcbcb7367f028493efd2b464e564b32f09be5debb62da1326f8bf
|
|
4
|
+
data.tar.gz: b1f0db683bb9b12ed407f14f3f1b1c0baba0a80cfd445c6182b9bd5ad7378821
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: '085b3be36d4031981294d5a5ddc929aae3d781c9d1c3d8446f4508f7c7c526064c8b6529b68237e3e3517885f22251b3db071839dae1bcdf8df654d1e79b2e70'
|
|
7
|
+
data.tar.gz: 212614cb1145bbe3f22d9b005e6524af133b5d50f659ca8da2efff0d6416efd7ff90fccf39575719d4237df08029a5dcab6b8ef8dfd5072027bce902bc2b3bd8
|
data/.ruby-version
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
3.4.
|
|
1
|
+
3.4.9
|
data/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,21 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## [0.2.0] - 2026-04-28
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
- `defer` configuration option (default: `true`) — when enabled, rake task enhancements only collect touched tables; they do not run maintenance automatically
|
|
9
|
+
- `AfterMigrate.run!` public API to explicitly trigger maintenance on all collected tables across all schemas, then clear the store — intended to be called once after all tenant migrations complete in multi-tenant setups
|
|
10
|
+
- `after_migrate:run` rake task as a convenience wrapper around `AfterMigrate.run!`
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
- **Breaking**: replaced `AfterMigrate::Current` (`ActiveSupport::CurrentAttributes`) with a module-level persistent store (`Concurrent::Map`). The store accumulates touched tables across multiple rake task invocations and only resets when `AfterMigrate.run!` is called.
|
|
14
|
+
- Fixed `all_tables` analyze mode - previously iterated `.each_value` (yielding `Concurrent::Set` objects) instead of schema names, producing incorrect SQL queries.
|
|
15
|
+
|
|
16
|
+
### Removed
|
|
17
|
+
- `AfterMigrate::Current` - no longer needed. If you referenced this class directly, use `AfterMigrate.affected_tables` instead.
|
|
18
|
+
- `Executor.call(reset:)` parameter - the store is always cleared after `run!`; pass `schema:` to scope execution to a single schema.
|
|
19
|
+
|
|
5
20
|
## [0.1.0] - 2025-04-05
|
|
6
21
|
|
|
7
22
|
### Added
|
|
@@ -8,13 +8,13 @@ module AfterMigrate
|
|
|
8
8
|
/x
|
|
9
9
|
|
|
10
10
|
PATTERNS = {
|
|
11
|
-
update: /update\s+(?:only\s+)?(#{IDENT})(?!\
|
|
12
|
-
insert: /insert\s+into\s+(#{IDENT})(?!\
|
|
13
|
-
delete: /delete\s+from\s+(#{IDENT})(?!\
|
|
14
|
-
drop_table: /drop\s+table\s+(?:if\s+exists\s+)?(#{IDENT})(?!\
|
|
15
|
-
alter_table: /alter\s+table\s+(#{IDENT})(?!\
|
|
16
|
-
create_table: /create\s+table\s+(?:if\s+not\s+exists\s+)?(#{IDENT})(?!\
|
|
17
|
-
from_join: /(?:from|join)\s+(#{IDENT})(?!\
|
|
11
|
+
update: /update\s+(?:only\s+)?(#{IDENT})(?!\()/ix,
|
|
12
|
+
insert: /insert\s+into\s+(#{IDENT})(?!\()/ix,
|
|
13
|
+
delete: /delete\s+from\s+(#{IDENT})(?!\()/ix,
|
|
14
|
+
drop_table: /drop\s+table\s+(?:if\s+exists\s+)?(#{IDENT})(?!\()/ix,
|
|
15
|
+
alter_table: /alter\s+table\s+(#{IDENT})(?!\()/ix,
|
|
16
|
+
create_table: /create\s+table\s+(?:if\s+not\s+exists\s+)?(#{IDENT})(?!\()/ix,
|
|
17
|
+
from_join: /(?:from|join)\s+(#{IDENT})(?!\()/ix
|
|
18
18
|
}.freeze
|
|
19
19
|
|
|
20
20
|
def parse_tables(sql)
|
|
@@ -13,24 +13,15 @@ module AfterMigrate
|
|
|
13
13
|
return unless sql.match?(/\A\s*(CREATE|ALTER|DROP|INSERT|UPDATE|DELETE|RENAME\s+TABLE|TRUNCATE)/i)
|
|
14
14
|
|
|
15
15
|
table_names = parse_tables(sql)
|
|
16
|
-
schema
|
|
17
|
-
|
|
18
|
-
collect_tables(schema:, table_names:)
|
|
16
|
+
schema = fetch_schema
|
|
17
|
+
AfterMigrate.merge_tables(schema, table_names)
|
|
19
18
|
end
|
|
20
19
|
|
|
21
20
|
private
|
|
22
21
|
|
|
23
|
-
def collect_tables(schema:, table_names:)
|
|
24
|
-
return if table_names.blank?
|
|
25
|
-
|
|
26
|
-
AfterMigrate::Current.affected_tables ||= Hash.new { |h, k| h[k] = Concurrent::Set.new }
|
|
27
|
-
AfterMigrate::Current.affected_tables[schema].merge(table_names)
|
|
28
|
-
end
|
|
29
|
-
|
|
30
22
|
def fetch_schema
|
|
31
23
|
connection = ActiveRecord::Base.connection
|
|
32
|
-
|
|
33
|
-
case adapter
|
|
24
|
+
case connection.adapter_name
|
|
34
25
|
when 'PostgreSQL'
|
|
35
26
|
quoted = connection.schema_search_path.split(',').first
|
|
36
27
|
quoted&.delete('"')
|
|
@@ -39,8 +30,7 @@ module AfterMigrate
|
|
|
39
30
|
|
|
40
31
|
def parse_tables(sql)
|
|
41
32
|
connection = ActiveRecord::Base.connection
|
|
42
|
-
|
|
43
|
-
case adapter
|
|
33
|
+
case connection.adapter_name
|
|
44
34
|
when 'PostgreSQL'
|
|
45
35
|
AfterMigrate::Postgresql.parse_tables(sql)
|
|
46
36
|
when 'SQLite'
|
|
@@ -48,7 +38,7 @@ module AfterMigrate
|
|
|
48
38
|
when 'Mysql2', 'Trilogy'
|
|
49
39
|
AfterMigrate::Mysql.parse_tables(sql)
|
|
50
40
|
else
|
|
51
|
-
AfterMigrate.log("No maintenance implemented for #{
|
|
41
|
+
AfterMigrate.log("No maintenance implemented for #{connection.adapter_name}")
|
|
52
42
|
end
|
|
53
43
|
end
|
|
54
44
|
end
|
|
@@ -11,16 +11,17 @@ module AfterMigrate
|
|
|
11
11
|
|
|
12
12
|
module_function
|
|
13
13
|
|
|
14
|
-
def call(
|
|
15
|
-
|
|
16
|
-
return if
|
|
17
|
-
return run_optimize(schema:, tables: target_tables[schema]) if schema.present?
|
|
14
|
+
def call(schema: nil)
|
|
15
|
+
tables = target_tables
|
|
16
|
+
return if tables.blank?
|
|
18
17
|
|
|
19
|
-
|
|
20
|
-
run_optimize(schema:
|
|
18
|
+
if schema.present?
|
|
19
|
+
run_optimize(schema:, tables: tables[schema]) if tables[schema].present?
|
|
20
|
+
else
|
|
21
|
+
tables.each { |s, t| run_optimize(schema: s, tables: t) }
|
|
21
22
|
end
|
|
22
23
|
ensure
|
|
23
|
-
AfterMigrate
|
|
24
|
+
AfterMigrate.reset!
|
|
24
25
|
end
|
|
25
26
|
|
|
26
27
|
public :call
|
|
@@ -31,14 +32,13 @@ module AfterMigrate
|
|
|
31
32
|
table_names = tables.to_a.sort
|
|
32
33
|
return if table_names.empty?
|
|
33
34
|
|
|
34
|
-
AfterMigrate.log("Migration touched #{table_names.size} table(s): #{table_names.join(', ')}")
|
|
35
|
+
AfterMigrate.log("Migration touched #{table_names.size} table(s) in schema #{schema.inspect}: #{table_names.join(', ')}")
|
|
35
36
|
optimize_tables(schema:, table_names:)
|
|
36
37
|
end
|
|
37
38
|
|
|
38
39
|
def optimize_tables(schema:, table_names:)
|
|
39
40
|
connection = ActiveRecord::Base.connection
|
|
40
|
-
|
|
41
|
-
case adapter
|
|
41
|
+
case connection.adapter_name
|
|
42
42
|
when 'PostgreSQL'
|
|
43
43
|
AfterMigrate::Postgresql.optimize_tables(schema:, table_names:, connection:)
|
|
44
44
|
when 'SQLite'
|
|
@@ -46,27 +46,26 @@ module AfterMigrate
|
|
|
46
46
|
when 'Mysql2', 'Trilogy'
|
|
47
47
|
AfterMigrate::Mysql.optimize_tables(schema:, table_names:, connection:)
|
|
48
48
|
else
|
|
49
|
-
AfterMigrate.log("No maintenance implemented for #{
|
|
49
|
+
AfterMigrate.log("No maintenance implemented for #{connection.adapter_name}")
|
|
50
50
|
end
|
|
51
51
|
end
|
|
52
52
|
|
|
53
53
|
def target_tables
|
|
54
54
|
case AfterMigrate.configuration.analyze
|
|
55
55
|
when 'all_tables'
|
|
56
|
-
AfterMigrate
|
|
57
|
-
all_tables(schema:)
|
|
56
|
+
AfterMigrate.affected_tables.keys.each_with_object({}) do |schema, hash|
|
|
57
|
+
hash[schema] = all_tables(schema:)
|
|
58
58
|
end
|
|
59
59
|
when 'only_affected_tables'
|
|
60
|
-
AfterMigrate
|
|
60
|
+
AfterMigrate.affected_tables
|
|
61
61
|
else
|
|
62
|
-
|
|
62
|
+
{}
|
|
63
63
|
end
|
|
64
64
|
end
|
|
65
65
|
|
|
66
66
|
def all_tables(schema:)
|
|
67
67
|
connection = ActiveRecord::Base.connection
|
|
68
|
-
|
|
69
|
-
case adapter
|
|
68
|
+
case connection.adapter_name
|
|
70
69
|
when 'PostgreSQL'
|
|
71
70
|
AfterMigrate::Postgresql.all_tables(schema:)
|
|
72
71
|
when 'SQLite'
|
|
@@ -12,6 +12,8 @@ module AfterMigrate
|
|
|
12
12
|
AfterMigrate::Collector.call(*args)
|
|
13
13
|
end
|
|
14
14
|
|
|
15
|
+
# Unsubscribe when the app starts serving requests so normal web traffic
|
|
16
|
+
# is never collected.
|
|
15
17
|
app.executor.to_run { ActiveSupport::Notifications.unsubscribe(subscription) }
|
|
16
18
|
end
|
|
17
19
|
|
|
@@ -24,8 +26,16 @@ module AfterMigrate
|
|
|
24
26
|
Rake::Task[task_name].enhance do
|
|
25
27
|
next unless AfterMigrate.configuration.enabled
|
|
26
28
|
next unless AfterMigrate.configuration.rake_tasks_enhanced
|
|
29
|
+
next if AfterMigrate.configuration.defer
|
|
27
30
|
|
|
28
|
-
AfterMigrate
|
|
31
|
+
AfterMigrate.run!
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
namespace :after_migrate do
|
|
36
|
+
desc 'Run database maintenance (ANALYZE/VACUUM) on all tables collected across migrations'
|
|
37
|
+
task run: :environment do
|
|
38
|
+
AfterMigrate.run!
|
|
29
39
|
end
|
|
30
40
|
end
|
|
31
41
|
end
|
data/lib/after_migrate.rb
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require 'after_migrate/version'
|
|
4
|
-
require 'after_migrate/current'
|
|
5
4
|
require 'after_migrate/collector'
|
|
6
5
|
require 'after_migrate/executor'
|
|
7
6
|
require 'after_migrate/railtie'
|
|
8
7
|
|
|
9
8
|
module AfterMigrate
|
|
10
9
|
class Configuration
|
|
11
|
-
attr_accessor :enabled, :verbose, :vacuum, :analyze, :rake_tasks_enhanced
|
|
10
|
+
attr_accessor :enabled, :verbose, :vacuum, :analyze, :rake_tasks_enhanced, :defer
|
|
12
11
|
|
|
13
12
|
def initialize
|
|
14
13
|
@enabled = true
|
|
@@ -16,6 +15,7 @@ module AfterMigrate
|
|
|
16
15
|
@vacuum = true
|
|
17
16
|
@analyze = 'only_affected_tables'
|
|
18
17
|
@rake_tasks_enhanced = true
|
|
18
|
+
@defer = true
|
|
19
19
|
end
|
|
20
20
|
end
|
|
21
21
|
|
|
@@ -31,5 +31,29 @@ module AfterMigrate
|
|
|
31
31
|
def log(msg)
|
|
32
32
|
warn "[after_migrate] #{msg}" if AfterMigrate.configuration.verbose
|
|
33
33
|
end
|
|
34
|
+
|
|
35
|
+
# Persistent cross-migration store: schema_name => Concurrent::Set<table_name>
|
|
36
|
+
def affected_tables
|
|
37
|
+
@affected_tables ||= Concurrent::Map.new
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def merge_tables(schema, table_names)
|
|
41
|
+
return if table_names.blank?
|
|
42
|
+
|
|
43
|
+
set = affected_tables.compute_if_absent(schema) { Concurrent::Set.new }
|
|
44
|
+
set.merge(table_names)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Trigger database maintenance on all collected tables, then clear the store.
|
|
48
|
+
# In multi-tenant setups call this once after all tenant migrations complete.
|
|
49
|
+
def run!(schema: nil)
|
|
50
|
+
return unless configuration.enabled
|
|
51
|
+
|
|
52
|
+
Executor.call(schema:)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def reset!
|
|
56
|
+
@affected_tables = nil
|
|
57
|
+
end
|
|
34
58
|
end
|
|
35
59
|
end
|
metadata
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: after_migrate
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.2.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Nikolay Moskvin
|
|
8
|
-
autorequire:
|
|
9
8
|
bindir: exe
|
|
10
9
|
cert_chain: []
|
|
11
|
-
date:
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
12
11
|
dependencies:
|
|
13
12
|
- !ruby/object:Gem::Dependency
|
|
14
13
|
name: pg_query
|
|
@@ -66,7 +65,6 @@ files:
|
|
|
66
65
|
- lib/after_migrate/adapters/sql.rb
|
|
67
66
|
- lib/after_migrate/adapters/sqlite.rb
|
|
68
67
|
- lib/after_migrate/collector.rb
|
|
69
|
-
- lib/after_migrate/current.rb
|
|
70
68
|
- lib/after_migrate/executor.rb
|
|
71
69
|
- lib/after_migrate/railtie.rb
|
|
72
70
|
- lib/after_migrate/version.rb
|
|
@@ -79,7 +77,6 @@ metadata:
|
|
|
79
77
|
source_code_uri: https://github.com/moskvin/after_migrate
|
|
80
78
|
changelog_uri: https://github.com/moskvin/after_migrate/blob/master/CHANGELOG.md
|
|
81
79
|
rubygems_mfa_required: 'true'
|
|
82
|
-
post_install_message:
|
|
83
80
|
rdoc_options: []
|
|
84
81
|
require_paths:
|
|
85
82
|
- lib
|
|
@@ -94,8 +91,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
94
91
|
- !ruby/object:Gem::Version
|
|
95
92
|
version: '0'
|
|
96
93
|
requirements: []
|
|
97
|
-
rubygems_version: 3.
|
|
98
|
-
signing_key:
|
|
94
|
+
rubygems_version: 3.6.9
|
|
99
95
|
specification_version: 4
|
|
100
96
|
summary: Automatically ANALYZE and VACUUM tables touched during Rails migrations.
|
|
101
97
|
test_files: []
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require 'active_support/current_attributes'
|
|
4
|
-
|
|
5
|
-
module AfterMigrate
|
|
6
|
-
class Current < ActiveSupport::CurrentAttributes
|
|
7
|
-
attribute :affected_tables
|
|
8
|
-
|
|
9
|
-
resets do
|
|
10
|
-
self.affected_tables = Hash.new { |h, k| h[k] = Concurrent::Set.new }
|
|
11
|
-
end
|
|
12
|
-
end
|
|
13
|
-
end
|