<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>PostgreSQL on Nelson Figueroa</title><link>https://nelson.cloud/categories/postgresql/</link><description>Recent content in PostgreSQL on Nelson Figueroa</description><image><title>Nelson Figueroa</title><url>https://nelson.cloud/apple-touch-icon.png</url><link>https://nelson.cloud/categories/postgresql/</link></image><language>en</language><lastBuildDate>Thu, 29 Oct 2026 00:00:00 -0700</lastBuildDate><atom:link href="https://nelson.cloud/categories/postgresql/index.xml" rel="self" type="application/rss+xml"/><item><title>Enums Can Be Enforced at the Database Level in PostgreSQL</title><link>https://nelson.cloud/enums-can-be-enforced-at-the-database-level-in-postgresql/?ref=rss</link><pubDate>Thu, 29 Oct 2026 00:00:00 -0700</pubDate><guid>https://nelson.cloud/enums-can-be-enforced-at-the-database-level-in-postgresql/?ref=rss</guid><description>A PostgreSQL column can be restricted to a predefined set of values using an enumerated type (enum).</description><content:encoded><![CDATA[<p>PostgreSQL databases support having 

<a href="https://en.wikipedia.org/wiki/Enumerated_type" target="_blank" rel="noopener">enumerated type</a> (enum) columns so that the column only accepts one of a predefined set of values. I&rsquo;ve worked with 

<a href="https://en.wikipedia.org/wiki/Object%E2%80%93relational_mapping" target="_blank" rel="noopener">ORMs</a> like 

<a href="https://guides.rubyonrails.org/active_record_basics.html" target="_blank" rel="noopener">Active Record</a> from 

<a href="https://rubyonrails.org/" target="_blank" rel="noopener">Ruby on Rails</a>. Using Active Record, I could work with database columns that only allowed a certain set of values. I always thought that was only possible due to the ORM layer abstracting away these things, but that&rsquo;s not the case. Enums can be enforced at the database level (at least with PostgreSQL).</p>
<p>I&rsquo;ll demonstrate on a PostgreSQL container with a simple table. I&rsquo;ll be using Podman. You can also use Docker to follow along, just replace <code>podman</code> with <code>docker</code> in the commands.</p>
<p>Make sure Podman/Docker is running, then run the following to get a running PostgreSQL container locally:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl"><span class="nb">podman</span> run <span class="no">-d</span> <span class="no">--name</span> enum-demo <span class="no">-e</span> <span class="nv">POSTGRES_PASSWORD</span><span class="o">=</span>postgres docker.io/library/postgres:18</span></span></code></pre></div><p>Then exec into the running container:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-console" data-lang="console"><span class="line"><span class="cl"><span class="gp">$</span> <span class="nb">podman</span> <span class="nb">exec</span> <span class="no">-it</span> enum-demo psql <span class="no">-U</span> postgres
</span></span><span class="line"><span class="cl"><span class="go">psql (18.6 (Debian 18.6-1.pgdg13+2))
</span></span></span><span class="line"><span class="cl"><span class="go">Type &#34;help&#34; for help.
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="go">postgres=#
</span></span></span></code></pre></div><p>Now we can start setting up the table to try out enums. First, we&rsquo;ll create the enum type itself. It&rsquo;s going to be a type just like <code>INTEGER</code> or <code>TEXT</code> are types in a database. I&rsquo;m thinking of CI/CD pipelines to use as an example, so the enum will be called <code>pipeline_status</code> and it&rsquo;ll only accept the values <code>pending</code>, <code>running</code>, <code>succeeded</code>, and <code>failed</code>. Run this SQL query in the container:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sql" data-lang="sql"><span class="line"><span class="cl"><span class="k">CREATE</span><span class="w"> </span><span class="k">TYPE</span><span class="w"> </span><span class="n">pipeline_status</span><span class="w"> </span><span class="k">AS</span><span class="w"> </span><span class="n">ENUM</span><span class="w"> </span><span class="p">(</span><span class="s1">&#39;pending&#39;</span><span class="p">,</span><span class="w"> </span><span class="s1">&#39;running&#39;</span><span class="p">,</span><span class="w"> </span><span class="s1">&#39;succeeded&#39;</span><span class="p">,</span><span class="w"> </span><span class="s1">&#39;failed&#39;</span><span class="p">);</span></span></span></code></pre></div><p>PostgreSQL should return:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">CREATE TYPE</span></span></code></pre></div><p>Now let&rsquo;s create a simple table that makes use of the enum. The table will be called <code>pipeline_runs</code> and it&rsquo;ll have two columns: <code>branch</code> of type <code>TEXT</code>, and <code>status</code> which uses our enum type <code>pipeline_status</code>. Run this in the container:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sql" data-lang="sql"><span class="line"><span class="cl"><span class="k">CREATE</span><span class="w"> </span><span class="k">TABLE</span><span class="w"> </span><span class="n">pipeline_runs</span><span class="w"> </span><span class="p">(</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="n">branch</span><span class="w"> </span><span class="nb">TEXT</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="n">status</span><span class="w"> </span><span class="n">pipeline_status</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">);</span></span></span></code></pre></div><p>PostgreSQL should return:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">CREATE TABLE</span></span></code></pre></div><p>Now let&rsquo;s try inserting a row. This SQL query inserts a row where <code>branch</code> is <code>main</code> and <code>status</code> is one of the four predefined enum values (<code>running</code>).</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sql" data-lang="sql"><span class="line"><span class="cl"><span class="k">INSERT</span><span class="w"> </span><span class="k">INTO</span><span class="w"> </span><span class="n">pipeline_runs</span><span class="w"> </span><span class="p">(</span><span class="n">branch</span><span class="p">,</span><span class="w"> </span><span class="n">status</span><span class="p">)</span><span class="w"> </span><span class="k">VALUES</span><span class="w"> </span><span class="p">(</span><span class="s1">&#39;main&#39;</span><span class="p">,</span><span class="w"> </span><span class="s1">&#39;running&#39;</span><span class="p">);</span></span></span></code></pre></div><p>PostgreSQL replies:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">INSERT 0 1</span></span></code></pre></div><p>We can check for the newly created row:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sql" data-lang="sql"><span class="line"><span class="cl"><span class="k">SELECT</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="k">FROM</span><span class="w"> </span><span class="n">pipeline_runs</span><span class="p">;</span></span></span></code></pre></div><p>You should see:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl"> branch | status
</span></span><span class="line"><span class="cl">--------+---------
</span></span><span class="line"><span class="cl"> main   | running
</span></span><span class="line"><span class="cl">(1 row)</span></span></code></pre></div><p>Now let&rsquo;s try inserting a row with an enum value that is not a part of the values we defined. We&rsquo;ll try inserting <code>cancelled</code> for the <code>status</code> column. We should get an error.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sql" data-lang="sql"><span class="line"><span class="cl"><span class="k">INSERT</span><span class="w"> </span><span class="k">INTO</span><span class="w"> </span><span class="n">pipeline_runs</span><span class="w"> </span><span class="p">(</span><span class="n">branch</span><span class="p">,</span><span class="w"> </span><span class="n">status</span><span class="p">)</span><span class="w"> </span><span class="k">VALUES</span><span class="w"> </span><span class="p">(</span><span class="s1">&#39;main&#39;</span><span class="p">,</span><span class="w"> </span><span class="s1">&#39;cancelled&#39;</span><span class="p">);</span></span></span></code></pre></div><p>PostgreSQL should say:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">ERROR:  invalid input value for enum pipeline_status: &#34;cancelled&#34;
</span></span><span class="line"><span class="cl">LINE 1: ...TO pipeline_runs (branch, status) VALUES (&#39;main&#39;, &#39;cancelled...
</span></span><span class="line"><span class="cl">                                                             ^</span></span></code></pre></div><p>The <code>cancelled</code> value isn&rsquo;t one of <code>('pending', 'running', 'succeeded', 'failed')</code>, so PostgreSQL gives us an error. PostgreSQL itself is enforcing enums! No ORMs here.</p>
<p>Note that the values in the enum are case sensitive. So inserting <code>Pending</code> instead of <code>pending</code> will fail:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sql" data-lang="sql"><span class="line"><span class="cl"><span class="k">INSERT</span><span class="w"> </span><span class="k">INTO</span><span class="w"> </span><span class="n">pipeline_runs</span><span class="w"> </span><span class="p">(</span><span class="n">branch</span><span class="p">,</span><span class="w"> </span><span class="n">status</span><span class="p">)</span><span class="w"> </span><span class="k">VALUES</span><span class="w"> </span><span class="p">(</span><span class="s1">&#39;main&#39;</span><span class="p">,</span><span class="w"> </span><span class="s1">&#39;Pending&#39;</span><span class="p">);</span></span></span></code></pre></div><p>PostgreSQL returns the error:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">ERROR:  invalid input value for enum pipeline_status: &#34;Pending&#34;
</span></span><span class="line"><span class="cl">LINE 1: ...TO pipeline_runs (branch, status) VALUES (&#39;main&#39;, &#39;Pending&#39;)...
</span></span><span class="line"><span class="cl">                                                             ^</span></span></code></pre></div><p>Another caveat is that inserting <code>NULL</code> as the value for <code>status</code> is still allowed unless the column is also created with <code>NOT NULL</code>.</p>
<p>Let&rsquo;s say we actually want <code>cancelled</code> to be a part of the enum. We can modify the enum like so:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sql" data-lang="sql"><span class="line"><span class="cl"><span class="k">ALTER</span><span class="w"> </span><span class="k">TYPE</span><span class="w"> </span><span class="n">pipeline_status</span><span class="w"> </span><span class="k">ADD</span><span class="w"> </span><span class="n">VALUE</span><span class="w"> </span><span class="s1">&#39;cancelled&#39;</span><span class="p">;</span></span></span></code></pre></div><p>PostgreSQL responds with:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">ALTER TYPE</span></span></code></pre></div><p>Now that <code>cancelled</code> is a valid value, we can try inserting again and we won&rsquo;t get an error:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sql" data-lang="sql"><span class="line"><span class="cl"><span class="k">INSERT</span><span class="w"> </span><span class="k">INTO</span><span class="w"> </span><span class="n">pipeline_runs</span><span class="w"> </span><span class="p">(</span><span class="n">branch</span><span class="p">,</span><span class="w"> </span><span class="n">status</span><span class="p">)</span><span class="w"> </span><span class="k">VALUES</span><span class="w"> </span><span class="p">(</span><span class="s1">&#39;main&#39;</span><span class="p">,</span><span class="w"> </span><span class="s1">&#39;cancelled&#39;</span><span class="p">);</span></span></span></code></pre></div><p>PostgreSQL responds with:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">INSERT 0 1</span></span></code></pre></div><p>We should have two rows total now. We can double check:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sql" data-lang="sql"><span class="line"><span class="cl"><span class="k">SELECT</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="k">FROM</span><span class="w"> </span><span class="n">pipeline_runs</span><span class="p">;</span></span></span></code></pre></div><p>You should see:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl"> branch |  status
</span></span><span class="line"><span class="cl">--------+-----------
</span></span><span class="line"><span class="cl"> main   | running
</span></span><span class="line"><span class="cl"> main   | cancelled
</span></span><span class="line"><span class="cl">(2 rows)</span></span></code></pre></div><p>We successfully added a value to the <code>pipeline_status</code> enum and used it in a new row.</p>
<p>One final note: there isn&rsquo;t a clean way to remove a value from an enum. You can add values, but you would need to rebuild the enum if you want to remove any values. Plan ahead!</p>
<p>Now that the demo is over, exit the PostgreSQL prompt by running:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">\q</span></span></code></pre></div><p>Then clean up the container and, optionally, the image:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-console" data-lang="console"><span class="line"><span class="cl"><span class="gp">$</span> <span class="nb">podman</span> rm <span class="no">-f</span> enum-demo
</span></span><span class="line"><span class="cl"><span class="go">enum-demo
</span></span></span></code></pre></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-console" data-lang="console"><span class="line"><span class="cl"><span class="gp">$</span> <span class="nb">podman</span> rmi docker.io/library/postgres:18
</span></span><span class="line"><span class="cl"><span class="go">Untagged: docker.io/library/postgres:18
</span></span></span><span class="line"><span class="cl"><span class="go">Deleted: 07ee628d2e71f8956c85fc5466906a6d4038cf00f6db07f067d810ef8fec8161
</span></span></span></code></pre></div><p>We created an enum type with predefined values, tried inserting both valid and invalid values, and it was all enforced at the database level with no abstractions from an ORM!</p>
]]></content:encoded></item></channel></rss>