<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en-us">
	<id>https://justatheory.com/</id>
	<title>Just a Theory</title>
	<subtitle>Periodically irregular technology and culture blogging since 2002. By David E. Wheeler.</subtitle>
	<updated>2026-04-13T22:22:53Z</updated>
	<link rel="self" type="application/atom+xml" href="https://justatheory.com/feed.xml"/>
	<link rel="alternate" type="text/html" href="https://justatheory.com/"/>
	<link rel="alternate" type="application/feed&#43;json" href="https://justatheory.com/feed.json"/>
	<author>
		<name>David E. Wheeler</name>
		<email>david@justatheory.com</email>
		<uri>https://justatheory.com/</uri>
	</author>
	<generator uri="https://gohugo.io/" version="0.159.2">Hugo</generator>
	<icon>https://justatheory.com/icon-512x512.png</icon>
	<rights>© David E. Wheeler. This work is licensed under CC BY-NC-SA 4.0.</rights>
	<entry>
		<id>https://justatheory.com/2026/04/pg_clickhouse-0.2.0/</id>
		<title type="html"><![CDATA[pg_clickhouse 0.2.0]]></title>
		<link rel="alternate" type="text/html" href="https://justatheory.com/2026/04/pg_clickhouse-0.2.0/"/>
		<updated>2026-04-13T22:22:53Z</updated>
		<published>2026-04-13T22:22:53Z</published>
		<author>
			<name>David E. Wheeler</name>
			<email>david@justatheory.com</email>
			<uri>https://justatheory.com/</uri>
		</author>
		<category scheme="https://justatheory.com/tags" term="postgres" label="Postgres"/>
		<category scheme="https://justatheory.com/tags" term="pg_clickhouse" label="pg_clickhouse"/>
		<category scheme="https://justatheory.com/tags" term="clickhouse" label="ClickHouse"/>
		<category scheme="https://justatheory.com/tags" term="release" label="Release"/>
		<category scheme="https://justatheory.com/tags" term="regular-expressions" label="Regular Expressions"/>
		<summary type="html"><![CDATA[I guess this is a pg_clickhouse announcement blog, now.]]></summary>
		<content type="html" xml:base="https://justatheory.com/" xml:space="preserve">
<![CDATA[    <article class="post">
        <div class="text">
<p>In response to a generous corpus of real-world user feedback, we&rsquo;ve been hard
at work the past week adding a slew of updates to <a href="https://pgxn.org/dist/pg_clickhouse/" title="pg_clickhouse on PGXN">pg_clickhouse</a>, the query
interface for ClickHouse from Postgres. As usual, we focused on improving
pushdown, especially for various date and time, array, and regular expression
functions.</p>
<p>Regular expressions prove to be a particular challenge, because while Postgres
supports <a href="https://www.postgresql.org/docs/18/functions-matching.html#FUNCTIONS-POSIX-REGEXP" title="PostgreSQL Docs: POSIX Regular Expressions">POSIX Regular Expressions</a>, ClickHouse relies on <a href="https://github.com/google/re2/wiki/Syntax" title="RE2 Syntax">RE2</a>. For
simple regular expressions that no doubt make up a huge number of use cases,
the differences matter little or not at all. But these two engines take quite
different approaches to regular expression evaluation, so issues will come up.</p>
<p>To address this, the new regular expression pushdown code examines the flags
passed to the Postgres regular expression functions and refuses to push down
in the presence of incompatible flags. It will push down compatible flags,
though it takes pains to also pass <code>(?-s)</code> to disable the <code>s</code> flag, because
ClickHouse <a href="https://clickhouse.com/docs/sql-reference/functions/string-search-functions#match" title="ClickHouse Docs: match">enables <code>s</code></a> by default, contrary to the expectations of the
Postgres regular expression user.</p>
<p>pg_clickhouse does not (yet?) examine the flags embedded in the regular
expression, but v0.2.0 now provides the <code>pg_clickhouse.pushdown_regex</code>
setting, which can disable regular expression pushdown:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sql" data-lang="sql"><span class="line"><span class="cl"><span class="k">SET</span><span class="w"> </span><span class="n">pg_clickhouse</span><span class="p">.</span><span class="n">pushdown_regex</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">&#39;false&#39;</span><span class="p">;</span><span class="w">
</span></span></span></code></pre></div><p>My colleague <a href="https://serprex.github.io">Philip Dubé</a> has also started work embedding
ClickHouse-compatible regular expression functions that use <a href="https://github.com/google/re2/wiki/Syntax" title="RE2 Syntax">re2</a> directly, to
provide more options soon &mdash; not to mention a standalone extension with just
those functions.</p>
<p>As with all pg_clickhouse releases to date, v0.2.0 does not break
compatibility with previous versions at all: once the new library has been
installed and reloaded, existing v0.1 releases get all the benefits. There is,
however, a new function, <code>pgch_version()</code>, which requires an upgrade to
use:</p>
<pre tabindex="0"><code class="language-pgsql" data-lang="pgsql">try=# ALTER EXTENSION pg_clickhouse UPDATE TO &#39;0.2&#39;;
ALTER EXTENSION

try=# select pgch_version();
 pgch_version 
--------------
 0.2.0
(1 row)
</code></pre><p>We plan for a lot more to come, including improved subquery pushdown, more
function pushdown, string and date formatting pushdown, and more. Watch <a href="http://justatheory.com/" title="Just a Theory">this
space</a> for further announcements and the <a href="https://clickhouse.com/blog">ClickHouse Blog</a> for a forthcoming
post covering the pg_clickhouse features and improvements in detail.
Meanwhile, here&rsquo;s where to get the new release:</p>
<ul>
<li><a href="https://pgxn.org/dist/pg_clickhouse/0.2.0/" title="pg_clickhouse 0.2.0 on PGXN">PGXN</a></li>
<li><a href="https://github.com/ClickHouse/pg_clickhouse/releases/tag/v0.2.0" title="pg_clickhouse 0.2.0 on GitHub">GitHub</a></li>
<li><a href="https://github.com/ClickHouse/pg_clickhouse/pkgs/container/pg_clickhouse" title="pg_clickhouse OCI Images">Docker</a></li>
</ul>
<p>Thanks again my colleagues, <a href="https://iska.is" title="Kaushik’s Bits &amp; Pieces">Kaushik Iska</a> and <a href="https://serprex.github.io">Philip Dubé</a> for the slew of
pull requests and feature brainstorming.</p>

        </div>

        <footer class="tags">
            <h5>More about…</h5>
            <ul>
                <li><a href="https://justatheory.com/tags/postgres/">Postgres</a></li>
                <li><a href="https://justatheory.com/tags/pg_clickhouse/">pg_clickhouse</a></li>
                <li><a href="https://justatheory.com/tags/clickhouse/">ClickHouse</a></li>
                <li><a href="https://justatheory.com/tags/release/">Release</a></li>
                <li><a href="https://justatheory.com/tags/regular-expressions/">Regular Expressions</a></li>
            </ul>
        </footer>
    </article>
]]></content>
	</entry>
	<entry>
		<id>https://justatheory.com/2026/04/pg_clickhouse-0.1.10/</id>
		<title type="html"><![CDATA[pg_clickhouse 0.1.10]]></title>
		<link rel="alternate" type="text/html" href="https://justatheory.com/2026/04/pg_clickhouse-0.1.10/"/>
		<updated>2026-04-06T21:38:34Z</updated>
		<published>2026-04-06T21:38:34Z</published>
		<author>
			<name>David E. Wheeler</name>
			<email>david@justatheory.com</email>
			<uri>https://justatheory.com/</uri>
		</author>
		<category scheme="https://justatheory.com/tags" term="postgres" label="Postgres"/>
		<category scheme="https://justatheory.com/tags" term="pg_clickhouse" label="pg_clickhouse"/>
		<category scheme="https://justatheory.com/tags" term="clickhouse" label="ClickHouse"/>
		<category scheme="https://justatheory.com/tags" term="release" label="Release"/>
		<summary type="html"><![CDATA[Hi, it&rsquo;s me with another update to pg_clickhouse.]]></summary>
		<content type="html" xml:base="https://justatheory.com/" xml:space="preserve">
<![CDATA[    <article class="post">
        <div class="text">
<p>Hi, it&rsquo;s me, back again with another update to pg_clickhouse, the query
interface for ClickHouse from Postgres. This release, <a href="https://github.com/ClickHouse/pg_clickhouse/releases/tag/v0.1.10" title="pg_clickhouse 0.1.10 on GitHub">v0.1.10</a>,
maintains binary compatibility with earlier versions but ships a number of
significant improvements that increase compatibility of Postgres features with
ClickHouse. Highlights include:</p>
<ul>
<li>Mappings for the <code>JSON</code> and <code>JSONB</code> <code>-&gt; TEXT</code> and <code>-&gt;&gt; TEXT</code> operators, as
well as <code>jsonb_extract_path_text()</code> and <code>jsonb_extract_path()</code>, to be pushed
down to ClickHouse using its <a href="https://clickhouse.com/docs/sql-reference/data-types/newjson#reading-json-paths-as-sub-columns" title="ClickHouse Docs: Reading JSON paths as sub-columns">sub-column syntax</a>.</li>
<li>Mappings to push down the Postgres <code>statement_timestamp()</code>,
<code>transaction_timestamp()</code>, and <code>clock_timestamp()</code> functions, as well as
the Postgres &ldquo;SQL Value Functions&rdquo;, including <code>CURRENT_TIMESTAMP</code>,
<code>CURRENT_USER</code>, and <code>CURRENT_DATABASE</code>.</li>
<li>And the big one: mappings to push down compatible <strong>window functions</strong>,
including <code>ROW_NUMBER</code>, <code>RANK</code>, <code>DENSE_RANK</code>, <code>LEAD</code>,<code>LAG</code>, <code>FIRST_VALUE</code>,
<code>LAST_VALUE</code>, <code>NTH_VALUE</code>, <code>NTILE</code>, <code>CUME_DIST</code>, <code>PERCENT_RANK</code>,  and
<code>MIN</code>/<code>MAX OVER</code>.</li>
<li>Oh yeah, the other big one: added <strong>result set streaming</strong> to the HTTP
driver. Rather that load all the results A testing loading a 1GB table
reduced memory consumption from over 1GB to 73MB peak.</li>
</ul>
<p>We&rsquo;ll work up a longer post to show off some of these features in the next
week. But in the meantime, git it while it&rsquo;s hot!</p>
<ul>
<li><a href="https://pgxn.org/dist/pg_clickhouse/0.1.10/" title="pg_clickhouse 0.1.10 on PGXN">PGXN</a></li>
<li><a href="https://github.com/ClickHouse/pg_clickhouse/releases/tag/v0.1.10" title="pg_clickhouse 0.1.10 on GitHub">GitHub</a></li>
<li><a href="https://github.com/ClickHouse/pg_clickhouse/pkgs/container/pg_clickhouse" title="pg_clickhouse OCI Images">Docker</a></li>
</ul>
<p>Thanks to my colleagues, <a href="https://iska.is" title="Kaushik’s Bits &amp; Pieces">Kaushik Iska</a> and <a href="https://serprex.github.io">Philip Dubé</a> for the slew of pull
requests I waded through this past week!</p>

        </div>

        <footer class="tags">
            <h5>More about…</h5>
            <ul>
                <li><a href="https://justatheory.com/tags/postgres/">Postgres</a></li>
                <li><a href="https://justatheory.com/tags/pg_clickhouse/">pg_clickhouse</a></li>
                <li><a href="https://justatheory.com/tags/clickhouse/">ClickHouse</a></li>
                <li><a href="https://justatheory.com/tags/release/">Release</a></li>
            </ul>
        </footer>
    </article>
]]></content>
	</entry>
	<entry>
		<id>https://justatheory.com/2026/04/pg_clickhouse-0.1.6/</id>
		<title type="html"><![CDATA[pg_clickhouse 0.1.6]]></title>
		<link rel="alternate" type="text/html" href="https://justatheory.com/2026/04/pg_clickhouse-0.1.6/"/>
		<updated>2026-04-06T20:44:26Z</updated>
		<published>2026-04-02T15:21:13Z</published>
		<author>
			<name>David E. Wheeler</name>
			<email>david@justatheory.com</email>
			<uri>https://justatheory.com/</uri>
		</author>
		<category scheme="https://justatheory.com/tags" term="postgres" label="Postgres"/>
		<category scheme="https://justatheory.com/tags" term="pg_clickhouse" label="pg_clickhouse"/>
		<category scheme="https://justatheory.com/tags" term="clickhouse" label="ClickHouse"/>
		<category scheme="https://justatheory.com/tags" term="release" label="Release"/>
		<summary type="html"><![CDATA[Another bug fix and pushdown-improving release of the foreign data wrapper.]]></summary>
		<content type="html" xml:base="https://justatheory.com/" xml:space="preserve">
<![CDATA[    <article class="post">
        <div class="text">
<p>We fixed a few bugs this week in <a href="https://pgxn.org/dist/pg_clickhouse/" title="pg_clickhouse on PGXN">pg_clickhouse</a>, the query interface for
ClickHouse from Postgres. It features improved query cancellation and function
&amp; operator pushdown, including <code>to_timestamp(float8)</code>, <code>ILIKE</code>, <code>LIKE</code>, and
regex operators. Get the new v0.1.6 release from the usual places:</p>
<ul>
<li><a href="https://pgxn.org/dist/pg_clickhouse/0.1.6/" title="pg_clickhouse 0.1.6 on PGXN">PGXN</a></li>
<li><a href="https://github.com/ClickHouse/pg_clickhouse/releases/tag/v0.1.6" title="pg_clickhouse 0.1.6 on GitHub">GitHub</a></li>
<li><a href="https://github.com/ClickHouse/pg_clickhouse/pkgs/container/pg_clickhouse" title="pg_clickhouse OCI Images">Docker</a></li>
</ul>
<p>Thanks to my colleague, <a href="https://iska.is" title="Kaushik’s Bits &amp; Pieces">Kaushik Iska</a>, for most of these fixes!</p>

        </div>

        <footer class="tags">
            <h5>More about…</h5>
            <ul>
                <li><a href="https://justatheory.com/tags/postgres/">Postgres</a></li>
                <li><a href="https://justatheory.com/tags/pg_clickhouse/">pg_clickhouse</a></li>
                <li><a href="https://justatheory.com/tags/clickhouse/">ClickHouse</a></li>
                <li><a href="https://justatheory.com/tags/release/">Release</a></li>
            </ul>
        </footer>
    </article>
]]></content>
	</entry>
	<entry>
		<id>https://justatheory.com/2026/03/pg_clickhouse-0.1.5/</id>
		<title type="html"><![CDATA[pg_clickhouse 0.1.5]]></title>
		<link rel="alternate" type="text/html" href="https://justatheory.com/2026/03/pg_clickhouse-0.1.5/"/>
		<updated>2026-03-20T19:15:47Z</updated>
		<published>2026-03-20T19:15:47Z</published>
		<author>
			<name>David E. Wheeler</name>
			<email>david@justatheory.com</email>
			<uri>https://justatheory.com/</uri>
		</author>
		<category scheme="https://justatheory.com/tags" term="postgres" label="Postgres"/>
		<category scheme="https://justatheory.com/tags" term="pg_clickhouse" label="pg_clickhouse"/>
		<category scheme="https://justatheory.com/tags" term="clickhouse" label="ClickHouse"/>
		<category scheme="https://justatheory.com/tags" term="release" label="Release"/>
		<summary type="html"><![CDATA[New maintenance release of pg_clickhouse: v0.1.5.]]></summary>
		<content type="html" xml:base="https://justatheory.com/" xml:space="preserve">
<![CDATA[    <article class="post">
        <div class="text">
<p>I&rsquo;ve been busy with an internal project at work, but have responded to a few
<a href="https://pgxn.org/dist/pg_clickhouse/" title="pg_clickhouse on PGXN">pg_clickhouse</a> reports for a couple crashes and vulnerabilities, thanks to
pen testing and a community security report. These <a href="https://github.com/ClickHouse/pg_clickhouse/releases/tag/v0.1.5" title="pg_clickhouse 0.1.5 on GitHub">changes</a> drive the
release of v0.1.5 today.</p>
<p>Get it from the usual sources:</p>
<ul>
<li><a href="https://pgxn.org/dist/pg_clickhouse/0.1.5/" title="pg_clickhouse 0.1.5 on PGXN">PGXN</a></li>
<li><a href="https://github.com/ClickHouse/pg_clickhouse/releases/tag/v0.1.5" title="pg_clickhouse 0.1.5 on GitHub">GitHub</a></li>
<li><a href="https://github.com/ClickHouse/pg_clickhouse/pkgs/container/pg_clickhouse" title="pg_clickhouse OCI Images">Docker</a></li>
</ul>
<p>Appreciation to my employer, <a href="https://clickhouse.com/clickhouse" title="ClickHouse: The fastest open-source analytical database">ClickHouse</a>, for championing this extension.</p>

        </div>

        <footer class="tags">
            <h5>More about…</h5>
            <ul>
                <li><a href="https://justatheory.com/tags/postgres/">Postgres</a></li>
                <li><a href="https://justatheory.com/tags/pg_clickhouse/">pg_clickhouse</a></li>
                <li><a href="https://justatheory.com/tags/clickhouse/">ClickHouse</a></li>
                <li><a href="https://justatheory.com/tags/release/">Release</a></li>
            </ul>
        </footer>
    </article>
]]></content>
	</entry>
	<entry>
		<id>https://justatheory.com/2026/02/pg_clickhouse-0.1.4/</id>
		<title type="html"><![CDATA[pg_clickhouse v0.1.4]]></title>
		<link rel="alternate" type="text/html" href="https://justatheory.com/2026/02/pg_clickhouse-0.1.4/"/>
		<updated>2026-02-17T22:24:08Z</updated>
		<published>2026-02-17T22:24:08Z</published>
		<author>
			<name>David E. Wheeler</name>
			<email>david@justatheory.com</email>
			<uri>https://justatheory.com/</uri>
		</author>
		<category scheme="https://justatheory.com/tags" term="postgres" label="Postgres"/>
		<category scheme="https://justatheory.com/tags" term="pg_clickhouse" label="pg_clickhouse"/>
		<category scheme="https://justatheory.com/tags" term="release" label="Release"/>
		<summary type="html"><![CDATA[A quick note on the release of pg_clickhouse v0.1.4.]]></summary>
		<content type="html" xml:base="https://justatheory.com/" xml:space="preserve">
<![CDATA[    <article class="post">
        <div class="text">
<p>Just a quick post to note the release of <a href="https://pgxn.org/dist/pg_clickhouse/" title="pg_clickhouse on PGXN">pg_clickhouse</a> v0.1.4. This v0.1
maintenance release can be upgraded in-place and requires no
<code>ALTER EXTENSION UPDATE</code> command; as soon as sessions reload the shared
library they&rsquo;ll be good to go.</p>
<p>Thanks in part to reports from attentive users, v0.1.4&rsquo;s most significant
changes improve the following:</p>
<ul>
<li>The binary driver now properly inserts <code>NULL</code> into a <a href="https://clickhouse.com/docs/sql-reference/data-types/nullable" title="ClickHouse Docs: Nullable(T)">Nullable(T)</a> column.
Previously it would raise an error.</li>
<li>The http driver now properly parses arrays. Previously it improperly
included single quotes in string items and would choke on brackets (<code>[]</code>)
in values.</li>
<li>Both drivers now support mapping a ClickHouse <a href="https://clickhouse.com/docs/sql-reference/data-types/string" title="ClickHouse Docs: String">String</a> types to Postgres
<a href="https://www.postgresql.org/docs/current/datatype-binary.html" title="Postgres Docs: Binary Data Types">BYTEA</a> columns. Previously the worked only with <a href="https://www.postgresql.org/docs/current/datatype-character.html" title="Postgres Docs: Character Types">text types</a>, which is
generally preferred. But since ClickHouse explicitly supports binary data
in <a href="https://clickhouse.com/docs/sql-reference/data-types/string" title="ClickHouse Docs: String">String</a> values (notably <a href="https://clickhouse.com/docs/sql-reference/functions/hash-functions" title="ClickHouse Docs: Hash functions">hash function</a> return values), pg_clickhouse
needs to support it, as well.</li>
</ul>
<p>Get it in all the usual places:</p>
<ul>
<li><a href="https://pgxn.org/dist/pg_clickhouse/0.1.4/" title="pg_clickhouse 0.1.4 on PGXN">PGXN</a></li>
<li><a href="https://github.com/ClickHouse/pg_clickhouse/releases/tag/v0.1.4" title="pg_clickhouse 0.1.4 on GitHub">GitHub</a></li>
<li><a href="https://github.com/ClickHouse/pg_clickhouse/pkgs/container/pg_clickhouse" title="pg_clickhouse OCI Images">Docker</a></li>
</ul>
<p>My thanks to pg_clickhouse users like <a href="https://github.com/ClickHouse/pg_clickhouse/issues/140" title="ClickHouse/pg_clickhouse#140 Connection Terminates Unexpectedly When Using NULL Value for UUID">Rahul Mehta</a> for reporting issues, and
to my employer, <a href="https://clickhouse.com/clickhouse" title="ClickHouse: The fastest open-source analytical database">ClickHouse</a>, for championing this extension. Next up: more
aggregate function mapping, hash function pushdown, and improved subquery
(specifically, <code>SubPlan</code>) pushdown.</p>

        </div>

        <footer class="tags">
            <h5>More about…</h5>
            <ul>
                <li><a href="https://justatheory.com/tags/postgres/">Postgres</a></li>
                <li><a href="https://justatheory.com/tags/pg_clickhouse/">pg_clickhouse</a></li>
                <li><a href="https://justatheory.com/tags/release/">Release</a></li>
            </ul>
        </footer>
    </article>
]]></content>
	</entry>
	<entry>
		<id>https://justatheory.com/2026/01/pgxn-tools-v1.7/</id>
		<title type="html"><![CDATA[🛠️ PGXN Tools v1.7]]></title>
		<link rel="alternate" type="text/html" href="https://justatheory.com/2026/01/pgxn-tools-v1.7/"/>
		<updated>2026-01-24T22:53:11Z</updated>
		<published>2026-01-24T22:53:11Z</published>
		<author>
			<name>David E. Wheeler</name>
			<email>david@justatheory.com</email>
			<uri>https://justatheory.com/</uri>
		</author>
		<category scheme="https://justatheory.com/tags" term="postgres" label="Postgres"/>
		<category scheme="https://justatheory.com/tags" term="pgxn" label="PGXN"/>
		<category scheme="https://justatheory.com/tags" term="docker" label="Docker"/>
		<category scheme="https://justatheory.com/tags" term="github-workflow" label="GitHub Workflow"/>
		<summary type="html"><![CDATA[Just released the PGXN test and build OCI image upgraded to Trixie and
improving PGXS build parallelization.]]></summary>
		<content type="html" xml:base="https://justatheory.com/" xml:space="preserve">
<![CDATA[    <article class="post">
        <div class="text">
<p>Today I released v1.7.0 of the <a href="https://hub.docker.com/r/pgxn/pgxn-tools">pgxn-tools OCI image</a>, which simplifies
Postgres extension testing and <a href="https://pgxn.org" title="PostgreSQL Extension Network">PGXN</a> distribution. The new version includes
just a few updates and improvements:</p>
<ul>
<li>Upgraded the Debian base image from Bookworm to Trixie</li>
<li>Set the <code>PGUSER</code> environment variable to <code>postgres</code> in the <code>Dockerfile</code>,
removing the need for users to remember to do it.</li>
<li>Updated <a href="https://github.com/pgxn/docker-pgxn-tools?tab=readme-ov-file#pg-build-test"><code>pg-build-test</code></a>  to set <code>MAKEFLAGS=&quot;-j $(nprocs)&quot;</code> to shorten
build runtimes.</li>
<li>Also updated <a href="https://github.com/pgxn/docker-pgxn-tools?tab=readme-ov-file#pgrx-build-test"><code>pgrx-build-test</code></a> to pass <code>-j $(nprocs)</code>, for the same
reason.</li>
<li>Upgraded the pgrx test extension to v0.16.1 and test it on Postgres
versions 13-16.</li>
</ul>
<p>Just a security and quality of coding life release. Ideally existing workflows
will continue to work as they always have.</p>

        </div>

        <footer class="tags">
            <h5>More about…</h5>
            <ul>
                <li><a href="https://justatheory.com/tags/postgres/">Postgres</a></li>
                <li><a href="https://justatheory.com/tags/pgxn/">PGXN</a></li>
                <li><a href="https://justatheory.com/tags/docker/">Docker</a></li>
                <li><a href="https://justatheory.com/tags/github-workflow/">GitHub Workflow</a></li>
            </ul>
        </footer>
    </article>
]]></content>
	</entry>
	<entry>
		<id>https://justatheory.com/2025/12/welcome-dmjwk/</id>
		<title type="html"><![CDATA[Welcome dmjwk]]></title>
		<link rel="alternate" type="text/html" href="https://justatheory.com/2025/12/welcome-dmjwk/"/>
		<updated>2025-12-30T03:21:05Z</updated>
		<published>2025-12-30T03:21:05Z</published>
		<author>
			<name>David E. Wheeler</name>
			<email>david@justatheory.com</email>
			<uri>https://justatheory.com/</uri>
		</author>
		<category scheme="https://justatheory.com/tags" term="oauth" label="OAuth"/>
		<category scheme="https://justatheory.com/tags" term="jwt" label="JWT"/>
		<category scheme="https://justatheory.com/tags" term="jwk" label="JWK"/>
		<category scheme="https://justatheory.com/tags" term="go" label="Go"/>
		<category scheme="https://justatheory.com/tags" term="bearer" label="Bearer"/>
		<category scheme="https://justatheory.com/tags" term="demo" label="Demo"/>
		<summary type="html"><![CDATA[I wrote a dead simple demo IDP server. Never use it for real workloads. But
you might find it useful to demo services that require Bearer Token
authentication.]]></summary>
		<content type="html" xml:base="https://justatheory.com/" xml:space="preserve">
<![CDATA[    <article class="post">
        <div class="text">
<p>Please welcome <a href="https://github.com/theory/dmjwk" title="dmjwk: Simple OAuth 2 JWK/JWT demo service">dmjwk</a> into the world. This &ldquo;demo JWK&rdquo; (or &ldquo;dumb JWK&rdquo; if you
like) service provides super simple Identity Provider APIs strictly for demo
purposes.</p>
<p>Say you&rsquo;ve written a service that depends on a public JSON Web Key (<a href="https://www.rfc-editor.org/rfc/rfc7517" title="RFC 7517: JSON Web Key (JWK)">JWK</a>) set
to authenticate JSON Web Tokens (<a href="https://www.rfc-editor.org/rfc/rfc7519" title="RFC 7519 JSON Web Token (JWT)">JWT</a>) submitted as <a href="https://datatracker.ietf.org/doc/html/rfc6750" title="RFC 6750 --- The OAuth 2.0 Authorization Framework: Bearer Token Usage">OAuth 2 Bearer
Tokens</a>. Your users will normally configure the service to use an
internal or well-known provider, such as <a href="https://auth0.com/docs/secure/tokens/json-web-tokens/json-web-key-sets" title="Auth0 Docs: JSON Web Key Sets">Auth0</a>, <a href="https://developer.okta.com/docs/guides/validate-id-tokens/main/#retrieve-the-json-web-key-set" title="Okta Docs: Retrieve the JSON Web Key Set">Okta</a>, or <a href="https://docs.aws.amazon.com/cognito/latest/developerguide/amazon-cognito-user-pools-using-tokens-verifying-a-jwt.html" title="Amazon Cognito Docs: Verifying JSON web tokens">AWS</a>. Such
providers might be too heavyweight for demo purposes, however.</p>
<p>For my own use, I needed nothing more than a <a href="https://docs.docker.com/compose/" title="Docker Manuals: Docker Compose">Docker Compose</a> file with
local-only services. I also wanted some control over the contents of the
tokens, since my records the <code>sub</code> field from the <code>JWT</code> in an audit trail, and
something like <code>1a1077e6-3b87-1282-789c-f70e66dab825</code> (as in <a href="https://stackoverflow.com/q/79838080/79202" title="Stack OVerflow: How do I customize core claims in Hashi Vault JWTs?">Vault JWTs</a>)
makes for less-than-friendly text to describe in a demo.</p>
<p>I created <a href="https://github.com/theory/dmjwk" title="dmjwk: Simple OAuth 2 JWK/JWT demo service">dmjwk</a> to scratch this itch. It provides a basic <a href="https://datatracker.ietf.org/doc/html/rfc6749#section-4.3">Resource Owner
Password Credentials Grant</a> OAuth 2 flow to create custom JWTs, a well-known
URL for the public JWK set, and a simple API that validates JWTs. None of it
is real, it&rsquo;s all for show, but the show&rsquo;s the point.</p>
<h2 id="quick-start">Quick Start</h2>
<p>The simplest way to start dmjwk is with its <a href="https://ghcr.io/theory/dmjwk" title="dmjwk OCI Packages">OCI image</a> (there are <a href="https://github.com/theory/dmjwk/releases" title="dmjwk Releases">binaries
for 40 platforms</a>, as well). It starts on port 443, since hosts commonly
reserve that port, let&rsquo;s map it to 4433 instead:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">docker run -d -p 4433:443 --name dmjwk --volume .:/etc/dmjwk ghcr.io/theory/dmjwk
</span></span></code></pre></div><p>This command fires up dmjwk with a self-signed TLS certificate for localhost
and creates a root cert bundle, <code>ca.pem</code>, in the current directory. Use it
with your favorite HTTP client to make validated requests.</p>
<h3 id="jwk-set">JWK Set</h3>
<p>For example, to fetch the JWK set:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">curl -s --cacert ca.pem https://localhost:4433/.well-known/jwks.json
</span></span></code></pre></div><p>By default dmjwk creates a single JWK in the set that looks something like
this (JSON reformatted):</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;keys&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;kty&#34;</span><span class="p">:</span> <span class="s2">&#34;EC&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;crv&#34;</span><span class="p">:</span> <span class="s2">&#34;P-256&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;x&#34;</span><span class="p">:</span> <span class="s2">&#34;Ld98DHMIIanlpdOhYf-8GljNHnxHW_i6Bq0iltw9J98&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;y&#34;</span><span class="p">:</span> <span class="s2">&#34;xxyRGhCFIjdQFD-TAs-y6uf18wsPvkq8wH_FsGY1GyU&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">  <span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>Configure services to use this URL,
<code>https://localhost:4433/.well-known/jwks.json</code>, to to validate JWTs created by
dmjwk.</p>
<h3 id="authorization">Authorization</h3>
<p>To fetch a JWT signed by the first key in the JWK set (just the one in this
example), make an <code>application/x-www-form-urlencoded</code> POST with the required
<code>grant_type</code>, <code>username</code>, and <code>password</code> fields:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl"><span class="nv">form</span><span class="o">=</span><span class="s1">&#39;grant_type=password&amp;username=kamala&amp;password=a2FtYWxh&#39;</span>
</span></span><span class="line"><span class="cl">curl -s --cacert ca.pem -d <span class="s2">&#34;</span><span class="nv">$form</span><span class="s2">&#34;</span> https://localhost:4433/authorization
</span></span></code></pre></div><p>dmjwk stores no actual usernames and passwords; it&rsquo;s all for show. Provide any
username you like and <a href="https://en.wikipedia.org/wiki/Base64" title="Wikipedia: Base64">Base64</a>-encode the username, without trailing equal
signs, as the password.</p>
<p>Example successful response:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;access_token&#34;</span><span class="p">:</span> <span class="s2">&#34;eyJhbGciOiJFUzI1NiIsImtpZCI6IiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJrYW1hbGEiLCJleHAiOjE3NjY5NDQyNzcsImlhdCI6MTc2Njk0MDY3NywianRpIjoiZ3hhNnNib292aTg5dSJ9.04efdORHDA3GIPMnWErMPy4mXXsBfbnMJlzqZsxGVEc2cRvEWI0Mt_IqHDK4RYK_14BCEu2nTMiEPtgwC2IZ5A&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;token_type&#34;</span><span class="p">:</span> <span class="s2">&#34;Bearer&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;expires_in&#34;</span><span class="p">:</span> <span class="mi">3600</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;scope&#34;</span><span class="p">:</span> <span class="s2">&#34;read&#34;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>Parsing the the <code>access_token</code> JWT from the response provides this header:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;alg&#34;</span><span class="p">:</span> <span class="s2">&#34;ES256&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;kid&#34;</span><span class="p">:</span> <span class="s2">&#34;&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;typ&#34;</span><span class="p">:</span> <span class="s2">&#34;JWT&#34;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>And this payload:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;sub&#34;</span><span class="p">:</span> <span class="s2">&#34;kamala&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;exp&#34;</span><span class="p">:</span> <span class="mi">1766944277</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;iat&#34;</span><span class="p">:</span> <span class="mi">1766940677</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;jti&#34;</span><span class="p">:</span> <span class="s2">&#34;gxa6sboovi89u&#34;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>We can further customize its contents by passing any of a few <a href="https://github.com/theory/dmjwk?tab=readme-ov-file#form-fields" title="dmjwk Docs: /authorization Form Fields">additional
parameters</a>. To specify an audience and issuer, for example:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl"><span class="nv">form</span><span class="o">=</span><span class="s1">&#39;grant_type=password&amp;username=kamala&amp;password=a2FtYWxh&amp;iss=spacely+sprockets&amp;aud=cogswell.cogs&#39;</span>
</span></span><span class="line"><span class="cl">curl -s --cacert ca.pem -d <span class="s2">&#34;</span><span class="nv">$form</span><span class="s2">&#34;</span> https://localhost:4433/authorization
</span></span></code></pre></div><p>Which returns something like:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;access_token&#34;</span><span class="p">:</span> <span class="s2">&#34;eyJhbGciOiJFUzI1NiIsImtpZCI6IiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzcGFjZWx5IHNwcm9ja2V0cyIsInN1YiI6ImthbWFsYSIsImF1ZCI6WyJjb2dzd2VsbC5jb2dzIl0sImV4cCI6MTc2NzAzNDIyNCwiaWF0IjoxNzY3MDMwNjI0LCJqdGkiOiIxNXZmaDhzYm41YWFxIn0.IGRdD5HGiWLOXggZhb9zPlLK40WWy8R0-HmSuIhaObD6WEwA2WXIBWg_MqtFFQISKLXrjNDHphXtEJsx6FZBOQ&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;token_type&#34;</span><span class="p">:</span> <span class="s2">&#34;Bearer&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;expires_in&#34;</span><span class="p">:</span> <span class="mi">3600</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;scope&#34;</span><span class="p">:</span> <span class="s2">&#34;read&#34;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>Now the JWT payload is:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;iss&#34;</span><span class="p">:</span> <span class="s2">&#34;spacely sprockets&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;sub&#34;</span><span class="p">:</span> <span class="s2">&#34;kamala&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;aud&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;cogswell.cogs&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">],</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;exp&#34;</span><span class="p">:</span> <span class="mi">1767034206</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;iat&#34;</span><span class="p">:</span> <span class="mi">1767030606</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;jti&#34;</span><span class="p">:</span> <span class="s2">&#34;8ri9vfsg5f8mj&#34;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>This allows customization appropriate for your service, which might determine
authorization based on the contents of the various JWT fields.</p>
<p>A request that fails to authenticate the username and password, e.g.:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl"><span class="nv">form</span><span class="o">=</span><span class="s1">&#39;grant_type=password&amp;username=kamala&amp;password=nope&#39;</span>
</span></span><span class="line"><span class="cl">curl -s --cacert ca.pem -d <span class="s2">&#34;</span><span class="nv">$form</span><span class="s2">&#34;</span> https://localhost:4433/authorization
</span></span></code></pre></div><p>Will return an appropriate response:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;error&#34;</span><span class="p">:</span> <span class="s2">&#34;invalid_request&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;error_description&#34;</span><span class="p">:</span> <span class="s2">&#34;incorrect password&#34;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><h3 id="resource">Resource</h3>
<p>For simple JWT validation, POST a JWT returned from the
<a href="#authorization">authorization</a> API as a <a href="https://datatracker.ietf.org/doc/html/rfc6750" title="RFC 6750 --- The OAuth 2.0 Authorization Framework: Bearer Token Usage">Bearer token</a> to
<code>/resource</code>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl"><span class="nv">tok</span><span class="o">=</span><span class="k">$(</span>curl -s --cacert ca.pem -d <span class="s2">&#34;</span><span class="nv">$form</span><span class="s2">&#34;</span> https://localhost:4433/authorization <span class="p">|</span> jq -r .access_token<span class="k">)</span>
</span></span><span class="line"><span class="cl">curl -s --cacert ca.pem -H <span class="s2">&#34;Authorization: Bearer </span><span class="nv">$tok</span><span class="s2">&#34;</span> https://localhost:4433/resource -d <span class="s1">&#39;HELLO WORLD
</span></span></span><span class="line"><span class="cl"><span class="s1">&#39;</span>
</span></span></code></pre></div><p>The response simply returns the request body:</p>
<pre tabindex="0"><code>HELLO WORLD
</code></pre><p>A request that fails to authenticate, say with an invalid Bearer token:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">curl -s --cacert ca.pem -H <span class="s2">&#34;Authorization: Bearer NOT&#34;</span> https://localhost:4433/resource -d <span class="s1">&#39;HELLO WORLD&#39;</span>
</span></span></code></pre></div><p>Returns an appropriate error response:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;error&#34;</span><span class="p">:</span> <span class="s2">&#34;invalid_token&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;error_description&#34;</span><span class="p">:</span> <span class="s2">&#34;token is malformed: token contains an invalid number of segments&#34;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><h2 id="that-s-it">That&rsquo;s It</h2>
<p>dmjwk includes a fair number of <a href="https://github.com/theory/dmjwk?tab=readme-ov-file#configuration" title="dmjwk: Configuration">configuration options</a>, including external
certificates, custom host naming (useful with <a href="https://docs.docker.com/compose/" title="Docker Manuals: Docker Compose">Docker Compose</a>), and multiple
key generation. If you find it useful for your demos (but not for production
&mdash; <strong>DON&rsquo;T DO THAT</strong>) &mdash; let me know. And if not, that&rsquo;s fine, too. This is
a bit of my pursuit of a <a href="https://www.joanwestenberg.com/thin-desires-are-eating-your-life/" title="JA Westenberg: “Thin Desires Are Eating Your Life”">thick desire</a>, made mainly for me, but it pleases me
if others find it helpful too.</p>

        </div>

        <footer class="tags">
            <h5>More about…</h5>
            <ul>
                <li><a href="https://justatheory.com/tags/oauth/">OAuth</a></li>
                <li><a href="https://justatheory.com/tags/jwt/">JWT</a></li>
                <li><a href="https://justatheory.com/tags/jwk/">JWK</a></li>
                <li><a href="https://justatheory.com/tags/go/">Go</a></li>
                <li><a href="https://justatheory.com/tags/bearer/">Bearer</a></li>
                <li><a href="https://justatheory.com/tags/demo/">Demo</a></li>
            </ul>
        </footer>
    </article>
]]></content>
	</entry>
	<entry>
		<id>https://justatheory.com/2025/12/taming-guc-extra/</id>
		<title type="html"><![CDATA[🐏 Taming PostgreSQL GUC “extra” Data]]></title>
		<link rel="alternate" type="text/html" href="https://clickhouse.com/blog/taming-postgres-guc-extra-data"/>
		<link rel="related" type="text/html" href="https://justatheory.com/2025/12/taming-guc-extra/"/>
		<updated>2025-12-18T18:04:50Z</updated>
		<published>2025-12-18T18:04:50Z</published>
		<author>
			<name>David E. Wheeler</name>
			<email>david@justatheory.com</email>
			<uri>https://justatheory.com/</uri>
		</author>
		<category scheme="https://justatheory.com/tags" term="postgres" label="Postgres"/>
		<category scheme="https://justatheory.com/tags" term="guc" label="GUC"/>
		<category scheme="https://justatheory.com/tags" term="pg_clickhouse" label="pg_clickhouse"/>
		<summary type="html"><![CDATA[For the ClickHouse blog I wrote up learning how to work with C data structures
and memory allocation within the tight constraints of the Postgres &ldquo;GUC&rdquo; API.]]></summary>
		<content type="html" xml:base="https://justatheory.com/" xml:space="preserve">
<![CDATA[    <article class="post">
        <div class="text">
<p>New <a href="https://clickhouse.com/blog/taming-postgres-guc-extra-data" title="Taming PostgreSQL GUC “extra” Data">post</a> up on on the ClickHouse blog:</p>
  
    <blockquote>
      <p>I wanted to optimize away parsing the key/value pairs from the
<a href="https://pgxn.org/dist/pg_clickhouse/" title="pg_clickhouse on PGXN">pg_clickhouse</a> <code>pg_clickhouse.session_settings</code> GUC for every query by
pre-parsing it on assignment and assigning it to a separate variable. It
took a few tries, as the GUC API requires quite specific memory allocation
for extra data to work properly. It took me a few tries to land on a
workable and correct solution.</p>

    </blockquote>
  
<p>Struggling to understand, making missteps, and ultimately coming to a
reasonable design and solution satisfies me so immensely that I always want to
share. This piece gets down in the C coding weeds; my fellow extension coders
might enjoy it.</p>

        </div>

        <footer class="tags">
            <h5>More about…</h5>
            <ul>
                <li><a href="https://justatheory.com/tags/postgres/">Postgres</a></li>
                <li><a href="https://justatheory.com/tags/guc/">GUC</a></li>
                <li><a href="https://justatheory.com/tags/pg_clickhouse/">pg_clickhouse</a></li>
            </ul>
        </footer>
    </article>
]]></content>
	</entry>
	<entry>
		<id>https://justatheory.com/2025/12/pg_clickhouse/</id>
		<title type="html"><![CDATA[Introducing pg_clickhouse]]></title>
		<link rel="alternate" type="text/html" href="https://clickhouse.com/blog/introducing-pg_clickhouse"/>
		<link rel="related" type="text/html" href="https://justatheory.com/2025/12/pg_clickhouse/"/>
		<updated>2025-12-10T16:34:06Z</updated>
		<published>2025-12-10T16:34:06Z</published>
		<author>
			<name>David E. Wheeler</name>
			<email>david@justatheory.com</email>
			<uri>https://justatheory.com/</uri>
		</author>
		<category scheme="https://justatheory.com/tags" term="postgres" label="Postgres"/>
		<category scheme="https://justatheory.com/tags" term="pg_clickhouse" label="pg_clickhouse"/>
		<category scheme="https://justatheory.com/tags" term="clickhouse" label="ClickHouse"/>
		<summary type="html"><![CDATA[Introducing pg_clickhouse, a PostgreSQL extension that runs your analytics
queries on ClickHouse right from PostgreSQL without rewriting any SQL.]]></summary>
		<content type="html" xml:base="https://justatheory.com/" xml:space="preserve">
<![CDATA[    <article class="post">


	<figure class="clear" title="pg_clickhouse: a PostgreSQL extension to run ClickHouse queries from PostgreSQL">
			<img src="https://justatheory.com/2025/12/pg_clickhouse/pg_clickhouse.png" alt="PostgreSQL Logo ⇔ pg_clickhouse ⇔ ClickHouse Logo" title="pg_clickhouse: a PostgreSQL extension to run ClickHouse queries from PostgreSQL" />
	</figure>

        <div class="text">
<p>The <a href="https://clickhouse.com/blog/">ClickHouse blog</a> has a <a href="https://clickhouse.com/blog/introducing-pg_clickhouse" title="Introducing pg_clickhouse: A Postgres extension for querying ClickHouse">posted</a> a piece by yours truly introducing
<a href="https://github.com/clickHouse/pg_clickhouse" title="pg_clickhouse on GitHub">pg_clickhouse</a>, a PostgreSQL extension to run ClickHouse queries from
PostgreSQL:</p>
  
    <blockquote>
      <p>While <a href="https://github.com/ildus/clickhouse_fdw">clickhouse_fdw</a> and its predecessor, <a href="https://www.postgresql.org/docs/current/postgres-fdw.html" title="PostgreSQL Docs: postgres_fdw — access data stored in external PostgreSQL servers">postgres_fdw</a>, provided the
foundation for our FDW, we set out to modernize the code &amp; build process, to
fix bugs &amp; address shortcomings, and to engineer into a complete product
featuring near universal <a href="https://www.postgresql.org/about/featurematrix/detail/postgres_fdw-pushdown/" title="PostgreSQL Features: postgres_fdw pushdown">pushdown</a> for analytics queries and aggregations.</p>
<p>Such advances include:</p>
<ul>
<li>Adopting standard <a href="https://www.postgresql.org/docs/current/extend-pgxs.html" title="PostgreSQL Extension Building Infrastructure">PGXS</a> build pipeline for PostgreSQL extensions</li>
<li>Adding prepared INSERT support to and adopting the latest supported</li>
<li>release of the <a href="https://github.com/clickHouse/clickhouse-cpp/">ClickHouse C++ library</a></li>
<li>Creating test cases and CI <a href="https://github.com/ClickHouse/pg_clickhouse/actions">workflows</a> to ensure it works on PostgreSQL
versions 13-18 and ClickHouse versions 22-25</li>
<li>Support for TLS-based connections for both the <a href="https://clickhouse.com/docs/native-protocol/basics" title="ClickHouse Docs: Native Protocol">binary protocol</a> and the
<a href="https://clickhouse.com/docs/interfaces/http" title="ClickHouse Docs: HTTP Interface">HTTP API</a>, required for <a href="https://clickhouse.com/cloud" title="Serverless. Simple. ClickHouse Cloud.">ClickHouse Cloud</a></li>
<li>Bool, Decimal, and JSON support</li>
<li>Transparent aggregate function pushdown, including for <a href="https://www.postgresql.org/docs/current/functions-aggregate.html#FUNCTIONS-ORDEREDSET-TABLE" title="PostgreSQL Docs: Ordered-Set Aggregate Functions">ordered-set
aggregates</a> like <code>percentile_cont()</code></li>
<li><a href="https://clickhouse.com/blog/clickhouse-fully-supports-joins-part1#left--right-semi-join" title="ClickHouse Blog: (LEFT / RIGHT) SEMI JOIN">SEMI JOIN</a> pushdown</li>
</ul>

    </blockquote>
  
<p>I&rsquo;ve spent most of the last couple months working on this project, learning a
ton about <a href="https://clickhouse.com/clickhouse" title="ClickHouse: The fastest open-source analytical database">ClickHouse</a>, <a href="https://www.postgresql.org/docs/current/fdw-callbacks.html" title="PostgreSQL Docs: Foreign Data Wrapper Callback Routines">foreign data wrappers</a>, C and C++, and query
pushdown. Interested? Try ou the Docker image:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">docker run --name pg_clickhouse -e <span class="nv">POSTGRES_PASSWORD</span><span class="o">=</span>my_pass <span class="se">\
</span></span></span><span class="line"><span class="cl">       -d ghcr.io/clickhouse/pg_clickhouse:18
</span></span><span class="line"><span class="cl">docker <span class="nb">exec</span> -it pg_clickhouse psql -U postgres -c <span class="s1">&#39;CREATE EXTENSION pg_clickhouse&#39;</span>
</span></span></code></pre></div><p>Or install it from <a href="https://pgxn.org/dist/pg_clickhouse/" title="pg_clickhouse on PGXN">PGXN</a> (requires C and C++ build tools, <code>cmake</code>, and the
openssl libs, libcurl, and libuuid):</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">pgxn install pg_clickhouse
</span></span></code></pre></div><p>Or download it and build it yourself from:</p>
<ul>
<li><a href="https://pgxn.org/dist/pg_clickhouse/" title="pg_clickhouse on PGXN">PGXN</a></li>
<li><a href="https://github.com/clickHouse/pg_clickhouse" title="pg_clickhouse on GitHub">GitHub</a></li>
</ul>
<p>Let me know what you think!</p>

        </div>

        <footer class="tags">
            <h5>More about…</h5>
            <ul>
                <li><a href="https://justatheory.com/tags/postgres/">Postgres</a></li>
                <li><a href="https://justatheory.com/tags/pg_clickhouse/">pg_clickhouse</a></li>
                <li><a href="https://justatheory.com/tags/clickhouse/">ClickHouse</a></li>
            </ul>
        </footer>
    </article>
]]></content>
	</entry>
	<entry>
		<id>https://justatheory.com/2025/10/sqitch-1.6.0/</id>
		<title type="html"><![CDATA[Sqitch 1.6.0: Now with ClickHouse!]]></title>
		<link rel="alternate" type="text/html" href="https://justatheory.com/2025/10/sqitch-1.6.0/"/>
		<updated>2025-10-06T22:01:19Z</updated>
		<published>2025-10-06T22:01:19Z</published>
		<author>
			<name>David E. Wheeler</name>
			<email>david@justatheory.com</email>
			<uri>https://justatheory.com/</uri>
		</author>
		<category scheme="https://justatheory.com/tags" term="sqitch" label="Sqitch"/>
		<category scheme="https://justatheory.com/tags" term="clickhouse" label="ClickHouse"/>
		<summary type="html"><![CDATA[Sqitch 1.6.0, out today, adds support for managing ClickHouse databases.]]></summary>
		<content type="html" xml:base="https://justatheory.com/" xml:space="preserve">
<![CDATA[    <article class="post">


	<figure class="clear" title="Sqitch: Sensible database change management for ClickHouse">
			<img src="https://justatheory.com/2025/10/sqitch-1.6.0/sqitch-clickhouse.png" alt="ClickHouse Logo, A ❤️, Sqitch Logo" title="Sqitch: Sensible database change management for ClickHouse" />
	</figure>

        <div class="text">
<p>Out today: <a href="https://sqitch.org" title="Sqitch: Sensible database change management">Sqitch</a> v1.6.0. This release adds a brand new engine:
<a href="https://clickhouse.com/clickhouse" title="Real-Time Data Analytics Platform | ClickHouse">ClickHouse</a>. I started a new job at ClickHouse on September 2, and my first
task, as a way to get to know the database, was to add it to Sqitch.
Fortuitously, ClickHouse added support for updates and deletes, which Sqitch
requires, in the August release. Sqitch v1.6.0 therefore supports ClickHouse
25.8 or later.</p>
<p>As for the other engines Sqitch supports, this release includes a <a href="https://sqitch.org/docs/manual/sqitchtutorial-clickhouse/" title="Sqitch ClickHouse Tutorial">ClickHouse
tutorial</a>, the <code>--with-clickhouse-support</code> option in the <a href="https://github.com/sqitchers/homebrew-sqitch">Homebrew
tap</a>, and <a href="https://hub.docker.com/r/sqitch/sqitch/tags?name=clickhouse" title="Sqitch ClickHouse Tags on Docker Hub">Sqitch ClickHouse Docker tags</a>.</p>
<p>Find it in the usual places:</p>
<ul>
<li><a href="https://sqitch.org" title="Sqitch: Sensible database change management">sqitch.org</a></li>
<li><a href="https://github.com/sqitchers/sqitch">GitHub</a></li>
<li><a href="https://metacpan.org/dist/App-Sqitch">CPAN</a></li>
<li><a href="https://hub.docker.com/r/sqitch/sqitch">Docker</a></li>
<li><a href="https://github.com/sqitchers/homebrew-sqitch">Homebrew</a></li>
</ul>
<p>Thanks for using Sqitch, and <a href="/about" title="About Just a Theory">do let me know</a> if you use it to manage a
ClickHouse database, or if you run into any issues or challenges.</p>

        </div>

        <footer class="tags">
            <h5>More about…</h5>
            <ul>
                <li><a href="https://justatheory.com/tags/sqitch/">Sqitch</a></li>
                <li><a href="https://justatheory.com/tags/clickhouse/">ClickHouse</a></li>
            </ul>
        </footer>
    </article>
]]></content>
	</entry>
	<entry>
		<id>https://justatheory.com/2025/05/pg_module_magic_ext/</id>
		<title type="html"><![CDATA[Postgres Extensions: Use PG_MODULE_MAGIC_EXT]]></title>
		<link rel="alternate" type="text/html" href="https://justatheory.com/2025/05/pg_module_magic_ext/"/>
		<updated>2025-05-29T22:09:22Z</updated>
		<published>2025-05-29T22:09:22Z</published>
		<author>
			<name>David E. Wheeler</name>
			<email>david@justatheory.com</email>
			<uri>https://justatheory.com/</uri>
		</author>
		<category scheme="https://justatheory.com/tags" term="postgres" label="Postgres"/>
		<category scheme="https://justatheory.com/tags" term="extensions" label="Extensions"/>
		<category scheme="https://justatheory.com/tags" term="pg_module_magic_ext" label="PG_MODULE_MAGIC_EXT"/>
		<summary type="html"><![CDATA[Details for extension authors for how to use the new <code>PG_MODULE_MAGIC_EXT</code>
macro introduced in PostgreSQL 18.]]></summary>
		<content type="html" xml:base="https://justatheory.com/" xml:space="preserve">
<![CDATA[    <article class="post">
        <div class="text">
<p>A quick note for PostgreSQL extension maintainers: PostgreSQL 18 introduces a
new macro: <code>PG_MODULE_MAGIC_EXT</code>. Use it to name and version your modules.
Where your module <code>.c</code> file likely has:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="n">PG_MODULE_MAGIC</span><span class="p">;</span>
</span></span></code></pre></div><p>Or:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="cp">#ifdef PG_MODULE_MAGIC
</span></span></span><span class="line"><span class="cl"><span class="n">PG_MODULE_MAGIC</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="cp">#endif
</span></span></span></code></pre></div><p>Change it to something like:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="cp">#ifdef PG_MODULE_MAGIC_EXT
</span></span></span><span class="line"><span class="cl"><span class="nf">PG_MODULE_MAGIC_EXT</span><span class="p">(.</span><span class="n">name</span> <span class="o">=</span> <span class="s">&#34;module_name&#34;</span><span class="p">,</span> <span class="p">.</span><span class="n">version</span> <span class="o">=</span> <span class="s">&#34;1.2.3&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="cp">#else
</span></span></span><span class="line"><span class="cl"><span class="n">PG_MODULE_MAGIC</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="cp">#endif
</span></span></span></code></pre></div><p>Replace the name of your module and the version as appropriate. Note that
<code>PG_MODULE_MAGIC</code> was added in Postgres 8.2; if for some reason your module
still supports earlier versions, use a nested <code>#ifdef</code> to conditionally
execute it:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="cp">#ifdef PG_MODULE_MAGIC_EXT
</span></span></span><span class="line"><span class="cl"><span class="nf">PG_MODULE_MAGIC_EXT</span><span class="p">(.</span><span class="n">name</span> <span class="o">=</span> <span class="s">&#34;module_name&#34;</span><span class="p">,</span> <span class="p">.</span><span class="n">version</span> <span class="o">=</span> <span class="s">&#34;1.2.3&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="cp">#else
</span></span></span><span class="line"><span class="cl"><span class="cp">#ifdef PG_MODULE_MAGIC
</span></span></span><span class="line"><span class="cl"><span class="n">PG_MODULE_MAGIC</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="cp">#endif
</span></span></span><span class="line"><span class="cl"><span class="cp">#endif
</span></span></span></code></pre></div><p>If you manage the module version in your <code>Makefile</code>, as the <a href="https://manager.pgxn.org/howto#neworder">PGXN Howto
suggests</a>, consider renaming the <code>.c</code> file to <code>.c.in</code> and changing the
<code>Makefile</code> like so:</p>
<ul>
<li>
<p>Replace <code>.version = &quot;1.2.3&quot;</code> with <code>.version = &quot;__VERSION__&quot;</code></p>
</li>
<li>
<p>Add <code>src/$(EXTENSION).c</code> to <code>EXTRA_CLEAN</code></p>
</li>
<li>
<p>Add this <code>make</code> target:</p>
<pre tabindex="0"><code>src/$(EXTENSION).c: src/$(EXTENSION).c.in
	sed -e &#39;s,__VERSION__,$(EXTVERSION),g&#39; $&lt; &gt; $@
</code></pre></li>
<li>
<p>If you use Git, add <code>/src/*.c</code> to <code>.gitignore</code></p>
</li>
</ul>
<p>For an example of this pattern, see <a href="https://github.com/theory/pg-semver/commit/3526789">semver@3526789</a>.</p>
<p>That&rsquo;s all!</p>

        </div>

        <footer class="tags">
            <h5>More about…</h5>
            <ul>
                <li><a href="https://justatheory.com/tags/postgres/">Postgres</a></li>
                <li><a href="https://justatheory.com/tags/extensions/">Extensions</a></li>
                <li><a href="https://justatheory.com/tags/pg_module_magic_ext/">PG_MODULE_MAGIC_EXT</a></li>
            </ul>
        </footer>
    </article>
]]></content>
	</entry>
	<entry>
		<id>https://justatheory.com/2025/05/extension-packaging-adventures/</id>
		<title type="html"><![CDATA[Adventures in Extension Packaging]]></title>
		<link rel="alternate" type="text/html" href="https://justatheory.com/2025/05/extension-packaging-adventures/"/>
		<updated>2025-06-14T15:32:03Z</updated>
		<published>2025-05-22T17:31:09Z</published>
		<author>
			<name>David E. Wheeler</name>
			<email>david@justatheory.com</email>
			<uri>https://justatheory.com/</uri>
		</author>
		<category scheme="https://justatheory.com/tags" term="postgres" label="Postgres"/>
		<category scheme="https://justatheory.com/tags" term="extensions" label="Extensions"/>
		<category scheme="https://justatheory.com/tags" term="packaging" label="Packaging"/>
		<category scheme="https://justatheory.com/tags" term="pgconf.dev" label="PGConf.dev"/>
		<category scheme="https://justatheory.com/tags" term="cloudnativepg" label="CloudNativePG"/>
		<summary type="html"><![CDATA[Narrative version of a PGConf.dev talk covering the many issues I stumbled
upon while designing a universal packaging format for PostgreSQL extensions,
maintaining pgt.dev packages, and experimenting with CloudNativePG
immutability.]]></summary>
		<content type="html" xml:base="https://justatheory.com/" xml:space="preserve">
<![CDATA[    <article class="post">
        <div class="text">
<p>I gave a presentation at <a href="https://2025.pgconf.dev" title="PostgreSQL Development Conference 2025">PGConf.dev</a> last week, <a href="https://www.pgevents.ca/events/pgconfdev2025/schedule/session/331-adventures-in-extension-packaging/">Adventures in Extension
Packaging</a>. It summarizes stuff I learned in the past year in developing
the <a href="https://github.com/pgxn/rfcs/pull/3" title="RFC: Meta Spec v2">PGXN Meta v2 RFC</a>, re-packaging all of the extensions on
<a href="https://pgt.dev" title="Trunk: A Postgres Extension Registry">pgt.dev</a>, and experimenting with the <a href="https://cloudnative-pg.io" title="Run PostgreSQL. The Kubernetes way.">CloudNativePG</a> community&rsquo;s
<a href="https://www.gabrielebartolini.it/articles/2025/03/the-immutable-future-of-postgresql-extensions-in-kubernetes-with-cloudnativepg/" title="The Immutable Future of PostgreSQL Extensions in Kubernetes with CloudNativePG">proposal</a> to mount extension OCI images in immutable PostgreSQL
containers.</p>
<p>Turns out a ton of work and experimentation remains to be done.</p>
<ul>
<li><a href="https://www.youtube.com/watch?v=Xfjq6yU0W2I" title="Adventures in Extension Packaging (PGConf.dev 2025)">Video</a></li>
<li><a href="https://justatheory.com/2025/05/extension-packaging-adventures/adventures-extension-packaging.pdf">Slides</a></li>
</ul>
<p><object
data="https://justatheory.com/2025/05/extension-packaging-adventures/adventures-extension-packaging.pdf"
class="slides-wide"
type="application/pdf"
title="Adventures in Extension Packaging">
</object></p>
<p>Previous work covers the first half of the talk, including:</p>
<ul>
<li>A brief introduction to <a href="http://pgxn.org/" title="PostgreSQL Extension Network">PGXN</a>, borrowing from the <a href="https://justatheory.com/2025/03/mini-summit-one/#state-of-the-extensions-ecosystem">State of the
Extensions Ecosystem</a></li>
<li>The metadata designed to enable automated packaging of extensions added to
the <a href="https://github.com/pgxn/rfcs/pull/3" title="RFC: Meta Spec v2">PGXN Meta v2 RFC</a></li>
<li>The <a href="https://justatheory.com/2024/06/trunk-poc/" title="POC: PGXN Binary Distribution Format">Trunk Packaging Format</a>, a.k.a., <a href="https://github.com/pgxn/rfcs/pull/2">PGXN RFC 2</a></li>
<li><a href="https://justatheory.com/2024/06/trunk-oci-poc/" title="POC: Distributing Trunk Binaries via OCI">OCI distribution</a> of Trunk packages</li>
</ul>
<p>The rest of the talk encompasses newer work. Read on for details.</p>
<h2 id="automated-packaging-challenges">Automated Packaging Challenges</h2>
<p>Back in December I took over maintenance of the <a href="https://pgt.dev" title="Trunk: A Postgres Extension Registry">Trunk registry</a>,
a.k.a., <a href="https://pgt.dev" title="Trunk: A Postgres Extension Registry">pgt.dev</a>, refactoring and upgrading all 200+ extensions and adding
Postgres 17 builds. This experience opened my eyes to the wide variety of
extension build patterns and configurations, even when supporting a single OS
(Ubuntu 22.04 &ldquo;Jammy&rdquo;). Some examples:</p>
<ul>
<li><a href="https://github.com/tembo-io/trunk/blob/5f3de6d/contrib/pg_search/Dockerfile#L18">pglogical requires</a> an extra <code>make</code> param to build on PostgreSQL 17:
<code>make -C LDFLAGS_EX=&quot;-L/usr/lib/postgresql/17/lib&quot;</code></li>
<li>Some <a href="https://github.com/pgcentralfoundation/pgrx" title="pgrx: Build Postgres Extensions with Rust!">pgrx</a> extensions require additional params, for example:
<ul>
<li><a href="https://github.com/tembo-io/trunk/blob/5f3de6d/contrib/pg_search/Dockerfile#L18">pg_search needs</a> the <code>--features</code> flag to enable icu</li>
<li><a href="https://github.com/tembo-io/trunk/blob/5f3de6d/contrib/vectorscale/Dockerfile#L25">vectorscale requires</a> the environment variable
<code>RUSTFLAGS=&quot;-C target-feature=+avx2,+fma&quot;</code></li>
</ul>
</li>
<li><a href="https://github.com/tembo-io/trunk/blob/5f3de6d/contrib/pljava/Dockerfile#L18">pljava needs</a> a pointer to <code>libjvm</code>:
<code>mvn clean install -Dpljava.libjvmdefault=/usr/lib/x86_64-linux-gnu/libjvm.so</code></li>
<li><a href="https://github.com/tembo-io/trunk/blob/5f3de6d/contrib/plrust/Dockerfile#L41-L46">plrust needs</a> files to be moved around, a shell script to be run, and to
be built from a subdirectory</li>
<li><a href="https://github.com/tembo-io/trunk/blob/39b385d/contrib/postgresbson/Dockerfile#L15-L18">bson also needs</a> files to be moved around and a pointer to <code>libbson</code></li>
<li><a href="https://github.com/tembo-io/trunk/blob/39b385d/contrib/timescaledb/Dockerfile#L14">timescale requires</a> an environment variable and shell script to run
before building</li>
<li>Many extensions require patching to build for various configurations and
OSes, like <a href="https://github.com/tembo-io/trunk/blob/39b385d/contrib/pguri/Dockerfile#L15">this tweak</a> to build <a href="https://github.com/petere/pguri">pguri</a> on Postgres 17 and <a href="https://github.com/tembo-io/trunk/blob/39b385d/contrib/duckdb_fdw/Dockerfile#L18">this patch</a>
to get <a href="https://github.com/alitrack/duckdb_fdw/">duckdb_fdw</a> to build at all</li>
</ul>
<p>Doubtless there&rsquo;s much more. These sorts of challenges led the RPM and APT
packaging systems to support explicit scripting and patches for every package.
I don&rsquo;t think it would be sensible to support build scripting in the <a href="https://github.com/pgxn/rfcs/pull/3" title="RFC: Meta Spec v2">meta
spec</a>.</p>
<p>However, the <a href="https://github.com/pgxn/meta">PGXN meta SDK</a> I developed last year supports the merging of
multiple <code>META.json</code> files, so that downstream packagers could maintain files
with additional configurations, including explicit build steps or lists of
packages, to support these use cases.</p>
<p>Furthermore, the plan to add reporting to PGXN v2 means that downstream
packages could report build failures, which would appear on PGXN, where they&rsquo;d
encourage some maintainers, at least, to fix issues within their control.</p>
<h2 id="dependency-resolution">Dependency Resolution</h2>
<p>Dependencies present another challenge. The <a href="https://github.com/pgxn/rfcs/pull/3" title="RFC: Meta Spec v2">v2 spec</a> supports third
party dependencies &mdash; those not part of Postgres itself or the ecosystem of
extensions. Ideally, an extension like <a href="https://github.com/petere/pguri">pguri</a> would define its dependence on
the <a href="https://uriparser.github.io">uriparser</a> library like so:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;dependencies&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;postgres&#34;</span><span class="p">:</span> <span class="p">{</span> <span class="nt">&#34;version&#34;</span><span class="p">:</span> <span class="s2">&#34;&gt;= 9.3&#34;</span> <span class="p">},</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;packages&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;build&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;requires&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">          <span class="nt">&#34;pkg:generic/uriparser&#34;</span><span class="p">:</span> <span class="mi">0</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">      <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>An intelligent build client will parse the dependencies, provided as <a href="https://github.com/package-url/purl-spec" title="purl-spec: A minimal specification for purl a.k.a. a package “mostly universal” URL">purl</a>s,
to determine the appropriate OS packages to install to satisfy. For example,
building on a Debian-based system, it would know to install <code>liburiparser-dev</code>
to build the extension and require <code>liburiparser1</code> to run it.</p>
<p>With the aim to support multiple OSes and versions &mdash; not to mention Postgres
versions &mdash; the proposed PGXN binary registry would experience quite the
combinatorial explosion to support all possible dependencies on all possible
OSes and versions. While I propose to start simple (Linux and macOS, Postgres
14-18) and gradually grow, it could quickly get quite cumbersome.</p>
<p>So much so that I can practically hear <a href="https://www.df7cb.de" title="Christoph Berg">Christoph</a>&rsquo;s and <a href="https://people.planetpostgresql.org/devrim/" title="Devrim Gündüz">Devrim</a>&rsquo;s reactions
from here:</p>


	<figure title="Photo of Ronald Reagan and his team laughing uproariously with the white Impact Bold-style meme text at the top that reads, “AND THEN HE SAID…”, followed by large text at the bottom that reads, “WE’LL PACKAGE EVERY EXTENSION FOR EVERY PLATFORM!”">
			<img src="https://justatheory.com/2025/05/extension-packaging-adventures/laughing-1.jpeg" alt="Photo of Ronald Reagan and his team laughing uproariously with the white Impact Bold-style meme text at the top that reads, “AND THEN HE SAID…”, followed by large text at the bottom that reads, “WE’LL PACKAGE EVERY EXTENSION FOR EVERY PLATFORM!”" />
		<figcaption>
			<p>Photo of Christoph, Devrim, and other long-time packagers laughing at me.</p>
		</figcaption>
	</figure>

<p>Or perhaps:</p>


	<figure title="Photo of two German shepherds looking at a pink laptop and appearing to laugh hysterically, with the white Impact Bold-style meme text at the top that reads, “AND THEN HE SAID…”, followed by large text at the bottom that reads, “UPSTREAM MAINTAINERS WILL FIX BUILD FAILURES!”">
			<img src="https://justatheory.com/2025/05/extension-packaging-adventures/laughing-2.jpeg" alt="Photo of two German shepherds looking at a pink laptop and appearing to laugh hysterically, with the white Impact Bold-style meme text at the top that reads, “AND THEN HE SAID…”, followed by large text at the bottom that reads, “UPSTREAM MAINTAINERS WILL FIX BUILD FAILURES!”" />
		<figcaption>
			<p>Photo of Christoph and Devrim laughing at me.</p>
		</figcaption>
	</figure>

<p>I hardly blame them.</p>
<h2 id="a-cloudnativepg-side-quest">A CloudNativePG Side Quest</h2>
<p><a href="https://www.gabrielebartolini.it">Gabriele Bartolini</a> blogged the <a href="https://www.gabrielebartolini.it/articles/2025/03/the-immutable-future-of-postgresql-extensions-in-kubernetes-with-cloudnativepg/" title="The Immutable Future of PostgreSQL Extensions in Kubernetes with CloudNativePG">proposal</a> to deploy extensions to
<a href="https://cloudnative-pg.io" title="Run PostgreSQL. The Kubernetes way.">CloudNativePG</a> containers without violating the immutability of the
container. The introduction of the <a href="https://github.com/postgres/postgres/commit/4f7f7b0"><code>extension_control_path</code></a> GUC in Postgres
18 and the <a href="https://kubernetes.io/docs/concepts/storage/volumes/#image">ImageVolume</a> feature in Kubernetes 1.33 enable the pattern, likely
to be introduced in CloudNativePG v1.27. Here&rsquo;s a sample CloudNativePG cluster
manifest with the proposed extension configuration:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">apiVersion</span><span class="p">:</span><span class="w"> </span><span class="l">postgresql.cnpg.io/v1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">kind</span><span class="p">:</span><span class="w"> </span><span class="l">Cluster</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">metadata</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">postgresql-with-extensions</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">spec</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">instances</span><span class="p">:</span><span class="w"> </span><span class="m">1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">imageName</span><span class="p">:</span><span class="w"> </span><span class="l">ghcr.io/cloudnative-pg/postgresql-trunk:18-devel</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">postgresql</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">extensions</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">vector</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">image</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">reference</span><span class="p">:</span><span class="w"> </span><span class="l">ghcr.io/cloudnative-pg/pgvector-18-testing</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">storage</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">storageClass</span><span class="p">:</span><span class="w"> </span><span class="l">standard</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">size</span><span class="p">:</span><span class="w"> </span><span class="l">1Gi</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p>The <code>extensions</code> object at lines 9-12 configures <a href="https://pgxn.org/dist/vector/">pgvector</a> simply by
referencing an <a href="https://opencontainers.org" title="Open Container Initiative">OCI</a> image that contains nothing but the files for the
extension. To &ldquo;install&rdquo; the extension, the <a href="https://github.com/cloudnative-pg/cloudnative-pg/pull/6546" title="cloudnative-pg/cloudnative-pg#6546: feat: add support for configuring PostgreSQL extensions via Image Volume">proposed patch</a> triggers a rolling
update, replicas first. For each instance, it takes the following steps:</p>
<ul>
<li>
<p>Mounts each extension as a read-only <a href="https://kubernetes.io/docs/concepts/storage/volumes/#image">ImageVolume</a> under <code>/extensions</code>; in
this example, <code>/extensions/vector</code> provides the complete contents of the
image</p>
</li>
<li>
<p>Updates <code>LD_LIBRARY_PATH</code> to include the path to the <code>lib</code> directory of
the each extension, e.g., <code>/extensions/vector/lib</code>.</p>
</li>
<li>
<p>Updates the <code>extension_control_path</code> and <a href="https://www.postgresql.org/docs/crrent/runtime-config-client.html#GUC-DYNAMIC-LIBRARY-PATH"><code>dynamic_library_path</code></a> GUCs to
point to the <code>share</code> and <code>lib</code> directories of each extension, in this
example:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ini" data-lang="ini"><span class="line"><span class="cl"><span class="na">extension_control_path</span> <span class="o">=</span> <span class="s">&#39;$system:/extensions/vector/share&#39;</span>
</span></span><span class="line"><span class="cl"><span class="na">dynamic_library_path</span>   <span class="o">=</span> <span class="s">&#39;$libdir:/extensions/vector/lib&#39;</span>
</span></span></code></pre></div></li>
</ul>
<p>This works! Alas, the pod restart is absolutely necessary, whether or not any
extension requires it,<sup id="fnref:1"><a href="#fn:1" class="footnote-ref" rel="footnote">1</a></sup>, because:</p>
<ul>
<li>Kubernetes resolves volume mounts, including <a href="https://kubernetes.io/docs/concepts/storage/volumes/#image">ImageVolume</a>s, at pod
startup</li>
<li>The <code>dynamic_library_path</code> and <code>extension_control_path</code> GUCs require a
Postgres restart</li>
<li>Each extension requires another path to be appended to both of these GUCs,
as well as the <code>LD_LIBRARY_PATH</code></li>
</ul>
<p>Say we wanted to use five extensions. The <code>extensions</code> part of the manifest
would look something like this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">extensions</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">vector</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">image</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">reference</span><span class="p">:</span><span class="w"> </span><span class="l">ghcr.io/cloudnative-pg/pgvector-18-testing</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">semver</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">image</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">reference</span><span class="p">:</span><span class="w"> </span><span class="l">ghcr.io/example/semver:0.40.0</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">auto_explain</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">image</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">reference</span><span class="p">:</span><span class="w"> </span><span class="l">ghcr.io/example/auto_explain:18</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">bloom</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">image</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">reference</span><span class="p">:</span><span class="w"> </span><span class="l">ghcr.io/example/bloom:18</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">postgis</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">image</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">reference</span><span class="p">:</span><span class="w"> </span><span class="l">ghcr.io/example/postgis:18</span><span class="w">
</span></span></span></code></pre></div><p>To support this configuration, CNPG must configure the GUCs like so:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ini" data-lang="ini"><span class="line"><span class="cl"><span class="na">extension_control_path</span> <span class="o">=</span> <span class="s">&#39;$system:/extensions/vector/share:/extensions/semver/share:/extensions/auto_explain/share:/extensions/bloom/share:/extensions/postgis/share&#39;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="na">dynamic_library_path</span>   <span class="o">=</span> <span class="s">&#39;$libdir:/extensions/vector/lib:/extensions/semver/lib:/extensions/auto_explain/lib:/extensions/bloom/lib:/extensions/postgis/lib&#39;</span>
</span></span></code></pre></div><p>And also <code>LD_LIBRARY_PATH</code>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl"><span class="nv">LD_LIBRARY_PATH</span><span class="o">=</span><span class="s2">&#34;</span><span class="nv">$LD_LIBRARY_PATH</span><span class="s2">:/extensions/vector/lib:/extensions/semver/lib:/extensions/auto_explain/lib:/extensions/&#34;</span>
</span></span></code></pre></div><p>In other words, every additional extension requires another prefix to be
appended to each of these configurations. Ideally we could use a single prefix
for all extensions, avoiding the need to update these configs and therefore to
restart Postgres. Setting aside the <a href="https://kubernetes.io/docs/concepts/storage/volumes/#image">ImageVolume</a> limitation<sup id="fnref:2"><a href="#fn:2" class="footnote-ref" rel="footnote">2</a></sup> for the
moment, this pattern would require no rolling restarts and no GUC updates
unless a newly-added extension requires pre-loading via
<a href="https://www.postgresql.org/docs/17/runtime-config-client.html#GUC-SHARED-PRELOAD-LIBRARIES"><code>shared_preload_libraries</code></a>.</p>
<p>Getting there, however, requires a different extension file layout than
PostgreSQL currently uses.</p>
<h2 id="rfc-extension-packaging-and-lookup">RFC: Extension Packaging and Lookup</h2>
<p>Imagine this:</p>
<ul>
<li>A single extension search path GUC</li>
<li>Each extension in its own eponymous directory</li>
<li>Pre-defined subdirectory names used inside each extension directory</li>
</ul>
<p>The search path might look something like:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ini" data-lang="ini"><span class="line"><span class="cl"><span class="na">extension_search_path</span> <span class="o">=</span> <span class="s">&#39;$system:/extensions:/usr/local/extensions&#39;</span>
</span></span></code></pre></div><p>Looking at one of these directories, <code>/extensions</code>, its contents would be
extension directories:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-console" data-lang="console"><span class="line"><span class="cl"><span class="go">❯ ls -1 extensions
</span></span></span><span class="line"><span class="cl"><span class="go">auto_explain
</span></span></span><span class="line"><span class="cl"><span class="go">bloom
</span></span></span><span class="line"><span class="cl"><span class="go">postgis
</span></span></span><span class="line"><span class="cl"><span class="go">semver
</span></span></span><span class="line"><span class="cl"><span class="go">vector
</span></span></span></code></pre></div><p>And the contents of one these extension directories would be something like:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-console" data-lang="console"><span class="line"><span class="cl"><span class="go">❯ tree extensions/semver
</span></span></span><span class="line"><span class="cl"><span class="go">extensions/semver
</span></span></span><span class="line"><span class="cl"><span class="go">├── doc
</span></span></span><span class="line"><span class="cl"><span class="go">│   └── semver.md
</span></span></span><span class="line"><span class="cl"><span class="go">├── lib
</span></span></span><span class="line"><span class="cl"><span class="go">│   └── semver.so
</span></span></span><span class="line"><span class="cl"><span class="go">├── semver.control
</span></span></span><span class="line"><span class="cl"><span class="go">└── sql
</span></span></span><span class="line"><span class="cl"><span class="go">    ├── semver--0.31.0--0.31.1.sql
</span></span></span><span class="line"><span class="cl"><span class="go">    ├── semver--0.31.1--0.31.2.sql
</span></span></span><span class="line"><span class="cl"><span class="go">    ├── semver--0.31.2--0.32.0.sql
</span></span></span><span class="line"><span class="cl"><span class="go">    └── semver--0.5.0--0.10.0.sql
</span></span></span></code></pre></div><p>For this pattern, Postgres would look for the appropriately-named
directory with a control file in each of the paths. To find the <code>semver</code>
extension, for example, it would find <code>/extensions/semver/semver.control</code>.</p>
<p>All the other files for the extension would live in specifically-named
subdirectories: <code>doc</code> for documentation files, <code>lib</code> for shared libraries,
<code>sql</code> for SQL deployment files, plus <code>bin</code>, <code>man</code>, <code>html</code>, <code>include</code>,
<code>locale</code>, and any other likely resources.</p>
<p>With all of the files required for an extension bundled into well-defined
subdirectories of a single directory, it lends itself to the layout of the
proposed <a href="https://github.com/pgxn/rfcs/pull/2">binary distribution format</a>. Couple it with <a href="https://justatheory.com/2024/06/trunk-oci-poc/" title="POC: Distributing Trunk Binaries via OCI">OCI
distribution</a> and it becomes a natural fit for <a href="https://kubernetes.io/docs/concepts/storage/volumes/#image">ImageVolume</a> deployment:
simply map each extension OCI image to a subdirectory of the desired search
path and you&rsquo;re done. The <code>extensions</code> object in the CNPG Cluster manifest
remains unchanged, and CNPG no longer needs to manipulate any GUCs.</p>
<p>Some might recognize this proposal from a <a href="https://justatheory.com/2024/11/rfc-extension-packaging-lookup/" title="RFC: Extension Packaging &amp; Lookup">previous RFC post</a>. It not only
simplifies the CloudNativePG use cases, but because it houses all of the files
for an extension in a single bundle, it also vastly simplifies installation
on any system:</p>
<ol>
<li>Download the extension package</li>
<li>Validate its signature &amp; contents</li>
<li>Unpack its contents into a directory named for the extension in the
extension search path</li>
</ol>
<p>Simple!</p>
<h2 id="fun-with-dependencies">Fun With Dependencies</h2>
<p>Many extensions depend on external libraries, and rely on the OS to find them.
OS packagers follow the dependency patterns of their packaging systems:
require the installation of other packages to satisfy the dependencies.</p>
<p>How could a pattern be generalized by the <a href="https://justatheory.com/2024/06/trunk-poc/" title="POC: PGXN Binary Distribution Format">Trunk Packaging Format</a> to work on
all OSes? I see two potential approaches:</p>
<ol>
<li>List the dependencies as <a href="https://github.com/package-url/purl-spec" title="purl-spec: A minimal specification for purl a.k.a. a package “mostly universal” URL">purl</a>s that the installing client translates to
the appropriate OS packages it installs.</li>
<li>Bundle dependencies in the Trunk package itself</li>
</ol>
<p>Option 1 will work well for most use cases, but not immutable systems like
<a href="https://cloudnative-pg.io" title="Run PostgreSQL. The Kubernetes way.">CloudNativePG</a>. Option 2 could work for such situations. But perhaps you
noticed the omission of <code>LD_LIBRARY_PATH</code> manipulation in the packaging and
lookup discussion above. Setting aside the multitude of reasons to avoid
<code>LD_LIBRARY_PATH</code><sup id="fnref:3"><a href="#fn:3" class="footnote-ref" rel="footnote">3</a></sup>, how else could the OS find shared libraries needed by
an extension?</p>
<p>Typically, one installs shared libraries in one of a few directories known to
tools like <a href="https://www.man7.org/linux/man-pages/man8/ldconfig.8.html">ldconfig</a>, which must run after each install to cache their
locations. But one cannot rely on <code>ldconfig</code> in immutable environments,
because the cache of course cannot be mutated.</p>
<p>We could, potentially, rely on <a href="https://en.wikipedia.org/wiki/Rpath" title="Wikipedia: rpath">rpath</a>, a feature of modern dynamic linkers
that reads a list of known paths from the header of a binary file. In fact,
most modern OSes <a href="https://lekensteyn.nl/rpath.html" title="RPATH support">support</a> <code>$ORIGIN</code> as an <code>rpath</code> value<sup id="fnref:4"><a href="#fn:4" class="footnote-ref" rel="footnote">4</a></sup> (or
<code>@loader_path</code> on Darwin/macOS), which refers to the same directory in which
the binary file appears. Imagine this pattern:</p>
<ul>
<li>The Trunk package for an extension includes dependency libraries alongside
the extension module</li>
<li>The module is compiled with <code>rpath=$ORIGIN</code></li>
</ul>
<p>To test this pattern, let&rsquo;s install the Postgres 18 beta and try the pattern
with the <a href="https://github.com/petere/pguri">pguri</a> extension. First, remove the <code>$libdir/</code> prefix (as <a href="https://justatheory.com/2025/04/update-control/" title="Update Your Control Files">discussed
previously</a>) and patch the extension for Postgres 17+:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">perl -i -pe <span class="s1">&#39;s{\$libdir/}{}&#39;</span> pguri/uri.control pguri/*.sql
</span></span><span class="line"><span class="cl">perl -i -pe <span class="s1">&#39;s/^(PG_CPPFLAGS.+)/$1 -Wno-int-conversion/&#39;</span> pguri/Makefile
</span></span></code></pre></div><p>Then compile it with <code>CFLAGS</code> to set <code>rpath</code> and install it with a <code>prefix</code>
parameter:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">make <span class="nv">CFLAGS</span><span class="o">=</span><span class="s1">&#39;-Wl,-rpath,\$$ORIGIN&#39;</span>
</span></span><span class="line"><span class="cl">make install <span class="nv">prefix</span><span class="o">=</span>/usr/local/postgresql
</span></span></code></pre></div><p>With the module installed, move the <code>liburiparser</code> shared library from OS
packaging to the <code>lib</code> directory under the prefix, resulting in these
contents:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-console" data-lang="console"><span class="line"><span class="cl"><span class="go">❯ ls -1 /usr/local/postgresql/lib
</span></span></span><span class="line"><span class="cl"><span class="go">liburiparser.so.1
</span></span></span><span class="line"><span class="cl"><span class="go">liburiparser.so.1.0.30
</span></span></span><span class="line"><span class="cl"><span class="go">uri.so
</span></span></span></code></pre></div><p>The <a href="https://linux.die.net/man/1/chrpath">chrpath</a> utility shows that the extension module, <code>uri.so</code>, has its
<code>RUNPATH</code> (the modern implementation of <code>rparth</code>) properly configured:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-console" data-lang="console"><span class="line"><span class="cl"><span class="go">❯ chrpath /usr/local/postgresql/lib/uri.so 
</span></span></span><span class="line"><span class="cl"><span class="go">uri.so: RUNPATH=$ORIGIN
</span></span></span></code></pre></div><p>Will the OS be able to find the dependency? Use <a href="https://linux.die.net/man/1/ldd">ldd</a> to find out:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-console" data-lang="console"><span class="line"><span class="cl"><span class="go">❯ ldd /usr/local/postgresql/lib/uri.so 
</span></span></span><span class="line"><span class="cl"><span class="go">	linux-vdso.so.1
</span></span></span><span class="line"><span class="cl"><span class="go">	liburiparser.so.1 =&gt; /usr/local/postgresql/lib/liburiparser.so.1
</span></span></span><span class="line"><span class="cl"><span class="go">	libc.so.6 =&gt; /lib/aarch64-linux-gnu/libc.so.6
</span></span></span><span class="line"><span class="cl"><span class="go">	/lib/ld-linux-aarch64.so.1
</span></span></span></code></pre></div><p>The second line of output shows that it does in fact find <code>liburiparser.so.1</code>
where we put it. So far so good. Just need to tell the GUCs where to find them
and restart Postgres:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ini" data-lang="ini"><span class="line"><span class="cl"><span class="na">extension_control_path</span> <span class="o">=</span> <span class="s">&#39;$system:/usr/local/postgresql/share&#39;</span>
</span></span><span class="line"><span class="cl"><span class="na">dynamic_library_path</span>   <span class="o">=</span> <span class="s">&#39;$libdir:/usr/local/postgresql/lib&#39;</span>
</span></span></code></pre></div><p>And then it works!</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-console" data-lang="console"><span class="line"><span class="cl"><span class="go">❯ psql -c &#34;CREATE EXTENSION uri&#34;
</span></span></span><span class="line"><span class="cl"><span class="go">CREATE EXTENSION
</span></span></span><span class="line"><span class="cl"><span class="go">❯ psql -c &#34;SELECT &#39;https://example.com/&#39;::uri&#34;
</span></span></span><span class="line"><span class="cl"><span class="go">         uri          
</span></span></span><span class="line"><span class="cl"><span class="go">----------------------
</span></span></span><span class="line"><span class="cl"><span class="go"> https://example.com/
</span></span></span></code></pre></div><p>Success! So we can adopt this pattern, yes?</p>
<h3 id="a-wrinkle">A Wrinkle</h3>
<p>Well, maybe. Try it with a second extension, <a href="https://github.com/pramsey/pgsql-http" title="HTTP client for PostgreSQL, retrieve a web page from inside the database.">http</a>, once again building it
with <code>rpath=$ORIGIN</code> and installing it in the custom lib directory:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">perl -i -pe <span class="s1">&#39;s{$libdir/}{}g&#39;</span> *.control
</span></span><span class="line"><span class="cl">make <span class="nv">CFLAGS</span><span class="o">=</span><span class="s1">&#39;-Wl,-rpath,\$$ORIGIN&#39;</span>
</span></span><span class="line"><span class="cl">make install <span class="nv">prefix</span><span class="o">=</span>/usr/local/postgresql
</span></span></code></pre></div><p>Make sure it took:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-console" data-lang="console"><span class="line"><span class="cl"><span class="go">❯ chrpath /usr/local/postgresql/lib/http.so 
</span></span></span><span class="line"><span class="cl"><span class="go">http.so: RUNPATH=$ORIGIN
</span></span></span></code></pre></div><p>Now use <a href="https://linux.die.net/man/1/ldd">ldd</a> to see what shared libraries it needs:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-console" data-lang="console"><span class="line"><span class="cl"><span class="go">❯ ldd /usr/local/postgresql/lib/http.so
</span></span></span><span class="line"><span class="cl"><span class="go">	linux-vdso.so.1 
</span></span></span><span class="line"><span class="cl"><span class="go">	libcurl.so.4 =&gt; not found
</span></span></span><span class="line"><span class="cl"><span class="go">	libc.so.6 =&gt; /lib/aarch64-linux-gnu/libc.so.6
</span></span></span></code></pre></div><p>Naturally it needs <code>libcurl</code>; let&rsquo;s copy it from another system and try again:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="hl"><span class="lnt"> 4
</span></span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="hl"><span class="lnt"> 7
</span></span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-console" data-lang="console"><span class="line"><span class="cl"><span class="go">❯ scp dev:libcurl.so.4 /usr/local/postgresql/lib/
</span></span></span><span class="line"><span class="cl"><span class="go">❯ ldd /usr/local/postgresql/lib/http.so
</span></span></span><span class="line"><span class="cl"><span class="go">	linux-vdso.so.1
</span></span></span><span class="line hl"><span class="cl"><span class="go">	libcurl.so.4 =&gt; /usr/local/postgresql/lib/libcurl.so.4
</span></span></span><span class="line"><span class="cl"><span class="go">	libc.so.6 =&gt; /lib/aarch64-linux-gnu/libc.so.6
</span></span></span><span class="line"><span class="cl"><span class="go">	/lib/ld-linux-aarch64.so.1
</span></span></span><span class="line hl"><span class="cl"><span class="go">	libnghttp2.so.14 =&gt; not found
</span></span></span><span class="line"><span class="cl"><span class="go">	libidn2.so.0 =&gt; /lib/aarch64-linux-gnu/libidn2.so.0
</span></span></span><span class="line"><span class="cl"><span class="go">	librtmp.so.1 =&gt; not found
</span></span></span><span class="line"><span class="cl"><span class="go">	libssh.so.4 =&gt; not found
</span></span></span><span class="line"><span class="cl"><span class="go">	libpsl.so.5 =&gt; not found
</span></span></span><span class="line"><span class="cl"><span class="go">	libssl.so.3 =&gt; /lib/aarch64-linux-gnu/libssl.so.3
</span></span></span><span class="line"><span class="cl"><span class="go">	libcrypto.so.3 =&gt; /lib/aarch64-linux-gnu/libcrypto.so.3
</span></span></span><span class="line"><span class="cl"><span class="go">	libgssapi_krb5.so.2 =&gt; /lib/aarch64-linux-gnu/libgssapi_krb5.so.2
</span></span></span><span class="line"><span class="cl"><span class="go">	libldap.so.2 =&gt; not found
</span></span></span><span class="line"><span class="cl"><span class="go">	liblber.so.2 =&gt; not found
</span></span></span><span class="line"><span class="cl"><span class="go">	libzstd.so.1 =&gt; /lib/aarch64-linux-gnu/libzstd.so.1
</span></span></span><span class="line"><span class="cl"><span class="go">	libbrotlidec.so.1 =&gt; not found
</span></span></span><span class="line"><span class="cl"><span class="go">	libz.so.1 =&gt; /lib/aarch64-linux-gnu/libz.so.1
</span></span></span></code></pre></td></tr></table>
</div>
</div><p>Line 4 shows it found <code>libcurl.so.4</code> where we put it, but the rest of the
output lists a bunch of new dependencies that need to be satisfied. These did
not appear before because the <code>http.so</code> module doesn&rsquo;t depend on them; the
<code>libcurl.so</code> library does. Let&rsquo;s add <code>libnghttp2</code> and try again:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="hl"><span class="lnt"> 7
</span></span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-console" data-lang="console"><span class="line"><span class="cl"><span class="go">❯ scp dev:libnghttp2.so.14 /usr/local/postgresql/lib/
</span></span></span><span class="line"><span class="cl"><span class="go">❯ ldd /usr/local/postgresql/lib/http.so
</span></span></span><span class="line"><span class="cl"><span class="go">	linux-vdso.so.1
</span></span></span><span class="line"><span class="cl"><span class="go">	libcurl.so.4 =&gt; /usr/local/postgresql/lib/libcurl.so.4
</span></span></span><span class="line"><span class="cl"><span class="go">	libc.so.6 =&gt; /lib/aarch64-linux-gnu/libc.so.6
</span></span></span><span class="line"><span class="cl"><span class="go">	/lib/ld-linux-aarch64.so.1
</span></span></span><span class="line hl"><span class="cl"><span class="go">	libnghttp2.so.14 =&gt; not found
</span></span></span><span class="line"><span class="cl"><span class="go">	libidn2.so.0 =&gt; /lib/aarch64-linux-gnu/libidn2.so.0
</span></span></span><span class="line"><span class="cl"><span class="go">	librtmp.so.1 =&gt; not found
</span></span></span><span class="line"><span class="cl"><span class="go">	libssh.so.4 =&gt; not found
</span></span></span><span class="line"><span class="cl"><span class="go">	libpsl.so.5 =&gt; not found
</span></span></span><span class="line"><span class="cl"><span class="go">	libssl.so.3 =&gt; /lib/aarch64-linux-gnu/libssl.so.3
</span></span></span><span class="line"><span class="cl"><span class="go">	libcrypto.so.3 =&gt; /lib/aarch64-linux-gnu/libcrypto.so.3
</span></span></span><span class="line"><span class="cl"><span class="go">	libgssapi_krb5.so.2 =&gt; /lib/aarch64-linux-gnu/libgssapi_krb5.so.2
</span></span></span><span class="line"><span class="cl"><span class="go">	libldap.so.2 =&gt; not found
</span></span></span><span class="line"><span class="cl"><span class="go">	liblber.so.2 =&gt; not found
</span></span></span><span class="line"><span class="cl"><span class="go">	libzstd.so.1 =&gt; /lib/aarch64-linux-gnu/libzstd.so.1
</span></span></span><span class="line"><span class="cl"><span class="go">	libbrotlidec.so.1 =&gt; not found
</span></span></span><span class="line"><span class="cl"><span class="go">	libz.so.1 =&gt; /lib/aarch64-linux-gnu/libz.so.1
</span></span></span></code></pre></td></tr></table>
</div>
</div><p>Sadly, as line 7 shows, it still can&rsquo;t find <code>libnghttp2.so</code>.</p>
<p>It turns out that <a href="https://en.wikipedia.org/wiki/Rpath" title="Wikipedia: rpath">rpath</a> works only for immediate dependencies. To solve this
problem, <code>liburl</code> and all other shared libraries must also be compiled with
<code>rpath=$ORIGIN</code> &mdash; which means we can&rsquo;t simply copy those libraries from OS
packages<sup id="fnref:5"><a href="#fn:5" class="footnote-ref" rel="footnote">5</a></sup>. In th meantime, only deirect dependencies could be
bundled with an extension.</p>
<h2 id="project-status">Project Status</h2>
<p>The vision of accessible, easy-install extensions everywhere remains intact.
I&rsquo;m close to completing a first release of the PGXN v2 <a href="https://github.com/pgxn/build/">build SDK</a> with
support for meta spec v1 and v2, <a href="https://www.postgresql.org/docs/current/extend-pgxs.html" title="PostgreSQL Docs: Extension Building Infrastructure">PGXS</a>, and <a href="https://github.com/pgcentralfoundation/pgrx" title="pgrx: Build Postgres Extensions with Rust!">pgrx</a> extensions. I expect the
first deliverable to be a command-line client to complement and eventuallly
replace the <a href="https://pgxn.github.io/pgxnclient/">original CLI</a>. It will be put to work building all the extensions
currently distributed on <a href="http://pgxn.org/" title="PostgreSQL Extension Network">PGXN</a>, which will surface new issues and patterns
that inform the development and completion of the <a href="https://github.com/pgxn/rfcs/pull/2">v2 meta spec</a>.</p>
<p>In the future, I&rsquo;d also like to:</p>
<ul>
<li>Finish working out Trunk format and dependency patterns</li>
<li>Develop and submit the prroposed <code>extension_search_path</code> patch</li>
<li>Submit <a href="https://kubernetes.io/docs/concepts/storage/volumes/#image">ImageVolume</a> feedback to Kubernetes to allow runtime mounting</li>
<li>Start building and distributing OCI Trunk packages</li>
<li>Make the pattern available for distributed registries, so anyone can build
their own Trunk releases!</li>
<li>Hack fully-dynamic extension loading into <a href="https://cloudnative-pg.io" title="Run PostgreSQL. The Kubernetes way.">CloudNativePG</a></li>
</ul>
<h2 id="let-s-talk">Let&rsquo;s Talk</h2>
<p>I recognize the ambition here, but feel equal to it. Perhaps not every bit
will work out, but I firmly believe in setting a clear vision and executing
toward it while pragmatically revisiting and revising it as experience
warrants.</p>
<p>If you&rsquo;d like to contribute to the project or employ me to continue working on
it, let&rsquo;s talk! Hit me up via one of the services listed on the <a href="https://justatheory.com/about/" title="About Just a Theory">about page</a>.</p>
<div class="footnotes">
<hr>
<ol>
<li id="fn:1">
<p>The feature does not yet support pre-loading shared libraries.
Presumably a flag will be introduced to add the extension to
<a href="https://www.postgresql.org/docs/17/runtime-config-client.html#GUC-SHARED-PRELOAD-LIBRARIES"><code>shared_preload_libraries</code></a>.&#160;<a href="#fnref:1" class="footnote-backref">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:2">
<p>Though we should certainly <a href="https://github.com/kubernetes/enhancements/issues/4639#issuecomment-2898844498">request</a> the ability to add new
<code>ImageVolume</code> mounts without a restart. We can&rsquo;t be the only ones thinking
about kind of feature, right?&#160;<a href="#fnref:2" class="footnote-backref">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:3">
<p>In general, one should avoid <code>LD_LIBRARY_PATH</code> for variety of
reasons, not least of which its <a href="https://blogs.oracle.com/solaris/post/ld_library_path-just-say-no" title="Oracle Solaris Blog: LD_LIBRARY_PATH - just say no">bluntness</a>. For various security reasons,
macOS ignores it unless <a href="https://support.apple.com/guide/security/system-integrity-protection-secb7ea06b49/web" title="Apple Platform Security: System Integrity Protection">sip</a> is disabled, and SELinux <a href="https://selinux.tycho.nsa.narkive.com/yQnAv3QF/policy-regarding-ld-library-path#" title="SELinux policy regarding LD_LIBRARY_PATH">prevents its
propagation</a> to new processes.&#160;<a href="#fnref:3" class="footnote-backref">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:4">
<p>Although not Windows, alas.&#160;<a href="#fnref:4" class="footnote-backref">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:5">
<p>Unless packagers could be pursuaded to build all libraries
with <code>rpath=$ORIGIN</code>, which seems like a tall order.&#160;<a href="#fnref:5" class="footnote-backref">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>

        </div>

        <footer class="tags">
            <h5>More about…</h5>
            <ul>
                <li><a href="https://justatheory.com/tags/postgres/">Postgres</a></li>
                <li><a href="https://justatheory.com/tags/extensions/">Extensions</a></li>
                <li><a href="https://justatheory.com/tags/packaging/">Packaging</a></li>
                <li><a href="https://justatheory.com/tags/pgconf.dev/">PGConf.dev</a></li>
                <li><a href="https://justatheory.com/tags/cloudnativepg/">CloudNativePG</a></li>
            </ul>
        </footer>
    </article>
]]></content>
	</entry>
	<entry>
		<id>https://justatheory.com/2025/05/release-on-pgxn/</id>
		<title type="html"><![CDATA[Auto-Release PostgreSQL Extensions on PGXN]]></title>
		<link rel="alternate" type="text/html" href="https://justatheory.com/2025/05/release-on-pgxn/"/>
		<updated>2025-05-20T15:49:30Z</updated>
		<published>2025-05-20T15:49:30Z</published>
		<author>
			<name>David E. Wheeler</name>
			<email>david@justatheory.com</email>
			<uri>https://justatheory.com/</uri>
		</author>
		<category scheme="https://justatheory.com/tags" term="postgres" label="Postgres"/>
		<category scheme="https://justatheory.com/tags" term="pgxn" label="PGXN"/>
		<category scheme="https://justatheory.com/tags" term="extension" label="Extension"/>
		<category scheme="https://justatheory.com/tags" term="github" label="GitHub"/>
		<category scheme="https://justatheory.com/tags" term="github-actions" label="GitHub Actions"/>
		<category scheme="https://justatheory.com/tags" term="automation" label="Automation"/>
		<category scheme="https://justatheory.com/tags" term="ci/cd" label="CI/CD"/>
		<summary type="html"><![CDATA[Step-by-step instructions to publish PostgreSQL extensions and utilities on
the PostgreSQL Extension Network (PGXN).]]></summary>
		<content type="html" xml:base="https://justatheory.com/" xml:space="preserve">
<![CDATA[    <article class="post">
        <div class="text">
<p>I last wrote about auto-releasing PostgreSQL extensions on PGXN <a href="https://justatheory.com/2020/10/release-postgres-extensions-with-github-actions/" title="Automate Postgres Extension Releases on GitHub and PGXN">back in
2020</a>, but I thought it worthwhile, following my <a href="https://pgext.day" title="Postgres Extensions Day Montréal 2025">Postgres
Extensions Day</a> talk last week, to return again to the basics. With the
goal to get as many extensions distributed on <a href="https://pgxn.org" title="PostgreSQL Extension Network">PGXN</a> as possible, this post
provides step-by-step instructions to help the author of any extension or
Postgres utility to quickly and easily publish every release.</p>
<h2 id="tl-dr">TL;DR</h2>
<ol>
<li>Create a <a href="https://manager.pgxn.org/account/register" title="Request a PGXN Account">PGXN Manager</a> account</li>
<li>Add a <a href="https://rfcs.pgxn.org/0001-meta-spec-v1.html"><code>META.json</code></a> file to your project</li>
<li>Add a <a href="https://hub.docker.com/r/pgxn/pgxn-tools">pgxn-tools</a> powered CI/CD pipeline to publish on tag push</li>
<li><a href="#write-killer-docs">Fully-document</a> your extensions</li>
</ol>
<h2 id="release-your-extensions-on-pgxn">Release your extensions on PGXN</h2>
<p><a href="https://pgxn.org" title="PostgreSQL Extension Network">PGXN</a> aims to become the defacto source for all open-source PostgreSQL
extensions and tools, in order to help users quickly find and learn how to use
extensions to meet their needs. Currently, PGXN distributes source releases
for around 400 extensions (stats on the <a href="https://pgxn.org/about/" title="About PGXN">about page</a>), a fraction of the ca.
<a href="https://gist.github.com/joelonsql/e5aa27f8cc9bd22b8999b7de8aee9d47" title="🗺🐘 1000+ PostgreSQL EXTENSIONs">1200 known extensions</a>. Anyone looking for an extension might exist to solve
some problem must rely on search engines to find potential solutions between
PGXN, GitHub, GitLab, blogs, social media posts, and more. Without a single
trusted source for extensions, and with the proliferation of <a href="https://en.wikipedia.org/wiki/AI_slop" title="Wikipedia: AI Slop">AI Slop</a> in
search engine results, finding extensions aside from a few well-known
solutions proves a challenge.</p>
<p>By publishing releases and full documentation &mdash; all fully indexed by its
search index &mdash; PGXN aims to be that trusted source. Extension authors
provide all the documentation, which PGXN formats for legibility and linking.
See, for example, the <a href="https://pgxn.org/dist/vector/README.html">pgvector docs</a>.</p>
<p>If you want to make it easier for users to find your extensions, to read your
documentation &mdash; not to mention provide sources for binary packaging systems
&mdash; publish every release on PGXN.</p>
<p>Here&rsquo;s how.</p>
<h3 id="create-an-account">Create an Account</h3>
<p>Step one: create a <a href="https://manager.pgxn.org/account/register" title="Request a PGXN Account">PGXN Manager</a> account. The <em>Email</em>, <em>Nickname</em>, and <em>Why</em>
fields are required. The form asks &ldquo;why&rdquo; as a simple filter for bad actors.
Write a sentence describing what you&rsquo;d like to release &mdash; ideally with a link
to the source repository &mdash; and submit. We&rsquo;ll get the account approved
forthwith, which will send a confirmation email to your address. Follow the
link in the email and you&rsquo;ll be good to go.</p>
<h3 id="anatomy-of-a-distribution">Anatomy of a Distribution</h3>
<p>A PostgreSQL extension source tree generally looks something like this (taken
from the <a href="https://github.com/theory/kv-pair/">pair repository</a>):</p>
<pre tabindex="0"><code class="language-tree" data-lang="tree">pair
├── Changes
├── doc
│   └── pair.md
├── Makefile
├── META.json
├── pair.control
├── README.md
├── sql
│   ├── pair--unpackaged--0.1.2.sql
│   └── pair.sql
└── test
    ├── expected
    │   └── base.out
    └── sql
        └── base.sql
</code></pre><p>Extension authors will recognize the standard <a href="https://www.postgresql.org/docs/current/extend-pgxs.html" title="PostgreSQL Docs: Extension Building Infrastructure">PGXS</a> (or <a href="https://github.com/pgcentralfoundation/pgrx" title="pgrx: Build Postgres Extensions with Rust!">pgrx</a>) source
distribution files; only <code>META.json</code> file needs explaining. The <code>META.json</code>
file is, frankly, the only file that PGXN requires in a release. It contains
the metadata to describe the release, following the <a href="https://rfcs.pgxn.org/0001-meta-spec-v1.html">PGXN Meta Spec</a>.
This example contains only the required fields:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;name&#34;</span><span class="p">:</span> <span class="s2">&#34;pair&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;version&#34;</span><span class="p">:</span> <span class="s2">&#34;0.1.0&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;abstract&#34;</span><span class="p">:</span> <span class="s2">&#34;A key/value pair data type&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;maintainer&#34;</span><span class="p">:</span> <span class="s2">&#34;David E. Wheeler &lt;david@justatheory.com&gt;&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;license&#34;</span><span class="p">:</span> <span class="s2">&#34;postgresql&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;provides&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;pair&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;file&#34;</span><span class="p">:</span> <span class="s2">&#34;sql/pair.sql&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;version&#34;</span><span class="p">:</span> <span class="s2">&#34;0.1.0&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">  <span class="p">},</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;meta-spec&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;version&#34;</span><span class="p">:</span> <span class="s2">&#34;1.0.0&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>Presumably these fields contain no surprises, but a couple of details:</p>
<ul>
<li>It starts with the name of the distribution, <code>pair</code>, and the release
version, <code>0.1.0</code>.</li>
<li>The <code>abstract</code> provides a brief description of the extension, while the
<code>maintainer</code> contains contact information.</li>
<li>The <code>license</code> stipulates the distribution license, of course, usually one
of a few known, but may be <a href="https://rfcs.pgxn.org/0001-meta-spec-v1.html#license">customized</a>.</li>
<li>The <code>provides</code> object lists the extensions or tools provided, each named
by an object key that points to details about the extension, including
main file, version, and potentially an abstract and documentation file.</li>
<li>The <code>meta-spec</code> object identifies the meta spec version used for the
<code>META.json</code> itself.</li>
</ul>
<h3 id="release-it">Release It!</h3>
<p>This file with these fields is all you need to make a release. Assuming Git,
package up the extension source files like so (replacing your extension name
and version as appropriate).</p>
<pre tabindex="0"><code>git archive --format zip --prefix=pair-0.1.0 -o pair-0.1.0.zip HEAD
</code></pre><p>Then navigate to the <a href="https://manager.pgxn.org/upload">release page</a>, authenticate, and upload the resulting
<code>.zip</code> file.</p>


	<figure class="frame" title="Screenshot with a box labeled “Upload a Distribution Archive”. It contains an “Archive” label in front of a button labeled “Choose File”. Next to it is a zip file icon and  the text “pair-0.1.0.zip”. Below the box is another button labeled “Release It!”">
			<img src="https://justatheory.com/2025/05/release-on-pgxn/upload-to-pgxn.png" alt="Screenshot with a box labeled “Upload a Distribution Archive”. It contains an “Archive” label in front of a button labeled “Choose File”. Next to it is a zip file icon and  the text “pair-0.1.0.zip”. Below the box is another button labeled “Release It!”" />
	</figure>

<p>And that&rsquo;s it! Your release will appear on <a href="https://pgxn.org" title="PostgreSQL Extension Network">pgxn.org</a> and on <a href="https://mastodon.social/@pgxn">Mastodon</a>
within five minutes.</p>
<h2 id="let-s-automate-it">Let&rsquo;s Automate it!</h2>
<p>All those steps would be a pain in the ass to follow for every release. Let&rsquo;s
automate it using <a href="https://hub.docker.com/r/pgxn/pgxn-tools">pgxn-tools</a>! This OCI image contains the tools necessary to
package and upload an extension release to PGXN. Ideally, use a <a href="https://en.wikipedia.org/wiki/CI/CD" title="Wikipedia: CI/CD">CI/CD</a>
pipeline like a <a href="https://docs.github.com/en/actions/writing-workflows">GitHub Workflow</a> to publish a release on every version tag.</p>
<h3 id="set-up-secrets">Set up Secrets</h3>
<p><a href="https://hub.docker.com/r/pgxn/pgxn-tools">pgxn-tools</a> uses your PGXN credentials to publish releases. To keep them
safe, use the secrets feature of your preferred CI/CD tool. This figure shows
the &ldquo;Secrets and variables&rdquo; configuration for a GitHub repository, with two
repository secrets: <code>PGXN_USERNAME</code> and <code>PGXN_PASSWORD</code>:</p>


	<figure class="frame" title="Screenshot of GitHub Secrets configuration featuring two repository secrets: PGXN_USERNAME and PGXN_PASSWORD.">
			<img src="https://justatheory.com/2025/05/release-on-pgxn/github-secrets.png" alt="Screenshot of GitHub Secrets configuration featuring two repository secrets: PGXN_USERNAME and PGXN_PASSWORD." />
	</figure>

<h3 id="create-a-pipeline">Create a Pipeline</h3>
<p>Use those secrets and <a href="https://hub.docker.com/r/pgxn/pgxn-tools">pgxn-tools</a> in CI/CD pipeline. Here, for example, is a
minimal GitHub workflow to publish a release for every <a href="https://semver.org" title="Semantic Versioning 2.0.0">SemVer</a> tag:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">on</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">push</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">tags</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s1">&#39;v[0-9]+.[0-9]+.[0-9]+&#39;</span><span class="p">]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">jobs</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">release</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Release on PGXN</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">runs-on</span><span class="p">:</span><span class="w"> </span><span class="l">ubuntu-latest</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">container</span><span class="p">:</span><span class="w"> </span><span class="l">pgxn/pgxn-tools</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">env</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">PGXN_USERNAME</span><span class="p">:</span><span class="w"> </span><span class="l">${{ secrets.PGXN_USERNAME }}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">PGXN_PASSWORD</span><span class="p">:</span><span class="w"> </span><span class="l">${{ secrets.PGXN_PASSWORD }}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">steps</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Check out the repo</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">uses</span><span class="p">:</span><span class="w"> </span><span class="l">actions/checkout@v4</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Bundle the Release</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">run</span><span class="p">:</span><span class="w"> </span><span class="l">pgxn-bundle</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Release on PGXN</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">run</span><span class="p">:</span><span class="w"> </span><span class="l">pgxn-release</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p>Details:</p>
<ul>
<li>Line 3 configures the workflow to run on a <a href="https://semver.org" title="Semantic Versioning 2.0.0">SemVer</a> tag push, typically
used to denote a release.</li>
<li>Line 8 configures the workflow job to run inside a <a href="https://hub.docker.com/r/pgxn/pgxn-tools">pgxn-tools</a> container.</li>
<li>Lines 10-11 set environment variables with the credentials from the
secrets.</li>
<li>Line 16 bundles the release using either <code>git archive</code> or <code>zip</code>.</li>
<li>Line 18 publishes the release on PGXN.</li>
</ul>
<p>Now publishing a new release is as simple as pushing a <a href="https://semver.org" title="Semantic Versioning 2.0.0">SemVer</a> tag, like so:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">git tag v0.1.0 -sm <span class="s1">&#39;Tag v0.1.0&#39;</span>
</span></span><span class="line"><span class="cl">git push --follow-tags
</span></span></code></pre></div><p>That&rsquo;s it! The workflow will automatically publish the extension for every
release, ensuring the latest and greatest always make it to PGXN where users
and packagers will find them.</p>
<p>The <a href="https://hub.docker.com/r/pgxn/pgxn-tools">pgxn-tools</a> image also provides tools to easily test a <a href="https://www.postgresql.org/docs/current/extend-pgxs.html" title="PostgreSQL Docs: Extension Building Infrastructure">PGXS</a> or <a href="https://github.com/pgcentralfoundation/pgrx" title="pgrx: Build Postgres Extensions with Rust!">pgrx</a>
extension on supported PostgreSQL versions (going back as far as 8.2), also
super useful in a CI/CD pipeline. See <a href="https://justatheory.com/2020/06/test-extensions-with-github-actions/">Test Postgres Extensions With GitHub
Actions</a> for instructions. Depending on your CI/CD tool of choice, you might
take additional steps, such as publishing a release on GitHub, as <a href="https://justatheory.com/2020/10/release-postgres-extensions-with-github-actions/" title="Automate Postgres Extension Releases on GitHub and PGXN">previously
described</a>.</p>
<h2 id="optimizing-for-pgxn">Optimizing for PGXN</h2>
<p>But let&rsquo;s dig deeper into how to optimize extensions for maximum
discoverability and user visibility on <a href="https://pgxn.org" title="PostgreSQL Extension Network">PGXN</a>.</p>
<h3 id="add-more-metadata">Add More Metadata</h3>
<p>The <code>META.json</code> file supports many more fields that PGXN indexes and
references. These improve the chances users will find what they&rsquo;re looking
for. This detailed example demonstrates how a <a href="https://postgis.net" title="PostGIS">PostGIS</a> <code>META.json</code> file might
start to provide additional metadata:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span><span class="lnt">29
</span><span class="lnt">30
</span><span class="lnt">31
</span><span class="lnt">32
</span><span class="lnt">33
</span><span class="lnt">34
</span><span class="lnt">35
</span><span class="lnt">36
</span><span class="lnt">37
</span><span class="lnt">38
</span><span class="lnt">39
</span><span class="lnt">40
</span><span class="lnt">41
</span><span class="lnt">42
</span><span class="lnt">43
</span><span class="lnt">44
</span><span class="lnt">45
</span><span class="lnt">46
</span><span class="lnt">47
</span><span class="lnt">48
</span><span class="lnt">49
</span><span class="lnt">50
</span><span class="lnt">51
</span><span class="lnt">52
</span><span class="lnt">53
</span><span class="lnt">54
</span><span class="lnt">55
</span><span class="lnt">56
</span><span class="lnt">57
</span><span class="lnt">58
</span><span class="lnt">59
</span><span class="lnt">60
</span><span class="lnt">61
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">   <span class="nt">&#34;name&#34;</span><span class="p">:</span> <span class="s2">&#34;postgis&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">   <span class="nt">&#34;abstract&#34;</span><span class="p">:</span> <span class="s2">&#34;Geographic Information Systems Extensions to PostgreSQL&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">   <span class="nt">&#34;description&#34;</span><span class="p">:</span> <span class="s2">&#34;This distribution contains a module which implements GIS simple features, ties the features to R-tree indexing, and provides many spatial functions for accessing and analyzing geographic data.&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">   <span class="nt">&#34;version&#34;</span><span class="p">:</span> <span class="s2">&#34;3.5.0&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">   <span class="nt">&#34;maintainer&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;Paul Ramsey &lt;pramsey@example.com&gt;&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;Sandro Santilli &lt;sandro@examle.net&gt;&#34;</span>
</span></span><span class="line"><span class="cl">   <span class="p">],</span>
</span></span><span class="line"><span class="cl">   <span class="nt">&#34;license&#34;</span><span class="p">:</span> <span class="p">[</span> <span class="s2">&#34;gpl_2&#34;</span><span class="p">,</span> <span class="s2">&#34;gpl_3&#34;</span> <span class="p">],</span>
</span></span><span class="line"><span class="cl">   <span class="nt">&#34;provides&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;postgis&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">         <span class="nt">&#34;abstract&#34;</span><span class="p">:</span> <span class="s2">&#34;PostGIS geography spatial types and functions&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">         <span class="nt">&#34;file&#34;</span><span class="p">:</span> <span class="s2">&#34;extensions/postgis/postgis.control&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">         <span class="nt">&#34;docfile&#34;</span><span class="p">:</span> <span class="s2">&#34;extensions/postgis/doc/postgis.md&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">         <span class="nt">&#34;version&#34;</span><span class="p">:</span> <span class="s2">&#34;3.5.0&#34;</span>
</span></span><span class="line"><span class="cl">      <span class="p">},</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;address_standardizer&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">         <span class="nt">&#34;abstract&#34;</span><span class="p">:</span> <span class="s2">&#34;Used to parse an address into constituent elements. Generally used to support geocoding address normalization step.&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">         <span class="nt">&#34;file&#34;</span><span class="p">:</span> <span class="s2">&#34;extensions/address_standardizer/address_standardizer.control&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">         <span class="nt">&#34;docfile&#34;</span><span class="p">:</span> <span class="s2">&#34;extensions/address_standardizer/README.address_standardizer&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">         <span class="nt">&#34;version&#34;</span><span class="p">:</span> <span class="s2">&#34;3.5.0&#34;</span>
</span></span><span class="line"><span class="cl">      <span class="p">}</span>
</span></span><span class="line"><span class="cl">   <span class="p">},</span>
</span></span><span class="line"><span class="cl">   <span class="nt">&#34;prereqs&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;runtime&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">         <span class="nt">&#34;requires&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;PostgreSQL&#34;</span><span class="p">:</span> <span class="s2">&#34;12.0.0&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;plpgsql&#34;</span><span class="p">:</span> <span class="mi">0</span>
</span></span><span class="line"><span class="cl">         <span class="p">}</span>
</span></span><span class="line"><span class="cl">      <span class="p">},</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;test&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">         <span class="nt">&#34;recommends&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;pgTAP&#34;</span><span class="p">:</span> <span class="mi">0</span>
</span></span><span class="line"><span class="cl">         <span class="p">}</span>
</span></span><span class="line"><span class="cl">      <span class="p">}</span>
</span></span><span class="line"><span class="cl">   <span class="p">},</span>
</span></span><span class="line"><span class="cl">   <span class="nt">&#34;resources&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;bugtracker&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">         <span class="nt">&#34;web&#34;</span><span class="p">:</span> <span class="s2">&#34;https://trac.osgeo.org/postgis/&#34;</span>
</span></span><span class="line"><span class="cl">      <span class="p">},</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;repository&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">         <span class="nt">&#34;url&#34;</span><span class="p">:</span> <span class="s2">&#34;https://git.osgeo.org/gitea/postgis/postgis.git&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">         <span class="nt">&#34;web&#34;</span><span class="p">:</span> <span class="s2">&#34;https://git.osgeo.org/gitea/postgis/postgis&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">         <span class="nt">&#34;type&#34;</span><span class="p">:</span> <span class="s2">&#34;git&#34;</span>
</span></span><span class="line"><span class="cl">      <span class="p">}</span>
</span></span><span class="line"><span class="cl">   <span class="p">},</span>
</span></span><span class="line"><span class="cl">   <span class="nt">&#34;generated_by&#34;</span><span class="p">:</span> <span class="s2">&#34;David E. Wheeler&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">   <span class="nt">&#34;meta-spec&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;version&#34;</span><span class="p">:</span> <span class="s2">&#34;1.0.0&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;url&#34;</span><span class="p">:</span> <span class="s2">&#34;https://pgxn.org/meta/spec.txt&#34;</span>
</span></span><span class="line"><span class="cl">   <span class="p">},</span>
</span></span><span class="line"><span class="cl">   <span class="nt">&#34;tags&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;gis&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;spatial&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;geometry&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;raster&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;geography&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;location&#34;</span>
</span></span><span class="line"><span class="cl">   <span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><ul>
<li>Line 4 contains a longer description of the distribution.</li>
<li>Lines 6-9 show how to list multiple maintainers as an array.</li>
<li>Line 10 demonstrates support for an array of licenses.</li>
<li>Lines 11-24 list multiple extensions included in the distribution, with
abstracts and documentation files for each.</li>
<li>Lines 25-37 identify dependencies for various phases of the distribution
lifecycle, including configure, build, test, runtime, and develop. Each
contains an object identifying PostgreSQL or extension dependencies.</li>
<li>Lines 38-47 lists resources for the distribution, including issue
tracking and source code repository.</li>
<li>Lines 53-60 contains an array of tags, an arbitrary list of keywords for a
distribution used both in the search index and the <a href="https://pgxn.org/tags" title="PGXN Release Tags">PGXN tag cloud</a>.</li>
</ul>
<p>Admittedly the <a href="https://rfcs.pgxn.org/0001-meta-spec-v1.html">PGXN Meta Spec</a> provides a great deal of information.
Perhaps the simplest way to manage it is to copy an existing <code>META.json</code> from
another project (or above) and edit it. In general, only the <code>version</code> fields
require updating for each release.</p>
<h3 id="write-killer-docs">Write Killer Docs</h3>
<p>The most successful extensions provide ample descriptive and reference
documentation, as well as examples. Most extensions feature a README, of
course, which contains basic information, build and install instructions, and
contact info. But as the <a href="#anatomy-of-a-distribution">pair tree</a>, illustrates,
PGXN also supports extension-specific documentation in a variety of formats,
including:</p>
<ul>
<li><a href="https://asciidoc.org">Asciidoc</a></li>
<li><a href="https://www.bbcode.org">BBcode</a></li>
<li><a href="https://www.wikicreole.org">Creole</a></li>
<li><a href="https://whatwg.org/html">HTML</a></li>
<li><a href="https://daringfireball.net/projects/markdown/">Markdown</a></li>
<li><a href="https://en.wikipedia.org/wiki/Help:Contents/Editing_Wikipedia">MediaWiki</a></li>
<li><a href="https://fletcherpenney.net/multimarkdown/">MultiMarkdown</a></li>
<li><a href="https://metacpan.org/dist/perl/view/pod/perlpodspec.pod">Pod</a></li>
<li><a href="https://docutils.sourceforge.io/rst.html">reStructuredText</a></li>
<li><a href="https://textile-lang.com">Textile</a></li>
<li><a href="https://trac.edgewall.org/wiki/WikiFormatting">Trac</a></li>
</ul>
<p>Some examples:</p>
<ul>
<li><a href="https://github.com/theory/pg-jsonschema-boon/blob/main/doc/jsonschema.md">jsonschema</a> (Markdown)</li>
<li><a href="https://github.com/theory/pg-semver/blob/main/doc/semver.mmd">semver</a>
(MultiMarkdown)</li>
</ul>
<p>PGXN will also index and format additional documentation files in any of the
above formats. See, for example, all the files formatted for <a href="https://pgxn.org/dist/orafce/">orafce</a>.</p>
<h3 id="exclude-files-from-release">Exclude Files from Release</h3>
<p>Use <a href="https://git-scm.com/docs/gitattributes">gitattributes</a> to exclude files from the release. For example,
distributions don&rsquo;t generally include <code>.gitignore</code> or the contents of the
<code>.github</code> directory. Exclude them from the archive created by <code>git archive</code> by
assigning <code>export-ignore</code> to each path to exclude in the <code>.gitattributes</code>
file, like so:</p>
<pre tabindex="0"><code>.gitignore export-ignore
.gitattributes export-ignore
.github export-ignore
</code></pre><h2 id="what-s-it-all-for">What&rsquo;s It All For?</h2>
<p><a href="https://pgxn.org" title="PostgreSQL Extension Network">PGXN</a> aims to be the trusted system of record for open-source PostgreSQL
extensions. Of course that requires that it contain all (or nearly all) of
said extensions. Hence this post.</p>
<p>Please help make it so by adding your extensions, both to help users find the
extensions they need, and to improve the discoverability of your extensions.
Over time, we aim to feed downstream extension distribution systems, such as
<a href="https://yum.postgresql.org" title="PostgreSQL Yum Repository">Yum</a>, <a href="https://wiki.postgresql.org/wiki/Apt" title="The PostgreSQL Wiki: “Apt”">APT</a>, <a href="https://cloudnative-pg.io" title="Run PostgreSQL. The Kubernetes way.">CloudNativePG</a>, <a href="https://justatheory.com/2024/06/trunk-oci-poc/" title="POC: Distributing Trunk Binaries via OCI">OCI</a>, and more.</p>
<p>Let&rsquo;s make extensions available everywhere to everyone.</p>

        </div>

        <footer class="tags">
            <h5>More about…</h5>
            <ul>
                <li><a href="https://justatheory.com/tags/postgres/">Postgres</a></li>
                <li><a href="https://justatheory.com/tags/pgxn/">PGXN</a></li>
                <li><a href="https://justatheory.com/tags/extension/">Extension</a></li>
                <li><a href="https://justatheory.com/tags/github/">GitHub</a></li>
                <li><a href="https://justatheory.com/tags/github-actions/">GitHub Actions</a></li>
                <li><a href="https://justatheory.com/tags/automation/">Automation</a></li>
                <li><a href="https://justatheory.com/tags/ci/cd/">CI/CD</a></li>
            </ul>
        </footer>
    </article>
]]></content>
	</entry>
	<entry>
		<id>https://justatheory.com/2025/05/mini-summit-cnpg/</id>
		<title type="html"><![CDATA[Mini Summit 5 Transcript: Improving the PostgreSQL Extensions Experience in Kubernetes with CloudNativePG]]></title>
		<link rel="alternate" type="text/html" href="https://justatheory.com/2025/05/mini-summit-cnpg/"/>
		<updated>2025-05-28T22:34:21Z</updated>
		<published>2025-05-19T03:05:10Z</published>
		<author>
			<name>David E. Wheeler</name>
			<email>david@justatheory.com</email>
			<uri>https://justatheory.com/</uri>
		</author>
		<category scheme="https://justatheory.com/tags" term="postgres" label="Postgres"/>
		<category scheme="https://justatheory.com/tags" term="extensions" label="Extensions"/>
		<category scheme="https://justatheory.com/tags" term="pgconf" label="PGConf"/>
		<category scheme="https://justatheory.com/tags" term="summit" label="Summit"/>
		<category scheme="https://justatheory.com/tags" term="cloudnativepg" label="CloudNativePG"/>
		<category scheme="https://justatheory.com/tags" term="gabriele-bartolini" label="Gabriele Bartolini"/>
		<summary type="html"><![CDATA[At the final Mini-Summit of 2025, Gabriele Bartolini gave an overview of
PostgreSQL extension management in CloudNativePG.]]></summary>
		<content type="html" xml:base="https://justatheory.com/" xml:space="preserve">
<![CDATA[    <article class="post">


	<figure title="PostgresSQL Extension Mini Summit: Extension Management in CNPG">
			<img src="https://justatheory.com/shared/extension-ecosystem-summit/cnpg-extensions-card.jpeg" alt="Orange card with large black text reading “Extension Management in CNPG”. Smaller text below reads “Gabriele Bartolini (EDB)” and that is the date, “05.07.2025”." title="PostgresSQL Extension Mini Summit: Extension Management in CNPG" />
	</figure>

        <div class="text">
<p>The final PostgresSQL Extension Mini-Summit took place on May 7. <a href="https://www.gabrielebartolini.it">Gabriele
Bartolini</a> gave an overview of PostgreSQL extension management in
<a href="https://cloudnative-pg.io" title="Run PostgreSQL. The Kubernetes way.">CloudNativePG</a> (CNPG). This talk brings together the topics of several
previous Mini-Summits &mdash; notably <a href="https://peter.eisentraut.org">Peter Eisentraut</a> on <a href="https://justatheory.com/2025/04/mini-summit-two/" title="2025 Postgres Extensions Mini Summit Two">implementing an
extension search path</a> &mdash; to look at the limitations of extension
support in CloudNativePG and the possibilities enabled by the extension search
path feature and the Kubernetes 1.33 <a href="https://kubernetes.io/docs/concepts/storage/volumes/#image">ImageVolume</a> feature. Check it out:</p>
<ul>
<li><a href="https://www.youtube.com/watch?v=LbNuUs59j2I">Video</a></li>
<li><a href="https://justatheory.com/2025/05/mini-summit-cnpg/postgres-operand-image-future.pdf">PDF Slides</a></li>
</ul>
<p>Or read on for the full transcript with thanks to <a href="https://dev.to/@floord">Floor Drees</a> for putting it
together.</p>
<h2 id="introduction">Introduction</h2>
<p>Floor Drees.</p>
<p>On May 7 we hosted the last of five (5) virtual Mini-Summits that lead up to
the big one at the Postgres Development Conference (PGConf.Dev), taking place
next week, in Montreal, Canada. <a href="https://www.gabrielebartolini.it">Gabriele Bartolini</a>, <a href="https://cloudnative-pg.io" title="Run PostgreSQL. The Kubernetes way.">CloudNativePG</a>
maintainer, PostgreSQL Contributor, and VP Cloud Native at <a href="https://enterprisedb.com" title="EnterpriseDB">EDB</a>, joined to
talk about improving the Postgres extensions experience in Kubernetes with
CloudNativePG.</p>
<p>The organizers:</p>
<ul>
<li><a href="https://justatheory.com/">David Wheeler</a>, Principal Architect at <a href="https://tembo.io/">Tembo</a>, maintainer of <a href="https://pgxn.org/">PGXN</a></li>
<li><a href="https://ca.linkedin.com/in/yrashk">Yurii Rashkovskii</a>, <a href="https://omnigres.com/">Omnigres</a></li>
<li><a href="https://pgxn.org/user/keithf4/">Keith Fiske</a>, <a href="https://www.crunchydata.com/">Crunchy Data</a></li>
<li><a href="https://dev.to/@floord">Floor Drees</a>, Principal Program Manager at <a href="https://enterprisedb.com" title="EnterpriseDB">EDB</a>, PostgreSQL CoCC member,
PGDay Lowlands organizer</li>
</ul>
<p>The stream and the closed captions available for the recording are supported
by <a href="https://2025.pgconf.dev" title="PostgreSQL Development Conference 2025">PGConf.dev</a> and their gold level sponsors, Google, AWS, Huawei, Microsoft,
and EDB.</p>
<h2 id="improving-the-postgres-extensions-experience-in-kubernetes-with-cloudnativepg">Improving the Postgres extensions experience in Kubernetes with CloudNativePG</h2>
<p>Gabriele Bartolini.</p>
<p>Hi everyone. Thanks for this opportunity, and thank you Floor and David for
inviting me today.</p>
<p>I normally start every presentation with a question, and this is actually the
question that has been hitting me and the other maintainers of <a href="https://cloudnative-pg.io" title="Run PostgreSQL. The Kubernetes way.">CloudNativePG</a>
&mdash; and some are in this call &mdash; from the first day. We know that extensions
are important in Kubernetes, in Postgres, and we&rsquo;ve always been asking how can
we deploy extensions, without breaking the immutability of the container.</p>
<p>So today I will be telling basically our story, and hopefully providing good
insights in the future about how with CloudNativePG we are trying to improve
the experience of Postgres extensions when running databases, including
issues.</p>
<p>I&rsquo;ve been using Postgres for 25 years. I&rsquo;m one of the co-founders of
2ndQuadrant, which was bought by a <a href="https://enterprisedb.com" title="EnterpriseDB">EDB</a> in 2020. And because of my
contributions, I&rsquo;ve been recognized as a Postgres contributor and I&rsquo;m really
grateful for that. And I&rsquo;m also &ldquo;Data on Kubernetes ambassador&rdquo;; my role is to
promote the usage of stateful workloads in Kubernetes. I&rsquo;m also DevOps
evangelist. I always say this: DevOps is the reason why I encountered
Kubernetes, and it will also be the reason why I move away one day from
Kubernetes. It&rsquo;s about culture and I&rsquo;ll explain this later.</p>
<p>In the past I&rsquo;ve been working with <a href="https://pgbarman.org">Barman</a>; I&rsquo;m one of the creators of
Barman. And since 2022, I&rsquo;m one of the maintainers of <a href="https://cloudnative-pg.io" title="Run PostgreSQL. The Kubernetes way.">CloudNativePG</a>. I want
to thank my company, <a href="https://enterprisedb.com" title="EnterpriseDB">EDB</a>, for being the major contributor in Postgres
history in terms of source code. And right now we are also the creators of
CloudNativePG. And as we&rsquo;ll see, the company donated the IP to the <a href="https://www.cncf.io">CNCF</a>. So
it&rsquo;s something that is quite rare, and I&rsquo;m really grateful for that.</p>
<p>What I plan to cover tonight is first, set the context and talk about
immutable application containers, which have been kind of a dogma for us from
day one. Then, how we are handling right now extensions in Kubernetes with
CNPG. This is quite similar to the way other operators deal with it. Then the
future and key takeaways.</p>
<p>First, we&rsquo;re talking about Kubernetes. If you&rsquo;re not familiar, it&rsquo;s an
orchestration system for containers. It&rsquo;s not just an executor of containers,
but it&rsquo;s a complex system that also manages infrastructure. When it manages
infrastructure, it also manages cloud native applications that are also called
workloads. When we&rsquo;re thinking about Postgres in Kubernetes, the database is a
workload like the others. That, I think, is the most important mind shift
among Postres users that I have faced myself, that I&rsquo;ve always treated
Postgres differently from the rest. Here in Kubernetes is it&rsquo;s just another
workload.</p>
<p>Then of course, it&rsquo;s not like any other workload, and that&rsquo;s where operators
come into play, and I think the work that we are doing even tonight is in the
direction to improve how databases is run in Kubernetes in general, and for
everyone.</p>
<p>It was open sourced in 2014, and, it&rsquo;s owned by the <a href="https://www.cncf.io">CNCF</a>, and it&rsquo;s actually
the first project that graduated, and graduated is the most advanced stage in
the graduation process of the CNCF, which starts with sandbox, then incubation
and then graduation.</p>
<p>CloudNativePG is an operator for Postgres. It&rsquo;s production-ready &mdash; what we
say is level five. Level five is kind of an utopic, and unbounded level, the
highest one as defined by the operator development framework. It&rsquo;s used by all
these players including Tembo, IBM Cloud Paks, Google Cloud, Azure, Akamai,
and so on. CNPG is a CNCF project since January. It&rsquo;s distributed under Apache
License 2.0 and the IP &mdash; the Intellectual Property &mdash; is owned by the
community and protected by the CNCF. It therefore is a vendor neutral and
openly governed project. This is kind of a guarantee that it will always be
free. This is also, in my opinion, a differentiation between CloudNativePG and
the rest.</p>
<p>The project was originally created by EDB, but specifically at that time, by
2ndQuadrant. And, as I always like to recall, it was Simon Riggs that put me
in charge of the initiative. I&rsquo;ll always be grateful to Simon, not only for
that, but for everything he has done for me and the team.</p>
<p>CNPG can be installed in several ways. As you can see, it&rsquo;s very popular in
terms of stars. There&rsquo;s more than 4,000 commits. And what&rsquo;s impressive is the
number of downloads in three years, which is 78 million, which means that it&rsquo;s
used the way we wanted it to be used: with CICD pipelines.</p>
<p>This is the <a href="https://landscape.cncf.io">CNCF landscape</a>; these are the CNCF projects. As you can see,
there are only five projects in the CNCF in the database area, and
CloudNativePG is the only one for Postgres. Our aim for 2025 and 2026 is to
become incubating. If you&rsquo;re using CNPG and you want to help with the process,
get in touch with me and Floor.</p>
<p>I think to understand again, what, why we&rsquo;ve done all this process, that led
to the patch that, you&rsquo;ve seen in Postgres 18, it&rsquo;s important to understand
what cloud native has meant to us since we started in 2019. We&rsquo;ve got our own
definition, but I think it still applies. For us it&rsquo;s three things, Cloud
native. It&rsquo;s people that work following DevOps culture. For example, there are
some capabilities that come from DevOps that apply to the cloud native world.
I selected some of them like in user infrastructure, infrastructure
abstraction, version control. These three form the infrastructure-as-code
principle, together with the declarative configuration.</p>
<p>A shift left on security. You&rsquo;ll see with CloudNativePG, we rarely mention
security because it&rsquo;s pretty much everywhere. It&rsquo;s part of the process. Then
continuous delivery.</p>
<p>The second item is immutable application containers, which kind of led the
immutable way of thinking about extensions. And then the third one is that
these application containers must be orchestrated via an
infrastructure-as-code by an orchestrator, and the standard right now is
Kubernetes.</p>
<p>For us it&rsquo;s these three things, and without any of them, you cannot achieve
cloud native.</p>
<p>So what are these immutable application containers? To explain immutability
I&rsquo;d like to talk about immutable infrastructure, which is probably what the
majority of people that have historically worked with Postgres are used to.
I&rsquo;m primarily referring to traditional environments like VMs and bare metal
where the main ways we deploy Postgres is through packages, maybe even managed
by configuration managers, but still, packages are the main artifacts. The
infrastructure is seen as a long-term kind of project. Changes happen over
time and are incremental updates, updates on an existing infrastructure. So if
you want to know the history of the infrastructure over time, you need to
check all the changes that have applied. In case of failure of a system,
systems are healed. So that&rsquo;s the <a href="https://cloudscaling.com/blog/cloud-computing/the-history-of-pets-vs-cattle/">pets</a> concept that comes from DevOps.</p>
<p>On the other hand, immutable infrastructure relies on <a href="https://opencontainers.org">OCI</a> container images.
OCI is a standard, the <a href="https://opencontainers.org">Open Container Initiative</a> and it&rsquo;s part of the
Linux Foundation as well. Immutable infrastructure is founded on continuous
delivery, which is the foundation of <a href="https://www.gitops.tech">GitOps</a> practices. In an immutable
infrastructure, releasing a new version of an application is not updating the
system&rsquo;s application, it is building a new image and publishing it on a public
registry and then deploying it. Changes in the system happen in an atomic way:
the new version of a container is pulled from the registry and the existing
image is almost instantaneously replaced by the new one. This is true for
stateless applications and we&rsquo;ll see, in the case of stateful applications
like Postgres, is not that instantaneous because we need to perform a
switchover or restart &mdash; in any case, generate a downtime.</p>
<p>When it comes to Kubernetes, the choice was kind of obvious to go towards that
immutable infrastructure. So no incremental updates, and in the case of
stateful workloads where you cannot change the content of the container, you
can use data volumes or persistent volumes. These containers are not changed.
If you want to change even a single file or a binary in a container image, you
need to create a new one. This is very important for security and change
management policies in general.</p>
<p>But what I really like about this way of managing our infrastructure is that,
at any time, Kubernetes knows exactly what software is running in your
infrastructure. All of this is versioned in an <a href="https://en.wikipedia.org/wiki/Change_control">SCM</a>, like Git or whatever.
This is something that in the mutable world is less easy to obtain. Again, for
security, this is the foundational thing because this is how you can control
<a href="https://cve.mitre.org">CVEs</a>, the vulnerabilities in your system. This is a very basic
representation of how you build, contain &mdash; let&rsquo;s say the lifecycle of a
container image. You create a <code>Dockerfile</code>, you put it in Git, for example,
then there&rsquo;s an action or a pipeline that creates the container image, maybe
even run some tests and then pushes it to the container registry.</p>
<p>I walked you through the concepts of mutable and immutable containers, what
are, these immutable application containers? If you go back and read what we
were rising before CloudNativePG was famous or was even used, we were always
putting in immutable application containers as one of the principles we could
not lose.</p>
<p>For an immutable application container, it means that there&rsquo;s only a single
application running; that&rsquo;s why it&rsquo;s called &ldquo;application&rdquo;. If you have been
using Docker, you are more familiar with system containers: you run a Debian
system, you just connect and then you start treating it like a VM. Application
containers are not like that. And then they are immutable &mdash; read-only &mdash; so
you cannot even make any change or perform updates of packages. But in
CloudNativePG, because we are managing databases, we need to put the database
files in separate persistent volumes. Persistent volumes are standard
resources provided by Kubernetes. This is where we put PGDATA and, if you
want, a separate volume for WAL files with different storage specifications
and even an optional number of table spaces.</p>
<p>CloudNativePG orchestrates what we call &ldquo;operand images&rdquo;. These are very
important to understand. They contain the Postgres binaries and they&rsquo;re
orchestrated via what we call the &ldquo;instance manager&rdquo;. The instance manager is
just the process that runs and controlled Postgres; I&rsquo;ss the PID 1 &mdash; or the
entry point &mdash; of the container.</p>
<p>There&rsquo;s no other, like SSHD or other, other applications work. There&rsquo;s just
the instance manager that then controls everything else. And this is the
project of the operating images. This is one open source project, and every
week we rebuild the Postgres containers. We recently made some changes to the
flavors of these images and I&rsquo;ll talk about it shortly.</p>
<p>We mentioned the database, we mentioned the binaries, but what about
extensions? This is the problem. Postgres extensions in Kubernetes with
CloudNativePG is the next section, and it&rsquo;s kind of a drama. I&rsquo;m not hiding
this. The way we are managing extensions in Kubernetes right now, in my
opinion, is not enough. It works, but it&rsquo;s got several limitations &mdash; mostly
limitations in terms of usage.</p>
<p>For example, we cannot place them in the data files or in persistent volumes
because these volumes are not read-only in any way. In any case, they cannot
be strictly immutable. So we discarded this option to have persistent volume
where you could kind of deploy extensions and maybe you can even download on
the fly or use the package manager to download them or these kind of
operations. We discarded this from the start and we embraced the operand image
solution. Essentially what we did was placing these extensions in the same
operand image that contains the Postgres binaries. This is a typical approach
of also the other operators. If you think about also <a href="https://github.com/zalando/postgres-operator">Zalando</a> we call it &ldquo;the
Spilo way&rdquo;. Spilo contained all the software that would run with the Zalando
operator.</p>
<p>Our approach was a bit different, in that we wanted lighter images, so we
created a few flavors of images, and also selected some extensions that we
placed in the images. But in general, we recommended to build custom images.
We provided instructions and we&rsquo;ve also provided the requirements to build
container images. But as you can see, the complexity of the operational layer
is quite high, it&rsquo;s not reasonable to ask any user or any customer to build
their own images.</p>
<p>This is how they look now, although this is changing as I was saying:</p>


	<figure title="A stack of boxes with “Debian base image” at the top, then “PostgreSQL”, then “Barman Cloud”, and finally  three “Extension” boxes at the bottom.">
			<img src="https://justatheory.com/2025/05/mini-summit-cnpg/postgres-operand-image-now.png" alt="A stack of boxes with “Debian base image” at the top, then “PostgreSQL”, then “Barman Cloud”, and finally  three “Extension” boxes at the bottom." />
	</figure>

<p>You&rsquo;ve got a base image, for example, the Debian base image. You deploy the
Postgres binaries. Then &mdash; even right now though it&rsquo;s changing &mdash;
CloudNativePG requires Barman Cloud to be installed. And then we install the
extensions that we think are needed. For example, I think we distribute
<a href="https://pgaudit.org/">pgAudit</a>, if I recall correctly, <a href="https://pgxn.org/dist/vector/">pgvector</a> and <a href="https://github.com/EnterpriseDB/pg_failover_slots">pg_failover_slots</a>. Every
layer you add, of course, the image is heavier and we still rely on packages
for most extensions.</p>
<p>The problem is, you&rsquo;ve got a cluster that is already running and you want, for
example, to test an extension that&rsquo;s just come out, or you want to deploy it
in production. If that extension is not part of the images that we build, you
have to build your own image. Because of the possible combinations of
extensions that exist, it&rsquo;s impossible to build all of these combinations. You
could build, for example, a system that allows you to select what extensions
you want and then build the image, but in our way of thinking, this was not
the right approach. And then you&rsquo;ve got system dependencies and, if an
extension brings a vulnerability that affects the whole image and requires
more updates &mdash; not just of the cluster, but also of the builds of the image.</p>
<p>We wanted to do something else, but we immediately faced some limitations of
the technologies. One  was on Postgres, the other one was on Kubernetes. In
Postgres, extensions need to be placed in a single folder. It&rsquo;s not possible
to define multiple locations, but thanks to the work that Peter and this team
have done, <a href="https://justatheory.com/2025/04/mini-summit-two/" title="2025 Postgres Extensions Mini Summit Two">now we&rsquo;ve got <code>extension_control_path</code> in version 18</a>.</p>
<p>Kubernetes could not allow until, 10 days ago, to mount OCI artifacts as
read-only volumes. There&rsquo;s a new feature that is now part of Kubernetes 1.33
that allows us to do it.</p>
<p>This is <a href="https://github.com/postgres/postgres/commit/4f7f7b0">the patch</a> that I was talking about, by <a href="https://peter.eisentraut.org">Peter Eisentraut</a>. I&rsquo;m
really happy that CloudNativePG is mentioned as one of the use cases.
And there&rsquo;s also mentioned for the work that, me, David, and Marco and,
primarily Marco and Niccolò from CloudNativePG have done.</p>
<p>This is <a href="https://github.com/kubernetes/enhancements/issues/4639">the patch</a> that introduced VolumeSource in Kubernetes 1.33.</p>
<p>The idea is that with Postgres 18 now we can set in the configuration where we
can look up for extensions in the file system. And then, if there are
libraries, we can also use the existing <code>dynamic_library_path</code> GUC.</p>
<p>So, you remember, this is where we come from [image above]; the good thing is
we have the opportunity to build Postgres images that are minimal, that only
contain Postgres.</p>


	<figure title="Three stacks of boxes. On the left, “Debian base image” on top of “PostgreSQL”. On the right, “Debian base image” on top of “Barman Cloud”. On the lower right, a single box for an extension.">
			<img src="https://justatheory.com/2025/05/mini-summit-cnpg/postgres-operand-image-future.png" alt="Three stacks of boxes. On the left, “Debian base image” on top of “PostgreSQL”. On the right, “Debian base image” on top of “Barman Cloud”. On the lower right, a single box for an extension." />
	</figure>

<p>Instead of recreating them every week &mdash; because it&rsquo;s very likely that
something has some dependency, has a CVE, and so recreate them for everyone,
forcing everyone to update their Postgres systems &mdash; we can now release them
maybe once a month, and pretty much follow the Postgres cadence patch
releases, and maybe if there are CVEs it&rsquo;s released more frequently.</p>
<p>The other good thing is that now we are working to remove the dependency on
Barman Cloud for CloudNativePG. CloudNativePG has a new plugin interface and
with 1.26 with &mdash; which is expected in the next weeks &mdash; we are suggesting
people start moving new workloads to the Barman Cloud plugin solution. What
happens is that Barman Cloud will be in that sidecar image. So it will be
distributed separately, and so its lifecycle is independent from the rest. But
the biggest advantage is that any extension in Postgres can be distributed &mdash;
right now we&rsquo;ve got packages &mdash; The idea is that they are distributed also as
images.</p>
<p>If we start thinking about this approach, if I write an extension for
Postgres, until now I&rsquo;ve been building only packages for Debian or for RPM
systems. If I start thinking about also building container images, they could
be immediately used by the new way of CloudNativePG to manage extensions.
That&rsquo;s my ultimate goal, let&rsquo;s put it that way.</p>
<p>This is how things will change at run time without breaking immutability.</p>


	<figure title="A box labeled “PostgreSQL Pod” with four separate boxes inside, labeled “Container Postgres”, “Sidecar Barman Cloud”, “Volume Extension 1”, and “Volume Extension 2”.">
			<img src="https://justatheory.com/2025/05/mini-summit-cnpg/runtime-change.png" alt="A box labeled “PostgreSQL Pod” with four separate boxes inside, labeled “Container Postgres”, “Sidecar Barman Cloud”, “Volume Extension 1”, and “Volume Extension 2”." />
	</figure>

<p>There will be no more need to think about all the possible combinations of
extensions. There will be the Postgres pod that runs, for example, a primary
or standby, that will have the container for Postgres. If you&rsquo;re using Barman
Cloud, the sidecar container managed by the plugin with Barman Cloud. And
then, for every extension you have, you will have a different image volume
that is read-only, very light, only containing the files distributed in the
container image of the extension, and that&rsquo;s all.</p>
<p>Once you&rsquo;ve got these, we can then coordinate the settings for external
<code>extension_control_path</code> and <code>dynamic_library_path</code>. What we did was, starting
a fail fast pilot project within EDB to test the work that Peter was doing on
the <code>extension_control_path</code>. For that we used the <a href="https://github.com/cloudnative-pg/postgres-trunk-containers">Postgres Trunk Containers
project</a>, which is a very interesting project that we have at
CloudNativePG. Every day it rebuilds the latest snapshot of the master branch
of Postgres so that we are able to catch, at an early stage, problems with the
new version of Postgres in CloudNativePG. But there&rsquo;s also an action that
builds container images for a specific, for example, <a href="https://commitfest.postgresql.org">Commitfest</a> patch. So we
use that.</p>
<p>Niccolò wrote a pilot patch, an exploratory patch, for the operator to define
the extensions stanza inside the cluster resource. He also built some bare
container images for a few extensions. We make sure to include a very simple
one and the most complex one, which is <a href="https://postgis.net">PostGIS</a>. This is the patch that &mdash;
it&rsquo;s still a draft &mdash; and the idea is to have it in the next version, 1.27
for CloudNativePG. This is how it works:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">apiVersion</span><span class="p">:</span><span class="w"> </span><span class="l">postgresql.cnpg.io/v1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">kind</span><span class="p">:</span><span class="w"> </span><span class="l">Cluster</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">metadata</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">postgresql-with-extensions</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">spec</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">instances</span><span class="p">:</span><span class="w"> </span><span class="m">1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">imageName</span><span class="p">:</span><span class="w"> </span><span class="l">ghcr.io/cloudnative-pg/postgresql-trunk:18-devel</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">postgresql</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">extensions</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">pgvector</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">image</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">reference</span><span class="p">:</span><span class="w"> </span><span class="l">ghcr.io/cloudnative-pg/pgvector-18-testing:latest</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">storage</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">storageClass</span><span class="p">:</span><span class="w"> </span><span class="l">standard</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">size</span><span class="p">:</span><span class="w"> </span><span class="l">1Gi</span><span class="w">
</span></span></span></code></pre></div><p>We have the <code>extensions</code> section in the cluster definition. We name the
extension. Theoretically we could also define the version and we point to the
image. What&rsquo;s missing in this pilot patch is support for image catalogs, but
that&rsquo;s something else that we can worry about later.</p>
<p>What happens under the hood is that when you update, or when you add a new
extension in the cluster definition, a rolling update is initiated. So there&rsquo;s
this short downtime, but the container image is loaded in the replicas first,
and then in the primary. n image volume is mounted for each extension in,
let&rsquo;s say, <code>/extensions/$name_of_extension</code> folder and CNPG updates, these two
parameters. It&rsquo;s quite clean, quite neat. It works, but most of the work needs
to happen here. So that&rsquo;s been my call, I mean to call container images as a
first class artifacts. If these changes, we have a new way to distribute
images.</p>
<p>Just to approach the conclusion, if you want to know more about the whole
story, I wrote <a href="https://www.gabrielebartolini.it/articles/2025/03/the-immutable-future-of-postgresql-extensions-in-kubernetes-with-cloudnativepg/" title="The Immutable Future of PostgreSQL Extensions in Kubernetes with CloudNativePG">this blog article</a> that recaps everything, and the key
takeaway for me &mdash; and then we go more on the patch if you want to, and also
address the questions. But what is important for me? Being in the Postgres
community for a long time, I think this is a good way, a good moment for us to
challenge the status quo of the extension distribution ecosystem.</p>
<p>I think we have an opportunity now to define a standard, which, I just want to
be clear, I&rsquo;m focusing myself primarily on CNPG, but this is in general, even
for other operators. I&rsquo;m sure that this will benefit everyone and overall it
will reduce the waste that we collectively create when distributing these
extensions in Kubernetes. If this becomes a standard way to distribute
extensions, the benefits will be much better operational work for everyone,
primarily also easier testing and validation of extensions. I mean, right now,
if you see an extension, ideally that extension &mdash; and it&rsquo;s very easy to
build &mdash; if you&rsquo;re in GitHub, to build the container images. GitHub, for
example, already provides the whole infrastructure for you to easily build
container images.</p>
<p>So if we find a standard way to define a GitHub action to build Postgres
extensions, I think, if you&rsquo;re a developer of an extension, you can just use
it and then you find a registry in your project directly that continuously
publishes or periodically publishes this extension. Any user can just
reference that image URL and then without having to build images, they&rsquo;re just
one rolling update away from testing a patch, testing also the upgrade paths.</p>
<p>I think there are some unknown unknowns that kind of scare me, in general,
about upgrades, upgrades of extensions. This is, in my opinion, one of the
biggest issues. It&rsquo;s not that they&rsquo;re not solved, but they require more
attention and more testing if you&rsquo;re using them in an immutable world. All of
these will, in my opinion, will be much, much better with the approach we&rsquo;ve
proposed. Images will be lighter, and the lighter image is also safer and more
secure, so less prone to have CVEs,lLess prone to require frequent updates,
and also they reduce the usage of bandwidth, for an organization in general.
What I was saying before, any extension project can be fully independent,
have their own way to build images and publish them.</p>
<p>One last point. I keep hearing many signs, that all of the stuff that we are
proposing right now seem like a kind of a limitation of Kubernetes. The way I
see it, in my view, that it&rsquo;s not actually a limitation, it&rsquo;s that these
problems have never been addressed before. The biggest mistake we can do is
focus on the specific problem of managing extensions without analyzing the
benefits that the entire stack brings to an organization. Kubernetes brings a
lot of benefits in terms of security, velocity, change management and,
operations that any organization must consider right now. Any Postgres DBA,
any Postgres user, my advice is, if you haven&rsquo;t done it yet, start taking
Kubernetes, seriously.</p>
<h2 id="discussion">Discussion</h2>
<p>Floor: I do think that David, you wanted to talk maybe a little bit about the
mutable volume pattern?</p>
<p>David: Well, if people are interested, in your early slide where you were
looking at alternatives, one you were thinking of was putting extensions on a
mutable volume and you decided not to do that. But at Tembo we did do that and
I did a bunch of work trying to improve it and try to minimize image size and
all that in the last couple months. Tembo Cloud is shutting down now, so I had
to stop before I finished it, but I made quite a bit of progress. I&rsquo;m happy to
kind of talk through the ideas there. But I think that this approach is a
better long term solution, fundamentally.</p>
<p>Gabriele: I would like if Marco and Niccolò, if you want to talk about the
actual work you&rsquo;ve done. Meanwhile, Peter asks, &ldquo;why does an installation of
an extension require a small downtime?&rdquo; The reason is that at the moment, the
image volume patch, if you add a new image volume, it requires the pod to
restart. Nico or Marco, Jonathan, if you want to correct me on that.</p>
<p>Nico or Marco or Jonathan: It provides a rolling update of the cluster right
now.</p>
<p>Gabriele: So that&rsquo;s the reason. That&rsquo;s the only drawback, but the benefits in
my opinion, are&hellip;</p>
<p>David: My understanding is that, to add a new extension, it&rsquo;s mounted it in a
different place. And because every single extension is its own mount, you have
to add it to both those GUCs. And at least one of them requires a restart.</p>
<p>Gabriele: But then for example, we&rsquo;ve had this conversation at EDB for
example, we&rsquo;re planning to have flavors of predefined extensions. For
example, you can choose a flavor and we distribute those extensions. For
example, I dunno, for AI we place some AI kind of extensions in the same
image, so it would be different.</p>
<p>But otherwise I&rsquo;m considering the most extreme case of one extension, one
container image, which in my opinion, for the open source world is the way
that hopefully will happen. Because this way, think about that &ndash; I haven&rsquo;t
mentioned this &mdash; if I write an extension, I can then build the image and
then run automated tests using Kubernetes to assess my extension on GitHub. If
those tests fail, my commit will never be merged on main. This is trunk
development, continuous delivery. This is, in my opinion, a far better way of
delivering and developing software. This is, again, the reason why we ended up
in Kubernetes. It&rsquo;s not because it&rsquo;s a technology we like, it&rsquo;s a toy or so,
it&rsquo;s because it solves bigger problems than database problems.</p>
<p>Even when we talk about databases, there&rsquo;s still work that needs to be done,
needs to be improved. I&rsquo;m really happy that we have more people that know
Postgres nowadays that are joining CloudNativePG, and are elevating the
discussions more and more on the database level. Because before it was
primarily on Kubernetes level, but now we see people that know Postgres better
than me get in CloudNativePG and propose new ideas, which is great. Which is
the way it needs to be, in my opinion.</p>
<p>But I remember, Tembo approached us because we actually talked a lot with
them. Jonathan, Marco, I&rsquo;m sure that you recall, when they were evaluating
different operators and they chose CloudNativePG. I remember we had these
discussions where they asked us to break immutability and we said, &ldquo;no way&rdquo;.
That&rsquo;s why I think Tembo had to do the solution you described, because we
didn&rsquo;t want to do it upstream.</p>
<p>I think, to be honest, and to be fair, if image volumes were not added, we
would&rsquo;ve probably gone down that path, because this way of managing
extensions, as I was saying, is not scalable, the current one. Because we want
to always improve, I think that the approach we need to be critical on what we
do. So, I don&rsquo;t know, Niccolò, Marco, I would like you to, if you want, explain
briefly.</p>
<p>[A bit of chatter, opened <a href="https://github.com/EnterpriseDB/pgvector/blob/dev/5645/Dockerfile.cnpg">this Dockerfile</a>.]</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-Dockerfile" data-lang="Dockerfile"><span class="line"><span class="cl"><span class="k">FROM</span><span class="w"> </span><span class="s">ghcr.io/cloudnative-pg/postgresql-trunk:18-devel</span><span class="w"> </span><span class="k">AS</span><span class="w"> </span><span class="s">builder</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="k">USER</span><span class="w"> </span><span class="s">0</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="k">COPY</span> . /tmp/pgvector<span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="k">RUN</span> <span class="nb">set</span> -eux<span class="p">;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">	mkdir -p /opt/extension <span class="o">&amp;&amp;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">	apt-get update <span class="o">&amp;&amp;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">	apt-get install -y --no-install-recommends build-essential clang-16 llvm-16-dev <span class="o">&amp;&amp;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">	<span class="nb">cd</span> /tmp/pgvector <span class="o">&amp;&amp;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">	make clean <span class="o">&amp;&amp;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">	make <span class="nv">OPTFLAGS</span><span class="o">=</span><span class="s2">&#34;&#34;</span> <span class="o">&amp;&amp;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">	make install <span class="nv">datadir</span><span class="o">=</span>/opt/extension/share/ <span class="nv">pkglibdir</span><span class="o">=</span>/opt/extension/lib/<span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="k">FROM</span><span class="w"> </span><span class="s">scratch</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="k">COPY</span> --from<span class="o">=</span>builder /opt/extension/lib/* /lib/<span class="err">
</span></span></span><span class="line"><span class="cl"><span class="k">COPY</span> --from<span class="o">=</span>builder /opt/extension/share/extension/* /share/<span class="err">
</span></span></span></code></pre></div><p>Niccolò: I forked, for example, <a href="https://pgxn.org/dist/vector/">pgvector</a>, That&rsquo;s what we can do basically
for every simple extensions that we can just build. This is a bit more
complicated because we have to build from a trunk version of Postgres 18. So
we have to compile pgvector from source, and then in a scratch layer we just
archive the libraries and every other content that was previously built. But
ideally whenever PG 18 comes out as a stable version of Postgres, we just need
to <code>apt install pgvector</code> and grab the files from the path. Where it gets a
bit more tricky is in the case of <a href="https://postgis.net">PostGIS</a>, or <a href="https://github.com/timescale/timescaledb">TimescaleDB</a>, or any
extension whose library requires third party libraries. For example, PostGIS
has a strong requirement on the geometric libraries, so you need to import
them as well inside the mount volume. I can link you an example of the
<a href="https://github.com/cloudnative-pg/postgres-trunk-containers/blob/dev/postgis/postgis/Dockerfile-postgis.cnpg">PostGIS one</a>.</p>
<p>Gabriele: I think it&rsquo;s important, we&rsquo;ve got, I think Peter here, David as
well, I mean, for example, if we could get standard ways in Postgres to
generate <code>Dockerfile</code>s for extensions, that could be great. And as I said,
these extensions can be used by any operator, not only CNPG.</p>
<p>David: That&rsquo;s my <a href="https://justatheory.com/2024/06/trunk-oci-poc/" title="POC: Distributing Trunk Binaries via OCI">POC</a> does. It&rsquo;s a patch against the PGXS that would build a
trunk image.</p>
<p>Gabriele: This is the work that Niccolò had to do to make PostGIS work in the
pilot project: he had to copy everything.</p>
<p>Niccolò: I think we can make it a little bit smoother and dynamically figure
out everything from the policies library, so we don&rsquo;t have to code everything
<a href="https://github.com/cloudnative-pg/postgres-trunk-containers/blob/dev/postgis/postgis/Dockerfile-postgis.cnpg">like this</a>, but this is just a proof of concept that it can
work.</p>
<p>David: So you installed all those shared libraries that were from packages.</p>
<p>Niccolò: Yeah, they&rsquo;re being copied in the same <code>MountVolume</code> where the actual
extensions are copied as well. And then the pilot patch is able to set up the
library path inside the pod so that it makes the libraries available to the
system because of course, these libraries are only part of the <code>MountVolume</code>.
They&rsquo;re not injected inside the system libraries of the pod, so we have to set
up the library path to make them available to Postgres. That&rsquo;s how we&rsquo;re able
to use them.</p>
<p>David: So they end up in <code>PKGLIBDIR</code> but they still work.</p>
<p>Niccolò: Yeah.</p>
<p>Gabriele: I mean, there&rsquo;s better ideas, better ways. As Niccolò also said, it
was a concept.</p>
<p>David: Probably a lot of these shared libraries could be shared with other
extensions. So you might actually want other OCI images that just have some of
the libraries that shared between.</p>
<p>Gabriele: Yeah, absolutely. So we could work on a special kind of, extensions
or even metadatas so that we can place, you know…</p>
<p>So, yeah, that&rsquo;s it.</p>
<p>Jonathan: I think it&rsquo;s important to invite everyone to try and test this,
especially the Postgres <a href="https://github.com/cloudnative-pg/postgres-trunk-containers">trunk</a> containers, when they want to try something
new stuff, new like this one, just because we always need people testing. When
more people review and test, it&rsquo;s amazing. Because every time we release
something, probably we&rsquo;ll miss something, some extension like PostGIS missing
one of the libraries that wasn&rsquo;t included in the path. Even if we can try to
find a way to include it, it will not be there. So testing, please! Test all
the time!</p>
<p>Gabriele: Well, we&rsquo;ve got this action now, they&rsquo;re failing. I mean, it&rsquo;s a bit
embarrassing. [Cross talk.] We already have patch to fix it.</p>
<p>But I mean, this is a great project as I mentioned before, because it allows
us to test the current version of Postgres, but also if you want to build from
a <a href="https://commitfest.postgresql.org">Commitfest</a> or if you&rsquo;ve got your own Postgres repository with sources, you
can compile, you can get the images from using <a href="https://github.com/cloudnative-pg/postgres-trunk-containers">this project</a>.</p>
<p>Floor: Gabriele, did you want to talk about <a href="https://www.cisa.gov/sbom">SBOM</a>s?</p>
<p>Gabriele: I forgot to mention <a href="https://www.cisa.gov/sbom">Software Bill of Materials</a>. They&rsquo;re very
important. It&rsquo;s kind of now basic for any container image. There&rsquo;s also the
possibility to add them to these container images too. This is very important.
Again, in a change manager for security and all of that &mdash; in general supply
chain. And signatures too. But we&rsquo;ve got signature for packages as well.
There&rsquo;s also a attestation of provenance.</p>
<p>Floor: Very good, thanks everyone!</p>

        </div>

        <footer class="tags">
            <h5>More about…</h5>
            <ul>
                <li><a href="https://justatheory.com/tags/postgres/">Postgres</a></li>
                <li><a href="https://justatheory.com/tags/extensions/">Extensions</a></li>
                <li><a href="https://justatheory.com/tags/pgconf/">PGConf</a></li>
                <li><a href="https://justatheory.com/tags/summit/">Summit</a></li>
                <li><a href="https://justatheory.com/tags/cloudnativepg/">CloudNativePG</a></li>
                <li><a href="https://justatheory.com/tags/gabriele-bartolini/">Gabriele Bartolini</a></li>
            </ul>
        </footer>
    </article>
]]></content>
	</entry>
	<entry>
		<id>https://justatheory.com/2025/05/cbor-json-number-string/</id>
		<title type="html"><![CDATA[CBOR Tag for JSON Number Strings]]></title>
		<link rel="alternate" type="text/html" href="https://justatheory.com/2025/05/cbor-json-number-string/"/>
		<updated>2025-05-18T19:32:06Z</updated>
		<published>2025-05-18T19:32:06Z</published>
		<author>
			<name>David E. Wheeler</name>
			<email>david@justatheory.com</email>
			<uri>https://justatheory.com/</uri>
		</author>
		<category scheme="https://justatheory.com/tags" term="go" label="Go"/>
		<category scheme="https://justatheory.com/tags" term="cbor" label="CBOR"/>
		<category scheme="https://justatheory.com/tags" term="json" label="JSON"/>
		<category scheme="https://justatheory.com/tags" term="ietf" label="IETF"/>
		<category scheme="https://justatheory.com/tags" term="iana" label="IANA"/>
		<summary type="html"><![CDATA[Use the new IANA-registered tag to preserve JSON numbers represented as strings when encoding in Concise Binary Object Representation.]]></summary>
		<content type="html" xml:base="https://justatheory.com/" xml:space="preserve">
<![CDATA[    <article class="post">
        <div class="text">
<p>For a side project, I&rsquo;m converting JSON inputs to <a href="https://cbor.io" title="CBOR — Concise Binary Object Representation">CBOR</a>, or Concise Binary
Object Representation, defined by <a href="https://www.rfc-editor.org/rfc/rfc8949.html" title="RFC 8949 Concise Binary Object Representation">RFC 8949</a>, in order to store a more compact
representation in the database. This go Go app uses <a href="https://pkg.go.dev/encoding/json">encoding/json</a> package&rsquo;s
<a href="https://pkg.go.dev/encoding/json#Decoder.UseNumber"><code>UseNumber</code></a> decoding option to preserve numbers as strings, rather tha
<code>float64</code>s. Alas, CBOR has no support for such a feature, so such values
cannot survive a round-trip to CBOR and back, as demonstrating by this example
using the <a href="https://pkg.go.dev/github.com/fxamacker/cbor/v2">github.com/fxamacker/cbor</a> package (<a href="https://go.dev/play/p/a0ukEGoQFSG">playground</a>)</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="c1">// Decode JSON number using json.Number.</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nx">input</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">bytes</span><span class="p">.</span><span class="nf">NewReader</span><span class="p">([]</span><span class="nb">byte</span><span class="p">(</span><span class="s">`{&#34;temp&#34;: 98.6}`</span><span class="p">))</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nx">dec</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">json</span><span class="p">.</span><span class="nf">NewDecoder</span><span class="p">(</span><span class="nx">input</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nx">dec</span><span class="p">.</span><span class="nf">UseNumber</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">var</span><span class="w"> </span><span class="nx">val</span><span class="w"> </span><span class="kd">map</span><span class="p">[</span><span class="kt">string</span><span class="p">]</span><span class="kt">any</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">if</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">dec</span><span class="p">.</span><span class="nf">Decode</span><span class="p">(</span><span class="o">&amp;</span><span class="nx">val</span><span class="p">);</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">log</span><span class="p">.</span><span class="nf">Fatalf</span><span class="p">(</span><span class="s">&#34;Err: %v&#34;</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="c1">// Encode as CBOR.</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nx">data</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">cbor</span><span class="p">.</span><span class="nf">Marshal</span><span class="p">(</span><span class="nx">val</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">if</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">log</span><span class="p">.</span><span class="nf">Fatalf</span><span class="p">(</span><span class="s">&#34;Err: %v&#34;</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="c1">// Decode back into Go.</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">var</span><span class="w"> </span><span class="nx">newVal</span><span class="w"> </span><span class="kd">map</span><span class="p">[</span><span class="kt">string</span><span class="p">]</span><span class="kt">any</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">if</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">cbor</span><span class="p">.</span><span class="nf">Unmarshal</span><span class="p">(</span><span class="nx">data</span><span class="p">,</span><span class="w"> </span><span class="o">&amp;</span><span class="nx">newVal</span><span class="p">);</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">log</span><span class="p">.</span><span class="nf">Fatalf</span><span class="p">(</span><span class="s">&#34;Err: %v&#34;</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="c1">// Encode as JSON.</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nx">output</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">json</span><span class="p">.</span><span class="nf">Marshal</span><span class="p">(</span><span class="nx">newVal</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">if</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">log</span><span class="p">.</span><span class="nf">Fatalf</span><span class="p">(</span><span class="s">&#34;Err: %v&#34;</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nx">fmt</span><span class="p">.</span><span class="nf">Printf</span><span class="p">(</span><span class="s">&#34;%s\n&#34;</span><span class="p">,</span><span class="w"> </span><span class="nx">output</span><span class="p">)</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p>The output:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span><span class="nt">&#34;temp&#34;</span><span class="p">:</span><span class="s2">&#34;98.6&#34;</span><span class="p">}</span>
</span></span></code></pre></div><p>Note that the input on line 2 contains the number <code>98.6</code>, but once the value
has been transformed to CBOR and back it becomes the string <code>&quot;98.6&quot;</code>.</p>
<p>I wanted to preserve JSON numbers treated as strings. Fortunately, <a href="https://cbor.io" title="CBOR — Concise Binary Object Representation">CBOR</a> uses
numeric tags to identify data types, and includes a <a href="https://www.iana.org/assignments/cbor-tags/cbor-tags.xhtml" title="Concise Binary Object Representation (CBOR) Tags">registry</a> maintained by
<a href="https://www.iana.org" title="Internet Assigned Numbers Authority">IANA</a>. I <a href="https://mailarchive.ietf.org/arch/msg/cbor/BjA7Bc0CSubgIDGyzyiTJeLSGaQ/">proposed</a> a new tag for JSON numbers as strings and, through a few
iterations, the <a href="https://mailman3.ietf.org/mailman3/lists/cbor@ietf.org/">CBOR group</a> graciously accepted the <a href="https://gist.github.com/theory/ef667af1c725240e6e30d525786d58e6" title="JSON Number String Tag for CBOR">formal description of
semantics</a> and assigned tag <code>284</code> in the <a href="https://www.iana.org/assignments/cbor-tags/cbor-tags.xhtml" title="Concise Binary Object Representation (CBOR) Tags">registry</a>.</p>
<p>Now any system that handles JSON numbers as strings can use this tag to
preserve the numeric representation in JSON output.</p>
<p>Here&rsquo;s how to use the tag customization features of
<a href="https://pkg.go.dev/github.com/fxamacker/cbor/v2">github.com/fxamacker/cbor</a> to transparently round-trip <code>json.Number</code> values
<a href="https://go.dev/play/p/o2-4a76fE_5">playground</a>:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span><span class="lnt">29
</span><span class="lnt">30
</span><span class="lnt">31
</span><span class="lnt">32
</span><span class="lnt">33
</span><span class="lnt">34
</span><span class="lnt">35
</span><span class="lnt">36
</span><span class="lnt">37
</span><span class="lnt">38
</span><span class="lnt">39
</span><span class="lnt">40
</span><span class="lnt">41
</span><span class="lnt">42
</span><span class="lnt">43
</span><span class="lnt">44
</span><span class="lnt">45
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="c1">// Create tag 284 for JSON Number as string.</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nx">tags</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">cbor</span><span class="p">.</span><span class="nf">NewTagSet</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nx">tags</span><span class="p">.</span><span class="nf">Add</span><span class="p">(</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">cbor</span><span class="p">.</span><span class="nx">TagOptions</span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nx">EncTag</span><span class="p">:</span><span class="w"> </span><span class="nx">cbor</span><span class="p">.</span><span class="nx">EncTagRequired</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nx">DecTag</span><span class="p">:</span><span class="w"> </span><span class="nx">cbor</span><span class="p">.</span><span class="nx">DecTagRequired</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">},</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">reflect</span><span class="p">.</span><span class="nf">TypeOf</span><span class="p">(</span><span class="nx">json</span><span class="p">.</span><span class="nf">Number</span><span class="p">(</span><span class="s">&#34;&#34;</span><span class="p">)),</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="mi">284</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="c1">// Create a custom CBOR encoder and decoder:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nx">em</span><span class="p">,</span><span class="w"> </span><span class="nx">_</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">cbor</span><span class="p">.</span><span class="nx">EncOptions</span><span class="p">{}.</span><span class="nf">EncModeWithTags</span><span class="p">(</span><span class="nx">tags</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nx">dm</span><span class="p">,</span><span class="w"> </span><span class="nx">_</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">cbor</span><span class="p">.</span><span class="nx">DecOptions</span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">DefaultMapType</span><span class="p">:</span><span class="w"> </span><span class="nx">reflect</span><span class="p">.</span><span class="nf">TypeOf</span><span class="p">(</span><span class="kd">map</span><span class="p">[</span><span class="kt">string</span><span class="p">]</span><span class="kt">any</span><span class="p">(</span><span class="kc">nil</span><span class="p">)),</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}.</span><span class="nf">DecModeWithTags</span><span class="p">(</span><span class="nx">tags</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="c1">// Decode JSON number using json.Number.</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nx">input</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">bytes</span><span class="p">.</span><span class="nf">NewReader</span><span class="p">([]</span><span class="nb">byte</span><span class="p">(</span><span class="s">`{&#34;temp&#34;: 98.6}`</span><span class="p">))</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nx">dec</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">json</span><span class="p">.</span><span class="nf">NewDecoder</span><span class="p">(</span><span class="nx">input</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nx">dec</span><span class="p">.</span><span class="nf">UseNumber</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">var</span><span class="w"> </span><span class="nx">val</span><span class="w"> </span><span class="kd">map</span><span class="p">[</span><span class="kt">string</span><span class="p">]</span><span class="kt">any</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">if</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">dec</span><span class="p">.</span><span class="nf">Decode</span><span class="p">(</span><span class="o">&amp;</span><span class="nx">val</span><span class="p">);</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">log</span><span class="p">.</span><span class="nf">Fatalf</span><span class="p">(</span><span class="s">&#34;Err: %v&#34;</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="c1">// Encode as CBOR.</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nx">data</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">em</span><span class="p">.</span><span class="nf">Marshal</span><span class="p">(</span><span class="nx">val</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">if</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">log</span><span class="p">.</span><span class="nf">Fatalf</span><span class="p">(</span><span class="s">&#34;Err: %v&#34;</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="c1">// Decode back into Go.</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">var</span><span class="w"> </span><span class="nx">newVal</span><span class="w"> </span><span class="kd">map</span><span class="p">[</span><span class="kt">string</span><span class="p">]</span><span class="kt">any</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">if</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">dm</span><span class="p">.</span><span class="nf">Unmarshal</span><span class="p">(</span><span class="nx">data</span><span class="p">,</span><span class="w"> </span><span class="o">&amp;</span><span class="nx">newVal</span><span class="p">);</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">log</span><span class="p">.</span><span class="nf">Fatalf</span><span class="p">(</span><span class="s">&#34;Err: %v&#34;</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="c1">// Encode as JSON.</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nx">output</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">json</span><span class="p">.</span><span class="nf">Marshal</span><span class="p">(</span><span class="nx">newVal</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">if</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">log</span><span class="p">.</span><span class="nf">Fatalf</span><span class="p">(</span><span class="s">&#34;Err: %v&#34;</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nx">fmt</span><span class="p">.</span><span class="nf">Printf</span><span class="p">(</span><span class="s">&#34;%s\n&#34;</span><span class="p">,</span><span class="w"> </span><span class="nx">output</span><span class="p">)</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p>Lines 1-16 contain the main difference from the previous example. They create
a CBOR encoder (<code>em</code>) and decoder (<code>dm</code>) with tag <code>284</code> assigned to
<code>json.Number</code> values. The code then uses them rather than the <code>cbor</code> package
to <code>Marshal</code> and <code>Unmarshal</code> the values on lines 28 and 35. The result:</p>
<pre tabindex="0"><code>{&#34;temp&#34;:98.6}
</code></pre><p>Et voilà! <code>json.Number</code> values are once again preserved.</p>
<p>I believe these custom CBOR encoder and decoder configurations bring full
round-trip compatibility to any regular JSON value decoded by <a href="https://pkg.go.dev/encoding/json">encoding/json</a>.
The other important config for that compatibility is the <code>DefaultMapType</code>
decoding option on line 15, which ensures maps use <code>string</code> values for map
keys rather the CBOR-default <code>any</code> values.</p>

        </div>

        <footer class="tags">
            <h5>More about…</h5>
            <ul>
                <li><a href="https://justatheory.com/tags/go/">Go</a></li>
                <li><a href="https://justatheory.com/tags/cbor/">CBOR</a></li>
                <li><a href="https://justatheory.com/tags/json/">JSON</a></li>
                <li><a href="https://justatheory.com/tags/ietf/">IETF</a></li>
                <li><a href="https://justatheory.com/tags/iana/">IANA</a></li>
            </ul>
        </footer>
    </article>
]]></content>
	</entry>
	<entry>
		<id>https://justatheory.com/2025/05/2025-gsoc/</id>
		<title type="html"><![CDATA[2025 GSOC: Mankirat Singh — ABI Compliance Reporting]]></title>
		<link rel="alternate" type="text/html" href="https://justatheory.com/2025/05/2025-gsoc/"/>
		<updated>2025-05-13T18:25:11Z</updated>
		<published>2025-05-13T18:25:11Z</published>
		<author>
			<name>David E. Wheeler</name>
			<email>david@justatheory.com</email>
			<uri>https://justatheory.com/</uri>
		</author>
		<category scheme="https://justatheory.com/tags" term="postgres" label="Postgres"/>
		<category scheme="https://justatheory.com/tags" term="summer-of-code" label="Summer of Code"/>
		<category scheme="https://justatheory.com/tags" term="abi-compliance" label="ABI Compliance"/>
		<category scheme="https://justatheory.com/tags" term="mankirat-singh" label="Mankirat Singh"/>
		<summary type="html"><![CDATA[Please welcome 2025 Google Summer of Code contributor Mankirat Singh, who will
be developing an ABI compliance checker for the PostgreSQL maintenance
branches.]]></summary>
		<content type="html" xml:base="https://justatheory.com/" xml:space="preserve">
<![CDATA[    <article class="post">
        <div class="text">
<p>I&rsquo;m pleased to welcome <a href="https://blog.mankiratsingh.com">Mankirat Singh</a> to the Postgres community as a
2025 <a href="https://summerofcode.withgoogle.com">Google Summer of Code</a> contributor. Mankirat will be developing an ABI
compliance checker and reporting system to help identify and prevent
unintentional ABI changes in future minor Postgres releases. This follows on
the heels of the addition of <a href="https://justatheory.com/2024/06/abi-api-guidance/" title="Patch: Postgres ABI and API Guidance">ABI and API guidance</a> in Postgres 18, as well as
the <a href="https://www.enterprisedb.com/blog/explaining-abi-breakage-postgresql-171">ABI-breaking Postgres 17.1 release</a>. What timing!</p>
<p>Please follow Mankirat&rsquo;s <a href="https://blog.mankiratsingh.com">blog</a> as he develops the project this summer, under
the mentorship of myself and <a href="https://pashagolub.github.io/blog/">Pavlo Golub</a>. It should also soon be on <a href="https://planet.postgresql.org">Planet
PostgreSQL</a>. We&rsquo;ve also set up the <code>#gsoc2025-abi-compliance-checker</code> channel
on the <a href="https://pgtreats.info/slack-invite">community Slack</a> for ad-hoc discussion. Join us!</p>

        </div>

        <footer class="tags">
            <h5>More about…</h5>
            <ul>
                <li><a href="https://justatheory.com/tags/postgres/">Postgres</a></li>
                <li><a href="https://justatheory.com/tags/summer-of-code/">Summer of Code</a></li>
                <li><a href="https://justatheory.com/tags/abi-compliance/">ABI Compliance</a></li>
                <li><a href="https://justatheory.com/tags/mankirat-singh/">Mankirat Singh</a></li>
            </ul>
        </footer>
    </article>
]]></content>
	</entry>
	<entry>
		<id>https://justatheory.com/2025/05/mini-summit-cnpg-extensions/</id>
		<title type="html"><![CDATA[Mini Summit 5: Extension Management in CNPG]]></title>
		<link rel="alternate" type="text/html" href="https://www.meetup.com/postgres-extensions-ecosystem-mini-summits/events/306551747/"/>
		<link rel="related" type="text/html" href="https://justatheory.com/2025/05/mini-summit-cnpg-extensions/"/>
		<updated>2025-05-05T21:51:22Z</updated>
		<published>2025-05-05T21:51:22Z</published>
		<author>
			<name>David E. Wheeler</name>
			<email>david@justatheory.com</email>
			<uri>https://justatheory.com/</uri>
		</author>
		<category scheme="https://justatheory.com/tags" term="postgres" label="Postgres"/>
		<category scheme="https://justatheory.com/tags" term="extensions" label="Extensions"/>
		<category scheme="https://justatheory.com/tags" term="pgconf" label="PGConf"/>
		<category scheme="https://justatheory.com/tags" term="summit" label="Summit"/>
		<category scheme="https://justatheory.com/tags" term="cloudnativepg" label="CloudNativePG"/>
		<category scheme="https://justatheory.com/tags" term="gabriele-bartolini" label="Gabriele Bartolini"/>
		<summary type="html"><![CDATA[The last Mini-Summit of the year features Gabriele Bartolini talking about
extension management in CloudNativePG.]]></summary>
		<content type="html" xml:base="https://justatheory.com/" xml:space="preserve">
<![CDATA[    <article class="post">


	<figure title="PostgresSQL Extension Mini Summit: Extension Management in CNPG"><a href="https://www.meetup.com/postgres-extensions-ecosystem-mini-summits/events/306551747/">
			<img src="https://justatheory.com/shared/extension-ecosystem-summit/cnpg-extensions-card.jpeg" alt="Orange card with large black text reading “Extension Management in CNPG”. Smaller text below reads “Gabriele Bartolini (EDB)” and that is the date, “05.07.2025”." title="PostgresSQL Extension Mini Summit: Extension Management in CNPG" />
		</a>
	</figure>

        <div class="text">
<p>The last Extension Ecosystem Mini-Summit is upon us. How did that happen?</p>
<p>Join us for a virtual conference session featuring <a href="https://www.gabrielebartolini.it">Gabriele Bartolini</a>, who
will be discussing <a href="https://www.meetup.com/postgres-extensions-ecosystem-mini-summits/events/306551747/">Extension Management in CNPG</a>. I&rsquo;m psyched for this one,
as the PostgresSQL community has contributed quite a lot to improving
extensions management in <a href="https://cloudnative-pg.io" title="Run PostgreSQL. The Kubernetes way.">CloudNativePG</a> in the past year, some of which we
covered in <a href="https://justatheory.com/2025/04/mini-summit-two/" title="2025 Postgres Extensions Mini Summit Two: “Implementing an Extension Search Path”">previously</a>. If you miss it, the video, slides, and transcript
will appear here soon.</p>
<p>Though it may be a week or two to get the transcripts done, considering that
<a href="https://2025.pgconf.dev" title="PostgreSQL Development Conference 2025">PGConf.dev</a> is next week, and featuring the <a href="https://www.pgevents.ca/events/pgconfdev2025/schedule/session/241">Extension Ecosystem Summit</a> on
Tuesday, 13 May in Montreál, CA. Hope to see you there; be sure to say &ldquo;hi!&rdquo;</p>

        </div>

        <footer class="tags">
            <h5>More about…</h5>
            <ul>
                <li><a href="https://justatheory.com/tags/postgres/">Postgres</a></li>
                <li><a href="https://justatheory.com/tags/extensions/">Extensions</a></li>
                <li><a href="https://justatheory.com/tags/pgconf/">PGConf</a></li>
                <li><a href="https://justatheory.com/tags/summit/">Summit</a></li>
                <li><a href="https://justatheory.com/tags/cloudnativepg/">CloudNativePG</a></li>
                <li><a href="https://justatheory.com/tags/gabriele-bartolini/">Gabriele Bartolini</a></li>
            </ul>
        </footer>
    </article>
]]></content>
	</entry>
	<entry>
		<id>https://justatheory.com/2025/05/2025-mini-summit-four/</id>
		<title type="html"><![CDATA[Mini Summit 4 Transcript: The User POV]]></title>
		<link rel="alternate" type="text/html" href="https://justatheory.com/2025/05/2025-mini-summit-four/"/>
		<updated>2025-05-01T21:02:39Z</updated>
		<published>2025-05-01T21:02:39Z</published>
		<author>
			<name>Floor Drees</name>
		</author>
		<category scheme="https://justatheory.com/tags" term="postgres" label="Postgres"/>
		<category scheme="https://justatheory.com/tags" term="extensions" label="Extensions"/>
		<category scheme="https://justatheory.com/tags" term="pgconf" label="PGConf"/>
		<category scheme="https://justatheory.com/tags" term="summit" label="Summit"/>
		<category scheme="https://justatheory.com/tags" term="celeste-horgan" label="Celeste Horgan"/>
		<category scheme="https://justatheory.com/tags" term="sonia-valeja" label="Sonia Valeja"/>
		<category scheme="https://justatheory.com/tags" term="alexey-palazhchenko" label="Alexey Palazhchenko"/>
		<summary type="html"><![CDATA[Last week Floor Drees moderated a panel on &ldquo;The User POV&rdquo; at the fourth
Extension Mini-Summit. Read on for the transcript and link to the video.]]></summary>
		<content type="html" xml:base="https://justatheory.com/" xml:space="preserve">
<![CDATA[    <article class="post">


	<figure title="PostgresSQL Extension Mini Summit: The User POV"><a href="https://www.meetup.com/postgres-extensions-ecosystem-mini-summits/events/306682786/">
			<img src="https://justatheory.com/shared/extension-ecosystem-summit/user-pov.jpeg" alt="Orange card with large black text reading “The User POV”. Smaller text above reads “04.23.2025” and below reads “Celeste Horgan (Aiven), Sonia Valeja (Percona), &amp; Alexey Palazhchenko (FerretDB)”" title="PostgresSQL Extension Mini Summit: The User POV" />
		</a>
	</figure>

        <div class="text">
<p>On April 23, we hosted the fourth of five (5) virtual Mini-Summits that lead
up to the big one at the Postgres Development Conference (<a href="https://2025.pgconf.dev" title="PostgreSQL Development Conference 2025">PGConf.dev</a>),
taking place May 13-16, in Montreál, Canada. <a href="https://www.linkedin.com/in/celeste-horgan-b65b5a1a/">Celeste Horgan</a>, Developer
Educator at Aiven, <a href="https://www.linkedin.com/in/sonia-valeja-69517a140/">Sonia Valeja</a>, PostgreSQL DBA at Percona, and <a href="https://www.linkedin.com/in/alexeypalazhchenko/overlay/about-this-profile/">Alexey
Palazhchenko</a>, CTO FerretDB, joined for a panel discussion moderated by <a href="https://dev.to/@floord">Floor
Drees</a>.</p>
<ul>
<li><a href="https://www.youtube.com/watch?v=d6XjsNAUvIw">Video</a></li>
</ul>
<p>And now, the transcripts of &ldquo;The User POV&rdquo; panel, by <a href="https://dev.to/@floord">Floor Drees</a></p>
<h2 id="introduction">Introduction</h2>
<p>My name is Floor, I&rsquo;m one of the organizers of these Extension Ecosystem
Mini-Summits. Other organizers are also here:</p>
<ul>
<li><a href="https://justatheory.com/">David Wheeler</a>, Principal Architect at <a href="https://tembo.io/">Tembo</a>, maintainer of <a href="https://pgxn.org/">PGXN</a></li>
<li><a href="https://ca.linkedin.com/in/yrashk">Yurii Rashkovskii</a>, <a href="https://omnigres.com/">Omnigres</a></li>
<li><a href="https://pgxn.org/user/keithf4/">Keith Fiske</a>, <a href="https://www.crunchydata.com/">Crunchy Data</a></li>
<li><a href="https://dev.to/@floord">Floor Drees</a>, Principal Program Manager at <a href="https://enterprisedb.com" title="EnterpriseDB">EDB</a>, PostgreSQL CoCC member,
PGDay Lowlands organizer</li>
</ul>
<p>The stream and the closed captions available for the recording are supported
by PGConf.Dev and their gold level <a href="https://2025.pgconf.dev/sponsors.html">sponsors</a>, Google, AWS, Huawei, Microsoft,
and EDB.</p>
<p>Next, and last in this series, on May 7 we&rsquo;re gonna have <a href="https://www.gabrielebartolini.it">Gabriele Bartolini</a>
talk to us about <a href="https://www.meetup.com/postgres-extensions-ecosystem-mini-summits/events/306551747/">Extension Management in CloudNativePG</a>. Definitely make
sure you head over to the <a href="https://www.meetup.com/postgres-extensions-ecosystem-mini-summits/" title="Postgres Extension Ecosystem Mini-Summit on Meetup">Meetup</a> page, if you haven&rsquo;t already, and RSVP for
that one!</p>
<h2 id="the-user-pov">The User POV</h2>
<p><strong>Floor:</strong> For the penultimate edition of this series, we&rsquo;re inviting a couple
of Postgres extension and tooling users to talk about how they pick and choose
projects that they want to use, how they do their due diligence and, their
experience with running extensions.</p>
<p>But I just wanted to set the context for the meeting today. We thought that
being in the depth of it all, if you&rsquo;re an extension developer, you kind of
lose the perspective of what it&rsquo;s like to use extensions and other auxiliary
tooling. You lose that user&rsquo;s point of view. But users, maybe they&rsquo;re coming
from other ecosystems are used to, maybe a different, probably smoother
experience. I&rsquo;m coming from the Rails and Ruby community, so RubyGems are my
one stop shop for extending functionality.</p>
<p>That&rsquo;s definitely a completely different experience from when I started using
Postgres extensions. That&rsquo;s not to say that those ecosystems and NPM and PIP
and WordPress don&rsquo;t have their own issues, ut we can certainly learn from some
of the differences between the ecosystems. Ultimately, what we want to cover
today is the experience of using extensions in 2025, and what are our users'
wishes for the future?</p>
<p><strong>Celeste:</strong> Hello my name is Celeste, I am on the developer relations team at
Aiven. I only really started using Postgres as a part of my job here at Aiven,
but have been a much longer contributor to similar-sized ecosystems. I was
really heavily involved in the Kubernetes ecosystem for quite a while.
Kubernetes is an extensible-by-design piece of software, but it&rsquo;s many, many
generations of software development later than some of the concepts that
Postgres pioneered. Thank you for having me, Floor!</p>
<p><strong>Sonia:</strong> Hello everybody! I started working with PostgreSQL in the year
2012, and since then it has been a quite a journey. Postgres has been my
primary database, and along with learning PostgreSQL, I learned the other
database alongside. I learned Oracle, I learned SQLServer, but only from the
perspective &mdash; which is important &mdash; to migrate from X database to
PostgresSQL, as in Oracle to PostgreSQL migration, SQLServer to PostgreSQL
migration. I learned about the other databases and I&rsquo;m fortunate to work as a
PostgreSQL developer, PL/pgSQL Developer, PostgreSQL DBA, onsite coordinator,
offsite coordinator, sometimes a trainer. So, in and out, it has been like I&rsquo;m
breathing PostgreSQL since then.</p>
<p><strong>Alexey:</strong> Thanks for having me! I first worked with Postgres in 2005. Fast
forward to today and I am doing FerretDB, which is the open source MongoDB
replacement built on top of PostgreSQL and also on top of the DocumentDB
extension recently open-sourced by Microsoft. We provide this extension to our
users, but also we consume this extension as users of that extension.
Somewhere in between, between 2005 and now, I also worked at Percona. At
Percona I worked on monitoring software and worked with pg_stat_statements and
pg_stat_monitor, which is made by Percona and I have pretty much a lot of
experience with Postgres extensions.</p>
<p><strong>Floor:</strong> And you&rsquo;re cheating a little on this panel, seeing as you are not
only a user but also a provider. I definitely have some questions for you!</p>
<p>And y&rsquo;all talked a little about your sort of experience with extensibility of
other software or technology, and comparing that to the Postgres experience.
Can you all talk about what the main differences are that you have observed
with other ecosystems?</p>
<p><strong>Celeste:</strong> I think as somebody who&rsquo;s a bit of a newer Postgres user and I
guess comes from a different community, the biggest thing that weirded me out,
when I started working with Postgres, is that there&rsquo;s no way to install an
extension except to install it against your live database.</p>
<p>If you compare that to something like Kubernetes, which again has a rather
robust extensibility ecosystem, both on the networking side of things, but
also other aspects of it, the inherent software architecture makes it so that
you have to plan out what you&rsquo;re going to do, and then you apply a plan. In
theory you can&rsquo;t apply a plan or add extensions to Kubernetes that won&rsquo;t work
or will somehow break the system. Again, in theory, in practice things are
more interesting.</p>
<p>But with Postgres and with databases in general, you&rsquo;re always working with
the live dataset, or at some point you have to work with the live dataset. So
there&rsquo;s no real way to test.</p>
<p><strong>Sonia:</strong> Most of the other databases &mdash; apart from PostgreSQL, which I have
worked with &mdash; most of them are licensed. So Oracle and SQLServer. When it
comes to PostgreSQL, it&rsquo;s an open source, so you do your own thing: you do the
installation, do the checkout everything, which is open source, you can see
the code, and things like that. But when it comes to other databases, I since
it&rsquo;s licensed, it is managed by the specific vendor, so you do not have rights
to do anything else. The things which will be common, like you do the POC in
both the databases before you actually implement it in the production
environment.</p>
<p><strong>Alexey:</strong> Floor, you mentioned RubyGems, and I was thinking that actually
there is something similar between PostgreSQL extensions and RubyGems in a
sense that RubyGems quite often extend built-in Ruby classes, and Postgres
extensions could do the same. There is no separation between public and
private inside PostgreSQL, it&rsquo;s all just C symbols, no special mark, don&rsquo;t
touch the CPI, we are going to change it at central detail. Nothing like that.
They try not to break compatibility needlessly, but on the other hand, you
have to check all versions of your extensions with all separate versions of
PostgreSQL. In that sense it&rsquo;s quite similar, unlike some other languages
where&rsquo;s there&rsquo;s better separation between internal private, if not on the
compiler level, at least on like documentation level or something like that.</p>
<p><strong>Celeste:</strong> That&rsquo;s not necessarily a criticism of Postgres. I think it&rsquo;s just
that&rsquo;s those were the tools available to Postgres as a community when Postgres
was being developed. There are some advantages to that too, because, for lack
of a better word, the lack of checks and balances let some Postgres extensions
do very, very interesting things that would maybe not be possible under a more
restricted framework.</p>
<p><strong>Floor:</strong> The main difference I see between those two is that I know to go to
RubyGems as my place to get my plugins &mdash; or my gems, in that case. Whereas
with Postgres, they can live pretty much anywhere, right? There&rsquo;s different
directories and there&rsquo;s different places where you can get your stuff and
maybe there&rsquo;s something that is in a private repo somewhere because that&rsquo;s
what another team at your company is working on. It&rsquo;s a bit of a mess, you
know? It&rsquo;s really difficult to navigate, where maybe other things are lot less
difficult to navigate because there&rsquo;s just the single place.</p>
<p>I wanna talk a little bit about when you&rsquo;re looking for an extension to do a
certain thing for you. What do you consider when you&rsquo;re looking for an
extension or when you&rsquo;re comparing some of its tooling? I wrote down a couple
of things that you might be looking at, or what I might be looking at: maybe
it&rsquo;s docs and tutorials, maybe it&rsquo;s &ldquo;has it seen a recent release?&rdquo; Has it
seen frequent releases? Is there only one company that is offering this
extension? Or is it multiple companies supporting this extension? Is it a
community-built tool? Is it already in use by other teams in your company? So
it&rsquo;s something that has been tested out with your system, with your stack, and
you feel like it&rsquo;s something that you can easily adopt.</p>
<p>So what are some of the things for you that you definitely look at when you&rsquo;re
looking to adopt new tooling?</p>
<p><strong>Celeste:</strong> I think the main thing you wanna look for when you&rsquo;re looking at
really any open source project, whether it&rsquo;s an extension or not, is both
proof points within the project, but also social proof. Proof points within
the project are things that you mentioned, like is there documentation? Does
this seem to be actively maintained? Is the commit log in GitHub moving? How
many open issues are there? Are those open issues being closed over time?
Those are project health indicators. For example, if you look at the <a href="https://chaoss.community">CHAOSS
Project</a>, Dawn Foster has done a ton of work around monitoring project health
there.</p>
<p>But I think the other half of this &mdash; and this was actually something we
worked on a lot at the Cloud Native Computing Foundation when I was there, and
that work continues  &mdash; is &mdash; and this makes a bit more sense in some cases
than others &mdash; is social proof. So, are there other companies using it? Can
you point to case studies? Can you point to case studies of something being in
production? Can you point to people giving conference talks where they mention
something being in use?</p>
<p>This becomes really important when you start thinking about things being
enterprise-grade, an when you start thinking about the idea of
enterprise-grade open source. Everybody who&rsquo;s on this panel works for a
company that does enterprise-grade open source database software, and you have
to ask yourself what that means. A lot of what that means is that other
enterprises are using it ,because that&rsquo;s means that something comes to a
certain level of reliability.</p>
<p><strong>Sonia:</strong> I would like to add some things. What I look at is how difficult or
how easy it is to install, configure, and upgrade the extension, and, whether
it needs restart of the database service or not. Why do I look at the restart
aspect? Because when I install it or configure or upgrade or whatever activity
I perform with it, if it requires the restart, that means it is not configured
online, so I need to involve other folks to do the database restart, as in an
application is connecting to it. When I restart, it goes for a maintenance
window for a very small time &mdash; whatever duration it goes offline, the
database service. So whether it requires restart or not, that is also very
important for me to understand.</p>
<p>Apart from the documentation, which should be of course easy to understand.
That is one of the aspects while you install and configure. It should not be
that difficult that I need to refer every time, everything, and do it, and
then maybe, I might need to create another script to use it. It should not be
the case. I look to those aspects, as well.</p>
<p>Apart from that, I also see how do I monitor the activities of this extension,
like whether it is available in the logs &mdash; what that extension is doing. So
it should not break my existing things basically. So how stable and how
durable it is, and I should be able to monitor the activities, whatever that
extension is doing.</p>
<p>From the durability perspective, even if I&rsquo;m not able to monitor via logs, it
should be durable enough to that it should not break anything else, which is
up and running.</p>
<p>One more thing. I will definitely perform the POC, before putting it into the
production, into some lower environment or in my test environment somewhere
else.</p>
<p><strong>Floor:</strong> How do you figure out though, how easy something is to sort of set
up and configure? Are you looking for that information from a README or
some documentation? Because I&rsquo;ve definitely seen some very poorly documented
stuff out there&hellip;</p>
<p><strong>Sonia:</strong> Yeah, documentation is one aspect. Apart from that, when you do the
POC, you will actually using you&rsquo;ll be actually using that. So with that POC
itself, you&rsquo;ll be able to understand how easy it is to install, configure, and
use it.</p>
<p><strong>Alexey:</strong> For me as a user, I would say the most important thing is whatever
extension is packaged and easy to install. And if it&rsquo;s not packaged in the
same way as PostgreSQL is packaged. For example, if I get PostgreSQL from my
Ubuntu distribution, if extension is not in the same Ubuntu target, it might
as well not exist for me because there is no way I&rsquo;m going to compile it
myself. It&rsquo;s like hundreds of flags and that being C, and okay, I can make it
1% faster, but then it&rsquo;ll be insecure and will bring PostgreSQL down, or
worse. So there are a lot of problems like that.</p>
<p>If it&rsquo;s not a package, then I would just probably just do something which is
not as good, not as stable, but I will do it myself and will be able to
support them using some third party extensions that is not packaged properly.
And properly for me, is the high bar. So if it&rsquo;s some third party network of
extensions, that might be okay, I will take a look. But then of course, if
it&rsquo;s in the Ubuntu repository or Debian repository, that would be of course,
much better.</p>
<p><strong>Floor:</strong> I think that&rsquo;s the build versus buy &mdash; or not necessarily <em>buy</em> if
it&rsquo;s open source. Not to say that open source is free. But that&rsquo;s the
discussion, right? When do you decide to spend the time to build something
over adopting something? And so for you, that&rsquo;s mainly down to packaging?</p>
<p><strong>Alexey:</strong> For me that&rsquo;s the most important one because for features we
generally need to use in the current job and previous jobs, there are enough
hooks on the PostgreSQL itself to make what we want to do ourselves. Like if
sometimes we need to parse logs, sometimes we need to parse some low level
counters, but that&rsquo;s doable and we could do it in a different language and in
the way we can maintain it ourselves. If you talk about PostgreSQL, I
typically recommend C and if there&rsquo;s some problem, we will have a bigger
problem finding someone to maintain it, to fix it fast.</p>
<p><strong>Floor:</strong> Alright When you build it yourself, would you then also open-source
it yourself and take on the burden of maintenance?</p>
<p><strong>Alexey:</strong> I mean that really depends on the job. Like at Percona we open
sourced pg_stat_monitor. But that was like, implicit goal of making this
extension open source to make it like a superset of pg_stat_statement. In
FerretDB of course, DocumentDB is open source &mdash; we contribute to it, but I
couldn&rsquo;t say that&rsquo;s easier. Of course if it was written like in our perfect
language, Go, it would be much, much easier. Unfortunately, it&rsquo;s not. So we
have to deal with it with packaging and what not.</p>
<p><strong>Floor:</strong> I guess it&rsquo;s also like build versus buy versus fork because there&rsquo;s
definitely different forks available for a similar tooling that is just
optimized for a little bit of a different use case. But again, that&rsquo;s then
another project out there that needs to be maintained.</p>
<p><strong>Alexey:</strong> But at the same time, if you fork something, and don&rsquo;t want to
contribute back, you just don&rsquo;t have this problem of maintaining it for
someone else. You just maintain it for yourself. Of course, like if someone
else in upstream wants to pull your changes, they will be able to. And then
when they look at you like you&rsquo;re a bad part of the community because you
don&rsquo;t contribute back, but that depends on the size of the company, whatever
you have the sources and all that.</p>
<p><strong>Celeste:</strong> But now you&rsquo;re touching on something that I feel very strongly
about when it comes to open source. Why open source anything to begin with? If
we can all just maintain close forks of everything that we need, why is
Postgres open source to begin with and why does it continue to be open source
and why are we having this discussion 30 or 40 years into the lifespan of
Postgres at this point?</p>
<p>The fact of the matter is that Postgres being open source is the reason that
we&rsquo;re still here today. Postgres is a 30 plus year old database at this point.
Yes, it&rsquo;s extremely well architected because it continues to be applicable to
modern use cases when it comes to data. But really the fundamental of the
matter is that it is <em>free,</em> and being free means that two things can happen.
One, it&rsquo;s a very smart move for businesses to build a business on top of a
particular piece of software. But two &mdash; and I would argue that this is
actually the more important point when it comes to open source and the long
term viability of open source &mdash; is that because it is free, that means it is
A) proliferative, it has proliferated across the software industry and B) it
is extremely valuable for professionals to learn Postgres or to learn
Kubernetes or to learn Linux because they know that they&rsquo;re gonna encounter
that sometime in their career.</p>
<p>So when it comes to extensions, why open source an extension? You could simply
close source an extension. It&rsquo;s the same reason: if you use open source
extensions, you can then hire for people who have potentially encountered
those extensions before.</p>
<p>I work for a managed service provider that deploys quite a few Postgreses for
quite a few clients. I obviously have a bit of a stake in the build versus buy
versus fork debate that is entirely financial and entirely linked to my
wellbeing. Regardless, it still makes sense for a company like Aiven to invest
in open source technologies, but it makes a lot more sense for us to hire
Postgres experts who can then manage those extensions and manage the
installation of those extensions and manage whether your database works or not
against certain extensions, than it is for literally every company out there
on the planet to hire a Postgres professional. There&rsquo;s still a use case for
open-sourcing these things. That is a much larger discussion though, and I
don&rsquo;t wanna derail this panel. [Laughs.]</p>
<p><strong>Floor:</strong> I mean, if Alexey is game, you got yourself a conversation.</p>
<p><strong>Alexey:</strong> First of all, I completely agree with you and I of course built my
whole carrier on open source. But there&rsquo;s also the other side. So let&rsquo;s say
you build an open source extension which is very specific, very niche, solves
your particular problem. And there are like 20 other people who are like, you
have the same problem, and then all 20 come to your GitHub and ask questions
about it. And they do it for free. You just waste your time supporting them
essentially. And you are a small company, you are just three people and you
open-source this extension just for fun. And they are three people and two of
them work full time and support that.</p>
<p><strong>Celeste:</strong> Oh yeah, no, I didn&rsquo;t say the economics of this worked out for
the people doing the open-sourcing, just to be perfectly clear. I think a much
larger question around the sustainability of open source communities in
general. Postgres, the overall project, and say, for example, the main
Kubernetes project, are outliers in terms of the amount of support and the
amount of manpower and people and the energy they get. Whereas most things
that get open-sourced are &mdash; I think Tidelift had <a href="https://tidelift.com/open-source-maintainer-survey-2024" title="The 2024 Tidelift state of the open source maintainer report">a survey</a>: the average
maintainer size for any given open source project is one. That is a much
larger debate though. Realistically it makes a lot of sense, particularly for
larger companies, to use open source software, Postgres included, because it
accelerates their time to innovation. They don&rsquo;t need to worry about
developing a database, for example. And if they&rsquo;re using Postgres and they
decide they want time series data, they don&rsquo;t need to worry about migrating to
a time series database when they can just use Timescale.</p>
<p>However, &ldquo;are they contributing back to those projects?&rdquo; becomes a really big
question. I think the next questions that Floor would like to lead us to, amd
I&rsquo;m just going to take the reins here, Floor &mdash;</p>
<p><strong>Floor:</strong> Are you taking my job??</p>
<p><strong>Celeste:</strong> Hardly, hardly, I could never! My understanding of why we&rsquo;re
having this series of conversations that&rsquo;s around the sustainability of the
Postgres extensions ecosystem,is that there&rsquo;s a governance question there as
well. For the end user, the ideal state for any Postgres extension is that
they&rsquo;re blessed and vetted by the central project. But as soon as you start
doing that, you start realizing how limited the resources in even a massive
project like Postgres are. And then you start asking: Where should those
people be coming from? And then you start thinking: There are companies like
Microsoft out there in the world that are hiring a lot of open source
contributors, and that&rsquo;s great, but&hellip; What about the governments? What about
the universities? What about the smaller companies? The real issue is the
manpower and there&rsquo;s only so far you can go, as a result of that. There&rsquo;s
always sustainability issues around all open source, including Postgres
extensions, that come down to the sustainability of open source as a whole and
whether or not this is a reasonable way of developing software. Sorry to get
deep. [Laughs.]</p>
<p><strong>Floor:</strong> Yeah, I think these are discussions that we&rsquo;re definitely having a
lot in the open source community, and in the hallway at a lot of conferences.</p>
<p>We&rsquo;re gonna open it up to  audience questions too in a minute. So if people
want to continue talking about the drama that is open source and sustainable
open source, we can definitely continue this discussion.</p>
<p>Maybe going back a little bit, Alexey, can we talk a little bit about &mdash;
because you&rsquo;re also a provider &mdash; what your definition of &ldquo;done&rdquo; is or what
you wanna offer your users at minimum when you do decide to open-source some
of your stuff or make available some of some of your stuff.</p>
<p><strong>Alexey:</strong> As an open source company, what we do, we just publish our code on
GitHub and that&rsquo;s it. It&rsquo;s open source, that&rsquo;s done. Knock yourself out and if
you want some support, you just pay us, and then we will. That&rsquo;s how we make
money. Well, of course not. That&rsquo;s more complicated than that, and I wish it
was like to some degree, sometimes. Now there still a lot of users who just
come and ask for questions for free, and you want to support them because you
want to increase adoption and all that.</p>
<p>The same with extensions. So as I just described the situation, of course,
that was a bit like, not to provoke a discussion, but, let&rsquo;s say you built a
PostgreSQL extension, you need to have some hooks in the core that ideally
would be stable, don&rsquo;t change between versions as we discussed. That&rsquo;s a bit
of a problem. PostgreSQL, no separation between private and public API. Then
how do you install? You need to package it some way that is the same way as
your current PostgreSQL version is packaged. There is no easy way, for
example, to extend a version of PostgreSQL, which is a part of Docker, you
just build your own container.</p>
<p><strong>Celeste:</strong> I&rsquo;ll segway into the point that I think I was supposed to make
when we were talking about extensions ecosystem, as opposed to a rant about
the sustainability of open source, which I am unfortunately always down to
give. Here&rsquo;s the thing with extensions ecosystems. For the end user, it is
significantly more beneficial if those extensions are somehow
centrally-controlled. If you think about something like RubyGems or the Python
package installer or even Docker to a certain extent, those are all ways of
centralizing. Though with some of the exploits that have gone on with NPM
recently, there are obviously still problems there.</p>
<p>I mentioned, there&rsquo;s always staffing problems when it comes to open source.
Assigning somebody to approve every single extension under the sun isn&rsquo;t
really sustainable from a human perspective. The way that we handle this in
the Kubernetes community &mdash; particularly the container network interfaces, of
which there are many, many, many &mdash; is we effectively manage it with
governance. We have a page on the documentation in the website that says: here
are all the container network interfaces that have chosen to list themselves
with us. The listings are alphabetical, so there is no order of precedence.</p>
<p>The community does not take responsibility for this code because we simply
cannot. In being a container network interface, it means that they implement
certain functionalities, like an interface in the programming sense. We just
left it at that. That was the solution that the Kubernetes community came to.
I don&rsquo;t know if that&rsquo;s the solution that the Postgres community will
eventually come to, but community governance is a huge part of the solution to
that problem, in my opinion.</p>
<p><strong>Alexey:</strong> I think one big difference between NPM and NodeJS ecosystem in
general, and, for example, Postgres extensions, is that NPM was so popular and
there are so many packages mostly because NodeJS by itself is quite small. The
core of NodeJS is really, really small. There is now standard library and a
lot of functionality is external. So I would say as long as your core, like
PostgreSQL or Ruby or Kubernetes is large enough, the amount of extensions
will be limited just by that. Because many people will not use any extensions,
they will just use the core. That could solve a problem of waiting and
name-squatting, but just by itself. I would say PostgreSQL more or less solves
this problem to some degree.</p>
<p><strong>Floor:</strong> Before we open up for some questions from participants, Sonia, in a
previous call, shared a little bit of a horror story with us, with wanting to
use a certain extension and not being able to. I think this is something that
other people can resonate with, having been through a similar thing. Let&rsquo;s
hear that story, And then, of course, Celeste, Alexey, if you have similar
stories, do share before we open up for questions from the rest of the peeps
joining here.</p>
<p><strong>Sonia:</strong> So there was this requirement to transfer data from one database to
another database, specifically with respect to PostgreSQL. I wanted to
transfer the data from the production environment to some other environment,
or internally within the non-production environments. I created this extension
called <a href="https://www.postgresql.org/docs/current/contrib-dblink-function.html">dblink</a>. I&rsquo;m talking about way back, 2012, 2013, somewhere, when I
started working with PostgreSQL, I used that extension. When you configure
that extension, we need to give the credentials in a human readable format.
And then, at times it also gets stored in the logs or somewhere.</p>
<p>I mean, even if it is not storing the logs, what the security team or the
audit team mentioned was that since it is using the credentials in a human
readable format, this is not good. And if somebody has has access to X
database, they also get the access to the Y database or the Y cluster. And
what if it goes to the production environment and then somebody can just steal
the data, without us even knowing it. It&rsquo;ll not get logged inside the logs,
that somebody has accessed my production database via non-production database.
So that&rsquo;s not good, and was not acceptable by the auditors.</p>
<p>I love that extension today also, because without doing any scripting or
anything, you just access one database from another database and then get
whatever you want. But then as a developer, it might be very easy for me to
use that thing. But then as an other person who is trying to snoop into your
production database or the other data of anything, it&rsquo;s easy for them. So we
were asked not to use that extension specifically, at least not to connect to
the production environment.</p>
<p>I was working for a taxation project. It was a financial critical data, and
they did not want it to have any risk of anybody reaching to that data because
it was the numbers, the financial figures, and was critical. So that&rsquo;s the
reason we were refrained from using it for that particular project. But then
other projects, which were not that critical, I somehow managed to convince
them to use it. [Laughs.]</p>
<p><strong>Floor:</strong> So it&rsquo;s sometimes you will choose it for convenience and it&rsquo;s
acceptable risk, and then there might be restrictions from other teams as
well. Thanks for sharing that. If anyone wants to un-mute and ask questions or
share their own horror stories, you&rsquo;re now very welcome to.</p>
<p><strong>Yurii:</strong> There was a really good point about extensions being available as
part of your operating system environment, for example Ubuntu packages or Red
Hat packages. This is where we still have a lot of difficulty in general, in
this ecosystem. Obviously PGDG is doing an amazing job capturing a fraction of
those extensions. But because it is a complicated job, oftentimes unpaid,
people are trying to make the best out of it. On the one hand, it does serve
as a filter, as in only the best of the best extensions that people really use
get through that filter and become part of PGDG distribution. But it also
creates an impediment. For example, PGDG is not always able to update them as
the releases come out. Oftentimes people do need the latest, the best releases
available, and not when the packagers have time.</p>
<p>The other problem is how do extensions become popular if they&rsquo;re not there in
the first place? It creates that kind of problem where you&rsquo;re stuck with what
you have. And there&rsquo;s a problem with a discovery: how do I find them? And how
do I trust this build? Or can I even get those builds for my operating system?</p>
<p>Obviously there are some efforts that try to mitigate that by building a
docker container and you run them with just copies of those files. But
obviously there&rsquo;s a demand for a native deployment method. That is, if I
deploy my Postgres this way &mdash; say using RPM in my Red Hat-based distro, or
Debian based &mdash; I want everything else to fall into that. I don&rsquo;t want a new
system.</p>
<p>I think we, we still have a lot of work to do on that end. I&rsquo;ve been putting
some effort on our end to try and find how can we save a packager&rsquo;s time that
has basically decreased the amount of work that that needs to be done. Can we
go essentially from, here&rsquo;s the URL for the extension, figure it out. Like 80%
of them can, we just figure them out and package them automatically, and
repackage them when new versions come out, an only assign people on them for
the remaining 20% that are not building according to a certain convention. So
they need some attention.</p>
<p>This way we can get more extensions out and extract more value out of these
extensions. By using them, we&rsquo;re helping the authors gain a wider audience and
effectively create value for everybody in the community. Otherwise, they would
feel like, &ldquo;I can&rsquo;t really promote this as well as I would&rsquo;ve loved to, like
another ecosystems &mdash; RubyGems were mentioned today, and NPM, etc. It&rsquo;s easy
to get your stuff out there. Whereas in the Postgres community, it is not easy
to get your stuff out there. Because there are so many risks associated with
that, we are oftentimes working with production data, right?</p>
<p>We need to make sure there is less friction on any other side. We need to get
these extensions to get considered. That&rsquo;s at least one of the points that I
wanted to mention. I think there&rsquo;s a lot to be done and I really hope that the
conference next month in Montréal will actually be a great place to get the
best minds together again and hash out some of the ideas that we&rsquo;ve been
discussing in the past number of months.</p>
<p><strong>Floor:</strong> David, do you wanna ask your question of where people go to learn
more about extensions and find their extensions?</p>
<p><strong>David:</strong> This is something that I tried to solve a while ago with a modicum
of success &mdash; a bit. My question is, where do you all go to learn more about
extensions? To find out what extensions are available or, is there an
extension that does X, Y, Z? How do you find out if there is and, then
evaluate it? Where do you go?</p>
<p><strong>Alexey:</strong> I generally just search, I guess. I don&rsquo;t go to anything. The last
place I generally research and quite often I learned on some blog post on
sometimes on GitHub itself.</p>
<p><strong>Celeste:</strong> If you think about that project-level activity proof, and then
the social proof, I think that Postgres actually has a really unique advantage
compared to a lot of other open source projects because it&rsquo;s been going for so
long and because there is a very entrenched community. It&rsquo;s very easy to find
social proof for basically anything Postgres-related that you might want.</p>
<p>If you do a search for, like, &ldquo;I want a Postgres extension that does X&rdquo;,
you&rsquo;re going to get comparatively better Google search results because there&rsquo;s
years and years and years of search results in some cases. However, that does
come with the equal and opposite problem of when you have maintenance issues,
because things have been going for years and years, and you don&rsquo;t know whether
things have been maintained or not.</p>
<p>I&rsquo;m thinking about this from an open source management perspective, and as
somebody who is not necessarily involved in the open source development of
Postgres. I think there is a case that you could make for some amount of
community vetting of some extensions and publicizing that community-vetting,
and having a small subset of &mdash; this has some sort of seal of approval, it&rsquo;s
not gonna like nuke your database. To a certain extent, I think Postgres
already does that, because it does ship with a set of extensions by default.
In shipping with those extensions, it&rsquo;s effectively saying the upstream
Postgres community blesses these, such that we will ship Postgres with them
because we are pretty confident that these are note going to nuke your
database.</p>
<p>When I was at the CNCF, I supported a whole bunch of different open source
projects. I was everybody&rsquo;s documentation girl. So I&rsquo;m trying to throw things
at them and then hopefully you can talk about them in Montréal and maybe
something useful will come of it. Another thing that you can use is almost
like an alpha beta experimental sort of feature where you define some set of
criteria for something being alpha or experimental, you define some set of
criteria that if met, they can call themselves beta, you define some set of
criteria of something being &ldquo;production ready&rdquo; for an extensions ecosystem.
Then you can have people submit applications and then it&rsquo;s less of a mad rush.</p>
<p>I guess if I had any advice &mdash; not that Postgres needs my Charlton advice &mdash;
it would be to think about how you wanna manage this from a community
governance perspective, or else you will find yourself in utter mayhem.
There&rsquo;s a reason that the Kubernetes container network interface page
specifies that things have to be listed in alphabetical order. It&rsquo;s because
there was mayhem until we decided to list things in alphabetical order. It
seems completely silly, but it is real. [Laughs.]</p>
<p><strong>Alexey:</strong> So my next project is going to start with &ldquo;aa&rdquo;.</p>
<p><strong>Sonia:</strong> Yeah, what Celeste said. I will research about it online, normally,
and I will find something and, if I get lots of options for doing X thing, a
lot of extensions, I will go and search the documentation on postgresql.org
and then try to figure out which one is the one to start with my POC.</p>
<p><strong>Celeste:</strong> Let me flip the question for you, Sonia. In an ideal world. If
you were to try and find an extension to use for a particular task, how would
you find that extension?</p>
<p><strong>Sonia:</strong> Normally I will research it, Google it most of the times, and then try
to find out &mdash;</p>
<p><strong>Celeste:</strong> But pretend you don&rsquo;t have to Google it. Pretend that maybe
there&rsquo;s a website or a resource. What would your ideal way of doing that be?
If you had some way that would give you more of a guarantee that it was
trustworthy, or would make it easier to find, or something. Would it be a tool
like RubyGems? Would it be a page on the Postgres website&rsquo;s documentation?</p>
<p><strong>Sonia:</strong> Page! The PostgreSQL website documentation. The Postgres
documentation is like a Bible for me, so I keep researching on that. In fact,
previously when you used to Google out anything, you used to get the initial
link as the postgresql.org, the website. Nowadays you don&rsquo;t get the link as a
first link, but then I will scroll down to the page. I will try to figure out
where it is postgresql.org and then go there. That&rsquo;s the first thing. Now
since I&rsquo;ve been into the field, since a very long time, then I know, okay,
this website is authentic, I can go and check out the blogs, like who else has
used it or what is their experience or things like that.</p>
<p><strong>Jay Miller:</strong> I have to ask this only because I am new to thinking about
Postgres outside of how I interact with it from a web developer&rsquo;s perspective.
Usually I use some ORM, I use some module. I&rsquo;m a Python developer, so I use
Python, and then from there, I don&rsquo;t think about my database ever again.</p>
<p>Now I want to think about it more. I want to have a very strong relationship
with it. And we live in a world where you have to say that one of the answers
is going to be AI. One of the answers is I search for something, I get some
AI response, and, and here&rsquo;s like the&hellip;</p>
  
    <blockquote>
      <p><strong>David in comments:</strong> SLOP.</p>

    </blockquote>
  
<p><strong>Jay:</strong> Exactly, this is the problem. If I don&rsquo;t know what I should do and I
get a response, when the response could have just been, &ldquo;use this extension,
it does everything you need to do and it makes your life so much easier.&rdquo;
Instead, I wind up spending days, if not weeks, going in and fighting against
the system itself. Sonia, you mentioned having that experience. The idea or
the ability to discern when to go with some very kludgey PostgreSQL function
that makes your life miserable, to, &ldquo;oh, there&rsquo;s an extension for this
already! I&rsquo;m just going to use that.&rdquo; How do you expose that to people who are
not dumb, they&rsquo;re not vibe coding, they just finally have a reason to actively
think about what their database is doing behind the scenes.</p>
<p><strong>Sonia:</strong> If I understood your question correctly, you wanted to explore what
kind of activities a specific extension is doing.</p>
<p><strong>Jay:</strong> I would just love the like, &ldquo;hey, you&rsquo;re trying to do a thing, this
has already been solved in this extension over here, so you don&rsquo;t have to
think about it.&rdquo; Or &ldquo;you&rsquo;re trying to do something brand new, no one&rsquo;s thought
about this before, or people have thought about it before and talked about how
much of a pain it is. Maybe you should create an extension that does this. And
here&rsquo;s the steps to do that.&rdquo; Where is the proper documentation around coming
to that decision, or the community support for it?</p>
<p><strong>Sonia:</strong> That&rsquo;s a great question to discuss inside the community, to be
honest. Like, how do we go about that?</p>
<p><strong>David:</strong> Come to Montréal and help us figure it out.</p>
<p><strong>Jay:</strong> I was afraid of that answer. I&rsquo;ll see you in New York, or hopefully
Chicago on Friday.</p>
<p><strong>Floor:</strong> Fair enough, but definitely a wonderful question that we should
note down for the discussion.</p>
<p><strong>Sonia:</strong> One thing which I want to add, this just reminded me of. There was
<a href="https://talkingpostgres.com/episodes/why-mentor-postgres-developers-with-robert-haas">one podcast which I was listening with Robert Haas</a>. The podcast is organized
by one of the Microsoft folks. The podcast was revolving around how to commit
inside the PostgreSQL, or how to read what is written inside the PostgreSQL
and the ecosystem around that. The questions were related to that. That could
also help. And of course, definitely when you go to a conference, which we are
discussing at the moment, there you&rsquo;ll find a good answer. But listening to
that podcast will help you give the answers to an extent.</p>
<p><strong>Floor:</strong> I think that&rsquo;s <a href="https://talkingpostgres.com">Talking Postgres</a> with Claire Giordano, or if it
was the previous version, it was the &ldquo;Path to Citus Con&rdquo;, because that was
what it was called before.</p>
<p><strong>David:</strong> The summit that&rsquo;s in Montréal on May 13th is an unconference
session. We have a limited amount of time, so we want to collect topic ideas
and ad hoc votes for ideas of things to discuss. Last year I used a website
with Post-Its. This year I&rsquo;m just trying a spreadsheet. I posted a link to the
Google Sheet, which anybody in the world can access and pollute &mdash; I mean,
put in great ideas &mdash; and star the ideas they&rsquo;re really interested in talking
about. And I&rsquo;d really appreciate, people contributing to that. Good topics
came up today! Thank you.</p>
<p><strong>Floor:</strong> Thanks everyone for joining us. Thank you for our panelists
specifically, for sharing their experiences.</p>

        </div>

        <footer class="tags">
            <h5>More about…</h5>
            <ul>
                <li><a href="https://justatheory.com/tags/postgres/">Postgres</a></li>
                <li><a href="https://justatheory.com/tags/extensions/">Extensions</a></li>
                <li><a href="https://justatheory.com/tags/pgconf/">PGConf</a></li>
                <li><a href="https://justatheory.com/tags/summit/">Summit</a></li>
                <li><a href="https://justatheory.com/tags/celeste-horgan/">Celeste Horgan</a></li>
                <li><a href="https://justatheory.com/tags/sonia-valeja/">Sonia Valeja</a></li>
                <li><a href="https://justatheory.com/tags/alexey-palazhchenko/">Alexey Palazhchenko</a></li>
            </ul>
        </footer>
    </article>
]]></content>
	</entry>
	<entry>
		<id>https://justatheory.com/2025/04/update-control/</id>
		<title type="html"><![CDATA[Update Your Control Files]]></title>
		<link rel="alternate" type="text/html" href="https://justatheory.com/2025/04/update-control/"/>
		<updated>2025-04-28T20:08:49Z</updated>
		<published>2025-04-28T20:08:49Z</published>
		<author>
			<name>David E. Wheeler</name>
			<email>david@justatheory.com</email>
			<uri>https://justatheory.com/</uri>
		</author>
		<category scheme="https://justatheory.com/tags" term="postgres" label="Postgres"/>
		<category scheme="https://justatheory.com/tags" term="extensions" label="Extensions"/>
		<category scheme="https://justatheory.com/tags" term="pgxs" label="PGXS"/>
		<summary type="html"><![CDATA[Suggestions to PostgreSQL extension maintainers to make some tweaks to your
<code>.control</code> files and <code>Makefile</code>s.]]></summary>
		<content type="html" xml:base="https://justatheory.com/" xml:space="preserve">
<![CDATA[    <article class="post">
        <div class="text">
<p>Reviews of the extension search path patch, now <a href="https://github.com/postgres/postgres/commit/4f7f7b0">committed</a> and slated
for PostgreSQL 18, revealed a few issues with extension configuration. Based
on the ensuing discussion, and even though PostgreSQL 18 will include
workarounds, it&rsquo;s best to make adjustments to the extensions you maintain, the
better to serve existing PostgreSQL versions and to hew closer to best
practices.</p>
<p>Thus, a couple of recommendations for extension maintainers.</p>
<ol>
<li>
<p>Remove the <code>$libdir/</code> prefix from the <code>module_pathname</code> directive in the
<a href="https://www.postgresql.org/docs/current/extend-extensions.html#EXTEND-EXTENSIONS-FILES">control file</a>. The <code>$libdir/</code> requires extension modules to live in
<code>pkglibdir</code> (see <a href="https://www.postgresql.org/docs/17/app-pgconfig.html">pg_config</a>), and no other directories included in
<code>dynamic_library_path</code>, which limits where users can install it. Although
PostgreSQL 18 will ignore the prefix, the docs will also no longer
recommend it.</p>
</li>
<li>
<p>Remove the <code>directory</code> parameter from the <a href="https://www.postgresql.org/docs/current/extend-extensions.html#EXTEND-EXTENSIONS-FILES">control file</a> and the
<code>MODULEDIR</code> directive from the <code>Makefile</code>. Honestly, few people used these
directives, which installed extension files in subdirectories or even
completely different absolute directories. In some cases they may have
been useful for testing or extension organization, but the introduction of
the <a href="https://github.com/postgres/postgres/commit/4f7f7b0">extension search path</a> alleviates its use cases.</p>
</li>
</ol>
<p>These changes will future-proof your extensions and make them better ecosystem
citizens. Plus, they clean out some otherwise funky configurations that just
aren&rsquo;t necessary. Make the changes today &mdash; and while you&rsquo;re at it, test your
extensions with PostgreSQL 18 pre-releases!</p>
<p>Look, <a href="https://github.com/theory/pg-semver/pull/76">I&rsquo;ll go first</a>.</p>

        </div>

        <footer class="tags">
            <h5>More about…</h5>
            <ul>
                <li><a href="https://justatheory.com/tags/postgres/">Postgres</a></li>
                <li><a href="https://justatheory.com/tags/extensions/">Extensions</a></li>
                <li><a href="https://justatheory.com/tags/pgxs/">PGXS</a></li>
            </ul>
        </footer>
    </article>
]]></content>
	</entry>
	<entry>
		<id>https://justatheory.com/2025/04/mini-summit-user-pov/</id>
		<title type="html"><![CDATA[Mini Summit 4: The User POV]]></title>
		<link rel="alternate" type="text/html" href="https://www.meetup.com/postgres-extensions-ecosystem-mini-summits/events/306682918/"/>
		<link rel="related" type="text/html" href="https://justatheory.com/2025/04/mini-summit-user-pov/"/>
		<updated>2025-04-21T17:26:52Z</updated>
		<published>2025-04-21T17:26:52Z</published>
		<author>
			<name>David E. Wheeler</name>
			<email>david@justatheory.com</email>
			<uri>https://justatheory.com/</uri>
		</author>
		<category scheme="https://justatheory.com/tags" term="postgres" label="Postgres"/>
		<category scheme="https://justatheory.com/tags" term="extensions" label="Extensions"/>
		<category scheme="https://justatheory.com/tags" term="pgconf" label="PGConf"/>
		<category scheme="https://justatheory.com/tags" term="summit" label="Summit"/>
		<category scheme="https://justatheory.com/tags" term="celeste-horgan" label="Celeste Horgan"/>
		<category scheme="https://justatheory.com/tags" term="sonia-valeja" label="Sonia Valeja"/>
		<category scheme="https://justatheory.com/tags" term="alexey-palazhchenko" label="Alexey Palazhchenko"/>
		<summary type="html"><![CDATA[Join our fine panel on Wednesday at Extension Mini Summit #4 to hear all about
&ldquo;The User POV&rdquo; &mdash; for better and for worse!]]></summary>
		<content type="html" xml:base="https://justatheory.com/" xml:space="preserve">
<![CDATA[    <article class="post">


	<figure title="PostgresSQL Extension Mini Summit: The User POV"><a href="https://www.meetup.com/postgres-extensions-ecosystem-mini-summits/events/306682786/">
			<img src="https://justatheory.com/shared/extension-ecosystem-summit/user-pov.jpeg" alt="Orange card with large black text reading “The User POV”. Smaller text above reads “04.23.2025” and below reads “Celeste Horgan (Aiven), Sonia Valeja (Percona), &amp; Alexey Palazhchenko (FerretDB)”" title="PostgresSQL Extension Mini Summit: The User POV" />
		</a>
	</figure>

        <div class="text">
<p>And we&rsquo;re back.</p>
<p>This Wednesday, April 9 at noon America/New_York (16:00 UTC) for <a href="https://www.meetup.com/postgres-extensions-ecosystem-mini-summits/events/306682918/" title="Postgres Extensions Ecosystem Mini-Summit #4">Extension
Mini Summit #4</a>, where our panel consisting of <a href="https://www.linkedin.com/in/celeste-horgan-b65b5a1a/">Celeste Horgan</a> (Aiven),
<a href="https://www.linkedin.com/in/sonia-valeja-69517a140/">Sonia Valeja</a> (Percona), and <a href="https://www.linkedin.com/in/alexeypalazhchenko/overlay/about-this-profile/">Alexey Palazhchenko</a> (FerretDB) will discuss
&ldquo;The User POV&rdquo;. This session will be a terrific opportunity for those of us
who develop extensions to get an earful from the people who use them, in both
anger and joy. Bang on the <a href="https://www.meetup.com/postgres-extensions-ecosystem-mini-summits/" title="Postgres Extension Ecosystem Mini-Summit on Meetup">Meetup</a> to register for this live video session.</p>

        </div>

        <footer class="tags">
            <h5>More about…</h5>
            <ul>
                <li><a href="https://justatheory.com/tags/postgres/">Postgres</a></li>
                <li><a href="https://justatheory.com/tags/extensions/">Extensions</a></li>
                <li><a href="https://justatheory.com/tags/pgconf/">PGConf</a></li>
                <li><a href="https://justatheory.com/tags/summit/">Summit</a></li>
                <li><a href="https://justatheory.com/tags/celeste-horgan/">Celeste Horgan</a></li>
                <li><a href="https://justatheory.com/tags/sonia-valeja/">Sonia Valeja</a></li>
                <li><a href="https://justatheory.com/tags/alexey-palazhchenko/">Alexey Palazhchenko</a></li>
            </ul>
        </footer>
    </article>
]]></content>
	</entry>
	<entry>
		<id>https://justatheory.com/2025/04/fix-postgres-strchrnul/</id>
		<title type="html"><![CDATA[Fix Postgres <code>strchrnul</code> Compile Error on macOS 15.4]]></title>
		<link rel="alternate" type="text/html" href="https://justatheory.com/2025/04/fix-postgres-strchrnul/"/>
		<updated>2025-04-16T19:03:26Z</updated>
		<published>2025-04-16T19:03:26Z</published>
		<author>
			<name>David E. Wheeler</name>
			<email>david@justatheory.com</email>
			<uri>https://justatheory.com/</uri>
		</author>
		<category scheme="https://justatheory.com/tags" term="postgres" label="Postgres"/>
		<category scheme="https://justatheory.com/tags" term="macos" label="macOS"/>
		<category scheme="https://justatheory.com/tags" term="pgenv" label="pgenv"/>
		<summary type="html"><![CDATA[A fix for the <code>error: 'strchrnul' is only available on macOS 15.4 or newer</code>
Postgres compile error.]]></summary>
		<content type="html" xml:base="https://justatheory.com/" xml:space="preserve">
<![CDATA[    <article class="post">
        <div class="text">
<p>Just a quick note to users of <a href="https://github.com/theory/pgenv" title="PostgreSQL binary manager">pgenv</a> and anyone else who compiles Postgres on
macOS. In macOS 15.4, Apple introduced a new API, <code>strchrnul</code>, which is common
from other platforms. As a result attempting to compile Postgres on 15.4 and
later will lead to this error:</p>
<pre tabindex="0"><code>snprintf.c:414:27: error: &#39;strchrnul&#39; is only available on macOS 15.4 or newer [-Werror,-Wunguarded-availability-new]
  414 |                         const char *next_pct = strchrnul(format + 1, &#39;%&#39;);
      |                                                ^~~~~~~~~
snprintf.c:366:14: note: &#39;strchrnul&#39; has been marked as being introduced in macOS 15.4 here, but the deployment target is macOS 15.0.0
  366 | extern char *strchrnul(const char *s, int c);
      |              ^
snprintf.c:414:27: note: enclose &#39;strchrnul&#39; in a __builtin_available check to silence this warning
</code></pre><p>Tom Lane <a href="https://postgr.es/m/385134.1743523038@sss.pgh.pa.us">chased down and committed the fix</a>, which will be in the next
releases of Postgres 13-17. It should also go away once macOS 16.0 comes out.
But in the meantime, set <code>MACOSX_DEPLOYMENT_TARGET</code> to the current OS release
to avoid the error:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl"><span class="nb">export</span> <span class="nv">MACOSX_DEPLOYMENT_TARGET</span><span class="o">=</span><span class="s2">&#34;</span><span class="k">$(</span>sw_vers -productVersion<span class="k">)</span><span class="s2">&#34;</span>
</span></span></code></pre></div><p>If you use <a href="https://github.com/theory/pgenv" title="PostgreSQL binary manager">pgenv</a>, you can <a href="https://github.com/theory/pgenv/issues/93" title="theory/pgenv#93">add it to your configuration</a>. It will need to be
added to all the version configs, too, unless they don&rsquo;t exist and you also set:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl"><span class="nv">PGENV_WRITE_CONFIGURATION_FILE_AUTOMATICALLY</span><span class="o">=</span>no
</span></span></code></pre></div>
        </div>

        <footer class="tags">
            <h5>More about…</h5>
            <ul>
                <li><a href="https://justatheory.com/tags/postgres/">Postgres</a></li>
                <li><a href="https://justatheory.com/tags/macos/">macOS</a></li>
                <li><a href="https://justatheory.com/tags/pgenv/">pgenv</a></li>
            </ul>
        </footer>
    </article>
]]></content>
	</entry>
	<entry>
		<id>https://justatheory.com/2025/04/mini-summit-three/</id>
		<title type="html"><![CDATA[Mini Summit 3 Transcript: Apt Extension Packaging]]></title>
		<link rel="alternate" type="text/html" href="https://justatheory.com/2025/04/mini-summit-three/"/>
		<updated>2025-04-14T22:48:22Z</updated>
		<published>2025-04-14T22:48:22Z</published>
		<author>
			<name>David E. Wheeler</name>
			<email>david@justatheory.com</email>
			<uri>https://justatheory.com/</uri>
		</author>
		<category scheme="https://justatheory.com/tags" term="postgres" label="Postgres"/>
		<category scheme="https://justatheory.com/tags" term="extensions" label="Extensions"/>
		<category scheme="https://justatheory.com/tags" term="pgconf" label="PGConf"/>
		<category scheme="https://justatheory.com/tags" term="summit" label="Summit"/>
		<category scheme="https://justatheory.com/tags" term="debian" label="Debian"/>
		<category scheme="https://justatheory.com/tags" term="apt" label="APT"/>
		<category scheme="https://justatheory.com/tags" term="christoph-berg" label="Christoph Berg"/>
		<category scheme="https://justatheory.com/tags" term="transcript" label="Transcript"/>
		<summary type="html"><![CDATA[Last week Christoph Berg, who maintains PostgreSQL&rsquo;s APT packaging system,
gave a very nice talk on that system. Herein lie the transcript and links to
the slides and video.]]></summary>
		<content type="html" xml:base="https://justatheory.com/" xml:space="preserve">
<![CDATA[    <article class="post">


	<figure title="PostgresSQL Extension Mini Summit: Implementing an Extension Search Patch">
			<img src="https://justatheory.com/shared/extension-ecosystem-summit/apt-packaging-card.jpeg" alt="Orange card with large black text reading “APT Extension Packaging”. Smaller text below reads “Christoph Berg, Debian/Cybertec” and “04.09.2025”. A photo of Christoph looking cooly at the camera appears on the right." title="PostgresSQL Extension Mini Summit: Implementing an Extension Search Patch" />
	</figure>

        <div class="text">
<p>Last week <a href="https://www.df7cb.de">Christoph Berg</a>, who maintains PostgreSQL&rsquo;s APT packaging system,
gave a very nice talk on that system at the third PostgreSQL <a href="https://www.meetup.com/postgres-extensions-ecosystem-mini-summits/" title="Postgres Extension Ecosystem Mini-Summit on Meetup">Extension
Mini-Summit</a>. We&rsquo;re hosting five of these virtual sessions in the
lead-up to the main <a href="https://www.pgevents.ca/events/pgconfdev2025/schedule/session/241/" title="PGConf.dev: Extensions Ecosystem Summit">Extension Summit</a> at <a href="https://2025.pgconf.dev" title="PostgreSQL Development Conference 2025">PGConf.dev</a> on May 13 in Montréal,
Canada. Check out Christoph&rsquo;s session on April 9:</p>
<ul>
<li><a href="https://www.youtube.com/watch?v=35a7YCEfaRY">Video</a></li>
<li><a href="https://justatheory.com/shared/extension-ecosystem-summit/apt-extension-packaging.pdf">Slides</a></li>
</ul>
<p>There are two more Mini-Summits coming up:</p>
<ul>
<li>April 23: <a href="https://www.meetup.com/postgres-extensions-ecosystem-mini-summits/events/306682918/">The User POV</a>. Join our panelist of extension users for a
lively discussion on tool choice, due diligence, and their experience
running extensions.</li>
<li>May 7: <a href="https://www.meetup.com/postgres-extensions-ecosystem-mini-summits/events/306551747/">Extension Management in CloudNativePG&quot;</a>. <a href="https://cloudnative-pg.io" title="Run PostgreSQL. The Kubernetes way.">CNPG</a> maintainer
<a href="https://www.gabrielebartolini.it">Gabriele Bartolini</a> will talk about recent developments in extension
management in this official <a href="https://www.cncf.io/projects/cloudnativepg/">CNCF project</a>.</li>
</ul>
<p>Join <a href="https://www.meetup.com/postgres-extensions-ecosystem-mini-summits/" title="Postgres Extension Ecosystem Mini-Summit on Meetup">the Meetup</a> to attend!</p>
<p>And now, without further ado, thanks to the efforts of <a href="https://dev.to/@floord">Floor Drees</a>, the
thing you&rsquo;ve all been waiting for: the transcript!</p>
<h2 id="introduction">Introduction</h2>
<p>David Wheeler introduced the organizers:</p>
<ul>
<li><a href="https://justatheory.com/">David Wheeler</a>, Principal Architect at <a href="https://tembo.io/">Tembo</a>, maintainer of <a href="https://pgxn.org/">PGXN</a></li>
<li><a href="https://ca.linkedin.com/in/yrashk">Yurii Rashkovskii</a>, <a href="https://omnigres.com/">Omnigres</a></li>
<li><a href="https://pgxn.org/user/keithf4/">Keith Fiske</a>, <a href="https://www.crunchydata.com/">Crunchy Data</a></li>
<li><a href="https://dev.to/@floord">Floor Drees</a>, Principal Program Manager at <a href="https://enterprisedb.com" title="EnterpriseDB">EDB</a>, PostgreSQL CoCC member,
PGDay Lowlands organizer</li>
</ul>
<p><a href="https://www.df7cb.de">Christoph Berg</a>, <a href="https://wiki.postgresql.org/wiki/Apt">PostgreSQL APT</a> developer and maintainer par excellence,
talked through the technical underpinnings of developing and maintaining
PostgresSQL and extension packages.</p>
<p>The stream and the closed captions available for the recording are supported
by <a href="https://2025.pgconf.dev" title="PostgreSQL Development Conference 2025">PGConf.dev</a> and its gold level <a href="https://2025.pgconf.dev/sponsors.html">sponsors</a>: Google, AWS, Huawei, Microsoft,
and EDB.</p>
<h2 id="apt-extension-packaging">APT Extension Packaging</h2>
<p>Speaker: <a href="https://www.df7cb.de">Christoph Berg</a></p>
<p>Hello everyone. So what is this about? It&rsquo;s about packaging things for
PostgresSQL for Debian distributions. We have PostgreSQL server packages,
extension packages, application packages and other things. The general
workflow is that we are uploading packages to Debian unstable first. This is
sort of the master copy, and from there things eventually get to Debian
testing. Once they&rsquo;re being released, they end up in Debian stable.</p>
<p>Perhaps more importantly for the view today is that the same package is then
also rebuilt for <a href="https://apt.postgresql.org">apt.postgresql.org</a> for greater coverage of Postgres major
versions. And eventually the package will also end up in an Ubuntu release
because, Ubuntu is copying Debian unstable, or Debian testing, every six
months and then doing their release from there. But I don&rsquo;t have any stakes in
that.</p>
<p>For an overview of what we are doing in this Postgres team, I can just briefly
show you <a href="https://qa.debian.org/developer.php?email=team%2bpostgresql%40tracker.debian.org">this overview page</a>. That&rsquo;s basically the view of packages we are
maintaining. Currently it&rsquo;s 138, mostly Postgres extensions, a few other
applications, and whatever comes up in the Postgres ecosystem.</p>
<p>To get a bit more technical let&rsquo;s look at how the Debian packages look from
the inside.</p>
<p>We have two sorts of packages. We have source packages, which are the source
of things that are built. The way it works is that we have a directory inside
that source tree called Debian, which has the configuration bits about how the
packages created should look like. And from this the actual binary packages,
the <code>.deb</code> files are built.</p>
<p>Over the past years, I&rsquo;ve got a few questions about, &ldquo;how do I get my
application, my extension, and so on packaged?&rdquo; And I wrote that down as <a href="https://salsa.debian.org/postgresql/postgresql-common/blob/master/doc/postgresql-debian-packaging.md">a
document</a>. Hopefully to answer most of the questions. And I kind of think that
since I wrote this down last year, the questions somehow stopped. If you use
that document and like it, please tell me because no one has ever given me any
feedback about that. The talk today is kind of loosely based on this document.</p>
<p>I&rsquo;m not going to assume that you know a whole lot of Debian packaging, but I
can&rsquo;t cover all the details here, so I&rsquo;ll keep the generic bits a bit
superficial and dive a bit more into the Postgres-specific parts.</p>
<p>Generally, the most important file in the Debian package is this Debian
control file, which describes the source and the binary packages. This is
where the dependencies are declared. This is where the package description
goes, and so on. In the Postgres context, we have the first problem that, we
don&rsquo;t want to encode any specific PG major versions inside that control file,
so we don&rsquo;t have to change it each year once a new Postgres version comes out.</p>
<p>This is why, instead of a Debian control file, we actually have a
<code>debian/control.in</code> file, and then there&rsquo;s a tool called <code>pg_buildext</code>,
originally written by <a href="https://tapoueh.org/about/">Dimitri Fontaine</a>, one or two decades ago, and then
maintained by me and the other Postgres maintainers since then. That tool is,
among other things, responsible for rewriting that <code>control.in</code> file to the
actual <code>control</code> file.</p>
<p>I just picked one random extension that I happen to have on the system here.
This postgresql-semver extension, the upstream author is actually David here.
In <a href="https://salsa.debian.org/debian/postgresql-semver/-/blob/debian/master/debian/control.in?ref_type=heads">this control file</a> we say the name of the package, the name of the
Debian maintainer &mdash; in this case the group &mdash; there&rsquo;s a few uploaders, there&rsquo;s
build dependencies and other things that are omitted here because, the slide
was already full. And then we have, next to this source section, we have a
package section and here we have this placeholder:
<code>postgresql-PGVERSION-semver</code>.</p>
<p>Once we feed this <code>control.in</code> file through this <code>pg_buildext</code> tool, it&rsquo;ll
generate the control file, which expands this <code>PGVERSION</code> placeholder to
actually a list of packages. This is just a mechanical translation; we have
<code>postgresql-15-semver</code>, 16, 17 and whatever other version is supported at that
point.</p>
<p>Once a new PostgreSQL version is released, PostgreSQL 18 comes out, we don&rsquo;t
have to touch anything in this <code>control.in</code> file. We just rerun this
<code>pg_buildext</code> update control command, and it&rsquo;ll automatically add the new
package.</p>
<p>There&rsquo;s about half a dozen layers talking to each other when building a
package On the lowest level, no one actually touches it at at that level. But
Debian packages are actually <code>ar</code> archives, the one from library fame, was yet
another, archive inside control called <code>control.tar.xz</code> or something. But. No
one actually touches it at that level anymore.</p>
<p>We have <code>dpkg</code> on top of that, which provides some building blocks for
creating actual Debian packages. So you would call <code>dpkg-builddeb</code> and other
<code>dpkg</code> helpers to actually create a package from that. But because this is
complicated, there&rsquo;s yet another level on top of that, called <code>debhelper</code>.
This is the actual standard for building Debian package nowadays. So instead
of invoking all the <code>dpkg</code> tools directly, everyone uses the step helper tools
which provide some wrappers for the most common build steps that are executed.
I will show an example in a second.</p>
<p>Next to these wrappers for calling &ldquo;create me a package&rdquo;, &ldquo;copy all files&rdquo;,
and so on, there&rsquo;s also this program called <code>dh</code>, it&rsquo;s called a sequencer
because it&rsquo;ll invoke all the other tools in the correct order. So let me show
you an example before it gets too confusing. The top level command to actually
build a Debian package &mdash; to create the binary packages from the source
package &mdash; is called <code>dpkg-buildpackage</code>. It will invoke this <code>debian/rules</code>
file. The <code>debian/rules</code> file is where all the commands go that are used to
build a package. For historical reasons it&rsquo;s a Makefile. In the shortest
incantation it just says, &ldquo;for anything that is called invoke this <code>dh</code>
sequencer with some arguments.&rdquo;</p>
<p>Let me skip ahead one more slide and if we&rsquo;re actually running it like that,
it kind of looks like this. I&rsquo;m invoking <code>dpkg-buildpackage</code>,
<code>dpkg-buildpackage</code> invokes <code>debian/rules</code> with target name <code>debian/rules</code>,
invokes <code>dh</code> and <code>dh</code> then calls all the helper steps that are required for
getting the package to run. The first one would be
<code>dh_update_autotools_config</code>, so if any ancient auto conf things are used,
it&rsquo;ll be updated. The package will be reconfigured, and then it would it will
be built and so on.</p>
<p>This was the generic Debian part. Postgres actually adds more automation on
top of that. This is this &ldquo;<code>dh</code> with <code>pgxs</code> step.&rdquo; Let me go back two slides.
We have this <code>pgxs</code> plugin for <code>debhelper</code> which adds more build steps that
actually call out this tool called <code>pg_buildext</code>, which interfaces with the
<code>pgxs</code> build system in your extension package. Basically <code>debhelper</code> calls
this <code>pgxs</code> plugin, and this <code>pgxs</code> plugin called <code>pg_buildext</code>, and this one
finally invokes the <code>make</code> command, including any <code>PG_CONFIG</code> or whatever
settings that are required for compiling this extension.</p>
<p>If we go back to the output here, we can see that one of the steps here is
actually invoking this <code>pg_buildext</code> tool and <code>pg_buildext</code> will then continue to
actually compile this extension.</p>
<p>This means in the normal case for extensions that don&rsquo;t do anything special,
you will actually get away with a very short <code>debian/rules</code> file. Most of the
time it&rsquo;s just a few lines. In this case I added more configuration for two of
the helpers. In this step, I told <code>dh_installchangelogs</code> that, in this
package, the changelog has a file name that <code>dh_installchangelogs</code> doesn&rsquo;t
automatically recognize. Usually if you have a file called <code>changelog</code>, it
will be automatically picked up. But in this case I told it to use this file.
Then I&rsquo;m telling it that some documentation file should be included in all
packages. Everything else is standard and will be picked up by the default
Debian tool chain.</p>
<p>Another thing specific for the Postgres bits is that we like to run the
package tests at build time. One of the build steps that gets executed is this
<code>dh_pgxs</code> test wrapper, which in turn invokes <code>pg_buildext install check</code>.
That will create a new Postgres cluster and proceed to invoke <code>pg_regress</code> on
that package. This is actually the place where this patch that <a href="https://justatheory.com/2025/04/mini-summit-two/" title="2025 Extension Mini Summit 2: Implementing an extension search path">Peter was
talking about two weeks ago</a> is coming into play.</p>
<p>The actual call chain of events is that <code>dh_pgxs</code> starts
<code>pg_buildext installcheck</code>, <code>pg_buildext</code> starts <code>pg_virtualenv</code>, which is a
small wrapper shipped with Debian &mdash; but not very specific to Debian &mdash; that
just creates a new Postgres environment and then executes any command in that
environment. This is actually very handy to create test instances. I&rsquo;m using
that all day. So if anyone is asking me, &ldquo;can you try this on Postgres 15?&rdquo; or
something, I&rsquo;m using <code>pg_virtualenv -v 15</code> to fire up a temporary Postgres
instance. I can then play with it, break it or something, and, as soon as I
exit the shell that <code>pg_virtualenv</code> opens, the cluster will be deleted again.</p>
<p>In the context of <code>pg_buildext</code>, what <code>pg_virtualenv</code> is doing here is that
it&rsquo;s calling <code>pg_createcluster</code> to actually fire up that instance and it&rsquo;s
passing an option to set this <code>extension_control_path</code> to the temporary
directory that the extension was installed to during the build process. While
we are compiling the package, the actual install command is invoked, but it
does not write to <code>/usr/share/postgresql</code> or something, but it writes to a
subdirectory of the package build directory. So it&rsquo;s writing to
<code>debian/$PACKAGE/$THE_ORIGINAL_PATH</code>.</p>
<p>And that&rsquo;s why before we had this in Postgres 18, the Debian packages had a
patch that does the same thing as this <code>extension_control_path</code> setting. It
was called <code>extension_destdir</code>. It was basically doing the same thing except
that it was always assuming that you had this structure of some prefix and
then the original path. The new patch is more flexible that: it can be an
arbitrary directory. The old <code>extension_destdir</code> patch assumes that it&rsquo;s
always <code>/$something/usr/share/postgres/$something</code>. I&rsquo;m glad that that patch
finally went in and we can still run the test at build time.</p>
<p>So far we&rsquo;ve only seen how to build things for one Postgres version. The
reason why this <code>pg_buildext</code> layer is there is that this tool is the one that
does the building for each version in turn. So <code>pg_buildext</code> will execute any
command pass to it for all the versions that are currently supported by that
package. What&rsquo;s happening here is that we have one source package for
extension covered. And that one source package then builds a separate binary
for each of the major versions covered. But it does this from a single build
run.</p>
<p>In contrast to what <a href="https://github.com/devrimgunduz" title="Devrim Gündüz">Devrim</a> is doing with the <a href="https://yum.postgresql.org">RPM packages</a>, he&rsquo;s actually
in invoking the builds several times separately for each version. We could
also have done this, it&rsquo;s just a design choice that, we&rsquo;ve done it one way
round and he&rsquo;s doing it the other way round.</p>
<p>To tell <code>pg_buildext</code> which versions are supported by the package, there&rsquo;s a
file called <code>debian/pgversions</code> which usually just contains a single line
where you can either say, &ldquo;all versions are supported&rdquo;, or you can say that
&ldquo;anything, starting 9.1&rdquo; or &ldquo;starting PostgreSQL 15 and later&rdquo; is supported.
In this example here, 9.1+ is actually copied from the semver package because
the requirement there was that it needs to support extensions and that&rsquo;s when
9.1 was introduced. We don&rsquo;t care about these old versions anymore, but the
file was never changed since it was written.</p>
<p>We know how to build several Postgres major versions from a source package.
Now the next axis is supporting multiple architectures. The build is invoked
separately for each architecture. This single source package is compiled
several times for each architecture. On <a href="https://apt.postgresql.org">apt.postgresql.org</a>, we&rsquo;re currently
supporting amd64, arm64 and ppc64el. We used to have s390x support, but I
killed that recently because IBM is not supporting any build machine anymore
that actually works. Inside Debian there are a lot more architecture
supported.</p>
<p>There&rsquo;s also something called Debian ports, which are not official
architectures, but either new architectures that are being introduced like
this loong64 thing, or it&rsquo;s sometimes it&rsquo;s old architectures that are not
official anymore, but are still being kept around like the Sparc one. There&rsquo;s
also some experimental things like hurd-amd64, hurd-i386. Isn&rsquo;t even Linux.
This is a hurd kernel, but still running everything Debian on top of it, and
some time ago it even started to support Postgres. The packages are even
passing the tests there, which is kind of surprising for something that hasn&rsquo;t
ever seen any production.</p>
<p>For Postgres 17, <a href="https://buildd.debian.org/status/package.php?p=postgresql-17">it looks like this</a>. The architectures in the upper half of
that table are the official ones, and the gray area on the bottom are the
unofficial ones that are, let&rsquo;s say, less supported. If anything breaks in the
upper half, maintainers are supposed to fix it. If anything breaks in the
lower half, people might care or might not care.</p>
<p>I like to keep it working because if Postgres breaks, all the other software
that needs it &mdash; like <code>libpq</code>, so it&rsquo;s not even extensions, but any software
that depends on <code>libpq</code> &mdash; wouldn&rsquo;t work anymore if that&rsquo;s not being built
anymore. So I try to keep everything updated, but some architectures are very
weird and just don&rsquo;t work. But at the moment it looks quite good. We even got
Postgres 18 running recently. There were some problems with that until last
week, but I actually got that fixed on the <a href="http://archives.postgresql.org/pgsql-hackers/" title="pgsql-hackers Archives">pg-hackers list</a>.</p>
<p>So, we have several Postgres major versions. We have several architectures.
But we also have multiple distribution releases. For Debian this is currently
sid (or unstable), trixie, (currently testing), bookworm, bullseye, Ubuntu
plucky, oracular, noble, jammy, focal &mdash; I get to know one funny adjective
each year, once Ubuntu releases something new. We&rsquo;re compiling things for each
of those and because compiling things yields a different result on each of
these distributions, we want things to have different version numbers so
people can actually tell apart where the package is coming from.</p>
<p>Also, if you are upgrading &mdash; let&rsquo;s say from Debian bullseye to Debian
bookworm &mdash; you want new Postgres packages compiled for bookworm. So things
in bookworm need to have higher version numbers than things in bullseye so you
actually get an upgrade if you are upgrading the operating system. This means
that packages have slightly different version numbers, and what I said before
&mdash; that it&rsquo;s just one source package &mdash; it&rsquo;s kind of not true because, once
we have new version numbers, we also get new source packages.</p>
<p>But these just differ in a new change log entry. It&rsquo;s basically the same
thing, they just get a new change log entry added, which is automatically
created. That includes this, plus version number part. Wwhat we&rsquo;re doing is
that the original version number gets uploaded to Debian, but packages that
show up on <a href="https://apt.postgresql.org">apt.postgresql.org</a> have a marker inside the version number that
says &ldquo;PGDG plus the distribution release number&rdquo;. So for the Ubuntu version,
it says <code>PGDG-24.0.4</code> or something and then Debian is, it&rsquo;s plus
120-something.</p>
<p>The original source package is tweaked a bit using <a href="https://salsa.debian.org/postgresql/apt.postgresql.org/-/blob/master/jenkins/generate-pgdg-source">this shell script</a>. I&rsquo;m
not going to show it now because it&rsquo;s quite long, but, you can look it up
there. This is mostly about creating these extra version numbers for these
special distributions. It applies a few other tweaks to get packages working
in older releases. Usually we can just take the original source or source
package and recompile it on the older Debians and older Ubuntus. But sometimes
build dependencies are not there, or have different names, or some feature
doesn&rsquo;t work. In that case, this <code>generate-pgdg-source</code> has some tweaks, which
basically invokes <code>set</code> commands on the source package to change some minor
bits. We try to keep that to minimum, but sometimes, things don&rsquo;t work out.</p>
<p>For example, when <code>set compression</code> support was new in Postgre, compiling the
newer Postgres versions for the older releases required some tweaks to disable
that on the older releases, because they didn&rsquo;t have the required libraries
yet.</p>
<p>If you&rsquo;re putting it all together, you get this combinatorial explosion. From
one project, <code>postgresql-semver</code>, we get this many builds and each of those
builds &mdash; I can actually show you <a href="https://jengus.postgresql.org/job/postgresql-semver-binaries/">the actual page</a> &mdash; each of those builds
is actually several packages. If you look at the list of artifacts there, it&rsquo;s
creating one package for PostgreSQL 10, 11, 12, and so on. At the moment it&rsquo;s
still building for PostgreSQL 10 because I never disabled it. I&rsquo;m not going to
complain if the support for the older versions is broken at some point. It&rsquo;s
just being done at the moment because it doesn&rsquo;t cost much.</p>
<p>And that means that, from one source package quite a lot of artifacts are
being produced. The current statistics are this:</p>
<ul>
<li>63355 .deb files</li>
<li>2452 distinct package names</li>
<li>2928 source packages</li>
<li>210 distinct source package names</li>
<li>47 GB repository size</li>
</ul>
<p>We have 63,000 <code>.deb</code> files. That&rsquo;s 2,400 distinct package names &mdash; so
<code>package-$PGVERSION</code> mostly built from that many source packages. The actual
number of distinct source packages is 210. Let&rsquo;s say half of that is
extensions. Then there&rsquo;s of course separate source packages for Postgres 10,
11, 12, and so on, and there&rsquo;s a few application packages. Yeah, in total the
repository is 47 gigabytes at the moment.</p>
<p>This is current stuff. All the old distributions are moved to
<a href="https://apt-archive.postgresql.org">apt-archive.postgresql.org</a>. We are only keeping the latest built inside the
repository. So if you&rsquo;re looking for the second-latest version of something,
you can go to <a href="https://apt-archive.postgresql.org">apt-archive.postgresql.org</a>. I don&rsquo;t have statistics for that,
but that is much larger. If I had to guess, I would say probably something
like 400 gigabytes/ I could also be off by with guessing.</p>
<p>That was how to get from the source to the actual packages. What we&rsquo;re doing
on top of that is doing more testing. Next to the tests that we are running at
build time, we are also running tests at installation time, or once the
package is installed we can run tests. For many packages, that&rsquo;s actually the
same tests, just rerun on the actual binaries as installed, as opposed to
<code>debian/something</code>. Sometimes it&rsquo;s also different tests For some tests it&rsquo;s
just simple smoke tests. id everything get installed to the correct location
and does the service actually start, sometimes it&rsquo;s more complex things.</p>
<p>Many test suites are meant to be run at compilation time, but we want to run
them at install time. This is kind of <code>make check</code>, <code>make installcheck</code>, but
some projects are not really prepared to do that. They really want, before you
can run the test suite, you have to basically compile everything. I try to
avoid that because things that work at compilation time might not mean that
it&rsquo;s running at install time because we forgot to install some parts of the
build.</p>
<p>I try to get the test suite running with as few compilation steps as possible,
but sometimes it just doesn&rsquo;t work. Sometimes the <code>Makefile</code> assumes that
<code>configure</code> was run and that certain variables got substituted somewhere.
Sometimes you can get it running by calling <code>make</code> with more parameters, but
it tends to break easily if something changes upstream. If you&rsquo;re an extension
author, please think of someone not compiling your software but still wanting
to run the tests.</p>
<p>What we&rsquo;re doing there is to run these tests each month. On each day, each
month, a random set of tests is scheduled &mdash; that&rsquo;s three or four per day or
something. It&rsquo;s not running everything each day because if something breaks, I
can&rsquo;t fix 50 things in parallel. You can see <a href="https://jengus.postgresql.org/view/Testsuite/">test suite tab</a> there. At the
moment, actually everything worked. For example, we could check something&hellip;</p>
<p>With <a href="https://jengus.postgresql.org/view/Testsuite/job/bgw-replstatus-autopkgtest/">this background worker rapid status</a> thing, that&rsquo;s an extension that
<a href="https://www.hagander.net" title="Magnus Hagander">Magnus</a> wrote sometime ago. Everything is running fine,  but something was
broken in January. Ah, there, the S390 machine was acting up. That was
probably a pretty boring failure. Probably something with network broken. Not
too interesting. This is actually why I shut down this architecture, because
the built machine was always having weird problems. This is how we keep the
system actually healthy and running.</p>
<p>One thing that&rsquo;s also catching problems is called <a href="https://jengus.postgresql.org/view/Testsuite/job/debcheck/">debcheck</a>. This is a static
installability analysis tool by Debian. You feed it a set of packages and it
will tell you if everything is installable. In this case, something was not
installable on Debian testing. And &mdash; if we scroll down there &mdash; it would
say that <code>postgresql-10-icu-ext</code> was not installable because this <code>lib-icu-72</code>
package was missing. What happened there is that project or library change
so-name, from time to time, and in this case, in Debian, ICU was moving from
72 to 76 and I just had to recompile this module to make it work.</p>
<p>Usually if something breaks, it&rsquo;s usually on the development suites &mdash; sid,
trixie, unstable, and testing &mdash; the others usually don&rsquo;t break. If the
others break, then I messed something up.</p>
<p>That was a short tour of how the packaging there works. For open issues or
pain pain points that there might be, there are packages that don&rsquo;t have any
tests. If we are looking at, what was the number, 63,000 packages, I&rsquo;m not
going to test them by hand, so we really rely on everything being tested
automatically. Extensions are usually very well covered, so there&rsquo;s usually
not a problem.</p>
<p>Sometimes there&rsquo;s extensions that don&rsquo;t have tests, but they are kind of hard
to test. For example, modules that don&rsquo;t produce any SQL outputs like
<a href="https://www.postgresql.org/docs/current/auto-explain.html">auto_explain</a> are kind of hard to test because the output goes somewhere
else. I mean, in the concrete case, auto_explain probably has tests, but it&rsquo;s
sometimes it&rsquo;s things that are not as easily testable as new data types.</p>
<p>Things that usually don&rsquo;t have tests by nature is GUI applications; any
program that opens a window is hard to test. But anything that produces text
output is usually something I like to cover. Problems with software that we
are shipping and that actually breaks in production is usually in the area
where the tests were not existing before.</p>
<p>One problem is that some upstream extensions only start supporting Postgres 18
after the release. People should really start doing that before, so we can
create the packages before the 18.0 release. Not sure when the actual best
point to start would be; maybe today because yesterday was feature freeze. But
sometime during the summer would be awesome. Otherwise <a href="https://github.com/devrimgunduz" title="Devrim Gündüz">Devrim</a> and I will go
chasing people and telling them, &ldquo;please fix that.&rdquo;</p>
<p>We have of course packages for Postgres 18, but we don&rsquo;t have extension
packages for Postgres 18 yet. I will start building that perhaps now, after
feature freeze. Let&rsquo;s see how, how much works and not. Usually more than half
of the packages just work. Some have trivial problems and some have hard
problems, and I don&rsquo;t know yet if Postgres 18 will be a release with more hard
problems or more trivial problems.</p>
<p>Another problem that we&rsquo;re running into sometimes is that upstream only cares
about 64bit Intel and nothing else. We recently stopped caring about 32 bits
for extensions completely. So Debian at postgresql.org is not building any
extension packages for any 32-bit architectures anymore. We killed i386, but
we also killed arm, and so on, on the Debian side.</p>
<p>The reason is that there are too many weird bugs that I have to fix, or at at
least find, and then chase upstreams about fixing their 32-bit problems. They
usually tell me &ldquo;I don&rsquo;t have any 32-bit environment to test,&rdquo; and they don&rsquo;t
really care. In the end, there are no users of most extensions on 32-bit
anyway. So we decided that it just doesn&rsquo;t make sense to fix that. In order to
prevent the problems from appearing in the first place, we just disabled
everything 32-bit for the extensions.</p>
<p>The server is still being built. It behaves nicely. I did find a 32-bit
problem in Postgres 18 last week, but that was easy to fix and not that much
of a problem. But my life got a lot better once I started not caring about
32-bit anymore. Now the only problem left is big-endian s390x in Debian, but
that doesn&rsquo;t cause that many problems.</p>
<p>One thing where we are only covering a bit of stuff is if projects have
multiple active branches. There are some projects that do separate releases
per Postgres major version. For example, <a href="https://pgxn.org/dist/pgaudit/">pgaudit</a> has separate branches for
each of the Postgres versions, so we are tracking those separately, just to
make pgaudit available. <a href="https://pgxn.org/search?q=pg_hint_plan&amp;amp;in=dists">pg-hint-plan</a> is the same, and this Postgres graph
extension thing (<a href="https://pgxn.org/dist/apacheage/">Apache Age</a>) is also the same. This is just to support all
the Postgres major versions. We have separate source packages for each of the
major versions, which is kind of a pain, but doesn&rsquo;t work otherwise.</p>
<p>Where we are not supporting several branches is if upstream is maintaining
several branches in parallel. For example, <a href="https://postgis.net" title="PostGIS">PostGIS</a> is maintaining 3.5, 3.4,
3.3 and so on, and we are always only packaging the latest one. Same for
<a href="https://www.pgpool.net/">Pgpool</a>, and there&rsquo;s probably other projects that do that. We just don&rsquo;t do
that because it would be even more packages we have to take care of. So we are
just packaging the latest one, ad so far there were not that many complaints
about it.</p>
<p>Possibly next on the roadmap is looking at what to do with <a href="https://www.rust-lang.org">Rust</a> extensions.
We don&rsquo;t have anything Rust yet, but that will probably be coming. It&rsquo;s
probably not very hard; the question is just how much of the build
dependencies of the average extension is already covered in Debian packages
and how much would we have to build or do we just go and render all the
dependencies or what&rsquo;s the best way forward?</p>
<p>There&rsquo;s actually a very small number of packages that are shipped on
<a href="https://apt.postgresql.org">apt.postgresql.org</a> that are not in Debian for this reason. For example, the
<a href="https://tada.github.io/pljava/">PL/Java</a> extension is not in Debian because too many of the build
dependencies are not packaged in Debian. I have not enough free time to
actually care about those Java things, and I can&rsquo;t talk Java anyway, so it
wouldn&rsquo;t make much sense anyway.</p>
<p>I hope that was not too much, in the too short time.</p>
<h2 id="questions-and-comments">Questions and comments</h2>
<ul>
<li>
<p>Pavlo Golub: When you show the <code>pg_virtualenv</code>, usage, do you use pre-built
binaries or do you rebuild every time? Like for every new version you are
using?</p>
</li>
<li>
<p>Christoph: No, no, that&rsquo;s using the prebuilt binaries. The way it works
is, I have many Postgres versions installed on that machine, and then I
can just go and say, <code>pg_virtualenv</code>, and I want, let&rsquo;s say, an 8.2
server. It&rsquo;s calling <code>initdb</code> on the newer version, it&rsquo;s actually telling
it to skip the <code>fsync</code> &mdash; that&rsquo;s why 8.3 was taking a bit longer, because
it doesn&rsquo;t have that option yet. And there it&rsquo;s setting <code>PGPORT</code>, <code>PGHOST</code>
and so on, variables. So I can just connect and then play with this old
server. The problem is that <code>psql</code> pro-compatibility at some point, but
it&rsquo;s still working for sending normal commands to modern <code>psql</code>.</p>
</li>
<li>
<p>Pavlo: For modern <code>psql</code>, yeah. That&rsquo;s cool! Can you add not only vanilla
Postgres, but any other flavors like by EDB or Cybertec or, &hellip;?</p>
</li>
<li>
<p>Christoph: I&rsquo;ve thought about supporting that; the problem there is that
there&rsquo;s conflicting requirements. What we&rsquo;ve done on the Cybertec side is
that if the other Postgres distribution wants to be compatible to this
one, it really has to place things in the same directories. So it&rsquo;s
installing to exactly this location and if it&rsquo;s actually behaving like the
original, it&rsquo;ll just work. If it&rsquo;s installing to <code>/opt/edb/something</code>, its
not supported at the moment, but that&rsquo;s something we could easily add.
What it&rsquo;s really doing is just invoking the existing tools with enough
parameters to put the data directory into some temporary location.</p>
</li>
<li>
<p>Pavlo: And one more question. You had <a href="https://go.dev">Go</a> extensions mentioned on your last
slide, but you didn&rsquo;t tell anything about those.</p>
</li>
<li>
<p>Christoph: Yeah, the story is the same as with Rust. We have not done
anything with it yet and we need to explore it.</p>
</li>
<li>
<p>David Wheeler: Yurii was saying a bit about that in the chat. It seems
like the problem is that, both of them expect to download most of their
dependencies. And vendoring them swells up the size of the download and
since they&rsquo;re not runtime dependencies, but compile-time dependencies, it
seems kind of silly to make packages.</p>
</li>
<li>
<p>Christoph: Yeah. For Debian, the answer is that Debian wants to be
self-contained, so downloading things from the internet at build time is
prohibited. The ideal solution is to package everything; if it&rsquo;s things
that are really used only by one package, then vendoring the modules might
be an option. But people will look funny at you if you try to do that.</p>
</li>
<li>
<p>Yurii: I think part of the problem here is that in the Rust ecosystem in
particular, it&rsquo;s very common to have <em>a lot</em> of dependencies, as in
hundreds. When you start having one dependency and that dependency brings
another dependency. The other part of the problem is that you might depend
on a particular range of versions of particular dependencies and others
depend on others. Packaging all of that as individual dependencies is
becoming something that is really difficult to accomplish. So vendorizing
and putting that as part of the source is something that we could do to
avoid the problem.</p>
</li>
<li>
<p>Christoph: Yeah, of course, it&rsquo;s the easy solution. Some of the
programming language ecosystems fit better into Debian than others. So I
don&rsquo;t know how well Rust fits or not.</p>
<p>What I know from the Java world is that they also like to version
everything and put version restrictions on their dependencies. But what
Debian Java packaging helpers are doing is just to nuke all those
restrictions away and just use the latest version and usually that just
works. So you&rsquo;re reducing the problem by one axis by having everything at
the latest version. No idea how reasonable the Rust version ranges there
are. So if you can just ignore them and things
still work, or&hellip;</p>
</li>
<li>
<p>Yurii: Realistically, this is impossible. They do require particular
versions and they will not compile oftentimes. The whole toolchain expects
particular versions. This is not only dependency systems themselves, it&rsquo;s
also Rust. A package or extension can have a particular demand
for minimum supported Rust version. If that version is not available in
particular distro, you just can&rsquo;t compile.</p>
</li>
<li>
<p>Christoph: Then the answer is we don&rsquo;t compile and you don&rsquo;t get it. I
mean, Rust is possibly still very new and people depend on the latest
features and then are possibly just out of luck if they want something on
Debian bullseye. But at some point that problem should resolve itself and
Rust get more stable so that problem is not as common anymore.</p>
</li>
<li>
<p>Yurii: It&rsquo;s an interesting take actually because if you think about, the
languages that have been around for much longer should have solved this
problem. But if you look at, I don&rsquo;t know, C, C++, so GCC and Clang,
right? They keep evolving and changing all the time too. So there&rsquo;s a lot
of code say in C++ that would not compile with a compiler that is older
than say, three years. So yeah, but we see that in old languages.</p>
</li>
<li>
<p>Christoph: Yea, but Postgres knows about that problem and just doesn&rsquo;t use
any features that are not available in all compilers. Postgres has
solved the problem.</p>
</li>
<li>
<p>Yurii: Others not so much. Others can do whatever they
want.</p>
</li>
<li>
<p>Christoph: If upstream doesn&rsquo;t care about their users, that&rsquo;s upstream&rsquo;s
problem.</p>
</li>
<li>
<p>David: I think if there&rsquo;s there&rsquo;s a centralized place where the discussion
of how to manage stuff, like Go and Rust do, on packaging systems is
happening, I think it&rsquo;s reaching a point where there&rsquo;s so much stuff that
we&rsquo;ve gotta figure out how to work up a solution.</p>
</li>
<li>
<p>Christoph: We can do back ports of certain things in the repository and
make certain toolchain bits available on the older distributions. But you
have to stop at some point. I&rsquo;m certainly not going to introduce GCC back
ports, because I just can&rsquo;t manage that. So far we haven&rsquo;t done much of
that. I think <a href="https://github.com/devrimgunduz" title="Devrim Gündüz">Devrim</a> is actually backporting parts of the GIST tool
chain, like GL and libproj or something. I&rsquo;ve always been using what is
available in the base distribution for that. There is some room for making
it work, but it&rsquo;s always the question of how much extra work we want to
put in, how much do we want to deviate from the base distribution, and
ultimately also, support the security bits of that.</p>
</li>
</ul>
<p>[David makes a pitch for the next two sessions and thanks everyone for coming].</p>

        </div>

        <footer class="tags">
            <h5>More about…</h5>
            <ul>
                <li><a href="https://justatheory.com/tags/postgres/">Postgres</a></li>
                <li><a href="https://justatheory.com/tags/extensions/">Extensions</a></li>
                <li><a href="https://justatheory.com/tags/pgconf/">PGConf</a></li>
                <li><a href="https://justatheory.com/tags/summit/">Summit</a></li>
                <li><a href="https://justatheory.com/tags/debian/">Debian</a></li>
                <li><a href="https://justatheory.com/tags/apt/">APT</a></li>
                <li><a href="https://justatheory.com/tags/christoph-berg/">Christoph Berg</a></li>
                <li><a href="https://justatheory.com/tags/transcript/">Transcript</a></li>
            </ul>
        </footer>
    </article>
]]></content>
	</entry>
	<entry>
		<id>https://justatheory.com/2025/04/mini-summit-apt-packaging/</id>
		<title type="html"><![CDATA[Mini Summit 3: APT Extension Packaging]]></title>
		<link rel="alternate" type="text/html" href="https://www.meetup.com/postgres-extensions-ecosystem-mini-summits/events/306682786/"/>
		<link rel="related" type="text/html" href="https://justatheory.com/2025/04/mini-summit-apt-packaging/"/>
		<updated>2025-04-07T18:33:23Z</updated>
		<published>2025-04-07T18:33:23Z</published>
		<author>
			<name>David E. Wheeler</name>
			<email>david@justatheory.com</email>
			<uri>https://justatheory.com/</uri>
		</author>
		<category scheme="https://justatheory.com/tags" term="postgres" label="Postgres"/>
		<category scheme="https://justatheory.com/tags" term="extensions" label="Extensions"/>
		<category scheme="https://justatheory.com/tags" term="pgconf" label="PGConf"/>
		<category scheme="https://justatheory.com/tags" term="summit" label="Summit"/>
		<category scheme="https://justatheory.com/tags" term="debian" label="Debian"/>
		<category scheme="https://justatheory.com/tags" term="apt" label="APT"/>
		<category scheme="https://justatheory.com/tags" term="christoph-berg" label="Christoph Berg"/>
		<summary type="html"><![CDATA[Join us at PostgresSQL Extension Mini Summit #3 this week, where PostgreSQL
Debian packaging maintainer Christoph Berg will takes on a tour of APT
extension packaging.]]></summary>
		<content type="html" xml:base="https://justatheory.com/" xml:space="preserve">
<![CDATA[    <article class="post">


	<figure title="PostgresSQL Extension Mini Summit: APT Extension Packaging"><a href="https://www.meetup.com/postgres-extensions-ecosystem-mini-summits/events/306682786/">
			<img src="https://justatheory.com/shared/extension-ecosystem-summit/apt-packaging-card.jpeg" alt="Orange card with large black text reading “APT Extension Packaging”. Smaller text below reads “Christoph Berg, Debian/Cybertec” and “04.09.2025”. A photo of Christoph looking cooly at the camera appears on the right." title="PostgresSQL Extension Mini Summit: APT Extension Packaging" />
		</a>
	</figure>

        <div class="text">
<p>This Wednesday, April 9 at noon America/New_York (16:00 UTC) for <a href="https://www.meetup.com/postgres-extensions-ecosystem-mini-summits/events/306682786/" title="Postgres Extensions Ecosystem Mini-Summit #3">Extension
Mini Summit #3</a>, where <a href="https://www.df7cb.de">Christoph Berg</a> will take us on a tour of the
<a href="https://wiki.postgresql.org/wiki/Apt" title="PostgreSQL Wiki: Apt">PostgreSQL Global Development Group&rsquo;s APT repository</a> with a focus on
packaging extensions. For those of us foolish enough to consider building our
own binary packaging systems for extensions, this will be an essential
session. For everyone else, come be amazed by the sheer volume of extensions
readily available from the repository. Browse on over to the <a href="https://www.meetup.com/postgres-extensions-ecosystem-mini-summits/" title="Postgres Extension Ecosystem Mini-Summit on Meetup">Meetup</a> to
register for this live video conference.</p>

        </div>

        <footer class="tags">
            <h5>More about…</h5>
            <ul>
                <li><a href="https://justatheory.com/tags/postgres/">Postgres</a></li>
                <li><a href="https://justatheory.com/tags/extensions/">Extensions</a></li>
                <li><a href="https://justatheory.com/tags/pgconf/">PGConf</a></li>
                <li><a href="https://justatheory.com/tags/summit/">Summit</a></li>
                <li><a href="https://justatheory.com/tags/debian/">Debian</a></li>
                <li><a href="https://justatheory.com/tags/apt/">APT</a></li>
                <li><a href="https://justatheory.com/tags/christoph-berg/">Christoph Berg</a></li>
            </ul>
        </footer>
    </article>
]]></content>
	</entry>
	<entry>
		<id>https://justatheory.com/2025/04/mini-summit-two/</id>
		<title type="html"><![CDATA[2025 Postgres Extensions Mini Summit Two]]></title>
		<link rel="alternate" type="text/html" href="https://justatheory.com/2025/04/mini-summit-two/"/>
		<updated>2025-04-01T19:32:52Z</updated>
		<published>2025-04-01T19:32:52Z</published>
		<author>
			<name>David E. Wheeler</name>
			<email>david@justatheory.com</email>
			<uri>https://justatheory.com/</uri>
		</author>
		<category scheme="https://justatheory.com/tags" term="postgres" label="Postgres"/>
		<category scheme="https://justatheory.com/tags" term="extensions" label="Extensions"/>
		<category scheme="https://justatheory.com/tags" term="pgconf" label="PGConf"/>
		<category scheme="https://justatheory.com/tags" term="summit" label="Summit"/>
		<category scheme="https://justatheory.com/tags" term="peter-eisentraut" label="Peter Eisentraut"/>
		<category scheme="https://justatheory.com/tags" term="transcript" label="Transcript"/>
		<summary type="html"><![CDATA[A transcript of from the second PostgreSQL Extension Mini Summit,
&ldquo;Implementing an Extension Search Path&rdquo;, by Peter Eisentraut.]]></summary>
		<content type="html" xml:base="https://justatheory.com/" xml:space="preserve">
<![CDATA[    <article class="post">


	<figure title="PostgresSQL Extension Mini Summit: Implementing an Extension Search Patch"><a href="https://www.youtube.com/watch?v=xtnt06zhONk">
			<img src="https://justatheory.com/shared/extension-ecosystem-summit/search-path-card.jpeg" alt="Orange card with large black text reading “Implementing an Extension Search Patch”. Smaller text below reads “Peter Eisentraut, EDB” and “03.26.2025”. A photo of Peter speaking into a mic at a conference appears on the right." title="PostgresSQL Extension Mini Summit: Implementing an Extension Search Patch" />
		</a>
	</figure>

        <div class="text">
<p>Last Wednesday, March 26, we hosted the second of five virtual <a href="https://www.meetup.com/postgres-extensions-ecosystem-mini-summits/" title="Postgres Extension Ecosystem Mini-Summit on Meetup">Extension
Mini-Summits</a> in the lead up to <a href="https://www.pgevents.ca/events/pgconfdev2025/schedule/session/241/" title="PGConf.dev: Extensions Ecosystem Summit"><em>the big one</em></a> at the
Postgres Development Conference (<a href="https://2025.pgconf.dev" title="PostgreSQL Development Conference 2025">PGConf.dev</a>) on May 13 in Montréal, Canada.
<a href="https://peter.eisentraut.org">Peter Eisentraut</a> gave a very nice presentation on the history, design
decisions, and problems solved by &ldquo;Implementing an Extension Search Path&rdquo;.
That talk, plus another 10-15m of discussion, is now available for your
viewing pleasure:</p>
<ul>
<li><a href="https://www.youtube.com/watch?v=xtnt06zhONk">Video</a></li>
<li><a href="https://justatheory.com/shared/extension-ecosystem-summit/implementing-an-extension-search-path.pdf">Slides</a></li>
</ul>
<p>If you&rsquo;d like to attend any of the next three Mini-Summits, join <a href="https://www.meetup.com/postgres-extensions-ecosystem-mini-summits/" title="Postgres Extension Ecosystem Mini-Summit on Meetup">the
Meetup</a>!</p>
<p>Once again, with many thanks again to <a href="https://dev.to/@floord">Floor Drees</a> for the effort, here&rsquo;s the
transcript from the session.</p>
<h2 id="introduction">Introduction</h2>
<p>Floor Drees introduced the organizers:</p>
<ul>
<li><a href="https://justatheory.com/">David Wheeler</a>, Principal Architect at <a href="https://tembo.io/">Tembo</a>, maintainer of <a href="https://pgxn.org/">PGXN</a></li>
<li><a href="https://ca.linkedin.com/in/yrashk">Yurii Rashkovskii</a>, <a href="https://omnigres.com/">Omnigres</a></li>
<li><a href="https://pgxn.org/user/keithf4/">Keith Fiske</a>, <a href="https://www.crunchydata.com/">Crunchy Data</a></li>
<li><a href="https://dev.to/@floord">Floor Drees</a>, Principal Program Manager at <a href="https://enterprisedb.com" title="EnterpriseDB">EDB</a>, PostgreSQL CoCC member,
PGDay Lowlands organizer</li>
</ul>
<p><a href="https://peter.eisentraut.org">Peter Eisentraut</a>, contributor to PostgreSQL development since 1999, talked
about implementing an extension search path.</p>
<p>The stream and the closed captions available for the recording are supported
by PGConf.dev and their gold level <a href="https://2025.pgconf.dev/sponsors.html">sponsors</a>, Google, AWS, Huawei, Microsoft,
and EDB.</p>
<h2 id="implementing-an-extension-search-path">Implementing an extension search path</h2>
<p>Peter: Thank you for having me!</p>
<p>I&rsquo;m gonna talk about a current project by me and a couple of people I have
worked with, and that will hopefully ship with Postgres 18 in a few months.</p>
<p>So, what do I know about extensions? I&rsquo;m a Postgres core developer, but I&rsquo;ve
developed a few extensions in my time, here&rsquo;s a list of extensions that I&rsquo;ve
built over the years.</p>
<ul>
<li><a href="https://github.com/petere/plsh">plsh</a></li>
<li><a href="https://github.com/petere/pguint">pguint</a></li>
<li><a href="https://github.com/petere/pgpcre">pgpcre</a></li>
<li><a href="https://github.com/petere/pguri">pguri</a></li>
<li><a href="https://github.com/petere/plxslt">plxslt</a></li>
<li><a href="https://github.com/petere/pgemailaddr">pgemailaddr</a></li>
<li><a href="https://github.com/petere/pgtrashcan">pgtrashcan</a></li>
</ul>
<p>Some of those are experiments, or sort of one-offs. Some of those are actually
used in production.</p>
<p>I&rsquo;ve also contributed to well-known extensions: <a href="https://pgxn.org/dist/orafce/">orafce</a>; and back in the day,
<a href="https://github.com/2ndQuadrant/pglogical">pglogical</a>, <a href="https://www.enterprisedb.com/docs/pgd/4/bdr/">BDR</a>, and <a href="https://github.com/EnterpriseDB/pg_failover_slots">pg_failover_slots</a>, at <a href="https://enterprisedb.com" title="EnterpriseDB">EDB</a>, and previously
2ndQuadrant. Those are obviously used widely and in important production
environments.</p>
<p>I also wrote an extension installation manager called <a href="https://github.com/petere/pex">pex</a> at one point. The
point of pex was to do it in one shell script, so you don&rsquo;t have any
dependencies. It&rsquo;s just a shell script, and you can say <code>pex install orafce</code>
and it installs it. This was a proof of concept, in a sense, but was actually
quite useful sometimes for development, when you just need an extension and
you don&rsquo;t know where to get it.</p>
<p>And then I wrote, even more experimental, a follow-on project called
<a href="https://github.com/petere/autopex">autopex</a>, which is a plugin module that you load into Postgres that
automatically installs an extension if you need it. If you call <code>CREATE EXTENSION orafce</code>, for example, and you don&rsquo;t have it installed, autopex
downloads and installs it. Obviously highly insecure and dubious in terms of
modern software distribution practice, but  it does work: you can just run
<code>CREATE EXTENSION</code>, and it just installs it if you don&rsquo;t have it. That kind of
works.</p>
<p>So anyways, so I&rsquo;ve worked on these various aspects of these over time. If
you&rsquo;re interested in any of these projects, they&rsquo;re all under my <a href="https://github.com/petere/">GitHub
account</a>.</p>
<p>In the context of this presentation&hellip;this was essentially not my idea. People
came to me and asked me to work on this, and as it worked out, multiple people
came to me with their problems or questions, and then it turned out it was all
the same question. These are the problems I was approached about.</p>
<p>The first one is <em>extension management in the Kubernetes environment.</em> we&rsquo;ll
hear about this in a <a href="https://www.meetup.com/postgres-extensions-ecosystem-mini-summits/events/306551747/" title="PostgreSQL Extension Mini Summit: Extension Management in CNPG">future talk</a> in this series. <a href="https://www.gabrielebartolini.it">Gabriele Bartolini</a> from
the <a href="https://cloudnative-pg.io">CloudNativePG</a> project approached me and said that the issue in a
Kubernetes environment is that if you launch a Postgres service, you don&rsquo;t
install packages, you have a pre-baked disk image that contains the software
that you need. There&rsquo;s a Postgres server and maybe some backup software in
that image, and if you want to install an extension, and the extension is not
in that image, you need to rebuild the image with the extension. That&rsquo;s very
inconvenient.</p>
<p>The ideal scenario would be that you have additional disk images for the
extensions and you just somehow attach them. I&rsquo;m hand waving through the
Kubernetes terminology, and again, there will be <a href="https://www.meetup.com/postgres-extensions-ecosystem-mini-summits/events/306551747/" title="PostgreSQL Extension Mini Summit: Extension Management in CNPG">a presentation</a>
about that in more detail. But I think the idea is clear: you want to have
these immutable disk images that contain your pieces of software, and if you
want to install more of them, you just wanna have these disk images augment
&rsquo;em together, and that doesn&rsquo;t work at the moment.</p>
<p>Problem number two is: I was approached by a maintainer of the <a href="https://postgresapp.com">Postgres.app</a>
project, a Mac binary distribution for Postgres. It&rsquo;s a nice, user-friendly
binary distribution for Postgres. This is sort of a similar problem: on macOS
you have these <code>.app</code> files to distribute software. They&rsquo;re this sort of weird
hybrid between a zip file with files in it and a directory you can look into,
so it&rsquo;s kind of weird. But it&rsquo;s basically an archive with software in it. And
in this case it has Postgres in it and it integrates nicely into your system.
But again, if you want to install an extension, that doesn&rsquo;t work as easily,
because you would need to open up that archive and stick the extension in
there somehow, or overwrite files.</p>
<p>And there&rsquo;s also a tie in with the way these packages are signed by Apple, and
if you, mess with the files in the package, then the signature becomes
invalid. It&rsquo;s the way it&rsquo;s been explained to me. I hope this was approximately
accurate, but you already get the idea, right? There&rsquo;s the same problem where
you have this base bundle of software that is immutable or that you want to
keep immutable and you want to add things to it, which doesn&rsquo;t work.</p>
<p>And then the third problem I was asked to solve came from the Debian package
maintainer, who will also <a href="https://www.meetup.com/postgres-extensions-ecosystem-mini-summits/events/306682786/" title="PostgreSQL Extension Mini Summit: Apt Extension Packaging">speak later</a> in this presentation series. What he
wanted to do was to run the tests of an extension while the package is being
built. That makes sense. You wanna run the tests of the software that you&rsquo;re
building the package for in general. But in order to do that, you have to
install the extension into the the normal file system location, right? That
seems bad. You don&rsquo;t want to install the software while you&rsquo;re into the main
system while you&rsquo;re building it. He actually wrote a custom patch to be able
to do that, which then my work was inspired by.</p>
<p>Those are the problems I was approached about.</p>
<p>I had some problems I wanted to solve myself based on my experience working
with extensions. While I was working on these various extensions over the
years, one thing that never worked is that you could never run <code>make check</code>.
It wasn&rsquo;t supported by the PGXS build system. Again, it&rsquo;s the same issue.</p>
<p>It&rsquo;s essentially a subset of the Debian problem: you want to run a test of the
software before you install it, but Postgres can only load an extension from a
fixed location, and so this doesn&rsquo;t work. It&rsquo;s very annoying because it makes
the software development cycle much more complicated. You always have to then,
then run <code>make all</code>, <code>make install</code>, make sure you have a server running,
<code>make installcheck</code>. And then you would want to test it against various
different server versions. Usually they have to run this in some weird loop.
I&rsquo;ve written custom scripts and stuff all around this, but it&rsquo;s was never
satisfactory. It should just work.</p>
<p>That&rsquo;s the problem I definitely wanted to solve. The next problem  &mdash; and
these are are all subsets of each other &mdash; that if you have Postgres
installed from a package, like an RPM package for example, and then you build
the extension locally, you have to install the extension into the directory
locations that are controlled by your operating system. If you have Postgres
under <code>/usr</code>, then the extensions also have to be installed under <code>/usr</code>,
whereas you probably want to install them under <code>/usr/local</code> or somewhere
else. You want to keep those locally built things separately, but that&rsquo;s not
possible.</p>
<p>And finally &mdash; this is a bit more complicated to explain &mdash; I&rsquo;m mainly using
macOS at the moment, and the <a href="https://brew.sh" title="The Missing Package Manager for macOS (or Linux)">Homebrew</a> package manager is widely used there.
But it doesn&rsquo;t support extensions very well at all. It&rsquo;s really weird because
the way it works is that each package is essentially installed into a separate
subdirectory, and then it&rsquo;s all symlinked together. And that works just fine.
You have a bunch of <code>bin</code> directories, and it&rsquo;s just a bunch of symlinks to
different subdirectories and that works, because then you can just swap these
things out and upgrade packages quite easily. That&rsquo;s just a design choice and
it&rsquo;s fine.</p>
<p>But again, if you wanna install an extension, the extension would be its own
package &mdash; PostGIS, for example &mdash; and it would go into its own directory.
But that&rsquo;s not the directory where Postgres would look for it. You would have
to install it into the directory structure that belongs to the other package.
And that just doesn&rsquo;t work. It&rsquo;s just does not fit with that system at all.
There are weird hacks at the moment, but it&rsquo;s not satisfactory. Doesn&rsquo;t work
at all.</p>
<p>It turned out, all of these things have sort of came up over the years and
some of these, people have approached me about them, and I realized these are
essentially all the same problem. The extension file location is hard-coded to
be inside the Postgres installation tree. Here as an example: it&rsquo;s usually
under something like <code>/usr/share/postgresql/extension/</code>, and you can&rsquo;t install
extensions anywhere else. If you want to  keep this location managed by the
operating system or managed by your package management or in some kind of
immutable disk image, you can&rsquo;t. And so these are essentially all versions of
the same problem. So that&rsquo;s why I got engaged and tried to find a solution
that addresses all of &rsquo;em.</p>
<p>I had worked on this already before, a long time ago, and then someone broke
it along the way. And now I&rsquo;m fixing it again. If you go way, way back, before
extensions as such existed in Postgres in 9.1, when you wanted to install a
piece of software that consists of a shared library object and some SQL, you
had to install the shared library object into a predetermined location just
like you do now. In addition, you had to run that SQL file by hand, basically,
like you run <code>psql -f install_orafce.sql</code> or something like that. Extensions
made that a little nicer, but it&rsquo;s the same idea underneath.</p>
<p>In 2001, I realized this problem already and implemented a configuration
setting called <code>dynamic_library_path</code>, which allows you to set a different
location for your shared library. Then you can say</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ini" data-lang="ini"><span class="line"><span class="cl"><span class="na">dynamic_library_path</span> <span class="o">=</span> <span class="s">&#39;/usr/local/my-stuff/something&#39;</span>
</span></span></code></pre></div><p>And then Postgres would look there. The SQL file just knows where is
because you run it manually. You would then run</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">psql -f /usr/local/my-stuff/something/something.sql
</span></span></code></pre></div><p>That fixed that problem at the time. And when extensions were implemented, I
was essentially not paying attention or, you know, nobody was paying
attention. Extension support were a really super nice feature, of course, but
it broke this previously-available feature: then you couldn&rsquo;t install your
extensions anywhere you wanted to; you were tied to this specific file system,
location, <code>dynamic_library_path</code> still existed: you could still set it
somewhere, but you couldn&rsquo;t really make much use of it. I mean, you could make
use of it for things that are not extensions. If you have some kind of plugin
module or modules that install hooks, you could still do that. But not for an
extension that consist of a set of SQL scripts and a control file and
<code>dynamic_library_path</code>.</p>
<p>As I was being approached about these things, I realized that was just the
problem and we should just now fix that. The recent history went as follows.</p>
<p>In April, 2024, just about a year ago now, David Wheeler started <a href="https://postgr.es/m/E7C7BFFB-8857-48D4-A71F-88B359FADCFD@justatheory.com">a hackers
thread</a> suggesting <a href="https://www.df7cb.de">Christoph Berg</a>&rsquo;s Debian patch as a starting point for
discussions. Like, &ldquo;here&rsquo;s this thing, shouldn&rsquo;t we do something about this?&rdquo;</p>
<p>There was, a fair amount of discussion. I was not really involved at the time.
This was just after feature freeze,and so I wasn&rsquo;t paying much attention to
it. But the discussion was quite lively and a lot of people pitched in and
had their ideas and thoughts about it. And so a lot of important, filtering
work was done at that time.</p>
<p>Later, in September, <a href="https://www.gabrielebartolini.it">Gabriele</a>, my colleague from EDB who
works on <a href="https://cloudnative-pg.io">CloudNativePG</a>, approached me about this issue and said like: &ldquo;hey,
this is important, we need this to make extensions useful in the Kubernetes
environment.&rdquo; And he said, &ldquo;can you work, can you work on this?&rdquo;</p>
<p>I said, &ldquo;yeah, sure, in a couple months I might have time.&rdquo; [Laughs]. But it
sort of turns out that, at <a href="https://pgconf.eu">PGConf.EU</a> we had a big brain trust meeting of
various people who basically all came and said, &ldquo;hey, I heard you&rsquo;re working
on <code>extension_control_path</code>, I also need that!&rdquo;</p>
<p><a href="https://www.gabrielebartolini.it">Gabriele</a> was there, and <a href="https://github.com/tbussmann">Tobias Bussmann</a> from
<a href="https://postgresapp.com">Postgres.app</a> was there ,and <a href="https://www.df7cb.de">Christoph</a>, and I was like,
yeah, I really need this <code>extension_control_path</code> to make this work. So I made
sure to talk to everybody there and, and make sure that, if we did this, would
it work for you? And then we kind of had a good idea of how it should work.</p>
<p>In November the first patch was posted and last week it was <a href="https://github.com/postgres/postgres/commit/4f7f7b0">committed</a>. I
think there&rsquo;s still a little bit of discussion of some details and, we
certainly still have some time before the release to fine tune it, but the
main work is hopefully done.</p>
<p>This is <a href="https://github.com/postgres/postgres/commit/4f7f7b0">the commit</a> I made last week. The fact that this presentation was
scheduled gave me additional motivation to get it done. I wanna give some
credits to people who reviewed it. Obviously David did a lot of reviews and
feedback in general. My colleague Matheus, who I think I saw him earlier, he
was also here on the call, did help me quite a bit with sort of finishing the
patch. And then Gabriele, Marco and Nicolò, who work on <a href="https://cloudnative-pg.io">CloudNativePG</a>, did a
large amount of testing.</p>
<p>They set up a whole sort of sandbox environment making test images for
extensions and, simulating the entire process of attaching these to the main
image. Again, I&rsquo;m butchering the terminology, but I&rsquo;m just trying to explain
it in general terms. They did the whole end-to-end testing of what that would
then look like with <a href="https://cloudnative-pg.io">CloudNativePG</a>. And again, that will, I assume, be
discussed when Gabriele <a href="https://www.meetup.com/postgres-extensions-ecosystem-mini-summits/events/306551747/" title="PostgreSQL Extension Mini Summit: Extension Management in CNPG">presents</a> in a few weeks.</p>
<p>These are the stats from the patch</p>
<pre tabindex="0"><code>commit 4f7f7b03758

doc/src/sgml/config.sgml                                     |  68 +++++
doc/src/sgml/extend.sgml                                     |  19 +-
doc/src/sgml/ref/create_extension.sgml                       |   6 +-
src/Makefile.global.in                                       |  19 +-
src/backend/commands/extension.c                             | 403 +++++++++++++++++----------
src/backend/utils/fmgr/dfmgr.c                               |  77 +++--
src/backend/utils/misc/guc_tables.c                          |  13 +
src/backend/utils/misc/postgresql.conf.sample                |   1 +
src/include/commands/extension.h                             |   2 +
src/include/fmgr.h                                           |   3 +
src/test/modules/test_extensions/Makefile                    |   1 +
src/test/modules/test_extensions/meson.build                 |   5 +
.../modules/test_extensions/t/001_extension_control_path.pl  |  80 ++++++
</code></pre><p>the reason I show this is that, it&rsquo;s not big! What I did is use the same
infrastructure and mechanisms that already existed for the
<code>dynamic_library_path</code>. That&rsquo;s the code in that&rsquo;s in <code>dfmgr</code> there in the
middle. That&rsquo;s where this little path search is implemented9. And then of
course, in <code>extension..c</code> there&rsquo;s some code that&rsquo;s basically just a bunch of
utility functions, like to list all the extensions and list all the versions
of all the extensions. Those utility functions exist and they needed to be
updated to do the path search. Everything else is pretty straightforward.
There&rsquo;s just a few configuration settings added to the documentation and the
sample files and so on. It&rsquo;s not that much really.</p>
<p>One thing we also did was add tests for this, Down there in <code>test_extensions</code>.
We wrote some tests to make sure this works. Well, it&rsquo;s one thing to make sure
it works, but the other thing is if we wanna make changes or we find problems
with it, or we wanna develop this further in the future, we have a record of
how it works, which is why you write tests. I just wanted to point that out
because we didn&rsquo;t really have that before and it was quite helpful to build
confidence that we know how this works.</p>
<p>So how does it work? Let&rsquo;s say you have your Postgres installation in a
standard Linux file system package controlled location. None of the actual
packages look like this, I believe, but it&rsquo;s a good example. You have your
stuff under the <code>/usr/bin/</code>, you have the shared libraries in the
<code>/usr/lib/something</code>, you have the extension control files and SQL files in
the <code>/usr/share/</code> or something. That&rsquo;s your base installation. And then you
wanna install your extension into some other place to keep these things
separate. So you have <code>/usr/local/mystuff/</code>, for example.</p>
<p>Another thing that this patch implemented is that you can now also do this:
when you build an extension, you can write <code>make install prefix=something</code>.
Before you couldn&rsquo;t do that, but there was also no point because if you
installed it somewhere else, you couldn&rsquo;t do anything with it there. Now you
can load it from somewhere else, but you can also install it there &mdash; which
obviously are the two important sides of that.</p>
<p>And then you set these two settings: <code>dynamic_library_path</code> is an existing
configuration setting, yYou set that to where your lib directory is, and then
the <code>extension_control_path</code> is a new setting. The titular setting of this
talk, where you tell it where your extension control files are.</p>
<p>There&rsquo;s these placeholders, <code>$libdir</code> and <code>$system</code> which mean the system
location, and then the other locations are your other locations, and it&rsquo;s
separated by colon (and semi-colon on Windows). We had some arguments about
what exactly the <code>extension_control_path</code> placeholder should be called and,
people continue to have different opinions. What it does is it looks in the
list directories for the control file, and then where it finds the control
file from there, it loads all the other files.</p>
<p>And there&rsquo;s a fairly complicated mechanism. There&rsquo;s obviously the actual SQL
files, but there&rsquo;s also these auxiliary control files, which I didn&rsquo;t even
know that existed. So you can have version specific control files. It&rsquo;s a
fairly complicated system, so we wanted to be clear  that what is happening is
the, the main control file  is searched for in these directories, and then
wherever it&rsquo;s found, that&rsquo;s where it looks for the other things. You can&rsquo;t
have the control file in one path and then the SQL files in another part of
the path; that&rsquo;s not how it works.</p>
<p>That solves problem number five. Let&rsquo;s see what problem number five was. I
forgot [Chuckles]. This is the basic problem, that you no longer have to
install the extensions in the directories that are ostensibly controlled by
the operating system or your package manager.</p>
<p>So then how would Debian packaging use this? I got this information from
<a href="https://www.df7cb.de">Christoph</a>. He figured out how to do this. He just said, &ldquo;Oh,
I did this, and that&rsquo;s how it works.&rdquo; During packaging, the packaging scripts
that built it up in packages that you just pass these:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl"><span class="nv">PKGARGS</span><span class="o">=</span><span class="s2">&#34;--pgoption extension_control_path=</span><span class="nv">$PWD</span><span class="s2">/debian/</span><span class="nv">$PACKAGE</span><span class="s2">/usr/share/postgresql/</span><span class="nv">$v</span><span class="s2">/extension:\$system
</span></span></span><span class="line"><span class="cl"><span class="s2">--pgoption dynamic_library_path=</span><span class="nv">$PWD</span><span class="s2">/debian/</span><span class="nv">$PACKAGE</span><span class="s2">/usr/lib/postgresql/</span><span class="nv">$v</span><span class="s2">/lib:/usr/lib/postgresql/</span><span class="nv">$v</span><span class="s2">/lib&#34;</span>
</span></span></code></pre></div><p>These options set the control path and the <code>dynamic_library_path</code> and these
versions and then it works. This was confirmed that this addresses his
problem. He no longer has to carry his custom patch. This solves problem
number three.</p>
<p>The question people ask is, &ldquo;why do we have two?&rdquo; Or maybe you&rsquo;ve asked
yourself that. Why do we need two settings. We have the
<code>dynamic_library_path</code>, we have the <code>extension_control_path</code>. Isn&rsquo;t that kind
of the same thing? Kind of, yes! But in general, it is not guaranteed that
these two things are in a in a fixed relative location.</p>
<p>Let&rsquo;s go back to our fake example. We have the libraries in
<code>/usr/lib/postgresql</code> and the SQL and control files in
<code>/usr/share/postgresql</code>, for example. Now you could say, why don&rsquo;t we just set
it to <code>/usr</code>? Or, for example, why don&rsquo;t we just set the path to
<code>/usr/local/mystuff</code> and it should figure out the sub directories. That would
be nice, but it doesn&rsquo;t quite work in general because it&rsquo;s not guaranteed that
those are the subdirectories. There could be, for example. <code>lib64</code>, for
example, right? Or some other so architecture-specific subdirectory names. Or
people can just name them whatever they want. So, this may be marginal, but it
is possible. You need to keep in mind that the subdirectory structure is not
necessarily fixed.</p>
<p>So we need two settings. The way I thought about this, if you compile C code,
you also have two settings. And if you think about it, it&rsquo;s exactly the same
thing. When you compile C code, you always have to do <code>-I</code> and <code>-L</code>: <code>I</code> for
the include files, <code>L</code> for the  lib files. This is basically the same thing.
The include file is also the text file that describes the interfaces and the
libraries are the libraries. Again, you need two options, because you can&rsquo;t
just tell the compiler, oh, look for it in <code>/usr/local</code> because the
subdirectories could be different. There could be architecture specific lib
directories. That&rsquo;s a common case. You need those two settings. Usually they
go in parallel. If somebody has a plan on how to do it simpler, follow up
patches are welcome.</p>
<p>But the main point of why this approach was taken is also to get it done in a
few months. I started thinking about this, or I was contacted about this in
September and I started thinking about it seriously in the October/November
timeframe. That&rsquo;s quite late in the development cycle to start a feature like
this, which I thought would be more controversial! People haven&rsquo;t really
complained that this breaks the security of extensions or anything like that.
I was a little bit afraid of that.</p>
<p>So I wanted to really base it on an existing facility that we already had, and
that&rsquo;s why I wanted to make sure it works exactly in parallel to the other
path that we already have, and that has existed for a long time, and was
designed for this exact purpose. That was also the reason why we chose this
path of least resistance, perhaps.</p>
<p>This is the solution progress for the six problems that I described initially.
The <a href="https://cloudnative-pg.io">CloudNativePG</a> folks obviously have accompanied this project actively and
have already prototyped the integration solution. And, and presumably we will
hear about some of that at the <a href="https://www.meetup.com/postgres-extensions-ecosystem-mini-summits/events/306551747/" title="PostgreSQL Extension Mini Summit: Extension Management in CNPG">meeting on May 7th</a>, where
Gabriele will talk about this.</p>
<p><a href="https://postgresapp.com">Postgres.app</a> I haven&rsquo;t been in touch with, but one of the maintainers is
here, maybe you can give feedback later. Debian is done as I described, and
they will also be at <a href="https://www.meetup.com/postgres-extensions-ecosystem-mini-summits/events/306682786/" title="PostgreSQL Extension Mini Summit: Apt Extension Packaging">the next meeting</a>, maybe there will be some
comment on that.</p>
<p>One thing that&rsquo;s not fully implemented is the the <code>make check</code> issue. I did
send a follow-up patch about that, which was a really quick prototype hack,
and people really liked it. I&rsquo;m slightly tempted to give it a push and try to
get it into Postgres 18. This is a work in progress, but it&rsquo;s, there&rsquo;s sort of
a way forward. The local install problem I said is done.</p>
<p><a href="https://brew.sh" title="The Missing Package Manager for macOS (or Linux)">Homebrew</a>, I haven&rsquo;t looked into. It&rsquo;s more complicated, and I&rsquo;m also not
very closely involved in the development of that. I&rsquo;ll just be an outsider
maybe sending patches or suggestions at some point, maybe when the release is
closer and, and we&rsquo;ve settled everything.</p>
<p>I have some random other thoughts here. I&rsquo;m not actively working on these
right now, but I have worked on it in the past and I plan to work on it again.
Basically the conversion of all the building to <a href="https://www.postgresql.org/docs/current/install-meson.html">Meson</a> is on my mind, and
other people&rsquo;s mind.</p>
<p>Right now we have two build systems: the <code>make</code> build system and the <a href="https://www.postgresql.org/docs/current/install-meson.html">Meson</a>
build system, and all the production packages, as far as I know, are built
with <code>make</code>. Eventually we wanna move all of that over to Meson, but we want
to test all the extensions and if it still works. As far as I know, it does
work; there&rsquo;s nothing that really needs to be implemented, but we need to go
through all the extensions and test them.</p>
<p>Secondly &mdash; this is optional; I&rsquo;m not saying this is a requirement &mdash; but
you may wish to also build your own extensions with Meson. But that&rsquo;s in my
mind, not a requirement. You can also use <code>cmake</code> or do whatever you want. But
there&rsquo;s been some prototypes of that. Solutions exist if you&rsquo;re interested.</p>
<p>And to facilitate the second point, there&rsquo;s been the proposal &mdash; which I
think was well received, but it just needs to be fully implemented &mdash; to
provide a <code>pkg-config</code> file to build against the server, and <code>cmake</code> and Meson
would work very well with that. Then you can just say  here&rsquo;s a <code>pkg-config</code>
file to build against the server. It&rsquo;s much easier than setting all the
directories yourself or extracting them from <a href="https://www.postgresql.org/docs/current/app-pgconfig.html"><code>pg_config</code></a>. Maybe that&rsquo;s
something coming for the next release cycle.</p>
<p>That&rsquo;s what I had. So <code>extension_control_path</code> is coming in Postgres 18. What
you can do is test and validate that against your use cases and and help
integration into the downstream users. Again, if you&rsquo;re sort of a package or
anything like that, you know, you can make use of that. That is all for me.</p>
<p>Thank you!</p>
<h2 id="questions-comments">Questions, comments</h2>
<ul>
<li>
<p>Reading the comments where several audience members suggested Peter
follows Conference Driven Development he confirmed that that&rsquo;s definitely
a thing.</p>
</li>
<li>
<p>Someone asked for the &ldquo;requirements gathering document&rdquo;. Peter said that
that&rsquo;s just a big word for &ldquo;just some notes I have&rdquo;. &ldquo;It&rsquo;s not like an
actual document. I called it the requirements gathering. That sounds very
formal, but it&rsquo;s just chatting to various people and someone at the next
table overheard us talking and it&rsquo;s like, &lsquo;Hey! I need that too!&rsquo;&rdquo;</p>
</li>
<li>
<p>Christoph: I tried to get this fixed or implemented or something at least
once over the last 10 something-ish years, and was basically shot down on
grounds of security issues if people mess up their system. And what
happens if you set the extension path to something, install an extension,
and then set the path to something else and then you can&rsquo;t upgrade. And
all sorts of weird things that people can do with their system in order to
break them. Thanks for ignoring all that bullshit and just getting it
done! It&rsquo;s an administrator-level setting and people can do whatever they
want with it.</p>
<p>So what I then did is just to implement that patch and, admittedly I never
got around to even try to put it upstream. So thanks David for pushing
that ahead. It was clear that the Debian version of the patch wasn&rsquo;t
acceptable because it was too limited. It made some assumptions about the
direct restructure of Debian packages. So it always included the prefix in
the path. The feature that Peter implemented solves my problem. It does
solve a lot of more problems, so thanks for that.</p>
</li>
<li>
<p>Peter: Testing all extensions. What we&rsquo;ve talked about is doing this
through the Debian packaging system because the idea was to maybe make a
separate branch or a separate sub-repository of some sort, switch it to
build Meson, and rebuild all the extension packages and see what happens.
I guess that&rsquo;s how far we&rsquo;ve come. I doesn&rsquo;t actually mean they all work,
but I guess that most of them has tests, so we just wanted to test, see
if it works.</p>
<p>There are some really subtle problems. Well, the ones I know of have been
fixed, but there&rsquo;s some things that certain compilation options are not
substituted into the <code>Makefile</code>s correctly, so then all your extensions
are built without any optimizations, for example, without any <code>-O</code>
options. I&rsquo;m not really sure how to detect those automatically, but at
least, just rebuild everything once might be an option. Or just do it
manually. There are not thousands of extensions. There are not even
hundreds that are relevant. There are several dozens, and I think that&rsquo;s
good coverage.</p>
</li>
<li>
<p>Christoph: I realize that doing it on the packaging side makes sense
because we all have these tests running. So I was looking into it. The
first time I tried, I stopped once I realized that Meson doesn&rsquo;t support
LLVM yet; and the second time I tried, I just <code>diff</code>-ed the generated
<code>Makefile</code>s to see if there&rsquo;s any difference that looks suspicious. At
thus point I should just continue and do compilation run and see what the
tests are doing and and stuff.</p>
<p>So my hope would be that I could run <code>diff</code> on the results; the problem is
compiling with Postgres with Autoconf once and then with Meson the second
time, then see if it has an impact on the extensions compiled. But my idea
was that if I&rsquo;m just running <code>diff</code> on the two compilations and there&rsquo;s no
difference, there&rsquo;s no point in testing because they&rsquo;re identical anyway.</p>
</li>
<li>
<p>Peter Oooh, you want the actual compilation, for the <code>Makefile</code> output to
be the same.</p>
</li>
<li>
<p>Christoph: Yeah. I don&rsquo;t have to run that test, But the <code>diff</code> was a bit
too big to be readable. There was lots of white space noise in there. But
there were also some actual changes. Some were not really bad, like9 in
some points variables were using a fully qualified path for the <code>make</code>
directory or something, and then some points not; but, maybe we can just
work on making that difference smaller and then arguing about correctness
is easier.</p>
</li>
<li>
<p>Peter: Yeah, that sounds like a good approach.</p>
</li>
<li>
<p>Jakob: Maybe I can give some feedback from <a href="https://postgresapp.com">Postgres.app</a>. So, thank you
very much. I think this solves a lot of problems that we have had with
extensions over the years, especially because it allows us to separate the
extensions and the main Postgres distribution. For Postgres.app we
basically have to decide which extensions to include and we can&rsquo;t offer
additional extensions when people ask for them without shipping them for
everyone. So that&rsquo;s a big win.</p>
<p>One question I am wondering about is the use case of people building their
own extensions. As far as I understand, you have to provide the prefix/
And one thing I&rsquo;m wondering whether there is there some way to give a
default value for the prefix. Like in <a href="https://www.postgresql.org/docs/current/app-pgconfig.html"><code>pg_config</code></a> or in something like
that, so people who just type <code>make install</code> automatically get some path.</p>
</li>
<li>
<p>Peter: That might be an interesting follow on. I&rsquo;m making a note of it.
I&rsquo;m not sure how you&rsquo;d&hellip;</p>
</li>
<li>
<p>Jakob: I&rsquo;m just thinking because a big problem is that a lot of people who
try things don&rsquo;t follow the instructions for the specific Postgres. So for
example, if we write documentation how to build extensions and people on a
completely different system &mdash; like people Google stuff and they get
instruction &mdash; they&rsquo;ll just try random paths. Right now, if you just
type <code>make install</code>, it works on most systems because it just builds into
the standard directories.</p>
</li>
<li>
<p>Peter: Yeah, David puts it like, &ldquo;should there be a different default
extension location?&rdquo; I think that&rsquo;s probably not an unreasonable
direction. I think that&rsquo;s something we should maybe think about, once this
is stabilized. I think for your <a href="https://postgresapp.com">Postgres.app</a> use case, it, I think you
could probably even implement that yourself with a one or two line patch
so that at least, if you install Postgres.app, then somebody tries to
build an extension, they get a reasonable location.</p>
</li>
<li>
<p>David: If I could jump in there, Jakob, my assumption was that
<a href="https://postgresapp.com">Postgres.app</a> would do something like designate the <code>Application Support</code>
directory and <code>Preferences</code> in <code>~/Library</code> as where extensions should be
installed. And yeah, there could be some patch to PGXS to put stuff there
by default.</p>
</li>
<li>
<p>Jakob: Yeah, that would be nice!</p>
</li>
<li>
<p>Peter: Robert asked a big question here. What do we think the security
consequences of this patch? Well, one of the premises is that we already
have <code>dynamic_library_path</code>, which works exactly the same way, and there
haven&rsquo;t been any concerns about that. Well, maybe there have been
concerns, but nothing that was acted on. If you set the path to somewhere
where anybody can write stuff, then yeah, that&rsquo;s not so good. But that&rsquo;s
the same as anything. Certainly there were concerns as I read through the
discussion.</p>
<p>I assumed <em>somebody</em> would hav security questions, so I really wanted to
base it on this existing mechanism and not invent something completely
new. So far nobody has objected to it [Chuckles]. But yeah, of course you
can make a mess of it if you go into that <code>extension_control_path = /tmp</code>!
That&rsquo;s probably not good. But don&rsquo;t do that.</p>
</li>
<li>
<p>David: That&rsquo;s I think in part the <a href="https://en.wikipedia.org/wiki/XZ_Utils_backdoor">xz exploit</a> kind of made people more
receptive to this patch because we want to reduce the number of patches
that packaging maintainers have to maintain.</p>
</li>
<li>
<p>Peter: Obviously this is something people do. Better we have one solution
that people then can use and that we at least we understand, as opposed to
everybody going out and figuring out their own complicated solutions.</p>
</li>
<li>
<p>David: Peter, I think there are still some issues with the behavior of
<code>MODULEDIR</code> from PGXS and <code>directory</code> in the control file that this
doesn&rsquo;t quite work with this extension. Do you have some thoughts on how
to address those issues?</p>
</li>
<li>
<p>Peter: For those who are not following: there&rsquo;s an existing, I guess,
rarely used feature that, in the control file, you can specify directory
options, which then specifies where other files are located. And this
doesn&rsquo;t work the way you think it should maybe it&rsquo;s not clear what that
should do if you find it in a path somewhere. I guess it&rsquo;s so rarely used
that we might maybe just get rid of it; that was one of the options.</p>
<p>In my mental model of how the C compiler works, it sets an <a href="https://en.wikipedia.org/wiki/Rpath"><code>rpath</code></a> on
something. If you set an absolute <code>rpath</code> somewhere and you know it&rsquo;s not
gonna work if you move the thing to a different place in the path. I&rsquo;m not
sure if that&rsquo;s a good analogy, but it sort of has similar consequences. If
you hard-code absolute path, then path search is not gonna work. But yeah,
that&rsquo;s on the list I need to look into.</p>
</li>
<li>
<p>David: For what it&rsquo;s worth, I discovered last week that the part of this
patch where you&rsquo;re stripping out <code>$libdir</code> and the extension make file that
was in modules, I think? That also needs to be done when you use <code>rpath</code>
to install an extension and point to extensions today with Postgres 17.
Happy to see that one go.</p>
</li>
<li>
<p>Christoph: Thanks for fixing that part. I was always wondering why this
was broken. The way it was broken. It looked very weird and it turned out
it was just broken and not me not understanding it.</p>
</li>
<li>
<p>David: I think it might have been a documentation oversight back when
extensions were added at 9.1 to say this is how you list the modules.</p>
<p>Anyway, this is great! Im super excited for this patch and where it&rsquo;s
going and the promise for stuff in the future. Just from your list of the
<strong>six issues</strong> it addresses, it&rsquo;s obviously something that covers a
variety of pain points. I appreciate you doing that.</p>
</li>
<li>
<p>Peter: Thank you!</p>
</li>
</ul>
<p>Many thanks and congratulations wrap up this call.</p>
<p>The next Mini-Summit is on <a href="https://www.meetup.com/postgres-extensions-ecosystem-mini-summits/events/306682786/" title="PostgreSQL Extension Mini Summit: Apt Extension Packaging">April 9</a>, <a href="https://www.df7cb.de">Christoph Berg</a> (Debian,
and also Cybertec) will join us to talk about Apt Extension Packaging.</p>

        </div>

        <footer class="tags">
            <h5>More about…</h5>
            <ul>
                <li><a href="https://justatheory.com/tags/postgres/">Postgres</a></li>
                <li><a href="https://justatheory.com/tags/extensions/">Extensions</a></li>
                <li><a href="https://justatheory.com/tags/pgconf/">PGConf</a></li>
                <li><a href="https://justatheory.com/tags/summit/">Summit</a></li>
                <li><a href="https://justatheory.com/tags/peter-eisentraut/">Peter Eisentraut</a></li>
                <li><a href="https://justatheory.com/tags/transcript/">Transcript</a></li>
            </ul>
        </footer>
    </article>
]]></content>
	</entry>
	<entry>
		<id>https://justatheory.com/2025/03/mini-summit-search-path/</id>
		<title type="html"><![CDATA[Mini Summit 2: Extension Search Path Patch]]></title>
		<link rel="alternate" type="text/html" href="https://www.meetup.com/postgres-extensions-ecosystem-mini-summits/events/306569342/"/>
		<link rel="related" type="text/html" href="https://justatheory.com/2025/03/mini-summit-search-path/"/>
		<updated>2025-03-24T21:14:27Z</updated>
		<published>2025-03-24T21:14:27Z</published>
		<author>
			<name>David E. Wheeler</name>
			<email>david@justatheory.com</email>
			<uri>https://justatheory.com/</uri>
		</author>
		<category scheme="https://justatheory.com/tags" term="postgres" label="Postgres"/>
		<category scheme="https://justatheory.com/tags" term="extensions" label="Extensions"/>
		<category scheme="https://justatheory.com/tags" term="pgconf" label="PGConf"/>
		<category scheme="https://justatheory.com/tags" term="summit" label="Summit"/>
		<category scheme="https://justatheory.com/tags" term="search-path" label="Search Path"/>
		<category scheme="https://justatheory.com/tags" term="peter-eisentraut" label="Peter Eisentraut"/>
		<summary type="html"><![CDATA[Join us at PostgresSQL Extension Mini Summit #2 this week, where PostgreSQL
committer Peter Eisentraut will discuss the extension search path patch.]]></summary>
		<content type="html" xml:base="https://justatheory.com/" xml:space="preserve">
<![CDATA[    <article class="post">


	<figure title="PostgresSQL Extension Mini Summit: Implementing an Extension Search Patch"><a href="https://www.meetup.com/postgres-extensions-ecosystem-mini-summits/events/306569342/">
			<img src="https://justatheory.com/shared/extension-ecosystem-summit/search-path-card.jpeg" alt="Orange card with large black text reading “Implementing an Extension Search Patch”. Smaller text below reads “Peter Eisentraut, EDB” and “03.26.2025”. A photo of Peter speaking into a mic at a conference appears on the right." title="PostgresSQL Extension Mini Summit: Implementing an Extension Search Patch" />
		</a>
	</figure>

        <div class="text">
<p>This Wednesday, March 26 at noon America/New_York (16:00 UTC), <a href="https://peter.eisentraut.org">Peter
Eisentraut</a> has graciously agreed to give a talk at the <a href="https://www.meetup.com/postgres-extensions-ecosystem-mini-summits/events/306569342/" title="Postgres Extensions Ecosystem Mini-Summit #2">Extension Mini Summit
#2</a> on the <a href="https://github.com/postgres/postgres/commit/4f7f7b0">extension search path patch</a> he recently committed to PostgreSQL.
I&rsquo;m personally stoked for this topic, as freeing extensions from the legacy of
a single directory opens up a number of new patterns for packaging,
installation, and testing extensions. Hit the <a href="https://www.meetup.com/postgres-extensions-ecosystem-mini-summits/" title="Postgres Extension Ecosystem Mini-Summit on Meetup">Meetup</a> to register for this
live video conference, and to brainstorm novel uses for this new feature,
expected to debut in PostgreSQL 18.</p>

        </div>

        <footer class="tags">
            <h5>More about…</h5>
            <ul>
                <li><a href="https://justatheory.com/tags/postgres/">Postgres</a></li>
                <li><a href="https://justatheory.com/tags/extensions/">Extensions</a></li>
                <li><a href="https://justatheory.com/tags/pgconf/">PGConf</a></li>
                <li><a href="https://justatheory.com/tags/summit/">Summit</a></li>
                <li><a href="https://justatheory.com/tags/search-path/">Search Path</a></li>
                <li><a href="https://justatheory.com/tags/peter-eisentraut/">Peter Eisentraut</a></li>
            </ul>
        </footer>
    </article>
]]></content>
	</entry>
	<entry>
		<id>https://justatheory.com/2025/03/mini-summit-one/</id>
		<title type="html"><![CDATA[2025 Postgres Extensions Mini Summit One]]></title>
		<link rel="alternate" type="text/html" href="https://justatheory.com/2025/03/mini-summit-one/"/>
		<updated>2025-03-24T20:46:58Z</updated>
		<published>2025-03-24T20:46:58Z</published>
		<author>
			<name>David E. Wheeler</name>
			<email>david@justatheory.com</email>
			<uri>https://justatheory.com/</uri>
		</author>
		<category scheme="https://justatheory.com/tags" term="postgres" label="Postgres"/>
		<category scheme="https://justatheory.com/tags" term="pgxn" label="PGXN"/>
		<category scheme="https://justatheory.com/tags" term="extensions" label="Extensions"/>
		<category scheme="https://justatheory.com/tags" term="pgconf" label="PGConf"/>
		<category scheme="https://justatheory.com/tags" term="summit" label="Summit"/>
		<category scheme="https://justatheory.com/tags" term="transcript" label="Transcript"/>
		<summary type="html"><![CDATA[A rough transcript of my talk &ldquo;State of the Extension Ecosystem&rdquo;.]]></summary>
		<content type="html" xml:base="https://justatheory.com/" xml:space="preserve">
<![CDATA[    <article class="post">
        <div class="text">
<p>Back on March 12, we hosted the first in a series of <a href="https://www.meetup.com/postgres-extensions-ecosystem-mini-summits/" title="Postgres Extension Ecosystem Mini-Summit on Meetup">PostgreSQL Extensions
Mini Summits</a> leading up to the <a href="https://www.pgevents.ca/events/pgconfdev2025/schedule/session/241/" title="PGConf.dev: Extensions Ecosystem Summit">Extension Ecosystem
Summit</a> at PGConf.dev on May 13. I once again inaugurated the series
with a short talk on the State of the Extension Ecosystem. The talk was
followed by 15 minutes or so of discussion. Here are the relevant links:</p>
<ul>
<li><a href="https://www.youtube.com/watch?v=ebHpEDX9D2Y">Video</a></li>
<li><a href="https://justatheory.com/2024/06/trunk-oci-poc/#demo" title="POC: Distributing Trunk Binaries via OCI (Demo)">OCI Demo</a></li>
<li><a href="https://justatheory.com/shared/extension-ecosystem-summit/state-of-the-ecosystem-mini-summit-2025.pdf">Slides</a></li>
</ul>
<p>And now, with many thanks to <a href="https://dev.to/@floord">Floor Drees</a> for the effort, the transcript from
the session.</p>
<h2 id="introduction">Introduction</h2>
<p>Floor Drees introduced the organizers:</p>
<ul>
<li><a href="https://justatheory.com/">David Wheeler</a>, Principal Architect at <a href="https://tembo.io/">Tembo</a>, maintainer of <a href="https://pgxn.org/">PGXN</a></li>
<li><a href="https://ca.linkedin.com/in/yrashk">Yurii Rashkovskii</a>, <a href="https://omnigres.com/">Omnigres</a></li>
<li><a href="https://pgxn.org/user/keithf4/">Keith Fiske</a>, <a href="https://www.crunchydata.com/">Crunchy Data</a></li>
<li><a href="https://dev.to/@floord">Floor Drees</a>, Principal Program Manager at <a href="https://enterprisedb.com" title="EnterpriseDB">EDB</a>, PostgreSQL CoCC member,
PGDay Lowlands organizer</li>
</ul>
<p>David presented a State of the Extension Ecosystem at this first event, and
shared some updates from PGXN land.</p>
<p>The stream and the closed captions available for the recording are supported
by PGConf.dev and their gold level <a href="https://2025.pgconf.dev/sponsors.html">sponsors</a>, Google, AWS, Huawei, Microsoft,
and EDB.</p>
<h2 id="state-of-the-extensions-ecosystem">State of the Extensions Ecosystem</h2>
<p>So I wanted to give a brief update on the state of the Postgres extension
ecosystem, the past, present, and future. Let&rsquo;s give a brie history; it&rsquo;s
quite long, actually.</p>
<p>There were originally two approaches back in the day. You could use shared
preload libraries to have it preload dynamic shareable libraries into the main
process. And then you could do pure SQL stuff using, including procedural
languages like PL/Perl, PL/Tcl, and such.</p>
<p>And there were a few intrepid early adopters, including PostGIS, BioPostgres,
PL/R, PL/Proxy, and pgTAP, who all made it work. Beginning of Postgres 9.1
Dimitri Fontaine added support for explicit support for extensions in the
Postgres core itself. The key features included the ability to compile and
install extensions. This is again, pure SQL and shared libraries.</p>
<p>There are <code>CREATE</code>, <code>UPDATE</code>, and <code>DROP EXTENSION</code> commands in SQL that you
can use to add extensions to a database, upgrade them to new versions and to
remove them. And then <code>pg_dump</code> and <code>pg_restore</code> support so that extensions
could be considered a single bundle to be backed up and restored with all of
their individual objects being included as part of the backup.</p>
<p>Back then, a number of us, myself included, saw this as an opportunity to have
the extensibility of Postgres itself be a fundamental part of the community
and distribution. I was a long time user of Perl and used CPAN, and I thought
we had something like CPAN for Postgres. So, I proposed <a href="https://pgxn.org/">PGXN</a>, the
PostgreSQL Extension Network, back in 2010. The idea was to do distribution of
source code. You would register namespaces for your extensions.</p>
<p>There was discovery via a website for search, documentation published, tags to
help you find different kinds of objects, and to support installation through
a command line interface. The compile and install stuff that Postgres itself
provides, using PGXS and Configure.</p>
<p>This is what PGXN looks like today. It was launched in 2011. There&rsquo;s a command
line client, this website, an API an a registry you can upload your extensions
to. The most recent one was <code>pg_task</code> a day or so ago.</p>
<p>In the interim, since that came out in 2011/2012, the cloud providers have
come into their own with Postgres, but their support for extensions tends to
be rather limited. For non-core extension counts, as of yesterday, Azure
provides 38 extensions, GCP provides 44 extensions, and AWS 51. These are the
third party extensions that don&rsquo;t come with Postgres and its contrib itself.
Meanwhile, PGXN has 420 extensions available to download, compile, build, and
install.</p>
<p>A GitHub project that tracks random extensions on the internet,
(<a href="https://gist.github.com/joelonsql/e5aa27f8cc9bd22b8999b7de8aee9d47">joelonsql/PostgreSQL-EXTENSIONs.md</a>), which is pretty comprehensive,
has almost 1200 extensions listed. So the question is why is the support not
more broad? Why aren&rsquo;t there a thousand extensions available in every one of
these systems?</p>
<p>Rthis has been a fairly common question that&rsquo;s come up in the last couple
years. A number of new projects have tired to fill in the gaps. One is
<a href="https://github.com/aws/pg_tle" title="pg_tle: Framework for building trusted language extensions for PostgreSQL">Trusted Language Extensions</a>. They wanted to make it easier to
distribute extensions without needing dynamic shared libraries by adding
additional features in the database itself.</p>
<p>The idea was to empower app developers to make it easy to install extensions
via SQL functions rather than having to access the file system of the database
server system itself. It can be portable, so there&rsquo;s no compilation required,
it hooks into the create extension command transparently, supports custom data
types, and there have been plans for foreign data wrappers and background
workers. I&rsquo;m not sure how that&rsquo;s progressed in the past year. The <a href="https://github.com/aws/pg_tle" title="pg_tle: Framework for building trusted language extensions for PostgreSQL">pg_tle</a>
extension itself was created by AWS and Supabase.</p>
<p>Another recent entrant in tooling for extensions is <a href="https://github.com/pgcentralfoundation/pgrx" title="pgrx: Build Postgres Extensions with Rust!">pgrx</a>, which is native
Rust extensions in Postgres. You build dynamic shared libraries, but write
them in pure Rust. The API for pgrx provides full access to Postgres features,
and still provides the developer-friendly tooling that Rust developers are
used to. There&rsquo;s been a lot of community excitement the last couple of years
around pgrx, and it remains under active development &mdash; version 0.13.0 just
came out a week or so ago. It&rsquo;s sponsored and run out of the PgCentral
Foundation.</p>
<p>There have also been a several new registries that have come up to try to fill
the gap and make extensions available. They have emphasized different things
than PGXN. One was ease of use. So, for example, here <a href="https://pgxman.com/" title="npm for PostgreSQL">pgxman</a> says it should
be really easy to install a client in a single command, and then it installs
something, and then it downloads and installs a binary version of your an
extension.</p>
<p>And then there was platform neutrality. They wanted to do binary distribution
and support multiple different platform, to know what binary∑ to install for a
given platform. They provide stats. PGXN doesn&rsquo;t provide any stats, but some
of them are list stats like how many downloads we had, how many in the last
180 days.</p>
<p>And curation. Trunk is another binary extension registry, from my employer,
Tembo. They do categorization of all the extensions on Trunk, which is at 237
now. Quite a few people have come forward to tells us that they don&rsquo;t
necessarily use Trunk to install extensions, but use them to find them,
because the categories are really helpful for people to figure out what sorts
of things are even available, and an option to use.</p>
<p>So here&rsquo;s the State of the Ecosystem as I see it today.</p>
<ul>
<li>
<p>There have been some lost opportunities from the initial excitement around
2010. Extensions remain difficult to find and discover. Some are on PGXN,
some are on GitHub, some are on Trunk, some are on GitLab, etc. There&rsquo;s no
like one place to go to find them all.</p>
</li>
<li>
<p>They remain under-documented and difficult to understand. It takes effort
for developers to write documentation for their extensions, and a lot of
them aren&rsquo;t able to. Some of them do write the documentation, but they
might be in a format that something like PGXN doesn&rsquo;t understand.</p>
</li>
<li>
<p>The maturity of extensions can be difficult to gauge. If you look at that
<a href="https://gist.github.com/joelonsql/e5aa27f8cc9bd22b8999b7de8aee9d47">list of 1200 extensions</a> on GitHub, which ones are the good ones?
Which ones do people care about? That page in particular show the number
of stars for each extension, but that the only metric.</p>
</li>
<li>
<p>They&rsquo;re difficult to configure and install. This is something TLE really
tried to solve, but the uptake on TLE has not been great so far, and it
doesn&rsquo;t support all the use cases. There are a lot of use cases that need
to be able to access the internal APIs of Postgres itself, which means
compiling stuff into shared libraries, and writing them in C or Rust or a
couple of other compiled languages.</p>
<p>That makes them difficult to configure. You have ask questions lik: Which
build system do I use? Do I install the tooling? How do I install it and
configure it? What dependencies does it have? Et cetera.</p>
</li>
<li>
<p>There&rsquo;s no comprehensive binary packaging. The Postgres community&rsquo;s own
packaging systems for Linux &mdash; Apt, and YUM &mdash; do a remarkably good job
of packaging extensions. They probably have more extensions packaged for
those platforms than any of the others. If they have the extension you
need and you&rsquo;re using the PGDG repositories, then this stuff is there. But
even those are still like a fraction of all the potential available
extensions that are out there.</p>
</li>
<li>
<p>Dependency management can be pretty painful. It&rsquo;s difficult to know what
you need to install. I was messing around  yesterday with the PgSQL HTTP
extension, which is a great extension that depends on libcurl. I thought
maybe I could build a package that includes libcurl as part of it. But
then I realized that libcurl depends on other packages, other dynamic
libraries. So I&rsquo;d have to figure out what all those are to get them all
together.</p>
<p>A lot of that goes away if you use a system like apt or yum. But if you,
if you don&rsquo;t, or you just want to install stuff on your Mac or Windows,
it&rsquo;s much more difficult.</p>
</li>
<li>
<p>Centralized source distribution, we&rsquo;ve found found, is insufficient. Even
if all the extensions were available on PGXN, not everybody has the
wherewithal or the expertise to find what they need, download it, compile
it, and build it. Moreover, you don&rsquo;t want to have a compiler on your
production system, so you don&rsquo;t want to be building stuff from source on
your production system. So then you have to get to the business of
building your own packages, which is a whole thing.</p>
</li>
</ul>
<p>But in this state of the extension ecosystem we see new opportunities too. One
I&rsquo;ve been working on for the past year, which we call &ldquo;PGXN v2&rdquo;, is made
possible by my employer, Tembo. The idea was to consider the emerging patterns
&mdash; new registries and new ways of building and releasing and developing
extensions &mdash; and to figure out the deficiencies, and to engage deeply with
the community to work up potential solutions, and to design and implement a
new architecture. The idea is to serve the community for the next decade
really make a PGXN and its infrastructure the source of record for extensions
for Postgres.</p>
<p>In the past year, I did a bunch of design work on it. Here&rsquo;s a high level
architectural view. We&rsquo;d have a root registry, which is still the source code
distribution stuff. There&rsquo;s a web UX over it that would evolve from the
current website. And there&rsquo;s a command line client that knows how to build
extensions from the registry.</p>
<p>But in addition to those three parts, which we have today, we would evolve a
couple of additional parts.</p>
<ol>
<li>
<p>One is &ldquo;interactions&rdquo;, so that when somebody releases a new extension on
PGXN, some notifications could go out through webhooks or some sort of
queue so that downstream systems like the packaging systems could know
something new has come out and maybe automate building and updating their
packages.</p>
</li>
<li>
<p>There could be &ldquo;stats and reports&rdquo;, so we can provide data like how many
downloads there are, what binary registries make them available, what
kinds of reviews and quality metrics rate them. We can develop these stats
and display those on the website.</p>
</li>
<li>
<p>And, ideally, a &ldquo;packaging registry&rdquo; for PGXN to provide binary packages
for all the major platforms of all the extensions we can, to simplify the
installation of extensions for anybody who needs to use them. For
extensions that aren&rsquo;t available through PGDG or if you&rsquo;re not using that
system and you want to install extensions. Late last year, I was focused
on figuring out how t build the packaging system.</p>
</li>
</ol>
<p>Another change that went down in the past year was the Extension Ecosystem
Summit itself. This took place at PGConf.Dev last May. The idea was for a
community of people to come together to collaborate, examine ongoing work in
the extension distribution, examine challenges, identify questions, propose
solutions, and agree on directions for execution. Let&rsquo;s take a look at the
topics that we covered last year at the summit.</p>
<ul>
<li>
<p>One was extension metadata, where the topics covered included packaging
and discoverability, extension development, compatibility and taxonomies
as being important to represent a metadata about extensions &mdash; as well as
versioning standards. One of the outcomes was <a href="https://github.com/pgxn/rfcs/pull/3" title="RFC: Meta Spec v2">an RFC</a> for
version two of the PGXN metadata that incorporates a lot of those needs
into a new metadata format to describe extensions more broadly.</p>
</li>
<li>
<p>Another topic was the binary distribution format and what it should look
like, if we were to have major, distribution format. We talked about being
able to support multiple versions of an extension at one time. There was
some talk about the Python Wheel format as a potential precedent for
binary distribution of code.</p>
<p>There&rsquo;s also an idea to distribute extensions through Docker containers,
also known as the <a href="https://opencontainers.org">Open Container Initiative</a>. Versioning came up
here, as well. One of the outcomes from this session was another PGXN <a href="https://github.com/pgxn/rfcs/pull/2" title="RFC: Binary Distribution Format">RFC
for binary distribution</a>, which was inspired by Python Wheel among other
stuff.</p>
<p>I wanted to give <a href="https://justatheory.com/2024/06/trunk-oci-poc/#demo" title="POC: Distributing Trunk Binaries via OCI (Demo)">a brief demo</a> build on that format. I hacked
some changes into the PGXS <code>Makefile</code> to add a new target, <code>trunk</code> that
builds a binary package called a &ldquo;trunk&rdquo; and uploads it to an OCI registry
for distribution. <a href="https://justatheory.com/2024/06/trunk-oci-poc/#demo" title="POC: Distributing Trunk Binaries via OCI (Demo)">Here&rsquo;s what it looks like</a>.</p>
<ul>
<li>
<p>On my Mac I was compiling my semver extension. Then I go into a Linux
container and compile it again for Linux using the <code>make trunk</code>
command. The result is two <code>.trunk</code> files, one for Postgres 16 on
Darwin and one for Postgres 16 on Linux.</p>
</li>
<li>
<p>There are also some JSON files that are annotations specifically for
OCI. We have a command where we can push these images to an OCI
registry.</p>
</li>
<li>
<p>Then we can then use an install command that knows to download and
install the version of the build appropriate for this platform
(macOS). And then I go into Linux and do the same thing. It also
knows, because of the OCI standard, what the platform is, and so it
installs the appropriate binary.</p>
</li>
</ul>
</li>
<li>
<p>Another topic was ABI and API compatibility. There was some talk at the
Summit about what is the definition of an ABI and an API and how do we
define internal APIs and their use? Maybe there&rsquo;s some way to categorize
APIs in Postgres core for red, green, or in-between, something like that.
There was desire to have more hooks available into different parts of the
system.</p>
<p>One of the outcomes of this session was that I worked with Peter
Eisentraut on some stability guidance for the API and ABI that is now
committed in the docs. You can <a href="https://www.postgresql.org/docs/devel/xfunc-c.html#XFUNC-API-ABI-STABILITY-GUIDANCE" title="Postgres Docs: Server API and ABI Stability Guidance">read them now</a> on in the developer docs,
they&rsquo;ll be part of the Postgres 18 release. The idea is that minor version
releases should be safe to use with other minor versions. If you compiled
your extension against one minor version, it should be perfectly
compatible with other minor versions of the same major release.</p>
<p>Interestingly, there was a release earlier this year, like two weeks after
Peter committed this, where there was an API break. It&rsquo;s the first time in
like 10 years. Robert Treat and I spent quite a bit of time trying to look
for a previous time that happened. I think there was one about 10 years
ago, but then this one happened and, notably it broke the Timescale
database. The Core Team decided to release a fix just a week later to
restore the ABI compatibility.</p>
<p>So it&rsquo;s clear that even though there&rsquo;s guidance, you should in general
be able to rely on it, and it was a motivating factor for the a new
release to fix an ABI break, there are no guarantees.</p>
<p>Another thing that might happen is that I <a href="https://wiki.postgresql.org/wiki/GSoC_2025#ABI_Compliance_Checker" title="PostgreSQL Wiki/GSoC 2025: ABI Compliance Checker">proposed a Google Summer of
Code project to build an ABI checker service</a>. Peter
[embarrassing forgetfulness and misattributed national identity omitted]
Geoghegan <a href="https://postgr.es/m/CAH2-Wzm-W6hSn71sUkz0Rem=qDEU7TnFmc7_jG2DjrLFef_WKQ@mail.gmail.com">POC&rsquo;d an ABI checker</a> in 2023. The project is to take Peter&rsquo;s
POC and build something that could potentially run on every commit or push
to the back branches of the project. Maybe it could be integrated into the
build farm so that, if there&rsquo;s a back-patch to an earlier branch and it
turns red, they quickly the ABI was broken. This change could potentially
provide a higher level of guarantee &mdash; even if they don&rsquo;t end up using
the word &ldquo;guarantee&rdquo; about the stability of the ABIs and APIs. I&rsquo;m hoping
this happens; a number of people have asked about it, and at least one
person has written an application.</p>
</li>
<li>
<p>Another topic at the summit last year was including or excluding
extensions in core. They&rsquo;ve talked about when to add something to core,
when to remove something from core,  whether items in contrib should
actually be moved into core itself, and whether to move metadata about
extensions into catalog. And once again, support for multiple versions
came up; this is a perennial challenge! But I&rsquo;m not aware of much work on
these questions. I&rsquo;m wondering if it&rsquo;s time for a revisit,</p>
</li>
<li>
<p>As a bonus item &mdash; this wasn&rsquo;t a formal topic at the summit last year,
but it came up many times in the mini-summits &mdash; is the challenge of
packaging and lookup. There&rsquo;s only one path to extensions in <code>SHAREDIR</code>.
This creates a number of difficulties. Christoph Berg has a patch for a
PGDG and Debian that adds a second directory. This allowed the PGDG stuff
to actually run tests against extensions without changing the core
installation of the Postgres service itself. Another one is <a href="https://cloudnative-pg.io" title="Run PostgreSQL. The Kubernetes way.">Cloud Native
Postgres</a> immutability. If that directory is part of the image, for
your CloudNative Postgres, you can&rsquo;t install extensions into it.</p>
<p>It&rsquo;s a similar issue, for <a href="https://postgresapp.com">Postgres.app</a> immutability. Postgres.app is a
Mac app, and it&rsquo;s signed by a  certificate provided by Apple. But that
means that if you install an extension in its <code>SHAREDIR</code>, it changes the
signature of the application and it won&rsquo;t start. They work around this
issue through a number of symlink shenanigans, but these issues could be
solved by allowing extension to be installed in multiple locations.</p>
<p>Starting with Christoph&rsquo;s search path patch and a number of discussions we
had at PGConf last year, <a href="https://peter.eisentraut.org">Peter Eisentraut</a> has been working on a search
path patch to the core that would work similar to shared preload
libraries, but it&rsquo;s for finding extension control files. This would allow
you to have them in multiple directories and it will find them in path.</p>
<p>Another interesting development in this line has been, the
<a href="https://cloudnative-pg.io" title="Run PostgreSQL. The Kubernetes way.">CloudNativePG</a> project has been using that extension search path
patch to prototype a new feature coming to Kubernetes that allows one to
mount a volume that&rsquo;s actually another Docker image. If you have your
extension distributed as an OCI image, you can specify that it be mounted
and installed via your CNPG cluster configuration. That means when CNPG
spins up, it puts the extension in the right place. It updates the search
path variables and stuff just works.</p>
<p>A lot of the thought about the stuff went into a <a href="https://justatheory.com/2024/11/rfc-extension-packaging-lookup/" title="RFC: Extension Packaging &amp; Lookup">less formal RFC</a> I wrote
up in my blog, rather than on PGXN. The idea is to take these improvements
and try to more formally specify the organization of extensions separate
from how Postgres organizes shared libraries and shared files.</p>
</li>
</ul>
<p>I said, we&rsquo;re bringing the Extension Summit back! There will be another
Extension Summit hosted our team of organizers, myself, Floor, Keith Fiske
from Crunchy Data, and Yurii from Omnigres. That will be on May 13th in the
morning at <a href="https://2025.pgconf.dev" title="PostgreSQL Development Conference 2025">PGConf.dev</a>; we appreciate their support.</p>
<p>The idea of these Mini Summits is to bring up a number of topics of interest.
Have somebody come and do a 20 or 40 minute talk about it, and then we can
have discussion about implications.</p>
<p>Floor mentioned the schedule, but briefly:</p>
<ul>
<li>March 12: <a href="https://justatheory.com/">David Wheeler</a>, PGXN: &ldquo;State of the Extension Ecosystem”</li>
<li>March 24: <a href="https://peter.eisentraut.org">Peter Eisentraut</a>, Core Team: &ldquo;Implementing an Extension Search Path&rdquo;</li>
<li>April 9: <a href="https://www.df7cb.de">Christoph Berg</a>, Debian: &ldquo;Apt Extension Packaging&rdquo;</li>
<li>April 23:</li>
<li>May 7: <a href="https://www.gabrielebartolini.it">Gabriele Bartolini</a>, CNPG &ldquo;Extension Management in CloudNativePG&rdquo;</li>
</ul>
<p>So, what are your interests in extensions and how they can be improved. There
are a lot of potential topics to talk about at the Summit or at these Mini
Summits: development tools, canonical registry, how easy it is to publish,
continuous delivery, yada, yada, yada, security scanning &mdash; all sorts of
stuff that could go into conceiving, designing, developing, distributing
extensions for Postgres.</p>
<p>I hoe you all will participate. I appreciate you taking the time to listen to
me for half an hour. So I&rsquo;d like to turn it over to, discussion, if people
would like to join in, talk about implications of stuff. Also, we can get to
any questions here.</p>
<h2 id="questions-comments-shout-outs">Questions, comments, shout-outs</h2>
<p><em>Floor</em>: David, at one point you talked about, metadata taxonomy. If you can
elaborate on that a little bit, that&rsquo;s Peter&rsquo;s question.</p>
<p><em>David</em>: So one that people told me that they found useful was one provided by
<a href="https://pgt.dev">Trunk</a>. So it has these limited number of categories, so if you&rsquo;re interested
in machine learning stuff, you could go to the <a href="https://pgt.dev/?cat=machine_learning" title="Trunk Categories: Machine Learning">machine learning</a> stuff and it
shows you what extensions are potentially available. They have 237 extensions
on Trunk now.</p>
<p>PGXN itself allows arbitrary tagging of stuff. It builds <a href="https://pgxn.org/tags" title="PGXN: Release Tags">this little tag
cloud</a>. But if I look at this one here, you can see <a href="https://pgxn.org/dist/uint128/1.0.1/" title="PGXN: uint128 v1.0.1">this one</a> has a bunch of
tags. These are arbitrary tags that are applied by the author. The current
metadata looks <a href="https://api.pgxn.org/src/uint128/uint128-1.0.1/META.json" title="PGXN: uint128 v1.0.1 META.json">like this</a>. It&rsquo;s just plain JSON, and it has a list of tags.
The <a href="https://github.com/pgxn/rfcs/pull/3" title="RFC: Meta Spec v2">PGXN Meta v2 RFC</a> has a bunch of examples. It&rsquo;s an evolution of
that <code>META.json</code>, so the idea is to have a classifications that includes tags
as before, but also adds categories, which are a limited list that would be
controlled by the core [he means &ldquo;root&rdquo;] registry:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;classifications&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;tags&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;testing&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;pair&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;parameter&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="p">],</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;categories&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;Machine Learning&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="p">]</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><h2 id="announcements">Announcements</h2>
<p>Yurii made a number of announcements, summarizing:</p>
<ul>
<li>
<p>There is a new library that they&rsquo;ve been developing at Omnigres that
allows you to develop Postgres extensions in C++. For people who are
interested in developing extensions in C++ and gaining the benefits of
that and not having to do all the tedious things that we have to do with C
extensions: look for <a href="https://cppgres.org">Cppgres</a>. Yurii thinks that within a couple of
months it will reach parity with pgrx.</p>
<p><em>David</em>: So it sounds like it would work more closely to the way PGXS and
C works. Whereas pgrx has all these additional Rust crates you have to
load and like slow compile times and all these dependencies.</p>
<p><em>Yurii</em>: This is just like a layer over the C stuff, an evolution of that.
It&rsquo;s essentially a header only library, so it&rsquo;s a very common thing in the
C++ world. So you don&rsquo;t have to build anything and you just include a
file. And in fact the way I use it, I amalgamate all the header files that
we have into one. Whenever I include it in the project, I just copy the
amalgamation and it&rsquo;s just one file. You don&rsquo;t have any other build chain
associated yet. It is C++ 20, which some people consider new, but by the
time it&rsquo;s mature it&rsquo;s already five years old and most compilers support
it. They have decent support of C++ 20 with a few exclusions, but those
are relatively minor. So for that reason, it&rsquo;s not C++ 23, for example,
because it&rsquo;s not very well supported across compilers, but C++ 20 is.</p>
</li>
<li>
<p>Yurii is giving a talk about <a href="https://github.com/postgres-pm/pgpm">PostgresPM</a> at the Postgres Conference in
Orlando. He&rsquo;ll share the slides and recording with this group. The idea
behind PostgresPM is that it takes a lot of heuristics, takes the URLs of
packages and of extensions and creates packages for different outputs like
for Red Hat, for Debian, perhaps for some other formats in the future. It
focuses on the idea that a lot of things can be figured out.</p>
<p>For example: do we have a new version? Well, we can look at list of tags
in the Git repo. Very commonly that works for say 80 percent of
extensions. Do we need a C compiler? We can see whether we have C files.
We can figure out a lot of stuff without packagers having to specify that
manually every time they have a new extension. And they don&rsquo;t have to
repackage every time there is a new release, because we can detect new
releases and try to build.</p>
</li>
<li>
<p>Yurii is also running an event that, while not affiliated with PGConf.dev,
is strategically scheduled to happen one day before PGConf.dev: <a href="https://pgext.day">Postgres
Extensions Day</a>. The Call for Speakers is open until April 1st. There&rsquo;s
also an option for people who cannot or would not come to Montréal this
year to submit a prerecorded talk. The point of the event is not just to
bring people together, but also ti surface content that can be interesting
to other people. The event itself is free.</p>
</li>
</ul>
<p>Make sure to join our <a href="https://www.meetup.com/postgres-extensions-ecosystem-mini-summits/" title="Postgres Extension Ecosystem Mini-Summit on Meetup">Meetup group</a> and join us live, March 26,
when <a href="https://peter.eisentraut.org">Peter Eisentraut</a> joins us to talk about implementing an extension search
path.</p>

        </div>

        <footer class="tags">
            <h5>More about…</h5>
            <ul>
                <li><a href="https://justatheory.com/tags/postgres/">Postgres</a></li>
                <li><a href="https://justatheory.com/tags/pgxn/">PGXN</a></li>
                <li><a href="https://justatheory.com/tags/extensions/">Extensions</a></li>
                <li><a href="https://justatheory.com/tags/pgconf/">PGConf</a></li>
                <li><a href="https://justatheory.com/tags/summit/">Summit</a></li>
                <li><a href="https://justatheory.com/tags/transcript/">Transcript</a></li>
            </ul>
        </footer>
    </article>
]]></content>
	</entry>
	<entry>
		<id>https://justatheory.com/2025/03/extension-ecosystem-summit/</id>
		<title type="html"><![CDATA[Extension Ecosystem Summit 2025]]></title>
		<link rel="alternate" type="text/html" href="https://justatheory.com/2025/03/extension-ecosystem-summit/"/>
		<updated>2025-04-14T22:48:17Z</updated>
		<published>2025-03-05T00:35:56Z</published>
		<author>
			<name>David E. Wheeler</name>
			<email>david@justatheory.com</email>
			<uri>https://justatheory.com/</uri>
		</author>
		<category scheme="https://justatheory.com/tags" term="postgres" label="Postgres"/>
		<category scheme="https://justatheory.com/tags" term="pgxn" label="PGXN"/>
		<category scheme="https://justatheory.com/tags" term="extensions" label="Extensions"/>
		<category scheme="https://justatheory.com/tags" term="pgconf" label="PGConf"/>
		<category scheme="https://justatheory.com/tags" term="montr%C3%A9al" label="Montréal"/>
		<category scheme="https://justatheory.com/tags" term="summit" label="Summit"/>
		<summary type="html"><![CDATA[We&rsquo;re doing it again! The PostgreSQL Extension Ecosystem Summit returns to
PGConf.dev. We&rsquo;ll again be looking at indexing, discovery, packaging, and
core support. And we&rsquo;ll once again be hosting a series of &ldquo;Mini Summits&rdquo;
leading up to the event. Join us!]]></summary>
		<content type="html" xml:base="https://justatheory.com/" xml:space="preserve">
<![CDATA[    <article class="post">


	<figure title="PGConf.dev"><a href="https://pgconf.dev">
			<img src="https://justatheory.com/shared/extension-ecosystem-summit/pgconf.dev.png" alt="Logo for PGConf.dev" title="PGConf.dev" />
		</a>
	</figure>

        <div class="text">
<p>I&rsquo;m happy to announce that some PostgreSQL colleagues and have once again
organized the <a href="https://www.pgevents.ca/events/pgconfdev2025/schedule/session/241/" title="PGConf.dev: Extensions Ecosystem Summit">Extension Ecosystem Summit</a> at <a href="https://2025.pgconf.dev" title="PostgreSQL Development Conference 2025">PGConf.dev</a> in Montréal on May
13. <a href="https://www.linkedin.com/in/floordrees/">Floor Drees</a>, <a href="https://yrashk.com">Yurii Rashkovskii</a>, <a href="https://www.keithf4.com">Keith Fiske</a> will be on hand to kick
off this <a href="https://en.wikipedia.org/wiki/Unconference" title="Wikipedia: Unconference">unconference</a> session:</p>
  
    <blockquote>
      <p>Participants will collaborate to learn about and explore the ongoing work on
PostgreSQL development and distribution, examine challenges, identify
questions, propose solutions, and agree on directions for execution.</p>

    </blockquote>
  
<p>Going to PGConf.dev? Select it as an &ldquo;Additional Option&rdquo; when you <a href="https://www.pgevents.ca/events/pgconfdev2025/register/" title="Register for PGConf.dev or Update your Registration">register</a>,
or <a href="https://www.pgevents.ca/events/pgconfdev2025/register/" title="Register for PGConf.dev or Update your Registration">update your registration</a> if you&rsquo;ve already registered. Hope to
see you there!</p>
<hr>


	<figure title="Photo of the summit of Mount Hood"><a href="https://www.eventbrite.com/e/851125899477">
			<img src="https://justatheory.com/shared/extension-ecosystem-summit/summit.jpeg" alt="Photo of the summit of Mount Hood" />
		</a>
	</figure>

<h2 id="extension-ecosystem-mini-summit-2-0">Extension Ecosystem Mini-Summit 2.0</h2>
<p>We are also once again hosting a series of virtual gatherings in the lead-up
to the Summit, the <a href="https://www.meetup.com/postgres-extensions-ecosystem-mini-summits/" title="Postgres Extension Ecosystem Mini-Summit on Meetup">Postgres Extension Ecosystem Mini-Summit</a>.</p>
<p>Join us for an hour or so every other Wednesday starting March 12 to hear
contributors to a variety of community and commercial extension initiatives
outline the problems they want to solve, their attempts to so, challenges
discovered along the way, and dreams for an ideal extension ecosystem in the
future. Tentative speaker lineup (will post updates as the schedule fills in):</p>
<ul>
<li>March 12: <a href="https://justatheory.com/">David Wheeler</a>, PGXN: &ldquo;State of the Extension Ecosystem”</li>
<li>March 24: <a href="https://peter.eisentraut.org">Peter Eisentraut</a>, Core Team: &ldquo;Implementing an Extension Search Path&rdquo;</li>
<li>April 9: <a href="https://www.df7cb.de">Christoph Berg</a>, Debian: &ldquo;Apt Extension Packaging&rdquo;</li>
<li>April 23: <a href="https://www.linkedin.com/in/celeste-horgan-b65b5a1a/">Celeste Horgan</a>, <a href="https://www.linkedin.com/in/sonia-valeja-69517a140/">Sonia Valeja</a>, and <a href="https://www.linkedin.com/in/alexeypalazhchenko/overlay/about-this-profile/">Alexey Palazhchenko</a>: &ldquo;The
User POV&rdquo;</li>
<li>May 7: <a href="https://www.gabrielebartolini.it">Gabriele Bartolini</a>, CNPG &ldquo;Extension Management in CloudNativePG&rdquo;</li>
</ul>
<p>Join the <a href="https://www.meetup.com/postgres-extensions-ecosystem-mini-summits/" title="Postgres Extension Ecosystem Mini-Summit on Meetup">meetup</a> for details. These sessions will be recorded and Posted to
the <a href="https://www.youtube.com/@pgconfdev">PGConf.dev YouTube</a> and we&rsquo;ll have again detailed transcripts. Many
thanks to my co-organizers <a href="https://www.linkedin.com/in/floordrees/">Floor Drees</a> and <a href="https://yrashk.com">Yurii Rashkovskii</a>, as well as
the <a href="https://2025.pgconf.dev/about/">PGConf.dev organizers</a> for making this all happen!</p>
<p><strong>Update 2025-04-14:</strong> Added the April 23 session topic and panelists.</p>

        </div>

        <footer class="tags">
            <h5>More about…</h5>
            <ul>
                <li><a href="https://justatheory.com/tags/postgres/">Postgres</a></li>
                <li><a href="https://justatheory.com/tags/pgxn/">PGXN</a></li>
                <li><a href="https://justatheory.com/tags/extensions/">Extensions</a></li>
                <li><a href="https://justatheory.com/tags/pgconf/">PGConf</a></li>
                <li><a href="https://justatheory.com/tags/montr%C3%A9al/">Montréal</a></li>
                <li><a href="https://justatheory.com/tags/summit/">Summit</a></li>
            </ul>
        </footer>
    </article>
]]></content>
	</entry>
	<entry>
		<id>https://justatheory.com/2025/01/sqitch-1.5.0/</id>
		<title type="html"><![CDATA[Sqitch 1.5.0]]></title>
		<link rel="alternate" type="text/html" href="https://justatheory.com/2025/01/sqitch-1.5.0/"/>
		<updated>2025-01-09T02:30:18Z</updated>
		<published>2025-01-09T02:30:18Z</published>
		<author>
			<name>David E. Wheeler</name>
			<email>david@justatheory.com</email>
			<uri>https://justatheory.com/</uri>
		</author>
		<category scheme="https://justatheory.com/tags" term="sqitch" label="Sqitch"/>
		<category scheme="https://justatheory.com/tags" term="mariadb" label="MariaDB"/>
		<category scheme="https://justatheory.com/tags" term="mysql" label="MySQL"/>
		<category scheme="https://justatheory.com/tags" term="yugabyte" label="Yugabyte"/>
		<category scheme="https://justatheory.com/tags" term="oracle" label="Oracle"/>
		<category scheme="https://justatheory.com/tags" term="snowflake" label="Snowflake"/>
		<summary type="html"><![CDATA[Sqitch v1.5.0: out now in all the usual places!]]></summary>
		<content type="html" xml:base="https://justatheory.com/" xml:space="preserve">
<![CDATA[    <article class="post">


	<figure class="clear right" title="Sqitch: Sensible database change management">
			<img src="https://justatheory.com/2025/01/sqitch-1.5.0/sqitch-icon.png" alt="Sqitch Logo" title="Sqitch: Sensible database change management" width="256px" />
	</figure>

        <div class="text">
<p>Released yesterday: <a href="https://sqitch.org" title="Sqitch: Sensible database change management">Sqitch</a> v1.5.0. This version the MySQL driver
<a href="https://metacpan.org/pod/DBD::mysql" title="DBD::mysql - MySQL driver for the Perl5 Database Interface (DBI)">DBD::mysql</a> with <a href="https://metacpan.org/pod/DBD::MariaDB" title="DBD::MariaDB - MariaDB and MySQL driver for the Perl5 Database Interface (DBI)">DBD::MariaDB</a>, both for its better backward compatibility
with MySQL as well as MariaDB driver libraries and for its improved Unicode
handling. The <a href="https://hub.docker.com/r/sqitch/sqitch">Docker image</a> likewise switched to the MariaDB <code>mysql</code>
client. I expect no compatibility issues, but you never know! Please file an
<a href="https://github.com/sqitchers/sqitch/issues" title="Sqitch Issues">issue</a> should you find any.</p>
<p>V1.5.0 also features a fixes for Yugabyte deployment, Oracle error handling,
existing Snowflake schemas, connecting to MySQL/MariaDB without a database
name, and omitting the <code>checkit</code> MySQL/MariaDB function when the Sqitch user
lacks sufficient permission to create it. Sqitch now will also complain when
deploying with <code>--log-only</code> and a deployment file is missing.</p>
<p>Find it in the usual places:</p>
<ul>
<li><a href="https://sqitch.org" title="Sqitch: Sensible database change management">sqitch.org</a></li>
<li><a href="https://github.com/sqitchers/sqitch">GitHub</a></li>
<li><a href="https://metacpan.org/dist/App-Sqitch">CPAN</a></li>
<li><a href="https://hub.docker.com/r/sqitch/sqitch">Docker</a></li>
<li><a href="https://github.com/sqitchers/homebrew-sqitch">Homebrew</a></li>
</ul>
<p>Many thanks to everyone who has enjoyed using Sqitch and let me know in
person, via email Mastodon, bug reports, and patches. It gratifies me how
useful people find it.</p>

        </div>

        <footer class="tags">
            <h5>More about…</h5>
            <ul>
                <li><a href="https://justatheory.com/tags/sqitch/">Sqitch</a></li>
                <li><a href="https://justatheory.com/tags/mariadb/">MariaDB</a></li>
                <li><a href="https://justatheory.com/tags/mysql/">MySQL</a></li>
                <li><a href="https://justatheory.com/tags/yugabyte/">Yugabyte</a></li>
                <li><a href="https://justatheory.com/tags/oracle/">Oracle</a></li>
                <li><a href="https://justatheory.com/tags/snowflake/">Snowflake</a></li>
            </ul>
        </footer>
    </article>
]]></content>
	</entry>
	<entry>
		<id>https://justatheory.com/2025/01/uri-mysql-mariadb/</id>
		<title type="html"><![CDATA[Should URI::mysql Switch to DBD::MariaDB?]]></title>
		<link rel="alternate" type="text/html" href="https://www.perlmonks.org/?node_id=11163487"/>
		<link rel="related" type="text/html" href="https://justatheory.com/2025/01/uri-mysql-mariadb/"/>
		<updated>2025-01-01T22:47:31Z</updated>
		<published>2025-01-01T22:47:31Z</published>
		<author>
			<name>David E. Wheeler</name>
			<email>david@justatheory.com</email>
			<uri>https://justatheory.com/</uri>
		</author>
		<category scheme="https://justatheory.com/tags" term="perl" label="Perl"/>
		<category scheme="https://justatheory.com/tags" term="sqitch" label="Sqitch"/>
		<category scheme="https://justatheory.com/tags" term="uridb" label="URI::db"/>
		<category scheme="https://justatheory.com/tags" term="mysql" label="MySQL"/>
		<category scheme="https://justatheory.com/tags" term="mariadb" label="MariaDB"/>
		<summary type="html"><![CDATA[Should Sqitch and URI::mysql use DBD::MariaDB instead of DBD::mysql? If so,
what are the implications for Sqitch deployment and usage?]]></summary>
		<content type="html" xml:base="https://justatheory.com/" xml:space="preserve">
<![CDATA[    <article class="post">
        <div class="text">
<p>I seek the wisdom of the Perl Monks:</p>
  
    <blockquote>
      <p>The Sqitch project got <a href="https://github.com/sqitchers/sqitch/issues/825" title="sqitchers/sqitch#825 Support DBD::MariaDB">a request</a> to switch from <a href="https://metacpan.org/pod/DBD::mysql">DBD::mysql</a>
to <a href="https://metacpan.org/pod/DBD::MariaDB">DBD::MariaDB</a>. DBD::mysql 5&rsquo;s requirement to build from the MySQL 8
client library provides the impetus for the request, but in poking around, I
found a <a href="https://blogs.perl.org/users/grinnz/2023/12/migrating-from-dbdmysql-to-dbdmariadb.html">blogs.perl.org post</a> highlighting some Unicode fixes in
DBD::MariaDB, as well.</p>
<p>Now, Sqitch likely doesn&rsquo;t have the Unicode issue (it always works with Perl
Unicode strings), but it depends on <a href="https://metacpan.org/pod/URI::db">URI::db</a> to provide the DBI connection
string. For MySQL URIs, the <a href="https://metacpan.org/dist/URI-db/source/lib/URI/mysql.pm#L6">URI::mysql dbi_driver</a> method returns <code>mysql</code>.</p>
<p>Should it be changed to return <code>MariaDB</code>, instead? Is there general
community consensus that DBD::MariaDB provides better compatibility with
both MySQL and MariaDB these days?</p>
<p>I&rsquo;m also curious what the impact of this change would be for Sqitch.
Presumably, if DBD::MariaDB can build against either the MariaDB or MySQL
client library, it is the more flexible choice to continue supporting both
databases going forward.</p>

    </blockquote>
  
<p>Feedback appreciated <a href="https://www.perlmonks.org/?node_id=11163487">via PerlMonks</a> or the <a href="https://github.com/sqitchers/sqitch/issues/825" title="sqitchers/sqitch#825 Support DBD::MariaDB">Sqitch issue</a>.</p>
  
    <blockquote class="alert alert-note">
      <p class="alert-title">Update 2025-01-08</p>
      <p><a href="https://metacpan.org/release/DWHEELER/URI-db-0.23">URI-db 0.23</a> uses <a href="https://metacpan.org/pod/DBD::MariaDB">DBD::MariaDB</a> instead of <a href="https://metacpan.org/pod/DBD::mysql">DBD::mysql</a> for both
URI::mysql and URI::MariaDB.</p>
<p>Similarly, <a href="https://github.com/sqitchers/sqitch/releases/tag/v1.5.0">Sqitch v1.5.0</a> always uses <a href="https://metacpan.org/pod/DBD::MariaDB">DBD::MariaDB</a> when connecting to
MySQL or MariaDB, even when using older versions of URI::db. Thanks everyone
for the feedback and suggestions!</p>
    </blockquote>
  

        </div>

        <footer class="tags">
            <h5>More about…</h5>
            <ul>
                <li><a href="https://justatheory.com/tags/perl/">Perl</a></li>
                <li><a href="https://justatheory.com/tags/sqitch/">Sqitch</a></li>
                <li><a href="https://justatheory.com/tags/uridb/">URI::db</a></li>
                <li><a href="https://justatheory.com/tags/mysql/">MySQL</a></li>
                <li><a href="https://justatheory.com/tags/mariadb/">MariaDB</a></li>
            </ul>
        </footer>
    </article>
]]></content>
	</entry>
	<entry>
		<id>https://justatheory.com/2025/01/jsonpath-located/</id>
		<title type="html"><![CDATA[New JSONPath Feature: SelectLocated]]></title>
		<link rel="alternate" type="text/html" href="https://justatheory.com/2025/01/jsonpath-located/"/>
		<updated>2025-01-01T20:43:50Z</updated>
		<published>2025-01-01T20:43:50Z</published>
		<author>
			<name>David E. Wheeler</name>
			<email>david@justatheory.com</email>
			<uri>https://justatheory.com/</uri>
		</author>
		<category scheme="https://justatheory.com/tags" term="go" label="Go"/>
		<category scheme="https://justatheory.com/tags" term="jsonpath" label="JSONPath"/>
		<category scheme="https://justatheory.com/tags" term="playground" label="Playground"/>
		<category scheme="https://justatheory.com/tags" term="rfc-9535" label="RFC 9535"/>
		<summary type="html"><![CDATA[New in the jsonpath Go package and Playground: &ldquo;Located&rdquo; results that pair
selected values with normalized paths to their locations.]]></summary>
		<content type="html" xml:base="https://justatheory.com/" xml:space="preserve">
<![CDATA[    <article class="post">
        <div class="text">
<p>Happy New Year! 🎉🥳🍾🥂</p>
<p>The <a href="https://www.rfc-editor.org/rfc/rfc9535.html" title="RFC 9535 JSONPath: Query Expressions for JSON">JSONPath RFC</a> includes a section on defining <a href="https://www.rfc-editor.org/rfc/rfc9535#name-normalized-paths" title="RFC 9535 JSONPath: Normalized Paths">normalized paths</a>,
which use a subset of JSONPath syntax to define paths to the location of a
node in a JSON value. I hadn&rsquo;t thought much about it, but noticed that the
<a href="https://serdejsonpath.live">serde JSONPath Sandbox</a> provides a &ldquo;Located&rdquo; switch adds them to query
results. For the sake of complementarity, I added the same feature to the <a href="https://theory.github.io/jsonpath/">Go
JSONPath Playground</a>.</p>
<p>🛝 See it in action with <a href="https://theory.github.io/jsonpath/?p=%2524..price&amp;amp;j=%257B%250A%2520%2520%2522store%2522%253A%2520%257B%250A%2520%2520%2520%2520%2522book%2522%253A%2520%255B%250A%2520%2520%2520%2520%2520%2520%257B%250A%2520%2520%2520%2520%2520%2520%2520%2520%2522category%2522%253A%2520%2522reference%2522%252C%250A%2520%2520%2520%2520%2520%2520%2520%2520%2522author%2522%253A%2520%2522Nigel%2520Rees%2522%252C%250A%2520%2520%2520%2520%2520%2520%2520%2520%2522title%2522%253A%2520%2522Sayings%2520of%2520the%2520Century%2522%252C%250A%2520%2520%2520%2520%2520%2520%2520%2520%2522price%2522%253A%25208.95%250A%2520%2520%2520%2520%2520%2520%257D%252C%250A%2520%2520%2520%2520%2520%2520%257B%250A%2520%2520%2520%2520%2520%2520%2520%2520%2522category%2522%253A%2520%2522fiction%2522%252C%250A%2520%2520%2520%2520%2520%2520%2520%2520%2522author%2522%253A%2520%2522Evelyn%2520Waugh%2522%252C%250A%2520%2520%2520%2520%2520%2520%2520%2520%2522title%2522%253A%2520%2522Sword%2520of%2520Honour%2522%252C%250A%2520%2520%2520%2520%2520%2520%2520%2520%2522price%2522%253A%252012.99%250A%2520%2520%2520%2520%2520%2520%257D%252C%250A%2520%2520%2520%2520%2520%2520%257B%250A%2520%2520%2520%2520%2520%2520%2520%2520%2522category%2522%253A%2520%2522fiction%2522%252C%250A%2520%2520%2520%2520%2520%2520%2520%2520%2522author%2522%253A%2520%2522Herman%2520Melville%2522%252C%250A%2520%2520%2520%2520%2520%2520%2520%2520%2522title%2522%253A%2520%2522Moby%2520Dick%2522%252C%250A%2520%2520%2520%2520%2520%2520%2520%2520%2522isbn%2522%253A%2520%25220-553-21311-3%2522%252C%250A%2520%2520%2520%2520%2520%2520%2520%2520%2522price%2522%253A%25208.99%250A%2520%2520%2520%2520%2520%2520%257D%252C%250A%2520%2520%2520%2520%2520%2520%257B%250A%2520%2520%2520%2520%2520%2520%2520%2520%2522category%2522%253A%2520%2522fiction%2522%252C%250A%2520%2520%2520%2520%2520%2520%2520%2520%2522author%2522%253A%2520%2522J.%2520R.%2520R.%2520Tolkien%2522%252C%250A%2520%2520%2520%2520%2520%2520%2520%2520%2522title%2522%253A%2520%2522The%2520Lord%2520of%2520the%2520Rings%2522%252C%250A%2520%2520%2520%2520%2520%2520%2520%2520%2522isbn%2522%253A%2520%25220-395-19395-8%2522%252C%250A%2520%2520%2520%2520%2520%2520%2520%2520%2522price%2522%253A%252022.99%250A%2520%2520%2520%2520%2520%2520%257D%250A%2520%2520%2520%2520%255D%252C%250A%2520%2520%2520%2520%2522bicycle%2522%253A%2520%257B%250A%2520%2520%2520%2520%2520%2520%2522color%2522%253A%2520%2522red%2522%252C%250A%2520%2520%2520%2520%2520%2520%2522price%2522%253A%2520399%250A%2520%2520%2520%2520%257D%250A%2520%2520%257D%250A%257D&amp;amp;o=1">this example</a>, where instead of the default output:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">[</span>
</span></span><span class="line"><span class="cl">  <span class="mf">8.95</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="mf">12.99</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="mf">8.99</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="mf">22.99</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="mi">399</span>
</span></span><span class="line"><span class="cl"><span class="p">]</span>
</span></span></code></pre></div><p>The located result is:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">[</span>
</span></span><span class="line"><span class="cl">  <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;node&#34;</span><span class="p">:</span> <span class="mf">8.95</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;path&#34;</span><span class="p">:</span> <span class="s2">&#34;$[&#39;store&#39;][&#39;book&#39;][0][&#39;price&#39;]&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">},</span>
</span></span><span class="line"><span class="cl">  <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;node&#34;</span><span class="p">:</span> <span class="mf">12.99</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;path&#34;</span><span class="p">:</span> <span class="s2">&#34;$[&#39;store&#39;][&#39;book&#39;][1][&#39;price&#39;]&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">},</span>
</span></span><span class="line"><span class="cl">  <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;node&#34;</span><span class="p">:</span> <span class="mf">8.99</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;path&#34;</span><span class="p">:</span> <span class="s2">&#34;$[&#39;store&#39;][&#39;book&#39;][2][&#39;price&#39;]&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">},</span>
</span></span><span class="line"><span class="cl">  <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;node&#34;</span><span class="p">:</span> <span class="mf">22.99</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;path&#34;</span><span class="p">:</span> <span class="s2">&#34;$[&#39;store&#39;][&#39;book&#39;][3][&#39;price&#39;]&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">},</span>
</span></span><span class="line"><span class="cl">  <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;node&#34;</span><span class="p">:</span> <span class="mi">399</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;path&#34;</span><span class="p">:</span> <span class="s2">&#34;$[&#39;store&#39;][&#39;bicycle&#39;][&#39;price&#39;]&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">]</span>
</span></span></code></pre></div><p><a href="https://pkg.go.dev/github.com/theory/jsonpath@v0.3.0">v0.3.0</a> of the <code>github.com/theory/jsonpath</code> Go package enables this feature
via its new <a href="https://pkg.go.dev/github.com/theory/jsonpath@v0.3.0#Path.SelectLocated"><code>SelectLocated</code></a> method, which returns a <a href="https://pkg.go.dev/github.com/theory/jsonpath@v0.3.0#LocatedNodeList"><code>LocatedNodeList</code></a> that
shows off a few of the benfits of pairing JSONPath query results with paths
that uniquely identify their locations in a JSON value, including sorting and
deduplication. It also takes advantage of <a href="https://go.dev/blog/range-functions">Go v1.23 iterators</a>, providing
methods to range over all the results, just the node values, and just the
paths. As a result, v0.3.0 now requires Go 1.23.</p>
<p>The <a href="https://crates.io/crates/serde_json_path">serde_json_path Rust crate</a> inspired the use of <a href="https://pkg.go.dev/github.com/theory/jsonpath@v0.3.0#LocatedNodeList"><code>LocatedNodeList</code></a>
rather than a simple slice of <a href="https://pkg.go.dev/github.com/theory/jsonpath@v0.3.0/spec#LocatedNode"><code>LocatedNode</code></a> structs, but I truly embraced it
once I noticed the the focus on &ldquo;nodelists&rdquo; in the <a href="https://www.rfc-editor.org/rfc/rfc9535#name-overview" title="RFC 9535 JSONPath: Overview">RFC&rsquo;s overview</a>, which
provides this definition:</p>
  
    <blockquote>
      <p>A JSONPath <em>expression</em> is a string that, when applied to a JSON value (the
<em>query argument</em>), selects zero or more nodes of the argument and outputs
these nodes as a nodelist.</p>

    </blockquote>
  
<p>It regularly refers to nodelists thereafter, and it seemed useful to have an
object to which more features can be added in the future.
<code>github.com/theory/jsonpath</code> <a href="https://pkg.go.dev/github.com/theory/jsonpath@v0.3.0">v0.3.0</a> thererfore also changes the result value
of <a href="https://pkg.go.dev/github.com/theory/jsonpath@v0.3.0#Path.Select"><code>Select</code></a> from <code>[]any</code> to the new <a href="https://pkg.go.dev/github.com/theory/jsonpath@v0.3.0#NodeList"><code>NodeList</code></a> struct, an alias for
<code>[]any</code>. For now it adds a single method, <code>All</code>, which again relies on <a href="https://go.dev/blog/range-functions">Go
v1.23 iterators</a> to iterate over selected nodes.</p>
<p>While the data type has changed, usage otherwise has not. One can iterate
directly over values just as before:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="k">for</span><span class="w"> </span><span class="nx">_</span><span class="p">,</span><span class="w"> </span><span class="nx">val</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="k">range</span><span class="w"> </span><span class="nx">path</span><span class="p">.</span><span class="nf">Select</span><span class="p">(</span><span class="nx">jsonInput</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Printf</span><span class="p">(</span><span class="s">&#34;%v\n&#34;</span><span class="p">,</span><span class="w"> </span><span class="nx">val</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></div><p>But <code>All</code> removes the need to alias-away the index value with <code>_</code>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="k">for</span><span class="w"> </span><span class="nx">val</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="k">range</span><span class="w"> </span><span class="nx">path</span><span class="p">.</span><span class="nf">Select</span><span class="p">(</span><span class="nx">jsonInput</span><span class="p">).</span><span class="nf">All</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Printf</span><span class="p">(</span><span class="s">&#34;%v\n&#34;</span><span class="p">,</span><span class="w"> </span><span class="nx">val</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></div><p>I don&rsquo;t expect any further incompatible changes to the main <code>jsonpath</code> module,
but adding these return values now allows new features to be added to the
selected node lists in the future.</p>
<p>May you find it useful!</p>

        </div>

        <footer class="tags">
            <h5>More about…</h5>
            <ul>
                <li><a href="https://justatheory.com/tags/go/">Go</a></li>
                <li><a href="https://justatheory.com/tags/jsonpath/">JSONPath</a></li>
                <li><a href="https://justatheory.com/tags/playground/">Playground</a></li>
                <li><a href="https://justatheory.com/tags/rfc-9535/">RFC 9535</a></li>
            </ul>
        </footer>
    </article>
]]></content>
	</entry>
	<entry>
		<id>https://justatheory.com/2024/12/playground-update/</id>
		<title type="html"><![CDATA[SQL/JSON Path Playground Update]]></title>
		<link rel="alternate" type="text/html" href="https://theory.github.io/sqljson/"/>
		<link rel="related" type="text/html" href="https://justatheory.com/2024/12/playground-update/"/>
		<updated>2024-12-31T20:40:32Z</updated>
		<published>2024-12-31T20:40:32Z</published>
		<author>
			<name>David E. Wheeler</name>
			<email>david@justatheory.com</email>
			<uri>https://justatheory.com/</uri>
		</author>
		<category scheme="https://justatheory.com/tags" term="postgres" label="Postgres"/>
		<category scheme="https://justatheory.com/tags" term="sql/json" label="SQL/JSON"/>
		<category scheme="https://justatheory.com/tags" term="json-path" label="JSON Path"/>
		<category scheme="https://justatheory.com/tags" term="go" label="Go"/>
		<category scheme="https://justatheory.com/tags" term="playground" label="Playground"/>
		<summary type="html"><![CDATA[The Go SQL/JSON Playground has been updated with a fresh design and PostgreSQL
17 compatibility.]]></summary>
		<content type="html" xml:base="https://justatheory.com/" xml:space="preserve">
<![CDATA[    <article class="post">
        <div class="text">
<p>Based on the recently-released Go <a href="https://theory.github.io/jsonpath/" title="Go JSONPath Playground">JSONPath</a> and <a href="https://theory.github.io/jsontree/" title="Go JSONTree Playground">JSONTree</a> playgrounds, I&rsquo;ve
updated the design and of the <a href="https://theory.github.io/sqljson/" title="Go SQL/JSON Playground">SQL/JSON Playground</a>. It now comes
populated with sample JSON borrowed from <a href="https://www.rfc-editor.org/rfc/rfc9535.html" title="RFC 9535 JSONPath: Query Expressions for JSON">RFC 9535</a>, as well as a selection of
queries that randomly populate the query field on each reload. I believe this
makes the playground nicer to start using, not to mention more pleasing to the
eye.</p>
<p>The playground has also been updated to use the recently-released
<a href="https://pkg.go.dev/github.com/theory/sqljson@v0.2.1/path">sqljson/path v0.2 package</a>, which replicates a few changes included in the
<a href="https://www.postgresql.org/about/news/postgresql-17-released-2936/" title="PostgreSQL 17 Released!">PostgreSQL 17 release</a>. Notably, the <code>.string()</code> function no longer uses a
time zone or variable format to for dates and times.</p>
<p>Curious to see it in action? <a href="https://theory.github.io/sqljson/" title="Go SQL/JSON Playground">Check it out!</a></p>

        </div>

        <footer class="tags">
            <h5>More about…</h5>
            <ul>
                <li><a href="https://justatheory.com/tags/postgres/">Postgres</a></li>
                <li><a href="https://justatheory.com/tags/sql/json/">SQL/JSON</a></li>
                <li><a href="https://justatheory.com/tags/json-path/">JSON Path</a></li>
                <li><a href="https://justatheory.com/tags/go/">Go</a></li>
                <li><a href="https://justatheory.com/tags/playground/">Playground</a></li>
            </ul>
        </footer>
    </article>
]]></content>
	</entry>
	<entry>
		<id>https://justatheory.com/2024/12/jsontree/</id>
		<title type="html"><![CDATA[JSONTree Module and Playground]]></title>
		<link rel="alternate" type="text/html" href="https://justatheory.com/2024/12/jsontree/"/>
		<updated>2024-12-22T21:33:39Z</updated>
		<published>2024-12-22T21:33:39Z</published>
		<author>
			<name>David E. Wheeler</name>
			<email>david@justatheory.com</email>
			<uri>https://justatheory.com/</uri>
		</author>
		<category scheme="https://justatheory.com/tags" term="go" label="Go"/>
		<category scheme="https://justatheory.com/tags" term="jsontree" label="JSONTree"/>
		<category scheme="https://justatheory.com/tags" term="jsonpath" label="JSONPath"/>
		<category scheme="https://justatheory.com/tags" term="playground" label="Playground"/>
		<category scheme="https://justatheory.com/tags" term="rfc-9535" label="RFC 9535"/>
		<summary type="html"><![CDATA[I&rsquo;m happy to announce the release of the JSONTree Go module and Wasm-powered
in-browser playground.]]></summary>
		<content type="html" xml:base="https://justatheory.com/" xml:space="preserve">
<![CDATA[    <article class="post">
        <div class="text">
<p>As a follow-up to the JSONPath module and playground I released <a href="https://justatheory.com/2024/11/go-jsonpath-playground/" title="Introducing RFC 9535 Go JSONPath and Playground">last month</a>,
I&rsquo;m happy to announce the follow-up project, called JSONTree. I&rsquo;ve implemented
it in the <a href="https://pkg.go.dev/github.com/theory/jsontree">github.com/theory/jsontree</a> Go package, and built a <a href="https://webassembly.org" title="WebAssembly">Wasm</a>-powered
browser <a href="https://theory.github.io/jsontree/" title="Go JSONTree Playground">playground</a> for it.</p>
<h2 id="jsontree">JSON<em>Tree</em>?</h2>
<p>While a <a href="https://www.rfc-editor.org/rfc/rfc9535.html" title="RFC 9535 JSONPath: Query Expressions for JSON">RFC 9535 JSONPath</a> query selects and returns an array of values from
the end of a path expression, a JSONTree compiles multiple JSONPath queries
into a single query that selects values from multiple path expressions. It
returns results not as an array, but as a subset of the query input,
preserving the paths for each selected value.</p>
<p>In other words, it compiles multiple paths into a single tree of selection
paths, and preserves the tree structure of the input. Hence JSON<em>Tree</em>.</p>
<h3 id="example">Example</h3>
<p>Consider this JSON:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;store&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;book&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">      <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;category&#34;</span><span class="p">:</span> <span class="s2">&#34;reference&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;author&#34;</span><span class="p">:</span> <span class="s2">&#34;Nigel Rees&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;title&#34;</span><span class="p">:</span> <span class="s2">&#34;Sayings of the Century&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;price&#34;</span><span class="p">:</span> <span class="mf">8.95</span>
</span></span><span class="line"><span class="cl">      <span class="p">},</span>
</span></span><span class="line"><span class="cl">      <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;category&#34;</span><span class="p">:</span> <span class="s2">&#34;fiction&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;author&#34;</span><span class="p">:</span> <span class="s2">&#34;Evelyn Waugh&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;title&#34;</span><span class="p">:</span> <span class="s2">&#34;Sword of Honour&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;price&#34;</span><span class="p">:</span> <span class="mf">12.99</span>
</span></span><span class="line"><span class="cl">      <span class="p">},</span>
</span></span><span class="line"><span class="cl">      <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;category&#34;</span><span class="p">:</span> <span class="s2">&#34;fiction&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;author&#34;</span><span class="p">:</span> <span class="s2">&#34;Herman Melville&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;title&#34;</span><span class="p">:</span> <span class="s2">&#34;Moby Dick&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;isbn&#34;</span><span class="p">:</span> <span class="s2">&#34;0-553-21311-3&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;price&#34;</span><span class="p">:</span> <span class="mf">8.99</span>
</span></span><span class="line"><span class="cl">      <span class="p">},</span>
</span></span><span class="line"><span class="cl">      <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;category&#34;</span><span class="p">:</span> <span class="s2">&#34;fiction&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;author&#34;</span><span class="p">:</span> <span class="s2">&#34;J. R. R. Tolkien&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;title&#34;</span><span class="p">:</span> <span class="s2">&#34;The Lord of the Rings&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;isbn&#34;</span><span class="p">:</span> <span class="s2">&#34;0-395-19395-8&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;price&#34;</span><span class="p">:</span> <span class="mf">22.99</span>
</span></span><span class="line"><span class="cl">      <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">],</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;bicycle&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;color&#34;</span><span class="p">:</span> <span class="s2">&#34;red&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;price&#34;</span><span class="p">:</span> <span class="mi">399</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>This JSONPath query:</p>
<pre tabindex="0"><code class="language-jsonpath" data-lang="jsonpath">$..price
</code></pre><p>Selects these values (<a href="https://theory.github.io/jsonpath/?p=%2524..price&amp;amp;j=%257B%250A%2520%2520%2522store%2522%253A%2520%257B%250A%2520%2520%2520%2520%2522book%2522%253A%2520%255B%250A%2520%2520%2520%2520%2520%2520%257B%250A%2520%2520%2520%2520%2520%2520%2520%2520%2522category%2522%253A%2520%2522reference%2522%252C%250A%2520%2520%2520%2520%2520%2520%2520%2520%2522author%2522%253A%2520%2522Nigel%2520Rees%2522%252C%250A%2520%2520%2520%2520%2520%2520%2520%2520%2522title%2522%253A%2520%2522Sayings%2520of%2520the%2520Century%2522%252C%250A%2520%2520%2520%2520%2520%2520%2520%2520%2522price%2522%253A%25208.95%250A%2520%2520%2520%2520%2520%2520%257D%252C%250A%2520%2520%2520%2520%2520%2520%257B%250A%2520%2520%2520%2520%2520%2520%2520%2520%2522category%2522%253A%2520%2522fiction%2522%252C%250A%2520%2520%2520%2520%2520%2520%2520%2520%2522author%2522%253A%2520%2522Evelyn%2520Waugh%2522%252C%250A%2520%2520%2520%2520%2520%2520%2520%2520%2522title%2522%253A%2520%2522Sword%2520of%2520Honour%2522%252C%250A%2520%2520%2520%2520%2520%2520%2520%2520%2522price%2522%253A%252012.99%250A%2520%2520%2520%2520%2520%2520%257D%252C%250A%2520%2520%2520%2520%2520%2520%257B%250A%2520%2520%2520%2520%2520%2520%2520%2520%2522category%2522%253A%2520%2522fiction%2522%252C%250A%2520%2520%2520%2520%2520%2520%2520%2520%2522author%2522%253A%2520%2522Herman%2520Melville%2522%252C%250A%2520%2520%2520%2520%2520%2520%2520%2520%2522title%2522%253A%2520%2522Moby%2520Dick%2522%252C%250A%2520%2520%2520%2520%2520%2520%2520%2520%2522isbn%2522%253A%2520%25220-553-21311-3%2522%252C%250A%2520%2520%2520%2520%2520%2520%2520%2520%2522price%2522%253A%25208.99%250A%2520%2520%2520%2520%2520%2520%257D%252C%250A%2520%2520%2520%2520%2520%2520%257B%250A%2520%2520%2520%2520%2520%2520%2520%2520%2522category%2522%253A%2520%2522fiction%2522%252C%250A%2520%2520%2520%2520%2520%2520%2520%2520%2522author%2522%253A%2520%2522J.%2520R.%2520R.%2520Tolkien%2522%252C%250A%2520%2520%2520%2520%2520%2520%2520%2520%2522title%2522%253A%2520%2522The%2520Lord%2520of%2520the%2520Rings%2522%252C%250A%2520%2520%2520%2520%2520%2520%2520%2520%2522isbn%2522%253A%2520%25220-395-19395-8%2522%252C%250A%2520%2520%2520%2520%2520%2520%2520%2520%2522price%2522%253A%252022.99%250A%2520%2520%2520%2520%2520%2520%257D%250A%2520%2520%2520%2520%255D%252C%250A%2520%2520%2520%2520%2522bicycle%2522%253A%2520%257B%250A%2520%2520%2520%2520%2520%2520%2522color%2522%253A%2520%2522red%2522%252C%250A%2520%2520%2520%2520%2520%2520%2522price%2522%253A%2520399%250A%2520%2520%2520%2520%257D%250A%2520%2520%257D%250A%257D&amp;amp;o=0">playground</a>):</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">[</span><span class="mf">8.95</span><span class="p">,</span> <span class="mf">12.99</span><span class="p">,</span> <span class="mf">8.99</span><span class="p">,</span> <span class="mf">22.99</span><span class="p">,</span> <span class="mi">399</span><span class="p">]</span>
</span></span></code></pre></div><p>While this JSONPath query:</p>
<pre tabindex="0"><code class="language-jsonpath" data-lang="jsonpath">$..author
</code></pre><p>Selects (<a href="https://theory.github.io/jsonpath/?p=%2524..author&amp;amp;j=%257B%250A%2520%2520%2522store%2522%253A%2520%257B%250A%2520%2520%2520%2520%2522book%2522%253A%2520%255B%250A%2520%2520%2520%2520%2520%2520%257B%250A%2520%2520%2520%2520%2520%2520%2520%2520%2522category%2522%253A%2520%2522reference%2522%252C%250A%2520%2520%2520%2520%2520%2520%2520%2520%2522author%2522%253A%2520%2522Nigel%2520Rees%2522%252C%250A%2520%2520%2520%2520%2520%2520%2520%2520%2522title%2522%253A%2520%2522Sayings%2520of%2520the%2520Century%2522%252C%250A%2520%2520%2520%2520%2520%2520%2520%2520%2522price%2522%253A%25208.95%250A%2520%2520%2520%2520%2520%2520%257D%252C%250A%2520%2520%2520%2520%2520%2520%257B%250A%2520%2520%2520%2520%2520%2520%2520%2520%2522category%2522%253A%2520%2522fiction%2522%252C%250A%2520%2520%2520%2520%2520%2520%2520%2520%2522author%2522%253A%2520%2522Evelyn%2520Waugh%2522%252C%250A%2520%2520%2520%2520%2520%2520%2520%2520%2522title%2522%253A%2520%2522Sword%2520of%2520Honour%2522%252C%250A%2520%2520%2520%2520%2520%2520%2520%2520%2522price%2522%253A%252012.99%250A%2520%2520%2520%2520%2520%2520%257D%252C%250A%2520%2520%2520%2520%2520%2520%257B%250A%2520%2520%2520%2520%2520%2520%2520%2520%2522category%2522%253A%2520%2522fiction%2522%252C%250A%2520%2520%2520%2520%2520%2520%2520%2520%2522author%2522%253A%2520%2522Herman%2520Melville%2522%252C%250A%2520%2520%2520%2520%2520%2520%2520%2520%2522title%2522%253A%2520%2522Moby%2520Dick%2522%252C%250A%2520%2520%2520%2520%2520%2520%2520%2520%2522isbn%2522%253A%2520%25220-553-21311-3%2522%252C%250A%2520%2520%2520%2520%2520%2520%2520%2520%2522price%2522%253A%25208.99%250A%2520%2520%2520%2520%2520%2520%257D%252C%250A%2520%2520%2520%2520%2520%2520%257B%250A%2520%2520%2520%2520%2520%2520%2520%2520%2522category%2522%253A%2520%2522fiction%2522%252C%250A%2520%2520%2520%2520%2520%2520%2520%2520%2522author%2522%253A%2520%2522J.%2520R.%2520R.%2520Tolkien%2522%252C%250A%2520%2520%2520%2520%2520%2520%2520%2520%2522title%2522%253A%2520%2522The%2520Lord%2520of%2520the%2520Rings%2522%252C%250A%2520%2520%2520%2520%2520%2520%2520%2520%2522isbn%2522%253A%2520%25220-395-19395-8%2522%252C%250A%2520%2520%2520%2520%2520%2520%2520%2520%2522price%2522%253A%252022.99%250A%2520%2520%2520%2520%2520%2520%257D%250A%2520%2520%2520%2520%255D%252C%250A%2520%2520%2520%2520%2522bicycle%2522%253A%2520%257B%250A%2520%2520%2520%2520%2520%2520%2522color%2522%253A%2520%2522red%2522%252C%250A%2520%2520%2520%2520%2520%2520%2522price%2522%253A%2520399%250A%2520%2520%2520%2520%257D%250A%2520%2520%257D%250A%257D&amp;amp;o=0">playground</a>):</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">[</span>
</span></span><span class="line"><span class="cl">  <span class="s2">&#34;Nigel Rees&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="s2">&#34;Evelyn Waugh&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="s2">&#34;Herman Melville&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="s2">&#34;J. R. R. Tolkien&#34;</span>
</span></span><span class="line"><span class="cl"><span class="p">]</span>
</span></span></code></pre></div><p>JSONTree compiles these two JSONPaths into a single query that merges the
<code>author</code> and <code>price</code> selectors into a single segment, which stringifies to a
<a href="https://en.wikipedia.org/wiki/Tree_(command)" title="Wikipedia: Tree (command)">tree</a>-style format (<a href="https://theory.github.io/jsontree/?p=%2524..author%250A%2524..price&amp;amp;j=&amp;amp;o=2">playground</a>):</p>
<pre tabindex="0"><code class="language-tree" data-lang="tree">$
└── ..[&#34;author&#34;,&#34;price&#34;]
</code></pre><p>This JSONTree returns the appropriate subset of the original JSON object
(<a href="https://theory.github.io/jsontree/?p=%2524..author%250A%2524..price&amp;amp;j=%257B%250A%2520%2520%2522store%2522%253A%2520%257B%250A%2520%2520%2520%2520%2522book%2522%253A%2520%255B%250A%2520%2520%2520%2520%2520%2520%257B%250A%2520%2520%2520%2520%2520%2520%2520%2520%2522category%2522%253A%2520%2522reference%2522%252C%250A%2520%2520%2520%2520%2520%2520%2520%2520%2522author%2522%253A%2520%2522Nigel%2520Rees%2522%252C%250A%2520%2520%2520%2520%2520%2520%2520%2520%2522title%2522%253A%2520%2522Sayings%2520of%2520the%2520Century%2522%252C%250A%2520%2520%2520%2520%2520%2520%2520%2520%2522price%2522%253A%25208.95%250A%2520%2520%2520%2520%2520%2520%257D%252C%250A%2520%2520%2520%2520%2520%2520%257B%250A%2520%2520%2520%2520%2520%2520%2520%2520%2522category%2522%253A%2520%2522fiction%2522%252C%250A%2520%2520%2520%2520%2520%2520%2520%2520%2522author%2522%253A%2520%2522Evelyn%2520Waugh%2522%252C%250A%2520%2520%2520%2520%2520%2520%2520%2520%2522title%2522%253A%2520%2522Sword%2520of%2520Honour%2522%252C%250A%2520%2520%2520%2520%2520%2520%2520%2520%2522price%2522%253A%252012.99%250A%2520%2520%2520%2520%2520%2520%257D%252C%250A%2520%2520%2520%2520%2520%2520%257B%250A%2520%2520%2520%2520%2520%2520%2520%2520%2522category%2522%253A%2520%2522fiction%2522%252C%250A%2520%2520%2520%2520%2520%2520%2520%2520%2522author%2522%253A%2520%2522Herman%2520Melville%2522%252C%250A%2520%2520%2520%2520%2520%2520%2520%2520%2522title%2522%253A%2520%2522Moby%2520Dick%2522%252C%250A%2520%2520%2520%2520%2520%2520%2520%2520%2522isbn%2522%253A%2520%25220-553-21311-3%2522%252C%250A%2520%2520%2520%2520%2520%2520%2520%2520%2522price%2522%253A%25208.99%250A%2520%2520%2520%2520%2520%2520%257D%252C%250A%2520%2520%2520%2520%2520%2520%257B%250A%2520%2520%2520%2520%2520%2520%2520%2520%2522category%2522%253A%2520%2522fiction%2522%252C%250A%2520%2520%2520%2520%2520%2520%2520%2520%2522author%2522%253A%2520%2522J.%2520R.%2520R.%2520Tolkien%2522%252C%250A%2520%2520%2520%2520%2520%2520%2520%2520%2522title%2522%253A%2520%2522The%2520Lord%2520of%2520the%2520Rings%2522%252C%250A%2520%2520%2520%2520%2520%2520%2520%2520%2522isbn%2522%253A%2520%25220-395-19395-8%2522%252C%250A%2520%2520%2520%2520%2520%2520%2520%2520%2522price%2522%253A%252022.99%250A%2520%2520%2520%2520%2520%2520%257D%250A%2520%2520%2520%2520%255D%252C%250A%2520%2520%2520%2520%2522bicycle%2522%253A%2520%257B%250A%2520%2520%2520%2520%2520%2520%2522color%2522%253A%2520%2522red%2522%252C%250A%2520%2520%2520%2520%2520%2520%2522price%2522%253A%2520399%250A%2520%2520%2520%2520%257D%250A%2520%2520%257D%250A%257D&amp;amp;o=0">playground</a>):</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;store&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;book&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">      <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;author&#34;</span><span class="p">:</span> <span class="s2">&#34;Nigel Rees&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;price&#34;</span><span class="p">:</span> <span class="mf">8.95</span>
</span></span><span class="line"><span class="cl">      <span class="p">},</span>
</span></span><span class="line"><span class="cl">      <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;author&#34;</span><span class="p">:</span> <span class="s2">&#34;Evelyn Waugh&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;price&#34;</span><span class="p">:</span> <span class="mf">12.99</span>
</span></span><span class="line"><span class="cl">      <span class="p">},</span>
</span></span><span class="line"><span class="cl">      <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;author&#34;</span><span class="p">:</span> <span class="s2">&#34;Herman Melville&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;price&#34;</span><span class="p">:</span> <span class="mf">8.99</span>
</span></span><span class="line"><span class="cl">      <span class="p">},</span>
</span></span><span class="line"><span class="cl">      <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;author&#34;</span><span class="p">:</span> <span class="s2">&#34;J. R. R. Tolkien&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;price&#34;</span><span class="p">:</span> <span class="mf">22.99</span>
</span></span><span class="line"><span class="cl">      <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">],</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;bicycle&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;price&#34;</span><span class="p">:</span> <span class="mi">399</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>Note that the original data structure remains, but only for the subset of the
structure selected by the JSONPath queries.</p>
<h2 id="use-cases">Use Cases</h2>
<p>A couple of use cases drove the conception and design of JSONPath.</p>
<h3 id="permissions">Permissions</h3>
<p>Consider an application in which <a href="https://en.wikipedia.org/wiki/Access-control_list" title="Wikipedia: Access-control list">ACL</a>s define permissions for groups of users
to access specific branches or fields of JSON documents. When delivering a
document, the app would:</p>
<ul>
<li>Fetch the groups the user belongs to</li>
<li>Convert the permissions from each into JSONPath queries</li>
<li>Compile the JSONPath queries into an JSONTree query</li>
<li>Select and return the permitted subset of the document to the user</li>
</ul>
<h3 id="selective-indexing">Selective Indexing</h3>
<p>Consider a searchable document storage system. For large or complex documents,
it may be infeasible or unnecessary to index the entire document for full-text
search. To index a subset of the fields or branches, one would:</p>
<ul>
<li>Define JSONPaths the fields or branches to index</li>
<li>Compile the JSONPath queries into a JSONTree query</li>
<li>Select and submit only the specified subset of each document to the
indexing system</li>
</ul>
<h2 id="go-example">Go Example</h2>
<p>Use the <a href="https://pkg.go.dev/github.com/theory/jsontree">github.com/theory/jsontree</a> Go package together with
<a href="https://pkg.go.dev/github.com/theory/jsonpath">github.com/theory/jsonpath</a> to compile and execute JSONTree queries:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kn">package</span><span class="w"> </span><span class="nx">main</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"> </span><span class="p">(</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="s">&#34;fmt&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="s">&#34;github.com/theory/jsonpath&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="s">&#34;github.com/theory/jsontree&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="nf">main</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="c1">// JSON as unmarshaled by encoding/json.</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">value</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="kd">map</span><span class="p">[</span><span class="kt">string</span><span class="p">]</span><span class="kt">any</span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="s">&#34;name&#34;</span><span class="p">:</span><span class="w">  </span><span class="s">&#34;Barrack Obama&#34;</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="s">&#34;years&#34;</span><span class="p">:</span><span class="w"> </span><span class="s">&#34;2009-2017&#34;</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="s">&#34;emails&#34;</span><span class="p">:</span><span class="w"> </span><span class="p">[]</span><span class="kt">any</span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">			</span><span class="s">&#34;potus@example.com&#34;</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">			</span><span class="s">&#34;barrack@example.net&#34;</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="p">},</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="c1">// Compile multiple JSONPaths into a JSONTree.</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">tree</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">jsontree</span><span class="p">.</span><span class="nf">New</span><span class="p">(</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">jsonpath</span><span class="p">.</span><span class="nf">MustParse</span><span class="p">(</span><span class="s">&#34;$.name&#34;</span><span class="p">),</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">jsonpath</span><span class="p">.</span><span class="nf">MustParse</span><span class="p">(</span><span class="s">&#34;$.emails[1]&#34;</span><span class="p">),</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="c1">// Select from the input value.</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">js</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">json</span><span class="p">.</span><span class="nf">Marshal</span><span class="p">(</span><span class="nx">tree</span><span class="p">.</span><span class="nf">Select</span><span class="p">(</span><span class="nx">value</span><span class="p">))</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">if</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">log</span><span class="p">.</span><span class="nf">Fatal</span><span class="p">(</span><span class="nx">err</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Printf</span><span class="p">(</span><span class="s">&#34;%#v\n&#34;</span><span class="p">,</span><span class="w"> </span><span class="nx">tree</span><span class="p">.</span><span class="nf">Select</span><span class="p">(</span><span class="nx">value</span><span class="p">))</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></div><p>And the output:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span><span class="nt">&#34;emails&#34;</span><span class="p">:[</span><span class="s2">&#34;barrack@example.net&#34;</span><span class="p">],</span><span class="nt">&#34;name&#34;</span><span class="p">:</span><span class="s2">&#34;Barrack Obama&#34;</span><span class="p">}</span>
</span></span></code></pre></div><p>Note that the index position of the selected email was not preserved. Replace
<code>New</code> with <a href="https://pkg.go.dev/github.com/theory/jsontree#NewFixedModeTree" title="github.com/theory/jsontree: NewFixedModeTree"><code>NewFixedModeTree</code></a> to create a &ldquo;fixed mode&rdquo; JSONTree that
preserves index positions by filling gaps with <code>null</code>s. Its output of the
above example would be:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span><span class="nt">&#34;emails&#34;</span><span class="p">:[</span><span class="kc">null</span><span class="p">,</span><span class="s2">&#34;barrack@example.net&#34;</span><span class="p">],</span><span class="nt">&#34;name&#34;</span><span class="p">:</span><span class="s2">&#34;Barrack Obama&#34;</span><span class="p">}</span>
</span></span></code></pre></div><h2 id="status">Status</h2>
<p>The public interface of the <code>jsontree</code> module is quite minimal and stable. But
I suspect there may remain some flaws in the merging of JSONPath selectors.
Please report bugs via <a href="https://github.com/theory/jsontree/issues/" title="theory/jsontree: Issues">GitHub issues</a> and I&rsquo;ll get them fixed up ASAP.</p>
<p>Otherwise, please share and enjoy!</p>

        </div>

        <footer class="tags">
            <h5>More about…</h5>
            <ul>
                <li><a href="https://justatheory.com/tags/go/">Go</a></li>
                <li><a href="https://justatheory.com/tags/jsontree/">JSONTree</a></li>
                <li><a href="https://justatheory.com/tags/jsonpath/">JSONPath</a></li>
                <li><a href="https://justatheory.com/tags/playground/">Playground</a></li>
                <li><a href="https://justatheory.com/tags/rfc-9535/">RFC 9535</a></li>
            </ul>
        </footer>
    </article>
]]></content>
	</entry>
</feed>
