ridgepole: v0.7.6でignoreという属性をつけられるようにした

ridgeporeのv0.7.6でignoreという属性をカラム・インデックス・FKにつけられるようにした。

github.com


あんまり周知していなかったせいで、全然知られていないが、ridgepoleでもexecuteがつかえる。

https://github.com/winebarrel/ridgepole#execute

create_table "authors", force: :cascade do |t|
  t.string "name", null: false
end

create_table "books", force: :cascade do |t|
  t.string  "title",     null: false
  t.integer "author_id", null: false
end

add_index "books", ["author_id"], name: "idx_author_id", using: :btree

execute("ALTER TABLE books ADD CONSTRAINT fk_author FOREIGN KEY (author_id) REFERENCES authors (id)") do |c|
  # Execute SQL only if there is no foreign key
  c.raw_connection.query(<<-SQL).each.length.zero?
    SELECT 1 FROM information_schema.key_column_usage
     WHERE TABLE_SCHEMA = 'bookshelf'
       AND CONSTRAINT_NAME = 'fk_author' LIMIT 1
  SQL
end

冪等性を担保するために少しだけ拡張していて、ブロック内の返り値がtrueの場合のみ、executeを実行するようにしている。 ブロックにはコネクションを渡しているので、存在チェックをすることで重複してexecuteが実行されないようにすることができる。

…が、たとえばN-gramのフルテキストインデックスのように、Railsが対応していないカラムやインデックスをexecuteで作成しようとするとめんどくさいことになる。

例えば

create_table "books", id: false, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8", force: :cascade do |t|
  t.string "title", null: false
  t.index ["title"], name: "ft_index", type: :fulltext
end

execute("CREATE FULLTEXT INDEX ft_index ON books(title) WITH PARSER ngram") do |c|
  rows = c.raw_connection.query('SHOW INDEX FROM books', as: :hash)
  !rows.map{|r| r.fetch('Key_name') }.include?('ft_index')
end

のような感じでインデックスを貼ろうと思うと、先に「N-gramではないインデックス」が作成されてしまって、「N-gramのインデックス」はexecuteでは作成されなくなる。

executeのブロック内のクエリで頑張って「N-gramではない場合は再作成」というようにすれば、「N-gramのインデックス」を貼ることはできるが、流石にめんどくさいので、カラムやインデックスにignoreという属性をもたせることにした。

ignoreを使うとこんな感じになる

# -*- mode: ruby -*-
# vi: set ft=ruby :
create_table "books", id: false, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8", force: :cascade do |t|
  t.string "title", null: false
  t.index ["title"], name: "ft_index", type: :fulltext, ignore: true
end

execute("CREATE FULLTEXT INDEX ft_index ON books(title) WITH PARSER ngram") do |c|
  rows = c.raw_connection.query('SHOW INDEX FROM books', as: :hash)
  !rows.map{|r| r.fetch('Key_name') }.include?('ft_index')
end

…先ほどとほぼ同じだけれど、ignore: trueがついたカラム・インデックス・FKについては、ridgepoleは無視、全く変更に関与しないようにしている(つもり)。 なので、このスキーマ定義はRailsでは対応していない「N-gramのインデックス」を冪等性を保ったまま作成することができる(はず)。

テストが通ったらリリースする予定。