pg_sql_triggers 1.0.1 → 1.1.1
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/CHANGELOG.md +88 -0
- data/COVERAGE.md +58 -0
- data/Goal.md +180 -138
- data/README.md +8 -2
- data/RELEASE.md +1 -1
- data/app/controllers/pg_sql_triggers/dashboard_controller.rb +4 -1
- data/app/controllers/pg_sql_triggers/generator_controller.rb +67 -5
- data/app/models/pg_sql_triggers/trigger_registry.rb +73 -10
- data/app/views/pg_sql_triggers/generator/new.html.erb +18 -0
- data/app/views/pg_sql_triggers/generator/preview.html.erb +233 -13
- data/app/views/pg_sql_triggers/shared/_confirmation_modal.html.erb +32 -0
- data/config/initializers/pg_sql_triggers.rb +69 -0
- data/db/migrate/20251222000001_create_pg_sql_triggers_tables.rb +2 -0
- data/db/migrate/20251229071916_add_timing_to_pg_sql_triggers_registry.rb +8 -0
- data/docs/README.md +2 -2
- data/docs/api-reference.md +22 -4
- data/docs/getting-started.md +1 -1
- data/docs/kill-switch.md +3 -3
- data/docs/usage-guide.md +73 -0
- data/docs/web-ui.md +14 -0
- data/lib/generators/pg_sql_triggers/templates/README +1 -1
- data/lib/generators/pg_sql_triggers/templates/create_pg_sql_triggers_tables.rb +2 -0
- data/lib/generators/pg_sql_triggers/templates/initializer.rb +8 -0
- data/lib/pg_sql_triggers/drift/db_queries.rb +116 -0
- data/lib/pg_sql_triggers/drift/detector.rb +187 -0
- data/lib/pg_sql_triggers/drift/reporter.rb +179 -0
- data/lib/pg_sql_triggers/drift.rb +14 -11
- data/lib/pg_sql_triggers/dsl/trigger_definition.rb +15 -1
- data/lib/pg_sql_triggers/generator/form.rb +3 -1
- data/lib/pg_sql_triggers/generator/service.rb +81 -25
- data/lib/pg_sql_triggers/migrator/pre_apply_comparator.rb +344 -0
- data/lib/pg_sql_triggers/migrator/pre_apply_diff_reporter.rb +143 -0
- data/lib/pg_sql_triggers/migrator/safety_validator.rb +258 -0
- data/lib/pg_sql_triggers/migrator.rb +58 -0
- data/lib/pg_sql_triggers/registry/manager.rb +96 -9
- data/lib/pg_sql_triggers/testing/function_tester.rb +66 -24
- data/lib/pg_sql_triggers/testing/syntax_validator.rb +24 -1
- data/lib/pg_sql_triggers/version.rb +1 -1
- data/lib/pg_sql_triggers.rb +12 -0
- data/scripts/generate_coverage_report.rb +129 -0
- metadata +19 -12
data/Goal.md
CHANGED
|
@@ -83,7 +83,7 @@ Registry tracks:
|
|
|
83
83
|
- ~~table_name~~
|
|
84
84
|
- ~~version~~
|
|
85
85
|
- ~~enabled~~
|
|
86
|
-
- ~~checksum~~ (
|
|
86
|
+
- ~~checksum~~ (✅ fully implemented - consistent field-concatenation algorithm)
|
|
87
87
|
- ~~source (dsl / generated / manual_sql)~~
|
|
88
88
|
- ~~environment~~
|
|
89
89
|
- ~~installed_at~~
|
|
@@ -92,37 +92,39 @@ Registry tracks:
|
|
|
92
92
|
Rails must always know:
|
|
93
93
|
- ~~what exists~~
|
|
94
94
|
- ~~how it was created~~
|
|
95
|
-
-
|
|
95
|
+
- ✅ whether it drifted (drift detection fully implemented)
|
|
96
96
|
|
|
97
97
|
---
|
|
98
98
|
|
|
99
|
-
### D. Safe Apply & Deploy
|
|
99
|
+
### D. Safe Apply & Deploy ✅ (fully implemented via migrations)
|
|
100
100
|
|
|
101
101
|
Applying triggers must:
|
|
102
|
-
-
|
|
103
|
-
-
|
|
104
|
-
-
|
|
105
|
-
-
|
|
106
|
-
-
|
|
102
|
+
- ✅ Run in a transaction (migrations run in transactions)
|
|
103
|
+
- ✅ Diff expected vs actual (fully implemented - pre-apply comparison before migration execution)
|
|
104
|
+
- ✅ Never blindly DROP + CREATE (fully implemented - safety validator blocks unsafe DROP + CREATE patterns)
|
|
105
|
+
- ✅ Support rollback on failure (migration rollback exists)
|
|
106
|
+
- ✅ Update registry atomically (registry updated during migration execution)
|
|
107
|
+
|
|
108
|
+
**Status:** Core functionality fully implemented through migration system. Pre-apply comparison shows diff between expected (from migration) and actual (from database) state before applying migrations. Safety validator explicitly blocks unsafe DROP + CREATE operations, preventing migrations from blindly dropping and recreating existing database objects without validation.
|
|
107
109
|
|
|
108
110
|
---
|
|
109
111
|
|
|
110
|
-
### E. Drift Detection
|
|
112
|
+
### E. Drift Detection ✅ (fully implemented)
|
|
111
113
|
|
|
112
114
|
System must detect:
|
|
113
|
-
-
|
|
114
|
-
-
|
|
115
|
-
-
|
|
116
|
-
-
|
|
117
|
-
-
|
|
115
|
+
- ✅ Missing triggers (implemented via DROPPED state)
|
|
116
|
+
- ✅ Version mismatch (implemented via checksum comparison)
|
|
117
|
+
- ✅ Function body drift (implemented via checksum comparison)
|
|
118
|
+
- ✅ Manual SQL overrides (implemented via MANUAL_OVERRIDE state)
|
|
119
|
+
- ✅ Unknown external triggers (implemented via UNKNOWN state)
|
|
118
120
|
|
|
119
121
|
Drift states:
|
|
120
|
-
1.
|
|
121
|
-
2.
|
|
122
|
-
3.
|
|
123
|
-
4.
|
|
124
|
-
5.
|
|
125
|
-
6.
|
|
122
|
+
1. ✅ Managed & In Sync (fully implemented)
|
|
123
|
+
2. ✅ Managed & Drifted (fully implemented)
|
|
124
|
+
3. ✅ Manual Override (fully implemented)
|
|
125
|
+
4. ✅ Disabled (fully implemented)
|
|
126
|
+
5. ✅ Dropped (Recorded) (fully implemented)
|
|
127
|
+
6. ✅ Unknown (External) (fully implemented)
|
|
126
128
|
|
|
127
129
|
---
|
|
128
130
|
|
|
@@ -134,14 +136,14 @@ Provide console APIs:
|
|
|
134
136
|
~~PgSqlTriggers::Registry.enabled~~
|
|
135
137
|
~~PgSqlTriggers::Registry.disabled~~
|
|
136
138
|
~~PgSqlTriggers::Registry.for_table(:users)~~
|
|
137
|
-
~~PgSqlTriggers::Registry.diff~~ (
|
|
139
|
+
~~PgSqlTriggers::Registry.diff~~ (✅ fully working with drift detection)
|
|
138
140
|
~~PgSqlTriggers::Registry.validate!~~
|
|
139
141
|
|
|
140
142
|
~~No raw SQL required by users.~~
|
|
141
143
|
|
|
142
144
|
---
|
|
143
145
|
|
|
144
|
-
## 4. Free-Form SQL Execution (MANDATORY) ❌ (routes
|
|
146
|
+
## 4. Free-Form SQL Execution (MANDATORY) ❌ (routes defined but no implementation)
|
|
145
147
|
|
|
146
148
|
The gem MUST support free-form SQL execution.
|
|
147
149
|
|
|
@@ -154,7 +156,7 @@ This is required for:
|
|
|
154
156
|
|
|
155
157
|
Free-form SQL is wrapped in **named SQL capsules**:
|
|
156
158
|
|
|
157
|
-
- ❌ Must be named (routes
|
|
159
|
+
- ❌ Must be named (routes defined in `config/routes.rb`, no controller exists)
|
|
158
160
|
- ❌ Must declare environment (not implemented)
|
|
159
161
|
- ❌ Must declare purpose (not implemented)
|
|
160
162
|
- ❌ Must be applied explicitly (not implemented)
|
|
@@ -165,6 +167,8 @@ Rules:
|
|
|
165
167
|
- ❌ Registry updated (not implemented)
|
|
166
168
|
- ❌ Marked as `source = manual_sql` (not implemented)
|
|
167
169
|
|
|
170
|
+
**Status:** Routes exist for `sql_capsules#new`, `sql_capsules#create`, `sql_capsules#show`, and `sql_capsules#execute`, but no controller, views, or logic implemented. Autoload reference exists in `lib/pg_sql_triggers/sql.rb` but file does not exist.
|
|
171
|
+
|
|
168
172
|
---
|
|
169
173
|
|
|
170
174
|
## 5. Permissions Model v1 ⚠️ (structure exists, not enforced)
|
|
@@ -264,27 +268,27 @@ trigger.enable!(confirmation: "EXECUTE TRIGGER_ENABLE")
|
|
|
264
268
|
|
|
265
269
|
UI is operational, not decorative.
|
|
266
270
|
|
|
267
|
-
### Dashboard ✅ (
|
|
268
|
-
-
|
|
269
|
-
-
|
|
270
|
-
-
|
|
271
|
-
-
|
|
272
|
-
-
|
|
273
|
-
- ⚠️ Drift state (
|
|
274
|
-
-
|
|
275
|
-
-
|
|
276
|
-
|
|
277
|
-
### Trigger Detail Page
|
|
278
|
-
-
|
|
279
|
-
- ❌ SQL diff
|
|
280
|
-
-
|
|
281
|
-
|
|
282
|
-
### Actions (State-Based) ⚠️ (
|
|
283
|
-
- ⚠️ Enable (method exists but no UI buttons
|
|
284
|
-
- ⚠️ Disable (method exists but no UI buttons
|
|
285
|
-
- ❌ Drop (not implemented)
|
|
286
|
-
- ❌ Re-execute (not implemented)
|
|
287
|
-
- ❌ Execute SQL capsule (not implemented)
|
|
271
|
+
### Dashboard ✅ (implemented, drift display pending)
|
|
272
|
+
- ✅ Trigger name
|
|
273
|
+
- ✅ Table
|
|
274
|
+
- ✅ Version
|
|
275
|
+
- ✅ Status (enabled/disabled)
|
|
276
|
+
- ✅ Source
|
|
277
|
+
- ⚠️ Drift state (UI shows drift count but drift detection logic not implemented)
|
|
278
|
+
- ✅ Environment
|
|
279
|
+
- ❌ Last applied (installed_at exists in registry but not displayed in dashboard)
|
|
280
|
+
|
|
281
|
+
### Trigger Detail Page ⚠️ (partial - shown in tables/show but not dedicated)
|
|
282
|
+
- ⚠️ Summary panel (trigger info shown in tables/show view but no dedicated detail route/page)
|
|
283
|
+
- ❌ SQL diff (expected vs actual comparison)
|
|
284
|
+
- ⚠️ Registry state (basic info shown, but not comprehensive state display)
|
|
285
|
+
|
|
286
|
+
### Actions (State-Based) ⚠️ (backend methods exist, UI actions missing)
|
|
287
|
+
- ⚠️ Enable (console method `TriggerRegistry#enable!` exists with kill switch protection, but no UI buttons)
|
|
288
|
+
- ⚠️ Disable (console method `TriggerRegistry#disable!` exists with kill switch protection, but no UI buttons)
|
|
289
|
+
- ❌ Drop (not implemented - no method or UI)
|
|
290
|
+
- ❌ Re-execute (not implemented - no method or UI)
|
|
291
|
+
- ❌ Execute SQL capsule (not implemented - SQL capsules not implemented)
|
|
288
292
|
|
|
289
293
|
Buttons must:
|
|
290
294
|
- ❌ Be permission-aware (permissions defined but not enforced in UI)
|
|
@@ -335,6 +339,26 @@ This gem must be described as:
|
|
|
335
339
|
|
|
336
340
|
## 13. Implementation Status & Improvements Needed
|
|
337
341
|
|
|
342
|
+
### 📊 Quick Status Summary
|
|
343
|
+
|
|
344
|
+
**Fully Implemented:**
|
|
345
|
+
- ✅ Trigger Declaration DSL (Section 3.A)
|
|
346
|
+
- ✅ Trigger Generation (Section 3.B)
|
|
347
|
+
- ✅ Trigger Registry (Section 3.C) - with consistent field-concatenation checksum algorithm
|
|
348
|
+
- ✅ Safe Apply & Deploy (Section 3.D) - fully implemented with safety validation
|
|
349
|
+
- ✅ Drift Detection (Section 3.E) - fully implemented with all 6 drift states
|
|
350
|
+
- ✅ Rails Console Introspection (Section 3.F) - including working diff method
|
|
351
|
+
- ✅ Kill Switch for Production Safety (Section 6) - fully implemented
|
|
352
|
+
- ✅ Basic UI Dashboard (Section 8) - migration management, tables view, generator
|
|
353
|
+
|
|
354
|
+
**Partially Implemented:**
|
|
355
|
+
- ⚠️ UI (Section 8) - dashboard and tables view exist, but no dedicated trigger detail page, no enable/disable buttons
|
|
356
|
+
- ⚠️ Permissions Model (Section 5) - structure exists but not enforced
|
|
357
|
+
|
|
358
|
+
**Not Implemented (Critical):**
|
|
359
|
+
- ❌ SQL Capsules (Section 4) - MANDATORY feature, routes exist but no implementation
|
|
360
|
+
- ❌ Drop & Re-Execute Flow (Section 9) - CRITICAL operational requirement
|
|
361
|
+
|
|
338
362
|
### ✅ Achieved Features
|
|
339
363
|
|
|
340
364
|
**Core Infrastructure:**
|
|
@@ -343,6 +367,7 @@ This gem must be described as:
|
|
|
343
367
|
- ✅ Trigger Generation (form-based wizard, DSL + migration files) - Section 3.B
|
|
344
368
|
- ✅ Database Introspection (tables, triggers, columns) - Supporting infrastructure
|
|
345
369
|
- ✅ Trigger Migrations system (rake tasks + UI) - Supporting infrastructure
|
|
370
|
+
- ✅ Drift Detection (all 6 states, detector, reporter, console APIs) - Section 3.E
|
|
346
371
|
- ✅ Rails Console Introspection APIs (`PgSqlTriggers::Registry.*`) - Section 3.F
|
|
347
372
|
- ✅ Enable/Disable trigger methods on TriggerRegistry model - Basic functionality
|
|
348
373
|
- ✅ Kill Switch for Production Safety (fully implemented) - Section 6
|
|
@@ -363,16 +388,35 @@ This gem must be described as:
|
|
|
363
388
|
|
|
364
389
|
**From Section 3.C (Trigger Registry):**
|
|
365
390
|
- ✅ Registry tracks: trigger_name, table_name, version, enabled, source, environment, installed_at, last_verified_at
|
|
366
|
-
- ✅ Registry tracks checksum (
|
|
391
|
+
- ✅ Registry tracks checksum (✅ consistent field-concatenation algorithm across all creation paths)
|
|
367
392
|
- ✅ Rails knows what exists and how it was created
|
|
368
393
|
|
|
394
|
+
**From Section 3.E (Drift Detection):**
|
|
395
|
+
- ✅ Drift::Detector class with all 6 drift states
|
|
396
|
+
- ✅ Drift::Reporter class for formatting drift reports
|
|
397
|
+
- ✅ Drift::DbQueries helper for PostgreSQL system catalog queries
|
|
398
|
+
- ✅ Detection of missing triggers (DROPPED state)
|
|
399
|
+
- ✅ Detection of version/function body drift (DRIFTED state via checksum)
|
|
400
|
+
- ✅ Detection of manual SQL overrides (MANUAL_OVERRIDE state)
|
|
401
|
+
- ✅ Detection of unknown external triggers (UNKNOWN state)
|
|
402
|
+
- ✅ Detection of disabled triggers (DISABLED state)
|
|
403
|
+
- ✅ Detection of in-sync triggers (IN_SYNC state)
|
|
404
|
+
- ✅ Registry convenience methods (drifted, in_sync, unknown_triggers, dropped)
|
|
405
|
+
- ✅ TriggerRegistry instance methods (drift_state, drift_result, drifted?, in_sync?, dropped?)
|
|
406
|
+
- ✅ Comprehensive test coverage for Detector and Reporter
|
|
407
|
+
|
|
369
408
|
**From Section 3.F (Rails Console Introspection):**
|
|
370
409
|
- ✅ `PgSqlTriggers::Registry.list` (note: namespace differs slightly from goal)
|
|
371
410
|
- ✅ `PgSqlTriggers::Registry.enabled`
|
|
372
411
|
- ✅ `PgSqlTriggers::Registry.disabled`
|
|
373
412
|
- ✅ `PgSqlTriggers::Registry.for_table(:users)`
|
|
374
413
|
- ✅ `PgSqlTriggers::Registry.validate!`
|
|
375
|
-
- ✅
|
|
414
|
+
- ✅ `PgSqlTriggers::Registry.diff` (fully working with drift detection)
|
|
415
|
+
- ✅ `PgSqlTriggers::Registry.drifted` (returns all drifted triggers)
|
|
416
|
+
- ✅ `PgSqlTriggers::Registry.in_sync` (returns all in-sync triggers)
|
|
417
|
+
- ✅ `PgSqlTriggers::Registry.unknown_triggers` (returns all external triggers)
|
|
418
|
+
- ✅ `PgSqlTriggers::Registry.dropped` (returns all dropped triggers)
|
|
419
|
+
- ✅ No raw SQL required by users for basic operations (enable/disable via console methods)
|
|
376
420
|
|
|
377
421
|
**From Section 5 (Permissions Model):**
|
|
378
422
|
- ✅ Permission structure exists (Viewer, Operator, Admin roles defined)
|
|
@@ -388,44 +432,43 @@ This gem must be described as:
|
|
|
388
432
|
- ✅ Thread-safe override mechanism
|
|
389
433
|
|
|
390
434
|
**From Section 8 (UI):**
|
|
391
|
-
- ✅ Dashboard with: Trigger name, Table, Version, Status, Source, Environment
|
|
392
|
-
-
|
|
435
|
+
- ✅ Dashboard with: Trigger name, Table, Version, Status (enabled/disabled), Source, Environment
|
|
436
|
+
- ⚠️ Dashboard displays drift count (UI shows drifted stat, but drift detection logic not implemented, so will be 0 or error)
|
|
437
|
+
- ✅ Tables view with table listing and trigger details
|
|
438
|
+
- ✅ Tables/show view shows trigger info for a specific table (not a dedicated trigger detail page)
|
|
439
|
+
- ✅ Generator UI (form-based wizard for creating triggers)
|
|
440
|
+
- ✅ Migration management UI (up/down/redo with kill switch protection)
|
|
441
|
+
- ❌ Trigger detail page (no dedicated route/page, only shown in tables/show)
|
|
393
442
|
|
|
394
443
|
---
|
|
395
444
|
|
|
396
445
|
### 🔴 HIGH PRIORITY - Critical Missing Features
|
|
397
446
|
|
|
398
|
-
|
|
399
|
-
|
|
447
|
+
**Note:** Priorities have been adjusted based on actual implementation status. SQL Capsules (marked MANDATORY in Section 4) moved from MEDIUM to HIGH priority as it's a critical missing feature.
|
|
448
|
+
|
|
449
|
+
#### 1. SQL Capsules (MANDATORY - Section 4) - CRITICAL
|
|
450
|
+
**Priority:** HIGH - Mandatory feature for emergency operations
|
|
400
451
|
|
|
401
|
-
**Status:**
|
|
452
|
+
**Status:** Routes defined, but no implementation
|
|
402
453
|
|
|
403
454
|
**Missing Files:**
|
|
404
|
-
- ❌ `lib/pg_sql_triggers/
|
|
405
|
-
- ❌ `lib/pg_sql_triggers/
|
|
455
|
+
- ❌ `lib/pg_sql_triggers/sql/capsule.rb` - SQL capsule definition class (autoloaded but file doesn't exist)
|
|
456
|
+
- ❌ `lib/pg_sql_triggers/sql/executor.rb` - SQL execution with transaction, checksum, registry update
|
|
457
|
+
- ❌ `app/controllers/pg_sql_triggers/sql_capsules_controller.rb` - UI controller (routes reference it but it doesn't exist)
|
|
458
|
+
- ❌ SQL capsule views (new, show, create, execute)
|
|
459
|
+
- ❌ SQL capsule storage mechanism (could use registry table with `source = manual_sql`)
|
|
406
460
|
|
|
407
461
|
**Missing Functionality:**
|
|
408
|
-
- ❌
|
|
409
|
-
- ❌
|
|
410
|
-
- ❌
|
|
411
|
-
- ❌
|
|
412
|
-
- ❌
|
|
413
|
-
- ❌
|
|
414
|
-
|
|
415
|
-
#### 2. Safe Apply & Deploy (Section 3.D)
|
|
416
|
-
**Priority:** HIGH - Deployment safety
|
|
462
|
+
- ❌ Named SQL capsules with environment and purpose declaration
|
|
463
|
+
- ❌ Explicit application workflow with confirmation
|
|
464
|
+
- ❌ Transactional execution
|
|
465
|
+
- ❌ Checksum verification
|
|
466
|
+
- ❌ Registry update with `source = manual_sql`
|
|
467
|
+
- ❌ Kill switch protection (should block in production)
|
|
417
468
|
|
|
418
|
-
**
|
|
469
|
+
**Impact:** Critical feature marked as MANDATORY in goal but completely missing. Emergency SQL execution not possible.
|
|
419
470
|
|
|
420
|
-
|
|
421
|
-
- ❌ Safe apply method that runs in a transaction
|
|
422
|
-
- ❌ Diff expected vs actual state before applying
|
|
423
|
-
- ❌ Explicit safety checks (never blindly DROP + CREATE)
|
|
424
|
-
- ❌ Rollback on failure with registry rollback
|
|
425
|
-
- ❌ Atomic registry update
|
|
426
|
-
- ❌ Integration with migrations and generator service
|
|
427
|
-
|
|
428
|
-
#### 3. Drop & Re-Execute Flow (CRITICAL - Section 9)
|
|
471
|
+
#### 2. Drop & Re-Execute Flow (Section 9) - CRITICAL
|
|
429
472
|
**Priority:** HIGH - Operational requirements
|
|
430
473
|
|
|
431
474
|
**Status:** Not implemented
|
|
@@ -437,30 +480,35 @@ This gem must be described as:
|
|
|
437
480
|
- ❌ Confirmation dialogs with typed confirmation text
|
|
438
481
|
- ❌ Transactional execution and registry update
|
|
439
482
|
|
|
440
|
-
|
|
483
|
+
**Impact:** Cannot safely drop or re-execute triggers. Operational workflows blocked.
|
|
441
484
|
|
|
442
|
-
|
|
485
|
+
#### 3. Safe Apply & Deploy (Section 3.D) - ✅ FULLY IMPLEMENTED
|
|
486
|
+
**Priority:** MEDIUM-HIGH - Deployment safety enhancement
|
|
443
487
|
|
|
444
|
-
|
|
445
|
-
**Priority:** MEDIUM - Emergency operations
|
|
488
|
+
**Status:** Fully implemented - pre-apply comparison and safety validation added
|
|
446
489
|
|
|
447
|
-
**
|
|
490
|
+
**What Works:**
|
|
491
|
+
- ✅ Migrations run in transactions
|
|
492
|
+
- ✅ Migration rollback supported
|
|
493
|
+
- ✅ Registry updated during migrations
|
|
494
|
+
- ✅ Pre-apply comparison (diff expected vs actual) before migration execution
|
|
495
|
+
- ✅ Diff reporting shows what will change before applying
|
|
496
|
+
- ✅ Safety validator blocks unsafe DROP + CREATE operations
|
|
497
|
+
- ✅ Explicit validation prevents migrations from blindly dropping and recreating existing objects
|
|
448
498
|
|
|
449
|
-
**
|
|
450
|
-
-
|
|
451
|
-
-
|
|
452
|
-
-
|
|
453
|
-
-
|
|
454
|
-
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
#### 5. Trigger Detail Page (Section 8 - UI)
|
|
499
|
+
**Implementation Details:**
|
|
500
|
+
- `Migrator::SafetyValidator` class detects unsafe DROP + CREATE patterns in migrations
|
|
501
|
+
- Validator checks if migrations would drop existing database objects and recreate them
|
|
502
|
+
- Blocks migration execution if unsafe patterns detected (unless explicitly allowed)
|
|
503
|
+
- Configuration option `allow_unsafe_migrations` (default: false) for global override
|
|
504
|
+
- Environment variable `ALLOW_UNSAFE_MIGRATIONS=true` for per-migration override
|
|
505
|
+
- Provides clear error messages explaining unsafe operations and how to proceed
|
|
506
|
+
|
|
507
|
+
---
|
|
508
|
+
|
|
509
|
+
### 🟡 MEDIUM PRIORITY - User-Facing Features
|
|
510
|
+
|
|
511
|
+
#### 4. Trigger Detail Page (Section 8 - UI)
|
|
464
512
|
**Priority:** MEDIUM - Usability
|
|
465
513
|
|
|
466
514
|
**Status:** Partial (shown in tables/show but not dedicated page)
|
|
@@ -473,51 +521,43 @@ This gem must be described as:
|
|
|
473
521
|
- ❌ Action buttons (Enable/Disable/Drop/Re-execute/Execute SQL capsule)
|
|
474
522
|
- ❌ Permission-aware, environment-aware, kill switch-aware button visibility
|
|
475
523
|
|
|
476
|
-
####
|
|
477
|
-
**Priority:** MEDIUM - Usability
|
|
524
|
+
#### 5. UI Actions (Section 8)
|
|
525
|
+
**Priority:** MEDIUM - Usability
|
|
478
526
|
|
|
479
|
-
**Status:**
|
|
527
|
+
**Status:** Backend methods exist, UI buttons missing
|
|
480
528
|
|
|
481
529
|
**Missing:**
|
|
482
|
-
- ❌ Enable/Disable buttons in dashboard and
|
|
483
|
-
- ❌ Drop button (
|
|
484
|
-
- ❌ Re-execute button
|
|
485
|
-
- ❌ Execute SQL capsule button (
|
|
486
|
-
- ❌ Permission checking in controllers
|
|
487
|
-
- ❌ Permission checking in UI (hide/disable buttons)
|
|
488
|
-
- ✅ Kill switch enforcement in UI (fully implemented - see Section 6)
|
|
489
|
-
- ❌ Environment awareness in UI actions
|
|
490
|
-
|
|
491
|
-
---
|
|
530
|
+
- ❌ Enable/Disable buttons in dashboard and tables/show pages (methods exist: `TriggerRegistry#enable!` and `#disable!`)
|
|
531
|
+
- ❌ Drop button (requires drop functionality from Section 9)
|
|
532
|
+
- ❌ Re-execute button (requires re-execute functionality from Section 9)
|
|
533
|
+
- ❌ Execute SQL capsule button (requires SQL capsules from Section 4)
|
|
492
534
|
|
|
493
|
-
|
|
535
|
+
**What Works:**
|
|
536
|
+
- ✅ Kill switch enforcement in UI (fully implemented - see Section 6)
|
|
537
|
+
- ✅ Migration actions (up/down/redo) with kill switch protection
|
|
494
538
|
|
|
495
|
-
####
|
|
496
|
-
**Priority:**
|
|
539
|
+
#### 6. Permissions Enforcement (Section 5)
|
|
540
|
+
**Priority:** MEDIUM - Security
|
|
497
541
|
|
|
498
|
-
**Status:**
|
|
542
|
+
**Status:** Permission structure exists but not enforced
|
|
499
543
|
|
|
500
544
|
**Missing:**
|
|
501
|
-
- ❌ Permission
|
|
545
|
+
- ❌ Permission checking in controllers (UI actions should check permissions)
|
|
546
|
+
- ❌ Permission checking in UI (hide/disable buttons based on role)
|
|
547
|
+
- ❌ Permission checks in `TriggerRegistry#enable!` and `disable!` (currently only kill switch checked)
|
|
502
548
|
- ❌ Permission checks in rake tasks
|
|
503
549
|
- ❌ Permission checks in console APIs
|
|
504
550
|
- ❌ Actor context passing through all operations
|
|
505
551
|
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
**Status:** Partially implemented
|
|
552
|
+
**What Exists:**
|
|
553
|
+
- ✅ Permission structure (Viewer, Operator, Admin roles defined)
|
|
554
|
+
- ✅ Permission model classes (`PgSqlTriggers::Permissions::Checker`)
|
|
510
555
|
|
|
511
|
-
|
|
512
|
-
- ⚠️ Registry manager uses "placeholder" checksum instead of calculating real checksum
|
|
513
|
-
- ✅ Generator service calculates checksum correctly
|
|
514
|
-
- ⚠️ Need consistent checksum calculation across all creation paths
|
|
556
|
+
---
|
|
515
557
|
|
|
516
|
-
|
|
517
|
-
- Replace "placeholder" in `Registry::Manager.register` with actual checksum calculation
|
|
518
|
-
- Ensure checksum is calculated consistently (same algorithm as generator)
|
|
558
|
+
### 🟢 LOW PRIORITY - Polish & Improvements
|
|
519
559
|
|
|
520
|
-
####
|
|
560
|
+
#### 7. Enhanced Logging & Audit Trail
|
|
521
561
|
**Priority:** LOW - Operational polish
|
|
522
562
|
|
|
523
563
|
**Status:** Kill switch logging is comprehensive; audit trail could be enhanced
|
|
@@ -527,7 +567,7 @@ This gem must be described as:
|
|
|
527
567
|
- ✅ Kill switch overrides logging (fully implemented)
|
|
528
568
|
- ⚠️ Comprehensive audit trail table for production operation attempts (optional enhancement - logging exists but structured audit table would be better)
|
|
529
569
|
|
|
530
|
-
####
|
|
570
|
+
#### 8. Error Handling Consistency
|
|
531
571
|
**Priority:** LOW - Code quality
|
|
532
572
|
|
|
533
573
|
**Status:** Kill switch errors are properly implemented; other error types need consistency
|
|
@@ -535,10 +575,10 @@ This gem must be described as:
|
|
|
535
575
|
**Missing:**
|
|
536
576
|
- ✅ Kill switch violations raise `KillSwitchError` (fully implemented)
|
|
537
577
|
- ❌ Permission violations should raise `PermissionError`
|
|
538
|
-
-
|
|
578
|
+
- ✅ Drift detection implemented (can be used for error handling)
|
|
539
579
|
- ❌ Consistent error handling across all operations
|
|
540
580
|
|
|
541
|
-
####
|
|
581
|
+
#### 9. Testing Coverage
|
|
542
582
|
**Priority:** LOW - Quality assurance
|
|
543
583
|
|
|
544
584
|
**Status:** Kill switch has comprehensive tests; other areas need coverage
|
|
@@ -546,11 +586,11 @@ This gem must be described as:
|
|
|
546
586
|
**Missing:**
|
|
547
587
|
- ❌ SQL capsules need tests
|
|
548
588
|
- ✅ Kill switch has comprehensive tests (fully tested)
|
|
549
|
-
-
|
|
589
|
+
- ✅ Drift detection has comprehensive tests (fully tested)
|
|
550
590
|
- ❌ Permission enforcement needs tests
|
|
551
591
|
- ❌ Drop/re-execute flow needs tests
|
|
552
592
|
|
|
553
|
-
####
|
|
593
|
+
#### 10. Documentation Updates
|
|
554
594
|
**Priority:** LOW - User experience
|
|
555
595
|
|
|
556
596
|
**Status:** Kill switch is well documented; other areas need documentation
|
|
@@ -560,17 +600,19 @@ This gem must be described as:
|
|
|
560
600
|
- ✅ README includes kill switch documentation with enforcement details (fully documented)
|
|
561
601
|
- ❌ Need examples for SQL capsules
|
|
562
602
|
- ❌ Need examples for permission configuration
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
- ⚠️
|
|
570
|
-
-
|
|
571
|
-
-
|
|
572
|
-
-
|
|
573
|
-
- ⚠️ `
|
|
603
|
+
- ✅ Drift detection fully documented in implementation plan
|
|
604
|
+
|
|
605
|
+
#### 11. Partial Implementation Notes
|
|
606
|
+
**Priority:** LOW - Known issues and technical debt
|
|
607
|
+
|
|
608
|
+
**Known Issues:**
|
|
609
|
+
- ⚠️ **Permissions Model** - Structure exists but not enforced in UI/CLI/console
|
|
610
|
+
- ✅ **Kill Switch** - Fully implemented (see Section 6 for details)
|
|
611
|
+
- ✅ **Checksum** - Fully implemented with consistent field-concatenation algorithm across all creation paths
|
|
612
|
+
- ✅ **Drift Detection** - Fully implemented with all 6 drift states, comprehensive tests, and console APIs
|
|
613
|
+
- ⚠️ **Dashboard** - `installed_at` exists in registry table but not displayed in UI
|
|
614
|
+
- ⚠️ **Trigger Detail Page** - No dedicated route/page, info shown in tables/show view only
|
|
615
|
+
- ⚠️ **Enable/Disable UI** - Console methods exist with kill switch protection, but no UI buttons
|
|
574
616
|
|
|
575
617
|
---
|
|
576
618
|
|
data/README.md
CHANGED
|
@@ -102,7 +102,7 @@ Three-tier permission system (Viewer, Operator, Admin) with customizable authori
|
|
|
102
102
|
|
|
103
103
|
## Examples
|
|
104
104
|
|
|
105
|
-
For working examples and complete demonstrations, check out the [example repository](https://github.com/
|
|
105
|
+
For working examples and complete demonstrations, check out the [example repository](https://github.com/samaswin/pg_triggers_example).
|
|
106
106
|
|
|
107
107
|
## Core Principles
|
|
108
108
|
|
|
@@ -117,9 +117,15 @@ After checking out the repo, run `bin/setup` to install dependencies. Run `rake
|
|
|
117
117
|
|
|
118
118
|
To install this gem locally, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and run `bundle exec rake release`.
|
|
119
119
|
|
|
120
|
+
## Test Coverage
|
|
121
|
+
|
|
122
|
+
See [COVERAGE.md](COVERAGE.md) for detailed coverage information.
|
|
123
|
+
|
|
124
|
+
**Total Coverage: 84.97%**
|
|
125
|
+
|
|
120
126
|
## Contributing
|
|
121
127
|
|
|
122
|
-
Bug reports and pull requests are welcome on GitHub at https://github.com/
|
|
128
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/samaswin/pg_sql_triggers.
|
|
123
129
|
|
|
124
130
|
## License
|
|
125
131
|
|
data/RELEASE.md
CHANGED
|
@@ -152,7 +152,7 @@ gem search pg_sql_triggers
|
|
|
152
152
|
|
|
153
153
|
### 11. Create a GitHub Release (Optional but Recommended)
|
|
154
154
|
|
|
155
|
-
1. Go to https://github.com/
|
|
155
|
+
1. Go to https://github.com/samaswin/pg_sql_triggers/releases
|
|
156
156
|
2. Click "Draft a new release"
|
|
157
157
|
3. Select the tag you just created (e.g., `v1.0.1`)
|
|
158
158
|
4. Use the CHANGELOG entry as the release notes
|
|
@@ -4,11 +4,14 @@ module PgSqlTriggers
|
|
|
4
4
|
class DashboardController < ApplicationController
|
|
5
5
|
def index
|
|
6
6
|
@triggers = PgSqlTriggers::TriggerRegistry.order(created_at: :desc)
|
|
7
|
+
|
|
8
|
+
# Get drift summary
|
|
9
|
+
drift_summary = PgSqlTriggers::Drift::Reporter.summary
|
|
7
10
|
@stats = {
|
|
8
11
|
total: @triggers.count,
|
|
9
12
|
enabled: @triggers.enabled.count,
|
|
10
13
|
disabled: @triggers.disabled.count,
|
|
11
|
-
drifted:
|
|
14
|
+
drifted: drift_summary[:drifted]
|
|
12
15
|
}
|
|
13
16
|
|
|
14
17
|
# Migration status with pagination
|
|
@@ -8,7 +8,16 @@ module PgSqlTriggers
|
|
|
8
8
|
# GET /generator/new
|
|
9
9
|
# Display the multi-step form wizard
|
|
10
10
|
def new
|
|
11
|
-
|
|
11
|
+
# Restore form data from session if available (when user clicks "Back to Edit")
|
|
12
|
+
session_data = session[:generator_form_data]
|
|
13
|
+
if session_data
|
|
14
|
+
# Convert to hash and symbolize keys for Form initialization
|
|
15
|
+
form_data = session_data.is_a?(Hash) ? session_data.symbolize_keys : session_data.to_h.symbolize_keys
|
|
16
|
+
@form = PgSqlTriggers::Generator::Form.new(form_data)
|
|
17
|
+
session.delete(:generator_form_data)
|
|
18
|
+
else
|
|
19
|
+
@form = PgSqlTriggers::Generator::Form.new
|
|
20
|
+
end
|
|
12
21
|
@available_tables = fetch_available_tables
|
|
13
22
|
end
|
|
14
23
|
|
|
@@ -17,7 +26,19 @@ module PgSqlTriggers
|
|
|
17
26
|
def preview
|
|
18
27
|
@form = PgSqlTriggers::Generator::Form.new(generator_params)
|
|
19
28
|
|
|
29
|
+
# If user clicked "Back to Edit", store form data in session and redirect
|
|
30
|
+
if params[:back_to_edit].present?
|
|
31
|
+
# Store form data as a hash (convert ActionController::Parameters to hash)
|
|
32
|
+
session[:generator_form_data] = generator_params.to_h
|
|
33
|
+
redirect_to new_generator_path
|
|
34
|
+
return
|
|
35
|
+
end
|
|
36
|
+
|
|
20
37
|
if @form.valid?
|
|
38
|
+
# Store form data in session so it can be restored if user clicks "Back to Edit"
|
|
39
|
+
# Convert ActionController::Parameters to hash
|
|
40
|
+
session[:generator_form_data] = generator_params.to_h
|
|
41
|
+
|
|
21
42
|
# Validate SQL function body (required field)
|
|
22
43
|
@sql_validation = validate_function_sql(@form)
|
|
23
44
|
|
|
@@ -58,6 +79,8 @@ module PgSqlTriggers
|
|
|
58
79
|
result = PgSqlTriggers::Generator::Service.create_trigger(@form, actor: current_actor)
|
|
59
80
|
|
|
60
81
|
if result[:success]
|
|
82
|
+
# Clear session data after successful creation
|
|
83
|
+
session.delete(:generator_form_data)
|
|
61
84
|
files_msg = "Migration: #{result[:migration_path]}, DSL: #{result[:dsl_path]}"
|
|
62
85
|
redirect_to root_path,
|
|
63
86
|
notice: "Trigger generated successfully. Files created: #{files_msg}"
|
|
@@ -104,7 +127,7 @@ module PgSqlTriggers
|
|
|
104
127
|
def generator_params
|
|
105
128
|
params.require(:pg_sql_triggers_generator_form).permit(
|
|
106
129
|
:trigger_name, :table_name, :function_name, :version,
|
|
107
|
-
:enabled, :condition, :generate_function_stub, :function_body,
|
|
130
|
+
:enabled, :condition, :timing, :generate_function_stub, :function_body,
|
|
108
131
|
events: [], environments: []
|
|
109
132
|
)
|
|
110
133
|
end
|
|
@@ -137,13 +160,52 @@ module PgSqlTriggers
|
|
|
137
160
|
return nil if form.function_body.blank?
|
|
138
161
|
|
|
139
162
|
# Create a temporary trigger registry object for validation
|
|
140
|
-
|
|
163
|
+
# Only include condition if the column exists
|
|
164
|
+
registry_attributes = {
|
|
141
165
|
trigger_name: form.trigger_name,
|
|
166
|
+
table_name: form.table_name,
|
|
142
167
|
function_body: form.function_body
|
|
143
|
-
|
|
168
|
+
}
|
|
169
|
+
# Only set condition if the column exists in the database
|
|
170
|
+
if PgSqlTriggers::TriggerRegistry.column_names.include?("condition")
|
|
171
|
+
registry_attributes[:condition] = form.condition
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
temp_registry = PgSqlTriggers::TriggerRegistry.new(registry_attributes)
|
|
175
|
+
|
|
176
|
+
# Build definition JSON for condition validation
|
|
177
|
+
definition = {
|
|
178
|
+
name: form.trigger_name,
|
|
179
|
+
table_name: form.table_name,
|
|
180
|
+
function_name: form.function_name,
|
|
181
|
+
events: form.events.compact_blank,
|
|
182
|
+
version: form.version,
|
|
183
|
+
enabled: form.enabled,
|
|
184
|
+
environments: form.environments.compact_blank,
|
|
185
|
+
condition: form.condition,
|
|
186
|
+
timing: form.timing || "before",
|
|
187
|
+
function_body: form.function_body
|
|
188
|
+
}
|
|
189
|
+
temp_registry.definition = definition.to_json
|
|
144
190
|
|
|
145
191
|
validator = PgSqlTriggers::Testing::SyntaxValidator.new(temp_registry)
|
|
146
|
-
|
|
192
|
+
|
|
193
|
+
# Validate function syntax
|
|
194
|
+
function_result = validator.validate_function_syntax
|
|
195
|
+
return function_result unless function_result[:valid]
|
|
196
|
+
|
|
197
|
+
# Validate condition if present
|
|
198
|
+
if form.condition.present?
|
|
199
|
+
condition_result = validator.validate_condition
|
|
200
|
+
unless condition_result[:valid]
|
|
201
|
+
return {
|
|
202
|
+
valid: false,
|
|
203
|
+
error: "WHEN condition validation failed: #{condition_result[:error]}"
|
|
204
|
+
}
|
|
205
|
+
end
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
function_result
|
|
147
209
|
rescue StandardError => e
|
|
148
210
|
{ valid: false, error: "Validation error: #{e.message}" }
|
|
149
211
|
end
|