/*! ****************************************************************************** * * Pentaho Data Integration * * Copyright (C) 2002-2016 by Pentaho : http://www.pentaho.com * ******************************************************************************* * * 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.pentaho.di.core.database; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyString; import static org.mockito.Matchers.eq; import static org.mockito.AdditionalMatchers.aryEq; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import java.lang.reflect.Field; import java.sql.*; import java.util.List; import javax.sql.DataSource; import org.junit.BeforeClass; import org.junit.Test; import org.mockito.Mockito; import org.pentaho.di.core.KettleClientEnvironment; import org.pentaho.di.core.database.DataSourceProviderInterface.DatasourceType; import org.pentaho.di.core.exception.KettleDatabaseBatchException; import org.pentaho.di.core.exception.KettleDatabaseException; import org.pentaho.di.core.logging.LogLevel; import org.pentaho.di.core.logging.LoggingObjectInterface; import org.pentaho.di.core.logging.LoggingObjectType; import org.pentaho.di.core.logging.SimpleLoggingObject; import org.pentaho.di.core.row.RowMetaInterface; import org.pentaho.di.core.row.ValueMetaInterface; import org.pentaho.di.core.row.value.ValueMetaNumber; import org.pentaho.di.core.variables.VariableSpace; public class DatabaseUnitTest { private static final String TEST_NAME_OF_DB_CONNECTION = "TEST_CONNECTION"; private static final String SQL_MOCK_EXCEPTION_MESSAGE = "SQL mock exception"; private static final SQLException SQL_EXCEPTION = new SQLException( SQL_MOCK_EXCEPTION_MESSAGE ); private static final String EXISTING_TABLE_NAME = "TABLE"; private static final String NOT_EXISTING_TABLE_NAME = "NOT_EXISTING_TABLE"; private static final String SCHEMA_TO_CHECK = "schemaPattern"; private static final String[] TABLE_TYPES_TO_GET = { "TABLE", "VIEW" }; private ResultSet resultSetMock = mock( ResultSet.class ); private DatabaseMeta dbMetaMock = mock( DatabaseMeta.class ); private DatabaseMetaData dbMetaDataMock = mock( DatabaseMetaData.class ); static LoggingObjectInterface log = new SimpleLoggingObject( "junit", LoggingObjectType.GENERAL, null ); @BeforeClass public static void setUp() throws Exception { KettleClientEnvironment.init(); } @Test public void testGetQueryFieldsFromPreparedStatement() throws Exception { String sql = "select * from employees"; String columnName = "salary"; DatabaseMeta meta = Mockito.mock( DatabaseMeta.class ); PreparedStatement ps = Mockito.mock( PreparedStatement.class ); Connection conn = mockConnection( mock( DatabaseMetaData.class ) ); ResultSetMetaData rsMetaData = mock( ResultSetMetaData.class ); when( rsMetaData.getColumnCount() ).thenReturn( 1 ); when( rsMetaData.getColumnName( 1 ) ).thenReturn( columnName ); when( rsMetaData.getColumnLabel( 1 ) ).thenReturn( columnName ); when( rsMetaData.getColumnType( 1 ) ).thenReturn( Types.DECIMAL ); Mockito.when( meta.stripCR( anyString() ) ).thenReturn( sql ); Mockito.when( meta.getDatabaseInterface() ).thenReturn( new MySQLDatabaseMeta() ); Mockito.when( conn.prepareStatement( sql, ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY ) ) .thenReturn( ps ); Mockito.when( ps.getMetaData() ).thenReturn( rsMetaData ); Database db = new Database( log, meta ); db.setConnection( conn ); RowMetaInterface rowMetaInterface = db.getQueryFieldsFromPreparedStatement( sql ); assertEquals( rowMetaInterface.size(), 1 ); assertEquals( rowMetaInterface.getValueMeta( 0 ).getName(), columnName ); assertTrue( rowMetaInterface.getValueMeta( 0 ) instanceof ValueMetaNumber ); } @Test public void testGetQueryFieldsFromDatabaseMetaData() throws Exception { DatabaseMeta meta = Mockito.mock( DatabaseMeta.class ); DatabaseMetaData dbMetaData = mock( DatabaseMetaData.class ); Connection conn = mockConnection( dbMetaData ); ResultSet columns = mock( ResultSet.class ); String columnName = "year"; String columnType = "Integer"; int columnSize = 15; Mockito.when( dbMetaData.getColumns( anyString(), anyString(), anyString(), anyString() ) ).thenReturn( columns ); Mockito.when( columns.next() ).thenReturn( true ).thenReturn( false ); Mockito.when( columns.getString( "COLUMN_NAME" ) ).thenReturn( columnName ); Mockito.when( columns.getString( "SOURCE_DATA_TYPE" ) ).thenReturn( columnType ); Mockito.when( columns.getInt( "COLUMN_SIZE" ) ).thenReturn( columnSize ); Database db = new Database( log, meta ); db.setConnection( conn ); RowMetaInterface rowMetaInterface = db.getQueryFieldsFromDatabaseMetaData(); assertEquals( rowMetaInterface.size(), 1 ); assertEquals( rowMetaInterface.getValueMeta( 0 ).getName(), columnName ); assertEquals( rowMetaInterface.getValueMeta( 0 ).getOriginalColumnTypeName(), columnType ); assertEquals( rowMetaInterface.getValueMeta( 0 ).getLength(), columnSize ); } @Test public void testGetQueryFieldsFallback() throws Exception { String sql = "select * from employees"; String columnName = "salary"; DatabaseMeta meta = Mockito.mock( DatabaseMeta.class ); PreparedStatement ps = Mockito.mock( PreparedStatement.class ); Connection conn = mockConnection( mock( DatabaseMetaData.class ) ); ResultSetMetaData rsMetaData = mock( ResultSetMetaData.class ); ResultSet rs = Mockito.mock( ResultSet.class ); when( rsMetaData.getColumnCount() ).thenReturn( 1 ); when( rsMetaData.getColumnName( 1 ) ).thenReturn( columnName ); when( rsMetaData.getColumnLabel( 1 ) ).thenReturn( columnName ); when( rsMetaData.getColumnType( 1 ) ).thenReturn( Types.DECIMAL ); when( ps.executeQuery() ).thenReturn( rs ); Mockito.when( meta.stripCR( anyString() ) ).thenReturn( sql ); Mockito.when( meta.getDatabaseInterface() ).thenReturn( new MySQLDatabaseMeta() ); Mockito.when( conn.prepareStatement( sql ) ).thenReturn( ps ); Mockito.when( rs.getMetaData() ).thenReturn( rsMetaData ); Database db = new Database( log, meta ); db.setConnection( conn ); RowMetaInterface rowMetaInterface = db.getQueryFieldsFallback( sql, false, null, null ); assertEquals( rowMetaInterface.size(), 1 ); assertEquals( rowMetaInterface.getValueMeta( 0 ).getName(), columnName ); assertTrue( rowMetaInterface.getValueMeta( 0 ) instanceof ValueMetaNumber ); } /** * PDI-11363. when using getLookup calls there is no need to make attempt to retrieve row set metadata for every call. * That may bring performance penalty depends on jdbc driver implementation. For some drivers that penalty can be huge * (postgres). * <p/> * During the execution calling getLookup() method we changing usually only lookup where clause which will not impact * return row structure. * * @throws KettleDatabaseException * @throws SQLException */ @Test public void testGetLookupMetaCalls() throws KettleDatabaseException, SQLException { DatabaseMeta meta = Mockito.mock( DatabaseMeta.class ); Mockito.when( meta.getQuotedSchemaTableCombination( Mockito.anyString(), Mockito.anyString() ) ).thenReturn( "a" ); Mockito.when( meta.quoteField( Mockito.anyString() ) ).thenReturn( "a" ); PreparedStatement ps = Mockito.mock( PreparedStatement.class ); ResultSet rs = Mockito.mock( ResultSet.class ); Mockito.when( ps.executeQuery() ).thenReturn( rs ); ResultSetMetaData rmeta = Mockito.mock( ResultSetMetaData.class ); Mockito.when( rs.getMetaData() ).thenReturn( rmeta ); Mockito.when( rmeta.getColumnCount() ).thenReturn( 0 ); Mockito.when( ps.getMetaData() ).thenReturn( rmeta ); Database db = new Database( log, meta ); Connection conn = Mockito.mock( Connection.class ); Mockito.when( conn.prepareStatement( Mockito.anyString() ) ).thenReturn( ps ); db.setConnection( conn ); String[] name = new String[] { "a" }; db.setLookup( "a", name, name, name, name, "a" ); for ( int i = 0; i < 10; i++ ) { db.getLookup(); } Mockito.verify( rmeta, Mockito.times( 1 ) ).getColumnCount(); } /** * Test that for every PreparedStatement passed into lookup signature we do reset and re-create row meta. * * @throws SQLException * @throws KettleDatabaseException */ @Test public void testGetLookupCallPSpassed() throws SQLException, KettleDatabaseException { DatabaseMeta meta = Mockito.mock( DatabaseMeta.class ); PreparedStatement ps = Mockito.mock( PreparedStatement.class ); ResultSet rs = Mockito.mock( ResultSet.class ); Mockito.when( ps.executeQuery() ).thenReturn( rs ); ResultSetMetaData rmeta = Mockito.mock( ResultSetMetaData.class ); Mockito.when( rs.getMetaData() ).thenReturn( rmeta ); Mockito.when( rmeta.getColumnCount() ).thenReturn( 0 ); Mockito.when( ps.getMetaData() ).thenReturn( rmeta ); Database db = new Database( log, meta ); db.getLookup( ps ); Mockito.verify( rmeta, Mockito.times( 1 ) ).getColumnCount(); } @Test public void testCreateKettleDatabaseBatchExceptionNullUpdatesWhenSQLException() { assertNull( Database.createKettleDatabaseBatchException( "", new SQLException() ).getUpdateCounts() ); } @Test public void testCreateKettleDatabaseBatchExceptionNotUpdatesWhenBatchUpdateException() { assertNotNull( Database.createKettleDatabaseBatchException( "", new BatchUpdateException( new int[ 0 ] ) ).getUpdateCounts() ); } @Test public void testCreateKettleDatabaseBatchExceptionConstructsExceptionList() { BatchUpdateException root = new BatchUpdateException(); SQLException next = new SQLException(); SQLException next2 = new SQLException(); root.setNextException( next ); next.setNextException( next2 ); List<Exception> exceptionList = Database.createKettleDatabaseBatchException( "", root ).getExceptionsList(); assertEquals( 2, exceptionList.size() ); assertEquals( next, exceptionList.get( 0 ) ); assertEquals( next2, exceptionList.get( 1 ) ); } @Test( expected = KettleDatabaseBatchException.class ) public void testInsertRowWithBatchAlwaysThrowsKettleBatchException() throws KettleDatabaseException, SQLException { DatabaseMeta mockDatabaseMeta = mock( DatabaseMeta.class ); when( mockDatabaseMeta.supportsBatchUpdates() ).thenReturn( true ); DatabaseMetaData mockDatabaseMetaData = mock( DatabaseMetaData.class ); when( mockDatabaseMetaData.supportsBatchUpdates() ).thenReturn( true ); Connection mockConnection = mockConnection( mockDatabaseMetaData ); PreparedStatement ps = mock( PreparedStatement.class ); when( ps.executeBatch() ).thenThrow( new SQLException() ); Database database = new Database( mockLogger(), mockDatabaseMeta ); database.setCommit( 1 ); database.setConnection( mockConnection ); database.insertRow( ps, true, true ); } @Test( expected = KettleDatabaseException.class ) public void testInsertRowWithoutBatchDoesntThrowKettleBatchException() throws KettleDatabaseException, SQLException { DatabaseMeta mockDatabaseMeta = mock( DatabaseMeta.class ); when( mockDatabaseMeta.supportsBatchUpdates() ).thenReturn( true ); DatabaseMetaData mockDatabaseMetaData = mock( DatabaseMetaData.class ); when( mockDatabaseMetaData.supportsBatchUpdates() ).thenReturn( true ); Connection mockConnection = mockConnection( mockDatabaseMetaData ); PreparedStatement ps = mock( PreparedStatement.class ); when( ps.executeUpdate() ).thenThrow( new SQLException() ); Database database = new Database( mockLogger(), mockDatabaseMeta ); database.setConnection( mockConnection ); try { database.insertRow( ps, true, true ); } catch ( KettleDatabaseBatchException e ) { // noop } } @Test( expected = KettleDatabaseBatchException.class ) public void testEmptyAndCommitWithBatchAlwaysThrowsKettleBatchException() throws KettleDatabaseException, SQLException { DatabaseMeta mockDatabaseMeta = mock( DatabaseMeta.class ); when( mockDatabaseMeta.supportsBatchUpdates() ).thenReturn( true ); DatabaseMetaData mockDatabaseMetaData = mock( DatabaseMetaData.class ); when( mockDatabaseMetaData.supportsBatchUpdates() ).thenReturn( true ); Connection mockConnection = mockConnection( mockDatabaseMetaData ); PreparedStatement ps = mock( PreparedStatement.class ); when( ps.executeBatch() ).thenThrow( new SQLException() ); Database database = new Database( mockLogger(), mockDatabaseMeta ); database.setCommit( 1 ); database.setConnection( mockConnection ); database.emptyAndCommit( ps, true, 1 ); } @Test( expected = KettleDatabaseException.class ) public void testEmptyAndCommitWithoutBatchDoesntThrowKettleBatchException() throws KettleDatabaseException, SQLException { DatabaseMeta mockDatabaseMeta = mock( DatabaseMeta.class ); when( mockDatabaseMeta.supportsBatchUpdates() ).thenReturn( true ); DatabaseMetaData mockDatabaseMetaData = mock( DatabaseMetaData.class ); when( mockDatabaseMetaData.supportsBatchUpdates() ).thenReturn( true ); Connection mockConnection = mockConnection( mockDatabaseMetaData ); PreparedStatement ps = mock( PreparedStatement.class ); doThrow( new SQLException() ).when( ps ).close(); Database database = new Database( mockLogger(), mockDatabaseMeta ); database.setConnection( mockConnection ); try { database.emptyAndCommit( ps, true, 1 ); } catch ( KettleDatabaseBatchException e ) { // noop } } @SuppressWarnings( "deprecation" ) @Test( expected = KettleDatabaseBatchException.class ) public void testInsertFinishedWithBatchAlwaysThrowsKettleBatchException() throws KettleDatabaseException, SQLException { DatabaseMeta mockDatabaseMeta = mock( DatabaseMeta.class ); when( mockDatabaseMeta.supportsBatchUpdates() ).thenReturn( true ); DatabaseMetaData mockDatabaseMetaData = mock( DatabaseMetaData.class ); when( mockDatabaseMetaData.supportsBatchUpdates() ).thenReturn( true ); Connection mockConnection = mockConnection( mockDatabaseMetaData ); PreparedStatement ps = mock( PreparedStatement.class ); when( ps.executeBatch() ).thenThrow( new SQLException() ); Database database = new Database( mockLogger(), mockDatabaseMeta ); database.setCommit( 1 ); database.setConnection( mockConnection ); database.insertFinished( ps, true ); } @SuppressWarnings( "deprecation" ) @Test( expected = KettleDatabaseException.class ) public void testInsertFinishedWithoutBatchDoesntThrowKettleBatchException() throws KettleDatabaseException, SQLException { DatabaseMeta mockDatabaseMeta = mock( DatabaseMeta.class ); when( mockDatabaseMeta.supportsBatchUpdates() ).thenReturn( true ); DatabaseMetaData mockDatabaseMetaData = mock( DatabaseMetaData.class ); when( mockDatabaseMetaData.supportsBatchUpdates() ).thenReturn( true ); Connection mockConnection = mockConnection( mockDatabaseMetaData ); PreparedStatement ps = mock( PreparedStatement.class ); doThrow( new SQLException() ).when( ps ).close(); Database database = new Database( mockLogger(), mockDatabaseMeta ); database.setConnection( mockConnection ); try { database.insertFinished( ps, true ); } catch ( KettleDatabaseBatchException e ) { // noop } } @Test public void insertRowAndExecuteBatchCauseNoErrors() throws Exception { DatabaseMeta dbMeta = mock( DatabaseMeta.class ); when( dbMeta.supportsBatchUpdates() ).thenReturn( true ); DatabaseMetaData dbMetaData = mock( DatabaseMetaData.class ); when( dbMetaData.supportsBatchUpdates() ).thenReturn( true ); Database db = new Database( mockLogger(), dbMeta ); db.setConnection( mockConnection( dbMetaData ) ); db.setCommit( 1 ); PreparedStatement ps = mock( PreparedStatement.class ); db.insertRow( ps, true, false ); verify( ps ).addBatch(); db.executeAndClearBatch( ps ); verify( ps ).executeBatch(); verify( ps ).clearBatch(); } @Test public void insertRowWhenDbDoNotSupportBatchLeadsToCommit() throws Exception { DatabaseMeta dbMeta = mock( DatabaseMeta.class ); when( dbMeta.supportsBatchUpdates() ).thenReturn( false ); DatabaseMetaData dbMetaData = mock( DatabaseMetaData.class ); when( dbMetaData.supportsBatchUpdates() ).thenReturn( false ); Database db = new Database( mockLogger(), dbMeta ); db.setConnection( mockConnection( dbMetaData ) ); db.setCommit( 1 ); PreparedStatement ps = mock( PreparedStatement.class ); db.insertRow( ps, true, false ); verify( ps, never() ).addBatch(); verify( ps ).executeUpdate(); } @Test public void testGetCreateSequenceStatement() throws Exception { DatabaseMeta dbMeta = mock( DatabaseMeta.class ); when( dbMeta.supportsSequences() ).thenReturn( true ); when( dbMeta.supportsSequenceNoMaxValueOption() ).thenReturn( true ); DatabaseMetaData dbMetaData = mock( DatabaseMetaData.class ); DatabaseInterface databaseInterface = mock( DatabaseInterface.class ); doReturn( databaseInterface ).when( dbMeta ).getDatabaseInterface(); Database db = new Database( mockLogger(), dbMeta ); db.setConnection( mockConnection( dbMetaData ) ); db.setCommit( 1 ); db.getCreateSequenceStatement( "schemaName", "seq", "10", "1", "-1", false ); verify( databaseInterface, times( 1 ) ).getSequenceNoMaxValueOption(); } @Test public void testPrepareSQL() throws Exception { DatabaseMeta dbMeta = mock( DatabaseMeta.class ); DatabaseInterface databaseInterface = mock( DatabaseInterface.class ); doReturn( databaseInterface ).when( dbMeta ).getDatabaseInterface(); DatabaseMetaData dbMetaData = mock( DatabaseMetaData.class ); Database db = new Database( mockLogger(), dbMeta ); db.setConnection( mockConnection( dbMetaData ) ); db.setCommit( 1 ); db.prepareSQL( "SELECT * FROM DUMMY" ); db.prepareSQL( "SELECT * FROM DUMMY", true ); verify( databaseInterface, times( 2 ) ).supportsAutoGeneratedKeys(); } @Test public void testGetCreateTableStatement() throws Exception { ValueMetaInterface v = mock( ValueMetaInterface.class ); DatabaseMeta dbMeta = mock( DatabaseMeta.class ); DatabaseInterface databaseInterface = mock( DatabaseInterface.class ); doReturn( " " ).when( databaseInterface ).getDataTablespaceDDL( any( VariableSpace.class ), eq( dbMeta ) ); doReturn( "CREATE TABLE " ).when( databaseInterface ).getCreateTableStatement(); doReturn( databaseInterface ).when( dbMeta ).getDatabaseInterface(); DatabaseMetaData dbMetaData = mock( DatabaseMetaData.class ); Database db = new Database( mockLogger(), dbMeta ); db.setConnection( mockConnection( dbMetaData ) ); db.setCommit( 1 ); String tableName = "DUMMY", tk = "tKey", pk = "pKey"; RowMetaInterface fields = mock( RowMetaInterface.class ); doReturn( 1 ).when( fields ).size(); doReturn( v ).when( fields ).getValueMeta( 0 ); boolean useAutoInc = true, semiColon = true; doReturn( "double foo" ).when( dbMeta ).getFieldDefinition( v, tk, pk, useAutoInc ); doReturn( true ).when( dbMeta ).requiresCreateTablePrimaryKeyAppend(); String statement = db.getCreateTableStatement( tableName, fields, tk, useAutoInc, pk, semiColon ); String expectedStatRegexp = concatWordsForRegexp( "CREATE TABLE DUMMY", "\\(", "double foo", ",", "PRIMARY KEY \\(tKey\\)", ",", "PRIMARY KEY \\(pKey\\)", "\\)", ";" ); assertTrue( statement.matches( expectedStatRegexp ) ); doReturn( "CREATE COLUMN TABLE " ).when( databaseInterface ).getCreateTableStatement(); statement = db.getCreateTableStatement( tableName, fields, tk, useAutoInc, pk, semiColon ); expectedStatRegexp = concatWordsForRegexp( "CREATE COLUMN TABLE DUMMY", "\\(", "double foo", ",", "PRIMARY KEY \\(tKey\\)", ",", "PRIMARY KEY \\(pKey\\)", "\\)", ";" ); assertTrue( statement.matches( expectedStatRegexp ) ); } @Test public void testCheckTableExistsByDbMeta_Success() throws Exception { when( resultSetMock.next() ).thenReturn( true, false ); when( resultSetMock.getString( "TABLE_NAME" ) ).thenReturn( EXISTING_TABLE_NAME ); when( dbMetaDataMock.getTables( any(), anyString(), anyString(), aryEq( TABLE_TYPES_TO_GET ) ) ).thenReturn( resultSetMock ); Database db = new Database( mockLogger(), dbMetaMock ); db.setConnection( mockConnection( dbMetaDataMock ) ); assertTrue( "The table " + EXISTING_TABLE_NAME + " is not in db meta data but should be here", db.checkTableExistsByDbMeta( SCHEMA_TO_CHECK, EXISTING_TABLE_NAME ) ); } @Test public void testCheckTableNotExistsByDbMeta() throws Exception { when( resultSetMock.next() ).thenReturn( true, false ); when( resultSetMock.getString( "TABLE_NAME" ) ).thenReturn( EXISTING_TABLE_NAME ); when( dbMetaDataMock.getTables( any(), anyString(), anyString(), aryEq( TABLE_TYPES_TO_GET ) ) ).thenReturn( resultSetMock ); Database db = new Database( mockLogger(), dbMetaMock ); db.setConnection( mockConnection( dbMetaDataMock ) ); assertFalse( "The table " + NOT_EXISTING_TABLE_NAME + " is in db meta data but should not be here", db.checkTableExistsByDbMeta( SCHEMA_TO_CHECK, NOT_EXISTING_TABLE_NAME ) ); } @Test public void testCheckTableExistsByDbMetaThrowsKettleDatabaseException() { KettleDatabaseException kettleDatabaseException = new KettleDatabaseException( "Unable to check if table [" + EXISTING_TABLE_NAME + "] exists on connection [" + TEST_NAME_OF_DB_CONNECTION + "].", SQL_EXCEPTION ); try { when( dbMetaMock.getName() ).thenReturn( TEST_NAME_OF_DB_CONNECTION ); when( resultSetMock.next() ).thenReturn( true, false ); when( resultSetMock.getString( "TABLE_NAME" ) ).thenThrow( SQL_EXCEPTION ); when( dbMetaDataMock.getTables( any(), anyString(), anyString(), aryEq( TABLE_TYPES_TO_GET ) ) ).thenReturn( resultSetMock ); Database db = new Database( mockLogger(), dbMetaMock ); db.setConnection( mockConnection( dbMetaDataMock ) ); db.checkTableExistsByDbMeta( SCHEMA_TO_CHECK, EXISTING_TABLE_NAME ); fail( "There should be thrown KettleDatabaseException but was not." ); } catch ( KettleDatabaseException e ) { assertTrue( e instanceof KettleDatabaseException ); assertEquals( kettleDatabaseException.getLocalizedMessage(), e.getLocalizedMessage() ); } catch ( Exception ex ) { fail( "There should be thrown KettleDatabaseException but was :" + ex.getMessage() ); } } @Test public void testCheckTableExistsByDbMetaThrowsKettleDatabaseException_WhenDbMetaNull() { KettleDatabaseException kettleDatabaseException = new KettleDatabaseException( "Unable to get database meta-data from the database." ); try { when( resultSetMock.next() ).thenReturn( true, false ); when( dbMetaDataMock.getTables( any(), anyString(), anyString(), aryEq( TABLE_TYPES_TO_GET ) ) ).thenReturn( resultSetMock ); Database db = new Database( mockLogger(), dbMetaMock ); db.setConnection( mockConnection( null ) ); db.checkTableExistsByDbMeta( SCHEMA_TO_CHECK, EXISTING_TABLE_NAME ); fail( "There should be thrown KettleDatabaseException but was not." ); } catch ( KettleDatabaseException e ) { assertTrue( e instanceof KettleDatabaseException ); assertEquals( kettleDatabaseException.getLocalizedMessage(), e.getLocalizedMessage() ); } catch ( Exception ex ) { fail( "There should be thrown KettleDatabaseException but was :" + ex.getMessage() ); } } @Test public void testCheckTableExistsByDbMetaThrowsKettleDatabaseException_WhenUnableToGetTableNames() { KettleDatabaseException kettleDatabaseException = new KettleDatabaseException( "Unable to get table-names from the database meta-data.", SQL_EXCEPTION ); try { when( resultSetMock.next() ).thenReturn( true, false ); when( dbMetaDataMock.getTables( any(), anyString(), anyString(), aryEq( TABLE_TYPES_TO_GET ) ) ).thenThrow( SQL_EXCEPTION ); Database db = new Database( mockLogger(), dbMetaMock ); db.setConnection( mockConnection( dbMetaDataMock ) ); db.checkTableExistsByDbMeta( SCHEMA_TO_CHECK, EXISTING_TABLE_NAME ); fail( "There should be thrown KettleDatabaseException but was not." ); } catch ( KettleDatabaseException e ) { assertTrue( e instanceof KettleDatabaseException ); assertEquals( kettleDatabaseException.getLocalizedMessage(), e.getLocalizedMessage() ); } catch ( Exception ex ) { fail( "There should be thrown KettleDatabaseException but was :" + ex.getMessage() ); } } @Test public void testCheckTableExistsByDbMetaThrowsKettleDatabaseException_WhenResultSetNull() { KettleDatabaseException kettleDatabaseException = new KettleDatabaseException( "Unable to get table-names from the database meta-data." ); try { when( resultSetMock.next() ).thenReturn( true, false ); when( dbMetaDataMock.getTables( any(), anyString(), anyString(), aryEq( TABLE_TYPES_TO_GET ) ) ).thenReturn( null ); Database db = new Database( mockLogger(), dbMetaMock ); db.setConnection( mockConnection( dbMetaDataMock ) ); db.checkTableExistsByDbMeta( SCHEMA_TO_CHECK, EXISTING_TABLE_NAME ); fail( "There should be thrown KettleDatabaseException but was not." ); } catch ( KettleDatabaseException e ) { assertTrue( e instanceof KettleDatabaseException ); assertEquals( kettleDatabaseException.getLocalizedMessage(), e.getLocalizedMessage() ); } catch ( Exception ex ) { fail( "There should be thrown KettleDatabaseException but was :" + ex.getMessage() ); } } @Test public void mySqlVarBinaryIsConvertedToStringType() throws Exception { ResultSetMetaData rsMeta = mock( ResultSetMetaData.class ); when( rsMeta.getColumnCount() ).thenReturn( 1 ); when( rsMeta.getColumnLabel( 1 ) ).thenReturn( "column" ); when( rsMeta.getColumnName( 1 ) ).thenReturn( "column" ); when( rsMeta.getColumnType( 1 ) ).thenReturn( java.sql.Types.VARBINARY ); ResultSet rs = mock( ResultSet.class ); when( rs.getMetaData() ).thenReturn( rsMeta ); PreparedStatement ps = mock( PreparedStatement.class ); when( ps.executeQuery() ).thenReturn( rs ); DatabaseMeta meta = new DatabaseMeta(); meta.setDatabaseInterface( new MySQLDatabaseMeta() ); DatabaseMetaData dbMetaData = mock( DatabaseMetaData.class ); Database db = new Database( log, meta ); db.setConnection( mockConnection( dbMetaData ) ); db.getLookup( ps, false, true ); RowMetaInterface rowMeta = db.getReturnRowMeta(); assertEquals( 1, rowMeta.size() ); ValueMetaInterface valueMeta = rowMeta.getValueMeta( 0 ); assertEquals( ValueMetaInterface.TYPE_STRING, valueMeta.getType() ); assertEquals( ValueMetaInterface.STORAGE_TYPE_BINARY_STRING, valueMeta.getStorageType() ); } private static String concatWordsForRegexp( String... words ) { String emptySpace = "\\s*"; StringBuilder sb = new StringBuilder( emptySpace ); for ( String word : words ) { sb.append( word ).append( emptySpace ); } return sb.toString(); } private static LoggingObjectInterface mockLogger() { LoggingObjectInterface logger = mock( LoggingObjectInterface.class ); when( logger.getLogLevel() ).thenReturn( LogLevel.NOTHING ); return logger; } private static Connection mockConnection( DatabaseMetaData dbMetaData ) throws SQLException { Connection connection = mock( Connection.class ); when( connection.getMetaData() ).thenReturn( dbMetaData ); return connection; } @Test public void usesCustomDsProviderIfSet_Pooling() throws Exception { DatabaseMeta meta = new DatabaseMeta(); meta.setUsingConnectionPool( true ); testUsesCustomDsProviderIfSet( meta ); } @Test public void usesCustomDsProviderIfSet_Jndi() throws Exception { DatabaseMeta meta = new DatabaseMeta(); meta.setAccessType( DatabaseMeta.TYPE_ACCESS_JNDI ); testUsesCustomDsProviderIfSet( meta ); } private DataSourceProviderInterface testUsesCustomDsProviderIfSet( DatabaseMeta meta ) throws Exception { Connection connection = mock( Connection.class ); DataSource ds = mock( DataSource.class ); when( ds.getConnection() ).thenReturn( connection ); when( ds.getConnection( anyString(), anyString() ) ).thenReturn( connection ); DataSourceProviderInterface provider = mock( DataSourceProviderInterface.class ); when( provider.getNamedDataSource( anyString(), any( DataSourceProviderInterface.DatasourceType.class ) ) ) .thenReturn( ds ); Database db = new Database( log, meta ); final DataSourceProviderInterface existing = DataSourceProviderFactory.getDataSourceProviderInterface(); try { DataSourceProviderFactory.setDataSourceProviderInterface( provider ); db.normalConnect( null ); } finally { DataSourceProviderFactory.setDataSourceProviderInterface( existing ); } assertEquals( connection, db.getConnection() ); return provider; } @Test public void jndiAccessTypePrevailsPooled() throws Exception { // this test is a guard of Database.normalConnect() contract: // it firstly tries to use JNDI name DatabaseMeta meta = new DatabaseMeta(); meta.setAccessType( DatabaseMeta.TYPE_ACCESS_JNDI ); meta.setUsingConnectionPool( true ); DataSourceProviderInterface provider = testUsesCustomDsProviderIfSet( meta ); verify( provider ).getNamedDataSource( anyString(), eq( DatasourceType.JNDI ) ); verify( provider, never() ).getNamedDataSource( anyString(), eq( DatasourceType.POOLED ) ); } @Test public void testDisconnectPstmCloseFail() throws SQLException, KettleDatabaseException, NoSuchFieldException, IllegalAccessException { DatabaseMeta dbMeta = mock( DatabaseMeta.class ); DatabaseMetaData dbMetaData = mock( DatabaseMetaData.class ); Database db = new Database( mockLogger(), dbMeta ); Connection connection = mockConnection( dbMetaData ); db.setConnection( connection ); db.setCommit( 1 ); PreparedStatement ps = mock( PreparedStatement.class ); Class<Database> databaseClass = Database.class; Field fieldPstmt = databaseClass.getDeclaredField( "pstmt" ); fieldPstmt.setAccessible( true ); fieldPstmt.set( db, ps ); Mockito.doThrow( new SQLException( "Test SQL exception" ) ).when( ps ).close(); db.disconnect(); verify( connection, times( 1 ) ).close(); } @Test public void testDisconnectCommitFail() throws SQLException, NoSuchFieldException, IllegalAccessException { DatabaseMeta dbMeta = mock( DatabaseMeta.class ); when( dbMeta.supportsEmptyTransactions() ).thenReturn( true ); DatabaseMetaData dbMetaData = mock( DatabaseMetaData.class ); when( dbMetaData.supportsTransactions() ).thenReturn( true ); Database db = new Database( mockLogger(), dbMeta ); Connection connection = mockConnection( dbMetaData ); db.setConnection( connection ); db.setCommit( 1 ); PreparedStatement ps = mock( PreparedStatement.class ); Class<Database> databaseClass = Database.class; Field fieldPstmt = databaseClass.getDeclaredField( "pstmt" ); fieldPstmt.setAccessible( true ); fieldPstmt.set( db, ps ); Mockito.doThrow( new SQLException( "Test SQL exception" ) ).when( connection ).commit(); db.disconnect(); verify( connection, times( 1 ) ).close(); } @Test public void testDisconnectConnectionGroup() throws SQLException { DatabaseMeta dbMeta = mock( DatabaseMeta.class ); DatabaseMetaData dbMetaData = mock( DatabaseMetaData.class ); Database db = new Database( mockLogger(), dbMeta ); Connection connection = mockConnection( dbMetaData ); db.setConnection( connection ); db.setConnectionGroup( "1" ); db.disconnect(); verify( connection, never() ).close(); } }