{"id":31136091,"url":"https://github.com/davidsmfreire/sqlshield","last_synced_at":"2026-04-25T17:04:29.708Z","repository":{"id":219572636,"uuid":"749370662","full_name":"davidsmfreire/sqlshield","owner":"davidsmfreire","description":"Schema-aware static analysis for SQL strings in application code.","archived":false,"fork":false,"pushed_at":"2026-04-24T23:19:08.000Z","size":223,"stargazers_count":1,"open_issues_count":1,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2026-04-25T00:35:19.925Z","etag":null,"topics":["linter","sql","static-analysis"],"latest_commit_sha":null,"homepage":"","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/davidsmfreire.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2024-01-28T11:44:58.000Z","updated_at":"2026-04-24T23:17:12.000Z","dependencies_parsed_at":"2025-09-18T07:56:01.693Z","dependency_job_id":"567278fb-8133-4c97-be39-17845c8349b1","html_url":"https://github.com/davidsmfreire/sqlshield","commit_stats":null,"previous_names":["davidsmfreire/sqlshield"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/davidsmfreire/sqlshield","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/davidsmfreire%2Fsqlshield","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/davidsmfreire%2Fsqlshield/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/davidsmfreire%2Fsqlshield/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/davidsmfreire%2Fsqlshield/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/davidsmfreire","download_url":"https://codeload.github.com/davidsmfreire/sqlshield/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/davidsmfreire%2Fsqlshield/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32266044,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-25T09:15:33.318Z","status":"ssl_error","status_checked_at":"2026-04-25T09:15:31.997Z","response_time":59,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["linter","sql","static-analysis"],"created_at":"2025-09-18T07:55:52.490Z","updated_at":"2026-04-25T17:04:29.702Z","avatar_url":"https://github.com/davidsmfreire.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"# sqlshield\n\n\u003e Schema-aware SQL linter for embedded queries. Catches missing tables,\n\u003e missing columns, and broken JOINs in raw SQL strings inside source code \n\u003e — at edit time, not at runtime.\n\n```python\ndef fetch_user(uid):\n    return db.execute(f\"SELECT id, nickname FROM users WHERE id = {uid}\")\n```\n\n```text\n$ sqlshield --directory src --schema schema.sql\nsrc/queries.py:2: error: Column `nickname` not found in table `users`\n```\n\n## Why\n\nRaw SQL strings inside application code are a common blind spot. Type\ncheckers don't read them. Database connections are mocked in unit tests.\nThe error surfaces only when the query runs — which is fine on the happy\npath and miserable everywhere else. sqlshield reads your source files,\nextracts every SQL string, and validates each one against your schema —\nwithout touching a database.\n\nIt works on:\n\n- Plain `\"…\"` and raw `r#\"…\"#` Rust string literals (sqlx-idiomatic).\n- Python f-strings (`f\"…{x}…\"`) and `.format()` strings (`{{` / `}}`\n  escapes preserved).\n- Go raw / interpreted string literals, including `fmt.Sprintf` verbs.\n- JavaScript / TypeScript single-, double-, and template-string literals\n  (`.js`, `.ts`, `.tsx`); `${…}` template substitutions are stripped\n  before parsing.\n- Standalone `.sql` files (via the LSP).\n\nIt checks:\n\n- Tables and columns referenced anywhere — projection, `WHERE`, `HAVING`,\n  `GROUP BY`, `ORDER BY`, `JOIN ON` / `USING`, function arguments, `CASE`\n  branches, `CAST`, arithmetic, set operations.\n- `INSERT` / `UPDATE` / `DELETE` target tables and column lists.\n- `WITH` / CTEs (including `WITH RECURSIVE` and explicit column lists),\n  derived tables in `FROM`, parenthesized join groups, scalar / `IN` /\n  `EXISTS` subqueries — each in their own scope.\n- Schema-qualified table names (`public.users`) — strict for qualified\n  queries, permissive for bare ones.\n\n## Install\n\n```sh\n# Rust users\ncargo install sqlshield-cli\n\n# Python users (distribution name is `sqlshield-py`; module import is `sqlshield`)\npip install sqlshield-py\n```\n\nOr build from source:\n\n```sh\ngit clone https://github.com/davidsmfreire/sqlshield\ncd sqlshield\ncargo build --release\n./target/release/sqlshield --help\n```\n\n## Quick start\n\n1. Write a schema file (`schema.sql` by default):\n\n   ```sql\n   CREATE TABLE users (id INT, name VARCHAR(255), email VARCHAR(255));\n   CREATE TABLE orders (id INT, user_id INT, total INT);\n   ```\n\n2. Run sqlshield on your source tree:\n\n   ```sh\n   sqlshield --directory src --schema schema.sql\n   ```\n\n3. Each finding is reported as `path:line: error: \u003cdescription\u003e`. The\n   process exits `0` on clean, `1` if validation errors were found, and\n   `2` for IO / config problems (missing schema, malformed config,\n   stdin read failure).\n\n### Standalone query mode\n\n```sh\necho \"SELECT id, missing FROM users\" | sqlshield --stdin --schema schema.sql\n# error: Column `missing` not found in table `users`\n```\n\nUseful for editor integrations that pipe a single buffer through the\nlinter.\n\n### JSON output\n\n```sh\nsqlshield --directory src --schema schema.sql --format json\n```\n\n```json\n[\n  {\n    \"location\": \"src/queries.py:2\",\n    \"description\": \"Column `nickname` not found in table `users`\"\n  }\n]\n```\n\nStable shape; safe to pipe into `jq` or feed to a CI annotator.\n\n## Configuration\n\nDrop a `.sqlshield.toml` at the project root. CLI flags override the\nconfig; the config overrides defaults.\n\n```toml\n# .sqlshield.toml\nschema = \"db/schema.sql\"\ndirectory = \"src\"\ndialect = \"postgres\"\n# Or pull the schema from a live database instead of a file:\n# db_url = \"postgres://user:pass@localhost/mydb\"\n# db_url = \"sqlite:///abs/path/to/db.sqlite\"\n```\n\nLive introspection is feature-gated; the published binary ships with it\non. To validate against a running database without a `.sql` file, pass\n`--db-url` (or set `db_url` in the config). Postgres and SQLite are\nsupported today; MySQL is pending a Rust toolchain bump.\n\nSupported dialects: `generic` (default), `postgres` / `postgresql` / `pg`,\n`mysql`, `sqlite`, `mssql` / `sqlserver`, `snowflake`, `bigquery` / `bq`,\n`redshift`, `clickhouse`, `duckdb`, `hive`, `ansi`. The dialect controls\nhow the SQL parser handles vendor-specific syntax (Postgres `::` casts,\nMySQL backticks, …).\n\nThe walker prunes `target/`, `.git/`, `node_modules/`, `.venv/`, `venv/`,\n`__pycache__/`, `.pytest_cache/`, `.mypy_cache/`, `.ruff_cache/`, `.tox/`,\n`dist/`, `build/`, `.idea/`, and `.vscode/` automatically.\n\n## Editor integration\n\n[`sqlshield-lsp`](sqlshield-lsp/README.md) is a Language Server that\npublishes diagnostics for embedded SQL on every `didOpen` / `didChange`.\nAny LSP-aware editor (Neovim, Helix, Emacs, Zed, …) can show inline\nsquiggles on the offending SQL string. The crate's README has the wiring\nrecipes.\n\nA first-party VS Code extension lives at\n[`editors/vscode/`](editors/vscode/README.md) — it spawns `sqlshield-lsp`\nover stdio and forwards diagnostics as you type.\n\n## pre-commit\n\nsqlshield ships a [pre-commit](https://pre-commit.com/) hook so it can\ngate every commit alongside your other linters. Add it to\n`.pre-commit-config.yaml`:\n\n```yaml\n- repo: https://github.com/davidsmfreire/sqlshield\n  rev: sqlshield-cli-v0.0.2  # pin to a tagged release\n  hooks:\n    - id: sqlshield\n```\n\n`pass_filenames: false` is hard-coded in the hook definition because\nsqlshield walks the directory itself (driven by `.sqlshield.toml` or its\ndefaults) — file-by-file invocation isn't supported. The hook still\nruns only when files matching `\\.(py|rs|go|js|ts|tsx|sql)$` change.\n\nThe hook uses `language: rust`, so pre-commit's first run compiles the\nCLI from source via `cargo install`. Subsequent runs reuse the cached\nbinary.\n\n## Python integration\n\n[`sqlshield-py`](sqlshield-py/) exposes `validate_query` and\n`validate_files` as Python functions:\n\n```python\nimport sqlshield\n\nerrors = sqlshield.validate_query(\n    \"SELECT email FROM users\",\n    \"CREATE TABLE users (id INT, name VARCHAR(255))\",\n)\n# ['Column `email` not found in table `users`']\n```\n\n## Feature support\n\n| Clause / construct                                  | Status |\n| --------------------------------------------------- | :----: |\n| `SELECT` projection                                  |   ✅   |\n| `WHERE` / `HAVING` / `GROUP BY` / `ORDER BY`         |   ✅   |\n| Projection alias references in `HAVING` / `ORDER BY` |   ✅   |\n| `JOIN` `ON` / `USING`                                |   ✅   |\n| Parenthesized join groups                            |   ✅   |\n| `WITH` / CTE, `WITH RECURSIVE`, explicit `(a, b)` lists |   ✅   |\n| Derived tables (`FROM (SELECT …) alias`)             |   ✅   |\n| Subqueries (`IN`, `EXISTS`, scalar) — own scope      |   ✅   |\n| `UNION` / `INTERSECT` / `EXCEPT`                     |   ✅   |\n| `INSERT` (incl. `INSERT … SELECT`)                   |   ✅   |\n| `UPDATE` (assignments, `WHERE`, `FROM`)              |   ✅   |\n| `DELETE` (`USING`, `WHERE`)                          |   ✅   |\n| `WITH … INSERT/UPDATE`                               |   ✅   |\n| Schema-qualified names (`public.users`)              |   ✅   |\n| `ALTER TABLE ADD/DROP/RENAME COLUMN` ingestion       |   ✅   |\n| `CREATE VIEW` / `CREATE TABLE … AS SELECT`           |   ✅   |\n| Function args / `CASE` / `CAST` / arithmetic         |   ✅   |\n| Case-insensitive identifier matching                 |   ✅   |\n| 12 SQL dialects via `--dialect`                      |   ✅   |\n| `MERGE INTO … USING … WHEN [NOT] MATCHED`            |   ✅   |\n| Postgres quoted-vs-unquoted identifier folding       |   ✅   |\n| Live database introspection (Postgres + SQLite)      |   ✅   |\n| MySQL live introspection                             |   ✗    |\n\n## Limitations\n\n- **Identifier matching is ASCII case-insensitive.** sqlshield treats\n  `Id` and `id` as the same column. Postgres-style \"quoted identifiers\n  are case-sensitive\" semantics aren't modeled.\n- **Dynamic table / column names** (`SELECT {col} FROM t`) substitute the\n  placeholder with `1`. Column-position placeholders silently pass; table-\n  position placeholders break the parse and the query is dropped.\n- **Two qualified tables sharing a bare name** (`schema_a.users` and\n  `schema_b.users`) collide on the bare key — last declaration wins for\n  unqualified queries. Qualified references resolve strictly.\n- **Schema is parsed once.** Triggers, stored procedures, and INSTEAD OF\n  rules aren't tracked.\n- **Per-file errors are silently swallowed** during a directory scan\n  (parse failures, missing-extension errors). Use `--stdin` to surface\n  them for a single query.\n\n## Architecture\n\n```text\nSource file (*.py, *.rs)\n   │ tree-sitter extracts string literals (decoded escapes / raw strings)\n   ▼\nSQL string (with `{…}` placeholders replaced by `1`)\n   │ sqlparser parses with the chosen dialect\n   ▼\nAST (Vec\u003cStatement\u003e)\n   │ recursive walker: scope-aware Expr resolution + clause validators\n   ▼\nVec\u003cSqlValidationError\u003e\n```\n\nWorkspace layout:\n\n- [`sqlshield/`](sqlshield/) — core library. Public surface:\n  `validate_query`, `validate_files`, `Dialect`, `SqlShieldError`.\n- [`sqlshield-cli/`](sqlshield-cli/) — clap-based CLI wrapper.\n- [`sqlshield-py/`](sqlshield-py/) — PyO3 bindings.\n- [`sqlshield-lsp/`](sqlshield-lsp/) — `tower-lsp` Language Server.\n- [`sqlshield-introspect/`](sqlshield-introspect/) — live schema reader\n  for Postgres and SQLite (consumed by `--db-url`).\n- [`editors/vscode/`](editors/vscode/) — first-party VS Code extension\n  wrapping `sqlshield-lsp`.\n\n## Similar tools\n\n- [`postguard`](https://github.com/andywer/postguard) — Postgres-only,\n  ts-only, runs against a live database.\n- [`schemasafe`](https://github.com/schemasafe/schemasafe) —\n  query-checker for TypeScript / JavaScript.\n- [`sqlc`](https://github.com/sqlc-dev/sqlc) — code generator that\n  type-checks SQL against a schema; reads the schema, then generates\n  bindings rather than linting existing code.\n- [`squawk`](https://github.com/sbdchd/squawk) — Postgres migration\n  linter; complementary, not overlapping (squawk lints DDL, sqlshield\n  lints embedded DML/SELECT).\n\nsqlshield's niche: language-agnostic extraction (Python, Rust, Go, and\nJavaScript / TypeScript today, extensible) with a multi-dialect parser,\nno database connection required (live introspection optional).\n\n## Contributing\n\nSee [CONTRIBUTING.md](CONTRIBUTING.md) for the dev setup, the\nClauseValidation extension recipe, and the release process.\n\n## License\n\n[MIT](LICENSE).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdavidsmfreire%2Fsqlshield","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdavidsmfreire%2Fsqlshield","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdavidsmfreire%2Fsqlshield/lists"}