Skip to content

Commit ae12f79

Browse files
committed
#77: Reimplement projections via listeners instead of TransactionLocal + Allow custom tables for projections
1 parent ac58941 commit ae12f79

File tree

20 files changed

+782
-250
lines changed

20 files changed

+782
-250
lines changed

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

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -82,11 +82,12 @@ public void commit() {
8282
throw new IllegalStateException("Transaction was invalidated. Commit isn't possible");
8383
}
8484
endTransaction("commit()", this::commitImpl);
85+
options.getRepositoryTransactionListeners().forEach(l -> l.onCommit(this));
8586
}
8687

8788
private void commitImpl() {
8889
try {
89-
transactionLocal.projectionCache().applyProjectionChanges(this);
90+
options.getRepositoryTransactionListeners().forEach(l -> l.onBeforeFlushWrites(this));
9091

9192
for (Runnable pendingWrite : pendingWrites) {
9293
pendingWrite.run();
@@ -102,6 +103,7 @@ private void commitImpl() {
102103
@Override
103104
public void rollback() {
104105
endTransaction("rollback()", this::rollbackImpl);
106+
options.getRepositoryTransactionListeners().forEach(l -> l.onRollback(this));
105107
}
106108

107109
private void rollbackImpl() {
@@ -149,7 +151,7 @@ final <T extends Entity<T>> void doInWriteTransaction(
149151
});
150152
if (options.isImmediateWrites()) {
151153
query.run();
152-
transactionLocal.projectionCache().applyProjectionChanges(this);
154+
options.getRepositoryTransactionListeners().forEach(l -> l.onImmediateWrite(this));
153155
} else {
154156
pendingWrites.add(query);
155157
}

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

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,8 @@ public void update(Entity.Id<T> id, Changeset changeset) {
9393

9494
transaction.getWatcher().markRowRead(tableDescriptor, id);
9595
transaction.doInWriteTransaction("update(" + id + ", " + changeset + ")", tableDescriptor, shard -> shard.update(id, patch));
96-
transaction.getTransactionLocal().firstLevelCache(tableDescriptor).remove(id);
96+
getFirstLevelCache().remove(id);
97+
transaction.getOptions().getEntityEventListeners().forEach(l -> l.onUpdate(tableDescriptor, id, changeset));
9798
}
9899

99100
@Override
@@ -178,7 +179,7 @@ public T find(Entity.Id<T> id) {
178179
if (id.isPartial()) {
179180
throw new IllegalArgumentException("Cannot use partial id in find method");
180181
}
181-
return transaction.getTransactionLocal().firstLevelCache(tableDescriptor).get(id, __ -> {
182+
return getFirstLevelCache().get(id, __ -> {
182183
markKeyRead(id);
183184
T entity = transaction.doInTransaction("find(" + id + ")", tableDescriptor, shard -> shard.find(id));
184185
return postLoad(entity);
@@ -196,7 +197,7 @@ public <V extends View> V find(Class<V> viewType, Entity.Id<T> id) {
196197
throw new IllegalArgumentException("Cannot use partial id in find method");
197198
}
198199

199-
FirstLevelCache<T> cache = transaction.getTransactionLocal().firstLevelCache(tableDescriptor);
200+
FirstLevelCache<T> cache = getFirstLevelCache();
200201
if (cache.containsKey(id)) {
201202
return cache.peek(id)
202203
.map(entity -> toView(viewType, schema, entity))
@@ -428,25 +429,25 @@ public T insert(T tt) {
428429
T t = tt.preSave();
429430
transaction.getWatcher().markRowRead(tableDescriptor, t.getId());
430431
transaction.doInWriteTransaction("insert(" + t + ")", tableDescriptor, shard -> shard.insert(t));
431-
transaction.getTransactionLocal().firstLevelCache(tableDescriptor).put(t);
432-
transaction.getTransactionLocal().projectionCache().save(t);
432+
getFirstLevelCache().put(t);
433+
transaction.getOptions().getEntityEventListeners().forEach(l -> l.onSave(tableDescriptor, t));
433434
return t;
434435
}
435436

436437
@Override
437438
public T save(T tt) {
438439
T t = tt.preSave();
439440
transaction.doInWriteTransaction("save(" + t + ")", tableDescriptor, shard -> shard.save(t));
440-
transaction.getTransactionLocal().firstLevelCache(tableDescriptor).put(t);
441-
transaction.getTransactionLocal().projectionCache().save(t);
441+
getFirstLevelCache().put(t);
442+
transaction.getOptions().getEntityEventListeners().forEach(l -> l.onSave(tableDescriptor, t));
442443
return t;
443444
}
444445

445446
@Override
446447
public void delete(Entity.Id<T> id) {
447448
transaction.doInWriteTransaction("delete(" + id + ")", tableDescriptor, shard -> shard.delete(id));
448-
transaction.getTransactionLocal().firstLevelCache(tableDescriptor).putEmpty(id);
449-
transaction.getTransactionLocal().projectionCache().delete(id);
449+
getFirstLevelCache().putEmpty(id);
450+
transaction.getOptions().getEntityEventListeners().forEach(l -> l.onDelete(tableDescriptor, id));
450451
}
451452

452453
@Override
@@ -575,8 +576,9 @@ public T postLoad(T entity) {
575576
return null;
576577
}
577578
T t = entity.postLoad();
578-
transaction.getTransactionLocal().firstLevelCache(tableDescriptor).put(t);
579-
transaction.getTransactionLocal().projectionCache().load(t);
579+
getFirstLevelCache().put(t);
580+
transaction.getOptions().getEntityEventListeners().forEach(l -> l.onLoad(tableDescriptor, t));
581+
580582
return t;
581583
}
582584

repository-test/src/main/java/tech/ydb/yoj/repository/test/RepositoryTest.java

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
import tech.ydb.yoj.repository.test.sample.model.BadlyWrappedEntity;
4747
import tech.ydb.yoj.repository.test.sample.model.BadlyWrappedEntity.BadStringValueWrapper;
4848
import tech.ydb.yoj.repository.test.sample.model.Book;
49+
import tech.ydb.yoj.repository.test.sample.model.BookProjectionsOldStyle;
4950
import tech.ydb.yoj.repository.test.sample.model.Bubble;
5051
import tech.ydb.yoj.repository.test.sample.model.BytePkEntity;
5152
import tech.ydb.yoj.repository.test.sample.model.Complex;
@@ -686,7 +687,7 @@ public void viewStreamAll() {
686687
}
687688
db.tx(() -> db.table(Book.class)
688689
.streamAll(Book.TitleViewId.class, 100)
689-
.forEach(titleView -> assertThat(titleView.getTitle()).isNotBlank()));
690+
.forEach(titleView -> assertThat(titleView.title()).isNotBlank()));
690691

691692
assertThat(db.tx(() -> db.table(Book.class).streamAll(Book.TitleViewId.class, 2)
692693
.limit(1).collect(toList())))
@@ -2188,6 +2189,57 @@ public void projections() {
21882189
.isEqualTo(0L);
21892190
}
21902191

2192+
@Test
2193+
public void projectionsOldStyle() {
2194+
db.tx(() -> {
2195+
db.table(BookProjectionsOldStyle.class).save(new BookProjectionsOldStyle(new BookProjectionsOldStyle.Id("1"), 1, "title1", List.of("author1")));
2196+
db.table(BookProjectionsOldStyle.class).save(new BookProjectionsOldStyle(new BookProjectionsOldStyle.Id("2"), 1, "title2", List.of("author2")));
2197+
db.table(BookProjectionsOldStyle.class).save(new BookProjectionsOldStyle(new BookProjectionsOldStyle.Id("3"), 1, null, List.of("author1", "author2")));
2198+
db.table(BookProjectionsOldStyle.class).save(new BookProjectionsOldStyle(new BookProjectionsOldStyle.Id("4"), 1, "title1", List.of()));
2199+
});
2200+
2201+
assertThat(db.tx(() -> db.table(BookProjectionsOldStyle.ByTitle.class).countAll()))
2202+
.isEqualTo(3L);
2203+
assertThat(db.tx(() -> db.table(BookProjectionsOldStyle.ByTitle.class).find(Range.create(new BookProjectionsOldStyle.ByTitle.Id("title1", null)))))
2204+
.hasSize(2);
2205+
assertThat(db.tx(() -> db.table(BookProjectionsOldStyle.ByTitle.class).find(Range.create(new BookProjectionsOldStyle.ByTitle.Id("title2", null)))))
2206+
.hasSize(1);
2207+
2208+
assertThat(db.tx(() -> db.table(BookProjectionsOldStyle.ByAuthor.class).countAll()))
2209+
.isEqualTo(4L);
2210+
assertThat(db.tx(() -> db.table(BookProjectionsOldStyle.ByAuthor.class).find(Range.create(new BookProjectionsOldStyle.ByAuthor.Id("author1", null)))))
2211+
.hasSize(2);
2212+
assertThat(db.tx(() -> db.table(BookProjectionsOldStyle.ByAuthor.class).find(Range.create(new BookProjectionsOldStyle.ByAuthor.Id("author2", null)))))
2213+
.hasSize(2);
2214+
2215+
db.tx(() -> {
2216+
db.table(BookProjectionsOldStyle.class).modifyIfPresent(new BookProjectionsOldStyle.Id("1"), b -> b.updateTitle("title2"));
2217+
db.table(BookProjectionsOldStyle.class).modifyIfPresent(new BookProjectionsOldStyle.Id("2"), b -> b.updateTitle(null));
2218+
db.table(BookProjectionsOldStyle.class).modifyIfPresent(new BookProjectionsOldStyle.Id("3"), b -> b.withAuthors(List.of("author2")));
2219+
db.table(BookProjectionsOldStyle.class).modifyIfPresent(new BookProjectionsOldStyle.Id("4"), b -> b.withAuthors(List.of("author1", "author2")));
2220+
});
2221+
2222+
assertThat(db.tx(() -> db.table(BookProjectionsOldStyle.ByTitle.class).countAll()))
2223+
.isEqualTo(2L);
2224+
assertThat(db.tx(() -> db.table(BookProjectionsOldStyle.ByTitle.class).find(Range.create(new BookProjectionsOldStyle.ByTitle.Id("title1", null)))))
2225+
.hasSize(1);
2226+
assertThat(db.tx(() -> db.table(BookProjectionsOldStyle.ByTitle.class).find(Range.create(new BookProjectionsOldStyle.ByTitle.Id("title2", null)))))
2227+
.hasSize(1);
2228+
2229+
assertThat(db.tx(() -> db.table(BookProjectionsOldStyle.ByAuthor.class).countAll()))
2230+
.isEqualTo(5L);
2231+
assertThat(db.tx(() -> db.table(BookProjectionsOldStyle.ByAuthor.class).find(Range.create(new BookProjectionsOldStyle.ByAuthor.Id("author1", null)))))
2232+
.hasSize(2);
2233+
assertThat(db.tx(() -> db.table(BookProjectionsOldStyle.ByAuthor.class).find(Range.create(new BookProjectionsOldStyle.ByAuthor.Id("author2", null)))))
2234+
.hasSize(3);
2235+
2236+
db.tx(() -> db.table(BookProjectionsOldStyle.class).findAll().forEach(b -> db.table(BookProjectionsOldStyle.class).delete(b.getId())));
2237+
assertThat(db.tx(() -> db.table(BookProjectionsOldStyle.ByTitle.class).countAll()))
2238+
.isEqualTo(0L);
2239+
assertThat(db.tx(() -> db.table(BookProjectionsOldStyle.ByAuthor.class).countAll()))
2240+
.isEqualTo(0L);
2241+
}
2242+
21912243
/**
21922244
* {@link #parallelTx(boolean, boolean, Consumer)} make two tx.
21932245
* In first - read from table (see consumers - findAll, findId, findRange)

repository-test/src/main/java/tech/ydb/yoj/repository/test/entity/TestEntities.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import tech.ydb.yoj.repository.db.TableDescriptor;
77
import tech.ydb.yoj.repository.test.sample.model.BadlyWrappedEntity;
88
import tech.ydb.yoj.repository.test.sample.model.Book;
9+
import tech.ydb.yoj.repository.test.sample.model.BookProjectionsOldStyle;
910
import tech.ydb.yoj.repository.test.sample.model.Bubble;
1011
import tech.ydb.yoj.repository.test.sample.model.BytePkEntity;
1112
import tech.ydb.yoj.repository.test.sample.model.Complex;
@@ -47,6 +48,7 @@ private TestEntities() {
4748
public static final List<Class<? extends Entity>> ALL = List.of(
4849
Project.class, UniqueProject.class, TypeFreak.class, Complex.class, Referring.class, Primitive.class,
4950
Book.class, Book.ByAuthor.class, Book.ByTitle.class,
51+
BookProjectionsOldStyle.class, BookProjectionsOldStyle.ByAuthor.class, BookProjectionsOldStyle.ByTitle.class,
5052
LogEntry.class, Team.class,
5153
BytePkEntity.class,
5254
EntityWithValidation.class,
Lines changed: 31 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,65 +1,58 @@
11
package tech.ydb.yoj.repository.test.sample.model;
22

3-
import lombok.Value;
3+
import lombok.NonNull;
44
import lombok.With;
55
import tech.ydb.yoj.repository.db.Entity;
6+
import tech.ydb.yoj.repository.db.RecordEntity;
67
import tech.ydb.yoj.repository.db.Table;
8+
import tech.ydb.yoj.repository.db.projection.EntityWithProjections;
9+
import tech.ydb.yoj.repository.db.projection.ProjectionCollection;
710

811
import java.util.List;
9-
import java.util.Optional;
10-
import java.util.stream.Collectors;
11-
import java.util.stream.Stream;
1212

13-
@Value
1413
@With
15-
public class Book implements Entity<Book> {
16-
Id id;
17-
int version;
18-
String title;
19-
List<String> authors;
20-
14+
public record Book(
15+
Id id,
16+
int version,
17+
String title,
18+
List<String> authors
19+
) implements RecordEntity<Book>, EntityWithProjections<Book> {
2120
public Book updateTitle(String title) {
2221
return withTitle(title).withVersion(version + 1);
2322
}
2423

2524
@Override
26-
public List<Entity<?>> createProjections() {
27-
return Stream.concat(
28-
Optional.ofNullable(title).map(t -> new ByTitle(new ByTitle.Id(t, id))).stream(),
29-
authors.stream().map(a -> new ByAuthor(new ByAuthor.Id(a, id)))
30-
).collect(Collectors.toList());
25+
public ProjectionCollection collectProjections() {
26+
return ProjectionCollection.builder()
27+
.addEntityIfNotNull(title, t -> new ByTitle(new ByTitle.Id(t, id)))
28+
.addAllEntities(authors.stream().map(a -> new ByAuthor(new ByAuthor.Id(a, id))))
29+
.build();
3130
}
3231

33-
@Value
34-
public static class Id implements Entity.Id<Book> {
35-
String id;
32+
public record Id(String id) implements Entity.Id<Book> {
3633
}
3734

38-
@Value
39-
public static class ByTitle implements Entity<ByTitle> {
40-
Id id;
35+
public record ByTitle(Id id) implements RecordEntity<ByTitle> {
36+
public record Id(
37+
@NonNull
38+
String title,
4139

42-
@Value
43-
public static class Id implements Entity.Id<ByTitle> {
44-
String title;
45-
Book.Id id;
40+
Book.Id id
41+
) implements Entity.Id<ByTitle> {
4642
}
4743
}
4844

49-
@Value
50-
public static class ByAuthor implements Entity<ByAuthor> {
51-
Id id;
52-
53-
@Value
54-
public static class Id implements Entity.Id<ByAuthor> {
55-
String author;
56-
Book.Id id;
45+
public record ByAuthor(Id id) implements RecordEntity<ByAuthor> {
46+
public record Id(
47+
String author,
48+
Book.Id id
49+
) implements Entity.Id<ByAuthor> {
5750
}
5851
}
5952

60-
@Value
61-
public static class TitleViewId implements Table.ViewId<Book> {
62-
Id id;
63-
String title;
53+
public record TitleViewId(
54+
Id id,
55+
String title
56+
) implements Table.RecordViewId<Book> {
6457
}
6558
}
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
package tech.ydb.yoj.repository.test.sample.model;
2+
3+
import lombok.NonNull;
4+
import lombok.Value;
5+
import lombok.With;
6+
import tech.ydb.yoj.repository.db.Entity;
7+
import tech.ydb.yoj.repository.db.Table;
8+
9+
import java.util.List;
10+
import java.util.Optional;
11+
import java.util.stream.Collectors;
12+
import java.util.stream.Stream;
13+
14+
@Value
15+
@With
16+
public class BookProjectionsOldStyle implements Entity<BookProjectionsOldStyle> {
17+
Id id;
18+
int version;
19+
String title;
20+
List<String> authors;
21+
22+
public BookProjectionsOldStyle updateTitle(String title) {
23+
return withTitle(title).withVersion(version + 1);
24+
}
25+
26+
@Override
27+
public List<Entity<?>> createProjections() {
28+
return Stream.concat(
29+
Optional.ofNullable(title).map(t -> new ByTitle(new ByTitle.Id(t, id))).stream(),
30+
authors.stream().map(a -> new ByAuthor(new ByAuthor.Id(a, id)))
31+
).collect(Collectors.toList());
32+
}
33+
34+
@Value
35+
public static class Id implements Entity.Id<BookProjectionsOldStyle> {
36+
String id;
37+
}
38+
39+
@Value
40+
public static class ByTitle implements Entity<ByTitle> {
41+
Id id;
42+
43+
@Value
44+
public static class Id implements Entity.Id<ByTitle> {
45+
@NonNull
46+
String title;
47+
48+
BookProjectionsOldStyle.Id id;
49+
}
50+
}
51+
52+
@Value
53+
public static class ByAuthor implements Entity<ByAuthor> {
54+
Id id;
55+
56+
@Value
57+
public static class Id implements Entity.Id<ByAuthor> {
58+
String author;
59+
BookProjectionsOldStyle.Id id;
60+
}
61+
}
62+
63+
@Value
64+
public static class TitleViewId implements Table.ViewId<BookProjectionsOldStyle> {
65+
Id id;
66+
String title;
67+
}
68+
}

repository-ydb-v2/src/main/java/tech/ydb/yoj/repository/ydb/YdbRepositoryTransaction.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,7 @@ public void commit() {
155155
throw t;
156156
}
157157
endTransaction("commit", this::doCommit);
158+
options.getRepositoryTransactionListeners().forEach(l -> l.onCommit(this));
158159
}
159160

160161
@Override
@@ -169,6 +170,7 @@ public void rollback() {
169170
log.info("Failed to rollback the transaction", t);
170171
}
171172
});
173+
options.getRepositoryTransactionListeners().forEach(l -> l.onRollback(this));
172174
}
173175

174176
private void doCommit() {
@@ -282,7 +284,7 @@ private static <PARAMS> Params getSdkParams(Statement<PARAMS, ?> statement, PARA
282284
}
283285

284286
private void flushPendingWrites() {
285-
transactionLocal.projectionCache().applyProjectionChanges(this);
287+
options.getRepositoryTransactionListeners().forEach(l -> l.onBeforeFlushWrites(this));
286288
QueriesMerger.create(cache)
287289
.merge(pendingWrites)
288290
.forEach(this::execute);
@@ -480,7 +482,7 @@ public <PARAMS> void pendingExecute(Statement<PARAMS, ?> statement, PARAMS value
480482
YdbRepository.Query<PARAMS> query = new YdbRepository.Query<>(statement, value);
481483
if (options.isImmediateWrites()) {
482484
execute(query);
483-
transactionLocal.projectionCache().applyProjectionChanges(this);
485+
options.getRepositoryTransactionListeners().forEach(l -> l.onImmediateWrite(this));
484486
} else {
485487
pendingWrites.add(query);
486488
}

0 commit comments

Comments
 (0)