如何解决区分sql语法中的函数名和函数参数
使用这个 grammar ,我试图从 sql 查询中提取用户编写的表达式。
例如,我想从这个查询中提取 FNAME、LName 和姓名。
SELECT TRIM(CONCAT(FNAME,LNAME)) AS `name`FROM CLIENTS;
[解析树]
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
更好的方法(利用听众为您做的事情):注意:我将 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 举报,一经查实,本站将立刻删除。