Skip to content

Commit

Permalink
Configurable Parser Timeout via Feature (#1592)
Browse files Browse the repository at this point in the history
* Configurable Parser Timeout via Feature

Fixes #1582
Implement Parser Timeout Feature, e. g. `CCJSqlParserUtil.parse(sqlStr, parser -> parser.withTimeOut(60000));`
Add a special test failing after a long time only, to test TimeOut vs. Parser Exception

* Appease Codacy

* Appease Codacy

Co-authored-by: Tobias <t.warneke@gmx.net>
  • Loading branch information
manticore-projects and wumpz committed Jul 19, 2022
1 parent cfba6e5 commit 7400013
Show file tree
Hide file tree
Showing 6 changed files with 83 additions and 4 deletions.
1 change: 1 addition & 0 deletions README.md
Expand Up @@ -73,6 +73,7 @@ Additionally, we have fixed many errors and improved the code quality and the te
* support table option **character set** and **index** options
* support Postgresql optional **TABLE** in **TRUNCATE**
* support for `ANALYZE mytable`
* Implement Parser Timeout Feature, e. g. `CCJSqlParserUtil.parse(sqlStr, parser -> parser.withTimeOut(60000));`
* extended support Postgres' `Extract( field FROM source)` where `field` is a String instead of a Keyword


Expand Down
13 changes: 13 additions & 0 deletions src/main/java/net/sf/jsqlparser/parser/AbstractJSqlParser.java
Expand Up @@ -32,12 +32,21 @@ public P withAllowComplexParsing(boolean allowComplexParsing) {
public P withUnsupportedStatements(boolean allowUnsupportedStatements) {
return withFeature(Feature.allowUnsupportedStatements, allowUnsupportedStatements);
}

public P withTimeOut(int timeOutMillSeconds) {
return withFeature(Feature.timeOut, timeOutMillSeconds);
}

public P withFeature(Feature f, boolean enabled) {
getConfiguration().setValue(f, enabled);
return me();
}

public P withFeature(Feature f, int value) {
getConfiguration().setValue(f, value);
return me();
}

public abstract FeatureConfiguration getConfiguration();

public abstract P me();
Expand All @@ -46,6 +55,10 @@ public boolean getAsBoolean(Feature f) {
return getConfiguration().getAsBoolean(f);
}

public Integer getAsInteger(Feature f) {
return getConfiguration().getAsInteger(f);
}

public void setErrorRecovery(boolean errorRecovery) {
this.errorRecovery = errorRecovery;
}
Expand Down
6 changes: 3 additions & 3 deletions src/main/java/net/sf/jsqlparser/parser/CCJSqlParserUtil.java
Expand Up @@ -21,6 +21,7 @@
import java.util.function.Consumer;
import net.sf.jsqlparser.JSQLParserException;
import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.parser.feature.Feature;
import net.sf.jsqlparser.statement.Statement;
import net.sf.jsqlparser.statement.Statements;

Expand All @@ -33,7 +34,6 @@
@SuppressWarnings("PMD.CyclomaticComplexity")
public final class CCJSqlParserUtil {
public final static int ALLOWED_NESTING_DEPTH = 10;
public static final int PARSER_TIMEOUT = 6000;

private CCJSqlParserUtil() {
}
Expand Down Expand Up @@ -255,7 +255,7 @@ public Statement call() throws Exception {
});
executorService.shutdown();

statement = future.get(PARSER_TIMEOUT, TimeUnit.MILLISECONDS);
statement = future.get( parser.getConfiguration().getAsInteger(Feature.timeOut), TimeUnit.MILLISECONDS);
} catch (TimeoutException ex) {
parser.interrupted = true;
throw new JSQLParserException("Time out occurred.", ex);
Expand Down Expand Up @@ -319,7 +319,7 @@ public Statements call() throws Exception {
});
executorService.shutdown();

statements = future.get(PARSER_TIMEOUT, TimeUnit.MILLISECONDS);
statements = future.get( parser.getConfiguration().getAsInteger(Feature.timeOut) , TimeUnit.MILLISECONDS);
} catch (TimeoutException ex) {
parser.interrupted = true;
throw new JSQLParserException("Time out occurred.", ex);
Expand Down
2 changes: 2 additions & 0 deletions src/main/java/net/sf/jsqlparser/parser/feature/Feature.java
Expand Up @@ -741,6 +741,8 @@ public enum Feature {
* needs to be switched off, when VALIDATING statements or parsing blocks
*/
allowUnsupportedStatements(false),

timeOut( 6000)
;

private Object value;
Expand Down
Expand Up @@ -58,7 +58,11 @@ public Object getValue(Feature feature) {
}

public boolean getAsBoolean(Feature f) {
return Boolean.valueOf(String.valueOf(getValue(f)));
return Boolean.parseBoolean(String.valueOf(getValue(f)));
}

public Integer getAsInteger(Feature f) {
return Integer.valueOf(String.valueOf(getValue(f)));
}

public String getAsString(Feature f) {
Expand Down
59 changes: 59 additions & 0 deletions src/test/java/net/sf/jsqlparser/parser/CCJSqlParserUtilTest.java
Expand Up @@ -14,6 +14,8 @@
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeoutException;

import net.sf.jsqlparser.JSQLParserException;
import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.expression.LongValue;
Expand All @@ -29,6 +31,7 @@

import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.function.Executable;

public class CCJSqlParserUtilTest {

Expand Down Expand Up @@ -270,4 +273,60 @@ public void testCondExpressionIssue1482_2() throws JSQLParserException {
Expression expr = CCJSqlParserUtil.parseCondExpression("test_table_enum.f1_enum IN ('TEST2'::test.\"test_enum\")", false);
assertEquals("test_table_enum.f1_enum IN ('TEST2'::test.\"test_enum\")", expr.toString());
}

@Test
public void testTimeOutIssue1582() throws InterruptedException {
// This statement is INVALID on purpose
// There are crafted INTO keywords in order to make it fail but only after a long time (40 seconds plus)

String sqlStr = "" +
"select\n" +
" t0.operatienr\n" +
" , case\n" +
" when\n" +
" case when (t0.vc_begintijd_operatie is null or lpad((extract('hours' into t0.vc_begintijd_operatie::timestamp))::text,2,'0') ||':'|| lpad(extract('minutes' from t0.vc_begintijd_operatie::timestamp)::text,2,'0') = '00:00') then null\n" +
" else (greatest(((extract('hours' into (t0.vc_eindtijd_operatie::timestamp-t0.vc_begintijd_operatie::timestamp))*60 + extract('minutes' from (t0.vc_eindtijd_operatie::timestamp-t0.vc_begintijd_operatie::timestamp)))/60)::numeric(12,2),0))*60\n" +
" end = 0 then null\n" +
" else '25. Meer dan 4 uur'\n" +
" end \n" +
" as snijtijd_interval";

// With DEFAULT TIMEOUT 6 Seconds, we expect the statement to timeout normally
// A TimeoutException wrapped into a Parser Exception should be thrown

assertThrows(TimeoutException.class, new Executable() {
@Override
public void execute() throws Throwable {
try {
CCJSqlParserUtil.parse(sqlStr);
} catch (JSQLParserException ex) {
Throwable cause = ((JSQLParserException) ex).getCause();
if (cause!=null) {
throw cause;
} else {
throw ex;
}
}
}
});

// With custom TIMEOUT 60 Seconds, we expect the statement to not timeout but to fail instead
// No TimeoutException wrapped into a Parser Exception must be thrown
// Instead we expect a Parser Exception only
assertThrows(JSQLParserException.class, new Executable() {
@Override
public void execute() throws Throwable {
try {
CCJSqlParserUtil.parse(sqlStr, parser -> parser.withTimeOut(60000));
} catch (JSQLParserException ex) {
Throwable cause = ((JSQLParserException) ex).getCause();
if (cause instanceof TimeoutException) {
throw cause;
} else {
throw ex;
}
}
}
});
}
}

0 comments on commit 7400013

Please sign in to comment.