From 37b076be5121edbe0412f6b8ef190d595692b0e0 Mon Sep 17 00:00:00 2001 From: Sam Brannen <104798+sbrannen@users.noreply.github.com> Date: Mon, 11 Aug 2025 15:22:48 +0300 Subject: [PATCH] Support multiple result sets in ScriptUtils.executeSqlScript() Prior to this commit, ScriptUtils.executeSqlScript() treated every statement within the script as if it were a single insert/update/delete statement. This disregarded the fact that the execution of a JDBC Statement can result in multiple individual statements, some of which result in a ResultSet and others that result in an update count. For example, when executing a stored procedure on Sybase, ScriptUtils did not execute all statements within the stored procedure. To address that, this commit revises the implementation of ScriptUtils.executeSqlScript() so that it handles multiple results and differentiates between result sets and update counts. Closes gh-35248 --- .../jdbc/datasource/init/ScriptUtils.java | 41 ++++++++--- .../simple/JdbcClientIntegrationTests.java | 10 +-- .../SimpleJdbcInsertIntegrationTests.java | 2 +- .../init/ScriptUtilsIntegrationTests.java | 69 ++++++++++++++++++- .../init/users-schema-with-custom-schema.sql | 2 +- .../jdbc/datasource/init/users-schema.sql | 2 +- 6 files changed, 106 insertions(+), 20 deletions(-) diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/datasource/init/ScriptUtils.java b/spring-jdbc/src/main/java/org/springframework/jdbc/datasource/init/ScriptUtils.java index b93ab0b6a92..ec40441a7aa 100644 --- a/spring-jdbc/src/main/java/org/springframework/jdbc/datasource/init/ScriptUtils.java +++ b/spring-jdbc/src/main/java/org/springframework/jdbc/datasource/init/ScriptUtils.java @@ -258,18 +258,29 @@ public abstract class ScriptUtils { for (String statement : statements) { stmtNumber++; try { - stmt.execute(statement); - int rowsAffected = stmt.getUpdateCount(); + boolean hasResultSet = stmt.execute(statement); + int updateCount = -1; if (logger.isDebugEnabled()) { - logger.debug(rowsAffected + " returned as update count for SQL: " + statement); - SQLWarning warningToLog = stmt.getWarnings(); - while (warningToLog != null) { - logger.debug("SQLWarning ignored: SQL state '" + warningToLog.getSQLState() + - "', error code '" + warningToLog.getErrorCode() + - "', message [" + warningToLog.getMessage() + "]"); - warningToLog = warningToLog.getNextWarning(); - } + logSqlWarnings(stmt); } + do { + if (hasResultSet) { + // We invoke getResultSet() to ensure the JDBC driver processes + // it, but we intentionally ignore the returned ResultSet since + // we cannot do anything meaningful with it here. + stmt.getResultSet(); + if (logger.isDebugEnabled()) { + logger.debug("ResultSet returned for SQL: " + statement); + } + } + else { + updateCount = stmt.getUpdateCount(); + if (updateCount >= 0 && logger.isDebugEnabled()) { + logger.debug(updateCount + " returned as update count for SQL: " + statement); + } + } + hasResultSet = stmt.getMoreResults(); + } while (hasResultSet || updateCount != -1); } catch (SQLException ex) { boolean dropStatement = StringUtils.startsWithIgnoreCase(statement.trim(), "drop"); @@ -307,6 +318,16 @@ public abstract class ScriptUtils { } } + private static void logSqlWarnings(Statement stmt) throws SQLException { + SQLWarning warningToLog = stmt.getWarnings(); + while (warningToLog != null) { + logger.debug("SQLWarning ignored: SQL state '" + warningToLog.getSQLState() + + "', error code '" + warningToLog.getErrorCode() + + "', message [" + warningToLog.getMessage() + "]"); + warningToLog = warningToLog.getNextWarning(); + } + } + /** * Read a script from the provided resource, using the supplied comment prefixes * and statement separator, and build a {@code String} containing the lines. diff --git a/spring-jdbc/src/test/java/org/springframework/jdbc/core/simple/JdbcClientIntegrationTests.java b/spring-jdbc/src/test/java/org/springframework/jdbc/core/simple/JdbcClientIntegrationTests.java index fd4daf2064d..55130eb1ec9 100644 --- a/spring-jdbc/src/test/java/org/springframework/jdbc/core/simple/JdbcClientIntegrationTests.java +++ b/spring-jdbc/src/test/java/org/springframework/jdbc/core/simple/JdbcClientIntegrationTests.java @@ -74,7 +74,7 @@ class JdbcClientIntegrationTests { @Test void updateWithGeneratedKeys() { - int expectedId = 2; + int expectedId = 1; String firstName = "Jane"; String lastName = "Smith"; @@ -92,7 +92,7 @@ class JdbcClientIntegrationTests { @Test void updateWithGeneratedKeysAndKeyColumnNames() { - int expectedId = 2; + int expectedId = 1; String firstName = "Jane"; String lastName = "Smith"; @@ -110,7 +110,7 @@ class JdbcClientIntegrationTests { @Test void updateWithGeneratedKeysUsingNamedParameters() { - int expectedId = 2; + int expectedId = 1; String firstName = "Jane"; String lastName = "Smith"; @@ -129,7 +129,7 @@ class JdbcClientIntegrationTests { @Test void updateWithGeneratedKeysAndKeyColumnNamesUsingNamedParameters() { - int expectedId = 2; + int expectedId = 1; String firstName = "Jane"; String lastName = "Smith"; @@ -217,7 +217,7 @@ class JdbcClientIntegrationTests { private static void assertResults(List users) { - assertThat(users).containsExactly(new User(2, "John", "John"), new User(3, "John", "Smith")); + assertThat(users).containsExactly(new User(1, "John", "John"), new User(2, "John", "Smith")); } record Name(String name) {} diff --git a/spring-jdbc/src/test/java/org/springframework/jdbc/core/simple/SimpleJdbcInsertIntegrationTests.java b/spring-jdbc/src/test/java/org/springframework/jdbc/core/simple/SimpleJdbcInsertIntegrationTests.java index 02928652e53..720fbb330a4 100644 --- a/spring-jdbc/src/test/java/org/springframework/jdbc/core/simple/SimpleJdbcInsertIntegrationTests.java +++ b/spring-jdbc/src/test/java/org/springframework/jdbc/core/simple/SimpleJdbcInsertIntegrationTests.java @@ -323,7 +323,7 @@ class SimpleJdbcInsertIntegrationTests { protected void insertJaneSmith(SimpleJdbcInsert insert) { Number id = insert.executeAndReturnKey(Map.of("first_name", "Jane", "last_name", "Smith")); - assertThat(id.intValue()).isEqualTo(2); + assertThat(id.intValue()).isEqualTo(1); assertNumRows(2); } diff --git a/spring-jdbc/src/test/java/org/springframework/jdbc/datasource/init/ScriptUtilsIntegrationTests.java b/spring-jdbc/src/test/java/org/springframework/jdbc/datasource/init/ScriptUtilsIntegrationTests.java index 8eb91b29689..f59db0e6a46 100644 --- a/spring-jdbc/src/test/java/org/springframework/jdbc/datasource/init/ScriptUtilsIntegrationTests.java +++ b/spring-jdbc/src/test/java/org/springframework/jdbc/datasource/init/ScriptUtilsIntegrationTests.java @@ -16,13 +16,24 @@ package org.springframework.jdbc.datasource.init; +import java.nio.charset.StandardCharsets; import java.sql.SQLException; +import java.util.List; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.Parameter; +import org.junit.jupiter.params.ParameterizedClass; +import org.junit.jupiter.params.provider.EnumSource; +import org.springframework.core.io.ByteArrayResource; +import org.springframework.core.io.Resource; +import org.springframework.core.io.support.EncodedResource; +import org.springframework.jdbc.core.DataClassRowMapper; import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assumptions.assumeThat; import static org.springframework.jdbc.datasource.init.ScriptUtils.executeSqlScript; /** @@ -32,16 +43,22 @@ import static org.springframework.jdbc.datasource.init.ScriptUtils.executeSqlScr * @since 4.0.3 * @see ScriptUtilsTests */ +@ParameterizedClass +@EnumSource(EmbeddedDatabaseType.class) class ScriptUtilsIntegrationTests extends AbstractDatabaseInitializationTests { + @Parameter + EmbeddedDatabaseType databaseType; + + @Override protected EmbeddedDatabaseType getEmbeddedDatabaseType() { - return EmbeddedDatabaseType.HSQL; + return this.databaseType; } @BeforeEach void setUpSchema() throws SQLException { - executeSqlScript(db.getConnection(), usersSchema()); + executeSqlScript(db.getConnection(), encodedResource(usersSchema()), false, true, "--", null, "/*", "*/"); } @Test @@ -59,4 +76,52 @@ class ScriptUtilsIntegrationTests extends AbstractDatabaseInitializationTests { assertUsersDatabaseCreated("Hoeller", "Brannen"); } + @Test + @SuppressWarnings("unchecked") + void statementWithMultipleResultSets() throws SQLException { + // Derby does not support multiple statements/ResultSets within a single Statement. + assumeThat(this.databaseType).isNotSameAs(EmbeddedDatabaseType.DERBY); + + EncodedResource resource = encodedResource(resource("users-data.sql")); + executeSqlScript(db.getConnection(), resource, false, true, "--", null, "/*", "*/"); + + assertUsersInDatabase(user("Sam", "Brannen")); + + resource = encodedResource(inlineResource(""" + SELECT last_name FROM users WHERE id = 0; + UPDATE users SET first_name = 'Jane' WHERE id = 0; + UPDATE users SET last_name = 'Smith' WHERE id = 0; + SELECT last_name FROM users WHERE id = 0; + GO + """)); + + String separator = "GO\n"; + executeSqlScript(db.getConnection(), resource, false, true, "--", separator, "/*", "*/"); + + assertUsersInDatabase(user("Jane", "Smith")); + } + + private void assertUsersInDatabase(User... expectedUsers) { + List users = jdbcTemplate.query("SELECT * FROM users WHERE id = 0", + new DataClassRowMapper<>(User.class)); + assertThat(users).containsExactly(expectedUsers); + } + + + private static EncodedResource encodedResource(Resource resource) { + return new EncodedResource(resource); + } + + private static Resource inlineResource(String sql) { + byte[] bytes = sql.getBytes(StandardCharsets.UTF_8); + return new ByteArrayResource(bytes, "inline SQL"); + } + + private static User user(String firstName, String lastName) { + return new User(0, firstName, lastName); + } + + record User(int id, String firstName, String lastName) { + } + } diff --git a/spring-jdbc/src/test/resources/org/springframework/jdbc/datasource/init/users-schema-with-custom-schema.sql b/spring-jdbc/src/test/resources/org/springframework/jdbc/datasource/init/users-schema-with-custom-schema.sql index 6da1c297820..0959c7e6955 100644 --- a/spring-jdbc/src/test/resources/org/springframework/jdbc/datasource/init/users-schema-with-custom-schema.sql +++ b/spring-jdbc/src/test/resources/org/springframework/jdbc/datasource/init/users-schema-with-custom-schema.sql @@ -5,7 +5,7 @@ SET SCHEMA my_schema; DROP TABLE users IF EXISTS; CREATE TABLE users ( - id INTEGER GENERATED BY DEFAULT AS IDENTITY, + id INTEGER GENERATED BY DEFAULT AS IDENTITY(START WITH 0) PRIMARY KEY, first_name VARCHAR(50) NOT NULL, last_name VARCHAR(50) NOT NULL ); diff --git a/spring-jdbc/src/test/resources/org/springframework/jdbc/datasource/init/users-schema.sql b/spring-jdbc/src/test/resources/org/springframework/jdbc/datasource/init/users-schema.sql index 523c4a7c2b1..d9cb2918b1c 100644 --- a/spring-jdbc/src/test/resources/org/springframework/jdbc/datasource/init/users-schema.sql +++ b/spring-jdbc/src/test/resources/org/springframework/jdbc/datasource/init/users-schema.sql @@ -1,7 +1,7 @@ DROP TABLE users IF EXISTS; CREATE TABLE users ( - id INTEGER GENERATED BY DEFAULT AS IDENTITY, + id INTEGER GENERATED BY DEFAULT AS IDENTITY(START WITH 0) PRIMARY KEY, first_name VARCHAR(50) NOT NULL, last_name VARCHAR(50) NOT NULL );