Skip to content

Commit cfedd31

Browse files
avoid quadratic positional access over linked-list column lists (#2860)
1 parent 9663289 commit cfedd31

4 files changed

Lines changed: 87 additions & 7 deletions

File tree

‎src/main/java/net/sf/jsqlparser/statement/select/Select.java‎

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -131,9 +131,13 @@ public static StringBuilder appendStringListTo(StringBuilder builder, List<?> li
131131
builder.append("(");
132132
}
133133

134-
int size = list.size();
135-
for (int i = 0; i < size; i++) {
136-
builder.append(list.get(i)).append(i < size - 1 ? comma : "");
134+
boolean first = true;
135+
for (Object element : list) {
136+
if (!first) {
137+
builder.append(comma);
138+
}
139+
builder.append(element);
140+
first = false;
137141
}
138142

139143
if (useBrackets) {

‎src/main/java/net/sf/jsqlparser/util/ConnectExpressionsVisitor.java‎

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
*/
1010
package net.sf.jsqlparser.util;
1111

12+
import java.util.Iterator;
1213
import java.util.LinkedList;
1314
import java.util.List;
1415

@@ -70,15 +71,16 @@ public <S> T visit(PlainSelect plainSelect, S context) {
7071
}
7172

7273
if (itemsExpr.size() > 1) {
74+
Iterator<SelectItem<? extends Expression>> it = itemsExpr.iterator();
7375
BinaryExpression binExpr = createBinaryExpression();
74-
binExpr.setLeftExpression(itemsExpr.get(0).getExpression());
75-
for (int i = 1; i < itemsExpr.size() - 1; i++) {
76-
binExpr.setRightExpression(itemsExpr.get(i).getExpression());
76+
binExpr.setLeftExpression(it.next().getExpression());
77+
binExpr.setRightExpression(it.next().getExpression());
78+
while (it.hasNext()) {
7779
BinaryExpression binExpr2 = createBinaryExpression();
7880
binExpr2.setLeftExpression(binExpr);
81+
binExpr2.setRightExpression(it.next().getExpression());
7982
binExpr = binExpr2;
8083
}
81-
binExpr.setRightExpression(itemsExpr.get(itemsExpr.size() - 1).getExpression());
8284

8385
SelectItem<Expression> sei = new SelectItem<>();
8486
sei.setExpression(binExpr);
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
/*-
2+
* #%L
3+
* JSQLParser library
4+
* %%
5+
* Copyright (C) 2004 - 2019 JSQLParser
6+
* %%
7+
* Dual licensed under GNU LGPL 2.1 or Apache License 2.0
8+
* #L%
9+
*/
10+
package net.sf.jsqlparser.statement.select;
11+
12+
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
13+
import net.sf.jsqlparser.statement.Statement;
14+
import org.junit.jupiter.api.Test;
15+
16+
import java.time.Duration;
17+
18+
import static org.junit.jupiter.api.Assertions.assertEquals;
19+
import static org.junit.jupiter.api.Assertions.assertTimeoutPreemptively;
20+
21+
public class JoinUsingDeParseTest {
22+
23+
private static String usingSelect(int columnCount) {
24+
StringBuilder sb = new StringBuilder("SELECT * FROM a JOIN b USING (");
25+
for (int i = 0; i < columnCount; i++) {
26+
if (i > 0) {
27+
sb.append(", ");
28+
}
29+
sb.append("c").append(i);
30+
}
31+
return sb.append(")").toString();
32+
}
33+
34+
@Test
35+
void deParseOfLargeUsingColumnListIsLinear() throws Exception {
36+
// usingColumns is a LinkedList, so deparsing the clause through
37+
// Select.appendStringListTo used to read it by index (get(i)), which is O(n^2).
38+
String sql = usingSelect(200000);
39+
Statement statement = CCJSqlParserUtil.parse(sql);
40+
assertEquals(sql, assertTimeoutPreemptively(Duration.ofSeconds(5), statement::toString));
41+
}
42+
43+
@Test
44+
void deParseOfUsingColumnListIsUnchanged() throws Exception {
45+
String sql = "SELECT * FROM a JOIN b USING (c0, c1, c2)";
46+
assertEquals(sql, CCJSqlParserUtil.parse(sql).toString());
47+
}
48+
}

‎src/test/java/net/sf/jsqlparser/util/ConnectExpressionsVisitorTest.java‎

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,10 @@
1818
import org.junit.jupiter.api.Test;
1919

2020
import java.io.StringReader;
21+
import java.time.Duration;
2122

2223
import static org.junit.jupiter.api.Assertions.assertEquals;
24+
import static org.junit.jupiter.api.Assertions.assertTimeoutPreemptively;
2325

2426
public class ConnectExpressionsVisitorTest {
2527

@@ -54,4 +56,28 @@ protected BinaryExpression createBinaryExpression() {
5456

5557
assertEquals("SELECT a + b + c AS testexpr FROM test", select.toString());
5658
}
59+
60+
@Test
61+
public void testVisit_PlainSelect_manyItemsIsLinear() throws JSQLParserException {
62+
int itemCount = 200000;
63+
StringBuilder sql = new StringBuilder("select ");
64+
for (int i = 0; i < itemCount; i++) {
65+
if (i > 0) {
66+
sql.append(',');
67+
}
68+
sql.append('c').append(i);
69+
}
70+
sql.append(" from test");
71+
Select select = (Select) parserManager.parse(new StringReader(sql.toString()));
72+
ConnectExpressionsVisitor<Void> instance = new ConnectExpressionsVisitor<>() {
73+
@Override
74+
protected BinaryExpression createBinaryExpression() {
75+
return new Concat();
76+
}
77+
};
78+
// itemsExpr is a LinkedList; connecting the items used to index it by position (get(i)),
79+
// which is O(n^2) in the number of select items.
80+
assertTimeoutPreemptively(Duration.ofSeconds(5), () -> select.accept(instance, null));
81+
assertEquals(1, select.getPlainSelect().getSelectItems().size());
82+
}
5783
}

0 commit comments

Comments
 (0)