Apache 方解石簡介
1. 概述
在本教程中,我們將了解Apache Calcite 。它是一個功能強大的資料管理框架,可用於與資料存取相關的各種用例。方解石專注於從任何來源檢索數據,而不是儲存數據。此外,其查詢最佳化功能可實現更快、更有效率的資料檢索。
讓我們從與 Apache Calcite 相關的用例開始探索更多內容。
2. Apache 方解石用例
由於其功能,Apache Calcite 可用於多種用例:
為新資料庫建立查詢引擎需要數年時間。然而,Calcite 幫助我們立即開始使用開箱即用的可擴展 SQL 解析器、驗證器和優化器。方解石已用於建構HerdDB 、 Apache Druid 、 MapD等資料庫。
由於 Calcite 能夠與多個資料庫集成,因此它被廣泛用於建立資料倉儲和商業智慧工具,例如Apache Kyline 、 Apache Wayang 、阿里巴巴 MaxCompute等。
Calcite 是 Apache Kafka、 Apache Apex和 Flink 等串流平台不可或缺的元件,這些平台有助於建立可以呈現和分析即時來源的工具。
3. 任何地方的任何數據
Apache Calcite 提供現成的適配器來與 Cassandra、Elasticsearch、MongoDB 等第三方資料來源整合。
讓我們詳細探討一下。
3.1.高級別重要課程
Apache Calcite 提供了一個用於檢索資料的強大框架。這個框架是可擴展的。因此,也可以創建自訂的新適配器。讓我們來看看重要的 Java 類別:
Apache Calcite 適配器提供ElasticsearchSchemaFactory
、 MongoSchemaFactory
、 FileSchemaFactory
等類,實作SchemaFactory
介面。 SchemaFactory
透過建立Schema
JSON/YAML 模型檔案中定義的虛擬 Schema 來幫助以統一的方式連接底層資料來源。
3.2. CSV 適配器
此外,讓我們看一個範例,其中我們將使用 SQL 查詢從 CSV 檔案讀取資料。讓我們先匯入在pom.xml
檔案中使用檔案適配器所需的必要Maven 依賴項:
<dependency>
<groupId>org.apache.calcite</groupId>
<artifactId>calcite-core</artifactId>
<version>1.34</version>
</dependency>
<dependency>
<groupId>org.apache.calcite</groupId>
<artifactId>calcite-file</artifactId>
<version>1.34</version>
</dependency>
接下來,讓我們在model.json
中定義模型:
{
"version": "1.0",
"defaultSchema": "TRADES",
"schemas": [
{
"name": "TRADES",
"type": "custom",
"factory": "org.apache.calcite.adapter.file.FileSchemaFactory",
"operand": {
"directory": "trades"
}
}
]
}
model.json
中指定的FileSchemaFactory
會尋找 CSV 檔案的trades
目錄並建立虛擬TRADES
模式。隨後, trades
目錄下的 CSV 檔案將被視為表格。
在繼續查看文件適配器的運作情況之前,讓我們先看一下trade.csv
文件,我們將使用方解石適配器查詢該文件:
tradeid:int,product:string,qty:int
232312123,"RFTXC",100
232312124,"RFUXC",200
232312125,"RFSXC",1000
CSV 檔案包含三個欄位: tradeid
、 product,
和qty
。此外,列標題還指定資料類型。 CSV 檔案中總共有 3 筆交易記錄。
最後,讓我們看看如何使用 Calcite 適配器來取得記錄:
@Test
void whenCsvSchema_thenQuerySuccess() throws SQLException {
Properties info = new Properties();
info.put("model", getPath("model.json"));
try (Connection connection = DriverManager.getConnection("jdbc:calcite:", info);) {
Statement statement = connection.createStatement();
ResultSet resultSet = statement.executeQuery("select * from trades.trade");
assertEquals(3, resultSet.getMetaData().getColumnCount());
List<Integer> tradeIds = new ArrayList<>();
while (resultSet.next()) {
tradeIds.add(resultSet.getInt("tradeid"));
}
assertEquals(3, tradeIds.size());
}
}
Calcite 適配器採用model
屬性來建立模仿檔案系統的虛擬模式。然後,它使用通常的 JDBC 語義從trade.csv
檔案中取得記錄。
文件適配器不僅可以讀取 CSV 文件,還可以讀取HTML 和 JSON 文件。此外,為了處理 CSV 文件,Apache Calcite 還提供了一個特殊的 CSV 適配器,用於處理使用CSVSchemaFactory
高階用例。
3.3. Java 物件的記憶體中 SQL 操作
與 CSV 適配器範例類似,讓我們來看另一個範例,在 Apache Calcite 的幫助下,我們將在 Java 物件上執行 SQL 查詢。
假設CompanySchema
類別中有兩個Employee
和Department
類別陣列:
public class CompanySchema {
public Employee[] employees;
public Department[] departments;
}
現在,讓我們來看看Employee
類別:
public class Employee {
public String name;
public String id;
public String deptId;
public Employee(String name, String id, String deptId) {
this.name = name;
this.id = id;
this.deptId = deptId;
}
}
與Employee
類別類似,我們定義Department
類別:
public class Department {
public String deptId;
public String deptName;
public Department(String deptId, String deptName) {
this.deptId = deptId;
this.deptName = deptName;
}
}
假設有三個部門:財務、行銷、人力資源。我們將對CompanySchema
物件執行查詢以查找每個departments
的employees
人數:
@Test
void whenQueryEmployeesObject_thenSuccess() throws SQLException {
Properties info = new Properties();
info.setProperty("lex", "JAVA");
Connection connection = DriverManager.getConnection("jdbc:calcite:", info);
CalciteConnection calciteConnection = connection.unwrap(CalciteConnection.class);
SchemaPlus rootSchema = calciteConnection.getRootSchema();
Schema schema = new ReflectiveSchema(companySchema);
rootSchema.add("company", schema);
Statement statement = calciteConnection.createStatement();
String query = "select dept.deptName, count(emp.id) "
+ "from company.employees as emp "
+ "join company.departments as dept "
+ "on (emp.deptId = dept.deptId) "
+ "group by dept.deptName";
assertDoesNotThrow(() -> {
ResultSet resultSet = statement.executeQuery(query);
while (resultSet.next()) {
logger.info("Dept Name:" + resultSet.getString(1)
+ " No. of employees:" + resultSet.getInt(2));
}
});
}
有趣的是,該方法運作良好並且也獲得結果。在該方法中,Apache Calcite 類別ReflectiveSchema
協助建立CompanySchema
物件的架構。然後,它運行 SQL 查詢並使用標準 JDBC 語義取得記錄。
此外,這個範例證明,無論來源為何,Calcite 都可以使用 SQL 語句從任何地方取得資料。
4. 查詢處理
查詢處理是 Apache calcite 的核心功能。
標準 JDBC 驅動程式或 SQL 用戶端對資料庫執行查詢。而Apache Calcite 在解析和驗證查詢後,會智慧地最佳化它們以實現高效執行、節省資源並提高效能。
4.1.解碼查詢處理步驟
Calcite 提供了非常標準的組件來幫助查詢處理:
有趣的是,我們還可以擴展這些組件以滿足任何資料庫的特定要求。讓我們詳細了解這些步驟。
4.2. SQL 解析器和驗證器
作為解析過程的一部分,解析器將 SQL 查詢轉換為稱為 AST(抽象語法樹)的樹狀結構。
假設對Teacher
和Department
兩個表進行 SQL 查詢:
Select Teacher.name, Department.name
From Teacher join
Department On (Department.deptid = Teacher.deptid)
Where Department.name = 'Science'
首先,查詢解析器將查詢轉換為 AST,然後執行基本的語法驗證:
此外,驗證器在語義上驗證節點,例如:
- 驗證函數和運算符
- 根據資料庫目錄驗證資料庫物件(例如表格和列)
4.3.關係表達式產生器
隨後,在驗證步驟之後,關係式表達式產生器使用一些常見的關係運算子轉換語法樹:
-
LogicalTableScan
:從表中讀取數據 -
LogicalFilter
:根據條件選擇行 -
LogicalProject
:選擇要包含的特定列 -
LogicalJoin
:根據匹配值組合兩個表中的行
考慮前面顯示的 AST,從中導出的對應邏輯關係表達式將是:
LogicalProject(
projects=[
$0.name AS name0,
$1.name AS name1
],
input=LogicalFilter(
condition=[
($1.name = 'Science')
],
input=LogicalJoin(
condition=[
($0.deptid = $1.deptid)
],
left=LogicalTableScan(table=[[Teacher]]),
right=LogicalTableScan(table=[[Department]])
)
)
)
在關係式中, $0
和$1
代表表Teacher
和Department
。本質上,它是一個數學表達式,有助於理解將執行哪些操作來獲得結果。但是,它沒有與執行相關的資訊。
4.4.查詢最佳化器
然後,Calcite 優化器對關係表達式應用最佳化。一些常見的優化包括:
- 謂詞下推:將過濾器推到盡可能靠近資料來源的位置,以減少所獲得的資料量
- 連接重新排序:重新排列連接順序以最小化中間結果並提高效率
- 投影下推:下推投影以避免處理不必要的列
- 索引使用:識別和利用索引來加速資料檢索
4.5.查詢計劃器、產生器和執行器
最佳化後,Calcite 查詢規劃器會建立一個執行計劃來執行最佳化的查詢。執行計劃指定查詢引擎取得和處理資料所採取的確切步驟。這也稱為特定於後端查詢引擎的實體計劃。
然後, Calcite 查詢產生器以特定於所選執行引擎的語言產生程式碼。
最後,Executor連接資料庫執行最終的查詢。
5. 結論
在本文中,我們探討了 Apache Calcite 的功能,它可以快速為資料庫配備標準化 SQL 解析器、驗證器和最佳化器。因此,Calcite 使供應商免於開發長達數年的查詢引擎,使他們能夠優先考慮後端儲存。此外,Calcite 的現成適配器簡化了與不同資料庫的連接,有助於開發統一的整合介面。
此外,透過利用 Calcite,資料庫開發人員可以加快上市時間並提供強大、多功能的 SQL 功能。
本文中使用的程式碼可以在 GitHub 上找到。