当我为列设置默认函数时,为什么 Postgres 会双引号第一个函数标识符?

Why is Postgres double-quoting the first function identifier when I set a default function for a column?

我在 Rails 项目中有一个带有默认表达式的遗留 table。此表达式选择一个随机的四位数并将其转换为字符串。在我的测试中,我一直在 psql 中输入这个确切的语句来设置默认表达式:

alter table owners alter column signing_code set default 
substring(to_char(((random() * ((10000 - 1))::double precision) + 
(1)::double precision), '0000'::text), '....$'::text);

然后当我这样做时 \d owners 这是 Postgres 决定的我实际的默认表达式:

default "substring"(to_char(((random() * ((10000 - 1))::double 
precision) + (1)::double precision), '0000'::text), '....$'::text)

注意第一个函数标识符 substring 周围的双引号。这会导致 Rails 架构 dumps/loads:

出现两个问题
  1. 将架构转储到 db/schema.rb 时,会产生无效 Ruby,因为双引号未转义
  2. 即使您正确地手动转义了引号,当将架构加载回数据库时,Rails 错误地将整个表达式设置为默认字符串值,而不是表达式(即它用单引号)

有没有办法让 Postgres 在我的例子中不对嵌套函数调用中的第一个函数进行双引号?那将是一个很好的解决方法,否则我将提交 Rails 项目的错误。

为什么 substring 函数被双引号引用并不重要,这两个表达式在功能上是等价的。 PostgreSQL 将解析默认表达式,然后保存解析后的版本,在此过程中,substring 标识符被双引号。如果你像这样添加 CHECK 约束,你会看到类似的事情发生:

check (c in (1, 2, 3))

在具有该约束的 table 上执行 \d 将得到:

CHECK (c = ANY (ARRAY[1, 2, 3]))

PostgreSQL 将 in 表达式转换为等效的 = ANY 表达式,因为这是它希望在内部使用的内容。这两个表达式在功能上是等价的。

真正的问题是 ActiveRecord 不知道如何处理比单个文字值更复杂的默认表达式。 AR 对默认值做出了一个无效的假设,然后盲目地把事情搞得一团糟。

当您处理任何超出最简单的 DDL SQL 的事情时,您最好的选择是使用 db/structure.sql 而不是 db/schema.rbstructure.sql 使用数据库的本机 dump/restore 工具,因此它将理解并保留数据库所知道的一切。

切换到 structure.sql 非常简单:

  1. 更新 config/application.rb 以使用正确的格式:

    config.active_record.schema_format = :sql
    
  2. 使用 db:structure:dump rake 任务转储你的 structure.sql

  3. 从源代码树和版本控制中删除 db/schema.rb

  4. db/structure.sql 添加到修订控制。

  5. 重新训练您的手指使用不同的 rake 任务来转储和恢复模式:

    • db:structure:dump 而不是 db:schema:dump.
    • db:structure:load 而不是 db:schema:load.

我总是从 structure.sql 开始,因为 schema.rb 对于我想如何使用我的数据库来说太有限了。