含Postgres生成列的Rails迁移测试环境报错,为何开发/生产环境运行正常?
不同环境迁移行为差异的根因
开发、生产环境正常执行的原因
- 这类环境的数据库初始化流程为逐次、按版本执行所有迁移脚本。编写的原生SQL
ALTER TABLE语句会被完整透传给Postgres,数据库会正确识别GENERATED ALWAYS AS (...) STORED是存储生成列的合法定义,完全符合语法规则,因此不会触发报错。 - 流程中
schema.rb仅作为迁移执行完成后的库结构导出文件存在,不会被反向用来搭建数据库,因此哪怕导出的结构定义有错误,也不会影响开发、生产环境的运行。
测试环境直接报错的核心原因
- Rails测试环境默认的数据库初始化逻辑和其他环境完全不同:为了压缩测试准备时长,它不会逐次执行全部迁移,而是直接加载导出的schema结构来快速搭建测试库。
- 当前Rails的PostgreSQL适配器存在能力缺陷:在导出存储生成列的定义到
schema.rb时,无法正确识别GENERATED ALWAYS的语法属性,错误把生成列的计算表达式当成了普通列的DEFAULT值写入文件,最终生成的定义为:t.tsvector "agency_names_tsvector", default: -> { "to_tsvector('english'::regconfig, (COALESCE(agency_names, ''::character varying))::text)" } - 当测试环境基于这个错误的schema定义建表时,Postgres会直接收到带同表列引用的DEFAULT子句——普通列的DEFAULT值不允许引用其他同表列,这是Postgres的语法硬限制,因此直接抛出
PG::FeatureNotSupported: ERROR: cannot use column reference in DEFAULT expression的错误。
统一配置修复的合理性
- 全环境统一设置
config.active_record.schema_format = :sql时,Rails会导出Postgres原生DDL格式的structure.sql,文件中会完整保留存储生成列的合法定义,不会出现把生成列错写为DEFAULT的问题,既解决了测试环境的报错,也不需要给不同环境配置做差异化调整。
注:这个问题和迁移脚本本身的写法无关,只要使用schema.rb的Ruby格式导出结构,遇到存储生成列的场景就会触发适配器的导出缺陷。
内容的提问来源于stack exchange,提问作者Ondrae
相关产品推荐
相关产品推荐

