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) {