/*! ******************************************************************************
*
* 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.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import java.sql.DatabaseMetaData;
import java.sql.Date;
import java.sql.ResultSet;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mockito;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import org.pentaho.di.core.KettleClientEnvironment;
import org.pentaho.di.core.row.value.ValueMetaDate;
import org.pentaho.di.core.row.value.ValueMetaString;
import org.pentaho.di.core.variables.Variables;
import org.pentaho.di.repository.LongObjectId;
public class BaseDatabaseMetaTest {
BaseDatabaseMeta nativeMeta, odbcMeta, jndiMeta;
@Before
public void setupOnce() throws Exception {
nativeMeta = new ConcreteBaseDatabaseMeta();
nativeMeta.setAccessType( DatabaseMeta.TYPE_ACCESS_NATIVE );
odbcMeta = new ConcreteBaseDatabaseMeta();
nativeMeta.setAccessType( DatabaseMeta.TYPE_ACCESS_ODBC );
jndiMeta = new ConcreteBaseDatabaseMeta();
KettleClientEnvironment.init();
}
@Test
public void testShowIsTreatedAsAResultsQuery() throws Exception {
List<SqlScriptStatement> sqlScriptStatements =
new H2DatabaseMeta().getSqlScriptStatements( "show annotations from service" );
assertTrue( sqlScriptStatements.get( 0 ).isQuery() );
}
@Test
public void testDefaultSettings() throws Exception {
// Note - this method should only use native or odbc.
// The jndi meta is used for mutations of the meta, and it would
// not be threadsafe in a multi-threaded testing environment
// (each test run in its own thread).
assertEquals( -1, nativeMeta.getDefaultDatabasePort() );
assertTrue( nativeMeta.supportsSetCharacterStream() );
assertTrue( nativeMeta.supportsAutoInc() );
assertEquals( "", nativeMeta.getLimitClause( 5 ) );
assertEquals( 0, nativeMeta.getNotFoundTK( true ) );
assertEquals( "", nativeMeta.getSQLNextSequenceValue( "FOO" ) );
assertEquals( "", nativeMeta.getSQLCurrentSequenceValue( "FOO" ) );
assertEquals( "", nativeMeta.getSQLSequenceExists( "FOO" ) );
assertTrue( nativeMeta.isFetchSizeSupported() );
assertFalse( nativeMeta.needsPlaceHolder() );
assertTrue( nativeMeta.supportsSchemas() );
assertTrue( nativeMeta.supportsCatalogs() );
assertTrue( nativeMeta.supportsEmptyTransactions() );
assertEquals( "SUM", nativeMeta.getFunctionSum() );
assertEquals( "AVG", nativeMeta.getFunctionAverage() );
assertEquals( "MIN", nativeMeta.getFunctionMinimum() );
assertEquals( "MAX", nativeMeta.getFunctionMaximum() );
assertEquals( "COUNT", nativeMeta.getFunctionCount() );
assertEquals( "\"", nativeMeta.getStartQuote() );
assertEquals( "\"", nativeMeta.getEndQuote() );
assertEquals( "FOO.BAR", nativeMeta.getSchemaTableCombination( "FOO", "BAR" ) );
assertEquals( DatabaseMeta.CLOB_LENGTH, nativeMeta.getMaxTextFieldLength() );
assertEquals( DatabaseMeta.CLOB_LENGTH, nativeMeta.getMaxVARCHARLength() );
assertTrue( nativeMeta.supportsTransactions() );
assertFalse( nativeMeta.supportsSequences() );
assertTrue( nativeMeta.supportsBitmapIndex() );
assertTrue( nativeMeta.supportsSetLong() );
assertArrayEquals( new String[] {}, nativeMeta.getReservedWords() );
assertTrue( nativeMeta.quoteReservedWords() );
assertFalse( nativeMeta.supportsRepository() );
assertArrayEquals( new String[] { "TABLE" }, nativeMeta.getTableTypes() );
assertArrayEquals( new String[] { "VIEW" }, nativeMeta.getViewTypes() );
assertArrayEquals( new String[] { "SYNONYM" }, nativeMeta.getSynonymTypes() );
assertFalse( nativeMeta.useSchemaNameForTableList() );
assertTrue( nativeMeta.supportsViews() );
assertFalse( nativeMeta.supportsSynonyms() );
assertNull( nativeMeta.getSQLListOfProcedures() );
assertNull( nativeMeta.getSQLListOfSequences() );
assertTrue( nativeMeta.supportsFloatRoundingOnUpdate() );
assertNull( nativeMeta.getSQLLockTables( new String[] { "FOO" } ) );
assertNull( nativeMeta.getSQLUnlockTables( new String[] { "FOO" } ) );
assertTrue( nativeMeta.supportsTimeStampToDateConversion() );
assertTrue( nativeMeta.supportsBatchUpdates() );
assertFalse( nativeMeta.supportsBooleanDataType() );
assertFalse( nativeMeta.supportsTimestampDataType() );
assertTrue( nativeMeta.preserveReservedCase() );
assertTrue( nativeMeta.isDefaultingToUppercase() );
Map<String, String> emptyMap = new HashMap<String, String>();
assertEquals( emptyMap, nativeMeta.getExtraOptions() );
assertEquals( ";", nativeMeta.getExtraOptionSeparator() );
assertEquals( "=", nativeMeta.getExtraOptionValueSeparator() );
assertEquals( ";", nativeMeta.getExtraOptionIndicator() );
assertTrue( nativeMeta.supportsOptionsInURL() );
assertNull( nativeMeta.getExtraOptionsHelpText() );
assertTrue( nativeMeta.supportsGetBlob() );
assertNull( nativeMeta.getConnectSQL() );
assertTrue( nativeMeta.supportsSetMaxRows() );
assertFalse( nativeMeta.isUsingConnectionPool() );
assertEquals( ConnectionPoolUtil.defaultMaximumNrOfConnections, nativeMeta.getMaximumPoolSize() );
assertEquals( ConnectionPoolUtil.defaultInitialNrOfConnections, nativeMeta.getInitialPoolSize() );
assertFalse( nativeMeta.isPartitioned() );
assertArrayEquals( new PartitionDatabaseMeta[0], nativeMeta.getPartitioningInformation() );
Properties emptyProps = new Properties();
assertEquals( emptyProps, nativeMeta.getConnectionPoolingProperties() );
assertTrue( nativeMeta.needsToLockAllTables() );
assertTrue( nativeMeta.isStreamingResults() );
assertFalse( nativeMeta.isQuoteAllFields() );
assertFalse( nativeMeta.isForcingIdentifiersToLowerCase() );
assertFalse( nativeMeta.isForcingIdentifiersToUpperCase() );
assertFalse( nativeMeta.isUsingDoubleDecimalAsSchemaTableSeparator() );
assertTrue( nativeMeta.isRequiringTransactionsOnQueries() );
assertEquals( "org.pentaho.di.core.database.DatabaseFactory", nativeMeta.getDatabaseFactoryName() );
assertNull( nativeMeta.getPreferredSchemaName() );
assertFalse( nativeMeta.supportsSequenceNoMaxValueOption() );
assertFalse( nativeMeta.requiresCreateTablePrimaryKeyAppend() );
assertFalse( nativeMeta.requiresCastToVariousForIsNull() );
assertFalse( nativeMeta.isDisplaySizeTwiceThePrecision() );
assertTrue( nativeMeta.supportsPreparedStatementMetadataRetrieval() );
assertFalse( nativeMeta.supportsResultSetMetadataRetrievalOnly() );
assertFalse( nativeMeta.isSystemTable( "FOO" ) );
assertTrue( nativeMeta.supportsNewLinesInSQL() );
assertNull( nativeMeta.getSQLListOfSchemas() );
assertEquals( 0, nativeMeta.getMaxColumnsInIndex() );
assertTrue( nativeMeta.supportsErrorHandlingOnBatchUpdates() );
assertTrue( nativeMeta.isExplorable() );
assertNull( nativeMeta.getXulOverlayFile() );
assertTrue( nativeMeta.onlySpaces( " \t \n \r " ) );
assertFalse( nativeMeta.isMySQLVariant() );
assertTrue( nativeMeta.canTest() );
assertTrue( nativeMeta.requiresName() );
assertTrue( nativeMeta.releaseSavepoint() );
Variables v = new Variables();
v.setVariable( "FOOVARIABLE", "FOOVALUE" );
DatabaseMeta dm = new DatabaseMeta();
dm.setDatabaseInterface( nativeMeta );
assertEquals( "", nativeMeta.getDataTablespaceDDL( v, dm ) );
assertEquals( "", nativeMeta.getIndexTablespaceDDL( v, dm ) );
assertFalse( nativeMeta.useSafePoints() );
assertTrue( nativeMeta.supportsErrorHandling() );
assertEquals( "'DATA'", nativeMeta.getSQLValue( new ValueMetaString( "FOO" ), "DATA", null ) );
assertEquals( "'15'", nativeMeta.getSQLValue( new ValueMetaString( "FOO" ), "15", null ) );
assertEquals( "_", nativeMeta.getFieldnameProtector() );
assertEquals( "_1ABC_123", nativeMeta.getSafeFieldname( "1ABC 123" ) );
BaseDatabaseMeta tmpSC = new ConcreteBaseDatabaseMeta( ) {
@Override
public String[] getReservedWords() {
return new String[] { "SELECT" };
}
};
assertEquals( "SELECT_", tmpSC.getSafeFieldname( "SELECT" ) );
assertEquals( "NOMAXVALUE", nativeMeta.getSequenceNoMaxValueOption() );
assertTrue( nativeMeta.supportsAutoGeneratedKeys() );
assertNull( nativeMeta.customizeValueFromSQLType( new ValueMetaString( "FOO" ), null, 0 ) );
assertTrue( nativeMeta.fullExceptionLog( new RuntimeException( "xxxx" ) ) );
}
@SuppressWarnings( "deprecation" )
@Test
public void testDeprecatedItems() throws Exception {
assertEquals( "'2016-08-11'", nativeMeta.getSQLValue( new ValueMetaDate( "FOO" ), new Date( 116, 7, 11 ), "YYYY-MM-dd" ) );
assertEquals( "\"FOO\".\"BAR\"", nativeMeta.getBackwardsCompatibleSchemaTableCombination( "FOO", "BAR" ) );
assertEquals( "\"null\".\"BAR\"", nativeMeta.getBackwardsCompatibleSchemaTableCombination( null, "BAR" ) ); // not sure this is right ...
assertEquals( "FOO\".\"BAR\"", nativeMeta.getBackwardsCompatibleSchemaTableCombination( "FOO\"", "BAR" ) );
assertEquals( "FOO\".BAR\"", nativeMeta.getBackwardsCompatibleSchemaTableCombination( "FOO\"", "BAR\"" ) );
assertEquals( "\"FOO\"", nativeMeta.getBackwardsCompatibleTable( "FOO" ) );
assertEquals( "\"null\"", nativeMeta.getBackwardsCompatibleTable( null ) ); // not sure this should happen but it does
assertEquals( "FOO\"", nativeMeta.getBackwardsCompatibleTable( "FOO\"" ) );
assertEquals( "\"FOO", nativeMeta.getBackwardsCompatibleTable( "\"FOO" ) );
}
@Test
public void testDefaultSQLStatements() {
// Note - this method should use only native or odbc metas.
// Use of the jndi meta here could create a race condition
// when test cases are run by multiple threads
String lineSep = System.getProperty( "line.separator" );
String expected = "ALTER TABLE FOO DROP BAR" + lineSep;
assertEquals( expected, odbcMeta.getDropColumnStatement( "FOO", new ValueMetaString( "BAR" ), "", false, "", false ) );
assertEquals( "TRUNCATE TABLE FOO", odbcMeta.getTruncateTableStatement( "FOO" ) );
assertEquals( "SELECT * FROM FOO", odbcMeta.getSQLQueryFields( "FOO" ) );
assertEquals( "SELECT 1 FROM FOO", odbcMeta.getSQLTableExists( "FOO" ) );
assertEquals( "SELECT FOO FROM BAR", odbcMeta.getSQLColumnExists( "FOO", "BAR" ) );
assertEquals( "insert into \"FOO\".\"BAR\"(KEYFIELD, VERSIONFIELD) values (0, 1)",
nativeMeta.getSQLInsertAutoIncUnknownDimensionRow( "\"FOO\".\"BAR\"", "KEYFIELD", "VERSIONFIELD" ) );
assertEquals( "select count(*) FROM FOO", nativeMeta.getSelectCountStatement( "FOO" ) );
assertEquals( "COL9", nativeMeta.generateColumnAlias( 9, "FOO" ) );
assertEquals( "[SELECT 1, INSERT INTO FOO VALUES(BAR), DELETE FROM BAR]", nativeMeta.parseStatements( "SELECT 1;INSERT INTO FOO VALUES(BAR);DELETE FROM BAR" ).toString() );
assertEquals( "CREATE TABLE ", nativeMeta.getCreateTableStatement() );
assertEquals( "DROP TABLE IF EXISTS FOO", nativeMeta.getDropTableIfExistsStatement( "FOO" ) );
}
@Test
public void testGettersSetters() {
// Note - this method should *ONLY* use the jndi meta and not the odbc or native ones.
// This is the only method in this test class that mutates the meta.
jndiMeta.setUsername( "FOO" );
assertEquals( "FOO", jndiMeta.getUsername() );
jndiMeta.setPassword( "BAR" );
assertEquals( "BAR", jndiMeta.getPassword() );
jndiMeta.setAccessType( DatabaseMeta.TYPE_ACCESS_JNDI );
assertEquals( "", jndiMeta.getUsername() );
assertEquals( "", jndiMeta.getPassword() );
assertFalse( jndiMeta.isChanged() );
jndiMeta.setChanged( true );
assertTrue( jndiMeta.isChanged() );
jndiMeta.setName( "FOO" );
assertEquals( "FOO", jndiMeta.getName() );
assertEquals( "FOO", jndiMeta.getDisplayName() );
jndiMeta.setName( null );
assertNull( jndiMeta.getName() );
assertEquals( "FOO", jndiMeta.getDisplayName() );
jndiMeta.setDisplayName( null );
assertNull( jndiMeta.getDisplayName() );
jndiMeta.setDatabaseName( "FOO" );
assertEquals( "FOO", jndiMeta.getDatabaseName() );
assertEquals( "-1", jndiMeta.getDatabasePortNumberString() );
jndiMeta.setDatabasePortNumberString( "9876" );
assertEquals( "9876", jndiMeta.getDatabasePortNumberString() );
jndiMeta.setDatabasePortNumberString( null );
assertEquals( "9876", jndiMeta.getDatabasePortNumberString() ); // not sure I agree with this behavior
jndiMeta.setHostname( "FOO" );
assertEquals( "FOO", jndiMeta.getHostname() );
LongObjectId id = new LongObjectId( 9876 );
jndiMeta.setObjectId( id );
assertEquals( id, jndiMeta.getObjectId() );
jndiMeta.setServername( "FOO" );
assertEquals( "FOO", jndiMeta.getServername() );
jndiMeta.setDataTablespace( "FOO" );
assertEquals( "FOO", jndiMeta.getDataTablespace() );
jndiMeta.setIndexTablespace( "FOO" );
assertEquals( "FOO", jndiMeta.getIndexTablespace() );
Properties attrs = jndiMeta.getAttributes();
Properties testAttrs = new Properties();
testAttrs.setProperty( "FOO", "BAR" );
jndiMeta.setAttributes( testAttrs );
assertEquals( testAttrs, jndiMeta.getAttributes() );
jndiMeta.setAttributes( attrs ); // reset attributes back to what they were...
jndiMeta.setSupportsBooleanDataType( true );
assertTrue( jndiMeta.supportsBooleanDataType() );
jndiMeta.setSupportsTimestampDataType( true );
assertTrue( jndiMeta.supportsTimestampDataType() );
jndiMeta.setPreserveReservedCase( false );
assertFalse( jndiMeta.preserveReservedCase() );
jndiMeta.addExtraOption( "JNDI", "FOO", "BAR" );
Map<String, String> expectedOptionsMap = new HashMap<String, String>();
expectedOptionsMap.put( "JNDI.FOO", "BAR" );
assertEquals( expectedOptionsMap, jndiMeta.getExtraOptions() );
jndiMeta.setConnectSQL( "SELECT COUNT(*) FROM FOO" );
assertEquals( "SELECT COUNT(*) FROM FOO", jndiMeta.getConnectSQL() );
jndiMeta.setUsingConnectionPool( true );
assertTrue( jndiMeta.isUsingConnectionPool() );
jndiMeta.setMaximumPoolSize( 15 );
assertEquals( 15, jndiMeta.getMaximumPoolSize() );
jndiMeta.setInitialPoolSize( 5 );
assertEquals( 5, jndiMeta.getInitialPoolSize() );
jndiMeta.setPartitioned( true );
assertTrue( jndiMeta.isPartitioned() );
PartitionDatabaseMeta[] clusterInfo = new PartitionDatabaseMeta[1];
PartitionDatabaseMeta aClusterDef = new PartitionDatabaseMeta( "FOO", "BAR", "WIBBLE", "NATTIE" );
aClusterDef.setUsername( "FOOUSER" );
aClusterDef.setPassword( "BARPASSWORD" );
clusterInfo[0] = aClusterDef;
jndiMeta.setPartitioningInformation( clusterInfo );
PartitionDatabaseMeta[] gotPartitions = jndiMeta.getPartitioningInformation();
// MB: Can't use arrayEquals because the PartitionDatabaseMeta doesn't have a toString. :(
// assertArrayEquals( clusterInfo, gotPartitions );
assertTrue( gotPartitions != null );
if ( gotPartitions != null ) {
assertEquals( 1, gotPartitions.length );
PartitionDatabaseMeta compareWith = gotPartitions[0];
// MB: Can't use x.equals(y) because PartitionDatabaseMeta doesn't override equals... :(
assertEquals( aClusterDef.getClass(), compareWith.getClass() );
assertEquals( aClusterDef.getDatabaseName(), compareWith.getDatabaseName() );
assertEquals( aClusterDef.getHostname(), compareWith.getHostname() );
assertEquals( aClusterDef.getPartitionId(), compareWith.getPartitionId() );
assertEquals( aClusterDef.getPassword(), compareWith.getPassword() );
assertEquals( aClusterDef.getPort(), compareWith.getPort() );
assertEquals( aClusterDef.getUsername(), compareWith.getUsername() );
}
Properties poolProperties = new Properties();
poolProperties.put( "FOO", "BAR" );
poolProperties.put( "BAR", "FOO" );
poolProperties.put( "ZZZZZZZZZZZZZZ", "Z.Z.Z.Z.Z.Z.Z.Z.a.a.a.a.a.a.a.a.a" );
poolProperties.put( "TOM", "JANE" );
poolProperties.put( "AAAAAAAAAAAAA", "BBBBB.BBB.BBBBBBB.BBBBBBBB.BBBBBBBBBBBBBB" );
jndiMeta.setConnectionPoolingProperties( poolProperties );
Properties compareWithProps = jndiMeta.getConnectionPoolingProperties();
assertEquals( poolProperties, compareWithProps );
jndiMeta.setStreamingResults( false );
assertFalse( jndiMeta.isStreamingResults() );
jndiMeta.setQuoteAllFields( true );
jndiMeta.setForcingIdentifiersToLowerCase( true );
jndiMeta.setForcingIdentifiersToUpperCase( true );
assertTrue( jndiMeta.isQuoteAllFields() );
assertTrue( jndiMeta.isForcingIdentifiersToLowerCase() );
assertTrue( jndiMeta.isForcingIdentifiersToUpperCase() );
jndiMeta.setUsingDoubleDecimalAsSchemaTableSeparator( true );
assertTrue( jndiMeta.isUsingDoubleDecimalAsSchemaTableSeparator() );
jndiMeta.setPreferredSchemaName( "FOO" );
assertEquals( "FOO", jndiMeta.getPreferredSchemaName() );
}
private int rowCnt = 0;
@Test
public void testCheckIndexExists() throws Exception {
Database db = Mockito.mock( Database.class );
ResultSet rs = Mockito.mock( ResultSet.class );
DatabaseMetaData dmd = Mockito.mock( DatabaseMetaData.class );
DatabaseMeta dm = Mockito.mock( DatabaseMeta.class );
Mockito.when( dm.getQuotedSchemaTableCombination( "", "FOO" ) ).thenReturn( "FOO" );
Mockito.when( rs.next() ).thenAnswer( new Answer<Boolean>() {
public Boolean answer( InvocationOnMock invocation ) throws Throwable {
rowCnt++;
return new Boolean( rowCnt < 3 );
}
} );
Mockito.when( db.getDatabaseMetaData() ).thenReturn( dmd );
Mockito.when( dmd.getIndexInfo( null, null, "FOO", false, true ) ).thenReturn( rs );
Mockito.when( rs.getString( "COLUMN_NAME" ) ).thenAnswer( new Answer<String>() {
@Override
public String answer( InvocationOnMock invocation ) throws Throwable {
if ( rowCnt == 1 ) {
return "ROW1COL2";
} else if ( rowCnt == 2 ) {
return "ROW2COL2";
} else {
return null;
}
}
} );
Mockito.when( db.getDatabaseMeta() ).thenReturn( dm );
assertTrue( odbcMeta.checkIndexExists( db, "", "FOO", new String[] { "ROW1COL2", "ROW2COL2" } ) );
assertFalse( odbcMeta.checkIndexExists( db, "", "FOO", new String[] { "ROW2COL2", "NOTTHERE" } ) );
assertFalse( odbcMeta.checkIndexExists( db, "", "FOO", new String[] { "NOTTHERE", "ROW1COL2" } ) );
}
}