Skip to content

Configurable Parser Timeout via Feature #1592

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Jul 19, 2022
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
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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;
}
}
}
});
}
}