著者: vivo インターネット検索チーム - 鄧傑
1. 背景
テクノロジーの継続的な発展に伴い、ビッグデータの分野でますます多くの技術的フレームワークが出現しています。学習コストとビッグ データの難しさを軽減するために、ますます多くのビッグ データ テクノロジとアプリケーションがデータ クエリ用の SQL をサポートし始めています。学習コストが低い言語として、データクエリの SQL サポートにより、ユーザーがビッグデータを使用するためのしきい値を下げることができ、より多くのユーザーがビッグデータを使用できるようになります。
この記事では、主にビジネスに適用する SQL パーサーの実装方法を紹介すると同時に、SQL パーサーの実際のプロセスを具体的なケースとともに紹介します。
2. なぜ SQL パーサーが必要なのですか?
プロジェクト システム アーキテクチャを設計するときは、通常、技術的な調査を行います。なぜ SQL パーサーが必要なのかを考えてみましょう。選択した SQL パーサーが現在の技術要件を満たすことができると判断するにはどうすればよいですか?
2.1 従来の SQL クエリ
従来の SQL クエリは、完全なデータベース プロトコルに依存しています。たとえば、データは MySQL や Oracle などのリレーショナル データベースに格納され、標準の SQL 構文があります。次の図に示すように、さまざまな SQL ステートメントを使用してビジネス要件を達成できます。
しかし、大量のデータを扱う場合、リレーショナル データベースだけでは実際のビジネス ニーズを満たすことは難しく、ビッグ データ エコシステムの技術コンポーネントを使用して実際のビジネス ニーズを解決する必要があります。
2.2 実際の適用シナリオ
ビッグ データ エコシステムの技術コンポーネントを使用する場合、Hive、Spark、Flink などの一部の技術コンポーネントには独自の SQL がありますが、Kafka、HBase などの一部の技術コンポーネント自体には SQL がありません。以下では、次の図に示すように、SQL を使用しないシナリオと SQL パーサーを使用するシナリオを比較できます。
上の図から、図の左側にある SQL を使用せずに技術コンポーネントを使用する場合、クエリを実装するときに、Kafka や HBase などの技術コンポーネントとデータをやり取りするために、さまざまなビジネス ロジック インターフェイスを記述する必要があることがわかります。このようなコンポーネントの増加に伴ってクエリ機能の複雑さが増すと、インターフェースの各セットの複雑さも増し、これもその後の拡張と保守に非常に不便です。図の右側では、SQL パーサーを導入した後、ビジネス ロジックを完成させ、さまざまな技術コンポーネントに適応するために必要なインターフェイスのセットのみが必要になります。
3. SQL パーサーとは何ですか?
実際のビジネス シナリオに適用する SQL パーサーを選択する前に、まず SQL パーサーの主要な知識ポイントを理解しましょう。
3.1 SQL パーサーには何が含まれていますか?
SQL パーサーを使用する場合、SQL を解析する手順は、次のような Java/Python プログラムを解析する手順と非常によく似ています。
- C/C++ では、字句解析と構文解析に LEX と YACC を使用できます。
- Java では、JavaCC または ANTLR を使用できます。
パーサーを使用する過程で、パーサーは通常、字句解析、構文解析、意味解析の 3 つの部分で構成されます。
3.1.1 字句解析とは?
字句解析を理解するには?字句解析タスクを開始すると、字句解析タスクを開始すると、文字を 1 つずつ読み取って解析プログラムにロードし、バイト ストリームをスキャンして、単語形成規則に従って識別します。 . 文字は 1 つずつエントリに分割されます. 単語の分割のルールは、スペースに遭遇したときに分割し、セミコロンに遭遇したときに字句解析を終了することです. たとえば、単純な SQL は次のようになります。
SQL の例
SELECT name FROM tab;
字句解析の後、結果は次のようになります。
3.1.2 解析とは?
文法解析を理解するには?文法分析はこのように理解できます. 文法分析タスクを開始すると, 文法分析タスクは語彙分析の結果に基づいてエントリシーケンスを異なる文法句に結合し, 構成された文法文は対応する文法規則と比較されます.適応が成功した場合、対応する抽象構文ツリーが生成されます。そうでない場合、構文エラー例外がスローされます。たとえば、次の SQL ステートメントは次のとおりです。
SQL の例
SELECT name FROM tab WHERE id=1001;
合意されたルールは次のとおりです。
上の表で、赤いコンテンツは通常、大文字のキーワードや記号などである終端記号を表し、小文字のコンテンツは非終端記号であり、フィールドやテーブル名などの命名規則に一般的に使用されます。特定の AST データ構造を次の図に示します。
3.1.3 セマンティック解析とは?
セマンティック解析を理解するには? セマンティック パーシングはこのように理解できますが、セマンティック パーシングのタスクは、フィールド、フィールド タイプ、関数、テーブルのチェックなど、パーシングによって得られた抽象構文木を効果的に検証することです。たとえば、次の文:
SQL の例
SELECT name FROM tab WHERE id=1001;
上記の SQL ステートメントの場合、セマンティック分析タスクは次のチェックを実行します。
- テーブル名が SQL ステートメントに存在するかどうか。
- フィールド名がテーブル タブに存在するかどうか。
- WHERE 条件の id フィールド タイプが 1001 と比較できるかどうか。
上記のチェックが完了すると、セマンティック解析により、オプティマイザーが使用する対応する式が生成されます。
4. SQL パーサーの選び方は?
パーサーの核となる知識ポイントを理解したら、実際のビジネスに適用する適切な SQL パーサーをどのように選択すればよいでしょうか? 次に、主流の 2 つの SQL パーサーを比較してみましょう。それぞれANTLRと方解石です。
4.1 アントラー
ANTLR は、構造化テキストまたはバイナリ ファイルの読み取り、処理、実行、および変換に使用できる強力なパーサー ジェネレーターです。ビッグデータの一部の SQL フレームワークで広く使用されており、たとえば、Hive のレキシカル ファイルは ANTLR3 で記述され、Presto のレキシカル ファイルも ANTLR4 で実装されており、SparkSQLambda のレキシカル ファイルも のレキシカル ファイルで書き換えられています。 Presto. 他にも HBase の SQL があります ツール Phoenix も SQL 解析に ANTLR ツールを使用しています。
ANTLR を使用して SQL を実装すると、実行または実装のプロセスは大まかに次のようになります。レキシカル ファイルを実装します (。実行する物理的な実行計画。
公式ウェブサイトの例:
ANTLR式
assign : ID '=' expr ';' ;
パーサーのコードは次のようになります。
ANTLR パーサー コード
void assign() {
match(ID);
match('=');
expr();
match(';');
}
4.1.1 パーサー
パーサーは、言語を認識するために使用されるプログラムであり、それ自体は字句解析器と構文解析器の 2 つの部分で構成されています。字句解析フェーズで解決される主な問題は、キーワードと、INT (型キーワード) や ID (変数識別子) などのさまざまな識別子です。構文解析は、主に字句解析の結果に基づいて構文解析番号を構築します。
したがって、字句解析と構文解析が正しく機能するためには、ANTLR4 を使用する際に文法 (Grammar) を定義する必要があります。
文字ストリーム (CharStream) を解析ツリーに変換できます。文字ストリームは字句解析後にトークン ストリームになります。トークン ストリームは最終的に、リーフ ノード (TerminalNode) と非リーフ ノード (RuleNode) を含む解析ツリーにアセンブルされます。特定の解析ツリーを次の図に示します。
4.1.2 文法
ANTLR は、多くの一般的に使用される言語の文法ファイルを公式に提供しており、直接変更して再利用することができます: https://github.com/antlr/grammars-v4
構文を使用する場合は、次の点に注意してください。
- 構文名とファイル名は一貫している必要があります。
- パーサー規則は小文字で始まります。
- レクサー規則は大文字で始まります。
- 文字列を引用するには、'string' 単一引用符を使用します。
- 開始記号を指定する必要はありません。
- ルールはセミコロンで終わります。
- ...
4.1.3 ANTLR4 による簡易計算機能の実現
以下は、ANTLR4 の使用法を説明するための簡単な例です。達成する必要がある機能的な効果は次のとおりです。
ANTLR の例
1+2 => 1+2=3
1+2*4 => 1+2*4=9
1+2*4-5 => 1+2*4-5=4
1+2*4-5+20/5 => 1+2*4-5+20/5=8
(1+2)*4 => (1+2)*4=12
ANTLR を介した処理フローを次の図に示します。
全体として、原則として、再帰的な降下。つまり、直接呼び出すことができる式 (expr など) を定義するか、ループ内で他の式を呼び出すことができますが、最終的には、それ以上呼び出すことができないコアな式が確実に存在します。
ステップ 1: レキシカル ルール ファイル (CommonLexerRules.g4) を定義する
CommonLexerRules.g4
// 定义词法规则
lexer grammar CommonLexerRules;
//////// 定义词法
// 匹配ID
ID : [a-zA-Z]+ ;
// 匹配INT
INT : [0-9]+ ;
// 匹配换行符
NEWLINE: '\n'('\r'?);
// 跳过空格、跳格、换行符
WS : [ \t\n\r]+ -> skip;
//////// 运算符
DIV:'/';
MUL:'*';
ADD:'+';
SUB:'-';
EQU:'=';
ステップ 2: 文法規則ファイル (LibExpr.g4) を定義する
LibExpr.g4
// 定于语法规则
grammar LibExpr;
// 导入词法规则
import CommonLexerRules;
// 词法根
prog:stat+ EOF?;
// 定义声明
stat:expr (NEWLINE)? # printExpr
| ID '=' expr (NEWLINE)? # assign
| NEWLINE # blank
;
// 定义表达式
expr:expr op=('*'|'/') expr # MulDiv
|expr op=('+'|'-') expr # AddSub
|'(' expr ')' # Parens
|ID # Id
|INT # Int
;
ステップ 3: 生成されたファイルをコンパイルする
Maven プロジェクトの場合は、次の依存関係を pom ファイルに追加します。
ANTLR は JAR に依存します
<dependencies>
<dependency>
<groupId>org.antlr</groupId>
<artifactId>antlr4</artifactId>
<version>4.9.3</version>
</dependency>
<dependency>
<groupId>org.antlr</groupId>
<artifactId>antlr4-runtime</artifactId>
<version>4.9.3</version>
</dependency>
</dependencies>
次に、Maven コンパイル コマンドを実行します。
Maven コンパイル コマンド
mvn generate-sources
ステップ 4: 簡単なサンプルコードを書く
予算へのサンプル テキスト:
サンプルテキスト
1+2
1+2*4
1+2*4-5
1+2*4-5+20/5
(1+2)*4
足し算、引き算、掛け算、割り算の論理クラス:
ロジック実装クラス
package com.vivo.learn.sql;
import java.util.HashMap;
import java.util.Map;
/**
* 重写访问器规则,实现数据计算功能
* 目标:
* 1+2 => 1+2=3
* 1+2*4 => 1+2*4=9
* 1+2*4-5 => 1+2*4-5=4
* 1+2*4-5+20/5 => 1+2*4-5+20/5=8
* (1+2)*4 => (1+2)*4=12
*/
public class LibExprVisitorImpl extends LibExprBaseVisitor<Integer> {
// 定义数据
Map<String,Integer> data = new HashMap<String,Integer>();
// expr (NEWLINE)? # printExpr
@Override
public Integer visitPrintExpr(LibExprParser.PrintExprContext ctx) {
System.out.println(ctx.expr().getText()+"="+visit(ctx.expr()));
return visit(ctx.expr());
}
// ID '=' expr (NEWLINE)? # assign
@Override
public Integer visitAssign(LibExprParser.AssignContext ctx) {
// 获取id
String id = ctx.ID().getText();
// // 获取value
int value = Integer.valueOf(visit(ctx.expr()));
// 缓存ID数据
data.put(id,value);
// 打印日志
System.out.println(id+"="+value);
return value;
}
// NEWLINE # blank
@Override
public Integer visitBlank(LibExprParser.BlankContext ctx) {
return 0;
}
// expr op=('*'|'/') expr # MulDiv
@Override
public Integer visitMulDiv(LibExprParser.MulDivContext ctx) {
// 左侧数字
int left = Integer.valueOf(visit(ctx.expr(0)));
// 右侧数字
int right = Integer.valueOf(visit(ctx.expr(1)));
// 操作符号
int opType = ctx.op.getType();
// 调试
// System.out.println("visitMulDiv>>>>> left:"+left+",opType:"+opType+",right:"+right);
// 判断是否为乘法
if(LibExprParser.MUL==opType){
return left*right;
}
// 判断是否为除法
return left/right;
}
// expr op=('+'|'-') expr # AddSub
@Override
public Integer visitAddSub(LibExprParser.AddSubContext ctx) {
// 获取值和符号
// 左侧数字
int left = Integer.valueOf(visit(ctx.expr(0)));
// 右侧数字
int right = Integer.valueOf(visit(ctx.expr(1)));
// 操作符号
int opType = ctx.op.getType();
// 调试
// System.out.println("visitAddSub>>>>> left:"+left+",opType:"+opType+",right:"+right);
// 判断是否为加法
if(LibExprParser.ADD==opType){
return left+right;
}
// 判断是否为减法
return left-right;
}
// '(' expr ')' # Parens
@Override
public Integer visitParens(LibExprParser.ParensContext ctx) {
// 递归下调
return visit(ctx.expr());
}
// ID # Id
@Override
public Integer visitId(LibExprParser.IdContext ctx) {
// 获取id
String id = ctx.ID().getText();
// 判断ID是否被定义
if(data.containsKey(id)){
// System.out.println("visitId>>>>> id:"+id+",value:"+data.get(id));
return data.get(id);
}
return 0;
}
// INT # Int
@Override
public Integer visitInt(LibExprParser.IntContext ctx) {
// System.out.println("visitInt>>>>> int:"+ctx.INT().getText());
return Integer.valueOf(ctx.INT().getText());
}
}
Main 関数は結果クラスを出力します。
package com.vivo.learn.sql;
import org.antlr.v4.runtime.tree.ParseTree;
import java.io.FileNotFoundException;
import java.io.IOException;
import org.antlr.v4.runtime.*;
/**
* 打印语法树
*/
public class TestLibExprPrint {
// 打印语法树 input -> lexer -> tokens -> parser -> tree -> print
public static void main(String args[]){
printTree("E:\\smartloli\\hadoop\\sql-parser-example\\src\\main\\resources\\testCase.txt");
}
/**
* 打印语法树 input -> lexer -> token -> parser -> tree
* @param fileName
*/
private static void printTree(String fileName){
// 定义输入流
ANTLRInputStream input = null;
// 判断文件名是否为空,若不为空,则读取文件内容,若为空,则读取输入流
if(fileName!=null){
try{
input = new ANTLRFileStream(fileName);
}catch(FileNotFoundException fnfe){
System.out.println("文件不存在,请检查后重试!");
}catch(IOException ioe){
System.out.println("文件读取异常,请检查后重试!");
}
}else{
try{
input = new ANTLRInputStream(System.in);
}catch(FileNotFoundException fnfe){
System.out.println("文件不存在,请检查后重试!");
}catch(IOException ioe){
System.out.println("文件读取异常,请检查后重试!");
}
}
// 定义词法规则分析器
LibExprLexer lexer = new LibExprLexer(input);
// 生成通用字符流
CommonTokenStream tokens = new CommonTokenStream(lexer);
// 语法解析
LibExprParser parser = new LibExprParser(tokens);
// 生成语法树
ParseTree tree = parser.prog();
// 打印语法树
// System.out.println(tree.toStringTree(parser));
// 生命访问器
LibExprVisitorImpl visitor = new LibExprVisitorImpl();
visitor.visit(tree);
}
}
コードを実行すると、最終的な出力が次の図に示されます。
4.2 方解石
上記の ANTLR コンテンツは、語彙分析と構文分析の簡単なプロセスを示していますが、ANTLR は SQL クエリを実装する必要があるため、独自の語彙および文法関連ファイルを定義し、ANTLR プラグインを使用してファイルをコンパイルする必要があります。次にコードを生成します (Thrift の類似の使用法では、最初にインターフェイスを定義し、次に対応する言語ファイルにコンパイルし、最後にこれらの生成されたクラスまたはインターフェイスを継承または実装します)。
4.2.1 原理と利点
Apache Calcite の出現により、これらの複雑なプロジェクトが大幅に簡素化されました。Calcite を使用すると、ユーザーはシステムに SQL シェルを簡単に配置して、効率的なクエリ パフォーマンスの最適化を行うことができます。
- クエリ言語;
- クエリの最適化;
- クエリの実行;
- データ管理;
- データストレージ;
上記の 5 つの機能は通常、データベース システムに含まれる一般的な機能です。Calcite が設計を行っていたとき、それは 3 つのグリーン パーツのみに焦点を当てていると判断し、次のデータ管理とデータ ストレージはさまざまな外部ストレージまたはコンピューティング エンジンに任せました。
データの管理と保管、特にデータの保管は非常に複雑であり、データ自体の特性から実装の多様性にもつながります。Calcite はこれら 2 つの部分の設計を放棄し、上位層のより一般的なモジュールに焦点を当てているため、十分に軽量であり、システムの複雑さを制御でき、開発者のエネルギーが過大になることはありません。
同時に、カルサイトは初期の車輪に何度も行きませんでした、そして再利用できるものは直接再利用されます。これも、開発者が受け入れられる理由の 1 つです。たとえば、次の 2 つの例を考えてみましょう。
- 例 1: SQL パーサーとして、Calcite は重要な SQL 解析の車輪を再発明するのではなく、オープン ソース JavaCC を直接使用して SQL ステートメントを Java コードに変換し、さらにそれを抽象構文木 (AST) に変換して、次の段階で使用するため。
- 例 2: Calcite は、後で説明する柔軟なメタデータ機能をサポートするために、Java コードのランタイム コンパイルをサポートする必要があります。デフォルトの JavaC は重すぎるため、より軽量なコンパイラが必要です. Calcite もホイールを構築することを選択しませんでしたが、オープンソースの Janino ソリューションを使用しました.
上の写真は Calcite によって公式に提供されたアーキテクチャ図です. この写真から得られる情報は、一方では、上で述べたことを確認するものです. Calcite は十分に単純であり、すべきでないことをしません.一方、さらに重要なことに、Calcite は十分にモジュール化され、プラグインできるように設計されています。
- [JDBC クライアント]: このモジュールは、JDBC クライアントを使用するアプリケーションをサポートするために使用されます。
- [SQL Parser and Validator]: このモジュールは、SQL の解析と検証に使用されます。
- [Expressions Builder]: 独自の SQL 解析と検証をサポートするためのフレームワーク ドッキング。
- [演算子式]: このモジュールは、関係式を処理するために使用されます。
- [Metadata Provider]: このモジュールは、外部のカスタム メタデータをサポートするために使用されます。
- [Pluggable Rules]: このモジュールは、最適化ルールを定義するために使用されます。
- [Query Optimizer] : クエリの最適化に焦点を当てたコアモジュール。
機能モジュールの分割は十分に合理的かつ独立しているため、完全に統合するのではなく、一部のみを選択して使用できます. 基本的に、各モジュールはカスタマイズをサポートしているため、ユーザーはシステムをさらにカスタマイズできます.
上記のビッグデータで一般的に使用されるコンポーネントはすべて Calcite と統合されており、Hive が独自の SQL 解析を行い、Calcite のクエリ最適化機能のみを使用していることがわかります。Flink と同様に、Calcite は解析から最適化まで直接使用されます。
上記の Calcite 統合方法はすべて Calcite のモジュールをライブラリとして使用します。重すぎると感じる場合は、よりシンプルなアダプター機能を選択できます。外部システムとのデータのやり取りは、Spark などのフレームワークのカスタム ソースまたはシンクを通じて実現されます。
上図は代表的なアダプタの使い方ですが、例えばKafkaアダプタを使えば、アプリケーション層でSQLを直接渡すことができ、最下層はJavaとKafkaに自動変換されてデータのやり取りを行います(後者ではケース操作あり)部)。
4.2.2 Calcite は KSQL クエリ Kafka を実装します
Kafka でトピック内のデータをクエリするには、EFAK の SQL 実装 (元の Kafka Eagle オープン ソース プロジェクト) を参照してください。
1. 通常の SQL クエリ
SQL クエリ
select * from video_search_query where partition in (0) limit 10
プレビュー スクリーンショット:
2.UDF クエリ
SQL クエリ
select JSON(msg,'query') as query,JSON(msg,'pv') as pv from video_search_query where `partition` in (0) limit 10
プレビュー スクリーンショット:
4.3 ANTLR4 と Calcite SQL 解析の比較
4.3.1 ANTLR4 による SQL の解析
ANTLR4 で SQL を解析する主なプロセスには、語彙ファイルと文法ファイルの定義、SQL 解析ロジック クラスの作成、メイン サービスからの SQL ロジック クラスの呼び出しが含まれます。
1.字句ファイルと文法ファイルを定義する
公式 Web サイトで提供されているオープン ソース アドレスを参照できます。詳細
2. SQL解析ロジッククラスを書く
ここでは、SQL テーブル名の解析を実装するクラスを作成します。具体的な実装コードは次のとおりです。
テーブル名を解析する
public class TableListener extends antlr4.sql.MySqlParserBaseListener {
private String tableName = null;
public void enterQueryCreateTable(antlr4.sql.MySqlParser.QueryCreateTableContext ctx) {
List<MySqlParser.TableNameContext> tableSourceContexts = ctx.getRuleContexts(antlr4.sql.MySqlParser.TableNameContext.class);
for (antlr4.sql.MySqlParser.TableNameContext tableSource : tableSourceContexts) {
// 获取表名
tableName = tableSource.getText();
}
}
public String getTableName() {
return tableName;
}
}
3. メイン サービスが SQL ロジック クラスを呼び出す
SQL 解析を実装するロジック クラスを呼び出します。具体的なコードは次のとおりです。
主なサービス
public class AntlrClient {
public static void main(String[] args) {
// antlr4 格式化SQL
antlr4.sql.MySqlLexer lexer = new antlr4.sql.MySqlLexer(CharStreams.fromString("create table table2 select tid from table1;"));
antlr4.sql.MySqlParser parser = new antlr4.sql.MySqlParser(new CommonTokenStream(lexer));
// 定义TableListener
TableListener listener = new TableListener();
ParseTreeWalker.DEFAULT.walk(listener, parser.sqlStatements());
// 获取表名
String tableName= listener.getTableName();
// 输出表名
System.out.println(tableName);
}
}
4.3.2 Calcite 構文解析 SQL
ANTLR と比較して、Calcite の SQL の構文解析プロセスは比較的単純であり、開発では、字句および文法ファイルの定義と記述に注意を払う必要はなく、特定のビジネス ロジックの実装にのみ注意を払う必要があります。たとえば、SQL COUNT 操作を実装する場合、Calcite の実装手順は次のとおりです。
1.pom の依存関係
Calcite は JAR に依存します
<dependencies>
<!-- 这里对Calcite适配依赖进行封装,引入下列包即可 -->
<dependency>
<groupId>org.smartloli</groupId>
<artifactId>jsql-client</artifactId>
<version>1.0.0</version>
</dependency>
</dependencies>
2.コードを実装する
方解石サンプルコード
package com.vivo.learn.sql.calcite;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import org.smartloli.util.JSqlUtils;
public class JSqlClient {
public static void main(String[] args) {
JSONObject tabSchema = new JSONObject();
tabSchema.put("id","integer");
tabSchema.put("name","varchar");
JSONArray datasets = JSON.parseArray("[{\"id\":1,\"name\":\"aaa\",\"age\":20},{\"id\":2,\"name\":\"bbb\",\"age\":21},{\"id\":3,\"name\":\"ccc\",\"age\":22}]");
String tabName = "userinfo";
String sql = "select count(*) as cnt from \"userinfo\"";
try{
String result = JSqlUtils.query(tabSchema,tabName,datasets,sql);
System.out.println("result: "+result);
}catch (Exception e){
e.printStackTrace();
}
}
}
3. スクリーンショットのプレビュー
4.3.3 比較結果
包括的な比較では、2 つのテクノロジの学習コスト、使用の複雑さ、および柔軟性を比較し、実際のビジネス ニーズに対応する SQL パーサーとして Calcite を選択できます。
V. まとめ
また、スタンドアロンモードの場合、実行計画を実行コードに変換するのは簡単ですが、分散分野では、さまざまなコンピューティング エンジンが存在するため、より具体的なコンピューティング エンジンに近い記述が必要になります。つまり、物理的な計画です。つまり、論理プランは抽象的なレイヤーの記述にすぎませんが、物理プランは特定のコンピューティング エンジンに直接リンクされています。
上記のシナリオを満たすために、通常は SQL パーサーを導入できます。
- 対話型クエリとしてリレーショナル データベース (MySQL、Oracle など) 用にカスタマイズされた SQL を提供します。
- JDBC、ODBC、さまざまなデータベースなどの標準インターフェースを開発者に提供します。
- データ アナリストや、プログラミング言語にあまり精通していないが、データを使用する必要があるその他の人々向け。
- ビッグ データ テクノロジ コンポーネントには SQL が付属していません。
参考文献:
- https://github.com/smartloli/EFAK
- https://github.com/antlr/antlr4
- https://github.com/antlr/grammars-v4
- https://github.com/apache/calcite