Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package tech.ydb.yoj.databind;

import com.google.common.base.Preconditions;
import com.google.common.reflect.TypeToken;
import lombok.NonNull;
import tech.ydb.yoj.ExperimentalApi;
import tech.ydb.yoj.InternalApi;
Expand All @@ -10,6 +9,7 @@
import tech.ydb.yoj.databind.schema.CustomConverterException;
import tech.ydb.yoj.databind.schema.CustomValueTypeInfo;
import tech.ydb.yoj.databind.schema.Schema.JavaField;
import tech.ydb.yoj.databind.schema.reflect.Types;
import tech.ydb.yoj.util.lang.Annotations;

import javax.annotation.Nullable;
Expand Down Expand Up @@ -71,7 +71,7 @@ private static <J, C extends Comparable<? super C>> ValueConverter<J, C> createC
public static <J, C extends Comparable<? super C>> CustomValueTypeInfo<J, C> getCustomValueTypeInfo(
@NonNull Type type, @Nullable Column columnAnnotation
) {
Class<?> rawType = type instanceof Class<?> ? (Class<?>) type : TypeToken.of(type).getRawType();
Class<?> rawType = Types.getRawType(type);
CustomValueType cvt = getCustomValueType(rawType, columnAnnotation);
if (cvt == null) {
return null;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
package tech.ydb.yoj.databind.expression.values;

import com.google.common.reflect.TypeToken;
import lombok.NonNull;
import tech.ydb.yoj.databind.FieldValueType;
import tech.ydb.yoj.databind.expression.IllegalExpressionException.FieldTypeError.StringFieldExpected;
import tech.ydb.yoj.databind.expression.IllegalExpressionException.FieldTypeError.UnknownEnumConstant;
import tech.ydb.yoj.databind.expression.IllegalExpressionException.FieldTypeError.UuidFieldExpected;
import tech.ydb.yoj.databind.schema.reflect.Types;

import java.lang.reflect.Type;
import java.util.Optional;
Expand All @@ -23,7 +23,7 @@ public Optional<Comparable<?>> getComparableByType(Type fieldType, FieldValueTyp
return switch (valueType) {
case STRING -> Optional.of(str);
case ENUM -> {
@SuppressWarnings({"rawtypes", "unchecked"}) var enumType = (Class<Enum>) TypeToken.of(fieldType).getRawType();
@SuppressWarnings({"rawtypes", "unchecked"}) var enumType = (Class<Enum>) Types.getRawType(fieldType);
@SuppressWarnings("unchecked") var enumValue = (Comparable<?>) Enum.valueOf(enumType, str);
yield Optional.of(enumValue);
}
Expand All @@ -45,7 +45,7 @@ public Optional<Comparable<?>> getComparableByType(Type fieldType, FieldValueTyp
public ValidationResult isValidValueOfType(Type fieldType, FieldValueType valueType) {
return switch (valueType) {
case STRING -> validFieldValue();
case ENUM -> enumHasConstant(TypeToken.of(fieldType).getRawType(), str)
case ENUM -> enumHasConstant(Types.getRawType(fieldType), str)
? validFieldValue()
: invalidFieldValue(p -> new UnknownEnumConstant(p, str), p -> format("Unknown enum constant for field \"%s\": \"%s\"", p, str));
case UUID -> isValidUuid()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package tech.ydb.yoj.databind.schema.reflect;

import com.google.common.base.Preconditions;
import com.google.common.reflect.TypeToken;
import kotlin.jvm.JvmClassMappingKt;
import kotlin.reflect.KClass;
import kotlin.reflect.KClassifier;
Expand All @@ -22,26 +21,26 @@
private final KProperty1.Getter<?, ?> getter;

public KotlinDataClassComponent(Reflector reflector, String name, KProperty1<?, ?> property) {
super(reflector, name, genericType(property), rawType(property), field(property));
super(reflector, name, genericJavaType(property), rawJavaClass(property), field(property));

this.getter = property.getGetter();
KCallablesJvm.setAccessible(this.getter, true);
}

private static Type genericType(KProperty1<?, ?> property) {
private static Type genericJavaType(KProperty1<?, ?> property) {
return ReflectJvmMapping.getJavaType(property.getReturnType());
}

private static Class<?> rawType(KProperty1<?, ?> property) {
Type genericType = genericType(property);
private static Class<?> rawJavaClass(KProperty1<?, ?> property) {
Type javaType = genericJavaType(property);

KType kPropertyType = property.getReturnType();
KClassifier kClassifier = kPropertyType.getClassifier();
if (kClassifier instanceof KClass<?> kClass) {
return JvmClassMappingKt.getJavaClass(kClass);
} else {
// fallback to Guava's TypeToken if kotlin-reflect returns unpredictable results ;-)
return TypeToken.of(genericType).getRawType();
return Types.getRawType(javaType);
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package tech.ydb.yoj.databind.schema.reflect;

import com.google.common.reflect.TypeToken;
import lombok.NonNull;
import tech.ydb.yoj.databind.FieldValueType;
import tech.ydb.yoj.databind.schema.configuration.SchemaRegistry;
Expand Down Expand Up @@ -49,7 +48,7 @@ public ReflectType<?> reflectFieldType(Type genericType, FieldValueType bindingT
}

private ReflectType<?> reflectFor(Type type, FieldValueType fvt) {
Class<?> rawType = type instanceof Class<?> clazz ? clazz : TypeToken.of(type).getRawType();
Class<?> rawType = Types.getRawType(type);
for (TypeFactory m : matchers) {
if (m.matches(rawType, fvt)) {
return m.create(this, rawType, fvt);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package tech.ydb.yoj.databind.schema.reflect;

import com.google.common.reflect.TypeToken;
import lombok.NonNull;
import tech.ydb.yoj.InternalApi;

import java.lang.reflect.Type;

@InternalApi
public final class Types {
private Types() {
}

@NonNull
public static Class<?> getRawType(@NonNull Type genericType) {
return genericType instanceof Class<?> clazz ? clazz : TypeToken.of(genericType).getRawType();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -175,8 +175,8 @@ public TableDescriptor<T> getTableDescriptor() {

@Override
public T find(Entity.Id<T> id) {
if (id.isPartial()) {
throw new IllegalArgumentException("Cannot use partial id in find method");
if (TableQueryImpl.isPartialId(id, schema)) {
throw new IllegalArgumentException("Cannot use partial ID in Table.find(ID) method");
}
return transaction.getTransactionLocal().firstLevelCache(tableDescriptor).get(id, __ -> {
markKeyRead(id);
Expand All @@ -192,8 +192,8 @@ public <ID extends Entity.Id<T>> List<T> find(Set<ID> ids) {

@Override
public <V extends View> V find(Class<V> viewType, Entity.Id<T> id) {
if (id.isPartial()) {
throw new IllegalArgumentException("Cannot use partial id in find method");
if (TableQueryImpl.isPartialId(id, schema)) {
throw new IllegalArgumentException("Cannot use partial ID in Table.find(Class<View>, ID) method");
}

FirstLevelCache<T> cache = transaction.getTransactionLocal().firstLevelCache(tableDescriptor);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import com.google.common.collect.Sets;
import com.google.common.reflect.TypeToken;
import lombok.NonNull;
import tech.ydb.yoj.DeprecationWarnings;
import tech.ydb.yoj.InternalApi;
import tech.ydb.yoj.databind.expression.FilterExpression;
import tech.ydb.yoj.databind.expression.OrderExpression;
Expand Down Expand Up @@ -77,11 +78,20 @@ public YdbTable(Class<T> type, QueryExecutor executor) {
this.tableDescriptor = TableDescriptor.from(schema);
}

/**
* @deprecated This {@code YdbTable} constructor uses reflection tricks to determine table entity type.
* It will be removed in YOJ 2.7.0.
*/
@Deprecated(forRemoval = true)
protected YdbTable(QueryExecutor executor) {
this.type = resolveEntityType();
this.executor = new CheckingQueryExecutor(executor);
this.schema = EntitySchema.of(type);
this.tableDescriptor = TableDescriptor.from(schema);

DeprecationWarnings.warnOnce("new YdbTable(QueryExecutor)[" + type.getName() + "]",
"new YdbTable(QueryExecutor) constructor will be removed in YOJ 2.7.0. Please use the new YdbTable(TableDescriptor, QueryExecutor) "
+ "or the new YdbTable(TableDescriptor, QueryExecutor) constructor instead");
}

public YdbTable(TableDescriptor<T> tableDescriptor, QueryExecutor executor) {
Expand Down Expand Up @@ -110,6 +120,7 @@ protected final QueryExecutor getExecutor() {
return executor;
}

@Deprecated(forRemoval = true)
@SuppressWarnings("unchecked")
private Class<T> resolveEntityType() {
return (Class<T>) (new TypeToken<T>(getClass()) {
Expand Down Expand Up @@ -261,7 +272,7 @@ private <K, V> Stream<V> readTableStream(ReadTableMapper<K, V> mapper, ReadTable

@Override
public T find(Entity.Id<T> id) {
if (id.isPartial()) {
if (TableQueryImpl.isPartialId(id, schema)) {
throw new IllegalArgumentException("Cannot use partial id in find method");
}
return executor.getTransactionLocal().firstLevelCache(tableDescriptor).get(id, __ -> {
Expand Down Expand Up @@ -372,7 +383,7 @@ public <ID extends Id<T>> List<T> find(
if (ids.isEmpty()) {
return List.of();
}
var isPartialIdMode = ids.iterator().next().isPartial();
var isPartialIdMode = TableQueryImpl.isPartialId(ids.iterator().next(), schema);
List<T> found = postLoad(findUncached(ids, filter, orderBy, limit));
if (!isPartialIdMode && ids.size() > found.size()) {
Set<Id<T>> foundIds = found.stream().map(Entity::getId).collect(toSet());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import com.google.common.reflect.TypeToken;
import lombok.AccessLevel;
import lombok.Getter;
import tech.ydb.yoj.DeprecationWarnings;
import tech.ydb.yoj.ExperimentalApi;
import tech.ydb.yoj.databind.expression.FilterExpression;
import tech.ydb.yoj.databind.expression.OrderExpression;
Expand All @@ -23,15 +24,30 @@ protected AbstractDelegatingTable(Table<T> target) {
this.target = target;
}

/**
* @deprecated This constructor uses reflection tricks to guess entity type for the table,
* and will be removed in YOJ 3.0.0.
* Please use {@link AbstractDelegatingTable#AbstractDelegatingTable(Class)} or
* {@link AbstractDelegatingTable#AbstractDelegatingTable(TableDescriptor)} instead.
*/
@Deprecated(forRemoval = true)
protected AbstractDelegatingTable() {
DeprecationWarnings.warnOnce("new AbstractDelegatingTable()",
"Nullary AbstractDelegatingTable constructor will be removed in YOJ 3.0.0. "
+ "Please use 1-arg Class<T> or TableDescriptor<T> constructor instead");
this.target = BaseDb.current(BaseDb.class).table(resolveEntityType());
}

protected AbstractDelegatingTable(Class<T> entityType) {
this.target = BaseDb.current(BaseDb.class).table(entityType);
}

@ExperimentalApi(issue = "https://github.com/ydb-platform/yoj-project/issues/32")
protected AbstractDelegatingTable(TableDescriptor<T> tableDescriptor) {
this.target = BaseDb.current(BaseDb.class).table(tableDescriptor);
}

@Deprecated(forRemoval = true)
@SuppressWarnings("unchecked")
private Class<T> resolveEntityType() {
return (Class<T>) (new TypeToken<T>(getClass()) {
Expand Down
22 changes: 18 additions & 4 deletions repository/src/main/java/tech/ydb/yoj/repository/db/Entity.java
Original file line number Diff line number Diff line change
Expand Up @@ -60,17 +60,31 @@ default <EXCEPTION extends Exception> E resolve(
return Tx.Current.get().getRepositoryTransaction().table(getType()).find(this, throwIfAbsent);
}

/**
* @deprecated This method will be removed in YOJ 3.0.0. Please stop using it.
*/
@SuppressWarnings("unchecked")
@Deprecated(forRemoval = true)
default Class<E> getType() {
DeprecationWarnings.warnOnce(
"Entity.Id.getType()",
"You are using Entity.Id.getType() which will be removed in YOJ 3.0.0. Please stop using this method"
);
return (Class<E>) new TypeToken<E>(getClass()) {
}.getRawType();
}

/**
* @deprecated This method will be removed in YOJ 3.0.0. Please use other ways to check if ID is partial
* (i.e., has some of its trailing components set to {@code null} to implicitly indicate an <em>ID range</em>.)
*/
@Deprecated(forRemoval = true)
default boolean isPartial() {
var schema = EntitySchema.of(getType()).getIdSchema();
var columns = schema.flattenFields();
var nonNullFields = schema.flatten(this);
return columns.size() > nonNullFields.size();
DeprecationWarnings.warnOnce(
"Entity.Id.isPartial()",
"You are using Entity.Id.isPartial() which will be removed in YOJ 3.0.0. Please use other ways to check if ID represents a range"
);
return TableQueryImpl.isPartialId(this, EntitySchema.of(getType()));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ public static <E extends Entity<E>, ID extends Entity.Id<E>> List<E> find(
}

var orderBy = EntityExpressions.defaultOrder(table.getType());
var isPartialIdMode = ids.iterator().next().isPartial();
var isPartialIdMode = TableQueryImpl.isPartialId(ids.iterator().next(), table.getSchema());

var foundInCache = ids.stream()
.filter(cache::containsKey)
Expand Down Expand Up @@ -82,4 +82,11 @@ public static <E extends Entity<E>> TableQueryBuilder<E> toQueryBuilder(Table<E>
.offset(request.getOffset())
.limit(request.getPageSize() + 1);
}

public static <E extends Entity<E>, ID extends Entity.Id<E>> boolean isPartialId(ID id, EntitySchema<E> schema) {
var idSchema = schema.getIdSchema();
var columns = idSchema.flattenFields();
var nonNullFields = idSchema.flatten(id);
return columns.size() > nonNullFields.size();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import lombok.Value;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import tech.ydb.yoj.DeprecationWarnings;
import tech.ydb.yoj.repository.db.exception.QueryCancelledException;
import tech.ydb.yoj.repository.db.exception.QueryInterruptedException;
import tech.ydb.yoj.util.lang.Interrupts;
Expand Down Expand Up @@ -87,11 +88,18 @@ public DbValueUpdater(@NonNull ThreadFactoryCreator threadFactorySupplier) {
this(DEFAULT_CACHE_TIMEOUT, DEFAULT_SHUTDOWN_TIMEOUT, DEFAULT_MAX_LAG, DEFAULT_MAX_READ_DURATION, threadFactorySupplier);
}

/**
* @deprecated This constructor uses reflection tricks to determine the name of the entity being updated.
* This constructor will be removed in YOJ 2.7.0.
*/
@Deprecated(forRemoval = true)
public DbValueUpdater(@NonNull Duration pollInterval, @NonNull Duration shutdownTimeout,
@NonNull Duration maxAge, @NonNull Duration maxReadDuration,
@NonNull ThreadFactoryCreator threadFactorySupplier) {
this(pollInterval, shutdownTimeout, maxAge, maxReadDuration, vu -> new TypeToken<V>(vu.getClass()) {
}.getRawType().getSimpleName(), threadFactorySupplier);
DeprecationWarnings.warnOnce("DbValueUpdater/TypeToken",
"DbValueUpdater constructor without explicit `name` will be removed in YOJ 2.7.0. Please use the constructor with explicit name");
}

public DbValueUpdater(@NonNull String name,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import tech.ydb.yoj.InternalApi;
import tech.ydb.yoj.repository.db.Entity;
import tech.ydb.yoj.repository.db.RepositoryTransaction;
import tech.ydb.yoj.repository.db.Table;

import java.util.LinkedHashMap;
import java.util.List;
Expand Down Expand Up @@ -60,21 +61,28 @@ public void applyProjectionChanges(RepositoryTransaction transaction) {

oldProjections.values().stream()
.filter(e -> !newProjections.containsKey(e.getId()))
.forEach(e -> deleteEntity(transaction, e.getId()));
.forEach(e -> deleteEntity(transaction, e));
newProjections.values().stream()
.filter(e -> !e.equals(oldProjections.get(e.getId())))
.forEach(e -> saveEntity(transaction, e));
}

private <T extends Entity<T>> void deleteEntity(RepositoryTransaction transaction, Entity.Id<T> entityId) {
transaction.table(entityId.getType()).delete(entityId);
private <T extends Entity<T>> void deleteEntity(RepositoryTransaction transaction, Entity<T> entity) {
table(transaction, entity).delete(entity.getId());
}

private <T extends Entity<T>> void saveEntity(RepositoryTransaction transaction, Entity<T> entity) {
@SuppressWarnings("unchecked")
T castedEntity = (T) entity;

transaction.table(entity.getId().getType()).save(castedEntity);
table(transaction, entity).save(castedEntity);
}

private <T extends Entity<T>> Table<T> table(RepositoryTransaction transaction, Entity<T> entity) {
@SuppressWarnings("unchecked")
Class<T> entityType = (Class<T>) entity.getClass();

return transaction.table(entityType);
}

private Entity<?> mergeOldProjections(Entity<?> p1, Entity<?> p2) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
import static org.junit.Assert.assertTrue;

public class PojoEntityTest {

@Value
private static class Ent implements Entity<Ent> {
@NonNull
Expand All @@ -24,10 +23,11 @@ private static class Id implements Entity.Id<Ent> {

@Test
public void testPartialId() {
var schema = EntitySchema.of(Ent.class);
var completeId = new Ent.Id("a", "b");
var partialId = new Ent.Id("a", null);

assertTrue(partialId.isPartial());
assertFalse(completeId.isPartial());
assertTrue(TableQueryImpl.isPartialId(partialId, schema));
assertFalse(TableQueryImpl.isPartialId(completeId, schema));
}
}
Loading
Loading