首页 > 其他分享 >Antlr词法分析之技巧——修改某个token

Antlr词法分析之技巧——修改某个token

时间:2022-10-09 16:35:38浏览次数:69  
标签:Antlr ctx 词法 token limit LIMIT 100 ID SqlBaseParser

在上一次的博客Antlr词法分析之技巧——保留空白符中,

我们演示了如何通过词法分析、语法分析解析出SQL中的所有表名,然后给没有带库名前缀的表名添加库名前缀。

这一次我们要做一个更有意思的功能。

很多人都知道Hue这个工具,它支持各种数据库的网页查询。

数据库可能很大,如果用户没有指定limit,那么可能会把网页卡死。

那如果我们想要实现一个类似hue的产品,当用户在查询一张大表的时候,如果他没有写limit,我们希望能给他补上limit。

可能的查询SQL如下:

--单表查询SQL
select id from a.b.c 
--多表查询SQL
select b.id,b.job_id ,b.name,a.job_name from 
  (select id,job_name from zt_mysql.dev_center.process_info )a 
   right join 
  (select id,job_id,name from  zt_mysql.dev_center.task_info)b
  on a.id=b.job_id

那么如何才能实现这个功能呢?本篇博客的主角Antlr隆重登场了。

研究发现,limit涉及的语法大概有这些

statement
    : query                                                            #statementDefault


query
    :  with? queryNoWith
    ;

queryNoWith
    : queryTerm
      (ORDER BY sortItem (',' sortItem)*)?
      (OFFSET offset=rowCount (ROW | ROWS)?)?
      ( (LIMIT limit=limitRowCount)
      | (FETCH (FIRST | NEXT) (fetchFirst=rowCount)? (ROW | ROWS) (ONLY | WITH TIES))
      )?
    ;

limitRowCount
    : ALL
    | rowCount
    ;

rowCount
    : INTEGER_VALUE
    | QUESTION_MARK
    ;


queryPrimary
    : querySpecification                   #queryPrimaryDefault
    | TABLE qualifiedName                  #table
    | VALUES expression (',' expression)*  #inlineTable
    | '(' queryNoWith ')'                  #subquery
    ;

我们实现一个简单的listener方法,把它找到

@Slf4j
public class TrinoListener extends SqlBaseBaseListener {

    public TokenStreamRewriter rewriter;

    public TrinoListener(TokenStream tokens) {
        this.rewriter = new TokenStreamRewriter(tokens);
    }

    

    @Override
    public void exitQueryNoWith(SqlBaseParser.QueryNoWithContext ctx) {
        TerminalNode limit = ctx.LIMIT();
        SqlBaseParser.LimitRowCountContext limitRowCountContext = ctx.limitRowCount();
        RuleContext parent = ctx.getParent();
        RuleContext grandParent = parent.getParent();
        //没有limit,添加
            if (limit == null && limitRowCountContext == null) {
                rewriter.insertAfter(ctx.stop, " LIMIT 100");
            } 
        

    }
}

把刚才的单表SQL填进去试试看

 public static void main(String[] args) {
        String sql="select id from a.b.c";
        System.out.println(sql);

        CodePointCharStream charStream = CharStreams.fromString(sql.toUpperCase());
        SqlBaseLexer sqlBaseLexer = new SqlBaseLexer(charStream);
        CommonTokenStream tokenStream = new CommonTokenStream(sqlBaseLexer);
        SqlBaseParser sqlBaseParser = new SqlBaseParser(tokenStream);
        SqlBaseParser.SingleStatementContext tree = sqlBaseParser.singleStatement();
        TrinoListener trinoListener = new TrinoListener(tokenStream);
        ParseTreeWalker walker = new ParseTreeWalker();
        walker.walk(trinoListener,tree);
        String text = trinoListener.rewriter.getText();
        System.out.println(text);
    }

打印下结果

select id from a.b.c
16:12:34.225 [main] INFO com.example.sql.trino.TrinoListener - A.B.C
SELECT ID FROM A.B.C LIMIT 100

成功了!

再试试多表SQL

SELECT B.ID,B.JOB_ID ,B.NAME,A.JOB_NAME FROM 
(SELECT ID,JOB_NAME FROM ZT_MYSQL.DEV_CENTER.PROCESS_INFO LIMIT 100 )A 
RIGHT JOIN 
(SELECT ID,JOB_ID,NAME FROM  ZT_MYSQL.DEV_CENTER.TASK_INFO LIMIT 100)B
ON A.ID=B.JOB_ID LIMIT 100

发现有点不对劲,不仅最外层的查询添加了limit,连里面的子查询也添加了。这不符合我们的需求,

所以我们需要把这两者区分开来。

修改我们的listener实现

public void exitQueryNoWith(SqlBaseParser.QueryNoWithContext ctx) {
        TerminalNode limit = ctx.LIMIT();
        SqlBaseParser.LimitRowCountContext limitRowCountContext = ctx.limitRowCount();
        RuleContext parent = ctx.getParent();
        RuleContext grandParent = parent.getParent();
        //排除掉子查询subQuery中的limit
        if (grandParent instanceof SqlBaseParser.StatementDefaultContext) {
            //没有limit,添加
            if (limit == null && limitRowCountContext == null) {
                rewriter.insertAfter(ctx.stop, " LIMIT 100");
            } 
        }

    }

在执行一遍,打印一下

SELECT B.ID,B.JOB_ID ,B.NAME,A.JOB_NAME FROM 
(SELECT ID,JOB_NAME FROM ZT_MYSQL.DEV_CENTER.PROCESS_INFO )A 
RIGHT JOIN 
(SELECT ID,JOB_ID,NAME FROM  ZT_MYSQL.DEV_CENTER.TASK_INFO)B
ON A.ID=B.JOB_ID LIMIT 100

发现子查询中的limit已经不会添加了,离成功又进了一步!

现在还有一个问题,如果客户填了limit,但是limit 10000怎么办?这种情况也需要考虑

我们需要捕捉到用户的limit rowCount,然后改成我们要限制的值

继续修改我们的listener

public void exitQueryNoWith(SqlBaseParser.QueryNoWithContext ctx) {
        TerminalNode limit = ctx.LIMIT();
        SqlBaseParser.LimitRowCountContext limitRowCountContext = ctx.limitRowCount();
        RuleContext parent = ctx.getParent();
        RuleContext grandParent = parent.getParent();
        //排除掉子查询subQuery中的limit
        if (grandParent instanceof SqlBaseParser.StatementDefaultContext) {
            //没有limit,添加
            if (limit == null && limitRowCountContext == null) {
                rewriter.insertAfter(ctx.stop, " LIMIT 100");
            } else if (limit != null && limitRowCountContext != null) {
                //有limit,修改
                SqlBaseParser.RowCountContext rowCountContext = limitRowCountContext.rowCount();
                TerminalNode terminalNode = rowCountContext.INTEGER_VALUE();
                terminalNode.getSymbol();
                String text = terminalNode.getText();
                int integer = Integer.parseInt(text);
                if (integer > 100) {
                    rewriter.replace(terminalNode.getSymbol(), 100);
                }
            }
        }

    }

对客户提供的rowCount进行判断,如果超出了我们的限定值,则进行替换操作。

我们提供了新的SQL:

select id from a.b.c limit 10000
经过替换后变成了

SELECT ID FROM A.B.C LIMIT 100

是不是很酷!

rewriter不仅可以insert、replace,还支持delete,基本我们满足我们大部分需求了!

标签:Antlr,ctx,词法,token,limit,LIMIT,100,ID,SqlBaseParser
From: https://www.cnblogs.com/wangbin2188/p/16772594.html

相关文章

  • 40.TokenAuthentication认证
    TokenAuthentication认证介绍TokenAuthentication是一种简单的基于令牌的HTTP认证适用于CS架构,例如普通的桌面应用程序或移动客户端 TokenAuthentication认证使用......
  • 肖sir__python中获取token方法
    一、从响应头中获取token1、从登录接口的响应头中获取token值,存储在变量token中,方便后续接口请求的时候使用二、从响应体中获取token(1)token存在于单层字典数据中(2)token......
  • passwd:Authentication token manipulation error—错误的解决办法
    sudo passwd一直报错如下:AuthenticationtokenmanipulationerrorMMP的报这样的错误是:密码:身份验证令牌操作错误,一般是密码文件的权限的问题,不过也有可能是根目录......
  • 执行shell脚本报错syntax error near unexpected token `$'\r''解决方法
     今天在进行性能测试时,正好需要一个老脚本,直接拿过来修改一下就可以使用,但是运行时直接报错了syntaxerrornearunexpectedtoken`$'\r'内心一万个WTF,为啥不行呢第一步 ......
  • 三--5.词法分析程序的实现
         (印象里上图貌似有错误)                            ......
  • /bin/sh^M: bad interpreter: syntax error near unexpected token `elif'
    在Linux中执行.sh脚本,异常/bin/sh^M:badinterpreter:Nosuchfileordirectory。分析:这是不同系统编码格式引起的:在windows系统中编辑的.sh文件可能有不可见字符,所以在......
  • 如何使用 ABAP 代码消费需要传递 CSRF token 的 OData 服务试读版
    正如本教程的开篇介绍文章SAPOData开发教程-从入门到提高(包含SEGW,RAP和CDP)所提到的,SAPOData服务开发,从实现技术上来说,可以分为三大类。因此本教程也分为三大部......
  • 同时多个axios请求怎么实现无痛刷新token
    需求最近遇到个需求:前端登录后,后端返回token和token有效时间,当token过期时要求用旧token去获取新的token,前端需要做到无痛刷新token,即请求刷新token时要做到用户无感知。......
  • 前端无痛刷新Token
    前端无痛刷新Token这个需求场景很常见,几乎很多项目都会用上,之前项目也实现过,最近刚好有个项目要实现,重新梳理一番。需求对于需要前端实现无痛刷新Token,无非就两种:请求前判断......
  • Spring boot+VUE使用token实现登录验证及退出
    Springboot+VUE实现token验证Vue+SpringBoot实现token认证主要可分为六步:1.前端登录,post用户名和密码到后端。2.后端验证用户名和密码,若通过,生成一个token返回给前端......