Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -1430,8 +1430,30 @@ protected void partitionClauseRest(SQLPartitionBy clause) {

if (lexer.identifierEquals(FnvHash.Constants.SUBPARTITION)) {
lexer.nextToken();
acceptIdentifier("OPTIONS");
this.exprParser.parseAssignItem(subPartitionByClause.getOptions(), subPartitionByClause);
if (lexer.identifierEquals("TEMPLATE")) {
lexer.nextToken();
accept(Token.LPAREN);
for (; ; ) {
acceptIdentifier("SUBPARTITION");
SQLSubPartition subPartition = new SQLSubPartition();
subPartition.setName(this.exprParser.name());
SQLPartitionValue values = this.exprParser.parsePartitionValues();
if (values != null) {
subPartition.setValues(values);
}
subPartition.setParent(subPartitionByClause);
subPartitionByClause.getSubPartitionTemplate().add(subPartition);
if (lexer.token() == Token.COMMA) {
lexer.nextToken();
continue;
Comment on lines +1436 to +1448
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Suggestion] Template subpartitions are manually constructed with only name + VALUES, skipping per-subpartition properties (TABLESPACE, COMMENT, ENGINE, DATA DIRECTORY, etc.) that this.exprParser.parseSubPartition() handles. The Oracle parser's equivalent code correctly calls parseSubPartition().

Suggested fix: Replace the manual construction with a call to the existing parser method:

acceptIdentifier("SUBPARTITION");
SQLSubPartition subPartition = this.exprParser.parseSubPartition();
SQLPartitionValue values = this.exprParser.parsePartitionValues();
if (values != null) {
    subPartition.setValues(values);
}
subPartition.setParent(subPartitionByClause);
subPartitionByClause.getSubPartitionTemplate().add(subPartition);

}
break;
}
accept(Token.RPAREN);
} else {
acceptIdentifier("OPTIONS");
this.exprParser.parseAssignItem(subPartitionByClause.getOptions(), subPartitionByClause);
}
}

if (lexer.identifierEquals(FnvHash.Constants.SUBPARTITIONS)) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
package com.alibaba.druid.bvt.sql.oceanbase.issues;

import com.alibaba.druid.DbType;
import com.alibaba.druid.sql.ast.SQLStatement;
import com.alibaba.druid.sql.parser.SQLParserUtils;
import com.alibaba.druid.sql.parser.SQLStatementParser;
import org.junit.jupiter.api.Test;

import java.util.List;

import static org.junit.jupiter.api.Assertions.assertEquals;

/**
* Fix OceanBase/Oracle SUBPARTITION TEMPLATE parsing in CREATE TABLE.
* <p>
* The MySQL parser (used by OceanBase) only recognized SUBPARTITION OPTIONS
* after the subpartition-by clause, but not SUBPARTITION TEMPLATE which is
* used to define template subpartitions for composite partitioning.
*
* @see <a href="https://github.com/alibaba/druid/issues/6160">Issue #6160</a>
*/
public class Issue6160 {
@Test
public void test_subpartition_template_list_range() {
// Exact SQL from the issue
String sql = "CREATE TABLE t2_m_lr(col1 INT, col2 INT)\n"
+ "PARTITION BY LIST (col1)\n"
+ "SUBPARTITION BY RANGE(col2)\n"
+ "SUBPARTITION TEMPLATE\n"
+ " (SUBPARTITION mp0 VALUES LESS THAN(100),\n"
+ " SUBPARTITION mp1 VALUES LESS THAN(200),\n"
+ " SUBPARTITION mp2 VALUES LESS THAN(300))\n"
+ " (PARTITION p0 VALUES IN(1,3),\n"
+ " PARTITION p1 VALUES IN(4,6),\n"
+ " PARTITION p2 VALUES IN(7,9))";

for (DbType dbType : new DbType[]{DbType.oceanbase, DbType.mysql}) {
SQLStatementParser parser = SQLParserUtils.createSQLStatementParser(sql, dbType);
List<SQLStatement> stmtList = parser.parseStatementList();
assertEquals(1, stmtList.size());
}
}

@Test
public void test_subpartition_template_range_hash() {
String sql = "CREATE TABLE t_range_hash(col1 INT, col2 INT)\n"
+ "PARTITION BY RANGE(col1)\n"
+ "SUBPARTITION BY HASH(col2)\n"
+ "SUBPARTITION TEMPLATE\n"
+ " (SUBPARTITION sp0,\n"
+ " SUBPARTITION sp1,\n"
+ " SUBPARTITION sp2)\n"
+ " (PARTITION p0 VALUES LESS THAN(100),\n"
+ " PARTITION p1 VALUES LESS THAN(200))";

SQLStatementParser parser = SQLParserUtils.createSQLStatementParser(sql, DbType.oceanbase);
List<SQLStatement> stmtList = parser.parseStatementList();
assertEquals(1, stmtList.size());
}

@Test
public void test_subpartition_template_with_values_in() {
String sql = "CREATE TABLE t_range_list(col1 INT, col2 INT)\n"
+ "PARTITION BY RANGE(col1)\n"
+ "SUBPARTITION BY LIST(col2)\n"
+ "SUBPARTITION TEMPLATE\n"
+ " (SUBPARTITION sp0 VALUES IN(1,2,3),\n"
+ " SUBPARTITION sp1 VALUES IN(4,5,6))\n"
+ " (PARTITION p0 VALUES LESS THAN(100),\n"
+ " PARTITION p1 VALUES LESS THAN(200))";

SQLStatementParser parser = SQLParserUtils.createSQLStatementParser(sql, DbType.oceanbase);
List<SQLStatement> stmtList = parser.parseStatementList();
assertEquals(1, stmtList.size());
}

@Test
public void test_subpartition_options_still_works() {
// Ensure existing SUBPARTITION OPTIONS parsing is not broken
String sql = "CREATE TABLE t_opt(id INT)\n"
+ "PARTITION BY HASH(id)\n"
+ "SUBPARTITION BY KEY(id)\n"
+ "SUBPARTITIONS 4";

SQLStatementParser parser = SQLParserUtils.createSQLStatementParser(sql, DbType.mysql);
List<SQLStatement> stmtList = parser.parseStatementList();
assertEquals(1, stmtList.size());
}
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Suggestion] All 4 test cases assert stmtList.size() == 1 but never verify subPartitionTemplate contents (names, values, count). Neighboring tests (Issue5078, Issue6102) use round-trip verification via SQLParseAssertUtil.assertParseSql().

Suggested fix: Add AST-level assertions (after fixing output visitors for round-trip support):

SQLCreateTableStatement stmt = (SQLCreateTableStatement) stmtList.get(0);
SQLSubPartitionBy spBy = stmt.getPartitionBy().getSubPartitionBy();
List<SQLSubPartition> template = spBy.getSubPartitionTemplate();
assertEquals(3, template.size());
assertEquals("mp0", template.get(0).getName().getSimpleName());

Or use SQLParseAssertUtil.assertParseSql(sql, dbType) for round-trip validation once output visitors are fixed.