diff --git a/repository-ydb-v2/src/main/java/tech/ydb/yoj/repository/ydb/yql/YqlPrimitiveType.java b/repository-ydb-v2/src/main/java/tech/ydb/yoj/repository/ydb/yql/YqlPrimitiveType.java
index 9e6aad2f..7a60fbcc 100644
--- a/repository-ydb-v2/src/main/java/tech/ydb/yoj/repository/ydb/yql/YqlPrimitiveType.java
+++ b/repository-ydb-v2/src/main/java/tech/ydb/yoj/repository/ydb/yql/YqlPrimitiveType.java
@@ -12,6 +12,7 @@
import tech.ydb.proto.ValueProtos.Type.PrimitiveTypeId;
import tech.ydb.proto.ValueProtos.Value.ValueCase;
import tech.ydb.table.values.proto.ProtoValue;
+import tech.ydb.yoj.ExperimentalApi;
import tech.ydb.yoj.databind.ByteArray;
import tech.ydb.yoj.databind.DbType;
import tech.ydb.yoj.databind.FieldValueType;
@@ -87,6 +88,9 @@ public class YqlPrimitiveType implements YqlType {
private static final Setter INSTANT_SECOND_SETTER = (b, v) -> b.setInt64Value(((Instant) v).getEpochSecond());
private static final Setter INSTANT_UINT_SECOND_SETTER = (b, v) -> b.setUint64Value(((Instant) v).getEpochSecond());
private static final Setter TIMESTAMP_SETTER = (b, v) -> b.setUint64Value(ProtoValue.fromTimestamp((Instant) v).getUint64Value());
+ private static final Setter TIMESTAMP_SECONDS_SETTER = (b, v) -> b.setUint64Value(ProtoValue.fromTimestamp(((Instant) v).truncatedTo(ChronoUnit.SECONDS)).getUint64Value());
+ private static final Setter TIMESTAMP_MILLI_SETTER = (b, v) -> b.setUint64Value(ProtoValue.fromTimestamp(((Instant) v).truncatedTo(ChronoUnit.MILLIS)).getUint64Value());
+
private static final Setter DURATION_SETTER = (b, v) -> b.setInt64Value(ProtoValue.fromInterval((Duration) v).getInt64Value());
private static final Setter DURATION_UINT_SETTER = (b, v) -> b.setUint64Value(ProtoValue.fromInterval((Duration) v).getInt64Value());
private static final Setter DURATION_MILLI_SETTER = (b, v) -> b.setInt64Value(((Duration) v).toMillis());
@@ -125,6 +129,8 @@ public class YqlPrimitiveType implements YqlType {
private static final Getter INSTANT_SECOND_GETTER = v -> Instant.ofEpochSecond(v.getInt64Value());
private static final Getter INSTANT_UINT_SECOND_GETTER = v -> Instant.ofEpochSecond(v.getUint64Value());
private static final Getter TIMESTAMP_GETTER = ProtoValue::toTimestamp;
+ private static final Getter TIMESTAMP_SECONDS_GETTER = v -> ProtoValue.toTimestamp(v).truncatedTo(ChronoUnit.SECONDS);
+ private static final Getter TIMESTAMP_MILLI_GETTER = v -> ProtoValue.toTimestamp(v).truncatedTo(ChronoUnit.MILLIS);
private static final Getter DURATION_GETTER = ProtoValue::toInterval;
private static final Getter DURATION_UINT_GETTER = v -> Duration.of(v.getUint64Value(), ChronoUnit.MICROS);
private static final Getter DURATION_MILLI_GETTER = v -> Duration.ofMillis(v.getInt64Value());
@@ -163,17 +169,21 @@ public class YqlPrimitiveType implements YqlType {
registerYqlType(Long.class, PrimitiveTypeId.UINT64, null, false, ULONG_SETTER, ULONG_GETTER);
registerYqlType(Float.class, PrimitiveTypeId.FLOAT, null, true, FLOAT_SETTER, FLOAT_GETTER);
registerYqlType(Double.class, PrimitiveTypeId.DOUBLE, null, true, DOUBLE_SETTER, DOUBLE_GETTER);
+
registerYqlType(byte[].class, PrimitiveTypeId.STRING, null, true, BYTES_SETTER, BYTES_GETTER);
registerYqlType(ByteArray.class, PrimitiveTypeId.STRING, null, true, BYTE_ARRAY_SETTER, BYTE_ARRAY_GETTER);
- registerYqlType(Instant.class, PrimitiveTypeId.INT64, DbTypeQualifier.MILLISECONDS, true, INSTANT_SETTER, INSTANT_GETTER);
+
+ registerYqlType(Instant.class, PrimitiveTypeId.INT64, null, true, INSTANT_SETTER, INSTANT_GETTER); // defaults to millis
+ registerYqlType(Instant.class, PrimitiveTypeId.INT64, DbTypeQualifier.MILLISECONDS, false, INSTANT_SETTER, INSTANT_GETTER);
+ registerYqlType(Instant.class, PrimitiveTypeId.UINT64, null, false, INSTANT_UINT_SETTER, INSTANT_UINT_GETTER); // defaults to millis
registerYqlType(Instant.class, PrimitiveTypeId.UINT64, DbTypeQualifier.MILLISECONDS, false, INSTANT_UINT_SETTER, INSTANT_UINT_GETTER);
registerYqlType(Instant.class, PrimitiveTypeId.INT64, DbTypeQualifier.SECONDS, false, INSTANT_SECOND_SETTER, INSTANT_SECOND_GETTER);
registerYqlType(Instant.class, PrimitiveTypeId.UINT64, DbTypeQualifier.SECONDS, false, INSTANT_UINT_SECOND_SETTER, INSTANT_UINT_SECOND_GETTER);
registerYqlType(Instant.class, PrimitiveTypeId.TIMESTAMP, null, false, TIMESTAMP_SETTER, TIMESTAMP_GETTER);
- // XXX Temporarily require an explicit specification
- // of the database type for duration fields in order to find possible places of the old use
- // of duration in the DB model
- registerYqlType(Duration.class, PrimitiveTypeId.INTERVAL, null, false /* XXX true */, DURATION_SETTER, DURATION_GETTER);
+ registerYqlType(Instant.class, PrimitiveTypeId.TIMESTAMP, DbTypeQualifier.SECONDS, false, TIMESTAMP_SECONDS_SETTER, TIMESTAMP_SECONDS_GETTER);
+ registerYqlType(Instant.class, PrimitiveTypeId.TIMESTAMP, DbTypeQualifier.MILLISECONDS, false, TIMESTAMP_MILLI_SETTER, TIMESTAMP_MILLI_GETTER);
+
+ registerYqlType(Duration.class, PrimitiveTypeId.INTERVAL, null, true, DURATION_SETTER, DURATION_GETTER);
registerYqlType(Duration.class, PrimitiveTypeId.INT64, null, false, DURATION_SETTER, DURATION_GETTER);
registerYqlType(Duration.class, PrimitiveTypeId.UINT64, null, false, DURATION_UINT_SETTER, DURATION_UINT_GETTER);
registerYqlType(Duration.class, PrimitiveTypeId.INT64, DbTypeQualifier.MILLISECONDS, false, DURATION_MILLI_SETTER, DURATION_MILLI_GETTER);
@@ -321,14 +331,70 @@ private static void registerYqlType(
}
}
+ /**
+ * @deprecated Call {@link #useRecommendedMappingFor(FieldValueType[]) useNewMappingFor(STRING, ENUM)} instead.
+ */
+ @Deprecated(forRemoval = true)
public static void changeStringDefaultTypeToUtf8() {
- VALUE_DEFAULT_YQL_TYPES.put(FieldValueType.STRING, new ValueYqlTypeSelector(FieldValueType.STRING, PrimitiveTypeId.UTF8, null));
- VALUE_DEFAULT_YQL_TYPES.put(FieldValueType.ENUM, new ValueYqlTypeSelector(FieldValueType.ENUM, PrimitiveTypeId.UTF8, null));
+ useRecommendedMappingFor(FieldValueType.STRING, FieldValueType.ENUM);
}
+ /**
+ * @deprecated This method has a misleading name. Call {@link #useLegacyMappingFor(FieldValueType[]) useLegacyMappingFor(STRING, ENUM)} instead.
+ */
+ @Deprecated(forRemoval = true)
public static void resetStringDefaultTypeToDefaults() {
- VALUE_DEFAULT_YQL_TYPES.put(FieldValueType.STRING, new ValueYqlTypeSelector(FieldValueType.STRING, PrimitiveTypeId.STRING, null));
- VALUE_DEFAULT_YQL_TYPES.put(FieldValueType.ENUM, new ValueYqlTypeSelector(FieldValueType.ENUM, PrimitiveTypeId.STRING, null));
+ useLegacyMappingFor(FieldValueType.STRING, FieldValueType.ENUM);
+ }
+
+ /**
+ * Uses the legacy (YOJ 1.0.x) field value type ↔ YDB column type mapping for the specified field value type(s).
+ * If you need to support legacy applications, call {@code useLegacyMappingFor(STRING, ENUM, TIMESTAMP)} before using
+ * any YOJ features.
+ *
+ * @deprecated We STRONGLY advise against using the legacy mapping in new projects.
+ * Please call {@link #useRecommendedMappingFor(FieldValueType...) useNewMappingFor(STRING, ENUM, TIMESTAMP)} instead,
+ * and annotate custom-mapped columns with {@link Column @Column} where a different mapping is desired.
+ *
+ * @param fieldValueTypes field value types to use legacy mapping for
+ */
+ @Deprecated
+ public static void useLegacyMappingFor(FieldValueType... fieldValueTypes) {
+ for (var fvt : fieldValueTypes) {
+ switch (fvt) {
+ case STRING, ENUM -> VALUE_DEFAULT_YQL_TYPES.put(fvt, new ValueYqlTypeSelector(fvt, PrimitiveTypeId.STRING, null));
+ case TIMESTAMP -> {
+ var selector = new YqlTypeSelector(Instant.class, PrimitiveTypeId.INT64, null);
+ JAVA_DEFAULT_YQL_TYPES.put(Instant.class, selector);
+ YQL_TYPES.put(selector, new YqlPrimitiveType(Instant.class, PrimitiveTypeId.INT64, INSTANT_SETTER, INSTANT_GETTER));
+ }
+ default -> throw new IllegalArgumentException("There is no legacy mapping for field value type: " + fvt);
+ }
+ }
+ }
+
+ /**
+ * Uses the recommended field value type ↔ YDB column type mapping for the specified field value type(s).
+ *
+ * In new projects, we STRONGLY advise that you call {@code useNewMappingFor(STRING, ENUM, TIMESTAMP)}
+ * before using any YOJ features. This will eventually become the default mapping, and the call will become a no-op and
+ * mighe even be removed.
+ *
+ * @param fieldValueTypes field value types to use the new mapping for
+ */
+ @ExperimentalApi(issue = "https://github.com/ydb-platform/yoj-project/issues/20")
+ public static void useRecommendedMappingFor(FieldValueType... fieldValueTypes) {
+ for (var fvt : fieldValueTypes) {
+ switch (fvt) {
+ case STRING, ENUM -> VALUE_DEFAULT_YQL_TYPES.put(fvt, new ValueYqlTypeSelector(fvt, PrimitiveTypeId.UTF8, null));
+ case TIMESTAMP -> {
+ var selector = new YqlTypeSelector(Instant.class, PrimitiveTypeId.TIMESTAMP, null);
+ JAVA_DEFAULT_YQL_TYPES.put(Instant.class, selector);
+ YQL_TYPES.put(selector, new YqlPrimitiveType(Instant.class, PrimitiveTypeId.TIMESTAMP, TIMESTAMP_SETTER, TIMESTAMP_GETTER));
+ }
+ default -> throw new IllegalArgumentException("There is no new mapping for field value type: " + fvt);
+ }
+ }
}
/**
diff --git a/repository-ydb-v2/src/test/java/tech/ydb/yoj/repository/ydb/yql/YqlTypeAllTypesTest.java b/repository-ydb-v2/src/test/java/tech/ydb/yoj/repository/ydb/yql/YqlTypeAllTypesLegacyMappingTest.java
similarity index 70%
rename from repository-ydb-v2/src/test/java/tech/ydb/yoj/repository/ydb/yql/YqlTypeAllTypesTest.java
rename to repository-ydb-v2/src/test/java/tech/ydb/yoj/repository/ydb/yql/YqlTypeAllTypesLegacyMappingTest.java
index e32c41fa..c560f516 100644
--- a/repository-ydb-v2/src/test/java/tech/ydb/yoj/repository/ydb/yql/YqlTypeAllTypesTest.java
+++ b/repository-ydb-v2/src/test/java/tech/ydb/yoj/repository/ydb/yql/YqlTypeAllTypesLegacyMappingTest.java
@@ -4,7 +4,7 @@
import com.fasterxml.jackson.databind.node.JsonNodeFactory;
import com.fasterxml.jackson.databind.node.ObjectNode;
import lombok.AllArgsConstructor;
-import lombok.SneakyThrows;
+import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@@ -18,7 +18,6 @@
import tech.ydb.yoj.repository.db.common.CommonConverters;
import tech.ydb.yoj.repository.db.json.JacksonJsonConverter;
-import java.lang.reflect.Type;
import java.time.Duration;
import java.time.Instant;
import java.util.Collection;
@@ -27,10 +26,12 @@
import java.util.UUID;
import static org.assertj.core.api.Assertions.assertThat;
-import static org.junit.Assume.assumeTrue;
+import static tech.ydb.yoj.databind.FieldValueType.ENUM;
+import static tech.ydb.yoj.databind.FieldValueType.STRING;
+import static tech.ydb.yoj.databind.FieldValueType.TIMESTAMP;
@RunWith(Parameterized.class)
-public class YqlTypeAllTypesTest {
+public class YqlTypeAllTypesLegacyMappingTest {
static {
FieldValueType.registerStringValueType(UUID.class);
CommonConverters.defineJsonConverter(JacksonJsonConverter.getDefault());
@@ -39,6 +40,11 @@ public class YqlTypeAllTypesTest {
private static final Map OBJECT_VALUE = Map.of("string", "Unnamed", "number", 11, "boolean", true);
private static final TestSchema SCHEMA = new TestSchema(TestFields.class);
+ @BeforeClass
+ public static void setUp() {
+ YqlPrimitiveType.useLegacyMappingFor(STRING, ENUM, TIMESTAMP);
+ }
+
@Parameters
public static Collection