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
data/docs/getting-started.md
CHANGED
|
@@ -34,6 +34,20 @@ This will:
|
|
|
34
34
|
2. Create migrations for the registry table
|
|
35
35
|
3. Mount the engine at `/pg_sql_triggers`
|
|
36
36
|
|
|
37
|
+
## Gem schema migrations
|
|
38
|
+
|
|
39
|
+
The gem’s Rails schema migrations live under `db/migrate/` in this repository. Versions use the standard **`YYYYMMDDHHMMSS`** prefix (14 digits: date and clock time). Rails runs them in version order.
|
|
40
|
+
|
|
41
|
+
Run order (oldest first):
|
|
42
|
+
|
|
43
|
+
1. `20251222104327_create_pg_sql_triggers_tables.rb` — creates `pg_sql_triggers_registry`
|
|
44
|
+
2. `20251229071916_add_timing_to_pg_sql_triggers_registry.rb` — adds `timing` on the registry
|
|
45
|
+
3. `20260103114508_create_pg_sql_triggers_audit_log.rb` — creates `pg_sql_triggers_audit_log`
|
|
46
|
+
4. `20260228162233_add_for_each_to_pg_sql_triggers_registry.rb` — adds `for_each` on the registry
|
|
47
|
+
5. `20260412185841_add_constraint_deferral_to_pg_sql_triggers_registry.rb` — adds `constraint_trigger`, `deferrable`, `initially` on the registry
|
|
48
|
+
|
|
49
|
+
If you upgrade the gem and rename migration files locally, update the corresponding rows in `schema_migrations` so the version strings match these filenames.
|
|
50
|
+
|
|
37
51
|
## Verify Installation
|
|
38
52
|
|
|
39
53
|
After installation, you should have:
|
|
@@ -45,9 +59,19 @@ After installation, you should have:
|
|
|
45
59
|
|
|
46
60
|
## Quick Start Example
|
|
47
61
|
|
|
48
|
-
### 1.
|
|
62
|
+
### 1. Scaffold a Trigger (DSL + Migration)
|
|
63
|
+
|
|
64
|
+
Use the CLI generator to create both files at once:
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
rails generate pg_sql_triggers:trigger users_email_validation users insert update --timing before --function validate_user_email
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
This creates:
|
|
71
|
+
- `app/triggers/users_email_validation.rb` — DSL stub
|
|
72
|
+
- `db/triggers/TIMESTAMP_users_email_validation.rb` — migration with function + trigger SQL
|
|
49
73
|
|
|
50
|
-
|
|
74
|
+
Edit the DSL stub to match your requirements:
|
|
51
75
|
|
|
52
76
|
```ruby
|
|
53
77
|
# app/triggers/users_email_validation.rb
|
|
@@ -56,21 +80,12 @@ PgSqlTriggers::DSL.pg_sql_trigger "users_email_validation" do
|
|
|
56
80
|
on :insert, :update
|
|
57
81
|
function :validate_user_email
|
|
58
82
|
|
|
59
|
-
version 1
|
|
60
|
-
enabled
|
|
61
|
-
|
|
62
|
-
when_env :production
|
|
83
|
+
self.version = 1
|
|
84
|
+
self.enabled = true
|
|
85
|
+
timing :before
|
|
63
86
|
end
|
|
64
87
|
```
|
|
65
88
|
|
|
66
|
-
### 2. Generate a Migration
|
|
67
|
-
|
|
68
|
-
Create a trigger migration to implement the function:
|
|
69
|
-
|
|
70
|
-
```bash
|
|
71
|
-
rails generate trigger:migration add_email_validation
|
|
72
|
-
```
|
|
73
|
-
|
|
74
89
|
Edit the generated migration in `db/triggers/`:
|
|
75
90
|
|
|
76
91
|
```ruby
|
|
@@ -104,7 +119,7 @@ class AddEmailValidation < PgSqlTriggers::Migration
|
|
|
104
119
|
end
|
|
105
120
|
```
|
|
106
121
|
|
|
107
|
-
###
|
|
122
|
+
### 2. Run the Migration
|
|
108
123
|
|
|
109
124
|
Apply the trigger migration:
|
|
110
125
|
|
|
@@ -112,7 +127,7 @@ Apply the trigger migration:
|
|
|
112
127
|
rake trigger:migrate
|
|
113
128
|
```
|
|
114
129
|
|
|
115
|
-
###
|
|
130
|
+
### 3. Access the Web UI
|
|
116
131
|
|
|
117
132
|
Open your browser and navigate to:
|
|
118
133
|
|
data/docs/kill-switch.md
CHANGED
|
File without changes
|
data/docs/permissions.md
CHANGED
|
@@ -18,7 +18,7 @@ The permission system provides three levels of access:
|
|
|
18
18
|
|
|
19
19
|
- **Viewer**: Read-only access to view triggers and their status
|
|
20
20
|
- **Operator**: Can enable/disable triggers, apply migrations, generate triggers
|
|
21
|
-
- **Admin**: Full access including drop, re-execute, and
|
|
21
|
+
- **Admin**: Full access including drop, re-execute, and the reserved `execute_sql` permission action for host-defined privileged SQL
|
|
22
22
|
|
|
23
23
|
By default, all permissions are allowed (permissive mode). **You must configure permissions in production** to enforce access controls.
|
|
24
24
|
|
|
@@ -49,7 +49,7 @@ Admins have full access:
|
|
|
49
49
|
- All Operator permissions
|
|
50
50
|
- Drop triggers
|
|
51
51
|
- Re-execute triggers
|
|
52
|
-
-
|
|
52
|
+
- `execute_sql` (privileged SQL; for custom integrations — not used by built-in UI)
|
|
53
53
|
- Override drift detection
|
|
54
54
|
|
|
55
55
|
## Actions and Required Roles
|
|
@@ -67,7 +67,7 @@ The following actions are mapped to permission levels:
|
|
|
67
67
|
| `generate_trigger` | Operator | Generate triggers via UI |
|
|
68
68
|
| `test_trigger` | Operator | Test trigger functions |
|
|
69
69
|
| `drop_trigger` | Admin | Drop a trigger from database |
|
|
70
|
-
| `execute_sql` | Admin |
|
|
70
|
+
| `execute_sql` | Admin | Privileged SQL (`permission_checker` / custom tooling; not used by built-in UI) |
|
|
71
71
|
| `override_drift` | Admin | Override drift detection warnings |
|
|
72
72
|
|
|
73
73
|
## Configuration
|
|
@@ -272,12 +272,9 @@ PgSqlTriggers::Registry.drop(
|
|
|
272
272
|
confirmation: "EXECUTE TRIGGER_DROP"
|
|
273
273
|
)
|
|
274
274
|
|
|
275
|
-
#
|
|
276
|
-
PgSqlTriggers::
|
|
277
|
-
|
|
278
|
-
actor: current_user,
|
|
279
|
-
confirmation: "EXECUTE SQL"
|
|
280
|
-
)
|
|
275
|
+
# Custom tooling: gate raw SQL with the Admin-level :execute_sql action (requires Admin)
|
|
276
|
+
PgSqlTriggers::Permissions.check!(current_user, :execute_sql)
|
|
277
|
+
# ... your application's SQL execution ...
|
|
281
278
|
```
|
|
282
279
|
|
|
283
280
|
### Permission Errors in Console
|
data/docs/troubleshooting.md
CHANGED
|
File without changes
|
data/docs/ui-guide.md
CHANGED
|
File without changes
|
data/docs/usage-guide.md
CHANGED
|
@@ -15,7 +15,7 @@ PgSqlTriggers provides a Ruby DSL for defining triggers. Trigger definitions are
|
|
|
15
15
|
|
|
16
16
|
### Basic Trigger Definition
|
|
17
17
|
|
|
18
|
-
Create trigger definition files in `app/triggers
|
|
18
|
+
Create trigger definition files in `app/triggers/` (or scaffold them with the CLI generator — see below):
|
|
19
19
|
|
|
20
20
|
```ruby
|
|
21
21
|
# app/triggers/users_email_validation.rb
|
|
@@ -24,11 +24,9 @@ PgSqlTriggers::DSL.pg_sql_trigger "users_email_validation" do
|
|
|
24
24
|
on :insert, :update
|
|
25
25
|
function :validate_user_email
|
|
26
26
|
|
|
27
|
-
version 1
|
|
28
|
-
enabled
|
|
27
|
+
self.version = 1
|
|
28
|
+
self.enabled = true
|
|
29
29
|
timing :before
|
|
30
|
-
|
|
31
|
-
when_env :production
|
|
32
30
|
end
|
|
33
31
|
```
|
|
34
32
|
|
|
@@ -61,25 +59,30 @@ function :validate_user_email
|
|
|
61
59
|
Version number for tracking changes:
|
|
62
60
|
|
|
63
61
|
```ruby
|
|
64
|
-
version 1 # Increment when trigger logic changes
|
|
62
|
+
self.version = 1 # Increment when trigger logic changes
|
|
65
63
|
```
|
|
66
64
|
|
|
67
65
|
#### `enabled`
|
|
68
|
-
Initial state of the trigger:
|
|
66
|
+
Initial state of the trigger (defaults to `true`):
|
|
69
67
|
|
|
70
68
|
```ruby
|
|
71
|
-
enabled true # Trigger is active
|
|
72
|
-
enabled false # Trigger is inactive
|
|
69
|
+
self.enabled = true # Trigger is active (default)
|
|
70
|
+
self.enabled = false # Trigger is inactive
|
|
73
71
|
```
|
|
74
72
|
|
|
75
|
-
#### `
|
|
76
|
-
|
|
73
|
+
#### `for_each_row` / `for_each_statement`
|
|
74
|
+
PostgreSQL execution granularity (defaults to row-level):
|
|
77
75
|
|
|
78
76
|
```ruby
|
|
79
|
-
|
|
80
|
-
|
|
77
|
+
for_each_row # FOR EACH ROW (default)
|
|
78
|
+
for_each_statement # FOR EACH STATEMENT
|
|
81
79
|
```
|
|
82
80
|
|
|
81
|
+
#### `when_env`
|
|
82
|
+
**Deprecated.** Environment-specific trigger declarations cause schema drift between environments.
|
|
83
|
+
Use application-level configuration to gate trigger behaviour by environment instead.
|
|
84
|
+
Calling `when_env` emits a deprecation warning and will be removed in a future major version.
|
|
85
|
+
|
|
83
86
|
#### `timing`
|
|
84
87
|
Specifies when the trigger fires relative to the event (BEFORE or AFTER):
|
|
85
88
|
|
|
@@ -88,85 +91,127 @@ timing :before # Trigger fires before constraint checks (default)
|
|
|
88
91
|
timing :after # Trigger fires after constraint checks
|
|
89
92
|
```
|
|
90
93
|
|
|
91
|
-
|
|
94
|
+
#### `on_update_of`
|
|
95
|
+
Declares a **column-level** trigger that fires only when specific columns change
|
|
96
|
+
(PostgreSQL `UPDATE OF col1, col2`). This is a common performance optimisation for audit
|
|
97
|
+
triggers: they run only when the columns you care about are modified.
|
|
92
98
|
|
|
93
99
|
```ruby
|
|
94
|
-
#
|
|
95
|
-
|
|
100
|
+
on_update_of :email, :status # Sets event to UPDATE and records columns
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Notes:
|
|
104
|
+
- `on_update_of` sets the event to `update` and stores the column list; calling `on(...)` again
|
|
105
|
+
clears the column list.
|
|
106
|
+
- Column names must be simple SQL identifiers (`[a-zA-Z_][a-zA-Z0-9_]*`). The gem quotes them
|
|
107
|
+
in generated SQL, so pass them unquoted.
|
|
108
|
+
- The column list is included in the checksum, so changing the list is detected as drift.
|
|
109
|
+
|
|
110
|
+
#### `constraint_trigger!` / `deferrable` / `initially`
|
|
111
|
+
Declares a **constraint trigger** (`CREATE CONSTRAINT TRIGGER`) with optional deferral. Constraint
|
|
112
|
+
triggers must be `AFTER` triggers, cannot use `TRUNCATE`, and are the only triggers that can be
|
|
113
|
+
deferred.
|
|
114
|
+
|
|
115
|
+
```ruby
|
|
116
|
+
PgSqlTriggers::DSL.pg_sql_trigger "orders_integrity_check" do
|
|
96
117
|
table :orders
|
|
97
118
|
on :insert, :update
|
|
98
|
-
function :
|
|
119
|
+
function :check_orders_referential_integrity
|
|
120
|
+
|
|
121
|
+
constraint_trigger! # Emits CREATE CONSTRAINT TRIGGER, forces timing :after
|
|
122
|
+
self.deferrable = :deferrable
|
|
123
|
+
self.initially = :deferred # Evaluate at transaction commit
|
|
124
|
+
end
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
Valid combinations:
|
|
128
|
+
|
|
129
|
+
| `deferrable` | `initially` | SQL clause |
|
|
130
|
+
|---------------------|----------------------------|-------------------------------------|
|
|
131
|
+
| `:deferrable` | `:deferred` | `DEFERRABLE INITIALLY DEFERRED` |
|
|
132
|
+
| `:deferrable` | `:immediate` (or `nil`) | `DEFERRABLE INITIALLY IMMEDIATE` |
|
|
133
|
+
| `:not_deferrable` | (must be `nil`) | `NOT DEFERRABLE` |
|
|
134
|
+
| `nil` | `nil` | (no deferral clause) |
|
|
99
135
|
|
|
100
|
-
|
|
101
|
-
|
|
136
|
+
`Registry::Validator` rejects `deferrable`/`initially` unless `constraint_trigger!` is set,
|
|
137
|
+
and rejects constraint triggers that use `:before` timing or `TRUNCATE` events. Drift detection
|
|
138
|
+
reads deferral state from `pg_trigger.tgdeferrable` / `tginitdeferred` so changes made outside
|
|
139
|
+
the gem are surfaced as drift.
|
|
140
|
+
|
|
141
|
+
#### `depends_on`
|
|
142
|
+
Declares an **ordering hint** relative to another trigger on the same table. PostgreSQL fires
|
|
143
|
+
same-kind triggers in alphabetical order by name; `depends_on` captures the intended order so the
|
|
144
|
+
registry validator can verify that naming matches dependency declarations.
|
|
145
|
+
|
|
146
|
+
```ruby
|
|
147
|
+
PgSqlTriggers::DSL.pg_sql_trigger "log_user_change" do
|
|
148
|
+
table :users
|
|
149
|
+
on :insert, :update
|
|
150
|
+
function :log_user_change
|
|
102
151
|
timing :after
|
|
103
152
|
|
|
104
|
-
|
|
153
|
+
depends_on "validate_user_email" # Must exist on same table, same timing/FOR EACH, overlapping events
|
|
105
154
|
end
|
|
106
155
|
```
|
|
107
156
|
|
|
108
|
-
|
|
157
|
+
Run `rake trigger:validate_order` (or `PgSqlTriggers::Registry.validate!`) to enforce:
|
|
109
158
|
|
|
110
|
-
|
|
159
|
+
- Referenced prerequisite triggers exist and are on the same table.
|
|
160
|
+
- Prerequisites share timing (`before`/`after`), `FOR EACH` granularity, and have at least one
|
|
161
|
+
overlapping event with the dependent.
|
|
162
|
+
- There are no circular dependencies.
|
|
163
|
+
- Names sort alphabetically in the required order (the prerequisite's name must sort before
|
|
164
|
+
the dependent's).
|
|
111
165
|
|
|
112
|
-
|
|
166
|
+
The trigger detail page in the web UI displays prerequisites and dependents for DSL triggers.
|
|
113
167
|
|
|
114
|
-
|
|
168
|
+
### Complete Example
|
|
115
169
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
4. Create the trigger files
|
|
170
|
+
```ruby
|
|
171
|
+
# app/triggers/orders_billing_trigger.rb
|
|
172
|
+
PgSqlTriggers::DSL.pg_sql_trigger "orders_billing_trigger" do
|
|
173
|
+
table :orders
|
|
174
|
+
on :insert, :update
|
|
175
|
+
function :calculate_order_total
|
|
176
|
+
|
|
177
|
+
self.version = 2
|
|
178
|
+
self.enabled = true
|
|
179
|
+
timing :after
|
|
180
|
+
for_each_row
|
|
181
|
+
end
|
|
182
|
+
```
|
|
130
183
|
|
|
131
|
-
|
|
132
|
-
- A DSL definition file in `app/triggers/`
|
|
133
|
-
- A migration file in `db/triggers/`
|
|
134
|
-
- A registry entry in the database
|
|
184
|
+
## Trigger Generator
|
|
135
185
|
|
|
136
|
-
|
|
186
|
+
PgSqlTriggers provides a CLI generator that scaffolds a DSL definition and migration together from the command line.
|
|
137
187
|
|
|
138
|
-
|
|
188
|
+
### CLI Generator
|
|
139
189
|
|
|
140
190
|
```bash
|
|
141
|
-
|
|
142
|
-
|
|
191
|
+
rails generate pg_sql_triggers:trigger TRIGGER_NAME TABLE_NAME [EVENTS...] [--timing before|after] [--function fn_name]
|
|
192
|
+
```
|
|
143
193
|
|
|
144
|
-
|
|
145
|
-
|
|
194
|
+
Example:
|
|
195
|
+
|
|
196
|
+
```bash
|
|
197
|
+
rails generate pg_sql_triggers:trigger users_email_validation users insert update --timing before --function validate_user_email
|
|
146
198
|
```
|
|
147
199
|
|
|
148
|
-
This creates
|
|
200
|
+
This creates:
|
|
201
|
+
- `app/triggers/users_email_validation.rb` — DSL stub
|
|
202
|
+
- `db/triggers/TIMESTAMP_users_email_validation.rb` — migration with function + trigger SQL
|
|
203
|
+
|
|
204
|
+
Files are written directly into the working tree and go through version control and code review like any other source file.
|
|
149
205
|
|
|
150
|
-
### Generator
|
|
206
|
+
### Migration-Only Generator
|
|
151
207
|
|
|
152
|
-
|
|
153
|
-
- **Function Name Formatting**: Automatically quotes function names with special characters
|
|
154
|
-
- **Multiple Environments**: Supports multiple environment restrictions
|
|
155
|
-
- **Condition Escaping**: Properly escapes quotes in WHEN conditions
|
|
156
|
-
- **Event Combinations**: Handles single or multiple events (INSERT, UPDATE, DELETE, TRUNCATE)
|
|
157
|
-
- **Migration Numbering**: Automatically generates sequential migration numbers
|
|
158
|
-
- **Error Handling**: Graceful error handling with detailed error messages
|
|
208
|
+
To generate a standalone trigger migration without a DSL stub:
|
|
159
209
|
|
|
160
|
-
|
|
210
|
+
```bash
|
|
211
|
+
rails generate pg_sql_triggers:trigger_migration add_user_validation
|
|
212
|
+
```
|
|
161
213
|
|
|
162
|
-
|
|
163
|
-
- Function names with special characters (quoted vs unquoted)
|
|
164
|
-
- Multiple environments in a single trigger
|
|
165
|
-
- Complex WHEN conditions with quotes
|
|
166
|
-
- All event type combinations
|
|
167
|
-
- Standalone gem usage (without Rails context)
|
|
168
|
-
- Migration number collisions
|
|
169
|
-
- Blank events and environments (filtered automatically)
|
|
214
|
+
This creates a timestamped file in `db/triggers/` that you can edit to add your trigger logic.
|
|
170
215
|
|
|
171
216
|
## Trigger Migrations
|
|
172
217
|
|
|
@@ -177,7 +222,7 @@ Trigger migrations work similarly to Rails schema migrations but are specificall
|
|
|
177
222
|
Create a new trigger migration:
|
|
178
223
|
|
|
179
224
|
```bash
|
|
180
|
-
rails generate
|
|
225
|
+
rails generate pg_sql_triggers:trigger_migration add_validation_trigger
|
|
181
226
|
```
|
|
182
227
|
|
|
183
228
|
This creates a timestamped file in `db/triggers/`:
|
data/docs/web-ui.md
CHANGED
|
@@ -8,7 +8,6 @@ The PgSqlTriggers web interface provides a visual dashboard for managing trigger
|
|
|
8
8
|
- [Dashboard Overview](#dashboard-overview)
|
|
9
9
|
- [Managing Triggers](#managing-triggers)
|
|
10
10
|
- [Migration Management](#migration-management)
|
|
11
|
-
- [SQL Capsules](#sql-capsules)
|
|
12
11
|
- [Audit Log](#audit-log)
|
|
13
12
|
- [Permissions and Safety](#permissions-and-safety)
|
|
14
13
|
|
|
@@ -96,7 +95,7 @@ Each table row displays:
|
|
|
96
95
|
- **Status**: Summary of enabled/disabled triggers
|
|
97
96
|
- **Actions**:
|
|
98
97
|
- "View Details" - Navigate to the table detail page
|
|
99
|
-
- "Create Trigger" -
|
|
98
|
+
- "Create Trigger" - Scaffold a new trigger for this table using the CLI generator (see [Trigger Generator](usage-guide.md#trigger-generator))
|
|
100
99
|
|
|
101
100
|
### Table Detail Page
|
|
102
101
|
|
|
@@ -303,83 +302,6 @@ After each migration action:
|
|
|
303
302
|
- **Error**: Red flash message with error details
|
|
304
303
|
- **Warnings**: Yellow flash message if issues occurred
|
|
305
304
|
|
|
306
|
-
## SQL Capsules
|
|
307
|
-
|
|
308
|
-
SQL Capsules provide emergency escape hatches for executing SQL directly with comprehensive safety checks and audit logging.
|
|
309
|
-
|
|
310
|
-
### When to Use SQL Capsules
|
|
311
|
-
|
|
312
|
-
Use SQL Capsules for:
|
|
313
|
-
- Emergency fixes in production
|
|
314
|
-
- Critical data corrections
|
|
315
|
-
- Testing SQL functions
|
|
316
|
-
- Debugging trigger behavior
|
|
317
|
-
- One-off database operations
|
|
318
|
-
|
|
319
|
-
### Creating and Executing SQL Capsules
|
|
320
|
-
|
|
321
|
-
1. Navigate to "SQL Capsules" → "New SQL Capsule"
|
|
322
|
-
2. Fill in the capsule form:
|
|
323
|
-
- **Name**: Unique identifier (alphanumeric, underscores, hyphens only)
|
|
324
|
-
- **Environment**: Target environment (e.g., production, staging)
|
|
325
|
-
- **Purpose**: Detailed explanation of what the SQL does and why (required for audit trail)
|
|
326
|
-
- **SQL**: The SQL statement(s) to execute
|
|
327
|
-
3. Click "Create and Execute" or "Save for Later"
|
|
328
|
-
4. Review the capsule details on the confirmation page
|
|
329
|
-
5. In protected environments, enter confirmation text when prompted
|
|
330
|
-
6. Click "Execute" to run the SQL
|
|
331
|
-
7. Review the execution results
|
|
332
|
-
|
|
333
|
-
### Viewing Capsule History
|
|
334
|
-
|
|
335
|
-
1. Navigate to "SQL Capsules" → "History"
|
|
336
|
-
2. View list of previously executed capsules with:
|
|
337
|
-
- Name and purpose
|
|
338
|
-
- Environment and timestamp
|
|
339
|
-
- SQL checksum
|
|
340
|
-
- Execution status
|
|
341
|
-
3. Click on a capsule to view details
|
|
342
|
-
4. Re-execute historical capsules if needed
|
|
343
|
-
|
|
344
|
-
### Safety Features
|
|
345
|
-
|
|
346
|
-
- **Admin Permission Required**: Only Admin users can create and execute SQL capsules
|
|
347
|
-
- **Production Protection**: Requires typed confirmation in protected environments
|
|
348
|
-
- **Kill Switch Integration**: All executions are protected by kill switch
|
|
349
|
-
- **Comprehensive Logging**: All operations logged with actor, timestamp, and checksum
|
|
350
|
-
- **Transactional Execution**: SQL runs in a transaction and rolls back on error
|
|
351
|
-
- **Registry Storage**: All capsules are stored in the registry with checksums
|
|
352
|
-
- **Purpose Tracking**: Required purpose field ensures all executions are documented
|
|
353
|
-
|
|
354
|
-
### Example SQL Capsules
|
|
355
|
-
|
|
356
|
-
#### View All Triggers
|
|
357
|
-
```sql
|
|
358
|
-
SELECT
|
|
359
|
-
trigger_name,
|
|
360
|
-
event_object_table,
|
|
361
|
-
action_timing,
|
|
362
|
-
event_manipulation
|
|
363
|
-
FROM information_schema.triggers
|
|
364
|
-
WHERE trigger_schema = 'public';
|
|
365
|
-
```
|
|
366
|
-
|
|
367
|
-
#### Check Function Definitions
|
|
368
|
-
```sql
|
|
369
|
-
SELECT
|
|
370
|
-
routine_name,
|
|
371
|
-
routine_type
|
|
372
|
-
FROM information_schema.routines
|
|
373
|
-
WHERE routine_schema = 'public'
|
|
374
|
-
AND routine_name LIKE '%trigger%';
|
|
375
|
-
```
|
|
376
|
-
|
|
377
|
-
#### Verify Trigger State
|
|
378
|
-
```sql
|
|
379
|
-
SELECT * FROM pg_sql_triggers_registry
|
|
380
|
-
WHERE trigger_name = 'users_email_validation';
|
|
381
|
-
```
|
|
382
|
-
|
|
383
305
|
## Audit Log
|
|
384
306
|
|
|
385
307
|
The Audit Log provides a comprehensive view of all trigger operations performed through the web UI, console APIs, and CLI. This feature is essential for compliance, debugging, and tracking changes to your trigger ecosystem.
|
|
@@ -446,7 +368,6 @@ All of the following operations are logged to the audit log:
|
|
|
446
368
|
- **Disable Trigger**: Success/failure, before/after state
|
|
447
369
|
- **Drop Trigger**: Success/failure, reason, state changes
|
|
448
370
|
- **Re-execute Trigger**: Success/failure, reason, drift diff information
|
|
449
|
-
- **SQL Capsule Execution**: Success/failure, capsule details
|
|
450
371
|
- **Migration Operations**: Up, down, and redo operations (infrastructure ready)
|
|
451
372
|
|
|
452
373
|
Each log entry includes:
|
|
@@ -497,7 +418,6 @@ Cannot:
|
|
|
497
418
|
#### Admin (Full Access)
|
|
498
419
|
- All Operator permissions
|
|
499
420
|
- Drop triggers
|
|
500
|
-
- Execute SQL via capsules
|
|
501
421
|
- Modify registry directly
|
|
502
422
|
|
|
503
423
|
### Kill Switch Protection
|
|
@@ -529,9 +449,9 @@ PgSqlTriggers.configure do |config|
|
|
|
529
449
|
case action
|
|
530
450
|
when :view_triggers, :view_diffs
|
|
531
451
|
user.present? # Viewer level
|
|
532
|
-
when :enable_trigger, :disable_trigger, :apply_trigger, :
|
|
452
|
+
when :enable_trigger, :disable_trigger, :apply_trigger, :test_trigger
|
|
533
453
|
user.operator? || user.admin? # Operator level
|
|
534
|
-
when :drop_trigger, :
|
|
454
|
+
when :drop_trigger, :override_drift
|
|
535
455
|
user.admin? # Admin level
|
|
536
456
|
else
|
|
537
457
|
false
|
|
@@ -550,32 +470,12 @@ end
|
|
|
550
470
|
### Main Dashboard
|
|
551
471
|

|
|
552
472
|
|
|
553
|
-
### Trigger Generator
|
|
554
|
-
|
|
555
|
-
The trigger generator provides a comprehensive form for creating triggers:
|
|
556
|
-
|
|
557
|
-
1. **Basic Information**: Trigger name, table name, function name, and function body
|
|
558
|
-
2. **Trigger Events**: Select timing (BEFORE/AFTER) and events (INSERT, UPDATE, DELETE, TRUNCATE)
|
|
559
|
-
3. **Configuration**: Version, environments, WHEN condition, and enabled state
|
|
560
|
-
4. **Preview**: Review generated DSL and migration code with timing and condition information
|
|
561
|
-
|
|
562
|
-
The preview page displays:
|
|
563
|
-
- Generated DSL code with timing
|
|
564
|
-
- Trigger configuration summary (timing, events, table, function, condition)
|
|
565
|
-
- PL/pgSQL function body (editable)
|
|
566
|
-
- SQL validation results
|
|
567
|
-
|
|
568
|
-

|
|
569
|
-
|
|
570
473
|
### Migration Management
|
|
571
474
|

|
|
572
475
|
|
|
573
476
|
### Kill Switch Protection
|
|
574
477
|

|
|
575
478
|
|
|
576
|
-
### SQL Capsules
|
|
577
|
-

|
|
578
|
-
|
|
579
479
|
## Dashboard Enhancements (v1.3.0+)
|
|
580
480
|
|
|
581
481
|
### Last Applied Column
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
@@ -66,4 +66,18 @@ PgSqlTriggers.configure do |config|
|
|
|
66
66
|
# You can also override per-migration with ALLOW_UNSAFE_MIGRATIONS=true environment variable
|
|
67
67
|
# Default: false (recommended for safety)
|
|
68
68
|
config.allow_unsafe_migrations = false
|
|
69
|
+
|
|
70
|
+
# ========== Schema / structure.sql integration ==========
|
|
71
|
+
# Triggers are not included in db/schema.rb. After db:schema:load, the engine runs
|
|
72
|
+
# trigger:migrate by default. Set SKIP_TRIGGER_MIGRATE_AFTER_SCHEMA_LOAD=1 to skip.
|
|
73
|
+
# config.migrate_triggers_after_schema_load = true
|
|
74
|
+
#
|
|
75
|
+
# Optional: append comments to schema.rb pointing at db/trigger_structure.sql.
|
|
76
|
+
# config.append_trigger_notes_to_schema_dump = true
|
|
77
|
+
#
|
|
78
|
+
# Snapshot live trigger SQL: bin/rails trigger:dump (apply with trigger:load).
|
|
79
|
+
# config.trigger_structure_sql_path = Rails.root.join("db/trigger_structure.sql")
|
|
80
|
+
#
|
|
81
|
+
# For databases where triggers must appear in the main SQL dump, prefer:
|
|
82
|
+
# config.active_record.schema_format = :sql
|
|
69
83
|
end
|
|
File without changes
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class <%= trigger_class_name %> < PgSqlTriggers::Migration
|
|
4
|
+
def up
|
|
5
|
+
execute <<-SQL
|
|
6
|
+
CREATE OR REPLACE FUNCTION <%= function_name %>()
|
|
7
|
+
RETURNS TRIGGER AS $$
|
|
8
|
+
BEGIN
|
|
9
|
+
-- TODO: implement trigger logic
|
|
10
|
+
RETURN NEW;
|
|
11
|
+
END;
|
|
12
|
+
$$ LANGUAGE plpgsql;
|
|
13
|
+
SQL
|
|
14
|
+
|
|
15
|
+
execute <<-SQL
|
|
16
|
+
CREATE TRIGGER <%= trigger_name %>
|
|
17
|
+
<%= timing.upcase %> <%= events_sql %> ON <%= table_name %>
|
|
18
|
+
FOR EACH ROW
|
|
19
|
+
EXECUTE FUNCTION <%= function_name %>();
|
|
20
|
+
SQL
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def down
|
|
24
|
+
execute <<-SQL
|
|
25
|
+
DROP TRIGGER IF EXISTS <%= trigger_name %> ON <%= table_name %>;
|
|
26
|
+
DROP FUNCTION IF EXISTS <%= function_name %>();
|
|
27
|
+
SQL
|
|
28
|
+
end
|
|
29
|
+
end
|