微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

区分sql语法中的函数名和函数参数

如何解决区分sql语法中的函数名和函数参数

使用这个 grammar ,我试图从 sql 查询提取用户编写的表达式。

例如,我想从这个查询提取 FNAME、LName 和姓名。

SELECT TRIM(CONCAT(FNAME,LNAME)) AS `name`FROM  CLIENTS;

[解析树]

2

我可以用我的监听器提取名称”:

public void enterSelectSingle(ksqlParser.SelectSingleContext ctx) {
    super.enterSelectSingle(ctx);
    System.out.println(ctx.identifier().getText());
}

但是当我尝试使用 ctx.expression().getText(). 提取“FNAME,LNAME”时,我得到 TRIM(CONCAT(FNAME,LNAME))

我如何区分 CONCAT、TRIM、(,) 和,与 FNAME 和 LNAME,因为它们都被识别为标识符并隐藏在语法中的表达式之后?

解决方法

如果你看语法,你可以看到下面对``primaryExpression```的解析器规则

(它在您问题的树图中引用):

primaryExpression
    : literal                                                                             #literalExpression
    | identifier STRING                                                                   #typeConstructor
    | CASE valueExpression whenClause+ (ELSE elseExpression=expression)? END              #simpleCase
    | CASE whenClause+ (ELSE elseExpression=expression)? END                              #searchedCase
    | CAST '(' expression AS type ')'                                                     #cast
    | ARRAY '[' (expression (',' expression)*)? ']'                                       #arrayConstructor
    | MAP '(' (expression ASSIGN expression (',' expression ASSIGN expression)*)? ')'     #mapConstructor
    | STRUCT '(' (identifier ASSIGN expression (',' identifier ASSIGN expression)*)? ')'  #structConstructor
    | identifier '(' ASTERISK ')'                                                           #functionCall
    | identifier '(' (functionArgument (',' functionArgument)* (',' lambdaFunction)*)? ')' #functionCall
    | value=primaryExpression '[' index=valueExpression ']'                               #subscript
    | identifier                                                                          #columnReference
    | identifier '.' identifier                                                           #qualifiedColumnReference
    | base=primaryExpression STRUCT_FIELD_REF fieldName=identifier                        #dereference
    | '(' expression ')'                                                                  #parenthesizedExpression
    ;

与您的列引用匹配的替代项是 #columnReference 替代项。

这意味着,在您的侦听器中,您可以像这样覆盖 enterColumnReference 方法(以匹配 ONLY parseExpression 规则的替代方案)。它只有一个 identifier 规则成员,因此您只需引用它:

    @Override
    public void enterColumnReference(SqlBaseParser.ColumnReferenceContext ctx) {
        System.out.println(ctx.identifier().getText());
    }

有了这个覆盖(除了你的),我得到以下输出:

`name`
FNAME
LNAME

这个语法标记了解析器规则上的所有替代方案(至少就我注意到的而言)。这使得拦截(或侦听)非常具体的解析器规则替代方案变得相对容易。每个标记的替代方案都有自己的 enter*exit* 方法以及特定的 *Context 类,以便非常简单地访问规则成员。

就您的情况而言,您似乎只关心该替代 IF 它是 functionArgument。如果是这种情况,您可以在侦听器中引入一些状态管理来跟踪:

public class MyListener extends SqlBaseBaseListener {
    private boolean isFunctionArgument = false;

    @Override
    public void enterSelectSingle(SqlBaseParser.SelectSingleContext ctx) {
        System.out.println(ctx.identifier().getText());
    }

    @Override
    public void enterFunctionArgument(SqlBaseParser.FunctionArgumentContext ctx) {
        isFunctionArgument = true;
    }

    @Override
    public void exitListFunctions(SqlBaseParser.ListFunctionsContext ctx) {
        isFunctionArgument = true;
    }

    @Override
    public void enterColumnReference(SqlBaseParser.ColumnReferenceContext ctx) {
        if (isFunctionArgument) {
            System.out.println(ctx.identifier().getText());
        }
    }
}

在您的示例中,这不会更改输出,但应该让您了解如何选择更具体的使用上下文。

在“你可以从这里到达那里”的意义上,这是可能的:

    @Override
    public void enterSelectSingle(SqlBaseParser.SelectSingleContext ctx) {
        System.out.println(ctx.identifier().getText());
        SqlBaseParser.BooleanDefaultContext bdc = (SqlBaseParser.BooleanDefaultContext) ctx.expression().booleanExpression();
        SqlBaseParser.ValueExpressionDefaultContext cev = (SqlBaseParser.ValueExpressionDefaultContext) bdc.predicated().valueExpression();
        SqlBaseParser.FunctionCallContext fc = (SqlBaseParser.FunctionCallContext) cev.primaryExpression();
        for (SqlBaseParser.FunctionArgumentContext fa : fc.functionArgument()) {
            SqlBaseParser.BooleanDefaultContext bdcfa = (SqlBaseParser.BooleanDefaultContext) fa.expression().booleanExpression();
            SqlBaseParser.ValueExpressionDefaultContext cevfa = (SqlBaseParser.ValueExpressionDefaultContext) bdcfa.predicated().valueExpression();
            SqlBaseParser.FunctionCallContext fc2 = (SqlBaseParser.FunctionCallContext) cevfa.primaryExpression();
            for (SqlBaseParser.FunctionArgumentContext fa2 : fc2.functionArgument()) {
                SqlBaseParser.BooleanDefaultContext bdcfa2 = (SqlBaseParser.BooleanDefaultContext) fa2.expression().booleanExpression();
                SqlBaseParser.ValueExpressionDefaultContext cevfa2 = (SqlBaseParser.ValueExpressionDefaultContext) bdcfa2.predicated().valueExpression();
                SqlBaseParser.ColumnReferenceContext cr = (SqlBaseParser.ColumnReferenceContext) cevfa2.primaryExpression();
                System.out.println(cr.identifier().getText());
            }
        }
    }

这是一条艰难的道路(我只是直接将类型强制转换为您绝对应该进行 instanceof 测试的地方。)

它也很脆。任何小的结构变化都会导致此代码中断。因此,您需要大量逻辑来导航 singleSelect

下 parseTree 的所有可能排列

更好的方法(利用听众为您做的事情):注意:我将 enterSelectSingle 更改为 exitSelectSingle。你需要等到你听完子节点来收集参数并打印出来。

import java.util.ArrayList;

public class MyListener extends SqlBaseBaseListener {
    private boolean isFunctionArgument = false;
    private final ArrayList<String> args = new ArrayList<>();

    @Override
    public void exitSelectSingle(SqlBaseParser.SelectSingleContext ctx) {
        System.out.println(ctx.identifier().getText());
        for (String arg : args) {
            System.out.println(arg);
        }
        args.clear();
    }

    @Override
    public void enterFunctionArgument(SqlBaseParser.FunctionArgumentContext ctx) {
        isFunctionArgument = true;
    }

    @Override
    public void exitListFunctions(SqlBaseParser.ListFunctionsContext ctx) {
        isFunctionArgument = true;
    }

    @Override
    public void enterColumnReference(SqlBaseParser.ColumnReferenceContext ctx) {
        if (isFunctionArgument) {
            args.add(ctx.identifier().getText());
        }
    }
}

注意:即使是这段代码(为了简单起见)也不处理嵌套函数调用(为此,您需要创建一个 ArrayList 堆栈,然后在输入/退出函数时推送/弹出。

我通常编写代码来访问我的 parseTree,创建我想在我的程序中处理的简化内部树,但这是一个比这更长的答案,已经很长了,回复。

简而言之(为时已晚),它可能很复杂,您必须处理这种复杂性,除非您知道您只需要处理更简单的案例子集。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。