5. 想在代码中验证sql的正确性?
1. 简介
我们在平时的开发中可能会遇到需要验证一下sql是否正确,也就是需要check一下sql。
判断sql是否正确一般包含一下几点:
1. sql中使用的列是否存在
2. sql语法是否正确
3. sql中使用到的操作符/函数是否存在,有没有正确的使用
我们可以用以下的sql示例来探究一下使用calcite如何校验sql
select
u.sex,
max(u.age)
from user u
inner join role r on u.role_id = r.id
where r.id = 1
group by u.sex
2. Maven
<dependency>
<groupId>org.apache.calcite</groupId>
<artifactId>calcite-core</artifactId>
<version>1.37.0</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.33</version>
</dependency>
3. 验证
首先 在calcite中验证sql的正确性是通过使用calcite中的SqlValidator类进行校验的,但SqlValidator是一个接口, 通常是通过SqlValidatorUtil.newValidator(...)方法进行实例化的, 如下:
public static SqlValidatorWithHints newValidator(SqlOperatorTable opTab,
SqlValidatorCatalogReader catalogReader,
RelDataTypeFactory typeFactory,
SqlValidator.Config config)
{
return new SqlValidatorImpl(opTab, catalogReader, typeFactory, config);
}
- SqlOperatorTable:用来提供sql验证所需的操作符(SqlOperator)和函数(SqlFunction)例如:>, <, = 或max(),in()
- SqlValidatorCatalogReader:用来提供验证所需的元数据信息 例如: schema, table, column
- RelDataTypeFactory:处理数据类型的工厂类,用来提供类型、java类型和集合类型的创建和转化。针对不同的接口形式,calcite支持sql和java两种实现(SqlTypeFactoryImpl和JavaTypeFactoryImpl),当然这里用户可以针对不同的情况自行扩展
- SqlValidator.Config:可以自定义一些配置,例如是否开启类型隐式转换、是否开启 SQL 重写等等
3.1 创建SqlValidator
创建SqlValidator之前需要先实例化上述的四个入参对象,好在calcite提供了对应属性的默认实现,使得我们能很方便的创建SqlValidator对象
SqlValidator validator = SqlValidatorUtil.newValidator(
SqlStdOperatorTable.instance(),
catalogReader, // catalog信息需要自己手动创建
new SqlTypeFactoryImpl(RelDataTypeSystem.DEFAULT),
SqlValidator.Config.DEFAULT);
这里除了SqlValidatorCatalogReader对象需要额外的自己创建,如果没有其它特别的需求,我们都可以使用calcite提供的默认实现。
我们这里着重讲一下SqlValidatorCatalogReader对象如何创建
首先SqlValidatorCatalogReader使用来提供验证所需的catalog信息的,那我们就需要提供一下catalog信息(因为calcite需要做元数据的验证,比如表,字段是否存在,不提供元数据calcite谈何验证)
创建SqlValidatorCatalogReader有两种方式:
通过数据源的方式,也就是我们知道执行sql的server信息,把连接信息给calcite,让calcite自己去获取元信息并进行验证,也就是这个时候需要去连接db才能进行验证
@SneakyThrows
private static CalciteCatalogReader createCatalogReaderWithDataSource() {
Connection connection = DriverManager.getConnection("jdbc:calcite:");
CalciteConnection calciteConnection = connection.unwrap(CalciteConnection.class);
SchemaPlus rootSchema = calciteConnection.getRootSchema();
DataSource dataSource = JdbcSchema.dataSource(
"jdbc:mysql://localhost:3306/test",
"com.mysql.cj.jdbc.Driver",
"root",
"123456"
);
JdbcSchema jdbcSchema = JdbcSchema.create(rootSchema, "my_mysql", dataSource, null, null);
rootSchema.add("my_mysql", jdbcSchema);
calciteConnection.setSchema("my_mysql");
CalciteServerStatement statement = connection.createStatement().unwrap(CalciteServerStatement.class);
CalcitePrepare.Context prepareContext = statement.createPrepareContext();
SqlTypeFactoryImpl factory = new SqlTypeFactoryImpl(RelDataTypeSystem.DEFAULT); return new CalciteCatalogReader(
prepareContext.getRootSchema(),
prepareContext.getDefaultSchemaPath(),
factory,
calciteConnection.config());
}
手动添加catalog信息,不需要连库就能验证
private static CalciteCatalogReader createCatalogReaderWithMeta() {
SchemaPlus rootSchema = Frameworks.createRootSchema(true);
RelDataTypeSystem relDataTypeSystem = RelDataTypeSystem.DEFAULT;
RelDataTypeFactory typeFactory = new SqlTypeFactoryImpl(relDataTypeSystem);
rootSchema.add("user", new AbstractTable() {
@Override
public RelDataType getRowType(RelDataTypeFactory typeFactory) {
RelDataTypeFactory.Builder builder = typeFactory.builder();
builder.add("id", new BasicSqlType(relDataTypeSystem, SqlTypeName.INTEGER));
builder.add("name", new BasicSqlType(relDataTypeSystem, SqlTypeName.VARCHAR));
builder.add("age", new BasicSqlType(relDataTypeSystem, SqlTypeName.INTEGER));
builder.add("sex", new BasicSqlType(relDataTypeSystem, SqlTypeName.VARCHAR));
builder.add("role_id", new BasicSqlType(relDataTypeSystem, SqlTypeName.INTEGER));
return builder.build();
}
});
rootSchema.add("role", new AbstractTable() {
@Override
public RelDataType getRowType(RelDataTypeFactory typeFactory) {
RelDataTypeFactory.Builder builder = typeFactory.builder();
builder.add("id", new BasicSqlType(relDataTypeSystem, SqlTypeName.INTEGER));
builder.add("name", new BasicSqlType(relDataTypeSystem, SqlTypeName.VARCHAR));
return builder.build();
}
});
CalciteConnectionConfig connectionConfig = CalciteConnectionConfig.DEFAULT; return new CalciteCatalogReader(
CalciteSchema.from(rootSchema),
CalciteSchema.from(rootSchema).path(null),
typeFactory,
connectionConfig);
}
ok,至此创建
SqlValidator所需的参数都已备齐,但是当执行验证方法的时候所需的参数并不是sql字符串而是SqlValidator.validate(SqlNode topNode), 那么SqlNode又要怎么创建 ?
3.2 解析Sql
SqlNode 顾名思义就是sql节点对象,直接通过SqlParser对象创建,如下
SqlParser.Config config = SqlParser.config()
// 解析工厂
.withParserFactory(SqlParserImpl.FACTORY)
// 也可以直接设置为对应数据库的词法分析器
// .withLex(Lex.MYSQL)
// 不区分大小写
.withCaseSensitive(false)
// 引用符号为反引号
.withQuoting(Quoting.BACK_TICK)
// 未加引号的标识符在解析时不做处理
.withUnquotedCasing(Casing.UNCHANGED)
// 加引号的标识符在解析时不做处理
.withQuotedCasing(Casing.UNCHANGED)
// 使用默认的语法规则
.withConformance(SqlConformanceEnum.DEFAULT);
// sql解析器
final SqlParser parser = SqlParser.create(SQL, config);
// 将sql转换为calcite的SqlNode
SqlNode sqlNode = parser.parseQuery();
3.3 执行验证
通过上述的步骤 我们已经能创建SqlValidator对象并且能创建其验证时需要的SqlNode对象,其实很简单, 只要验证时不报错,即sql是正确的
try{
// 校验 sql
validator.validate(sqlNode);
log.info("sql is valid");
}
catch (Exception e) {
log.error("sql is invalid", e);
}
4. 完整验证代码
4.1 通过SqlValidator进行验证
package com.ldx.calcite;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.calcite.adapter.jdbc.JdbcSchema;
import org.apache.calcite.avatica.util.Casing;
import org.apache.calcite.avatica.util.Quoting;
import org.apache.calcite.config.CalciteConnectionConfig;
import org.apache.calcite.jdbc.CalciteConnection;
import org.apache.calcite.jdbc.CalcitePrepare;
import org.apache.calcite.jdbc.CalciteSchema;
import org.apache.calcite.prepare.CalciteCatalogReader;
import org.apache.calcite.rel.type.RelDataType;
import org.apache.calcite.rel.type.RelDataTypeFactory;
import org.apache.calcite.rel.type.RelDataTypeSystem;
import org.apache.calcite.schema.SchemaPlus;
import org.apache.calcite.schema.impl.AbstractTable;
import org.apache.calcite.server.CalciteServerStatement;
import org.apache.calcite.sql.SqlNode;
import org.apache.calcite.sql.fun.SqlStdOperatorTable;
import org.apache.calcite.sql.parser.SqlParser;
import org.apache.calcite.sql.parser.impl.SqlParserImpl;
import org.apache.calcite.sql.type.BasicSqlType;
import org.apache.calcite.sql.type.SqlTypeFactoryImpl;
import org.apache.calcite.sql.type.SqlTypeName;
import org.apache.calcite.sql.validate.SqlConformanceEnum;
import org.apache.calcite.sql.validate.SqlValidator;
import org.apache.calcite.sql.validate.SqlValidatorUtil;
import org.apache.calcite.tools.Frameworks;
import org.junit.jupiter.api.Test;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.DriverManager;
@Slf4j
public class SqlValidatorTest {
private static final String SQL = "select u.sex, max(u.age) from `user` u inner join role r on u.role_id = r.id where r.id = 1 group by u.sex";
@Test
@SneakyThrows
public void given_sql_and_meta_then_validate_sql() {
SqlParser.Config config = SqlParser.config()
// 解析工厂
.withParserFactory(SqlParserImpl.FACTORY)
// 也可以直接设置为对应数据库的词法分析器
// .withLex(Lex.MYSQL)
// 不区分大小写
.withCaseSensitive(false)
// 引用符号为反引号
.withQuoting(Quoting.BACK_TICK)
// 未加引号的标识符在解析时不做处理
.withUnquotedCasing(Casing.UNCHANGED)
// 加引号的标识符在解析时不做处理
.withQuotedCasing(Casing.UNCHANGED)
// 使用默认的语法规则
.withConformance(SqlConformanceEnum.DEFAULT);
// sql解析器
final SqlParser parser = SqlParser.create(SQL, config);
// 将SQL转换为Calcite的SqlNode
SqlNode sqlNode = parser.parseQuery();
// 创建 SqlValidator 来进行校验
SqlValidator validator = SqlValidatorUtil.newValidator(
SqlStdOperatorTable.instance(),
// 使用直接提供元信息的方式
createCatalogReaderWithMeta(),
// 使用提供数据源的方式
//createCatalogReaderWithDataSource(),
new SqlTypeFactoryImpl(RelDataTypeSystem.DEFAULT),
SqlValidator.Config.DEFAULT);
try{
// 校验 sql
validator.validate(sqlNode);
log.info("sql is valid");
}
catch (Exception e) {
log.error("sql is invalid", e);
}
}
private static CalciteCatalogReader createCatalogReaderWithMeta() {
SchemaPlus rootSchema = Frameworks.createRootSchema(true);
RelDataTypeSystem relDataTypeSystem = RelDataTypeSystem.DEFAULT;
RelDataTypeFactory typeFactory = new SqlTypeFactoryImpl(relDataTypeSystem);
rootSchema.add("user", new AbstractTable() {
@Override
public RelDataType getRowType(RelDataTypeFactory typeFactory) {
RelDataTypeFactory.Builder builder = typeFactory.builder();
builder.add("id", new BasicSqlType(relDataTypeSystem, SqlTypeName.INTEGER));
builder.add("name", new BasicSqlType(relDataTypeSystem, SqlTypeName.VARCHAR));
builder.add("age", new BasicSqlType(relDataTypeSystem, SqlTypeName.INTEGER));
builder.add("sex", new BasicSqlType(relDataTypeSystem, SqlTypeName.VARCHAR));
builder.add("role_id", new BasicSqlType(relDataTypeSystem, SqlTypeName.INTEGER));
return builder.build();
}
});
rootSchema.add("role", new AbstractTable() {
@Override
public RelDataType getRowType(RelDataTypeFactory typeFactory) {
RelDataTypeFactory.Builder builder = typeFactory.builder();
builder.add("id", new BasicSqlType(relDataTypeSystem, SqlTypeName.INTEGER));
builder.add("name", new BasicSqlType(relDataTypeSystem, SqlTypeName.VARCHAR));
return builder.build();
}
});
CalciteConnectionConfig connectionConfig = CalciteConnectionConfig.DEFAULT;
return new CalciteCatalogReader(
CalciteSchema.from(rootSchema),
CalciteSchema.from(rootSchema).path(null),
typeFactory,
connectionConfig);
}
@SneakyThrows
private static CalciteCatalogReader createCatalogReaderWithDataSource() {
Connection connection = DriverManager.getConnection("jdbc:calcite:");
CalciteConnection calciteConnection = connection.unwrap(CalciteConnection.class);
SchemaPlus rootSchema = calciteConnection.getRootSchema();
DataSource dataSource = JdbcSchema.dataSource(
"jdbc:mysql://localhost:3306/test",
"com.mysql.cj.jdbc.Driver",
"root",
"123456"
);
JdbcSchema jdbcSchema = JdbcSchema.create(rootSchema, "my_mysql", dataSource, null, null);
rootSchema.add("my_mysql", jdbcSchema);
calciteConnection.setSchema("my_mysql");
CalciteServerStatement statement = connection.createStatement().unwrap(CalciteServerStatement.class);
CalcitePrepare.Context prepareContext = statement.createPrepareContext();
SqlTypeFactoryImpl factory = new SqlTypeFactoryImpl(RelDataTypeSystem.DEFAULT);
return new CalciteCatalogReader(
prepareContext.getRootSchema(),
prepareContext.getDefaultSchemaPath(),
factory,
calciteConnection.config());
}
}
4.2 使用Planner对象进行验证
其实Planner.validate方法其底层使用的还是SqlValidator对象进行验证
package com.ldx.calcite;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.calcite.avatica.util.Casing;
import org.apache.calcite.avatica.util.Quoting;
import org.apache.calcite.rel.type.RelDataType;
import org.apache.calcite.rel.type.RelDataTypeFactory;
import org.apache.calcite.rel.type.RelDataTypeSystem;
import org.apache.calcite.schema.SchemaPlus;
import org.apache.calcite.schema.impl.AbstractTable;
import org.apache.calcite.sql.SqlNode;
import org.apache.calcite.sql.parser.SqlParser;
import org.apache.calcite.sql.parser.impl.SqlParserImpl;
import org.apache.calcite.sql.type.BasicSqlType;
import org.apache.calcite.sql.type.SqlTypeName;
import org.apache.calcite.sql.validate.SqlConformanceEnum;
import org.apache.calcite.tools.FrameworkConfig;
import org.apache.calcite.tools.Frameworks;
import org.apache.calcite.tools.Planner;
import org.apache.calcite.tools.ValidationException;
import org.junit.jupiter.api.Test;
@Slf4j
public class SqlValidatorWithPlannerTest {
private static final String SQL = "select u.sex, max(u.age) from `user` u inner join role r on u.role_id = r.id where r.id = 1 group by u.sex";
@Test
@SneakyThrows
public void given_sql_and_meta_then_validate_sql() {
// 创建Calcite配置
FrameworkConfig config = createFrameworkConfig();
// 创建Planner
Planner planner = Frameworks.getPlanner(config);
// 解析SQL
final SqlNode parse = planner.parse(SQL);
try {
// 获取SqlValidator进行校验
planner.validate(parse);
log.info("sql is valid");
} catch (ValidationException e) {
log.error("sql is invalid", e);
}
}
private static FrameworkConfig createFrameworkConfig() {
SchemaPlus rootSchema = Frameworks.createRootSchema(true);
RelDataTypeSystem relDataTypeSystem = RelDataTypeSystem.DEFAULT;
rootSchema.add("user", new AbstractTable() {
@Override
public RelDataType getRowType(RelDataTypeFactory typeFactory) {
RelDataTypeFactory.Builder builder = typeFactory.builder();
builder.add("id", new BasicSqlType(relDataTypeSystem, SqlTypeName.INTEGER));
builder.add("name", new BasicSqlType(relDataTypeSystem, SqlTypeName.VARCHAR));
builder.add("age", new BasicSqlType(relDataTypeSystem, SqlTypeName.INTEGER));
builder.add("sex", new BasicSqlType(relDataTypeSystem, SqlTypeName.VARCHAR));
builder.add("role_id", new BasicSqlType(relDataTypeSystem, SqlTypeName.INTEGER));
return builder.build();
}
});
rootSchema.add("role", new AbstractTable() {
@Override
public RelDataType getRowType(RelDataTypeFactory typeFactory) {
RelDataTypeFactory.Builder builder = typeFactory.builder();
builder.add("id", new BasicSqlType(relDataTypeSystem, SqlTypeName.INTEGER));
builder.add("name", new BasicSqlType(relDataTypeSystem, SqlTypeName.VARCHAR));
return builder.build();
}
});
SqlParser.Config config = SqlParser.config()
.withParserFactory(SqlParserImpl.FACTORY)
.withQuoting(Quoting.BACK_TICK)
.withCaseSensitive(false)
.withUnquotedCasing(Casing.UNCHANGED)
.withQuotedCasing(Casing.UNCHANGED)
.withConformance(SqlConformanceEnum.DEFAULT);
return Frameworks
.newConfigBuilder()
.defaultSchema(rootSchema)
.parserConfig(config)
.build();
}
}
5. 想在代码中验证sql的正确性?的更多相关文章
- C#中验证sql语句是否正确(不执行语句)
SET PARSEONLY检查每个 Transact-SQL 语句的语法并返回任何错误消息,但不编译和执行语句.SET PARSEONLY { ON | OFF }当 SET PARSEONLY 为 ...
- 关于在Java代码中写Sql语句需要注意的问题
最近做程序,时不时需要自己去手动将sql语句直接写入到Java代码中,写入sql语句时,需要注意几个小问题. 先看我之前写的几句简单的sql语句,自以为没有问题,但是编译直接报错. String st ...
- DevExpress后置代码中初始化SQL数据源的方法
//初始化SQL数据源的提供者和连接字符串 函数 OK public virtual void InitSqlDataSource_ConStr(SqlDataSource sql_ds) { Con ...
- PHP查询登录中的sql注入
---------------------------------------------------------------------------------------------------- ...
- 项目中通过单元测试代码中的spring事务是否起作用
今儿没事,想对代码中事务进行测试,于是乎就创建了一个单元测试进行测试,发现在方法中加上@Transactional注解后,发现在想数据库中插入数据时,代码执行成功,但数据库中却没有数据,于是各种检查, ...
- easyui datagrid 禁止选中行 EF的增删改查(转载) C# 获取用户IP地址(转载) MVC EF 执行SQL语句(转载) 在EF中执行SQL语句(转载) EF中使用SQL语句或存储过程 .net MVC使用Session验证用户登录 PowerDesigner 参照完整性约束(转载)
easyui datagrid 禁止选中行 没有找到可以直接禁止的属性,但是找到两个间接禁止的方式. 方式一: //onClickRow: function (rowIndex, rowData) ...
- 在 Java 代码中对 Kerberos 主体进行身份验证
转载请注明出处:http://www.cnblogs.com/xiaodf/ 本文举例说明如何使用 org.apache.hadoop.security.UserGroupInformation 类在 ...
- SQL语句在查询分析器中可以执行,代码中不能执行
问题:SQL语句在查询分析器中可以执行,代码中不能执行 解答:sql中包含数据库的关键字,将关键字用[]括起来,可以解决. 后记:建数据库的时候尽量避免使用关键字. 例子: sql.Format(&q ...
- jQuery Validate 表单验证插件----通过name属性来关联字段来验证,改变默认的提示信息,将校验规则写到 js 代码中
一.下载依赖包 网盘下载:https://yunpan.cn/cryvgGGAQ3DSW 访问密码 f224 二. 添加一个另外一个插件jquery.validate.messages_cn.js. ...
- 使用mongo-java-driver3.0.2.jar和mongodb3.0在java代码中的用户验证4
以下是使用mongo-java-driver3.0.2.jar和mongodb3.0.4在java代码中的用户验证: ServerAddress sa = new ServerAddress(host ...
随机推荐
- Business Object 开发
一 什么是BO BO(Business Object),封装在数据库之上,用于直接操作数据(增.删.改.查) 针对不同的BO,在安装目录下有对应的DLL文件,其中封装了BO各式针对具体的业务的方法, ...
- git 忽略某些文件
如果git项目里没有 '.gitignore' 文件,则需要执行下面的操作,生成一个 生成 '.gitignore' 文件 1. git bash 打开git窗口 执行:touch .gitign ...
- Flutter TextField设置值后光标位置偏移
Flutter TextField设置值后光标位置偏移 一般用controller设置值是这样设置的 TextEditingController controller = TextEditingCon ...
- 【C#】【平时作业】习题-6-静态成员
习题-6静态成员 一.概念题 1. 什么是静态成员 被static修饰的成员,叫做静态成员.静态成员是属于类的.通过类名直接访问. 当类第一次被访问的时候,就会将这个类下面的所有的静态成员创建在内存当 ...
- 【MyBatis】学习笔记15:通过分步查询解决一对多或多对多问题
目录 对象 SmbmsProvider.java SmbmsBill.java 接口 providerMapper.java orderMapper.java 映射文件 providerMapper. ...
- 使用OpenSSL创建生成CA证书、服务器、客户端证书及密钥
说明: 对于SSL单向认证:服务器需要CA证书.server证书.server私钥,客户端需要CA证. 对于SSL双向认证:服务器需要CA证书.server证书.server私钥,客户端需要CA证书, ...
- 实践解决:IDEA2022版本创建Maven项目时没有出现src目录
问题:IDEA创建Maven项目时没有出现src目录 创建Maven项目 新版本的IDEA创建是选用的是Maven Archetype,选择这个也是和Maven一样的.按照这样流程创建完成之后的的架构 ...
- Qt/C++音视频开发74-合并标签图形/生成yolo运算结果图形/文字和图形合并成一个/水印滤镜
一.前言 在使用yolo做人工智能运算后,运算结果除了一个方框,还可能需要增加文字显示在对应方框上,以便标记是何种物体,比如显示是人还是动物,或者还有可能追踪人员,显示该人员的姓名.这种应用场景非常普 ...
- Qt交叉编译整理的几点说明
关于交叉编译,对于初学者来说是个极难跨过去的砍(一旦跨过去了,以后遇到需要交叉编译的时候都是顺水推舟.信手拈来.),因为需要搭建交叉编译环境,好在现在厂家提供的板子基本上都是测试好的环境,尤其是提供的 ...
- Qt音视频开发9-ffmpeg录像存储
一.前言 上一篇文章写道直接将视频流保存裸流到文件,尽管裸流文件有一定的好处,但是 毕竟大部分用户需要的不是裸流而是MP4视频文件,所以需要将视频流保存成MP4文件,毕竟电脑上的播放器包括默认的播放器 ...