Support multi-line comments in SQL scripts

Prior to this commit neither ResourceDatabasePopulator nor
JdbcTestUtils properly supported multi-line comments (e.g., /* ... */).
Secondarily there has developed a significant amount of code
duplication in these two classes that has led to maintenance issues
over the years.

This commit addresses these issues as follows:

 - Common code has been extracted from ResourceDatabasePopulator and
   JdbcTestUtils and moved to a new ScriptUtils class in the
   spring-jdbc module.

 - Relevant test cases have been migrated from JdbcTestUtilsTests to
   ScriptUtilsTests.

 - ScriptUtils.splitSqlScript() has been modified to ignore multi-line
   comments in scripts during processing.

 - ResourceDatabasePopulator supports configuration of the start and end
   delimiters for multi-line (block) comments.

 - A new test case was added to ScriptUtilsTests for the new multi-line
   comment support.

Issue: SPR-9531
This commit is contained in:
cbaldwin74
2014-03-06 01:38:38 -08:00
committed by Sam Brannen
parent 6b31074e4a
commit e5c17560db
10 changed files with 744 additions and 500 deletions

View File

@@ -0,0 +1,238 @@
/*
* Copyright 2002-2014 the original author or authors.
*
* 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 org.springframework.jdbc.datasource.init;
import java.io.LineNumberReader;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.List;
import org.junit.After;
import org.junit.Test;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.support.EncodedResource;
import org.springframework.dao.DataAccessException;
import org.springframework.jdbc.UncategorizedSQLException;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabase;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
import org.springframework.transaction.support.TransactionSynchronizationManager;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
/**
* JUnit test cases for ScriptUtils.
*
* @author Chris Baldwin
*/
public class ScriptUtilsTests {
private final EmbeddedDatabaseBuilder builder = new EmbeddedDatabaseBuilder();
private final EmbeddedDatabase db = builder.build();
private final JdbcTemplate jdbcTemplate = new JdbcTemplate(db);
@After
public void shutDown() {
if (TransactionSynchronizationManager.isSynchronizationActive()) {
TransactionSynchronizationManager.clear();
TransactionSynchronizationManager.unbindResource(db);
}
db.shutdown();
}
@Test
public void splitSqlScriptDelimitedWithSemicolon() {
String rawStatement1 = "insert into customer (id, name)\nvalues (1, 'Rod ; Johnson'), (2, 'Adrian \n Collier')";
String cleanedStatement1 = "insert into customer (id, name) values (1, 'Rod ; Johnson'), (2, 'Adrian \n Collier')";
String rawStatement2 = "insert into orders(id, order_date, customer_id)\nvalues (1, '2008-01-02', 2)";
String cleanedStatement2 = "insert into orders(id, order_date, customer_id) values (1, '2008-01-02', 2)";
String rawStatement3 = "insert into orders(id, order_date, customer_id) values (1, '2008-01-02', 2)";
String cleanedStatement3 = "insert into orders(id, order_date, customer_id) values (1, '2008-01-02', 2)";
char delim = ';';
String script = rawStatement1 + delim + rawStatement2 + delim + rawStatement3 + delim;
List<String> statements = new ArrayList<String>();
ScriptUtils.splitSqlScript(script, delim, statements);
assertEquals("wrong number of statements", 3, statements.size());
assertEquals("statement 1 not split correctly", cleanedStatement1, statements.get(0));
assertEquals("statement 2 not split correctly", cleanedStatement2, statements.get(1));
assertEquals("statement 3 not split correctly", cleanedStatement3, statements.get(2));
}
@Test
public void splitSqlScriptDelimitedWithNewLine() {
String statement1 = "insert into customer (id, name) values (1, 'Rod ; Johnson'), (2, 'Adrian \n Collier')";
String statement2 = "insert into orders(id, order_date, customer_id) values (1, '2008-01-02', 2)";
String statement3 = "insert into orders(id, order_date, customer_id) values (1, '2008-01-02', 2)";
char delim = '\n';
String script = statement1 + delim + statement2 + delim + statement3 + delim;
List<String> statements = new ArrayList<String>();
ScriptUtils.splitSqlScript(script, delim, statements);
assertEquals("wrong number of statements", 3, statements.size());
assertEquals("statement 1 not split correctly", statement1, statements.get(0));
assertEquals("statement 2 not split correctly", statement2, statements.get(1));
assertEquals("statement 3 not split correctly", statement3, statements.get(2));
}
@Test
public void readAndSplitScriptContainingComments() throws Exception {
EncodedResource resource = new EncodedResource(new ClassPathResource("test-data-with-comments.sql", getClass()));
LineNumberReader lineNumberReader = new LineNumberReader(resource.getReader());
String script = ScriptUtils.readScript(lineNumberReader);
char delim = ';';
List<String> statements = new ArrayList<String>();
ScriptUtils.splitSqlScript(script, delim, statements);
String statement1 = "insert into customer (id, name) values (1, 'Rod; Johnson'), (2, 'Adrian Collier')";
String statement2 = "insert into orders(id, order_date, customer_id) values (1, '2008-01-02', 2)";
String statement3 = "insert into orders(id, order_date, customer_id) values (1, '2008-01-02', 2)";
// Statement 4 addresses the error described in SPR-9982.
String statement4 = "INSERT INTO persons( person_id , name) VALUES( 1 , 'Name' )";
assertEquals("wrong number of statements", 4, statements.size());
assertEquals("statement 1 not split correctly", statement1, statements.get(0));
assertEquals("statement 2 not split correctly", statement2, statements.get(1));
assertEquals("statement 3 not split correctly", statement3, statements.get(2));
assertEquals("statement 4 not split correctly", statement4, statements.get(3));
}
/**
* See <a href="https://jira.springsource.org/browse/SPR-10330">SPR-10330</a>
*/
@Test
public void readAndSplitScriptContainingCommentsWithLeadingTabs() throws Exception {
EncodedResource resource = new EncodedResource(new ClassPathResource(
"test-data-with-comments-and-leading-tabs.sql", getClass()));
LineNumberReader lineNumberReader = new LineNumberReader(resource.getReader());
String script = ScriptUtils.readScript(lineNumberReader);
char delim = ';';
List<String> statements = new ArrayList<String>();
ScriptUtils.splitSqlScript(script, delim, statements);
String statement1 = "insert into customer (id, name) values (1, 'Sam Brannen')";
String statement2 = "insert into orders(id, order_date, customer_id) values (1, '2013-06-08', 1)";
String statement3 = "insert into orders(id, order_date, customer_id) values (2, '2013-06-08', 1)";
assertEquals("wrong number of statements", 3, statements.size());
assertEquals("statement 1 not split correctly", statement1, statements.get(0));
assertEquals("statement 2 not split correctly", statement2, statements.get(1));
assertEquals("statement 3 not split correctly", statement3, statements.get(2));
}
/**
* See <a href="https://jira.springsource.org/browse/SPR-9531">SPR-9531</a>
*/
@Test
public void readAndSplitScriptContainingMuliLineComments() throws Exception {
EncodedResource resource = new EncodedResource(new ClassPathResource(
"test-data-with-multi-line-comments.sql", getClass()));
String script = ScriptUtils.readScript(resource);
char delim = ';';
List<String> statements = new ArrayList<String>();
ScriptUtils.splitSqlScript(script, delim, statements);
String statement1 = "INSERT INTO users(first_name, last_name) VALUES('Juergen', 'Hoeller')";
String statement2 = "INSERT INTO users(first_name, last_name) VALUES( 'Sam' , 'Brannen' )";
assertEquals("wrong number of statements", 2, statements.size());
assertEquals("statement 1 not split correctly", statement1, statements.get(0));
assertEquals("statement 2 not split correctly", statement2, statements.get(1));
}
@Test
public void containsDelimiters() {
assertTrue("test with ';' is wrong", !ScriptUtils.containsSqlScriptDelimiters("select 1\n select ';'", ';'));
assertTrue("test with delimiter ; is wrong",
ScriptUtils.containsSqlScriptDelimiters("select 1; select 2", ';'));
assertTrue("test with '\\n' is wrong",
!ScriptUtils.containsSqlScriptDelimiters("select 1; select '\\n\n';", '\n'));
assertTrue("test with delimiter \\n is wrong",
ScriptUtils.containsSqlScriptDelimiters("select 1\n select 2", '\n'));
}
@Test
public void executeSqlScript() throws SQLException {
EncodedResource schemaResource = new EncodedResource(new ClassPathResource(
"users-schema.sql", getClass()));
EncodedResource commentResource = new EncodedResource(new ClassPathResource(
"test-data-with-multi-line-comments.sql", getClass()));
Connection connection = db.getConnection();
Statement stmt = connection.createStatement();
TestScriptStatementExecutor executor = new TestScriptStatementExecutor(stmt);
try {
ScriptUtils.executeSqlScript(executor, schemaResource, false, false, ScriptUtils.DEFAULT_COMMENT_PREFIX,
ScriptUtils.DEFAULT_STATEMENT_SEPARATOR, ScriptUtils.DEFAULT_BLOCK_COMMENT_START_DELIMITER,
ScriptUtils.DEFAULT_BLOCK_COMMENT_END_DELIMITER);
ScriptUtils.executeSqlScript(executor, commentResource, false, false, ScriptUtils.DEFAULT_COMMENT_PREFIX,
ScriptUtils.DEFAULT_STATEMENT_SEPARATOR, ScriptUtils.DEFAULT_BLOCK_COMMENT_START_DELIMITER,
ScriptUtils.DEFAULT_BLOCK_COMMENT_END_DELIMITER);
}
finally {
stmt.close();
connection.close();
}
assertUsersDatabaseCreated("Hoeller", "Brannen");
}
private void assertUsersDatabaseCreated(String... lastNames) {
for (String lastName : lastNames) {
assertThat("Did not find user with last name [" + lastName + "].",
jdbcTemplate.queryForObject("select count(0) from users where last_name = ?", Integer.class, lastName),
equalTo(1));
}
}
private class TestScriptStatementExecutor implements ScriptUtils.ScriptStatementExecutor
{
Statement stmt;
public TestScriptStatementExecutor(Statement stmt) {
super();
this.stmt = stmt;
}
/* (non-Javadoc)
* @see org.springframework.jdbc.datasource.init.ScriptUtils.ScriptStatementExecutor#executeScriptStatement(java.lang.String)
*/
@Override
public int executeScriptStatement(String statement) throws DataAccessException {
try {
stmt.execute(statement);
return stmt.getUpdateCount();
}
catch (SQLException e) {
throw new UncategorizedSQLException(getClass().getName(), statement, e);
}
}
}
}

View File

@@ -0,0 +1,9 @@
-- The next comment line starts with a tab.
-- x, y, z...
insert into customer (id, name)
values (1, 'Sam Brannen');
-- This is also a comment with a leading tab.
insert into orders(id, order_date, customer_id) values (1, '2013-06-08', 1);
-- This is also a comment with a leading tab, a space, and a tab.
insert into orders(id, order_date, customer_id) values (2, '2013-06-08', 1);

View File

@@ -0,0 +1,16 @@
-- The next comment line has no text after the '--' prefix.
--
-- The next comment line starts with a space.
-- x, y, z...
insert into customer (id, name)
values (1, 'Rod; Johnson'), (2, 'Adrian Collier');
-- This is also a comment.
insert into orders(id, order_date, customer_id)
values (1, '2008-01-02', 2);
insert into orders(id, order_date, customer_id) values (1, '2008-01-02', 2);
INSERT INTO persons( person_id--
, name)
VALUES( 1 -- person_id
, 'Name' --name
);--

View File

@@ -0,0 +1,17 @@
/* This is a multi line comment
* The next comment line has no text
* The next comment line starts with a space.
* x, y, z...
*/
INSERT INTO users(first_name, last_name) VALUES('Juergen', 'Hoeller');
-- This is also a comment.
/*
* Let's add another comment
* that covers multiple lines
*/INSERT INTO
users(first_name, last_name)
VALUES( 'Sam' -- first_name
, 'Brannen' -- last_name
);--