SIGs|ParseEngine 小组介绍

我们刚刚发布了一个新手任务,欢迎认领

:smiley:追随大佬们的脚步啦

欢迎大佬 :smiley: :v:

1 个赞

跟着大佬学习 :grimacing:

大佬们,antlr4解析长sql,例如:insert … values(),(),()…();这种语句很慢,有考虑在解析引擎里做优化吗?

嗯嗯,目前也在考虑 antlr4 解析的性能优化,不过还没开始推进。你那边如果有好的想法,欢迎提交代码 :smiley:

嗯,我写了个demo,基本想法是这样的,当sql长度大于1K且是insert语句,我就转到另外一个方法里去处理;这个方法采用druid解析,把druid解析的结果转成antlr4解析的结果,再交给sharding去处理;但是我对sharding代码很不熟悉,我觉得适配性会很差 :upside_down_face:,但目前在公司项目里测试也还够用 :joy:,比原先的快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);
        }
    }

1 个赞

druid parse 在最开始的 shardingsphere 版本中被使用,不过后面为了提升兼容性,就放弃了,改用 antlr4 了。 druid 的性能确实会好很多,不过这对于shardingsphere来说可能不是一个好的方法,可能还是主要考虑 如何提升 antlr4 的性能。

1 个赞

有什么好的想法吗?我觉得,由于antlr4的特性,在面对长字符串时,不管它怎么优化,都不太可能像druid一样快 :upside_down_face:,但是,我也喜欢antlr4优雅的g4语法和它的超强兼容性 :grinning:

哈哈,还没开始这一块的内容,由于跟 druid 的方式不同,所以肯定是不可能达到跟 druid 一样快的。不过应该是还有不小的性能提升空间。如果你有好的想法或者有兴趣,也可以尝试改造。 :smiley:

论坛的小伙伴们,目前有一些任务大家有空可以长期参与 :grinning:
对Oracle 的 antlr4 语法进行进行校对和扩充。具体可以参见这个issue,在上诉 issue 回复即可参与。
对 sqlServer 感兴趣的也可以参与这个 issue

parseEngine 维护了新的一批任务列表,有兴趣的小伙伴欢迎查看认领。

本次新增了两个任务 校对并扩充 mysql 和 postgreSQL 的 antlr4 语法,感兴趣的小伙伴欢迎留言认领。

对pg的方言支持是不是越来越完善了呢

嗯,是的,目前 pg 和 mysql 的支持度是最好的,社区也在持续努力

公司历史比较多用pg的库,pg方言和特殊函数比较多 之前用的4.x版本 好些不支持
扩展语法支持的话,不知道扩展方便不方便 具体还没细看

嗯,好的,语法不支持问题是比较好解决的,主要就是涉及 antlr4 语法

:ok_hand:

最近我们从 mysql 和 postgresql 的官方测试集中提取了一些不支持 sql 到这个 文件
对解析感兴趣的小伙伴可以查看这个文件,来解决这些不支持的 sql。如果这些 不支持的 sql 都能够被解决,那么我们的 解析支持度将达到 100%

1 个赞

大家一起把自己公司用到的特殊函数支持起来

京ICP备2021015875号