/* * Copyright 2010-2017 Boxfuse GmbH * * 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.flywaydb.core.internal.dbsupport.sqlserver; import org.flywaydb.core.api.FlywayException; import org.flywaydb.core.api.MigrationState; import org.flywaydb.core.internal.dbsupport.FlywaySqlScriptException; import org.flywaydb.core.api.MigrationVersion; import org.flywaydb.core.internal.dbsupport.Schema; import org.flywaydb.core.internal.dbsupport.SqlScript; import org.flywaydb.core.internal.util.scanner.classpath.ClassPathResource; import org.flywaydb.core.migration.MigrationTestCase; import org.junit.Ignore; import org.junit.Test; import java.sql.CallableStatement; import java.sql.SQLException; import java.sql.Types; import java.util.HashMap; import java.util.List; import java.util.Map; import static org.junit.Assert.*; /** * Test to demonstrate the migration functionality using SQL Server. */ @SuppressWarnings({"JavaDoc"}) public abstract class SQLServerMigrationTestCase extends MigrationTestCase { @Override protected String getQuoteLocation() { return "migration/quote"; } @Test public void failedMigration() throws Exception { String tableName = "before_the_error"; flyway.setLocations("migration/failed"); Map<String, String> placeholders = new HashMap<String, String>(); placeholders.put("tableName", dbSupport.quote(tableName)); flyway.setPlaceholders(placeholders); try { flyway.migrate(); fail(); } catch (FlywaySqlScriptException e) { // root cause of exception must be defined, and it should be FlywaySqlScriptException assertNotNull(e.getCause()); assertTrue(e.getCause() instanceof SQLException); // and make sure the failed statement was properly recorded // Normal DB should fail at line 21. SqlServer fails at line 17 as statements are executed in batches. assertEquals(17, e.getLineNumber()); assertTrue(e.getStatement().contains("THIS IS NOT VALID SQL")); } } /** * Tests clean and migrate for SQL Server Stored Procedures. */ @Test public void storedProcedure() throws Exception { flyway.setLocations("migration/dbsupport/sqlserver/sql/procedure"); flyway.migrate(); assertEquals("Hello", jdbcTemplate.queryForString("SELECT value FROM test_data")); flyway.clean(); // Running migrate again on an unclean database, triggers duplicate object exceptions. flyway.migrate(); } /** * Tests clean and migrate for SQL Server Functions. */ @Test public void function() throws Exception { flyway.setLocations("migration/dbsupport/sqlserver/sql/function"); flyway.migrate(); // Test inlined function. jdbcTemplate.execute("INSERT INTO test_data (value) VALUES ('Hello')"); List<String> reverse = jdbcTemplate.queryForStringList("SELECT * from reverseInlineFunc();"); assertEquals(1, reverse.size()); assertEquals("olleH", reverse.get(0)); // Test table valued-function. final int count = 10; List<String> integers = jdbcTemplate.queryForStringList("SELECT * from dbo.positiveIntegers(?)", String.valueOf(count)); assertEquals(count, integers.size()); for (int i = 1; i <= 10; i++) { assertEquals(i, Integer.parseInt(integers.get(i - 1))); } flyway.clean(); // Running migrate again on an unclean database, triggers duplicate object exceptions. flyway.migrate(); } /** * Tests clean and migrate for SQL Server Triggers. */ @Test public void trigger() throws Exception { flyway.setLocations("migration/dbsupport/sqlserver/sql/trigger"); flyway.migrate(); assertEquals(3, jdbcTemplate.queryForInt("SELECT priority FROM customers where name='MS Internet Explorer Team'")); flyway.clean(); // Running migrate again on an unclean database, triggers duplicate object exceptions. flyway.migrate(); } /** * Tests clean and migrate for SQL Server Views. */ @Test public void view() throws Exception { flyway.setLocations("migration/dbsupport/sqlserver/sql/view"); flyway.migrate(); assertEquals(150, jdbcTemplate.queryForInt("SELECT value FROM v")); flyway.clean(); // Running migrate again on an unclean database, triggers duplicate object exceptions. flyway.migrate(); } /** * Tests clean and migrate for SQL Server Types. */ @Test public void type() throws Exception { flyway.setLocations("migration/dbsupport/sqlserver/sql/type"); flyway.migrate(); flyway.clean(); // Running migrate again on an unclean database, triggers duplicate object exceptions. flyway.migrate(); } /** * Tests clean and migrate for SQL Server assemblies. */ @Test public void assembly() throws Exception { CallableStatement stmt = jdbcTemplate.getConnection().prepareCall("EXEC sp_configure 'clr enabled', 1; RECONFIGURE;"); stmt.execute(); try { flyway.setLocations("migration/dbsupport/sqlserver/sql/assembly"); flyway.migrate(); // CLR procedure. stmt = jdbcTemplate.getConnection().prepareCall("EXEC helloFromProc ?, ?"); stmt.setString(1, "Alice"); stmt.registerOutParameter(2, Types.VARCHAR); stmt.execute(); assertEquals("Hello Alice", stmt.getString(2)); // CLR function. assertEquals("Hello Bob", jdbcTemplate.queryForString("SELECT dbo.helloFromFunc('Bob');")); List<String> greetings = jdbcTemplate.queryForStringList("SELECT * FROM dbo.helloFromTableValuedFunction(3, 'Charlie')"); assertEquals(3, greetings.size()); for (String greeting : greetings) { assertEquals("Hello Charlie", greeting); } String[] names = new String[]{"Dave", "Erin", "Faythe"}; for (String name : names) { jdbcTemplate.execute("INSERT INTO names (name) VALUES (?)", name); } // CLR trigger. greetings = jdbcTemplate.queryForStringList("SELECT * FROM triggered_greetings"); assertEquals(names.length, greetings.size()); for (String name : names) { assertTrue(greetings.remove("Hello " + name)); } // User aggregate. greetings = jdbcTemplate.queryForStringList("SELECT dbo.helloAll(name) FROM names"); assertEquals(1, greetings.size()); assertEquals("Hello Dave, Erin, Faythe", greetings.get(0)); flyway.clean(); // Running migrate again on an unclean database, triggers duplicate object exceptions. flyway.migrate(); } finally { try { jdbcTemplate.getConnection().prepareCall("EXEC sp_configure 'clr enabled', 0; RECONFIGURE;"); } catch (Exception e) { // Swallow. } } } /** * Tests clean and migrate for SQL Server unicode strings. */ @Test public void nvarchar() throws Exception { flyway.setLocations("migration/dbsupport/sqlserver/sql/nvarchar"); flyway.migrate(); flyway.clean(); } /** * Tests clean and migrate for SQL Server sequences. */ @Test public void sequence() throws Exception { flyway.setLocations("migration/dbsupport/sqlserver/sql/sequence"); flyway.migrate(); flyway.clean(); flyway.migrate(); } /** * Tests clean and migrate for default constraints with functions. */ @Test public void defaultConstraints() throws Exception { flyway.setLocations("migration/dbsupport/sqlserver/sql/default"); flyway.migrate(); flyway.clean(); } /** * Tests migrate error for pk constraints. */ @Test(expected = FlywayException.class) public void pkConstraints() throws Exception { flyway.setLocations("migration/dbsupport/sqlserver/sql/pkConstraint"); flyway.migrate(); } /** * Tests clean and migrate for synonyms. */ @Test public void synonym() throws Exception { flyway.setLocations("migration/dbsupport/sqlserver/sql/synonym"); flyway.migrate(); flyway.clean(); flyway.migrate(); } @Test public void itShouldCleanCheckConstraint() throws Exception { // given flyway.setLocations("migration/dbsupport/sqlserver/sql/checkConstraint"); flyway.migrate(); // when flyway.clean(); // then int pendingMigrations = flyway.info().pending().length; assertEquals(3, pendingMigrations); } /** * Tests a large migration that has been reported to hang on SqlServer 2005. */ @Ignore("Axel: Fails due to nested transaction being opened in script, causing outer transaction not to receive COMMIT statement") @Test public void large() throws Exception { flyway.setLocations("migration/dbsupport/sqlserver/sql/large", "org.flywaydb.core.internal.dbsupport.sqlserver.large"); flyway.setTarget(MigrationVersion.fromVersion("3.1.0")); flyway.migrate(); assertEquals("3.1.0", flyway.info().current().getVersion().toString()); assertEquals(MigrationState.SUCCESS, flyway.info().current().getState()); assertTrue(jdbcTemplate.queryForInt("SELECT COUNT(*) FROM dbo.CHANGELOG") > 0); } /** * Tests that dml errors that occur in the middle of a batch are correctly detected * see issue 718 */ @Test public void dmlErrorsCorrectlyDetected() throws Exception { String tableName = "sample_table"; flyway.setLocations("migration/dbsupport/sqlserver/sql/dmlErrorDetection"); Map<String, String> placeholders = new HashMap<String, String>(); placeholders.put("tableName", dbSupport.quote(tableName)); flyway.setPlaceholders(placeholders); try { flyway.migrate(); fail("This migration should have failed and this point shouldn't have been reached"); } catch (FlywaySqlScriptException e) { // root cause of exception must be defined, and it should be FlywaySqlScriptException assertNotNull(e.getCause()); assertTrue(e.getCause() instanceof SQLException); // and make sure the failed statement was properly recorded assertEquals(23, e.getLineNumber()); assertTrue(e.getStatement().contains("INSERT INTO")); assertTrue(e.getStatement().contains("VALUES(1)")); } } @Test public void msDBToolsIgnoredForEmpty() throws Exception { Schema schema = dbSupport.getOriginalSchema(); new SqlScript(new ClassPathResource("migration/dbsupport/sqlserver/createMSDBTools.sql", Thread.currentThread().getContextClassLoader()).loadAsString("UTF-8"), dbSupport). execute(jdbcTemplate); try { assertTrue("MS DB tools must be ignored in empty check.", schema.empty()); } finally { try { new SqlScript(new ClassPathResource("migration/dbsupport/sqlserver/dropMSDBTools.sql", Thread.currentThread().getContextClassLoader()).loadAsString("UTF-8"), dbSupport). execute(jdbcTemplate); } catch (Exception e) { // Swallow to prevent override of test raised exception. } } } @Test public void msDBToolsNotCleared() throws Exception { Schema schema = dbSupport.getOriginalSchema(); new SqlScript(new ClassPathResource("migration/dbsupport/sqlserver/createMSDBTools.sql", Thread.currentThread().getContextClassLoader()).loadAsString("UTF-8"), dbSupport). execute(jdbcTemplate); try { final String queryObjectCount = "SELECT COUNT(*) from sys.all_objects"; int initialObjectsCount = jdbcTemplate.queryForInt(queryObjectCount); schema.clean(); int finalObjectCount = jdbcTemplate.queryForInt(queryObjectCount); assertEquals("Cleaning the schema must not delete MS DB Tools objects.", initialObjectsCount, finalObjectCount); } finally { try { new SqlScript(new ClassPathResource("migration/dbsupport/sqlserver/dropMSDBTools.sql", Thread.currentThread().getContextClassLoader()).loadAsString("UTF-8"), dbSupport). execute(jdbcTemplate); } catch (Exception e) { // Swallow to prevent override of test raised exception. } } } @Override @Ignore("Not supported on SQL Server") public void setCurrentSchema() throws Exception { //Skip } @Override protected void createFlyway3MetadataTable() throws Exception { jdbcTemplate.execute("CREATE TABLE [schema_version] (\n" + " [version_rank] INT NOT NULL,\n" + " [installed_rank] INT NOT NULL,\n" + " [version] NVARCHAR(50) NOT NULL,\n" + " [description] NVARCHAR(200),\n" + " [type] NVARCHAR(20) NOT NULL,\n" + " [script] NVARCHAR(1000) NOT NULL,\n" + " [checksum] INT,\n" + " [installed_by] NVARCHAR(100) NOT NULL,\n" + " [installed_on] DATETIME NOT NULL DEFAULT GETDATE(),\n" + " [execution_time] INT NOT NULL,\n" + " [success] BIT NOT NULL\n" + ")"); jdbcTemplate.execute("ALTER TABLE [schema_version] ADD CONSTRAINT [schema_version_pk] PRIMARY KEY ([version])"); jdbcTemplate.execute("CREATE INDEX [schema_version_vr_idx] ON [schema_version] ([version_rank])"); jdbcTemplate.execute("CREATE INDEX [schema_version_ir_idx] ON [schema_version] ([installed_rank])"); jdbcTemplate.execute("CREATE INDEX [schema_version_s_idx] ON [schema_version] ([success])"); } }