pg_sql_triggers 1.4.0 → 1.5.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 (107) hide show
  1. checksums.yaml +4 -4
  2. data/.erb_lint.yml +0 -0
  3. data/.rspec +0 -0
  4. data/.rubocop.yml +6 -16
  5. data/AGENTS.md +8 -0
  6. data/CHANGELOG.md +104 -2
  7. data/COVERAGE.md +39 -41
  8. data/LICENSE +0 -0
  9. data/README.md +24 -3
  10. data/RELEASE.md +0 -0
  11. data/Rakefile +5 -0
  12. data/app/assets/javascripts/pg_sql_triggers/application.js +0 -0
  13. data/app/assets/javascripts/pg_sql_triggers/trigger_actions.js +0 -0
  14. data/app/assets/stylesheets/pg_sql_triggers/application.css +0 -0
  15. data/app/controllers/concerns/pg_sql_triggers/error_handling.rb +0 -0
  16. data/app/controllers/concerns/pg_sql_triggers/kill_switch_protection.rb +0 -0
  17. data/app/controllers/concerns/pg_sql_triggers/permission_checking.rb +6 -5
  18. data/app/controllers/pg_sql_triggers/application_controller.rb +0 -0
  19. data/app/controllers/pg_sql_triggers/audit_logs_controller.rb +81 -64
  20. data/app/controllers/pg_sql_triggers/dashboard_controller.rb +111 -34
  21. data/app/controllers/pg_sql_triggers/migrations_controller.rb +13 -14
  22. data/app/controllers/pg_sql_triggers/tables_controller.rb +8 -0
  23. data/app/controllers/pg_sql_triggers/triggers_controller.rb +1 -0
  24. data/app/helpers/pg_sql_triggers/dashboard_helper.rb +19 -0
  25. data/app/helpers/pg_sql_triggers/permissions_helper.rb +3 -2
  26. data/app/models/pg_sql_triggers/application_record.rb +0 -0
  27. data/app/models/pg_sql_triggers/audit_log.rb +29 -47
  28. data/app/models/pg_sql_triggers/trigger_registry.rb +105 -78
  29. data/app/views/layouts/pg_sql_triggers/application.html.erb +0 -0
  30. data/app/views/pg_sql_triggers/audit_logs/index.html.erb +9 -5
  31. data/app/views/pg_sql_triggers/dashboard/index.html.erb +107 -24
  32. data/app/views/pg_sql_triggers/shared/_confirmation_modal.html.erb +0 -0
  33. data/app/views/pg_sql_triggers/shared/_kill_switch_status.html.erb +0 -0
  34. data/app/views/pg_sql_triggers/tables/index.html.erb +26 -14
  35. data/app/views/pg_sql_triggers/tables/show.html.erb +0 -0
  36. data/app/views/pg_sql_triggers/triggers/_drop_modal.html.erb +0 -0
  37. data/app/views/pg_sql_triggers/triggers/_re_execute_modal.html.erb +0 -0
  38. data/app/views/pg_sql_triggers/triggers/show.html.erb +33 -0
  39. data/config/initializers/pg_sql_triggers.rb +0 -0
  40. data/config/routes.rb +0 -0
  41. data/db/migrate/{20251222000001_create_pg_sql_triggers_tables.rb → 20251222104327_create_pg_sql_triggers_tables.rb} +0 -0
  42. data/db/migrate/20251229071916_add_timing_to_pg_sql_triggers_registry.rb +0 -0
  43. data/db/migrate/{20260103000001_create_pg_sql_triggers_audit_log.rb → 20260103114508_create_pg_sql_triggers_audit_log.rb} +0 -0
  44. data/db/migrate/{20260228000001_add_for_each_to_pg_sql_triggers_registry.rb → 20260228162233_add_for_each_to_pg_sql_triggers_registry.rb} +0 -0
  45. data/db/migrate/20260412185841_add_constraint_deferral_to_pg_sql_triggers_registry.rb +9 -0
  46. data/docs/README.md +3 -0
  47. data/docs/api-reference.md +133 -0
  48. data/docs/audit-trail.md +1 -1
  49. data/docs/configuration.md +172 -0
  50. data/docs/getting-started.md +14 -0
  51. data/docs/kill-switch.md +0 -0
  52. data/docs/permissions.md +6 -9
  53. data/docs/troubleshooting.md +0 -0
  54. data/docs/ui-guide.md +0 -0
  55. data/docs/usage-guide.md +74 -0
  56. data/docs/web-ui.md +0 -0
  57. data/lib/generators/pg_sql_triggers/install_generator.rb +0 -0
  58. data/lib/generators/pg_sql_triggers/templates/README +0 -0
  59. data/lib/generators/pg_sql_triggers/templates/create_pg_sql_triggers_tables.rb +0 -0
  60. data/lib/generators/pg_sql_triggers/templates/initializer.rb +14 -0
  61. data/lib/generators/pg_sql_triggers/templates/trigger_dsl.rb.tt +0 -0
  62. data/lib/generators/pg_sql_triggers/templates/trigger_migration.rb.erb +0 -0
  63. data/lib/generators/pg_sql_triggers/templates/trigger_migration_full.rb.tt +0 -0
  64. data/lib/generators/pg_sql_triggers/trigger_generator.rb +0 -0
  65. data/lib/generators/pg_sql_triggers/trigger_migration_generator.rb +0 -0
  66. data/lib/pg_sql_triggers/alerting.rb +77 -0
  67. data/lib/pg_sql_triggers/database_introspection.rb +0 -0
  68. data/lib/pg_sql_triggers/deferral_checksum.rb +54 -0
  69. data/lib/pg_sql_triggers/drift/db_queries.rb +14 -5
  70. data/lib/pg_sql_triggers/drift/detector.rb +9 -1
  71. data/lib/pg_sql_triggers/drift/reporter.rb +0 -0
  72. data/lib/pg_sql_triggers/drift.rb +5 -0
  73. data/lib/pg_sql_triggers/dsl/trigger_definition.rb +56 -2
  74. data/lib/pg_sql_triggers/dsl.rb +0 -0
  75. data/lib/pg_sql_triggers/engine.rb +35 -0
  76. data/lib/pg_sql_triggers/errors.rb +0 -0
  77. data/lib/pg_sql_triggers/events_checksum.rb +114 -0
  78. data/lib/pg_sql_triggers/migration.rb +5 -6
  79. data/lib/pg_sql_triggers/migrator/pre_apply_comparator.rb +77 -73
  80. data/lib/pg_sql_triggers/migrator/pre_apply_diff_reporter.rb +0 -0
  81. data/lib/pg_sql_triggers/migrator/safety_validator.rb +3 -1
  82. data/lib/pg_sql_triggers/migrator.rb +90 -94
  83. data/lib/pg_sql_triggers/permissions/checker.rb +12 -15
  84. data/lib/pg_sql_triggers/permissions.rb +1 -0
  85. data/lib/pg_sql_triggers/rake_development_boot.rb +65 -0
  86. data/lib/pg_sql_triggers/registry/manager.rb +27 -13
  87. data/lib/pg_sql_triggers/registry/validator.rb +226 -2
  88. data/lib/pg_sql_triggers/registry.rb +0 -0
  89. data/lib/pg_sql_triggers/schema_dumper_extension.rb +32 -0
  90. data/lib/pg_sql_triggers/sql/kill_switch.rb +2 -1
  91. data/lib/pg_sql_triggers/sql.rb +0 -0
  92. data/lib/pg_sql_triggers/testing/dry_run.rb +0 -0
  93. data/lib/pg_sql_triggers/testing/function_tester.rb +97 -107
  94. data/lib/pg_sql_triggers/testing/safe_executor.rb +0 -0
  95. data/lib/pg_sql_triggers/testing/syntax_validator.rb +0 -0
  96. data/lib/pg_sql_triggers/testing.rb +0 -0
  97. data/lib/pg_sql_triggers/trigger_structure_dumper.rb +111 -0
  98. data/lib/pg_sql_triggers/version.rb +1 -1
  99. data/lib/pg_sql_triggers.rb +17 -0
  100. data/lib/tasks/trigger_migrations.rake +235 -152
  101. data/rakelib/pg_sql_triggers_environment.rake +9 -0
  102. data/scripts/generate_coverage_report.rb +4 -1
  103. data/sig/pg_sql_triggers.rbs +0 -0
  104. metadata +65 -13
  105. data/GEM_ANALYSIS.md +0 -368
  106. data/Goal.md +0 -742
  107. data/pg_sql_triggers.gemspec +0 -53
@@ -11,5 +11,5 @@
11
11
  # 3. Run: bundle exec rake release
12
12
  # See RELEASE.md for detailed release instructions
13
13
  module PgSqlTriggers
14
- VERSION = "1.4.0"
14
+ VERSION = "1.5.0"
15
15
  end
@@ -36,10 +36,24 @@ module PgSqlTriggers
36
36
  mattr_accessor :allow_unsafe_migrations
37
37
  self.allow_unsafe_migrations = false
38
38
 
39
+ # Path or callable returning path for trigger SQL snapshot (default: db/trigger_structure.sql).
40
+ mattr_accessor :trigger_structure_sql_path
41
+
42
+ mattr_accessor :append_trigger_notes_to_schema_dump
43
+ self.append_trigger_notes_to_schema_dump = true
44
+
45
+ mattr_accessor :migrate_triggers_after_schema_load
46
+ self.migrate_triggers_after_schema_load = true
47
+
39
48
  # PostgreSQL schema used by DbQueries. Override for non-public schemas.
40
49
  mattr_accessor :db_schema
41
50
  self.db_schema = "public"
42
51
 
52
+ # Callable invoked when drift check finds drifted, dropped, or unknown triggers (+nil+ = no-op).
53
+ # See {PgSqlTriggers::Alerting.check_and_notify} and +trigger:check_drift+.
54
+ mattr_accessor :drift_notifier
55
+ self.drift_notifier = nil
56
+
43
57
  # Drift states
44
58
  DRIFT_STATE_IN_SYNC = "in_sync"
45
59
  DRIFT_STATE_DRIFTED = "drifted"
@@ -62,4 +76,7 @@ module PgSqlTriggers
62
76
  autoload :Testing, "pg_sql_triggers/testing"
63
77
  autoload :Migration, "pg_sql_triggers/migration"
64
78
  autoload :Migrator, "pg_sql_triggers/migrator"
79
+ autoload :DeferralChecksum, "pg_sql_triggers/deferral_checksum"
80
+ autoload :EventsChecksum, "pg_sql_triggers/events_checksum"
81
+ autoload :Alerting, "pg_sql_triggers/alerting"
65
82
  end
@@ -1,179 +1,201 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # Helper method to check kill switch before dangerous operations
4
- def check_kill_switch!(operation)
5
- PgSqlTriggers::SQL::KillSwitch.check!(
6
- operation: operation,
7
- environment: Rails.env,
8
- confirmation: ENV.fetch("CONFIRMATION_TEXT", nil),
9
- actor: { type: "CLI", id: ENV.fetch("USER", "unknown") }
10
- )
11
- rescue PgSqlTriggers::KillSwitchError => e
12
- puts "\n#{e.message}\n"
13
- exit 1
14
- end
3
+ module PgSqlTriggersRakeHelpers
4
+ module MigrateTasks
5
+ module_function
15
6
 
16
- namespace :trigger do
17
- desc "Migrate trigger migrations (options: VERSION=x, VERBOSE=false)"
18
- task migrate: :environment do
19
- check_kill_switch!(:trigger_migrate)
7
+ def run_migrate
8
+ PgSqlTriggersRakeHelpers.check_kill_switch!(:trigger_migrate)
9
+ PgSqlTriggers::Migrator.ensure_migrations_table!
20
10
 
21
- PgSqlTriggers::Migrator.ensure_migrations_table!
11
+ target_version = ENV["VERSION"]&.to_i
12
+ verbose = ENV["VERBOSE"] != "false"
22
13
 
23
- target_version = ENV["VERSION"]&.to_i
24
- verbose = ENV["VERBOSE"] != "false"
14
+ if verbose
15
+ puts "Running trigger migrations..."
16
+ puts "Current version: #{PgSqlTriggers::Migrator.current_version}"
17
+ end
25
18
 
26
- if verbose
27
- puts "Running trigger migrations..."
28
- puts "Current version: #{PgSqlTriggers::Migrator.current_version}"
29
- end
19
+ PgSqlTriggers::Migrator.run_up(target_version)
30
20
 
31
- PgSqlTriggers::Migrator.run_up(target_version)
21
+ puts "Trigger migrations complete. Current version: #{PgSqlTriggers::Migrator.current_version}" if verbose
22
+ end
32
23
 
33
- puts "Trigger migrations complete. Current version: #{PgSqlTriggers::Migrator.current_version}" if verbose
34
- end
24
+ def run_rollback
25
+ PgSqlTriggersRakeHelpers.check_kill_switch!(:trigger_rollback)
26
+ PgSqlTriggers::Migrator.ensure_migrations_table!
35
27
 
36
- desc "Rollback trigger migrations (specify steps w/ STEP=n)"
37
- task rollback: :environment do
38
- check_kill_switch!(:trigger_rollback)
28
+ steps = ENV["STEP"] ? ENV["STEP"].to_i : 1
29
+ current_version = PgSqlTriggers::Migrator.current_version
30
+ target_version = [0, current_version - steps].max
39
31
 
40
- PgSqlTriggers::Migrator.ensure_migrations_table!
32
+ puts "Rolling back trigger migrations..."
33
+ puts "Current version: #{current_version}"
34
+ puts "Target version: #{target_version}"
41
35
 
42
- steps = ENV["STEP"] ? ENV["STEP"].to_i : 1
43
- current_version = PgSqlTriggers::Migrator.current_version
44
- target_version = [0, current_version - steps].max
36
+ PgSqlTriggers::Migrator.run_down(target_version)
45
37
 
46
- puts "Rolling back trigger migrations..."
47
- puts "Current version: #{current_version}"
48
- puts "Target version: #{target_version}"
38
+ puts "Rollback complete. Current version: #{PgSqlTriggers::Migrator.current_version}"
39
+ end
49
40
 
50
- PgSqlTriggers::Migrator.run_down(target_version)
41
+ def print_migrate_status
42
+ PgSqlTriggers::Migrator.ensure_migrations_table!
43
+ statuses = PgSqlTriggers::Migrator.status
44
+ return puts "No trigger migrations found" if statuses.empty?
51
45
 
52
- puts "Rollback complete. Current version: #{PgSqlTriggers::Migrator.current_version}"
53
- end
46
+ print_status_table(statuses)
47
+ end
54
48
 
55
- desc "Display status of trigger migrations"
56
- task "migrate:status" => :environment do
57
- PgSqlTriggers::Migrator.ensure_migrations_table!
49
+ def print_status_table(statuses)
50
+ puts "\nTrigger Migration Status"
51
+ puts "=" * 80
52
+ printf "%<version>-20s %<name>-40s %<status>-10s\n", version: "Version", name: "Name", status: "Status"
53
+ puts "-" * 80
54
+ statuses.each do |status|
55
+ printf "%<version>-20s %<name>-40s %<status>-10s\n",
56
+ version: status[:version], name: status[:name], status: status[:status]
57
+ end
58
+ puts "=" * 80
59
+ puts "Current version: #{PgSqlTriggers::Migrator.current_version}"
60
+ end
58
61
 
59
- statuses = PgSqlTriggers::Migrator.status
62
+ def run_migrate_up
63
+ PgSqlTriggersRakeHelpers.check_kill_switch!(:trigger_migrate_up)
64
+ version = ENV.fetch("VERSION", nil)
65
+ raise "VERSION is required" unless version
60
66
 
61
- if statuses.empty?
62
- puts "No trigger migrations found"
63
- return
67
+ PgSqlTriggers::Migrator.ensure_migrations_table!
68
+ PgSqlTriggers::Migrator.run_up(version.to_i)
69
+ puts "Trigger migration #{version} up complete"
64
70
  end
65
71
 
66
- puts "\nTrigger Migration Status"
67
- puts "=" * 80
68
- printf "%<version>-20s %<name>-40s %<status>-10s\n", version: "Version", name: "Name", status: "Status"
69
- puts "-" * 80
70
-
71
- statuses.each do |status|
72
- printf "%<version>-20s %<name>-40s %<status>-10s\n",
73
- version: status[:version],
74
- name: status[:name],
75
- status: status[:status]
72
+ def run_migrate_down
73
+ PgSqlTriggersRakeHelpers.check_kill_switch!(:trigger_migrate_down)
74
+ version = ENV.fetch("VERSION", nil)
75
+ raise "VERSION is required" unless version
76
+
77
+ PgSqlTriggers::Migrator.ensure_migrations_table!
78
+ PgSqlTriggers::Migrator.run_down(version.to_i)
79
+ puts "Trigger migration #{version} down complete"
76
80
  end
77
81
 
78
- puts "=" * 80
79
- puts "Current version: #{PgSqlTriggers::Migrator.current_version}"
82
+ def run_migrate_redo
83
+ PgSqlTriggersRakeHelpers.check_kill_switch!(:trigger_migrate_redo)
84
+ PgSqlTriggers::Migrator.ensure_migrations_table!
85
+
86
+ if ENV["VERSION"]
87
+ version = ENV["VERSION"].to_i
88
+ PgSqlTriggers::Migrator.run_down(version)
89
+ PgSqlTriggers::Migrator.run_up(version)
90
+ else
91
+ steps = ENV["STEP"] ? ENV["STEP"].to_i : 1
92
+ current_version = PgSqlTriggers::Migrator.current_version
93
+ target_version = [0, current_version - steps].max
94
+ PgSqlTriggers::Migrator.run_down(target_version)
95
+ PgSqlTriggers::Migrator.run_up
96
+ end
97
+
98
+ puts "Trigger migration redo complete"
99
+ end
80
100
  end
81
101
 
82
- desc "Runs the 'up' for a given migration VERSION"
83
- task "migrate:up" => :environment do
84
- check_kill_switch!(:trigger_migrate_up)
102
+ module_function
85
103
 
86
- version = ENV.fetch("VERSION", nil)
87
- raise "VERSION is required" unless version
104
+ extend MigrateTasks
88
105
 
89
- PgSqlTriggers::Migrator.ensure_migrations_table!
90
- PgSqlTriggers::Migrator.run_up(version.to_i)
91
- puts "Trigger migration #{version} up complete"
106
+ # @param result [Hash] single drift result from {PgSqlTriggers::Drift::Detector}
107
+ def drift_check_trigger_label(result)
108
+ result[:registry_entry]&.trigger_name ||
109
+ result[:db_trigger]&.fetch("trigger_name", nil) ||
110
+ "(unknown)"
92
111
  end
93
112
 
94
- desc "Runs the 'down' for a given migration VERSION"
95
- task "migrate:down" => :environment do
96
- check_kill_switch!(:trigger_migrate_down)
97
-
98
- version = ENV.fetch("VERSION", nil)
99
- raise "VERSION is required" unless version
100
-
101
- PgSqlTriggers::Migrator.ensure_migrations_table!
102
- PgSqlTriggers::Migrator.run_down(version.to_i)
103
- puts "Trigger migration #{version} down complete"
113
+ def check_kill_switch!(operation)
114
+ PgSqlTriggers::SQL::KillSwitch.check!(
115
+ operation: operation,
116
+ environment: Rails.env,
117
+ confirmation: ENV.fetch("CONFIRMATION_TEXT", nil),
118
+ actor: { type: "CLI", id: ENV.fetch("USER", "unknown") }
119
+ )
120
+ rescue PgSqlTriggers::KillSwitchError => e
121
+ puts "\n#{e.message}\n"
122
+ exit 1
104
123
  end
105
124
 
106
- desc "Rollbacks the database one migration and re migrate up (options: STEP=x, VERSION=x)"
107
- task "migrate:redo" => :environment do
108
- check_kill_switch!(:trigger_migrate_redo)
125
+ def dump_trigger_structure
126
+ path = ENV["FILE"].presence || ENV["TRIGGER_STRUCTURE_SQL"].presence
127
+ written = PgSqlTriggers::TriggerStructureDumper.dump_to(path)
128
+ puts "Wrote #{written}"
129
+ end
109
130
 
110
- PgSqlTriggers::Migrator.ensure_migrations_table!
131
+ def load_trigger_structure
132
+ check_kill_switch!(:trigger_load)
133
+ path = ENV["FILE"].presence || ENV["TRIGGER_STRUCTURE_SQL"].presence
134
+ PgSqlTriggers::TriggerStructureDumper.load_from(path)
135
+ puts "Loaded #{PgSqlTriggers::TriggerStructureDumper.resolve_path(path)}"
136
+ end
111
137
 
112
- if ENV["VERSION"]
113
- version = ENV["VERSION"].to_i
114
- PgSqlTriggers::Migrator.run_down(version)
115
- PgSqlTriggers::Migrator.run_up(version)
116
- else
117
- steps = ENV["STEP"] ? ENV["STEP"].to_i : 1
118
- current_version = PgSqlTriggers::Migrator.current_version
119
- target_version = [0, current_version - steps].max
138
+ def run_check_drift
139
+ outcome = PgSqlTriggers::Alerting.check_and_notify
140
+ results = outcome[:results]
141
+ alertable = outcome[:alertable]
120
142
 
121
- PgSqlTriggers::Migrator.run_down(target_version)
122
- PgSqlTriggers::Migrator.run_up
143
+ puts "PgSqlTriggers drift check: #{results.size} trigger(s), #{alertable.size} problem(s)."
144
+ alertable.each do |r|
145
+ puts " - #{drift_check_trigger_label(r)}: #{r[:state]} — #{r[:details]}"
146
+ end
147
+ puts "Notifier invoked." if outcome[:notified]
148
+ if alertable.any? && !outcome[:notified]
149
+ puts "No drift notifier configured; set PgSqlTriggers.drift_notifier to receive alerts."
123
150
  end
124
151
 
125
- puts "Trigger migration redo complete"
152
+ exit 1 if ENV["FAIL_ON_DRIFT"].present? && alertable.any?
126
153
  end
127
154
 
128
- desc "Retrieves the current schema version number for trigger migrations"
129
- task version: :environment do
130
- PgSqlTriggers::Migrator.ensure_migrations_table!
131
- puts "Current trigger migration version: #{PgSqlTriggers::Migrator.current_version}"
155
+ def run_validate_order
156
+ errors = PgSqlTriggers::Registry::Validator.trigger_order_validation_errors
157
+ if errors.empty?
158
+ puts "PgSqlTriggers: trigger depends_on / name order OK."
159
+ else
160
+ puts "PgSqlTriggers: trigger order validation failed:"
161
+ errors.each { |msg| puts " - #{msg}" }
162
+ exit 1
163
+ end
132
164
  end
133
165
 
134
- desc "Raises an error if there are pending trigger migrations"
135
- task "abort_if_pending_migrations" => :environment do
166
+ def abort_if_pending_trigger_migrations
136
167
  PgSqlTriggers::Migrator.ensure_migrations_table!
137
-
138
168
  pending = PgSqlTriggers::Migrator.pending_migrations
139
- if pending.any?
140
- puts "You have #{pending.length} pending trigger migration(s):"
141
- pending.each do |migration|
142
- puts " #{migration.version}_#{migration.name}"
143
- end
144
- raise "Pending trigger migrations found"
145
- end
169
+ return if pending.empty?
170
+
171
+ puts "You have #{pending.length} pending trigger migration(s):"
172
+ pending.each { |migration| puts " #{migration.version}_#{migration.name}" }
173
+ raise "Pending trigger migrations found"
146
174
  end
147
175
  end
148
176
 
149
- # Combined tasks for running both schema and trigger migrations
150
- namespace :db do
151
- desc "Migrate the database schema and triggers (options: VERSION=x, VERBOSE=false)"
152
- task "migrate:with_triggers" => :environment do
153
- check_kill_switch!(:db_migrate_with_triggers)
177
+ module PgSqlTriggersDbRakeHelpers
178
+ module_function
154
179
 
155
- verbose = ENV["VERBOSE"] != "false"
180
+ def check_kill_switch!(operation)
181
+ PgSqlTriggersRakeHelpers.check_kill_switch!(operation)
182
+ end
156
183
 
184
+ def run_db_migrate_with_triggers
185
+ check_kill_switch!(:db_migrate_with_triggers)
186
+ verbose = ENV["VERBOSE"] != "false"
157
187
  puts "Running schema and trigger migrations..." if verbose
158
-
159
- # Run schema migrations first
160
188
  Rake::Task["db:migrate"].invoke
161
-
162
- # Then run trigger migrations
163
189
  Rake::Task["trigger:migrate"].invoke
164
190
  end
165
191
 
166
- desc "Rollback schema and trigger migrations (specify steps w/ STEP=n)"
167
- task "rollback:with_triggers" => :environment do
192
+ def run_db_rollback_with_triggers
168
193
  check_kill_switch!(:db_rollback_with_triggers)
169
-
170
194
  ENV["STEP"] ? ENV["STEP"].to_i : 1
171
195
 
172
- # Determine which type of migration was last run
173
196
  schema_version = ActiveRecord::Base.connection.schema_migration_context.current_version || 0
174
197
  trigger_version = PgSqlTriggers::Migrator.current_version
175
198
 
176
- # Rollback the most recent migration (schema or trigger)
177
199
  if schema_version > trigger_version
178
200
  Rake::Task["db:rollback"].invoke
179
201
  else
@@ -181,8 +203,7 @@ namespace :db do
181
203
  end
182
204
  end
183
205
 
184
- desc "Display status of schema and trigger migrations"
185
- task "migrate:status:with_triggers" => :environment do
206
+ def print_migrate_status_with_triggers
186
207
  puts "\nSchema Migrations:"
187
208
  puts "=" * 80
188
209
  begin
@@ -196,24 +217,21 @@ namespace :db do
196
217
  Rake::Task["trigger:migrate:status"].invoke
197
218
  end
198
219
 
199
- desc "Runs the 'up' for a given migration VERSION (schema or trigger)"
200
- task "migrate:up:with_triggers" => :environment do
220
+ def run_db_migrate_up_with_triggers
201
221
  check_kill_switch!(:db_migrate_up_with_triggers)
202
-
203
222
  version = ENV.fetch("VERSION", nil)
204
223
  raise "VERSION is required" unless version
205
224
 
206
- version_int = version.to_i
207
-
208
- # Check if it's a schema or trigger migration
209
- schema_migrations = ActiveRecord::Base.connection.migration_context.migrations
210
- trigger_migrations = PgSqlTriggers::Migrator.migrations
225
+ invoke_up_for_version(version.to_i, version)
226
+ rescue StandardError => e
227
+ puts "Error: #{e.message}"
228
+ raise
229
+ end
211
230
 
212
- schema_migration = schema_migrations.find { |m| m.version == version_int }
213
- trigger_migration = trigger_migrations.find { |m| m.version == version_int }
231
+ def invoke_up_for_version(version_int, version)
232
+ schema_migration, trigger_migration = find_migrations(version_int)
214
233
 
215
234
  if schema_migration && trigger_migration
216
- # Both exist - run schema first
217
235
  Rake::Task["db:migrate:up"].invoke
218
236
  Rake::Task["trigger:migrate:up"].invoke
219
237
  elsif schema_migration
@@ -223,29 +241,20 @@ namespace :db do
223
241
  else
224
242
  raise "No migration found with version #{version}"
225
243
  end
226
- rescue StandardError => e
227
- puts "Error: #{e.message}"
228
- raise
229
244
  end
230
245
 
231
- desc "Runs the 'down' for a given migration VERSION (schema or trigger)"
232
- task "migrate:down:with_triggers" => :environment do
246
+ def run_db_migrate_down_with_triggers
233
247
  check_kill_switch!(:db_migrate_down_with_triggers)
234
-
235
248
  version = ENV.fetch("VERSION", nil)
236
249
  raise "VERSION is required" unless version
237
250
 
238
- version_int = version.to_i
239
-
240
- # Check if it's a schema or trigger migration
241
- schema_migrations = ActiveRecord::Base.connection.migration_context.migrations
242
- trigger_migrations = PgSqlTriggers::Migrator.migrations
251
+ invoke_down_for_version(version.to_i, version)
252
+ end
243
253
 
244
- schema_migration = schema_migrations.find { |m| m.version == version_int }
245
- trigger_migration = trigger_migrations.find { |m| m.version == version_int }
254
+ def invoke_down_for_version(version_int, version)
255
+ schema_migration, trigger_migration = find_migrations(version_int)
246
256
 
247
257
  if schema_migration && trigger_migration
248
- # Both exist - run trigger down first
249
258
  Rake::Task["trigger:migrate:down"].invoke
250
259
  Rake::Task["db:migrate:down"].invoke
251
260
  elsif schema_migration
@@ -257,8 +266,16 @@ namespace :db do
257
266
  end
258
267
  end
259
268
 
260
- desc "Rollbacks the database one migration and re migrate up (options: STEP=x, VERSION=x)"
261
- task "migrate:redo:with_triggers" => :environment do
269
+ def find_migrations(version_int)
270
+ schema_migrations = ActiveRecord::Base.connection.migration_context.migrations
271
+ trigger_migrations = PgSqlTriggers::Migrator.migrations
272
+ [
273
+ schema_migrations.find { |m| m.version == version_int },
274
+ trigger_migrations.find { |m| m.version == version_int }
275
+ ]
276
+ end
277
+
278
+ def run_db_migrate_redo_with_triggers
262
279
  check_kill_switch!(:db_migrate_redo_with_triggers)
263
280
 
264
281
  if ENV["VERSION"]
@@ -270,17 +287,83 @@ namespace :db do
270
287
  end
271
288
  end
272
289
 
273
- desc "Retrieves the current schema version numbers for schema and trigger migrations"
274
- task "version:with_triggers" => :environment do
290
+ def print_version_with_triggers
275
291
  schema_version = ActiveRecord::Base.connection.schema_migration_context.current_version
276
292
  trigger_version = PgSqlTriggers::Migrator.current_version
277
293
 
278
294
  puts "Schema migration version: #{schema_version || 0}"
279
295
  puts "Trigger migration version: #{trigger_version}"
280
296
  end
297
+ end
298
+
299
+ namespace :trigger do
300
+ desc "Dump managed PostgreSQL triggers to db/trigger_structure.sql (FILE=path to override)"
301
+ task(dump: :environment) { PgSqlTriggersRakeHelpers.dump_trigger_structure }
302
+
303
+ desc "Execute SQL from db/trigger_structure.sql (FILE=path to override)"
304
+ task(load: :environment) { PgSqlTriggersRakeHelpers.load_trigger_structure }
305
+
306
+ desc "Migrate trigger migrations (options: VERSION=x, VERBOSE=false)"
307
+ task(migrate: :environment) { PgSqlTriggersRakeHelpers.run_migrate }
308
+
309
+ desc "Rollback trigger migrations (specify steps w/ STEP=n)"
310
+ task(rollback: :environment) { PgSqlTriggersRakeHelpers.run_rollback }
311
+
312
+ desc "Display status of trigger migrations"
313
+ task("migrate:status" => :environment) { PgSqlTriggersRakeHelpers.print_migrate_status }
314
+
315
+ desc "Runs the 'up' for a given migration VERSION"
316
+ task("migrate:up" => :environment) { PgSqlTriggersRakeHelpers.run_migrate_up }
317
+
318
+ desc "Runs the 'down' for a given migration VERSION"
319
+ task("migrate:down" => :environment) { PgSqlTriggersRakeHelpers.run_migrate_down }
320
+
321
+ desc "Rollbacks the database one migration and re migrate up (options: STEP=x, VERSION=x)"
322
+ task("migrate:redo" => :environment) { PgSqlTriggersRakeHelpers.run_migrate_redo }
323
+
324
+ desc "Retrieves the current schema version number for trigger migrations"
325
+ task version: :environment do
326
+ PgSqlTriggers::Migrator.ensure_migrations_table!
327
+ puts "Current trigger migration version: #{PgSqlTriggers::Migrator.current_version}"
328
+ end
329
+
330
+ desc "Detect trigger drift; calls drift_notifier when drifted/dropped/unknown (FAIL_ON_DRIFT=1 exits non-zero)"
331
+ task(check_drift: :environment) { PgSqlTriggersRakeHelpers.run_check_drift }
332
+
333
+ desc "Validate trigger depends_on metadata (refs, cycles, compatibility, PostgreSQL name order)"
334
+ task(validate_order: :environment) { PgSqlTriggersRakeHelpers.run_validate_order }
335
+
336
+ desc "Raises an error if there are pending trigger migrations"
337
+ task("abort_if_pending_migrations" => :environment) do
338
+ PgSqlTriggersRakeHelpers.abort_if_pending_trigger_migrations
339
+ end
340
+ end
341
+
342
+ # Combined tasks for running both schema and trigger migrations
343
+ namespace :db do
344
+ desc "Migrate the database schema and triggers (options: VERSION=x, VERBOSE=false)"
345
+ task("migrate:with_triggers" => :environment) { PgSqlTriggersDbRakeHelpers.run_db_migrate_with_triggers }
346
+
347
+ desc "Rollback schema and trigger migrations (specify steps w/ STEP=n)"
348
+ task("rollback:with_triggers" => :environment) { PgSqlTriggersDbRakeHelpers.run_db_rollback_with_triggers }
349
+
350
+ desc "Display status of schema and trigger migrations"
351
+ task("migrate:status:with_triggers" => :environment) { PgSqlTriggersDbRakeHelpers.print_migrate_status_with_triggers }
352
+
353
+ desc "Runs the 'up' for a given migration VERSION (schema or trigger)"
354
+ task("migrate:up:with_triggers" => :environment) { PgSqlTriggersDbRakeHelpers.run_db_migrate_up_with_triggers }
355
+
356
+ desc "Runs the 'down' for a given migration VERSION (schema or trigger)"
357
+ task("migrate:down:with_triggers" => :environment) { PgSqlTriggersDbRakeHelpers.run_db_migrate_down_with_triggers }
358
+
359
+ desc "Rollbacks the database one migration and re migrate up (options: STEP=x, VERSION=x)"
360
+ task("migrate:redo:with_triggers" => :environment) { PgSqlTriggersDbRakeHelpers.run_db_migrate_redo_with_triggers }
361
+
362
+ desc "Retrieves the current schema version numbers for schema and trigger migrations"
363
+ task("version:with_triggers" => :environment) { PgSqlTriggersDbRakeHelpers.print_version_with_triggers }
281
364
 
282
365
  desc "Raises an error if there are pending migrations or trigger migrations"
283
- task "abort_if_pending_migrations:with_triggers" => :environment do
366
+ task("abort_if_pending_migrations:with_triggers" => :environment) do
284
367
  Rake::Task["db:abort_if_pending_migrations"].invoke
285
368
  Rake::Task["trigger:abort_if_pending_migrations"].invoke
286
369
  end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Provides :environment for engine tasks when developing the gem (see Rakefile).
4
+ # rubocop:disable Rails/RakeEnvironment -- this task *is* the Rails environment for gem dev
5
+ task :environment do
6
+ require_relative "../lib/pg_sql_triggers/rake_development_boot"
7
+ PgSqlTriggers::RakeDevelopmentBoot.boot!
8
+ end
9
+ # rubocop:enable Rails/RakeEnvironment
@@ -31,7 +31,8 @@ def extract_file_path(full_path)
31
31
  end
32
32
 
33
33
  def parse_coverage_data
34
- coverage_dir = Pathname.new(__dir__).parent.join("coverage")
34
+ workspace_root = Pathname.new(__dir__).parent.realpath
35
+ coverage_dir = workspace_root.join("coverage")
35
36
  resultset_file = coverage_dir.join(".resultset.json")
36
37
  last_run_file = coverage_dir.join(".last_run.json")
37
38
 
@@ -57,6 +58,8 @@ def parse_coverage_data
57
58
  relative_path = extract_file_path(full_path)
58
59
  # Skip files in spec, vendor, etc.
59
60
  next if relative_path.include?("/spec/") || relative_path.include?("/vendor/")
61
+ # Omit deleted or moved sources so stale .resultset.json cannot inflate the report
62
+ next unless workspace_root.join(relative_path).file?
60
63
 
61
64
  lines = file_data["lines"]
62
65
  coverage_percentage = calculate_file_coverage(lines)
File without changes