JavaScript製SQLフォーマッタをJavaに移植してみた
JavaScript製のSQL整形ライブラリ https://github.com/zeroturnaround/sql-formatter の出来が良かったので、Javaに移植してみた。
https://github.com/vertical-blank/sql-formatter
オリジナル版をNashornで動かすと重かったり、hibernate-coreに付属の物は大量に依存があるのが辛かったりというのも理由の一つ。
使い方
こんな感じで文字列を渡すだけ。
import com.github.vertical_blank.sqlformatter.SqlFormatter;
System.out.println(SqlFormatter.format("SELECT * FROM HOGE"));
SELECT
*
FROM
HOGE
サブクエリや CASE WHEN
が出現してもいい感じ
import com.github.vertical_blank.sqlformatter.SqlFormatter;
System.out.println(SqlFormatter.format("SELECT a,b, CASE WHEN a=1 THEN 'A' WHEN a = 2 THEN 'B' ELSE 'X' END FROM HOGE JOIN FUGA ON FUGA.c = HOGE.c AND FUGA.d = HOGE.d WHERE HOGE.e = 100 AND EXISTS (SELECT * FROM PIYO WHERE PIYO.X = HOGE.X)"));
SELECT
a,
b,
CASE
WHEN a = 1 THEN 'A'
WHEN a = 2 THEN 'B'
ELSE 'X'
END
FROM
HOGE
JOIN FUGA ON FUGA.c = HOGE.c
AND FUGA.d = HOGE.d
WHERE
HOGE.e = 100
AND EXISTS (
SELECT
*
FROM
PIYO
WHERE
PIYO.X = HOGE.X
)
その他にも、 format
メソッドに引数としてインデントとして使う文字列や、プレースホルダを置き換えるパラメータをListやMapとして渡すことができる。
苦労とか
なるべく本家のソースに近い状態で移植したかったけど、そうもいかない部分がそれなりにあった。
JSとJavaの正規表現の違い
jsの String.prototype.split
は、渡したパターンが括弧でくくられていると、返される配列にマッチした結果が含まれる模様。
RegExp でキャプチャした結果を分割した配列に含める
> 'a1b9c'.split(new RegExp('(\\d)'))
[ 'a', '1', 'b', '9', 'c' ]
本家では文字列からパターンが最初にマッチした部分文字列を取得するのにこの挙動を使っていたが、Javaの String#split
にそんな挙動はないので、最初にマッチした部分文字列を返すメソッドを別途作成した。
他にも、 [^]*
という謎のパターンが使われていたり。これはどうもjs特有の、改行を含む全ての文字にマッチするものみたい。
Java で同等の結果を得るには (?s).*
とする必要があり、一部正規表現を書き換えざるを得なかった。
JS固有の構文
本家では、こんな構文が割と頻繁に使われていた。
`xxxx${list.map(s => s.xxx).join('|')}xxxx`
Javaのリストには map
や join
が生えてないので、同様のメソッドを生やしたリストのラッパーを作ることで対処した。
文字列への変数展開が無いのはさすがにどうにもならないので、連結したり String.format
でがんばったり。
さいごに
正規表現は言語間で微妙に違いがあるので要注意。
ちなみにデモページでは、GraalVMのnative-imageで共有ライブラリにして、それをNodeJSから呼び出すものをGoogleCloudFunctionsにデプロイしたものを呼び出している。
というかそもそも、こちらの記事はこれがやりたくて試した物だった。
JSはブラウザ上でそのまま動くからデモページが作りやすそうでうらやましい。
クエリビルダが生成したものとか、やんちゃに文字列を連結してる(!)とかでSQLのログが読みづらいことがあるので、今後しれっと組み込んでいこうと思う。