Skip to content

Commit 521a27f

Browse files
authored
fix: avoid recursion when deparsing statement lists (#2651)
1 parent a2e8a39 commit 521a27f

6 files changed

Lines changed: 310 additions & 29 deletions

File tree

src/main/java/net/sf/jsqlparser/statement/Block.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
*/
1010
package net.sf.jsqlparser.statement;
1111

12+
import java.util.function.Consumer;
13+
1214
public class Block implements Statement {
1315
private boolean hasSemicolonAfterEnd = false;
1416

@@ -36,9 +38,13 @@ public <T, S> T accept(StatementVisitor<T> statementVisitor, S context) {
3638
}
3739

3840
public StringBuilder appendTo(StringBuilder builder) {
41+
return appendTo(builder, builder::append);
42+
}
43+
44+
public StringBuilder appendTo(StringBuilder builder, Consumer<Statements> statementsPrinter) {
3945
builder.append("BEGIN\n");
4046
if (statements != null) {
41-
builder.append(statements);
47+
statementsPrinter.accept(statements);
4248
}
4349
builder.append("END");
4450
if (hasSemicolonAfterEnd) {

src/main/java/net/sf/jsqlparser/statement/IfElseStatement.java

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
package net.sf.jsqlparser.statement;
1212

1313
import java.util.Objects;
14+
import java.util.function.Consumer;
1415

1516
import net.sf.jsqlparser.expression.Expression;
1617

@@ -65,12 +66,21 @@ public void setUsingSemicolonForIfStatement(boolean usingSemicolonForIfStatement
6566
}
6667

6768
public StringBuilder appendTo(StringBuilder builder) {
68-
builder.append("IF ").append(condition).append(" ").append(ifStatement)
69-
.append(usingSemicolonForIfStatement ? ";" : "");
69+
return appendTo(builder, builder::append, builder::append);
70+
}
71+
72+
public StringBuilder appendTo(StringBuilder builder, Consumer<Expression> expressionPrinter,
73+
Consumer<Statement> statementPrinter) {
74+
builder.append("IF ");
75+
expressionPrinter.accept(condition);
76+
builder.append(' ');
77+
statementPrinter.accept(ifStatement);
78+
builder.append(usingSemicolonForIfStatement ? ";" : "");
7079

7180
if (elseStatement != null) {
72-
builder.append(" ELSE ").append(elseStatement)
73-
.append(usingSemicolonForElseStatement ? ";" : "");
81+
builder.append(" ELSE ");
82+
statementPrinter.accept(elseStatement);
83+
builder.append(usingSemicolonForElseStatement ? ";" : "");
7484
}
7585
return builder;
7686
}

src/main/java/net/sf/jsqlparser/statement/Statements.java

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
import java.io.Serializable;
1313
import java.util.ArrayList;
1414
import java.util.List;
15+
import java.util.function.Consumer;
1516

1617
public class Statements extends ArrayList<Statement> implements Serializable {
1718

@@ -34,17 +35,30 @@ public <E extends Statement> E get(Class<E> type, int index) {
3435
return type.cast(get(index));
3536
}
3637

37-
@Override
38-
public String toString() {
39-
StringBuilder b = new StringBuilder();
38+
/**
39+
* Appends each statement using the supplied renderer and the list's separators. Blocks and
40+
* IF/ELSE statements retain their own semicolon settings. Null entries produced by error
41+
* recovery retain the legacy "null;" placeholder without invoking the renderer.
42+
*/
43+
public StringBuilder appendTo(StringBuilder builder, Consumer<Statement> statementPrinter) {
4044
for (Statement stmt : this) {
41-
// IfElseStatements and Blocks control the Semicolons by themselves
42-
if (stmt instanceof IfElseStatement || stmt instanceof Block) {
43-
b.append(stmt).append("\n");
45+
if (stmt == null) {
46+
builder.append("null");
4447
} else {
45-
b.append(stmt).append(";\n");
48+
statementPrinter.accept(stmt);
49+
}
50+
// IfElseStatements and Blocks control the Semicolons by themselves
51+
if (!(stmt instanceof IfElseStatement || stmt instanceof Block)) {
52+
builder.append(';');
4653
}
54+
builder.append('\n');
4755
}
48-
return b.toString();
56+
return builder;
57+
}
58+
59+
@Override
60+
public String toString() {
61+
StringBuilder builder = new StringBuilder();
62+
return appendTo(builder, builder::append).toString();
4963
}
5064
}

src/main/java/net/sf/jsqlparser/util/deparser/StatementDeParser.java

Lines changed: 5 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -344,8 +344,7 @@ public <S> StringBuilder visit(AlterEvent alterEvent, S context) {
344344

345345
@Override
346346
public <S> StringBuilder visit(Statements statements, S context) {
347-
statements.accept(this, context);
348-
return builder;
347+
return statements.appendTo(builder, statement -> statement.accept(this, context));
349348
}
350349

351350
@Override
@@ -435,18 +434,7 @@ public <S> StringBuilder visit(ShowTablesStatement showTables, S context) {
435434

436435
@Override
437436
public <S> StringBuilder visit(Block block, S context) {
438-
builder.append("BEGIN\n");
439-
if (block.getStatements() != null) {
440-
for (Statement stmt : block.getStatements()) {
441-
stmt.accept(this, context);
442-
builder.append(";\n");
443-
}
444-
}
445-
builder.append("END");
446-
if (block.hasSemicolonAfterEnd()) {
447-
builder.append(";");
448-
}
449-
return builder;
437+
return block.appendTo(builder, statements -> statements.accept(this, context));
450438
}
451439

452440
@Override
@@ -563,8 +551,9 @@ public <S> StringBuilder visit(AlterSession alterSession, S context) {
563551

564552
@Override
565553
public <S> StringBuilder visit(IfElseStatement ifElseStatement, S context) {
566-
ifElseStatement.appendTo(builder);
567-
return builder;
554+
return ifElseStatement.appendTo(builder,
555+
expression -> expression.accept(expressionDeParser, context),
556+
statement -> statement.accept(this, context));
568557
}
569558

570559
@Override

src/site/sphinx/usage.rst

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -690,6 +690,20 @@ The object model works in both directions. Build the tree from Java and print it
690690
691691
Assertions.assertEquals(expectedSQLStr, builder.toString());
692692
693+
The same visitor can render an entire statement list:
694+
695+
.. code-block:: java
696+
697+
Statements statements = CCJSqlParserUtil.parseStatements("SELECT 1; SELECT 2;");
698+
StringBuilder script = new StringBuilder();
699+
statements.accept(new StatementDeParser(script), null);
700+
Assertions.assertEquals("SELECT 1;\nSELECT 2;\n", script.toString());
701+
702+
Statement lists and nested blocks share the separator policy used by
703+
``Statements.toString()``. Blocks and ``IF/ELSE`` statements retain their own
704+
semicolon settings. Custom deparsers receive each child statement and the
705+
``IF/ELSE`` condition through the visitor API, with the supplied context.
706+
693707

694708
ODBC timestamp intervals
695709
==============================

0 commit comments

Comments
 (0)