Releases: Goldziher/scythe
v0.10.0
Highlights — Phase 0 of `scythe inspect`
This release introduces a new product surface for scythe: live-database operational health checks. Static linting catches problems visible in the SQL text; `scythe inspect` catches the ones that only emerge in a running database — foreign keys without covering indexes, tables with policies but Row Level Security disabled, duplicate indexes. Same `Finding` shape, same human / SARIF 2.1.0 / JSON reporters, same severity model and exit-code semantics as `scythe audit`.
This is Phase 0 of a five-phase delivery. See `docs/guide/inspect.md` for the full roadmap through v0.14.0.
Phase 0 check catalog
| ID | Name | Severity | Detection |
|---|---|---|---|
| SC-INS01 | missing-fk-index | warn | Foreign-key columns without a covering index — sequential scan on every join. |
| SC-INS02 | policy-exists-rls-disabled | error | Table has `CREATE POLICY` definitions but `ROW LEVEL SECURITY` is disabled — policies never apply. |
| SC-INS03 | duplicate-index | warn | Two or more indexes on the same table have identical definitions modulo name. |
Detection patterns are clean-room reimplementations of the equivalent supabase/splinter lints (0001, 0006, 0009). No source copied; `ATTRIBUTIONS.md` updated.
What ships
- `scythe inspect ` subcommand with `--format`, `--severity`, `--exit-zero`, `--output`, `--dialect`, `--list-checks` flags. URL resolves from positional arg → `$DATABASE_URL` → `$SCYTHE_DATABASE_URL`.
- New `scythe-inspect` crate carrying a `DbDriver` async trait, a tokio-postgres-backed `PostgresDriver`, and a `MysqlDriver` stub (keeps the trait shape engine-agnostic ahead of Phase 3 MySQL work).
- Public `scythe-inspect` pre-commit hook published via `.pre-commit-hooks.yaml`. CI-mode hook: `always_run: true`, `pass_filenames: false`, requires `$DATABASE_URL` (or `$SCYTHE_DATABASE_URL`) in the hook environment. Phase 1 will add `scythe.toml` `[inspect]` URL sourcing so local pre-commit use becomes natural.
- New CI workflow `.github/workflows/inspect-live.yml` runs the live integration tests against a `postgres:16-alpine` service.
- New docs page `docs/guide/inspect.md` — quick-start, check catalog, severity/exit-code semantics, GitHub Actions recipe, phased roadmap.
Phased roadmap
| Phase | Release | Theme | Engines | Checks added |
|---|---|---|---|---|
| 0 | v0.10.0 (this) | MVP scaffold + 3 checks | PG (MySQL stub) | SC-INS01..03 |
| 1 | v0.11.0 | Full PG check pack + TOML rule registry + `--explain` + `[inspect]` config | PG | SC-INS04..10 |
| 2 | v0.12.0 | Schema drift — declared catalog vs live | PG | SC-DFT01..05 |
| 3 | v0.13.0 | MySQL driver + initial MySQL check pack | PG + MySQL | SC-INS-MY01..06 |
| 4 | v0.14.0 | Stats-based — unused indexes, slow queries via `pg_stat_*` | PG | SC-INS-STAT01..04 |
Upgrade notes
- Pin pre-commit hooks to `rev: v0.10.0` to pick up `scythe-inspect`.
- The `scythe-inspect` hook is CI-mode only at Phase 0 (needs `$DATABASE_URL` in env). Local pre-commit runs without the variable will fail loudly. This is intentional; Phase 1 makes local use natural.
- All workspace crate versions bumped 0.9.0 → 0.10.0; the new `scythe-inspect` crate ships at 0.10.0.
See `CHANGELOG.md` for the per-rule, per-flag, per-file breakdown.
v0.9.0
Highlights
scythe lintnow runs the canonical audit rule packs by default, dialect-gated by the configured[[sql]].engine. Rules whosedialectslist excludes the engine are silently skipped, so amysqlproject does not see postgres-onlySC-MIG*findings. No CLI flag — the rules ship indefault_registry()and respect the existing[lint]severity overrides.- New
scythe-auditpre-commit hook published via.pre-commit-hooks.yaml. Runs the SC-SEC*/SC-RLS*/SC-MIG*/SC-CHK* packs over staged.sqlfiles without requiring ascythe.toml. Defaults to postgres; override per-hook withargs: [--dialect, mysql]. - Three new audit rule packs land in this release:
- Row Level Security — SC-RLS01 policy-references-user-metadata, SC-RLS02 policy-always-permissive, SC-RLS03 policy-uses-uncached-auth-function. Walks
CreatePolicy.using/.with_checktypedExprASTs; SC-RLS03 stops atExpr::Subqueryboundaries (the safe form). - CHECK quality — SC-CHK01 check-constraint-always-true. Catches column-level, table-level, and
ALTER TABLE ADD CONSTRAINTCHECKs whose expression is a tautology (true,1 = 1,NULL, or parenthesised variants). Lives in a newAntipatterncategory. - Splinter-inspired security/migration ports — SC-SEC12 function-search-path-mutable, SC-MIG19 unsupported-reg-types. Detection logic is clean-room reimplementations against
sqlparserASTs (splinter has no LICENSE file; ATTRIBUTIONS.md documents the courtesy attribution).
- Row Level Security — SC-RLS01 policy-references-user-metadata, SC-RLS02 policy-always-permissive, SC-RLS03 policy-uses-uncached-auth-function. Walks
- Migration safety rule pack extended from SC-MIG01..05 to SC-MIG01..19, covering NULL-contract integrity, column-type preferences, constraint-lock hazards, and reg* OID type bans alongside the original DDL hazards.
- Oracle bindings upgraded to sibyl 0.7. The codegen emitter was rewritten for sibyl 0.7's API breaks:
sibyl::preluderemoved,Varchar::as_str()no longer returnsResult,Date::timestamp()replaced withdate_and_time()tuple + chrono. The integration test selects["tokio", "nonblocking"]because sibyl 0.7'simpl Debug for LOBrequires one of those cfgs to compile at all. Trade-off:decimal → f64in the Oracle manifest because sibyl 0.7 has noToSql/FromSqlforrust_decimal::Decimal. - sqlx 0.8 → 0.9 in the Rust integration test crates. sqlx 0.9 tightened
raw_sql/queryto requireSqlSafeStr; templates wrap runtime strings withsqlx::AssertSqlSafe. - Five
test_enginescodegen test failures resolved. MSSQLDATETIMEOFFSET→datetime_tz, RedshiftSUPER→json, OracleNUMBER(p, s)→decimal(was falling through to the unknown-type fallback). Two stale fixture expectations corrected. - Pre-commit hook chain aligned with the polyrepo's shared source. Nine individual hook repos consolidated to
kreuzberg-dev/pre-commit-hooks v2.1.10for general/markdown/rust/shell/actions/typos/ai-rulez.rustdoc-lint,markdownlint-rumdl-strict, andrust-max-linesare listed but commented out with TODOs covering the gaps (~449 missing-doc errors, 35 long-line MD files, 4 source files over 1,000 LOC) — each is its own focused remediation.
See CHANGELOG.md for the full per-rule, per-matcher breakdown.
Upgrade notes
- Pin pre-commit hooks to
rev: v0.9.0to pick upscythe-audit. - Oracle users on Rust: sibyl 0.7 +
nonblockingfeature is required. The integration test still expects Oracle Instant Client at link time. mysql/sqlite/mssqlprojects:scythe lintwill start running the dialect-agnostic audit rules (SC-SEC01/02/03/06/07/08/09 and SC-CHK01). All postgres-specific rules are silently skipped.
v0.8.0
Added
- Kotlin extension functions (
extension_functions, opt-in) forkotlin-jdbcandkotlin-r2dbc. Generates idiomatic extension functions on the connection receiver —fun Connection.getUser(id: Int), called asconnection.getUser(id)— with expression bodies for value-returning queries.kotlin-r2dbcis reworked into asuspendextension onio.r2dbc.spi.Connection. Default off; existing output unchanged. (#43) - Configurable PHP namespace (
namespace) forphp-pdoandphp-amphp, enabling PSR-4 framework integration. Default remainsApp\Generated; an empty string omits the declaration. (#46)
Fixed
- Postgres 18 compatibility: the schema parser no longer crashes on psql meta-commands (
\restrict/\unrestrict) emitted bypg_dump 18+anddbmate. Such lines are stripped before parsing, so plain-format dumps are consumed as-is. (#49) - Python imports:
python-psycopg3,python-asyncpg, andpython-aiomysqlnow emitimport uuid/from typing import Anywhen their type mappings need them, fixing aNameErroron import of generated modules. (#48)
Full Changelog: v0.7.0...v0.8.0
v0.7.0
v0.6.13
Fixed
- Generated Rust code is now rustfmt-clean — scythe pipes generated
.rsfiles throughrustfmt --edition=2024so downstream projects no longer see whitespace-only diffs after runningcargo fmt. Falls back gracefully to unformatted output if rustfmt isn't on PATH.
Tests
- Regression tests assert embedded SQL preserves original casing of
LEAST/COALESCE/SUMand theasalias keyword.
v0.6.12
Fixed
- The 0.6.11 ON CONFLICT preprocessor scanned the raw SQL byte string, so text inside
--line comments and'…'literals could trigger the predicate-stripping path and chew into the surrounding INSERT body. The scanner now runs against an ASCII-uppercase mask where comments + string literals are replaced with same-length spaces, so positions still line up but only structural SQL is matched.
Supersedes v0.6.11; please skip directly to v0.6.12 if you haven't installed v0.6.11 yet.
v0.6.11
Fixed
- PostgreSQL: accept
INSERT … ON CONFLICT (cols) WHERE … DO …(the index-inference form for partial unique indexes). sqlparser-rs through 0.61 doesn't recognise the predicate, so scythe now strips it for the parser pass while keeping the original SQL for codegen and runtime, where Postgres validates and uses the predicate to pick the matching partial index. Mirrors the existing dialect-preprocess pattern used for Oracle and MSSQL.
v0.6.10
Fixed
- Clippy warnings in
scythe-lintstyle rules (collapsible_match) andtypescript-postgresbackend (unnecessary_sort_by)
Changed
- Fixture data for pending engines (MSSQL, Oracle, Redshift, Snowflake) moved from
engines_pending/totesting_data/engines_pending/— all fixtures now under one directory - Updated pre-commit hooks: ai-rulez v4.1.6, rumdl v0.1.88, cargo-sort v2.1.4
- Bumped integration test dependencies:
rand0.8.5 → 0.8.6,pgx/v55.7.4 → 5.9.2,gosnowflake1.10.1 → 1.13.3,snowflake-sdk1.15.0 → 2.0.4,snowflake-jdbc3.16.1 → 4.0.2
Full changelog: https://github.com/Goldziher/scythe/blob/main/CHANGELOG.md
v0.6.9
What's New
Pre-commit dialect auto-detection
scythe fmt and scythe lint now automatically read the SQL dialect from scythe.toml when files are passed directly by pre-commit hooks. Previously, the dialect defaulted to "ansi" unless explicitly specified via --dialect.
Integration test fixes
- 31/35 tests passing across 8 database engines
- Fixed PHP amphp PostgreSQL (autoload + API compatibility)
- Fixed Ruby/PHP SQLite
:execquery handling - Fixed Kotlin SQLite Float type literals
- Fixed Oracle Go EZ Connect format
- Fixed MariaDB C# UUID type casting
- Fixed Elixir Ecto/jamdb Oracle backends
- Snowflake CI simplified to Python fakesnow (no Docker emulator needed)
- Oracle CI: added Instant Client SDK for ruby-oci8
Known limitations
v0.6.8
What's New
MSSQL Support (9/10 backends passing)
- Full integration tests: Rust (tiberius), Python (pyodbc), Go (go-mssqldb), TypeScript (mssql), Java (JDBC), Kotlin (JDBC), C# (SqlClient), Ruby (TinyTds), PHP (PDO)
OUTPUT INSERTEDpreprocessing for parser compatibility- CI with SQL Server 2022 Docker
Redshift Support (13/13 backends passing)
- All PostgreSQL-compatible drivers with Redshift-specific manifests
- PG-compatible schema for CI testing
IDENTITY(N,N)schema preprocessing
Snowflake Support (Python passing via fakesnow)
- Python integration tests using fakesnow (DuckDB-backed)
TIMESTAMP_NTZ,TIMESTAMP_TZ,TIMESTAMP_LTZ,VARIANTtype mappings- CI with snowflake-emulator Docker
Other Fixes
- CI:
libaio1→libaio1t64for Ubuntu 24.04 - CI: SQLite
create_if_missing(true) - Go codegen:
@pNplaceholder rewriting for MSSQL - Rust tiberius codegen: proper
&dyn ToSqlparam binding - Ruby TinyTds codegen: type-aware inline value interpolation
- TypeScript mssql codegen: explicit
sql.*type bindings
Total: 89 integration test backends across 8 database engines