StandardSQLのLinterを作りました


概要

StandardSQL(BigQuery)のSQL Linter/Formatterを作りました.
需要はあんまりなさそうですが、せっかくなので内容について少し書いてみます.

コードはこちら
https://github.com/shigeru0215/sqlint

モチベーション

仕事で結構BigQueryのSQLを書くことがあるのですが、メンバによって細かい書き方が違ったのでチーム内でルールを決めました.
理由は、主に見やすさのためです.

例えば、以下のような感じです.

select   /* 予約後はすべて小文字 */
    a    /* インデントは4つずつ */
    , b  /* 改行のカンマは行末ではなく行頭にする */
from
    table1 as t1
    inner join table2 as t2
        on t1.x = t2.y  /* fromで一段下げ, onでも一段下げる */
where
    x > 10
limit
    100

ただ、SQLが小さければレビューで指摘すればいいですが、百行超えをチェックするのは大変です.
そこでLinterで手軽にチェックできるようにすれば、CIとかにも仕込めて便利かと思って作りました.

使い方

Flake8をイメージしてもらえると良いかと思います

pip install

PyPi link
執筆時のバージョンは 0.2.4 です

pip install sqlint

Lint

こんな感じに、lint/formatしたいSQLがあるとします.

$ cat example.sql
select
    a,
   b
FROM
    table1 as t1
    join table2 as t2
        on t1.x= t2.y
where
    x > 10
limit 100

そのまま引数に与えると、いろいろ教えてくれます.

$ sqlint example.sql
example.sql (L2, 6): comma must be head of line
example.sql (L3, 1): indent steps must be 4 multiples, but 3 spaces
example.sql (L4, 1): reserved keywords must be lower case: FROM -> from
example.sql (L6, 5): join context must be fully: JOIN -> INNER JOIN
example.sql (L7, 16): whitespace must be before binary operator: x=

指摘内容は上から

  • カンマは行末ではなく行頭にする ★
  • インデントは4つずつのところ、3つになっている ★
  • FROMなどの予約後は、全て小文字にする ★
  • JOINは省略せずに、INNER JOINなどフルコンテキストで書く
  • 演算子の左右はスペースを開ける

です.
内容の是非は前述の通りローカルルールですが、★をつけた部分は設定ファイルで変更できます(後述)

Format

-f オプションをつけると、元ファイルは変更せずにフォーマットされたSQLを出力します.
※ 現状では、Lintで指摘しない部分も修正されるので注意です.

$ sqlint example.sql -f
select
    a
    , b
from
    table1 as t1
    inner join table2 as t2
        on t1.x = t2.y
where
    x > 10
limit
    100

蛇足ですが、Lint自体は割と簡単(土日の正味2日ぐらい?)でできていて、チームメンバに展開しました.
そしたらフォーマット機能もほしいと言われたので追加実装してたのですが、面倒で手こずってしばらく放置でした.

Formatterの実装で、データ構造がイケていなくてLinterの方も書き換えたりしたので、最初の設計は大事だなーと感じました.

設定ファイルについて

設定ファイルはiniファイルで指定します.
flake8 などに相乗りで、 tox.ini に書いておくと良いでしょう.

tox.ini
[sqlint]
max-line-length = 128
# Default lint settings
# Comma position in breaking lines
# - head (default)
# - end
comma-position = head
# Reserved keyword style.
# - lower(default): All words are lower. (e.g) select
# - upper-head: Only a head of words is upper. (e.g) Select
# - upper-all: All head word is upper. (e.g) SELECT
keyword-style = lower
# indent steps in breaking a line
indent-steps = 4

[flake8]
# ...
  • max-line-length: Integer
    • 1行の最大文字数で、これを超える行はいい感じに途中で改行します(デフォルト 128)
    • (注意) 改行のロジックが甘いので、コメントが入ってると変な感じなることもあります(要修正)
  • comma-position: [head|end]
    • 改行時のカンマの位置を行頭か行末か指定します(デフォルト 行頭)
  • keyword-style: [lower|upper-head|upper-all]
    • 予約後の小文字/大文字指定です(デフォルト lower)
    • ex) [select|Select|SELECT]
  • indent-steps: Integer
    • インデントをいくずつにするかの指定です(デフォルト 4)

-c オプションで任意の設定ファイルを指定できます.

$ sqlint example.sql -f -c /path/tox.ini

追加機能とか実装した感想

追加または修正する予定の機能たちです.いつ追加されるかは未定です.
もしバグとかあったら、教えていただけると感謝です.

Linting/Format機能がつたないので修正

前述の通りチームのルールに合わせてLintingをしています.
が、まだ実装できていない部分や、チームのルール以外にもチェックできる点があると思います.
例えば、

  • 宣言されていないエイリアスを使おうとしている
  • joinのon句で比較演算子の左右を、Join先(From句に与えられているテーブル)が左に来るように統一する
  • サブクエリを禁止する

などなど.

また、Format機能もBigQueryのものよりましとはいえ、まだまだ改善の余地があります.
特にコメントを適当に処理しているので、SQLによってはだいぶずれると思います.

Chrome Extensionにしたい

Chrome extensionにして、BQのコンソール上で直接Formatしたいなと思って結構頑張ってました.
ただ、コンソール上に入力されたSQLをextension側で取得する方法がわからなかったので、保留になってます.

正確には離散的に取得できるときはあるのですが、入力時にリアルタイムで取得する方法がわかりませんでした.

BigQueryやChrom extensionに詳しい方いましたら、教えていただけると幸いです.