View Javadoc
1   /*
2    *
3    * The DbUnit Database Testing Framework
4    * Copyright (C)2002-2026, 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;
22  
23  import static org.assertj.core.api.Assertions.assertThat;
24  import static org.assertj.core.api.Assertions.assertThatCode;
25  
26  import java.sql.PreparedStatement;
27  import java.sql.SQLException;
28  import java.util.ArrayList;
29  import java.util.Arrays;
30  import java.util.List;
31  import java.util.Locale;
32  import java.util.concurrent.atomic.AtomicInteger;
33  
34  import org.dbunit.database.CachedResultSetTableFactory;
35  import org.dbunit.database.DatabaseConfig;
36  import org.dbunit.database.IDatabaseConnection;
37  import org.dbunit.database.IResultSetTable;
38  import org.dbunit.database.IResultSetTableFactory;
39  import org.dbunit.dataset.Column;
40  import org.dbunit.dataset.DataSetException;
41  import org.dbunit.dataset.ITable;
42  import org.dbunit.dataset.ITableMetaData;
43  import org.dbunit.operation.DatabaseOperation;
44  import org.dbunit.util.fileloader.DataFileLoader;
45  import org.dbunit.util.fileloader.FlatXmlDataFileLoader;
46  import org.junit.jupiter.api.Test;
47  
48  /**
49   * Real-database integration test that {@link DefaultPrepAndExpectedTestCase} selects only the
50   * columns it compares when {@link VerifyTableDefinition#isSortOnFilteredColumnsOnly()} is true,
51   * so an excluded column, typically a large one, is not fetched, and that it keeps selecting every
52   * column otherwise. A recording {@link IResultSetTableFactory} over the real one captures what the
53   * verification asked the database for.
54   *
55   * @since 3.6.0
56   */
57  class DefaultPrepAndExpectedTestCaseColumnProjectionIT
58  {
59      private static final String ALL_TABLES_DATA_FILE_NAME = "/xml/flatXmlDataSetTest.xml";
60      private static final String TEST_TABLE_DATA_FILE_NAME =
61              "/xml/columnProjectionPrepAndExpected.xml";
62      private static final String TEST_TABLE = "TEST_TABLE";
63      private static final String[] NO_COLUMNS = {};
64  
65      private final DataFileLoader dataFileLoader = new FlatXmlDataFileLoader();
66  
67      @Test
68      void testPostTest_sortOnFilteredColumnsOnlyOnEveryTable_passesOnTheDatabaseColumns()
69              throws Exception
70      {
71          final VerifyTableDefinition[] tables = {sorted("TEST_TABLE"), sorted("SECOND_TABLE"),
72                  sorted("EMPTY_TABLE"), sorted("PK_TABLE"), sorted("ONLY_PK_TABLE"),
73                  sorted("EMPTY_MULTITYPE_TABLE")};
74  
75          runWithRecordingFactory(ALL_TABLES_DATA_FILE_NAME, tables, (testCase, recording) ->
76                  assertThatCode(testCase::postTest)
77                          .as("Selecting the compared columns from the database's own column"
78                                  + " definitions must verify the same data as selecting every"
79                                  + " column, on every table shape.")
80                          .doesNotThrowAnyException());
81      }
82  
83      @Test
84      void testPostTest_sortOnFilteredColumnsOnlyWithExcludedColumns_selectsOnlyTheComparedColumns()
85              throws Exception
86      {
87          final VerifyTableDefinition[] tables = {new VerifyTableDefinition(TEST_TABLE,
88                  new String[] {"COLUMN2", "COLUMN3"}, null, null, true)};
89  
90          runWithRecordingFactory(TEST_TABLE_DATA_FILE_NAME, tables, (testCase, recording) ->
91          {
92              testCase.postTest();
93  
94              assertThat(recording.getRequestedColumnNames())
95                      .as("An excluded column plays no part in sorting or comparing, so it must not"
96                              + " be selected.")
97                      .containsExactly(Arrays.asList("COLUMN0", "COLUMN1"));
98              assertThat(recording.getSelectStatements())
99                      .as("No select * statement may be issued when the columns can be projected.")
100                     .isEmpty();
101         });
102     }
103 
104     @Test
105     void testPostTest_defaultSortWithExcludedColumns_selectsEveryColumn() throws Exception
106     {
107         final VerifyTableDefinition[] tables = {new VerifyTableDefinition(TEST_TABLE,
108                 new String[] {"COLUMN2", "COLUMN3"}, null, null, false)};
109 
110         runWithRecordingFactory(TEST_TABLE_DATA_FILE_NAME, tables, (testCase, recording) ->
111         {
112             testCase.postTest();
113 
114             assertThat(recording.getSelectStatements())
115                     .as("Every database column is a sort key in the default mode, so every column"
116                             + " must still be selected.")
117                     .singleElement().asString().containsIgnoringCase("select *");
118             assertThat(recording.getRequestedColumnNames())
119                     .as("The default mode must not project the columns, so no column list may be"
120                             + " requested.")
121                     .isEmpty();
122         });
123     }
124 
125     @Test
126     void testPostTest_sortOnFilteredColumnsOnlyAndEveryColumnExcluded_fallsBackToSelectingEveryColumn()
127             throws Exception
128     {
129         final VerifyTableDefinition[] tables = {new VerifyTableDefinition(TEST_TABLE,
130                 new String[] {"COLUMN0", "COLUMN1", "COLUMN2", "COLUMN3"}, null, null, true)};
131 
132         runWithRecordingFactory(TEST_TABLE_DATA_FILE_NAME, tables, (testCase, recording) ->
133         {
134             assertThatCode(testCase::postTest)
135                     .as("Nothing is compared, so the verification must pass.")
136                     .doesNotThrowAnyException();
137 
138             assertThat(recording.getSelectStatements())
139                     .as("A select list with no columns is invalid, so the whole table must be"
140                             + " selected instead.")
141                     .singleElement().asString().containsIgnoringCase("select *");
142         });
143     }
144 
145     @Test
146     void testPostTest_sortOnFilteredColumnsOnlyAndLoadTableDataFromDatabaseOverridden_callsTheOverride()
147             throws Exception
148     {
149         final VerifyTableDefinition[] tables = {new VerifyTableDefinition(TEST_TABLE,
150                 new String[] {"COLUMN2", "COLUMN3"}, null, null, true)};
151         final AtomicInteger overrideCalls = new AtomicInteger();
152         final IDatabaseTester tester = makeDatabaseTester();
153         final DefaultPrepAndExpectedTestCase testCase =
154                 new DefaultPrepAndExpectedTestCase(dataFileLoader, tester)
155                 {
156                     @Override
157                     public ITable loadTableDataFromDatabase(final String tableName,
158                             final IDatabaseConnection connection) throws Exception
159                     {
160                         overrideCalls.incrementAndGet();
161                         return super.loadTableDataFromDatabase(tableName, connection);
162                     }
163                 };
164         testCase.configureTest(tables, new String[] {TEST_TABLE_DATA_FILE_NAME},
165                 new String[] {TEST_TABLE_DATA_FILE_NAME});
166         testCase.preTest();
167 
168         try
169         {
170             testCase.postTest();
171         } finally
172         {
173             DatabaseEnvironment.getInstance().closeConnection();
174         }
175 
176         assertThat(overrideCalls)
177                 .as("A subclass that overrides how the table is loaded, for example to add a"
178                         + " where clause, must keep deciding that, so the columns are not"
179                         + " projected behind its back.")
180                 .hasValue(1);
181     }
182 
183     private interface Verification
184     {
185         void run(DefaultPrepAndExpectedTestCase testCase, RecordingResultSetTableFactory recording)
186                 throws Exception;
187     }
188 
189     private void runWithRecordingFactory(final String dataFileName,
190             final VerifyTableDefinition[] tables, final Verification verification)
191             throws Exception
192     {
193         final DatabaseEnvironment environment = DatabaseEnvironment.getInstance();
194         try
195         {
196             final IDatabaseConnection connection = environment.getConnection();
197             final RecordingResultSetTableFactory recording = new RecordingResultSetTableFactory();
198             connection.getConfig().setProperty(DatabaseConfig.PROPERTY_RESULTSET_TABLE_FACTORY,
199                     recording);
200             final IDatabaseTester tester = new DefaultDatabaseTester(connection);
201             tester.setTearDownOperation(DatabaseOperation.DELETE_ALL);
202             final DefaultPrepAndExpectedTestCase testCase =
203                     new DefaultPrepAndExpectedTestCase(dataFileLoader, tester);
204             testCase.configureTest(tables, new String[] {dataFileName},
205                     new String[] {dataFileName});
206             testCase.preTest();
207 
208             verification.run(testCase, recording);
209         } finally
210         {
211             environment.closeConnection();
212         }
213     }
214 
215     private static VerifyTableDefinition sorted(final String tableName)
216     {
217         return new VerifyTableDefinition(tableName, NO_COLUMNS, null, null, true);
218     }
219 
220     private IDatabaseTester makeDatabaseTester() throws Exception
221     {
222         final IDatabaseConnection connection = DatabaseEnvironment.getInstance().getConnection();
223         final IDatabaseTester tester = new DefaultDatabaseTester(connection);
224         tester.setTearDownOperation(DatabaseOperation.DELETE_ALL);
225         return tester;
226     }
227 
228     private static final class RecordingResultSetTableFactory implements IResultSetTableFactory
229     {
230         private final IResultSetTableFactory delegate = new CachedResultSetTableFactory();
231         private final List<String> selectStatements = new ArrayList<>();
232         private final List<List<String>> requestedColumnNames = new ArrayList<>();
233 
234         @Override
235         public IResultSetTable createTable(final String tableName, final String selectStatement,
236                 final IDatabaseConnection connection) throws SQLException, DataSetException
237         {
238             selectStatements.add(selectStatement);
239             return delegate.createTable(tableName, selectStatement, connection);
240         }
241 
242         @Override
243         public IResultSetTable createTable(final ITableMetaData metaData,
244                 final IDatabaseConnection connection) throws SQLException, DataSetException
245         {
246             final List<String> columnNames = new ArrayList<>();
247             for (final Column column : metaData.getColumns())
248             {
249                 columnNames.add(column.getColumnName().toUpperCase(Locale.ENGLISH));
250             }
251             requestedColumnNames.add(columnNames);
252             return delegate.createTable(metaData, connection);
253         }
254 
255         @Override
256         public IResultSetTable createTable(final String tableName,
257                 final PreparedStatement preparedStatement, final IDatabaseConnection connection)
258                 throws SQLException, DataSetException
259         {
260             return delegate.createTable(tableName, preparedStatement, connection);
261         }
262 
263         private List<String> getSelectStatements()
264         {
265             return selectStatements;
266         }
267 
268         private List<List<String>> getRequestedColumnNames()
269         {
270             return requestedColumnNames;
271         }
272     }
273 }