syntaqlite — Competitive Comparison
SQLite SQL tooling landscape.
Parser Comparison
Per-statement SQLite SQL parsing accuracy, validated against sqlite3 as ground truth.
Ground Truth
Validating all test statements against sqlite3:
| Statement | sqlite3 |
|---|---|
| T01: Multi ON CONFLICT UPSERT + RETURNING | OK |
| T02: Recursive CTE + MATERIALIZED / NOT MATERIALIZ | OK |
| T03: CREATE TABLE STRICT + WITHOUT ROWID + generat | OK |
| T04: UPDATE FROM + INDEXED BY | OK |
| T05: CREATE TRIGGER + RAISE + WHEN + FOR EACH ROW | OK |
| T06: FILTER clause + IIF + NULLS LAST | OK |
| T07: ATTACH DATABASE | OK |
| T08: INSERT OR REPLACE | OK |
| T09: CREATE VIRTUAL TABLE (FTS5) | OK |
| T10: PRAGMA | OK |
| T11: EXPLAIN QUERY PLAN | OK |
| T12: ALTER TABLE DROP COLUMN | OK |
| T13: ALTER TABLE RENAME COLUMN | OK |
| T14: REINDEX | OK |
| T15: Window frame RANGE BETWEEN | OK |
| T16: CREATE INDEX with WHERE (partial index) | OK |
| T17: REPLACE statement | OK |
| T18: Nested window functions + EXCLUDE | OK |
| T19: GLOB and LIKE with ESCAPE | OK |
| T20: INSERT with multiple VALUES + ON CONFLICT DO | OK |
| T21: Complex subquery expressions | OK |
| T22: ANALYZE | OK |
| T23: SAVEPOINT / RELEASE / ROLLBACK TO | OK |
| T24: DROP TABLE IF EXISTS | OK |
| T25: CREATE TABLE AS SELECT | OK |
| T26: DETACH DATABASE | OK |
| T27: UPSERT with complex expressions in DO UPDATE | OK |
| T28: WITH (non-recursive) + DELETE ... RETURNING | OK |
| T29: UPDATE ... RETURNING | OK |
| T30: RIGHT JOIN + IS DISTINCT FROM | OK |
| T31: FULL OUTER JOIN | OK |
| T32: JSON -> and ->> operators | OK |
| T33: Numeric literals with underscores | OK |
| T34: Multiple WINDOW definitions + nth_value + nti | OK |
| T35: HAVING without GROUP BY (3.39+) | OK |
| T36: IS NOT DISTINCT FROM in complex expression | OK |
| T37: Blob literals + CAST chains | OK |
| T38: GENERATED ALWAYS AS (VIRTUAL vs STORED) + com | OK |
| T39: Deeply nested CTE + compound SELECT (UNION / | OK |
| T40: Window GROUPS frame + EXCLUDE TIES | OK |
40/40 statements validated by sqlite3.
Parser Accuracy
Legend: PASS = correctly parses valid SQL, FAIL = rejects valid SQL, FP = accepts invalid SQL
| Test | sqlite3 | syntaqlite | lemon-rs | sql-parser-cst | sqlglot[c] | sqlfluff | sqlparser-rs | node-sql-parser |
|---|---|---|---|---|---|---|---|---|
| T01: Multi ON CONFLICT UPSERT + RETURN | OK | PASS | PASS | PASS | FAIL | FAIL | FAIL | FAIL |
| T02: Recursive CTE + MATERIALIZED / NO | OK | PASS | PASS | PASS | PASS | FAIL | FAIL | FAIL |
| T03: CREATE TABLE STRICT + WITHOUT ROW | OK | PASS | PASS | PASS | FAIL | PASS | FAIL | FAIL |
| T04: UPDATE FROM + INDEXED BY | OK | PASS | PASS | PASS | PASS | FAIL | FAIL | FAIL |
| T05: CREATE TRIGGER + RAISE + WHEN + F | OK | PASS | PASS | PASS | PASS | PASS | FAIL | PASS |
| T06: FILTER clause + IIF + NULLS LAST | OK | PASS | PASS | PASS | PASS | PASS | PASS | FAIL |
| T07: ATTACH DATABASE | OK | PASS | PASS | PASS | PASS | FAIL | PASS | PASS |
| T08: INSERT OR REPLACE | OK | PASS | PASS | PASS | PASS | PASS | PASS | PASS |
| T09: CREATE VIRTUAL TABLE (FTS5) | OK | PASS | PASS | PASS | PASS | FAIL | FAIL | FAIL |
| T10: PRAGMA | OK | PASS | PASS | PASS | PASS | PASS | PASS | FAIL |
| T11: EXPLAIN QUERY PLAN | OK | PASS | PASS | PASS | PASS | FAIL | PASS | FAIL |
| T12: ALTER TABLE DROP COLUMN | OK | PASS | PASS | PASS | PASS | PASS | PASS | PASS |
| T13: ALTER TABLE RENAME COLUMN | OK | PASS | PASS | PASS | PASS | PASS | PASS | PASS |
| T14: REINDEX | OK | PASS | PASS | PASS | PASS | FAIL | FAIL | FAIL |
| T15: Window frame RANGE BETWEEN | OK | PASS | PASS | PASS | PASS | PASS | PASS | FAIL |
| T16: CREATE INDEX with WHERE (partial | OK | PASS | PASS | PASS | PASS | PASS | PASS | PASS |
| T17: REPLACE statement | OK | PASS | PASS | PASS | PASS | PASS | PASS | PASS |
| T18: Nested window functions + EXCLUDE | OK | PASS | PASS | PASS | PASS | PASS | FAIL | FAIL |
| T19: GLOB and LIKE with ESCAPE | OK | PASS | PASS | PASS | PASS | PASS | FAIL | FAIL |
| T20: INSERT with multiple VALUES + ON | OK | PASS | PASS | PASS | PASS | PASS | PASS | FAIL |
| T21: Complex subquery expressions | OK | PASS | PASS | PASS | PASS | PASS | PASS | PASS |
| T22: ANALYZE | OK | PASS | PASS | PASS | PASS | FAIL | FAIL | FAIL |
| T23: SAVEPOINT / RELEASE / ROLLBACK TO | OK | PASS | PASS | PASS | PASS | FAIL | PASS | FAIL |
| T24: DROP TABLE IF EXISTS | OK | PASS | PASS | PASS | PASS | PASS | PASS | PASS |
| T25: CREATE TABLE AS SELECT | OK | PASS | PASS | PASS | PASS | PASS | PASS | PASS |
| T26: DETACH DATABASE | OK | PASS | PASS | PASS | PASS | FAIL | FAIL | FAIL |
| T27: UPSERT with complex expressions i | OK | PASS | PASS | PASS | PASS | PASS | PASS | FAIL |
| T28: WITH (non-recursive) + DELETE ... | OK | PASS | PASS | PASS | PASS | PASS | FAIL | FAIL |
| T29: UPDATE ... RETURNING | OK | PASS | PASS | PASS | PASS | PASS | PASS | PASS |
| T30: RIGHT JOIN + IS DISTINCT FROM | OK | PASS | PASS | PASS | PASS | PASS | PASS | FAIL |
| T31: FULL OUTER JOIN | OK | PASS | PASS | PASS | PASS | PASS | PASS | FAIL |
| T32: JSON -> and ->> operators | OK | PASS | PASS | PASS | PASS | PASS | PASS | PASS |
| T33: Numeric literals with underscores | OK | PASS | PASS | FAIL | FAIL | FAIL | FAIL | FAIL |
| T34: Multiple WINDOW definitions + nth | OK | PASS | PASS | PASS | PASS | PASS | PASS | FAIL |
| T35: HAVING without GROUP BY (3.39+) | OK | PASS | PASS | PASS | PASS | PASS | PASS | PASS |
| T36: IS NOT DISTINCT FROM in complex e | OK | PASS | PASS | PASS | PASS | PASS | PASS | FAIL |
| T37: Blob literals + CAST chains | OK | PASS | PASS | PASS | PASS | PASS | PASS | PASS |
| T38: GENERATED ALWAYS AS (VIRTUAL vs S | OK | PASS | PASS | PASS | FAIL | PASS | PASS | PASS |
| T39: Deeply nested CTE + compound SELE | OK | PASS | PASS | PASS | PASS | PASS | PASS | FAIL |
| T40: Window GROUPS frame + EXCLUDE TIE | OK | PASS | PASS | PASS | FAIL | PASS | FAIL | FAIL |
Scoreboard
| Tool | Correct | Rejects Valid | Accepts Invalid |
|---|---|---|---|
| syntaqlite | 40/40 (100%) ████████████████████ | - | - |
| lemon-rs | 40/40 (100%) ████████████████████ | - | - |
| sql-parser-cst | 39/40 (97%) ███████████████████ | 1 | - |
| sqlglot[c] | 35/40 (87%) █████████████████ | 5 | - |
| sqlfluff | 29/40 (72%) ██████████████ | 11 | - |
| sqlparser-rs | 26/40 (65%) █████████████ | 14 | - |
| node-sql-parser | 15/40 (37%) ███████ | 25 | - |
Parse Speed
bench.sql: 117 lines, 3,545 bytesbench_30x.sql: 3510 lines, 106,350 bytes
bench.sql (1x)
| Command | Mean [ms] | Min [ms] | Max [ms] | Relative |
|---|---|---|---|---|
syntaqlite | 1.8 ± 0.3 | 1.6 | 5.9 | 1.13 ± 0.28 |
lemon-rs | 1.6 ± 0.3 | 1.4 | 5.8 | 1.00 |
sql-parser-cst | 77.5 ± 4.0 | 74.3 | 92.6 | 49.97 ± 10.39 |
sqlglot[c] | 87.3 ± 6.7 | 82.8 | 113.4 | 56.30 ± 12.13 |
sqlparser-rs | 1.8 ± 0.1 | 1.7 | 2.8 | 1.17 ± 0.24 |
node-sql-parser | 74.4 ± 2.3 | 71.7 | 83.5 | 47.95 ± 9.77 |
sqlfluff | 710.5 ± 13.6 | 699.5 | 745.8 | 458.24 ± 92.72 |
bench_30x.sql (30x)
| Command | Mean [ms] | Min [ms] | Max [ms] | Relative |
|---|---|---|---|---|
syntaqlite | 2.6 ± 0.3 | 2.4 | 9.1 | 1.00 |
lemon-rs | 4.3 ± 0.2 | 4.1 | 5.4 | 1.63 ± 0.21 |
sql-parser-cst | 148.9 ± 11.1 | 140.2 | 181.2 | 56.66 ± 8.10 |
sqlglot[c] | 183.9 ± 3.5 | 180.3 | 194.7 | 69.99 ± 8.64 |
sqlparser-rs | 10.7 ± 0.4 | 10.2 | 13.2 | 4.08 ± 0.52 |
node-sql-parser | 151.1 ± 4.8 | 147.2 | 167.7 | 57.51 ± 7.26 |
sqlfluff | 504.2 ± 3.1 | 501.0 | 508.3 | 191.89 ± 23.45 |
Formatter Comparison
Round-trip correctness (format then validate with sqlite3) and speed.
Ground Truth
| Statement | sqlite3 |
|---|---|
| T01: Multi ON CONFLICT UPSERT + RETURNING | OK |
| T02: Recursive CTE + MATERIALIZED / NOT MATERIALIZ | OK |
| T03: CREATE TABLE STRICT + WITHOUT ROWID + generat | OK |
| T04: UPDATE FROM + INDEXED BY | OK |
| T05: CREATE TRIGGER + RAISE + WHEN + FOR EACH ROW | OK |
| T06: FILTER clause + IIF + NULLS LAST | OK |
| T07: ATTACH DATABASE | OK |
| T08: INSERT OR REPLACE | OK |
| T09: CREATE VIRTUAL TABLE (FTS5) | OK |
| T10: PRAGMA | OK |
| T11: EXPLAIN QUERY PLAN | OK |
| T12: ALTER TABLE DROP COLUMN | OK |
| T13: ALTER TABLE RENAME COLUMN | OK |
| T14: REINDEX | OK |
| T15: Window frame RANGE BETWEEN | OK |
| T16: CREATE INDEX with WHERE (partial index) | OK |
| T17: REPLACE statement | OK |
| T18: Nested window functions + EXCLUDE | OK |
| T19: GLOB and LIKE with ESCAPE | OK |
| T20: INSERT with multiple VALUES + ON CONFLICT DO | OK |
| T21: Complex subquery expressions | OK |
| T22: ANALYZE | OK |
| T23: SAVEPOINT / RELEASE / ROLLBACK TO | OK |
| T24: DROP TABLE IF EXISTS | OK |
| T25: CREATE TABLE AS SELECT | OK |
| T26: DETACH DATABASE | OK |
| T27: UPSERT with complex expressions in DO UPDATE | OK |
| T28: WITH (non-recursive) + DELETE ... RETURNING | OK |
| T29: UPDATE ... RETURNING | OK |
| T30: RIGHT JOIN + IS DISTINCT FROM | OK |
| T31: FULL OUTER JOIN | OK |
| T32: JSON -> and ->> operators | OK |
| T33: Numeric literals with underscores | OK |
| T34: Multiple WINDOW definitions + nth_value + nti | OK |
| T35: HAVING without GROUP BY (3.39+) | OK |
| T36: IS NOT DISTINCT FROM in complex expression | OK |
| T37: Blob literals + CAST chains | OK |
| T38: GENERATED ALWAYS AS (VIRTUAL vs STORED) + com | OK |
| T39: Deeply nested CTE + compound SELECT (UNION / | OK |
| T40: Window GROUPS frame + EXCLUDE TIES | OK |
40/40 statements validated by sqlite3.
Round-Trip Validation
For each formatter: does the formatted output still pass real SQLite?
| Test | syntaqlite | prettier-cst | sql-formatter | sqlglot[c] | sleek | sqruff |
|---|---|---|---|---|---|---|
| T01: Multi ON CONFLICT UPSERT + RETURN | OK | OK | OK | FAIL | OK | OK |
| T02: Recursive CTE + MATERIALIZED / NO | OK | OK | OK | CORRUPT | OK | OK |
| T03: CREATE TABLE STRICT + WITHOUT ROW | OK | OK | OK | FAIL | OK | OK |
| T04: UPDATE FROM + INDEXED BY | OK | OK | OK | OK | OK | OK |
| T05: CREATE TRIGGER + RAISE + WHEN + F | OK | OK | OK | CORRUPT | OK | OK |
| T06: FILTER clause + IIF + NULLS LAST | OK | OK | OK | OK | OK | OK |
| T07: ATTACH DATABASE | OK | OK | OK | OK | OK | OK |
| T08: INSERT OR REPLACE | OK | OK | OK | OK | OK | OK |
| T09: CREATE VIRTUAL TABLE (FTS5) | OK | OK | OK | OK | OK | OK |
| T10: PRAGMA | OK | OK | OK | OK | OK | OK |
| T11: EXPLAIN QUERY PLAN | OK | OK | OK | OK | OK | OK |
| T12: ALTER TABLE DROP COLUMN | OK | OK | OK | OK | OK | OK |
| T13: ALTER TABLE RENAME COLUMN | OK | OK | OK | OK | OK | OK |
| T14: REINDEX | OK | OK | OK | CORRUPT | OK | OK |
| T15: Window frame RANGE BETWEEN | OK | OK | OK | OK | OK | OK |
| T16: CREATE INDEX with WHERE (partial | OK | OK | OK | OK | OK | OK |
| T17: REPLACE statement | OK | OK | OK | OK | OK | OK |
| T18: Nested window functions + EXCLUDE | OK | OK | OK | OK | OK | OK |
| T19: GLOB and LIKE with ESCAPE | OK | OK | OK | OK | OK | OK |
| T20: INSERT with multiple VALUES + ON | OK | OK | OK | OK | OK | OK |
| T21: Complex subquery expressions | OK | OK | OK | OK | OK | OK |
| T22: ANALYZE | OK | OK | OK | OK | OK | OK |
| T23: SAVEPOINT / RELEASE / ROLLBACK TO | OK | OK | OK | CORRUPT | OK | OK |
| T24: DROP TABLE IF EXISTS | OK | OK | OK | OK | OK | OK |
| T25: CREATE TABLE AS SELECT | OK | OK | OK | OK | OK | OK |
| T26: DETACH DATABASE | OK | OK | OK | OK | OK | OK |
| T27: UPSERT with complex expressions i | OK | OK | OK | OK | OK | OK |
| T28: WITH (non-recursive) + DELETE ... | OK | OK | OK | OK | OK | OK |
| T29: UPDATE ... RETURNING | OK | OK | OK | OK | OK | OK |
| T30: RIGHT JOIN + IS DISTINCT FROM | OK | OK | OK | OK | OK | OK |
| T31: FULL OUTER JOIN | OK | OK | OK | OK | OK | OK |
| T32: JSON -> and ->> operators | OK | OK | OK | OK | OK | CORRUPT |
| T33: Numeric literals with underscores | OK | FAIL | FAIL | FAIL | CORRUPT | OK |
| T34: Multiple WINDOW definitions + nth | OK | OK | OK | OK | OK | OK |
| T35: HAVING without GROUP BY (3.39+) | OK | OK | OK | OK | OK | OK |
| T36: IS NOT DISTINCT FROM in complex e | OK | OK | OK | OK | OK | OK |
| T37: Blob literals + CAST chains | OK | OK | OK | OK | CORRUPT | OK |
| T38: GENERATED ALWAYS AS (VIRTUAL vs S | OK | OK | OK | FAIL | OK | CORRUPT |
| T39: Deeply nested CTE + compound SELE | OK | OK | OK | OK | OK | OK |
| T40: Window GROUPS frame + EXCLUDE TIE | OK | OK | OK | FAIL | OK | OK |
Scoreboard
| Tool | Formats | SQLite OK | Corrupt |
|---|---|---|---|
| syntaqlite | 40/40 | 40/40 | 0 |
| prettier-cst | 39/40 | 39/40 | 0 |
| sql-formatter | 39/40 | 39/40 | 0 |
| sqlglot[c] | 35/40 | 31/40 | 4 |
| sleek | 40/40 | 38/40 | 2 |
| sqruff | 40/40 | 38/40 | 2 |
Corruption Details
| Tool | Test | Error |
|---|---|---|
| sqlglot[c] | T02: Recursive CTE + MATERIALIZED / NOT MATERIALIZED | Error: in prepare, no such column: x |
| LIZED ( VALUES | ||
| sqlglot[c] | T05: CREATE TRIGGER + RAISE + WHEN + FOR EACH ROW | Error: in prepare, near "SELECT": syntax error |
| yees', OLD. | ||
| sqlglot[c] | T14: REINDEX | Error: in prepare, near "AS": syntax error |
| REINDEX AS idx_ | ||
| sqlglot[c] | T23: SAVEPOINT / RELEASE / ROLLBACK TO | Error: in prepare, near "AS": syntax error |
| SAVEPOINT AS my | ||
| sqruff | T32: JSON -> and ->> operators | Error: in prepare, near ">": syntax error |
| EXPLAIN SELECT | ||
| sleek | T33: Numeric literals with underscores | Error: in prepare, near "AS": syntax error |
| EXPLAIN SELECT | ||
| sleek | T37: Blob literals + CAST chains | Error: in prepare, near "AS": syntax error |
| EXPLAIN SELECT | ||
| sqruff | T38: GENERATED ALWAYS AS (VIRTUAL vs STORED) + complex expressions | Error: in prepare, near " |
| abel TEXT GENERA |
Format Speed
bench.sql: 117 lines, 3,545 bytesbench_30x.sql: 3510 lines, 106,350 bytes
bench.sql (1x)
| Command | Mean [ms] | Min [ms] | Max [ms] | Relative |
|---|---|---|---|---|
syntaqlite | 1.8 ± 0.1 | 1.7 | 4.9 | 1.00 |
prettier-cst | 380.0 ± 4.2 | 374.0 | 385.8 | 205.57 ± 15.57 |
sql-formatter | 80.2 ± 8.2 | 74.4 | 111.9 | 43.40 ± 5.49 |
sqlglot[c] | 87.9 ± 1.2 | 85.5 | 91.8 | 47.52 ± 3.62 |
sleek | 8.6 ± 1.1 | 7.9 | 23.3 | 4.67 ± 0.68 |
sqruff | 40.3 ± 1.0 | 38.9 | 44.4 | 21.81 ± 1.71 |
bench_30x.sql (30x)
| Command | Mean [ms] | Min [ms] | Max [ms] | Relative |
|---|---|---|---|---|
syntaqlite | 4.9 ± 0.2 | 4.7 | 6.4 | 1.00 |
prettier-cst | 536.4 ± 3.3 | 531.3 | 539.5 | 109.56 ± 3.70 |
sql-formatter | 199.0 ± 1.5 | 196.8 | 201.5 | 40.65 ± 1.38 |
sqlglot[c] | 269.5 ± 3.6 | 265.2 | 277.6 | 55.04 ± 1.97 |
sleek | 27.7 ± 0.7 | 26.6 | 32.2 | 5.65 ± 0.23 |
sqruff | 3690.8 ± 458.0 | 3342.0 | 4492.7 | 753.88 ± 96.83 |
Slow Tools (single timed run)
| Tool | Time |
|---|---|
| sqlfmt (1x) | 602ms |
| sqlfmt (30x) | 381ms |
| sqlfluff (1x) | 248ms |
Validator Comparison
Error detection accuracy and diagnostic quality.
Diagnostic Quality
A realistic query with subtle errors — how does each tool report them?
Query (2 errors: CTE declares 3 columns but SELECT produces 2; typo ROUDN):
WITH
monthly_stats(month, revenue, order_count) AS (
SELECT
STRFTIME('%Y-%m', o.created_at) AS month,
SUM(o.total) AS revenue
FROM orders o
WHERE o.status = 'completed'
GROUP BY STRFTIME('%Y-%m', o.created_at)
)
SELECT
ms.month,
ms.revenue,
ms.order_count,
ROUDN(ms.revenue / ms.order_count, 2) AS avg_order
FROM monthly_stats ms
ORDER BY ms.month DESC
LIMIT 12;
syntaqlite
Static semantic analysis — offline, no database needed. Finds both errors in one pass:
error: table 'monthly_stats' has 2 values for 3 columns
--> /var/folders/rx/t6_rqmqx0f15l7kgp7yjhcbc0000gn/T/tmpimbv0__j.sql:29:3
|
29 | monthly_stats(month, revenue, order_count) AS (
| ^~~~~~~~~~~~~
warning: unknown function 'ROUDN'
--> /var/folders/rx/t6_rqmqx0f15l7kgp7yjhcbc0000gn/T/tmpimbv0__j.sql:41:3
|
41 | ROUDN(ms.revenue / ms.order_count, 2) AS avg_order
| ^~~~~
= help: did you mean 'round'?
sqlite3
Runtime execution — stops at first error:
Error: in prepare, table monthly_stats has 2 values for 3 columns
sqlite-runner-lsp
Runtime via LSP — wraps sqlite3, same single error:
28:1 error Parse error: table monthly_stats has 2 values for 3 columns
sql-lint
Structural checks only:
/var/folders/rx/t6_rqmqx0f15l7kgp7yjhcbc0000gn/T/tmpum1hr0xz.sql:1 sql-lint was unable to lint the following query "WITH...
Error Detection Accuracy
Schema: users, orders, products, order_items. Ground truth: sqlite3.
| Test | Expect | syntaqlite | sqlite3 | sqlite-runner-lsp | sql-lint |
|---|---|---|---|---|---|
| keyword typo (SELEC) | error | FOUND | FOUND | FOUND | FOUND |
| missing close paren | error | FOUND | FOUND | FOUND | FOUND |
| double comma | error | FOUND | FOUND | FOUND | MISS |
| unterminated string | error | FOUND | FOUND | FOUND | MISS |
| trailing comma in VALUES | error | FOUND | FOUND | FOUND | FOUND |
| unknown table | error | FOUND | FOUND | FOUND | MISS |
| unknown table in JOIN | error | FOUND | FOUND | FOUND | MISS |
| unknown column | error | FOUND | FOUND | FOUND | MISS |
| unknown qualified column | error | FOUND | FOUND | FOUND | MISS |
| unknown column in SELECT | error | FOUND | FOUND | FOUND | MISS |
| SUBSTR: too few args | error | FOUND | FOUND | FOUND | MISS |
| REPLACE: too few args | error | FOUND | FOUND | FOUND | MISS |
| LENGTH: too many args | error | FOUND | FOUND | FOUND | MISS |
| COALESCE: zero args | error | MISS | FOUND | FOUND | MISS |
| CTE: 3 declared, 2 actual | error | FOUND | FOUND | FOUND | FOUND |
| valid: simple SELECT | valid | OK | OK | OK | OK |
| valid: JOIN + aggregate | valid | OK | OK | OK | OK |
| valid: SUBSTR with 3 args | valid | OK | OK | OK | OK |
| valid: COALESCE variadic | valid | OK | OK | OK | OK |
| valid: CTE columns match | valid | OK | OK | OK | FP |
| valid: built-in functions | valid | OK | OK | OK | OK |
| valid: INSERT | valid | OK | OK | OK | OK |
| valid: UPDATE | valid | OK | OK | OK | OK |
| valid: DELETE with WHERE | valid | OK | OK | OK | OK |
Scoreboard
| Tool | Approach | Correct | Missed | FP |
|---|---|---|---|---|
| sqlite3 | runtime execution | 24/24 ████████████████████ | - | - |
| sqlite-runner-lsp | runtime via LSP | 24/24 ████████████████████ | - | - |
| syntaqlite | static semantic | 23/24 ███████████████████ | 1 | - |
| sql-lint | structural checks | 12/24 ██████████ | 11 | 1 |
Validation Speed
bench.sql: 117 lines, 3,545 bytes (+ schema preamble)bench_30x.sql: 3510 lines, 106,350 bytes (+ schema preamble)
bench.sql (1x)
| Command | Mean [ms] | Min [ms] | Max [ms] | Relative |
|---|---|---|---|---|
syntaqlite | 5.7 ± 1.7 | 2.8 | 12.6 | 1.00 |
sqlite3 | 8.9 ± 4.9 | 4.9 | 24.1 | 1.55 ± 0.97 |
sqlite-runner-lsp | 93.6 ± 1.9 | 90.9 | 99.8 | 16.29 ± 4.74 |
sql-lint | 345.9 ± 6.0 | 336.5 | 357.3 | 60.20 ± 17.50 |
bench_30x.sql (30x)
| Command | Mean [ms] | Min [ms] | Max [ms] | Relative |
|---|---|---|---|---|
syntaqlite | 7.3 ± 0.1 | 7.1 | 7.7 | 1.00 |
sqlite3 | 10.1 ± 0.4 | 9.5 | 11.3 | 1.39 ± 0.05 |
sqlite-runner-lsp | 113.3 ± 1.2 | 111.2 | 115.3 | 15.52 ± 0.31 |
sql-lint | 365.4 ± 3.3 | 361.5 | 370.8 | 50.04 ± 0.96 |
LSP Comparison
Feature testing for SQLite-aware language servers.
Tested Capabilities
Each server is started, sent a test file, and probed for completion, hover, diagnostics, and formatting. Results are from actual LSP responses.
| Feature | syntaqlite | sqls | sql-language-server |
|---|---|---|---|
| Completion | Yes (150 items) | Yes (1 items) | Advertised (0 items) |
| Hover | No | Yes | No |
| Go to definition | No | Yes | No |
| Find references | No | No | No |
| Diagnostics: syntax | Yes | No | Yes |
| Diagnostics: semantic | Yes | No | No (style only) |
| Formatting | Yes | Yes | No |
| Rename | No | Yes | Yes |
| Signature help | No | Yes | No |
| Requires DB connection | No | Yes | No |
Diagnostic Detail
What each server reports for SELEC * FROM users; (syntax error):
syntaqlite
1:1 error syntax error near 'SELEC'
sqls
(no diagnostics)
sql-language-server
1:2 error Expected "$", "(", "--", "/*", "ALTER", "CREATE TABLE", "CREATE", "DELETE", "DROP TABLE", "DROP VIEW", "DROP", "INSERT", "REPLACE", "SELECT", "UPDATE", "WITH", "return", [ \t\n\r], or end of input but "S" found.
LSP Startup + Response Speed
Time to start server, send document, receive diagnostics, and exit:
| Command | Mean [ms] | Min [ms] | Max [ms] | Relative |
|---|---|---|---|---|
syntaqlite | 35.0 ± 3.3 | 31.7 | 49.3 | 1.00 |
sqls | 10069.1 ± 3.0 | 10064.3 | 10071.4 | 287.71 ± 27.07 |
sql-language-server | 449.8 ± 20.5 | 414.1 | 472.8 | 12.85 ± 1.34 |