DatabaseDataSet.java

  1. /*
  2.  *
  3.  * The DbUnit Database Testing Framework
  4.  * Copyright (C)2002-2004, DbUnit.org
  5.  *
  6.  * This library is free software; you can redistribute it and/or
  7.  * modify it under the terms of the GNU Lesser General Public
  8.  * License as published by the Free Software Foundation; either
  9.  * version 2.1 of the License, or (at your option) any later version.
  10.  *
  11.  * This library is distributed in the hope that it will be useful,
  12.  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13.  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  14.  * Lesser General Public License for more details.
  15.  *
  16.  * You should have received a copy of the GNU Lesser General Public
  17.  * License along with this library; if not, write to the Free Software
  18.  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
  19.  *
  20.  */

  21. package org.dbunit.database;

  22. import java.sql.Connection;
  23. import java.sql.DatabaseMetaData;
  24. import java.sql.ResultSet;
  25. import java.sql.SQLException;
  26. import java.util.HashSet;
  27. import java.util.Locale;
  28. import org.dbunit.DatabaseUnitRuntimeException;
  29. import org.dbunit.dataset.AbstractDataSet;
  30. import org.dbunit.dataset.Column;
  31. import org.dbunit.dataset.DataSetException;
  32. import org.dbunit.dataset.DataSetUtils;
  33. import org.dbunit.dataset.IDataSet;
  34. import org.dbunit.dataset.ITable;
  35. import org.dbunit.dataset.ITableIterator;
  36. import org.dbunit.dataset.ITableMetaData;
  37. import org.dbunit.dataset.NoSuchTableException;
  38. import org.dbunit.dataset.OrderedTableNameMap;
  39. import org.dbunit.dataset.filter.ITableFilterSimple;
  40. import org.dbunit.util.QualifiedTableName;
  41. import org.dbunit.util.SQLHelper;
  42. import org.slf4j.Logger;
  43. import org.slf4j.LoggerFactory;

  44. /**
  45.  * Provides access to a database instance as a {@link IDataSet}.
  46.  *
  47.  * @author Manuel Laflamme
  48.  * @author Last changed by: $Author$
  49.  * @version $Revision$ $Date$
  50.  * @since 1.0 (Feb 17, 2002)
  51.  */
  52. public class DatabaseDataSet extends AbstractDataSet
  53. {

  54.     /**
  55.      * Logger for this class
  56.      */
  57.     private static final Logger logger = LoggerFactory.getLogger(DatabaseDataSet.class);

  58.     private final IDatabaseConnection _connection;
  59.     private OrderedTableNameMap _tableMap = null;
  60.     private SchemaSet _schemaSet = new SchemaSet(isCaseSensitiveTableNames());

  61.     private final ITableFilterSimple _tableFilter;
  62.     private final ITableFilterSimple _oracleRecycleBinTableFilter;

  63.     /**
  64.      * Creates a new database data set
  65.      * @param connection
  66.      * @throws SQLException
  67.      */
  68.     DatabaseDataSet(IDatabaseConnection connection) throws SQLException
  69.     {
  70.         this(connection, connection.getConfig().getFeature(DatabaseConfig.FEATURE_CASE_SENSITIVE_TABLE_NAMES));
  71.     }


  72.     /**
  73.      * Creates a new database data set
  74.      * @param connection The database connection
  75.      * @param caseSensitiveTableNames Whether or not this dataset should use case sensitive table names
  76.      * @throws SQLException
  77.      * @since 2.4
  78.      */
  79.     public DatabaseDataSet(IDatabaseConnection connection, boolean caseSensitiveTableNames) throws SQLException
  80.     {
  81.         this(connection, caseSensitiveTableNames, null);
  82.     }

  83.     /**
  84.      * Creates a new database data set
  85.      * @param connection The database connection
  86.      * @param caseSensitiveTableNames Whether or not this dataset should use case sensitive table names
  87.      * @param tableFilter Table filter to specify tables to be omitted in this dataset. Can be <code>null</code>.
  88.      * @throws SQLException
  89.      * @since 2.4.3
  90.      */
  91.     public DatabaseDataSet(IDatabaseConnection connection, boolean caseSensitiveTableNames, ITableFilterSimple tableFilter)
  92.     throws SQLException
  93.     {
  94.         super(caseSensitiveTableNames);
  95.         if (connection == null) {
  96.             throw new NullPointerException(
  97.             "The parameter 'connection' must not be null");
  98.         }
  99.         _connection = connection;
  100.         _tableFilter = tableFilter;
  101.         _oracleRecycleBinTableFilter = new OracleRecycleBinTableFilter(connection.getConfig());
  102.     }



  103.     static String getSelectStatement(String schema, ITableMetaData metaData, String escapePattern)
  104.     throws DataSetException
  105.     {
  106.         if (logger.isDebugEnabled())
  107.         {
  108.             logger.debug("getSelectStatement(schema={}, metaData={}, escapePattern={}) - start",
  109.                     schema, metaData, escapePattern);
  110.         }

  111.         Column[] columns = metaData.getColumns();
  112.         Column[] primaryKeys = metaData.getPrimaryKeys();

  113.         if(columns.length==0){
  114.             throw new DatabaseUnitRuntimeException("At least one column is required to build a valid select statement. "+
  115.                     "Cannot load data for " + metaData);
  116.         }

  117.         // select
  118.         final StringBuilder sqlBuffer = new StringBuilder(128);
  119.         sqlBuffer.append("select ");
  120.         for (int i = 0; i < columns.length; i++)
  121.         {
  122.             if (i > 0)
  123.             {
  124.                 sqlBuffer.append(", ");
  125.             }
  126.             String columnName = new QualifiedTableName(
  127.                     columns[i].getColumnName(), null, escapePattern).getQualifiedName();
  128.             sqlBuffer.append(columnName);
  129.         }

  130.         // from
  131.         sqlBuffer.append(" from ");
  132.         sqlBuffer.append(new QualifiedTableName(
  133.                 metaData.getTableName(), schema, escapePattern).getQualifiedName());

  134.         // order by
  135.         for (int i = 0; i < primaryKeys.length; i++)
  136.         {
  137.             if (i == 0)
  138.             {
  139.                 sqlBuffer.append(" order by ");
  140.             }
  141.             else
  142.             {
  143.                 sqlBuffer.append(", ");
  144.             }
  145.             sqlBuffer.append(new QualifiedTableName(primaryKeys[i].getColumnName(), null, escapePattern).getQualifiedName());

  146.         }

  147.         return sqlBuffer.toString();
  148.     }

  149.     /**
  150.      * Get all the table names form the database that are not system tables.
  151.      */
  152.     private void initialize(String schema) throws DataSetException
  153.     {
  154.         logger.debug("initialize() - start");

  155.         DatabaseConfig config = _connection.getConfig();
  156.         boolean qualifiedTableNamesActive = Boolean.TRUE == config.getProperty(DatabaseConfig.FEATURE_QUALIFIED_TABLE_NAMES);
  157.        
  158.         if(schema == null || !qualifiedTableNamesActive)
  159.     {
  160.       // If FEATURE_QUALIFIED_TABLE_NAMES is inactive or no schema did have been provided
  161.       schema = getDefaultSchema();
  162.     }
  163.        
  164.         if (_tableMap != null && _schemaSet.contains(schema))
  165.         {
  166.             return;
  167.         }

  168.         try
  169.         {
  170.             logger.debug("Initializing the data set from the database...");

  171.             Connection jdbcConnection = _connection.getConnection();
  172.             DatabaseMetaData databaseMetaData = jdbcConnection.getMetaData();

  173.             if(SQLHelper.isSybaseDb(jdbcConnection.getMetaData()) && !jdbcConnection.getMetaData().getUserName().equals(schema) ){
  174.                 logger.warn("For sybase the schema name should be equal to the user name. " +
  175.                         "Otherwise the DatabaseMetaData#getTables() method might not return any columns. " +
  176.                 "See dbunit tracker #1628896 and http://issues.apache.org/jira/browse/TORQUE-40?page=all");
  177.             }

  178.             String[] tableType = (String[])config.getProperty(DatabaseConfig.PROPERTY_TABLE_TYPE);
  179.             IMetadataHandler metadataHandler = (IMetadataHandler) config.getProperty(DatabaseConfig.PROPERTY_METADATA_HANDLER);

  180.             ResultSet resultSet = metadataHandler.getTables(databaseMetaData, schema, tableType);

  181.             if(logger.isDebugEnabled())
  182.             {
  183.                 logger.debug(SQLHelper.getDatabaseInfo(jdbcConnection.getMetaData()));
  184.                 logger.debug("metadata resultset={}", resultSet);
  185.             }

  186.             try
  187.             {
  188.         if (_tableMap == null) {
  189.           _tableMap = super.createTableNameMap();
  190.         }
  191.         _schemaSet.add(schema);
  192.                 while (resultSet.next())
  193.                 {
  194.                     String schemaName = metadataHandler.getSchema(resultSet);
  195.                     String tableName = resultSet.getString(3);

  196.                     if(_tableFilter != null && !_tableFilter.accept(tableName))
  197.                     {
  198.                         logger.debug("Skipping table '{}'", tableName);
  199.                         continue;
  200.                     }
  201.                     if(!_oracleRecycleBinTableFilter.accept(tableName))
  202.                     {
  203.                         logger.debug("Skipping oracle recycle bin table '{}'", tableName);
  204.                         continue;
  205.                     }
  206.                     if (schema == null && !_schemaSet.contains(schemaName)) {
  207.                       _schemaSet.add(schemaName);
  208.                     }

  209.                     QualifiedTableName qualifiedTableName = new QualifiedTableName(tableName, schemaName);
  210.                     tableName = qualifiedTableName.getQualifiedNameIfEnabled(config);

  211.                     // Put the table into the table map
  212.                     _tableMap.add(tableName, null);
  213.                 }
  214.             }
  215.             finally
  216.             {
  217.                 resultSet.close();
  218.             }
  219.         }
  220.         catch (SQLException e)
  221.         {
  222.             throw new DataSetException(e);
  223.         }
  224.     }

  225.   private String getDefaultSchema() {
  226.     return _connection.getSchema();
  227.   }

  228.     ////////////////////////////////////////////////////////////////////////////
  229.     // AbstractDataSet class

  230.     protected ITableIterator createIterator(boolean reversed)
  231.     throws DataSetException
  232.     {
  233.         if(logger.isDebugEnabled())
  234.         {
  235.             logger.debug("createIterator(reversed={}) - start", String.valueOf(reversed));
  236.         }

  237.         String[] names = getTableNames();
  238.         if (reversed)
  239.         {
  240.             names = DataSetUtils.reverseStringArray(names);
  241.         }

  242.         return new DatabaseTableIterator(names, this);
  243.     }

  244.     ////////////////////////////////////////////////////////////////////////////
  245.     // IDataSet interface

  246.     public String[] getTableNames() throws DataSetException
  247.     {
  248.         initialize(null);

  249.         return _tableMap.getTableNames();
  250.     }

  251.     public ITableMetaData getTableMetaData(String tableName) throws DataSetException
  252.     {
  253.         logger.debug("getTableMetaData(tableName={}) - start", tableName);

  254.         QualifiedTableName qualifiedTableName = new QualifiedTableName(tableName, getDefaultSchema());
  255.        
  256.         initialize(qualifiedTableName.getSchema());

  257.         // Verify if table exist in the database
  258.         if (!_tableMap.containsTable(tableName))
  259.         {
  260.             logger.error("Table '{}' not found in tableMap={}", tableName,
  261.                     _tableMap);
  262.             throw new NoSuchTableException(tableName);
  263.         }

  264.         // Try to find cached metadata
  265.         ITableMetaData metaData = (ITableMetaData)_tableMap.get(tableName);
  266.         if (metaData != null)
  267.         {
  268.             return metaData;
  269.         }

  270.         // Create metadata and cache it
  271.         metaData = new DatabaseTableMetaData(tableName, _connection, true, super.isCaseSensitiveTableNames());
  272.         // Put the metadata object into the cache map
  273.         _tableMap.update(tableName, metaData);

  274.         return metaData;
  275.     }

  276.     public ITable getTable(String tableName) throws DataSetException
  277.     {
  278.         logger.debug("getTable(tableName={}) - start", tableName);

  279.         QualifiedTableName qualifiedTableName = new QualifiedTableName(tableName, getDefaultSchema());
  280.        
  281.         initialize(qualifiedTableName.getSchema());

  282.         try
  283.         {
  284.             ITableMetaData metaData = getTableMetaData(tableName);

  285.             DatabaseConfig config = _connection.getConfig();
  286.             IResultSetTableFactory factory = (IResultSetTableFactory)config.getProperty(
  287.                     DatabaseConfig.PROPERTY_RESULTSET_TABLE_FACTORY);
  288.             return factory.createTable(metaData, _connection);
  289.         }
  290.         catch (SQLException e)
  291.         {
  292.             throw new DataSetException(e);
  293.         }
  294.     }

  295.     private static class SchemaSet extends HashSet<String>
  296.     {
  297.         private static final long serialVersionUID = 1L;

  298.         private static final String NULL_REPLACEMENT =
  299.                 "NULL_REPLACEMENT_HASHKEY";

  300.         private boolean isCaseSensitive;

  301.         private SchemaSet(boolean isCaseSensitive)
  302.         {
  303.             this.isCaseSensitive = isCaseSensitive;
  304.         }

  305.         @Override
  306.         public boolean contains(Object o)
  307.         {
  308.             return super.contains(normalizeSchema(o));
  309.         }

  310.         @Override
  311.         public boolean add(String e)
  312.         {
  313.             return super.add(normalizeSchema(e));
  314.         }

  315.         private String normalizeSchema(Object source)
  316.         {
  317.             if (source == null)
  318.             {
  319.                 return NULL_REPLACEMENT;
  320.             } else if (!isCaseSensitive)
  321.             {
  322.                 return source.toString().toUpperCase(Locale.ENGLISH);
  323.             }
  324.             return source.toString();
  325.         }
  326.     }

  327.     private static class OracleRecycleBinTableFilter implements ITableFilterSimple
  328.     {
  329.         private final DatabaseConfig _config;

  330.         public OracleRecycleBinTableFilter(DatabaseConfig config)
  331.         {
  332.             this._config = config;
  333.         }

  334.         public boolean accept(String tableName) throws DataSetException
  335.         {
  336.             // skip oracle 10g recycle bin system tables if enabled
  337.             if(_config.getFeature(DatabaseConfig.FEATURE_SKIP_ORACLE_RECYCLEBIN_TABLES)) {
  338.                 // Oracle 10g workaround
  339.                 // don't process system tables (oracle recycle bin tables) which
  340.                 // are reported to the application due a bug in the oracle JDBC driver
  341.                 if (tableName.startsWith("BIN$"))
  342.                 {
  343.                     return false;
  344.                 }
  345.             }

  346.             return true;
  347.         }
  348.     }
  349. }