PRODUCTS

KEYWORDS

Dolt now supports committing to multiple branches in one transaction

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.