We’re writing Dolt, the world’s first and only SQL database that you can
branch and merge, push and pull, diff and clone, just like a Git repository. Dolt started its life
as a command-line tool for running version control commands on databases, and we modeled those
commands on Git’s own interface so our customers
wouldn’t have to learn a new tool. The idea was that any command like git checkout or git merge
should work the same in Dolt, so dolt checkout and dolt merge do the same thing as their Git
counterparts, working on database tables instead of files. Whenever we had a design question about
how the product should work, our default guiding principle was always “make it work the same way Git
does.”
Now Dolt is an OLTP database, and the primary way people use the version control features is through procedures and system tables. These features are also designed with the north star of “What does Git do?”, but there are a couple of distinct areas where we had to innovate because Git could offer no guidance. In particular, Git is fundamentally single-player in each clone, with a single branch checked out into a shared working set, whereas Dolt has to accommodate multiple users editing the same or different branch heads concurrently. On top of that, as a SQL database, Dolt has to conform to different standards and expectations about transactions and durability. So we had to invent our own transaction and session management model to bridge the two domains.
One area where Git could offer no guidance was what to do when more than one branch has edits and
the user tries to commit them. This situation is simply not possible in Git, and our early storage
library interfaces made the same assumption as Git, that only a single branch would be updated in
one atomic write. So we made the conservative choice and made multi-branch commits illegal. Prior to
Dolt 2.5.0, a transaction that wrote to multiple different branches would see an error on COMMIT
or CALL DOLT_COMMIT():
mysql> COMMIT;
ERROR 1105 (HY000): Cannot commit changes on more than one branch / database
Today, we’re excited to announce the first major change to this model since we invented it over 5 years ago. It’s now possible to write to multiple branches in the same atomic SQL transaction. This capability is out now, in Dolt 2.5.0.
Demo#
For this demo, we’ll be using a simple database called demo with a notes table. It has a main
branch and a feature branch.
CREATE DATABASE demo;
USE demo;
CREATE TABLE notes (id INT PRIMARY KEY, note VARCHAR(40));
CALL dolt_commit('-Am', 'Create notes table');
CALL dolt_branch('feature');
In Dolt 2.5.0 and later, you can write to two different branches in the same transaction and commit both together atomically.
mysql> START TRANSACTION;
Query OK, 0 rows affected
mysql> INSERT INTO notes VALUES (1, 'written on main');
Query OK, 1 row affected
mysql> CALL dolt_checkout('feature');
+--------+------------------------------+
| status | message |
+--------+------------------------------+
| 0 | Switched to branch 'feature' |
+--------+------------------------------+
1 row in set
mysql> INSERT INTO notes VALUES (2, 'written on feature');
Query OK, 1 row affected
mysql> COMMIT;
Query OK, 0 rows affected
mysql> SELECT * FROM `demo/main`.notes ORDER BY id;
+----+-----------------+
| id | note |
+----+-----------------+
| 1 | written on main |
+----+-----------------+
1 row in set
mysql> SELECT * FROM `demo/feature`.notes ORDER BY id;
+----+--------------------+
| id | note |
+----+--------------------+
| 2 | written on feature |
+----+--------------------+
1 row in set
Previously, this sequence would error on COMMIT with a message indicating it wasn’t allowed.
mysql> COMMIT;
ERROR 1105 (HY000): Cannot commit changes on more than one branch / database
The above example uses DOLT_CHECKOUT to change the session’s branch. But you can also use
revision-qualified database names to insert into different branches directly.
mysql> START TRANSACTION;
Query OK, 0 rows affected
mysql> INSERT INTO `demo/main`.notes VALUES (1, 'written on main');
Query OK, 1 row affected
mysql> INSERT INTO `demo/feature`.notes VALUES (2, 'written on feature');
Query OK, 1 row affected
mysql> COMMIT;
Query OK, 0 rows affected
mysql> SELECT * FROM `demo/main`.notes ORDER BY id;
+----+-----------------+
| id | note |
+----+-----------------+
| 1 | written on main |
+----+-----------------+
1 row in set
mysql> SELECT * FROM `demo/feature`.notes ORDER BY id;
+----+--------------------+
| id | note |
+----+--------------------+
| 2 | written on feature |
+----+--------------------+
1 row in set
The same sequence doesn’t work before 2.5.0.
mysql> START TRANSACTION;
Query OK, 0 rows affected
mysql> INSERT INTO `demo/main`.notes VALUES (1, 'written on main');
Query OK, 1 row affected
mysql> INSERT INTO `demo/feature`.notes VALUES (2, 'written on feature');
Query OK, 1 row affected
mysql> COMMIT;
ERROR 1105 (HY000): Cannot commit changes on more than one branch / database
Of course, Dolt wouldn’t be Dolt if you couldn’t create commits on your branches, just like Git. We
decided this is a disruptive enough change that it merits a new procedure:
dolt_commit_all. It works just like dolt_commit, but includes every branch with edits in a
session. For now, dolt_commit() will continue to allow edits on only a single branch, just as
in previous releases.
Here is the new dolt_commit_all procedure in action.
mysql> START TRANSACTION;
Query OK, 0 rows affected
mysql> INSERT INTO notes VALUES (1, 'written on main');
Query OK, 1 row affected
mysql> CALL dolt_checkout('feature');
+--------+------------------------------+
| status | message |
+--------+------------------------------+
| 0 | Switched to branch 'feature' |
+--------+------------------------------+
1 row in set
mysql> INSERT INTO notes VALUES (2, 'written on feature');
Query OK, 1 row affected
mysql> CALL dolt_commit_all('-am', 'Update both branches');
+---------+----------------------------------+
| branch | hash |
+---------+----------------------------------+
| feature | r23lpqo1opnohgj54pv8cpokoc4u84jk |
| main | vu228lb8hodsdp6qcc074vref2o52d6i |
+---------+----------------------------------+
2 rows in set
mysql> SELECT * FROM `demo/main`.notes ORDER BY id;
+----+-----------------+
| id | note |
+----+-----------------+
| 1 | written on main |
+----+-----------------+
1 row in set
mysql> SELECT message FROM `demo/main`.dolt_log LIMIT 1;
+----------------------+
| message |
+----------------------+
| Update both branches |
+----------------------+
1 row in set
mysql> SELECT * FROM `demo/feature`.notes ORDER BY id;
+----+--------------------+
| id | note |
+----+--------------------+
| 2 | written on feature |
+----+--------------------+
1 row in set
mysql> SELECT message FROM `demo/feature`.dolt_log LIMIT 1;
+----------------------+
| message |
+----------------------+
| Update both branches |
+----------------------+
1 row in set
Each branch gets a new Dolt commit written to it, using the same commit message. Note that, unlike
dolt_commit, the new procedure returns multiple rows, one for each branch committed.
This works the same with dolt_checkout() or by using revision-qualified database names. In either
case, every branch with edits gets a new commit.
mysql> START TRANSACTION;
Query OK, 0 rows affected
mysql> INSERT INTO `demo/main`.notes VALUES (1, 'written on main');
Query OK, 1 row affected
mysql> INSERT INTO `demo/feature`.notes VALUES (2, 'written on feature');
Query OK, 1 row affected
mysql> CALL dolt_commit_all('-am', 'Update both branches');
+---------+----------------------------------+
| branch | hash |
+---------+----------------------------------+
| feature | m37ikatf3pr35ec0mme554ri114efc4r |
| main | 90dtku58rqa8o7j06mi5tvhjiko9m0k4 |
+---------+----------------------------------+
2 rows in set
This new feature is also compatible with setting the @@dolt_transaction_commit system variable to
automatically create a new Dolt commit for every SQL commit. Just like with dolt_commit, for now
we are retaining the previous behavior of allowing edits on only a single branch. To opt into the new
multi-branch commit behavior with @@dolt_transaction_commit, set the new
@@dolt_multi_branch_commit system variable.
mysql> SET @@dolt_transaction_commit = TRUE;
Query OK, 0 rows affected
mysql> SET @@dolt_multi_branch_commit = TRUE;
Query OK, 0 rows affected
mysql> SET @@dolt_transaction_commit_message = 'Update both branches';
Query OK, 0 rows affected
mysql> START TRANSACTION;
Query OK, 0 rows affected
mysql> INSERT INTO `demo/main`.notes VALUES (1, 'written on main');
Query OK, 1 row affected
mysql> INSERT INTO `demo/feature`.notes VALUES (2, 'written on feature');
Query OK, 1 row affected
mysql> COMMIT;
Query OK, 0 rows affected
mysql> SELECT * FROM `demo/main`.notes ORDER BY id;
+----+-----------------+
| id | note |
+----+-----------------+
| 1 | written on main |
+----+-----------------+
1 row in set
mysql> SELECT message FROM `demo/main`.dolt_log LIMIT 1;
+----------------------+
| message |
+----------------------+
| Update both branches |
+----------------------+
1 row in set
mysql> SELECT * FROM `demo/feature`.notes ORDER BY id;
+----+--------------------+
| id | note |
+----+--------------------+
| 2 | written on feature |
+----+--------------------+
1 row in set
mysql> SELECT message FROM `demo/feature`.dolt_log LIMIT 1;
+----------------------+
| message |
+----------------------+
| Update both branches |
+----------------------+
1 row in set
Why now?#
We’ve had this feature on our roadmap for a long time, but our customers weren’t asking for the ability to atomically commit multiple branches at the same time. You can’t do this in Git, and people aren’t used to thinking about version control this way. But there was an exception: non-local tables.
Non-local tables live on one branch but show up (with that branch’s contents) in another. It’s a way to have “global” data that’s the same on all branches without constantly needing to merge it into every affected branch. This is commonly done for data that doesn’t make sense to version on each branch separately, such as the list of users. This feature has been more popular than we expected, especially in the age of agentic engineering. It’s a natural fit for a bunch of different problems, and agents have made wide use of it.
But non-local tables had a problem. People didn’t just want to treat them as read-only. They wanted to write application logic that updated global values and branch-local values in the same transaction, and when they tried, they got an error message, since this would mean committing to more than one branch in the same transaction.
mysql> COMMIT;
ERROR 1105 (HY000): Cannot commit changes on more than one branch / database
With Dolt 2.5.0, it is now possible to atomically commit non-local tables and branch-local tables in the same transaction. In fact, we developed this new multi-branch commit feature primarily to support this use case.
Here’s an example of it in action.
CREATE TABLE shared_notes (id INT PRIMARY KEY, note VARCHAR(40));
CALL dolt_commit('-Am', 'Create shared table');
CALL dolt_checkout('-b', 'feature');
CREATE TABLE local_notes (id INT PRIMARY KEY, note VARCHAR(40));
INSERT INTO dolt_nonlocal_tables (table_name, target_ref, ref_table, options) VALUES ('remote_notes', 'main', 'shared_notes', 'immediate');
CALL dolt_commit('-Am', 'Configure non-local table');
mysql> SELECT dolt_version(), active_branch();
+----------------+-----------------+
| dolt_version() | active_branch() |
+----------------+-----------------+
| 2.5.0 | feature |
+----------------+-----------------+
1 row in set
mysql> START TRANSACTION;
Query OK, 0 rows affected
mysql> INSERT INTO local_notes VALUES (1, 'local on feature');
Query OK, 1 row affected
mysql> INSERT INTO remote_notes VALUES (2, 'shared on main');
Query OK, 1 row affected
mysql> COMMIT;
Query OK, 0 rows affected
mysql> SELECT * FROM `demo/feature`.local_notes;
+----+------------------+
| id | note |
+----+------------------+
| 1 | local on feature |
+----+------------------+
1 row in set
mysql> SELECT * FROM `demo/main`.shared_notes;
+----+----------------+
| id | note |
+----+----------------+
| 2 | shared on main |
+----+----------------+
1 row in set
And of course, the same pattern also works with dolt_commit_all(), which can include changes to
both local and non-local tables in the same transaction. The host branch gets a new Dolt commit,
along with any locally edited branches.
mysql> CALL dolt_commit_all('-am', 'Update local and non-local tables');
+---------+----------------------------------+
| branch | hash |
+---------+----------------------------------+
| feature | mjhq9feqnpvdi4shsl6ipdcem8esg67v |
| main | i4mm0evobqucvn2963bjpepn99f5hk79 |
+---------+----------------------------------+
2 rows in set
mysql> SELECT * FROM `demo/feature`.local_notes;
+----+------------------+
| id | note |
+----+------------------+
| 1 | local on feature |
+----+------------------+
1 row in set
mysql> SELECT * FROM `demo/main`.shared_notes;
+----+----------------+
| id | note |
+----+----------------+
| 2 | shared on main |
+----+----------------+
1 row in set
mysql> SELECT message FROM `demo/feature`.dolt_log LIMIT 1;
+-----------------------------------+
| message |
+-----------------------------------+
| Update local and non-local tables |
+-----------------------------------+
1 row in set
mysql> SELECT message FROM `demo/main`.dolt_log LIMIT 1;
+-----------------------------------+
| message |
+-----------------------------------+
| Update local and non-local tables |
+-----------------------------------+
1 row in set
Conclusion#
Commits to multiple branches in the same transaction are available today in Dolt 2.5.0. Doltgres will get this capability in its next release.
Multi-branch commits are an exciting new capability in version-controlled databases, and we are very curious to see what our customers will build with it. Non-local tables are an obvious first candidate, since this new ability makes them much easier to use correctly. What else will you come up with?
Have questions about multi-branch commits? Want to learn more about Dolt and Doltgres? Visit us on the DoltHub Discord where our engineering team hangs out all day. Hope to see you there.