構文解析し、構造体としての表現(抽象構文木)を取得するための方法とその実装手順について記します。妥当性の検証や事前のセキュリティチェックをクエリを実行する前に行う必要がある場合にも、データクエリをSOQL文字列のままで扱うことは厳しいものがあります。このため、多くのアプリケーションでは、SOQLを直接記述するのではなく、一旦中間表現として何らかの構造体でクエリ情報を構成した後、最終的なクエリ実行の段階でSOQLとして文字列にシリアライズするといった方針を取っているものも多くあります。インテグレーション上の制約や、開発者向けのDXといった観点で、生の文字列のSOQLが入力として必要とされることもしばしばあります。このような場合に、アプリケーションが構造体としてクエリ情報を操作できるようにするために、入力として与えられたSOQLを構文解析することが求められてきます。Generator)を使うのが楽です。パーサジェネレータは何をしてくれるかというと、文法を定義したファイルを入力すると、その文法に従って文字列を解析し何らかの構造体として出力してくれるパーサプログラム自体を生成してくれます。今回はJavaScriptで動作することを目的としているので、パーサジェネレータ自体もJavaScriptで実装されているものを使うほうがよいでしょう。JavaScriptでのパーサジェネレータ実装はいろいろありますが、今回はPEG.jsを利用します。シンプルに文法を定義するSOQLの文法、といっても、「文法」が一体何を意味しているのかよくわからないかもしれません。ここでは『文字列の要素がどういう順番で並べられるかというルールを計算機が理解可能なように記したもの』とざっくりと理解しておきます。Grammar)という記述形式を用いて文法を定義します。通常、オープンな仕様の言語などでは何らかの形で文法についてもオープンにされていることが多いのですが、SOQLについてはリファレンスマニュアルはあるものの、計算機が理解可能なちゃんとした仕様レベルでの文法は公開されていません。こちらでリファレンス仕様から文法を定義してあげる必要があります。文法を作るなんて初めてだ、という方でもある程度わかりやすいように、シンプルな例から始めましょう。Object1のようなSOQLのみを解析可能な文法を作成してみます。PEG.jsでは以下のような形で記述できます。\t\n\r]*この文法を上から順番に見ていきます。whitespaces(__で表記。空白文字を1つ以上含む)で区切られているものとして定義されているのがわかります。これを満たしていることが(今回の)SOQLの構文としての条件となります。SelectClauseおよびFromClauseはどのようなものでしょうか。SelectClauseは文字列SELECT(大文字小文字区別なし)ではじまり、whitespacesで区切られた後、FieldList要素が含まれています。同じようにFromClauseは文字列FROM(大文字小文字区別なし)ではじまり、whitespaces、ObjectName要素が続いているのがわかります。SelectClauseから参照されているFieldListはというと、まず文法規則が/で区切られているのがわかります。PEGでは、この区切られたルールに対して、まず最初のルールにマッチするかどうかチェックし、マッチしない場合は次のルール、というように解析されていきます。Field要素で始まり、spacer(_で表記。Field要素と同等であることFieldおよびObjectName要素は、Identifier要素と同じであり、これは[0-9a-zA-Z_]の文字からなる文字列になります。実際に構文解析(パース)してみる本来ならパーサジェネレータであるPEG.jsに上記文法を入力としてパーサを生成させるのが正しい手順ですが、実はPEG.jsではオンライン環境でパーサ生成/実行までできてしまいます(これがJavaScript実装のパーサジェネレータの強みですね)。grammar"内にコピー&ペーストして実行してみます。input"には何らかのSOQL文を入力してみて下さい。〜のシンプルな形のみで、それ以外はエラーになるのがわかります。解析結果出力の構造を変更する上記のステップで、文法上妥当なSOQLについて正常にパースが実行できることは確認できましたが、出力として得られた情報はなんだかよくわからない入れ子の配列になっていました。構造化されているとは言えこのままでは少々アプリケーションが直接扱うには荷が重いため、生成する情報をカスタマイズしてみます。}のような構造体になるように調整します。\t\n\r]*上記では、各ルールの末尾に中括弧("{","}")で囲まれた表記が追加されていますが、PEG.jsではこれをactionといって、解析後の結果をどのような構造で返却するかをJavaScriptの関数で定義できるようになっています。fields:SelectClauseのように先頭にラベルが振られているものもありますが、actionの中からはこのラベル名を通じて要素の解析結果を変数として参照することができます。Onlineに貼り付けて実行してみましょう。解析後の出力結果が意図されたとおりになっているのがわかります。生成したパーサプログラムは、サイト上からそのままJavaScriptファイルとしてダウンロードできます。文法ルールの追加あとはもう少し複雑なクエリ表現でも解析可能なように、文法にルールを追加していきます。説明も面倒なので、こちらに文法定義を置いておきます。SOQLパーサの実装ライブラリ上記のパーサを実装したライブラリをすでに公開しています。対応するSOQLはまだ限られていますが、通常の用途であればほぼ問題なく構文解析できるようになっています。参考までに現在サポートされているSOQLの構文は以下のものです。CUBE句TYPEOF句やWITH句やその他のマイナーな構文もちゃんと対応したいところですが、まあ実用上こんなものでしょうか。構文解析結果の活用方法SOQLが構文解析できるようになったことで、以下のような活用方法が考えられます。今まで、あるSOQLが本当に妥当なものかどうかは、実際にAPIをコールしてクエリを実行するまでわからないのが実情でした。SOQLをオフラインで構文解析するおくことで、少なくとも構文上の間違いを持っているSOQLを検出することが可能になります。与えられたSOQLが妥当であるかどうかは、構文解析だけではまだ実はわかりません。構文解析に成功したとしても、項目やオブジェクトなどの情報は環境毎に異なるので、そのSOQLが妥当かどうかは、実際に存在する項目かどうか、そしてその項目のデータ型が何か、に依存しているからです。抽象構文木)のデータ構造を辿りながらチェックすることで可能になります。BYにも含まれること)もあります。そのような場合も解析後のASTを元にチェックを行うことが可能です。SOQLの補完もっともパワーを感じさせられるのは補完かもしれません。SQLなどでテーブル名や列名をダイナミックに補完してくれるフロントエンドを使ったことのある人もいるかもしれませんが、それが同様にSOQLでも可能になります。組織内のオブジェクトや関連する項目のリストは簡単にAPI経由で取得できるので、あとはそれを適切にユーザにフィードバックしてやればいいはずです。補完については、入力中の不完全なSOQL文を相手にすることになるので、実際には構文解析エラーが発生しますが、PEG.jsの場合はSOQL文中のどの位置の文字列が想定されているものと異なっていたか、エラーオブジェクトの中に通知されてきますので、それを利用して不完全なSOQLを構文上妥当な形に直すことができます。あとは構文解析された情報を利用して、現在のカーソル位置の要素を特定し、Salesforceメタデータ情報から適切な補完候補を引っ張ってきてリスト表示する、といった戦略を取ることができます。Consoleがあります。こちらはWebアプリケーションフロントエンドとして、SOQLの補完組み立てからSalesforceに対するクエリ実行までできるアプリケーションです。入力されたSOQLを解析し、ログイン済みのSalesforce組織からオブジェクトの項目情報などのメタデータを動的に取得して、補完候補を画面に表示します。こちらのアプリケーションはかなり昔のコード(実装もCoffeeScript)となっており、かつ今回説明したPEG.jsではなくjisonというパーサジェネレータ実装を使っています。文法についてもあまりサポートしていません。今後書き直されることもあるかもしれませんが、あまり期待しないでおいて下さい。https://github.com/stomita/soql-consoleSOQLの変換ここでは触れていませんでしたが、データ構造としてASTを作ってあげれば逆にSOQLへのシリアライズも可能であるということです。解析して得られたASTに対して適切なツリー変換を施してあげることで、ダイナミックに条件を挿入したり、合成したりと言ったことが比較的簡単になります。ついても、ASTから2つのクエリに分解してアプリケーション上で結果を結合する、といった荒業も可能になるかもしれません。まとめ構文解析って一見大変そうに見えますが、最近は文法さえ定義してしまえばいいので、お気楽でいいですね。SOQLにかぎらずいろいろトライしてみてはいかがでしょうか。文字列結合によるSOQL生成は、カジュアルに利用されることが多いですが、堅牢なアプリケーションにおいてはアンチパターンです。upLoginユーザーは見つかりませんでした。