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有两种方式:

  1. 通过数据源的方式,也就是我们知道执行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());
    }
  2. 手动添加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的正确性?的更多相关文章

  1. C#中验证sql语句是否正确(不执行语句)

    SET PARSEONLY检查每个 Transact-SQL 语句的语法并返回任何错误消息,但不编译和执行语句.SET PARSEONLY { ON | OFF }当 SET PARSEONLY 为 ...

  2. 关于在Java代码中写Sql语句需要注意的问题

    最近做程序,时不时需要自己去手动将sql语句直接写入到Java代码中,写入sql语句时,需要注意几个小问题. 先看我之前写的几句简单的sql语句,自以为没有问题,但是编译直接报错. String st ...

  3. DevExpress后置代码中初始化SQL数据源的方法

    //初始化SQL数据源的提供者和连接字符串 函数 OK public virtual void InitSqlDataSource_ConStr(SqlDataSource sql_ds) { Con ...

  4. PHP查询登录中的sql注入

    ---------------------------------------------------------------------------------------------------- ...

  5. 项目中通过单元测试代码中的spring事务是否起作用

    今儿没事,想对代码中事务进行测试,于是乎就创建了一个单元测试进行测试,发现在方法中加上@Transactional注解后,发现在想数据库中插入数据时,代码执行成功,但数据库中却没有数据,于是各种检查, ...

  6. easyui datagrid 禁止选中行 EF的增删改查(转载) C# 获取用户IP地址(转载) MVC EF 执行SQL语句(转载) 在EF中执行SQL语句(转载) EF中使用SQL语句或存储过程 .net MVC使用Session验证用户登录 PowerDesigner 参照完整性约束(转载)

    easyui datagrid 禁止选中行   没有找到可以直接禁止的属性,但是找到两个间接禁止的方式. 方式一: //onClickRow: function (rowIndex, rowData) ...

  7. 在 Java 代码中对 Kerberos 主体进行身份验证

    转载请注明出处:http://www.cnblogs.com/xiaodf/ 本文举例说明如何使用 org.apache.hadoop.security.UserGroupInformation 类在 ...

  8. SQL语句在查询分析器中可以执行,代码中不能执行

    问题:SQL语句在查询分析器中可以执行,代码中不能执行 解答:sql中包含数据库的关键字,将关键字用[]括起来,可以解决. 后记:建数据库的时候尽量避免使用关键字. 例子: sql.Format(&q ...

  9. jQuery Validate 表单验证插件----通过name属性来关联字段来验证,改变默认的提示信息,将校验规则写到 js 代码中

    一.下载依赖包 网盘下载:https://yunpan.cn/cryvgGGAQ3DSW  访问密码 f224 二. 添加一个另外一个插件jquery.validate.messages_cn.js. ...

  10. 使用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 ...

随机推荐

  1. Pytorch 手写数字识别 深度学习基础分享

    本篇是一次内部分享,给项目开发的同事分享什么是深度学习.用最简单的手写数字识别做例子,讲解了大概的原理. 手写数字识别 展示首先数字识别项目的使用.项目实现过程: 训练出模型 准备html手写板 fl ...

  2. Pytest接口自动化测试框架Python自动化测试开发

    一.引言 在软件开发过程中,接口测试是确保软件各个组件之间数据传输和功能交互正常工作的重要环节.通过接口测试,可以提高软件的整体质量和稳定性.Pytest是一个流行的Python自动化测试框架,提供了 ...

  3. 【杂谈】服务端能同时处理多少个 Socket 连接?背后的资源与限制分析

    一个服务端进程能同时连接多少个 Socket? 要理解一个服务端进程能同时支持多少个连接,首先我们需要明确一个 socket 连接 的表示方式.一个连接由四个部分组成:[LocalIP:LocalPo ...

  4. Doc for DevNow

    前言 DevNow 是一个精简的开源技术博客项目模版,支持 Vercel 一键部署,支持评论.搜索等功能,欢迎大家体验. 首先庆祝下 DevNow star 在不久前过百,对我来说还是蛮有成就感的,感 ...

  5. Qt音视频开发28-ffmpeg解码本地摄像头(yuv422转yuv420)

    一.前言 一开始用ffmpeg做的是视频流的解析,后面增加了本地视频文件的支持,到后面发现ffmpeg也是支持本地摄像头设备的,只要是原则上打通的比如win系统上相机程序.linux上茄子程序可以正常 ...

  6. Qt自定义控件大全文章导航

    文章 链接 Qt编写自定义控件1-汽车仪表盘 https://qtchina.blog.csdn.net/article/details/89407746 Qt编写自定义控件2-进度条标尺 https ...

  7. Qt开源作品15-视频监控画面

    一.前言 视频监控系统在整个安防领域,已经做到了烂大街的程序,全国起码几百家公司做过类似的系统,当然这一方面的需求量也是非常旺盛的,各种定制化的需求越来越多,尤其是这几年借着人脸识别的东风,发展更加迅 ...

  8. Qt编写的项目作品33-斗图神器(雨田哥作品)

    一.功能特点 支持HTTP,HTTPS网络表情图片下载,本地缓存. 采用MV模式,支持大量图片表情预览查看. 采用多线程异步下载图片刷新. 图片搜索功能(因网络提供API无信息字段提供,占搜索不了.但 ...

  9. IM开发干货分享:有赞移动端IM的组件化SDK架构设计实践

    本文由有赞技术团队原创分享,原题"有赞 APP IM SDK 组件架构设计",即时通讯网收录时有修订和改动,感谢原作者的无私分享. 1.引言 本文主要以Android客户端为例,记 ...

  10. 字符编码技术专题(一):快速理解ASCII、Unicode、GBK和UTF-8

    本文由阮一峰(ruanyifeng.com)分享,本文收录时有内容修订和排版优化. 1.引言 今天中午,我突然想搞清楚 Unicode 和 UTF-8 之间的关系,就开始查资料. 这个问题比我想象的复 ...