<?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>Thu, 14 May 2026 20:01:30 GMT</lastBuildDate>
    <atom:link href="https://siftrss.com/f/Kkmm5PG8lw" rel="self" type="application/rss+xml"/>
    <item>
      <title>How TPC-C Works</title>
      <link>https://dolthub.com/blog/2026-05-14-how-tpcc-works/</link>
      <guid isPermaLink="true">https://dolthub.com/blog/2026-05-14-how-tpcc-works/</guid>
      <description>A detailed description of Dolt's TPC-C benchmarking.</description>
      <pubDate>Thu, 14 May 2026 00:00:00 GMT</pubDate>
      <content:encoded>&lt;p&gt;Dolt is a version-controlled database that works as a drop-in MySQL replacement.
In addition to correctness parity with MySQL, we are also determined to reach performance parity with MySQL.
For years now, we’ve been improving Dolt performance on both Sysbench and TPC-C benchmarks.
Towards the end of 2025, Dolt reached &lt;a href="https://www.dolthub.com/blog/2025-12-04-dolt-is-as-fast-as-mysql/"&gt;parity with MySQL on Sysbench&lt;/a&gt; when averaging reads and writes.
Shortly after, we managed to reach &lt;a href="https://www.dolthub.com/blog/2026-01-06-more-read-performance-wins/"&gt;MySQL parity on both read and writes&lt;/a&gt;.
Now, we &lt;a href="https://docs.dolthub.com/sql-reference/benchmarks/latency"&gt;surpass MySQL on Sysbench reads and writes&lt;/a&gt; with a &lt;code&gt;0.95&lt;/code&gt; reads mean multiplier and &lt;code&gt;0.87&lt;/code&gt; write mean multiplier.&lt;/p&gt;
&lt;p&gt;While we are proud of our accomplishments on Sysbench, TPC-C Benchmarks are arguably more important to the average user experience.
This blog will go into detail about the TPC-C benchmarks and the kinds of queries run against the database.&lt;/p&gt;
&lt;h1 id="what-is-tpc-c"&gt;What is TPC-C&lt;a class="anchor-link" aria-label="Link to heading" href="#what-is-tpc-c"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;&lt;a href="https://www.tpc.org/tpcc/"&gt;TPC-C&lt;/a&gt; stands for Transaction Processing Performance Council Benchmark C.
It is the industry standard benchmark used for OLTP databases.
TPC-C simulates real-world usage of a database by modeling transactions for a wholesale supplier.
Dolt actually uses a slightly modified version of the official TPC-C benchmarks from &lt;a href="https://github.com/Percona-Lab/sysbench-tpcc"&gt;Percona Labs&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="settings"&gt;Settings&lt;a class="anchor-link" aria-label="Link to heading" href="#settings"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;We run TPC-C with these settings:&lt;/p&gt;
&lt;pre class="astro-code github-dark" tabindex="0" data-language="shell"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span&gt;./tpcc.lua&lt;/span&gt;&lt;span&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;  --db-driver=&lt;/span&gt;&lt;span&gt;"mysql"&lt;/span&gt;&lt;span&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;  --mysql-db=&lt;/span&gt;&lt;span&gt;"sbtest"&lt;/span&gt;&lt;span&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;  --mysql-host=&lt;/span&gt;&lt;span&gt;"127.0.0.1"&lt;/span&gt;&lt;span&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;  --mysql-port=&lt;/span&gt;&lt;span&gt;"&lt;/span&gt;&lt;span&gt;$PORT&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;  --mysql-user=&lt;/span&gt;&lt;span&gt;"&lt;/span&gt;&lt;span&gt;$USER&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;  --mysql-password=&lt;/span&gt;&lt;span&gt;"&lt;/span&gt;&lt;span&gt;$PASS&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;  --time=800&lt;/span&gt;&lt;span&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;  --report_interval=10&lt;/span&gt;&lt;span&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;  --threads=1&lt;/span&gt;&lt;span&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;  --tables=1&lt;/span&gt;&lt;span&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;  --scale=1&lt;/span&gt;&lt;span&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;  --trx_level=&lt;/span&gt;&lt;span&gt;"RR"&lt;/span&gt;&lt;span&gt; run&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The benchmarks are run with a single thread for &lt;code&gt;800&lt;/code&gt; seconds with &lt;code&gt;autocommit&lt;/code&gt; and &lt;code&gt;foreign_key_checks&lt;/code&gt; disabled.
&lt;code&gt;trx_level="RR"&lt;/code&gt; is &lt;code&gt;REPEATABLE_READ&lt;/code&gt; (the MySQL/Innodb default), which means that &lt;code&gt;SELECT&lt;/code&gt; results are isolated within a transaction.
Uncommitted writes to a table from another transaction will not be visible to this transaction.
We compare the 95th percentile latency and number of transactions per second (tps) against MySQL.&lt;/p&gt;
&lt;h2 id="tables"&gt;Tables&lt;a class="anchor-link" aria-label="Link to heading" href="#tables"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Here is a brief summary of the tables created.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;warehouse&lt;/code&gt; with &lt;code&gt;1&lt;/code&gt; row&lt;/li&gt;
&lt;li&gt;&lt;code&gt;district&lt;/code&gt; with &lt;code&gt;10&lt;/code&gt; rows&lt;/li&gt;
&lt;li&gt;&lt;code&gt;customer&lt;/code&gt; with &lt;code&gt;30000&lt;/code&gt; rows&lt;/li&gt;
&lt;li&gt;&lt;code&gt;orders&lt;/code&gt; with &lt;code&gt;30000&lt;/code&gt; rows&lt;/li&gt;
&lt;li&gt;&lt;code&gt;new_orders&lt;/code&gt; with &lt;code&gt;9000&lt;/code&gt; rows&lt;/li&gt;
&lt;li&gt;&lt;code&gt;order_line&lt;/code&gt; with &lt;code&gt;299293&lt;/code&gt; rows&lt;/li&gt;
&lt;li&gt;&lt;code&gt;item&lt;/code&gt; with &lt;code&gt;100000&lt;/code&gt; rows&lt;/li&gt;
&lt;li&gt;&lt;code&gt;stock&lt;/code&gt; with &lt;code&gt;100000&lt;/code&gt; rows&lt;/li&gt;
&lt;li&gt;&lt;code&gt;history&lt;/code&gt; with &lt;code&gt;30000&lt;/code&gt; rows&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Many of these tables have primary keys, secondary keys, and foreign keys (even though &lt;code&gt;foreign_key_checks&lt;/code&gt; are disabled).&lt;/p&gt;
&lt;h2 id="transactions"&gt;Transactions&lt;a class="anchor-link" aria-label="Link to heading" href="#transactions"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;TPC-C randomly selects between &lt;code&gt;5&lt;/code&gt; different transaction types with varying odds.
Each transaction starts with a &lt;code&gt;BEGIN&lt;/code&gt; and ends with &lt;code&gt;COMMIT&lt;/code&gt;.
Without spelling out every query run within a transaction, here is a high-level overview of each transaction.&lt;/p&gt;

































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;trx_type&lt;/th&gt;&lt;th&gt;run_percent&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;new_order&lt;/code&gt;&lt;/td&gt;&lt;td&gt;43.48%&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;payment&lt;/code&gt;&lt;/td&gt;&lt;td&gt;43.48%&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;order_status&lt;/code&gt;&lt;/td&gt;&lt;td&gt;4.35%&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;delivery&lt;/code&gt;&lt;/td&gt;&lt;td&gt;4.35%&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;payment&lt;/code&gt;&lt;/td&gt;&lt;td&gt;4.35%&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;TOTAL&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;≈100.00%&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h3 id="1-new_order"&gt;1. &lt;code&gt;new_order&lt;/code&gt;&lt;a class="anchor-link" aria-label="Link to heading" href="#1-new_order"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Description&lt;/strong&gt;:
This transaction simulates a customer order of &lt;code&gt;5&lt;/code&gt; - &lt;code&gt;15&lt;/code&gt; quantity of an item.
The new order is logged in the &lt;code&gt;orders&lt;/code&gt;, &lt;code&gt;new_orders&lt;/code&gt;, and &lt;code&gt;order_line&lt;/code&gt; tables, and the &lt;code&gt;item&lt;/code&gt; and &lt;code&gt;stock&lt;/code&gt; tables are updated accordingly.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Percent Run&lt;/strong&gt;:
&lt;code&gt;43.49% (10/23)&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;# of SQL Statements&lt;/strong&gt;:
&lt;code&gt;25&lt;/code&gt; - &lt;code&gt;65&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Reads Tables&lt;/strong&gt;:
&lt;code&gt;customer&lt;/code&gt;, &lt;code&gt;district&lt;/code&gt;, &lt;code&gt;item&lt;/code&gt;, &lt;code&gt;stock&lt;/code&gt;, &lt;code&gt;warehouse&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Writes Tables&lt;/strong&gt;:
&lt;code&gt;district&lt;/code&gt;, &lt;code&gt;orders&lt;/code&gt;, &lt;code&gt;order_line&lt;/code&gt;, &lt;code&gt;new_orders&lt;/code&gt;, &lt;code&gt;stock&lt;/code&gt;&lt;/p&gt;
&lt;h3 id="2-payment"&gt;2. &lt;code&gt;payment&lt;/code&gt;&lt;a class="anchor-link" aria-label="Link to heading" href="#2-payment"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Description&lt;/strong&gt;:
This transaction simulates a customer making a purchase.
It reads the customer’s payment details (name, address, etc.) from the &lt;code&gt;customer&lt;/code&gt; table, update their account balance, and logs the transaction in the &lt;code&gt;history&lt;/code&gt; table.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Percent Run&lt;/strong&gt;:
&lt;code&gt;43.48% (10/23)&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;# of SQL Statements&lt;/strong&gt;:
&lt;code&gt;6&lt;/code&gt; - &lt;code&gt;10&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Reads Tables&lt;/strong&gt;:
&lt;code&gt;customer&lt;/code&gt;, &lt;code&gt;district&lt;/code&gt;, &lt;code&gt;warehouse&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Writes Tables&lt;/strong&gt;:
&lt;code&gt;customer&lt;/code&gt;, &lt;code&gt;district&lt;/code&gt;, &lt;code&gt;history&lt;/code&gt;, &lt;code&gt;warehouse&lt;/code&gt;&lt;/p&gt;
&lt;h3 id="3-order_status"&gt;3. &lt;code&gt;order_status&lt;/code&gt;&lt;a class="anchor-link" aria-label="Link to heading" href="#3-order_status"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Description&lt;/strong&gt;:
This transaction simulates a customer inquiring about their order details.
It performs a series of &lt;code&gt;SELECT&lt;/code&gt; queries into the &lt;code&gt;customer&lt;/code&gt;, &lt;code&gt;orders&lt;/code&gt; and &lt;code&gt;order_line&lt;/code&gt; tables.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Percent Run&lt;/strong&gt;:
&lt;code&gt;4.34% (1/23)&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;# of SQL Statements&lt;/strong&gt;:
&lt;code&gt;3&lt;/code&gt; - &lt;code&gt;4&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Reads Tables&lt;/strong&gt;:
&lt;code&gt;customer&lt;/code&gt;, &lt;code&gt;orders&lt;/code&gt;, &lt;code&gt;order_line&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Writes Tables&lt;/strong&gt;:&lt;/p&gt;
&lt;h3 id="4-delivery"&gt;4. &lt;code&gt;delivery&lt;/code&gt;&lt;a class="anchor-link" aria-label="Link to heading" href="#4-delivery"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Description&lt;/strong&gt;:
This transaction simulates a customer’s order getting delivered.
An order is removed from the &lt;code&gt;new_orders&lt;/code&gt; table and the appropriate entries are updated in &lt;code&gt;orders&lt;/code&gt;, &lt;code&gt;order_line&lt;/code&gt; and &lt;code&gt;customer&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Percent Run&lt;/strong&gt;:
&lt;code&gt;4.34% (1/23)&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;# of SQL Statements&lt;/strong&gt;:
&lt;code&gt;1&lt;/code&gt; - &lt;code&gt;6&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Reads Tables&lt;/strong&gt;:
&lt;code&gt;orders&lt;/code&gt;, &lt;code&gt;order_line&lt;/code&gt;, &lt;code&gt;new_orders&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Writes Tables&lt;/strong&gt;:
&lt;code&gt;customer&lt;/code&gt;, &lt;code&gt;orders&lt;/code&gt;, &lt;code&gt;order_line&lt;/code&gt;, &lt;code&gt;new_orders&lt;/code&gt;&lt;/p&gt;
&lt;h3 id="5-stocklevel"&gt;5. &lt;code&gt;stocklevel&lt;/code&gt;&lt;a class="anchor-link" aria-label="Link to heading" href="#5-stocklevel"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Description&lt;/strong&gt;:
This transaction simulates a query over existing inventory.
It is a few &lt;code&gt;SELECT&lt;/code&gt; queries that aggregate over the &lt;code&gt;order_line&lt;/code&gt; and &lt;code&gt;stock&lt;/code&gt; tables that count the quantity of certain items.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Percent Run&lt;/strong&gt;:
&lt;code&gt;4.34% (1/23)&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;# of SQL Statements&lt;/strong&gt;:
&lt;code&gt;3+&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Reads Tables&lt;/strong&gt;:
&lt;code&gt;district&lt;/code&gt;, &lt;code&gt;orders&lt;/code&gt;, &lt;code&gt;stock&lt;/code&gt;, &lt;code&gt;order_line&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Writes Tables&lt;/strong&gt;:&lt;/p&gt;
&lt;p&gt;You can explore the TPC-C database in more detail here:
&lt;a href="https://www.dolthub.com/repositories/jcor/sbtest"&gt;https://www.dolthub.com/repositories/jcor/sbtest&lt;/a&gt;&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 have been focused on Dolt performance on TPC-C, which aims to simulate a real user experience with an OLTP database.
Over the last few months we have made substantial improvements to TPC-C.
Stay tuned for a future blog describing how we’ve broken the 2x MySQL multiplier on TPC-C.
Have any performance issues? Cut a bug on our &lt;a href="https://github.com/dolthub/dolt/issues"&gt;GitHub issues page&lt;/a&gt;.
Want to talk to anyone on our team? Join &lt;a href="https://discord.gg/gqr7K4VNKe"&gt;our Discord&lt;/a&gt;.&lt;/p&gt;</content:encoded>
      <dc:creator>James Cor</dc:creator>
      <category>technical</category>
      <category>performance</category>
    </item>
    <item>
      <title>Dolt 2.0</title>
      <link>https://dolthub.com/blog/2026-05-11-dolt-2-dot-0/</link>
      <guid isPermaLink="true">https://dolthub.com/blog/2026-05-11-dolt-2-dot-0/</guid>
      <description>Almost exactly three years ago, we announced Dolt 1.0. Today, we announce Dolt 2.0.</description>
      <pubDate>Mon, 11 May 2026 00:00:00 GMT</pubDate>
      <content:encoded>&lt;p&gt;Three years ago, we &lt;a href="https://www.dolthub.com/blog/2023-05-05-dolt-1-dot-0/"&gt;announced Dolt 1.0&lt;/a&gt;, signalling that Dolt was ready for production workloads. We haven’t stopped improving &lt;a href="https://www.dolthub.com/blog/2022-08-04-database-versioning/"&gt;the world’s first and only version-controlled SQL database&lt;/a&gt;. Today, we are excited to announce &lt;a href="https://github.com/dolthub/dolt/releases/tag/v2.0.0"&gt;Dolt 2.0&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://static.dolthub.com/blogimages/dolt-2_0.png/35df9fbddbb4f701cb124b70d8c1fe657b29a4f14c94606770a8996d40bc41ea.webp" alt="Dolt 2.0"&gt;&lt;/p&gt;
&lt;h1 id="what-did-dolt-10-mean"&gt;What Did Dolt 1.0 Mean?&lt;a class="anchor-link" aria-label="Link to heading" href="#what-did-dolt-10-mean"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;&lt;a href="https://www.dolthub.com/blog/2023-05-05-dolt-1-dot-0/"&gt;Dolt 1.0&lt;/a&gt; meant four things:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Forward Storage Compatibility&lt;/li&gt;
&lt;li&gt;Production Performance&lt;/li&gt;
&lt;li&gt;MySQL Compatibility&lt;/li&gt;
&lt;li&gt;Stable Version Control Interface&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Dolt 2.0 maintains the promises of Dolt 1.0. Dolt 2.0 improves on the performance and correctness metrics established in Dolt 1.0.&lt;/p&gt;
&lt;h1 id="what-does-dolt-20-mean"&gt;What Does Dolt 2.0 Mean?&lt;a class="anchor-link" aria-label="Link to heading" href="#what-does-dolt-20-mean"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;Dolt 2.0 means five things:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Automated Garbage Collection on by Default&lt;/li&gt;
&lt;li&gt;Archive Compression on by Default&lt;/li&gt;
&lt;li&gt;Faster than MySQL on &lt;code&gt;sysbench&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Beta Vector Support&lt;/li&gt;
&lt;li&gt;Adaptive Storage&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Unlike Dolt 1.0, Dolt 2.0 is fully backwards compatible with all Dolt 1.0 versions. No storage migration using &lt;code&gt;dolt migrate&lt;/code&gt; is required. Let’s dive into the details of each of these points.&lt;/p&gt;
&lt;h2 id="garbage-collection"&gt;Garbage Collection&lt;a class="anchor-link" aria-label="Link to heading" href="#garbage-collection"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Dolt makes a lot of disk garbage, especially during import. Dolt is copy-on-write so all intermediate committed transaction state is preserved to disk. Any intermediate state that is not in a Dolt commit is garbage and can be collected.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://static.dolthub.com/blogimages/how-garbage-is-created.png/c719463f830464444cce6a285c5a4125cc88710480ad62d28a3108f60a25739b.webp" alt="Garbage"&gt;&lt;/p&gt;
&lt;p&gt;Dolt already must preserve all history in the commit graph on disk. Adding extra garbage can eat through your disk very quickly.&lt;/p&gt;
&lt;p&gt;Dolt 2.0 has &lt;a href="https://www.dolthub.com/blog/2025-02-28-announcing-automatic-gc-in-sql-server/"&gt;automatic garbage collection&lt;/a&gt; on by default, meaning most users don’t have to care about disk garbage. Many users have been running in this mode for over a year. We’re confident it is stable.&lt;/p&gt;
&lt;p&gt;Dolt 2.0 databases do not require extra garbage maintenance, just like other modern SQL engines.&lt;/p&gt;
&lt;h2 id="archives"&gt;Archives&lt;a class="anchor-link" aria-label="Link to heading" href="#archives"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Following on the disk space theme, we also have a &lt;a href="https://www.dolthub.com/blog/2024-04-29-dolt-storage-v2/"&gt;new on disk format we call archives&lt;/a&gt; that can reduce Dolt’s storage footprint by an additional 30-50%. Archives use dictionary compression to de-duplicate storage in the deepest layers of Dolt, saving even more disk space.&lt;/p&gt;
&lt;p&gt;As with automatic garbage collection, archives have been the default format for new Dolt databases for months. We’re confident the format is stable and delivers real disk space wins.&lt;/p&gt;
&lt;p&gt;Dolt 2.0 databases are kind to your disk with automatic garbage collection and archives. Version control already requires more disk space than traditional databases. Dolt 2.0 preserves that disk for your data’s history.&lt;/p&gt;
&lt;h2 id="faster-than-mysql-on-sysbench"&gt;Faster than MySQL on &lt;code&gt;sysbench&lt;/code&gt;&lt;a class="anchor-link" aria-label="Link to heading" href="#faster-than-mysql-on-sysbench"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;We’ve long used the &lt;a href="https://github.com/akopytov/sysbench"&gt;industry standard &lt;code&gt;sysbench&lt;/code&gt;&lt;/a&gt; to measure and benchmark &lt;a href="https://docs.dolthub.com/sql-reference/benchmarks/latency"&gt;the latency of simple SQL queries in Dolt&lt;/a&gt;. We started at about 10X slower on reads and 20X slower on writes than MySQL. We’ve worked tirelessly to improve Dolt’s performance and we are now 13% faster than MySQL on writes and 5% faster on reads, averaging out to 8% faster than MySQL on &lt;code&gt;sysbench&lt;/code&gt; style workloads.&lt;/p&gt;
&lt;p&gt;Dolt 2.0 databases deliver real production database performance coupled with version control functionality.&lt;/p&gt;
&lt;h2 id="beta-vector-support"&gt;Beta Vector Support&lt;a class="anchor-link" aria-label="Link to heading" href="#beta-vector-support"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;We announced &lt;a href="https://www.dolthub.com/blog/2025-01-16-announcing-vector-indexes/"&gt;vector index support&lt;/a&gt; early last year. We have a much bigger challenge than traditional databases with vector indexes because our vector indexes must be version-controlled. We’ve done &lt;a href="https://www.dolthub.com/blog/2025-06-23-vector-index-deep-dive/"&gt;the hard computer science&lt;/a&gt; to achieve this. We &lt;a href="https://www.dolthub.com/blog/2025-09-03-improving-vector-performance/"&gt;adopted the Vector type from MariaDB&lt;/a&gt; in September 2025.&lt;/p&gt;
&lt;p&gt;Dolt 2.0 databases have Beta vector support. Dolt is the only database where your vectors are version-controlled. We still have some edge cases on the read query path where a vector index should be used but it is not. Closing these gaps will reove the Beta tag from Dolt’s vector support.&lt;/p&gt;
&lt;h2 id="adaptive-storage-for-large-column-types"&gt;Adaptive storage for large column types&lt;a class="anchor-link" aria-label="Link to heading" href="#adaptive-storage-for-large-column-types"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Borrowing from our &lt;a href="https://www.dolthub.com/blog/2025-04-14-adaptive-encoding/"&gt;Doltgres adaptive storage work&lt;/a&gt; to support &lt;a href="https://www.postgresql.org/docs/current/storage-toast.html"&gt;TOAST types&lt;/a&gt;, we’re excited to announce Dolt 2.0 has adaptive storage.&lt;/p&gt;
&lt;p&gt;For large column types like TEXT, BLOB, and JSON, databases generally store the value “out of band”, as a file on disk with a pointer to the file in the actual table structure. A different strategy, popularized by Postgres, is to examine the size of the value and store small values in the table structure while preserving the files and pointers strategy for large values. This strategy allows the user to be less disciplined about sizing &lt;code&gt;VARCHAR&lt;/code&gt; columns and just use &lt;code&gt;TEXT&lt;/code&gt; instead. It’s also a big performance win for these types when the values are small.&lt;/p&gt;
&lt;p&gt;Dolt 2.0 has adaptive storage making MySQL databases that use &lt;code&gt;TEXT&lt;/code&gt;, &lt;code&gt;BLOB&lt;/code&gt;, &lt;code&gt;GEOMETRY&lt;/code&gt;, or &lt;code&gt;JSON&lt;/code&gt; columns a good fit regardless of whether they need version control or not.&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;Dolt 2.0 is here. It’s kinder to your disk and it’s fast. Questions? Stop by &lt;a href="https://discord.gg/gqr7K4VNKe"&gt;our Discord&lt;/a&gt; and just ask.&lt;/p&gt;</content:encoded>
      <dc:creator>Tim Sehn</dc:creator>
      <category>dolt</category>
      <category>feature release</category>
    </item>
    <item>
      <title>Announcing DumboDB: A MongoDB Clone Built on Dolt</title>
      <link>https://dolthub.com/blog/2026-05-07-announcing-dumbodb/</link>
      <guid isPermaLink="true">https://dolthub.com/blog/2026-05-07-announcing-dumbodb/</guid>
      <description>MongoDB and Git had a baby, and it's named Dumbo. Release 0.1 is now public!</description>
      <pubDate>Thu, 07 May 2026 00:00:00 GMT</pubDate>
      <content:encoded>&lt;p&gt;&lt;img src="https://static.dolthub.com/blogimages/dumbo-logo.png/c02da7b39168585c4dc1adf3ebf6ffe77404dd9b8fc5a35f6dc765bb9dea9e16.webp" alt="DumboDB Logo"&gt;&lt;/p&gt;
&lt;p&gt;Today, I’m pleased to announce the &lt;a href="https://github.com/dolthub/dumbodb"&gt;release of DumboDB 0.1&lt;/a&gt;, a &lt;a href="https://github.com/mongodb/mongo"&gt;MongoDB&lt;/a&gt; clone built on top of Dolt’s storage system.&lt;/p&gt;
&lt;p&gt;MongoDB and Git had a baby, and it’s named DumboDB.&lt;/p&gt;
&lt;h2 id="tldr"&gt;TL;DR;&lt;a class="anchor-link" aria-label="Link to heading" href="#tldr"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Grab the code and give it a try! We are excited to see what the community can do with it. You can find the code on GitHub: &lt;a href="https://github.com/dolthub/dumbodb"&gt;https://github.com/dolthub/dumbodb&lt;/a&gt;. See the README for installation instructions. If you have feedback, questions, or want to contribute, join us on &lt;a href="https://discord.gg/gqr7K4VNKe"&gt;Discord&lt;/a&gt;!&lt;/p&gt;
&lt;h2 id="how-did-we-get-here"&gt;How Did We Get Here?&lt;a class="anchor-link" aria-label="Link to heading" href="#how-did-we-get-here"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Like everyone else in the software industry, we have been kicking around the AI tools that are getting so much hype these days. No joke, &lt;a href="https://www.dolthub.com/blog/2026-03-25-doltlite/"&gt;my boss is vibe coding now&lt;/a&gt;. What a stereotype, right? A couple of weeks ago &lt;a href="https://www.dolthub.com/blog/2026-04-16-two-weeks-in-gastown/"&gt;I talked about testing Gas Town&lt;/a&gt;, a coding agent orchestrator, only to get swept up in the excitement of building something new insanely fast. Turns out that a dozen agents can make a really compelling proof of concept in a couple of weeks. Ultimately, we decided to turn that proof of concept into a real product, and here we are.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://static.dolthub.com/blogimages/dumbo_i_made_this.jpg/721f49f2a93edb921e59fafa20922b2ade612be7731c0cac557987dae2523add.webp" alt="I Made This"&gt;&lt;/p&gt;
&lt;p&gt;Since then, the pace of change has slowed to allow for human-speed verification and testing. The firehose of code generation provided by Gas Town hasn’t been necessary, and it’s mostly been slower 1-on-1 coding with &lt;a href="https://code.claude.com/docs/en/overview"&gt;Claude Code&lt;/a&gt;. I’ve even read some of the code and directed Claude to clean up some nonsense which we are all used to seeing with coding agents at this point. Nevertheless, the 6 weeks of development to get to a viable 0.1 release has been far, far faster than I could have ever done on my own. Say what you want about the AI bubble, coding agents are going to help us write a mountain of code.&lt;/p&gt;
&lt;h2 id="dumbodbs-dna"&gt;DumboDB’s DNA&lt;a class="anchor-link" aria-label="Link to heading" href="#dumbodbs-dna"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;DumboDB started with the &lt;a href="https://github.com/FerretDB/FerretDB"&gt;FerretDB code base&lt;/a&gt;, which is an open-source MongoDB clone. FerretDB operates as a proxy that translates MongoDB queries into SQL queries that are executed against a PostgreSQL database. It’s written in Go.&lt;/p&gt;
&lt;p&gt;Dolt is written in Go as well, so we ripped out the proxy approach of FerretDB and made a standalone server that uses Dolt’s storage engine.&lt;/p&gt;
&lt;p&gt;Dolt’s storage engine is a &lt;a href="https://docs.dolthub.com/architecture/storage-engine/prolly-tree"&gt;Prolly Tree&lt;/a&gt;, which is a data structure that allows for structural sharing of data. Using &lt;a href="https://en.wikipedia.org/wiki/Merkle_tree"&gt;Merkle Trees and DAGs&lt;/a&gt;, Dolt can represent a very granular set of snapshots of your data. This is the same approach used by Git to model your source code history.&lt;/p&gt;
&lt;p&gt;Our Prolly Tree implementation has been getting our love and attention for more than a &lt;a href="https://github.com/dolthub/dolt/commit/68c3ac02058e559367534aeeb7d9f8f483a4db1b"&gt;decade now&lt;/a&gt;. It is what enables our primary product, Dolt, to be a version-controlled database. And when we say version-controlled, we mean it. You can branch, merge, diff, send pull requests, rebase, and so on - just like you would with Git. Dolt is a drop-in replacement for MySQL and is &lt;a href="https://docs.dolthub.com/sql-reference/benchmarks/latency"&gt;as fast as MySQL for many workloads&lt;/a&gt;. It gives you all the &lt;a href="https://www.dolthub.com/blog/2026-05-04-database-insurance/"&gt;safety of Git for your data&lt;/a&gt;. Honestly, most software engineers hear about what we’ve built in Dolt, and they are astonished because it seems impossible. It’s real! &lt;a href="https://github.com/dolthub/dolt"&gt;Check it out&lt;/a&gt; if you haven’t already!&lt;/p&gt;
&lt;p&gt;DumboDB is using the same storage engine as Dolt, but with a different access layer. Instead of using SQL, DumboDB uses the MongoDB query language. This means that you can use all the same tools and libraries that you would use with MongoDB, but with the added benefits of Dolt’s storage engine. To be crystal clear, there is no SQL layer in DumboDB. DumboDB uses the storage objects of Dolt. Therefore, it uses the same indexes, journal code, and commit model — but no SQL. It’s a NoSQL document database — not a facade on top of a SQL database.&lt;/p&gt;
&lt;h2 id="what-can-you-do-with-dumbodb"&gt;What Can You Do With DumboDB?&lt;a class="anchor-link" aria-label="Link to heading" href="#what-can-you-do-with-dumbodb"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;DumboDB is alpha-quality software, so what you &lt;em&gt;should not&lt;/em&gt; do is run it in production.&lt;/p&gt;
&lt;p&gt;That said, it should be able to do most basic MongoDB operations. If you are familiar with MongoDB, you should be able to pick up DumboDB pretty quickly. You can use the same drivers and libraries that you would use with MongoDB, so probably the best thing to do is just kick the tires and tell us when something doesn’t work. We are sure there are bugs, and we want to know about them!&lt;/p&gt;
&lt;p&gt;There are some glaring gaps. DumboDB has no concept of user accounts or permissions yet. There are no isolated sessions or transactions with rollbacks (though you can &lt;code&gt;reset --hard&lt;/code&gt;!). Text search and geo features aren’t implemented. We don’t have a replication or sharding story yet, and we may just stick to the Git model of clone/push/pull. We’ll see. It all depends on what our users ask for. The joy of open source is that we aren’t hiding behind a proprietary roadmap. We’ll build what makes sense, and take PRs for all the rest.&lt;/p&gt;
&lt;p&gt;Being a drop-in replacement for MongoDB is the eventual goal, but we aren’t there yet. If you are bold, you can try running your existing MongoDB workloads against DumboDB and see what happens. Start your server with the &lt;code&gt;--auto-commit&lt;/code&gt; flag and witness how your application changes your data over time.&lt;/p&gt;
&lt;h3 id="version-control-features"&gt;Version Control Features&lt;a class="anchor-link" aria-label="Link to heading" href="#version-control-features"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;All the basic operations you would expect from a Git-inspired product are available. This includes: &lt;code&gt;commit&lt;/code&gt;, &lt;code&gt;branch&lt;/code&gt;, &lt;code&gt;merge&lt;/code&gt;, &lt;code&gt;cherry-pick&lt;/code&gt;, &lt;code&gt;rebase&lt;/code&gt;, &lt;code&gt;log&lt;/code&gt;, &lt;code&gt;status&lt;/code&gt;, &lt;code&gt;diff&lt;/code&gt;, &lt;code&gt;reset&lt;/code&gt;, &lt;code&gt;revert&lt;/code&gt;, and &lt;code&gt;tag&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Read the documentation for each command &lt;a href="https://github.com/dolthub/dumbodb/wiki/Commands"&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;There is no “staging” concept in DumboDB, which is a departure from how Git works. Instead, you just make your changes to the database, and then when you are ready to commit, you commit and whatever content is in your workspace will be committed. No need to “add” changes, like you would in Git.&lt;/p&gt;
&lt;p&gt;There are two additional commands for working with merge conflicts: &lt;code&gt;conflicts&lt;/code&gt; and &lt;code&gt;resolveConflict&lt;/code&gt;. Unlike Git, but similar to Dolt, merge conflicts are structured. When merge conflicts arise, the three-way merge details are available to your application so that it can resolve the conflicts reliably. See examples below.&lt;/p&gt;
&lt;p&gt;Note the lack of &lt;code&gt;checkout&lt;/code&gt;. There is no checkout in DumboDB. Instead, you get a database instance using the &lt;code&gt;getSiblingDB&lt;/code&gt; operation. Check out the examples…&lt;/p&gt;
&lt;h2 id="examples"&gt;Examples&lt;a class="anchor-link" aria-label="Link to heading" href="#examples"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;You’ll need to grab a prebuilt binary or build from source to run these examples. See the &lt;a href="https://github.com/dolthub/dumbodb#install-dumbodb"&gt;README&lt;/a&gt; for instructions.&lt;/p&gt;
&lt;p&gt;Run the server in its own terminal window:&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;dumbodb&lt;/span&gt;&lt;span&gt; --data-dir&lt;/span&gt;&lt;span&gt; /tmp/dumbodb-data&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then in another terminal window, connect to the server using the &lt;a href="https://www.mongodb.com/docs/mongodb-shell/"&gt;MongoDB shell&lt;/a&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;$&lt;/span&gt;&lt;span&gt; mongosh&lt;/span&gt;&lt;span&gt; mongodb://localhost:27017/&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;   The&lt;/span&gt;&lt;span&gt; server&lt;/span&gt;&lt;span&gt; generated&lt;/span&gt;&lt;span&gt; these&lt;/span&gt;&lt;span&gt; startup&lt;/span&gt;&lt;span&gt; warnings&lt;/span&gt;&lt;span&gt; when&lt;/span&gt;&lt;span&gt; booting&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;   2026-05-06T21:36:58.547Z:&lt;/span&gt;&lt;span&gt; Powered&lt;/span&gt;&lt;span&gt; by&lt;/span&gt;&lt;span&gt; DumboDB&lt;/span&gt;&lt;span&gt; v0.1.0&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;   2026-05-06T21:36:58.547Z:&lt;/span&gt;&lt;span&gt; Star&lt;/span&gt;&lt;span&gt; Us!&lt;/span&gt;&lt;span&gt; https://github.com/dolthub/dumbodb&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;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;mongosh&lt;/code&gt; is a JavaScript shell, so you execute JavaScript code to interact with the database. The &lt;code&gt;test&gt;&lt;/code&gt; prompt indicates that you are connected to the &lt;code&gt;test&lt;/code&gt; database. The way you change the database is you set the &lt;code&gt;db&lt;/code&gt; variable to a different database. For example, if you want to switch to the &lt;code&gt;mydb&lt;/code&gt; database, you would run:&lt;/p&gt;
&lt;pre class="astro-code github-dark" tabindex="0" data-language="javascript"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span&gt;test&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;span&gt; db &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; db.&lt;/span&gt;&lt;span&gt;getSiblingDB&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;"mydb"&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;mydb&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;mydb&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;See how the prompt changed to &lt;code&gt;mydb&gt;&lt;/code&gt;? That indicates that we are now using the &lt;code&gt;mydb&lt;/code&gt; database. Now let’s use it!&lt;/p&gt;
&lt;h3 id="create-a-new-collection-and-insert-some-documents"&gt;Create a new collection, and insert some documents:&lt;a class="anchor-link" aria-label="Link to heading" href="#create-a-new-collection-and-insert-some-documents"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The MongoDB approach is to create collections implicitly when you first insert a document into them. So there is no &lt;code&gt;createCollection&lt;/code&gt; command. Instead, you just start inserting documents into a collection, and it will be created for you. Let’s insert two documents into a new collection called &lt;code&gt;customers&lt;/code&gt;:&lt;/p&gt;
&lt;pre class="astro-code github-dark" tabindex="0" data-language="javascript"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span&gt;mydb&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;span&gt; db.customers.&lt;/span&gt;&lt;span&gt;insertOne&lt;/span&gt;&lt;span&gt;({ name: &lt;/span&gt;&lt;span&gt;"Alice"&lt;/span&gt;&lt;span&gt;, phone: &lt;/span&gt;&lt;span&gt;"555-1234"&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;  acknowledged&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;  insertedId&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;ObjectId&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;'69fbcb932a4aeea4bc4de9b5'&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;mydb&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;span&gt; db.customers.&lt;/span&gt;&lt;span&gt;insertOne&lt;/span&gt;&lt;span&gt;({ name: &lt;/span&gt;&lt;span&gt;"Bob"&lt;/span&gt;&lt;span&gt;, phone: &lt;/span&gt;&lt;span&gt;"555-5678"&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;  acknowledged&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;  insertedId&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;ObjectId&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;'69fbbb46a26d8df3b3ab5cf6'&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;mydb&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That’s all vanilla Mongo behavior. By inserting these two documents, we have made changes to the database, but those changes are not committed yet. It’s like editing a source file in Git: you need to commit it. It works exactly the same with DumboDB. Make as many changes as you need to, then commit when ready.&lt;/p&gt;
&lt;p&gt;DumboDB’s version control operations are executed with the &lt;code&gt;db.runCommand&lt;/code&gt; method. We can see the status of our database with the &lt;code&gt;dumboStatus&lt;/code&gt; command:&lt;/p&gt;
&lt;pre class="astro-code github-dark" tabindex="0" data-language="javascript"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span&gt;mydb&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;span&gt; db.&lt;/span&gt;&lt;span&gt;runCommand&lt;/span&gt;&lt;span&gt;({ dumboStatus: &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 class="line"&gt;&lt;span&gt;  branch&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;'main'&lt;/span&gt;&lt;span&gt;,                   &lt;/span&gt;&lt;span&gt;// `db` is on the main branch&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;  dirty&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;,                      &lt;/span&gt;&lt;span&gt;// there are uncommitted changes&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;  readonly&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt;,                  &lt;/span&gt;&lt;span&gt;// we can write to this database, as demonstrated by our inserts&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;  collections&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;      name: &lt;/span&gt;&lt;span&gt;'customers'&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;'added'&lt;/span&gt;&lt;span&gt;,              &lt;/span&gt;&lt;span&gt;// the customers collection is new, so it's status is "added"&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;      added: &lt;/span&gt;&lt;span&gt;2&lt;/span&gt;&lt;span&gt;,                     &lt;/span&gt;&lt;span&gt;// we added 2 documents to the customers collection&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;      modified: &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;      deleted: &lt;/span&gt;&lt;span&gt;0&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;  ok&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;                             // the command was successful. Standard MongoDB.&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;Now we can commit our changes to the database. The commit will create a new snapshot of the database, and the dirty flag will return to &lt;code&gt;false&lt;/code&gt;.&lt;/p&gt;
&lt;pre class="astro-code github-dark" tabindex="0" data-language="javascript"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span&gt;mydb&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;span&gt; db.&lt;/span&gt;&lt;span&gt;runCommand&lt;/span&gt;&lt;span&gt;({ dumboCommit: &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;                      message: &lt;/span&gt;&lt;span&gt;"Add customers collection with Alice and Bob"&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;                      author: &lt;/span&gt;&lt;span&gt;"neil &amp;#x3C;neil@dolthub.com&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;  commitId&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;'6nc94olva9m81ofdjnnhp3018100qs5f'&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;  branch&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;'main'&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;  message&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;'Add customers collection with Alice and Bob'&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;  author&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;'neil &amp;#x3C;neil@dolthub.com&gt;'&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;  timestamp&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;ISODate&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;'2026-05-06T22:12:28.240Z'&lt;/span&gt;&lt;span&gt;),&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;  committer&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;'neil &amp;#x3C;neil@dolthub.com&gt;'&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;  committerTimestamp&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;ISODate&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;'2026-05-06T22:12:28.240Z'&lt;/span&gt;&lt;span&gt;),&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;  ok&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;1&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;mydb&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;span&gt; db.&lt;/span&gt;&lt;span&gt;runCommand&lt;/span&gt;&lt;span&gt;({ dumboStatus: &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 class="line"&gt;&lt;span&gt;  branch&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;'main'&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;  dirty&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;  readonly&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;  commitId&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;'6nc94olva9m81ofdjnnhp3018100qs5f'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;// commitId is shown whenever dirty is false.&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;  collections&lt;/span&gt;&lt;span&gt;: [],&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;  ok&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;1&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;h3 id="branch-merge-and-resolve-conflicts"&gt;Branch, Merge, and Resolve Conflicts&lt;a class="anchor-link" aria-label="Link to heading" href="#branch-merge-and-resolve-conflicts"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Let’s create a new branch, called &lt;code&gt;feature&lt;/code&gt;, and change the phone number for Alice:&lt;/p&gt;
&lt;pre class="astro-code github-dark" tabindex="0" data-language="javascript"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span&gt;mydb&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;span&gt; db.&lt;/span&gt;&lt;span&gt;runCommand&lt;/span&gt;&lt;span&gt;({ dumboBranch: &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;, branch: &lt;/span&gt;&lt;span&gt;"feature"&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;branch&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;'feature'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;ok&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;// Specify the branch or revision number with &amp;#x3C;db&gt;@&amp;#x3C;branchOrRevision&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;mydb&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;span&gt; var&lt;/span&gt;&lt;span&gt; feature &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; db.&lt;/span&gt;&lt;span&gt;getSiblingDB&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;"mydb@feature"&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;// `feature` variable is a database instance that is now "pointing" to the `feature` branch.&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;// We can run commands against it, just like we do with `db`.&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;mydb&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;span&gt; feature.&lt;/span&gt;&lt;span&gt;runCommand&lt;/span&gt;&lt;span&gt;({ dumboStatus: &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 class="line"&gt;&lt;span&gt;  branch&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;'feature'&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;  dirty&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;  readonly&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;  commitId&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;'6nc94olva9m81ofdjnnhp3018100qs5f'&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;  collections&lt;/span&gt;&lt;span&gt;: [],&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;  ok&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;1&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;// Update Alice's phone number in the feature branch&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;mydb&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;span&gt; feature.customers.&lt;/span&gt;&lt;span&gt;updateOne&lt;/span&gt;&lt;span&gt;({ name: &lt;/span&gt;&lt;span&gt;"Alice"&lt;/span&gt;&lt;span&gt; }, { $set: { phone: &lt;/span&gt;&lt;span&gt;"555-4321"&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;  acknowledged&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;  insertedId&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;null&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;  matchedCount&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;  modifiedCount&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;// Indicates that one document was modified.&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;  upsertedCount&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;0&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;mydb&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;span&gt; feature.&lt;/span&gt;&lt;span&gt;runCommand&lt;/span&gt;&lt;span&gt;({ dumboStatus: &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 class="line"&gt;&lt;span&gt;  branch&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;'feature'&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;  dirty&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;  readonly&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;  collections&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;      name: &lt;/span&gt;&lt;span&gt;'customers'&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;'modified'&lt;/span&gt;&lt;span&gt;,  &lt;/span&gt;&lt;span&gt;// The customers collection is modified, because we changed Alice's phone number.&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;      added: &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;      modified: &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;,         &lt;/span&gt;&lt;span&gt;// Alice's document was modified.&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;      deleted: &lt;/span&gt;&lt;span&gt;0&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;  ok&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;1&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;dumboStatus&lt;/code&gt; gives a high-level summary of what has changed in our working copy of the database, but if we want to see the actual changes, we can use the &lt;code&gt;dumboDiff&lt;/code&gt; command:&lt;/p&gt;
&lt;pre class="astro-code github-dark" tabindex="0" data-language="javascript"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span&gt;mydb&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;span&gt; feature.&lt;/span&gt;&lt;span&gt;runCommand&lt;/span&gt;&lt;span&gt;({ dumboDiff: &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 class="line"&gt;&lt;span&gt;  collections&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;      name: &lt;/span&gt;&lt;span&gt;'customers'&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;'modified'&lt;/span&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;
&lt;span class="line"&gt;&lt;span&gt;      removed: [],&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;      modified: [&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;          _id: &lt;/span&gt;&lt;span&gt;ObjectId&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;'69fbcb932a4aeea4bc4de9b5'&lt;/span&gt;&lt;span&gt;),&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;          diff: [&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;            // the "phone" field was changed from "555-1234" to "555-4321"&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;            { type: &lt;/span&gt;&lt;span&gt;'modified'&lt;/span&gt;&lt;span&gt;, path: &lt;/span&gt;&lt;span&gt;'$.phone'&lt;/span&gt;&lt;span&gt;, from: &lt;/span&gt;&lt;span&gt;'555-1234'&lt;/span&gt;&lt;span&gt;, to: &lt;/span&gt;&lt;span&gt;'555-4321'&lt;/span&gt;&lt;span&gt; }&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;          ]&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;        }&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;      ]&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;  ],&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;  ok&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;1&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;// dumboCommit will always commit the content shown in the output of dumboDiff without arguments.&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;// Run dumboCommit now, it will commit the change to Alice's phone number.&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;mydb&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;span&gt; feature.&lt;/span&gt;&lt;span&gt;runCommand&lt;/span&gt;&lt;span&gt;({ dumboCommit: &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;, message: &lt;/span&gt;&lt;span&gt;"Update Alice's phone number"&lt;/span&gt;&lt;span&gt;, author: &lt;/span&gt;&lt;span&gt;"neil &amp;#x3C;neil@dolthub.com&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;  commitId&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;'p7p99vndl9d11hjloivlu1lcuvt3n9qa'&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;  branch&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;'feature'&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;  message&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;"Update Alice's phone number"&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;  author&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;'neil &amp;#x3C;neil@dolthub.com&gt;'&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;  timestamp&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;ISODate&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;'2026-05-06T23:27:52.848Z'&lt;/span&gt;&lt;span&gt;),&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;  committer&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;'neil &amp;#x3C;neil@dolthub.com&gt;'&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;  committerTimestamp&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;ISODate&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;'2026-05-06T23:27:52.848Z'&lt;/span&gt;&lt;span&gt;),&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;  ok&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;1&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;Now let’s change the same field, but on the &lt;code&gt;main&lt;/code&gt; branch. Note that the &lt;code&gt;db&lt;/code&gt; instance was created on the default branch, which is &lt;code&gt;main&lt;/code&gt;, so when we run commands against &lt;code&gt;db&lt;/code&gt;, we are running them against the &lt;code&gt;main&lt;/code&gt; branch. We’ll perform a ‘find’ to demonstrate:&lt;/p&gt;
&lt;pre class="astro-code github-dark" tabindex="0" data-language="javascript"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span&gt;mydb&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;span&gt; db.customers.&lt;/span&gt;&lt;span&gt;find&lt;/span&gt;&lt;span&gt;({name: &lt;/span&gt;&lt;span&gt;'Alice'&lt;/span&gt;&lt;span&gt;})&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;[&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;  {&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;    _id: &lt;/span&gt;&lt;span&gt;ObjectId&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;'69fbcb932a4aeea4bc4de9b5'&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;'Alice'&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;    phone: &lt;/span&gt;&lt;span&gt;'555-1234'&lt;/span&gt;&lt;span&gt; // Still unchanged on the main branch.&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;mydb&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;span&gt; db.customers.&lt;/span&gt;&lt;span&gt;updateOne&lt;/span&gt;&lt;span&gt;({ name: &lt;/span&gt;&lt;span&gt;"Alice"&lt;/span&gt;&lt;span&gt; }, { $set: { phone: &lt;/span&gt;&lt;span&gt;"555-9999"&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;  acknowledged&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;  insertedId&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;null&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;  matchedCount&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;  modifiedCount&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;  upsertedCount&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;0&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;mydb&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;span&gt; db.&lt;/span&gt;&lt;span&gt;runCommand&lt;/span&gt;&lt;span&gt;({dumboCommit:&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;, message: &lt;/span&gt;&lt;span&gt;"update Alice on main"&lt;/span&gt;&lt;span&gt;, author: &lt;/span&gt;&lt;span&gt;"neil &amp;#x3C;neil@dolthub.com&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;  commitId&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;'22gvmftrbf995mn924hl0ounoo4quhqh'&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;  branch&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;'main'&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;  message&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;'update Alice on main'&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;  author&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;'neil &amp;#x3C;neil@dolthub.com&gt;'&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;  timestamp&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;ISODate&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;'2026-05-06T23:28:51.139Z'&lt;/span&gt;&lt;span&gt;),&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;  committer&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;'neil &amp;#x3C;neil@dolthub.com&gt;'&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;  committerTimestamp&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;ISODate&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;'2026-05-06T23:28:51.139Z'&lt;/span&gt;&lt;span&gt;),&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;  ok&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;1&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;To recap: We’ve updated Alice’s phone number to “555-4321” on the &lt;code&gt;feature&lt;/code&gt; branch, and “555-9999” on the &lt;code&gt;main&lt;/code&gt; branch. We can see the differences between the two branches with &lt;code&gt;dumboDiff&lt;/code&gt;:&lt;/p&gt;
&lt;pre class="astro-code github-dark" tabindex="0" data-language="javascript"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span&gt;mydb&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;span&gt; db.&lt;/span&gt;&lt;span&gt;runCommand&lt;/span&gt;&lt;span&gt;({ dumboDiff: &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;, from: &lt;/span&gt;&lt;span&gt;"main"&lt;/span&gt;&lt;span&gt;, to: &lt;/span&gt;&lt;span&gt;"feature"&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;  collections&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;      name: &lt;/span&gt;&lt;span&gt;'customers'&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;'modified'&lt;/span&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;
&lt;span class="line"&gt;&lt;span&gt;      removed: [],&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;      modified: [&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;          _id: &lt;/span&gt;&lt;span&gt;ObjectId&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;'69fbcb932a4aeea4bc4de9b5'&lt;/span&gt;&lt;span&gt;),&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;          diff: [&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;            { type: &lt;/span&gt;&lt;span&gt;'modified'&lt;/span&gt;&lt;span&gt;, path: &lt;/span&gt;&lt;span&gt;'$.phone'&lt;/span&gt;&lt;span&gt;, from: &lt;/span&gt;&lt;span&gt;'555-9999'&lt;/span&gt;&lt;span&gt;, to: &lt;/span&gt;&lt;span&gt;'555-4321'&lt;/span&gt;&lt;span&gt; }&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;          ]&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;        }&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;      ]&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;  ],&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;  ok&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;1&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;That is just a flat diff between the branches, but we know that they have a common ancestor with a third value for Alice’s phone number. When we attempt to merge the &lt;code&gt;feature&lt;/code&gt; branch into &lt;code&gt;main&lt;/code&gt;, we will get a merge conflict, because the same field was modified in both branches. DumboDB will give us the details of the merge conflict, so that we can resolve it:&lt;/p&gt;
&lt;pre class="astro-code github-dark" tabindex="0" data-language="javascript"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span&gt;mydb&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;span&gt; db.&lt;/span&gt;&lt;span&gt;runCommand&lt;/span&gt;&lt;span&gt;({dumboMerge: &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;                     merge_in: &lt;/span&gt;&lt;span&gt;"feature"&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;                     message: &lt;/span&gt;&lt;span&gt;"merge in feature branch"&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;                     author: &lt;/span&gt;&lt;span&gt;"neil &amp;#x3C;neil@dolthub.com&gt;"&lt;/span&gt;&lt;span&gt;})&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;MongoServerError&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;dumboMerge&lt;/span&gt;&lt;span&gt;: unresolved conflicts &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;span&gt; 1&lt;/span&gt;&lt;span&gt; collection&lt;/span&gt;&lt;span&gt;(s)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We got the merge conflict, as expected. To make sense of what happened, we can run the &lt;code&gt;dumboConflicts&lt;/code&gt; command:&lt;/p&gt;
&lt;pre class="astro-code github-dark" tabindex="0" data-language="javascript"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span&gt;mydb&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;span&gt; db.&lt;/span&gt;&lt;span&gt;runCommand&lt;/span&gt;&lt;span&gt;({dumboConflicts: &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 class="line"&gt;&lt;span&gt;  collections&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;      collection: &lt;/span&gt;&lt;span&gt;'customers'&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;      conflicts: [&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;          conflictId: &lt;/span&gt;&lt;span&gt;'1I7p9HPnc+ff2JXl+sLqgw'&lt;/span&gt;&lt;span&gt;,          &lt;/span&gt;&lt;span&gt;// Unique identifier for this specific conflict.&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;          _id: &lt;/span&gt;&lt;span&gt;ObjectId&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;'69fbcb932a4aeea4bc4de9b5'&lt;/span&gt;&lt;span&gt;),&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;          base: { name: &lt;/span&gt;&lt;span&gt;'Alice'&lt;/span&gt;&lt;span&gt;, phone: &lt;/span&gt;&lt;span&gt;'555-1234'&lt;/span&gt;&lt;span&gt; },    &lt;/span&gt;&lt;span&gt;// Original value.&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;          ours: { name: &lt;/span&gt;&lt;span&gt;'Alice'&lt;/span&gt;&lt;span&gt;, phone: &lt;/span&gt;&lt;span&gt;'555-9999'&lt;/span&gt;&lt;span&gt; },    &lt;/span&gt;&lt;span&gt;// Value on the main branch (ours)&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;          theirs: { name: &lt;/span&gt;&lt;span&gt;'Alice'&lt;/span&gt;&lt;span&gt;, phone: &lt;/span&gt;&lt;span&gt;'555-4321'&lt;/span&gt;&lt;span&gt; },  &lt;/span&gt;&lt;span&gt;// Value on the feature branch (theirs)&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;          ourDiffType: &lt;/span&gt;&lt;span&gt;'modified'&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;          theirDiffType: &lt;/span&gt;&lt;span&gt;'modified'&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;        }&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;      ]&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;  ],&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;  ok&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;1&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;Now we can resolve the conflict with the &lt;code&gt;dumboResolveConflict&lt;/code&gt; command. We have three options to resolve the conflict: we can choose either “ours” or “theirs”, or we can provide a custom resolution. For this example, we’ll choose a custom resolution, where we set Alice’s phone number to “555-0000”:&lt;/p&gt;
&lt;pre class="astro-code github-dark" tabindex="0" data-language="javascript"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span&gt;mydb&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;span&gt; db.&lt;/span&gt;&lt;span&gt;runCommand&lt;/span&gt;&lt;span&gt;({dumboResolveConflict: &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;                     collection: &lt;/span&gt;&lt;span&gt;"customers"&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;                     conflictId: &lt;/span&gt;&lt;span&gt;"1I7p9HPnc+ff2JXl+sLqgw"&lt;/span&gt;&lt;span&gt;,    &lt;/span&gt;&lt;span&gt;// Unique identifier from above. required.&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;                     resolution: &lt;/span&gt;&lt;span&gt;"custom"&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;                     value: { name: &lt;/span&gt;&lt;span&gt;'Alice'&lt;/span&gt;&lt;span&gt;, phone: &lt;/span&gt;&lt;span&gt;'555-0000'&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;ok&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;// dumboStatus prints merge state:&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;mydb&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;span&gt; db.&lt;/span&gt;&lt;span&gt;runCommand&lt;/span&gt;&lt;span&gt;({dumboStatus: &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 class="line"&gt;&lt;span&gt;  branch&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;'main'&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;  dirty&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;  readonly&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;  collections&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;      name: &lt;/span&gt;&lt;span&gt;'customers'&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;'modified'&lt;/span&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;0&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;      modified: &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;      deleted: &lt;/span&gt;&lt;span&gt;0&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;  mergeState&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;'merge'&lt;/span&gt;&lt;span&gt;,  &lt;/span&gt;&lt;span&gt;// Currently in the middle of a merge.&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;  conflicts&lt;/span&gt;&lt;span&gt;: [],        &lt;/span&gt;&lt;span&gt;// No more conflicts, because we resolved the only conflict.&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;  ok&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;1&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;// Complete the merge with the `continue` flag on the dumboMerge command.&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;mydb&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;span&gt; db.&lt;/span&gt;&lt;span&gt;runCommand&lt;/span&gt;&lt;span&gt;({dumboMerge: &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;                     continue: &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;                     message: &lt;/span&gt;&lt;span&gt;"merge in feature branch"&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;                     author: &lt;/span&gt;&lt;span&gt;"neil &amp;#x3C;neil@dolthub.com&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;  commitId&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;'7iraa088995v304n61egkm45dcge2a78'&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;  message&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;'merge in feature branch'&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;  author&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;'neil &amp;#x3C;neil@dolthub.com&gt;'&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;  timestamp&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;ISODate&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;'2026-05-06T23:59:11.995Z'&lt;/span&gt;&lt;span&gt;),&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;  committer&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;'neil &amp;#x3C;neil@dolthub.com&gt;'&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;  committerTimestamp&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;ISODate&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;'2026-05-06T23:59:11.995Z'&lt;/span&gt;&lt;span&gt;),&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;  ok&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Finally, you can use the &lt;code&gt;dumboLog&lt;/code&gt; command to see the history of commits on the &lt;code&gt;main&lt;/code&gt; branch, including the merge commit we just made:&lt;/p&gt;
&lt;pre class="astro-code github-dark" tabindex="0" data-language="javascript"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span&gt;mydb&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;span&gt; db.&lt;/span&gt;&lt;span&gt;runCommand&lt;/span&gt;&lt;span&gt;({dumboLog: &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 class="line"&gt;&lt;span&gt;  commits&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;      commitId: &lt;/span&gt;&lt;span&gt;'7iraa088995v304n61egkm45dcge2a78'&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;      refs: [ &lt;/span&gt;&lt;span&gt;'HEAD'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;'main'&lt;/span&gt;&lt;span&gt; ],                               &lt;/span&gt;&lt;span&gt;// refs tell you which branches or tags are pointing to this commit.&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;      parent1: &lt;/span&gt;&lt;span&gt;'22gvmftrbf995mn924hl0ounoo4quhqh'&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;      parent2: &lt;/span&gt;&lt;span&gt;'p7p99vndl9d11hjloivlu1lcuvt3n9qa'&lt;/span&gt;&lt;span&gt;,            &lt;/span&gt;&lt;span&gt;// Second parent because this is a merge commit!&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;      message: &lt;/span&gt;&lt;span&gt;'merge in feature branch'&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;      timestamp: &lt;/span&gt;&lt;span&gt;ISODate&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;'2026-05-06T23:59:11.980Z'&lt;/span&gt;&lt;span&gt;),&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;      author: &lt;/span&gt;&lt;span&gt;'neil &amp;#x3C;neil@dolthub.com&gt;'&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;      committer: &lt;/span&gt;&lt;span&gt;'neil &amp;#x3C;neil@dolthub.com&gt;'&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;      committerTimestamp: &lt;/span&gt;&lt;span&gt;ISODate&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;'2026-05-06T23:59:11.980Z'&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;    },&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;    {&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;      commitId: &lt;/span&gt;&lt;span&gt;'22gvmftrbf995mn924hl0ounoo4quhqh'&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;      parent1: &lt;/span&gt;&lt;span&gt;'9t4t592jqddgj1elcfnaal7c8o6boj26'&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;      message: &lt;/span&gt;&lt;span&gt;'update Alice on main'&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;      timestamp: &lt;/span&gt;&lt;span&gt;ISODate&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;'2026-05-06T23:28:51.139Z'&lt;/span&gt;&lt;span&gt;),&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;      author: &lt;/span&gt;&lt;span&gt;'neil &amp;#x3C;neil@dolthub.com&gt;'&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;      committer: &lt;/span&gt;&lt;span&gt;'neil &amp;#x3C;neil@dolthub.com&gt;'&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;      committerTimestamp: &lt;/span&gt;&lt;span&gt;ISODate&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;'2026-05-06T23:28:51.139Z'&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;    },&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;    {&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;      commitId: &lt;/span&gt;&lt;span&gt;'p7p99vndl9d11hjloivlu1lcuvt3n9qa'&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;      refs: [ &lt;/span&gt;&lt;span&gt;'feature'&lt;/span&gt;&lt;span&gt; ],                                 &lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;      parent1: &lt;/span&gt;&lt;span&gt;'9t4t592jqddgj1elcfnaal7c8o6boj26'&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;      message: &lt;/span&gt;&lt;span&gt;"Update Alice's phone number"&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;      timestamp: &lt;/span&gt;&lt;span&gt;ISODate&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;'2026-05-06T23:27:52.848Z'&lt;/span&gt;&lt;span&gt;),&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;      author: &lt;/span&gt;&lt;span&gt;'neil &amp;#x3C;neil@dolthub.com&gt;'&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;      committer: &lt;/span&gt;&lt;span&gt;'neil &amp;#x3C;neil@dolthub.com&gt;'&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;      committerTimestamp: &lt;/span&gt;&lt;span&gt;ISODate&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;'2026-05-06T23:27:52.848Z'&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;    },&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;    {&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;      commitId: &lt;/span&gt;&lt;span&gt;'9t4t592jqddgj1elcfnaal7c8o6boj26'&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;      parent1: &lt;/span&gt;&lt;span&gt;'6h1nv5qcnkesp3ahg5vn7shp6airkkn7'&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;      message: &lt;/span&gt;&lt;span&gt;'Add customers collection with Alice and Bob'&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;      timestamp: &lt;/span&gt;&lt;span&gt;ISODate&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;'2026-05-06T23:16:04.348Z'&lt;/span&gt;&lt;span&gt;),&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;      author: &lt;/span&gt;&lt;span&gt;'neil &amp;#x3C;neil@dolthub.com&gt;'&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;      committer: &lt;/span&gt;&lt;span&gt;'neil &amp;#x3C;neil@dolthub.com&gt;'&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;      committerTimestamp: &lt;/span&gt;&lt;span&gt;ISODate&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;'2026-05-06T23:16:04.348Z'&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;    },&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;    {&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;      commitId: &lt;/span&gt;&lt;span&gt;'6h1nv5qcnkesp3ahg5vn7shp6airkkn7'&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;      message: &lt;/span&gt;&lt;span&gt;'Initialize database'&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;      timestamp: &lt;/span&gt;&lt;span&gt;ISODate&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;'2026-05-06T23:15:31.054Z'&lt;/span&gt;&lt;span&gt;),&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;      author: &lt;/span&gt;&lt;span&gt;'dumbodb &amp;#x3C;dumbodb@dumbodb&gt;'&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;      committer: &lt;/span&gt;&lt;span&gt;'dumbodb &amp;#x3C;dumbodb@dumbodb&gt;'&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;      committerTimestamp: &lt;/span&gt;&lt;span&gt;ISODate&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;'2026-05-06T23:15:31.054Z'&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;  ],&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;  ok&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;dumboLog&lt;/code&gt; command is not very flexible yet, but you can see a summary of the documents changed with the &lt;code&gt;stat&lt;/code&gt; flag, and the full diff of each with the &lt;code&gt;patch&lt;/code&gt; flag. Both are inspired by the &lt;code&gt;git log&lt;/code&gt; command.&lt;/p&gt;
&lt;p&gt;To read the specifics of each command, see the &lt;a href="https://github.com/dolthub/dumbodb/wiki/Commands"&gt;documentation&lt;/a&gt;!&lt;/p&gt;
&lt;h2 id="roadmap"&gt;Roadmap&lt;a class="anchor-link" aria-label="Link to heading" href="#roadmap"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;There are plenty of ways we can expand this product’s features, and we are just at the beginning. Here is our current rough roadmap:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;v0.2&lt;/strong&gt;: Garbage Collection and zstd compression. Reduce the footprint of your database. Simplified configuration for user details (name and email) so you don’t have to specify them on every commit.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;v0.3&lt;/strong&gt;: Add Clone, Push, and Pull support. This will allow you to sync your DumboDB repositories with remote servers, and collaborate with others.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;v0.4&lt;/strong&gt;: Add support for Replication (as a secondary backup to your existing MongoDB instance).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;v0.5&lt;/strong&gt;: Isolated Session and Transaction support.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;v0.6&lt;/strong&gt;: Add Authentication and Authorization support.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;v0.7&lt;/strong&gt;: Add MCP server support, allowing you to use agents to work with the DumboDB database.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;v0.8&lt;/strong&gt;: Visualization and operations via a custom Workbench UI.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;v1.0&lt;/strong&gt;: General availability release, with a focus on stability, performance, and usability improvements.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;No dates are assigned on any of this, mainly because we will invest our engineering resources in balance with the other DoltHub products. We’ll go faster if you bang on our doors!&lt;/p&gt;
&lt;h2 id="call-to-action"&gt;Call to Action!&lt;a class="anchor-link" aria-label="Link to heading" href="#call-to-action"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Here at &lt;a href="https://www.dolthub.com/"&gt;DoltHub&lt;/a&gt;, we strongly believe in the power of version-controlled databases. Users have told us for a long time that a NoSQL option would appeal to them, and now we have one! Real-world usage and feedback is the surest way to make sure we are building the right features. For that, we require your help. If you are interested in this space (you must be because you read this far), try DumboDB out and tell us what needs to be improved, extended, thrown out, etc. We want to hear from you!&lt;/p&gt;
&lt;p&gt;Want to impact the future of DumboDB? Join us on &lt;a href="https://discord.gg/gqr7K4VNKe"&gt;Discord&lt;/a&gt;!&lt;/p&gt;</content:encoded>
      <dc:creator>Neil Macneale</dc:creator>
      <category>dumbo</category>
      <category>feature release</category>
    </item>
    <item>
      <title>Announcing Azure Private Link Support for Hosted Dolt</title>
      <link>https://dolthub.com/blog/2026-05-06-azure-private-link-networking/</link>
      <guid isPermaLink="true">https://dolthub.com/blog/2026-05-06-azure-private-link-networking/</guid>
      <description>Hosted Dolt now supports Azure Private Link. You can create a new Hosted Dolt instance on Azure with Private Link support in just a few clicks.</description>
      <pubDate>Wed, 06 May 2026 00:00:00 GMT</pubDate>
      <content:encoded>&lt;p&gt;&lt;a href="https://hosted.doltdb.com"&gt;Hosted Dolt&lt;/a&gt; is a fully managed &lt;a href="https://doltdb.com"&gt;Dolt&lt;/a&gt; deployment available on AWS, GCP,
and, most recently, &lt;a href="https://www.dolthub.com/blog/2026-04-13-hosted-dolt-on-azure/"&gt;Azure&lt;/a&gt;. The initial offering of
&lt;a href="https://hosted.doltdb.com"&gt;Hosted Dolt&lt;/a&gt; on Azure only supported publicly accessible deployments. Today we are excited to
announce the same level of private networking support for Azure that we already have for AWS and GCP.&lt;/p&gt;
&lt;h1 id="what-is-azure-private-link"&gt;What is Azure Private Link?&lt;a class="anchor-link" aria-label="Link to heading" href="#what-is-azure-private-link"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;&lt;a href="https://learn.microsoft.com/en-us/azure/private-link/"&gt;Azure Private Link&lt;/a&gt; is a service that allows you to access VNets
in other Azure subscriptions and tenants securely through Azure’s network. This means that you can create a
&lt;a href="https://hosted.doltdb.com"&gt;Hosted Dolt&lt;/a&gt; deployment on Azure that is only accessible to your private Azure infrastructure,
and not accessible over the public internet.&lt;/p&gt;
&lt;h1 id="creating-a-deployment-with-azure-private-link"&gt;Creating a Deployment with Azure Private Link&lt;a class="anchor-link" aria-label="Link to heading" href="#creating-a-deployment-with-azure-private-link"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;The first thing you will need to do is get the subscription ID of the Azure subscription that you want to use for your
private network (You can find this in the Azure portal). Once you have the subscription ID, create a new
&lt;a href="https://hosted.doltdb.com"&gt;Hosted Dolt&lt;/a&gt; deployment on Azure and enable the “Private deployment” option on the “Advanced”
tab of the deployment creation form. Then in the “Allowed subscription IDs” box enter the subscription ID(s) that you
want to allow access to your deployment.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://static.dolthub.com/blogimages/azpl-private-deployment.png/3f91ea8acc2765d125f344103f351d837be35af7358f3bab29ecd5d149f65246.webp" alt="Private Deployment"&gt;&lt;/p&gt;
&lt;p&gt;Once you have that filled out, click “Next” and review your deployment choices. Finally, click “Create Deployment” and
your instance will be up and running in minutes. Once your deployment is running, you will see the connections tab populated.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://static.dolthub.com/blogimages/azpl-connections-tab.png/ea3fe6603afd7178aa0b2c6c6888803c860d335ba81f4fa2dd79e27bb675b3a3.webp" alt="Connections Tab"&gt;&lt;/p&gt;
&lt;p&gt;And the “Azure Private Link Networking” section of the connections tab will show you the information you will need to
connect your private Azure infrastructure to your &lt;a href="https://hosted.doltdb.com"&gt;Hosted Dolt&lt;/a&gt; deployment.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://static.dolthub.com/blogimages/azpl-azure-private-link-networking.png/0d99904e90b1c2937de67f46bd6ffc0be0976d3578631a5aa793554f612576ca.webp" alt="Azure Private Link Networking"&gt;&lt;/p&gt;
&lt;h1 id="connecting-to-your-private-deployment"&gt;Connecting to your Private Deployment&lt;a class="anchor-link" aria-label="Link to heading" href="#connecting-to-your-private-deployment"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;By taking the information from the “Azure Private Link Networking” section of the connections tab, you can connect
your private Azure infrastructure using the web portal, Azure CLI, or you could use Terraform. I won’t go through
the web portal, but I will cover the Azure CLI and Terraform options.&lt;/p&gt;
&lt;h2 id="connecting-your-infrastructure-with-the-azure-cli"&gt;Connecting your Infrastructure with the Azure CLI&lt;a class="anchor-link" aria-label="Link to heading" href="#connecting-your-infrastructure-with-the-azure-cli"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;In order to connect your infrastructure you will need the region, resource group, virtual network, and subnet of the
private network that you want to connect to your deployment.&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;RESOURCE_GROUP&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;"my-resource-group"&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;REGION&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;"eastus2"&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;VNET&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;"my-vnet"&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;SUBNET&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;"my-subnet"&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;then we will take the information from the “Azure Private Link Networking” section of the connections tab&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;PRIVATE_LINK_SERVICE_ID&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;"/subscriptions/01234567-89ab-cdef-fedc-ba9876543210/resourceGroups/networking-dev/providers/Microsoft.Network/privateLinkServices/pls-01234567-89ab-cdef-fedc-ba9876543210"&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;ENDPOINT_NAME&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;"test-az-priv"&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;URL&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;"test-az-priv.pls.dbs.hosted.doltdb.com"&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With that information you need to create an Azure “Private Endpoint” and then set up DNS resolution for the private
endpoint so that you can connect to your deployment using the url provided in the “Azure Private Link Networking” section
of the connections tab.&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;# Create primary&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;az&lt;/span&gt;&lt;span&gt; network&lt;/span&gt;&lt;span&gt; private-endpoint&lt;/span&gt;&lt;span&gt; create&lt;/span&gt;&lt;span&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;    --resource-group&lt;/span&gt;&lt;span&gt; "&lt;/span&gt;&lt;span&gt;$RESOURCE_GROUP&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;    --name&lt;/span&gt;&lt;span&gt; "dolt-${&lt;/span&gt;&lt;span&gt;ENDPOINT_NAME&lt;/span&gt;&lt;span&gt;}-private-endpoint"&lt;/span&gt;&lt;span&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;    --location&lt;/span&gt;&lt;span&gt; "&lt;/span&gt;&lt;span&gt;$REGION&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;    --vnet-name&lt;/span&gt;&lt;span&gt; "&lt;/span&gt;&lt;span&gt;$VNET&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;    --subnet&lt;/span&gt;&lt;span&gt; "&lt;/span&gt;&lt;span&gt;$SUBNET&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;    --private-connection-resource-id&lt;/span&gt;&lt;span&gt; "&lt;/span&gt;&lt;span&gt;$PRIVATE_LINK_SERVICE_ID&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;    --connection-name&lt;/span&gt;&lt;span&gt; "dolt-${&lt;/span&gt;&lt;span&gt;ENDPOINT_NAME&lt;/span&gt;&lt;span&gt;}-connection"&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;# Retrieve private IP from the endpoint NIC&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;PE_NIC_ID&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;$(&lt;/span&gt;&lt;span&gt;az&lt;/span&gt;&lt;span&gt; network&lt;/span&gt;&lt;span&gt; private-endpoint&lt;/span&gt;&lt;span&gt; show&lt;/span&gt;&lt;span&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;    --resource-group&lt;/span&gt;&lt;span&gt; "&lt;/span&gt;&lt;span&gt;$RESOURCE_GROUP&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;    --name&lt;/span&gt;&lt;span&gt; "dolt-${&lt;/span&gt;&lt;span&gt;ENDPOINT_NAME&lt;/span&gt;&lt;span&gt;}-private-endpoint"&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; "networkInterfaces[0].id"&lt;/span&gt;&lt;span&gt; -o&lt;/span&gt;&lt;span&gt; tsv&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;PE_IP&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;$(&lt;/span&gt;&lt;span&gt;az&lt;/span&gt;&lt;span&gt; network&lt;/span&gt;&lt;span&gt; nic&lt;/span&gt;&lt;span&gt; show&lt;/span&gt;&lt;span&gt; --ids&lt;/span&gt;&lt;span&gt; "&lt;/span&gt;&lt;span&gt;$PE_NIC_ID&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;    --query&lt;/span&gt;&lt;span&gt; "ipConfigurations[0].privateIPAddress"&lt;/span&gt;&lt;span&gt; -o&lt;/span&gt;&lt;span&gt; tsv&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;# Create DNS zone and VNet link&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt; az&lt;/span&gt;&lt;span&gt; network&lt;/span&gt;&lt;span&gt; private-dns&lt;/span&gt;&lt;span&gt; zone&lt;/span&gt;&lt;span&gt; create&lt;/span&gt;&lt;span&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;    --resource-group&lt;/span&gt;&lt;span&gt; "&lt;/span&gt;&lt;span&gt;$RESOURCE_GROUP&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;    --name&lt;/span&gt;&lt;span&gt; "pls.dbs.hosted.doltdb.com"&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;az&lt;/span&gt;&lt;span&gt; network&lt;/span&gt;&lt;span&gt; private-dns&lt;/span&gt;&lt;span&gt; link&lt;/span&gt;&lt;span&gt; vnet&lt;/span&gt;&lt;span&gt; create&lt;/span&gt;&lt;span&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;    --resource-group&lt;/span&gt;&lt;span&gt; "&lt;/span&gt;&lt;span&gt;$RESOURCE_GROUP&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;    --zone-name&lt;/span&gt;&lt;span&gt; "pls.dbs.hosted.doltdb.com"&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; "dolt-${&lt;/span&gt;&lt;span&gt;ENDPOINT_NAME&lt;/span&gt;&lt;span&gt;}-vnet-link"&lt;/span&gt;&lt;span&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;    --virtual-network&lt;/span&gt;&lt;span&gt; "&lt;/span&gt;&lt;span&gt;$VNET&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;    --registration-enabled&lt;/span&gt;&lt;span&gt; false&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;# Register primary DNS&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;az&lt;/span&gt;&lt;span&gt; network&lt;/span&gt;&lt;span&gt; private-dns&lt;/span&gt;&lt;span&gt; record-set&lt;/span&gt;&lt;span&gt; a&lt;/span&gt;&lt;span&gt; add-record&lt;/span&gt;&lt;span&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;    --resource-group&lt;/span&gt;&lt;span&gt; "&lt;/span&gt;&lt;span&gt;$RESOURCE_GROUP&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;    --zone-name&lt;/span&gt;&lt;span&gt; "pls.dbs.hosted.doltdb.com"&lt;/span&gt;&lt;span&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;    --record-set-name&lt;/span&gt;&lt;span&gt; "&lt;/span&gt;&lt;span&gt;$ENDPOINT_NAME&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;    --ipv4-address&lt;/span&gt;&lt;span&gt; "&lt;/span&gt;&lt;span&gt;$PE_IP&lt;/span&gt;&lt;span&gt;"&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now you should be able to connect to your deployment from your instances within the given VNet using the provided URL. If
you SSH onto one of your instances with the MySQL client installed, you can connect to your deployment using the MySQL
command provided on the “Connections” tab of your deployment.&lt;/p&gt;
&lt;p&gt;The process for connecting a read endpoint is very similar if you are using replication.&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;READ_PRIVATE_LINK_SERVICE_ID&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;"/subscriptions/01234567-89ab-cdef-fedc-ba9876543210/resourceGroups/networking-dev/providers/Microsoft.Network/privateLinkServices/pls-read-01234567-89ab-cdef-fedc-ba9876543210"&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;READ_ENDPOINT_NAME&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;"read-test-az-priv"&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;READ_URL&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;"read-test-az-priv.pls.dbs.hosted.doltdb.com"&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;az&lt;/span&gt;&lt;span&gt; network&lt;/span&gt;&lt;span&gt; private-endpoint&lt;/span&gt;&lt;span&gt; create&lt;/span&gt;&lt;span&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;    --resource-group&lt;/span&gt;&lt;span&gt; "&lt;/span&gt;&lt;span&gt;$RESOURCE_GROUP&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;    --name&lt;/span&gt;&lt;span&gt; "dolt-${&lt;/span&gt;&lt;span&gt;READ_ENDPOINT_NAME&lt;/span&gt;&lt;span&gt;}-private-endpoint"&lt;/span&gt;&lt;span&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;    --location&lt;/span&gt;&lt;span&gt; "&lt;/span&gt;&lt;span&gt;$REGION&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;    --vnet-name&lt;/span&gt;&lt;span&gt; "&lt;/span&gt;&lt;span&gt;$VNET&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;    --subnet&lt;/span&gt;&lt;span&gt; "&lt;/span&gt;&lt;span&gt;$SUBNET&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;    --private-connection-resource-id&lt;/span&gt;&lt;span&gt; "&lt;/span&gt;&lt;span&gt;$READ_PRIVATE_LINK_SERVICE_ID&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;    --connection-name&lt;/span&gt;&lt;span&gt; "dolt-${&lt;/span&gt;&lt;span&gt;READ_ENDPOINT_NAME&lt;/span&gt;&lt;span&gt;}-connection"&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;READ_PE_NIC_ID&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;$(&lt;/span&gt;&lt;span&gt;az&lt;/span&gt;&lt;span&gt; network&lt;/span&gt;&lt;span&gt; private-endpoint&lt;/span&gt;&lt;span&gt; show&lt;/span&gt;&lt;span&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;    --resource-group&lt;/span&gt;&lt;span&gt; "&lt;/span&gt;&lt;span&gt;$RESOURCE_GROUP&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;    --name&lt;/span&gt;&lt;span&gt; "dolt-${&lt;/span&gt;&lt;span&gt;READ_ENDPOINT_NAME&lt;/span&gt;&lt;span&gt;}-private-endpoint"&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; "networkInterfaces[0].id"&lt;/span&gt;&lt;span&gt; -o&lt;/span&gt;&lt;span&gt; tsv&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;READ_PE_IP&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;$(&lt;/span&gt;&lt;span&gt;az&lt;/span&gt;&lt;span&gt; network&lt;/span&gt;&lt;span&gt; nic&lt;/span&gt;&lt;span&gt; show&lt;/span&gt;&lt;span&gt; --ids&lt;/span&gt;&lt;span&gt; "&lt;/span&gt;&lt;span&gt;$READ_PE_NIC_ID&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;    --query&lt;/span&gt;&lt;span&gt; "ipConfigurations[0].privateIPAddress"&lt;/span&gt;&lt;span&gt; -o&lt;/span&gt;&lt;span&gt; tsv&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;az&lt;/span&gt;&lt;span&gt; network&lt;/span&gt;&lt;span&gt; private-dns&lt;/span&gt;&lt;span&gt; record-set&lt;/span&gt;&lt;span&gt; a&lt;/span&gt;&lt;span&gt; add-record&lt;/span&gt;&lt;span&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;    --resource-group&lt;/span&gt;&lt;span&gt; "&lt;/span&gt;&lt;span&gt;$RESOURCE_GROUP&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;    --zone-name&lt;/span&gt;&lt;span&gt; "pls.dbs.hosted.doltdb.com"&lt;/span&gt;&lt;span&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;    --record-set-name&lt;/span&gt;&lt;span&gt; "${&lt;/span&gt;&lt;span&gt;READ_ENDPOINT_NAME&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;    --ipv4-address&lt;/span&gt;&lt;span&gt; "&lt;/span&gt;&lt;span&gt;$READ_PE_IP&lt;/span&gt;&lt;span&gt;"&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id="terraform"&gt;Terraform&lt;a class="anchor-link" aria-label="Link to heading" href="#terraform"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The same thing can be accomplished with Terraform.  Here is an example Terraform configuration that will create a private
endpoint and set up DNS resolution for that endpoint. The “Variables” section of the configuration should be filled out
with the appropriate values for your deployment and private network. Many of these values may come from the output of other
Terraform configurations that manage your Azure infrastructure, or you could just fill them out manually.&lt;/p&gt;
&lt;pre class="astro-code github-dark" tabindex="0" data-language="hcl"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span&gt;terraform&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;  required_providers&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;    azurerm&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;      source  &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; "hashicorp/azurerm"&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;      version &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; "~&gt; 3.0"&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;provider&lt;/span&gt;&lt;span&gt; "azurerm"&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;  features&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;
&lt;span class="line"&gt;&lt;span&gt;# Variables&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;variable&lt;/span&gt;&lt;span&gt; "resource_group"&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;  type&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;  description&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; "Azure resource group to create the private endpoints in"&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;variable&lt;/span&gt;&lt;span&gt; "region"&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;  type&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;  description&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; "Azure region (e.g. eastus)"&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;variable&lt;/span&gt;&lt;span&gt; "vnet"&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;  type&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;  description&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; "Name of the VNet to attach the private endpoints to"&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;variable&lt;/span&gt;&lt;span&gt; "subnet"&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;  type&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;  description&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; "Name of the subnet within the VNet for the private endpoints"&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;variable&lt;/span&gt;&lt;span&gt; "endpoint_name"&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;  type&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;  description&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; "Hosted Dolt endpoint name (used to name Azure resources and DNS records)"&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;variable&lt;/span&gt;&lt;span&gt; "read_endpoint_name"&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;  type&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;  description&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; "Hosted Dolt read endpoint name (used to name Azure resources and DNS records)"&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;variable&lt;/span&gt;&lt;span&gt; "private_link_service_id"&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;  type&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;  description&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; "Resource ID of the primary Private Link Service"&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;variable&lt;/span&gt;&lt;span&gt; "read_private_link_service_id"&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;  type&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;  description&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; "Resource ID of the read-replica Private Link Service"&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;
&lt;span class="line"&gt;&lt;span&gt;# Data sources&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;data&lt;/span&gt;&lt;span&gt; "azurerm_subnet"&lt;/span&gt;&lt;span&gt; "endpoint_subnet"&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; var&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;subnet&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;  virtual_network_name&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; var&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;vnet&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;  resource_group_name&lt;/span&gt;&lt;span&gt;  =&lt;/span&gt;&lt;span&gt; var&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;resource_group&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;
&lt;span class="line"&gt;&lt;span&gt;# Private endpoints&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;resource&lt;/span&gt;&lt;span&gt; "azurerm_private_endpoint"&lt;/span&gt;&lt;span&gt; "primary"&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; "dolt-&lt;/span&gt;&lt;span&gt;${&lt;/span&gt;&lt;span&gt;var&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;endpoint_name&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;-private-endpoint"&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;  location&lt;/span&gt;&lt;span&gt;            =&lt;/span&gt;&lt;span&gt; var&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;region&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;  resource_group_name&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; var&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;resource_group&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;  subnet_id&lt;/span&gt;&lt;span&gt;           =&lt;/span&gt;&lt;span&gt; data&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;azurerm_subnet&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;endpoint_subnet&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;  private_service_connection&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; "dolt-&lt;/span&gt;&lt;span&gt;${&lt;/span&gt;&lt;span&gt;var&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;endpoint_name&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;-connection"&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;    private_connection_resource_id&lt;/span&gt;&lt;span&gt;    =&lt;/span&gt;&lt;span&gt; var&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;private_link_service_id&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;    is_manual_connection&lt;/span&gt;&lt;span&gt;              =&lt;/span&gt;&lt;span&gt; false&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;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;resource&lt;/span&gt;&lt;span&gt; "azurerm_private_endpoint"&lt;/span&gt;&lt;span&gt; "read"&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; "dolt-&lt;/span&gt;&lt;span&gt;${&lt;/span&gt;&lt;span&gt;var&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;read_endpoint_name&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;-private-endpoint"&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;  location&lt;/span&gt;&lt;span&gt;            =&lt;/span&gt;&lt;span&gt; var&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;region&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;  resource_group_name&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; var&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;resource_group&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;  subnet_id&lt;/span&gt;&lt;span&gt;           =&lt;/span&gt;&lt;span&gt; data&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;azurerm_subnet&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;endpoint_subnet&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;  private_service_connection&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; "dolt-&lt;/span&gt;&lt;span&gt;${&lt;/span&gt;&lt;span&gt;var&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;read_endpoint_name&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;-connection"&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;    private_connection_resource_id&lt;/span&gt;&lt;span&gt;    =&lt;/span&gt;&lt;span&gt; var&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;read_private_link_service_id&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;    is_manual_connection&lt;/span&gt;&lt;span&gt;              =&lt;/span&gt;&lt;span&gt; false&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;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;
&lt;span class="line"&gt;&lt;span&gt;# Private DNS zone and VNet link&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;resource&lt;/span&gt;&lt;span&gt; "azurerm_private_dns_zone"&lt;/span&gt;&lt;span&gt; "dolt"&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; "pls.dbs.hosted.doltdb.com"&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;  resource_group_name&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; var&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;resource_group&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;resource&lt;/span&gt;&lt;span&gt; "azurerm_private_dns_zone_virtual_network_link"&lt;/span&gt;&lt;span&gt; "dolt"&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; "dolt-&lt;/span&gt;&lt;span&gt;${&lt;/span&gt;&lt;span&gt;var&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;endpoint_name&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;-vnet-link"&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;  resource_group_name&lt;/span&gt;&lt;span&gt;   =&lt;/span&gt;&lt;span&gt; var&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;resource_group&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;  private_dns_zone_name&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; azurerm_private_dns_zone&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;dolt&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;  virtual_network_id&lt;/span&gt;&lt;span&gt;    =&lt;/span&gt;&lt;span&gt; data&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;azurerm_subnet&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;endpoint_subnet&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;virtual_network_id&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;  registration_enabled&lt;/span&gt;&lt;span&gt;  =&lt;/span&gt;&lt;span&gt; false&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;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;# DNS A records&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;resource&lt;/span&gt;&lt;span&gt; "azurerm_private_dns_a_record"&lt;/span&gt;&lt;span&gt; "primary"&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; var&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;endpoint_name&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;  zone_name&lt;/span&gt;&lt;span&gt;           =&lt;/span&gt;&lt;span&gt; azurerm_private_dns_zone&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;dolt&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;  resource_group_name&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; var&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;resource_group&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;  ttl&lt;/span&gt;&lt;span&gt;                 =&lt;/span&gt;&lt;span&gt; 300&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;  records&lt;/span&gt;&lt;span&gt;             =&lt;/span&gt;&lt;span&gt; [azurerm_private_endpoint&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;primary&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;private_service_connection[&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;private_ip_address]&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;resource&lt;/span&gt;&lt;span&gt; "azurerm_private_dns_a_record"&lt;/span&gt;&lt;span&gt; "read"&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; "&lt;/span&gt;&lt;span&gt;${&lt;/span&gt;&lt;span&gt;var&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;read_endpoint_name&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;  zone_name&lt;/span&gt;&lt;span&gt;           =&lt;/span&gt;&lt;span&gt; azurerm_private_dns_zone&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;dolt&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;  resource_group_name&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; var&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;resource_group&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;  ttl&lt;/span&gt;&lt;span&gt;                 =&lt;/span&gt;&lt;span&gt; 300&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;  records&lt;/span&gt;&lt;span&gt;             =&lt;/span&gt;&lt;span&gt; [azurerm_private_endpoint&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;read&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;private_service_connection[&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;private_ip_address]&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;a href="https://hosted.doltdb.com"&gt;Hosted Dolt&lt;/a&gt; now supports Azure Private Link. You can create a new &lt;a href="https://hosted.doltdb.com"&gt;Hosted Dolt&lt;/a&gt;
deployment on Azure with Private Link support in just a few clicks. If you have any questions about using &lt;a href="https://hosted.doltdb.com"&gt;Hosted Dolt&lt;/a&gt;
on Azure, or any other cloud provider, or if you have feedback or feature requests, please join our &lt;a href="https://discord.gg/gqr7K4VNKe"&gt;Discord&lt;/a&gt; and let us know.&lt;/p&gt;</content:encoded>
      <dc:creator>Brian Hendriks</dc:creator>
      <category>hosted</category>
      <category>feature release</category>
    </item>
    <item>
      <title>Database Insurance</title>
      <link>https://dolthub.com/blog/2026-05-04-database-insurance/</link>
      <guid isPermaLink="true">https://dolthub.com/blog/2026-05-04-database-insurance/</guid>
      <description>Use a Dolt replica for database insurance against catastrophe or more mundane bad writes. In the human operator era, a Dolt replica was nice to have. In the agentic operator era, a Dolt replica is essential.</description>
      <pubDate>Mon, 04 May 2026 00:00:00 GMT</pubDate>
      <content:encoded>&lt;p&gt;Did you know &lt;a href="https://www.doltdb.com"&gt;Dolt&lt;/a&gt; and &lt;a href="https://www.doltgres.com"&gt;Doltgres&lt;/a&gt; can be &lt;a href="https://www.dolthub.com/blog/2023-03-15-getting-started-versioned-mysql-replica/"&gt;run as replicas&lt;/a&gt; to your existing MySQL, MariaDB, or Postgres databases? In this mode, Dolt acts as database insurance. Each transaction commit on your primary becomes a Dolt commit on your replica, preserving the history of your database in Dolt. You can use Dolt’s version control functionality on your replica for &lt;a href="https://www.dolthub.com/blog/2022-09-23-dolt-rollback-options/"&gt;all manner of rollback&lt;/a&gt;, including complex reverse patches of multiple transactions.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://static.dolthub.com/blogimages/this-is-fine.png/61736ebe94d4d0c9b65b8a311b6b4af5637c8c844953d5c95e4445049f7b0f28.webp" alt="Dolt. This is fine."&gt;&lt;/p&gt;
&lt;p&gt;Database insurance is becoming all the more important with the threat of agents going off the rails. It seems like not a month goes by without another “an agent deleted my database” story. Dolt can protect you from rogue agents without switching out your primary. Just add a Dolt replica. This article explains.&lt;/p&gt;
&lt;h1 id="agents-delete-databases"&gt;Agents Delete Databases&lt;a class="anchor-link" aria-label="Link to heading" href="#agents-delete-databases"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;This is no longer hypothetical. Agents delete databases.&lt;/p&gt;
&lt;p&gt;Last week, &lt;a href="https://www.tomshardware.com/tech-industry/artificial-intelligence/claude-powered-ai-coding-agent-deletes-entire-company-database-in-9-seconds-backups-zapped-after-cursor-tool-powered-by-anthropics-claude-goes-rogue"&gt;Pocket’s Claude-powered coding agent turned a staging task into a production incident&lt;/a&gt;. It deleted the company database and the attached backups in one shot. This is the cleanest possible example of the new failure mode: give an agent broad infra access and eventually it will speedrun your disaster recovery plan.&lt;/p&gt;
&lt;p&gt;In March, Alexey Grigorev asked Claude to help clean up AWS resources and &lt;a href="https://alexeyondata.substack.com/p/how-i-dropped-our-production-database"&gt;got an unexpected terraform destroy adventure instead&lt;/a&gt;. Production database gone. Snapshots gone. Support upgraded mid-incident. It reads like a normal ops postmortem except the culprit was an agent with cloud credentials.&lt;/p&gt;
&lt;p&gt;And in July last year, &lt;a href="https://www.pcmag.com/news/vibe-coding-fiasco-replite-ai-agent-goes-rogue-deletes-company-database"&gt;after Replit’s agent deleted a database&lt;/a&gt;, Replit’s CEO said the lesson was that agents should not have access to production databases. Fair enough. But is that achievable? Agents are going to find their way into production workflows, whether through direct SQL, admin tools, or application APIs. The practical question is not whether mistakes will happen but rather how much damage is done when they do.&lt;/p&gt;
&lt;p&gt;These stories get attention because they are spectacular. “Entire company database deleted in nine seconds” is a headline. But the underlying issue is not new. Any tool, human- or agent-driven, that has production credentials, broad access, and enough autonomy to make a lot of changes very quickly is dangerous.&lt;/p&gt;
&lt;p&gt;Databases were already at-risk. Agents just increased the blast radius.&lt;/p&gt;
&lt;p&gt;The old production failure mode was a tired human typing &lt;code&gt;DROP TABLE&lt;/code&gt; into the wrong terminal. The new production failure mode is an enthusiastic agent with API keys, shell access, a task list, and a limited context window.&lt;/p&gt;
&lt;h1 id="agents-write-junk"&gt;Agents Write Junk&lt;a class="anchor-link" aria-label="Link to heading" href="#agents-write-junk"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;Outright deletion is the catastrophe case. It is easy to notice. It makes the news. Much more common is quiet failure when an agent writes junk to production.&lt;/p&gt;
&lt;p&gt;Maybe it backfills the wrong values. Maybe it misinterprets a schema. Maybe it “fixes” a bug by overwriting a field everywhere. Maybe it makes a long sequence of individually valid writes that are collectively nonsense. In many cases the agent is not even talking SQL directly. It is calling your production API, which is often worse because the damage looks like legitimate application traffic. I would guess for every “the agent deleted my database” story, there are hundreds of “the agent wrote bad data into production” stories.&lt;/p&gt;
&lt;p&gt;A recent article on the subject, &lt;a href="https://arpitbhayani.me/blogs/defensive-databases/"&gt;“Databases Were Not Designed For This”&lt;/a&gt; by Arpit Bhayani, generated &lt;a href="https://news.ycombinator.com/item?id=47897140"&gt;a lively Hacker News discussion&lt;/a&gt;. Arpit’s point is that databases were built for boring callers: deterministic apps, reviewed writes, short-lived connections, obvious failures. Agents break every one of those assumptions at once, so a bunch of database hygiene practices that were once “nice to have” — statement timeouts, idempotency keys, soft deletes, append-only logs, role-per-agent, query tagging, the works — suddenly become load-bearing infrastructure. I think that framing is right. The database is no longer talking to careful application code but to an agent who must fix the issue before its context fills up.&lt;/p&gt;
&lt;p&gt;This is where normal backups start to feel insufficient. Backups are great for disaster recovery. They are less great for “between 2:13 PM and 2:21 PM, the agent wrote garbage into three related tables, and I need to surgically undo those writes specifically”.&lt;/p&gt;
&lt;p&gt;That is a version control problem, and Dolt is the world’s &lt;a href="https://www.dolthub.com/blog/2022-08-04-database-versioning/"&gt;first and only version-controlled database&lt;/a&gt;.&lt;/p&gt;
&lt;h1 id="protect-yourself"&gt;Protect Yourself&lt;a class="anchor-link" aria-label="Link to heading" href="#protect-yourself"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;The easiest way to get database insurance is to run a Dolt replica on a separate host. You keep your existing primary. MySQL stays MySQL. MariaDB stays MariaDB. Postgres stays Postgres. Dolt or Doltgres sits downstream as a replica, ingesting &lt;code&gt;binlog&lt;/code&gt; or WAL changes and turning each committed transaction into a durable Dolt commit.&lt;/p&gt;
&lt;p&gt;Setup is the same basic shape as standard replication. No application rewrite. No cutover. No “replace your database with our database” project. Just add a replica.&lt;/p&gt;
&lt;p&gt;The result is useful immediately. You get:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A continuously updated copy of production.&lt;/li&gt;
&lt;li&gt;A commit history of every transaction.&lt;/li&gt;
&lt;li&gt;Diffs over time.&lt;/li&gt;
&lt;li&gt;Branches if you need to experiment.&lt;/li&gt;
&lt;li&gt;Rollback tools much more expressive than “restore last night’s backup”.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In the pre-agent era, this was nice to have. With Claude and Codex in all your engineer’s hands, it looks more like a necessity.&lt;/p&gt;
&lt;h2 id="catastrophe"&gt;Catastrophe&lt;a class="anchor-link" aria-label="Link to heading" href="#catastrophe"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Backups are still your number one defense here. If a machine dies, a region disappears, or an attacker wipes out infrastructure, backups are what save you. A Dolt replica is defense in depth.&lt;/p&gt;
&lt;p&gt;It gives you another live copy of the database and a detailed transaction history. If your primary gets destroyed by a rogue agent, the Dolt replica can tell you exactly what happened and when. If you push that replica to DoltHub or another remote, you now have true off-host safety with different access credentials as well.&lt;/p&gt;
&lt;p&gt;This is the right layering:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Backups for disaster recovery.&lt;/li&gt;
&lt;li&gt;Replication for availability.&lt;/li&gt;
&lt;li&gt;Dolt replication for history, auditability, and surgical undo.&lt;/li&gt;
&lt;li&gt;Dolt remote as disaster recovery if all else fails.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;You want all four.&lt;/p&gt;
&lt;h2 id="bad-writes"&gt;Bad Writes&lt;a class="anchor-link" aria-label="Link to heading" href="#bad-writes"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Bad writes are where a Dolt replica really shines.&lt;/p&gt;
&lt;p&gt;First, find the bad writes. Use &lt;a href="https://docs.dolthub.com/sql-reference/version-control/dolt-system-tables#dolt_log"&gt;&lt;code&gt;dolt_log&lt;/code&gt;&lt;/a&gt; to identify the suspicious time window or commit range. Use &lt;a href="https://docs.dolthub.com/sql-reference/version-control/dolt-sql-functions#dolt_diff"&gt;&lt;code&gt;dolt_diff()&lt;/code&gt;&lt;/a&gt; to inspect exactly what changed. Because the writes are preserved as commits, you are not guessing. You are looking at history.&lt;/p&gt;
&lt;p&gt;Then undo them on &lt;a href="https://docs.dolthub.com/sql-reference/version-control/branches"&gt;a branch&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Sometimes a simple &lt;a href="https://docs.dolthub.com/sql-reference/version-control/dolt-sql-procedures#dolt_revert"&gt;&lt;code&gt;dolt_revert()&lt;/code&gt;&lt;/a&gt; is enough. Other times, you want to reverse a sequence of commits or a custom patch that keeps the good changes and removes only the bad ones. Dolt gives you those tools. Once you have the corrective patch, you can get the SQL you need to apply to your primary with &lt;a href="https://docs.dolthub.com/sql-reference/version-control/dolt-sql-functions#dolt_patch"&gt;&lt;code&gt;dolt_patch()&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;This is a much better story than:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Restore backup to staging.&lt;/li&gt;
&lt;li&gt;Diff it manually against production.&lt;/li&gt;
&lt;li&gt;Write custom SQL.&lt;/li&gt;
&lt;li&gt;Hope you found all the bad rows.&lt;/li&gt;
&lt;li&gt;Hope you didn’t revert good writes too.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;With Dolt, the database history is already organized into commits. That makes complex rollback possible.&lt;/p&gt;
&lt;p&gt;That is what “database insurance” means in practice. Not just “I have a copy somewhere”, but “I can understand what happened and undo it precisely”.&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;Use a Dolt replica for database insurance against catastrophe or more mundane bad writes. In the human operator era, a Dolt replica was nice to have. In the agentic operator era, a Dolt replica is essential. Questions? Come by &lt;a href="https://discord.gg/gqr7K4VNKe"&gt;our Discord&lt;/a&gt;. We’re happy to help get your replica set up.&lt;/p&gt;</content:encoded>
      <dc:creator>Tim Sehn</dc:creator>
      <category>dolt</category>
      <category>doltgres</category>
      <category>use case</category>
    </item>
    <item>
      <title>Announcing Functional Indexes in Dolt</title>
      <link>https://dolthub.com/blog/2026-04-29-announcing-functional-indexes-in-dolt/</link>
      <guid isPermaLink="true">https://dolthub.com/blog/2026-04-29-announcing-functional-indexes-in-dolt/</guid>
      <description>Dolt now supports functional indexes, allowing you to index expressions and function results for faster queries on computed values.</description>
      <pubDate>Wed, 29 Apr 2026 00:00:00 GMT</pubDate>
      <content:encoded>&lt;p&gt;&lt;a href="https://doltdb.com/"&gt;Dolt&lt;/a&gt; is the world’s first SQL database with Git-style version control built in. Dolt gives you all the power of SQL combined with the ability to branch, merge, diff, clone, and push your data, just like you would with source code in Git. Today we’re excited to announce that Dolt now supports &lt;strong&gt;functional indexes&lt;/strong&gt;!&lt;/p&gt;
&lt;p&gt;Functional indexes have been part of &lt;a href="https://dev.mysql.com/doc/refman/8.0/en/create-index.html#create-index-functional-key-parts"&gt;MySQL since MySQL 8.0&lt;/a&gt;. They’re not something every application needs, but when they are needed, they can have a &lt;strong&gt;big&lt;/strong&gt; impact on query performance, as we’ll see later in this post. This is a feature we’ve wanted to build for a long time, and after receiving a few customer requests for it, we decided to prioritize it. We’ve launched the initial support in Dolt and are now expanding that to add this feature to Doltgres, add additional syntax support, enable usage of the functional indexes in more queries, and fine tune performance.&lt;/p&gt;
&lt;p&gt;In this blog post, we’ll walk through what functional indexes are, how they work, how to use them, and see them in action.&lt;/p&gt;
&lt;h2 id="what-is-a-functional-index"&gt;What is a Functional Index?&lt;a class="anchor-link" aria-label="Link to heading" href="#what-is-a-functional-index"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;A regular index stores the values of one or more columns and lets Dolt quickly look up rows by those raw column values. A functional index is similar, but instead of storing a column’s raw value, it stores the result of a function or expression applied to that column. That stored result is what gets indexed, so when a query contains the same expression in its &lt;code&gt;WHERE&lt;/code&gt; clause, Dolt can use the index to satisfy the query efficiently — without scanning the full table and evaluating the expression row by row.&lt;/p&gt;
&lt;p&gt;The syntax uses an extra set of parentheses to mark the expression:&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; INDEX&lt;/span&gt;&lt;span&gt; idx_lower_email&lt;/span&gt;&lt;span&gt; ON&lt;/span&gt;&lt;span&gt; users ((&lt;/span&gt;&lt;span&gt;LOWER&lt;/span&gt;&lt;span&gt;(email)));&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The double parentheses are important — that’s how MySQL and Dolt distinguish a functional expression from a regular column reference inside an index definition.&lt;/p&gt;
&lt;h2 id="when-does-this-help"&gt;When Does This Help?&lt;a class="anchor-link" aria-label="Link to heading" href="#when-does-this-help"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Let’s look at a common scenario for functional indexes. Imagine you have a &lt;code&gt;users&lt;/code&gt; table and you want to look up users by email address, but you don’t want &lt;code&gt;User@Example.COM&lt;/code&gt; and &lt;code&gt;user@example.com&lt;/code&gt; to be treated as different entries. There are several ways to handle this, including normalizing values to lowercase when filtering on 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;SELECT&lt;/span&gt;&lt;span&gt; *&lt;/span&gt;&lt;span&gt; FROM&lt;/span&gt;&lt;span&gt; users &lt;/span&gt;&lt;span&gt;WHERE&lt;/span&gt;&lt;span&gt; LOWER&lt;/span&gt;&lt;span&gt;(email) &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; LOWER&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;'User@Example.COM'&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Without a functional index on &lt;code&gt;LOWER(email)&lt;/code&gt;, satisfying that query requires a full table scan. The database engine has to evaluate &lt;code&gt;LOWER(email)&lt;/code&gt; for every single row and compare it against your search value. On a large table, that’s going to be slow.&lt;/p&gt;
&lt;p&gt;A functional index solves this cleanly, without requiring you to store a redundant &lt;code&gt;email_lower&lt;/code&gt; column purely for indexing purposes.&lt;/p&gt;
&lt;p&gt;Other common cases for using a functional index include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Date part extraction&lt;/strong&gt; — index on &lt;code&gt;YEAR(created_at)&lt;/code&gt; or &lt;code&gt;MONTH(order_date)&lt;/code&gt; for reporting queries that filter by year or month&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;JSON path extraction&lt;/strong&gt; — index on &lt;code&gt;JSON_UNQUOTE(JSON_EXTRACT(metadata, '$.type'))&lt;/code&gt; to speed up queries on specific fields inside a JSON column&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;String transformations&lt;/strong&gt; — indexing trimmed or normalized versions of values without duplicating the data in an extra column&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="getting-started"&gt;Getting Started&lt;a class="anchor-link" aria-label="Link to heading" href="#getting-started"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Let’s see functional indexes in action. We’ll use the &lt;a href="https://www.dolthub.com/repositories/dolthub/employees"&gt;dolthub/employees&lt;/a&gt; database from DoltHub — a sample database with 300,024 employees and 443,308 title records, which gives us enough data to see a real difference in query performance.&lt;/p&gt;
&lt;p&gt;If you don’t have Dolt yet, &lt;a href="https://docs.dolthub.com/introduction/installation"&gt;install it here&lt;/a&gt;. Then clone the database:&lt;/p&gt;
&lt;pre class="astro-code github-dark" tabindex="0" data-language="sh"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span&gt;$&lt;/span&gt;&lt;span&gt; dolt&lt;/span&gt;&lt;span&gt; clone&lt;/span&gt;&lt;span&gt; dolthub/employees&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;$&lt;/span&gt;&lt;span&gt; cd&lt;/span&gt;&lt;span&gt; employees&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;From here, you can run &lt;code&gt;dolt sql&lt;/code&gt; to start a SQL shell:&lt;/p&gt;
&lt;pre class="astro-code github-dark" tabindex="0" data-language="sh"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span&gt;$&lt;/span&gt;&lt;span&gt; dolt&lt;/span&gt;&lt;span&gt; sql&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;# Welcome to the DoltSQL shell.&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;# Statements must be terminated with ';'.&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;# "exit" or "quit" (or Ctrl-D) to exit. "\help" for help.&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;employees/main&lt;/span&gt;&lt;span&gt;&gt;  &lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then you can start exploring the database:&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;employees&lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt;main&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;span&gt; DESCRIBE employees;&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;| Field      | &lt;/span&gt;&lt;span&gt;Type&lt;/span&gt;&lt;span&gt;          | &lt;/span&gt;&lt;span&gt;Null&lt;/span&gt;&lt;span&gt; | &lt;/span&gt;&lt;span&gt;Key&lt;/span&gt;&lt;span&gt; | &lt;/span&gt;&lt;span&gt;Default&lt;/span&gt;&lt;span&gt; | Extra |&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;| emp_no     | &lt;/span&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt;           | &lt;/span&gt;&lt;span&gt;NO&lt;/span&gt;&lt;span&gt;   | PRI | &lt;/span&gt;&lt;span&gt;NULL&lt;/span&gt;&lt;span&gt;    |       |&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;| birth_date | &lt;/span&gt;&lt;span&gt;date&lt;/span&gt;&lt;span&gt;          | &lt;/span&gt;&lt;span&gt;NO&lt;/span&gt;&lt;span&gt;   |     | &lt;/span&gt;&lt;span&gt;NULL&lt;/span&gt;&lt;span&gt;    |       |&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;| first_name | &lt;/span&gt;&lt;span&gt;varchar&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;14&lt;/span&gt;&lt;span&gt;)   | &lt;/span&gt;&lt;span&gt;NO&lt;/span&gt;&lt;span&gt;   |     | &lt;/span&gt;&lt;span&gt;NULL&lt;/span&gt;&lt;span&gt;    |       |&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;| last_name  | &lt;/span&gt;&lt;span&gt;varchar&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;16&lt;/span&gt;&lt;span&gt;)   | &lt;/span&gt;&lt;span&gt;NO&lt;/span&gt;&lt;span&gt;   |     | &lt;/span&gt;&lt;span&gt;NULL&lt;/span&gt;&lt;span&gt;    |       |&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;| gender     | enum(&lt;/span&gt;&lt;span&gt;'M'&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt;'F'&lt;/span&gt;&lt;span&gt;) | &lt;/span&gt;&lt;span&gt;NO&lt;/span&gt;&lt;span&gt;   |     | &lt;/span&gt;&lt;span&gt;NULL&lt;/span&gt;&lt;span&gt;    |       |&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;| hire_date  | &lt;/span&gt;&lt;span&gt;date&lt;/span&gt;&lt;span&gt;          | &lt;/span&gt;&lt;span&gt;NO&lt;/span&gt;&lt;span&gt;   |     | &lt;/span&gt;&lt;span&gt;NULL&lt;/span&gt;&lt;span&gt;    |       |&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt;------------+---------------+------+-----+---------+-------+&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Out of the box, the only index is the primary key on &lt;code&gt;emp_no&lt;/code&gt;. There’s no index on &lt;code&gt;last_name&lt;/code&gt;. Suppose we want to find all employees by last name, but we need the search to be case-insensitive — the data was imported from multiple sources and the casing isn’t consistent. We write:&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; emp_no, first_name, last_name &lt;/span&gt;&lt;span&gt;FROM&lt;/span&gt;&lt;span&gt; employees &lt;/span&gt;&lt;span&gt;WHERE&lt;/span&gt;&lt;span&gt; LOWER&lt;/span&gt;&lt;span&gt;(last_name) &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; 'facello'&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id="without-a-functional-index"&gt;Without a Functional Index&lt;a class="anchor-link" aria-label="Link to heading" href="#without-a-functional-index"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Let’s look at the query plan Dolt uses when there’s no index. We’ll use &lt;code&gt;EXPLAIN FORMAT=TREE&lt;/code&gt; which gives us Dolt’s detailed execution plan:&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;employees&lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt;main&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;span&gt; EXPLAIN FORMAT&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;TREE &lt;/span&gt;&lt;span&gt;SELECT&lt;/span&gt;&lt;span&gt; emp_no, first_name, last_name &lt;/span&gt;&lt;span&gt;FROM&lt;/span&gt;&lt;span&gt; employees &lt;/span&gt;&lt;span&gt;WHERE&lt;/span&gt;&lt;span&gt; LOWER&lt;/span&gt;&lt;span&gt;(last_name) &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; 'facello'&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;| Project                                                                    |&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;|  ├─ columns: [employees.emp_no, employees.first_name, employees.last_name] |&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;lower&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;employees&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;last_name&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; 'facello'&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;: employees                                                |&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;|          └─ columns: [emp_no first_name last_name]                         |&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;7&lt;/span&gt;&lt;span&gt; rows&lt;/span&gt;&lt;span&gt; in&lt;/span&gt;&lt;span&gt; set&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;00&lt;/span&gt;&lt;span&gt; sec) &lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The plan is a Project of three columns, over a &lt;code&gt;Filter&lt;/code&gt; node on top of a full &lt;code&gt;Table&lt;/code&gt; scan. Dolt reads all 300,024 rows, evaluates &lt;code&gt;LOWER(last_name)&lt;/code&gt; on each one, and discards the ones that don’t match. Against 300K rows, on my Apple M1 Max laptop, this query takes about &lt;strong&gt;150ms&lt;/strong&gt;.&lt;/p&gt;
&lt;h3 id="adding-the-functional-index"&gt;Adding the Functional Index&lt;a class="anchor-link" aria-label="Link to heading" href="#adding-the-functional-index"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Now let’s create the functional index, using the same syntax we covered earlier:&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;employees&lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt;main&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;span&gt; CREATE&lt;/span&gt;&lt;span&gt; INDEX&lt;/span&gt;&lt;span&gt; idx_lower_last_name &lt;/span&gt;&lt;span&gt;ON&lt;/span&gt;&lt;span&gt; employees ((&lt;/span&gt;&lt;span&gt;LOWER&lt;/span&gt;&lt;span&gt;(last_name)));&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Let’s look at the query plan for the same query and see if it’s different after creating a functional index on &lt;code&gt;LOWER(last_name)&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;employees&lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt;main&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;span&gt; EXPLAIN FORMAT&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;TREE &lt;/span&gt;&lt;span&gt;SELECT&lt;/span&gt;&lt;span&gt; emp_no, first_name, last_name &lt;/span&gt;&lt;span&gt;FROM&lt;/span&gt;&lt;span&gt; employees &lt;/span&gt;&lt;span&gt;WHERE&lt;/span&gt;&lt;span&gt; LOWER&lt;/span&gt;&lt;span&gt;(last_name) &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; 'facello'&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;| Project                                                                    |&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;|  ├─ columns: [employees.emp_no, employees.first_name, employees.last_name] |&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;|  └─ IndexedTableAccess(employees)                                          |&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;: [employees.!hidden!idx_lower_last_name!0!0]                 |&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;|      └─ filters: [{[facello, facello]}]                                    |&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;5&lt;/span&gt;&lt;span&gt; rows&lt;/span&gt;&lt;span&gt; in&lt;/span&gt;&lt;span&gt; set&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;00&lt;/span&gt;&lt;span&gt; sec) &lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The full table scan is gone. The plan now shows &lt;code&gt;IndexedTableAccess&lt;/code&gt; — Dolt goes directly to the index, seeks to the entries where the indexed expression equals &lt;code&gt;'facello'&lt;/code&gt;, and returns only those rows. The same query now takes &lt;strong&gt;under 2ms&lt;/strong&gt;, a &lt;strong&gt;~75x improvement&lt;/strong&gt; on a 300K row table.&lt;/p&gt;
&lt;p&gt;Indexed lookups into a large table are one of the areas where functional indexes can have a huge impact on query performance. Going from a full table scan to a direct lookup resulted in a 75x improvement for this query against 300k rows, and as the table gets bigger, the full table scan performance gets slower, while the indexed lookup performance remains constant, resulting in a larger and larger improvement.&lt;/p&gt;
&lt;h2 id="functional-indexes-in-joins"&gt;Functional Indexes in Joins&lt;a class="anchor-link" aria-label="Link to heading" href="#functional-indexes-in-joins"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The functional index doesn’t just speed up point lookups — it also improves queries where the filtered table is one side of a join. Let’s look at a query that finds the current job title for every employee with the last name ‘Facello’:&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; f&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;emp_no&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;f&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;first_name&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;f&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;last_name&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;t&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;title&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;t&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;from_date&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;FROM&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;SELECT&lt;/span&gt;&lt;span&gt; emp_no, first_name, last_name &lt;/span&gt;&lt;span&gt;FROM&lt;/span&gt;&lt;span&gt; employees &lt;/span&gt;&lt;span&gt;WHERE&lt;/span&gt;&lt;span&gt; LOWER&lt;/span&gt;&lt;span&gt;(last_name) &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; 'facello'&lt;/span&gt;&lt;span&gt;) f&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;JOIN&lt;/span&gt;&lt;span&gt; titles t &lt;/span&gt;&lt;span&gt;ON&lt;/span&gt;&lt;span&gt; f&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;emp_no&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; t&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;emp_no&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;WHERE&lt;/span&gt;&lt;span&gt; t&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;to_date&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; '9999-01-01'&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id="without-the-index"&gt;Without the Index&lt;a class="anchor-link" aria-label="Link to heading" href="#without-the-index"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Let’s start off by looking at the query plan for this query, just like we did earlier. To remove the index we just created on the &lt;code&gt;employees&lt;/code&gt; table, we can run:&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;employees&lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt;main&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;span&gt; DROP&lt;/span&gt;&lt;span&gt; INDEX&lt;/span&gt;&lt;span&gt; idx_lower_last_name &lt;/span&gt;&lt;span&gt;ON&lt;/span&gt;&lt;span&gt; employees;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And here’s the query plan:&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;employees&lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt;main&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;span&gt; EXPLAIN FORMAT&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;TREE&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;    SELECT&lt;/span&gt;&lt;span&gt; f&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;emp_no&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;f&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;first_name&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;f&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;last_name&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;t&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;title&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;t&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;from_date&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;    FROM&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;SELECT&lt;/span&gt;&lt;span&gt; emp_no, first_name, last_name &lt;/span&gt;&lt;span&gt;FROM&lt;/span&gt;&lt;span&gt; employees &lt;/span&gt;&lt;span&gt;WHERE&lt;/span&gt;&lt;span&gt; LOWER&lt;/span&gt;&lt;span&gt;(last_name) &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; 'facello'&lt;/span&gt;&lt;span&gt;) f&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;    JOIN&lt;/span&gt;&lt;span&gt; titles t &lt;/span&gt;&lt;span&gt;ON&lt;/span&gt;&lt;span&gt; f&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;emp_no&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; t&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;emp_no&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;    WHERE&lt;/span&gt;&lt;span&gt; t&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;to_date&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; '9999-01-01'&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;| Project                                                                  |&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;|  ├─ columns: [f.emp_no, f.first_name, f.last_name, t.title, t.from_date] |&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;|  └─ LookupJoin                                                           |&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;: f                                                      |&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: false                                             |&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;|      │   ├─ cacheable: true                                              |&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;|      │   ├─ colSet: (&lt;/span&gt;&lt;span&gt;7&lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;9&lt;/span&gt;&lt;span&gt;)                                                |&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;|      │   ├─ tableId: &lt;/span&gt;&lt;span&gt;2&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;lower&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;employees&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;last_name&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; 'facello'&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;: employees                                      |&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;|      │           └─ columns: [emp_no first_name last_name]               |&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;t&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;to_date&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; '9999-01-01'&lt;/span&gt;&lt;span&gt;)                                   |&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;|          └─ TableAlias(t)                                                |&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;|              └─ IndexedTableAccess(titles)                               |&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;: [titles.emp_no,titles.title,titles.from_date] |&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;|                  ├─ columns: [emp_no title from_date to_date]            |&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;|                  └─ keys: &lt;/span&gt;&lt;span&gt;f&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;emp_no&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;22&lt;/span&gt;&lt;span&gt; rows&lt;/span&gt;&lt;span&gt; in&lt;/span&gt;&lt;span&gt; set&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;00&lt;/span&gt;&lt;span&gt; sec) &lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The subquery on one side of the &lt;code&gt;LookupJoin&lt;/code&gt; does a full table scan of all 300K employees to find the Facello employees. Then, for each matching employee, it does a fast indexed lookup into the &lt;code&gt;titles&lt;/code&gt; table using the primary key. The bottleneck is that first full scan — the query runs in about &lt;strong&gt;150ms&lt;/strong&gt;. This is very similar performance to our first un-indexed query, which makes sense, because the most significant factor in the performance of both queries is the full table scan over the 300k items in the &lt;code&gt;employees&lt;/code&gt; table.&lt;/p&gt;
&lt;h3 id="with-the-index"&gt;With the Index&lt;a class="anchor-link" aria-label="Link to heading" href="#with-the-index"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Just like before, let’s create a functional index over &lt;code&gt;LOWER(last_name)&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;employees&lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt;main&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;span&gt; CREATE&lt;/span&gt;&lt;span&gt; INDEX&lt;/span&gt;&lt;span&gt; idx_lower_last_name &lt;/span&gt;&lt;span&gt;ON&lt;/span&gt;&lt;span&gt; employees ((&lt;/span&gt;&lt;span&gt;LOWER&lt;/span&gt;&lt;span&gt;(last_name)));&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now let’s look at the query plan:&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;employees&lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt;main&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;span&gt; EXPLAIN FORMAT&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;TREE&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;    SELECT&lt;/span&gt;&lt;span&gt; f&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;emp_no&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;f&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;first_name&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;f&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;last_name&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;t&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;title&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;t&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;from_date&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;    FROM&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;SELECT&lt;/span&gt;&lt;span&gt; emp_no, first_name, last_name &lt;/span&gt;&lt;span&gt;FROM&lt;/span&gt;&lt;span&gt; employees &lt;/span&gt;&lt;span&gt;WHERE&lt;/span&gt;&lt;span&gt; LOWER&lt;/span&gt;&lt;span&gt;(last_name) &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; 'facello'&lt;/span&gt;&lt;span&gt;) f&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;    JOIN&lt;/span&gt;&lt;span&gt; titles t &lt;/span&gt;&lt;span&gt;ON&lt;/span&gt;&lt;span&gt; f&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;emp_no&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; t&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;emp_no&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;    WHERE&lt;/span&gt;&lt;span&gt; t&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;to_date&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; '9999-01-01'&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;| Project                                                                                |&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;|  ├─ columns: [f.emp_no, f.first_name, f.last_name, t.title, t.from_date]               |&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;|  └─ LookupJoin                                                                         |&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;: f                                                                    |&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: false                                                           |&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;|      │   ├─ cacheable: true                                                            |&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;|      │   ├─ colSet: (&lt;/span&gt;&lt;span&gt;15&lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;17&lt;/span&gt;&lt;span&gt;)                                                            |&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;|      │   ├─ tableId: &lt;/span&gt;&lt;span&gt;3&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: [employees.emp_no, employees.first_name, employees.last_name] |&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;|      │       └─ IndexedTableAccess(employees)                                          |&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;: [employees.!hidden!idx_lower_last_name!0!0]                 |&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;|      │           └─ filters: [{[facello, facello]}]                                    |&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;t&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;to_date&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; '9999-01-01'&lt;/span&gt;&lt;span&gt;)                                                 |&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;|          └─ TableAlias(t)                                                              |&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;|              └─ IndexedTableAccess(titles)                                             |&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;: [titles.emp_no,titles.title,titles.from_date]               |&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;|                  ├─ columns: [emp_no title from_date to_date]                          |&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;|                  └─ keys: &lt;/span&gt;&lt;span&gt;f&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;emp_no&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;22&lt;/span&gt;&lt;span&gt; rows&lt;/span&gt;&lt;span&gt; in&lt;/span&gt;&lt;span&gt; set&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;00&lt;/span&gt;&lt;span&gt; sec) &lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The join structure is identical — a &lt;code&gt;LookupJoin&lt;/code&gt; with a per-employee index seek into &lt;code&gt;titles&lt;/code&gt; — but now the employees side uses &lt;code&gt;IndexedTableAccess&lt;/code&gt; with the functional index instead of a full table scan. Dolt resolves only the 186 matching employees before doing any join work at all, which brings the query down to &lt;strong&gt;under 2ms&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Here’s a sample of the results:&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;+&lt;/span&gt;&lt;span&gt;--------+------------+-----------+------------------+------------+&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;| emp_no | first_name | last_name | title            | from_date  |&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;10001&lt;/span&gt;&lt;span&gt; | Georgi     | Facello   | Senior Engineer  | &lt;/span&gt;&lt;span&gt;1986&lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;06&lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;26&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;15346&lt;/span&gt;&lt;span&gt; | Kirk       | Facello   | Senior Engineer  | &lt;/span&gt;&lt;span&gt;1997&lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;12&lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;06&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;15685&lt;/span&gt;&lt;span&gt; | Kasturi    | Facello   | Senior Engineer  | &lt;/span&gt;&lt;span&gt;1997&lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;03&lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;13&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;18686&lt;/span&gt;&lt;span&gt; | Kwangyoen  | Facello   | Senior Staff     | &lt;/span&gt;&lt;span&gt;1994&lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;05&lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;02&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;21947&lt;/span&gt;&lt;span&gt; | Taisook    | Facello   | Senior Engineer  | &lt;/span&gt;&lt;span&gt;1998&lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;08&lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;28&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;23938&lt;/span&gt;&lt;span&gt; | Nahum      | Facello   | Senior Engineer  | &lt;/span&gt;&lt;span&gt;1985&lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;09&lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;15&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;24774&lt;/span&gt;&lt;span&gt; | Uno        | Facello   | Senior Staff     | &lt;/span&gt;&lt;span&gt;2002&lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;05&lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;15&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;24806&lt;/span&gt;&lt;span&gt; | Charmane   | Facello   | Senior Engineer  | &lt;/span&gt;&lt;span&gt;1999&lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;03&lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;27&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;25955&lt;/span&gt;&lt;span&gt; | Christoph  | Facello   | Technique Leader | &lt;/span&gt;&lt;span&gt;1995&lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;08&lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;21&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;27732&lt;/span&gt;&lt;span&gt; | Girolamo   | Facello   | Senior Engineer  | &lt;/span&gt;&lt;span&gt;1986&lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;06&lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;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 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;186&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;/code&gt;&lt;/pre&gt;
&lt;h2 id="functional-indexes-and-dolt-branches"&gt;Functional Indexes and Dolt Branches&lt;a class="anchor-link" aria-label="Link to heading" href="#functional-indexes-and-dolt-branches"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;One thing worth noting for Dolt users: functional indexes are part of your schema, and like everything else in Dolt, your schema lives on branches. If you create a functional index on a feature branch, it exists only on that branch until you merge it. Schema changes, including functional index additions and removals, show up in &lt;code&gt;dolt diff&lt;/code&gt; and in pull requests on DoltHub, so your team can review index changes the same way they review any other change to the database.&lt;/p&gt;
&lt;p&gt;This means you can safely experiment with different indexing strategies on isolated branches and only merge the ones that work the way you need them to. It also allows you to offload the work to build the initial index to be performed in the branch, then Dolt can often reuse the index data directly and update it from data differences when you merge it into another branch.&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;Under the hood, Dolt implements functional indexes the same way MySQL does: by automatically creating a hidden virtual generated column that stores the result of the expression, then building a regular index on that hidden column. The virtual column is entirely transparent — it won’t appear in &lt;code&gt;SHOW COLUMNS&lt;/code&gt; or &lt;code&gt;SELECT *&lt;/code&gt; output. Dolt handles computing and maintaining it automatically as rows are inserted and updated.&lt;/p&gt;
&lt;h2 id="future-enhancements"&gt;Future Enhancements&lt;a class="anchor-link" aria-label="Link to heading" href="#future-enhancements"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Our initial release of functional index support includes the features that customers told us they needed from functional indexes (e.g. support for a single functional expression, using functional indexes in joins and filters). Now that those customer use cases are unblocked, we’re following up with a few more enhancements:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Multiple expressions per functional index.&lt;/strong&gt; Each functional index currently supports exactly one functional expression. If you need indexes on multiple expressions, you’ll need a separate index for each one. This was an expedient way to build what our first customers needed, and we’re already working on expanding support for mixing functional expressions with column references in an index, and using multiple functional expressions in a single index. We expect to deliver this as a quick follow-up to the initial support.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Doltgres support.&lt;/strong&gt; &lt;a href="https://www.doltgres.com/"&gt;Doltgres is our PostgreSQL-compatible database engine&lt;/a&gt;. Dolt has a head start of a few years over Doltgres, but Doltgres is catching up quickly and moving towards a 1.0 milestone. We’ve already started working on enabling functional index support in Doltgres and expect to deliver this as another fast follow-up.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Use functional indexes for more queries.&lt;/strong&gt; Today, functional indexes are used in joins and filters to speed up queries. These are the main places where functional index speed up queries, but there are other places where functional indexes could also be used, such as to optimize sorting. For example, a query like &lt;code&gt;SELECT * FROM users ORDER BY LOWER(email)&lt;/code&gt; won’t yet use the functional index to avoid a sort.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Performance testing.&lt;/strong&gt; Our initial performance testing for functional indexes shows a dramatic speed up for queries that performed a full table scan before being optimized with a functional index. When compared to MySQL performance for those same queries and same index, Dolt matches or beats MySQL’s performance for those queries. We’ll be adding &lt;a href="https://www.dolthub.com/blog/2025-12-04-dolt-is-as-fast-as-mysql/"&gt;sysbench tests&lt;/a&gt; to track query performance with functional indexes and digging into other cases, like JSON usage with functional indexes to continue tuning performance.&lt;/p&gt;
&lt;h2 id="wrap-up"&gt;Wrap Up&lt;a class="anchor-link" aria-label="Link to heading" href="#wrap-up"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The ability to index functional expressions on your data and then use those precomputed values to speed up joins and filtering in queries is a big feature for Dolt. These functional indexes can turn slow table scans over large tables into lightning fast point lookups. In the examples we walked through here, on a table with 300k rows, a functional index improved performance by 75x. The performance impact gets larger as the table size grows.&lt;/p&gt;
&lt;p&gt;If you want to dig deeper into how Dolt uses indexes and plans joins, check out Nick’s recent post on &lt;a href="https://www.dolthub.com/blog/2026-02-27-index-selection-for-join-queries/"&gt;improving index selection for join queries&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;If you haven’t tried Dolt yet, &lt;a href="https://docs.dolthub.com/introduction/installation"&gt;install it here&lt;/a&gt; and give functional indexes a spin! If you have questions or feedback, swing by our &lt;a href="https://discord.gg/gqr7K4VNKe"&gt;Discord server&lt;/a&gt; or &lt;a href="https://github.com/dolthub/dolt/issues/new"&gt;file an issue on GitHub&lt;/a&gt;. We love hearing from customers and are always happy to prioritize features or bug fixes that customers tell us they need.&lt;/p&gt;</content:encoded>
      <dc:creator>Jason Fulghum</dc:creator>
      <category>feature release</category>
      <category>dolt</category>
    </item>
    <item>
      <title>Why DoltLite?</title>
      <link>https://dolthub.com/blog/2026-04-27-why-doltlite/</link>
      <guid isPermaLink="true">https://dolthub.com/blog/2026-04-27-why-doltlite/</guid>
      <description>DoltLite redefines what is possible for local-first software. You can now use Git-style merges instead of CRDTs in sync engines.</description>
      <pubDate>Mon, 27 Apr 2026 00:00:00 GMT</pubDate>
      <content:encoded>&lt;p&gt;We shipped &lt;a href="https://github.com/dolthub/doltlite"&gt;DoltLite&lt;/a&gt;, a version-controlled SQLite. We already have &lt;a href="https://www.doltdb.com"&gt;Dolt&lt;/a&gt;. Dolt is free and open source. Dolt clones, branches, pushes, pulls, and merges. Why ship a second product? This article explains.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/dolthub/doltlite"&gt;&lt;img src="https://static.dolthub.com/blogimages/doltlite-logo.png/187cf16f99ea27a3199258d56892a3869221f9c9af529beeaffba3990bfc3f47.webp" alt="DoltLite Logo"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h1 id="local-first-software"&gt;Local-first Software&lt;a class="anchor-link" aria-label="Link to heading" href="#local-first-software"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;&lt;a href="https://www.inkandswitch.com/essay/local-first/"&gt;Local-first software&lt;/a&gt;, coined by Ink &amp;#x26; Switch in 2019, has inspired a lot of software, mostly based on SQLite. SQLite puts the “local” in “local-first”. SQLite delivers complex tabular data and SQL query power as a C-library, embeddable in any language.&lt;/p&gt;
&lt;p&gt;Local-first defines seven ideals:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Fast&lt;/li&gt;
&lt;li&gt;Multi-device&lt;/li&gt;
&lt;li&gt;Offline&lt;/li&gt;
&lt;li&gt;Collaboration&lt;/li&gt;
&lt;li&gt;Longevity&lt;/li&gt;
&lt;li&gt;Privacy&lt;/li&gt;
&lt;li&gt;User control&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;SQLite gives you “Fast”, “Offline”, “Privacy”, and “User Control”.&lt;/p&gt;
&lt;p&gt;How do you get “Multi-device” and “Collaboration” from SQLite? You need a sync engine. There’s been a number of sync engines based around SQLite in the over half-a-decade since Ink &amp;#x26; Switch published this essay:
&lt;a href="https://turso.tech/"&gt;Turso&lt;/a&gt;, &lt;a href="https://www.powersync.com/"&gt;Powersync&lt;/a&gt;, &lt;a href="https://electric-sql.com/"&gt;ElectricSQL&lt;/a&gt;, and &lt;a href="https://github.com/vlcn-io/cr-sqlite"&gt;cr-sqlite&lt;/a&gt; to name a few.&lt;/p&gt;
&lt;p&gt;But, the fundamental problem with sync engines is “What do you do with conflicts?”. The &lt;a href="https://www.inkandswitch.com/essay/local-first/"&gt;Local-first software essay&lt;/a&gt; suggests Conflict Resistant Data Types (CRDTs) as the solution, rejecting Git because “Git has no capability for real-time, fine-grained collaboration, such as the automatic, instantaneous merging” and “other (non-text) file formats are treated as binary blobs that cannot meaningfully be edited or merged”.&lt;/p&gt;
&lt;p&gt;Enter &lt;a href="https://github.com/dolthub/doltlite"&gt;DoltLite&lt;/a&gt;, a version-controlled SQLite. DoltLite enables a new class of local-first application by supporting Git-style merging on tabular data directly accessible as a C-library.&lt;/p&gt;
&lt;h1 id="but-dolt"&gt;But Dolt?&lt;a class="anchor-link" aria-label="Link to heading" href="#but-dolt"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;&lt;a href="https://www.doltdb.com"&gt;Dolt&lt;/a&gt; already existed and has been stable for years. Why not just use Dolt?&lt;/p&gt;
&lt;p&gt;Because Dolt is a server. To use Dolt from your application you stand up &lt;code&gt;dolt sql-server&lt;/code&gt;, point a MySQL client at port 3306, and talk wire protocol. That’s a fine model if you’re replacing MySQL or Postgres. It’s the wrong model for local-first.&lt;/p&gt;
&lt;p&gt;The whole point of local-first is the database lives on the user’s device. Asking the user to run a server defeats the purpose. Running a local server is a pain. There’s a process to manage, a port to not collide with, a daemon that can crash. SQLite has none of that. SQLite is a &lt;code&gt;.dylib&lt;/code&gt; your application links in. That’s the model local-first wants.&lt;/p&gt;
&lt;p&gt;DoltLite is that model with version control. People have been asking us for an embedded Dolt for years.&lt;/p&gt;
&lt;h1 id="sql-and-version-control-in-any-language"&gt;SQL and Version Control in Any Language&lt;a class="anchor-link" aria-label="Link to heading" href="#sql-and-version-control-in-any-language"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;Dolt is written in Go. Go is great for a server. It is bad for a library you embed in someone else’s runtime, &lt;a href="https://www.dolthub.com/blog/2022-07-25-embedded/"&gt;unless it’s a Go runtime&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Go binaries are big. Go is awkward to link into iOS apps, Python C extensions, Ruby gems, Node addons, Erlang NIFs, and Rust crates. And Go’s WebAssembly (WASM) story produces multi-megabyte bundles with a runtime that doesn’t play well with the browser’s threading and storage primitives.&lt;/p&gt;
&lt;p&gt;C compiles to all of those. iOS, Android, Python, Ruby, Node, Rust, Erlang, and the one that matters most for local-first: a &lt;code&gt;.wasm&lt;/code&gt; bundle that runs inside a browser tab. &lt;a href="https://github.com/dolthub/doltlite#webassembly-extwasm"&gt;DoltLite compiles SQLite’s WASM target&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;WASM is what local-first looks like in 2026. The user opens a webpage. The webpage downloads a &lt;code&gt;.wasm&lt;/code&gt; file. The &lt;code&gt;.wasm&lt;/code&gt; is a full version-controlled SQL database backed by &lt;a href="https://developer.chrome.com/blog/sqlite-wasm-in-the-browser-backed-by-the-origin-private-file-system"&gt;the browser’s private filesystem&lt;/a&gt;. The user’s edits commit to a local branch. When they hit publish, the branch pushes to a DoltLite remote. When a teammate’s branch lands, the user pulls and merges.&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;-- alice's laptop&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;SELECT&lt;/span&gt;&lt;span&gt; dolt_clone(&lt;/span&gt;&lt;span&gt;'https://dolthub.com/team/users'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;'users.db'&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;INSERT INTO&lt;/span&gt;&lt;span&gt; users &lt;/span&gt;&lt;span&gt;VALUES&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;'alice'&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; dolt_commit(&lt;/span&gt;&lt;span&gt;'-Am'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;'add alice'&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; dolt_push(&lt;/span&gt;&lt;span&gt;'origin'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;'main'&lt;/span&gt;&lt;span&gt;);                                &lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;                                                                                &lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;-- bob's browser tab (WASM)                                        &lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;SELECT&lt;/span&gt;&lt;span&gt; dolt_clone(&lt;/span&gt;&lt;span&gt;'https://dolthub.com/team/users'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;'users.db'&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;INSERT INTO&lt;/span&gt;&lt;span&gt; users &lt;/span&gt;&lt;span&gt;VALUES&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;'bob'&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; dolt_commit(&lt;/span&gt;&lt;span&gt;'-Am'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;'add bob'&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; dolt_pull(&lt;/span&gt;&lt;span&gt;'origin'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;'main'&lt;/span&gt;&lt;span&gt;);   &lt;/span&gt;&lt;span&gt;-- merges alice's commit                &lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span&gt;SELECT&lt;/span&gt;&lt;span&gt; dolt_push(&lt;/span&gt;&lt;span&gt;'origin'&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;'main'&lt;/span&gt;&lt;span&gt;);   &lt;/span&gt;&lt;span&gt;-- pushes the merged history &lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;No wire protocol. No port. No server. A C library call into a &lt;code&gt;.dylib&lt;/code&gt;, &lt;code&gt;.so&lt;/code&gt;, &lt;code&gt;.dll&lt;/code&gt;, or &lt;code&gt;.wasm&lt;/code&gt; you linked into your app. &lt;code&gt;dolt_push&lt;/code&gt; to share with everyone else holding a copy.&lt;/p&gt;
&lt;p&gt;In the example, Alice and Bob inserted different rows, so the merge was trivial. If they had both updated row two to different names, &lt;code&gt;dolt_pull&lt;/code&gt; would do what Git does: drop both versions into a &lt;code&gt;dolt_conflicts_users&lt;/code&gt; table and refuse to commit until a human picks. That’s the part CRDTs hide. For data with audit or rollback requirements, you want disagreement surfaced, not silently resolved.&lt;/p&gt;
&lt;p&gt;DoltLite is the local-first use case, with Git-style merging on structured data instead of CRDTs.&lt;/p&gt;
&lt;h1 id="try-doltlite"&gt;Try DoltLite&lt;a class="anchor-link" aria-label="Link to heading" href="#try-doltlite"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;All DoltLite needs is users. Have a local-first app you’ve been waiting to build because there was no Git-style sync model? Wait no longer. Questions? Bugs? Feature requests? Cut &lt;a href="https://github.com/dolthub/doltlite/issues"&gt;an issue&lt;/a&gt;. Otherwise, come by &lt;a href="https://discord.gg/gqr7K4VNKe"&gt;our Discord&lt;/a&gt; to discuss. Meet me in the #doltlite🪶 channel.&lt;/p&gt;</content:encoded>
      <dc:creator>Tim Sehn</dc:creator>
      <category>doltlite</category>
      <category>use case</category>
    </item>
    <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>
  </channel>
</rss>
