From cbf2e5ebf0b7b5a999d4dd9222038ee8e59c79ab Mon Sep 17 00:00:00 2001 From: Burak KALAYCI Date: Fri, 21 Aug 2026 15:31:59 +0300 Subject: [PATCH] fix: parse MySQL simple-form CREATE FUNCTION in multi-statement scripts Simple-form CREATE FUNCTION (RETURN expr without BEGIN...END) consumed the trailing semicolon on the body and then treated leftover tokens as an unexpected end of the function. A following SELECT or CREATE in the same script therefore failed to parse. Parse the body without requiring end-of-script, and treat a terminator on the function body as completing the CREATE statement. Fixes #6705 --- .../mysql/parser/MySqlStatementParser.java | 12 +++- .../druid/sql/parser/SQLStatementParser.java | 20 ++++++ ...MySqlCreateFunctionMultiStatementTest.java | 62 +++++++++++++++++++ 3 files changed, 92 insertions(+), 2 deletions(-) create mode 100644 core/src/test/java/com/alibaba/druid/bvt/sql/mysql/create_function/MySqlCreateFunctionMultiStatementTest.java diff --git a/core/src/main/java/com/alibaba/druid/sql/dialect/mysql/parser/MySqlStatementParser.java b/core/src/main/java/com/alibaba/druid/sql/dialect/mysql/parser/MySqlStatementParser.java index ac37b1bef8..ee6388838b 100644 --- a/core/src/main/java/com/alibaba/druid/sql/dialect/mysql/parser/MySqlStatementParser.java +++ b/core/src/main/java/com/alibaba/druid/sql/dialect/mysql/parser/MySqlStatementParser.java @@ -8287,7 +8287,12 @@ public SQLCreateFunctionStatement parseCreateFunction() { if (lexer.token() == Token.BEGIN) { block = this.parseBlock(); } else { - block = this.parseStatement(); + // Simple-form RETURN body is not a complete script; parseStatement() + // would reject leftover tokens via checkEndToken. + block = this.parseStatement0(); + if (lexer.nextIf(Token.SEMI)) { + block.setAfterSemi(true); + } } stmt.setBlock(block); @@ -8369,7 +8374,10 @@ public SQLCreateProcedureStatement parseCreateProcedure() { if (lexer.token() == Token.BEGIN) { block = this.parseBlock(); } else { - block = this.parseStatement(); + block = this.parseStatement0(); + if (lexer.nextIf(Token.SEMI)) { + block.setAfterSemi(true); + } } stmt.setBlock(block); diff --git a/core/src/main/java/com/alibaba/druid/sql/parser/SQLStatementParser.java b/core/src/main/java/com/alibaba/druid/sql/parser/SQLStatementParser.java index 193002cfdc..ebdc039185 100644 --- a/core/src/main/java/com/alibaba/druid/sql/parser/SQLStatementParser.java +++ b/core/src/main/java/com/alibaba/druid/sql/parser/SQLStatementParser.java @@ -288,6 +288,9 @@ public void parseStatementList(List statementList, int max, SQLObj stmt.addBeforeComment(comments); stmt.setParent(parent); statementList.add(stmt); + if (createStatementConsumedSemi(stmt)) { + semi = true; + } continue; } case INSERT: { @@ -5328,6 +5331,23 @@ private void checkEndToken() { } } + /** + * Simple-form CREATE FUNCTION/PROCEDURE consumes the statement terminator on + * the body, not on the CREATE statement itself. + */ + private static boolean createStatementConsumedSemi(SQLStatement stmt) { + if (stmt.isAfterSemi()) { + return true; + } + SQLStatement block = null; + if (stmt instanceof SQLCreateFunctionStatement) { + block = ((SQLCreateFunctionStatement) stmt).getBlock(); + } else if (stmt instanceof SQLCreateProcedureStatement) { + block = ((SQLCreateProcedureStatement) stmt).getBlock(); + } + return block != null && block.isAfterSemi(); + } + public void parseExplainFormatPartition(SQLExplainStatement explain) { } diff --git a/core/src/test/java/com/alibaba/druid/bvt/sql/mysql/create_function/MySqlCreateFunctionMultiStatementTest.java b/core/src/test/java/com/alibaba/druid/bvt/sql/mysql/create_function/MySqlCreateFunctionMultiStatementTest.java new file mode 100644 index 0000000000..bd92dc3d3e --- /dev/null +++ b/core/src/test/java/com/alibaba/druid/bvt/sql/mysql/create_function/MySqlCreateFunctionMultiStatementTest.java @@ -0,0 +1,62 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.druid.bvt.sql.mysql.create_function; + +import com.alibaba.druid.sql.SQLUtils; +import com.alibaba.druid.sql.ast.SQLStatement; +import com.alibaba.druid.sql.ast.statement.SQLCreateFunctionStatement; +import com.alibaba.druid.sql.ast.statement.SQLSelectStatement; +import com.alibaba.druid.util.JdbcConstants; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class MySqlCreateFunctionMultiStatementTest { + @Test + public void testSimpleFormFunctionThenSelect() { + String sql = "CREATE FUNCTION f() RETURNS INT RETURN 1;\nSELECT 1;"; + List stmts = SQLUtils.parseStatements(sql, JdbcConstants.MYSQL); + assertEquals(2, stmts.size()); + assertTrue(stmts.get(0) instanceof SQLCreateFunctionStatement); + assertTrue(stmts.get(1) instanceof SQLSelectStatement); + String functionSql = SQLUtils.toMySqlString(stmts.get(0)); + assertTrue(functionSql.contains("CREATE FUNCTION")); + assertTrue(functionSql.contains("RETURN 1")); + assertFalse(functionSql.contains(";;")); + } + + @Test + public void testSimpleFormFunctionThenFunction() { + String sql = "CREATE FUNCTION f() RETURNS INT RETURN 1;\nCREATE FUNCTION g() RETURNS INT RETURN 2;"; + List stmts = SQLUtils.parseStatements(sql, JdbcConstants.MYSQL); + assertEquals(2, stmts.size()); + assertTrue(stmts.get(0) instanceof SQLCreateFunctionStatement); + assertTrue(stmts.get(1) instanceof SQLCreateFunctionStatement); + } + + @Test + public void testBeginEndFunctionThenSelect() { + String sql = "CREATE FUNCTION f() RETURNS INT BEGIN RETURN 1; END;\nSELECT 1;"; + List stmts = SQLUtils.parseStatements(sql, JdbcConstants.MYSQL); + assertEquals(2, stmts.size()); + assertTrue(stmts.get(0) instanceof SQLCreateFunctionStatement); + assertTrue(stmts.get(1) instanceof SQLSelectStatement); + } +}