Puzzled by rollback to tag behavior -- changeset with tag is also removed

My original mental model of Liquibase had tags going at the end of groups of changes.

For example, I would expect a list of changes for release 1.0.0 to end with a tag called 1.0.0. I would also expect a list of changes for release 1.1.0 to end with a tag called 1.1.0. This particular example uses changesets to do tagging rather than the tag command, and I’m also confused about the differences between those two methods of tagging.

If I deployed the 1.0.0 changes followed by the 1.1.0 changes, I would expect to see a changelog table like the following:

liquibase_testing=# select id, filename, orderexecuted, tag,liquibase from databasechangelog order by orderexecuted desc;

    id     |            filename             | orderexecuted |  tag  | liquibase

-----------±--------------------------------±--------------±------±----------

 tag 1.1.0 | changes/db.changelog-master.xml |             6 | 1.1.0 | 3.8.9

 change 4  | changes/db.changelog-master.xml |             5 |       | 3.8.9

 change 3  | changes/db.changelog-master.xml |             4 |       | 3.8.9

 tag 1.0.0 | changes/db.changelog-master.xml |             3 | 1.0.0 | 3.8.9

 change 2  | changes/db.changelog-master.xml |             2 |       | 3.8.9

 change 1  | changes/db.changelog-master.xml |             1 |       | 3.8.9

(6 rows)

Then if I wanted to roll back all the changes for version 1.1.0, I would tell Liquibase to rollback to tag 1.0.0. I would expect for all of the 1.1.0 changes to be removed, leaving all of the 1.0.0 changes (including the 1.0.0 tag), as follows:

liquibase_testing=# select id, filename, orderexecuted, tag,liquibase from databasechangelog order by orderexecuted desc;

    id     |            filename             | orderexecuted |  tag  | liquibase

-----------±--------------------------------±--------------±------±----------

 tag 1.0.0 | changes/db.changelog-master.xml |             3 | 1.0.0 | 3.8.9

 change 2  | changes/db.changelog-master.xml |             2 |       | 3.8.9

 change 1  | changes/db.changelog-master.xml |             1 |       | 3.8.9

(3 rows)

Instead, the changeset with the 1.0.0 tag gets removed as well!

liquibase_testing=# select id, filename, orderexecuted, tag,liquibase from databasechangelog order by orderexecuted desc;

    id    |            filename             | orderexecuted | tag | liquibase

----------±--------------------------------±--------------±----±----------

 change 2 | changes/db.changelog-master.xml |             2 |     | 3.8.9

 change 1 | changes/db.changelog-master.xml |             1 |     | 3.8.9

(2 rows)

It looks like the tag command updates the tag column of the latest row in the database change log table, so if I had used that instead of tag changesets, I would also lose “change 2”, which is definitely not what I want.

What’s wrong with my mental model of how Liquibase tags work?

I can work around the mismatch by putting tag changesets at the beginning of each series of changes, but I am still very puzzled as to why this is how Liquibase behaves.

This seems to be a bug or an unfortunate design choice, per https://stackoverflow.com/questions/40263564/liquibase-rollback-to-tag-mechanism-also-deletes-tag-record-how-come, which refers to https://liquibase.jira.com/browse/CORE-2815 where the new behavior was introduced and https://liquibase.jira.com/browse/CORE-2946 where the new behavior is reported as a bug.

Also, my assumption about what would happen if I used the tag command was wrong.

If instead of putting tags in my changelog XML, I comment out changes 3 and 4, run through with changes 1 and 2, use the tag command with version 1.0.0, then uncomment changes 3 and 4, run through the changes again, and then use the tag command with version 1.1.0, things behave as I would expect.