@@ -164,6 +164,53 @@ public class CCJSqlParser extends AbstractJSqlParser<CCJSqlParser> {
164164 return true;
165165 }
166166
167+
168+ /**
169+ * Checks whether the given token can start the operator in a RegularCondition
170+ * (comparison operators, JSON operators, regex operators, geometry distance, etc.)
171+ *
172+ * Used to avoid expensive syntactic lookaheads like LOOKAHEAD(RegularCondition()).
173+ * By the time this is called, lower-precedence operators like "-" (subtraction)
174+ * and "||" (concatenation) have already been consumed by SimpleExpression,
175+ * so seeing them here means they are comparison-level operators.
176+ */
177+
178+ protected boolean isComparisonOperatorAhead() {
179+ try {
180+ Token token = getToken(1);
181+ if (token.image.equals("(") && getToken(2).image.equals("+")) {
182+ // Oracle (+) — only route to RegularConditionRHS if a real
183+ // comparison operator follows after "(" "+" ")"
184+ return isComparisonOperator(getToken(4));
185+ }
186+ return isComparisonOperator(token);
187+ } catch (Exception e) {
188+ return false;
189+ }
190+ }
191+
192+ protected static boolean isComparisonOperator(Token token) {
193+ if (token.image == null || token.image.isEmpty()) {
194+ return false;
195+ }
196+ switch (token.image.charAt(0)) {
197+ case '>': // >, >=
198+ case '=': // =, =*
199+ case '~': // ~, ~*
200+ return true;
201+ case '<': // <, <=, <>, <@, <->, <#>, <=>, <&
202+ return true;
203+ case '*': return token.image.equals("*=");
204+ case '!': return token.image.startsWith("!~") || token.image.startsWith("!=");
205+ case '@': return token.image.equals("@@") || token.image.equals("@>");
206+ case '?': return true; // ?, ?|, ?&
207+ case '-': return true; // -, -#
208+ case '|': return token.image.equals("||");
209+ case '^': return token.image.startsWith("^=");
210+ case '&': return token.image.equals("&&") || token.image.equals("&>");
211+ default: return false;
212+ }
213+ }
167214}
168215
169216PARSER_END(CCJSqlParser)
@@ -5994,52 +6041,72 @@ Expression AndExpression() :
59946041Expression Condition():
59956042{
59966043 Expression result;
5997- Token token ;
6044+ Expression left ;
59986045 boolean not = false;
5999- boolean exclamationMarkNot = false;
6046+ boolean exclamationMarkNot = false;
6047+ int oraclePrior = EqualsTo.NO_ORACLE_PRIOR;
6048+ int oracleJoin = EqualsTo.NO_ORACLE_JOIN;
60006049}
60016050{
60026051 [ LOOKAHEAD(2) (<K_NOT> { not=true; } | "!" { not=true; exclamationMarkNot=true; })]
60036052 (
6004- LOOKAHEAD(RegularCondition(), {!interrupted}) result=RegularCondition ()
6053+ result=ExistsExpression ()
60056054 |
6006- result=SQLCondition()
6007- )
6055+ [ LOOKAHEAD(2) <K_PRIOR> { oraclePrior = EqualsTo.ORACLE_PRIOR_START; } ]
6056+ left=SimpleExpression() { result = left; }
60086057
6009- { return not?new NotExpression(result, exclamationMarkNot):result; }
6010- }
6011-
6012- Expression OverlapsCondition():{
6013- ExpressionList left = new ExpressionList();
6014- ExpressionList right = new ExpressionList();
6015- }
6016- {
6017- //As per the sql2003 standard, we need at least two items in the list if there is not explicit ROW prefix
6018- //More than two expression are allowed per the sql2003 grammar.
6019- left = ParenthesedExpressionList()
6020- <K_OVERLAPS>
6021- right = ParenthesedExpressionList()
6058+ // Consume Oracle (+) once, before dispatching
6059+ [
6060+ LOOKAHEAD("(" "+" ")")
6061+ "(" "+" ")"
6062+ {
6063+ oracleJoin = EqualsTo.ORACLE_JOIN_RIGHT;
6064+ if (left instanceof Column) {
6065+ ((Column) left).setOldOracleJoinSyntax(oracleJoin);
6066+ }
6067+ }
6068+ ]
60226069
6023- {return new OverlapsCondition(left, right);}
6070+ [
6071+ LOOKAHEAD({ isComparisonOperatorAhead() }) result = RegularConditionRHS(left, oracleJoin)
6072+ |
6073+ LOOKAHEAD(2, <K_OVERLAPS>) result = OverlapsCondition(left)
6074+ |
6075+ LOOKAHEAD(3, {!interrupted}) result=InExpression(left)
6076+ | LOOKAHEAD(3) result=ExcludesExpression(left)
6077+ | LOOKAHEAD(3) result=IncludesExpression(left)
6078+ | LOOKAHEAD(2) result=Between(left)
6079+ | result = MemberOfExpression(left)
6080+ | LOOKAHEAD(3) result=IsNullExpression(left)
6081+ | LOOKAHEAD(3) result=IsBooleanExpression(left)
6082+ | LOOKAHEAD(3) result=IsUnknownExpression(left)
6083+ | LOOKAHEAD(2) result=LikeExpression(left)
6084+ | LOOKAHEAD(3) result=IsDistinctExpression(left)
6085+ | result=SimilarToExpression(left)
6086+ ]
6087+ )
6088+ {
6089+ if (oraclePrior == EqualsTo.ORACLE_PRIOR_START
6090+ && result instanceof SupportsOldOracleJoinSyntax) {
6091+ ((SupportsOldOracleJoinSyntax) result).setOraclePriorPosition(oraclePrior);
6092+ }
6093+ return not ? new NotExpression(result, exclamationMarkNot) : result;
6094+ }
60246095}
60256096
6026- Expression RegularCondition( ) #RegularCondition:
6097+ Expression RegularConditionRHS(Expression leftExpression, int oracleJoinRight ) #RegularCondition:
60276098{
60286099 Expression result = null;
6029- Expression leftExpression;
60306100 Expression rightExpression;
6031- int oracleJoin=EqualsTo.NO_ORACLE_JOIN;
6032- int oraclePrior=EqualsTo.NO_ORACLE_PRIOR;
6033- boolean binary = false;
6034- boolean not = false;
6101+ int oracleJoin = EqualsTo.NO_ORACLE_JOIN;
6102+ int oraclePrior = EqualsTo.NO_ORACLE_PRIOR;
6103+ Token token;
60356104}
60366105{
6037- [ LOOKAHEAD(2) <K_PRIOR> { oraclePrior = EqualsTo.ORACLE_PRIOR_START; }]
6038- leftExpression=ComparisonItem() { result = leftExpression ; }
6106+ // Only consume (+) here if it wasn't already consumed by Condition
6107+ [ LOOKAHEAD("(" "+" ")") "(" "+" ")" { oracleJoin = EqualsTo.ORACLE_JOIN_RIGHT ; } ]
60396108
6040- [ "(" "+" ")" { oracleJoin=EqualsTo.ORACLE_JOIN_RIGHT; } ]
6041-
6042- (
6109+ (
60436110 LOOKAHEAD(2)
60446111 ">" { result = new GreaterThan(); }
60456112 | "<" { result = new MinorThan(); }
@@ -6059,7 +6126,6 @@ Expression RegularCondition() #RegularCondition:
60596126 | "~*" { result = new RegExpMatchOperator(RegExpMatchOperatorType.MATCH_CASEINSENSITIVE); }
60606127 | "!~" { result = new RegExpMatchOperator(RegExpMatchOperatorType.NOT_MATCH_CASESENSITIVE); }
60616128 | "!~*" { result = new RegExpMatchOperator(RegExpMatchOperatorType.NOT_MATCH_CASEINSENSITIVE); }
6062-
60636129 | "@>" { result = new JsonOperator("@>"); }
60646130 | "<@" { result = new JsonOperator("<@"); }
60656131 | "?" { result = new JsonOperator("?"); }
@@ -6074,28 +6140,46 @@ Expression RegularCondition() #RegularCondition:
60746140 )
60756141
60766142 ( LOOKAHEAD(2) <K_PRIOR> rightExpression=ComparisonItem() { oraclePrior = EqualsTo.ORACLE_PRIOR_END; }
6077- | rightExpression=ComparisonItem() )
6143+ | rightExpression=ComparisonItem() )
60786144
6079- [ LOOKAHEAD(2) "(" "+" ")" { oracleJoin= EqualsTo.ORACLE_JOIN_LEFT; } ]
6145+ [ LOOKAHEAD(2) "(" "+" ")" { oracleJoin = EqualsTo.ORACLE_JOIN_LEFT; } ]
60806146
60816147 {
60826148 BinaryExpression regCond = (BinaryExpression) result;
60836149 regCond.setLeftExpression(leftExpression);
60846150 regCond.setRightExpression(rightExpression);
60856151
6086- if (oracleJoin> 0)
6087- ((SupportsOldOracleJoinSyntax)result).setOldOracleJoinSyntax(oracleJoin);
6152+ if (oracleJoin > 0)
6153+ ((SupportsOldOracleJoinSyntax) result).setOldOracleJoinSyntax(oracleJoin);
60886154
6089- if (oraclePrior!= EqualsTo.NO_ORACLE_PRIOR)
6090- ((SupportsOldOracleJoinSyntax)result).setOraclePriorPosition(oraclePrior);
6155+ if (oraclePrior != EqualsTo.NO_ORACLE_PRIOR)
6156+ ((SupportsOldOracleJoinSyntax) result).setOraclePriorPosition(oraclePrior);
60916157 }
60926158
60936159 {
6094- linkAST(result,jjtThis);
6160+ linkAST(result, jjtThis);
60956161 return result;
60966162 }
60976163}
60986164
6165+ Expression OverlapsCondition(Expression leftExpression):
6166+ {
6167+ ExpressionList right;
6168+ }
6169+ {
6170+ <K_OVERLAPS>
6171+ right = ParenthesedExpressionList()
6172+ {
6173+ ExpressionList left;
6174+ if (leftExpression instanceof ExpressionList) {
6175+ left = (ExpressionList) leftExpression;
6176+ } else {
6177+ left = new ExpressionList(leftExpression);
6178+ }
6179+ return new OverlapsCondition(left, right);
6180+ }
6181+ }
6182+
60996183Expression SQLCondition():
61006184{
61016185 Expression result;
@@ -6104,10 +6188,11 @@ Expression SQLCondition():
61046188{
61056189 (
61066190 result=ExistsExpression()
6107- | LOOKAHEAD( OverlapsCondition(), {!interrupted}) result=OverlapsCondition()
61086191 | left = SimpleExpression() { result = left; }
61096192 [
61106193 LOOKAHEAD(2) (
6194+ LOOKAHEAD(2, <K_OVERLAPS>) result=OverlapsCondition(left)
6195+ |
61116196 LOOKAHEAD(3, {!interrupted}) result=InExpression(left)
61126197 |
61136198 LOOKAHEAD(3) result=ExcludesExpression(left)
@@ -6213,18 +6298,22 @@ Expression Between(Expression leftExpression) :
62136298 (
62146299 LOOKAHEAD( ParenthesedSelect() ) betweenExpressionStart = ParenthesedSelect()
62156300 |
6216- LOOKAHEAD( RegularCondition() ) betweenExpressionStart = RegularCondition()
6217- |
6218- betweenExpressionStart = SimpleExpression()
6301+ betweenExpressionStart = SimpleExpression()
6302+ [
6303+ LOOKAHEAD({ isComparisonOperatorAhead() })
6304+ betweenExpressionStart = RegularConditionRHS(betweenExpressionStart, EqualsTo.NO_ORACLE_JOIN)
6305+ ]
62196306 )
62206307
62216308 <K_AND>
62226309 (
62236310 LOOKAHEAD( ParenthesedSelect() ) betweenExpressionEnd = ParenthesedSelect()
62246311 |
6225- LOOKAHEAD( RegularCondition() ) betweenExpressionEnd = RegularCondition()
6226- |
62276312 betweenExpressionEnd = SimpleExpression()
6313+ [
6314+ LOOKAHEAD({ isComparisonOperatorAhead() })
6315+ betweenExpressionEnd = RegularConditionRHS(betweenExpressionEnd, EqualsTo.NO_ORACLE_JOIN)
6316+ ]
62286317 )
62296318
62306319 {
0 commit comments