はじめまして、LOBでGopherをやっているChifung Cheungです。
最近は筋トレに集中してあんまりネタがないので、夏休みに作ってみたDSLについて軽く話したいと思います。
やりたいこと
みんなさんAWS DynamoDBのコマンドラインツールaws-cli
を使ったことがありましょうか?
簡単なCRUD操作をするには、aws-cli
ではかなり複雑になります。
例えば、users
テーブル内のgroup_id = 1
(HashKey)とuser_id = "DUMMY_USER_1"
(RangeKey)のユーザをGETするには、
aws dynamodb get-item --table-name users --key '{ "group_id": {"N": "1" }, "user_id": {"S": "DUMMY_USER_1"} }'
のように書けなければならない。
このような煩雑なコマンドを、
select * from users where group_id = 1 and user_id = "DUMMY_USER_1";
のようなSQL-likeのように書けるなら、大喜びでしょうね。
便宜上、この自作言語をDDQL(DynamoDB Query Language)と名付けます。
設計
アーキテクチャ
目標を達成するには、ASTParserとExecutor、2つのパーツが必要です。
- ASTParserは、DDQLを機械が読めるデータ構造AST(Abstract syntax tree)に変換する役目を担います。極ピュアなパーツであります。
- Executorは、ASTを読み込んでAWS形式に変換してAWSで実行するためのパーツです。要注意なのは、ASTとAWSのコマンドは一対一の関係ではありません。例えば上記のDDQLを見るだけで、
group_id
とuser_id
はHashKeyかRangeKeyかわからないので、AWSのdescribe table
でテーブルのスキーマを取得してその情報でget
かquery
かどちらを使うのを決めないといけません。
MySQLのようなしっかりしたシステムだと、上記の部分だけではなく、OptimizerやSchedulerなどの登場人物がいって、複雑なSQLを簡素化したり実行環境によって効率的な実行計画を練ったりしますが、今回のタスクでは特に必要がありません。
また、世の中ではASTParserというパーツが存在しないQuery DSLも存在します。例えばaws-cliが使っているDynamoDB Low-Level APIの仕様をご覧ください。
POST / HTTP/1.1
......
X-Amz-Target: DynamoDB_20120810.GetItem
{
"TableName": "Pets",
"Key": {
"AnimalType": {"S": "Dog"},
"Name": {"S": "Fido"}
}
}
DynamoDB Low-Level APIは、ASTをJSONの形式で表現して、マシンにとってはとても読み取りやすい構造になっています。ほかに、ElasticsearchのQuery DSLも似た思想で設計されています。
JSON以外に、YAMLやS式でDSLを表現するのもかなりあります。既存のフォーマットでDSLを表すのが、AST Parserを設計・開発するの手間を省け、本来価値があるべきの部分の開発に集中できます。
とはいえ、使いやすさこそがDSLの存在価値なので、手軽くDynamoDBのデータを確認できるツールはやはりほしいですね。
Tech Stack
parserを書く方法は、大体三種類に分かれています。
- 手書き。本気で自作言語を作るならこの手でしょうね。一番実行効率的な方法ですが、開発時間が長くて腕次第で汚いコードになってしまう可能性があります。この問題に対して、既存のオープンソースのparserをベースで開発するのも割といい選択肢です。近年、parserをライブラリとして公開する言語が多くて、例えばgolang, TypeScript, Pythonのような言語にはparser/compiler/astのようなライブラリがあって、ASTベースで言語をカスタマイズするのも可能になりました。Golangのastに基づいて開発したHashiCorpのSentinelはその一例です。
- Parser Generator。構文定義ファイルからParserを作る手法です。その代表はYaccとbisonです。強力ですが、学習コストが高いので僕みたいの初心者にはハードルが高いです。
- Paser Combinator。複数の簡単なParserから複雑なParserを作る考え方。構造がキレイで初心者に優しくてそこそこのパーフォーマンスが出ます。SQL-likeの言語を作るぐらいなら割とふさわしい。
なので、Parser Combinatorでやるのを決めました。
Gopherなのでgoで書こうとしたらgoにはいいParser CombinatorのライブラリがないのでRustにしました。ライブラリはnomにしました。
Parser Combinatorについて詳しく下記のスライドに参考できればと思います。
開発
まだまだ開発中ですが、簡単なselect文を実行するまでにはできました。
そろそろ年末年始になるので正月太りにならないように頑張って筋トレをするので細かく紹介を割愛します。
興味ある方はぜひコードをご一読ください。
https://github.com/cheung-chifung/ddql
バイバイ