This is an automated email from the ASF dual-hosted git repository. amanin pushed a commit to branch fix/sql-temporal in repository https://gitbox.apache.org/repos/asf/sis.git
commit f52c54a4ea29d211dfbddb66bb0b155a58a4e239 Author: Alexis Manin <[email protected]> AuthorDate: Mon May 23 19:22:29 2022 +0200 chore(Storage): add a test class dedicated to temporal value conversion. --- .../sql/feature/TemporalValueGetterTest.java | 191 +++++++++++++++++++++ .../org/apache/sis/test/suite/SQLTestSuite.java | 1 + 2 files changed, 192 insertions(+) diff --git a/storage/sis-sqlstore/src/test/java/org/apache/sis/internal/sql/feature/TemporalValueGetterTest.java b/storage/sis-sqlstore/src/test/java/org/apache/sis/internal/sql/feature/TemporalValueGetterTest.java new file mode 100644 index 0000000000..399ac2d48c --- /dev/null +++ b/storage/sis-sqlstore/src/test/java/org/apache/sis/internal/sql/feature/TemporalValueGetterTest.java @@ -0,0 +1,191 @@ +package org.apache.sis.internal.sql.feature; + +import java.sql.Connection; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.time.Instant; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.stream.Collectors; +import org.apache.sis.test.TestCase; +import org.apache.sis.test.sql.TestDatabase; +import org.junit.AfterClass; +import org.junit.Assume; +import org.junit.AssumptionViolatedException; +import org.junit.BeforeClass; +import org.junit.Test; + +import static java.time.ZoneOffset.ofHours; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; + +/** + * Check access and conversion to database datetime values. + */ +public class TemporalValueGetterTest extends TestCase { + + private static final String DB_TITLE = "Test Temporal values"; + private static final String TEST_SCHEMA = "SIS_TEST_TEMPORAL"; + private static final String DEFAULT_DRIVER = "DEFAULT"; + public static final String PG_DRIVER = "POSTGRESQL"; + private static Map<String, TestDatabase> DATABASES; + + @BeforeClass + public static void initTestDatabases() throws Exception { + if (DATABASES != null) throw new IllegalStateException("Test database should be null before initialization"); + DATABASES = new HashMap<>(); + DATABASES.put(DEFAULT_DRIVER, TestDatabase.create(DB_TITLE)); + DATABASES.put("HSQLDB", TestDatabase.createOnHSQLDB(DB_TITLE, true)); + DATABASES.put("H2", TestDatabase.createOnH2(DB_TITLE)); + try { + final TestDatabase db = TestDatabase.createOnPostgreSQL(TEST_SCHEMA, true); + DATABASES.put(PG_DRIVER, db); + } catch (AssumptionViolatedException e) { + // Ok, environment does not contain a PostGreSQL test database + out.println("[FINE] PostgreSQL database deactivated due to assumption requirement not met: "+e.getMessage()); + } + } + + @AfterClass + public static void disposeTestDatabases() throws Exception { + if (DATABASES != null) { + List<Exception> closeErrors = DATABASES.values().stream() + .map(db -> { + try { + db.close(); + return null; + } catch (Exception e) { + return e; + } + }) + .filter(Objects::nonNull) + .collect(Collectors.toList()); + if (!closeErrors.isEmpty()) { + final Exception base = closeErrors.get(0); + for (int i = 1 ; i < closeErrors.size() ; i++) { + base.addSuppressed(closeErrors.get(i)); + } + throw base; + } + } + } + + @Test + public void sql_date_should_translate_to_java_time_LocalDate() { + testOnEachDatabase((driver, db) -> { + execute(driver, db, + "CREATE TABLE TEST_DATE (\"value\" DATE)", + "INSERT INTO TEST_DATE (\"value\") VALUES ('2022-05-01'), ('2022-05-31'), (null)" + ); + + ValueGetter<LocalDate> getter = ValueGetter.AsDate.INSTANCE; + execute(driver, db, "SELECT \"value\" FROM TEST_DATE", r -> { + r.next(); + assertEquals(LocalDate.of(2022, 5, 1), getter.getValue(null, r, 1)); + r.next(); + assertEquals(LocalDate.of(2022, 5, 31), getter.getValue(null, r, 1)); + r.next(); + // Ensure no NPE occurs, i.e the converter is resilient to null inputs + assertNull(getter.getValue(null, r, 1)); + assertFalse("Defense against test change: only 3 values should be present in the test table", r.next()); + }); + }); + } + + @Test + public void sql_timestamp_should_translate_to_java_time_LocalDateTime() { + testOnEachDatabase((driver, db) -> { + execute(driver, db, + "CREATE TABLE TEST_TIMESTAMP_WITHOUT_TIMEZONE (\"value\" TIMESTAMP)", + "INSERT INTO TEST_TIMESTAMP_WITHOUT_TIMEZONE (\"value\") VALUES ('2022-05-01 01:01:01'), ('2022-05-31 23:59:59.999'), (null)" + ); + + ValueGetter<LocalDateTime> getter = ValueGetter.AsLocalDateTime.INSTANCE; + execute(driver, db, "SELECT \"value\" FROM TEST_TIMESTAMP_WITHOUT_TIMEZONE", r -> { + r.next(); + assertEquals(LocalDateTime.of(2022, 5, 1, 1, 1, 1), getter.getValue(null, r, 1)); + r.next(); + assertEquals(LocalDateTime.of(2022, 5, 31, 23, 59, 59, 999_000_000), getter.getValue(null, r, 1)); + r.next(); + // Ensure no NPE occurs, i.e the converter is resilient to null inputs + assertNull(getter.getValue(null, r, 1)); + assertFalse("Defense against test change: only 3 values should be present in the test table", r.next()); + }); + }); + } + + @Test + public void sql_timestamp_with_timezone_should_translate_to_offset_date_time() { + testOnEachDatabase((driver, db) -> { + Assume.assumeFalse("Derby does not support TIMESTAMP WITH TIME ZONE data type", DEFAULT_DRIVER.equals(driver)); + execute(driver, db, + "CREATE TABLE TEST_TIMESTAMP_WITH_TIMEZONE (\"value\" TIMESTAMP WITH TIME ZONE)", + "INSERT INTO TEST_TIMESTAMP_WITH_TIMEZONE (\"value\") VALUES ('2022-05-01 01:01:01+01:00'), ('2022-05-31 23:59:59.999+02:00'), (null)" + ); + + ValueGetter<Instant> getter = ValueGetter.AsInstant.INSTANCE; + execute(driver, db, "SELECT \"value\" FROM TEST_TIMESTAMP_WITH_TIMEZONE", r -> { + r.next(); + assertEquals(LocalDateTime.of(2022, 5, 1, 1, 1, 1).atOffset(ofHours(1)).toInstant(), getter.getValue(null, r, 1)); + r.next(); + assertEquals(LocalDateTime.of(2022, 5, 31, 23, 59, 59, 999_000_000).atOffset(ofHours(2)).toInstant(), getter.getValue(null, r, 1)); + r.next(); + // Ensure no NPE occurs, i.e the converter is resilient to null inputs + assertNull(getter.getValue(null, r, 1)); + assertFalse("Defense against test change: only 3 values should be present in the test table", r.next()); + }); + }); + } + + private void testOnEachDatabase(DatabaseTester tester) { + DATABASES.forEach((driver, db) -> { + try { + tester.test(driver, db); + } catch (AssumptionViolatedException e) { + // OK, user has deactivated test upon some conditions + out.println("[FINE] Error ignored because of failed assumption. Driver="+driver+", reason="+e.getMessage()); + } catch (AssertionError e) { + throw new AssertionError("Test failed for driver: "+driver, e); + } catch (Exception e) { + throw new RuntimeException("Error while testing driver: "+driver, e); + } + }); + } + + @FunctionalInterface + private interface DatabaseTester { + void test(String driver, TestDatabase db) throws Exception; + } + + @FunctionalInterface + private interface QueryAction { + void accept(ResultSet queryResult) throws Exception; + } + + private static void execute(String driver, TestDatabase db, String firstQuery, String... moreQueries) throws SQLException { + try (Connection c = db.source.getConnection()) { + if (PG_DRIVER.equals(driver)) c.setSchema(TEST_SCHEMA); + try (Statement s = c.createStatement()) { + s.execute(firstQuery); + for (String q : moreQueries) s.execute(q); + } + } + } + + private static void execute(String driver, TestDatabase db, String sqlQuery, QueryAction action) throws Exception { + try (Connection c = db.source.getConnection()) { + if (PG_DRIVER.equals(driver)) c.setSchema(TEST_SCHEMA); + try (Statement s = c.createStatement(); + ResultSet r = s.executeQuery(sqlQuery) + ) { + action.accept(r); + } + } + } +} diff --git a/storage/sis-sqlstore/src/test/java/org/apache/sis/test/suite/SQLTestSuite.java b/storage/sis-sqlstore/src/test/java/org/apache/sis/test/suite/SQLTestSuite.java index 566261d653..673e53cf43 100644 --- a/storage/sis-sqlstore/src/test/java/org/apache/sis/test/suite/SQLTestSuite.java +++ b/storage/sis-sqlstore/src/test/java/org/apache/sis/test/suite/SQLTestSuite.java @@ -25,6 +25,7 @@ import org.junit.BeforeClass; * All tests from the {@code sis-sqlstore} module, in rough dependency order. */ @Suite.SuiteClasses({ + org.apache.sis.internal.sql.feature.TemporalValueGetterTest.class, org.apache.sis.internal.sql.feature.GeometryGetterTest.class, org.apache.sis.internal.sql.feature.SelectionClauseWriterTest.class, org.apache.sis.internal.sql.postgis.BandTest.class,
