Comparison: detailed results

Per-statement breakdowns, reproduction instructions, and methodology for the competitive comparison.

Generated on arm64-darwin with syntaqlite 0.1.0 on 2026-03-14.

To reproduce these results: tools/run-comparison --setup && tools/run-comparison --all. See the script for requirements and tool versions.


Parser details

Ground truth

Every test statement validated against sqlite3 (via EXPLAIN):

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.

Per-statement results

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-

Speed details

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

bench.sql (1×)

CommandMean [ms]Min [ms]Max [ms]Relative
syntaqlite1.7 ± 0.31.66.71.12 ± 0.46
lemon-rs1.5 ± 0.61.320.31.00
sql-parser-cst78.9 ± 7.474.5115.251.81 ± 20.03
sqlglot[c]86.2 ± 3.282.193.456.61 ± 21.33
sqlparser-rs1.8 ± 0.21.63.81.18 ± 0.46
node-sql-parser73.3 ± 1.171.376.048.14 ± 18.07
sqlfluff445.7 ± 5.8437.6457.5292.56 ± 109.79

bench_30x.sql (30×)

CommandMean [ms]Min [ms]Max [ms]Relative
syntaqlite2.5 ± 0.22.45.31.00
lemon-rs4.1 ± 0.14.05.41.62 ± 0.12
sql-parser-cst141.8 ± 2.4139.5151.455.84 ± 4.05
sqlglot[c]182.2 ± 3.9179.4195.271.76 ± 5.28
sqlparser-rs11.0 ± 0.610.315.14.32 ± 0.39
node-sql-parser149.7 ± 1.7147.3155.258.94 ± 4.20
sqlfluff6382.9 ± 40.86333.26426.12513.49 ± 177.72

Formatter details

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 produce identical EXPLAIN bytecode to the original? This verifies semantic preservation, not just syntactic validity. "CORRUPT" means the bytecode differs or EXPLAIN fails.

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 LASTOKOKOKOKOKFAIL
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 BETWEENOKOKOKOKOKFAIL
T16: CREATE INDEX with WHERE (partialOKOKOKOKOKOK
T17: REPLACE statementOKOKOKOKOKOK
T18: Nested window functions + EXCLUDEOKOKOKOKOKFAIL
T19: GLOB and LIKE with ESCAPEOKOKOKOKCORRUPTOK
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 + nthOKOKOKOKOKFAIL
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 SELEOKOKOKOKOKFAIL
T40: Window GROUPS frame + EXCLUDE TIEOKOKOKFAILOKOK

Scoreboard

ToolCorrectCorruptRefused
syntaqlite40/40 (100%) ████████████████████--
prettier-cst39/40 (97%) ███████████████████-1
sql-formatter39/40 (97%) ███████████████████-1
sleek37/40 (92%) ██████████████████3-
sqruff33/40 (82%) ████████████████25
sqlglot[c]31/40 (77%) ███████████████45

Corruption details

ToolTestError
sqlglot[c]T02: Recursive CTE + MATERIALIZED / NOT MATERIALIZEDEXPLAIN failed on formatted SQL
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_
sleekT19: GLOB and LIKE with ESCAPEEXPLAIN bytecode differs from original
sqlglot[c]T23: SAVEPOINT / RELEASE / ROLLBACK TOError: in prepare, near "AS": syntax error
SAVEPOINT AS my
sqruffT32: JSON -> and ->> operatorsEXPLAIN failed on formatted SQL
sleekT33: Numeric literals with underscoresEXPLAIN failed on formatted SQL
sleekT37: Blob literals + CAST chainsEXPLAIN failed on formatted SQL
sqruffT38: GENERATED ALWAYS AS (VIRTUAL vs STORED) + complex expressionsError: in prepare, near "
abel TEXT GENERA

Speed details

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

bench.sql (1×)

CommandMean [ms]Min [ms]Max [ms]Relative
syntaqlite1.8 ± 0.11.72.21.00
prettier-cst404.8 ± 5.0397.0412.1228.45 ± 7.74
sql-formatter75.3 ± 1.373.179.542.50 ± 1.53
sqlglot[c]87.4 ± 1.285.792.649.32 ± 1.70
sleek8.4 ± 0.37.99.94.76 ± 0.22
sqruff39.7 ± 0.638.542.022.41 ± 0.80

bench_30x.sql (30×)

CommandMean [ms]Min [ms]Max [ms]Relative
syntaqlite4.9 ± 0.14.85.71.00
prettier-cst558.1 ± 4.2553.9564.7113.23 ± 2.82
sql-formatter199.4 ± 2.8195.7204.640.45 ± 1.11
sqlglot[c]264.5 ± 2.1261.3269.753.66 ± 1.34
sleek27.2 ± 0.626.431.75.52 ± 0.18
sqruff3347.0 ± 62.43262.93432.5679.02 ± 20.51

Slow tools (single timed run)

ToolTime
sqlfmt (1x)517ms
sqlfmt (30x)309ms
sqlfluff (1x)185ms

Validator details

Diagnostic quality showcase

A realistic query with 2 subtle errors. How does each tool report them?

Query (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/tmpico33e5u.sql:30:3
   |
30 |   monthly_stats(month, revenue, order_count) AS (
   |   ^~~~~~~~~~~~~
warning: unknown function 'ROUDN'
  --> /var/folders/rx/t6_rqmqx0f15l7kgp7yjhcbc0000gn/T/tmpico33e5u.sql:42:3
   |
42 |   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:

(no diagnostics)

sql-lint

Structural checks only:

/var/folders/rx/t6_rqmqx0f15l7kgp7yjhcbc0000gn/T/tmpxbgeg7p3.sql:1 sql-lint was unable to lint the following query "WITH...

Per-case error detection

Schema: users, orders, products, order_items. Ground truth: sqlite3.

TestExpectsyntaqlitesqlite3sqlite-runner-lspsql-lint
keyword typo (SELEC)errorFOUNDFOUNDMISSFOUND
missing close parenerrorFOUNDFOUNDMISSFOUND
double commaerrorFOUNDFOUNDMISSMISS
unterminated stringerrorFOUNDFOUNDMISSMISS
trailing comma in VALUESerrorFOUNDFOUNDMISSFOUND
unknown tableerrorFOUNDFOUNDMISSMISS
unknown table in JOINerrorFOUNDFOUNDMISSMISS
unknown columnerrorFOUNDFOUNDMISSMISS
unknown qualified columnerrorFOUNDFOUNDMISSMISS
unknown column in SELECTerrorFOUNDFOUNDMISSMISS
SUBSTR: too few argserrorFOUNDFOUNDMISSMISS
REPLACE: too few argserrorFOUNDFOUNDMISSMISS
LENGTH: too many argserrorFOUNDFOUNDMISSMISS
COALESCE: zero argserrorMISSFOUNDMISSMISS
CTE: 3 declared, 2 actualerrorFOUNDFOUNDMISSFOUND
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 ████████████████████--
syntaqlitestatic semantic23/24 ███████████████████1-
sql-lintstructural checks12/24 ██████████111
sqlite-runner-lspruntime via LSP9/24 ███████15-

Speed details

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

bench.sql (1×)

CommandMean [ms]Min [ms]Max [ms]Relative
syntaqlite2.0 ± 0.11.93.31.00
sqlite34.7 ± 0.24.46.12.34 ± 0.17
sqlite-runner-lsp10069.1 ± 7.510054.810075.04972.95 ± 270.92
sql-lint356.5 ± 5.9348.7364.1176.07 ± 10.03

bench_30x.sql (30×)

CommandMean [ms]Min [ms]Max [ms]Relative
syntaqlite6.1 ± 0.25.88.21.00
sqlite319.8 ± 8.19.768.53.24 ± 1.34
sqlite-runner-lsp10072.1 ± 4.210066.910078.61650.58 ± 62.91
sql-lint378.1 ± 3.0374.3382.161.97 ± 2.41

LSP details

Tested capabilities

Each server is started, sent a test file, and probed for completion, hover, diagnostics, and formatting via the LSP protocol.

Featuresyntaqlitesqls ¹sql-language-server
CompletionYes (129 items)Yes (6 items)Yes (11 items)
HoverNoYesNo
Go to definitionYesYesNo
Find referencesYesNoNo
Diagnostics: syntaxYesNoYes
Diagnostics: semanticYesNoNo (style only)
FormattingYesYesNo
RenameYesYesYes
Signature helpYesYesNo
Requires DB connectionNoYesNo

¹ sqls requires a live database connection. Completion and hover results come from the connected database schema, not static analysis. Without a database, these features return no results.

Completion depth

ToolItems
syntaqlite129 ████████████████████
sql-language-server11 █
sqls ¹6 █

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.

Speed details

Time to start server, send document, receive diagnostics, and exit:

CommandMean [ms]Min [ms]Max [ms]Relative
syntaqlite32.3 ± 0.930.334.71.00
sqls10065.8 ± 2.810063.010069.3311.28 ± 8.73
sql-language-server470.5 ± 5.1464.1478.614.55 ± 0.44