Browse Source

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
pull/35405/head
Sam Brannen 4 months ago
parent
commit
37b076be51
  1. 41
      spring-jdbc/src/main/java/org/springframework/jdbc/datasource/init/ScriptUtils.java
  2. 10
      spring-jdbc/src/test/java/org/springframework/jdbc/core/simple/JdbcClientIntegrationTests.java
  3. 2
      spring-jdbc/src/test/java/org/springframework/jdbc/core/simple/SimpleJdbcInsertIntegrationTests.java
  4. 69
      spring-jdbc/src/test/java/org/springframework/jdbc/datasource/init/ScriptUtilsIntegrationTests.java
  5. 2
      spring-jdbc/src/test/resources/org/springframework/jdbc/datasource/init/users-schema-with-custom-schema.sql
  6. 2
      spring-jdbc/src/test/resources/org/springframework/jdbc/datasource/init/users-schema.sql

41
spring-jdbc/src/main/java/org/springframework/jdbc/datasource/init/ScriptUtils.java

@ -258,18 +258,29 @@ public abstract class ScriptUtils {
for (String statement : statements) { for (String statement : statements) {
stmtNumber++; stmtNumber++;
try { try {
stmt.execute(statement); boolean hasResultSet = stmt.execute(statement);
int rowsAffected = stmt.getUpdateCount(); int updateCount = -1;
if (logger.isDebugEnabled()) { if (logger.isDebugEnabled()) {
logger.debug(rowsAffected + " returned as update count for SQL: " + statement); logSqlWarnings(stmt);
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();
}
} }
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) { catch (SQLException ex) {
boolean dropStatement = StringUtils.startsWithIgnoreCase(statement.trim(), "drop"); 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 * Read a script from the provided resource, using the supplied comment prefixes
* and statement separator, and build a {@code String} containing the lines. * and statement separator, and build a {@code String} containing the lines.

10
spring-jdbc/src/test/java/org/springframework/jdbc/core/simple/JdbcClientIntegrationTests.java

@ -74,7 +74,7 @@ class JdbcClientIntegrationTests {
@Test @Test
void updateWithGeneratedKeys() { void updateWithGeneratedKeys() {
int expectedId = 2; int expectedId = 1;
String firstName = "Jane"; String firstName = "Jane";
String lastName = "Smith"; String lastName = "Smith";
@ -92,7 +92,7 @@ class JdbcClientIntegrationTests {
@Test @Test
void updateWithGeneratedKeysAndKeyColumnNames() { void updateWithGeneratedKeysAndKeyColumnNames() {
int expectedId = 2; int expectedId = 1;
String firstName = "Jane"; String firstName = "Jane";
String lastName = "Smith"; String lastName = "Smith";
@ -110,7 +110,7 @@ class JdbcClientIntegrationTests {
@Test @Test
void updateWithGeneratedKeysUsingNamedParameters() { void updateWithGeneratedKeysUsingNamedParameters() {
int expectedId = 2; int expectedId = 1;
String firstName = "Jane"; String firstName = "Jane";
String lastName = "Smith"; String lastName = "Smith";
@ -129,7 +129,7 @@ class JdbcClientIntegrationTests {
@Test @Test
void updateWithGeneratedKeysAndKeyColumnNamesUsingNamedParameters() { void updateWithGeneratedKeysAndKeyColumnNamesUsingNamedParameters() {
int expectedId = 2; int expectedId = 1;
String firstName = "Jane"; String firstName = "Jane";
String lastName = "Smith"; String lastName = "Smith";
@ -217,7 +217,7 @@ class JdbcClientIntegrationTests {
private static void assertResults(List<User> users) { private static void assertResults(List<User> 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) {} record Name(String name) {}

2
spring-jdbc/src/test/java/org/springframework/jdbc/core/simple/SimpleJdbcInsertIntegrationTests.java

@ -323,7 +323,7 @@ class SimpleJdbcInsertIntegrationTests {
protected void insertJaneSmith(SimpleJdbcInsert insert) { protected void insertJaneSmith(SimpleJdbcInsert insert) {
Number id = insert.executeAndReturnKey(Map.of("first_name", "Jane", "last_name", "Smith")); Number id = insert.executeAndReturnKey(Map.of("first_name", "Jane", "last_name", "Smith"));
assertThat(id.intValue()).isEqualTo(2); assertThat(id.intValue()).isEqualTo(1);
assertNumRows(2); assertNumRows(2);
} }

69
spring-jdbc/src/test/java/org/springframework/jdbc/datasource/init/ScriptUtilsIntegrationTests.java

@ -16,13 +16,24 @@
package org.springframework.jdbc.datasource.init; package org.springframework.jdbc.datasource.init;
import java.nio.charset.StandardCharsets;
import java.sql.SQLException; import java.sql.SQLException;
import java.util.List;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; 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 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; 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 * @since 4.0.3
* @see ScriptUtilsTests * @see ScriptUtilsTests
*/ */
@ParameterizedClass
@EnumSource(EmbeddedDatabaseType.class)
class ScriptUtilsIntegrationTests extends AbstractDatabaseInitializationTests { class ScriptUtilsIntegrationTests extends AbstractDatabaseInitializationTests {
@Parameter
EmbeddedDatabaseType databaseType;
@Override @Override
protected EmbeddedDatabaseType getEmbeddedDatabaseType() { protected EmbeddedDatabaseType getEmbeddedDatabaseType() {
return EmbeddedDatabaseType.HSQL; return this.databaseType;
} }
@BeforeEach @BeforeEach
void setUpSchema() throws SQLException { void setUpSchema() throws SQLException {
executeSqlScript(db.getConnection(), usersSchema()); executeSqlScript(db.getConnection(), encodedResource(usersSchema()), false, true, "--", null, "/*", "*/");
} }
@Test @Test
@ -59,4 +76,52 @@ class ScriptUtilsIntegrationTests extends AbstractDatabaseInitializationTests {
assertUsersDatabaseCreated("Hoeller", "Brannen"); 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<User> 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) {
}
} }

2
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; DROP TABLE users IF EXISTS;
CREATE TABLE users ( 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, first_name VARCHAR(50) NOT NULL,
last_name VARCHAR(50) NOT NULL last_name VARCHAR(50) NOT NULL
); );

2
spring-jdbc/src/test/resources/org/springframework/jdbc/datasource/init/users-schema.sql

@ -1,7 +1,7 @@
DROP TABLE users IF EXISTS; DROP TABLE users IF EXISTS;
CREATE TABLE users ( 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, first_name VARCHAR(50) NOT NULL,
last_name VARCHAR(50) NOT NULL last_name VARCHAR(50) NOT NULL
); );

Loading…
Cancel
Save