区分 sql 语法中的函数名和函数参数
Differentiate function name and function arguments in sql grammar
有了这个 grammar ,我试图从 sql 查询中提取用户编写的表达式。
例如,我想从此查询中提取 FNAME、LName 和名称。
SELECT TRIM(CONCAT(FNAME , LNAME)) AS `name`FROM CLIENTS;
[解析树]
我可以用我的听众提取 'name' :
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
class 以非常简单地访问规则成员。
在您的情况下,您似乎只关心那个替代方案 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());
}
}
}
在您的示例中,这不会更改输出,但应该让您了解如何 select 更具体的使用上下文。
在“你可以从这里到达那里”的意义上,它是可能的:
@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 堆栈,然后 push/pop 当你 enter/exit 函数。
我通常编写代码来访问我的 parseTree,创建我想在我的程序中处理的简化内部树,但这是一个比这更长的答案,已经很长了,回复。
简而言之(为时已晚),它可能很复杂,您必须处理这种复杂性,除非您知道您只需要处理一个更简单的案例子集。
有了这个 grammar ,我试图从 sql 查询中提取用户编写的表达式。
例如,我想从此查询中提取 FNAME、LName 和名称。
SELECT TRIM(CONCAT(FNAME , LNAME)) AS `name`FROM CLIENTS;
[解析树]
我可以用我的听众提取 'name' :
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
class 以非常简单地访问规则成员。
在您的情况下,您似乎只关心那个替代方案 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());
}
}
}
在您的示例中,这不会更改输出,但应该让您了解如何 select 更具体的使用上下文。
在“你可以从这里到达那里”的意义上,它是可能的:
@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 堆栈,然后 push/pop 当你 enter/exit 函数。
我通常编写代码来访问我的 parseTree,创建我想在我的程序中处理的简化内部树,但这是一个比这更长的答案,已经很长了,回复。
简而言之(为时已晚),它可能很复杂,您必须处理这种复杂性,除非您知道您只需要处理一个更简单的案例子集。