|
| 1 | +# qShape |
| 2 | + |
| 3 | +AST-level canonicalization and fingerprinting of PostgreSQL queries, |
| 4 | +plus a `pg_stat_statements` capture command that ranks workload by |
| 5 | +execution time, not just call count. |
| 6 | + |
| 7 | +`pg_stat_statements.queryid` hashes the parse tree after literal-to-`$N` |
| 8 | +substitution but before any AST normalization. ORMs (Rails, |
| 9 | +ActiveRecord, SQLAlchemy, Prisma, Sequelize) produce many queryids for |
| 10 | +one logical query shape. `qshape` collapses those variants to a single |
| 11 | +canonical fingerprint and aggregates their timing. |
| 12 | + |
| 13 | +## Install |
| 14 | + |
| 15 | +Homebrew: |
| 16 | + |
| 17 | +``` |
| 18 | +brew install boringsql/boringsql/qshape |
| 19 | +``` |
| 20 | + |
| 21 | +From source: |
| 22 | + |
| 23 | +``` |
| 24 | +go install github.com/boringsql/qshape/cmd/qshape@latest |
| 25 | +``` |
| 26 | + |
| 27 | +Pre-built binaries for macOS and Linux (amd64 + arm64) are published on |
| 28 | +each release via [GoReleaser](https://github.com/boringsql/qshape/releases). |
| 29 | + |
| 30 | +## CLI |
| 31 | + |
| 32 | +``` |
| 33 | +qshape normalize "SELECT u.id FROM users u WHERE u.id = 1" |
| 34 | +SELECT.id FROM users u WHERE id = 1 |
| 35 | +
|
| 36 | +qshape fingerprint "SELECT id FROM users WHERE id = 1" |
| 37 | +sha1:63fe28385e8b4d95 |
| 38 | +
|
| 39 | +qshape capture "postgres://user:pass@host/db" > queries.json |
| 40 | +
|
| 41 | +qshape attribute --in clusters.json --conn "$DATABASE_URL" > queries-attributed.json |
| 42 | +``` |
| 43 | + |
| 44 | +`capture` connects directly to a PostgreSQL node and reads |
| 45 | +`pg_stat_statements` with original whitespace and timing intact, then |
| 46 | +writes `{"clusters":[...]}` to stdout sorted by descending |
| 47 | +`total_exec_time_ms`. Passing `-` (or omitting the SQL arg) to |
| 48 | +`normalize` / `fingerprint` reads stdin. |
| 49 | + |
| 50 | +## License |
| 51 | + |
| 52 | +BSD 2-Clause. |
0 commit comments