Skip to content

Commit d709c4e

Browse files
edeadingtonclaude
andauthored
Remove the 500-token cap from the nested set operation lookahead (#2780)
isNestedSetOperationAhead() gave up after 500 tokens, so a parenthesised set operation whose first branch is longer than that was parsed as a parenthesised FROM item and failed on the set operator. Walk the token chain once instead of calling getToken(i) per position, which removes the need for a cap. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
1 parent 066f581 commit d709c4e

2 files changed

Lines changed: 20 additions & 21 deletions

File tree

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

Lines changed: 6 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -765,27 +765,12 @@ public class CCJSqlParser extends AbstractJSqlParser<CCJSqlParser> {
765765
if (getToken(1).kind != OPENING_BRACKET) return false;
766766
if (getToken(2).kind != OPENING_BRACKET) return false;
767767
// Skip any run of opening brackets: ( ( ( ... ( SELECT
768-
int i = 3;
769-
while (getToken(i).kind == OPENING_BRACKET) i++;
770-
int k = getToken(i).kind;
771-
if (k != K_SELECT && k != K_WITH && k != K_VALUES) return false;
772-
// Scan forward counting brackets to find the matching ) for token(2)
773-
// We are now inside all the opening brackets; depth = number of brackets skipped - 1
774-
// (token(1) is the outer one, token(2..i-1) are the inner ones we scanned)
775-
int depth = i - 2; // brackets from position 2 to i-1 inclusive
776-
i++;
777-
while (i <= 500) {
778-
Token t; try { t = getToken(i); } catch (TokenMgrException e) { return false; }
779-
if (t == null || t.kind == 0) return false;
780-
if (t.kind == OPENING_BRACKET) depth++;
781-
else if (t.kind == CLOSING_BRACKET) {
782-
if (--depth == 0) break;
783-
}
784-
i++;
785-
}
786-
if (i > 500) return false;
787-
// Token at position i+1 follows the innermost closing )
788-
Token next; try { next = getToken(i + 1); } catch (TokenMgrException e) { return false; }
768+
Token innerOpen = getToken(2);
769+
Token t = innerOpen;
770+
while (t.kind == OPENING_BRACKET) t = nextWithItemToken(t);
771+
if (t.kind != K_SELECT && t.kind != K_WITH && t.kind != K_VALUES) return false;
772+
// Follow the token chain directly; repeated getToken(i) calls rescan from the start each time
773+
Token next = skipBalancedBracketGroup(innerOpen);
789774
if (next == null) return false;
790775
return next.kind == K_UNION || next.kind == K_INTERSECT ||
791776
next.kind == K_EXCEPT || next.kind == K_MINUS;

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

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,8 @@
9191
@Execution(ExecutionMode.CONCURRENT)
9292
public class SelectTest {
9393

94+
private static final String LONG_CONDITION = "B = 1 AND ".repeat(150) + "B = 1";
95+
9496
private final CCJSqlParserManager parserManager = new CCJSqlParserManager();
9597

9698
@Test
@@ -5362,6 +5364,18 @@ public void testSetOperationWithParenthesisIssue1094() throws JSQLParserExceptio
53625364
"SELECT * FROM ((SELECT A FROM tbl) UNION DISTINCT (SELECT B FROM tbl2)) AS union1");
53635365
}
53645366

5367+
@Test
5368+
public void testSetOperationWithParenthesisAndLongFirstBranch() throws JSQLParserException {
5369+
assertSqlCanBeParsedAndDeparsed("SELECT * FROM ((SELECT A FROM tbl WHERE " + LONG_CONDITION
5370+
+ ") UNION ALL (SELECT A FROM tbl2)) AS union1");
5371+
}
5372+
5373+
@Test
5374+
public void testSetOperationInExpressionWithLongFirstBranch() throws JSQLParserException {
5375+
assertSqlCanBeParsedAndDeparsed("SELECT * FROM tbl WHERE A IN ((SELECT A FROM tbl2 WHERE "
5376+
+ LONG_CONDITION + ") UNION ALL (SELECT A FROM tbl3))");
5377+
}
5378+
53655379
@Test
53665380
public void testSetOperationWithParenthesisIssue1094_2() throws JSQLParserException {
53675381
String sqlStr =

0 commit comments

Comments
 (0)