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:

Statementsqlite3
T01: Multi ON CONFLICT UPSERT + RETURNINGOK
T02: Recursive CTE + MATERIALIZED / NOT MATERIALIZOK
T03: CREATE TABLE STRICT + WITHOUT ROWID + generatOK
T04: UPDATE FROM + INDEXED BYOK
T05: CREATE TRIGGER + RAISE + WHEN + FOR EACH ROWOK
T06: FILTER clause + IIF + NULLS LASTOK
T07: ATTACH DATABASEOK
T08: INSERT OR REPLACEOK
T09: CREATE VIRTUAL TABLE (FTS5)OK
T10: PRAGMAOK
T11: EXPLAIN QUERY PLANOK
T12: ALTER TABLE DROP COLUMNOK
T13: ALTER TABLE RENAME COLUMNOK
T14: REINDEXOK
T15: Window frame RANGE BETWEENOK
T16: CREATE INDEX with WHERE (partial index)OK
T17: REPLACE statementOK
T18: Nested window functions + EXCLUDEOK
T19: GLOB and LIKE with ESCAPEOK
T20: INSERT with multiple VALUES + ON CONFLICT DOOK
T21: Complex subquery expressionsOK
T22: ANALYZEOK
T23: SAVEPOINT / RELEASE / ROLLBACK TOOK
T24: DROP TABLE IF EXISTSOK
T25: CREATE TABLE AS SELECTOK
T26: DETACH DATABASEOK
T27: UPSERT with complex expressions in DO UPDATEOK
T28: WITH (non-recursive) + DELETE ... RETURNINGOK
T29: UPDATE ... RETURNINGOK
T30: RIGHT JOIN + IS DISTINCT FROMOK
T31: FULL OUTER JOINOK
T32: JSON -> and ->> operatorsOK
T33: Numeric literals with underscoresOK
T34: Multiple WINDOW definitions + nth_value + ntiOK
T35: HAVING without GROUP BY (3.39+)OK
T36: IS NOT DISTINCT FROM in complex expressionOK
T37: Blob literals + CAST chainsOK
T38: GENERATED ALWAYS AS (VIRTUAL vs STORED) + comOK
T39: Deeply nested CTE + compound SELECT (UNION /OK
T40: Window GROUPS frame + EXCLUDE TIESOK

40/40 statements validated by sqlite3.

Parser Accuracy

Legend: PASS = correctly parses valid SQL, FAIL = rejects valid SQL, FP = accepts invalid SQL

Testsqlite3syntaqlitelemon-rssql-parser-cstsqlglot[c]sqlfluffsqlparser-rsnode-sql-parser
T01: Multi ON CONFLICT UPSERT + RETURNOKPASSPASSPASSFAILFAILFAILFAIL
T02: Recursive CTE + MATERIALIZED / NOOKPASSPASSPASSPASSFAILFAILFAIL
T03: CREATE TABLE STRICT + WITHOUT ROWOKPASSPASSPASSFAILPASSFAILFAIL
T04: UPDATE FROM + INDEXED BYOKPASSPASSPASSPASSFAILFAILFAIL
T05: CREATE TRIGGER + RAISE + WHEN + FOKPASSPASSPASSPASSPASSFAILPASS
T06: FILTER clause + IIF + NULLS LASTOKPASSPASSPASSPASSPASSPASSFAIL
T07: ATTACH DATABASEOKPASSPASSPASSPASSFAILPASSPASS
T08: INSERT OR REPLACEOKPASSPASSPASSPASSPASSPASSPASS
T09: CREATE VIRTUAL TABLE (FTS5)OKPASSPASSPASSPASSFAILFAILFAIL
T10: PRAGMAOKPASSPASSPASSPASSPASSPASSFAIL
T11: EXPLAIN QUERY PLANOKPASSPASSPASSPASSFAILPASSFAIL
T12: ALTER TABLE DROP COLUMNOKPASSPASSPASSPASSPASSPASSPASS
T13: ALTER TABLE RENAME COLUMNOKPASSPASSPASSPASSPASSPASSPASS
T14: REINDEXOKPASSPASSPASSPASSFAILFAILFAIL
T15: Window frame RANGE BETWEENOKPASSPASSPASSPASSPASSPASSFAIL
T16: CREATE INDEX with WHERE (partialOKPASSPASSPASSPASSPASSPASSPASS
T17: REPLACE statementOKPASSPASSPASSPASSPASSPASSPASS
T18: Nested window functions + EXCLUDEOKPASSPASSPASSPASSPASSFAILFAIL
T19: GLOB and LIKE with ESCAPEOKPASSPASSPASSPASSPASSFAILFAIL
T20: INSERT with multiple VALUES + ONOKPASSPASSPASSPASSPASSPASSFAIL
T21: Complex subquery expressionsOKPASSPASSPASSPASSPASSPASSPASS
T22: ANALYZEOKPASSPASSPASSPASSFAILFAILFAIL
T23: SAVEPOINT / RELEASE / ROLLBACK TOOKPASSPASSPASSPASSFAILPASSFAIL
T24: DROP TABLE IF EXISTSOKPASSPASSPASSPASSPASSPASSPASS
T25: CREATE TABLE AS SELECTOKPASSPASSPASSPASSPASSPASSPASS
T26: DETACH DATABASEOKPASSPASSPASSPASSFAILFAILFAIL
T27: UPSERT with complex expressions iOKPASSPASSPASSPASSPASSPASSFAIL
T28: WITH (non-recursive) + DELETE ...OKPASSPASSPASSPASSPASSFAILFAIL
T29: UPDATE ... RETURNINGOKPASSPASSPASSPASSPASSPASSPASS
T30: RIGHT JOIN + IS DISTINCT FROMOKPASSPASSPASSPASSPASSPASSFAIL
T31: FULL OUTER JOINOKPASSPASSPASSPASSPASSPASSFAIL
T32: JSON -> and ->> operatorsOKPASSPASSPASSPASSPASSPASSPASS
T33: Numeric literals with underscoresOKPASSPASSFAILFAILFAILFAILFAIL
T34: Multiple WINDOW definitions + nthOKPASSPASSPASSPASSPASSPASSFAIL
T35: HAVING without GROUP BY (3.39+)OKPASSPASSPASSPASSPASSPASSPASS
T36: IS NOT DISTINCT FROM in complex eOKPASSPASSPASSPASSPASSPASSFAIL
T37: Blob literals + CAST chainsOKPASSPASSPASSPASSPASSPASSPASS
T38: GENERATED ALWAYS AS (VIRTUAL vs SOKPASSPASSPASSFAILPASSPASSPASS
T39: Deeply nested CTE + compound SELEOKPASSPASSPASSPASSPASSPASSFAIL
T40: Window GROUPS frame + EXCLUDE TIEOKPASSPASSPASSFAILPASSFAILFAIL

Scoreboard

ToolCorrectRejects ValidAccepts Invalid
syntaqlite40/40 (100%) ████████████████████--
lemon-rs40/40 (100%) ████████████████████--
sql-parser-cst39/40 (97%) ███████████████████1-
sqlglot[c]35/40 (87%) █████████████████5-
sqlfluff29/40 (72%) ██████████████11-
sqlparser-rs26/40 (65%) █████████████14-
node-sql-parser15/40 (37%) ███████25-

Parse Speed

  • bench.sql: 117 lines, 3,545 bytes
  • bench_30x.sql: 3510 lines, 106,350 bytes

bench.sql (1x)

CommandMean [ms]Min [ms]Max [ms]Relative
syntaqlite1.8 ± 0.31.65.91.13 ± 0.28
lemon-rs1.6 ± 0.31.45.81.00
sql-parser-cst77.5 ± 4.074.392.649.97 ± 10.39
sqlglot[c]87.3 ± 6.782.8113.456.30 ± 12.13
sqlparser-rs1.8 ± 0.11.72.81.17 ± 0.24
node-sql-parser74.4 ± 2.371.783.547.95 ± 9.77
sqlfluff710.5 ± 13.6699.5745.8458.24 ± 92.72

bench_30x.sql (30x)

CommandMean [ms]Min [ms]Max [ms]Relative
syntaqlite2.6 ± 0.32.49.11.00
lemon-rs4.3 ± 0.24.15.41.63 ± 0.21
sql-parser-cst148.9 ± 11.1140.2181.256.66 ± 8.10
sqlglot[c]183.9 ± 3.5180.3194.769.99 ± 8.64
sqlparser-rs10.7 ± 0.410.213.24.08 ± 0.52
node-sql-parser151.1 ± 4.8147.2167.757.51 ± 7.26
sqlfluff504.2 ± 3.1501.0508.3191.89 ± 23.45

Formatter Comparison

Round-trip correctness (format then validate with sqlite3) and speed.

Ground Truth

Statementsqlite3
T01: Multi ON CONFLICT UPSERT + RETURNINGOK
T02: Recursive CTE + MATERIALIZED / NOT MATERIALIZOK
T03: CREATE TABLE STRICT + WITHOUT ROWID + generatOK
T04: UPDATE FROM + INDEXED BYOK
T05: CREATE TRIGGER + RAISE + WHEN + FOR EACH ROWOK
T06: FILTER clause + IIF + NULLS LASTOK
T07: ATTACH DATABASEOK
T08: INSERT OR REPLACEOK
T09: CREATE VIRTUAL TABLE (FTS5)OK
T10: PRAGMAOK
T11: EXPLAIN QUERY PLANOK
T12: ALTER TABLE DROP COLUMNOK
T13: ALTER TABLE RENAME COLUMNOK
T14: REINDEXOK
T15: Window frame RANGE BETWEENOK
T16: CREATE INDEX with WHERE (partial index)OK
T17: REPLACE statementOK
T18: Nested window functions + EXCLUDEOK
T19: GLOB and LIKE with ESCAPEOK
T20: INSERT with multiple VALUES + ON CONFLICT DOOK
T21: Complex subquery expressionsOK
T22: ANALYZEOK
T23: SAVEPOINT / RELEASE / ROLLBACK TOOK
T24: DROP TABLE IF EXISTSOK
T25: CREATE TABLE AS SELECTOK
T26: DETACH DATABASEOK
T27: UPSERT with complex expressions in DO UPDATEOK
T28: WITH (non-recursive) + DELETE ... RETURNINGOK
T29: UPDATE ... RETURNINGOK
T30: RIGHT JOIN + IS DISTINCT FROMOK
T31: FULL OUTER JOINOK
T32: JSON -> and ->> operatorsOK
T33: Numeric literals with underscoresOK
T34: Multiple WINDOW definitions + nth_value + ntiOK
T35: HAVING without GROUP BY (3.39+)OK
T36: IS NOT DISTINCT FROM in complex expressionOK
T37: Blob literals + CAST chainsOK
T38: GENERATED ALWAYS AS (VIRTUAL vs STORED) + comOK
T39: Deeply nested CTE + compound SELECT (UNION /OK
T40: Window GROUPS frame + EXCLUDE TIESOK

40/40 statements validated by sqlite3.

Round-Trip Validation

For each formatter: does the formatted output still pass real SQLite?

Testsyntaqliteprettier-cstsql-formattersqlglot[c]sleeksqruff
T01: Multi ON CONFLICT UPSERT + RETURNOKOKOKFAILOKOK
T02: Recursive CTE + MATERIALIZED / NOOKOKOKCORRUPTOKOK
T03: CREATE TABLE STRICT + WITHOUT ROWOKOKOKFAILOKOK
T04: UPDATE FROM + INDEXED BYOKOKOKOKOKOK
T05: CREATE TRIGGER + RAISE + WHEN + FOKOKOKCORRUPTOKOK
T06: FILTER clause + IIF + NULLS LASTOKOKOKOKOKOK
T07: ATTACH DATABASEOKOKOKOKOKOK
T08: INSERT OR REPLACEOKOKOKOKOKOK
T09: CREATE VIRTUAL TABLE (FTS5)OKOKOKOKOKOK
T10: PRAGMAOKOKOKOKOKOK
T11: EXPLAIN QUERY PLANOKOKOKOKOKOK
T12: ALTER TABLE DROP COLUMNOKOKOKOKOKOK
T13: ALTER TABLE RENAME COLUMNOKOKOKOKOKOK
T14: REINDEXOKOKOKCORRUPTOKOK
T15: Window frame RANGE BETWEENOKOKOKOKOKOK
T16: CREATE INDEX with WHERE (partialOKOKOKOKOKOK
T17: REPLACE statementOKOKOKOKOKOK
T18: Nested window functions + EXCLUDEOKOKOKOKOKOK
T19: GLOB and LIKE with ESCAPEOKOKOKOKOKOK
T20: INSERT with multiple VALUES + ONOKOKOKOKOKOK
T21: Complex subquery expressionsOKOKOKOKOKOK
T22: ANALYZEOKOKOKOKOKOK
T23: SAVEPOINT / RELEASE / ROLLBACK TOOKOKOKCORRUPTOKOK
T24: DROP TABLE IF EXISTSOKOKOKOKOKOK
T25: CREATE TABLE AS SELECTOKOKOKOKOKOK
T26: DETACH DATABASEOKOKOKOKOKOK
T27: UPSERT with complex expressions iOKOKOKOKOKOK
T28: WITH (non-recursive) + DELETE ...OKOKOKOKOKOK
T29: UPDATE ... RETURNINGOKOKOKOKOKOK
T30: RIGHT JOIN + IS DISTINCT FROMOKOKOKOKOKOK
T31: FULL OUTER JOINOKOKOKOKOKOK
T32: JSON -> and ->> operatorsOKOKOKOKOKCORRUPT
T33: Numeric literals with underscoresOKFAILFAILFAILCORRUPTOK
T34: Multiple WINDOW definitions + nthOKOKOKOKOKOK
T35: HAVING without GROUP BY (3.39+)OKOKOKOKOKOK
T36: IS NOT DISTINCT FROM in complex eOKOKOKOKOKOK
T37: Blob literals + CAST chainsOKOKOKOKCORRUPTOK
T38: GENERATED ALWAYS AS (VIRTUAL vs SOKOKOKFAILOKCORRUPT
T39: Deeply nested CTE + compound SELEOKOKOKOKOKOK
T40: Window GROUPS frame + EXCLUDE TIEOKOKOKFAILOKOK

Scoreboard

ToolFormatsSQLite OKCorrupt
syntaqlite40/4040/400
prettier-cst39/4039/400
sql-formatter39/4039/400
sqlglot[c]35/4031/404
sleek40/4038/402
sqruff40/4038/402

Corruption Details

ToolTestError
sqlglot[c]T02: Recursive CTE + MATERIALIZED / NOT MATERIALIZEDError: in prepare, no such column: x
LIZED ( VALUES
sqlglot[c]T05: CREATE TRIGGER + RAISE + WHEN + FOR EACH ROWError: in prepare, near "SELECT": syntax error
yees', OLD.
sqlglot[c]T14: REINDEXError: in prepare, near "AS": syntax error
REINDEX AS idx_
sqlglot[c]T23: SAVEPOINT / RELEASE / ROLLBACK TOError: in prepare, near "AS": syntax error
SAVEPOINT AS my
sqruffT32: JSON -> and ->> operatorsError: in prepare, near ">": syntax error
EXPLAIN SELECT
sleekT33: Numeric literals with underscoresError: in prepare, near "AS": syntax error
EXPLAIN SELECT
sleekT37: Blob literals + CAST chainsError: in prepare, near "AS": syntax error
EXPLAIN SELECT
sqruffT38: GENERATED ALWAYS AS (VIRTUAL vs STORED) + complex expressionsError: in prepare, near "
abel TEXT GENERA

Format Speed

  • bench.sql: 117 lines, 3,545 bytes
  • bench_30x.sql: 3510 lines, 106,350 bytes

bench.sql (1x)

CommandMean [ms]Min [ms]Max [ms]Relative
syntaqlite1.8 ± 0.11.74.91.00
prettier-cst380.0 ± 4.2374.0385.8205.57 ± 15.57
sql-formatter80.2 ± 8.274.4111.943.40 ± 5.49
sqlglot[c]87.9 ± 1.285.591.847.52 ± 3.62
sleek8.6 ± 1.17.923.34.67 ± 0.68
sqruff40.3 ± 1.038.944.421.81 ± 1.71

bench_30x.sql (30x)

CommandMean [ms]Min [ms]Max [ms]Relative
syntaqlite4.9 ± 0.24.76.41.00
prettier-cst536.4 ± 3.3531.3539.5109.56 ± 3.70
sql-formatter199.0 ± 1.5196.8201.540.65 ± 1.38
sqlglot[c]269.5 ± 3.6265.2277.655.04 ± 1.97
sleek27.7 ± 0.726.632.25.65 ± 0.23
sqruff3690.8 ± 458.03342.04492.7753.88 ± 96.83

Slow Tools (single timed run)

ToolTime
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.

TestExpectsyntaqlitesqlite3sqlite-runner-lspsql-lint
keyword typo (SELEC)errorFOUNDFOUNDFOUNDFOUND
missing close parenerrorFOUNDFOUNDFOUNDFOUND
double commaerrorFOUNDFOUNDFOUNDMISS
unterminated stringerrorFOUNDFOUNDFOUNDMISS
trailing comma in VALUESerrorFOUNDFOUNDFOUNDFOUND
unknown tableerrorFOUNDFOUNDFOUNDMISS
unknown table in JOINerrorFOUNDFOUNDFOUNDMISS
unknown columnerrorFOUNDFOUNDFOUNDMISS
unknown qualified columnerrorFOUNDFOUNDFOUNDMISS
unknown column in SELECTerrorFOUNDFOUNDFOUNDMISS
SUBSTR: too few argserrorFOUNDFOUNDFOUNDMISS
REPLACE: too few argserrorFOUNDFOUNDFOUNDMISS
LENGTH: too many argserrorFOUNDFOUNDFOUNDMISS
COALESCE: zero argserrorMISSFOUNDFOUNDMISS
CTE: 3 declared, 2 actualerrorFOUNDFOUNDFOUNDFOUND
valid: simple SELECTvalidOKOKOKOK
valid: JOIN + aggregatevalidOKOKOKOK
valid: SUBSTR with 3 argsvalidOKOKOKOK
valid: COALESCE variadicvalidOKOKOKOK
valid: CTE columns matchvalidOKOKOKFP
valid: built-in functionsvalidOKOKOKOK
valid: INSERTvalidOKOKOKOK
valid: UPDATEvalidOKOKOKOK
valid: DELETE with WHEREvalidOKOKOKOK

Scoreboard

ToolApproachCorrectMissedFP
sqlite3runtime execution24/24 ████████████████████--
sqlite-runner-lspruntime via LSP24/24 ████████████████████--
syntaqlitestatic semantic23/24 ███████████████████1-
sql-lintstructural checks12/24 ██████████111

Validation Speed

  • bench.sql: 117 lines, 3,545 bytes (+ schema preamble)
  • bench_30x.sql: 3510 lines, 106,350 bytes (+ schema preamble)

bench.sql (1x)

CommandMean [ms]Min [ms]Max [ms]Relative
syntaqlite5.7 ± 1.72.812.61.00
sqlite38.9 ± 4.94.924.11.55 ± 0.97
sqlite-runner-lsp93.6 ± 1.990.999.816.29 ± 4.74
sql-lint345.9 ± 6.0336.5357.360.20 ± 17.50

bench_30x.sql (30x)

CommandMean [ms]Min [ms]Max [ms]Relative
syntaqlite7.3 ± 0.17.17.71.00
sqlite310.1 ± 0.49.511.31.39 ± 0.05
sqlite-runner-lsp113.3 ± 1.2111.2115.315.52 ± 0.31
sql-lint365.4 ± 3.3361.5370.850.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.

Featuresyntaqlitesqlssql-language-server
CompletionYes (150 items)Yes (1 items)Advertised (0 items)
HoverNoYesNo
Go to definitionNoYesNo
Find referencesNoNoNo
Diagnostics: syntaxYesNoYes
Diagnostics: semanticYesNoNo (style only)
FormattingYesYesNo
RenameNoYesYes
Signature helpNoYesNo
Requires DB connectionNoYesNo

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:

CommandMean [ms]Min [ms]Max [ms]Relative
syntaqlite35.0 ± 3.331.749.31.00
sqls10069.1 ± 3.010064.310071.4287.71 ± 27.07
sql-language-server449.8 ± 20.5414.1472.812.85 ± 1.34