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 static org.assertj.core.api.Assertions.assertThat;
25  import static org.junit.jupiter.api.Assertions.assertThrows;
26  import static org.junit.jupiter.api.Assertions.assertTrue;
27  
28  import java.sql.DatabaseMetaData;
29  import java.util.ArrayList;
30  import java.util.List;
31  import java.util.Locale;
32  
33  import org.junit.jupiter.api.Assumptions;
34  
35  import org.dbunit.AbstractDatabaseIT;
36  import org.dbunit.DatabaseEnvironment;
37  import org.dbunit.DdlExecutor;
38  import org.dbunit.TestFeature;
39  import org.dbunit.TurkishDefaultLocale;
40  import org.dbunit.dataset.Column;
41  import org.dbunit.dataset.Columns;
42  import org.dbunit.dataset.IDataSet;
43  import org.dbunit.dataset.ITable;
44  import org.dbunit.dataset.ITableMetaData;
45  import org.dbunit.dataset.NoSuchTableException;
46  import org.dbunit.dataset.datatype.DataType;
47  import org.dbunit.dataset.datatype.DataTypeException;
48  import org.dbunit.dataset.datatype.DefaultDataTypeFactory;
49  import org.dbunit.dataset.datatype.IDataTypeFactory;
50  import org.dbunit.dataset.filter.IColumnFilter;
51  import org.dbunit.testutil.TestUtils;
52  import org.junit.jupiter.api.Test;
53  
54  /**
55   * @author Manuel Laflamme
56   * @version $Revision$
57   * @since Mar 14, 2002
58   */
59  class DatabaseTableMetaDataIT extends AbstractDatabaseIT
60  {
61  
62      public static final String TEST_TABLE = "TEST_TABLE";
63  
64      /**
65       * Replaces {@code _connection} with a fresh connection after test tables
66       * have been dropped, so that {@code AbstractDatabaseIT.tearDown()} uses an
67       * up-to-date dataset that does not include the dropped tables.
68       *
69       * @throws Exception
70       */
71      private void refreshConnection() throws Exception
72      {
73          _connection.close();
74          _connection = getDatabaseTester().getConnection();
75          setUpDatabaseConfig(_connection.getConfig());
76      }
77  
78      protected IDataSet createDataSet() throws Exception
79      {
80          return _connection.createDataSet();
81      }
82  
83      @Override
84      protected String convertString(final String str) throws Exception
85      {
86          return DatabaseEnvironment.getInstance().convertString(str);
87      }
88  
89      @Test
90      void testGetPrimaryKeys_withPkTable_returnsAllPrimaryKeyColumns() throws Exception
91      {
92          final String tableName = "PK_TABLE";
93          // String[] expected = {"PK0"};
94          final String[] expected = {"PK0", "PK1", "PK2"};
95  
96          final ITableMetaData metaData =
97                  createDataSet().getTableMetaData(tableName);
98          final Column[] columns = metaData.getPrimaryKeys();
99          assertThat(columns).as("pl count").hasSize(expected.length);
100 
101         for (int i = 0; i < columns.length; i++)
102         {
103             final Column column = columns[i];
104             assertThat(column.getColumnName()).as("name")
105                     .isEqualTo(convertString(expected[i]));
106         }
107     }
108 
109     @Test
110     void testGetPrimaryKeys_withTableHavingNoPk_returnsEmptyArray() throws Exception
111     {
112         final String tableName = TEST_TABLE;
113 
114         final ITableMetaData metaData =
115                 createDataSet().getTableMetaData(tableName);
116         final Column[] columns = metaData.getPrimaryKeys();
117         assertThat(columns).as("pk count").isEmpty();
118     }
119 
120     @Test
121     void testGetPrimaryKeys_withFilterYieldingNoColumns_fallsBackToDatabaseDeclaredPrimaryKeys()
122             throws Exception
123     {
124         final String tableName = "PK_TABLE";
125         final String[] expected = {"PK0", "PK1", "PK2"};
126         final IColumnFilter noMatchFilter = (filterTableName, column) -> false;
127 
128         _connection.getConfig().setProperty(
129                 DatabaseConfig.PROPERTY_PRIMARY_KEY_FILTER, noMatchFilter);
130         try
131         {
132             final ITableMetaData metaData =
133                     createDataSet().getTableMetaData(tableName);
134             final Column[] columns = metaData.getPrimaryKeys();
135 
136             assertThat(columns).as("pk count").hasSize(expected.length);
137             for (int i = 0; i < columns.length; i++)
138             {
139                 assertThat(columns[i].getColumnName()).as("name")
140                         .isEqualTo(convertString(expected[i]));
141             }
142         }
143         finally
144         {
145             _connection.getConfig().setProperty(
146                     DatabaseConfig.PROPERTY_PRIMARY_KEY_FILTER, null);
147         }
148     }
149 
150     @Test
151     void testCreation_withUnknownTable_throwsNoSuchTableException() throws Exception
152     {
153         final String tableName = "UNKNOWN_TABLE";
154         final IDatabaseConnection connection = getConnection();
155         final String schema = connection.getSchema();
156         final NoSuchTableException expected = assertThrows(
157                 NoSuchTableException.class,
158                 () -> new DatabaseTableMetaData(tableName, getConnection()),
159                 "Should not be able to create a DatabaseTableMetaData for an unknown table");
160 
161         final String msg =
162                 "Did not find table '" + convertString("UNKNOWN_TABLE")
163                         + "' in schema '" + schema + "'";
164         assertThat(expected).hasMessage(msg);
165     }
166 
167     @Test
168     void testGetColumns_withUnknownTableAndNoValidation_returnsEmptyColumnArray() throws Exception
169     {
170         // Since the "unknown_table" does not exist it also does not have any
171         // columns
172         final String tableName = "UNKNOWN_TABLE";
173         final boolean validate = false;
174 
175         final ITableMetaData metaData =
176                 new DatabaseTableMetaData(tableName, getConnection(), validate);
177 
178         final Column[] columns = metaData.getColumns();
179         assertThat(columns).isEmpty();
180     }
181 
182     @Test
183     void testGetColumns_withPkTable_returnsCorrectNullabilityForEachColumn() throws Exception
184     {
185         final String tableName = "PK_TABLE";
186         final String[] notNullable = {"PK0", "PK1", "PK2"};
187         final String[] nullable = {"NORMAL0", "NORMAL1"};
188 
189         final ITableMetaData metaData =
190                 createDataSet().getTableMetaData(tableName);
191         final Column[] columns = metaData.getColumns();
192 
193         assertThat(columns).as("column count")
194                 .hasSize(nullable.length + notNullable.length);
195 
196         // not nullable
197         for (int i = 0; i < notNullable.length; i++)
198         {
199             final Column column = Columns.getColumn(notNullable[i], columns);
200             assertThat(column.getNullable()).as(notNullable[i])
201                     .isEqualTo(Column.NO_NULLS);
202         }
203 
204         // nullable
205         for (int i = 0; i < nullable.length; i++)
206         {
207             final Column column = Columns.getColumn(nullable[i], columns);
208             assertThat(column.getNullable()).as(nullable[i])
209                     .isEqualTo(Column.NULLABLE);
210         }
211     }
212 
213     @Test
214     void testGetColumns_whenDataTypeFactoryReturnsUnknown_returnsEmptyColumnArray() throws Exception
215     {
216         final IDataTypeFactory dataTypeFactory = new DefaultDataTypeFactory()
217         {
218             @Override
219             public DataType createDataType(final int sqlType,
220                     final String sqlTypeName, final String tableName,
221                     final String columnName) throws DataTypeException
222             {
223                 return DataType.UNKNOWN;
224             }
225         };
226         this._connection.getConfig().setProperty(
227                 DatabaseConfig.PROPERTY_DATATYPE_FACTORY, dataTypeFactory);
228 
229         final String tableName = "EMPTY_MULTITYPE_TABLE";
230         final ITableMetaData metaData =
231                 createDataSet().getTableMetaData(tableName);
232         final Column[] columns = metaData.getColumns();
233         // No columns recognized -> should not provide any columns here
234         assertThat(columns).as("Should be an empty column array").isEmpty();
235     }
236 
237     @Test
238     void testGetColumns_withMultitypeTable_returnsCorrectDataTypeForEachColumn() throws Exception
239     {
240         final String tableName = "EMPTY_MULTITYPE_TABLE";
241 
242         final List<String> expectedNames = new ArrayList<>();
243         expectedNames.add("VARCHAR_COL");
244         expectedNames.add("NUMERIC_COL");
245         expectedNames.add("TIMESTAMP_COL");
246 
247         final List<DataType> expectedTypes = new ArrayList<>();
248         expectedTypes.add(DataType.VARCHAR);
249         expectedTypes.add(DataType.NUMERIC);
250         expectedTypes.add(DataType.TIMESTAMP);
251 
252         final DatabaseEnvironment environment =
253                 DatabaseEnvironment.getInstance();
254         if (environment.support(TestFeature.VARBINARY))
255         {
256             expectedNames.add("VARBINARY_COL");
257             expectedTypes.add(DataType.VARBINARY);
258         }
259 
260         // Check correct setup
261         assertThat(expectedNames).as("expected columns")
262                 .hasSize(expectedTypes.size());
263 
264         final ITableMetaData metaData =
265                 createDataSet().getTableMetaData(tableName);
266         final Column[] columns = metaData.getColumns();
267         assertThat(columns).as("column count").hasSize(4);
268 
269         for (int i = 0; i < expectedNames.size(); i++)
270         {
271             final Column column = columns[i];
272             assertThat(column.getColumnName()).as("name")
273                     .isEqualTo(convertString(expectedNames.get(i)));
274             if (expectedTypes.get(i).equals(DataType.NUMERIC))
275             {
276                 // 2009-10-10 TODO John Hurst: hack for Oracle, returns
277                 // java.sql.Types.DECIMAL for this column
278                 assertThat(column)
279                         .as("Expected numeric datatype, got ["
280                                 + column.getDataType() + "]")
281                         .satisfiesAnyOf(
282                                 dataType -> assertThat(dataType.getDataType())
283                                         .isEqualTo(DataType.NUMERIC),
284                                 dataType -> assertThat(dataType.getDataType())
285                                         .isEqualTo(DataType.DECIMAL));
286 
287             } else if (expectedTypes.get(i).equals(DataType.TIMESTAMP)
288                     && column.getDataType().equals(DataType.DATE))
289             {
290                 // 2009-10-22 TODO John Hurst: hack for Postgresql, returns DATE
291                 // for TIMESTAMP.
292                 // Need to move DataType comparison to DatabaseEnvironment.
293                 assertTrue(true);
294             } else if (expectedTypes.get(i).equals(DataType.VARBINARY)
295                     && column.getDataType().equals(DataType.VARCHAR))
296             {
297                 // 2009-10-22 TODO John Hurst: hack for Postgresql, returns
298                 // VARCHAR for VARBINARY.
299                 // Need to move DataType comparison to DatabaseEnvironment.
300                 assertTrue(true);
301             } else
302             {
303                 assertThat(column.getDataType()).as("datatype")
304                         .isEqualTo(expectedTypes.get(i));
305 
306             }
307         }
308     }
309 
310     /**
311      * Tests whether dbunit works correctly when the local machine has a
312      * specific locale set while having case sensitivity=false (so that the
313      * "toUpperCase()" is internally invoked on table names)
314      * 
315      * @throws Exception
316      */
317     @Test
318     @TurkishDefaultLocale
319     void testGetTable_withTurkishLocaleActive_findsTableIgnoringLocaleSpecificUpperCase() throws Exception
320     {
321         // To test bug report #1537894 where the user has a turkish locale set
322         // on his box, where "i".toUpperCase() produces an "\u0131" ("I" with
323         // dot above) which is not equal to "I".
324 
325         // Use the "EMPTY_MULTITYPE_TABLE" because it has an "I" in the
326         // name.
327         // Use as input a completely lower-case string so that the internal
328         // "toUpperCase()" has effect
329         // 2009-11-06 TODO John Hurst: not working in original form with
330         // MySQL.
331         // Is it because "internal toUpperCase() mentioned above is actually
332         // not being called?
333         // Investigate further.
334         // String tableName = "empty_multitype_table";
335         final String tableName = "EMPTY_MULTITYPE_TABLE";
336 
337         final IDataSet dataSet = this._connection.createDataSet();
338         final ITable table = dataSet.getTable(tableName);
339         // Should now find the table, regardless that we gave the tableName
340         // in lowerCase
341         assertThat(table).as("Table '" + tableName + "' was not found")
342                 .isNotNull();
343     }
344 
345     /**
346      * Tests the pattern-like column retrieval from the database. DbUnit should
347      * not interpret any table names as regex patterns.
348      *
349      * @throws Exception
350      */
351     @Test
352     void testGetColumns_withPatternLikeTableName_doesNotInterpretUnderscoreAsPattern() throws Exception
353     {
354         DdlExecutor.dropTables(_connection.getConnection(),
355                 "PATTERN_LIKE_TABLE_XX", "PATTERN_LIKE_TABLE_X_");
356         DdlExecutor.executeDdlFile(
357                 TestUtils.getFile("sql/hypersonic_dataset_pattern_test.sql"),
358                 _connection.getConnection(), false);
359         try
360         {
361             final String tableName = "PATTERN_LIKE_TABLE_X_";
362             final String[] columnNames = {"VARCHAR_COL_XUNDERSCORE"};
363 
364             final ITableMetaData metaData =
365                     _connection.createDataSet().getTableMetaData(tableName);
366             final Column[] columns = metaData.getColumns();
367             assertThat(columns).as("column count").hasSize(columnNames.length);
368 
369             for (int i = 0; i < columnNames.length; i++)
370             {
371                 final Column column =
372                         Columns.getColumn(columnNames[i], columns);
373                 assertThat(column.getColumnName()).as(columnNames[i])
374                         .isEqualToIgnoringCase(columnNames[i]);
375             }
376         }
377         finally
378         {
379             DdlExecutor.dropTables(_connection.getConnection(),
380                     "PATTERN_LIKE_TABLE_XX", "PATTERN_LIKE_TABLE_X_");
381             refreshConnection();
382         }
383     }
384 
385     @Test
386     void testCreation_withCaseSensitiveEnabled_onlyFindsMixedCaseTable() throws Exception
387     {
388         final java.sql.DatabaseMetaData dbMeta =
389                 _connection.getConnection().getMetaData();
390         Assumptions.assumeTrue(
391                 dbMeta.supportsMixedCaseQuotedIdentifiers(),
392                 "Skip: database does not treat quoted identifiers as case-sensitive.");
393         Assumptions.assumeTrue(
394                 "\"".equals(dbMeta.getIdentifierQuoteString()),
395                 "Skip: database does not use ANSI double-quote identifier syntax.");
396         Assumptions.assumeFalse(
397                 dbMeta.storesLowerCaseIdentifiers(),
398                 "Skip: database stores unquoted identifiers in lowercase.");
399         DdlExecutor.dropTables(_connection.getConnection(),
400                 "UPPER_CASE_TABLE");
401         try
402         {
403             DdlExecutor.executeSql(_connection.getConnection(),
404                     "DROP TABLE \"MixedCaseTable\"");
405         }
406         catch (final Exception ignored)
407         {
408             // MixedCaseTable may not exist on first run
409         }
410         DdlExecutor.executeDdlFile(
411                 TestUtils.getFile("sql/hypersonic_case_sensitive_test.sql"),
412                 _connection.getConnection(), false);
413         try
414         {
415             final String tableName = "MixedCaseTable";
416             final String tableNameWrongCase = "MIXEDCASETABLE";
417             final boolean validate = true;
418             final boolean caseSensitive = true;
419 
420             // Skip databases (e.g. MSSQL with case-insensitive collation) that find
421             // 'MixedCaseTable' even when queried as 'MIXEDCASETABLE'.
422             final String schemaName = _connection.getSchema();
423             try (java.sql.ResultSet wrongCaseRs = _connection.getConnection().getMetaData()
424                     .getTables(null, schemaName, tableNameWrongCase, null))
425             {
426                 Assumptions.assumeFalse(wrongCaseRs.next(),
427                         "Skip: database finds 'MixedCaseTable' via 'MIXEDCASETABLE' (case-insensitive identifiers).");
428             }
429 
430             final ITableMetaData metaData = new DatabaseTableMetaData(tableName,
431                     _connection, validate, caseSensitive);
432             final Column[] columns = metaData.getColumns();
433             assertThat(columns).hasSize(1);
434             assertThat(columns[0].getColumnName()).isEqualTo("COL1");
435 
436             // Now test with same table name but wrong case
437             final NoSuchTableException expected =
438                     assertThrows(NoSuchTableException.class, () -> {
439                         new DatabaseTableMetaData(tableNameWrongCase,
440                                 _connection, validate, caseSensitive);
441                     }, "Should not be able to create DatabaseTableMetaData with non-existing table name "
442                             + tableNameWrongCase + ". Created ");
443             assertThat(expected.getMessage().indexOf(tableNameWrongCase))
444                     .isNotNegative();
445         }
446         finally
447         {
448             DdlExecutor.dropTables(_connection.getConnection(),
449                     "UPPER_CASE_TABLE");
450             try
451             {
452                 DdlExecutor.executeSql(_connection.getConnection(),
453                         "DROP TABLE \"MixedCaseTable\"");
454             }
455             catch (final Exception ignored)
456             {
457                 // ignore if already dropped
458             }
459             refreshConnection();
460         }
461     }
462 
463     /**
464      * Ensure that the same table name is returned by
465      * {@link DatabaseTableMetaData#getTableName()} as the specified by the
466      * input parameter.
467      * 
468      * @throws Exception
469      */
470     @Test
471     void testGetTableName_withFullyQualifiedSchemaTableName_returnsSchemaPrefixedName() throws Exception
472     {
473         final DatabaseEnvironment environment =
474                 DatabaseEnvironment.getInstance();
475         final String schema = environment.getProfile().getSchema();
476 
477         assertThat(schema)
478                 .as("Precondition: db environment 'schema' must not be null")
479                 .isNotNull();
480         // Connection jdbcConn = _connection.getConnection();
481         // String schema = SQLHelper.getSchema(jdbcConn);
482         final DatabaseTableMetaData metaData = new DatabaseTableMetaData(
483                 schema + "." + TEST_TABLE, _connection);
484         assertThat(metaData.getTableName())
485                 .isEqualTo(schema + "." + convertString(TEST_TABLE));
486     }
487 
488     @Test
489     void testGetTableName_whenDatabaseStoresUpperCase_returnsUpperCasedTableName() throws Exception
490     {
491         final IDatabaseConnection connection = getConnection();
492         final DatabaseMetaData metaData =
493                 connection.getConnection().getMetaData();
494         if (metaData.storesUpperCaseIdentifiers())
495         {
496             final DatabaseTableMetaData dbTableMetaData =
497                     new DatabaseTableMetaData(
498                             TEST_TABLE.toLowerCase(Locale.ENGLISH),
499                             _connection);
500             // Table name should have been "toUpperCase'd"
501             assertThat(dbTableMetaData.getTableName())
502                     .isEqualTo(TEST_TABLE.toUpperCase(Locale.ENGLISH));
503         } else
504         {
505             // skip the test
506             assertTrue(true);
507         }
508     }
509 
510     @Test
511     void testGetTableName_whenDatabaseStoresLowerCase_returnsLowerCasedTableName() throws Exception
512     {
513         final IDatabaseConnection connection = getConnection();
514         final DatabaseMetaData metaData =
515                 connection.getConnection().getMetaData();
516         if (metaData.storesLowerCaseIdentifiers())
517         {
518             final DatabaseTableMetaData dbTableMetaData =
519                     new DatabaseTableMetaData(
520                             TEST_TABLE.toUpperCase(Locale.ENGLISH),
521                             _connection);
522             // Table name should have been "toUpperCase'd"
523             assertThat(dbTableMetaData.getTableName())
524                     .isEqualTo(TEST_TABLE.toLowerCase(Locale.ENGLISH));
525         } else
526         {
527             // skip the test
528             assertTrue(true);
529         }
530     }
531 }