Skip to content

Commit 37b076b

Browse files
committed
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
1 parent a9453a5 commit 37b076b

File tree

6 files changed

+106
-20
lines changed

6 files changed

+106
-20
lines changed

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

Lines changed: 31 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -258,18 +258,29 @@ public static void executeSqlScript(Connection connection, EncodedResource resou
258258
for (String statement : statements) {
259259
stmtNumber++;
260260
try {
261-
stmt.execute(statement);
262-
int rowsAffected = stmt.getUpdateCount();
261+
boolean hasResultSet = stmt.execute(statement);
262+
int updateCount = -1;
263263
if (logger.isDebugEnabled()) {
264-
logger.debug(rowsAffected + " returned as update count for SQL: " + statement);
265-
SQLWarning warningToLog = stmt.getWarnings();
266-
while (warningToLog != null) {
267-
logger.debug("SQLWarning ignored: SQL state '" + warningToLog.getSQLState() +
268-
"', error code '" + warningToLog.getErrorCode() +
269-
"', message [" + warningToLog.getMessage() + "]");
270-
warningToLog = warningToLog.getNextWarning();
271-
}
264+
logSqlWarnings(stmt);
272265
}
266+
do {
267+
if (hasResultSet) {
268+
// We invoke getResultSet() to ensure the JDBC driver processes
269+
// it, but we intentionally ignore the returned ResultSet since
270+
// we cannot do anything meaningful with it here.
271+
stmt.getResultSet();
272+
if (logger.isDebugEnabled()) {
273+
logger.debug("ResultSet returned for SQL: " + statement);
274+
}
275+
}
276+
else {
277+
updateCount = stmt.getUpdateCount();
278+
if (updateCount >= 0 && logger.isDebugEnabled()) {
279+
logger.debug(updateCount + " returned as update count for SQL: " + statement);
280+
}
281+
}
282+
hasResultSet = stmt.getMoreResults();
283+
} while (hasResultSet || updateCount != -1);
273284
}
274285
catch (SQLException ex) {
275286
boolean dropStatement = StringUtils.startsWithIgnoreCase(statement.trim(), "drop");
@@ -307,6 +318,16 @@ public static void executeSqlScript(Connection connection, EncodedResource resou
307318
}
308319
}
309320

321+
private static void logSqlWarnings(Statement stmt) throws SQLException {
322+
SQLWarning warningToLog = stmt.getWarnings();
323+
while (warningToLog != null) {
324+
logger.debug("SQLWarning ignored: SQL state '" + warningToLog.getSQLState() +
325+
"', error code '" + warningToLog.getErrorCode() +
326+
"', message [" + warningToLog.getMessage() + "]");
327+
warningToLog = warningToLog.getNextWarning();
328+
}
329+
}
330+
310331
/**
311332
* Read a script from the provided resource, using the supplied comment prefixes
312333
* and statement separator, and build a {@code String} containing the lines.

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

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ void shutdownDatabase() {
7474

7575
@Test
7676
void updateWithGeneratedKeys() {
77-
int expectedId = 2;
77+
int expectedId = 1;
7878
String firstName = "Jane";
7979
String lastName = "Smith";
8080

@@ -92,7 +92,7 @@ void updateWithGeneratedKeys() {
9292

9393
@Test
9494
void updateWithGeneratedKeysAndKeyColumnNames() {
95-
int expectedId = 2;
95+
int expectedId = 1;
9696
String firstName = "Jane";
9797
String lastName = "Smith";
9898

@@ -110,7 +110,7 @@ void updateWithGeneratedKeysAndKeyColumnNames() {
110110

111111
@Test
112112
void updateWithGeneratedKeysUsingNamedParameters() {
113-
int expectedId = 2;
113+
int expectedId = 1;
114114
String firstName = "Jane";
115115
String lastName = "Smith";
116116

@@ -129,7 +129,7 @@ void updateWithGeneratedKeysUsingNamedParameters() {
129129

130130
@Test
131131
void updateWithGeneratedKeysAndKeyColumnNamesUsingNamedParameters() {
132-
int expectedId = 2;
132+
int expectedId = 1;
133133
String firstName = "Jane";
134134
String lastName = "Smith";
135135

@@ -217,7 +217,7 @@ void selectWithReusedNamedParameterListFromBeanProperties() {
217217

218218

219219
private static void assertResults(List<User> users) {
220-
assertThat(users).containsExactly(new User(2, "John", "John"), new User(3, "John", "Smith"));
220+
assertThat(users).containsExactly(new User(1, "John", "John"), new User(2, "John", "Smith"));
221221
}
222222

223223
record Name(String name) {}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -323,7 +323,7 @@ protected void assertNumRows(long count) {
323323

324324
protected void insertJaneSmith(SimpleJdbcInsert insert) {
325325
Number id = insert.executeAndReturnKey(Map.of("first_name", "Jane", "last_name", "Smith"));
326-
assertThat(id.intValue()).isEqualTo(2);
326+
assertThat(id.intValue()).isEqualTo(1);
327327
assertNumRows(2);
328328
}
329329

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

Lines changed: 67 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,24 @@
1616

1717
package org.springframework.jdbc.datasource.init;
1818

19+
import java.nio.charset.StandardCharsets;
1920
import java.sql.SQLException;
21+
import java.util.List;
2022

2123
import org.junit.jupiter.api.BeforeEach;
2224
import org.junit.jupiter.api.Test;
25+
import org.junit.jupiter.params.Parameter;
26+
import org.junit.jupiter.params.ParameterizedClass;
27+
import org.junit.jupiter.params.provider.EnumSource;
2328

29+
import org.springframework.core.io.ByteArrayResource;
30+
import org.springframework.core.io.Resource;
31+
import org.springframework.core.io.support.EncodedResource;
32+
import org.springframework.jdbc.core.DataClassRowMapper;
2433
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;
2534

35+
import static org.assertj.core.api.Assertions.assertThat;
36+
import static org.assertj.core.api.Assumptions.assumeThat;
2637
import static org.springframework.jdbc.datasource.init.ScriptUtils.executeSqlScript;
2738

2839
/**
@@ -32,16 +43,22 @@
3243
* @since 4.0.3
3344
* @see ScriptUtilsTests
3445
*/
46+
@ParameterizedClass
47+
@EnumSource(EmbeddedDatabaseType.class)
3548
class ScriptUtilsIntegrationTests extends AbstractDatabaseInitializationTests {
3649

50+
@Parameter
51+
EmbeddedDatabaseType databaseType;
52+
53+
3754
@Override
3855
protected EmbeddedDatabaseType getEmbeddedDatabaseType() {
39-
return EmbeddedDatabaseType.HSQL;
56+
return this.databaseType;
4057
}
4158

4259
@BeforeEach
4360
void setUpSchema() throws SQLException {
44-
executeSqlScript(db.getConnection(), usersSchema());
61+
executeSqlScript(db.getConnection(), encodedResource(usersSchema()), false, true, "--", null, "/*", "*/");
4562
}
4663

4764
@Test
@@ -59,4 +76,52 @@ void executeSqlScriptContainingSingleQuotesNestedInsideDoubleQuotes() throws SQL
5976
assertUsersDatabaseCreated("Hoeller", "Brannen");
6077
}
6178

79+
@Test
80+
@SuppressWarnings("unchecked")
81+
void statementWithMultipleResultSets() throws SQLException {
82+
// Derby does not support multiple statements/ResultSets within a single Statement.
83+
assumeThat(this.databaseType).isNotSameAs(EmbeddedDatabaseType.DERBY);
84+
85+
EncodedResource resource = encodedResource(resource("users-data.sql"));
86+
executeSqlScript(db.getConnection(), resource, false, true, "--", null, "/*", "*/");
87+
88+
assertUsersInDatabase(user("Sam", "Brannen"));
89+
90+
resource = encodedResource(inlineResource("""
91+
SELECT last_name FROM users WHERE id = 0;
92+
UPDATE users SET first_name = 'Jane' WHERE id = 0;
93+
UPDATE users SET last_name = 'Smith' WHERE id = 0;
94+
SELECT last_name FROM users WHERE id = 0;
95+
GO
96+
"""));
97+
98+
String separator = "GO\n";
99+
executeSqlScript(db.getConnection(), resource, false, true, "--", separator, "/*", "*/");
100+
101+
assertUsersInDatabase(user("Jane", "Smith"));
102+
}
103+
104+
private void assertUsersInDatabase(User... expectedUsers) {
105+
List<User> users = jdbcTemplate.query("SELECT * FROM users WHERE id = 0",
106+
new DataClassRowMapper<>(User.class));
107+
assertThat(users).containsExactly(expectedUsers);
108+
}
109+
110+
111+
private static EncodedResource encodedResource(Resource resource) {
112+
return new EncodedResource(resource);
113+
}
114+
115+
private static Resource inlineResource(String sql) {
116+
byte[] bytes = sql.getBytes(StandardCharsets.UTF_8);
117+
return new ByteArrayResource(bytes, "inline SQL");
118+
}
119+
120+
private static User user(String firstName, String lastName) {
121+
return new User(0, firstName, lastName);
122+
}
123+
124+
record User(int id, String firstName, String lastName) {
125+
}
126+
62127
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ SET SCHEMA my_schema;
55
DROP TABLE users IF EXISTS;
66

77
CREATE TABLE users (
8-
id INTEGER GENERATED BY DEFAULT AS IDENTITY,
8+
id INTEGER GENERATED BY DEFAULT AS IDENTITY(START WITH 0) PRIMARY KEY,
99
first_name VARCHAR(50) NOT NULL,
1010
last_name VARCHAR(50) NOT NULL
1111
);
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
DROP TABLE users IF EXISTS;
22

33
CREATE TABLE users (
4-
id INTEGER GENERATED BY DEFAULT AS IDENTITY,
4+
id INTEGER GENERATED BY DEFAULT AS IDENTITY(START WITH 0) PRIMARY KEY,
55
first_name VARCHAR(50) NOT NULL,
66
last_name VARCHAR(50) NOT NULL
77
);

0 commit comments

Comments
 (0)