Apache Calcite简介

1.概览

在本文中我们将了解Apache Calcite。它是一个强大的数据管理框架,可以应用于各种与数据访问相关的场景。Calcite的重点在于从任何源头获取数据,而不是存储数据。另外,它的查询优化功能使得数据获取更快、更高效。

接下来,我们将深入探讨更多细节,首先从Apache Calcite的应用场景开始。

2. Apache Calcite 用例

由于其强大的功能,Apache Calcite可以在多个应用场景中发挥作用:

在这里插入图片描述

为新数据库构建查询引擎需要花费很多时间。然而,Calcite提供了一个即插即用的可扩展SQL解析器,验证器和优化器,使我们能够立即开始工作。Calcite已经被应用于构建诸如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通过在JSON/YAML模型文件中创建虚拟Schema,帮助以统一的方式连接底层数据源。

3.2 CSV适配器

现在让我们看一个例子,我们将使用SQL查询从CSV文件中读取数据。让我们从在pom.xml文件中导入使用文件适配器所需的必要Maven依赖开始

<dependency>
    <groupId>org.apache.calcite</groupId>
    <artifactId>calcite-core</artifactId>
    <version>1.36.0</version>
</dependency>
<dependency>
    <groupId>org.apache.calcite</groupId>
    <artifactId>calcite-file</artifactId>
    <version>1.36.0</version>
</dependency>

接着编写模型配置

{
  "version": "1.0",
  "defaultSchema": "TRADES",
  "schemas": [
    {
      "name": "TRADES",
      "type": "custom",
      "factory": "org.apache.calcite.adapter.file.FileSchemaFactory",
      "operand": {
        "directory": "trades"
      }
    }
  ]
}

在model.json中指定的FileSchemaFactory查看trades目录下的CSV文件,并创建一个虚拟的TRADES schema。随后,trades目录下的CSV文件被视为表格。

tradeid:int,product:string,qty:int
232312123,"RFTXC",100
232312124,"RFUXC",200
232312125,"RFSXC",1000

CSV文件有三列:tradeid,product和qty。此外,列标题还指定了数据类型。总共,CSV文件中有三个交易记录。

model.json文件、TRADE.csv均放在resource目录下。
在这里插入图片描述

最后,让我们看看如何使用Calcite适配器获取记录:

import static org.junit.Assert.assertEquals;

import java.net.URL;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import org.junit.Test;

public class CsvTest {
    @Test
    public void whenCsvSchema_thenQuerySuccess() throws SQLException {
        String fileName = "model.json";
        Properties info = new Properties();
        info.put("model", getPath(fileName));
        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());
        }
    }

    private String getPath(String model) {
        URL url = ClassLoader.getSystemClassLoader().getResource(model);
        return url.getPath();
    }
}

Calcite适配器使用模型属性来创建一个模拟文件系统的虚拟schema。然后,它使用常规的JDBC语义从trade.csv文件中获取记录。

文件适配器不仅可以读取CSV文件,还可以读取HTML和JSON文件。此外,为了处理CSV文件,Apache Calcite还提供了一个特殊的CSV适配器,用于处理使用CSVSchemaFactory的高级应用场景。

3.3. In-Memory SQL Operation on Java Objects

3.3 通过SQL访问Java内存对象

类似于CSV适配器的例子,让我们看另一个例子,借助Apache Calcite,我们将在Java对象上运行SQL查询。

假设CompanySchema类中有Employee和Department类的两个数组:

public class CompanySchema {
    public Employee[] employees;
    public Department[] departments;
}
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;
    }
}
public class Department {
    public String deptId;
    public String deptName;

    public Department(String deptId, String deptName) {
        this.deptId = deptId;
        this.deptName = deptName;
    }
}

假设有三个部门:财务,市场和人力资源。我们将在CompanySchema对象上运行一个查询,以找出每个部门的员工人数:

import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;

import calcite.memSql.CompanySchema;
import calcite.memSql.Department;
import calcite.memSql.Employee;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;
import org.apache.calcite.adapter.java.ReflectiveSchema;
import org.apache.calcite.jdbc.CalciteConnection;
import org.apache.calcite.schema.Schema;
import org.apache.calcite.schema.SchemaPlus;
import org.junit.Before;
import org.junit.Test;

public class inMemTest {
    static CompanySchema companySchema = new CompanySchema();
    @Before
    public void setup() {
        Department dept1 = new Department("HR", "Human Resource");
        Department dept2 = new Department("MKT", "Marketing");
        Department dept3 = new Department("FIN", "Finance");

        Employee emp1 = new Employee("Tom", "1234", "HR");
        Employee emp2 = new Employee("Harry", "39731", "FIN");
        Employee emp3 = new Employee("Danny", "45632", "FIN");
        Employee emp4 = new Employee("Jenny", "78654", "MKT");

        companySchema.departments = new Department[]{dept1, dept2, dept3};
        companySchema.employees = new Employee[]{emp1, emp2, emp3, emp4};
    }

    @Test
    public 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()) {
                System.out.println("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解析查询处理步骤

在这里插入图片描述
我们也可以扩展这些组件以满足任何数据库的特定要求。让我们更详细地了解这些步骤。

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'

在这里插入图片描述

接下来,验证器会在语义层面上验证节点:
验证函数和运算符 根据数据库的目录验证数据库对象,如表和列

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查询生成器会在特定于所选执行引擎的语言中生成代码。
最后,执行器会连接到数据库执行最终的查询。

5. 总结

在这篇文章中,我们探讨了Apache Calcite的能力,它能快速地为数据库提供标准化的SQL解析器,验证器和优化器。这让供应商无需花费太多时间开发查询引擎,使他们能够优先考虑后端存储。此外,Calcite的适配器简化了与各种数据库的连接,有助于开发统一的集成接口。

更进一步,通过利用Calcite,数据库开发者可以加快产品上市的时间,同时提供强大、多功能的SQL功能。

附录

源码:https://github.com/eugenp/tutorials/tree/master/persistence-modules/java-calcite

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值