<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Miłosz Kordziński</title>
    <description>The latest articles on DEV Community by Miłosz Kordziński (@milekv).</description>
    <link>https://dev.to/milekv</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4076003%2Ff7719960-3a5a-415a-a2d1-79a454e86c8e.jpg</url>
      <title>DEV Community: Miłosz Kordziński</title>
      <link>https://dev.to/milekv</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/milekv"/>
    <language>en</language>
    <item>
      <title>Running the same SQL checks in a browser, CLI and pull request</title>
      <dc:creator>Miłosz Kordziński</dc:creator>
      <pubDate>Thu, 13 Aug 2026 09:32:30 +0000</pubDate>
      <link>https://dev.to/milekv/running-the-same-sql-checks-in-a-browser-cli-and-pull-request-52hk</link>
      <guid>https://dev.to/milekv/running-the-same-sql-checks-in-a-browser-cli-and-pull-request-52hk</guid>
      <description>&lt;p&gt;I wanted one set of SQL checks to work in three places: while exploring a query, from a terminal and during code review.&lt;/p&gt;

&lt;p&gt;That became SQL Atlas. It is a local, deterministic SQL analyzer with a browser interface, a CLI and a GitHub Action. This article covers the interfaces, the CI contract and the limits of static SQL analysis.&lt;/p&gt;

&lt;h2&gt;
  
  
  One analyzer, three interfaces
&lt;/h2&gt;

&lt;p&gt;The analyzer returns structured data instead of printing messages directly. Each interface decides how to present the same result:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The browser explains findings and links them to learning material.&lt;/li&gt;
&lt;li&gt;The CLI returns text, JSON or Markdown and uses stable exit codes.&lt;/li&gt;
&lt;li&gt;The GitHub Action converts findings into file annotations and a job summary.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Keeping presentation outside the analyzer prevents the CLI and Action from becoming separate implementations with different behavior.&lt;/p&gt;

&lt;h2&gt;
  
  
  A CLI needs a contract
&lt;/h2&gt;

&lt;p&gt;The CLI accepts one or more files, or SQL through standard input:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx &lt;span class="nt"&gt;--yes&lt;/span&gt; sql-atlas@0.5.1 analyze query.sql
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"SELECT * FROM customers;"&lt;/span&gt; | npx &lt;span class="nt"&gt;--yes&lt;/span&gt; sql-atlas@0.5.1 analyze -
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It supports PostgreSQL, MySQL, Oracle, SQLite, SQL Server and a generic mode. Output can be text for a person, JSON for another program or Markdown for an issue or report.&lt;/p&gt;

&lt;p&gt;Exit codes are part of the interface:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;0&lt;/code&gt; means analysis completed and the configured policy passed.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;1&lt;/code&gt; means analysis completed but a severity or score threshold failed.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;2&lt;/code&gt; means the command or input was invalid.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This distinction matters in CI. A policy failure is not the same as a broken invocation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Turning findings into pull request feedback
&lt;/h2&gt;

&lt;p&gt;The Action runs as a bundled Node 24 program and does not download dependencies at runtime. A minimal workflow looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;SQL review&lt;/span&gt;

&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;pull_request&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;paths&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;**/*.sql"&lt;/span&gt;

&lt;span class="na"&gt;permissions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;contents&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;read&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;sql-atlas&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v7&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;milekv/sql-atlas@v0.5.1&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;paths&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
            &lt;span class="s"&gt;migrations/**/*.sql&lt;/span&gt;
            &lt;span class="s"&gt;schema/**/*.sql&lt;/span&gt;
          &lt;span class="na"&gt;dialect&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgresql&lt;/span&gt;
          &lt;span class="na"&gt;fail-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;critical&lt;/span&gt;
          &lt;span class="na"&gt;min-score&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;60&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Findings become GitHub file annotations. The full result is written to the job summary, and the Action exposes file count, finding count and lowest score as outputs.&lt;/p&gt;

&lt;p&gt;The default policy only fails on critical findings. Teams can start in report-only mode with &lt;code&gt;fail-on: none&lt;/code&gt;, inspect false positives and add stricter thresholds later.&lt;/p&gt;

&lt;h2&gt;
  
  
  What static analysis cannot know
&lt;/h2&gt;

&lt;p&gt;SQL Atlas does not connect to a database. It cannot know table sizes, data distribution, available indexes, planner settings or the real execution plan.&lt;/p&gt;

&lt;p&gt;For that reason, a warning such as a function applied to a filtered column means "check whether this blocks the index strategy you expect", not "this query is slow". Runtime performance still needs &lt;code&gt;EXPLAIN&lt;/code&gt;, representative data and production-like measurements.&lt;/p&gt;

&lt;p&gt;The browser includes a local PostgreSQL EXPLAIN JSON viewer for that next step, but the analyzer deliberately keeps its claims narrow.&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing the distribution surfaces
&lt;/h2&gt;

&lt;p&gt;The project tests the analyzer and both automation interfaces. CI builds the web app, CLI and Action bundle. A smoke workflow runs the repository's own Action against a known SQL file and verifies its outputs. CI also rebuilds the committed Action bundle and checks that it has no uncommitted difference.&lt;/p&gt;

&lt;p&gt;The CLI package has no runtime dependencies. I verified the public npm package from an empty directory with a clean cache, including the executable version and a real stdin analysis.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Demo: &lt;a href="https://milekv.github.io/sql-atlas/" rel="noopener noreferrer"&gt;https://milekv.github.io/sql-atlas/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Source: &lt;a href="https://github.com/milekv/sql-atlas" rel="noopener noreferrer"&gt;https://github.com/milekv/sql-atlas&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;npm: &lt;a href="https://www.npmjs.com/package/sql-atlas" rel="noopener noreferrer"&gt;https://www.npmjs.com/package/sql-atlas&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;v0.5.1: &lt;a href="https://github.com/milekv/sql-atlas/releases/tag/v0.5.1" rel="noopener noreferrer"&gt;https://github.com/milekv/sql-atlas/releases/tag/v0.5.1&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I am particularly interested in examples where a rule is too broad, misses a dialect detail or produces an unhelpful CI annotation.&lt;/p&gt;

</description>
      <category>sql</category>
      <category>github</category>
      <category>typescript</category>
      <category>opensource</category>
    </item>
  </channel>
</rss>
