Skip to content

Commit 0a24410

Browse files
committed
noticket: Get rid of most calls to Entity.Id.getType() and use better and simpler logic for "default sort order"
...because `Entity.Id.getType()` uses `TypeToken` which is **extremely slow** in tight loops when run via Bazel JUnit5 tests, which have a non-null `SecurityManager` installed (`SecurityManager` is to prevent `System.exit()` calls from tests, but it happens to check every reflective access made by `TypeToken`, of which there are **plenty**.)
1 parent 6704aac commit 0a24410

File tree

26 files changed

+470
-265
lines changed

26 files changed

+470
-265
lines changed

databind/src/main/java/tech/ydb/yoj/InternalApi.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
* Annotates internal YOJ implementation details (classes, interfaces, methods, fields, constants, etc.) that need to be
1919
* {@code public} to be used from different and/or multiple packages, but are not stable even across minor YOJ releases.
2020
* <p>Non-{@code public} (e.g., package-private) classes, interfaces, methods, fields, constants etc. in YOJ are assumed
21-
* to be internal implementation details regardless of the presence of an {@code &#64;InternalApi} annotation on them.
21+
* to be internal implementation details regardless of the presence of an {@code @InternalApi} annotation on them.
2222
*/
2323
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, ANNOTATION_TYPE, PACKAGE, MODULE, RECORD_COMPONENT})
2424
@Retention(SOURCE)

databind/src/main/java/tech/ydb/yoj/databind/schema/reflect/StdReflector.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ public ReflectType<?> reflectFieldType(Type genericType, FieldValueType bindingT
4949
}
5050

5151
private ReflectType<?> reflectFor(Type type, FieldValueType fvt) {
52-
Class<?> rawType = TypeToken.of(type).getRawType();
52+
Class<?> rawType = type instanceof Class<?> clazz ? clazz : TypeToken.of(type).getRawType();
5353
for (TypeFactory m : matchers) {
5454
if (m.matches(rawType, fvt)) {
5555
return m.create(this, rawType, fvt);

repository-inmemory/src/main/java/tech/ydb/yoj/repository/test/inmemory/InMemoryDataShard.java

+15-9
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
import tech.ydb.yoj.databind.schema.Schema;
44
import tech.ydb.yoj.repository.db.Entity;
5-
import tech.ydb.yoj.repository.db.EntityIdSchema;
65
import tech.ydb.yoj.repository.db.EntitySchema;
76
import tech.ydb.yoj.repository.db.Range;
87
import tech.ydb.yoj.repository.db.Table;
@@ -18,18 +17,19 @@
1817
import java.util.List;
1918
import java.util.Map;
2019
import java.util.Set;
20+
import java.util.SortedMap;
2121
import java.util.TreeMap;
2222

2323
/*package*/ final class InMemoryDataShard<T extends Entity<T>> {
2424
private final TableDescriptor<T> tableDescriptor;
2525
private final EntitySchema<T> schema;
26-
private final TreeMap<Entity.Id<T>, InMemoryEntityLine> entityLines;
26+
private final SortedMap<Entity.Id<T>, InMemoryEntityLine> entityLines;
2727
private final Map<Long, Set<Entity.Id<T>>> uncommited = new HashMap<>();
2828

2929
private InMemoryDataShard(
3030
TableDescriptor<T> tableDescriptor,
3131
EntitySchema<T> schema,
32-
TreeMap<Entity.Id<T>, InMemoryEntityLine> entityLines
32+
SortedMap<Entity.Id<T>, InMemoryEntityLine> entityLines
3333
) {
3434
this.tableDescriptor = tableDescriptor;
3535
this.schema = schema;
@@ -39,18 +39,24 @@ private InMemoryDataShard(
3939
public InMemoryDataShard(TableDescriptor<T> tableDescriptor) {
4040
this(
4141
tableDescriptor,
42-
EntitySchema.of(tableDescriptor.entityType()),
43-
createEmptyLines(tableDescriptor.entityType())
42+
EntitySchema.of(tableDescriptor.entityType())
4443
);
4544
}
4645

47-
private static <T extends Entity<T>> TreeMap<Entity.Id<T>, InMemoryEntityLine> createEmptyLines(Class<T> type) {
48-
return new TreeMap<>(EntityIdSchema.getIdComparator(type));
46+
private InMemoryDataShard(
47+
TableDescriptor<T> tableDescriptor,
48+
EntitySchema<T> schema
49+
) {
50+
this(tableDescriptor, schema, createEmptyLines(schema));
51+
}
52+
53+
private static <T extends Entity<T>> SortedMap<Entity.Id<T>, InMemoryEntityLine> createEmptyLines(EntitySchema<T> schema) {
54+
return new TreeMap<>(schema.getIdSchema());
4955
}
5056

5157
public synchronized InMemoryDataShard<T> createSnapshot() {
52-
TreeMap<Entity.Id<T>, InMemoryEntityLine> snapshotLines = createEmptyLines(tableDescriptor.entityType());
53-
for (Map.Entry<Entity.Id<T>, InMemoryEntityLine> entry : entityLines.entrySet()) {
58+
var snapshotLines = createEmptyLines(schema);
59+
for (var entry : entityLines.entrySet()) {
5460
snapshotLines.put(entry.getKey(), entry.getValue().createSnapshot());
5561
}
5662
return new InMemoryDataShard<>(tableDescriptor, schema, snapshotLines);

repository-inmemory/src/main/java/tech/ydb/yoj/repository/test/inmemory/InMemoryRepositoryTransaction.java

+8-2
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import com.google.common.base.Stopwatch;
44
import com.google.common.collect.Iterables;
55
import lombok.Getter;
6+
import tech.ydb.yoj.DeprecationWarnings;
67
import tech.ydb.yoj.repository.BaseDb;
78
import tech.ydb.yoj.repository.db.Entity;
89
import tech.ydb.yoj.repository.db.RepositoryTransaction;
@@ -57,16 +58,21 @@ private long getVersion() {
5758

5859
@Override
5960
public <T extends Entity<T>> Table<T> table(Class<T> c) {
60-
return new InMemoryTable<>(getMemory(c));
61+
return new InMemoryTable<>(this, c);
6162
}
6263

6364
@Override
6465
public <T extends Entity<T>> Table<T> table(TableDescriptor<T> tableDescriptor) {
6566
return new InMemoryTable<>(this, tableDescriptor);
6667
}
6768

68-
@Deprecated // use other constructor of InMemoryTable
69+
/**
70+
* @deprecated {@code DbMemory} and this method will be removed in YOJ 3.0.0.
71+
*/
72+
@Deprecated(forRemoval = true)
6973
public final <T extends Entity<T>> InMemoryTable.DbMemory<T> getMemory(Class<T> c) {
74+
DeprecationWarnings.warnOnce("InMemoryTable.getMemory",
75+
"InMemoryTable.getMemory(Class<T>) will be removed in YOJ 3.0.0. Please stop using this method");
7076
return new InMemoryTable.DbMemory<>(c, this);
7177
}
7278

repository-inmemory/src/main/java/tech/ydb/yoj/repository/test/inmemory/InMemoryTable.java

+53-20
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import com.google.common.base.Preconditions;
44
import com.google.common.collect.Iterables;
55
import com.google.common.collect.Sets;
6+
import tech.ydb.yoj.DeprecationWarnings;
67
import tech.ydb.yoj.databind.expression.FilterExpression;
78
import tech.ydb.yoj.databind.expression.OrderExpression;
89
import tech.ydb.yoj.databind.schema.ObjectSchema;
@@ -14,6 +15,7 @@
1415
import tech.ydb.yoj.repository.db.Range;
1516
import tech.ydb.yoj.repository.db.Table;
1617
import tech.ydb.yoj.repository.db.TableDescriptor;
18+
import tech.ydb.yoj.repository.db.TableQueryBuilder;
1719
import tech.ydb.yoj.repository.db.TableQueryImpl;
1820
import tech.ydb.yoj.repository.db.ViewSchema;
1921
import tech.ydb.yoj.repository.db.cache.FirstLevelCache;
@@ -32,19 +34,28 @@
3234
import static java.util.stream.Collectors.toList;
3335
import static java.util.stream.Collectors.toUnmodifiableMap;
3436
import static java.util.stream.Collectors.toUnmodifiableSet;
37+
import static tech.ydb.yoj.repository.db.TableQueryImpl.getEntityByIdComparator;
3538

3639
public class InMemoryTable<T extends Entity<T>> implements Table<T> {
3740
private final EntitySchema<T> schema;
3841
private final TableDescriptor<T> tableDescriptor;
3942
private final InMemoryRepositoryTransaction transaction;
4043

41-
@Deprecated // Don't use DbMemory, use other constructor instead
44+
/**
45+
* @deprecated {@code DbMemory} and this constructor will be removed in YOJ 3.0.0.
46+
* Please use other constructors instead.
47+
*/
48+
@Deprecated(forRemoval = true)
4249
public InMemoryTable(DbMemory<T> memory) {
4350
this(memory.transaction(), memory.type());
51+
DeprecationWarnings.warnOnce("new InMemoryTable(DbMemory)",
52+
"Please do not use the InMemoryTable(DbMemory<T>) constructor, it will be removed in YOJ 3.0.0");
4453
}
4554

4655
public InMemoryTable(InMemoryRepositoryTransaction transaction, Class<T> type) {
47-
this(transaction, TableDescriptor.from(EntitySchema.of(type)));
56+
this.schema = EntitySchema.of(type);
57+
this.tableDescriptor = TableDescriptor.from(schema);
58+
this.transaction = transaction;
4859
}
4960

5061
public InMemoryTable(InMemoryRepositoryTransaction transaction, TableDescriptor<T> tableDescriptor) {
@@ -53,6 +64,11 @@ public InMemoryTable(InMemoryRepositoryTransaction transaction, TableDescriptor<
5364
this.transaction = transaction;
5465
}
5566

67+
@Override
68+
public TableQueryBuilder<T> query() {
69+
return new TableQueryBuilder<>(this, schema);
70+
}
71+
5672
@Override
5773
public List<T> findAll() {
5874
transaction.getWatcher().markTableRead(tableDescriptor);
@@ -103,7 +119,7 @@ public List<T> find(
103119
@Nullable Long offset
104120
) {
105121
// NOTE: InMemoryTable doesn't handle index.
106-
return InMemoryQueries.find(() -> findAll().stream(), filter, orderBy, limit, offset);
122+
return TableQueryImpl.find(() -> findAll().stream(), schema, filter, orderBy, limit, offset);
107123
}
108124

109125
@Override
@@ -173,8 +189,8 @@ public TableDescriptor<T> getTableDescriptor() {
173189

174190
@Override
175191
public T find(Entity.Id<T> id) {
176-
if (id.isPartial()) {
177-
throw new IllegalArgumentException("Cannot use partial id in find method");
192+
if (TableQueryImpl.isPartialId(id, schema)) {
193+
throw new IllegalArgumentException("Cannot use partial ID in Table.find() method");
178194
}
179195
return transaction.getTransactionLocal().firstLevelCache(tableDescriptor).get(id, __ -> {
180196
markKeyRead(id);
@@ -185,13 +201,13 @@ public T find(Entity.Id<T> id) {
185201

186202
@Override
187203
public <ID extends Entity.Id<T>> List<T> find(Set<ID> ids) {
188-
return TableQueryImpl.find(this, getFirstLevelCache(), ids);
204+
return TableQueryImpl.find(this, schema, getFirstLevelCache(), ids);
189205
}
190206

191207
@Override
192208
public <V extends View> V find(Class<V> viewType, Entity.Id<T> id) {
193-
if (id.isPartial()) {
194-
throw new IllegalArgumentException("Cannot use partial id in find method");
209+
if (TableQueryImpl.isPartialId(id, schema)) {
210+
throw new IllegalArgumentException("Cannot use partial ID in Table.find() method");
195211
}
196212

197213
FirstLevelCache<T> cache = transaction.getTransactionLocal().firstLevelCache(tableDescriptor);
@@ -208,10 +224,13 @@ public <V extends View> V find(Class<V> viewType, Entity.Id<T> id) {
208224
@Override
209225
@SuppressWarnings("unchecked")
210226
public <ID extends Entity.Id<T>> List<T> find(Range<ID> range) {
211-
transaction.getWatcher().markRangeRead(tableDescriptor, range);
227+
Preconditions.checkArgument(range.getType() == schema.getIdSchema(),
228+
"ID schema mismatch: Range was constructed with a different ID schema than the YdbTable");
229+
230+
markRangeRead(range);
212231
return findAll0().stream()
213232
.filter(e -> range.contains((ID) e.getId()))
214-
.sorted(EntityIdSchema.SORT_ENTITY_BY_ID)
233+
.sorted(getEntityByIdComparator(schema))
215234
.collect(toList());
216235
}
217236

@@ -232,7 +251,7 @@ public <V extends View, ID extends Entity.Id<T>> List<V> find(Class<V> viewType,
232251

233252
@Override
234253
public <V extends View, ID extends Entity.Id<T>> List<V> find(Class<V> viewType, Set<ID> ids) {
235-
return find(viewType, ids, null, EntityExpressions.defaultOrder(getType()), null);
254+
return find(viewType, ids, null, EntityExpressions.defaultOrder(schema), null);
236255
}
237256

238257
@Override
@@ -404,9 +423,10 @@ private boolean isPrefixedFields(List<String> keyFields, Set<String> fields) {
404423

405424
private <ID extends Entity.Id<T>> void markKeyRead(ID id) {
406425
EntityIdSchema<Entity.Id<T>> idSchema = schema.getIdSchema();
407-
if (idSchema.flattenFieldNames().size() != idSchema.flatten(id).size()) {
426+
Map<String, Object> eqMap = idSchema.flatten(id);
427+
if (idSchema.flattenFieldNames().size() != eqMap.size()) {
408428
// Partial key, will throw error when not searching by PK prefix
409-
transaction.getWatcher().markRangeRead(tableDescriptor, Range.create(id, id));
429+
transaction.getWatcher().markRangeRead(tableDescriptor, Range.create(idSchema, eqMap));
410430
} else {
411431
transaction.getWatcher().markRowRead(tableDescriptor, id);
412432
}
@@ -475,7 +495,7 @@ public <ID extends Entity.Id<T>> Stream<T> streamPartial(ID partial, int batchSi
475495
Preconditions.checkArgument(1 <= batchSize && batchSize <= 5000,
476496
"batchSize must be in range [1, 5000], got %s", batchSize);
477497

478-
Range<ID> range = partial == null ? null : Range.create(partial);
498+
Range<ID> range = rangeForPartialId(partial);
479499
markRangeRead(range);
480500

481501
return streamPartial0(range);
@@ -508,20 +528,26 @@ public <ID extends Entity.Id<T>> Stream<ID> streamPartialIds(ID partial, int bat
508528
Preconditions.checkArgument(1 <= batchSize && batchSize <= 10000,
509529
"batchSize must be in range [1, 10000], got %s", batchSize);
510530

511-
Range<ID> range = partial == null ? null : Range.create(partial);
531+
Range<ID> range = rangeForPartialId(partial);
512532
markRangeRead(range);
513533

514534
return streamPartial0(range).map(e -> (ID) e.getId());
515535
}
516536

517-
private <ID extends Entity.Id<T>> void markRangeRead(Range<ID> range) {
537+
private <ID extends Entity.Id<T>> void markRangeRead(@Nullable Range<ID> range) {
518538
if (range == null) {
519539
transaction.getWatcher().markTableRead(tableDescriptor);
520540
} else {
521541
transaction.getWatcher().markRangeRead(tableDescriptor, range);
522542
}
523543
}
524544

545+
@Nullable
546+
private <ID extends Entity.Id<T>> Range<ID> rangeForPartialId(ID partial) {
547+
EntityIdSchema<ID> idSchema = schema.getIdSchema();
548+
return partial == null ? null : Range.create(idSchema, idSchema.flatten(partial));
549+
}
550+
525551
private <ID extends Entity.Id<T>> Stream<T> readTableStream(ReadTableParams<ID> params) {
526552
if (!transaction.getOptions().getIsolationLevel().isReadOnly()) {
527553
throw new IllegalTransactionIsolationLevelException("readTable", transaction.getOptions().getIsolationLevel());
@@ -533,7 +559,7 @@ private <ID extends Entity.Id<T>> Stream<T> readTableStream(ReadTableParams<ID>
533559
.stream()
534560
.filter(e -> readTableFilter(e, params));
535561
if (params.isOrdered()) {
536-
stream = stream.sorted(EntityIdSchema.SORT_ENTITY_BY_ID);
562+
stream = stream.sorted(getEntityByIdComparator(schema));
537563
}
538564
if (params.getRowLimit() > 0) {
539565
stream = stream.limit(params.getRowLimit());
@@ -542,18 +568,20 @@ private <ID extends Entity.Id<T>> Stream<T> readTableStream(ReadTableParams<ID>
542568
}
543569

544570
private <ID extends Entity.Id<T>> boolean readTableFilter(T e, ReadTableParams<ID> params) {
571+
EntityIdSchema<ID> idSchema = schema.getIdSchema();
572+
545573
@SuppressWarnings("unchecked")
546574
ID id = (ID) e.getId();
547575
ID from = params.getFromKey();
548576
if (from != null) {
549-
int compare = EntityIdSchema.ofEntity(id.getType()).compare(id, from);
577+
int compare = idSchema.compare(id, from);
550578
if (params.isFromInclusive() ? compare < 0 : compare <= 0) {
551579
return false;
552580
}
553581
}
554582
ID to = params.getToKey();
555583
if (to != null) {
556-
int compare = EntityIdSchema.ofEntity(id.getType()).compare(id, to);
584+
int compare = idSchema.compare(id, to);
557585
return params.isToInclusive() ? compare <= 0 : compare < 0;
558586
}
559587
return true;
@@ -587,7 +615,12 @@ private static <V extends Table.View, T extends Entity<T>> V toView(
587615
}
588616

589617

590-
@Deprecated // Legacy. Using only for creating InMemoryTable. Use constructor of InMemoryTable instead
618+
/**
619+
* @deprecated Legacy class, used only for creating {@code InMemoryTable}.
620+
* This class will be removed in YOJ 3.0.0.
621+
* Please use other constructors of {@code InMemoryTable} instead.
622+
*/
623+
@Deprecated(forRemoval = true)
591624
public record DbMemory<T extends Entity<T>>(
592625
Class<T> type,
593626
InMemoryRepositoryTransaction transaction

repository-inmemory/src/test/java/tech/ydb/yoj/repository/test/inmemory/TestInMemoryRepository.java

+3-3
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ public SupabubbleTable supabubbles() {
105105

106106
@Override
107107
public Supabubble2Table supabubbles2() {
108-
return new Supabubble2InMemoryTable(getMemory(Supabubble2.class));
108+
return new Supabubble2InMemoryTable(this, Supabubble2.class);
109109
}
110110

111111
@Override
@@ -145,8 +145,8 @@ public Table<MultiWrappedEntity2> multiWrappedEntities2() {
145145
}
146146

147147
private static class Supabubble2InMemoryTable extends InMemoryTable<Supabubble2> implements TestEntityOperations.Supabubble2Table {
148-
public Supabubble2InMemoryTable(DbMemory<Supabubble2> memory) {
149-
super(memory);
148+
public Supabubble2InMemoryTable(InMemoryRepositoryTransaction transaction, Class<Supabubble2> type) {
149+
super(transaction, type);
150150
}
151151
}
152152

0 commit comments

Comments
 (0)