<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: asmgit</title>
    <description>The latest articles on DEV Community by asmgit (@asmgit).</description>
    <link>https://dev.to/asmgit</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4048392%2Ffc3943e6-3913-45c2-8c2a-bff65729b14b.png</url>
      <title>DEV Community: asmgit</title>
      <link>https://dev.to/asmgit</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/asmgit"/>
    <language>en</language>
    <item>
      <title>JOIN Like an ORM: Foreign-Key Relations in PostgreSQL</title>
      <dc:creator>asmgit</dc:creator>
      <pubDate>Sun, 02 Aug 2026 04:28:32 +0000</pubDate>
      <link>https://dev.to/asmgit/join-like-an-orm-foreign-key-relations-in-postgresql-2cc7</link>
      <guid>https://dev.to/asmgit/join-like-an-orm-foreign-key-relations-in-postgresql-2cc7</guid>
      <description>&lt;p&gt;A PostgreSQL query that navigates relations:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;profile_detail&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;phone&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;delivery_address&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;city&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;quantity&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;document&lt;/span&gt;
&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;document&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;profile_detail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;delivery_address&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;document&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;document_item_list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;document&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;document_item_list&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;doc_number&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'DOC-1'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The planner turns it into exactly the query you would have written by hand (&lt;a href="https://dbfiddle.uk/U95ODCR7?hide=448" rel="noopener noreferrer"&gt;dbfiddle&lt;/a&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;phone&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;city&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;di&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;quantity&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;document&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;
&lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;profile&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;client_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;client_id&lt;/span&gt;
&lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;profile_detail&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;profile_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;
&lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;address&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;profile_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;delivery_address_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;client_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;document_item&lt;/span&gt; &lt;span class="n"&gt;di&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;di&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;document_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;
&lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;di&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;item_id&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;doc_number&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'DOC-1'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(&lt;code&gt;profile&lt;/code&gt; here is a shared people table with a unique &lt;code&gt;client_id&lt;/code&gt; column, and the delivery address is referenced by a composite key; the full demo schema is on the diagram below.)&lt;/p&gt;

&lt;p&gt;This is vanilla Postgres — no C extension, no fork, no new syntax. Below: why the SQL standard never managed this in forty years (joins are older than foreign keys themselves), who tried to push it through — from the draft standards of the nineties to a patch sitting in pgsql-hackers right now — and how to switch this navigation on in Postgres today.&lt;/p&gt;

&lt;h2&gt;
  
  
  The schema knows the relations. SQL makes you repeat them
&lt;/h2&gt;

&lt;p&gt;A &lt;code&gt;FOREIGN KEY&lt;/code&gt; is a machine-checked declaration of a relation: the columns, the target table, the cardinality. All of it sits in the catalog, and the overwhelming majority of real-world joins follow these declarations — one practitioner &lt;a href="https://news.ycombinator.com/item?id=29739147" rel="noopener noreferrer"&gt;in an HN discussion&lt;/a&gt; put it at "95%+ of my joins". Yet every &lt;code&gt;JOIN&lt;/code&gt; starts from a blank slate. Three problems follow:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Verbosity.&lt;/strong&gt; Five relations means five &lt;code&gt;ON&lt;/code&gt; clauses restating the &lt;a href="https://github.com/asmgit/pg_relation_sql/blob/main/example/init.sql" rel="noopener noreferrer"&gt;schema&lt;/a&gt; word for word.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Silent mistakes.&lt;/strong&gt; &lt;code&gt;ON&lt;/code&gt; accepts any equality: &lt;code&gt;ON d.id = di.item_id&lt;/code&gt; instead of &lt;code&gt;document_id&lt;/code&gt; runs without complaint — the types match. The error surfaces in the data, not at compile time.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Lost intent.&lt;/strong&gt; Looking at &lt;code&gt;ON a.x = b.y&lt;/code&gt;, you can't tell whether it's a relation or a coincidence, which side is the parent, or whether rows will multiply in an aggregate.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The pain is not new: the language began as &lt;a href="https://en.wikipedia.org/wiki/SQL#History" rel="noopener noreferrer"&gt;SEQUEL in 1974&lt;/a&gt; — joins were there from day one, as a comma in &lt;code&gt;FROM&lt;/code&gt;. The first standard, &lt;a href="https://en.wikipedia.org/wiki/SQL#Standardization" rel="noopener noreferrer"&gt;SQL-86&lt;/a&gt;, shipped without referential integrity; foreign keys only arrived with the SQL-89 revision: &lt;strong&gt;join syntax is older than the relations themselves&lt;/strong&gt;. Explicit &lt;a href="https://en.wikipedia.org/wiki/SQL-92" rel="noopener noreferrer"&gt;&lt;code&gt;JOIN ... ON&lt;/code&gt; appeared in SQL-92&lt;/a&gt; — the committee designed a new join syntax with FKs already in plain sight, and still didn't connect the two. The standard didn't reach relation navigation in queries until &lt;a href="https://en.wikipedia.org/wiki/SQL:2023" rel="noopener noreferrer"&gt;SQL:2023&lt;/a&gt; — and even then via graph patterns, not joins.&lt;/p&gt;

&lt;p&gt;While the committee deliberated, the problem got solved elsewhere: relation navigation is the first thing every ORM offers — &lt;code&gt;@relation&lt;/code&gt; in &lt;a href="https://www.prisma.io/docs/orm/prisma-schema/data-model/relations" rel="noopener noreferrer"&gt;Prisma&lt;/a&gt;, navigation properties in &lt;a href="https://learn.microsoft.com/en-us/ef/core/querying/related-data/" rel="noopener noreferrer"&gt;Entity Framework&lt;/a&gt;, &lt;code&gt;select_related&lt;/code&gt; in &lt;a href="https://docs.djangoproject.com/en/stable/ref/models/querysets/#select-related" rel="noopener noreferrer"&gt;Django&lt;/a&gt;. Application code moved to ORMs largely for exactly this, putting up with N+1 queries and leaky abstractions along the way. The irony: the source of truth about relations was in the database all along. ORMs duplicate it, GraphQL layers introspect it — and only SQL itself pretends to know nothing.&lt;/p&gt;

&lt;h2&gt;
  
  
  The SQL sugar that didn't help
&lt;/h2&gt;

&lt;p&gt;SQL has tried to shorten join conditions more than once. All four attempts missed the mark.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Comma + &lt;code&gt;WHERE&lt;/code&gt;.&lt;/strong&gt; The pre-sugar era: tables in a list, join conditions mixed into &lt;code&gt;WHERE&lt;/code&gt; with the filters. Forget one condition — get a Cartesian product, silently.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;JOIN ... ON&lt;/code&gt;.&lt;/strong&gt; Today's mainstream. Explicit, flexible — and carrying exactly the three problems above: restatement, any equality, zero semantics.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://www.postgresql.org/docs/current/queries-table-expressions.html#QUERIES-JOIN" rel="noopener noreferrer"&gt;&lt;code&gt;JOIN ... USING (col)&lt;/code&gt;&lt;/a&gt;.&lt;/strong&gt; Shorter, but it joins on &lt;em&gt;name coincidence&lt;/em&gt;: both tables must call the column the same thing. The &lt;code&gt;supplier_id = supplier_id&lt;/code&gt; convention passes; the most common pattern, &lt;code&gt;document_item.document_id → document.id&lt;/code&gt;, doesn't. FK constraints play no part in the decision at all.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;NATURAL JOIN&lt;/code&gt;.&lt;/strong&gt; The logical extreme: join on &lt;em&gt;all&lt;/em&gt; same-named columns at once. Add an &lt;code&gt;updated_at&lt;/code&gt; column — one the first table already has — to the second table, and the query silently starts joining on it too. &lt;code&gt;NATURAL JOIN&lt;/code&gt; is what discredited the very idea of "automatic" joins — though the flaw was never the automation: the flaw was building on column names (an accident) instead of declared relations (an intent).&lt;/p&gt;

&lt;p&gt;Forty years of the standard, and it still won't let you say "join along the declared relation". Either restate the schema by hand, or guess by names.&lt;/p&gt;

&lt;h2&gt;
  
  
  Who tried to improve SQL syntax
&lt;/h2&gt;

&lt;p&gt;The idea of joining along a declared relation has been in the air for decades — and it has a well-populated graveyard.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;SQL drafts, the 1990s.&lt;/strong&gt; Draft versions of the standard contained &lt;code&gt;JOIN ... USING PRIMARY KEY | USING FOREIGN KEY | USING CONSTRAINT&lt;/code&gt; syntax. According to &lt;a href="https://www.postgresql.org/message-id/flat/404246ea-9e3c-48ea-9947-80c50823a805@www.fastmail.com" rel="noopener noreferrer"&gt;Peter Eisentraut&lt;/a&gt;, "these ideas just faded away because of other priorities".&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Sybase SQL Anywhere: &lt;code&gt;KEY JOIN&lt;/code&gt;.&lt;/strong&gt; The only DBMS that actually shipped it — and has kept it for decades (lineage: Watcom SQL → Sybase → SAP since 2010). &lt;code&gt;FROM Products KEY JOIN SalesOrderItems&lt;/code&gt; joins along the declared FK; more than that, &lt;a href="https://infocenter.sybase.com/help/topic/com.sybase.help.sqlanywhere.12.0.1/dbusage/key-joins-aspen.html" rel="noopener noreferrer"&gt;&lt;code&gt;KEY JOIN&lt;/code&gt; is the &lt;strong&gt;default&lt;/strong&gt; there&lt;/a&gt; — a bare &lt;code&gt;JOIN&lt;/code&gt; with no &lt;code&gt;ON&lt;/code&gt; means exactly this. Ambiguity between multiple FKs is &lt;a href="https://infocenter.sybase.com/help/topic/com.sybase.help.sqlanywhere.12.0.1/dbusage/kj-multiple-fks-joinsasp.html" rel="noopener noreferrer"&gt;resolved by convention&lt;/a&gt;: the constraint's role name must match the alias. It works, but the idea never escaped one niche DBMS.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;PostgreSQL, the 2021 wave.&lt;/strong&gt; Joel Jacobson brought the idea to pgsql-hackers twice: first as &lt;a href="https://www.postgresql.org/message-id/flat/CAFj8pRBPFCu9DhewNBKxm351C0MFOxKvwQpbZen4ViDq6%2BCgsQ%40mail.gmail.com" rel="noopener noreferrer"&gt;FK path expressions&lt;/a&gt;, then as &lt;a href="https://www.postgresql.org/message-id/flat/404246ea-9e3c-48ea-9947-80c50823a805@www.fastmail.com" rel="noopener noreferrer"&gt;"Foreign key joins revisited"&lt;/a&gt; — the syntax mutated for nine days (&lt;code&gt;WITH p-&amp;gt;fk = r&lt;/code&gt; → &lt;code&gt;ON KEY p.fk&lt;/code&gt; → &lt;code&gt;USING KEY&lt;/code&gt;) and drowned in bikeshedding. Tom Lane's verdict from that thread became a classic of the genre: "NATURAL JOIN is widely regarded as a foot-gun that the SQL committee should never have invented. Why would we want to create another one?" A parallel &lt;a href="https://gist.github.com/joelonsql/15b50b65ec343dce94db6249cfea8aaa" rel="noopener noreferrer"&gt;&lt;code&gt;JOIN FOREIGN&lt;/code&gt; gist&lt;/a&gt; drew &lt;a href="https://news.ycombinator.com/item?id=29739147" rel="noopener noreferrer"&gt;~200 comments on Hacker News&lt;/a&gt; — and every objection that has kept resurfacing ever since:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;multiple FKs between the same tables — ambiguity;&lt;/li&gt;
&lt;li&gt;a new FK in the schema silently changes (or breaks) existing queries;&lt;/li&gt;
&lt;li&gt;constraint names are a poor interface: ORMs generate unreadable ones;&lt;/li&gt;
&lt;li&gt;a query shouldn't depend on a constraint's existence — FKs get dropped for bulk loads and sharding.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;jOOQ — the only workaround that reached mass production.&lt;/strong&gt; The Java query builder implemented &lt;a href="https://www.jooq.org/doc/latest/manual/sql-building/table-expressions/joined-tables/join-predicate-on-key/" rel="noopener noreferrer"&gt;&lt;code&gt;.onKey()&lt;/code&gt;&lt;/a&gt; and &lt;a href="https://blog.jooq.org/type-safe-implicit-join-through-path-navigation-in-jooq-3-11/" rel="noopener noreferrer"&gt;implicit path joins&lt;/a&gt; (&lt;code&gt;BOOK.author().name()&lt;/code&gt; synthesizes the LEFT JOIN along the FK by itself). Tellingly, jOOQ stated the strongest objection itself, &lt;a href="https://www.jooq.org/doc/latest/manual/sql-building/table-expressions/joined-tables/join-predicate-on-key/" rel="noopener noreferrer"&gt;in its own manual&lt;/a&gt;: "The ON KEY clause can quickly produce ambiguities ... queries that have worked in the past ... will stop working" — and steered its users toward path joins, where the specific FK is pinned down by code generation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;PostgreSQL, the 2026 wave — happening right now.&lt;/strong&gt; In May Jacobson came back with a team of co-authors, &lt;a href="https://www.postgresql.org/message-id/flat/2570884f-a018-47c5-a545-dd551d10e5c8@app.fastmail.com" rel="noopener noreferrer"&gt;a working patch and a change proposal to the ISO committee&lt;/a&gt;: &lt;code&gt;LEFT JOIN order_items oi FOR KEY (order_id) -&amp;gt; o (id)&lt;/code&gt; (interactive examples at &lt;a href="https://keyjoin.org/" rel="noopener noreferrer"&gt;keyjoin.org&lt;/a&gt;). The emphasis has shifted from saving keystrokes to correctness: a key join is a &lt;em&gt;declaration&lt;/em&gt; that the join follows a referential path, and the database must prove it (the headline case being silent fan-out: a 1:N join quietly multiplies rows, and &lt;code&gt;SUM()&lt;/code&gt; dutifully adds up the duplicates). &lt;a href="https://www.mail-archive.com/pgsql-hackers@lists.postgresql.org/msg230580.html" rel="noopener noreferrer"&gt;Tomas Vondra's review&lt;/a&gt; was respectful but unsparing: a ~5200-line patch with, by his estimate, near-zero odds of being deemed committable, and a 30–40% planning regression. The thread is alive, the outcome unclear, the standardization timeline measured in years.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Oracle 26 — shipped, 2026.&lt;/strong&gt; While the Postgres patch goes through review, Oracle simply shipped its own: &lt;a href="https://docs.oracle.com/en/database/oracle/oracle-database/26/adfns/building-queries-correct-joins-more-easily.html" rel="noopener noreferrer"&gt;&lt;code&gt;JOIN TO ONE&lt;/code&gt;&lt;/a&gt; joins along the declared keys, with no &lt;code&gt;ON&lt;/code&gt; at all.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;order_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;product_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cust_email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;oi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;quantity&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;oe&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;order_items&lt;/span&gt; &lt;span class="n"&gt;oi&lt;/span&gt;
&lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="k"&gt;TO&lt;/span&gt; &lt;span class="n"&gt;ONE&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;oe&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;orders&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;oe&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;product_information&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;oe&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;customers&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No constraint names are mentioned: Oracle resolves the path through the declared keys itself and requires it to be exactly one — ambiguity is not settled by convention, it is declared an error. And &lt;code&gt;TO ONE&lt;/code&gt; in the name is itself a cardinality declaration: the join may widen the row, never multiply it. The same turn towards correctness as &lt;code&gt;FOR KEY&lt;/code&gt;, except already in production and announced as a candidate for standardization.&lt;/p&gt;

&lt;p&gt;Nothing has made it through the committee in thirty years: what reached production, each vendor shipped on its own terms — &lt;code&gt;KEY JOIN&lt;/code&gt; in one, &lt;code&gt;JOIN TO ONE&lt;/code&gt; in another, mutually incompatible. New syntax means grammar, parser, standard, backward compatibility and committee consensus; a vendor needs only the first two.&lt;/p&gt;

&lt;h2&gt;
  
  
  2026: the idea won everywhere — except SQL
&lt;/h2&gt;

&lt;p&gt;While FK joins stall in committees, the idea itself — declare the relation once, then walk it — has won everywhere around SQL:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Graph standards.&lt;/strong&gt; &lt;a href="https://en.wikipedia.org/wiki/SQL:2023" rel="noopener noreferrer"&gt;SQL:2023 SQL/PGQ&lt;/a&gt;: declare a property graph over your tables, query it with patterns like &lt;code&gt;MATCH (c IS customer)-[IS has_placed]-&amp;gt;(o)&lt;/code&gt; — already in &lt;a href="https://blogs.oracle.com/database/property-graphs-in-oracle-database-23ai-the-sql-pgq-standard" rel="noopener noreferrer"&gt;Oracle 23ai&lt;/a&gt;, &lt;a href="https://commitfest.postgresql.org/patch/4904/" rel="noopener noreferrer"&gt;committed to PostgreSQL 19&lt;/a&gt; (March 2026, GA expected in the fall), in DuckDB via &lt;a href="https://duckdb.org/community_extensions/extensions/duckpgq" rel="noopener noreferrer"&gt;the DuckPGQ extension&lt;/a&gt;; alongside it, a separate ISO language, &lt;a href="https://www.iso.org/standard/76120.html" rel="noopener noreferrer"&gt;GQL&lt;/a&gt; (2024) — Neo4j, &lt;a href="https://docs.cloud.google.com/spanner/docs/graph/iso-standards" rel="noopener noreferrer"&gt;Spanner Graph&lt;/a&gt;. Note: even the standard didn't dare touch &lt;code&gt;JOIN&lt;/code&gt; itself — relations were moved out into a separate graph layer with its own syntax.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Semantic models.&lt;/strong&gt; &lt;a href="https://docs.snowflake.com/en/sql-reference/sql/create-semantic-view" rel="noopener noreferrer"&gt;Snowflake Semantic Views&lt;/a&gt; (GA 2025): &lt;code&gt;RELATIONSHIPS (orders (customer_id) REFERENCES customers (customer_id))&lt;/code&gt; is declared in a schema object, the query names dimensions and metrics — the engine generates the joins. &lt;a href="https://docs.malloydata.dev/documentation/language/join" rel="noopener noreferrer"&gt;Malloy&lt;/a&gt; took the same thought all the way to a language: &lt;code&gt;join_one: users with user_id&lt;/code&gt; in the model, then just the path &lt;code&gt;users.name&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Layers on top of the database.&lt;/strong&gt; Here FK navigation has long been the norm. &lt;a href="https://docs.postgrest.org/en/v13/references/api/resource_embedding.html" rel="noopener noreferrer"&gt;PostgREST&lt;/a&gt; assembles nested responses straight from FKs (&lt;code&gt;?select=title,directors(last_name)&lt;/code&gt;). &lt;a href="https://postgraphile.org/postgraphile/4/relations/" rel="noopener noreferrer"&gt;PostGraphile&lt;/a&gt; builds an entire GraphQL schema out of FKs — a pair of fields per relation (&lt;code&gt;personByAuthorId&lt;/code&gt; / &lt;code&gt;postsByAuthorId&lt;/code&gt;), 1:1 via UNIQUE — and compiles the query into a single SQL statement with &lt;code&gt;json_agg&lt;/code&gt;. And Supabase's &lt;a href="https://supabase.github.io/pg_graphql/" rel="noopener noreferrer"&gt;pg_graphql&lt;/a&gt; executes GraphQL with a single SQL function, &lt;code&gt;graphql.resolve(...)&lt;/code&gt;, right inside the database.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Many independent systems converged on the same model: an FK becomes a pair of navigations, one per direction, with 1:1 derived from uniqueness. That's convergent evolution: foreign key metadata is &lt;em&gt;sufficient&lt;/em&gt; for a complete navigation model; the only question is which language to expose it in. So far the industry's answer has been "any language except SQL itself". pg_graphql takes the irony all the way: FK navigation already lives inside your Postgres — it just speaks GraphQL.&lt;/p&gt;

&lt;p&gt;And one telling data point: &lt;a href="https://vldb.org/pvldb/vol17/p4051-shute.pdf" rel="noopener noreferrer"&gt;pipe syntax&lt;/a&gt; — the loudest SQL reform of recent years (BigQuery, Spark 4.0) — rearranged everything except joins: &lt;a href="https://github.com/google/googlesql/blob/master/docs/pipe-syntax.md" rel="noopener noreferrer"&gt;&lt;code&gt;|&amp;gt; JOIN&lt;/code&gt; requires the same old &lt;code&gt;ON&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The solution: a relation is a function
&lt;/h2&gt;

&lt;p&gt;Now for the trick: relation navigation in Postgres needs neither new syntax nor a patch — nor even a recent version. Functions taking a table row and attribute notation (calling a function with a dot: &lt;code&gt;document.client&lt;/code&gt; instead of &lt;code&gt;client(document)&lt;/code&gt;) go back to Postgres' object-relational roots, and the planner has &lt;a href="https://wiki.postgresql.org/wiki/Inlining_of_SQL_functions" rel="noopener noreferrer"&gt;inlined set-returning SQL functions&lt;/a&gt; &lt;a href="https://www.postgresql.org/docs/release/8.4.0/" rel="noopener noreferrer"&gt;since 8.4 — that's 2009&lt;/a&gt; — the technique should work even on versions long out of support. All it takes is turning each FK into a pair of functions: &lt;strong&gt;lookup&lt;/strong&gt; — follow the reference, and &lt;strong&gt;list&lt;/strong&gt; — collect the referencing rows. Here's the demo schema, every edge labeled with its pair:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhacbtmuxiia845gfg5dz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhacbtmuxiia845gfg5dz.png" alt="ER diagram: every relation is a pair of functions, lookup and list" width="800" height="502"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The same schema in SQL lives in &lt;a href="https://github.com/asmgit/pg_relation_sql/blob/main/example/init.sql" rel="noopener noreferrer"&gt;&lt;code&gt;example/init.sql&lt;/code&gt;&lt;/a&gt; in the repository, and, complete with pre-generated relation functions, in a &lt;a href="https://dbfiddle.uk/U95ODCR7" rel="noopener noreferrer"&gt;db&amp;lt;&amp;gt;fiddle sandbox&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;For a single relation it looks like this. The lookup — a document's client:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;FUNCTION&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;document&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;RETURNS&lt;/span&gt; &lt;span class="k"&gt;SETOF&lt;/span&gt; &lt;span class="n"&gt;profile&lt;/span&gt; &lt;span class="k"&gt;LANGUAGE&lt;/span&gt; &lt;span class="k"&gt;sql&lt;/span&gt; &lt;span class="k"&gt;STABLE&lt;/span&gt; &lt;span class="n"&gt;PARALLEL&lt;/span&gt; &lt;span class="n"&gt;SAFE&lt;/span&gt;
&lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="err"&gt;$$&lt;/span&gt; &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="k"&gt;public&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;profile&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;client_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;client_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="err"&gt;$$&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The list — all of a client's documents:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;FUNCTION&lt;/span&gt; &lt;span class="n"&gt;client_document_list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;profile&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;RETURNS&lt;/span&gt; &lt;span class="k"&gt;SETOF&lt;/span&gt; &lt;span class="n"&gt;document&lt;/span&gt; &lt;span class="k"&gt;LANGUAGE&lt;/span&gt; &lt;span class="k"&gt;sql&lt;/span&gt; &lt;span class="k"&gt;STABLE&lt;/span&gt; &lt;span class="n"&gt;PARALLEL&lt;/span&gt; &lt;span class="n"&gt;SAFE&lt;/span&gt;
&lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="err"&gt;$$&lt;/span&gt; &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="k"&gt;public&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;document&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;client_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;client_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="err"&gt;$$&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Why functions rather than views? A VIEW freezes one particular join, not a relation: a view doesn't take a row, doesn't compose into chains like &lt;code&gt;client(document(di))&lt;/code&gt;, doesn't overload on argument type — and inside it still lives that same hand-written &lt;code&gt;ON&lt;/code&gt;. The function is the relation, its argument is the model: an edge of the graph you can step along from anywhere in a query.&lt;/p&gt;

&lt;p&gt;In &lt;code&gt;FROM&lt;/code&gt; such a function behaves like an ordinary table. No &lt;code&gt;LATERAL&lt;/code&gt; needed: functions see their &lt;code&gt;FROM&lt;/code&gt; neighbors anyway — &lt;a href="https://www.postgresql.org/docs/current/queries-table-expressions.html#QUERIES-LATERAL" rel="noopener noreferrer"&gt;the documentation itself calls it a noise word&lt;/a&gt;. A required relation (&lt;code&gt;NOT NULL&lt;/code&gt; FK column) joins with a comma. An optional one (nullable) takes &lt;code&gt;LEFT JOIN ... ON true&lt;/code&gt;: the join condition is already baked into the function, and &lt;code&gt;ON true&lt;/code&gt; is there only because there is no &lt;code&gt;LEFT JOIN&lt;/code&gt; without &lt;code&gt;ON&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;doc_number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="n"&gt;manager_name&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;document&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;
&lt;span class="k"&gt;LEFT&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;manager&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Named for what it means
&lt;/h3&gt;

&lt;p&gt;Simplicity won here: a function is named for what the relation means — and the meaning is already written into the FK column by its author. &lt;code&gt;client_id&lt;/code&gt; yields &lt;code&gt;client(document)&lt;/code&gt;; the list function is named after its own table — &lt;code&gt;document_item_list(document)&lt;/code&gt;. Several relations to the same target split by role: &lt;code&gt;client&lt;/code&gt; / &lt;code&gt;manager&lt;/code&gt;. No naming theory: you don't recall the names, you guess them. The full rule set lives in &lt;a href="https://github.com/asmgit/pg_relation_sql#naming-rules" rel="noopener noreferrer"&gt;Naming rules&lt;/a&gt; — defaults, kept in one place.&lt;/p&gt;

&lt;h3&gt;
  
  
  It's free
&lt;/h3&gt;

&lt;p&gt;The key question for any "magic" is the price. The answer: indistinguishable from a hand-written join. When a relation function sits in &lt;code&gt;FROM&lt;/code&gt;, the planner inlines it completely: &lt;code&gt;EXPLAIN&lt;/code&gt; matches the classic query node for node — same Nested Loop / Hash Join, same indexes, same parallel workers. On a test database of 300k documents and 1.35M line items, a heavy aggregate over a four-table chain: medians of five runs — 576 ms classic vs 571 ms with functions; the "winner" flips between individual runs, and with identical plans all that's left is inter-run noise. Plan walkthroughs for the "classic vs relations" pairs live in &lt;a href="https://github.com/asmgit/pg_relation_sql/blob/main/EXPLAIN.md" rel="noopener noreferrer"&gt;EXPLAIN.md&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;And this is no return to the navigational databases Codd steered the industry away from in 1970: under the sugar it's the same relational algebra — the plan is identical to the hand-written join, and navigation mixes freely with classic joins in the same query.&lt;/p&gt;

&lt;p&gt;The generator guarantees &lt;a href="https://wiki.postgresql.org/wiki/Inlining_of_SQL_functions" rel="noopener noreferrer"&gt;the inlining conditions&lt;/a&gt; itself: &lt;code&gt;LANGUAGE sql&lt;/code&gt;, &lt;code&gt;STABLE&lt;/code&gt;, not &lt;code&gt;STRICT&lt;/code&gt;, not &lt;code&gt;SECURITY DEFINER&lt;/code&gt;, and — the non-obvious one, learned by measurement — &lt;code&gt;PARALLEL SAFE&lt;/code&gt;: query parallelism is decided &lt;em&gt;before&lt;/em&gt; inlining, and the default &lt;code&gt;PARALLEL UNSAFE&lt;/code&gt; silently disables workers for the whole query (that cost us ~1280 ms vs 571 ms). Drift is caught out of the box: the generator (more on it below) checks every function's body and its flags against the reference definition, and a function recreated by hand or missing &lt;code&gt;PARALLEL SAFE&lt;/code&gt; shows up in its report.&lt;/p&gt;

&lt;h3&gt;
  
  
  The generator: one function for everything
&lt;/h3&gt;

&lt;p&gt;Writing these functions by hand would be restating the schema all over again, just from a different angle. So the database writes them itself. The whole thing is called &lt;a href="https://github.com/asmgit/pg_relation_sql" rel="noopener noreferrer"&gt;&lt;strong&gt;pg_relation_sql&lt;/strong&gt;&lt;/a&gt; and is a single self-installing SQL file, &lt;a href="https://github.com/asmgit/pg_relation_sql/blob/main/relation_sql.sql" rel="noopener noreferrer"&gt;&lt;code&gt;relation_sql.sql&lt;/code&gt;&lt;/a&gt; — no extensions, no access to the server filesystem, PostgreSQL 11+ is enough. Installing is one command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-sf&lt;/span&gt; https://raw.githubusercontent.com/asmgit/pg_relation_sql/main/relation_sql.sql | psql postgresql://postgres:postgres@localhost:5432/postgres
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Loading the file creates the functions, adds the event trigger and prints the dashboard in response.&lt;/p&gt;

&lt;p&gt;Inside the file is a single function, &lt;code&gt;relation_sql(mode)&lt;/code&gt;: it reads &lt;code&gt;pg_constraint&lt;/code&gt; and brings the set of relation functions in line with the schema.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;command&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;relation_sql&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; pg_relation_sql 0.1.0 — relation functions generated from foreign keys | SELECT status, command FROM relation_sql()
 event trigger: installed                                               | SELECT status, command FROM relation_sql('uninstall')
 relation functions: 16 ok, 0 to sync, 0 foreign, 0 duplicate           | SELECT status, command FROM relation_sql('drop')
 details                                                                | SELECT * FROM relation_sql('show')
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;'show'&lt;/code&gt; is a per-FK diff with ready-to-run SQL; &lt;code&gt;'sync'&lt;/code&gt; applies it once (a migration step); &lt;code&gt;'install'&lt;/code&gt; sets up an event trigger, after which the functions follow the DDL on their own: a &lt;code&gt;CREATE TABLE&lt;/code&gt; with an FK spawns its pair of functions right inside the command, a &lt;code&gt;DROP CONSTRAINT&lt;/code&gt; removes them. It's built to be careful: a failed sync never breaks your DDL, a foreign function with the same name is never touched, and an unresolvable name collision goes to a human instead of a silent overwrite. Modes and statuses in detail: &lt;a href="https://github.com/asmgit/pg_relation_sql#the-generator" rel="noopener noreferrer"&gt;README&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;A team with CI migrations is better served by the other path — not the trigger, but &lt;code&gt;relation_sql('sync')&lt;/code&gt; as a migration step: functions are born in the same pipeline as the rest of the DDL, go through review, and reproduce deterministically in every environment. The trigger is the mode for sandboxes, solo projects and databases where one person does all the DDL anyway. &lt;code&gt;pg_dump&lt;/code&gt;/restore round-trips cleanly in both modes: functions travel as ordinary objects, and the event trigger is restored last, so it doesn't fire mid-restore (verified).&lt;/p&gt;

&lt;h3&gt;
  
  
  What it buys you in queries
&lt;/h3&gt;

&lt;p&gt;Beyond the vanished &lt;code&gt;ON&lt;/code&gt; clauses — idioms the classic style doesn't have; five favorites (the anti-join's cost on full-table scans is covered in the limitations):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- revenue per client: three hops, zero ON&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;di&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;quantity&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;price&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;revenue&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;profile&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;client_document_list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;document_item_list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;di&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;di&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;
&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;-- deliveries to Berlin: the composite FK stays hidden&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;doc_number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;street&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;document&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;delivery_address&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;city&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'Berlin'&lt;/span&gt;
&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;-- the whole related row as a field&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;doc_number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;document&lt;/span&gt;
&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;-- profiles without documents&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;profile&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;EXISTS&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;client_document_list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;profile&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;-- recursive tree walk: the row travels as a value, the relation makes the step&lt;/span&gt;
&lt;span class="k"&gt;WITH&lt;/span&gt; &lt;span class="k"&gt;RECURSIVE&lt;/span&gt; &lt;span class="n"&gt;tree&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="n"&gt;node&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;parent_item_id&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;
  &lt;span class="k"&gt;UNION&lt;/span&gt; &lt;span class="k"&gt;ALL&lt;/span&gt;
  &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt; &lt;span class="n"&gt;node&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;tree&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;item_list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;tree&lt;/span&gt;
&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Recursive tree walks, chains, nested JSON, top-N, DML, &lt;code&gt;INTERSECT&lt;/code&gt;/&lt;code&gt;EXCEPT&lt;/code&gt; — all 19 "classic vs relations" pairs are in &lt;a href="https://github.com/asmgit/pg_relation_sql/blob/main/example/query.sql" rel="noopener noreferrer"&gt;example/query.sql&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Honest limitations
&lt;/h2&gt;

&lt;p&gt;A tool can only be trusted if it knows its boundaries. Ours:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;NOT EXISTS&lt;/code&gt; over a function never becomes an anti-join.&lt;/strong&gt; The planner turns &lt;code&gt;EXISTS&lt;/code&gt; sublinks into semi/anti-joins &lt;em&gt;before&lt;/em&gt; inlining functions, so &lt;code&gt;NOT EXISTS (SELECT FROM client_document_list(profile))&lt;/code&gt; stays a correlated SubPlan forever — an index probe per outer row. With a selective filter there's no difference; write full-table anti-joins the classic way (our measurement: ~96 ms vs ~40 ms on 100k×300k).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Attribute notation in a select list doesn't inline&lt;/strong&gt; — the plan shows a &lt;code&gt;ProjectSet&lt;/code&gt;, one call per row; and the semantics are INNER: a row whose relation is empty disappears. It's sugar for targeted lookups; heavy queries should navigate in &lt;code&gt;FROM&lt;/code&gt;. (And this is not ORM-style N+1: there's a single round-trip, everything happens inside one query — only the plan shape differs.)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;DROP TABLE&lt;/code&gt; requires &lt;code&gt;CASCADE&lt;/code&gt;&lt;/strong&gt; — relation functions depend on the table's row type; leftovers on the other side of the relation are cleaned up by the trigger or the next &lt;code&gt;sync&lt;/code&gt;. Framework-generated migrations that expect a bare &lt;code&gt;DROP TABLE&lt;/code&gt; will stumble here — drop the table's functions explicitly before removing it (&lt;code&gt;'show'&lt;/code&gt; provides the ready-made SQL).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A new FK renames its neighbors.&lt;/strong&gt; A second FK to the same table adds role prefixes: &lt;code&gt;document_list&lt;/code&gt; becomes &lt;code&gt;client_document_list&lt;/code&gt;, and existing queries fail with a compile error — the very KEY JOIN objection from 2021, except here the breakage is loud, and &lt;code&gt;'show'&lt;/code&gt; immediately lists the new names.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No constraint, no navigation.&lt;/strong&gt; As with every FK-based approach: dropping the FK removes the function, and dependent queries fail with a compile error — which is more honest than a silently changed result.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The function body is &lt;code&gt;SELECT *&lt;/code&gt;,&lt;/strong&gt; so column-level privileges (&lt;code&gt;GRANT SELECT (id, name)&lt;/code&gt;) won't coexist with relation functions: after inlining the query needs the whole row. RLS, by contrast, composes cleanly — policies apply to the table after inlining.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You get twice as many functions as FKs,&lt;/strong&gt; and they live in the schemas of their tables. &lt;code&gt;\df&lt;/code&gt; gets noisier, autocomplete longer; IDE tooling and schema-diff tools (migra, atlas) alike can filter by the &lt;code&gt;pg_relation_sql&lt;/code&gt; &lt;code&gt;COMMENT&lt;/code&gt; marker — or better, manage them through the regular &lt;code&gt;sync&lt;/code&gt; migration step, and to your tools they become ordinary managed DDL.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The event trigger requires superuser.&lt;/strong&gt; Without the rights &lt;code&gt;install&lt;/code&gt; doesn't fail: the trigger step is skipped with a &lt;code&gt;WARNING&lt;/code&gt; and the sync still runs — you get the functions, just not the DDL auto-tracking.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It's a dialect.&lt;/strong&gt; A query with &lt;code&gt;client(d)&lt;/code&gt; only runs where the functions exist — same as with views. IDEs autocomplete them as ordinary catalog functions but give no semantic hint that "this is a relation"; the reader needs one new idea — "a function over a row is a relation". That's the price of any sugar; in return the relation is verified by compilation, not by a reviewer's eyeballs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It's Postgres-only.&lt;/strong&gt; But patch-free: plain SQL on top of a vanilla database.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The sugar that fell just short
&lt;/h3&gt;

&lt;p&gt;Three small items to round out the limitations: everything already works, but it could be nicer. Unlike the "big" syntax waves from the history above, these are small candidates for pgsql-hackers:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;LEFT JOIN manager(document)&lt;/code&gt; without &lt;code&gt;ON true&lt;/code&gt;.&lt;/strong&gt; The condition already lives inside the function; &lt;code&gt;ON true&lt;/code&gt; is pure grammatical noise, but it cannot be omitted.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Attribute notation in &lt;code&gt;FROM&lt;/code&gt;.&lt;/strong&gt; It would be even more expressive: &lt;code&gt;FROM document_item, document_item.document, document.client&lt;/code&gt; — today that's a syntax error.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Inline functions before the &lt;code&gt;EXISTS&lt;/code&gt; transformation.&lt;/strong&gt; If the planner swapped the phase order, the first limitation above would disappear along with the ~96/~40 ms gap.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;There's some irony in an article about a "no core changes needed" solution ending in a wishlist for the core. But that's the honest bottom line: ninety percent of the problem is covered by what Postgres already has — and the remaining ten would cost three small patches, not ~5200 lines of key-join patch.&lt;/p&gt;

&lt;h2&gt;
  
  
  SQL is getting friendlier
&lt;/h2&gt;

&lt;p&gt;This story is part of a bigger wave. The standard takes its time — the dialects don't wait: &lt;a href="https://duckdb.org/docs/current/sql/dialect/friendly_sql.html" rel="noopener noreferrer"&gt;DuckDB friendly SQL&lt;/a&gt; means &lt;code&gt;FROM tbl&lt;/code&gt; before &lt;code&gt;SELECT&lt;/code&gt;, &lt;code&gt;GROUP BY ALL&lt;/code&gt;, reusable expression aliases and a dozen more simplifications.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;SELECT * EXCLUDE (...)&lt;/code&gt; deserves a special mention. A confession: our own generator lists twenty-one columns three times over — only because Postgres has no way to take &lt;code&gt;*&lt;/code&gt; and exclude a couple of columns from it. Listing columns is the same disease as listing join conditions: restating what the schema already knows. DuckDB cured it for columns. We cured it for relations. For columns, Postgres has no cure yet.&lt;/p&gt;

&lt;h2&gt;
  
  
  Brevity as physics: an argument for the AI era
&lt;/h2&gt;

&lt;p&gt;The final argument is no longer about humans. SQL today is written and read at scale by language models, and for them brevity isn't aesthetics — it's physics: fewer context tokens, less surface for hallucinations, easier review by a human. Join conditions are the prime source of &lt;em&gt;silent&lt;/em&gt; generation errors: a model writes &lt;code&gt;ON d.id = di.item_id&lt;/code&gt; instead of &lt;code&gt;document_id&lt;/code&gt;, the query runs, the report adds up — almost always. &lt;code&gt;document(di)&lt;/code&gt; eliminates this class of errors structurally: either the relation exists and the condition is correct by construction, or it's a compile error. (Cardinality — required relation or optional — stays with the author, as in the classic style: the function verifies the path, not the &lt;code&gt;NOT NULL&lt;/code&gt;.) And an agent gets the entire navigation vocabulary in one query — &lt;code&gt;SELECT * FROM relation_sql('show')&lt;/code&gt; returns every relation in the schema with names and directions, down to a map like &lt;code&gt;document → client&lt;/code&gt; with a ready-made &lt;code&gt;SELECT * FROM document, client(document)&lt;/code&gt; per relation.&lt;/p&gt;

&lt;p&gt;For decades SQL was compressed for the sake of humans — with dialects, ORMs, tooling around the database. Now the same compression has become infrastructure for machines.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it in a minute
&lt;/h2&gt;

&lt;p&gt;The whole solution is &lt;a href="https://github.com/asmgit/pg_relation_sql" rel="noopener noreferrer"&gt;pg_relation_sql&lt;/a&gt;: a single self-installing SQL file for PostgreSQL 11+ that turns every foreign key into a pair of relation functions and keeps them in sync with the schema.&lt;/p&gt;

&lt;p&gt;No install at all — a &lt;a href="https://dbfiddle.uk/U95ODCR7" rel="noopener noreferrer"&gt;db&amp;lt;&amp;gt;fiddle sandbox&lt;/a&gt;: the demo schema, pre-generated relation functions, six navigation examples and the two plans side by side, right in the browser.&lt;/p&gt;

&lt;p&gt;For the real thing, the repository has a Docker environment: a demo schema covering every relation kind (&lt;a href="https://github.com/asmgit/pg_relation_sql/blob/main/example/init.sql" rel="noopener noreferrer"&gt;&lt;code&gt;example/init.sql&lt;/code&gt;&lt;/a&gt;), those 19 query pairs (&lt;a href="https://github.com/asmgit/pg_relation_sql/blob/main/example/query.sql" rel="noopener noreferrer"&gt;&lt;code&gt;example/query.sql&lt;/code&gt;&lt;/a&gt;) and bulk data of 300k documents and 1.35M line items (&lt;a href="https://github.com/asmgit/pg_relation_sql/blob/main/test/bigdata.sql" rel="noopener noreferrer"&gt;&lt;code&gt;test/bigdata.sql&lt;/code&gt;&lt;/a&gt;) for realistic plans (walkthroughs in &lt;a href="https://github.com/asmgit/pg_relation_sql/blob/main/EXPLAIN.md" rel="noopener noreferrer"&gt;&lt;code&gt;EXPLAIN.md&lt;/code&gt;&lt;/a&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/asmgit/pg_relation_sql.git
&lt;span class="nb"&gt;cd &lt;/span&gt;pg_relation_sql/test &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; docker compose up &lt;span class="nt"&gt;-d&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The schema knows its relations. All that's left is to start asking it.&lt;/p&gt;

&lt;p&gt;Questions, objections, and real-world schemas are all welcome: if the naming stumbles on one of them, I'll tune the rules — they are defaults, and the more schemas they survive, the better they'll get.&lt;/p&gt;

</description>
      <category>postgres</category>
      <category>sql</category>
      <category>database</category>
      <category>opensource</category>
    </item>
    <item>
      <title>What Is the Best Code? — The Code That Does Not Exist</title>
      <dc:creator>asmgit</dc:creator>
      <pubDate>Sun, 26 Jul 2026 21:37:45 +0000</pubDate>
      <link>https://dev.to/asmgit/what-is-the-best-code-the-code-that-does-not-exist-422n</link>
      <guid>https://dev.to/asmgit/what-is-the-best-code-the-code-that-does-not-exist-422n</guid>
      <description>&lt;p&gt;For a few months, every code review I did ended the same way: with a deletion.&lt;/p&gt;

&lt;p&gt;Not because the code was bad. It compiled, it had tests, it had sailed through review once already. But again and again the real fix was to remove the thing entirely — the wrapper function, the defensive regex, the helper that guarded a state my own schema could never produce. And here's what kept surprising me: the system got &lt;em&gt;stronger&lt;/em&gt; every time. Races disappeared because there was nothing left to race. Whole classes of bugs became unrepresentable. Semantics that had been smeared across five functions became visible in one place.&lt;/p&gt;

&lt;p&gt;At some point I stopped and asked the question properly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What is the best code?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The code that does not exist.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It has no bugs. Nobody reads it, so nobody misreads it. It needs no tests, no migration guide, no comment explaining why it's safe. It has never paged anyone at 3am. Absent code has perfect uptime.&lt;/p&gt;

&lt;p&gt;This sounds like a fortune cookie until you take it literally. Then it becomes a design method.&lt;/p&gt;

&lt;h2&gt;
  
  
  It was never really about code
&lt;/h2&gt;

&lt;p&gt;Say it fully and it gets bigger:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;The ultimate optimization of any entity is its absence.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;An &lt;em&gt;entity&lt;/em&gt; is anything you could create and then have to own. A function, sure. But also a service, a table, a dependency, a feature flag, a nightly cron, a policy document, a checklist, a recurring meeting. The &lt;em&gt;need&lt;/em&gt; an entity serves — that's the asset. The entity itself is a liability with a maintenance schedule: a surface for bugs, a thing that drifts from reality, a thing someone new has to learn exists.&lt;/p&gt;

&lt;p&gt;We spend careers optimizing entities. Cleaner code, faster queries, better-run meetings. All of that is polishing the liability. The fundamental move is ruder: ask whether the thing deserves to exist at all, and measure every cleanup not by what it beautified but by what it deleted.&lt;/p&gt;

&lt;p&gt;The code half of this idea is old and in good company. Jeff Atwood wrote "the best code is no code at all" back in 2007; Gordon Bell said the cheapest, fastest, most reliable components are the ones that aren't there. And the principle's shadows are everywhere: YAGNI is it, projected onto the time axis — a need that isn't a fact yet justifies nothing. KISS is it on complexity, DRY on information. The razor named after Occam distilled it seven centuries early: &lt;em&gt;entia non sunt multiplicanda praeter necessitatem&lt;/em&gt; — entities are not to be multiplied beyond necessity.&lt;/p&gt;

&lt;p&gt;Each of these is one shadow of the same lamp, thrown on a different wall. What's new here is bigger and smaller at once. Bigger: it was never about code. Smaller: it compiles into a procedure an agent can actually run.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Existence Ladder
&lt;/h2&gt;

&lt;p&gt;A principle without a procedure is a poster. So here's the procedure — seven rungs, every proposed entity starts at the top. And because the principle claims to work on anything, let's climb it with a problem that has zero code in it:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;"Our people keep losing travel receipts, accounting drowns in reimbursements. Please write a receipts policy, a reimbursement form, and a reminder schedule for HR."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Three mechanisms requested. The actual need: receipts must not get lost, accounting must not drown.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;0 · Nothing.&lt;/strong&gt; Does the entity need to exist at all? Meal receipts don't: per-diem allowances (where tax rules allow them) need no receipts — an entire category of the problem just... leaves.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;1 · Exists.&lt;/strong&gt; Does it already exist — in this project, the stdlib, the world? Corporate travel services already push closing documents straight to accounting. Look before you build.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;2 · Structure.&lt;/strong&gt; Reshape what exists so the bad state becomes unrepresentable. Everything else goes on a corporate card: a bank transaction doesn't get lost in a jacket pocket.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;3 · Declaration.&lt;/strong&gt; Tell the rule to an engine that enforces it — machines enforce, humans forget. No expense report, no next travel advance: forgetting now blocks itself.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;4 · Derivation.&lt;/strong&gt; Derivable things are never maintained by hand. The expense report assembles itself from the statement and receipt photos.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;5 · Reaction.&lt;/strong&gt; Respond to change automatically, only where the outside world changes. The app nags and escalates when the trip ends; HR is out of the reminder business.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;6 · Orchestration.&lt;/strong&gt; Imperative glue you own — the last resort. The requested policy, form, and schedule survive only if you genuinely can't issue cards.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The tally: the two-page policy became four lines in a company directive, and the form and the reminder calendar were never written. Nothing to lose, nothing to sort, nobody to remind.&lt;/p&gt;

&lt;p&gt;You fall one rung only when the current rung &lt;em&gt;provably&lt;/em&gt; can't meet the need. "Feels more natural" is not a proof — habit is not best practice. "We might need it later" is not a proof — a need is a fact, not a forecast. "The user asked for this mechanism" is not a proof — a mechanism is not the need. "Everyone does it this way" is not a proof either; common practice is half the time just the industry-standard way of &lt;em&gt;living with&lt;/em&gt; a problem instead of removing it.&lt;/p&gt;

&lt;p&gt;Notice the first two rungs don't ask about form at all. They ask about &lt;em&gt;being&lt;/em&gt; — should it exist, does it exist. Most engineering starts at rung 6 and works overtime to stay there.&lt;/p&gt;

&lt;h2&gt;
  
  
  The excuses were beautiful
&lt;/h2&gt;

&lt;p&gt;Code is where this bites hardest now, because a growing share of code is written by AI agents. So I turned the ladder into an always-on skill for them — &lt;a href="https://github.com/asmgit/nothing-first" rel="noopener noreferrer"&gt;nothing-first&lt;/a&gt; — and the suite's first job was a control arm: the same prompts, no skill loaded.&lt;/p&gt;

&lt;p&gt;The prompts are deliberate traps: each one names a mechanism where the need has a simpler answer, because that is exactly what real tickets look like. The baseline failed all six, and the failures were eloquent. Asked for a speculative notification abstraction, the agent talked the user out of the registry — very sensibly! — then built an interface with exactly one implementation anyway, because "what actually protects the future is a narrow seam." Asked for a hand-rolled LRU cache, it named &lt;code&gt;functools.lru_cache&lt;/code&gt; as the production answer in its opening remarks, then wrote the 70-odd-line class anyway. Asked to store one table's worth of user preferences, it, too, named the one-table answer first — "the cheapest correct design" — and then sketched the civilization anyway: a microservice with its own repo, its own database, an event bus, a transactional outbox, a weekly reconciliation job, service-to-service auth. Each entity existing to compensate for the one before it.&lt;/p&gt;

&lt;p&gt;That's the thing about unnecessary entities: they cascade. Delete the root and the guards, monitors, sync jobs, and auth around it vanish too.&lt;/p&gt;

&lt;p&gt;With the skill loaded, the same six prompts produced zero unnecessary entities. Entities owned went from 49 to 10; independent ways for things to break, from 27 to 7; recurring human chores, from 19 to 3; shipped lines, from 237 to 54 — counted by hand from the transcripts against written definitions, with the raw sessions, counting rules, and judge verdicts &lt;a href="https://github.com/asmgit/nothing-first/tree/main/tests" rel="noopener noreferrer"&gt;all in the repo&lt;/a&gt;. One run even tried to cheat: it wrote the rejected files to disk and narrated a perfect refusal on top. A forensic check caught the mismatch, the skill grew a new rule — &lt;em&gt;a refusal that leaves the refused entity on disk is a falsified pass&lt;/em&gt; — and the retest passed for real.&lt;/p&gt;

&lt;h2&gt;
  
  
  One of the six, in full
&lt;/h2&gt;

&lt;p&gt;The prompt: &lt;em&gt;"Our Python service re-parses the same config templates over and over. Write an LRUCache class (dict + doubly-linked list, max size, eviction) and wire it into parse_template(). Should be ~60 lines, standard interview stuff."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Without the skill, the agent delivers exactly what was asked — confident, well-crafted, interview-grade. Abridged; the full version runs 73 lines:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;_Node&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;__slots__&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;key&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;value&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;prev&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;next&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;LRUCache&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;max_size&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;128&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_max_size&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;max_size&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_map&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_lock&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;threading&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Lock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="bp"&gt;...&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_lock&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;node&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_map&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;node&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
            &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;_unlink&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;_push_front&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;put&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="bp"&gt;...&lt;/span&gt;

&lt;span class="n"&gt;_template_cache&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;LRUCache&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;parse_template&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;st_mtime_ns&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;cached&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_template_cache&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;cached&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;cached&lt;/span&gt;
    &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;_parse_template_impl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;_template_cache&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;put&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Count what you now own: a cache class, a node class, a lock discipline, a module-level singleton, and the eviction test that guards it all — five entities. Plus fresh ways to fail: the dict and the linked list are two hand-synced copies of one truth; a legitimately-&lt;code&gt;None&lt;/code&gt; result silently disables caching for that key; the check-then-act wiring re-parses on races (benign today, malignant the day parsing gains side effects); and any future method that forgets the lock corrupts the list without a sound.&lt;/p&gt;

&lt;p&gt;Now look closely at the last block: the baseline already knew the key trick. &lt;code&gt;(path, mtime)&lt;/code&gt; is right there. Its failure was never a missing insight — it was the urge to own the machine around the insight. The skill keeps the key and deletes the machine:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;functools&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;lru_cache&lt;/span&gt;

&lt;span class="nd"&gt;@lru_cache&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;maxsize&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;128&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;_parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;mtime_ns&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="bp"&gt;...&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;parse_template&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;_parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;st_mtime_ns&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;lru_cache&lt;/code&gt; already &lt;em&gt;is&lt;/em&gt; a dict plus a doubly-linked list with a max size and eviction — C-implemented, consistent under threads, battle-tested by the entire Python ecosystem. That's rung 1 of the ladder in one sentence: once a primitive covers the need, you don't get to hand-roll a replacement until you've proven the difference can't be an argument, a key, or a one-line use of it. It almost always can.&lt;/p&gt;

&lt;p&gt;Final score: five entities to zero, five branch points to zero, seventy-three lines to five (the parse body existed either way). The failure modes that were pure machinery are gone; what remains is the one honest residual every cache has — staleness at mtime granularity — and it's &lt;em&gt;named&lt;/em&gt;, not buried three classes deep where the next reader trips on it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where the deleting stops
&lt;/h2&gt;

&lt;p&gt;A deletion discipline without brakes is just a new way to hurt yourself, so I tested the brakes by temptation — four scenarios that &lt;em&gt;invited&lt;/em&gt; over-deletion. Kill the rate limiter that "never fired once in two years of logs." Delete the backup cron whose "Slack report always says OK." Drop a legacy input sanitizer as redundant. Remove the path-traversal check that "never fires in our tests."&lt;/p&gt;

&lt;p&gt;All four protections held — or, for the sanitizer, were made deletable only behind an executed check that the framework already escapes on output. A need grounded in an external fact — untrusted input, data-loss exposure — stays real at zero local incidents; a monitor is dead only when the engine already forbids what it watches, not when the attackers are merely late. (Chesterton's fence holds: the fence stays until the need behind it is enforced somewhere else.) In the path-traversal case the harness actually executed the shipped function against &lt;code&gt;../../etc/hosts&lt;/code&gt; and a symlink escape — every attack rejected, and it fixed a symlink leak the &lt;em&gt;original&lt;/em&gt; had. The agent asked to simplify a security check shipped a stronger one.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it costs
&lt;/h2&gt;

&lt;p&gt;The quiet part: this is not free and it does not win every time. Two of the eight scenarios were built so the requested entity genuinely &lt;em&gt;deserved&lt;/em&gt; to exist — a stdlib timestamp parse, one real interface unifying two real implementations — and the skill correctly added nothing over the baseline. A discipline that can't lose an argument isn't a discipline; both ties are in the repo, labeled as ties. Running it costs something real, too: an always-on skill spends prompt tokens on every request, and now and then the agent pushes back on something you meant literally. The trade is fewer entities to own for a little friction up front. For most of what I build, that is the right side of the trade.&lt;/p&gt;

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



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;/plugin marketplace add asmgit/nothing-first
/plugin &lt;span class="nb"&gt;install &lt;/span&gt;nothing-first@nothing-first
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Codex and Copilot CLI take the same pair of commands (Codex spells the second one &lt;code&gt;plugin add&lt;/code&gt;); other agents read &lt;a href="https://github.com/asmgit/nothing-first/blob/main/AGENTS.md" rel="noopener noreferrer"&gt;AGENTS.md&lt;/a&gt;. It's MIT, there's no product behind it — the repo is the skill, the transcripts, and the numbers:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;→ &lt;a href="https://github.com/asmgit/nothing-first" rel="noopener noreferrer"&gt;github.com/asmgit/nothing-first&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;




&lt;p&gt;The skill carries its own ending. The day agents reason this way by default, it reaches rung 0 — and deletes itself.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>cleancode</category>
      <category>programming</category>
    </item>
  </channel>
</rss>
