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

本文涉及的产品
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
全局流量管理 GTM,标准版 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。

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





目录
相关文章
|
4天前
|
SQL 监控 数据库
SQL语句是否都需要解析及其相关技巧和方法
在数据库管理中,SQL(结构化查询语言)语句的使用无处不在,它们负责数据的查询、插入、更新和删除等操作
|
12天前
|
SQL 存储 数据库
SQL语句是否都需要解析及其相关技巧与方法
在数据库管理系统中,SQL(Structured Query Language)语句作为与数据库交互的桥梁,其执行过程往往涉及到一个或多个解析阶段
|
4天前
|
SQL 数据可视化 BI
SQL语句及查询结果解析:技巧与方法
在数据库管理和数据分析中,SQL语句扮演着至关重要的角色
|
10天前
|
SQL 监控 关系型数据库
SQL错误代码1303解析与处理方法
在SQL编程和数据库管理中,遇到错误代码是常有的事,其中错误代码1303在不同数据库系统中可能代表不同的含义
|
10天前
|
SQL 存储 关系型数据库
SQL默认索引是什么:深入解析与技巧
在SQL数据库中,索引是一种用于提高查询性能的重要数据结构
|
10天前
|
SQL 开发框架 .NET
ASP.NET连接SQL数据库:实现过程与关键细节解析an3.021-6232.com
随着互联网技术的快速发展,ASP.NET作为一种广泛使用的服务器端开发技术,其与数据库的交互操作成为了应用开发中的重要环节。本文将详细介绍在ASP.NET中如何连接SQL数据库,包括连接的基本概念、实现步骤、关键代码示例以及常见问题的解决方案。由于篇幅限制,本文不能保证达到完整的2000字,但会确保
|
10天前
|
SQL 监控 测试技术
全面解析SQL数据库迁移:步骤、挑战与最佳实践a8u.0335pw.com
随着信息技术的快速发展,数据库迁移已成为企业和组织在IT领域经常面临的一项任务。数据库迁移涉及到数据的转移、转换和适应新环境的过程,特别是在使用SQL数据库时。本文将详细介绍SQL数据库迁移的过程,探讨其面临的挑战,并分享一些最佳实践。一、数据库迁移概述数据库迁移是指将数据库从一个环境迁移到另一个环
|
SQL Java 关系型数据库
V$SQLAREA解析
V$SQLAREA lists statistics on shared SQL area and contains one row per SQL string.
814 0
|
1月前
|
关系型数据库 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)")
|
3月前
|
SQL 存储 监控
SQL Server的并行实施如何优化?
【7月更文挑战第23天】SQL Server的并行实施如何优化?
85 13

推荐镜像

更多