liquigo: Liquibase by golang

liquigo是一个参考Liquibase中间件的golang开发的数据库版本管理工具,实现了大多数以xml格式配置的常用语法,支持的数据库类型和语法参见如下表格:

Liquibase语法(仅支持xml格式) MySQL / TiDB / OceanBase MariaDB openGauss / PostgreSQL KingbaseES 达梦 SQLite Oracle SQL Server
变更集:changeSet
变更集注释:comment
创建表:createTable
删除表:dropTable
重命名表:renameTable -
添加列:addColumn -
删除列:dropColumn
重命名列:renameColumn -
变更列:modifyDataType -
删除默认值:dropDefaultValue -
创建视图:createView
删除视图:dropView
创建索引:createIndex
删除索引:dropIndex
属性替换${}
执行SQL语句:sql
执行SQL文件:sqlFile
前置条件:preConditions
前置条件与或非:not,and,or
前置条件存在判断:tableExists,viewExists
前置条件存在判断:columnExists,sqlCheck
前置条件存在判断:indexExists ×
事务回滚:rollback
开发调试用停止:stop

测试过的数据库系统版本有:

openGauss V2.1.0 (兼容PostgreSQL模式)

OceanBase V3.1.1 (兼容MySQL模式)

MySQL V5.7.27 / MySQL V8.0.28

TiDB V5.4.0

MariaDB V10.3.34

PostgreSQL V12.10

KingbaseES V008R006

达梦 V8.1.2.2

SQLite V3.38

Oracle Express Edition V21c

SQL Server V2019

具体使用请参考app.yml配置文件以及entry-*.xml和db目录下的若干xml配置文件。


ChangeSet配置详解

变更集:changeSet

  • 属性id: 必须全局唯一

  • 属性author: 该changeSet的作者

  • 属性failOnError: [可选项]运行出错时是否终止运行,默认为true

  • 属性runAlways: [可选项]该changeSet是否每次都运行,默认为false

  • 属性dbms: [可选项]该changeSet适用的数据库类型,默认为all,例如:mysql,dm,!sqlite 表示适用mysql和达梦,不适用sqlite

  • 属性ignore: [可选项]是否忽略该changeSet,默认为false,不忽略

  • 元素comment: [可选项]该changeSet的注释说明,会记入db_change_set管理表的changeset_comment字段中

<changeSet id="app01-ddl-03-test-dbms" author="liquigo" failOnError="false" runAlways="true" dbms="mysql,!sqlite,!h2">
    <comment>comment of this changeSet</comment>
    ...
</changeSet>
<changeSet id="app01-dml-03-test-ignore" author="liquigo" ignore="true">
    <comment>ignore propertie for testing changeSet</comment>
    ...
</changeSet>

创建表:createTable

<createTable tableName="test_column_type" remarks="test the data type of the field">
    <column name="type_boolean" type="boolean" />
    <column name="type_bit6" type="bit(6)" />
    <column name="type_tinyint" type="tinyint" />
    <column name="type_smallint" type="smallint" />
    <column name="type_mediumint" type="mediumint" />
    <column name="type_int" type="int" />
    <column name="type_integer" type="integer" />
    <column name="type_bigint" type="bigint" />
    <column name="type_float" type="float(6,2)" />
    <column name="type_real" type="real(5,2)" />
    <column name="type_double" type="double(10,3)" />
    <column name="type_decimal" type="decimal(15,3)" />
    <column name="type_numeric" type="numeric(10,0)" />
    <column name="type_number" type="number(10,0)" />
    <column name="type_char" type="char(36)" />
    <column name="type_varchar" type="varchar(36)" />
    <column name="type_binary" type="binary(36)" />
    <column name="type_varbinary" type="varbinary(36)" />
    <column name="type_tinyblob" type="tinyblob" />
    <column name="type_blob" type="blob" />
    <column name="type_mediumblob" type="mediumblob" />
    <column name="type_longblob" type="longblob" />
    <column name="type_tinytext" type="tinytext" />
    <column name="type_text" type="text" />
    <column name="type_mediumtext" type="mediumtext" />
    <column name="type_longtext" type="longtext" />
    <column name="type_datetime" type="datetime" />
    <column name="type_timestamp" type="timestamp" />
    <column name="type_date" type="date" />
    <column name="type_time" type="time" />
    <column name="type_uuid" type="uuid" />
    <column name="type_currency" type="currency" />
</createTable>
<createTable tableName="test_role" remarks="test role">
    <column name="id" type="varchar(36)">
        <constraints primaryKey="true" nullable="false" primaryKeyName="pk_test_role_key" />
    </column>
    <column name="creator" type="varchar(36)" defaultValue="${createUser.defaultValue}" />
    <column name="created" type="decimal(15)" defaultValue="1" />
    <column name="updater" type="varchar(36)" defaultValue="${createUser.defaultValue}" />
    <column name="updated" type="decimal(15)" defaultValue="1" />
    <column name="uuid" type="varchar(36)">
        <constraints primaryKey="true" nullable="false" primaryKeyName="pk_test_role_key"
                    unique="true" uniqueConstraintName="uk_test_role_uuid"/>
    </column>
    <column name="role_name" type="varchar(50)" remarks="role name">
        <constraints unique="true" nullable="false" uniqueConstraintName="uk_test_role_name" />
    </column>
    <column name="detail" type="varchar(250)" defaultValue="role" remarks="role detail" >
        <constraints nullable="false" />
    </column>
    <column name="app_id" type="varchar(36)" defaultValue="3101" remarks="foreign key sum_app.id" />
</createTable>

对应生成的适配达梦的sql脚本如下:

create table test_column_type (
  type_boolean bit,
  type_bit6 bit,
  type_tinyint tinyint,
  type_smallint smallint,
  type_mediumint int,
  type_int int,
  type_integer integer,
  type_bigint bigint,
  type_float float,
  type_real real,
  type_double double,
  type_decimal decimal(15,3),
  type_numeric numeric(10,0),
  type_number number(10,0),
  type_char char(36),
  type_varchar varchar(36),
  type_binary binary(36),
  type_varbinary varbinary(36),
  type_tinyblob blob,
  type_blob blob,
  type_mediumblob blob,
  type_longblob blob,
  type_tinytext text,
  type_text text,
  type_mediumtext text,
  type_longtext text,
  type_datetime datetime,
  type_timestamp timestamp,
  type_date date,
  type_time time,
  type_uuid varchar(36),
  type_currency decimal(15,2)
);
comment on table test_column_type is 'test the data type of the field';

create table test_role (
  id varchar(36) not null,
  creator varchar(36) default '20000',
  created decimal(15) default 1,
  updater varchar(36) default '20000',
  updated decimal(15) default 1,
  uuid varchar(36) not null,
  role_name varchar(50) not null,
  detail varchar(250) not null default 'role',
  app_id varchar(36) default '3101',
  constraint pk_test_role_key primary key (id,uuid)
);
comment on table test_role is 'test role';
create unique index uk_test_role_uuid on test_role (uuid);
create unique index uk_test_role_name on test_role (role_name);
comment on column test_role.role_name is 'role name';
comment on column test_role.detail is 'role detail';
comment on column test_role.app_id is 'foreign key sum_app.id';

删除表:dropTable

<dropTable cascadeConstraints="true" tableName="test_t03"/>

对应生成的适配达梦的sql脚本如下:

drop table test_t03 cascade constraints;

重命名表:renameTable

<renameTable oldTableName="test_rename_table" newTableName="test_rename_table_new" />

对应生成的适配达梦的sql脚本如下:

alter table test_rename_table rename to test_rename_table_new;

添加列:addColumn

<addColumn tableName="test_t04">
    <column name="first_login_time" type="varchar(30)" remarks="first login time, yyyy-MM-dd HH:mm:ss" />
    <column name="login_count" type="decimal(7)" defaultValue="0" remarks="login success count">
        <constraints nullable="false"/>
    </column>
    <column name="login_account" type="varchar(20)" defaultValue="default value" remarks="login account">
        <constraints unique="true" nullable="false" uniqueConstraintName="uk_user_login_account" />
    </column>
</addColumn>

对应生成的适配达梦的sql脚本如下:

alter table test_t04 add first_login_time varchar(30);
comment on column test_t04.first_login_time is 'first login time, yyyy-MM-dd HH:mm:ss';
alter table test_t04 add login_count decimal(7) not null default 0;
comment on column test_t04.login_count is 'login success count';
alter table test_t04 add login_account varchar(20) not null default 'default value';
comment on column test_t04.login_account is 'login account';
create unique index uk_user_login_account on test_t04 (login_account);

删除列:dropColumn

<dropColumn columnName="first_login_time" tableName="test_t04"/>
<dropColumn tableName="test_t04">
    <column name="login_count"/>
    <column name="login_account"/>
</dropColumn>

对应生成的适配达梦的sql脚本如下:

alter table test_t04 drop column first_login_time cascade;
alter table test_t04 drop column login_count cascade;
alter table test_t04 drop column login_account cascade;

重命名列:renameColumn

<renameColumn columnDataType="varchar(36)"
        newColumnName="new_app_id"
        oldColumnName="app_id"
        remarks="app id"
        tableName="test_t04"/>

对应生成的适配达梦的sql脚本如下:

alter table test_t04 rename column app_id to new_app_id;
comment on column test_t04.new_app_id is 'app id';

变更列:modifyDataType

<modifyDataType columnName="new_app_id" newDataType="varchar(50)" tableName="test_t04"/>
<modifyDataType columnName="org_id" newDataType="varchar(70)" tableName="test_t04"/>

对应生成的适配达梦的sql脚本如下:

alter table test_t04 modify new_app_id varchar(50);
alter table test_t04 modify org_id varchar(70);

删除默认值:dropDefaultValue

<dropDefaultValue columnDataType="varchar(250)" columnName="detail" tableName="test_role"/>

对应生成的适配达梦的sql脚本如下:

alter table test_role modify detail default null;

创建视图:createView

<createView fullDefinition="false"
        replaceIfExists="true"
        remarks="view of test role"
        viewName="v_test_role">
    select id, role_name, app_id from test_role where created >= 1644800296000;
</createView>

对应生成的适配达梦的sql脚本如下:

drop view if exists v_test_role;
create or replace view v_test_role as 
select id, role_name, app_id from test_role where created >= 1644800296000;
comment on view v_test_role is 'view of test role';

删除视图:dropView

<dropView viewName="v_test_role" dropIfExists="true"/>

对应生成的适配达梦的sql脚本如下:

drop view if exists v_test_role;

创建索引:createIndex

<createIndex indexName="idx_test_property_person_name" tableName="${tableName}">
    <column name="person_name" type="varchar(100)" />
</createIndex>
<createIndex indexName="idx_test_property_id_card" tableName="${tableName}" unique="true">
    <column name="id_card" type="varchar(150)" />
</createIndex>

对应生成的适配达梦的sql脚本如下:

create index idx_test_property_person_name on test_property_replace (person_name);
create unique index idx_test_property_id_card on test_property_replace (id_card);

删除索引:dropIndex

<dropIndex indexName="uk_test_role_name" tableName="test_role"/>

对应生成的适配达梦的sql脚本如下:

drop index uk_test_role_name;

属性替换${}

<!-- entry-***.xml -->
<property name="tableName" value="test_property_replace" dbms="all" />
<property name="pk" value="pk_"/>
<property name="a" value="aaa"/>
<property name="b" value="bbb"/>
<property name="c" value="ccc"/>
<property name="d" value="default ddd"/>
<property name="date" value="date" dbms="oracle" />
<property name="date" value="datetime" dbms="mysql,mariadb,mssql,tidb" />
<property name="date" value="timestamp" dbms="hsqldb" />
<property name="date" value="timestamp" dbms="h2" />
<property name="date" value="date" dbms="dm" />
<property name="date" value="date" dbms="kingbase" />
<property name="date" value="integer" dbms="sqlite" />
<property name="date" value="timestamp" dbms="postgres" />
<property name="createUser.defaultValue" value="20000" dbms="none" />

<!-- changeSet -->
<createTable tableName="${tableName}" remarks="test property replace">
    <column name="id" type="varchar(36)">
        <constraints primaryKey="true" nullable="false"
                        primaryKeyName="${pk}test_property_replace" />
    </column>
    <column name="creator" type="varchar(36)" defaultValue="${createUser.defaultValue}" />
    <column name="create_time" type="decimal(15)" defaultValue="1" />
    <column name="person_name" type="varchar(100)" remarks="person name"/>
    <column name="gender" type="varchar(20)" remarks="gender" />
    <column name="birthdate" type="${date}" remarks="birth date" />
    <column name="${a}_${b}_${c}" type="varchar(50)" defaultValue="${d}" remarks="multi placer" />
    <column name="person_status" type="varchar(20)" remarks="person status" />
    <column name="telephone" type="varchar(150)" remarks="telephone" />
    <column name="email" type="varchar(50)" remarks="email" />
    <column name="photo" type="text" remarks="photo" />
</createTable>

对应生成的适配达梦的sql脚本如下:

create table test_property_replace (
  id varchar(36) not null,
  creator varchar(36) default '20000',
  create_time decimal(15) default 1,
  person_name varchar(100),
  gender varchar(20),
  birthdate date,
  aaa_bbb_ccc varchar(50) default 'default ddd',
  person_status varchar(20),
  telephone varchar(150),
  email varchar(50),
  photo text,
  constraint pk_test_property_replace primary key (id)
);
comment on table test_property_replace is 'test property replace';
comment on column test_property_replace.person_name is 'person name';
comment on column test_property_replace.gender is 'gender';
comment on column test_property_replace.birthdate is 'birth date';
comment on column test_property_replace.aaa_bbb_ccc is 'multi placer';
comment on column test_property_replace.person_status is 'person status';
comment on column test_property_replace.telephone is 'telephone';
comment on column test_property_replace.email is 'email';
comment on column test_property_replace.photo is 'photo';

执行SQL语句:sql

<sql>
    delete from test_role where created = 1644800296000; -- single line comment
    insert into test_role(id, creator, created, updater, updated, uuid, role_name, detail, app_id) values ('1', '1', 1644800296000, '1', 1644800296000, 'uuid1', 'role_name1', '1', '1');
    <!-- comment1 -->
    insert into test_role(id, creator, created, updater, updated, uuid, role_name, detail, app_id) values ('2', '1', 1644800296000, '1', 1644800296000, 'uuid2', 'role_name2', '1', '1');
    /*
        Multiline comment, append record
    */
    <!--
        comment2
    -->
    insert into test_role(id, creator, created, updater, updated, uuid, role_name, detail, app_id) values ('3', '1', 1644800296000, '1', 1644800296000, 'uuid3', 'role_name3', '1', '1');
    insert into test_role(id, creator, created, updater, updated, uuid, role_name, detail, app_id) values ('4', '4', 1644800296000, '4', 1644800296000, 'uuid4', 'role_name4', '1', '1');
</sql>

执行SQL文件:sqlFile

<sqlFile dbms="!sqlite, !postgres, mysql"
    path="db/app01/app01-01.sql" />
<sqlFile dbms="oracle,dameng"
    path="db/app01/app01-01-oracle.sql" />
<sqlFile dbms="!sqlite, postgres, mysql"
    path="D://dev//vue-front//liquigo//db//app01//app01-02.sql" />

前置条件:preConditions

<changeSet id="app01-ddl-pre-cond-01-logical-true" author="liquigo">
    <comment><![CDATA[ !false = true ]]></comment>
    <preConditions onFail="MARK_RAN" onError="HALT" onFailMessage="Fail Message" onErrorMessage="Error Message">
        <not>
            <tableExists tableName="t_user" />
        </not>
    </preConditions>
    <createTable tableName="test_pre_cond_01">
        <column name="id" type="varchar(36)" />
        <column name="name" type="varchar(36)" />
    </createTable>
</changeSet>

前置条件与或非:not,and,or

<changeSet id="app01-ddl-pre-cond-09-logical-true" author="liquigo">
    <comment><![CDATA[ (true && false) || (true && false) || !(true && false) = true ]]></comment>
    <preConditions onFail="MARK_RAN">
        <or>
            <and>
                <tableExists tableName="test_role_true" />
                <viewExists viewName="v_user_view_false" />
            </and>
            <and>
                <tableExists tableName="test_role_1_true" />
                <viewExists viewName="v_test_role_1_false" />
            </and>
            <not>
                <tableExists tableName="test_role_2_true" />
                <viewExists viewName="v_test_role_2_false" />
            </not>
        </or>
    </preConditions>
    <createTable tableName="test_pre_cond_09">
        <column name="id" type="varchar(36)" />
        <column name="name" type="varchar(36)" />
    </createTable>
</changeSet>

前置条件存在判断:tableExists,viewExists,columnExists,indexExists,sqlCheck

<tableExists tableName="test_role_2_true" />
<viewExists viewName="v_test_role_2_false" />
<columnExists tableName="test_role" columnName="role_name" />
<indexExists indexName="idx_test_property_person_name" />
<sqlCheck expectedResult="1">select count(1) from test_role where id = '1'</sqlCheck>

事务回滚:rollback

<changeSet id="app01-ddl-17-rollback" author="liquigo" runOnChange="true">
    <comment>test preConditions and rollback</comment>
    <preConditions onFail="MARK_RAN">
        <not>
            <tableExists tableName="test_rollback" />
        </not>
    </preConditions>
    <createTable tableName="test_rollback" remarks="test rollback">
        <column name="id" type="varchar(36)">
            <constraints primaryKey="true" nullable="false" primaryKeyName="pk_test_rollback_key" />
        </column>
        <column name="role_name" type="varchar(50)" remarks="role name" />
        <column name="detail" type="varchar(260)" defaultValue="role detail" remarks="role detail" />
        <column name="app_id" type="varchar(36)" remarks="foreign key app.id" />
        <column name="org_id" type="varchar(36)" remarks="foreign key org.id" />
    </createTable>
    <addColumn tableName="test_rollback">
        <column name="first_login_time" type="varchar(30)" remarks="first login time, yyyy-MM-dd HH:mm:ss" />
        <column name="login_count" type="decimal(7)" defaultValue="0" remarks="login count" />
    </addColumn>
    <rollback>
        <dropColumn tableName="test_rollback">
            <column name="login_count"/>
            <column name="first_login_time"/>
        </dropColumn>
        <dropTable cascadeConstraints="true" tableName="test_rollback"/>
    </rollback>
    <rollback>
        drop table test_rollback
    </rollback>
</changeSet>

推荐一个完整的changeSet的结构,按顺序如下:

  1. comment 提供注释说明,用于备忘

  2. preConditions 进行前置条件判断

  3. create/drop/add/.. 一个或多个数据库变更标签

  4. rollback 数据库变更失败后的回滚操作

<changeSet id="app01-ddl-17-rollback" author="liquigo">
    <comment>注释说明,用于备忘</comment>
    <preConditions onFail="MARK_RAN">
        <not>
            <tableExists tableName="test_rollback" />
        </not>
    </preConditions>
    <createTable tableName="test_rollback" remarks="test rollback">
        <column name="id" type="varchar(36)">
            <constraints primaryKey="true" nullable="false" primaryKeyName="pk_test_rollback_key" />
        </column>
        <column name="role_name" type="varchar(50)" remarks="role name" />
        <column name="detail" type="varchar(260)" defaultValue="role detail" remarks="role detail" />
        <column name="app_id" type="varchar(36)" remarks="foreign key app.id" />
        <column name="org_id" type="varchar(36)" remarks="foreign key org.id" />
    </createTable>
    <rollback>
        <dropTable cascadeConstraints="true" tableName="test_rollback"/>
    </rollback>
</changeSet>

开发调试用停止:stop

当运行到包含stop标签的changeSet时停止(包含stop标签的changeSet不运行)。

<changeSet id="..." author="liquigo">
    ...
</changeSet>
<changeSet id="liquigo-stop-for-debug" author="liquigo">
    <stop message="liquigo stop message"/>
</changeSet>
<changeSet id="..." author="liquigo">
    ...
</changeSet>

注意事项

【禁止在sql标签中运行ddl语句】

所有的ddl变更必须使用对应的xml标签配置实现

<changeSet id="app01-create-add-index" author="liquigo">
    <sql splitStatements="true" stripComments="true">
        create unique index idx_sys_dict_type on sys_dict_type(name);
        create index idx_itemcode_sys_dict_item on sys_dict_item(item_code);
    </sql>
</changeSet>
<changeSet id="app01-init-create-06" author="liquigo">
    <sql splitStatements="true" stripComments="true">
        alter table sys_audit_log modify user_agent varchar(250);
    </sql>
</changeSet>
<changeSet id="app01-create-02" author="liquigo">
    <sql splitStatements="true" stripComments="true">
        alter table svc_datax_task add target_name varchar(36);
    </sql>
</changeSet>