自考本科条件,湛江seo,在线购物网站怎么做,wordpress xampp 教程1、校验的作用
1#xff09;完善语义信息
例如在SQL语句中#xff0c;如果碰到select * 这样的指令#xff0c;在SQL的语义当中#xff0c;“*” 指的是取出对应数据源中所有字段的信息#xff0c;因此就需要根据元数据信息来展开。
2#xff09;结合元数据信息来纠偏…1、校验的作用
1完善语义信息
例如在SQL语句中如果碰到select * 这样的指令在SQL的语义当中“*” 指的是取出对应数据源中所有字段的信息因此就需要根据元数据信息来展开。
2结合元数据信息来纠偏
例如查询引擎需要查看SQL语句中对应的数据源、函数、字段是否能够根据元数据信息找到。
3SqlNode-RelNode
在Calcite中将解析层转换的SqlNode结合元数据信息转换成RelNode信息。
2、元数据定义
1Calcite中元数据的基本概念
当前很多单一数据管理系统只能够支持单一的数据模型。
为了支持多种数据模型Calcite对不同的数据模型进行组织以Model数据模型、Schema数据模式、Table表的结构将其管理的数据进行了规约 1Model
在Calcite当中Model对应管理的数据模型主要是关系模型对部分NoSQL也提供了支持例如支持键值模型的Cassandra支持文档模型的MongoDB等。
Calcite支持多种数据模型的注册将不同模型的数据拉取到内存当中统一成关系模型并用统一的函数进行操作统一呈现给用户。
Calcite目前还不支持图模型以及图数据库
一方面图数据库目前还没有业界通用的查询语言很多时候图数据库都是使用编程的形式来管理的因此这方面支持的成本会比较高。
另一方面当前Calcite内部的数据类型以通用的关系数据库的数据类型为主辅以一些空间数据类型图数据类型的兼容性较难实现而且操作图的操作符也与关系代数的数据操作符相差较大。
2Schema
Schema被定义成描述符的持久命名集合它包含表、列、数据类型、视图、存储过程、关系、主键、外键等概念。
很多数据库都对Schema进行了拆分并将Schema与数据库等同。 在Calcite当中可以建立一个权限Schema里面包含用户、权限、角色等实体表。
同样可以用Schema来定义表和函数的命名空间。
为了保证Schema的功能完善和使用灵活Calcite中的Schema还可以嵌套。
3Table
在Calcite当中Table对应的是表的概念用来存储数据的基本数据结构。
在Calcite当中其核心采用了关系模型因此表是关系代数中的表格——由一些约束条件进行约束的二维数组数据集。
4Function
在Calcite中Function对应函数的概念。
在关系数据库中除了关系代数本身对数据的操作方法函数也是对数据进行操作的重要补充往往会作为数据库的一个对象是独立的程序单元。
Calcite支持函数也支持用户对函数的自定义操作用户可以用代码实现自定义函数然后在数据模型配置文件当中注册。
5数据类型Type
在Calcite中可以自定义数据类型和名称比如将 VARCHAR(10) 重命名为t_name那么建表时就可以直接用t_name代表VARCHAR(10)。
2数据模型定义
在Calcite中定义数据模型默认采用配置文件的方式只需要准备一个JSON或者YAML文件由于JSON和YAML都可以完成参数配置的工作因此它们之间可以互相转换。
采用以下JSON文件将其命名为“model.json”其中定义了3个配置信息即表示版本号的“version”、表示Schema默认名称的“defaultSchema” 以及表示具体数据模式的“schemas”。
数据模型用JSON文件方式定义的结构
{version: 1.0,defaultSchema: mongo,schemas: [...]
} 数据模型用YAML文件方式定义的结构
version: 1.0
defaultSchema: mongo
schemas:
-[Schema...]在这两个配置文件中重点在Schema的定义上Calcite定义了3种类型即MAP、JDBC、CUSTOM虽然在实现上它们都有统一的接口但具体的属性有些差别可以参考Calcite源码 org.apache.calcite.model 包下的实现类。
1Schema定义分类
aMAP类型
MAP是默认类型在一个结构中定义了所有的表、函数和数据类型MAP的本意是映射数据模型的定义可以看作各种元数据的映射组合。
MAP类型的定义方式
{name: MAP_SCHEMA,type: map,tables: [...],functions: [...],types: [...]
} bJDBC类型
JDBC 类型的 Schema 是给遵循 JDBC 规范的数据库的特权这个 Schema 直接对应一个数据库。
而在 JDBC 当中需要配置下面几个参数
用于指定数据连接驱动的 “jdbcDriver”
用于指定数据源位置的“jdbcUrl”这里要根据不同数据源的JDBC规则来进行配置有一些数据源支持配置多个节点信息
用于指定用户名和密码的“jdbcUser”和“jdbcPassword”
JDBC类型的定义方式 {name: JDBC_SCHEMA,type: jdbc,jdbcDriver: com.mysql.jdbc.Driver,jdbcUrl: jdbc:mysql://localhost:3306/db_cdm,jdbcUser: root,jdbcPassword: root123,
} cCUSTOM类型
CUSTOM 类型是用来给用户自定义数据源的参数可扩展、自由度大使用最广泛。
“factory” 的值对应自己写的 SchemaFactory 工厂接口的实现表示这是用户自己创建的数据模式
“operand”是一个映射关系利用Map这种类型来进行封装代表用户自定义的参数。
以注册MySQL Schema作为示例对应的配置信息
{name: MYSQL,type: custom,factory: cn.com.ptpress.cdm.schema.mysql.MysqlSchemaFactory,operand: {url: jdbc:mysql://localhost:3306/db_cdm,user: root,pass: password}
} 通过对Schema的定义完成了对数据库实体的定义对视图、函数、数据类型这几种实体的元数据同样需要进行指定。
2.视图的元数据定义
在数据库中视图一般是基于一张或者几张基本表导出的虚拟表作用是使用户在执行同样的查询逻辑时不必反复书写同样的查询语句。
视图也可以像表一样查询所以定义其元数据时也可以采用类似的模板只是核心是构成视图的SQL语句。
视图的元数据定义示例
{name: V_SYS_ROLE,type: view,sql: select * from sys_role limit 10,modifiable: false
} 其中各个参数的含义如下
name视图名称。
type元数据类型为视图。
sql构成视图的SQL语句。
modifiable是否可通过视图修改原始数据如果设为null或不设置Calcite会自己推导不能修改的视图会报错。
3函数定义
函数在Calcite中是一个非常重要的组成部分支持用户自行定义函数的方法。
这个过程主要分为两个步骤定义函数配置文件和定义函数实体类。
定义函数配置文件指用户需要在JSON文件中指定函数的名称“name”、对应实体类的全路径名“className”以及在实体类内对应的函数名称“methodName”。
“name”和“methodName”不一致因为“name”是在Calcite元数据体系内的名称而“methodName”是这个函数调用时采用的函数名称。
functions: [{name: my_len,className: cn.com.ptpress.cdm.schema.function.MyFunction,methodName: myLen}
] 在定义函数的实体类时由于Calcite内部会采用动态代码生成技术调用相关的函数相关的调用逻辑会拼接成字符串动态编译和调用因此只要能够保证Calcite运行时找得到这个实体类就可以正常调用。
函数实体类的定义示例
/*** 自定义函数的实体类*/
public class MyFunction {/*** 计算二进制数中1的个数*/public int myLen(int permission) {int c 0;for (; permission 0; c) {permission (permission - 1);}return c;}
}4.数据类型定义
可以在 JSON 文件当中配置 types 来对列及其数据类型进行指定。
指定一个数据类型的属性名称为“vc”类型为“varchar”也为这个数据类型指定了别名——“C”同时声明了“type”为“boolean”。
在这个配置文件中同时出现了attributes和type如果二者出现冲突最终生效的是type。
数据类型的定义示例 types: [{name: C,type: boolean,attributes: [{name: vc,type: varchar}]}
]3自定义表元数据实现
1.Schema 创建
Schema在Calcite中是由对应的实体类定义的Calcite已经提供了相关的接口——AbstractSchema只需要实现这个接口和其中的get TableMap方法。
其中 MysqlSchema 继承 AbstractSchema重写的 getTableMap 方法返回的是一个Map映射键为表名值为Table实例代码中的tables 列表通过构造方法传入。
这种情况是一次性加载所有元数据的情况如果想要更加灵活地实现Schema可以直接从接口层开始实现。
Schema的实现
import cn.com.ptpress.cdm.schema.common.CdmTable;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.apache.calcite.schema.impl.AbstractSchema;import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;/**这个类是用来封装MySQL的Schema信息的*/
Data
EqualsAndHashCode(callSuper true)
AllArgsConstructor
public class MysqlSchema extends AbstractSchema {// Schmea名称private String name;// Schema下的表private ListCdmTable tables;Overrideprotected MapString, org.apache.calcite.schema.Table getTableMap() {return tables.stream().collect(Collectors.toMap(CdmTable::getName, t - t));}
}实现对应的工厂类——MysqlSchemaFactory这个类的主要作用就是基于配置文件传来的参数构造Schema对象也就是创建MysqlSchema实例。
Calcite提供了对应的接口——SchemaFactory只需要实现这个接口并实现它的方法——create即可。
create方法需要获取MySQL的所有表然后封装到MysqlSchema里通过直接查询MySQL元数据来获取。
构造Schema对象主要分成4步
根据参数获取连接查询表元数据信息查询每张表的列信息并封装Table信息构造Schema对象并返回
构造Schema对象的具体过程
import cn.com.ptpress.cdm.schema.common.CdmColumn;
import cn.com.ptpress.cdm.schema.common.CdmTable;
import org.apache.calcite.schema.Schema;
import org.apache.calcite.schema.SchemaFactory;
import org.apache.calcite.schema.SchemaPlus;import java.sql.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;/*** 构造MySQL的Schema对象的核心方法* 其中包含3个参数parentSchema封装了父级别的Schema信息nameSchema的信息operand从配置文件传来的配置信息JSON文件中的operand配置*/
public class MysqlSchemaFactory implements SchemaFactory {Overridepublic Schema create(SchemaPlus parentSchema, String name, MapString, Object operand) {// 1. 根据参数获取链接try (final Connection conn DriverManager.getConnection(String.valueOf(operand.get(url)),String.valueOf(operand.get(user)), String.valueOf(operand.get(pass)))) {// 2. 查询表元数据信息final Statement stmt conn.createStatement();final ResultSet rs stmt.executeQuery(SHOW TABLES);// 3. 查询表信息-show tables,查询列信息-desc tableListCdmTable tables new ArrayList(8);while (rs.next()) {final String table rs.getString(1);tables.add(new CdmTable(table, getColumns(conn, table)));}// 4. 构造 Schema 对象返回return new MysqlSchema(name, tables);} catch (SQLException e) {throw new RuntimeException(e);}}// 获取列信息private ListCdmColumn getColumns(Connection conn, String table) throws SQLException {final Statement stmt conn.createStatement();final ResultSet rs stmt.executeQuery(DESC table);ListCdmColumn columns new ArrayList();while (rs.next()) {columns.add(new CdmColumn(rs.getString(Field),typeMap(pureType(rs.getString(Type)))));}return columns;}/*** mysql 有的类型和 calcite不一样需要修改下别名*/private String typeMap(String type) {switch (type.toLowerCase()) {case int:return integer;default:return type;}}/*** 传入的type含有类型长度如 bigint(20), varchar(258)* 需要去掉括号*/private String pureType(String type) {final int i type.indexOf(();return i 0 ? type.substring(0, i) : type;}
}2.表元数据创建
创建Schema下面的表元数据对表元数据Calcite也提供了对应的实体——Table可以继承其子类——AbstractTable只需要重写其getRowType方法返回表的字段名和类型映射。
CdmColumn 包含列名和列类型的封装。
需要构造的是RelDataType可以通过RelDataTypeFactory创建这里使用SQL类型此外使用Java类型也可以构造RelDataType。
表元数据的创建示例
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import org.apache.calcite.rel.type.RelDataType;
import org.apache.calcite.rel.type.RelDataTypeFactory;
import org.apache.calcite.schema.impl.AbstractTable;
import org.apache.calcite.sql.type.SqlTypeName;
import org.apache.calcite.util.Pair;import java.util.ArrayList;
import java.util.List;/*** 表元数据内部信息的实现逻辑*/
Data
EqualsAndHashCode(callSuper true)
AllArgsConstructor
NoArgsConstructor
public class CdmTable extends AbstractTable {/*** 表名*/private String name;/*** 表的列*/private ListCdmColumn columns;Overridepublic RelDataType getRowType(RelDataTypeFactory typeFactory) {ListString names new ArrayList();ListRelDataType types new ArrayList();for (CdmColumn c : columns) {names.add(c.getName());RelDataType sqlType typeFactory.createSqlType(SqlTypeName.get(c.getType().toUpperCase()));types.add(sqlType);}return typeFactory.createStructType(Pair.zip(names, types));}
}import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.apache.calcite.sql.type.SqlTypeName;/*** 列元数据的创建逻辑*/
Data
AllArgsConstructor
NoArgsConstructor
public class CdmColumn {/*** 列名*/private String name;/*** 列类型,可以使用calcite扩展的sql类型{link SqlTypeName}*/private String type;
}定义好 Table 对象需要将其和 Schema 关联起来这一步在定义 MysqlSchema 时已经完成了而 CdmTable 的构建是在MysqlSchemaFactory 里完成的。
既然Schema是通过SchemaFactory创建的为什么Table不是通过TableFactory创建的呢
对表“sys_role”的内部元数据信息进行定义
“name”指定了表名“type”指定了表的类型此处的“custom”同样表示这张表是用户自定义的“factory”指定了封装表内部元数据信息的工厂类的全路径名“operand”指定自定义参数定义了2个参数来指定表内列的元数据信息路径和数据路径。
对表内部的元数据信息进行定义
{name: sys_role,type: custom,factory: cn.com.ptpress.cdm.schema.csv.CsvTableFactory,operand: {colPath: src/main/resources/sys_role/col_type.json,dataPath: src/main/resources/sys_role/data.csv}
}这2个文件很简单定义表内列的元数据和数据。
定义表内列的元数据和数据
// col_type.json
[{name: role,type: varchar},{name: permission,type: integer}
]// data.csv
role,permission
admin,111111
user,000011
nobody,000000
dev,000111
test,001011CsvTableFactory是表对象创建工厂类与前述的SchemaFactory类似Calcite提供的接口是TableFactory需要实现这个接口并且实现它的create方法。
这里的执行逻辑主要分为两步
首先利用JSON解析器来对配置文件中传来的参数进行解析获取相关的列信息。
然后将这些列信息以及数据信息封装到CsvTable对象中并回传。
import cn.com.ptpress.cdm.schema.common.CdmColumn;
import com.fasterxml.jackson.core.type.TypeReference;
import lombok.SneakyThrows;
import org.apache.calcite.rel.type.RelDataType;
import org.apache.calcite.schema.SchemaPlus;
import org.apache.calcite.schema.TableFactory;import java.io.File;
import java.util.List;
import java.util.Map;import static cn.com.ptpress.cdm.schema.util.JsonUtil.JSON_MAPPER;/*** 创建表的工厂类*/
public class CsvTableFactory implements TableFactoryCsvTable {/*** 构造对应的表对象——CsvTable*/OverrideSneakyThrowspublic CsvTable create(SchemaPlus schema, String name, Map operand, RelDataType rowType) {// 1. 获取列信息final String colTypePath String.valueOf(operand.get(colPath));final ListCdmColumn columns JSON_MAPPER.readValue(new File(colTypePath),new TypeReferenceListCdmColumn() {});// 2. 将列信息和数据信息封装到CsvTable对象中并回传return new CsvTable(name, columns, String.valueOf(operand.get(dataPath)));}
}CsvTable仅仅是对CdmTable的扩展dataPath需要保留下来供后面查询数据使用。
import cn.com.ptpress.cdm.schema.common.CdmColumn;
import cn.com.ptpress.cdm.schema.common.CdmTable;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;import java.util.List;/*** CsvTable的扩展实现*/
Data
EqualsAndHashCode(callSuper true)
NoArgsConstructor
public class CsvTable extends CdmTable {/*** 数据路径*/private String dataPath;/*** 构造方法*/public CsvTable(String name, ListCdmColumn columns, String dataPath) {super(name, columns);this.dataPath dataPath;}
}4解析数据模型
创建了model.json文件也声明了想要的元数据结构但不知道是否正确需要检验。
该文件最终会被ModelHandler处理解析JSON文件只需要使用Jackson反序列化得到的JsonRoot就是整个数据模型结构。
如果是YAML文件使用YAMLMapper对象解析即可。
数据模型配置文件解析流程
// 构建JSON的解析对象
final ObjectMapper JSON_MAPPER new ObjectMapper().configure(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES, true).configure(JsonParser.Feature.ALLOW_SINGLE_QUOTES, true).configure(JsonParser.Feature.ALLOW_COMMENTS, true);// 获取JSON文件的根节点
final JsonRoot jsonRoot JSON_MAPPER.readValue(new File(src/main/resources/model.json), JsonRoot.class);// 输出Schema对象到控制台
System.out.println(jsonRoot.schemas);将对应的配置传入 Calcite 在获取连接时直接将相关的文件传入即可。
在Calcite中传入数据模型配置文件
DriverManager.getConnection(jdbc:calcite:modelsrc/main/resources/model.json)model参数会被解析后面的值会传入ModelHandlerModelHandler通过访问者模式解析出Schema、Table等元数据信息构造出Calcite能够使用的对象初始化声明的工厂类创建Schema和Table实例并将其保存在内存中供校验器调用。
3、校验流程
1Calcite校验过程中的核心类
1.SqlOperatorTable
SqlOperatorTable是用来定义查找SQL算子SqlOperator和函数的接口这里的SQL算子是SQL解析树节点的类型是SqlNode节点的必要组成部分设定了SqlNode的类型。
它内部设定的查询操作也是与关系代数相关联的比如描述一个查询节点的算子名字叫SELECT参数包括查询的字段、查询的数据源、过滤条件以及数据组织和分析的参数。
2.SqlValidatorCatalogReader
SqlValidatorCatalogReader用来给SqlValidator提供目录Catalog信息也就是获得表、类型和Schema信息它的实现类为CalciteCatalogReader是元数据和校验器的连接桥梁。
其初始化过程需要依赖上下文所以在创建数据连接Connection时会从Connection对象里拿到上下文对象Context获取Schema信息Connection里的Schema信息就来自model.json文件。
3.RelDataTypeFactory
RelDataTypeFactory是处理数据类型的工厂类它负责SQL类型、Java类型和集合类型的创建和转化。
针对不同的接口形式Calcite支持SQL和Java两种实现SqlTypeFactoryImpl和JavaTypeFactoryImpl当然这里用户可以针对不同的情况自行扩展。
4.SqlValidator
Calcite的校验过程核心对象是SqlValidator它要承担查询计划的校验过程。
除了要依赖上述的几个核心类对象还会有本身的配置信息比如是否允许类型隐式转换、是否展开选择的列等等。
SqlValidator的构造和工作过程 // 构造SqlValidator实例Connection connection DriverManager.getConnection(jdbc:calcite:modelsrc/main/resources/model.json);CalciteServerStatement statement connection.createStatement().unwrap(CalciteServerStatement.class);CalcitePrepare.Context prepareContext statement.createPrepareContext();SqlTypeFactoryImpl factory new SqlTypeFactoryImpl(RelDataTypeSystem.DEFAULT);// SqlValidatorCatalogReader用来给SqlValidator提供目录Catalog信息也就是获得表、类型和Schema信息// 它的实现类为CalciteCatalogReader是元数据和校验器的连接桥梁。CalciteCatalogReader calciteCatalogReader new CalciteCatalogReader(prepareContext.getRootSchema(),prepareContext.getDefaultSchemaPath(),factory,new CalciteConnectionConfigImpl(new Properties()));final SqlStdOperatorTable instance SqlStdOperatorTable.instance();// Calcite的校验过程核心对象是SqlValidator它要承担查询计划的校验过程。SqlValidator validator SqlValidatorUtil.newValidator(SqlOperatorTables.chain(instance, calciteCatalogReader),calciteCatalogReader, factory, SqlValidator.Config.DEFAULT.withIdentifierExpansion(true));为了更为直观地展示 SqlValidator 的作用一条尚未校验的SQL语句可以看到其中包含多种查询子句有过滤条件、排序操作等。
其中最重要的是在查询的字段内有一个“ * ”它表示需要查询“u”表的所有字段。
校验前的SQL语句
SELECTu.*,r.permission
FROMMYSQL.sys_user AS u,CSV.sys_role AS r
WHEREu.role r.role
ORDER BYid 校验后得到的SQL语句最明显的变化就是原先“*”的位置已经被拆解开将“u”表中所有的字段都展开来看。
除了这个作用SqlValidator也完成了对所有子句中涉及的库表关系的校验包括表是否存在、字段是否正确、字段类型是否合法、表的别名是否呼应。
校验后的SQL语句
SELECTu.id,u.user_name,u.password,u.is_admin,u.role,u.created_date,u.modified_date,r.permission
FROMMYSQL.sys_user AS u,CSV.sys_role AS r
WHEREu.role r.role
ORDER BYid5.作用域
Calcite内的作用域SqlValidatorScope指SQL每个子查询的解析范围可以是解析树中的任何位置或者任何含有列的节点。
在这里一条SQL语句会被拆分成两个作用域它们之间可能存在依赖关系但是每个作用域都是一个完整的查询。 6.命名空间
Calcite中的命名空间Namespace指一个SQL查询所用的数据源这些数据源可能是表、视图、子查询等。
使用一条SQL语句同时对两张表进行查询此处就会分化出两个命名空间分别指代两个数据源“emp”和“dept”。 2校验流程
示例SQL语句
select *
from
(select u.*
,r.permission
from sys_user u, CSV.sys_role r
where u.roler.role
order by id) 1标准化SQL语句
为了简化后面的逻辑Calcite会把节点重写为标准格式具体包括SqlOrderBy、SqlDelete、SqlUpdate、SqlMerge和Values等。
SqlOrderBy和Values会被重写成SqlSelect而SqlDelete和SqlUpdate所删除和更新的数据会被封装在SqlSelect中方便后面执行。
此处也是SqlSelect算子里面也包含SqlOrderBy子查询看起来结果没什么变化但在代码层面SQL节点已经被重写了。
标准化后的SQL语句
SELECT*
FROM(SELECT*FROMsys_userORDER BYid)2注册命名空间和作用域
注册命名空间和作用域也就是将SQL语句中的命名空间和作用域提取出来存储在临时变量中。
这里会得到3个命名空间1个是sys_user这张原表另外2个是Select-Namespace表示数据来自子查询、2个作用域表示列分别出现在2个作用域范围。
这一步完成后会得到下面的SQL语句可以看到作用域已经被重写同时生成了一个变量名来代替子查询表的别名。
使用命名空间和作用域之后的SQL语句
SELECT*
FROM(SELECT*FROMsys_user AS sys_userORDER BYid) AS EXPR$03执行校验逻辑
执行校验逻辑需要调用元数据的内容通过调用各个SqlNode节点的 validate方法最终还是回到 SqlValidatorImpl 类利用上面得到的命名空间和作用域结合元数据进行校验。
对于查询语句的命名空间校验会对各个部分单独校验。
SQL语句校验的流程
// 校验数据源
validateFrom(select.getFrom(), fromType, fromScope);
// 校验过滤条件
validateWhereClause(select);
// 校验分组条件
validateGroupClause(select);
// 校验Having条件
validateHavingClause(select);
// 校验窗口函数
validateWindowClause(select);
// 校验查询条件
validateSelectList(selectItems, select, targetRowType);根据配置最终SQL语句会被展开和替换。
SQL语句被展开和替换
SELECTEXPR$0.id,EXPR$0.user_name,EXPR$0.password,EXPR$0.is_admin,EXPR$0.role,EXPR$0.created_date,EXPR$0.modified_dateFROM(SELECTsys_user.id,sys_user.user_name,sys_user.password,sys_user.is_admin,sys_user.role,sys_user.created_date,sys_user.modified_dateFROMMYSQL.sys_user AS sys_userORDER BYid) AS EXPR$04校验失败
如果语句引用的对象有误那么错误会被直接抛出来。
通过异常信息非常容易定位错误常见的错误有找不到字段、找不到UDF以及找不到表或Schema。
比如使用SQL语句 select len from sys_user在找不到表中字段 len 时通过异常链可以很清楚地看到调用过程和代码行数可以确定是因为在展开Select投影字段expandSelectExpr时找不到对应的列而失败的。
at org.apache.calcite.sql.SqlIdentifier.accept(SqlIdentifier.java:320)at .validate.SqlValidatorImpl.expandSelectExpr(SqlValidatorImpl.java:5600)at .validate.SqlValidatorImpl.expandSelectItem(SqlValidatorImpl.java:411)at .validate.SqlValidatorImpl.validateSelectList(SqlValidatorImpl.java:4205)at .validate.SqlValidatorImpl.validateSelect(SqlValidatorImpl.java:3474)at .validate.SelectNamespace.validateImpl(SelectNamespace.java:60)at .validate.AbstractNamespace.validate(AbstractNamespace.java:84)at .validate.SqlValidatorImpl.validateNamespace(SqlValidatorImpl.java:1067)at .validate.SqlValidatorImpl.validateQuery(SqlValidatorImpl.java:1041)at .SqlSelect.validate(SqlSelect.java:232)at .validate.SqlValidatorImpl.validateScopedExpression(SqlValidatorImpl.java:1016)at .validate.SqlValidatorImpl.validate(SqlValidatorImpl.java:724)Caused by: .validate.SqlValidatorException: Column len not found in any table但是有的问题并不能在校验阶段被发现。
比如使用SQL语句select * from sys_ user where id‘a’明明知道id是int类型执行肯定会报错但SQL校验的结果仅仅将字符a的类型转换为bigint做了一次SQL语句重写这样只有到执行时才能发现类型不匹配。
通过校验但是仍然会存在问题的SQL语句
SELECTsys_user.id,sys_user.user_name,sys_user.password,sys_user.is_admin,sys_user.role,sys_user.created_date,sys_user.modified_date
FROMMYSQL.sys_user AS sys_user
WHEREsys_user.id CAST(a AS BIGINT)4、元数据DDL
之前是通过文件的形式来声明元数据的这种声明方式在使用过程中非常不方便。
一般来说数据管理系统会设定DDL来对元数据进行操作这样就能够通过SQL语句来控制元数据。
然而Calcite专注的是查询和优化核心依赖里只包含查询相关的SQL语法DDL部分的功能单独写在了calcite-server模块中在calcite-core模块中包含DDL相关的SqlNode子类比如SqlDrop、SqlCreatecalcite-server模块实际上是扩展了语法文件支持了部分DDL。
首先要引入calcite-server模块依赖如果使用Maven来管理可以直接在pom.xml文件中添加对应的依赖坐标信息。
dependencygroupIdorg.apache.calcite/groupIdartifactIdcalcite-server/artifactIdversion${calcite-version}/version
/dependency然后在创建连接时指定新的语法解析工厂类这个工厂类写在了ServerDdlExecutor类里可以通过指定“PARSER_FACTORY”参数来修改使用的语法。
final Properties p new Properties();// 在配置信息中添加语法解析工厂信息
p.put(CalciteConnectionProperty.PARSER_FACTORY.camelName(), ServerDdlExecutor.class.getName() #PARSER_FACTORY);// 获取数据库连接对象并进行进一步的操作
try (final Connection conn DriverManager.getConnection(jdbc:calcite:, p)){...}经过配置就可以用SQL创建数据模式、表、视图、函数和数据类型等实体信息。
用SQL创建实体信息
final Statement s conn.createStatement();
// 创建数据模式
s.execute(CREATE SCHEMA s);
// 创建表
s.executeUpdate(CREATE TABLE s.t(age int, name varchar(10)));
// 插入数据
s.executeUpdate(INSERT INTO s.t values(18,jimo),(20,hehe));
ResultSet rs s.executeQuery(SELECT count(*) FROM s.t);
rs.next();
assertEquals(2, rs.getInt(1));// 创建视图
s.executeUpdate(CREATE VIEW v1 AS select name from s.t);
rs s.executeQuery(SELECT * FROM v1);
rs.next();
assertEquals(jimo, rs.getString(1));// 创建数据类型
s.executeUpdate(CREATE TYPE vc10 as varchar(10));
s.executeUpdate(CREATE TABLE t1(age int, name vc10));// 删除视图和注销数据类型
s.executeUpdate(DROP VIEW v1);
s.executeUpdate(DROP TYPE vc10);不过Calcite对这部分并不重视这几个DDL语法并不完善。
5、示例配置文件定义元数据并解析校验
1配置文件-Model.json
resources/model.json
{version: 1.0,defaultSchema: MYSQL,schemas: [{name: MYSQL,type: custom,factory: cn.com.ptpress.cdm.schema.mysql.MysqlSchemaFactory,operand: {url: jdbc:mysql://localhost:3306/db_cdm?useSSLfalse,user: root,pass: root},functions: [{name: test,className: cn.com.ptpress.cdm.schema.function.MyFunction,methodName: test}],tables: [{name: v_num,type: view,sql: select 12*3,path: [MYSQL],modifiable: false}]},{name: JDBC_MYSQL,type: jdbc,jdbcDriver: com.mysql.jdbc.Driver,jdbcUrl: jdbc:mysql://localhost:3306/db_cdm?useSSLfalse,jdbcUser: root,jdbcPassword: root,jdbcSchema: db_cdm},{name: CSV,type: map,tables: [{name: sys_role,type: custom,factory: cn.com.ptpress.cdm.schema.csv.CsvTableFactory,operand: {colPath: src/main/resources/sys_role/col_type.json,dataPath: src/main/resources/sys_role/data.csv}},{name: v_user_role,type: view,sql: select 12*3,path: [CSV],modifiable: false}],functions: [{name: test,className: cn.com.ptpress.cdm.schema.function.MyFunction,methodName: test}],types: [{name: C,type: varchar,attributes: [{name: vc,type: varchar}]}]}]
}2CSV 数据文件和元数据文件
1.元数据文件resources/sys_role/col_type.json
[{name: role,type: varchar},{name: permission,type: integer}
]2.数据文件resources/sys_role/data.csv
role,permission
admin,111111
user,000011
nobody,000000
dev,000111
test,0010113CSV Schema 和 解析工厂
1.CsvTableFactory
import cn.com.ptpress.cdm.schema.common.CdmColumn;
import com.fasterxml.jackson.core.type.TypeReference;
import lombok.SneakyThrows;
import org.apache.calcite.rel.type.RelDataType;
import org.apache.calcite.schema.SchemaPlus;
import org.apache.calcite.schema.TableFactory;import java.io.File;
import java.util.List;
import java.util.Map;import static cn.com.ptpress.cdm.schema.util.JsonUtil.JSON_MAPPER;/*** 创建表的工厂类*/
public class CsvTableFactory implements TableFactoryCsvTable {/*** 构造对应的表对象——CsvTable*/OverrideSneakyThrowspublic CsvTable create(SchemaPlus schema, String name, Map operand, RelDataType rowType) {// 1. 获取列信息final String colTypePath String.valueOf(operand.get(colPath));final ListCdmColumn columns JSON_MAPPER.readValue(new File(colTypePath),new TypeReferenceListCdmColumn() {});// 2. 将列信息和数据信息封装到CsvTable对象中并回传return new CsvTable(name, columns, String.valueOf(operand.get(dataPath)));}
}2.CsvTable
package cn.com.ptpress.cdm.schema.csv;import cn.com.ptpress.cdm.schema.common.CdmColumn;
import cn.com.ptpress.cdm.schema.common.CdmTable;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;import java.util.List;/*** CsvTable的扩展实现*/
Data
EqualsAndHashCode(callSuper true)
NoArgsConstructor
public class CsvTable extends CdmTable {/*** 数据路径*/private String dataPath;/*** 构造方法*/public CsvTable(String name, ListCdmColumn columns, String dataPath) {super(name, columns);this.dataPath dataPath;}
}3.CdmTable
package cn.com.ptpress.cdm.schema.common;import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import org.apache.calcite.rel.type.RelDataType;
import org.apache.calcite.rel.type.RelDataTypeFactory;
import org.apache.calcite.schema.impl.AbstractTable;
import org.apache.calcite.sql.type.SqlTypeName;
import org.apache.calcite.util.Pair;import java.util.ArrayList;
import java.util.List;/*** 表元数据内部信息的实现逻辑*/
Data
EqualsAndHashCode(callSuper true)
AllArgsConstructor
NoArgsConstructor
public class CdmTable extends AbstractTable {/*** 表名*/private String name;/*** 表的列*/private ListCdmColumn columns;Overridepublic RelDataType getRowType(RelDataTypeFactory typeFactory) {ListString names new ArrayList();ListRelDataType types new ArrayList();for (CdmColumn c : columns) {names.add(c.getName());RelDataType sqlType typeFactory.createSqlType(SqlTypeName.get(c.getType().toUpperCase()));types.add(sqlType);}return typeFactory.createStructType(Pair.zip(names, types));}
}4.CdmColumn
package cn.com.ptpress.cdm.schema.common;import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.apache.calcite.sql.type.SqlTypeName;/*** 列元数据的创建逻辑*/
Data
AllArgsConstructor
NoArgsConstructor
public class CdmColumn {/*** 列名*/private String name;/*** 列类型,可以使用calcite扩展的sql类型{link SqlTypeName}*/private String type;
}4MySQL Schema 和 解析工厂
1.MysqlSchema
package cn.com.ptpress.cdm.schema.mysql;import cn.com.ptpress.cdm.schema.common.CdmTable;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.apache.calcite.schema.impl.AbstractSchema;import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;/**这个类是用来封装MySQL的Schema信息的*/
Data
EqualsAndHashCode(callSuper true)
AllArgsConstructor
public class MysqlSchema extends AbstractSchema {// Schmea名称private String name;// Schema下的表private ListCdmTable tables;Overrideprotected MapString, org.apache.calcite.schema.Table getTableMap() {return tables.stream().collect(Collectors.toMap(CdmTable::getName, t - t));}
}2.MysqlSchemaFactory
package cn.com.ptpress.cdm.schema.mysql;import cn.com.ptpress.cdm.schema.common.CdmColumn;
import cn.com.ptpress.cdm.schema.common.CdmTable;
import org.apache.calcite.schema.Schema;
import org.apache.calcite.schema.SchemaFactory;
import org.apache.calcite.schema.SchemaPlus;import java.sql.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;/*** 构造MySQL的Schema对象的核心方法*/
public class MysqlSchemaFactory implements SchemaFactory {Overridepublic Schema create(SchemaPlus parentSchema, String name, MapString, Object operand) {// 1. 使用try with表达式来获取数据连接以保证try结构体结束时数据连接自动关闭try (final Connection conn DriverManager.getConnection(String.valueOf(operand.get(url)),String.valueOf(operand.get(user)), String.valueOf(operand.get(pass)))) {// 2. 利用数据连接对象构建表达式对象final Statement stmt conn.createStatement();final ResultSet rs stmt.executeQuery(SHOW TABLES);// 3. 使用循环的方式将获取的数据相关信息放置到表元数据列表中ListCdmTable tables new ArrayList(8);while (rs.next()) {final String table rs.getString(1);tables.add(new CdmTable(table, getColumns(conn, table)));}// 4. 最终将获取的Schema信息返回return new MysqlSchema(name, tables);} catch (SQLException e) {throw new RuntimeException(e);}}// 获取列信息private ListCdmColumn getColumns(Connection conn, String table) throws SQLException {final Statement stmt conn.createStatement();final ResultSet rs stmt.executeQuery(DESC table);ListCdmColumn columns new ArrayList();while (rs.next()) {columns.add(new CdmColumn(rs.getString(Field),typeMap(pureType(rs.getString(Type)))));}return columns;}/*** mysql 有的类型和 calcite不一样需要修改下别名*/private String typeMap(String type) {switch (type.toLowerCase()) {case int:return integer;default:return type;}}/*** 传入的type含有类型长度如 bigint(20), varchar(258)* 需要去掉括号*/private String pureType(String type) {final int i type.indexOf(();return i 0 ? type.substring(0, i) : type;}
}5自定义函数
1.MyFunction
package cn.com.ptpress.cdm.schema.function;public class MyFunction {public MyFunction() {}/*** 计算1的个数*/public int myLen(int permission) {int c 0;for (; permission 0; c) {permission (permission - 1);}return c;}public String test(String in) {return hh;}
}6JSON 工具类
package cn.com.ptpress.cdm.schema.util;import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.ObjectMapper;public class JsonUtil {public final static ObjectMapper JSON_MAPPER new ObjectMapper().configure(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES, true).configure(JsonParser.Feature.ALLOW_SINGLE_QUOTES, true).configure(JsonParser.Feature.ALLOW_COMMENTS, true);
}