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  package org.dbunit.dataset.datatype;
22  
23  import static org.assertj.core.api.Assertions.assertThat;
24  import static org.junit.jupiter.api.Assumptions.assumeTrue;
25  
26  import java.sql.Connection;
27  import java.sql.PreparedStatement;
28  import java.sql.ResultSet;
29  import java.sql.SQLException;
30  import java.sql.Statement;
31  import java.sql.Timestamp;
32  import java.sql.Types;
33  import java.time.OffsetDateTime;
34  
35  import org.dbunit.AbstractDatabaseIT;
36  import org.dbunit.DatabaseEnvironment;
37  import org.dbunit.TestFeature;
38  import org.dbunit.database.IDatabaseConnection;
39  import org.junit.jupiter.api.AfterEach;
40  import org.junit.jupiter.api.BeforeEach;
41  import org.junit.jupiter.api.Test;
42  
43  /**
44   * Integration tests for {@link TimestampDataType#setSqlValue} against whichever real database is
45   * configured by the active Maven profile (see {@link DatabaseEnvironment}), proving issue #831's
46   * fix for {@code TIMESTAMP WITH TIME ZONE} columns against a real JDBC driver, not just the H2
47   * in-memory driver used by {@link TimestampDataTypeTest}.
48   *
49   * <p>
50   * The zoned-column test skips itself (rather than failing) on any profile declaring
51   * {@link TestFeature#TIMESTAMP_WITH_TIMEZONE} unsupported via
52   * {@code dbunit.profile.unsupportedFeatures} - either because the SQL dialect rejects
53   * {@code TIMESTAMP WITH TIME ZONE} column syntax outright (e.g. Derby, MySQL, SQL Server, which
54   * has no equivalent type), or because the JDBC driver cannot reliably report
55   * {@link Types#TIMESTAMP_WITH_TIMEZONE} from {@link java.sql.ParameterMetaData} for such a column
56   * even though the database itself supports the type (a known limitation of Oracle's driver, not a
57   * dbunit defect). Declaring the gap in the profile up front - rather than discovering it by
58   * catching whatever exception a given driver happens to throw - means any other failure during
59   * setup surfaces as a real test error instead of being silently treated as unsupported. The
60   * plain-TIMESTAMP fallback test needs no such gating since every supported database accepts that
61   * syntax, though it substitutes {@code DATETIME2} on SQL Server, whose {@code TIMESTAMP} type is
62   * an unrelated, non-nullable row-versioning type rather than a date/time type.
63   *
64   * @since 3.4.0
65   */
66  class TimestampDataTypeIT
67  {
68      private static final String ZONED_TABLE = "TIMESTAMPTYPE_TZ_IT";
69  
70      private static final String PLAIN_TABLE = "TIMESTAMPTYPE_PLAIN_IT";
71  
72      private static final String DATASET_VALUE = "2026-07-22 15:00:00 -0200";
73  
74      private IDatabaseConnection connection;
75  
76      @BeforeEach
77      void setUp() throws Exception
78      {
79          connection = DatabaseEnvironment.getInstance().getConnection();
80      }
81  
82      @AfterEach
83      void tearDown() throws Exception
84      {
85          if (connection != null)
86          {
87              dropTableQuietly(ZONED_TABLE);
88              dropTableQuietly(PLAIN_TABLE);
89              connection.close();
90              connection = null;
91          }
92      }
93  
94      private void dropTableQuietly(final String tableName)
95      {
96          try (Statement statement = connection.getConnection().createStatement())
97          {
98              statement.execute("DROP TABLE " + tableName);
99          } catch (final SQLException e)
100         {
101             // Table may not exist: either a prior run never created it, or
102             // the active profile does not support the column type under
103             // test.
104         }
105     }
106 
107     @Test
108     void testSetSqlValue_withOffsetDifferentFromLocalZone_writesCorrectInstantToTimestampWithTimeZoneColumn()
109             throws Exception
110     {
111         assumeTrue(AbstractDatabaseIT.environmentHasFeature(TestFeature.TIMESTAMP_WITH_TIMEZONE),
112                 "Skipping: active database profile declares TIMESTAMP_WITH_TIMEZONE unsupported "
113                         + "(see dbunit.profile.unsupportedFeatures).");
114 
115         final Connection jdbcConnection = connection.getConnection();
116         dropTableQuietly(ZONED_TABLE);
117         try (Statement statement = jdbcConnection.createStatement())
118         {
119             statement.execute("CREATE TABLE " + ZONED_TABLE
120                     + " (ID INTEGER NOT NULL PRIMARY KEY, TIM TIMESTAMP WITH TIME ZONE)");
121         }
122 
123         try (PreparedStatement insert = jdbcConnection
124                 .prepareStatement("INSERT INTO " + ZONED_TABLE + " VALUES (1, ?)"))
125         {
126             final int parameterType = insert.getParameterMetaData().getParameterType(1);
127             assumeTrue(parameterType == Types.TIMESTAMP_WITH_TIMEZONE,
128                     "Skipping: active database driver does not report TIMESTAMP_WITH_TIMEZONE "
129                             + "parameter metadata for this column.");
130 
131             new TimestampDataType().setSqlValue(DATASET_VALUE, 1, insert);
132             insert.executeUpdate();
133         }
134 
135         try (PreparedStatement select = jdbcConnection
136                 .prepareStatement("SELECT TIM FROM " + ZONED_TABLE + " WHERE ID = 1");
137                 ResultSet resultSet = select.executeQuery())
138         {
139             resultSet.next();
140             final OffsetDateTime stored = resultSet.getObject(1, OffsetDateTime.class);
141             assertThat(stored)
142                     .as("a TIMESTAMP WITH TIME ZONE column must store the offset implied by "
143                             + "the dataset's own time zone, not the JVM's default time zone.")
144                     .isEqualTo(OffsetDateTime.parse("2026-07-22T15:00:00-02:00"));
145         }
146     }
147 
148     @Test
149     void testSetSqlValue_withOffsetDifferentFromLocalZone_writesLiteralDigitsToTimestampColumnWithoutTimeZone()
150             throws Exception
151     {
152         final Connection jdbcConnection = connection.getConnection();
153         dropTableQuietly(PLAIN_TABLE);
154         final boolean isSqlServer = jdbcConnection.getMetaData().getDatabaseProductName()
155                 .startsWith("Microsoft SQL Server");
156         final String plainTimestampType = isSqlServer ? "DATETIME2" : "TIMESTAMP";
157         try (Statement statement = jdbcConnection.createStatement())
158         {
159             statement.execute("CREATE TABLE " + PLAIN_TABLE
160                     + " (ID INTEGER NOT NULL PRIMARY KEY, TIM " + plainTimestampType + ")");
161         }
162 
163         try (PreparedStatement insert = jdbcConnection
164                 .prepareStatement("INSERT INTO " + PLAIN_TABLE + " VALUES (1, ?)"))
165         {
166             new TimestampDataType().setSqlValue(DATASET_VALUE, 1, insert);
167             insert.executeUpdate();
168         }
169 
170         try (PreparedStatement select = jdbcConnection
171                 .prepareStatement("SELECT TIM FROM " + PLAIN_TABLE + " WHERE ID = 1");
172                 ResultSet resultSet = select.executeQuery())
173         {
174             resultSet.next();
175             assertThat(resultSet.getTimestamp(1))
176                     .as("issue #711: a column without a time zone must still receive the "
177                             + "dataset's literal wall-clock digits, regardless of the JVM's "
178                             + "default time zone.")
179                     .isEqualTo(Timestamp.valueOf("2026-07-22 15:00:00"));
180         }
181     }
182 }