Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 31 additions & 29 deletions src/main/java/net/sf/jsqlparser/parser/CCJSqlParserUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
import java.io.Reader;
import java.util.Stack;
import java.util.concurrent.Callable;
import java.util.concurrent.CancellationException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
Expand Down Expand Up @@ -129,6 +131,9 @@ public static Statement parse(String sql, ExecutorService executorService,
LOGGER.info("Trying SIMPLE parsing " + (allowComplex ? "first" : "only"));
statement = parseStatement(parser.withAllowComplexParsing(false), executorService);
} catch (JSQLParserException ex) {
if (wasExecutionStopped(ex)) {
throw ex;
}
LOGGER.info("Nesting Depth" + getNestingDepth(sql));
if (allowComplex
&& (allowedNestingDepth < 0 || getNestingDepth(sql) <= allowedNestingDepth)) {
Expand Down Expand Up @@ -407,24 +412,7 @@ public static Expression parseCondExpression(String conditionalExpressionStr,

public static Statement parseStatement(CCJSqlParser parser, ExecutorService executorService)
throws JSQLParserException {
Statement statement;
Future<Statement> future = executorService.submit(new Callable<Statement>() {
@Override
public Statement call() throws ParseException {
return parser.Statement();
}
});
try {
statement = future.get(parser.getAsLong(Feature.timeOut),
TimeUnit.MILLISECONDS);
} catch (TimeoutException ex) {
parser.interrupted = true;
future.cancel(true);
throw new JSQLParserException("Time out occurred.", ex);
} catch (Exception ex) {
throw new JSQLParserException(ex);
}
return statement;
return executeParser(parser, executorService, parser::Statement);
}

/**
Expand Down Expand Up @@ -478,6 +466,9 @@ public static Statements parseStatements(String sqls, ExecutorService executorSe
try {
return parseStatements(parser.withAllowComplexParsing(false), executorService);
} catch (JSQLParserException ex) {
if (wasExecutionStopped(ex)) {
throw ex;
}
// when fast simple parsing fails, try complex parsing but only if it has a chance to
// succeed
if (allowComplex
Expand All @@ -502,24 +493,35 @@ public static Statements parseStatements(String sqls, ExecutorService executorSe
*/
public static Statements parseStatements(CCJSqlParser parser, ExecutorService executorService)
throws JSQLParserException {
Statements statements = null;
Future<Statements> future = executorService.submit(new Callable<Statements>() {
@Override
public Statements call() throws ParseException {
return parser.Statements();
}
});
return executeParser(parser, executorService, parser::Statements);
}

private static <T> T executeParser(CCJSqlParser parser, ExecutorService executorService,
Callable<T> operation) throws JSQLParserException {
Future<T> future = executorService.submit(operation);
try {
statements = future.get(parser.getAsLong(Feature.timeOut),
TimeUnit.MILLISECONDS);
return future.get(parser.getAsLong(Feature.timeOut), TimeUnit.MILLISECONDS);
} catch (InterruptedException ex) {
parser.interrupted = true;
future.cancel(true);
Thread.currentThread().interrupt();
throw new JSQLParserException(ex);
} catch (TimeoutException ex) {
parser.interrupted = true;
future.cancel(true);
throw new JSQLParserException("Time out occurred.", ex);
} catch (Exception ex) {
} catch (CancellationException ex) {
parser.interrupted = true;
throw new JSQLParserException(ex);
} catch (ExecutionException ex) {
throw new JSQLParserException(ex);
}
return statements;
}

private static boolean wasExecutionStopped(JSQLParserException exception) {
Throwable cause = exception.getCause();
return cause instanceof InterruptedException || cause instanceof TimeoutException
|| cause instanceof CancellationException;
}

public static void streamStatements(StatementListener listener, InputStream is, String encoding)
Expand Down
2 changes: 1 addition & 1 deletion src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ public class CCJSqlParser extends AbstractJSqlParser<CCJSqlParser> {
public final static Logger LOGGER = Logger.getLogger(CCJSqlParser.class.getName());
public int bracketsCounter = 0;
public int caseCounter = 0;
public boolean interrupted = false;
public volatile boolean interrupted = false;

public CCJSqlParser withConfiguration(FeatureConfiguration configuration) {
token_source.configuration = configuration;
Expand Down
142 changes: 142 additions & 0 deletions src/test/java/net/sf/jsqlparser/parser/ParserExecutionTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
/*-
* #%L
* JSQLParser library
* %%
* Copyright (C) 2004 - 2019 JSQLParser
* %%
* Dual licensed under GNU LGPL 2.1 or Apache License 2.0
* #L%
*/
package net.sf.jsqlparser.parser;

import static org.junit.jupiter.api.Assertions.*;
import java.util.List;
import java.util.concurrent.AbstractExecutorService;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.Executors;
import net.sf.jsqlparser.JSQLParserException;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;

class ParserExecutionTest {
private static class QueuedExecutor extends AbstractExecutorService {
Future<?> task;
int submissions;
boolean shutdown;

@Override
public void execute(Runnable command) {
task = (Future<?>) command;
submissions++;
}

@Override
public void shutdown() {
shutdown = true;
}

@Override
public List<Runnable> shutdownNow() {
shutdown();
return List.of();
}

@Override
public boolean isShutdown() {
return shutdown;
}

@Override
public boolean isTerminated() {
return shutdown;
}

@Override
public boolean awaitTermination(long timeout, TimeUnit unit) {
return shutdown;
}
}

@ParameterizedTest
@ValueSource(booleans = {false, true})
void cancelsTheSubmittedTaskAndPreservesCallerInterruption(boolean multiple) {
QueuedExecutor executor = new QueuedExecutor();
CCJSqlParser parser = CCJSqlParserUtil.newParser("SELECT 1");
Thread.currentThread().interrupt();
try {
JSQLParserException error = assertThrows(JSQLParserException.class, () -> {
if (multiple) {
CCJSqlParserUtil.parseStatements(parser, executor);
} else {
CCJSqlParserUtil.parseStatement(parser, executor);
}
});
assertInstanceOf(InterruptedException.class, error.getCause());
assertTrue(Thread.currentThread().isInterrupted());
assertTrue(parser.interrupted);
assertTrue(executor.task.isCancelled());
assertFalse(executor.isShutdown());
} finally {
Thread.interrupted();
}
}

@ParameterizedTest
@ValueSource(booleans = {false, true})
void doesNotRetryInterruptedConvenienceCalls(boolean multiple) {
QueuedExecutor executor = new QueuedExecutor();
Thread.currentThread().interrupt();
try {
JSQLParserException error = assertThrows(JSQLParserException.class, () -> {
if (multiple) {
CCJSqlParserUtil.parseStatements("SELECT 1", executor, null);
} else {
CCJSqlParserUtil.parse("SELECT 1", executor, null);
}
});
assertInstanceOf(InterruptedException.class, error.getCause());
assertEquals(1, executor.submissions);
assertTrue(executor.task.isCancelled());
assertTrue(Thread.currentThread().isInterrupted());
} finally {
Thread.interrupted();
}
}

@ParameterizedTest
@ValueSource(booleans = {false, true})
void timeoutCancelsOneTaskWithoutRetryingOrInterruptingTheCaller(boolean multiple) {
QueuedExecutor executor = new QueuedExecutor();
JSQLParserException error = assertThrows(JSQLParserException.class, () -> {
if (multiple) {
CCJSqlParserUtil.parseStatements("SELECT 1", executor,
parser -> parser.withTimeOut(0));
} else {
CCJSqlParserUtil.parse("SELECT 1", executor, parser -> parser.withTimeOut(0));
}
});
assertInstanceOf(TimeoutException.class, error.getCause());
assertEquals(1, executor.submissions);
assertTrue(executor.task.isCancelled());
assertFalse(Thread.currentThread().isInterrupted());
assertFalse(executor.isShutdown());
}

@Test
void successfulAndInvalidParsingKeepTheCallerExecutorUsable() throws Exception {
var executor = Executors.newSingleThreadExecutor();
try {
assertEquals("SELECT 1", CCJSqlParserUtil.parse("SELECT 1", executor, null).toString());
assertThrows(JSQLParserException.class,
() -> CCJSqlParserUtil.parse("SELECT FROM", executor, null));
assertEquals("SELECT 2;\n",
CCJSqlParserUtil.parseStatements("SELECT 2", executor, null).toString());
assertFalse(executor.isShutdown());
} finally {
executor.shutdownNow();
}
}
}
Loading