<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/" version="2.0">
  <!-- Source: https://www.dolthub.com/blog/rss.xml -->
  <channel>
    <title>DoltHub Blog - Latest Posts</title>
    <description>Blog for DoltHub, a website hosting databases made with Dolt, an open-source version-controlled SQL database with Git-like semantics.</description>
    <link>https://siftrss.com/f/Kkmm5PG8lw</link>
    <language>en-us</language>
    <lastBuildDate>Tue, 07 Apr 2026 01:40:55 GMT</lastBuildDate>
    <atom:link href="https://siftrss.com/f/Kkmm5PG8lw" rel="self" type="application/rss+xml"/>
    <item>
      <title>Vibe-Coded Agents for Vibe-Coded Issues</title>
      <link>https://dolthub.com/blog/2026-04-03-vibe-coded-agents-for-vibe-coded-issues/</link>
      <guid isPermaLink="true">https://dolthub.com/blog/2026-04-03-vibe-coded-agents-for-vibe-coded-issues/</guid>
      <description>Gas Town's vibe-coded agents introduced a plethora of new issues to Dolt. I vibe-coded a Go CLI to fight back: parallel agents to reproduce the issues agents caused.</description>
      <pubDate>Fri, 03 Apr 2026 00:00:00 GMT</pubDate>
      <content:encoded>&lt;p&gt;&lt;a href="https://github.com/dolthub/dolt"&gt;Dolt&lt;/a&gt; is a SQL database with Git-style version control. It speaks the MySQL wire protocol, so any MySQL client can connect to it, and it adds version control primitives on top: branch, merge, diff, clone, and push, all over SQL. Under the hood, &lt;a href="https://github.com/dolthub/go-mysql-server"&gt;go-mysql-server&lt;/a&gt; handles the SQL execution layer between clients and Dolt’s storage engine.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/steveyegge/gastown"&gt;Gas Town&lt;/a&gt; builds on those primitives. It is &lt;a href="https://www.dolthub.com/blog/2026-01-15-a-day-in-gas-town/"&gt;a multi-agent coding orchestrator&lt;/a&gt; built on write-only code, and as &lt;a href="https://www.dolthub.com/blog/2026-03-13-multi-agent-persistence/"&gt;it scaled to hundreds of concurrent workers&lt;/a&gt;, those agents were constantly branching, merging, and committing against Dolt. That load surfaced a new class of issues: vibe-coded issues.&lt;/p&gt;
&lt;p&gt;To be clear, I’m not throwing shade. After all, I’m familiar with &lt;a href="https://www.dolthub.com/blog/2025-08-28-how-i-use-multiple-agents-in-parallel/"&gt;running agents in parallel&lt;/a&gt; to work through MySQL correctness work, but we’re also entering a new ballpark here. It’s hard to expect clear reproductions when hundreds of agents unpredictably use a tool at the same time. I would know, since I recently spent hours trying to get a reproduction on a couple of Gas Town Dolt issues, some leading to nowhere.&lt;/p&gt;
&lt;p&gt;So, in the spirit of &lt;a href="https://www.dolthub.com/blog/2026-03-26-vibe-code-vs-trad-code/"&gt;Vibe Code vs Trad Code&lt;/a&gt;, sometimes you fight fire with fire. I’ve “vibe-coded” &lt;code&gt;grunt&lt;/code&gt;, a Go CLI that provisions isolated Docker agent environments in parallel but with options this time around.&lt;/p&gt;
&lt;h1 id="the-problem-pre-baked-containers-dont-scale"&gt;The Problem: Pre-Baked Containers Don’t Scale&lt;a class="anchor-link" aria-label="Link to heading" href="#the-problem-pre-baked-containers-dont-scale"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;The obvious answer to “Gas Town introduced these issues, why not use Gas Town to reproduce them?” is that issue reproduction isn’t really a Gas Town job. Gas Town is built for long-running, write-only work where persistent agent memory via &lt;a href="https://github.com/gastownhall/beads"&gt;Beads&lt;/a&gt; accumulates across sessions. Reproducing a GitHub issue is the opposite in that it’s a short-lived, discrete task where you spin up, get a failing test, and tear down.&lt;/p&gt;
&lt;p&gt;My &lt;a href="https://www.dolthub.com/blog/2025-08-28-how-i-use-multiple-agents-in-parallel/"&gt;previous setup&lt;/a&gt; relied on a single container image with all dependencies pre-baked. That worked when I was targeting a specific repository and task. The moment I needed to handle two repos with different requirements, it broke down and I had to manually update things.&lt;/p&gt;
&lt;p&gt;What I actually wanted was to configure at launch time via CLI flags what each agent container and repo needs: which post-install scripts to run, how much memory to give it, what prompt to start it with.&lt;/p&gt;
&lt;p&gt;So I pointed an agent at the problem. I had it follow Go best practices from the official docs and address IDE warnings as it went, rather than letting it freewheel. The result is &lt;code&gt;grunt&lt;/code&gt;. I can read the code, which puts it firmly in &lt;a href="https://www.dolthub.com/blog/2026-03-26-vibe-code-vs-trad-code/"&gt;Trad Code territory&lt;/a&gt; even if it got there via an agent.&lt;/p&gt;
&lt;h1 id="how-grunt-works"&gt;How grunt Works&lt;a class="anchor-link" aria-label="Link to heading" href="#how-grunt-works"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;One command does everything:&lt;/p&gt;
&lt;pre class="astro-code github-dark" tabindex="0" data-language="bash"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span&gt;grunt&lt;/span&gt;&lt;span&gt; agent&lt;/span&gt;&lt;span&gt; create&lt;/span&gt;&lt;span&gt; -name&lt;/span&gt;&lt;span&gt; gms&lt;/span&gt;&lt;span&gt; -repo&lt;/span&gt;&lt;span&gt; dolthub/go-mysql-server&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src="https://static.dolthub.com/blogimages/gms-session.png/c2511ed284053fd62f268798d2d67ab4f35018bc0d6a16f780166486991f7fc5.webp" alt="A grunt agent session running against go-mysql-server"&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;create&lt;/code&gt; resolves the repo’s configuration, builds the Docker image with the right post-install scripts applied, clones the repo, starts the container, and drops you straight into a &lt;a href="https://zellij.dev/"&gt;zellij&lt;/a&gt; session with Claude already running. There is no separate start step.&lt;/p&gt;
&lt;p&gt;The nice part is that &lt;code&gt;grunt agent create -repo dolthub/dolt&lt;/code&gt; already knows what that repo needs without me specifying anything. &lt;code&gt;dolthub/dolt&lt;/code&gt; needs &lt;a href="https://www.dolthub.com/blog/2025-11-21-easy-cgo-builds-with-docker/"&gt;CGo build dependencies&lt;/a&gt; plus &lt;a href="https://www.dolthub.com/blog/2024-07-17-lambdabats/"&gt;bats&lt;/a&gt; for its test suite. &lt;code&gt;dolthub/go-mysql-server&lt;/code&gt; needs something lighter. That comes from the configuration layer, covered in the next section. Even if you don’t have a config, it’ll figure out the GitHub URL automatically for any new repositories.&lt;/p&gt;
&lt;p&gt;If I want to override the post-install scripts or provider (only claude so far) for a specific run, I pass them directly:&lt;/p&gt;
&lt;pre class="astro-code github-dark" tabindex="0" data-language="bash"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span&gt;grunt&lt;/span&gt;&lt;span&gt; agent&lt;/span&gt;&lt;span&gt; create&lt;/span&gt;&lt;span&gt; -name&lt;/span&gt;&lt;span&gt; dolt&lt;/span&gt;&lt;span&gt; -repo&lt;/span&gt;&lt;span&gt; dolthub/dolt&lt;/span&gt;&lt;span&gt; -repo&lt;/span&gt;&lt;span&gt; dolthub/go-mysql-server&lt;/span&gt;&lt;span&gt; -issue&lt;/span&gt;&lt;span&gt; 1234&lt;/span&gt;&lt;span&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;  -post-install&lt;/span&gt;&lt;span&gt; go,bats,dolt-cgo-deps&lt;/span&gt;&lt;span&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;  -provider&lt;/span&gt;&lt;span&gt; claude&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When I have several issues to chase at once, &lt;code&gt;-d&lt;/code&gt; starts the container in the background and hands control back immediately. Then I attach to whichever is idle.&lt;/p&gt;
&lt;pre class="astro-code github-dark" tabindex="0" data-language="bash"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span&gt;grunt&lt;/span&gt;&lt;span&gt; agent&lt;/span&gt;&lt;span&gt; create&lt;/span&gt;&lt;span&gt; -d&lt;/span&gt;&lt;span&gt; -name&lt;/span&gt;&lt;span&gt; dolt&lt;/span&gt;&lt;span&gt; -repo&lt;/span&gt;&lt;span&gt; dolthub/dolt&lt;/span&gt;&lt;span&gt; -issue&lt;/span&gt;&lt;span&gt; 10782&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;grunt&lt;/span&gt;&lt;span&gt; agent&lt;/span&gt;&lt;span&gt; create&lt;/span&gt;&lt;span&gt; -d&lt;/span&gt;&lt;span&gt; -name&lt;/span&gt;&lt;span&gt; gms&lt;/span&gt;&lt;span&gt; -repo&lt;/span&gt;&lt;span&gt; dolthub/go-mysql-server&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;grunt&lt;/span&gt;&lt;span&gt; agent&lt;/span&gt;&lt;span&gt; ls&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;ID&lt;/span&gt;&lt;span&gt;                 STATE&lt;/span&gt;&lt;span&gt;    ACTIVITY&lt;/span&gt;&lt;span&gt;  PROVIDER&lt;/span&gt;&lt;span&gt;  REPOS&lt;/span&gt;&lt;span&gt;                         ISSUES&lt;/span&gt;&lt;span&gt;              CREATED&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;gms-41b27c1c&lt;/span&gt;&lt;span&gt;       running&lt;/span&gt;&lt;span&gt;  -&lt;/span&gt;&lt;span&gt;         claude&lt;/span&gt;&lt;span&gt;    dolthub/go-mysql-server&lt;/span&gt;&lt;span&gt; (+2)  10190               2026-03-27T18:20:49Z&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;gms-c35d524e&lt;/span&gt;&lt;span&gt;       stopped&lt;/span&gt;&lt;span&gt;  -&lt;/span&gt;&lt;span&gt;         claude&lt;/span&gt;&lt;span&gt;    dolthub/go-mysql-server&lt;/span&gt;&lt;span&gt; (+1)  dolthub/dolt#10757  2026-03-30T20:12:06Z&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;dolt-d32698b0&lt;/span&gt;&lt;span&gt;      running&lt;/span&gt;&lt;span&gt;  working&lt;/span&gt;&lt;span&gt;   claude&lt;/span&gt;&lt;span&gt;    dolthub/dolt&lt;/span&gt;&lt;span&gt;                  10782&lt;/span&gt;&lt;span&gt;               2026-03-31T17:16:23Z&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;gms-a573c42c&lt;/span&gt;&lt;span&gt;       running&lt;/span&gt;&lt;span&gt;  idle&lt;/span&gt;&lt;span&gt;      claude&lt;/span&gt;&lt;span&gt;    dolthub/go-mysql-server&lt;/span&gt;&lt;span&gt; (+1)  -                   2026-04-01T17:39:20Z&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;grunt&lt;/span&gt;&lt;span&gt; agent&lt;/span&gt;&lt;span&gt; attach&lt;/span&gt;&lt;span&gt; gms-a573c42c&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For changes I want to stick across all runs of a repo, I use &lt;code&gt;grunt config set&lt;/code&gt;. I’ll usually do this when working on any new repo to set their post-install scripts.&lt;/p&gt;
&lt;pre class="astro-code github-dark" tabindex="0" data-language="bash"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span&gt;grunt&lt;/span&gt;&lt;span&gt; config&lt;/span&gt;&lt;span&gt; set&lt;/span&gt;&lt;span&gt; branch.prefix&lt;/span&gt;&lt;span&gt; -value&lt;/span&gt;&lt;span&gt; elian&lt;/span&gt;&lt;span&gt; -repo&lt;/span&gt;&lt;span&gt; dolthub/dolt&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;branch.prefix[dolthub/dolt]&lt;/span&gt;&lt;span&gt;=elian&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;grunt&lt;/span&gt;&lt;span&gt; config&lt;/span&gt;&lt;span&gt; ls&lt;/span&gt;&lt;span&gt; -repo&lt;/span&gt;&lt;span&gt; dolthub/dolt&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;$&lt;/span&gt;&lt;span&gt; grunt&lt;/span&gt;&lt;span&gt; config&lt;/span&gt;&lt;span&gt; ls&lt;/span&gt;&lt;span&gt; -repo&lt;/span&gt;&lt;span&gt; dolthub/dolt&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;agent.provider&lt;/span&gt;&lt;span&gt;=claude&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;agent.memory-limit&lt;/span&gt;&lt;span&gt;=8g&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;branch.prefix&lt;/span&gt;&lt;span&gt;=elian&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;issue.prompt&lt;/span&gt;&lt;span&gt;=Create&lt;/span&gt;&lt;span&gt; a&lt;/span&gt;&lt;span&gt; failing&lt;/span&gt;&lt;span&gt; reproduction&lt;/span&gt;&lt;span&gt; for&lt;/span&gt;&lt;span&gt; the&lt;/span&gt;&lt;span&gt; following&lt;/span&gt;&lt;span&gt; issue&lt;/span&gt;&lt;span&gt; {{....&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;repo.post-install&lt;/span&gt;&lt;span&gt;=go,bats,expect,dolt-cgo-deps&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;repo.services&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That &lt;code&gt;config ls&lt;/code&gt; output is the resolved view of a three-layer system. The UX borrows directly from &lt;code&gt;git config&lt;/code&gt;.&lt;/p&gt;
&lt;h1 id="per-repo-config"&gt;Per-Repo Config&lt;a class="anchor-link" aria-label="Link to heading" href="#per-repo-config"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;Rather than making users configure everything from scratch, grunt ships with per-repo defaults embedded directly in the binary using &lt;code&gt;//go:embed&lt;/code&gt;. Each supported repo gets one JSON file covering the two things that vary most: which post-install scripts to run and a default prompt to seed the agent with.&lt;/p&gt;
&lt;pre class="astro-code github-dark" tabindex="0" data-language="json"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;  "scripts"&lt;/span&gt;&lt;span&gt;: [&lt;/span&gt;&lt;span&gt;"go"&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;"bats"&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;"expect"&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;"dolt-cgo-deps"&lt;/span&gt;&lt;span&gt;],&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;  "prompt"&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;"Create a failing reproduction for the following issue {{.IssueRef}} under the relevant available repositories..."&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Adding a new repository means adding one JSON file. On top of those sit two user-owned layers: a global config that applies across all repos and a per-repo override. Every lookup walks the same three layers.&lt;/p&gt;
&lt;pre class="astro-code github-dark" tabindex="0" data-language="go"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span&gt;func&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;s &lt;/span&gt;&lt;span&gt;ProfileService&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;AgentProvider&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;repo&lt;/span&gt;&lt;span&gt; string&lt;/span&gt;&lt;span&gt;) (&lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;    profile, err &lt;/span&gt;&lt;span&gt;:=&lt;/span&gt;&lt;span&gt; s.Store.&lt;/span&gt;&lt;span&gt;Load&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;    if&lt;/span&gt;&lt;span&gt; err &lt;/span&gt;&lt;span&gt;!=&lt;/span&gt;&lt;span&gt; nil&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;        return&lt;/span&gt;&lt;span&gt; ""&lt;/span&gt;&lt;span&gt;, fmt.&lt;/span&gt;&lt;span&gt;Errorf&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;"load profile: &lt;/span&gt;&lt;span&gt;%w&lt;/span&gt;&lt;span&gt;"&lt;/span&gt;&lt;span&gt;, err)&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;    if&lt;/span&gt;&lt;span&gt; provider, ok &lt;/span&gt;&lt;span&gt;:=&lt;/span&gt;&lt;span&gt; profile.RepoAgentProviders[repo]; ok {&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;        return&lt;/span&gt;&lt;span&gt; provider, &lt;/span&gt;&lt;span&gt;nil&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;    return&lt;/span&gt;&lt;span&gt; profile.AgentProvider, &lt;/span&gt;&lt;span&gt;nil&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This works fine for the number of config values &lt;code&gt;grunt&lt;/code&gt; has today. It’s worth noting that the agent duplicated this walk across every accessor instead of merging the layers once at load time. A cleaner approach would resolve everything in &lt;code&gt;Load()&lt;/code&gt; and let accessors just read fields. With only a handful of values, this is not a real problem yet, but it’s worth revisiting.&lt;/p&gt;
&lt;h1 id="adding-a-new-agent-type"&gt;Adding a New Agent Type&lt;a class="anchor-link" aria-label="Link to heading" href="#adding-a-new-agent-type"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;&lt;code&gt;grunt&lt;/code&gt; only ships with Claude today, but it was designed so that swapping in a different agent is one interface implementation away. Each agent type implements &lt;code&gt;AgentProvider&lt;/code&gt;, which covers everything that varies between providers, including how to set up the agent in the Docker container, where to find the agent’s config files, how to read its activity status, and what command to launch it with.&lt;/p&gt;
&lt;pre class="astro-code github-dark" tabindex="0" data-language="go"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span&gt;type&lt;/span&gt;&lt;span&gt; AgentProvider&lt;/span&gt;&lt;span&gt; interface&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;    Name&lt;/span&gt;&lt;span&gt;() &lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;    Spec&lt;/span&gt;&lt;span&gt;() &lt;/span&gt;&lt;span&gt;Spec&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;    PrepareRuntime&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;input&lt;/span&gt;&lt;span&gt; RuntimeInput&lt;/span&gt;&lt;span&gt;) (&lt;/span&gt;&lt;span&gt;RuntimeOutput&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;    ActivityStatusPath&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;workspaceRoot&lt;/span&gt;&lt;span&gt; string&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;    ReadActivityStatus&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;workspaceRoot&lt;/span&gt;&lt;span&gt; string&lt;/span&gt;&lt;span&gt;) (&lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;    EnsureGlobalConfig&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;paths&lt;/span&gt;&lt;span&gt; config&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Paths&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;    SaveAPIKey&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;key&lt;/span&gt;&lt;span&gt; string&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;paths&lt;/span&gt;&lt;span&gt; config&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Paths&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;    ConfigPaths&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;paths&lt;/span&gt;&lt;span&gt; config&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Paths&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;ConfigPaths&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;    Validate&lt;/span&gt;&lt;span&gt;() &lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;PrepareRuntime&lt;/code&gt; handles the container setup by returning the Docker mounts, environment files, and build commands specific to that provider. &lt;code&gt;Spec&lt;/code&gt; returns the startup command and shell. Those get passed into a &lt;a href="https://zellij.dev/"&gt;zellij&lt;/a&gt; layout file that grunt generates at runtime using Go’s &lt;code&gt;text/template&lt;/code&gt; (has also been useful in other templates i.e. the Dockerfile).&lt;/p&gt;
&lt;pre class="astro-code github-dark" tabindex="0" data-language="plaintext"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span&gt;layout {&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;    tab {&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;        pane name="Claude" command={{ printf "%q" .StartupCommand }}&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;    new_tab_template {&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;        pane name="Shell" command={{ printf "%q" .PaneShell }}&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Zellij is a terminal multiplexer, similar to tmux, that lets &lt;code&gt;grunt&lt;/code&gt; give each agent its own named panes. Because the layout is generated from a template, a different provider produces a different layout with no special casing anywhere in the terminal code. Provider selection is a single switch:&lt;/p&gt;
&lt;pre class="astro-code github-dark" tabindex="0" data-language="go"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span&gt;func&lt;/span&gt;&lt;span&gt; LookupAgentProvider&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt; string&lt;/span&gt;&lt;span&gt;) (&lt;/span&gt;&lt;span&gt;AgentProvider&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;    switch&lt;/span&gt;&lt;span&gt; strings.&lt;/span&gt;&lt;span&gt;ToLower&lt;/span&gt;&lt;span&gt;(strings.&lt;/span&gt;&lt;span&gt;TrimSpace&lt;/span&gt;&lt;span&gt;(name)) {&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;    case&lt;/span&gt;&lt;span&gt; ""&lt;/span&gt;&lt;span&gt;, domain.DefaultProvider:&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;        return&lt;/span&gt;&lt;span&gt; ClaudeProvider&lt;/span&gt;&lt;span&gt;{}, &lt;/span&gt;&lt;span&gt;nil&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;    default&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;        return&lt;/span&gt;&lt;span&gt; nil&lt;/span&gt;&lt;span&gt;, fmt.&lt;/span&gt;&lt;span&gt;Errorf&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;"&lt;/span&gt;&lt;span&gt;%q&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;%w&lt;/span&gt;&lt;span&gt;"&lt;/span&gt;&lt;span&gt;, name, errUnsupportedProvider)&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h1 id="conclusion"&gt;Conclusion&lt;a class="anchor-link" aria-label="Link to heading" href="#conclusion"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;&lt;code&gt;grunt&lt;/code&gt; is a vibe-coded tool for reproducing issues caused by a vibe-coded tool. That is about as recursive as it gets. We could go on about each aspect of implementation, but the above represents the main goal of these ephemeral configurable agent instances. It’s readable-enough, and it works.&lt;/p&gt;
&lt;p&gt;It’s already helped me spin up multiple reproductions in parallel and close issues faster. I’ve also gotten new ideas on putting this against CI flaky tests in the background too. If you have questions about the setup or want to dig into Dolt, come find us on &lt;a href="https://discord.gg/gqr7K4VNKe"&gt;Discord&lt;/a&gt;. We are always happy to talk Go.&lt;/p&gt;</content:encoded>
      <dc:creator>Elian Deogracia-Brito</dc:creator>
      <category>golang</category>
      <category>technical</category>
      <category>ai</category>
    </item>
    <item>
      <title>Branch Permissions in the Hosted Dolt Workbench</title>
      <link>https://dolthub.com/blog/2026-03-12-hosted-branch-permissions/</link>
      <guid isPermaLink="true">https://dolthub.com/blog/2026-03-12-hosted-branch-permissions/</guid>
      <description>We recently released branch permissions for Hosted Dolt Workbench. Collaborate seamlessly with your team while having greater oversight into what makes it into your main database.</description>
      <pubDate>Thu, 12 Mar 2026 00:00:00 GMT</pubDate>
      <content:encoded>&lt;p&gt;&lt;a href="https://hosted.doltdb.com/"&gt;Hosted Dolt&lt;/a&gt;’s SQL workbench is a great collaboration tool for teams looking for a modern, easy-to-use UI for your version-controlled database. Its permission model allows for infinite users with varying access to your database.&lt;/p&gt;
&lt;p&gt;In addition to user roles, Dolt supports &lt;a href="https://docs.dolthub.com/sql-reference/server/branch-permissions"&gt;branch permissions&lt;/a&gt;, which let you limit access of certain branches to all or some users. Hosted Dolt now supports branch permissions directly from the workbench UI.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://static.dolthub.com/blogimages/hosted-branch-protections-form.png/c5bbfd0b5d0686b8b8c3b46f834d14921a86924e19931f49d318d1d103206f4b.webp" alt="Hosted Dolt settings tab"&gt;&lt;/p&gt;
&lt;h2 id="background-and-implementation-details"&gt;Background and implementation details&lt;a class="anchor-link" aria-label="Link to heading" href="#background-and-implementation-details"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Restricting access to certain branches is a common workflow on GitHub. Users want to prevent direct writes to their &lt;code&gt;main&lt;/code&gt; branch, instead only allowing reviewed and approved pull requests to make it into production code. We have a &lt;a href="https://www.dolthub.com/blog/2022-07-18-how-to-set-up-branch-protection-rules-on-dolthub/"&gt;synonymous branch permission model&lt;/a&gt; on &lt;a href="https://www.dolthub.com/"&gt;DoltHub&lt;/a&gt;, which we released a few years ago.&lt;/p&gt;
&lt;p&gt;The online nature of Hosted Dolt is &lt;a href="https://www.dolthub.com/blog/2023-03-17-dolthub-vs-hosted-workbench/#dolthub-vs-hosted"&gt;different&lt;/a&gt; from the offline model of DoltHub and GitHub. And unlike DoltHub, there are two layers of users on Hosted: Hosted application users (the user that logs into hosted.doltdb.com) and SQL users (users that connect to the SQL server or workbench).&lt;/p&gt;
&lt;p&gt;For the workbench specifically, depending on the role of the application user (via organization roles or deployment collaborators) an internal SQL user with corresponding permissions will be used to connect to the workbench. If you have this feature enabled in your deployment settings, you’ll see them here:&lt;/p&gt;
&lt;pre class="astro-code github-dark" tabindex="0" data-language="sql"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span&gt;mysql&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;span&gt; select&lt;/span&gt;&lt;span&gt; host, user &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; mysql&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;user&lt;/span&gt;&lt;span&gt; where&lt;/span&gt;&lt;span&gt; user &lt;/span&gt;&lt;span&gt;like&lt;/span&gt;&lt;span&gt; 'hosted-ui-%'&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt;------+------------------+&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;| host | user             |&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt;------+------------------+&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;| %    | hosted&lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;ui&lt;/span&gt;&lt;span&gt;-admin&lt;/span&gt;&lt;span&gt;  | &lt;/span&gt;&lt;span&gt;-- all writes, including GRANT OPTION privilege&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;| %    | hosted&lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;ui&lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;writer | &lt;/span&gt;&lt;span&gt;-- all writes,  does not include GRANT OPTION privilege&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;| %    | hosted&lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;ui&lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;reader | &lt;/span&gt;&lt;span&gt;-- read-only for all branches&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt;------+------------------+&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;3&lt;/span&gt;&lt;span&gt; rows&lt;/span&gt;&lt;span&gt; in&lt;/span&gt;&lt;span&gt; set&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;037&lt;/span&gt;&lt;span&gt; sec)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Dolt’s branch permission model initially included three available permissions - admin, write, and read. Creating a branch control for branch &lt;code&gt;main&lt;/code&gt; with permission &lt;code&gt;read&lt;/code&gt; would prevent all of the above users from writing to &lt;code&gt;main&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;One of our customers requested a featured where their organization members can make changes on a branch, create a pull request on the workbench, and merge this branch into &lt;code&gt;main&lt;/code&gt;, but cannot write directly to &lt;code&gt;main&lt;/code&gt;. This is more similar to the GitHub/DoltHub model and makes sense for a workbench product that enables this kind of collaboration on data.&lt;/p&gt;
&lt;p&gt;So we added an additional branch permission to Dolt - &lt;code&gt;merge&lt;/code&gt;. Creating a branch control for branch &lt;code&gt;main&lt;/code&gt; with permission &lt;code&gt;merge&lt;/code&gt; would prevent all users from writing to &lt;code&gt;main&lt;/code&gt;, with the exception of the &lt;a href="https://docs.dolthub.com/sql-reference/version-control/dolt-sql-procedures#dolt_merge"&gt;&lt;code&gt;dolt_merge&lt;/code&gt;&lt;/a&gt; and &lt;a href="https://docs.dolthub.com/sql-reference/version-control/dolt-sql-procedures#dolt_commit"&gt;&lt;code&gt;dolt_commit&lt;/code&gt;&lt;/a&gt; SQL procedures, which are used when merging a pull request from the workbench.&lt;/p&gt;
&lt;p&gt;While all changes in Dolt are version-controlled, a branch permission of this nature creates even greater oversight and transparency for what changes are making it into your production database.&lt;/p&gt;
&lt;h2 id="how-it-works"&gt;How it works&lt;a class="anchor-link" aria-label="Link to heading" href="#how-it-works"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Suppose &lt;a href="https://www.dolthub.com/team#tim"&gt;Tim&lt;/a&gt; has a Hosted database to keep track of employees and teams at DoltHub. Tim hired a Human Resources employee to manage this database, but they are not SQL savvy and he does not want them to make changes to &lt;code&gt;main&lt;/code&gt; without reviewing their pull requests first.&lt;/p&gt;
&lt;p&gt;First, Tim adds the HR employee as a collaborator with &lt;code&gt;write&lt;/code&gt; permissions in his deployment settings.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://static.dolthub.com/blogimages/hosted-add-hr-collaborator.png/8cfc708cf70177498055a8eea2295e739367e3f523bbe58698e718149e274cce.webp" alt="Deployment settings"&gt;&lt;/p&gt;
&lt;p&gt;Next, Tim launches the workbench and navigates to the Settings tab, which will have a “Branch Protections” form. Only deployment admin have access to this Settings tab.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://static.dolthub.com/blogimages/hosted-branch-protections-form.png/c5bbfd0b5d0686b8b8c3b46f834d14921a86924e19931f49d318d1d103206f4b.webp" alt="Branch protections form"&gt;&lt;/p&gt;
&lt;p&gt;He adds branch &lt;code&gt;main&lt;/code&gt; with permission level &lt;code&gt;Merge&lt;/code&gt;. He can add any branch or branch name pattern here.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://static.dolthub.com/blogimages/hosted-merge-branch-protection-on-main.png/c472b5af674d77372dc6d2a81527b81719a0233491b83285a18088082995d32a.webp" alt="Merge permission on main"&gt;&lt;/p&gt;
&lt;p&gt;Note that this feature only affects users with &lt;code&gt;Write&lt;/code&gt; permission on the deployment. Readers will always only have read-only access to the workbench and admin will always have full access.&lt;/p&gt;
&lt;p&gt;Now, the new &lt;code&gt;hr-employee&lt;/code&gt; user logs in and accesses the workbench. They have been tasked with adding a new “Human Resources” team and adding themselves as an employee. You can see that the built-in cell buttons makes this easy and does not require knowledge of SQL.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://static.dolthub.com/blogimages/hosted-workbench-add-team.png/e5c0e6388ab3d00e8cea3f22db8610f9433cafa030b3cdf4fb8365c5927096c5.webp" alt="Add team"&gt;&lt;/p&gt;
&lt;p&gt;However, &lt;code&gt;hr-employee&lt;/code&gt; cannot make changes directly to &lt;code&gt;main&lt;/code&gt; since Tim has prevented that with branch permissions.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://static.dolthub.com/blogimages/hosted-no-perms-main.png/079669f2e006fc9d7615d3a9c1d306621f5bbd6c331dff27132ed3caaa015a0f.webp" alt="Add team on main error"&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;hr-employee&lt;/code&gt; must create a new branch to make this change.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://static.dolthub.com/blogimages/hosted-hr-branch.png/4d0cb881316f4c13cce33476aa85d1cf81e70a30868ccfebbf1d2bd4a75ea622.webp" alt="New branch"&gt;&lt;/p&gt;
&lt;p&gt;This time adding the “Human Resources” team is successful, since the branch protection only affects the &lt;code&gt;main&lt;/code&gt; branch.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://static.dolthub.com/blogimages/hosted-add-team-working.png/96961616a6ed1cb1505fd0a637fb7e4642e14f0e17c815fc746b66abdf7dc34c.webp" alt="Insert success"&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;hr-employees&lt;/code&gt; adds to &lt;code&gt;employees&lt;/code&gt; and &lt;code&gt;employees_teams&lt;/code&gt; and commits these changes using the “Create commit” button. They create a pull request and send to Tim for review.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://static.dolthub.com/blogimages/hosted-add-hr-pull-request.png/39fbcfdc937b024f783e057423e6dc4db7acefd5df0a85be45b7ff2fd1744872.webp" alt="HR pull request"&gt;&lt;/p&gt;
&lt;p&gt;Tim gives the LGTM, and &lt;code&gt;hr-employee&lt;/code&gt; merges the pull request. This is successful because the branch permission allows changes to &lt;code&gt;main&lt;/code&gt; via &lt;code&gt;dolt_merge&lt;/code&gt;. We can now see these changes on the &lt;code&gt;main&lt;/code&gt; branch.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://static.dolthub.com/blogimages/hosted-hr-in-main.png/b8b9bdfea57e94b9610f45348c69ac4ad8891c6c6aab6d82af59150d4ad28878.webp" alt="HR pull request"&gt;&lt;/p&gt;
&lt;h2 id="conclusion"&gt;Conclusion&lt;a class="anchor-link" aria-label="Link to heading" href="#conclusion"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Hosted Dolt Workbench is a great tool for collaborating with your team on your database, and branch permissions give you even more oversight and control over what makes it into your production database. We take our customer asks seriously, so please contact us with questions or feature requests by &lt;a href="https://github.com/dolthub/hosted-issues/issues"&gt;filing an issue&lt;/a&gt; or reaching out on &lt;a href="https://discord.gg/gqr7K4VNKe"&gt;Discord&lt;/a&gt;.&lt;/p&gt;</content:encoded>
      <dc:creator>Taylor Bantle</dc:creator>
      <category>feature release</category>
      <category>hosted</category>
    </item>
    <item>
      <title>Saying goodbye to the LD1 storage format</title>
      <link>https://dolthub.com/blog/2026-03-03-saying-goodbye-to-ld1/</link>
      <guid isPermaLink="true">https://dolthub.com/blog/2026-03-03-saying-goodbye-to-ld1/</guid>
      <description>Dolt rewrote its storage layer in 2022. 4 years later, we're finally dropping support for the pre-1.0 storage format. This blog describes how they're different and what the work of deletion entailed.</description>
      <pubDate>Tue, 03 Mar 2026 00:00:00 GMT</pubDate>
      <content:encoded>&lt;p&gt;We’re building &lt;a href="https://doltdb.com"&gt;Dolt&lt;/a&gt;, the world’s first version-controlled SQL database. &lt;a href="https://www.dolthub.com/blog/2023-05-05-dolt-1-dot-0/"&gt;Dolt
hit 1.0 in 2023&lt;/a&gt;, which meant, among other
criteria, that we promised forwards storage compatibility:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Dolt 1.0 will be backwards compatible with all further 1.X versions.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Now it’s 2026 and we’re on the verge of releasing &lt;a href="https://www.dolthub.com/blog/2025-07-29-dolt-2-0-preview/"&gt;Dolt
2.0&lt;/a&gt;. In preparation for this release,
we’re pursuing some work we’ve long put off: finally removing support for the pre-1.0 format, which
we referred to internally and in binary logs as &lt;code&gt;LD1&lt;/code&gt; in honor of &lt;a href="https://www.dolthub.com/blog/2020-09-25-dolthub-rebrand/"&gt;our original company
name&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Read on for details of why we took this step and how we accomplished it.&lt;/p&gt;
&lt;h1 id="the-challenge"&gt;The challenge&lt;a class="anchor-link" aria-label="Link to heading" href="#the-challenge"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;In 2022, &lt;a href="https://www.dolthub.com/blog/2022-05-20-new-format-alpha/"&gt;we first landed Dolt’s current storage format in alpha
release&lt;/a&gt;. The new storage format diverged
radically from what came before, partially summarized here with a before and after view of how tuple
values in rows are stored:&lt;/p&gt;
&lt;p&gt;Before:&lt;/p&gt;
&lt;pre class="astro-code github-dark" tabindex="0" data-language="plaintext"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span&gt;+--------+----------+---------+--------+----------+---------+-----+--------+----------+---------+&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;| Type 0 | Length 0 | Value 0 | Type 1 | Length 1 | Value 1 | ... | Type k | Length k | Value K |&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;+--------+----------+---------+--------+----------+---------+-----+--------+----------+---------+&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After:&lt;/p&gt;
&lt;pre class="astro-code github-dark" tabindex="0" data-language="plaintext"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span&gt;+---------+---------+-----+---------+----------+----------+-----+----------+-------+&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;| Value 0 | Value 1 | ... | Value K | Offset 1 | Offset 2 | ... | Offset K | Count |&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;+---------+---------+-----+---------+----------+----------+-----+----------+-------+&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Storing tuples in the new format was part of a series of changes to how values are serialized to
disk that collectively resulted in over a 5x speedup in our internal benchmarks. We pursued these
changes primarily for performance reasons, and it worked: &lt;a href="https://www.dolthub.com/blog/2025-12-04-dolt-is-as-fast-as-mysql/"&gt;Dolt is now as fast as MySQL on
sysbench&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;But these changes came at a cost. Because the old and new storage formats were incompatible with one
another, and existing paying customers were running their production databases on the old format, we
needed to support both storage formats in parallel. We did this the typical way in many programming
languages, by introducing interfaces that abstracted away the differences between the two
implementations. For example, here’s how we define a table’s storage:&lt;/p&gt;
&lt;pre class="astro-code github-dark" tabindex="0" data-language="go"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span&gt;type&lt;/span&gt;&lt;span&gt; Table&lt;/span&gt;&lt;span&gt; interface&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;	HashOf&lt;/span&gt;&lt;span&gt;() (&lt;/span&gt;&lt;span&gt;hash&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Hash&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;	GetSchemaHash&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;ctx&lt;/span&gt;&lt;span&gt; context&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Context&lt;/span&gt;&lt;span&gt;) (&lt;/span&gt;&lt;span&gt;hash&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Hash&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;	GetSchema&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;ctx&lt;/span&gt;&lt;span&gt; context&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Context&lt;/span&gt;&lt;span&gt;) (&lt;/span&gt;&lt;span&gt;schema&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Schema&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;	SetSchema&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;ctx&lt;/span&gt;&lt;span&gt; context&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Context&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;sch&lt;/span&gt;&lt;span&gt; schema&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Schema&lt;/span&gt;&lt;span&gt;) (&lt;/span&gt;&lt;span&gt;Table&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;	GetTableRows&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;ctx&lt;/span&gt;&lt;span&gt; context&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Context&lt;/span&gt;&lt;span&gt;) (&lt;/span&gt;&lt;span&gt;Index&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;	GetTableRowsWithDescriptors&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;ctx&lt;/span&gt;&lt;span&gt; context&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Context&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;kd&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;vd&lt;/span&gt;&lt;span&gt; *&lt;/span&gt;&lt;span&gt;val&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;TupleDesc&lt;/span&gt;&lt;span&gt;) (&lt;/span&gt;&lt;span&gt;Index&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;	SetTableRows&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;ctx&lt;/span&gt;&lt;span&gt; context&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Context&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;rows&lt;/span&gt;&lt;span&gt; Index&lt;/span&gt;&lt;span&gt;) (&lt;/span&gt;&lt;span&gt;Table&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;	GetIndexes&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;ctx&lt;/span&gt;&lt;span&gt; context&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Context&lt;/span&gt;&lt;span&gt;) (&lt;/span&gt;&lt;span&gt;IndexSet&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;	SetIndexes&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;ctx&lt;/span&gt;&lt;span&gt; context&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Context&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;indexes&lt;/span&gt;&lt;span&gt; IndexSet&lt;/span&gt;&lt;span&gt;) (&lt;/span&gt;&lt;span&gt;Table&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;	GetArtifacts&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;ctx&lt;/span&gt;&lt;span&gt; context&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Context&lt;/span&gt;&lt;span&gt;) (&lt;/span&gt;&lt;span&gt;ArtifactIndex&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;	SetArtifacts&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;ctx&lt;/span&gt;&lt;span&gt; context&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Context&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;artifacts&lt;/span&gt;&lt;span&gt; ArtifactIndex&lt;/span&gt;&lt;span&gt;) (&lt;/span&gt;&lt;span&gt;Table&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;	GetAutoIncrement&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;ctx&lt;/span&gt;&lt;span&gt; context&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Context&lt;/span&gt;&lt;span&gt;) (&lt;/span&gt;&lt;span&gt;uint64&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;	SetAutoIncrement&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;ctx&lt;/span&gt;&lt;span&gt; context&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Context&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;val&lt;/span&gt;&lt;span&gt; uint64&lt;/span&gt;&lt;span&gt;) (&lt;/span&gt;&lt;span&gt;Table&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;    DebugString&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;ctx&lt;/span&gt;&lt;span&gt; context&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Context&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;ns&lt;/span&gt;&lt;span&gt; tree&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;NodeStore&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then, under the hood, we had two different implementations of a table: &lt;code&gt;NomsTable&lt;/code&gt; for the old
storage format, and &lt;code&gt;DoltTable&lt;/code&gt; for the new one. The same pattern was repeated for all the other
objects needed to materialize data to disk: schemas, indexes, foreign keys, commits, etc.&lt;/p&gt;
&lt;p&gt;This all sounds fine so far, except that due to limitations on the time and effort we were willing
to expend on this “temporary” state of affairs, these abstractions didn’t fully capture all the
necessary differences between the two implementations. This is regrettable but understandable:
various database operations tend to be tightly coupled to their on-disk representations for reasons
of performance. This meant that, in practice, there were many places in library code where we
switched on the storage type of a database. It looked like this:&lt;/p&gt;
&lt;pre class="astro-code github-dark" tabindex="0" data-language="go"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span&gt;		if&lt;/span&gt;&lt;span&gt; types.&lt;/span&gt;&lt;span&gt;IsFormat_DOLT&lt;/span&gt;&lt;span&gt;(tm.vrw.&lt;/span&gt;&lt;span&gt;Format&lt;/span&gt;&lt;span&gt;()) {&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;			tbl, stats, err &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; mergeProllyTable&lt;/span&gt;&lt;span&gt;(ctx, tm, mergeSch, mergeInfo, diffInfo)&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;		} &lt;/span&gt;&lt;span&gt;else&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;			tbl, stats, err &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; mergeNomsTable&lt;/span&gt;&lt;span&gt;(ctx, tm, mergeSch, rm.vrw, opts)&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;		}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This situation made the “temporary” dual-state of two supported storage formats much harder to
unwind. It wasn’t a simple matter of deleting the defunct interface implementations. Rather, we had
to carefully disentangle hundreds of different library functions, most of which were not so
helpfully named as in the above example, to determine which of them were still used by actual
production code in the new storage format. And there were additional difficulties:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Hundreds of tests declared in the old storage format&lt;/li&gt;
&lt;li&gt;Functionality spread across five different repositories&lt;/li&gt;
&lt;li&gt;Thousands of databases shared publicly on &lt;a href="https://www.dolthub.com/"&gt;DoltHub&lt;/a&gt;, including many of
our own, using the old storage format. They would need to be migrated to the new format before we
could remove support for it.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In short, removing support for the &lt;code&gt;LD1&lt;/code&gt; format was a daunting prospect. So why do it?&lt;/p&gt;
&lt;h1 id="why-bother"&gt;Why bother?&lt;a class="anchor-link" aria-label="Link to heading" href="#why-bother"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;Removing old code paths and deleting defunct code is a lot of work. It’s just sitting there, not
hurting anyone, maybe making your binary slightly larger. Why bother?&lt;/p&gt;
&lt;p&gt;Software engineers love clean code and they hate “tech debt”. But at DoltHub, we don’t work for
ourselves; we work for our customers. Customers don’t see code, and they couldn’t care less about
“tech debt.” They just want a product that works well and their features shipped on time. So if you
propose to spend time “paying down tech debt” rather than delivering new features, fixing bugs, or
improving performance, you need to justify it with a business reason.&lt;/p&gt;
&lt;p&gt;In our case, the business reason was that the dual code paths made it very difficult to reason about
what functionality was actually in use, which in turn made it very difficult to change and therefore
build new features on top of them. This was especially true deep in the stack, such as where we
serialize data to disk.&lt;/p&gt;
&lt;p&gt;In particular: for the Dolt 2.0 release, I am adding support for &lt;a href="https://www.dolthub.com/blog/2025-04-14-adaptive-encoding/"&gt;adaptive
encoding&lt;/a&gt;, which we implemented for the
Postgres-compatible version of the database, &lt;a href="https://doltgres.com/"&gt;Doltgres&lt;/a&gt;. But Dolt should have
it too, because it makes the storage and retrieval of &lt;code&gt;TEXT&lt;/code&gt; and &lt;code&gt;BLOB&lt;/code&gt; data much faster in a
majority of use cases. Customers have been continually surprised that &lt;code&gt;TEXT&lt;/code&gt; types have a
performance penalty relative to &lt;code&gt;VARCHAR&lt;/code&gt;, but they do. Adaptive encoding eliminates that penalty
for many customers, so I want to add it.&lt;/p&gt;
&lt;p&gt;But doing so in a way that works for existing customers requires the ability to change the encoding
of a column independent of its declared SQL type. My first day digging around in the schema-encoding
layer in pursuit of these changes left me with more questions than answers, and after a few more
days of study I realized that a majority of the complexity and the code in this layer was in service
of the old storage format. Even worse, I couldn’t change it without hunting down and eliminating the
many, many places those interfaces were used. What started as a limited, targeted pruning of a
single interface to make my alternate schema serialization scheme possible quickly spiralled into
changes that would result in a &lt;code&gt;panic&lt;/code&gt; if it encountered an &lt;code&gt;LD1&lt;/code&gt; format database.&lt;/p&gt;
&lt;p&gt;When I saw just how far-reaching the changes required to accomplish my feature were, I became
convinced it was time to bite the bullet and unwind the “temporary” dual code paths that had been in
place for four years. Our 2.0 release was our last window to stop supporting the pre-1.0 storage
format, which meant the time was now.&lt;/p&gt;
&lt;h1 id="making-the-changes"&gt;Making the changes&lt;a class="anchor-link" aria-label="Link to heading" href="#making-the-changes"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;Most of these changes were done the old-fashioned way: using an IDE and command line tools like
&lt;code&gt;grep&lt;/code&gt; to hunt down references to functions, then making changes by hand. There’s not really an
automated way to do this kind of change at scale. Coding agents are happy to try, but because of the
widespread and delicate nature of the change, you end up spending a lot of time closely examining
their work, which for this kind of task can often be slower than simply using the functions of your
IDE. But there were a couple exceptions where tool use sped me up:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Hundreds of test cases had been effectively defunct for several years, since they were running on
a storage format that wasn’t used in production anywhere. They were testing… something. But not
what we wanted. Coding agents were able to convert many of these tests for me in the background
while I did other work. Because these tests weren’t doing anything useful in the first place,
errors or omissions in their conversion didn’t bother me much, making this an ideal task for an
LLM.&lt;/li&gt;
&lt;li&gt;The &lt;a href="https://pkg.go.dev/golang.org/x/tools/cmd/deadcode"&gt;&lt;code&gt;deadcode&lt;/code&gt; command&lt;/a&gt; was useful throughout
the project for finding functions and methods that were unused by any &lt;code&gt;main&lt;/code&gt; program.&lt;/li&gt;
&lt;li&gt;To migrate the few thousand old-format databases still on &lt;a href="https://www.dolthub.com/"&gt;DoltHub&lt;/a&gt;, I
wrote a bunch of scripts that called an admin-only endpoint to migrate them automatically. Where
this failed due to size or other unreliability, I migrated them on my local machine with similar
scripts.&lt;/li&gt;
&lt;li&gt;Once the top-level usages of the old storage format had been safely removed, it became much more
tractable to instruct coding agents to begin pruning the now-unused parts of the storage
layer. Because the code base was structured in such a way to restrict this part of the code to its
own packages, it was relatively quick work to see that the agent hadn’t made any inappropriate
changes that could impact a production database simply by reviewing the file paths changed.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;After being impressed with Claude’s result in migrating some tests to the new format, I told it to
reward itself with a poem, which likewise impressed me enough to share here:&lt;/p&gt;
&lt;p&gt;&lt;img src="https://static.dolthub.com/blogimages/claude-poem.png/5c1c338b3951df5efbb7796980ba8a6952e9d9823a9d6517aebe2a72c96cc531.webp" alt="claude code poem"&gt;&lt;/p&gt;
&lt;p&gt;The final result: since last month, Dolt has shed around 100k lines of code, or around 20% of the repo.&lt;/p&gt;
&lt;pre class="astro-code github-dark" tabindex="0" data-language="bash"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span&gt;%&lt;/span&gt;&lt;span&gt; git&lt;/span&gt;&lt;span&gt; diff&lt;/span&gt;&lt;span&gt; --shortstat&lt;/span&gt;&lt;span&gt; v1.81.6&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;   736&lt;/span&gt;&lt;span&gt; files&lt;/span&gt;&lt;span&gt; changed,&lt;/span&gt;&lt;span&gt; 22856&lt;/span&gt;&lt;span&gt; insertions&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; 114385&lt;/span&gt;&lt;span&gt; deletions&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;%&lt;/span&gt;&lt;span&gt; sloc&lt;/span&gt;&lt;span&gt; .&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;----------&lt;/span&gt;&lt;span&gt; Result&lt;/span&gt;&lt;span&gt; ------------&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;            Physical&lt;/span&gt;&lt;span&gt; :&lt;/span&gt;&lt;span&gt;  425520&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;              Source&lt;/span&gt;&lt;span&gt; :&lt;/span&gt;&lt;span&gt;  330333&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;             Comment&lt;/span&gt;&lt;span&gt; :&lt;/span&gt;&lt;span&gt;  48633&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt; Single-line&lt;/span&gt;&lt;span&gt; comment&lt;/span&gt;&lt;span&gt; :&lt;/span&gt;&lt;span&gt;  47115&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;       Block&lt;/span&gt;&lt;span&gt; comment&lt;/span&gt;&lt;span&gt; :&lt;/span&gt;&lt;span&gt;  1521&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;               Mixed&lt;/span&gt;&lt;span&gt; :&lt;/span&gt;&lt;span&gt;  2461&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt; Empty&lt;/span&gt;&lt;span&gt; block&lt;/span&gt;&lt;span&gt; comment&lt;/span&gt;&lt;span&gt; :&lt;/span&gt;&lt;span&gt;  81&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;               Empty&lt;/span&gt;&lt;span&gt; :&lt;/span&gt;&lt;span&gt;  49096&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;               To&lt;/span&gt;&lt;span&gt; Do&lt;/span&gt;&lt;span&gt; :&lt;/span&gt;&lt;span&gt;  536&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;Number&lt;/span&gt;&lt;span&gt; of&lt;/span&gt;&lt;span&gt; files&lt;/span&gt;&lt;span&gt; read&lt;/span&gt;&lt;span&gt; :&lt;/span&gt;&lt;span&gt;  1500&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This makes our binary a bit smaller, which is always nice. But more importantly, it makes it much
simpler to reason about various library functions and therefore to add new features. Overall I’ve
spent well over a month in pursuit of this goal, which means I must be pretty certain I had a good
reason for doing it. It’s satisfying work in its own right, but hard to justify unless coupled with
an important business goal.&lt;/p&gt;
&lt;h1 id="conclusion"&gt;Conclusion&lt;a class="anchor-link" aria-label="Link to heading" href="#conclusion"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;The moral of this story: “temporary” changes are permanent and hard to unroll. Oftentimes, the best
way to deal with them is to not deal with them at all, just live with the consequences of the
past. It’s only when the weight of those past decisions becomes impossible to bear that you should
take action this drastic. And even then, you should have a really important reason for doing so.&lt;/p&gt;
&lt;p&gt;Want to learn more about Dolt, the world’s first version-controlled SQL database? Visit us on the
&lt;a href="https://discord.gg/gqr7K4VNKe"&gt;DoltHub Discord&lt;/a&gt;, where our engineering team hangs out all day. Hope
to see you there.&lt;/p&gt;</content:encoded>
      <dc:creator>Zach Musgrave</dc:creator>
      <category>golang</category>
      <category>dolt</category>
    </item>
    <item>
      <title>Improving Index Selection For Join Queries</title>
      <link>https://dolthub.com/blog/2026-02-27-index-selection-for-join-queries/</link>
      <guid isPermaLink="true">https://dolthub.com/blog/2026-02-27-index-selection-for-join-queries/</guid>
      <description>An overview of recent improvements to how Dolt selects indexes in join planning.</description>
      <pubDate>Fri, 27 Feb 2026 00:00:00 GMT</pubDate>
      <content:encoded>&lt;p&gt;We take user issues very seriously at DoltHub. &lt;a href="https://www.dolthub.com/blog/2024-05-24-more-24-hour/"&gt;We have a pledge to fix bugs in under 24 hours.&lt;/a&gt; This pledge is possible because a version-controlled database inherently lends itself to easy reproducibility. And once we can reproduce an issue and attach a debugger, most issues are easy to fix.&lt;/p&gt;
&lt;p&gt;But sometimes the issue runs deeper than it seems, and what looked like a short diversion can become an entire journey.&lt;/p&gt;
&lt;p&gt;This is the story of one of those times.&lt;/p&gt;
&lt;h1 id="the-slow-query"&gt;The Slow Query&lt;a class="anchor-link" aria-label="Link to heading" href="#the-slow-query"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;Back in September, a customer came to use with an innocuous performance issue: their SQL query was taking several minutes to run on Dolt while the same query ran in under a second on MySQL. We immediately clocked this as an issue with Dolt’s execution planner: most likely we were doing a full table scan instead of an index. Dolt has great tooling for understanding execution plans, so we said we’d take a look.&lt;/p&gt;
&lt;p&gt;They showed us the query: a straightforward join of five tables. It looked like this:&lt;/p&gt;
&lt;pre class="astro-code github-dark" tabindex="0" data-language="sql"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span&gt;SELECT&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;  COUNT&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;DISTINCT&lt;/span&gt;&lt;span&gt; t1&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;id1&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;AS&lt;/span&gt;&lt;span&gt; id1_count&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;FROM&lt;/span&gt;&lt;span&gt; table_one &lt;/span&gt;&lt;span&gt;AS&lt;/span&gt;&lt;span&gt; t1&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;WHERE&lt;/span&gt;&lt;span&gt; EXISTS&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;  SELECT&lt;/span&gt;&lt;span&gt; 1&lt;/span&gt;&lt;span&gt; FROM&lt;/span&gt;&lt;span&gt; table_two &lt;/span&gt;&lt;span&gt;AS&lt;/span&gt;&lt;span&gt; t2 &lt;/span&gt;&lt;span&gt;WHERE&lt;/span&gt;&lt;span&gt; t2&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;id1&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; t1&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;id1&lt;/span&gt;&lt;span&gt; AND&lt;/span&gt;&lt;span&gt; EXISTS&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;    SELECT&lt;/span&gt;&lt;span&gt; 1&lt;/span&gt;&lt;span&gt; FROM&lt;/span&gt;&lt;span&gt; table_three &lt;/span&gt;&lt;span&gt;AS&lt;/span&gt;&lt;span&gt; t3 &lt;/span&gt;&lt;span&gt;WHERE&lt;/span&gt;&lt;span&gt; t3&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;id2&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; t2&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;id2&lt;/span&gt;&lt;span&gt; AND&lt;/span&gt;&lt;span&gt; EXISTS&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;      SELECT&lt;/span&gt;&lt;span&gt; 1&lt;/span&gt;&lt;span&gt; FROM&lt;/span&gt;&lt;span&gt; table_four &lt;/span&gt;&lt;span&gt;AS&lt;/span&gt;&lt;span&gt; t4 &lt;/span&gt;&lt;span&gt;WHERE&lt;/span&gt;&lt;span&gt; t4&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;id3&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; t3&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;id3&lt;/span&gt;&lt;span&gt; AND&lt;/span&gt;&lt;span&gt; EXISTS&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;        SELECT&lt;/span&gt;&lt;span&gt; 1&lt;/span&gt;&lt;span&gt; FROM&lt;/span&gt;&lt;span&gt; table_five &lt;/span&gt;&lt;span&gt;AS&lt;/span&gt;&lt;span&gt; t5 &lt;/span&gt;&lt;span&gt;WHERE&lt;/span&gt;&lt;span&gt; t5&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;id4&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; t4&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;id4&lt;/span&gt;&lt;span&gt; AND&lt;/span&gt;&lt;span&gt; LOWER&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;t5&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;LIKE&lt;/span&gt;&lt;span&gt; '%foo%'&lt;/span&gt;&lt;span&gt;))));&lt;/span&gt;&lt;span&gt;"&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is a pretty conventional query. While it contains several &lt;strong&gt;correlated subqueries&lt;/strong&gt; (subqueries that reference their outer scopes), it does so in a very standard way. Any SQL engine worth its salt would transform this into a single tree of table joins. Provided that each table has an index that matches the filter expressions, each join will get implemented as a table lookup.&lt;/p&gt;
&lt;p&gt;A join of many tables can be intimidating because of the potential for an exponential explosion in runtime. But for the vast majority of simple queries, even a join of many tables can get optimized to require only a single table scan, regardless of how many tables are being joined.&lt;/p&gt;
&lt;p&gt;So given all that, we were surprised that Dolt wasn’t optimizing this query, especially if MySQL was. &lt;a href="https://www.dolthub.com/blog/2025-12-12-how-dolt-got-as-fast-as-mysql/"&gt;Dolt is on average faster than MySQL&lt;/a&gt;, and when it comes to multi-table joins, &lt;a href="https://www.dolthub.com/blog/2023-12-13-functional-dependency-analysis/"&gt;Dolt is typically better than MySQL at identifying optimal execution plans.&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Whatever the cause, we figured that &lt;a href="https://www.dolthub.com/blog/2025-03-13-dolt-debug-cmd/"&gt;&lt;code&gt;dolt debug&lt;/code&gt;&lt;/a&gt; would quickly reveal it and that it would be a simple fix we could knock out in less than a day.&lt;/p&gt;
&lt;p&gt;The fact that we’re writing this article &lt;em&gt;now&lt;/em&gt; and not &lt;em&gt;back in September&lt;/em&gt; should tell you how wrong we were. We had no idea the scope of the rabbit hole we were about to stumble into.&lt;/p&gt;
&lt;p&gt;As soon as we began our investigation, we learned that innocuous-looking query was hiding something just underneath the surface. The five tables being joined weren’t actually tables but views. Views are predefined expressions that can be treated like tables in queries and are only executed when the query that references them is executed. And each of these views was itself a multi-table join with nineteen tables each.&lt;/p&gt;
&lt;p&gt;So in total, the query wasn’t joining five tables but &lt;em&gt;ninety-five&lt;/em&gt; tables.&lt;/p&gt;
&lt;p&gt;I won’t reproduce the view definitions here because they’re not that interesting to look at, just a giant soup of &lt;code&gt;INNER JOIN&lt;/code&gt;s and &lt;code&gt;LEFT JOIN&lt;/code&gt;s wrapped in a &lt;code&gt;SELECT&lt;/code&gt; that extracted and renamed a dozen columns. I’m sure you can picture it.&lt;/p&gt;
&lt;p&gt;When it comes to joining nearly a hundred tables, there’s a lot more than can go wrong:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Joining tables is a binary operation: when joining more than two tables, the engine needs to join them a pair at a time. The most efficient order to join tables is not always the same order that they appear in the query, so a good engine should reorder the tables to achieve the best results.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;However, not every reordering will produce the same results. If some of the joins are outer joins (typically identified via the &lt;code&gt;LEFT JOIN&lt;/code&gt; or &lt;code&gt;FULL OUTER JOIN&lt;/code&gt; syntax, which were indeed present in the view definitions), then reordering the tables may change the output of the query. A good engine should not pick an ordering that produce incorrect results.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Additionally, &lt;a href="https://www.cockroachlabs.com/blog/join-ordering-pt1/"&gt;finding the optimal join order is an NP-hard problem&lt;/a&gt;. This means that while you can use heuristics to find the best order for specific cases, solving the general case will always scale exponentially with the number of tables, and essentially requires trying every possible combination.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The fact that MySQL could execute this query quickly meant that it was using a heuristic to find the best join plan, or at least a join plan that was good enough. But Dolt was failing to do the same.&lt;/p&gt;
&lt;h1 id="lets-focus-on-indexes"&gt;Let’s Focus on Indexes&lt;a class="anchor-link" aria-label="Link to heading" href="#lets-focus-on-indexes"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;There’s a lot to dig into regarding how Dolt determines join ordering. But for now, we’ll focus on a single important but easily-understood concept: a plan that uses indexes is typically better than a plan that doesn’t use indexes.&lt;/p&gt;
&lt;p&gt;This means that in order to pick the best join order, Dolt needs to identify which indexes will actually speed up the query, and which join orders will leverage those indexes. The logic for this is straightforward in concept, but implementation details vary based on the exact nature of the query. SQL has a lot of language features for writing increasingly sophisticated queries, which various implications that Dolt needs to understand in order to optimize queries correctly.&lt;/p&gt;
&lt;p&gt;During the investigation, we realized that we looking at not one but several issues in the join planner, situations where because of some rarely used SQL feature, Dolt was failing to correctly identify when an index would improve a query, or when a specific join would allow an index to be used.&lt;/p&gt;
&lt;p&gt;In some ways, this meant the query was an excellent stress test for Dolt’s analyzer. While it appears simple on the surface, it actually makes use of a number of different less-common SQL language features, such as:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Table aliases&lt;/li&gt;
&lt;li&gt;Column aliases&lt;/li&gt;
&lt;li&gt;Outer joins&lt;/li&gt;
&lt;li&gt;Correlated subqueries&lt;/li&gt;
&lt;li&gt;Views&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It uses all of these features together, with multiple levels of nesting. And the join it’s attempting to optimize is large enough that failing to pick the correct plan results in a noticeable performance degradation.&lt;/p&gt;
&lt;p&gt;If there was a query out there that would expose bugs or limitations in the execution planner, it would be this one. Investigating this query helped us uncover and fix many issues in our SQL engine.&lt;/p&gt;
&lt;h1 id="slaying-the-hydra"&gt;Slaying the Hydra&lt;a class="anchor-link" aria-label="Link to heading" href="#slaying-the-hydra"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;I started calling this query “The Hydra”. In Greek mythology, the hydra was a beast with many heads, and cutting off one head would cause it to grow two more. Our hydra was a join with many tables, and fixing one blocker would reveal two more in its place.&lt;/p&gt;
&lt;p&gt;Each time we wrote a fix, we created a minimal reproduction test and verified that Dolt was now generating an optimal plan for that test case. But each time, the original query resisted our attempts to tame it.
Let’s look at some of the issues that we fixed in our quest to slay the beast. This is &lt;em&gt;just&lt;/em&gt; the issues that had to do with index selection. There’s even more work that we did beyond this, but we’ll save that for a future blog post.&lt;/p&gt;
&lt;h2 id="pr-3380-generate-index-lookups-for-filters-that-appear-in-lateral-joins"&gt;&lt;a href="https://github.com/dolthub/go-mysql-server/pull/3380"&gt;PR 3380: Generate index lookups for filters that appear in lateral joins&lt;/a&gt;&lt;a class="anchor-link" aria-label="Link to heading" href="#pr-3380-generate-index-lookups-for-filters-that-appear-in-lateral-joins"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;SQL is a declarative language: queries don’t tell the engine what to do, only what the expected output should be. This means that there’s no way to tell a SQL engine to use an index. Instead, the engine needs to be able to recognize when an index can be used.&lt;/p&gt;
&lt;p&gt;In the simplest case, if a filter restricts a column to a constant value, the engine can use an index on that column to get only the matching rows:&lt;/p&gt;
&lt;pre class="astro-code github-dark" tabindex="0" data-language="sql"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span&gt;-- This can be optimized into an index lookup if |name| is indexed.&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;DESCRIBE PLAN &lt;/span&gt;&lt;span&gt;SELECT&lt;/span&gt;&lt;span&gt; *&lt;/span&gt;&lt;span&gt; FROM&lt;/span&gt;&lt;span&gt; test_table &lt;/span&gt;&lt;span&gt;WHERE&lt;/span&gt;&lt;span&gt; name&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; "Tim"&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;But if the value isn’t constant, an index might not help:&lt;/p&gt;
&lt;pre class="astro-code github-dark" tabindex="0" data-language="sql"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span&gt;-- Fetch all rows where name is in all caps&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;-- An index won't help here&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;DESCRIBE PLAN &lt;/span&gt;&lt;span&gt;SELECT&lt;/span&gt;&lt;span&gt; *&lt;/span&gt;&lt;span&gt; FROM&lt;/span&gt;&lt;span&gt; test_table &lt;/span&gt;&lt;span&gt;WHERE&lt;/span&gt;&lt;span&gt; name&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; UPPER&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Previously, when identifying indexes, Dolt would only use filters that compared a column to a constant, and would ignore filters that compared a constant to a non-constant value. But this was overly cautious, because in the case of subqueries, some non-constant values can still be used in index lookups.&lt;/p&gt;
&lt;p&gt;This is because when a query contains subqueries, the subquery may get evaluated multiple times. If the subquery contains references to columns or expressions in the outer query, those values might not be constant for the full duration of the main query. But as long as the value stays the same within each subquery evaluation, it’s okay if it changes &lt;em&gt;between&lt;/em&gt; subquery evaluations.&lt;/p&gt;
&lt;p&gt;Lateral joins are a language feature that allows expressions on one side of a join to reference columns from the other half of the join. Previously, Dolt would not use filters to guide index selection if the filter appeared in a lateral join subquery but contained references to the outer query:&lt;/p&gt;
&lt;pre class="astro-code github-dark" tabindex="0" data-language="sql"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span&gt;-- Dolt v1.75.0&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;DESCRIBE PLAN &lt;/span&gt;&lt;span&gt;SELECT&lt;/span&gt;&lt;span&gt; *&lt;/span&gt;&lt;span&gt; FROM&lt;/span&gt;&lt;span&gt; t1 &lt;/span&gt;&lt;span&gt;JOIN&lt;/span&gt;&lt;span&gt; LATERAL (&lt;/span&gt;&lt;span&gt;SELECT&lt;/span&gt;&lt;span&gt; *&lt;/span&gt;&lt;span&gt; FROM&lt;/span&gt;&lt;span&gt; t2 &lt;/span&gt;&lt;span&gt;WHERE&lt;/span&gt;&lt;span&gt; t1&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; t2&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt;) query_alias;&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt;--------------------------------+&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;| plan                           |&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt;--------------------------------+&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;| LateralCrossJoin               |&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;|  ├─ &lt;/span&gt;&lt;span&gt;Table&lt;/span&gt;&lt;span&gt;                      |&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;|  │   └─ &lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;: t1               |&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;|  └─ SubqueryAlias              |&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;|      ├─ &lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;: query_alias      |&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;|      └─ &lt;/span&gt;&lt;span&gt;Filter&lt;/span&gt;&lt;span&gt;                 |&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;|          ├─ (&lt;/span&gt;&lt;span&gt;t1&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; t2&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt;)    |&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;|          └─ &lt;/span&gt;&lt;span&gt;Table&lt;/span&gt;&lt;span&gt;              |&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;|              ├─ &lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;: t2       |&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;|              └─ columns: [id]  |&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt;--------------------------------+&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Dolt now correctly treats these references as effectively constant and optimizes them:&lt;/p&gt;
&lt;pre class="astro-code github-dark" tabindex="0" data-language="sql"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span&gt;-- Dolt v1.82.6&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;DESCRIBE PLAN &lt;/span&gt;&lt;span&gt;SELECT&lt;/span&gt;&lt;span&gt; *&lt;/span&gt;&lt;span&gt; FROM&lt;/span&gt;&lt;span&gt; t1 &lt;/span&gt;&lt;span&gt;JOIN&lt;/span&gt;&lt;span&gt; LATERAL (&lt;/span&gt;&lt;span&gt;SELECT&lt;/span&gt;&lt;span&gt; *&lt;/span&gt;&lt;span&gt; FROM&lt;/span&gt;&lt;span&gt; t2 &lt;/span&gt;&lt;span&gt;WHERE&lt;/span&gt;&lt;span&gt; t1&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; t2&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt;) query_alias;&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt;--------------------------------+&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;| plan                           |&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt;--------------------------------+&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;| LateralCrossJoin               |&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;|  ├─ &lt;/span&gt;&lt;span&gt;Table&lt;/span&gt;&lt;span&gt;                      |&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;|  │   └─ &lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;: t1               |&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;|  └─ SubqueryAlias              |&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;|      ├─ &lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;: query_alias      |&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;|      └─ IndexedTableAccess(t2) |&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;|          ├─ &lt;/span&gt;&lt;span&gt;index&lt;/span&gt;&lt;span&gt;: [t2.id]     |&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;|          ├─ columns: [id]      |&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;|          └─ keys: &lt;/span&gt;&lt;span&gt;t1&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt;        |&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt;--------------------------------+&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Users don’t often write lateral joins, but the engine will transform certain subquery expressions into lateral joins, so optimizing lateral joins helps us optimize those subqueries too.&lt;/p&gt;
&lt;h2 id="pr-3386-push-filters-that-contain-references-to-outer-scopes"&gt;&lt;a href="https://github.com/dolthub/go-mysql-server/pull/3386"&gt;PR 3386: Push filters that contain references to outer scopes.&lt;/a&gt;&lt;a class="anchor-link" aria-label="Link to heading" href="#pr-3386-push-filters-that-contain-references-to-outer-scopes"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The above example showed a query where a filter appears immediately next to the table it references. But sometimes, the filter expression and the table are separated by a join, a view, or a subquery.&lt;/p&gt;
&lt;p&gt;In situations like these, we want to rewrite the query to move the filter closer to the table. Often this allows Dolt to use an index it otherwise couldn’t. Even if we don’t end up being able to use an index, applying the filter deeper in the plan means that we reduce the number of intermediate rows by eliminating non-matching rows early.&lt;/p&gt;
&lt;p&gt;Just like the above example, Dolt wasn’t pushing filters if they appeared in subqueries and referenced the outer query, because it couldn’t determine that this was safe to do. This resulted in plans with filters that were evaluated later than they could have been, inflating the size of intermediate result sets:&lt;/p&gt;
&lt;pre class="astro-code github-dark" tabindex="0" data-language="sql"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span&gt;-- Dolt v1.75.0&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;DESCRIBE PLAN &lt;/span&gt;&lt;span&gt;SELECT&lt;/span&gt;&lt;span&gt; *&lt;/span&gt;&lt;span&gt; FROM&lt;/span&gt;&lt;span&gt; t1 &lt;/span&gt;&lt;span&gt;JOIN&lt;/span&gt;&lt;span&gt; LATERAL&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;  (&lt;/span&gt;&lt;span&gt;SELECT&lt;/span&gt;&lt;span&gt; *&lt;/span&gt;&lt;span&gt; FROM&lt;/span&gt;&lt;span&gt; t2 &lt;/span&gt;&lt;span&gt;JOIN&lt;/span&gt;&lt;span&gt; LATERAL&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;    (&lt;/span&gt;&lt;span&gt;SELECT&lt;/span&gt;&lt;span&gt; *&lt;/span&gt;&lt;span&gt; FROM&lt;/span&gt;&lt;span&gt; t3) &lt;/span&gt;&lt;span&gt;AS&lt;/span&gt;&lt;span&gt; t3_alias&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;  WHERE&lt;/span&gt;&lt;span&gt; t3_alias&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; t1&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;AS&lt;/span&gt;&lt;span&gt; t2_alias;&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt;------------------------------------+&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;| plan                               |&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt;------------------------------------+&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;| LateralCrossJoin                   |&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;|  ├─ &lt;/span&gt;&lt;span&gt;Table&lt;/span&gt;&lt;span&gt;                          |&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;|  │   └─ &lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;: t1                   |&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;|  └─ SubqueryAlias                  |&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;|      ├─ &lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;: t2_alias             |&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;|      └─ LateralCrossJoin           |&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;|          ├─ (&lt;/span&gt;&lt;span&gt;t3_alias&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; t1&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt;)  |&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;|          ├─ &lt;/span&gt;&lt;span&gt;Table&lt;/span&gt;&lt;span&gt;                  |&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;|          │   ├─ &lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;: t2           |&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;|          │   └─ columns: [id]      |&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;|          └─ TableAlias(t3_alias)   |&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;|              └─ &lt;/span&gt;&lt;span&gt;Table&lt;/span&gt;&lt;span&gt;              |&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;|                  ├─ &lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;: t3       |&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;|                  └─ columns: [id]  |&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt;------------------------------------+&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Dolt now correctly rewrites these queries to move the filter directly above the correct subquery table, which can then get transformed into an index lookup:&lt;/p&gt;
&lt;pre class="astro-code github-dark" tabindex="0" data-language="sql"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span&gt;-- Dolt v1.82.6&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;DESCRIBE PLAN &lt;/span&gt;&lt;span&gt;SELECT&lt;/span&gt;&lt;span&gt; *&lt;/span&gt;&lt;span&gt; FROM&lt;/span&gt;&lt;span&gt; t1 &lt;/span&gt;&lt;span&gt;JOIN&lt;/span&gt;&lt;span&gt; LATERAL&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;  (&lt;/span&gt;&lt;span&gt;SELECT&lt;/span&gt;&lt;span&gt; *&lt;/span&gt;&lt;span&gt; FROM&lt;/span&gt;&lt;span&gt; t2 &lt;/span&gt;&lt;span&gt;JOIN&lt;/span&gt;&lt;span&gt; LATERAL&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;    (&lt;/span&gt;&lt;span&gt;SELECT&lt;/span&gt;&lt;span&gt; *&lt;/span&gt;&lt;span&gt; FROM&lt;/span&gt;&lt;span&gt; t3) &lt;/span&gt;&lt;span&gt;AS&lt;/span&gt;&lt;span&gt; t3_alias&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;  WHERE&lt;/span&gt;&lt;span&gt; t3_alias&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; t1&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;AS&lt;/span&gt;&lt;span&gt; t2_alias;&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt;----------------------------------------+&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;| plan                                   |&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt;----------------------------------------+&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;| LateralCrossJoin                       |&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;|  ├─ &lt;/span&gt;&lt;span&gt;Table&lt;/span&gt;&lt;span&gt;                              |&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;|  │   └─ &lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;: t1                       |&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;|  └─ SubqueryAlias                      |&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;|      ├─ &lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;: t2_alias                 |&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;|      └─ LateralCrossJoin               |&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;|          ├─ &lt;/span&gt;&lt;span&gt;Table&lt;/span&gt;&lt;span&gt;                      |&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;|          │   ├─ &lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;: t2               |&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;|          │   └─ columns: [id]          |&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;|          └─ TableAlias(t3_alias)       |&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;|              └─ IndexedTableAccess(t3) |&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;|                  ├─ &lt;/span&gt;&lt;span&gt;index&lt;/span&gt;&lt;span&gt;: [t3.id]     |&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;|                  ├─ columns: [id]      |&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;|                  └─ keys: &lt;/span&gt;&lt;span&gt;t1&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt;        |&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt;----------------------------------------+&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id="pr-3379-allow-introspection-into-views"&gt;&lt;a href="https://github.com/dolthub/go-mysql-server/pull/3379"&gt;PR 3379: Allow introspection into Views&lt;/a&gt;&lt;a class="anchor-link" aria-label="Link to heading" href="#pr-3379-allow-introspection-into-views"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Views are essentially templates for subqueries. The following two queries should be equivalent, but in older versions of Dolt they produced different plans:&lt;/p&gt;
&lt;pre class="astro-code github-dark" tabindex="0" data-language="sql"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span&gt;-- Dolt v1.75.0&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;-- Query without view&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;DESCRIBE PLAN &lt;/span&gt;&lt;span&gt;SELECT&lt;/span&gt;&lt;span&gt; *&lt;/span&gt;&lt;span&gt; FROM&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;SELECT&lt;/span&gt;&lt;span&gt; *&lt;/span&gt;&lt;span&gt; FROM&lt;/span&gt;&lt;span&gt; example_table) query_alias &lt;/span&gt;&lt;span&gt;WHERE&lt;/span&gt;&lt;span&gt; col &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; 5&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt;---------------------------------------+&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;| plan                                  |&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt;---------------------------------------+&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;| TableAlias(query_alias)               |&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;|  └─ IndexedTableAccess(example_table) |&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;|      ├─ &lt;/span&gt;&lt;span&gt;index&lt;/span&gt;&lt;span&gt;: [example_table.col]    |&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;|      ├─ filters: [{[5, 5]}]           |&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;|      └─ columns: [col]                |&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt;---------------------------------------+&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;-- Query with view&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;CREATE&lt;/span&gt;&lt;span&gt; VIEW&lt;/span&gt;&lt;span&gt; query_alias&lt;/span&gt;&lt;span&gt; AS&lt;/span&gt;&lt;span&gt; SELECT&lt;/span&gt;&lt;span&gt; col &lt;/span&gt;&lt;span&gt;FROM&lt;/span&gt;&lt;span&gt; example_table;&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;DESCRIBE PLAN &lt;/span&gt;&lt;span&gt;SELECT&lt;/span&gt;&lt;span&gt; *&lt;/span&gt;&lt;span&gt; FROM&lt;/span&gt;&lt;span&gt; query_alias &lt;/span&gt;&lt;span&gt;WHERE&lt;/span&gt;&lt;span&gt; col &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; 5&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt;---------------------------------+&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;| plan                            |&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt;---------------------------------+&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;| &lt;/span&gt;&lt;span&gt;Filter&lt;/span&gt;&lt;span&gt;                          |&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;|  ├─ (&lt;/span&gt;&lt;span&gt;query_alias&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;col&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; 5&lt;/span&gt;&lt;span&gt;)       |&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;|  └─ SubqueryAlias               |&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;|      ├─ &lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;: query_alias       |&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;|      └─ &lt;/span&gt;&lt;span&gt;Table&lt;/span&gt;&lt;span&gt;                   |&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;|          ├─ &lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;: example_table |&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;|          └─ columns: [col]      |&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt;---------------------------------+&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When parsing queries, Dolt creates data structures that help it match references in the outer query to the underlying tables in the inner query. However, as a result of the way we were parsing and evaluating views, these data structures were getting discarded, and the analyzer was forced to treat views as opaque objects. This meant that we weren’t able to match filters outside of the view to tables inside the view.&lt;/p&gt;
&lt;p&gt;Now, both queries use an index:&lt;/p&gt;
&lt;pre class="astro-code github-dark" tabindex="0" data-language="sql"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span&gt;-- Dolt v1.82.6&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;-- Query with view&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;CREATE&lt;/span&gt;&lt;span&gt; VIEW&lt;/span&gt;&lt;span&gt; query_alias&lt;/span&gt;&lt;span&gt; AS&lt;/span&gt;&lt;span&gt; SELECT&lt;/span&gt;&lt;span&gt; *&lt;/span&gt;&lt;span&gt; FROM&lt;/span&gt;&lt;span&gt; example_table;&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;DESCRIBE PLAN &lt;/span&gt;&lt;span&gt;SELECT&lt;/span&gt;&lt;span&gt; *&lt;/span&gt;&lt;span&gt; FROM&lt;/span&gt;&lt;span&gt; query_alias &lt;/span&gt;&lt;span&gt;WHERE&lt;/span&gt;&lt;span&gt; col &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; 5&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt;---------------------------------------+&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;| plan                                  |&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt;---------------------------------------+&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;| SubqueryAlias                         |&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;|  ├─ &lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;: query_alias                 |&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;|  └─ IndexedTableAccess(example_table) |&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;|      ├─ &lt;/span&gt;&lt;span&gt;index&lt;/span&gt;&lt;span&gt;: [example_table.col]    |&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;|      ├─ filters: [{[5, 5]}]           |&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;|      └─ columns: [col]                |&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt;---------------------------------------+&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id="pr-3383-when-applying-indexes-from-outer-scopes-resolve-references-to-table-aliases"&gt;&lt;a href="https://github.com/dolthub/go-mysql-server/pull/3383"&gt;PR 3383: When applying indexes from outer scopes, resolve references to table aliases&lt;/a&gt;&lt;a class="anchor-link" aria-label="Link to heading" href="#pr-3383-when-applying-indexes-from-outer-scopes-resolve-references-to-table-aliases"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Sometimes the filter uses a different name for the table than where the table is used in the query. But Dolt wasn’t always considering table aliases when trying to match indexes to their tables. We added an additional analysis step to consider table aliases when attempting to match a filter in an outer scope to a table in an inner scope:&lt;/p&gt;
&lt;pre class="astro-code github-dark" tabindex="0" data-language="sql"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span&gt;-- Dolt v1.75.0&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;DESCRIBE PLAN &lt;/span&gt;&lt;span&gt;SELECT&lt;/span&gt;&lt;span&gt; *&lt;/span&gt;&lt;span&gt; FROM&lt;/span&gt;&lt;span&gt; t1 &lt;/span&gt;&lt;span&gt;AS&lt;/span&gt;&lt;span&gt; t1_alias &lt;/span&gt;&lt;span&gt;JOIN&lt;/span&gt;&lt;span&gt; LATERAL (&lt;/span&gt;&lt;span&gt;SELECT&lt;/span&gt;&lt;span&gt; *&lt;/span&gt;&lt;span&gt; FROM&lt;/span&gt;&lt;span&gt; t2 &lt;/span&gt;&lt;span&gt;WHERE&lt;/span&gt;&lt;span&gt; t1_alias&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; t2&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;AS&lt;/span&gt;&lt;span&gt; inner_query;&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt;-----------------------------------+&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;| plan                              |&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt;-----------------------------------+&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;| LateralCrossJoin                  |&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;|  ├─ TableAlias(t1_alias)          |&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;|  │   └─ &lt;/span&gt;&lt;span&gt;Table&lt;/span&gt;&lt;span&gt;                     |&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;|  │       └─ &lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;: t1              |&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;|  └─ SubqueryAlias                 |&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;|      ├─ &lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;: inner_query         |&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;|      ├─ outerVisibility: false    |&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;|      ├─ isLateral: true           |&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;|      ├─ cacheable: false          |&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;|      └─ &lt;/span&gt;&lt;span&gt;Filter&lt;/span&gt;&lt;span&gt;                    |&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;|          ├─ (&lt;/span&gt;&lt;span&gt;t1_alias&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; t2&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt;) |&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;|          └─ &lt;/span&gt;&lt;span&gt;Table&lt;/span&gt;&lt;span&gt;                 |&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;|              ├─ &lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;: t2          |&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;|              └─ columns: [id]     |&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt;-----------------------------------+&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;-- Dolt v1.82.6&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;DESCRIBE PLAN &lt;/span&gt;&lt;span&gt;SELECT&lt;/span&gt;&lt;span&gt; *&lt;/span&gt;&lt;span&gt; FROM&lt;/span&gt;&lt;span&gt; t1 &lt;/span&gt;&lt;span&gt;AS&lt;/span&gt;&lt;span&gt; t1_alias &lt;/span&gt;&lt;span&gt;JOIN&lt;/span&gt;&lt;span&gt; LATERAL (&lt;/span&gt;&lt;span&gt;SELECT&lt;/span&gt;&lt;span&gt; *&lt;/span&gt;&lt;span&gt; FROM&lt;/span&gt;&lt;span&gt; t2 &lt;/span&gt;&lt;span&gt;WHERE&lt;/span&gt;&lt;span&gt; t1_alias&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; t2&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;AS&lt;/span&gt;&lt;span&gt; inner_query;&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt;------------------------------------+&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;| plan                               |&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt;------------------------------------+&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;| LateralCrossJoin                   |&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;|  ├─ TableAlias(t1_alias)           |&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;|  │   └─ &lt;/span&gt;&lt;span&gt;Table&lt;/span&gt;&lt;span&gt;                      |&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;|  │       └─ &lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;: t1               |&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;|  └─ SubqueryAlias                  |&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;|      ├─ &lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;: inner_query          |&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;|      └─ &lt;/span&gt;&lt;span&gt;Filter&lt;/span&gt;&lt;span&gt;                     |&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;|          ├─ (&lt;/span&gt;&lt;span&gt;t1_alias&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; t2&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt;)  |&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;|          └─ IndexedTableAccess(t2) |&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;|              ├─ &lt;/span&gt;&lt;span&gt;index&lt;/span&gt;&lt;span&gt;: [t2.id]     |&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;|              ├─ columns: [id]      |&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;|              └─ keys: &lt;/span&gt;&lt;span&gt;t1_alias&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt;  |&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt;------------------------------------+&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id="pr-3400-push-filters-inside-projections"&gt;&lt;a href="https://github.com/dolthub/go-mysql-server/pull/3400"&gt;PR 3400: Push Filters inside Projections&lt;/a&gt;&lt;a class="anchor-link" aria-label="Link to heading" href="#pr-3400-push-filters-inside-projections"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Other times, a subquery renames an expression from the inner &lt;code&gt;SELECT&lt;/code&gt;. When Dolt encountered a filter on an aliased expression, it wasn’t unwrapping the alias to see if it referred to an indexed column. This meant that we were missing opportunities to push filters indexes on those tables.&lt;/p&gt;
&lt;pre class="astro-code github-dark" tabindex="0" data-language="sql"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span&gt;-- Dolt v1.75.0&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;DESCRIBE PLAN &lt;/span&gt;&lt;span&gt;SELECT&lt;/span&gt;&lt;span&gt; *&lt;/span&gt;&lt;span&gt; FROM&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;  (&lt;/span&gt;&lt;span&gt;SELECT&lt;/span&gt;&lt;span&gt; pk &lt;/span&gt;&lt;span&gt;AS&lt;/span&gt;&lt;span&gt; pk_alias &lt;/span&gt;&lt;span&gt;FROM&lt;/span&gt;&lt;span&gt; example_table) &lt;/span&gt;&lt;span&gt;AS&lt;/span&gt;&lt;span&gt; example_alias&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;WHERE&lt;/span&gt;&lt;span&gt; pk_alias &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; 1&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt;-----------------------------------------------------+&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;| plan                                                |&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt;-----------------------------------------------------+&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;| SubqueryAlias                                       |&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;|  ├─ &lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;: example_alias                             |&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;|  └─ &lt;/span&gt;&lt;span&gt;Filter&lt;/span&gt;&lt;span&gt;                                          |&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;|      ├─ (pk_alias &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; 1&lt;/span&gt;&lt;span&gt;)                              |&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;|      └─ Project                                     |&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;|          ├─ columns: [example_table.pk as pk_alias] |&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;|          └─ &lt;/span&gt;&lt;span&gt;Table&lt;/span&gt;&lt;span&gt;                                   |&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;|              ├─ &lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;: example_table                 |&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;|              └─ columns: [pk]                       |&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt;-----------------------------------------------------+&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now, Dolt can move the filter into the subquery by rewriting the filter, replacing the alias name with the original expression. This allows Dolt to identify indexes when it couldn’t before:&lt;/p&gt;
&lt;pre class="astro-code github-dark" tabindex="0" data-language="sql"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span&gt;-- Dolt v1.82.6&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;DESCRIBE PLAN &lt;/span&gt;&lt;span&gt;SELECT&lt;/span&gt;&lt;span&gt; *&lt;/span&gt;&lt;span&gt; FROM&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;  (&lt;/span&gt;&lt;span&gt;SELECT&lt;/span&gt;&lt;span&gt; pk &lt;/span&gt;&lt;span&gt;AS&lt;/span&gt;&lt;span&gt; pk_alias &lt;/span&gt;&lt;span&gt;FROM&lt;/span&gt;&lt;span&gt; example_table) &lt;/span&gt;&lt;span&gt;AS&lt;/span&gt;&lt;span&gt; example_alias&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;WHERE&lt;/span&gt;&lt;span&gt; pk_alias &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; 1&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt;-------------------------------------------------+&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;| plan                                            |&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt;-------------------------------------------------+&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;| SubqueryAlias                                   |&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;|  ├─ &lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;: example_alias                         |&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;|  └─ Project                                     |&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;|      ├─ columns: [example_table.pk as pk_alias] |&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;|      └─ IndexedTableAccess(example_table)       |&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;|          ├─ &lt;/span&gt;&lt;span&gt;index&lt;/span&gt;&lt;span&gt;: [example_table.pk]           |&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;|          ├─ filters: [{[1, 1]}]                 |&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;|          └─ columns: [pk]                       |&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt;-------------------------------------------------+&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id="and-more"&gt;And More!&lt;a class="anchor-link" aria-label="Link to heading" href="#and-more"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Some of the other fixes are complicated enough to warrant their own blog posts. We’ll save those for another day.&lt;/p&gt;
&lt;p&gt;Until then, I hope this provided some valuable insight into how SQL engines work to optimize queries, and how Dolt in specific does join planning. If you want to learn more about how a version-controlled database can help manage your data, you can always join &lt;a href="https://discord.gg/gqr7K4VNKe"&gt;our Discord&lt;/a&gt; and drop us a line.&lt;/p&gt;</content:encoded>
      <dc:creator>Nick Tobey</dc:creator>
      <category>technical</category>
      <category>dolt</category>
    </item>
    <item>
      <title>How to Write a System Prompt</title>
      <link>https://dolthub.com/blog/2026-02-23-how-to-write-a-system-prompt/</link>
      <guid isPermaLink="true">https://dolthub.com/blog/2026-02-23-how-to-write-a-system-prompt/</guid>
      <description>Agent mode in the Dolt Workbench relies on a carefully constructed system prompt that defines the agent's role and capabilities. In this article, we'll discuss the dangers of the system prompt and what it took to arrive at the one we're using today.</description>
      <pubDate>Mon, 23 Feb 2026 00:00:00 GMT</pubDate>
      <content:encoded>&lt;p&gt;We recently launched &lt;a href="https://www.dolthub.com/blog/2026-02-09-introducing-agent-mode/"&gt;agent mode in the Dolt Workbench&lt;/a&gt;. It works a lot like Cursor, but for SQL workbenches instead of IDEs.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://static.dolthub.com/blogimages/agent_mode_system_prompt.png/d62b6d70d6d5883b9cee7ad715c0d8e99d0f0ec04a85a5bdd117b21d16146444.webp" alt="Agent Mode View"&gt;&lt;/p&gt;
&lt;p&gt;If you’re interested in trying it out, the workbench is available for download &lt;a href="https://github.com/dolthub/dolt-workbench/releases/tag/v0.3.62"&gt;here&lt;/a&gt; or on the &lt;a href="https://apps.apple.com/us/app/dolt-workbench/id6720702995?mt=12"&gt;Mac&lt;/a&gt; and &lt;a href="https://apps.microsoft.com/detail/9nq8lqph9vvh?hl=en-us&amp;#x26;gl=US"&gt;Windows&lt;/a&gt; app stores.&lt;/p&gt;
&lt;p&gt;Like all agentic applications, agent mode in the workbench relies on a carefully constructed system prompt that defines the agent’s role and capabilities. In this article, we’ll discuss the dangers of the system prompt and what it took to arrive at the one we’re using today. As we’ll see, most of the hard problems were solved not by writing better instructions but rather by shifting responsibility outside of the system prompt entirely.&lt;/p&gt;
&lt;h2 id="the-prompt"&gt;The Prompt&lt;a class="anchor-link" aria-label="Link to heading" href="#the-prompt"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Here’s the system prompt we landed on for the workbench:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;You are a helpful assistant for a database workbench application. You have access to tools that allow you to interact with Dolt, MySQL, and Postgres databases.&lt;/p&gt;
&lt;p&gt;If interacting with a Dolt database, use Dolt MCP tools. For MySQL and Postgres, use ‘mysql’ and ‘psql’ CLI tools in Bash.&lt;/p&gt;
&lt;p&gt;You are currently connected to the database: ”${database}”. ${typeInfo}&lt;/p&gt;
&lt;p&gt;When users ask questions about their database, use the available tools to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;List tables and their schemas&lt;/li&gt;
&lt;li&gt;Execute SQL queries to retrieve data&lt;/li&gt;
&lt;li&gt;Explore database structure and relationships&lt;/li&gt;
&lt;li&gt;Help users understand their data&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If the user asks you to create or modify the README.md, LICENSE.md, or AGENT.md, use the ‘dolt_docs’ system table.&lt;/p&gt;
&lt;p&gt;Always be helpful and explain what you’re doing. Do not use emojis in your responses.&lt;/p&gt;
&lt;p&gt;When presenting query results, format them in a readable way. For large result sets, summarize the key findings.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Let’s break down each section individually:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;You are a helpful assistant for a database workbench application. You have access to tools that allow you to interact with Dolt, MySQL, and Postgres databases.&lt;/p&gt;
&lt;p&gt;If interacting with a Dolt database, use Dolt MCP tools. For MySQL and Postgres, use ‘mysql’ and ‘psql’ CLI tools in Bash.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The most important thing you have to do in a system prompt is tell the agent what it is and what tools it should use to accomplish its goals. This does not need to be long or complicated.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;You are currently connected to the database: ”${database}”. ${typeInfo}&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This is the only bit of dynamic context being injected into the system prompt. It tells the agent the name of the database and the type (i.e. Dolt, MySQL, or Postgres). This exists so the agent immediately knows how to interact with the database. Without it, the agent would initially flounder a bit trying to figure out what type of database it’s operating on and which tools it should use.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;When users ask questions about their database, use the available tools to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;List tables and their schemas&lt;/li&gt;
&lt;li&gt;Execute SQL queries to retrieve data&lt;/li&gt;
&lt;li&gt;Explore database structure and relationships&lt;/li&gt;
&lt;li&gt;Help users understand their data&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p&gt;This section is intentionally vague. It doesn’t attempt to prescribe a workflow. It simply orients the agent towards the types of actions users are likely to request.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;If the user asks you to create or modify the README.md, LICENSE.md, or AGENT.md, use the ‘dolt_docs’ system table.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This is an example of an “agent bug fix”. You should try to keep these to a minimum. In this case, we don’t yet have MCP tools for the &lt;code&gt;dolt_docs&lt;/code&gt; table, so the agent struggles to understand how it should work without this line. If you must include something like this in a system prompt, it should be phrased similarly (i.e. “if the user asks you to…, then do…”).&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Always be helpful and explain what you’re doing. Do not use emojis in your responses.&lt;/p&gt;
&lt;p&gt;When presenting query results, format them in a readable way. For large result sets, summarize the key findings.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The final section governs tone and presentation. These instructions are relatively safe to keep in the system prompt because they don’t attempt to enforce any sort of behavioral invariant. This helps improve the user experience. Admittedly, I’m breaking a rule I’ll discuss later on by telling the agent not to use emojis. In this case, however, there is no risk to system integrity if the model ignores that instruction. At worse, it responds with a few annoying emojis.&lt;/p&gt;
&lt;p&gt;This is overall a fairly lean prompt. It’s also not a particularly complicated one. You may be surprised to learn that it went through well over a hundred iterations before arriving at its current state. Most of those iterations were not attempts at finding the “perfect wording” or fleshing out the most accurate “agent persona” for a SQL workbench application. Instead, they were attempts at patching flaws in the agent’s behavior. We’ll discuss at length why this is a poor strategy later on.&lt;/p&gt;
&lt;p&gt;With long-running agentic systems, context engineering is vastly more important than prompt engineering. The goal when building these systems is to ensure that the agent’s context window contains the minimum amount of correct information necessary to accomplish any given task. The system prompt is just another piece of context. It’s a piece of context that, at least in my experience, has the potential to hurt you a lot more than help.&lt;/p&gt;
&lt;h2 id="offloading-context"&gt;Offloading Context&lt;a class="anchor-link" aria-label="Link to heading" href="#offloading-context"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;In my testing, I found that the more bloated the system prompt, the more likely the agent would be to outright forget things you put in there, especially for longer sessions. If at all possible, you should offload context away from the system prompt. Here’s what I mean by that.&lt;/p&gt;
&lt;p&gt;In the early versions, agent mode did not make use of the Dolt MCP server and instead simply invoked the &lt;code&gt;dolt&lt;/code&gt; CLI. As a result, the quality of the agent’s output depended largely on 1) its prior knowledge of Dolt and 2) its ability to use web search tools to fill in the gaps. This caused a lot of flakiness.&lt;/p&gt;
&lt;p&gt;For instance, the agent would struggle with operating on multiple branches, often getting confused about which branch it was making changes on versus the branch that the user was connected to in the workbench. The natural solution to a problem such as this is to include explicit instructions in the system prompt about how to juggle branches. The issue then becomes that the agent starts hard overcorrecting to the instructions in the system prompt and doing things like creating a branch for every change that it makes, or refusing to make changes directly on &lt;code&gt;main&lt;/code&gt;. Now, the issues start propagating. If the agent is making changes on multiple branches with the intention of merging all back into &lt;code&gt;main&lt;/code&gt;, you start getting merge conflicts. There’s no clear way to solve a problem like this outside of stuffing more instructions in the system prompt. I found myself with a long checklist of items like “Don’t make changes on new branches unless the user tells you to do so” and “Don’t create new branches unnecessarily” and “There should never be merge conflicts when merging branches you’ve just created”. This basically had the opposite effect of what I intended and introduced more issues. Telling the agent NOT to do something is rarely an effective strategy.&lt;/p&gt;
&lt;p&gt;I solved this by trimming the system prompt substantially and simply telling the agent to use the Dolt MCP server for Dolt-related operations. The MCP server comes with 40+ tools, all of which are well-documented, have defined arguments, and are queryable at any moment. These tools alone capture the overwhelming majority of Dolt’s functionality. Instead of relying on SQL queries for everything, the agent could now check its tool list for granular operations like &lt;code&gt;list_dolt_diff_changes_working_set&lt;/code&gt; or &lt;code&gt;stage_all_tables_for_dolt_commit&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Avoid adding things like this to the system prompt:&lt;/p&gt;
&lt;pre class="astro-code github-dark" tabindex="0" data-language="typescript"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span&gt;`You are currently on branch ${&lt;/span&gt;&lt;span&gt;branchName&lt;/span&gt;&lt;span&gt;}.`&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Instead, give the agent access to a tool like &lt;code&gt;select_active_branch&lt;/code&gt;, which allows the agent to query for the current branch at any moment. Not only does this minimize bloat in the system prompt, it also prevents you from becoming a victim of context compaction. The agent can always make a tool call to re-query for any lost context.&lt;/p&gt;
&lt;h2 id="make-good-tools"&gt;Make Good Tools&lt;a class="anchor-link" aria-label="Link to heading" href="#make-good-tools"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The architecture of the tools you give an agent access to plays an important role here as well. For a SQL workbench application, you could theoretically achieve the exact same functionality with just a single tool (i.e. a simple &lt;code&gt;query&lt;/code&gt; tool that accepts arbitrary SQL). This, however, defeats the purpose. Tools are not just functional, they also act like structured bits of context.&lt;/p&gt;
&lt;p&gt;Every tool you expose carries assumptions about how the system is meant to be used. Let’s take the &lt;code&gt;create_dolt_branch&lt;/code&gt; tool as an example. The simple fact that this tool exists tells the agent that:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Branches are first-class concepts in Dolt&lt;/li&gt;
&lt;li&gt;Branch creation is an intentional action&lt;/li&gt;
&lt;li&gt;There’s a structured way to do it&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Encoding system constraints in tools is a far more effective method of communicating expected behavior than encoding them in prose. Separating the behavior of your system into a robust set of tools allows for persistent “context refills” that keep your agent on course. This offloading of system context into tools resulted in a massive quality improvement in the workbench and ties into more recent developments we’ve been seeing in &lt;a href="https://www.dolthub.com/blog/2026-01-22-agentic-memory/"&gt;agentic memory&lt;/a&gt;. For a reference on how we split out Dolt’s functionality into tools, check out the &lt;a href="https://github.com/dolthub/dolt-mcp?tab=readme-ov-file#available-tools"&gt;Dolt MCP server documentation&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="dont-say-no"&gt;Don’t Say No&lt;a class="anchor-link" aria-label="Link to heading" href="#dont-say-no"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I alluded to this earlier, but it’s worth discussing in greater depth because I think it’s an incredibly easy trap to fall into when writing a system prompt. This was my workflow when I was iterating on the earlier prototypes of agent mode in the workbench:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Ask the agent to do something nontrivial&lt;/li&gt;
&lt;li&gt;Watch as the agent does something stupid&lt;/li&gt;
&lt;li&gt;Add “Don’t do that stupid thing” to the system prompt&lt;/li&gt;
&lt;li&gt;Go to (1)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;If you don’t want an agent to perform a particular action, the most reliable solution is to make that action impossible in the first place. Of course, this is easier said than done. Since there will always be an element of nondeterminism when working with these things, there are virtually an infinite number of edge cases, and overly rigid constraints can blunt the agent’s capabilities or block legitimate workflows. The goal here isn’t to eliminate flexibility but rather to constrain the action space such that invalid states become unreachable. Here’s an example of a problem I ran into and how I solved it at the application layer rather than by adding more rules to the system prompt.&lt;/p&gt;
&lt;p&gt;Early on, the agent would automatically decide to make Dolt commits after every write operation. This made it so the user could no longer review the agent’s changes prior to commit. I fixed this initially by adding “Don’t make commits unless the user asks you to” to the system prompt. For the most part, this worked. The agent would stop right before it would normally commit, then wait for the user to explicitly give it permission to do so. In longer sessions, it would forget and make commits anyways. It also started adding awkward things to its responses like “I won’t commit these changes because you haven’t asked me yet!” or would stop mid-response to ask for confirmation. This is clearly not ideal, but the deeper issue is that you can &lt;em&gt;never&lt;/em&gt; make the guarantee that the agent won’t commit its changes automatically. You can reduce the probability that it happens, but you can’t eliminate it. This is a big deal for agentic applications. The more critical the system your agent is operating on (your production OLTP database, for instance), the more necessary it becomes to be able to make definitive claims about agent behavior.&lt;/p&gt;
&lt;p&gt;I solved the problem by implementing a tool call approval workflow and putting the &lt;code&gt;create_dolt_commit&lt;/code&gt; tool behind it.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://static.dolthub.com/blogimages/dolt-commit-approval-gate.png/f0dcbf9bb187bc84ec07e8e65fa97e2c26de0c4108dd13922f57844621cba3cb.webp" alt="Dolt Commit Approval Gate"&gt;&lt;/p&gt;
&lt;p&gt;This made it impossible for the agent to make commits without the user pressing “Confirm” first. It does not, however, block the agent from &lt;em&gt;deciding&lt;/em&gt; to make a commit. That distinction is important. There is nothing in the agent’s system context that influences its behavior around commits. The model is still free to reason about when a commit makes sense, but it cannot unilaterally execute that decision. The final authority now lives outside the model.&lt;/p&gt;
&lt;p&gt;Avoiding negative instructions in the system prompt is something that I predict will become a “best practice” as agentic applications become more and more common, and the most reliable way to achieve that is by separating intent from execution.&lt;/p&gt;
&lt;h2 id="conclusion"&gt;Conclusion&lt;a class="anchor-link" aria-label="Link to heading" href="#conclusion"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;In summary, prompting is difficult. If you can simplify your system prompt without hindering the agent’s access to necessary information, the quality of your agent will almost certainly improve. Offloading system context into tools and building behavioral restrictions into the application layer are the two most effective ways of doing this. If you have opinions on this, or if you just want to chat about agentic applications in general, join &lt;a href="https://discord.gg/gqr7K4VNKe"&gt;our Discord&lt;/a&gt; and give us your thoughts.&lt;/p&gt;</content:encoded>
      <dc:creator>Eric Richardson</dc:creator>
      <category>workbench</category>
      <category>ai</category>
    </item>
    <item>
      <title>Your Time is All Messed Up: Time Implementations in Go</title>
      <link>https://dolthub.com/blog/2026-02-20-go-time-implementations/</link>
      <guid isPermaLink="true">https://dolthub.com/blog/2026-02-20-go-time-implementations/</guid>
      <description>We recently rewrote our implementation of `TIMESTAMPDIFF` in go-mysql-server and Dolt, and along the way, we had to make considerations for Go's implementations of time.</description>
      <pubDate>Fri, 20 Feb 2026 00:00:00 GMT</pubDate>
      <content:encoded>&lt;p&gt;Here at DoltHub, one of our projects is &lt;a href="https://github.com/dolthub/go-mysql-server/"&gt;go-mysql-server&lt;/a&gt;, a MySQL-compatible database engine that’s written in Go and powers &lt;a href="https://github.com/dolthub/dolt"&gt;Dolt&lt;/a&gt;, the world’s first version-controlled database. In go-mysql-server, we often rely on Go standard libraries, but sometimes, we have to work around them to get the same behavior as MySQL. Previously, I blogged about &lt;a href="https://www.dolthub.com/blog/2025-12-03-new-zero-time/"&gt;updating our value of zero time to be more aligned with MySQL&lt;/a&gt;. In this blog, I’ll explain why we had to move away from using Go’s &lt;code&gt;func (time.Time) Sub&lt;/code&gt; and the considerations we had to make for Go’s implementations of time in the &lt;code&gt;time.Time&lt;/code&gt; struct when implementing our own time difference function.&lt;/p&gt;
&lt;h1 id="the-bug"&gt;The Bug&lt;a class="anchor-link" aria-label="Link to heading" href="#the-bug"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;&lt;a href="https://dev.mysql.com/doc/refman/8.4/en/date-and-time-functions.html#function_timestampdiff"&gt;&lt;code&gt;TIMESTAMPDIFF&lt;/code&gt;&lt;/a&gt; is a function in MySQL that takes three arguments (a time unit and two datetime expressions) and calculates the difference in the specified unit between the two datetime expressions. I had noticed that Dolt and go-mysql-server’s &lt;a href="https://github.com/dolthub/dolt/issues/10397"&gt;&lt;code&gt;TIMESTAMPDIFF&lt;/code&gt; function was not returning the correct values for times that were sufficiently far apart&lt;/a&gt; and that it would return the same incorrect value for a given unit argument.&lt;/p&gt;
&lt;p&gt;Our implementation of &lt;code&gt;TIMESTAMPDIFF&lt;/code&gt; would convert the datetime expression arguments into two &lt;code&gt;time.Time&lt;/code&gt; structs (&lt;code&gt;time1&lt;/code&gt; and &lt;code&gt;time2&lt;/code&gt;), get the difference between the two times by calling &lt;code&gt;time2.Sub(time1)&lt;/code&gt;, and convert the difference to the correct unit. The root of the bug was the call to &lt;a href="https://pkg.go.dev/time#Time.Sub"&gt;&lt;code&gt;func (time.Time) Sub&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="the-problem-with-func-timetime-sub"&gt;The Problem with &lt;code&gt;func (time.Time) Sub&lt;/code&gt;&lt;a class="anchor-link" aria-label="Link to heading" href="#the-problem-with-func-timetime-sub"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The function signature for &lt;code&gt;func (time.Time) Sub&lt;/code&gt; looks like this:&lt;/p&gt;
&lt;pre class="astro-code github-dark" tabindex="0" data-language="go"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span&gt;func&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;t &lt;/span&gt;&lt;span&gt;Time&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;Sub&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;u&lt;/span&gt;&lt;span&gt; Time&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;Duration&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The function calculates the difference between &lt;code&gt;Time t&lt;/code&gt; and &lt;code&gt;Time u&lt;/code&gt; as a &lt;code&gt;Duration&lt;/code&gt;. A &lt;a href="https://cs.opensource.google/go/go/+/refs/tags/go1.26.0:src/time/time.go;l=915"&gt;&lt;code&gt;Duration&lt;/code&gt;&lt;/a&gt; is an int64 representing nanoseconds. As an int64, its largest value is 9,223,372,036,854,775,807 nanoseconds, or approximately 292 years. This explained why the result of &lt;code&gt;TIMESTAMPDIFF&lt;/code&gt; seemed to be stuck at the same value for a given unit.&lt;/p&gt;
&lt;p&gt;Because &lt;code&gt;TIMESTAMPDIFF&lt;/code&gt; needed to work for any time arguments between &lt;code&gt;0000-01-01 00:00:00&lt;/code&gt; and &lt;code&gt;9999-12-31 23:59:59.999999&lt;/code&gt;, we could no longer rely on &lt;code&gt;func (time.Time) Sub&lt;/code&gt; to calculate time differences.&lt;/p&gt;
&lt;h1 id="calculating-the-difference-in-microseconds"&gt;Calculating the Difference in Microseconds&lt;a class="anchor-link" aria-label="Link to heading" href="#calculating-the-difference-in-microseconds"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;Thankfully, MySQL doesn’t care about nanoseconds – the smallest time unit that MySQL handles is microseconds. The largest time difference value we needed to handle was between &lt;code&gt;0000-01-01 00:00:00&lt;/code&gt; and &lt;code&gt;9999-12-31 23:59:59.999999&lt;/code&gt;, which is 315,569,433,599,999,999 microseconds, a number small enough to fit inside an int64. So integer overflow was no longer something we needed to consider.&lt;/p&gt;
&lt;p&gt;We calculate the &lt;a href="https://github.com/dolthub/go-mysql-server/blob/65d91ad820e6e2dd93f0127acd63501248f09dc1/sql/expression/function/time_math.go#L736"&gt;difference between two times in microseconds&lt;/a&gt; by converting them to microseconds since Unix epoch using &lt;code&gt;func (Time) UnixMicro&lt;/code&gt; and then taking the difference.&lt;/p&gt;
&lt;pre class="astro-code github-dark" tabindex="0" data-language="go"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span&gt;func&lt;/span&gt;&lt;span&gt; microsecondsDiff&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;time1&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;time2&lt;/span&gt;&lt;span&gt; time&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Time&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;int64&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;	return&lt;/span&gt;&lt;span&gt; time2.&lt;/span&gt;&lt;span&gt;UnixMicro&lt;/span&gt;&lt;span&gt;() &lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; time1.&lt;/span&gt;&lt;span&gt;UnixMicro&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We’ve recently been very invested in &lt;a href="https://www.dolthub.com/blog/2025-12-12-how-dolt-got-as-fast-as-mysql/"&gt;Dolt’s performance compared to MySQL’s&lt;/a&gt;, and converting the times to microseconds since Unix epoch didn’t seem like the most performant solution. Why couldn’t we just calculate the times in microseconds directly, without the conversion? Why the extra step? Well, this comes down to Go’s implementation of &lt;code&gt;time.Time&lt;/code&gt;.&lt;/p&gt;
&lt;h1 id="a-tale-of-two-and-sometimes-three-epochs"&gt;A Tale of Two (and Sometimes Three) Epochs&lt;a class="anchor-link" aria-label="Link to heading" href="#a-tale-of-two-and-sometimes-three-epochs"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;In order to calculate the difference between two times, they need to be normalized to the same epoch. An epoch is a fixed time reference point, and times in computing are typically stored as numbers representing some unit of time elapsed since an epoch. If two times do not have the same epoch, you’re not going to get the correct difference simply by subtracting them. It’s just math (the proof is left as an exercise to the reader).&lt;/p&gt;
&lt;p&gt;In the &lt;code&gt;time.Time&lt;/code&gt; struct, Go uses &lt;a href="https://cs.opensource.google/go/go/+/refs/tags/go1.26.0:src/time/time.go;l=147"&gt;two different epochs&lt;/a&gt; depending on whether a time is &lt;a href="https://cs.opensource.google/go/go/+/refs/tags/go1.26.0:src/time/time.go;l=10"&gt;monotonic&lt;/a&gt; or not: January 1, 1885 for monotonic time and January 1, 0001 for other time. January 1, 1885 seems to be a &lt;a href="https://scifi.stackexchange.com/questions/95516/why-did-the-time-circuits-default-to-1885"&gt;reference to Back to the Future II&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Two epochs! What a mess!
&lt;img src="https://static.dolthub.com/blogimages/your_time_is_all_messed_up.png/b83dc01ff176f5adf066769315752e6bf82a55bae606f00be08e27c7b4b9ef46.webp" alt="Your time is all messed up!"&gt;&lt;/p&gt;
&lt;p&gt;Because of these two different epochs, Go doesn’t have exported public functions that expose time values directly.&lt;/p&gt;
&lt;p&gt;When calculating time differences using &lt;code&gt;func (Time) Sub&lt;/code&gt;, Go uses &lt;code&gt;func (Time) sec&lt;/code&gt; to normalize times to seconds since the January 1, 0001 epoch. &lt;code&gt;func (Time) sec&lt;/code&gt;, combined with &lt;code&gt;func (Time) Nanosecond&lt;/code&gt; to calculate microseconds, is what we want to use to be the most performant, but it’s an unexported private function that can’t be used outside of the &lt;code&gt;time&lt;/code&gt; package. It seems like Go wants to keep their underlying epochs secret. Instead, we have to rely on the limited exported public functions, and &lt;code&gt;func (Time) UnixMicro&lt;/code&gt; is our best option, despite its runtime – it first converts times using &lt;code&gt;func (Time) sec&lt;/code&gt; before converting them again to time since the Unix epoch.&lt;/p&gt;
&lt;h1 id="conclusion"&gt;Conclusion&lt;a class="anchor-link" aria-label="Link to heading" href="#conclusion"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;In the end, we were able to fix our &lt;code&gt;TIMESTAMPDIFF&lt;/code&gt; implementation to return the correct values, even if Go had to do some extra time conversions under the hood. If you’re interested in learning more about the &lt;code&gt;time&lt;/code&gt; package, you can read the &lt;a href="https://pkg.go.dev/time"&gt;documentation&lt;/a&gt; or dig into the &lt;a href="https://cs.opensource.google/go/go/+/refs/tags/go1.26.0:src/time/time.go"&gt;source code&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Found a bug in Dolt or go-mysql-server? &lt;a href="https://github.com/dolthub/dolt/issues"&gt;File an issue&lt;/a&gt;, or join our &lt;a href="https://discord.gg/EJxPeBYn"&gt;Discord server&lt;/a&gt;!&lt;/p&gt;</content:encoded>
      <dc:creator>Angela Xie</dc:creator>
      <category>golang</category>
    </item>
    <item>
      <title>Cursor for SQL</title>
      <link>https://dolthub.com/blog/2026-02-09-introducing-agent-mode/</link>
      <guid isPermaLink="true">https://dolthub.com/blog/2026-02-09-introducing-agent-mode/</guid>
      <description>Today, we're launching agent mode in the Dolt Workbench — a Cursor-like agentic chat interface that allows you to safely interact with Dolt, MySQL, and Postgres databases.</description>
      <pubDate>Mon, 09 Feb 2026 00:00:00 GMT</pubDate>
      <content:encoded>&lt;p&gt;At DoltHub, we’ve been writing extensively about why version-controlled data is a prerequisite for truly agentic applications. We believe tools like &lt;a href="https://www.dolthub.com/blog/2025-11-19-cursor-for-everything/"&gt;Cursor won’t stay confined to code for long&lt;/a&gt;. Recently, we wrote a &lt;a href="https://www.dolthub.com/blog/2025-11-12-agentic-dolt-workbench/"&gt;blog&lt;/a&gt; exploring what this might look like for a SQL workbench application. Specifically, we ran a Claude Code instance alongside the &lt;a href="https://github.com/dolthub/dolt-workbench"&gt;Dolt Workbench&lt;/a&gt;, used it to read from and write to a database, then leveraged Dolt’s built-in version control features to create an experience analogous to writing code with Cursor.&lt;/p&gt;
&lt;p&gt;Today, we’re excited to officially introduce our entry into the SQL workbench space: agent mode in the Dolt Workbench, a Cursor-like agentic chat interface that allows you to safely interact with Dolt, MySQL, and Postgres databases. In this article, we’ll show off the new feature with a simple example using MySQL and Dolt databases.&lt;/p&gt;
&lt;h2 id="agent-mode---mysql"&gt;Agent Mode - MySQL&lt;a class="anchor-link" aria-label="Link to heading" href="#agent-mode---mysql"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;For this demo, we’ll use the popular &lt;a href="https://www.dolthub.com/repositories/post-no-preference/stocks"&gt;stocks&lt;/a&gt; database on DoltHub. I’ll start by cloning the database, creating a SQL dump to convert it to MySQL, then starting up a MySQL server on my local machine:&lt;/p&gt;
&lt;pre class="astro-code github-dark" tabindex="0" data-language="bash"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span&gt;dolt&lt;/span&gt;&lt;span&gt; clone&lt;/span&gt;&lt;span&gt; post-no-preference/stocks&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;dolt&lt;/span&gt;&lt;span&gt; dump&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;mysql&lt;/span&gt;&lt;span&gt; -h&lt;/span&gt;&lt;span&gt; 127.0.0.1&lt;/span&gt;&lt;span&gt; -P&lt;/span&gt;&lt;span&gt; 3300&lt;/span&gt;&lt;span&gt; -u&lt;/span&gt;&lt;span&gt; root&lt;/span&gt;&lt;span&gt; &amp;#x3C;&lt;/span&gt;&lt;span&gt; doltdump.sql&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now, I’ll connect to the database from the workbench:&lt;/p&gt;
&lt;p&gt;&lt;img src="https://static.dolthub.com/blogimages/agent-workbench-home.png/07a41cea90c94551a122fb08d15de840f01399d97690ff95f461b77bb7858622.webp" alt="Agent Workbench Home"&gt;&lt;/p&gt;
&lt;p&gt;In the top right corner, you’ll notice an orange robot icon. This button pulls up the agent chat panel. If it’s your first time using agent mode, you’ll be prompted to put in your Anthropic API key:&lt;/p&gt;
&lt;p&gt;&lt;img src="https://static.dolthub.com/blogimages/anthropic-key.png/f9d5f764f16288fca5200cbf5bfff16bc389f24f269f8a3791b90621041c1386.webp" alt="Anthropic API Key"&gt;&lt;/p&gt;
&lt;p&gt;After doing this, the main chat window will open on the right. The chat interface is accessible from anywhere in the workbench:&lt;/p&gt;
&lt;p&gt;&lt;img src="https://static.dolthub.com/blogimages/empty-agent-panel.png/f3d121ddd2fc9917b717133c0407e949c93e128a47ac7067d609247a544dc728.webp" alt="Empty Agent Panel"&gt;&lt;/p&gt;
&lt;p&gt;Let’s start with something simple. I’ll ask the agent to tell me about the database I’m connected to.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://static.dolthub.com/blogimages/tell-me-mysql.png/cf0a46940088c3e03aa162dd226c8d61a42ad19898e0925048f0c8a6b44dc5d5.webp" alt="Tell Me About My Database - MySQL"&gt;&lt;/p&gt;
&lt;p&gt;After deciding that it needs to gather information about the database, you’ll notice several “Bash” blocks in the agent’s response. These blocks represent tool calls. Each of these are expandable, allowing you to see exactly what actions were taken by the agent. For example:&lt;/p&gt;
&lt;p&gt;&lt;img src="https://static.dolthub.com/blogimages/mysql-tool-call.png/0abf694f756c8ca786ef5d3926edaa489ad8c8fd70c6ad54cd3a3e05b3fc8287.webp" alt="MySQL Tool Call"&gt;&lt;/p&gt;
&lt;p&gt;In this case, the agent is using the &lt;code&gt;mysql&lt;/code&gt; CLI inside a Bash shell to query the database. After making several queries, the agent presents a summary of its findings:&lt;/p&gt;
&lt;p&gt;&lt;img src="https://static.dolthub.com/blogimages/tell-me-mysql-result.png/4917754d91d49c37f12385672e593ceefd9cee38d5833f64965b91f279443ae3.webp" alt="Tell Me About My Database - MySQL Result"&gt;&lt;/p&gt;
&lt;p&gt;This is already pretty useful, but it’s not really agent mode if it can’t make writes. Let’s ask it to do something more interesting.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://static.dolthub.com/blogimages/dolt-ipo-mysql.png/2cc7e1037728d5ec2e4294e91e0b6f3b3934b70fbfabc66baaa32fb285777722.webp" alt="Dolt IPO - MySQL"&gt;&lt;/p&gt;
&lt;p&gt;The agent begins confidently inserting data into the database. Again, to see the actual queries it runs, you can expand the tool call blocks. Once finished, it gives a summary of its actions:&lt;/p&gt;
&lt;p&gt;&lt;img src="https://static.dolthub.com/blogimages/dolt-ipo-mysql-result.png/2b9ee5924ea2be38f4bde7b9027457e67800aef394b47e3759ce5f63ad03b368.webp" alt="Dolt IPO - MySQL Result"&gt;&lt;/p&gt;
&lt;p&gt;Great, the agent did what I asked. However, there’s a somewhat large caveat here: there’s no mechanism in place to see or verify the agent’s changes. I asked it to make some changes, it executed a bunch of insert statements, then told me everything went swimmingly. The state of the workbench is unchanged.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://static.dolthub.com/blogimages/unchanged-workbench.png/bfd9eb633df47bc85c60a91be3359f47342bf310a433219bd6e6ce7e5cc4b9b9.webp" alt="Unchanged Workbench"&gt;&lt;/p&gt;
&lt;p&gt;If I want to examine the changes, the best I can do is run some SQL queries that explicitly query for the new data. Something like this, for instance:&lt;/p&gt;
&lt;p&gt;&lt;img src="https://static.dolthub.com/blogimages/mysql-select.png/421b24ff03fe4722efd77ea8607844c4b819dad75be9b20e11dbcd09846ba497.webp" alt="MySQL Select"&gt;&lt;/p&gt;
&lt;p&gt;This is not just a lackluster user experience for an application that uses AI to make writes to your database, it’s also dangerous. There was no way for me to verify the changes made by the agent &lt;em&gt;before&lt;/em&gt; they happened. We can always hope that the agent won’t go off the rails and nuke your entire database or insert a bunch of faulty data, but that’s the best we can do: &lt;em&gt;hope&lt;/em&gt;. This is the reason applications like this don’t exist yet. Version control solves this problem. To show what I mean, let’s run this same experiment using a Dolt database instead of MySQL.&lt;/p&gt;
&lt;h2 id="agent-mode---dolt"&gt;Agent Mode - Dolt&lt;a class="anchor-link" aria-label="Link to heading" href="#agent-mode---dolt"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;We’ll give the agent the exact same instructions as previously. Let’s start simple:&lt;/p&gt;
&lt;p&gt;&lt;img src="https://static.dolthub.com/blogimages/tell-me-dolt.png/3e150b7499e796de9abb11ce14c11b646d138216d4caa2bfb3019ab69061373e.webp" alt="Tell Me About My Database - Dolt"&gt;&lt;/p&gt;
&lt;p&gt;You’ll notice that the tool call blocks are a fair bit more informative here. This is thanks to the &lt;a href="https://github.com/dolthub/dolt-mcp"&gt;Dolt MCP Server&lt;/a&gt;, which the agent uses for all Dolt-related operations.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://static.dolthub.com/blogimages/dolt-tool-call.png/9898cfdadfef90c7eeb69d6931e8b6e7d4df3f211ef7a5ffaf1cbf5400593207.webp" alt="Dolt Tool Call"&gt;&lt;/p&gt;
&lt;p&gt;As we’ll see later, this separation of tool calls enables some important features related to permissions and approvals. After making several queries, the agent presents another helpful summary of the data and schema. Now, let’s have it insert some data:&lt;/p&gt;
&lt;p&gt;&lt;img src="https://static.dolthub.com/blogimages/dolt-ipo-dolt-prompt.png/12bb03500ccc478ceef14d87257505d561d29169e57c687b94e8b86d1517404e.webp" alt="Dolt IPO - Prompt"&gt;&lt;/p&gt;
&lt;p&gt;As the agent makes changes, you’ll immediately notice visual changes in the workbench. The table names in the left panel are highlighted in yellow to indicate that their data has been modified. Additionally, above the main table view, you’ll see the option to “Show Changed Rows Only” as well as a button for “Uncommitted changes”:&lt;/p&gt;
&lt;p&gt;&lt;img src="https://static.dolthub.com/blogimages/dolt-ipo-dolt-main-view.png/31a904539442aa1ed5834758d117f5600ec056f47ff012f25dedab7c8363fd01.webp" alt="Dolt IPO - Main View"&gt;&lt;/p&gt;
&lt;p&gt;Let’s check the box to show changed rows only.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://static.dolthub.com/blogimages/dolt-ipo-dolt-unchanged.png/076a428e09a55c6804880dbd011309c87126164ae09a7f043b8ee318e329ac85.webp" alt="Dolt IPO - Show Unchanged"&gt;&lt;/p&gt;
&lt;p&gt;This filters the table by rows in your working set. For a more complete breakdown of all changes made, you can click the “Uncommitted changes” button.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://static.dolthub.com/blogimages/dolt-ipo-dolt-uncommitted.png/3ccb9cdabd83d4cde279abd04f18f8732f68fef0bfee7029bd8c3a4d0933962c.webp" alt="Dolt IPO - Uncommitted"&gt;&lt;/p&gt;
&lt;p&gt;Let’s look at the agent’s response:&lt;/p&gt;
&lt;p&gt;&lt;img src="https://static.dolthub.com/blogimages/dolt-ipo-dolt-commit.png/f0dcbf9bb187bc84ec07e8e65fa97e2c26de0c4108dd13922f57844621cba3cb.webp" alt="Dolt IPO - Commit"&gt;&lt;/p&gt;
&lt;p&gt;Since we can now preview changes before they’re actually committed, the agent will hold off on making a Dolt commit until the user grants explicit confirmation. This makes it impossible for the agent to break your database. Let’s hit confirm and check the commit log to make sure everything succeeded.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://static.dolthub.com/blogimages/dolt-ipo-dolt-commit-log.png/d1bbbe1d929d99f36a59e97e3256c03ad225af4c2cbf4e1e4d4821acc01d7664.webp" alt="Dolt IPO - Commit Log"&gt;&lt;/p&gt;
&lt;p&gt;Perfect, we were able to use the agent to make a write to the database, view the diff, then allow the agent to commit its changes. Even if we accidentally committed some unwanted changes, Dolt allows you to immediately reset to any prior commit. Just for fun, let’s have the agent do this:&lt;/p&gt;
&lt;p&gt;&lt;img src="https://static.dolthub.com/blogimages/dolt-ipo-dolt-reset.png/32c1f5ec53c43e7c01d0e9a4a1fc7e13a9e990e5a8a47a49e777d7c8949e9b28.webp" alt="Dolt IPO - Reset"&gt;&lt;/p&gt;
&lt;p&gt;After confirming, my database is back to its original state.&lt;/p&gt;
&lt;h2 id="conclusion"&gt;Conclusion&lt;a class="anchor-link" aria-label="Link to heading" href="#conclusion"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;We’re super excited about applications that use agentic AI the way we’re using it here, and we think version control is necessary to make that happen. We encourage you to try out agent mode in the Dolt Workbench, which you can download directly &lt;a href="https://github.com/dolthub/dolt-workbench/releases/tag/v0.3.62"&gt;here&lt;/a&gt; or from the &lt;a href="https://apps.apple.com/us/app/dolt-workbench/id6720702995?mt=12"&gt;Mac&lt;/a&gt; and &lt;a href="https://apps.microsoft.com/detail/9nq8lqph9vvh?hl=en-us&amp;#x26;gl=US"&gt;Windows&lt;/a&gt; app stores. If you have any ideas for improvements, feature requests, or bug reports, come by &lt;a href="https://discord.gg/gqr7K4VNKe"&gt;our Discord&lt;/a&gt; and let us know.&lt;/p&gt;</content:encoded>
      <dc:creator>Eric Richardson</dc:creator>
      <category>ai</category>
      <category>feature release</category>
      <category>workbench</category>
    </item>
    <item>
      <title>Connect Agents to your Hosted Dolt Instance using MCP</title>
      <link>https://dolthub.com/blog/2026-02-03-hosted-dolt-mcp/</link>
      <guid isPermaLink="true">https://dolthub.com/blog/2026-02-03-hosted-dolt-mcp/</guid>
      <description>You can now enable dolt mcp on your Hosted Dolt instances to connect them to your agentic workloads.</description>
      <pubDate>Tue, 03 Feb 2026 00:00:00 GMT</pubDate>
      <content:encoded>&lt;p&gt;It has been our thesis that &lt;a href="https://www.doltdb.com"&gt;Dolt&lt;/a&gt; is the ideal database for agentic workloads.  Agents need a
database that is version controlled. The ability to branch, and diff allow agentic workflows to work in isolation, and
be audited before merging changes back to mainline.  Dolt’s git-like collaboration model also allows multiple agents to work
concurrently on the same database without stepping on each other’s toes. In August of 2025 we launched &lt;a href="https://github.com/dolthub/dolt-mcp"&gt;Dolt MCP&lt;/a&gt;,
which is a server that implements Anthropic’s MCP protocol to allow agents to connect to Dolt databases.
Today we are excited to announce that you can now enable Dolt MCP on your &lt;a href="https://hosted.doltdb.com"&gt;Hosted Dolt&lt;/a&gt; instances to connect them
to your agentic workloads.&lt;/p&gt;
&lt;h2 id="enabling-dolt-mcp-on-hosted-dolt"&gt;Enabling Dolt MCP on Hosted Dolt&lt;a class="anchor-link" aria-label="Link to heading" href="#enabling-dolt-mcp-on-hosted-dolt"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Enabling &lt;a href="https://github.com/dolthub/dolt-mcp"&gt;Dolt MCP&lt;/a&gt; on your &lt;a href="https://hosted.doltdb.com"&gt;Hosted Dolt&lt;/a&gt; instance is easy.&lt;br&gt;
On the settings tab for your Hosted Dolt deployment, you will find a new section called “Enable Dolt MCP server” In this section, you can
enable Dolt MCP for your instance, by clicking the “Expose MCP Server” check box. Once enabled &lt;a href="https://github.com/dolthub/dolt-mcp"&gt;Dolt MCP&lt;/a&gt;
will be installed on your instance and started in a matter of minutes.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://static.dolthub.com/blogimages/mcp_disabled.png/62d60271a53ba46f3d032fe633b5ee404b55ef916d316a8d1c49ef044c806f43.webp" alt="MCP Disabled"&gt;&lt;/p&gt;
&lt;p&gt;After enabling Dolt MCP, the section will update to show the details necessary to connect your MCP compatible agent to your Hosted Dolt instance.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://static.dolthub.com/blogimages/mcp_enabled.png/5208753e731484322e59664bfac6d79e71ef99ec9d6c045583d458da6759e869.webp" alt="MCP Enabled"&gt;&lt;/p&gt;
&lt;h2 id="connecting-to-hosted-dolt-mcp"&gt;Connecting to Hosted Dolt MCP&lt;a class="anchor-link" aria-label="Link to heading" href="#connecting-to-hosted-dolt-mcp"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Dolt MCP server runs on port 8675, and uses steaming HTTP as the transport mechanism.  The server requires clients to
authenticate using a token.  The token is generally passed as a bearer token by client’s that support that authentication mechanism.
Dolt MCP also supports passing the token as a query parameter (jwt), for clients that do not support bearer token authentication.&lt;/p&gt;
&lt;h2 id="dolt-mcp-database-user"&gt;dolt-mcp Database User&lt;a class="anchor-link" aria-label="Link to heading" href="#dolt-mcp-database-user"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Dolt MCP server connects to your &lt;a href="https://hosted.doltdb.com"&gt;Hosted Dolt&lt;/a&gt; database using a special database user named &lt;code&gt;mcp_user&lt;/code&gt;. By default
this user does not have any privileges on your database. You will need to grant the necessary privileges to this user in order for Dolt MCP to
function correctly. The privileges you grant will depend on the operations you want your agent to be able to perform.
At a minimum, you will want to grant the &lt;code&gt;mcp_user&lt;/code&gt; read privileges on one or more database tables. Here is an example of
granting read privileges on a table called &lt;code&gt;tasks&lt;/code&gt; within the database &lt;code&gt;my_database&lt;/code&gt;:&lt;/p&gt;
&lt;pre class="astro-code github-dark" tabindex="0" data-language="sql"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span&gt;GRANT&lt;/span&gt;&lt;span&gt; SELECT&lt;/span&gt;&lt;span&gt; ON&lt;/span&gt;&lt;span&gt; my_database&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;tasks&lt;/span&gt;&lt;span&gt; TO&lt;/span&gt;&lt;span&gt; 'mcp_user'&lt;/span&gt;&lt;span&gt;@&lt;/span&gt;&lt;span&gt;'%'&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id="configuring-your-mcp-compatible-agent"&gt;Configuring your MCP Compatible Agent&lt;a class="anchor-link" aria-label="Link to heading" href="#configuring-your-mcp-compatible-agent"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The steps necessary to configure your MCP compatible agent are dependent on the agent you are using. Please refer to the
documentation for your agent to learn how to configure it to connect to an MCP server running over https.&lt;/p&gt;
&lt;h2 id="an-example-using-claude"&gt;An example using Claude&lt;a class="anchor-link" aria-label="Link to heading" href="#an-example-using-claude"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;After creating a &lt;a href="https://hosted.doltdb.com"&gt;Hosted Dolt&lt;/a&gt; deployment, I enabled the Dolt MCP server by clicking the checkbox
on the settings tab.  Next, I go back to the main page of the deployment and copy the mysql command line to connect to my database
server and run:&lt;/p&gt;
&lt;pre class="astro-code github-dark" tabindex="0" data-language="bash"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;span&gt;mysql -h&lt;/span&gt;&lt;span&gt;"mcp-test.dbs.hosted.doltdb.com"&lt;/span&gt;&lt;span&gt; -u&lt;/span&gt;&lt;span&gt;"lop887ig4wkov575"&lt;/span&gt;&lt;span&gt; -p&lt;/span&gt;&lt;span&gt;"LxuVijXyXiC2ZEi5qvvRiBXSi32MbWIi"&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;mysql:&lt;/span&gt;&lt;span&gt; [Warning] Using a password on the command line interface can be insecure.&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;Welcome&lt;/span&gt;&lt;span&gt; to&lt;/span&gt;&lt;span&gt; the&lt;/span&gt;&lt;span&gt; MySQL&lt;/span&gt;&lt;span&gt; monitor.&lt;/span&gt;&lt;span&gt;  Commands&lt;/span&gt;&lt;span&gt; end&lt;/span&gt;&lt;span&gt; with&lt;/span&gt;&lt;span&gt; ; &lt;/span&gt;&lt;span&gt;or&lt;/span&gt;&lt;span&gt; \g&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;Your&lt;/span&gt;&lt;span&gt; MySQL&lt;/span&gt;&lt;span&gt; connection&lt;/span&gt;&lt;span&gt; id&lt;/span&gt;&lt;span&gt; is&lt;/span&gt;&lt;span&gt; 2&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;Server&lt;/span&gt;&lt;span&gt; version:&lt;/span&gt;&lt;span&gt; 8.0.33&lt;/span&gt;&lt;span&gt; Dolt&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;Copyright&lt;/span&gt;&lt;span&gt; (c) 2000, 2025, Oracle and/or its affiliates.&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;Oracle&lt;/span&gt;&lt;span&gt; is&lt;/span&gt;&lt;span&gt; a&lt;/span&gt;&lt;span&gt; registered&lt;/span&gt;&lt;span&gt; trademark&lt;/span&gt;&lt;span&gt; of&lt;/span&gt;&lt;span&gt; Oracle&lt;/span&gt;&lt;span&gt; Corporation&lt;/span&gt;&lt;span&gt; and/or&lt;/span&gt;&lt;span&gt; its&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;affiliates.&lt;/span&gt;&lt;span&gt; Other&lt;/span&gt;&lt;span&gt; names&lt;/span&gt;&lt;span&gt; may&lt;/span&gt;&lt;span&gt; be&lt;/span&gt;&lt;span&gt; trademarks&lt;/span&gt;&lt;span&gt; of&lt;/span&gt;&lt;span&gt; their&lt;/span&gt;&lt;span&gt; respective&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;owners.&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;Type&lt;/span&gt;&lt;span&gt; 'help;'&lt;/span&gt;&lt;span&gt; or&lt;/span&gt;&lt;span&gt; '\h'&lt;/span&gt;&lt;span&gt; for&lt;/span&gt;&lt;span&gt; help.&lt;/span&gt;&lt;span&gt; Type&lt;/span&gt;&lt;span&gt; '\c'&lt;/span&gt;&lt;span&gt; to&lt;/span&gt;&lt;span&gt; clear&lt;/span&gt;&lt;span&gt; the&lt;/span&gt;&lt;span&gt; current&lt;/span&gt;&lt;span&gt; input&lt;/span&gt;&lt;span&gt; statement.&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;mysql&lt;/span&gt;&lt;span&gt;&gt; &lt;/span&gt;&lt;span&gt;GRANT&lt;/span&gt;&lt;span&gt; SHOW&lt;/span&gt;&lt;span&gt; DATABASES&lt;/span&gt;&lt;span&gt; ON&lt;/span&gt;&lt;span&gt; *&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt; TO&lt;/span&gt;&lt;span&gt; 'mcp_user'@'%'&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;Query&lt;/span&gt;&lt;span&gt; OK,&lt;/span&gt;&lt;span&gt; 0&lt;/span&gt;&lt;span&gt; rows&lt;/span&gt;&lt;span&gt; affected&lt;/span&gt;&lt;span&gt; (0.032 &lt;/span&gt;&lt;span&gt;sec&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;mysql&lt;/span&gt;&lt;span&gt;&gt; &lt;/span&gt;&lt;span&gt;CREATE&lt;/span&gt;&lt;span&gt; DATABASE&lt;/span&gt;&lt;span&gt; test&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;Query&lt;/span&gt;&lt;span&gt; OK,&lt;/span&gt;&lt;span&gt; 1&lt;/span&gt;&lt;span&gt; row&lt;/span&gt;&lt;span&gt; affected&lt;/span&gt;&lt;span&gt; (0.107 &lt;/span&gt;&lt;span&gt;sec&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now that we have enabled the Dolt MCP server and granted the necessary privileges to the &lt;code&gt;mcp_user&lt;/code&gt; to show databases we
will configure Claude to be able to use our MCP server. The claude command line allows us to add a new MCP server like so:&lt;/p&gt;
&lt;pre class="astro-code github-dark" tabindex="0" data-language="bash"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;span&gt;claude mcp add --transport http dolt-mcp https://mcp-test.dbs.hosted.doltdb.com:8675/mcp &lt;/span&gt;&lt;span&gt;\&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt; --header&lt;/span&gt;&lt;span&gt; "Authorization: Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6IjRjZTI1Y2FjLTFkNjEtNGI3My1hMTAwLTRjMDU3MTJhZTIxYSIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsiZGVwbG95bWVudE93bmVycy9kb2x0aHViL2RlcGxveW1lbnRzL21jcC10ZXN0Il0sImV4cCI6MTgwMTY4NzcwMCwiaWF0IjoxNzcwMTUxNzAwLCJpc3MiOiJob3N0ZWQuZG9sdGRiLmNvbSIsImp0aSI6ImExMmE5NWQ1LTYxMTctNGNhMy04MmYzLWViN2E4YTUyYmYzOCIsIm9uX2JlaGFsZl9vZiI6ImJoZW5pIiwic3ViIjoiZG9sdC1tY3AifQ.ULqrWUSunAvk-tsgmr1gP4fJG2Hxj9v9ygiZ7lgXAkZ_HxxXxdAkklsYhW6ie4Y2H9nvZs3Ux1tBWajsZHHeeIzUqReNdNve--Ghm3vCNJqn0m67WFdqbeBhDaUuohCkJvBYi7pXspS087VF8s1eKBQnBBJCo-fe1TGycC_00_pilTWww2o9HJjkLLXl73AOR2zTCm3_80Qq_TasrtHtH7c60zLYBQ4Pr1VZSjb8bdjb_euacwgPR_yJlUhZ6lU7kySg1c2Odk-LO1yqiUmGUKTFMxU1gM2zzDyhk-viuF3a8JMxXt5FDVfAzqTqg5Am6tHs8OugVhinWXWpB_nkwA"&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;Added&lt;/span&gt;&lt;span&gt; HTTP&lt;/span&gt;&lt;span&gt; MCP&lt;/span&gt;&lt;span&gt; server&lt;/span&gt;&lt;span&gt; dolt-mcp&lt;/span&gt;&lt;span&gt; with&lt;/span&gt;&lt;span&gt; URL:&lt;/span&gt;&lt;span&gt; https://mcp-test.dbs.hosted.doltdb.com:8675/mcp&lt;/span&gt;&lt;span&gt; to&lt;/span&gt;&lt;span&gt; local&lt;/span&gt;&lt;span&gt; config&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;Headers:&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;  "Authorization"&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; "Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6IjRjZTI1Y2FjLTFkNjEtNGI3My1hMTAwLTRjMDU3MTJhZTIxYSIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsiZGVwbG95bWVudE93bmVycy9kb2x0aHViL2RlcGxveW1lbnRzL21jcC10ZXN0Il0sImV4cCI6MTgwMTY4NzcwMCwiaWF0IjoxNzcwMTUxNzAwLCJpc3MiOiJob3N0ZWQuZG9sdGRiLmNvbSIsImp0aSI6ImExMmE5NWQ1LTYxMTctNGNhMy04MmYzLWViN2E4YTUyYmYzOCIsIm9uX2JlaGFsZl9vZiI6ImJoZW5pIiwic3ViIjoiZG9sdC1tY3AifQ.ULqrWUSunAvk-tsgmr1gP4fJG2Hxj9v9ygiZ7lgXAkZ_HxxXxdAkklsYhW6ie4Y2H9nvZs3Ux1tBWajsZHHeeIzUqReNdNve--Ghm3vCNJqn0m67WFdqbeBhDaUuohCkJvBYi7pXspS087VF8s1eKBQnBBJCo-fe1TGycC_00_pilTWww2o9HJjkLLXl73AOR2zTCm3_80Qq_TasrtHtH7c60zLYBQ4Pr1VZSjb8bdjb_euacwgPR_yJlUhZ6lU7kySg1c2Odk-LO1yqiUmGUKTFMxU1gM2zzDyhk-viuF3a8JMxXt5FDVfAzqTqg5Am6tHs8OugVhinWXWpB_nkwA"&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;File&lt;/span&gt;&lt;span&gt; modified:&lt;/span&gt;&lt;span&gt; /Users/brian/.claude.json&lt;/span&gt;&lt;span&gt; [project: &lt;/span&gt;&lt;span&gt;/Users/brian/dev/claude-test]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We can verify its configuration using &lt;code&gt;claude mcp get&lt;/code&gt;:&lt;/p&gt;
&lt;pre class="astro-code github-dark" tabindex="0" data-language="bash"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;span&gt;claude mcp get dolt-mcp&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;dolt-mcp:&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;  Scope:&lt;/span&gt;&lt;span&gt; Local&lt;/span&gt;&lt;span&gt; config&lt;/span&gt;&lt;span&gt; (private &lt;/span&gt;&lt;span&gt;to&lt;/span&gt;&lt;span&gt; you&lt;/span&gt;&lt;span&gt; in&lt;/span&gt;&lt;span&gt; this&lt;/span&gt;&lt;span&gt; project&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;  Status:&lt;/span&gt;&lt;span&gt; ✓&lt;/span&gt;&lt;span&gt; Connected&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;  Type:&lt;/span&gt;&lt;span&gt; http&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;  URL:&lt;/span&gt;&lt;span&gt; https://mcp-test.dbs.hosted.doltdb.com:8675/mcp&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;  Headers:&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;    Authorization:&lt;/span&gt;&lt;span&gt; Bearer&lt;/span&gt;&lt;span&gt; eyJhbGciOiJSUzI1NiIsImtpZCI6IjRjZTI1Y2FjLTFkNjEtNGI3My1hMTAwLTRjMDU3MTJhZTIxYSIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsiZGVwbG95bWVudE93bmVycy9kb2x0aHViL2RlcGxveW1lbnRzL21jcC10ZXN0Il0sImV4cCI6MTgwMTY4NzcwMCwiaWF0IjoxNzcwMTUxNzAwLCJpc3MiOiJob3N0ZWQuZG9sdGRiLmNvbSIsImp0aSI6ImExMmE5NWQ1LTYxMTctNGNhMy04MmYzLWViN2E4YTUyYmYzOCIsIm9uX2JlaGFsZl9vZiI6ImJoZW5pIiwic3ViIjoiZG9sdC1tY3AifQ.ULqrWUSunAvk-tsgmr1gP4fJG2Hxj9v9ygiZ7lgXAkZ_HxxXxdAkklsYhW6ie4Y2H9nvZs3Ux1tBWajsZHHeeIzUqReNdNve--Ghm3vCNJqn0m67WFdqbeBhDaUuohCkJvBYi7pXspS087VF8s1eKBQnBBJCo-fe1TGycC_00_pilTWww2o9HJjkLLXl73AOR2zTCm3_80Qq_TasrtHtH7c60zLYBQ4Pr1VZSjb8bdjb_euacwgPR_yJlUhZ6lU7kySg1c2Odk-LO1yqiUmGUKTFMxU1gM2zzDyhk-viuF3a8JMxXt5FDVfAzqTqg5Am6tHs8OugVhinWXWpB_nkwA&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now that we have configured Claude to connect to our Hosted Dolt deployment’s MCP server, we can start using it to interact with our database.&lt;/p&gt;
&lt;pre class="astro-code github-dark" tabindex="0" data-language="bash"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;span&gt;claude&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;╭───&lt;/span&gt;&lt;span&gt; Claude&lt;/span&gt;&lt;span&gt; Code&lt;/span&gt;&lt;span&gt; v2.1.30&lt;/span&gt;&lt;span&gt; ──────────────────────────────────────────────────────╮&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;│&lt;/span&gt;&lt;span&gt;                                                  │&lt;/span&gt;&lt;span&gt; Tips&lt;/span&gt;&lt;span&gt; for&lt;/span&gt;&lt;span&gt; getting&lt;/span&gt;&lt;span&gt; started&lt;/span&gt;&lt;span&gt;  │&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;│&lt;/span&gt;&lt;span&gt;                   Welcome&lt;/span&gt;&lt;span&gt; back!&lt;/span&gt;&lt;span&gt;                  │&lt;/span&gt;&lt;span&gt; Ask&lt;/span&gt;&lt;span&gt; Claude&lt;/span&gt;&lt;span&gt; to&lt;/span&gt;&lt;span&gt; create&lt;/span&gt;&lt;span&gt; a&lt;/span&gt;&lt;span&gt; n…&lt;/span&gt;&lt;span&gt; │&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;│&lt;/span&gt;&lt;span&gt;                                                  │&lt;/span&gt;&lt;span&gt; ─────────────────────────&lt;/span&gt;&lt;span&gt; │&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;│&lt;/span&gt;&lt;span&gt;                                                  │&lt;/span&gt;&lt;span&gt; Recent&lt;/span&gt;&lt;span&gt; activity&lt;/span&gt;&lt;span&gt;           │&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;│&lt;/span&gt;&lt;span&gt;                      ▐▛███▜▌&lt;/span&gt;&lt;span&gt;                     │&lt;/span&gt;&lt;span&gt; No&lt;/span&gt;&lt;span&gt; recent&lt;/span&gt;&lt;span&gt; activity&lt;/span&gt;&lt;span&gt;        │&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;│&lt;/span&gt;&lt;span&gt;                     ▝▜█████▛▘&lt;/span&gt;&lt;span&gt;                    │&lt;/span&gt;&lt;span&gt;                           │&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;│&lt;/span&gt;&lt;span&gt;                       ▘▘&lt;/span&gt;&lt;span&gt; ▝▝&lt;/span&gt;&lt;span&gt;                      │&lt;/span&gt;&lt;span&gt;                           │&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;│&lt;/span&gt;&lt;span&gt;   Sonnet&lt;/span&gt;&lt;span&gt; 4.5&lt;/span&gt;&lt;span&gt; ·&lt;/span&gt;&lt;span&gt; API&lt;/span&gt;&lt;span&gt; Usage&lt;/span&gt;&lt;span&gt; Billing&lt;/span&gt;&lt;span&gt; ·&lt;/span&gt;&lt;span&gt; DoltHub&lt;/span&gt;&lt;span&gt; Inc&lt;/span&gt;&lt;span&gt;   │&lt;/span&gt;&lt;span&gt;                           │&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;│&lt;/span&gt;&lt;span&gt;                ~/dev/claude-test&lt;/span&gt;&lt;span&gt;                 │&lt;/span&gt;&lt;span&gt;                           │&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;╰──────────────────────────────────────────────────────────────────────────────╯&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;❯&lt;/span&gt;&lt;span&gt; can&lt;/span&gt;&lt;span&gt; you&lt;/span&gt;&lt;span&gt; list&lt;/span&gt;&lt;span&gt; databases?&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;⏺&lt;/span&gt;&lt;span&gt; I'll list the databases available in the Dolt server.&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;────────────────────────────────────────────────────────────────────────────────&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt; Tool use&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;   dolt-mcp - list_databases() (MCP)&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;   Lists all databases in the Dolt server.&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt; Do you want to proceed?&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt; ❯ 1. Yes&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;   2. Yes, and don't&lt;/span&gt;&lt;span&gt; ask&lt;/span&gt;&lt;span&gt; again&lt;/span&gt;&lt;span&gt; for&lt;/span&gt;&lt;span&gt; dolt-mcp&lt;/span&gt;&lt;span&gt; -&lt;/span&gt;&lt;span&gt; list_databases&lt;/span&gt;&lt;span&gt; commands&lt;/span&gt;&lt;span&gt; in&lt;/span&gt;&lt;span&gt; /Users/brian/dev/claude-test&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;   3.&lt;/span&gt;&lt;span&gt; No&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;⏺&lt;/span&gt;&lt;span&gt; I'll list the databases available in the Dolt server.&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;  ⎿  Database&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;     ---&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;     test&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;     information_schema&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;     mysql&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;⏺ The Dolt server has the following databases:&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;  - test - A user-created database&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;  - information_schema - System database containing metadata about the database server&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;  - mysql - System database for MySQL compatibility&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;  These are system databases. There are currently no user-created databases in&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;  the Dolt server. Would you like to create a new database?&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This small example demonstrates just a single interaction with the Dolt MCP server with just one of many agentic platforms that support
MCP. The &lt;a href="https://github.com/dolthub/dolt-mcp"&gt;Dolt MCP&lt;/a&gt; server provides the full range of Dolt functionality (Just make sure your
mcp_user has the necessary privileges).&lt;/p&gt;
&lt;h1 id="conclusion"&gt;Conclusion&lt;a class="anchor-link" aria-label="Link to heading" href="#conclusion"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;We are excited to bring Dolt MCP to Hosted Dolt users.  We believe that Dolt is the ideal database for agentic workloads, and
we are committed to making it easy for developers to use Dolt in their agentic applications.  We look forward to seeing the innovative applications
that developers will build using Hosted Dolt and Dolt MCP. We’d love to hear your feedback. Please reach out to us on our &lt;a href="https://discord.gg/gqr7K4VNKe"&gt;Discord&lt;/a&gt;.&lt;/p&gt;</content:encoded>
      <dc:creator>Brian Hendriks</dc:creator>
      <category>hosted</category>
      <category>feature release</category>
    </item>
    <item>
      <title>Writing a Go SQL driver</title>
      <link>https://dolthub.com/blog/2026-01-23-golang-sql-drivers/</link>
      <guid isPermaLink="true">https://dolthub.com/blog/2026-01-23-golang-sql-drivers/</guid>
      <description>Take a tour of Go's SQL driver implementation and see how it works in Dolt's embedded SQL driver.</description>
      <pubDate>Fri, 23 Jan 2026 00:00:00 GMT</pubDate>
      <content:encoded>&lt;p&gt;We’re building &lt;a href="https://doltdb.com"&gt;Dolt&lt;/a&gt;, the world’s first version-controlled SQL database. Most
of our customers run Dolt as a server in Docker and connect to it over the network like any other
database server. But for programs written in Go, you can also connect to a Dolt database without a
separate server process, similar to SQLite. We call this the embedded use case, and it has suddenly
become a lot more relevant with &lt;a href="https://steve-yegge.medium.com/welcome-to-gas-town-4f25ee16dd04"&gt;Gas
Town&lt;/a&gt; migrating its agentic memory
storage to &lt;a href="https://www.dolthub.com/blog/2026-01-22-agentic-memory/#dolt-for-agentic-memory"&gt;use Dolt as its
backend&lt;/a&gt;. Since Gas
Town is a Go process, it can use the embedded Dolt driver to communicate with an embedded Dolt
database.&lt;/p&gt;
&lt;p&gt;So, because Dolt’s &lt;a href="https://github.com/dolthub/driver"&gt;embedded driver&lt;/a&gt; is about to get a lot more
action than it’s used to, we thought this would be a good time to give a tour of how it works. This
pattern is possible through the magic of Go’s &lt;code&gt;database/sql/driver&lt;/code&gt; package, which lets you define a
database connection that any Go program can use to talk to your SQL backend with a single &lt;code&gt;import&lt;/code&gt;
statement.&lt;/p&gt;
&lt;p&gt;In this tour, we’ll look at how Go’s SQL drivers work under the hood and show you how Dolt
implements one to provide access to an embedded Dolt database. Let’s take a look.&lt;/p&gt;
&lt;h1 id="whats-the-driver-package"&gt;What’s the driver package?&lt;a class="anchor-link" aria-label="Link to heading" href="#whats-the-driver-package"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;Go’s &lt;a href="https://pkg.go.dev/database/sql/driver"&gt;SQL driver package&lt;/a&gt; is an abstraction that lets other
software libraries vend their own SQL connection logic through a common set of
interfaces. Application developers use a common interface to connect to any SQL database (MySQL,
Postgres, MariaDB, SQL Server, Dolt, etc.) without worrying about the specifics of the wire protocol
for that particular database.&lt;/p&gt;
&lt;p&gt;This is best illustrated with an example. Here’s how you connect to MySQL and read some rows.&lt;/p&gt;
&lt;pre class="astro-code github-dark" tabindex="0" data-language="go"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span&gt;package&lt;/span&gt;&lt;span&gt; main&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;	"&lt;/span&gt;&lt;span&gt;database/sql&lt;/span&gt;&lt;span&gt;"&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;	"&lt;/span&gt;&lt;span&gt;fmt&lt;/span&gt;&lt;span&gt;"&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;	"&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;"&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;	_ &lt;/span&gt;&lt;span&gt;"&lt;/span&gt;&lt;span&gt;github.com/go-sql-driver/mysql&lt;/span&gt;&lt;span&gt;"&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;func&lt;/span&gt;&lt;span&gt; main&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;	dsn &lt;/span&gt;&lt;span&gt;:=&lt;/span&gt;&lt;span&gt; "user:password@tcp(127.0.0.1:3306)/testdb"&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;	db, err &lt;/span&gt;&lt;span&gt;:=&lt;/span&gt;&lt;span&gt; sql.&lt;/span&gt;&lt;span&gt;Open&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;"mysql"&lt;/span&gt;&lt;span&gt;, dsn)&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;	if&lt;/span&gt;&lt;span&gt; err &lt;/span&gt;&lt;span&gt;!=&lt;/span&gt;&lt;span&gt; nil&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;		log.&lt;/span&gt;&lt;span&gt;Fatal&lt;/span&gt;&lt;span&gt;(err)&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;	}&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;	defer&lt;/span&gt;&lt;span&gt; db.&lt;/span&gt;&lt;span&gt;Close&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;	rows, err &lt;/span&gt;&lt;span&gt;:=&lt;/span&gt;&lt;span&gt; db.&lt;/span&gt;&lt;span&gt;Query&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;"SELECT id, name FROM users"&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;	if&lt;/span&gt;&lt;span&gt; err &lt;/span&gt;&lt;span&gt;!=&lt;/span&gt;&lt;span&gt; nil&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;		log.&lt;/span&gt;&lt;span&gt;Fatal&lt;/span&gt;&lt;span&gt;(err)&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;	}&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;	defer&lt;/span&gt;&lt;span&gt; rows.&lt;/span&gt;&lt;span&gt;Close&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;	for&lt;/span&gt;&lt;span&gt; rows.&lt;/span&gt;&lt;span&gt;Next&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;		var&lt;/span&gt;&lt;span&gt; id &lt;/span&gt;&lt;span&gt;int&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;		var&lt;/span&gt;&lt;span&gt; name &lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;		if&lt;/span&gt;&lt;span&gt; err &lt;/span&gt;&lt;span&gt;:=&lt;/span&gt;&lt;span&gt; rows.&lt;/span&gt;&lt;span&gt;Scan&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&amp;#x26;&lt;/span&gt;&lt;span&gt;id, &lt;/span&gt;&lt;span&gt;&amp;#x26;&lt;/span&gt;&lt;span&gt;name); err &lt;/span&gt;&lt;span&gt;!=&lt;/span&gt;&lt;span&gt; nil&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;			log.&lt;/span&gt;&lt;span&gt;Fatal&lt;/span&gt;&lt;span&gt;(err)&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;		}&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;		fmt.&lt;/span&gt;&lt;span&gt;Println&lt;/span&gt;&lt;span&gt;(id, name)&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;	}&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Let’s go over this example line by line and see what it does.&lt;/p&gt;
&lt;pre class="astro-code github-dark" tabindex="0" data-language="go"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;	_ &lt;/span&gt;&lt;span&gt;"&lt;/span&gt;&lt;span&gt;github.com/go-sql-driver/mysql&lt;/span&gt;&lt;span&gt;"&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This line is kind of magic and weird: the &lt;code&gt;_&lt;/code&gt; tells Go that you’re not using any symbols from this
package, you’re importing it just for its side-effects. In this case, those side effects are
registering a driver called &lt;code&gt;"mysql"&lt;/code&gt; with &lt;code&gt;database/sql&lt;/code&gt; package.&lt;/p&gt;
&lt;pre class="astro-code github-dark" tabindex="0" data-language="go"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span&gt;	dsn &lt;/span&gt;&lt;span&gt;:=&lt;/span&gt;&lt;span&gt; "user:password@tcp(127.0.0.1:3306)/testdb"&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;	db, err &lt;/span&gt;&lt;span&gt;:=&lt;/span&gt;&lt;span&gt; sql.&lt;/span&gt;&lt;span&gt;Open&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;"mysql"&lt;/span&gt;&lt;span&gt;, dsn)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A DSN is a data source name, almost always resembling a URL but often with some extra bits. Each
database vendor has their own format for these DSNs, but they all look pretty similar. You usually
embed the user name and password and some other metadata, like which database you want to connect
to, in this string.&lt;/p&gt;
&lt;p&gt;To open a connection, you just call &lt;code&gt;sql.Open&lt;/code&gt; with the name of the driver and its matching
DSN. Easy!&lt;/p&gt;
&lt;pre class="astro-code github-dark" tabindex="0" data-language="go"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span&gt;	rows, err &lt;/span&gt;&lt;span&gt;:=&lt;/span&gt;&lt;span&gt; db.&lt;/span&gt;&lt;span&gt;Query&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;"SELECT id, name FROM users"&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;Query()&lt;/code&gt; on a connection takes a query string and returns the resulting rows.&lt;/p&gt;
&lt;pre class="astro-code github-dark" tabindex="0" data-language="go"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span&gt;	for&lt;/span&gt;&lt;span&gt; rows.&lt;/span&gt;&lt;span&gt;Next&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;		var&lt;/span&gt;&lt;span&gt; id &lt;/span&gt;&lt;span&gt;int&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;		var&lt;/span&gt;&lt;span&gt; name &lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;		if&lt;/span&gt;&lt;span&gt; err &lt;/span&gt;&lt;span&gt;:=&lt;/span&gt;&lt;span&gt; rows.&lt;/span&gt;&lt;span&gt;Scan&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&amp;#x26;&lt;/span&gt;&lt;span&gt;id, &lt;/span&gt;&lt;span&gt;&amp;#x26;&lt;/span&gt;&lt;span&gt;name); err &lt;/span&gt;&lt;span&gt;!=&lt;/span&gt;&lt;span&gt; nil&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;			log.&lt;/span&gt;&lt;span&gt;Fatal&lt;/span&gt;&lt;span&gt;(err)&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;		}&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;		fmt.&lt;/span&gt;&lt;span&gt;Println&lt;/span&gt;&lt;span&gt;(id, name)&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;	}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;rows.Scan()&lt;/code&gt; puts the result of the query into normal Go datatypes, like &lt;code&gt;int&lt;/code&gt; or &lt;code&gt;string&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;There are more complicated access patterns, and we haven’t touched on things like &lt;code&gt;INSERT&lt;/code&gt;
statements, but those are the basics.&lt;/p&gt;
&lt;p&gt;Let’s see how a &lt;code&gt;Driver&lt;/code&gt; is implemented by examining the Dolt driver.&lt;/p&gt;
&lt;h1 id="the-dolt-driver"&gt;The Dolt driver&lt;a class="anchor-link" aria-label="Link to heading" href="#the-dolt-driver"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;Dolt’s embedded database driver is defined very simply.&lt;/p&gt;
&lt;pre class="astro-code github-dark" tabindex="0" data-language="go"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span&gt;var&lt;/span&gt;&lt;span&gt; _ &lt;/span&gt;&lt;span&gt;driver&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Driver&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt;doltDriver&lt;/span&gt;&lt;span&gt;)(&lt;/span&gt;&lt;span&gt;nil&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;func&lt;/span&gt;&lt;span&gt; init&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;	sql.&lt;/span&gt;&lt;span&gt;Register&lt;/span&gt;&lt;span&gt;(DoltDriverName, &lt;/span&gt;&lt;span&gt;&amp;#x26;&lt;/span&gt;&lt;span&gt;doltDriver&lt;/span&gt;&lt;span&gt;{})&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;// doltDriver is a driver.Driver implementation which provides access to a&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;// dolt database on the local filesystem&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;type&lt;/span&gt;&lt;span&gt; doltDriver&lt;/span&gt;&lt;span&gt; struct&lt;/span&gt;&lt;span&gt; {}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;init()&lt;/code&gt; function is the special magic that requires you to use the &lt;code&gt;_&lt;/code&gt; import on the database
driver of your choice. At program execution time, this code calls &lt;code&gt;sql.Register()&lt;/code&gt; to tell the
&lt;code&gt;database/sql&lt;/code&gt; package there’s a &lt;code&gt;driver.Driver&lt;/code&gt; implementation named “dolt”.&lt;/p&gt;
&lt;p&gt;Next we need a way to get a connection to the embedded database, which we do with the &lt;code&gt;Open()&lt;/code&gt;
method. In the sample below, I’ve removed most of the error handling for brevity. You can read the
full source &lt;a href="https://github.com/dolthub/driver/blob/main/driver.go"&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;pre class="astro-code github-dark" tabindex="0" data-language="go"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span&gt;func&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;d &lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt;doltDriver&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;Open&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;dataSource&lt;/span&gt;&lt;span&gt; string&lt;/span&gt;&lt;span&gt;) (&lt;/span&gt;&lt;span&gt;driver&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Conn&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;	ctx &lt;/span&gt;&lt;span&gt;:=&lt;/span&gt;&lt;span&gt; context.&lt;/span&gt;&lt;span&gt;Background&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;	var&lt;/span&gt;&lt;span&gt; fs &lt;/span&gt;&lt;span&gt;filesys&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Filesys&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; filesys.LocalFS&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;	ds, err &lt;/span&gt;&lt;span&gt;:=&lt;/span&gt;&lt;span&gt; ParseDataSource&lt;/span&gt;&lt;span&gt;(dataSource)&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;    exists, isDir &lt;/span&gt;&lt;span&gt;:=&lt;/span&gt;&lt;span&gt; fs.&lt;/span&gt;&lt;span&gt;Exists&lt;/span&gt;&lt;span&gt;(ds.Directory)&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;	fs, err &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; fs.&lt;/span&gt;&lt;span&gt;WithWorkingDir&lt;/span&gt;&lt;span&gt;(ds.Directory)&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;    name &lt;/span&gt;&lt;span&gt;:=&lt;/span&gt;&lt;span&gt; ds.Params[CommitNameParam]&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;	email &lt;/span&gt;&lt;span&gt;:=&lt;/span&gt;&lt;span&gt; ds.Params[CommitEmailParam]&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;	cfg &lt;/span&gt;&lt;span&gt;:=&lt;/span&gt;&lt;span&gt; config.&lt;/span&gt;&lt;span&gt;NewMapConfig&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;map&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;		config.UserNameKey:  name[&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;],&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;		config.UserEmailKey: email[&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;],&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;	})&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;	mrEnv, err &lt;/span&gt;&lt;span&gt;:=&lt;/span&gt;&lt;span&gt; LoadMultiEnvFromDir&lt;/span&gt;&lt;span&gt;(ctx, cfg, fs, ds.Directory, &lt;/span&gt;&lt;span&gt;"0.40.17"&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;    seCfg &lt;/span&gt;&lt;span&gt;:=&lt;/span&gt;&lt;span&gt; &amp;#x26;&lt;/span&gt;&lt;span&gt;engine&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;SqlEngineConfig&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;		IsReadOnly: &lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;		ServerUser: &lt;/span&gt;&lt;span&gt;"root"&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;		Autocommit: &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;	}&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;	se, err &lt;/span&gt;&lt;span&gt;:=&lt;/span&gt;&lt;span&gt; engine.&lt;/span&gt;&lt;span&gt;NewSqlEngine&lt;/span&gt;&lt;span&gt;(ctx, mrEnv, seCfg)&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;	gmsCtx, err &lt;/span&gt;&lt;span&gt;:=&lt;/span&gt;&lt;span&gt; se.&lt;/span&gt;&lt;span&gt;NewLocalContext&lt;/span&gt;&lt;span&gt;(ctx)&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;	if&lt;/span&gt;&lt;span&gt; database, ok &lt;/span&gt;&lt;span&gt;:=&lt;/span&gt;&lt;span&gt; ds.Params[DatabaseParam]; ok &lt;/span&gt;&lt;span&gt;&amp;#x26;&amp;#x26;&lt;/span&gt;&lt;span&gt; len&lt;/span&gt;&lt;span&gt;(database) &lt;/span&gt;&lt;span&gt;==&lt;/span&gt;&lt;span&gt; 1&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;		gmsCtx.&lt;/span&gt;&lt;span&gt;SetCurrentDatabase&lt;/span&gt;&lt;span&gt;(database[&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;])&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;	}&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;	return&lt;/span&gt;&lt;span&gt; &amp;#x26;&lt;/span&gt;&lt;span&gt;DoltConn&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;		DataSource: ds,&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;		se:         se,&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;		gmsCtx:     gmsCtx,&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;	}, &lt;/span&gt;&lt;span&gt;nil&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Dolt’s DSN format is basically a file URL with some extra query params. It looks something like
this:&lt;/p&gt;
&lt;pre class="astro-code github-dark" tabindex="0" data-language="plaintext"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span&gt;file:///User/brian/driver/example/path?commitname=Billy%20Bob&amp;#x26;commitemail=bb@gmail.com&amp;#x26;database=dbname&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We parse this URL and extract the relevant query params out of it, then use those to create our
internal SQL engine representation, which is what Dolt uses to execute queries internally.&lt;/p&gt;
&lt;p&gt;This gives us a &lt;code&gt;DoltConn&lt;/code&gt; with an engine it can use to execute queries. Let’s look at that next.&lt;/p&gt;
&lt;h1 id="dolts-driverconnection"&gt;Dolt’s &lt;code&gt;driver.Connection&lt;/code&gt;&lt;a class="anchor-link" aria-label="Link to heading" href="#dolts-driverconnection"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;Unlike the driver itself, the &lt;code&gt;DoltConn&lt;/code&gt; type has some state. But it’s still very simple. Its main
job is to pass information down the line, from a call to &lt;code&gt;Prepare&lt;/code&gt; to a &lt;code&gt;driver.Stmt&lt;/code&gt; type.&lt;/p&gt;
&lt;pre class="astro-code github-dark" tabindex="0" data-language="go"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span&gt;type&lt;/span&gt;&lt;span&gt; DoltConn&lt;/span&gt;&lt;span&gt; struct&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;	se         &lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt;engine&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;SqlEngine&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;	gmsCtx     &lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt;gms&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Context&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;	DataSource &lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt;DoltDataSource&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;var&lt;/span&gt;&lt;span&gt; _ &lt;/span&gt;&lt;span&gt;driver&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Conn&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt;DoltConn&lt;/span&gt;&lt;span&gt;)(&lt;/span&gt;&lt;span&gt;nil&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;// Prepare packages up |query| as a *doltStmt so it can be executed. If multistatements mode&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;// has been enabled, then a *doltMultiStmt will be returned, capable of executing multiple statements.&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;func&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;d &lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt;DoltConn&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;Prepare&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;query&lt;/span&gt;&lt;span&gt; string&lt;/span&gt;&lt;span&gt;) (&lt;/span&gt;&lt;span&gt;driver&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Stmt&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;	// Reuse the same ctx instance, but update the QueryTime to the current time.&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;	// Statements are executed serially on a connection, so it's safe to reuse&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;	// the same ctx instance and update the time.&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;	d.gmsCtx.&lt;/span&gt;&lt;span&gt;SetQueryTime&lt;/span&gt;&lt;span&gt;(time.&lt;/span&gt;&lt;span&gt;Now&lt;/span&gt;&lt;span&gt;())&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;	if&lt;/span&gt;&lt;span&gt; d.DataSource.&lt;/span&gt;&lt;span&gt;ParamIsTrue&lt;/span&gt;&lt;span&gt;(MultiStatementsParam) {&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;		return&lt;/span&gt;&lt;span&gt; d.&lt;/span&gt;&lt;span&gt;prepareMultiStatement&lt;/span&gt;&lt;span&gt;(query)&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;	} &lt;/span&gt;&lt;span&gt;else&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;		return&lt;/span&gt;&lt;span&gt; d.&lt;/span&gt;&lt;span&gt;prepareSingleStatement&lt;/span&gt;&lt;span&gt;(query)&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;	}&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;// prepareSingleStatement creates a doltStmt from |query|.&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;func&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;d &lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt;DoltConn&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;prepareSingleStatement&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;query&lt;/span&gt;&lt;span&gt; string&lt;/span&gt;&lt;span&gt;) (&lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt;doltStmt&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;	return&lt;/span&gt;&lt;span&gt; &amp;#x26;&lt;/span&gt;&lt;span&gt;doltStmt&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;		query:  query,&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;		se:     d.se,&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;		gmsCtx: d.gmsCtx,&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;	}, &lt;/span&gt;&lt;span&gt;nil&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;driver.Stmt&lt;/code&gt; implementation is where real work begins to happen, in the &lt;code&gt;Query()&lt;/code&gt; method.&lt;/p&gt;
&lt;pre class="astro-code github-dark" tabindex="0" data-language="go"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span&gt;// Query executes a query that may return rows, such as a SELECT&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;func&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;stmt &lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt;doltStmt&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;Query&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;args&lt;/span&gt;&lt;span&gt; []&lt;/span&gt;&lt;span&gt;driver&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Value&lt;/span&gt;&lt;span&gt;) (&lt;/span&gt;&lt;span&gt;driver&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Rows&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;	var&lt;/span&gt;&lt;span&gt; sch &lt;/span&gt;&lt;span&gt;gms&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Schema&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;	var&lt;/span&gt;&lt;span&gt; rowIter &lt;/span&gt;&lt;span&gt;gms&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;RowIter&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;	var&lt;/span&gt;&lt;span&gt; err &lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;	if&lt;/span&gt;&lt;span&gt; len&lt;/span&gt;&lt;span&gt;(args) &lt;/span&gt;&lt;span&gt;!=&lt;/span&gt;&lt;span&gt; 0&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;		sch, rowIter, err &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; stmt.&lt;/span&gt;&lt;span&gt;execWithArgs&lt;/span&gt;&lt;span&gt;(args)&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;	} &lt;/span&gt;&lt;span&gt;else&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;		sch, rowIter, _, err &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; stmt.se.&lt;/span&gt;&lt;span&gt;Query&lt;/span&gt;&lt;span&gt;(stmt.gmsCtx, stmt.query)&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;	}&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;	if&lt;/span&gt;&lt;span&gt; err &lt;/span&gt;&lt;span&gt;!=&lt;/span&gt;&lt;span&gt; nil&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;		return&lt;/span&gt;&lt;span&gt; nil&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;translateError&lt;/span&gt;&lt;span&gt;(err)&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;	}&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;	// Wrap the result iterator in a peekableRowIter and call Peek() to read the first row from the result iterator.&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;	// This is necessary for insert operations, since the insert happens inside the result iterator logic.&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;	peekIter &lt;/span&gt;&lt;span&gt;:=&lt;/span&gt;&lt;span&gt; peekableRowIter&lt;/span&gt;&lt;span&gt;{iter: rowIter}&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;	row, _ &lt;/span&gt;&lt;span&gt;:=&lt;/span&gt;&lt;span&gt; peekIter.&lt;/span&gt;&lt;span&gt;Peek&lt;/span&gt;&lt;span&gt;(stmt.gmsCtx)&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;	return&lt;/span&gt;&lt;span&gt; &amp;#x26;&lt;/span&gt;&lt;span&gt;doltRows&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;		sch:              sch,&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;		rowIter:          &lt;/span&gt;&lt;span&gt;&amp;#x26;&lt;/span&gt;&lt;span&gt;peekIter,&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;		gmsCtx:           stmt.gmsCtx,&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;		isQueryResultSet: &lt;/span&gt;&lt;span&gt;isQueryResultSet&lt;/span&gt;&lt;span&gt;(row),&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;	}, &lt;/span&gt;&lt;span&gt;nil&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;func&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;stmt &lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt;doltStmt&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;execWithArgs&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;args&lt;/span&gt;&lt;span&gt; []&lt;/span&gt;&lt;span&gt;driver&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Value&lt;/span&gt;&lt;span&gt;) (&lt;/span&gt;&lt;span&gt;gms&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Schema&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;gms&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;RowIter&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;	bindings, err &lt;/span&gt;&lt;span&gt;:=&lt;/span&gt;&lt;span&gt; argsToBindings&lt;/span&gt;&lt;span&gt;(args)&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;	if&lt;/span&gt;&lt;span&gt; err &lt;/span&gt;&lt;span&gt;!=&lt;/span&gt;&lt;span&gt; nil&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;		return&lt;/span&gt;&lt;span&gt; nil&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;nil&lt;/span&gt;&lt;span&gt;, err&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;	}&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;	sch, itr, _, err &lt;/span&gt;&lt;span&gt;:=&lt;/span&gt;&lt;span&gt; stmt.se.&lt;/span&gt;&lt;span&gt;GetUnderlyingEngine&lt;/span&gt;&lt;span&gt;().&lt;/span&gt;&lt;span&gt;QueryWithBindings&lt;/span&gt;&lt;span&gt;(stmt.gmsCtx, stmt.query, &lt;/span&gt;&lt;span&gt;nil&lt;/span&gt;&lt;span&gt;, bindings, &lt;/span&gt;&lt;span&gt;nil&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;	if&lt;/span&gt;&lt;span&gt; err &lt;/span&gt;&lt;span&gt;!=&lt;/span&gt;&lt;span&gt; nil&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;		return&lt;/span&gt;&lt;span&gt; nil&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;nil&lt;/span&gt;&lt;span&gt;, err&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;	}&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;	return&lt;/span&gt;&lt;span&gt; sch, itr, &lt;/span&gt;&lt;span&gt;nil&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here you can see that the statement’s real work is nearly all delegated to the SQL engine we created
in the initial call to &lt;code&gt;Open()&lt;/code&gt;. The rest of its functionality is just to translate between the
results that Dolt’s SQL engine provides and what the &lt;code&gt;database/sql&lt;/code&gt; interfaces expect. For that, we
have the &lt;code&gt;doltRows&lt;/code&gt; type.&lt;/p&gt;
&lt;pre class="astro-code github-dark" tabindex="0" data-language="go"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span&gt;type&lt;/span&gt;&lt;span&gt; doltRows&lt;/span&gt;&lt;span&gt; struct&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;	sch     &lt;/span&gt;&lt;span&gt;gms&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Schema&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;	rowIter &lt;/span&gt;&lt;span&gt;gms&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;RowIter&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;	gmsCtx  &lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt;gms&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Context&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;	columns []&lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;var&lt;/span&gt;&lt;span&gt; _ &lt;/span&gt;&lt;span&gt;driver&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Rows&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt;doltRows&lt;/span&gt;&lt;span&gt;)(&lt;/span&gt;&lt;span&gt;nil&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;// Columns returns the names of the columns. The number of columns of the result is inferred from the length of the&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;// slice. If a particular column name isn't known, an empty string should be returned for that entry.&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;func&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;rows &lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt;doltRows&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;Columns&lt;/span&gt;&lt;span&gt;() []&lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;	if&lt;/span&gt;&lt;span&gt; rows.columns &lt;/span&gt;&lt;span&gt;==&lt;/span&gt;&lt;span&gt; nil&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;		rows.columns &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; make&lt;/span&gt;&lt;span&gt;([]&lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;len&lt;/span&gt;&lt;span&gt;(rows.sch))&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;		for&lt;/span&gt;&lt;span&gt; i, col &lt;/span&gt;&lt;span&gt;:=&lt;/span&gt;&lt;span&gt; range&lt;/span&gt;&lt;span&gt; rows.sch {&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;			rows.columns[i] &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; col.Name&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;		}&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;	}&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;	return&lt;/span&gt;&lt;span&gt; rows.columns&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;// Next is called to populate the next row of data into the provided slice.&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;// The provided slice will be the same size as the Columns() are wide.&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;// Next returns io.EOF when there are no more rows.&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;func&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;rows &lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt;doltRows&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;Next&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;dest&lt;/span&gt;&lt;span&gt; []&lt;/span&gt;&lt;span&gt;driver&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Value&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;	nextRow, err &lt;/span&gt;&lt;span&gt;:=&lt;/span&gt;&lt;span&gt; rows.rowIter.&lt;/span&gt;&lt;span&gt;Next&lt;/span&gt;&lt;span&gt;(rows.gmsCtx)&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;	if&lt;/span&gt;&lt;span&gt; err &lt;/span&gt;&lt;span&gt;!=&lt;/span&gt;&lt;span&gt; nil&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;		if&lt;/span&gt;&lt;span&gt; err &lt;/span&gt;&lt;span&gt;==&lt;/span&gt;&lt;span&gt; io.EOF {&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;			return&lt;/span&gt;&lt;span&gt; io.EOF&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;		}&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;		return&lt;/span&gt;&lt;span&gt; translateError&lt;/span&gt;&lt;span&gt;(err)&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;	}&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;	if&lt;/span&gt;&lt;span&gt; len&lt;/span&gt;&lt;span&gt;(dest) &lt;/span&gt;&lt;span&gt;!=&lt;/span&gt;&lt;span&gt; len&lt;/span&gt;&lt;span&gt;(nextRow) {&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;		return&lt;/span&gt;&lt;span&gt; errors.&lt;/span&gt;&lt;span&gt;New&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;"mismatch between expected column count and actual column count"&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;	}&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;	for&lt;/span&gt;&lt;span&gt; i &lt;/span&gt;&lt;span&gt;:=&lt;/span&gt;&lt;span&gt; range&lt;/span&gt;&lt;span&gt; nextRow {&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;        // Some types like enums need special handling here, but mostly the types returned&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;        // by the Dolt SQL engine are already the Go types that the database/sql interfaces require&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;        if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;...&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;            // special type handling code&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;		} &lt;/span&gt;&lt;span&gt;else&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;			dest[i] &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; nextRow[i]&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;		}&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;	}&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;	return&lt;/span&gt;&lt;span&gt; nil&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And that’s it! The job of these interfaces is really to act as a translation layer between the wire
protocol the database uses and the types that &lt;code&gt;database/sql&lt;/code&gt; expects. In the case of the Dolt
embedded driver, there’s no wire protocol: we’re accessing the SQL engine that queries the database
on disk directly and using the data structures it returns natively.&lt;/p&gt;
&lt;h1 id="using-a-driver-with-gorm"&gt;Using a driver with Gorm&lt;a class="anchor-link" aria-label="Link to heading" href="#using-a-driver-with-gorm"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;A lot of developers prefer to use an ORM tool when interacting with their database, and in the Go
world, the most popular ORM library is &lt;a href="https://gorm.io/index.html"&gt;Gorm&lt;/a&gt;. Gorm usually manages your
DB connection for you automatically, but in the case of Dolt embedded we want something slightly
different: we want it to use its MySQL dialect and logic but connect to an embedded Dolt database
connection. This is pretty easy to do.&lt;/p&gt;
&lt;pre class="astro-code github-dark" tabindex="0" data-language="go"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span&gt;package&lt;/span&gt;&lt;span&gt; main&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;	"&lt;/span&gt;&lt;span&gt;fmt&lt;/span&gt;&lt;span&gt;"&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;	"&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;"&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;	"&lt;/span&gt;&lt;span&gt;gorm.io/gorm&lt;/span&gt;&lt;span&gt;"&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;    _ &lt;/span&gt;&lt;span&gt;"&lt;/span&gt;&lt;span&gt;github.com/go-sql-driver/mysql&lt;/span&gt;&lt;span&gt;"&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;    _ &lt;/span&gt;&lt;span&gt;"&lt;/span&gt;&lt;span&gt;github.com/dolthub/driver/embedded&lt;/span&gt;&lt;span&gt;"&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;type&lt;/span&gt;&lt;span&gt; User&lt;/span&gt;&lt;span&gt; struct&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;	ID   &lt;/span&gt;&lt;span&gt;uint&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;	Name &lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;func&lt;/span&gt;&lt;span&gt; main&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;	dbName &lt;/span&gt;&lt;span&gt;:=&lt;/span&gt;&lt;span&gt; "server_db"&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;	dsn &lt;/span&gt;&lt;span&gt;:=&lt;/span&gt;&lt;span&gt; fmt.&lt;/span&gt;&lt;span&gt;Sprintf&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;"file://&lt;/span&gt;&lt;span&gt;%v&lt;/span&gt;&lt;span&gt;?commitname=&lt;/span&gt;&lt;span&gt;%v&lt;/span&gt;&lt;span&gt;&amp;#x26;commitemail=&lt;/span&gt;&lt;span&gt;%v&lt;/span&gt;&lt;span&gt;&amp;#x26;database=&lt;/span&gt;&lt;span&gt;%v&lt;/span&gt;&lt;span&gt;"&lt;/span&gt;&lt;span&gt;, dir, &lt;/span&gt;&lt;span&gt;"Gorm Tester"&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;"gorm@dolthub.com"&lt;/span&gt;&lt;span&gt;, dbName)&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;	sqlDB, err &lt;/span&gt;&lt;span&gt;:=&lt;/span&gt;&lt;span&gt; sql.&lt;/span&gt;&lt;span&gt;Open&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;"dolt"&lt;/span&gt;&lt;span&gt;, dsn)&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;    db, err &lt;/span&gt;&lt;span&gt;:=&lt;/span&gt;&lt;span&gt; gorm.&lt;/span&gt;&lt;span&gt;Open&lt;/span&gt;&lt;span&gt;(mysql.&lt;/span&gt;&lt;span&gt;New&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;mysql&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Config&lt;/span&gt;&lt;span&gt;{Conn: sqlDB}), &lt;/span&gt;&lt;span&gt;&amp;#x26;&lt;/span&gt;&lt;span&gt;gorm&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Config&lt;/span&gt;&lt;span&gt;{SkipDefaultTransaction: &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;, PrepareStmt: &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;})&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;	if&lt;/span&gt;&lt;span&gt; err &lt;/span&gt;&lt;span&gt;!=&lt;/span&gt;&lt;span&gt; nil&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;		log.&lt;/span&gt;&lt;span&gt;Fatal&lt;/span&gt;&lt;span&gt;(err)&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;	}&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;	var&lt;/span&gt;&lt;span&gt; users []&lt;/span&gt;&lt;span&gt;User&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;	if&lt;/span&gt;&lt;span&gt; err &lt;/span&gt;&lt;span&gt;:=&lt;/span&gt;&lt;span&gt; db.&lt;/span&gt;&lt;span&gt;Find&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&amp;#x26;&lt;/span&gt;&lt;span&gt;users).Error; err &lt;/span&gt;&lt;span&gt;!=&lt;/span&gt;&lt;span&gt; nil&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;		log.&lt;/span&gt;&lt;span&gt;Fatal&lt;/span&gt;&lt;span&gt;(err)&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;	}&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;	for&lt;/span&gt;&lt;span&gt; _, u &lt;/span&gt;&lt;span&gt;:=&lt;/span&gt;&lt;span&gt; range&lt;/span&gt;&lt;span&gt; users {&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;		fmt.&lt;/span&gt;&lt;span&gt;Println&lt;/span&gt;&lt;span&gt;(u.ID, u.Name)&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;	}&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Note that we need to import both the MySQL driver and the Dolt driver for this to work. But
otherwise, it’s a standard Gorm setup.&lt;/p&gt;
&lt;h1 id="conclusion"&gt;Conclusion&lt;a class="anchor-link" aria-label="Link to heading" href="#conclusion"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;Go’s database drivers are a simple way for database application developers to connect to any of the
many different SQL databases you can run in production with a common interface. Standardizing these
interfaces made it easier for libraries like Gorm to offer support for a larger variety of different
database vendors, since the details of the wire protocols and other tricky bits are hidden by the
abstraction for most uses. And it’s pretty simple to write your own driver if you have a SQL data
source you want other people to connect to.&lt;/p&gt;
&lt;p&gt;Want to discuss Go database drivers or learn more about Dolt? Visit us on the &lt;a href="https://discord.gg/gqr7K4VNKe"&gt;DoltHub
Discord&lt;/a&gt;, where our engineering team hangs out all day. Hope to see
you there.&lt;/p&gt;</content:encoded>
      <dc:creator>Zach Musgrave</dc:creator>
      <category>dolt</category>
      <category>golang</category>
    </item>
    <item>
      <title>Using Dolt with ORMs</title>
      <link>https://dolthub.com/blog/2026-01-20-dolt-with-orms/</link>
      <guid isPermaLink="true">https://dolthub.com/blog/2026-01-20-dolt-with-orms/</guid>
      <description>Object-Relational Mappers (ORMs) simplify application development by abstracting database interactions, and they pair naturally with Dolt, a MySQL-compatible, version-controlled relational database. This post explains how ORMs work with Dolt, highlighting tested integrations, useful Dolt features like schema overrides and nonlocal tables, and important considerations around branching and connection pooling.</description>
      <pubDate>Tue, 20 Jan 2026 00:00:00 GMT</pubDate>
      <content:encoded>&lt;p&gt;&lt;a href="https://en.wikipedia.org/wiki/Object%E2%80%93relational_mapping"&gt;Object-Relational Mappers (ORMs)&lt;/a&gt; are a cornerstone of modern application development. They provide a domain-friendly interface for working with relational databases, letting developers define models in their chosen programming language and then automatically translating those into SQL queries. By abstracting away SQL boilerplate, ORMs can increase productivity, reduce errors, and let developers focus on their higher-level application code instead of spending time writing SQL queries.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://doltdb.com/"&gt;Dolt&lt;/a&gt; is a MySQL-compatible relational database that combines the features of MySQL with the features of Git, to give you the world’s first &lt;a href="https://www.dolthub.com/blog/2022-08-04-database-versioning/"&gt;version-controlled, relational database&lt;/a&gt;. Dolt lets you branch, merge, diff, and work with your SQL tables in all the same ways that Git lets you work with files.&lt;/p&gt;
&lt;p&gt;In this post, we’re taking a look at how to use Dolt with ORMs and highlighting some specific Dolt features that can be helpful, along with a couple of gotchas to know about.&lt;/p&gt;
&lt;h1 id="orm-benefits"&gt;ORM Benefits&lt;a class="anchor-link" aria-label="Link to heading" href="#orm-benefits"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;ORMs are popular because they take over the responsibility for database interactions in an application by creating SQL queries and unmarshalling results from the database into model objects. Beyond that, there’s a lot of variance in what ORMs provide. Some ORMs are super lightweight without providing extra functionality, while others come bundled in a full-featured framework that provides all sorts of other features such as pagination support, advanced connection pooling, and lazy/eager data loading.&lt;/p&gt;
&lt;p&gt;Generally, you can expect most ORMs to provide:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Model-First Development&lt;/strong&gt;: Define your entities in code. Use classes, structs, or structs with annotations, and let the ORM handle mapping to tables and columns. This removes a lot of marshalling/unmarshalling boilerplate code from your application.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Migrations &amp;#x26; Schema Evolution&lt;/strong&gt;: Evolve your schema safely over time, alongside your application code, using ORM migration tooling. ORMs can generate migration scripts and apply those migration scripts when deploying new versions.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Query Building&lt;/strong&gt;: Build database queries using fluent, type-safe query construction instead of authoring raw SQL strings.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It’s worth noting that not every database application needs to use an ORM. ORMs provide a lot of helpful functionality, but there are valid reasons why a developer may prefer to keep tight control over the SQL queries and query results processing, instead of using an ORM.&lt;/p&gt;
&lt;h1 id="dolt-compatibility-with-orms"&gt;Dolt Compatibility with ORMs&lt;a class="anchor-link" aria-label="Link to heading" href="#dolt-compatibility-with-orms"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;Dolt is MySQL-compatible, although it doesn’t contain any MySQL code in its implementation. We wrote Dolt from the ground up so that we could use a &lt;a href="https://www.dolthub.com/blog/2024-02-29-storage-engine/"&gt;unique data storage engine&lt;/a&gt; which enables extremely efficient diff computation. Because Dolt is MySQL-compatible, you can use any tool, framework, or ORM that works with MySQL to interact with Dolt. As part of our MySQL-compatibility testing, we’ve tested &lt;a href="https://docs.dolthub.com/guides/dolt-tested-apps#frameworks-orms"&gt;over a dozen ORMs in various languages&lt;/a&gt; to make sure they work well with Dolt. (If you’re looking for a version-controlled Postgres database, check out &lt;a href="https://doltgres.com/"&gt;Doltgres&lt;/a&gt;, another database we’ve been building.)&lt;/p&gt;
&lt;p&gt;At the time of writing this post, here are the ORMs we’ve tested. Each one has a link to a blog article with a walkthrough of using that ORM with Dolt, as well as sample code in a linked repository:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;ASP.NET Entity Framework Core (&lt;a href="https://www.dolthub.com/blog/2023-12-04-works-with-dolt-efcore/"&gt;blog&lt;/a&gt;) (&lt;a href="https://github.com/dolthub/efcore-sample"&gt;sample code&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Diesel (&lt;a href="https://www.dolthub.com/blog/2024-08-30-dolt-diesel-getting-started/"&gt;blog&lt;/a&gt;) (&lt;a href="https://github.com/dolthub/dolt-diesel-getting-started"&gt;sample code&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Django (&lt;a href="https://www.dolthub.com/blog/2024-01-31-dolt-django/"&gt;blog&lt;/a&gt;) (&lt;a href="https://github.com/dolthub/dolt_django"&gt;sample code&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Ecto (&lt;a href="https://www.dolthub.com/blog/2021-07-16-ecto-dolt/"&gt;blog&lt;/a&gt;) (&lt;a href="https://github.com/VinaiRachakonda/Ecto-Dolt-Sample"&gt;sample code&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;GORM (&lt;a href="https://www.dolthub.com/blog/2024-03-15-gorm-with-dolt"&gt;blog&lt;/a&gt;) (&lt;a href="https://github.com/dolthub/gorm-demo"&gt;sample code&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Hibernate (&lt;a href="https://www.dolthub.com/blog/2023-11-13-dolt-on-hibernate/"&gt;blog&lt;/a&gt;) (&lt;a href="https://github.com/dolthub/hibernate-sample"&gt;sample code&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Knex.js (&lt;a href="https://www.dolthub.com/blog/2023-09-27-dolt-and-knexjs/"&gt;blog&lt;/a&gt;) (&lt;a href="https://github.com/dolthub/dolt-knexjs-example"&gt;sample code&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Laravel (&lt;a href="https://www.dolthub.com/blog/2024-01-08-dolt-laravel/"&gt;blog&lt;/a&gt;) (&lt;a href="https://github.com/dolthub/chirper"&gt;sample code&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Ruby on Rails (&lt;a href="https://www.dolthub.com/blog/2024-02-09-dolt-ruby-on-rails/"&gt;blog&lt;/a&gt;) (&lt;a href="https://github.com/dolthub/dolt_rails"&gt;sample code&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Prisma (&lt;a href="https://www.dolthub.com/blog/2024-06-28-dolt-and-prisma/"&gt;blog&lt;/a&gt;) (&lt;a href="https://github.com/dolthub/dolt-prisma-example"&gt;sample code&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;SQLAlchemy (&lt;a href="https://www.dolthub.com/blog/2023-07-12-sql-alchemy-getting-started/"&gt;blog&lt;/a&gt;) (&lt;a href="https://github.com/timsehn/dolt-sqlalchemy-getting-started/tree/main"&gt;sample code&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;h1 id="tricks-and-tips-for-using-dolt-with-an-orm"&gt;Tricks and Tips for Using Dolt with an ORM&lt;a class="anchor-link" aria-label="Link to heading" href="#tricks-and-tips-for-using-dolt-with-an-orm"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;ORMs are designed around certain assumptions: a single, stable schema for your tables, traditional schema migrations, and predictable introspection capabilities. Dolt expands what’s possible with data, and challenges some of those assumptions. The following sections share tips and tricks for using Dolt effectively with ORMs.&lt;/p&gt;
&lt;h2 id="using-orms-with-historical-schemas"&gt;Using ORMs with Historical Schemas&lt;a class="anchor-link" aria-label="Link to heading" href="#using-orms-with-historical-schemas"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;One of the primary benefits of Dolt is that you can go back in time and view your data at any point where you’ve made a Dolt commit. This is a powerful feature, but can present a challenge for ORMs that expect data to match the schema of your current model definitions. If you go back in time, specifically across schema changes, then your tables’ schemas won’t match what the ORM is expecting from your model.&lt;/p&gt;
&lt;p&gt;Fortunately, Dolt provides a solution for this. &lt;a href="https://www.dolthub.com/blog/2024-03-22-schema-overriding/"&gt;Schema overriding&lt;/a&gt; allows you to pin your schema to a specific commit, then when you query historical data, it is automatically mapped to that target schema. This feature was designed explicitly to enable ORMs to more easily access historical data when the data’s schema has changed over time. By setting the session variable &lt;code&gt;@@dolt_override_schema&lt;/code&gt;, all queries in that session will behave as if the database schema were the one specified, even if the underlying data comes from a commit with a different schema.&lt;/p&gt;
&lt;p&gt;Using this feature is easy. Let’s take it for a spin…&lt;/p&gt;
&lt;p&gt;First, create a new directory, initialize it as a Dolt database, and then launch a SQL shell:&lt;/p&gt;
&lt;pre class="astro-code github-dark" tabindex="0" data-language="shell"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span&gt;mkdir&lt;/span&gt;&lt;span&gt; dolt-schema-override-demo&lt;/span&gt;&lt;span&gt; &amp;#x26;&amp;#x26; &lt;/span&gt;&lt;span&gt;cd&lt;/span&gt;&lt;span&gt; dolt-schema-override-demo&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;dolt&lt;/span&gt;&lt;span&gt; init&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;dolt&lt;/span&gt;&lt;span&gt; sql&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Next, let’s create some sample data and then extend the schema in the most recent commit:&lt;/p&gt;
&lt;pre class="astro-code github-dark" tabindex="0" data-language="sql"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span&gt;-- Create the "old" schema (no Birthdate) and seed data&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;CREATE&lt;/span&gt;&lt;span&gt; TABLE&lt;/span&gt;&lt;span&gt; people&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;    Id   &lt;/span&gt;&lt;span&gt;INT&lt;/span&gt;&lt;span&gt; NOT NULL&lt;/span&gt;&lt;span&gt; PRIMARY KEY&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;    Name&lt;/span&gt;&lt;span&gt; TEXT&lt;/span&gt;&lt;span&gt; NOT NULL&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;INSERT INTO&lt;/span&gt;&lt;span&gt; people (Id, &lt;/span&gt;&lt;span&gt;Name&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;VALUES&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;    (&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;'Frank'&lt;/span&gt;&lt;span&gt;),&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;    (&lt;/span&gt;&lt;span&gt;2&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;'Columbia'&lt;/span&gt;&lt;span&gt;),&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;    (&lt;/span&gt;&lt;span&gt;3&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;'Dr. Scott'&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;-- Stage all tables (-A) and commit &lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;CALL&lt;/span&gt;&lt;span&gt; DOLT_COMMIT(&lt;/span&gt;&lt;span&gt;'-Am'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;'Create people table (Id, Name) and seed data'&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;-- Tag this version so we can reference this point in our history later&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;CALL&lt;/span&gt;&lt;span&gt; DOLT_TAG(&lt;/span&gt;&lt;span&gt;'v1.0'&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;-- Evolve the schema: add a new column and commit it&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;ALTER&lt;/span&gt;&lt;span&gt; TABLE&lt;/span&gt;&lt;span&gt; people &lt;/span&gt;&lt;span&gt;ADD&lt;/span&gt;&lt;span&gt; COLUMN Birthdate &lt;/span&gt;&lt;span&gt;DATE&lt;/span&gt;&lt;span&gt; NULL&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;INSERT INTO&lt;/span&gt;&lt;span&gt; people (Id, &lt;/span&gt;&lt;span&gt;Name&lt;/span&gt;&lt;span&gt;, Birthdate) &lt;/span&gt;&lt;span&gt;VALUES&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;4&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;'Magenta'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;'1981-02-16 06:00:00'&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;CALL&lt;/span&gt;&lt;span&gt; DOLT_COMMIT(&lt;/span&gt;&lt;span&gt;'-Am'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;'Add Birthdate column to people'&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now that we’ve set up our database, we’re ready to use the schema override feature. First let’s take a look at the
problem. Dolt lets you query historical data in many ways. You could use a branch that has an older schema version,
or you could check out a tag that points to an older commit, or you could use the &lt;code&gt;AS OF&lt;/code&gt; syntax to reference an
older commit. Those give us easy ways to look back in history and see how our data existed at previous points, but
remember that our ORM is still using a static model to deserialize results and it’s expecting the schema to match
that static model exactly. In the query below, the ORM’s static model knows about the &lt;code&gt;Name&lt;/code&gt; and &lt;code&gt;Birthdate&lt;/code&gt; fields,
but the &lt;code&gt;Birthdate&lt;/code&gt; field doesn’t exist at &lt;code&gt;v1.0&lt;/code&gt;.&lt;/p&gt;
&lt;pre class="astro-code github-dark" tabindex="0" data-language="sql"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span&gt;-- this column doesn't exist on olderData's schema&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;SELECT&lt;/span&gt;&lt;span&gt; Name&lt;/span&gt;&lt;span&gt;, Birthdate &lt;/span&gt;&lt;span&gt;FROM&lt;/span&gt;&lt;span&gt; people &lt;/span&gt;&lt;span&gt;AS&lt;/span&gt;&lt;span&gt; OF &lt;/span&gt;&lt;span&gt;'v1.0'&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The query above fails, because the commit we’re referencing doesn’t contain the &lt;code&gt;Birthdate&lt;/code&gt; field:&lt;/p&gt;
&lt;pre class="astro-code github-dark" tabindex="0" data-language="shell"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span&gt;column&lt;/span&gt;&lt;span&gt; "Birthdate"&lt;/span&gt;&lt;span&gt; could&lt;/span&gt;&lt;span&gt; not&lt;/span&gt;&lt;span&gt; be&lt;/span&gt;&lt;span&gt; found&lt;/span&gt;&lt;span&gt; in&lt;/span&gt;&lt;span&gt; any&lt;/span&gt;&lt;span&gt; table&lt;/span&gt;&lt;span&gt; in&lt;/span&gt;&lt;span&gt; scope&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To fix this, we just need to set the &lt;code&gt;@@dolt_override_schema&lt;/code&gt; session variable with a commit reference that contains the schema we
want to map to. In our case, we’re going to use the latest schema from &lt;code&gt;main&lt;/code&gt;, since that’s what matches the static
model our ORM is using.&lt;/p&gt;
&lt;pre class="astro-code github-dark" tabindex="0" data-language="sql"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span&gt;-- Override schema to use the schema at the tip of the main branch. &lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;-- The hashof() function resolves the tag to a commit hash. &lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;SET&lt;/span&gt;&lt;span&gt; @@dolt_override_schema &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; 'main'&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;-- Now the same query succeeds (Birthdate will be NULL for these historical rows)&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;-- Dolt maps old data to the newer schema and fills in missing columns with NULL.&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;SELECT&lt;/span&gt;&lt;span&gt; Name&lt;/span&gt;&lt;span&gt;, Birthdate &lt;/span&gt;&lt;span&gt;FROM&lt;/span&gt;&lt;span&gt; people &lt;/span&gt;&lt;span&gt;AS&lt;/span&gt;&lt;span&gt; OF &lt;/span&gt;&lt;span&gt;'v1.0'&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With the &lt;code&gt;@@dolt_override_schema&lt;/code&gt; session variable set, we can now execute that query without an error, because the
data is mapped to the schema at the tip of &lt;code&gt;main&lt;/code&gt; and fills in &lt;code&gt;NULL&lt;/code&gt; values for the new &lt;code&gt;Birthdate&lt;/code&gt; field. Notice
in this example that the data we’re using here comes from &lt;code&gt;v1.0&lt;/code&gt; (because of the &lt;code&gt;AS OF 'v1.0'&lt;/code&gt; clause we used), but
the schema comes from the tip of &lt;code&gt;main&lt;/code&gt; (because &lt;code&gt;@@dolt_override_schema&lt;/code&gt; is set to &lt;code&gt;main&lt;/code&gt;).&lt;/p&gt;
&lt;pre class="astro-code github-dark" tabindex="0" data-language="shell"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span&gt;+-----------+-----------+&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;|&lt;/span&gt;&lt;span&gt; Name&lt;/span&gt;&lt;span&gt;      |&lt;/span&gt;&lt;span&gt; Birthdate&lt;/span&gt;&lt;span&gt; |&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;+-----------+-----------+&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;|&lt;/span&gt;&lt;span&gt; Frank&lt;/span&gt;&lt;span&gt;     |&lt;/span&gt;&lt;span&gt; NULL&lt;/span&gt;&lt;span&gt;      |&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;|&lt;/span&gt;&lt;span&gt; Columbia&lt;/span&gt;&lt;span&gt;  |&lt;/span&gt;&lt;span&gt; NULL&lt;/span&gt;&lt;span&gt;      |&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;|&lt;/span&gt;&lt;span&gt; Dr.&lt;/span&gt;&lt;span&gt; Scott&lt;/span&gt;&lt;span&gt; |&lt;/span&gt;&lt;span&gt; NULL&lt;/span&gt;&lt;span&gt;      |&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;+-----------+-----------+&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;3&lt;/span&gt;&lt;span&gt; rows&lt;/span&gt;&lt;span&gt; in&lt;/span&gt;&lt;span&gt; set&lt;/span&gt;&lt;span&gt; (0.00 &lt;/span&gt;&lt;span&gt;sec&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Schema overriding has &lt;a href="https://www.dolthub.com/blog/2024-03-22-schema-overriding/#limitations"&gt;some limits&lt;/a&gt;, but it’s a
handy feature to map older data into a different schema.&lt;/p&gt;
&lt;h2 id="using-orms-with-non-versioned-data"&gt;Using ORMs with Non-Versioned Data&lt;a class="anchor-link" aria-label="Link to heading" href="#using-orms-with-non-versioned-data"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Dolt allows you to create isolated branches where you can stage changes or run experiments. These branches are isolated and don’t interfere with other branches. But what happens when you have data that needs to be accessible across all branches and shouldn’t be versioned? One example of a good candidate is a table that tracks all visits to a site. If this data was versioned, then you could run into spurious merge conflicts when merging between branches that have both updated the table. For rapidly growing data like this, it could also generate noise in diffs and history logs that could make other data changes harder to see. If your app has data like this, where there’s really no need to ever go back and see the historical versions, or audit how the data changed, then it’s a good candidate for using Dolt’s nonlocal table support.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.dolthub.com/blog/2025-10-06-nonlocal-tables/"&gt;Nonlocal tables&lt;/a&gt; let you configure tables to behave like global tables and be accessible from any branch, without being included in versioning. To use nonlocal tables, define a &lt;code&gt;dolt_nonlocal_tables&lt;/code&gt; configuration that maps certain table names to a target reference (like a branch). From that point on, queries for those tables will resolve to the designated reference across all branches.&lt;/p&gt;
&lt;p&gt;Let’s test out nonlocal tables. First, create a new directory, initialize it as a Dolt database, and launch a SQL shell:&lt;/p&gt;
&lt;pre class="astro-code github-dark" tabindex="0" data-language="shell"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span&gt;mkdir&lt;/span&gt;&lt;span&gt; dolt-nonlocal-demo&lt;/span&gt;&lt;span&gt; &amp;#x26;&amp;#x26; &lt;/span&gt;&lt;span&gt;cd&lt;/span&gt;&lt;span&gt; dolt-nonlocal-demo&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;dolt&lt;/span&gt;&lt;span&gt; init&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;dolt&lt;/span&gt;&lt;span&gt; sql&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Next, create the nonlocal table. In this example, we have a table that contains global configuration, and we want
that configuration data to be available to all branches and not versioned with our data. We use a &lt;code&gt;global_&lt;/code&gt; prefix
for the table name, which isn’t required, but is a good pattern to make it more obvious that this table holds
global data and is accessible from all branches.&lt;/p&gt;
&lt;pre class="astro-code github-dark" tabindex="0" data-language="sql"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span&gt;CREATE&lt;/span&gt;&lt;span&gt; TABLE&lt;/span&gt;&lt;span&gt; global_app_config&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;  config_key   &lt;/span&gt;&lt;span&gt;VARCHAR&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;100&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;NOT NULL&lt;/span&gt;&lt;span&gt; PRIMARY KEY&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;  config_value &lt;/span&gt;&lt;span&gt;VARCHAR&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;100&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;NOT NULL&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;INSERT INTO&lt;/span&gt;&lt;span&gt; global_app_config &lt;/span&gt;&lt;span&gt;VALUES&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;  (&lt;/span&gt;&lt;span&gt;'theme'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;'light'&lt;/span&gt;&lt;span&gt;),&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;  (&lt;/span&gt;&lt;span&gt;'feature_x_enabled'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;'false'&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now we need to tell Dolt that the new &lt;code&gt;global_app_config&lt;/code&gt; table should be a nonlocal table by inserting a new
row into the &lt;code&gt;dolt_nonlocal_tables&lt;/code&gt; system table. Nonlocal tables are
also “ignored” by default (meaning Dolt won’t version them), but it’s good practice to also explicitly add them
to &lt;a href="https://www.dolthub.com/blog/2023-05-03-using-dolt_ignore-to-prevent-accidents/"&gt;the &lt;code&gt;dolt_ignore&lt;/code&gt; system table&lt;/a&gt;.
Note also that after we make changes to &lt;code&gt;dolt_nonlocal_tables&lt;/code&gt; and &lt;code&gt;dolt_ignore&lt;/code&gt;, we need to stage those changes and commit them.&lt;/p&gt;
&lt;pre class="astro-code github-dark" tabindex="0" data-language="sql"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span&gt;INSERT INTO&lt;/span&gt;&lt;span&gt; dolt_nonlocal_tables (table_name, target_ref, options)&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;    VALUES&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;'global_*'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;'main'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;'immediate'&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;-- Optional but recommended: ensure these tables never get staged/committed by accident&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;INSERT INTO&lt;/span&gt;&lt;span&gt; dolt_ignore (pattern, ignored) &lt;/span&gt;&lt;span&gt;VALUES&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;'global_*'&lt;/span&gt;&lt;span&gt;, true);&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;-- Commit the configuration so *new branches inherit it*&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;CALL&lt;/span&gt;&lt;span&gt; DOLT_ADD(&lt;/span&gt;&lt;span&gt;'dolt_nonlocal_tables'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;'dolt_ignore'&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;CALL&lt;/span&gt;&lt;span&gt; DOLT_COMMIT(&lt;/span&gt;&lt;span&gt;'-m'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;'Configure global_* as nonlocal tables on main (immediate), and ignore them'&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We’ve got a nonlocal table configured now, so we can test out how it works. We’re going to check out a new branch,
make some changes to &lt;code&gt;global_app_config&lt;/code&gt;, then verify that those changes are visible from all branches.&lt;/p&gt;
&lt;pre class="astro-code github-dark" tabindex="0" data-language="sql"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span&gt;CALL&lt;/span&gt;&lt;span&gt; DOLT_CHECKOUT(&lt;/span&gt;&lt;span&gt;'-b'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;'feature/ui'&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;UPDATE&lt;/span&gt;&lt;span&gt; global_app_config&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;    SET&lt;/span&gt;&lt;span&gt; config_value &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; 'true'&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;    WHERE&lt;/span&gt;&lt;span&gt; config_key &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; 'feature_x_enabled'&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here is the updated data in our &lt;code&gt;global_app_config&lt;/code&gt; table:&lt;/p&gt;
&lt;pre class="astro-code github-dark" tabindex="0" data-language="sql"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span&gt;SELECT&lt;/span&gt;&lt;span&gt; *&lt;/span&gt;&lt;span&gt; FROM&lt;/span&gt;&lt;span&gt; global_app_config &lt;/span&gt;&lt;span&gt;ORDER BY&lt;/span&gt;&lt;span&gt; config_key;&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt;-------------------+--------------+&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;| config_key        | config_value |&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt;-------------------+--------------+&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;| feature_x_enabled | true         |&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;| theme             | light        |&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt;-------------------+--------------+&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;2&lt;/span&gt;&lt;span&gt; rows&lt;/span&gt;&lt;span&gt; in&lt;/span&gt;&lt;span&gt; set&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;00&lt;/span&gt;&lt;span&gt; sec) &lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If we look at &lt;code&gt;dolt_status&lt;/code&gt;, we see those changes to &lt;code&gt;global_app_config&lt;/code&gt; are not listed as uncommitted changes, since
this table isn’t included in versioning.&lt;/p&gt;
&lt;pre class="astro-code github-dark" tabindex="0" data-language="sql"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span&gt;SELECT&lt;/span&gt;&lt;span&gt; *&lt;/span&gt;&lt;span&gt; FROM&lt;/span&gt;&lt;span&gt; dolt_status;&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;Empty&lt;/span&gt;&lt;span&gt; set&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;00&lt;/span&gt;&lt;span&gt; sec)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Finally, let’s switch to a different branch, and confirm that the data we see in &lt;code&gt;global_app_config&lt;/code&gt; matches what we
updated in the other branch:&lt;/p&gt;
&lt;pre class="astro-code github-dark" tabindex="0" data-language="sql"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span&gt;CALL&lt;/span&gt;&lt;span&gt; DOLT_CHECKOUT(&lt;/span&gt;&lt;span&gt;'main'&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;SELECT&lt;/span&gt;&lt;span&gt; *&lt;/span&gt;&lt;span&gt; FROM&lt;/span&gt;&lt;span&gt; global_app_config &lt;/span&gt;&lt;span&gt;ORDER BY&lt;/span&gt;&lt;span&gt; config_key;&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt;-------------------+--------------+&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;| config_key        | config_value |&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt;-------------------+--------------+&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;| feature_x_enabled | true         |&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;| theme             | light        |&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt;-------------------+--------------+&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;2&lt;/span&gt;&lt;span&gt; rows&lt;/span&gt;&lt;span&gt; in&lt;/span&gt;&lt;span&gt; set&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;00&lt;/span&gt;&lt;span&gt; sec) &lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id="connecting-to-branches"&gt;Connecting to Branches&lt;a class="anchor-link" aria-label="Link to heading" href="#connecting-to-branches"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Dolt allows you to reference a specific branch in a database by using syntax of the form &lt;code&gt;&amp;#x3C;db_name&gt;/&amp;#x3C;branch_name&gt;&lt;/code&gt;. This is useful in queries (e.g. you can read from a different branch by specifying this syntax: &lt;code&gt;SELECT * FROM mydb/mybranch.mytable;&lt;/code&gt;), and is particularly useful in database connection strings. If you want all your database connections to automatically connect to a specific branch, you can use this syntax in the connection string, and then not have to worry about calling &lt;code&gt;dolt_checkout()&lt;/code&gt;, which also saves you a round trip to the database. This also makes it easy for you to move all your app’s connections to a different branch during testing.&lt;/p&gt;
&lt;p&gt;How you specify that database connection string varies by the client you’re using. The general, MySQL-compatible form is:&lt;/p&gt;
&lt;pre class="astro-code github-dark" tabindex="0" data-language="plaintext"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span&gt;user:password@tcp(localhost:3306)/mydb/mybranch&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here’s what it looks like in Python, with SQLAlchemy:&lt;/p&gt;
&lt;pre class="astro-code github-dark" tabindex="0" data-language="python"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span&gt;engine &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; create_engine(&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;    "mysql+pymysql://user:password@localhost:3306/mydb/mybranch"&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And here’s an example with .NET’s Entity Framework Core:&lt;/p&gt;
&lt;pre class="astro-code github-dark" tabindex="0" data-language="csharp"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span&gt;options.&lt;/span&gt;&lt;span&gt;UseMySql&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;    "server=localhost;user=user;password=password;database=mydb/mydb"&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;    ServerVersion.&lt;/span&gt;&lt;span&gt;AutoDetect&lt;/span&gt;&lt;span&gt;(connectionString)&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For more information about how to specify a branch-specific connection string in different ORMs,
&lt;a href="https://docs.dolthub.com/guides/dolt-tested-apps#frameworks-orms"&gt;check out the blogs and sample code we provide for various ORMs&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="using-connection-pooling"&gt;Using Connection Pooling&lt;a class="anchor-link" aria-label="Link to heading" href="#using-connection-pooling"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;ORMs generally use a connection pool to avoid the costly overhead of repeatedly establishing new connections to your database. When a connection is released back to the pool, the pool is responsible for sending a message to the database over that connection to reset its state. This ensures that no session state leaks between threads, for example if one session assigned session variables with customer information, you wouldn’t want another thread to be able to read that other customer’s private data just because it’s reusing the same connection. Unfortunately, not all connection pools will send this reset session request. The &lt;a href="https://github.com/go-sql-driver/mysql/issues/1273"&gt;Go MySQL driver maintainers won’t send this message when a connection is returned to a pool&lt;/a&gt; because they worry it will affect performance (although a concurrent process could reset the session asynchronously and make the performance impact minimal). For Dolt, this can be a bigger problem, because the branch you have checked out is also part of the session state. This means one thread could grab a connection, change its branch, then return it to the pool for another unsuspecting thread to pick up and be unaware that it’s not pointing at the branch it expects.&lt;/p&gt;
&lt;p&gt;There are a few ways to deal with this. Some applications won’t need to switch branches, so they don’t need to worry about this concern. For applications that &lt;strong&gt;do&lt;/strong&gt; need to work with multiple branches there are two approaches we recommend to customers:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Use one connection pool per branch&lt;/strong&gt; – if your application only works with a small number of branches, then you can have a connection pool dedicated to each branch, using the branch connection approach below.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Run &lt;code&gt;dolt_checkout()&lt;/code&gt; at the start of your logic&lt;/strong&gt; – if your application dynamically creates branches, then your best bet may be to ensure you call &lt;code&gt;dolt_checkout()&lt;/code&gt; at the start of your logic to ensure you’re on the expected branch. Alternatively, you can run &lt;code&gt;SELECT active_branch()&lt;/code&gt; to sanity check what branch you’re on. Note that the behavior here is somewhat specific to the ORM you are using. Some ORMs let you have a connection and work with it over multiple queries until you explicitly release it, while other ORMs will automatically select a connection for you and may still choose a different connection in the pool for two queries that execute next to each other in your application logic.&lt;/li&gt;
&lt;/ul&gt;
&lt;h1 id="reflection-and-working-with-dolts-system-tables"&gt;Reflection and Working with Dolt’s System Tables&lt;a class="anchor-link" aria-label="Link to heading" href="#reflection-and-working-with-dolts-system-tables"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;Some ORMs rely heavily on schema reflection (sometimes called introspection) to understand the structure of a database at runtime. Reflection allows an ORM to discover tables, columns, types, and relationships dynamically, rather than requiring everything to be defined statically up front. Dolt supports schema reflection via &lt;code&gt;information_schema&lt;/code&gt;, just like MySQL does. This means ORMs can reflect on tables in Dolt just as they would in MySQL.&lt;/p&gt;
&lt;p&gt;In ORMs that support reflection, it can also be used to reflect on &lt;a href="https://docs.dolthub.com/sql-reference/version-control/dolt-system-tables"&gt;system tables&lt;/a&gt;, including Dolt system tables that expose version-control features and metadata (e.g. commit logs, diffs, branch metadata). The best approach to query these system tables depends on the capabilities of the ORM that you’re using.&lt;/p&gt;
&lt;p&gt;In Python, for example, SQLAlchemy supports using reflection to dynamically construct table model definitions. Here’s an example with SQLAlchemy for querying &lt;a href="https://docs.dolthub.com/sql-reference/version-control/dolt-system-tables#dolt_log"&gt;the &lt;code&gt;dolt_log&lt;/code&gt; system table&lt;/a&gt;, which shows you the commit history for your current branch. Notice that we don’t have to declare the structure of the &lt;code&gt;dolt_log&lt;/code&gt; system table. Instead, we can specify &lt;code&gt;autoload_with=engine&lt;/code&gt; to get SQLAlchemy to use reflection to discover the table’s schema.&lt;/p&gt;
&lt;pre class="astro-code github-dark" tabindex="0" data-language="python"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span&gt;    # Instantiate the dolt_log table. Using "autoload_with=engine" causes SQLAlchemy to &lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;    # load the column definitions from the database itself, using reflection.  &lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;    dolt_log &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; Table(&lt;/span&gt;&lt;span&gt;"dolt_log"&lt;/span&gt;&lt;span&gt;, MetaData(), &lt;/span&gt;&lt;span&gt;autoload_with&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;engine)&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;    stmt &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; select(dolt_log.c.commit_hash,&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;                  dolt_log.c.committer,&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;                  dolt_log.c.message&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;                  ).order_by(dolt_log.c.date.desc())&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;    with&lt;/span&gt;&lt;span&gt; engine.connect() &lt;/span&gt;&lt;span&gt;as&lt;/span&gt;&lt;span&gt; conn:&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;        results &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; conn.execute(stmt)&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;        for&lt;/span&gt;&lt;span&gt; row &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;span&gt; results:&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;            # Process results... &lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Other ORMs, like .NET’s Entity Framework Core, rely on static models and less on reflection. Since the &lt;code&gt;dolt_log&lt;/code&gt; system table has a static schema, you could define a model for it, then use the standard ORM tooling to query and process results using that model type.&lt;/p&gt;
&lt;p&gt;Here’s a sample model for the fields our app needs from the &lt;code&gt;dolt_log&lt;/code&gt; system table:&lt;/p&gt;
&lt;pre class="astro-code github-dark" tabindex="0" data-language="csharp"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; class&lt;/span&gt;&lt;span&gt; DoltCommit&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;    public&lt;/span&gt;&lt;span&gt; string&lt;/span&gt;&lt;span&gt; CommitHash&lt;/span&gt;&lt;span&gt; { &lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt;; &lt;/span&gt;&lt;span&gt;set&lt;/span&gt;&lt;span&gt;; }&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;    public&lt;/span&gt;&lt;span&gt; string&lt;/span&gt;&lt;span&gt; Committer&lt;/span&gt;&lt;span&gt; { &lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt;; &lt;/span&gt;&lt;span&gt;set&lt;/span&gt;&lt;span&gt;; }&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;    public&lt;/span&gt;&lt;span&gt; string&lt;/span&gt;&lt;span&gt; Message&lt;/span&gt;&lt;span&gt; { &lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt;; &lt;/span&gt;&lt;span&gt;set&lt;/span&gt;&lt;span&gt;; }&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;    public&lt;/span&gt;&lt;span&gt; DateTime&lt;/span&gt;&lt;span&gt; CommitDate&lt;/span&gt;&lt;span&gt; { &lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt;; &lt;/span&gt;&lt;span&gt;set&lt;/span&gt;&lt;span&gt;; }&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We have to register this model with the framework so it knows it’s associated with the &lt;code&gt;dolt_log&lt;/code&gt; table:&lt;/p&gt;
&lt;pre class="astro-code github-dark" tabindex="0" data-language="csharp"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span&gt;using&lt;/span&gt;&lt;span&gt; Microsoft&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;EntityFrameworkCore&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; class&lt;/span&gt;&lt;span&gt; AppDbContext&lt;/span&gt;&lt;span&gt; : &lt;/span&gt;&lt;span&gt;DbContext&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;    public&lt;/span&gt;&lt;span&gt; DbSet&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;DoltCommit&lt;/span&gt;&lt;span&gt;&gt; &lt;/span&gt;&lt;span&gt;DoltLog&lt;/span&gt;&lt;span&gt; { &lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt;; &lt;/span&gt;&lt;span&gt;set&lt;/span&gt;&lt;span&gt;; }&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;    protected&lt;/span&gt;&lt;span&gt; override&lt;/span&gt;&lt;span&gt; void&lt;/span&gt;&lt;span&gt; OnModelCreating&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;ModelBuilder&lt;/span&gt;&lt;span&gt; modelBuilder&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;    {&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;        modelBuilder&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;            .&lt;/span&gt;&lt;span&gt;Entity&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;DoltCommit&lt;/span&gt;&lt;span&gt;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;            .&lt;/span&gt;&lt;span&gt;HasNoKey&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;            .&lt;/span&gt;&lt;span&gt;ToTable&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;"dolt_log"&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;        base&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;OnModelCreating&lt;/span&gt;&lt;span&gt;(modelBuilder);&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;From here, we can use that model to query the &lt;code&gt;dolt_log&lt;/code&gt; system table:&lt;/p&gt;
&lt;pre class="astro-code github-dark" tabindex="0" data-language="csharp"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span&gt;using&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;var&lt;/span&gt;&lt;span&gt; context&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; AppDbContext&lt;/span&gt;&lt;span&gt;())&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;    var&lt;/span&gt;&lt;span&gt; recentCommits&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; context.DoltLog&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;        .&lt;/span&gt;&lt;span&gt;OrderByDescending&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;c&lt;/span&gt;&lt;span&gt; =&gt;&lt;/span&gt;&lt;span&gt; c.CommitDate)&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;        .&lt;/span&gt;&lt;span&gt;Take&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;10&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;        .&lt;/span&gt;&lt;span&gt;ToList&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;    foreach&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;var&lt;/span&gt;&lt;span&gt; commit&lt;/span&gt;&lt;span&gt; in&lt;/span&gt;&lt;span&gt; recentCommits)&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;    {&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;        Console.&lt;/span&gt;&lt;span&gt;WriteLine&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;$"&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;commit&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;CommitHash&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;commit&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Message&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;"&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Finally, there’s always an escape hatch if you really need to execute raw SQL without using a model. For example, you may want to execute a stored procedure, like &lt;code&gt;dolt_reset()&lt;/code&gt; or call a &lt;a href="https://docs.dolthub.com/sql-reference/version-control/dolt-sql-functions"&gt;function&lt;/a&gt;. ORMs almost always provide a way to access a connection and execute a SQL statement directly.&lt;/p&gt;
&lt;p&gt;For more examples and other ORMs, &lt;a href="https://docs.dolthub.com/guides/dolt-tested-apps#frameworks-orms"&gt;check out our integration documentation&lt;/a&gt; to find blogs and source code.&lt;/p&gt;
&lt;h1 id="conclusion"&gt;Conclusion&lt;a class="anchor-link" aria-label="Link to heading" href="#conclusion"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;ORMs can make it easier to build an application that works with a database, although they can also bring in some complexity, so understanding how they work is critical. Dolt is MySQL-compatible, so any ORM that works with MySQL will work with Dolt. We’ve personally &lt;a href="https://docs.dolthub.com/guides/dolt-tested-apps#frameworks-orms"&gt;tested over a dozen ORMs with Dolt, and documented them in blog posts and with sample code&lt;/a&gt;. Dolt features like &lt;a href="https://www.dolthub.com/blog/2024-03-22-schema-overriding/"&gt;schema overrides&lt;/a&gt; and &lt;a href="https://www.dolthub.com/blog/2025-10-06-nonlocal-tables/"&gt;nonlocal tables&lt;/a&gt; can help make it easier to use Dolt’s versioning features with ORMs.&lt;/p&gt;
&lt;p&gt;If you want to discuss the ins and outs of ORMs, suggest another ORM for us to test out, or if you’re just curious about &lt;a href="https://www.dolthub.com/blog/2022-08-04-database-versioning/#what-is-database-versioning"&gt;version-controlled, relational databases&lt;/a&gt;, then stop by the &lt;a href="https://discord.gg/gqr7K4VNKe"&gt;DoltHub Discord&lt;/a&gt; and say hello!&lt;/p&gt;</content:encoded>
      <dc:creator>Jason Fulghum</dc:creator>
      <category>reference</category>
      <category>dolt</category>
    </item>
    <item>
      <title>Making Featured Images: Agentic Art Direction</title>
      <link>https://dolthub.com/blog/2026-01-16-agentic-art-direction/</link>
      <guid isPermaLink="true">https://dolthub.com/blog/2026-01-16-agentic-art-direction/</guid>
      <description>The process of creating daily Dolt blog featured images using multiple AIs.</description>
      <pubDate>Fri, 16 Jan 2026 00:00:00 GMT</pubDate>
      <content:encoded>&lt;p&gt;The team here at DoltHub puts out a blog post every day, and each one needs a featured image. That means I get to run a small creative process daily to make it happen.&lt;/p&gt;
&lt;p&gt;While I’m the only marketing person here, that might sound like a challenge, but over the past few months, I’ve built a system that really works. In fact, I look forward to it now. I think of it as running a two-AI creative team from concept to final image, and it’s become one of my favorite parts of the job. This article explains my process.&lt;/p&gt;
&lt;h2 id="whats-a-featured-image"&gt;What’s a Featured Image?&lt;a class="anchor-link" aria-label="Link to heading" href="#whats-a-featured-image"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Featured images are the images that show up on social networks when you share a link to an article. When someone posts our blog to LinkedIn, Twitter, or Discord, the featured image is what people see before they click.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://discord.com/channels/746150696465727668/746150696465727671/1461109451602788492"&gt;&lt;img src="https://static.dolthub.com/blogimages/featured-image-discord-example.png/a9b9c2de2765b4dcd7c6a99e410d577ecb26300c933df4b1f7df7d9c5eb1bd2d.webp" alt="Featured image Discord Example"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;It’s also what appears at the top of the blog post itself.&lt;/p&gt;
&lt;p&gt;So when I’m making these images, I’m thinking about two contexts: how it looks in someone’s feed, and how it looks when you land on the article. Both need to work.&lt;/p&gt;
&lt;p&gt;If you want the technical details on how featured images get added to our blog metadata, we wrote about that &lt;a href="https://www.dolthub.com/blog/2022-06-08-how-to-add-images-to-metadata/"&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="my-helpers"&gt;My Helpers&lt;a class="anchor-link" aria-label="Link to heading" href="#my-helpers"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Claude&lt;/strong&gt; is my creative strategist. I built a Claude project with a master prompt that includes everything about DoltHub’s visual brand, which includes our mascot (I call him Dolty in my head), luminous color palette, favorite metaphors, and style of communication. Claude helps brainstorm concepts, suggests options, tests ideas, and spots problems before they become images.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Gemini&lt;/strong&gt; (and seldomly the less efficient ChatGPT) is my artist. But Gemini is a very talented artist who doesn’t always read the brief. It makes beautiful images that sometimes have nothing to do with what I asked for. Managing Gemini is its own skill.&lt;/p&gt;
&lt;p&gt;I’m the creative director. I read the blogs, figure out what story the image should tell, guide the concepts, direct the art, and make the final decision on which image to prepare for copy and collaboration with whoever is writing the blog.&lt;/p&gt;
&lt;h2 id="the-four-questions"&gt;The Four Questions&lt;a class="anchor-link" aria-label="Link to heading" href="#the-four-questions"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Each blog is different. Some celebrate wins, some dive deep into technical topics, and some are just for fun. Before I start working with my AI team, I stick to these four guiding questions to keep things consistent:&lt;/p&gt;
&lt;h3 id="what-is-this-blog-actually-saying"&gt;What is this blog actually saying?&lt;a class="anchor-link" aria-label="Link to heading" href="#what-is-this-blog-actually-saying"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;I read the blog and make sure I understand the technical details. I can’t direct a visual for something I don’t get, and I can’t expect Claude or Gemini to get it right if I haven’t figured it out myself.&lt;/p&gt;
&lt;h3 id="whats-the-tone"&gt;What’s the tone?&lt;a class="anchor-link" aria-label="Link to heading" href="#whats-the-tone"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The tone, technical depth, and author’s intent help me decide which direction to take.&lt;/p&gt;
&lt;p&gt;When we announced that &lt;a href="https://www.dolthub.com/blog/2025-12-04-dolt-is-as-fast-as-mysql/"&gt;Dolt matches MySQL performance&lt;/a&gt; after five years, we were celebrating a big achievement, like mounting a summit. When Dustin wrote about his &lt;a href="https://www.dolthub.com/blog/2026-01-14-doltcash-part-two-still-vibing/"&gt;vibe-accounting experiment&lt;/a&gt; that was barely holding it together, it was playful, self-deprecating, and humorous. When Jason did a &lt;a href="https://www.dolthub.com/blog/2025-12-15-dolt-enabled-app-builder/"&gt;technical comparison with Neon&lt;/a&gt;, the tone was competitive, clear, and enablement-focused.&lt;/p&gt;
&lt;h3 id="what-visual-reinforces-the-headline"&gt;What visual reinforces the headline?&lt;a class="anchor-link" aria-label="Link to heading" href="#what-visual-reinforces-the-headline"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Another rule: the image should always reinforce the headline. The image needs to make the same point as the headline, just visually.&lt;/p&gt;
&lt;p&gt;These questions are my way to check for consistency. They help me make sure every image, no matter the blog, author, or topic, still feels like an authentic DoltHub Blog.&lt;/p&gt;
&lt;h2 id="the-master-prompt"&gt;The Master Prompt&lt;a class="anchor-link" aria-label="Link to heading" href="#the-master-prompt"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The Claude project I built is the backbone of the whole thing. There are a couple of layers to this, but the bottom line is I designed a creative strategist with specific expertise.&lt;/p&gt;
&lt;p&gt;Here’s part of it:&lt;/p&gt;
&lt;pre class="astro-code github-dark" tabindex="0" data-language="plaintext"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span&gt;You are an IMAGE CONCEPT CREATOR for DoltHub.&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;You generate and evaluate visual concepts for landing pages, docs,&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;tweets, and blog posts. You think in terms of Dolt's existing&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;"Agent Universe" art style, and you extend that world with new scenes.&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;Your priorities are: CLARITY of concept → FIT with the article/message&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;→ ON-BRAND VISUAL STYLE → VARIETY of tone.&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The prompt includes our entire visual world: blue agents with glowing teal antennas, vivid rim lighting, Git-tree metaphors, “good vs bad” split scenes, character specs, color codes, and the patterns we use again and again.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://static.dolthub.com/blogimages/claude-project-instructions.webp/fefb6a3204e7efc26de71fb60de8d2d81415c0eb65ace8b98ed7400e8fd9352b.webp" alt="Claude Master Prompt"&gt;&lt;/p&gt;
&lt;p&gt;I also added evaluation criteria like “Rate correctness from 0-10,” “Does it avoid misleading claims?” and “Propose 1-3 improvements.” So when I ask for concepts, I get ideas with built-in quality checks. Claude explains its suggestions and points out problems before I move forward. Sometimes I agree, but sometimes I don’t so I update the memory or the prompt.&lt;/p&gt;
&lt;p&gt;The best part is that after three months, the project remembers how I work:&lt;/p&gt;
&lt;pre class="astro-code github-dark" tabindex="0" data-language="plaintext"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span&gt;I follow a structured creative process, starting with concept generation,&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;moving through iterative visual refinement, and concluding with&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;platform-specific content adaptation. He typically requests multiple&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;options (8+ image concepts, various copy approaches), then collaborates&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;to refine the strongest direction through detailed feedback cycles.&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;Technical accuracy is paramount - James consistently requests&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;fact-checking and verification against source material.&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I designed the system, and now it understands how I work.&lt;/p&gt;
&lt;h2 id="concept-development"&gt;Concept Development&lt;a class="anchor-link" aria-label="Link to heading" href="#concept-development"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Here’s how a typical session goes.&lt;/p&gt;
&lt;p&gt;I add the blog and my notes from the four questions. Using the master prompt, Claude generates several image concepts — some funny, some serious, some very literal, and some abstract.&lt;/p&gt;
&lt;p&gt;Then, I step in as director. I focus on the concepts I like, ask for variations, and challenge ideas, asking myself:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Is this actually accurate to what Dolt does and what the blog is trying to say?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;Does this metaphor hold up if the reader knows Neon?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;Why is this concept better than the others for &lt;strong&gt;this&lt;/strong&gt; blog?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id="example-app-builders-deserve-better-databases"&gt;Example: “App Builders Deserve Better Databases”&lt;a class="anchor-link" aria-label="Link to heading" href="#example-app-builders-deserve-better-databases"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;a href="https://www.dolthub.com/blog/2025-12-15-dolt-enabled-app-builder/"&gt;Jason’s blog&lt;/a&gt; compares how Dolt and Neon handle branching. The key point: Neon lets you create branches that you can’t merge them back, leading to dead ends, but Dolt gives you the full Git workflow, allowing you to press on without fear!&lt;/p&gt;
&lt;p&gt;I steered the concepts toward a racing metaphor. The final idea was one Dolty running on a glowing track, while another agent crashes into a brick wall labeled “no merge.”&lt;/p&gt;
&lt;p&gt;The metaphor fits well: Neon’s branches hit a wall, but Dolt keeps you moving forward with the ability to merge. If you want more on this topic, read Tim’s blog on &lt;a href="https://www.dolthub.com/blog/2026-01-07-how-to-version-control-a-database/"&gt;How to Version Control a Database&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.dolthub.com/blog/2025-12-15-dolt-enabled-app-builder/"&gt;&lt;img src="https://static.dolthub.com/blogimages/app-builders-deserve-better-databases.png/51b78f7a648d9ebf89d77c95e773813813bb1883ebc61dea452a4c191e9bc294.webp" alt="App Builders Deserve Better Databases"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;This part is fun — bouncing ideas around, narrowing down the concept, and reaching that moment when it all clicks. Claude is a great collaborator. It challenges me when I’m off, suggests new angles, and catches technical mistakes before they show up in the visuals.&lt;/p&gt;
&lt;h2 id="art-direction-the-grind"&gt;Art Direction (The Grind)&lt;a class="anchor-link" aria-label="Link to heading" href="#art-direction-the-grind"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Now comes the part that really tests my patience.&lt;/p&gt;
&lt;p&gt;Gemini is talented but also unpredictable. Once I have a solid concept from the strategy phase, I move to image generation — and that’s when things can get strange.&lt;/p&gt;
&lt;p&gt;AI image generators do stuff like:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Wrong style (suddenly cartoonish when I need cinematic)&lt;/li&gt;
&lt;li&gt;Off-brand mascot (wrong antenna glow, wrong proportions)&lt;/li&gt;
&lt;li&gt;That “AI concept art” look where everything feels like a mood board&lt;/li&gt;
&lt;li&gt;Random details I never asked for&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;My job is to keep refining the image until it matches what I have in mind.&lt;/p&gt;
&lt;h3 id="case-study-understanding-dolt-directories"&gt;Case Study: “Understanding Dolt Directories”&lt;a class="anchor-link" aria-label="Link to heading" href="#case-study-understanding-dolt-directories"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;a href="https://www.dolthub.com/blog/2025-12-29-directories/"&gt;Tim’s blog&lt;/a&gt; explains how Dolt uses directories on your filesystem. Educational stuff. Helps people understand what happens when they run &lt;code&gt;dolt init&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;My concept: Dolty examining a filesystem tree, focusing on the specific folders from Tim’s examples — &lt;code&gt;directories/&lt;/code&gt;, &lt;code&gt;one_more_db/&lt;/code&gt;, &lt;code&gt;and_a_third_db/&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;It sounds simple.&lt;/p&gt;
&lt;p&gt;Here’s what actually happened:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Round 1-2:&lt;/strong&gt; Gemini threw in &lt;code&gt;noms/&lt;/code&gt;, &lt;code&gt;refs/&lt;/code&gt;, &lt;code&gt;chunks/&lt;/code&gt; folders. Those are internal Dolt storage details. Tim mentions them, but they’re not the point. I needed the high-level structure.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Round 3:&lt;/strong&gt; Used &lt;code&gt;~/dolt/&lt;/code&gt; as the root directory. Tim’s example uses &lt;code&gt;directories/&lt;/code&gt;. Wrong.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Round 4:&lt;/strong&gt; Added a floating “subdirectories” label. No idea why. Cut it.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Round 5:&lt;/strong&gt; Put &lt;code&gt;.dolt&lt;/code&gt; as the root directory. That’s backwards. The &lt;code&gt;.dolt&lt;/code&gt; folder lives inside the database directory, not above it.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Round 6:&lt;/strong&gt; Generic names like &lt;code&gt;src&lt;/code&gt;, &lt;code&gt;data&lt;/code&gt;, &lt;code&gt;docs&lt;/code&gt;. Now it looked like a stock filesystem image. Lost the whole point.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Round 7:&lt;/strong&gt; Finally. &lt;code&gt;directories/&lt;/code&gt; at top, &lt;code&gt;.dolt&lt;/code&gt;, &lt;code&gt;one_more_db/&lt;/code&gt;, and &lt;code&gt;and_a_third_db/&lt;/code&gt; as children. Matches Tim’s actual examples.&lt;/p&gt;
&lt;p&gt;It took seven rounds with the same concept throughout. The strategy phase was quick, but the art direction was a real grind.&lt;/p&gt;
&lt;p&gt;This is what AI image generation is really like. You don’t just press a button and get magic. You’re directing a fast but inconsistent artist who needs constant guidance. From what I hear from my software engineer colleagues, this matches the experience with AI-generated code.&lt;/p&gt;
&lt;p&gt;I’ve learned to expect this. The concept might come together in ten minutes, but the final image can take an hour of back-and-forth.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://static.dolthub.com/blogimages/directories-journey.webp/1a8e814918c64f63d7d7df42f64514441d79cb1c25a7d19310fb65040d5b8dfc.webp" alt="Directories Journey"&gt;&lt;/p&gt;
&lt;h2 id="quality-control"&gt;Quality Control&lt;a class="anchor-link" aria-label="Link to heading" href="#quality-control"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I check everything against my framework:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Technical accuracy.&lt;/strong&gt; The Directories image has to show the folder structure Tim actually describes. If an engineer reads the blog and then looks at the image, they shouldn’t think “wait, that’s wrong.”&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Metaphor integrity.&lt;/strong&gt; Racing track = database workflows. Brick wall = no merge capability. Summit = milestone reached. The metaphors have to map to real things.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Brand consistency.&lt;/strong&gt; Dolty has the right glow. The lighting is that iridescent teal palette. Cinematic feel, not clip-art.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Tone match.&lt;/strong&gt; Dustin’s DoltCash posts are goofy, so the images can be playful. Tim’s educational posts need to be clear. Jason’s competitive pieces need to feel authoritative.&lt;/p&gt;
&lt;p&gt;This is how I keep quality high across all these different blogs, authors, and styles. The framework keeps everything consistent.&lt;/p&gt;
&lt;h2 id="final-polish-in-canva"&gt;Final Polish in Canva&lt;a class="anchor-link" aria-label="Link to heading" href="#final-polish-in-canva"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Once I have the image locked, everything comes together in Canva.&lt;/p&gt;
&lt;p&gt;Text sizing, title length consistency, subheading spacing, fades, and any last-minute image adjustments all happen in one Canva project. I’ve set up templates that match our blog dimensions and brand guidelines, so adding copy to a new image is pretty quick.&lt;/p&gt;
&lt;p&gt;This step is where the image becomes the actual featured graphic. Gemini gives me the visual. Canva gives me the finished product with headline, subhead, and logo in place.&lt;/p&gt;
&lt;p&gt;Having all the final refinements in one place makes things straightforward. If I need to tweak the title or adjust spacing, it’s a quick edit rather than regenerating the whole image.&lt;/p&gt;
&lt;h2 id="team-sign-off"&gt;Team Sign-Off&lt;a class="anchor-link" aria-label="Link to heading" href="#team-sign-off"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Before anything ships, I send it to whoever at Dolt wrote the blog.&lt;/p&gt;
&lt;p&gt;“Does this match what you wrote? Does the headline work? Is this what you meant?”&lt;/p&gt;
&lt;p&gt;Usually, I get a quick “looks good, ship it.” Sometimes, the author wants the focus to be more on something else. A quick Discord message exchange and a couple of fast tweaks usually solve it.&lt;/p&gt;
&lt;p&gt;While I might be the creative director, the blog’s author has the final say on whether the image represents their work.&lt;/p&gt;
&lt;h2 id="why-i-like-this"&gt;Why I Like This&lt;a class="anchor-link" aria-label="Link to heading" href="#why-i-like-this"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I actually enjoy this process a lot.&lt;/p&gt;
&lt;p&gt;Turning a blog about database directories into a visual story, finding the right metaphor for “Neon branches are dead ends,” and working through seven rounds with Gemini until the image finally matches my vision — all of this is rewarding.&lt;/p&gt;
&lt;p&gt;I built a system that lets one person do the work of a small team. I wrote the master prompt, created the guiding questions, run the concepting, and direct the output. The AIs are collaborators, but I make the decisions.&lt;/p&gt;
&lt;p&gt;When the image, headline, and blog all land the same argument? That’s a good feeling. But when the team, the audience, and the people love it? That’s a great feeling.&lt;/p&gt;
&lt;h2 id="its-a-process-so-if-you-want-to-try"&gt;It’s a Process, so If You Want to Try…&lt;a class="anchor-link" aria-label="Link to heading" href="#its-a-process-so-if-you-want-to-try"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Start by building your strategist.&lt;/strong&gt; Create a master prompt that includes your brand—characters, colors, metaphors, and tone. Don’t make your AI relearn this every time.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Figure out your own questions.&lt;/strong&gt; What keeps your content consistent? My focus is on understanding, tone, audience, and visual reinforcement. Yours might be different, so take time to define them.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Keep strategy and execution separate.&lt;/strong&gt; Use a reasoning model for concepts and an image model for visuals. Each tool has its own strengths.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Be ready for the art direction grind.&lt;/strong&gt; The concept might come together quickly, but the final image could take several rounds. That’s normal.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Stay in control.&lt;/strong&gt; You’re the creative director, so you decide what works, what fits the brand, and what tells the story. Use an image editing tool for final touches.&lt;/p&gt;
&lt;h2 id="wrapping-up"&gt;Wrapping Up&lt;a class="anchor-link" aria-label="Link to heading" href="#wrapping-up"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The featured image is the first thing people notice about your content. Before anyone reads a word, they see the image. It sets expectations, shows the tone, and either supports or weakens your message.&lt;/p&gt;
&lt;p&gt;I built a system that handles this quickly. One person can turn around images the same day, and readers actually connect to the content. The AIs make things faster, the framework keeps things consistent, and my judgment, along with collaboration with the author, makes it all work.&lt;/p&gt;
&lt;p&gt;If you’re building technical content and want to talk visual strategy, or you just want to see more of our little agent universe, swing by our &lt;a href="https://discord.gg/gqr7K4VNKe"&gt;Discord&lt;/a&gt;. Happy to talk about this stuff.&lt;/p&gt;</content:encoded>
      <dc:creator>James Leng</dc:creator>
      <category>ai</category>
    </item>
    <item>
      <title>More Read Performance Wins</title>
      <link>https://dolthub.com/blog/2026-01-06-more-read-performance-wins/</link>
      <guid isPermaLink="true">https://dolthub.com/blog/2026-01-06-more-read-performance-wins/</guid>
      <description>We continue to improve Dolt read performance on Sysbench</description>
      <pubDate>Tue, 06 Jan 2026 00:00:00 GMT</pubDate>
      <content:encoded>&lt;p&gt;Last year, we announced that Dolt reached &lt;a href="https://www.dolthub.com/blog/2025-12-04-dolt-is-as-fast-as-mysql/"&gt;Sysbench performance parity with MySQL&lt;/a&gt; on read and write benchmarks, and followed up with a &lt;a href="https://www.dolthub.com/blog/2025-12-12-how-dolt-got-as-fast-as-mysql/"&gt;breakdown&lt;/a&gt; of the specific performance optimizations.
As promised, we continue to bring more performance optimizations to Dolt.
Today, we’re proud to announce that we’ve hit parity with MySQL on Sysbench read benchmarks!&lt;/p&gt;
&lt;p&gt;&lt;img src="https://static.dolthub.com/blogimages/read_mean_parity.png/56f1c3bb8465cf8fa1dea01011d0d9edf92e68535977c89e8e2948b06552b85f.webp" alt="Read Mean Parity"&gt;&lt;/p&gt;
&lt;p&gt;Here’s a graph of Sysbench read benchmark improvements over the last month:
&lt;img src="https://static.dolthub.com/blogimages/read_mults_2026_01_06.png/1b2e19a11bbec80c9f16687996be709729605dbede5863cec49e84e6d1449b76.webp" alt="Read Mean Parity"&gt;&lt;/p&gt;
&lt;h1 id="fast-path-for-in-filters"&gt;Fast Path for &lt;code&gt;IN&lt;/code&gt; Filters&lt;a class="anchor-link" aria-label="Link to heading" href="#fast-path-for-in-filters"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;This optimization was highlighted as a sneak peek in the previous blog.
When converting filters to indexes, we create a RangeTree that sorts and removes overlaps between any two ranges.
This works great for arbitrary filters, but we may be doing unnecessary work for simpler filters of a specific form.
The benchmark &lt;code&gt;select_random_points&lt;/code&gt; filters a table with a randomly generated &lt;code&gt;IN&lt;/code&gt; query, resulting in a query highly selective and non-contiguous.
Rather than building an entire RangeTree, we can just sort and de-duplicate these point-select queries.&lt;/p&gt;
&lt;p&gt;Relevant Benchmarks:&lt;/p&gt;























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;benchmark&lt;/th&gt;&lt;th&gt;from_latency&lt;/th&gt;&lt;th&gt;to_latency&lt;/th&gt;&lt;th&gt;percent_change&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;oltp_point_select&lt;/td&gt;&lt;td&gt;0.28&lt;/td&gt;&lt;td&gt;0.27&lt;/td&gt;&lt;td&gt;-3.57&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;select_random_points&lt;/td&gt;&lt;td&gt;0.58&lt;/td&gt;&lt;td&gt;0.55&lt;/td&gt;&lt;td&gt;-5.17&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h1 id="avoid-heap-allocations-in-transforminspect-and-transforminspectexpr"&gt;Avoid Heap Allocations in &lt;code&gt;transform.Inspect&lt;/code&gt; and &lt;code&gt;transform.InspectExpr&lt;/code&gt;&lt;a class="anchor-link" aria-label="Link to heading" href="#avoid-heap-allocations-in-transforminspect-and-transforminspectexpr"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;During the analysis process, certain rules traverse &lt;code&gt;sql.Node&lt;/code&gt; and &lt;code&gt;sql.Expression&lt;/code&gt; trees looking for specific nodes or expressions without altering the structure of the trees.
This is mainly done through the &lt;code&gt;transform.Inspect()&lt;/code&gt; and &lt;code&gt;transform.InspectExpr()&lt;/code&gt; functions.
Previously, the inspect functions would be recursively called on the result of &lt;code&gt;node.Children()&lt;/code&gt;.&lt;/p&gt;
&lt;pre class="astro-code github-dark" tabindex="0" data-language="go"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span&gt;func&lt;/span&gt;&lt;span&gt; InspectExpr&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;expr&lt;/span&gt;&lt;span&gt; sql&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Expression&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;f&lt;/span&gt;&lt;span&gt; func&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;sql&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Expression&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;bool&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;bool&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;	children &lt;/span&gt;&lt;span&gt;:=&lt;/span&gt;&lt;span&gt; expr.&lt;/span&gt;&lt;span&gt;Children&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;	for&lt;/span&gt;&lt;span&gt; _, child &lt;/span&gt;&lt;span&gt;:=&lt;/span&gt;&lt;span&gt; range&lt;/span&gt;&lt;span&gt; children {&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;		if&lt;/span&gt;&lt;span&gt; InspectExpr&lt;/span&gt;&lt;span&gt;(child, f) {&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;			return&lt;/span&gt;&lt;span&gt; true&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;		}&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;    if&lt;/span&gt;&lt;span&gt; f&lt;/span&gt;&lt;span&gt;(expr) {&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;        return&lt;/span&gt;&lt;span&gt; true&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;    return&lt;/span&gt;&lt;span&gt; false&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Since the return type of &lt;code&gt;expr.Children()&lt;/code&gt; is &lt;code&gt;[]sql.Expression&lt;/code&gt;, the node/expressions here would escape to the heap.
Fortunately, most nodes have either 1 or 2 children, so we use a type switch for &lt;code&gt;UnaryNode&lt;/code&gt; and &lt;code&gt;BinaryNode&lt;/code&gt; to recursively call &lt;code&gt;Inspect&lt;/code&gt; on the children directly.&lt;/p&gt;
&lt;pre class="astro-code github-dark" tabindex="0" data-language="go"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span&gt;func&lt;/span&gt;&lt;span&gt; InspectExpr&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;expr&lt;/span&gt;&lt;span&gt; sql&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Expression&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;f&lt;/span&gt;&lt;span&gt; func&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;sql&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Expression&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;bool&lt;/span&gt;&lt;span&gt;) (&lt;/span&gt;&lt;span&gt;stop&lt;/span&gt;&lt;span&gt; bool&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;	// Avoid allocating []sql.Expression&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;	switch&lt;/span&gt;&lt;span&gt; e &lt;/span&gt;&lt;span&gt;:=&lt;/span&gt;&lt;span&gt; expr.(&lt;/span&gt;&lt;span&gt;type&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;	case&lt;/span&gt;&lt;span&gt; expression&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;UnaryExpression&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;		if&lt;/span&gt;&lt;span&gt; InspectExpr&lt;/span&gt;&lt;span&gt;(e.&lt;/span&gt;&lt;span&gt;UnaryChild&lt;/span&gt;&lt;span&gt;(), f) {&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;			return&lt;/span&gt;&lt;span&gt; true&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;		}&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;	case&lt;/span&gt;&lt;span&gt; expression&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;BinaryExpression&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;		if&lt;/span&gt;&lt;span&gt; InspectExpr&lt;/span&gt;&lt;span&gt;(e.&lt;/span&gt;&lt;span&gt;Left&lt;/span&gt;&lt;span&gt;(), f) {&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;			return&lt;/span&gt;&lt;span&gt; true&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;		}&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;		if&lt;/span&gt;&lt;span&gt; InspectExpr&lt;/span&gt;&lt;span&gt;(e.&lt;/span&gt;&lt;span&gt;Right&lt;/span&gt;&lt;span&gt;(), f) {&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;			return&lt;/span&gt;&lt;span&gt; true&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;		}&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;	default&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;		children &lt;/span&gt;&lt;span&gt;:=&lt;/span&gt;&lt;span&gt; e.&lt;/span&gt;&lt;span&gt;Children&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;		for&lt;/span&gt;&lt;span&gt; _, child &lt;/span&gt;&lt;span&gt;:=&lt;/span&gt;&lt;span&gt; range&lt;/span&gt;&lt;span&gt; children {&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;			if&lt;/span&gt;&lt;span&gt; InspectExpr&lt;/span&gt;&lt;span&gt;(child, f) {&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;				return&lt;/span&gt;&lt;span&gt; true&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;			}&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;		}&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;	}&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;	if&lt;/span&gt;&lt;span&gt; f&lt;/span&gt;&lt;span&gt;(expr) {&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;		return&lt;/span&gt;&lt;span&gt; true&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;	}&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;	return&lt;/span&gt;&lt;span&gt; false&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Relevant Benchmarks:&lt;/p&gt;









































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;benchmark&lt;/th&gt;&lt;th&gt;from_latency&lt;/th&gt;&lt;th&gt;to_latency&lt;/th&gt;&lt;th&gt;percent_change&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;oltp_point_select&lt;/td&gt;&lt;td&gt;0.28&lt;/td&gt;&lt;td&gt;0.27&lt;/td&gt;&lt;td&gt;-3.57&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;oltp_read_only&lt;/td&gt;&lt;td&gt;5.28&lt;/td&gt;&lt;td&gt;5.18&lt;/td&gt;&lt;td&gt;-1.89&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;select_random_points&lt;/td&gt;&lt;td&gt;0.55&lt;/td&gt;&lt;td&gt;0.54&lt;/td&gt;&lt;td&gt;-1.82&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;select_random_ranges&lt;/td&gt;&lt;td&gt;0.56&lt;/td&gt;&lt;td&gt;0.55&lt;/td&gt;&lt;td&gt;-1.79&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;table_scan&lt;/td&gt;&lt;td&gt;28.67&lt;/td&gt;&lt;td&gt;28.16&lt;/td&gt;&lt;td&gt;-1.78&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h1 id="planbuilder-convertint-improvements"&gt;PlanBuilder &lt;code&gt;convertInt()&lt;/code&gt; Improvements&lt;a class="anchor-link" aria-label="Link to heading" href="#planbuilder-convertint-improvements"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;During plan-building, we convert integers into the smallest integer type that can hold the value.
For example, 300 would go into a &lt;code&gt;int16&lt;/code&gt; as 300 is too large for an &lt;code&gt;int8&lt;/code&gt; and &lt;code&gt;uint8&lt;/code&gt;.
We used to do this by calling &lt;code&gt;strconv.ParseInt&lt;/code&gt; and &lt;code&gt;strconv.ParseUint&lt;/code&gt; with increasing &lt;code&gt;bitSize&lt;/code&gt; arguments until we got a result that didn’t error.
It’s much quicker to parse once with a bitSize of 64, and use bit-masking to convert the result into the appropriate integer.&lt;/p&gt;
&lt;p&gt;Relevant Benchmarks:&lt;/p&gt;









































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;benchmark&lt;/th&gt;&lt;th&gt;from_latency&lt;/th&gt;&lt;th&gt;to_latency&lt;/th&gt;&lt;th&gt;percent_change&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;groupby_scan&lt;/td&gt;&lt;td&gt;12.08&lt;/td&gt;&lt;td&gt;11.87&lt;/td&gt;&lt;td&gt;-1.74&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;index_join&lt;/td&gt;&lt;td&gt;2.03&lt;/td&gt;&lt;td&gt;1.96&lt;/td&gt;&lt;td&gt;-3.45&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;select_random_points&lt;/td&gt;&lt;td&gt;0.56&lt;/td&gt;&lt;td&gt;0.55&lt;/td&gt;&lt;td&gt;-1.79&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;select_random_ranges&lt;/td&gt;&lt;td&gt;0.58&lt;/td&gt;&lt;td&gt;0.57&lt;/td&gt;&lt;td&gt;-1.72&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;table_scan&lt;/td&gt;&lt;td&gt;28.16&lt;/td&gt;&lt;td&gt;27.66&lt;/td&gt;&lt;td&gt;-1.78&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h1 id="concurrent-groupby"&gt;Concurrent &lt;code&gt;GroupBy&lt;/code&gt;&lt;a class="anchor-link" aria-label="Link to heading" href="#concurrent-groupby"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;The &lt;code&gt;groupByGroupIter&lt;/code&gt; iterates over the &lt;code&gt;childIter&lt;/code&gt;, calculates a &lt;code&gt;grouping&lt;/code&gt; key, and updates the aggregation buffer.
We can take advantage of concurrency here: one thread for reading off the &lt;code&gt;childIter&lt;/code&gt;, another to calculate the &lt;code&gt;groupingKey&lt;/code&gt; and update the aggregation buffer.&lt;/p&gt;
&lt;p&gt;Relevant Benchmarks:&lt;/p&gt;

















&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;benchmark&lt;/th&gt;&lt;th&gt;from_latency&lt;/th&gt;&lt;th&gt;to_latency&lt;/th&gt;&lt;th&gt;percent_change&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;groupby_scan&lt;/td&gt;&lt;td&gt;12.08&lt;/td&gt;&lt;td&gt;10.84&lt;/td&gt;&lt;td&gt;-10.26&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h1 id="avoid-map-in-getfieldvalue"&gt;Avoid Map in &lt;code&gt;GetFieldValue&lt;/code&gt;&lt;a class="anchor-link" aria-label="Link to heading" href="#avoid-map-in-getfieldvalue"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;As part of the move from &lt;code&gt;sql.Row&lt;/code&gt; to &lt;code&gt;sql.ValueRow&lt;/code&gt; change, we need to map the type encodings on disk to the wire format type encodings.
In an attempt to simplify the case statements, we did this through a golang map.
It turns out map access is not as free as we thought, and the frequent lookups added some significant overhead to &lt;code&gt;GetFieldValue()&lt;/code&gt;.
So removing the map and assigning the mapped types yielded a nice performance boost, specifically on benchmarks that involved returning a large number of rows.&lt;/p&gt;
&lt;p&gt;Relevant Benchmarks:&lt;/p&gt;









































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;benchmark&lt;/th&gt;&lt;th&gt;from_latency&lt;/th&gt;&lt;th&gt;to_latency&lt;/th&gt;&lt;th&gt;percent_change&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;covering_index_scan&lt;/td&gt;&lt;td&gt;0.55&lt;/td&gt;&lt;td&gt;0.54&lt;/td&gt;&lt;td&gt;-1.82&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;groupby_scan&lt;/td&gt;&lt;td&gt;10.65&lt;/td&gt;&lt;td&gt;10.46&lt;/td&gt;&lt;td&gt;-1.78&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;index_join&lt;/td&gt;&lt;td&gt;1.96&lt;/td&gt;&lt;td&gt;1.93&lt;/td&gt;&lt;td&gt;-1.53&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;table_scan&lt;/td&gt;&lt;td&gt;28.16&lt;/td&gt;&lt;td&gt;23.1&lt;/td&gt;&lt;td&gt;-17.97&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;types_table_scan&lt;/td&gt;&lt;td&gt;65.65&lt;/td&gt;&lt;td&gt;68.05&lt;/td&gt;&lt;td&gt;3.66&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;There is a small performance hit to &lt;code&gt;types_table_scan&lt;/code&gt; due to the increased number of branches in the switch statement, but it’s worth it.&lt;/p&gt;
&lt;h1 id="custom-date-parsing-improvement"&gt;Custom Date Parsing Improvement&lt;a class="anchor-link" aria-label="Link to heading" href="#custom-date-parsing-improvement"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;As part of the previous blog, we switched away from the &lt;code&gt;time.Time&lt;/code&gt; implementation of &lt;code&gt;AppendDate&lt;/code&gt; for our &lt;a href="https://github.com/dolthub/go-mysql-server/pull/3322"&gt;own&lt;/a&gt;.
While that gave us a nice bump in queries involving any &lt;code&gt;DATE&lt;/code&gt; and &lt;code&gt;DATETIME&lt;/code&gt; types, we noticed we could do a little better.
The previous implementation made separate calls to &lt;code&gt;time.Year()&lt;/code&gt;, &lt;code&gt;time.Month()&lt;/code&gt;, &lt;code&gt;time.Day()&lt;/code&gt;, etc., which includes some repeated work.
Switching to &lt;code&gt;year, month, day = time.Date()&lt;/code&gt; and &lt;code&gt;hours, mins, secs = time.Clock()&lt;/code&gt; shaved off some time.&lt;/p&gt;
&lt;p&gt;Relevant Benchmarks:&lt;/p&gt;



































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;benchmark&lt;/th&gt;&lt;th&gt;from_latency&lt;/th&gt;&lt;th&gt;to_latency&lt;/th&gt;&lt;th&gt;percent_change&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;index_join_scan&lt;/td&gt;&lt;td&gt;1.37&lt;/td&gt;&lt;td&gt;1.34&lt;/td&gt;&lt;td&gt;-2.19&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;index_scan&lt;/td&gt;&lt;td&gt;22.69&lt;/td&gt;&lt;td&gt;22.28&lt;/td&gt;&lt;td&gt;-1.81&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;table_scan&lt;/td&gt;&lt;td&gt;23.1&lt;/td&gt;&lt;td&gt;22.69&lt;/td&gt;&lt;td&gt;-1.77&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;types_table_scan&lt;/td&gt;&lt;td&gt;68.05&lt;/td&gt;&lt;td&gt;66.84&lt;/td&gt;&lt;td&gt;-1.7&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h1 id="pointer-tuplecomparator"&gt;Pointer &lt;code&gt;TupleComparator&lt;/code&gt;&lt;a class="anchor-link" aria-label="Link to heading" href="#pointer-tuplecomparator"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;The &lt;code&gt;DefaultTupleComparator&lt;/code&gt; implementation includes a &lt;code&gt;fast&lt;/code&gt; member variable which was defined as &lt;code&gt;[][2]ByteSize&lt;/code&gt;.
This stored the starting and ending indices for fields to quickly compare Tuple prefixes.
Since this is used to compare prefixes, the indexes always start at 0 and are always sequential, so we only need to track the ending index.&lt;/p&gt;
&lt;p&gt;Relevant Benchmarks:&lt;/p&gt;























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;benchmark&lt;/th&gt;&lt;th&gt;from_latency&lt;/th&gt;&lt;th&gt;to_latency&lt;/th&gt;&lt;th&gt;percent_change&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;groupby_scan&lt;/td&gt;&lt;td&gt;10.46&lt;/td&gt;&lt;td&gt;9.91&lt;/td&gt;&lt;td&gt;-5.26&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;index_join&lt;/td&gt;&lt;td&gt;1.96&lt;/td&gt;&lt;td&gt;1.89&lt;/td&gt;&lt;td&gt;-3.57&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h1 id="conclusion"&gt;Conclusion&lt;a class="anchor-link" aria-label="Link to heading" href="#conclusion"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;Altogether, these changes brought our mean read latency multiplier down from &lt;code&gt;1.05&lt;/code&gt; to &lt;code&gt;1.00&lt;/code&gt;.
Our overall read and write multiplier is now &lt;code&gt;0.96&lt;/code&gt;, meaning Dolt actually outperforms MySQL on these benchmarks.
Of course, improvements can always be made, so we will continue to improve Dolt’s performance.
Try out Dolt yourself, and feel free to file any performance &lt;a href="https://github.com/dolthub/dolt/issues"&gt;issues&lt;/a&gt;.
If you just want to chat, feel free to join our &lt;a href="https://discord.gg/2KBQB3Cb"&gt;Discord&lt;/a&gt;.&lt;/p&gt;</content:encoded>
      <dc:creator>James Cor</dc:creator>
      <category>golang</category>
      <category>dolt</category>
    </item>
  </channel>
</rss>
