道化的プログラミング

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のリストには mapjoin が生えてないので、同様のメソッドを生やしたリストのラッパーを作ることで対処した。

文字列への変数展開が無いのはさすがにどうにもならないので、連結したり String.format でがんばったり。

さいごに

正規表現は言語間で微妙に違いがあるので要注意。

ちなみにデモページでは、GraalVMのnative-imageで共有ライブラリにして、それをNodeJSから呼び出すものをGoogleCloudFunctionsにデプロイしたものを呼び出している。
というかそもそも、こちらの記事はこれがやりたくて試した物だった。
JSはブラウザ上でそのまま動くからデモページが作りやすそうでうらやましい。

クエリビルダが生成したものとか、やんちゃに文字列を連結してる(!)とかでSQLのログが読みづらいことがあるので、今後しれっと組み込んでいこうと思う。

参考にしたもの