schema-risk 0.1.3

Stop dangerous database migrations before they reach production
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
<div align="center">

# SchemaRisk


**One bad migration away from production downtime.**

SchemaRisk catches dangerous PostgreSQL migrations before they hit production.

[![Crates.io](https://img.shields.io/crates/v/schema-risk.svg)](https://crates.io/crates/schema-risk)
[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
[![CI](https://github.com/Keystones-Lab/Schema-risk/actions/workflows/ci.yml/badge.svg)](https://github.com/Keystones-Lab/Schema-risk/actions)

[Quick Start](#quick-start) • [Demo](#see-it-in-action) • [CI Integration](#ci-integration) • [Docs](#commands)

</div>

---

## The Problem


```sql
CREATE INDEX idx_email ON users(email);
```

This runs silently in seconds locally. On production with 10M rows? **Table locked for 8+ minutes. API down.**

```sql
ALTER TABLE users ALTER COLUMN status TYPE VARCHAR(50);
```

Looks harmless. Actually: **Full table rewrite. Lock every row. Downtime.**

```sql
ALTER TABLE orders ADD COLUMN shipped BOOLEAN NOT NULL;
```

Works on empty tables. Production? **Instant failure. Transaction rollback.**

**These are real incidents.** Every week, teams deploy migrations that silently break production.

---

## The Solution


```bash
cargo install schema-risk
```

```bash
schema-risk analyze migrations/001_add_index.sql
```

```
┌─────────────────────────────────────────────────────────────────────────────┐
│ SchemaRisk Analysis                                                         │
├─────────────────────────────────────────────────────────────────────────────┤
│ File: migrations/001_add_index.sql                                          │
│ Risk: HIGH (score: 70)                                                      │
├─────────────────────────────────────────────────────────────────────────────┤
│ ⚠ WARNING                                                                   │
│ CREATE INDEX on 'users' without CONCURRENTLY will hold a SHARE lock         │
│ for the duration of the index build                                         │
├─────────────────────────────────────────────────────────────────────────────┤
│ ✓ SAFE ALTERNATIVE                                                          │
│ CREATE INDEX CONCURRENTLY idx_email ON users(email);                        │
└─────────────────────────────────────────────────────────────────────────────┘
```

**30 seconds to install. 1 command to prevent downtime.**

---

## See It in Action


```bash
# Built-in demo — see SchemaRisk catch real problems

schema-risk demo
```

Output:

```
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
 SchemaRisk Demo - Real-World Migration Analysis
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

Analyzing dangerous migration...

┌─────────────────────────────────────────────────────────────┐
│ ⛔ CRITICAL RISK DETECTED                                   │
├─────────────────────────────────────────────────────────────┤
│ Operation: ALTER COLUMN TYPE on `users.email`               │
│ Impact:    Full table rewrite (~5M rows)                    │
│ Lock:      ACCESS EXCLUSIVE (blocks all queries)            │
│ Duration:  8-15 minutes estimated                           │
├─────────────────────────────────────────────────────────────┤
│ ✓ Zero-Downtime Alternative:                                │
│                                                             │
│   -- Step 1: Add shadow column                              │
│   ALTER TABLE users ADD COLUMN email_v2 VARCHAR(255);       │
│                                                             │
│   -- Step 2: Backfill in batches                            │
│   UPDATE users SET email_v2 = email WHERE email_v2 IS NULL  │
│   LIMIT 10000;                                              │
│                                                             │
│   -- Step 3: Atomic swap                                    │
│   ALTER TABLE users RENAME COLUMN email TO email_old;       │
│   ALTER TABLE users RENAME COLUMN email_v2 TO email;        │
│                                                             │
│   -- Step 4: Drop old column                                │
│   ALTER TABLE users DROP COLUMN email_old;                  │
└─────────────────────────────────────────────────────────────┘

→ This migration would have caused 15 minutes of downtime.
→ SchemaRisk gives you the safe path instead.
```

---

## Quick Start


### Install


```bash
# From crates.io (recommended)

cargo install schema-risk

# From source

git clone https://github.com/Keystones-Lab/Schema-risk
cd Schema-risk && cargo install --path .
```

### Analyze Your Migrations


```bash
# Single file

schema-risk analyze db/migrations/001_add_users.sql

# All migrations

schema-risk analyze "db/migrations/*.sql"

# Auto-discover and analyze everything

schema-risk doctor
```

### Get Safe Alternatives


```bash
# Preview what the safe version looks like

schema-risk fix migrations/risky.sql --dry-run

# Generate fixed migration file

schema-risk fix migrations/risky.sql --output migrations/risky_safe.sql
```

---

## CI Integration


**Block dangerous migrations at PR time.** Add one file and every PR gets automatic risk reports.

### GitHub Actions (2 minutes)


Create `.github/workflows/schema-risk.yml`:

```yaml
name: Migration Safety Check

on:
  pull_request:
    paths:
      - 'db/migrations/**'
      - 'migrations/**'
      - 'prisma/migrations/**'

jobs:
  analyze:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Install SchemaRisk
        run: cargo install schema-risk

      - name: Analyze migrations
        run: |
          schema-risk ci-report "migrations/*.sql" \
            --format github-comment \
            --fail-on high \
            --pg-version 14
```

**Result:** Every PR with SQL changes gets a comment like:

> **⚠️ HIGH RISK** — This migration may cause production issues.
>
> | File | Risk | Score | Lock | Est. Duration |
> |------|:----:|------:|------|-------------:|
> | `001_add_index.sql` | **HIGH** | 70 | `SHARE` | ~90s |
>
> **Safe Alternative:**
> ```sql
> CREATE INDEX CONCURRENTLY idx_email ON users(email);
> ```

---

## What SchemaRisk Catches


| Operation | Risk | Why It's Dangerous |
|-----------|------|-------------------|
| `CREATE INDEX` (no `CONCURRENTLY`) | 🔴 HIGH | Blocks all writes during build |
| `ALTER COLUMN TYPE` | 🔴 HIGH | Full table rewrite, exclusive lock |
| `ADD COLUMN NOT NULL` (no default) | 🔴 HIGH | Fails on existing rows |
| `DROP TABLE` | ⛔ CRITICAL | Irreversible data loss |
| `DROP COLUMN` | 🔴 HIGH | Breaks app code still reading it |
| `RENAME COLUMN/TABLE` | 🔴 HIGH | Breaks all downstream queries |
| `SET NOT NULL` | 🟡 MEDIUM | Full table scan to validate |
| `ADD COLUMN DEFAULT` (PG < 11) | 🔴 HIGH | Table rewrite (metadata-only on PG11+) |
| `TRUNCATE` | ⛔ CRITICAL | Immediate data destruction |
| `ON DELETE CASCADE` | 🟡 MEDIUM | Silent cascading deletes |

---

## Commands


### `analyze` — Risk Assessment


```bash
schema-risk analyze migrations/001.sql
schema-risk analyze "migrations/*.sql" --format json
schema-risk analyze migrations/ --pg-version 14 --verbose
schema-risk analyze migrations/ --fail-on high  # Exit 1 if HIGH+
```

### `fix` — Safe Migration Generator


```bash
schema-risk fix migrations/001.sql --dry-run      # Preview
schema-risk fix migrations/001.sql --output safe.sql
```

### `guard` — Interactive Confirmation Gate


```bash
schema-risk guard migrations/dangerous.sql
# → Blocks execution until you confirm


# Safe pattern for scripts:

schema-risk guard migration.sql && psql -f migration.sql
```

### `doctor` — Zero-Config Analysis


```bash
schema-risk doctor              # Auto-discover and analyze all migrations
schema-risk doctor --verbose    # Show discovery details
```

### `demo` — See It In Action


```bash
schema-risk demo     # Built-in demonstration of SchemaRisk capabilities
```

### `ci-report` — PR Comments


```bash
schema-risk ci-report "migrations/*.sql" --format github-comment
schema-risk ci-report "migrations/*.sql" --format gitlab-comment
schema-risk ci-report "migrations/*.sql" --format json
```

### `discover` — Find Migrations


```bash
schema-risk discover .          # Find all migration directories
schema-risk discover . --json   # Output as JSON
```

---

## Configuration


Generate a config file:

```bash
schema-risk init
```

Example `schema-risk.yml`:

```yaml
version: 2

thresholds:
  fail_on: high      # Exit non-zero on HIGH or CRITICAL
  guard_on: medium   # Require confirmation on MEDIUM+

rules:
  disabled: []       # Rule IDs to skip: [R01, R02]
  table_overrides:
    sessions:
      ignored: true  # Skip analysis for ephemeral tables

guard:
  require_typed_confirmation: true   # "yes I am sure" for CRITICAL
  audit_log: ".schemarisk-audit.json"
  block_agents: true
  block_ci: false
```

---

## PostgreSQL Version Awareness


SchemaRisk knows PostgreSQL internals. Same SQL, different behavior:

| Operation | PG 10 | PG 11+ |
|-----------|-------|--------|
| `ADD COLUMN DEFAULT` | Full table rewrite | Metadata-only ✓ |
| `SET NOT NULL` | Long exclusive lock | CHECK constraint workaround available |

```bash
# Score accurately for your PG version

schema-risk analyze migrations/ --pg-version 10
schema-risk analyze migrations/ --pg-version 14
```

---

## Real Production Scenarios


Test these to validate SchemaRisk before rolling out to your team:

```bash
# 1. Safe migration (should pass cleanly)

schema-risk analyze examples/safe.sql

# 2. Risky operations (should flag with alternatives)

schema-risk analyze examples/risky.sql

# 3. Critical destructive ops (should block)

schema-risk guard examples/critical.sql --dry-run

# 4. Full fix generation

schema-risk fix examples/risky.sql --dry-run

# 5. CI output format

schema-risk ci-report "examples/*.sql" --format github-comment
```

---

## Exit Codes


| Code | Meaning |
|------|---------|
| `0` | Safe / below threshold |
| `1` | Risk meets or exceeds `--fail-on` threshold |
| `2` | Parse or I/O error |
| `3` | Guard runtime error |
| `4` | Blocked by guard |

---

## FAQ


**Q: Does this work with my ORM?**
A: Yes. SchemaRisk analyzes raw SQL. Works with Prisma, Rails, Django, Diesel, or any tool that generates SQL migrations.

**Q: How accurate is the lock duration estimate?**
A: It's a heuristic based on table size. For precise estimates, use `--table-rows users:5000000` or connect to your database with `--db-url`.

**Q: Can I run this in my CI pipeline?**
A: Yes. That's the primary use case. Use `--fail-on high` to block PRs with dangerous migrations.

**Q: What about MySQL/SQLite?**
A: Currently PostgreSQL only. The locking and DDL behavior is Postgres-specific.

---

## Contributing


```bash
git clone https://github.com/Keystones-Lab/Schema-risk
cd Schema-risk
cargo test
cargo clippy -- -D warnings
```

---

## License


MIT

---

<div align="center">

**Stop dangerous migrations before they reach production.**

[Install Now](#quick-start) • [See Demo](#see-it-in-action) • [Report Issues](https://github.com/Keystones-Lab/Schema-risk/issues)

</div>