<rss version="2.0"
    xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>Menial</title>
        <link>https://menial.co.uk/feed/index.xml</link>
        <description>Recent content on Menial</description>
        <generator>Hugo -- gohugo.io</generator>
        <language>en</language>
    
        <atom:link href="https://menial.co.uk/feed/index.xml" rel="self" type="application/rss+xml" />
    
    
    <item>
<title>Base 3.3.0 Released</title>
<link>https://menial.co.uk/blog/2026/03/08/base-3.3.0-released/</link>
<pubDate>Sun, 08 Mar 2026 09:00:00 +0000</pubDate>
      
      <guid isPermaLink="false">https://menial.co.uk/blog/2026/03/08/base-3.3.0-released/</guid>
<description>&lt;p&gt;Base 3.3 is now &lt;a href=&#34;https://menial.co.uk/base/&#34;&gt;available for download&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Base now provides Quick Look previews for SQLite databases. When you&amp;rsquo;ve got a database selected in
the Finder, pressing the spacebar will show a list of tables, views, triggers and indexes in
that file.&lt;/p&gt;
&lt;p&gt;&lt;img
class=&#34;img-fluid&#34;
srcset=&#34;
/blog/2026/03/08/base-3.3.0-released/quick-look@2x.png
 2x&#34;
src=&#34;
/blog/2026/03/08/base-3.3.0-released/quick-look@2x.png
&#34;
alt=&#34;Quick Look preview showing database schema&#34;
style=&#34;display: block; margin: 0 auto;&#34; /&gt;&lt;/p&gt;
&lt;p&gt;I find this really useful when I&amp;rsquo;m working with multiple databases or just want to quickly check
what&amp;rsquo;s in a file without opening it properly. The table and view items are expandable so you can see
indexes and triggers belonging to each one.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;All changes in this version:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;New features&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;New Quick Look extension for previewing database schemas&lt;/li&gt;
&lt;li&gt;Enable the &lt;a href=&#34;https://www.sqlite.org/geopoly.html&#34;&gt;Geopoly&lt;/a&gt; and  &lt;a href=&#34;https://sqlite.org/rbu.html&#34;&gt;RBU&lt;/a&gt;
extensions for use&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Changes and improvements&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;page_size&lt;/code&gt; database option is now a dropdown instead of text field&lt;/li&gt;
&lt;li&gt;Updated SQLite to v3.51.2&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Fixed&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The numpad enter key can now be used to execute queries&lt;/li&gt;
&lt;li&gt;The selected row in the data browser jumps around less when editing data&lt;/li&gt;
&lt;li&gt;Data browser table headers no longer overlap row numbers when scrolling&lt;/li&gt;
&lt;li&gt;Table headers no longer get out-of-sync with columns when switching between tables&lt;/li&gt;
&lt;li&gt;Fixed a crash which occurred when adding a filter in the data browser&lt;/li&gt;
&lt;li&gt;Fixed a crash which occurred when removing certain columns in the table editor&lt;/li&gt;
&lt;li&gt;Fixed overlapping text in SQL query results&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You can &lt;a href=&#34;https://menial.co.uk/base/&#34;&gt;download Base directly&lt;/a&gt;, get it on the
&lt;a href=&#34;https://apps.apple.com/us/app/base-sqlite-editor/id6744867438&#34;&gt;Mac App Store&lt;/a&gt; or via
&lt;a href=&#34;https://go.setapp.com/stp69?refAppID=135&amp;amp;stc=p&#34;&gt;Setapp&lt;/a&gt;.&lt;/p&gt;</description>
</item>
    
    <item>
<title>Base 3.2.0 Released</title>
<link>https://menial.co.uk/blog/2025/12/01/base-3.2.0-released/</link>
<pubDate>Mon, 01 Dec 2025 07:00:00 +0000</pubDate>
      
      <guid isPermaLink="false">https://menial.co.uk/blog/2025/12/01/base-3.2.0-released/</guid>
<description>&lt;p&gt;Base 3.2 is now &lt;a href=&#34;https://menial.co.uk/base/&#34;&gt;available for download&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;This update contains mostly quality-of-life improvements, with improvements to the table schema
editor, data browsing table and SQL autocompletion.&lt;/p&gt;
&lt;p&gt;The full list of changes is as follows:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;New features&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The table schema editor has been simplified. Columns and table constraints are now presented in a
single list with section titles dividing them. This more closely fits how tables are described in
code. The buttons for adding/removing items now have text labels, making it easier to see what
you&amp;rsquo;re changing.&lt;/li&gt;
&lt;li&gt;You can now copy rows and cells from the data browser table from a right-click menu on the table&lt;/li&gt;
&lt;li&gt;The data browser now has an &amp;ldquo;insert special&amp;rdquo; context menu for quickly inserting dates, UUIDs and
&lt;code&gt;NULL&lt;/code&gt; values&lt;/li&gt;
&lt;li&gt;SQL autocompletions for SQLite functions has been expanded to cover all built-in functions&lt;/li&gt;
&lt;li&gt;Added experimental options (opt-in from the settings window) for working with large tables:
&lt;ul&gt;
&lt;li&gt;Disabling the counting of rows in a table&lt;/li&gt;
&lt;li&gt;Allow selecting which columns should be shown in the data browser&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Changes and improvements&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;SQL autocomplete suggestions no longer automatically show when not needed (eg. after typing a
semicolon or a complete keyword)&lt;/li&gt;
&lt;li&gt;The sidebar now only shows database schema names when multiple non-empty databases are attached&lt;/li&gt;
&lt;li&gt;The row editor preview column now resizes to fill available space&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Fixed&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The SQL editor no longer loses focus after executing a statement&lt;/li&gt;
&lt;li&gt;Clicks in the space below text in the SQL editor now behave like other text views on macOS&lt;/li&gt;
&lt;li&gt;Fixed the &amp;ldquo;Send to SQL&amp;rdquo; button not working&lt;/li&gt;
&lt;li&gt;Database pragma values now update correctly after being changed&lt;/li&gt;
&lt;li&gt;Tables containing columns with default value of a signed number are now displayed correctly&lt;/li&gt;
&lt;li&gt;Fixed a UI misalignment in the autocomplete window&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You can &lt;a href=&#34;https://menial.co.uk/base/&#34;&gt;download Base directly&lt;/a&gt;, get it on the
&lt;a href=&#34;https://apps.apple.com/us/app/base-sqlite-editor/id6744867438&#34;&gt;Mac App Store&lt;/a&gt; or via
&lt;a href=&#34;https://go.setapp.com/stp69?refAppID=135&amp;amp;stc=p&#34;&gt;Setapp&lt;/a&gt;.&lt;/p&gt;</description>
</item>
    
    <item>
<title>SQLite Dates</title>
<link>https://menial.co.uk/blog/2025/11/17/sqlite-dates/</link>
<pubDate>Mon, 17 Nov 2025 10:00:00 +0000</pubDate>
      
      <guid isPermaLink="false">https://menial.co.uk/blog/2025/11/17/sqlite-dates/</guid>
<description>&lt;p&gt;SQLite columns are flexibly-typed, you can put broadly anything in any column regardless of the
declared type of that column. There also isn&amp;rsquo;t a specific type for dates or times. This means that
when it comes to storing dates or times, we&amp;rsquo;ve got choices to make. I&amp;rsquo;ll go through the main options
in this post.&lt;/p&gt;
&lt;p&gt;To start with, we need to provide some context:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;SQLite lets you put values of any type you want in a column&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;&lt;/li&gt;
&lt;li&gt;SQLite doesn&amp;rsquo;t have date- or time-specific column types&lt;/li&gt;
&lt;li&gt;The &lt;a href=&#34;https://sqlite.org/lang_datefunc.html&#34;&gt;date and time functions&lt;/a&gt; in SQLite work with &lt;code&gt;TEXT&lt;/code&gt;,
&lt;code&gt;REAL&lt;/code&gt; and &lt;code&gt;INTEGER&lt;/code&gt; values&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a href=&#34;#fn:2&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These three points might seem contradictory, since we&amp;rsquo;re saying on one hand that SQLite doesn&amp;rsquo;t care
about types, but also that the date functions work only on certain types. That&amp;rsquo;s because the date
functions have a few specific formats (&amp;quot;&lt;a href=&#34;https://sqlite.org/lang_datefunc.html#tmval&#34;&gt;time values&lt;/a&gt;&amp;quot;)
that they can handle, so it&amp;rsquo;s less about the &lt;em&gt;column types&lt;/em&gt; and more about the &lt;em&gt;value formats&lt;/em&gt;.&lt;/p&gt;
&lt;h4 id=&#34;the-formats&#34;&gt;The formats&lt;/h4&gt;
&lt;table class=&#34;content-table&#34;&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;ISO8601 (&lt;tt&gt;TEXT&lt;/tt&gt;)&lt;/th&gt;
      &lt;th&gt;Julian day number (&lt;tt&gt;REAL&lt;/tt&gt;)&lt;/th&gt;
      &lt;th&gt;Unix timestamp (&lt;tt&gt;INTEGER&lt;/tt&gt;)&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code id=&#34;iso8601-time&#34;&gt;-&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;&lt;code id=&#34;julian-time&#34;&gt;-&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;&lt;code id=&#34;unix-time&#34;&gt;-&lt;/code&gt;&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;
&lt;h5 id=&#34;dates-as-text&#34;&gt;Dates as &lt;code&gt;TEXT&lt;/code&gt;&lt;/h5&gt;
&lt;p&gt;If you want to handle dates as text, then you&amp;rsquo;ll need to format dates as
&lt;a href=&#34;https://en.wikipedia.org/wiki/ISO_8601&#34;&gt;ISO 8601&lt;/a&gt; strings. The ISO standard describes quite a lot
of options for formatting dates, but SQLite uses only a few of them. Thankfully it&amp;rsquo;s the ones I
expect most people think of when ISO 8601 gets mentioned.&lt;/p&gt;
&lt;p&gt;The full list of accepted formats is available on the
&lt;a href=&#34;https://sqlite.org/lang_datefunc.html#tmval&#34;&gt;SQLite website&lt;/a&gt;, but a few examples are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Date and time: &lt;code&gt;2025-11-01T18:30:00.000&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Date only: &lt;code&gt;2025-11-01&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Time only: &lt;code&gt;18:30:00.000&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These formats can also include a timezone specifier. Internally SQLite works with UTC, so when you
use the date functions any dates with a timezone specified will be converted to UTC:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sql&#34; data-lang=&#34;sql&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;SELECT&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;datetime&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;2025-11-01 18:30:00.000+09:00&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;AS&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;utcValue&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;-- Returns: &amp;#39;2025-11-01 09:30:00&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h5 id=&#34;dates-as-real&#34;&gt;Dates as &lt;code&gt;REAL&lt;/code&gt;&lt;/h5&gt;
&lt;p&gt;This one might sound odd to start with, but bear with it. You can store dates as
&lt;a href=&#34;https://en.wikipedia.org/wiki/Julian_day&#34;&gt;Julian day numbers&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The Julian day number is the number of days since &lt;code&gt;-4713-11-24 12:00:00&lt;/code&gt;&lt;sup id=&#34;fnref:3&#34;&gt;&lt;a href=&#34;#fn:3&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;3&lt;/a&gt;&lt;/sup&gt; and can
include decimal places to cover units of less than a day. So it&amp;rsquo;s really just a timestamp, with the
reference date set a long way in the past.&lt;/p&gt;
&lt;p&gt;A few examples of Julian dates:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;1&lt;sup&gt;st&lt;/sup&gt; January 2000 at 00:00 UTC: &lt;code&gt;2451544.5&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;1&lt;sup&gt;st&lt;/sup&gt; November 2025 at 18:30 UTC: &lt;code&gt;2460981.2708333335&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;The unix epoch date: &lt;code&gt;2440587.5&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h5 id=&#34;dates-as-integer&#34;&gt;Dates as &lt;code&gt;INTEGER&lt;/code&gt;&lt;/h5&gt;
&lt;p&gt;This one&amp;rsquo;s a bit more common: dates stored as &lt;a href=&#34;https://en.wikipedia.org/wiki/Unix_time&#34;&gt;unix time&lt;/a&gt;;
the number of seconds since &lt;code&gt;1970-01-01 00:00:00&lt;/code&gt; UTC.&lt;/p&gt;
&lt;p&gt;SQLite can&amp;rsquo;t tell just from looking at a number whether it&amp;rsquo;s a Julian day number or unix
timestamp and by default it assumes numbers are Julian days&lt;sup id=&#34;fnref:4&#34;&gt;&lt;a href=&#34;#fn:4&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;4&lt;/a&gt;&lt;/sup&gt;. If you use unix
time with the date functions you&amp;rsquo;ll need to tell the function by passing in an extra parameter:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sql&#34; data-lang=&#34;sql&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;SELECT&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;datetime&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;1762021800&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;unixepoch&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;-- Returns &amp;#39;2025-11-01 18:30:00&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h5 id=&#34;other-formats&#34;&gt;Other formats&lt;/h5&gt;
&lt;p&gt;You can also store Julian day numbers as &lt;code&gt;INTEGER&lt;/code&gt; values, or unix timestamps as decimals if you
want. If you only need to store a specific day, not a time, an integer day number works just fine.
If you want fractional seconds for a unix timestamp, you can store it as a &lt;code&gt;REAL&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s also worth pointing out that you&amp;rsquo;re not limited to these formats for storage. You can represent
dates and times in any way you want. You could choose to use the email date format
(&lt;code&gt;Tue, 01 Nov 2025 18:30:00 UTC&lt;/code&gt;) or perhaps Cocoa&amp;rsquo;s &lt;code&gt;TimeInterval&lt;/code&gt; (the number of seconds since
1&lt;sup&gt;st&lt;/sup&gt; January 2001 as a decimal). These - and any others you might find - can be saved
perfectly well in your tables.&lt;/p&gt;
&lt;p&gt;If you do choose a different format though, the built-in date and time functions in SQLite won&amp;rsquo;t
work with your data. This may or may not be a problem for you depending on how you use the database,
but it&amp;rsquo;s important to consider when choosing a format.&lt;/p&gt;
&lt;h4 id=&#34;which-to-choose&#34;&gt;Which to choose?&lt;/h4&gt;
&lt;p&gt;As usual, it depends.&lt;/p&gt;
&lt;p&gt;You&amp;rsquo;ll want to consider things like:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;How am I interacting with the database? Do I need people to be able to read dates by eye?&lt;/li&gt;
&lt;li&gt;What systems will be using the database? What is their preferred serialisation format?&lt;/li&gt;
&lt;li&gt;What precision do I need?&lt;/li&gt;
&lt;li&gt;Do I care about storage space?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Generally my preference is to store dates as &lt;code&gt;TEXT&lt;/code&gt; unless there is a good reason not to.
I choose this because:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;They&amp;rsquo;re human readable&lt;/li&gt;
&lt;li&gt;They&amp;rsquo;re widely compatible with other tools&lt;/li&gt;
&lt;li&gt;There&amp;rsquo;s little to no risk of getting them confused with other formats&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;There are disadvantages though:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Converting to/from strings may have performance costs&lt;/li&gt;
&lt;li&gt;You use more storage&lt;sup id=&#34;fnref:5&#34;&gt;&lt;a href=&#34;#fn:5&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;5&lt;/a&gt;&lt;/sup&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Most of the time these drawbacks are not very significant to me, so I don&amp;rsquo;t worry about them.&lt;/p&gt;
&lt;script&gt;
function updateTimes() {
  const now = new Date();

  // ISO 8601 format (TEXT), stripped of milliseconds
  const iso8601 = now.toISOString().split(&#39;.&#39;)[0] + &#39;Z&#39;;
  document.getElementById(&#39;iso8601-time&#39;).textContent = iso8601;

  // Julian day number (REAL)
  // Unix timestamp in milliseconds to Julian day number
  // Julian day 0 starts at noon on January 1, 4713 BC
  // Unix epoch (Jan 1, 1970 00:00:00 UTC) is Julian day 2440587.5
  const unixTimestampSeconds = now.getTime() / 1000;
  const julianDay = (unixTimestampSeconds / 86400.0) + 2440587.5;
  document.getElementById(&#39;julian-time&#39;).textContent = julianDay.toFixed(7);

  // Unix timestamp (INTEGER)
  const unixTimestamp = Math.floor(now.getTime() / 1000);
  document.getElementById(&#39;unix-time&#39;).textContent = unixTimestamp;
}

// Update immediately, then once a second
updateTimes();
setInterval(updateTimes, 1000);
&lt;/script&gt;
&lt;div class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34;&gt;
&lt;p&gt;Unless you&amp;rsquo;ve chosen to declare a table to be
&lt;a href=&#34;https://www.sqlite.org/stricttables.html&#34;&gt;strictly typed&lt;/a&gt;&amp;#160;&lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:2&#34;&gt;
&lt;p&gt;See the &lt;a href=&#34;https://sqlite.org/datatype3.html#date_and_time_datatype&#34;&gt;SQLite documentation on data types&lt;/a&gt;&amp;#160;&lt;a href=&#34;#fnref:2&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:3&#34;&gt;
&lt;p&gt;You might notice an inconsistency here. The SQLite documentation says that the
reference date is &amp;ldquo;noon in Greenwich on November 24, 4714 B.C.&amp;rdquo;, but &lt;code&gt;datetime(0)&lt;/code&gt; returns
&lt;code&gt;-4713-11-24 12:00:00&lt;/code&gt;, which has a different year number.
That&amp;rsquo;s because the &lt;a href=&#34;https://en.wikipedia.org/wiki/Proleptic_Gregorian_calendar&#34;&gt;proleptic Gregorian calendar&lt;/a&gt;
doesn&amp;rsquo;t have a year zero, it jumps from 1BC to 1AD, while ISO8601 uses
&lt;a href=&#34;https://en.wikipedia.org/wiki/Astronomical_year_numbering&#34;&gt;astronomical year numbering&lt;/a&gt; which does
have a year zero.&amp;#160;&lt;a href=&#34;#fnref:3&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:4&#34;&gt;
&lt;p&gt;There is an &lt;code&gt;auto&lt;/code&gt; modifier to date/time functions which will let SQLite try
to auto-detect the date representation from an integer, based on its size.&amp;#160;&lt;a href=&#34;#fnref:4&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:5&#34;&gt;
&lt;p&gt;Sometimes quite a lot more. For the same date: &lt;code&gt;2025-11-01 18:30:00&lt;/code&gt; is 19 bytes;
&lt;code&gt;2460981.2708333335&lt;/code&gt; is 8 bytes; &lt;code&gt;1762021800&lt;/code&gt; is 4 bytes&amp;#160;&lt;a href=&#34;#fnref:5&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</description>
</item>
    
    <item>
<title>Rowids and Primary Keys</title>
<link>https://menial.co.uk/blog/2025/10/20/rowids-and-primary-keys/</link>
<pubDate>Mon, 20 Oct 2025 18:00:00 +0000</pubDate>
      
      <guid isPermaLink="false">https://menial.co.uk/blog/2025/10/20/rowids-and-primary-keys/</guid>
<description>&lt;p&gt;SQLite has the concept of a &lt;em&gt;rowid&lt;/em&gt;. A special column with a unique integer identifier for each row.
It&amp;rsquo;s present in most tables, but generally hidden from view. This post gives an overview of how
rowids work and their relationship with primary keys.&lt;/p&gt;
&lt;div class=&#34;info-box&#34;&gt;
  All the SQL commands in this post can be followed along in sequence. You can either create a new
database &lt;a href=&#34;https://menial.co.uk/base/&#34;&gt;using Base&lt;/a&gt; or by using the &lt;code&gt;sqlite3&lt;/code&gt; command-line tool.
&lt;/div&gt;

&lt;p&gt;If you run the following SQL:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sql&#34; data-lang=&#34;sql&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;CREATE&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;TABLE&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;rowidExample&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;id&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;INTEGER&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;You&amp;rsquo;ll end up with a table containing only one visible column. But the &lt;code&gt;rowid&lt;/code&gt; column is there too,
just not shown by default.
If we run these statements:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sql&#34; data-lang=&#34;sql&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;INSERT&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;INTO&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;rowidExample&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;DEFAULT&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;VALUES&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;INSERT&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;INTO&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;rowidExample&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;DEFAULT&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;VALUES&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;INSERT&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;INTO&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;rowidExample&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;DEFAULT&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;VALUES&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;SELECT&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;rowid&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;id&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;FROM&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;rowidExample&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;You&amp;rsquo;ll get the following results:&lt;/p&gt;
&lt;table class=&#34;content-table&#34;&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th style=&#34;text-align: &#34;&gt;rowid&lt;/th&gt;
      &lt;th style=&#34;text-align: &#34;&gt;id&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td style=&#34;text-align: &#34;&gt;1&lt;/td&gt;
      &lt;td style=&#34;text-align: &#34;&gt;NULL&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&#34;text-align: &#34;&gt;2&lt;/td&gt;
      &lt;td style=&#34;text-align: &#34;&gt;NULL&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&#34;text-align: &#34;&gt;3&lt;/td&gt;
      &lt;td style=&#34;text-align: &#34;&gt;NULL&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;At this point it might be tempting to think &amp;ldquo;Great! I don&amp;rsquo;t need to specify a primary key now, I&amp;rsquo;ve
got this one anyway&amp;rdquo;. Suppress that thought. The rowid (mostly) isn&amp;rsquo;t for you or me. It&amp;rsquo;s for SQLite
itself. If you rely on it as-is, you&amp;rsquo;ll run in to problems.&lt;/p&gt;
&lt;p&gt;For example, rowids can change. If you run these statements:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sql&#34; data-lang=&#34;sql&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;DELETE&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;FROM&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;rowidExample&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;WHERE&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;rowid&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;2&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;VACUUM&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;SELECT&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;rowid&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;id&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;FROM&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;rowidExample&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;You&amp;rsquo;ll get these results:&lt;/p&gt;
&lt;table class=&#34;content-table&#34;&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th style=&#34;text-align: &#34;&gt;rowid&lt;/th&gt;
      &lt;th style=&#34;text-align: &#34;&gt;id&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td style=&#34;text-align: &#34;&gt;1&lt;/td&gt;
      &lt;td style=&#34;text-align: &#34;&gt;NULL&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&#34;text-align: &#34;&gt;2&lt;/td&gt;
      &lt;td style=&#34;text-align: &#34;&gt;NULL&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Which might seem odd, we just deleted the row with rowid 2 after all. But &lt;code&gt;VACUUM&lt;/code&gt; can cause rowids
to be changed&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;p&gt;So what do we do instead?&lt;/p&gt;
&lt;h4 id=&#34;make-your-own-keys&#34;&gt;Make your own keys&lt;/h4&gt;
&lt;p&gt;You should always specify your own keys. If you need a unique reference for a row, then you&amp;rsquo;ll want
to explicitly model that in your tables. A simple option is to add an integer primary key column,
like this:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sql&#34; data-lang=&#34;sql&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;CREATE&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;TABLE&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;explicitPrimaryKey&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;id&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;INTEGER&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;PRIMARY&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;KEY&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The natural follow-up is to try the same steps as before:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sql&#34; data-lang=&#34;sql&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;INSERT&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;INTO&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;explicitPrimaryKey&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;DEFAULT&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;VALUES&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;INSERT&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;INTO&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;explicitPrimaryKey&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;DEFAULT&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;VALUES&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;INSERT&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;INTO&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;explicitPrimaryKey&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;DEFAULT&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;VALUES&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;SELECT&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;rowid&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;id&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;FROM&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;explicitPrimaryKey&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;table class=&#34;content-table&#34;&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th style=&#34;text-align: &#34;&gt;rowid&lt;/th&gt;
      &lt;th style=&#34;text-align: &#34;&gt;id&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td style=&#34;text-align: &#34;&gt;1&lt;/td&gt;
      &lt;td style=&#34;text-align: &#34;&gt;1&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&#34;text-align: &#34;&gt;2&lt;/td&gt;
      &lt;td style=&#34;text-align: &#34;&gt;2&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&#34;text-align: &#34;&gt;3&lt;/td&gt;
      &lt;td style=&#34;text-align: &#34;&gt;3&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;This is mostly the same, except the &lt;code&gt;rowid&lt;/code&gt; and &lt;code&gt;id&lt;/code&gt; columns both have the same value.
Taking the next step, we get:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sql&#34; data-lang=&#34;sql&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;DELETE&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;FROM&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;explicitPrimaryKey&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;WHERE&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;rowid&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;2&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;VACUUM&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;SELECT&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;rowid&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;id&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;FROM&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;explicitPrimaryKey&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;table class=&#34;content-table&#34;&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th style=&#34;text-align: &#34;&gt;rowid&lt;/th&gt;
      &lt;th style=&#34;text-align: &#34;&gt;id&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td style=&#34;text-align: &#34;&gt;1&lt;/td&gt;
      &lt;td style=&#34;text-align: &#34;&gt;1&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&#34;text-align: &#34;&gt;3&lt;/td&gt;
      &lt;td style=&#34;text-align: &#34;&gt;3&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Which is what we really wanted the first time! The rowids haven&amp;rsquo;t been changed by the &lt;code&gt;VACUUM&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;That&amp;rsquo;s because columns declared as &lt;code&gt;INTEGER PRIMARY KEY&lt;/code&gt; become an alias for the rowid column.
SQLite now knows that they shouldn&amp;rsquo;t be rearranged and leaves them alone.&lt;/p&gt;
&lt;h4 id=&#34;which-is-the-real-primary-key&#34;&gt;Which is the &amp;ldquo;real&amp;rdquo; primary key?&lt;/h4&gt;
&lt;p&gt;That&amp;rsquo;s a fun question.&lt;/p&gt;
&lt;p&gt;For you and me, creating and querying tables, the primary key is the one we
declare. It might be a column with &lt;code&gt;PRIMARY KEY&lt;/code&gt; as one of the constraints, or a &lt;code&gt;PRIMARY KEY&lt;/code&gt; table
constraint across multiple columns.&lt;/p&gt;
&lt;p&gt;For the internals of SQLite though, the &amp;ldquo;real&amp;rdquo; primary key is the rowid. That&amp;rsquo;s the value that gets
used in the internal data structures.&lt;/p&gt;
&lt;p&gt;If you use a column declared as &lt;code&gt;INTEGER PRIMARY KEY&lt;/code&gt;, then our &amp;ldquo;outside&amp;rdquo; primary key is an alias of
the &amp;ldquo;internal&amp;rdquo; primary key. Most of the time, we don&amp;rsquo;t need to worry about that though&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a href=&#34;#fn:2&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;h4 id=&#34;what-about-autoincrement&#34;&gt;What about &lt;code&gt;AUTOINCREMENT&lt;/code&gt;?&lt;/h4&gt;
&lt;p&gt;For most cases, you don&amp;rsquo;t want or need it.&lt;/p&gt;
&lt;p&gt;If you have an &lt;code&gt;INTEGER PRIMARY KEY&lt;/code&gt; column, because it&amp;rsquo;s an alias for the rowid, SQLite will
automatically provide a value for you if you don&amp;rsquo;t specify one. It&amp;rsquo;ll try to pick the next largest
number in that column. If you&amp;rsquo;ve managed to use up to the maximum 64-bit integer value
(9,223,372,036,854,775,807), it&amp;rsquo;ll pick numbers at random until it finds an unused one or gives up.&lt;/p&gt;
&lt;p&gt;If you add the &lt;code&gt;AUTOINCREMENT&lt;/code&gt; keyword to that column, the behaviour will change a bit. SQLite
guarantees that values in this column will always be larger than existing values. If you insert the
maximum integer value to this column, SQLite will not allow any more rows to be inserted in the
table.&lt;/p&gt;
&lt;h4 id=&#34;other-special-or-unusual-things&#34;&gt;Other special or unusual things&lt;/h4&gt;
&lt;p&gt;Primary keys don&amp;rsquo;t have to always be integers. You can have all sorts:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sql&#34; data-lang=&#34;sql&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;-- A table of books, using the ISBN as the primary key
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;CREATE&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;TABLE&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;books&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;isbn&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;TEXT&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;PRIMARY&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;KEY&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;title&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;TEXT&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;NOT&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;NULL&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;-- A table matching students to enrolled courses.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;-- The combination of studentId and courseId must be unique,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;-- you can&amp;#39;t have a student enrolling in any given course more than once.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;CREATE&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;TABLE&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;enrollments&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;studentId&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;INTEGER&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;NOT&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;NULL&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;courseId&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;TEXT&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;NOT&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;NULL&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;enrollmentDate&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;DATE&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;PRIMARY&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;KEY&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;studentId&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;courseId&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;It&amp;rsquo;s interesting to note that in these cases, the &lt;code&gt;PRIMARY KEY&lt;/code&gt; is basically just a
&lt;code&gt;UNIQUE&lt;/code&gt; constraint. Because of this, SQLite allows NULL values unless the column is declared as &lt;code&gt;NOT NULL&lt;/code&gt;. This isn&amp;rsquo;t standard SQL, but is a long-standing quirk of SQLite.&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sql&#34; data-lang=&#34;sql&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;CREATE&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;TABLE&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;booksAllowingNull&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;isbn&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;TEXT&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;PRIMARY&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;KEY&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;title&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;TEXT&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;author&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;TEXT&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;INSERT&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;INTO&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;booksAllowingNull&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;VALUES&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;NULL&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;The Colour of Magic&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;Pratchett, T&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;INSERT&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;INTO&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;booksAllowingNull&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;VALUES&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;NULL&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;The Light Fantastic&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;Pratchett, T&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Will result in two rows being inserted into the table, both with a &lt;code&gt;NULL&lt;/code&gt; value for &lt;code&gt;isbn&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;If we change this to:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sql&#34; data-lang=&#34;sql&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;CREATE&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;TABLE&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;booksWithoutNull&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;isbn&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;TEXT&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;PRIMARY&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;KEY&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;NOT&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;NULL&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;title&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;TEXT&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;author&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;TEXT&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;INSERT&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;INTO&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;booksWithoutNull&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;VALUES&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;NULL&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;The Colour of Magic&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;Pratchett, T&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The &lt;code&gt;INSERT&lt;/code&gt; will fail, as we&amp;rsquo;d expected.&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s worth noting that again, columns declared &lt;code&gt;INTEGER PRIMARY KEY&lt;/code&gt; get special treatment and are
automatically assumed to be &lt;code&gt;NOT NULL&lt;/code&gt; even if you don&amp;rsquo;t specify it&lt;sup id=&#34;fnref:3&#34;&gt;&lt;a href=&#34;#fn:3&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;3&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;h5 id=&#34;rowid-aliases&#34;&gt;Rowid aliases&lt;/h5&gt;
&lt;p&gt;You can refer to the rowid using the names &lt;code&gt;rowid&lt;/code&gt;, &lt;code&gt;_rowid_&lt;/code&gt; or &lt;code&gt;oid&lt;/code&gt;. But you can also create
columns with those names. So for this table, the internal rowid value can&amp;rsquo;t be accessed from the outside:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sql&#34; data-lang=&#34;sql&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;CREATE&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;TABLE&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;rowidNames&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;rowid&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;INTEGER&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;_rowid_&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;INTEGER&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;oid&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;INTEGER&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;I have no idea why anyone would choose do this, but it turns out you can!&lt;/p&gt;
&lt;h5 id=&#34;without-rowid-tables&#34;&gt;&lt;code&gt;WITHOUT ROWID&lt;/code&gt; tables&lt;/h5&gt;
&lt;p&gt;It is possible to declare a table as explicitly &lt;em&gt;not&lt;/em&gt; having a rowid. This isn&amp;rsquo;t as common and
deserves an article to itself. For now it&amp;rsquo;s enough to note that this is a thing that is
possible. If you want to read more about this feature, there is an excellent description
&lt;a href=&#34;https://sqlite.org/withoutrowid.html&#34;&gt;on the SQLite website&lt;/a&gt;.&lt;/p&gt;
&lt;h4 id=&#34;references&#34;&gt;References&lt;/h4&gt;
&lt;p&gt;The SQLite website has excellent, detailed information about:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://www.sqlite.org/rowidtable.html&#34;&gt;Tables using rowids&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.sqlite.org/autoinc.html&#34;&gt;The autoincrement keyword&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.sqlite.org/lang_createtable.html#primkeyconst&#34;&gt;Primary key constraints&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.sqlite.org/lang_vacuum.html#how_vacuum_works&#34;&gt;How &lt;code&gt;VACUUM&lt;/code&gt; works&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34;&gt;
&lt;p&gt;Copying rows between tables with &lt;code&gt;INSERT INTO table2 SELECT * FROM table1;&lt;/code&gt; won&amp;rsquo;t keep the
same rowids either.&amp;#160;&lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:2&#34;&gt;
&lt;p&gt;You might sometimes care about this for performance reasons, since lookups made by rowid are
usually faster than other primary key types.&amp;#160;&lt;a href=&#34;#fnref:2&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:3&#34;&gt;
&lt;p&gt;This automatic behaviour also happens for &lt;code&gt;STRICT&lt;/code&gt; and &lt;code&gt;WITHOUT ROWID&lt;/code&gt; tables.&amp;#160;&lt;a href=&#34;#fnref:3&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</description>
</item>
    
    <item>
<title>Base 3.1.0 Released</title>
<link>https://menial.co.uk/blog/2025/10/06/base-3.1.0-released/</link>
<pubDate>Mon, 06 Oct 2025 07:00:00 +0000</pubDate>
      
      <guid isPermaLink="false">https://menial.co.uk/blog/2025/10/06/base-3.1.0-released/</guid>
<description>&lt;p&gt;Base 3.1.0 is now &lt;a href=&#34;https://menial.co.uk/base/&#34;&gt;available for download&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;This update adopts the new Liquid Glass look on macOS 26 and contains a good selection of
quality-of-life improvements to make using Base even nicer.&lt;/p&gt;
&lt;p&gt;Because Base is now built for macOS 26, it adopts the new Liquid Glass design. This doesn&amp;rsquo;t
radically change the appearance of the app - all the parts of the interface are still in roughly the
same places. Many parts of the UI are a bit chunkier now, so to adapt to this the window size is
slightly larger and the sidebar a bit wider by default.&lt;/p&gt;
&lt;p&gt;The other changes and improvements are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Updated SQLite to &lt;a href=&#34;https://www.sqlite.org/releaselog/3_50_4.html&#34;&gt;v3.50.4&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Added a new button (&lt;svg class=&#34;bi&#34; width=&#34;16&#34; height=&#34;16&#34;&gt;&lt;use href=&#34;#arrow-down-up&#34;&gt;&lt;/use&gt;&lt;/svg&gt;)
which provides a way to clear any sorting or filtering options in the data browser&lt;/li&gt;
&lt;li&gt;Adding a button to jump to a table in the database size inspector&lt;/li&gt;
&lt;li&gt;Adding an option to always enable foreign keys when opening a database (defaults to off)&lt;/li&gt;
&lt;li&gt;Adding an option to hide shadow tables (defaults to on)&lt;/li&gt;
&lt;li&gt;Fixed a bug causing the add &amp;amp; remove filter buttons to disappear&lt;/li&gt;
&lt;li&gt;Re-ordering filter predicates to put more commonly used ones at the top&lt;/li&gt;
&lt;li&gt;When showing the table editor, the table name field is automatically selected&lt;/li&gt;
&lt;li&gt;When importing or exporting finishes, the app will show a notification&lt;/li&gt;
&lt;li&gt;Base will now sometimes, politely, ask for a review on the App Store&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You can &lt;a href=&#34;https://menial.co.uk/base/&#34;&gt;download Base directly&lt;/a&gt;, get it on the
&lt;a href=&#34;https://apps.apple.com/us/app/base-sqlite-editor/id6744867438&#34;&gt;Mac App Store&lt;/a&gt; or via
&lt;a href=&#34;https://go.setapp.com/stp69?refAppID=135&amp;amp;stc=p&#34;&gt;Setapp&lt;/a&gt;.&lt;/p&gt;
&lt;svg style=&#34;display: none;&#34;&gt;
    &lt;defs&gt;
        &lt;symbol id=&#34;arrow-down-up&#34; viewBox=&#34;0 0 16 16&#34;&gt;
        &lt;path fill-rule=&#34;evenodd&#34; d=&#34;M11.5 15a.5.5 0 0 0 .5-.5V2.707l3.146 3.147a.5.5 0 0 0 .708-.708l-4-4a.5.5 0 0 0-.708 0l-4 4a.5.5 0 1 0 .708.708L11 2.707V14.5a.5.5 0 0 0 .5.5m-7-14a.5.5 0 0 1 .5.5v11.793l3.146-3.147a.5.5 0 0 1 .708.708l-4 4a.5.5 0 0 1-.708 0l-4-4a.5.5 0 0 1 .708-.708L4 13.293V1.5a.5.5 0 0 1 .5-.5&#34;/&gt;
        &lt;/symbol&gt;
    &lt;/defs&gt;
&lt;/svg&gt;</description>
</item>
    
    <item>
<title>Retrospective on making Base 3</title>
<link>https://menial.co.uk/blog/2025/09/29/retrospective-on-making-base-3/</link>
<pubDate>Mon, 29 Sep 2025 06:30:00 +0000</pubDate>
      
      <guid isPermaLink="false">https://menial.co.uk/blog/2025/09/29/retrospective-on-making-base-3/</guid>
<description>&lt;p&gt;The first version of Base was released in late 2008. It grew into a v2 by early 2011 and kept going
until the last feature release in 2020 (with a few bugfix releases after).&lt;/p&gt;
&lt;p&gt;On the 4th July 2020, I started a new branch for Base v3.&lt;/p&gt;
&lt;h4 id=&#34;getting-started&#34;&gt;Getting Started&lt;/h4&gt;
&lt;p&gt;In mid-2020, the codebase for Base was struggling a bit. It was mostly Objective-C (which I still
love) with some tentative Swift adoption dotted here and there. Any individual unit of code wasn&amp;rsquo;t
too bad, but the whole was a bit tangled and causing headaches for making larger changes.&lt;/p&gt;
&lt;p&gt;So I decided a clean slate was needed.&lt;/p&gt;
&lt;p&gt;I just &lt;em&gt;know&lt;/em&gt; people will be wincing at that declaration. I do too now. Sometime in the last 20
years I had read Joel Spolsky&amp;rsquo;s blog post on how rewriting a product is the
&lt;a href=&#34;https://www.joelonsoftware.com/2000/04/06/things-you-should-never-do-part-i/&#34;&gt;single worst strategic mistake a software company can make&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I didn&amp;rsquo;t think this really applied to me. Not for egotistical &amp;ldquo;I&amp;rsquo;m better than that&amp;rdquo; reasons, more
because I didn&amp;rsquo;t think the situation was the same. Much of Spolsky&amp;rsquo;s post focuses on how reading
code is more difficult than writing, on how teams need spend time figuring out how the code works.
But that wouldn&amp;rsquo;t affect me, because &lt;em&gt;I wrote all of it&lt;/em&gt;! I know why that weird edge case
is there. I know what idiot (me) wrote that dodgy function.&lt;/p&gt;
&lt;p&gt;With that in mind, I created a new app target in Xcode and made a start.&lt;/p&gt;
&lt;h4 id=&#34;mistakes-i-made&#34;&gt;Mistakes I made&lt;/h4&gt;
&lt;p&gt;These are just the highlights. There were plenty of smaller ones that weren&amp;rsquo;t particularly notable
and didn&amp;rsquo;t have much effect on the whole - except possibly in aggregate.&lt;/p&gt;
&lt;h5 id=&#34;1-rewriting-functioning-code&#34;&gt;1. Rewriting functioning code&lt;/h5&gt;
&lt;p&gt;As we&amp;rsquo;ve already noted, I wrote the original code. I knew all the shortcuts and gnarly corners.
Naturally I decided to fix all of them as I went along. The problem here turned out to be much
simpler than you might expect: Time.&lt;/p&gt;
&lt;p&gt;All of those shortcuts and gnarly corners had accumulated over 12 years. Yes, I knew why they were
there and wouldn&amp;rsquo;t have to spend time rediscovering old problems, but I would still have to
carefully preserve their behaviours in any new systems and ensure nothing was lost in translation.
And that takes time.&lt;/p&gt;
&lt;h5 id=&#34;2-adopting-swiftui-too-quickly&#34;&gt;2. Adopting SwiftUI too quickly&lt;/h5&gt;
&lt;p&gt;I decided to adopt SwiftUI in 2020. This was, in retrospect, optimistic at best.&lt;/p&gt;
&lt;p&gt;SwiftUI was barely a year old, still evolving and changing with each OS release. What worked in one
version would be tweaked in the next. UI that worked fine yesterday looked different today.&lt;/p&gt;
&lt;p&gt;The learning curve was steep, and I was trying to build release-worthy software while simultaneously
learning how to use the tools. And the new tools didn&amp;rsquo;t have all the features of the old way. And
the tools changed between OS versions.&lt;/p&gt;
&lt;p&gt;Over the course of building a feature I&amp;rsquo;d realise I was missing important functionality and have to
back out huge chunks of work and switch back to AppKit&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;p&gt;Please don&amp;rsquo;t take this item as me blaming SwiftUI for the problems. Rather the problem was my not
knowing when and when not to use it and ending up trying to force it into shapes it didn&amp;rsquo;t fit.&lt;/p&gt;
&lt;h5 id=&#34;3-aiming-for-perfection&#34;&gt;3. Aiming for perfection&lt;/h5&gt;
&lt;p&gt;Since this was a from-scratch rewrite, I wanted it to be done &lt;em&gt;right&lt;/em&gt;&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a href=&#34;#fn:2&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt;. And I wasn&amp;rsquo;t happy with
most of the progress. This led to frustratingly slow work and trying several times to get different
parts right.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve only recently come across a quote from &lt;a href=&#34;https://www.themarginalian.org/2014/01/29/ira-glass-success-daniel-sax/&#34;&gt;Ira Glass on the taste-skill gap&lt;/a&gt;,
describing a period of time where your skills haven&amp;rsquo;t caught up to your taste. And that&amp;rsquo;s totally
where I was. I &lt;em&gt;knew&lt;/em&gt; the app could be better, that was the reason for the rewrite! But I wasn&amp;rsquo;t
moving on once it was better, because I felt it wasn&amp;rsquo;t &lt;em&gt;better enough&lt;/em&gt;.&lt;/p&gt;
&lt;h5 id=&#34;4-taking-the-difficult-path&#34;&gt;4. Taking the difficult path&lt;/h5&gt;
&lt;p&gt;More than once I had a choice to either accept an easy or quick solution with a compromise, or do
it the hard way and try for a better result. Apparently my default is to choose the hard way - it
requires active mental effort to take an easier, but &amp;ldquo;less ideal&amp;rdquo; route.&lt;/p&gt;
&lt;p&gt;One example was the SQL editor. It&amp;rsquo;s a text view that needs syntax highlighting and autocomplete.
There was an basic implementation in the existing app, which had reliability problems and did want
an overhaul. There wasn&amp;rsquo;t a third party library that I could just drop in without customising, so I
started making my own text view to do the job. Before long I was bogged down, trying to wrangle
&lt;code&gt;NSTextView&lt;/code&gt; to do my bidding, make the autocomplete work how it should and also keep working on the
rest of the app. It never did end up the way I wanted.&lt;/p&gt;
&lt;p&gt;This was far more work than using someone else&amp;rsquo;s existing control and customising it to my needs.&lt;/p&gt;
&lt;h4 id=&#34;stuff-outside-my-control&#34;&gt;Stuff outside my control&lt;/h4&gt;
&lt;p&gt;2020 to 2025 has been an odd time to put it mildly. We&amp;rsquo;ve all seen a lot of changes. Some of those
were disruptive and meant that I didn&amp;rsquo;t work on Base for longer stretches of time than I&amp;rsquo;d have
liked.  There&amp;rsquo;s very little I could have done to mitigate this, but it&amp;rsquo;s important to recognise that
sometimes life just gets in the way of your plans.&lt;/p&gt;
&lt;h4 id=&#34;trying-again&#34;&gt;Trying again&lt;/h4&gt;
&lt;p&gt;By 2023, almost exactly three years later, I had to acknowledge that I&amp;rsquo;d dug myself into a bit of
a hole.&lt;/p&gt;
&lt;p&gt;The original branch&lt;sup id=&#34;fnref:3&#34;&gt;&lt;a href=&#34;#fn:3&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;3&lt;/a&gt;&lt;/sup&gt;, started in July 2020, had grown into something of a monster.
Three years of off-and-on (mostly off) development, thousands of lines of new code, and still no
useful product. The git history still contained a bunch of half finished features and experiments.&lt;/p&gt;
&lt;p&gt;So after a bit of thinking, I started again. The git commit reads:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;Add new Base3 target

I’m trying again. Again.
Back to basics this time. Try not to chase the new shiny.
&lt;/code&gt;&lt;/pre&gt;&lt;h4 id=&#34;what-actually-worked&#34;&gt;What actually worked&lt;/h4&gt;
&lt;p&gt;A much simpler plan:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Translate the app, feature by feature, from Objective-C to Swift&lt;/li&gt;
&lt;li&gt;Create Swift Packages for each feature and core components&lt;sup id=&#34;fnref:4&#34;&gt;&lt;a href=&#34;#fn:4&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;4&lt;/a&gt;&lt;/sup&gt;&lt;/li&gt;
&lt;li&gt;Rely on some third-party libraries&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;No sweeping architectural changes. No adopting of the latest frameworks. Just a methodical,
mechanical translation of working code into a more modern language, with some tidying as we go.&lt;/p&gt;
&lt;h5 id=&#34;translating-without-actively-rewriting&#34;&gt;Translating without actively rewriting&lt;/h5&gt;
&lt;p&gt;Translating from Objective-C to Swift improved matters quite a lot. Partly because Swift is more
terse and so the code is more compact and readable. Partly because of the hundreds (if not
thousands) of micro-improvements you pick up as part of the move.&lt;/p&gt;
&lt;h5 id=&#34;using-plenty-of-packages&#34;&gt;Using plenty of packages&lt;/h5&gt;
&lt;p&gt;Swift Package Manager turned out to be really useful for this sort of thing for a few reasons:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;It enforced better separation of concerns by stopping shortcuts. Code in another package isn&amp;rsquo;t as
freely available to you, so you have to consider what you&amp;rsquo;re doing more carefully.
Related: Adding a dependency to a package feels like a big decision and puts a psychological
barrier in the way of potential bad habits.&lt;/li&gt;
&lt;li&gt;It broke the process down into clearly definable steps. I could set a goal of migrating the table
editor. Then, when it was done, there was a functioning table editor package and I could delete
the original code. This was also incredibly helpful to help prevent myself from feeling
overwhelmed.&lt;/li&gt;
&lt;li&gt;It made testing much more routine. This could possibly come under point 1 above, but is worth
pointing out explicitly. Testing isolated packages is just so much nicer than testing components
within an entire app. I guess it&amp;rsquo;s not technically very different, but it &lt;em&gt;feels&lt;/em&gt; different.&lt;/li&gt;
&lt;/ol&gt;
&lt;h5 id=&#34;third-party-libraries&#34;&gt;Third-party libraries&lt;/h5&gt;
&lt;p&gt;The key ones are &lt;a href=&#34;https://github.com/groue/GRDB.swift&#34;&gt;GRDB&lt;/a&gt; and &lt;a href=&#34;https://github.com/krzyzanowskim/STTextView&#34;&gt;STTextView&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;GRDB is an incredibly well made and popular SQLite toolkit. Base is only using a portion of what it
can do and I&amp;rsquo;d like to write more about it in the future.&lt;/p&gt;
&lt;p&gt;STTextView is a really great text editor with plenty of room for customisation. Trying to build my
own - when libraries like this exist - is not justifiable.&lt;/p&gt;
&lt;h5 id=&#34;scheming&#34;&gt;Scheming&lt;/h5&gt;
&lt;p&gt;Base is distributed through this website, the Mac App Store and Setapp. Each comes with a different
set of requirements for updating, licensing and distribution rules. The previous version of Base was
developed before the App Store and Setapp were around. Each became a separate app target within
Xcode, customised to build with slightly different files and settings.&lt;/p&gt;
&lt;p&gt;This time, I have one target with three build schemes. Each has scheme has separate build
configurations in &lt;code&gt;.xcconfig&lt;/code&gt; files which link different libraries and apply different tweaks.
It&amp;rsquo;s so much easier to reason about and maintain.&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&#34;some-observations&#34;&gt;Some observations&lt;/h4&gt;
&lt;p&gt;The decision to start a rewrite &lt;em&gt;again&lt;/em&gt; in 2023 was less hare-brained than the first time. This time
I had a lot more pieces lined up for use. I was still working inside the same Xcode project, so that
parts could easily be dragged from one app target to another for re-use. Most importantly, because
it wasn&amp;rsquo;t so much a rewrite as a translation.&lt;/p&gt;
&lt;p&gt;I did actually adopt some SwiftUI in places where it made sense. And (again) in a few places it
didn&amp;rsquo;t. But this time I&amp;rsquo;d learned: If you&amp;rsquo;re trying to do something with SwiftUI and hitting
problems that can&amp;rsquo;t be solved quickly, give up! Several times I saw a place where I thought it could
save me time, only to hit problems. Each time I tossed the code and went back to AppKit.&lt;/p&gt;
&lt;h4 id=&#34;what-i-learned&#34;&gt;What I learned&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Spolsky was absolutely right and my circumstances weren&amp;rsquo;t as different as I thought&lt;sup id=&#34;fnref:5&#34;&gt;&lt;a href=&#34;#fn:5&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;5&lt;/a&gt;&lt;/sup&gt;&lt;/li&gt;
&lt;li&gt;Psychological traps are everywhere. When you know your own code intimately, you see all its flaws.
The urge to fix everything can be distracting and almost overwhelming.&lt;/li&gt;
&lt;li&gt;Code that works is valuable, even if it&amp;rsquo;s not pretty&lt;/li&gt;
&lt;li&gt;I&amp;rsquo;m more easily distracted than I realised&lt;/li&gt;
&lt;li&gt;I&amp;rsquo;m lucky that I don&amp;rsquo;t depend on the income from this project&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;None of this is new. Most of it has been industry dogma for years, yet we still fall into the
same traps.&lt;/p&gt;
&lt;p&gt;Base 3 eventually shipped. It took rather longer than I&amp;rsquo;d hoped.&lt;/p&gt;
&lt;p&gt;The app is better for the rewrite - it&amp;rsquo;s faster, nicer to use, and has a few new features. Inside,
it has better architecture and is more maintainable. People using it will only notice the first
part, they can&amp;rsquo;t see inside - but I know.&lt;/p&gt;
&lt;h4 id=&#34;suggestions-for-others&#34;&gt;Suggestions for others&lt;/h4&gt;
&lt;p&gt;Here I could try to lay down some rules - or at least guidelines - that I think others should follow
when contemplating a rewrite of some software. To be honest, I don&amp;rsquo;t feel comfortable doing that.&lt;/p&gt;
&lt;p&gt;If you&amp;rsquo;ve read this far, you can see the mistakes I made. You can tell yourself you&amp;rsquo;ll learn from
them, or that you wouldn&amp;rsquo;t have made them in the first place. Or maybe you&amp;rsquo;ll be like I was and
think that your circumstances are different. It may be worth taking a step back and checking whether
that&amp;rsquo;s actually true.&lt;/p&gt;
&lt;p&gt;Remember: If you can&amp;rsquo;t set a good example, you can at least be a horrible warning&lt;sup id=&#34;fnref:6&#34;&gt;&lt;a href=&#34;#fn:6&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;6&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;div class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34;&gt;
&lt;p&gt;Note that there&amp;rsquo;s nothing wrong with AppKit. Using it is not a defeat. It&amp;rsquo;s just that I should
have been more judicious about where I didn&amp;rsquo;t use it.&amp;#160;&lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:2&#34;&gt;
&lt;p&gt;For my definition of &amp;ldquo;right&amp;rdquo;. Or at least better.&amp;#160;&lt;a href=&#34;#fnref:2&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:3&#34;&gt;
&lt;p&gt;The fact this branch had lived so long might be considered a red flag in and of itself.&amp;#160;&lt;a href=&#34;#fnref:3&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:4&#34;&gt;
&lt;p&gt;After reading a post by Simon B. Støvring. The post seems to be gone, but there&amp;rsquo;s a
&lt;a href=&#34;https://async.techconnection.io/talks/frenchkit/swift-connection-2023/simon-b-stovring-achieving-loose-coupling-with-pure-dependency-injection-and-the-composition-root/&#34;&gt;conference talk available&lt;/a&gt;&amp;#160;&lt;a href=&#34;#fnref:4&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:5&#34;&gt;
&lt;p&gt;I&amp;rsquo;d recommend the book &lt;a href=&#34;https://sites.prh.com/how-big-things-get-done-book&#34;&gt;How Big Things Get Done&lt;/a&gt;
to pretty much anyone building anything. It specifically calls out people saying
&amp;ldquo;but my project is different!&amp;rdquo;. With references and citations. It stings a bit.&amp;#160;&lt;a href=&#34;#fnref:5&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:6&#34;&gt;
&lt;p&gt;&lt;a href=&#34;https://www.goodreads.com/quotes/1963-if-you-can-t-be-a-good-example-then-you-ll-just&#34;&gt;Quote by Catherine Aird&lt;/a&gt;.
I always thought this came from Terry Pratchett, but when searching for a link I found the original.&amp;#160;&lt;a href=&#34;#fnref:6&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</description>
</item>
    
    <item>
<title>Base 3.0.2 Released</title>
<link>https://menial.co.uk/blog/2025/08/31/base-3.0.2-released/</link>
<pubDate>Sun, 31 Aug 2025 07:30:00 +0000</pubDate>
      
      <guid isPermaLink="false">https://menial.co.uk/blog/2025/08/31/base-3.0.2-released/</guid>
<description>&lt;p&gt;Base 3.0.2 is now available for download.&lt;/p&gt;
&lt;p&gt;This is a bug-fixing release, containing the following changes:&lt;/p&gt;
&lt;p&gt;From Base 3.0.1:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The last selected table and tab in a database is now remembered between launches&lt;/li&gt;
&lt;li&gt;The database size inspector chart was missing the legend - it&amp;rsquo;s back now&lt;/li&gt;
&lt;li&gt;When entering numbers with leading zeros in a &lt;code&gt;TEXT&lt;/code&gt; column, the leading zeros are no longer removed&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;From Base 3.0.2:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The &amp;ldquo;show internal tables&amp;rdquo; option was being ignored - it is now respected and defaults to being switched off&lt;/li&gt;
&lt;li&gt;Using the tab key to switch between editing table cells no longer causes the selected row to be changed&lt;/li&gt;
&lt;li&gt;Whitespace is now ignored when checking license codes (direct-sale version only)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You can &lt;a href=&#34;https://menial.co.uk/base/&#34;&gt;download Base directly&lt;/a&gt;, get it on the
&lt;a href=&#34;https://apps.apple.com/us/app/base-sqlite-editor/id6744867438&#34;&gt;Mac App Store&lt;/a&gt; or via
&lt;a href=&#34;https://go.setapp.com/stp69?refAppID=135&amp;amp;stc=p&#34;&gt;Setapp&lt;/a&gt;.&lt;/p&gt;</description>
</item>
    
    <item>
<title>Base 3.0 Released</title>
<link>https://menial.co.uk/blog/2025/08/16/base-3.0-released/</link>
<pubDate>Sat, 16 Aug 2025 07:00:00 +0000</pubDate>
      
      <guid isPermaLink="false">https://menial.co.uk/blog/2025/08/16/base-3.0-released/</guid>
<description>&lt;img src=&#34;https://menial.co.uk/img/base3-icon-256.png&#34; srcset=&#34;https://menial.co.uk/img/base3-icon-256.png 1x, https://menial.co.uk/img/base3-icon-256@2x.png 2x&#34; alt=&#34;Base app icon&#34; width=&#34;192&#34; height=&#34;192&#34; class=&#34;leftinset&#34;&gt;
&lt;p&gt;&lt;br /&gt;I&#39;m incredibly happy to announce that Base 3.0 is now available for download.&lt;/p&gt;
&lt;p&gt;The app has had a major overhaul to all areas while keeping a familiar UI.&lt;br /&gt;
Base 3 is faster, more capable and even nicer to use.&lt;/p&gt;
&lt;p class=&#34;clearfix&#34;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;Highlights of this version include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Working with multiple attached databases&lt;/li&gt;
&lt;li&gt;Support for more SQLite features like:
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;WITHOUT ROWID&lt;/code&gt; &amp;amp; &lt;code&gt;STRICT&lt;/code&gt; tables&lt;/li&gt;
&lt;li&gt;Partial indexes (with &lt;code&gt;WHERE&lt;/code&gt; clauses)&lt;/li&gt;
&lt;li&gt;Named table constraints&lt;/li&gt;
&lt;li&gt;Generated columns&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Better syntax highlighting &amp;amp; autocomplete in the SQL editor&lt;/li&gt;
&lt;li&gt;It&amp;rsquo;s faster at all operations, especially import &amp;amp; export&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;An overview of the application with screenshots is available &lt;a href=&#34;https://menial.co.uk/base/&#34;&gt;on the product page&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&#34;pricing-and-requirements&#34;&gt;Pricing and requirements&lt;/h3&gt;
&lt;p&gt;Base 3 is being distributed as a separate app to Base 2&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;. It&amp;rsquo;s free to download with
some features limited to those who purchase an unlock license.&lt;/p&gt;
&lt;p&gt;Licenses are priced at £29.99 (or local equivalent) and can be &lt;a href=&#34;https://menial.co.uk/base/buy&#34;&gt;purchased here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;People who are currently using Base 2 can continue to do so. It won&amp;rsquo;t auto-update to Base 3
and there will not be any more updates to Base 2.&lt;/p&gt;
&lt;p&gt;Base 3 requires macOS 15 (Sequoia) or newer.&lt;/p&gt;
&lt;div class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34;&gt;
&lt;p&gt;Except for people using Base via Setapp, which is subscription based.&amp;#160;&lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</description>
</item>
    
    <item>
<title>Base 2.5 Released</title>
<link>https://menial.co.uk/blog/2020/04/02/base-2.5-released/</link>
<pubDate>Thu, 02 Apr 2020 18:10:45 +0100</pubDate>
      
      <guid isPermaLink="false">https://menial.co.uk/blog/2020/04/02/base-2.5-released/</guid>
<description>&lt;p&gt;After a bit of a gap, I&amp;rsquo;m pleased to say a new update to &lt;a href=&#34;https://menial.co.uk/base/&#34;&gt;Base&lt;/a&gt; is available to download.&lt;/p&gt;
&lt;p&gt;The highlights include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Modernising the UI a bit to bring it more in line with recent versions of macOS.
It looks familiar, but &lt;em&gt;feels less old fashioned&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;Adding syntax highlighting themes. You can now have different text colours for light &amp;amp; dark modes which switch automatically.&lt;/li&gt;
&lt;li&gt;Adding support for macOS Dark Mode throughout the app. No more eye glare!&lt;/li&gt;
&lt;li&gt;All versions of Base now require macOS 10.14 (Mojave) and have enabled sandboxing&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;There&amp;rsquo;s other minor bugfixes in there too which sort out some UI glitches and misplaced buttons.&lt;/p&gt;
&lt;p&gt;You can buy or download a &lt;a href=&#34;https://menial.co.uk/base/&#34;&gt;trial version from the homepage&lt;/a&gt;. You can also buy Base from the &lt;a href=&#34;https://apps.apple.com/app/apple-store/id402383384?pt=16693&amp;amp;ct=blog&amp;amp;mt=8&#34;&gt;Mac App Store&lt;/a&gt; or download from &lt;a href=&#34;https://go.setapp.com/stp69?stc=b&#34;&gt;Setapp&lt;/a&gt;&lt;/p&gt;
</description>
</item>
    
    <item>
<title>Base 2.4.11 Released</title>
<link>https://menial.co.uk/blog/2017/03/21/base-2.4.11-released/</link>
<pubDate>Tue, 21 Mar 2017 20:03:00 +0000</pubDate>
      
      <guid isPermaLink="false">https://menial.co.uk/blog/2017/03/21/base-2.4.11-released/</guid>
<description>&lt;p&gt;Base 2.4.11 is now available to download for direct sale, Setapp and Mac App Store users.&lt;/p&gt;
&lt;p&gt;This update fixes a bug causing Base to sometimes crash when re-opening a database on launch. It also updates to SQLite version 3.17.0.&lt;/p&gt;
&lt;p&gt;Base 2.4.11 can be downloaded &lt;a href=&#34;http://menial.co.uk/base/&#34;&gt;from the product page&lt;/a&gt;, &lt;a href=&#34;https://go.setapp.com/stp69?stc=b&#34;&gt;Setapp&lt;/a&gt; or &lt;a href=&#34;http://itunes.apple.com/app/base/id402383384?mt=12&#34;&gt;the Mac App Store&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;As always, &lt;a href=&#34;http://update.menial.co.uk/releasenotes/base/2107/&#34;&gt;release notes&lt;/a&gt; are available and if you’ve found a bug or have a question, please do &lt;a href=&#34;http://menial.co.uk/contact/&#34;&gt;get in touch&lt;/a&gt;.&lt;/p&gt;
</description>
</item>
    
    <item>
<title>Base on Setapp</title>
<link>https://menial.co.uk/blog/2017/01/25/base-on-setapp/</link>
<pubDate>Wed, 25 Jan 2017 13:00:00 +0000</pubDate>
      
      <guid isPermaLink="false">https://menial.co.uk/blog/2017/01/25/base-on-setapp/</guid>
<description>&lt;p&gt;&lt;a href=&#34;https://go.setapp.com/stp69?stc=b&#34;&gt;&lt;img src=&#34;https://menial.co.uk/uploads/setapp-icon-128.png&#34;  height=&#34;128px&#34; width=&#34;128px&#34; alt=&#34;Setapp icon&#34; title=&#34;Setapp icon&#34; class=&#34;leftinset&#34;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Base is now available on &lt;a href=&#34;https://go.setapp.com/stp69?stc=b&#34;&gt;Setapp&lt;/a&gt;, a new service from MacPaw which gives you access to more than 50 great apps for $9.99 per month, with a month&amp;rsquo;s free trial to start.&lt;/p&gt;
&lt;p&gt;When you subscribe to Setapp, you have unlimited access to a curated library of useful apps. There are no upgrade fees, no ads, and no in-app purchases. One fixed fee gets you all of it.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://go.setapp.com/stp69?stc=b&#34;&gt;Go ahead and try Setapp&lt;/a&gt;. I think you&amp;rsquo;ll like it.&lt;/p&gt;
</description>
</item>
    
    <item>
<title>InkStamp - A rubber stamp sticker pack for iMessage</title>
<link>https://menial.co.uk/blog/2016/09/13/inkstamp-a-rubber-stamp-sticker-pack-for-imessage/</link>
<pubDate>Tue, 13 Sep 2016 19:42:00 +0000</pubDate>
      
      <guid isPermaLink="false">https://menial.co.uk/blog/2016/09/13/inkstamp-a-rubber-stamp-sticker-pack-for-imessage/</guid>
<description>&lt;p&gt;I&amp;rsquo;ve made something small and fun. A rubber stamp sticker pack for iOS 10!&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s a collection of iMessage stickers in the style of rubber stamps. There are stamps for likes and dislikes,
reactions, star ratings, and a bunch of other odds and ends.&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s a couple of images showing them in use:&lt;/p&gt;
&lt;p&gt;
	&lt;a href=&#34;https://menial.co.uk/uploads/inkstamp-ss1-full.png&#34;&gt;&lt;img src=&#34;https://menial.co.uk/uploads/inkstamp-ss1.png&#34; alt=&#34;InkStamp sticker pack screenshot&#34; width=&#34;225&#34; height=&#34;400&#34; style=&#34;border: 1px solid #333;&#34; /&gt;&lt;/a&gt;
		&lt;a href=&#34;https://menial.co.uk/uploads/inkstamp-ss2-full.png&#34;&gt;&lt;img src=&#34;https://menial.co.uk/uploads/inkstamp-ss2.png&#34; alt=&#34;InkStamp sticker pack screenshot&#34; width=&#34;225&#34; height=&#34;400&#34; style=&#34;border: 1px solid #333;&#34; /&gt;&lt;/a&gt;
&lt;/p&gt;
&lt;p&gt;The InkStamp sticker pack is a 99¢ (79p) download available &lt;a href=&#34;https://itunes.apple.com/us/app/inkstamp/id1152305908?ls=1&amp;amp;mt=8&amp;amp;app=messages&#34;&gt;from the App Store for iMessage&lt;/a&gt;.&lt;/p&gt;
</description>
</item>
    
    <item>
<title>Base 2.4.10 Released</title>
<link>https://menial.co.uk/blog/2015/06/05/base-2.4.10-released/</link>
<pubDate>Fri, 05 Jun 2015 07:53:00 +0000</pubDate>
      
      <guid isPermaLink="false">https://menial.co.uk/blog/2015/06/05/base-2.4.10-released/</guid>
<description>&lt;p&gt;Base 2.4.10 is now available to download for both direct sale and Mac App Store users. This update fixes some minor problems and updates to the latest version of SQLite.&lt;/p&gt;
&lt;p&gt;Base 2.4.10 can be downloaded &lt;a href=&#34;http://menial.co.uk/base/&#34;&gt;from the product page&lt;/a&gt; or from &lt;a href=&#34;http://itunes.apple.com/app/base/id402383384?mt=12&#34;&gt;the Mac App Store&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;As always, the full list of changes can be found in the &lt;a href=&#34;http://update.menial.co.uk/releasenotes/base/1678/&#34;&gt;release notes&lt;/a&gt; and if you’ve found a bug or have a question, please do &lt;a href=&#34;http://menial.co.uk/contact/&#34;&gt;get in touch&lt;/a&gt;.&lt;/p&gt;
</description>
</item>
    
    <item>
<title>Base 2.4.9 Released</title>
<link>https://menial.co.uk/blog/2014/09/27/base-2.4.9-released/</link>
<pubDate>Sat, 27 Sep 2014 09:45:00 +0000</pubDate>
      
      <guid isPermaLink="false">https://menial.co.uk/blog/2014/09/27/base-2.4.9-released/</guid>
<description>&lt;p&gt;Base 2.4.9 is now available to download for both direct sale and Mac App Store users. This is a bugfix release to prepare for OS X Yosemite.&lt;/p&gt;
&lt;p&gt;Base 2.4.9 can be downloaded &lt;a href=&#34;http://menial.co.uk/base/&#34;&gt;from the product page&lt;/a&gt; or from &lt;a href=&#34;http://itunes.apple.com/app/base/id402383384?mt=12&#34;&gt;the Mac App Store&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;As always, the full list of changes can be found in the &lt;a href=&#34;http://update.menial.co.uk/releasenotes/base/1547/&#34;&gt;release notes&lt;/a&gt; and if you’ve found a bug or have a question, please do &lt;a href=&#34;http://menial.co.uk/contact/&#34;&gt;get in touch&lt;/a&gt;.&lt;/p&gt;
</description>
</item>
    
    <item>
<title>Base Turns 5 Years Old</title>
<link>https://menial.co.uk/blog/2013/11/16/base-turns-5-years-old/</link>
<pubDate>Sat, 16 Nov 2013 07:00:00 +0000</pubDate>
      
      <guid isPermaLink="false">https://menial.co.uk/blog/2013/11/16/base-turns-5-years-old/</guid>
<description>&lt;p&gt;Five years ago today, Base 1.0 was released blinking into the internet. Since that day there have been 29 updates varying from minor patches to major upgrades.&lt;/p&gt;
&lt;p&gt;There&amp;rsquo;s still a long way to go with this app. Development is still ongoing and I am looking forward to the next five years. I hope you will all join me.&lt;/p&gt;
&lt;p&gt;To celebrate, Base is 50% off for the next three days. Until the end of 18&lt;sup&gt;th&lt;/sup&gt; November 2013 Base will be priced at £9.99 ($13.99 or regional equivalent). You can &lt;a href=&#34;https://itunes.apple.com/app/base/id402383384?mt=12&#34;&gt;buy Base from the Mac App Store&lt;/a&gt; or &lt;a href=&#34;https://menial.co.uk/base/buy/&#34;&gt;from this website&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Thank you to all of you who have helped make Base what it is today and to those whose feedback, bug reports and suggestions are helping to make it even better in the future.&lt;/p&gt;
</description>
</item>
    
    <item>
<title>Base 2.4.8 Released</title>
<link>https://menial.co.uk/blog/2013/11/02/base-2.4.8-released/</link>
<pubDate>Sat, 02 Nov 2013 16:12:16 +0000</pubDate>
      
      <guid isPermaLink="false">https://menial.co.uk/blog/2013/11/02/base-2.4.8-released/</guid>
<description>&lt;p&gt;Base 2.4.8 is now available to download for those purchasing licenses via the web store. An update for Mac App Store users has been submitted and will hopefully be approved for sale quickly. While normally both versions will have updates released together, the bugs that are fixed in this version were annoying enough to warrant separate releases.&lt;/p&gt;
&lt;p&gt;The main changes in this release are to disable smart quotes and smart dashes in the SQL editor. The SQLite version has also been updated to 3.8.1.&lt;/p&gt;
&lt;p&gt;Base 2.4.8 can be downloaded &lt;a href=&#34;http://menial.co.uk/base/&#34;&gt;from the product page&lt;/a&gt; or from &lt;a href=&#34;http://itunes.apple.com/app/base/id402383384?mt=12&#34;&gt;the Mac App Store&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;As always, the full list of changes can be found in the &lt;a href=&#34;http://update.menial.co.uk/releasenotes/base/1445/&#34;&gt;release notes&lt;/a&gt; and if you’ve found a bug or have a question, please do &lt;a href=&#34;http://menial.co.uk/contact/&#34;&gt;get in touch&lt;/a&gt;.&lt;/p&gt;
</description>
</item>
    
    <item>
<title>Base 2.4.7 Released</title>
<link>https://menial.co.uk/blog/2013/10/18/base-2.4.7-released/</link>
<pubDate>Fri, 18 Oct 2013 07:46:26 +0000</pubDate>
      
      <guid isPermaLink="false">https://menial.co.uk/blog/2013/10/18/base-2.4.7-released/</guid>
<description>&lt;p&gt;Base 2.4.7 is now available to download for both web and App Store users. This improves compatibility with Mac OS 10.9 Mavericks and files generated by iOS 7.&lt;/p&gt;
&lt;p&gt;Base 2.4.7 can be downloaded &lt;a href=&#34;http://menial.co.uk/base/&#34;&gt;from the product page&lt;/a&gt; or from &lt;a href=&#34;http://itunes.apple.com/app/base/id402383384?mt=12&#34;&gt;the Mac App Store&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;As always, the full list of changes can be found in the &lt;a href=&#34;http://update.menial.co.uk/releasenotes/base/1409/&#34;&gt;release notes&lt;/a&gt; and if you’ve found a bug or have a question, please do &lt;a href=&#34;http://menial.co.uk/contact/&#34;&gt;get in touch&lt;/a&gt;.&lt;/p&gt;
</description>
</item>
    
    <item>
<title>Base 2.4.6 Released</title>
<link>https://menial.co.uk/blog/2013/07/21/base-2.4.6-released/</link>
<pubDate>Sun, 21 Jul 2013 11:15:46 +0000</pubDate>
      
      <guid isPermaLink="false">https://menial.co.uk/blog/2013/07/21/base-2.4.6-released/</guid>
<description>&lt;p&gt;Base 2.4.6 is now available to download for both web and App Store users. This update fixes several display and export bugs.&lt;/p&gt;
&lt;p&gt;Base 2.4.6 can be downloaded &lt;a href=&#34;http://menial.co.uk/base/&#34;&gt;from the product page&lt;/a&gt; or from &lt;a href=&#34;http://itunes.apple.com/app/base/id402383384?mt=12&#34;&gt;the Mac App Store&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;As always, the full list of changes can be found in the &lt;a href=&#34;http://update.menial.co.uk/releasenotes/base/1301/&#34;&gt;release notes&lt;/a&gt; and if you’ve found a bug or have a question, please do &lt;a href=&#34;http://menial.co.uk/contact/&#34;&gt;get in touch&lt;/a&gt;.&lt;/p&gt;
</description>
</item>
    
    <item>
<title>Base 2.4.5 Released</title>
<link>https://menial.co.uk/blog/2013/06/07/base-2.4.5-released/</link>
<pubDate>Fri, 07 Jun 2013 17:41:16 +0000</pubDate>
      
      <guid isPermaLink="false">https://menial.co.uk/blog/2013/06/07/base-2.4.5-released/</guid>
<description>&lt;p&gt;Base 2.4.5 is now available to download for both web and App Store users. This update fixes a number of fiddly little bugs across most parts of the app. Notable changes include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Tabbing between fields in the data browser now wraps from line to line&lt;/li&gt;
&lt;li&gt;Failed CSV imports now report where and why failure occurred&lt;/li&gt;
&lt;li&gt;Table list filtering no longer loses focus after each keystroke&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Base 2.4.5 can be downloaded &lt;a href=&#34;http://menial.co.uk/base/&#34;&gt;from the product page&lt;/a&gt; or from &lt;a href=&#34;http://itunes.apple.com/app/base/id402383384?mt=12&#34;&gt;the Mac App Store&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;As always, the full list of changes can be found in the &lt;a href=&#34;http://update.menial.co.uk/releasenotes/base/1224/&#34;&gt;release notes&lt;/a&gt; and if you’ve found a bug or have a question, please do &lt;a href=&#34;http://menial.co.uk/contact/&#34;&gt;get in touch&lt;/a&gt;.&lt;/p&gt;
</description>
</item>
    
    <item>
<title>Base 2.4.4 Released</title>
<link>https://menial.co.uk/blog/2013/05/17/base-2.4.4-released/</link>
<pubDate>Fri, 17 May 2013 08:09:30 +0000</pubDate>
      
      <guid isPermaLink="false">https://menial.co.uk/blog/2013/05/17/base-2.4.4-released/</guid>
<description>&lt;p&gt;Base 2.4.4 is now available to download for both web and App Store users. The changes in this version are small, but significant:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Sandboxing is now re-enabled for App Store customers. See &lt;a href=&#34;https://menial.co.uk/blog/2013/03/28/base-2-4-3-released/&#34;&gt;this blog post&lt;/a&gt; for more details&lt;/li&gt;
&lt;li&gt;A crash-on-launch bug has been fixed&lt;/li&gt;
&lt;li&gt;An interface glitch where the SQL error notice could not be dismissed has been fixed.&lt;/li&gt;
&lt;li&gt;Base now requires Mac OS 10.8 or newer to run&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Base 2.4.4 can be downloaded &lt;a href=&#34;http://menial.co.uk/base/&#34;&gt;from the product page&lt;/a&gt; or from &lt;a href=&#34;http://itunes.apple.com/app/base/id402383384?mt=12&#34;&gt;the Mac App Store&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;As always, the full list of changes can be found in the &lt;a href=&#34;http://update.menial.co.uk/releasenotes/base/1201/&#34;&gt;release notes&lt;/a&gt; and if you’ve found a bug or have a question, please do &lt;a href=&#34;http://menial.co.uk/contact/&#34;&gt;get in touch&lt;/a&gt;.&lt;/p&gt;
</description>
</item>
    
  </channel>
</rss>
