如何编写Ruby脚本检测PostgreSQL新增索引并实现对应测试
解决方案:对比structure.sql并验证迁移新增的索引
1. 编写Ruby脚本对比分支间的structure.sql
直接通过Git拉取上游分支的structure.sql内容,解析并对比索引差异。以下是针对PostgreSQL的示例脚本(MySQL需要调整正则匹配规则):
# 获取上游分支的structure.sql内容 def fetch_upstream_structure(upstream_branch = "origin/main") `git show #{upstream_branch}:db/structure.sql`.split("\n") end # 读取当前分支的structure.sql def read_current_structure File.readlines("db/structure.sql") end # 解析SQL中的索引定义(PostgreSQL版) def extract_indexes(sql_lines) indexes = [] sql_lines.each do |line| # 匹配PostgreSQL标准CREATE INDEX语句 next unless line.match?(/CREATE INDEX (\w+) ON (\w+\.\w+) USING \w+ \((.+)\);/) match_data = line.match(/CREATE INDEX (\w+) ON (\w+\.\w+) USING \w+ \((.+)\);/) indexes << { name: match_data[1], table: match_data[2].split(".").last, # 提取public.posts中的posts columns: match_data[3].split(",").map(&:strip) } end indexes end # 找出当前分支新增的索引 def detect_new_indexes(upstream_indexes, current_indexes) upstream_index_names = upstream_indexes.map { |idx| idx[:name] } current_indexes.reject { |idx| upstream_index_names.include?(idx[:name]) } end # 执行对比流程 upstream_lines = fetch_upstream_structure current_lines = read_current_structure upstream_indexes = extract_indexes(upstream_lines) current_indexes = extract_indexes(current_lines) new_indexes = detect_new_indexes(upstream_indexes, current_indexes) # 输出结果 puts "检测到的新增索引:" new_indexes.each do |idx| puts "- 名称: #{idx[:name]} | 表: #{idx[:table]} | 字段: #{idx[:columns].join(", ")}" end
注意:如果使用MySQL,修改extract_indexes中的正则,匹配MySQL的索引语句格式:
if line.match?(/CREATE INDEX (\w+) ON (\w+) \((.+)\);/) match_data = line.match(/CREATE INDEX (\w+) ON (\w+) \((.+)\);/) # 后续逻辑不变 end
2. 编写Ruby测试验证迁移的正确性
用Minitest编写集成测试,直接查询数据库索引,确保只新增预期的索引:
# test/integration/migration_index_validation_test.rb require "test_helper" class MigrationIndexValidationTest < ActiveSupport::TestCase test "迁移仅新增预期的person_id索引" do # 获取上游分支的索引名称列表 upstream_structure = `git show origin/main:db/structure.sql` upstream_indexes = extract_indexes(upstream_structure.split("\n")) upstream_index_names = upstream_indexes.map { |idx| idx[:name] } # 获取当前数据库中posts表的所有索引 current_post_indexes = ActiveRecord::Base.connection.indexes("posts") current_index_names = current_post_indexes.map { |idx| idx.name } # 计算新增的索引 new_indexes = current_index_names - upstream_index_names # 断言仅存在预期的新增索引 expected_index = "index_posts_on_person_id" assert_equal [expected_index], new_indexes, "迁移新增了非预期的索引,请检查迁移代码" end private def extract_indexes(sql_lines) indexes = [] sql_lines.each do |line| if line.match?(/CREATE INDEX (\w+) ON (\w+\.\w+) USING \w+ \((.+)\);/) match_data = line.match(/CREATE INDEX (\w+) ON (\w+\.\w+) USING \w+ \((.+)\);/) indexes << { name: match_data[1] } end end indexes end end
集成到GitLab CI/CD
在.gitlab-ci.yml中添加测试步骤,确保MR中自动运行验证:
test:index-validation: stage: test script: - bundle install --deployment - rails db:create db:schema:load RAILS_ENV=test - rails test test/integration/migration_index_validation_test.rb only: - merge_requests
3. 获取索引信息的实用方法
除了解析SQL文件,还可以通过Rails内置的ActiveRecord API直接查询数据库索引,更可靠:
- 获取指定表的所有索引:
# 返回IndexDefinition对象数组,包含name、columns、unique等属性 ActiveRecord::Base.connection.indexes("posts")
- 检查某个索引是否存在:
# 按字段检查 index_exists?(:posts, :person_id) # 按索引名检查 index_exists?(:posts, nil, name: "index_posts_on_person_id")
这些方法可以替代SQL解析,在测试中直接使用,避免因SQL格式差异导致的解析错误。
内容的提问来源于stack exchange,提问作者Anton-Ivanov
相关产品推荐
相关产品推荐

