2020年5月24日日曜日

webpackのproductionモードでも動くSequelizeの直書きSQLを書く方法


背景

sequelizeとはnodejsでDBを便利に利用するためのORマッパーです。
webpackとは、javascriptで作ったプロジェクトを束ねてくれるプログラムです。自分はtypescriptで書いた作ったプロジェクトをjavascriptへビルド(トランスパイル)する時に利用してます。

sequelizeでは検索条件としてSQLを直書きして渡せたりするのですが、webpackをproductionモードでビルドを行うと、単語の短縮化によってエラーが発生することがあります。
備忘録を兼ねて対応方法を共有します。

問題

下記の2つのモデルを例として使います。
  • User
    DBのテーブル名はusers
  • Article
    DBのテーブル名はarticles
    user_idを持つ

この2つのテーブルに対して「Articleを2つ以上もつUserを取り出す」ためのクエリを直書きすると、こう書けます。

import { literal, Op } from "sequelize"

const users = User.where({ [Op.and]: [
  literal('(select count(*) from articles as article_count \
where article_count.userId = User.id) >= 2')
}})

これはwebpackがdevelopモードなら問題なく動くのですが、modeをproductionにすると下記のようなエラーが発生します。

UnhandledPromiseRejectionWarning: SequelizeDatabaseError: Unknown column 'User.id' in 'where clause'

productionモードでwebpackのトランスパイルを動かすと、minimize機能によって単語などが短縮されます。
短縮された結果、プログラムとしてはUserのことを「p」として扱っているけれど直書きしたクエリでは「User」となっているため、「User」という文字列にに該当するものが無いためにエラーが発生しています。

対応方法

この問題に対応するにはいくつかの方法を思いつきます。
  • literalで直書きするSQLのモデル名にプログラムが認識している名前を適用する
  • literalを使わずにsqlを組み立てる
    (記事数を取得するクエリを実行した後、その値を元にユーザーを絞り込むなど)
  • webpackのproductionモードでminimizeを無効にする

productionモードで直書きSQLを動かしたいので、「literalで直書きするSQLのモデル名にプログラムが認識している名前を適用する」方法をここでは解説します。

短縮されたモデルの名前は .name で取れるため、下記のように記述することでproductionモードでも動くクエリになります。
(文字列をバッククーオート(`)での定義にし、「User」の箇所を「${User.name}」にしています。)

import { literal, Op } from "sequelize"

const users = User.where({ [Op.and]: [
  literal(`(select count(*) from articles as article_count \
where article_count.userId = ${User.name}.id) >= 2`)
}})

この記述ならproductionモードでビルドしても動いてくれます。

まとめ

webpackのproductionモードでビルドすると単語が短縮化されるために直書きSQLが動かなくなることがありますが。 .name でモデルの文字列を取り出してSQLに適用すれば、短縮化されても動くSQLを作れました。

0 件のコメント :