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"/>