<?xml version="1.0" encoding="utf-8" standalone="yes"?><?xml-stylesheet href="/feed.css?v=208f42b55cff" type="text/css"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:site="https://lalitm.com/feed/ns#"><channel><title>Lalit Maganti (Tag: Databases)</title><link>https://lalitm.com/tags/databases/</link><description>Recent content tagged Databases on Lalit Maganti</description><site:notice>This is a feed.
Feeds let you subscribe to updates from this site using a feed reader. Copy this page's URL from your address bar and paste it into your reader.
New to feeds? Read: https://aboutfeeds.com</site:notice><docs>https://aboutfeeds.com</docs><generator>Hugo -- gohugo.io</generator><language>en</language><lastBuildDate>Wed, 17 Jun 2026 21:53:00 +0100</lastBuildDate><atom:link href="https://lalitm.com/tags/databases/index.xml" rel="self" type="application/rss+xml"/><item><title>syntaqlite 0.6: SQLite dot commands and pyodide</title><link>https://lalitm.com/post/syntaqlite-06/</link><pubDate>Wed, 17 Jun 2026 21:53:00 +0100</pubDate><guid>https://lalitm.com/post/syntaqlite-06/</guid><description>Since my original launch post for syntaqlite, I’ve been quietly working away on it in the background. A lot of the work has been fixing correctness bugs which I discovered as I integrated it into production as the parser for PerfettoSQL in the Perfetto trace processor: as I wrote previously, this has been my dream for over 8 years so it’s amazing to see it finally realized.
Just today, I released syntaqlite 0.6, the most interesting release since the original launch, so I wanted to talk about what’s new.
The biggest step forward for real world applicability is that we now support SQLite dot commands:
.print 'running foo now'; .read foo.sql; .print 'running bar now'; .read bar.sql; select * from baz; -- ^ baz will error if it's not part of the schema! SQLite scripts are very common in the wild and in the past we would simply error on dot commands like .read and .print, causing spurious warnings and an inability to format files like this properly. Now, these lines will be silently ignored while still parsing, formatting and validating the SQL inside!</description><content:encoded>&lt;p&gt;Since my &lt;a href="https://lalitm.com/post/syntaqlite/"&gt;original launch post&lt;/a&gt; for syntaqlite,
I&amp;rsquo;ve been quietly working away on it in the background. A lot of the work has
been fixing correctness bugs which I discovered as I integrated it into
production as the parser for PerfettoSQL in the Perfetto trace processor: as I
wrote previously, this has been my dream for over 8 years so it&amp;rsquo;s amazing to see
it finally realized.&lt;/p&gt;
&lt;p&gt;Just today, I released
&lt;a href="https://github.com/LalitMaganti/syntaqlite/releases/tag/v0.6.0"&gt;syntaqlite 0.6&lt;/a&gt;,
the most interesting release since the original launch, so I wanted to talk
about what&amp;rsquo;s new.&lt;/p&gt;
&lt;p&gt;The biggest step forward for real world applicability is that we now support
&lt;em&gt;SQLite dot commands&lt;/em&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sql" data-lang="sql"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;print&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;running foo now&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;read&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;foo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;print&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;running bar now&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;read&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;bar&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;select&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;baz&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;-- ^ baz will error if it&amp;#39;s not part of the schema!
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;SQLite scripts are very common in the wild and in the past we would simply error
on dot commands like &lt;code&gt;.read&lt;/code&gt; and &lt;code&gt;.print&lt;/code&gt;, causing spurious warnings and an
inability to format files like this properly. Now, these lines will be silently
ignored while still parsing, formatting and validating the SQL inside!&lt;/p&gt;
&lt;p&gt;In followup releases, I plan on making syntaqlite more aware of the &lt;em&gt;semantics&lt;/em&gt;
of these dot commands, especially &lt;code&gt;.read&lt;/code&gt;: we should be able to do things like
verify the file exists or to use the contents of those files for populating
schemas etc.&lt;/p&gt;
&lt;p&gt;Along with this, the release of
&lt;a href="https://blog.pyodide.org/posts/314-release/"&gt;Pyodide 314.0&lt;/a&gt; last week means
that we can now publish Python wheels to PyPI for Pyodide: I&amp;rsquo;ve taken advantage
of that to immediately add support for it. So you can now go to the
&lt;a href="https://pyodide.org/en/stable/console.html"&gt;Pyodide playground&lt;/a&gt; and run the
following:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;micropip&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;micropip&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;install&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;syntaqlite&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;syntaqlite&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;sq&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;syntaqlite&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Syntaqlite&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Formatting&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sq&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;format_sql&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;select 1&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;keyword_case&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;upper&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# SELECT 1;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Analysis&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;schema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;syntaqlite&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Schema&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;ddl&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;CREATE TABLE users (id INT, name TEXT, email TEXT, active INT)&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sq&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;analyze&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;SELECT nme FROM users WHERE active = 1&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;syntaqlite&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AnalysisOutput&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TEXT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;render_options&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;syntaqlite&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RenderOptions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;source_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;query.sql&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# warning: unknown column &amp;#39;nme&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# --&amp;gt; query.sql:1:8&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# |&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 1 | SELECT nme FROM users WHERE active = 1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# | ^~~&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# = help: did you mean &amp;#39;name&amp;#39;?&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This is another step on the journey towards a 1.0 release which I can now see
appearing on the horizon!&lt;/p&gt;</content:encoded></item><item><title>syntaqlite: high-fidelity devtools that SQLite deserves</title><link>https://lalitm.com/post/syntaqlite/</link><pubDate>Tue, 17 Mar 2026 05:35:45 +0000</pubDate><guid>https://lalitm.com/post/syntaqlite/</guid><description>Most SQL tools treat SQLite as a “flavor” of a generic SQL parser. They approximate the language, which means they break on SQLite-exclusive features like virtual tables, miss syntax like UPSERT, and ignore the 22 compile-time flags that change the syntax SQLite accepts.
So I built syntaqlite: an open-source parser, formatter, validator, and LSP built directly on SQLite’s own Lemon-generated grammar. It sees SQL exactly how SQLite sees it, no matter which version of SQLite you’re using or which feature flags you compiled with.
It ships as a CLI, VS Code extension, Claude Code LSP plugin, and C/Rust libraries.
There’s also a web playground which you can try now: paste any SQLite SQL and see parsing, formatting, and validation live in the browser, no install needed. Full documentation is available here.
Here’s syntaqlite in action:
Formatting with the CLI
&gt; syntaqlite fmt -e "select u.name,u.email,count(e.id) as events from users u join events e on e.user_id=u.id where u.signed_up_at&gt;=date('now','-30 days') group by u.name,u.email having count(e.id)&gt;10 order by events desc" SELECT u.name, u.email, count(e.id) AS events FROM users AS u JOIN events AS e ON e.user_id = u.id WHERE u.signed_up_at &gt;= date('now', '-30 days') GROUP BY u.name, u.email HAVING count(e.id) &gt; 10 ORDER BY events DESC; Validation with the CLI</description><content:encoded>&lt;p&gt;Most SQL tools treat SQLite as a &amp;ldquo;flavor&amp;rdquo; of a generic SQL parser. They approximate the language, which means they break on SQLite-exclusive features like &lt;a href="https://www.sqlite.org/vtab.html"&gt;virtual tables&lt;/a&gt;, miss syntax like &lt;a href="https://www.sqlite.org/lang_upsert.html"&gt;UPSERT&lt;/a&gt;, and ignore the &lt;a href="https://www.sqlite.org/compile.html"&gt;22 compile-time flags&lt;/a&gt; that change the syntax SQLite accepts.&lt;/p&gt;
&lt;p&gt;So I built &lt;a href="https://github.com/lalitMaganti/syntaqlite"&gt;syntaqlite&lt;/a&gt;: an open-source parser, formatter, validator, and LSP built directly on SQLite&amp;rsquo;s own Lemon-generated grammar. It sees SQL exactly how SQLite sees it, no matter which version of SQLite you&amp;rsquo;re using or which feature flags you compiled with.&lt;/p&gt;
&lt;p&gt;It ships as a &lt;a href="https://docs.syntaqlite.com/main/getting-started/cli"&gt;CLI&lt;/a&gt;, &lt;a href="https://docs.syntaqlite.com/main/getting-started/vscode"&gt;VS Code extension&lt;/a&gt;, &lt;a href="https://docs.syntaqlite.com/main/getting-started/claude-code"&gt;Claude Code LSP plugin&lt;/a&gt;, and &lt;a href="https://docs.syntaqlite.com/main/getting-started/c-parser/"&gt;C&lt;/a&gt;/&lt;a href="https://docs.syntaqlite.com/main/getting-started/rust/"&gt;Rust&lt;/a&gt; libraries.&lt;/p&gt;
&lt;p&gt;There&amp;rsquo;s also a &lt;strong&gt;&lt;a href="https://playground.syntaqlite.com/"&gt;web playground&lt;/a&gt;&lt;/strong&gt; which you can try now: paste any SQLite SQL and see parsing, formatting, and validation live in the browser, no install needed. &lt;a href="https://docs.syntaqlite.com/"&gt;Full documentation is available here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s syntaqlite in action:&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Formatting with the CLI&lt;/em&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&amp;gt; syntaqlite fmt -e &lt;span class="s2"&gt;&amp;#34;select u.name,u.email,count(e.id) as events from users u join events e on e.user_id=u.id where u.signed_up_at&amp;gt;=date(&amp;#39;now&amp;#39;,&amp;#39;-30 days&amp;#39;) group by u.name,u.email having count(e.id)&amp;gt;10 order by events desc&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sql" data-lang="sql"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;events&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;users&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;JOIN&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;events&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;ON&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;WHERE&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;signed_up_at&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;now&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;-30 days&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;GROUP&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;BY&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;HAVING&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;ORDER&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;BY&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;events&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;DESC&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;em&gt;Validation with the CLI&lt;/em&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&amp;gt; syntaqlite --sqlite-version 3.37.0 validate &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -e &lt;span class="s2"&gt;&amp;#34;SELECT json_extract(data, &amp;#39;&lt;/span&gt;$&lt;span class="s2"&gt;.name&amp;#39;) FROM events;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;error: unknown function &amp;#39;json_extract&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --&amp;gt; &amp;lt;expression&amp;gt;:1:8
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; |
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;1 | SELECT json_extract(data, &amp;#39;$.name&amp;#39;) FROM events;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; | ^~~~~~~~~~~~
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;em&gt;LSP support in VSCode&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="https://lalitm.com/img/syntaqlite-vscode.png" alt="VS Code showing syntaqlite catching a misspelled column name"&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;LSP in Claude Code&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="https://lalitm.com/img/syntaqlite-cc.png" alt="Claude Code using syntaqlite to catch and fix column name errors"&gt;&lt;/p&gt;
&lt;h2 id="why-it-exists"&gt;Why it exists&lt;a class="heading-anchor" href="#why-it-exists" aria-label="Permalink to Why it exists"&gt;#&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;I maintain &lt;a href="https://perfetto.dev/docs/analysis/perfetto-sql-syntax"&gt;PerfettoSQL&lt;/a&gt;, a dialect of SQLite SQL used for trace analysis in &lt;a href="https://perfetto.dev/"&gt;Perfetto&lt;/a&gt;. Across Google there are 200K+ lines of the language. At that scale, you hit &lt;em&gt;every&lt;/em&gt; corner of it. I tried every SQLite tool I could find and time after time I was disappointed: false positives on valid syntax, misparsed statements, formatters that silently corrupted the SQL by changing semantics. It always came down to a hand-written or generic grammar approximating SQLite rather than matching it to the letter.&lt;/p&gt;
&lt;p&gt;Moreover, SQLite&amp;rsquo;s SQL is not one fixed language. Apart from flags that change the syntax, there are another 12 flags that gate &lt;a href="https://www.sqlite.org/compile.html"&gt;built-in functions&lt;/a&gt;, as well as APIs for programs to &lt;a href="https://www.sqlite.org/c3ref/create_function.html"&gt;register custom functions&lt;/a&gt; and &lt;a href="https://www.sqlite.org/vtab.html"&gt;virtual tables&lt;/a&gt;. In Perfetto, we make &lt;em&gt;extensive&lt;/em&gt; use of both virtual tables and functions and most tools just don&amp;rsquo;t give us the flexibility to treat these as first-class extensions of the language.&lt;/p&gt;
&lt;p&gt;The SQLite language also constantly evolves: in the last several years, 3.25.0 added window functions, 3.35.0 added &lt;code&gt;RETURNING&lt;/code&gt;, 3.38.0 added built-in JSON functions to the amalgamation. And because SQLite is embedded, you can&amp;rsquo;t assume everyone is on the latest version. This is certainly the case for Perfetto: people link against different versions of SQLite and we need to make sure our standard library is resilient to that.&lt;/p&gt;
&lt;p&gt;Now with all of this said, I could have built all these tools just for PerfettoSQL and called it a day. But the more I thought about it, the more I felt that the difference between &amp;ldquo;good tooling for PerfettoSQL&amp;rdquo; and &amp;ldquo;good tooling for SQLite&amp;rdquo; is so small that it&amp;rsquo;s worth solving for the wider use case. SQLite is ubiquitous, yet it still lacks the quality tooling other languages have had for over a decade.&lt;/p&gt;
&lt;p&gt;Android is one case I think could especially benefit. SQLite is integral to almost every Android app not to mention the platform itself. But the ecosystem is also famously fragmented: if you&amp;rsquo;re an Android app developer, you &lt;strong&gt;need&lt;/strong&gt; to reason about older platform versions, which in turn means older versions of SQLite.&lt;/p&gt;
&lt;p&gt;As an example, Android 13 ships with SQLite 3.32 (&lt;a href="https://developer.android.com/reference/android/database/sqlite/package-summary"&gt;link&lt;/a&gt;). If you use the &lt;code&gt;RETURNING&lt;/code&gt; keyword and your users are on Android 13, it will fail at runtime even though it passes fine on your test device. Wouldn&amp;rsquo;t it be nice to statically verify this on your host instead of discovering it on a real device?&lt;/p&gt;
&lt;p&gt;No tool I found handled all of this: the precise SQLite grammar, version matching, compile flags, dialect extensions. And that&amp;rsquo;s exactly why syntaqlite exists.&lt;/p&gt;
&lt;h2 id="highlights"&gt;Highlights&lt;a class="heading-anchor" href="#highlights" aria-label="Permalink to Highlights"&gt;#&lt;/a&gt;
&lt;/h2&gt;
&lt;h3 id="validate-semantics-with-great-error-messages"&gt;Validate semantics with great error messages&lt;a class="heading-anchor" href="#validate-semantics-with-great-error-messages" aria-label="Permalink to Validate semantics with great error messages"&gt;#&lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;Parsing alone is table stakes. But I also wanted to catch common mistakes like reaching for &lt;code&gt;user_name&lt;/code&gt; when the column is actually &lt;code&gt;username&lt;/code&gt;. For this reason, syntaqlite also resolves table, column, and function references, catching errors like misspelled column names or references to tables that don&amp;rsquo;t exist.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve also tried to make the error messages &lt;em&gt;really&lt;/em&gt; good: at the quality level you would expect to see from modern tools in other languages like rustc or tsc. This means both accurate, line/column level indication with snippets of the error but also &amp;ldquo;did you mean&amp;rdquo; hints, pointing you in the right direction. The error messages should be &lt;em&gt;significantly&lt;/em&gt; better than SQLite&amp;rsquo;s and it&amp;rsquo;s something I plan on making even better with time.&lt;/p&gt;
&lt;p&gt;As for correctness, I&amp;rsquo;ve tested syntaqlite against ~396K statements from &lt;a href="https://sqlite.org/testing.html"&gt;SQLite&amp;rsquo;s upstream test suite&lt;/a&gt; with ~99.7% agreement on parse acceptance &lt;sup id="fnref:1"&gt;&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref"&gt;1&lt;/a&gt;&lt;/sup&gt;. Before we get to 1.0, I fully expect this to reach 100%.&lt;/p&gt;
&lt;p&gt;There&amp;rsquo;s also a &lt;a href="https://docs.syntaqlite.com/main/reference/comparison/"&gt;detailed comparison&lt;/a&gt; against other tools covering parser accuracy, formatter correctness, validator quality, and performance. Of course the benchmarks and stress tests were written by me, so take it with a grain of salt. But hopefully the results speak for themselves.&lt;/p&gt;
&lt;h3 id="lsp-for-editors-and-coding-agents"&gt;LSP for editors and coding agents&lt;a class="heading-anchor" href="#lsp-for-editors-and-coding-agents" aria-label="Permalink to LSP for editors and coding agents"&gt;#&lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;syntaqlite ships a full language server: completions, hover, go-to-definition, find references, rename, diagnostics, and formatting. Unlike most languages, SQL queries depend on an external schema that isn&amp;rsquo;t in the source file. The LSP needs to know about that schema to give you useful diagnostics.&lt;/p&gt;
&lt;p&gt;The most common way to provide this is with a &lt;code&gt;syntaqlite.toml&lt;/code&gt; at the root of your project &lt;sup id="fnref:2"&gt;&lt;a href="#fn:2" class="footnote-ref" role="doc-noteref"&gt;2&lt;/a&gt;&lt;/sup&gt;. Point it at your &lt;code&gt;CREATE TABLE&lt;/code&gt; definitions (which you can dump using &lt;code&gt;.schema&lt;/code&gt; on your SQLite database; see the &lt;a href="https://docs.syntaqlite.com/main/guides/project-setup/"&gt;project setup guide&lt;/a&gt; for details) and syntaqlite uses those for autocompletion, diagnostics, and find references.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-toml" data-lang="toml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;schemas&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;&amp;#34;**/*.sql&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;db/schema.sql&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This tells syntaqlite: for every &lt;code&gt;.sql&lt;/code&gt; file in the project, validate against the definitions in &lt;code&gt;db/schema.sql&lt;/code&gt;. You can point at multiple schema files, and scope different schemas to different directories.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;syntaqlite.toml&lt;/code&gt; can be used to configure other preferences like indentation for formatting and the SQLite version as well. The idea is that you check this into your repo and the whole team gets the same experience.&lt;/p&gt;
&lt;h3 id="it-validates-sql-inside-other-languages-experimental"&gt;It validates SQL inside other languages (experimental)&lt;a class="heading-anchor" href="#it-validates-sql-inside-other-languages-experimental" aria-label="Permalink to It validates SQL inside other languages (experimental)"&gt;#&lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;SQL is unusual in that it often lives &lt;em&gt;inside&lt;/em&gt; other languages: string literals in Python, template literals in TypeScript, raw strings in C++, and so on.&lt;/p&gt;
&lt;p&gt;As an experimental feature (and with caveats!), syntaqlite can extract and validate SQL &lt;em&gt;inside&lt;/em&gt; other languages, even if you have string interpolation. For example, in a Python f-string:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# app.py&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_user_stats&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;SELECT name, ROUDN(score, 2) FROM users WHERE id = &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;syntaqlite validate --experimental-lang python app.py
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;warning: unknown function &amp;#39;ROUDN&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --&amp;gt; app.py:4:23
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; |
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;4 | f&amp;#34;SELECT name, ROUDN(score, 2) FROM users WHERE id = {user_id}&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; | ^~~~~
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; = help: did you mean &amp;#39;round&amp;#39;?
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;It handles the &lt;code&gt;{user_id}&lt;/code&gt; interpolation hole and still catches the function typo with a did-you-mean suggestion.&lt;/p&gt;
&lt;h3 id="and-more"&gt;And more&lt;a class="heading-anchor" href="#and-more" aria-label="Permalink to And more"&gt;#&lt;/a&gt;
&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;SQLite version- and flag-aware.&lt;/strong&gt; Tell syntaqlite which SQLite version and compile-time flags your target has; the formatter, validator, and LSP all respect them.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Custom dialect support.&lt;/strong&gt; Define grammar, formatting, and validation rules, compile to a shared library, load at runtime. I built this for PerfettoSQL, but it works for any engine that extends SQLite&amp;rsquo;s syntax. For example, a mobile analytics engine with custom aggregation functions or a local-first app framework with its own replication primitives.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Fast.&lt;/strong&gt; The formatter processes 3,500 lines of SQL in ~5ms. Full benchmarks on the &lt;a href="https://docs.syntaqlite.com/main/reference/comparison/"&gt;comparison page&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="how-it-works"&gt;How it works&lt;a class="heading-anchor" href="#how-it-works" aria-label="Permalink to How it works"&gt;#&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;I plan on writing a detailed followup on the technical side, but I did want to give a short summary here.&lt;/p&gt;
&lt;p&gt;Fundamentally syntaqlite is a C/Rust/C sandwich:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;+-------------+---------------+--------------+-------+--------------------+
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;| VS Code | Claude Code | Playground | CLI | C bindings |
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;| Extension | / Agents | (WASM) | | (Go, Kotlin...) |
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;+-------------+---------------+--------------+-------+--------------------+
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;| Language Server (LSP) |
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;+-------------------------------------------------------------------------+
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;| Formatter · Validator · Analyzer (Rust) |
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;+-------------------------------------------------------------------------+
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;| Parser · Tokenizer · AST Arena (C, from SQLite) |
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;+-------------------------------------------------------------------------+
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The bottom layer is C: SQLite&amp;rsquo;s Lemon-generated grammar and tokenizer, plus an arena storing a &lt;a href="https://jhwlr.io/super-flat-ast/"&gt;super flat AST&lt;/a&gt;. This layer has no Rust dependency and can be used entirely on its own, which matters if you&amp;rsquo;re building a database engine or other tool in C/C++ that just needs to parse SQLite SQL &lt;sup id="fnref:3"&gt;&lt;a href="#fn:3" class="footnote-ref" role="doc-noteref"&gt;3&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;p&gt;The middle layer is Rust. It reads the C arena directly with no copying or deserialization. The formatter generates a &lt;a href="https://lindig.github.io/papers/strictly-pretty-2000.pdf"&gt;Wadler-Lindig document&lt;/a&gt; using an AST-walking interpreter; the semantic analyzer walks the same AST, resolving references and producing diagnostics with byte-accurate source locations.&lt;/p&gt;
&lt;p&gt;The top layer is C again, completing the sandwich: the Rust tools are exported back through FFI so C/C++ projects can link syntaqlite as a plain C library. This also makes bindings for other languages possible: Go, Kotlin/Java, and Python are planned before 1.0. Of course, if you&amp;rsquo;re using Rust directly you can just use the Rust library, skipping the top C layer.&lt;/p&gt;
&lt;p&gt;The obvious question I&amp;rsquo;m expecting is &amp;ldquo;why not just write everything in Rust&amp;rdquo;? I wanted the parser to work &lt;em&gt;exactly&lt;/em&gt; as SQLite does, which meant &lt;em&gt;literally&lt;/em&gt; extracting SQLite&amp;rsquo;s own code and adapting it to my needs. And writing it in C with the same constraints as SQLite itself (no dependencies beyond the C standard library) means it can be linked into practically anything: database engines, C++ projects like Perfetto&amp;rsquo;s trace processor, or any environment where SQLite runs.&lt;/p&gt;
&lt;p&gt;On the other hand, I really did not want to write a formatter, semantic analyzer, and LSP in C. While I do like the &amp;ldquo;simplicity&amp;rdquo; of C, writing a full JSON-RPC server and protocol sounds like a nightmare.&lt;/p&gt;
&lt;h2 id="where-this-is-going"&gt;Where this is going&lt;a class="heading-anchor" href="#where-this-is-going" aria-label="Permalink to Where this is going"&gt;#&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;In my head, there&amp;rsquo;s a clear roadmap for the project side:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;100% upstream parity.&lt;/strong&gt; The ~0.3% gap against SQLite&amp;rsquo;s test suite is mostly from not fully tracking built-in tables and virtual tables. It just needs another round of &amp;ldquo;source code introspection&amp;rdquo; across all the SQLite versions and integrating those tables into syntaqlite&amp;rsquo;s builtin catalog.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;LSP enhancements.&lt;/strong&gt; Code actions, document symbols, inlay hints for inferred column types. All of these basically making the editor experience even better.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Robust embedded SQL.&lt;/strong&gt; The Python/TypeScript support is really just hacked together with regexes as a proof of concept. My goal is to extend this to use proper parser libraries &lt;em&gt;and&lt;/em&gt; add support for more languages.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Polish and performance.&lt;/strong&gt; The focus for 0.1 was getting the architecture right. There&amp;rsquo;s room to improve performance, API surface, and documentation across the whole stack.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;As for the blog, this project was the reason I&amp;rsquo;ve been completely silent over the last month, but now I&amp;rsquo;m excited to start writing a lot more again. For one, there&amp;rsquo;s the technical followup for the decisions I took and what I learned along the way.&lt;/p&gt;
&lt;p&gt;For another, I used coding agents (mainly Claude Code) extensively while building syntaqlite. There were plenty of moments where following them blindly would have sunk the project, but I can also say with certainty that syntaqlite would not exist without them. I want to dive into that tension: what worked, what didn&amp;rsquo;t, and the complicated feelings I came away with.&lt;/p&gt;
&lt;p&gt;So if you work with SQLite SQL at any scale, I&amp;rsquo;d appreciate you giving syntaqlite a try and telling me what breaks! SQLite is a project which famously plans to be &lt;a href="https://sqlite.org/lts.html"&gt;supported until 2050&lt;/a&gt;. My hope is that syntaqlite will be there as a companion every step of the way, making the experience of writing SQLite SQL that much nicer.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/lalitMaganti/syntaqlite"&gt;syntaqlite on GitHub&lt;/a&gt; | &lt;a href="https://docs.syntaqlite.com/"&gt;Docs&lt;/a&gt; | &lt;a href="https://playground.syntaqlite.com/"&gt;Playground&lt;/a&gt;&lt;/p&gt;
&lt;div class="footnotes" role="doc-endnotes"&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id="fn:1"&gt;
&lt;p&gt;Per-statement results are in &lt;a href="https://github.com/lalitMaganti/syntaqlite/tree/main/tests/upstream_baselines"&gt;&lt;code&gt;tests/upstream_baselines/&lt;/code&gt;&lt;/a&gt;. The remaining 0.3% is largely down to not perfectly matching the builtin tables and then edge cases around where and when column aliases come into scope: it&amp;rsquo;s actually really complicated!&amp;#160;&lt;a href="#fnref:1" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:2"&gt;
&lt;p&gt;You can also pass schema files directly via &lt;code&gt;--schema&lt;/code&gt; on the command line without needing a config file.&amp;#160;&lt;a href="#fnref:2" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:3"&gt;
&lt;p&gt;The plan is to replace the PerfettoSQL parser in Perfetto&amp;rsquo;s trace processor with this C layer.&amp;#160;&lt;a href="#fnref:3" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</content:encoded></item></channel></rss>