View Javadoc
1   /*
2    *
3    * The DbUnit Database Testing Framework
4    * Copyright (C)2005, 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.search;
22  
23  import static org.assertj.core.api.Assertions.assertThat;
24  import static org.junit.jupiter.api.Assertions.fail;
25  
26  import java.io.File;
27  import java.sql.Connection;
28  import java.util.HashSet;
29  import java.util.Set;
30  import java.util.TreeSet;
31  
32  import org.dbunit.AbstractDatabaseIT;
33  import org.dbunit.DdlExecutor;
34  import org.dbunit.HypersonicEnvironment;
35  import org.dbunit.database.DatabaseConnection;
36  import org.dbunit.database.PrimaryKeyFilter.PkTableMap;
37  import org.dbunit.dataset.IDataSet;
38  import org.dbunit.dataset.NoSuchTableException;
39  import org.dbunit.testutil.TestUtils;
40  import org.dbunit.util.search.SearchException;
41  import org.junit.jupiter.api.Test;
42  
43  /**
44   * @author Felipe Leme (dbunit@felipeal.net)
45   * @author Last changed by: $Author$
46   * @version $Revision$ $Date$
47   * @since Aug 28, 2005
48   */
49  class TablesDependencyHelperIT extends AbstractDatabaseIT
50  {
51  
52      /**
53       * Replaces {@code _connection} with a fresh connection after test tables
54       * have been dropped, so that {@code AbstractDatabaseIT.tearDown()} uses an
55       * up-to-date dataset that does not include the dropped tables.
56       *
57       * @throws Exception
58       */
59      private void refreshConnection() throws Exception
60      {
61          _connection.close();
62          _connection = getDatabaseTester().getConnection();
63          setUpDatabaseConfig(_connection.getConfig());
64      }
65  
66  
67      @Test
68      void testGetDependentTables_withSingleRootTable_returnsDependentTablesInFkOrder() throws Exception
69      {
70          DdlExecutor.dropTables(_connection.getConnection(), "B", "C", "E",
71                  "H", "F", "G", "A", "D");
72          DdlExecutor.executeDdlFile(
73                  TestUtils.getFile(
74                          "sql/" + ImportNodesFilterSearchCallbackIT.SQL_FILE),
75                  _connection.getConnection(), false);
76          try
77          {
78              final String[][] allInput =
79                      ImportNodesFilterSearchCallbackIT.SINGLE_INPUT;
80              final String[][] allExpectedOutput =
81                      ImportNodesFilterSearchCallbackIT.SINGLE_OUTPUT;
82              for (int i = 0; i < allInput.length; i++)
83              {
84                  final String[] input = allInput[i];
85                  final String[] expectedOutput = allExpectedOutput[i];
86                  final String[] actualOutput = TablesDependencyHelper
87                          .getDependentTables(_connection, input[0]);
88                  assertThat(actualOutput).as("output didn't match for i=" + i + ".")
89                          .usingElementComparator(String.CASE_INSENSITIVE_ORDER)
90                          .containsExactly(expectedOutput);
91              }
92          }
93          finally
94          {
95              DdlExecutor.dropTables(_connection.getConnection(), "B", "C", "E",
96                      "H", "F", "G", "A", "D");
97              refreshConnection();
98          }
99      }
100 
101     @Test
102     void testGetDependentTables_whenRootTableDoesNotExist_throwsSearchException()
103             throws Exception
104     {
105         DdlExecutor.dropTables(_connection.getConnection(), "B", "C", "E",
106                 "H", "F", "G", "A", "D");
107         DdlExecutor.executeDdlFile(
108                 TestUtils.getFile(
109                         "sql/" + ImportNodesFilterSearchCallbackIT.SQL_FILE),
110                 _connection.getConnection(), false);
111         try
112         {
113             TablesDependencyHelper.getDependentTables(_connection,
114                     "XXXXXX_TABLE_NON_EXISTING");
115             fail("Should not be able to get the dependent tables for a non existing input table");
116         }
117         catch (final SearchException expected)
118         {
119             final Throwable cause = expected.getCause();
120             assertThat(cause).isInstanceOf(NoSuchTableException.class);
121 
122             final String expectedSchema =
123                     getEnvironment().getProfile().getSchema();
124             final String expectedMessage =
125                     "The table 'XXXXXX_TABLE_NON_EXISTING' does not exist in schema '"
126                             + expectedSchema + "'";
127             assertThat(cause.getMessage())
128                     .as("NoSuchTableException message.")
129                     .isEqualToIgnoringCase(expectedMessage);
130         }
131         finally
132         {
133             DdlExecutor.dropTables(_connection.getConnection(), "B", "C", "E",
134                     "H", "F", "G", "A", "D");
135             refreshConnection();
136         }
137     }
138 
139     @Test
140     void testGetDependentTables_withMultipleRootTables_returnsDependentTablesInFkOrder() throws Exception
141     {
142         DdlExecutor.dropTables(_connection.getConnection(), "B", "C", "E",
143                 "H", "F", "G", "A", "D");
144         DdlExecutor.executeDdlFile(
145                 TestUtils.getFile(
146                         "sql/" + ImportNodesFilterSearchCallbackIT.SQL_FILE),
147                 _connection.getConnection(), false);
148         try
149         {
150             final String[][] allInput =
151                     ImportNodesFilterSearchCallbackIT.COMPOUND_INPUT;
152             final String[][] allExpectedOutput =
153                     ImportNodesFilterSearchCallbackIT.COMPOUND_OUTPUT;
154             for (int i = 0; i < allInput.length; i++)
155             {
156                 final String[] input = allInput[i];
157                 final String[] expectedOutput = allExpectedOutput[i];
158                 final String[] actualOutput = TablesDependencyHelper
159                         .getDependentTables(_connection, input);
160                 assertThat(actualOutput).as("output didn't match for i=" + i + ".")
161                         .usingElementComparator(String.CASE_INSENSITIVE_ORDER)
162                         .containsExactly(expectedOutput);
163             }
164         }
165         finally
166         {
167             DdlExecutor.dropTables(_connection.getConnection(), "B", "C", "E",
168                     "H", "F", "G", "A", "D");
169             refreshConnection();
170         }
171     }
172 
173     @Test
174     void testGetAllDependentTables_withSingleRootTable_returnsAllDependentTablesInOrder() throws Exception
175     {
176         DdlExecutor.dropTables(_connection.getConnection(), "C", "D", "E",
177                 "A", "B", "F");
178         DdlExecutor.executeDdlFile(
179                 TestUtils.getFile(
180                         "sql/" + ImportAndExportKeysSearchCallbackIT.SQL_FILE),
181                 _connection.getConnection(), false);
182         try
183         {
184             final String[][] allInput =
185                     ImportAndExportKeysSearchCallbackIT.SINGLE_INPUT;
186             final String[][] allExpectedOutput =
187                     ImportAndExportKeysSearchCallbackIT.SINGLE_OUTPUT;
188             for (int i = 0; i < allInput.length; i++)
189             {
190                 final String[] input = allInput[i];
191                 final String[] expectedOutput = allExpectedOutput[i];
192                 final String[] actualOutput = TablesDependencyHelper
193                         .getAllDependentTables(_connection, input[0]);
194                 assertThat(actualOutput).as("output didn't match for i=" + i + ".")
195                         .usingElementComparator(String.CASE_INSENSITIVE_ORDER)
196                         .containsExactly(expectedOutput);
197             }
198         }
199         finally
200         {
201             DdlExecutor.dropTables(_connection.getConnection(), "C", "D", "E",
202                     "A", "B", "F");
203             refreshConnection();
204         }
205     }
206 
207     @Test
208     void testGetAllDependentTables_withMultipleRootTables_returnsAllDependentTablesInOrder() throws Exception
209     {
210         DdlExecutor.dropTables(_connection.getConnection(), "C", "D", "E",
211                 "A", "B", "F");
212         DdlExecutor.executeDdlFile(
213                 TestUtils.getFile(
214                         "sql/" + ImportAndExportKeysSearchCallbackIT.SQL_FILE),
215                 _connection.getConnection(), false);
216         try
217         {
218             final String[][] allInput =
219                     ImportAndExportKeysSearchCallbackIT.COMPOUND_INPUT;
220             final String[][] allExpectedOutput =
221                     ImportAndExportKeysSearchCallbackIT.COMPOUND_OUTPUT;
222             for (int i = 0; i < allInput.length; i++)
223             {
224                 final String[] input = allInput[i];
225                 final String[] expectedOutput = allExpectedOutput[i];
226                 final String[] actualOutput = TablesDependencyHelper
227                         .getAllDependentTables(_connection, input);
228                 assertThat(actualOutput).as("output didn't match for i=" + i + ".")
229                         .usingElementComparator(String.CASE_INSENSITIVE_ORDER)
230                         .containsExactly(expectedOutput);
231             }
232         }
233         finally
234         {
235             DdlExecutor.dropTables(_connection.getConnection(), "C", "D", "E",
236                     "A", "B", "F");
237             refreshConnection();
238         }
239     }
240 
241     @Test
242     void testGetDirectDependentTables_withMultiHopChain_excludesTransitivePrerequisites()
243             throws Exception
244     {
245         DdlExecutor.dropTables(_connection.getConnection(), "B", "C", "E",
246                 "H", "F", "G", "A", "D");
247         DdlExecutor.executeDdlFile(
248                 TestUtils.getFile(
249                         "sql/" + ImportNodesFilterSearchCallbackIT.SQL_FILE),
250                 _connection.getConnection(), false);
251         try
252         {
253             // Despite its name, getDirectDependentTables actually returns the tables
254             // the root's own FKs point to (its direct prerequisites), via
255             // ImportedKeysSearchCallback -- a longstanding, pre-existing naming quirk
256             // in this class, confirmed empirically, not introduced by this change.
257             // C's own FKs point directly to A and F; C reaches D only transitively,
258             // via A -> D (FKD). A direct-only (depth=1) query must exclude D.
259             final Set<String> actualOutput = TablesDependencyHelper
260                     .getDirectDependentTables(_connection, "C");
261 
262             assertThat(actualOutput)
263                     .as("C's direct FK targets must be exactly {C, A, F}, "
264                             + "excluding D which is only reachable transitively via A.")
265                     .usingElementComparator(String.CASE_INSENSITIVE_ORDER)
266                     .containsExactlyInAnyOrder("C", "A", "F");
267         }
268         finally
269         {
270             DdlExecutor.dropTables(_connection.getConnection(), "B", "C", "E",
271                     "H", "F", "G", "A", "D");
272             refreshConnection();
273         }
274     }
275 
276     @Test
277     void testGetDirectDependsOnTables_withMultiHopChain_excludesTransitiveDependents()
278             throws Exception
279     {
280         DdlExecutor.dropTables(_connection.getConnection(), "B", "C", "E",
281                 "H", "F", "G", "A", "D");
282         DdlExecutor.executeDdlFile(
283                 TestUtils.getFile(
284                         "sql/" + ImportNodesFilterSearchCallbackIT.SQL_FILE),
285                 _connection.getConnection(), false);
286         try
287         {
288             // Despite its name, getDirectDependsOnTables actually returns the tables
289             // with a direct FK pointing at the root (its direct dependents), via
290             // ExportedKeysSearchCallback -- the same pre-existing naming quirk as
291             // above, in the opposite direction.
292             // C, E, and G each have a direct FKA pointing at A. B depends on A only
293             // transitively, via B -> C -> A (FKC then FKA). A direct-only (depth=1)
294             // query must exclude B.
295             final Set<String> actualOutput = TablesDependencyHelper
296                     .getDirectDependsOnTables(_connection, "A");
297 
298             assertThat(actualOutput)
299                     .as("Tables with a direct FK to A must be exactly {A, C, E, G}, "
300                             + "excluding B which only depends on A transitively via C.")
301                     .usingElementComparator(String.CASE_INSENSITIVE_ORDER)
302                     .containsExactlyInAnyOrder("A", "C", "E", "G");
303         }
304         finally
305         {
306             DdlExecutor.dropTables(_connection.getConnection(), "B", "C", "E",
307                     "H", "F", "G", "A", "D");
308             refreshConnection();
309         }
310     }
311 
312     @Test
313     void testGetAllDataset_withSingleTableInput_returnsDataSetContainingDependentTables() throws Exception
314     {
315         DdlExecutor.dropTables(_connection.getConnection(), "C", "D", "E",
316                 "A", "B", "F");
317         DdlExecutor.executeDdlFile(
318                 TestUtils.getFile(
319                         "sql/" + ImportAndExportKeysSearchCallbackIT.SQL_FILE),
320                 _connection.getConnection(), false);
321         try
322         {
323             final String[][] allInput =
324                     ImportAndExportKeysSearchCallbackIT.SINGLE_INPUT;
325             final String[][] allExpectedOutput =
326                     ImportAndExportKeysSearchCallbackIT.SINGLE_OUTPUT;
327             for (int i = 0; i < allInput.length; i++)
328             {
329                 final String[] input = allInput[i];
330                 final String[] expectedOutput = allExpectedOutput[i];
331                 final IDataSet actualOutput = TablesDependencyHelper
332                         .getAllDataset(_connection, input[0], new HashSet<>());
333                 final String[] actualOutputTables =
334                         actualOutput.getTableNames();
335                 assertThat(actualOutputTables)
336                         .as("output didn't match for i=" + i + ".")
337                         .usingElementComparator(String.CASE_INSENSITIVE_ORDER)
338                         .containsExactly(expectedOutput);
339             }
340         }
341         finally
342         {
343             DdlExecutor.dropTables(_connection.getConnection(), "C", "D", "E",
344                     "A", "B", "F");
345             refreshConnection();
346         }
347     }
348 
349     @Test
350     void testGetAllDataset_withTableInSeparateSchema_returnsDataSetContainingDependentTables() throws Exception
351     {
352         // This test requires HSQLDB-specific SET SCHEMA DDL; kept on HSQLDB intentionally.
353         final Connection hsqlConn =
354                 HypersonicEnvironment.createJdbcConnection("mem:schema_test");
355         DdlExecutor.executeDdlFile(
356                 TestUtils.getFile("sql/hypersonic_switch_schema.sql"),
357                 hsqlConn, false);
358         DdlExecutor.executeDdlFile(
359                 TestUtils.getFile(
360                         "sql/" + ImportAndExportKeysSearchCallbackIT.SQL_FILE),
361                 hsqlConn, false);
362         final org.dbunit.database.IDatabaseConnection hsqlDbConn =
363                 new DatabaseConnection(hsqlConn);
364         try
365         {
366             final String[][] allInputWithSchema =
367                     ImportAndExportKeysSearchCallbackIT
368                             .getSingleInputWithSchema("TEST_SCHEMA");
369             final String[][] allExpectedOutput =
370                     ImportAndExportKeysSearchCallbackIT.SINGLE_OUTPUT;
371             for (int i = 0; i < allInputWithSchema.length; i++)
372             {
373                 final String[] input = allInputWithSchema[i];
374                 final String[] expectedOutput = allExpectedOutput[i];
375                 final IDataSet actualOutput = TablesDependencyHelper
376                         .getAllDataset(hsqlDbConn, input[0], new HashSet<>());
377                 final String[] actualOutputTables =
378                         actualOutput.getTableNames();
379                 assertThat(actualOutputTables)
380                         .as("output didn't match for i=" + i + ".")
381                         .usingElementComparator(String.CASE_INSENSITIVE_ORDER)
382                         .containsExactly(expectedOutput);
383             }
384         }
385         finally
386         {
387             DdlExecutor.dropTables(hsqlConn, "C", "D", "E", "A", "B", "F");
388             HypersonicEnvironment.shutdown(hsqlConn);
389         }
390     }
391 
392     /**
393      * Ensure the order is not lost on the way because of the conversion between
394      * Map and Array.
395      *
396      * @throws Exception
397      */
398     @Test
399     void testGetAllDataset_withMultipleTableInputs_returnsDataSetPreservingDependencyOrder() throws Exception
400     {
401         DdlExecutor.dropTables(_connection.getConnection(), "B", "C", "E",
402                 "H", "F", "G", "A", "D");
403         DdlExecutor.executeDdlFile(
404                 TestUtils.getFile(
405                         "sql/" + ImportNodesFilterSearchCallbackIT.SQL_FILE),
406                 _connection.getConnection(), false);
407         try
408         {
409             final String[][] allInput =
410                     ImportNodesFilterSearchCallbackIT.COMPOUND_INPUT;
411             final String[][] allExpectedOutput =
412                     ImportNodesFilterSearchCallbackIT.COMPOUND_OUTPUT;
413             for (int i = 0; i < allInput.length; i++)
414             {
415                 final String[] input = allInput[i];
416                 final PkTableMap inputMap = new PkTableMap();
417                 for (int j = 0; j < input.length; j++)
418                 {
419                     inputMap.put(input[j], new TreeSet<>());
420                 }
421 
422                 final String[] expectedOutput = allExpectedOutput[i];
423                 final IDataSet actualOutput = TablesDependencyHelper
424                         .getDataset(_connection, inputMap);
425                 final String[] actualOutputArray =
426                         actualOutput.getTableNames();
427                 assertThat(actualOutputArray)
428                         .as("output didn't match for i=" + i + ".")
429                         .usingElementComparator(String.CASE_INSENSITIVE_ORDER)
430                         .containsExactly(expectedOutput);
431             }
432         }
433         finally
434         {
435             DdlExecutor.dropTables(_connection.getConnection(), "B", "C", "E",
436                     "H", "F", "G", "A", "D");
437             refreshConnection();
438         }
439     }
440 
441 }