GoからZetaSQLを使う
この記事は 2021 Go Advent Calendar 4日目 の記事です。
Introduction
Goを使ってBigQueryやSpannerのSQLをフォーマットしたりパースしたりしたいなと思い調べたらGoogleが公開しているZetaSQLというライブラリにたどり着きました。
ただZetaSQLはC++とJavaで書かれていたのですが、他にGo製のOSSなどもなく自前でSQLパーサーをちゃんと書くのも大変なのでこれを使ってどうにかできないか模索した話になります。
最終的にはGoからZetaSQLを呼び出すserverを作りました。↓
What’s ZetaSQL
ZetaSQLはGoogleの標準SQLの方言であり、BigQueryのクエリエンジンであるDremelでも使われてるそうです。
ちゃんとした公式の情報がないですがApache BeamでもサポートされておりDataFlow SQLはこのSQLに則っていると思われます。
Calling from Go
Goが好きなので、このZetaSQLをGoから扱えるようにしようと思います。
このZetaSQL本体はBazelでビルドされているので、Bazelを使って取り込めます。またC++ライブラリとしてビルドできればCGOを使ってGoにバインドすることが可能です。
設定にあたっては下記のレポジトリがとても参考になりました。
ebendutoit/zetasql-analyzer-server
1. CGO
まずはGoとZetaSQLをリンクさせるコードです。今回一つの関数を例にコードを貼っていきます。まずは ZetaSQLの必要なファイルをimportして、C++側からZetaSQLをコールするラッパーを定義します。C++に慣れた人なら多分問題ないのでしょうが、普段Goばっか書いてるので自分は手こずりました。↓
次にGoとC部分をリンクします。 import C
の上にコメントとしてCの定義を書けば、コンパイルする際に読み込まれてCの型やincludeした関数にアクセスできます。↓
これでGoの関数として呼び出すことができます。zetasql-serverではこれをサーバの結果として返すようにしました。
2. Build
前述の通りBazelを使ってビルドします。
時間の関係で .bazelrc
や CROSSTOOL
の細かい設定の説明は実際のソース見てもらえればと思いますが、端折るとWORKSPACE
の記述により google/zetasql
を取り込みます。これは google/zetasql
側で定義されてる関数を呼び出すことでビルドしています。
実際に google/zetasql
内でも同様に定義されています。
またBuildに関しては下記の BUILD
ファイル↓の 定義よりCGOをTrueとすることで、Cを取り込めます。これで bazel build ://zetasql-server
でビルドすることができます。
ただZetaSQLのビルドにとても時間がかかるようで、自分マシンでbazelコマンドを実行すると15〜20分ぐらいかかってしまって検証がとても非効率です、、、。
(ZetaSQLのビルドをなんとかしたい、、、)
3. Run
Dockerfileを見てもらえればわかりますが、かなり泥臭いです。bazelでビルドしたGoのバイナリを拾ってそれを実行しています。
bazel runしても動くので環境によってはそれでも大丈夫かなと思います。
4.In the Future
今後としては、ビルドできたのならわざわざサーバーじゃなくてcliとして扱えるほうが良いよなと思っており別レポジトリ作って新たなラッパーを作ろうかなと思っています。
またASTを構造化(JSONとか)した形で返せるようにしたいので、もう少しCGOについて学ぼうかなと思っています。
現時点ではZetaSQLのASTのDebug出力をJSONにするというパーサーは強引に書いたのですが、そもそもZetaSQLからGoにわたすところを工夫できないかなと考えています。パーサーに関しては下記の記事で書きました。
Conclusion
Goを使ってSpannerやBigQueryのSQLで遊びたかっただけなのですが、まさかこんなに大変なことになるとは思っていませんでした。あまり google/zetasql
自体にアップデートが頻繁にあるわけではなさそうですが、 仕事でGCPを使ってる以上は引き続きウォッチし続けていきたいなと思います。