Skip to content

Commit 3a5da44

Browse files
Allow Complex Parsing of Functions (#1200)
* Allow Complex Parsing of Functions Fixes issues #1190 #1103 * Apply Complex Parsing to PrimaryExpression() Fixes issue #1194 * Increase Test Timeout to 2 seconds for slow CI Servers. * Appease Codazy
1 parent a5204f6 commit 3a5da44

File tree

7 files changed

+129
-19
lines changed

7 files changed

+129
-19
lines changed

src/main/java/net/sf/jsqlparser/parser/AbstractJSqlParser.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,9 @@ public P withSquareBracketQuotation(boolean allowSquareBracketQuotation) {
2525
return withFeature(Feature.allowSquareBracketQuotation, allowSquareBracketQuotation);
2626
}
2727

28+
public P withAllowComplexParsing(boolean allowComplexParsing) {
29+
return withFeature(Feature.allowComplexParsing, allowComplexParsing);
30+
}
2831
public P withFeature(Feature f, boolean enabled) {
2932
getConfiguration().setValue(f, enabled);
3033
return me();

src/main/java/net/sf/jsqlparser/parser/CCJSqlParserUtil.java

Lines changed: 36 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
* @author toben
2525
*/
2626
public final class CCJSqlParserUtil {
27+
public final static int ALLOWED_NESTING_DEPTH = 7;
2728

2829
private CCJSqlParserUtil() {
2930
}
@@ -53,7 +54,9 @@ public static Statement parse(String sql) throws JSQLParserException {
5354
* @throws JSQLParserException
5455
*/
5556
public static Statement parse(String sql, Consumer<CCJSqlParser> consumer) throws JSQLParserException {
56-
CCJSqlParser parser = newParser(sql);
57+
boolean allowComplexParsing = getNestingDepth(sql)<=ALLOWED_NESTING_DEPTH;
58+
59+
CCJSqlParser parser = newParser(sql).withAllowComplexParsing(allowComplexParsing);
5760
if (consumer != null) {
5861
consumer.accept(parser);
5962
}
@@ -110,7 +113,9 @@ public static Expression parseExpression(String expression, boolean allowPartial
110113
}
111114

112115
public static Expression parseExpression(String expression, boolean allowPartialParse, Consumer<CCJSqlParser> consumer) throws JSQLParserException {
113-
CCJSqlParser parser = newParser(expression);
116+
boolean allowComplexParsing = getNestingDepth(expression)<=ALLOWED_NESTING_DEPTH;
117+
118+
CCJSqlParser parser = newParser(expression).withAllowComplexParsing(allowComplexParsing);
114119
if (consumer != null) {
115120
consumer.accept(parser);
116121
}
@@ -154,7 +159,9 @@ public static Expression parseCondExpression(String condExpr, boolean allowParti
154159
}
155160

156161
public static Expression parseCondExpression(String condExpr, boolean allowPartialParse, Consumer<CCJSqlParser> consumer) throws JSQLParserException {
157-
CCJSqlParser parser = newParser(condExpr);
162+
boolean allowComplexParsing = getNestingDepth(condExpr)<=ALLOWED_NESTING_DEPTH;
163+
164+
CCJSqlParser parser = newParser(condExpr).withAllowComplexParsing(allowComplexParsing);
158165
if (consumer != null) {
159166
consumer.accept(parser);
160167
}
@@ -190,7 +197,9 @@ public static Statement parseStatement(CCJSqlParser parser) throws JSQLParserExc
190197
* @return the statements parsed
191198
*/
192199
public static Statements parseStatements(String sqls) throws JSQLParserException {
193-
CCJSqlParser parser = newParser(sqls);
200+
boolean allowComplexParsing = getNestingDepth(sqls)<=ALLOWED_NESTING_DEPTH;
201+
202+
CCJSqlParser parser = newParser(sqls).withAllowComplexParsing(allowComplexParsing);
194203
return parseStatements(parser);
195204
}
196205

@@ -225,5 +234,28 @@ public static void streamStatements(StatementListener listener, InputStream is,
225234
throw new JSQLParserException(ex);
226235
}
227236
}
237+
238+
public static int getNestingDepth(String sql) {
239+
int maxlevel=0;
240+
int level=0;
241+
242+
char[] chars = sql.toCharArray();
243+
for (char c:chars) {
244+
switch(c) {
245+
case '(':
246+
level++;
247+
break;
248+
case ')':
249+
if (maxlevel<level) {
250+
maxlevel = level;
251+
}
252+
level--;
253+
break;
254+
default:
255+
// Codazy/PMD insists in a Default statement
256+
}
257+
}
258+
return maxlevel;
259+
}
228260

229261
}

src/main/java/net/sf/jsqlparser/parser/feature/Feature.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -710,6 +710,13 @@ public enum Feature {
710710
*/
711711
allowSquareBracketQuotation(false),
712712

713+
// PERFORMANCE
714+
715+
/**
716+
* allows complex expression parameters or named parameters for functions
717+
* will be switched off, when deep nesting of functions is detected
718+
*/
719+
allowComplexParsing(true)
713720
;
714721

715722
private Object value;

src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1347,7 +1347,7 @@ String RelObjectNameWithoutValue() :
13471347
| tk=<K_TABLE> | tk=<K_DATETIMELITERAL> | tk=<K_COMMIT> | tk=<K_PRECISION>
13481348
| tk=<K_INSERT> | tk=<K_INDEX> | tk=<K_PRIMARY> | tk=<K_ENABLE>
13491349
| tk=<K_TEMP> | tk=<K_TEMPORARY> | tk=<K_TO> | tk=<K_TYPE> | tk=<K_ISNULL>
1350-
| tk=<K_ZONE> | tk=<K_COLUMNS> | tk=<K_DESCRIBE> | tk=<K_FN> | tk=<K_PATH>
1350+
| tk=<K_ZONE> | tk=<K_COLUMNS> | tk=<K_DESCRIBE> | tk=<K_FN> | tk=<K_PATH>
13511351
| tk=<K_DATE_LITERAL> | tk=<K_NEXTVAL> | tk=<K_TRUE> | tk=<K_FALSE> | tk=<K_DUPLICATE>
13521352
| tk=<K_READ> | tk=<K_SCHEMA> | tk=<K_SIGNED> | tk=<K_SIZE> | tk=<K_SEQUENCE> | tk=<K_SESSION> | tk=<K_SKIP>
13531353
| tk=<K_SYNONYM>
@@ -3373,6 +3373,14 @@ Expression PrimaryExpression() #PrimaryExpression:
33733373

33743374
| LOOKAHEAD("(" retval=SubSelect() ")") "(" retval=SubSelect() ")"
33753375

3376+
| LOOKAHEAD({getAsBoolean(Feature.allowComplexParsing)}) "(" list = ComplexExpressionList() ")"
3377+
{
3378+
if (list.getExpressions().size() == 1) {
3379+
retval = new Parenthesis(list.getExpressions().get(0));
3380+
} else {
3381+
retval = new RowConstructor().withExprList(list);
3382+
}
3383+
}
33763384
| "(" list = SimpleExpressionList(true) ")"
33773385
{
33783386
if (list.getExpressions().size() == 1) {
@@ -3912,7 +3920,9 @@ Function InternalFunction(Function retval) :
39123920
|
39133921
LOOKAHEAD(3) namedExpressionList=NamedExpressionList1()
39143922
|
3915-
LOOKAHEAD(NamedExpressionListExprFirst()) namedExpressionList = NamedExpressionListExprFirst()
3923+
LOOKAHEAD(NamedExpressionListExprFirst(), { getAsBoolean(Feature.allowComplexParsing) }) namedExpressionList = NamedExpressionListExprFirst()
3924+
|
3925+
LOOKAHEAD(3, { getAsBoolean(Feature.allowComplexParsing) }) (expressionList=ComplexExpressionList() [ orderByList = OrderByElements() { retval.setOrderByElements(orderByList); } ])
39163926
|
39173927
LOOKAHEAD(3) (expressionList=SimpleExpressionList(true) [ orderByList = OrderByElements() { retval.setOrderByElements(orderByList); } ])
39183928
|

src/test/java/net/sf/jsqlparser/parser/CCJSqlParserUtilTest.java

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -230,4 +230,41 @@ public void testParseExpressionWithBracketsIssue1159_2() throws Exception {
230230
parser -> parser.withSquareBracketQuotation(true));
231231
assertEquals("[travel_data].[travel_id]", result.toString());
232232
}
233+
@Test
234+
public void testNestingDepth() throws Exception {
235+
assertEquals(2,
236+
CCJSqlParserUtil.getNestingDepth("SELECT concat(concat('A','B'),'B') FROM mytbl"));
237+
assertEquals(20, CCJSqlParserUtil.getNestingDepth(
238+
"concat(concat(concat(concat(concat(concat(concat(concat(concat(concat(concat(concat(concat(concat(concat(concat(concat(concat(concat(concat('A','B'),'B'),'B'),'B'),'B'),'B'),'B'),'B'),'B'),'B'),'B'),'B'),'B'),'B'),'B'),'B'),'B'),'B'),'B'),'B') FROM mytbl"));
239+
assertEquals(4, CCJSqlParserUtil.getNestingDepth(""
240+
+ "-- MERGE 1\n"
241+
+ "MERGE INTO cfe.impairment imp\n" + " USING ( WITH x AS (\n"
242+
+ " SELECT a.id_instrument\n"
243+
+ " , a.id_currency\n"
244+
+ " , a.id_instrument_type\n"
245+
+ " , b.id_portfolio\n"
246+
+ " , c.attribute_value product_code\n"
247+
+ " , t.valid_date\n" + " , t.ccf\n"
248+
+ " FROM cfe.instrument a\n"
249+
+ " INNER JOIN cfe.impairment b\n"
250+
+ " ON a.id_instrument = b.id_instrument\n"
251+
+ " LEFT JOIN cfe.instrument_attribute c\n"
252+
+ " ON a.id_instrument = c.id_instrument\n"
253+
+ " AND c.id_attribute = 'product'\n"
254+
+ " INNER JOIN cfe.ext_ccf t\n"
255+
+ " ON ( a.id_currency LIKE t.id_currency )\n"
256+
+ " AND ( a.id_instrument_type LIKE t.id_instrument_type )\n"
257+
+ " AND ( b.id_portfolio LIKE t.id_portfolio\n"
258+
+ " OR ( b.id_portfolio IS NULL\n"
259+
+ " AND t.id_portfolio = '%' ) )\n"
260+
+ " AND ( c.attribute_value LIKE t.product_code\n"
261+
+ " OR ( c.attribute_value IS NULL\n"
262+
+ " AND t.product_code = '%' ) ) )\n"
263+
+ "SELECT /*+ PARALLEL */ *\n" + " FROM x x1\n"
264+
+ " WHERE x1.valid_date = ( SELECT max\n"
265+
+ " FROM x\n"
266+
+ " WHERE id_instrument = x1.id_instrument ) ) s\n"
267+
+ " ON ( imp.id_instrument = s.id_instrument )\n" + "WHEN MATCHED THEN\n"
268+
+ " UPDATE SET imp.ccf = s.ccf\n" + ";"));
269+
}
233270
}

src/test/java/net/sf/jsqlparser/statement/select/NestedBracketsPerformanceTest.java

Lines changed: 23 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -23,17 +23,17 @@
2323
*/
2424
public class NestedBracketsPerformanceTest {
2525

26-
@Test
26+
@Test(timeout = 2000)
2727
public void testIssue766() throws JSQLParserException {
2828
assertSqlCanBeParsedAndDeparsed("SELECT concat(concat(concat(concat(concat(concat(concat(concat(concat(concat(concat(concat(concat(concat(concat(concat(concat(concat(concat(concat('1','2'),'3'),'4'),'5'),'6'),'7'),'8'),'9'),'10'),'11'),'12'),'13'),'14'),'15'),'16'),'17'),'18'),'19'),'20'),'21'),col1 FROM tbl t1", true);
2929
}
3030

31-
@Test
31+
@Test(timeout = 2000)
3232
public void testIssue766_2() throws JSQLParserException {
3333
assertSqlCanBeParsedAndDeparsed("SELECT concat(concat(concat('1', '2'), '3'), '4'), col1 FROM tbl t1");
3434
}
3535

36-
@Test
36+
@Test(timeout = 2000)
3737
public void testIssue235() throws JSQLParserException {
3838
assertSqlCanBeParsedAndDeparsed("SELECT CASE WHEN ( CASE WHEN ( CASE WHEN ( CASE WHEN ( 1 ) THEN 0 END ) THEN 0 END ) THEN 0 END ) THEN 0 END FROM a", true);
3939
}
@@ -116,7 +116,7 @@ public void testIssue856() throws JSQLParserException {
116116
assertSqlCanBeParsedAndDeparsed(sql);
117117
}
118118

119-
@Test
119+
@Test(timeout = 2000)
120120
public void testRecursiveBracketExpressionIssue1019() {
121121
assertEquals("IF(1=1, 1, 2)", buildRecursiveBracketExpression("IF(1=1, $1, 2)", "1", 0));
122122
assertEquals("IF(1=1, IF(1=1, 1, 2), 2)", buildRecursiveBracketExpression("IF(1=1, $1, 2)", "1", 1));
@@ -128,12 +128,12 @@ public void testRecursiveBracketExpressionIssue1019_2() throws JSQLParserExcepti
128128
doIncreaseOfParseTimeTesting("IF(1=1, $1, 2)", "1", 10);
129129
}
130130

131-
@Test
131+
@Test(timeout = 2000)
132132
public void testIssue1013() throws JSQLParserException {
133133
assertSqlCanBeParsedAndDeparsed("SELECT ((((((((((((((((tblA)))))))))))))))) FROM mytable");
134134
}
135135

136-
@Test
136+
@Test(timeout = 2000)
137137
public void testIssue1013_2() throws JSQLParserException {
138138
assertSqlCanBeParsedAndDeparsed("SELECT * FROM ((((((((((((((((tblA))))))))))))))))");
139139
}
@@ -143,7 +143,7 @@ public void testIssue1013_3() throws JSQLParserException {
143143
assertSqlCanBeParsedAndDeparsed("SELECT * FROM (((tblA)))");
144144
}
145145

146-
@Test
146+
@Test(timeout = 2000)
147147
public void testIssue1013_4() throws JSQLParserException {
148148
String s = "tblA";
149149
for (int i = 1; i < 100; i++) {
@@ -161,13 +161,13 @@ public void testIssue1013_4() throws JSQLParserException {
161161
*
162162
* @throws JSQLParserException
163163
*/
164-
@Test
164+
@Test(timeout = 2000)
165165
public void testIncreaseOfParseTime() throws JSQLParserException {
166-
doIncreaseOfParseTimeTesting("concat($1,'B')", "'A'", 20);
166+
doIncreaseOfParseTimeTesting("concat($1,'B')", "'A'", 50);
167167
}
168168

169169
private void doIncreaseOfParseTimeTesting(String template, String finalExpression, int maxDepth) throws JSQLParserException {
170-
long oldDurationTime = 1000;
170+
long oldDurationTime = 2000;
171171
int countProblematic = 0;
172172
for (int i = 0; i < maxDepth; i++) {
173173
String sql = "SELECT " + buildRecursiveBracketExpression(template, finalExpression, i) + " FROM mytbl";
@@ -202,4 +202,16 @@ private String buildRecursiveBracketExpression(String template, String finalExpr
202202
}
203203
return template.replace("$1", buildRecursiveBracketExpression(template, finalExpression, depth - 1));
204204
}
205-
}
205+
206+
@Test(timeout = 2000)
207+
public void testIssue1103() throws JSQLParserException {
208+
assertSqlCanBeParsedAndDeparsed(
209+
"SELECT\n" + "ROUND(ROUND(ROUND(ROUND(ROUND(ROUND(ROUND(ROUND(\n"
210+
+ "ROUND(ROUND(ROUND(ROUND(ROUND(ROUND(ROUND(ROUND(\n"
211+
+ "ROUND(ROUND(ROUND(ROUND(ROUND(ROUND(ROUND(ROUND(\n"
212+
+ "ROUND(ROUND(ROUND(ROUND(ROUND(ROUND(ROUND(ROUND(0\n" + ",0),0),0),0),0),0),0),0)\n"
213+
+ ",0),0),0),0),0),0),0),0)\n" + ",0),0),0),0),0),0),0),0)\n"
214+
+ ",0),0),0),0),0),0),0),0)",
215+
true);
216+
}
217+
}

src/test/java/net/sf/jsqlparser/statement/select/SelectTest.java

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4544,12 +4544,21 @@ public void testKeywordCostsIssue1185() throws JSQLParserException {
45444544
}
45454545

45464546
@Test
4547-
public void testKeywordCostsIssue1135() throws JSQLParserException {
4547+
public void testFunctionWithComplexParameters_Issue1190() throws JSQLParserException {
4548+
assertSqlCanBeParsedAndDeparsed("SELECT to_char(a = '3') FROM dual", true);
4549+
}
4550+
4551+
@Test
4552+
public void testConditionsWithExtraBrackets_Issue1194() throws JSQLParserException {
4553+
assertSqlCanBeParsedAndDeparsed("SELECT (col IS NULL) FROM tbl", true);
4554+
}
4555+
4556+
public void testWithValueListWithExtraBrackets1135() throws JSQLParserException {
45484557
assertSqlCanBeParsedAndDeparsed("with sample_data(day, value) as (values ((0, 13), (1, 12), (2, 15), (3, 4), (4, 8), (5, 16))) select day, value from sample_data", true);
45494558
}
45504559

45514560
@Test
4552-
public void testKeywordCostsIssue1135_2() throws JSQLParserException {
4561+
public void testWithValueListWithOutExtraBrackets1135() throws JSQLParserException {
45534562
assertSqlCanBeParsedAndDeparsed("with sample_data(day, value) as (values (0, 13), (1, 12), (2, 15), (3, 4), (4, 8), (5, 16)) select day, value from sample_data", true);
45544563
}
45554564

0 commit comments

Comments
 (0)