Add cleaners for clearing a schema the first time before running a test

This commit is contained in:
Christian Beikov 2021-02-03 11:02:38 +01:00
parent d767d46d05
commit 08d9fe1a3b
14 changed files with 1766 additions and 0 deletions

View File

@ -9,6 +9,8 @@ package org.hibernate.envers.test;
import java.util.Arrays;
import java.util.List;
import org.hibernate.testing.cleaner.DatabaseCleaner;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@ -22,6 +24,10 @@ import org.jboss.logging.Logger;
@RunWith(EnversRunner.class)
public abstract class AbstractEnversTest {
static {
DatabaseCleaner.clearSchemas();
}
protected final Logger log = Logger.getLogger( getClass() );
private String auditStrategy;

View File

@ -0,0 +1,194 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.testing.cleaner;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* @author Christian Beikov
*/
public abstract class AbstractMySQLDatabaseCleaner implements DatabaseCleaner {
private static final Logger LOG = Logger.getLogger( AbstractMySQLDatabaseCleaner.class.getName() );
private static final String SYSTEM_SCHEMAS = "'information_schema'," +
"'mysql'," +
"'sys'," +
"'performance_schema'";
private final List<String> ignoredTables = new ArrayList<>();
private final Map<String, List<String>> clearingSqlsPerSchema = new HashMap<>();
@Override
public void addIgnoredTable(String tableName) {
ignoredTables.add( tableName );
}
@Override
public void clearAllSchemas(Connection connection) {
try {
clearSchema( connection, connection.getSchema() );
}
catch (SQLException e) {
throw new RuntimeException( e );
}
}
@Override
public void clearSchema(Connection connection, String schemaName) {
clearSchema0( connection, schemaName );
}
private void clearSchema0(Connection c, String schemaName) {
clearingSqlsPerSchema.remove( schemaName );
try (Statement s = c.createStatement()) {
// Collect schema names
LOG.log( Level.FINEST, "Collect table names: START" );
ResultSet rs = s.executeQuery(
"SELECT TABLE_SCHEMA, TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = '" + schemaName + "'" );
StringBuilder sb = new StringBuilder( "DROP TABLE " );
if ( rs.next() ) {
do {
String tableSchema = rs.getString( 1 );
String tableName = rs.getString( 2 );
sb.append( tableSchema );
sb.append( '.' );
sb.append( tableName );
sb.append( ',' );
} while ( rs.next() );
}
else {
// Nothing to clear since there are no tables
return;
}
sb.setCharAt( sb.length() - 1, ' ' );
LOG.log( Level.FINEST, "Collect table names: END" );
// Disable foreign keys
LOG.log( Level.FINEST, "Disable foreign keys: START" );
s.execute( "SET FOREIGN_KEY_CHECKS = 0" );
LOG.log( Level.FINEST, "Disable foreign keys: END" );
LOG.log( Level.FINEST, "Dropping tables: START" );
String sql = sb.toString();
s.execute( sql );
LOG.log( Level.FINEST, "Dropping tables: END" );
// Enable foreign keys
LOG.log( Level.FINEST, "Enabling foreign keys: START" );
s.execute( "SET FOREIGN_KEY_CHECKS = 1" );
LOG.log( Level.FINEST, "Enabling foreign keys: END" );
LOG.log( Level.FINEST, "Committing: START" );
c.commit();
LOG.log( Level.FINEST, "Committing: END" );
}
catch (SQLException e) {
try {
c.rollback();
}
catch (SQLException e1) {
e.addSuppressed( e1 );
}
throw new RuntimeException( e );
}
}
@Override
public void clearAllData(Connection connection) {
clearData0(
connection,
null,
statement -> {
try {
return statement.executeQuery(
"SELECT TABLE_SCHEMA, TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA NOT IN (" + SYSTEM_SCHEMAS + ")" );
}
catch (SQLException e) {
throw new RuntimeException( e );
}
}
);
}
@Override
public void clearData(Connection connection, String schemaName) {
clearData0(
connection,
schemaName,
statement -> {
try {
return statement.executeQuery(
"SELECT TABLE_SCHEMA, TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = '" + schemaName + "'" );
}
catch (SQLException e) {
throw new RuntimeException( e );
}
}
);
}
private void clearData0(Connection connection, String schemaName, Function<Statement, ResultSet> tablesProvider) {
try (Statement s = connection.createStatement()) {
// Disable foreign keys
LOG.log( Level.FINEST, "Disable foreign keys: START" );
s.execute( "SET FOREIGN_KEY_CHECKS = 0" );
LOG.log( Level.FINEST, "Disable foreign keys: END" );
// Delete data
LOG.log( Level.FINEST, "Deleting data: START" );
List<String> clearingSqls = clearingSqlsPerSchema.get( schemaName );
if ( clearingSqls == null ) {
clearingSqls = new ArrayList<>();
ResultSet rs = tablesProvider.apply( s );
while ( rs.next() ) {
String tableSchema = rs.getString( 1 );
String tableName = rs.getString( 2 );
if ( !ignoredTables.contains( tableName ) ) {
clearingSqls.add( createClearingStatementForTable( tableSchema, tableName ) );
}
}
clearingSqlsPerSchema.put( schemaName, clearingSqls );
}
for ( String clearingSql : clearingSqls ) {
s.execute( clearingSql );
}
LOG.log( Level.FINEST, "Deleting data: END" );
// Enable foreign keys
LOG.log( Level.FINEST, "Enabling foreign keys: START" );
s.execute( "SET FOREIGN_KEY_CHECKS = 1" );
LOG.log( Level.FINEST, "Enabling foreign keys: END" );
LOG.log( Level.FINEST, "Committing: START" );
connection.commit();
LOG.log( Level.FINEST, "Committing: END" );
}
catch (SQLException e) {
try {
connection.rollback();
}
catch (SQLException e1) {
e.addSuppressed( e1 );
}
throw new RuntimeException( e );
}
}
protected abstract String createClearingStatementForTable(String tableSchema, String tableName);
}

View File

@ -0,0 +1,416 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.testing.cleaner;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* @author Christian Beikov
*/
public class DB2DatabaseCleaner implements DatabaseCleaner {
private static final Logger LOG = Logger.getLogger( DB2DatabaseCleaner.class.getName() );
private static final String SYSTEM_SCHEMAS = "'SYSCAT',"
+ "'SYSIBM',"
+ "'SYSIBMADM',"
+ "'SYSPUBLIC',"
+ "'SYSSTAT',"
+ "'SYSTOOLS'";
private final List<String> ignoredTables = new ArrayList<>();
private final Map<String, Map<String, List<String>>> cachedForeignKeysPerSchema = new HashMap<>();
@Override
public boolean isApplicable(Connection connection) {
try {
return connection.getMetaData().getDatabaseProductName().startsWith( "DB2" );
}
catch (SQLException e) {
throw new RuntimeException( "Could not resolve the database metadata!", e );
}
}
@Override
public void addIgnoredTable(String tableName) {
ignoredTables.add( tableName );
}
@Override
public void clearAllSchemas(Connection c) {
cachedForeignKeysPerSchema.clear();
try (Statement s = c.createStatement()) {
ResultSet rs;
List<String> sqls = new ArrayList<>();
// Collect schema objects
LOG.log( Level.FINEST, "Collect schema objects: START" );
rs = s.executeQuery(
"SELECT 'DROP INDEX \"' || TRIM(INDSCHEMA) || '\".\"' || TRIM(INDNAME) || '\"' " +
"FROM SYSCAT.INDEXES " +
"WHERE UNIQUERULE = 'D' " +
"AND INDSCHEMA NOT IN (" + SYSTEM_SCHEMAS + ")"
);
while ( rs.next() ) {
sqls.add( rs.getString( 1 ) );
}
rs = s.executeQuery(
"SELECT 'ALTER TABLE \"' || TRIM(TABSCHEMA) || '\".\"' || TRIM(TABNAME) || '\" DROP FOREIGN KEY \"' || TRIM(CONSTNAME) || '\"' " +
"FROM SYSCAT.TABCONST " +
"WHERE TYPE = 'F' " +
"AND TABSCHEMA NOT IN (" + SYSTEM_SCHEMAS + ")"
);
while ( rs.next() ) {
sqls.add( rs.getString( 1 ) );
}
rs = s.executeQuery(
"SELECT 'ALTER TABLE \"' || TRIM(TABSCHEMA) || '\".\"' || TRIM(TABNAME) || '\" DROP UNIQUE \"' || TRIM(INDNAME) || '\"' " +
"FROM SYSCAT.INDEXES " +
"WHERE UNIQUERULE = 'U' " +
"AND INDSCHEMA NOT IN (" + SYSTEM_SCHEMAS + ")"
);
while ( rs.next() ) {
sqls.add( rs.getString( 1 ) );
}
rs = s.executeQuery(
"SELECT 'ALTER TABLE \"' || TRIM(TABSCHEMA) || '\".\"' || TRIM(TABNAME) || '\" DROP PRIMARY KEY' " +
"FROM SYSCAT.INDEXES " +
"WHERE UNIQUERULE = 'P' " +
"AND INDSCHEMA NOT IN (" + SYSTEM_SCHEMAS + ")"
);
while ( rs.next() ) {
sqls.add( rs.getString( 1 ) );
}
rs = s.executeQuery(
"SELECT 'DROP VIEW \"' || TRIM(TABSCHEMA) || '\".\"' || TRIM(TABNAME) || '\"' " +
"FROM SYSCAT.TABLES " +
"WHERE TYPE = 'V' " +
"AND TABSCHEMA NOT IN (" + SYSTEM_SCHEMAS + ")"
);
while ( rs.next() ) {
sqls.add( rs.getString( 1 ) );
}
rs = s.executeQuery(
"SELECT 'DROP TABLE \"' || TRIM(TABSCHEMA) || '\".\"' || TRIM(TABNAME) || '\"' " +
"FROM SYSCAT.TABLES " +
"WHERE TYPE = 'T' " +
"AND TABSCHEMA NOT IN (" + SYSTEM_SCHEMAS + ")"
);
while ( rs.next() ) {
sqls.add( rs.getString( 1 ) );
}
rs = s.executeQuery(
"SELECT 'DROP SEQUENCE \"' || TRIM(SEQSCHEMA) || '\".\"' || TRIM(SEQNAME) || '\"' " +
"FROM SYSCAT.SEQUENCES " +
"WHERE SEQSCHEMA NOT IN (" + SYSTEM_SCHEMAS + ")"
);
while ( rs.next() ) {
sqls.add( rs.getString( 1 ) );
}
LOG.log( Level.FINEST, "Collect schema objects: END" );
LOG.log( Level.FINEST, "Dropping schema objects: START" );
for ( String sql : sqls ) {
try {
s.execute( sql );
}
catch (SQLException e) {
if ( -204 == e.getErrorCode() ) {
// Apparently we deleted this along with a dependent object since it doesn't exist anymore
}
else {
throw e;
}
}
}
LOG.log( Level.FINEST, "Dropping schema objects: END" );
LOG.log( Level.FINEST, "Committing: START" );
c.commit();
LOG.log( Level.FINEST, "Committing: END" );
}
catch (SQLException e) {
try {
c.rollback();
}
catch (SQLException e1) {
e.addSuppressed( e1 );
}
throw new RuntimeException( e );
}
}
@Override
public void clearSchema(Connection c, String schemaName) {
cachedForeignKeysPerSchema.remove( schemaName );
schemaName = schemaName.toUpperCase();
try (Statement s = c.createStatement()) {
ResultSet rs;
List<String> sqls = new ArrayList<>();
// Collect schema objects
LOG.log( Level.FINEST, "Collect schema objects: START" );
rs = s.executeQuery(
"SELECT 'DROP INDEX \"' || TRIM(INDSCHEMA) || '\".\"' || TRIM(INDNAME) || '\"' " +
"FROM SYSCAT.INDEXES " +
"WHERE UNIQUERULE = 'D' " +
"AND INDSCHEMA = '" + schemaName + "'"
);
while ( rs.next() ) {
sqls.add( rs.getString( 1 ) );
}
rs = s.executeQuery(
"SELECT 'ALTER TABLE \"' || TRIM(TABSCHEMA) || '\".\"' || TRIM(TABNAME) || '\" DROP FOREIGN KEY \"' || TRIM(CONSTNAME) || '\"' " +
"FROM SYSCAT.TABCONST " +
"WHERE TYPE = 'F' " +
"AND TABSCHEMA = '" + schemaName + "'"
);
while ( rs.next() ) {
sqls.add( rs.getString( 1 ) );
}
rs = s.executeQuery(
"SELECT 'ALTER TABLE \"' || TRIM(TABSCHEMA) || '\".\"' || TRIM(TABNAME) || '\" DROP UNIQUE \"' || TRIM(INDNAME) || '\"' " +
"FROM SYSCAT.INDEXES " +
"WHERE UNIQUERULE = 'U' " +
"AND INDSCHEMA = '" + schemaName + "'"
);
while ( rs.next() ) {
sqls.add( rs.getString( 1 ) );
}
rs = s.executeQuery(
"SELECT 'ALTER TABLE \"' || TRIM(TABSCHEMA) || '\".\"' || TRIM(TABNAME) || '\" DROP PRIMARY KEY' " +
"FROM SYSCAT.INDEXES " +
"WHERE UNIQUERULE = 'P' " +
"AND INDSCHEMA = '" + schemaName + "'"
);
while ( rs.next() ) {
sqls.add( rs.getString( 1 ) );
}
rs = s.executeQuery(
"SELECT 'DROP VIEW \"' || TRIM(TABSCHEMA) || '\".\"' || TRIM(TABNAME) || '\"' " +
"FROM SYSCAT.TABLES " +
"WHERE TYPE = 'V' " +
"AND TABSCHEMA = '" + schemaName + "'"
);
while ( rs.next() ) {
sqls.add( rs.getString( 1 ) );
}
rs = s.executeQuery(
"SELECT 'DROP TABLE \"' || TRIM(TABSCHEMA) || '\".\"' || TRIM(TABNAME) || '\"' " +
"FROM SYSCAT.TABLES " +
"WHERE TYPE = 'T' " +
"AND TABSCHEMA = '" + schemaName + "'"
);
while ( rs.next() ) {
sqls.add( rs.getString( 1 ) );
}
rs = s.executeQuery(
"SELECT 'DROP SEQUENCE \"' || TRIM(SEQSCHEMA) || '\".\"' || TRIM(SEQNAME) || '\"' " +
"FROM SYSCAT.SEQUENCES " +
"WHERE SEQSCHEMA = '" + schemaName + "'"
);
while ( rs.next() ) {
sqls.add( rs.getString( 1 ) );
}
LOG.log( Level.FINEST, "Collect schema objects: END" );
LOG.log( Level.FINEST, "Dropping schema objects: START" );
for ( String sql : sqls ) {
try {
s.execute( sql );
}
catch (SQLException e) {
if ( -204 == e.getErrorCode() ) {
// Apparently we deleted this along with a dependent object since it doesn't exist anymore
}
else {
throw e;
}
}
}
LOG.log( Level.FINEST, "Dropping schema objects: END" );
LOG.log( Level.FINEST, "Committing: START" );
c.commit();
LOG.log( Level.FINEST, "Committing: END" );
}
catch (SQLException e) {
try {
c.rollback();
}
catch (SQLException e1) {
e.addSuppressed( e1 );
}
throw new RuntimeException( e );
}
}
@Override
public void clearAllData(Connection connection) {
Map<String, List<String>> cachedForeignKeys = cachedForeignKeysPerSchema.get( null );
if ( cachedForeignKeys == null ) {
cachedForeignKeys = collectAllForeignKeys( connection );
cachedForeignKeysPerSchema.put( null, cachedForeignKeys );
}
deleteAllData( connection, cachedForeignKeys );
}
@Override
public void clearData(Connection connection, String schemaName) {
Map<String, List<String>> cachedForeignKeys = cachedForeignKeysPerSchema.get( schemaName );
if ( cachedForeignKeys == null ) {
cachedForeignKeys = collectForeignKeys( connection, schemaName.toUpperCase() );
cachedForeignKeysPerSchema.put( schemaName, cachedForeignKeys );
}
deleteAllData( connection, cachedForeignKeys );
}
private Map<String, List<String>> collectAllForeignKeys(Connection c) {
try (Statement s = c.createStatement()) {
// Collect table names for schemas
LOG.log( Level.FINEST, "Collect table names: START" );
ResultSet rs = s.executeQuery(
"SELECT TABLE_SCHEMA || '.' || TABLE_NAME FROM SYSIBM.TABLES WHERE TABLE_SCHEMA NOT IN (" + SYSTEM_SCHEMAS + ")"
);
Map<String, List<String>> foreignKeys = new HashMap<>();
while ( rs.next() ) {
foreignKeys.put( rs.getString( 1 ), new ArrayList<String>() );
}
LOG.log( Level.FINEST, "Collect table names: END" );
// Collect foreign keys for tables
LOG.log( Level.FINEST, "Collect foreign keys: START" );
ResultSet rs2 = s.executeQuery(
"SELECT FKTABLE_SCHEM || '.' || FKTABLE_NAME, FK_NAME FROM SYSIBM.SQLFOREIGNKEYS WHERE FKTABLE_SCHEM NOT IN (" + SYSTEM_SCHEMAS + ")"
);
while ( rs2.next() ) {
foreignKeys.get( rs2.getString( 1 ) ).add( rs2.getString( 2 ) );
}
LOG.log( Level.FINEST, "Collect foreign keys: END" );
return foreignKeys;
}
catch (SQLException e) {
try {
c.rollback();
}
catch (SQLException e1) {
e.addSuppressed( e1 );
}
throw new RuntimeException( e );
}
}
private Map<String, List<String>> collectForeignKeys(Connection c, String schemaName) {
try (Statement s = c.createStatement()) {
// Collect table names for schemas
LOG.log( Level.FINEST, "Collect table names: START" );
ResultSet rs = s.executeQuery(
"SELECT TRIM(TABLE_SCHEMA) || '.' || TABLE_NAME FROM SYSIBM.TABLES WHERE TABLE_SCHEMA = '" + schemaName + "'"
);
Map<String, List<String>> foreignKeys = new HashMap<>();
while ( rs.next() ) {
foreignKeys.put( rs.getString( 1 ), new ArrayList<String>() );
}
LOG.log( Level.FINEST, "Collect table names: END" );
// Collect foreign keys for tables
LOG.log( Level.FINEST, "Collect foreign keys: START" );
ResultSet rs2 = s.executeQuery(
"SELECT FKTABLE_SCHEM || '.' || FKTABLE_NAME, FK_NAME FROM SYSIBM.SQLFOREIGNKEYS WHERE FKTABLE_SCHEM = '" + schemaName + "'"
);
while ( rs2.next() ) {
foreignKeys.get( rs2.getString( 1 ) ).add( rs2.getString( 2 ) );
}
LOG.log( Level.FINEST, "Collect foreign keys: END" );
return foreignKeys;
}
catch (SQLException e) {
try {
c.rollback();
}
catch (SQLException e1) {
e.addSuppressed( e1 );
}
throw new RuntimeException( e );
}
}
private void deleteAllData(Connection c, Map<String, List<String>> foreignKeys) {
try (Statement s = c.createStatement()) {
// Disable foreign keys
LOG.log( Level.FINEST, "Disable foreign keys: START" );
for ( Map.Entry<String, List<String>> entry : foreignKeys.entrySet() ) {
for ( String fk : entry.getValue() ) {
s.execute( "ALTER TABLE " + entry.getKey() + " ALTER FOREIGN KEY " + fk + " NOT ENFORCED" );
}
}
c.commit();
LOG.log( Level.FINEST, "Disable foreign keys: END" );
// Delete data
LOG.log( Level.FINEST, "Deleting data: START" );
for ( String table : foreignKeys.keySet() ) {
if ( !ignoredTables.contains( table ) ) {
s.execute( "TRUNCATE TABLE " + table + " IMMEDIATE" );
// DB2 needs a commit after every truncate statement
c.commit();
}
}
LOG.log( Level.FINEST, "Deleting data: END" );
// Enable foreign keys
LOG.log( Level.FINEST, "Enabling foreign keys: START" );
for ( Map.Entry<String, List<String>> entry : foreignKeys.entrySet() ) {
for ( String fk : entry.getValue() ) {
s.execute( "ALTER TABLE " + entry.getKey() + " ALTER FOREIGN KEY " + fk + " ENFORCED" );
}
}
LOG.log( Level.FINEST, "Enabling foreign keys: END" );
LOG.log( Level.FINEST, "Committing: START" );
c.commit();
LOG.log( Level.FINEST, "Committing: END" );
}
catch (SQLException e) {
try {
c.rollback();
}
catch (SQLException e1) {
e.addSuppressed( e1 );
}
throw new RuntimeException( e );
}
}
}

View File

@ -0,0 +1,42 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.testing.cleaner;
import java.sql.Connection;
/**
* @author Christian Beikov
*/
public interface DatabaseCleaner {
static void clearSchemas() {
final DatabaseCleaner cleaner = DatabaseCleanerContext.CLEANER;
if ( cleaner != null ) {
JdbcConnectionContext.work( cleaner::clearAllSchemas );
}
}
static void clearData() {
final DatabaseCleaner cleaner = DatabaseCleanerContext.CLEANER;
if ( cleaner != null ) {
JdbcConnectionContext.work( cleaner::clearAllData );
}
}
void addIgnoredTable(String tableName);
boolean isApplicable(Connection connection);
void clearAllSchemas(Connection connection);
void clearSchema(Connection connection, String schemaName);
void clearAllData(Connection connection);
void clearData(Connection connection, String schemaName);
}

View File

@ -0,0 +1,41 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.testing.cleaner;
/**
* @author Christian Beikov
*/
public final class DatabaseCleanerContext {
public static final DatabaseCleaner CLEANER;
static {
CLEANER = JdbcConnectionContext.workReturning(
connection -> {
final DatabaseCleaner[] cleaners = new DatabaseCleaner[] {
new DB2DatabaseCleaner(),
new H2DatabaseCleaner(),
new SQLServerDatabaseCleaner(),
new MySQL5DatabaseCleaner(),
new MySQL8DatabaseCleaner(),
new MariaDBDatabaseCleaner(),
new OracleDatabaseCleaner(),
new PostgreSQLDatabaseCleaner()
};
for ( DatabaseCleaner cleaner : cleaners ) {
if ( cleaner.isApplicable( connection ) ) {
return cleaner;
}
}
return null;
}
);
}
private DatabaseCleanerContext() {
}
}

View File

@ -0,0 +1,158 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.testing.cleaner;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* @author Christian Beikov
*/
public class H2DatabaseCleaner implements DatabaseCleaner {
private static final Logger LOG = Logger.getLogger( H2DatabaseCleaner.class.getName() );
private static final String SYSTEM_SCHEMAS = "'INFORMATION_SCHEMA'";
private final List<String> ignoredTables = new ArrayList<>();
private final Map<String, List<String>> cachedTableNamesPerSchema = new HashMap<>();
@Override
public boolean isApplicable(Connection connection) {
try {
return connection.getMetaData().getDatabaseProductName().startsWith( "H2" );
}
catch (SQLException e) {
throw new RuntimeException( "Could not resolve the database metadata!", e );
}
}
@Override
public void addIgnoredTable(String tableName) {
ignoredTables.add( tableName );
}
@Override
public void clearAllSchemas(Connection c) {
cachedTableNamesPerSchema.clear();
try (Statement s = c.createStatement()) {
LOG.log( Level.FINEST, "Dropping schema objects: START" );
s.execute( "DROP ALL OBJECTS" );
LOG.log( Level.FINEST, "Dropping schema objects: END" );
LOG.log( Level.FINEST, "Committing: START" );
c.commit();
LOG.log( Level.FINEST, "Committing: END" );
}
catch (SQLException e) {
try {
c.rollback();
}
catch (SQLException e1) {
e.addSuppressed( e1 );
}
throw new RuntimeException( e );
}
}
@Override
public void clearSchema(Connection c, String schemaName) {
throw new UnsupportedOperationException();
}
@Override
public void clearAllData(Connection connection) {
clearData0(
connection,
null,
statement -> {
try {
return statement.executeQuery(
"SELECT TABLE_SCHEMA, TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA NOT IN (" + SYSTEM_SCHEMAS + ")" );
}
catch (SQLException sqlException) {
throw new RuntimeException( sqlException );
}
}
);
}
@Override
public void clearData(Connection connection, String schemaName) {
clearData0(
connection,
schemaName,
statement -> {
try {
return statement.executeQuery(
"SELECT TABLE_SCHEMA, TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = '" + schemaName + "'" );
}
catch (SQLException sqlException) {
throw new RuntimeException( sqlException );
}
}
);
}
private void clearData0(Connection connection, String schemaName, Function<Statement, ResultSet> tablesProvider) {
try (Statement s = connection.createStatement()) {
// Disable foreign keys
LOG.log( Level.FINEST, "Disable foreign keys: START" );
s.execute( "SET REFERENTIAL_INTEGRITY FALSE" );
LOG.log( Level.FINEST, "Disable foreign keys: END" );
// Delete data
LOG.log( Level.FINEST, "Deleting data: START" );
List<String> cachedTableNames = cachedTableNamesPerSchema.get( schemaName );
if ( cachedTableNames == null ) {
cachedTableNames = new ArrayList<>();
ResultSet rs = tablesProvider.apply( s );
while ( rs.next() ) {
String tableSchema = rs.getString( 1 );
String tableName = rs.getString( 2 );
if ( !ignoredTables.contains( tableName ) ) {
cachedTableNames.add( tableSchema + "." + tableName );
}
}
cachedTableNamesPerSchema.put( schemaName, cachedTableNames );
}
for ( String table : cachedTableNames ) {
s.execute( "TRUNCATE TABLE " + table );
}
LOG.log( Level.FINEST, "Deleting data: END" );
// Enable foreign keys
LOG.log( Level.FINEST, "Enabling foreign keys: START" );
s.execute( "SET REFERENTIAL_INTEGRITY TRUE" );
LOG.log( Level.FINEST, "Enabling foreign keys: END" );
LOG.log( Level.FINEST, "Committing: START" );
connection.commit();
LOG.log( Level.FINEST, "Committing: END" );
}
catch (SQLException e) {
try {
connection.rollback();
}
catch (SQLException e1) {
e.addSuppressed( e1 );
}
throw new RuntimeException( e );
}
}
}

View File

@ -0,0 +1,79 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.testing.cleaner;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.Driver;
import java.util.Properties;
import org.hibernate.cfg.AvailableSettings;
/**
* @author Christian Beikov
*/
public final class JdbcConnectionContext {
private static final Driver driver;
private static final String url;
private static final String user;
private static final String password;
private static final Properties properties;
static {
final Properties connectionProperties = new Properties();
try (InputStream inputStream = Thread.currentThread()
.getContextClassLoader()
.getResourceAsStream( "hibernate.properties" )) {
connectionProperties.load( inputStream );
final String driverClassName = connectionProperties.getProperty(
AvailableSettings.DRIVER );
driver = (Driver) Class.forName( driverClassName ).newInstance();
url = connectionProperties.getProperty(
AvailableSettings.URL );
user = connectionProperties.getProperty(
AvailableSettings.USER );
password = connectionProperties.getProperty(
AvailableSettings.PASS );
Properties p = new Properties();
p.put( "user", user );
p.put( "password", password );
properties = p;
}
catch (Exception e) {
throw new IllegalArgumentException( e );
}
}
public static void work(ConnectionConsumer work) {
try (Connection connection = driver.connect( url, properties )) {
connection.setAutoCommit( false );
work.consume( connection );
}
catch (Exception e) {
throw new IllegalArgumentException( e );
}
}
public static <R> R workReturning(ConnectionFunction<R> work) {
try (Connection connection = driver.connect( url, properties )) {
connection.setAutoCommit( false );
return work.apply( connection );
}
catch (Exception e) {
throw new IllegalArgumentException( e );
}
}
public static interface ConnectionConsumer {
void consume(Connection c) throws Exception;
}
public static interface ConnectionFunction<R> {
R apply(Connection c) throws Exception;
}
private JdbcConnectionContext() {
}
}

View File

@ -0,0 +1,32 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.testing.cleaner;
import java.sql.Connection;
import java.sql.SQLException;
/**
* @author Christian Beikov
*/
public class MariaDBDatabaseCleaner extends AbstractMySQLDatabaseCleaner {
@Override
public boolean isApplicable(Connection connection) {
try {
return connection.getMetaData().getDatabaseProductName().equals( "MariaDB" )
&& connection.getMetaData().getDriverName().startsWith( "MariaDB" );
}
catch (SQLException e) {
throw new RuntimeException( "Could not resolve the database metadata!", e );
}
}
@Override
protected String createClearingStatementForTable(String tableSchema, String tableName) {
return "TRUNCATE " + tableSchema + "." + tableName;
}
}

View File

@ -0,0 +1,33 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.testing.cleaner;
import java.sql.Connection;
import java.sql.SQLException;
/**
* @author Christian Beikov
*/
public class MySQL5DatabaseCleaner extends AbstractMySQLDatabaseCleaner {
@Override
public boolean isApplicable(Connection connection) {
try {
return connection.getMetaData().getDatabaseProductName().startsWith( "MySQL" ) && connection.getMetaData()
.getDatabaseMajorVersion() < 8;
}
catch (SQLException e) {
throw new RuntimeException( "Could not resolve the database metadata!", e );
}
}
@Override
protected String createClearingStatementForTable(String tableSchema, String tableName) {
// We do not use TRUNCATE for MySQL 5.7 due to https://bugs.mysql.com/bug.php?id=68184
return "DELETE FROM " + tableSchema + "." + tableName;
}
}

View File

@ -0,0 +1,32 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.testing.cleaner;
import java.sql.Connection;
import java.sql.SQLException;
/**
* @author Christian Beikov
*/
public class MySQL8DatabaseCleaner extends AbstractMySQLDatabaseCleaner {
@Override
public boolean isApplicable(Connection connection) {
try {
return connection.getMetaData().getDatabaseProductName().startsWith( "MySQL" ) && connection.getMetaData()
.getDatabaseMajorVersion() >= 8;
}
catch (SQLException e) {
throw new RuntimeException( "Could not resolve the database metadata!", e );
}
}
@Override
protected String createClearingStatementForTable(String tableSchema, String tableName) {
return "TRUNCATE " + tableSchema + "." + tableName;
}
}

View File

@ -0,0 +1,272 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.testing.cleaner;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* @author Christian Beikov
*/
public class OracleDatabaseCleaner implements DatabaseCleaner {
private static final Logger LOG = Logger.getLogger( OracleDatabaseCleaner.class.getName() );
private static final String SYSTEM_SEQUENCE_OWNERS = "'SYS'," +
"'CTXSYS'," +
"'DVSYS'," +
"'OJVMSYS'," +
"'ORDDATA'," +
"'MDSYS'," +
"'OLAPSYS'," +
"'LBACSYS'," +
"'XDB'," +
"'WMSYS'";
private final List<String> ignoredTables = new ArrayList<>();
private final Map<String, List<String>> cachedTruncateTableSqlPerSchema = new HashMap<>();
private final Map<String, List<String>> cachedConstraintDisableSqlPerSchema = new HashMap<>();
private final Map<String, List<String>> cachedConstraintEnableSqlPerSchema = new HashMap<>();
@Override
public boolean isApplicable(Connection connection) {
try {
return connection.getMetaData().getDatabaseProductName().startsWith( "Oracle" );
}
catch (SQLException e) {
throw new RuntimeException( "Could not resolve the database metadata!", e );
}
}
@Override
public void addIgnoredTable(String tableName) {
ignoredTables.add( tableName );
}
@Override
public void clearAllSchemas(Connection connection) {
cachedTruncateTableSqlPerSchema.clear();
cachedConstraintDisableSqlPerSchema.clear();
cachedConstraintEnableSqlPerSchema.clear();
clearSchema0(
connection,
statement -> {
try {
return statement.executeQuery(
"SELECT 'DROP TABLE ' || owner || '.\"' || table_name || '\" CASCADE CONSTRAINTS' " +
"FROM all_tables " +
// Exclude the tables owner by sys
"WHERE owner NOT IN ('SYS')" +
// Normally, user tables aren't in sysaux
" AND tablespace_name NOT IN ('SYSAUX')" +
// Apparently, user tables have global stats off
" AND global_stats = 'NO'" +
// Exclude the tables with names starting like 'DEF$_'
" AND table_name NOT LIKE 'DEF$\\_%' ESCAPE '\\'" +
" UNION ALL " +
"SELECT 'DROP SEQUENCE ' || sequence_owner || '.' || sequence_name FROM all_sequences WHERE sequence_owner NOT IN (" + SYSTEM_SEQUENCE_OWNERS + ")"
);
}
catch (SQLException sqlException) {
throw new RuntimeException( sqlException );
}
}
);
}
@Override
public void clearSchema(Connection connection, String schemaName) {
cachedTruncateTableSqlPerSchema.remove( schemaName );
cachedConstraintDisableSqlPerSchema.remove( schemaName );
cachedConstraintEnableSqlPerSchema.remove( schemaName );
clearSchema0(
connection,
statement -> {
try {
return statement.executeQuery(
"SELECT 'DROP TABLE ' || owner || '.\"' || table_name || '\" CASCADE CONSTRAINTS' " +
"FROM all_tables " +
"WHERE owner = '" + schemaName + "'" +
// Normally, user tables aren't in sysaux
" AND tablespace_name NOT IN ('SYSAUX')" +
// Apparently, user tables have global stats off
" AND global_stats = 'NO'" +
// Exclude the tables with names starting like 'DEF$_'
" AND table_name NOT LIKE 'DEF$\\_%' ESCAPE '\\'" +
" UNION ALL " +
"SELECT 'DROP SEQUENCE ' || sequence_owner || '.' || sequence_name FROM all_sequences WHERE sequence_owner = '" + schemaName + "'"
);
}
catch (SQLException sqlException) {
throw new RuntimeException( sqlException );
}
}
);
}
private void clearSchema0(Connection c, Function<Statement, ResultSet> sqlProvider) {
try (Statement s = c.createStatement()) {
ResultSet rs;
List<String> sqls = new ArrayList<>();
// Collect schema objects
LOG.log( Level.FINEST, "Collect schema objects: START" );
rs = sqlProvider.apply( s );
while ( rs.next() ) {
sqls.add( rs.getString( 1 ) );
}
LOG.log( Level.FINEST, "Collect schema objects: END" );
LOG.log( Level.FINEST, "Dropping schema objects: START" );
for ( String sql : sqls ) {
s.execute( sql );
}
LOG.log( Level.FINEST, "Dropping schema objects: END" );
LOG.log( Level.FINEST, "Committing: START" );
c.commit();
LOG.log( Level.FINEST, "Committing: END" );
}
catch (SQLException e) {
try {
c.rollback();
}
catch (SQLException e1) {
e.addSuppressed( e1 );
}
throw new RuntimeException( e );
}
}
@Override
public void clearAllData(Connection connection) {
clearData0(
connection,
null,
statement -> {
try {
return statement.executeQuery(
"SELECT tbl.owner || '.\"' || tbl.table_name || '\"', c.constraint_name FROM (" +
"SELECT owner, table_name " +
"FROM all_tables " +
// Exclude the tables owner by sys
"WHERE owner NOT IN ('SYS')" +
// Normally, user tables aren't in sysaux
" AND tablespace_name NOT IN ('SYSAUX')" +
// Apparently, user tables have global stats off
" AND global_stats = 'NO'" +
// Exclude the tables with names starting like 'DEF$_'
" AND table_name NOT LIKE 'DEF$\\_%' ESCAPE '\\'" +
") tbl LEFT JOIN all_constraints c ON tbl.owner = c.owner AND tbl.table_name = c.table_name AND constraint_type = 'R'"
);
}
catch (SQLException sqlException) {
throw new RuntimeException( sqlException );
}
}
);
}
@Override
public void clearData(Connection connection, String schemaName) {
clearData0(
connection,
schemaName, statement -> {
try {
return statement.executeQuery(
"SELECT tbl.owner || '.\"' || tbl.table_name || '\"', c.constraint_name FROM (" +
"SELECT owner, table_name " +
"FROM all_tables " +
"WHERE owner = '" + schemaName + "'" +
// Normally, user tables aren't in sysaux
" AND tablespace_name NOT IN ('SYSAUX')" +
// Apparently, user tables have global stats off
" AND global_stats = 'NO'" +
// Exclude the tables with names starting like 'DEF$_'
" AND table_name NOT LIKE 'DEF$\\_%' ESCAPE '\\'" +
") tbl LEFT JOIN all_constraints c ON tbl.owner = c.owner AND tbl.table_name = c.table_name AND constraint_type = 'R'"
);
}
catch (SQLException sqlException) {
throw new RuntimeException( sqlException );
}
}
);
}
private void clearData0(Connection connection, String schemaName, Function<Statement, ResultSet> tablesProvider) {
try (Statement s = connection.createStatement()) {
List<String> cachedTruncateTableSql = cachedTruncateTableSqlPerSchema.get( schemaName );
List<String> cachedConstraintDisableSql = cachedConstraintDisableSqlPerSchema.get( schemaName );
List<String> cachedConstraintEnableSql = cachedConstraintEnableSqlPerSchema.get( schemaName );
if ( cachedTruncateTableSql == null ) {
cachedTruncateTableSql = new ArrayList<>();
cachedConstraintDisableSql = new ArrayList<>();
cachedConstraintEnableSql = new ArrayList<>();
ResultSet rs = tablesProvider.apply( s );
while ( rs.next() ) {
String tableName = rs.getString( 1 );
String constraintName = rs.getString( 2 );
if ( !ignoredTables.contains( tableName ) ) {
cachedTruncateTableSql.add( "TRUNCATE TABLE \"" + tableName + "\"" );
if ( constraintName != null ) {
cachedConstraintDisableSql.add( "ALTER TABLE \"" + tableName + "\" DISABLE CONSTRAINT " + constraintName );
cachedConstraintEnableSql.add( "ALTER TABLE \"" + tableName + "\" ENABLE CONSTRAINT " + constraintName );
}
}
}
cachedTruncateTableSqlPerSchema.put( schemaName, cachedTruncateTableSql );
cachedConstraintDisableSqlPerSchema.put( schemaName, cachedConstraintDisableSql );
cachedConstraintEnableSqlPerSchema.put( schemaName, cachedConstraintEnableSql );
}
// Disable foreign keys
LOG.log( Level.FINEST, "Disable foreign keys: START" );
for ( String sql : cachedConstraintDisableSql ) {
s.execute( sql );
}
LOG.log( Level.FINEST, "Disable foreign keys: END" );
// Delete data
LOG.log( Level.FINEST, "Deleting data: START" );
for ( String sql : cachedTruncateTableSql ) {
s.execute( sql );
}
LOG.log( Level.FINEST, "Deleting data: END" );
// Enable foreign keys
LOG.log( Level.FINEST, "Enabling foreign keys: START" );
for ( String sql : cachedConstraintEnableSql ) {
s.execute( sql );
}
LOG.log( Level.FINEST, "Enabling foreign keys: END" );
LOG.log( Level.FINEST, "Committing: START" );
connection.commit();
LOG.log( Level.FINEST, "Committing: END" );
}
catch (SQLException e) {
try {
connection.rollback();
}
catch (SQLException e1) {
e.addSuppressed( e1 );
}
throw new RuntimeException( e );
}
}
}

View File

@ -0,0 +1,200 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.testing.cleaner;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* @author Christian Beikov
*/
public class PostgreSQLDatabaseCleaner implements DatabaseCleaner {
private static final Logger LOG = Logger.getLogger( PostgreSQLDatabaseCleaner.class.getName() );
private final List<String> ignoredTables = new ArrayList<>();
private final Map<String, String> truncateSqlPerSchema = new HashMap<>();
@Override
public boolean isApplicable(Connection connection) {
try {
return connection.getMetaData().getDatabaseProductName().startsWith( "PostgreSQL" );
}
catch (SQLException e) {
throw new RuntimeException( "Could not resolve the database metadata!", e );
}
}
@Override
public void addIgnoredTable(String tableName) {
ignoredTables.add( tableName.toLowerCase() );
}
@Override
public void clearAllSchemas(Connection connection) {
truncateSqlPerSchema.clear();
clearSchema0(
connection,
statement -> {
try {
return statement.executeQuery(
"SELECT SCHEMA_NAME FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME <> 'information_schema' AND SCHEMA_NAME NOT LIKE 'pg_%'" );
}
catch (SQLException sqlException) {
throw new RuntimeException( sqlException );
}
}
);
}
@Override
public void clearSchema(Connection connection, String schemaName) {
truncateSqlPerSchema.remove( schemaName );
clearSchema0(
connection,
statement -> {
try {
return statement.executeQuery(
"SELECT SCHEMA_NAME FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME = '" + schemaName + "'" );
}
catch (SQLException sqlException) {
throw new RuntimeException( sqlException );
}
}
);
}
private void clearSchema0(Connection c, Function<Statement, ResultSet> schemasProvider) {
try (Statement s = c.createStatement()) {
ResultSet rs;
final List<String> sqls = new ArrayList<>();
// Collect schema objects
String user = c.getMetaData().getUserName();
LOG.log( Level.FINEST, "Collect schema objects: START" );
rs = schemasProvider.apply( s );
while ( rs.next() ) {
String schema = rs.getString( 1 );
sqls.add( "DROP SCHEMA \"" + schema + "\" CASCADE" );
sqls.add( "CREATE SCHEMA \"" + schema + "\"" );
sqls.add( "GRANT ALL ON SCHEMA \"" + schema + "\" TO \"" + user + "\"" );
}
LOG.log( Level.FINEST, "Collect schema objects: END" );
LOG.log( Level.FINEST, "Dropping schema objects: START" );
for ( String sql : sqls ) {
s.execute( sql );
}
LOG.log( Level.FINEST, "Dropping schema objects: END" );
LOG.log( Level.FINEST, "Committing: START" );
c.commit();
LOG.log( Level.FINEST, "Committing: END" );
}
catch (SQLException e) {
try {
c.rollback();
}
catch (SQLException e1) {
e.addSuppressed( e1 );
}
throw new RuntimeException( e );
}
}
@Override
public void clearAllData(Connection connection) {
clearData0(
connection,
null,
statement -> {
try {
return statement.executeQuery(
"SELECT TABLE_SCHEMA, TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA <> 'information_schema' AND SCHEMA_NAME NOT LIKE 'pg_%'" );
}
catch (SQLException sqlException) {
throw new RuntimeException( sqlException );
}
}
);
}
@Override
public void clearData(Connection connection, String schemaName) {
clearData0(
connection,
schemaName,
statement -> {
try {
return statement.executeQuery(
"SELECT TABLE_SCHEMA, TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = '" + schemaName + "'" );
}
catch (SQLException sqlException) {
throw new RuntimeException( sqlException );
}
}
);
}
private void clearData0(Connection connection, String schemaName, Function<Statement, ResultSet> tablesProvider) {
try (Statement s = connection.createStatement()) {
// Delete data
LOG.log( Level.FINEST, "Deleting data: START" );
String truncateSql = truncateSqlPerSchema.get( schemaName );
if ( truncateSql == null ) {
StringBuilder sb = new StringBuilder();
sb.append( "TRUNCATE TABLE " );
ResultSet rs = tablesProvider.apply( s );
while ( rs.next() ) {
String tableSchema = rs.getString( 1 );
String tableName = rs.getString( 2 );
if ( !ignoredTables.contains( tableName ) ) {
sb.append( '"' );
sb.append( tableSchema );
sb.append( '"' );
sb.append( '.' );
sb.append( '"' );
sb.append( tableName );
sb.append( '"' );
sb.append( ',' );
}
}
sb.setCharAt( sb.length() - 1, ' ' );
sb.append( "RESTART IDENTITY CASCADE" );
truncateSql = sb.toString();
truncateSqlPerSchema.put( schemaName, truncateSql );
}
s.execute( truncateSql );
LOG.log( Level.FINEST, "Deleting data: END" );
LOG.log( Level.FINEST, "Committing: START" );
connection.commit();
LOG.log( Level.FINEST, "Committing: END" );
}
catch (SQLException e) {
try {
connection.rollback();
}
catch (SQLException e1) {
e.addSuppressed( e1 );
}
throw new RuntimeException( e );
}
}
}

View File

@ -0,0 +1,256 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.testing.cleaner;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* @author Christian Beikov
*/
public class SQLServerDatabaseCleaner implements DatabaseCleaner {
private static final Logger LOG = Logger.getLogger( SQLServerDatabaseCleaner.class.getName() );
private final List<String> ignoredTables = new ArrayList<>();
private final Map<String, List<String>> cachedTableNamesPerSchema = new HashMap<>();
@Override
public boolean isApplicable(Connection connection) {
try {
return connection.getMetaData().getDatabaseProductName().startsWith( "Microsoft SQL Server" );
}
catch (SQLException e) {
throw new RuntimeException( "Could not resolve the database metadata!", e );
}
}
@Override
public void addIgnoredTable(String tableName) {
ignoredTables.add( tableName );
}
@Override
public void clearAllSchemas(Connection c) {
cachedTableNamesPerSchema.clear();
try (Statement s = c.createStatement()) {
ResultSet rs;
List<String> sqls = new ArrayList<>();
// Collect schema objects
LOG.log( Level.FINEST, "Collect schema objects: START" );
rs = s.executeQuery(
"SELECT 'ALTER TABLE [' + TABLE_SCHEMA + '].[' + TABLE_NAME + '] DROP CONSTRAINT [' + CONSTRAINT_NAME + ']' FROM INFORMATION_SCHEMA.CONSTRAINT_TABLE_USAGE " +
"WHERE EXISTS (SELECT 1 FROM sys.Tables t JOIN sys.Schemas s ON t.schema_id = s.schema_id WHERE t.is_ms_shipped = 0 AND s.name = TABLE_SCHEMA AND t.name = TABLE_NAME) " +
"AND EXISTS (SELECT 1 FROM sys.Foreign_keys WHERE name = CONSTRAINT_NAME)" );
while ( rs.next() ) {
sqls.add( rs.getString( 1 ) );
}
rs = s.executeQuery(
"SELECT 'DROP VIEW [' + TABLE_SCHEMA + '].[' + TABLE_NAME + ']' FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE = 'VIEW' " +
"AND EXISTS (SELECT 1 FROM sys.Views t JOIN sys.Schemas s ON t.schema_id = s.schema_id WHERE t.is_ms_shipped = 0 AND s.name = TABLE_SCHEMA AND t.name = TABLE_NAME)" );
while ( rs.next() ) {
sqls.add( rs.getString( 1 ) );
}
rs = s.executeQuery(
"SELECT 'DROP TABLE [' + TABLE_SCHEMA + '].[' + TABLE_NAME + ']' FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE = 'BASE TABLE' " +
"AND EXISTS (SELECT 1 FROM sys.Tables t JOIN sys.Schemas s ON t.schema_id = s.schema_id WHERE t.is_ms_shipped = 0 AND s.name = TABLE_SCHEMA AND t.name = TABLE_NAME)" );
while ( rs.next() ) {
sqls.add( rs.getString( 1 ) );
}
rs = s.executeQuery(
"SELECT 'DROP SEQUENCE [' + SEQUENCE_SCHEMA + '].[' + SEQUENCE_NAME + ']' FROM INFORMATION_SCHEMA.SEQUENCES" );
while ( rs.next() ) {
sqls.add( rs.getString( 1 ) );
}
LOG.log( Level.FINEST, "Collect schema objects: END" );
LOG.log( Level.FINEST, "Dropping schema objects: START" );
for ( String sql : sqls ) {
s.execute( sql );
}
LOG.log( Level.FINEST, "Dropping schema objects: END" );
LOG.log( Level.FINEST, "Committing: START" );
c.commit();
LOG.log( Level.FINEST, "Committing: END" );
}
catch (SQLException e) {
try {
c.rollback();
}
catch (SQLException e1) {
e.addSuppressed( e1 );
}
throw new RuntimeException( e );
}
}
@Override
public void clearSchema(Connection c, String schemaName) {
cachedTableNamesPerSchema.remove( schemaName );
try (Statement s = c.createStatement()) {
ResultSet rs;
List<String> sqls = new ArrayList<>();
// Collect schema objects
LOG.log( Level.FINEST, "Collect schema objects: START" );
rs = s.executeQuery(
"SELECT 'ALTER TABLE [' + TABLE_SCHEMA + '].[' + TABLE_NAME + '] DROP CONSTRAINT [' + CONSTRAINT_NAME + ']' FROM INFORMATION_SCHEMA.CONSTRAINT_TABLE_USAGE " +
"WHERE EXISTS (SELECT 1 FROM sys.Tables t JOIN sys.Schemas s ON t.schema_id = s.schema_id WHERE t.is_ms_shipped = 0 AND s.name = TABLE_SCHEMA AND t.name = TABLE_NAME) " +
"AND EXISTS (SELECT 1 FROM sys.Foreign_keys WHERE name = CONSTRAINT_NAME) " +
"AND TABLE_SCHEMA = N'" + schemaName + "'" );
while ( rs.next() ) {
sqls.add( rs.getString( 1 ) );
}
rs = s.executeQuery(
"SELECT 'DROP VIEW [' + TABLE_SCHEMA + '].[' + TABLE_NAME + ']' FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE = 'VIEW' " +
"AND EXISTS (SELECT 1 FROM sys.Views t JOIN sys.Schemas s ON t.schema_id = s.schema_id WHERE t.is_ms_shipped = 0 AND s.name = TABLE_SCHEMA AND t.name = TABLE_NAME) " +
"AND TABLE_SCHEMA = N'" + schemaName + "'" );
while ( rs.next() ) {
sqls.add( rs.getString( 1 ) );
}
rs = s.executeQuery(
"SELECT 'DROP TABLE [' + TABLE_SCHEMA + '].[' + TABLE_NAME + ']' FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE = 'BASE TABLE' " +
"AND EXISTS (SELECT 1 FROM sys.Tables t JOIN sys.Schemas s ON t.schema_id = s.schema_id WHERE t.is_ms_shipped = 0 AND s.name = TABLE_SCHEMA AND t.name = TABLE_NAME) " +
"AND TABLE_SCHEMA = N'" + schemaName + "'" );
while ( rs.next() ) {
sqls.add( rs.getString( 1 ) );
}
rs = s.executeQuery(
"SELECT 'DROP SEQUENCE [' + SEQUENCE_SCHEMA + '].[' + SEQUENCE_NAME + ']' FROM INFORMATION_SCHEMA.SEQUENCES WHERE " +
"SEQUENCE_SCHEMA = N'" + schemaName + "'" );
while ( rs.next() ) {
sqls.add( rs.getString( 1 ) );
}
LOG.log( Level.FINEST, "Collect schema objects: END" );
LOG.log( Level.FINEST, "Dropping schema objects: START" );
for ( String sql : sqls ) {
s.execute( sql );
}
LOG.log( Level.FINEST, "Dropping schema objects: END" );
LOG.log( Level.FINEST, "Committing: START" );
c.commit();
LOG.log( Level.FINEST, "Committing: END" );
}
catch (SQLException e) {
try {
c.rollback();
}
catch (SQLException e1) {
e.addSuppressed( e1 );
}
throw new RuntimeException( e );
}
}
@Override
public void clearAllData(Connection connection) {
clearData0(
connection,
null,
statement -> {
try {
return statement.executeQuery(
"SELECT s.name, t.name FROM sys.Tables t JOIN sys.Schemas s ON t.schema_id = s.schema_id WHERE t.is_ms_shipped = 0" );
}
catch (SQLException sqlException) {
throw new RuntimeException( sqlException );
}
}
);
}
@Override
public void clearData(Connection connection, String schemaName) {
clearData0(
connection,
schemaName,
statement -> {
try {
return statement.executeQuery(
"SELECT s.name, t.name FROM sys.Tables t JOIN sys.Schemas s ON t.schema_id = s.schema_id WHERE t.is_ms_shipped = 0 AND s.name = N'" + schemaName + "'" );
}
catch (SQLException sqlException) {
throw new RuntimeException( sqlException );
}
}
);
}
private void clearData0(Connection connection, String schemaName, Function<Statement, ResultSet> tablesProvider) {
try (Statement s = connection.createStatement()) {
List<String> cachedTableNames = cachedTableNamesPerSchema.get( schemaName );
if ( cachedTableNames == null ) {
cachedTableNames = new ArrayList<>();
ResultSet rs = tablesProvider.apply( s );
while ( rs.next() ) {
String tableSchema = rs.getString( 1 );
String tableName = rs.getString( 2 );
if ( !ignoredTables.contains( tableName ) ) {
cachedTableNames.add( tableSchema + "." + tableName );
}
}
cachedTableNamesPerSchema.put( schemaName, cachedTableNames );
}
// Disable foreign keys
LOG.log( Level.FINEST, "Disable foreign keys: START" );
for ( String table : cachedTableNames ) {
s.execute( "ALTER TABLE " + table + " NOCHECK CONSTRAINT ALL" );
}
LOG.log( Level.FINEST, "Disable foreign keys: END" );
// Delete data
LOG.log( Level.FINEST, "Deleting data: START" );
for ( String table : cachedTableNames ) {
s.execute( "DELETE FROM " + table );
}
LOG.log( Level.FINEST, "Deleting data: END" );
// Enable foreign keys
LOG.log( Level.FINEST, "Enabling foreign keys: START" );
for ( String table : cachedTableNames ) {
s.execute( "ALTER TABLE " + table + " WITH CHECK CHECK CONSTRAINT ALL" );
}
LOG.log( Level.FINEST, "Enabling foreign keys: END" );
LOG.log( Level.FINEST, "Committing: START" );
connection.commit();
LOG.log( Level.FINEST, "Committing: END" );
}
catch (SQLException e) {
try {
connection.rollback();
}
catch (SQLException e1) {
e.addSuppressed( e1 );
}
throw new RuntimeException( e );
}
}
}

View File

@ -16,6 +16,7 @@ import javax.transaction.SystemException;
import org.hibernate.engine.transaction.internal.jta.JtaStatusHelper;
import org.hibernate.testing.AfterClassOnce;
import org.hibernate.testing.cleaner.DatabaseCleaner;
import org.hibernate.testing.jdbc.leak.ConnectionLeakUtil;
import org.hibernate.testing.jta.TestingJtaPlatformImpl;
import org.junit.After;
@ -34,6 +35,10 @@ import org.jboss.logging.Logger;
@RunWith( CustomRunner.class )
public abstract class BaseUnitTestCase {
static {
DatabaseCleaner.clearSchemas();
}
protected final Logger log = Logger.getLogger( getClass() );
private static boolean enableConnectionLeakDetection = Boolean.TRUE.toString()