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.HashSet;
29  import java.util.Locale;
30  import org.dbunit.DatabaseUnitRuntimeException;
31  import org.dbunit.dataset.AbstractDataSet;
32  import org.dbunit.dataset.Column;
33  import org.dbunit.dataset.DataSetException;
34  import org.dbunit.dataset.DataSetUtils;
35  import org.dbunit.dataset.IDataSet;
36  import org.dbunit.dataset.ITable;
37  import org.dbunit.dataset.ITableIterator;
38  import org.dbunit.dataset.ITableMetaData;
39  import org.dbunit.dataset.NoSuchTableException;
40  import org.dbunit.dataset.OrderedTableNameMap;
41  import org.dbunit.dataset.filter.ITableFilterSimple;
42  import org.dbunit.util.QualifiedTableName;
43  import org.dbunit.util.SQLHelper;
44  import org.slf4j.Logger;
45  import org.slf4j.LoggerFactory;
46  
47  /**
48   * Provides access to a database instance as a {@link IDataSet}.
49   * 
50   * @author Manuel Laflamme
51   * @author Last changed by: $Author$
52   * @version $Revision$ $Date$
53   * @since 1.0 (Feb 17, 2002)
54   */
55  public class DatabaseDataSet extends AbstractDataSet
56  {
57  
58      /**
59       * Logger for this class
60       */
61      private static final Logger logger = LoggerFactory.getLogger(DatabaseDataSet.class);
62  
63      private final IDatabaseConnection _connection;
64      private OrderedTableNameMap _tableMap = null;
65      private SchemaSet _schemaSet = new SchemaSet(isCaseSensitiveTableNames());
66  
67      private final ITableFilterSimple _tableFilter;
68      private final ITableFilterSimple _oracleRecycleBinTableFilter;
69  
70      /**
71       * Creates a new database data set
72       * @param connection
73       * @throws SQLException
74       */
75      DatabaseDataSet(IDatabaseConnection connection) throws SQLException
76      {
77          this(connection, connection.getConfig().getFeature(DatabaseConfig.FEATURE_CASE_SENSITIVE_TABLE_NAMES));
78      }
79  
80  
81      /**
82       * Creates a new database data set
83       * @param connection The database connection
84       * @param caseSensitiveTableNames Whether or not this dataset should use case sensitive table names
85       * @throws SQLException
86       * @since 2.4
87       */
88      public DatabaseDataSet(IDatabaseConnection connection, boolean caseSensitiveTableNames) throws SQLException
89      {
90          this(connection, caseSensitiveTableNames, null);
91      }
92  
93      /**
94       * Creates a new database data set
95       * @param connection The database connection
96       * @param caseSensitiveTableNames Whether or not this dataset should use case sensitive table names
97       * @param tableFilter Table filter to specify tables to be omitted in this dataset. Can be <code>null</code>.
98       * @throws SQLException
99       * @since 2.4.3
100      */
101     public DatabaseDataSet(IDatabaseConnection connection, boolean caseSensitiveTableNames, ITableFilterSimple tableFilter)
102     throws SQLException
103     {
104         super(caseSensitiveTableNames);
105         if (connection == null) {
106             throw new NullPointerException(
107             "The parameter 'connection' must not be null");
108         }
109         _connection = connection;
110         _tableFilter = tableFilter;
111         _oracleRecycleBinTableFilter = new OracleRecycleBinTableFilter(connection.getConfig());
112     }
113 
114 
115 
116     static String getSelectStatement(String schema, ITableMetaData metaData, String escapePattern)
117     throws DataSetException
118     {
119         if (logger.isDebugEnabled())
120         {
121             logger.debug("getSelectStatement(schema={}, metaData={}, escapePattern={}) - start",
122                     new Object[] { schema, metaData, escapePattern });
123         }
124 
125         Column[] columns = metaData.getColumns();
126         Column[] primaryKeys = metaData.getPrimaryKeys();
127 
128         if(columns.length==0){
129             throw new DatabaseUnitRuntimeException("At least one column is required to build a valid select statement. "+
130                     "Cannot load data for " + metaData);
131         }
132 
133         // select
134         StringBuffer sqlBuffer = new StringBuffer(128);
135         sqlBuffer.append("select ");
136         for (int i = 0; i < columns.length; i++)
137         {
138             if (i > 0)
139             {
140                 sqlBuffer.append(", ");
141             }
142             String columnName = new QualifiedTableName(
143                     columns[i].getColumnName(), null, escapePattern).getQualifiedName();
144             sqlBuffer.append(columnName);
145         }
146 
147         // from
148         sqlBuffer.append(" from ");
149         sqlBuffer.append(new QualifiedTableName(
150                 metaData.getTableName(), schema, escapePattern).getQualifiedName());
151 
152         // order by
153         for (int i = 0; i < primaryKeys.length; i++)
154         {
155             if (i == 0)
156             {
157                 sqlBuffer.append(" order by ");
158             }
159             else
160             {
161                 sqlBuffer.append(", ");
162             }
163             sqlBuffer.append(new QualifiedTableName(primaryKeys[i].getColumnName(), null, escapePattern).getQualifiedName());
164 
165         }
166 
167         return sqlBuffer.toString();
168     }
169 
170     /**
171      * Get all the table names form the database that are not system tables.
172      */
173     private void initialize(String schema) throws DataSetException
174     {
175         logger.debug("initialize() - start");
176 
177         DatabaseConfig config = _connection.getConfig();
178         boolean qualifiedTableNamesActive = Boolean.TRUE == config.getProperty(DatabaseConfig.FEATURE_QUALIFIED_TABLE_NAMES);
179         
180         if(schema == null || !qualifiedTableNamesActive)
181     {
182       // If FEATURE_QUALIFIED_TABLE_NAMES is inactive or no schema did have been provided
183       schema = getDefaultSchema();
184     }
185         
186         if (_tableMap != null && _schemaSet.contains(schema))
187         {
188             return;
189         }
190 
191         try
192         {
193             logger.debug("Initializing the data set from the database...");
194 
195             Connection jdbcConnection = _connection.getConnection();
196             DatabaseMetaData databaseMetaData = jdbcConnection.getMetaData();
197 
198             if(SQLHelper.isSybaseDb(jdbcConnection.getMetaData()) && !jdbcConnection.getMetaData().getUserName().equals(schema) ){
199                 logger.warn("For sybase the schema name should be equal to the user name. " +
200                         "Otherwise the DatabaseMetaData#getTables() method might not return any columns. " +
201                 "See dbunit tracker #1628896 and http://issues.apache.org/jira/browse/TORQUE-40?page=all");
202             }
203 
204             String[] tableType = (String[])config.getProperty(DatabaseConfig.PROPERTY_TABLE_TYPE);
205             IMetadataHandler metadataHandler = (IMetadataHandler) config.getProperty(DatabaseConfig.PROPERTY_METADATA_HANDLER);
206 
207             ResultSet resultSet = metadataHandler.getTables(databaseMetaData, schema, tableType);
208 
209             if(logger.isDebugEnabled())
210             {
211                 logger.debug(SQLHelper.getDatabaseInfo(jdbcConnection.getMetaData()));
212                 logger.debug("metadata resultset={}", resultSet);
213             }
214 
215             try
216             {
217         if (_tableMap == null) {
218           _tableMap = super.createTableNameMap();
219         }
220         _schemaSet.add(schema);
221                 while (resultSet.next())
222                 {
223                     String schemaName = metadataHandler.getSchema(resultSet);
224                     String tableName = resultSet.getString(3);
225 
226                     if(_tableFilter != null && !_tableFilter.accept(tableName))
227                     {
228                         logger.debug("Skipping table '{}'", tableName);
229                         continue;
230                     }
231                     if(!_oracleRecycleBinTableFilter.accept(tableName))
232                     {
233                         logger.debug("Skipping oracle recycle bin table '{}'", tableName);
234                         continue;
235                     }
236                     if (schema == null && !_schemaSet.contains(schemaName)) {
237                       _schemaSet.add(schemaName);
238                     }
239 
240                     QualifiedTableName qualifiedTableName = new QualifiedTableName(tableName, schemaName);
241                     tableName = qualifiedTableName.getQualifiedNameIfEnabled(config);
242 
243                     // Put the table into the table map
244                     _tableMap.add(tableName, null);
245                 }
246             }
247             finally
248             {
249                 resultSet.close();
250             }
251         }
252         catch (SQLException e)
253         {
254             throw new DataSetException(e);
255         }
256     }
257 
258   private String getDefaultSchema() {
259     return _connection.getSchema();
260   }
261 
262     ////////////////////////////////////////////////////////////////////////////
263     // AbstractDataSet class
264 
265     protected ITableIterator createIterator(boolean reversed)
266     throws DataSetException
267     {
268         if(logger.isDebugEnabled())
269         {
270             logger.debug("createIterator(reversed={}) - start", String.valueOf(reversed));
271         }
272 
273         String[] names = getTableNames();
274         if (reversed)
275         {
276             names = DataSetUtils.reverseStringArray(names);
277         }
278 
279         return new DatabaseTableIterator(names, this);
280     }
281 
282     ////////////////////////////////////////////////////////////////////////////
283     // IDataSet interface
284 
285     public String[] getTableNames() throws DataSetException
286     {
287         initialize(null);
288 
289         return _tableMap.getTableNames();
290     }
291 
292     public ITableMetaData getTableMetaData(String tableName) throws DataSetException
293     {
294         logger.debug("getTableMetaData(tableName={}) - start", tableName);
295 
296         QualifiedTableName qualifiedTableName = new QualifiedTableName(tableName, getDefaultSchema());
297         
298         initialize(qualifiedTableName.getSchema());
299 
300         // Verify if table exist in the database
301         if (!_tableMap.containsTable(tableName))
302         {
303             logger.error("Table '{}' not found in tableMap={}", tableName,
304                     _tableMap);
305             throw new NoSuchTableException(tableName);
306         }
307 
308         // Try to find cached metadata
309         ITableMetaData metaData = (ITableMetaData)_tableMap.get(tableName);
310         if (metaData != null)
311         {
312             return metaData;
313         }
314 
315         // Create metadata and cache it
316         metaData = new DatabaseTableMetaData(tableName, _connection, true, super.isCaseSensitiveTableNames());
317         // Put the metadata object into the cache map
318         _tableMap.update(tableName, metaData);
319 
320         return metaData;
321     }
322 
323     public ITable getTable(String tableName) throws DataSetException
324     {
325         logger.debug("getTable(tableName={}) - start", tableName);
326 
327         QualifiedTableName qualifiedTableName = new QualifiedTableName(tableName, getDefaultSchema());
328         
329         initialize(qualifiedTableName.getSchema());
330 
331         try
332         {
333             ITableMetaData metaData = getTableMetaData(tableName);
334 
335             DatabaseConfig config = _connection.getConfig();
336             IResultSetTableFactory factory = (IResultSetTableFactory)config.getProperty(
337                     DatabaseConfig.PROPERTY_RESULTSET_TABLE_FACTORY);
338             return factory.createTable(metaData, _connection);
339         }
340         catch (SQLException e)
341         {
342             throw new DataSetException(e);
343         }
344     }
345 
346     private static class SchemaSet extends HashSet<String>
347     {
348         private static final long serialVersionUID = 1L;
349 
350         private static final String NULL_REPLACEMENT =
351                 "NULL_REPLACEMENT_HASHKEY";
352 
353         private boolean isCaseSensitive;
354 
355         private SchemaSet(boolean isCaseSensitive)
356         {
357             this.isCaseSensitive = isCaseSensitive;
358         }
359 
360         @Override
361         public boolean contains(Object o)
362         {
363             return super.contains(normalizeSchema(o));
364         }
365 
366         @Override
367         public boolean add(String e)
368         {
369             return super.add(normalizeSchema(e));
370         }
371 
372         private String normalizeSchema(Object source)
373         {
374             if (source == null)
375             {
376                 return NULL_REPLACEMENT;
377             } else if (isCaseSensitive)
378             {
379                 return source.toString().toUpperCase(Locale.ENGLISH);
380             }
381             return source.toString();
382         }
383     }
384 
385     private static class OracleRecycleBinTableFilter implements ITableFilterSimple
386     {
387         private final DatabaseConfig _config;
388 
389         public OracleRecycleBinTableFilter(DatabaseConfig config)
390         {
391             this._config = config;
392         }
393 
394         public boolean accept(String tableName) throws DataSetException
395         {
396             // skip oracle 10g recycle bin system tables if enabled
397             if(_config.getFeature(DatabaseConfig.FEATURE_SKIP_ORACLE_RECYCLEBIN_TABLES)) {
398                 // Oracle 10g workaround
399                 // don't process system tables (oracle recycle bin tables) which
400                 // are reported to the application due a bug in the oracle JDBC driver
401                 if (tableName.startsWith("BIN$"))
402                 {
403                     return false;
404                 }
405             }
406 
407             return true;
408         }
409     }
410 }