解析SQL并添加条件的两种实现方式

本文涉及的产品
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云解析 DNS,旗舰版 1个月
简介: SQL 解析

解析SQL并添加条件的两种实现方式


在最近的工作中,需要用到解析SQL并添加相应的条件以实现某个功能。调研时发现了两种方案,在此之前并没有接触过相应的SQL解析方案。故记录一下,以供参考



SQL解析用途


在之前的开发中,了解最多的关于SQL解析就是mysql架构的SQL解析器,但是并没有深入了解过该原理。


那么在业务层进行SQL的解析有何用途?我认为最关键的是权限,在当前环境中,数据安全是方方面面的。在大型组织中,人员繁杂,对于数据权限的管控非常严格,如何控制不同成员的数据权限非常重要。


数据库中数据的透出大都是通过SQL进行查询的,那么业务层就可以在组织中人员查询数据的时候,根据不同的人员赋予不同的权限。即使对数据的保护,也是减少人员干涉的,减轻DBA的审核工作。


如阿里的ADB可以对不同的员工的操作数据库的权限进行控制。



Coding


目前解析SQL方式一般有JSQL Parser、 ANTLR4、JavaCC几种方式。


像Druid中使用的SQL解析就是JSQL Parser的方式。而ANTLR4是开源的语法解析框架,如果是自己动手实现,比较困难,同时也需要较长的时间进行验证。所以选择了Druid中已经实现的SQL解析器实现当前这个功能---解析SQL,并根据用户添加权限。因为Druid并不支持大数据相关的数据源SQL解析,所以我也根据部分数据源中已经实现的解析框架实现了该功能。




Druid 解析SQL语法的代码实现


阿里的Druid框架,支持解析SQL并添加相应的条件。实现起来比较简单。以下代码仅为示例,具体实现还要以需求进行进一步开发



/*** @author leo_zl  2022/2/28*/publicclassMysqlParserVisitorextendsMySqlASTVisitorAdapter {
/*** 限制访问的表名* [parser_table,demo_table]*/privatefinalSettableNameSet;
/*** 每个表的约束条件** {*    "parser_table":"id=1" ,*    "demo_table":"id=2"* }*/privatefinalMaptableConditionMap;
publicMysqlParserVisitor(SettableNameSet, MaptableConditionMap) {
this.tableNameSet=tableNameSet;
this.tableConditionMap=tableConditionMap;
    }
@Overridepublicbooleanvisit(SQLExprTableSourcereference) {
StringtableName=reference.getName().toString();
// 符合目标表源名if (isTargetTable(tableName)) {
SQLObjectparent=reference.getParent();
Stringalias=reference.getAlias();
// 回溯到选择语句while (!(parentinstanceofMySqlSelectQueryBlock) &&parent!=null) {
parent=parent.getParent();
            }
// 插入行控制条件Stringconditions=formCondition(tableName, alias);
if (StringUtils.isNotEmpty(conditions) &&parent!=null) {
                ((MySqlSelectQueryBlock) parent).addCondition(conditions);
            }
        }
returnfalse;
    }
privateStringformCondition(StringtableName, Stringalias) {
Stringcondition=tableConditionMap.get(tableName);
if (StringUtils.isEmpty(condition)) {
returnnull;
        }
returnalias==null?condition : alias+"."+condition;
    }
privatebooleanisTargetTable(StringtableName) {
returntableNameSet.contains(tableName);
    }
}


单测以及使用方式


/*** @author leo_zl  2022/2/28*/@RunWith(PowerMockRunner.class)
@PrepareForTest(MysqlParserVisitor.class)
publicclassMysqlParserVisitorTest {
@Testpublicvoidvisit() {
SettableNameSet=newHashSet<>();
tableNameSet.add("parser_table");
MaptableConditionMap=newHashMap<>();
tableConditionMap.put("parser_table", "id=1");
StringinputSql="SELECT * FROM parser_table";
SQLStatementsqlStatement=SQLUtils.parseSingleStatement(inputSql, JdbcConstants.MYSQL);
MysqlParserVisitorvisitor=newMysqlParserVisitor(tableNameSet, tableConditionMap);
sqlStatement.accept(visitor);
StringexpectSql="SELECT *\n"+"FROM parser_table\n"+"WHERE id = 1";
Assert.assertEquals(expectSql, sqlStatement.toString());
    }
}


### ANTLR4 方式的解析SQL


此处说的ANTLR4的解析SQL的方式,指的是复用Phoenix的解析器进行实现的。并非从ANTLR4框架构架的。


/*** @author leo_zl  2022/2/9*/publicclassPhoenixSqlParser {
privatestaticfinalLoggerLOG=LoggerFactory.getLogger(PhoenixSqlParser.class);
publicStringprivilegeControl(Stringsql, MapprivilegeInfo) {
LOG.info("original sql :\n{}", sql);
SQLParsersqlParser=newSQLParser(sql);
try {
SelectStatementselectStatement=sqlParser.parseQuery();
selectStatement=parserSelect(selectStatement, privilegeInfo);
if (selectStatement.isUnion()) {
Listselects=selectStatement.getSelects();
returnStringUtils.join(selects, " \n UNION ALL \n");
            }
returnselectStatement.toString();
        } catch (SQLExceptione) {
LOG.info("解析SQL 异常:{}", e.getMessage());
        }
returnsql;
    }
privateSelectStatementparserSelect(SelectStatementselectStatement, MapprivilegeInfo) throwsSQLException {
if (selectStatement.isUnion()) {
returnparseUnion(selectStatement, privilegeInfo);
        }
// 获取from nodereturnparseTable(selectStatement, privilegeInfo);
    }
privateSelectStatementparseUnion(SelectStatementselectStatement, MapprivilegeInfo) throwsSQLException {
Listselects=selectStatement.getSelects();
ListnewStatements=newArrayList<>();
for (SelectStatementselect : selects) {
SelectStatementnewStatement=parserSelect(select, privilegeInfo);
newStatements.add(newStatement);
        }
StringdestSql=StringUtils.join(newStatements, " \n UNION ALL ");
SQLParsersqlParser=newSQLParser(destSql);
returnsqlParser.parseQuery();
    }
privateSelectStatementparseTable(SelectStatementselectStatement, MapprivilegeInfo) throwsSQLException {
TableNodetableNode=selectStatement.getFrom();
if (tableNodeinstanceofJoinTableNode) {
// join 查询JoinTableNodejoinTableNode= (JoinTableNode) tableNode;
TableNodelhs=joinTableNode.getLHS();
SelectStatementleftSelect=parseJoinTable(selectStatement, lhs, privilegeInfo);
TableNoderhs=joinTableNode.getRHS();
returnparseJoinTable(leftSelect, rhs, privilegeInfo);
        } else {
returnparseJoinTable(selectStatement, tableNode, privilegeInfo);
        }
    }
privateSelectStatementparseJoinTable(SelectStatementselectStatement, TableNodetableNode, MapprivilegeInfo) throwsSQLException {
if (tableNodeinstanceofDerivedTableNode) {
// 子查询DerivedTableNodesubQuery= (DerivedTableNode) tableNode;
SelectStatementsubQuerySelect=subQuery.getSelect();
returnparserSelect(subQuerySelect, privilegeInfo);
        } elseif (tableNodeinstanceofNamedTableNode) {
// 普通表NamedTableNodenamedTableNode= (NamedTableNode) tableNode;
TableNamename=namedTableNode.getName();
Stringalias=namedTableNode.getAlias();
ParseNodeparseNode=constructParseNode(name, alias, privilegeInfo);
returnselectStatement.combine(parseNode);
        }
returnselectStatement;
    }
privateParseNodeconstructParseNode(TableNametableName, StringaliasName, MapprivilegeInfo) throwsSQLException {
Stringconditions=privilegeInfo.get(tableName.getTableName());
if (Objects.isNull(conditions)) {
returnnull;
        }
conditions=aliasName==null?conditions : aliasName+"."+conditions;
// 因为不熟悉Phoenix parser的API,所以暂时使用这种笨办法,构建对应的nodeStringBuildersqlBuilder=newStringBuilder("SELECT * FROM "+tableName+" ");
if (Objects.nonNull(aliasName)) {
sqlBuilder.append(aliasName).append(" ");
        }
Stringsql=sqlBuilder.append("WHERE ").append(conditions).toString();
SQLParsersqlParser=newSQLParser(sql);
returnsqlParser.parseQuery().getWhere();
    }
}


单测以及使用


/*** @author leo_zl  2022/2/28*/@RunWith(PowerMockRunner.class)
@PowerMockIgnore({"javax.*"})
publicclassPhoenixSqlParserTest {
@InjectMocksprivatePhoenixSqlParserphoenixSqlParser;
@TestpublicvoidprivilegeControl() {
StringinputSql="SELECT full_name FROM SALES_PERSON WHERE ranking >= 5.0\n"+"    UNION ALL SELECT reviewer_name FROM CUSTOMER_REVIEW WHERE score >= 8.0\n"+"\n";
Mapconditions=newHashMap<>();
conditions.put("SALES_PERSON","id = 1");
StringresultSql=phoenixSqlParser.privilegeControl(inputSql, conditions);
StringexpectSql="SELECT FULL_NAME FROM SALES_PERSON  WHERE (RANKING >= 5.0 AND ID = 1) \n"+" UNION ALL \n"+"SELECT REVIEWER_NAME FROM CUSTOMER_REVIEW  WHERE SCORE >= 8.0";
Assert.assertEquals(expectSql,resultSql);
    }
}

Gitee地址


后记


Druid虽然支持了大部分的数据源,但是比较灵活的实现方式还是ANTLR4。但是因为时间的缘故,并没有选择使用ANTLR4进行解析SQL。

以后有机会还是可以去实现一下,增长见识。





目录
相关文章
|
13天前
|
SQL 监控 数据库
SQL语句是否都需要解析及其相关技巧和方法
在数据库管理中,SQL(结构化查询语言)语句的使用无处不在,它们负责数据的查询、插入、更新和删除等操作
|
20天前
|
SQL 存储 数据库
SQL语句是否都需要解析及其相关技巧与方法
在数据库管理系统中,SQL(Structured Query Language)语句作为与数据库交互的桥梁,其执行过程往往涉及到一个或多个解析阶段
|
13天前
|
SQL 数据可视化 BI
SQL语句及查询结果解析:技巧与方法
在数据库管理和数据分析中,SQL语句扮演着至关重要的角色
|
18天前
|
SQL 监控 关系型数据库
SQL错误代码1303解析与处理方法
在SQL编程和数据库管理中,遇到错误代码是常有的事,其中错误代码1303在不同数据库系统中可能代表不同的含义
|
18天前
|
SQL 存储 关系型数据库
SQL默认索引是什么:深入解析与技巧
在SQL数据库中,索引是一种用于提高查询性能的重要数据结构
|
19天前
|
SQL 开发框架 .NET
ASP.NET连接SQL数据库:实现过程与关键细节解析an3.021-6232.com
随着互联网技术的快速发展,ASP.NET作为一种广泛使用的服务器端开发技术,其与数据库的交互操作成为了应用开发中的重要环节。本文将详细介绍在ASP.NET中如何连接SQL数据库,包括连接的基本概念、实现步骤、关键代码示例以及常见问题的解决方案。由于篇幅限制,本文不能保证达到完整的2000字,但会确保
|
22天前
|
SQL 分布式计算 大数据
大数据-97 Spark 集群 SparkSQL 原理详细解析 Broadcast Shuffle SQL解析过程(一)
大数据-97 Spark 集群 SparkSQL 原理详细解析 Broadcast Shuffle SQL解析过程(一)
33 0
|
2月前
|
关系型数据库 MySQL 网络安全
5-10Can't connect to MySQL server on 'sh-cynosl-grp-fcs50xoa.sql.tencentcdb.com' (110)")
5-10Can't connect to MySQL server on 'sh-cynosl-grp-fcs50xoa.sql.tencentcdb.com' (110)")
|
4月前
|
SQL 存储 监控
SQL Server的并行实施如何优化?
【7月更文挑战第23天】SQL Server的并行实施如何优化?
92 13
|
4月前
|
SQL
解锁 SQL Server 2022的时间序列数据功能
【7月更文挑战第14天】要解锁SQL Server 2022的时间序列数据功能,可使用`generate_series`函数生成整数序列,例如:`SELECT value FROM generate_series(1, 10)。此外,`date_bucket`函数能按指定间隔(如周)对日期时间值分组,这些工具结合窗口函数和其他时间日期函数,能高效处理和分析时间序列数据。更多信息请参考官方文档和技术资料。

推荐镜像

更多