ドライバーアーキテクチャに関するブログ記事シリーズのパート 4 です。これまでの記事では、さまざまなドライバーおよびアダプター技術のサポート方法と、その他のコアドライバーサービスについて説明しました。
これまでのシリーズはこちらからお読みください:
今回は、データアクセススタック(中間レイヤーの右側)とプロバイダー実装(下部レイヤー)に注目していきます。

データアクセスチェーン
プロバイダーでのクエリ実行は、常にデータアクセスチェーン(DAC)を通じて処理されます。DAC は、IDataAccess と呼ばれるインターフェースを通じてコアドライバー機能を実装するオブジェクトの集まりです。以下のサービスを提供します:
トランスポート接続のオープンとクローズ
メタデータクエリ
クエリとコマンドの実行
データアクセスオブジェクトはチェーン状にスタックされており、上位レイヤーが任意の操作を完全にインターセプトしたり、チェーン内の次のIDataAccess オブジェクトの前後で前処理・後処理を行ったりできるようになっています。
フルデータベース向けのドライバーであれば、ドライバー実装そのものだけで構成されるシンプルなデータアクセスチェーンで済みます。一方、他のドライバーは異なるレイヤーを活用して、必要に応じてデータソースの機能を拡張しています。
いくつかの実装がありますが、中でも多くのドライバーで使用される、特に重要なものをご紹介します。
OAuth サポート
認証に OAuth プロトコルを使用するプロバイダーでは、プロトコル自体の実装や OAuth トークンの取得・リフレッシュに複雑なロジックが必要です。これを簡素化するため、コマンドがドライバーに到達する前に、接続に有効な OAuth トークンがセットされていることを保証する IDataAccess 実装を用意しています。
このレイヤーでは、必要に応じてトークンの永続化やリフレッシュも処理できます。
関数とエイリアスの解決
クエリ機能が制限されているデータソース向けに、SQL クエリ AST の変換を処理してドライバーが解釈しやすくする IDataAccess 実装があります。変換の例としては、次のようなものがあります:
クライアントサイドクエリエンジン
このコンポーネントは、クエリ機能が制限されているプロバイダーでも複雑なクエリを実行できるように構築されたものです。プロバイダー実装と連携してクエリを評価し、データソースが自力で実行できる部分と、クライアントサイドで補完が必要な部分を判断します。
この分析の目標は、データソースのクエリ機能に基づいてスマートに判断し、できるだけ多くの処理をデータソース側にプッシュダウンすることです。では、プロバイダーとクライアントサイドクエリエンジンはどのように連携しているのでしょうか? ここでは 2 つの抽象化が使われています。
クエリ評価コンポーネントは、SQL クエリを検査し、ドライバーがネイティブに実行できない部分を示す情報を返します。たとえば、あるプロバイダーが特定のセレクターに対してのみ操作を実行できるといった情報を、クライアントサイドクエリエンジンに伝えられます。
もうひとつの例として、結果のフィルタリングをネイティブにサポートするものの制限があるデータソースが挙げられます。特定のフィールドでのみフィルタリングが可能なデータソースもあれば、等値比較だけは可能でも他の比較演算子は使えないデータソースもあります。
クエリスライサーコンポーネントは、より特定のケースにおいて、単一のクエリを複数の独立したクエリに分割するために使用します。たとえば、次のようなクエリを考えてみましょう:
SELECT * FROM Documents WHERE DocId IN (123, 342, 874)
データソースが ID 指定で 1 件ずつしかドキュメントを取得できない場合、このクエリを実行する方法のひとつとして、3 つの個別クエリに分割する方法があります:
SELECT * FROM Documents WHERE DocId = 123
SELECT * FROM Documents WHERE DocId = 342
SELECT * FROM Documents WHERE DocId = 874
これらの評価結果に基づいて、クライアントサイドクエリエンジンは次のような判断を行います:
プロバイダーが実行できる形にクエリを簡素化する。たとえば、WHERE 句の条件を緩めて結果のスーパーセットを取得し、クライアントサイドでさらにフィルタリングするといった方法です。
必要に応じて複雑なクエリを複数のクエリに分解する。JOIN やサブクエリ、ネストされたクエリを含むケースのほか、あるクエリの結果をもとにデータソースへの追加クエリを構築するケースにも対応します。
アグリゲーションをデータソース側にプッシュダウンするか、データソースが返す結果セットのサイズを最小化しつつクライアントサイドで計算する。
もちろん、クエリをクライアントサイドで(部分的にでも)評価することには大きなトレードオフがあります。このモデルでは効率的に実行できないクエリは常に存在しますし、特にコストのかかるケースもあります。CData 製品の指針は、データソースに対してできる限り多くの処理をプッシュダウンして最も効率的なクエリを生成すること、そして同時にお客様のニーズに応える柔軟なクエリ機能を提供することです。
この効率性は、ビジネスインテリジェンスやデータ分析・可視化ツールで CData 製品のドライバーをご利用いただいているお客様にとって特に重要です。こうしたツールは、基盤となるデータソースではネイティブに処理できないことが多い、高度に複雑なクエリを生成します。
結果セットチェーン
プロバイダー実装のコンポーネントを見ていく前に、ドライバーモデルにおける結果セットの表現方法を確認しておきましょう。結果セットオブジェクトは、クエリ実行の結果として IDataAccess 実装から生成されます。
結果セットはIResultSet インターフェースを実装するオブジェクトとして表現され、以下の機能を提供します:
結果セットカラムに関するメタデータの取得
結果の反復処理(イテレーション)
現在の行のカラムからの値の取得
IDataAccess と同様に、結果セットもレイヤー化できます。つまり、ある IDataAccess 実装がチェーン内の次の要素を呼び出してクエリを実行し、返された IResultSet オブジェクトをラップして機能を追加したり、形状を変更したりできるということです。関数とエイリアスの解決コンポーネントは、まさにこの仕組みで動作しています。
プロバイダー実装
新しいプロバイダーを実装する際には、2 つの方法があります:
この選択は基本的に、プロトコル系かそうでないかで決まります。カスタムトランスポートプロトコルが必要なプロバイダーやフルデータベースシステムの場合は、通常、新しいIDataAccessレイヤーを実装します。REST や OData ベースのドライバーの場合は、2 番目の方法を採用することが多いです。後者で使用するフレームワークには、ドライバー開発を簡素化するための豊富な機能が含まれており、ページ単位での結果セット取得(並列でのページフェッチを含む)など、多くの一般的なシナリオに対応しています。
データアクセスチェーン以外にも、すべてのプロバイダーに共通する重要なコンポーネントがいくつかあります。プロバイダー固有の実装として作成する場合もあれば、フレームワーク内のベース実装を再利用する場合もあります。このカテゴリには、クエリ評価コンポーネントとクエリスライサーコンポーネントが含まれます。
すべてのプロバイダーで必須となるのがプロバイダーオプションです。上位レイヤーやリクエスト処理の多くの動作を設定する役割を担っています。
設定内容には以下が含まれます:
プロバイダーのIDataAccess チェーンの構成
ドライバーのクエリ評価・クエリスライサーコンポーネントの構成
ドライバーのグローバルオプションとフラグの設定。たとえば:
バルク操作のサポート有無
デフォルトで適用する SQL 正規化ルール
データソースがネイティブにサーバーサイドで評価できる関数の登録
複数スキーマのサポート有無
静的スキーマか動的スキーマか
まとめ
シリーズの最後に、重要なポイントをまとめます。
フロントエンドでのコード生成を軸としたフレームワークにより、さまざまなドライバー / アダプター技術に対応するネイティブドライバーを構築できます。柔軟で拡張性の高いモデルのおかげで、新しい技術への対応も容易です。
すべてのドライバーがテクノロジー間で一貫した動作を提供し、アップデートや改善も全体にわたって適用されます。
ドライバーの機能は常に進化しています。柔軟なフレームワークを構築しているため、改善や新機能の追加を一度行えば、多くの(すべてではないにしても)ドライバーがその恩恵を受けられます。
パフォーマンスは開発プロセスにおける最重要事項のひとつです。ドライバーのパフォーマンス向上を常に研究し、そこで得た知見をドライバーポートフォリオ全体に反映しています。
このドライバーアーキテクチャの概要が、250 以上のデータソースをあらゆる主要データアクセス技術スタックで持続的に開発・保守・サポートしている仕組みを知る手がかりとなれば幸いです。
※本記事はCData US ブログ CData Architecture: Query Execution の翻訳です。