<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://richard-towers.com/feed.xml" rel="self" type="application/atom+xml" /><link href="https://richard-towers.com/" rel="alternate" type="text/html" /><updated>2026-03-17T16:30:27+00:00</updated><id>https://richard-towers.com/feed.xml</id><title type="html">richard-towers.com</title><subtitle>Personal blog and webpage</subtitle><author><name>Richard Towers</name></author><entry><title type="html">Dev containers are pretty cool, actually</title><link href="https://richard-towers.com/2026/03/17/dev-containers-are-pretty-cool.html" rel="alternate" type="text/html" title="Dev containers are pretty cool, actually" /><published>2026-03-17T00:00:00+00:00</published><updated>2026-03-17T00:00:00+00:00</updated><id>https://richard-towers.com/2026/03/17/dev-containers-are-pretty-cool</id><content type="html" xml:base="https://richard-towers.com/2026/03/17/dev-containers-are-pretty-cool.html"><![CDATA[<h1 id="dev-containers-are-pretty-cool-actually">Dev containers are pretty cool, actually</h1>

<p>Just because you’re paranoid doesn’t mean everyone isn’t out to get you.</p>

<p>Running the twin gauntlets of npm and <code class="language-plaintext highlighter-rouge">--dangerously-skip-permissions</code> gives you a lot to be paranoid about.</p>

<p>It seems like every other week someone manages to compromise a popular npm package and tries to steal all my precious NFT apes, and it’s just a matter of time until someone prompt-injects my yolo-mode vibecoding session.</p>

<p>I’ve been thinking about this problem in one form or another for years - mostly through a “compromised package” lens, but more recently with coding agents as well. How do you keep your development environment secure, while still reaping the productivity benefits of open source and AI?</p>

<p>The solution is to separate the <em>things you don’t trust</em> from the <em>things you care about</em>. You shouldn’t trust npm packages or coding agents. You should care about your API keys, user data, and (if you’re that way inclined) your apes.</p>

<p>Assume the environment you’re developing software in might be compromised, because you’ve exposed it to <em>things you don’t trust</em>. Then make sure that the <em>things you care about</em> exist in an environment that’s well separated from the potentially compromised development environment.</p>

<p>In an enterprise setting, you might achieve this with some fancy pattern like Privileged Access Workstations (PAWs), or by doing your dev work on virtualised desktop infrastructure (VDI). Those patterns come with big drawbacks though - PAWs require ~double the hardware, VDIs have setup costs and inevitably seem to have terrible user experience (UX).</p>

<p>Dev containers provide a lighter weight option that’s more accessible to solo developers and smaller companies. A bit of discipline is required (which is always bad where security is concerned), but I think they hit a nice sweet spot between security and convenience.</p>

<h2 id="whats-a-dev-container">What’s a dev container?</h2>

<blockquote>
  <p>A development container (or dev container for short) allows you to use a container as a full-featured development environment
– <a href="https://containers.dev/">https://containers.dev/</a></p>
</blockquote>

<p>It’s a container with the development tooling you need installed (things like language runtimes, linters, language servers), and your source code mounted in. You can connect an editor (e.g. VSCode or IntelliJ) running on your host machine to a dev container running in docker locally, or in a remote environment.</p>

<p>There are a few nice things about this.</p>

<p>Firstly, you avoid most of the annoying UX issues of a virtualised desktop environment, because your editor is still running on the same machine with the keyboard and mouse plugged into it.</p>

<p>Secondly, and more relevant to my concerns above, it isolates the development environment from the host machine. This means that a malicious npm package installed in the dev container has dramatically less scope to cause trouble than it would running on the host environment - so long as the <em>things you care about</em> haven’t made their way into your dev container.</p>

<p>Coming up with a secure workflow using dev containers does mean you need to think about what you care about, and carefully separate it from the things you don’t trust.</p>

<h2 id="securing-the-things-you-care-about-with-dev-containers">Securing the things you care about with dev containers</h2>

<p>If you’re relying on dev containers for security, you need to make sure you actually separate your dev environments from the things you care about. It’s no help being in a dev container if you use the AWS CLI to administer your cloud environment in the same container you just npm installed some untrustworthy package from the internet.</p>

<p>Things you want to keep out of the container you’re doing dev work in:</p>

<ul>
  <li>API keys and credentials</li>
  <li>User data (particularly PII, or anything sensitive)</li>
  <li>Private or commercially sensitive source code</li>
</ul>

<p>You can still use dev containers to administer systems from the CLI, if you want, but you should make sure you don’t run anything you don’t trust in <em>those</em> containers.</p>

<p>As mentioned above, one of the untrustworthy things you might want to do in your dev container is run a coding agent. If you’re using a remote model (e.g. claude code), this results in a circular problem - as soon as you authenticate your coding agent to its API, you’ve given it something you care about (API credentials), so you have to at least worry that a prompt injection or hallucination-squatting npm package (i.e. a malicious package with a name likely to be hallucinated by an LLM) might steal those credentials. I don’t think there’s a way around this - <code class="language-plaintext highlighter-rouge">--dangerously-skip-permissions</code> still does what it says on the tin, sadly. At least isolating the agent from your host OS and any other secrets that it doesn’t fundamentally need is better than nothing though.</p>

<h2 id="gotchas-and-caveats">Gotchas and caveats</h2>

<p>Dev containers aren’t designed with security as their primary use case, so it is probably possible for a motivated and skilled adversary to cause problems outside of the container. There’s the security of the container runtime itself (say, docker), which I’m not qualified to judge.</p>

<p>If you’re connecting your IDE (say VSCode) to the container, that opens up more attack surface that you have to worry about. Often, IDEs will do clever things like forward your host machine’s ssh session into the container, often without warning you that they’re doing it, so you might expose something you care about (being able to ssh into things and pull / push from git remotes) without even realizing it.</p>

<p>Also I’m just a bloke on the internet - probably don’t take security advice from some random guy’s blog.</p>

<p>All that said, I think dev containers are a good step up from just running everything on your host OS, which is still what a lot of developers do. Usability is really okay, and the isolation is a lot better than nothing.</p>]]></content><author><name>Richard Towers</name></author><category term="workflow" /><category term="security" /><summary type="html"><![CDATA[Just because you’re paranoid doesn’t mean everyone isn’t out to get you.]]></summary></entry><entry><title type="html">As I was going to St Ives…</title><link href="https://richard-towers.com/2025/10/25/as-i-was-going-to-st-ives.html" rel="alternate" type="text/html" title="As I was going to St Ives…" /><published>2025-10-25T00:00:00+00:00</published><updated>2025-10-25T00:00:00+00:00</updated><id>https://richard-towers.com/2025/10/25/as-i-was-going-to-st-ives</id><content type="html" xml:base="https://richard-towers.com/2025/10/25/as-i-was-going-to-st-ives.html"><![CDATA[<h1 id="as-i-was-going-to-st-ives">As I was going to St Ives…</h1>

<figure>
<img class="fixed-width-20" alt="Image from 'The Rhymes of Mother Goose' in St. Nicholas, Volume 40. A smartly dressed bespectacled man holds his hands up in surprise as at least 8 wives in tall hats and innumerable sacks, cats and kits crowd the background." src="/static/images/St_Nicholas-V40-P2-164.jpg" width="320" style="aspect-ratio: 460 / 599;" />
<figcaption>William Fayal Clarke, Public domain, via <a href="https://commons.wikimedia.org/wiki/File:St_Nicholas-V40-P2-164.jpg">Wikimedia Commons</a></figcaption>
</figure>

<h2 id="prelude">Prelude</h2>

<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">drop</span> <span class="k">table</span> <span class="n">if</span> <span class="k">exists</span> <span class="n">relationships</span><span class="p">;</span>
<span class="k">create</span> <span class="k">table</span> <span class="n">relationships</span><span class="p">(</span>
  <span class="n">id</span> <span class="nb">serial</span><span class="p">,</span>
  <span class="k">source</span> <span class="n">uuid</span><span class="p">,</span>
  <span class="n">target</span> <span class="n">uuid</span> <span class="k">default</span> <span class="n">gen_random_uuid</span><span class="p">(),</span>
  <span class="n">relationship</span> <span class="nb">text</span>
<span class="p">);</span>

<span class="k">drop</span> <span class="k">table</span> <span class="n">if</span> <span class="k">exists</span> <span class="n">characters</span><span class="p">;</span>
<span class="k">create</span> <span class="k">table</span> <span class="n">characters</span><span class="p">(</span>
  <span class="n">id</span> <span class="n">uuid</span> <span class="k">default</span> <span class="n">gen_random_uuid</span><span class="p">()</span> <span class="k">unique</span><span class="p">,</span>
  <span class="k">type</span> <span class="nb">text</span><span class="p">,</span>
  <span class="n">name</span> <span class="nb">text</span>
<span class="p">);</span>
</code></pre></div></div>

<h2 id="as-i-was-going-to-st-ives-1">As I was going to St Ives</h2>

<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">with</span>
  <span class="n">narrator</span> <span class="k">as</span> <span class="p">(</span>
    <span class="k">insert</span> <span class="k">into</span> <span class="n">characters</span> <span class="p">(</span><span class="n">name</span><span class="p">,</span> <span class="k">type</span><span class="p">)</span>
    <span class="k">values</span> <span class="p">(</span><span class="s1">'Narrator'</span><span class="p">,</span> <span class="s1">'Narrator'</span><span class="p">)</span>
    <span class="n">returning</span> <span class="n">id</span>
  <span class="p">),</span>
  <span class="n">journey</span> <span class="k">as</span> <span class="p">(</span>
    <span class="k">insert</span> <span class="k">into</span> <span class="n">relationships</span><span class="p">(</span><span class="k">source</span><span class="p">,</span> <span class="n">relationship</span><span class="p">)</span>
    <span class="k">select</span> <span class="n">narrator</span><span class="p">.</span><span class="n">id</span><span class="p">,</span> <span class="s1">'Going to'</span>
    <span class="k">from</span> <span class="n">narrator</span>
    <span class="n">returning</span> <span class="n">target</span>
  <span class="p">),</span>
  <span class="n">st_ives</span> <span class="k">as</span> <span class="p">(</span>
    <span class="k">insert</span> <span class="k">into</span> <span class="n">characters</span> <span class="p">(</span><span class="n">id</span><span class="p">,</span> <span class="n">name</span><span class="p">,</span> <span class="k">type</span><span class="p">)</span>
    <span class="k">select</span> <span class="n">journey</span><span class="p">.</span><span class="n">target</span><span class="p">,</span> <span class="s1">'St Ives'</span><span class="p">,</span> <span class="s1">'Place'</span>
    <span class="k">from</span> <span class="n">journey</span>
    <span class="n">returning</span> <span class="n">id</span>
  <span class="p">),</span>
</code></pre></div></div>

<h2 id="i-met-a-man">I met a man</h2>

<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code>  <span class="n">meeting</span> <span class="k">as</span> <span class="p">(</span>
    <span class="k">insert</span> <span class="k">into</span> <span class="n">relationships</span><span class="p">(</span><span class="k">source</span><span class="p">,</span> <span class="n">relationship</span><span class="p">)</span>
    <span class="k">select</span> <span class="n">narrator</span><span class="p">.</span><span class="n">id</span><span class="p">,</span> <span class="s1">'Met'</span>
    <span class="k">from</span> <span class="n">narrator</span>
    <span class="n">returning</span> <span class="n">target</span>
  <span class="p">),</span>
  <span class="n">man</span> <span class="k">as</span> <span class="p">(</span>
    <span class="k">insert</span> <span class="k">into</span> <span class="n">characters</span><span class="p">(</span><span class="n">id</span><span class="p">,</span> <span class="n">name</span><span class="p">,</span> <span class="k">type</span><span class="p">)</span>
    <span class="k">select</span> <span class="n">meeting</span><span class="p">.</span><span class="n">target</span><span class="p">,</span> <span class="s1">'Man'</span><span class="p">,</span> <span class="s1">'Man'</span>
    <span class="k">from</span> <span class="n">meeting</span>
    <span class="n">returning</span> <span class="n">id</span>
  <span class="p">),</span>
</code></pre></div></div>
<h2 id="with-seven-wives">with seven wives,</h2>

<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code>  <span class="n">marriages</span> <span class="k">as</span> <span class="p">(</span>
    <span class="k">insert</span> <span class="k">into</span> <span class="n">relationships</span><span class="p">(</span><span class="k">source</span><span class="p">,</span> <span class="n">relationship</span><span class="p">)</span>
    <span class="k">select</span> <span class="n">man</span><span class="p">.</span><span class="n">id</span><span class="p">,</span> <span class="s1">'Wife'</span>
    <span class="k">from</span> <span class="n">man</span><span class="p">,</span> <span class="n">generate_series</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="mi">7</span><span class="p">)</span>
    <span class="n">returning</span> <span class="n">target</span>
  <span class="p">),</span>
  <span class="n">wives</span> <span class="k">as</span> <span class="p">(</span>
    <span class="k">insert</span> <span class="k">into</span> <span class="n">characters</span><span class="p">(</span><span class="n">id</span><span class="p">,</span> <span class="n">name</span><span class="p">,</span> <span class="k">type</span><span class="p">)</span>
    <span class="k">select</span> <span class="n">marriages</span><span class="p">.</span><span class="n">target</span><span class="p">,</span> <span class="s1">'Wife '</span> <span class="o">||</span> <span class="n">row_number</span><span class="p">()</span> <span class="n">over</span> <span class="p">(),</span> <span class="s1">'Wife'</span>
    <span class="k">from</span> <span class="n">marriages</span>
    <span class="n">returning</span> <span class="n">id</span>
  <span class="p">),</span>
</code></pre></div></div>
<h2 id="each-wife-had-seven-sacks">Each wife had seven sacks,</h2>

<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code>  <span class="n">sack_owners</span> <span class="k">as</span> <span class="p">(</span>
    <span class="k">insert</span> <span class="k">into</span> <span class="n">relationships</span><span class="p">(</span><span class="k">source</span><span class="p">,</span> <span class="n">relationship</span><span class="p">)</span>
    <span class="k">select</span> <span class="n">wives</span><span class="p">.</span><span class="n">id</span><span class="p">,</span> <span class="s1">'Owns'</span>
    <span class="k">from</span> <span class="n">wives</span><span class="p">,</span> <span class="n">generate_series</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="mi">7</span><span class="p">)</span>
    <span class="n">returning</span> <span class="n">target</span>
  <span class="p">),</span>
  <span class="n">sacks</span> <span class="k">as</span> <span class="p">(</span>
    <span class="k">insert</span> <span class="k">into</span> <span class="n">characters</span><span class="p">(</span><span class="n">id</span><span class="p">,</span> <span class="n">name</span><span class="p">,</span> <span class="k">type</span><span class="p">)</span>
    <span class="k">select</span> <span class="n">sack_owners</span><span class="p">.</span><span class="n">target</span><span class="p">,</span> <span class="s1">'Sack '</span> <span class="o">||</span> <span class="n">row_number</span><span class="p">()</span> <span class="n">over</span> <span class="p">(),</span> <span class="s1">'Sack'</span>
    <span class="k">from</span> <span class="n">sack_owners</span>
    <span class="n">returning</span> <span class="n">id</span>
  <span class="p">),</span>
</code></pre></div></div>

<h2 id="each-sack-had-seven-cats">Each sack had seven cats,</h2>

<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code>  <span class="n">cat_sacks</span> <span class="k">as</span> <span class="p">(</span>
    <span class="k">insert</span> <span class="k">into</span> <span class="n">relationships</span><span class="p">(</span><span class="k">source</span><span class="p">,</span> <span class="n">relationship</span><span class="p">)</span>
    <span class="k">select</span> <span class="n">sacks</span><span class="p">.</span><span class="n">id</span><span class="p">,</span> <span class="s1">'Contains'</span>
    <span class="k">from</span> <span class="n">sacks</span><span class="p">,</span> <span class="n">generate_series</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="mi">7</span><span class="p">)</span>
    <span class="n">returning</span> <span class="n">target</span>
  <span class="p">),</span>
  <span class="n">cats</span> <span class="k">as</span> <span class="p">(</span>
    <span class="k">insert</span> <span class="k">into</span> <span class="n">characters</span><span class="p">(</span><span class="n">id</span><span class="p">,</span> <span class="n">name</span><span class="p">,</span> <span class="k">type</span><span class="p">)</span>
    <span class="k">select</span> <span class="n">cat_sacks</span><span class="p">.</span><span class="n">target</span><span class="p">,</span> <span class="s1">'Cat '</span> <span class="o">||</span> <span class="n">row_number</span><span class="p">()</span> <span class="n">over</span> <span class="p">(),</span> <span class="s1">'Cat'</span>
    <span class="k">from</span> <span class="n">cat_sacks</span>
    <span class="n">returning</span> <span class="n">id</span>
  <span class="p">),</span>
</code></pre></div></div>

<h2 id="each-cat-had-seven-kits">Each cat had seven kits:</h2>

<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code>  <span class="n">kit_cats</span> <span class="k">as</span> <span class="p">(</span>
    <span class="k">insert</span> <span class="k">into</span> <span class="n">relationships</span><span class="p">(</span><span class="k">source</span><span class="p">,</span> <span class="n">relationship</span><span class="p">)</span>
    <span class="k">select</span> <span class="n">cats</span><span class="p">.</span><span class="n">id</span><span class="p">,</span> <span class="s1">'Parent'</span>
    <span class="k">from</span> <span class="n">cats</span><span class="p">,</span> <span class="n">generate_series</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="mi">7</span><span class="p">)</span>
    <span class="n">returning</span> <span class="n">target</span>
  <span class="p">)</span>
<span class="k">insert</span> <span class="k">into</span> <span class="n">characters</span><span class="p">(</span><span class="n">id</span><span class="p">,</span> <span class="n">name</span><span class="p">,</span> <span class="k">type</span><span class="p">)</span>
<span class="k">select</span> <span class="n">kit_cats</span><span class="p">.</span><span class="n">target</span><span class="p">,</span> <span class="s1">'Kit '</span> <span class="o">||</span> <span class="n">row_number</span><span class="p">()</span> <span class="n">over</span> <span class="p">(),</span> <span class="s1">'Kit'</span>
<span class="k">from</span> <span class="n">kit_cats</span><span class="p">;</span>
</code></pre></div></div>

<h2 id="kits-cats-sacks-and-wives-how-many-were-there-going-to-st-ives">Kits, cats, sacks, and wives, How many were there going to St Ives?</h2>

<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">select</span> <span class="k">count</span><span class="p">(</span><span class="o">*</span><span class="p">)</span>
<span class="k">from</span> <span class="n">relationships</span>
<span class="k">join</span> <span class="n">characters</span> <span class="n">place</span>
  <span class="k">on</span> <span class="n">relationships</span><span class="p">.</span><span class="n">target</span> <span class="o">=</span> <span class="n">place</span><span class="p">.</span><span class="n">id</span>
  <span class="k">and</span> <span class="n">relationship</span> <span class="o">=</span> <span class="s1">'Going to'</span>
  <span class="k">and</span> <span class="n">place</span><span class="p">.</span><span class="n">name</span><span class="o">=</span><span class="s1">'St Ives'</span>
<span class="k">join</span> <span class="n">characters</span> <span class="k">on</span> <span class="n">relationships</span><span class="p">.</span><span class="k">source</span> <span class="o">=</span> <span class="n">characters</span><span class="p">.</span><span class="n">id</span>
<span class="k">where</span> <span class="n">characters</span><span class="p">.</span><span class="k">type</span> <span class="k">in</span> <span class="p">(</span><span class="s1">'Kit'</span><span class="p">,</span> <span class="s1">'Cat'</span><span class="p">,</span> <span class="s1">'Sack'</span><span class="p">,</span> <span class="s1">'Wife'</span><span class="p">);</span>

<span class="c1">-- +-----+</span>
<span class="c1">-- |count|</span>
<span class="c1">-- +-----+</span>
<span class="c1">-- |0    |</span>
<span class="c1">-- +-----+</span>
</code></pre></div></div>

<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">select</span> <span class="k">type</span><span class="p">,</span> <span class="k">count</span><span class="p">(</span><span class="o">*</span><span class="p">)</span>
<span class="k">from</span> <span class="n">characters</span>
<span class="k">where</span> <span class="k">type</span> <span class="o">!=</span> <span class="s1">'Place'</span>
<span class="k">group</span> <span class="k">by</span> <span class="k">rollup</span> <span class="p">(</span><span class="k">type</span><span class="p">)</span>
<span class="k">order</span> <span class="k">by</span> <span class="k">count</span><span class="p">(</span><span class="o">*</span><span class="p">),</span> <span class="k">type</span><span class="p">;</span>

<span class="c1">-- +--------+-----+</span>
<span class="c1">-- |type    |count|</span>
<span class="c1">-- +--------+-----+</span>
<span class="c1">-- |Man     |1    |</span>
<span class="c1">-- |Narrator|1    |</span>
<span class="c1">-- |Wife    |7    |</span>
<span class="c1">-- |Sack    |49   |</span>
<span class="c1">-- |Cat     |343  |</span>
<span class="c1">-- |Kit     |2401 |</span>
<span class="c1">-- |null    |2802 |</span>
<span class="c1">-- +--------+-----+</span>
</code></pre></div></div>]]></content><author><name>Richard Towers</name></author><category term="postgres" /><category term="sql" /><summary type="html"><![CDATA[I met a man with seven wives...]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://richard-towers.com/static/images/St_Nicholas-V40-P2-164.jpg" /><media:content medium="image" url="https://richard-towers.com/static/images/St_Nicholas-V40-P2-164.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Representing graphs in Postgresql</title><link href="https://richard-towers.com/2025/02/16/representing-graphs-in-postgres.html" rel="alternate" type="text/html" title="Representing graphs in Postgresql" /><published>2025-02-16T00:00:00+00:00</published><updated>2025-02-16T00:00:00+00:00</updated><id>https://richard-towers.com/2025/02/16/representing-graphs-in-postgres</id><content type="html" xml:base="https://richard-towers.com/2025/02/16/representing-graphs-in-postgres.html"><![CDATA[<h1 id="representing-graphs-in-postgresql">Representing graphs in Postgresql</h1>

<p>Let’s say we’ve got some graph-like data, such as a social network.</p>

<p>We can represent this generically with two tables:</p>

<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">create</span> <span class="k">table</span> <span class="n">nodes</span> <span class="p">(</span>
  <span class="n">id</span> <span class="nb">serial</span> <span class="k">primary</span> <span class="k">key</span><span class="p">,</span>
  <span class="n">name</span> <span class="nb">text</span><span class="p">,</span>
  <span class="n">details</span> <span class="n">jsonb</span>
<span class="p">);</span>

<span class="k">create</span> <span class="k">table</span> <span class="n">edges</span> <span class="p">(</span>
  <span class="n">id</span> <span class="nb">serial</span> <span class="k">primary</span> <span class="k">key</span><span class="p">,</span>
  <span class="k">type</span> <span class="nb">text</span><span class="p">,</span>
  <span class="n">from_id</span> <span class="nb">integer</span> <span class="k">references</span> <span class="n">nodes</span><span class="p">(</span><span class="n">id</span><span class="p">),</span>
  <span class="n">to_id</span> <span class="nb">integer</span> <span class="k">references</span> <span class="n">nodes</span><span class="p">(</span><span class="n">id</span><span class="p">),</span>
  <span class="n">details</span> <span class="n">jsonb</span>
<span class="p">);</span>
</code></pre></div></div>

<p>And we can insert some data (using lord of the rings characters) like this:</p>

<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">insert</span> <span class="k">into</span> <span class="n">nodes</span> <span class="p">(</span><span class="n">name</span><span class="p">,</span> <span class="n">details</span><span class="p">)</span> <span class="k">values</span>
  <span class="p">(</span><span class="s1">'Frodo Baggins'</span><span class="p">,</span> <span class="s1">'{"species": "Hobbit"}'</span><span class="p">),</span> <span class="c1">-- 1</span>
  <span class="p">(</span><span class="s1">'Bilbo Baggins'</span><span class="p">,</span> <span class="s1">'{"species": "Hobbit"}'</span><span class="p">),</span> <span class="c1">-- 2</span>
  <span class="p">(</span><span class="s1">'Samwise Gamgee'</span><span class="p">,</span> <span class="s1">'{"species": "Hobbit"}'</span><span class="p">),</span> <span class="c1">-- 3</span>
  <span class="p">(</span><span class="s1">'Hamfast Gamgee'</span><span class="p">,</span> <span class="s1">'{"species": "Hobbit"}'</span><span class="p">),</span> <span class="c1">-- 4</span>
  <span class="p">(</span><span class="s1">'Gandalf'</span><span class="p">,</span> <span class="s1">'{"species": "Wizard"}'</span><span class="p">),</span> <span class="c1">-- 5</span>
  <span class="p">(</span><span class="s1">'Aragorn'</span><span class="p">,</span> <span class="s1">'{"species": "Human"}'</span><span class="p">),</span> <span class="c1">-- 6</span>
  <span class="p">(</span><span class="s1">'Arathorn'</span><span class="p">,</span> <span class="s1">'{"species": "Human"}'</span><span class="p">),</span> <span class="c1">-- 7</span>
  <span class="p">(</span><span class="s1">'Legolas'</span><span class="p">,</span> <span class="s1">'{"species": "Elf"}'</span><span class="p">),</span> <span class="c1">-- 8</span>
  <span class="p">(</span><span class="s1">'Thranduil'</span><span class="p">,</span> <span class="s1">'{"species": "Elf"}'</span><span class="p">),</span> <span class="c1">-- 9</span>
  <span class="p">(</span><span class="s1">'Gimli'</span><span class="p">,</span> <span class="s1">'{"species": "Dwarf"}'</span><span class="p">),</span> <span class="c1">-- 10</span>
  <span class="p">(</span><span class="s1">'Gloin'</span><span class="p">,</span> <span class="s1">'{"species": "Dwarf"}'</span><span class="p">);</span> <span class="c1">-- 11</span>

<span class="c1">-- Parents</span>
<span class="k">insert</span> <span class="k">into</span> <span class="n">edges</span> <span class="p">(</span><span class="k">type</span><span class="p">,</span> <span class="n">from_id</span><span class="p">,</span> <span class="n">to_id</span><span class="p">,</span> <span class="n">details</span><span class="p">)</span> <span class="k">values</span>
  <span class="p">(</span><span class="s1">'parent'</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="s1">'{}'</span><span class="p">),</span>
  <span class="p">(</span><span class="s1">'parent'</span><span class="p">,</span> <span class="mi">4</span><span class="p">,</span> <span class="mi">3</span><span class="p">,</span> <span class="s1">'{}'</span><span class="p">),</span>
  <span class="p">(</span><span class="s1">'parent'</span><span class="p">,</span> <span class="mi">7</span><span class="p">,</span> <span class="mi">6</span><span class="p">,</span> <span class="s1">'{}'</span><span class="p">),</span>
  <span class="p">(</span><span class="s1">'parent'</span><span class="p">,</span> <span class="mi">9</span><span class="p">,</span> <span class="mi">8</span><span class="p">,</span> <span class="s1">'{}'</span><span class="p">),</span>
  <span class="p">(</span><span class="s1">'parent'</span><span class="p">,</span> <span class="mi">11</span><span class="p">,</span> <span class="mi">10</span><span class="p">,</span> <span class="s1">'{}'</span><span class="p">);</span>
</code></pre></div></div>

<h2 id="simple-queries---finding-parents-and-children">Simple queries - finding parents and children</h2>

<p>We can find someone’s parents by joining the <code class="language-plaintext highlighter-rouge">edges</code> table to the <code class="language-plaintext highlighter-rouge">nodes</code> table.</p>

<p>Hardcoding the child id (3) to find Samwise’s parents:</p>

<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">select</span> <span class="n">parent</span><span class="p">.</span><span class="n">name</span> <span class="k">from</span> <span class="n">nodes</span> <span class="n">child</span>
<span class="k">join</span> <span class="n">edges</span> <span class="k">on</span> <span class="n">edges</span><span class="p">.</span><span class="n">to_id</span> <span class="o">=</span> <span class="n">child</span><span class="p">.</span><span class="n">id</span>
<span class="k">join</span> <span class="n">nodes</span> <span class="n">parent</span> <span class="k">on</span> <span class="n">edges</span><span class="p">.</span><span class="n">from_id</span> <span class="o">=</span> <span class="n">parent</span><span class="p">.</span><span class="n">id</span>
<span class="k">where</span> <span class="n">child</span><span class="p">.</span><span class="n">id</span> <span class="o">=</span> <span class="mi">3</span><span class="p">;</span>
</code></pre></div></div>

<p>If we didn’t know Samwise’s id, we could find him by name first using a CTE:</p>

<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">with</span> <span class="n">child</span> <span class="k">as</span> <span class="p">(</span><span class="k">select</span> <span class="n">id</span> <span class="k">from</span> <span class="n">nodes</span> <span class="k">where</span> <span class="n">name</span> <span class="o">=</span> <span class="s1">'Samwise Gamgee'</span><span class="p">)</span>
<span class="k">select</span> <span class="n">parent</span><span class="p">.</span><span class="n">name</span> <span class="k">from</span> <span class="n">child</span>
<span class="k">join</span> <span class="n">edges</span> <span class="k">on</span> <span class="n">edges</span><span class="p">.</span><span class="n">to_id</span> <span class="o">=</span> <span class="n">child</span><span class="p">.</span><span class="n">id</span>
<span class="k">join</span> <span class="n">nodes</span> <span class="n">parent</span> <span class="k">on</span> <span class="n">edges</span><span class="p">.</span><span class="n">from_id</span> <span class="o">=</span> <span class="n">parent</span><span class="p">.</span><span class="n">id</span><span class="p">;</span>
</code></pre></div></div>

<h2 id="more-complex-queries---friends-of-friends">More complex queries - friends of friends</h2>

<p>Not all of our characters knew each other directly. The friendship graph is a bit more complex:</p>

<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">insert</span> <span class="k">into</span> <span class="n">edges</span> <span class="p">(</span><span class="k">type</span><span class="p">,</span> <span class="n">from_id</span><span class="p">,</span> <span class="n">to_id</span><span class="p">,</span> <span class="n">details</span><span class="p">)</span> <span class="k">values</span>
  <span class="c1">-- Everyone in the fellowship is friends with everyone else</span>
  <span class="p">(</span><span class="s1">'friend'</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">3</span><span class="p">,</span> <span class="s1">'{}'</span><span class="p">),</span> <span class="c1">-- Frodo and Sam</span>
  <span class="p">(</span><span class="s1">'friend'</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">5</span><span class="p">,</span> <span class="s1">'{}'</span><span class="p">),</span> <span class="c1">-- Frodo and Gandalf</span>
  <span class="p">(</span><span class="s1">'friend'</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">6</span><span class="p">,</span> <span class="s1">'{}'</span><span class="p">),</span> <span class="c1">-- Frodo and Aragorn</span>
  <span class="p">(</span><span class="s1">'friend'</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">8</span><span class="p">,</span> <span class="s1">'{}'</span><span class="p">),</span> <span class="c1">-- Frodo and Legolas</span>
  <span class="p">(</span><span class="s1">'friend'</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">10</span><span class="p">,</span> <span class="s1">'{}'</span><span class="p">),</span> <span class="c1">-- Frodo and Gimli</span>
  <span class="p">(</span><span class="s1">'friend'</span><span class="p">,</span> <span class="mi">3</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="s1">'{}'</span><span class="p">),</span> <span class="c1">-- Sam and Frodo</span>
  <span class="p">(</span><span class="s1">'friend'</span><span class="p">,</span> <span class="mi">3</span><span class="p">,</span> <span class="mi">5</span><span class="p">,</span> <span class="s1">'{}'</span><span class="p">),</span> <span class="c1">-- Sam and Gandalf</span>
  <span class="p">(</span><span class="s1">'friend'</span><span class="p">,</span> <span class="mi">3</span><span class="p">,</span> <span class="mi">6</span><span class="p">,</span> <span class="s1">'{}'</span><span class="p">),</span> <span class="c1">-- Sam and Aragorn</span>
  <span class="p">(</span><span class="s1">'friend'</span><span class="p">,</span> <span class="mi">3</span><span class="p">,</span> <span class="mi">8</span><span class="p">,</span> <span class="s1">'{}'</span><span class="p">),</span> <span class="c1">-- Sam and Legolas</span>
  <span class="p">(</span><span class="s1">'friend'</span><span class="p">,</span> <span class="mi">3</span><span class="p">,</span> <span class="mi">10</span><span class="p">,</span> <span class="s1">'{}'</span><span class="p">),</span> <span class="c1">-- Sam and Gimli</span>
  <span class="p">(</span><span class="s1">'friend'</span><span class="p">,</span> <span class="mi">5</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="s1">'{}'</span><span class="p">),</span> <span class="c1">-- Gandalf and Frodo</span>
  <span class="p">(</span><span class="s1">'friend'</span><span class="p">,</span> <span class="mi">5</span><span class="p">,</span> <span class="mi">3</span><span class="p">,</span> <span class="s1">'{}'</span><span class="p">),</span> <span class="c1">-- Gandalf and Sam</span>
  <span class="p">(</span><span class="s1">'friend'</span><span class="p">,</span> <span class="mi">5</span><span class="p">,</span> <span class="mi">6</span><span class="p">,</span> <span class="s1">'{}'</span><span class="p">),</span> <span class="c1">-- Gandalf and Aragorn</span>
  <span class="p">(</span><span class="s1">'friend'</span><span class="p">,</span> <span class="mi">5</span><span class="p">,</span> <span class="mi">8</span><span class="p">,</span> <span class="s1">'{}'</span><span class="p">),</span> <span class="c1">-- Gandalf and Legolas</span>
  <span class="p">(</span><span class="s1">'friend'</span><span class="p">,</span> <span class="mi">5</span><span class="p">,</span> <span class="mi">10</span><span class="p">,</span> <span class="s1">'{}'</span><span class="p">),</span> <span class="c1">-- Gandalf and Gimli</span>
  <span class="p">(</span><span class="s1">'friend'</span><span class="p">,</span> <span class="mi">6</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="s1">'{}'</span><span class="p">),</span> <span class="c1">-- Aragorn and Frodo</span>
  <span class="p">(</span><span class="s1">'friend'</span><span class="p">,</span> <span class="mi">6</span><span class="p">,</span> <span class="mi">3</span><span class="p">,</span> <span class="s1">'{}'</span><span class="p">),</span> <span class="c1">-- Aragorn and Sam</span>
  <span class="p">(</span><span class="s1">'friend'</span><span class="p">,</span> <span class="mi">6</span><span class="p">,</span> <span class="mi">5</span><span class="p">,</span> <span class="s1">'{}'</span><span class="p">),</span> <span class="c1">-- Aragorn and Gandalf</span>
  <span class="p">(</span><span class="s1">'friend'</span><span class="p">,</span> <span class="mi">6</span><span class="p">,</span> <span class="mi">8</span><span class="p">,</span> <span class="s1">'{}'</span><span class="p">),</span> <span class="c1">-- Aragorn and Legolas</span>
  <span class="p">(</span><span class="s1">'friend'</span><span class="p">,</span> <span class="mi">6</span><span class="p">,</span> <span class="mi">10</span><span class="p">,</span> <span class="s1">'{}'</span><span class="p">),</span> <span class="c1">-- Aragorn and Gimli</span>
  <span class="p">(</span><span class="s1">'friend'</span><span class="p">,</span> <span class="mi">8</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="s1">'{}'</span><span class="p">),</span> <span class="c1">-- Legolas and Frodo</span>
  <span class="p">(</span><span class="s1">'friend'</span><span class="p">,</span> <span class="mi">8</span><span class="p">,</span> <span class="mi">3</span><span class="p">,</span> <span class="s1">'{}'</span><span class="p">),</span> <span class="c1">-- Legolas and Sam</span>
  <span class="p">(</span><span class="s1">'friend'</span><span class="p">,</span> <span class="mi">8</span><span class="p">,</span> <span class="mi">5</span><span class="p">,</span> <span class="s1">'{}'</span><span class="p">),</span> <span class="c1">-- Legolas and Gandalf</span>
  <span class="p">(</span><span class="s1">'friend'</span><span class="p">,</span> <span class="mi">8</span><span class="p">,</span> <span class="mi">6</span><span class="p">,</span> <span class="s1">'{}'</span><span class="p">),</span> <span class="c1">-- Legolas and Aragorn</span>
  <span class="p">(</span><span class="s1">'friend'</span><span class="p">,</span> <span class="mi">8</span><span class="p">,</span> <span class="mi">10</span><span class="p">,</span> <span class="s1">'{}'</span><span class="p">),</span> <span class="c1">-- Legolas and Gimli</span>
  <span class="p">(</span><span class="s1">'friend'</span><span class="p">,</span> <span class="mi">10</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="s1">'{}'</span><span class="p">),</span> <span class="c1">-- Gimli and Frodo</span>
  <span class="p">(</span><span class="s1">'friend'</span><span class="p">,</span> <span class="mi">10</span><span class="p">,</span> <span class="mi">3</span><span class="p">,</span> <span class="s1">'{}'</span><span class="p">),</span> <span class="c1">-- Gimli and Sam</span>
  <span class="p">(</span><span class="s1">'friend'</span><span class="p">,</span> <span class="mi">10</span><span class="p">,</span> <span class="mi">5</span><span class="p">,</span> <span class="s1">'{}'</span><span class="p">),</span> <span class="c1">-- Gimli and Gandalf</span>
  <span class="p">(</span><span class="s1">'friend'</span><span class="p">,</span> <span class="mi">10</span><span class="p">,</span> <span class="mi">6</span><span class="p">,</span> <span class="s1">'{}'</span><span class="p">),</span> <span class="c1">-- Gimli and Aragorn</span>
  <span class="p">(</span><span class="s1">'friend'</span><span class="p">,</span> <span class="mi">10</span><span class="p">,</span> <span class="mi">8</span><span class="p">,</span> <span class="s1">'{}'</span><span class="p">),</span> <span class="c1">-- Gimli and Legolas</span>
  <span class="c1">-- Bilbo was friends with Hamfast and Gandalf</span>
  <span class="p">(</span><span class="s1">'friend'</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">4</span><span class="p">,</span> <span class="s1">'{}'</span><span class="p">),</span> <span class="c1">-- Bilbo and Hamfast</span>
  <span class="p">(</span><span class="s1">'friend'</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">5</span><span class="p">,</span> <span class="s1">'{}'</span><span class="p">),</span> <span class="c1">-- Bilbo and Gandalf</span>
  <span class="c1">-- And vice versa</span>
  <span class="p">(</span><span class="s1">'friend'</span><span class="p">,</span> <span class="mi">4</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="s1">'{}'</span><span class="p">),</span> <span class="c1">-- Hamfast and Bilbo</span>
  <span class="p">(</span><span class="s1">'friend'</span><span class="p">,</span> <span class="mi">5</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="s1">'{}'</span><span class="p">),</span> <span class="c1">-- Gandalf and Bilbo</span>
  <span class="c1">-- Gandalf was friends with Bilbo, Hamfast and Thranduil, but for the sake of</span>
  <span class="c1">-- argument let's say he didn't know Gloin or Arathorn</span>
  <span class="p">(</span><span class="s1">'friend'</span><span class="p">,</span> <span class="mi">5</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="s1">'{}'</span><span class="p">),</span> <span class="c1">-- Gandalf and Bilbo</span>
  <span class="p">(</span><span class="s1">'friend'</span><span class="p">,</span> <span class="mi">5</span><span class="p">,</span> <span class="mi">4</span><span class="p">,</span> <span class="s1">'{}'</span><span class="p">),</span> <span class="c1">-- Gandalf and Hamfast</span>
  <span class="p">(</span><span class="s1">'friend'</span><span class="p">,</span> <span class="mi">5</span><span class="p">,</span> <span class="mi">9</span><span class="p">,</span> <span class="s1">'{}'</span><span class="p">),</span> <span class="c1">-- Gandalf and Thranduil</span>
  <span class="c1">-- And vice versa</span>
  <span class="p">(</span><span class="s1">'friend'</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">5</span><span class="p">,</span> <span class="s1">'{}'</span><span class="p">),</span> <span class="c1">-- Bilbo and Gandalf</span>
  <span class="p">(</span><span class="s1">'friend'</span><span class="p">,</span> <span class="mi">4</span><span class="p">,</span> <span class="mi">5</span><span class="p">,</span> <span class="s1">'{}'</span><span class="p">),</span> <span class="c1">-- Hamfast and Gandalf</span>
  <span class="p">(</span><span class="s1">'friend'</span><span class="p">,</span> <span class="mi">9</span><span class="p">,</span> <span class="mi">5</span><span class="p">,</span> <span class="s1">'{}'</span><span class="p">);</span> <span class="c1">-- Thranduil and Gandalf</span>
</code></pre></div></div>

<p>We could follow two friendship links using CTEs like before. Let’s say friends of friends of Samwise:</p>

<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">with</span> <span class="n">samwise</span> <span class="k">as</span> <span class="p">(</span><span class="k">select</span> <span class="n">id</span> <span class="k">from</span> <span class="n">nodes</span> <span class="k">where</span> <span class="n">name</span> <span class="o">=</span> <span class="s1">'Samwise Gamgee'</span><span class="p">),</span>
<span class="n">samwise_friends</span> <span class="k">as</span> <span class="p">(</span>
  <span class="k">select</span> <span class="k">distinct</span> <span class="n">edges</span><span class="p">.</span><span class="n">to_id</span> <span class="k">from</span> <span class="n">edges</span>
  <span class="k">join</span> <span class="n">samwise</span> <span class="k">on</span> <span class="n">edges</span><span class="p">.</span><span class="n">from_id</span> <span class="o">=</span> <span class="n">samwise</span><span class="p">.</span><span class="n">id</span> <span class="k">and</span> <span class="n">edges</span><span class="p">.</span><span class="k">type</span> <span class="o">=</span> <span class="s1">'friend'</span>
<span class="p">),</span>
<span class="n">friends_of_friends</span> <span class="k">as</span> <span class="p">(</span>
  <span class="k">select</span> <span class="k">distinct</span> <span class="n">edges</span><span class="p">.</span><span class="n">to_id</span> <span class="k">from</span> <span class="n">edges</span>
  <span class="k">join</span> <span class="n">samwise_friends</span> <span class="k">on</span> <span class="n">edges</span><span class="p">.</span><span class="n">from_id</span> <span class="o">=</span> <span class="n">samwise_friends</span><span class="p">.</span><span class="n">to_id</span> <span class="k">and</span> <span class="n">edges</span><span class="p">.</span><span class="k">type</span> <span class="o">=</span> <span class="s1">'friend'</span>
<span class="p">)</span>
<span class="k">select</span> <span class="n">name</span> <span class="k">from</span> <span class="n">friends_of_friends</span> <span class="k">join</span> <span class="n">nodes</span> <span class="k">on</span> <span class="n">nodes</span><span class="p">.</span><span class="n">id</span> <span class="o">=</span> <span class="n">friends_of_friends</span><span class="p">.</span><span class="n">to_id</span><span class="p">;</span>
</code></pre></div></div>

<p>This returns almost everyone, except for Arathorn and Gloin who we’ve decided weren’t friends with anyone Samwise knew
(even though they probably knew Gandalf, in reality).</p>

<p>If we don’t mind building up arbitrarily long chains of CTEs, we can follow any number of edges using this technique.</p>

<p>It would be kind of nice to be able to pass in the root node and the edge paths as parameters though, rather than
dynamically building up the CTEs.</p>

<h2 id="parameterised-recursive-ctes">Parameterised recursive CTEs</h2>

<p>Let’s say we want to find the parents of the friends-of-friends of Samwise.</p>

<p>We could do this naively by building up a CTE for each level of friendship:</p>

<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">with</span> <span class="n">samwise</span> <span class="k">as</span> <span class="p">(</span><span class="k">select</span> <span class="n">id</span> <span class="k">from</span> <span class="n">nodes</span> <span class="k">where</span> <span class="n">name</span> <span class="o">=</span> <span class="s1">'Samwise Gamgee'</span><span class="p">),</span>
<span class="n">samwise_friends</span> <span class="k">as</span> <span class="p">(</span>
  <span class="k">select</span> <span class="k">distinct</span> <span class="n">edges</span><span class="p">.</span><span class="n">to_id</span> <span class="k">from</span> <span class="n">edges</span>
  <span class="k">join</span> <span class="n">samwise</span> <span class="k">on</span> <span class="n">edges</span><span class="p">.</span><span class="n">from_id</span> <span class="o">=</span> <span class="n">samwise</span><span class="p">.</span><span class="n">id</span> <span class="k">and</span> <span class="n">edges</span><span class="p">.</span><span class="k">type</span> <span class="o">=</span> <span class="s1">'friend'</span>
<span class="p">),</span>
<span class="n">friends_of_friends</span> <span class="k">as</span> <span class="p">(</span>
  <span class="k">select</span> <span class="k">distinct</span> <span class="n">edges</span><span class="p">.</span><span class="n">to_id</span> <span class="k">from</span> <span class="n">edges</span>
  <span class="k">join</span> <span class="n">samwise_friends</span> <span class="k">on</span> <span class="n">edges</span><span class="p">.</span><span class="n">from_id</span> <span class="o">=</span> <span class="n">samwise_friends</span><span class="p">.</span><span class="n">to_id</span> <span class="k">and</span> <span class="n">edges</span><span class="p">.</span><span class="k">type</span> <span class="o">=</span> <span class="s1">'friend'</span>
<span class="p">),</span>
<span class="n">friends_of_friends_parents</span> <span class="k">as</span> <span class="p">(</span>
  <span class="k">select</span> <span class="k">distinct</span> <span class="n">edges</span><span class="p">.</span><span class="n">from_id</span> <span class="k">from</span> <span class="n">edges</span>
  <span class="k">join</span> <span class="n">friends_of_friends</span> <span class="k">on</span> <span class="n">edges</span><span class="p">.</span><span class="n">to_id</span> <span class="o">=</span> <span class="n">friends_of_friends</span><span class="p">.</span><span class="n">to_id</span> <span class="k">and</span> <span class="n">edges</span><span class="p">.</span><span class="k">type</span> <span class="o">=</span> <span class="s1">'parent'</span>
<span class="p">)</span>
<span class="k">select</span> <span class="n">name</span> <span class="k">from</span> <span class="n">friends_of_friends_parents</span> <span class="k">join</span> <span class="n">nodes</span> <span class="k">on</span> <span class="n">nodes</span><span class="p">.</span><span class="n">id</span> <span class="o">=</span> <span class="n">friends_of_friends_parents</span><span class="p">.</span><span class="n">from_id</span><span class="p">;</span>
</code></pre></div></div>

<p>But this quickly becomes unwieldy. Instead, let’s pull the variable bits out, and use a recursive CTE:</p>

<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">with</span> <span class="k">recursive</span> 
<span class="n">root</span><span class="p">(</span><span class="n">id</span><span class="p">)</span> <span class="k">as</span> <span class="p">(</span><span class="k">select</span> <span class="n">id</span> <span class="k">from</span> <span class="n">nodes</span> <span class="k">where</span> <span class="n">name</span> <span class="o">=</span> <span class="s1">'Samwise Gamgee'</span><span class="p">),</span>
<span class="n">paths</span><span class="p">(</span><span class="n">path</span><span class="p">)</span> <span class="k">as</span> <span class="p">(</span><span class="k">values</span> <span class="p">(</span><span class="s1">'{friend,friend,parent}'</span><span class="p">::</span><span class="nb">text</span><span class="p">[])),</span>
<span class="n">results</span><span class="p">(</span><span class="n">id</span><span class="p">)</span> <span class="k">as</span> <span class="p">(</span>
  <span class="k">select</span> <span class="n">root</span><span class="p">.</span><span class="n">id</span><span class="p">,</span> <span class="mi">1</span> <span class="k">as</span> <span class="n">path_index</span> <span class="k">from</span> <span class="n">root</span>
<span class="k">union</span>
  <span class="k">select</span> <span class="n">edges</span><span class="p">.</span><span class="n">from_id</span><span class="p">,</span> <span class="n">path_index</span> <span class="o">+</span> <span class="mi">1</span> <span class="k">as</span> <span class="n">path_index</span> <span class="k">from</span> <span class="n">results</span>
  <span class="k">join</span> <span class="n">edges</span> <span class="k">on</span> <span class="n">edges</span><span class="p">.</span><span class="n">to_id</span> <span class="o">=</span> <span class="n">results</span><span class="p">.</span><span class="n">id</span>
  <span class="k">join</span> <span class="n">paths</span> <span class="k">on</span> <span class="n">edges</span><span class="p">.</span><span class="k">type</span> <span class="o">=</span> <span class="n">paths</span><span class="p">.</span><span class="n">path</span><span class="p">[</span><span class="n">path_index</span><span class="p">]</span>
<span class="p">)</span>
<span class="k">select</span> <span class="o">*</span> <span class="k">from</span> <span class="n">results</span>
<span class="k">join</span> <span class="n">nodes</span> <span class="k">on</span> <span class="n">nodes</span><span class="p">.</span><span class="n">id</span> <span class="o">=</span> <span class="n">results</span><span class="p">.</span><span class="n">id</span>
<span class="k">join</span> <span class="n">paths</span> <span class="k">on</span> <span class="k">cardinality</span><span class="p">(</span><span class="n">paths</span><span class="p">.</span><span class="n">path</span><span class="p">)</span> <span class="o">+</span> <span class="mi">1</span> <span class="o">=</span> <span class="n">results</span><span class="p">.</span><span class="n">path_index</span><span class="p">;</span>
</code></pre></div></div>

<p>This can then be parameterised, so we can create this as a prepared statement and call it lots of times with
different root nodes and paths.</p>

<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">with</span> <span class="k">recursive</span> 
<span class="n">root</span><span class="p">(</span><span class="n">id</span><span class="p">)</span> <span class="k">as</span> <span class="p">(</span><span class="k">select</span> <span class="n">id</span> <span class="k">from</span> <span class="n">nodes</span> <span class="k">where</span> <span class="n">name</span> <span class="o">=</span> <span class="o">?</span><span class="p">),</span>
<span class="n">paths</span><span class="p">(</span><span class="n">path</span><span class="p">)</span> <span class="k">as</span> <span class="p">(</span><span class="k">values</span> <span class="p">(</span><span class="o">?</span><span class="p">::</span><span class="nb">text</span><span class="p">[])),</span>
<span class="n">results</span><span class="p">(</span><span class="n">id</span><span class="p">)</span> <span class="k">as</span> <span class="p">(</span>
  <span class="k">select</span> <span class="n">root</span><span class="p">.</span><span class="n">id</span><span class="p">,</span> <span class="mi">1</span> <span class="k">as</span> <span class="n">path_index</span> <span class="k">from</span> <span class="n">root</span>
<span class="k">union</span>
  <span class="k">select</span> <span class="n">edges</span><span class="p">.</span><span class="n">from_id</span><span class="p">,</span> <span class="n">path_index</span> <span class="o">+</span> <span class="mi">1</span> <span class="k">as</span> <span class="n">path_index</span> <span class="k">from</span> <span class="n">results</span>
  <span class="k">join</span> <span class="n">edges</span> <span class="k">on</span> <span class="n">edges</span><span class="p">.</span><span class="n">to_id</span> <span class="o">=</span> <span class="n">results</span><span class="p">.</span><span class="n">id</span>
  <span class="k">join</span> <span class="n">paths</span> <span class="k">on</span> <span class="n">edges</span><span class="p">.</span><span class="k">type</span> <span class="o">=</span> <span class="n">paths</span><span class="p">.</span><span class="n">path</span><span class="p">[</span><span class="n">path_index</span><span class="p">]</span>
<span class="p">)</span>
<span class="k">select</span> <span class="o">*</span> <span class="k">from</span> <span class="n">results</span>
<span class="k">join</span> <span class="n">nodes</span> <span class="k">on</span> <span class="n">nodes</span><span class="p">.</span><span class="n">id</span> <span class="o">=</span> <span class="n">results</span><span class="p">.</span><span class="n">id</span>
<span class="k">join</span> <span class="n">paths</span> <span class="k">on</span> <span class="k">cardinality</span><span class="p">(</span><span class="n">paths</span><span class="p">.</span><span class="n">path</span><span class="p">)</span> <span class="o">+</span> <span class="mi">1</span> <span class="o">=</span> <span class="n">results</span><span class="p">.</span><span class="n">path_index</span><span class="p">;</span>
</code></pre></div></div>

<p>If we wanted every node along the path, instead of just the end node, we could remove the join to <code class="language-plaintext highlighter-rouge">paths</code> in the final
line.</p>

<h2 id="conclusion">Conclusion</h2>

<p>This is one way of representing graph-like data in Postgresql, and querying it in a flexible way.</p>

<p>I’d be very interested in hearing about other techniques or improvements.</p>]]></content><author><name>Richard Towers</name></author><category term="postgres" /><category term="sql" /><summary type="html"><![CDATA[Some tricks and techniques for working with graph or tree like data in Postgresql]]></summary></entry><entry><title type="html">Recursive CTEs in ActiveRecord</title><link href="https://richard-towers.com/2024/02/03/recursive-ctes-in-active-record.html" rel="alternate" type="text/html" title="Recursive CTEs in ActiveRecord" /><published>2024-02-03T00:00:00+00:00</published><updated>2024-02-03T00:00:00+00:00</updated><id>https://richard-towers.com/2024/02/03/recursive-ctes-in-active-record</id><content type="html" xml:base="https://richard-towers.com/2024/02/03/recursive-ctes-in-active-record.html"><![CDATA[<h1 id="recursive-ctes-in-activerecord">Recursive CTEs in ActiveRecord</h1>

<h2 id="update-2025">Update 2025</h2>

<p><a href="https://github.com/rails/rails/blob/7-2-stable/activerecord/CHANGELOG.md">As of Rails 7.2</a> ActiveRecord has
support for recursive CTEs built in (thanks to ClearlyClaire).</p>

<p>It would still be a bit of a faff to construct the exact SQL from the postgres docs
using Arel (as this blog post does), but roughly you could do:</p>

<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">T</span> <span class="o">&lt;</span> <span class="no">ActiveRecord</span><span class="o">::</span><span class="no">Base</span>
  <span class="k">def</span> <span class="nc">self</span><span class="o">.</span><span class="nf">table_name</span> <span class="o">=</span> <span class="s2">"t"</span>
<span class="k">end</span>

<span class="no">ActiveRecord</span><span class="o">::</span><span class="no">Base</span><span class="p">.</span><span class="nf">connection</span><span class="p">.</span><span class="nf">execute</span><span class="p">(</span>
  <span class="no">T</span><span class="p">.</span><span class="nf">with_recursive</span><span class="p">(</span>
    <span class="no">Arel</span><span class="p">.</span><span class="nf">sql</span><span class="p">(</span><span class="s2">"t(n)"</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">[</span>
      <span class="no">Arel</span><span class="p">.</span><span class="nf">sql</span><span class="p">(</span><span class="s2">"VALUES (1)"</span><span class="p">),</span>
      <span class="no">Arel</span><span class="p">.</span><span class="nf">sql</span><span class="p">(</span><span class="s2">"SELECT n+1 FROM t WHERE n &lt; 100"</span><span class="p">)</span>
    <span class="p">]</span>
  <span class="p">).</span><span class="nf">to_sql</span>
<span class="p">)</span>
</code></pre></div></div>

<p>The example from the changelog is probably better:</p>

<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="no">Post</span><span class="p">.</span><span class="nf">with_recursive</span><span class="p">(</span>
  <span class="ss">post_and_replies: </span><span class="p">[</span>
    <span class="no">Post</span><span class="p">.</span><span class="nf">where</span><span class="p">(</span><span class="ss">id: </span><span class="mi">42</span><span class="p">),</span>
    <span class="no">Post</span><span class="p">.</span><span class="nf">joins</span><span class="p">(</span><span class="s1">'JOIN post_and_replies ON posts.in_reply_to_id = post_and_replies.id'</span><span class="p">),</span>
  <span class="p">]</span>
<span class="p">)</span>
</code></pre></div></div>

<p>Generates the following SQL:</p>

<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">WITH</span> <span class="k">RECURSIVE</span> <span class="nv">"post_and_replies"</span> <span class="k">AS</span> <span class="p">(</span>
  <span class="p">(</span><span class="k">SELECT</span> <span class="nv">"posts"</span><span class="p">.</span><span class="o">*</span> <span class="k">FROM</span> <span class="nv">"posts"</span> <span class="k">WHERE</span> <span class="nv">"posts"</span><span class="p">.</span><span class="nv">"id"</span> <span class="o">=</span> <span class="mi">42</span><span class="p">)</span>
  <span class="k">UNION</span> <span class="k">ALL</span>
  <span class="p">(</span><span class="k">SELECT</span> <span class="nv">"posts"</span><span class="p">.</span><span class="o">*</span> <span class="k">FROM</span> <span class="nv">"posts"</span> <span class="k">JOIN</span> <span class="n">post_and_replies</span> <span class="k">ON</span> <span class="n">posts</span><span class="p">.</span><span class="n">in_reply_to_id</span> <span class="o">=</span> <span class="n">post_and_replies</span><span class="p">.</span><span class="n">id</span><span class="p">)</span>
<span class="p">)</span>
<span class="k">SELECT</span> <span class="nv">"posts"</span><span class="p">.</span><span class="o">*</span> <span class="k">FROM</span> <span class="nv">"posts"</span>
</code></pre></div></div>

<h2 id="original-post">Original post</h2>

<p><a href="https://guides.rubyonrails.org/active_record_basics.html">ActiveRecord</a> is the
Object Relational Mapper (ORM) in Rails which lets you interact with the database.</p>

<p>A lot of the time, you don’t have to think about SQL all that much - you write queries like:</p>

<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="no">User</span><span class="p">.</span><span class="nf">where</span><span class="p">(</span><span class="ss">name: </span><span class="s1">'Richard'</span><span class="p">,</span> <span class="ss">occupation: </span><span class="s1">'Code Monkey'</span><span class="p">).</span><span class="nf">order</span><span class="p">(</span><span class="ss">created_at: :desc</span><span class="p">)</span>
</code></pre></div></div>

<p>… and ActiveRecord turns them to SQL invisibly when it goes to the database
to fetch the results.</p>

<p>Occasionally though, we want to write a some SQL that isn’t well supported in
ActiveRecord, or is quite separate from the models we have in the database.</p>

<h2 id="recursive-ctes-in-postgresql">Recursive CTEs in Postgresql</h2>

<p>The <a href="https://www.postgresql.org/docs/current/queries-with.html#QUERIES-WITH-RECURSIVE">Postgresql docs on Recursive Queries</a>
give the following example of a SQL query that sums the integers from 1 through 100:</p>

<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">WITH</span> <span class="k">RECURSIVE</span> <span class="n">t</span><span class="p">(</span><span class="n">n</span><span class="p">)</span> <span class="k">AS</span> <span class="p">(</span>
    <span class="k">VALUES</span> <span class="p">(</span><span class="mi">1</span><span class="p">)</span>
  <span class="k">UNION</span> <span class="k">ALL</span>
    <span class="k">SELECT</span> <span class="n">n</span><span class="o">+</span><span class="mi">1</span> <span class="k">FROM</span> <span class="n">t</span> <span class="k">WHERE</span> <span class="n">n</span> <span class="o">&lt;</span> <span class="mi">100</span>
<span class="p">)</span>
<span class="k">SELECT</span> <span class="k">sum</span><span class="p">(</span><span class="n">n</span><span class="p">)</span> <span class="k">FROM</span> <span class="n">t</span><span class="p">;</span>
</code></pre></div></div>

<p>How would we express this in ActiveRecord? It doesn’t refer to any tables in
the database, and it uses a few bits of SQL which ActiveRecord hasn’t got great
support for (<code class="language-plaintext highlighter-rouge">WITH RECURSIVE</code>, <code class="language-plaintext highlighter-rouge">VALUES(1)</code>, <code class="language-plaintext highlighter-rouge">UNION ALL</code>).</p>

<p>ActiveRecord includes a lower-level library called Arel, which allows us to
manipulate the Abstract Syntax Tree more directly.</p>

<p>This includes the <code class="language-plaintext highlighter-rouge">Arel.sql()</code> method, which marks arbitrary strings as SQL
statements. So the easiest thing to do would be to just wrap the whole SQL
string with <code class="language-plaintext highlighter-rouge">Arel.sql()</code> and be done with it. That doesn’t compose well with
the rest of ActiveRecord though - it would be nice if we could mix and match
bits of custom SQL with ActiveRecord queries.</p>

<h2 id="recursive-ctes-in-activerecord--arel">Recursive CTEs in ActiveRecord / Arel</h2>

<p>We can use Arel to construct the syntax tree for the SQL in Postgresql’s docs.</p>

<p>First, we create a “table” reference called “t” - note that this isn’t actually
creating a table (and in fact there is no table in the database called “t” at
all), it’s just a way to refer to a table-like thing called “t”, like the one
we have in <code class="language-plaintext highlighter-rouge">WITH RECURSIVE t(n)</code>.</p>

<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">cte_table</span> <span class="o">=</span> <span class="no">Arel</span><span class="o">::</span><span class="no">Table</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="ss">:t</span><span class="p">)</span>
</code></pre></div></div>

<p>Next up we need the base case, which in SQL is <code class="language-plaintext highlighter-rouge">VALUES (1)</code>. The best way I can work out to do this is:</p>

<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">base_case</span> <span class="o">=</span> <span class="no">Arel</span><span class="o">::</span><span class="no">Nodes</span><span class="o">::</span><span class="no">ValuesList</span><span class="p">.</span><span class="nf">new</span><span class="p">([[</span><span class="mi">1</span><span class="p">]])</span>
</code></pre></div></div>

<p>Then we need the recursive case, <code class="language-plaintext highlighter-rouge">SELECT n+1 FROM t WHERE n &lt; 100</code> in SQL. We can refer to the <code class="language-plaintext highlighter-rouge">n</code> column on the <code class="language-plaintext highlighter-rouge">t</code> table reference using <code class="language-plaintext highlighter-rouge">cte_table[:n]</code>, leading to:</p>

<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">recursive_case</span> <span class="o">=</span> <span class="n">cte_table</span>
    <span class="p">.</span><span class="nf">project</span><span class="p">(</span><span class="n">cte_table</span><span class="p">[</span><span class="ss">:n</span><span class="p">]</span> <span class="o">+</span> <span class="mi">1</span><span class="p">)</span>
    <span class="p">.</span><span class="nf">where</span><span class="p">(</span><span class="n">cte_table</span><span class="p">[</span><span class="ss">:n</span><span class="p">].</span><span class="nf">lt</span><span class="p">(</span><span class="mi">100</span><span class="p">))</span>
</code></pre></div></div>

<p>Now we can create the CTE, which names itself <code class="language-plaintext highlighter-rouge">t(n)</code>, and is a <code class="language-plaintext highlighter-rouge">UNION ALL</code> between the base case and the recursive case:</p>

<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">cte_definition</span> <span class="o">=</span> <span class="no">Arel</span><span class="o">::</span><span class="no">Nodes</span><span class="o">::</span><span class="no">Cte</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span>
  <span class="no">Arel</span><span class="p">.</span><span class="nf">sql</span><span class="p">(</span><span class="s2">"t(n)"</span><span class="p">),</span>
  <span class="no">Arel</span><span class="o">::</span><span class="no">Nodes</span><span class="o">::</span><span class="no">UnionAll</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="n">base_case</span><span class="p">,</span> <span class="n">recursive_case</span><span class="p">),</span>
<span class="p">)</span>
</code></pre></div></div>

<p>(Note that we had to use <code class="language-plaintext highlighter-rouge">Arel.sql()</code> here - this isn’t very elegant, but I
couldn’t work out a way to get it to emit <code class="language-plaintext highlighter-rouge">t(n)</code> without wrapping it in quotes
like <code class="language-plaintext highlighter-rouge">"t(n)"</code> which doesn’t work).</p>

<p>Finally, we can put this all together with the <code class="language-plaintext highlighter-rouge">SELECT sum(n) FROM t</code> clause
and we have the full CTE:</p>

<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">cte</span> <span class="o">=</span> <span class="n">cte_table</span><span class="p">.</span><span class="nf">project</span><span class="p">(</span><span class="n">cte_table</span><span class="p">[</span><span class="ss">:n</span><span class="p">].</span><span class="nf">sum</span><span class="p">).</span><span class="nf">with</span><span class="p">(</span><span class="ss">:recursive</span><span class="p">,</span> <span class="n">cte_definition</span><span class="p">)</span>
</code></pre></div></div>

<p>Calling <code class="language-plaintext highlighter-rouge">cte.to_sql</code> gives us (almost) the exact SQL we wanted:</p>

<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">WITH</span> <span class="k">RECURSIVE</span> <span class="n">t</span><span class="p">(</span><span class="n">n</span><span class="p">)</span> <span class="k">AS</span> <span class="p">(</span> <span class="k">VALUES</span> <span class="p">(</span><span class="mi">1</span><span class="p">)</span> <span class="k">UNION</span> <span class="k">ALL</span> <span class="p">(</span><span class="k">SELECT</span> <span class="p">(</span><span class="nv">"t"</span><span class="p">.</span><span class="nv">"n"</span> <span class="o">+</span> <span class="mi">1</span><span class="p">)</span> <span class="k">FROM</span> <span class="nv">"t"</span> <span class="k">WHERE</span> <span class="nv">"t"</span><span class="p">.</span><span class="nv">"n"</span> <span class="o">&lt;</span> <span class="mi">100</span><span class="p">)</span> <span class="p">)</span> <span class="k">SELECT</span> <span class="k">SUM</span><span class="p">(</span><span class="nv">"t"</span><span class="p">.</span><span class="nv">"n"</span><span class="p">)</span> <span class="k">FROM</span> <span class="nv">"t"</span>
</code></pre></div></div>

<p>And running this against a Postgresql database with ActiveRecord:</p>

<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="no">ActiveRecord</span><span class="o">::</span><span class="no">Base</span><span class="p">.</span><span class="nf">connection</span><span class="p">.</span><span class="nf">execute</span><span class="p">(</span><span class="n">cte</span><span class="p">.</span><span class="nf">to_sql</span><span class="p">)</span>
</code></pre></div></div>

<p>Gives the answer - <code class="language-plaintext highlighter-rouge">{"sum"=&gt;5050}</code></p>

<p>All in all, a very convoluted way to do the equivalent of <code class="language-plaintext highlighter-rouge">(1..100).sum</code>.</p>

<p>(Or the more efficient <code class="language-plaintext highlighter-rouge">(100 * (1 + 100)) / 2</code> - thanks Carl Friedrich Gauss!)</p>

<h2 id="closing-thoughts">Closing thoughts</h2>

<p>The main reason to go to all this trouble is to be able to compose bits of
ActiveRecord with less well-supported SQL features, which can occasionally have
big performance advantages. Recursive CTEs can be very useful for traversing
tree and graph structures without having to make multiple calls to the
database, which in some situations can lead to big speed ups.</p>

<p>I’m not sure how stable the Arel library is - it’s possible it’s only really
intended to be used inside ActiveRecord, so this approach could be a bit of a
maintenance headache with ActiveRecord upgrades.</p>

<h2 id="prior-art--credits">Prior art / Credits</h2>

<p>I worked a lot of this out by following other people’s blog posts. In particular:</p>

<ul>
  <li><a href="https://medium.com/driven-by-code/6-useful-arel-techniques-for-composing-queries-544059e5f413">6 Useful Arel Techniques for Composing Queries</a></li>
  <li><a href="https://jpospisil.com/2014/06/16/the-definitive-guide-to-arel-the-sql-manager-for-ruby">The definitive guide to Arel, the SQL manager for Ruby</a></li>
  <li><a href="https://scimed.io/news/arel-part-ii-common-table-expressions-rails">Arel, part II: Common table expressions - rails</a></li>
</ul>

<p>… none of them quite made it clear how to implement the case in the docs
though, so here we are with another blog post.</p>]]></content><author><name>Richard Towers</name></author><category term="ruby" /><category term="postgres" /><category term="sql" /><summary type="html"><![CDATA[Using ActiveRecord / Arel to write the example recursive CTE from the Postgresql docs]]></summary></entry><entry><title type="html">Typescripting the technical interview</title><link href="https://richard-towers.com/2023/03/11/typescripting-the-technical-interview.html" rel="alternate" type="text/html" title="Typescripting the technical interview" /><published>2023-03-11T00:00:00+00:00</published><updated>2023-03-11T00:00:00+00:00</updated><id>https://richard-towers.com/2023/03/11/typescripting-the-technical-interview</id><content type="html" xml:base="https://richard-towers.com/2023/03/11/typescripting-the-technical-interview.html"><![CDATA[<h1 id="typescripting-the-technical-interview">Typescripting the technical interview</h1>

<p>An homage to Aphyr’s <a href="https://aphyr.com/posts/342-typing-the-technical-interview">Typing the technical interview</a></p>

<hr />

<p>Criss shows you into a meeting room.</p>

<p>Hoodie clad, and resembling no animal in particular, he seems familiar. Though you’re
confident you haven’t met. The room too, though this is your first visit.</p>

<p>“How’re you doing?” he asks</p>

<p>A difficult question to begin with, to explain what inner workings drive your
actions. Perhaps the question is rhetorical?</p>

<p>“How indeed”, you smile.</p>

<p>“… umm, great. Okay, shall we get into it?”</p>

<p>You nod, reassuringly.</p>

<p>“Right. So we’re going to do a little programming puzzle, so I can understand
how you solve problems. Don’t worry if you’re not able to complete the exercise,
the main thing is for me to get a feel for how you think and communicate.”</p>

<p>Worry? You struggle to recall the feeling. Perhaps in your youth, wintering in
Svalbard amongst the bears. Before you understood the seiðr.</p>

<p>“The problem is called N-Queens” Criss continues, “and it’s fairly simple. Given
an NxN chessboard, you need to find a way to place N queens on that board
safely. Try to keep your code concise.”</p>

<p>The trickle of déjà vu you were feeling becomes a flood. You <em>have</em> met Criss
before. In this room, solving this puzzle. But the memory won’t recall. Perhaps,
in a past life? But he’s far too young for that.</p>

<p>Pause a moment to compose yourself.</p>

<p>“Can I use any language?” you repeat.</p>

<p>“Well… we’re mostly a TypeScript shop. And we’ve had some …  unfortunate
experiences with people using other languages. A candidate used Haskell once,
and I did struggle a little bit to follow along. It wasn’t particularly concise.”</p>

<p>Ahhhhh. Vidrun has been here. That explains the déjà vu. You have shared much.</p>

<p>“TypeScript then”, you agree. Criss looks reassured. His doom is already set.</p>

<p>“We’ll need to start with a few runes”, you say.</p>

<p>Criss chips in immediately. “Runes, like in Go? Didn’t we <em>just</em> agree to use TypeScript?”</p>

<p>“Oh, no, not those kinds of runes. Runes, shadows of meaning. Symbolic. Unique.”</p>

<p>Inhale, inscribe.</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">ᚾ</span> <span class="o">=</span> <span class="nb">Symbol</span><span class="p">()</span>
<span class="kd">const</span> <span class="nx">ᛊ</span> <span class="o">=</span> <span class="nb">Symbol</span><span class="p">()</span>
<span class="kd">const</span> <span class="nx">ᛚ</span> <span class="o">=</span> <span class="nb">Symbol</span><span class="p">()</span>
<span class="kd">const</span> <span class="nx">ᛞ</span> <span class="o">=</span> <span class="nb">Symbol</span><span class="p">()</span>
</code></pre></div></div>

<p>“TypeScript is duck typed, you see. And one duck must not be confused for another.”</p>

<p>“You mean it’s structurally typed? Unlike something nominally typed like Java, or Haskell?”</p>

<p>“Yes, exactly”, you respond. Perhaps Criss is following after all. “Here, I’ll show you.”</p>

<p>Summon the void itself, and bind it with the essence of <code class="language-plaintext highlighter-rouge">ᚾ</code>. Need, nothingness, the frustrated longing to become.</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">type</span> <span class="nx">Nil</span> <span class="o">=</span> <span class="k">typeof</span> <span class="nx">ᚾ</span>
</code></pre></div></div>

<p>“Hold on, don’t we already have null and undefined? Why are we defining our own type for Nil?”</p>

<p>“The built in types come with too much baggage - null, undefined, falsey values. Better to start from a clean beginning.”</p>

<p>Smiling contentedly at the memory of Vidrun, complete the linked list.</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">type</span> <span class="nx">Cons</span><span class="o">&lt;</span><span class="nx">x</span><span class="p">,</span> <span class="nx">xs</span><span class="o">&gt;</span> <span class="o">=</span> <span class="p">[</span><span class="nx">x</span><span class="p">,</span> <span class="nx">xs</span><span class="p">]</span>
</code></pre></div></div>

<p>You hear Criss’ breath catch. Quickly, before he can escape.</p>

<p>“Let’s do booleans, to represent threat and safety”</p>

<p>Truth, fire, a sun. Bound with ᛊ. <br />
Lies, falsehood, the depths of a lake. Bound with ᛚ.</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">type</span> <span class="nx">True</span> <span class="o">=</span> <span class="k">typeof</span> <span class="nx">ᛊ</span>
<span class="kd">type</span> <span class="nx">False</span> <span class="o">=</span> <span class="k">typeof</span> <span class="nx">ᛚ</span>
</code></pre></div></div>

<p>“And some simple logic”</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">type</span> <span class="nx">Not</span><span class="o">&lt;</span><span class="nx">b1</span><span class="o">&gt;</span> <span class="o">=</span> <span class="nx">b1</span> <span class="kd">extends</span> <span class="nx">True</span> <span class="p">?</span> <span class="nx">False</span> <span class="p">:</span> <span class="nx">b1</span> <span class="kd">extends</span> <span class="nx">False</span> <span class="p">?</span> <span class="nx">True</span> <span class="p">:</span> <span class="nx">never</span>
<span class="kd">type</span> <span class="nx">Or</span><span class="o">&lt;</span><span class="nx">b1</span><span class="p">,</span> <span class="nx">b2</span><span class="o">&gt;</span> <span class="o">=</span> <span class="nx">b1</span> <span class="kd">extends</span> <span class="nx">True</span> <span class="p">?</span> <span class="nx">True</span> <span class="p">:</span> <span class="nx">b2</span> <span class="kd">extends</span> <span class="nx">True</span> <span class="p">?</span> <span class="nx">True</span> <span class="p">:</span> <span class="nx">False</span>
</code></pre></div></div>

<p>“… but why …” Criss begins, but doesn’t finish. Don’t worry. Continue.</p>

<p>Is there any truth? You sometimes wonder.</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">type</span> <span class="nx">AnyTrue</span><span class="o">&lt;</span><span class="nx">list</span><span class="o">&gt;</span> <span class="o">=</span> <span class="nx">list</span> <span class="kd">extends</span> <span class="nx">Cons</span><span class="o">&lt;</span><span class="nx">infer</span> <span class="nx">x</span><span class="p">,</span> <span class="nx">infer</span> <span class="nx">xs</span><span class="o">&gt;</span>
    <span class="p">?</span> <span class="nx">x</span> <span class="kd">extends</span> <span class="nx">True</span>
        <span class="p">?</span> <span class="nx">True</span>
        <span class="p">:</span> <span class="nx">AnyTrue</span><span class="o">&lt;</span><span class="nx">xs</span><span class="o">&gt;</span>
    <span class="p">:</span> <span class="nx">False</span>
</code></pre></div></div>

<p>“We’ll need to represent the coordinates of our queens, so we’ll need numbers too.”</p>

<p>Start by ensnaring zero. ᛞ, the day rune. Use a zero day and carry the zero.</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">type</span> <span class="nx">Zero</span> <span class="o">=</span> <span class="k">typeof</span> <span class="nx">ᛞ</span>
</code></pre></div></div>

<p>“Now we can define the rest of the natural numbers, building on zero.”</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">type</span> <span class="nx">S</span><span class="o">&lt;</span><span class="nx">n</span><span class="o">&gt;</span> <span class="o">=</span> <span class="p">[</span><span class="nx">n</span><span class="p">]</span>

<span class="kd">type</span> <span class="nx">One</span>   <span class="o">=</span> <span class="nx">S</span><span class="o">&lt;</span><span class="nx">Zero</span><span class="o">&gt;</span>
<span class="kd">type</span> <span class="nx">Two</span>   <span class="o">=</span> <span class="nx">S</span><span class="o">&lt;</span><span class="nx">One</span><span class="o">&gt;</span>
<span class="kd">type</span> <span class="nx">Three</span> <span class="o">=</span> <span class="nx">S</span><span class="o">&lt;</span><span class="nx">Two</span><span class="o">&gt;</span>
<span class="kd">type</span> <span class="nx">Four</span>  <span class="o">=</span> <span class="nx">S</span><span class="o">&lt;</span><span class="nx">Three</span><span class="o">&gt;</span>
<span class="kd">type</span> <span class="nx">Five</span>  <span class="o">=</span> <span class="nx">S</span><span class="o">&lt;</span><span class="nx">Four</span><span class="o">&gt;</span>
<span class="kd">type</span> <span class="nx">Six</span>   <span class="o">=</span> <span class="nx">S</span><span class="o">&lt;</span><span class="nx">Five</span><span class="o">&gt;</span>
<span class="kd">type</span> <span class="nx">Seven</span> <span class="o">=</span> <span class="nx">S</span><span class="o">&lt;</span><span class="nx">Six</span><span class="o">&gt;</span>
<span class="kd">type</span> <span class="nx">Eight</span> <span class="o">=</span> <span class="nx">S</span><span class="o">&lt;</span><span class="nx">Seven</span><span class="o">&gt;</span>
</code></pre></div></div>

<p>Criss is completely grey now. His breath begins to cloud, as the chill in the air intensifies. He knows what’s coming.</p>

<p>“We’ll need to be able to do a couple of operations on our numbers. Equality, difference.”</p>

<p>“Important concepts in a company like this.”, you add. Noting the distinct lack of the latter over the tundra of the open plan office.</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">type</span> <span class="nx">Equals</span><span class="o">&lt;</span><span class="nx">a</span><span class="p">,</span> <span class="nx">b</span><span class="o">&gt;</span> <span class="o">=</span> <span class="nx">a</span> <span class="kd">extends</span> <span class="nx">S</span><span class="o">&lt;</span><span class="nx">infer</span> <span class="nx">a_</span><span class="o">&gt;</span>
    <span class="p">?</span> <span class="nx">b</span> <span class="kd">extends</span> <span class="nx">S</span><span class="o">&lt;</span><span class="nx">infer</span> <span class="nx">b_</span><span class="o">&gt;</span>
        <span class="p">?</span> <span class="nx">Equals</span><span class="o">&lt;</span><span class="nx">a_</span><span class="p">,</span> <span class="nx">b_</span><span class="o">&gt;</span>
        <span class="p">:</span> <span class="nx">False</span>
    <span class="p">:</span> <span class="nx">b</span> <span class="kd">extends</span> <span class="nx">Zero</span>
        <span class="p">?</span> <span class="nx">True</span>
        <span class="p">:</span> <span class="nx">False</span>

<span class="kd">type</span> <span class="nx">AbsDiff</span><span class="o">&lt;</span><span class="nx">a</span><span class="p">,</span> <span class="nx">b</span><span class="o">&gt;</span> <span class="o">=</span> <span class="nx">a</span> <span class="kd">extends</span> <span class="nx">S</span><span class="o">&lt;</span><span class="nx">infer</span> <span class="nx">a_</span><span class="o">&gt;</span>
    <span class="p">?</span> <span class="nx">b</span> <span class="kd">extends</span> <span class="nx">S</span><span class="o">&lt;</span><span class="nx">infer</span> <span class="nx">b_</span><span class="o">&gt;</span>
        <span class="p">?</span> <span class="nx">AbsDiff</span><span class="o">&lt;</span><span class="nx">a_</span><span class="p">,</span> <span class="nx">b_</span><span class="o">&gt;</span>
        <span class="p">:</span> <span class="nx">a</span>
    <span class="p">:</span> <span class="nx">b</span> <span class="kd">extends</span> <span class="nx">S</span><span class="o">&lt;</span><span class="kr">any</span><span class="o">&gt;</span>
        <span class="p">?</span> <span class="nx">b</span>
        <span class="p">:</span> <span class="nx">Zero</span>
</code></pre></div></div>

<p>“And for convenience, let’s have a function to create a range from zero to n.”</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">type</span> <span class="nx">RangeFromZeroTo</span><span class="o">&lt;</span><span class="nx">n</span><span class="p">,</span> <span class="nx">xs</span> <span class="o">=</span> <span class="nx">Nil</span><span class="o">&gt;</span> <span class="o">=</span> <span class="nx">n</span> <span class="kd">extends</span> <span class="nx">S</span><span class="o">&lt;</span><span class="nx">infer</span> <span class="nx">n_</span><span class="o">&gt;</span>
    <span class="p">?</span> <span class="nx">RangeFromZeroTo</span><span class="o">&lt;</span><span class="nx">n_</span><span class="p">,</span> <span class="nx">Cons</span><span class="o">&lt;</span><span class="nx">n</span><span class="p">,</span> <span class="nx">xs</span><span class="o">&gt;&gt;</span>
    <span class="p">:</span> <span class="nx">Cons</span><span class="o">&lt;</span><span class="nx">Zero</span><span class="p">,</span> <span class="nx">xs</span><span class="o">&gt;</span>
</code></pre></div></div>

<p>“Criss! Criss, we’re ready now. It’s okay, we’re ready now.”</p>

<p>He’s mumbling something under his breath. “not again. not again.”</p>

<p>Define a queen. Any queen of your clan would need more depth than just x and y,
but keep it simple.</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">type</span> <span class="nx">Queen</span><span class="o">&lt;</span><span class="nx">x</span><span class="p">,</span> <span class="nx">y</span><span class="o">&gt;</span> <span class="o">=</span> <span class="p">[</span><span class="nx">x</span><span class="p">,</span> <span class="nx">y</span><span class="p">]</span>
</code></pre></div></div>

<p>Create a row of queens. Too threatening to be on the same row, at the same time,
in the same universe. They will have to be divided across the multiverse. This
too has happened, in the history of your kind.</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">type</span> <span class="nx">RowOfQueens</span><span class="o">&lt;</span><span class="nx">cols</span><span class="p">,</span> <span class="nx">row</span><span class="o">&gt;</span> <span class="o">=</span> <span class="nx">cols</span> <span class="kd">extends</span> <span class="nx">Cons</span><span class="o">&lt;</span><span class="nx">infer</span> <span class="nx">col</span><span class="p">,</span> <span class="nx">infer</span> <span class="nx">cols_</span><span class="o">&gt;</span>
    <span class="p">?</span> <span class="nx">Cons</span><span class="o">&lt;</span><span class="nx">Queen</span><span class="o">&lt;</span><span class="nx">row</span><span class="p">,</span> <span class="nx">col</span><span class="o">&gt;</span><span class="p">,</span> <span class="nx">RowOfQueens</span><span class="o">&lt;</span><span class="nx">cols_</span><span class="p">,</span> <span class="nx">row</span><span class="o">&gt;&gt;</span>
    <span class="p">:</span> <span class="nx">cols</span>
</code></pre></div></div>

<p>“A queen threatens another if she is in the same row, column, or diagonal.”</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">type</span> <span class="nx">Threatens</span><span class="o">&lt;</span><span class="nx">a</span><span class="p">,</span> <span class="nx">b</span><span class="o">&gt;</span> <span class="o">=</span> <span class="nx">a</span> <span class="kd">extends</span> <span class="nx">Queen</span><span class="o">&lt;</span><span class="nx">infer</span> <span class="nx">ax</span><span class="p">,</span> <span class="nx">infer</span> <span class="nx">ay</span><span class="o">&gt;</span>
    <span class="p">?</span> <span class="nx">b</span> <span class="kd">extends</span> <span class="nx">Queen</span><span class="o">&lt;</span><span class="nx">infer</span> <span class="nx">bx</span><span class="p">,</span> <span class="nx">infer</span> <span class="nx">by</span><span class="o">&gt;</span>
        <span class="p">?</span> <span class="nx">Or</span><span class="o">&lt;</span>
            <span class="nx">Or</span><span class="o">&lt;</span><span class="nx">Equals</span><span class="o">&lt;</span><span class="nx">ax</span><span class="p">,</span> <span class="nx">bx</span><span class="o">&gt;</span><span class="p">,</span> <span class="nx">Equals</span><span class="o">&lt;</span><span class="nx">ay</span><span class="p">,</span> <span class="nx">by</span><span class="o">&gt;&gt;</span><span class="p">,</span>
            <span class="nx">Equals</span><span class="o">&lt;</span><span class="nx">AbsDiff</span><span class="o">&lt;</span><span class="nx">ax</span><span class="p">,</span> <span class="nx">bx</span><span class="o">&gt;</span><span class="p">,</span> <span class="nx">AbsDiff</span><span class="o">&lt;</span><span class="nx">ay</span><span class="p">,</span> <span class="nx">by</span><span class="o">&gt;&gt;</span>
        <span class="o">&gt;</span>
        <span class="p">:</span> <span class="nx">never</span> <span class="p">:</span> <span class="nx">never</span>
</code></pre></div></div>

<p>“never… never…” whimpers Criss. You’re not sure if this is a question about
the code, but explain:</p>

<p>“Threatens shouldn’t be called with anything that isn’t a Queen. We’re using 
<code class="language-plaintext highlighter-rouge">never</code> to represent the exception where it’s called with the wrong types.”</p>

<p>You’ve swam upon the devil’s lake, but never, never, never, never. You’ll never
make the same mistake. No, never, never, never.</p>

<p>“We’re going to work our way down the rows of the chess board, placing queens
wherever possible. Given a list of already placed queens, which ones would
threaten a new queen?”</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">type</span> <span class="nx">ThreateningQueens</span><span class="o">&lt;</span><span class="nx">placedQueens</span><span class="p">,</span> <span class="nx">queen</span><span class="o">&gt;</span> <span class="o">=</span>
    <span class="nx">placedQueens</span> <span class="kd">extends</span> <span class="nx">Cons</span><span class="o">&lt;</span><span class="nx">infer</span> <span class="nx">placedQueen</span><span class="p">,</span> <span class="nx">infer</span> <span class="nx">placedQueens_</span><span class="o">&gt;</span>
        <span class="p">?</span> <span class="nx">Cons</span><span class="o">&lt;</span>
            <span class="nx">Threatens</span><span class="o">&lt;</span><span class="nx">placedQueen</span><span class="p">,</span> <span class="nx">queen</span><span class="o">&gt;</span><span class="p">,</span>
            <span class="nx">ThreateningQueens</span><span class="o">&lt;</span><span class="nx">placedQueens_</span><span class="p">,</span> <span class="nx">queen</span><span class="o">&gt;</span>
        <span class="o">&gt;</span>
        <span class="p">:</span> <span class="nx">Nil</span>
</code></pre></div></div>

<p>“A queen is safe only if none of the other queens are threatening her.”</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">type</span> <span class="nx">Safe</span><span class="o">&lt;</span><span class="nx">placedQueens</span><span class="p">,</span> <span class="nx">queen</span><span class="o">&gt;</span> <span class="o">=</span>
    <span class="nx">Not</span><span class="o">&lt;</span><span class="nx">AnyTrue</span><span class="o">&lt;</span><span class="nx">ThreateningQueens</span><span class="o">&lt;</span><span class="nx">placedQueens</span><span class="p">,</span> <span class="nx">queen</span><span class="o">&gt;&gt;&gt;</span>
</code></pre></div></div>

<p>“Now taking a list of possible queens, which ones would be safe from the queens already on the board?”</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">type</span> <span class="nx">FilterSafeQueens</span><span class="o">&lt;</span><span class="nx">candidates</span><span class="p">,</span> <span class="nx">placedQueens</span><span class="o">&gt;</span> <span class="o">=</span>
    <span class="nx">candidates</span> <span class="kd">extends</span> <span class="nx">Cons</span><span class="o">&lt;</span><span class="nx">infer</span> <span class="nx">q</span><span class="p">,</span> <span class="nx">infer</span> <span class="nx">qs</span><span class="o">&gt;</span>
        <span class="p">?</span> <span class="nx">Safe</span><span class="o">&lt;</span><span class="nx">placedQueens</span><span class="p">,</span> <span class="nx">q</span><span class="o">&gt;</span> <span class="kd">extends</span> <span class="nx">True</span>
            <span class="p">?</span> <span class="nx">Cons</span><span class="o">&lt;</span><span class="nx">q</span><span class="p">,</span> <span class="nx">FilterSafeQueens</span><span class="o">&lt;</span><span class="nx">qs</span><span class="p">,</span> <span class="nx">placedQueens</span><span class="o">&gt;&gt;</span>
            <span class="p">:</span> <span class="nx">FilterSafeQueens</span><span class="o">&lt;</span><span class="nx">qs</span><span class="p">,</span> <span class="nx">placedQueens</span><span class="o">&gt;</span>
        <span class="p">:</span> <span class="nx">Nil</span>
</code></pre></div></div>

<p>“As we work down the board, we only need to consider the positions in the next row where a queen would be safe.”</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">type</span> <span class="nx">Next</span><span class="o">&lt;</span><span class="nx">row</span><span class="p">,</span> <span class="nx">placedQueens</span> <span class="o">=</span> <span class="nx">Nil</span><span class="o">&gt;</span> <span class="o">=</span>
    <span class="nx">FilterSafeQueens</span><span class="o">&lt;</span><span class="nx">RowOfQueens</span><span class="o">&lt;</span><span class="nx">RangeFromZeroTo</span><span class="o">&lt;</span><span class="nx">N</span><span class="o">&gt;</span><span class="p">,</span> <span class="nx">row</span><span class="o">&gt;</span><span class="p">,</span> <span class="nx">placedQueens</span><span class="o">&gt;</span>
</code></pre></div></div>

<p>“Criss, are you ready?”</p>

<p>His eyelashes are coated with frost, framing a beautiful pair of eyes. Beautiful, but devoid of focus.</p>

<p>“A pair of mutually recursive functions to find the solution.”</p>

<p>Two lovers, they waltz. Not every step forwards, but backtracking, spinning, gently alighting on the answer at just the right moment.</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">type</span> <span class="nx">SolveNextRow</span><span class="o">&lt;</span><span class="nx">row</span><span class="p">,</span> <span class="nx">placedQueens</span><span class="o">&gt;</span> <span class="o">=</span>
    <span class="nx">Solve</span><span class="o">&lt;</span><span class="nx">Next</span><span class="o">&lt;</span><span class="nx">S</span><span class="o">&lt;</span><span class="nx">row</span><span class="o">&gt;</span><span class="p">,</span> <span class="nx">placedQueens</span><span class="o">&gt;</span><span class="p">,</span> <span class="nx">S</span><span class="o">&lt;</span><span class="nx">row</span><span class="o">&gt;</span><span class="p">,</span> <span class="nx">placedQueens</span><span class="o">&gt;</span>

<span class="kd">type</span> <span class="nx">Solve</span><span class="o">&lt;</span><span class="nx">candidates</span><span class="p">,</span> <span class="nx">row</span><span class="p">,</span> <span class="nx">placedQueens</span><span class="o">&gt;</span> <span class="o">=</span> <span class="nx">Equals</span><span class="o">&lt;</span><span class="nx">row</span><span class="p">,</span> <span class="nx">N</span><span class="o">&gt;</span> <span class="kd">extends</span> <span class="nx">True</span>
    <span class="p">?</span> <span class="nx">candidates</span> <span class="kd">extends</span> <span class="nx">Cons</span><span class="o">&lt;</span><span class="nx">infer</span> <span class="nx">x</span><span class="p">,</span> <span class="kr">any</span><span class="o">&gt;</span>
        <span class="p">?</span> <span class="nx">Cons</span><span class="o">&lt;</span><span class="nx">x</span><span class="p">,</span> <span class="nx">placedQueens</span><span class="o">&gt;</span>
        <span class="p">:</span> <span class="nx">Nil</span>
    <span class="p">:</span> <span class="nx">candidates</span> <span class="kd">extends</span> <span class="nx">Cons</span><span class="o">&lt;</span><span class="nx">infer</span> <span class="nx">x</span><span class="p">,</span> <span class="nx">infer</span> <span class="nx">xs</span><span class="o">&gt;</span>
        <span class="p">?</span> <span class="nx">SolveNextRow</span><span class="o">&lt;</span><span class="nx">row</span><span class="p">,</span> <span class="nx">Cons</span><span class="o">&lt;</span><span class="nx">x</span><span class="p">,</span> <span class="nx">placedQueens</span><span class="o">&gt;&gt;</span> <span class="kd">extends</span> <span class="nx">Nil</span>
            <span class="p">?</span> <span class="nx">Solve</span><span class="o">&lt;</span><span class="nx">xs</span><span class="p">,</span> <span class="nx">row</span><span class="p">,</span> <span class="nx">placedQueens</span><span class="o">&gt;</span>
            <span class="p">:</span> <span class="nx">SolveNextRow</span><span class="o">&lt;</span><span class="nx">row</span><span class="p">,</span> <span class="nx">Cons</span><span class="o">&lt;</span><span class="nx">x</span><span class="p">,</span> <span class="nx">placedQueens</span><span class="o">&gt;&gt;</span>
        <span class="p">:</span> <span class="nx">Nil</span>
</code></pre></div></div>

<p>“Criss” you say gently “the solution”</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// We get one queen for free at Zero, so:</span>
<span class="kd">type</span> <span class="nx">N</span> <span class="o">=</span> <span class="nx">Seven</span>
<span class="kd">type</span> <span class="nx">Solution</span> <span class="o">=</span> <span class="nx">Solve</span><span class="o">&lt;</span><span class="nx">Next</span><span class="o">&lt;</span><span class="nx">Zero</span><span class="o">&gt;</span><span class="p">,</span> <span class="nx">Zero</span><span class="p">,</span> <span class="nx">Nil</span><span class="o">&gt;</span>
</code></pre></div></div>

<p>A mouse flits across the Solution, and the language server renders:</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">Cons</span><span class="o">&lt;</span>
  <span class="nx">Queen</span><span class="o">&lt;</span><span class="nx">S</span><span class="o">&lt;</span><span class="nx">S</span><span class="o">&lt;</span><span class="nx">S</span><span class="o">&lt;</span><span class="nx">S</span><span class="o">&lt;</span><span class="nx">S</span><span class="o">&lt;</span><span class="nx">S</span><span class="o">&lt;</span><span class="nx">S</span><span class="o">&lt;</span><span class="nx">unique</span> <span class="nx">symbol</span><span class="o">&gt;&gt;&gt;&gt;&gt;&gt;&gt;</span><span class="p">,</span> <span class="nx">Three</span><span class="o">&gt;</span><span class="p">,</span>
  <span class="nx">Cons</span><span class="o">&lt;</span>
    <span class="nx">Queen</span><span class="o">&lt;</span><span class="nx">S</span><span class="o">&lt;</span><span class="nx">S</span><span class="o">&lt;</span><span class="nx">S</span><span class="o">&lt;</span><span class="nx">S</span><span class="o">&lt;</span><span class="nx">S</span><span class="o">&lt;</span><span class="nx">S</span><span class="o">&lt;</span><span class="nx">unique</span> <span class="nx">symbol</span><span class="o">&gt;&gt;&gt;&gt;&gt;&gt;</span><span class="p">,</span> <span class="nx">One</span><span class="o">&gt;</span><span class="p">,</span>
    <span class="nx">Cons</span><span class="o">&lt;</span>
      <span class="nx">Queen</span><span class="o">&lt;</span><span class="nx">S</span><span class="o">&lt;</span><span class="nx">S</span><span class="o">&lt;</span><span class="nx">S</span><span class="o">&lt;</span><span class="nx">S</span><span class="o">&lt;</span><span class="nx">S</span><span class="o">&lt;</span><span class="nx">unique</span> <span class="nx">symbol</span><span class="o">&gt;&gt;&gt;&gt;&gt;</span><span class="p">,</span> <span class="nx">Six</span><span class="o">&gt;</span><span class="p">,</span>
      <span class="nx">Cons</span><span class="o">&lt;</span>
        <span class="nx">Queen</span><span class="o">&lt;</span><span class="nx">S</span><span class="o">&lt;</span><span class="nx">S</span><span class="o">&lt;</span><span class="nx">S</span><span class="o">&lt;</span><span class="nx">S</span><span class="o">&lt;</span><span class="nx">unique</span> <span class="nx">symbol</span><span class="o">&gt;&gt;&gt;&gt;</span><span class="p">,</span> <span class="nx">Two</span><span class="o">&gt;</span><span class="p">,</span>
        <span class="nx">Cons</span><span class="o">&lt;</span>
          <span class="nx">Queen</span><span class="o">&lt;</span><span class="nx">S</span><span class="o">&lt;</span><span class="nx">S</span><span class="o">&lt;</span><span class="nx">S</span><span class="o">&lt;</span><span class="nx">unique</span> <span class="nx">symbol</span><span class="o">&gt;&gt;&gt;</span><span class="p">,</span> <span class="nx">Five</span><span class="o">&gt;</span><span class="p">,</span>
          <span class="nx">Cons</span><span class="o">&lt;</span>
            <span class="nx">Queen</span><span class="o">&lt;</span><span class="nx">S</span><span class="o">&lt;</span><span class="nx">S</span><span class="o">&lt;</span><span class="nx">unique</span> <span class="nx">symbol</span><span class="o">&gt;&gt;</span><span class="p">,</span> <span class="nx">Seven</span><span class="o">&gt;</span><span class="p">,</span>
            <span class="nx">Cons</span><span class="o">&lt;</span>
              <span class="nx">Queen</span><span class="o">&lt;</span><span class="nx">S</span><span class="o">&lt;</span><span class="nx">unique</span> <span class="nx">symbol</span><span class="o">&gt;</span><span class="p">,</span> <span class="nx">Four</span><span class="o">&gt;</span><span class="p">,</span>
              <span class="nx">Cons</span><span class="o">&lt;</span>
                <span class="nx">Queen</span><span class="o">&lt;</span><span class="nx">unique</span> <span class="nx">symbol</span><span class="p">,</span> <span class="nx">unique</span> <span class="nx">symbol</span><span class="o">&gt;</span><span class="p">,</span> <span class="nx">unique</span> <span class="nx">symbol</span><span class="o">&gt;</span>
              <span class="o">&gt;</span>
            <span class="o">&gt;</span>
          <span class="o">&gt;</span>
        <span class="o">&gt;</span>
      <span class="o">&gt;</span>
    <span class="o">&gt;</span>
  <span class="o">&gt;</span>
</code></pre></div></div>

<p>“Our chessboard is one indexed, not zero indexed, so that’s queens at a1, b5, c8, d6, e3, f7, g2, and h4. Does that work Criss?”</p>

<p>You sketch the solution on the whiteboard.</p>

<p><img src="/static/images/8-queens-solution.gif" alt="A chess board, showing 8 queens placed in positions where they don't threaten each other. Queens are in a1, b5, c8, d6, e3, f7, g2, and h4." /></p>

<p>As warmth begins to return to the room, Criss starts to recover.</p>

<p>“Okay, that looks like a correct solution, but the code is quite hard to follow, and it’s not very concise.” he asserts, wrongly.</p>

<p>“Oh it’s mostly just TypeScript boilerplate. I think you’ll find once it’s compiled down to JavaScript it’s perfectly concise.” you reassure him.</p>

<p>Invoke the compiler</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>tsc <span class="k">*</span>.ts <span class="nt">--lib</span> esnext <span class="nt">--outFile</span> /dev/stdout 
var ᚾ <span class="o">=</span> Symbol<span class="o">()</span><span class="p">;</span>
var ᛊ <span class="o">=</span> Symbol<span class="o">()</span><span class="p">;</span>
var ᛚ <span class="o">=</span> Symbol<span class="o">()</span><span class="p">;</span>
var ᛞ <span class="o">=</span> Symbol<span class="o">()</span><span class="p">;</span>
</code></pre></div></div>

<p>Criss looks like he’s been slapped. You leave him to it and show yourself out.</p>

<hr />

<h2 id="links">Links</h2>

<ul>
  <li><a href="https://www.typescriptlang.org/play?#code/PTAEEkDsAsEMBsCmAaUBLSBnAxgJzQEaIB0AsAFDYD2WALqIH1ooAvKAMoCeAtgVfABQBKCtTqhAU2gt23XgOGUameoC20KZx58hIxfUB7aGpmb5FEOwCuXLjVC1oiUADcqaACbpamRPABmqWJDcCDDc0egB3UOgbO1BETE9IbHsqb1AAAwY04lAAOUREF1RIKlsMAHNIOMxUW3tvXDMlXFhaAtB4GjLymypQImouEgpaDgAHexy0eCkR8ZTGChMwNi4p7tFWyFaXeA5QFuj7QatcPfmANVcGyFQB0aRWw-aMAGs2+DQlMnJZ+wBhRQAHgAHqhgZgAHxSADaoNA4IAuotyKYACoNWyobxoXAofagTBmSDZABCVCJbgitgk31MABk0HEsQhPNAqFRCk8XIhRrZMKB5rB2rA3qTyQFQFSospvr9QOizPZWL95uJhmN7AAxFlKmwa+bKZFynIlQEEACMUNYFtiwM2Ln5CvsAH5QNr4J5QAAuPrm232-nuz2up3e0CVByIXDq8agADyuDN5tQBAATFbff7EAFHQ0XfK82G01mcwXFaAQ4WfUHEMjTOB+bVcftIHtaBjoK6AJrkglUQa0NCDflhGjc3CyjWgACCradgI+Sgzi-oiDt2YdoABWEBGG8UfhqD3B-BUNdwJLm9DlfLPtnHHnp7DNbry1osFw9AIe2zmEgH+6AAvKMqGyXQahiFxYD2a4SFAABVT0hWA3BeigvZ-DcbAP1OJ4UNAmN7AALRAmZ9VSXQjSnNhAUgDNoUgJFyEI+NKlAdjWBokjUIhFjUVHdi1EBONKl4n4p1RaBcV1Gj+KoMS5U1clcA49hAUk6SFKnTU0EjVSaKUsxcC02M2DQC99MBHTIxM+w2EQSNICEszgTEpZQAAUQARzMBBQg4VAXDQbx91xRI4PALhRioT9-HoUQkl5fkMHxO5-D2D43miT5J1jbzfI9QFYBTDMhTXAM1OPFTYAAfTEwSKz6S9+Roqq+jqigGoa118pZIqapTDryC6wTqx1TrRqa8qN35biqAmkab1rYaRufcbmPE2NpwITAABFgu8IqSqkMr11LVrIFC-Yhu6qazs3C6roIG7Vtdba9oO-rBvq1afVgBai2atT0p+xa+gBhqfTmqjYwAJX8MpEE1VCuDm1EqFosF+VYSZ4AzJzpvO3dLoPSAXvY114cgRHkf7NGMbJ1Bt0wTH4UhUGw2ZwE5qxtyUTAXbEGxNihR8-JiRnVtQDF7MBVSDhlNAbB4H8SVyXgNxKjaE57G5XkolsVWACtGnoC9MNAAL3IIMx6DeHl3AJId7iGTb7AARUVbMQVQDh6LhDgmPcv5cRaewhVQsI5elr2sGydHelsUPNm6WheiIAUnNqAlYEGUBI78ehs8wXP7EHQZkHclLi9L0AiV0qNPHjuw9gieBpjgPS076XXdNcNpYDwKh4ieLgzHgQdI1wJv5WgT53LT3o4H5OBRnGSpOWrmI56UGKznlxWXmCXL7FhqgwjjbxPfFlnqA9VBI4zO-+UJzcubau+jxJlTn-Jxquevt7AuSs+AQlQGfC+V9Y63z4JgAa+dz4Qg5j6Z+r5QA9jMAAcj0pgMIuc67RSzpBByUxMH8hVm8FMttwwOSjEUWhuB6FTyYVGbIGDMHtxoVPdyXARRlxiCXPOqwlB8OyCaFhjCuF0KkROPiUlEBhx3MVPopUgaAMgMTK6sA4RtWgmeO6FV1GaIPAQHR38+h+wBq6BMwlEy9UKtolMrlUD2JZtBFMfswGeR8n1d6+0QpFThKYrxfjPruIsUg5BMiwwRijDDewGkFEp2puolm9xB4FFSagGWdEpDpKSC4VJQN37mPyZk2OX8rplMKdAv+rouaJMUWklWBT1HZNjl4xpySyipMBNU1J8CclIIBj6XGLE2CwH3H0lp5Sb7tPFhmE0tBAT3nnF07M5Ren9OgfM7MkT4luimK0XAEz9y9OwgEVwYdqigG2TfJ+mErmtBfvdfkJSrpeUqQeLykIrHsEmYgaZGSalzOllCV+uZFQQ0EvUoEnzDkTyjKcxAvSfmoDub+YZK0RrViOUigFqKbkYqwFEsZG1jTlUBMA4l2NchTAzDpRFJyCXQMBBAy+vSqY0xRvTQEOQvGP3RTMkFmK0FyXaFQKeNzaitwQLQQCYiSixCnnsJQDtvAxTwbgB0VCvyDxeO2fV5RUCYFGBgSAxrQCIy2LsfYHwyjQEHNTTOTx-C4IPAcE2Sgnj4AdfQKwgwtgn3YHwSMeQ7QQKpefIVwLUkZjYKGwF4blk0UFbc4VcbUCpsQTG1p0DbIhvgJGQEFygpQWeQ-aN6bY35qkK4qNYRUD8qBk6P5panlxGKUCNqcIQbQopluIEcIaUc0hnS+AIylaPPLZ2iFg6dw9q+SpU8-bGoJqLYgZNkbgFc2HRm-N4LXnjtXbdddxbwSVsbdWvN9yT2TTPZu8q26q27tzbMzFo7JpkvcpJewmB1aDhoCxHIahzLjL4LbNA1hOKJr5ZSuaXiebjr5qYJSP8aCOUZOFVA5VmjYCLjEaKmBQhQacvMbOwQ+4uAKjHG+LEoDcmBAuT4tAijLhY12hd5izFXRXdixqBMj2PVJn-W6DG1wghuWTT97EfTAknd+t20sAAMUhxNMfXZBmgqAEMsS8n6Vg6nASacAzcViiAC1eVTGpgIEmTOkdQHJSzABmGzjHjMQdM45+RFm9MABY3N2c8w5t0ylLMAFZAsaeC9pw5Nk9MADYosefgFpszLkIRAA">Explore the code in the TypeScript Playground</a></li>
  <li><a href="https://hachyderm.io/@richardTowers/110010654345838168">Discuss this post on Mastodon</a></li>
</ul>

<h2 id="errata">Errata</h2>

<ul>
  <li>2023-03-13 - There was a bug in the base case of the Solve “function”, pointed out by <a href="https://news.ycombinator.com/item?id=35134174">jtinder on Hacker news</a>.</li>
  <li>2023-03-13 - The solution was missing a trailing <code class="language-plaintext highlighter-rouge">Nil</code>, pointed out by <a href="https://g0v.social/@gholk/110016933349353417">@gholk on Mastodon</a></li>
  <li>2025-05-23 - An earlier version of this post stopped at a 7x7 chessboard, because of recursion limits in the TypeScript compiler. The new Go implementation of the typescript compiler has a much deeper limit though, and the solution works up to a 10x10 chessboard now. Updated the post to an 8x8 because that’s the classic.</li>
</ul>]]></content><author><name>Richard Towers</name></author><category term="typescript" /><summary type="html"><![CDATA[An homage to Aphyr's Typing the technical interview]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://richard-towers.com/static/images/8-queens-solution.gif" /><media:content medium="image" url="https://richard-towers.com/static/images/8-queens-solution.gif" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Finding the longest matching prefix in SQL</title><link href="https://richard-towers.com/2023/01/29/finding-the-longest-matching-prefix-in-sql.html" rel="alternate" type="text/html" title="Finding the longest matching prefix in SQL" /><published>2023-01-29T00:00:00+00:00</published><updated>2023-01-29T00:00:00+00:00</updated><id>https://richard-towers.com/2023/01/29/finding-the-longest-matching-prefix-in-sql</id><content type="html" xml:base="https://richard-towers.com/2023/01/29/finding-the-longest-matching-prefix-in-sql.html"><![CDATA[<h1 id="finding-the-longest-matching-prefix-in-sql">Finding the longest matching prefix in SQL</h1>

<p><a href="#the-solution">Skip to the solution</a></p>

<figure>
  <img class="fixed-width-20" alt="Photograph of a large deciduous tree in Richmond Park. The photographer, Simon Wilkes, commented: Perhaps one the best vantage points in Richmond Park, London. This bench is perfectly placed beneath a lonely tree, giving a clear view of the open fields and woodlands in every direction. In the early morning mist there’s a certain melancholy about the place - you could sit and ponder life here, without a care in the world." src="/static/images/simon-wilkes-S297j2CsdlM-unsplash.jpg" width="320" style="aspect-ratio: 3/2;" />
  <figcaption>Photo by <a href="https://unsplash.com/@simonfromengland?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Simon Wilkes</a> on <a href="https://unsplash.com/images/nature/tree?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Unsplash</a>
</figcaption>
</figure>

<p>We’ve got this fancy application at work - <a href="https://github.com/alphagov/router">router</a>. Router uses a data structure called a <a href="https://en.wikipedia.org/wiki/Trie">trie</a> to efficiently search a list of strings and find the one that is the longest prefix of some input.</p>

<p>For example, if I searched for <code class="language-plaintext highlighter-rouge">/apply-for-a-licence/temporary-event-notice/east-hertfordshire/apply-1</code>, router could very quickly tell me that the longest prefix in its database is <code class="language-plaintext highlighter-rouge">/apply-for-a-licence</code>. As <a href="https://technology.blog.gov.uk/2013/12/05/building-a-new-router-for-gov-uk/">this GDS’ blog post from 2013 says</a>, the trie data structure is a natural choice for this task.</p>

<p>But what if we wanted to accomplish the same thing in SQL, in a database which doesn’t come with a trie data structure?</p>

<h2 id="the-perils-of-stackexchange">The perils of stackexchange</h2>

<p>Obviously the first thing to do is to Google it. For me, the most promising result was <a href="https://dba.stackexchange.com/questions/43415/algorithm-for-finding-the-longest-prefix">this dba.stackexchange.com question</a>, which asks for an “Algorithm for finding the longest prefix”.</p>

<p>The example is a tiny bit more complicated than ours, but it looks similar enough. This looks like it should have a good answer.</p>

<p>When I first found this question there were three answers. <a href="https://dba.stackexchange.com/a/43444/268010">The top answer</a> had 22 upvotes and was marked as the best answer, the other two answers had one and zero upvotes each. So obviously the top answer is the place to focus.</p>

<p>But… oh boy is this complicated. The “simple” solution uses <code class="language-plaintext highlighter-rouge">DISTINCT ON</code>, a postgres specific extension, and is acknowledged to be very slow without an index. Next we’re into trigram indexes, but whether we use GIST or GIN, the query is still slow - taking around 1 second. The answer goes through a bunch of options - <code class="language-plaintext highlighter-rouge">text_pattern_ops</code>, “partial functional indexes”, recursive CTEs, explicitly laid out recursion with <code class="language-plaintext highlighter-rouge">UNION ALL</code>, SQL funtions, and finally dynamic SQL.</p>

<p>I know enough SQL to be dangerous, but at this point my eyes glazed over and I started to think that this problem was probably just too hard.</p>

<p>Maybe a relational database is just not the right solution to this problem?</p>

<h2 id="a-relational-database-is-always-the-right-solution-to-the-problem">A relational database is always the right solution to the problem</h2>

<p>Later on, in the shower (always the best place to get thinking done), the problem popped back into my head.</p>

<p>What if we sorted all the possible prefixes we know about, and then inserted the search key into that table?</p>

<p>If there’s a matching prefix, the prefix would always come immediately before the thing we’re searching for if they’re sorted alphabetically. Something like:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>  /apply-first-provisional-driving-licence
  /apply-for-a-digital-tachograph-company-smart-card
  /apply-for-a-digital-tachograph-driver-smart-card
👀/apply-for-a-licence &lt;--- 👀 The longest matching prefix
✨/apply-for-a-licence/temporary-event-notice/east-hertfordshire/apply-1 &lt;--- ✨ The thing we're searching for
  /apply-for-a-patent
  /apply-for-a-thames-accommodation-licence
</code></pre></div></div>

<p>The default data structure that postgres uses for its indexes is the <a href="https://en.wikipedia.org/wiki/B-tree">B-Tree</a>. B-Trees maintain a sorted order, so shouldn’t the database be able to work out an efficient way to do this? So long as we give it the right query?</p>

<h2 id="initial-attempts">Initial attempts</h2>

<p>Taking a really simple example where we’ve just got one column with all our path prefixes in:</p>

<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">create</span> <span class="k">table</span> <span class="n">routes</span><span class="p">(</span><span class="n">path</span> <span class="nb">text</span><span class="p">);</span>
<span class="k">create</span> <span class="k">index</span> <span class="n">routes_index</span> <span class="k">on</span> <span class="n">routes</span> <span class="p">(</span><span class="n">path</span><span class="p">);</span>
</code></pre></div></div>

<p>The first thing I tried was basically the same as the approach I sketched out above - find the item that would appear just before this item in the list:</p>

<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">select</span> <span class="n">path</span> <span class="k">from</span> <span class="n">routes</span>
<span class="k">where</span> <span class="s1">'/apply-for-a-licence/temporary-event-notice/east-hertfordshire/apply-1'</span> <span class="o">&gt;=</span> <span class="n">path</span>
<span class="k">order</span> <span class="k">by</span> <span class="n">path</span> <span class="k">desc</span>
<span class="k">limit</span> <span class="mi">1</span><span class="p">;</span>
</code></pre></div></div>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>/apply-for-a-licence
</code></pre></div></div>

<p>Which works! “Perfectly”…</p>

<p>It’s also efficient!</p>

<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">explain</span> <span class="k">analyze</span> <span class="k">select</span> <span class="n">path</span> <span class="k">from</span> <span class="n">routes</span>
<span class="k">where</span> <span class="s1">'/apply-for-a-licence/temporary-event-notice/east-hertfordshire/apply-1'</span> <span class="o">&gt;=</span> <span class="n">path</span>
<span class="k">order</span> <span class="k">by</span> <span class="n">path</span> <span class="k">desc</span>
<span class="k">limit</span> <span class="mi">1</span><span class="p">;</span>
</code></pre></div></div>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Limit (cost=0.28..0.42 rows=1 width=32) (actual time=0.036..0.037 rows=1 loops=1)
-&gt; Index Only Scan Backward using routes_index on routes (cost=0.28..303.96 rows=2267 width=32) (actual time=0.034..0.035 rows=1 loops=1)
Index Cond: (path &lt;= '/apply-for-a-licence/temporary-event-notice/east-hertfordshire/apply-1'::text)
Heap Fetches: 1
Planning Time: 0.329 ms
Execution Time: 0.056 ms
</code></pre></div></div>

<p>That “Index Only Scan” is what we want to see. Postgres can answer this query using the index only, which is very fast.</p>

<p>Unfortunately, this query will produce confusing / wrong results whenever you search for something which <em>doesn’t</em> have a matching prefix:</p>

<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">select</span> <span class="n">path</span> <span class="k">from</span> <span class="n">routes</span>
<span class="k">where</span> <span class="s1">'/aaron-a-aaronson'</span> <span class="o">&gt;=</span> <span class="n">path</span>
<span class="k">order</span> <span class="k">by</span> <span class="n">path</span> <span class="k">desc</span>
<span class="k">limit</span> <span class="mi">1</span><span class="p">;</span>
</code></pre></div></div>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>/30-hours-free-childcare
</code></pre></div></div>

<p>wat.</p>

<p>This could be fixed by wrapping the result in a check that it’s really a prefix of the search string, but there’s a better way.</p>

<h2 id="back-to-stackexchange">Back to stackexchange</h2>

<p>At this point I thought back to the stackexchange question. The people on that forum know a lot more about databases than I do. If it was really this easy to solve this problem, wouldn’t they have already suggested this approach? Perhaps there was some subtlety I hadn’t spotted.</p>

<p>Sure enough, <a href="https://dba.stackexchange.com/a/148947/268010">the second answer</a> (which I had ignored because it only had the one upvote) takes exactly this approach.</p>

<blockquote>
  <p>A string S is a prefix of a string T iff T is between S and SZ where Z is lexicographically larger than any other string (eg 99999999 with enough 9’s to exceed the longest possible phone number in the dataset, or sometimes 0xFF will work).
The longest common prefix for any given T is also lexicographically maximal, so a simple group by and max will find it.
…</p>
  <ul>
    <li>KWillets answered Sep 6, 2016 at 18:15</li>
  </ul>
</blockquote>

<p>Fancier words, but it’s the same idea.</p>

<p>Additionally, they had the bright idea to use <code class="language-plaintext highlighter-rouge">between ... and </code> and tack a suffix onto the column being searched to avoid the problem when you search for something which doesn’t have a matching prefix. 9 is a good choice of suffix if you’re dealing with numbers, but we’ve got letters too so I think <code class="language-plaintext highlighter-rouge">~</code> is a better choice (<code class="language-plaintext highlighter-rouge">~</code> is the last ASCII character when sorted alphabetically).</p>

<p>Repeating the sketch above, this would be something like:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>  /apply-first-provisional-driving-licence
  /apply-first-provisional-driving-licence~~~~~~~~
  /apply-for-a-digital-tachograph-company-smart-card
  /apply-for-a-digital-tachograph-company-smart-card~~~~~~~~
  /apply-for-a-digital-tachograph-driver-smart-card
  /apply-for-a-digital-tachograph-driver-smart-card~~~~~~~~
👀/apply-for-a-licence &lt;--- 👀 The longest matching prefix
✨/apply-for-a-licence/temporary-event-notice/east-hertfordshire/apply-1 &lt;--- ✨ The thing we're searching for
👀/apply-for-a-licence~~~~~~~~ &lt;--- 👀 The longest matching prefix, with a bunch of ~s tacked on to the end
  /apply-for-a-patent
  /apply-for-a-patent~~~~~~~~
  /apply-for-a-thames-accommodation-licence
  /apply-for-a-thames-accommodation-licence~~~~~~~~
</code></pre></div></div>

<p>I’ve given this answer another upvote. I think it’s really elegant (even if the bit about appending a bunch of <code class="language-plaintext highlighter-rouge">9</code>s / <code class="language-plaintext highlighter-rouge">~</code>s is a bit weird). If you’ve got a stackexchange account, <a href="https://dba.stackexchange.com/a/148947/268010">you should upvote it too</a>.</p>

<h2 id="the-solution">The solution</h2>

<p>Combining the clever insight from stackexchange with the query I came up with in the shower gives:</p>

<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">select</span> <span class="n">path</span> <span class="k">from</span> <span class="n">routes</span>
<span class="k">where</span> <span class="o">?</span> <span class="k">between</span> <span class="n">path</span> <span class="k">and</span> <span class="n">path</span><span class="o">||</span><span class="s1">'~~~~~'</span>
<span class="k">order</span> <span class="k">by</span> <span class="n">path</span> <span class="k">desc</span>
<span class="k">limit</span> <span class="mi">1</span><span class="p">;</span>
</code></pre></div></div>

<p>With <code class="language-plaintext highlighter-rouge">'/apply-for-a-licence/temporary-event-notice/east-hertfordshire/apply-1'</code> this gives:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>/apply-for-a-licence
</code></pre></div></div>

<p>With <code class="language-plaintext highlighter-rouge">'/aaron-a-aaronson'</code> this gives:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[No results]
</code></pre></div></div>

<p>Which is exactly what we want. And it’s still efficient:</p>

<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">explain</span> <span class="k">analyze</span> <span class="k">select</span> <span class="n">path</span> <span class="k">from</span> <span class="n">routes</span>
<span class="k">where</span> <span class="s1">'/aaron-a-aaronson'</span> <span class="k">between</span> <span class="n">path</span> <span class="k">and</span> <span class="n">path</span><span class="o">||</span><span class="s1">'~~~~~'</span>
<span class="k">order</span> <span class="k">by</span> <span class="n">path</span> <span class="k">desc</span>
<span class="k">limit</span> <span class="mi">1</span><span class="p">;</span>
</code></pre></div></div>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Limit (cost=0.28..0.70 rows=1 width=32) (actual time=0.036..0.036 rows=0 loops=1)
-&gt; Index Only Scan Backward using routes_index on routes (cost=0.28..315.29 rows=756 width=32) (actual time=0.035..0.035 rows=0 loops=1)
Index Cond: (path &lt;= '/aaron-a-aaronson'::text)
Filter: ('/aaron-a-aaronson'::text &lt;= (path || '~~~~~'::text))
Rows Removed by Filter: 5
Heap Fetches: 5
Planning Time: 0.305 ms
Execution Time: 0.057 ms
</code></pre></div></div>

<h2 id="conclusions">Conclusions</h2>

<ol>
  <li>Relational databases are brilliant</li>
  <li>It is possible to efficiently query for a longest prefix in SQL</li>
  <li>The best answer on stackexchange is sometimes hiding without many upvotes</li>
  <li>Try not to get intimidated by watching someone go down the rabbit hole on stackexchange</li>
</ol>]]></content><author><name>Richard Towers</name></author><category term="postgres" /><category term="sql" /><summary type="html"><![CDATA[How to efficiently find the longest matching prefix of some string in SQL, and the dangers of stackexchange]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://richard-towers.com/static/images/simon-wilkes-S297j2CsdlM-unsplash.jpg" /><media:content medium="image" url="https://richard-towers.com/static/images/simon-wilkes-S297j2CsdlM-unsplash.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">We’re going on a certificate hunt!</title><link href="https://richard-towers.com/2022/12/19/finding-certificates-in-binary-files.html" rel="alternate" type="text/html" title="We’re going on a certificate hunt!" /><published>2022-12-19T00:00:00+00:00</published><updated>2022-12-19T00:00:00+00:00</updated><id>https://richard-towers.com/2022/12/19/finding-certificates-in-binary-files</id><content type="html" xml:base="https://richard-towers.com/2022/12/19/finding-certificates-in-binary-files.html"><![CDATA[<h1 id="were-going-on-a-certificate-hunt">We’re going on a certificate hunt!</h1>

<p>I’ve spent a lot of my career wrangling certificates. They’re used to secure
traffic on the web, they’re used in authentication protocols like SAML, they’re
used for signing and encrypting various other kinds of document.</p>

<p>Certificates can be represented / disguised in a few different formats, and
some of them are easier to spot than others.</p>

<p>Recently I had to find an Adobe Reader Extensions certificate embedded in a PDF
document, which pushed my certificate hunting skills to the limit. This is the
story of that hunt.</p>

<h2 id="why-are-there-certificates-living-in-a-pdf">Why are there certificates living in a PDF?</h2>

<p>A particularly unpleasant legacy service at work uses PDF forms, which users
have to fill in using Adobe Acrobat Reader. The form filling functionality is
proprietary - you can only fill in PDF forms in Acrobat if the PDF was signed
by a particular key issued by Adobe. Which keys are trusted to sign these PDFs
is controlled by embedded certificates.</p>

<p>The certificate we were using was due to expire, so we updated it. I wanted to
verify that the PDFs being produced were using the new certificate and not the
old one (without waiting for the old one to expire and seeing if everything
broke).</p>

<h2 id="part-1-strings">Part 1: strings</h2>

<p>PDF documents are a combination of ASCII characters and binary data. On the
hope that the certificate I was looking for might be encoded in ASCII, I ran
the <code class="language-plaintext highlighter-rouge">strings</code> command on the PDF.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ strings file.pdf
%PDF-1.7
41 0 obj
&lt;&lt;/Filter/FlateDecode/First 23/Length 137/N 4/Type/ObjStm&gt;&gt;stream
R)i/P
``Zp
...
</code></pre></div></div>

<p>Unfortunately, there was nothing as obvious as <code class="language-plaintext highlighter-rouge">-----BEGIN CERTIFICATE-----</code>
(which indicates a PEM formatted certificate). The most promising bit of the
file looked like this:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>&lt;&lt;/Type/Sig/Filter/Adobe.PPKLite/SubFilter/adbe.pkcs7.detached/
... snip ...
Contents&lt;308006092886f70d01010b0500306c
...snip about 12000 characters ...
00000000000000000000000000000000&gt;
</code></pre></div></div>

<p>It’s not a certificate, but the <code class="language-plaintext highlighter-rouge">&lt;&lt;/Type/Sig</code> bit suggests it might be a
signature, which could well contain the certificate.</p>

<p>So perhaps there are some certificates hiding inside the big <code class="language-plaintext highlighter-rouge">Contents&lt;...&gt;</code>
string.</p>

<h2 id="part-2-a-fistful-of-octets">Part 2: A fistful of octets</h2>

<p>The structure of a certificate is defined by <a href="https://en.wikipedia.org/wiki/X.509">X.509</a>,
which is based on <a href="https://en.wikipedia.org/wiki/ASN.1">ASN.1</a>.</p>

<p>This structure is usually encoded using
<a href="https://en.wikipedia.org/wiki/X.690#DER_encoding">DER</a>, and sometimes further
encoded using <a href="https://en.wikipedia.org/wiki/Privacy-Enhanced_Mail">PEM</a>.</p>

<p>Which is all pretty confusing.</p>

<p>To find a certificate shaped needle in our haystack of
<code class="language-plaintext highlighter-rouge">Contents&lt;30800609288...</code> we need to know a bit about what to expect though.</p>

<p>Certificates always begin with <a href="https://datatracker.ietf.org/doc/html/rfc1422#autoid-5">an ASN.1
Sequence</a>, which (in
hexadecimal format) is represented by the number 30. The next octet (two
hexadecimal characters) specifies the length of the sequence.</p>

<p>In DER, <a href="https://en.wikipedia.org/wiki/X.690#Length_octets">length encoding can be “short form” or “long form”</a>.</p>

<p>Certificates are always longer than 127 bits, so the length will always be in
long form.  Certificates are also always longer than 256 bits, and almost
always shorter than 65,536 bits, which means DER needs two octect to specify
the sequence length.</p>

<p>This means the next octet will be 82, and that the next two octets will depend
on the length of the certificate.</p>

<p>So we know the beginning of the certificate will be <code class="language-plaintext highlighter-rouge">3082....</code>.</p>

<p>This is why PEM certificates always start with <code class="language-plaintext highlighter-rouge">MII</code> (which you might have
noticed if you work with certificates a lot).</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ xxd -r -p &lt;&lt;&lt; '3082' | base64    
MII=
</code></pre></div></div>

<p>grepping our haystack for 3082 followed by four hex characters yields a few candidates:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ grep -a -o '3082....' file.pdf 
308205a4
3082038c
30820222
3082020a
30820609
308203f1
30820122
3082010a
30820198
308206a1
30820489
30820222
3082020a
30820130
3082020c
</code></pre></div></div>

<p>Encouraging, but we only expect to see three or four certificates (assuming the
whole chain is included), so this is too many matches. Can we be more precise?</p>

<h2 id="part-3-a-few-octets-more">Part 3: A few octets more</h2>

<p>Fortunately, the first bit of the ASN.1 Sequence which makes up a certificate
is another, nested ASN.1 sequence. And this one is also (almost) always between
256 and 65,536 bits long. By the same reasoning as above, we can tell that the
two octets following the four we already worked out will also be 3082. So the
pattern we should look for is <code class="language-plaintext highlighter-rouge">3082....3082</code>:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ grep -a -o '3082....3082' file.pdf 
308205a43082
308206093082
308206a13082
</code></pre></div></div>

<p>Now we’re down to just three matching prefixes - could these be the starts of
our certificates?</p>

<h2 id="part-4-the-good-the-bad-and-the-ugly">Part 4: The good, the bad, and the ugly</h2>

<p>We’ve worked out the starts of our certificates, but we haven’t worked out
where they end. Fortunately, openssl is happy to pick up the first certificate
it sees and ignore everything after it, so we can actually skip that bit and
jump straight to trying to parse the (maybe) certificates.</p>

<p>We’ll need to decode the hexadecimal into binary (using <code class="language-plaintext highlighter-rouge">xxd -r -p</code>), and then
pass them to openssl (with <code class="language-plaintext highlighter-rouge">openssl x509 -inform DER</code>):</p>

<p>Taking the first prefix we found:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ grep -a -o '308205a43082[0-9a-f]*' file.pdf | xxd -r -p | openssl x509 -inform DER
-----BEGIN CERTIFICATE-----
MIIFpDCCA4ygAwIBAgIQXfEvX1enw+GwAtiTJwzd4TANBgkqhkiG9w0BAQsFADBs
MQswCQYDVQQGEwJVUzEjMCEGA1UEChMaQWRvYmUgU3lzdGVtcyBJbmNvcnBvcmF0
... snip ...
7quYkGyLWtTtZoB5J1b7OYUraMDuqG1s39jMchHjda1GqOwsBWxDqC4HqtdY7TK2
ofZLvqTHvT4=
-----END CERTIFICATE-----
</code></pre></div></div>

<p>TADA! And indeed this works for the other two prefixes - there are three
certificates in this PDF, a root, an intermediate and a leaf.</p>

<h2 id="conclusion">Conclusion</h2>

<p>This general technique of looking for hexadecimal streams matching
<code class="language-plaintext highlighter-rouge">/3082....3082/</code> should be a fairly robust way of finding certificates in
binary files. It might occasionally trip up if a certificate is very long or
very short, and it might find some false positives (particularly if there’s
other ASN.1 data in the file).</p>

<p>In our case we were lucky that the bit where the certificate lived was already
hex encoded, but it could probably still be spotted in the hexdump of a binary
file.</p>

<p>If the certificate were base64 encoded instead of hex encoded, you could use
the same trick with the pattern <code class="language-plaintext highlighter-rouge">/MI....CC/</code> (or something like that).</p>

<h2 id="bonus-pkcs7-and-parsing-asn1-with-openssl">Bonus: PKCS7 and parsing ASN.1 with openssl</h2>

<p>After I’d worked all of this out, I realised that the whole
<code class="language-plaintext highlighter-rouge">Contents&lt;30800609288...&gt;</code> string was itself in ASN.1 format, specifically
<a href="https://en.wikipedia.org/wiki/PKCS_7">PKCS #7</a>. This means there are a couple
of easier ways to parse it (but which might not work in other files).</p>

<p>The prefix <code class="language-plaintext highlighter-rouge">3080</code> means “a sequence of indeterminate length” (whereas <code class="language-plaintext highlighter-rouge">3082</code>
means “a sequence of length specified in the next two octets”).</p>

<p>Parsing the whole thing as PKCS #7:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ grep -a -o 'Contents&lt;[0-9a-f]*' file.pdf | cut -d '&lt;' -f 2 | xxd -r -p | openssl pkcs7 -inform DER -noout -print
PKCS7: 
  type: pkcs7-signedData (1.2.840.113549.1.7.2)
  d.sign: 
    version: 1
    md_algs:
        algorithm: sha1 (1.3.14.3.2.26)
        parameter: NULL
    contents: 
      type: pkcs7-data (1.2.840.113549.1.7.1)
      d.data: &lt;ABSENT&gt;
... snip ...
</code></pre></div></div>

<p>(This includes the certificate data I was looking for, albeit in a slightly inconvenient format)</p>

<p>Or parsing as ASN.1:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ grep -a -o 'Contents&lt;[0-9a-f]*' file.pdf | cut -d '&lt;' -f 2 | xxd -r -p | openssl asn1parse -inform DER
    0:d=0  hl=2 l=inf  cons: SEQUENCE          
    2:d=1  hl=2 l=   9 prim: OBJECT            :pkcs7-signedData
   13:d=1  hl=2 l=inf  cons: cont [ 0 ]        
   15:d=2  hl=2 l=inf  cons: SEQUENCE          
   17:d=3  hl=2 l=   1 prim: INTEGER           :01
   20:d=3  hl=2 l=  11 cons: SET               
   22:d=4  hl=2 l=   9 cons: SEQUENCE          
   24:d=5  hl=2 l=   5 prim: OBJECT            :sha1
   31:d=5  hl=2 l=   0 prim: NULL              
   33:d=3  hl=2 l=inf  cons: SEQUENCE   
</code></pre></div></div>]]></content><author><name>Richard Towers</name></author><category term="cryptography" /><summary type="html"><![CDATA[We're going to catch an X.509 certificate. What a beautiful day! We're not scared.]]></summary></entry><entry><title type="html">How I manage my email</title><link href="https://richard-towers.com/2022/01/23/how-i-manage-my-email.html" rel="alternate" type="text/html" title="How I manage my email" /><published>2022-01-23T00:00:00+00:00</published><updated>2022-01-23T00:00:00+00:00</updated><id>https://richard-towers.com/2022/01/23/how-i-manage-my-email</id><content type="html" xml:base="https://richard-towers.com/2022/01/23/how-i-manage-my-email.html"><![CDATA[<h1 id="how-i-manage-my-email">How I manage my email</h1>

<p>This is a post on how I manage my email inbox. Should be a strong contender for my most boring blog post.</p>

<p>2021 was not a good year for my relationship with my email inbox.</p>

<blockquote>
  <p>If you sent me an email, I probably haven’t read it yet. Sorry about that.</p>
  <ul>
    <li>me in weeknote S01E02 (17 Jan 2021)</li>
  </ul>
</blockquote>

<p>I get a lot of emails at work - somewhere between 50 and 100 per day.</p>

<p>Most of these emails are automated notifications of one kind or another, of which most can be safely ignored. A few automated notifications are (potentially) important enough that I let them hit my inbox.</p>

<p>Then there are emails sent by actual human beings - calendar invitations, trello and github updates, questions, messages about recruitment campaigns and so on. Some of these just need reading, some of them need quick responses. Occasionally someone sends me an email which requires a bit of work to respond to (maybe I need to do a bit of investigation to answer a question, maybe it just needs a detailed reply).</p>

<p>What tended to happen to me was:</p>

<ul>
  <li>I would get “on top of my emails” (a state where I was aware of everything in my inbox, and felt I would eventually address them all)</li>
  <li>A few emails would arrive which I didn’t have time to respond to immediately, and I’d leave these as unread to remind me to respond later</li>
  <li>Lots of less important emails would arrive, burying the few big emails that needed my response</li>
  <li>I’d start to feel anxious about the situation</li>
  <li>I’d start to avoid looking at my inbox because it made me feel anxious</li>
  <li>The situation would get much, much worse while I was ignoring it</li>
  <li>Occasionally I would entirely miss a chain of important emails, causing bad things to happen in the real world</li>
  <li>Eventually, I would have a multi-hour email binge and get back into a state where I was “on top of my emails”</li>
  <li>And then the cycle would repeat</li>
</ul>

<p>This was really a bad situation, to the point where I’m nervous even admitting it. It made me less effective at my job, and it made me feel awful too.</p>

<p>This year I’ve been trying to sort my relationship with email out. I figured I’d share a few techniques I use for managing my inbox. They’re gmail specific, but there will be equivalents in other providers. Most are obvious, but I imagine a few are niche enough that they’ll be new.</p>

<h2 id="unsubscribe">Unsubscribe</h2>

<p>If I get sent any automated marketing emails, I’ll find the unsubscribe link in the footer and unsubscribe.</p>

<p>If it’s in any way difficult to unsubscribe that email gets marked as spam. If you send marketing emails without unsubscribe links you deserve to have your reputation tarnished.</p>

<p>When I’m a member of a google group that gets a lot of email, I’ll consider whether I need to see every email, or whether <a href="https://support.google.com/groups/answer/9792489">switching my subscription to digest, abridged, or no email</a> is better.</p>

<h2 id="skip-the-inbox-using-filters">Skip the inbox using filters</h2>

<p>I get some automated emails which I can’t unsubscribe from, but which are never relevant to me. These are usually sent by systems within my own organization, so it’s not a good idea to try to get these flagged as spam.</p>

<p>Fortunately, gmail lets you <a href="https://support.google.com/mail/answer/6579">create rules to filter your emails</a>. One of the filtering options is to “Skip the Inbox (Archive it)”. Archived emails in gmail aren’t lost, they just don’t show up in the Inbox. So you can still search for these messages, or view them in the “All Mail” view.</p>

<h2 id="use-labels">Use labels</h2>

<p>Gmail lets you create your own labels, which you can then use to label messages in your inbox. <a href="https://support.google.com/mail/answer/118708">The Gmail Help docs explain how to use labels</a>.</p>

<p>I start most of my labels with numbers so they’ll appear in the order I want in the sidebar (they’re ordered alphabetically).</p>

<p>I have labels for things I particularly care about, like <code class="language-plaintext highlighter-rouge">3 - Apprentices</code>, <code class="language-plaintext highlighter-rouge">3 - Recruitment</code>, <code class="language-plaintext highlighter-rouge">4 - Incident</code>. I also have a tree of labels for various kinds of automated notifications - <code class="language-plaintext highlighter-rouge">5 - Automated Notifications/AWS</code>, <code class="language-plaintext highlighter-rouge">5 - Automated Notifications/GitHub</code>, <code class="language-plaintext highlighter-rouge">5 - Automated Notifications/Trello</code>.</p>

<p>The most important label I use is <code class="language-plaintext highlighter-rouge">0 - Needs response</code>. I use this one to stop me from leaving important things lingering in my inbox. Where there are things that are too big for me to respond immediately, I label them as “needs response” and archive them for later.</p>

<h2 id="label-using-filters">Label using filters</h2>

<p>You can use filters to assign labels to messages.</p>

<p>This is useful for stuff that’s being automatically archived because if you don’t label it, it’ll get lost in the “All Mail” swamp. A lot of my automatically archived email gets one kind or another kind of <code class="language-plaintext highlighter-rouge">5 - Automated Notifications</code> label. Mostly I still never look at this stuff, but when I do it’s useful to have it organised.</p>

<p>Note that you don’t have to Skip the Inbox when using filters. Some of my most useful filters just label stuff as it hits the inbox.</p>

<p>My favourite filter is the one that labels google calendar invitations:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Matches: (invite.ics OR invite.vcs) has:attachment
Do this: Apply label "Calendar Invitation"
</code></pre></div></div>

<p>Having these labeled in the inbox means I can go through just my calendar invitations. This is a lot less psychologically daunting than general mail, because calendar invitations rarely need more than a couple of seconds’ worth of thought - “am I going to attend this meeting? Yes / No”.</p>

<p>The same is true of Trello and GitHub notifications - I want to look at these, and I know they rarely take more than a few seconds to get through. Even if my inbox is a bit full, if there are a lot of Trello and GitHub notifications I know I can plough through those labels really quickly and see the total unread messages number plummet. Feels good.</p>

<h2 id="use-keyboard-shortcuts">Use keyboard shortcuts</h2>

<p>Sadly, at some point, I have to actually read the email. This is a process that has to be done manually.</p>

<p>It pays to be as efficient as possible. I want to focus on the emails, and spend as little time as possible clicking buttons in gmail.</p>

<p>There are <a href="https://support.google.com/mail/answer/6594?hl=en">keyboard shortcuts for Gmail</a> (which have to be enabled in the settings). Software developers famously love shortcut keys. Taking your hands off the keyboard and reaching for the mouse is such a waste of time, and an invitation for repetitive strain injury.</p>

<p>It’s worth mentioning that these keyboard shortcuts can be a little… dangerous. Sometimes I get the interface wrong and start typing a reply outside of the right text area. I can press a dozen different shortcut keys before I work out that I’m not typing a reply. A dozen potentially destructive actions which I have no memory of. Still, it’s totally worth it.</p>

<p>My most used shortcuts are:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">?</code> - the ever useful help dialog - shows you what all the keyboard shortcuts are</li>
  <li><code class="language-plaintext highlighter-rouge">l</code> - to open the labels dialog. For example l0 will open the labels dialog and search for 0 (which will be my 0 – Needs response label)</li>
  <li><code class="language-plaintext highlighter-rouge">e</code> - to archive an email. Once I’ve handled an email (either responded, or determined it doesn’t need a response), I’ll archive it.</li>
  <li><code class="language-plaintext highlighter-rouge">j/k</code> - older / newer conversation</li>
  <li><code class="language-plaintext highlighter-rouge">}</code> - archive conversation and go to the next newer conversation</li>
</ul>

<p>I can hammer through emails pretty quickly labelling and archiving as I go. Almost feels like vim.</p>

<h2 id="archive-manually-to-reach--inbox-zero-">Archive manually to reach ✨ Inbox Zero ✨</h2>

<p>I’m not a strict adherent to the Inbox Zero philosophy, but I do try to reach zero occasionally. I like to archive messages, rather than just reading them and leaving them in the inbox.</p>

<p>Some of the archiving is done automatically by my filters, the rest is done with the e and } keyboard shortcuts.</p>

<h2 id="track-metrics-and-set-service-level-objectives">Track metrics and set service level objectives</h2>

<p>The most extreme thing I’ve done to get on top of my emails is to keep metrics on them.</p>

<p>I wrote <a href="https://github.com/richardTowers/prometheus-gmail-exporter-go">a prometheus exporter for gmail</a> and used it to hook up my inbox and grafana. Hopefully I’ll find time to write a separate post about this thing.</p>

<p>The metrics integration has enabled me to create a kind of Service Level Objective dashboard for my emails. I aim to manually archive about 30 emails per day, and I track the change in messages in my Inbox over 24 hour and 1 hour periods.</p>

<p>If these numbers go red, I know it’s past time for me to look at my emails again. This screenshot shows that I’m not there yet with my Needs Response label.</p>

<p><img src="/static/images/screenshot-of-gmail-grafana-dashboard.png" alt="Screenshot showing an empty gmail inbox alongside a grafana dashboard with various metrics. The metrics show 0 Threads in Inbox (green), -211 Change in Inbox (green), 233 Emails archived (green), -34 Change in Inbox (green), and 12 Needs Response (red). There are line graphs showing a downward trend in Inbox Total and a slight upward trend in Needs Response." /></p>]]></content><author><name>Richard Towers</name></author><category term="productivity" /><summary type="html"><![CDATA[This is a post on how I manage my email inbox. Should be a strong contender for my most boring blog post.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://richard-towers.com/static/images/screenshot-of-gmail-grafana-dashboard.png" /><media:content medium="image" url="https://richard-towers.com/static/images/screenshot-of-gmail-grafana-dashboard.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">A dark week for international women’s day</title><link href="https://richard-towers.com/2021/03/14/male-violence-against-women.html" rel="alternate" type="text/html" title="A dark week for international women’s day" /><published>2021-03-14T00:00:00+00:00</published><updated>2021-03-14T00:00:00+00:00</updated><id>https://richard-towers.com/2021/03/14/male-violence-against-women</id><content type="html" xml:base="https://richard-towers.com/2021/03/14/male-violence-against-women.html"><![CDATA[<h1 id="a-dark-week-for-international-womens-day">A dark week for international women’s day</h1>

<p>This Monday was international women’s day. The theme this year was “choose to
challenge”, asking us all “How will you help forge a gender equal world?”</p>

<p>The rest of the week’s news has put this question in horribly sharp focus.</p>

<p>Sarah Everard was the same age as me. She grew up in the same town as me. She
studied at the same university as me. She lived in the same part of South
London as me. And yet while I have never felt unsafe walking on the streets
where I live, she was killed just walking home.</p>

<p><em>How will you help forge a gender equal world?</em></p>

<p>Plenty of people have pointed out that expecting women to change their
behaviour cannot be the answer. A world where women cannot safely walk home is
not a gender equal world. Somehow, we have to address the problem of male
violence against women.</p>

<p>I like to think I’m a decent guy, that I’m not part of the problem. I don’t
make catcalls. I try to avoid walking behind people at a creepy distance. But
even if I’m not directly part of the problem, that doesn’t mean I can’t be part
of the solution.</p>

<p>So, I’ll choose to challenge bad behaviour from other men when I see it. I
won’t fix society’s problems on my own. But if enough men commit to being
better, perhaps we can gradually tip the balance. One day, violent men will no
longer be able to hide within a male culture that accepts bad behaviour.</p>

<p>There was a vigil for Sarah last night. We observed it by candlelight on our
doorstep with our neighbours.</p>

<p>On Clapham Common the Met Police somehow ended up in a situation where they
demonstrated more male violence against women. I can’t understand how this was
allowed to happen. It points again to a society wide problem - from the police
on the ground and their senior leaders, all the way up to the courts and
politicians who chose not to intercede and allow an organised event.  Somehow a
peaceful vigil was allowed to be criminalised and turned violent, all in the
name of public safety.</p>

<p>A sad week, and another wake up call.</p>]]></content><author><name>Richard Towers</name></author><category term="personal" /><summary type="html"><![CDATA[Reflections on international women's day and the events surrounding the tragic death of Sarah Everard]]></summary></entry><entry><title type="html">A Month Note</title><link href="https://richard-towers.com/2021/03/07/weeknotes-s01e06-s01e09.html" rel="alternate" type="text/html" title="A Month Note" /><published>2021-03-07T00:00:00+00:00</published><updated>2021-03-07T00:00:00+00:00</updated><id>https://richard-towers.com/2021/03/07/weeknotes-s01e06-s01e09</id><content type="html" xml:base="https://richard-towers.com/2021/03/07/weeknotes-s01e06-s01e09.html"><![CDATA[<h1 id="a-month-note">A Month Note</h1>

<p>I always wondered how my colleagues were able to consistently write weeknotes.
How do they work up the enthusiasm?</p>

<p>Now that I’m trying it myself, I’m finding that it’s pretty much just as
difficult as I thought. For the last few weeks I just haven’t been able to face
it.</p>

<p>It’s been tough, the last few weeks. There’s a fair bit going on at work, and
most of it is stuff I don’t enjoy and don’t feel like I’m good at -
recruitment, people moves, management, governance and planning. Context
switching between all these things makes it feel like I’m not doing any of them
well, and that’s demoralising.</p>

<p>Objectively, things are probably not nearly as bad as I think. I’m doing the
best job I can in less-than-ideal circumstances. There have been some wins, for
sure. A long running campaign to promote four of our developers finally got
sorted out, which I should be thrilled about. I’m making good progress on lots
of other fronts too. But even the wins feel like I’m collapsing over the finish
line with no energy left to celebrate.</p>

<p>So yeah, I’m finding things difficult. I’m going to book some holiday in, and
hopefully that will let me reset. I’m encouraging the people I work with to do
the same.</p>

<p>But enough about that, what’s been going on in the world?</p>

<h2 id="in-the-news">In the news</h2>

<p>President Trump was acquitted. A lawyer wasn’t a cat.</p>

<h2 id="culture">Culture</h2>

<p>I’ve been binge watching <a href="https://en.wikipedia.org/wiki/The_Expanse_(TV_series)">The
Expanse</a>, which is a
hard sci-fi drama. It’s really excellent. The first season in particular has
some wonderful little details - the way whisky pours in low gravity, or the way
birds fly with the occasional lazy flap of their wings. The plot is nicely
paced, and doesn’t require too much suspension of disbelief.</p>

<h2 id="exercise">Exercise</h2>

<p>It got really snowy a few weeks back, and I holed myself up in the house all
week. Now that the sun is back, I’ve made it out on a few slightly longer
walks, which have felt nice. Still haven’t made it out for a run, but who
knows? Maybe one day.</p>

<h2 id="work">Work</h2>

<h3 id="a-changing-of-the-guard">A changing of the guard</h3>

<p>There was a major changing of the guard on GOV.UK.</p>

<p>Tim, our long time Head of Technology moved to the cabinet office’s Chief
Digital Information Office, resuming his role as a Lead Technical Architect.
Tim’s been an incredible support and inspiration to me in my year on GOV.UK,
and the programme will sorely miss him.</p>

<p>Himal joined us as Head of Technology and Architecture (the role name subtly
altered to reflect its hefty scope). It can’t be easy starting a role like
that when everyone’s remote, but from what I can see he’s been doing a stellar
job. It’s very exciting to a new perspective and fresh ideas in the most senior
technical role on GOV.UK. Welcome Himal!</p>

<h3 id="talking-to-our-infrastructure-providers">Talking to our infrastructure providers</h3>

<p>We had a couple of video calls with our infrastructure providers (Fastly, our
CDN provider, and AWS our infrastructure provider).</p>

<p>I can’t remember how much of what they said is covered by non-disclosure
agreements, so this section is going to be a bit boring.</p>

<p>They answered lots of questions, and gave us a bit of a sneak peak at their
roadmaps. We made puppy-dog eyes and begged for preview access to some of their
shiny new features.</p>

<h3 id="replatforming-router">Replatforming Router</h3>

<p>The Replatforming team has been thinking about
<a href="https://github.com/alphagov/router/">Router</a> for a while. It’s kind of like
the front door to GOV.UK. Well, maybe not the front door, because we have a CDN
and some load balancers in front of it. Maybe like the door inside a porch.
Because we’re changing the infrastructure it sits on, some of the architectural
choices made in designing router don’t make sense any more. Karl put together
<a href="https://github.com/alphagov/govuk-rfcs/pull/135">a Request for Comment (RFC) to change the way it reloads its
routes</a>, which we’ve been
discussing. Bill came up with <a href="https://github.com/alphagov/router/pull/188">a way to have multiple Routers sharing the same
database, but using different
domains</a>. This will let us run
independent clusters of apps in the same environment without needing to move
databases around.</p>

<p>The team also looked at some of our redundancy plans, to check that we’d be
able to use them if we needed them. In some places it looks like we’ve got a
bit more redundancy than we really need. I’m preparing another <a href="https://github.com/alphagov/govuk-rfcs/pull/136">RFC to remove
the backup CDN for our backup
CDN</a>.</p>

<h3 id="the-exploding-whale-of-florence-oregon">The Exploding Whale of Florence, Oregon</h3>

<p>Apropos of nothing, here’s a 1970 news report of the Oregon Highways division
disposing of a dead whale:</p>

<p><a href="https://www.youtube.com/watch?v=V6CLumsir34">https://www.youtube.com/watch?v=V6CLumsir34</a></p>

<h3 id="roadmapping">Roadmapping</h3>

<p>GOV.UK published our roadmap publicly for the first time in years -
<a href="https://www.gov.uk/roadmap">https://www.gov.uk/roadmap</a>.</p>

<p>We’re already thinking about the next iteration of the roadmap - what we’ll do
over the next year. I’ve spent quite a bit of time over the last few weeks
talking to various teams working around the publishing area of GOV.UK, trying
to work out how all our plans fit together, and into the big strategic picture.
It’s really great getting a chance to listen to the experts talk about their
areas.</p>

<h3 id="governance">Governance</h3>

<p>I honestly never thought my life would turn out this way.</p>

<p>I’ve been thinking and writing about “Governance” - how GOV.UK makes decisions
that affect its roadmap and its people and teams.</p>

<p>GOV.UK has been through a period where it has had to deliver critical
priorities with inflexible deadlines. For good reason, putting formal processes
around decision making hasn’t been a priority.</p>

<p>Now that things are calming down (a bit) we’ve got an opportunity to design
processes that will let us make these decisions in a calm and considered way,
and ensure that everyone who’s affected feels included and informed.</p>

<h3 id="interviews">Interviews</h3>

<p>I chaired three interviews for an interim site reliability engineer role, and
arranged one interview for a developer role (which unfortunately got
postponed).</p>

<p>I still need to schedule three more developer interviews.</p>

<h2 id="end-of-weeknote">End of weeknote</h2>

<p>👏 well done me. I’m back on the weeknote horse. Hopefully I’ll be able to
stick to a weekly cadence.</p>]]></content><author><name>Richard Towers</name></author><category term="weeknotes" /><summary type="html"><![CDATA[Weeknotes S01E06 - S01E09 - 7 March 2021]]></summary></entry></feed>