pg_sql_triggers 1.3.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.
- checksums.yaml +4 -4
- data/.erb_lint.yml +0 -0
- data/.rspec +0 -0
- data/.rubocop.yml +6 -16
- data/AGENTS.md +8 -0
- data/CHANGELOG.md +354 -0
- data/COVERAGE.md +39 -41
- data/LICENSE +0 -0
- data/README.md +44 -26
- data/RELEASE.md +0 -0
- data/Rakefile +5 -0
- data/app/assets/javascripts/pg_sql_triggers/application.js +0 -0
- data/app/assets/javascripts/pg_sql_triggers/trigger_actions.js +0 -0
- data/app/assets/stylesheets/pg_sql_triggers/application.css +0 -0
- data/app/controllers/concerns/pg_sql_triggers/error_handling.rb +0 -0
- data/app/controllers/concerns/pg_sql_triggers/kill_switch_protection.rb +0 -0
- data/app/controllers/concerns/pg_sql_triggers/permission_checking.rb +6 -5
- data/app/controllers/pg_sql_triggers/application_controller.rb +0 -0
- data/app/controllers/pg_sql_triggers/audit_logs_controller.rb +81 -64
- data/app/controllers/pg_sql_triggers/dashboard_controller.rb +111 -34
- data/app/controllers/pg_sql_triggers/migrations_controller.rb +13 -14
- data/app/controllers/pg_sql_triggers/tables_controller.rb +8 -0
- data/app/controllers/pg_sql_triggers/triggers_controller.rb +1 -0
- data/app/helpers/pg_sql_triggers/dashboard_helper.rb +19 -0
- data/app/helpers/pg_sql_triggers/permissions_helper.rb +3 -2
- data/app/models/pg_sql_triggers/application_record.rb +0 -0
- data/app/models/pg_sql_triggers/audit_log.rb +29 -47
- data/app/models/pg_sql_triggers/trigger_registry.rb +137 -74
- data/app/views/layouts/pg_sql_triggers/application.html.erb +0 -1
- data/app/views/pg_sql_triggers/audit_logs/index.html.erb +9 -5
- data/app/views/pg_sql_triggers/dashboard/index.html.erb +107 -27
- data/app/views/pg_sql_triggers/shared/_confirmation_modal.html.erb +0 -0
- data/app/views/pg_sql_triggers/shared/_kill_switch_status.html.erb +0 -0
- data/app/views/pg_sql_triggers/tables/index.html.erb +27 -18
- data/app/views/pg_sql_triggers/tables/show.html.erb +0 -2
- data/app/views/pg_sql_triggers/triggers/_drop_modal.html.erb +0 -0
- data/app/views/pg_sql_triggers/triggers/_re_execute_modal.html.erb +0 -0
- data/app/views/pg_sql_triggers/triggers/show.html.erb +33 -0
- data/config/initializers/pg_sql_triggers.rb +0 -0
- data/config/routes.rb +0 -14
- data/db/migrate/{20251222000001_create_pg_sql_triggers_tables.rb → 20251222104327_create_pg_sql_triggers_tables.rb} +0 -0
- data/db/migrate/20251229071916_add_timing_to_pg_sql_triggers_registry.rb +0 -0
- data/db/migrate/{20260103000001_create_pg_sql_triggers_audit_log.rb → 20260103114508_create_pg_sql_triggers_audit_log.rb} +0 -0
- data/db/migrate/20260228162233_add_for_each_to_pg_sql_triggers_registry.rb +8 -0
- data/db/migrate/20260412185841_add_constraint_deferral_to_pg_sql_triggers_registry.rb +9 -0
- data/docs/README.md +3 -0
- data/docs/api-reference.md +176 -152
- data/docs/audit-trail.md +1 -1
- data/docs/configuration.md +196 -3
- data/docs/getting-started.md +31 -16
- data/docs/kill-switch.md +0 -0
- data/docs/permissions.md +6 -9
- data/docs/troubleshooting.md +0 -0
- data/docs/ui-guide.md +0 -0
- data/docs/usage-guide.md +112 -67
- data/docs/web-ui.md +3 -103
- data/lib/generators/pg_sql_triggers/install_generator.rb +0 -0
- data/lib/generators/pg_sql_triggers/templates/README +0 -0
- data/lib/generators/pg_sql_triggers/templates/create_pg_sql_triggers_tables.rb +0 -0
- data/lib/generators/pg_sql_triggers/templates/initializer.rb +14 -0
- data/lib/generators/pg_sql_triggers/templates/trigger_dsl.rb.tt +11 -0
- data/lib/generators/pg_sql_triggers/templates/trigger_migration.rb.erb +0 -0
- data/lib/generators/pg_sql_triggers/templates/trigger_migration_full.rb.tt +29 -0
- data/lib/generators/pg_sql_triggers/trigger_generator.rb +83 -0
- data/lib/generators/pg_sql_triggers/trigger_migration_generator.rb +0 -0
- data/lib/pg_sql_triggers/alerting.rb +77 -0
- data/lib/pg_sql_triggers/database_introspection.rb +0 -0
- data/lib/pg_sql_triggers/deferral_checksum.rb +54 -0
- data/lib/pg_sql_triggers/drift/db_queries.rb +26 -13
- data/lib/pg_sql_triggers/drift/detector.rb +59 -38
- data/lib/pg_sql_triggers/drift/reporter.rb +0 -0
- data/lib/pg_sql_triggers/drift.rb +5 -0
- data/lib/pg_sql_triggers/dsl/trigger_definition.rb +68 -20
- data/lib/pg_sql_triggers/dsl.rb +0 -0
- data/lib/pg_sql_triggers/engine.rb +49 -0
- data/lib/pg_sql_triggers/errors.rb +0 -0
- data/lib/pg_sql_triggers/events_checksum.rb +114 -0
- data/lib/pg_sql_triggers/migration.rb +5 -6
- data/lib/pg_sql_triggers/migrator/pre_apply_comparator.rb +85 -82
- data/lib/pg_sql_triggers/migrator/pre_apply_diff_reporter.rb +0 -0
- data/lib/pg_sql_triggers/migrator/safety_validator.rb +34 -12
- data/lib/pg_sql_triggers/migrator.rb +137 -94
- data/lib/pg_sql_triggers/permissions/checker.rb +12 -15
- data/lib/pg_sql_triggers/permissions.rb +1 -0
- data/lib/pg_sql_triggers/rake_development_boot.rb +65 -0
- data/lib/pg_sql_triggers/registry/manager.rb +60 -21
- data/lib/pg_sql_triggers/registry/validator.rb +287 -6
- data/lib/pg_sql_triggers/registry.rb +0 -0
- data/lib/pg_sql_triggers/schema_dumper_extension.rb +32 -0
- data/lib/pg_sql_triggers/sql/kill_switch.rb +154 -275
- data/lib/pg_sql_triggers/sql.rb +0 -6
- data/lib/pg_sql_triggers/testing/dry_run.rb +0 -0
- data/lib/pg_sql_triggers/testing/function_tester.rb +97 -107
- data/lib/pg_sql_triggers/testing/safe_executor.rb +0 -0
- data/lib/pg_sql_triggers/testing/syntax_validator.rb +0 -0
- data/lib/pg_sql_triggers/testing.rb +0 -0
- data/lib/pg_sql_triggers/trigger_structure_dumper.rb +111 -0
- data/lib/pg_sql_triggers/version.rb +1 -1
- data/lib/pg_sql_triggers.rb +21 -1
- data/lib/tasks/trigger_migrations.rake +235 -152
- data/rakelib/pg_sql_triggers_environment.rake +9 -0
- data/scripts/generate_coverage_report.rb +4 -1
- data/sig/pg_sql_triggers.rbs +0 -0
- metadata +68 -22
- data/Goal.md +0 -742
- data/app/controllers/pg_sql_triggers/generator_controller.rb +0 -213
- data/app/controllers/pg_sql_triggers/sql_capsules_controller.rb +0 -161
- data/app/views/pg_sql_triggers/generator/new.html.erb +0 -388
- data/app/views/pg_sql_triggers/generator/preview.html.erb +0 -305
- data/app/views/pg_sql_triggers/sql_capsules/new.html.erb +0 -81
- data/app/views/pg_sql_triggers/sql_capsules/show.html.erb +0 -85
- data/lib/generators/trigger/migration_generator.rb +0 -60
- data/lib/pg_sql_triggers/generator/form.rb +0 -80
- data/lib/pg_sql_triggers/generator/service.rb +0 -339
- data/lib/pg_sql_triggers/generator.rb +0 -8
- data/lib/pg_sql_triggers/sql/capsule.rb +0 -79
- data/lib/pg_sql_triggers/sql/executor.rb +0 -200
|
@@ -7,122 +7,60 @@ module PgSqlTriggers
|
|
|
7
7
|
@trigger = trigger_registry
|
|
8
8
|
end
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
FUNCTION_NAME_PATTERN = /CREATE\s+(?:OR\s+REPLACE\s+)?FUNCTION\s+([a-zA-Z_][a-zA-Z0-9_]*)\s*\(/i
|
|
11
|
+
|
|
12
|
+
# Test ONLY the function, not the trigger.
|
|
13
|
+
#
|
|
14
|
+
# +test_context+ is accepted for API compatibility with future invocation logic.
|
|
15
|
+
# It is normalised to an empty hash when +nil+ so callers can pass either.
|
|
12
16
|
def test_function_only(test_context: {})
|
|
17
|
+
test_context ||= {}
|
|
13
18
|
results = {
|
|
14
19
|
function_created: false,
|
|
15
20
|
function_executed: false,
|
|
16
21
|
errors: [],
|
|
17
|
-
output: []
|
|
22
|
+
output: [],
|
|
23
|
+
context: test_context
|
|
18
24
|
}
|
|
19
25
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
results
|
|
23
|
-
results[:errors] << "Function body is missing"
|
|
24
|
-
return results
|
|
26
|
+
return fail_result(results, "Function body is missing") if @trigger.function_body.blank?
|
|
27
|
+
unless extract_function_name_from_body
|
|
28
|
+
return fail_result(results, "Function body does not contain a valid CREATE FUNCTION statement")
|
|
25
29
|
end
|
|
26
30
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
match = @trigger.function_body.match(pattern)
|
|
32
|
-
function_name_from_body = match[1] if match
|
|
33
|
-
end
|
|
31
|
+
run_function_test_transaction(results)
|
|
32
|
+
results[:output] << "\n⚠ Function rolled back (test mode)"
|
|
33
|
+
results
|
|
34
|
+
end
|
|
34
35
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
36
|
+
private
|
|
37
|
+
|
|
38
|
+
def fail_result(results, error_message)
|
|
39
|
+
results[:success] = false
|
|
40
|
+
results[:errors] << error_message
|
|
41
|
+
results
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def extract_function_name_from_body
|
|
45
|
+
return nil if @trigger.function_body.blank?
|
|
46
|
+
|
|
47
|
+
match = @trigger.function_body.match(FUNCTION_NAME_PATTERN)
|
|
48
|
+
match && match[1]
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def extract_function_name_from_definition
|
|
52
|
+
return nil if @trigger.definition.blank?
|
|
53
|
+
|
|
54
|
+
definition = JSON.parse(@trigger.definition)
|
|
55
|
+
definition["function_name"] || definition["name"]
|
|
56
|
+
rescue StandardError
|
|
57
|
+
nil
|
|
58
|
+
end
|
|
41
59
|
|
|
42
|
-
|
|
60
|
+
def run_function_test_transaction(results)
|
|
43
61
|
ActiveRecord::Base.transaction do
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
ActiveRecord::Base.connection.execute(@trigger.function_body)
|
|
47
|
-
results[:function_created] = true
|
|
48
|
-
results[:output] << "✓ Function created in test transaction"
|
|
49
|
-
rescue ActiveRecord::StatementInvalid, StandardError => e
|
|
50
|
-
results[:success] = false
|
|
51
|
-
results[:errors] << "Error during function creation: #{e.message}"
|
|
52
|
-
# Don't raise here, let it fall through to ensure block for rollback
|
|
53
|
-
end
|
|
54
|
-
|
|
55
|
-
# Try to invoke function directly (if test context provided)
|
|
56
|
-
# Note: Empty hash {} is not "present" in Rails, so check if it's not nil
|
|
57
|
-
if results[:function_created]
|
|
58
|
-
# This would require custom invocation logic
|
|
59
|
-
# For now, just verify it was created - if function was successfully created,
|
|
60
|
-
# we can assume it exists and is executable within the transaction
|
|
61
|
-
function_name = nil
|
|
62
|
-
|
|
63
|
-
# First, try to extract from function_body (most reliable)
|
|
64
|
-
if @trigger.function_body.present?
|
|
65
|
-
# Extract function name from CREATE FUNCTION statement
|
|
66
|
-
# Match: CREATE [OR REPLACE] FUNCTION function_name(...)
|
|
67
|
-
pattern = /CREATE\s+(?:OR\s+REPLACE\s+)?FUNCTION\s+([a-zA-Z_][a-zA-Z0-9_]*)\s*\(/i
|
|
68
|
-
match = @trigger.function_body.match(pattern)
|
|
69
|
-
function_name = match[1] if match
|
|
70
|
-
end
|
|
71
|
-
|
|
72
|
-
# Fallback to definition JSON if function_body extraction failed
|
|
73
|
-
if function_name.blank? && @trigger.definition.present?
|
|
74
|
-
definition = begin
|
|
75
|
-
JSON.parse(@trigger.definition)
|
|
76
|
-
rescue StandardError
|
|
77
|
-
{}
|
|
78
|
-
end
|
|
79
|
-
function_name = definition["function_name"] || definition[:function_name] ||
|
|
80
|
-
definition["name"] || definition[:name]
|
|
81
|
-
end
|
|
82
|
-
|
|
83
|
-
# Verify function exists in database by checking pg_proc
|
|
84
|
-
# Try to verify via query if function_name is available
|
|
85
|
-
if function_name.present?
|
|
86
|
-
begin
|
|
87
|
-
sanitized_name = begin
|
|
88
|
-
ActiveRecord::Base.connection.quote_string(function_name)
|
|
89
|
-
rescue StandardError => e
|
|
90
|
-
# If quote_string fails, use the function name as-is (less safe but allows test to continue)
|
|
91
|
-
results[:errors] << "Error during function name sanitization: #{e.message}"
|
|
92
|
-
function_name
|
|
93
|
-
end
|
|
94
|
-
check_sql = <<~SQL.squish
|
|
95
|
-
SELECT COUNT(*) as count
|
|
96
|
-
FROM pg_proc p
|
|
97
|
-
JOIN pg_namespace n ON p.pronamespace = n.oid
|
|
98
|
-
WHERE p.proname = '#{sanitized_name}'
|
|
99
|
-
AND n.nspname = 'public'
|
|
100
|
-
SQL
|
|
101
|
-
|
|
102
|
-
result = ActiveRecord::Base.connection.execute(check_sql).first
|
|
103
|
-
results[:function_executed] = result && result["count"].to_i.positive?
|
|
104
|
-
results[:output] << if results[:function_executed]
|
|
105
|
-
"✓ Function exists and is callable"
|
|
106
|
-
else
|
|
107
|
-
"✓ Function created (verified via successful creation)"
|
|
108
|
-
end
|
|
109
|
-
rescue ActiveRecord::StatementInvalid, StandardError => e
|
|
110
|
-
results[:function_executed] = false
|
|
111
|
-
results[:success] = false
|
|
112
|
-
results[:errors] << "Error during function verification: #{e.message}"
|
|
113
|
-
# Also add the original error message to ensure it's searchable in tests
|
|
114
|
-
results[:errors] << e.message unless results[:errors].include?(e.message)
|
|
115
|
-
results[:output] << "✓ Function created (verification failed)"
|
|
116
|
-
end
|
|
117
|
-
else
|
|
118
|
-
# If we can't extract function name, assume it was created successfully
|
|
119
|
-
# since function_created is true
|
|
120
|
-
results[:function_executed] = true
|
|
121
|
-
results[:output] << "✓ Function created (execution verified via successful creation)"
|
|
122
|
-
end
|
|
123
|
-
end
|
|
124
|
-
|
|
125
|
-
# Set success to true only if no errors occurred and function was created
|
|
62
|
+
create_function_in_transaction(results)
|
|
63
|
+
verify_function_in_transaction(results) if results[:function_created]
|
|
126
64
|
results[:success] = results[:errors].empty? && results[:function_created]
|
|
127
65
|
rescue ActiveRecord::StatementInvalid, StandardError => e
|
|
128
66
|
results[:success] = false
|
|
@@ -130,11 +68,63 @@ module PgSqlTriggers
|
|
|
130
68
|
ensure
|
|
131
69
|
raise ActiveRecord::Rollback
|
|
132
70
|
end
|
|
71
|
+
end
|
|
133
72
|
|
|
134
|
-
|
|
135
|
-
|
|
73
|
+
def create_function_in_transaction(results)
|
|
74
|
+
ActiveRecord::Base.connection.execute(@trigger.function_body)
|
|
75
|
+
results[:function_created] = true
|
|
76
|
+
results[:output] << "✓ Function created in test transaction"
|
|
77
|
+
rescue ActiveRecord::StatementInvalid, StandardError => e
|
|
78
|
+
results[:success] = false
|
|
79
|
+
results[:errors] << "Error during function creation: #{e.message}"
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def verify_function_in_transaction(results)
|
|
83
|
+
function_name = extract_function_name_from_body || extract_function_name_from_definition
|
|
84
|
+
|
|
85
|
+
if function_name.blank?
|
|
86
|
+
results[:function_executed] = true
|
|
87
|
+
results[:output] << "✓ Function created (execution verified via successful creation)"
|
|
88
|
+
return
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
verify_function_in_pg_proc(function_name, results)
|
|
136
92
|
end
|
|
137
|
-
|
|
93
|
+
|
|
94
|
+
def verify_function_in_pg_proc(function_name, results)
|
|
95
|
+
sanitized_name = safe_quote_function_name(function_name, results)
|
|
96
|
+
check_sql = <<~SQL.squish
|
|
97
|
+
SELECT COUNT(*) as count
|
|
98
|
+
FROM pg_proc p
|
|
99
|
+
JOIN pg_namespace n ON p.pronamespace = n.oid
|
|
100
|
+
WHERE p.proname = '#{sanitized_name}'
|
|
101
|
+
AND n.nspname = 'public'
|
|
102
|
+
SQL
|
|
103
|
+
|
|
104
|
+
result = ActiveRecord::Base.connection.execute(check_sql).first
|
|
105
|
+
results[:function_executed] = result && result["count"].to_i.positive?
|
|
106
|
+
results[:output] << if results[:function_executed]
|
|
107
|
+
"✓ Function exists and is callable"
|
|
108
|
+
else
|
|
109
|
+
"✓ Function created (verified via successful creation)"
|
|
110
|
+
end
|
|
111
|
+
rescue ActiveRecord::StatementInvalid, StandardError => e
|
|
112
|
+
results[:function_executed] = false
|
|
113
|
+
results[:success] = false
|
|
114
|
+
results[:errors] << "Error during function verification: #{e.message}"
|
|
115
|
+
results[:errors] << e.message unless results[:errors].include?(e.message)
|
|
116
|
+
results[:output] << "✓ Function created (verification failed)"
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def safe_quote_function_name(function_name, results)
|
|
120
|
+
ActiveRecord::Base.connection.quote_string(function_name)
|
|
121
|
+
rescue StandardError => e
|
|
122
|
+
# If quote_string fails, use the function name as-is (less safe but allows test to continue)
|
|
123
|
+
results[:errors] << "Error during function name sanitization: #{e.message}"
|
|
124
|
+
function_name
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
public
|
|
138
128
|
|
|
139
129
|
# Check if function already exists in database
|
|
140
130
|
def function_exists?
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "pathname"
|
|
4
|
+
require "time"
|
|
5
|
+
|
|
6
|
+
module PgSqlTriggers
|
|
7
|
+
# Builds a SQL snapshot of PostgreSQL triggers for db/trigger_structure.sql and
|
|
8
|
+
# emits schema.rb annotations so teams know triggers are outside schema.rb.
|
|
9
|
+
class TriggerStructureDumper
|
|
10
|
+
class << self
|
|
11
|
+
def resolve_path(override = nil)
|
|
12
|
+
base = override || PgSqlTriggers.trigger_structure_sql_path
|
|
13
|
+
resolved = base.respond_to?(:call) ? base.call : base
|
|
14
|
+
resolved ||= default_path
|
|
15
|
+
Pathname(resolved.to_s)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def default_path
|
|
19
|
+
raise "Rails.root is required" unless defined?(Rails) && Rails.respond_to?(:root) && Rails.root
|
|
20
|
+
|
|
21
|
+
Rails.root.join("db/trigger_structure.sql")
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def dump_to(path = nil, connection: ActiveRecord::Base.connection)
|
|
25
|
+
target = resolve_path(path)
|
|
26
|
+
target.dirname.mkpath
|
|
27
|
+
target.write(generate_sql(connection: connection))
|
|
28
|
+
target
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def load_from(path = nil, connection: ActiveRecord::Base.connection)
|
|
32
|
+
target = resolve_path(path)
|
|
33
|
+
raise Errno::ENOENT, target.to_s unless target.file?
|
|
34
|
+
|
|
35
|
+
sql = target.read
|
|
36
|
+
return if sql.strip.empty?
|
|
37
|
+
|
|
38
|
+
connection.raw_connection.exec(sql)
|
|
39
|
+
nil
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def generate_sql(connection: ActiveRecord::Base.connection)
|
|
43
|
+
header = <<~SQL
|
|
44
|
+
-- pg_sql_triggers trigger_structure.sql
|
|
45
|
+
-- Generated at: #{Time.now.utc.iso8601}
|
|
46
|
+
--
|
|
47
|
+
-- Apply with: bin/rails trigger:load
|
|
48
|
+
-- Prefer checking this file into version control alongside db/triggers migrations.
|
|
49
|
+
SQL
|
|
50
|
+
|
|
51
|
+
rows = trigger_rows(connection)
|
|
52
|
+
parts = [header]
|
|
53
|
+
|
|
54
|
+
rows.each do |row|
|
|
55
|
+
trigger_name = row["trigger_name"] || row[:trigger_name]
|
|
56
|
+
parts << ""
|
|
57
|
+
parts << "-- Trigger: #{trigger_name}"
|
|
58
|
+
append_definition(parts, row["function_definition"] || row[:function_definition])
|
|
59
|
+
append_definition(parts, row["trigger_definition"] || row[:trigger_definition])
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
parts.join("\n").strip.concat("\n")
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def schema_rb_annotation(connection: ActiveRecord::Base.connection)
|
|
66
|
+
names = managed_trigger_names(connection)
|
|
67
|
+
lines = []
|
|
68
|
+
lines << " # ---------------------------------------------------------------------------"
|
|
69
|
+
lines << " # pg_sql_triggers: PostgreSQL triggers are not captured in schema.rb."
|
|
70
|
+
lines << " # After db:schema:load, run: bin/rails trigger:migrate (or trigger:load)."
|
|
71
|
+
lines << " # SQL snapshot: db/trigger_structure.sql (bin/rails trigger:dump)."
|
|
72
|
+
lines << " # For full fidelity use config.active_record.schema_format = :sql."
|
|
73
|
+
lines << if names.any?
|
|
74
|
+
" # Managed triggers (#{names.length}): #{names.join(', ')}"
|
|
75
|
+
else
|
|
76
|
+
" # Managed triggers: (none registered in pg_sql_triggers_registry)"
|
|
77
|
+
end
|
|
78
|
+
lines << " # ---------------------------------------------------------------------------"
|
|
79
|
+
lines.join("\n")
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def managed_trigger_names(connection)
|
|
83
|
+
return [] unless connection.table_exists?("pg_sql_triggers_registry")
|
|
84
|
+
|
|
85
|
+
connection.select_values(
|
|
86
|
+
"SELECT trigger_name FROM pg_sql_triggers_registry ORDER BY trigger_name"
|
|
87
|
+
)
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
private
|
|
91
|
+
|
|
92
|
+
def trigger_rows(connection)
|
|
93
|
+
if connection.table_exists?("pg_sql_triggers_registry")
|
|
94
|
+
managed_trigger_names(connection).filter_map do |name|
|
|
95
|
+
PgSqlTriggers::Drift::DbQueries.find_trigger(name)
|
|
96
|
+
end
|
|
97
|
+
else
|
|
98
|
+
PgSqlTriggers::Drift::DbQueries.all_triggers
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def append_definition(parts, definition)
|
|
103
|
+
stmt = definition.to_s.strip
|
|
104
|
+
return if stmt.empty?
|
|
105
|
+
|
|
106
|
+
stmt += ";" unless stmt.end_with?(";")
|
|
107
|
+
parts << stmt
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
end
|
data/lib/pg_sql_triggers.rb
CHANGED
|
@@ -36,6 +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
|
+
|
|
48
|
+
# PostgreSQL schema used by DbQueries. Override for non-public schemas.
|
|
49
|
+
mattr_accessor :db_schema
|
|
50
|
+
self.db_schema = "public"
|
|
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
|
+
|
|
39
57
|
# Drift states
|
|
40
58
|
DRIFT_STATE_IN_SYNC = "in_sync"
|
|
41
59
|
DRIFT_STATE_DRIFTED = "drifted"
|
|
@@ -55,8 +73,10 @@ module PgSqlTriggers
|
|
|
55
73
|
autoload :Permissions, "pg_sql_triggers/permissions"
|
|
56
74
|
autoload :SQL, "pg_sql_triggers/sql"
|
|
57
75
|
autoload :DatabaseIntrospection, "pg_sql_triggers/database_introspection"
|
|
58
|
-
autoload :Generator, "pg_sql_triggers/generator"
|
|
59
76
|
autoload :Testing, "pg_sql_triggers/testing"
|
|
60
77
|
autoload :Migration, "pg_sql_triggers/migration"
|
|
61
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"
|
|
62
82
|
end
|