Skip to content
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

Performance Improvements #1439

Merged
merged 30 commits into from
Apr 14, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
de805ab
Adjust Gradle to JUnit 5
manticore-projects Nov 22, 2021
d5a6dca
Do not mark SpeedTest for concurrent execution
manticore-projects Nov 24, 2021
a9d0503
Remove unused imports
manticore-projects Nov 28, 2021
6dfa05f
Adjust Gradle to JUnit 5
manticore-projects Nov 22, 2021
8f0bfe6
Do not mark SpeedTest for concurrent execution
manticore-projects Nov 24, 2021
5cd0974
Remove unused imports
manticore-projects Nov 28, 2021
5f11d3f
Merge remote-tracking branch 'origin/master'
manticore-projects Nov 28, 2021
b393e00
Performance Improvements
manticore-projects Dec 10, 2021
cb3c91a
Appease PMD
manticore-projects Dec 10, 2021
cdc56c7
Resolve conflicts
manticore-projects Dec 10, 2021
f14cf33
Update Gradle Plugins to its latest versions
manticore-projects Dec 11, 2021
82ce02b
Revert unintended changes to the Test Resources
manticore-projects Dec 11, 2021
c984ff6
Appease PMD/Codacy
manticore-projects Dec 11, 2021
3ab04b0
Merge https://github.com/JSQLParser/JSqlParser
manticore-projects Dec 21, 2021
c075544
Merge branch 'master' into Issue1438
manticore-projects Dec 21, 2021
79bcbc9
Correct the Gradle "+" dependencies
manticore-projects Dec 21, 2021
4c38284
Bump version to 4.4.-SNAPSHOT
manticore-projects Dec 21, 2021
e25ee10
update build file
manticore-projects Dec 30, 2021
de94651
Merge branch 'JSQLParser:master' into master
manticore-projects Jan 24, 2022
84709a9
Merge branch 'master' of github.com:JSQLParser/JSqlParser
manticore-projects Mar 22, 2022
cba3125
revert unwarranted changes in test files
manticore-projects Mar 29, 2022
3b08fde
Merge remote-tracking branch 'origin/master' into Issue1438
manticore-projects Mar 29, 2022
960aaf2
strip the Exception Class Name from the Message
manticore-projects Mar 29, 2022
592de64
maxDepth = 10 collides with the Parser Timeout = 6 seconds
manticore-projects Mar 29, 2022
31caad9
License Headers
manticore-projects Mar 29, 2022
3c8da30
Merge branch 'master' of github.com:JSQLParser/JSqlParser
manticore-projects Apr 4, 2022
4cef39b
Merge branch 'master' into Issue1438
manticore-projects Apr 4, 2022
55f083f
Merge remote-tracking branch 'origin/master' into Issue1438
manticore-projects Apr 14, 2022
2bd7dee
Bump version to 4.5-SNAPSHOT
manticore-projects Apr 14, 2022
d7d2dbe
Merge remote-tracking branch 'manticore/Issue1438' into Issue1438
manticore-projects Apr 14, 2022
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
38 changes: 19 additions & 19 deletions build.gradle
Original file line number Diff line number Diff line change
@@ -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"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just curious. Does this work the same way maven dependency versioning works like [1.0,2.0)?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do not know about Maven does (In fact I hate Maven. You can't even clean a project while offline. I went straight from ANT to Gradle, which "just understands and does what I want" instead of forcing me into any dogma). This latest.release automatically retrieves the Latest Release of a Gradle Plugin.

}

group = 'com.github.jsqlparser'
version = '4.4-SNAPSHOT'
version = '4.5-SNAPSHOT'
description = 'JSQLParser library'
java.sourceCompatibility = JavaVersion.VERSION_1_8

Expand All @@ -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 {
Expand All @@ -58,7 +58,6 @@ java {

spotbugs
pmd

}

jacoco {
Expand Down Expand Up @@ -173,7 +172,7 @@ spotbugs {

pmd {
consoleOutput = false
toolVersion = "6.36.0"
toolVersion = "6.41.0"

sourceSets = [sourceSets.main]

Expand All @@ -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')
}
Expand Down Expand Up @@ -254,6 +253,7 @@ task renderRR() {
publishing {
publications {
maven(MavenPublication) {
artifactId 'jsqlparser'
from(components.java)
}
}
Expand Down
2 changes: 1 addition & 1 deletion ruleset.xml
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ under the License.
<rule ref="category/java/errorprone.xml/UselessOperationOnImmutable" />

<!-- for Codazy -->
<rule ref="category/java/errorprone.xml/MissingBreakInSwitch" />
<!-- <rule ref="category/java/errorprone.xml/MissingBreakInSwitch" /> -->

<rule ref="category/java/multithreading.xml/AvoidThreadGroup" />
<rule ref="category/java/multithreading.xml/DontCallThreadRun" />
Expand Down
182 changes: 140 additions & 42 deletions src/main/java/net/sf/jsqlparser/parser/CCJSqlParserUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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() {
}
Expand Down Expand Up @@ -54,13 +63,25 @@ public static Statement parse(String sql) throws JSQLParserException {
* @throws JSQLParserException
*/
public static Statement parse(String sql, Consumer<CCJSqlParser> 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);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If complex parsing is initially switched off, it should not be switched on in this method. Right? However, this is only some kind of hack. The grammar itself is flawed if we need to do something like this. The porblem is the excessive use of Lookups, but if the grammar would be designed that a fixed lookup would be enough, then we do not need a switch like this.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are correct in your observation, but I respectfully do not share your conclusion:

  1. It is not a hack but actually a completely valid approach (to my knowledge the only available method) to implement a IF ... THEN ... condition in the parser

  2. While the grammar certainly could be rewritten/optimized, I still claim that SQL is a bit exceptional:
    a) we try to be RDBMS/Dialect agnostic and so have to deal with a great flexibility in the Grammar
    b) SQL lacks reliable terminated Blocks, which makes it difficult to define Stop tokens. What do I mean with this: Obviously everything inside brackets ( ) is a block and the closing brackets ) terminates this block and we know that we can stop parsing an expression at the closing bracket. Unfortunately SQL was "designed" for assistant secretaries, who were supposed to query data without programming skills.

Best illustration is issue #1444:

select doc.fullName from XWikiDocument doc where doc.fullName like :fullName escape '/' and doc.author like :author escape '!'

Is this

select doc.fullName from XWikiDocument doc where ( doc.fullName like :fullName escape '/' ) 
and ( doc.author like :author escape '!' )

Or is this

select doc.fullName from XWikiDocument doc where doc.fullName like :fullName escape ( '/' 
and doc.author like :author escape '!' )

JavaCC by its nature lacks a lot of functionality to deal with those requirements. JavaCC 21 added some features for a reason and I believe ANTLR is there for a reason too.

Long story short:
Yes, you are right: too many lookup harms us badly. But I am also right: we do need those for parsing certain valid statements (look how functionality has grown since 4.0 and how the "new issue" rate went down since!)

So the suggest approach, while not a beauty at all solves that in a pragmatic way:
It tries to parse first without those slow lookup and only when this fails, it would activate the lookup -- because they are needed.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Of course, if anyone can point me on how to avoid those lookup without losing functionality, I am all in!

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) {
Expand Down Expand Up @@ -112,24 +133,44 @@ public static Expression parseExpression(String expression, boolean allowPartial
});
}

public static Expression parseExpression(String expression, boolean allowPartialParse, Consumer<CCJSqlParser> 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<CCJSqlParser> 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;
}

/**
Expand Down Expand Up @@ -158,24 +199,43 @@ public static Expression parseCondExpression(String condExpr, boolean allowParti
});
}

public static Expression parseCondExpression(String condExpr, boolean allowPartialParse, Consumer<CCJSqlParser> 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<CCJSqlParser> 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;
}

/**
Expand All @@ -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<Statement> future = executorService.submit(new Callable<Statement>() {
@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;
}

/**
Expand All @@ -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;
}

/**
Expand All @@ -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<Statements> future = executorService.submit(new Callable<Statements>() {
@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 {
Expand Down
Loading