Skip to content

Commit cdbaf1a

Browse files
authored
avoid quadratic member lookups in table and alias visitors (#2763)
TablesNamesFinder tracked derived-table, CTE and lateral names in an ArrayList and matched every plain table reference against it, and AddAliasesVisitor checked each generated alias against a LinkedList, so both walked parsed SQL in O(n^2). Both collections are used only for membership, so back them with a LinkedHashSet. Signed-off-by: Javid Khan <dxbjavid@gmail.com>
1 parent 8d12a5d commit cdbaf1a

4 files changed

Lines changed: 58 additions & 5 deletions

File tree

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

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

12-
import java.util.LinkedList;
13-
import java.util.List;
12+
import java.util.LinkedHashSet;
13+
import java.util.Set;
1414

1515
import java.util.Locale;
1616
import net.sf.jsqlparser.expression.Alias;
@@ -37,7 +37,7 @@
3737
public class AddAliasesVisitor<T> implements SelectVisitor<T>, SelectItemVisitor<T> {
3838

3939
private static final String NOT_SUPPORTED_YET = "Not supported yet.";
40-
private final List<String> aliases = new LinkedList<String>();
40+
private final Set<String> aliases = new LinkedHashSet<String>();
4141
private boolean firstRun = true;
4242
private int counter = 0;
4343
private String prefix = "A";

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
import java.util.ArrayList;
1313
import java.util.HashSet;
14+
import java.util.LinkedHashSet;
1415
import java.util.List;
1516
import java.util.Map;
1617
import java.util.Set;
@@ -265,7 +266,7 @@ public class TablesNamesFinder<Void>
265266
private boolean allowColumnProcessing = false;
266267
private Set<String> rowPatternVariables = java.util.Collections.emptySet();
267268

268-
private List<String> otherItemNames;
269+
private Set<String> otherItemNames;
269270

270271
public static Set<String> findTables(String sqlStr) throws JSQLParserException {
271272
TablesNamesFinder<?> tablesNamesFinder = new TablesNamesFinder<>();
@@ -1309,7 +1310,7 @@ public <S> Void visit(DateUnitExpression dateUnitExpression, S context) {
13091310
* @param allowColumnProcessing
13101311
*/
13111312
protected void init(boolean allowColumnProcessing) {
1312-
otherItemNames = new ArrayList<String>();
1313+
otherItemNames = new LinkedHashSet<String>();
13131314
tables = new HashSet<>();
13141315
this.allowColumnProcessing = allowColumnProcessing;
13151316
this.rowPatternVariables = java.util.Collections.emptySet();

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

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,10 @@
1515
import org.junit.jupiter.api.Test;
1616

1717
import java.io.StringReader;
18+
import java.time.Duration;
1819

1920
import static org.junit.jupiter.api.Assertions.assertEquals;
21+
import static org.junit.jupiter.api.Assertions.assertTimeoutPreemptively;
2022

2123
public class AddAliasesVisitorTest {
2224

@@ -68,4 +70,24 @@ public void testVisit_SetOperationList() throws JSQLParserException {
6870
assertEquals("SELECT 3 + 4 AS A1 FROM test UNION SELECT 7 + 8 AS A1 FROM test2",
6971
setOpList.toString());
7072
}
73+
74+
@Test
75+
public void testManySelectItemsDoNotScaleQuadratically() throws JSQLParserException {
76+
// Generated aliases were checked against the already-used ones with a linear
77+
// LinkedList.contains, so adding aliases to a select with m items cost O(m^2).
78+
final int m = 50000;
79+
final StringBuilder sb = new StringBuilder("SELECT ");
80+
for (int i = 0; i < m; i++) {
81+
if (i > 0) {
82+
sb.append(", ");
83+
}
84+
sb.append("c").append(i);
85+
}
86+
sb.append(" FROM t");
87+
88+
// Parsing is left outside the timed region; only the alias walk is measured.
89+
final Select select = (Select) parserManager.parse(new StringReader(sb.toString()));
90+
assertTimeoutPreemptively(Duration.ofSeconds(2),
91+
() -> select.accept(new AddAliasesVisitor<>(), null));
92+
}
7193
}

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

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,10 @@
1414
import static org.assertj.core.api.Assertions.assertThatThrownBy;
1515
import static org.junit.jupiter.api.Assertions.assertEquals;
1616
import static org.junit.jupiter.api.Assertions.assertNull;
17+
import static org.junit.jupiter.api.Assertions.assertTimeoutPreemptively;
1718
import static org.junit.jupiter.api.Assertions.assertTrue;
1819

20+
import java.time.Duration;
1921
import java.util.Arrays;
2022
import java.util.List;
2123
import java.util.Set;
@@ -1062,4 +1064,32 @@ void testUpdateOrderByLimitSubquery() throws JSQLParserException {
10621064
"MY_TABLE2");
10631065
}
10641066

1067+
@Test
1068+
void testManyDerivedNamesDoNotScaleQuadratically() throws JSQLParserException {
1069+
// Each aliased WITH item is remembered in otherItemNames and every plain table
1070+
// reference used to be matched against it with a linear ArrayList.contains, so a
1071+
// statement with n CTE aliases and n tables cost O(n^2) to walk.
1072+
final int n = 30000;
1073+
final StringBuilder sb = new StringBuilder("WITH ");
1074+
for (int i = 0; i < n; i++) {
1075+
if (i > 0) {
1076+
sb.append(", ");
1077+
}
1078+
sb.append("a").append(i).append(" AS (SELECT 1)");
1079+
}
1080+
sb.append(" SELECT * FROM ");
1081+
for (int i = 0; i < n; i++) {
1082+
if (i > 0) {
1083+
sb.append(", ");
1084+
}
1085+
sb.append("t").append(i);
1086+
}
1087+
1088+
// Parsing is linear and left outside the timed region; only the table walk is measured.
1089+
final Statement statement = CCJSqlParserUtil.parse(sb.toString());
1090+
final Set<String> tables = assertTimeoutPreemptively(Duration.ofSeconds(2),
1091+
() -> new TablesNamesFinder<>().getTables(statement));
1092+
assertEquals(n, tables.size());
1093+
}
1094+
10651095
}

0 commit comments

Comments
 (0)