# pgsafe

Static safety linter for PostgreSQL DDL migrations. `pgsafe` parses SQL and flags schema
changes likely to lock or break production — no database connection, no network.

**📖 Full documentation, the rules reference, and an in-browser playground:
[pgsafe.fixedwidth.tech](https://pgsafe.fixedwidth.tech)**

## Install

Install from [crates.io](https://crates.io/crates/pgsafe) with a Rust toolchain:

```sh
cargo install pgsafe
```

Or download a prebuilt binary from the [latest release](https://github.com/fixed-width/pgsafe/releases/latest)
(static Linux and macOS), or build from source with `cargo build --release` (binary at
`target/release/pgsafe`).

## Quickstart

```sh
pgsafe migration.sql                 # lint a file (exit 1 on a finding)
pgsafe --format json migration.sql   # machine-readable
pgsafe --list-rules                  # every rule this build checks
```

### Applying fixes

Findings that have an unambiguous mechanical rewrite carry a fix. Preview them:

```sh
pgsafe --diff db/migrate/003_add_index.sql
```

The output is a standard unified diff, so it pipes straight into `git apply`:

```sh
pgsafe --diff db/migrate/003_add_index.sql | git apply
```

Apply them in place (or to stdout when reading stdin):

```sh
pgsafe --fix db/migrate/003_add_index.sql
```

`--fix` and `--diff` are human-output only and cannot be combined with each other
or with `--format json`/`--format github`/`--format sarif`. A `-- pgsafe:ignore` finding is never
auto-fixed. After `--fix`, the exit code reflects re-linting the fixed file.

Run it in CI with the [GitHub Action](https://pgsafe.fixedwidth.tech/docs/ci/):

```yaml
- uses: fixed-width/pgsafe@v0.8.6
  with:
    files: "db/migrate/*.sql"
```

### SARIF output (GitHub code scanning)

`--format sarif` emits SARIF 2.1.0, for upload to GitHub code scanning:

```yaml
- run: pgsafe --format sarif db/migrate/*.sql > pgsafe.sarif
- uses: github/codeql-action/upload-sarif@v3
  # pgsafe exits non-zero when findings gate, so upload the results regardless:
  if: always()
  with:
    sarif_file: pgsafe.sarif
```

Findings (including `-- pgsafe:ignore`-suppressed ones, marked dismissed) become SARIF
results; a file that fails to parse becomes a tool-execution notification.

A findings run (exit 1) and a parse error (exit 2) both still write valid SARIF, so
`if: always()` uploads the results in the common cases. A configuration or I/O error
(e.g. an unreadable path) exits 2 *without* writing SARIF — the resulting 0-byte file
then (correctly) fails the upload. Pass repo-relative migration paths: absolute paths and
stdin don't map back to files GitHub can annotate as code-scanning alerts.

See [pgsafe.fixedwidth.tech/docs](https://pgsafe.fixedwidth.tech/docs/) for configuration,
output formats, and the full [rules reference](https://pgsafe.fixedwidth.tech/rules/).

## License

Apache-2.0. See [LICENSE](LICENSE).
