diff --git a/build.gradle b/build.gradle
index 127a97f70..0ebc5d626 100644
--- a/build.gradle
+++ b/build.gradle
@@ -1,18 +1,18 @@
plugins {
id 'java'
id 'maven-publish'
- id "ca.coglinc2.javacc" version "3.0.0"
+ id "ca.coglinc2.javacc" version "latest.release"
id 'jacoco'
- id "com.github.spotbugs" version "4.7.2"
+ id "com.github.spotbugs" version "latest.release"
id 'pmd'
id 'checkstyle'
// download the RR tools which have no Maven Repository
- id "de.undercouch.download" version "4.1.2"
+ id "de.undercouch.download" version "latest.release"
}
group = 'com.github.jsqlparser'
-version = '4.4-SNAPSHOT'
+version = '4.5-SNAPSHOT'
description = 'JSQLParser library'
java.sourceCompatibility = JavaVersion.VERSION_1_8
@@ -23,29 +23,29 @@ repositories {
maven {
url = uri('https://repo.maven.apache.org/maven2/')
}
+
maven {
url "https://plugins.gradle.org/m2/"
}
}
dependencies {
- testImplementation 'commons-io:commons-io:2.11.0'
- testImplementation 'junit:junit:4.13.2'
- testImplementation 'org.mockito:mockito-core:4.3.1'
- testImplementation 'org.assertj:assertj-core:3.22.0'
- testImplementation 'org.apache.commons:commons-lang3:3.12.0'
- testImplementation 'com.h2database:h2:2.1.210'
+ testImplementation 'commons-io:commons-io:2.+'
+ testImplementation 'org.mockito:mockito-core:4.+'
+ testImplementation 'org.assertj:assertj-core:3.+'
+ testImplementation 'org.hamcrest:hamcrest-core:2.+'
+ testImplementation 'org.apache.commons:commons-lang3:3.+'
+ testImplementation 'com.h2database:h2:2.+'
// for JaCoCo Reports
- testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.2'
- testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine'
-
+ testImplementation 'org.junit.jupiter:junit-jupiter-api:5.+'
+ testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.+'
+
// https://mvnrepository.com/artifact/org.mockito/mockito-junit-jupiter
- testImplementation 'org.mockito:mockito-junit-jupiter:4.3.1'
-
+ testImplementation 'org.mockito:mockito-junit-jupiter:4.+'
+
// enforce latest version of JavaCC
javacc 'net.java.dev.javacc:javacc:7.0.10'
-
}
compileJavacc {
@@ -58,7 +58,6 @@ java {
spotbugs
pmd
-
}
jacoco {
@@ -173,7 +172,7 @@ spotbugs {
pmd {
consoleOutput = false
- toolVersion = "6.36.0"
+ toolVersion = "6.41.0"
sourceSets = [sourceSets.main]
@@ -192,7 +191,7 @@ pmd {
}
checkstyle {
- toolVersion "8.45.1"
+ toolVersion "9.2"
sourceSets = [sourceSets.main, sourceSets.test]
configFile =rootProject.file('config/checkstyle/checkstyle.xml')
}
@@ -254,6 +253,7 @@ task renderRR() {
publishing {
publications {
maven(MavenPublication) {
+ artifactId 'jsqlparser'
from(components.java)
}
}
diff --git a/ruleset.xml b/ruleset.xml
index 1d06a9911..f7d2fcc15 100644
--- a/ruleset.xml
+++ b/ruleset.xml
@@ -103,7 +103,7 @@ under the License.
-
+
diff --git a/src/main/java/net/sf/jsqlparser/parser/CCJSqlParserUtil.java b/src/main/java/net/sf/jsqlparser/parser/CCJSqlParserUtil.java
index ca0874c19..f6ca99421 100644
--- a/src/main/java/net/sf/jsqlparser/parser/CCJSqlParserUtil.java
+++ b/src/main/java/net/sf/jsqlparser/parser/CCJSqlParserUtil.java
@@ -12,6 +12,12 @@
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
import java.util.function.Consumer;
import net.sf.jsqlparser.JSQLParserException;
import net.sf.jsqlparser.expression.Expression;
@@ -23,8 +29,11 @@
*
* @author toben
*/
+
+@SuppressWarnings("PMD.CyclomaticComplexity")
public final class CCJSqlParserUtil {
public final static int ALLOWED_NESTING_DEPTH = 10;
+ public static final int PARSER_TIMEOUT = 6000;
private CCJSqlParserUtil() {
}
@@ -54,13 +63,25 @@ public static Statement parse(String sql) throws JSQLParserException {
* @throws JSQLParserException
*/
public static Statement parse(String sql, Consumer consumer) throws JSQLParserException {
- boolean allowComplexParsing = getNestingDepth(sql)<=ALLOWED_NESTING_DEPTH;
-
- CCJSqlParser parser = newParser(sql).withAllowComplexParsing(allowComplexParsing);
- if (consumer != null) {
- consumer.accept(parser);
+ Statement statement = null;
+
+ // first, try to parse fast and simple
+ try {
+ CCJSqlParser parser = newParser(sql).withAllowComplexParsing(false);
+ if (consumer != null) {
+ consumer.accept(parser);
+ }
+ statement = parseStatement(parser);
+ } catch (JSQLParserException ex) {
+ if (getNestingDepth(sql)<=ALLOWED_NESTING_DEPTH) {
+ CCJSqlParser parser = newParser(sql).withAllowComplexParsing(true);
+ if (consumer != null) {
+ consumer.accept(parser);
+ }
+ statement = parseStatement(parser);
+ }
}
- return parseStatement(parser);
+ return statement;
}
public static CCJSqlParser newParser(String sql) {
@@ -112,24 +133,44 @@ public static Expression parseExpression(String expression, boolean allowPartial
});
}
- public static Expression parseExpression(String expression, boolean allowPartialParse, Consumer consumer) throws JSQLParserException {
- boolean allowComplexParsing = getNestingDepth(expression)<=ALLOWED_NESTING_DEPTH;
-
- CCJSqlParser parser = newParser(expression).withAllowComplexParsing(allowComplexParsing);
- if (consumer != null) {
- consumer.accept(parser);
- }
+ @SuppressWarnings("PMD.CyclomaticComplexity")
+ public static Expression parseExpression(String expressionStr, boolean allowPartialParse, Consumer consumer) throws JSQLParserException {
+ Expression expression = null;
+
+ // first, try to parse fast and simple
try {
- Expression expr = parser.Expression();
- if (!allowPartialParse && parser.getNextToken().kind != CCJSqlParserTokenManager.EOF) {
- throw new JSQLParserException("could only parse partial expression " + expr.toString());
+ CCJSqlParser parser = newParser(expressionStr).withAllowComplexParsing(false);
+ if (consumer != null) {
+ consumer.accept(parser);
+ }
+ try {
+ expression = parser.Expression();
+ if (parser.getNextToken().kind != CCJSqlParserTokenManager.EOF) {
+ throw new JSQLParserException("could only parse partial expression " + expression.toString());
+ }
+ } catch (ParseException ex) {
+ throw new JSQLParserException(ex);
+ }
+ } catch (JSQLParserException ex1) {
+ // when fast simple parsing fails, try complex parsing but only if it has a chance to succeed
+ if (getNestingDepth(expressionStr)<=ALLOWED_NESTING_DEPTH) {
+ CCJSqlParser parser = newParser(expressionStr).withAllowComplexParsing(true);
+ if (consumer != null) {
+ consumer.accept(parser);
+ }
+ try {
+ expression = parser.Expression();
+ if (!allowPartialParse && parser.getNextToken().kind != CCJSqlParserTokenManager.EOF) {
+ throw new JSQLParserException("could only parse partial expression " + expression.toString());
+ }
+ } catch (JSQLParserException ex) {
+ throw ex;
+ } catch (ParseException ex) {
+ throw new JSQLParserException(ex);
+ }
}
- return expr;
- } catch (JSQLParserException ex) {
- throw ex;
- } catch (ParseException ex) {
- throw new JSQLParserException(ex);
}
+ return expression;
}
/**
@@ -158,24 +199,43 @@ public static Expression parseCondExpression(String condExpr, boolean allowParti
});
}
- public static Expression parseCondExpression(String condExpr, boolean allowPartialParse, Consumer consumer) throws JSQLParserException {
- boolean allowComplexParsing = getNestingDepth(condExpr)<=ALLOWED_NESTING_DEPTH;
-
- CCJSqlParser parser = newParser(condExpr).withAllowComplexParsing(allowComplexParsing);
- if (consumer != null) {
- consumer.accept(parser);
- }
+ @SuppressWarnings("PMD.CyclomaticComplexity")
+ public static Expression parseCondExpression(String conditionalExpressionStr, boolean allowPartialParse, Consumer consumer) throws JSQLParserException {
+ Expression expression = null;
+
+ // first, try to parse fast and simple
try {
- Expression expr = parser.Expression();
- if (!allowPartialParse && parser.getNextToken().kind != CCJSqlParserTokenManager.EOF) {
- throw new JSQLParserException("could only parse partial expression " + expr.toString());
+ CCJSqlParser parser = newParser(conditionalExpressionStr).withAllowComplexParsing(false);
+ if (consumer != null) {
+ consumer.accept(parser);
+ }
+ try {
+ expression = parser.Expression();
+ if (parser.getNextToken().kind != CCJSqlParserTokenManager.EOF) {
+ throw new JSQLParserException("could only parse partial expression " + expression.toString());
+ }
+ } catch (ParseException ex) {
+ throw new JSQLParserException(ex);
+ }
+ } catch (JSQLParserException ex1) {
+ if (getNestingDepth(conditionalExpressionStr)<=ALLOWED_NESTING_DEPTH) {
+ CCJSqlParser parser = newParser(conditionalExpressionStr).withAllowComplexParsing(true);
+ if (consumer != null) {
+ consumer.accept(parser);
+ }
+ try {
+ expression = parser.Expression();
+ if (!allowPartialParse && parser.getNextToken().kind != CCJSqlParserTokenManager.EOF) {
+ throw new JSQLParserException("could only parse partial expression " + expression.toString());
+ }
+ } catch (JSQLParserException ex) {
+ throw ex;
+ } catch (ParseException ex) {
+ throw new JSQLParserException(ex);
+ }
}
- return expr;
- } catch (JSQLParserException ex) {
- throw ex;
- } catch (ParseException ex) {
- throw new JSQLParserException(ex);
}
+ return expression;
}
/**
@@ -184,11 +244,25 @@ public static Expression parseCondExpression(String condExpr, boolean allowParti
* @throws JSQLParserException
*/
public static Statement parseStatement(CCJSqlParser parser) throws JSQLParserException {
+ Statement statement = null;
try {
- return parser.Statement();
+ ExecutorService executorService = Executors.newSingleThreadExecutor();
+ Future future = executorService.submit(new Callable() {
+ @Override
+ public Statement call() throws Exception {
+ return parser.Statement();
+ }
+ });
+ executorService.shutdown();
+
+ statement = future.get(PARSER_TIMEOUT, TimeUnit.MILLISECONDS);
+ } catch (TimeoutException ex) {
+ parser.interrupted = true;
+ throw new JSQLParserException("Time out occurred.", ex);
} catch (Exception ex) {
throw new JSQLParserException(ex);
}
+ return statement;
}
/**
@@ -197,10 +271,20 @@ public static Statement parseStatement(CCJSqlParser parser) throws JSQLParserExc
* @return the statements parsed
*/
public static Statements parseStatements(String sqls) throws JSQLParserException {
- boolean allowComplexParsing = getNestingDepth(sqls)<=ALLOWED_NESTING_DEPTH;
-
- CCJSqlParser parser = newParser(sqls).withAllowComplexParsing(allowComplexParsing);
- return parseStatements(parser);
+ Statements statements = null;
+
+ // first, try to parse fast and simple
+ try {
+ CCJSqlParser parser = newParser(sqls).withAllowComplexParsing(false);
+ statements = parseStatements(parser);
+ } catch (JSQLParserException ex) {
+ // when fast simple parsing fails, try complex parsing but only if it has a chance to succeed
+ if (getNestingDepth(sqls)<=ALLOWED_NESTING_DEPTH) {
+ CCJSqlParser parser = newParser(sqls).withAllowComplexParsing(true);
+ statements = parseStatements(parser);
+ }
+ }
+ return statements;
}
/**
@@ -209,11 +293,25 @@ public static Statements parseStatements(String sqls) throws JSQLParserException
* @throws JSQLParserException
*/
public static Statements parseStatements(CCJSqlParser parser) throws JSQLParserException {
+ Statements statements = null;
try {
- return parser.Statements();
+ ExecutorService executorService = Executors.newSingleThreadExecutor();
+ Future future = executorService.submit(new Callable() {
+ @Override
+ public Statements call() throws Exception {
+ return parser.Statements();
+ }
+ });
+ executorService.shutdown();
+
+ statements = future.get(PARSER_TIMEOUT, TimeUnit.MILLISECONDS);
+ } catch (TimeoutException ex) {
+ parser.interrupted = true;
+ throw new JSQLParserException("Time out occurred.", ex);
} catch (Exception ex) {
throw new JSQLParserException(ex);
}
+ return statements;
}
public static void streamStatements(StatementListener listener, InputStream is, String encoding) throws JSQLParserException {
diff --git a/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt b/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt
index 1f26d4068..d313e5266 100644
--- a/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt
+++ b/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt
@@ -69,7 +69,8 @@ import java.util.*;
public class CCJSqlParser extends AbstractJSqlParser {
public int bracketsCounter = 0;
public int caseCounter = 0;
-
+ public boolean interrupted = false;
+
public CCJSqlParser withConfiguration(FeatureConfiguration configuration) {
token_source.configuration = configuration;
return this;
@@ -3356,7 +3357,7 @@ ExpressionList SimpleExpressionList(boolean outerBrackets) #ExpressionList:
}
{
expr=SimpleExpression() { expressions.add(expr); }
- ( LOOKAHEAD(2) "," expr=SimpleExpression() { expressions.add(expr); } )*
+ ( LOOKAHEAD(2, {!interrupted} ) "," expr=SimpleExpression() { expressions.add(expr); } )*
{
retval.setExpressions(expressions);
return retval;
@@ -3376,7 +3377,7 @@ ExpressionList ComplexExpressionList() #ExpressionList:
) { expressions.add(expr); }
(
- LOOKAHEAD(2) ","
+ LOOKAHEAD(2, {!interrupted}) ","
(
LOOKAHEAD(2) expr=OracleNamedFunctionParameter()
| expr=Expression()
@@ -3717,37 +3718,35 @@ Expression PrimaryExpression() #PrimaryExpression:
(
{ retval = new NullValue(); }
- | LOOKAHEAD(3) retval=CaseWhenExpression()
+ | LOOKAHEAD(3, {!interrupted}) retval=CaseWhenExpression()
| retval = SimpleJdbcParameter()
- | LOOKAHEAD(2) retval=JdbcNamedParameter()
+ | LOOKAHEAD(2, {!interrupted}) retval=JdbcNamedParameter()
| retval=UserVariable()
- | LOOKAHEAD(2) retval=NumericBind()
+ | LOOKAHEAD(2, {!interrupted}) retval=NumericBind()
- | LOOKAHEAD(3) retval=ExtractExpression()
+ | LOOKAHEAD(3, {!interrupted}) retval=ExtractExpression()
| retval=MySQLGroupConcat()
| retval=XMLSerializeExpr()
- | LOOKAHEAD(JsonExpression()) retval=JsonExpression()
+ | LOOKAHEAD(JsonExpression(), {!interrupted}) retval=JsonExpression()
- | LOOKAHEAD(Function()) retval=Function() [ LOOKAHEAD(2) retval = AnalyticExpression( (Function) retval ) ]
+ | LOOKAHEAD(JsonFunction(), {!interrupted}) retval = JsonFunction()
- | LOOKAHEAD(JsonFunction()) retval = JsonFunction()
-
- | LOOKAHEAD(JsonAggregateFunction()) retval = JsonAggregateFunction()
+ | LOOKAHEAD(JsonAggregateFunction(), {!interrupted}) retval = JsonAggregateFunction()
/* | LOOKAHEAD(FunctionWithCondParams()) retval = FunctionWithCondParams() */
- | LOOKAHEAD(FullTextSearch()) retval = FullTextSearch()
-
+ | LOOKAHEAD(FullTextSearch(), {!interrupted}) retval = FullTextSearch()
+ | LOOKAHEAD(Function(), {!interrupted}) retval=Function() [ LOOKAHEAD(2) retval = AnalyticExpression( (Function) retval ) ]
- | LOOKAHEAD(2) retval = IntervalExpression() { dateExpressionAllowed = false; }
+ | LOOKAHEAD(2, {!interrupted}) retval = IntervalExpression() { dateExpressionAllowed = false; }
| token= { retval = new DoubleValue(token.image); }
@@ -3755,26 +3754,26 @@ Expression PrimaryExpression() #PrimaryExpression:
| token= { retval = new HexValue(token.image); }
- | LOOKAHEAD(2) retval=CastExpression()
+ | LOOKAHEAD(2, {!interrupted}) retval=CastExpression()
- | LOOKAHEAD(2) retval=TryCastExpression()
+ | LOOKAHEAD(2, {!interrupted}) retval=TryCastExpression()
//| LOOKAHEAD(2) retval=RowConstructor()
// support timestamp expressions
| (token= | token=) { retval = new TimeKeyExpression(token.image); }
- | LOOKAHEAD(2) retval=DateTimeLiteralExpression()
+ | LOOKAHEAD(2, {!interrupted}) retval=DateTimeLiteralExpression()
- | LOOKAHEAD(2) retval=ArrayConstructor(true)
+ | LOOKAHEAD(2, {!interrupted}) retval=ArrayConstructor(true)
- | LOOKAHEAD(2) retval = NextValExpression()
+ | LOOKAHEAD(2, {!interrupted}) retval = NextValExpression()
| retval=ConnectByRootOperator()
- | LOOKAHEAD(2) { retval = new AllValue(); }
+ | LOOKAHEAD(2, {!interrupted}) { retval = new AllValue(); }
- | LOOKAHEAD(2) retval=Column()
+ | LOOKAHEAD(2, {!interrupted}) retval=Column()
| token= { retval = new StringValue(token.image); linkAST(retval,jjtThis); }
@@ -3784,30 +3783,17 @@ Expression PrimaryExpression() #PrimaryExpression:
| "{ts" token= "}" { retval = new TimestampValue(token.image); }
- | LOOKAHEAD("(" retval=SubSelect() ")") "(" retval=SubSelect() ")"
+ | LOOKAHEAD("(" retval=SubSelect() ")", {!interrupted} ) "(" retval=SubSelect() ")"
| (
- (
- (LOOKAHEAD({getAsBoolean(Feature.allowComplexParsing)}) "(" list = ComplexExpressionList() ")"
- {
- if (list.getExpressions().size() == 1) {
- retval = new Parenthesis(list.getExpressions().get(0));
- } else {
- retval = new RowConstructor().withExprList(list);
- }
+ "(" ( LOOKAHEAD( { getAsBoolean(Feature.allowComplexParsing) && !interrupted } ) list = ComplexExpressionList() | list = SimpleExpressionList(true) ) ")"
+ {
+ if (list.getExpressions().size() == 1) {
+ retval = new Parenthesis(list.getExpressions().get(0));
+ } else {
+ retval = new RowConstructor().withExprList(list);
}
- )
-
- | ("(" list = SimpleExpressionList(true) ")"
- {
- if (list.getExpressions().size() == 1) {
- retval = new Parenthesis(list.getExpressions().get(0));
- } else {
- retval = new RowConstructor().withExprList(list);
- }
- }
- )
- )
+ }
["." tmp=RelObjectNameExt() { retval = new RowGetExpression(retval, tmp); }]
)
)
@@ -4543,28 +4529,6 @@ FullTextSearch FullTextSearch() : {
}
}
-/* Function FunctionWithCondParams() #Function: {
- Function retval = new Function();
- String funcName = null;
- ExpressionList expressionList = null;
- Token token = null;
-}
-{
- (token = | token = | token = ) { funcName=token.image; }
-
- "("
- expressionList=ComplexExpressionList()
- ")"
-
- {
- retval.setParameters(expressionList);
- retval.setName(funcName);
- linkAST(retval,jjtThis);
- return retval;
- }
-} */
-
-
Function Function() #Function:
{
Function retval = new Function();
diff --git a/src/test/java/net/sf/jsqlparser/statement/select/NestedBracketsPerformanceTest.java b/src/test/java/net/sf/jsqlparser/statement/select/NestedBracketsPerformanceTest.java
index 5eb514d4d..ee6fc7bc2 100644
--- a/src/test/java/net/sf/jsqlparser/statement/select/NestedBracketsPerformanceTest.java
+++ b/src/test/java/net/sf/jsqlparser/statement/select/NestedBracketsPerformanceTest.java
@@ -131,9 +131,12 @@ public void testRecursiveBracketExpressionIssue1019() {
assertEquals("IF(1=1, IF(1=1, IF(1=1, 1, 2), 2), 2)", buildRecursiveBracketExpression("IF(1=1, $1, 2)", "1", 2));
}
+ // maxDepth = 10 collides with the Parser Timeout = 6 seconds
+ // temporarily restrict it to maxDepth = 6 for the moment
+ // @todo: implement methods to set the Parser Timeout explicitly and on demand
@Test
public void testRecursiveBracketExpressionIssue1019_2() throws JSQLParserException {
- doIncreaseOfParseTimeTesting("IF(1=1, $1, 2)", "1", 10);
+ doIncreaseOfParseTimeTesting("IF(1=1, $1, 2)", "1", 8);
}
@Test
diff --git a/src/test/java/net/sf/jsqlparser/statement/select/SelectTest.java b/src/test/java/net/sf/jsqlparser/statement/select/SelectTest.java
index 301891f70..7852b928f 100644
--- a/src/test/java/net/sf/jsqlparser/statement/select/SelectTest.java
+++ b/src/test/java/net/sf/jsqlparser/statement/select/SelectTest.java
@@ -15,6 +15,11 @@
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+
+import net.sf.jsqlparser.parser.CCJSqlParser;
import net.sf.jsqlparser.JSQLParserException;
import net.sf.jsqlparser.expression.*;
import net.sf.jsqlparser.expression.operators.arithmetic.Addition;
@@ -35,6 +40,8 @@
import net.sf.jsqlparser.statement.StatementVisitorAdapter;
import net.sf.jsqlparser.statement.Statements;
import static net.sf.jsqlparser.test.TestUtils.*;
+
+import net.sf.jsqlparser.test.MemoryLeakVerifier;
import org.apache.commons.io.IOUtils;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertEquals;
@@ -43,8 +50,12 @@
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
+
+import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.Timeout;
+import org.junit.jupiter.api.function.Executable;
import org.junit.jupiter.api.parallel.Execution;
import org.junit.jupiter.api.parallel.ExecutionMode;
@@ -1025,7 +1036,6 @@ public void testDistinctWithFollowingBrackets() throws JSQLParserException {
assertThat(selectBody.getDistinct())
.isNotNull()
.hasFieldOrPropertyWithValue("onSelectItems", null);
-
assertThat(selectBody.getSelectItems().get(0).toString())
.isEqualTo("(phone)");
}
@@ -5043,6 +5053,133 @@ public void testIgnoreNullsForWindowFunctionsIssue1429() throws JSQLParserExcept
assertSqlCanBeParsedAndDeparsed("SELECT lag(mydata) IGNORE NULLS OVER (ORDER BY sortorder) AS previous_status FROM mytable");
}
+ @Test
+ @Timeout(1000)
+ public void testPerformanceIssue1438() throws JSQLParserException {
+ assertSqlCanBeParsedAndDeparsed("" +
+ "SELECT \t* FROM TABLE_1 t1\n" +
+ "WHERE\n" +
+ "\t(((t1.COL1 = 'VALUE2' )\n" +
+ "\t\tAND (t1.CAL2 = 'VALUE2' ))\n" +
+ "\t\tAND (((1 = 1 )\n" +
+ "\t\t\tAND ((((((t1.id IN (940550 ,940600 ,940650 ,940700 ,940750 ,940800 ,940850 ,940900 ,940950 ,941000 ,941050 ,941100 ,941150 ,941200 ,941250 ,941300 ,941350 ,941400 ,941450 ,941500 ,941550 ,941600 ,941650 ,941700 ,941750 ,941800 ,941850 ,941900 ,941950 ,942000 ,942050 ,942100 ,942150 ,942200 ,942250 ,942300 ,942350 ,942400 ,942450 ,942500 ,942550 ,942600 ,942650 ,942700 ,942750 ,942800 ,942850 ,942900 ,942950 ,943000 ,943050 ,943100 ,943150 ,943200 ,943250 ,943300 ,943350 ,943400 ,943450 ,943500 ,943550 ,943600 ,943650 ,943700 ,943750 ,943800 ,943850 ,943900 ,943950 ,944000 ,944050 ,944100 ,944150 ,944200 ,944250 ,944300 ,944350 ,944400 ,944450 ,944500 ,944550 ,944600 ,944650 ,944700 ,944750 ,944800 ,944850 ,944900 ,944950 ,945000 ,945050 ,945100 ,945150 ,945200 ,945250 ,945300 ))\n" +
+ "\t\t\t\tOR (t1.id IN (945350 ,945400 ,945450 ,945500 ,945550 ,945600 ,945650 ,945700 ,945750 ,945800 ,945850 ,945900 ,945950 ,946000 ,946050 ,946100 ,946150 ,946200 ,946250 ,946300 ,946350 ,946400 ,946450 ,946500 ,946550 ,946600 ,946650 ,946700 ,946750 ,946800 ,946850 ,946900 ,946950 ,947000 ,947050 ,947100 ,947150 ,947200 ,947250 ,947300 ,947350 ,947400 ,947450 ,947500 ,947550 ,947600 ,947650 ,947700 ,947750 ,947800 ,947850 ,947900 ,947950 ,948000 ,948050 ,948100 ,948150 ,948200 ,948250 ,948300 ,948350 ,948400 ,948450 ,948500 ,948550 ,948600 ,948650 ,948700 ,948750 ,948800 ,948850 ,948900 ,948950 ,949000 ,949050 ,949100 ,949150 ,949200 ,949250 ,949300 ,949350 ,949400 ,949450 ,949500 ,949550 ,949600 ,949650 ,949700 ,949750 ,949800 ,949850 ,949900 ,949950 ,950000 ,950050 ,950100 )))\n" +
+ "\t\t\t\tOR (t1.id IN (950150 ,950200 ,950250 ,950300 ,950350 ,950400 ,950450 ,950500 ,950550 ,950600 ,950650 ,950700 ,950750 ,950800 ,950850 ,950900 ,950950 ,951000 ,951050 ,951100 ,951150 ,951200 ,951250 ,951300 ,951350 ,951400 ,951450 ,951500 ,951550 ,951600 ,951650 ,951700 ,951750 ,951800 ,951850 ,951900 ,951950 ,952000 ,952050 ,952100 ,952150 ,952200 ,952250 ,952300 ,952350 ,952400 ,952450 ,952500 ,952550 ,952600 ,952650 ,952700 ,952750 ,952800 ,952850 ,952900 ,952950 ,953000 ,953050 ,953100 ,953150 ,953200 ,953250 ,953300 ,953350 ,953400 ,953450 ,953500 ,953550 ,953600 ,953650 ,953700 )))\n" +
+ "\t\t\t\tOR (t1.id IN (953750 ,953800 ,953850 ,953900 ,953950 ,954000 ,954050 ,954100 ,954150 ,954200 ,954250 ,954300 ,954350 ,954400 ,954450 ,954500 ,954550 ,954600 ,954650 ,954700 ,954750 ,954800 ,954850 ,954900 ,954950 ,955000 ,955050 ,955100 ,955150 ,955200 ,955250 ,955300 ,955350 ,955400 ,955450 ,955500 ,955550 ,955600 ,955650 ,955700 ,955750 ,955800 ,955850 ,955900 ,955950 ,956000 ,956050 ,956100 ,956150 ,956200 ,956250 ,956300 ,956350 ,956400 ,956450 ,956500 ,956550 ,956600 ,956650 ,956700 ,956750 ,956800 ,956850 ,956900 ,956950 ,957000 ,957050 ,957100 ,957150 ,957200 ,957250 ,957300 )))\n" +
+ "\t\t\t\tOR (t1.id IN (944100, 944150, 944200, 944250, 944300, 944350, 944400, 944450, 944500, 944550, 944600, 944650, 944700, 944750, 944800, 944850, 944900, 944950, 945000 )))\n" +
+ "\t\t\t\tOR (t1.id IN (957350 ,957400 ,957450 ,957500 ,957550 ,957600 ,957650 ,957700 ,957750 ,957800 ,957850 ,957900 ,957950 ,958000 ,958050 ,958100 ,958150 ,958200 ,958250 ,958300 ,958350 ,958400 ,958450 ,958500 ,958550 ,958600 ,958650 ,958700 ,958750 ,958800 ,958850 ,958900 ,958950 ,959000 ,959050 ,959100 ,959150 ,959200 ,959250 ,959300 ,959350 ,959400 ,959450 ,959500 ,959550 ,959600 ,959650 ,959700 ,959750 ,959800 ,959850 ,959900 ,959950 ,960000 ,960050 ,960100 ,960150 ,960200 ,960250 ,960300 ,960350 ,960400 ,960450 ,960500 ,960550 ,960600 ,960650 ,960700 ,960750 ,960800 ,960850 ,960900 ,960950 ,961000 ,961050 ,961100 ,961150 ,961200 ,961250 ,961300 ,961350 ,961400 ,961450 ,961500 ,961550 ,961600 ,961650 ,961700 ,961750 ,961800 ,961850 ,961900 ,961950 ,962000 ,962050 ,962100 ))))\n" +
+ "\t\t\t\tOR (t1.id IN (962150 ,962200 ,962250 ,962300 ,962350 ,962400 ,962450 ,962500 ,962550 ,962600 ,962650 ,962700 ,962750 ,962800 ,962850 ,962900 ,962950 ,963000 ,963050 ,963100 ,963150 ,963200 ,963250 ,963300 ,963350 ,963400 ,963450 ,963500 ,963550 ,963600 ,963650 ,963700 ,963750 ,963800 ,963850 ,963900 ,963950 ,964000 ,964050 ,964100 ,964150 ,964200 ,964250 ,964300 ,964350 ,964400 ,964450 ,964500 ,964550 ,964600 ,964650 ,964700 ,964750 ,964800 ,964850 ,964900 ,964950 ,965000 ,965050 ,965100 ,965150 ,965200 ,965250 ,965300 ,965350 ,965400 ,965450 ,965500 ))))\n" +
+ "\tAND t1.COL3 IN (\n" +
+ "\t SELECT\n" +
+ "\t\t t2.COL3\n" +
+ "\t FROM\n" +
+ "\t\t TABLE_6 t6,\n" +
+ "\t\t TABLE_1 t5,\n" +
+ "\t\t TABLE_4 t4,\n" +
+ "\t\t TABLE_3 t3,\n" +
+ "\t\t TABLE_1 t2\n" +
+ "\t WHERE\n" +
+ "\t\t (((((((t5.CAL3 = T6.id)\n" +
+ "\t\t\t AND (t5.CAL5 = t6.CAL5))\n" +
+ "\t\t\t AND (t5.CAL1 = t6.CAL1))\n" +
+ "\t\t\t AND (t3.CAL1 IN (108500)))\n" +
+ "\t\t\t AND (t5.id = t2.id))\n" +
+ "\t\t\t AND NOT ((t6.CAL6 IN ('VALUE'))))\n" +
+ "\t\t\t AND ((t2.id = t3.CAL2)\n" +
+ "\t\t\t\t AND (t4.id = t3.CAL3))))\n" +
+ "ORDER BY\n" +
+ "\tt1.id ASC", true);
+ }
+
+ @Test
+ @Timeout(1000)
+ public void testPerformanceIssue1397() throws Exception {
+ String sqlStr = IOUtils.toString( SelectTest.class.getResource( "/net/sf/jsqlparser/statement/select/performanceIssue1397.sql" ), Charset.defaultCharset() );
+ assertSqlCanBeParsedAndDeparsed(sqlStr, true);
+ }
+
+ /**
+ * The purpose of the test is to run into a timeout and to stop the parser when this happens.
+ * We provide an INVALID statement for this purpose, which will fail the SIMPLE parse
+ * and then hang with COMPLEX parsing until the timeout occurs.
+ *
+ * We repeat that test multiple times and want to see no stale references to the Parser after timeout.
+ *
+ * @throws JSQLParserException
+ */
+ @Test
+ public void testParserInterruptedByTimeout() {
+ String sqlStr = "" +
+ "SELECT \t* FROM TABLE_1 t1\n" +
+ "WHERE\n" +
+ "\t(((t1.COL1 = 'VALUE2' )\n" +
+ "\t\tAND (t1.CAL2 = 'VALUE2' ))\n" +
+ "\t\tAND (((1 = 1 )\n" +
+ "\t\t\tAND ((((((t1.id IN (940550 ,940600 ,940650 ,940700 ,940750 ,940800 ,940850 ,940900 ,940950 ,941000 ,941050 ,941100 ,941150 ,941200 ,941250 ,941300 ,941350 ,941400 ,941450 ,941500 ,941550 ,941600 ,941650 ,941700 ,941750 ,941800 ,941850 ,941900 ,941950 ,942000 ,942050 ,942100 ,942150 ,942200 ,942250 ,942300 ,942350 ,942400 ,942450 ,942500 ,942550 ,942600 ,942650 ,942700 ,942750 ,942800 ,942850 ,942900 ,942950 ,943000 ,943050 ,943100 ,943150 ,943200 ,943250 ,943300 ,943350 ,943400 ,943450 ,943500 ,943550 ,943600 ,943650 ,943700 ,943750 ,943800 ,943850 ,943900 ,943950 ,944000 ,944050 ,944100 ,944150 ,944200 ,944250 ,944300 ,944350 ,944400 ,944450 ,944500 ,944550 ,944600 ,944650 ,944700 ,944750 ,944800 ,944850 ,944900 ,944950 ,945000 ,945050 ,945100 ,945150 ,945200 ,945250 ,945300 ))\n" +
+ "\t\t\t\tOR (t1.id IN (945350 ,945400 ,945450 ,945500 ,945550 ,945600 ,945650 ,945700 ,945750 ,945800 ,945850 ,945900 ,945950 ,946000 ,946050 ,946100 ,946150 ,946200 ,946250 ,946300 ,946350 ,946400 ,946450 ,946500 ,946550 ,946600 ,946650 ,946700 ,946750 ,946800 ,946850 ,946900 ,946950 ,947000 ,947050 ,947100 ,947150 ,947200 ,947250 ,947300 ,947350 ,947400 ,947450 ,947500 ,947550 ,947600 ,947650 ,947700 ,947750 ,947800 ,947850 ,947900 ,947950 ,948000 ,948050 ,948100 ,948150 ,948200 ,948250 ,948300 ,948350 ,948400 ,948450 ,948500 ,948550 ,948600 ,948650 ,948700 ,948750 ,948800 ,948850 ,948900 ,948950 ,949000 ,949050 ,949100 ,949150 ,949200 ,949250 ,949300 ,949350 ,949400 ,949450 ,949500 ,949550 ,949600 ,949650 ,949700 ,949750 ,949800 ,949850 ,949900 ,949950 ,950000 ,950050 ,950100 )))\n" +
+ "\t\t\t\tOR (t1.id IN (950150 ,950200 ,950250 ,950300 ,950350 ,950400 ,950450 ,950500 ,950550 ,950600 ,950650 ,950700 ,950750 ,950800 ,950850 ,950900 ,950950 ,951000 ,951050 ,951100 ,951150 ,951200 ,951250 ,951300 ,951350 ,951400 ,951450 ,951500 ,951550 ,951600 ,951650 ,951700 ,951750 ,951800 ,951850 ,951900 ,951950 ,952000 ,952050 ,952100 ,952150 ,952200 ,952250 ,952300 ,952350 ,952400 ,952450 ,952500 ,952550 ,952600 ,952650 ,952700 ,952750 ,952800 ,952850 ,952900 ,952950 ,953000 ,953050 ,953100 ,953150 ,953200 ,953250 ,953300 ,953350 ,953400 ,953450 ,953500 ,953550 ,953600 ,953650 ,953700 )))\n" +
+ "\t\t\t\tOR (t1.id IN (953750 ,953800 ,953850 ,953900 ,953950 ,954000 ,954050 ,954100 ,954150 ,954200 ,954250 ,954300 ,954350 ,954400 ,954450 ,954500 ,954550 ,954600 ,954650 ,954700 ,954750 ,954800 ,954850 ,954900 ,954950 ,955000 ,955050 ,955100 ,955150 ,955200 ,955250 ,955300 ,955350 ,955400 ,955450 ,955500 ,955550 ,955600 ,955650 ,955700 ,955750 ,955800 ,955850 ,955900 ,955950 ,956000 ,956050 ,956100 ,956150 ,956200 ,956250 ,956300 ,956350 ,956400 ,956450 ,956500 ,956550 ,956600 ,956650 ,956700 ,956750 ,956800 ,956850 ,956900 ,956950 ,957000 ,957050 ,957100 ,957150 ,957200 ,957250 ,957300 )))\n" +
+ "\t\t\t\tOR (t1.id IN (944100, 944150, 944200, 944250, 944300, 944350, 944400, 944450, 944500, 944550, 944600, 944650, 944700, 944750, 944800, 944850, 944900, 944950, 945000 )))\n" +
+ "\t\t\t\tOR (t1.id IN (957350 ,957400 ,957450 ,957500 ,957550 ,957600 ,957650 ,957700 ,957750 ,957800 ,957850 ,957900 ,957950 ,958000 ,958050 ,958100 ,958150 ,958200 ,958250 ,958300 ,958350 ,958400 ,958450 ,958500 ,958550 ,958600 ,958650 ,958700 ,958750 ,958800 ,958850 ,958900 ,958950 ,959000 ,959050 ,959100 ,959150 ,959200 ,959250 ,959300 ,959350 ,959400 ,959450 ,959500 ,959550 ,959600 ,959650 ,959700 ,959750 ,959800 ,959850 ,959900 ,959950 ,960000 ,960050 ,960100 ,960150 ,960200 ,960250 ,960300 ,960350 ,960400 ,960450 ,960500 ,960550 ,960600 ,960650 ,960700 ,960750 ,960800 ,960850 ,960900 ,960950 ,961000 ,961050 ,961100 ,961150 ,961200 ,961250 ,961300 ,961350 ,961400 ,961450 ,961500 ,961550 ,961600 ,961650 ,961700 ,961750 ,961800 ,961850 ,961900 ,961950 ,962000 ,962050 ,962100 ))))\n" +
+ "\t\t\t\tOR (t1.id IN (962150 ,962200 ,962250 ,962300 ,962350 ,962400 ,962450 ,962500 ,962550 ,962600 ,962650 ,962700 ,962750 ,962800 ,962850 ,962900 ,962950 ,963000 ,963050 ,963100 ,963150 ,963200 ,963250 ,963300 ,963350 ,963400 ,963450 ,963500 ,963550 ,963600 ,963650 ,963700 ,963750 ,963800 ,963850 ,963900 ,963950 ,964000 ,964050 ,964100 ,964150 ,964200 ,964250 ,964300 ,964350 ,964400 ,964450 ,964500 ,964550 ,964600 ,964650 ,964700 ,964750 ,964800 ,964850 ,964900 ,964950 ,965000 ,965050 ,965100 ,965150 ,965200 ,965250 ,965300 ,965350 ,965400 ,965450 ,965500 ))))\n" +
+ "\tAND t1.COL3 IN (\n" +
+ "\t SELECT\n" +
+ "\t\t t2.COL3\n" +
+ "\t FROM\n" +
+ "\t\t TABLE_6 t6,\n" +
+ "\t\t TABLE_1 t5,\n" +
+ "\t\t TABLE_4 t4,\n" +
+ "\t\t TABLE_3 t3,\n" +
+ "\t\t TABLE_1 t2\n" +
+ "\t WHERE\n" +
+ "\t\t (((((((t5.CAL3 = T6.id)\n" +
+ "\t\t\t AND (t5.CAL5 = t6.CAL5))\n" +
+ "\t\t\t AND (t5.CAL1 = t6.CAL1))\n" +
+ "\t\t\t AND (t3.CAL1 IN (108500)))\n" +
+ "\t\t\t AND (t5.id = t2.id))\n" +
+ "\t\t\t AND NOT ((t6.CAL6 IN ('VALUE'))))\n" +
+ "\t\t\t AND ((t2.id = t3.CAL2)\n" +
+ "\t\t\t\t AND (t4.id = t3.CAL3))))\n" +
+ // add two redundant unmatched brackets in order to make the Simple Parser fail
+ // and the complex parser stuck
+ " )) \n" +
+ "ORDER BY\n" +
+ "\tt1.id ASC";
+
+ MemoryLeakVerifier verifier = new MemoryLeakVerifier();
+
+ int parallelThreads = Runtime.getRuntime().availableProcessors() + 1;
+ ExecutorService executorService = Executors.newFixedThreadPool(parallelThreads);
+
+ for (int i=0; i 0) {
message = message.substring(0, pos);
diff --git a/src/test/java/net/sf/jsqlparser/test/MemoryLeakVerifier.java b/src/test/java/net/sf/jsqlparser/test/MemoryLeakVerifier.java
new file mode 100644
index 000000000..5784e5d95
--- /dev/null
+++ b/src/test/java/net/sf/jsqlparser/test/MemoryLeakVerifier.java
@@ -0,0 +1,116 @@
+/*-
+ * #%L
+ * JSQLParser library
+ * %%
+ * Copyright (C) 2004 - 2022 JSQLParser
+ * %%
+ * Dual licensed under GNU LGPL 2.1 or Apache License 2.0
+ * #L%
+ */
+package net.sf.jsqlparser.test;
+
+/* ====================================================================
+ Taken from Apache POI, with a big thanks.
+
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+
+import static org.junit.jupiter.api.Assertions.assertNull;
+
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A simple utility class that can verify that objects have been successfully garbage collected.
+ *
+ * Usage is something like
+ *
+ * private final MemoryLeakVerifier verifier = new MemoryLeakVerifier();
+
+ {@literal}After
+ void tearDown() {
+ verifier.assertGarbageCollected();
+ }
+
+ {@literal}Test
+ void someTest() {
+ ...
+ verifier.addObject(object);
+ }
+
+ *
+ * This will verify at the end of the test if the object is actually removed by the
+ * garbage collector or if it lingers in memory for some reason.
+ *
+ * Idea taken from http://stackoverflow.com/a/7410460/411846
+ */
+public class MemoryLeakVerifier {
+ private static final int MAX_GC_ITERATIONS = 50;
+ private static final int GC_SLEEP_TIME = 100;
+
+ private final List> references = new ArrayList<>();
+
+ public void addObject(Object object) {
+ references.add(new WeakReference<>(object));
+ }
+
+ /**
+ * Attempts to perform a full garbage collection so that all weak references will be removed. Usually only
+ * a single GC is required, but there have been situations where some unused memory is not cleared up on the
+ * first pass. This method performs a full garbage collection and then validates that the weak reference
+ * now has been cleared. If it hasn't then the thread will sleep for 100 milliseconds and then retry up to
+ * 50 more times. If after this the object still has not been collected then the assertion will fail.
+ *
+ * Based upon the method described in: http://www.javaworld.com/javaworld/javatips/jw-javatip130.html
+ */
+ public void assertGarbageCollected() {
+ assertGarbageCollected(MAX_GC_ITERATIONS);
+ }
+
+ /**
+ * Used only for testing the class itself where we would like to fail faster than 5 seconds
+ * @param maxIterations The number of times a GC will be invoked until a possible memory leak is reported
+ */
+ void assertGarbageCollected(int maxIterations) {
+ try {
+ for (WeakReference