You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何编写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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.19 11:03:12