Postgres 和 MS SQL 服务器上的相同更新日志

Same changelog on Postgres and MS SQL Server

我正在尝试使更新日志在两个不同的数据库上工作:MS SQL Server 和 PostgreSQL。变更日志在 SQL 服务器上运行良好,但数据库和字段的大小写使其在 PostgreSQL 上中断。我试过不在值周围使用引号(这会引发错误)并使用 objectQuotingStategy="QUOTE_ALL_OBJECTS",两者都不起作用。

<?xml version="1.0" encoding="UTF-8"?>

<databaseChangeLog
  xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog
         http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.1.xsd">

    <changeSet id="create-PushApplication" author="me">
        <createTable tableName="PushApplication">
            <column name="ApplicationKey" type="NUMERIC(19,0)" autoIncrement="true" remarks="Unique id of the application">
                <constraints primaryKey="true" primaryKeyName="PushApplication_PK" nullable="false" />
            </column>
            <column name="ApplicationId" type="VARCHAR(100)" remarks="Id of the application in the Push Server">
                <constraints unique="true" uniqueConstraintName="PushApplication_U1" nullable="false" />
            </column>
            <column name="MasterSecret" type="VARCHAR(100)" remarks="Password for the application in the Push Server">
                <constraints nullable="false" />
            </column>
            <column name="Name" type="VARCHAR(100)" remarks="Name of the application">
                <constraints nullable="false" />
            </column>
            <column name="Description" type="VARCHAR(200)" remarks="Description of the application" />
            <column name="DateCreated" type="DATETIME" remarks="Date the application was created" />
        </createTable>
    </changeSet>

    <changeSet id="create-PushVariant" author="me">
        <createTable tableName="PushVariant">
            <column name="VariantKey" type="NUMERIC(19,0)" autoIncrement="true" remarks="Unique id of the variant">
                <constraints primaryKey="true" primaryKeyName="PushVariant_PK" nullable="false" />
            </column>
            <column name="VariantId" type="VARCHAR(100)" remarks="Id of the variant in the Push Server">
                <constraints unique="true" uniqueConstraintName="PushVariant_UK1" nullable="false" />
            </column>
            <column name="Secret" type="VARCHAR(100)" remarks="Password for the variant in the Push Server">
                <constraints nullable="false" />
            </column>
            <column name="Name" type="VARCHAR(100)" remarks="Name of the variant">
                <constraints nullable="false" />
            </column>
            <column name="Description" type="VARCHAR(200)" remarks="Description of the variant" />
            <column name="ApplicationKey" type="NUMERIC(19,0)" remarks="Id of the application the variant belogns to">
                <constraints nullable="false" foreignKeyName="PushVariant_FK1" references="PushApplication(ApplicationKey)" />
            </column>
        </createTable>
    </changeSet>

</databaseChangeLog>

ERROR: relation "pushapplication" does not exist

table "PushApplication" 是在 PostgreSQL 中创建的,但是当它尝试创建 "PushVariant" 时,会为外键抛出此错误。

如果我将所有数据库名称和列名称更改为小写,这将起作用。但是,这会使 SQL 服务器的大小写不正确。

目标

目标是 "PushVariant" 在 SQL 服务器和 "pushvariant" 在 PostgreSQL。

有没有办法让变更日志中的大小写保留在 SQL 服务器中,但在 PostgreSQL 中保持小写?

使用通用源代码支持多个 DBMS 总是需要妥协。

即使有一个 SQL 标准明确定义了非引号标识符必须如何存储(全部大写),您所针对的两个 DBMS 都忽略了这一点。 Postgres 以小写形式存储未加引号的标识符,SQL 服务器是 "case-preserving"(尽管根据数据库的排序规则并不总是区分大小写 in)。

根据我在跨 DBMS 工作时的个人经验,(总是!)使用 un 引号和下划线的小写标识符是问题最少的方法。而且,如果您在某个时间点将 Oracle 加入其中,您就会将所有大写名称结束。


话虽如此:我认为 Liquibase 的引用策略实际上有一个错误。根据文档,objectQuotingStrategy="QUOTE_ONLY_RESERVED_WORDS" 应该 引用保留字,因此在您的示例中不应引用任何内容。但是 Liquibase 仍然引用任何使用混合大小写的名称——当前 3.4.1 版本仍然如此。

我认为最好的事情是如果 Liquibase 支持选择 objectQuotingStrategy="NEVER"(它不支持)


另一种选择是覆盖关于如何检测 Postgres 的 "need" 引用的默认实现,然后在 运行 针对 Postgres 数据库时使用该实现。

实现实际上很短,只需要检查非标准名称(以数字开头,包含空格或其他非法字符)然后引用那些.

我刚刚尝试了以下实现:

public class NonQuotingPostgresDatabase
  extends PostgresDatabase {

    @Override
    public String correctObjectName(String objectName, Class<? extends DatabaseObject> objectType) {
        return quoteIfNecessary(objectName);
    }

    @Override
    public String escapeObjectName(String objectName, Class<? extends DatabaseObject> objectType) {
        return quoteIfNecessary(objectName);
    }

    private String quoteIfNecessary(String objectName) {
        if (requiresQuoting(objectName)) {
            return "\"" + objectName + "\"";
        }
        return objectName;

    }
    protected boolean requiresQuoting(String identifier) {
        if (identifier == null) {
            return false;
        }
        if (isQuoted(identifier)) {
            return false;
        }
        return (identifier.contains("-") || startsWithNumeric(identifier) || isReservedWord(identifier));
    }

    protected boolean isQuoted(String identifier) {
        if (identifier == null) {
            return false;
        }
        return (identifier.startsWith("\"") && identifier.endsWith("\""));
    }

}

class 本质上只保留引用的标识符,只引用 确实 需要它的标识符。与内置 class 的最大区别在于它不检查混合大小写标识符。

如果将其放入 .jar 文件并放入 Liquibase 发行版的 lib 子文件夹中,它将被自动拾取。您需要做的就是添加参数 --databaseClass=NonQuotingPostgresDatabase when 运行 Liquibase against Postgres