雑文発散

«前の日記(2015-07-22) 最新 次の日記(2015-07-24)» 編集
過去の日記

2015-07-23 [長年日記]

[Embulk] embulk-output-mysql の merge モードの挙動をしりたくてソースコードを眺めてみたりしていた

embulk-output-mysql プラグインには、MySQL へのインポート時のモード設定がある。現状だと insert, insert_direct, truncate_insert, merge, merge_direct, replace の6種類。

いま使いたい動作だと、merge もしくは merge_direct の挙動が近そうだったのだけど、ちょっとだけやりたいこととマッチしない部分があった。

例えば、こんなテーブルがあるとする(適当にいま考えた)。

CREATE TABLE ramen (
  id   BIGINT AUTO_INCREMENT,
  name VARCHAR(100) NOT NULL,
  location VARCHAR(100),
  created_at DATETIME NOT NULL,
  updated_at DATETIME NOT NULL,
  PRIMARY KEY (id),
  UNIQUE INDEX name_idx (name)
);

現在はこんな感じのデータが入っている状態だとする。

id name location created_at updated_at
1 しじみラーメン和歌山 北海道 2015-07-18 2015-07-18

このテーブルへ新しいデータとして、こんなデータをブチこみたい。「しじみラーメン和歌山」の location が間違っていた(北海道じゃなくて青森が正しい)のに気がついたので、修正が入っているんだよ。

name location created_at updated_at
しじみラーメン和歌山 青森 2015-07-19 2015-07-19
中華のカトウ 新潟 2015-07-20 2015-07-20
坂新 福島 2015-07-20 2015-07-20

これを embulk-output-mysql の merge / merge_direct モードで入れると、こんな感じになる。ちゃんと「しじみラーメン和歌山」が「青森」に変わっている。でも、本当は更新したくなかった created_at の日付まで更新されちゃっている。この created_at を更新対象から外せないかなぁというのが今回の悩みポイント。

id name location created_at updated_at
1 しじみラーメン和歌山 青森 2015-07-19 2015-07-19
2 中華のカトウ 新潟 2015-07-20 2015-07-20
3 坂新 福島 2015-07-20 2015-07-20

merge_direct モードで発行されるクエリは、抜粋するとこんな感じになる。実際にはプリペアドステートメントを作成した後でパラメータを設定してガシガシ回しているみたいだし、このクエリがそのまま発行されている訳ではない。

INSERT INTO ramen (
  name,
  location,
  created_at,
  updated_at)
VALUES (
  'しじみラーメン和歌山',
  '青森',
  '2015-07-19',
  '2015-07-19' )
ON DUPLICATE KEY UPDATE
  name = VALUES(name),
  location = VALUES(location),
  created_at = VALUES(created_at),
  updated_at = VALUES(updated_at)
;

このクエリを次のように書き換えられれば、この悩みは解決しそうな気がする。ON DUPLICATE KEY UPDATE の後ろにあった created_at = VALUES(created_at) を取り除いた感じ。

INSERT INTO ramen (
  name,
  location,
  created_at,
  updated_at)
VALUES (
  'しじみラーメン和歌山',
  '青森',
  '2015-07-19',
  '2015-07-19' )
ON DUPLICATE KEY UPDATE
  name = VALUES(name),
  location = VALUES(location),
  updated_at = VALUES(updated_at)
;

この ON DUPLICATE KEY UPDATE あたりの構文は embulk-output-mysql プラグインのこの辺りで組み立てられているのは把握できた。

for (int i=0; i < toTableSchema.getCount(); i++) {
    if(i != 0) { sb.append(", "); }
    String columnName = quoteIdentifierString(toTableSchema.getColumnName(i));
    sb.append(columnName).append(" = VALUES(").append(columnName).append(")");
}

一方で、なにやら mergeKeys という変数が渡されているいることにも気が付いた。

@Override
protected String buildPreparedMergeSql(String toTable, JdbcSchema toTableSchema, List<String> mergeKeys) throws SQLException

現在はドキュメントに書かれていないのだけど、これは、Embulk の config で次のように指定するもののようだ。

out:
  type: mysql
  [snip]
  merge_keys:
    - name
    - location

このリストを使って、ON DUPLICATE KEY UPDATE の対象カラムを指定することができるんじゃないか?と思って、手元でちょっと修正してみたら、それっぽく動くことは動いた。

ただ、embulk-output-postgresql の中での使われ方を見ると、どうもマージ対象を絞り込むためのキーとして使うものみたいだったし、用途が違っている気がする。名前が mergeKeys ってところで気がつけよって話だけど。

mergeKeys 以外に updateColumns みたいなリストを新たに渡すために Config 項目を増やして、それを buildPreparedMergeSql() に渡すように修正して、、、という感じで修正すれば実現可能な気がするんだけど、Java 歴が数ヶ月、Embulk 歴が3日の状態だといろいろ悩んでしまって、なかなか先に進まない。

あと、merge モードのときに UPDATE 対象のカラムが限定できる機能って一般的に欲しいものなのだろうか(オレはいま欲しいんだけど)。

この例だと created_at が気になるだけなので、こいつにデフォルト値を設定しておいて、INSERT / UPDATE 対象から外しちゃえばいいという手法で逃げられそうな気がするけど。デフォルト値を入れられない感じのデータ場合の需要がオレの他にもあるんだろうか。。。