<?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>Fri, 24 Apr 2026 16:25:20 GMT</lastBuildDate>
    <atom:link href="https://siftrss.com/f/Kkmm5PG8lw" rel="self" type="application/rss+xml"/>
    <item>
      <title>How Dolt Represents and Evaluates Queries: A Case Study</title>
      <link>https://dolthub.com/blog/2026-04-21-how-dolt-represents-and-evaluates-queries/</link>
      <guid isPermaLink="true">https://dolthub.com/blog/2026-04-21-how-dolt-represents-and-evaluates-queries/</guid>
      <description>We look at a recent bug fix to understand how Dolt actually represents queries internally. It turns out, database engines and compilers have a lot in common.</description>
      <pubDate>Mon, 20 Apr 2026 00:00:00 GMT</pubDate>
      <content:encoded>&lt;p&gt;Want to know a cool secret about database engines? They’re literally the same thing as compilers.&lt;/p&gt;
&lt;p&gt;At my previous job, I worked on Google’s internal Java compiler. Now, I’m one of the developers of &lt;a href="https://github.com/dolthub/dolt"&gt;Dolt&lt;/a&gt;, the first version-controlled SQL database, as well as &lt;a href="https://github.com/dolthub/go-mysql-server/"&gt;go-mysql-server&lt;/a&gt;, the SQL engine that Dolt depends on.&lt;/p&gt;
&lt;p&gt;It turns out that the skills and techniques that I first learned while writing a compiler are the exact same techniques that Dolt uses in its database engine. And that’s because when you break it down, database engines are just a domain-specific compiler:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;General-purpose compilers turn human-readable source code into machine-readable programs that manipulate variables and produce side-effects.&lt;/li&gt;
&lt;li&gt;SQL engines turn human-readable SQL queries into machine-readable execution plans that manipulate table columns and produce a stream of rows.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Most compiler concepts map directly onto database engines. &lt;a href="https://sqlite.org/opcode.html"&gt;Some database engines like SQLite even work by producing bytecode that executes in a special-purpose VM.&lt;/a&gt;. Dolt doesn’t do that, but it does create an &lt;a href="https://en.wikipedia.org/wiki/Abstract_syntax_tree"&gt;abstract syntax tree&lt;/a&gt; almost exactly like one you would see in any other interpreted language.&lt;/p&gt;
&lt;p&gt;The main difference is that in most languages, evaluating a syntax tree produces a return value and side effects. In Dolt, evaluating a syntax tree produces an iterator. This also means that each intermediate node in the tree is an iterator defined in terms of one or more child iterators. This approach is often called the &lt;strong&gt;volcano model&lt;/strong&gt; or &lt;strong&gt;iterator model&lt;/strong&gt; of database engine design.&lt;/p&gt;
&lt;p&gt;This means that database engines can have the same types of bugs as traditional languages and a lot of the same thorns. We recently fixed a correctness issue in Dolt that does a good job demonstrating this. &lt;a href="https://github.com/dolthub/go-mysql-server/pull/3428"&gt;You can find the writeup and the fix here.&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;To trigger the incorrect behavior, you needed a query that 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;CREATE&lt;/span&gt;&lt;span&gt; TABLE&lt;/span&gt;&lt;span&gt; ab&lt;/span&gt;&lt;span&gt; (a &lt;/span&gt;&lt;span&gt;INT&lt;/span&gt;&lt;span&gt; PRIMARY KEY&lt;/span&gt;&lt;span&gt;, b &lt;/span&gt;&lt;span&gt;INT&lt;/span&gt;&lt;span&gt;);&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; three_pk&lt;/span&gt;&lt;span&gt; (pk1 &lt;/span&gt;&lt;span&gt;TINYINT&lt;/span&gt;&lt;span&gt;, pk2 &lt;/span&gt;&lt;span&gt;TINYINT&lt;/span&gt;&lt;span&gt;, pk3 &lt;/span&gt;&lt;span&gt;TINYINT&lt;/span&gt;&lt;span&gt;, col &lt;/span&gt;&lt;span&gt;TINYINT&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;PRIMARY KEY&lt;/span&gt;&lt;span&gt; (pk1, pk2, pk3));&lt;/span&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; ab &lt;/span&gt;&lt;span&gt;AS&lt;/span&gt;&lt;span&gt; ab1 &lt;/span&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; *&lt;/span&gt;&lt;span&gt; FROM&lt;/span&gt;&lt;span&gt; ab &lt;/span&gt;&lt;span&gt;AS&lt;/span&gt;&lt;span&gt; ab2 &lt;/span&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; *&lt;/span&gt;&lt;span&gt; FROM&lt;/span&gt;&lt;span&gt; ab &lt;/span&gt;&lt;span&gt;AS&lt;/span&gt;&lt;span&gt; ab3 &lt;/span&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; *&lt;/span&gt;&lt;span&gt; FROM&lt;/span&gt;&lt;span&gt; three_pk &lt;/span&gt;&lt;span&gt;WHERE&lt;/span&gt;&lt;span&gt; pk1 &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; ab1&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;a&lt;/span&gt;&lt;span&gt; and&lt;/span&gt;&lt;span&gt; pk2 &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; ab2&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;a&lt;/span&gt;&lt;span&gt; and&lt;/span&gt;&lt;span&gt; pk3 &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; ab3&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;a&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;/code&gt;&lt;/pre&gt;
&lt;p&gt;Running this query on older versions of Dolt would trigger a panic. To understand why, let’s talk about scopes.&lt;/p&gt;
&lt;h1 id="scopes"&gt;Scopes&lt;a class="anchor-link" aria-label="Link to heading" href="#scopes"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;Scopes are something that every programmer has to deal with even if they don’t realize it. It’s how programs resolve symbols to the things that are actually being referenced.&lt;/p&gt;
&lt;p&gt;SQL queries reference tables and columns by name, and Dolt needs to map those names onto the tables and columns they represent. This is not as simple as it looks, because the same name can refer to different tables based on where it appears in the statement. The following is a valid SQL 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;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; EXISTS&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; test_table &lt;/span&gt;&lt;span&gt;where&lt;/span&gt;&lt;span&gt; test_table&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; 1&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;and&lt;/span&gt;&lt;span&gt; test_table&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; 2&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In this query, there are two tables named &lt;code&gt;test_table&lt;/code&gt; and two filter conditions that name &lt;code&gt;test_table&lt;/code&gt;. Which condition refers to which child iterator? If Dolt resolves these names incorrectly, it will produce incorrect output.&lt;/p&gt;
&lt;p&gt;Two names can also refer to the same table, again depending on where the names appear. Consider this simple query with a two-part primary key:&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; test_table&lt;/span&gt;&lt;span&gt;(pk1 &lt;/span&gt;&lt;span&gt;INT&lt;/span&gt;&lt;span&gt;, pk2 &lt;/span&gt;&lt;span&gt;INT&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;PRIMARY KEY&lt;/span&gt;&lt;span&gt; (pk1, pk2));&lt;/span&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; (&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; test_table&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;pk2&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;AS&lt;/span&gt;&lt;span&gt; table_alias &lt;/span&gt;&lt;span&gt;where&lt;/span&gt;&lt;span&gt; table_alias&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;pk1&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; 2&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This query can be optimized into a simple table lookup, but only if Dolt can detect that the two &lt;code&gt;WHERE&lt;/code&gt; clauses refer to the same table, even though the two clauses use different table names.&lt;/p&gt;
&lt;p&gt;Thus, it does not suffice for Dolt to simply keep a global dictionary that maps names onto tables, because the rules for resolving references are not global. Different parts of the query introduce their own namespace, which changes how names are resolved. These namespaces are scopes.&lt;/p&gt;
&lt;p&gt;So how does a database engine keep these scopes straight? How are table references actually represented in the abstract syntax tree?&lt;/p&gt;
&lt;h2 id="scopes-at-analysis-time"&gt;Scopes at Analysis Time&lt;a class="anchor-link" aria-label="Link to heading" href="#scopes-at-analysis-time"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The most naive approach is to simply store the same names in the AST as they appear in the query. Then, whenever the engine wants to analyze, optimize, or execute part of the tree, it resolves the name using scope rules. This works, but it’s incredibly brittle:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Any optimization that transforms the AST needs to be very careful. If a node contains a reference, moving that node to another part of the tree could change the meaning of that reference, and change the behavior of the query. Whenever the engine transforms the tree, it would need to update any references.&lt;/li&gt;
&lt;li&gt;In fringe cases, a transformation may result in an impossible tree, where we need to reference a table but it’s impossible to do so because that table’s name is being shadowed by another.&lt;/li&gt;
&lt;li&gt;Needing to resolve references repeatedly is slow and wasteful.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This is actually how Dolt used to operate years ago, and it was the source of subtle bugs. So we switched to a better approach: when analyzing a query, we assign incrementing globally unique IDs to tables and columns. Every reference is resolved once, and then the name gets replaced with the ID. Since each ID always refers to the same table or column, we can safely modify the AST without any risk of changing the query’s meaning.&lt;/p&gt;
&lt;p&gt;But this is only half of the situation of scopes.&lt;/p&gt;
&lt;h2 id="scopes-at-runtime"&gt;Scopes at Runtime&lt;a class="anchor-link" aria-label="Link to heading" href="#scopes-at-runtime"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;It’s not enough to just be able to resolve references to the table or column they represent, we also need to track those values while the query is running. Operations that produce column values need to be able to send those values to the operations that read them.&lt;/p&gt;
&lt;p&gt;In general-purpose languages, this might be accomplished with registers, or by writing to values in memory. But SQL queries are declarative and functional and don’t have state: when evaluating a node in the syntax tree, the iterator it produces is often a pure function of:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Columns inherited from parent nodes
&lt;ul&gt;
&lt;li&gt;Example: In &lt;code&gt;SELECT id FROM a WHERE EXISTS (SELECT id FROM b WHERE a.id = b.id)&lt;/code&gt;, the inner query references a column from the outer query.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Columns returned from child nodes
&lt;ul&gt;
&lt;li&gt;Example: In &lt;code&gt;SELECT id FROM (SELECT id FROM a) AS a_alias where a_alias.id = 1&lt;/code&gt;, the outer query references a column from the inner query.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Columns defined in sibling nodes
&lt;ul&gt;
&lt;li&gt;Example: In &lt;code&gt;SELECT id FROM a JOIN LATERAL (SELECT id FROM b WHERE a.id = b.id)&lt;/code&gt;, the right side of the join references a column from the left side of the join.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Each of these columns can have multiple values over the lifetime of the query, but only one at a time. A SQL engine needs a strategy to represent this internally.&lt;/p&gt;
&lt;p&gt;A naive approach might be to maintain a mapping from each column name to its current value. Except as we already saw, names don’t map one-to-one onto columns. In fact, it’s perfectly valid MySQL to for a table alias to have multiple columns with the same name. For example, the below query produces a table alias with two columns both named &lt;code&gt;pk&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;CREATE&lt;/span&gt;&lt;span&gt; TABLE&lt;/span&gt;&lt;span&gt; test_table&lt;/span&gt;&lt;span&gt;(pk &lt;/span&gt;&lt;span&gt;INT&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;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; test_table &lt;/span&gt;&lt;span&gt;JOIN&lt;/span&gt;&lt;span&gt; test_table) &lt;/span&gt;&lt;span&gt;AS&lt;/span&gt;&lt;span&gt; table_alias;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Even more problematic is that a table alias column might have &lt;em&gt;no&lt;/em&gt; name:&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; (&lt;/span&gt;&lt;span&gt;SELECT&lt;/span&gt;&lt;span&gt; 1&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;AS&lt;/span&gt;&lt;span&gt; table_alias;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In either case, it’s not possible to reference these columns in filters, but they can still impact the results of the query if the alias is used in a &lt;code&gt;SELECT *&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;So if we can’t track values by their column name, another approach might be to use the unique column IDs we discussed in the previous section. But there could be many such IDs, and each node in the AST only cares about a small number of them. Managing lots of small maps is also not very performant, and we care about performance.&lt;/p&gt;
&lt;p&gt;Fortunately, there’s an approach to that gives us both clarity and performance. Every scope in a SQL query always has the same number of columns. So the number of columns referenceable from any node in the AST is a constant value that can be determined by statically analyzing the query. The number of columns in that node’s iterator is also a constant value.&lt;/p&gt;
&lt;p&gt;Examples:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A table reference with N columns produces an iterator that returns a list of five values.&lt;/li&gt;
&lt;li&gt;A &lt;code&gt;SELECT col1, col2, ... colN WHERE EXISTS (...)&lt;/code&gt; construct creates N referenceable columns for every node within the subquery.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Each node can have an array where we store the value of each of these columns. Each element in the array corresponds to a different column. Before we evaluate the query, we can analyze the AST to determine which column each array element represents. Now if we want to represent an expression that reads a column, we don’t need to store the name of the column in the AST, and we don’t need to store that column’s globally unique ID either: all we need to store is the offset within that node’s own array corresponding to that column.&lt;/p&gt;
&lt;p&gt;This is the approach that Dolt uses: it analyzes the tree, determines the exact set of columns visible to each node, and replaces column references with the correct offset into that node’s array that will contain that column at runtime.&lt;/p&gt;
&lt;p&gt;We can illustrate this process with some diagrams. In each case, we show the nodes in the AST, and each node has both an ordered sequence of columns it produces (the output schema), and an ordered lists of columns it can reference (the input schema). The color of each cell indicates the node that originally produced the value. Note how nodes can contain column references from children, siblings, or parents, but in each case the number of columns can be statically determined.&lt;/p&gt;
&lt;p&gt;A node referencing its child:&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; test_table&lt;/span&gt;&lt;span&gt;(a &lt;/span&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt;, b &lt;/span&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt;, c &lt;/span&gt;&lt;span&gt;int&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; c&lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt; AS&lt;/span&gt;&lt;span&gt; d &lt;/span&gt;&lt;span&gt;FROM&lt;/span&gt;&lt;span&gt; test_table;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Would result in an AST that looks like this:&lt;/p&gt;
&lt;p&gt;&lt;img src="https://static.dolthub.com/blogimages/ast-child.svg/c1eb0eb3597c600cc5b743a41588051270245a20bc8e15c127188c15bb754b26.svg" alt="node with child reference"&gt;&lt;/p&gt;
&lt;p&gt;A node referencing its parent:&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; test_table&lt;/span&gt;&lt;span&gt;(a &lt;/span&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt;, b &lt;/span&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt;, c &lt;/span&gt;&lt;span&gt;int&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; a &lt;/span&gt;&lt;span&gt;FROM&lt;/span&gt;&lt;span&gt; test_table &lt;/span&gt;&lt;span&gt;AS&lt;/span&gt;&lt;span&gt; t1 &lt;/span&gt;&lt;span&gt;WHERE&lt;/span&gt;&lt;span&gt; EXISTS&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;SELECT&lt;/span&gt;&lt;span&gt; b &lt;/span&gt;&lt;span&gt;FROM&lt;/span&gt;&lt;span&gt; test_table &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; t1&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;a&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;a&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Would result in an AST that looks like this:&lt;/p&gt;
&lt;p&gt;&lt;img src="https://static.dolthub.com/blogimages/ast-parent.svg/5f8e321bc1841193cb1170493715d10ce03d971997b743c941aa25ecd95aeb8a.svg" alt="node with parent reference"&gt;&lt;/p&gt;
&lt;p&gt;In order for this optimization to work correctly, every node must agree on how many columns it receives from each of their children, and how many columns are visible from parent and sibling scopes. If these numbers don’t agree, it could lead to situations where Dolt accesses the wrong value at runtime, or accesses the array out-of-bounds and panics.&lt;/p&gt;
&lt;p&gt;So with all this in mind, let’s look back at the query that triggered the bug:&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; ab&lt;/span&gt;&lt;span&gt; (a &lt;/span&gt;&lt;span&gt;INT&lt;/span&gt;&lt;span&gt; PRIMARY KEY&lt;/span&gt;&lt;span&gt;, b &lt;/span&gt;&lt;span&gt;INT&lt;/span&gt;&lt;span&gt;);&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; three_pk&lt;/span&gt;&lt;span&gt; (pk1 &lt;/span&gt;&lt;span&gt;TINYINT&lt;/span&gt;&lt;span&gt;, pk2 &lt;/span&gt;&lt;span&gt;TINYINT&lt;/span&gt;&lt;span&gt;, pk3 &lt;/span&gt;&lt;span&gt;TINYINT&lt;/span&gt;&lt;span&gt;, col &lt;/span&gt;&lt;span&gt;TINYINT&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;PRIMARY KEY&lt;/span&gt;&lt;span&gt; (pk1, pk2, pk3));&lt;/span&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; ab &lt;/span&gt;&lt;span&gt;AS&lt;/span&gt;&lt;span&gt; ab1 &lt;/span&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; *&lt;/span&gt;&lt;span&gt; FROM&lt;/span&gt;&lt;span&gt; ab &lt;/span&gt;&lt;span&gt;AS&lt;/span&gt;&lt;span&gt; ab2 &lt;/span&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; *&lt;/span&gt;&lt;span&gt; FROM&lt;/span&gt;&lt;span&gt; ab &lt;/span&gt;&lt;span&gt;AS&lt;/span&gt;&lt;span&gt; ab3 &lt;/span&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; *&lt;/span&gt;&lt;span&gt; FROM&lt;/span&gt;&lt;span&gt; three_pk &lt;/span&gt;&lt;span&gt;WHERE&lt;/span&gt;&lt;span&gt; pk1 &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; ab1&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;a&lt;/span&gt;&lt;span&gt; and&lt;/span&gt;&lt;span&gt; pk2 &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; ab2&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;a&lt;/span&gt;&lt;span&gt; and&lt;/span&gt;&lt;span&gt; pk3 &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; ab3&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;a&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;/code&gt;&lt;/pre&gt;
&lt;p&gt;The simplest explanation for the root cause was this:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;When Dolt optimized this query, it would transform it into multiple nested join nodes.&lt;/li&gt;
&lt;li&gt;Each of these join nodes introduced a new table alias that could be referenced by its children.&lt;/li&gt;
&lt;li&gt;If a join condition referenced a column, then Dolt would need to compute the offset of that column in the join node’s array of column references. This required knowing how many of the columns in the array came from parent scopes, how many came from sibling scopes, and how many were from that node’s children.&lt;/li&gt;
&lt;li&gt;The logic for computing how many columns in the input schema came from outer scopes did not consider the fact scopes could be introduced in the middle of a nested join. This made it impossible to correctly calculate this for every node in the AST.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;There were some attempts made to account for this issue, but in adjusting the calculations for common queries, it broke the calculations for less common queries. Ultimately, these adjustments made the logic harder to reason about. In the end, we fixed the issue by completely rewriting how we generated iterators during join in order to make it simpler to analyze them.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/dolthub/go-mysql-server/pull/3428"&gt;The full scope of the issue and the fix are more complicated, but this was the general idea.&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;I hope this illuminates what actually goes on inside a database engine.&lt;/p&gt;
&lt;p&gt;As always, if you have any thoughts about database design, or if you’re curious how a version-controlled database can benefit you, then you should hop into our &lt;a href="https://discord.gg/gqr7K4VNKe"&gt;Discord&lt;/a&gt; and we’d love to discuss it with you.&lt;/p&gt;</content:encoded>
      <dc:creator>Nick Tobey</dc:creator>
      <category>dolt</category>
    </item>
    <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>
  </channel>
</rss>
