// CHECKSTYLE:FileLength:OFF /*! ****************************************************************************** * * Pentaho Data Integration * * Copyright (C) 2002-2017 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 org.pentaho.di.core.Const; import org.pentaho.di.core.util.Utils; import org.pentaho.di.core.RowMetaAndData; import org.pentaho.di.core.encryption.Encr; import org.pentaho.di.core.exception.KettleDatabaseException; import org.pentaho.di.core.exception.KettlePluginException; import org.pentaho.di.core.exception.KettleValueException; import org.pentaho.di.core.exception.KettleXMLException; import org.pentaho.di.core.logging.LogChannel; import org.pentaho.di.core.logging.LogChannelInterface; import org.pentaho.di.core.plugins.DatabasePluginType; import org.pentaho.di.core.plugins.PluginInterface; import org.pentaho.di.core.plugins.PluginRegistry; import org.pentaho.di.core.row.RowMetaInterface; import org.pentaho.di.core.row.ValueMetaInterface; import org.pentaho.di.core.row.value.ValueMetaBase; import org.pentaho.di.core.row.value.ValueMetaString; import org.pentaho.di.core.util.ExecutorUtil; import org.pentaho.di.core.variables.VariableSpace; import org.pentaho.di.core.variables.Variables; import org.pentaho.di.core.xml.XMLHandler; import org.pentaho.di.core.xml.XMLInterface; import org.pentaho.di.i18n.BaseMessages; import org.pentaho.di.repository.ObjectId; import org.pentaho.di.repository.ObjectRevision; import org.pentaho.di.repository.RepositoryDirectory; import org.pentaho.di.repository.RepositoryDirectoryInterface; import org.pentaho.di.repository.RepositoryElementInterface; import org.pentaho.di.repository.RepositoryObjectType; import org.pentaho.di.shared.SharedObjectBase; import org.pentaho.di.shared.SharedObjectInterface; import org.w3c.dom.Node; import java.sql.ResultSet; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.Enumeration; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.Set; import java.util.concurrent.Callable; import java.util.concurrent.Future; /** * This class defines the database specific parameters for a certain database type. It also provides static information * regarding a number of well known databases. * * @author Matt * @since 18-05-2003 * */ public class DatabaseMeta extends SharedObjectBase implements Cloneable, XMLInterface, SharedObjectInterface, VariableSpace, RepositoryElementInterface { private static Class<?> PKG = Database.class; // for i18n purposes, needed by Translator2!! public static final String XML_TAG = "connection"; public static final RepositoryObjectType REPOSITORY_ELEMENT_TYPE = RepositoryObjectType.DATABASE; private static final String DROP_TABLE_STATEMENT = "DROP TABLE IF EXISTS "; // Comparator for sorting databases alphabetically by name public static final Comparator<DatabaseMeta> comparator = new Comparator<DatabaseMeta>() { @Override public int compare( DatabaseMeta dbm1, DatabaseMeta dbm2 ) { return dbm1.getName().compareToIgnoreCase( dbm2.getName() ); } }; private DatabaseInterface databaseInterface; private static volatile Future<Map<String, DatabaseInterface>> allDatabaseInterfaces; static { PluginRegistry.getInstance().addPluginListener( DatabasePluginType.class, new org.pentaho.di.core.plugins.PluginTypeListener() { @Override public void pluginAdded( Object serviceObject ) { clearDatabaseInterfacesMap(); } @Override public void pluginRemoved( Object serviceObject ) { clearDatabaseInterfacesMap(); } @Override public void pluginChanged( Object serviceObject ) { clearDatabaseInterfacesMap(); } } ); } private VariableSpace variables = new Variables(); private ObjectRevision objectRevision; private boolean readOnly = false; /** * Indicates that the connections doesn't point to a type of database yet. * * @deprecated */ @Deprecated public static final int TYPE_DATABASE_NONE = 0; /** * Connection to a MySQL database * * @deprecated */ @Deprecated public static final int TYPE_DATABASE_MYSQL = 1; /** * Connection to an Oracle database * * @deprecated */ @Deprecated public static final int TYPE_DATABASE_ORACLE = 2; /** * Connection to an AS/400 (IBM iSeries) DB400 database * * @deprecated */ @Deprecated public static final int TYPE_DATABASE_AS400 = 3; /** * Connection to an Microsoft Access database * * @deprecated */ @Deprecated public static final int TYPE_DATABASE_ACCESS = 4; /** * Connection to a Microsoft SQL Server database * * @deprecated */ @Deprecated public static final int TYPE_DATABASE_MSSQL = 5; /** * Connection to an IBM DB2 database * * @deprecated */ @Deprecated public static final int TYPE_DATABASE_DB2 = 6; /** * Connection to a PostgreSQL database * * @deprecated */ @Deprecated public static final int TYPE_DATABASE_POSTGRES = 7; /** * Connection to an Intersystems Cache database * * @deprecated */ @Deprecated public static final int TYPE_DATABASE_CACHE = 8; /** * Connection to an IBM Informix database * * @deprecated */ @Deprecated public static final int TYPE_DATABASE_INFORMIX = 9; /** * Connection to a Sybase ASE database * * @deprecated */ @Deprecated public static final int TYPE_DATABASE_SYBASE = 10; /** * Connection to a Gupta SQLBase database * * @deprecated */ @Deprecated public static final int TYPE_DATABASE_GUPTA = 11; /** * Connection to a DBase III/IV/V database through JDBC * * @deprecated */ @Deprecated public static final int TYPE_DATABASE_DBASE = 12; /** * Connection to a FireBird database * * @deprecated */ @Deprecated public static final int TYPE_DATABASE_FIREBIRD = 13; /** * Connection to a SAP DB database * * @deprecated */ @Deprecated public static final int TYPE_DATABASE_SAPDB = 14; /** * Connection to a Hypersonic java database * * @deprecated */ @Deprecated public static final int TYPE_DATABASE_HYPERSONIC = 15; /** * Connection to a generic database * * @deprecated */ @Deprecated public static final int TYPE_DATABASE_GENERIC = 16; /** * Connection to an SAP R/3 system * * @deprecated */ @Deprecated public static final int TYPE_DATABASE_SAPR3 = 17; /** * Connection to an Ingress database * * @deprecated */ @Deprecated public static final int TYPE_DATABASE_INGRES = 18; /** * Connection to a Borland Interbase database * * @deprecated */ @Deprecated public static final int TYPE_DATABASE_INTERBASE = 19; /** * Connection to an ExtenDB database * * @deprecated */ @Deprecated public static final int TYPE_DATABASE_EXTENDB = 20; /** * Connection to a Teradata database * * @deprecated */ @Deprecated public static final int TYPE_DATABASE_TERADATA = 21; /** * Connection to an Oracle RDB database * * @deprecated */ @Deprecated public static final int TYPE_DATABASE_ORACLE_RDB = 22; /** * Connection to an H2 database * * @deprecated */ @Deprecated public static final int TYPE_DATABASE_H2 = 23; /** * Connection to a Netezza database * * @deprecated */ @Deprecated public static final int TYPE_DATABASE_NETEZZA = 24; /** * Connection to an IBM UniVerse database * * @deprecated */ @Deprecated public static final int TYPE_DATABASE_UNIVERSE = 25; /** * Connection to a SQLite database * * @deprecated */ @Deprecated public static final int TYPE_DATABASE_SQLITE = 26; /** * Connection to an Apache Derby database * * @deprecated */ @Deprecated public static final int TYPE_DATABASE_DERBY = 27; /** * Connection to a BMC Remedy Action Request System * * @deprecated */ @Deprecated public static final int TYPE_DATABASE_REMEDY_AR_SYSTEM = 28; /** * Connection to a Palo MOLAP Server * * @deprecated */ @Deprecated public static final int TYPE_DATABASE_PALO = 29; /** * Connection to a SybaseIQ ASE database * * @deprecated */ @Deprecated public static final int TYPE_DATABASE_SYBASEIQ = 30; /** * Connection to a Greenplum database * * @deprecated */ @Deprecated public static final int TYPE_DATABASE_GREENPLUM = 31; /** * Connection to a MonetDB database * * @deprecated */ @Deprecated public static final int TYPE_DATABASE_MONETDB = 32; /** * Connection to a KingbaseES database * * @deprecated */ @Deprecated public static final int TYPE_DATABASE_KINGBASEES = 33; /** * Connection to a Vertica database * * @deprecated */ @Deprecated public static final int TYPE_DATABASE_VERTICA = 34; /** * Connection to a Neoview database * * @deprecated */ @Deprecated public static final int TYPE_DATABASE_NEOVIEW = 35; /** * Connection to a LucidDB database * * @deprecated */ @Deprecated public static final int TYPE_DATABASE_LUCIDDB = 36; /** * Connection to an Infobright database * * @deprecated */ @Deprecated public static final int TYPE_DATABASE_INFOBRIGHT = 37; /** * Connect natively through JDBC thin driver to the database. */ public static final int TYPE_ACCESS_NATIVE = 0; /** * Connect to the database using ODBC. */ public static final int TYPE_ACCESS_ODBC = 1; /** * Connect to the database using OCI. (Oracle only) */ public static final int TYPE_ACCESS_OCI = 2; /** * Connect to the database using plugin specific method. (SAP ERP) */ public static final int TYPE_ACCESS_PLUGIN = 3; /** * Connect to the database using JNDI. */ public static final int TYPE_ACCESS_JNDI = 4; /** * Short description of the access type, used in XML and the repository. */ public static final String[] dbAccessTypeCode = { "Native", "ODBC", "OCI", "Plugin", "JNDI", ",", }; /** * Longer description for user interactions. */ public static final String[] dbAccessTypeDesc = { "Native (JDBC)", "ODBC", "OCI", "Plugin specific access method", "JNDI", "Custom", }; /** * Use this length in a String value to indicate that you want to use a CLOB in stead of a normal text field. */ public static final int CLOB_LENGTH = 9999999; /** * The value to store in the attributes so that an empty value doesn't get lost... */ public static final String EMPTY_OPTIONS_STRING = "><EMPTY><"; /** * Construct a new database connections. Note that not all these parameters are not always mandatory. * * @param name * The database name * @param type * The type of database * @param access * The type of database access * @param host * The hostname or IP address * @param db * The database name * @param port * The port on which the database listens. * @param user * The username * @param pass * The password */ public DatabaseMeta( String name, String type, String access, String host, String db, String port, String user, String pass ) { setValues( name, type, access, host, db, port, user, pass ); addOptions(); } /** * Create an empty database connection * */ public DatabaseMeta() { setDefault(); addOptions(); } /** * Set default values for an Oracle database. * */ public void setDefault() { setValues( "", "Oracle", "Native", "", "", "1521", "", "" ); } /** * Add a list of common options for some databases. * */ public void addOptions() { databaseInterface.addDefaultOptions(); setSupportsBooleanDataType( true ); setSupportsTimestampDataType( true ); } /** * @return the system dependend database interface for this database metadata definition */ public DatabaseInterface getDatabaseInterface() { return databaseInterface; } /** * Set the system dependend database interface for this database metadata definition * * @param databaseInterface * the system dependend database interface */ public void setDatabaseInterface( DatabaseInterface databaseInterface ) { this.databaseInterface = databaseInterface; } /** * Search for the right type of DatabaseInterface object and clone it. * * @param databaseType * the type of DatabaseInterface to look for (description) * @return The requested DatabaseInterface * * @throws KettleDatabaseException * when the type could not be found or referenced. */ public static final DatabaseInterface getDatabaseInterface( String databaseType ) throws KettleDatabaseException { DatabaseInterface di = findDatabaseInterface( databaseType ); if ( di == null ) { throw new KettleDatabaseException( BaseMessages.getString( PKG, "DatabaseMeta.Error.DatabaseInterfaceNotFound", databaseType ) ); } return (DatabaseInterface) di.clone(); } /** * Search for the right type of DatabaseInterface object and return it. * * @param databaseTypeDesc * the type of DatabaseInterface to look for (id or description) * @return The requested DatabaseInterface * * @throws KettleDatabaseException * when the type could not be found or referenced. */ private static final DatabaseInterface findDatabaseInterface( String databaseTypeDesc ) throws KettleDatabaseException { PluginRegistry registry = PluginRegistry.getInstance(); PluginInterface plugin = registry.getPlugin( DatabasePluginType.class, databaseTypeDesc ); if ( plugin == null ) { plugin = registry.findPluginWithName( DatabasePluginType.class, databaseTypeDesc ); } if ( plugin == null ) { throw new KettleDatabaseException( "database type with plugin id [" + databaseTypeDesc + "] couldn't be found!" ); } return getDatabaseInterfacesMap().get( plugin.getIds()[0] ); } /** * Returns the database ID of this database connection if a repository was used before. * * @return the ID of the db connection. */ @Override public ObjectId getObjectId() { return databaseInterface.getObjectId(); } @Override public void setObjectId( ObjectId id ) { databaseInterface.setObjectId( id ); } @Override public Object clone() { DatabaseMeta databaseMeta = new DatabaseMeta(); databaseMeta.replaceMeta( this ); databaseMeta.setObjectId( null ); return databaseMeta; } public void replaceMeta( DatabaseMeta databaseMeta ) { this.setValues( databaseMeta.getName(), databaseMeta.getPluginId(), databaseMeta.getAccessTypeDesc(), databaseMeta .getHostname(), databaseMeta.getDatabaseName(), databaseMeta.getDatabasePortNumberString(), databaseMeta.getUsername(), databaseMeta.getPassword() ); this.setServername( databaseMeta.getServername() ); this.setDataTablespace( databaseMeta.getDataTablespace() ); this.setIndexTablespace( databaseMeta.getIndexTablespace() ); this.databaseInterface = (DatabaseInterface) databaseMeta.databaseInterface.clone(); this.setObjectId( databaseMeta.getObjectId() ); this.setChanged(); } public void setValues( String name, String type, String access, String host, String db, String port, String user, String pass ) { try { databaseInterface = getDatabaseInterface( type ); } catch ( KettleDatabaseException kde ) { throw new RuntimeException( "Database type not found!", kde ); } setName( name ); setAccessType( getAccessType( access ) ); setHostname( host ); setDBName( db ); setDBPort( port ); setUsername( user ); setPassword( pass ); setServername( null ); setChanged( false ); } public void setDatabaseType( String type ) { DatabaseInterface oldInterface = databaseInterface; try { databaseInterface = getDatabaseInterface( type ); } catch ( KettleDatabaseException kde ) { throw new RuntimeException( "Database type [" + type + "] not found!", kde ); } setName( oldInterface.getName() ); setDisplayName( oldInterface.getDisplayName() ); setAccessType( oldInterface.getAccessType() ); setHostname( oldInterface.getHostname() ); setDBName( oldInterface.getDatabaseName() ); setDBPort( oldInterface.getDatabasePortNumberString() ); setUsername( oldInterface.getUsername() ); setPassword( oldInterface.getPassword() ); setServername( oldInterface.getServername() ); setDataTablespace( oldInterface.getDataTablespace() ); setIndexTablespace( oldInterface.getIndexTablespace() ); setChanged( oldInterface.isChanged() ); } public void setValues( DatabaseMeta info ) { databaseInterface = (DatabaseInterface) info.databaseInterface.clone(); } /** * Sets the name of the database connection. This name should be unique in a transformation and in general in a single * repository. * * @param name * The name of the database connection */ @Override public void setName( String name ) { databaseInterface.setName( name ); } /** * Returns the name of the database connection * * @return The name of the database connection */ @Override public String getName() { return databaseInterface.getName(); } public void setDisplayName( String displayName ) { databaseInterface.setDisplayName( displayName ); } /** * Returns the name of the database connection * * @return The name of the database connection */ public String getDisplayName() { return databaseInterface.getDisplayName(); } /** * Returns the type of database, one of * <p> * TYPE_DATABASE_MYSQL * <p> * TYPE_DATABASE_ORACLE * <p> * TYPE_DATABASE_... * <p> * * @return the database type * @Deprecated public int getDatabaseType() { return databaseInterface.getDatabaseType(); } */ /** * The plugin ID of the database interface */ public String getPluginId() { return databaseInterface.getPluginId(); } /* * Sets the type of database. * * @param db_type The database type public void setDatabaseType(int db_type) { databaseInterface this.databaseType = * db_type; } */ /** * Return the type of database access. One of * <p> * TYPE_ACCESS_NATIVE * <p> * TYPE_ACCESS_ODBC * <p> * TYPE_ACCESS_OCI * <p> * * @return The type of database access. */ public int getAccessType() { return databaseInterface.getAccessType(); } /** * Set the type of database access. * * @param access_type * The access type. */ public void setAccessType( int access_type ) { databaseInterface.setAccessType( access_type ); } /** * Returns a short description of the type of database. * * @return A short description of the type of database. * @deprecated This is actually the plugin ID */ @Deprecated public String getDatabaseTypeDesc() { return getPluginId(); } /** * Gets you a short description of the type of database access. * * @return A short description of the type of database access. */ public String getAccessTypeDesc() { return dbAccessTypeCode[getAccessType()]; } /** * Return the hostname of the machine on which the database runs. * * @return The hostname of the database. */ public String getHostname() { return databaseInterface.getHostname(); } /** * Sets the hostname of the machine on which the database runs. * * @param hostname * The hostname of the machine on which the database runs. */ public void setHostname( String hostname ) { databaseInterface.setHostname( hostname ); } /** * Return the port on which the database listens as a String. Allows for parameterisation. * * @return The database port. */ public String getDatabasePortNumberString() { return databaseInterface.getDatabasePortNumberString(); } /** * Sets the port on which the database listens. * * @param db_port * The port number on which the database listens */ public void setDBPort( String db_port ) { databaseInterface.setDatabasePortNumberString( db_port ); } /** * Return the name of the database. * * @return The database name. */ public String getDatabaseName() { return databaseInterface.getDatabaseName(); } /** * Set the name of the database. * * @param databaseName * The new name of the database */ public void setDBName( String databaseName ) { databaseInterface.setDatabaseName( databaseName ); } /** * Get the username to log into the database on this connection. * * @return The username to log into the database on this connection. */ public String getUsername() { return databaseInterface.getUsername(); } /** * Sets the username to log into the database on this connection. * * @param username * The username */ public void setUsername( String username ) { databaseInterface.setUsername( username ); } /** * Get the password to log into the database on this connection. * * @return the password to log into the database on this connection. */ public String getPassword() { return databaseInterface.getPassword(); } /** * Sets the password to log into the database on this connection. * * @param password * the password to log into the database on this connection. */ public void setPassword( String password ) { databaseInterface.setPassword( password ); } /** * @param servername * the Informix servername */ public void setServername( String servername ) { databaseInterface.setServername( servername ); } /** * @return the Informix servername */ public String getServername() { return databaseInterface.getServername(); } public String getDataTablespace() { return databaseInterface.getDataTablespace(); } public void setDataTablespace( String data_tablespace ) { databaseInterface.setDataTablespace( data_tablespace ); } public String getIndexTablespace() { return databaseInterface.getIndexTablespace(); } public void setIndexTablespace( String index_tablespace ) { databaseInterface.setIndexTablespace( index_tablespace ); } public void setChanged() { setChanged( true ); } public void setChanged( boolean ch ) { databaseInterface.setChanged( ch ); } public boolean hasChanged() { return databaseInterface.isChanged(); } public void clearChanged() { databaseInterface.setChanged( false ); } @Override public String toString() { return getDisplayName(); } /** * @return The extra attributes for this database connection */ public Properties getAttributes() { return databaseInterface.getAttributes(); } /** * Set extra attributes on this database connection * * @param attributes * The extra attributes to set on this database connection. */ public void setAttributes( Properties attributes ) { databaseInterface.setAttributes( attributes ); } /** * Constructs a new database using an XML string snippet. It expects the snippet to be enclosed in * <code>connection</code> tags. * * @param xml * The XML string to parse * @throws KettleXMLException * in case there is an XML parsing error */ public DatabaseMeta( String xml ) throws KettleXMLException { this( XMLHandler.getSubNode( XMLHandler.loadXMLString( xml ), "connection" ) ); } /** * Reads the information from an XML Node into this new database connection. * * @param con * The Node to read the data from * @throws KettleXMLException */ public DatabaseMeta( Node con ) throws KettleXMLException { this(); try { String type = XMLHandler.getTagValue( con, "type" ); try { databaseInterface = getDatabaseInterface( type ); } catch ( KettleDatabaseException kde ) { throw new KettleXMLException( "Unable to create new database interface", kde ); } setName( XMLHandler.getTagValue( con, "name" ) ); setDisplayName( getName() ); setHostname( XMLHandler.getTagValue( con, "server" ) ); String acc = XMLHandler.getTagValue( con, "access" ); setAccessType( getAccessType( acc ) ); setDBName( XMLHandler.getTagValue( con, "database" ) ); // The DB port is read here too for backward compatibility! getName() // setDBPort( XMLHandler.getTagValue( con, "port" ) ); setUsername( XMLHandler.getTagValue( con, "username" ) ); setPassword( Encr.decryptPasswordOptionallyEncrypted( XMLHandler.getTagValue( con, "password" ) ) ); setServername( XMLHandler.getTagValue( con, "servername" ) ); setDataTablespace( XMLHandler.getTagValue( con, "data_tablespace" ) ); setIndexTablespace( XMLHandler.getTagValue( con, "index_tablespace" ) ); setReadOnly( Boolean.valueOf( XMLHandler.getTagValue( con, "read_only" ) ) ); // Also, read the database attributes... Node attrsnode = XMLHandler.getSubNode( con, "attributes" ); if ( attrsnode != null ) { List<Node> attrnodes = XMLHandler.getNodes( attrsnode, "attribute" ); for ( Node attrnode : attrnodes ) { String code = XMLHandler.getTagValue( attrnode, "code" ); String attribute = XMLHandler.getTagValue( attrnode, "attribute" ); if ( code != null && attribute != null ) { getAttributes().put( code, attribute ); } getDatabasePortNumberString(); } } } catch ( Exception e ) { throw new KettleXMLException( "Unable to load database connection info from XML node", e ); } } @Override public String getXML() { StringBuilder retval = new StringBuilder( 250 ); retval.append( " <" ).append( XML_TAG ).append( '>' ).append( Const.CR ); retval.append( " " ).append( XMLHandler.addTagValue( "name", getName() ) ); retval.append( " " ).append( XMLHandler.addTagValue( "server", getHostname() ) ); retval.append( " " ).append( XMLHandler.addTagValue( "type", getPluginId() ) ); retval.append( " " ).append( XMLHandler.addTagValue( "access", getAccessTypeDesc() ) ); retval.append( " " ).append( XMLHandler.addTagValue( "database", getDatabaseName() ) ); retval.append( " " ).append( XMLHandler.addTagValue( "port", getDatabasePortNumberString() ) ); retval.append( " " ).append( XMLHandler.addTagValue( "username", getUsername() ) ); retval.append( " " ).append( XMLHandler.addTagValue( "password", Encr.encryptPasswordIfNotUsingVariables( getPassword() ) ) ); retval.append( " " ).append( XMLHandler.addTagValue( "servername", getServername() ) ); retval.append( " " ).append( XMLHandler.addTagValue( "data_tablespace", getDataTablespace() ) ); retval.append( " " ).append( XMLHandler.addTagValue( "index_tablespace", getIndexTablespace() ) ); // only write the tag out if it is set to true if ( isReadOnly() ) { retval.append( " " ).append( XMLHandler.addTagValue( "read_only", Boolean.toString( isReadOnly() ) ) ); } retval.append( " <attributes>" ).append( Const.CR ); List<String> list = new ArrayList<>(); Set<Object> keySet = getAttributes().keySet(); for ( Object object : keySet ) { list.add( (String) object ); } Collections.sort( list ); // Sort the entry-sets to make sure we can compare XML strings: if the order is different, // the XML is different. for ( Iterator<String> iter = list.iterator(); iter.hasNext(); ) { String code = iter.next(); String attribute = getAttributes().getProperty( code ); if ( !Utils.isEmpty( attribute ) ) { retval.append( " <attribute>" + XMLHandler.addTagValue( "code", code, false ) + XMLHandler.addTagValue( "attribute", attribute, false ) + "</attribute>" + Const.CR ); } } retval.append( " </attributes>" ).append( Const.CR ); retval.append( " </" + XML_TAG + ">" ).append( Const.CR ); return retval.toString(); } @Override public int hashCode() { return getName().hashCode(); // name of connection is unique! } @Override public boolean equals( Object obj ) { return obj instanceof DatabaseMeta && getName().equals( ( (DatabaseMeta) obj ).getName() ); } public String getURL() throws KettleDatabaseException { return getURL( null ); } public String getURL( String partitionId ) throws KettleDatabaseException { // First see if we're not doing any JNDI... // /* * This doesn't make much sense here - we check but do nothing? if ( getAccessType() == TYPE_ACCESS_JNDI ) { // We * can't really determine the URL here. // // } */ String baseUrl; String hostname; String port; String databaseName; if ( isPartitioned() && !Utils.isEmpty( partitionId ) ) { // Get the cluster information... PartitionDatabaseMeta partition = getPartitionMeta( partitionId ); hostname = environmentSubstitute( partition.getHostname() ); port = environmentSubstitute( partition.getPort() ); databaseName = environmentSubstitute( partition.getDatabaseName() ); } else { hostname = environmentSubstitute( getHostname() ); port = environmentSubstitute( getDatabasePortNumberString() ); databaseName = environmentSubstitute( getDatabaseName() ); } baseUrl = databaseInterface.getURL( environmentSubstitute( hostname ), environmentSubstitute( port ), environmentSubstitute( databaseName ) ); String url = environmentSubstitute( baseUrl ); if ( databaseInterface.supportsOptionsInURL() ) { url = appendExtraOptions( url, getExtraOptions() ); } // else { // We need to put all these options in a Properties file later (Oracle & Co.) // This happens at connect time... // } return url; } protected String appendExtraOptions( String url, Map<String, String> extraOptions ) { if ( extraOptions.isEmpty() ) { return url; } StringBuilder urlBuilder = new StringBuilder( url ); final String optionIndicator = getExtraOptionIndicator(); final String optionSeparator = getExtraOptionSeparator(); final String valueSeparator = getExtraOptionValueSeparator(); Iterator<String> iterator = extraOptions.keySet().iterator(); boolean first = true; while ( iterator.hasNext() ) { String typedParameter = iterator.next(); int dotIndex = typedParameter.indexOf( '.' ); if ( dotIndex == -1 ) { continue; } final String value = extraOptions.get( typedParameter ); if ( Utils.isEmpty( value ) || value.equals( EMPTY_OPTIONS_STRING ) ) { // skip this science no value is provided continue; } final String typeCode = typedParameter.substring( 0, dotIndex ); final String parameter = typedParameter.substring( dotIndex + 1 ); // Only add to the URL if it's the same database type code, // or underlying database is the same for both id's, and any subset of // connection settings for one database is valid for another boolean dbForBothDbInterfacesIsSame = false; try { DatabaseInterface primaryDb = getDbInterface( typeCode ); dbForBothDbInterfacesIsSame = databaseForBothDbInterfacesIsTheSame( primaryDb, getDatabaseInterface() ); } catch ( KettleDatabaseException e ) { getGeneralLogger().logError( "DatabaseInterface with " + typeCode + " database type is not found! Parameter " + parameter + "won't be appended to URL" ); } if ( dbForBothDbInterfacesIsSame ) { if ( first && url.indexOf( valueSeparator ) == -1 ) { urlBuilder.append( optionIndicator ); } else { urlBuilder.append( optionSeparator ); } urlBuilder.append( parameter ).append( valueSeparator ).append( value ); first = false; } } return urlBuilder.toString(); } /** * This method is designed to identify whether the actual database for two database connection types is the same. * This situation can occur in two cases: * 1. plugin id of {@code primary} is the same as plugin id of {@code secondary} * 2. {@code secondary} is a descendant {@code primary} (with any deepness). */ protected boolean databaseForBothDbInterfacesIsTheSame( DatabaseInterface primary, DatabaseInterface secondary ) { if ( primary == null || secondary == null ) { throw new IllegalArgumentException( "DatabaseInterface shouldn't be null!" ); } if ( primary.getPluginId() == null || secondary.getPluginId() == null ) { return false; } if ( primary.getPluginId().equals( secondary.getPluginId() ) ) { return true; } return primary.getClass().isAssignableFrom( secondary.getClass() ); } public Properties getConnectionProperties() { Properties properties = new Properties(); Map<String, String> map = getExtraOptions(); if ( map.size() > 0 ) { Iterator<String> iterator = map.keySet().iterator(); while ( iterator.hasNext() ) { String typedParameter = iterator.next(); int dotIndex = typedParameter.indexOf( '.' ); if ( dotIndex >= 0 ) { String typeCode = typedParameter.substring( 0, dotIndex ); String parameter = typedParameter.substring( dotIndex + 1 ); String value = map.get( typedParameter ); // Only add to the URL if it's the same database type code... // if ( databaseInterface.getPluginId().equals( typeCode ) ) { if ( value != null && value.equals( EMPTY_OPTIONS_STRING ) ) { value = ""; } properties.put( parameter, environmentSubstitute( Const.NVL( value, "" ) ) ); } } } } return properties; } public String getExtraOptionIndicator() { return getDatabaseInterface().getExtraOptionIndicator(); } /** * @return The extra option separator in database URL for this platform (usually this is semicolon ; ) */ public String getExtraOptionSeparator() { return getDatabaseInterface().getExtraOptionSeparator(); } /** * @return The extra option value separator in database URL for this platform (usually this is the equal sign = ) */ public String getExtraOptionValueSeparator() { return getDatabaseInterface().getExtraOptionValueSeparator(); } /** * Add an extra option to the attributes list * * @param databaseTypeCode * The database type code for which the option applies * @param option * The option to set * @param value * The value of the option */ public void addExtraOption( String databaseTypeCode, String option, String value ) { databaseInterface.addExtraOption( databaseTypeCode, option, value ); } public void applyDefaultOptions( DatabaseInterface databaseInterface ) { final Map<String, String> extraOptions = getExtraOptions(); final Map<String, String> defaultOptions = databaseInterface.getDefaultOptions(); for ( String option : defaultOptions.keySet() ) { String value = defaultOptions.get( option ); String[] split = option.split( "[.]", 2 ); if ( !extraOptions.containsKey( option ) && split.length == 2 ) { addExtraOption( split[0], split[1], value ); } } } /** * @deprecated because the same database can support transactions or not. It all depends on the database setup. * Therefor, we look at the database metadata DatabaseMetaData.supportsTransactions() in stead of this. * @return true if the database supports transactions */ @Deprecated public boolean supportsTransactions() { return databaseInterface.supportsTransactions(); } public boolean supportsAutoinc() { return databaseInterface.supportsAutoInc(); } public boolean supportsSequences() { return databaseInterface.supportsSequences(); } public String getSQLSequenceExists( String sequenceName ) { return databaseInterface.getSQLSequenceExists( sequenceName ); } public boolean supportsBitmapIndex() { return databaseInterface.supportsBitmapIndex(); } public boolean supportsSetLong() { return databaseInterface.supportsSetLong(); } /** * @return true if the database supports schemas */ public boolean supportsSchemas() { return databaseInterface.supportsSchemas(); } /** * @return true if the database supports catalogs */ public boolean supportsCatalogs() { return databaseInterface.supportsCatalogs(); } /** * * @return true when the database engine supports empty transaction. (for example Informix does not on a non-ANSI * database type!) */ public boolean supportsEmptyTransactions() { return databaseInterface.supportsEmptyTransactions(); } /** * See if this database supports the setCharacterStream() method on a PreparedStatement. * * @return true if we can set a Stream on a field in a PreparedStatement. False if not. */ public boolean supportsSetCharacterStream() { return databaseInterface.supportsSetCharacterStream(); } /** * Get the maximum length of a text field for this database connection. This includes optional CLOB, Memo and Text * fields. (the maximum!) * * @return The maximum text field length for this database type. (mostly CLOB_LENGTH) */ public int getMaxTextFieldLength() { return databaseInterface.getMaxTextFieldLength(); } public static final int getAccessType( String dbaccess ) { int i; if ( dbaccess == null ) { return TYPE_ACCESS_NATIVE; } for ( i = 0; i < dbAccessTypeCode.length; i++ ) { if ( dbAccessTypeCode[i].equalsIgnoreCase( dbaccess ) ) { return i; } } for ( i = 0; i < dbAccessTypeDesc.length; i++ ) { if ( dbAccessTypeDesc[i].equalsIgnoreCase( dbaccess ) ) { return i; } } return TYPE_ACCESS_NATIVE; } public static final String getAccessTypeDesc( int dbaccess ) { if ( dbaccess < 0 ) { return null; } if ( dbaccess > dbAccessTypeCode.length ) { return null; } return dbAccessTypeCode[dbaccess]; } public static final String getAccessTypeDescLong( int dbaccess ) { if ( dbaccess < 0 ) { return null; } if ( dbaccess > dbAccessTypeDesc.length ) { return null; } return dbAccessTypeDesc[dbaccess]; } public static final DatabaseInterface[] getDatabaseInterfaces() { List<DatabaseInterface> list = new ArrayList<>( getDatabaseInterfacesMap().values() ); return list.toArray( new DatabaseInterface[list.size()] ); } /** * Clear the database interfaces map. The map is cached by getDatabaseInterfacesMap(), but in some instances it may * need to be reloaded (such as adding/updating Database plugins). After calling clearDatabaseInterfacesMap(), the * next call to getDatabaseInterfacesMap() will reload the map. */ public static final void clearDatabaseInterfacesMap() { allDatabaseInterfaces = null; } private static final Future<Map<String, DatabaseInterface>> createDatabaseInterfacesMap() { return ExecutorUtil.getExecutor().submit( new Callable<Map<String, DatabaseInterface>>() { private Map<String, DatabaseInterface> doCreate() { LogChannelInterface log = LogChannel.GENERAL; PluginRegistry registry = PluginRegistry.getInstance(); List<PluginInterface> plugins = registry.getPlugins( DatabasePluginType.class ); HashMap<String, DatabaseInterface> tmpAllDatabaseInterfaces = new HashMap<>(); for ( PluginInterface plugin : plugins ) { try { DatabaseInterface databaseInterface = (DatabaseInterface) registry.loadClass( plugin ); databaseInterface.setPluginId( plugin.getIds()[ 0 ] ); databaseInterface.setPluginName( plugin.getName() ); tmpAllDatabaseInterfaces.put( plugin.getIds()[ 0 ], databaseInterface ); } catch ( KettlePluginException cnfe ) { // System.out.println( "Could not create connection entry for " + plugin.getName() + ". " + cnfe.getCause().getClass().getName() ); log.logError( "Could not create connection entry for " + plugin.getName() + ". " + cnfe.getCause().getClass().getName() ); if ( log.isDebug() ) { log.logDebug( "Debug-Error loading plugin: " + plugin, cnfe ); } } catch ( Exception e ) { log.logError( "Error loading plugin: " + plugin, e ); } } return Collections.unmodifiableMap( tmpAllDatabaseInterfaces ); } @Override public Map<String, DatabaseInterface> call() throws Exception { return doCreate(); } } ); } public static final Map<String, DatabaseInterface> getDatabaseInterfacesMap() { Future<Map<String, DatabaseInterface>> allDatabaseInterfaces = DatabaseMeta.allDatabaseInterfaces; while ( allDatabaseInterfaces == null ) { DatabaseMeta.allDatabaseInterfaces = createDatabaseInterfacesMap(); allDatabaseInterfaces = DatabaseMeta.allDatabaseInterfaces; } try { return allDatabaseInterfaces.get(); } catch ( Exception e ) { clearDatabaseInterfacesMap(); // doCreate() above doesn't declare any exceptions so anything that comes out SHOULD be a runtime exception if ( e instanceof RuntimeException ) { throw (RuntimeException) e; } else { throw new RuntimeException( e ); } } } public static final int[] getAccessTypeList( String dbTypeDesc ) { try { DatabaseInterface di = findDatabaseInterface( dbTypeDesc ); return di.getAccessTypeList(); } catch ( KettleDatabaseException kde ) { return null; } } public static final int getPortForDBType( String strtype, String straccess ) { try { DatabaseInterface di = getDatabaseInterface( strtype ); di.setAccessType( getAccessType( straccess ) ); return di.getDefaultDatabasePort(); } catch ( KettleDatabaseException kde ) { return -1; } } public int getDefaultDatabasePort() { return databaseInterface.getDefaultDatabasePort(); } public int getNotFoundTK( boolean use_autoinc ) { return databaseInterface.getNotFoundTK( use_autoinc ); } public String getDriverClass() { return environmentSubstitute( databaseInterface.getDriverClass() ); } public String stripCR( String sbsql ) { if ( sbsql == null ) { return null; } return stripCR( new StringBuilder( sbsql ) ); } public String stripCR( StringBuffer sbsql ) { // DB2 Can't handle \n in SQL Statements... if ( !supportsNewLinesInSQL() ) { // Remove CR's for ( int i = sbsql.length() - 1; i >= 0; i-- ) { if ( sbsql.charAt( i ) == '\n' || sbsql.charAt( i ) == '\r' ) { sbsql.setCharAt( i, ' ' ); } } } return sbsql.toString(); } public String stripCR( StringBuilder sbsql ) { // DB2 Can't handle \n in SQL Statements... if ( !supportsNewLinesInSQL() ) { // Remove CR's for ( int i = sbsql.length() - 1; i >= 0; i-- ) { if ( sbsql.charAt( i ) == '\n' || sbsql.charAt( i ) == '\r' ) { sbsql.setCharAt( i, ' ' ); } } } return sbsql.toString(); } public String getSeqNextvalSQL( String sequenceName ) { return databaseInterface.getSQLNextSequenceValue( sequenceName ); } public String getSQLCurrentSequenceValue( String sequenceName ) { return databaseInterface.getSQLCurrentSequenceValue( sequenceName ); } public boolean isFetchSizeSupported() { return databaseInterface.isFetchSizeSupported(); } /** * Indicates the need to insert a placeholder (0) for auto increment fields. * * @return true if we need a placeholder for auto increment fields in insert statements. */ public boolean needsPlaceHolder() { return databaseInterface.needsPlaceHolder(); } public String getFunctionSum() { return databaseInterface.getFunctionSum(); } public String getFunctionAverage() { return databaseInterface.getFunctionAverage(); } public String getFunctionMaximum() { return databaseInterface.getFunctionMaximum(); } public String getFunctionMinimum() { return databaseInterface.getFunctionMinimum(); } public String getFunctionCount() { return databaseInterface.getFunctionCount(); } /** * Check the database connection parameters and give back an array of remarks * * @return an array of remarks Strings */ public String[] checkParameters() { ArrayList<String> remarks = new ArrayList<>(); if ( getDatabaseInterface() == null ) { remarks.add( BaseMessages.getString( PKG, "DatabaseMeta.BadInterface" ) ); } if ( getName() == null || getName().length() == 0 ) { remarks.add( BaseMessages.getString( PKG, "DatabaseMeta.BadConnectionName" ) ); } if ( !isPartitioned() && ( ( (BaseDatabaseMeta) getDatabaseInterface() ).requiresName() && !( getDatabaseInterface() instanceof GenericDatabaseMeta ) ) ) { if ( getDatabaseName() == null || getDatabaseName().length() == 0 ) { remarks.add( BaseMessages.getString( PKG, "DatabaseMeta.BadDatabaseName" ) ); } } return remarks.toArray( new String[ remarks.size() ] ); } /** * This is now replaced with getQuotedSchemaTableCombination(), enforcing the use of the quoteFields call * * @param schemaName * @param tableName * @return * @deprecated please use getQuotedSchemaTableCombination() */ @Deprecated public String getSchemaTableCombination( String schemaName, String tableName ) { return getQuotedSchemaTableCombination( schemaName, tableName ); } /** * Calculate the schema-table combination, usually this is the schema and table separated with a dot. (schema.table) * * @param schemaName * the schema-name or null if no schema is used. * @param tableName * the table name * @return the schemaname-tablename combination */ public String getQuotedSchemaTableCombination( String schemaName, String tableName ) { if ( Utils.isEmpty( schemaName ) ) { if ( Utils.isEmpty( getPreferredSchemaName() ) ) { return quoteField( environmentSubstitute( tableName ) ); // no need to look further } else { return databaseInterface.getSchemaTableCombination( quoteField( environmentSubstitute( getPreferredSchemaName() ) ), quoteField( environmentSubstitute( tableName ) ) ); } } else { return databaseInterface.getSchemaTableCombination( quoteField( environmentSubstitute( schemaName ) ), quoteField( environmentSubstitute( tableName ) ) ); } } public boolean isClob( ValueMetaInterface v ) { boolean retval = true; if ( v == null || v.getLength() < DatabaseMeta.CLOB_LENGTH ) { retval = false; } else { return true; } return retval; } public String getFieldDefinition( ValueMetaInterface v, String tk, String pk, boolean use_autoinc ) { return getFieldDefinition( v, tk, pk, use_autoinc, true, true ); } public String getFieldDefinition( ValueMetaInterface v, String tk, String pk, boolean use_autoinc, boolean add_fieldname, boolean add_cr ) { String definition = v.getDatabaseColumnTypeDefinition( databaseInterface, tk, pk, use_autoinc, add_fieldname, add_cr ); if ( !Utils.isEmpty( definition ) ) { return definition; } return databaseInterface.getFieldDefinition( v, tk, pk, use_autoinc, add_fieldname, add_cr ); } public String getLimitClause( int nrRows ) { return databaseInterface.getLimitClause( nrRows ); } /** * @param tableName * The table or schema-table combination. We expect this to be quoted properly already! * @return the SQL for to get the fields of this table. */ public String getSQLQueryFields( String tableName ) { return databaseInterface.getSQLQueryFields( tableName ); } public String getAddColumnStatement( String tablename, ValueMetaInterface v, String tk, boolean use_autoinc, String pk, boolean semicolon ) { String retval = databaseInterface.getAddColumnStatement( tablename, v, tk, use_autoinc, pk, semicolon ); retval += Const.CR; if ( semicolon ) { retval += ";" + Const.CR; } return retval; } public String getDropColumnStatement( String tablename, ValueMetaInterface v, String tk, boolean use_autoinc, String pk, boolean semicolon ) { String retval = databaseInterface.getDropColumnStatement( tablename, v, tk, use_autoinc, pk, semicolon ); retval += Const.CR; if ( semicolon ) { retval += ";" + Const.CR; } return retval; } public String getModifyColumnStatement( String tablename, ValueMetaInterface v, String tk, boolean use_autoinc, String pk, boolean semicolon ) { String retval = databaseInterface.getModifyColumnStatement( tablename, v, tk, use_autoinc, pk, semicolon ); retval += Const.CR; if ( semicolon ) { retval += ";" + Const.CR; } return retval; } /** * @return an array of reserved words for the database type... */ public String[] getReservedWords() { return databaseInterface.getReservedWords(); } /** * @return true if reserved words need to be double quoted ("password", "select", ...) */ public boolean quoteReservedWords() { return databaseInterface.quoteReservedWords(); } /** * @return The start quote sequence, mostly just double quote, but sometimes [, ... */ public String getStartQuote() { return databaseInterface.getStartQuote(); } /** * @return The end quote sequence, mostly just double quote, but sometimes ], ... */ public String getEndQuote() { return databaseInterface.getEndQuote(); } /** * Returns a quoted field if this is needed: contains spaces, is a reserved word, ... * * @param field * The fieldname to check for quoting * @return The quoted field (if this is needed. */ public String quoteField( String field ) { if ( Utils.isEmpty( field ) ) { return null; } if ( isForcingIdentifiersToLowerCase() ) { field = field.toLowerCase(); } else if ( isForcingIdentifiersToUpperCase() ) { field = field.toUpperCase(); } // If the field already contains quotes, we don't touch it anymore, just return the same string... if ( field.indexOf( getStartQuote() ) >= 0 || field.indexOf( getEndQuote() ) >= 0 ) { return field; } if ( isReservedWord( field ) && quoteReservedWords() ) { return handleCase( getStartQuote() + field + getEndQuote() ); } else { if ( databaseInterface.isQuoteAllFields() || hasSpacesInField( field ) || hasSpecialCharInField( field ) || hasDotInField( field ) ) { return getStartQuote() + field + getEndQuote(); } else { return field; } } } private String handleCase( String field ) { if ( preserveReservedCase() ) { return field; } else { if ( databaseInterface.isDefaultingToUppercase() ) { return field.toUpperCase(); } else { return field.toLowerCase(); } } } /** * Determines whether or not this field is in need of quoting:<br> * - When the fieldname contains spaces<br> * - When the fieldname is a reserved word<br> * * @param fieldname * the fieldname to check if there is a need for quoting * @return true if the fieldname needs to be quoted. */ public boolean isInNeedOfQuoting( String fieldname ) { return isReservedWord( fieldname ) || hasSpacesInField( fieldname ); } /** * Returns true if the string specified is a reserved word on this database type. * * @param word * The word to check * @return true if word is a reserved word on this database. */ public boolean isReservedWord( String word ) { String[] reserved = getReservedWords(); if ( Const.indexOfString( word, reserved ) >= 0 ) { return true; } return false; } /** * Detects if a field has spaces in the name. We need to quote the field in that case. * * @param fieldname * The fieldname to check for spaces * @return true if the fieldname contains spaces */ public boolean hasSpacesInField( String fieldname ) { if ( fieldname == null ) { return false; } if ( fieldname.indexOf( ' ' ) >= 0 ) { return true; } return false; } /** * Detects if a field has spaces in the name. We need to quote the field in that case. * * @param fieldname * The fieldname to check for spaces * @return true if the fieldname contains spaces */ public boolean hasSpecialCharInField( String fieldname ) { if ( fieldname == null ) { return false; } if ( fieldname.indexOf( '/' ) >= 0 ) { return true; } if ( fieldname.indexOf( '-' ) >= 0 ) { return true; } if ( fieldname.indexOf( '+' ) >= 0 ) { return true; } if ( fieldname.indexOf( ',' ) >= 0 ) { return true; } if ( fieldname.indexOf( '*' ) >= 0 ) { return true; } if ( fieldname.indexOf( '(' ) >= 0 ) { return true; } if ( fieldname.indexOf( ')' ) >= 0 ) { return true; } if ( fieldname.indexOf( '{' ) >= 0 ) { return true; } if ( fieldname.indexOf( '}' ) >= 0 ) { return true; } if ( fieldname.indexOf( '[' ) >= 0 ) { return true; } if ( fieldname.indexOf( ']' ) >= 0 ) { return true; } if ( fieldname.indexOf( '%' ) >= 0 ) { return true; } if ( fieldname.indexOf( '@' ) >= 0 ) { return true; } if ( fieldname.indexOf( '?' ) >= 0 ) { return true; } return false; } public boolean hasDotInField( String fieldname ) { if ( fieldname == null ) { return false; } if ( fieldname.indexOf( '.' ) >= 0 ) { return true; } return false; } /** * Checks the fields specified for reserved words and quotes them. * * @param fields * the list of fields to check * @return true if one or more values have a name that is a reserved word on this database type. */ public boolean replaceReservedWords( RowMetaInterface fields ) { boolean hasReservedWords = false; for ( int i = 0; i < fields.size(); i++ ) { ValueMetaInterface v = fields.getValueMeta( i ); if ( isReservedWord( v.getName() ) ) { hasReservedWords = true; v.setName( quoteField( v.getName() ) ); } } return hasReservedWords; } /** * Checks the fields specified for reserved words * * @param fields * the list of fields to check * @return The nr of reserved words for this database. */ public int getNrReservedWords( RowMetaInterface fields ) { int nrReservedWords = 0; for ( int i = 0; i < fields.size(); i++ ) { ValueMetaInterface v = fields.getValueMeta( i ); if ( isReservedWord( v.getName() ) ) { nrReservedWords++; } } return nrReservedWords; } /** * @return a list of types to get the available tables */ public String[] getTableTypes() { return databaseInterface.getTableTypes(); } /** * @return a list of types to get the available views */ public String[] getViewTypes() { return databaseInterface.getViewTypes(); } /** * @return a list of types to get the available synonyms */ public String[] getSynonymTypes() { return databaseInterface.getSynonymTypes(); } /** * @return true if we need to supply the schema-name to getTables in order to get a correct list of items. */ public boolean useSchemaNameForTableList() { return databaseInterface.useSchemaNameForTableList(); } /** * @return true if the database supports views */ public boolean supportsViews() { return databaseInterface.supportsViews(); } /** * @return true if the database supports synonyms */ public boolean supportsSynonyms() { return databaseInterface.supportsSynonyms(); } /** * * @return The SQL on this database to get a list of stored procedures. */ public String getSQLListOfProcedures() { return databaseInterface.getSQLListOfProcedures(); } /** * @param tableName * The tablename to be truncated * @return The SQL statement to remove all rows from the specified statement, if possible without using transactions */ public String getTruncateTableStatement( String schema, String tableName ) { return databaseInterface.getTruncateTableStatement( getQuotedSchemaTableCombination( schema, tableName ) ); } /** * @return true if the database rounds floating point numbers to the right precision. For example if the target field * is number(7,2) the value 12.399999999 is converted into 12.40 */ public boolean supportsFloatRoundingOnUpdate() { return databaseInterface.supportsFloatRoundingOnUpdate(); } /** * @param tableNames * The names of the tables to lock * @return The SQL commands to lock database tables for write purposes. null is returned in case locking is not * supported on the target database. */ public String getSQLLockTables( String[] tableNames ) { return databaseInterface.getSQLLockTables( tableNames ); } /** * @param tableNames * The names of the tables to unlock * @return The SQL commands to unlock databases tables. null is returned in case locking is not supported on the * target database. */ public String getSQLUnlockTables( String[] tableNames ) { return databaseInterface.getSQLUnlockTables( tableNames ); } /** * @return a feature list for the chosen database type. * */ public List<RowMetaAndData> getFeatureSummary() { List<RowMetaAndData> list = new ArrayList<>(); RowMetaAndData r = null; final String par = "Parameter"; final String val = "Value"; ValueMetaInterface testValue = new ValueMetaString( "FIELD" ); testValue.setLength( 30 ); if ( databaseInterface != null ) { // Type of database r = new RowMetaAndData(); r.addValue( par, ValueMetaInterface.TYPE_STRING, "Database type" ); r.addValue( val, ValueMetaInterface.TYPE_STRING, getPluginId() ); list.add( r ); // Type of access r = new RowMetaAndData(); r.addValue( par, ValueMetaInterface.TYPE_STRING, "Access type" ); r.addValue( val, ValueMetaInterface.TYPE_STRING, getAccessTypeDesc() ); list.add( r ); // Name of database r = new RowMetaAndData(); r.addValue( par, ValueMetaInterface.TYPE_STRING, "Database name" ); r.addValue( val, ValueMetaInterface.TYPE_STRING, getDatabaseName() ); list.add( r ); // server host name r = new RowMetaAndData(); r.addValue( par, ValueMetaInterface.TYPE_STRING, "Server hostname" ); r.addValue( val, ValueMetaInterface.TYPE_STRING, getHostname() ); list.add( r ); // Port number r = new RowMetaAndData(); r.addValue( par, ValueMetaInterface.TYPE_STRING, "Service port" ); r.addValue( val, ValueMetaInterface.TYPE_STRING, getDatabasePortNumberString() ); list.add( r ); // Username r = new RowMetaAndData(); r.addValue( par, ValueMetaInterface.TYPE_STRING, "Username" ); r.addValue( val, ValueMetaInterface.TYPE_STRING, getUsername() ); list.add( r ); // Informix server r = new RowMetaAndData(); r.addValue( par, ValueMetaInterface.TYPE_STRING, "Informix server name" ); r.addValue( val, ValueMetaInterface.TYPE_STRING, getServername() ); list.add( r ); // Other properties... Enumeration<Object> keys = getAttributes().keys(); while ( keys.hasMoreElements() ) { String key = (String) keys.nextElement(); String value = getAttributes().getProperty( key ); r = new RowMetaAndData(); r.addValue( par, ValueMetaInterface.TYPE_STRING, "Extra attribute [" + key + "]" ); r.addValue( val, ValueMetaInterface.TYPE_STRING, value ); list.add( r ); } // driver class r = new RowMetaAndData(); r.addValue( par, ValueMetaInterface.TYPE_STRING, "Driver class" ); r.addValue( val, ValueMetaInterface.TYPE_STRING, getDriverClass() ); list.add( r ); // URL String pwd = getPassword(); setPassword( "password" ); // Don't give away the password in the URL! String url = ""; try { url = getURL(); } catch ( Exception e ) { url = ""; } // SAP etc. r = new RowMetaAndData(); r.addValue( par, ValueMetaInterface.TYPE_STRING, "URL" ); r.addValue( val, ValueMetaInterface.TYPE_STRING, url ); list.add( r ); setPassword( pwd ); // SQL: Next sequence value r = new RowMetaAndData(); r.addValue( par, ValueMetaInterface.TYPE_STRING, "SQL: next sequence value" ); r.addValue( val, ValueMetaInterface.TYPE_STRING, getSeqNextvalSQL( "SEQUENCE" ) ); list.add( r ); // is set fetch size supported r = new RowMetaAndData(); r.addValue( par, ValueMetaInterface.TYPE_STRING, "supported: set fetch size" ); r.addValue( val, ValueMetaInterface.TYPE_STRING, isFetchSizeSupported() ? "Y" : "N" ); list.add( r ); // needs place holder for auto increment r = new RowMetaAndData(); r.addValue( par, ValueMetaInterface.TYPE_STRING, "auto increment field needs placeholder" ); r.addValue( val, ValueMetaInterface.TYPE_STRING, needsPlaceHolder() ? "Y" : "N" ); list.add( r ); // Sum function r = new RowMetaAndData(); r.addValue( par, ValueMetaInterface.TYPE_STRING, "SUM aggregate function" ); r.addValue( val, ValueMetaInterface.TYPE_STRING, getFunctionSum() ); list.add( r ); // Avg function r = new RowMetaAndData(); r.addValue( par, ValueMetaInterface.TYPE_STRING, "AVG aggregate function" ); r.addValue( val, ValueMetaInterface.TYPE_STRING, getFunctionAverage() ); list.add( r ); // Minimum function r = new RowMetaAndData(); r.addValue( par, ValueMetaInterface.TYPE_STRING, "MIN aggregate function" ); r.addValue( val, ValueMetaInterface.TYPE_STRING, getFunctionMinimum() ); list.add( r ); // Maximum function r = new RowMetaAndData(); r.addValue( par, ValueMetaInterface.TYPE_STRING, "MAX aggregate function" ); r.addValue( val, ValueMetaInterface.TYPE_STRING, getFunctionMaximum() ); list.add( r ); // Count function r = new RowMetaAndData(); r.addValue( par, ValueMetaInterface.TYPE_STRING, "COUNT aggregate function" ); r.addValue( val, ValueMetaInterface.TYPE_STRING, getFunctionCount() ); list.add( r ); // Schema-table combination r = new RowMetaAndData(); r.addValue( par, ValueMetaInterface.TYPE_STRING, "Schema / Table combination" ); r.addValue( val, ValueMetaInterface.TYPE_STRING, getQuotedSchemaTableCombination( "SCHEMA", "TABLE" ) ); list.add( r ); // Limit clause r = new RowMetaAndData(); r.addValue( par, ValueMetaInterface.TYPE_STRING, "LIMIT clause for 100 rows" ); r.addValue( val, ValueMetaInterface.TYPE_STRING, getLimitClause( 100 ) ); list.add( r ); // add column statement r = new RowMetaAndData(); r.addValue( par, ValueMetaInterface.TYPE_STRING, "Add column statement" ); r.addValue( val, ValueMetaInterface.TYPE_STRING, getAddColumnStatement( "TABLE", testValue, null, false, null, false ) ); list.add( r ); // drop column statement r = new RowMetaAndData(); r.addValue( par, ValueMetaInterface.TYPE_STRING, "Drop column statement" ); r.addValue( val, ValueMetaInterface.TYPE_STRING, getDropColumnStatement( "TABLE", testValue, null, false, null, false ) ); list.add( r ); // Modify column statement r = new RowMetaAndData(); r.addValue( par, ValueMetaInterface.TYPE_STRING, "Modify column statement" ); r.addValue( val, ValueMetaInterface.TYPE_STRING, getModifyColumnStatement( "TABLE", testValue, null, false, null, false ) ); list.add( r ); // List of reserved words String reserved = ""; if ( getReservedWords() != null ) { for ( int i = 0; i < getReservedWords().length; i++ ) { reserved += ( i > 0 ? ", " : "" ) + getReservedWords()[i]; } } r = new RowMetaAndData(); r.addValue( par, ValueMetaInterface.TYPE_STRING, "List of reserved words" ); r.addValue( val, ValueMetaInterface.TYPE_STRING, reserved ); list.add( r ); // Quote reserved words? r = new RowMetaAndData(); r.addValue( par, ValueMetaInterface.TYPE_STRING, "Quote reserved words?" ); r.addValue( val, ValueMetaInterface.TYPE_STRING, quoteReservedWords() ? "Y" : "N" ); list.add( r ); // Start Quote r = new RowMetaAndData(); r.addValue( par, ValueMetaInterface.TYPE_STRING, "Start quote for reserved words" ); r.addValue( val, ValueMetaInterface.TYPE_STRING, getStartQuote() ); list.add( r ); // End Quote r = new RowMetaAndData(); r.addValue( par, ValueMetaInterface.TYPE_STRING, "End quote for reserved words" ); r.addValue( val, ValueMetaInterface.TYPE_STRING, getEndQuote() ); list.add( r ); // List of table types String types = ""; String[] slist = getTableTypes(); if ( slist != null ) { for ( int i = 0; i < slist.length; i++ ) { types += ( i > 0 ? ", " : "" ) + slist[i]; } } r = new RowMetaAndData(); r.addValue( par, ValueMetaInterface.TYPE_STRING, "List of JDBC table types" ); r.addValue( val, ValueMetaInterface.TYPE_STRING, types ); list.add( r ); // List of view types types = ""; slist = getViewTypes(); if ( slist != null ) { for ( int i = 0; i < slist.length; i++ ) { types += ( i > 0 ? ", " : "" ) + slist[i]; } } r = new RowMetaAndData(); r.addValue( par, ValueMetaInterface.TYPE_STRING, "List of JDBC view types" ); r.addValue( val, ValueMetaInterface.TYPE_STRING, types ); list.add( r ); // List of synonym types types = ""; slist = getSynonymTypes(); if ( slist != null ) { for ( int i = 0; i < slist.length; i++ ) { types += ( i > 0 ? ", " : "" ) + slist[i]; } } r = new RowMetaAndData(); r.addValue( par, ValueMetaInterface.TYPE_STRING, "List of JDBC synonym types" ); r.addValue( val, ValueMetaInterface.TYPE_STRING, types ); list.add( r ); // Use schema-name to get list of tables? r = new RowMetaAndData(); r.addValue( par, ValueMetaInterface.TYPE_STRING, "use schema name to get table list?" ); r.addValue( val, ValueMetaInterface.TYPE_STRING, useSchemaNameForTableList() ? "Y" : "N" ); list.add( r ); // supports view? r = new RowMetaAndData(); r.addValue( par, ValueMetaInterface.TYPE_STRING, "supports views?" ); r.addValue( val, ValueMetaInterface.TYPE_STRING, supportsViews() ? "Y" : "N" ); list.add( r ); // supports synonyms? r = new RowMetaAndData(); r.addValue( par, ValueMetaInterface.TYPE_STRING, "supports synonyms?" ); r.addValue( val, ValueMetaInterface.TYPE_STRING, supportsSynonyms() ? "Y" : "N" ); list.add( r ); // SQL: get list of procedures? r = new RowMetaAndData(); r.addValue( par, ValueMetaInterface.TYPE_STRING, "SQL: list of procedures" ); r.addValue( val, ValueMetaInterface.TYPE_STRING, getSQLListOfProcedures() ); list.add( r ); // SQL: get truncate table statement? r = new RowMetaAndData(); r.addValue( par, ValueMetaInterface.TYPE_STRING, "SQL: truncate table" ); String truncateStatement = getTruncateTableStatement( null, "TABLE" ); r.addValue( val, ValueMetaInterface.TYPE_STRING, truncateStatement != null ? truncateStatement : "Not supported by this database type" ); list.add( r ); // supports float rounding on update? r = new RowMetaAndData(); r.addValue( par, ValueMetaInterface.TYPE_STRING, "supports floating point rounding on update/insert" ); r.addValue( val, ValueMetaInterface.TYPE_STRING, supportsFloatRoundingOnUpdate() ? "Y" : "N" ); list.add( r ); // supports time stamp to date conversion r = new RowMetaAndData(); r.addValue( par, ValueMetaInterface.TYPE_STRING, "supports timestamp-date conversion" ); r.addValue( val, ValueMetaInterface.TYPE_STRING, supportsTimeStampToDateConversion() ? "Y" : "N" ); list.add( r ); // supports batch updates r = new RowMetaAndData(); r.addValue( par, ValueMetaInterface.TYPE_STRING, "supports batch updates" ); r.addValue( val, ValueMetaInterface.TYPE_STRING, supportsBatchUpdates() ? "Y" : "N" ); list.add( r ); // supports boolean values r = new RowMetaAndData(); r.addValue( par, ValueMetaInterface.TYPE_STRING, "supports boolean data type" ); r.addValue( val, ValueMetaInterface.TYPE_STRING, supportsBooleanDataType() ? "Y" : "N" ); list.add( r ); } return list; } /** * @return true if the database result sets support getTimeStamp() to retrieve date-time. (Date) */ public boolean supportsTimeStampToDateConversion() { return databaseInterface.supportsTimeStampToDateConversion(); } /** * @return true if the database JDBC driver supports batch updates For example Interbase doesn't support this! */ public boolean supportsBatchUpdates() { return databaseInterface.supportsBatchUpdates(); } /** * @return true if the database supports a boolean, bit, logical, ... datatype */ public boolean supportsBooleanDataType() { return databaseInterface.supportsBooleanDataType(); } /** * * @param b * Set to true if the database supports a boolean, bit, logical, ... datatype */ public void setSupportsBooleanDataType( boolean b ) { databaseInterface.setSupportsBooleanDataType( b ); } /** * @return true if the database supports the Timestamp data type (nanosecond precision and all) */ public boolean supportsTimestampDataType() { return databaseInterface.supportsTimestampDataType(); } /** * * @param b * Set to true if the database supports the Timestamp data type (nanosecond precision and all) */ public void setSupportsTimestampDataType( boolean b ) { databaseInterface.setSupportsTimestampDataType( b ); } /** * @return true if reserved words' case should be preserved */ public boolean preserveReservedCase() { return databaseInterface.preserveReservedCase(); } /** * @return true if reserved words' case should be preserved */ public void setPreserveReservedCase( boolean b ) { databaseInterface.setPreserveReservedCase( b ); } /** * Changes the names of the fields to their quoted equivalent if this is needed * * @param fields * The row of fields to change */ public void quoteReservedWords( RowMetaInterface fields ) { for ( int i = 0; i < fields.size(); i++ ) { ValueMetaInterface v = fields.getValueMeta( i ); v.setName( quoteField( v.getName() ) ); } } /** * @return a map of all the extra URL options you want to set. */ public Map<String, String> getExtraOptions() { return databaseInterface.getExtraOptions(); } /** * @return true if the database supports connection options in the URL, false if they are put in a Properties object. */ public boolean supportsOptionsInURL() { return databaseInterface.supportsOptionsInURL(); } /** * @return extra help text on the supported options on the selected database platform. */ public String getExtraOptionsHelpText() { return databaseInterface.getExtraOptionsHelpText(); } /** * @return true if the database JDBC driver supports getBlob on the resultset. If not we must use getBytes() to get * the data. */ public boolean supportsGetBlob() { return databaseInterface.supportsGetBlob(); } /** * @return The SQL to execute right after connecting */ public String getConnectSQL() { return databaseInterface.getConnectSQL(); } /** * @param sql * The SQL to execute right after connecting */ public void setConnectSQL( String sql ) { databaseInterface.setConnectSQL( sql ); } /** * @return true if the database supports setting the maximum number of return rows in a resultset. */ public boolean supportsSetMaxRows() { return databaseInterface.supportsSetMaxRows(); } /** * Verify the name of the database and if required, change it if it already exists in the list of databases. * * @param databases * the databases to check against. * @param oldname * the old name of the database * @return the new name of the database connection */ public String verifyAndModifyDatabaseName( List<DatabaseMeta> databases, String oldname ) { String name = getName(); if ( name.equalsIgnoreCase( oldname ) ) { return name; // nothing to see here: move along! } int nr = 2; while ( DatabaseMeta.findDatabase( databases, getName() ) != null ) { setName( name + " " + nr ); setDisplayName( name + " " + nr ); nr++; } return getName(); } /** * @return true if we want to use a database connection pool */ public boolean isUsingConnectionPool() { return databaseInterface.isUsingConnectionPool(); } /** * @param usePool * true if we want to use a database connection pool */ public void setUsingConnectionPool( boolean usePool ) { databaseInterface.setUsingConnectionPool( usePool ); } /** * @return the maximum pool size */ public int getMaximumPoolSize() { return databaseInterface.getMaximumPoolSize(); } /** * @param maximumPoolSize * the maximum pool size */ public void setMaximumPoolSize( int maximumPoolSize ) { databaseInterface.setMaximumPoolSize( maximumPoolSize ); } /** * @return the initial pool size */ public int getInitialPoolSize() { return databaseInterface.getInitialPoolSize(); } /** * @param initalPoolSize * the initial pool size */ public void setInitialPoolSize( int initalPoolSize ) { databaseInterface.setInitialPoolSize( initalPoolSize ); } /** * @return true if the connection contains partitioning information */ public boolean isPartitioned() { return databaseInterface.isPartitioned(); } /** * @param partitioned * true if the connection is set to contain partitioning information */ public void setPartitioned( boolean partitioned ) { databaseInterface.setPartitioned( partitioned ); } /** * @return the available partition/host/databases/port combinations in the cluster */ public PartitionDatabaseMeta[] getPartitioningInformation() { if ( !isPartitioned() ) { return new PartitionDatabaseMeta[] {}; } return databaseInterface.getPartitioningInformation(); } /** * @param partitionInfo * the available partition/host/databases/port combinations in the cluster */ public void setPartitioningInformation( PartitionDatabaseMeta[] partitionInfo ) { databaseInterface.setPartitioningInformation( partitionInfo ); } /** * Finds the partition metadata for the given partition iD * * @param partitionId * The partition ID to look for * @return the partition database metadata or null if nothing was found. */ public PartitionDatabaseMeta getPartitionMeta( String partitionId ) { PartitionDatabaseMeta[] partitionInfo = getPartitioningInformation(); for ( int i = 0; i < partitionInfo.length; i++ ) { if ( partitionInfo[i].getPartitionId().equals( partitionId ) ) { return partitionInfo[i]; } } return null; } public Properties getConnectionPoolingProperties() { return databaseInterface.getConnectionPoolingProperties(); } public void setConnectionPoolingProperties( Properties properties ) { databaseInterface.setConnectionPoolingProperties( properties ); } public String getSQLTableExists( String tablename ) { return databaseInterface.getSQLTableExists( tablename ); } public String getSQLColumnExists( String columnname, String tablename ) { return databaseInterface.getSQLColumnExists( columnname, tablename ); } public boolean needsToLockAllTables() { return databaseInterface.needsToLockAllTables(); } /** * @return true if the database is streaming results (normally this is an option just for MySQL). */ public boolean isStreamingResults() { return databaseInterface.isStreamingResults(); } /** * @param useStreaming * true if we want the database to stream results (normally this is an option just for MySQL). */ public void setStreamingResults( boolean useStreaming ) { databaseInterface.setStreamingResults( useStreaming ); } /** * @return true if all fields should always be quoted in db */ public boolean isQuoteAllFields() { return databaseInterface.isQuoteAllFields(); } /** * @param quoteAllFields * true if all fields in DB should be quoted. */ public void setQuoteAllFields( boolean quoteAllFields ) { databaseInterface.setQuoteAllFields( quoteAllFields ); } /** * @return true if all identifiers should be forced to lower case */ public boolean isForcingIdentifiersToLowerCase() { return databaseInterface.isForcingIdentifiersToLowerCase(); } /** * @param forceLowerCase * true if all identifiers should be forced to lower case */ public void setForcingIdentifiersToLowerCase( boolean forceLowerCase ) { databaseInterface.setForcingIdentifiersToLowerCase( forceLowerCase ); } /** * @return true if all identifiers should be forced to upper case */ public boolean isForcingIdentifiersToUpperCase() { return databaseInterface.isForcingIdentifiersToUpperCase(); } /** * @param forceUpperCase * true if all identifiers should be forced to upper case */ public void setForcingIdentifiersToUpperCase( boolean forceUpperCase ) { databaseInterface.setForcingIdentifiersToUpperCase( forceUpperCase ); } /** * Find a database with a certain name in an arraylist of databases. * * @param databases * The ArrayList of databases * @param dbname * The name of the database connection * @return The database object if one was found, null otherwise. */ public static final DatabaseMeta findDatabase( List<? extends SharedObjectInterface> databases, String dbname ) { if ( databases == null ) { return null; } for ( int i = 0; i < databases.size(); i++ ) { DatabaseMeta ci = (DatabaseMeta) databases.get( i ); if ( ci.getName().equalsIgnoreCase( dbname ) ) { return ci; } } return null; } public static int indexOfName( String[] databaseNames, String name ) { if ( databaseNames == null || name == null ) { return -1; } for ( int i = 0; i < databaseNames.length; i++ ) { String databaseName = databaseNames[ i ]; if ( name.equalsIgnoreCase( databaseName ) ) { return i; } } return -1; } /** * Find a database with a certain ID in an arraylist of databases. * * @param databases * The ArrayList of databases * @param id * The id of the database connection * @return The database object if one was found, null otherwise. */ public static final DatabaseMeta findDatabase( List<DatabaseMeta> databases, ObjectId id ) { if ( databases == null ) { return null; } for ( DatabaseMeta ci : databases ) { if ( ci.getObjectId() != null && ci.getObjectId().equals( id ) ) { return ci; } } return null; } @Override public void copyVariablesFrom( VariableSpace space ) { variables.copyVariablesFrom( space ); } @Override public String environmentSubstitute( String aString ) { return variables.environmentSubstitute( aString ); } @Override public String[] environmentSubstitute( String[] aString ) { return variables.environmentSubstitute( aString ); } @Override public String fieldSubstitute( String aString, RowMetaInterface rowMeta, Object[] rowData ) throws KettleValueException { return variables.fieldSubstitute( aString, rowMeta, rowData ); } @Override public VariableSpace getParentVariableSpace() { return variables.getParentVariableSpace(); } @Override public void setParentVariableSpace( VariableSpace parent ) { variables.setParentVariableSpace( parent ); } @Override public String getVariable( String variableName, String defaultValue ) { return variables.getVariable( variableName, defaultValue ); } @Override public String getVariable( String variableName ) { return variables.getVariable( variableName ); } @Override public boolean getBooleanValueOfVariable( String variableName, boolean defaultValue ) { if ( !Utils.isEmpty( variableName ) ) { String value = environmentSubstitute( variableName ); if ( !Utils.isEmpty( value ) ) { return ValueMetaBase.convertStringToBoolean( value ); } } return defaultValue; } @Override public void initializeVariablesFrom( VariableSpace parent ) { variables.initializeVariablesFrom( parent ); } @Override public String[] listVariables() { return variables.listVariables(); } @Override public void setVariable( String variableName, String variableValue ) { variables.setVariable( variableName, variableValue ); } @Override public void shareVariablesWith( VariableSpace space ) { variables = space; } @Override public void injectVariables( Map<String, String> prop ) { variables.injectVariables( prop ); } /** * @return the SQL Server instance */ public String getSQLServerInstance() { // This is also covered/persisted by JDBC option MS SQL Server / instancename / <somevalue> // We want to return <somevalue> // --> MSSQL.instancename return getExtraOptions().get( getPluginId() + ".instance" ); } /** * @param instanceName * the SQL Server instance */ public void setSQLServerInstance( String instanceName ) { // This is also covered/persisted by JDBC option MS SQL Server / instancename / <somevalue> // We want to return set <somevalue> // --> MSSQL.instancename if ( ( instanceName != null ) && ( instanceName.length() > 0 ) ) { addExtraOption( getPluginId(), "instance", instanceName ); } } /** * @return true if the Microsoft SQL server uses two decimals (..) to separate schema and table (default==false). */ public boolean isUsingDoubleDecimalAsSchemaTableSeparator() { return databaseInterface.isUsingDoubleDecimalAsSchemaTableSeparator(); } /** * @param useDoubleDecimalSeparator * true if we want the database to stream results (normally this is an option just for MySQL). */ public void setUsingDoubleDecimalAsSchemaTableSeparator( boolean useDoubleDecimalSeparator ) { databaseInterface.setUsingDoubleDecimalAsSchemaTableSeparator( useDoubleDecimalSeparator ); } /** * @return true if this database needs a transaction to perform a query (auto-commit turned off). */ public boolean isRequiringTransactionsOnQueries() { return databaseInterface.isRequiringTransactionsOnQueries(); } public String testConnection() { StringBuilder report = new StringBuilder(); // If the plug-in needs to provide connection information, we ask the DatabaseInterface... // try { DatabaseFactoryInterface factory = getDatabaseFactory(); return factory.getConnectionTestReport( this ); } catch ( ClassNotFoundException e ) { report .append( BaseMessages.getString( PKG, "BaseDatabaseMeta.TestConnectionReportNotImplemented.Message" ) ) .append( Const.CR ); report.append( BaseMessages.getString( PKG, "DatabaseMeta.report.ConnectionError", getName() ) + e.toString() + Const.CR ); report.append( Const.getStackTracker( e ) + Const.CR ); } catch ( Exception e ) { report.append( BaseMessages.getString( PKG, "DatabaseMeta.report.ConnectionError", getName() ) + e.toString() + Const.CR ); report.append( Const.getStackTracker( e ) + Const.CR ); } return report.toString(); } public DatabaseFactoryInterface getDatabaseFactory() throws Exception { PluginRegistry registry = PluginRegistry.getInstance(); PluginInterface plugin = registry.getPlugin( DatabasePluginType.class, databaseInterface.getPluginId() ); if ( plugin == null ) { throw new KettleDatabaseException( "database type with plugin id [" + databaseInterface.getPluginId() + "] couldn't be found!" ); } ClassLoader loader = registry.getClassLoader( plugin ); Class<?> clazz = Class.forName( databaseInterface.getDatabaseFactoryName(), true, loader ); return (DatabaseFactoryInterface) clazz.newInstance(); } public String getPreferredSchemaName() { return databaseInterface.getPreferredSchemaName(); } public void setPreferredSchemaName( String preferredSchemaName ) { databaseInterface.setPreferredSchemaName( preferredSchemaName ); } /** * Not used in this case, simply return root / */ @Override public RepositoryDirectoryInterface getRepositoryDirectory() { return new RepositoryDirectory(); } @Override public void setRepositoryDirectory( RepositoryDirectoryInterface repositoryDirectory ) { throw new RuntimeException( "Setting a directory on a database connection is not supported" ); } @Override public RepositoryObjectType getRepositoryElementType() { return REPOSITORY_ELEMENT_TYPE; } @Override public ObjectRevision getObjectRevision() { return objectRevision; } @Override public void setObjectRevision( ObjectRevision objectRevision ) { this.objectRevision = objectRevision; } @Override public String getDescription() { // NOT USED return null; } @Override public void setDescription( String description ) { // NOT USED } public boolean supportsSequenceNoMaxValueOption() { return databaseInterface.supportsSequenceNoMaxValueOption(); } public boolean requiresCreateTablePrimaryKeyAppend() { return databaseInterface.requiresCreateTablePrimaryKeyAppend(); } public boolean requiresCastToVariousForIsNull() { return databaseInterface.requiresCastToVariousForIsNull(); } public boolean isDisplaySizeTwiceThePrecision() { return databaseInterface.isDisplaySizeTwiceThePrecision(); } public boolean supportsPreparedStatementMetadataRetrieval() { return databaseInterface.supportsPreparedStatementMetadataRetrieval(); } public boolean isSystemTable( String tableName ) { return databaseInterface.isSystemTable( tableName ); } private boolean supportsNewLinesInSQL() { return databaseInterface.supportsNewLinesInSQL(); } public String getSQLListOfSchemas() { return databaseInterface.getSQLListOfSchemas(); } public int getMaxColumnsInIndex() { return databaseInterface.getMaxColumnsInIndex(); } public boolean supportsErrorHandlingOnBatchUpdates() { return databaseInterface.supportsErrorHandlingOnBatchUpdates(); } /** * Get the SQL to insert a new empty unknown record in a dimension. * * @param schemaTable * the schema-table name to insert into * @param keyField * The key field * @param versionField * the version field * @return the SQL to insert the unknown record into the SCD. */ public String getSQLInsertAutoIncUnknownDimensionRow( String schemaTable, String keyField, String versionField ) { return databaseInterface.getSQLInsertAutoIncUnknownDimensionRow( schemaTable, keyField, versionField ); } /** * @return true if this is a relational database you can explore. Return false for SAP, PALO, etc. */ public boolean isExplorable() { return databaseInterface.isExplorable(); } /** * * @return The SQL on this database to get a list of sequences. */ public String getSQLListOfSequences() { return databaseInterface.getSQLListOfSequences(); } public String quoteSQLString( String string ) { return databaseInterface.quoteSQLString( string ); } /** * @see DatabaseInterface#generateColumnAlias(int, String) */ public String generateColumnAlias( int columnIndex, String suggestedName ) { return databaseInterface.generateColumnAlias( columnIndex, suggestedName ); } public boolean isMySQLVariant() { return databaseInterface.isMySQLVariant(); } public Long getNextBatchId( Database ldb, String schemaName, String tableName, String fieldName ) throws KettleDatabaseException { return databaseInterface.getNextBatchId( this, ldb, schemaName, tableName, fieldName ); } public Object getValueFromResultSet( ResultSet rs, ValueMetaInterface val, int i ) throws KettleDatabaseException { return databaseInterface.getValueFromResultSet( rs, val, i ); } /** * Marker used to determine if the DatabaseMeta should be allowed to be modified/saved. It does NOT prevent object * modification. * * @return */ public boolean isReadOnly() { return readOnly; } /** * Sets the marker used to determine if the DatabaseMeta should be allowed to be modified/saved. Setting to true does * NOT prevent object modification. * * @return */ public void setReadOnly( boolean readOnly ) { this.readOnly = readOnly; } public String getSequenceNoMaxValueOption() { return databaseInterface.getSequenceNoMaxValueOption(); } /** * @return true if the database supports autoGeneratedKeys */ public boolean supportsAutoGeneratedKeys() { return databaseInterface.supportsAutoGeneratedKeys(); } /** * Customizes the ValueMetaInterface defined in the base * * @return String the create table statement */ public String getCreateTableStatement() { return databaseInterface.getCreateTableStatement(); } /** * Forms the drop table statement specific for a certain RDBMS. * * @param tableName Name of the table to drop * @return Drop table statement specific for the current database * @see <a href="http://jira.pentaho.com/browse/BISERVER-13024">BISERVER-13024</a> */ public String getDropTableIfExistsStatement( String tableName ) { if ( databaseInterface instanceof DatabaseInterfaceExtended ) { return ( (DatabaseInterfaceExtended) databaseInterface ).getDropTableIfExistsStatement( tableName ); } // A fallback statement in case somehow databaseInterface is of an old version. // This is the previous, and in fact, buggy implementation. See BISERVER-13024. return DROP_TABLE_STATEMENT + tableName; } /** * For testing */ protected LogChannelInterface getGeneralLogger() { return LogChannel.GENERAL; } /** * For testing */ protected DatabaseInterface getDbInterface( String typeCode ) throws KettleDatabaseException { return getDatabaseInterface( typeCode ); } }