当前位置: 首页 > 知识库问答 >
问题:

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

松茂实
2023-03-14

使用这种语法,我试图从sql查询中提取用户编写的表达式。

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

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

[解析树]

我可以使用ListNor提取“名称”:

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

但是当我试图用ctx提取“FNAME,LNAME”时。表达式()。getText() 我得到修剪(CONCAT(FNAME,LNAME))

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


共有1个答案

钱澄邈
2023-03-14

如果您查看语法,您可以看到下面的“主表达式”解析器规则

(您的问题中的树形图中引用了该选项):

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备选方案。

这意味着,在您的侦听器中,您可以重写entColnInformation方法,就像这样(仅匹配parseExpresation规则的替代方案)。它只有一个标识符规则成员,所以您只需引用它:

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

通过该覆盖(除了您的覆盖),我得到以下输出:

`name`
FNAME
LNAME

该语法标记了解析器规则上的所有备选方案(至少就我所注意到的)。这使得截取(或监听)非常特定的解析器规则选项相对容易。每个标记的备选方案都有自己的enter*exit*方法,以及一个特定的*上下文类,使得对规则成员的访问非常简单。

在您的情况下,似乎只有当它是一个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

更好的方法(利用监听器为您所做的工作):注意:我将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());
        }
    }
}

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

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

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

 类似资料:
  • 我对数据库开发非常陌生,因此我对以下示例有一些疑问: 函数f1()-语言sql 函数f2()-语言plpgsql > 这两个函数都可以像或一样调用。 如果我调用选择f1('world'),输出将是: 并为输出: 错误:查询没有结果数据的目的地提示:如果要丢弃SELECT的结果,请改为使用PERFORM。上下文:PL/pgSQL函数f11(字符变化)第2行SQL语句 ********** 错误 **

  • 问题内容: 我已经创建了分区函数,但是无法将其应用于表。我不确定我要去哪里错。 这是我的分区函数: 尝试应用于此表: 但是,当我尝试执行表脚本时,出现此错误: 请帮忙。 分步骤重新发布我的代码 皮纳尔的补习非常棒!这是一个简短的摘要 为每个分区添加文件组 创建分区功能 AS RANGE left FOR VALUES (20120301) 创建分区方案 AS PARTITION Partition

  • 问题内容: 数据库开发 是一个非常新的事物,因此我对以下示例有一些疑问: 函数f1()- 语言sql 函数f2()- 语言plpgsql 这两个 函数 都可以称为或。 如果我打电话, 输出 将是: 并 输出 为: 错误:查询没有结果数据的目的地提示:如果要舍弃SELECT的结果,请改用PERFORM。上下文:SQL语句 *上的 PL / pgSQL函数f11(字符变化)第2行 * 错误 ** 我想

  • Kotlin中的参数与Java中有些不同。如你所见,我们先写参数的名字再写它的类型: fun add(x: Int, y: Int) : Int { return x + y } 我们可以给参数指定一个默认值使得它们变得可选,这是非常有帮助的。这里有一个例子,在Activity中创建了一个函数用来toast一段信息: fun toast(message: String, length: I

  • 在 Python 中,定义函数和调用函数都很简单,但如何定义函数参数和传递函数参数,则涉及到一些套路了。总的来说,Python 的函数参数主要分为以下几种: 必选参数 默认参数 可变参数 关键字参数 必选参数 必选参数可以说是最常见的了,顾名思义,必选参数就是在调用函数的时候要传入数量一致的参数,比如: >>> def add(x, y): # x, y 是必选参数 ...

  • 本文向大家介绍详解C语言中getgid()函数和getegid()函数的区别,包括了详解C语言中getgid()函数和getegid()函数的区别的使用技巧和注意事项,需要的朋友参考一下 C语言getgid()函数:取得组识别码函数 头文件: 定义函数: 函数说明:getgid()用来取得执行目前进程的组识别码。 返回值:返回组识别码 范例 执行: C语言getegid()函数:获得组识别码 头文