<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Sql on Serhii Chechun - Developer&#39;s Blog</title>
    <link>https://blog.chechun.org/tags/sql/</link>
    <description>Recent content in Sql on Serhii Chechun - Developer&#39;s Blog</description>
    <generator>Hugo -- gohugo.io</generator>
    <language>en-us</language>
    <copyright>© 2026 Serhii Chechun</copyright>
    <lastBuildDate>Sun, 27 Sep 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://blog.chechun.org/tags/sql/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>PostgreSQL Query Validation: A Practical Guide to pgqv</title>
      <link>https://blog.chechun.org/posts/pg-query-validate-tool/</link>
      <pubDate>Sun, 27 Sep 2026 00:00:00 +0000</pubDate>
      
      <guid>https://blog.chechun.org/posts/pg-query-validate-tool/</guid>
      <description>&lt;p&gt;There is a specific moment in a deployment when a schema typo stops being free. The migration has
started, the tool has opened a transaction against production, and PostgreSQL reports that &lt;code&gt;type &amp;quot;varchat&amp;quot; does not exist&lt;/code&gt;. The fix is one character. The cost is a failed release, a half-applied
migration, and whatever the rollback procedure happens to be on a Friday afternoon.&lt;/p&gt;</description>
      <content>&lt;p&gt;There is a specific moment in a deployment when a schema typo stops being free. The migration has
started, the tool has opened a transaction against production, and PostgreSQL reports that &lt;code&gt;type &amp;quot;varchat&amp;quot; does not exist&lt;/code&gt;. The fix is one character. The cost is a failed release, a half-applied
migration, and whatever the rollback procedure happens to be on a Friday afternoon.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;pgqv&lt;/code&gt; is a small command-line tool that exists to move that discovery earlier. It reads a &lt;code&gt;.sql&lt;/code&gt;
file, checks it against the PostgreSQL grammar, and reports anything suspicious with the file, the
line, the column and a caret pointing at the exact token - in a few milliseconds, with no database
anywhere in sight.&lt;/p&gt;
&lt;p&gt;This guide covers what it checks, how to install it on each platform, and how to use it in practice:
from a one-off check of a schema file to a pre-commit hook that stops the typo before it is even
committed.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;The Golden Rule:&lt;/strong&gt; validate before something else does. A typo costs two seconds in a pre-commit
hook and a failed release in a migration. The tool that catches it should run early, offline, and
fast enough that nobody is tempted to skip it.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id=&#34;1-what-pgqv-checks-and-what-it-does-not&#34;&gt;1. What pgqv Checks (and What It Does Not)&lt;/h2&gt;
&lt;p&gt;Two checks, run over a single &lt;code&gt;.sql&lt;/code&gt; file:&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;Check&lt;/th&gt;
					&lt;th&gt;Example it catches&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;Syntax&lt;/strong&gt; - the file must parse as PostgreSQL&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;select from where;&lt;/code&gt;&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;Type-name typos&lt;/strong&gt; - an unqualified type one edit from a built-in&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;VARCHAT(255)&lt;/code&gt;, &lt;code&gt;BOOLEN&lt;/code&gt;, &lt;code&gt;TSVETCOR&lt;/code&gt;&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The second one is the reason the tool exists. PostgreSQL accepts &lt;em&gt;any&lt;/em&gt; identifier as a type name and
only resolves it when the statement runs, so &lt;code&gt;VARCHAT(255)&lt;/code&gt; is valid syntax that fails on a real
server. Editing distance is what makes the check usable: flagging every unresolved type name would
light up every schema that defines its own enums or domains, so &lt;code&gt;pgqv&lt;/code&gt; only reports names that are
one character away from a built-in type - a substitution, a transposition, an insertion or a
deletion.&lt;/p&gt;
&lt;p&gt;What it does &lt;strong&gt;not&lt;/strong&gt; do is resolve anything against a catalog. It has no connection to a database,
so table names, column names and function signatures are all outside its reach. That is the price of
running offline, and it is a deliberate trade: a validator that needs a live PostgreSQL cannot run
in a pre-commit hook, in CI on a pull request, or on a laptop between two &lt;code&gt;git&lt;/code&gt; commands.&lt;/p&gt;
&lt;h2 id=&#34;2-installing-pgqv&#34;&gt;2. Installing pgqv&lt;/h2&gt;
&lt;p&gt;Four routes, in the order most people will want them. The first requires no compiler at all.&lt;/p&gt;
&lt;h3 id=&#34;21-homebrew-macos-and-linux&#34;&gt;2.1 Homebrew (macOS and Linux)&lt;/h3&gt;
&lt;p&gt;The formula lives in the project&amp;rsquo;s own repository under &lt;code&gt;Formula/&lt;/code&gt;, so there is no separate
&lt;code&gt;homebrew-tap&lt;/code&gt; to add:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ brew tap serhii-chechun/pg-query-validate https://github.com/serhii-chechun/pg-query-validate
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ brew trust serhii-chechun/pg-query-validate
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ brew install pgqv
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;==&amp;gt; Tapping serhii-chechun/pg-query-validate
Cloning into &amp;#39;.../homebrew-pg-query-validate&amp;#39;...
Tapped 1 formula (28 files, 94.2KB).
Trusted tap: https://github.com/serhii-chechun/pg-query-validate
==&amp;gt; Fetching downloads for: pgqv
✔︎ Formula pgqv (1.0.0)
==&amp;gt; Installing pgqv from serhii-chechun/pg-query-validate
🍺  /opt/homebrew/Cellar/pgqv/1.0.0: 4 files, 8.5MB, built in 1 second
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The middle step is not optional, and it is the one most people will trip over. Homebrew trusts its
own taps by default and refuses to load formulae from a third-party tap until it is trusted:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;Error: Refusing to load formula serhii-chechun/pg-query-validate/pgqv from untrusted tap serhii-chechun/pg-query-validate.
Run `brew trust --formula serhii-chechun/pg-query-validate/pgqv` or `brew trust serhii-chechun/pg-query-validate` to trust it.
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Trusting the whole tap is the sensible choice here - the tap contains exactly one formula.&lt;/p&gt;
&lt;p&gt;The formula installs the &lt;strong&gt;prebuilt binary&lt;/strong&gt; for the platform rather than building from source. That
is deliberate: a Homebrew build has no network access, so it could not fetch the Go modules this
project depends on, and it would recompile a large body of bundled C code on every install. The
practical consequence is that &lt;code&gt;brew install pgqv&lt;/code&gt; finishes in about a second, and no compiler is
involved.&lt;/p&gt;
&lt;p&gt;To remove it again:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ brew uninstall pgqv
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ brew untap serhii-chechun/pg-query-validate
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id=&#34;22-prebuilt-binaries&#34;&gt;2.2 Prebuilt binaries&lt;/h3&gt;
&lt;p&gt;Every release attaches the archives below, each containing a single self-contained binary. They need
no compiler, no PostgreSQL and no shared library - just the system C library.&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;Platform&lt;/th&gt;
					&lt;th&gt;Archive&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;macOS, Apple Silicon&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;pgqv_1.0.0_darwin_arm64.tar.gz&lt;/code&gt;&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;macOS, Intel&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;pgqv_1.0.0_darwin_amd64.tar.gz&lt;/code&gt;&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Linux, x86-64&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;pgqv_1.0.0_linux_amd64.tar.gz&lt;/code&gt;&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Linux, arm64&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;pgqv_1.0.0_linux_arm64.tar.gz&lt;/code&gt;&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Windows, x86-64&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;pgqv_1.0.0_windows_amd64.zip&lt;/code&gt;&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;They are attached to &lt;a href=&#34;https://github.com/serhii-chechun/pg-query-validate/releases/tag/v1.0.0&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;the v1.0.0
release&lt;/a&gt;
. Download the one
that matches the machine, or use &lt;code&gt;curl&lt;/code&gt; directly - which also makes the step reproducible in a
Dockerfile or a bootstrap script:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;macOS and Linux&lt;/strong&gt;&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ curl -LO https://github.com/serhii-chechun/pg-query-validate/releases/download/v1.0.0/pgqv_1.0.0_linux_amd64.tar.gz
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ tar -xzf pgqv_1.0.0_linux_amd64.tar.gz
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ sudo install -m &lt;span style=&#34;color:#ae81ff&#34;&gt;755&lt;/span&gt; pgqv /usr/local/bin/pgqv
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Swap &lt;code&gt;linux_amd64&lt;/code&gt; for the archive that matches the host: &lt;code&gt;darwin_arm64&lt;/code&gt; on an M-series Mac,
&lt;code&gt;linux_arm64&lt;/code&gt; on a Graviton box or an ARM container.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Windows&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The Windows archive is a &lt;code&gt;.zip&lt;/code&gt; containing &lt;code&gt;pgqv.exe&lt;/code&gt;. Extract it and put the directory on &lt;code&gt;PATH&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-powershell&#34; data-lang=&#34;powershell&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;gt; Expand-Archive pgqv_1.0.0_windows_amd64.zip -DestinationPath .
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;gt; .\pgqv.exe .\schema.sql
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Confirm the install with the usage banner - running &lt;code&gt;pgqv&lt;/code&gt; with no arguments prints it:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ pgqv
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;PostgreSQL Query Validator v1.0 (c) 2026, Serhii Chechun
Usage: pgqv &amp;lt;filename.sql&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id=&#34;23-with-go-install&#34;&gt;2.3 With &lt;code&gt;go install&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;If a Go toolchain is already present, this is the shortest route. It requires &lt;strong&gt;Go 1.27.1 or newer&lt;/strong&gt;
and &lt;strong&gt;a C compiler&lt;/strong&gt;, because the SQL parser is compiled from C:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ go install github.com/serhii-chechun/pg-query-validate/cmd/pgqv@v1.0.0
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;go: downloading github.com/serhii-chechun/pg-query-validate v1.0.0
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The binary lands in &lt;code&gt;$(go env GOPATH)/bin&lt;/code&gt; (usually &lt;code&gt;~/go/bin&lt;/code&gt;), which has to be on &lt;code&gt;PATH&lt;/code&gt;. With a
warm build cache this takes about five seconds; on a cold machine the first build compiles a large
amount of bundled C and takes a few minutes.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Gotcha:&lt;/strong&gt; &lt;code&gt;CGO_ENABLED=0 go install ...&lt;/code&gt; does not work, and the failure is worth recognising
because it is not a network problem:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;# github.com/serhii-chechun/pg-query-validate/cmd/pgqv
cmd/pgqv/main.go:45:19: undefined: pgq.Parse
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Go has disabled cgo, so the parser simply is not compiled in. On macOS this only bites if
&lt;code&gt;CGO_ENABLED&lt;/code&gt; was exported to &lt;code&gt;0&lt;/code&gt; globally; the default is fine.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&#34;24-from-source&#34;&gt;2.4 From source&lt;/h3&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ git clone https://github.com/serhii-chechun/pg-query-validate.git
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ cd pg-query-validate
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ go build -o pgqv ./cmd/pgqv
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This is also the route for platforms without a prebuilt archive - notably musl-based distributions
such as Alpine, where the glibc-linked release binary will not run:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ apk add --no-cache go build-base
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ go build -o pgqv ./cmd/pgqv
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id=&#34;25-verifying-a-download&#34;&gt;2.5 Verifying a download&lt;/h3&gt;
&lt;p&gt;Each release also publishes &lt;code&gt;SHA256SUMS&lt;/code&gt;. If the archive came from anywhere other than the release
page, check it:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ curl -LO https://github.com/serhii-chechun/pg-query-validate/releases/download/v1.0.0/SHA256SUMS
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ shasum -a &lt;span style=&#34;color:#ae81ff&#34;&gt;256&lt;/span&gt; -c SHA256SUMS
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;pgqv_1.0.0_darwin_amd64.tar.gz: OK
pgqv_1.0.0_darwin_arm64.tar.gz: OK
pgqv_1.0.0_linux_amd64.tar.gz: OK
pgqv_1.0.0_linux_arm64.tar.gz: OK
pgqv_1.0.0_windows_amd64.zip: OK
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;On Linux, &lt;code&gt;sha256sum -c SHA256SUMS&lt;/code&gt; is the equivalent command.&lt;/p&gt;
&lt;h2 id=&#34;3-your-first-validation&#34;&gt;3. Your First Validation&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;pgqv&lt;/code&gt; takes exactly one file:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ pgqv &amp;lt;filename.sql&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Point it at a schema with a typo in it, and it reports the file, the position and the fix:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ pgqv schema.sql
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;error: unknown type &amp;#34;varchat&amp;#34; (did you mean &amp;#34;varchar&amp;#34;?)
 --&amp;gt; schema.sql:3:22
  |
3 |     name             VARCHAT(255) NOT NULL,
  |                      ^^^^^^^
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;There is nothing to configure and nothing to connect. The tool reads the file, parses it, walks the
result and exits.&lt;/p&gt;
&lt;h2 id=&#34;4-reading-the-output&#34;&gt;4. Reading the Output&lt;/h2&gt;
&lt;p&gt;Every finding is three lines: a message, a &lt;code&gt;file:line:column&lt;/code&gt; location, and the offending source
line with a caret underline sized to the token.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;The message&lt;/strong&gt; names the problem and, for a typo, the type that was probably meant.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The location&lt;/strong&gt; is clickable in most terminals and editors - &lt;code&gt;schema.sql:3:22&lt;/code&gt; jumps straight there.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The caret&lt;/strong&gt; narrows a long line down to the one token that is wrong, which matters in a file full of aligned column definitions.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Typos are reported in lower case even when the source is upper case, because PostgreSQL folds
unquoted identifiers before the parser ever sees them. &lt;code&gt;VARCHAT&lt;/code&gt; in the file becomes &lt;code&gt;&amp;quot;varchat&amp;quot;&lt;/code&gt; in
the message.&lt;/p&gt;
&lt;p&gt;Exit codes make the tool usable in scripts:&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;Code&lt;/th&gt;
					&lt;th&gt;Meaning&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;0&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;No problems found&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;1&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;Problems found, or the file could not be read or parsed&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id=&#34;5-real-examples&#34;&gt;5. Real Examples&lt;/h2&gt;
&lt;h3 id=&#34;51-several-typos-in-one-file&#34;&gt;5.1 Several typos in one file&lt;/h3&gt;
&lt;p&gt;The tool reports every finding in a single run rather than stopping at the first, so one pass tells
you everything that needs fixing:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ pgqv schema.sql
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;error: unknown type &amp;#34;varchat&amp;#34; (did you mean &amp;#34;varchar&amp;#34;?)
 --&amp;gt; /tmp/schema.sql:3:10
  |
3 |     name VARCHAT(255) not null,
  |          ^^^^^^^

error: unknown type &amp;#34;smallit&amp;#34; (did you mean &amp;#34;smallint&amp;#34;?)
 --&amp;gt; /tmp/schema.sql:4:21
  |
4 |     interval_months SMALLIT not null
  |                     ^^^^^^^

error: unknown type &amp;#34;tsvetcor&amp;#34; (did you mean &amp;#34;tsvector&amp;#34;?)
 --&amp;gt; /tmp/schema.sql:9:14
  |
9 |     features TSVETCOR not null,
  |              ^^^^^^^^

error: unknown type &amp;#34;boolen&amp;#34; (did you mean &amp;#34;boolean&amp;#34;?)
  --&amp;gt; /tmp/schema.sql:10:15
   |
10 |     is_active BOOLEN not null
   |               ^^^^^^
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Note the gutter on the last two findings: it widens to fit the line number, so the carets stay
aligned on lines 9 and 10 just as they do on line 3. &lt;code&gt;TSVETCOR&lt;/code&gt; is caught by the transposition case&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;two adjacent characters the wrong way round, which is exactly the shape of a hand-typed typo, and
which a naive &amp;ldquo;one character different&amp;rdquo; check would miss.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;52-a-clean-file&#34;&gt;5.2 A clean file&lt;/h3&gt;
&lt;p&gt;A file with nothing wrong produces no output at all, and exits &lt;code&gt;0&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ pgqv clean.sql
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ echo $?
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Silence on success is deliberate - it means the output of a run over a directory is exactly the list
of files that need attention.&lt;/p&gt;
&lt;h3 id=&#34;53-a-syntax-error-is-a-different-animal&#34;&gt;5.3 A syntax error is a different animal&lt;/h3&gt;
&lt;p&gt;Some problems are rejected by the grammar itself, and those are reported differently, because there
is no parse tree to walk:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ pgqv broken.sql
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;Processing issue: PG_SQL parsing: syntax error at or near &amp;#34;where&amp;#34;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Both kinds exit &lt;code&gt;1&lt;/code&gt;, so a script does not have to distinguish them - but the message makes clear
whether the file is malformed or merely suspicious.&lt;/p&gt;
&lt;h3 id=&#34;54-errors-that-are-not-validation-findings&#34;&gt;5.4 Errors that are not validation findings&lt;/h3&gt;
&lt;p&gt;A missing file is an error, not a finding, and says so:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ pgqv missing.sql
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;Error opening file: open missing.sql: no such file or directory
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Running with no arguments prints the usage banner and exits &lt;code&gt;1&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id=&#34;6-using-it-in-a-workflow&#34;&gt;6. Using It in a Workflow&lt;/h2&gt;
&lt;h3 id=&#34;61-a-loop-over-a-schema-directory&#34;&gt;6.1 A loop over a schema directory&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;pgqv&lt;/code&gt; validates one file per invocation, which is the honest interface but does mean a directory
check is a loop:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ &lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; f in schemas/*.sql; &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt; pgqv &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;$f&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;||&lt;/span&gt; exit 1; &lt;span style=&#34;color:#66d9ef&#34;&gt;done&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Because a clean file is silent, wrapping the loop is enough to get a usable CI summary - the only
output is the list of files that failed.&lt;/p&gt;
&lt;h3 id=&#34;62-a-ci-step&#34;&gt;6.2 A CI step&lt;/h3&gt;
&lt;p&gt;The tool needs no database service, no container and no PostgreSQL client in the job, which is the
whole point of running it early:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-yaml&#34; data-lang=&#34;yaml&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;- &lt;span style=&#34;color:#f92672&#34;&gt;name&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;Validate PostgreSQL schemas&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;run&lt;/span&gt;: |&lt;span style=&#34;color:#e6db74&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;    set -euo pipefail
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;    for f in backend/schemas/**/*.sql; do
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;      echo &amp;#34;checking $f&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;      pgqv &amp;#34;$f&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;    done&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Using the prebuilt binary keeps the job fast; using &lt;code&gt;go install&lt;/code&gt; keeps the job simple if a Go
toolchain is already in the image.&lt;/p&gt;
&lt;h3 id=&#34;63-a-pre-commit-hook&#34;&gt;6.3 A pre-commit hook&lt;/h3&gt;
&lt;p&gt;The most valuable place to run it is before the typo is committed:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-yaml&#34; data-lang=&#34;yaml&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;repos&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  - &lt;span style=&#34;color:#f92672&#34;&gt;repo&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;local&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;hooks&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      - &lt;span style=&#34;color:#f92672&#34;&gt;id&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;pgqv&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#f92672&#34;&gt;name&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;Validate PostgreSQL SQL&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#f92672&#34;&gt;entry&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;scripts/pgqv-all.sh&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#f92672&#34;&gt;language&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;system&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#f92672&#34;&gt;files&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;\.sql$&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The wrapper exists because of a trap worth knowing about. &lt;code&gt;pgqv&lt;/code&gt; reads only its first argument and
silently ignores the rest:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ pgqv clean.sql typo.sql
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ echo $?
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;That second file has a &lt;code&gt;VARCHAT&lt;/code&gt; in it, and the exit code is still &lt;code&gt;0&lt;/code&gt; - so a hook that passes a
batch of filenames straight to &lt;code&gt;pgqv&lt;/code&gt; will check one file and report success for all of them. The
wrapper makes the one-file interface explicit:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;#!/usr/bin/env bash
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# pgqv validates a single file, so check each argument in turn.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;set -uo pipefail
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;status&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; f in &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;$@&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;; &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  pgqv &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;$f&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;||&lt;/span&gt; status&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;done&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;exit &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;$status&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Gotcha:&lt;/strong&gt; a tool that ignores extra arguments turns a batch call into silent under-coverage. Until
&lt;code&gt;pgqv&lt;/code&gt; accepts multiple files or a directory, always drive it from a loop - and prefer &lt;code&gt;exit 1&lt;/code&gt; on
the first failure if a partial check would be misleading.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&#34;7-troubleshooting&#34;&gt;7. Troubleshooting&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;&amp;ldquo;Refusing to load formula &amp;hellip; from untrusted tap.&amp;rdquo;&lt;/strong&gt; Homebrew requires an explicit trust step for
third-party taps. Run &lt;code&gt;brew trust serhii-chechun/pg-query-validate&lt;/code&gt; and retry the install.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The downloaded macOS binary will not run.&lt;/strong&gt; A binary downloaded through a browser carries a
quarantine flag, and Gatekeeper may refuse an unsigned executable. Either allow it under &lt;em&gt;System
Settings - Privacy &amp;amp; Security&lt;/em&gt;, or clear the attribute:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ xattr -d com.apple.quarantine /usr/local/bin/pgqv
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Installing through Homebrew sidesteps this entirely.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;It will not run on Alpine.&lt;/strong&gt; The Linux archives are dynamically linked against glibc, so they do
not run on musl-based systems. Build from source there (section 2.4).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Only the first file was checked.&lt;/strong&gt; &lt;code&gt;pgqv&lt;/code&gt; takes a single filename; extra arguments are ignored.
Drive it from a loop (section 6.3).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Piping into it does nothing.&lt;/strong&gt; There is no stdin support - &lt;code&gt;pgqv &amp;lt; schema.sql&lt;/code&gt; prints the usage
banner and exits &lt;code&gt;1&lt;/code&gt;. Pass a filename.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Valid PostgreSQL 18 syntax is reported as a syntax error.&lt;/strong&gt; This is the grammar ceiling at work:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ pgqv pg18.sql
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;Processing issue: PG_SQL parsing: syntax error at or near &amp;#34;enforced&amp;#34;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The file is valid - &lt;code&gt;NOT ENFORCED&lt;/code&gt; is a PostgreSQL 18 constraint attribute - but the tool parses
with the PostgreSQL 17 grammar, because that is the newest grammar available in its parser
dependency. New syntax will be recognised once that dependency ships a PostgreSQL 18 release.&lt;/p&gt;
&lt;h2 id=&#34;8-limitations&#34;&gt;8. Limitations&lt;/h2&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;Limitation&lt;/th&gt;
					&lt;th&gt;Why&lt;/th&gt;
					&lt;th&gt;Workaround&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;PostgreSQL 17 grammar&lt;/td&gt;
					&lt;td&gt;Newest available parser dependency&lt;/td&gt;
					&lt;td&gt;Nothing yet - it is a dependency bump when released&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;No catalog resolution&lt;/td&gt;
					&lt;td&gt;Deliberately offline&lt;/td&gt;
					&lt;td&gt;Pair it with a migration dry-run against a test database&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Type typos only, one edit away&lt;/td&gt;
					&lt;td&gt;A stricter rule fires on every user-defined type&lt;/td&gt;
					&lt;td&gt;None - this is the tradeoff that keeps it usable&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;One file per run&lt;/td&gt;
					&lt;td&gt;The current interface&lt;/td&gt;
					&lt;td&gt;Loop over files (section 6.1)&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;No stdin, no flags&lt;/td&gt;
					&lt;td&gt;Unimplemented, not a design position&lt;/td&gt;
					&lt;td&gt;Pass a filename&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Two of these deserve a sentence each, because they define what the tool is for. It is not a
substitute for running migrations against a real database - it will never tell you that a column
does not exist. It is a fast first line of defence that catches a specific, embarrassing class of
mistake before anything else has to be involved.&lt;/p&gt;
&lt;h2 id=&#34;9-conclusion&#34;&gt;9. Conclusion&lt;/h2&gt;
&lt;p&gt;The value of &lt;code&gt;pgqv&lt;/code&gt; is mostly in what it does not need: no database, no server, no configuration, no
container. That is what lets it run in a pre-commit hook - which is the only place where catching a
typo is genuinely free - and it is why the tool exists at all rather than a paragraph being added to
a migration checklist.&lt;/p&gt;
&lt;p&gt;In practice the setup is two commands and a loop. Install it with &lt;code&gt;brew install pgqv&lt;/code&gt; or by
unpacking one archive, point it at the schema files, and let the silence on success be the signal:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ &lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; f in schemas/*.sql; &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt; pgqv &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;$f&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;||&lt;/span&gt; exit 1; &lt;span style=&#34;color:#66d9ef&#34;&gt;done&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Everything else in this guide - the exit codes, the caret output, the wrapper for batches of files -
is in service of that one line being safe to put in CI on a Friday afternoon.&lt;/p&gt;
&lt;h2 id=&#34;10-references--examples&#34;&gt;10. References &amp;amp; Examples&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;The tool&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/serhii-chechun/pg-query-validate&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;pg-query-validate&lt;/a&gt;
 - source, README and release notes&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/serhii-chechun/pg-query-validate/releases/tag/v1.0.0&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;v1.0.0 release&lt;/a&gt;
 - the archives and &lt;code&gt;SHA256SUMS&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/serhii-chechun/pg-query-validate/blob/main/Formula/pgqv.rb&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;&lt;code&gt;Formula/pgqv.rb&lt;/code&gt;&lt;/a&gt;
 - the Homebrew formula, hosted in the project&amp;rsquo;s own repository&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;PostgreSQL&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://www.postgresql.org/docs/current/datatype.html&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;PostgreSQL: Data Types&lt;/a&gt;
 - the built-in types the typo check suggests from&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.postgresql.org/docs/18/release-18.html&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;PostgreSQL 18 release notes&lt;/a&gt;
 - the syntax the current grammar does not yet know&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.postgresql.org/docs/current/sql-createtable.html&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;PostgreSQL: &lt;code&gt;CREATE TABLE&lt;/code&gt;&lt;/a&gt;
 - where &lt;code&gt;VARCHAT&lt;/code&gt; would have failed&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Tooling used in this guide&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://docs.brew.sh/Taps&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;Homebrew: Taps&lt;/a&gt;
 - tapping a repository other than &lt;code&gt;homebrew-core&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://docs.brew.sh/Tap-Trust&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;Homebrew: Tap Trust&lt;/a&gt;
 - why &lt;code&gt;brew trust&lt;/code&gt; is required for third-party taps&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://pre-commit.com/&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;pre-commit&lt;/a&gt;
 - the hook configuration in section 6.3&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://docs.github.com/en/actions&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;GitHub Actions&lt;/a&gt;
 - the CI step in section 6.2&lt;/li&gt;
&lt;/ul&gt;
</content>
    </item>
    
  </channel>
</rss>
