StandardSQLのLinterを作りました

# 概要

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

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

# モチベーション

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

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

```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](https://pypi.org/project/sqlint/)
執筆時のバージョンは `0.2.4` です

```bash
pip install sqlint
```

## Lint

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

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

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

```bash
$ 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で指摘しない部分も修正されるので注意です.

```bash
$ 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` に書いておくと良いでしょう.

```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` オプションで任意の設定ファイルを指定できます.

```bash
$ 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に詳しい方いましたら、教えていただけると幸いです.