This is an automated email from the ASF dual-hosted git repository.

ntimofeev pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/cayenne.git


The following commit(s) were added to refs/heads/master by this push:
     new 25264972e CAY-2804 LocalTimeValueType potential loss of precision
25264972e is described below

commit 25264972e9a4c32133c0d254fa6bbe133a165182
Author: Mikhail Dzianishchyts <[email protected]>
AuthorDate: Tue Jun 18 17:12:20 2024 +0400

    CAY-2804 LocalTimeValueType potential loss of precision
    
    Signed-off-by: Nikita Timofeev <[email protected]>
---
 .../cayenne/access/types/LocalTimeValueType.java   |  13 +-
 .../java/org/apache/cayenne/dba/AutoAdapter.java   |   8 ++
 .../java/org/apache/cayenne/dba/DbAdapter.java     |  10 ++
 .../java/org/apache/cayenne/dba/JdbcAdapter.java   |  32 +++--
 .../java/org/apache/cayenne/dba/TypesMapping.java  |   7 ++
 .../org/apache/cayenne/dba/db2/DB2Adapter.java     |   8 ++
 .../org/apache/cayenne/dba/derby/DerbyAdapter.java |   8 ++
 .../apache/cayenne/dba/oracle/Oracle8Adapter.java  |   8 ++
 .../apache/cayenne/dba/oracle/OracleAdapter.java   |   8 ++
 .../apache/cayenne/access/types/Java8TimeIT.java   |  11 +-
 .../access/types/LocalTimeValueTypeTest.java       | 137 +++++++++++++++++++++
 .../org/apache/cayenne/unit/DB2UnitDbAdapter.java  |   5 +
 .../apache/cayenne/unit/DerbyUnitDbAdapter.java    |   5 +
 .../apache/cayenne/unit/OracleUnitDbAdapter.java   |   5 +
 .../org/apache/cayenne/unit/UnitDbAdapter.java     |   8 ++
 .../testcontainers/SqlServerContainerProvider.java |   1 +
 cayenne/src/test/resources/java8.map.xml           |   2 +-
 17 files changed, 263 insertions(+), 13 deletions(-)

diff --git 
a/cayenne/src/main/java/org/apache/cayenne/access/types/LocalTimeValueType.java 
b/cayenne/src/main/java/org/apache/cayenne/access/types/LocalTimeValueType.java
index beb3df8cc..22f6ca675 100644
--- 
a/cayenne/src/main/java/org/apache/cayenne/access/types/LocalTimeValueType.java
+++ 
b/cayenne/src/main/java/org/apache/cayenne/access/types/LocalTimeValueType.java
@@ -20,7 +20,12 @@
 package org.apache.cayenne.access.types;
 
 import java.sql.Time;
+import java.time.Instant;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
 import java.time.LocalTime;
+import java.time.ZoneId;
+import java.time.ZonedDateTime;
 
 /**
  * @since 4.0
@@ -39,12 +44,16 @@ public class LocalTimeValueType implements 
ValueObjectType<LocalTime, Time> {
 
     @Override
     public LocalTime toJavaObject(Time value) {
-        return value.toLocalTime();
+        Instant instant = Instant.ofEpochMilli(value.getTime());
+        return LocalTime.ofInstant(instant, ZoneId.systemDefault());
     }
 
     @Override
     public Time fromJavaObject(LocalTime object) {
-        return Time.valueOf(object);
+        LocalDateTime epochDateTime = object.atDate(LocalDate.EPOCH);
+        ZonedDateTime zonedDateTime = 
epochDateTime.atZone(ZoneId.systemDefault());
+        long epochMillis = zonedDateTime.toInstant().toEpochMilli();
+        return new Time(epochMillis);
     }
 
     @Override
diff --git a/cayenne/src/main/java/org/apache/cayenne/dba/AutoAdapter.java 
b/cayenne/src/main/java/org/apache/cayenne/dba/AutoAdapter.java
index 845154bfa..93a3679f6 100644
--- a/cayenne/src/main/java/org/apache/cayenne/dba/AutoAdapter.java
+++ b/cayenne/src/main/java/org/apache/cayenne/dba/AutoAdapter.java
@@ -153,6 +153,14 @@ public class AutoAdapter implements DbAdapter {
                return getAdapter().typeSupportsLength(type);
        }
 
+       /**
+        * @since 5.0
+        */
+       @Override
+       public boolean typeSupportsScale(int type) {
+               return getAdapter().typeSupportsScale(type);
+       }
+
        @Override
        public Collection<String> dropTableStatements(DbEntity table) {
                return getAdapter().dropTableStatements(table);
diff --git a/cayenne/src/main/java/org/apache/cayenne/dba/DbAdapter.java 
b/cayenne/src/main/java/org/apache/cayenne/dba/DbAdapter.java
index aa4ba1c18..f2007ea2a 100644
--- a/cayenne/src/main/java/org/apache/cayenne/dba/DbAdapter.java
+++ b/cayenne/src/main/java/org/apache/cayenne/dba/DbAdapter.java
@@ -110,6 +110,16 @@ public interface DbAdapter {
 
        boolean typeSupportsLength(int type);
 
+       /**
+        * Returns true if supplied type can have a scale attribute as a part 
of column definition.
+        *
+        * @param type sql type code
+        * @return <code>true</code> if a given type supports scale
+        *
+        * @since 5.0
+        */
+       boolean typeSupportsScale(int type);
+
        /**
         * Returns a collection of SQL statements needed to drop a database 
table.
         *
diff --git a/cayenne/src/main/java/org/apache/cayenne/dba/JdbcAdapter.java 
b/cayenne/src/main/java/org/apache/cayenne/dba/JdbcAdapter.java
index d778a325a..f859bb830 100644
--- a/cayenne/src/main/java/org/apache/cayenne/dba/JdbcAdapter.java
+++ b/cayenne/src/main/java/org/apache/cayenne/dba/JdbcAdapter.java
@@ -32,7 +32,6 @@ import org.apache.cayenne.CayenneRuntimeException;
 import org.apache.cayenne.access.DataNode;
 import org.apache.cayenne.access.sqlbuilder.sqltree.SQLTreeProcessor;
 import org.apache.cayenne.access.translator.ParameterBinding;
-import org.apache.cayenne.access.translator.batch.BatchTranslatorFactory;
 import org.apache.cayenne.access.translator.ejbql.EJBQLTranslatorFactory;
 import org.apache.cayenne.access.translator.ejbql.JdbcEJBQLTranslatorFactory;
 import org.apache.cayenne.access.translator.select.DefaultSelectTranslator;
@@ -247,12 +246,27 @@ public class JdbcAdapter implements DbAdapter {
      *
      * @since 4.0
      */
+    @Override
     public boolean typeSupportsLength(int type) {
         return type == Types.BINARY || type == Types.CHAR || type == 
Types.NCHAR || type == Types.NVARCHAR
                 || type == Types.LONGNVARCHAR || type == Types.DECIMAL || type 
== Types.DOUBLE || type == Types.FLOAT
                 || type == Types.NUMERIC || type == Types.REAL || type == 
Types.VARBINARY || type == Types.VARCHAR;
     }
 
+    /**
+     * Returns true if supplied type can have a scale attribute as a part of 
column definition.
+     *
+     * @param type sql type code
+     * @return <code>true</code> if a given type supports scale
+     *
+     * @since 5.0
+     */
+    @Override
+    public boolean typeSupportsScale(int type) {
+        return type == Types.DECIMAL || type == Types.DOUBLE || type == 
Types.REAL || type == Types.NUMERIC
+                || type == Types.TIME || type == Types.TIMESTAMP;
+    }
+
     /**
      * @since 3.0
      */
@@ -342,21 +356,21 @@ public class JdbcAdapter implements DbAdapter {
     }
 
     public static String sizeAndPrecision(DbAdapter adapter, DbAttribute 
column) {
-        if (!adapter.typeSupportsLength(column.getType())) {
+        if (!adapter.typeSupportsLength(column.getType()) && 
!adapter.typeSupportsScale(column.getType())) {
             return "";
         }
 
         int len = column.getMaxLength();
-        int scale = TypesMapping.isDecimal(column.getType()) && 
column.getType() != Types.FLOAT ? column.getScale()
-                : -1;
+        int scale = TypesMapping.isDateTime(column.getType())
+                    || TypesMapping.isDecimal(column.getType()) && 
column.getType() != Types.FLOAT
+                ? column.getScale() : -1;
 
-        // sanity check
-        if (scale > len) {
-            scale = -1;
+        if (len > 0) {
+            return "(" + len + (scale >= 0 && len > scale ? ", " + scale : "") 
+ ")";
         }
 
-        if (len > 0) {
-            return "(" + len + (scale >= 0 ? ", " + scale : "") + ")";
+        if (scale >= 0 && TypesMapping.isDateTime(column.getType())) {
+            return "(" + scale + ")";
         }
 
         return "";
diff --git a/cayenne/src/main/java/org/apache/cayenne/dba/TypesMapping.java 
b/cayenne/src/main/java/org/apache/cayenne/dba/TypesMapping.java
index 695120981..d5eeaa236 100644
--- a/cayenne/src/main/java/org/apache/cayenne/dba/TypesMapping.java
+++ b/cayenne/src/main/java/org/apache/cayenne/dba/TypesMapping.java
@@ -310,6 +310,13 @@ public class TypesMapping {
                return type == DECIMAL || type == DOUBLE || type == FLOAT || 
type == REAL || type == NUMERIC;
        }
 
+       /**
+        * @since 5.0
+        */
+       public static boolean isDateTime(int type) {
+               return type == TIME || type == TIMESTAMP;
+       }
+
        /**
         * Returns an array of string names of the default JDBC data types.
         */
diff --git a/cayenne/src/main/java/org/apache/cayenne/dba/db2/DB2Adapter.java 
b/cayenne/src/main/java/org/apache/cayenne/dba/db2/DB2Adapter.java
index 0d6fa50d0..89c139cb3 100644
--- a/cayenne/src/main/java/org/apache/cayenne/dba/db2/DB2Adapter.java
+++ b/cayenne/src/main/java/org/apache/cayenne/dba/db2/DB2Adapter.java
@@ -133,6 +133,14 @@ public class DB2Adapter extends JdbcAdapter {
         return type == Types.LONGVARCHAR || type == Types.LONGVARBINARY || 
super.typeSupportsLength(type);
     }
 
+    /**
+     * @since 5.0
+     */
+    @Override
+    public boolean typeSupportsScale(int type) {
+        return type != Types.TIME && super.typeSupportsScale(type);
+    }
+
     /**
      * @since 4.2
      */
diff --git 
a/cayenne/src/main/java/org/apache/cayenne/dba/derby/DerbyAdapter.java 
b/cayenne/src/main/java/org/apache/cayenne/dba/derby/DerbyAdapter.java
index 7ddf5b0c2..540b50245 100644
--- a/cayenne/src/main/java/org/apache/cayenne/dba/derby/DerbyAdapter.java
+++ b/cayenne/src/main/java/org/apache/cayenne/dba/derby/DerbyAdapter.java
@@ -165,6 +165,14 @@ public class DerbyAdapter extends JdbcAdapter {
         }
     }
 
+    /**
+     * @since 5.0
+     */
+    @Override
+    public boolean typeSupportsScale(int type) {
+        return type != Types.TIME && super.typeSupportsScale(type);
+    }
+
     /**
      * @since 4.2
      */
diff --git 
a/cayenne/src/main/java/org/apache/cayenne/dba/oracle/Oracle8Adapter.java 
b/cayenne/src/main/java/org/apache/cayenne/dba/oracle/Oracle8Adapter.java
index c609af680..2cb4a839e 100644
--- a/cayenne/src/main/java/org/apache/cayenne/dba/oracle/Oracle8Adapter.java
+++ b/cayenne/src/main/java/org/apache/cayenne/dba/oracle/Oracle8Adapter.java
@@ -32,6 +32,7 @@ import org.apache.cayenne.resource.ResourceLocator;
 
 import java.lang.reflect.Method;
 import java.net.URL;
+import java.sql.Types;
 import java.util.List;
 
 /**
@@ -96,4 +97,11 @@ public class Oracle8Adapter extends OracleAdapter {
                return super.findResource(name);
        }
 
+       /**
+        * @since 5.0
+        */
+       @Override
+       public boolean typeSupportsScale(int type) {
+               return type != Types.TIMESTAMP && super.typeSupportsScale(type);
+       }
 }
diff --git 
a/cayenne/src/main/java/org/apache/cayenne/dba/oracle/OracleAdapter.java 
b/cayenne/src/main/java/org/apache/cayenne/dba/oracle/OracleAdapter.java
index ad077d47f..501e7daf8 100644
--- a/cayenne/src/main/java/org/apache/cayenne/dba/oracle/OracleAdapter.java
+++ b/cayenne/src/main/java/org/apache/cayenne/dba/oracle/OracleAdapter.java
@@ -300,6 +300,14 @@ public class OracleAdapter extends JdbcAdapter {
                return SYSTEM_SCHEMAS;
        }
 
+       /**
+        * @since 5.0
+        */
+       @Override
+       public boolean typeSupportsScale(int type) {
+               return type != Types.TIME && super.typeSupportsScale(type);
+       }
+
        /**
         * @since 3.0
         */
diff --git 
a/cayenne/src/test/java/org/apache/cayenne/access/types/Java8TimeIT.java 
b/cayenne/src/test/java/org/apache/cayenne/access/types/Java8TimeIT.java
index bfa7e735f..c349a4f0f 100644
--- a/cayenne/src/test/java/org/apache/cayenne/access/types/Java8TimeIT.java
+++ b/cayenne/src/test/java/org/apache/cayenne/access/types/Java8TimeIT.java
@@ -26,6 +26,7 @@ import java.time.LocalDateTime;
 import java.time.LocalTime;
 import java.time.Period;
 import java.time.temporal.ChronoField;
+import java.time.temporal.TemporalField;
 
 import org.apache.cayenne.access.DataContext;
 import org.apache.cayenne.di.Inject;
@@ -36,6 +37,7 @@ import org.apache.cayenne.testdo.java8.LocalDateTestEntity;
 import org.apache.cayenne.testdo.java8.LocalDateTimeTestEntity;
 import org.apache.cayenne.testdo.java8.LocalTimeTestEntity;
 import org.apache.cayenne.testdo.java8.PeriodTestEntity;
+import org.apache.cayenne.unit.UnitDbAdapter;
 import org.apache.cayenne.unit.di.runtime.CayenneProjects;
 import org.apache.cayenne.unit.di.runtime.RuntimeCase;
 import org.apache.cayenne.unit.di.runtime.UseCayenneRuntime;
@@ -52,6 +54,9 @@ public class Java8TimeIT extends RuntimeCase {
        @Inject
        private DataContext context;
 
+       @Inject
+       private UnitDbAdapter unitDbAdapter;
+
        @Inject
        private DBHelper dbHelper;
 
@@ -101,10 +106,14 @@ public class Java8TimeIT extends RuntimeCase {
 
                LocalTimeTestEntity testRead = 
ObjectSelect.query(LocalTimeTestEntity.class).selectOne(context);
 
+               TemporalField testValue = unitDbAdapter.supportsPreciseTime()
+                               ? ChronoField.MILLI_OF_DAY
+                               : ChronoField.SECOND_OF_DAY;
+
                assertNotNull(testRead.getTime());
                assertEquals(LocalTime.class, testRead.getTime().getClass());
                assertEquals(localTime.toSecondOfDay(), 
testRead.getTime().toSecondOfDay());
-
+               assertEquals(localTime.get(testValue), 
testRead.getTime().get(testValue));
        }
 
        @Test
diff --git 
a/cayenne/src/test/java/org/apache/cayenne/access/types/LocalTimeValueTypeTest.java
 
b/cayenne/src/test/java/org/apache/cayenne/access/types/LocalTimeValueTypeTest.java
new file mode 100644
index 000000000..d3071afc9
--- /dev/null
+++ 
b/cayenne/src/test/java/org/apache/cayenne/access/types/LocalTimeValueTypeTest.java
@@ -0,0 +1,137 @@
+/*****************************************************************
+ *   Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    https://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ ****************************************************************/
+
+package org.apache.cayenne.access.types;
+
+import org.junit.Test;
+
+import java.sql.Time;
+import java.time.Instant;
+import java.time.LocalTime;
+import java.time.ZoneId;
+import java.time.temporal.ChronoField;
+import java.util.Arrays;
+import java.util.List;
+import java.util.TimeZone;
+import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+
+public class LocalTimeValueTypeTest {
+
+    private static final LocalTimeValueType valueType = new 
LocalTimeValueType();
+
+    @Test
+    public void testToJavaObject() {
+        // UTC 17:50:23.123
+        long utcMillis = 64223123;
+        long systemMillis = Instant.ofEpochMilli(utcMillis)
+                .atZone(ZoneId.systemDefault())
+                .get(ChronoField.MILLI_OF_DAY);
+
+        Time time = new Time(utcMillis);
+        LocalTime localTime = valueType.toJavaObject(time);
+
+        assertEquals(systemMillis, localTime.get(ChronoField.MILLI_OF_DAY));
+    }
+
+    @Test
+    public void testFromJavaObject() {
+        // UTC 17:50:23.123
+        long utcMillis = 64223123;
+        long systemMillis = Instant.ofEpochMilli(utcMillis)
+                .atZone(ZoneId.systemDefault())
+                .getLong(ChronoField.MILLI_OF_DAY);
+
+        LocalTime localTime = 
LocalTime.ofNanoOfDay(TimeUnit.MILLISECONDS.toNanos(systemMillis));
+        Time time = valueType.fromJavaObject(localTime);
+
+        assertEquals(utcMillis, time.getTime());
+    }
+
+    @Test
+    public void testToJavaObjectFromJavaObject() {
+        // UTC 17:50:23.123
+        long utcMillis = 64223123;
+
+        Time time = new Time(utcMillis);
+        LocalTime localTime = valueType.toJavaObject(time);
+        Time newTime = valueType.fromJavaObject(localTime);
+
+        assertEquals(time, newTime);
+    }
+
+    @Test
+    public void testToJavaObject_isBackwardCompatible() {
+        // UTC 17:50:23.123
+        long utcMillis = 64223123;
+
+        Time time = new Time(utcMillis);
+        LocalTime impreciseLocalTime = time.toLocalTime();
+        LocalTime localTime = valueType.toJavaObject(time);
+
+        assertEquals(impreciseLocalTime.toSecondOfDay(), 
localTime.get(ChronoField.SECOND_OF_DAY));
+    }
+
+    @Test
+    public void testFromJavaObject_isBackwardCompatible() {
+        // UTC 17:50:23.123
+        long utcMillis = 64223123;
+        long systemMillis = Instant.ofEpochMilli(utcMillis)
+                .atZone(ZoneId.systemDefault())
+                .getLong(ChronoField.MILLI_OF_DAY);
+
+        LocalTime localTime = 
LocalTime.ofNanoOfDay(TimeUnit.MILLISECONDS.toNanos(systemMillis));
+        Time impreciseTime = Time.valueOf(localTime);
+        Time time = valueType.fromJavaObject(localTime);
+
+        assertEquals(TimeUnit.MILLISECONDS.toSeconds(impreciseTime.getTime()),
+                     TimeUnit.MILLISECONDS.toSeconds(time.getTime()));
+    }
+
+    @Test
+    public void testToJavaObjectFromJavaObject_changeTimeZone() {
+        TimeZone originalTimeZone = TimeZone.getDefault();
+
+        try {
+            // UTC 17:50:23.123
+            long utcMillis = 64223123;
+
+            Time time = new Time(utcMillis);
+            LocalTime localTime = valueType.toJavaObject(time);
+            TimeZone.setDefault(getOtherTimeZone(originalTimeZone));
+            Time newTime = valueType.fromJavaObject(localTime);
+
+            assertNotEquals(time, newTime);
+        } finally {
+            TimeZone.setDefault(originalTimeZone);
+        }
+    }
+
+    private static TimeZone getOtherTimeZone(TimeZone timeZone) {
+        List<TimeZone> timeZones = Arrays.stream(TimeZone.getAvailableIDs())
+                .map(TimeZone::getTimeZone)
+                .distinct()
+                .filter(tz -> tz.getRawOffset() != timeZone.getRawOffset())
+                .collect(Collectors.toList());
+        return timeZones.get(0);
+    }
+}
diff --git 
a/cayenne/src/test/java/org/apache/cayenne/unit/DB2UnitDbAdapter.java 
b/cayenne/src/test/java/org/apache/cayenne/unit/DB2UnitDbAdapter.java
index 1f2519c95..94ce3310e 100644
--- a/cayenne/src/test/java/org/apache/cayenne/unit/DB2UnitDbAdapter.java
+++ b/cayenne/src/test/java/org/apache/cayenne/unit/DB2UnitDbAdapter.java
@@ -73,4 +73,9 @@ public class DB2UnitDbAdapter extends UnitDbAdapter {
     public boolean supportsSelectBooleanExpression() {
         return false;
     }
+
+    @Override
+    public boolean supportsPreciseTime() {
+        return false;
+    }
 }
diff --git 
a/cayenne/src/test/java/org/apache/cayenne/unit/DerbyUnitDbAdapter.java 
b/cayenne/src/test/java/org/apache/cayenne/unit/DerbyUnitDbAdapter.java
index dcc0872a3..c9daca590 100644
--- a/cayenne/src/test/java/org/apache/cayenne/unit/DerbyUnitDbAdapter.java
+++ b/cayenne/src/test/java/org/apache/cayenne/unit/DerbyUnitDbAdapter.java
@@ -77,4 +77,9 @@ public class DerbyUnitDbAdapter extends UnitDbAdapter {
     public boolean supportsNullComparison() {
         return false;
     }
+
+    @Override
+    public boolean supportsPreciseTime() {
+        return false;
+    }
 }
diff --git 
a/cayenne/src/test/java/org/apache/cayenne/unit/OracleUnitDbAdapter.java 
b/cayenne/src/test/java/org/apache/cayenne/unit/OracleUnitDbAdapter.java
index 715a11171..ad855856d 100644
--- a/cayenne/src/test/java/org/apache/cayenne/unit/OracleUnitDbAdapter.java
+++ b/cayenne/src/test/java/org/apache/cayenne/unit/OracleUnitDbAdapter.java
@@ -150,4 +150,9 @@ public class OracleUnitDbAdapter extends UnitDbAdapter {
     public boolean supportsSerializableTransactionIsolation() {
         return true;
     }
+
+    @Override
+    public boolean supportsPreciseTime() {
+        return false;
+    }
 }
diff --git a/cayenne/src/test/java/org/apache/cayenne/unit/UnitDbAdapter.java 
b/cayenne/src/test/java/org/apache/cayenne/unit/UnitDbAdapter.java
index 177dbdae3..80d32ecc5 100644
--- a/cayenne/src/test/java/org/apache/cayenne/unit/UnitDbAdapter.java
+++ b/cayenne/src/test/java/org/apache/cayenne/unit/UnitDbAdapter.java
@@ -430,4 +430,12 @@ public class UnitDbAdapter {
     public boolean supportScalarAsExpression(){
         return false;
     }
+
+    /**
+     * Returns true if the target database has time type with fractional 
seconds.
+     * @since 5.0
+     */
+    public boolean supportsPreciseTime() {
+        return true;
+    }
 }
diff --git 
a/cayenne/src/test/java/org/apache/cayenne/unit/testcontainers/SqlServerContainerProvider.java
 
b/cayenne/src/test/java/org/apache/cayenne/unit/testcontainers/SqlServerContainerProvider.java
index 11ae0c518..6a6560df0 100644
--- 
a/cayenne/src/test/java/org/apache/cayenne/unit/testcontainers/SqlServerContainerProvider.java
+++ 
b/cayenne/src/test/java/org/apache/cayenne/unit/testcontainers/SqlServerContainerProvider.java
@@ -28,6 +28,7 @@ public class SqlServerContainerProvider extends 
TestContainerProvider {
     @Override
     JdbcDatabaseContainer<?> createContainer(DockerImageName dockerImageName) {
         return new MSSQLServerContainer<>(dockerImageName)
+                .withUrlParam("sendTimeAsDatetime", "false")
                 .acceptLicense();
     }
 
diff --git a/cayenne/src/test/resources/java8.map.xml 
b/cayenne/src/test/resources/java8.map.xml
index 8d8b871f0..7a7e14927 100644
--- a/cayenne/src/test/resources/java8.map.xml
+++ b/cayenne/src/test/resources/java8.map.xml
@@ -23,7 +23,7 @@
        </db-entity>
        <db-entity name="LOCAL_TIME_TEST">
                <db-attribute name="ID" type="INTEGER" isPrimaryKey="true" 
isMandatory="true"/>
-               <db-attribute name="TimeField" type="TIME"/>
+               <db-attribute name="TimeField" type="TIME" scale="3"/>
        </db-entity>
        <db-entity name="PERIOD_TEST">
                <db-attribute name="ID" type="INTEGER" isPrimaryKey="true" 
isMandatory="true"/>

Reply via email to