This is an automated email from the ASF dual-hosted git repository.

duanzhengqiang pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/shardingsphere.git


The following commit(s) were added to refs/heads/master by this push:
     new 55706067d1c Support Oracle SQL parser correctly extract REGEXP_SUBSTR 
parameters. (#37924)
55706067d1c is described below

commit 55706067d1c7721c336361b526e70f8c904cdf07
Author: Claire <[email protected]>
AuthorDate: Mon Feb 2 14:41:15 2026 +0800

    Support Oracle SQL parser correctly extract REGEXP_SUBSTR parameters. 
(#37924)
    
    * support Oracle SQL parser
    
    * support new version
    
    * update
    
    * update
    
    * support test
    
    * realese-notes
    
    * realese-notes
    
    * realese-notes
---
 RELEASE-NOTES.md                                   |   2 +
 .../visitor/statement/OracleStatementVisitor.java  | 110 ++++++++++++++++++++-
 .../statement/type/OracleDMLStatementVisitor.java  |  31 +++++-
 .../dml/predicate/HierarchicalQuerySegment.java    |  43 ++++++++
 .../core/statement/type/dml/SelectStatement.java   |  12 +++
 .../case/dml/select-start-with-connect-by.xml      |  34 +++++++
 .../supported/dml/select-start-with-connect-by.xml |   1 +
 7 files changed, 231 insertions(+), 2 deletions(-)

diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md
index b4215e244ce..567841328ed 100644
--- a/RELEASE-NOTES.md
+++ b/RELEASE-NOTES.md
@@ -6,6 +6,8 @@
 
 ### Bug Fixes
 
+1. SQL Parser: Support Oracle SQL parser correctly extract REGEXP_SUBSTR 
parameters - [#37924](https://github.com/apache/shardingsphere/pull/37924)
+
 ## Release 5.5.3
 
 ### CVE
diff --git 
a/parser/sql/engine/dialect/oracle/src/main/java/org/apache/shardingsphere/sql/parser/engine/oracle/visitor/statement/OracleStatementVisitor.java
 
b/parser/sql/engine/dialect/oracle/src/main/java/org/apache/shardingsphere/sql/parser/engine/oracle/visitor/statement/OracleStatementVisitor.java
index 46ef6ce099e..cbf3b099279 100644
--- 
a/parser/sql/engine/dialect/oracle/src/main/java/org/apache/shardingsphere/sql/parser/engine/oracle/visitor/statement/OracleStatementVisitor.java
+++ 
b/parser/sql/engine/dialect/oracle/src/main/java/org/apache/shardingsphere/sql/parser/engine/oracle/visitor/statement/OracleStatementVisitor.java
@@ -224,7 +224,96 @@ public abstract class OracleStatementVisitor extends 
OracleStatementBaseVisitor<
     
     @Override
     public final ASTNode visitParameterMarker(final ParameterMarkerContext 
ctx) {
-        return new ParameterMarkerValue(globalParameterMarkerSegments.size(), 
ParameterMarkerType.QUESTION);
+        return new ParameterMarkerValue(getParameterMarkerIndex(ctx), 
ParameterMarkerType.QUESTION);
+    }
+    
+    private int getParameterMarkerIndex(final ParameterMarkerContext ctx) {
+        int startIndex = ctx.getStart().getStartIndex();
+        if (startIndex <= 0) {
+            return 0;
+        }
+        String sql = ctx.getStart().getInputStream().getText(new Interval(0, 
startIndex - 1));
+        return countParameterMarkers(sql);
+    }
+    
+    private int countParameterMarkers(final String sql) {
+        ParameterMarkerScanState state = new ParameterMarkerScanState();
+        while (state.index < sql.length()) {
+            if (advanceInLineComment(sql, state)) {
+                continue;
+            }
+            if (advanceInBlockComment(sql, state)) {
+                continue;
+            }
+            if (advanceInOrToggleStringLiteral(sql, state)) {
+                continue;
+            }
+            if (enterLineOrBlockComment(sql, state)) {
+                continue;
+            }
+            if (!state.inStringLiteral && sql.charAt(state.index) == '?') {
+                state.result++;
+            }
+            state.index++;
+        }
+        return state.result;
+    }
+    
+    private boolean advanceInLineComment(final String sql, final 
ParameterMarkerScanState state) {
+        if (!state.inLineComment) {
+            return false;
+        }
+        char ch = sql.charAt(state.index);
+        if ('\n' == ch || '\r' == ch) {
+            state.inLineComment = false;
+        }
+        state.index++;
+        return true;
+    }
+    
+    private boolean advanceInBlockComment(final String sql, final 
ParameterMarkerScanState state) {
+        if (!state.inBlockComment) {
+            return false;
+        }
+        char ch = sql.charAt(state.index);
+        if ('*' == ch && state.index + 1 < sql.length() && '/' == 
sql.charAt(state.index + 1)) {
+            state.inBlockComment = false;
+            state.index += 2;
+        } else {
+            state.index++;
+        }
+        return true;
+    }
+    
+    private boolean advanceInOrToggleStringLiteral(final String sql, final 
ParameterMarkerScanState state) {
+        if (sql.charAt(state.index) != '\'') {
+            return false;
+        }
+        if (state.inStringLiteral && state.index + 1 < sql.length() && '\'' == 
sql.charAt(state.index + 1)) {
+            state.index += 2;
+        } else {
+            state.inStringLiteral = !state.inStringLiteral;
+            state.index++;
+        }
+        return true;
+    }
+    
+    private boolean enterLineOrBlockComment(final String sql, final 
ParameterMarkerScanState state) {
+        if (state.inStringLiteral) {
+            return false;
+        }
+        char ch = sql.charAt(state.index);
+        if ('-' == ch && state.index + 1 < sql.length() && '-' == 
sql.charAt(state.index + 1)) {
+            state.inLineComment = true;
+            state.index += 2;
+            return true;
+        }
+        if ('/' == ch && state.index + 1 < sql.length() && '*' == 
sql.charAt(state.index + 1)) {
+            state.inBlockComment = true;
+            state.index += 2;
+            return true;
+        }
+        return false;
     }
     
     @Override
@@ -533,6 +622,12 @@ public abstract class OracleStatementVisitor extends 
OracleStatementBaseVisitor<
         if (null != ctx.LIKE()) {
             return createBinaryOperationExpressionFromLike(ctx);
         }
+        if (null != ctx.PRIOR()) {
+            return null == ctx.predicate() ? new 
CommonExpressionSegment(ctx.start.getStartIndex(), ctx.stop.getStopIndex(), 
getOriginalText(ctx)) : visit(ctx.predicate());
+        }
+        if (null == ctx.bitExpr(0)) {
+            return new CommonExpressionSegment(ctx.start.getStartIndex(), 
ctx.stop.getStopIndex(), getOriginalText(ctx));
+        }
         return visit(ctx.bitExpr(0));
     }
     
@@ -1317,4 +1412,17 @@ public abstract class OracleStatementVisitor extends 
OracleStatementBaseVisitor<
     protected void decreaseCursorForLoopLevel() {
         --cursorForLoopLevel;
     }
+    
+    private static final class ParameterMarkerScanState {
+        
+        private int index;
+        
+        private int result;
+        
+        private boolean inStringLiteral;
+        
+        private boolean inLineComment;
+        
+        private boolean inBlockComment;
+    }
 }
diff --git 
a/parser/sql/engine/dialect/oracle/src/main/java/org/apache/shardingsphere/sql/parser/engine/oracle/visitor/statement/type/OracleDMLStatementVisitor.java
 
b/parser/sql/engine/dialect/oracle/src/main/java/org/apache/shardingsphere/sql/parser/engine/oracle/visitor/statement/type/OracleDMLStatementVisitor.java
index 6a2884aef76..439e94f8dcb 100644
--- 
a/parser/sql/engine/dialect/oracle/src/main/java/org/apache/shardingsphere/sql/parser/engine/oracle/visitor/statement/type/OracleDMLStatementVisitor.java
+++ 
b/parser/sql/engine/dialect/oracle/src/main/java/org/apache/shardingsphere/sql/parser/engine/oracle/visitor/statement/type/OracleDMLStatementVisitor.java
@@ -107,6 +107,7 @@ import 
org.apache.shardingsphere.sql.parser.autogen.OracleStatementParser.Update
 import 
org.apache.shardingsphere.sql.parser.autogen.OracleStatementParser.UsingClauseContext;
 import 
org.apache.shardingsphere.sql.parser.autogen.OracleStatementParser.WhereClauseContext;
 import 
org.apache.shardingsphere.sql.parser.autogen.OracleStatementParser.WithClauseContext;
+import 
org.apache.shardingsphere.sql.parser.autogen.OracleStatementParser.HierarchicalQueryClauseContext;
 import 
org.apache.shardingsphere.sql.parser.engine.oracle.visitor.statement.OracleStatementVisitor;
 import org.apache.shardingsphere.sql.parser.statement.core.enums.CombineType;
 import org.apache.shardingsphere.sql.parser.statement.core.enums.JoinType;
@@ -150,6 +151,7 @@ import 
org.apache.shardingsphere.sql.parser.statement.core.segment.dml.order.ite
 import 
org.apache.shardingsphere.sql.parser.statement.core.segment.dml.order.item.IndexOrderByItemSegment;
 import 
org.apache.shardingsphere.sql.parser.statement.core.segment.dml.order.item.OrderByItemSegment;
 import 
org.apache.shardingsphere.sql.parser.statement.core.segment.dml.predicate.HavingSegment;
+import 
org.apache.shardingsphere.sql.parser.statement.core.segment.dml.predicate.HierarchicalQuerySegment;
 import 
org.apache.shardingsphere.sql.parser.statement.core.segment.dml.predicate.LockSegment;
 import 
org.apache.shardingsphere.sql.parser.statement.core.segment.dml.predicate.WhereSegment;
 import 
org.apache.shardingsphere.sql.parser.statement.core.segment.dml.table.MultiTableConditionalIntoElseSegment;
@@ -441,10 +443,12 @@ public final class OracleDMLStatementVisitor extends 
OracleStatementVisitor impl
         if (null != ctx.whereClause()) {
             result.setWhere((WhereSegment) visit(ctx.whereClause()));
         }
+        if (null != ctx.hierarchicalQueryClause()) {
+            result.setHierarchicalQuery((HierarchicalQuerySegment) 
visit(ctx.hierarchicalQueryClause()));
+        }
         if (null != ctx.groupByClause()) {
             result.setGroupBy((GroupBySegment) visit(ctx.groupByClause()));
         }
-        // TODO Visit hierarchicalQueryClause
         if (null != ctx.modelClause()) {
             result.setModel((ModelSegment) visit(ctx.modelClause()));
         }
@@ -649,6 +653,9 @@ public final class OracleDMLStatementVisitor extends 
OracleStatementVisitor impl
         if (null != ctx.whereClause()) {
             result.setWhere((WhereSegment) visit(ctx.whereClause()));
         }
+        if (null != ctx.hierarchicalQueryClause()) {
+            result.setHierarchicalQuery((HierarchicalQuerySegment) 
visit(ctx.hierarchicalQueryClause()));
+        }
         if (null != ctx.groupByClause()) {
             result.setGroupBy((GroupBySegment) visit(ctx.groupByClause()));
             if (null != ctx.groupByClause().havingClause()) {
@@ -661,6 +668,28 @@ public final class OracleDMLStatementVisitor extends 
OracleStatementVisitor impl
         return result;
     }
     
+    @Override
+    public ASTNode visitHierarchicalQueryClause(final 
HierarchicalQueryClauseContext ctx) {
+        HierarchicalQuerySegment result = new 
HierarchicalQuerySegment(ctx.getStart().getStartIndex(), 
ctx.getStop().getStopIndex());
+        result.setNoCycle(null != ctx.NOCYCLE());
+        if (ctx.expr().isEmpty()) {
+            return result;
+        }
+        boolean connectByFirst = 
"CONNECT".equalsIgnoreCase(ctx.getStart().getText());
+        if (connectByFirst) {
+            result.setConnectBy((ExpressionSegment) visit(ctx.expr(0)));
+            if (ctx.expr().size() > 1) {
+                result.setStartWith((ExpressionSegment) visit(ctx.expr(1)));
+            }
+        } else {
+            result.setStartWith((ExpressionSegment) visit(ctx.expr(0)));
+            if (ctx.expr().size() > 1) {
+                result.setConnectBy((ExpressionSegment) visit(ctx.expr(1)));
+            }
+        }
+        return result;
+    }
+    
     @Override
     public ASTNode visitHavingClause(final HavingClauseContext ctx) {
         ExpressionSegment expr = (ExpressionSegment) visit(ctx.expr());
diff --git 
a/parser/sql/statement/core/src/main/java/org/apache/shardingsphere/sql/parser/statement/core/segment/dml/predicate/HierarchicalQuerySegment.java
 
b/parser/sql/statement/core/src/main/java/org/apache/shardingsphere/sql/parser/statement/core/segment/dml/predicate/HierarchicalQuerySegment.java
new file mode 100644
index 00000000000..1c2aec4ae86
--- /dev/null
+++ 
b/parser/sql/statement/core/src/main/java/org/apache/shardingsphere/sql/parser/statement/core/segment/dml/predicate/HierarchicalQuerySegment.java
@@ -0,0 +1,43 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package 
org.apache.shardingsphere.sql.parser.statement.core.segment.dml.predicate;
+
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+import lombok.Setter;
+import org.apache.shardingsphere.sql.parser.statement.core.segment.SQLSegment;
+import 
org.apache.shardingsphere.sql.parser.statement.core.segment.dml.expr.ExpressionSegment;
+
+/**
+ * Hierarchical query segment for Oracle START WITH / CONNECT BY.
+ */
+@RequiredArgsConstructor
+@Getter
+@Setter
+public final class HierarchicalQuerySegment implements SQLSegment {
+    
+    private final int startIndex;
+    
+    private final int stopIndex;
+    
+    private boolean noCycle;
+    
+    private ExpressionSegment startWith;
+    
+    private ExpressionSegment connectBy;
+}
diff --git 
a/parser/sql/statement/core/src/main/java/org/apache/shardingsphere/sql/parser/statement/core/statement/type/dml/SelectStatement.java
 
b/parser/sql/statement/core/src/main/java/org/apache/shardingsphere/sql/parser/statement/core/statement/type/dml/SelectStatement.java
index fa82ab269f7..a13423521aa 100644
--- 
a/parser/sql/statement/core/src/main/java/org/apache/shardingsphere/sql/parser/statement/core/statement/type/dml/SelectStatement.java
+++ 
b/parser/sql/statement/core/src/main/java/org/apache/shardingsphere/sql/parser/statement/core/statement/type/dml/SelectStatement.java
@@ -28,6 +28,7 @@ import 
org.apache.shardingsphere.sql.parser.statement.core.segment.dml.order.Gro
 import 
org.apache.shardingsphere.sql.parser.statement.core.segment.dml.order.OrderBySegment;
 import 
org.apache.shardingsphere.sql.parser.statement.core.segment.dml.pagination.limit.LimitSegment;
 import 
org.apache.shardingsphere.sql.parser.statement.core.segment.dml.predicate.HavingSegment;
+import 
org.apache.shardingsphere.sql.parser.statement.core.segment.dml.predicate.HierarchicalQuerySegment;
 import 
org.apache.shardingsphere.sql.parser.statement.core.segment.dml.predicate.LockSegment;
 import 
org.apache.shardingsphere.sql.parser.statement.core.segment.dml.predicate.WhereSegment;
 import 
org.apache.shardingsphere.sql.parser.statement.core.segment.generic.ModelSegment;
@@ -54,6 +55,8 @@ public final class SelectStatement extends DMLStatement {
     
     private WhereSegment where;
     
+    private HierarchicalQuerySegment hierarchicalQuery;
+    
     private GroupBySegment groupBy;
     
     private HavingSegment having;
@@ -104,6 +107,15 @@ public final class SelectStatement extends DMLStatement {
         return Optional.ofNullable(where);
     }
     
+    /**
+     * Get hierarchical query.
+     *
+     * @return hierarchical query segment
+     */
+    public Optional<HierarchicalQuerySegment> getHierarchicalQuery() {
+        return Optional.ofNullable(hierarchicalQuery);
+    }
+    
     /**
      * Get group by segment.
      *
diff --git 
a/test/it/parser/src/main/resources/case/dml/select-start-with-connect-by.xml 
b/test/it/parser/src/main/resources/case/dml/select-start-with-connect-by.xml
index b8bfa42de90..5739e38bad8 100644
--- 
a/test/it/parser/src/main/resources/case/dml/select-start-with-connect-by.xml
+++ 
b/test/it/parser/src/main/resources/case/dml/select-start-with-connect-by.xml
@@ -70,4 +70,38 @@
             </expr>
         </where>
     </select>
+
+    <select sql-case-id="select_with_connect_by" parameters="2, 1">
+        <projections start-index="7" stop-index="7">
+            <shorthand-projection start-index="7" stop-index="7" />
+        </projections>
+        <from>
+            <subquery-table start-index="14" stop-index="167">
+                <subquery>
+                    <select>
+                        <projections start-index="22" stop-index="97">
+                            <expression-projection alias="order_id" 
text="REGEXP_SUBSTR('5214|1521|5152|1616|218|8226', '[^|]+', 1, LEVEL)" 
start-index="22" stop-index="97" />
+                        </projections>
+                        <from>
+                            <simple-table name="dual" start-index="104" 
stop-index="107" />
+                        </from>
+                    </select>
+                </subquery>
+            </subquery-table>
+        </from>
+        <where start-index="169" stop-index="185">
+            <expr>
+                <binary-operation-expression start-index="175" 
stop-index="185">
+                    <left>
+                        <column name="ROWNUM" start-index="175" 
stop-index="180" />
+                    </left>
+                    <operator>&lt;=</operator>
+                    <right>
+                        <literal-expression value="1" start-index="185" 
stop-index="185" />
+                        <parameter-marker-expression parameter-index="1" 
start-index="185" stop-index="185" />
+                    </right>
+                </binary-operation-expression>
+            </expr>
+        </where>
+    </select>
 </sql-parser-test-cases>
diff --git 
a/test/it/parser/src/main/resources/sql/supported/dml/select-start-with-connect-by.xml
 
b/test/it/parser/src/main/resources/sql/supported/dml/select-start-with-connect-by.xml
index 3e5bfcefd68..e007f677255 100644
--- 
a/test/it/parser/src/main/resources/sql/supported/dml/select-start-with-connect-by.xml
+++ 
b/test/it/parser/src/main/resources/sql/supported/dml/select-start-with-connect-by.xml
@@ -18,4 +18,5 @@
 
 <sql-cases>
     <sql-case id="select_start_with_connect_by" value="SELECT * FROM (SELECT 
level, o.* FROM t_order o WHERE o.order_id = 1 START WITH o.user_id = 1 CONNECT 
BY PRIOR user_id = user_id ORDER BY level) temp WHERE ROWNUM = 1" 
db-types="Oracle" />
+    <sql-case id="select_with_connect_by" value="SELECT * FROM (SELECT 
REGEXP_SUBSTR('5214|1521|5152|1616|218|8226', '[^|]+', 1, LEVEL) AS order_id 
FROM dual CONNECT BY REGEXP_SUBSTR(?, '[^|]+', 1, LEVEL) IS NOT NULL) WHERE 
ROWNUM &lt;= ?" db-types="Oracle"/>
 </sql-cases>

Reply via email to