Skip to content

Commit ce6afd3

Browse files
davidsmfreireclaude
andcommitted
feat: dialect-aware folding, MERGE, live introspection, VS Code, audit fixes
This commit bundles a stack of related work on the validation core, the schema layer, and the editor surface; see ROADMAP.md for the resulting status. Validation core: - Thread `Dialect` through the validation layer; replace ad-hoc ASCII lowercasing with `fold_ident` / `fold_str` / `qualified_key` so Postgres preserves quoted-identifier case while every other dialect keeps the prior case-insensitive behavior. - Convert `Extras` to an owned `HashMap<String, HashSet<String>>` keyed by dialect-folded names; eliminates lifetime juggling and ad-hoc case-insensitive scans. - Add `Statement::Merge` support (target / source / ON / WHEN MATCHED / WHEN NOT MATCHED) in `validation/clauses/merge.rs`. - 3-part qualified column refs (`schema.table.col`). - Ambiguity detection: flag unqualified columns visible in 2+ relations; `JOIN ... USING(col)` keeps the legacy non-ambiguous resolution since shared-name presence is the whole point of USING. - NATURAL JOIN: warn when the right-hand factor shares no column with the accumulated left-hand scope (most engines silently degrade to a Cartesian product, which is rarely intended). - Set-op (UNION / INTERSECT / EXCEPT) column-arity check; wildcard branches are skipped since their arity isn't known statically. Schema: - Schema parser folds identifiers per dialect on ingest so query-side and schema-side keys collide deterministically. Live introspection: - New `sqlshield-introspect` crate reads schemas directly from a running Postgres or SQLite; CLI `--db-url` accepts a connection string in place of `--schema`. MySQL is left for a follow-up: `mysql_common` uses unstable `let`-chain syntax that hasn't reached the project's pinned toolchain. Editor surface: - LSP polls the schema file's mtime before each validation pass and reloads on change — picks up schema edits without restarting. - LSP `.sql` path drops the redundant first-pass empty-schema validation and parses + validates once against the cached schema. - New `editors/vscode` extension wraps `sqlshield-lsp` over stdio for a first-party VS Code experience. Tests cover each new behavior (set-op arity, 3-part qualifier, NATURAL JOIN happy + sad path, ambiguity, USING-not-ambiguous, MERGE, Postgres quoted-vs-unquoted folding, wildcard expansion). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 551b1e5 commit ce6afd3

36 files changed

Lines changed: 3085 additions & 366 deletions

Cargo.lock

Lines changed: 829 additions & 13 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ resolver = "2"
33
members = [
44
"sqlshield",
55
"sqlshield-cli",
6+
"sqlshield-introspect",
67
"sqlshield-lsp",
78
"sqlshield-py",
89
]

ROADMAP.md

Lines changed: 20 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,32 +2,38 @@
22

33
## Done
44

5-
- Schema-aware validation across SELECT / INSERT / UPDATE / DELETE,
6-
CTEs (incl. `WITH RECURSIVE`), set ops, derived tables, JOIN ON /
7-
USING, scope-aware subqueries.
5+
- Schema-aware validation across SELECT / INSERT / UPDATE / DELETE /
6+
MERGE, CTEs (incl. `WITH RECURSIVE`), set ops, derived tables,
7+
JOIN ON / USING / NATURAL, scope-aware subqueries.
88
- Schema ingestion: `CREATE TABLE`, `ALTER TABLE` (ADD/DROP/RENAME
99
COLUMN), `CREATE VIEW`, `CREATE TABLE … AS SELECT`.
10-
- 12 SQL dialects via `--dialect`.
11-
- ASCII case-insensitive identifier matching.
10+
- 12 SQL dialects via `--dialect`; Postgres quoted-vs-unquoted
11+
identifier folding (other dialects keep ASCII case-insensitive
12+
matching).
13+
- 3-part qualified column refs (`schema.table.col`).
14+
- Ambiguity detection for unqualified columns visible in 2+ relations.
15+
- Set-op (UNION / INTERSECT / EXCEPT) column-arity check.
16+
- `SELECT *` / `SELECT t.*` projection expansion in CTEs and derived
17+
tables (resolves outer references and outer `ORDER BY`).
1218
- Output formats: text + JSON; split exit codes; `--stdin` mode.
1319
- `.sqlshield.toml` configuration with CLI override layering.
1420
- Parallel file walker (rayon) with default ignore list.
1521
- Language Server (`sqlshield-lsp`) for inline editor diagnostics
16-
in `.py` / `.rs` / `.sql`.
22+
in `.py` / `.rs` / `.sql`; auto-reload on schema-file changes.
23+
- First-party VS Code extension (`editors/vscode`) wrapping
24+
`sqlshield-lsp` over stdio.
25+
- Live database introspection (`sqlshield-introspect`, exposed via
26+
`--db-url`): read schema directly from a running Postgres or SQLite
27+
instance.
1728
- Python bindings (`sqlshield-py`).
1829

1930
## Considering
2031

21-
- **Live database introspection** — connect to Postgres / MySQL /
22-
Sqlite and read the schema directly, no SQL dump required.
23-
- **Postgres quoted-vs-unquoted identifier folding** — currently we
24-
treat all identifiers as case-insensitive; a quoted-aware mode
25-
would match Postgres semantics more precisely.
26-
- **`MERGE` support** — would round out the DML coverage.
32+
- **MySQL live introspection** — pending: `mysql_common` uses unstable
33+
Rust features that haven't reached the project's pinned toolchain.
34+
A toolchain bump or a different sync driver would unblock this.
2735
- **More language extractors** — Go, TypeScript, Java string literals.
2836
Each is a small `finder/<lang>.rs` module + tree-sitter grammar.
29-
- **First-party VS Code extension** — currently the LSP is wired via
30-
generic LSP-client extensions.
3137

3238
## Not planned
3339

editors/vscode/.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
node_modules/
2+
out/
3+
*.vsix

editors/vscode/.vscodeignore

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
.vscode/**
2+
.vscode-test/**
3+
src/**
4+
.gitignore
5+
.eslintrc.json
6+
**/tsconfig.json
7+
**/*.map
8+
**/*.ts
9+
node_modules

editors/vscode/README.md

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# sqlshield for VS Code
2+
3+
First-party VS Code integration for [sqlshield](https://github.com/davidsmfreire/sqlshield)
4+
a schema-aware static linter for raw SQL embedded in `.py`, `.rs`, and `.sql`
5+
files.
6+
7+
## Requirements
8+
9+
The extension wraps the `sqlshield-lsp` binary; install it first:
10+
11+
```bash
12+
cargo install sqlshield-lsp
13+
```
14+
15+
If `sqlshield-lsp` is not on `PATH`, set `sqlshield.serverPath` to the
16+
absolute path.
17+
18+
## How it works
19+
20+
The extension spawns `sqlshield-lsp` over stdio and forwards diagnostics
21+
to the editor as you type. A `.sqlshield.toml` at the workspace root
22+
configures schema location and dialect; see the project README for the
23+
full set of options.
24+
25+
## Commands
26+
27+
* **sqlshield: Restart Language Server** — restart the server without
28+
reloading the window. Useful after editing the schema or
29+
`.sqlshield.toml` from outside VS Code.
30+
31+
## Settings
32+
33+
* `sqlshield.serverPath` — binary path (default: `sqlshield-lsp`).
34+
* `sqlshield.trace.server` — log LSP traffic for debugging.

editors/vscode/package-lock.json

Lines changed: 140 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

editors/vscode/package.json

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
{
2+
"name": "sqlshield-vscode",
3+
"displayName": "sqlshield",
4+
"description": "Schema-aware static linter for raw SQL embedded in Python, Rust, and .sql files.",
5+
"version": "0.0.1",
6+
"publisher": "davidsmfreire",
7+
"license": "MIT",
8+
"repository": {
9+
"type": "git",
10+
"url": "https://github.com/davidsmfreire/sqlshield"
11+
},
12+
"engines": {
13+
"vscode": "^1.85.0"
14+
},
15+
"categories": [
16+
"Linters",
17+
"Programming Languages"
18+
],
19+
"keywords": [
20+
"sql",
21+
"linter",
22+
"postgres",
23+
"mysql",
24+
"sqlite",
25+
"schema"
26+
],
27+
"activationEvents": [
28+
"onLanguage:sql",
29+
"onLanguage:python",
30+
"onLanguage:rust",
31+
"workspaceContains:**/.sqlshield.toml",
32+
"workspaceContains:**/schema.sql"
33+
],
34+
"main": "./out/extension.js",
35+
"contributes": {
36+
"configuration": {
37+
"title": "sqlshield",
38+
"properties": {
39+
"sqlshield.serverPath": {
40+
"type": "string",
41+
"default": "sqlshield-lsp",
42+
"description": "Path to the sqlshield-lsp binary. Defaults to 'sqlshield-lsp' on PATH."
43+
},
44+
"sqlshield.trace.server": {
45+
"type": "string",
46+
"scope": "window",
47+
"enum": ["off", "messages", "verbose"],
48+
"default": "off",
49+
"description": "Trace LSP traffic between VS Code and the sqlshield language server."
50+
}
51+
}
52+
},
53+
"commands": [
54+
{
55+
"command": "sqlshield.restartServer",
56+
"title": "sqlshield: Restart Language Server"
57+
}
58+
]
59+
},
60+
"scripts": {
61+
"vscode:prepublish": "npm run compile",
62+
"compile": "tsc -p ./",
63+
"watch": "tsc -watch -p ./"
64+
},
65+
"dependencies": {
66+
"vscode-languageclient": "^9.0.1"
67+
},
68+
"devDependencies": {
69+
"@types/node": "^20.0.0",
70+
"@types/vscode": "^1.85.0",
71+
"typescript": "^5.4.0"
72+
}
73+
}

0 commit comments

Comments
 (0)