Skip to content

Commit 2de2417

Browse files
authored
Preserve EXEC bind arguments and support OUTPUT parameters (#2580)
1 parent 7bb6e1c commit 2de2417

8 files changed

Lines changed: 295 additions & 41 deletions

File tree

‎src/main/java/net/sf/jsqlparser/expression/ExpressionVisitor.java‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
package net.sf.jsqlparser.expression;
1111

1212
import java.util.List;
13+
import net.sf.jsqlparser.statement.execute.ExecuteArgument;
1314
import net.sf.jsqlparser.expression.operators.arithmetic.Addition;
1415
import net.sf.jsqlparser.expression.operators.arithmetic.BitwiseAnd;
1516
import net.sf.jsqlparser.expression.operators.arithmetic.BitwiseLeftShift;
@@ -72,6 +73,10 @@
7273

7374
public interface ExpressionVisitor<T> {
7475

76+
default <S> T visit(ExecuteArgument argument, S context) {
77+
return argument.getExpression().accept(this, context);
78+
}
79+
7580
default <S> T visitExpressions(ExpressionList<? extends Expression> expressions, S context) {
7681
if (expressions != null) {
7782
expressions.forEach(expression -> expression.accept(this, context));

‎src/main/java/net/sf/jsqlparser/statement/execute/Execute.java‎

Lines changed: 29 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,13 @@
1010
package net.sf.jsqlparser.statement.execute;
1111

1212
import java.util.Locale;
13+
import java.util.StringJoiner;
14+
import java.util.function.Consumer;
15+
import net.sf.jsqlparser.expression.Expression;
1316
import net.sf.jsqlparser.expression.operators.relational.ExpressionList;
1417
import net.sf.jsqlparser.expression.operators.relational.ParenthesedExpressionList;
1518
import net.sf.jsqlparser.statement.Statement;
1619
import net.sf.jsqlparser.statement.StatementVisitor;
17-
import net.sf.jsqlparser.statement.select.PlainSelect;
1820

1921
import java.util.List;
2022

@@ -33,13 +35,11 @@ public void setName(String name) {
3335
}
3436

3537
public void setName(List<String> names) {
38+
StringJoiner qualifiedName = new StringJoiner(".");
3639
for (String item : names) {
37-
if (this.name != null) {
38-
this.name = this.name + "." + item;
39-
} else {
40-
this.name = item;
41-
}
40+
qualifiedName.add(item == null ? "" : item);
4241
}
42+
this.name = qualifiedName.toString();
4343
}
4444

4545
public ExpressionList getExprList() {
@@ -70,11 +70,29 @@ public <T, S> T accept(StatementVisitor<T> statementVisitor, S context) {
7070

7171
@Override
7272
public String toString() {
73-
return execType.name() + " " + name
74-
+ (exprList != null
75-
? " " + PlainSelect.getStringList(exprList, true,
76-
exprList instanceof ParenthesedExpressionList)
77-
: "");
73+
StringBuilder builder = new StringBuilder();
74+
return appendTo(builder, builder::append).toString();
75+
}
76+
77+
public StringBuilder appendTo(StringBuilder builder, Consumer<Expression> expressionPrinter) {
78+
builder.append(execType.name()).append(' ').append(name);
79+
if (exprList != null) {
80+
builder.append(' ');
81+
boolean brackets = exprList instanceof ParenthesedExpressionList;
82+
if (brackets) {
83+
builder.append('(');
84+
}
85+
for (int i = 0; i < exprList.size(); i++) {
86+
if (i > 0) {
87+
builder.append(", ");
88+
}
89+
expressionPrinter.accept((Expression) exprList.get(i));
90+
}
91+
if (brackets) {
92+
builder.append(')');
93+
}
94+
}
95+
return builder;
7896
}
7997

8098
public Execute withExecType(ExecType execType) {
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
/*-
2+
* #%L
3+
* JSQLParser library
4+
* %%
5+
* Copyright (C) 2004 - 2026 JSQLParser
6+
* %%
7+
* Dual licensed under GNU LGPL 2.1 or Apache License 2.0
8+
* #L%
9+
*/
10+
package net.sf.jsqlparser.statement.execute;
11+
12+
import java.util.Objects;
13+
import java.util.function.Consumer;
14+
import net.sf.jsqlparser.expression.Expression;
15+
import net.sf.jsqlparser.expression.ExpressionVisitor;
16+
import net.sf.jsqlparser.parser.ASTNodeAccessImpl;
17+
18+
/** An EXEC argument with a call-specific OUTPUT modifier. */
19+
public class ExecuteArgument extends ASTNodeAccessImpl implements Expression {
20+
private Expression expression;
21+
private boolean output;
22+
23+
public ExecuteArgument(Expression expression, boolean output) {
24+
setExpression(expression);
25+
this.output = output;
26+
}
27+
28+
public Expression getExpression() {
29+
return expression;
30+
}
31+
32+
public void setExpression(Expression expression) {
33+
this.expression = Objects.requireNonNull(expression, "expression");
34+
}
35+
36+
public boolean isOutput() {
37+
return output;
38+
}
39+
40+
public void setOutput(boolean output) {
41+
this.output = output;
42+
}
43+
44+
@Override
45+
public <T, S> T accept(ExpressionVisitor<T> visitor, S context) {
46+
return visitor.visit(this, context);
47+
}
48+
49+
public StringBuilder appendTo(StringBuilder builder, Consumer<Expression> expressionPrinter) {
50+
expressionPrinter.accept(expression);
51+
if (output) {
52+
builder.append(" OUTPUT");
53+
}
54+
return builder;
55+
}
56+
57+
@Override
58+
public String toString() {
59+
StringBuilder builder = new StringBuilder();
60+
return appendTo(builder, builder::append).toString();
61+
}
62+
}

‎src/main/java/net/sf/jsqlparser/util/TablesNamesFinder.java‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,7 @@
129129
import net.sf.jsqlparser.statement.delete.ParenthesedDelete;
130130
import net.sf.jsqlparser.statement.drop.Drop;
131131
import net.sf.jsqlparser.statement.execute.Execute;
132+
import net.sf.jsqlparser.statement.execute.ExecuteArgument;
132133
import net.sf.jsqlparser.statement.export.Export;
133134
import net.sf.jsqlparser.statement.grant.Grant;
134135
import net.sf.jsqlparser.statement.imprt.Import;
@@ -1698,6 +1699,11 @@ public <S> Void visit(Execute execute, S context) {
16981699
return null;
16991700
}
17001701

1702+
@Override
1703+
public <S> Void visit(ExecuteArgument argument, S context) {
1704+
return argument.getExpression().accept(this, context);
1705+
}
1706+
17011707
@Override
17021708
public void visit(Execute execute) {
17031709
StatementVisitor.super.visit(execute);

‎src/main/java/net/sf/jsqlparser/util/deparser/ExecuteDeParser.java‎

Lines changed: 1 addition & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,7 @@
99
*/
1010
package net.sf.jsqlparser.util.deparser;
1111

12-
import java.util.List;
1312

14-
import net.sf.jsqlparser.expression.Expression;
1513
import net.sf.jsqlparser.expression.ExpressionVisitor;
1614
import net.sf.jsqlparser.statement.execute.Execute;
1715

@@ -27,24 +25,7 @@ public ExecuteDeParser(ExpressionVisitor<StringBuilder> expressionVisitor,
2725

2826
@Override
2927
public void deParse(Execute execute) {
30-
builder.append(execute.getExecType().name()).append(" ").append(execute.getName());
31-
if (execute.isParenthesis()) {
32-
builder.append(" (");
33-
} else if (execute.getExprList() != null) {
34-
builder.append(" ");
35-
}
36-
if (execute.getExprList() != null) {
37-
List<Expression> expressions = execute.getExprList().getExpressions();
38-
for (int i = 0; i < expressions.size(); i++) {
39-
if (i > 0) {
40-
builder.append(", ");
41-
}
42-
expressions.get(i).accept(expressionVisitor, null);
43-
}
44-
}
45-
if (execute.isParenthesis()) {
46-
builder.append(")");
47-
}
28+
execute.appendTo(builder, expression -> expression.accept(expressionVisitor, null));
4829
}
4930

5031
public ExpressionVisitor<StringBuilder> getExpressionVisitor() {

‎src/main/java/net/sf/jsqlparser/util/deparser/ExpressionDeParser.java‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1419,6 +1419,12 @@ public <S> StringBuilder visit(UserVariable var, S context) {
14191419
return builder;
14201420
}
14211421

1422+
@Override
1423+
public <S> StringBuilder visit(
1424+
net.sf.jsqlparser.statement.execute.ExecuteArgument argument, S context) {
1425+
return argument.appendTo(builder, expression -> expression.accept(this, context));
1426+
}
1427+
14221428
@Override
14231429
public <S> StringBuilder visit(NumericBind bind, S context) {
14241430
builder.append(bind.toString());

‎src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt‎

Lines changed: 46 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,21 @@ public class CCJSqlParser extends AbstractJSqlParser<CCJSqlParser> {
126126
// and must not be taken as the JSON path operator
127127
private int mapKeyDepth = 0;
128128

129+
private Expression createOutputArgument(Expression argument) throws ParseException {
130+
Expression value = argument;
131+
if (argument instanceof VariableAssignment) {
132+
value = ((VariableAssignment) argument).getExpression();
133+
} else if (argument instanceof EqualsTo
134+
&& ((EqualsTo) argument).getLeftExpression() instanceof UserVariable) {
135+
value = ((EqualsTo) argument).getRightExpression();
136+
}
137+
if (!(value instanceof UserVariable || value instanceof JdbcParameter
138+
|| value instanceof JdbcNamedParameter)) {
139+
throw new ParseException("An OUTPUT argument must be a variable or bind parameter");
140+
}
141+
return new net.sf.jsqlparser.statement.execute.ExecuteArgument(argument, true);
142+
}
143+
129144
private void linkAST(ASTNodeAccess access, Node node) {
130145
access.setASTNode(node);
131146
node.jjtSetValue(access);
@@ -10678,27 +10693,48 @@ EqualsTo VariableExpression(): {
1067810693
}
1067910694

1068010695
Execute Execute(): {
10681-
Token token;
1068210696
ObjectNames funcName;
10683-
ExpressionList expressionList = null;
10697+
ExpressionList expressionList;
1068410698
Execute execute = new Execute();
10685-
List<Expression> namedExprList;
10686-
Expression expr;
1068710699
}
1068810700
{
1068910701
(<K_EXEC> { execute.setExecType(Execute.ExecType.EXEC); }
1069010702
| <K_EXECUTE> { execute.setExecType(Execute.ExecType.EXECUTE); }
1069110703
| <K_CALL> { execute.setExecType(Execute.ExecType.CALL); } )
1069210704

10693-
funcName=RelObjectNames() { execute.setName(funcName.getNames()); }
10705+
// A colon starts a bind argument here; it must not join the procedure name.
10706+
funcName=ColumnIdentifier() { execute.setName(funcName.getNames()); }
10707+
[ LOOKAHEAD(2) expressionList=ExecuteArguments() { execute.setExprList(expressionList); } ]
10708+
{ return execute; }
10709+
}
1069410710

10711+
ExpressionList ExecuteArguments(): {
10712+
ExpressionList arguments = new ExpressionList();
10713+
Expression argument;
10714+
}
10715+
{
1069510716
(
10696-
LOOKAHEAD(2) expressionList=ExpressionList() { execute.setExprList(expressionList); }
10697-
)?
10717+
LOOKAHEAD("(") arguments=ExpressionList()
10718+
|
10719+
argument=ExecuteArgument() { arguments.add(argument); }
10720+
( "," argument=ExecuteArgument() { arguments.add(argument); } )*
10721+
)
10722+
{ return arguments; }
10723+
}
10724+
10725+
Expression ExecuteArgument(): {
10726+
Expression argument;
10727+
boolean output = false;
10728+
}
10729+
{
10730+
argument=Expression()
10731+
[
10732+
LOOKAHEAD({ isAccessKeywordAhead("OUTPUT") || isAccessKeywordAhead("OUT") })
10733+
( <K_OUTPUT> | AccessKeyword("OUT") )
10734+
{ output = true; }
10735+
]
10736+
{ return output ? createOutputArgument(argument) : argument; }
1069810737

10699-
{
10700-
return execute;
10701-
}
1070210738
}
1070310739

1070410740
FullTextSearch FullTextSearch() : {

0 commit comments

Comments
 (0)