View Javadoc
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  
22  package org.dbunit.database;
23  
24  import java.sql.Connection;
25  import java.sql.DatabaseMetaData;
26  import java.sql.ResultSet;
27  import java.sql.SQLException;
28  import java.util.ArrayList;
29  import java.util.Arrays;
30  import java.util.Collections;
31  import java.util.List;
32  
33  import org.dbunit.dataset.AbstractTableMetaData;
34  import org.dbunit.dataset.Column;
35  import org.dbunit.dataset.Columns;
36  import org.dbunit.dataset.DataSetException;
37  import org.dbunit.dataset.ITableMetaData;
38  import org.dbunit.dataset.NoSuchTableException;
39  import org.dbunit.dataset.datatype.IDataTypeFactory;
40  import org.dbunit.dataset.filter.IColumnFilter;
41  import org.dbunit.util.QualifiedTableName;
42  import org.dbunit.util.SQLHelper;
43  import org.slf4j.Logger;
44  import org.slf4j.LoggerFactory;
45  
46  /**
47   * Container for the metadata for one database table. The metadata is initialized
48   * using a {@link IDatabaseConnection}.
49   * 
50   * @author Manuel Laflamme
51   * @author Last changed by: $Author$
52   * @version $Revision$ $Date$
53   * @since Mar 8, 2002
54   * @see ITableMetaData
55   */
56  public class DatabaseTableMetaData extends AbstractTableMetaData
57  {
58  
59      /**
60       * Logger for this class
61       */
62      private static final Logger logger = LoggerFactory.getLogger(DatabaseTableMetaData.class);
63  
64      /**
65       * Table name, potentially qualified
66       */
67      private final QualifiedTableName _qualifiedTableNameSupport;
68      private final String _originalTableName;
69      private final IDatabaseConnection _connection;
70      private Column[] _columns;
71      private Column[] _primaryKeys;
72      private boolean _caseSensitiveMetaData;
73  	//added by hzhan032
74      private IColumnFilter lastKeyFilter;
75  
76      
77      DatabaseTableMetaData(String tableName, IDatabaseConnection connection) throws DataSetException
78      {
79      	this(tableName, connection, true);
80      }
81      
82      /**
83       * Creates a new database table metadata
84       * @param tableName The name of the table - can be fully qualified
85       * @param connection The database connection
86       * @param validate Whether or not to validate the given input data. It is not recommended to
87       * set the validation to <code>false</code> because it is then possible to create an instance
88       * of this object for a db table that does not exist.
89       * @throws DataSetException
90       */
91      DatabaseTableMetaData(String tableName, IDatabaseConnection connection, boolean validate) throws DataSetException
92      {
93          this(tableName, connection, validate, false);
94      }
95      
96      /**
97       * Creates a new database table metadata
98       * @param tableName The name of the table - can be fully qualified
99       * @param connection The database connection
100      * @param validate Whether or not to validate the given input data. It is not recommended to
101      * set the validation to <code>false</code> because it is then possible to create an instance
102      * of this object for a db table that does not exist.
103      * @param caseSensitiveMetaData Whether or not the metadata looked up in a case sensitive way
104      * @throws DataSetException
105      * @since 2.4.1
106      */
107     DatabaseTableMetaData(final String tableName, IDatabaseConnection connection, boolean validate, boolean caseSensitiveMetaData) throws DataSetException
108     {
109     	if (tableName == null) {
110 			throw new NullPointerException("The parameter 'tableName' must not be null");
111 		}
112     	if (connection == null) {
113 			throw new NullPointerException("The parameter 'connection' must not be null");
114 		}
115     	
116         _connection = connection;
117         _caseSensitiveMetaData = caseSensitiveMetaData;
118 
119         try
120         {
121              Connection jdbcConnection = connection.getConnection();
122              if(!caseSensitiveMetaData)
123              {
124                  _originalTableName = SQLHelper.correctCase(tableName, jdbcConnection);
125                  SQLHelper.logDebugIfValueChanged(tableName, _originalTableName, "Corrected table name:", DatabaseTableMetaData.class);
126              }
127              else
128              {
129                  _originalTableName = tableName;
130              }
131              
132              // qualified names support - table name and schema is stored here
133              _qualifiedTableNameSupport = new QualifiedTableName(_originalTableName, _connection.getSchema());
134 
135              if(validate) 
136              {
137                  String schemaName = _qualifiedTableNameSupport.getSchema();
138                  String plainTableName = _qualifiedTableNameSupport.getTable();
139                  logger.debug("Validating if table '{}' exists in schema '{}' ...", plainTableName, schemaName);
140                  try {
141                      DatabaseConfig config = connection.getConfig();
142                      IMetadataHandler metadataHandler = (IMetadataHandler) config.getProperty(DatabaseConfig.PROPERTY_METADATA_HANDLER);
143                      DatabaseMetaData databaseMetaData = jdbcConnection.getMetaData();
144                      if(!metadataHandler.tableExists(databaseMetaData, schemaName, plainTableName))
145                      {
146                          throw new NoSuchTableException("Did not find table '" + plainTableName + "' in schema '" + schemaName + "'");
147                      }
148                  }
149                  catch (SQLException e)
150                  {
151                      throw new DataSetException("Exception while validation existence of table '" + plainTableName + "'", e);
152                  }
153              }
154              else
155              {
156                  logger.debug("Validation switched off. Will not check if table exists.");
157              }
158         }
159         catch (SQLException e)
160         {
161             throw new DataSetException("Exception while retrieving JDBC connection from dbunit connection '" + connection + "'", e);
162         }
163         
164     }
165 
166     /**
167      * @param tableName
168      * @param resultSet
169      * @param dataTypeFactory
170      * @return The table metadata created for the given parameters
171      * @throws DataSetException
172      * @throws SQLException
173      * @deprecated since 2.3.0. use {@link ResultSetTableMetaData#ResultSetTableMetaData(String, ResultSet, IDataTypeFactory, boolean)}
174      */
175     public static ITableMetaData createMetaData(String tableName,
176             ResultSet resultSet, IDataTypeFactory dataTypeFactory)
177             throws DataSetException, SQLException
178     {
179     	if (logger.isDebugEnabled())
180     	{
181     		logger.debug("createMetaData(tableName={}, resultSet={}, dataTypeFactory={}) - start",
182     				new Object[]{ tableName, resultSet, dataTypeFactory });
183     	}
184 
185     	return new ResultSetTableMetaData(tableName, resultSet, dataTypeFactory, false);
186     }
187 
188 
189     
190     /**
191      * @param tableName
192      * @param resultSet
193      * @param connection
194      * @return The table metadata created for the given parameters
195      * @throws SQLException
196      * @throws DataSetException
197      * @deprecated since 2.3.0. use {@link org.dbunit.database.ResultSetTableMetaData#ResultSetTableMetaData(String, ResultSet, IDatabaseConnection, boolean)}
198      */
199     public static ITableMetaData createMetaData(String tableName,
200             ResultSet resultSet, IDatabaseConnection connection)
201             throws SQLException, DataSetException
202     {
203     	if (logger.isDebugEnabled())
204     	{
205     		logger.debug("createMetaData(tableName={}, resultSet={}, connection={}) - start",
206     				new Object[] { tableName, resultSet, connection });
207     	}
208     	return new ResultSetTableMetaData(tableName,resultSet,connection, false);
209     }
210 
211     private String[] getPrimaryKeyNames() throws SQLException
212     {
213         logger.debug("getPrimaryKeyNames() - start");
214 
215     	String schemaName = _qualifiedTableNameSupport.getSchema();
216     	String tableName = _qualifiedTableNameSupport.getTable();
217 
218         Connection connection = _connection.getConnection();
219         DatabaseMetaData databaseMetaData = connection.getMetaData();
220         
221         DatabaseConfig config = _connection.getConfig();
222         IMetadataHandler metadataHandler = (IMetadataHandler) config.getProperty(DatabaseConfig.PROPERTY_METADATA_HANDLER);
223         
224         ResultSet resultSet = metadataHandler.getPrimaryKeys(databaseMetaData, schemaName, tableName);
225 
226         List list = new ArrayList();
227         try
228         {
229             while (resultSet.next())
230             {
231                 String name = resultSet.getString(4);
232                 int sequence = resultSet.getInt(5);
233                 list.add(new PrimaryKeyData(name, sequence));
234             }
235         }
236         finally
237         {
238             resultSet.close();
239         }
240 
241         Collections.sort(list);
242         String[] keys = new String[list.size()];
243         for (int i = 0; i < keys.length; i++)
244         {
245             PrimaryKeyData data = (PrimaryKeyData)list.get(i);
246             keys[i] = data.getName();
247         }
248 
249         return keys;
250     }
251 
252     private class PrimaryKeyData implements Comparable
253     {
254         private final String _name;
255         private final int _index;
256 
257         public PrimaryKeyData(String name, int index)
258         {
259             _name = name;
260             _index = index;
261         }
262 
263         public String getName()
264         {
265             logger.debug("getName() - start");
266 
267             return _name;
268         }
269 
270         public int getIndex()
271         {
272             return _index;
273         }
274 
275         ////////////////////////////////////////////////////////////////////////
276         // Comparable interface
277 
278         public int compareTo(Object o)
279         {
280             PrimaryKeyData data = (PrimaryKeyData)o;
281             return getIndex() - data.getIndex();
282         }
283     }
284 
285     ////////////////////////////////////////////////////////////////////////////
286     // ITableMetaData interface
287 
288     public String getTableName()
289     {
290         // Ensure that the same table name is returned as specified in the input.
291         // This is necessary to support fully qualified XML dataset imports.
292         //"<dataset>"
293         //"<FREJA.SALES SALES_ID=\"8756\" DEALER_ID=\"4467\"/>"
294         //"<CAS.ORDERS ORDER_ID=\"1000\" DEALER_CODE=\"4468\"/>"
295         //"</dataset>";
296         return this._originalTableName;
297     }
298 
299     public Column[] getColumns() throws DataSetException
300     {
301         logger.debug("getColumns() - start");
302 
303         if (_columns == null)
304         {
305             try
306             {
307                 // qualified names support
308             	String schemaName = _qualifiedTableNameSupport.getSchema();
309             	String tableName = _qualifiedTableNameSupport.getTable();
310             	
311                 Connection jdbcConnection = _connection.getConnection();
312                 DatabaseMetaData databaseMetaData = jdbcConnection.getMetaData();
313                 
314                 DatabaseConfig config = _connection.getConfig();
315                 
316                 IMetadataHandler metadataHandler = (IMetadataHandler)config.getProperty(DatabaseConfig.PROPERTY_METADATA_HANDLER);
317                 ResultSet resultSet = metadataHandler.getColumns(databaseMetaData, schemaName, tableName);
318 
319                 try
320                 {
321                     IDataTypeFactory dataTypeFactory = super.getDataTypeFactory(_connection);
322                     boolean datatypeWarning = config.getFeature(
323                             DatabaseConfig.FEATURE_DATATYPE_WARNING);
324 
325                     List columnList = new ArrayList();
326                     while (resultSet.next())
327                     {
328                         // Check for exact table/schema name match because
329                         // databaseMetaData.getColumns() uses patterns for the lookup
330                         boolean match = metadataHandler.matches(resultSet, schemaName, tableName, _caseSensitiveMetaData);
331                         if(match)
332                         {
333                             Column column = SQLHelper.createColumn(resultSet, dataTypeFactory, datatypeWarning);
334                             if(column != null)
335                             {
336                                 columnList.add(column);
337                             }
338                         }
339                         else
340                         {
341                             logger.debug("Skipping <schema.table> '" + resultSet.getString(2) + "." + 
342                                     resultSet.getString(3) + "' because names do not exactly match.");
343                         }
344                     }
345 
346                     if (columnList.size() == 0)
347                     {
348                     	logger.warn("No columns found for table '"+ tableName +"' that are supported by dbunit. " +
349                     			"Will return an empty column list");
350                     }
351 
352                     _columns = (Column[])columnList.toArray(new Column[0]);
353                 }
354                 finally
355                 {
356                     resultSet.close();
357                 }
358             }
359             catch (SQLException e)
360             {
361                 throw new DataSetException(e);
362             }
363         }
364         return _columns;
365     }
366 
367     private boolean primaryKeyFilterChanged(IColumnFilter keyFilter)
368     {
369         return (keyFilter != lastKeyFilter);
370     }
371 
372     public Column[] getPrimaryKeys() throws DataSetException
373     {
374         logger.debug("getPrimaryKeys() - start");
375                 DatabaseConfig config = _connection.getConfig();
376         IColumnFilter primaryKeysFilter = (IColumnFilter) config.getProperty(
377                         DatabaseConfig.PROPERTY_PRIMARY_KEY_FILTER);
378 
379         if (_primaryKeys == null || primaryKeyFilterChanged(primaryKeysFilter)) {
380             try {
381                 lastKeyFilter = primaryKeysFilter;
382                 if (primaryKeysFilter != null) {
383                 	_primaryKeys = Columns.getColumns(getTableName(), getColumns(),
384                             primaryKeysFilter);
385                 } else {
386                 	String[] pkNames = getPrimaryKeyNames();
387                     _primaryKeys = Columns.getColumns(pkNames, getColumns());
388                 }
389             }
390             catch (SQLException e)
391             {
392                 throw new DataSetException(e);
393             }
394         }
395         return _primaryKeys;
396     }
397 
398     ////////////////////////////////////////////////////////////////////////////
399     // Object class
400     public String toString()
401     {
402         try
403         {
404             String tableName = getTableName();
405             String columns = Arrays.asList(getColumns()).toString();
406             String primaryKeys = Arrays.asList(getPrimaryKeys()).toString();
407             return "table=" + tableName + ", cols=" + columns + ", pk=" + primaryKeys + "";
408         }
409         catch (DataSetException e)
410         {
411             return super.toString();
412         }
413     }
414 }