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

strongduanmu 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 cf2c169ed16 Support parenthesized WITH queries and qualified PL/SQL 
collection types in Oracle parser (#39914)
cf2c169ed16 is described below

commit cf2c169ed1692d84d7df79f85bda75b699143e26
Author: ZhangCheng <[email protected]>
AuthorDate: Tue Sep 22 14:07:12 2026 +0800

    Support parenthesized WITH queries and qualified PL/SQL collection types in 
Oracle parser (#39914)
    
    * Support additional Oracle parser syntax
    
    * Apply Spotless formatting to Oracle parser test
    
    * Fix Oracle parser edge cases
    
    * Update release notes for Oracle parser enhancements
---
 RELEASE-NOTES.md                                   |  2 ++
 .../src/main/antlr4/imports/oracle/DMLStatement.g4 |  2 +-
 .../oracle/src/main/antlr4/imports/oracle/PLSQL.g4 |  9 +++++++--
 .../statement/type/OracleDDLStatementVisitor.java  |  7 ++++---
 .../statement/type/OracleDMLStatementVisitor.java  | 11 +++++++++++
 .../type/OracleDMLStatementVisitorTest.java        | 23 ++++++++++++++++++++--
 .../main/resources/case/ddl/create-procedure.xml   |  1 +
 .../src/main/resources/case/dml/select-with.xml    | 23 ++++++++++++++++++++++
 .../sql/supported/ddl/create-procedure.xml         |  1 +
 .../resources/sql/supported/dml/select-with.xml    |  1 +
 10 files changed, 72 insertions(+), 8 deletions(-)

diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md
index fd18eaf67c8..2d29ec08451 100644
--- a/RELEASE-NOTES.md
+++ b/RELEASE-NOTES.md
@@ -131,6 +131,8 @@
 1. SQL Parser: Support Oracle PL/SQL syntax parsing and binding - 
[#39723](https://github.com/apache/shardingsphere/pull/39723)
 1. SQL Parser: Support scalar expressions in Oracle `IN` predicates - 
[#39876](https://github.com/apache/shardingsphere/pull/39876)
 1. SQL Parser: Support Oracle `ORDER BY` after `FOR UPDATE` and `HAVING` 
without `GROUP BY` - 
[#39877](https://github.com/apache/shardingsphere/pull/39877)
+1. SQL Parser: Support Oracle parenthesized `WITH` queries and qualified 
PL/SQL collection types - 
[#39914](https://github.com/apache/shardingsphere/pull/39914)
+1. SQL Parser: Support Oracle `DROP UNUSED COLUMNS` syntax - 
[#39917](https://github.com/apache/shardingsphere/pull/39917)
 1. SQL Binder: Support select order by index bind metadata - 
[#38386](https://github.com/apache/shardingsphere/pull/38386)
 1. SQL Binder: Support SQL bind when with temp table name is same with 
physical table - [#38411](https://github.com/apache/shardingsphere/pull/38411)
 1. SQL Binder: Support PostgreSQL whole-row projection binding - 
[#39276](https://github.com/apache/shardingsphere/pull/39276)
diff --git 
a/parser/sql/engine/dialect/oracle/src/main/antlr4/imports/oracle/DMLStatement.g4
 
b/parser/sql/engine/dialect/oracle/src/main/antlr4/imports/oracle/DMLStatement.g4
index 63a7ecacdfb..0c2c213c31b 100644
--- 
a/parser/sql/engine/dialect/oracle/src/main/antlr4/imports/oracle/DMLStatement.g4
+++ 
b/parser/sql/engine/dialect/oracle/src/main/antlr4/imports/oracle/DMLStatement.g4
@@ -160,7 +160,7 @@ select
     ;
 
 selectSubquery
-    : selectSubquery combineType selectSubquery | ((queryBlock | 
parenthesisSelectSubquery) pivotClause? orderByClause? rowLimitingClause)
+    : selectSubquery combineType selectSubquery | ((queryBlock | withClause 
parenthesisSelectSubquery | parenthesisSelectSubquery) pivotClause? 
orderByClause? rowLimitingClause)
     ;
 
 combineType
diff --git 
a/parser/sql/engine/dialect/oracle/src/main/antlr4/imports/oracle/PLSQL.g4 
b/parser/sql/engine/dialect/oracle/src/main/antlr4/imports/oracle/PLSQL.g4
index 54b6b486388..f781265e017 100644
--- a/parser/sql/engine/dialect/oracle/src/main/antlr4/imports/oracle/PLSQL.g4
+++ b/parser/sql/engine/dialect/oracle/src/main/antlr4/imports/oracle/PLSQL.g4
@@ -581,13 +581,18 @@ itemDeclaration
 collectionVariableDecl
     : variableName
       (
-      typeName (ASSIGNMENT_OPERATOR_ (qualifiedExpression | functionCall | 
variableName))?
-      | typeName (ASSIGNMENT_OPERATOR_  (collectionConstructor | 
variableName))?
+      collectionTypeName (ASSIGNMENT_OPERATOR_ (qualifiedExpression | 
functionCall | variableName))?
+      | collectionTypeName (ASSIGNMENT_OPERATOR_  (collectionConstructor | 
variableName))?
       | typeName MOD_ TYPE
       )
       SEMI_
     ;
 
+collectionTypeName
+    : typeName
+    | schemaName DOT_ owner DOT_ name
+    ;
+
 qualifiedExpression
     : typemark LP_ aggregate RP_
     ;
diff --git 
a/parser/sql/engine/dialect/oracle/src/main/java/org/apache/shardingsphere/sql/parser/engine/oracle/visitor/statement/type/OracleDDLStatementVisitor.java
 
b/parser/sql/engine/dialect/oracle/src/main/java/org/apache/shardingsphere/sql/parser/engine/oracle/visitor/statement/type/OracleDDLStatementVisitor.java
index 84dbfd5a7da..c9ece94fcec 100644
--- 
a/parser/sql/engine/dialect/oracle/src/main/java/org/apache/shardingsphere/sql/parser/engine/oracle/visitor/statement/type/OracleDDLStatementVisitor.java
+++ 
b/parser/sql/engine/dialect/oracle/src/main/java/org/apache/shardingsphere/sql/parser/engine/oracle/visitor/statement/type/OracleDDLStatementVisitor.java
@@ -2381,9 +2381,10 @@ public final class OracleDDLStatementVisitor extends 
OracleStatementVisitor impl
         
         @Override
         public Void visitCollectionVariableDecl(final 
CollectionVariableDeclContext ctx) {
-            if (null != ctx.TYPE() && null != ctx.MOD_() && null != 
ctx.typeName().owner()) {
-                
result.getTables().add(createTypeAttributeTable(ctx.typeName()));
-                
result.getColumns().add(createTypeAttributeColumn(ctx.typeName()));
+            TypeNameContext typeName = null == ctx.typeName() ? 
ctx.collectionTypeName().typeName() : ctx.typeName();
+            if (null != ctx.TYPE() && null != ctx.MOD_() && null != 
typeName.owner()) {
+                result.getTables().add(createTypeAttributeTable(typeName));
+                result.getColumns().add(createTypeAttributeColumn(typeName));
             }
             return super.visitCollectionVariableDecl(ctx);
         }
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 220cbb83206..5ce990d4955 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
@@ -762,9 +762,20 @@ public final class OracleDMLStatementVisitor extends 
OracleStatementVisitor impl
             result = createSelectCombineClause(ctx, left);
         } else {
             result = null == ctx.queryBlock() ? (SelectStatement) 
visit(ctx.parenthesisSelectSubquery()) : (SelectStatement) 
visit(ctx.queryBlock());
+            if (null != ctx.withClause()) {
+                SelectStatement previous = result;
+                result = 
createSelectStatementBuilder(previous).with((WithSegment) 
visit(ctx.withClause())).build();
+                result.addParameterMarkers(previous.getParameterMarkers());
+                result.getVariableNames().addAll(previous.getVariableNames());
+                result.getComments().addAll(previous.getComments());
+            }
         }
         if (null != ctx.orderByClause()) {
+            SelectStatement previous = result;
             result = 
createSelectStatementBuilder(result).orderBy((OrderBySegment) 
visit(ctx.orderByClause())).build();
+            result.addParameterMarkers(previous.getParameterMarkers());
+            result.getVariableNames().addAll(previous.getVariableNames());
+            result.getComments().addAll(previous.getComments());
         }
         result.addParameterMarkers(ctx.getParent() instanceof ExecuteContext ? 
getGlobalParameterMarkerSegments() : popAllStatementParameterMarkerSegments());
         result.getVariableNames().addAll(getVariableNames());
diff --git 
a/parser/sql/engine/dialect/oracle/src/test/java/org/apache/shardingsphere/sql/parser/engine/oracle/visitor/statement/type/OracleDMLStatementVisitorTest.java
 
b/parser/sql/engine/dialect/oracle/src/test/java/org/apache/shardingsphere/sql/parser/engine/oracle/visitor/statement/type/OracleDMLStatementVisitorTest.java
index 6c1bae49841..104643517de 100644
--- 
a/parser/sql/engine/dialect/oracle/src/test/java/org/apache/shardingsphere/sql/parser/engine/oracle/visitor/statement/type/OracleDMLStatementVisitorTest.java
+++ 
b/parser/sql/engine/dialect/oracle/src/test/java/org/apache/shardingsphere/sql/parser/engine/oracle/visitor/statement/type/OracleDMLStatementVisitorTest.java
@@ -21,17 +21,36 @@ import 
org.apache.shardingsphere.sql.parser.engine.api.CacheOption;
 import org.apache.shardingsphere.sql.parser.engine.api.SQLParserEngine;
 import 
org.apache.shardingsphere.sql.parser.engine.api.SQLStatementVisitorEngine;
 import 
org.apache.shardingsphere.sql.parser.engine.exception.SQLParsingException;
+import 
org.apache.shardingsphere.sql.parser.statement.core.statement.type.dml.SelectStatement;
 import org.junit.jupiter.api.Test;
 
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
 import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
 
 class OracleDMLStatementVisitorTest {
     
     @Test
     void assertVisitSelect() {
+        assertThrows(SQLParsingException.class, () -> parse("SELECT 1 FROM 
DUAL ORDER BY 1 FOR UPDATE NOWAIT ORDER BY 1"));
+    }
+    
+    @Test
+    void assertVisitWithParenthesizedMainQuery() {
+        SelectStatement actual = parse("WITH q AS (SELECT 1 AS n FROM DUAL) 
(SELECT n FROM q)");
+        assertTrue(actual.getWith().isPresent());
+    }
+    
+    @Test
+    void assertVisitWithParenthesizedMainQueryWithOrderBy() {
+        SelectStatement actual = parse("WITH q AS (SELECT 1 AS n FROM DUAL) 
(SELECT ? FROM q) ORDER BY 1");
+        assertThat(actual.getParameterMarkers().size(), is(1));
+    }
+    
+    private SelectStatement parse(final String sql) {
         CacheOption cacheOption = new CacheOption(128, 1024L);
         SQLParserEngine parserEngine = new SQLParserEngine("Oracle", 
cacheOption);
-        SQLStatementVisitorEngine visitorEngine = new 
SQLStatementVisitorEngine("Oracle");
-        assertThrows(SQLParsingException.class, () -> 
visitorEngine.visit(parserEngine.parse("SELECT 1 FROM DUAL ORDER BY 1 FOR 
UPDATE NOWAIT ORDER BY 1", false)));
+        return (SelectStatement) new 
SQLStatementVisitorEngine("Oracle").visit(parserEngine.parse(sql, false));
     }
 }
diff --git a/test/it/parser/src/main/resources/case/ddl/create-procedure.xml 
b/test/it/parser/src/main/resources/case/ddl/create-procedure.xml
index 27f48287021..407afce7268 100644
--- a/test/it/parser/src/main/resources/case/ddl/create-procedure.xml
+++ b/test/it/parser/src/main/resources/case/ddl/create-procedure.xml
@@ -51,6 +51,7 @@
         </sql-statements>
     </create-procedure>
     <create-procedure sql-case-id="create_plsql_block" />
+    <create-procedure 
sql-case-id="create_plsql_block_with_three_part_collection_type" />
     <create-procedure sql-case-id="create_procedure_with_insert_into_values">
         <procedure-name name="T522_PROC" />
         <sql-statements>
diff --git a/test/it/parser/src/main/resources/case/dml/select-with.xml 
b/test/it/parser/src/main/resources/case/dml/select-with.xml
index 93545f7863d..ac4cd883019 100644
--- a/test/it/parser/src/main/resources/case/dml/select-with.xml
+++ b/test/it/parser/src/main/resources/case/dml/select-with.xml
@@ -484,6 +484,29 @@
         </from>
     </select>
 
+    <select sql-case-id="select_with_parenthesized_main_query_oracle">
+        <with start-index="0" stop-index="26">
+            <common-table-expression name="q" start-index="5" stop-index="26">
+                <subquery-expression start-index="11" stop-index="25">
+                    <select>
+                        <projections start-index="18" stop-index="18">
+                            <column-projection name="n" start-index="18" 
stop-index="18" />
+                        </projections>
+                        <from>
+                            <simple-table name="t" start-index="25" 
stop-index="25" />
+                        </from>
+                    </select>
+                </subquery-expression>
+            </common-table-expression>
+        </with>
+        <projections start-index="36" stop-index="36">
+            <column-projection name="n" start-index="36" stop-index="36" />
+        </projections>
+        <from>
+            <simple-table name="q" start-index="43" stop-index="43" />
+        </from>
+    </select>
+
     <select sql-case-id="select_with_oracle_cte_column_aliases">
         <with start-index="0" stop-index="108">
             <common-table-expression name="user_cte" start-index="5" 
stop-index="108">
diff --git 
a/test/it/parser/src/main/resources/sql/supported/ddl/create-procedure.xml 
b/test/it/parser/src/main/resources/sql/supported/ddl/create-procedure.xml
index b7d6bdce229..6687c84b1da 100644
--- a/test/it/parser/src/main/resources/sql/supported/ddl/create-procedure.xml
+++ b/test/it/parser/src/main/resources/sql/supported/ddl/create-procedure.xml
@@ -47,6 +47,7 @@
     <sql-case id="create_procedure_with_cursor_definition" value="CREATE OR 
REPLACE EDITIONABLE PROCEDURE my_proc AS cursor cursor1 is select distinct a 
from tbl; var1 varchar2(500); var2 varchar2(500); BEGIN INSERT INTO t VALUES 
(var1, var2); END;" db-types="Oracle" />
     <sql-case id="create_procedure_with_collection_type_definition" 
value="CREATE OR REPLACE EDITIONABLE PROCEDURE my_proc AS TYPE my_type is table 
of my_table.id%TYPE; BEGIN INSERT INTO t VALUES (var1, var2); END;" 
db-types="Oracle" />
     <sql-case id="create_plsql_block" value="DECLARE warehouse NUMBER := 1;   
ground    NUMBER := 1;   insured   NUMBER := 1;   result    NUMBER; BEGIN   
SELECT BIN_TO_NUM(warehouse, ground, insured) INTO result FROM DUAL;   UPDATE 
orders SET order_status = result WHERE order_id = 2441; END;" db-types="Oracle" 
/>
+    <sql-case id="create_plsql_block_with_three_part_collection_type" 
value="DECLARE foo_values SYS.DBMS_SQL.VARCHAR2A; BEGIN NULL; END;" 
db-types="Oracle" />
     <sql-case id="create_procedure_with_insert_into_values" value="create 
procedure T522_PROC (i int =10) as begin insert into T522 (ROW_INT) values 
(:i); end" db-types="Firebird"/>
     <sql-case 
id="create_procedure_with_declare_and_cursor_for_in_select_and_open" 
value="CREATE PROCEDURE F865_PROC (offset_value INT, page_size INT) as DECLARE 
catalog_page CURSOR FOR (SELECT * FROM company ORDER BY namecompany OFFSET 
:offset_value ROWS FETCH NEXT :page_size ROWS ONLY); BEGIN OPEN catalog_page; 
END" db-types="Firebird"/>
     <sql-case id="create_procedure_with_handler_and_table_creation" 
value="CREATE PROCEDURE peter1 () BEGIN DECLARE v INTEGER DEFAULT 1234; DECLARE 
CONTINUE HANDLER FOR SQLWARNING BEGIN SHOW WARNINGS; SELECT &quot;handler done: 
&quot;,v; END; CREATE TABLE gg (smallint_column SMALLINT); CALL peter2(v); END" 
db-types="MySQL" />
diff --git 
a/test/it/parser/src/main/resources/sql/supported/dml/select-with.xml 
b/test/it/parser/src/main/resources/sql/supported/dml/select-with.xml
index a1457492490..3d8fa0ca8cb 100644
--- a/test/it/parser/src/main/resources/sql/supported/dml/select-with.xml
+++ b/test/it/parser/src/main/resources/sql/supported/dml/select-with.xml
@@ -26,6 +26,7 @@
     <sql-case id="select_with_multiple_cte_definitions" value="WITH 
cte1(status, user_id) AS (SELECT status, user_id FROM t_order), cte2(item_id) 
AS (SELECT item_id FROM t_order_item) SELECT status, user_id, item_id FROM cte1 
INNER JOIN cte2 ON cte1.user_id = cte2.user_id" db-types="SQLServer" />
     <sql-case id="select_with_single_subquery" value="WITH t AS (SELECT a+2 
c,b FROM t1) SELECT c,b FROM t" 
db-types="MySQL,PostgreSQL,openGauss,Doris,Firebird" />
     <sql-case id="select_with_oracle_single_subquery" value="WITH t AS (SELECT 
a+2 c,b FROM t1) SELECT c,b FROM t" db-types="Oracle" />
+    <sql-case id="select_with_parenthesized_main_query_oracle" value="WITH q 
AS (SELECT n FROM t) (SELECT n FROM q)" db-types="Oracle" />
     <sql-case id="select_with_oracle_cte_column_aliases" value="WITH user_cte 
(user_id_alias, user_name_alias, email_alias) AS (SELECT user_id, user_name, 
email FROM t_user) SELECT user_id_alias, user_name_alias, email_alias FROM 
user_cte ORDER BY user_id_alias" db-types="Oracle" />
     <sql-case id="select_with_multiple_subquery" value="WITH cte1(col1, col2, 
col3) as (SELECT emp_no,first_name,last_name FROM employees WHERE 
emp_no=10012), cte2(col1, col2, col3) as (SELECT emp_no,first_name,last_name 
from employees WHERE emp_no=10012) SELECT col1, col2, col3 FROM cte1" 
db-types="MySQL" />
     <sql-case id="select_with_recursive_union_all1" value="WITH RECURSIVE 
DirectoryCTE as (SELECT * FROM table1 WHERE id = 1 AND project_id = 2 UNION ALL 
SELECT * FROM project_file_catalog t INNER JOIN DirectoryCTE cte ON 
t.project_id = cte.project_id AND t.parent_id = cte.id) SELECT * FROM 
DirectoryCTE ORDER BY level" db-types="MySQL" />

Reply via email to