当我为列设置默认函数时,为什么 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:
出现两个问题
- 将架构转储到 db/schema.rb 时,会产生无效 Ruby,因为双引号未转义
- 即使您正确地手动转义了引号,当将架构加载回数据库时,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.rb
。 structure.sql
使用数据库的本机 dump/restore 工具,因此它将理解并保留数据库所知道的一切。
切换到 structure.sql
非常简单:
更新 config/application.rb
以使用正确的格式:
config.active_record.schema_format = :sql
使用 db:structure:dump
rake 任务转储你的 structure.sql
。
从源代码树和版本控制中删除 db/schema.rb
。
将 db/structure.sql
添加到修订控制。
重新训练您的手指使用不同的 rake 任务来转储和恢复模式:
db:structure:dump
而不是 db:schema:dump
.
db:structure:load
而不是 db:schema:load
.
我总是从 structure.sql
开始,因为 schema.rb
对于我想如何使用我的数据库来说太有限了。
我在 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:
- 将架构转储到 db/schema.rb 时,会产生无效 Ruby,因为双引号未转义
- 即使您正确地手动转义了引号,当将架构加载回数据库时,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.rb
。 structure.sql
使用数据库的本机 dump/restore 工具,因此它将理解并保留数据库所知道的一切。
切换到 structure.sql
非常简单:
更新
config/application.rb
以使用正确的格式:config.active_record.schema_format = :sql
使用
db:structure:dump
rake 任务转储你的structure.sql
。从源代码树和版本控制中删除
db/schema.rb
。将
db/structure.sql
添加到修订控制。重新训练您的手指使用不同的 rake 任务来转储和恢复模式:
db:structure:dump
而不是db:schema:dump
.db:structure:load
而不是db:schema:load
.
我总是从 structure.sql
开始,因为 schema.rb
对于我想如何使用我的数据库来说太有限了。