我们刚刚发布了一个新手任务,欢迎认领
追随大佬们的脚步啦
欢迎大佬
跟着大佬学习
大佬们,antlr4解析长sql,例如:insert … values(),(),()…();这种语句很慢,有考虑在解析引擎里做优化吗?
嗯嗯,目前也在考虑 antlr4 解析的性能优化,不过还没开始推进。你那边如果有好的想法,欢迎提交代码
嗯,我写了个demo,基本想法是这样的,当sql长度大于1K且是insert语句,我就转到另外一个方法里去处理;这个方法采用druid解析,把druid解析的结果转成antlr4解析的结果,再交给sharding去处理;但是我对sharding代码很不熟悉,我觉得适配性会很差 ,但目前在公司项目里测试也还够用 ,比原先的快8到10倍,解析时间。
代码如下:
public final class SQLStatementParserExecutor {
//......
public SQLStatement parse(final String sql) {
if (sql.length() > 1024 && ("insert".equals(sql.trim().substring(0, 6)) || "INSERT".equals(sql.trim().substring(0, 6))) ) {
log.info("你进入的是Druid SQL parser");
return druidPar(sql);
}
SQLStatement sqlStatement = visitorEngine.visit(parserEngine.parse(sql, false));
return visitorEngine.visit(parserEngine.parse(sql, false));
}
public SQLStatement druidPar(final String sql) {
//druid
MySqlStatementParser druidParser = new MySqlStatementParser(sql);
com.powersi.sqlparser.sql.ast.SQLStatement druidSQLStatement = druidParser.parseStatement();
MySqlInsertStatement insert = (MySqlInsertStatement)druidSQLStatement;
SQLExprTableSource tableSource = insert.getTableSource();
String tableName = insert.getTableSource().getTableName();
List<SQLExpr> columns = insert.getColumns();
// 子查询
SQLSelect query = insert.getQuery();
//如果有子查询,则全部交给sharding
if (query != null){
return visitorEngine.visit(parserEngine.parse(sql, false));
}
List<SQLInsertStatement.ValuesClause> valuesList = insert.getValuesList();
List<SQLExpr> values = valuesList.get(0).getValues();
MySQLInsertStatement insertStatement = new MySQLInsertStatement();
//替换setAssignment
replaceAssignment(insertStatement);
//替换setOnDuplicateKeyColumns
replaceOnDuplicatekeyColumns(insertStatement);
//替换table
replaceTable(tableSource, tableName, insertStatement);
//替换insertColumns
replaceInsertColumns(insert, columns, insertStatement);
//替换insertSelect
replaceInsertSelect(insertStatement);
//替换values
replaceValues(valuesList, values, insertStatement);
//替换parameterCount
replaceParameterCount(insertStatement);
//替换commentSegments
replaceCommentSegments(insertStatement);
return insertStatement;
}
//替换values
private void replaceValues(List<SQLInsertStatement.ValuesClause> valuesList, List<SQLExpr> values, MySQLInsertStatement insertStatement) {
int valuesListSize = valuesList.size();
int valuesSize = values.size();
List<List<ExpressionSegment>> expressionSegmentsList = new LinkedList<>();
ExpressionSegment expressionSegment = null;
//记录起始位置
int valuesStartIndex;
//记录结束位置
int valuesStopIndex;
InsertValuesSegment insertValuesSegment = null;
//遍历插入的记录
for (int i = 0; i < valuesListSize; i++) {
valuesStartIndex = valuesList.get(i).getStartIndex();
valuesStopIndex = valuesList.get(i).getEndIndex();
List<ExpressionSegment> expressionSegments = new LinkedList<>();
//遍历每条插入记录里的字段值
for (int j = 0;j < valuesSize;j++){
expressionSegments.add(null);
SQLExpr sqlExpr = valuesList.get(i).getValues().get(j);
if (sqlExpr instanceof SQLNullExpr){
expressionSegment = new CommonExpressionSegment(sqlExpr.getStartIndex(), sqlExpr.getEndIndex(), sqlExpr.toString());
} else if (sqlExpr instanceof SQLValuableExpr){
expressionSegment = new LiteralExpressionSegment(sqlExpr.getStartIndex(), sqlExpr.getEndIndex(), ((SQLValuableExpr) sqlExpr).getValue());
} else {
throw new RuntimeException("这个值是未知类型-->" + "valuesListSize:" + i + "valuesSize:" + j );
}
expressionSegments.set(j,expressionSegment);
}
expressionSegmentsList.add(expressionSegments);
insertValuesSegment = new InsertValuesSegment(valuesStartIndex, valuesStopIndex, expressionSegmentsList.get(i));
insertStatement.getValues().add(insertValuesSegment);
}
}
//替换insertColumns
private void replaceInsertColumns(MySqlInsertStatement insert, List<SQLExpr> columns, MySQLInsertStatement insertStatement) {
int columnsStartIndex;
int columnsStopIndex;
LinkedList<ColumnSegment> columnList = null;
if (columns.size() == 0){
columnList = new LinkedList<>();
columnsStartIndex = columnsStopIndex = insert.getColumnIndex(VALUES) - 1;
}else {
ColumnSegment column = null;
columnList = new LinkedList<>();
//左括号的位置
columnsStartIndex = insert.getColumnIndex(LPAREN);
//右括号的位置
columnsStopIndex = insert.getColumnIndex(RPAREN);
for (SQLExpr sqlExpr : columns) {
//判断字段名是否带了反引号
if (sqlExpr.isBackticks()){
column = new ColumnSegment(sqlExpr.getStartIndex(), sqlExpr.getEndIndex(), new IdentifierValue(sqlExpr.toString(), QuoteCharacter.BACK_QUOTE));
}else {
column = new ColumnSegment(sqlExpr.getStartIndex(), sqlExpr.getEndIndex(), new IdentifierValue(sqlExpr.toString(), QuoteCharacter.NONE));
}
columnList.add(column);
}
}
InsertColumnsSegment insertColumnsSegment = new InsertColumnsSegment(columnsStartIndex, columnsStopIndex, columnList);
insertStatement.setInsertColumns(insertColumnsSegment);
}
//替换table
private void replaceTable(SQLExprTableSource tableSource, String tableName, MySQLInsertStatement insertStatement) {
int tableStartIndex = tableSource.getExpr().getStartIndex();
int tableStopIndex = tableSource.getExpr().getEndIndex();
// 判断表名是否带了反引号
if (tableSource.getExpr().isBackticks()){
IdentifierValue table = new IdentifierValue(tableName, QuoteCharacter.BACK_QUOTE);
TableNameSegment tableNameSegment = new TableNameSegment(tableStartIndex,tableStopIndex,table);
SimpleTableSegment simpleTableSegment = new SimpleTableSegment(tableNameSegment);
insertStatement.setTable(simpleTableSegment);
}else {
IdentifierValue table = new IdentifierValue(tableName, QuoteCharacter.NONE);
TableNameSegment tableNameSegment = new TableNameSegment(tableStartIndex,tableStopIndex,table);
SimpleTableSegment simpleTableSegment = new SimpleTableSegment(tableNameSegment);
insertStatement.setTable(simpleTableSegment);
}
}
druid parse 在最开始的 shardingsphere 版本中被使用,不过后面为了提升兼容性,就放弃了,改用 antlr4 了。 druid 的性能确实会好很多,不过这对于shardingsphere来说可能不是一个好的方法,可能还是主要考虑 如何提升 antlr4 的性能。
有什么好的想法吗?我觉得,由于antlr4的特性,在面对长字符串时,不管它怎么优化,都不太可能像druid一样快 ,但是,我也喜欢antlr4优雅的g4语法和它的超强兼容性
哈哈,还没开始这一块的内容,由于跟 druid 的方式不同,所以肯定是不可能达到跟 druid 一样快的。不过应该是还有不小的性能提升空间。如果你有好的想法或者有兴趣,也可以尝试改造。
论坛的小伙伴们,目前有一些任务大家有空可以长期参与
对Oracle 的 antlr4 语法进行进行校对和扩充。具体可以参见这个issue,在上诉 issue 回复即可参与。
对 sqlServer 感兴趣的也可以参与这个 issue
parseEngine 维护了新的一批任务列表,有兴趣的小伙伴欢迎查看认领。
本次新增了两个任务 校对并扩充 mysql 和 postgreSQL 的 antlr4 语法,感兴趣的小伙伴欢迎留言认领。
对pg的方言支持是不是越来越完善了呢
嗯,是的,目前 pg 和 mysql 的支持度是最好的,社区也在持续努力
公司历史比较多用pg的库,pg方言和特殊函数比较多 之前用的4.x版本 好些不支持
扩展语法支持的话,不知道扩展方便不方便 具体还没细看
嗯,好的,语法不支持问题是比较好解决的,主要就是涉及 antlr4 语法
最近我们从 mysql 和 postgresql 的官方测试集中提取了一些不支持 sql 到这个 文件
对解析感兴趣的小伙伴可以查看这个文件,来解决这些不支持的 sql。如果这些 不支持的 sql 都能够被解决,那么我们的 解析支持度将达到 100%
大家一起把自己公司用到的特殊函数支持起来