Skip to content

Commit 4f0488c

Browse files
fix: Complex Parsing Approach
- optionally provide a global Executor, instead spawning one for each parse - run into Complex Parsing only, when Complex Parsing was allowed - provide a Logger - fixes #1792
1 parent da32442 commit 4f0488c

8 files changed

Lines changed: 232 additions & 88 deletions

File tree

src/main/java/net/sf/jsqlparser/parser/CCJSqlParserUtil.java

Lines changed: 84 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@
1919
import java.util.concurrent.TimeUnit;
2020
import java.util.concurrent.TimeoutException;
2121
import java.util.function.Consumer;
22+
import java.util.logging.Level;
23+
import java.util.logging.Logger;
24+
2225
import net.sf.jsqlparser.JSQLParserException;
2326
import net.sf.jsqlparser.expression.Expression;
2427
import net.sf.jsqlparser.parser.feature.Feature;
@@ -33,13 +36,25 @@
3336

3437
@SuppressWarnings("PMD.CyclomaticComplexity")
3538
public final class CCJSqlParserUtil {
39+
public final static Logger LOGGER = Logger.getLogger(CCJSqlParserUtil.class.getName());
40+
static {
41+
LOGGER.setLevel(Level.WARNING);
42+
}
43+
3644
public final static int ALLOWED_NESTING_DEPTH = 10;
3745

3846
private CCJSqlParserUtil() {}
3947

4048
public static Statement parse(Reader statementReader) throws JSQLParserException {
49+
ExecutorService executorService = Executors.newSingleThreadExecutor();
50+
Statement statement = null;
4151
CCJSqlParser parser = new CCJSqlParser(new StreamProvider(statementReader));
42-
return parseStatement(parser);
52+
try {
53+
statement = parseStatement(parser, executorService);
54+
} finally {
55+
executorService.shutdown();
56+
}
57+
return statement;
4358
}
4459

4560
public static Statement parse(String sql) throws JSQLParserException {
@@ -62,22 +77,41 @@ public static Statement parse(String sql) throws JSQLParserException {
6277
*/
6378
public static Statement parse(String sql, Consumer<CCJSqlParser> consumer)
6479
throws JSQLParserException {
80+
81+
ExecutorService executorService = Executors.newSingleThreadExecutor();
82+
Statement statement = null;
83+
try {
84+
statement = parse(sql, executorService, consumer);
85+
} finally {
86+
executorService.shutdown();
87+
}
88+
return statement;
89+
}
90+
91+
public static Statement parse(String sql, ExecutorService executorService,
92+
Consumer<CCJSqlParser> consumer)
93+
throws JSQLParserException {
6594
Statement statement = null;
6695

6796
// first, try to parse fast and simple
97+
CCJSqlParser parser = newParser(sql);
98+
if (consumer != null) {
99+
consumer.accept(parser);
100+
}
101+
boolean allowComplex = parser.getConfiguration().getAsBoolean(Feature.allowComplexParsing);
102+
LOGGER.info("Allowed Complex Parsing: " + allowComplex);
68103
try {
69-
CCJSqlParser parser = newParser(sql).withAllowComplexParsing(false);
70-
if (consumer != null) {
71-
consumer.accept(parser);
72-
}
73-
statement = parseStatement(parser);
104+
LOGGER.info("Trying SIMPLE parsing " + (allowComplex ? "first" : "only"));
105+
statement = parseStatement(parser.withAllowComplexParsing(false), executorService);
74106
} catch (JSQLParserException ex) {
75-
if (getNestingDepth(sql) <= ALLOWED_NESTING_DEPTH) {
76-
CCJSqlParser parser = newParser(sql).withAllowComplexParsing(true);
107+
if (allowComplex && getNestingDepth(sql) <= ALLOWED_NESTING_DEPTH) {
108+
LOGGER.info("Trying COMPLEX parsing when SIMPLE parsing failed");
109+
// beware: the parser must not be reused, but needs to be re-initiated
110+
parser = newParser(sql);
77111
if (consumer != null) {
78112
consumer.accept(parser);
79113
}
80-
statement = parseStatement(parser);
114+
statement = parseStatement(parser.withAllowComplexParsing(true), executorService);
81115
} else {
82116
throw ex;
83117
}
@@ -252,24 +286,25 @@ public static Expression parseCondExpression(String conditionalExpressionStr,
252286
}
253287

254288
/**
255-
* @param parser
256-
* @return the statement parsed
257-
* @throws JSQLParserException
289+
* @param parser the Parser armed with a Statement text
290+
* @param executorService the Executor Service for parsing within a Thread
291+
* @return the parsed Statement
292+
* @throws JSQLParserException when either the Statement can't be parsed or the configured
293+
* timeout is reached
258294
*/
259-
public static Statement parseStatement(CCJSqlParser parser) throws JSQLParserException {
295+
296+
public static Statement parseStatement(CCJSqlParser parser, ExecutorService executorService)
297+
throws JSQLParserException {
260298
Statement statement = null;
261299
try {
262-
ExecutorService executorService = Executors.newSingleThreadExecutor();
263300
Future<Statement> future = executorService.submit(new Callable<Statement>() {
264301
@Override
265302
public Statement call() throws Exception {
266303
return parser.Statement();
267304
}
268305
});
269-
executorService.shutdown();
270-
271-
statement = future.get( parser.getConfiguration().getAsLong(Feature.timeOut), TimeUnit.MILLISECONDS);
272-
306+
statement = future.get(parser.getConfiguration().getAsLong(Feature.timeOut),
307+
TimeUnit.MILLISECONDS);
273308
} catch (TimeoutException ex) {
274309
parser.interrupted = true;
275310
throw new JSQLParserException("Time out occurred.", ex);
@@ -288,55 +323,68 @@ public static Statements parseStatements(String sqls) throws JSQLParserException
288323
return parseStatements(sqls, null);
289324
}
290325

326+
public static Statements parseStatements(String sqls, Consumer<CCJSqlParser> consumer)
327+
throws JSQLParserException {
328+
ExecutorService executorService = Executors.newSingleThreadExecutor();
329+
final Statements statements = parseStatements(sqls, executorService, consumer);
330+
executorService.shutdown();
331+
332+
return statements;
333+
}
334+
291335
/**
292336
* Parse a statement list.
293337
*
294338
* @return the statements parsed
295339
*/
296-
public static Statements parseStatements(String sqls, Consumer<CCJSqlParser> consumer)
340+
public static Statements parseStatements(String sqls, ExecutorService executorService,
341+
Consumer<CCJSqlParser> consumer)
297342
throws JSQLParserException {
298343
Statements statements = null;
299344

345+
CCJSqlParser parser = newParser(sqls);
346+
if (consumer != null) {
347+
consumer.accept(parser);
348+
}
349+
boolean allowComplex = parser.getConfiguration().getAsBoolean(Feature.allowComplexParsing);
350+
300351
// first, try to parse fast and simple
301352
try {
302-
CCJSqlParser parser = newParser(sqls).withAllowComplexParsing(false);
303-
if (consumer != null) {
304-
consumer.accept(parser);
305-
}
306-
statements = parseStatements(parser);
353+
statements = parseStatements(parser.withAllowComplexParsing(false), executorService);
307354
} catch (JSQLParserException ex) {
308355
// when fast simple parsing fails, try complex parsing but only if it has a chance to
309356
// succeed
310-
if (getNestingDepth(sqls) <= ALLOWED_NESTING_DEPTH) {
311-
CCJSqlParser parser = newParser(sqls).withAllowComplexParsing(true);
357+
if (allowComplex && getNestingDepth(sqls) <= ALLOWED_NESTING_DEPTH) {
358+
// beware: parser must not be re-used but needs to be re-initiated
359+
parser = newParser(sqls);
312360
if (consumer != null) {
313361
consumer.accept(parser);
314362
}
315-
statements = parseStatements(parser);
363+
statements = parseStatements(parser.withAllowComplexParsing(true), executorService);
316364
}
317365
}
318366
return statements;
319367
}
320368

321369
/**
322-
* @param parser
323-
* @return the statements parsed
324-
* @throws JSQLParserException
370+
* @param parser the Parser armed with a Statement text
371+
* @param executorService the Executor Service for parsing within a Thread
372+
* @return the Statements (representing a List of single statements)
373+
* @throws JSQLParserException when either the Statement can't be parsed or the configured
374+
* timeout is reached
325375
*/
326-
public static Statements parseStatements(CCJSqlParser parser) throws JSQLParserException {
376+
public static Statements parseStatements(CCJSqlParser parser, ExecutorService executorService)
377+
throws JSQLParserException {
327378
Statements statements = null;
328379
try {
329-
ExecutorService executorService = Executors.newSingleThreadExecutor();
330380
Future<Statements> future = executorService.submit(new Callable<Statements>() {
331381
@Override
332382
public Statements call() throws Exception {
333383
return parser.Statements();
334384
}
335385
});
336-
executorService.shutdown();
337-
338-
statements = future.get( parser.getConfiguration().getAsLong(Feature.timeOut) , TimeUnit.MILLISECONDS);
339-
386+
statements = future.get(parser.getConfiguration().getAsLong(Feature.timeOut),
387+
TimeUnit.MILLISECONDS);
340388
} catch (TimeoutException ex) {
341389
parser.interrupted = true;
342390
throw new JSQLParserException("Time out occurred.", ex);

src/main/java/net/sf/jsqlparser/util/validation/ParseCapability.java

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,16 @@
99
*/
1010
package net.sf.jsqlparser.util.validation;
1111

12+
import java.util.concurrent.ExecutorService;
13+
import java.util.concurrent.Executors;
1214
import java.util.function.Consumer;
1315
import net.sf.jsqlparser.JSQLParserException;
1416
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
1517
import net.sf.jsqlparser.statement.Statements;
1618

1719
/**
18-
* package - private class for {@link Validation} to parse the statements
19-
* within it's own {@link ValidationCapability}
20+
* package - private class for {@link Validation} to parse the statements within it's own
21+
* {@link ValidationCapability}
2022
*
2123
* @author gitmotte
2224
*/
@@ -36,20 +38,25 @@ public String getStatements() {
3638
}
3739

3840
/**
39-
* @return <code>null</code> on parse error, otherwise the {@link Statements}
40-
* parsed.
41+
* @return <code>null</code> on parse error, otherwise the {@link Statements} parsed.
4142
*/
4243
public Statements getParsedStatements() {
4344
return parsedStatement;
4445
}
4546

4647
@Override
4748
public void validate(ValidationContext context, Consumer<ValidationException> errorConsumer) {
49+
ExecutorService executorService = Executors.newSingleThreadExecutor();
4850
try {
4951
this.parsedStatement = CCJSqlParserUtil.parseStatements(
50-
CCJSqlParserUtil.newParser(statements).withConfiguration(context.getConfiguration()));
52+
CCJSqlParserUtil.newParser(statements)
53+
.withConfiguration(context.getConfiguration()),
54+
executorService);
5155
} catch (JSQLParserException e) {
52-
errorConsumer.accept(new ParseException("Cannot parse statement: " + e.getMessage(), e));
56+
errorConsumer
57+
.accept(new ParseException("Cannot parse statement: " + e.getMessage(), e));
58+
} finally {
59+
executorService.shutdown();
5360
}
5461
}
5562

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

Lines changed: 26 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -3081,7 +3081,7 @@ Expression AndExpression() :
30813081
}
30823082
{
30833083
(
3084-
LOOKAHEAD(Condition())
3084+
LOOKAHEAD(Condition(), {!interrupted})
30853085
left=Condition()
30863086
|
30873087
[ <K_NOT> { not=true; } | "!" { not=true; exclamationMarkNot=true; } ]
@@ -3093,7 +3093,7 @@ Expression AndExpression() :
30933093
{ boolean useOperator = false; }
30943094
(<K_AND> | <K_AND_OPERATOR> {useOperator=true;} )
30953095
(
3096-
LOOKAHEAD(Condition())
3096+
LOOKAHEAD(Condition(), {!interrupted})
30973097
right=Condition()
30983098
|
30993099
[ <K_NOT> { not=true; } | "!" { not=true; exclamationMarkNot=true; } ]
@@ -3216,8 +3216,8 @@ Expression SQLCondition():
32163216
{
32173217
(
32183218
result=ExistsExpression()
3219-
| LOOKAHEAD(InExpression()) result=InExpression()
3220-
| LOOKAHEAD(OverlapsCondition()) result=OverlapsCondition()
3219+
| LOOKAHEAD(InExpression() , {!interrupted}) result=InExpression()
3220+
| LOOKAHEAD(OverlapsCondition(), {!interrupted}) result=OverlapsCondition()
32213221
| left = SimpleExpression() { result = left; }
32223222
[
32233223
LOOKAHEAD(2) (
@@ -3258,7 +3258,7 @@ Expression InExpression() #InExpression :
32583258
(
32593259
LOOKAHEAD(2) token=<S_CHAR_LITERAL> { rightExpression = new StringValue(token.image); }
32603260
| LOOKAHEAD(3) rightExpression = Function()
3261-
| LOOKAHEAD(ParenthesedSelect()) rightExpression = ParenthesedSelect()
3261+
| LOOKAHEAD(ParenthesedSelect(), {!interrupted}) rightExpression = ParenthesedSelect()
32623262
| LOOKAHEAD(3) rightExpression = ParenthesedExpressionList()
32633263
| rightExpression = SimpleExpression()
32643264
)
@@ -4057,6 +4057,7 @@ JsonExpression JsonExpression() : {
40574057
CastExpression castExpr = null;
40584058
}
40594059
{
4060+
// LOOKAHEAD(3, {getAsBoolean(Feature.allowComplexParsing) && !interrupted})
40604061
(
40614062
LOOKAHEAD(3, {!interrupted}) expr=CaseWhenExpression()
40624063
|
@@ -4066,13 +4067,13 @@ JsonExpression JsonExpression() : {
40664067
|
40674068
expr=UserVariable()
40684069
|
4069-
LOOKAHEAD(JsonFunction(), {!interrupted}) expr = JsonFunction()
4070+
LOOKAHEAD(JsonFunction(), {getAsBoolean(Feature.allowComplexParsing) && !interrupted}) expr = JsonFunction()
40704071
|
4071-
LOOKAHEAD(JsonAggregateFunction(), {!interrupted}) expr = JsonAggregateFunction()
4072+
LOOKAHEAD(JsonAggregateFunction(), {getAsBoolean(Feature.allowComplexParsing) && !interrupted}) expr = JsonAggregateFunction()
40724073
|
4073-
LOOKAHEAD(FullTextSearch(), {!interrupted}) expr = FullTextSearch()
4074+
LOOKAHEAD(FullTextSearch(), {getAsBoolean(Feature.allowComplexParsing) && !interrupted}) expr = FullTextSearch()
40744075
|
4075-
LOOKAHEAD( 3 , {!interrupted && getAsBoolean(Feature.allowComplexParsing) } ) expr=Function()
4076+
LOOKAHEAD( 3 , {getAsBoolean(Feature.allowComplexParsing) && !interrupted} ) expr=Function()
40764077
|
40774078
LOOKAHEAD( 2, {!interrupted} ) expr=Column()
40784079
|
@@ -4551,9 +4552,14 @@ Expression CaseWhenExpression() #CaseWhenExpression:
45514552
<K_CASE> { caseCounter++; }
45524553
[ switchExp=Expression() ]
45534554
( clause=WhenThenSearchCondition() { whenClauses.add(clause); } )+
4554-
[<K_ELSE> (LOOKAHEAD( ["("] CaseWhenExpression() [")"] ( <K_WHEN> | <K_ELSE> | <K_END> ) ) ["("] elseExp=CaseWhenExpression() [")" { ((CaseExpression) elseExp).setUsingBrackets(true); } ]
4555-
| elseExp=Expression()
4556-
)
4555+
[
4556+
<K_ELSE>
4557+
(
4558+
LOOKAHEAD(3, {!interrupted}) "(" elseExp=CaseWhenExpression() ")" { elseExp = new Parenthesis( elseExp ); }
4559+
| LOOKAHEAD(3, {!interrupted}) elseExp=CaseWhenExpression()
4560+
| LOOKAHEAD(3, {getAsBoolean(Feature.allowComplexParsing) && !interrupted}) elseExp=Expression()
4561+
| elseExp=SimpleExpression()
4562+
)
45574563
]
45584564
<K_END> { caseCounter--; }
45594565
{
@@ -4567,16 +4573,17 @@ Expression CaseWhenExpression() #CaseWhenExpression:
45674573
WhenClause WhenThenSearchCondition():
45684574
{
45694575
WhenClause whenThen = new WhenClause();
4570-
Expression whenExp = null;
4571-
Expression thenExp = null;
4576+
Expression whenExp;
4577+
Expression thenExp;
45724578
}
45734579
{
45744580
<K_WHEN> whenExp=Expression()
4575-
<K_THEN> (
4576-
LOOKAHEAD( ["("] CaseWhenExpression() [")"] ( <K_WHEN> | <K_ELSE> | <K_END> ) ) ["("] thenExp=CaseWhenExpression() [")" { ((CaseExpression) thenExp).setUsingBrackets(true); }]
4577-
|
4578-
thenExp=Expression()
4579-
)
4581+
<K_THEN>
4582+
(
4583+
LOOKAHEAD({getAsBoolean(Feature.allowComplexParsing) && !interrupted}) thenExp=Expression()
4584+
|
4585+
thenExp=SimpleExpression()
4586+
)
45804587
{
45814588
whenThen.setWhenExpression(whenExp);
45824589
whenThen.setThenExpression(thenExp);

0 commit comments

Comments
 (0)