使用这种语法,我试图从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,因为它们都被识别为标识符并隐藏在语法中的表达式后面?
如果您查看语法,您可以看到下面的“主表达式”解析器规则
(您的问题中的树形图中引用了该选项):
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()函数:获得组识别码 头文