IGNITE-13669: Support temporal types. (#216)
diff --git a/modules/api/src/main/java/org/apache/ignite/schema/ColumnType.java b/modules/api/src/main/java/org/apache/ignite/schema/ColumnType.java
index 9d69a14..d15e72f 100644
--- a/modules/api/src/main/java/org/apache/ignite/schema/ColumnType.java
+++ b/modules/api/src/main/java/org/apache/ignite/schema/ColumnType.java
@@ -57,10 +57,22 @@
/** 128-bit UUID. */
public static final ColumnType UUID = new ColumnType(ColumnTypeSpec.UUID);
+ /** Timezone-free three-part value representing a year, month, and day. */
+ public static final ColumnType DATE = new ColumnType(ColumnTypeSpec.DATE);
+
+ /** String varlen type of unlimited length. */
+ private static final VarLenColumnType UNLIMITED_STRING = stringOf(0);
+
+ /** Blob varlen type of unlimited length. */
+ private static final VarLenColumnType UNLIMITED_BLOB = blobOf(0);
+
+ /** Number type with unlimited precision. */
+ public static final NumberColumnType UNLIMITED_NUMBER = new NumberColumnType(ColumnTypeSpec.NUMBER, NumberColumnType.UNLIMITED_PRECISION);
+
/**
- * Bitmask type factory method.
+ * Returns bit mask type.
*
- * @param bits Bitmask size in bits.
+ * @param bits Bit mask size in bits.
* @return Bitmap type.
*/
public static VarLenColumnType bitmaskOf(int bits) {
@@ -68,16 +80,16 @@
}
/**
- * String factory method.
+ * Returns string type of unlimited length.
*
* @return String type.
*/
public static VarLenColumnType string() {
- return stringOf(0);
+ return UNLIMITED_STRING;
}
/**
- * String factory method for fix-sized string type.
+ * Return string type of limited size.
*
* @param length String length in chars.
* @return String type.
@@ -87,16 +99,17 @@
}
/**
- * Blob type factory method.
+ * Returns blob type of unlimited length.
*
* @return Blob type.
+ * @see #blobOf(int)
*/
public static VarLenColumnType blobOf() {
- return blobOf(0);
+ return UNLIMITED_BLOB;
}
/**
- * Blob type factory method for fix-sized blob.
+ * Return blob type of limited length.
*
* @param length Blob length in bytes.
* @return Blob type.
@@ -106,11 +119,11 @@
}
/**
- * Number type factory method.
+ * Returns number type with given precision.
*
* @param precision Precision of value.
* @return Number type.
- * @throws IllegalArgumentException If precision is not positive.
+ * @throws IllegalArgumentException If precision value was invalid.
*/
public static NumberColumnType numberOf(int precision) {
if (precision <= 0)
@@ -120,21 +133,22 @@
}
/**
- * Number type factory method.
+ * Returns number type with the default precision.
*
* @return Number type.
+ * @see #numberOf(int)
*/
public static NumberColumnType numberOf() {
- return new NumberColumnType(ColumnTypeSpec.NUMBER, NumberColumnType.UNLIMITED_PRECISION);
+ return UNLIMITED_NUMBER;
}
/**
- * Decimal type factory method.
+ * Returns decimal type with given precision and scale.
*
* @param precision Precision.
* @param scale Scale.
* @return Decimal type.
- * @throws IllegalArgumentException If precision or scale are invalid.
+ * @throws IllegalArgumentException If precision and/or scale values were invalid.
*/
public static DecimalColumnType decimalOf(int precision, int scale) {
if (precision <= 0)
@@ -145,15 +159,16 @@
if (precision < scale)
throw new IllegalArgumentException("Precision [" + precision + "] must be" +
- " not lower than scale [ " + scale + " ].");
+ " not lower than scale [ " + scale + " ].");
return new DecimalColumnType(ColumnTypeSpec.DECIMAL, precision, scale);
}
/**
- * Decimal type factory method with default precision and scale values.
+ * Returns decimal type with default precision and scale values.
*
* @return Decimal type.
+ * @see #decimalOf(int, int)
*/
public static DecimalColumnType decimalOf() {
return new DecimalColumnType(
@@ -164,6 +179,93 @@
}
/**
+ * Returns timezone-free type representing a time of day in hours, minutes, seconds, and fractional seconds
+ * with the default precision of 6 (microseconds).
+ *
+ * @return Native type.
+ * @see TemporalColumnType#DEFAULT_PRECISION
+ * @see #time(int)
+ */
+ public static TemporalColumnType time() {
+ return new TemporalColumnType(ColumnTypeSpec.TIME, TemporalColumnType.DEFAULT_PRECISION);
+ }
+
+ /**
+ * Returns timezone-free type representing a time of day in hours, minutes, seconds, and fractional seconds.
+ * <p>
+ * Precision is a number of digits in fractional seconds part,
+ * from 0 - whole seconds precision up to 9 - nanoseconds precision.
+ *
+ * @param precision The number of digits in fractional seconds part. Accepted values are in range [0-9].
+ * @return Native type.
+ * @throws IllegalArgumentException If precision value was invalid.
+ */
+ public static TemporalColumnType time(int precision) {
+ if (precision < 0 || precision > 9)
+ throw new IllegalArgumentException("Unsupported fractional seconds precision: " + precision);
+
+ return new TemporalColumnType(ColumnTypeSpec.TIME, precision);
+ }
+
+ /**
+ * Returns timezone-free datetime encoded as (date, time) with the default time precision of 6 (microseconds).
+ *
+ * @return Native type.
+ * @see TemporalColumnType#DEFAULT_PRECISION
+ * @see #datetime(int)
+ */
+ public static TemporalColumnType datetime() {
+ return new TemporalColumnType(ColumnTypeSpec.DATETIME, TemporalColumnType.DEFAULT_PRECISION);
+ }
+
+ /**
+ * Returns timezone-free datetime encoded as (date, time).
+ * <p>
+ * Precision is a number of digits in fractional seconds part of time,
+ * from 0 - whole seconds precision up to 9 - nanoseconds precision.
+ *
+ * @param precision The number of digits in fractional seconds part. Accepted values are in range [0-9].
+ * @return Native type.
+ * @throws IllegalArgumentException If precision value was invalid.
+ */
+ public static TemporalColumnType datetime(int precision) {
+ if (precision < 0 || precision > 9)
+ throw new IllegalArgumentException("Unsupported fractional seconds precision: " + precision);
+
+ return new TemporalColumnType(ColumnTypeSpec.DATETIME, precision);
+ }
+
+ /**
+ * Returns point in time as number of ticks since Jan 1, 1970 00:00:00.000 (with no timezone)
+ * with the default precision of 6 (microseconds).
+ *
+ * @return Native type.
+ * @see TemporalColumnType#DEFAULT_PRECISION
+ * @see #timestamp(int)
+ */
+ public static TemporalColumnType timestamp() {
+ return new TemporalColumnType(ColumnTypeSpec.TIMESTAMP, TemporalColumnType.DEFAULT_PRECISION);
+ }
+
+ /**
+ * Returns point in time as number of ticks since Jan 1, 1970 00:00:00.000 (with no timezone).
+ * Ticks that are stored can be precised to second, millisecond, microsecond or nanosecond.
+ * <p>
+ * Precision is a number of digits in fractional seconds part of time,
+ * from 0 - whole seconds precision up to 9 - nanoseconds precision.
+ *
+ * @param precision The number of digits in fractional seconds part. Accepted values are in range [0-9].
+ * @return Native type.
+ * @throws IllegalArgumentException If precision value was invalid.
+ */
+ public static TemporalColumnType timestamp(int precision) {
+ if (precision < 0 || precision > 9)
+ throw new IllegalArgumentException("Unsupported fractional seconds precision: " + precision);
+
+ return new TemporalColumnType(ColumnTypeSpec.TIMESTAMP, precision);
+ }
+
+ /**
* Column type of variable length.
*/
public static class VarLenColumnType extends ColumnType {
@@ -274,8 +376,7 @@
DecimalColumnType type = (DecimalColumnType)o;
- return precision == type.precision &&
- scale == type.scale;
+ return precision == type.precision && scale == type.scale;
}
/** {@inheritDoc} */
@@ -289,7 +390,7 @@
*/
public static class NumberColumnType extends ColumnType {
/** Undefined precision. */
- public static final int UNLIMITED_PRECISION = 0;
+ private static final int UNLIMITED_PRECISION = 0;
/** Max precision of value. If -1, column has no precision restrictions. */
private final int precision;
@@ -309,7 +410,7 @@
/**
* Returns column precision.
*
- * @return Max value precision.
+ * @return Max number of digits.
*/
public int precision() {
return precision;
@@ -319,11 +420,71 @@
@Override public boolean equals(Object o) {
if (this == o)
return true;
+
if (o == null || getClass() != o.getClass())
return false;
+
if (!super.equals(o))
return false;
+
NumberColumnType type = (NumberColumnType)o;
+
+ return precision == type.precision;
+ }
+
+ /** {@inheritDoc} */
+ @Override public int hashCode() {
+ return Objects.hash(super.hashCode(), precision);
+ }
+ }
+
+ /**
+ * Column type of variable length.
+ */
+ public static class TemporalColumnType extends ColumnType {
+ /** Default temporal type precision: microseconds. */
+ public static final int DEFAULT_PRECISION = 6;
+
+ /** Fractional seconds precision. */
+ private final int precision;
+
+ /**
+ * Creates temporal type.
+ *
+ * @param typeSpec Type spec.
+ * @param precision Fractional seconds meaningful digits. Allowed values are 0-9,
+ * where {@code 0} means second precision, {@code 9} means 1-ns precision.
+ */
+ private TemporalColumnType(ColumnTypeSpec typeSpec, int precision) {
+ super(typeSpec);
+
+ assert precision >= 0 && precision < 10;
+
+ this.precision = precision;
+ }
+
+ /**
+ * Return column precision.
+ *
+ * @return Number of fractional seconds meaningful digits.
+ */
+ public int precision() {
+ return precision;
+ }
+
+ /** {@inheritDoc} */
+ @Override public boolean equals(Object o) {
+ if (this == o)
+ return true;
+
+ if (o == null || getClass() != o.getClass())
+ return false;
+
+ if (!super.equals(o))
+ return false;
+
+ TemporalColumnType type = (TemporalColumnType)o;
+
return precision == type.precision;
}
@@ -370,6 +531,18 @@
/** A decimal floating-point number. */
DECIMAL,
+ /** Timezone-free date. */
+ DATE,
+
+ /** Timezone-free time with precision. */
+ TIME,
+
+ /** Timezone-free datetime. */
+ DATETIME,
+
+ /** Number of ticks since Jan 1, 1970 00:00:00.000 (with no timezone). Tick unit depends on precision. */
+ TIMESTAMP,
+
/** 128-bit UUID. */
UUID,
diff --git a/modules/api/src/main/java/org/apache/ignite/table/Tuple.java b/modules/api/src/main/java/org/apache/ignite/table/Tuple.java
index adc38e4..fd0e346 100644
--- a/modules/api/src/main/java/org/apache/ignite/table/Tuple.java
+++ b/modules/api/src/main/java/org/apache/ignite/table/Tuple.java
@@ -17,6 +17,10 @@
package org.apache.ignite.table;
+import java.time.Instant;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
import java.util.BitSet;
import java.util.UUID;
import org.apache.ignite.binary.BinaryObject;
@@ -237,4 +241,68 @@
* @return Column value.
*/
BitSet bitmaskValue(int columnIndex);
+
+ /**
+ * Gets {@code LocalDate} column value.
+ *
+ * @param columnName Column name.
+ * @return Column value.
+ */
+ LocalDate dateValue(String columnName);
+
+ /**
+ * Gets {@code LocalDate} column value.
+ *
+ * @param columnIndex Column index.
+ * @return Column value.
+ */
+ LocalDate dateValue(int columnIndex);
+
+ /**
+ * Gets {@code LocalTime} column value.
+ *
+ * @param columnName Column name.
+ * @return Column value.
+ */
+ LocalTime timeValue(String columnName);
+
+ /**
+ * Gets {@code LocalTime} column value.
+ *
+ * @param columnIndex Column index.
+ * @return Column value.
+ */
+ LocalTime timeValue(int columnIndex);
+
+ /**
+ * Gets {@code LocalDateTime} column value.
+ *
+ * @param columnName Column name.
+ * @return Column value.
+ */
+ LocalDateTime datetimeValue(String columnName);
+
+ /**
+ * Gets {@code LocalDateTime} column value.
+ *
+ * @param columnIndex Column index.
+ * @return Column value.
+ */
+ LocalDateTime datetimeValue(int columnIndex);
+
+ /**
+ * Gets {@code Instant} column value.
+ *
+ * @param columnName Column name.
+ * @return Column value.
+ */
+ Instant timestampValue(String columnName);
+
+ /**
+ * Gets {@code Instant} column value.
+ *
+ * @param columnIndex Column index.
+ * @return Column value.
+ */
+ Instant timestampValue(int columnIndex);
}
diff --git a/modules/client-common/src/main/java/org/apache/ignite/client/proto/ClientDataType.java b/modules/client-common/src/main/java/org/apache/ignite/client/proto/ClientDataType.java
index 45d1280..dbda15f 100644
--- a/modules/client-common/src/main/java/org/apache/ignite/client/proto/ClientDataType.java
+++ b/modules/client-common/src/main/java/org/apache/ignite/client/proto/ClientDataType.java
@@ -53,4 +53,16 @@
/** BitMask. */
public static final int BITMASK = 11;
+
+ /** Date. */
+ public static final int DATE = 12;
+
+ /** Time. */
+ public static final int TIME = 13;
+
+ /** DateTime. */
+ public static final int DATETIME = 14;
+
+ /** Timestamp. */
+ public static final int TIMESTAMP = 15;
}
diff --git a/modules/client-common/src/main/java/org/apache/ignite/client/proto/ClientMessagePacker.java b/modules/client-common/src/main/java/org/apache/ignite/client/proto/ClientMessagePacker.java
index facefa2..8b3254d 100644
--- a/modules/client-common/src/main/java/org/apache/ignite/client/proto/ClientMessagePacker.java
+++ b/modules/client-common/src/main/java/org/apache/ignite/client/proto/ClientMessagePacker.java
@@ -22,6 +22,10 @@
import java.math.BigDecimal;
import java.math.BigInteger;
import java.nio.ByteBuffer;
+import java.time.Instant;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
import java.util.BitSet;
import java.util.UUID;
import io.netty.buffer.ByteBuf;
@@ -348,6 +352,59 @@
}
/**
+ * Writes a date.
+ *
+ * @param val Date value.
+ * @return This instance.
+ * @throws UnsupportedOperationException Not supported.
+ */
+ public ClientMessagePacker packDate(LocalDate val) {
+ assert !closed : "Packer is closed";
+
+ throw new UnsupportedOperationException("TODO: IGNITE-15163");
+ }
+
+ /**
+ * Writes a time.
+ *
+ * @param val Time value.
+ * @return This instance.
+ * @throws UnsupportedOperationException Not supported.
+ */
+ public ClientMessagePacker packTime(LocalTime val) {
+ assert !closed : "Packer is closed";
+
+ throw new UnsupportedOperationException("TODO: IGNITE-15163");
+ }
+
+ /**
+ * Writes a datetime.
+ *
+ * @param val Datetime value.
+ * @return This instance.
+ * @throws UnsupportedOperationException Not supported.
+ */
+ public ClientMessagePacker packDateTime(LocalDateTime val) {
+ packDate(val.toLocalDate());
+ packTime(val.toLocalTime());
+
+ return this;
+ }
+
+ /**
+ * Writes a timestamp.
+ *
+ * @param val Timestamp value.
+ * @return This instance.
+ * @throws UnsupportedOperationException Not supported.
+ */
+ public ClientMessagePacker packTimestamp(Instant val) {
+ assert !closed : "Packer is closed";
+
+ throw new UnsupportedOperationException("TODO: IGNITE-15163");
+ }
+
+ /**
* Packs an object.
*
* @param val Object value.
@@ -384,6 +441,18 @@
if (val instanceof BitSet)
return packBitSet((BitSet) val);
+ if (val instanceof LocalDate)
+ return packDate((LocalDate)val);
+
+ if (val instanceof LocalTime)
+ return packTime((LocalTime)val);
+
+ if (val instanceof LocalDateTime)
+ return packDateTime((LocalDateTime)val);
+
+ if (val instanceof Instant)
+ return packTimestamp((Instant)val);
+
// TODO: Support all basic types IGNITE-15163
throw new UnsupportedOperationException("Unsupported type, can't serialize: " + val.getClass());
}
diff --git a/modules/client-common/src/main/java/org/apache/ignite/client/proto/ClientMessageUnpacker.java b/modules/client-common/src/main/java/org/apache/ignite/client/proto/ClientMessageUnpacker.java
index a058c9c..cdf1e18 100644
--- a/modules/client-common/src/main/java/org/apache/ignite/client/proto/ClientMessageUnpacker.java
+++ b/modules/client-common/src/main/java/org/apache/ignite/client/proto/ClientMessageUnpacker.java
@@ -22,6 +22,10 @@
import java.math.BigDecimal;
import java.math.BigInteger;
import java.nio.ByteBuffer;
+import java.time.Instant;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
import java.util.BitSet;
import java.util.UUID;
import io.netty.buffer.ByteBuf;
@@ -38,6 +42,8 @@
import static org.apache.ignite.client.proto.ClientDataType.BITMASK;
import static org.apache.ignite.client.proto.ClientDataType.BYTES;
+import static org.apache.ignite.client.proto.ClientDataType.DATE;
+import static org.apache.ignite.client.proto.ClientDataType.DATETIME;
import static org.apache.ignite.client.proto.ClientDataType.DECIMAL;
import static org.apache.ignite.client.proto.ClientDataType.DOUBLE;
import static org.apache.ignite.client.proto.ClientDataType.FLOAT;
@@ -46,6 +52,8 @@
import static org.apache.ignite.client.proto.ClientDataType.INT64;
import static org.apache.ignite.client.proto.ClientDataType.INT8;
import static org.apache.ignite.client.proto.ClientDataType.STRING;
+import static org.apache.ignite.client.proto.ClientDataType.TIME;
+import static org.apache.ignite.client.proto.ClientDataType.TIMESTAMP;
/**
* Ignite-specific MsgPack extension based on Netty ByteBuf.
@@ -362,6 +370,54 @@
}
/**
+ * Reads a date.
+ *
+ * @return Date value.
+ * @throws UnsupportedOperationException Not supported yet.
+ */
+ public LocalDate unpackDate() {
+ assert refCnt > 0 : "Unpacker is closed";
+
+ throw new UnsupportedOperationException("TODO: IGNITE-15163");
+ }
+
+ /**
+ * Reads a time.
+ *
+ * @return Time value.
+ * @throws UnsupportedOperationException Not supported yet.
+ */
+ public LocalTime unpackTime() {
+ assert refCnt > 0 : "Unpacker is closed";
+
+ throw new UnsupportedOperationException("TODO: IGNITE-15163");
+ }
+
+ /**
+ * Reads a datetime.
+ *
+ * @return Datetime value.
+ * @throws UnsupportedOperationException Not supported yet.
+ */
+ public LocalDateTime unpackDateTime() {
+ assert refCnt > 0 : "Unpacker is closed";
+
+ throw new UnsupportedOperationException("TODO: IGNITE-15163");
+ }
+
+ /**
+ * Reads a timestamp.
+ *
+ * @return Timestamp value.
+ * @throws UnsupportedOperationException Not supported yet.
+ */
+ public Instant unpackTimestamp() {
+ assert refCnt > 0 : "Unpacker is closed";
+
+ throw new UnsupportedOperationException("TODO: IGNITE-15163");
+ }
+
+ /**
* Unpacks an object based on the specified type.
*
* @param dataType Data type code.
@@ -408,6 +464,18 @@
case BITMASK:
return unpackBitSet();
+
+ case DATE:
+ return unpackDate();
+
+ case TIME:
+ return unpackTime();
+
+ case DATETIME:
+ return unpackDateTime();
+
+ case TIMESTAMP:
+ return unpackTimestamp();
}
throw new IgniteException("Unknown client data type: " + dataType);
diff --git a/modules/client-handler/src/main/java/org/apache/ignite/client/handler/requests/table/ClientTableCommon.java b/modules/client-handler/src/main/java/org/apache/ignite/client/handler/requests/table/ClientTableCommon.java
index 9cc3dc3..3bff8d6 100644
--- a/modules/client-handler/src/main/java/org/apache/ignite/client/handler/requests/table/ClientTableCommon.java
+++ b/modules/client-handler/src/main/java/org/apache/ignite/client/handler/requests/table/ClientTableCommon.java
@@ -18,6 +18,10 @@
package org.apache.ignite.client.handler.requests.table;
import java.math.BigDecimal;
+import java.time.Instant;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
import java.util.ArrayList;
import java.util.BitSet;
import java.util.Collection;
@@ -49,7 +53,7 @@
* @param schemaVer Schema version.
* @param schema Schema.
*/
- public static void writeSchema(ClientMessagePacker packer,int schemaVer, SchemaDescriptor schema) {
+ public static void writeSchema(ClientMessagePacker packer, int schemaVer, SchemaDescriptor schema) {
packer.packInt(schemaVer);
if (schema == null) {
@@ -85,7 +89,7 @@
return;
}
- var schema = ((SchemaAware) tuple).schema();
+ var schema = ((SchemaAware)tuple).schema();
writeTuple(packer, tuple, schema);
}
@@ -99,9 +103,9 @@
* @throws IgniteException on failed serialization.
*/
public static void writeTuple(
- ClientMessagePacker packer,
- Tuple tuple,
- SchemaDescriptor schema
+ ClientMessagePacker packer,
+ Tuple tuple,
+ SchemaDescriptor schema
) {
writeTuple(packer, tuple, schema, false, false);
}
@@ -116,10 +120,10 @@
* @throws IgniteException on failed serialization.
*/
public static void writeTuple(
- ClientMessagePacker packer,
- Tuple tuple,
- SchemaDescriptor schema,
- boolean skipHeader
+ ClientMessagePacker packer,
+ Tuple tuple,
+ SchemaDescriptor schema,
+ boolean skipHeader
) {
writeTuple(packer, tuple, schema, skipHeader, false);
}
@@ -135,11 +139,11 @@
* @throws IgniteException on failed serialization.
*/
public static void writeTuple(
- ClientMessagePacker packer,
- Tuple tuple,
- SchemaDescriptor schema,
- boolean skipHeader,
- boolean keyOnly
+ ClientMessagePacker packer,
+ Tuple tuple,
+ SchemaDescriptor schema,
+ boolean skipHeader,
+ boolean keyOnly
) {
if (tuple == null) {
packer.packNil();
@@ -189,12 +193,13 @@
for (Tuple tuple : tuples) {
if (schema == null) {
- schema = ((SchemaAware) tuple).schema();
+ schema = ((SchemaAware)tuple).schema();
packer.packInt(schema.version());
packer.packInt(tuples.size());
- } else
- assert schema.version() == ((SchemaAware) tuple).schema().version();
+ }
+ else
+ assert schema.version() == ((SchemaAware)tuple).schema().version();
writeTuple(packer, tuple, schema, true, keyOnly);
}
@@ -257,10 +262,10 @@
* @return Tuple.
*/
public static Tuple readTuple(
- ClientMessageUnpacker unpacker,
- TableImpl table,
- boolean keyOnly,
- SchemaDescriptor schema
+ ClientMessageUnpacker unpacker,
+ TableImpl table,
+ boolean keyOnly,
+ SchemaDescriptor schema
) {
var builder = table.tupleBuilder();
@@ -367,6 +372,18 @@
case BITMASK:
return ClientDataType.BITMASK;
+
+ case DATE:
+ return ClientDataType.DATE;
+
+ case TIME:
+ return ClientDataType.TIME;
+
+ case DATETIME:
+ return ClientDataType.DATETIME;
+
+ case TIMESTAMP:
+ return ClientDataType.TIMESTAMP;
}
throw new IgniteException("Unsupported native type: " + spec);
@@ -382,43 +399,43 @@
switch (col.type().spec()) {
case INT8:
- packer.packByte((byte) val);
+ packer.packByte((byte)val);
break;
case INT16:
- packer.packShort((short) val);
+ packer.packShort((short)val);
break;
case INT32:
- packer.packInt((int) val);
+ packer.packInt((int)val);
break;
case INT64:
- packer.packLong((long) val);
+ packer.packLong((long)val);
break;
case FLOAT:
- packer.packFloat((float) val);
+ packer.packFloat((float)val);
break;
case DOUBLE:
- packer.packDouble((double) val);
+ packer.packDouble((double)val);
break;
case DECIMAL:
- packer.packDecimal((BigDecimal) val);
+ packer.packDecimal((BigDecimal)val);
break;
case UUID:
- packer.packUuid((UUID) val);
+ packer.packUuid((UUID)val);
break;
case STRING:
- packer.packString((String) val);
+ packer.packString((String)val);
break;
case BYTES:
- byte[] bytes = (byte[]) val;
+ byte[] bytes = (byte[])val;
packer.packBinaryHeader(bytes.length);
packer.writePayload(bytes);
break;
@@ -427,6 +444,22 @@
packer.packBitSet((BitSet)val);
break;
+ case DATE:
+ packer.packDate((LocalDate)val);
+ break;
+
+ case TIME:
+ packer.packTime((LocalTime)val);
+ break;
+
+ case DATETIME:
+ packer.packDateTime((LocalDateTime)val);
+ break;
+
+ case TIMESTAMP:
+ packer.packTimestamp((Instant)val);
+ break;
+
default:
throw new IgniteException("Data type not supported: " + col.type());
}
diff --git a/modules/client/src/main/java/org/apache/ignite/internal/client/table/ClientTupleBuilder.java b/modules/client/src/main/java/org/apache/ignite/internal/client/table/ClientTupleBuilder.java
index 62a49d6..97a97f9 100644
--- a/modules/client/src/main/java/org/apache/ignite/internal/client/table/ClientTupleBuilder.java
+++ b/modules/client/src/main/java/org/apache/ignite/internal/client/table/ClientTupleBuilder.java
@@ -17,6 +17,10 @@
package org.apache.ignite.internal.client.table;
+import java.time.Instant;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
import java.util.BitSet;
import java.util.Iterator;
import java.util.UUID;
@@ -212,6 +216,46 @@
}
/** {@inheritDoc} */
+ @Override public LocalDate dateValue(String columnName) {
+ return value(columnName);
+ }
+
+ /** {@inheritDoc} */
+ @Override public LocalDate dateValue(int columnIndex) {
+ return value(columnIndex);
+ }
+
+ /** {@inheritDoc} */
+ @Override public LocalTime timeValue(String columnName) {
+ return value(columnName);
+ }
+
+ /** {@inheritDoc} */
+ @Override public LocalTime timeValue(int columnIndex) {
+ return value(columnIndex);
+ }
+
+ /** {@inheritDoc} */
+ @Override public LocalDateTime datetimeValue(String columnName) {
+ return value(columnName);
+ }
+
+ /** {@inheritDoc} */
+ @Override public LocalDateTime datetimeValue(int columnIndex) {
+ return value(columnIndex);
+ }
+
+ /** {@inheritDoc} */
+ @Override public Instant timestampValue(String columnName) {
+ return value(columnName);
+ }
+
+ /** {@inheritDoc} */
+ @Override public Instant timestampValue(int columnIndex) {
+ return value(columnIndex);
+ }
+
+ /** {@inheritDoc} */
@NotNull @Override public Iterator<Object> iterator() {
return new Iterator<>() {
/** Current column index. */
diff --git a/modules/client/src/test/java/org/apache/ignite/client/ClientTupleBuilderTest.java b/modules/client/src/test/java/org/apache/ignite/client/ClientTupleBuilderTest.java
index 592988f..dbde506 100644
--- a/modules/client/src/test/java/org/apache/ignite/client/ClientTupleBuilderTest.java
+++ b/modules/client/src/test/java/org/apache/ignite/client/ClientTupleBuilderTest.java
@@ -17,9 +17,13 @@
package org.apache.ignite.client;
+import java.time.Instant;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
+import java.time.Month;
import java.util.BitSet;
import java.util.UUID;
-
import org.apache.ignite.client.proto.ClientDataType;
import org.apache.ignite.internal.client.table.ClientColumn;
import org.apache.ignite.internal.client.table.ClientSchema;
@@ -138,10 +142,19 @@
new ClientColumn("uuid", ClientDataType.UUID, false, false, 6),
new ClientColumn("str", ClientDataType.STRING, false, false, 7),
new ClientColumn("bits", ClientDataType.BITMASK, false, false, 8),
+ new ClientColumn("time", ClientDataType.TIME, false, false, 9),
+ new ClientColumn("date", ClientDataType.DATE, false, false, 10),
+ new ClientColumn("datetime", ClientDataType.DATETIME, false, false, 11),
+ new ClientColumn("timestamp", ClientDataType.TIMESTAMP, false, false, 12)
});
var uuid = UUID.randomUUID();
+ var date = LocalDate.of(1995, Month.MAY, 23);
+ var time = LocalTime.of(17, 0, 1, 222_333_444);
+ var datetime = LocalDateTime.of(1995, Month.MAY, 23, 17, 0, 1, 222_333_444);
+ var timestamp = Instant.now();
+
var builder = new ClientTupleBuilder(schema)
.set("i8", (byte)1)
.set("i16", (short)2)
@@ -151,7 +164,11 @@
.set("double", (double)6.6)
.set("uuid", uuid)
.set("str", "8")
- .set("bits", new BitSet(3));
+ .set("bits", new BitSet(3))
+ .set("date", date)
+ .set("time", time)
+ .set("datetime", datetime)
+ .set("timestamp", timestamp);
var tuple = builder.build();
@@ -181,6 +198,11 @@
assertEquals(0, tuple.bitmaskValue(8).length());
assertEquals(0, tuple.bitmaskValue("bits").length());
+
+ assertEquals(date, tuple.dateValue("date"));
+ assertEquals(time, tuple.timeValue("time"));
+ assertEquals(datetime, tuple.datetimeValue("datetime"));
+ assertEquals(timestamp, tuple.timestampValue("timestamp"));
}
private static ClientTupleBuilder getBuilder() {
diff --git a/modules/client/src/test/java/org/apache/ignite/client/CustomTuple.java b/modules/client/src/test/java/org/apache/ignite/client/CustomTuple.java
index b48a1da..81fa756 100644
--- a/modules/client/src/test/java/org/apache/ignite/client/CustomTuple.java
+++ b/modules/client/src/test/java/org/apache/ignite/client/CustomTuple.java
@@ -17,6 +17,10 @@
package org.apache.ignite.client;
+import java.time.Instant;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
import java.util.BitSet;
import java.util.Iterator;
import java.util.UUID;
@@ -51,8 +55,10 @@
@Override public String columnName(int columnIndex) {
switch (columnIndex) {
- case 0: return "id";
- case 1: return "name";
+ case 0:
+ return "id";
+ case 1:
+ return "name";
}
return null;
@@ -60,8 +66,10 @@
@Override public Integer columnIndex(String columnName) {
switch (columnName) {
- case "id": return 0;
- case "name": return 1;
+ case "id":
+ return 0;
+ case "name":
+ return 1;
}
return null;
@@ -69,8 +77,10 @@
@Override public <T> T valueOrDefault(String columnName, T def) {
switch (columnName) {
- case "id": return (T) id;
- case "name": return (T) name;
+ case "id":
+ return (T)id;
+ case "name":
+ return (T)name;
}
return def;
@@ -82,8 +92,10 @@
@Override public <T> T value(int columnIndex) {
switch (columnIndex) {
- case 0: return (T) id;
- case 1: return (T) name;
+ case 0:
+ return (T)id;
+ case 1:
+ return (T)name;
}
return null;
@@ -169,6 +181,38 @@
throw new UnsupportedOperationException();
}
+ @Override public LocalDate dateValue(String columnName) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override public LocalDate dateValue(int columnIndex) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override public LocalTime timeValue(String columnName) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override public LocalTime timeValue(int columnIndex) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override public LocalDateTime datetimeValue(String columnName) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override public LocalDateTime datetimeValue(int columnIndex) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override public Instant timestampValue(String columnName) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override public Instant timestampValue(int columnIndex) {
+ throw new UnsupportedOperationException();
+ }
+
@NotNull @Override public Iterator<Object> iterator() {
throw new UnsupportedOperationException();
}
diff --git a/modules/schema/src/main/java/org/apache/ignite/internal/schema/NativeTypeSpec.java b/modules/schema/src/main/java/org/apache/ignite/internal/schema/NativeTypeSpec.java
index 8624633..be3da0f 100644
--- a/modules/schema/src/main/java/org/apache/ignite/internal/schema/NativeTypeSpec.java
+++ b/modules/schema/src/main/java/org/apache/ignite/internal/schema/NativeTypeSpec.java
@@ -19,6 +19,10 @@
import java.math.BigDecimal;
import java.math.BigInteger;
+import java.time.Instant;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
import java.util.BitSet;
import org.apache.ignite.internal.schema.row.Row;
import org.apache.ignite.internal.tostring.S;
@@ -150,6 +154,46 @@
@Override public Object objectValue(Row tup, int colIdx) {
return tup.numberValue(colIdx);
}
+ },
+
+ /**
+ * Native type representing a timezone-free date.
+ */
+ DATE("date", true) {
+ /** {@inheritDoc} */
+ @Override public Object objectValue(Row tup, int colIdx) {
+ return tup.dateValue(colIdx);
+ }
+ },
+
+ /**
+ * Native type representing a timezone-free time.
+ */
+ TIME("time", true) {
+ /** {@inheritDoc} */
+ @Override public Object objectValue(Row tup, int colIdx) {
+ return tup.timeValue(colIdx);
+ }
+ },
+
+ /**
+ * Native type representing a timezone-free datetime.
+ */
+ DATETIME("datetime", true) {
+ /** {@inheritDoc} */
+ @Override public Object objectValue(Row tup, int colIdx) {
+ return tup.dateTimeValue(colIdx);
+ }
+ },
+
+ /**
+ * Native type representing a timestamp in milliseconds since Jan 1, 1970 00:00:00.000 (with no timezone).
+ */
+ TIMESTAMP("timestamp", true) {
+ /** {@inheritDoc} */
+ @Override public Object objectValue(Row tup, int colIdx) {
+ return tup.timestampValue(colIdx);
+ }
};
/** Flag indicating whether this type specifies a fixed-length type. */
@@ -234,7 +278,17 @@
else if (cls == Double.class)
return NativeTypeSpec.DOUBLE;
- // Other types
+ // Temporal types.
+ else if (cls == LocalDate.class)
+ return NativeTypeSpec.DATE;
+ else if (cls == LocalTime.class)
+ return NativeTypeSpec.TIME;
+ else if (cls == LocalDateTime.class)
+ return NativeTypeSpec.DATETIME;
+ else if (cls == Instant.class)
+ return NativeTypeSpec.TIMESTAMP;
+
+ // Other types.
else if (cls == byte[].class)
return NativeTypeSpec.BYTES;
else if (cls == String.class)
diff --git a/modules/schema/src/main/java/org/apache/ignite/internal/schema/NativeTypes.java b/modules/schema/src/main/java/org/apache/ignite/internal/schema/NativeTypes.java
index 00fb486..508be27 100644
--- a/modules/schema/src/main/java/org/apache/ignite/internal/schema/NativeTypes.java
+++ b/modules/schema/src/main/java/org/apache/ignite/internal/schema/NativeTypes.java
@@ -53,6 +53,9 @@
/** */
public static final NativeType BYTES = new VarlenNativeType(NativeTypeSpec.BYTES, Integer.MAX_VALUE);
+ /** Timezone-free three-part value representing a year, month, and day. */
+ public static final NativeType DATE = new NativeType(NativeTypeSpec.DATE, 3);
+
/** Don't allow to create an instance. */
private NativeTypes() {
}
@@ -109,6 +112,66 @@
}
/**
+ * Creates a TIME type with given precision.
+ *
+ * @param precision Fractional seconds meaningful digits. Allowed values are 0-9 for second to nanosecond precision.
+ * @return Native type.
+ */
+ public static NativeType time(int precision) {
+ return TemporalNativeType.time(precision);
+ }
+
+ /**
+ * Creates DATETIME type as pair (date, time).
+ *
+ * @param precision Fractional seconds meaningful digits. Allowed values are 0-9 for second to nanosecond precision.
+ * @return Native type.
+ */
+ public static NativeType datetime(int precision) {
+ return TemporalNativeType.datetime(precision);
+ }
+
+ /**
+ * Creates TIMESTAMP type.
+ *
+ * @param precision Fractional seconds meaningful digits. Allowed values are 0-9 for second to nanosecond precision.
+ * @return Native type.
+ */
+ public static NativeType timestamp(int precision) {
+ return TemporalNativeType.timestamp(precision);
+ }
+
+ /**
+ * Creates a TIME type with default precision.
+ *
+ * @return Native type.
+ * @see #time(int)
+ */
+ public static NativeType time() {
+ return TemporalNativeType.time(ColumnType.TemporalColumnType.DEFAULT_PRECISION);
+ }
+
+ /**
+ * Creates DATETIME type with default precision.
+ *
+ * @return Native type.
+ * @see #datetime(int)
+ */
+ public static NativeType datetime() {
+ return TemporalNativeType.datetime(ColumnType.TemporalColumnType.DEFAULT_PRECISION);
+ }
+
+ /**
+ * Creates TIMESTAMP type with default precision.
+ *
+ * @return Native type.
+ * @see #timestamp(int)
+ */
+ public static NativeType timestamp() {
+ return TemporalNativeType.timestamp(ColumnType.TemporalColumnType.DEFAULT_PRECISION);
+ }
+
+ /**
* Return the native type for specified object.
*
* @param val Object to map to native type.
@@ -142,6 +205,18 @@
case UUID:
return UUID;
+ case DATE:
+ return DATE;
+
+ case TIME:
+ return time();
+
+ case DATETIME:
+ return datetime();
+
+ case TIMESTAMP:
+ return timestamp();
+
case STRING:
return stringOf(((CharSequence)val).length());
@@ -204,6 +279,18 @@
case UUID:
return UUID;
+ case DATE:
+ return DATE;
+
+ case TIME:
+ return time(((ColumnType.TemporalColumnType)type).precision());
+
+ case DATETIME:
+ return datetime(((ColumnType.TemporalColumnType)type).precision());
+
+ case TIMESTAMP:
+ return timestamp(((ColumnType.TemporalColumnType)type).precision());
+
case BITMASK:
return new BitmaskNativeType(((ColumnType.VarLenColumnType)type).length());
diff --git a/modules/schema/src/main/java/org/apache/ignite/internal/schema/TemporalNativeType.java b/modules/schema/src/main/java/org/apache/ignite/internal/schema/TemporalNativeType.java
new file mode 100644
index 0000000..ec29856
--- /dev/null
+++ b/modules/schema/src/main/java/org/apache/ignite/internal/schema/TemporalNativeType.java
@@ -0,0 +1,98 @@
+/*
+ * 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
+ *
+ * http://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.ignite.internal.schema;
+
+import org.apache.ignite.internal.tostring.S;
+
+/**
+ * Temporal native type.
+ */
+public class TemporalNativeType extends NativeType {
+ /**
+ * Creates TIME type.
+ *
+ * @param precision Fractional seconds precision.
+ * @return Native type.
+ */
+ static TemporalNativeType time(int precision) {
+ int size = (precision > 3) ? 6 : 4;
+
+ return new TemporalNativeType(NativeTypeSpec.TIME, size, precision);
+ }
+
+ /**
+ * Creates DATETIME type.
+ *
+ * @param precision Fractional seconds precision.
+ * @return Native type.
+ */
+ static TemporalNativeType datetime(int precision) {
+ int size = NativeTypes.DATE.sizeInBytes() + ((precision > 3) ? 6 : 4);
+
+ return new TemporalNativeType(NativeTypeSpec.DATETIME, size, precision);
+ }
+
+ /**
+ * Creates TIMESTAMP type.
+ *
+ * @param precision Fractional seconds precision.
+ * @return Native type.
+ */
+ static TemporalNativeType timestamp(int precision) {
+ int size = (precision == 0) ? 8 : 12;
+
+ return new TemporalNativeType(NativeTypeSpec.TIMESTAMP, size, precision);
+ }
+
+ /** Fractional seconds precision. */
+ private final int precision;
+
+ /**
+ * Creates temporal type.
+ *
+ * @param typeSpec Type spec.
+ * @param precision Fractional seconds precision.
+ */
+ private TemporalNativeType(NativeTypeSpec typeSpec, int size, int precision) {
+ super(typeSpec, size);
+
+ if (precision < 0 || precision > 9)
+ throw new IllegalArgumentException("Unsupported fractional seconds precision: " + precision);
+
+ this.precision = precision;
+ }
+
+ /**
+ * Return fractional seconds precision.
+ *
+ * @return Precicion;
+ */
+ public int precision() {
+ return precision;
+ }
+
+ /** {@inheritDoc} */
+ @Override public boolean mismatch(NativeType type) {
+ return super.mismatch(type) || precision < ((TemporalNativeType)type).precision;
+ }
+
+ /** {@inheritDoc} */
+ @Override public String toString() {
+ return S.toString(TemporalNativeType.class.getSimpleName(), "name", spec(), "precision", precision);
+ }
+}
diff --git a/modules/schema/src/main/java/org/apache/ignite/internal/schema/configuration/SchemaConfigurationConverter.java b/modules/schema/src/main/java/org/apache/ignite/internal/schema/configuration/SchemaConfigurationConverter.java
index 0927c25..9b0c5ca 100644
--- a/modules/schema/src/main/java/org/apache/ignite/internal/schema/configuration/SchemaConfigurationConverter.java
+++ b/modules/schema/src/main/java/org/apache/ignite/internal/schema/configuration/SchemaConfigurationConverter.java
@@ -19,6 +19,10 @@
import java.math.BigDecimal;
import java.math.BigInteger;
+import java.time.Instant;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
@@ -90,15 +94,19 @@
putType(ColumnType.FLOAT);
putType(ColumnType.DOUBLE);
putType(ColumnType.UUID);
+ putType(ColumnType.DATE);
}
- /** */
+ /**
+ * @param type Column type.
+ */
private static void putType(ColumnType type) {
types.put(type.typeSpec().name(), type);
}
/**
* Convert SortedIndexColumn to IndexColumnChange.
+ *
* @param col IndexColumnChange.
* @param colInit IndexColumnChange to fulfill.
* @return IndexColumnChange to get result from.
@@ -294,6 +302,15 @@
break;
+ case "TIME":
+ case "DATETIME":
+ case "TIMESTAMP":
+ ColumnType.TemporalColumnType temporalColType = (ColumnType.TemporalColumnType)colType;
+
+ colTypeChg.changePrecision(temporalColType.precision());
+
+ break;
+
default:
throw new IllegalArgumentException("Unknown type " + colType.typeSpec().name());
}
@@ -340,6 +357,15 @@
case "NUMBER":
return ColumnType.numberOf(colTypeView.precision());
+ case "TIME":
+ return ColumnType.time(colTypeView.precision());
+
+ case "DATETIME":
+ return ColumnType.datetime(colTypeView.precision());
+
+ case "TIMESTAMP":
+ return ColumnType.timestamp(colTypeView.precision());
+
default:
throw new IllegalArgumentException("Unknown type " + typeName);
}
@@ -455,7 +481,7 @@
LinkedHashMap<String, Column> colsMap = new LinkedHashMap<>(colsView.size());
- columns.forEach((i,v) -> colsMap.put(v.name(), v));
+ columns.forEach((i, v) -> colsMap.put(v.name(), v));
return new SchemaTableImpl(schemaName, tableName, colsMap, indices);
}
@@ -549,7 +575,7 @@
else if (cls == double.class)
return ColumnType.DOUBLE;
- // Boxed primitives.
+ // Boxed primitives.
else if (cls == Byte.class)
return ColumnType.INT8;
else if (cls == Short.class)
@@ -563,7 +589,17 @@
else if (cls == Double.class)
return ColumnType.DOUBLE;
- // Other types
+ // Temporal types.
+ else if (cls == LocalDate.class)
+ return ColumnType.DATE;
+ else if (cls == LocalTime.class)
+ return ColumnType.time(ColumnType.TemporalColumnType.DEFAULT_PRECISION);
+ else if (cls == LocalDateTime.class)
+ return ColumnType.datetime(ColumnType.TemporalColumnType.DEFAULT_PRECISION);
+ else if (cls == Instant.class)
+ return ColumnType.timestamp(ColumnType.TemporalColumnType.DEFAULT_PRECISION);
+
+ // Other types
else if (cls == String.class)
return ColumnType.string();
else if (cls == UUID.class)
diff --git a/modules/schema/src/main/java/org/apache/ignite/internal/schema/configuration/SchemaDescriptorConverter.java b/modules/schema/src/main/java/org/apache/ignite/internal/schema/configuration/SchemaDescriptorConverter.java
index 8287a0d..059bb7c 100644
--- a/modules/schema/src/main/java/org/apache/ignite/internal/schema/configuration/SchemaDescriptorConverter.java
+++ b/modules/schema/src/main/java/org/apache/ignite/internal/schema/configuration/SchemaDescriptorConverter.java
@@ -21,6 +21,10 @@
import java.math.BigDecimal;
import java.math.BigInteger;
import java.math.RoundingMode;
+import java.time.Instant;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
@@ -95,21 +99,39 @@
case BITMASK:
return NativeTypes.bitmaskOf(((ColumnType.VarLenColumnType)colType).length());
- case STRING:
+ case STRING: {
int strLen = ((ColumnType.VarLenColumnType)colType).length();
if (strLen == 0)
strLen = Integer.MAX_VALUE;
return NativeTypes.stringOf(strLen);
-
- case BLOB:
+ }
+ case BLOB: {
int blobLen = ((ColumnType.VarLenColumnType)colType).length();
if (blobLen == 0)
blobLen = Integer.MAX_VALUE;
return NativeTypes.blobOf(blobLen);
+ }
+ case DATE:
+ return NativeTypes.DATE;
+ case TIME: {
+ ColumnType.TemporalColumnType temporalType = (ColumnType.TemporalColumnType)colType;
+
+ return NativeTypes.time(temporalType.precision());
+ }
+ case DATETIME: {
+ ColumnType.TemporalColumnType temporalType = (ColumnType.TemporalColumnType)colType;
+
+ return NativeTypes.datetime(temporalType.precision());
+ }
+ case TIMESTAMP: {
+ ColumnType.TemporalColumnType temporalType = (ColumnType.TemporalColumnType)colType;
+
+ return NativeTypes.timestamp(temporalType.precision());
+ }
case NUMBER: {
ColumnType.NumberColumnType numberType = (ColumnType.NumberColumnType)colType;
@@ -161,12 +183,20 @@
return Double.parseDouble(dflt);
case DECIMAL:
return new BigDecimal(dflt).setScale(((DecimalNativeType)type).scale(), RoundingMode.HALF_UP);
+ case NUMBER:
+ return new BigInteger(dflt);
case STRING:
return dflt;
case UUID:
return java.util.UUID.fromString(dflt);
- case NUMBER:
- return new BigInteger(dflt);
+ case DATE:
+ return LocalDate.parse(dflt);
+ case TIME:
+ return LocalTime.parse(dflt);
+ case DATETIME:
+ return LocalDateTime.parse(dflt);
+ case TIMESTAMP:
+ return Instant.parse(dflt);
default:
throw new SchemaException("Default value is not supported for type: type=" + type.toString());
}
diff --git a/modules/schema/src/main/java/org/apache/ignite/internal/schema/registry/UpgradingRowAdapter.java b/modules/schema/src/main/java/org/apache/ignite/internal/schema/registry/UpgradingRowAdapter.java
index eccddcb..2c6b443 100644
--- a/modules/schema/src/main/java/org/apache/ignite/internal/schema/registry/UpgradingRowAdapter.java
+++ b/modules/schema/src/main/java/org/apache/ignite/internal/schema/registry/UpgradingRowAdapter.java
@@ -18,6 +18,7 @@
package org.apache.ignite.internal.schema.registry;
import java.math.BigDecimal;
+import java.math.BigInteger;
import java.util.BitSet;
import java.util.UUID;
import org.apache.ignite.internal.schema.BinaryRow;
@@ -72,13 +73,7 @@
return mapper.map(colIdx);
}
- /**
- * Reads value for specified column.
- *
- * @param colIdx Column index.
- * @return Column value.
- * @throws InvalidTypeException If actual column type does not match the requested column type.
- */
+ /** {@inheritDoc} */
@Override public byte byteValue(int colIdx) throws InvalidTypeException {
int mappedId = mapColumn(colIdx);
@@ -90,13 +85,7 @@
return mappedId < 0 ? (byte)column.defaultValue() : super.byteValue(mappedId);
}
- /**
- * Reads value for specified column.
- *
- * @param colIdx Column index.
- * @return Column value.
- * @throws InvalidTypeException If actual column type does not match the requested column type.
- */
+ /** {@inheritDoc} */
@Override public Byte byteValueBoxed(int colIdx) throws InvalidTypeException {
int mappedId = mapColumn(colIdx);
@@ -108,13 +97,7 @@
return mappedId < 0 ? (Byte)column.defaultValue() : super.byteValueBoxed(mappedId);
}
- /**
- * Reads value for specified column.
- *
- * @param colIdx Column index.
- * @return Column value.
- * @throws InvalidTypeException If actual column type does not match the requested column type.
- */
+ /** {@inheritDoc} */
@Override public short shortValue(int colIdx) throws InvalidTypeException {
int mappedId = mapColumn(colIdx);
@@ -126,13 +109,7 @@
return mappedId < 0 ? (short)column.defaultValue() : super.shortValue(mappedId);
}
- /**
- * Reads value for specified column.
- *
- * @param colIdx Column index.
- * @return Column value.
- * @throws InvalidTypeException If actual column type does not match the requested column type.
- */
+ /** {@inheritDoc} */
@Override public Short shortValueBoxed(int colIdx) throws InvalidTypeException {
int mappedId = mapColumn(colIdx);
@@ -144,13 +121,7 @@
return mappedId < 0 ? (Short)column.defaultValue() : super.shortValueBoxed(mappedId);
}
- /**
- * Reads value for specified column.
- *
- * @param colIdx Column index.
- * @return Column value.
- * @throws InvalidTypeException If actual column type does not match the requested column type.
- */
+ /** {@inheritDoc} */
@Override public int intValue(int colIdx) throws InvalidTypeException {
int mappedId = mapColumn(colIdx);
@@ -162,13 +133,7 @@
return mappedId < 0 ? (int)column.defaultValue() : super.intValue(mappedId);
}
- /**
- * Reads value for specified column.
- *
- * @param colIdx Column index.
- * @return Column value.
- * @throws InvalidTypeException If actual column type does not match the requested column type.
- */
+ /** {@inheritDoc} */
@Override public Integer intValueBoxed(int colIdx) throws InvalidTypeException {
int mappedId = mapColumn(colIdx);
@@ -180,13 +145,7 @@
return mappedId < 0 ? (Integer)column.defaultValue() : super.intValueBoxed(mappedId);
}
- /**
- * Reads value for specified column.
- *
- * @param colIdx Column index.
- * @return Column value.
- * @throws InvalidTypeException If actual column type does not match the requested column type.
- */
+ /** {@inheritDoc} */
@Override public long longValue(int colIdx) throws InvalidTypeException {
int mappedId = mapColumn(colIdx);
@@ -198,13 +157,7 @@
return mappedId < 0 ? (long)column.defaultValue() : super.longValue(mappedId);
}
- /**
- * Reads value for specified column.
- *
- * @param colIdx Column index.
- * @return Column value.
- * @throws InvalidTypeException If actual column type does not match the requested column type.
- */
+ /** {@inheritDoc} */
@Override public Long longValueBoxed(int colIdx) throws InvalidTypeException {
int mappedId = mapColumn(colIdx);
@@ -216,13 +169,7 @@
return mappedId < 0 ? (Long)column.defaultValue() : super.longValueBoxed(mappedId);
}
- /**
- * Reads value for specified column.
- *
- * @param colIdx Column index.
- * @return Column value.
- * @throws InvalidTypeException If actual column type does not match the requested column type.
- */
+ /** {@inheritDoc} */
@Override public float floatValue(int colIdx) throws InvalidTypeException {
int mappedId = mapColumn(colIdx);
@@ -234,13 +181,7 @@
return mappedId < 0 ? (float)column.defaultValue() : super.floatValue(mappedId);
}
- /**
- * Reads value for specified column.
- *
- * @param colIdx Column index.
- * @return Column value.
- * @throws InvalidTypeException If actual column type does not match the requested column type.
- */
+ /** {@inheritDoc} */
@Override public Float floatValueBoxed(int colIdx) throws InvalidTypeException {
int mappedId = mapColumn(colIdx);
@@ -252,13 +193,7 @@
return mappedId < 0 ? (Float)column.defaultValue() : super.floatValueBoxed(mappedId);
}
- /**
- * Reads value for specified column.
- *
- * @param colIdx Column index.
- * @return Column value.
- * @throws InvalidTypeException If actual column type does not match the requested column type.
- */
+ /** {@inheritDoc} */
@Override public double doubleValue(int colIdx) throws InvalidTypeException {
int mappedId = mapColumn(colIdx);
@@ -270,13 +205,8 @@
return mappedId < 0 ? (double)column.defaultValue() : super.doubleValue(mappedId);
}
- /**
- * Reads value for specified column.
- *
- * @param colIdx Column index.
- * @return Column value.
- * @throws InvalidTypeException If actual column type does not match the requested column type.
- */
+
+ /** {@inheritDoc} */
@Override public Double doubleValueBoxed(int colIdx) throws InvalidTypeException {
int mappedId = mapColumn(colIdx);
@@ -288,25 +218,31 @@
return mappedId < 0 ? (Double)column.defaultValue() : super.doubleValueBoxed(mappedId);
}
- /**
- * Reads value from specified column.
- *
- * @param colIdx Column index.
- * @return Column value.
- * @throws InvalidTypeException If actual column type does not match the requested column type.
- */
+ /** {@inheritDoc} */
@Override public BigDecimal decimalValue(int colIdx) throws InvalidTypeException {
- // TODO: IGNITE-13668 decimal support
- return null;
+ int mappedId = mapColumn(colIdx);
+
+ Column column = mappedId < 0 ? mapper.mappedColumn(colIdx) : super.rowSchema().column(mappedId);
+
+ if (NativeTypeSpec.DECIMAL != column.type().spec())
+ throw new SchemaException("Type conversion is not supported yet.");
+
+ return mappedId < 0 ? (BigDecimal)column.defaultValue() : super.decimalValue(mappedId);
}
- /**
- * Reads value for specified column.
- *
- * @param colIdx Column index.
- * @return Column value.
- * @throws InvalidTypeException If actual column type does not match the requested column type.
- */
+ /** {@inheritDoc} */
+ @Override public BigInteger numberValue(int colIdx) throws InvalidTypeException {
+ int mappedId = mapColumn(colIdx);
+
+ Column column = mappedId < 0 ? mapper.mappedColumn(colIdx) : super.rowSchema().column(mappedId);
+
+ if (NativeTypeSpec.NUMBER != column.type().spec())
+ throw new SchemaException("Type conversion is not supported yet.");
+
+ return mappedId < 0 ? (BigInteger)column.defaultValue() : super.numberValue(mappedId);
+ }
+
+ /** {@inheritDoc} */
@Override public String stringValue(int colIdx) throws InvalidTypeException {
int mappedId = mapColumn(colIdx);
@@ -318,13 +254,7 @@
return mappedId < 0 ? (String)column.defaultValue() : super.stringValue(mappedId);
}
- /**
- * Reads value for specified column.
- *
- * @param colIdx Column index.
- * @return Column value.
- * @throws InvalidTypeException If actual column type does not match the requested column type.
- */
+ /** {@inheritDoc} */
@Override public byte[] bytesValue(int colIdx) throws InvalidTypeException {
int mappedId = mapColumn(colIdx);
@@ -336,13 +266,7 @@
return mappedId < 0 ? (byte[])column.defaultValue() : super.bytesValue(mappedId);
}
- /**
- * Reads value for specified column.
- *
- * @param colIdx Column index.
- * @return Column value.
- * @throws InvalidTypeException If actual column type does not match the requested column type.
- */
+ /** {@inheritDoc} */
@Override public UUID uuidValue(int colIdx) throws InvalidTypeException {
int mappedId = mapColumn(colIdx);
@@ -354,13 +278,8 @@
return mappedId < 0 ? (UUID)column.defaultValue() : super.uuidValue(mappedId);
}
- /**
- * Reads value for specified column.
- *
- * @param colIdx Column index.
- * @return Column value.
- * @throws InvalidTypeException If actual column type does not match the requested column type.
- */
+
+ /** {@inheritDoc} */
@Override public BitSet bitmaskValue(int colIdx) throws InvalidTypeException {
int mappedId = mapColumn(colIdx);
diff --git a/modules/schema/src/main/java/org/apache/ignite/internal/schema/row/Row.java b/modules/schema/src/main/java/org/apache/ignite/internal/schema/row/Row.java
index 3cb1af4..c254fdc 100644
--- a/modules/schema/src/main/java/org/apache/ignite/internal/schema/row/Row.java
+++ b/modules/schema/src/main/java/org/apache/ignite/internal/schema/row/Row.java
@@ -22,6 +22,10 @@
import java.math.BigDecimal;
import java.math.BigInteger;
import java.nio.ByteBuffer;
+import java.time.Instant;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
import java.util.BitSet;
import java.util.UUID;
import org.apache.ignite.internal.schema.BinaryRow;
@@ -31,13 +35,19 @@
import org.apache.ignite.internal.schema.InvalidTypeException;
import org.apache.ignite.internal.schema.NativeTypeSpec;
import org.apache.ignite.internal.schema.SchemaDescriptor;
+import org.apache.ignite.internal.schema.TemporalNativeType;
/**
* Schema-aware row.
* <p>
* The class contains non-generic methods to read boxed and unboxed primitives based on the schema column types.
* Any type conversions and coercions should be implemented outside the row by the key-value or query runtime.
+ * <p>
* When a non-boxed primitive is read from a null column value, it is converted to the primitive type default value.
+ * <p>
+ * Natively supported temporal types are decoded automatically after read.
+ *
+ * @see TemporalTypesHelper
*/
public class Row implements BinaryRow {
/** Schema descriptor. */
@@ -238,7 +248,7 @@
long offLen = findColumn(col, NativeTypeSpec.DECIMAL);
if (offLen < 0)
- return offLen == -1 ? null : (BigDecimal)rowSchema().column(col).defaultValue();
+ return null;
int off = offset(offLen);
int len = length(offLen);
@@ -261,7 +271,7 @@
long offLen = findColumn(col, NativeTypeSpec.NUMBER);
if (offLen < 0)
- return offLen == -1 ? null : (BigInteger)rowSchema().column(col).defaultValue();
+ return null;
int off = offset(offLen);
int len = length(offLen);
@@ -348,10 +358,121 @@
}
/**
- * @return Row flags.
+ * Reads value for specified column.
+ *
+ * @param col Column index.
+ * @return Column value.
+ * @throws InvalidTypeException If actual column type does not match the requested column type.
*/
- private boolean hasFlag(int flag) {
- return ((readShort(FLAGS_FIELD_OFFSET) & flag)) != 0;
+ public LocalDate dateValue(int col) throws InvalidTypeException {
+ long offLen = findColumn(col, NativeTypeSpec.DATE);
+
+ if (offLen < 0)
+ return null;
+
+ int off = offset(offLen);
+
+ return readDate(off);
+ }
+
+ /**
+ * Reads value for specified column.
+ *
+ * @param col Column index.
+ * @return Column value.
+ * @throws InvalidTypeException If actual column type does not match the requested column type.
+ */
+ public LocalTime timeValue(int col) throws InvalidTypeException {
+ long offLen = findColumn(col, NativeTypeSpec.TIME);
+
+ if (offLen < 0)
+ return null;
+
+ int off = offset(offLen);
+
+ TemporalNativeType type = (TemporalNativeType)schema.column(col).type();
+
+ return readTime(off, type);
+ }
+
+ /**
+ * Reads value for specified column.
+ *
+ * @param col Column index.
+ * @return Column value.
+ * @throws InvalidTypeException If actual column type does not match the requested column type.
+ */
+ public LocalDateTime dateTimeValue(int col) throws InvalidTypeException {
+ long offLen = findColumn(col, NativeTypeSpec.DATETIME);
+
+ if (offLen < 0)
+ return null;
+
+ int off = offset(offLen);
+
+ TemporalNativeType type = (TemporalNativeType)schema.column(col).type();
+
+ return LocalDateTime.of(readDate(off), readTime(off + 3, type));
+ }
+
+ /**
+ * Reads value for specified column.
+ *
+ * @param col Column index.
+ * @return Column value.
+ * @throws InvalidTypeException If actual column type does not match the requested column type.
+ */
+ public Instant timestampValue(int col) throws InvalidTypeException {
+ long offLen = findColumn(col, NativeTypeSpec.TIMESTAMP);
+
+ if (offLen < 0)
+ return null;
+
+ int off = offset(offLen);
+
+ TemporalNativeType type = (TemporalNativeType)schema.column(col).type();
+
+ long seconds = readLong(off);
+ int nanos = 0;
+
+ if (type.precision() != 0)
+ nanos = readInteger(off + 8);
+
+ return Instant.ofEpochSecond(seconds, nanos);
+ }
+
+ /**
+ * Reads and decode time column value.
+ *
+ * @param off Offset
+ * @param type Temporal type precision.
+ * @return LocalTime value.
+ */
+ private LocalTime readTime(int off, TemporalNativeType type) {
+ long time = Integer.toUnsignedLong(readInteger(off));
+
+ if (type.precision() > 3) {
+ time <<= 16;
+ time |= Short.toUnsignedLong(readShort(off + 4));
+ time = (time >>> TemporalTypesHelper.NANOSECOND_PART_LEN) << 32 | (time & TemporalTypesHelper.NANOSECOND_PART_MASK);
+ }
+ else // Decompress
+ time = (time >>> TemporalTypesHelper.MILLISECOND_PART_LEN) << 32 | (time & TemporalTypesHelper.MILLISECOND_PART_MASK);
+
+ return TemporalTypesHelper.decodeTime(type, time);
+ }
+
+ /**
+ * Reads and decode date column value.
+ *
+ * @param off Offset
+ * @return LocalDate value.
+ */
+ private LocalDate readDate(int off) {
+ int date = Short.toUnsignedInt(readShort(off)) << 8;
+ date |= Byte.toUnsignedInt(readByte(off + 2));
+
+ return TemporalTypesHelper.decodeDate(date);
}
/**
@@ -364,10 +485,13 @@
*
* @param colIdx Column index.
* @param type Expected column type.
- * @return Encoded offset + length of the column.
+ * @return {@code -1} if value is {@code null} for a column,
+ * or {@link Long#MAX_VALUE} if column is unknown,
+ * otherwise encoded offset + length of the column.
* @see #offset(long)
* @see #length(long)
* @see InvalidTypeException If actual column type does not match the requested column type.
+ * @see org.apache.ignite.internal.schema.registry.UpgradingRowAdapter
*/
protected long findColumn(int colIdx, NativeTypeSpec type) throws InvalidTypeException {
// Get base offset (key start or value start) for the given column.
diff --git a/modules/schema/src/main/java/org/apache/ignite/internal/schema/row/RowAssembler.java b/modules/schema/src/main/java/org/apache/ignite/internal/schema/row/RowAssembler.java
index edd3c4e..261943e 100644
--- a/modules/schema/src/main/java/org/apache/ignite/internal/schema/row/RowAssembler.java
+++ b/modules/schema/src/main/java/org/apache/ignite/internal/schema/row/RowAssembler.java
@@ -23,6 +23,10 @@
import java.nio.charset.CharacterCodingException;
import java.nio.charset.CharsetEncoder;
import java.nio.charset.StandardCharsets;
+import java.time.Instant;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
import java.util.Arrays;
import java.util.BitSet;
import java.util.UUID;
@@ -39,6 +43,7 @@
import org.apache.ignite.internal.schema.NativeTypes;
import org.apache.ignite.internal.schema.NumberNativeType;
import org.apache.ignite.internal.schema.SchemaDescriptor;
+import org.apache.ignite.internal.schema.TemporalNativeType;
import static org.apache.ignite.internal.schema.BinaryRow.RowFlags.KEY_FLAGS_OFFSET;
import static org.apache.ignite.internal.schema.BinaryRow.RowFlags.VAL_FLAGS_OFFSET;
@@ -46,8 +51,14 @@
/**
* Utility class to build rows using column appending pattern. The external user of this class must consult
* with the schema and provide the columns in strict internal column sort order during the row construction.
+ * <p>
* Additionally, the user of this class should pre-calculate the resulting row size when possible to avoid
- * unnecessary data copies and allow some size-optimizations can be applied.
+ * unnecessary data copies and allow some size optimizations to be applied.
+ * <p>
+ * Natively supported temporal types are encoded automatically with preserving sort order before writing.
+ *
+ * @see #utf8EncodedLength(CharSequence)
+ * @see TemporalTypesHelper
*/
public class RowAssembler {
/** Schema. */
@@ -593,6 +604,101 @@
}
/**
+ * Appends LocalDate value for the current column to the chunk.
+ *
+ * @param val Column value.
+ * @return {@code this} for chaining.
+ */
+ public RowAssembler appendDate(LocalDate val) {
+ checkType(NativeTypes.DATE);
+
+ int date = TemporalTypesHelper.encodeDate(val);
+
+ writeDate(curOff, date);
+
+ if (isKeyChunk())
+ keyHash += 31 * keyHash + val.hashCode();
+
+ shiftColumn(NativeTypes.DATE.sizeInBytes());
+
+ return this;
+ }
+
+ /**
+ * Appends LocalTime value for the current column to the chunk.
+ *
+ * @param val Column value.
+ * @return {@code this} for chaining.
+ */
+ public RowAssembler appendTime(LocalTime val) {
+ checkType(NativeTypeSpec.TIME);
+
+ TemporalNativeType type = (TemporalNativeType)curCols.column(curCol).type();
+
+ writeTime(buf, curOff, val, type);
+
+ if (isKeyChunk())
+ keyHash += 31 * keyHash + val.hashCode();
+
+ shiftColumn(type.sizeInBytes());
+
+ return this;
+ }
+
+ /**
+ * Appends LocalDateTime value for the current column to the chunk.
+ *
+ * @param val Column value.
+ * @return {@code this} for chaining.
+ */
+ public RowAssembler appendDateTime(LocalDateTime val) {
+ checkType(NativeTypeSpec.DATETIME);
+
+ TemporalNativeType type = (TemporalNativeType)curCols.column(curCol).type();
+
+ int date = TemporalTypesHelper.encodeDate(val.toLocalDate());
+
+ writeDate(curOff, date);
+ writeTime(buf, curOff + 3, val.toLocalTime(), type);
+
+ if (isKeyChunk())
+ keyHash += 31 * keyHash + val.hashCode();
+
+ shiftColumn(type.sizeInBytes());
+
+ return this;
+ }
+
+ /**
+ * Appends Instant value for the current column to the chunk.
+ *
+ * @param val Column value.
+ * @return {@code this} for chaining.
+ */
+ public RowAssembler appendTimestamp(Instant val) {
+ checkType(NativeTypeSpec.TIMESTAMP);
+
+ TemporalNativeType type = (TemporalNativeType)curCols.column(curCol).type();
+
+ long seconds = val.getEpochSecond();
+ int nanos = TemporalTypesHelper.normalizeNanos(val.getNano(), type.precision());
+
+ buf.putLong(curOff, seconds);
+
+ if (type.precision() != 0) // Write only meaningful bytes.
+ buf.putInt(curOff + 8, nanos);
+
+ if (isKeyChunk()) {
+ keyHash += 31 * keyHash + Long.hashCode(seconds);
+ keyHash += 31 * keyHash + Integer.hashCode(nanos);
+ }
+
+ shiftColumn(type.sizeInBytes());
+
+ return this;
+ }
+
+ /**
* @return Serialized row.
*/
public BinaryRow build() {
@@ -653,6 +759,41 @@
}
/**
+ * Writes date.
+ *
+ * @param off Offset.
+ * @param date Compacted date.
+ */
+ private void writeDate(int off, int date) {
+ buf.putShort(off, (short)(date >>> 8));
+ buf.put(off + 2, (byte)(date & 0xFF));
+ }
+
+ /**
+ * Writes time.
+ *
+ * @param buf
+ * @param off Offset.
+ * @param val Time.
+ * @param type Native type.
+ */
+ static void writeTime(ExpandableByteBuf buf, int off, LocalTime val, TemporalNativeType type) {
+ long time = TemporalTypesHelper.encodeTime(type, val);
+
+ if (type.precision() > 3) {
+ time = ((time >>> 32) << TemporalTypesHelper.NANOSECOND_PART_LEN) | (time & TemporalTypesHelper.NANOSECOND_PART_MASK);
+
+ buf.putInt(off, (int)(time >>> 16));
+ buf.putShort(off + 4, (short)(time & 0xFFFF_FFFFL));
+ }
+ else {
+ time = ((time >>> 32) << TemporalTypesHelper.MILLISECOND_PART_LEN) | (time & TemporalTypesHelper.MILLISECOND_PART_MASK);
+
+ buf.putInt(off, (int)time);
+ }
+ }
+
+ /**
* Checks that the type being appended matches the column type.
*
* @param type Type spec that is attempted to be appended.
diff --git a/modules/schema/src/main/java/org/apache/ignite/internal/schema/row/TemporalTypesHelper.java b/modules/schema/src/main/java/org/apache/ignite/internal/schema/row/TemporalTypesHelper.java
new file mode 100644
index 0000000..fce400d
--- /dev/null
+++ b/modules/schema/src/main/java/org/apache/ignite/internal/schema/row/TemporalTypesHelper.java
@@ -0,0 +1,296 @@
+/*
+ * 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
+ *
+ * http://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.ignite.internal.schema.row;
+
+import java.time.LocalDate;
+import java.time.LocalTime;
+import org.apache.ignite.internal.schema.TemporalNativeType;
+
+/**
+ * Helper class for temporal type conversions.
+ * <p>
+ * Provides methods to encode/decode temporal types in a compact way for further writing to row.
+ * Conversion preserves natural type order.
+ * <p>
+ * DATE is a fixed-length type which compacted representation keeps ordering, value is signed and fit into a 3-bytes.
+ * Thus, DATE value can be compared by bytes where first byte is signed and others - unsigned.
+ * Thus temporal functions, like YEAR(), can easily extracts fields with a mask,
+ * <p>
+ * Date compact structure:
+ * ┌──────────────┬─────────┬────────┐
+ * │ Year(signed) │ Month │ Day │
+ * ├──────────────┼─────────┼────────┤
+ * │ 15 bits │ 4 bits │ 5 bits │
+ * └──────────────┴─────────┴────────┘
+ * <p>
+ * TIME is a fixed-length type supporting accuracy from 1 second up to 1 nanosecond.
+ * Compacted time representation keeps ordering, values fits to 4-6 bytes value.
+ * The first 18 bits is used for hours, minutes and seconds, and the last bits for fractional seconds:
+ * 14 for millisecond precision and 30 for nanosecond.
+ * Values of a type of any intermediate precisions is normalized to the type,
+ * then stored as shortest possible structure without a precision lost.
+ * <p>
+ * Time compact structure:
+ * ┌─────────┬─────────┬──────────┬─────────────┐
+ * │ Hours │ Minutes │ Seconds │ Sub-seconds │
+ * ├─────────┼─────────┼──────────┼─────────────┤
+ * │ 6 bit │ 6 bits │ 6 bit │ 14 bits │ - 32-bits in total.
+ * │ 6 bit │ 6 bits │ 6 bit │ 30 bits │ - 48-bits in total.
+ * └─────────┴─────────┴──────────┴─────────────┘
+ * <p>
+ * DATETIME is just a concatenation of DATE and TIME values.
+ * <p>
+ * TIMESTAMP has similar structure to {@link java.time.Instant} and supports precision from 1 second up to 1 nanosecond.
+ * Fractional seconds part is stored in a separate bit sequence which is omitted for {@code 0} accuracy.
+ * <p>
+ * Total value size is 8/12 bytes depending on the type precision.
+ * <p>
+ * Timestamp compact structure:
+ * ┌──────────────────────────┬─────────────┐
+ * │ Seconds since the epoch │ Sub-seconds │
+ * ├──────────────────────────┼─────────────┤
+ * │ 64 bits │ 0/32 bits │
+ * └──────────────────────────┴─────────────┘
+ *
+ * @see org.apache.ignite.internal.schema.row.Row
+ * @see org.apache.ignite.internal.schema.row.RowAssembler
+ */
+public class TemporalTypesHelper {
+ /** Month field length. */
+ public static final int MONTH_FIELD_LENGTH = 4;
+
+ /** Day field length. */
+ public static final int DAY_FIELD_LENGTH = 5;
+
+ /** Hours field length. */
+ public static final int HOUR_FIELD_LENGTH = 5;
+
+ /** Minutes field length. */
+ public static final int MINUTES_FIELD_LENGTH = 6;
+
+ /** Seconds field length. */
+ public static final int SECONDS_FIELD_LENGTH = 6;
+
+ /** Max year boundary. */
+ public static final int MAX_YEAR = (1 << 14) - 1;
+
+ /** Min year boundary. */
+ public static final int MIN_YEAR = -(1 << 14);
+
+ /** Fractional part length for millis precision. */
+ public static final int MILLISECOND_PART_LEN = 14;
+
+ /** Fractional part mask for millis precision. */
+ public static final long MILLISECOND_PART_MASK = (1L << MILLISECOND_PART_LEN) - 1;
+
+ /** Fractional part length for nanos precision. */
+ public static final int NANOSECOND_PART_LEN = 30;
+
+ /** Fractional part mask for nanos precision. */
+ public static final long NANOSECOND_PART_MASK = (1L << NANOSECOND_PART_LEN) - 1;
+
+ /**
+ * @param len Mask length in bits.
+ * @return Mask.
+ */
+ private static int mask(int len) {
+ return (1 << len) - 1;
+ }
+
+ /**
+ * Compact LocalDate.
+ *
+ * @param date Date.
+ * @return Encoded date.
+ */
+ public static int encodeDate(LocalDate date) {
+ int val = date.getYear() << MONTH_FIELD_LENGTH;
+ val = (val | date.getMonthValue()) << DAY_FIELD_LENGTH;
+ val |= date.getDayOfMonth();
+
+ return val & (0x00FF_FFFF);
+ }
+
+ /**
+ * Expands to LocalDate.
+ *
+ * @param date Encoded date.
+ * @return LocalDate instance.
+ */
+ public static LocalDate decodeDate(int date) {
+ date = (date << 8) >> 8; // Restore sign.
+
+ int day = (date) & mask(DAY_FIELD_LENGTH);
+ int mon = (date >>= DAY_FIELD_LENGTH) & mask(MONTH_FIELD_LENGTH);
+ int year = (date >> MONTH_FIELD_LENGTH); // Sign matters.
+
+ return LocalDate.of(year, mon, day);
+ }
+
+ /**
+ * Encode LocalTime to long as concatenation of 2 int values:
+ * encoded time with precision of seconds and fractional seconds.
+ *
+ * @param type Native temporal type.
+ * @param localTime Time.
+ * @return Encoded local time.
+ * @see #NANOSECOND_PART_LEN
+ * @see #MILLISECOND_PART_LEN
+ */
+ public static long encodeTime(TemporalNativeType type, LocalTime localTime) {
+ int time = localTime.getHour() << (MINUTES_FIELD_LENGTH + SECONDS_FIELD_LENGTH);
+ time |= localTime.getMinute() << SECONDS_FIELD_LENGTH;
+ time |= localTime.getSecond();
+
+ int fractional = truncateTo(type.precision(), localTime.getNano());
+
+ return ((long)time << 32) | fractional;
+ }
+
+ /**
+ * Decode to LocalTime.
+ *
+ * @param type Type.
+ * @param time Encoded time.
+ * @return LocalTime instance.
+ */
+ public static LocalTime decodeTime(TemporalNativeType type, long time) {
+ int fractional = (int)time;
+ int time0 = (int)(time >>> 32);
+
+ int sec = time0 & mask(SECONDS_FIELD_LENGTH);
+ int min = (time0 >>>= SECONDS_FIELD_LENGTH) & mask(MINUTES_FIELD_LENGTH);
+ int hour = (time0 >>> MINUTES_FIELD_LENGTH) & mask(HOUR_FIELD_LENGTH);
+
+ // Convert to nanoseconds.
+ switch (type.precision()) {
+ case 0:
+ break;
+ case 1:
+ case 2:
+ case 3: {
+ fractional *= 1_000_000;
+ break;
+ }
+ case 4:
+ case 5:
+ case 6: {
+ fractional *= 1_000;
+ break;
+ }
+ default:
+ break;
+ }
+
+ return LocalTime.of(hour, min, sec, fractional);
+ }
+
+ /**
+ * Normalize nanoseconds regarding the precision.
+ *
+ * @param nanos Nanoseconds.
+ * @param precision Meaningful digits.
+ * @return Normalized nanoseconds.
+ */
+ public static int normalizeNanos(int nanos, int precision) {
+ switch (precision) {
+ case 0:
+ nanos = 0;
+ break;
+ case 1:
+ nanos = (nanos / 100_000_000) * 100_000_000; // 100ms precision.
+ break;
+ case 2:
+ nanos = (nanos / 10_000_000) * 10_000_000; // 10ms precision.
+ break;
+ case 3: {
+ nanos = (nanos / 1_000_000) * 1_000_000; // 1ms precision.
+ break;
+ }
+ case 4: {
+ nanos = (nanos / 100_000) * 100_000; // 100mcs precision.
+ break;
+ }
+ case 5: {
+ nanos = (nanos / 10_000) * 10_000; // 10mcs precision.
+ break;
+ }
+ case 6: {
+ nanos = (nanos / 1_000) * 1_000; // 1mcs precision.
+ break;
+ }
+ case 7: {
+ nanos = (nanos / 100) * 100; // 100ns precision.
+ break;
+ }
+ case 8: {
+ nanos = (nanos / 10) * 10; // 10ns precision.
+ break;
+ }
+ case 9: {
+ nanos = nanos; // 1ns precision
+ break;
+ }
+ default: // Should never get here.
+ throw new IllegalArgumentException("Unsupported fractional seconds precision: " + precision);
+ }
+
+ return nanos;
+ }
+
+ /**
+ * Normalize to given precision and truncate to meaningful time unit.
+ *
+ * @param precision Precision.
+ * @param nanos Seconds' fractional part.
+ * @return Truncated fractional seconds (millis, micros or nanos).
+ */
+ private static int truncateTo(int precision, int nanos) {
+ switch (precision) {
+ case 0:
+ return 0;
+ case 1:
+ return (nanos / 100_000_000) * 100; // 100ms precision.
+ case 2:
+ return (nanos / 10_000_000) * 10; // 10ms precision.
+ case 3: {
+ return nanos / 1_000_000; // 1ms precision.
+ }
+ case 4: {
+ return (nanos / 100_000) * 100; // 100mcs precision.
+ }
+ case 5: {
+ return (nanos / 10_000) * 10; // 10mcs precision.
+ }
+ case 6: {
+ return nanos / 1_000; // 1mcs precision.
+ }
+ case 7: {
+ return (nanos / 100) * 100; // 100ns precision.
+ }
+ case 8: {
+ return (nanos / 10) * 10; // 10ns precision.
+ }
+ case 9: {
+ return nanos; // 1ns precision
+ }
+ default: // Should never get here.
+ throw new IllegalArgumentException("Unsupported fractional seconds precision: " + precision);
+ }
+ }
+}
diff --git a/modules/schema/src/test/java/org/apache/ignite/internal/schema/ColumnTest.java b/modules/schema/src/test/java/org/apache/ignite/internal/schema/ColumnTest.java
index 1e3d845..c6d30bc 100644
--- a/modules/schema/src/test/java/org/apache/ignite/internal/schema/ColumnTest.java
+++ b/modules/schema/src/test/java/org/apache/ignite/internal/schema/ColumnTest.java
@@ -21,6 +21,7 @@
import org.junit.jupiter.api.Test;
import static org.apache.ignite.internal.schema.NativeTypes.BYTES;
+import static org.apache.ignite.internal.schema.NativeTypes.DATE;
import static org.apache.ignite.internal.schema.NativeTypes.INT32;
import static org.apache.ignite.internal.schema.NativeTypes.STRING;
import static org.junit.jupiter.api.Assertions.assertEquals;
@@ -37,15 +38,17 @@
Column[] cols = new Column[] {
new Column("C", BYTES, false),
new Column("B", INT32, false),
+ new Column("A", DATE, false),
new Column("AD", STRING, false),
new Column("AA", STRING, false),
};
Arrays.sort(cols);
- assertEquals("B", cols[0].name());
- assertEquals("C", cols[1].name());
- assertEquals("AA", cols[2].name());
- assertEquals("AD", cols[3].name());
+ assertEquals("A", cols[0].name());
+ assertEquals("B", cols[1].name());
+ assertEquals("C", cols[2].name());
+ assertEquals("AA", cols[3].name());
+ assertEquals("AD", cols[4].name());
}
}
diff --git a/modules/schema/src/test/java/org/apache/ignite/internal/schema/NativeTypeTest.java b/modules/schema/src/test/java/org/apache/ignite/internal/schema/NativeTypeTest.java
index 3876557..5c732bc 100644
--- a/modules/schema/src/test/java/org/apache/ignite/internal/schema/NativeTypeTest.java
+++ b/modules/schema/src/test/java/org/apache/ignite/internal/schema/NativeTypeTest.java
@@ -21,6 +21,7 @@
import org.junit.jupiter.api.Test;
import static org.apache.ignite.internal.schema.NativeTypes.BYTES;
+import static org.apache.ignite.internal.schema.NativeTypes.DATE;
import static org.apache.ignite.internal.schema.NativeTypes.DOUBLE;
import static org.apache.ignite.internal.schema.NativeTypes.FLOAT;
import static org.apache.ignite.internal.schema.NativeTypes.INT16;
@@ -28,7 +29,18 @@
import static org.apache.ignite.internal.schema.NativeTypes.INT64;
import static org.apache.ignite.internal.schema.NativeTypes.INT8;
import static org.apache.ignite.internal.schema.NativeTypes.STRING;
+import static org.apache.ignite.internal.schema.NativeTypes.UUID;
+import static org.apache.ignite.internal.schema.NativeTypes.bitmaskOf;
+import static org.apache.ignite.internal.schema.NativeTypes.blobOf;
+import static org.apache.ignite.internal.schema.NativeTypes.datetime;
+import static org.apache.ignite.internal.schema.NativeTypes.decimalOf;
+import static org.apache.ignite.internal.schema.NativeTypes.from;
+import static org.apache.ignite.internal.schema.NativeTypes.numberOf;
+import static org.apache.ignite.internal.schema.NativeTypes.stringOf;
+import static org.apache.ignite.internal.schema.NativeTypes.time;
+import static org.apache.ignite.internal.schema.NativeTypes.timestamp;
import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
/**
@@ -43,14 +55,14 @@
assertTrue(INT8.compareTo(STRING) < 0);
assertTrue(INT8.compareTo(BYTES) < 0);
- assertTrue(NativeTypes.INT32.compareTo(STRING) < 0);
- assertTrue(NativeTypes.INT32.compareTo(BYTES) < 0);
+ assertTrue(INT32.compareTo(STRING) < 0);
+ assertTrue(INT32.compareTo(BYTES) < 0);
- assertTrue(NativeTypes.INT64.compareTo(STRING) < 0);
- assertTrue(NativeTypes.INT64.compareTo(BYTES) < 0);
+ assertTrue(INT64.compareTo(STRING) < 0);
+ assertTrue(INT64.compareTo(BYTES) < 0);
- assertTrue(NativeTypes.UUID.compareTo(STRING) < 0);
- assertTrue(NativeTypes.UUID.compareTo(BYTES) < 0);
+ assertTrue(UUID.compareTo(STRING) < 0);
+ assertTrue(UUID.compareTo(BYTES) < 0);
}
/**
@@ -58,9 +70,22 @@
*/
@Test
public void compareFixlenTypesBySize() {
- assertTrue(NativeTypes.INT16.compareTo(NativeTypes.INT32) < 0);
- assertTrue(NativeTypes.INT32.compareTo(NativeTypes.INT64) < 0);
- assertTrue(NativeTypes.INT64.compareTo(NativeTypes.UUID) < 0);
+ assertTrue(INT16.compareTo(INT32) < 0);
+ assertTrue(INT32.compareTo(INT64) < 0);
+ assertTrue(INT64.compareTo(UUID) < 0);
+
+ assertTrue(INT16.compareTo(DATE) < 0);
+ assertTrue(DATE.compareTo(INT32) < 0);
+
+ assertTrue(DATE.compareTo(time(0)) < 0);
+ assertTrue(INT32.compareTo(time(4)) < 0);
+ assertTrue(time(3).compareTo(time(4)) < 0);
+ assertTrue(time(9).compareTo(datetime(0)) < 0);
+
+ assertTrue(datetime(3).compareTo(INT64) < 0);
+ assertTrue(INT64.compareTo(datetime(4)) < 0);
+
+ assertTrue(INT64.compareTo(timestamp(1)) < 0);
}
/**
@@ -68,7 +93,54 @@
*/
@Test
public void compareFixlenTypesByDesc() {
- assertTrue(NativeTypes.FLOAT.compareTo(NativeTypes.INT32) < 0);
+ assertTrue(FLOAT.compareTo(INT32) < 0);
+ assertTrue(datetime(0).compareTo(INT64) < 0);
+ assertTrue(INT32.compareTo(time(0)) < 0);
+ assertTrue(INT32.compareTo(time(3)) < 0);
+ assertTrue(INT64.compareTo(timestamp(0)) < 0);
+ }
+
+ /**
+ *
+ */
+ @Test
+ public void validateTemporalTypesLength() {
+ assertEquals(3, DATE.sizeInBytes());
+
+ assertEquals(6, time().sizeInBytes());
+ assertEquals(4, time(0).sizeInBytes());
+ assertEquals(4, time(3).sizeInBytes());
+ assertEquals(6, time(4).sizeInBytes());
+ assertEquals(6, time(9).sizeInBytes());
+
+ assertEquals(9, datetime().sizeInBytes());
+ assertEquals(7, datetime(0).sizeInBytes());
+ assertEquals(7, datetime(3).sizeInBytes());
+ assertEquals(9, datetime(4).sizeInBytes());
+ assertEquals(9, datetime(9).sizeInBytes());
+
+ assertEquals(12, timestamp().sizeInBytes());
+ assertEquals(8, timestamp(0).sizeInBytes());
+ assertEquals(12, timestamp(1).sizeInBytes());
+ assertEquals(12, timestamp(9).sizeInBytes());
+
+ assertEquals(0, datetime().compareTo(datetime(6)));
+ assertEquals(0, time().compareTo(time(6)));
+ assertEquals(0, timestamp().compareTo(timestamp(6)));
+ }
+
+ /**
+ *
+ */
+ @Test
+ public void invalidTemporalTypes() {
+ assertThrows(IllegalArgumentException.class, () -> time(-1));
+ assertThrows(IllegalArgumentException.class, () -> timestamp(-1));
+ assertThrows(IllegalArgumentException.class, () -> datetime(-1));
+
+ assertThrows(IllegalArgumentException.class, () -> time(10));
+ assertThrows(IllegalArgumentException.class, () -> timestamp(10));
+ assertThrows(IllegalArgumentException.class, () -> datetime(10));
}
/**
@@ -84,21 +156,32 @@
*/
@Test
public void createNativeTypeFromColumnType() {
- assertEquals(INT8, NativeTypes.from(ColumnType.INT8));
- assertEquals(INT16, NativeTypes.from(ColumnType.INT16));
- assertEquals(INT32, NativeTypes.from(ColumnType.INT32));
- assertEquals(INT64, NativeTypes.from(ColumnType.INT64));
- assertEquals(FLOAT, NativeTypes.from(ColumnType.FLOAT));
- assertEquals(DOUBLE, NativeTypes.from(ColumnType.DOUBLE));
- assertEquals(BYTES, NativeTypes.from(ColumnType.blobOf()));
- assertEquals(STRING, NativeTypes.from(ColumnType.string()));
+ assertEquals(INT8, from(ColumnType.INT8));
+ assertEquals(INT16, from(ColumnType.INT16));
+ assertEquals(INT32, from(ColumnType.INT32));
+ assertEquals(INT64, from(ColumnType.INT64));
+ assertEquals(FLOAT, from(ColumnType.FLOAT));
+ assertEquals(DOUBLE, from(ColumnType.DOUBLE));
+ assertEquals(DATE, from(ColumnType.DATE));
+ assertEquals(BYTES, from(ColumnType.blobOf()));
+ assertEquals(STRING, from(ColumnType.string()));
+
+ assertEquals(time(), from(ColumnType.time(ColumnType.TemporalColumnType.DEFAULT_PRECISION)));
+ assertEquals(datetime(), from(ColumnType.datetime(ColumnType.TemporalColumnType.DEFAULT_PRECISION)));
+ assertEquals(timestamp(), from(ColumnType.timestamp(ColumnType.TemporalColumnType.DEFAULT_PRECISION)));
for (int i = 1; i < 800; i += 100) {
- assertEquals(NativeTypes.blobOf(i), NativeTypes.from(ColumnType.blobOf(i)));
- assertEquals(NativeTypes.stringOf(i), NativeTypes.from(ColumnType.stringOf(i)));
- assertEquals(NativeTypes.bitmaskOf(i), NativeTypes.from(ColumnType.bitmaskOf(i)));
- assertEquals(NativeTypes.numberOf(i), NativeTypes.from(ColumnType.numberOf(i)));
- assertEquals(NativeTypes.decimalOf(i, i), NativeTypes.from(ColumnType.decimalOf(i, i)));
+ assertEquals(blobOf(i), from(ColumnType.blobOf(i)));
+ assertEquals(stringOf(i), from(ColumnType.stringOf(i)));
+ assertEquals(bitmaskOf(i), from(ColumnType.bitmaskOf(i)));
+ assertEquals(numberOf(i), from(ColumnType.numberOf(i)));
+ assertEquals(decimalOf(i, i), from(ColumnType.decimalOf(i, i)));
+ }
+
+ for (int i = 0; i <= 9; i++) {
+ assertEquals(time(i), from(ColumnType.time(i)));
+ assertEquals(datetime(i), from(ColumnType.datetime(i)));
+ assertEquals(timestamp(i), from(ColumnType.timestamp(i)));
}
}
}
diff --git a/modules/schema/src/test/java/org/apache/ignite/internal/schema/RowTest.java b/modules/schema/src/test/java/org/apache/ignite/internal/schema/RowTest.java
index 59728f6..6244b2c 100644
--- a/modules/schema/src/test/java/org/apache/ignite/internal/schema/RowTest.java
+++ b/modules/schema/src/test/java/org/apache/ignite/internal/schema/RowTest.java
@@ -19,6 +19,10 @@
import java.math.BigDecimal;
import java.math.BigInteger;
+import java.time.Instant;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
import java.util.Arrays;
import java.util.BitSet;
import java.util.Collections;
@@ -33,6 +37,7 @@
import org.junit.jupiter.api.Test;
import static org.apache.ignite.internal.schema.NativeTypes.BYTES;
+import static org.apache.ignite.internal.schema.NativeTypes.DATE;
import static org.apache.ignite.internal.schema.NativeTypes.DOUBLE;
import static org.apache.ignite.internal.schema.NativeTypes.FLOAT;
import static org.apache.ignite.internal.schema.NativeTypes.INT16;
@@ -41,6 +46,9 @@
import static org.apache.ignite.internal.schema.NativeTypes.INT8;
import static org.apache.ignite.internal.schema.NativeTypes.STRING;
import static org.apache.ignite.internal.schema.NativeTypes.UUID;
+import static org.apache.ignite.internal.schema.NativeTypes.datetime;
+import static org.apache.ignite.internal.schema.NativeTypes.time;
+import static org.apache.ignite.internal.schema.NativeTypes.timestamp;
import static org.apache.ignite.internal.schema.TestUtils.randomBytes;
import static org.apache.ignite.internal.schema.TestUtils.randomString;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
@@ -70,7 +78,7 @@
*/
@Test
public void nullableFixSizedColumns() {
- Column[] keyCols = new Column[] {
+ Column[] keyCols = new Column[]{
new Column("keyByteCol", INT8, false),
new Column("keyShortCol", INT16, false),
new Column("keyIntCol", INT32, false),
@@ -78,11 +86,15 @@
new Column("keyFloatCol", FLOAT, false),
new Column("keyDoubleCol", DOUBLE, false),
new Column("keyUuidCol", UUID, false),
+ new Column("keyDateCol", DATE, false),
+ new Column("keyTimeCol", time(), false),
+ new Column("keyDateTimeCol", datetime(), false),
+ new Column("keyTimeStampCol", timestamp(), false),
new Column("keyBitmask1Col", NativeTypes.bitmaskOf(4), false),
new Column("keyBitmask2Col", NativeTypes.bitmaskOf(22), false)
};
- Column[] valCols = new Column[] {
+ Column[] valCols = new Column[]{
new Column("valByteCol", INT8, false),
new Column("valShortCol", INT16, false),
new Column("valIntCol", INT32, false),
@@ -90,6 +102,10 @@
new Column("valFloatCol", FLOAT, false),
new Column("valDoubleCol", DOUBLE, false),
new Column("valUuidCol", UUID, false),
+ new Column("valDateCol", DATE, false),
+ new Column("valTimeCol", time(), false),
+ new Column("valDateTimeCol", datetime(), false),
+ new Column("valTimeStampCol", timestamp(), false),
new Column("valBitmask1Col", NativeTypes.bitmaskOf(4), false),
new Column("valBitmask2Col", NativeTypes.bitmaskOf(22), false)
};
@@ -102,7 +118,7 @@
*/
@Test
public void fixSizedColumns() {
- Column[] keyCols = new Column[] {
+ Column[] keyCols = new Column[]{
new Column("keyByteCol", INT8, true),
new Column("keyShortCol", INT16, true),
new Column("keyIntCol", INT32, true),
@@ -110,11 +126,15 @@
new Column("keyFloatCol", FLOAT, true),
new Column("keyDoubleCol", DOUBLE, true),
new Column("keyUuidCol", UUID, true),
+ new Column("keyDateCol", DATE, true),
+ new Column("keyTimeCol", time(), true),
+ new Column("keyDateTimeCol", datetime(), true),
+ new Column("keyTimeStampCol", timestamp(), true),
new Column("keyBitmask1Col", NativeTypes.bitmaskOf(4), true),
new Column("keyBitmask2Col", NativeTypes.bitmaskOf(22), true),
};
- Column[] valCols = new Column[] {
+ Column[] valCols = new Column[]{
new Column("valByteCol", INT8, true),
new Column("valShortCol", INT16, true),
new Column("valIntCol", INT32, true),
@@ -122,6 +142,10 @@
new Column("valFloatCol", FLOAT, true),
new Column("valDoubleCol", DOUBLE, true),
new Column("valUuidCol", UUID, true),
+ new Column("valDateCol", DATE, true),
+ new Column("valTimeCol", time(), true),
+ new Column("valDateTimeCol", datetime(), true),
+ new Column("valTimeStampCol", timestamp(), true),
new Column("valBitmask1Col", NativeTypes.bitmaskOf(4), true),
new Column("valBitmask2Col", NativeTypes.bitmaskOf(22), true),
};
@@ -134,22 +158,24 @@
*/
@Test
public void mixedColumns() {
- Column[] keyCols = new Column[] {
+ Column[] keyCols = new Column[]{
new Column("keyByteCol", INT8, false),
new Column("keyShortCol", INT16, false),
new Column("keyIntCol", INT32, false),
new Column("keyLongCol", INT64, false),
+ new Column("keyDateTimeCol", datetime(), false),
new Column("keyBytesCol", BYTES, false),
new Column("keyStringCol", STRING, false),
new Column("keyNumberCol", NativeTypes.numberOf(9), false),
new Column("keyDecimalCol", NativeTypes.decimalOf(20, 3), false),
};
- Column[] valCols = new Column[] {
- new Column("keyByteCol", INT8, true),
- new Column("keyShortCol", INT16, true),
- new Column("keyIntCol", INT32, true),
- new Column("keyLongCol", INT64, true),
+ Column[] valCols = new Column[]{
+ new Column("valByteCol", INT8, true),
+ new Column("valShortCol", INT16, true),
+ new Column("valIntCol", INT32, true),
+ new Column("valLongCol", INT64, true),
+ new Column("valDateTimeCol", datetime(), true),
new Column("valBytesCol", BYTES, true),
new Column("valStringCol", STRING, true),
new Column("valNumberCol", NativeTypes.numberOf(9), true),
@@ -160,18 +186,44 @@
}
/**
+ * Check row serialization for schema with various columns.
+ */
+ @Test
+ public void temporalColumns() {
+ Column[] keyCols = new Column[]{
+ new Column("keyTimestampCol1", timestamp(0), false),
+ new Column("keyTimestampCol2", timestamp(1), false),
+ new Column("keyTimestampCol3", timestamp(4), false),
+ new Column("keyTimestampCol4", timestamp(9), false),
+ };
+
+ Column[] valCols = new Column[]{
+ new Column("valDateTimeCol1", datetime(0), false),
+ new Column("valDateTimeCol2", datetime(1), true),
+ new Column("valDateTimeCol3", datetime(4), false),
+ new Column("valDateTimeCol4", datetime(9), true),
+ new Column("valTimeCol1", time(0), true),
+ new Column("valTimeCol2", time(1), true),
+ new Column("valTimeCol3", time(4), false),
+ new Column("valTimeCol4", time(9), false),
+ };
+
+ checkSchema(keyCols, valCols);
+ }
+
+ /**
* Check row serialization for schema with non-nullable varlen columns only.
*/
@Test
public void varlenColumns() {
- Column[] keyCols = new Column[] {
+ Column[] keyCols = new Column[]{
new Column("keyBytesCol", BYTES, false),
new Column("keyStringCol", STRING, false),
new Column("keyNumberCol", NativeTypes.numberOf(9), false),
new Column("keyDecimalCol", NativeTypes.decimalOf(20, 3), false),
};
- Column[] valCols = new Column[] {
+ Column[] valCols = new Column[]{
new Column("valBytesCol", BYTES, false),
new Column("valStringCol", STRING, false),
new Column("valNumberCol", NativeTypes.numberOf(9), false),
@@ -186,12 +238,12 @@
*/
@Test
public void nullableVarlenColumns() {
- Column[] keyCols = new Column[] {
+ Column[] keyCols = new Column[]{
new Column("keyBytesCol", BYTES, true),
new Column("keyStringCol", STRING, true),
};
- Column[] valCols = new Column[] {
+ Column[] valCols = new Column[]{
new Column("valBytesCol", BYTES, true),
new Column("valStringCol", STRING, true),
new Column("valNumberCol", NativeTypes.numberOf(9), true),
@@ -520,6 +572,22 @@
asm.appendBitmask((BitSet)vals[i]);
break;
+ case DATE:
+ asm.appendDate((LocalDate)vals[i]);
+ break;
+
+ case TIME:
+ asm.appendTime((LocalTime)vals[i]);
+ break;
+
+ case DATETIME:
+ asm.appendDateTime((LocalDateTime)vals[i]);
+ break;
+
+ case TIMESTAMP:
+ asm.appendTimestamp((Instant)vals[i]);
+ break;
+
default:
throw new IllegalStateException("Unsupported test type: " + type);
}
diff --git a/modules/schema/src/test/java/org/apache/ignite/internal/schema/TemporalTypesTest.java b/modules/schema/src/test/java/org/apache/ignite/internal/schema/TemporalTypesTest.java
new file mode 100644
index 0000000..5c6bbe1
--- /dev/null
+++ b/modules/schema/src/test/java/org/apache/ignite/internal/schema/TemporalTypesTest.java
@@ -0,0 +1,84 @@
+/*
+ * 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
+ *
+ * http://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.ignite.internal.schema;
+
+import java.time.LocalDate;
+import java.time.LocalTime;
+import java.time.temporal.ChronoUnit;
+import org.apache.ignite.internal.schema.row.TemporalTypesHelper;
+import org.apache.ignite.schema.ColumnType;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+/**
+ * Test temporal type compaction.
+ */
+public class TemporalTypesTest {
+ /**
+ * Check date boundaries.
+ */
+ @Test
+ void testDate() {
+ checkDate(LocalDate.of(0, 1, 1));
+ checkDate(LocalDate.of(-1, 1, 11));
+
+ LocalDate maxDate = LocalDate.of(TemporalTypesHelper.MAX_YEAR, 12, 31);
+ LocalDate minDate = LocalDate.of(TemporalTypesHelper.MIN_YEAR, 1, 1);
+
+ checkDate(maxDate);
+ checkDate(minDate);
+
+ assertThrows(AssertionError.class, () -> checkDate(maxDate.plusDays(1)));
+ assertThrows(AssertionError.class, () -> checkDate(minDate.minusDays(1)));
+ }
+
+ /**
+ * Check time boundaries.
+ */
+ @Test
+ void testTime() {
+ for (int i = 0; i <= 9; i++) {
+ checkTime(TemporalNativeType.time(i), LocalTime.MAX.withNano(TemporalTypesHelper.normalizeNanos(LocalTime.MAX.getNano(), i))); // Seconds precision.
+ checkTime(TemporalNativeType.time(i), LocalTime.MIN);
+ }
+
+ checkTime(TemporalNativeType.time(ColumnType.TemporalColumnType.DEFAULT_PRECISION), LocalTime.MAX.truncatedTo(ChronoUnit.MICROS));
+ checkTime(TemporalNativeType.time(9), LocalTime.MAX);
+
+ assertThrows(AssertionError.class, () -> checkTime(TemporalNativeType.time(ColumnType.TemporalColumnType.DEFAULT_PRECISION), LocalTime.MAX));
+ assertThrows(AssertionError.class, () -> checkTime(TemporalNativeType.time(0), LocalTime.MAX));
+ assertThrows(AssertionError.class, () -> checkTime(TemporalNativeType.time(8), LocalTime.MAX));
+ }
+
+ /**
+ * @param date Date.
+ */
+ private void checkDate(LocalDate date) {
+ assertEquals(date, TemporalTypesHelper.decodeDate(TemporalTypesHelper.encodeDate(date)));
+ }
+
+ /**
+ * @param type Type to validate against.
+ * @param time Time value.
+ */
+ private void checkTime(TemporalNativeType type, LocalTime time) {
+ assertEquals(time, TemporalTypesHelper.decodeTime(type, TemporalTypesHelper.encodeTime(type, time)));
+ }
+}
diff --git a/modules/schema/src/test/java/org/apache/ignite/internal/schema/TestUtils.java b/modules/schema/src/test/java/org/apache/ignite/internal/schema/TestUtils.java
index 878fb76..6d7152f 100644
--- a/modules/schema/src/test/java/org/apache/ignite/internal/schema/TestUtils.java
+++ b/modules/schema/src/test/java/org/apache/ignite/internal/schema/TestUtils.java
@@ -19,9 +19,19 @@
import java.math.BigDecimal;
import java.math.BigInteger;
+import java.time.Instant;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
+import java.time.Year;
+import java.time.temporal.ChronoUnit;
import java.util.BitSet;
import java.util.Random;
+import static org.apache.ignite.internal.schema.row.TemporalTypesHelper.MAX_YEAR;
+import static org.apache.ignite.internal.schema.row.TemporalTypesHelper.MIN_YEAR;
+import static org.apache.ignite.internal.schema.row.TemporalTypesHelper.normalizeNanos;
+
/**
* Test utility class.
*/
@@ -74,6 +84,30 @@
return randomBitSet(rnd, maskType.bits());
}
+ case DATE: {
+ Year year = Year.of(rnd.nextInt(MAX_YEAR - MIN_YEAR) + MIN_YEAR);
+
+ return LocalDate.ofYearDay(year.getValue(), rnd.nextInt(year.length()) + 1);
+ }
+
+ case TIME:
+ return LocalTime.of(rnd.nextInt(24), rnd.nextInt(60), rnd.nextInt(60),
+ normalizeNanos(rnd.nextInt(1_000_000_000), ((TemporalNativeType)type).precision()));
+
+ case DATETIME: {
+ Year year = Year.of(rnd.nextInt(MAX_YEAR - MIN_YEAR) + MIN_YEAR);
+
+ LocalDate date = LocalDate.ofYearDay(year.getValue(), rnd.nextInt(year.length()) + 1);
+ LocalTime time = LocalTime.of(rnd.nextInt(24), rnd.nextInt(60), rnd.nextInt(60),
+ normalizeNanos(rnd.nextInt(1_000_000_000), ((TemporalNativeType)type).precision()));
+
+ return LocalDateTime.of(date, time);
+ }
+
+ case TIMESTAMP:
+ return Instant.ofEpochMilli(rnd.nextLong()).truncatedTo(ChronoUnit.SECONDS)
+ .plusNanos(normalizeNanos(rnd.nextInt(1_000_000_000), ((TemporalNativeType)type).precision()));
+
default:
throw new IllegalArgumentException("Unsupported type: " + type);
}
diff --git a/modules/table/src/main/java/org/apache/ignite/internal/table/RowChunkAdapter.java b/modules/table/src/main/java/org/apache/ignite/internal/table/RowChunkAdapter.java
index d3ddd3a..a58d960 100644
--- a/modules/table/src/main/java/org/apache/ignite/internal/table/RowChunkAdapter.java
+++ b/modules/table/src/main/java/org/apache/ignite/internal/table/RowChunkAdapter.java
@@ -17,6 +17,10 @@
package org.apache.ignite.internal.table;
+import java.time.Instant;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
import java.util.BitSet;
import java.util.Iterator;
import java.util.UUID;
@@ -225,6 +229,66 @@
}
/** {@inheritDoc} */
+ @Override public LocalDate dateValue(String columnName) {
+ Column col = columnByName(columnName);
+
+ return row().dateValue(col.schemaIndex());
+ }
+
+ /** {@inheritDoc} */
+ @Override public LocalDate dateValue(int columnIndex) {
+ schema().validateColumnIndex(columnIndex);
+
+ return row().dateValue(columnIndex);
+ }
+
+ /** {@inheritDoc} */
+ @Override public LocalTime timeValue(String columnName) {
+ Column col = columnByName(columnName);
+
+ return row().timeValue(col.schemaIndex());
+ }
+
+ /** {@inheritDoc} */
+ @Override public LocalTime timeValue(int columnIndex) {
+ schema().validateColumnIndex(columnIndex);
+
+ return row().timeValue(columnIndex);
+ }
+
+ /** {@inheritDoc} */
+ @Override public LocalDateTime datetimeValue(String columnName) {
+ Column col = columnByName(columnName);
+
+ return row().dateTimeValue(col.schemaIndex());
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @return
+ */
+ @Override public LocalDateTime datetimeValue(int columnIndex) {
+ schema().validateColumnIndex(columnIndex);
+
+ return row().dateTimeValue(columnIndex);
+ }
+
+ /** {@inheritDoc} */
+ @Override public Instant timestampValue(String columnName) {
+ Column col = columnByName(columnName);
+
+ return row().timestampValue(col.schemaIndex());
+ }
+
+ /** {@inheritDoc} */
+ @Override public Instant timestampValue(int columnIndex) {
+ schema().validateColumnIndex(columnIndex);
+
+ return row().timestampValue(columnIndex);
+ }
+
+ /** {@inheritDoc} */
@NotNull @Override public Iterator<Object> iterator() {
return new Iterator<>() {
/** Current column index. */
diff --git a/modules/table/src/main/java/org/apache/ignite/internal/table/TupleBuilderImpl.java b/modules/table/src/main/java/org/apache/ignite/internal/table/TupleBuilderImpl.java
index d208129..4ced975 100644
--- a/modules/table/src/main/java/org/apache/ignite/internal/table/TupleBuilderImpl.java
+++ b/modules/table/src/main/java/org/apache/ignite/internal/table/TupleBuilderImpl.java
@@ -17,6 +17,10 @@
package org.apache.ignite.internal.table;
+import java.time.Instant;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
import java.util.BitSet;
import java.util.HashMap;
import java.util.Iterator;
@@ -224,6 +228,46 @@
}
/** {@inheritDoc} */
+ @Override public LocalDate dateValue(String columnName) {
+ return value(columnName);
+ }
+
+ /** {@inheritDoc} */
+ @Override public LocalDate dateValue(int columnIndex) {
+ return value(columnIndex);
+ }
+
+ /** {@inheritDoc} */
+ @Override public LocalTime timeValue(String columnName) {
+ return value(columnName);
+ }
+
+ /** {@inheritDoc} */
+ @Override public LocalTime timeValue(int columnIndex) {
+ return value(columnIndex);
+ }
+
+ /** {@inheritDoc} */
+ @Override public LocalDateTime datetimeValue(String columnName) {
+ return value(columnName);
+ }
+
+ /** {@inheritDoc} */
+ @Override public LocalDateTime datetimeValue(int columnIndex) {
+ return value(columnIndex);
+ }
+
+ /** {@inheritDoc} */
+ @Override public Instant timestampValue(String columnName) {
+ return value(columnName);
+ }
+
+ /** {@inheritDoc} */
+ @Override public Instant timestampValue(int columnIndex) {
+ return value(columnIndex);
+ }
+
+ /** {@inheritDoc} */
@Override public SchemaDescriptor schema() {
return schemaDesc;
}
diff --git a/modules/table/src/test/java/org/apache/ignite/internal/table/impl/TestTupleBuilder.java b/modules/table/src/test/java/org/apache/ignite/internal/table/impl/TestTupleBuilder.java
index 440d7b7..dceee30 100644
--- a/modules/table/src/test/java/org/apache/ignite/internal/table/impl/TestTupleBuilder.java
+++ b/modules/table/src/test/java/org/apache/ignite/internal/table/impl/TestTupleBuilder.java
@@ -17,6 +17,10 @@
package org.apache.ignite.internal.table.impl;
+import java.time.Instant;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
import java.util.BitSet;
import java.util.HashMap;
import java.util.Iterator;
@@ -180,6 +184,46 @@
}
/** {@inheritDoc} */
+ @Override public LocalDate dateValue(String columnName) {
+ return value(columnName);
+ }
+
+ /** {@inheritDoc} */
+ @Override public LocalDate dateValue(int columnIndex) {
+ return null;
+ }
+
+ /** {@inheritDoc} */
+ @Override public LocalTime timeValue(String columnName) {
+ return value(columnName);
+ }
+
+ /** {@inheritDoc} */
+ @Override public LocalTime timeValue(int columnIndex) {
+ return null;
+ }
+
+ /** {@inheritDoc} */
+ @Override public LocalDateTime datetimeValue(String columnName) {
+ return value(columnName);
+ }
+
+ /** {@inheritDoc} */
+ @Override public LocalDateTime datetimeValue(int columnIndex) {
+ return null;
+ }
+
+ /** {@inheritDoc} */
+ @Override public Instant timestampValue(String columnName) {
+ return value(columnName);
+ }
+
+ /** {@inheritDoc} */
+ @Override public Instant timestampValue(int columnIndex) {
+ return null;
+ }
+
+ /** {@inheritDoc} */
@NotNull @Override public Iterator<Object> iterator() {
throw new UnsupportedOperationException();
}