You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ignite.apache.org by ko...@apache.org on 2022/07/14 12:35:22 UTC
[ignite-3] branch main updated: IGNITE-17212 Sql. Add support for DEFAULT operator (#941)
This is an automated email from the ASF dual-hosted git repository.
korlov pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/ignite-3.git
The following commit(s) were added to refs/heads/main by this push:
new 1e5d7ae33 IGNITE-17212 Sql. Add support for DEFAULT operator (#941)
1e5d7ae33 is described below
commit 1e5d7ae33200b906de918778fef6af8f5069ffb8
Author: korlov42 <ko...@gridgain.com>
AuthorDate: Thu Jul 14 15:35:18 2022 +0300
IGNITE-17212 Sql. Add support for DEFAULT operator (#941)
---
.../ignite/internal/sql/engine/ItDmlTest.java | 132 +++++++++++++++++++++
.../internal/sql/engine/util/QueryChecker.java | 21 +---
.../internal/sql/engine/exec/exp/RexImpTable.java | 12 +-
.../internal/sql/engine/externalize/RelJson.java | 2 +
.../prepare/ddl/DdlSqlToCommandConverter.java | 64 +++++++++-
.../sql/engine/schema/IgniteTableImpl.java | 7 +-
.../sql/engine/schema/TableDescriptorImpl.java | 5 +-
.../ignite/internal/sql/engine/util/TypeUtils.java | 13 +-
8 files changed, 233 insertions(+), 23 deletions(-)
diff --git a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/sql/engine/ItDmlTest.java b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/sql/engine/ItDmlTest.java
index dc5e931b1..422795774 100644
--- a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/sql/engine/ItDmlTest.java
+++ b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/sql/engine/ItDmlTest.java
@@ -22,6 +22,11 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
+import java.math.BigDecimal;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
+import java.util.List;
import java.util.stream.Collectors;
import org.apache.calcite.runtime.CalciteContextException;
import org.apache.ignite.internal.sql.engine.util.Commons;
@@ -307,4 +312,131 @@ public class ItDmlTest extends AbstractBasicIntegrationTest {
assertEquals(3, pkVals.size());
}
+
+ @Test
+ public void testInsertDefaultValue() {
+ var args = List.of(
+ // TODO: IGNITE-17298
+ // new DefaultValueArg("BOOLEAN", "TRUE", Boolean.TRUE),
+ // new DefaultValueArg("BOOLEAN NOT NULL", "TRUE", Boolean.TRUE),
+
+ new DefaultValueArg("BIGINT", "10", 10L),
+ new DefaultValueArg("INTEGER", "10", 10),
+ new DefaultValueArg("SMALLINT", "10", (short) 10),
+ new DefaultValueArg("TINYINT", "10", (byte) 10),
+ new DefaultValueArg("DOUBLE", "10.01", 10.01d),
+ new DefaultValueArg("FLOAT", "10.01", 10.01f),
+ new DefaultValueArg("DECIMAL(4, 2)", "10.01", new BigDecimal("10.01")),
+ new DefaultValueArg("CHAR(2)", "'10'", "10"),
+ new DefaultValueArg("VARCHAR", "'10'", "10"),
+ new DefaultValueArg("VARCHAR NOT NULL", "'10'", "10"),
+ new DefaultValueArg("VARCHAR(2)", "'10'", "10"),
+
+ // TODO: IGNITE-17373
+ // new DefaultValueArg("INTERVAL DAYS TO SECONDS", "INTERVAL '10' DAYS", Duration.ofDays(10)),
+ // new DefaultValueArg("INTERVAL YEARS TO MONTHS", "INTERVAL '10' MONTHS", Period.ofMonths(10)),
+ // new DefaultValueArg("INTERVAL MONTHS", "INTERVAL '10' YEARS", Period.ofYears(10)),
+
+ new DefaultValueArg("DATE", "DATE '2021-01-01'", LocalDate.parse("2021-01-01")),
+ new DefaultValueArg("TIME", "TIME '01:01:01'", LocalTime.parse("01:01:01")),
+ new DefaultValueArg("TIMESTAMP", "TIMESTAMP '2021-01-01 01:01:01'", LocalDateTime.parse("2021-01-01T01:01:01")),
+
+ // TODO: IGNITE-17376
+ // new DefaultValueArg("TIMESTAMP WITH LOCAL TIME ZONE", "TIMESTAMP '2021-01-01 01:01:01'"
+ // , LocalDateTime.parse("2021-01-01T01:01:01")),
+
+ new DefaultValueArg("BINARY(3)", "x'010203'", new byte[]{1, 2, 3})
+
+ // TODO: IGNITE-17374
+ // new DefaultValueArg("VARBINARY", "x'010203'", new byte[]{1, 2, 3})
+ );
+
+ checkDefaultValue(args);
+
+ checkWrongDefault("VARCHAR", "10");
+ checkWrongDefault("INT", "'10'");
+ checkWrongDefault("INT", "TRUE");
+ checkWrongDefault("DATE", "10");
+ checkWrongDefault("DATE", "TIME '01:01:01'");
+ checkWrongDefault("TIME", "TIMESTAMP '2021-01-01 01:01:01'");
+
+ // TODO: IGNITE-17298
+ // checkWrongDefault("BOOLEAN", "1");
+
+ // TODO: IGNITE-17373
+ // checkWrongDefault("INTERVAL DAYS", "INTERVAL '10' MONTHS");
+ // checkWrongDefault("INTERVAL MONTHS", "INTERVAL '10' DAYS");
+
+ checkWrongDefault("VARBINARY", "'10'");
+ checkWrongDefault("VARBINARY", "10");
+ }
+
+ private void checkDefaultValue(List<DefaultValueArg> args) {
+ assert args.size() > 0;
+
+ try {
+ StringBuilder createStatementBuilder = new StringBuilder("CREATE TABLE test (id INT PRIMARY KEY");
+
+ var idx = 0;
+ for (var arg : args) {
+ createStatementBuilder.append(", col_").append(idx++).append(" ").append(arg.sqlType)
+ .append(" DEFAULT ").append(arg.sqlVal);
+ }
+
+ var createStatement = createStatementBuilder.append(")").toString();
+
+ sql(createStatement);
+ sql("INSERT INTO test (id) VALUES (0)");
+
+ var expectedVals = args.stream()
+ .map(a -> a.expectedVal)
+ .collect(Collectors.toList());
+
+ var columnEnumerationBuilder = new StringBuilder("col_0");
+
+ for (int i = 1; i < args.size(); i++) {
+ columnEnumerationBuilder.append(", col_").append(i);
+ }
+
+ var columnEnumeration = columnEnumerationBuilder.toString();
+
+ checkQueryResult("SELECT " + columnEnumeration + " FROM test", expectedVals);
+
+ sql("DELETE FROM test");
+ sql("INSERT INTO test (id, " + columnEnumeration + ") VALUES (0, "
+ + ", DEFAULT".repeat(args.size()).substring(2) + ")");
+
+ checkQueryResult("SELECT " + columnEnumeration + " FROM test", expectedVals);
+ } finally {
+ sql("DROP TABLE IF EXISTS test");
+ }
+ }
+
+ private void checkQueryResult(String sql, List<Object> expectedVals) {
+ assertQuery(sql).returns(expectedVals.toArray()).check();
+ }
+
+ private void checkWrongDefault(String sqlType, String sqlVal) {
+ try {
+ assertThrows(
+ IgniteException.class,
+ () -> sql("CREATE TABLE test (val " + sqlType + " DEFAULT " + sqlVal + ")"),
+ "Cannot convert literal"
+ );
+ } finally {
+ sql("DROP TABLE IF EXISTS test");
+ }
+ }
+
+ private static class DefaultValueArg {
+ final String sqlType;
+ final String sqlVal;
+ final Object expectedVal;
+
+ private DefaultValueArg(String sqlType, String sqlVal, Object expectedVal) {
+ this.sqlType = sqlType;
+ this.sqlVal = sqlVal;
+ this.expectedVal = expectedVal;
+ }
+ }
}
diff --git a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/sql/engine/util/QueryChecker.java b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/sql/engine/util/QueryChecker.java
index c03a7f126..06dadbd71 100644
--- a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/sql/engine/util/QueryChecker.java
+++ b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/sql/engine/util/QueryChecker.java
@@ -33,6 +33,7 @@ import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
+import java.util.Objects;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import org.apache.ignite.internal.sql.SqlColumnTypeConverter;
@@ -42,7 +43,6 @@ import org.apache.ignite.sql.ColumnMetadata;
import org.hamcrest.CoreMatchers;
import org.hamcrest.Matcher;
import org.hamcrest.core.SubstringMatcher;
-import org.jetbrains.annotations.Nullable;
/**
* Query checker.
@@ -439,7 +439,9 @@ public abstract class QueryChecker {
Object item1 = it1.next();
Object item2 = it2.next();
- if (!eq(item1, item2)) {
+ if (item1 instanceof Collection && item2 instanceof Collection) {
+ assertEqualsCollections((Collection<?>) item1, (Collection<?>) item2);
+ } else if (!Objects.deepEquals(item1, item2)) {
fail("Collections are not equal (position " + idx + "):\nExpected: " + exp + "\nActual: " + act);
}
@@ -447,21 +449,10 @@ public abstract class QueryChecker {
}
}
- /**
- * Tests whether specified arguments are equal, or both {@code null}.
- *
- * @param o1 Object to compare.
- * @param o2 Object to compare.
- * @return Returns {@code true} if the specified arguments are equal, or both {@code null}.
- */
- private static boolean eq(@Nullable Object o1, @Nullable Object o2) {
- return o1 == null ? o2 == null : o2 != null && (o1 == o2 || o1.equals(o2));
- }
-
/**
* List comparator.
*/
- private class ListComparator implements Comparator<List<?>> {
+ private static class ListComparator implements Comparator<List<?>> {
/** {@inheritDoc} */
@SuppressWarnings({"rawtypes", "unchecked"})
@Override
@@ -477,7 +468,7 @@ public abstract class QueryChecker {
Object item1 = it1.next();
Object item2 = it2.next();
- if (eq(item1, item2)) {
+ if (Objects.deepEquals(item1, item2)) {
continue;
}
diff --git a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/exp/RexImpTable.java b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/exp/RexImpTable.java
index 56e870f4f..4afcfbc3a 100644
--- a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/exp/RexImpTable.java
+++ b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/exp/RexImpTable.java
@@ -244,6 +244,9 @@ public class RexImpTable {
private final Map<SqlOperator, RexCallImplementor> map = new HashMap<>();
+ /** Placeholder for DEFAULT operator value. */
+ public static final Object DEFAULT_VALUE_PLACEHOLDER = new DefaultValuePlaceholder();
+
/**
* Constructor.
* TODO Documentation https://issues.apache.org/jira/browse/IGNITE-15859
@@ -2477,7 +2480,7 @@ public class RexImpTable {
@Override
Expression implementSafe(final RexToLixTranslator translator,
final RexCall call, final List<Expression> argValueList) {
- return Expressions.constant(null);
+ return Expressions.field(null, RexImpTable.class, "DEFAULT_VALUE_PLACEHOLDER");
}
}
@@ -2549,4 +2552,11 @@ public class RexImpTable {
}
};
}
+
+ private static class DefaultValuePlaceholder {
+ @Override
+ public String toString() {
+ return "DEFAULT";
+ }
+ }
}
diff --git a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/externalize/RelJson.java b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/externalize/RelJson.java
index 8fcfccba3..8394c9c55 100644
--- a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/externalize/RelJson.java
+++ b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/externalize/RelJson.java
@@ -775,6 +775,8 @@ class RelJson {
literal = toEnum(literal);
} else if (type.getSqlTypeName().getFamily() == SqlTypeFamily.BINARY) {
literal = toByteString(literal);
+ } else if (type.getSqlTypeName().getFamily() == SqlTypeFamily.TIMESTAMP && literal instanceof Integer) {
+ literal = ((Integer) literal).longValue();
}
return rexBuilder.makeLiteral(literal, type, true);
diff --git a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/ddl/DdlSqlToCommandConverter.java b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/ddl/DdlSqlToCommandConverter.java
index 289027035..5d74853b9 100644
--- a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/ddl/DdlSqlToCommandConverter.java
+++ b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/ddl/DdlSqlToCommandConverter.java
@@ -22,12 +22,18 @@ import static java.util.stream.Collectors.toUnmodifiableMap;
import static org.apache.ignite.configuration.schemas.store.UnknownDataStorageConfigurationSchema.UNKNOWN_DATA_STORAGE;
import static org.apache.ignite.internal.util.CollectionUtils.nullOrEmpty;
+import java.math.BigDecimal;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
+import java.time.ZoneOffset;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
+import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@@ -48,6 +54,9 @@ import org.apache.calcite.sql.ddl.SqlDropTable;
import org.apache.calcite.sql.ddl.SqlKeyConstraint;
import org.apache.calcite.sql.parser.SqlParserPos;
import org.apache.calcite.sql.type.SqlTypeName;
+import org.apache.calcite.util.DateString;
+import org.apache.calcite.util.TimeString;
+import org.apache.calcite.util.TimestampString;
import org.apache.ignite.internal.sql.engine.prepare.IgnitePlanner;
import org.apache.ignite.internal.sql.engine.prepare.PlanningContext;
import org.apache.ignite.internal.sql.engine.sql.IgniteSqlAlterTableAddColumn;
@@ -263,7 +272,7 @@ public class DdlSqlToCommandConverter {
Object dflt = null;
if (col.expression != null) {
- dflt = ((SqlLiteral) col.expression).getValue();
+ dflt = fromLiteral(relType, ((SqlLiteral) col.expression));
}
cols.add(new ColumnDefinition(name, relType, dflt));
@@ -557,4 +566,57 @@ public class DdlSqlToCommandConverter {
private TableOptionInfo<?> dataStorageFieldOptionInfo(Entry<String, Class<?>> e) {
return new TableOptionInfo<>(e.getKey(), e.getValue(), null, (cmd, o) -> cmd.addDataStorageOption(e.getKey(), o));
}
+
+ /**
+ * Creates a value of required type from the literal.
+ */
+ private static Object fromLiteral(RelDataType columnType, SqlLiteral literal) {
+ try {
+ switch (columnType.getSqlTypeName()) {
+ case VARCHAR:
+ case CHAR:
+ return literal.getValueAs(String.class);
+ case DATE:
+ return LocalDate.ofEpochDay(literal.getValueAs(DateString.class).getDaysSinceEpoch());
+ case TIME:
+ return LocalTime.ofNanoOfDay(TimeUnit.MILLISECONDS.toNanos(literal.getValueAs(TimeString.class).getMillisOfDay()));
+ case TIMESTAMP: {
+ var tsString = literal.getValueAs(TimestampString.class);
+
+ return LocalDateTime.ofEpochSecond(
+ TimeUnit.MILLISECONDS.toSeconds(tsString.getMillisSinceEpoch()),
+ (int) (TimeUnit.MILLISECONDS.toNanos(tsString.getMillisSinceEpoch() % 1000)),
+ ZoneOffset.UTC
+ );
+ }
+ case TIMESTAMP_WITH_LOCAL_TIME_ZONE: {
+ // TODO: IGNITE-17376
+ throw new UnsupportedOperationException("https://issues.apache.org/jira/browse/IGNITE-17376");
+ }
+ case INTEGER:
+ return literal.getValueAs(Integer.class);
+ case BIGINT:
+ return literal.getValueAs(Long.class);
+ case SMALLINT:
+ return literal.getValueAs(Short.class);
+ case TINYINT:
+ return literal.getValueAs(Byte.class);
+ case DECIMAL:
+ return literal.getValueAs(BigDecimal.class);
+ case DOUBLE:
+ return literal.getValueAs(Double.class);
+ case REAL:
+ case FLOAT:
+ return literal.getValueAs(Float.class);
+ case BINARY:
+ case VARBINARY:
+ return literal.getValueAs(byte[].class);
+ default:
+ throw new IllegalStateException("Unknown type [type=" + columnType + ']');
+ }
+ } catch (Throwable th) {
+ // catch throwable here because literal throws an AssertionError when unable to cast value to a given class
+ throw new IgniteException("Unable co convert literal", th);
+ }
+ }
}
diff --git a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/schema/IgniteTableImpl.java b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/schema/IgniteTableImpl.java
index 80c76b3f8..18f3b0f26 100644
--- a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/schema/IgniteTableImpl.java
+++ b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/schema/IgniteTableImpl.java
@@ -50,6 +50,7 @@ import org.apache.ignite.internal.schema.row.Row;
import org.apache.ignite.internal.schema.row.RowAssembler;
import org.apache.ignite.internal.sql.engine.exec.ExecutionContext;
import org.apache.ignite.internal.sql.engine.exec.RowHandler;
+import org.apache.ignite.internal.sql.engine.exec.exp.RexImpTable;
import org.apache.ignite.internal.sql.engine.metadata.ColocationGroup;
import org.apache.ignite.internal.sql.engine.prepare.MappingQueryContext;
import org.apache.ignite.internal.sql.engine.rel.logical.IgniteLogicalIndexScan;
@@ -317,7 +318,7 @@ public class IgniteTableImpl extends AbstractTable implements InternalIgniteTabl
if (colDesc.key() && Commons.implicitPkEnabled() && Commons.IMPLICIT_PK_COL_NAME.equals(colDesc.name())) {
val = colDesc.defaultValue();
} else {
- val = hnd.get(colDesc.logicalIndex(), row);
+ val = replaceDefault(hnd.get(colDesc.logicalIndex(), row), colDesc);
}
val = TypeUtils.fromInternal(ectx, val, NativeTypeSpec.toClass(colDesc.physicalType().spec(), colDesc.nullable()));
@@ -456,6 +457,10 @@ public class IgniteTableImpl extends AbstractTable implements InternalIgniteTabl
return ColocationGroup.forAssignments(assignments);
}
+ private Object replaceDefault(Object val, ColumnDescriptor desc) {
+ return val == RexImpTable.DEFAULT_VALUE_PLACEHOLDER ? desc.defaultValue() : val;
+ }
+
private class StatisticsImpl implements Statistic {
private static final int STATS_CLI_UPDATE_THRESHOLD = 200;
diff --git a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/schema/TableDescriptorImpl.java b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/schema/TableDescriptorImpl.java
index d0d0befb4..37dbf5260 100644
--- a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/schema/TableDescriptorImpl.java
+++ b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/schema/TableDescriptorImpl.java
@@ -37,6 +37,7 @@ import org.apache.ignite.internal.sql.engine.trait.IgniteDistribution;
import org.apache.ignite.internal.sql.engine.trait.IgniteDistributions;
import org.apache.ignite.internal.sql.engine.type.IgniteTypeFactory;
import org.apache.ignite.internal.sql.engine.util.Commons;
+import org.apache.ignite.internal.sql.engine.util.TypeUtils;
/**
* TableDescriptorImpl.
@@ -156,7 +157,9 @@ public class TableDescriptorImpl extends NullInitializerExpressionFactory implem
final RexBuilder rexBuilder = ctx.getRexBuilder();
final IgniteTypeFactory typeFactory = (IgniteTypeFactory) rexBuilder.getTypeFactory();
- return rexBuilder.makeLiteral(desc.defaultValue(), deriveLogicalType(typeFactory, desc), false);
+ Object defaultValue = TypeUtils.toInternal(null, desc.defaultValue());
+
+ return rexBuilder.makeLiteral(defaultValue, deriveLogicalType(typeFactory, desc), false);
}
/** {@inheritDoc} */
diff --git a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/util/TypeUtils.java b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/util/TypeUtils.java
index b8620b0c5..760a60aa2 100644
--- a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/util/TypeUtils.java
+++ b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/util/TypeUtils.java
@@ -62,6 +62,8 @@ public class TypeUtils {
private static final Set<SqlTypeName> CONVERTABLE_TYPES = EnumSet.of(
SqlTypeName.DATE,
SqlTypeName.TIME,
+ SqlTypeName.BINARY,
+ SqlTypeName.VARBINARY,
SqlTypeName.TIME_WITH_LOCAL_TIME_ZONE,
SqlTypeName.TIMESTAMP,
SqlTypeName.TIMESTAMP_WITH_LOCAL_TIME_ZONE,
@@ -247,9 +249,11 @@ public class TypeUtils {
} else if (storageType == LocalDate.class) {
return (int) ((LocalDate) val).toEpochDay();
} else if (storageType == LocalTime.class) {
- return (int) (((LocalTime) val).toNanoOfDay() / 1000 / 1000 /* convert to millis */);
+ return (int) (TimeUnit.NANOSECONDS.toMillis(((LocalTime) val).toNanoOfDay()));
} else if (storageType == LocalDateTime.class) {
- return ((LocalDateTime) val).toEpochSecond(ZoneOffset.UTC);
+ var dt = (LocalDateTime) val;
+
+ return TimeUnit.SECONDS.toMillis(dt.toEpochSecond(ZoneOffset.UTC)) + TimeUnit.NANOSECONDS.toMillis(dt.getNano());
} else if (storageType == Duration.class) {
return TimeUnit.SECONDS.toMillis(((Duration) val).getSeconds())
+ TimeUnit.NANOSECONDS.toMillis(((Duration) val).getNano());
@@ -272,9 +276,10 @@ public class TypeUtils {
} else if (storageType == LocalDate.class && val instanceof Integer) {
return LocalDate.ofEpochDay((Integer) val);
} else if (storageType == LocalTime.class && val instanceof Integer) {
- return LocalTime.ofNanoOfDay(Long.valueOf((Integer) val) * 1000 * 1000 /* convert from millis */);
+ return LocalTime.ofNanoOfDay(TimeUnit.MILLISECONDS.toNanos(Long.valueOf((Integer) val)));
} else if (storageType == LocalDateTime.class && (val instanceof Long)) {
- return LocalDateTime.ofEpochSecond((Long) val / 1000, (int) ((Long) val % 1000) * 1000 * 1000, ZoneOffset.UTC);
+ return LocalDateTime.ofEpochSecond(TimeUnit.MILLISECONDS.toSeconds((Long) val),
+ (int) TimeUnit.MILLISECONDS.toNanos((Long) val % 1000), ZoneOffset.UTC);
} else if (storageType == Duration.class && val instanceof Long) {
return Duration.ofMillis((Long) val);
} else if (storageType == Period.class && val instanceof Integer) {