DatabaseTableMetaData.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.ArrayList;
  27. import java.util.Arrays;
  28. import java.util.Collections;
  29. import java.util.List;

  30. import org.dbunit.dataset.AbstractTableMetaData;
  31. import org.dbunit.dataset.Column;
  32. import org.dbunit.dataset.Columns;
  33. import org.dbunit.dataset.DataSetException;
  34. import org.dbunit.dataset.ITableMetaData;
  35. import org.dbunit.dataset.NoSuchTableException;
  36. import org.dbunit.dataset.datatype.IDataTypeFactory;
  37. import org.dbunit.dataset.filter.IColumnFilter;
  38. import org.dbunit.util.QualifiedTableName;
  39. import org.dbunit.util.SQLHelper;
  40. import org.slf4j.Logger;
  41. import org.slf4j.LoggerFactory;

  42. /**
  43.  * Container for the metadata for one database table. The metadata is initialized
  44.  * using a {@link IDatabaseConnection}.
  45.  *
  46.  * @author Manuel Laflamme
  47.  * @author Last changed by: $Author$
  48.  * @version $Revision$ $Date$
  49.  * @since Mar 8, 2002
  50.  * @see ITableMetaData
  51.  */
  52. public class DatabaseTableMetaData extends AbstractTableMetaData
  53. {

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

  58.     /**
  59.      * Table name, potentially qualified
  60.      */
  61.     private final QualifiedTableName _qualifiedTableNameSupport;
  62.     private final String _originalTableName;
  63.     private final IDatabaseConnection _connection;
  64.     private Column[] _columns;
  65.     private Column[] _primaryKeys;
  66.     private boolean _caseSensitiveMetaData;
  67.     //added by hzhan032
  68.     private IColumnFilter lastKeyFilter;

  69.    
  70.     DatabaseTableMetaData(String tableName, IDatabaseConnection connection) throws DataSetException
  71.     {
  72.         this(tableName, connection, true);
  73.     }
  74.    
  75.     /**
  76.      * Creates a new database table metadata
  77.      * @param tableName The name of the table - can be fully qualified
  78.      * @param connection The database connection
  79.      * @param validate Whether or not to validate the given input data. It is not recommended to
  80.      * set the validation to <code>false</code> because it is then possible to create an instance
  81.      * of this object for a db table that does not exist.
  82.      * @throws DataSetException
  83.      */
  84.     DatabaseTableMetaData(String tableName, IDatabaseConnection connection, boolean validate) throws DataSetException
  85.     {
  86.         this(tableName, connection, validate, false);
  87.     }
  88.    
  89.     /**
  90.      * Creates a new database table metadata
  91.      * @param tableName The name of the table - can be fully qualified
  92.      * @param connection The database connection
  93.      * @param validate Whether or not to validate the given input data. It is not recommended to
  94.      * set the validation to <code>false</code> because it is then possible to create an instance
  95.      * of this object for a db table that does not exist.
  96.      * @param caseSensitiveMetaData Whether or not the metadata looked up in a case sensitive way
  97.      * @throws DataSetException
  98.      * @since 2.4.1
  99.      */
  100.     DatabaseTableMetaData(final String tableName, IDatabaseConnection connection, boolean validate, boolean caseSensitiveMetaData) throws DataSetException
  101.     {
  102.         if (tableName == null) {
  103.             throw new NullPointerException("The parameter 'tableName' must not be null");
  104.         }
  105.         if (connection == null) {
  106.             throw new NullPointerException("The parameter 'connection' must not be null");
  107.         }
  108.        
  109.         _connection = connection;
  110.         _caseSensitiveMetaData = caseSensitiveMetaData;

  111.         try
  112.         {
  113.              Connection jdbcConnection = connection.getConnection();
  114.              if(!caseSensitiveMetaData)
  115.              {
  116.                  _originalTableName = SQLHelper.correctCase(tableName, jdbcConnection);
  117.                  SQLHelper.logDebugIfValueChanged(tableName, _originalTableName, "Corrected table name:", DatabaseTableMetaData.class);
  118.              }
  119.              else
  120.              {
  121.                  _originalTableName = tableName;
  122.              }
  123.              
  124.              // qualified names support - table name and schema is stored here
  125.              _qualifiedTableNameSupport = new QualifiedTableName(_originalTableName, _connection.getSchema());

  126.              if(validate)
  127.              {
  128.                  String schemaName = _qualifiedTableNameSupport.getSchema();
  129.                  String plainTableName = _qualifiedTableNameSupport.getTable();
  130.                  logger.debug("Validating if table '{}' exists in schema '{}' ...", plainTableName, schemaName);
  131.                  try {
  132.                      DatabaseConfig config = connection.getConfig();
  133.                      IMetadataHandler metadataHandler = (IMetadataHandler) config.getProperty(DatabaseConfig.PROPERTY_METADATA_HANDLER);
  134.                      DatabaseMetaData databaseMetaData = jdbcConnection.getMetaData();
  135.                      if(!metadataHandler.tableExists(databaseMetaData, schemaName, plainTableName))
  136.                      {
  137.                          throw new NoSuchTableException("Did not find table '" + plainTableName + "' in schema '" + schemaName + "'");
  138.                      }
  139.                  }
  140.                  catch (SQLException e)
  141.                  {
  142.                      throw new DataSetException("Exception while validation existence of table '" + plainTableName + "'", e);
  143.                  }
  144.              }
  145.              else
  146.              {
  147.                  logger.debug("Validation switched off. Will not check if table exists.");
  148.              }
  149.         }
  150.         catch (SQLException e)
  151.         {
  152.             throw new DataSetException("Exception while retrieving JDBC connection from dbunit connection '" + connection + "'", e);
  153.         }
  154.        
  155.     }

  156.     /**
  157.      * @param tableName
  158.      * @param resultSet
  159.      * @param dataTypeFactory
  160.      * @return The table metadata created for the given parameters
  161.      * @throws DataSetException
  162.      * @throws SQLException
  163.      * @deprecated since 2.3.0. use {@link ResultSetTableMetaData#ResultSetTableMetaData(String, ResultSet, IDataTypeFactory, boolean)}
  164.      */
  165.     public static ITableMetaData createMetaData(String tableName,
  166.             ResultSet resultSet, IDataTypeFactory dataTypeFactory)
  167.             throws DataSetException, SQLException
  168.     {
  169.         if (logger.isDebugEnabled())
  170.         {
  171.             logger.debug("createMetaData(tableName={}, resultSet={}, dataTypeFactory={}) - start",
  172.                     new Object[]{ tableName, resultSet, dataTypeFactory });
  173.         }

  174.         return new ResultSetTableMetaData(tableName, resultSet, dataTypeFactory, false);
  175.     }


  176.    
  177.     /**
  178.      * @param tableName
  179.      * @param resultSet
  180.      * @param connection
  181.      * @return The table metadata created for the given parameters
  182.      * @throws SQLException
  183.      * @throws DataSetException
  184.      * @deprecated since 2.3.0. use {@link org.dbunit.database.ResultSetTableMetaData#ResultSetTableMetaData(String, ResultSet, IDatabaseConnection, boolean)}
  185.      */
  186.     public static ITableMetaData createMetaData(String tableName,
  187.             ResultSet resultSet, IDatabaseConnection connection)
  188.             throws SQLException, DataSetException
  189.     {
  190.         if (logger.isDebugEnabled())
  191.         {
  192.             logger.debug("createMetaData(tableName={}, resultSet={}, connection={}) - start",
  193.                     new Object[] { tableName, resultSet, connection });
  194.         }
  195.         return new ResultSetTableMetaData(tableName,resultSet,connection, false);
  196.     }

  197.     private String[] getPrimaryKeyNames() throws SQLException
  198.     {
  199.         logger.debug("getPrimaryKeyNames() - start");

  200.         String schemaName = _qualifiedTableNameSupport.getSchema();
  201.         String tableName = _qualifiedTableNameSupport.getTable();

  202.         Connection connection = _connection.getConnection();
  203.         DatabaseMetaData databaseMetaData = connection.getMetaData();
  204.        
  205.         DatabaseConfig config = _connection.getConfig();
  206.         IMetadataHandler metadataHandler = (IMetadataHandler) config.getProperty(DatabaseConfig.PROPERTY_METADATA_HANDLER);
  207.        
  208.         ResultSet resultSet = metadataHandler.getPrimaryKeys(databaseMetaData, schemaName, tableName);

  209.         List list = new ArrayList();
  210.         try
  211.         {
  212.             while (resultSet.next())
  213.             {
  214.                 String name = resultSet.getString(4);
  215.                 int sequence = resultSet.getInt(5);
  216.                 list.add(new PrimaryKeyData(name, sequence));
  217.             }
  218.         }
  219.         finally
  220.         {
  221.             resultSet.close();
  222.         }

  223.         Collections.sort(list);
  224.         String[] keys = new String[list.size()];
  225.         for (int i = 0; i < keys.length; i++)
  226.         {
  227.             PrimaryKeyData data = (PrimaryKeyData)list.get(i);
  228.             keys[i] = data.getName();
  229.         }

  230.         return keys;
  231.     }

  232.     private class PrimaryKeyData implements Comparable
  233.     {
  234.         private final String _name;
  235.         private final int _index;

  236.         public PrimaryKeyData(String name, int index)
  237.         {
  238.             _name = name;
  239.             _index = index;
  240.         }

  241.         public String getName()
  242.         {
  243.             logger.debug("getName() - start");

  244.             return _name;
  245.         }

  246.         public int getIndex()
  247.         {
  248.             return _index;
  249.         }

  250.         ////////////////////////////////////////////////////////////////////////
  251.         // Comparable interface

  252.         public int compareTo(Object o)
  253.         {
  254.             PrimaryKeyData data = (PrimaryKeyData)o;
  255.             return getIndex() - data.getIndex();
  256.         }
  257.     }

  258.     ////////////////////////////////////////////////////////////////////////////
  259.     // ITableMetaData interface

  260.     public String getTableName()
  261.     {
  262.         // Ensure that the same table name is returned as specified in the input.
  263.         // This is necessary to support fully qualified XML dataset imports.
  264.         //"<dataset>"
  265.         //"<FREJA.SALES SALES_ID=\"8756\" DEALER_ID=\"4467\"/>"
  266.         //"<CAS.ORDERS ORDER_ID=\"1000\" DEALER_CODE=\"4468\"/>"
  267.         //"</dataset>";
  268.         return this._originalTableName;
  269.     }

  270.     public Column[] getColumns() throws DataSetException
  271.     {
  272.         logger.debug("getColumns() - start");

  273.         if (_columns == null)
  274.         {
  275.             try
  276.             {
  277.                 // qualified names support
  278.                 String schemaName = _qualifiedTableNameSupport.getSchema();
  279.                 String tableName = _qualifiedTableNameSupport.getTable();
  280.                
  281.                 Connection jdbcConnection = _connection.getConnection();
  282.                 DatabaseMetaData databaseMetaData = jdbcConnection.getMetaData();
  283.                
  284.                 DatabaseConfig config = _connection.getConfig();
  285.                
  286.                 IMetadataHandler metadataHandler = (IMetadataHandler)config.getProperty(DatabaseConfig.PROPERTY_METADATA_HANDLER);
  287.                 ResultSet resultSet = metadataHandler.getColumns(databaseMetaData, schemaName, tableName);

  288.                 try
  289.                 {
  290.                     IDataTypeFactory dataTypeFactory = super.getDataTypeFactory(_connection);
  291.                     boolean datatypeWarning = config.getFeature(
  292.                             DatabaseConfig.FEATURE_DATATYPE_WARNING);

  293.                     List columnList = new ArrayList();
  294.                     while (resultSet.next())
  295.                     {
  296.                         // Check for exact table/schema name match because
  297.                         // databaseMetaData.getColumns() uses patterns for the lookup
  298.                         boolean match = metadataHandler.matches(resultSet, schemaName, tableName, _caseSensitiveMetaData);
  299.                         if(match)
  300.                         {
  301.                             Column column = SQLHelper.createColumn(resultSet, dataTypeFactory, datatypeWarning);
  302.                             if(column != null)
  303.                             {
  304.                                 columnList.add(column);
  305.                             }
  306.                         }
  307.                         else
  308.                         {
  309.                             logger.debug("Skipping <schema.table> '" + resultSet.getString(2) + "." +
  310.                                     resultSet.getString(3) + "' because names do not exactly match.");
  311.                         }
  312.                     }

  313.                     if (columnList.size() == 0)
  314.                     {
  315.                         logger.warn("No columns found for table '"+ tableName +"' that are supported by dbunit. " +
  316.                                 "Will return an empty column list");
  317.                     }

  318.                     _columns = (Column[])columnList.toArray(new Column[0]);
  319.                 }
  320.                 finally
  321.                 {
  322.                     resultSet.close();
  323.                 }
  324.             }
  325.             catch (SQLException e)
  326.             {
  327.                 throw new DataSetException(e);
  328.             }
  329.         }
  330.         return _columns;
  331.     }

  332.     private boolean primaryKeyFilterChanged(IColumnFilter keyFilter)
  333.     {
  334.         return (keyFilter != lastKeyFilter);
  335.     }

  336.     public Column[] getPrimaryKeys() throws DataSetException
  337.     {
  338.         logger.debug("getPrimaryKeys() - start");
  339.                 DatabaseConfig config = _connection.getConfig();
  340.         IColumnFilter primaryKeysFilter = (IColumnFilter) config.getProperty(
  341.                         DatabaseConfig.PROPERTY_PRIMARY_KEY_FILTER);

  342.         if (_primaryKeys == null || primaryKeyFilterChanged(primaryKeysFilter)) {
  343.             try {
  344.                 lastKeyFilter = primaryKeysFilter;
  345.                 if (primaryKeysFilter != null) {
  346.                     _primaryKeys = Columns.getColumns(getTableName(), getColumns(),
  347.                             primaryKeysFilter);
  348.                 } else {
  349.                     String[] pkNames = getPrimaryKeyNames();
  350.                     _primaryKeys = Columns.getColumns(pkNames, getColumns());
  351.                 }
  352.             }
  353.             catch (SQLException e)
  354.             {
  355.                 throw new DataSetException(e);
  356.             }
  357.         }
  358.         return _primaryKeys;
  359.     }

  360.     ////////////////////////////////////////////////////////////////////////////
  361.     // Object class
  362.     public String toString()
  363.     {
  364.         try
  365.         {
  366.             String tableName = getTableName();
  367.             String columns = Arrays.asList(getColumns()).toString();
  368.             String primaryKeys = Arrays.asList(getPrimaryKeys()).toString();
  369.             return "table=" + tableName + ", cols=" + columns + ", pk=" + primaryKeys + "";
  370.         }
  371.         catch (DataSetException e)
  372.         {
  373.             return super.toString();
  374.         }
  375.     }
  376. }