Apache Calcite - 自定义数据源适配之访问内存列表

前言

上一篇文章中学习了Calcite基本概念,其中框架的核心能力是通过统一的Sql访问不同来源的数据。这篇文章中将通过一个简单的例子学习如何实现改功能。 最终通过sql来访问Java List中的数据。

准备工作

maven依赖

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

数据源定义

PersonList维护了一个Person对象列表,我们将通过sql访问PersonList中的数据

public class PersonList {
    private List<Person> personList;

    private PersonList(List<Person> personList) {
        this.personList = personList;
    }

    public static PersonList create() {
        return new PersonList(Lists.newArrayList());
    }
    public void addPerson(Person person) {
        personList.add(person);
    }

    public Iterator<Person> getIterator() {
        return personList.iterator();

    }
}

public class Person {
    private Long id;
    private String name;
    private Integer age;

    public static Person createRandomly() {
        Person person = new Person();
        person.setId((long) ((int) ((Math.random() * 1000000000) + 1)));
        person.setName("Person" + person.getId());
        person.setAge((int) ((Math.random() * 100) + 1));
        return person;
    }
	// 省略getter setter
}

核心对象介绍

为了适配多源数据,根据上一篇文章,我们需要扩展实现Calcite中的Schema与Table。

schema

在Calcite 中,Schema 是一个非常核心的概念,它代表了一种数据组织方式,用于定义数据源的结构。Schema 可以被理解为数据库中的一个模式(schema),它包含了表(tables)、视图(views)、类型(types)和其他数据库对象的集合。在 Calcite 中,Schema 是数据访问的逻辑组织和层次结构的基础。

  • 数据源抽象:Schema 为不同类型的数据源提供了一个统一的抽象层。不论数据存储在何处(如 JDBC 数据库、文件、内存中的集合等),都可以通过定义相应的 Schema 来实现对这些数据的查询和操作。

  • 查询解析和执行:在执行 SQL 查询时,Calcite 需要知道每个表的结构,包括列名、数据类型等信息。Schema 提供了这些信息,使 Calcite 能够解析 SQL 语句并生成相应的执行计划。

  • 数据访问的统一入口:通过定义 Schema,开发者可以在不改变查询逻辑的情况下,更换后端数据源。这提供了很高的灵活性,使得系统能够适应不同的数据存储需求。

  • 支持多数据源和数据联合:Calcite 能够同时访问多个数据源,并且可以在一个查询中联合多个数据源的数据。每个数据源都由一个 Schema 描述,Calcite 负责处理数据源之间的通信和数据转换。

  • 扩展性和自定义:开发者可以实现自定义的 Schema,对数据访问逻辑进行定制。这对于特殊的数据格式或特定的性能优化非常有用。

table

Table 是一个核心概念,它代表了可查询的数据集合。在数据库术语中,一个表通常是数据的行和列的集合,而在 Calcite 中,Table 接口更加通用,可以表示不仅仅是传统的表格数据,还可以表示流数据或任何其他形式的结构化数据。

在 Calcite 的架构中,Table 是一个接口,定义在 org.apache.calcite.schema 包中。它提供了对数据的抽象视图,不关心数据的存储方式或物理格式。通过实现不同类型的 Table 接口,Calcite 能够支持多种数据源,如 JDBC 数据库、CSV 文件、内存中的数据结构等。

Calcite 中的 Table 可以具体化为以下几种类型,每种类型都有其特定的用途和功能:

  • ScannableTable:这种类型的表支持全表扫描。当查询需要访问表中的所有数据时,可以使用此类型的表。
  • FilterableTable:这种表支持在表的实现层面上应用过滤条件。这意味着不需要将整个数据集加载到内存中,而是可以推送过滤逻辑到数据源(如果数据源支持这样的操作),从而提高查询效率。
  • ProjectableFilterableTable:结合了 FilterableTable 和投影(选择特定列)的能力。这允许在读取数据时仅返回查询所需的列,减少数据传输和处理的开销。
  • TranslatableTable:这种类型的表可以被翻译成特定的执行计划。这是 Calcite 中非常强大的功能,因为它允许开发者自定义如何将 SQL 查询转换为对底层数据源的操作。

代码实现

结构描述

  1. 扩展实现Schema,对我们要访问的数据源进行抽象
  2. 扩展实现ScannableTable访问数据集
  3. 创建Table实例,关联实际数据集
  4. 创建Schema实列,维护Table实例
  5. 将创建的Schema加入Calcite
  6. 创建连接对象
  7. 执行Sql
  8. 遍历结果

实现代码

schema与table

public class ListSchema extends AbstractSchema {
    Map<String, Table> tableMap = new HashMap<>();
    
    public void addTable(String name, Table table) {
        tableMap.put(name, table);
    }

    public ListSchema() {
    }

    @Override
    protected Map<String, Table> getTableMap() {
        return tableMap;
    }
}

public class TableForList extends AbstractTable implements ScannableTable{

        private PersonList personList;

        public TableForList(PersonList personList) {
            this.personList = personList;
        }

        @Override
        public Enumerable<Object[]> scan(DataContext root) {
            return new DefaultEnumerable<Object[]>() {

                @Override
                public Enumerator<Object[]> enumerator() {
                    return null;
                }

                @NotNull
                @Override
                public Iterator<Object[]> iterator() {
                    Iterator<Person> iterator = personList.getIterator();
                    return new Iterator<Object[]>() {


                        @Override
                        public boolean hasNext() {
                            return iterator.hasNext();
                        }

                        // 迭代返回每行数据
                        @Override
                        public Object[] next() {
                            Person next = iterator.next();
                            return new Object[]{next.getId(),next.getName(),next.getAge()};
                        }
                    };
                }
            };
        }

        @Override
        public RelDataType getRowType(RelDataTypeFactory typeFactory) {
            return typeFactory.builder()
                .add("id", typeFactory.createSqlType(SqlTypeName.BIGINT))
                .add("name",typeFactory.createSqlType(SqlTypeName.VARCHAR))
                .add("age",typeFactory.createSqlType(SqlTypeName.INTEGER))
                .build();
        }
    }

主流程

@Test
public void test() throws Exception{
  PersonList personList = PersonList.create();
  personList.addPerson(Person.createRandomly());
  personList.addPerson(Person.createRandomly());
  personList.addPerson(Person.createRandomly());

  Table tableForList = new TableForList(personList);
  ListSchema listSchema = new ListSchema();
  listSchema.addTable("MyTable", tableForList);

  Properties info = new Properties();
  Connection connection = DriverManager.getConnection("jdbc:calcite:", info);
  CalciteConnection calciteConnection = connection.unwrap(CalciteConnection.class);
  SchemaPlus rootSchema = calciteConnection.getRootSchema();

  rootSchema.add("listSchema", listSchema);
  Statement statement = calciteConnection.createStatement();

  ResultSet resultSet = statement.executeQuery("select * from \"listSchema\".\"MyTable\"");

  while (resultSet.next()) {
      int columnCount = resultSet.getMetaData().getColumnCount();
      for (int i = 1; i <= columnCount; i++) {
          String value = resultSet.getString(i);
          System.out.printf("%s ",value);
      }
      System.out.println();
  }
}

异常处理清单

1.SqlValidatorException: Object ‘HTTPSCHEMA’ not found; did you mean ‘httpSchema’?

原sql写为 “select * from listSchema.MyTable”, 异常指出在执行 SQL 查询时遇到了问题。错误明确指出了问题所在:Object ‘HTTPSCHEMA’ not found; did you mean ‘httpSchema’?。这意味着 Calcite 在尝试查找名为 HTTPSCHEMA 的对象时失败了,这通常是因为对象名称的大小写不匹配。3种解决方法如下:

  • 使用引号:如果你想明确指定标识符的大小写,可以在 SQL 查询中使用双引号将标识符括起来。例如,如果 schema 名称是小写的 listSchema,这样写查询:

select * from “listSchema”.“MyTable”;

  • 配置 Calcite 的大小写敏感性:在创建 Calcite 连接时设置 unquotedCasing 来定义对于未加引号的标识符的大小写处理方式。默认情况下,Calcite 将未加引号的标识符视为大写。如果希望 Calcite 忽略标识符的大小写,可以设置 caseSensitive 为 false。

Properties info = new Properties();
info.setProperty(“caseSensitive”, “false”);
Connection connection = DriverManager.getConnection(“jdbc:calcite:”, info);

  • 连接参数中设置词法规则,这里我们设置info参数lex,根据官方手册可以有这些参数,BIG_QUERY, JAVA, MYSQL, MYSQL_ANSI, ORACLE (default), SQL_SERVER., 我们设置为JAVA即可
  Properties info = new Properties();
  Connection connection = DriverManager.getConnection("jdbc:calcite:", info);

2. 打印结果 java.sql.SQLException: invalid column ordinal: 0

打印结果代码为,最初实现时,i = 0 开始打印。由于框架是从下标1开始访问,因此i=0 导致输出错误

      for (int i = 1; i <= columnCount; i++) {
          String value = resultSet.getString(i);
          System.out.printf("%s ",value);
      }

总结

实现多源访问,了解框架结构及核心类最终实现

<think>嗯,用户问是否需要建立一个像Meger那样的统一元数据中间层来处理解析和映射逻辑。首先,我需要确认用户提到的“Meger”是指什么。可能是拼写错误,应该是“Merger”或者某个特定的工具或系统?不过根据上下文,可能是指类似的技术方案,比如数据整合或元数据管理工具。假设用户指的是需要构建一个统一的元数据中间层,类似于某些现有系统,比如LinkedIn的DataHub、Lyft的Amundsen等。 接下来,用户的问题核心是是否需要这样的中间层。我需要考虑几个方面:元数据管理的现状、系统的复杂性、团队规模、数据来源多样性、是否需要跨系统协作等。如果用户所在的企业或项目中有多个数据源,数据格式不统一,或者存在跨团队协作困难,那么统一元数据中间层可能是有必要的。 然后,思考元数据中间层的作用。例如,元数据中间层可以集中管理元数据,提供统一的查询接口,支持数据发现、血缘分析、权限管理等。同时,中间层可以处理不同数据源之间的映射和转换,降低系统间的耦合度。但也要考虑构建和维护这样的中间层的成本,包括开发时间、资源投入以及后续的维护工作。 另外,用户提到的“解析映射等逻辑”可能涉及数据集成中的ETL过程,或者API网关中的请求路由转换。如果是数据处理流程中的元数据管理,是否需要中间层取决于现有系统的痛点。比如,是否存在数据孤岛,元数据分散导致维护困难,或者数据血缘不清晰影响问题排查。 还需要考虑替代方案。是否有现有的工具或服务可以替代自建中间层?例如,使用开源的元数据管理工具如Apache Atlas,或者云服务提供的元数据管理方案。如果现有工具能满足需求,可能不需要自建,节省成本。 最后,用户的身份可能是技术负责人或架构师,需要做出技术决策。他们可能面临数据治理的挑战,希望提高系统的可维护性和扩展性。深层需求可能是如何有效管理日益增长的数据源和复杂的系统交互,确保数据的一致性和可追溯性。 总结下来,回答需要分析适用场景、优缺点、实施建议,并根据用户的具体情况提供建议,可能需要进一步询问他们的业务规模和数据复杂度,以给出更精准的建议。</think>是否需要建立统一的元数据中间层(类似Merger的设计理念),需结合**数据规模、系统复杂度、团队协作需求**综合判断。以下是分场景分析: --- ### **一、何时需要元数据中间层?** #### **1. 多源异构数据整合** - **场景**:存在$N \geq 3$种异构数据源(如MySQL、Kafka、S3),且业务需跨源联合查询或分析。 - **价值**:中间层通过**统一Schema映射**(如将MySQL表结构映射为Protobuf格式)降低数据使用门槛。 #### **2. 数据血缘与治理需求** - **场景**:需追踪字段级数据血缘(例如:订单金额$order\_amount$的加工路径)。 - **方案**:中间层记录元数据变更历史,结合图谱数据库(如Neo4j)可视化上下游依赖。 #### **3. 动态配置与权限管控** - **场景**:敏感字段(如$user\_phone$)需按角色动态脱敏(如DBA可见明文,分析师看到$phone\_masked$)。 - **实现**:中间层定义字段级权限策略,拦截查询请求并重写SQL。 --- ### **二、核心设计要点** #### **1. 元模型标准化** - **目标**:抽象通用元模型(如Table、Column、Pipeline),兼容不同系统特性。 - **示例**: ```protobuf message MetadataEntity { string id = 1; // 唯一标识符 string source_type = 2; // 数据源类型(如"postgresql") map<string, string> properties = 3; // 扩展属性(如分区字段$partition_key$) } ``` #### **2. 解析与映射引擎** - **功能**: - **语法解析**:将SQL、API请求等解析为逻辑执行计划。 - **规则引擎**:根据元数据动态路由(如将查询$SELECT * FROM logs$路由至Elasticsearch)。 - **工具选型**: - 使用Apache Calcite实现SQL解析与优化。 - 用CUE或Rego语言编写映射规则。 #### **3. 性能优化策略** - **缓存机制**:对高频元数据(如表结构)使用Redis缓存,降低延迟(目标$P99 < 10ms$)。 - **增量同步**:通过CDC(如Debezium)捕获元数据变更,避免全量扫描。 --- ### **三、替代方案与成本权衡** | **方案** | **适用场景** | **成本** | |-------------------------|---------------------------------|-----------------------------------| | **自建中间层** | 跨系统协作复杂、定制需求高 | 高(需6-12个月开发,持续维护) | | **开源工具(如Atlas)** | 中小规模,需快速落地 | 中(需适配业务逻辑) | | **云服务(如Glue)** | 重度依赖AWS/Aliyun生态 | 低(按量付费,但可控性差) | --- ### **四、实施建议** 1. **MVP验证**:优先实现核心功能(如元数据注册与检索),用$POC$验证价值。 2. **分阶段演进**: - **阶段1**:静态元数据目录(人工维护)。 - **阶段2**:自动化血缘采集(集成Airflow、dbt)。 - **阶段3**:智能推荐(根据查询模式推荐表关联关系)。 3. **与现有系统集成**:通过Hook机制监听数据库DDL变更(如PostgreSQL的Event Trigger)。 --- 若您的系统已出现**跨团队数据定义冲突**或**数据溯源效率低下**,建议启动元数据中间层建设。可进一步讨论具体技术选型(如采用GraphQL还是gRPC作为查询接口)。
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值