やわらかテック

興味のあること。業務を通して得られた発見。個人的に試してみたことをアウトプットしています🍵

sequelizeで全テーブルに対してTRUNCATEを実行する

業務でsequelizeというNode.js製のORMマッパーを触る機会がありました。
単体テストを実行する度に、データベース(MySQL)のテーブルをクリア(全削除)するために制約を無視してTRUNCATEを全テーブルに対して実行したいのですがsequelizeでは対応するメソッドは提供されていませんでした。

// 外部キー制約のあるデータが存在しているとエラーになる
await sequelize.truncate({ cascade: true, restartIdentity: true });

// テーブルに対応するモデル1つずつに対して実行する必要がある
await User.destroy({ truncate: true });

Model Querying - Basics | Sequelize

テーブルをドロップするという手もありますが、その後にテーブルを再作成・マイグレーションを実行する必要があるため非常にコストが高くなってしまいます。何とかならんものか...と悩んだ結果、生SQLを実行することにしました。
簡単に実行できるように関数化したので、同じ問題で困っている人がいれば使ってみてください。

全テーブルからデータを削除する

以下の関数をコピペして実行してください。
外部キー制約を無視してTRUNCATEを実行するようになっているので問答無用でデータが消えます。

const Sequelize = require('sequelize');
const sequelize = new Sequelize(/* ... */);

const truncateAllTable = async () => {
  const tables = await sequelize.getQueryInterface().showAllTables();
  if (tables.length > 0) {
    const trancateSql = tables.reduce((acc, table) => `${acc} TRUNCATE TABLE ${table};`, '');
    await sequelize.query(`
      BEGIN;
        SET FOREIGN_KEY_CHECKS = 0;
        ${trancateSql}
        SET FOREIGN_KEY_CHECKS = 1;
      COMMIT;
    `);
    console.info(`:::全テーブルに対してTRUNCATEを実行しました(適応したテーブル数: ${tables.length})`);
  }
}

Sequelizeクラスのインスタンスを作成する際に指定する接続情報は適当な値に更新してください。
あとは定義した上記の関数を呼び出せば、全テーブルからデータの削除が完了します。

await truncateAllTable();

以上です。お疲れ様でした。

おまけ: SQLの解説

まず外部キーの制約を解除するために一時的にFOREIGN_KEY_CHECKSを0に更新しています。
これで外部キーの制約を無視して、データの削除が可能になります。ただしFOREIGN_KEY_CHECKSを1に戻しておかないと、以降、外部キー制約によるデータ保護がされないため、注意が必要です。

 BEGIN;
    SET FOREIGN_KEY_CHECKS = 0;
    :
    SET FOREIGN_KEY_CHECKS = 1;
COMMIT;

dev.mysql.com

あとは息をするように、トランザクションをかけておけば安心です。
外部キー制約の適応有無が変数1つで変更できることには驚きました。
個人的には「こんな簡単に制約を無視できるようにして良いのか...?」と思うのですが、確かにデータをまとめて消したいといったケースには便利ですね。どちらかというとTRUNCATEDELETE句のオプションとして指定できても良い気がします。

少しでも「ええな〜」と思ったらはてなスター・はてなブックマーク・シェアを頂けると励みになります。