sqlsift-lsp 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.gitignore +2 -0
- package/CHANGELOG.md +157 -0
- package/LICENSE +21 -0
- package/README.md +263 -0
- package/binary-install.js +212 -0
- package/binary.js +126 -0
- package/install.js +4 -0
- package/npm-shrinkwrap.json +519 -0
- package/package.json +99 -0
- package/run-sqlsift-lsp.js +4 -0
package/.gitignore
ADDED
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [Unreleased]
|
|
9
|
+
|
|
10
|
+
## [0.1.0-alpha.8] - 2026-02-13
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
- **Project renamed from sqlsurge to sqlsift** to avoid name conflict with existing [senkenn/sqlsurge](https://github.com/senkenn/sqlsurge) VS Code extension
|
|
14
|
+
- crate names: `sqlsift-core`, `sqlsift-cli`, `sqlsift-lsp`
|
|
15
|
+
- npm package: `sqlsift-cli`
|
|
16
|
+
- binary: `sqlsift`
|
|
17
|
+
- config file: `sqlsift.toml`
|
|
18
|
+
|
|
19
|
+
### Fixed
|
|
20
|
+
- Fix `auto-tag.yml` release automation: add `actions: write` permission to enable `gh workflow run`
|
|
21
|
+
|
|
22
|
+
## [0.1.0-alpha.7] - 2026-02-11
|
|
23
|
+
|
|
24
|
+
### Added
|
|
25
|
+
- **LSP server** (`sqlsift-lsp`): Language Server Protocol support for real-time SQL diagnostics in editors
|
|
26
|
+
- textDocument/didOpen, didChange, didSave, didClose
|
|
27
|
+
- Automatic schema loading from `sqlsift.toml`
|
|
28
|
+
- Schema file change detection with catalog rebuild
|
|
29
|
+
- Diagnostic severity, error codes, and help messages
|
|
30
|
+
- 12 unit tests (diagnostic conversion, state management)
|
|
31
|
+
- **VS Code extension** (`editors/vscode/`): Thin LSP client for VS Code
|
|
32
|
+
- Configurable server path via `sqlsift.serverPath` setting
|
|
33
|
+
- SQL language configuration (comments, brackets)
|
|
34
|
+
- **Neovim support**: Works out of the box with built-in LSP client
|
|
35
|
+
|
|
36
|
+
### Fixed
|
|
37
|
+
- `SELECT *` diagnostic now points to the `SELECT` keyword instead of file start
|
|
38
|
+
|
|
39
|
+
## [0.1.0-alpha.6] - 2026-02-08
|
|
40
|
+
|
|
41
|
+
### Added
|
|
42
|
+
- **Type inference engine**: SQL expression type checking for WHERE clauses and JOIN conditions
|
|
43
|
+
- E0003 (type-mismatch): Detect incompatible type comparisons (e.g., `WHERE id = 'text'`)
|
|
44
|
+
- E0007 (join-type-mismatch): Detect JOIN condition type incompatibilities (e.g., `ON users.id = orders.name`)
|
|
45
|
+
- Binary operator type validation: comparisons (=, !=, <, >, <=, >=) and arithmetic (+, -, *, /)
|
|
46
|
+
- Nested expression type inference: `(a + b) * 2 = c`
|
|
47
|
+
- Numeric type compatibility: implicit casts between TINYINT, SMALLINT, INTEGER, BIGINT
|
|
48
|
+
|
|
49
|
+
### Changed
|
|
50
|
+
- Reorganized test suite: moved integration tests to `tests/analyzer_tests.rs` (74 tests)
|
|
51
|
+
- Improved API documentation with doc-test examples
|
|
52
|
+
- Replaced `unwrap()` with `expect()` in catalog code for better error messages
|
|
53
|
+
|
|
54
|
+
## [0.1.0-alpha.5] - 2026-02-08
|
|
55
|
+
|
|
56
|
+
### Added
|
|
57
|
+
- **MySQL dialect support**: Full schema parsing and query validation for MySQL
|
|
58
|
+
- MySQL-specific types: `TINYINT`, `MEDIUMINT`, `UNSIGNED` integer variants, `DATETIME`, inline `ENUM`
|
|
59
|
+
- `AUTO_INCREMENT` handling with implicit NOT NULL inference
|
|
60
|
+
- 10 MySQL unit tests covering schema parsing, SELECT, JOIN, INSERT, UPDATE, DELETE, subquery, CTE, and error detection
|
|
61
|
+
- Real-world MySQL test fixtures:
|
|
62
|
+
- **Sakila** (BSD): 16 tables, 40 valid queries, 12 error detection tests
|
|
63
|
+
- **Chinook MySQL** (MIT): 11 tables, 40 valid queries, 12 error detection tests
|
|
64
|
+
|
|
65
|
+
## [0.1.0-alpha.4] - 2026-02-08
|
|
66
|
+
|
|
67
|
+
### Added
|
|
68
|
+
- **Derived table (subquery in FROM) support**: Resolve aliases and validate column references for `FROM (SELECT ...) AS sub`
|
|
69
|
+
- **LATERAL vs non-LATERAL scope isolation**: Non-LATERAL subqueries correctly cannot see outer FROM tables
|
|
70
|
+
- **UPDATE ... FROM / DELETE ... USING**: PostgreSQL-specific multi-table update/delete syntax
|
|
71
|
+
- **Recursive CTE support**: CTEs can reference themselves in recursive queries
|
|
72
|
+
- **Table-valued functions in FROM**: `generate_series()`, `unnest()` etc. recognized as table sources
|
|
73
|
+
- **UNION/INTERSECT/EXCEPT column inference**: Infer output columns from set operations for CTE/derived table validation
|
|
74
|
+
- **Comprehensive expression resolution**: AtTimeZone, Collate, Ceil/Floor, Overlay, IsDistinctFrom, IsUnknown, SimilarTo, Tuple, Array, Subscript, Method, GroupingSets/Cube/Rollup
|
|
75
|
+
- **Function FILTER/OVER clause resolution**: Validate column references in `COUNT(*) FILTER (WHERE ...)` and `OVER (PARTITION BY ... ORDER BY ...)`
|
|
76
|
+
- **ORDER BY column resolution**: Validate ORDER BY references including SELECT alias support
|
|
77
|
+
- **Named function argument resolution**: Handle `func(name => value)` syntax
|
|
78
|
+
- 72 PostgreSQL pattern test fixtures (basic, advanced, and expression coverage)
|
|
79
|
+
|
|
80
|
+
### Fixed
|
|
81
|
+
- WHERE subquery scope leak: subqueries in IN/EXISTS no longer pollute outer table scope
|
|
82
|
+
- VALUES derived table column aliases now correctly applied
|
|
83
|
+
- Empty derived_columns (table-valued functions) no longer cause false column-not-found errors
|
|
84
|
+
|
|
85
|
+
## [0.1.0-alpha.3] - 2026-02-08
|
|
86
|
+
|
|
87
|
+
### Added
|
|
88
|
+
- **`--dialect` flag wired up**: CLI `--dialect` option now correctly configures the SQL parser dialect (previously ignored)
|
|
89
|
+
- **Real-world schema test fixtures**: Chinook, Pagila, Northwind schemas with comprehensive valid/invalid query tests covering SELECT, JOIN, INSERT, UPDATE, DELETE, subqueries, and CTEs
|
|
90
|
+
- Third-party license file for test fixtures
|
|
91
|
+
|
|
92
|
+
### Fixed
|
|
93
|
+
- `--dialect` CLI flag was completely ignored; PostgreSQL dialect was hardcoded throughout
|
|
94
|
+
- ALTER TABLE warnings for non-schema-affecting operations (e.g., `OWNER TO`) are now suppressed
|
|
95
|
+
|
|
96
|
+
## [0.1.0-alpha.2] - 2026-02-08
|
|
97
|
+
|
|
98
|
+
### Added
|
|
99
|
+
- **CHECK constraints**: Column-level and table-level CHECK constraint parsing and storage
|
|
100
|
+
- **CREATE TYPE AS ENUM**: Enum type definitions with value storage in catalog
|
|
101
|
+
- **GENERATED AS IDENTITY**: ALWAYS and BY DEFAULT identity columns with implicit NOT NULL
|
|
102
|
+
- **CREATE VIEW**: View definitions with column inference from SELECT projection, wildcard expansion, and query-time resolution
|
|
103
|
+
- **ALTER TABLE**: ADD COLUMN, DROP COLUMN, RENAME COLUMN, RENAME TABLE, ADD CONSTRAINT support
|
|
104
|
+
- **Resilient SQL parsing**: Gracefully skip unsupported DDL statements (CREATE FUNCTION, CREATE TRIGGER, CREATE DOMAIN, etc.) instead of failing the entire schema file
|
|
105
|
+
- Real-world test fixtures from Sakila and webknossos schemas
|
|
106
|
+
|
|
107
|
+
### Fixed
|
|
108
|
+
- Schema files with mixed supported/unsupported SQL statements now parse correctly
|
|
109
|
+
- Comments preceding DDL statements no longer cause statement-by-statement parsing to skip valid statements
|
|
110
|
+
|
|
111
|
+
### Changed
|
|
112
|
+
- Known Limitations updated: VIEWs are now supported; ALTER TABLE is now supported
|
|
113
|
+
|
|
114
|
+
## [0.1.0-alpha.1] - 2026-02-07
|
|
115
|
+
|
|
116
|
+
### Added
|
|
117
|
+
- Initial release of sqlsift
|
|
118
|
+
- SQL static analysis against schema definitions
|
|
119
|
+
- Support for PostgreSQL dialect
|
|
120
|
+
- Schema parsing from CREATE TABLE statements
|
|
121
|
+
- Query validation for SELECT, INSERT, UPDATE, DELETE statements
|
|
122
|
+
- Error detection:
|
|
123
|
+
- E0001: Table not found
|
|
124
|
+
- E0002: Column not found
|
|
125
|
+
- E0003: Type mismatch (reserved)
|
|
126
|
+
- E0004: Potential NULL violation (reserved)
|
|
127
|
+
- E0005: Column count mismatch in INSERT
|
|
128
|
+
- E0006: Ambiguous column reference
|
|
129
|
+
- E0007: JOIN type mismatch (reserved)
|
|
130
|
+
- E1000: Parse error
|
|
131
|
+
- JOIN condition validation
|
|
132
|
+
- Subquery support (including correlated subqueries)
|
|
133
|
+
- CTE (Common Table Expressions) support
|
|
134
|
+
- Error position reporting (line and column numbers)
|
|
135
|
+
- Multiple output formats: human-readable, JSON, SARIF
|
|
136
|
+
- Configuration file support (sqlsift.toml)
|
|
137
|
+
- Rule disabling via CLI (--disable) or config file
|
|
138
|
+
- CLI with check, schema, and parse commands
|
|
139
|
+
- Typo suggestions using Levenshtein distance
|
|
140
|
+
- CI/CD integration support via exit codes and SARIF output
|
|
141
|
+
- Framework integration examples (Rails, Prisma)
|
|
142
|
+
|
|
143
|
+
### Known Limitations
|
|
144
|
+
- Only PostgreSQL dialect fully supported
|
|
145
|
+
- Type checking is basic (existence only, not full type inference)
|
|
146
|
+
- No support for VIEWs, functions, or stored procedures
|
|
147
|
+
- Derived table (subquery in FROM) column resolution is incomplete
|
|
148
|
+
|
|
149
|
+
[Unreleased]: https://github.com/yukikotani231/sqlsift/compare/v0.1.0-alpha.8...HEAD
|
|
150
|
+
[0.1.0-alpha.8]: https://github.com/yukikotani231/sqlsift/compare/v0.1.0-alpha.7...v0.1.0-alpha.8
|
|
151
|
+
[0.1.0-alpha.7]: https://github.com/yukikotani231/sqlsift/compare/v0.1.0-alpha.6...v0.1.0-alpha.7
|
|
152
|
+
[0.1.0-alpha.6]: https://github.com/yukikotani231/sqlsift/compare/v0.1.0-alpha.5...v0.1.0-alpha.6
|
|
153
|
+
[0.1.0-alpha.5]: https://github.com/yukikotani231/sqlsift/compare/v0.1.0-alpha.4...v0.1.0-alpha.5
|
|
154
|
+
[0.1.0-alpha.4]: https://github.com/yukikotani231/sqlsift/compare/v0.1.0-alpha.3...v0.1.0-alpha.4
|
|
155
|
+
[0.1.0-alpha.3]: https://github.com/yukikotani231/sqlsift/compare/v0.1.0-alpha.2...v0.1.0-alpha.3
|
|
156
|
+
[0.1.0-alpha.2]: https://github.com/yukikotani231/sqlsift/compare/v0.1.0-alpha.1...v0.1.0-alpha.2
|
|
157
|
+
[0.1.0-alpha.1]: https://github.com/yukikotani231/sqlsift/releases/tag/v0.1.0-alpha.1
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 yukikotani
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
# sqlsift
|
|
2
|
+
|
|
3
|
+
[](https://github.com/yukikotani231/sqlsift/actions/workflows/ci.yml)
|
|
4
|
+
[](https://crates.io/crates/sqlsift-cli)
|
|
5
|
+
[](LICENSE)
|
|
6
|
+
|
|
7
|
+
**SQL static analyzer that validates queries against schema definitions — no database connection required.**
|
|
8
|
+
|
|
9
|
+
sqlsift parses your DDL files (CREATE TABLE, CREATE VIEW, CREATE TYPE, ALTER TABLE, etc.) and validates SQL queries at build time, catching errors like missing tables, unknown columns, and typos before they reach production.
|
|
10
|
+
|
|
11
|
+
> **Note:** sqlsift is in early development (alpha). APIs and diagnostics may change between versions. Feedback and contributions are welcome!
|
|
12
|
+
|
|
13
|
+
## Features
|
|
14
|
+
|
|
15
|
+
- **Zero database dependency** — Works entirely offline using schema SQL files
|
|
16
|
+
- **Framework agnostic** — Works with Rails, Prisma, raw SQL migrations, and more
|
|
17
|
+
- **Helpful diagnostics** — Clear error messages with suggestions for typos
|
|
18
|
+
- **CI-ready** — JSON and SARIF output formats for integration with CI/CD pipelines
|
|
19
|
+
- **Fast** — Built in Rust for speed
|
|
20
|
+
|
|
21
|
+
## Installation
|
|
22
|
+
|
|
23
|
+
### via npm (Recommended)
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
npm install -g sqlsift-cli
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Or use directly with `npx`:
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
npx sqlsift-cli check --schema schema.sql query.sql
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### via Cargo
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
cargo install sqlsift-cli
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### From GitHub Releases
|
|
42
|
+
|
|
43
|
+
Download the latest binary from [Releases](https://github.com/yukikotani231/sqlsift/releases).
|
|
44
|
+
|
|
45
|
+
## Quick Start
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
# Validate queries against a schema file
|
|
49
|
+
sqlsift check --schema schema.sql queries/*.sql
|
|
50
|
+
|
|
51
|
+
# Use multiple schema files
|
|
52
|
+
sqlsift check -s users.sql -s orders.sql queries/*.sql
|
|
53
|
+
|
|
54
|
+
# Use a migrations directory
|
|
55
|
+
sqlsift check --schema-dir ./migrations queries/*.sql
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Example
|
|
59
|
+
|
|
60
|
+
Given a schema:
|
|
61
|
+
|
|
62
|
+
```sql
|
|
63
|
+
-- schema.sql
|
|
64
|
+
CREATE TABLE users (
|
|
65
|
+
id SERIAL PRIMARY KEY,
|
|
66
|
+
name VARCHAR(100) NOT NULL,
|
|
67
|
+
email TEXT UNIQUE
|
|
68
|
+
);
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
And a query with errors:
|
|
72
|
+
|
|
73
|
+
```sql
|
|
74
|
+
-- query.sql
|
|
75
|
+
SELECT naem, user_id FROM users;
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
sqlsift will report:
|
|
79
|
+
|
|
80
|
+
```
|
|
81
|
+
error[E0002]: Column 'naem' not found
|
|
82
|
+
= help: Did you mean 'name'?
|
|
83
|
+
|
|
84
|
+
error[E0002]: Column 'user_id' not found
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## Framework Integration
|
|
88
|
+
|
|
89
|
+
### Prisma
|
|
90
|
+
|
|
91
|
+
Prisma generates SQL migration files automatically:
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
sqlsift check --schema-dir prisma/migrations queries/*.sql
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### Rails
|
|
98
|
+
|
|
99
|
+
With `config.active_record.schema_format = :sql`:
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
sqlsift check --schema db/structure.sql queries/*.sql
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Or with SQL migrations:
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
sqlsift check --schema-dir db/migrate queries/*.sql
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### Raw SQL
|
|
112
|
+
|
|
113
|
+
Just point to your schema files:
|
|
114
|
+
|
|
115
|
+
```bash
|
|
116
|
+
sqlsift check --schema schema/*.sql queries/**/*.sql
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
## Diagnostic Rules
|
|
120
|
+
|
|
121
|
+
| Code | Name | Description | Status |
|
|
122
|
+
|------|------|-------------|--------|
|
|
123
|
+
| E0001 | table-not-found | Referenced table does not exist in schema | ✅ Implemented |
|
|
124
|
+
| E0002 | column-not-found | Referenced column does not exist in table | ✅ Implemented |
|
|
125
|
+
| E0003 | type-mismatch | Type incompatibility in expressions (comparisons, arithmetic) | ✅ Implemented |
|
|
126
|
+
| E0004 | potential-null-violation | Possible NOT NULL constraint violation | 🚧 Reserved |
|
|
127
|
+
| E0005 | column-count-mismatch | INSERT column count doesn't match values | ✅ Implemented |
|
|
128
|
+
| E0006 | ambiguous-column | Column reference is ambiguous across tables | ✅ Implemented |
|
|
129
|
+
| E0007 | join-type-mismatch | JOIN condition compares incompatible types | ✅ Implemented |
|
|
130
|
+
|
|
131
|
+
### Type Inference Coverage (E0003, E0007)
|
|
132
|
+
|
|
133
|
+
**Currently Detected:**
|
|
134
|
+
- ✅ WHERE clause comparisons (`WHERE id = 'text'`)
|
|
135
|
+
- ✅ Arithmetic operations (`SELECT name + 10`)
|
|
136
|
+
- ✅ JOIN conditions (`ON users.id = orders.user_name`)
|
|
137
|
+
- ✅ INSERT value type mismatches (`INSERT INTO users (id) VALUES ('text')`)
|
|
138
|
+
- ✅ UPDATE assignment type mismatches (`UPDATE users SET id = 'text'`)
|
|
139
|
+
- ✅ Nested expressions (`WHERE (a + b) * 2 = 'text'`)
|
|
140
|
+
- ✅ All comparison operators (=, !=, <, >, <=, >=)
|
|
141
|
+
- ✅ Numeric type compatibility (INTEGER, BIGINT, DECIMAL, etc.)
|
|
142
|
+
|
|
143
|
+
**Not Yet Detected:**
|
|
144
|
+
- ⏳ CAST expression type inference
|
|
145
|
+
- ⏳ Function return types (COUNT, SUM, AVG, etc.)
|
|
146
|
+
- ⏳ CASE expression type consistency
|
|
147
|
+
- ⏳ Subquery/CTE column type inference
|
|
148
|
+
|
|
149
|
+
## CLI Reference
|
|
150
|
+
|
|
151
|
+
```
|
|
152
|
+
sqlsift check [OPTIONS] <FILES>...
|
|
153
|
+
|
|
154
|
+
Arguments:
|
|
155
|
+
<FILES>... SQL files to validate (supports glob patterns)
|
|
156
|
+
|
|
157
|
+
Options:
|
|
158
|
+
-s, --schema <FILE> Schema definition file (can be specified multiple times)
|
|
159
|
+
--schema-dir <DIR> Directory containing schema files
|
|
160
|
+
-c, --config <FILE> Path to configuration file [default: sqlsift.toml]
|
|
161
|
+
--disable <RULE> Disable specific rules (e.g., E0001, E0002)
|
|
162
|
+
-d, --dialect <NAME> SQL dialect [default: postgresql]
|
|
163
|
+
-f, --format <FORMAT> Output format: human, json, sarif [default: human]
|
|
164
|
+
--max-errors <N> Maximum number of errors before stopping [default: 100]
|
|
165
|
+
-v, --verbose Enable verbose output
|
|
166
|
+
-q, --quiet Suppress non-error output
|
|
167
|
+
-h, --help Print help
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
## Output Formats
|
|
171
|
+
|
|
172
|
+
### Human (default)
|
|
173
|
+
|
|
174
|
+
```
|
|
175
|
+
error[E0002]: Column 'user_id' not found in table 'users'
|
|
176
|
+
--> queries/fetch.sql:3:12
|
|
177
|
+
|
|
|
178
|
+
3 | WHERE users.user_id = $1
|
|
179
|
+
| ^^^^^^^^^
|
|
180
|
+
|
|
|
181
|
+
= help: Did you mean 'id'?
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
### JSON
|
|
185
|
+
|
|
186
|
+
```bash
|
|
187
|
+
sqlsift check -s schema.sql -f json queries/*.sql
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
### SARIF (for GitHub Code Scanning)
|
|
191
|
+
|
|
192
|
+
```bash
|
|
193
|
+
sqlsift check -s schema.sql -f sarif queries/*.sql > results.sarif
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
## Supported SQL Queries
|
|
197
|
+
|
|
198
|
+
- SELECT, INSERT, UPDATE, DELETE with full column/table validation
|
|
199
|
+
- JOINs (INNER, LEFT, RIGHT, FULL, CROSS, NATURAL) with ON/USING clause validation
|
|
200
|
+
- CTEs (WITH clause) including recursive CTEs
|
|
201
|
+
- Subqueries (WHERE IN/EXISTS, FROM derived tables, scalar subqueries)
|
|
202
|
+
- LATERAL vs non-LATERAL scope isolation
|
|
203
|
+
- UPDATE ... FROM / DELETE ... USING (PostgreSQL extensions)
|
|
204
|
+
- Window functions (OVER, PARTITION BY, FILTER)
|
|
205
|
+
- GROUPING SETS, CUBE, ROLLUP
|
|
206
|
+
- DISTINCT ON, UNION / INTERSECT / EXCEPT
|
|
207
|
+
- ORDER BY with SELECT alias support
|
|
208
|
+
- Comprehensive expression coverage (CASE, CAST, JSON operators, AT TIME ZONE, ARRAY, etc.)
|
|
209
|
+
|
|
210
|
+
## Supported DDL
|
|
211
|
+
|
|
212
|
+
- `CREATE TABLE` (columns, constraints, primary keys, foreign keys, UNIQUE)
|
|
213
|
+
- `CREATE VIEW` (column inference from SELECT projection)
|
|
214
|
+
- `CREATE TYPE AS ENUM`
|
|
215
|
+
- `ALTER TABLE` (ADD/DROP/RENAME COLUMN, ADD CONSTRAINT, RENAME TABLE)
|
|
216
|
+
- `CHECK` constraints (column-level and table-level)
|
|
217
|
+
- `GENERATED AS IDENTITY` columns (ALWAYS / BY DEFAULT)
|
|
218
|
+
- Resilient parsing — unsupported DDL (functions, triggers, domains, etc.) is gracefully skipped
|
|
219
|
+
|
|
220
|
+
## Supported SQL Dialects
|
|
221
|
+
|
|
222
|
+
- **PostgreSQL** (default) — fully supported
|
|
223
|
+
- **MySQL** — supported (`--dialect mysql`)
|
|
224
|
+
- **SQLite** — supported (`--dialect sqlite`)
|
|
225
|
+
|
|
226
|
+
Use the `--dialect` flag to specify the dialect.
|
|
227
|
+
|
|
228
|
+
## Roadmap
|
|
229
|
+
|
|
230
|
+
### Completed
|
|
231
|
+
- [x] Configuration file (`sqlsift.toml`)
|
|
232
|
+
- [x] MySQL dialect support
|
|
233
|
+
- [x] SQLite dialect support
|
|
234
|
+
- [x] Type inference for expressions (WHERE, JOIN, arithmetic, INSERT/UPDATE)
|
|
235
|
+
- [x] LSP server for editor integration (VS Code extension)
|
|
236
|
+
|
|
237
|
+
### Planned
|
|
238
|
+
- [ ] CAST expression type inference
|
|
239
|
+
- [ ] Function return type inference (COUNT, SUM, AVG, etc.)
|
|
240
|
+
- [ ] CASE expression type consistency checking
|
|
241
|
+
- [ ] Subquery/CTE column type inference
|
|
242
|
+
- [ ] Custom rule plugins
|
|
243
|
+
|
|
244
|
+
## Contributing
|
|
245
|
+
|
|
246
|
+
Contributions are welcome! Please feel free to submit a Pull Request.
|
|
247
|
+
|
|
248
|
+
1. Fork the repository
|
|
249
|
+
2. Create your feature branch (`git checkout -b feature/amazing-feature`)
|
|
250
|
+
3. Run tests (`cargo test`)
|
|
251
|
+
4. Commit your changes (`git commit -m 'Add amazing feature'`)
|
|
252
|
+
5. Push to the branch (`git push origin feature/amazing-feature`)
|
|
253
|
+
6. Open a Pull Request
|
|
254
|
+
|
|
255
|
+
## License
|
|
256
|
+
|
|
257
|
+
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
|
258
|
+
|
|
259
|
+
## Acknowledgments
|
|
260
|
+
|
|
261
|
+
- [sqlparser-rs](https://github.com/apache/datafusion-sqlparser-rs) — SQL parsing
|
|
262
|
+
- [miette](https://github.com/zkat/miette) — Diagnostic rendering
|
|
263
|
+
- [clap](https://github.com/clap-rs/clap) — CLI framework
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
const { createWriteStream, existsSync, mkdirSync, mkdtemp } = require("fs");
|
|
2
|
+
const { join, sep } = require("path");
|
|
3
|
+
const { spawnSync } = require("child_process");
|
|
4
|
+
const { tmpdir } = require("os");
|
|
5
|
+
|
|
6
|
+
const axios = require("axios");
|
|
7
|
+
const rimraf = require("rimraf");
|
|
8
|
+
const tmpDir = tmpdir();
|
|
9
|
+
|
|
10
|
+
const error = (msg) => {
|
|
11
|
+
console.error(msg);
|
|
12
|
+
process.exit(1);
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
class Package {
|
|
16
|
+
constructor(platform, name, url, filename, zipExt, binaries) {
|
|
17
|
+
let errors = [];
|
|
18
|
+
if (typeof url !== "string") {
|
|
19
|
+
errors.push("url must be a string");
|
|
20
|
+
} else {
|
|
21
|
+
try {
|
|
22
|
+
new URL(url);
|
|
23
|
+
} catch (e) {
|
|
24
|
+
errors.push(e);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
if (name && typeof name !== "string") {
|
|
28
|
+
errors.push("package name must be a string");
|
|
29
|
+
}
|
|
30
|
+
if (!name) {
|
|
31
|
+
errors.push("You must specify the name of your package");
|
|
32
|
+
}
|
|
33
|
+
if (binaries && typeof binaries !== "object") {
|
|
34
|
+
errors.push("binaries must be a string => string map");
|
|
35
|
+
}
|
|
36
|
+
if (!binaries) {
|
|
37
|
+
errors.push("You must specify the binaries in the package");
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (errors.length > 0) {
|
|
41
|
+
let errorMsg =
|
|
42
|
+
"One or more of the parameters you passed to the Binary constructor are invalid:\n";
|
|
43
|
+
errors.forEach((error) => {
|
|
44
|
+
errorMsg += error;
|
|
45
|
+
});
|
|
46
|
+
errorMsg +=
|
|
47
|
+
'\n\nCorrect usage: new Package("my-binary", "https://example.com/binary/download.tar.gz", {"my-binary": "my-binary"})';
|
|
48
|
+
error(errorMsg);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
this.platform = platform;
|
|
52
|
+
this.url = url;
|
|
53
|
+
this.name = name;
|
|
54
|
+
this.filename = filename;
|
|
55
|
+
this.zipExt = zipExt;
|
|
56
|
+
this.installDirectory = join(__dirname, "node_modules", ".bin_real");
|
|
57
|
+
this.binaries = binaries;
|
|
58
|
+
|
|
59
|
+
if (!existsSync(this.installDirectory)) {
|
|
60
|
+
mkdirSync(this.installDirectory, { recursive: true });
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
exists() {
|
|
65
|
+
for (const binaryName in this.binaries) {
|
|
66
|
+
const binRelPath = this.binaries[binaryName];
|
|
67
|
+
const binPath = join(this.installDirectory, binRelPath);
|
|
68
|
+
if (!existsSync(binPath)) {
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return true;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
install(fetchOptions, suppressLogs = false) {
|
|
76
|
+
if (this.exists()) {
|
|
77
|
+
if (!suppressLogs) {
|
|
78
|
+
console.error(
|
|
79
|
+
`${this.name} is already installed, skipping installation.`,
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
return Promise.resolve();
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (existsSync(this.installDirectory)) {
|
|
86
|
+
rimraf.sync(this.installDirectory);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
mkdirSync(this.installDirectory, { recursive: true });
|
|
90
|
+
|
|
91
|
+
if (!suppressLogs) {
|
|
92
|
+
console.error(`Downloading release from ${this.url}`);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return axios({ ...fetchOptions, url: this.url, responseType: "stream" })
|
|
96
|
+
.then((res) => {
|
|
97
|
+
return new Promise((resolve, reject) => {
|
|
98
|
+
mkdtemp(`${tmpDir}${sep}`, (err, directory) => {
|
|
99
|
+
let tempFile = join(directory, this.filename);
|
|
100
|
+
const sink = res.data.pipe(createWriteStream(tempFile));
|
|
101
|
+
sink.on("error", (err) => reject(err));
|
|
102
|
+
sink.on("close", () => {
|
|
103
|
+
if (/\.tar\.*/.test(this.zipExt)) {
|
|
104
|
+
const result = spawnSync("tar", [
|
|
105
|
+
"xf",
|
|
106
|
+
tempFile,
|
|
107
|
+
// The tarballs are stored with a leading directory
|
|
108
|
+
// component; we strip one component in the
|
|
109
|
+
// shell installers too.
|
|
110
|
+
"--strip-components",
|
|
111
|
+
"1",
|
|
112
|
+
"-C",
|
|
113
|
+
this.installDirectory,
|
|
114
|
+
]);
|
|
115
|
+
if (result.status == 0) {
|
|
116
|
+
resolve();
|
|
117
|
+
} else if (result.error) {
|
|
118
|
+
reject(result.error);
|
|
119
|
+
} else {
|
|
120
|
+
reject(
|
|
121
|
+
new Error(
|
|
122
|
+
`An error occurred untarring the artifact: stdout: ${result.stdout}; stderr: ${result.stderr}`,
|
|
123
|
+
),
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
} else if (this.zipExt == ".zip") {
|
|
127
|
+
let result;
|
|
128
|
+
if (this.platform.artifactName.includes("windows")) {
|
|
129
|
+
// Windows does not have "unzip" by default on many installations, instead
|
|
130
|
+
// we use Expand-Archive from powershell
|
|
131
|
+
result = spawnSync("powershell.exe", [
|
|
132
|
+
"-NoProfile",
|
|
133
|
+
"-NonInteractive",
|
|
134
|
+
"-Command",
|
|
135
|
+
`& {
|
|
136
|
+
param([string]$LiteralPath, [string]$DestinationPath)
|
|
137
|
+
Expand-Archive -LiteralPath $LiteralPath -DestinationPath $DestinationPath -Force
|
|
138
|
+
}`,
|
|
139
|
+
tempFile,
|
|
140
|
+
this.installDirectory,
|
|
141
|
+
]);
|
|
142
|
+
} else {
|
|
143
|
+
result = spawnSync("unzip", [
|
|
144
|
+
"-q",
|
|
145
|
+
tempFile,
|
|
146
|
+
"-d",
|
|
147
|
+
this.installDirectory,
|
|
148
|
+
]);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (result.status == 0) {
|
|
152
|
+
resolve();
|
|
153
|
+
} else if (result.error) {
|
|
154
|
+
reject(result.error);
|
|
155
|
+
} else {
|
|
156
|
+
reject(
|
|
157
|
+
new Error(
|
|
158
|
+
`An error occurred unzipping the artifact: stdout: ${result.stdout}; stderr: ${result.stderr}`,
|
|
159
|
+
),
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
} else {
|
|
163
|
+
reject(
|
|
164
|
+
new Error(`Unrecognized file extension: ${this.zipExt}`),
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
});
|
|
169
|
+
});
|
|
170
|
+
})
|
|
171
|
+
.then(() => {
|
|
172
|
+
if (!suppressLogs) {
|
|
173
|
+
console.error(`${this.name} has been installed!`);
|
|
174
|
+
}
|
|
175
|
+
})
|
|
176
|
+
.catch((e) => {
|
|
177
|
+
error(`Error fetching release: ${e.message}`);
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
run(binaryName, fetchOptions) {
|
|
182
|
+
const promise = !this.exists()
|
|
183
|
+
? this.install(fetchOptions, true)
|
|
184
|
+
: Promise.resolve();
|
|
185
|
+
|
|
186
|
+
promise
|
|
187
|
+
.then(() => {
|
|
188
|
+
const [, , ...args] = process.argv;
|
|
189
|
+
|
|
190
|
+
const options = { cwd: process.cwd(), stdio: "inherit" };
|
|
191
|
+
|
|
192
|
+
const binRelPath = this.binaries[binaryName];
|
|
193
|
+
if (!binRelPath) {
|
|
194
|
+
error(`${binaryName} is not a known binary in ${this.name}`);
|
|
195
|
+
}
|
|
196
|
+
const binPath = join(this.installDirectory, binRelPath);
|
|
197
|
+
const result = spawnSync(binPath, args, options);
|
|
198
|
+
|
|
199
|
+
if (result.error) {
|
|
200
|
+
error(result.error);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
process.exit(result.status);
|
|
204
|
+
})
|
|
205
|
+
.catch((e) => {
|
|
206
|
+
error(e.message);
|
|
207
|
+
process.exit(1);
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
module.exports.Package = Package;
|