こんにちは。CData Software Japan リードエンジニアの杉本です。
今回はCData 製品の中で一番奥が深いといっても過言ではない、CData REST Driver のカスタマイズ方法を徹底解説します。
ここではゴールとして「REST Driver の特性・仕様を理解し、シンプルなREST APIであれば、RSDファイルを自身でカスタマイズして作成することができるようになる」ということを目標に解説していきます。
なお、Web APIとは? というお話や、VS Code、Postmanの使い方などについては、細かい解説を行っていないのでご了承ください。Web APIを何かしら触ったことがある方、を主な対象としています。
今回はCData REST JDBC Driver を対象として行っていますが、作成したRSDファイルはどのテクノロジーでも利用可能です。
開発マシンはWindows、マシンにはOpenJDKをインストールして実装しています。
使用するツールなど
効率的なカスタマイズのため以下のソフトウェアを予めダウンロードしておきましょう。
・DbVisualizer(12.1.9を使用しました):https://www.dbvis.com/
・Postman:https://www.postman.com/
・VS Code:https://code.visualstudio.com/
・Fiddler(Classic):https://www.telerik.com/fiddler
(本記事ではJDBC Driver を利用していますが、ODBCやADO.NET、Excel Add-in でも利用可能です)
また、以下のVS Code 用スニペットもあると便利です。
https://kageura.hatenadiary.jp/entry/cdatasnippet
対象のAPI
対象とするAPIはパブリックに公開されていて、皆さんが利用できるものを選びました。
https://docs.postman-echo.com/
https://swapi.dev/
https://www.odata.org/getting-started/understand-odata-in-6-steps/
REST Driver のインストールとDbVizualizer の設定
最初にカスタマイズに利用するCData REST JDBC Driver をインストールしておきます。
https://www.cdata.com/jp/drivers/rest/download/jdbc/
インストールが完了したら、DbVisalizerで利用できるようにDriver Manager から登録しておきましょう。
REST Driver のカスタマイズには「.rsd」という拡張子のテキストファイルを用いて行います。
今回は予め「C:\CData_REST\HandsOn」というフォルダを作成しておき、そこにRSDファイルを作り込んでいく想定で進めます。
そして、REST Driver の接続では以下のような文字列を設定しておきます。このLocationに指定したフォルダのRSDファイルが読み取られ、REST Driver で利用できるようになります。
jdbc:rest:Location=C:\CData_REST\HandsOn;
カスタマイズするにあたって覚えておいたほうが良いこと
CData REST Driver をカスタマイズしていく際に、Driver がどのようなAPI Request を行っているのか? をウォッチすることは重要です。
API Request を確認する方法はLogを出力する方法とFiddlerというツールを使う方法の2種類があります。
使い方については以下の記事をそれぞれ参照してみてください。
https://www.cdata.com/jp/blog/2018-12-07-110000
https://www.cdata.com/jp/blog/cdatafiddler
個人的にはAPI Scriptをカスタマイズする場合は、Fiddlerを使うのがおすすめです。実際の通信の流れやパスワードなどセキュアな情報を含めたやりとりがリアルタイムで確認できるためです。
API Script とは
それでは早速始めていきましょう。
CData REST Driver は内部でRSDファイルというREST APIへのリクエストや取得した結果を各カラムへパースするロジックを記述するための「API Script」という独自のXMLベースのプログラミング言語があります。
https://cdn.cdata.com/help/DWH/jp/jdbc/pg_apischemaintro.htm
ユーザーはこのAPI Script を用いて様々なREST API の仕様に対応したCData Driver を作成することができます。
API Script は主に以下のような機能を定義、実装することができるようになっており
・CData Driver を通じて利用できるテーブル・カラムのデザインを定義する
・if/else構文やcase 構文などでプログラミングの処理を記述できる
・HTTP Request やSleepなどのオペレーションを呼び出す
・変数の定義、オペレーションへの引数、処理結果の作成・変更を行うことができる
・処理結果をもとに、レコード・カラムの結果を出力する
https://cdn.cdata.com/help/DWH/jp/jdbc/pg_CustomSchemas.htm
全体像としては以下のような構成で成り立っています。これをもとにREST Driver がテーブルデザインとして読み取り、SQLを受け付けるようになります。
Hello World:Push とは何か?
とはいえ、実際に動かしてみないと、なかなかイメージが沸かないと思うので、まずはHello Worldをしてみましょう。
REST Driver をカスタマイズしていく上で、もっとも重要なポイントは処理内容とレコードの結果を紐づけることです。
最初に最もシンプルなREST Driver のテーブルを作ってみます。
「C:\CData_REST\HandsOn」のフォルダに「Sample.rsd」ファイルを作成し、以下のXMLを貼り付けます。
これは「Hello」というカラムを持つ「Sample」テーブルです。
これをDbVisualizer から読み込むことで、以下のようなビューが表示され
「SELECT Hello FROM Sample;」のSQL文で以下のような結果が取得できます。
これを細かく解説していきましょう。
まず、カラムは以下の行で定義されています。この行を増やすことで、カラムをどんどん追加して定義することが可能です。また、xs:typeというプロパティでカラムの型情報を定義することができます。
SELECT処理は「」という記述の中で実施されるというルールがあります。
「 」は「Hello」という変数(アイテムと呼ばれています)に「World」という値を格納しています。
そして「 」という処理(キーワード)が呼び出されることにより、変数として格納されている項目がカラムの項目名のマッピングされて、レコードの結果として出力される、という仕組みになっています。
もう少し応用してみましょう。
例えば、「Hoge」というカラムを追加してみます。
全体像は以下のようになります。
さらにpushの前に「 」という変数の定義を追加しました。
これをもとに処理結果を見てみましょう。
なお、カラムをカスタマイズした場合は、Driver が持つスキーマの情報をリフレッシュする必要があります。
その場合は「reset schema CACHE;」というクエリを呼び出すことでリフレッシュが可能です。
これで以下のようにクエリを実行してみると、2つのカラムの結果が出力できました。
SELECT * FROM Sample;
では、レコードを増やす場合はどうするのでしょうか?
以下のように変数の定義とpushを繰り返すことで、レコードを増やすことが可能です。
なお、一度pushを行うと指定していた変数はリフレッシュされます。そのため、以下のような記述の場合、空のレコードが出力されてしまうので注意しましょう。
ここまでWeb APIに接続すらしない、カスタマイズのサンプルを見てきましたが、実はこれがREST Driver の実装の本質です。
値を変数(アイテム)に定義し、その変数と関連するカラムに値をプッシュする。このようなシンプルな仕組みでCDataのテクノロジーは成り立っています。
アイテムの使い方
それでは次にアイテムの使い方について解説していきます。
API Script を駆使する上で、アイテム、いわゆる変数はとても重要な要素です。
前述の通り、以下のように宣言を行うことで、定義することができ、
アイテムは[ItemName]で参照することが可能です。
https://cdn.cdata.com/help/DWH/jp/jdbc/pg_items.htm
例えば、以下のようなRSDファイルを実行すると、
このような結果を得ることができます。変数名が置き換わっていることがわかるかなと思います。
なお、以下のような記述で定義することも可能です。まとめてアイテムを構成したい場合はこちらの構文を使うと良いでしょう。
FirstName = Kazuya
LastName = Sugimoto
さらにアイテムは階層構造を持つことができます。例えば、Userというitemを定義して、そこにアトリビュートとして、FirstName、LastNameを定義するようなイメージです。
これを最終的に以下のように参照できます。
この階層構造はオペレーションやpushキーワードで必要なアトリビュートを持つアイテムを指定する場合に利用するケースが多いです。
pushは「 」のような記述で、レコードと紐づけるアイテムを指定できます。これにより、以下のように記述することもできます。
フォーマッターの使い方
API Script ではフォーマッターと呼ばれる変数を操作するための関数が提供されています。
この機能を使うことでアイテムに対して文字列操作や計算処理を行うことが可能です。
https://cdn.cdata.com/help/DWH/jp/jdbc/pg_valueitemformatters.htm
例えば英語の文字列をすべて大文字に変換する場合「toupper()」というフォーマッターが利用できます。
使い方は変数名とパイプ、フォーマッターを繋いで利用します。例えばFullNameというアイテムを変換する場合は以下のように記述します。
これらを駆使して、以下のように実装してみます。
他のプログラミング言語でもよく出てくるSubStringなどもあり、以下のように記述できます。()の中でそれぞれ引数を指定します。
パイプを繋いで処理することも可能です。
フォーマッターはたくさん存在するので、利用したいシチュエーションに応じて探してみると良いでしょう。
キーワードの使い方
キーワードとは一般的なプログラミング言語でも存在する条件分岐やループ、フロー制御に関する処理を記述する構文です。
https://cdn.cdata.com/help/DWH/jp/jdbc/pg_apiscriptreferencekeywordreference.htm
ちなみにアイテム(変数)の定義やpushもこのキーワードの一種となっています。
実際にいくつか使ってみましょう。
例えばif文は以下のように記述します。これはFirstNameというアイテムに格納されている値が「Kazuya」であれば、そのキーワードのスコープの中の処理を実施するというものです。
以下のように利用します。この場合、FirstNameには「Taro」が入っているため、IF文の中の処理は実行されません。
elseを行いたい場合は、elseキーワードを追加して、そのネストされたスコープの中で処理を記述します。
また、使用頻度の高いものとして、繰り返し処理にあたるenumもあります。
例えば「Hello,World,Kazuya,Sugimoto」という文字列を「,」で分解して、繰り返し処理を行うということが可能です。
繰り返し処理の結果は「_value」というアイテムの中に格納されて、enumスコープの中が複数回呼び出されます。
pushも絡めて記述すると、以下のような結果が得られます。
enumの対象はアイテムを指定することもできます。その場合[_attr]でアトリビュートの値にもアクセスできます。
その他に使うシチュエーションが多いものとしては、例外を発生させるthrowキーワードです。
API Script はIDEにあるような高度なデバッグ機能が無いため、問題の切り分けなどを行う際に利用するケースが多いです。
以下のようにエラーメッセージを出力することができます。
オペレーションの使い方
さて、ここまでAPI Script としての基本的な記述、構文について解説してきましたが、やはりREST Driver の本質は外部のWeb API と接続し、SQLを通じて結果を得ることにあると思います。
ここでは実際にWeb APIへのリクエストを担う機能であるオペレーションの呼び出し部分を解説していきます。
https://cdn.cdata.com/help/DWH/jp/jdbc/pg_op-jsonproviderget.htm
オペレーションはキーワードのようにいくつか機能がありますが、必ず覚えなければいけないオペレーションは「jsonproviderGet」「xmlproviderGet」「csvproviderGet」の3種類です。
さらに、この3種類は基本的な考え方は共通で、Web APIのレスポンスの結果がJSONなのか、XMLなのか、CSVなのかで使い分けるのみです。そのため、今回の記事では「jsonproviderGet」のオペレーションのみに内容を絞って解説します。
連携対象のAPIとして、パブリックなWeb APIとして公開されているスターウォーズAPIを利用してみました。
https://swapi.dev/
以下のようなデータがPostman を通じて取得できます。
GET https://swapi.dev/api/people
CData REST Driver を用いてドライバーをカスタマイズする際には「どういった要素をテーブル形式で取得できるようにするのか?」という考え方が重要です。
今回はこのresultsに含まれるデータをテーブルとして出力することを目指します。
オペレーションのパラメータ
オペレーションを使うにあたってまず把握しなければいけない要素はパラメータです。
「jsonproviderGet」では、最低限「URI」と「JSONPath」の2種類のパラメータを指定して利用する必要があります。
「URI」は文字通り、「どのURIリソースからJSONのデータを取得するか?」を指定します。
「JSONPath」は「取得した結果のJSONデータをどこの配列やオブジェクトを起点としてデータを参照するか?」という指定です。
JSONを抜粋してみると、以下のようにresultsの配列の中にスターウォーズの登場人物が一覧で取得できるようになっています。
今回はこの「results」要素を中心として、テーブル構成を行うため
{
"count": 82,
"next": " https://swapi.dev/api/people/?page=2",
"previous": null,
"results": [
{
"name": "Luke Skywalker",
"height": "172",
"mass": "77",
"hair_color": "blond",
"skin_color": "fair",
"eye_color": "blue",
"birth_year": "19BBY",
"gender": "male",
"homeworld": " https://swapi.dev/api/planets/1/",
"films": [
" https://swapi.dev/api/films/1/",
" https://swapi.dev/api/films/2/",
" https://swapi.dev/api/films/3/",
" https://swapi.dev/api/films/6/"
],
"species": [],
"vehicles": [
" https://swapi.dev/api/vehicles/14/",
" https://swapi.dev/api/vehicles/30/"
],
"starships": [
" https://swapi.dev/api/starships/12/",
" https://swapi.dev/api/starships/22/"
],
"created": "2014-12-09T13:50:51.644000Z",
"edited": "2014-12-20T21:17:56.891000Z",
"url": " https://swapi.dev/api/people/1/"
}
URIとJSONPathのパラメータを以下のように指定します。
このアイテムの指定の後に、以下のように「api:call」キーワードを利用して、jsonproviderGetのオペレーションを呼び出します。
このパラメータの指定は以下のように明示的に指定することも可能です。特に指定がなければ、全体要素として指定されたアイテムを識別してリクエストが行われます。 いくつかオペレーションを呼び出すシチュエーションの場合は、明示的にアイテムを指定するほうが、見通しが良くなるでしょう。
このURIとJSONPathは必須パラメータとして紹介しましたが、オプションパラメータがたくさん存在します。
https://cdn.cdata.com/help/DWH/jp/jdbc/pg_op-jsonproviderget.htm
カラムマッピングについて
取得した結果は「api:call」キーワードの中でJSONPathで指定された配列の回数分、繰り返しアイテムとして定義されます。 ただし、このアイテムにアクセスするには、予めそのJSONオブジェクトのパス、「XPath」を指定する必要があります。
パスの指定方法は大きく分けて3種類あります。
1つ目はパラメータとして指定するパターン。「ElementMapPath」と「ElementMapName」という2つのパラメータを番号で一対にして、アクセスしたいJSONのオブジェクトパスおよび結果のアイテムの名称を指定します。 なお、この指定を行う場合、結果を取得するにはカラム名とアイテム名を別途紐づけてあげる必要があります。
2つ目はカラムのアトリビュート名を利用するパターン。この場合自動的に「name」という要素が識別されて、「name」アイテムとして格納されます。
3つ目はカラムアトリビュートのプロパティとしてXPathを指定するパターン。以下のように「other:XPath」というプロパティをカラムに指定することで取得できるようになります。 この場合、アイテムの名前はカラムの名前になるため「Hello」アイテムでアクセスする形になります。
言葉だけでは少しわかりにくいので、例を示します。
今回は新しく「People.rsd」というテーブルを作成し、Helloというカラムを定義しました。
ここに先程のパラメータの指定を含めて、callの中の定義されているアイテムを確認してみます。
例えば以下のようにcallの中で「name」アイテムをエラーとして発行すると
このようにresultsの中の「name」がアイテムとして定義されていることがわかります。
このcallの中で定義されているアイテムは「out」という指定で特定のアイテムにマッピングできます。こちらのほうが明示的でわかりやすいかもしれません。
このようにJSONPathで指定された配列要素の回数分、jsonproviderGetの中の要素が繰り返し実行されます。ここがjsonproviderGetを利用する上でかなり重要なポイントなのでしっかり理解しておくと良いです。
この仕組と最初に解説した「push」の仕組みを組み合わせることで、レコードとして取得することが可能になります。
以下のようにHelloカラムに対して、nameの結果を出力するには以下のように書くことができます。
これは仕組みを理解してもらうためにちょっと回りくどいAPI Scriptの書き方をしましたが、実際の実装としてはもっとシンプルにできます。
先程お話した通り、レスポンスのアイテムを指定するXPathはカラム名やカラムプロパティの「other:XPath」でも指定できるとお話しました。
そもため、「ElementMapPath」「ElementMapName」を省略して、以下のように「other:XPath」だけで記述しても構いませんし
(※結果は同じ)
XPathとカラム名が一致するのであれば、以下のように指定しても構いません。ただし、カラム名とアイテム名のマッピングは大文字小文字を区別するので、XPathで明示的に指定したほうが無難なケースが多いです。
このようにjsonproviderGetは取得した結果をアイテムとして格納し、カラム名と関連付けることで、レコードとしての結果を得ることができます。
この仕組でCData REST Driver はWeb APIとの連携を実現しています。
ここまでが大まかなCData REST Driver の基本解説でした。
とてもシンプルに思われるかもしれませんが、この仕組を応用して、ほとんどのREST API対する実装をカバーできます。
接続文字列のパラメータを取得する
CData JDBC Driver では以下のように接続文字列という形式で認証情報やアクセス先のサーバーのアドレスなどを指定することができます。
この指定もAPI Script の中で取得することができます。
この接続文字列の情報は「_connection.プロパティ名」でアクセスできます。
例えば、上記のLocationのプロパティを取得した場合は[_connection.Location]となります。
なお、独自の接続文字列のプロパティを作成して、指定することも可能です。
この場合、接続文字列上はバリデーションで弾かれないようにするため、Otherの項目として指定する必要があります。
jdbc:rest:Location=C:\CData_REST\HandsOn;Other="OriginalProperty=HelloWorld!";
認証アプローチ
Web API と連携するにあたって、認証アプローチの対応は欠かせない要素ですね。
CData REST Driver は標準機能としても様々な認証方法をサポートしていますし、API Script の中でそれらをカスタマイズすることも可能です。
例えばオーソドックスなBasic認証の対応をしてみましょう。「 https://postman-echo.com/basic-auth」に対して専用のID・PWを設けてリクエストを行うと、認証結果がレスポンスとして返ってきます。
GET /basic-auth HTTP/1.1 Host: postman-echo.com Accept: application/json; charset=utf-8 Authorization: Basic cG9zdG1hbjpwYXNzd29yZA==
CData REST Driver では汎用的な認証に関しては接続文字列の指定でそのままアクセスが可能となっています。
サポートしている認証方法については以下のページを参照してみてください。
https://cdn.cdata.com/help/DWH/jp/jdbc/RSBREST_p_AuthScheme.htm
以下のような接続プロパティを追加して
jdbc:rest:Location=C:\CData_REST\HandsOn;AuthScheme=Basic;User=postman;Password=password;
「 https://postman-echo.com/basic-auth」にアクセスする「BasicAuth」というRSDファイルを作成しておきます。
これでSELECTを発行すると、Basic認証を行って、接続が完了できました。
なお、接続プロパティを使わずにAPI Script で指定することも可能です。この場合、オペレーションのjsonproviderGetのパラメータを参考に実装します。
https://cdn.cdata.com/help/DWH/jp/jdbc/pg_op-jsonproviderget.htm#authentication
これで接続文字列からUserやPasswordを外しても、同じにように結果が取得できます。
このように接続文字列で指定した認証アプローチは暗黙的にjsonproviderGetのオペレーションに指定されるので注意しましょう。
なお、ケースによってはカスタムヘッダーを指定したアプローチもあると思います。
GET /headers HTTP/1.1 Host: postman-echo.com apikey: HelloWorld
この場合は「Header:Name#」と「Header:Value#」というパラメータを用いて、リクエストすることが可能です。
なお、OAuth での処理については、考慮するべき事項がとても多くなってしまうので、今回の記事では詳細は割愛します。
Azure ADなどベーシックなOAuth の実装であれば、Basic認証と同様に接続文字列の設定だけで接続することが可能です。
https://cdn.cdata.com/help/DWH/jp/jdbc/RSBREST_c_OAuth.htm
以下のBlog記事で詳しい設定例が掲載されているので、参考にしてみると良いかと思います。
https://www.cdata.com/jp/blog/2018-12-26-104252
フィルター条件の指定
API Sciptの実装を進めていくと、自ずと必要となってくる要素がフィルター条件の指定かなと思います。
まず、フィルター条件の考え方について確認しておきましょう。フィルター条件は2種類の考え方があります。
それはサーバーサイドフィルターとクライアントサイドフィルターです。
例を見てみましょう。先の章でとりあげたスターウォーズAPIの例をサンプルにしています。
例えば、以下のようなAPI Script を実装します。
このテーブルに対して「SELECT * FROM People」を実行すると下記のような結果が取得できます。
続いてAPI Scriptはそのままで「SELECT * FROM People WHERE name = 'Luke Skywalker';」というクエリを実行してみます。
すると以下のような結果になります。スターウォーズAPIへのリクエスト方法は変わっていないのですが、Where 句で指定された条件を元に、CData REST Driver が暗黙的に結果をフィルターし、レコードを出力しました。
これがクライアントサイドフィルターの実装です。
一見フィルター処理はされているように見えるものの、裏側のAPIリクエストのデータ取得件数は変わっていないため、パフォーマンス的には変化がありません。
そこでWhere句で指定された条件を元にAPI側のフィルター条件への橋渡しの実装を行う必要が出てくるわけですが、それをサーバーサイドフィルターと呼んでいます。
それでは実際にやってみましょう。
スターウォーズAPIでは以下のようにsearchのURLクエリパラメータを用いて、レスポンス結果をフィルターすることが可能です。
「GET https://swapi.dev/api/people?search=Luke Skywalker」
API Script、jsonoperatorGetでのURLクエリパラメータの指定は簡単な方法だと以下のようにURIに直接指定する方法
もしくは「ParamName」「ParamValue」という指定をする方法の2種類があります。(こちらの指定の場合URLエンコードは暗黙的に実施されます。)
ただ、これだけではWhere句の条件を動的に指定できないため、Where句で指定された値を参照する実装を行います。 その方法が「_input」というアイテムの指定です。
CData REST Driver では先に述べた接続文字列を取得するアイテム「_connection」などをデフォルトアイテムと呼んでおり、「_input」もその1種類です。
これはWhere 句やINSERTなどで指定されたSQLのパラメータを参照することができる専用のアイテムになっています。
https://cdn.cdata.com/help/DWH/jp/jdbc/pg_items.htm#script-inputs-input
これを用いることで、以下のように「SELECT * FROM People WHERE name = 'Luke Skywalker';」といった形式で動的にフィルター条件を指定できるようになります。
ちなみに、このパラメータ指定はカラムのプロパティ「other:filter="search:="」として実装することも可能です。
しかし、この実装にはいくつか欠点があります。
1つ目はWhere句が指定されない場合、[_input.name]というアイテムにアクセスができないというエラーが発生する問題です。
これは以下のように「exists」キーワードで予めアイテムの存在チェックを行うことで回避できます。
次は完全一致の問題です。実は先に述べたクライアントサイドフィルターとサーバーサイドフィルターは連動して機能します。
スターウォーズAPIのsearchパラメータは部分一致がデフォルトの挙動であるため、「https://swapi.dev/api/people?search=Luke」というリクエストでもデータが取得できます。
しかし、以下のようなSQL「SELECT * FROM People WHERE name = 'Luke';」を指定しても、SQL上の「=」は完全一致の条件であるため、サーバーサイドフィルターした結果をさらにクライアントサイドフィルターでフィルタリングするため、結果としては取得することができません。
では、LIKEを使えばいいじゃないかとなるかもしれないのですが、「_input」はその制約上Likeのデータは参照することができません。
(これはREST Driver すべてのクエリパラメータをサーバーサイドフィルターとしてサポートしようとすると、膨大な条件分岐処理が必要となってしまうためです。)
これはいくつかアプローチ方法があります。1つ目は疑似カラムと呼ばれる特殊なカラムを用いる方法です。
https://cdn.cdata.com/help/DWH/jp/jdbc/pg_customschemaselect.htm#%E7%96%91%E4%BC%BC%E3%82%AB%E3%83%A9%E3%83%A0%E3%81%AB%E3%82%88%E3%82%8B%E6%A4%9C%E7%B4%A2
これは以下のようにカラムの指定の中に「input」というパラメータだけを受け取るためのカラムを追加して利用します。
これで条件指定を渡すことにより、元のカラムは影響を受けずに、サーバーサイドフィルターを効かせることができるようになります。
また、日付のフィルター条件において、範囲指定を行うようなケースは専用の書き方があります。
例えばクエリパラメータで「?modifedBefore=2017-10-10&modifedSince=2017-09-01」といった形でフィルターの開始日、終了日を指定するような場合です。
https://cdn.cdata.com/help/DWH/jp/jdbc/pg_customschemaselect.htm#%E3%82%B5%E3%83%BC%E3%83%90%E3%83%BC%E4%B8%8A%E3%81%A7select-where-%E3%82%92%E5%87%A6%E7%90%86
これは以下のように「other:filter=」の指定で「WHERE modifedAt < '2017-10-10' AND modifedAt > '2017-09-01'」のような条件式をリクエストできるようになります。
ただ、API Script 内部でこのURLクエリパラメータを_input アイテムから取り出して、チューニングする、といったことはできないので注意してください。
データのPOST対応
Web APIによっては、データを検索するために専用のクエリパラメータをHTTP のBodyに指定して、POSTリクエストを送る、といったシチュエーションもあるかなと思います。
そういった場合にも「jsonoperatorGet」は利用できます。
例えばPostman Echo を利用して、以下のような「application/x-www-form-urlencoded」のリクエストを指定するとします。
POST /post HTTP/1.1 Host: postman-echo.com Content-Type: application/x-www-form-urlencoded Content-Length: 21
hello=world&hoge=fuga
jsonproviderGetではデフォルトでGETリクエストを行いますが、Methodアイテムを用いて、このメソッドを上書きすることができ、ContentTypeも同名のアイテムで指定できます。
application/x-www-form-urlencoded のパラメータは「ParamName」「ParamValue」の組み合わせで指定可能です。
https://cdn.cdata.com/help/DWH/jp/jdbc/pg_op-jsonproviderget.htm#http
もっとフレキシブルに記述したい場合はDataアイテムで指定します。 ただ、デフォルトでエンコードされてしまうため、今回のPostman Echoの場合は「EncodePostData」というアイテムをfalseに指定してリクエストします。
JSONフォーマットでのリクエストなどもこのように記述できます。JSONのようなフォーマットの場合、データはvalueとして記述するのではなく、以下のようにXML上に記述できるようにすると良いでしょう。
{
"hello":"world",
"hoge":"fuga"
}
ページネーションの実装
CData REST Driver を使ってWeb APIと連携する場合、ページネーションの実装は必須といっても過言ではありません。
他のCData Driver の実装でも同様ですが、特にフィルター条件が無くSELECTが実施された場合、CData Driver はWeb APIが提供しているデータをできるだけすべて取得してから結果を返すような挙動が標準となっています。
CData REST Driver でもこのようなページネーションの仕組みをAPI Script の記述によってサポートできます。
https://cdn.cdata.com/help/DWH/jp/jdbc/pg_customschemaselect.htm#%E3%83%9A%E3%83%BC%E3%82%B8%E3%83%B3%E3%82%B0%E3%81%AE%E5%AE%9F%E8%A3%85
さて、まず先に上げたスターウォーズAPIの実装を見てみましょう。
このAPIでは「https://swapi.dev/api/people/?page=2」といった形でpageクエリパラメータを指定することで、取得したいデータのページを指定することができ、この数値を加算していくことで全データを探索できます。
またこのページネーションの有無はレスポンスのnextプロパティからも識別することができます。
これを以下のテーブルに実装してみましょう。
まずページネーションをCData REST Driver で有効化するために「EnablePaging」というアイテムにtureを設定します。
ちなみにこのアイテムはjsonproviderGetのパラメータの一つです。
https://cdn.cdata.com/help/DWH/jp/jdbc/pg_op-jsonproviderget.htm#advanced-processing
このパラメータを有効化することにより、jsonproviderGetは取得した結果の配列要素の処理が終わった後、もう一度処理を呼び出すようになります。
次に必要となるのがページネーションの定義です。Web APIは前述のpage指定やlimit、offset指定、nextTokenの指定など、ページネーションの実装がWeb API毎で多様に存在します。
CData REST Driver では代表的なページネーションスキームの設定をいくつかサポートしているので、これらを用いて設定することができます。
例えば今回のようなページ番号をインクリメントしていく実装の場合、「PageNumberParam」「PageSize」というアイテムを指定することで実装できます。
「PageNumberParam」にクエリパラメータの名前、「PageSize」で一回あたりに取得できる件数を指定します。
もしクエリパラメータで一回あたりのデータ取得件数の上限としてPageSizeが指定できる場合は「PageSizeParam」という指定を加えることで、PageSizeをクエリパラメータとして渡して指定することも可能です。
ちなみに、スターウォーズAPIではレスポンス結果に次のページのURLが入ってくる仕様になっています。その場合は、「PageUrlPath」というパラメータを指定することで、ページネーションを実装させることも可能です。
ちなみにこちらの実装のほうが、次のページが存在しないことをレスポンスボディから識別することができるため、不要なAPI リクエストが削減される可能性があります。
他にもいくつかページネーションの実装パターン、カスタム実装パターンなどあるので、REST Driver のドキュメントを参照してみてください。
https://cdn.cdata.com/help/DWH/jp/jdbc/pg_customschemaselect.htm#%E3%83%9A%E3%83%BC%E3%82%B8%E3%83%B3%E3%82%B0%E3%81%AE%E5%AE%9F%E8%A3%85
Data Modelの指定
ここまで明確に触れてこなかったのですが、CData REST Driver ではJSONやXMLのデータをどのように処理するのか? というアプローチについて、3種類のパターンをもちいて実装されています。
それが「Document」「FlattenedDocuments」「Relational」の3種類です。
この詳しい内容は以下の記事を参照ください。ここでは要点と知っておいたほうが良い要素に絞って解説します。
https://qiita.com/sugimomoto/items/5ea6746389f1cec0c54c
まず3種類のData Model の概要です。
Document
まず、Documentモデルです。Documentモデルは対象の配列オブジェクト(JSONPathで指定された要素)を読み取り、そこを行分割の軸としてテーブル化して提供します。
上記のJSONは「Documents」という配列構造があり、その配列のオブジェクトの中に各種プロパティと共に、子の配列構造「Files」を保持しています。
Documentモデルは、この最初のDocuments配列をベースにモデル化とレコード化を行い、ユーザーへデータを提供します。
なお、ネストされた配列構造の JSON/XML は、JSON/XML のまま値として提供します。
Relatioinal
Relationalモデルは、JSON/XML ファイルから、複数の配列構造を読み取り、RDBモデルのように、それぞれ配列をテーブル化するというアプローチです。
上記のように、Topレベルの配列オブジェクトが「Documents」で、配下に「Files」という配列オブジェクトがあれば
「Documents 1 : N Files」という2つのテーブルに分割し提供されます。
また、各テーブルに分割するにあたって、それぞれidの自動振り分けも行いますので、分割されたテーブルをJOINする場合は、このidを利用します。
FlattenedDocuments
最後に、ちょっとわかりづらい FlattenedDocuments。FlattenedDocuments はネストされた配列構造をすべてフラット化し、1テーブルとして扱います。
Relational で構成されたテーブルをJOIN・CROSS JOINしたものであると例えればわかりやすいかもしれません。
最もネストが深いテーブルを軸として、親テーブルをそれぞれ JOIN していき、ユーザーへレコードを提供します。
なお、以下のように、同階層に存在している配列オブジェクトが存在する場合は CROSS JOIN します。
どのData Model を使うべきか?
ここは個人的な見解も含みますが、まず基本的に「Document」か「FlattenedDocuments」しか利用しません。そしてほとんどのAPIは「Document」でカバーできます。
これは「Relational」は「Document」で実装されたテーブルの応用モデルでしかなく、「Document」モデルを利用することで同様の実装ができるためです。API Scriptを使いたくない、もしくはJSONのデータモデルを確認したい、というシチュエーションでのみ利用するパターンかなと思います。
次に「FlattenedDocuments」の使い道ですが、これは対象のJSONパスの配列要素の親要素または子要素をカラムとして表現したい場合に利用します。
例えば前述で上げた以下のようなJSONのモデリングを行う場合に、子要素である「files」をレコードの軸としながら、親要素のtitleなどを1テーブルとして表現したい場合があると思います。
「Document」はJSONPathを必ず1要素しか指定できないのですが、「FlattenDocuments」は複数要素を指定して、JSONを分解することができます。
このため、複数階層からデータにアクセスし、レコードとして表現することができるようになっています。
このような観点で使い分けると良いでしょう。
※なお、細かいことを言えば、FlattendDocumentsだけでDocumentを含めたほとんどのケースをカバーすることができるのですが、デフォルトのDataModelパラメータがDocumentであり、分解方法がシンプルであるため、まずはDocumentで実装を試してみることを個人的におすすめしています。
一例をOData APIのサンプルを使って示したいと思います。
特定のデータを含むリクエストを送りたいため、ハードコードで「https://services.odata.org/V4/(S(icld2w0pn20zue0pjqeqpfya))/TripPinServiceRW/People?%24skiptoken=8」として指定しています。
以下のようなFirstName、LastNameというプロパティが最上位の配列要素に存在し、子要素としてAddressInfo配列に「Address」というプロパティがあります。
これを「FlattenedDocuments」を用いて、RSDファイルとしておこすと、以下のような形になります。「JSONPath」が複数指定されているのがわかりますね。
これで結果として以下のような子要素を軸としたテーブルが作られます。親要素が子要素分プッシュされているのがわかりますね。
ちなみに無理やりDataModelで「Document」を指定すると、以下のようなエラーが発生します。
[Code: 0, SQL State: HY000] Nested JSONPaths cannot be specified when using the Document Data Model.
RSDファイルの生成
さて、今回の記事ではAPI Script の習熟のため、手動でカスタマイズする方法を中心として解説してきましたが、CData REST Driver にはRSDファイルの雛形を作成する機能がデフォルトで含まれています。
これは「GenerateSchemaFiles」というプロパティを「OnStart」に指定することで、コネクション時に生成されます。
https://cdn.cdata.com/help/DWH/jp/jdbc/pg_GeneratingSchemas.htm
合わせてリクエスト先のURLおよびJSONPath(XPath)、RSDファイルの生成先となるフォルダパスを指定します。
jdbc:rest:URI=https://swapi.dev/api/people/;XPath=/results;Location=C:\CData_REST\RSD;GenerateSchemaFiles=OnStart
すると以下のようにLocationに指定したフォルダにRSDファイルが生成されます。全カラムがJSONPathやDataModelを元に生成されるので、便利だと思います。
INSERT・UPDATE・DELETEの対応
ここまでSELECTを中心として、REST Driver のカスタマイズ方法を解説してきましたが、同じような形でINSERT・UPDATE・DELETEにも対応したカスタマイズが可能です。
先に生成したRSDファイルにも書かれているように「」「」「 」の部分がそれぞれ対応しているため、この記述の中でAPI リクエスト処理を実装すことで、Web APIに向けたデータの作成や削除を実現することができます。
対象のAPIは先に検証したpostman echoのPOSTリクエストです。
POST /post HTTP/1.1 Host: postman-echo.com Content-Type: application/x-www-form-urlencoded Content-Length: 21
hello=world&hoge=fuga
例えば以下のようなCRUDSampleというテーブルを定義して、「INSERT INTO CRUDSample(hello,hoge)VALUES('world','fuga')」というリクエストができるようにしてみましょう。
INSERT分のパラメータもWhere句と同じように「_input.パラメータ名」で取得することができます。
これで前述のようなINSERT文を発行すると
以下のようなAPI リクエストが行われます。
UPDATE文やDELETE文でもお作法は同様ですが、keyのカラムが無いとバリデーション段階でエラーが発生するので「 」というようにカラムにkeyのプロパティを追加して、実装するようにしましょう。
これで「UPDATE CRUDSample SET hello = 'world', hoge = 'fuga' WHERE id = 'keyvalue';」といったSQLが記述できます。
Keyの値も同様に「_input.KeyColumnName」で取得できます。
ちなみにストアドプロシージャも同じような形で作成できます。
https://cdn.cdata.com/help/DWH/jp/jdbc/pg_customschemastoredproc.htm
Tips1:一回のSELECT文で複数種類のAPIリクエストを発行したい
ここからはTipsです。よくある質問として、「一回のSELECT文で複数種類のAPIリクエストを発行したい」というものがあります。
これは、一度認証用のAPIでTokenを発行して、そのTokenを使って実際のリソースへのアクセスを行いたい、といったものです。
これはページネーションの仕組みを応用することで対応することができます。
通常「jsonproviderGet」は一回の処理の中で一回しか呼ばれませんが、「EnablePaging」を指定しておくことで、次に定義されている「jsonproviderGet」もコールすることができるようになります。
例をお見せしましょう。最初に「https://postman-echo.com/headers」に対してTokenを取得するイメージのAPIリクエストを行います。
jsonproviderGetの中でそのレスポンスを一度デフォルトアイテムの中に格納して、次のjsonproviderGetで利用しています。
これで以下のように結果が取得できます。
ちなみにこの実装方法は応用範囲がとても広く、例えばあるエンドポイントでIDの一覧を取得して、別なエンドポイントでそのIDを使ってリクエストを繰り返す、みたいなこともできます。
Tips2:テーブルとビューの定義の違い
RSDファイルをカスタマイズしていくと、以下のようにテーブル・ビューで出し分けがされることに気づくかもしれません。
この仕組みはとても単純で、以下のようにINSERT/UPDATE/DELETEのいずれかが存在するとテーブルの扱いになります。
Tips3:スキーマのリフレッシュ
RSDファイルをカスタマイズしているときに、カラムの情報などをリフレッシュして、識別しなおすための「reset schema CACHE;」はとても大事なコマンドです。
ちなみにこのスキーマのリフレッシュが必要なタイミングはテーブル名やカラムのプロパティを変更した場合のみです。
「jsonproviderGet」などの処理を記述する部分はスキーマをリフレッシュしなくてもRSDファイルを保存するだけで最新の挙動が実行されます。
Tips4:API Profileの中身を覗く
CDataではREST Driver の類似製品としてAPI Driver という製品を提供しており、そこではProfileという形式で予め定義された設定ファイルを使って、各種Web APIに接続できるようにしています。
実はこのAPI Profileは中身を見ると、REST Driver と同じRSDファイルが利用されています。
https://www.cdata.com/jp/apidriver/download/#profiles
確認方法は単純で、ダウンロードした「.apip」のファイルの拡張子を「.zip」に変更し、解凍するだけです。
すると以下のように中のRSDファイルが確認でき、REST Driver と同じように作られているのがわかるかなと思います。
オペレーションは「apisadoExecuteJSONGet」という名前に変わっていますが、できることは「jsonproviderGet」同じです。
ぜひ気になるAPIがあれば、参考にしてみてください。
Tips5:アイテムを一括でエラーメッセージに出力する
API Scriptは結構デバッグ作業が大変なプログラミング言語です。
そこで私がよく利用しているDebug Tipsを共有します。
現在の箇所でアイテムがどのように定義されているのか?を確認したいシチュエーションは多いと思います。
そんなときは該当箇所で以下のようなScriptを実行すると、そこで定義されているアイテムがすべて出力されます。
[_attr] = [_value]
例えば、以下のように利用します。
[_attr] = [_value]
おわりに
とても長くなりましたが、これでこの記事は終了にしたいと思います。
色々と書いてきましたが、REST Driver を理解するポイントは「pushの扱い」と「jsonproviderGetの仕組み」の2つだと思っています。
この2つの機能がしっかりと理解できるだけで、できることがとても大きく広がります。
ぜひ皆さんのお好きなWeb APIに接続するための一助になればと思います。
もしカスタマイズしていてわからない部分があれば、お気軽にテクニカルサポートまでお問い合わせください。
https://www.cdata.com/jp/support/submit.aspx
関連コンテンツ