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 ...
随机推荐
- oracle客户端安装先决条件检查出现PRVF-7531错误
场景:在局域网内,远程一台客户机进行客户端安装 度娘后,说一般情况下,由于操作系统未开启默认共享,导致Oracle无法检查环境的可用性. 查看server服务正常开启. 通过net share将c进行 ...
- Mysql的个人习惯
一定要有主键 一般使用的innodb引擎中会根据主健创建聚簇索引,这种方式会使得数据排列的更连续,减少随机IO 关于数据长度 考虑数据范围,够用的前提下,数据长度是越简单越好,数据类型是越小越好. 尽 ...
- jmeter使用jdbc连接SQL server,执行SQL报错处理
前置环境参数:jdk-8u391-windows-x64,驱动:sqljdbc4.jar 备注:这是解决后的截图,将就用 问题一:使用jmeter5.5,使用jdbc连接SQL server,执行SQ ...
- PDFSharp - Graphics
PDFSharp - Graphics Graphics - PDFsharp and MigraDoc Wiki 所有的 Graphics 类型都设计成模仿来自 System.Drawing 命名空 ...
- 实用干货分享(4)- 分布式金融PaaS容器化部署实战
编辑 一.学习链接 http://www.itmuch.com/docker/00-docker-lession-index/ 二.安装步骤 sudo yum install -y yum-ut ...
- java.time 的纪年方式
Date date = new Date(); Instant instant = date.toInstant(); Chronology chronology = HijrahChronology ...
- 【NAS】绿联NAS+极狐Gitlab+1Panel
1. 准备域名 例如我的 ???.mllt.cc 2. 内网穿透 我使用的Natfrp(https://www.natfrp.com/tunnel/) 创建HTTP隧道(对应端口10080)创建HTT ...
- 【网站搭建】开源社区Flarum搭建记录
环境 服务器系统:腾讯云 OpenCloudOS 宝塔版本:免费版8.0.1 Nginx:1.24.0 MySQL:5.7.42 PHP:8.1.21 萌狼蓝天 2023年8月7日 PHP设置 1.安 ...
- linux命令系列 sudo apt-get update和upgrade的区别
入门linux的同志,刚开始最迫切想知道的,大概一个是中文输入法,另一个就是怎么安装软件.本文主要讲一下LINUX安装软件方面的特点.在windows下安装软件,我们只需要有EXE文件,然后双击,下一 ...
- 龙哥量化:什么是ZXNH直线拟合指标?ZXNH信号漂移,未来函数检测不到, 函数列表没有,大坑哦哦哦
如果您需要代写技术指标公式, 请联系我. 龙哥QQ:591438821 龙哥微信:Long622889 这个函数太坑, 我也不明白原理是什么, 是未来函数,信号会漂移, zxnh的值只有0和1,下面一 ...