// 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 java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;
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.KettleValueException;
import org.pentaho.di.core.row.ValueMetaInterface;
import org.pentaho.di.core.variables.VariableSpace;
import org.pentaho.di.repository.ObjectId;
/**
* This class contains the basic information on a database connection. It is not intended to be used other than the
* inheriting classes such as OracleDatabaseInfo, ...
*
* @author Matt
* @since 11-mrt-2005
*/
public abstract class BaseDatabaseMeta implements Cloneable, DatabaseInterfaceExtended {
/**
* The port number of the database as string: allows for parameterization.
*/
public static final String ATTRIBUTE_PORT_NUMBER = "PORT_NUMBER";
/**
* The SQL to execute at connect time (right after connecting)
*/
public static final String ATTRIBUTE_SQL_CONNECT = "SQL_CONNECT";
/**
* A flag to determine if we should use connection pooling or not.
*/
public static final String ATTRIBUTE_USE_POOLING = "USE_POOLING";
/**
* If we use connection pooling, this would contain the maximum pool size
*/
public static final String ATTRIBUTE_MAXIMUM_POOL_SIZE = "MAXIMUM_POOL_SIZE";
/**
* If we use connection pooling, this would contain the initial pool size
*/
public static final String ATTRIBUTE_INITIAL_POOL_SIZE = "INITIAL_POOL_SIZE";
/**
* The prefix for all the extra options attributes
*/
public static final String ATTRIBUTE_PREFIX_EXTRA_OPTION = "EXTRA_OPTION_";
/**
* A flag to determine if the connection is clustered or not.
*/
public static final String ATTRIBUTE_IS_CLUSTERED = "IS_CLUSTERED";
/**
* The clustering partition ID name prefix
*/
private static final String ATTRIBUTE_CLUSTER_PARTITION_PREFIX = "CLUSTER_PARTITION_";
/**
* The clustering hostname prefix
*/
public static final String ATTRIBUTE_CLUSTER_HOSTNAME_PREFIX = "CLUSTER_HOSTNAME_";
/**
* The clustering port prefix
*/
public static final String ATTRIBUTE_CLUSTER_PORT_PREFIX = "CLUSTER_PORT_";
/**
* The clustering database name prefix
*/
public static final String ATTRIBUTE_CLUSTER_DBNAME_PREFIX = "CLUSTER_DBNAME_";
/**
* The clustering database username prefix
*/
public static final String ATTRIBUTE_CLUSTER_USERNAME_PREFIX = "CLUSTER_USERNAME_";
/**
* The clustering database password prefix
*/
public static final String ATTRIBUTE_CLUSTER_PASSWORD_PREFIX = "CLUSTER_PASSWORD_";
/** The pooling parameters */
public static final String ATTRIBUTE_POOLING_PARAMETER_PREFIX = "POOLING_";
/**
* A flag to determine if we should use result streaming on MySQL
*/
public static final String ATTRIBUTE_USE_RESULT_STREAMING = "STREAM_RESULTS";
/**
* A flag to determine if we should use a double decimal separator to specify schema/table combinations on MS-SQL
* server
*/
public static final String ATTRIBUTE_MSSQL_DOUBLE_DECIMAL_SEPARATOR = "MSSQL_DOUBLE_DECIMAL_SEPARATOR";
/**
* A flag to determine if we should quote all fields
*/
public static final String ATTRIBUTE_QUOTE_ALL_FIELDS = "QUOTE_ALL_FIELDS";
/**
* A flag to determine if we should force all identifiers to lower case
*/
public static final String ATTRIBUTE_FORCE_IDENTIFIERS_TO_LOWERCASE = "FORCE_IDENTIFIERS_TO_LOWERCASE";
/**
* A flag to determine if we should force all identifiers to UPPER CASE
*/
public static final String ATTRIBUTE_FORCE_IDENTIFIERS_TO_UPPERCASE = "FORCE_IDENTIFIERS_TO_UPPERCASE";
/**
* The preferred schema to use if no other has been specified.
*/
public static final String ATTRIBUTE_PREFERRED_SCHEMA_NAME = "PREFERRED_SCHEMA_NAME";
/**
* Checkbox to allow you to configure if the database supports the boolean data type or not. Defaults to "false" for
* backward compatibility!
*/
public static final String ATTRIBUTE_SUPPORTS_BOOLEAN_DATA_TYPE = "SUPPORTS_BOOLEAN_DATA_TYPE";
/**
* Checkbox to allow you to configure if the database supports the Timestamp data type or not. Defaults to "false" for
* backward compatibility!
*/
public static final String ATTRIBUTE_SUPPORTS_TIMESTAMP_DATA_TYPE = "SUPPORTS_TIMESTAMP_DATA_TYPE";
/**
* Checkbox to allow you to configure if the reserved words will have their case changed during the handleCase call
*/
public static final String ATTRIBUTE_PRESERVE_RESERVED_WORD_CASE = "PRESERVE_RESERVED_WORD_CASE";
public static final String SEQUENCE_FOR_BATCH_ID = "SEQUENCE_FOR_BATCH_ID";
public static final String AUTOINCREMENT_SQL_FOR_BATCH_ID = "AUTOINCREMENT_SQL_FOR_BATCH_ID";
/**
* Boolean to indicate if savepoints can be released Most databases do, so we set it to true. Child classes can
* overwrite with false if need be.
*/
protected boolean releaseSavepoint = true;
/**
* The SQL, minus the table name, to select the number of rows from a table
*/
public static final String SELECT_COUNT_STATEMENT = "select count(*) FROM";
public static final DatabaseConnectionPoolParameter[] poolingParameters = new DatabaseConnectionPoolParameter[] {
new DatabaseConnectionPoolParameter(
"defaultAutoCommit", "true", "The default auto-commit state of connections created by this pool." ),
new DatabaseConnectionPoolParameter(
"defaultReadOnly", null, "The default read-only state of connections created by this pool.\n"
+ "If not set then the setReadOnly method will not be called.\n "
+ "(Some drivers don't support read only mode, ex: Informix)" ),
new DatabaseConnectionPoolParameter(
"defaultTransactionIsolation", null,
"the default TransactionIsolation state of connections created by this pool. "
+ "One of the following: (see javadoc)\n\n * NONE\n * "
+ "READ_COMMITTED\n * READ_UNCOMMITTED\n * REPEATABLE_READ * SERIALIZABLE\n" ),
new DatabaseConnectionPoolParameter(
"defaultCatalog", null, "The default catalog of connections created by this pool." ),
new DatabaseConnectionPoolParameter(
"initialSize", "0", "The initial number of connections that are created when the pool is started." ),
new DatabaseConnectionPoolParameter(
"maxActive", "8",
"The maximum number of active connections that can be allocated from this pool at the same time, "
+ "or non-positive for no limit." ),
new DatabaseConnectionPoolParameter(
"maxIdle", "8", "The maximum number of connections that can remain idle in the pool, "
+ "without extra ones being released, or negative for no limit." ),
new DatabaseConnectionPoolParameter(
"minIdle", "0", "The minimum number of connections that can remain idle in the pool, "
+ "without extra ones being created, or zero to create none." ),
new DatabaseConnectionPoolParameter(
"maxWait", "-1", "The maximum number of milliseconds that the pool will wait "
+ "(when there are no available connections) for a connection to be returned "
+ "before throwing an exception, or -1 to wait indefinitely." ),
new DatabaseConnectionPoolParameter(
"validationQuery", null, "The SQL query that will be used to validate connections from this pool "
+ "before returning them to the caller.\n"
+ "If specified, this query MUST be an SQL SELECT statement that returns at least one row." ),
new DatabaseConnectionPoolParameter(
"testOnBorrow", "true",
"The indication of whether objects will be validated before being borrowed from the pool.\n"
+ "If the object fails to validate, it will be dropped from the pool, "
+ "and we will attempt to borrow another.\n"
+ "NOTE - for a true value to have any effect, the validationQuery parameter "
+ "must be set to a non-null string." ),
new DatabaseConnectionPoolParameter(
"testOnReturn", "false",
"The indication of whether objects will be validated before being returned to the pool.\n"
+ "NOTE - for a true value to have any effect, the validationQuery parameter must be set "
+ "to a non-null string." ),
new DatabaseConnectionPoolParameter(
"testWhileIdle", "false",
"The indication of whether objects will be validated by the idle object evictor (if any). "
+ "If an object fails to validate, it will be dropped from the pool.\n"
+ "NOTE - for a true value to have any effect, the validationQuery parameter must be set to a "
+ "non-null string." ),
new DatabaseConnectionPoolParameter(
"timeBetweenEvictionRunsMillis", null,
"The number of milliseconds to sleep between runs of the idle object evictor thread. "
+ "When non-positive, no idle object evictor thread will be run." ),
new DatabaseConnectionPoolParameter(
"poolPreparedStatements", "false", "Enable prepared statement pooling for this pool." ),
new DatabaseConnectionPoolParameter(
"maxOpenPreparedStatements", "-1",
"The maximum number of open statements that can be allocated from the statement pool at the same time, "
+ "or zero for no limit." ),
new DatabaseConnectionPoolParameter(
"accessToUnderlyingConnectionAllowed", "false",
"Controls if the PoolGuard allows access to the underlying connection." ),
new DatabaseConnectionPoolParameter(
"removeAbandoned", "false",
"Flag to remove abandoned connections if they exceed the removeAbandonedTimout.\n"
+ "If set to true a connection is considered abandoned and eligible for removal "
+ "if it has been idle longer than the removeAbandonedTimeout. "
+ "Setting this to true can recover db connections from poorly written applications which "
+ "fail to close a connection." ),
new DatabaseConnectionPoolParameter(
"removeAbandonedTimeout", "300", "Timeout in seconds before an abandoned connection can be removed." ),
new DatabaseConnectionPoolParameter(
"logAbandoned", "false",
"Flag to log stack traces for application code which abandoned a Statement or Connection.\n"
+ "Logging of abandoned Statements and Connections adds overhead for every Connection open or "
+ "new Statement because a stack trace has to be generated." ), };
private static final String FIELDNAME_PROTECTOR = "_";
private String name;
private String displayName;
private int accessType; // Database.TYPE_ODBC / NATIVE / OCI
private String hostname;
private String databaseName;
private String username;
private String password;
private String servername; // Informix only!
private String dataTablespace; // data storage location, For Oracle & perhaps others
private String indexTablespace; // index storage location, For Oracle & perhaps others
private boolean changed;
private Properties attributes;
private ObjectId objectId;
private String pluginId;
private String pluginName;
public BaseDatabaseMeta() {
attributes = new Properties();
changed = false;
if ( getAccessTypeList() != null && getAccessTypeList().length > 0 ) {
accessType = getAccessTypeList()[0];
}
}
/**
* @return plugin ID of this class
*/
@Override
public String getPluginId() {
return pluginId;
}
/**
* @param pluginId
* The plugin ID to set.
*/
@Override
public void setPluginId( String pluginId ) {
this.pluginId = pluginId;
}
/**
* @return plugin name of this class
*/
@Override
public String getPluginName() {
return pluginName;
}
/**
* @param pluginName
* The plugin name to set.
*/
@Override
public void setPluginName( String pluginName ) {
this.pluginName = pluginName;
}
@Override
public abstract int[] getAccessTypeList();
/**
* @return Returns the accessType.
*/
@Override
public int getAccessType() {
return accessType;
}
/**
* @param accessType
* The accessType to set.
*/
@Override
public void setAccessType( int accessType ) {
this.accessType = accessType;
if ( this.accessType == DatabaseMeta.TYPE_ACCESS_JNDI ) {
this.username = "";
this.password = "";
}
}
/**
* @return Returns the changed.
*/
@Override
public boolean isChanged() {
return changed;
}
/**
* @param changed
* The changed to set.
*/
@Override
public void setChanged( boolean changed ) {
this.changed = changed;
}
/**
* @return Returns the connection name.
*/
@Override
public String getName() {
return name;
}
/**
* @param name
* The connection Name to set.
*/
@Override
public void setName( String name ) {
this.name = name;
// Default display name to be the same as connection name if it has not
// been initialized before
if ( ( getDisplayName() == null ) || ( getDisplayName().length() == 0 ) ) {
setDisplayName( name );
}
}
/**
* @return Returns the un-escaped connection Name.
*/
public String getDisplayName() {
return displayName;
}
/**
* @param displayName The un-escaped connection Name to set.
*/
public void setDisplayName( String displayName ) {
this.displayName = displayName;
}
/**
* @return Returns the databaseName.
*/
@Override
public String getDatabaseName() {
return databaseName;
}
/**
* @param databaseName
* The databaseName to set.
*/
@Override
public void setDatabaseName( String databaseName ) {
this.databaseName = databaseName;
}
/**
* @param databasePortNumberString
* The databasePortNumber string to set.
*/
@Override
public void setDatabasePortNumberString( String databasePortNumberString ) {
if ( databasePortNumberString != null ) {
getAttributes().put( BaseDatabaseMeta.ATTRIBUTE_PORT_NUMBER, databasePortNumberString );
}
}
/**
* @return Returns the databasePortNumber string.
*/
@Override
public String getDatabasePortNumberString() {
return getAttributes().getProperty( ATTRIBUTE_PORT_NUMBER, "-1" );
}
/**
* @return Returns the hostname.
*/
@Override
public String getHostname() {
return hostname;
}
/**
* @param hostname
* The hostname to set.
*/
@Override
public void setHostname( String hostname ) {
this.hostname = hostname;
}
/**
* @return Returns the id.
*/
@Override
public ObjectId getObjectId() {
return objectId;
}
/**
* @param id
* The id to set.
*/
@Override
public void setObjectId( ObjectId id ) {
this.objectId = id;
}
/**
* @return Returns the password.
*/
@Override
public String getPassword() {
return password;
}
/**
* @param password
* The password to set.
*/
@Override
public void setPassword( String password ) {
if ( this.accessType == DatabaseMeta.TYPE_ACCESS_JNDI ) {
this.password = "";
} else {
this.password = password;
}
}
/**
* @return Returns the servername.
*/
@Override
public String getServername() {
return servername;
}
/**
* @param servername
* The servername to set.
*/
@Override
public void setServername( String servername ) {
this.servername = servername;
}
/**
* @return Returns the tablespaceData.
*/
@Override
public String getDataTablespace() {
return dataTablespace;
}
/**
* @param dataTablespace
* The data tablespace to set.
*/
@Override
public void setDataTablespace( String dataTablespace ) {
this.dataTablespace = dataTablespace;
}
/**
* @return Returns the index tablespace.
*/
@Override
public String getIndexTablespace() {
return indexTablespace;
}
/**
* @param indexTablespace
* The index tablespace to set.
*/
@Override
public void setIndexTablespace( String indexTablespace ) {
this.indexTablespace = indexTablespace;
}
/**
* @return Returns the username.
*/
@Override
public String getUsername() {
return username;
}
/**
* @param username
* The username to set.
*/
@Override
public void setUsername( String username ) {
if ( this.accessType == DatabaseMeta.TYPE_ACCESS_JNDI ) {
this.username = "";
}
this.username = username;
}
/**
* @return The extra attributes for this database connection
*/
@Override
public Properties getAttributes() {
return attributes;
}
/**
* Set extra attributes on this database connection
*
* @param attributes
* The extra attributes to set on this database connection.
*/
@Override
public void setAttributes( Properties attributes ) {
this.attributes = attributes;
}
/**
* Clone the basic settings for this connection!
*/
@Override
public Object clone() {
BaseDatabaseMeta retval = null;
try {
retval = (BaseDatabaseMeta) super.clone();
// CLone the attributes as well...
retval.attributes = (Properties) attributes.clone();
} catch ( CloneNotSupportedException e ) {
throw new RuntimeException( e );
}
return retval;
}
/*
* *******************************************************************************
* DEFAULT SETTINGS FOR ALL DATABASES ********************************************************************************
*/
/**
* @return the default database port number
*/
@Override
public int getDefaultDatabasePort() {
return -1; // No default port or not used.
}
@Override public Map<String, String> getDefaultOptions() {
return Collections.emptyMap();
}
/**
* 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.
*/
@Override
public boolean supportsSetCharacterStream() {
return true;
}
/**
* @return Whether or not the database can use auto increment type of fields (pk)
*/
@Override
public boolean supportsAutoInc() {
return true;
}
@Override
public String getLimitClause( int nrRows ) {
return "";
}
@Override
public int getNotFoundTK( boolean use_autoinc ) {
return 0;
}
/**
* Get the SQL to get the next value of a sequence. (Oracle/PGSQL only)
*
* @param sequenceName
* The sequence name
* @return the SQL to get the next value of a sequence. (Oracle/PGSQL only)
*/
@Override
public String getSQLNextSequenceValue( String sequenceName ) {
return "";
}
/**
* Get the current value of a database sequence
*
* @param sequenceName
* The sequence to check
* @return The current value of a database sequence
*/
@Override
public String getSQLCurrentSequenceValue( String sequenceName ) {
return "";
}
/**
* Check if a sequence exists.
*
* @param sequenceName
* The sequence to check
* @return The SQL to get the name of the sequence back from the databases data dictionary
*/
@Override
public String getSQLSequenceExists( String sequenceName ) {
return "";
}
/**
* Checks whether or not the command setFetchSize() is supported by the JDBC driver...
*
* @return true is setFetchSize() is supported!
*/
@Override
public boolean isFetchSizeSupported() {
return true;
}
/**
* 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.
*/
@Override
public boolean needsPlaceHolder() {
return false;
}
/**
* @return true if the database supports schemas
*/
@Override
public boolean supportsSchemas() {
return true;
}
/**
* @return true if the database supports catalogs
*/
@Override
public boolean supportsCatalogs() {
return true;
}
/**
*
* @return true when the database engine supports empty transaction. (for example Informix does not on a non-ANSI
* database type!)
*/
@Override
public boolean supportsEmptyTransactions() {
return true;
}
/**
* @return the function for SUM agrregate
*/
@Override
public String getFunctionSum() {
return "SUM";
}
/**
* @return the function for Average agrregate
*/
@Override
public String getFunctionAverage() {
return "AVG";
}
/**
* @return the function for Minimum agrregate
*/
@Override
public String getFunctionMinimum() {
return "MIN";
}
/**
* @return the function for Maximum agrregate
*/
@Override
public String getFunctionMaximum() {
return "MAX";
}
/**
* @return the function for Count agrregate
*/
@Override
public String getFunctionCount() {
return "COUNT";
}
/**
* Get the schema-table combination to query the right table. Usually that is SCHEMA.TABLENAME, however there are
* exceptions to this rule...
*
* @param schema_name
* The schema name
* @param table_part
* The tablename
* @return the schema-table combination to query the right table.
*/
@Override
public String getSchemaTableCombination( String schema_name, String table_part ) {
return schema_name + "." + table_part;
}
/**
* Checks for quotes before quoting schema and table. Many dialects had hardcoded quotes, they probably didn't get
* updated properly when quoteFields() was introduced to DatabaseMeta.
*
* @param schemaPart
* @param tablePart
* @return quoted schema and table
*
* @deprecated we should phase this out in 5.0, but it's there to keep backwards compatibility in the 4.x releases.
*/
@Deprecated
public String getBackwardsCompatibleSchemaTableCombination( String schemaPart, String tablePart ) {
String schemaTable = "";
if ( schemaPart != null && ( schemaPart.contains( getStartQuote() ) || schemaPart.contains( getEndQuote() ) ) ) {
schemaTable += schemaPart;
} else {
schemaTable += getStartQuote() + schemaPart + getEndQuote();
}
schemaTable += ".";
if ( tablePart != null && ( tablePart.contains( getStartQuote() ) || tablePart.contains( getEndQuote() ) ) ) {
schemaTable += tablePart;
} else {
schemaTable += getStartQuote() + tablePart + getEndQuote();
}
return schemaTable;
}
/**
* Checks for quotes before quoting table. Many dialects had hardcoded quotes, they probably didn't get updated
* properly when quoteFields() was introduced to DatabaseMeta.
*
* @param tablePart
*
* @return quoted table
*
* @deprecated we should phase this out in 5.0, but it's there to keep backwards compatibility in the 4.x releases.
*/
@Deprecated
public String getBackwardsCompatibleTable( String tablePart ) {
if ( tablePart != null && ( tablePart.contains( getStartQuote() ) || tablePart.contains( getEndQuote() ) ) ) {
return tablePart;
} else {
return getStartQuote() + tablePart + getEndQuote();
}
}
/**
* 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)
*/
@Override
public int getMaxTextFieldLength() {
return DatabaseMeta.CLOB_LENGTH;
}
/**
* Get the maximum length of a text field (VARCHAR) for this database connection. If this size is exceeded use a CLOB.
*
* @return The maximum VARCHAR field length for this database type. (mostly identical to getMaxTextFieldLength() -
* CLOB_LENGTH)
*/
@Override
public int getMaxVARCHARLength() {
return DatabaseMeta.CLOB_LENGTH;
}
/**
* @return true if the database supports transactions.
*/
@Override
public boolean supportsTransactions() {
return true;
}
/**
* @return true if the database supports sequences
*/
@Override
public boolean supportsSequences() {
return false;
}
/**
* @return true if the database supports bitmap indexes
*/
@Override
public boolean supportsBitmapIndex() {
return true;
}
/**
* @return true if the database JDBC driver supports the setLong command
*/
@Override
public boolean supportsSetLong() {
return true;
}
/**
* Generates the SQL statement to drop a column from the specified table
*
* @param tablename
* The table to add
* @param v
* The column defined as a value
* @param tk
* the name of the technical key field
* @param use_autoinc
* whether or not this field uses auto increment
* @param pk
* the name of the primary key field
* @param semicolon
* whether or not to add a semi-colon behind the statement.
* @return the SQL statement to drop a column from the specified table
*/
@Override
public String getDropColumnStatement( String tablename, ValueMetaInterface v, String tk, boolean use_autoinc,
String pk, boolean semicolon ) {
return "ALTER TABLE " + tablename + " DROP " + v.getName() + Const.CR;
}
/**
* @return an array of reserved words for the database type...
*/
@Override
public String[] getReservedWords() {
return new String[] {};
}
/**
* @return true if reserved words need to be double quoted ("password", "select", ...)
*/
@Override
public boolean quoteReservedWords() {
return true;
}
/**
* @return The start quote sequence, mostly just double quote, but sometimes [, ...
*/
@Override
public String getStartQuote() {
return "\"";
}
/**
* @return The end quote sequence, mostly just double quote, but sometimes ], ...
*/
@Override
public String getEndQuote() {
return "\"";
}
/**
* @return true if Kettle can create a repository on this type of database.
*/
@Override
public boolean supportsRepository() {
return false;
}
/**
* @return a list of table types to retrieve tables for the database
*/
@Override
public String[] getTableTypes() {
return new String[] { "TABLE" };
}
/**
* @return a list of table types to retrieve views for the database
*/
@Override
public String[] getViewTypes() {
return new String[] { "VIEW" };
}
/**
* @return a list of table types to retrieve synonyms for the database
*/
@Override
public String[] getSynonymTypes() {
return new String[] { "SYNONYM" };
}
/**
* @return true if we need to supply the schema-name to getTables in order to get a correct list of items.
*/
@Override
public boolean useSchemaNameForTableList() {
return false;
}
/**
* @return true if the database supports views
*/
@Override
public boolean supportsViews() {
return true;
}
/**
* @return true if the database supports synonyms
*/
@Override
public boolean supportsSynonyms() {
return false;
}
/**
* @return The SQL on this database to get a list of stored procedures.
*/
@Override
public String getSQLListOfProcedures() {
return null;
}
/**
* @return The SQL on this database to get a list of sequences.
*/
@Override
public String getSQLListOfSequences() {
return null;
}
/**
* @param tableName
* The table to be truncated.
* @return The SQL statement to truncate a table: remove all rows from it without a transaction
*/
@Override
public String getTruncateTableStatement( String tableName ) {
return "TRUNCATE TABLE " + tableName;
}
/**
* Returns the minimal SQL to launch in order to determine the layout of the resultset for a given database table
*
* @param tableName
* The name of the table to determine the layout for
* @return The SQL to launch.
*/
@Override
public String getSQLQueryFields( String tableName ) {
return "SELECT * FROM " + tableName;
}
/**
* Most databases round number(7,2) 17.29999999 to 17.30, but some don't.
*
* @return true if the database supports roundinf of floating point data on update/insert
*/
@Override
public boolean supportsFloatRoundingOnUpdate() {
return true;
}
/**
* @param tableNames
* The names of the tables to lock
* @return The SQL command to lock database tables for write purposes. null is returned in case locking is not
* supported on the target database. null is the default value
*/
@Override
public String getSQLLockTables( String[] tableNames ) {
return null;
}
/**
* @param tableNames
* The names of the tables to unlock
* @return The SQL command to unlock database tables. null is returned in case locking is not supported on the target
* database. null is the default value
*/
@Override
public String getSQLUnlockTables( String[] tableNames ) {
return null;
}
/**
* @return true if the database supports timestamp to date conversion. For example Interbase doesn't support this!
*/
@Override
public boolean supportsTimeStampToDateConversion() {
return true;
}
/**
* @return true if the database JDBC driver supports batch updates For example Interbase doesn't support this!
*/
@Override
public boolean supportsBatchUpdates() {
return true;
}
/**
* @return true if the database supports a boolean, bit, logical, ... datatype The default is false: map to a string.
*/
@Override
public boolean supportsBooleanDataType() {
String usePool = attributes.getProperty( ATTRIBUTE_SUPPORTS_BOOLEAN_DATA_TYPE, "N" );
return "Y".equalsIgnoreCase( usePool );
}
/**
* @param b
* Set to true if the database supports a boolean, bit, logical, ... datatype
*/
@Override
public void setSupportsBooleanDataType( boolean b ) {
attributes.setProperty( ATTRIBUTE_SUPPORTS_BOOLEAN_DATA_TYPE, b ? "Y" : "N" );
}
/**
* @return true if the database supports the Timestamp data type (nanosecond precision and all)
*/
@Override
public boolean supportsTimestampDataType() {
String supportsTimestamp = attributes.getProperty( ATTRIBUTE_SUPPORTS_TIMESTAMP_DATA_TYPE, "N" );
return "Y".equalsIgnoreCase( supportsTimestamp );
}
/**
*
* @param b
* Set to true if the database supports the Timestamp data type (nanosecond precision and all)
*/
@Override
public void setSupportsTimestampDataType( boolean b ) {
attributes.setProperty( ATTRIBUTE_SUPPORTS_TIMESTAMP_DATA_TYPE, b ? "Y" : "N" );
}
/**
* @return true if reserved words' case should be preserved
*/
@Override
public boolean preserveReservedCase() {
String usePool = attributes.getProperty( ATTRIBUTE_PRESERVE_RESERVED_WORD_CASE, "Y" );
return "Y".equalsIgnoreCase( usePool );
}
/**
* @param b
* Set to true if reserved words' case should be preserved
*/
@Override
public void setPreserveReservedCase( boolean b ) {
attributes.setProperty( ATTRIBUTE_PRESERVE_RESERVED_WORD_CASE, b ? "Y" : "N" );
}
/**
* @return true if the database defaults to naming tables and fields in uppercase. True for most databases except for
* stuborn stuff like Postgres ;-)
*/
@Override
public boolean isDefaultingToUppercase() {
return true;
}
/**
* @return all the extra options that are set to be used for the database URL
*/
@Override
public Map<String, String> getExtraOptions() {
Map<String, String> map = new Hashtable<String, String>();
for ( Enumeration<Object> keys = attributes.keys(); keys.hasMoreElements(); ) {
String attribute = (String) keys.nextElement();
if ( attribute.startsWith( ATTRIBUTE_PREFIX_EXTRA_OPTION ) ) {
String value = attributes.getProperty( attribute, "" );
// Add to the map...
map.put( attribute.substring( ATTRIBUTE_PREFIX_EXTRA_OPTION.length() ), value );
}
}
return map;
}
/**
* 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
*/
@Override
public void addExtraOption( String databaseTypeCode, String option, String value ) {
attributes.put( ATTRIBUTE_PREFIX_EXTRA_OPTION + databaseTypeCode + "." + option, value );
}
/**
* @return The extra option separator in database URL for this platform (usually this is semicolon ; )
*/
@Override
public String getExtraOptionSeparator() {
return ";";
}
/**
* @return The extra option value separator in database URL for this platform (usually this is the equal sign = )
*/
@Override
public String getExtraOptionValueSeparator() {
return "=";
}
/**
* @return This indicator separates the normal URL from the options
*/
@Override
public String getExtraOptionIndicator() {
return ";";
}
/**
* @return true if the database supports connection options in the URL, false if they are put in a Properties object.
*/
@Override
public boolean supportsOptionsInURL() {
return true;
}
/**
* @return extra help text on the supported options on the selected database platform.
*/
@Override
public String getExtraOptionsHelpText() {
return null;
}
/**
* @return true if the database JDBC driver supports getBlob on the resultset. If not we must use getBytes() to get
* the data.
*/
@Override
public boolean supportsGetBlob() {
return true;
}
/**
* @return The SQL to execute right after connecting
*/
@Override
public String getConnectSQL() {
return attributes.getProperty( ATTRIBUTE_SQL_CONNECT );
}
/**
* @param sql
* The SQL to execute right after connecting
*/
@Override
public void setConnectSQL( String sql ) {
attributes.setProperty( ATTRIBUTE_SQL_CONNECT, sql );
}
/**
* @return true if the database supports setting the maximum number of return rows in a resultset.
*/
@Override
public boolean supportsSetMaxRows() {
return true;
}
/**
* @return true if we want to use a database connection pool
*/
@Override
public boolean isUsingConnectionPool() {
String usePool = attributes.getProperty( ATTRIBUTE_USE_POOLING );
return "Y".equalsIgnoreCase( usePool );
}
/**
* @param usePool
* true if we want to use a database connection pool
*/
@Override
public void setUsingConnectionPool( boolean usePool ) {
attributes.setProperty( ATTRIBUTE_USE_POOLING, usePool ? "Y" : "N" );
}
/**
* @return the maximum pool size
*/
@Override
public int getMaximumPoolSize() {
return Const.toInt(
attributes.getProperty( ATTRIBUTE_MAXIMUM_POOL_SIZE ), ConnectionPoolUtil.defaultMaximumNrOfConnections );
}
/**
* @param maximumPoolSize
* the maximum pool size
*/
@Override
public void setMaximumPoolSize( int maximumPoolSize ) {
attributes.setProperty( ATTRIBUTE_MAXIMUM_POOL_SIZE, Integer.toString( maximumPoolSize ) );
}
/**
* @return the initial pool size
*/
@Override
public int getInitialPoolSize() {
return Const.toInt(
attributes.getProperty( ATTRIBUTE_INITIAL_POOL_SIZE ), ConnectionPoolUtil.defaultInitialNrOfConnections );
}
/**
* @param initialPoolSize
* the initial pool size
*/
@Override
public void setInitialPoolSize( int initialPoolSize ) {
attributes.setProperty( ATTRIBUTE_INITIAL_POOL_SIZE, Integer.toString( initialPoolSize ) );
}
/**
* @return true if we want to use a database connection pool
*/
@Override
public boolean isPartitioned() {
String isClustered = attributes.getProperty( ATTRIBUTE_IS_CLUSTERED );
return "Y".equalsIgnoreCase( isClustered );
}
/**
* @param clustered
* true if we want to use a database connection pool
*/
@Override
public void setPartitioned( boolean clustered ) {
attributes.setProperty( ATTRIBUTE_IS_CLUSTERED, clustered ? "Y" : "N" );
}
/**
* @return the available partition/host/databases/port combinations in the cluster
*/
@Override
public PartitionDatabaseMeta[] getPartitioningInformation() {
// find the maximum number of attributes starting with ATTRIBUTE_CLUSTER_HOSTNAME_PREFIX
int nr = 0;
while ( ( attributes.getProperty( ATTRIBUTE_CLUSTER_HOSTNAME_PREFIX + nr ) ) != null ) {
nr++;
}
PartitionDatabaseMeta[] clusterInfo = new PartitionDatabaseMeta[nr];
for ( nr = 0; nr < clusterInfo.length; nr++ ) {
String partitionId = attributes.getProperty( ATTRIBUTE_CLUSTER_PARTITION_PREFIX + nr );
String hostname = attributes.getProperty( ATTRIBUTE_CLUSTER_HOSTNAME_PREFIX + nr );
String port = attributes.getProperty( ATTRIBUTE_CLUSTER_PORT_PREFIX + nr );
String dbName = attributes.getProperty( ATTRIBUTE_CLUSTER_DBNAME_PREFIX + nr );
String username = attributes.getProperty( ATTRIBUTE_CLUSTER_USERNAME_PREFIX + nr );
String password = attributes.getProperty( ATTRIBUTE_CLUSTER_PASSWORD_PREFIX + nr );
clusterInfo[nr] = new PartitionDatabaseMeta( partitionId, hostname, port, dbName );
clusterInfo[nr].setUsername( username );
clusterInfo[nr].setPassword( Encr.decryptPasswordOptionallyEncrypted( password ) );
}
return clusterInfo;
}
/**
* @param clusterInfo
* the available partition/host/databases/port combinations in the cluster
*/
@Override
public void setPartitioningInformation( PartitionDatabaseMeta[] clusterInfo ) {
for ( int nr = 0; nr < clusterInfo.length; nr++ ) {
PartitionDatabaseMeta meta = clusterInfo[nr];
attributes.put( ATTRIBUTE_CLUSTER_PARTITION_PREFIX + nr, Const.NVL( meta.getPartitionId(), "" ) );
attributes.put( ATTRIBUTE_CLUSTER_HOSTNAME_PREFIX + nr, Const.NVL( meta.getHostname(), "" ) );
attributes.put( ATTRIBUTE_CLUSTER_PORT_PREFIX + nr, Const.NVL( meta.getPort(), "" ) );
attributes.put( ATTRIBUTE_CLUSTER_DBNAME_PREFIX + nr, Const.NVL( meta.getDatabaseName(), "" ) );
attributes.put( ATTRIBUTE_CLUSTER_USERNAME_PREFIX + nr, Const.NVL( meta.getUsername(), "" ) );
attributes.put( ATTRIBUTE_CLUSTER_PASSWORD_PREFIX + nr, Const.NVL( Encr
.encryptPasswordIfNotUsingVariables( meta.getPassword() ), "" ) );
}
}
/**
* @return The set of properties (newly created object) that contains the connection pooling parameters All
* environment variables will be replaced here.
*/
@Override
public Properties getConnectionPoolingProperties() {
Properties properties = new Properties();
for ( Iterator<Object> iter = attributes.keySet().iterator(); iter.hasNext(); ) {
String element = (String) iter.next();
if ( element.startsWith( ATTRIBUTE_POOLING_PARAMETER_PREFIX ) ) {
String key = element.substring( ATTRIBUTE_POOLING_PARAMETER_PREFIX.length() );
String value = attributes.getProperty( element );
properties.put( key, value );
}
}
return properties;
}
@Override
public void setConnectionPoolingProperties( Properties properties ) {
// Clear our the previous set of pool parameters
for ( Iterator<Object> iter = attributes.keySet().iterator(); iter.hasNext(); ) {
String key = (String) iter.next();
if ( key.startsWith( ATTRIBUTE_POOLING_PARAMETER_PREFIX ) ) {
attributes.remove( key );
}
}
for ( Iterator<Object> iter = properties.keySet().iterator(); iter.hasNext(); ) {
String element = (String) iter.next();
String value = properties.getProperty( element );
if ( !Utils.isEmpty( element ) && !Utils.isEmpty( value ) ) {
attributes.put( ATTRIBUTE_POOLING_PARAMETER_PREFIX + element, value );
}
}
}
@Override
public String getSQLTableExists( String tablename ) {
return "SELECT 1 FROM " + tablename;
}
@Override
public String getSQLColumnExists( String columnname, String tablename ) {
return "SELECT " + columnname + " FROM " + tablename;
}
@Override
public boolean needsToLockAllTables() {
return true;
}
/**
* @return true if the database is streaming results (normally this is an option just for MySQL).
*/
@Override
public boolean isStreamingResults() {
String usePool = attributes.getProperty( ATTRIBUTE_USE_RESULT_STREAMING, "Y" ); // DEFAULT TO YES!!
return "Y".equalsIgnoreCase( usePool );
}
/**
* @param useStreaming
* true if we want the database to stream results (normally this is an option just for MySQL).
*/
@Override
public void setStreamingResults( boolean useStreaming ) {
attributes.setProperty( ATTRIBUTE_USE_RESULT_STREAMING, useStreaming ? "Y" : "N" );
}
/**
* @return true if all fields should always be quoted in db
*/
@Override
public boolean isQuoteAllFields() {
String quoteAllFields = attributes.getProperty( ATTRIBUTE_QUOTE_ALL_FIELDS, "N" ); // DEFAULT TO NO!!
return "Y".equalsIgnoreCase( quoteAllFields );
}
/**
* @param quoteAllFields
* true if we want the database to stream results (normally this is an option just for MySQL).
*/
@Override
public void setQuoteAllFields( boolean quoteAllFields ) {
attributes.setProperty( ATTRIBUTE_QUOTE_ALL_FIELDS, quoteAllFields ? "Y" : "N" );
}
/**
* @return true if all identifiers should be forced to lower case
*/
@Override
public boolean isForcingIdentifiersToLowerCase() {
String forceLowerCase = attributes.getProperty( ATTRIBUTE_FORCE_IDENTIFIERS_TO_LOWERCASE, "N" ); // DEFAULT TO NO!!
return "Y".equalsIgnoreCase( forceLowerCase );
}
/**
* @param forceLowerCase
* true if all identifiers should be forced to lower case
*/
@Override
public void setForcingIdentifiersToLowerCase( boolean forceLowerCase ) {
attributes.setProperty( ATTRIBUTE_FORCE_IDENTIFIERS_TO_LOWERCASE, forceLowerCase ? "Y" : "N" );
}
/**
* @return true if all identifiers should be forced to upper case
*/
@Override
public boolean isForcingIdentifiersToUpperCase() {
String forceUpperCase = attributes.getProperty( ATTRIBUTE_FORCE_IDENTIFIERS_TO_UPPERCASE, "N" ); // DEFAULT TO NO!!
return "Y".equalsIgnoreCase( forceUpperCase );
}
/**
* @param forceUpperCase
* true if all identifiers should be forced to upper case
*/
@Override
public void setForcingIdentifiersToUpperCase( boolean forceUpperCase ) {
attributes.setProperty( ATTRIBUTE_FORCE_IDENTIFIERS_TO_UPPERCASE, forceUpperCase ? "Y" : "N" );
}
/**
* @return true if we use a double decimal separator to specify schema/table combinations on MS-SQL server
*/
@Override
public boolean isUsingDoubleDecimalAsSchemaTableSeparator() {
String usePool = attributes.getProperty( ATTRIBUTE_MSSQL_DOUBLE_DECIMAL_SEPARATOR, "N" ); // DEFAULT TO YES!!
return "Y".equalsIgnoreCase( usePool );
}
/**
* @param useDoubleDecimalSeparator
* true if we should use a double decimal separator to specify schema/table combinations on MS-SQL server
*/
@Override
public void setUsingDoubleDecimalAsSchemaTableSeparator( boolean useDoubleDecimalSeparator ) {
attributes.setProperty( ATTRIBUTE_MSSQL_DOUBLE_DECIMAL_SEPARATOR, useDoubleDecimalSeparator ? "Y" : "N" );
}
/**
* @return true if this database needs a transaction to perform a query (auto-commit turned off).
*/
@Override
public boolean isRequiringTransactionsOnQueries() {
return true;
}
/**
* You can use this method to supply an alternate factory for the test method in the dialogs. This is useful for
* plugins like SAP/R3 and PALO.
*
* @return the name of the database test factory to use.
*/
@Override
public String getDatabaseFactoryName() {
return DatabaseFactory.class.getName();
}
/**
* @return The preferred schema name of this database connection.
*/
@Override
public String getPreferredSchemaName() {
return attributes.getProperty( ATTRIBUTE_PREFERRED_SCHEMA_NAME );
}
/**
* @param preferredSchemaName
* The preferred schema name of this database connection.
*/
@Override
public void setPreferredSchemaName( String preferredSchemaName ) {
attributes.setProperty( ATTRIBUTE_PREFERRED_SCHEMA_NAME, preferredSchemaName );
}
/**
* Verifies on the specified database connection if an index exists on the fields with the specified name.
*
* @param database
* a connected database
* @param schemaName
* @param tableName
* @param idx_fields
* @return true if the index exists, false if it doesn't.
* @throws KettleDatabaseException
*/
@Override
public boolean checkIndexExists( Database database, String schemaName, String tableName, String[] idx_fields ) throws KettleDatabaseException {
String tablename = database.getDatabaseMeta().getQuotedSchemaTableCombination( schemaName, tableName );
boolean[] exists = new boolean[idx_fields.length];
for ( int i = 0; i < exists.length; i++ ) {
exists[i] = false;
}
try {
// Get a list of all the indexes for this table
ResultSet indexList = null;
try {
indexList = database.getDatabaseMetaData().getIndexInfo( null, null, tablename, false, true );
while ( indexList.next() ) {
// String tablen = indexList.getString("TABLE_NAME");
// String indexn = indexList.getString("INDEX_NAME");
String column = indexList.getString( "COLUMN_NAME" );
// int pos = indexList.getShort("ORDINAL_POSITION");
// int type = indexList.getShort("TYPE");
int idx = Const.indexOfString( column, idx_fields );
if ( idx >= 0 ) {
exists[idx] = true;
}
}
} finally {
if ( indexList != null ) {
indexList.close();
}
}
// See if all the fields are indexed...
boolean all = true;
for ( int i = 0; i < exists.length && all; i++ ) {
if ( !exists[i] ) {
all = false;
}
}
return all;
} catch ( Exception e ) {
throw new KettleDatabaseException( "Unable to determine if indexes exists on table [" + tablename + "]", e );
}
}
/**
* @return true if the database supports the NOMAXVALUE sequence option. The default is false, AS/400 and DB2 support
* this.
*/
@Override
public boolean supportsSequenceNoMaxValueOption() {
return false;
}
/**
* @return true if we need to append the PRIMARY KEY block in the create table block after the fields, required for
* Cache.
*/
@Override
public boolean requiresCreateTablePrimaryKeyAppend() {
return false;
}
/**
* @return true if the database requires you to cast a parameter to varchar before comparing to null. Only required
* for DB2 and Vertica
*
*/
@Override
public boolean requiresCastToVariousForIsNull() {
return false;
}
/**
* @return Handles the special case of DB2 where the display size returned is twice the precision. In that case, the
* length is the precision.
*
*/
@Override
public boolean isDisplaySizeTwiceThePrecision() {
return false;
}
/**
* Most databases allow you to retrieve result metadata by preparing a SELECT statement.
*
* @return true if the database supports retrieval of query metadata from a prepared statement. False if the query
* needs to be executed first.
*/
@Override
public boolean supportsPreparedStatementMetadataRetrieval() {
return true;
}
/**
* @return true if this database only supports metadata retrieval on a result set, never on a statement (even if the
* statement has been executed)
*/
@Override
public boolean supportsResultSetMetadataRetrievalOnly() {
return false;
}
/**
* @param tableName
* @return true if the specified table is a system table
*/
@Override
public boolean isSystemTable( String tableName ) {
return false;
}
/**
* @return true if the database supports newlines in a SQL statements.
*/
@Override
public boolean supportsNewLinesInSQL() {
return true;
}
/**
* @return the SQL to retrieve the list of schemas or null if the JDBC metadata needs to be used.
*/
@Override
public String getSQLListOfSchemas() {
return null;
}
/**
* @return The maximum number of columns in a database, <=0 means: no known limit
*/
@Override
public int getMaxColumnsInIndex() {
return 0;
}
/**
* @return true if the database supports error handling (recovery of failure) while doing batch updates.
*/
@Override
public boolean supportsErrorHandlingOnBatchUpdates() {
return true;
}
/**
* 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.
*/
@Override
public String getSQLInsertAutoIncUnknownDimensionRow( String schemaTable, String keyField, String versionField ) {
return "insert into " + schemaTable + "(" + keyField + ", " + versionField + ") values (0, 1)";
}
/**
* @return true if this is a relational database you can explore. Return false for SAP, PALO, etc.
*/
@Override
public boolean isExplorable() {
return true;
}
/**
* @return The name of the XUL overlay file to display extra options. This is only used in case of a non-standard
* plugin. Usually this method returns null.
*/
@Override
public String getXulOverlayFile() {
return null;
}
/**
* @param string
* @return A string that is properly quoted for use in a SQL statement (insert, update, delete, etc)
*/
@Override
public String quoteSQLString( String string ) {
string = string.replaceAll( "'", "''" );
string = string.replaceAll( "\\n", "\\\\n" );
string = string.replaceAll( "\\r", "\\\\r" );
return "'" + string + "'";
}
/**
* Build the SQL to count the number of rows in the passed table.
*
* @param tableName
* @return
*/
@Override
public String getSelectCountStatement( String tableName ) {
return SELECT_COUNT_STATEMENT + " " + tableName;
}
@Override
public String generateColumnAlias( int columnIndex, String suggestedName ) {
return "COL" + Integer.toString( columnIndex );
}
/**
* Parse all possible statements from the provided SQL script.
*
* @param sqlScript
* Raw SQL Script to be parsed into executable statements.
* @return List of parsed SQL statements to be executed separately.
*/
@Override
public List<String> parseStatements( String sqlScript ) {
List<SqlScriptStatement> scriptStatements = getSqlScriptStatements( sqlScript );
List<String> statements = new ArrayList<String>();
for ( SqlScriptStatement scriptStatement : scriptStatements ) {
statements.add( scriptStatement.getStatement() );
}
return statements;
}
/**
* Parse the statements in the provided SQL script, provide more information about where each was found in the script.
*
* @param sqlScript
* Raw SQL Script to be parsed into executable statements.
* @return List of SQL script statements to be executed separately.
*/
@Override
public List<SqlScriptStatement> getSqlScriptStatements( String sqlScript ) {
List<SqlScriptStatement> statements = new ArrayList<SqlScriptStatement>();
String all = sqlScript;
int from = 0;
int to = 0;
int length = all.length();
while ( to < length ) {
char c = all.charAt( to );
// Skip comment lines...
//
while ( all.substring( from ).startsWith( "--" ) ) {
int nextLineIndex = all.indexOf( Const.CR, from );
from = nextLineIndex + Const.CR.length();
if ( to >= length ) {
break;
}
c = all.charAt( c );
}
if ( to >= length ) {
break;
}
// Skip over double quotes...
//
if ( c == '"' ) {
int nextDQuoteIndex = all.indexOf( '"', to + 1 );
if ( nextDQuoteIndex >= 0 ) {
to = nextDQuoteIndex + 1;
}
}
// Skip over back-ticks
if ( c == '`' ) {
int nextBacktickIndex = all.indexOf( '`', to + 1 );
if ( nextBacktickIndex >= 0 ) {
to = nextBacktickIndex + 1;
}
}
c = all.charAt( to );
if ( c == '\'' ) {
boolean skip = true;
// Don't skip over \' or ''
//
if ( to > 0 ) {
char prevChar = all.charAt( to - 1 );
if ( prevChar == '\\' || prevChar == '\'' ) {
skip = false;
}
}
// Jump to the next quote and continue from there.
//
while ( skip ) {
int nextQuoteIndex = all.indexOf( '\'', to + 1 );
if ( nextQuoteIndex >= 0 ) {
to = nextQuoteIndex + 1;
skip = false;
if ( to < all.length() ) {
char nextChar = all.charAt( to );
if ( nextChar == '\'' ) {
skip = true;
to++;
}
}
if ( to > 0 ) {
char prevChar = all.charAt( to - 2 );
if ( prevChar == '\\' ) {
skip = true;
to++;
}
}
}
}
}
c = all.charAt( to );
// end of statement
if ( c == ';' || to >= length - 1 ) {
if ( to >= length - 1 ) {
to++; // grab last char also!
}
String stat = all.substring( from, to );
if ( !onlySpaces( stat ) ) {
String s = Const.trim( stat );
statements.add( new SqlScriptStatement(
s, from, to, s.toUpperCase().startsWith( "SELECT" ) || s.toLowerCase().startsWith( "show" ) ) );
}
to++;
from = to;
} else {
to++;
}
}
return statements;
}
/**
* @param str
* @return True if {@code str} contains only spaces.
*/
protected boolean onlySpaces( String str ) {
for ( int i = 0; i < str.length(); i++ ) {
int c = str.charAt( i );
if ( c != ' ' && c != '\t' && c != '\n' && c != '\r' ) {
return false;
}
}
return true;
}
/**
* @return true if the database is a MySQL variant, like MySQL 5.1, InfiniDB, InfoBright, and so on.
*/
@Override
public boolean isMySQLVariant() {
return false;
}
/**
* @return true if the database type can be tested against a database instance
*/
public boolean canTest() {
return true;
}
/**
* @return true if the database name is a required parameter
*/
public boolean requiresName() {
return true;
}
/**
* Returns a true of savepoints can be released, false if not.
*
* @return
*/
@Override
public boolean releaseSavepoint() {
return releaseSavepoint;
}
public Long getNextBatchIdUsingSequence( String sequenceName, String schemaName, DatabaseMeta dbm, Database ldb ) throws KettleDatabaseException {
return ldb.getNextSequenceValue( schemaName, sequenceName, null );
}
public Long getNextBatchIdUsingAutoIncSQL( String autoIncSQL, DatabaseMeta dbm, Database ldb ) throws KettleDatabaseException {
Long rtn = null;
PreparedStatement stmt = ldb.prepareSQL( autoIncSQL, true );
try {
stmt.executeUpdate();
RowMetaAndData rmad = ldb.getGeneratedKeys( stmt );
if ( rmad.getRowMeta().size() > 0 ) {
rtn = rmad.getRowMeta().getInteger( rmad.getData(), 0 );
} else {
throw new KettleDatabaseException( "Unable to retrieve value of auto-generated technical key : "
+ "no value found!" );
}
} catch ( KettleValueException kve ) {
throw new KettleDatabaseException( kve );
} catch ( SQLException sqlex ) {
throw new KettleDatabaseException( sqlex );
} finally {
try {
stmt.close();
} catch ( SQLException ignored ) {
// Ignored
}
}
return rtn;
}
public Long getNextBatchIdUsingLockTables( DatabaseMeta dbm, Database ldb, String schemaName, String tableName,
String fieldName ) throws KettleDatabaseException {
// The old way of doing things...
Long rtn = null;
// Make sure we lock that table to avoid concurrency issues
String schemaAndTable = dbm.getQuotedSchemaTableCombination( schemaName, tableName );
ldb.lockTables( new String[] { schemaAndTable, } );
try {
// Now insert value -1 to create a real write lock blocking the other
// requests.. FCFS
String sql = "INSERT INTO " + schemaAndTable + " (" + dbm.quoteField( fieldName ) + ") values (-1)";
ldb.execStatement( sql );
// Now this next lookup will stall on the other connections
//
rtn = ldb.getNextValue( null, schemaName, tableName, fieldName );
} finally {
// Remove the -1 record again...
String sql = "DELETE FROM " + schemaAndTable + " WHERE " + dbm.quoteField( fieldName ) + "= -1";
ldb.execStatement( sql );
ldb.unlockTables( new String[] { schemaAndTable, } );
}
return rtn;
}
@Override
public Long getNextBatchId( DatabaseMeta dbm, Database ldb,
String schemaName, String tableName, String fieldName ) throws KettleDatabaseException {
// Always take off autocommit.
ldb.setCommit( 10 );
//
// Temporary work-around to handle batch-id from extended options
// Eventually want this promoted to proper dialogs and such
//
Map<String, String> connectionExtraOptions = this.getExtraOptions();
String sequenceProp = this.getPluginId() + "." + SEQUENCE_FOR_BATCH_ID;
String autoIncSQLProp = this.getPluginId() + "." + AUTOINCREMENT_SQL_FOR_BATCH_ID;
if ( connectionExtraOptions != null ) {
if ( this.supportsSequences() && connectionExtraOptions.containsKey( sequenceProp ) ) {
return getNextBatchIdUsingSequence( connectionExtraOptions.get( sequenceProp ), schemaName, dbm, ldb );
} else if ( this.supportsAutoInc() && connectionExtraOptions.containsKey( autoIncSQLProp ) ) {
return getNextBatchIdUsingAutoIncSQL( connectionExtraOptions.get( autoIncSQLProp ), dbm, ldb );
}
}
return getNextBatchIdUsingLockTables( dbm, ldb, schemaName, tableName, fieldName );
}
/**
* Returns the tablespace DDL fragment for a "Data" tablespace. In most databases that use tablespaces this is where
* the tables are to be created.
*
* @param variables
* variables used for possible substitution
* @param databaseMeta
* databaseMeta the database meta used for possible string enclosure of the tablespace. This method needs
* this as this is done after environmental substitution.
*
* @return String the tablespace name for tables in the format "tablespace TABLESPACE_NAME". The TABLESPACE_NAME and
* the passed DatabaseMata determines if TABLESPACE_NAME is to be enclosed in quotes.
*/
@Override
public String getDataTablespaceDDL( VariableSpace variables, DatabaseMeta databaseMeta ) {
return getTablespaceDDL( variables, databaseMeta, databaseMeta.getDatabaseInterface().getDataTablespace() );
}
/**
* Returns the tablespace DDL fragment for a "Index" tablespace.
*
* @param variables
* variables used for possible substitution
* @param databaseMeta
* databaseMeta the database meta used for possible string enclosure of the tablespace. This method needs
* this as this is done after environmental substitution.
*
* @return String the tablespace name for indices in the format "tablespace TABLESPACE_NAME". The TABLESPACE_NAME and
* the passed DatabaseMata determines if TABLESPACE_NAME is to be enclosed in quotes.
*/
@Override
public String getIndexTablespaceDDL( VariableSpace variables, DatabaseMeta databaseMeta ) {
return getTablespaceDDL( variables, databaseMeta, databaseMeta.getDatabaseInterface().getIndexTablespace() );
}
/**
* Returns an empty string as most databases do not support tablespaces. Subclasses can override this method to
* generate the DDL.
*
* @param variables
* variables needed for variable substitution.
* @param databaseMeta
* databaseMeta needed for it's quoteField method. Since we are doing variable substitution we need to meta
* so that we can act on the variable substitution first and then the creation of the entire string that will
* be retuned.
* @param tablespaceName
* tablespaceName name of the tablespace.
*
* @return String an empty String as most databases do not use tablespaces.
*/
public String getTablespaceDDL( VariableSpace variables, DatabaseMeta databaseMeta, String tablespaceName ) {
return "";
}
/**
* This method allows a database dialect to convert database specific data types to Kettle data types.
*
* @param rs
* The result set to use
* @param val
* The description of the value to retrieve
* @param i
* the index on which we need to retrieve the value, 0-based.
* @return The correctly converted Kettle data type corresponding to the valueMeta description.
* @throws KettleDatabaseException
*/
@Override
public Object getValueFromResultSet( ResultSet rs, ValueMetaInterface val, int i ) throws KettleDatabaseException {
return val.getValueFromResultSet( this, rs, i );
}
/**
* @return true if the database supports the use of safe-points and if it is appropriate to ever use it (default to
* false)
*/
@Override
public boolean useSafePoints() {
return false;
}
/**
* @return true if the database supports error handling (the default). Returns false for certain databases (SQLite)
* that invalidate a prepared statement or even the complete connection when an error occurs.
*/
@Override
public boolean supportsErrorHandling() {
return true;
}
@Override
public String getSQLValue( ValueMetaInterface valueMeta, Object valueData, String dateFormat ) throws KettleValueException {
StringBuilder ins = new StringBuilder();
if ( valueMeta.isNull( valueData ) ) {
ins.append( "null" );
} else {
// Normal cases...
//
switch ( valueMeta.getType() ) {
case ValueMetaInterface.TYPE_BOOLEAN:
case ValueMetaInterface.TYPE_STRING:
String string = valueMeta.getString( valueData );
// Have the database dialect do the quoting.
// This also adds the single quotes around the string (thanks to PostgreSQL)
//
string = quoteSQLString( string );
ins.append( string );
break;
case ValueMetaInterface.TYPE_DATE:
Date date = valueMeta.getDate( valueData );
if ( Utils.isEmpty( dateFormat ) ) {
ins.append( "'" + valueMeta.getString( valueData ) + "'" );
} else {
try {
java.text.SimpleDateFormat formatter = new java.text.SimpleDateFormat( dateFormat );
ins.append( "'" + formatter.format( date ) + "'" );
} catch ( Exception e ) {
throw new KettleValueException( "Error : ", e );
}
}
break;
default:
ins.append( valueMeta.getString( valueData ) );
break;
}
}
return ins.toString();
}
protected String getFieldnameProtector() {
return FIELDNAME_PROTECTOR;
}
/**
* Sanitize a string for usage as a field name
* <ul>
* <li>Append an underscore to any field name that matches a reserved word</li>
* <li>Replaces spaces with underscores</li>
* <li>Prefixes a string with underscore that begins with a number</li>
* </ul>
*
* @param fieldname
* value to sanitize
* @return
*/
@Override
public String getSafeFieldname( String fieldname ) {
StringBuilder newName = new StringBuilder( fieldname.length() );
char[] protectors = getFieldnameProtector().toCharArray();
// alpha numerics , underscores, field protectors only
for ( int idx = 0; idx < fieldname.length(); idx++ ) {
char c = fieldname.charAt( idx );
if ( ( c >= 'a' && c <= 'z' ) || ( c >= 'A' && c <= 'Z' ) || ( c >= '0' && c <= '9' ) || ( c == '_' ) ) {
newName.append( c );
} else if ( c == ' ' ) {
newName.append( '_' );
} else {
// allow protectors
for ( char protector : protectors ) {
if ( c == protector ) {
newName.append( c );
}
}
}
// else {
// swallow this character
// }
}
fieldname = newName.toString();
// don't allow reserved words
for ( String reservedWord : getReservedWords() ) {
if ( fieldname.equalsIgnoreCase( reservedWord ) ) {
fieldname = fieldname + getFieldnameProtector();
}
}
fieldname = fieldname.replace( " ", getFieldnameProtector() );
// can't start with a number
if ( fieldname.matches( "^[0-9].*" ) ) {
fieldname = getFieldnameProtector() + fieldname;
}
return fieldname;
}
/**
* @return string with the no max value sequence option.
*/
@Override
public String getSequenceNoMaxValueOption() {
return "NOMAXVALUE";
}
/**
* @return true if the database supports autoGeneratedKeys
*/
@Override
public boolean supportsAutoGeneratedKeys() {
return true;
}
/**
* Customizes the ValueMetaInterface defined in the base
*
* @param v the determined valueMetaInterface
* @param rm the sql result
* @param index the index to the column
* @return ValueMetaInterface customized with the data base specific types
*/
@Override
public ValueMetaInterface customizeValueFromSQLType( ValueMetaInterface v, java.sql.ResultSetMetaData rm, int index )
throws SQLException {
return null;
}
/**
* Customizes the ValueMetaInterface defined in the base
*
* @return String the create table statement
*/
@Override
public String getCreateTableStatement() {
return "CREATE TABLE ";
}
/**
* Forms drop table statement.
* This standard construct syntax is not legal for certain RDBMSs,
* and should be overridden according to their specifics.
*
* @param tableName Name of the table to drop
* @return Standard drop table statement
*/
@Override
public String getDropTableIfExistsStatement( String tableName ) {
return "DROP TABLE IF EXISTS " + tableName;
}
@Override
public boolean fullExceptionLog( Exception e ) {
return true;
}
@Override
public void addDefaultOptions() {
}
}