Rails6でのSQLServerにてinsert_all!
は使えるがinsert_all
が使えなく、調査したのでメモ。
出るエラーは以下
Model.insert_all([{name: 'test'}])
ArgumentError: ActiveRecord::ConnectionAdapters::SQLServerAdapter does not support skipping duplicates
環境
activerecord-sqlserver-adapter (6.0.0)
activerecord (~> 6.0.0)
Railsのコードを読む
結論を先に言うと、ActiveRecord#insert_all
は以下でon_duplicate: :skip
という主キーが重複するレコードをスキップする引数をデフォルトで渡しているが、activerecord-sqlserver-adapter
ではこれをサポートしていないのが原因。
def insert_all(attributes, returning: nil, unique_by: nil)
InsertAll.new(self, attributes, on_duplicate: :skip, returning: returning, unique_by: unique_by).execute
end
少し深堀りすると、ActiveRecord#insert_all
のinitializer
の中でサポートしているアダプタをチェックしている
def initialize(model, inserts, on_duplicate:, returning: nil, unique_by: nil)
## 省略
ensure_valid_options_for_connection! # ← ここ
end
ensure_valid_options_for_connection!
の中身を見てみると、一番最初に書いたエラーを吐いている部分があり、これによって使えないっぽい。
def ensure_valid_options_for_connection!
## 省略
if skip_duplicates? && !connection.supports_insert_on_duplicate_skip?
raise ArgumentError, "#{connection.class} does not support skipping duplicates"
end
end
supports_insert_on_duplicate_skip?
の中身を見てみると、サポートしているアダプタはtrueを返すようになっている。
trueを返すのはmysql, sqlite3, postgresql
だけなので、sqlserverは使えず、ここでraiseされて使えないことがわかる
insert_all!は何故使える?
insert_all!
はon_duplicate: :raise
になっている
def insert_all!(attributes, returning: nil, record_timestamps: nil)
InsertAll.new(self, attributes, on_duplicate: :raise, returning: returning, record_timestamps: record_timestamps).execute
end
これによって、先ほどのensure_valid_options_for_connection!
の条件がfalseになり、また重複行があった場合はActiveRecord::RecordNotUnique
が発生するので問題なく使える。
Issueがあった
実はもうIssueが立っていて、上に書いたことが説明されている
修正しようとしたPRもあるが、Draftなのでまだ使えなそう。。。
v7.0.0のリリースノートを見てもon_duplicateに関することは書いていないので、まだ使えなそうです。